From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Mon, 17 Mar 2025 10:57:59 +0100 Subject: phy: rockchip: naneng: Add fallback for old DTs See https://lore.kernel.org/lkml/20250106070000.605284-1-amadeus@jmu.edu.cn/ Signed-off-by: Ondrej Jirman --- drivers/usb/dwc3/core.c | 46 +++++++++- drivers/usb/dwc3/core.h | 12 +++ drivers/usb/dwc3/drd.c | 34 ++++--- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 111111111111..222222222222 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -152,7 +152,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy) } reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); - reg |= DWC3_GCTL_PRTCAPDIR(mode); + reg |= DWC3_GCTL_PRTCAPDIR(mode & DWC3_GCTL_PRTCAP_OTG); dwc3_writel(dwc->regs, DWC3_GCTL, reg); dwc->current_dr_role = mode; @@ -191,6 +191,7 @@ static void __dwc3_set_mode(struct work_struct *work) dwc3_host_exit(dwc); break; case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: dwc3_gadget_exit(dwc); dwc3_event_buffers_cleanup(dwc); break; @@ -210,12 +211,43 @@ static void __dwc3_set_mode(struct work_struct *work) * Only perform GCTL.CoreSoftReset when there's DRD role switching. */ if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) || - DWC3_VER_IS_PRIOR(DWC31, 190A)) && + DWC3_VER_IS_PRIOR(DWC31, 190A) || dwc->usb3_phy_reset_quirk) && desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) { + /* + * RK3399 TypeC PHY needs to be powered off and powered on again + * for it to apply the correct Type-C plug orientation setting + * and reconfigure itself. + * + * For that purpose we observe complete USB disconnect via + * extcon in drd.c and pass it to __dwc3_set_mode as + * desired_dr_role == 0. + * + * We thus handle transitions between three states of + * desired_dr_role here: + * + * - DWC3_GCTL_PRTCAP_HOST + * - DWC3_GCTL_PRTCAP_DEVICE + * - DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED - almost equivalent to + * DWC3_GCTL_PRTCAP_DEVICE, present only to distinguish + * disconnected state, and so that set_mode is called when + * user plugs in the device to the host. + */ + if (dwc->usb3_phy_powered && dwc->usb3_phy_reset_quirk) + for (int j = 0; j < dwc->num_usb3_ports; j++) + phy_power_off(dwc->usb3_generic_phy[j]); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg |= DWC3_GCTL_CORESOFTRESET; dwc3_writel(dwc->regs, DWC3_GCTL, reg); + if (dwc->usb3_phy_reset_quirk) { + for (int j = 0; j < dwc->num_usb3_ports; j++) { + ret = phy_power_on(dwc->usb3_generic_phy[j]); + //XXX: bleh + dwc->usb3_phy_powered = ret >= 0; + } + } + /* * Wait for internal clocks to synchronized. DWC_usb31 and * DWC_usb32 may need at least 50ms (less for DWC_usb3). To @@ -257,6 +289,7 @@ static void __dwc3_set_mode(struct work_struct *work) } break; case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: dwc3_core_soft_reset(dwc); dwc3_event_buffers_setup(dwc); @@ -1844,6 +1877,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->dis_split_quirk = device_property_read_bool(dev, "snps,dis-split-quirk"); + dwc->usb3_phy_reset_quirk = device_property_read_bool(dev, + "snps,usb3-phy-reset-quirk"); dwc->lpm_nyet_threshold = lpm_nyet_threshold; dwc->tx_de_emphasis = tx_de_emphasis; @@ -2439,6 +2474,7 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: if (pm_runtime_suspended(dwc->dev)) break; ret = dwc3_gadget_suspend(dwc); @@ -2503,11 +2539,12 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: ret = dwc3_core_init_for_resume(dwc); if (ret) return ret; - dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE, true); + dwc3_set_prtcap(dwc, dwc->current_dr_role, true); dwc3_gadget_resume(dwc); break; case DWC3_GCTL_PRTCAP_HOST: @@ -2571,6 +2608,7 @@ static int dwc3_runtime_checks(struct dwc3 *dwc) { switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: if (dwc->connected) return -EBUSY; break; @@ -2609,6 +2647,7 @@ int dwc3_runtime_resume(struct dwc3 *dwc) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: if (dwc->pending_events) { pm_runtime_put(dev); dwc->pending_events = false; @@ -2633,6 +2672,7 @@ int dwc3_runtime_idle(struct dwc3 *dwc) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED: if (dwc3_runtime_checks(dwc)) return -EBUSY; break; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 111111111111..222222222222 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -264,6 +264,12 @@ #define DWC3_GCTL_PRTCAP_HOST 1 #define DWC3_GCTL_PRTCAP_DEVICE 2 #define DWC3_GCTL_PRTCAP_OTG 3 +/* This is not a real register value, but a special state used for + * current_dr_role to mean DWC3_GCTL_PRTCAP_DEVICE in disconnected + * state. Value is chosen so that masking with register width + * produces DWC3_GCTL_PRTCAP_DEVICE value. + */ +#define DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED 6 #define DWC3_GCTL_CORESOFTRESET BIT(11) #define DWC3_GCTL_SOFITPSYNC BIT(10) @@ -1152,6 +1158,10 @@ struct dwc3_scratchpad_array { * @sys_wakeup: set if the device may do system wakeup. * @wakeup_configured: set if the device is configured for remote wakeup. * @suspended: set to track suspend event due to U3/L2. + * @usb3_phy_reset_quirk: set to power cycle the USB3 PHY during mode + * changes. Useful on RK3399 that needs this + * to apply Type-C orientation changes in + * Type-C phy driver. * @susphy_state: state of DWC3_GUSB2PHYCFG_SUSPHY + DWC3_GUSB3PIPECTL_SUSPHY * before PM suspend. * @imod_interval: set the interrupt moderation interval in 250ns @@ -1392,6 +1402,8 @@ struct dwc3 { unsigned suspended:1; unsigned susphy_state:1; + unsigned usb3_phy_reset_quirk:1; + u16 imod_interval; int max_cfg_eps; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 111111111111..222222222222 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -417,15 +417,28 @@ void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) static void dwc3_drd_update(struct dwc3 *dwc) { - int id; + u32 mode = DWC3_GCTL_PRTCAP_DEVICE_DISCONNECTED; + int ret; if (dwc->edev) { - id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); - if (id < 0) - id = 0; - dwc3_set_mode(dwc, id ? - DWC3_GCTL_PRTCAP_HOST : - DWC3_GCTL_PRTCAP_DEVICE); + ret = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (ret > 0) + mode = DWC3_GCTL_PRTCAP_HOST; + + if (dwc->usb3_phy_reset_quirk) { + /* + * With this quirk enabled, we want to pass 0 + * to dwc3_set_mode to signal no USB connection + * state. + */ + ret = extcon_get_state(dwc->edev, EXTCON_USB); + if (ret > 0) + mode = DWC3_GCTL_PRTCAP_DEVICE; + } else { + mode = DWC3_GCTL_PRTCAP_DEVICE; + } + + dwc3_set_mode(dwc, mode); } } @@ -434,9 +447,7 @@ static int dwc3_drd_notifier(struct notifier_block *nb, { struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); - dwc3_set_mode(dwc, event ? - DWC3_GCTL_PRTCAP_HOST : - DWC3_GCTL_PRTCAP_DEVICE); + dwc3_drd_update(dwc); return NOTIFY_DONE; } @@ -547,8 +558,7 @@ int dwc3_drd_init(struct dwc3 *dwc) if (dwc->edev) { dwc->edev_nb.notifier_call = dwc3_drd_notifier; - ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, - &dwc->edev_nb); + ret = extcon_register_notifier_all(dwc->edev, &dwc->edev_nb); if (ret < 0) { dev_err(dwc->dev, "couldn't register cable notifier\n"); return ret; -- Armbian