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 | ||
9d521071 AS |
39 | /* USB glue registers */ |
40 | #define USB_CTRL0 0x00 | |
41 | #define USB_CTRL1 0x04 | |
42 | ||
43 | #define USB_CTRL0_PORTPWR_EN BIT(12) /* 1 - PPC enabled (default) */ | |
44 | #define USB_CTRL0_USB3_FIXED BIT(22) /* 1 - USB3 permanent attached */ | |
45 | #define USB_CTRL0_USB2_FIXED BIT(23) /* 1 - USB2 permanent attached */ | |
46 | ||
47 | #define USB_CTRL1_OC_POLARITY BIT(16) /* 0 - HIGH / 1 - LOW */ | |
48 | #define USB_CTRL1_PWR_POLARITY BIT(17) /* 0 - HIGH / 1 - LOW */ | |
49 | ||
6dd25659 LJ |
50 | struct dwc3_imx8mp { |
51 | struct device *dev; | |
52 | struct platform_device *dwc3; | |
81915384 | 53 | void __iomem *hsio_blk_base; |
9d521071 | 54 | void __iomem *glue_base; |
6dd25659 LJ |
55 | struct clk *hsio_clk; |
56 | struct clk *suspend_clk; | |
57 | int irq; | |
58 | bool pm_suspended; | |
59 | bool wakeup_pending; | |
60 | }; | |
61 | ||
9d521071 AS |
62 | static void imx8mp_configure_glue(struct dwc3_imx8mp *dwc3_imx) |
63 | { | |
64 | struct device *dev = dwc3_imx->dev; | |
65 | u32 value; | |
66 | ||
67 | if (!dwc3_imx->glue_base) | |
68 | return; | |
69 | ||
70 | value = readl(dwc3_imx->glue_base + USB_CTRL0); | |
71 | ||
72 | if (device_property_read_bool(dev, "fsl,permanently-attached")) | |
73 | value |= (USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); | |
74 | else | |
75 | value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); | |
76 | ||
77 | if (device_property_read_bool(dev, "fsl,disable-port-power-control")) | |
78 | value &= ~(USB_CTRL0_PORTPWR_EN); | |
79 | else | |
80 | value |= USB_CTRL0_PORTPWR_EN; | |
81 | ||
82 | writel(value, dwc3_imx->glue_base + USB_CTRL0); | |
83 | ||
84 | value = readl(dwc3_imx->glue_base + USB_CTRL1); | |
85 | if (device_property_read_bool(dev, "fsl,over-current-active-low")) | |
86 | value |= USB_CTRL1_OC_POLARITY; | |
87 | else | |
88 | value &= ~USB_CTRL1_OC_POLARITY; | |
89 | ||
90 | if (device_property_read_bool(dev, "fsl,power-active-low")) | |
91 | value |= USB_CTRL1_PWR_POLARITY; | |
92 | else | |
93 | value &= ~USB_CTRL1_PWR_POLARITY; | |
94 | ||
95 | writel(value, dwc3_imx->glue_base + USB_CTRL1); | |
96 | } | |
97 | ||
6dd25659 LJ |
98 | static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx) |
99 | { | |
100 | struct dwc3 *dwc3 = platform_get_drvdata(dwc3_imx->dwc3); | |
101 | u32 val; | |
102 | ||
103 | if (!dwc3) | |
104 | return; | |
105 | ||
81915384 | 106 | val = readl(dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); |
6dd25659 LJ |
107 | |
108 | if ((dwc3->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc3->xhci) | |
109 | val |= USB_WAKEUP_EN | USB_WAKEUP_SS_CONN | | |
110 | USB_WAKEUP_U3_EN | USB_WAKEUP_DPDM_EN; | |
111 | else if (dwc3->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) | |
112 | val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | | |
113 | USB_WAKEUP_VBUS_SRC_SESS_VAL; | |
114 | ||
81915384 | 115 | writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); |
6dd25659 LJ |
116 | } |
117 | ||
118 | static void dwc3_imx8mp_wakeup_disable(struct dwc3_imx8mp *dwc3_imx) | |
119 | { | |
120 | u32 val; | |
121 | ||
81915384 | 122 | val = readl(dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); |
6dd25659 | 123 | val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); |
81915384 | 124 | writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); |
6dd25659 LJ |
125 | } |
126 | ||
127 | static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx) | |
128 | { | |
129 | struct dwc3_imx8mp *dwc3_imx = _dwc3_imx; | |
130 | struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); | |
131 | ||
132 | if (!dwc3_imx->pm_suspended) | |
133 | return IRQ_HANDLED; | |
134 | ||
135 | disable_irq_nosync(dwc3_imx->irq); | |
136 | dwc3_imx->wakeup_pending = true; | |
137 | ||
138 | if ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc->xhci) | |
139 | pm_runtime_resume(&dwc->xhci->dev); | |
140 | else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) | |
141 | pm_runtime_get(dwc->dev); | |
142 | ||
143 | return IRQ_HANDLED; | |
144 | } | |
145 | ||
146 | static int dwc3_imx8mp_probe(struct platform_device *pdev) | |
147 | { | |
148 | struct device *dev = &pdev->dev; | |
149 | struct device_node *dwc3_np, *node = dev->of_node; | |
150 | struct dwc3_imx8mp *dwc3_imx; | |
9d521071 | 151 | struct resource *res; |
6dd25659 LJ |
152 | int err, irq; |
153 | ||
154 | if (!node) { | |
155 | dev_err(dev, "device node not found\n"); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
159 | dwc3_imx = devm_kzalloc(dev, sizeof(*dwc3_imx), GFP_KERNEL); | |
160 | if (!dwc3_imx) | |
161 | return -ENOMEM; | |
162 | ||
163 | platform_set_drvdata(pdev, dwc3_imx); | |
164 | ||
165 | dwc3_imx->dev = dev; | |
166 | ||
81915384 AS |
167 | dwc3_imx->hsio_blk_base = devm_platform_ioremap_resource(pdev, 0); |
168 | if (IS_ERR(dwc3_imx->hsio_blk_base)) | |
169 | return PTR_ERR(dwc3_imx->hsio_blk_base); | |
6dd25659 | 170 | |
9d521071 AS |
171 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
172 | if (!res) { | |
173 | dev_warn(dev, "Base address for glue layer missing. Continuing without, some features are missing though."); | |
174 | } else { | |
175 | dwc3_imx->glue_base = devm_ioremap_resource(dev, res); | |
176 | if (IS_ERR(dwc3_imx->glue_base)) | |
177 | return PTR_ERR(dwc3_imx->glue_base); | |
178 | } | |
179 | ||
6dd25659 LJ |
180 | dwc3_imx->hsio_clk = devm_clk_get(dev, "hsio"); |
181 | if (IS_ERR(dwc3_imx->hsio_clk)) { | |
182 | err = PTR_ERR(dwc3_imx->hsio_clk); | |
183 | dev_err(dev, "Failed to get hsio clk, err=%d\n", err); | |
184 | return err; | |
185 | } | |
186 | ||
187 | err = clk_prepare_enable(dwc3_imx->hsio_clk); | |
188 | if (err) { | |
189 | dev_err(dev, "Failed to enable hsio clk, err=%d\n", err); | |
190 | return err; | |
191 | } | |
192 | ||
193 | dwc3_imx->suspend_clk = devm_clk_get(dev, "suspend"); | |
194 | if (IS_ERR(dwc3_imx->suspend_clk)) { | |
195 | err = PTR_ERR(dwc3_imx->suspend_clk); | |
196 | dev_err(dev, "Failed to get suspend clk, err=%d\n", err); | |
197 | goto disable_hsio_clk; | |
198 | } | |
199 | ||
200 | err = clk_prepare_enable(dwc3_imx->suspend_clk); | |
201 | if (err) { | |
202 | dev_err(dev, "Failed to enable suspend clk, err=%d\n", err); | |
203 | goto disable_hsio_clk; | |
204 | } | |
205 | ||
206 | irq = platform_get_irq(pdev, 0); | |
207 | if (irq < 0) { | |
208 | err = irq; | |
209 | goto disable_clks; | |
210 | } | |
211 | dwc3_imx->irq = irq; | |
212 | ||
9d521071 AS |
213 | imx8mp_configure_glue(dwc3_imx); |
214 | ||
6dd25659 LJ |
215 | pm_runtime_set_active(dev); |
216 | pm_runtime_enable(dev); | |
217 | err = pm_runtime_get_sync(dev); | |
218 | if (err < 0) | |
219 | goto disable_rpm; | |
220 | ||
b9699208 | 221 | dwc3_np = of_get_compatible_child(node, "snps,dwc3"); |
6dd25659 | 222 | if (!dwc3_np) { |
0b2b149e | 223 | err = -ENODEV; |
6dd25659 LJ |
224 | dev_err(dev, "failed to find dwc3 core child\n"); |
225 | goto disable_rpm; | |
226 | } | |
227 | ||
228 | err = of_platform_populate(node, NULL, NULL, dev); | |
229 | if (err) { | |
230 | dev_err(&pdev->dev, "failed to create dwc3 core\n"); | |
231 | goto err_node_put; | |
232 | } | |
233 | ||
234 | dwc3_imx->dwc3 = of_find_device_by_node(dwc3_np); | |
235 | if (!dwc3_imx->dwc3) { | |
236 | dev_err(dev, "failed to get dwc3 platform device\n"); | |
237 | err = -ENODEV; | |
238 | goto depopulate; | |
239 | } | |
240 | of_node_put(dwc3_np); | |
241 | ||
6a48d0ae NL |
242 | err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, |
243 | IRQF_ONESHOT, dev_name(dev), dwc3_imx); | |
244 | if (err) { | |
245 | dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); | |
246 | goto depopulate; | |
247 | } | |
248 | ||
6dd25659 LJ |
249 | device_set_wakeup_capable(dev, true); |
250 | pm_runtime_put(dev); | |
251 | ||
252 | return 0; | |
253 | ||
254 | depopulate: | |
255 | of_platform_depopulate(dev); | |
256 | err_node_put: | |
257 | of_node_put(dwc3_np); | |
258 | disable_rpm: | |
259 | pm_runtime_disable(dev); | |
260 | pm_runtime_put_noidle(dev); | |
261 | disable_clks: | |
262 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
263 | disable_hsio_clk: | |
264 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
265 | ||
266 | return err; | |
267 | } | |
268 | ||
269 | static int dwc3_imx8mp_remove(struct platform_device *pdev) | |
270 | { | |
271 | struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev); | |
272 | struct device *dev = &pdev->dev; | |
273 | ||
274 | pm_runtime_get_sync(dev); | |
275 | of_platform_depopulate(dev); | |
276 | ||
277 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
278 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
279 | ||
280 | pm_runtime_disable(dev); | |
281 | pm_runtime_put_noidle(dev); | |
282 | platform_set_drvdata(pdev, NULL); | |
283 | ||
284 | return 0; | |
285 | } | |
286 | ||
287 | static int __maybe_unused dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, | |
288 | pm_message_t msg) | |
289 | { | |
290 | if (dwc3_imx->pm_suspended) | |
291 | return 0; | |
292 | ||
293 | /* Wakeup enable */ | |
294 | if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev)) | |
295 | dwc3_imx8mp_wakeup_enable(dwc3_imx); | |
296 | ||
297 | dwc3_imx->pm_suspended = true; | |
298 | ||
299 | return 0; | |
300 | } | |
301 | ||
302 | static int __maybe_unused dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, | |
303 | pm_message_t msg) | |
304 | { | |
305 | struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); | |
306 | int ret = 0; | |
307 | ||
308 | if (!dwc3_imx->pm_suspended) | |
309 | return 0; | |
310 | ||
311 | /* Wakeup disable */ | |
312 | dwc3_imx8mp_wakeup_disable(dwc3_imx); | |
313 | dwc3_imx->pm_suspended = false; | |
314 | ||
9d521071 AS |
315 | /* Upon power loss any previous configuration is lost, restore it */ |
316 | imx8mp_configure_glue(dwc3_imx); | |
317 | ||
6dd25659 LJ |
318 | if (dwc3_imx->wakeup_pending) { |
319 | dwc3_imx->wakeup_pending = false; | |
320 | if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) { | |
321 | pm_runtime_mark_last_busy(dwc->dev); | |
322 | pm_runtime_put_autosuspend(dwc->dev); | |
323 | } else { | |
324 | /* | |
325 | * Add wait for xhci switch from suspend | |
326 | * clock to normal clock to detect connection. | |
327 | */ | |
328 | usleep_range(9000, 10000); | |
329 | } | |
330 | enable_irq(dwc3_imx->irq); | |
331 | } | |
332 | ||
333 | return ret; | |
334 | } | |
335 | ||
336 | static int __maybe_unused dwc3_imx8mp_pm_suspend(struct device *dev) | |
337 | { | |
338 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
339 | int ret; | |
340 | ||
341 | ret = dwc3_imx8mp_suspend(dwc3_imx, PMSG_SUSPEND); | |
342 | ||
343 | if (device_may_wakeup(dwc3_imx->dev)) | |
344 | enable_irq_wake(dwc3_imx->irq); | |
345 | else | |
346 | clk_disable_unprepare(dwc3_imx->suspend_clk); | |
347 | ||
348 | clk_disable_unprepare(dwc3_imx->hsio_clk); | |
349 | dev_dbg(dev, "dwc3 imx8mp pm suspend.\n"); | |
350 | ||
351 | return ret; | |
352 | } | |
353 | ||
354 | static int __maybe_unused dwc3_imx8mp_pm_resume(struct device *dev) | |
355 | { | |
356 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
357 | int ret; | |
358 | ||
359 | if (device_may_wakeup(dwc3_imx->dev)) { | |
360 | disable_irq_wake(dwc3_imx->irq); | |
361 | } else { | |
362 | ret = clk_prepare_enable(dwc3_imx->suspend_clk); | |
363 | if (ret) | |
364 | return ret; | |
365 | } | |
366 | ||
367 | ret = clk_prepare_enable(dwc3_imx->hsio_clk); | |
368 | if (ret) | |
369 | return ret; | |
370 | ||
371 | ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME); | |
372 | ||
373 | pm_runtime_disable(dev); | |
374 | pm_runtime_set_active(dev); | |
375 | pm_runtime_enable(dev); | |
376 | ||
377 | dev_dbg(dev, "dwc3 imx8mp pm resume.\n"); | |
378 | ||
379 | return ret; | |
380 | } | |
381 | ||
382 | static int __maybe_unused dwc3_imx8mp_runtime_suspend(struct device *dev) | |
383 | { | |
384 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
385 | ||
386 | dev_dbg(dev, "dwc3 imx8mp runtime suspend.\n"); | |
387 | ||
388 | return dwc3_imx8mp_suspend(dwc3_imx, PMSG_AUTO_SUSPEND); | |
389 | } | |
390 | ||
391 | static int __maybe_unused dwc3_imx8mp_runtime_resume(struct device *dev) | |
392 | { | |
393 | struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); | |
394 | ||
395 | dev_dbg(dev, "dwc3 imx8mp runtime resume.\n"); | |
396 | ||
397 | return dwc3_imx8mp_resume(dwc3_imx, PMSG_AUTO_RESUME); | |
398 | } | |
399 | ||
400 | static const struct dev_pm_ops dwc3_imx8mp_dev_pm_ops = { | |
401 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_imx8mp_pm_suspend, dwc3_imx8mp_pm_resume) | |
402 | SET_RUNTIME_PM_OPS(dwc3_imx8mp_runtime_suspend, | |
403 | dwc3_imx8mp_runtime_resume, NULL) | |
404 | }; | |
405 | ||
406 | static const struct of_device_id dwc3_imx8mp_of_match[] = { | |
407 | { .compatible = "fsl,imx8mp-dwc3", }, | |
408 | {}, | |
409 | }; | |
410 | MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match); | |
411 | ||
412 | static struct platform_driver dwc3_imx8mp_driver = { | |
413 | .probe = dwc3_imx8mp_probe, | |
414 | .remove = dwc3_imx8mp_remove, | |
415 | .driver = { | |
416 | .name = "imx8mp-dwc3", | |
417 | .pm = &dwc3_imx8mp_dev_pm_ops, | |
418 | .of_match_table = dwc3_imx8mp_of_match, | |
419 | }, | |
420 | }; | |
421 | ||
422 | module_platform_driver(dwc3_imx8mp_driver); | |
423 | ||
424 | MODULE_ALIAS("platform:imx8mp-dwc3"); | |
425 | MODULE_AUTHOR("jun.li@nxp.com"); | |
426 | MODULE_LICENSE("GPL v2"); | |
427 | MODULE_DESCRIPTION("DesignWare USB3 imx8mp Glue Layer"); |