1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * dwc3-rtk.c - Realtek DWC3 Specific Glue layer |
4 | * |
5 | * Copyright (C) 2023 Realtek Semiconductor Corporation |
6 | * |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_platform.h> |
14 | #include <linux/suspend.h> |
15 | #include <linux/sys_soc.h> |
16 | #include <linux/usb/otg.h> |
17 | #include <linux/usb/of.h> |
18 | #include <linux/usb/role.h> |
19 | |
20 | #include "core.h" |
21 | |
22 | #define WRAP_CTR_REG 0x0 |
23 | #define DISABLE_MULTI_REQ BIT(1) |
24 | #define DESC_R2W_MULTI_DISABLE BIT(9) |
25 | #define FORCE_PIPE3_PHY_STATUS_TO_0 BIT(13) |
26 | |
27 | #define WRAP_USB2_PHY_UTMI_REG 0x8 |
28 | #define TXHSVM_EN BIT(3) |
29 | |
30 | #define WRAP_PHY_PIPE_REG 0xC |
31 | #define RESET_DISABLE_PIPE3_P0 BIT(0) |
32 | #define CLOCK_ENABLE_FOR_PIPE3_PCLK BIT(1) |
33 | |
34 | #define WRAP_USB_HMAC_CTR0_REG 0x60 |
35 | #define U3PORT_DIS BIT(8) |
36 | |
37 | #define WRAP_USB2_PHY_REG 0x70 |
38 | #define USB2_PHY_EN_PHY_PLL_PORT0 BIT(12) |
39 | #define USB2_PHY_EN_PHY_PLL_PORT1 BIT(13) |
40 | #define USB2_PHY_SWITCH_MASK 0x707 |
41 | #define USB2_PHY_SWITCH_DEVICE 0x0 |
42 | #define USB2_PHY_SWITCH_HOST 0x606 |
43 | |
44 | #define WRAP_APHY_REG 0x128 |
45 | #define USB3_MBIAS_ENABLE BIT(1) |
46 | |
47 | /* pm control */ |
48 | #define WRAP_USB_DBUS_PWR_CTRL_REG 0x160 |
49 | #define USB_DBUS_PWR_CTRL_REG 0x0 |
50 | #define DBUS_PWR_CTRL_EN BIT(0) |
51 | |
52 | struct dwc3_rtk { |
53 | struct device *dev; |
54 | void __iomem *regs; |
55 | size_t regs_size; |
56 | void __iomem *pm_base; |
57 | |
58 | struct dwc3 *dwc; |
59 | |
60 | enum usb_role cur_role; |
61 | struct usb_role_switch *role_switch; |
62 | }; |
63 | |
64 | static void switch_usb2_role(struct dwc3_rtk *rtk, enum usb_role role) |
65 | { |
66 | void __iomem *reg; |
67 | int val; |
68 | |
69 | reg = rtk->regs + WRAP_USB2_PHY_REG; |
70 | val = ~USB2_PHY_SWITCH_MASK & readl(addr: reg); |
71 | |
72 | switch (role) { |
73 | case USB_ROLE_DEVICE: |
74 | writel(USB2_PHY_SWITCH_DEVICE | val, addr: reg); |
75 | break; |
76 | case USB_ROLE_HOST: |
77 | writel(USB2_PHY_SWITCH_HOST | val, addr: reg); |
78 | break; |
79 | default: |
80 | dev_dbg(rtk->dev, "%s: role=%d\n" , __func__, role); |
81 | break; |
82 | } |
83 | } |
84 | |
85 | static void switch_dwc3_role(struct dwc3_rtk *rtk, enum usb_role role) |
86 | { |
87 | if (!rtk->dwc->role_sw) |
88 | return; |
89 | |
90 | usb_role_switch_set_role(sw: rtk->dwc->role_sw, role); |
91 | } |
92 | |
93 | static enum usb_role dwc3_rtk_get_role(struct dwc3_rtk *rtk) |
94 | { |
95 | enum usb_role role; |
96 | |
97 | role = rtk->cur_role; |
98 | |
99 | if (rtk->dwc && rtk->dwc->role_sw) |
100 | role = usb_role_switch_get_role(sw: rtk->dwc->role_sw); |
101 | else |
102 | dev_dbg(rtk->dev, "%s not usb_role_switch role=%d\n" , __func__, role); |
103 | |
104 | return role; |
105 | } |
106 | |
107 | static void dwc3_rtk_set_role(struct dwc3_rtk *rtk, enum usb_role role) |
108 | { |
109 | rtk->cur_role = role; |
110 | |
111 | switch_dwc3_role(rtk, role); |
112 | mdelay(10); |
113 | switch_usb2_role(rtk, role); |
114 | } |
115 | |
116 | #if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) |
117 | static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) |
118 | { |
119 | struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw); |
120 | |
121 | dwc3_rtk_set_role(rtk, role); |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) |
127 | { |
128 | struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw); |
129 | |
130 | return dwc3_rtk_get_role(rtk); |
131 | } |
132 | |
133 | static int dwc3_rtk_setup_role_switch(struct dwc3_rtk *rtk) |
134 | { |
135 | struct usb_role_switch_desc dwc3_role_switch = {NULL}; |
136 | |
137 | dwc3_role_switch.name = dev_name(dev: rtk->dev); |
138 | dwc3_role_switch.driver_data = rtk; |
139 | dwc3_role_switch.allow_userspace_control = true; |
140 | dwc3_role_switch.fwnode = dev_fwnode(rtk->dev); |
141 | dwc3_role_switch.set = dwc3_usb_role_switch_set; |
142 | dwc3_role_switch.get = dwc3_usb_role_switch_get; |
143 | rtk->role_switch = usb_role_switch_register(parent: rtk->dev, desc: &dwc3_role_switch); |
144 | if (IS_ERR(ptr: rtk->role_switch)) |
145 | return PTR_ERR(ptr: rtk->role_switch); |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int dwc3_rtk_remove_role_switch(struct dwc3_rtk *rtk) |
151 | { |
152 | if (rtk->role_switch) |
153 | usb_role_switch_unregister(sw: rtk->role_switch); |
154 | |
155 | rtk->role_switch = NULL; |
156 | |
157 | return 0; |
158 | } |
159 | #else |
160 | #define dwc3_rtk_setup_role_switch(x) 0 |
161 | #define dwc3_rtk_remove_role_switch(x) 0 |
162 | #endif |
163 | |
164 | static const char *const speed_names[] = { |
165 | [USB_SPEED_UNKNOWN] = "UNKNOWN" , |
166 | [USB_SPEED_LOW] = "low-speed" , |
167 | [USB_SPEED_FULL] = "full-speed" , |
168 | [USB_SPEED_HIGH] = "high-speed" , |
169 | [USB_SPEED_WIRELESS] = "wireless" , |
170 | [USB_SPEED_SUPER] = "super-speed" , |
171 | [USB_SPEED_SUPER_PLUS] = "super-speed-plus" , |
172 | }; |
173 | |
174 | static enum usb_device_speed __get_dwc3_maximum_speed(struct device_node *np) |
175 | { |
176 | struct device_node *dwc3_np; |
177 | const char *maximum_speed; |
178 | int ret; |
179 | |
180 | dwc3_np = of_get_compatible_child(parent: np, compatible: "snps,dwc3" ); |
181 | if (!dwc3_np) |
182 | return USB_SPEED_UNKNOWN; |
183 | |
184 | ret = of_property_read_string(np: dwc3_np, propname: "maximum-speed" , out_string: &maximum_speed); |
185 | if (ret < 0) |
186 | goto out; |
187 | |
188 | ret = match_string(array: speed_names, ARRAY_SIZE(speed_names), string: maximum_speed); |
189 | |
190 | out: |
191 | of_node_put(node: dwc3_np); |
192 | |
193 | return (ret < 0) ? USB_SPEED_UNKNOWN : ret; |
194 | } |
195 | |
196 | static int dwc3_rtk_init(struct dwc3_rtk *rtk) |
197 | { |
198 | struct device *dev = rtk->dev; |
199 | void __iomem *reg; |
200 | int val; |
201 | enum usb_device_speed maximum_speed; |
202 | const struct soc_device_attribute rtk_soc_kylin_a00[] = { |
203 | { .family = "Realtek Kylin" , .revision = "A00" , }, |
204 | { /* empty */ } }; |
205 | const struct soc_device_attribute rtk_soc_hercules[] = { |
206 | { .family = "Realtek Hercules" , }, { /* empty */ } }; |
207 | const struct soc_device_attribute rtk_soc_thor[] = { |
208 | { .family = "Realtek Thor" , }, { /* empty */ } }; |
209 | |
210 | if (soc_device_match(matches: rtk_soc_kylin_a00)) { |
211 | reg = rtk->regs + WRAP_CTR_REG; |
212 | val = readl(addr: reg); |
213 | writel(DISABLE_MULTI_REQ | val, addr: reg); |
214 | dev_info(dev, "[bug fixed] 1295/1296 A00: add workaround to disable multiple request for D-Bus" ); |
215 | } |
216 | |
217 | if (soc_device_match(matches: rtk_soc_hercules)) { |
218 | reg = rtk->regs + WRAP_USB2_PHY_REG; |
219 | val = readl(addr: reg); |
220 | writel(USB2_PHY_EN_PHY_PLL_PORT1 | val, addr: reg); |
221 | dev_info(dev, "[bug fixed] 1395 add workaround to disable usb2 port 2 suspend!" ); |
222 | } |
223 | |
224 | reg = rtk->regs + WRAP_USB2_PHY_UTMI_REG; |
225 | val = readl(addr: reg); |
226 | writel(TXHSVM_EN | val, addr: reg); |
227 | |
228 | maximum_speed = __get_dwc3_maximum_speed(np: dev->of_node); |
229 | if (maximum_speed != USB_SPEED_UNKNOWN && maximum_speed <= USB_SPEED_HIGH) { |
230 | if (soc_device_match(matches: rtk_soc_thor)) { |
231 | reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG; |
232 | val = readl(addr: reg); |
233 | writel(U3PORT_DIS | val, addr: reg); |
234 | } else { |
235 | reg = rtk->regs + WRAP_CTR_REG; |
236 | val = readl(addr: reg); |
237 | writel(FORCE_PIPE3_PHY_STATUS_TO_0 | val, addr: reg); |
238 | |
239 | reg = rtk->regs + WRAP_PHY_PIPE_REG; |
240 | val = ~CLOCK_ENABLE_FOR_PIPE3_PCLK & readl(addr: reg); |
241 | writel(RESET_DISABLE_PIPE3_P0 | val, addr: reg); |
242 | |
243 | reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG; |
244 | val = readl(addr: reg); |
245 | writel(U3PORT_DIS | val, addr: reg); |
246 | |
247 | reg = rtk->regs + WRAP_APHY_REG; |
248 | val = readl(addr: reg); |
249 | writel(val: ~USB3_MBIAS_ENABLE & val, addr: reg); |
250 | |
251 | dev_dbg(rtk->dev, "%s: disable usb 3.0 phy\n" , __func__); |
252 | } |
253 | } |
254 | |
255 | reg = rtk->regs + WRAP_CTR_REG; |
256 | val = readl(addr: reg); |
257 | writel(DESC_R2W_MULTI_DISABLE | val, addr: reg); |
258 | |
259 | /* Set phy Dp/Dm initial state to host mode to avoid the Dp glitch */ |
260 | reg = rtk->regs + WRAP_USB2_PHY_REG; |
261 | val = ~USB2_PHY_SWITCH_MASK & readl(addr: reg); |
262 | writel(USB2_PHY_SWITCH_HOST | val, addr: reg); |
263 | |
264 | if (rtk->pm_base) { |
265 | reg = rtk->pm_base + USB_DBUS_PWR_CTRL_REG; |
266 | val = DBUS_PWR_CTRL_EN | readl(addr: reg); |
267 | writel(val, addr: reg); |
268 | } |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | static int dwc3_rtk_probe_dwc3_core(struct dwc3_rtk *rtk) |
274 | { |
275 | struct device *dev = rtk->dev; |
276 | struct device_node *node = dev->of_node; |
277 | struct platform_device *dwc3_pdev; |
278 | struct device *dwc3_dev; |
279 | struct device_node *dwc3_node; |
280 | enum usb_dr_mode dr_mode; |
281 | int ret = 0; |
282 | |
283 | ret = dwc3_rtk_init(rtk); |
284 | if (ret) |
285 | return -EINVAL; |
286 | |
287 | ret = of_platform_populate(root: node, NULL, NULL, parent: dev); |
288 | if (ret) { |
289 | dev_err(dev, "failed to add dwc3 core\n" ); |
290 | return ret; |
291 | } |
292 | |
293 | dwc3_node = of_get_compatible_child(parent: node, compatible: "snps,dwc3" ); |
294 | if (!dwc3_node) { |
295 | dev_err(dev, "failed to find dwc3 core node\n" ); |
296 | ret = -ENODEV; |
297 | goto depopulate; |
298 | } |
299 | |
300 | dwc3_pdev = of_find_device_by_node(np: dwc3_node); |
301 | if (!dwc3_pdev) { |
302 | dev_err(dev, "failed to find dwc3 core platform_device\n" ); |
303 | ret = -ENODEV; |
304 | goto err_node_put; |
305 | } |
306 | |
307 | dwc3_dev = &dwc3_pdev->dev; |
308 | rtk->dwc = platform_get_drvdata(pdev: dwc3_pdev); |
309 | if (!rtk->dwc) { |
310 | dev_err(dev, "failed to find dwc3 core\n" ); |
311 | ret = -ENODEV; |
312 | goto err_pdev_put; |
313 | } |
314 | |
315 | dr_mode = usb_get_dr_mode(dev: dwc3_dev); |
316 | if (dr_mode != rtk->dwc->dr_mode) { |
317 | dev_info(dev, "dts set dr_mode=%d, but dwc3 set dr_mode=%d\n" , |
318 | dr_mode, rtk->dwc->dr_mode); |
319 | dr_mode = rtk->dwc->dr_mode; |
320 | } |
321 | |
322 | switch (dr_mode) { |
323 | case USB_DR_MODE_PERIPHERAL: |
324 | rtk->cur_role = USB_ROLE_DEVICE; |
325 | break; |
326 | case USB_DR_MODE_HOST: |
327 | rtk->cur_role = USB_ROLE_HOST; |
328 | break; |
329 | default: |
330 | dev_dbg(rtk->dev, "%s: dr_mode=%d\n" , __func__, dr_mode); |
331 | break; |
332 | } |
333 | |
334 | if (device_property_read_bool(dev: dwc3_dev, propname: "usb-role-switch" )) { |
335 | ret = dwc3_rtk_setup_role_switch(rtk); |
336 | if (ret) { |
337 | dev_err(dev, "dwc3_rtk_setup_role_switch fail=%d\n" , ret); |
338 | goto err_pdev_put; |
339 | } |
340 | rtk->cur_role = dwc3_rtk_get_role(rtk); |
341 | } |
342 | |
343 | switch_usb2_role(rtk, role: rtk->cur_role); |
344 | |
345 | platform_device_put(pdev: dwc3_pdev); |
346 | of_node_put(node: dwc3_node); |
347 | |
348 | return 0; |
349 | |
350 | err_pdev_put: |
351 | platform_device_put(pdev: dwc3_pdev); |
352 | err_node_put: |
353 | of_node_put(node: dwc3_node); |
354 | depopulate: |
355 | of_platform_depopulate(parent: dev); |
356 | |
357 | return ret; |
358 | } |
359 | |
360 | static int dwc3_rtk_probe(struct platform_device *pdev) |
361 | { |
362 | struct dwc3_rtk *rtk; |
363 | struct device *dev = &pdev->dev; |
364 | struct resource *res; |
365 | void __iomem *regs; |
366 | int ret = 0; |
367 | |
368 | rtk = devm_kzalloc(dev, size: sizeof(*rtk), GFP_KERNEL); |
369 | if (!rtk) { |
370 | ret = -ENOMEM; |
371 | goto out; |
372 | } |
373 | |
374 | platform_set_drvdata(pdev, data: rtk); |
375 | |
376 | rtk->dev = dev; |
377 | |
378 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
379 | if (!res) { |
380 | dev_err(dev, "missing memory resource\n" ); |
381 | ret = -ENODEV; |
382 | goto out; |
383 | } |
384 | |
385 | regs = devm_ioremap_resource(dev, res); |
386 | if (IS_ERR(ptr: regs)) { |
387 | ret = PTR_ERR(ptr: regs); |
388 | goto out; |
389 | } |
390 | |
391 | rtk->regs = regs; |
392 | rtk->regs_size = resource_size(res); |
393 | |
394 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
395 | if (res) { |
396 | rtk->pm_base = devm_ioremap_resource(dev, res); |
397 | if (IS_ERR(ptr: rtk->pm_base)) { |
398 | ret = PTR_ERR(ptr: rtk->pm_base); |
399 | goto out; |
400 | } |
401 | } |
402 | |
403 | ret = dwc3_rtk_probe_dwc3_core(rtk); |
404 | |
405 | out: |
406 | return ret; |
407 | } |
408 | |
409 | static void dwc3_rtk_remove(struct platform_device *pdev) |
410 | { |
411 | struct dwc3_rtk *rtk = platform_get_drvdata(pdev); |
412 | |
413 | rtk->dwc = NULL; |
414 | |
415 | dwc3_rtk_remove_role_switch(rtk); |
416 | |
417 | of_platform_depopulate(parent: rtk->dev); |
418 | } |
419 | |
420 | static void dwc3_rtk_shutdown(struct platform_device *pdev) |
421 | { |
422 | struct dwc3_rtk *rtk = platform_get_drvdata(pdev); |
423 | |
424 | of_platform_depopulate(parent: rtk->dev); |
425 | } |
426 | |
427 | static const struct of_device_id rtk_dwc3_match[] = { |
428 | { .compatible = "realtek,rtd-dwc3" }, |
429 | {}, |
430 | }; |
431 | MODULE_DEVICE_TABLE(of, rtk_dwc3_match); |
432 | |
433 | #ifdef CONFIG_PM_SLEEP |
434 | static int dwc3_rtk_suspend(struct device *dev) |
435 | { |
436 | return 0; |
437 | } |
438 | |
439 | static int dwc3_rtk_resume(struct device *dev) |
440 | { |
441 | struct dwc3_rtk *rtk = dev_get_drvdata(dev); |
442 | |
443 | dwc3_rtk_init(rtk); |
444 | |
445 | switch_usb2_role(rtk, role: rtk->cur_role); |
446 | |
447 | /* runtime set active to reflect active state. */ |
448 | pm_runtime_disable(dev); |
449 | pm_runtime_set_active(dev); |
450 | pm_runtime_enable(dev); |
451 | |
452 | return 0; |
453 | } |
454 | |
455 | static const struct dev_pm_ops dwc3_rtk_dev_pm_ops = { |
456 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_rtk_suspend, dwc3_rtk_resume) |
457 | }; |
458 | |
459 | #define DEV_PM_OPS (&dwc3_rtk_dev_pm_ops) |
460 | #else |
461 | #define DEV_PM_OPS NULL |
462 | #endif /* CONFIG_PM_SLEEP */ |
463 | |
464 | static struct platform_driver dwc3_rtk_driver = { |
465 | .probe = dwc3_rtk_probe, |
466 | .remove_new = dwc3_rtk_remove, |
467 | .driver = { |
468 | .name = "rtk-dwc3" , |
469 | .of_match_table = rtk_dwc3_match, |
470 | .pm = DEV_PM_OPS, |
471 | }, |
472 | .shutdown = dwc3_rtk_shutdown, |
473 | }; |
474 | |
475 | module_platform_driver(dwc3_rtk_driver); |
476 | |
477 | MODULE_AUTHOR("Stanley Chang <stanley_chang@realtek.com>" ); |
478 | MODULE_DESCRIPTION("DesignWare USB3 Realtek Glue Layer" ); |
479 | MODULE_ALIAS("platform:rtk-dwc3" ); |
480 | MODULE_LICENSE("GPL" ); |
481 | MODULE_SOFTDEP("pre: phy_rtk_usb2 phy_rtk_usb3" ); |
482 | |