summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArtur Petrosyan <Arthur.Petrosyan@synopsys.com>2021-04-16 15:47:14 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2021-05-14 10:44:23 +0300
commitce7b62d857919e511c6d5171c578c4b7e1bf0e67 (patch)
tree8d41de9877b428108ce7a1febb5ab7ba62b21f68
parent6f7ed537ca2d1d53410122def2aad676841b8639 (diff)
downloadlinux-ce7b62d857919e511c6d5171c578c4b7e1bf0e67.tar.xz
usb: dwc2: Fix hibernation between host and device modes.
[ Upstream commit 24d209dba5a3959b2ebde7cf3ad40c8015e814cf ] When core is in hibernation in host mode and a device cable was connected then driver exited from device hibernation. However, registers saved for host mode and when exited from device hibernation register restore would be done for device register which was wrong because there was no device registers stored to restore. - Added dwc_handle_gpwrdn_disc_det() function which handles gpwrdn disconnect detect flow and exits hibernation without restoring the registers. - Updated exiting from hibernation in GPWRDN_STS_CHGINT with calling dwc_handle_gpwrdn_disc_det() function. Here no register is restored which is the solution described above. Fixes: 65c9c4c6b01f ("usb: dwc2: Add dwc2_handle_gpwrdn_intr() handler") Acked-by: Minas Harutyunyan <Minas.Harutyunyan@synopsys.com> Signed-off-by: Artur Petrosyan <Arthur.Petrosyan@synopsys.com> Signed-off-by: Minas Harutyunyan <Minas.Harutyunyan@synopsys.com> Link: https://lore.kernel.org/r/20210416124715.75355A005D@mailhost.synopsys.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
-rw-r--r--drivers/usb/dwc2/core_intr.c154
1 files changed, 83 insertions, 71 deletions
diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c
index f73e78143ad1..9da27ec22d58 100644
--- a/drivers/usb/dwc2/core_intr.c
+++ b/drivers/usb/dwc2/core_intr.c
@@ -665,6 +665,71 @@ static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg)
return 0;
}
+/**
+ * dwc_handle_gpwrdn_disc_det() - Handles the gpwrdn disconnect detect.
+ * Exits hibernation without restoring registers.
+ *
+ * @hsotg: Programming view of DWC_otg controller
+ * @gpwrdn: GPWRDN register
+ */
+static inline void dwc_handle_gpwrdn_disc_det(struct dwc2_hsotg *hsotg,
+ u32 gpwrdn)
+{
+ u32 gpwrdn_tmp;
+
+ /* Switch-on voltage to the core */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Reset core */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Disable Power Down Clamp */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Deassert reset core */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp |= GPWRDN_PWRDNRSTN;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Disable PMU interrupt */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PMUINTSEL;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+
+ /* De-assert Wakeup Logic */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PMUACTV;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+
+ hsotg->hibernated = 0;
+ hsotg->bus_suspended = 0;
+
+ if (gpwrdn & GPWRDN_IDSTS) {
+ hsotg->op_state = OTG_STATE_B_PERIPHERAL;
+ dwc2_core_init(hsotg, false);
+ dwc2_enable_global_interrupts(hsotg);
+ dwc2_hsotg_core_init_disconnected(hsotg, false);
+ dwc2_hsotg_core_connect(hsotg);
+ } else {
+ hsotg->op_state = OTG_STATE_A_HOST;
+
+ /* Initialize the Core for Host mode */
+ dwc2_core_init(hsotg, false);
+ dwc2_enable_global_interrupts(hsotg);
+ dwc2_hcd_start(hsotg);
+ }
+}
+
/*
* GPWRDN interrupt handler.
*
@@ -686,64 +751,14 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg)
if ((gpwrdn & GPWRDN_DISCONN_DET) &&
(gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) {
- u32 gpwrdn_tmp;
-
dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__);
-
- /* Switch-on voltage to the core */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Reset core */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Disable Power Down Clamp */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Deassert reset core */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp |= GPWRDN_PWRDNRSTN;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Disable PMU interrupt */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PMUINTSEL;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
-
- /* De-assert Wakeup Logic */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PMUACTV;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
-
- hsotg->hibernated = 0;
-
- if (gpwrdn & GPWRDN_IDSTS) {
- hsotg->op_state = OTG_STATE_B_PERIPHERAL;
- dwc2_core_init(hsotg, false);
- dwc2_enable_global_interrupts(hsotg);
- dwc2_hsotg_core_init_disconnected(hsotg, false);
- dwc2_hsotg_core_connect(hsotg);
- } else {
- hsotg->op_state = OTG_STATE_A_HOST;
-
- /* Initialize the Core for Host mode */
- dwc2_core_init(hsotg, false);
- dwc2_enable_global_interrupts(hsotg);
- dwc2_hcd_start(hsotg);
- }
- }
-
- if ((gpwrdn & GPWRDN_LNSTSCHG) &&
- (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) {
+ /*
+ * Call disconnect detect function to exit from
+ * hibernation
+ */
+ dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn);
+ } else if ((gpwrdn & GPWRDN_LNSTSCHG) &&
+ (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) {
dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__);
if (hsotg->hw_params.hibernation &&
hsotg->hibernated) {
@@ -754,24 +769,21 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg)
dwc2_exit_hibernation(hsotg, 1, 0, 1);
}
}
- }
- if ((gpwrdn & GPWRDN_RST_DET) && (gpwrdn & GPWRDN_RST_DET_MSK)) {
+ } else if ((gpwrdn & GPWRDN_RST_DET) &&
+ (gpwrdn & GPWRDN_RST_DET_MSK)) {
dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__);
if (!linestate && (gpwrdn & GPWRDN_BSESSVLD))
dwc2_exit_hibernation(hsotg, 0, 1, 0);
- }
- if ((gpwrdn & GPWRDN_STS_CHGINT) &&
- (gpwrdn & GPWRDN_STS_CHGINT_MSK) && linestate) {
+ } else if ((gpwrdn & GPWRDN_STS_CHGINT) &&
+ (gpwrdn & GPWRDN_STS_CHGINT_MSK)) {
dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__);
- if (hsotg->hw_params.hibernation &&
- hsotg->hibernated) {
- if (gpwrdn & GPWRDN_IDSTS) {
- dwc2_exit_hibernation(hsotg, 0, 0, 0);
- call_gadget(hsotg, resume);
- } else {
- dwc2_exit_hibernation(hsotg, 1, 0, 1);
- }
- }
+ /*
+ * As GPWRDN_STS_CHGINT exit from hibernation flow is
+ * the same as in GPWRDN_DISCONN_DET flow. Call
+ * disconnect detect helper function to exit from
+ * hibernation.
+ */
+ dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn);
}
}