Skip to content

Commit

Permalink
usb: dwc2: Implement recovery after PM domain off
Browse files Browse the repository at this point in the history
According to the dt-bindings there are some platforms, which have a
dedicated USB power domain for DWC2 IP core supply. If the power domain
is switched off during system suspend then all USB register will lose
their settings.

So in case PM domains are defined consider this case by recover the
settings on resume. This doesn't handle wakeup yet.

Signed-off-by: Stefan Wahren <[email protected]>
  • Loading branch information
lategoodbye committed Jul 14, 2024
1 parent d418b30 commit 218a671
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 0 deletions.
16 changes: 16 additions & 0 deletions drivers/usb/dwc2/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,22 @@ int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,
return dwc2_gadget_exit_hibernation(hsotg, rem_wakeup, reset);
}

int dwc2_enter_poweroff(struct dwc2_hsotg *hsotg, int is_host)
{
if (is_host)
return dwc2_host_enter_poweroff(hsotg);
else
return dwc2_gadget_enter_poweroff(hsotg);
}

int dwc2_exit_poweroff(struct dwc2_hsotg *hsotg, int is_host)
{
if (is_host)
return dwc2_host_exit_poweroff(hsotg);
else
return dwc2_gadget_exit_poweroff(hsotg);
}

/*
* Do core a soft reset of the core. Be careful with this because it
* resets all the internal state machines of the core.
Expand Down
15 changes: 15 additions & 0 deletions drivers/usb/dwc2/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ struct dwc2_hsotg {
unsigned int reset_phy_on_wake:1;
unsigned int need_phy_for_wake:1;
unsigned int phy_off_for_suspend:1;
bool has_pm_domains;
u16 frame_number;

struct phy *phy;
Expand Down Expand Up @@ -1316,6 +1317,8 @@ int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup,
int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host);
int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,
int reset, int is_host);
int dwc2_enter_poweroff(struct dwc2_hsotg *hsotg, int is_host);
int dwc2_exit_poweroff(struct dwc2_hsotg *hsotg, int is_host);
void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg);
int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy);

Expand Down Expand Up @@ -1435,6 +1438,8 @@ int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg);
int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg);
void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg);
void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg);
int dwc2_gadget_enter_poweroff(struct dwc2_hsotg *hsotg);
int dwc2_gadget_exit_poweroff(struct dwc2_hsotg *hsotg);
static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg)
{ hsotg->fifo_map = 0; }
#else
Expand Down Expand Up @@ -1482,6 +1487,10 @@ static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {}
static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {}
int dwc2_gadget_enter_poweroff(struct dwc2_hsotg *hsotg)
{ return 0; }
int dwc2_gadget_exit_poweroff(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) {}
#endif

Expand All @@ -1505,6 +1514,8 @@ int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg,
void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg);
void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup);
bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2);
int dwc2_host_enter_poweroff(struct dwc2_hsotg *hsotg);
int dwc2_host_exit_poweroff(struct dwc2_hsotg *hsotg);
static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg)
{ schedule_work(&hsotg->phy_reset_work); }
#else
Expand Down Expand Up @@ -1544,6 +1555,10 @@ static inline void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg,
int rem_wakeup) {}
static inline bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2)
{ return false; }
static inline int dwc2_host_enter_poweroff(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_host_exit_poweroff(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {}

#endif
Expand Down
53 changes: 53 additions & 0 deletions drivers/usb/dwc2/gadget.c
Original file line number Diff line number Diff line change
Expand Up @@ -5710,3 +5710,56 @@ void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup)
hsotg->lx_state = DWC2_L0;
hsotg->bus_suspended = false;
}

int dwc2_gadget_enter_poweroff(struct dwc2_hsotg *hsotg)
{
int ret;

dev_dbg(hsotg->dev, "Entering device power off.\n");

/* Backup all registers */
ret = dwc2_backup_global_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to backup global registers\n",
__func__);
return ret;
}

ret = dwc2_backup_device_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to backup device registers\n",
__func__);
return ret;
}

hsotg->lx_state = DWC2_L2;

dev_dbg(hsotg->dev, "Entering device power off completed.\n");
return ret;
}

int dwc2_gadget_exit_poweroff(struct dwc2_hsotg *hsotg)
{
int ret;

dev_dbg(hsotg->dev, "Exiting device power off.\n");

ret = dwc2_restore_global_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to restore registers\n",
__func__);
return ret;
}

ret = dwc2_restore_device_registers(hsotg, 0);
if (ret) {
dev_err(hsotg->dev, "%s: failed to restore device registers\n",
__func__);
return ret;
}

hsotg->lx_state = DWC2_L0;

dev_dbg(hsotg->dev, "Exiting device power off completed.\n");
return ret;
}
74 changes: 74 additions & 0 deletions drivers/usb/dwc2/hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -4352,6 +4352,15 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)
if (hsotg->flags.b.port_connect_status == 0)
goto skip_power_saving;

if (hsotg->has_pm_domains) {
ret = dwc2_enter_poweroff(hsotg, 1);
if (ret)
dev_err(hsotg->dev, "enter poweroff failed\n");
/* After entering suspend, hardware is not accessible */
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
goto skip_power_saving;
}

switch (hsotg->params.power_down) {
case DWC2_POWER_DOWN_PARAM_PARTIAL:
/* Enter partial_power_down */
Expand Down Expand Up @@ -4423,6 +4432,18 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
if (hsotg->lx_state != DWC2_L2)
goto unlock;

if (hsotg->has_pm_domains) {
ret = dwc2_exit_poweroff(hsotg, 1);
if (ret)
dev_err(hsotg->dev, "exit poweroff failed\n");
/*
* Set HW accessible bit before powering on the controller
* since an interrupt may rise.
*/
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
goto unlock;
}

hprt0 = dwc2_read_hprt0(hsotg);

/*
Expand Down Expand Up @@ -5993,3 +6014,56 @@ void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup)
jiffies + msecs_to_jiffies(71));
}
}

int dwc2_host_enter_poweroff(struct dwc2_hsotg *hsotg)
{
int ret;

dev_dbg(hsotg->dev, "Entering host power off.\n");

/* Backup all registers */
ret = dwc2_backup_global_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to backup global registers\n",
__func__);
return ret;
}

ret = dwc2_backup_host_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to backup host registers\n",
__func__);
return ret;
}

hsotg->lx_state = DWC2_L2;

dev_dbg(hsotg->dev, "Entering host power off completed.\n");
return ret;
}

int dwc2_host_exit_poweroff(struct dwc2_hsotg *hsotg)
{
int ret;

dev_dbg(hsotg->dev, "Exiting host power off.\n");

ret = dwc2_restore_global_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to restore registers\n",
__func__);
return ret;
}

ret = dwc2_restore_host_registers(hsotg);
if (ret) {
dev_err(hsotg->dev, "%s: failed to restore host registers\n",
__func__);
return ret;
}

hsotg->lx_state = DWC2_L0;

dev_dbg(hsotg->dev, "Exiting host power off completed.\n");
return ret;
}
3 changes: 3 additions & 0 deletions drivers/usb/dwc2/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ static int dwc2_driver_probe(struct platform_device *dev)
of_property_read_bool(dev->dev.of_node,
"snps,need-phy-for-wake");

hsotg->has_pm_domains = of_property_read_bool(dev->dev.of_node,
"power-domains");

/*
* Before performing any core related operations
* check core version.
Expand Down

0 comments on commit 218a671

Please sign in to comment.