Commit | Line | Data |
---|---|---|
6dd25659 | 1 | // SPDX-License-Identifier: GPL-2.0 |
ed577c32 | 2 | /* |
6dd25659 LJ |
3 | * dwc3-imx8mp.c - NXP imx8mp Specific Glue layer |
4 | * | |
5 | * Copyright (c) 2020 NXP. | |
6 | */ | |
7 | ||
8 | #include <linux/clk.h> | |
9 | #include <linux/interrupt.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/of_platform.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/pm_runtime.h> | |
16 | ||
17 | #include "core.h" | |
18 | ||
19 | /* USB wakeup registers */ | |
20 | #define USB_WAKEUP_CTRL 0x00 | |
21 | ||
22 | /* Global wakeup interrupt enable, also used to clear interrupt */ | |
23 | #define USB_WAKEUP_EN BIT(31) | |
24 | /* Wakeup from connect or disconnect, only for superspeed */ | |
25 | #define USB_WAKEUP_SS_CONN BIT(5) | |
26 | /* 0 select vbus_valid, 1 select sessvld */ | |
27 | #define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4) | |
28 | /* Enable signal for wake up from u3 state */ | |
29 | #define USB_WAKEUP_U3_EN BIT(3) | |
30 | /* Enable signal for wake up from id change */ | |
31 | #define USB_WAKEUP_ID_EN BIT(2) | |
32 | /* Enable signal for wake up from vbus change */ | |
33 | #define USB_WAKEUP_VBUS_EN BIT(1) | |
34 | /* Enable signal for wake up from dp/dm change */ | |
35 | #define USB_WAKEUP_DPDM_EN BIT(0) | |
36 | ||
37 | #define USB_WAKEUP_EN_MASK GENMASK(5, 0) | |
38 | ||
39 | struct dwc3_imx8mp { | |
40 | struct device *dev; | |
41 | struct platform_device *dwc3; | |
42 | void __iomem *glue_base; | |
43 | struct clk *hsio_clk; | |
44 | struct clk *suspend_clk; | |
45 | int irq; | |
46 | bool pm_suspended; | |
47 | bool wakeup_pending; | |
48 | }; | |
49 | ||
50 | static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx) | |
51 | { | |
52 | struct dwc3 *dwc3 = platform_get_drvdata(dwc3_imx->dwc3); | |
53 | u32 val; | |
54 | ||
55 | if (!dwc3) | |
56 | return; | |
57 | ||
58 | val = readl(dwc3_imx->glue_base + USB_WAKEUP_CTRL); | |
59 | ||
60 | if ((dwc3->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc3->xhci) | |
61 | val |= USB_WAKEUP_EN | USB_WAKEUP_SS_CONN | | |
62 | USB_WAKEUP_U3_EN | USB_WAKEUP_DPDM_EN; | |
63 | else if (dwc3->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) | |
64 | val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | | |
65 | USB_WAKEUP_VBUS_SRC_SESS_VAL; | |
66 | ||
67 | writel(val, dwc3_imx->glue_base + USB_WAKEUP_CTRL); | |
68 | } | |
69 | ||
70 | static void dwc3_imx8mp_wakeup_disable(struct dwc3_imx8mp *dwc3_imx) | |
71 | { | |
72 | u32 val; | |
73 | ||
74 | val = readl(dwc3_imx->glue_base + USB_WAKEUP_CTRL); | |
75 | val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); | |
76 | writel(val, dwc3_imx->glue_base + USB_WAKEUP_CTRL); | |
77 | } | |
78 | ||
79 | static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx) | |
80 | { | |
81 | struct dwc3_imx8mp *dwc3_imx = _dwc3_imx; | |
82 | struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); | |
83 | ||
84 | if (!dwc3_imx->pm_suspended) | |
85 | return IRQ_HANDLED; | |
86 | ||
87 | disable_irq_nosync(dwc3_imx->irq); | |
88 | dwc3_imx->wakeup_pending = true; | |
89 | ||
90 | if ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc->xhci) | |
91 | pm_runtime_resume(&dwc->xhci->dev); | |
92 | else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) | |
93 | pm_runtime_get(dwc->dev); | |
94 | ||
95 | return IRQ_HANDLED; | |
96 | } | |
97 | ||
98 | static int dwc3_imx8mp_probe(struct platform_device *pdev) | |
99 | { | |
100 | struct device *dev = &pdev->dev; | |
101 | struct device_node *dwc3_np, *node = dev->of_node; | |
102 | struct dwc3_imx8mp *dwc3_imx; | |
103 | int err, irq; | |
104 | ||
105 | if (!node) { | |
106 | dev_err(dev, "device node not found\n"); | |
107 | return -EINVAL; | |
108 | } | |
109 | ||
110 | dwc3_imx = devm_kzalloc(dev, sizeof(*dwc3_imx), GFP_KERNEL); | |
111 | if (!dwc3_imx) | |
112 | return -ENOMEM; | |
113 | ||
114 | platform_set_drvdata(pdev, dwc3_imx); | |
115 | ||
116 | dwc3_imx->dev = dev; | |
117 | ||
118 | dwc3_imx->glue_base = devm_platform_ioremap_resource(pdev, 0); | |
119 | if (IS_ERR(dwc3_imx->glue_base)) | |
120 | return PTR_ERR(dwc3_imx->glue_base); | |
121 | ||
122 | dwc3_imx->hsio_clk = devm_clk_get(dev, "hsio"); | |
123 | if (IS_ERR(dwc3_imx->hsio_clk)) { | |
124 | err = PTR_ERR(dwc3_imx->hsio_clk); | |
125 | dev_err(dev, "Failed to get hsio clk, err=%d\n", err); | |
126 | return err; | |
127 | } | |
128 | ||
129 | err = clk_prepare_enable(dwc3_imx->hsio_clk); | |
130 | if (err) { | |
131 | dev_err(dev, "Failed to enable hsio clk, err=%d\n", err); | |
132 | return err; | |
133 | } | |
134 | ||
135 | dwc3_imx->suspend_clk = devm_clk_get(dev, "suspend"); | |
136 | if (IS_ERR(dwc3_imx->suspend_clk)) { | |
137 | err = PTR_ERR(dwc3_imx->suspend_clk); | |
138 | dev_err(dev, "Failed to get suspend clk, err=%d\n", err); | |
139 | goto disable_hsio_clk; | |
140 | } | |
141 | ||
142 | err = clk_prepare_enable(dwc3_imx->suspend_clk); | |
143 | if (err) { | |
144 | dev_err(dev, "Failed to enable suspend clk, err=%d\n", err); | |
145 | goto disable_hsio_clk; | |
146 | } | |
147 | ||
148 | irq = platform_get_irq(pdev, 0); | |
149 | if (irq < 0) { | |
150 | err = irq; | |
151 | goto disable_clks; | |
152 | } | |
153 | dwc3_imx->irq = irq; | |
154 | ||
155 | err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, | |
156 | IRQF_ONESHOT, dev_name(dev), dwc3_imx); | |
157 | if (err) { | |
158 | dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); | |
159 | goto disable_clks; | |
160 | } | |
161 | ||
162 | pm_runtime_set_active(dev); | |
163 | pm_runtime_enable(dev); | |
164 | err = pm_runtime_get_sync(dev); | |
165 | if (err < 0) | |
166 | goto disable_rpm; | |
167 | ||
b9699208 | 168 | dwc3_np = of_get_compatible_child(node, "snps,dwc3"); |
6dd25659 | 169 | if (!dwc3_np) { |
0b2b149e | 170 | err = -ENODEV; |
6dd25659 LJ |
171 | dev_err(dev, "failed to find dwc3 core child\n"); |
172 | goto disable_rpm; | |
173 | } | |
174 | ||
175 | err = of_platform_populate(node, NULL, NULL, dev); | |
176 | if (err) { | |
177 | dev_err(&pdev->dev, "failed to create dwc3 core\n"); | |
178 | goto err_node_put; | |
179 | } | |
180 | ||
181 | dwc3_imx->dwc3 = of_find_device_by_node(dwc3_np); | |
182 | if (!dwc3_imx->dwc3) { | |
183 | dev_err(dev, "failed to get dwc3 platform device\n"); | |
184 | err = -ENODEV; | |
185 | goto depopulate; | |
186 | } | |
187 | of_node_put(dwc3_np); | |
188 | ||
189 | device_set_wakeup_capable(dev, true); | |
190 | pm_runtime_put(dev); | |
191 | ||
192 | return 0; | |
193 | ||
194 | depopulate: | |
195 | of_platform_depopulate(dev); | |
196 | err_node_put: | |
197 | of_node_put(dwc3_np); | |
198 | disable_rpm: | |
199 | pm_runtime_disable(dev); | |
200 | pm_runtime_put_noidle(dev); | |
201 | disable_clks: | |
202 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
203 | disable_hsio_clk: | |
204 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
205 | ||
206 | return err; | |
207 | } | |
208 | ||
209 | static int dwc3_imx8mp_remove(struct platform_device *pdev) | |
210 | { | |
211 | struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev); | |
212 | struct device *dev = &pdev->dev; | |
213 | ||
214 | pm_runtime_get_sync(dev); | |
215 | of_platform_depopulate(dev); | |
216 | ||
217 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
218 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
219 | ||
220 | pm_runtime_disable(dev); | |
221 | pm_runtime_put_noidle(dev); | |
222 | platform_set_drvdata(pdev, NULL); | |
223 | ||
224 | return 0; | |
225 | } | |
226 | ||
227 | static int __maybe_unused dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, | |
228 | pm_message_t msg) | |
229 | { | |
230 | if (dwc3_imx->pm_suspended) | |
231 | return 0; | |
232 | ||
233 | /* Wakeup enable */ | |
234 | if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev)) | |
235 | dwc3_imx8mp_wakeup_enable(dwc3_imx); | |
236 | ||
237 | dwc3_imx->pm_suspended = true; | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | static int __maybe_unused dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, | |
243 | pm_message_t msg) | |
244 | { | |
245 | struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); | |
246 | int ret = 0; | |
247 | ||
248 | if (!dwc3_imx->pm_suspended) | |
249 | return 0; | |
250 | ||
251 | /* Wakeup disable */ | |
252 | dwc3_imx8mp_wakeup_disable(dwc3_imx); | |
253 | dwc3_imx->pm_suspended = false; | |
254 | ||
255 | if (dwc3_imx->wakeup_pending) { | |
256 | dwc3_imx->wakeup_pending = false; | |
257 | if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) { | |
258 | pm_runtime_mark_last_busy(dwc->dev); | |
259 | pm_runtime_put_autosuspend(dwc->dev); | |
260 | } else { | |
261 | /* | |
262 | * Add wait for xhci switch from suspend | |
263 | * clock to normal clock to detect connection. | |
264 | */ | |
265 | usleep_range(9000, 10000); | |
266 | } | |
267 | enable_irq(dwc3_imx->irq); | |
268 | } | |
269 | ||
270 | return ret; | |
271 | } | |
272 | ||
273 | static int __maybe_unused dwc3_imx8mp_pm_suspend(struct device *dev) | |
274 | { | |
275 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
276 | int ret; | |
277 | ||
278 | ret = dwc3_imx8mp_suspend(dwc3_imx, PMSG_SUSPEND); | |
279 | ||
280 | if (device_may_wakeup(dwc3_imx->dev)) | |
281 | enable_irq_wake(dwc3_imx->irq); | |
282 | else | |
283 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
284 | ||
285 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
286 | dev_dbg(dev, "dwc3 imx8mp pm suspend.\n"); | |
287 | ||
288 | return ret; | |
289 | } | |
290 | ||
291 | static int __maybe_unused dwc3_imx8mp_pm_resume(struct device *dev) | |
292 | { | |
293 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
294 | int ret; | |
295 | ||
296 | if (device_may_wakeup(dwc3_imx->dev)) { | |
297 | disable_irq_wake(dwc3_imx->irq); | |
298 | } else { | |
299 | ret = clk_prepare_enable(dwc3_imx->suspend_clk); | |
300 | if (ret) | |
301 | return ret; | |
302 | } | |
303 | ||
304 | ret = clk_prepare_enable(dwc3_imx->hsio_clk); | |
305 | if (ret) | |
306 | return ret; | |
307 | ||
308 | ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME); | |
309 | ||
310 | pm_runtime_disable(dev); | |
311 | pm_runtime_set_active(dev); | |
312 | pm_runtime_enable(dev); | |
313 | ||
314 | dev_dbg(dev, "dwc3 imx8mp pm resume.\n"); | |
315 | ||
316 | return ret; | |
317 | } | |
318 | ||
319 | static int __maybe_unused dwc3_imx8mp_runtime_suspend(struct device *dev) | |
320 | { | |
321 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
322 | ||
323 | dev_dbg(dev, "dwc3 imx8mp runtime suspend.\n"); | |
324 | ||
325 | return dwc3_imx8mp_suspend(dwc3_imx, PMSG_AUTO_SUSPEND); | |
326 | } | |
327 | ||
328 | static int __maybe_unused dwc3_imx8mp_runtime_resume(struct device *dev) | |
329 | { | |
330 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
331 | ||
332 | dev_dbg(dev, "dwc3 imx8mp runtime resume.\n"); | |
333 | ||
334 | return dwc3_imx8mp_resume(dwc3_imx, PMSG_AUTO_RESUME); | |
335 | } | |
336 | ||
337 | static const struct dev_pm_ops dwc3_imx8mp_dev_pm_ops = { | |
338 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_imx8mp_pm_suspend, dwc3_imx8mp_pm_resume) | |
339 | SET_RUNTIME_PM_OPS(dwc3_imx8mp_runtime_suspend, | |
340 | dwc3_imx8mp_runtime_resume, NULL) | |
341 | }; | |
342 | ||
343 | static const struct of_device_id dwc3_imx8mp_of_match[] = { | |
344 | { .compatible = "fsl,imx8mp-dwc3", }, | |
345 | {}, | |
346 | }; | |
347 | MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match); | |
348 | ||
349 | static struct platform_driver dwc3_imx8mp_driver = { | |
350 | .probe = dwc3_imx8mp_probe, | |
351 | .remove = dwc3_imx8mp_remove, | |
352 | .driver = { | |
353 | .name = "imx8mp-dwc3", | |
354 | .pm = &dwc3_imx8mp_dev_pm_ops, | |
355 | .of_match_table = dwc3_imx8mp_of_match, | |
356 | }, | |
357 | }; | |
358 | ||
359 | module_platform_driver(dwc3_imx8mp_driver); | |
360 | ||
361 | MODULE_ALIAS("platform:imx8mp-dwc3"); | |
362 | MODULE_AUTHOR("jun.li@nxp.com"); | |
363 | MODULE_LICENSE("GPL v2"); | |
364 | MODULE_DESCRIPTION("DesignWare USB3 imx8mp Glue Layer"); |