Browse Source

drivers: udc_dwc2: Enter hibernation during suspend

Save power when the bus is suspended by entering hibernation if
hibernation support is enabled and controller supports hibernation.

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
pull/77937/head
Tomasz Moń 11 months ago committed by Fabio Baltieri
parent
commit
9f6b66f162
  1. 7
      drivers/usb/udc/Kconfig.dwc2
  2. 382
      drivers/usb/udc/udc_dwc2.c

7
drivers/usb/udc/Kconfig.dwc2

@ -17,6 +17,13 @@ config UDC_DWC2_DMA @@ -17,6 +17,13 @@ config UDC_DWC2_DMA
help
Enable Buffer DMA if DWC2 USB controller supports Internal DMA.
config UDC_DWC2_HIBERNATION
bool "DWC2 USB Hibernation support"
default y
depends on UDC_DWC2
help
Enable Hibernation if DWC2 USB controller supports hibernation.
config UDC_DWC2_STACK_SIZE
int "UDC DWC2 driver internal thread stack size"
depends on UDC_DWC2

382
drivers/usb/udc/udc_dwc2.c

@ -33,12 +33,22 @@ enum dwc2_drv_event_type { @@ -33,12 +33,22 @@ enum dwc2_drv_event_type {
DWC2_DRV_EVT_DOUT,
/* IN transaction for specific endpoint is finished */
DWC2_DRV_EVT_DIN,
/* Core should exit hibernation */
DWC2_DRV_EVT_HIBERNATION_EXIT,
};
enum dwc2_hibernation_exit_reason {
DWC2_HIBERNATION_EXIT_BUS_RESET,
DWC2_HIBERNATION_EXIT_HOST_WAKEUP,
};
struct dwc2_drv_event {
const struct device *dev;
enum dwc2_drv_event_type type;
union {
uint8_t ep;
enum dwc2_hibernation_exit_reason exit_reason;
};
};
K_MSGQ_DEFINE(drv_msgq, sizeof(struct dwc2_drv_event),
@ -64,11 +74,45 @@ K_MSGQ_DEFINE(drv_msgq, sizeof(struct dwc2_drv_event), @@ -64,11 +74,45 @@ K_MSGQ_DEFINE(drv_msgq, sizeof(struct dwc2_drv_event),
/* Get Data FIFO access register */
#define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1))
enum dwc2_suspend_type {
DWC2_SUSPEND_NO_POWER_SAVING,
DWC2_SUSPEND_HIBERNATION,
};
/* Registers that have to be stored before Partial Power Down or Hibernation */
struct dwc2_reg_backup {
uint32_t gotgctl;
uint32_t gahbcfg;
uint32_t gusbcfg;
uint32_t gintmsk;
uint32_t grxfsiz;
uint32_t gnptxfsiz;
uint32_t gi2cctl;
uint32_t glpmcfg;
uint32_t gdfifocfg;
union {
uint32_t dptxfsiz[15];
uint32_t dieptxf[15];
};
uint32_t dcfg;
uint32_t dctl;
uint32_t diepmsk;
uint32_t doepmsk;
uint32_t daintmsk;
uint32_t diepctl[16];
uint32_t dieptsiz[16];
uint32_t diepdma[16];
uint32_t doepctl[16];
uint32_t doeptsiz[16];
uint32_t doepdma[16];
uint32_t pcgcctl;
};
/* Driver private data per instance */
struct udc_dwc2_data {
struct k_thread thread_data;
struct dwc2_reg_backup backup;
uint32_t ghwcfg1;
uint32_t enumspd;
uint32_t txf_set;
uint32_t max_xfersize;
uint32_t max_pktcnt;
@ -78,8 +122,14 @@ struct udc_dwc2_data { @@ -78,8 +122,14 @@ struct udc_dwc2_data {
uint16_t rxfifo_depth;
uint16_t max_txfifo_depth[16];
uint16_t sof_num;
/* Configuration flags */
unsigned int dynfifosizing : 1;
unsigned int bufferdma : 1;
/* Runtime state flags */
unsigned int hibernated : 1;
unsigned int enumdone : 1;
unsigned int enumspd : 2;
enum dwc2_suspend_type suspend_type;
/* Number of endpoints including control endpoint */
uint8_t numdeveps;
/* Number of IN endpoints including control endpoint */
@ -89,6 +139,10 @@ struct udc_dwc2_data { @@ -89,6 +139,10 @@ struct udc_dwc2_data {
uint8_t setup[8];
};
static void dwc2_on_bus_reset(const struct device *dev);
static void dwc2_exit_hibernation(const struct device *dev);
static void dwc2_wait_for_bit(const struct device *dev,
mem_addr_t addr, uint32_t bit);
static void udc_dwc2_ep_disable(const struct device *dev,
struct udc_ep_config *const cfg, bool stall);
@ -771,6 +825,8 @@ static int dwc2_handle_evt_din(const struct device *dev, @@ -771,6 +825,8 @@ static int dwc2_handle_evt_din(const struct device *dev,
static ALWAYS_INLINE void dwc2_thread_handler(void *const arg)
{
const struct device *dev = (const struct device *)arg;
struct udc_dwc2_data *const priv = udc_get_private(dev);
const struct udc_dwc2_config *const config = dev->config;
struct udc_ep_config *ep_cfg;
struct dwc2_drv_event evt;
@ -796,6 +852,24 @@ static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) @@ -796,6 +852,24 @@ static ALWAYS_INLINE void dwc2_thread_handler(void *const arg)
LOG_DBG("DIN event");
dwc2_handle_evt_din(dev, ep_cfg);
break;
case DWC2_DRV_EVT_HIBERNATION_EXIT:
LOG_DBG("Hibernation exit event");
config->irq_disable_func(dev);
if (priv->hibernated) {
dwc2_exit_hibernation(dev);
/* Let stack know we are no longer suspended */
udc_set_suspended(dev, false);
udc_submit_event(dev, UDC_EVT_RESUME, 0);
if (evt.exit_reason == DWC2_HIBERNATION_EXIT_BUS_RESET) {
dwc2_on_bus_reset(dev);
}
}
config->irq_enable_func(dev);
break;
}
if (ep_cfg->addr != USB_CONTROL_EP_OUT && !udc_ep_is_busy(dev, ep_cfg->addr)) {
@ -840,6 +914,9 @@ static void dwc2_on_bus_reset(const struct device *dev) @@ -840,6 +914,9 @@ static void dwc2_on_bus_reset(const struct device *dev)
/* Clear device address during reset. */
sys_clear_bits((mem_addr_t)&base->dcfg, USB_DWC2_DCFG_DEVADDR_MASK);
/* Speed enumeration must happen after reset. */
priv->enumdone = 0;
}
static void dwc2_handle_enumdone(const struct device *dev)
@ -850,6 +927,7 @@ static void dwc2_handle_enumdone(const struct device *dev) @@ -850,6 +927,7 @@ static void dwc2_handle_enumdone(const struct device *dev)
dsts = sys_read32((mem_addr_t)&base->dsts);
priv->enumspd = usb_dwc2_get_dsts_enumspd(dsts);
priv->enumdone = 1;
}
static inline int dwc2_read_fifo_setup(const struct device *dev, uint8_t ep,
@ -1240,6 +1318,239 @@ static void dwc2_handle_incompisoout(const struct device *dev) @@ -1240,6 +1318,239 @@ static void dwc2_handle_incompisoout(const struct device *dev)
sys_write32(USB_DWC2_GINTSTS_INCOMPISOOUT, gintsts_reg);
}
static void dwc2_backup_registers(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct dwc2_reg_backup *backup = &priv->backup;
backup->gotgctl = sys_read32((mem_addr_t)&base->gotgctl);
backup->gahbcfg = sys_read32((mem_addr_t)&base->gahbcfg);
backup->gusbcfg = sys_read32((mem_addr_t)&base->gusbcfg);
backup->gintmsk = sys_read32((mem_addr_t)&base->gintmsk);
backup->grxfsiz = sys_read32((mem_addr_t)&base->grxfsiz);
backup->gnptxfsiz = sys_read32((mem_addr_t)&base->gnptxfsiz);
backup->gi2cctl = sys_read32((mem_addr_t)&base->gi2cctl);
backup->glpmcfg = sys_read32((mem_addr_t)&base->glpmcfg);
backup->gdfifocfg = sys_read32((mem_addr_t)&base->gdfifocfg);
for (uint8_t i = 1U; i < priv->ineps; i++) {
backup->dieptxf[i - 1] = sys_read32((mem_addr_t)&base->dieptxf[i - 1]);
}
backup->dcfg = sys_read32((mem_addr_t)&base->dcfg);
backup->dctl = sys_read32((mem_addr_t)&base->dctl);
backup->diepmsk = sys_read32((mem_addr_t)&base->diepmsk);
backup->doepmsk = sys_read32((mem_addr_t)&base->doepmsk);
backup->daintmsk = sys_read32((mem_addr_t)&base->daintmsk);
for (uint8_t i = 0U; i < 16; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
backup->diepctl[i] = sys_read32((mem_addr_t)&base->in_ep[i].diepctl);
if (backup->diepctl[i] & USB_DWC2_DEPCTL_DPID) {
backup->diepctl[i] |= USB_DWC2_DEPCTL_SETD1PID;
} else {
backup->diepctl[i] |= USB_DWC2_DEPCTL_SETD0PID;
}
backup->dieptsiz[i] = sys_read32((mem_addr_t)&base->in_ep[i].dieptsiz);
backup->diepdma[i] = sys_read32((mem_addr_t)&base->in_ep[i].diepdma);
}
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
backup->doepctl[i] = sys_read32((mem_addr_t)&base->out_ep[i].doepctl);
if (backup->doepctl[i] & USB_DWC2_DEPCTL_DPID) {
backup->doepctl[i] |= USB_DWC2_DEPCTL_SETD1PID;
} else {
backup->doepctl[i] |= USB_DWC2_DEPCTL_SETD0PID;
}
backup->doeptsiz[i] = sys_read32((mem_addr_t)&base->out_ep[i].doeptsiz);
backup->doepdma[i] = sys_read32((mem_addr_t)&base->out_ep[i].doepdma);
}
}
backup->pcgcctl = sys_read32((mem_addr_t)&base->pcgcctl);
}
static void dwc2_restore_essential_registers(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct dwc2_reg_backup *backup = &priv->backup;
uint32_t pcgcctl = backup->pcgcctl & USB_DWC2_PCGCCTL_RESTOREVALUE_MASK;
sys_write32(backup->glpmcfg, (mem_addr_t)&base->glpmcfg);
sys_write32(backup->gi2cctl, (mem_addr_t)&base->gi2cctl);
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
sys_write32(backup->gahbcfg | USB_DWC2_GAHBCFG_GLBINTRMASK,
(mem_addr_t)&base->gahbcfg);
sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts);
sys_write32(USB_DWC2_GINTSTS_RSTRDONEINT, (mem_addr_t)&base->gintmsk);
sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg);
sys_write32(backup->dcfg, (mem_addr_t)&base->dcfg);
pcgcctl |= USB_DWC2_PCGCCTL_RESTOREMODE | USB_DWC2_PCGCCTL_RSTPDWNMODULE;
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
k_busy_wait(1);
pcgcctl |= USB_DWC2_PCGCCTL_ESSREGRESTORED;
sys_write32(pcgcctl, (mem_addr_t)&base->pcgcctl);
}
static void dwc2_restore_device_registers(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
struct dwc2_reg_backup *backup = &priv->backup;
sys_write32(backup->gotgctl, (mem_addr_t)&base->gotgctl);
sys_write32(backup->gahbcfg, (mem_addr_t)&base->gahbcfg);
sys_write32(backup->gusbcfg, (mem_addr_t)&base->gusbcfg);
sys_write32(backup->gintmsk, (mem_addr_t)&base->gintmsk);
sys_write32(backup->grxfsiz, (mem_addr_t)&base->grxfsiz);
sys_write32(backup->gnptxfsiz, (mem_addr_t)&base->gnptxfsiz);
sys_write32(backup->gdfifocfg, (mem_addr_t)&base->gdfifocfg);
for (uint8_t i = 1U; i < priv->ineps; i++) {
sys_write32(backup->dieptxf[i - 1], (mem_addr_t)&base->dieptxf[i - 1]);
}
sys_write32(backup->dctl, (mem_addr_t)&base->dctl);
sys_write32(backup->diepmsk, (mem_addr_t)&base->diepmsk);
sys_write32(backup->doepmsk, (mem_addr_t)&base->doepmsk);
sys_write32(backup->daintmsk, (mem_addr_t)&base->daintmsk);
for (uint8_t i = 0U; i < 16; i++) {
uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(priv->ghwcfg1, i);
if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
sys_write32(backup->dieptsiz[i], (mem_addr_t)&base->in_ep[i].dieptsiz);
sys_write32(backup->diepdma[i], (mem_addr_t)&base->in_ep[i].diepdma);
sys_write32(backup->diepctl[i], (mem_addr_t)&base->in_ep[i].diepctl);
}
if (epdir == USB_DWC2_GHWCFG1_EPDIR_OUT || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) {
sys_write32(backup->doeptsiz[i], (mem_addr_t)&base->out_ep[i].doeptsiz);
sys_write32(backup->doepdma[i], (mem_addr_t)&base->out_ep[i].doepdma);
sys_write32(backup->doepctl[i], (mem_addr_t)&base->out_ep[i].doepctl);
}
}
}
static void dwc2_enter_hibernation(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gpwrdn_reg = (mem_addr_t)&base->gpwrdn;
mem_addr_t pcgcctl_reg = (mem_addr_t)&base->pcgcctl;
dwc2_backup_registers(dev);
/* This code currently only supports UTMI+. UTMI+ runs at either 30 or
* 60 MHz and therefore 1 us busy waits have sufficiently large margin.
*/
/* Enable PMU Logic */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV);
k_busy_wait(1);
/* Stop PHY clock */
sys_set_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_STOPPCLK);
k_busy_wait(1);
/* Enable PMU interrupt */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL);
k_busy_wait(1);
/* Unmask PMU interrupt bits */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_LINESTAGECHANGEMSK |
USB_DWC2_GPWRDN_RESETDETMSK |
USB_DWC2_GPWRDN_DISCONNECTDETECTMSK |
USB_DWC2_GPWRDN_STSCHNGINTMSK);
k_busy_wait(1);
/* Enable power clamps */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP);
k_busy_wait(1);
/* Switch off power to the controller */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH);
/* Mark that the core is hibernated */
priv->hibernated = 1;
LOG_DBG("Hibernated");
}
static void dwc2_exit_hibernation(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
struct udc_dwc2_data *const priv = udc_get_private(dev);
mem_addr_t gpwrdn_reg = (mem_addr_t)&base->gpwrdn;
mem_addr_t pcgcctl_reg = (mem_addr_t)&base->pcgcctl;
/* Switch on power to the controller */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNSWTCH);
k_busy_wait(1);
/* Reset the controller */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N);
k_busy_wait(1);
/* Enable restore from PMU */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE);
k_busy_wait(1);
/* Disable power clamps */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNCLMP);
/* Remove reset to the controller */
sys_set_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PWRDNRST_N);
k_busy_wait(1);
/* Disable PMU interrupt */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUINTSEL);
dwc2_restore_essential_registers(dev);
/* Wait for Restore Done Interrupt */
dwc2_wait_for_bit(dev, (mem_addr_t)&base->gintsts, USB_DWC2_GINTSTS_RSTRDONEINT);
sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts);
/* Disable restore from PMU */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_RESTORE);
k_busy_wait(1);
/* Clear reset to power down module */
sys_clear_bits(pcgcctl_reg, USB_DWC2_PCGCCTL_RSTPDWNMODULE);
/* Restore GUSBCFG, DCFG and DCTL */
sys_write32(priv->backup.gusbcfg, (mem_addr_t)&base->gusbcfg);
sys_write32(priv->backup.dcfg, (mem_addr_t)&base->dcfg);
sys_write32(priv->backup.dctl, (mem_addr_t)&base->dctl);
/* Disable PMU */
sys_clear_bits(gpwrdn_reg, USB_DWC2_GPWRDN_PMUACTV);
k_busy_wait(5);
sys_set_bits((mem_addr_t)&base->dctl, USB_DWC2_DCTL_PWRONPRGDONE);
k_msleep(1);
sys_write32(0xFFFFFFFFUL, (mem_addr_t)&base->gintsts);
dwc2_restore_device_registers(dev);
priv->hibernated = 0;
LOG_DBG("Hibernation exit complete");
}
static void udc_dwc2_isr_handler(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
@ -1249,6 +1560,39 @@ static void udc_dwc2_isr_handler(const struct device *dev) @@ -1249,6 +1560,39 @@ static void udc_dwc2_isr_handler(const struct device *dev)
uint32_t int_status;
uint32_t gintmsk;
if (priv->hibernated) {
uint32_t gpwrdn = sys_read32((mem_addr_t)&base->gpwrdn);
bool reset, resume = false;
enum dwc2_hibernation_exit_reason exit_reason;
/* Clear interrupts */
sys_write32(gpwrdn, (mem_addr_t)&base->gpwrdn);
if (gpwrdn & USB_DWC2_GPWRDN_LNSTSCHNG) {
resume = usb_dwc2_get_gpwrdn_linestate(gpwrdn) ==
USB_DWC2_GPWRDN_LINESTATE_DM1DP0;
exit_reason = DWC2_HIBERNATION_EXIT_HOST_WAKEUP;
}
reset = gpwrdn & USB_DWC2_GPWRDN_RESETDETECTED;
if (reset) {
exit_reason = DWC2_HIBERNATION_EXIT_BUS_RESET;
}
if (reset || resume) {
struct dwc2_drv_event evt = {
.dev = dev,
.type = DWC2_DRV_EVT_HIBERNATION_EXIT,
.exit_reason = exit_reason,
};
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
}
(void)dwc2_quirk_irq_clear(dev);
return;
}
gintmsk = sys_read32((mem_addr_t)&base->gintmsk);
/* Read and handle interrupt status register */
@ -1281,13 +1625,6 @@ static void udc_dwc2_isr_handler(const struct device *dev) @@ -1281,13 +1625,6 @@ static void udc_dwc2_isr_handler(const struct device *dev)
udc_submit_event(dev, UDC_EVT_RESET, 0);
}
if (int_status & USB_DWC2_GINTSTS_USBSUSP) {
/* Clear USB Suspend interrupt. */
sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg);
udc_set_suspended(dev, true);
udc_submit_event(dev, UDC_EVT_SUSPEND, 0);
}
if (int_status & USB_DWC2_GINTSTS_WKUPINT) {
/* Clear Resume/Remote Wakeup Detected interrupt. */
sys_write32(USB_DWC2_GINTSTS_WKUPINT, gintsts_reg);
@ -1317,6 +1654,27 @@ static void udc_dwc2_isr_handler(const struct device *dev) @@ -1317,6 +1654,27 @@ static void udc_dwc2_isr_handler(const struct device *dev)
if (int_status & USB_DWC2_GINTSTS_INCOMPISOOUT) {
dwc2_handle_incompisoout(dev);
}
if (int_status & USB_DWC2_GINTSTS_USBSUSP) {
if (!priv->enumdone) {
/* Clear stale suspend interrupt */
sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg);
continue;
}
/* Notify the stack */
udc_set_suspended(dev, true);
udc_submit_event(dev, UDC_EVT_SUSPEND, 0);
if (priv->suspend_type == DWC2_SUSPEND_HIBERNATION) {
dwc2_enter_hibernation(dev);
/* Next interrupt will be from PMU */
break;
}
/* Clear USB Suspend interrupt. */
sys_write32(USB_DWC2_GINTSTS_USBSUSP, gintsts_reg);
}
}
(void)dwc2_quirk_irq_clear(dev);
@ -1991,6 +2349,14 @@ static int udc_dwc2_init_controller(const struct device *dev) @@ -1991,6 +2349,14 @@ static int udc_dwc2_init_controller(const struct device *dev)
priv->dynfifosizing = true;
}
if (IS_ENABLED(CONFIG_UDC_DWC2_HIBERNATION) &&
ghwcfg4 & USB_DWC2_GHWCFG4_HIBERNATION) {
LOG_INF("Hibernation enabled");
priv->suspend_type = DWC2_SUSPEND_HIBERNATION;
} else {
priv->suspend_type = DWC2_SUSPEND_NO_POWER_SAVING;
}
/* Get the number or endpoints and IN endpoints we can use later */
priv->numdeveps = usb_dwc2_get_ghwcfg2_numdeveps(ghwcfg2) + 1U;
priv->ineps = usb_dwc2_get_ghwcfg4_ineps(ghwcfg4) + 1U;

Loading…
Cancel
Save