]>
Commit | Line | Data |
---|---|---|
e5666281 RM |
1 | /* |
2 | * Broadcom Northstar USB 3.0 PHY Driver | |
3 | * | |
4 | * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl> | |
fff3364a | 5 | * Copyright (C) 2016 Broadcom |
e5666281 RM |
6 | * |
7 | * All magic values used for initialization (and related comments) were obtained | |
8 | * from Broadcom's SDK: | |
9 | * Copyright (c) Broadcom Corp, 2012 | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License version 2 as | |
13 | * published by the Free Software Foundation. | |
14 | */ | |
15 | ||
16 | #include <linux/bcma/bcma.h> | |
17 | #include <linux/delay.h> | |
18 | #include <linux/err.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/of_platform.h> | |
21 | #include <linux/platform_device.h> | |
22 | #include <linux/phy/phy.h> | |
23 | #include <linux/slab.h> | |
24 | ||
25 | #define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ | |
26 | ||
fff3364a RM |
27 | #define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f |
28 | #define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000 | |
29 | #define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040 | |
30 | #define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060 | |
31 | ||
32 | /* Registers of PLL30 block */ | |
33 | #define BCM_NS_USB3_PLL_CONTROL 0x01 | |
34 | #define BCM_NS_USB3_PLLA_CONTROL0 0x0a | |
35 | #define BCM_NS_USB3_PLLA_CONTROL1 0x0b | |
36 | ||
37 | /* Registers of TX PMD block */ | |
38 | #define BCM_NS_USB3_TX_PMD_CONTROL1 0x01 | |
39 | ||
40 | /* Registers of PIPE block */ | |
41 | #define BCM_NS_USB3_LFPS_CMP 0x02 | |
42 | #define BCM_NS_USB3_LFPS_DEGLITCH 0x03 | |
43 | ||
e5666281 RM |
44 | enum bcm_ns_family { |
45 | BCM_NS_UNKNOWN, | |
46 | BCM_NS_AX, | |
47 | BCM_NS_BX, | |
48 | }; | |
49 | ||
50 | struct bcm_ns_usb3 { | |
51 | struct device *dev; | |
52 | enum bcm_ns_family family; | |
53 | void __iomem *dmp; | |
54 | void __iomem *ccb_mii; | |
55 | struct phy *phy; | |
56 | }; | |
57 | ||
58 | static const struct of_device_id bcm_ns_usb3_id_table[] = { | |
59 | { | |
60 | .compatible = "brcm,ns-ax-usb3-phy", | |
61 | .data = (int *)BCM_NS_AX, | |
62 | }, | |
63 | { | |
64 | .compatible = "brcm,ns-bx-usb3-phy", | |
65 | .data = (int *)BCM_NS_BX, | |
66 | }, | |
67 | {}, | |
68 | }; | |
69 | MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); | |
70 | ||
71 | static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, | |
72 | u32 mask, u32 value, unsigned long timeout) | |
73 | { | |
74 | unsigned long deadline = jiffies + timeout; | |
75 | u32 val; | |
76 | ||
77 | do { | |
78 | val = readl(addr); | |
79 | if ((val & mask) == value) | |
80 | return 0; | |
81 | cpu_relax(); | |
82 | udelay(10); | |
83 | } while (!time_after_eq(jiffies, deadline)); | |
84 | ||
85 | dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); | |
86 | ||
87 | return -EBUSY; | |
88 | } | |
89 | ||
90 | static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) | |
91 | { | |
92 | return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, | |
93 | 0x0100, 0x0000, | |
94 | usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); | |
95 | } | |
96 | ||
fff3364a RM |
97 | static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, |
98 | u16 value) | |
e5666281 | 99 | { |
fff3364a | 100 | u32 tmp = 0; |
e5666281 RM |
101 | int err; |
102 | ||
103 | err = bcm_ns_usb3_mii_mng_wait_idle(usb3); | |
104 | if (err < 0) { | |
105 | dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); | |
106 | return err; | |
107 | } | |
108 | ||
fff3364a RM |
109 | /* TODO: Use a proper MDIO bus layer */ |
110 | tmp |= 0x58020000; /* Magic value for MDIO PHY write */ | |
111 | tmp |= reg << 18; | |
112 | tmp |= value; | |
113 | writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); | |
e5666281 RM |
114 | |
115 | return 0; | |
116 | } | |
117 | ||
118 | static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) | |
119 | { | |
120 | int err; | |
121 | ||
122 | /* Enable MDIO. Setting MDCDIV as 26 */ | |
123 | writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); | |
124 | ||
125 | /* Wait for MDIO? */ | |
126 | udelay(2); | |
127 | ||
128 | /* USB3 PLL Block */ | |
fff3364a RM |
129 | err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, |
130 | BCM_NS_USB3_PHY_PLL30_BLOCK); | |
e5666281 RM |
131 | if (err < 0) |
132 | return err; | |
133 | ||
134 | /* Assert Ana_Pllseq start */ | |
fff3364a | 135 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000); |
e5666281 RM |
136 | |
137 | /* Assert CML Divider ratio to 26 */ | |
fff3364a | 138 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); |
e5666281 RM |
139 | |
140 | /* Asserting PLL Reset */ | |
fff3364a | 141 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000); |
e5666281 RM |
142 | |
143 | /* Deaaserting PLL Reset */ | |
fff3364a | 144 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); |
e5666281 RM |
145 | |
146 | /* Waiting MII Mgt interface idle */ | |
147 | bcm_ns_usb3_mii_mng_wait_idle(usb3); | |
148 | ||
149 | /* Deasserting USB3 system reset */ | |
150 | writel(0, usb3->dmp + BCMA_RESET_CTL); | |
151 | ||
152 | /* PLL frequency monitor enable */ | |
fff3364a | 153 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000); |
e5666281 RM |
154 | |
155 | /* PIPE Block */ | |
fff3364a RM |
156 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, |
157 | BCM_NS_USB3_PHY_PIPE_BLOCK); | |
e5666281 RM |
158 | |
159 | /* CMPMAX & CMPMINTH setting */ | |
fff3364a | 160 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d); |
e5666281 RM |
161 | |
162 | /* DEGLITCH MIN & MAX setting */ | |
fff3364a | 163 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302); |
e5666281 RM |
164 | |
165 | /* TXPMD block */ | |
fff3364a RM |
166 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, |
167 | BCM_NS_USB3_PHY_TX_PMD_BLOCK); | |
e5666281 RM |
168 | |
169 | /* Enabling SSC */ | |
fff3364a | 170 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); |
e5666281 RM |
171 | |
172 | /* Waiting MII Mgt interface idle */ | |
173 | bcm_ns_usb3_mii_mng_wait_idle(usb3); | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
178 | static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) | |
179 | { | |
180 | int err; | |
181 | ||
182 | /* Enable MDIO. Setting MDCDIV as 26 */ | |
183 | writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); | |
184 | ||
185 | /* Wait for MDIO? */ | |
186 | udelay(2); | |
187 | ||
188 | /* PLL30 block */ | |
fff3364a RM |
189 | err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, |
190 | BCM_NS_USB3_PHY_PLL30_BLOCK); | |
e5666281 RM |
191 | if (err < 0) |
192 | return err; | |
193 | ||
fff3364a | 194 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); |
e5666281 | 195 | |
fff3364a | 196 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0); |
e5666281 | 197 | |
fff3364a | 198 | bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c); |
e5666281 RM |
199 | |
200 | /* Enable SSC */ | |
fff3364a RM |
201 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, |
202 | BCM_NS_USB3_PHY_TX_PMD_BLOCK); | |
e5666281 | 203 | |
fff3364a | 204 | bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3); |
e5666281 | 205 | |
fff3364a | 206 | bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); |
e5666281 RM |
207 | |
208 | /* Waiting MII Mgt interface idle */ | |
209 | bcm_ns_usb3_mii_mng_wait_idle(usb3); | |
210 | ||
211 | /* Deasserting USB3 system reset */ | |
212 | writel(0, usb3->dmp + BCMA_RESET_CTL); | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | static int bcm_ns_usb3_phy_init(struct phy *phy) | |
218 | { | |
219 | struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy); | |
220 | int err; | |
221 | ||
222 | /* Perform USB3 system soft reset */ | |
223 | writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL); | |
224 | ||
225 | switch (usb3->family) { | |
226 | case BCM_NS_AX: | |
227 | err = bcm_ns_usb3_phy_init_ns_ax(usb3); | |
228 | break; | |
229 | case BCM_NS_BX: | |
230 | err = bcm_ns_usb3_phy_init_ns_bx(usb3); | |
231 | break; | |
232 | default: | |
233 | WARN_ON(1); | |
234 | err = -ENOTSUPP; | |
235 | } | |
236 | ||
237 | return err; | |
238 | } | |
239 | ||
240 | static const struct phy_ops ops = { | |
241 | .init = bcm_ns_usb3_phy_init, | |
242 | .owner = THIS_MODULE, | |
243 | }; | |
244 | ||
245 | static int bcm_ns_usb3_probe(struct platform_device *pdev) | |
246 | { | |
247 | struct device *dev = &pdev->dev; | |
248 | const struct of_device_id *of_id; | |
249 | struct bcm_ns_usb3 *usb3; | |
250 | struct resource *res; | |
251 | struct phy_provider *phy_provider; | |
252 | ||
253 | usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); | |
254 | if (!usb3) | |
255 | return -ENOMEM; | |
256 | ||
257 | usb3->dev = dev; | |
258 | ||
259 | of_id = of_match_device(bcm_ns_usb3_id_table, dev); | |
260 | if (!of_id) | |
261 | return -EINVAL; | |
262 | usb3->family = (enum bcm_ns_family)of_id->data; | |
263 | ||
264 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmp"); | |
265 | usb3->dmp = devm_ioremap_resource(dev, res); | |
266 | if (IS_ERR(usb3->dmp)) { | |
267 | dev_err(dev, "Failed to map DMP regs\n"); | |
268 | return PTR_ERR(usb3->dmp); | |
269 | } | |
270 | ||
271 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ccb-mii"); | |
272 | usb3->ccb_mii = devm_ioremap_resource(dev, res); | |
273 | if (IS_ERR(usb3->ccb_mii)) { | |
274 | dev_err(dev, "Failed to map ChipCommon B MII regs\n"); | |
275 | return PTR_ERR(usb3->ccb_mii); | |
276 | } | |
277 | ||
278 | usb3->phy = devm_phy_create(dev, NULL, &ops); | |
279 | if (IS_ERR(usb3->phy)) { | |
280 | dev_err(dev, "Failed to create PHY\n"); | |
281 | return PTR_ERR(usb3->phy); | |
282 | } | |
283 | ||
284 | phy_set_drvdata(usb3->phy, usb3); | |
285 | platform_set_drvdata(pdev, usb3); | |
286 | ||
287 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
288 | if (!IS_ERR(phy_provider)) | |
289 | dev_info(dev, "Registered Broadcom Northstar USB 3.0 PHY driver\n"); | |
290 | ||
291 | return PTR_ERR_OR_ZERO(phy_provider); | |
292 | } | |
293 | ||
294 | static struct platform_driver bcm_ns_usb3_driver = { | |
295 | .probe = bcm_ns_usb3_probe, | |
296 | .driver = { | |
297 | .name = "bcm_ns_usb3", | |
298 | .of_match_table = bcm_ns_usb3_id_table, | |
299 | }, | |
300 | }; | |
301 | module_platform_driver(bcm_ns_usb3_driver); | |
302 | ||
303 | MODULE_LICENSE("GPL v2"); |