From d98cabbf54ac579c1dd85a1cc34bf5af2baee3f9 Mon Sep 17 00:00:00 2001 From: schneider <schneider@blinkenlichts.net> Date: Mon, 9 Sep 2019 02:35:04 +0200 Subject: [PATCH] feat(sleep): Implement "deep" sleep Also modifies the PMIC UI code to be more intuitive --- epicardium/modules/hardware.c | 7 + epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 3 + epicardium/modules/pmic.c | 145 +++++++++---- epicardium/modules/sleep.c | 202 ++++++++++++++++++ lib/card10/card10.c | 6 + lib/card10/pmic.c | 5 +- .../Maxim/MAX32665/Source/system_max32665.c | 3 +- 8 files changed, 329 insertions(+), 43 deletions(-) create mode 100644 epicardium/modules/sleep.c diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index 370f9afb..aeb77c3a 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -54,6 +54,13 @@ int hardware_early_init(void) */ GPIO_Init(); + /* Set the power hold pin, so the PMIC does not turn off again */ + const gpio_cfg_t pwr_hold_pin = { + PORT_0, PIN_30, GPIO_FUNC_OUT, GPIO_PAD_NONE + }; + GPIO_Config(&pwr_hold_pin); + GPIO_OutSet(&pwr_hold_pin); + /* * PMIC (MAX77650) */ diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index e943af4f..dd74e322 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -17,6 +17,7 @@ module_sources = files( 'pmic.c', 'rtc.c', 'serial.c', + 'sleep.c', 'stream.c', 'trng.c', 'vibra.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 330f4f67..fb65a737 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -111,4 +111,7 @@ void max30001_mutex_init(void); #define MAX30001_MUTEX_WAIT_MS 50 extern gpio_cfg_t gpio_configs[]; + +/* ---------- Sleep -------------------------------------------------------- */ +void sleep_deepsleep(void); #endif /* MODULES_H */ diff --git a/epicardium/modules/pmic.c b/epicardium/modules/pmic.c index f8760532..921923c9 100644 --- a/epicardium/modules/pmic.c +++ b/epicardium/modules/pmic.c @@ -136,37 +136,22 @@ done: * Read the interrupt flag register and handle all interrupts which the PMIC has * sent. In most cases this will be the buttons. */ -static void -pmic_poll_interrupts(TickType_t *button_start_tick, TickType_t duration) +static uint8_t pmic_poll_interrupts(void) { while (hwlock_acquire(HWLOCK_I2C, LOCK_WAIT) < 0) { LOG_WARN("pmic", "Failed to acquire I2C. Retrying ..."); - xTaskNotify(pmic_task_id, PMIC_NOTIFY_IRQ, eSetBits); - return; + vTaskDelay(pdMS_TO_TICKS(100)); } uint8_t int_flag = MAX77650_getINT_GLBL(); hwlock_release(HWLOCK_I2C); - if (int_flag & MAX77650_INT_nEN_F) { - /* Button was pressed */ - *button_start_tick = xTaskGetTickCount(); - } - if (int_flag & MAX77650_INT_nEN_R) { - /* Button was released */ - *button_start_tick = 0; - if (duration < pdMS_TO_TICKS(400)) { - return_to_menu(); - } else { - LOG_WARN("pmic", "Resetting ..."); - card10_reset(); - } - } - /* TODO: Remove when all interrupts are handled */ if (int_flag & ~(MAX77650_INT_nEN_F | MAX77650_INT_nEN_R)) { LOG_WARN("pmic", "Unhandled PMIC Interrupt: %x", int_flag); } + + return int_flag; } __attribute__((noreturn)) static void pmic_die(float u_batt) @@ -192,11 +177,15 @@ __attribute__((noreturn)) static void pmic_die(float u_batt) for (int i = 0; i < 50000000; i++) __NOP(); - LOG_WARN("pmic", "Poweroff"); - MAX77650_setSFT_RST(0x2); + /* We have some of headroom to keep the RTC going. + * The battery protection circuit will shut down + * the system at 3.0 V */ + /* TODO: Wake-up when USB is attached again */ + sleep_deepsleep(); + card10_reset(); while (1) - __WFI(); + ; } /* @@ -291,7 +280,9 @@ static void vPmicTimerCb(TimerHandle_t xTimer) void vPmicTask(void *pvParameters) { - pmic_task_id = xTaskGetCurrentTaskHandle(); + pmic_task_id = xTaskGetCurrentTaskHandle(); + uint8_t interrupts = 0; + uint32_t reason = 0; ADC_Init(0x9, NULL); GPIO_Config(&gpio_cfg_adc0); @@ -314,33 +305,107 @@ void vPmicTask(void *pvParameters) } xTimerStart(pmic_timer, 0); - /* - * Poll once before going to sleep in case the PMIC had triggered an - * interrupt already. This can occur, for example, if the user presses - * the power-button during the version splash-screen. - */ - pmic_poll_interrupts(&button_start_tick, 0); + /* Clear all pending interrupts. */ + pmic_poll_interrupts(); while (1) { - uint32_t reason; - if (button_start_tick == 0) { - reason = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - } else { - reason = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)); + interrupts |= pmic_poll_interrupts(); + if (interrupts == 0) { + reason |= ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } - TickType_t duration = xTaskGetTickCount() - button_start_tick; + /* New interrupts */ + if (reason & PMIC_NOTIFY_IRQ) { + reason ^= PMIC_NOTIFY_IRQ; + interrupts |= pmic_poll_interrupts(); + } - if (button_start_tick != 0 && duration > pdMS_TO_TICKS(1000)) { - LOG_WARN("pmic", "Poweroff"); - MAX77650_setSFT_RST(0x2); + if (interrupts & MAX77650_INT_nEN_R) { + /* Ignored in this state */ + /* This can happen if the button is pressed + * during boot and released now. */ + interrupts ^= MAX77650_INT_nEN_R; /* Mark as handled. */ } - if (reason & PMIC_NOTIFY_IRQ) { - pmic_poll_interrupts(&button_start_tick, duration); + if (interrupts & MAX77650_INT_nEN_F) { + /* Button was pressed */ + interrupts ^= MAX77650_INT_nEN_F; /* Mark as handled. */ + + button_start_tick = xTaskGetTickCount(); + while (true) { + TickType_t duration = + xTaskGetTickCount() - button_start_tick; + + if (duration > 1000) { + disp_forcelock(); + epic_disp_clear(0x0000); + + char buf[20]; + sprintf(buf, + "Off in %d", + 7 - (int)(duration + 500) / + 1000); + epic_disp_print( + 0, + 0, + "Sleep zZz..", + 0xffff, + 0x0000 + ); + epic_disp_print( + 0, 25, buf, 0xf000, 0x0000 + ); + epic_disp_print( + 0, + 50, + " Reset ->", + 0xffff, + 0x0000 + ); + epic_disp_update(); + } + + if (duration >= pdMS_TO_TICKS(1000)) { + if (epic_buttons_read( + BUTTON_RIGHT_TOP)) { + LOG_WARN( + "pmic", + "Resetting ..." + ); + card10_reset(); + } + } + + if (interrupts & MAX77650_INT_nEN_R) { + /* Button is released */ + interrupts ^= + MAX77650_INT_nEN_R; /* Mark as handled. */ + + if (duration < pdMS_TO_TICKS(1000)) { + return_to_menu(); + } + + if (duration > pdMS_TO_TICKS(1000)) { + LOG_WARN("pmic", "Poweroff"); + sleep_deepsleep(); + card10_reset(); + } + break; + } + + reason |= ulTaskNotifyTake( + pdTRUE, pdMS_TO_TICKS(200) + ); + if (reason & PMIC_NOTIFY_IRQ) { + /* New interrupts */ + reason ^= PMIC_NOTIFY_IRQ; + interrupts |= pmic_poll_interrupts(); + } + } } if (reason & PMIC_NOTIFY_MONITOR) { + reason ^= PMIC_NOTIFY_MONITOR; pmic_check_battery(); } } diff --git a/epicardium/modules/sleep.c b/epicardium/modules/sleep.c new file mode 100644 index 00000000..a87bcb0c --- /dev/null +++ b/epicardium/modules/sleep.c @@ -0,0 +1,202 @@ +#include "epicardium.h" +#include "modules/modules.h" +#include "modules/log.h" + +#include "card10.h" +#include "simo.h" +#include "lp.h" +#include "max86150.h" +#include "MAX77650-Arduino-Library.h" + +#include "max32665.h" +#include "mxc_sys.h" +#include "mxc_pins.h" + +#include <stdint.h> + +/* Most code is taken and adapted rom EvKitExamples/LP/main.c */ + +static uint32_t old_clkcn; +static uint32_t old_perckcn0, old_perckcn1; + +void GPIOWAKE_IRQHandler(void) +{ + /* Nothing to do here */ +} + +static void switchToHIRC(void) +{ + MXC_GCR->clkcn &= ~(MXC_S_GCR_CLKCN_PSC_DIV128); + MXC_GCR->clkcn |= MXC_S_GCR_CLKCN_PSC_DIV4; + MXC_GCR->clkcn |= MXC_F_GCR_CLKCN_HIRC_EN; + + MXC_SETFIELD( + MXC_GCR->clkcn, + MXC_F_GCR_CLKCN_CLKSEL, + MXC_S_GCR_CLKCN_CLKSEL_HIRC + ); + + while (!(MXC_GCR->clkcn & MXC_F_GCR_CLKCN_CKRDY)) + ; /* Wait for the clock switch to occur */ + + /* Disable the now unused 96 MHz clock */ + MXC_GCR->clkcn &= ~(MXC_F_GCR_CLKCN_HIRC96M_EN); + + SystemCoreClockUpdate(); +} + +static void deepsleep(void) +{ + SIMO_setVregO_B(810); /* Reduce VCOREB to 0.81 V */ + LP_FastWakeupEnable(); + LP_EnterDeepSleepMode(); + SIMO_setVregO_B(1000); /* Restore VCOREB to 1 V */ +} + +static void turnOffClocks(void) +{ + old_perckcn0 = MXC_GCR->perckcn0; + old_perckcn1 = MXC_GCR->perckcn1; + + /* Allow the USB Switch to be turned off in deepsleep and backup modes. */ + /* TODO: should this be the default setting? */ + LP_USBSWLPDisable(); + + /* Disable all peripheral clocks except GPIO0 (needed for interrupt wakeup) */ + MXC_GCR->perckcn0 = 0xFFFFFFFE; + MXC_GCR->perckcn1 = 0xFFFFFFFF; +} + +static void turnOnClocks(void) +{ + MXC_GCR->perckcn0 = old_perckcn0; + MXC_GCR->perckcn1 = old_perckcn1; +} + +/* + * Move most GPIOs into a special low power state with the fact + * in mind that the external 3.3 V are switched off. + * + * E.g. this means that the SD card pins need to be pulled low + * to preven them from backfeeding into the 3.3 V rail via their + * external pull-up resistors. + * + * Pins needed to talk to the PMIC are left untouched. + * ECG AOUT and 32 kHz out as well. + */ +static void gpio_low_power(void) +{ + /* clang-format off */ + const gpio_cfg_t pins_low_power[] = { + { PORT_0, PIN_0, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // FLash + { PORT_0, PIN_1, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash + { PORT_0, PIN_2, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash + { PORT_0, PIN_3, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash + { PORT_0, PIN_4, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash + { PORT_0, PIN_5, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash + { PORT_0, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // I2C 3.3V + { PORT_0, PIN_7, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // I2C 3.3V + { PORT_0, PIN_8, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // Motor + { PORT_0, PIN_9, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // UART TX + { PORT_0, PIN_10, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // UART RX + { PORT_0, PIN_11, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // BMA400 interrupt + // 0, 12: PMIC IRQ + { PORT_0, PIN_13, GPIO_FUNC_IN, GPIO_PAD_NONE }, // BHI160 interrupt, not sure if PP + // 0, 14: PMIC I2C + // 0, 15: PMIC I2C + { PORT_0, PIN_16, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // PMIC AMUX + { PORT_0, PIN_17, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // SOA GPIO + // 0, 18: ECG AOUT + // 0, 19: 32 kHz + { PORT_0, PIN_20, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 1 + { PORT_0, PIN_21, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 2 + { PORT_0, PIN_22, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 3 + { PORT_0, PIN_23, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // IR-LED + { PORT_0, PIN_24, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display SS + { PORT_0, PIN_25, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display MOSI + { PORT_0, PIN_26, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // SOA GPIO + { PORT_0, PIN_27, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display SCK + { PORT_0, PIN_28, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // Backlight + { PORT_0, PIN_29, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 4 + // 0, 30: PMIC power hold + { PORT_0, PIN_31, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // ECG switch + + { PORT_1, PIN_0, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC + { PORT_1, PIN_1, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC + { PORT_1, PIN_2, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC + { PORT_1, PIN_3, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC + { PORT_1, PIN_4, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC + { PORT_1, PIN_5, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC + { PORT_1, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display RS + { PORT_1, PIN_7, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Portexpander interrupt + { PORT_1, PIN_8, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG CS TODO: better OUT high + { PORT_1, PIN_9, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // ECG SDI + { PORT_1, PIN_10, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG SDO + { PORT_1, PIN_11, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG SCK + { PORT_1, PIN_11, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG INT + { PORT_1, PIN_13, GPIO_FUNC_IN, GPIO_PAD_NONE }, // PPG Interrupt + { PORT_1, PIN_14, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // RGB LEDs + { PORT_1, PIN_15, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // RGB LEDs + }; + /* clang-format on */ + + const unsigned int num_pins = + (sizeof(pins_low_power) / sizeof(gpio_cfg_t)); + int i; + for (i = 0; i < num_pins; i++) { + GPIO_OutClr(&pins_low_power[i]); + GPIO_Config(&pins_low_power[i]); + } +} + +/* + * Go to deepsleep, turning off peripherals. + * + * This functions returns after waking up again. + * Currently the only wakeup source is an interrupt + * from the PMIC. + * + * Some peripherals and GPIOs are moved into a low + * power state before going to sleep. There is no guarantee + * that all peripherals will be moved to a low power state + * + * TODO: Move BHI160, BMA400, BME680, MAX30001 into a low + * power state. + * + * The state of the GPIOs and generally all peripherials + * on board is not restored after a wakeup. + */ +void sleep_deepsleep(void) +{ + LOG_WARN("pmic", "Powersave"); + epic_disp_backlight(0); + epic_leds_set_rocket(0, 0); + epic_leds_set_rocket(1, 0); + epic_leds_set_rocket(2, 0); +#if 1 + /* This will fail if there is no + * harmonic board attached */ + max86150_begin(); + max86150_getINT1(); + max86150_getINT2(); + max86150_shutDown(); +#endif + MAX77650_setEN_SBB2(0b100); + MAX77650_setSBIA_LPM(true); + core1_stop(); + MAX77650_getINT_GLBL(); + gpio_low_power(); + turnOffClocks(); + old_clkcn = MXC_GCR->clkcn; + switchToHIRC(); + deepsleep(); + + /* Now wait for an interrupt to wake us up */ + + turnOnClocks(); + MXC_GCR->clkcn = old_clkcn; + while (!(MXC_GCR->clkcn & MXC_F_GCR_CLKCN_CKRDY)) + ; /* Wait for the clock switch to occur */ + SystemCoreClockUpdate(); + MAX77650_setEN_SBB2(0b110); +} diff --git a/lib/card10/card10.c b/lib/card10/card10.c index 306d9f4d..09cc44ff 100644 --- a/lib/card10/card10.c +++ b/lib/card10/card10.c @@ -29,6 +29,10 @@ const gpio_cfg_t bhi_interrupt_pin = { PORT_0, PIN_13, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }; +static const gpio_cfg_t pwr_hold_pin = { + PORT_0, PIN_30, GPIO_FUNC_OUT, GPIO_PAD_NONE +}; + void card10_init(void) { printf("card10 init...\n"); @@ -41,6 +45,8 @@ void card10_init(void) I2C_Init(MXC_I2C1_BUS0, I2C_FAST_MODE, NULL); GPIO_Init(); + GPIO_Config(&pwr_hold_pin); + GPIO_OutSet(&pwr_hold_pin); pmic_init(); pmic_set_led(0, 0); diff --git a/lib/card10/pmic.c b/lib/card10/pmic.c index e60cf81a..609efc1c 100644 --- a/lib/card10/pmic.c +++ b/lib/card10/pmic.c @@ -1,5 +1,6 @@ #include "i2c.h" #include "pmic.h" +#include "lp.h" #include "MAX77650-Arduino-Library.h" #include <stdint.h> #include <stdio.h> @@ -45,7 +46,7 @@ void pmic_init(void) //MAX77650_setTV_SBB2(0b110100); //Set output Voltage of SBB2 to 5.0V MAX77650_setTV_SBB2(0b010010); //Set output Voltage of SBB2 to 3.3V #endif - MAX77650_setADE_SBB2(0b0); //Disable Active Discharge at SBB2 Output + MAX77650_setADE_SBB2(0b1); //Enable Active Discharge at SBB2 Output MAX77650_setEN_SBB2( 0b110); //Enable SBB2 is on irrespective of FPS whenever the on/off controller is in its "On via Software" or "On via On/Off Controller" states @@ -90,6 +91,8 @@ void pmic_init(void) ); NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(pmic_interrupt_pin.port)); + /* Allow the PMIC to interrupt us in deepsleep */ + LP_EnableGPIOWakeup((gpio_cfg_t *)&pmic_interrupt_pin); /* Setup power button interrupt */ MAX77650_setINT_M_GLBL(~(MAX77650_INT_nEN_R | MAX77650_INT_nEN_F)); /* Clear existing interrupts */ diff --git a/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Source/system_max32665.c b/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Source/system_max32665.c index 569913e2..90c4f0a0 100644 --- a/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Source/system_max32665.c +++ b/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Source/system_max32665.c @@ -169,7 +169,6 @@ __weak void SystemInit(void) MXC_GPIO0->vssel |= (1UL << 20) | (1UL << 21) | (1UL << 22) | (1UL << 29); // Wristband MXC_GPIO0->vssel |= (1UL << 17) | (1UL << 23) | (1UL << 28); // GPIO to TOP MXC_GPIO0->vssel |= (1UL << 24) | (1UL << 25) | (1UL << 26) | (1UL << 27); // SPI to TOP - MXC_GPIO0->vssel |= (1UL << 31); // ECG Switch MXC_GPIO0->ps |= 0xFFFFFFFF; MXC_GPIO0->pad_cfg1 |= 0xFFFFFFFF; @@ -178,7 +177,7 @@ __weak void SystemInit(void) // All GPIO on port 1 to 1.8 V first MXC_GPIO1->vssel = 0; MXC_GPIO1->vssel |= (1UL << 0) | (1UL << 1) | (1UL << 2) | (1UL << 3) | (1UL << 4) | (1UL << 5); // SDHC - MXC_GPIO1->vssel |= (1 << 6) | (1 << 7); // GPIO to TOP + MXC_GPIO1->vssel |= (1 << 6); // GPIO to TOP MXC_GPIO1->vssel |= (1 << 14) | (1 << 15); // GPIO for RGB LEDs #if BOARD_EVKIT -- GitLab