Add support for deep sleep Wake-up not fully implemented, use NVIC_SystemReset()...
authorNathael Pajani <nathael.pajani@ed3l.fr>
Mon, 7 Jan 2019 22:21:37 +0000 (23:21 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
core/system.c
include/core/system.h

index c3e8f9c..02f3f5d 100644 (file)
@@ -28,6 +28,8 @@
  */
 
 #include "core/system.h"
+#include "core/pio.h"
+#include "drivers/gpio.h"
 
 
 /* Private defines */
@@ -105,26 +107,177 @@ void system_set_default_power_state(void)
        sys_config->sys_AHB_clk_ctrl = LPC_SYS_ABH_CLK_CTRL_MEM_ALL;
 }
 
-/* Enter deep sleep.
- * NOTE : entering deep sleep implies a lot of side effects. I'll try to list them all here
+/* Enter sleep.
+ * NOTE : entering sleep implies some 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
+ * Note : sleep mode is described in section 4.7.2 of UM10441
  */
-void enter_deep_sleep(void)
+void enter_sleep(void)
+{
+       struct lpc_pm_unit* pmu = LPC_PMU;
+       struct syst_ctrl_block_regs* scb = LPC_SCB;
+
+       /* Set sleep config */
+       pmu->power_ctrl &= ~(LPC_DPD_EN);
+       scb->scr &= ~(SCB_SCR_SLEEPDEEP);
+       /* Enter sleep */
+       wfi();
+}
+
+/* Enter deep sleep.
+ * IMPORTANT : Entering deep sleep implies a lot of side effects. I'll try to list them all
+ *    here so this can be done right.
+ *
+ * If pc_wake_up (peripheral controlled wake up) is 1, the watchdog clock will be enabled
+ * and set to the lowest possible rate.
+ * If wdt_off is 1 and both pc_wake_up is 0 and watchdog clock is not locked, then the
+ * watchdog clock will be turned off and wakeup will only be possible using external
+ * interrupt sources or RTC wakeup
+ *
+ * The programmer has to handle all special cases such as locked watchdog clock source
+ * which would prevent deep sleep entry.
+ * Refer to chapter 4.8 of UM10441 for Deep-sleep mode details
+ * Refer to chapter 4.7.3 of UM10441 for indications about how to enter Deep-sleep mode.
+ *
+ * If external pin wakeup is used the programmer must provide a "WAKEUP_Handler".
+ * This handler may either be empty, or hold the waking related code.
+ *
+ * Before calling this function, the programmer must :
+ *    stop the watchdog if it was configured (unless ths watchdog clock was locked)
+ *    flush communication buffers
+ *    configure used (and maybe unused) GPIO to output low in order to reduce power used
+ *       during deep sleep
+ *    turn off the systick_timer
+ *
+ * The code executed after the call to enter_deep_sleep() will run on the internal RC clock.
+ * The programmer has to restore the running state (system clock, PIO/GPIO configuration,
+ * systick timer, ...) according to his needs.
+ * Note that comparator input pins (port 0 pin 19 to 26) will have been turned to output low
+ * by the enter_deep_sleep() function.
+ * The easiest way to do so is to call NVIC_SystemReset() within the WAKEUP_Handler()
+ * callback, which will reset the micro-controller.
+ * This function will have to be completed to restore most of the system state, disable
+ * start logic interrupts, restore previous main clock, and so on.
+ *
+ * Note : See remark about RTC and deep sleep in section 5.3.3 of UM10441
+ */
+#include "drivers/serial.h"
+void enter_deep_sleep(int wdt_off, int pc_wake_up, int rtc_off, const struct wakeup_pin* wakers)
 {
        struct lpc_sys_config* sys_config = LPC_SYS_CONFIG;
+       struct lpc_pm_unit* pmu = LPC_PMU;
+       struct syst_ctrl_block_regs* scb = LPC_SCB;
+       struct lpc_watchdog* wdt = LPC_WDT;
+       struct lpc_sys_start_logic_ctrl* stlog = sys_config->start_log_ctrl;
+       uint32_t tmp = 0, old_sys_clk = 0;
+       int i = 0;
+
+       /* Avoid any interference */
+       lpc_disable_irq();
+
+       /* Do not try to turn off watchdog clock if it is locked */
+       if (wdt->mode & LPC_WDT_CLK_POWER_LOCK) {
+               wdt_off = 0;
+       }
+
+       /* Disable deep power down */
+       pmu->power_ctrl &= ~(LPC_DPD_EN);
+
+       /* Set deep-sleep  config */
+       if (wdt_off == 1) {
+               if (lpc_private.brown_out_detection_enabled) {
+                       sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_WD_OFF_BOD_ON;
+               } else {
+                       sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_WD_OFF_BOD_OFF;
+               }
+       } else {
+               if (lpc_private.brown_out_detection_enabled) {
+                       sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_WD_ON_BOD_ON;
+               } else {
+                       sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_WD_ON_BOD_OFF;
+               }
+       }
 
        /* Ask for the same clock status when waking up */
        sys_config->powerdown_wake_cfg = sys_config->powerdown_run_cfg;
-       /* Set deep_sleep config */
-       if (lpc_private.brown_out_detection_enabled) {
-               sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_NOWDTLOCK_BOD_ON;
+
+       /* Change current clock source */
+       if (pc_wake_up == 1) {
+               /* Switch to low-speed WDO (watchdog oscilator) */
+               sys_config->powerdown_run_cfg &= ~(LPC_POWER_DOWN_WDT_OSC);
+               sys_config->main_clk_sel = LPC_MAIN_CLK_SRC_WATCHDOG_OSC;
        } else {
-               sys_config->powerdown_sleep_cfg = LPC_DEEP_SLEEP_CFG_NOWDTLOCK_BOD_OFF;
+               sys_config->main_clk_sel = LPC_MAIN_CLK_SRC_IRC_OSC;
+       }
+       sys_config->main_clk_upd_en = 0;
+       sys_config->main_clk_upd_en = 1;
+       while (!(sys_config->main_clk_upd_en & 0x01));
+
+       /* Configure start logic for GPIO */
+       if (wakers != NULL) {
+               uint32_t ext_pin_bits = 0;
+               uint32_t ext_edges_rising = 0;
+               i = 0;
+               while (wakers[i].pin <= 11) {
+                       ext_pin_bits |= LPC_STL(wakers[i].pin);
+                       if (wakers[i].edge == EDGE_RISING) { /* Defined in drivers/gpio.h */
+                               ext_edges_rising |= LPC_STL(wakers[i].pin);
+                       }
+                       i++;
+               }
+               stlog[0].reset = ext_pin_bits;
+               stlog[0].edge_ctrl = ext_edges_rising;
+               stlog[0].signal_en = ext_pin_bits;
+       }
+       /* Enable the start logic interrupts in NVIC */
+       if (wakers != NULL) {
+               i = 0;
+               while (wakers[i].pin <= 11) {
+                       NVIC_EnableIRQ(wakers[i].pin);
+                       i++;
+               }
        }
+
+       /* FIXME : Configure start logic for RTC, Watchdog and BOD */
+
+
+       /* Turn all comparator inputs to GPIO, set them to output low */
+       {
+               const struct pio tmp_pins[] = {
+                       LPC_GPIO_0_19, LPC_GPIO_0_20, LPC_GPIO_0_21, LPC_GPIO_0_22,
+                       LPC_GPIO_0_23, LPC_GPIO_0_24, LPC_GPIO_0_25, LPC_GPIO_0_26,
+               };
+               for (i = 0; i < 8; i++) {
+                       config_gpio(&(tmp_pins[i]), LPC_IO_MODE_INACTIVE, GPIO_DIR_OUT, 0);
+               }
+       }
+
+       /* Configure sys_AHB_clk_ctrl to leave only Watchdog clock running */
+       tmp = LPC_SYS_ABH_CLK_CTRL_MEM_ALL;
+       if (wdt_off == 0) {
+               tmp |= LPC_SYS_ABH_CLK_CTRL_Watchdog;
+       }
+       if (rtc_off == 0) {
+               tmp |= LPC_SYS_ABH_CLK_CTRL_RTC;
+       }
+       old_sys_clk = sys_config->sys_AHB_clk_ctrl;
+       sys_config->sys_AHB_clk_ctrl = tmp;
+
        /* Enter deep sleep */
+       scb->scr |= SCB_SCR_SLEEPDEEP;
+       /* Turn interrupts on */
+       lpc_enable_irq();
+       wfi();
+
+       /* Avoid any interference ... again */
+       lpc_disable_irq();
+       /* Restore running state */
+       sys_config->sys_AHB_clk_ctrl = old_sys_clk;
        /* FIXME */
+
+       /* Turn interrupts on once again */
+       lpc_enable_irq();
 }
 
 /* Enter deep power down.
@@ -262,7 +415,7 @@ void clock_config(uint32_t freq_sel)
 
        /* And call all clock updaters */
        propagate_main_clock();
-       /* Turn interrupts on once again*/
+       /* Turn interrupts on once again */
        lpc_enable_irq();
 }
 
