![Hal Emmerich](/assets/img/avatar_default.png)
Add backported patched from 5.x from usb related fixes Up kernel version to most recent lts
181 lines
6.5 KiB
Diff
181 lines
6.5 KiB
Diff
From c40cf7705e13d288d900e044c0a2f756e9e4909a Mon Sep 17 00:00:00 2001
|
|
From: Douglas Anderson <dianders@chromium.org>
|
|
Date: Tue, 16 Apr 2019 14:53:49 -0700
|
|
Subject: [PATCH 32/53] usb: dwc2: optionally assert phy reset when waking up
|
|
|
|
On the rk3288 USB host-only port (the one that's not the OTG-enabled
|
|
port) the PHY can get into a bad state when a wakeup is asserted (not
|
|
just a wakeup from full system suspend but also a wakeup from
|
|
autosuspend).
|
|
|
|
We can get the PHY out of its bad state by asserting its "port reset",
|
|
but unfortunately that seems to assert a reset onto the USB bus so it
|
|
could confuse things if we don't actually deenumerate / reenumerate the
|
|
device.
|
|
|
|
We can also get the PHY out of its bad state by fully resetting it using
|
|
the reset from the CRU (clock reset unit), which does a more full
|
|
reset. The CRU-based reset appears to actually cause devices on the bus
|
|
to be removed and reinserted, which fixes the problem (albeit in a hacky
|
|
way).
|
|
|
|
It's unfortunate that we need to do a full re-enumeration of devices at
|
|
wakeup time, but this is better than alternative of letting the bus get
|
|
wedged.
|
|
|
|
Signed-off-by: Douglas Anderson <dianders@chromium.org>
|
|
Signed-off-by: Yunzhi Li <lyz@rock-chips.com>
|
|
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
|
---
|
|
drivers/usb/dwc2/core.h | 8 ++++++++
|
|
drivers/usb/dwc2/core_intr.c | 12 ++++++++++++
|
|
drivers/usb/dwc2/hcd.c | 18 +++++++++++++++---
|
|
drivers/usb/dwc2/platform.c | 9 +++++++++
|
|
4 files changed, 44 insertions(+), 3 deletions(-)
|
|
|
|
diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
|
|
index 30bab8463c96..764c78ebee28 100644
|
|
--- a/drivers/usb/dwc2/core.h
|
|
+++ b/drivers/usb/dwc2/core.h
|
|
@@ -859,6 +859,8 @@ struct dwc2_hregs_backup {
|
|
* @gadget_enabled: Peripheral mode sub-driver initialization indicator.
|
|
* @ll_hw_enabled: Status of low-level hardware resources.
|
|
* @hibernated: True if core is hibernated
|
|
+ * @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a
|
|
+ * remote wakeup.
|
|
* @frame_number: Frame number read from the core. For both device
|
|
* and host modes. The value ranges are from 0
|
|
* to HFNUM_MAX_FRNUM.
|
|
@@ -972,6 +974,7 @@ struct dwc2_hregs_backup {
|
|
* @status_buf_dma: DMA address for status_buf
|
|
* @start_work: Delayed work for handling host A-cable connection
|
|
* @reset_work: Delayed work for handling a port reset
|
|
+ * @phy_reset_work: Work structure for doing a PHY reset
|
|
* @otg_port: OTG port number
|
|
* @frame_list: Frame list
|
|
* @frame_list_dma: Frame list DMA address
|
|
@@ -1045,6 +1048,7 @@ struct dwc2_hsotg {
|
|
unsigned int gadget_enabled:1;
|
|
unsigned int ll_hw_enabled:1;
|
|
unsigned int hibernated:1;
|
|
+ unsigned int reset_phy_on_wake:1;
|
|
u16 frame_number;
|
|
|
|
struct phy *phy;
|
|
@@ -1147,6 +1151,7 @@ struct dwc2_hsotg {
|
|
|
|
struct delayed_work start_work;
|
|
struct delayed_work reset_work;
|
|
+ struct work_struct phy_reset_work;
|
|
u8 otg_port;
|
|
u32 *frame_list;
|
|
dma_addr_t frame_list_dma;
|
|
@@ -1431,6 +1436,8 @@ int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg);
|
|
int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg);
|
|
int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg,
|
|
int rem_wakeup, int reset);
|
|
+static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg)
|
|
+{ schedule_work(&hsotg->phy_reset_work); }
|
|
#else
|
|
static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg)
|
|
{ return 0; }
|
|
@@ -1454,6 +1461,7 @@ static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg)
|
|
static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg,
|
|
int rem_wakeup, int reset)
|
|
{ return 0; }
|
|
+static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {}
|
|
|
|
#endif
|
|
|
|
diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c
|
|
index 19ae2595f1c3..6af6add3d4c0 100644
|
|
--- a/drivers/usb/dwc2/core_intr.c
|
|
+++ b/drivers/usb/dwc2/core_intr.c
|
|
@@ -435,6 +435,18 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg)
|
|
/* Restart the Phy Clock */
|
|
pcgcctl &= ~PCGCTL_STOPPCLK;
|
|
dwc2_writel(hsotg, pcgcctl, PCGCTL);
|
|
+
|
|
+ /*
|
|
+ * If we've got this quirk then the PHY is stuck upon
|
|
+ * wakeup. Assert reset. This will propagate out and
|
|
+ * eventually we'll re-enumerate the device. Not great
|
|
+ * but the best we can do. We can't call phy_reset()
|
|
+ * at interrupt time but there's no hurry, so we'll
|
|
+ * schedule it for later.
|
|
+ */
|
|
+ if (hsotg->reset_phy_on_wake)
|
|
+ dwc2_host_schedule_phy_reset(hsotg);
|
|
+
|
|
mod_timer(&hsotg->wkp_timer,
|
|
jiffies + msecs_to_jiffies(71));
|
|
} else {
|
|
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
|
|
index 8667ddf3ca74..978232a9e4a8 100644
|
|
--- a/drivers/usb/dwc2/hcd.c
|
|
+++ b/drivers/usb/dwc2/hcd.c
|
|
@@ -4376,6 +4376,17 @@ static void dwc2_hcd_reset_func(struct work_struct *work)
|
|
spin_unlock_irqrestore(&hsotg->lock, flags);
|
|
}
|
|
|
|
+static void dwc2_hcd_phy_reset_func(struct work_struct *work)
|
|
+{
|
|
+ struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg,
|
|
+ phy_reset_work);
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_reset(hsotg->phy);
|
|
+ if (ret)
|
|
+ dev_warn(hsotg->dev, "PHY reset failed\n");
|
|
+}
|
|
+
|
|
/*
|
|
* =========================================================================
|
|
* Linux HC Driver Functions
|
|
@@ -5152,6 +5163,8 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg)
|
|
destroy_workqueue(hsotg->wq_otg);
|
|
}
|
|
|
|
+ cancel_work_sync(&hsotg->phy_reset_work);
|
|
+
|
|
del_timer(&hsotg->wkp_timer);
|
|
}
|
|
|
|
@@ -5293,11 +5306,10 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)
|
|
hsotg->hc_ptr_array[i] = channel;
|
|
}
|
|
|
|
- /* Initialize hsotg start work */
|
|
+ /* Initialize work */
|
|
INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func);
|
|
-
|
|
- /* Initialize port reset work */
|
|
INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func);
|
|
+ INIT_WORK(&hsotg->phy_reset_work, dwc2_hcd_phy_reset_func);
|
|
|
|
/*
|
|
* Allocate space for storing data on status transactions. Normally no
|
|
diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c
|
|
index 9aa9682a5cd2..c01fa8ffc0c8 100644
|
|
--- a/drivers/usb/dwc2/platform.c
|
|
+++ b/drivers/usb/dwc2/platform.c
|
|
@@ -481,6 +481,15 @@ static int dwc2_driver_probe(struct platform_device *dev)
|
|
hsotg->gadget_enabled = 1;
|
|
}
|
|
|
|
+ hsotg->reset_phy_on_wake =
|
|
+ of_property_read_bool(dev->dev.of_node,
|
|
+ "snps,reset-phy-on-wake");
|
|
+ if (hsotg->reset_phy_on_wake && !hsotg->phy) {
|
|
+ dev_warn(hsotg->dev,
|
|
+ "Quirk reset-phy-on-wake only supports generic PHYs\n");
|
|
+ hsotg->reset_phy_on_wake = false;
|
|
+ }
|
|
+
|
|
if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
|
|
retval = dwc2_hcd_init(hsotg);
|
|
if (retval) {
|
|
--
|
|
2.11.0
|
|
|