diff options
-rw-r--r-- | chip/g/idle.c | 50 | ||||
-rw-r--r-- | chip/g/usb.c | 157 | ||||
-rw-r--r-- | include/system.h | 1 |
3 files changed, 151 insertions, 57 deletions
diff --git a/chip/g/idle.c b/chip/g/idle.c index fc955e0b30..7319c6edb8 100644 --- a/chip/g/idle.c +++ b/chip/g/idle.c @@ -5,6 +5,9 @@ #include "common.h" #include "console.h" +#include "pmu.h" +#include "system.h" +#include "task.h" #include "util.h" /* This function is assumed to exist, but we don't use it */ @@ -49,16 +52,61 @@ DECLARE_CONSOLE_COMMAND(idle, command_idle, "Set or show the idle action: wfi, sleep, deep sleep", NULL); +static void prepare_to_deep_sleep(void) +{ + /* No task switching! */ + interrupt_disable(); + + /* + * Preserve some state prior to deep sleep. Pretty much all we need is + * the device address, since everything else can be reinitialized on + * resume. + */ + GREG32(PMU, PWRDN_SCRATCH18) = GR_USB_DCFG; + + /* Latch the pinmux values */ + GREG32(PINMUX, HOLD) = 1; + + /* Wake only from USB for now */ + GR_PMU_EXITPD_MASK = + GC_PMU_EXITPD_MASK_UTMI_SUSPEND_N_MASK; + + /* Clamp the USB pins and shut the PHY down. We have to do this in + * three separate steps, or Bad Things happen. */ + GWRITE_FIELD(USB, PCGCCTL, PWRCLMP, 1); + GWRITE_FIELD(USB, PCGCCTL, RSTPDWNMODULE, 1); + GWRITE_FIELD(USB, PCGCCTL, STOPPCLK, 1); + + /* Get ready... */ + GR_PMU_LOW_POWER_DIS = + /* The next "wfi" will trigger it */ + GC_PMU_LOW_POWER_DIS_START_MASK | + /* ... with these rails off */ + GC_PMU_LOW_POWER_DIS_VDDL_MASK | /* <= this means deep sleep */ + GC_PMU_LOW_POWER_DIS_VDDIOF_MASK | + GC_PMU_LOW_POWER_DIS_VDDXO_MASK | + GC_PMU_LOW_POWER_DIS_JTR_RC_MASK; +} + /* Custom idle task, executed when no tasks are ready to be scheduled. */ void __idle(void) { + int sleep_ok; + while (1) { /* Don't even bother unless we've enabled it */ if (idle_action == IDLE_WFI) goto wfi; - /* TODO(wfrichar): sleep/deep-sleep stuff goes here... */ + /* Anyone still busy? */ + sleep_ok = DEEP_SLEEP_ALLOWED; + + /* We're allowed to sleep now, so set it up. */ + if (sleep_ok) + if (idle_action == IDLE_DEEP_SLEEP) + prepare_to_deep_sleep(); + /* Normal sleep is not yet implemented */ wfi: /* Wait for the next irq event. This stops the CPU clock and diff --git a/chip/g/usb.c b/chip/g/usb.c index de028935fe..9963f2dff7 100644 --- a/chip/g/usb.c +++ b/chip/g/usb.c @@ -11,6 +11,7 @@ #include "hooks.h" #include "link_defs.h" #include "registers.h" +#include "system.h" #include "task.h" #include "timer.h" #include "util.h" @@ -952,19 +953,6 @@ static void ep0_interrupt(uint32_t intr_on_out, uint32_t intr_on_in) } } -/* Endpoint-specific callback for when the USB device recognizes a reset */ -static void ep0_reset(void) -{ - /* Reset EP0 address */ - GWRITE_FIELD(USB, DCFG, DEVADDR, 0); - initialize_dma_buffers(); - expect_setup_packet(); - /* Clear our internal state */ - device_state = DS_DEFAULT; - configuration_value = 0; - -} - /****************************************************************************/ /* USB device initialization and shutdown routines */ @@ -1043,35 +1031,42 @@ static void setup_data_fifos(void) ; /* TODO: timeout 100ms */ } -static void usb_reset(void) +static void usb_init_endpoints(void) { int ep; - print_later("usb_reset()", 0, 0, 0, 0, 0); + print_later("usb_init_endpoints()", 0, 0, 0, 0, 0); - ep0_reset(); + /* Prepare to receive packets on EP0 */ + initialize_dma_buffers(); + expect_setup_packet(); + + /* Reset the other endpoints */ for (ep = 1; ep < USB_EP_COUNT; ep++) usb_ep_reset[ep](); } -static void usb_enumdone(void) +static void usb_reset(void) { - print_later("usb_enumdone()", 0, 0, 0, 0, 0); -} + print_later("usb_reset()", 0, 0, 0, 0, 0); -static void usb_wakeup(void) -{ - print_later("usb_wakeup()", 0, 0, 0, 0, 0); -} + /* Clear our internal state */ + device_state = DS_DEFAULT; + configuration_value = 0; -static void usb_early_suspend(void) -{ - print_later("usb_early_suspend()", 0, 0, 0, 0, 0); + /* Clear the device address */ + GWRITE_FIELD(USB, DCFG, DEVADDR, 0); + + /* Reinitialize all the endpoints */ + usb_init_endpoints(); } -static void usb_suspend(void) +static void usb_resetdet(void) { - print_later("usb_suspend()", 0, 0, 0, 0, 0); + /* TODO: Same as normal reset, right? I think we only get this if we're + * suspended (sleeping) and the host resets us. Try it and see. */ + print_later("usb_resetdet()", 0, 0, 0, 0, 0); + usb_reset(); } void usb_interrupt(void) @@ -1084,21 +1079,32 @@ void usb_interrupt(void) print_later("interrupt: GINTSTS 0x%08x", status, 0, 0, 0, 0); - if (status & GINTSTS(RESETDET)) - usb_wakeup(); + /* We can suspend if the host stops talking to us. But if anything else + * comes along (even ERLYSUSP), we should NOT suspend. */ + if (status & GINTSTS(USBSUSP)) { + print_later("usb_suspend()", 0, 0, 0, 0, 0); + enable_sleep(SLEEP_MASK_USB_DEVICE); + } else { + disable_sleep(SLEEP_MASK_USB_DEVICE); + } +#ifdef DEBUG_ME if (status & GINTSTS(ERLYSUSP)) - usb_early_suspend(); + print_later("usb_early_suspend()", 0, 0, 0, 0, 0); - if (status & GINTSTS(USBSUSP)) - usb_suspend(); + if (status & GINTSTS(WKUPINT)) + print_later("usb_wakeup()", 0, 0, 0, 0, 0); + + if (status & GINTSTS(ENUMDONE)) + print_later("usb_enumdone()", 0, 0, 0, 0, 0); +#endif + + if (status & GINTSTS(RESETDET)) + usb_resetdet(); if (status & GINTSTS(USBRST)) usb_reset(); - if (status & GINTSTS(ENUMDONE)) - usb_enumdone(); - /* Endpoint interrupts */ if (oepint || iepint) { /* Note: It seems that the DAINT bits are only trustworthy for @@ -1183,9 +1189,20 @@ void usb_disconnect(void) void usb_init(void) { - int i; + int i, resume; - print_later("usb_init()", 0, 0, 0, 0, 0); + /* USB is in use */ + disable_sleep(SLEEP_MASK_USB_DEVICE); + + /* + * Resuming from a deep sleep is a lot like a cold boot, but there are + * few things that we need to do slightly differently. However, we ONLY + * do them if we're really resuming due to a USB wakeup. If we're woken + * for some other reason, we just do a normal USB reset. The host + * doesn't mind. + */ + resume = ((system_get_reset_flags() & RESET_FLAG_USB_RESUME) && + (GR_USB_GINTSTS & GC_USB_GINTSTS_WKUPINT_MASK)); /* TODO(crosbug.com/p/46813): Clean this up. Do only what's needed, and * use meaningful constants instead of magic numbers. */ @@ -1220,7 +1237,10 @@ void usb_init(void) /* FIXME: Magic number! 14 is for 15MHz! Use 9 for 30MHz */ | GUSBCFG_USBTRDTIM(14); - usb_softreset(); + if (!resume) + /* Don't reset on resume, because some preserved internal state + * will be lost and there's no way to restore it. */ + usb_softreset(); GR_USB_GUSBCFG = GUSBCFG_PHYSEL_FS | GUSBCFG_FSINTF_6PIN | GUSBCFG_TOUTCAL(7) @@ -1233,21 +1253,36 @@ void usb_init(void) GAHBCFG_NP_TXF_EMP_LVL; /* Be in disconnected state until we are ready */ - usb_disconnect(); + if (!resume) + usb_disconnect(); + + if (resume) + /* DEVADDR is preserved in the USB module during deep sleep, + * but it doesn't show up in USB_DCFG on resume. If we don't + * restore it manually too, it doesn't work. */ + GR_USB_DCFG = GREG32(PMU, PWRDN_SCRATCH18); + else + /* Init: USB2 FS, Scatter/Gather DMA, DEVADDR = 0x00 */ + GR_USB_DCFG |= DCFG_DEVSPD_FS48 | DCFG_DESCDMA; + + /* If we've restored a nonzero device address, update our state. */ + if (GR_USB_DCFG & GC_USB_DCFG_DEVADDR_MASK) { + /* Caution: We only have one config TODAY, so there's no real + * difference between DS_CONFIGURED and DS_ADDRESS. */ + device_state = DS_CONFIGURED; + configuration_value = 1; + } else { + device_state = DS_DEFAULT; + configuration_value = 0; + } - /* Max speed: USB2 FS */ - GR_USB_DCFG = DCFG_DEVSPD_FS48 | DCFG_DESCDMA; + /* Now that DCFG.DesDMA is accurate, prepare the FIFOs */ + setup_data_fifos(); - /* Setup FIFO configuration */ - setup_data_fifos(); - - /* Device registers have been setup */ - GR_USB_DCTL |= DCTL_PWRONPRGDONE; - udelay(10); - GR_USB_DCTL &= ~DCTL_PWRONPRGDONE; - - /* Clear global NAKs */ - GR_USB_DCTL |= DCTL_CGOUTNAK | DCTL_CGNPINNAK; + /* If resuming, reinitialize the endpoints now. For a cold boot we'll + * do this as part of handling the host-driven reset. */ + if (resume) + usb_init_endpoints(); /* Clear any pending interrupts */ for (i = 0; i < 16; i++) { @@ -1273,16 +1308,23 @@ void usb_init(void) /* Endpoint activity, cleared by the DOEPINT/DIEPINT regs */ GINTMSK(OEPINT) | GINTMSK(IEPINT) | /* Reset detected while suspended. Need to wake up. */ - GINTMSK(RESETDET) | + GINTMSK(RESETDET) | /* TODO: Do we need this? */ /* Idle, Suspend detected. Should go to sleep. */ GINTMSK(ERLYSUSP) | GINTMSK(USBSUSP); + /* Device registers have been setup */ + GR_USB_DCTL |= DCTL_PWRONPRGDONE; + udelay(10); + GR_USB_DCTL &= ~DCTL_PWRONPRGDONE; + + /* Clear global NAKs */ + GR_USB_DCTL |= DCTL_CGOUTNAK | DCTL_CGNPINNAK; + #ifndef CONFIG_USB_INHIBIT_CONNECT /* Indicate our presence to the USB host */ - usb_connect(); + if (!resume) + usb_connect(); #endif - - print_later("usb_init() done", 0, 0, 0, 0, 0); } #ifndef CONFIG_USB_INHIBIT_INIT DECLARE_HOOK(HOOK_INIT, usb_init, HOOK_PRIO_DEFAULT); @@ -1299,4 +1341,7 @@ void usb_release(void) /* disable clocks */ clock_enable_module(MODULE_USB, 0); /* TODO: pin-mux */ + + /* USB is off, so sleep whenever */ + enable_sleep(SLEEP_MASK_USB_DEVICE); } diff --git a/include/system.h b/include/system.h index 6c8bbea06d..67a867f39e 100644 --- a/include/system.h +++ b/include/system.h @@ -313,6 +313,7 @@ enum { SLEEP_MASK_SPI = (1 << 6), /* SPI communications ongoing */ SLEEP_MASK_I2C_SLAVE = (1 << 7), /* I2C slave communication ongoing */ SLEEP_MASK_FAN = (1 << 8), /* Fan control loop ongoing */ + SLEEP_MASK_USB_DEVICE = (1 << 9), /* Generic USB device in use */ SLEEP_MASK_FORCE_NO_DSLEEP = (1 << 15), /* Force disable. */ |