Adding deep sleep support Improving sleep state support
authorNathael Pajani <nathael.pajani@ed3l.fr>
Sun, 30 Jan 2022 03:26:59 +0000 (04:26 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Fri, 10 Feb 2023 18:02:59 +0000 (19:02 +0100)
core/pmu.c
include/core/pmu.h

index 6bafb94..b3a6a3e 100644 (file)
  *
  *************************************************************************** */
 
+/* PMU unit */
 
 #include "lib/stdint.h"
 
 #include "core/lpc_regs.h"
 #include "core/lpc_core.h"
 #include "core/system.h"
+#include "core/watchdog.h"
 #include "core/pmu.h"
 #include "lib/errno.h"
 
 
 
-
-/* PMU unit */
+/* Read (read_gp_retain()) or Write (save_gp_retain()) to the general purpose
+ *   retain registers.
+ * There are four 32 bits general purpose retain registers on the LPC82x uC.
+ * Data written to these registers is retained even across uC resets or
+ *   reprogrammations so long as the uC remains powered.
+ * These functions read (or write) "nb" contiguous "values" from (or to) "offset"
+ *   general purpose registers.
+ * - "values" is a user provided buffer for the data read from (or to be written to)
+ *   the general purpose registers
+ * - "nb" is the buffer size.
+ * - "offset" is the first (starting at 0) general purpose register to be read
+ *   from (or written to)
+ * "offset + nb" must be <= 4
+ * The data is always placed or found in the first word of "values".
+ */
 int save_gp_retain(uint32_t* values, uint8_t nb, uint8_t offset)
 {
        struct lpc_pm_unit* pmu = LPC_PMU;
@@ -68,11 +83,14 @@ int read_gp_retain(uint32_t* values, uint8_t nb, uint8_t offset)
  * - do not enter "deep" sleep or power-down mode
  * - if "wake_sources_is_all_intrs" is set to 1, also wake up from unconfigured
  *   interrupt sources
+ * This function does not turn off systick if it is activated, so the system will
+ *   wake up on next tick.
  * This function returns after the next interrupt or event.
  */
 void enter_sleep(int wake_sources_is_all_intrs)
 {
        struct syst_ctrl_block_regs* scb = LPC_SCB;
+       struct lpc_pm_unit* pmu = LPC_PMU;
 
        /* Configure SCR for sleep mode */
        if (wake_sources_is_all_intrs == 1) {
@@ -80,28 +98,103 @@ void enter_sleep(int wake_sources_is_all_intrs)
        } else {
                scb->scr = 0;
        }
-       wfe();
+       pmu->power_ctrl = LPC_PM_DEFAULT;
+       wfi();
 }
 
+/* Enter permanent sleep state
+ * - Return to sleep mode after interrupt or event
+ * - do not enter "deep" sleep or power-down mode
+ * - if "wake_sources_is_all_intrs" is set to 1, also wake up from unconfigured
+ *   interrupt sources
+ * This function does not turn off systick if it is activated.
+ * This function does not returns after the next interrupt or event, unless a call to
+ *   exit_sleep() happened in an interrupt handler.
+ */
+void enter_continuous_sleep(int wake_sources_is_all_intrs)
+{
+       struct syst_ctrl_block_regs* scb = LPC_SCB;
+       struct lpc_pm_unit* pmu = LPC_PMU;
+
+       /* Configure SCR for continuous sleep mode */
+       if (wake_sources_is_all_intrs == 1) {
+               /* Wakeup from any interrupt, even those not enabled */
+               scb->scr = SCB_SCR_SEVONPEND | SCB_SCR_SLEEPONEXIT;
+       } else {
+               scb->scr = SCB_SCR_SLEEPONEXIT;
+       }
+       pmu->power_ctrl = LPC_PM_DEFAULT;
+       wfi();
+}
+
+/* Exit permanent sleep state
+ * Can be called from an interrupt handler to get back to thread mode at the end of
+ * the handler execution.
+ */
+void exit_sleep(void)
+{
+       struct syst_ctrl_block_regs* scb = LPC_SCB;
+       scb->scr &= ~SCB_SCR_SLEEPONEXIT;
+}
 
 /* Enter deep sleep.
  * NOTE : entering deep sleep implies a lot of side effects. I'll try to list them all here
  *  so this can be done right.
- *
+ * - The PLL is stopped, system must switch back to IRC before entering deep sleep
+ * - The IRC is running, but it's output is disabled.
+ * - Memory retains it's state
+ * - Flash stays ON
+ * - The peripherals will be stopped
+ * - Systick will be stopped
+ * - If using the Self Wake-Up timer as wake-up source then it must be turned ON before
+ *   calling the function, and it MUST use either the 10KHZ clock or the external pin clock.
  */
-void enter_deep_sleep(void)
+void enter_deep_sleep(uint32_t wakeup_freq_sel, uint32_t self_wakeup)
 {
        struct lpc_sys_config* sys_config = LPC_SYS_CONFIG;
+       struct syst_ctrl_block_regs* scb = LPC_SCB;
+       struct lpc_pm_unit* pmu = LPC_PMU;
+       uint32_t tmp = ~(0);
 
        /* Ask for the same clock status when waking up */
        sys_config->powerdown_wake_cfg = sys_config->powerdown_run_cfg;
-       /* Set deep_sleep config */
+
+       /* Set deep_sleep config (Watchdog and BrownOut detection running ?) */
        if (system_brown_out_detection_enabled()) {
-               sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_NOWDTLOCK_BOD_ON;
-       } else {
-               sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_NOWDTLOCK_BOD_OFF;
+               tmp &= ~(LPC_POWER_DOWN_BOD);
+       }
+       if (watchdog_get_timer_val()) {
+               tmp &= ~(LPC_POWER_DOWN_WDT_OSC);
        }
+       sys_config->powerdown_sleep_cfg = tmp;
+
+       /* Use Self Wake-Up timer to wake-up ? */
+       if (self_wakeup != 0) {
+               self_wakeup_timer_start(self_wakeup);
+       }
+
+       /* Turn off PLL and move to IRC */
+       clock_config(FREQ_SEL_IRC);
+
        /* Enter deep sleep */
-       /* FIXME */
+       pmu->power_ctrl = LPC_PM_DEEP_SLEEP;
+       scb->scr = SCB_SCR_SLEEPDEEP;
+
+       wfi();
+
+       pmu->power_ctrl = LPC_PM_DEFAULT;
+       scb->scr = 0;
+
+       /* Go back to PLL */
+       clock_config(wakeup_freq_sel);
+}
+
+
+
+/* Prevent deep power down entry */
+void prevent_deep_power_down(void)
+{
+       struct lpc_pm_unit* pmu = LPC_PMU;
+       pmu->power_ctrl |= LPC_PM_NO_DPD;
 }
 
index d27f8fe..bea8966 100644 (file)
  * - do not enter "deep" sleep or power-down mode
  * - if "wake_sources_is_all_intrs" is set to 1, also wake up from unconfigured
  *   interrupt sources
+ * This function does not turn off systick if it is activated, so the system will
+ *   wake up on next tick.
  * This function returns after the next interrupt or event.
  */
 void enter_sleep(int wake_sources_is_all_intrs);
 
+/* Enter permanent sleep state
+ * - Return to sleep mode after interrupt or event
+ * - do not enter "deep" sleep or power-down mode
+ * - if "wake_sources_is_all_intrs" is set to 1, also wake up from unconfigured
+ *   interrupt sources
+ * This function does not turn off systick if it is activated.
+ * This function does not returns after the next interrupt or event, unless a call to
+ *   exit_sleep() happened in an interrupt handler.
+ */
+void enter_continuous_sleep(int wake_sources_is_all_intrs);
+
+/* Exit permanent sleep state
+ * Can be called from an interrupt handler to get back to thread mode at the end of
+ * the handler execution.
+ */
+void exit_sleep(void);
 
 /* Enter deep sleep.
  * NOTE : entering deep sleep implies a lot of side effects. I'll try to list them all here
- *        so this can be done right.
- *
- * Note : see remark about RTC and deep sleep in section 5.3.3 of UM10441
+ *  so this can be done right.
+ * - The PLL is stopped, system must switch back to IRC before entering deep sleep
+ * - The IRC is running, but it's output is disabled.
+ * - Memory retains it's state
+ * - Flash stays ON
+ * - The peripherals will be stopped
+ * - Systick will be stopped
+ * - If using the Self Wake-Up timer as wake-up source then it must be turned ON before
+ *   calling the function, and it MUST use either the 10KHZ clock or the external pin clock.
  */
-void enter_deep_sleep(void);
+void enter_deep_sleep(uint32_t wakeup_freq_sel, uint32_t self_wakeup);
+
+
+/* Prevent deep power down entry */
+void prevent_deep_power_down(void);
 
 
 /* Read from or write to the general purpose retain registers
  * Values in these registers are kept over resets, whatever the reset source, so long as
  *  the part remains powered. (Even accross watchdog resets or flashing the device !)
+ *
+ * Read (read_gp_retain()) or Write (save_gp_retain()) to the general purpose
+ *   retain registers.
+ * There are four 32 bits general purpose retain registers on the LPC82x uC.
+ * Data written to these registers is retained even across uC resets or
+ *   reprogrammations so long as the uC remains powered.
+ * These functions read (or write) "nb" contiguous "values" from (or to) general purpose
+ *   registers, starting from "offset" general purpose register.
+ * - "values" is a user provided buffer for the data read from (or to be written to)
+ *   the general purpose registers
+ * - "nb" is the buffer size.
+ * - "offset" is the first (starting at 0) general purpose register to be read
+ *   from (or written to)
+ * "offset + nb" must be <= 4
  */
 int save_gp_retain(uint32_t* values, uint8_t nb, uint8_t offset);
 int read_gp_retain(uint32_t* values, uint8_t nb, uint8_t offset);