Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

usb: dwc3: introduce flatten model driver of i.MX Soc

The i.MX USB glue and DWC3 core are closely coupled. Describe the i.MX
USB block in a single block will bring more benefits than a parent-
child relation. To support the flatten model devicetree, DWC3 USB core
driver already support to directly register and initialize the core in
glue layer using one device. And many notification can be received in
glue layer timely and proper actions can be executed accordingly.

To align with mainstream, introduce a new driver to support flatten dwc3
devicetree model for i.MX Soc. Besides this driver disables wakeup irq
when system is active, no other function change in this version compared
to dwc3-imx8mp.c. After this new driver is settled, only maintenance
fixes will be added to dwc3-imx8mp.c, new features will only be added
to this new driver. Once all users switch to this new one, the legacy
driver will be removed at proper time.

Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Link: https://patch.msgid.link/20260212-add-flatten-dts-based-dwc3-imx-driver-v5-3-ff04a75ce221@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Xu Yang and committed by
Greg Kroah-Hartman
76fc9452 a717321a

+455
+12
drivers/usb/dwc3/Kconfig
··· 150 150 functionality. 151 151 Say 'Y' or 'M' if you have one such device. 152 152 153 + config USB_DWC3_IMX 154 + tristate "NXP iMX Platform" 155 + depends on OF && COMMON_CLK 156 + depends on (ARCH_MXC && ARM64) || COMPILE_TEST 157 + default USB_DWC3 158 + help 159 + NXP iMX SoC use DesignWare Core IP for USB2/3 160 + functionality. 161 + This driver also handles the wakeup feature outside 162 + of DesignWare Core. 163 + Say 'Y' or 'M' if you have one such device. 164 + 153 165 config USB_DWC3_XILINX 154 166 tristate "Xilinx Platforms" 155 167 depends on (ARCH_ZYNQMP || COMPILE_TEST) && OF
+1
drivers/usb/dwc3/Makefile
··· 55 55 obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o 56 56 obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom-legacy.o 57 57 obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o 58 + obj-$(CONFIG_USB_DWC3_IMX) += dwc3-imx.o 58 59 obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o 59 60 obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o 60 61 obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o
+442
drivers/usb/dwc3/dwc3-imx.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * dwc3-imx.c - NXP i.MX Soc USB3 Specific Glue layer 4 + * 5 + * Copyright 2026 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 + #include "glue.h" 19 + 20 + /* USB wakeup registers */ 21 + #define USB_WAKEUP_CTRL 0x00 22 + 23 + /* Global wakeup interrupt enable, also used to clear interrupt */ 24 + #define USB_WAKEUP_EN BIT(31) 25 + /* Wakeup from connect or disconnect, only for superspeed */ 26 + #define USB_WAKEUP_SS_CONN BIT(5) 27 + /* 0 select vbus_valid, 1 select sessvld */ 28 + #define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4) 29 + /* Enable signal for wake up from u3 state */ 30 + #define USB_WAKEUP_U3_EN BIT(3) 31 + /* Enable signal for wake up from id change */ 32 + #define USB_WAKEUP_ID_EN BIT(2) 33 + /* Enable signal for wake up from vbus change */ 34 + #define USB_WAKEUP_VBUS_EN BIT(1) 35 + /* Enable signal for wake up from dp/dm change */ 36 + #define USB_WAKEUP_DPDM_EN BIT(0) 37 + 38 + #define USB_WAKEUP_EN_MASK GENMASK(5, 0) 39 + 40 + /* USB glue registers */ 41 + #define USB_CTRL0 0x00 42 + #define USB_CTRL1 0x04 43 + 44 + #define USB_CTRL0_PORTPWR_EN BIT(12) /* 1 - PPC enabled (default) */ 45 + #define USB_CTRL0_USB3_FIXED BIT(22) /* 1 - USB3 permanent attached */ 46 + #define USB_CTRL0_USB2_FIXED BIT(23) /* 1 - USB2 permanent attached */ 47 + 48 + #define USB_CTRL1_OC_POLARITY BIT(16) /* 0 - HIGH / 1 - LOW */ 49 + #define USB_CTRL1_PWR_POLARITY BIT(17) /* 0 - HIGH / 1 - LOW */ 50 + 51 + struct dwc3_imx { 52 + struct dwc3 dwc; 53 + struct device *dev; 54 + void __iomem *blkctl_base; 55 + void __iomem *glue_base; 56 + struct clk *hsio_clk; 57 + struct clk *suspend_clk; 58 + int irq; 59 + bool pm_suspended; 60 + bool wakeup_pending; 61 + unsigned permanent_attached:1; 62 + unsigned disable_pwr_ctrl:1; 63 + unsigned overcur_active_low:1; 64 + unsigned power_active_low:1; 65 + }; 66 + 67 + #define to_dwc3_imx(d) container_of((d), struct dwc3_imx, dwc) 68 + 69 + static void dwc3_imx_get_property(struct dwc3_imx *dwc_imx) 70 + { 71 + struct device *dev = dwc_imx->dev; 72 + 73 + dwc_imx->permanent_attached = 74 + device_property_read_bool(dev, "fsl,permanently-attached"); 75 + dwc_imx->disable_pwr_ctrl = 76 + device_property_read_bool(dev, "fsl,disable-port-power-control"); 77 + dwc_imx->overcur_active_low = 78 + device_property_read_bool(dev, "fsl,over-current-active-low"); 79 + dwc_imx->power_active_low = 80 + device_property_read_bool(dev, "fsl,power-active-low"); 81 + } 82 + 83 + static void dwc3_imx_configure_glue(struct dwc3_imx *dwc_imx) 84 + { 85 + u32 value; 86 + 87 + if (!dwc_imx->glue_base) 88 + return; 89 + 90 + value = readl(dwc_imx->glue_base + USB_CTRL0); 91 + 92 + if (dwc_imx->permanent_attached) 93 + value |= USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED; 94 + else 95 + value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); 96 + 97 + if (dwc_imx->disable_pwr_ctrl) 98 + value &= ~USB_CTRL0_PORTPWR_EN; 99 + else 100 + value |= USB_CTRL0_PORTPWR_EN; 101 + 102 + writel(value, dwc_imx->glue_base + USB_CTRL0); 103 + 104 + value = readl(dwc_imx->glue_base + USB_CTRL1); 105 + if (dwc_imx->overcur_active_low) 106 + value |= USB_CTRL1_OC_POLARITY; 107 + else 108 + value &= ~USB_CTRL1_OC_POLARITY; 109 + 110 + if (dwc_imx->power_active_low) 111 + value |= USB_CTRL1_PWR_POLARITY; 112 + else 113 + value &= ~USB_CTRL1_PWR_POLARITY; 114 + 115 + writel(value, dwc_imx->glue_base + USB_CTRL1); 116 + } 117 + 118 + static void dwc3_imx_wakeup_enable(struct dwc3_imx *dwc_imx, pm_message_t msg) 119 + { 120 + struct dwc3 *dwc = &dwc_imx->dwc; 121 + u32 val; 122 + 123 + val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL); 124 + 125 + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) { 126 + val |= USB_WAKEUP_EN | USB_WAKEUP_DPDM_EN; 127 + if (PMSG_IS_AUTO(msg)) 128 + val |= USB_WAKEUP_SS_CONN | USB_WAKEUP_U3_EN; 129 + } else { 130 + val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | 131 + USB_WAKEUP_VBUS_SRC_SESS_VAL; 132 + } 133 + 134 + writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL); 135 + } 136 + 137 + static void dwc3_imx_wakeup_disable(struct dwc3_imx *dwc_imx) 138 + { 139 + u32 val; 140 + 141 + val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL); 142 + val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); 143 + writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL); 144 + } 145 + 146 + static irqreturn_t dwc3_imx_interrupt(int irq, void *data) 147 + { 148 + struct dwc3_imx *dwc_imx = data; 149 + struct dwc3 *dwc = &dwc_imx->dwc; 150 + 151 + if (!dwc_imx->pm_suspended) 152 + return IRQ_HANDLED; 153 + 154 + disable_irq_nosync(dwc_imx->irq); 155 + dwc_imx->wakeup_pending = true; 156 + 157 + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) 158 + pm_runtime_resume(&dwc->xhci->dev); 159 + else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) 160 + pm_runtime_get(dwc->dev); 161 + 162 + return IRQ_HANDLED; 163 + } 164 + 165 + static void dwc3_imx_pre_set_role(struct dwc3 *dwc, enum usb_role role) 166 + { 167 + if (role == USB_ROLE_HOST) 168 + /* 169 + * For xhci host, we need disable dwc core auto 170 + * suspend, because during this auto suspend delay(5s), 171 + * xhci host RUN_STOP is cleared and wakeup is not 172 + * enabled, if device is inserted, xhci host can't 173 + * response the connection. 174 + */ 175 + pm_runtime_dont_use_autosuspend(dwc->dev); 176 + else 177 + pm_runtime_use_autosuspend(dwc->dev); 178 + } 179 + 180 + static struct dwc3_glue_ops dwc3_imx_glue_ops = { 181 + .pre_set_role = dwc3_imx_pre_set_role, 182 + }; 183 + 184 + static const struct property_entry dwc3_imx_properties[] = { 185 + PROPERTY_ENTRY_BOOL("xhci-missing-cas-quirk"), 186 + PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"), 187 + {}, 188 + }; 189 + 190 + static const struct software_node dwc3_imx_swnode = { 191 + .properties = dwc3_imx_properties, 192 + }; 193 + 194 + static int dwc3_imx_probe(struct platform_device *pdev) 195 + { 196 + struct device *dev = &pdev->dev; 197 + struct dwc3_imx *dwc_imx; 198 + struct dwc3 *dwc; 199 + struct resource *res; 200 + const char *irq_name; 201 + struct dwc3_probe_data probe_data = {}; 202 + int ret, irq; 203 + 204 + dwc_imx = devm_kzalloc(dev, sizeof(*dwc_imx), GFP_KERNEL); 205 + if (!dwc_imx) 206 + return -ENOMEM; 207 + 208 + platform_set_drvdata(pdev, dwc_imx); 209 + dwc_imx->dev = dev; 210 + 211 + dwc3_imx_get_property(dwc_imx); 212 + 213 + dwc_imx->blkctl_base = devm_platform_ioremap_resource_byname(pdev, "blkctl"); 214 + if (IS_ERR(dwc_imx->blkctl_base)) 215 + return PTR_ERR(dwc_imx->blkctl_base); 216 + 217 + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "glue"); 218 + if (!res) { 219 + dev_warn(dev, "Base address for glue layer missing\n"); 220 + } else { 221 + dwc_imx->glue_base = devm_ioremap_resource(dev, res); 222 + if (IS_ERR(dwc_imx->glue_base)) 223 + return PTR_ERR(dwc_imx->glue_base); 224 + } 225 + 226 + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); 227 + if (!res) 228 + return dev_err_probe(dev, -ENODEV, "missing core memory resource\n"); 229 + 230 + dwc_imx->hsio_clk = devm_clk_get_enabled(dev, "hsio"); 231 + if (IS_ERR(dwc_imx->hsio_clk)) 232 + return dev_err_probe(dev, PTR_ERR(dwc_imx->hsio_clk), 233 + "Failed to get hsio clk\n"); 234 + 235 + dwc_imx->suspend_clk = devm_clk_get_enabled(dev, "suspend"); 236 + if (IS_ERR(dwc_imx->suspend_clk)) 237 + return dev_err_probe(dev, PTR_ERR(dwc_imx->suspend_clk), 238 + "Failed to get suspend clk\n"); 239 + 240 + irq = platform_get_irq_byname(pdev, "wakeup"); 241 + if (irq < 0) 242 + return irq; 243 + dwc_imx->irq = irq; 244 + 245 + irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", dev_name(dev)); 246 + if (!irq_name) 247 + return dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); 248 + 249 + ret = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx_interrupt, 250 + IRQF_ONESHOT | IRQF_NO_AUTOEN, 251 + irq_name, dwc_imx); 252 + if (ret) 253 + return dev_err_probe(dev, ret, "failed to request IRQ #%d\n", irq); 254 + 255 + ret = device_add_software_node(dev, &dwc3_imx_swnode); 256 + if (ret) 257 + return dev_err_probe(dev, ret, "failed to add software node\n"); 258 + 259 + dwc3_imx_configure_glue(dwc_imx); 260 + 261 + dwc = &dwc_imx->dwc; 262 + dwc->dev = dev; 263 + dwc->glue_ops = &dwc3_imx_glue_ops; 264 + 265 + probe_data.res = res; 266 + probe_data.dwc = dwc; 267 + probe_data.properties = DWC3_DEFAULT_PROPERTIES; 268 + probe_data.properties.needs_full_reinit = true; 269 + 270 + ret = dwc3_core_probe(&probe_data); 271 + if (ret) { 272 + device_remove_software_node(dev); 273 + return ret; 274 + } 275 + 276 + device_set_wakeup_capable(dev, true); 277 + return 0; 278 + } 279 + 280 + static void dwc3_imx_remove(struct platform_device *pdev) 281 + { 282 + struct device *dev = &pdev->dev; 283 + struct dwc3 *dwc = dev_get_drvdata(dev); 284 + 285 + dwc3_core_remove(dwc); 286 + device_remove_software_node(dev); 287 + } 288 + 289 + static void dwc3_imx_suspend(struct dwc3_imx *dwc_imx, pm_message_t msg) 290 + { 291 + if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc_imx->dev)) 292 + dwc3_imx_wakeup_enable(dwc_imx, msg); 293 + 294 + enable_irq(dwc_imx->irq); 295 + dwc_imx->pm_suspended = true; 296 + } 297 + 298 + static void dwc3_imx_resume(struct dwc3_imx *dwc_imx, pm_message_t msg) 299 + { 300 + struct dwc3 *dwc = &dwc_imx->dwc; 301 + 302 + dwc_imx->pm_suspended = false; 303 + if (!dwc_imx->wakeup_pending) 304 + disable_irq_nosync(dwc_imx->irq); 305 + 306 + dwc3_imx_wakeup_disable(dwc_imx); 307 + 308 + /* Upon power loss any previous configuration is lost, restore it */ 309 + dwc3_imx_configure_glue(dwc_imx); 310 + 311 + if (dwc_imx->wakeup_pending) { 312 + dwc_imx->wakeup_pending = false; 313 + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) 314 + pm_runtime_put_autosuspend(dwc->dev); 315 + else 316 + /* 317 + * Add wait for xhci switch from suspend 318 + * clock to normal clock to detect connection. 319 + */ 320 + usleep_range(9000, 10000); 321 + } 322 + } 323 + 324 + static int dwc3_imx_runtime_suspend(struct device *dev) 325 + { 326 + struct dwc3 *dwc = dev_get_drvdata(dev); 327 + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); 328 + int ret; 329 + 330 + ret = dwc3_runtime_suspend(dwc); 331 + if (ret) 332 + return ret; 333 + 334 + dwc3_imx_suspend(dwc_imx, PMSG_AUTO_SUSPEND); 335 + return 0; 336 + } 337 + 338 + static int dwc3_imx_runtime_resume(struct device *dev) 339 + { 340 + struct dwc3 *dwc = dev_get_drvdata(dev); 341 + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); 342 + 343 + dwc3_imx_resume(dwc_imx, PMSG_AUTO_RESUME); 344 + return dwc3_runtime_resume(dwc); 345 + } 346 + 347 + static int dwc3_imx_runtime_idle(struct device *dev) 348 + { 349 + return dwc3_runtime_idle(dev_get_drvdata(dev)); 350 + } 351 + 352 + static int dwc3_imx_pm_suspend(struct device *dev) 353 + { 354 + struct dwc3 *dwc = dev_get_drvdata(dev); 355 + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); 356 + int ret; 357 + 358 + ret = dwc3_pm_suspend(dwc); 359 + if (ret) 360 + return ret; 361 + 362 + dwc3_imx_suspend(dwc_imx, PMSG_SUSPEND); 363 + 364 + if (device_may_wakeup(dev)) { 365 + enable_irq_wake(dwc_imx->irq); 366 + device_set_out_band_wakeup(dev); 367 + } else { 368 + clk_disable_unprepare(dwc_imx->suspend_clk); 369 + } 370 + 371 + clk_disable_unprepare(dwc_imx->hsio_clk); 372 + 373 + return 0; 374 + } 375 + 376 + static int dwc3_imx_pm_resume(struct device *dev) 377 + { 378 + struct dwc3 *dwc = dev_get_drvdata(dev); 379 + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); 380 + int ret; 381 + 382 + if (device_may_wakeup(dwc_imx->dev)) { 383 + disable_irq_wake(dwc_imx->irq); 384 + } else { 385 + ret = clk_prepare_enable(dwc_imx->suspend_clk); 386 + if (ret) 387 + return ret; 388 + } 389 + 390 + ret = clk_prepare_enable(dwc_imx->hsio_clk); 391 + if (ret) { 392 + clk_disable_unprepare(dwc_imx->suspend_clk); 393 + return ret; 394 + } 395 + 396 + dwc3_imx_resume(dwc_imx, PMSG_RESUME); 397 + 398 + ret = dwc3_pm_resume(dwc); 399 + if (ret) 400 + return ret; 401 + 402 + return 0; 403 + } 404 + 405 + static void dwc3_imx_complete(struct device *dev) 406 + { 407 + dwc3_pm_complete(dev_get_drvdata(dev)); 408 + } 409 + 410 + static int dwc3_imx_prepare(struct device *dev) 411 + { 412 + return dwc3_pm_prepare(dev_get_drvdata(dev)); 413 + } 414 + 415 + static const struct dev_pm_ops dwc3_imx_dev_pm_ops = { 416 + SYSTEM_SLEEP_PM_OPS(dwc3_imx_pm_suspend, dwc3_imx_pm_resume) 417 + RUNTIME_PM_OPS(dwc3_imx_runtime_suspend, dwc3_imx_runtime_resume, 418 + dwc3_imx_runtime_idle) 419 + .complete = pm_sleep_ptr(dwc3_imx_complete), 420 + .prepare = pm_sleep_ptr(dwc3_imx_prepare), 421 + }; 422 + 423 + static const struct of_device_id dwc3_imx_of_match[] = { 424 + { .compatible = "nxp,imx8mp-dwc3", }, 425 + {}, 426 + }; 427 + MODULE_DEVICE_TABLE(of, dwc3_imx_of_match); 428 + 429 + static struct platform_driver dwc3_imx_driver = { 430 + .probe = dwc3_imx_probe, 431 + .remove = dwc3_imx_remove, 432 + .driver = { 433 + .name = "imx-dwc3", 434 + .pm = pm_ptr(&dwc3_imx_dev_pm_ops), 435 + .of_match_table = dwc3_imx_of_match, 436 + }, 437 + }; 438 + 439 + module_platform_driver(dwc3_imx_driver); 440 + 441 + MODULE_LICENSE("GPL"); 442 + MODULE_DESCRIPTION("DesignWare USB3 i.MX Glue Layer");