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 | ||
168 | dwc3_np = of_get_child_by_name(node, "dwc3"); | |
169 | if (!dwc3_np) { | |
170 | dev_err(dev, "failed to find dwc3 core child\n"); | |
171 | goto disable_rpm; | |
172 | } | |
173 | ||
174 | err = of_platform_populate(node, NULL, NULL, dev); | |
175 | if (err) { | |
176 | dev_err(&pdev->dev, "failed to create dwc3 core\n"); | |
177 | goto err_node_put; | |
178 | } | |
179 | ||
180 | dwc3_imx->dwc3 = of_find_device_by_node(dwc3_np); | |
181 | if (!dwc3_imx->dwc3) { | |
182 | dev_err(dev, "failed to get dwc3 platform device\n"); | |
183 | err = -ENODEV; | |
184 | goto depopulate; | |
185 | } | |
186 | of_node_put(dwc3_np); | |
187 | ||
188 | device_set_wakeup_capable(dev, true); | |
189 | pm_runtime_put(dev); | |
190 | ||
191 | return 0; | |
192 | ||
193 | depopulate: | |
194 | of_platform_depopulate(dev); | |
195 | err_node_put: | |
196 | of_node_put(dwc3_np); | |
197 | disable_rpm: | |
198 | pm_runtime_disable(dev); | |
199 | pm_runtime_put_noidle(dev); | |
200 | disable_clks: | |
201 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
202 | disable_hsio_clk: | |
203 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
204 | ||
205 | return err; | |
206 | } | |
207 | ||
208 | static int dwc3_imx8mp_remove(struct platform_device *pdev) | |
209 | { | |
210 | struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev); | |
211 | struct device *dev = &pdev->dev; | |
212 | ||
213 | pm_runtime_get_sync(dev); | |
214 | of_platform_depopulate(dev); | |
215 | ||
216 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
217 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
218 | ||
219 | pm_runtime_disable(dev); | |
220 | pm_runtime_put_noidle(dev); | |
221 | platform_set_drvdata(pdev, NULL); | |
222 | ||
223 | return 0; | |
224 | } | |
225 | ||
226 | static int __maybe_unused dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, | |
227 | pm_message_t msg) | |
228 | { | |
229 | if (dwc3_imx->pm_suspended) | |
230 | return 0; | |
231 | ||
232 | /* Wakeup enable */ | |
233 | if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev)) | |
234 | dwc3_imx8mp_wakeup_enable(dwc3_imx); | |
235 | ||
236 | dwc3_imx->pm_suspended = true; | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
241 | static int __maybe_unused dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, | |
242 | pm_message_t msg) | |
243 | { | |
244 | struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); | |
245 | int ret = 0; | |
246 | ||
247 | if (!dwc3_imx->pm_suspended) | |
248 | return 0; | |
249 | ||
250 | /* Wakeup disable */ | |
251 | dwc3_imx8mp_wakeup_disable(dwc3_imx); | |
252 | dwc3_imx->pm_suspended = false; | |
253 | ||
254 | if (dwc3_imx->wakeup_pending) { | |
255 | dwc3_imx->wakeup_pending = false; | |
256 | if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) { | |
257 | pm_runtime_mark_last_busy(dwc->dev); | |
258 | pm_runtime_put_autosuspend(dwc->dev); | |
259 | } else { | |
260 | /* | |
261 | * Add wait for xhci switch from suspend | |
262 | * clock to normal clock to detect connection. | |
263 | */ | |
264 | usleep_range(9000, 10000); | |
265 | } | |
266 | enable_irq(dwc3_imx->irq); | |
267 | } | |
268 | ||
269 | return ret; | |
270 | } | |
271 | ||
272 | static int __maybe_unused dwc3_imx8mp_pm_suspend(struct device *dev) | |
273 | { | |
274 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
275 | int ret; | |
276 | ||
277 | ret = dwc3_imx8mp_suspend(dwc3_imx, PMSG_SUSPEND); | |
278 | ||
279 | if (device_may_wakeup(dwc3_imx->dev)) | |
280 | enable_irq_wake(dwc3_imx->irq); | |
281 | else | |
282 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
283 | ||
284 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
285 | dev_dbg(dev, "dwc3 imx8mp pm suspend.\n"); | |
286 | ||
287 | return ret; | |
288 | } | |
289 | ||
290 | static int __maybe_unused dwc3_imx8mp_pm_resume(struct device *dev) | |
291 | { | |
292 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
293 | int ret; | |
294 | ||
295 | if (device_may_wakeup(dwc3_imx->dev)) { | |
296 | disable_irq_wake(dwc3_imx->irq); | |
297 | } else { | |
298 | ret = clk_prepare_enable(dwc3_imx->suspend_clk); | |
299 | if (ret) | |
300 | return ret; | |
301 | } | |
302 | ||
303 | ret = clk_prepare_enable(dwc3_imx->hsio_clk); | |
304 | if (ret) | |
305 | return ret; | |
306 | ||
307 | ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME); | |
308 | ||
309 | pm_runtime_disable(dev); | |
310 | pm_runtime_set_active(dev); | |
311 | pm_runtime_enable(dev); | |
312 | ||
313 | dev_dbg(dev, "dwc3 imx8mp pm resume.\n"); | |
314 | ||
315 | return ret; | |
316 | } | |
317 | ||
318 | static int __maybe_unused dwc3_imx8mp_runtime_suspend(struct device *dev) | |
319 | { | |
320 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
321 | ||
322 | dev_dbg(dev, "dwc3 imx8mp runtime suspend.\n"); | |
323 | ||
324 | return dwc3_imx8mp_suspend(dwc3_imx, PMSG_AUTO_SUSPEND); | |
325 | } | |
326 | ||
327 | static int __maybe_unused dwc3_imx8mp_runtime_resume(struct device *dev) | |
328 | { | |
329 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
330 | ||
331 | dev_dbg(dev, "dwc3 imx8mp runtime resume.\n"); | |
332 | ||
333 | return dwc3_imx8mp_resume(dwc3_imx, PMSG_AUTO_RESUME); | |
334 | } | |
335 | ||
336 | static const struct dev_pm_ops dwc3_imx8mp_dev_pm_ops = { | |
337 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_imx8mp_pm_suspend, dwc3_imx8mp_pm_resume) | |
338 | SET_RUNTIME_PM_OPS(dwc3_imx8mp_runtime_suspend, | |
339 | dwc3_imx8mp_runtime_resume, NULL) | |
340 | }; | |
341 | ||
342 | static const struct of_device_id dwc3_imx8mp_of_match[] = { | |
343 | { .compatible = "fsl,imx8mp-dwc3", }, | |
344 | {}, | |
345 | }; | |
346 | MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match); | |
347 | ||
348 | static struct platform_driver dwc3_imx8mp_driver = { | |
349 | .probe = dwc3_imx8mp_probe, | |
350 | .remove = dwc3_imx8mp_remove, | |
351 | .driver = { | |
352 | .name = "imx8mp-dwc3", | |
353 | .pm = &dwc3_imx8mp_dev_pm_ops, | |
354 | .of_match_table = dwc3_imx8mp_of_match, | |
355 | }, | |
356 | }; | |
357 | ||
358 | module_platform_driver(dwc3_imx8mp_driver); | |
359 | ||
360 | MODULE_ALIAS("platform:imx8mp-dwc3"); | |
361 | MODULE_AUTHOR("jun.li@nxp.com"); | |
362 | MODULE_LICENSE("GPL v2"); | |
363 | MODULE_DESCRIPTION("DesignWare USB3 imx8mp Glue Layer"); |