index b7be0bd..3959ad9 100644 (file)
@@ -30,6 +30,7 @@
 #include "core/lpc_regs.h"
 #include "core/lpc_core.h"
 #include "core/watchdog.h"
+#include "core/pio.h"
 
 
 /***************************************************************************** */
@@ -42,13 +43,59 @@ void system_set_default_power_state(void);
 /***************************************************************************** */
 /*                       Power                                                 */
 /***************************************************************************** */
-/* Enter deep sleep.
- * NOTE : entering deep sleep implies a lot of side effects. I'll try to list them all here
+/* Enter sleep.
+ * NOTE : entering sleep implies some 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
+ * Note : sleep mode is described in section 4.7.2 of UM10441
  */
-void enter_deep_sleep(void);
+void enter_sleep(void);
+
+
+struct wakeup_pin {
+       uint8_t pin;
+       uint8_t edge;
+};
+#define ARRAY_LAST_WAKER {0xFF, 0xFF}
+
+/* Enter deep sleep.
+ * IMPORTANT : Entering deep sleep implies a lot of side effects. I'll try to list them all
+ *    here so this can be done right.
+ *
+ * If pc_wake_up (peripheral controlled wake up) is 1, the watchdog clock will be enabled
+ * and set to the lowest possible rate.
+ * If wdt_off is 1 and both pc_wake_up is 0 and watchdog clock is not locked, then the
+ * watchdog clock will be turned off and wakeup will only be possible using external
+ * interrupt sources or RTC wakeup
+ *
+ * The programmer has to handle all special cases such as locked watchdog clock source
+ * which would prevent deep sleep entry.
+ * Refer to chapter 4.8 of UM10441 for Deep-sleep mode details
+ * Refer to chapter 4.7.3 of UM10441 for indications about how to enter Deep-sleep mode.
+ *
+ * If external pin wakeup is used the programmer must provide a "WAKEUP_Handler".
+ * This handler may either be empty, or hold the waking related code.
+ *
+ * Before calling this function, the programmer must :
+ *    stop the watchdog if it was configured (unless ths watchdog clock was locked)
+ *    flush communication buffers
+ *    configure used (and maybe unused) GPIO to output low in order to reduce power used
+ *       during deep sleep
+ *    turn off the systick_timer
+ *
+ * The code executed after the call to enter_deep_sleep() will run on the internal RC clock.
+ * The programmer has to restore the running state (system clock, PIO/GPIO configuration,
+ * systick timer, ...) according to his needs.
+ * Note that comparator input pins (port 0 pin 19 to 26) will have been turned to output low
+ * by the enter_deep_sleep() function.
+ * The easiest way to do so is to call NVIC_SystemReset() within the WAKEUP_Handler()
+ * callback, which will reset the micro-controller.
+ * This function will have to be completed to restore most of the system state, disable
+ * start logic interrupts, restore previous main clock, and so on.
+ *
+ * Note : See remark about RTC and deep sleep in section 5.3.3 of UM10441
+ */
+void enter_deep_sleep(int wdt_off, int pc_wake_up, int rtc_off, const struct wakeup_pin* wakers);
 
 /* Enter deep power down.
  * NOTE : entering deep power down implies a lot of side effects. I'll try to list them all here
@@ -128,6 +175,11 @@ struct lpc_sys_start_logic_ctrl
        volatile uint32_t reset;      /* 0x08 : reset Register 0  (-/W) */
        volatile uint32_t status;     /* 0x0C : status Register 0 (R/-) */
 };
