diff options
author | Douglas Anderson <dianders@chromium.org> | 2019-04-16 17:53:49 -0400 |
---|---|---|
committer | Felipe Balbi <felipe.balbi@linux.intel.com> | 2019-05-03 02:13:47 -0400 |
commit | c40cf7705e13d288d900e044c0a2f756e9e4909a (patch) | |
tree | 31ab9133f03c69cda3587744766a8ce59a72eddb | |
parent | cc389eaabd7082a14e46aaa5a02f87c9eef37d7f (diff) |
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>
-rw-r--r-- | drivers/usb/dwc2/core.h | 8 | ||||
-rw-r--r-- | drivers/usb/dwc2/core_intr.c | 12 | ||||
-rw-r--r-- | drivers/usb/dwc2/hcd.c | 18 | ||||
-rw-r--r-- | 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 { | |||
859 | * @gadget_enabled: Peripheral mode sub-driver initialization indicator. | 859 | * @gadget_enabled: Peripheral mode sub-driver initialization indicator. |
860 | * @ll_hw_enabled: Status of low-level hardware resources. | 860 | * @ll_hw_enabled: Status of low-level hardware resources. |
861 | * @hibernated: True if core is hibernated | 861 | * @hibernated: True if core is hibernated |
862 | * @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a | ||
863 | * remote wakeup. | ||
862 | * @frame_number: Frame number read from the core. For both device | 864 | * @frame_number: Frame number read from the core. For both device |
863 | * and host modes. The value ranges are from 0 | 865 | * and host modes. The value ranges are from 0 |
864 | * to HFNUM_MAX_FRNUM. | 866 | * to HFNUM_MAX_FRNUM. |
@@ -972,6 +974,7 @@ struct dwc2_hregs_backup { | |||
972 | * @status_buf_dma: DMA address for status_buf | 974 | * @status_buf_dma: DMA address for status_buf |
973 | * @start_work: Delayed work for handling host A-cable connection | 975 | * @start_work: Delayed work for handling host A-cable connection |
974 | * @reset_work: Delayed work for handling a port reset | 976 | * @reset_work: Delayed work for handling a port reset |
977 | * @phy_reset_work: Work structure for doing a PHY reset | ||
975 | * @otg_port: OTG port number | 978 | * @otg_port: OTG port number |
976 | * @frame_list: Frame list | 979 | * @frame_list: Frame list |
977 | * @frame_list_dma: Frame list DMA address | 980 | * @frame_list_dma: Frame list DMA address |
@@ -1045,6 +1048,7 @@ struct dwc2_hsotg { | |||
1045 | unsigned int gadget_enabled:1; | 1048 | unsigned int gadget_enabled:1; |
1046 | unsigned int ll_hw_enabled:1; | 1049 | unsigned int ll_hw_enabled:1; |
1047 | unsigned int hibernated:1; | 1050 | unsigned int hibernated:1; |
1051 | unsigned int reset_phy_on_wake:1; | ||
1048 | u16 frame_number; | 1052 | u16 frame_number; |
1049 | 1053 | ||
1050 | struct phy *phy; | 1054 | struct phy *phy; |
@@ -1147,6 +1151,7 @@ struct dwc2_hsotg { | |||
1147 | 1151 | ||
1148 | struct delayed_work start_work; | 1152 | struct delayed_work start_work; |
1149 | struct delayed_work reset_work; | 1153 | struct delayed_work reset_work; |
1154 | struct work_struct phy_reset_work; | ||
1150 | u8 otg_port; | 1155 | u8 otg_port; |
1151 | u32 *frame_list; | 1156 | u32 *frame_list; |
1152 | dma_addr_t frame_list_dma; | 1157 | dma_addr_t frame_list_dma; |
@@ -1431,6 +1436,8 @@ int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg); | |||
1431 | int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg); | 1436 | int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg); |
1432 | int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, | 1437 | int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, |
1433 | int rem_wakeup, int reset); | 1438 | int rem_wakeup, int reset); |
1439 | static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) | ||
1440 | { schedule_work(&hsotg->phy_reset_work); } | ||
1434 | #else | 1441 | #else |
1435 | static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) | 1442 | static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) |
1436 | { return 0; } | 1443 | { return 0; } |
@@ -1454,6 +1461,7 @@ static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) | |||
1454 | static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, | 1461 | static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, |
1455 | int rem_wakeup, int reset) | 1462 | int rem_wakeup, int reset) |
1456 | { return 0; } | 1463 | { return 0; } |
1464 | static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {} | ||
1457 | 1465 | ||
1458 | #endif | 1466 | #endif |
1459 | 1467 | ||
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) | |||
435 | /* Restart the Phy Clock */ | 435 | /* Restart the Phy Clock */ |
436 | pcgcctl &= ~PCGCTL_STOPPCLK; | 436 | pcgcctl &= ~PCGCTL_STOPPCLK; |
437 | dwc2_writel(hsotg, pcgcctl, PCGCTL); | 437 | dwc2_writel(hsotg, pcgcctl, PCGCTL); |
438 | |||
439 | /* | ||
440 | * If we've got this quirk then the PHY is stuck upon | ||
441 | * wakeup. Assert reset. This will propagate out and | ||
442 | * eventually we'll re-enumerate the device. Not great | ||
443 | * but the best we can do. We can't call phy_reset() | ||
444 | * at interrupt time but there's no hurry, so we'll | ||
445 | * schedule it for later. | ||
446 | */ | ||
447 | if (hsotg->reset_phy_on_wake) | ||
448 | dwc2_host_schedule_phy_reset(hsotg); | ||
449 | |||
438 | mod_timer(&hsotg->wkp_timer, | 450 | mod_timer(&hsotg->wkp_timer, |
439 | jiffies + msecs_to_jiffies(71)); | 451 | jiffies + msecs_to_jiffies(71)); |
440 | } else { | 452 | } 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) | |||
4376 | spin_unlock_irqrestore(&hsotg->lock, flags); | 4376 | spin_unlock_irqrestore(&hsotg->lock, flags); |
4377 | } | 4377 | } |
4378 | 4378 | ||
4379 | static void dwc2_hcd_phy_reset_func(struct work_struct *work) | ||
4380 | { | ||
4381 | struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, | ||
4382 | phy_reset_work); | ||
4383 | int ret; | ||
4384 | |||
4385 | ret = phy_reset(hsotg->phy); | ||
4386 | if (ret) | ||
4387 | dev_warn(hsotg->dev, "PHY reset failed\n"); | ||
4388 | } | ||
4389 | |||
4379 | /* | 4390 | /* |
4380 | * ========================================================================= | 4391 | * ========================================================================= |
4381 | * Linux HC Driver Functions | 4392 | * Linux HC Driver Functions |
@@ -5152,6 +5163,8 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) | |||
5152 | destroy_workqueue(hsotg->wq_otg); | 5163 | destroy_workqueue(hsotg->wq_otg); |
5153 | } | 5164 | } |
5154 | 5165 | ||
5166 | cancel_work_sync(&hsotg->phy_reset_work); | ||
5167 | |||
5155 | del_timer(&hsotg->wkp_timer); | 5168 | del_timer(&hsotg->wkp_timer); |
5156 | } | 5169 | } |
5157 | 5170 | ||
@@ -5293,11 +5306,10 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) | |||
5293 | hsotg->hc_ptr_array[i] = channel; | 5306 | hsotg->hc_ptr_array[i] = channel; |
5294 | } | 5307 | } |
5295 | 5308 | ||
5296 | /* Initialize hsotg start work */ | 5309 | /* Initialize work */ |
5297 | INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func); | 5310 | INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func); |
5298 | |||
5299 | /* Initialize port reset work */ | ||
5300 | INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func); | 5311 | INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func); |
5312 | INIT_WORK(&hsotg->phy_reset_work, dwc2_hcd_phy_reset_func); | ||
5301 | 5313 | ||
5302 | /* | 5314 | /* |
5303 | * Allocate space for storing data on status transactions. Normally no | 5315 | * 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) | |||
481 | hsotg->gadget_enabled = 1; | 481 | hsotg->gadget_enabled = 1; |
482 | } | 482 | } |
483 | 483 | ||
484 | hsotg->reset_phy_on_wake = | ||
485 | of_property_read_bool(dev->dev.of_node, | ||
486 | "snps,reset-phy-on-wake"); | ||
487 | if (hsotg->reset_phy_on_wake && !hsotg->phy) { | ||
488 | dev_warn(hsotg->dev, | ||
489 | "Quirk reset-phy-on-wake only supports generic PHYs\n"); | ||
490 | hsotg->reset_phy_on_wake = false; | ||
491 | } | ||
492 | |||
484 | if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { | 493 | if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { |
485 | retval = dwc2_hcd_init(hsotg); | 494 | retval = dwc2_hcd_init(hsotg); |
486 | if (retval) { | 495 | if (retval) { |