Commit 24d209db authored by Artur Petrosyan's avatar Artur Petrosyan Committed by Greg Kroah-Hartman

usb: dwc2: Fix hibernation between host and device modes.

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: 65c9c4c6 ("usb: dwc2: Add dwc2_handle_gpwrdn_intr() handler")
Acked-by: default avatarMinas Harutyunyan <Minas.Harutyunyan@synopsys.com>
Signed-off-by: default avatarArtur Petrosyan <Arthur.Petrosyan@synopsys.com>
Signed-off-by: default avatarMinas Harutyunyan <Minas.Harutyunyan@synopsys.com>
Link: https://lore.kernel.org/r/20210416124715.75355A005D@mailhost.synopsys.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent c2db8d7b
...@@ -680,6 +680,71 @@ static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) ...@@ -680,6 +680,71 @@ static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg)
return 0; 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. * GPWRDN interrupt handler.
* *
...@@ -701,64 +766,14 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) ...@@ -701,64 +766,14 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg)
if ((gpwrdn & GPWRDN_DISCONN_DET) && if ((gpwrdn & GPWRDN_DISCONN_DET) &&
(gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) { (gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) {
u32 gpwrdn_tmp;
dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__); dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__);
/*
/* Switch-on voltage to the core */ * Call disconnect detect function to exit from
gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); * hibernation
gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; */
dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn);
udelay(10); } else if ((gpwrdn & GPWRDN_LNSTSCHG) &&
(gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) {
/* 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) {
dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__); dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__);
if (hsotg->hw_params.hibernation && if (hsotg->hw_params.hibernation &&
hsotg->hibernated) { hsotg->hibernated) {
...@@ -769,24 +784,21 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) ...@@ -769,24 +784,21 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg)
dwc2_exit_hibernation(hsotg, 1, 0, 1); dwc2_exit_hibernation(hsotg, 1, 0, 1);
} }
} }
} } else if ((gpwrdn & GPWRDN_RST_DET) &&
if ((gpwrdn & GPWRDN_RST_DET) && (gpwrdn & GPWRDN_RST_DET_MSK)) { (gpwrdn & GPWRDN_RST_DET_MSK)) {
dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__); dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__);
if (!linestate && (gpwrdn & GPWRDN_BSESSVLD)) if (!linestate && (gpwrdn & GPWRDN_BSESSVLD))
dwc2_exit_hibernation(hsotg, 0, 1, 0); dwc2_exit_hibernation(hsotg, 0, 1, 0);
} } else if ((gpwrdn & GPWRDN_STS_CHGINT) &&
if ((gpwrdn & GPWRDN_STS_CHGINT) && (gpwrdn & GPWRDN_STS_CHGINT_MSK)) {
(gpwrdn & GPWRDN_STS_CHGINT_MSK) && linestate) {
dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__);
if (hsotg->hw_params.hibernation && /*
hsotg->hibernated) { * As GPWRDN_STS_CHGINT exit from hibernation flow is
if (gpwrdn & GPWRDN_IDSTS) { * the same as in GPWRDN_DISCONN_DET flow. Call
dwc2_exit_hibernation(hsotg, 0, 0, 0); * disconnect detect helper function to exit from
call_gadget(hsotg, resume); * hibernation.
} else { */
dwc2_exit_hibernation(hsotg, 1, 0, 1); dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn);
}
}
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment