1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * BCM6328 USBH PHY Controller Driver |
4 | * |
5 | * Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com> |
6 | * Copyright (C) 2015 Simon Arlott |
7 | * |
8 | * Derived from bcm963xx_4.12L.06B_consumer/kernel/linux/arch/mips/bcm963xx/setup.c: |
9 | * Copyright (C) 2002 Broadcom Corporation |
10 | * |
11 | * Derived from OpenWrt patches: |
12 | * Copyright (C) 2013 Jonas Gorski <jonas.gorski@gmail.com> |
13 | * Copyright (C) 2013 Florian Fainelli <f.fainelli@gmail.com> |
14 | * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr> |
15 | */ |
16 | |
17 | #include <linux/clk.h> |
18 | #include <linux/io.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of.h> |
21 | #include <linux/phy/phy.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/reset.h> |
24 | |
25 | /* USBH control register offsets */ |
26 | enum usbh_regs { |
27 | USBH_BRT_CONTROL1 = 0, |
28 | USBH_BRT_CONTROL2, |
29 | USBH_BRT_STATUS1, |
30 | USBH_BRT_STATUS2, |
31 | USBH_UTMI_CONTROL1, |
32 | #define USBH_UC1_DEV_MODE_SEL BIT(0) |
33 | USBH_TEST_PORT_CONTROL, |
34 | USBH_PLL_CONTROL1, |
35 | #define USBH_PLLC_REFCLKSEL_SHIFT 0 |
36 | #define USBH_PLLC_REFCLKSEL_MASK (0x3 << USBH_PLLC_REFCLKSEL_SHIFT) |
37 | #define USBH_PLLC_CLKSEL_SHIFT 2 |
38 | #define USBH_PLLC_CLKSEL_MASK (0x3 << USBH_PLLC_CLKSEL_MASK) |
39 | #define USBH_PLLC_XTAL_PWRDWNB BIT(4) |
40 | #define USBH_PLLC_PLL_PWRDWNB BIT(5) |
41 | #define USBH_PLLC_PLL_CALEN BIT(6) |
42 | #define USBH_PLLC_PHYPLL_BYP BIT(7) |
43 | #define USBH_PLLC_PLL_RESET BIT(8) |
44 | #define USBH_PLLC_PLL_IDDQ_PWRDN BIT(9) |
45 | #define USBH_PLLC_PLL_PWRDN_DELAY BIT(10) |
46 | #define USBH_6318_PLLC_PLL_SUSPEND_EN BIT(27) |
47 | #define USBH_6318_PLLC_PHYPLL_BYP BIT(29) |
48 | #define USBH_6318_PLLC_PLL_RESET BIT(30) |
49 | #define USBH_6318_PLLC_PLL_IDDQ_PWRDN BIT(31) |
50 | USBH_SWAP_CONTROL, |
51 | #define USBH_SC_OHCI_DATA_SWAP BIT(0) |
52 | #define USBH_SC_OHCI_ENDIAN_SWAP BIT(1) |
53 | #define USBH_SC_OHCI_LOGICAL_ADDR_EN BIT(2) |
54 | #define USBH_SC_EHCI_DATA_SWAP BIT(3) |
55 | #define USBH_SC_EHCI_ENDIAN_SWAP BIT(4) |
56 | #define USBH_SC_EHCI_LOGICAL_ADDR_EN BIT(5) |
57 | #define USBH_SC_USB_DEVICE_SEL BIT(6) |
58 | USBH_GENERIC_CONTROL, |
59 | #define USBH_GC_PLL_SUSPEND_EN BIT(1) |
60 | USBH_FRAME_ADJUST_VALUE, |
61 | USBH_SETUP, |
62 | #define USBH_S_IOC BIT(4) |
63 | #define USBH_S_IPP BIT(5) |
64 | USBH_MDIO, |
65 | USBH_MDIO32, |
66 | USBH_USB_SIM_CONTROL, |
67 | #define USBH_USC_LADDR_SEL BIT(5) |
68 | |
69 | __USBH_ENUM_SIZE |
70 | }; |
71 | |
72 | struct bcm63xx_usbh_phy_variant { |
73 | /* Registers */ |
74 | long regs[__USBH_ENUM_SIZE]; |
75 | |
76 | /* PLLC bits to set/clear for power on */ |
77 | u32 power_pllc_clr; |
78 | u32 power_pllc_set; |
79 | |
80 | /* Setup bits to set/clear for power on */ |
81 | u32 setup_clr; |
82 | u32 setup_set; |
83 | |
84 | /* Swap Control bits to set */ |
85 | u32 swapctl_dev_set; |
86 | |
87 | /* Test Port Control value to set if non-zero */ |
88 | u32 tpc_val; |
89 | |
90 | /* USB Sim Control bits to set */ |
91 | u32 usc_set; |
92 | |
93 | /* UTMI Control 1 bits to set */ |
94 | u32 utmictl1_dev_set; |
95 | }; |
96 | |
97 | struct bcm63xx_usbh_phy { |
98 | void __iomem *base; |
99 | struct clk *usbh_clk; |
100 | struct clk *usb_ref_clk; |
101 | struct reset_control *reset; |
102 | const struct bcm63xx_usbh_phy_variant *variant; |
103 | bool device_mode; |
104 | }; |
105 | |
106 | static const struct bcm63xx_usbh_phy_variant usbh_bcm6318 = { |
107 | .regs = { |
108 | [USBH_BRT_CONTROL1] = -1, |
109 | [USBH_BRT_CONTROL2] = -1, |
110 | [USBH_BRT_STATUS1] = -1, |
111 | [USBH_BRT_STATUS2] = -1, |
112 | [USBH_UTMI_CONTROL1] = 0x2c, |
113 | [USBH_TEST_PORT_CONTROL] = 0x1c, |
114 | [USBH_PLL_CONTROL1] = 0x04, |
115 | [USBH_SWAP_CONTROL] = 0x0c, |
116 | [USBH_GENERIC_CONTROL] = -1, |
117 | [USBH_FRAME_ADJUST_VALUE] = 0x08, |
118 | [USBH_SETUP] = 0x00, |
119 | [USBH_MDIO] = 0x14, |
120 | [USBH_MDIO32] = 0x18, |
121 | [USBH_USB_SIM_CONTROL] = 0x20, |
122 | }, |
123 | .power_pllc_clr = USBH_6318_PLLC_PLL_IDDQ_PWRDN, |
124 | .power_pllc_set = USBH_6318_PLLC_PLL_SUSPEND_EN, |
125 | .setup_set = USBH_S_IOC, |
126 | .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL, |
127 | .usc_set = USBH_USC_LADDR_SEL, |
128 | .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL, |
129 | }; |
130 | |
131 | static const struct bcm63xx_usbh_phy_variant usbh_bcm6328 = { |
132 | .regs = { |
133 | [USBH_BRT_CONTROL1] = 0x00, |
134 | [USBH_BRT_CONTROL2] = 0x04, |
135 | [USBH_BRT_STATUS1] = 0x08, |
136 | [USBH_BRT_STATUS2] = 0x0c, |
137 | [USBH_UTMI_CONTROL1] = 0x10, |
138 | [USBH_TEST_PORT_CONTROL] = 0x14, |
139 | [USBH_PLL_CONTROL1] = 0x18, |
140 | [USBH_SWAP_CONTROL] = 0x1c, |
141 | [USBH_GENERIC_CONTROL] = 0x20, |
142 | [USBH_FRAME_ADJUST_VALUE] = 0x24, |
143 | [USBH_SETUP] = 0x28, |
144 | [USBH_MDIO] = 0x2c, |
145 | [USBH_MDIO32] = 0x30, |
146 | [USBH_USB_SIM_CONTROL] = 0x34, |
147 | }, |
148 | .setup_set = USBH_S_IOC, |
149 | .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL, |
150 | .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL, |
151 | }; |
152 | |
153 | static const struct bcm63xx_usbh_phy_variant usbh_bcm6358 = { |
154 | .regs = { |
155 | [USBH_BRT_CONTROL1] = -1, |
156 | [USBH_BRT_CONTROL2] = -1, |
157 | [USBH_BRT_STATUS1] = -1, |
158 | [USBH_BRT_STATUS2] = -1, |
159 | [USBH_UTMI_CONTROL1] = -1, |
160 | [USBH_TEST_PORT_CONTROL] = 0x24, |
161 | [USBH_PLL_CONTROL1] = -1, |
162 | [USBH_SWAP_CONTROL] = 0x00, |
163 | [USBH_GENERIC_CONTROL] = -1, |
164 | [USBH_FRAME_ADJUST_VALUE] = -1, |
165 | [USBH_SETUP] = -1, |
166 | [USBH_MDIO] = -1, |
167 | [USBH_MDIO32] = -1, |
168 | [USBH_USB_SIM_CONTROL] = -1, |
169 | }, |
170 | /* |
171 | * The magic value comes for the original vendor BSP |
172 | * and is needed for USB to work. Datasheet does not |
173 | * help, so the magic value is used as-is. |
174 | */ |
175 | .tpc_val = 0x1c0020, |
176 | }; |
177 | |
178 | static const struct bcm63xx_usbh_phy_variant usbh_bcm6368 = { |
179 | .regs = { |
180 | [USBH_BRT_CONTROL1] = 0x00, |
181 | [USBH_BRT_CONTROL2] = 0x04, |
182 | [USBH_BRT_STATUS1] = 0x08, |
183 | [USBH_BRT_STATUS2] = 0x0c, |
184 | [USBH_UTMI_CONTROL1] = 0x10, |
185 | [USBH_TEST_PORT_CONTROL] = 0x14, |
186 | [USBH_PLL_CONTROL1] = 0x18, |
187 | [USBH_SWAP_CONTROL] = 0x1c, |
188 | [USBH_GENERIC_CONTROL] = -1, |
189 | [USBH_FRAME_ADJUST_VALUE] = 0x24, |
190 | [USBH_SETUP] = 0x28, |
191 | [USBH_MDIO] = 0x2c, |
192 | [USBH_MDIO32] = 0x30, |
193 | [USBH_USB_SIM_CONTROL] = 0x34, |
194 | }, |
195 | .power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY, |
196 | .setup_set = USBH_S_IOC, |
197 | .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL, |
198 | .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL, |
199 | }; |
200 | |
201 | static const struct bcm63xx_usbh_phy_variant usbh_bcm63268 = { |
202 | .regs = { |
203 | [USBH_BRT_CONTROL1] = 0x00, |
204 | [USBH_BRT_CONTROL2] = 0x04, |
205 | [USBH_BRT_STATUS1] = 0x08, |
206 | [USBH_BRT_STATUS2] = 0x0c, |
207 | [USBH_UTMI_CONTROL1] = 0x10, |
208 | [USBH_TEST_PORT_CONTROL] = 0x14, |
209 | [USBH_PLL_CONTROL1] = 0x18, |
210 | [USBH_SWAP_CONTROL] = 0x1c, |
211 | [USBH_GENERIC_CONTROL] = 0x20, |
212 | [USBH_FRAME_ADJUST_VALUE] = 0x24, |
213 | [USBH_SETUP] = 0x28, |
214 | [USBH_MDIO] = 0x2c, |
215 | [USBH_MDIO32] = 0x30, |
216 | [USBH_USB_SIM_CONTROL] = 0x34, |
217 | }, |
218 | .power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY, |
219 | .setup_clr = USBH_S_IPP, |
220 | .setup_set = USBH_S_IOC, |
221 | .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL, |
222 | .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL, |
223 | }; |
224 | |
225 | static inline bool usbh_has_reg(struct bcm63xx_usbh_phy *usbh, int reg) |
226 | { |
227 | return (usbh->variant->regs[reg] >= 0); |
228 | } |
229 | |
230 | static inline u32 usbh_readl(struct bcm63xx_usbh_phy *usbh, int reg) |
231 | { |
232 | return __raw_readl(addr: usbh->base + usbh->variant->regs[reg]); |
233 | } |
234 | |
235 | static inline void usbh_writel(struct bcm63xx_usbh_phy *usbh, int reg, |
236 | u32 value) |
237 | { |
238 | __raw_writel(val: value, addr: usbh->base + usbh->variant->regs[reg]); |
239 | } |
240 | |
241 | static int bcm63xx_usbh_phy_init(struct phy *phy) |
242 | { |
243 | struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy); |
244 | int ret; |
245 | |
246 | ret = clk_prepare_enable(clk: usbh->usbh_clk); |
247 | if (ret) { |
248 | dev_err(&phy->dev, "unable to enable usbh clock: %d\n" , ret); |
249 | return ret; |
250 | } |
251 | |
252 | ret = clk_prepare_enable(clk: usbh->usb_ref_clk); |
253 | if (ret) { |
254 | dev_err(&phy->dev, "unable to enable usb_ref clock: %d\n" , ret); |
255 | clk_disable_unprepare(clk: usbh->usbh_clk); |
256 | return ret; |
257 | } |
258 | |
259 | ret = reset_control_reset(rstc: usbh->reset); |
260 | if (ret) { |
261 | dev_err(&phy->dev, "unable to reset device: %d\n" , ret); |
262 | clk_disable_unprepare(clk: usbh->usb_ref_clk); |
263 | clk_disable_unprepare(clk: usbh->usbh_clk); |
264 | return ret; |
265 | } |
266 | |
267 | /* Configure to work in native CPU endian */ |
268 | if (usbh_has_reg(usbh, reg: USBH_SWAP_CONTROL)) { |
269 | u32 val = usbh_readl(usbh, reg: USBH_SWAP_CONTROL); |
270 | |
271 | val |= USBH_SC_EHCI_DATA_SWAP; |
272 | val &= ~USBH_SC_EHCI_ENDIAN_SWAP; |
273 | |
274 | val |= USBH_SC_OHCI_DATA_SWAP; |
275 | val &= ~USBH_SC_OHCI_ENDIAN_SWAP; |
276 | |
277 | if (usbh->device_mode && usbh->variant->swapctl_dev_set) |
278 | val |= usbh->variant->swapctl_dev_set; |
279 | |
280 | usbh_writel(usbh, reg: USBH_SWAP_CONTROL, value: val); |
281 | } |
282 | |
283 | if (usbh_has_reg(usbh, reg: USBH_SETUP)) { |
284 | u32 val = usbh_readl(usbh, reg: USBH_SETUP); |
285 | |
286 | val |= usbh->variant->setup_set; |
287 | val &= ~usbh->variant->setup_clr; |
288 | |
289 | usbh_writel(usbh, reg: USBH_SETUP, value: val); |
290 | } |
291 | |
292 | if (usbh_has_reg(usbh, reg: USBH_USB_SIM_CONTROL)) { |
293 | u32 val = usbh_readl(usbh, reg: USBH_USB_SIM_CONTROL); |
294 | |
295 | val |= usbh->variant->usc_set; |
296 | |
297 | usbh_writel(usbh, reg: USBH_USB_SIM_CONTROL, value: val); |
298 | } |
299 | |
300 | if (usbh->variant->tpc_val && |
301 | usbh_has_reg(usbh, reg: USBH_TEST_PORT_CONTROL)) |
302 | usbh_writel(usbh, reg: USBH_TEST_PORT_CONTROL, |
303 | value: usbh->variant->tpc_val); |
304 | |
305 | if (usbh->device_mode && |
306 | usbh_has_reg(usbh, reg: USBH_UTMI_CONTROL1) && |
307 | usbh->variant->utmictl1_dev_set) { |
308 | u32 val = usbh_readl(usbh, reg: USBH_UTMI_CONTROL1); |
309 | |
310 | val |= usbh->variant->utmictl1_dev_set; |
311 | |
312 | usbh_writel(usbh, reg: USBH_UTMI_CONTROL1, value: val); |
313 | } |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | static int bcm63xx_usbh_phy_power_on(struct phy *phy) |
319 | { |
320 | struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy); |
321 | |
322 | if (usbh_has_reg(usbh, reg: USBH_PLL_CONTROL1)) { |
323 | u32 val = usbh_readl(usbh, reg: USBH_PLL_CONTROL1); |
324 | |
325 | val |= usbh->variant->power_pllc_set; |
326 | val &= ~usbh->variant->power_pllc_clr; |
327 | |
328 | usbh_writel(usbh, reg: USBH_PLL_CONTROL1, value: val); |
329 | } |
330 | |
331 | return 0; |
332 | } |
333 | |
334 | static int bcm63xx_usbh_phy_power_off(struct phy *phy) |
335 | { |
336 | struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy); |
337 | |
338 | if (usbh_has_reg(usbh, reg: USBH_PLL_CONTROL1)) { |
339 | u32 val = usbh_readl(usbh, reg: USBH_PLL_CONTROL1); |
340 | |
341 | val &= ~usbh->variant->power_pllc_set; |
342 | val |= usbh->variant->power_pllc_clr; |
343 | |
344 | usbh_writel(usbh, reg: USBH_PLL_CONTROL1, value: val); |
345 | } |
346 | |
347 | return 0; |
348 | } |
349 | |
350 | static int bcm63xx_usbh_phy_exit(struct phy *phy) |
351 | { |
352 | struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy); |
353 | |
354 | clk_disable_unprepare(clk: usbh->usbh_clk); |
355 | clk_disable_unprepare(clk: usbh->usb_ref_clk); |
356 | |
357 | return 0; |
358 | } |
359 | |
360 | static const struct phy_ops bcm63xx_usbh_phy_ops = { |
361 | .exit = bcm63xx_usbh_phy_exit, |
362 | .init = bcm63xx_usbh_phy_init, |
363 | .power_off = bcm63xx_usbh_phy_power_off, |
364 | .power_on = bcm63xx_usbh_phy_power_on, |
365 | .owner = THIS_MODULE, |
366 | }; |
367 | |
368 | static struct phy *bcm63xx_usbh_phy_xlate(struct device *dev, |
369 | const struct of_phandle_args *args) |
370 | { |
371 | struct bcm63xx_usbh_phy *usbh = dev_get_drvdata(dev); |
372 | |
373 | usbh->device_mode = !!args->args[0]; |
374 | |
375 | return of_phy_simple_xlate(dev, args); |
376 | } |
377 | |
378 | static int __init bcm63xx_usbh_phy_probe(struct platform_device *pdev) |
379 | { |
380 | struct device *dev = &pdev->dev; |
381 | struct bcm63xx_usbh_phy *usbh; |
382 | const struct bcm63xx_usbh_phy_variant *variant; |
383 | struct phy *phy; |
384 | struct phy_provider *phy_provider; |
385 | |
386 | usbh = devm_kzalloc(dev, size: sizeof(*usbh), GFP_KERNEL); |
387 | if (!usbh) |
388 | return -ENOMEM; |
389 | |
390 | variant = device_get_match_data(dev); |
391 | if (!variant) |
392 | return -EINVAL; |
393 | usbh->variant = variant; |
394 | |
395 | usbh->base = devm_platform_ioremap_resource(pdev, index: 0); |
396 | if (IS_ERR(ptr: usbh->base)) |
397 | return PTR_ERR(ptr: usbh->base); |
398 | |
399 | usbh->reset = devm_reset_control_get_exclusive(dev, NULL); |
400 | if (IS_ERR(ptr: usbh->reset)) { |
401 | if (PTR_ERR(ptr: usbh->reset) != -EPROBE_DEFER) |
402 | dev_err(dev, "failed to get reset\n" ); |
403 | return PTR_ERR(ptr: usbh->reset); |
404 | } |
405 | |
406 | usbh->usbh_clk = devm_clk_get_optional(dev, id: "usbh" ); |
407 | if (IS_ERR(ptr: usbh->usbh_clk)) |
408 | return PTR_ERR(ptr: usbh->usbh_clk); |
409 | |
410 | usbh->usb_ref_clk = devm_clk_get_optional(dev, id: "usb_ref" ); |
411 | if (IS_ERR(ptr: usbh->usb_ref_clk)) |
412 | return PTR_ERR(ptr: usbh->usb_ref_clk); |
413 | |
414 | phy = devm_phy_create(dev, NULL, ops: &bcm63xx_usbh_phy_ops); |
415 | if (IS_ERR(ptr: phy)) { |
416 | dev_err(dev, "failed to create PHY\n" ); |
417 | return PTR_ERR(ptr: phy); |
418 | } |
419 | |
420 | platform_set_drvdata(pdev, data: usbh); |
421 | phy_set_drvdata(phy, data: usbh); |
422 | |
423 | phy_provider = devm_of_phy_provider_register(dev, |
424 | bcm63xx_usbh_phy_xlate); |
425 | if (IS_ERR(ptr: phy_provider)) { |
426 | dev_err(dev, "failed to register PHY provider\n" ); |
427 | return PTR_ERR(ptr: phy_provider); |
428 | } |
429 | |
430 | dev_dbg(dev, "Registered BCM63xx USB PHY driver\n" ); |
431 | |
432 | return 0; |
433 | } |
434 | |
435 | static const struct of_device_id bcm63xx_usbh_phy_ids[] __initconst = { |
436 | { .compatible = "brcm,bcm6318-usbh-phy" , .data = &usbh_bcm6318 }, |
437 | { .compatible = "brcm,bcm6328-usbh-phy" , .data = &usbh_bcm6328 }, |
438 | { .compatible = "brcm,bcm6358-usbh-phy" , .data = &usbh_bcm6358 }, |
439 | { .compatible = "brcm,bcm6362-usbh-phy" , .data = &usbh_bcm6368 }, |
440 | { .compatible = "brcm,bcm6368-usbh-phy" , .data = &usbh_bcm6368 }, |
441 | { .compatible = "brcm,bcm63268-usbh-phy" , .data = &usbh_bcm63268 }, |
442 | { /* sentinel */ } |
443 | }; |
444 | MODULE_DEVICE_TABLE(of, bcm63xx_usbh_phy_ids); |
445 | |
446 | static struct platform_driver bcm63xx_usbh_phy_driver __refdata = { |
447 | .driver = { |
448 | .name = "bcm63xx-usbh-phy" , |
449 | .of_match_table = bcm63xx_usbh_phy_ids, |
450 | }, |
451 | .probe = bcm63xx_usbh_phy_probe, |
452 | }; |
453 | module_platform_driver(bcm63xx_usbh_phy_driver); |
454 | |
455 | MODULE_DESCRIPTION("BCM63xx USBH PHY driver" ); |
456 | MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>" ); |
457 | MODULE_AUTHOR("Simon Arlott" ); |
458 | MODULE_LICENSE("GPL" ); |
459 | |