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_compatible_child(node, "snps,dwc3"); 169 if (!dwc3_np) { 170 err = -ENODEV; 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"); 365