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

usb: dwc2: override PHY input signals with usb role switch support

This patch adds support for usb role switch to dwc2, by using overriding
control of the PHY voltage valid and ID input signals.

iddig signal (ID) can be overridden:
- when setting GUSBCFG_FORCEHOSTMODE, iddig input pin is overridden with 1;
- when setting GUSBCFG_FORCEDEVMODE, iddig input pin is overridden with 0.

avalid/bvalid/vbusvalid signals can be overridden respectively with:
- GOTGCTL_AVALOEN + GOTGCTL_AVALOVAL
- GOTGCTL_BVALOEN + GOTGCTL_BVALOVAL
- GOTGCTL_VBVALEN + GOTGCTL_VBVALOVAL

It is possible to determine valid sessions thanks to usb role switch:
- if USB_ROLE_NONE then !avalid && !bvalid && !vbusvalid
- if USB_ROLE_DEVICE then !avalid && bvalid && vbusvalid
- if USB_ROLE_HOST then avalid && !bvalid && vbusvalid

Acked-by: Minas Harutyunyan <hminas@synopsys.com>
Acked-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>

authored by

Amelie Delaunay and committed by
Felipe Balbi
17f93402 14793fae

+210 -4
+1
drivers/usb/dwc2/Kconfig
··· 5 5 depends on HAS_DMA 6 6 depends on USB || USB_GADGET 7 7 depends on HAS_IOMEM 8 + select USB_ROLE_SWITCH 8 9 help 9 10 Say Y here if your system has a Dual Role Hi-Speed USB 10 11 controller based on the DesignWare HSOTG IP Core.
+1 -1
drivers/usb/dwc2/Makefile
··· 3 3 ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG 4 4 5 5 obj-$(CONFIG_USB_DWC2) += dwc2.o 6 - dwc2-y := core.o core_intr.o platform.o 6 + dwc2-y := core.o core_intr.o platform.o drd.o 7 7 dwc2-y += params.o 8 8 9 9 ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),)
+9
drivers/usb/dwc2/core.h
··· 860 860 * - USB_DR_MODE_PERIPHERAL 861 861 * - USB_DR_MODE_HOST 862 862 * - USB_DR_MODE_OTG 863 + * @role_sw: usb_role_switch handle 863 864 * @hcd_enabled: Host mode sub-driver initialization indicator. 864 865 * @gadget_enabled: Peripheral mode sub-driver initialization indicator. 865 866 * @ll_hw_enabled: Status of low-level hardware resources. ··· 1055 1054 struct dwc2_core_params params; 1056 1055 enum usb_otg_state op_state; 1057 1056 enum usb_dr_mode dr_mode; 1057 + struct usb_role_switch *role_sw; 1058 1058 unsigned int hcd_enabled:1; 1059 1059 unsigned int gadget_enabled:1; 1060 1060 unsigned int ll_hw_enabled:1; ··· 1378 1376 return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; 1379 1377 } 1380 1378 1379 + int dwc2_drd_init(struct dwc2_hsotg *hsotg); 1380 + void dwc2_drd_suspend(struct dwc2_hsotg *hsotg); 1381 + void dwc2_drd_resume(struct dwc2_hsotg *hsotg); 1382 + void dwc2_drd_exit(struct dwc2_hsotg *hsotg); 1383 + 1381 1384 /* 1382 1385 * Dump core registers and SPRAM 1383 1386 */ ··· 1399 1392 int dwc2_gadget_init(struct dwc2_hsotg *hsotg); 1400 1393 void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, 1401 1394 bool reset); 1395 + void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg); 1402 1396 void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); 1403 1397 void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); 1404 1398 int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); ··· 1425 1417 { return 0; } 1426 1418 static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, 1427 1419 bool reset) {} 1420 + static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {} 1428 1421 static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} 1429 1422 static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} 1430 1423 static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg,
+180
drivers/usb/dwc2/drd.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * drd.c - DesignWare USB2 DRD Controller Dual-role support 4 + * 5 + * Copyright (C) 2020 STMicroelectronics 6 + * 7 + * Author(s): Amelie Delaunay <amelie.delaunay@st.com> 8 + */ 9 + 10 + #include <linux/iopoll.h> 11 + #include <linux/platform_device.h> 12 + #include <linux/usb/role.h> 13 + #include "core.h" 14 + 15 + static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) 16 + { 17 + unsigned long flags; 18 + u32 gotgctl; 19 + 20 + spin_lock_irqsave(&hsotg->lock, flags); 21 + 22 + gotgctl = dwc2_readl(hsotg, GOTGCTL); 23 + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN; 24 + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; 25 + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); 26 + dwc2_writel(hsotg, gotgctl, GOTGCTL); 27 + 28 + dwc2_force_mode(hsotg, false); 29 + 30 + spin_unlock_irqrestore(&hsotg->lock, flags); 31 + } 32 + 33 + static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid) 34 + { 35 + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); 36 + 37 + /* Check if A-Session is already in the right state */ 38 + if ((valid && (gotgctl & GOTGCTL_ASESVLD)) || 39 + (!valid && !(gotgctl & GOTGCTL_ASESVLD))) 40 + return -EALREADY; 41 + 42 + if (valid) 43 + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; 44 + else 45 + gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); 46 + dwc2_writel(hsotg, gotgctl, GOTGCTL); 47 + 48 + return 0; 49 + } 50 + 51 + static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid) 52 + { 53 + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); 54 + 55 + /* Check if B-Session is already in the right state */ 56 + if ((valid && (gotgctl & GOTGCTL_BSESVLD)) || 57 + (!valid && !(gotgctl & GOTGCTL_BSESVLD))) 58 + return -EALREADY; 59 + 60 + if (valid) 61 + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; 62 + else 63 + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL); 64 + dwc2_writel(hsotg, gotgctl, GOTGCTL); 65 + 66 + return 0; 67 + } 68 + 69 + static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role) 70 + { 71 + struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw); 72 + unsigned long flags; 73 + int already = 0; 74 + 75 + /* Skip session not in line with dr_mode */ 76 + if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) || 77 + (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) 78 + return -EINVAL; 79 + 80 + #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ 81 + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) 82 + /* Skip session if core is in test mode */ 83 + if (role == USB_ROLE_NONE && hsotg->test_mode) { 84 + dev_dbg(hsotg->dev, "Core is in test mode\n"); 85 + return -EBUSY; 86 + } 87 + #endif 88 + 89 + spin_lock_irqsave(&hsotg->lock, flags); 90 + 91 + if (role == USB_ROLE_HOST) { 92 + already = dwc2_ovr_avalid(hsotg, true); 93 + } else if (role == USB_ROLE_DEVICE) { 94 + already = dwc2_ovr_bvalid(hsotg, true); 95 + /* This clear DCTL.SFTDISCON bit */ 96 + dwc2_hsotg_core_connect(hsotg); 97 + } else { 98 + if (dwc2_is_device_mode(hsotg)) { 99 + if (!dwc2_ovr_bvalid(hsotg, false)) 100 + /* This set DCTL.SFTDISCON bit */ 101 + dwc2_hsotg_core_disconnect(hsotg); 102 + } else { 103 + dwc2_ovr_avalid(hsotg, false); 104 + } 105 + } 106 + 107 + spin_unlock_irqrestore(&hsotg->lock, flags); 108 + 109 + if (!already && hsotg->dr_mode == USB_DR_MODE_OTG) 110 + /* This will raise a Connector ID Status Change Interrupt */ 111 + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); 112 + 113 + dev_dbg(hsotg->dev, "%s-session valid\n", 114 + role == USB_ROLE_NONE ? "No" : 115 + role == USB_ROLE_HOST ? "A" : "B"); 116 + 117 + return 0; 118 + } 119 + 120 + int dwc2_drd_init(struct dwc2_hsotg *hsotg) 121 + { 122 + struct usb_role_switch_desc role_sw_desc = {0}; 123 + struct usb_role_switch *role_sw; 124 + int ret; 125 + 126 + if (!device_property_read_bool(hsotg->dev, "usb-role-switch")) 127 + return 0; 128 + 129 + role_sw_desc.driver_data = hsotg; 130 + role_sw_desc.fwnode = dev_fwnode(hsotg->dev); 131 + role_sw_desc.set = dwc2_drd_role_sw_set; 132 + role_sw_desc.allow_userspace_control = true; 133 + 134 + role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc); 135 + if (IS_ERR(role_sw)) { 136 + ret = PTR_ERR(role_sw); 137 + dev_err(hsotg->dev, 138 + "failed to register role switch: %d\n", ret); 139 + return ret; 140 + } 141 + 142 + hsotg->role_sw = role_sw; 143 + 144 + /* Enable override and initialize values */ 145 + dwc2_ovr_init(hsotg); 146 + 147 + return 0; 148 + } 149 + 150 + void dwc2_drd_suspend(struct dwc2_hsotg *hsotg) 151 + { 152 + u32 gintsts, gintmsk; 153 + 154 + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { 155 + gintmsk = dwc2_readl(hsotg, GINTMSK); 156 + gintmsk &= ~GINTSTS_CONIDSTSCHNG; 157 + dwc2_writel(hsotg, gintmsk, GINTMSK); 158 + gintsts = dwc2_readl(hsotg, GINTSTS); 159 + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); 160 + } 161 + } 162 + 163 + void dwc2_drd_resume(struct dwc2_hsotg *hsotg) 164 + { 165 + u32 gintsts, gintmsk; 166 + 167 + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { 168 + gintsts = dwc2_readl(hsotg, GINTSTS); 169 + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); 170 + gintmsk = dwc2_readl(hsotg, GINTMSK); 171 + gintmsk |= GINTSTS_CONIDSTSCHNG; 172 + dwc2_writel(hsotg, gintmsk, GINTMSK); 173 + } 174 + } 175 + 176 + void dwc2_drd_exit(struct dwc2_hsotg *hsotg) 177 + { 178 + if (hsotg->role_sw) 179 + usb_role_switch_unregister(hsotg->role_sw); 180 + }
+1 -1
drivers/usb/dwc2/gadget.c
··· 3530 3530 dwc2_readl(hsotg, DOEPCTL0)); 3531 3531 } 3532 3532 3533 - static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) 3533 + void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) 3534 3534 { 3535 3535 /* set the soft-disconnect bit */ 3536 3536 dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON);
+18 -2
drivers/usb/dwc2/platform.c
··· 323 323 if (hsotg->gadget_enabled) 324 324 dwc2_hsotg_remove(hsotg); 325 325 326 + dwc2_drd_exit(hsotg); 327 + 326 328 if (hsotg->params.activate_stm_id_vb_detection) 327 329 regulator_disable(hsotg->usb33d); 328 330 ··· 544 542 dwc2_writel(hsotg, ggpio, GGPIO); 545 543 } 546 544 545 + retval = dwc2_drd_init(hsotg); 546 + if (retval) { 547 + if (retval != -EPROBE_DEFER) 548 + dev_err(hsotg->dev, "failed to initialize dual-role\n"); 549 + goto error_init; 550 + } 551 + 547 552 if (hsotg->dr_mode != USB_DR_MODE_HOST) { 548 553 retval = dwc2_gadget_init(hsotg); 549 554 if (retval) 550 - goto error_init; 555 + goto error_drd; 551 556 hsotg->gadget_enabled = 1; 552 557 } 553 558 ··· 580 571 if (retval) { 581 572 if (hsotg->gadget_enabled) 582 573 dwc2_hsotg_remove(hsotg); 583 - goto error_init; 574 + goto error_drd; 584 575 } 585 576 hsotg->hcd_enabled = 1; 586 577 } ··· 612 603 dwc2_debugfs_exit(hsotg); 613 604 if (hsotg->hcd_enabled) 614 605 dwc2_hcd_remove(hsotg); 606 + error_drd: 607 + dwc2_drd_exit(hsotg); 608 + 615 609 error_init: 616 610 if (hsotg->params.activate_stm_id_vb_detection) 617 611 regulator_disable(hsotg->usb33d); ··· 632 620 633 621 if (is_device_mode) 634 622 dwc2_hsotg_suspend(dwc2); 623 + 624 + dwc2_drd_suspend(dwc2); 635 625 636 626 if (dwc2->params.activate_stm_id_vb_detection) { 637 627 unsigned long flags; ··· 714 700 715 701 /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ 716 702 dwc2_force_dr_mode(dwc2); 703 + 704 + dwc2_drd_resume(dwc2); 717 705 718 706 if (dwc2_is_device_mode(dwc2)) 719 707 ret = dwc2_hsotg_resume(dwc2);