+#define LPC_STLOGIC_EDGE_FALLING(x)  0x00
+#define LPC_STLOGIC_EDGE_RISING(x)   (0x01 << (x))
+#define LPC_STL(x) (0x01 << (x))
+
+
 struct lpc_sys_config
 {
        volatile uint32_t sys_mem_remap;   /* 0x000 System memory remap (R/W) */
@@ -237,8 +289,10 @@ struct lpc_sys_config
 #define LPC_POWER_DOWN_SYSPLL       (1 << 7)
 #define LPC_POWER_DOWN_COPARATOR    (1 << 15)
 
-#define LPC_DEEP_SLEEP_CFG_NOWDTLOCK_BOD_ON  0x0000FFF7
-#define LPC_DEEP_SLEEP_CFG_NOWDTLOCK_BOD_OFF 0x0000FFFF
+#define LPC_DEEP_SLEEP_CFG_WD_OFF_BOD_ON  0x0000FFF7
+#define LPC_DEEP_SLEEP_CFG_WD_OFF_BOD_OFF 0x0000FFFF
+#define LPC_DEEP_SLEEP_CFG_WD_ON_BOD_ON  0x0000FFB7
+#define LPC_DEEP_SLEEP_CFG_WD_ON_BOD_OFF 0x0000FFBF
 
 #define LPC_MAIN_CLK_SRC_IRC_OSC       0x00
 #define LPC_MAIN_CLK_SRC_PLL_IN        0x01