1 // SPDX-License-Identifier: GPL-2.0 2 /* 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"); 364