diff options
Diffstat (limited to 'keyboards/keychron/bluetooth/lpm_stm32l432.c')
-rw-r--r-- | keyboards/keychron/bluetooth/lpm_stm32l432.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/keyboards/keychron/bluetooth/lpm_stm32l432.c b/keyboards/keychron/bluetooth/lpm_stm32l432.c new file mode 100644 index 0000000000..a871a1b53c --- /dev/null +++ b/keyboards/keychron/bluetooth/lpm_stm32l432.c @@ -0,0 +1,339 @@ +/* Copyright 2022 @ lokher (https://www.keychron.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/****************************************************************************** + * + * Filename: lpm_stm32l432.c + * + * Description: Contains low power mode implementation + * + ******************************************************************************/ + +#include "quantum.h" +#include <usb_main.h> +#include "bluetooth.h" +#include "indicator.h" +#include "lpm.h" +#include "transport.h" +#include "battery.h" +#include "report_buffer.h" + +extern pin_t row_pins[MATRIX_ROWS]; +extern void select_all_cols(void); +extern bluetooth_transport_t bluetooth_transport; + +static pm_t power_mode = PM_RUN; + +static inline void stm32_clock_fast_init(void); + +bool lpm_set(pm_t mode) { + switch (mode) { +#ifdef LOW_POWER_RUN_MODE_ENABLE + case PM_RUN: + if (power_mode != PM_LOW_POWER_RUN)) return; + /* Set main regulator */ + PWR->CR1 &= ~PWR_CR1_LPR; + while (PWR->SR2 & PWR_SR2_REGLPF) + ; + // TODO: restore sysclk + return true; + // break; + + case PM_LOW_POWER_RUN: + if (power_mode != PM_RUN) return; + + // FLASH->ACR |= FLASH_ACR_RUN_PD; // Optional + // TODO: Decrease sysclk below 2 MHz + PWR->CR1 |= PWR_CR1_LPR; + return true; + // break; +#endif + case PM_SLEEP: + /* Wake source: Any interrupt or event */ + if (power_mode != PM_RUN) return false; + + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + break; + +#ifdef LOW_POWER_RUN_MODE_ENABLE + case PM_LOW_POWER_SLEEP: + /* Wake source: Any interrupt or event */ + if (power_mode != PM_LOW_POWER_RUN) return; /* Can only transit from PM_LOW_POWER_RUN */ + + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + __WFI(); + exit_low_power_mode(); + break; +#endif + case PM_STOP0: + /* Wake source: Reset pin, all I/Os, BOR, PVD, PVM, RTC, LCD, IWDG, + COMPx, USARTx, LPUART1, I2Cx, LPTIMx, USB, SWPMI */ + if (power_mode != PM_RUN) return false; + + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR1 |= PWR_CR1_LPMS_STOP0; + break; + + case PM_STOP1: + /* Wake source: Reset pin, all I/Os, BOR, PVD, PVM, RTC, LCD, IWDG, + COMPx, USARTx, LPUART1, I2Cx, LPTIMx, USB, SWPMI */ + if (power_mode != PM_RUN && power_mode != PM_LOW_POWER_RUN) return false; + + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR1 |= PWR_CR1_LPMS_STOP1; + break; + + case PM_STOP2: + /* Wake source: Reset pin, all I/Os, BOR, PVD, PVM, RTC, LCD, IWDG, + COMPx (x=1, 2), I2C3, LPUART1, LPTIM1, LPTIM2 */ + if (power_mode != PM_RUN) return false; + + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR1 |= PWR_CR1_LPMS_STOP2; + break; + + case PM_STANDBY_WITH_RAM: + /* Wake source: Reset, 5 I/O(PA0, PC13, PE6, PA2, PC5), BOR, RTC, IWDG */ + if (power_mode != PM_RUN && power_mode != PM_LOW_POWER_RUN) return false; + + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR1 |= PWR_CR1_LPMS_STANDBY; + PWR->CR3 |= PWR_CR3_RRS; + break; + + case PM_STANDBY: + /* Wake source: Reset, 2 I/O(PA0, PA2) in STM32L432Kx,, BOR, RTC, IWDG */ + if (power_mode != PM_RUN && power_mode != PM_LOW_POWER_RUN) return false; + + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR1 |= PWR_CR1_LPMS_STANDBY; + PWR->CR3 &= ~PWR_CR3_RRS; + break; + + case PM_SHUTDOWN: + /* Wake source: Reset, 2 I/O(PA0, PA2) in STM32L432Kx, RTC */ + if (power_mode != PM_RUN && power_mode != PM_LOW_POWER_RUN) return false; + + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR1 |= PWR_CR1_LPMS_SHUTDOWN; + break; + + default: + return false; + } + + return true; +} + +static inline void enter_low_power_mode_prepare(void) { +#if HAL_USE_RTC + nvicEnableVector(STM32_EXTI20_NUMBER, 13); + + PWR->CR3 |= PWR_CR3_EIWF; + + RTCWakeup wakeupspec; + wakeupspec.wutr = 0x040014; + rtcSTM32SetPeriodicWakeup(&RTCD1, &wakeupspec); +#endif + +#if defined(KEEP_USB_CONNECTION_IN_BLUETOOTH_MODE) + /* Usb unit is actived and running, stop and disconnect first */ + usbStop(&USBD1); + usbDisconnectBus(&USBD1); + + /* Isolate USB to save power.*/ + PWR->CR2 &= ~PWR_CR2_USV; /*PWR_CR2_USV is available on STM32L4x2xx and STM32L4x3xx devices only. */ +#endif + + palEnableLineEvent(BLUETOOTH_INT_INPUT_PIN, PAL_EVENT_MODE_FALLING_EDGE); + palEnableLineEvent(USB_POWER_SENSE_PIN, PAL_EVENT_MODE_BOTH_EDGES); + + /* Enable key matrix wake up */ + pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; + + for (uint8_t x = 0; x < MATRIX_ROWS; x++) { + if (row_pins[x] != NO_PIN) { + palEnableLineEvent(row_pins[x], PAL_EVENT_MODE_BOTH_EDGES); + } + } + + select_all_cols(); + +#if defined(DIP_SWITCH_PINS) +# define NUMBER_OF_DIP_SWITCHES (sizeof(dip_switch_pad) / sizeof(pin_t)) + static pin_t dip_switch_pad[] = DIP_SWITCH_PINS; + + for (uint8_t i = 0; i < NUMBER_OF_DIP_SWITCHES; i++) { + setPinInputLow(dip_switch_pad[i]); + } +#endif +} + +static inline void lpm_wakeup(void) { + stm32_clock_fast_init(); + if (bluetooth_transport.init) bluetooth_transport.init(true); + + chSysLock(); + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + + PWR->SCR |= PWR_SCR_CWUF; + PWR->SCR |= PWR_SCR_CSBF; + +#if defined(KEEP_USB_CONNECTION_IN_BLUETOOTH_MODE) + /* Remove USB isolation.*/ + PWR->CR2 |= PWR_CR2_USV; /* PWR_CR2_USV is available on STM32L4x2xx and STM32L4x3xx devices only. */ + usb_start(&USBD1); +#endif + + /* TIMx is disable during stop/standby/sleep mode, init after wakeup */ + stInit(); + timer_init(); + chSysUnlock(); + battery_init(); + + /* Disable all wake up pins */ + for (uint8_t x = 0; x < MATRIX_ROWS; x++) { + if (row_pins[x] != NO_PIN) { + palDisableLineEvent(row_pins[x]); + } + } + palDisableLineEvent(BLUETOOTH_INT_INPUT_PIN); + palDisableLineEvent(USB_POWER_SENSE_PIN); + +#if defined(DIP_SWITCH_PINS) + dip_switch_init(); + dip_switch_read(true); +#endif + +#if HAL_USE_RTC + rtcSTM32SetPeriodicWakeup(&RTCD1, NULL); + nvicDisableVector(STM32_EXTI20_NUMBER); +#endif +} + +/* + * NOTE: + * 1. Shall not use PM_LOW_POWER_RUN, PM_LOW_POWER_SLEEP, due to PM_LOW_POWER_RUN + * need to decrease system clock below 2 MHz. Dynamic clock is not yet supported + * for STM32L432xx in latest ChibiOS 21.6.0 so far. + * 2. Care must be taken to use PM_STANDBY_WITH_RAM, PM_STANDBY, PM_SHUTDOWN due to + * limited wake source, thus can't be waken via keyscan. PM_SHUTDOWN need LSE. + * 3. Reference from AN4621: STM32L4 and STM32L4+ ultra-low-power features overview + * for detail wake source + */ + +void enter_power_mode(pm_t mode) { +#if defined(KEEP_USB_CONNECTION_IN_BLUETOOTH_MODE) + /* Don't enter low power mode if attached to the host */ + if (mode > PM_SLEEP && usb_power_connected()) return; +#endif + + if (!lpm_set(mode)) return; + enter_low_power_mode_prepare(); + + // __DSB(); + __WFI(); + // __ISB(); + + lpm_wakeup(); + lpm_timer_reset(); + report_buffer_init(); + power_mode = PM_RUN; +} + +void usb_power_connect(void) { + PWR->CR2 |= PWR_CR2_USV; +} + +void usb_power_disconnect(void) { + PWR->CR2 &= ~PWR_CR2_USV; +} + +/* + * This is a simplified version of stm32_clock_init() by removing unnecessary clock initlization + * code snippet. The original stm32_clock_init() take about 2ms, but ckbt51 sends data via uart + * about 200us after wakeup pin is assert, it means that we must get everything ready before + * uart data coming when wakeup pin interrupt of MCU is triggerred. + * Here we reduce clock init time to 100us. + */ +inline void stm32_clock_fast_init(void) { +#if !STM32_NO_INIT + /* Core voltage setup.*/ + PWR->CR1 = STM32_VOS; + while ((PWR->SR2 & PWR_SR2_VOSF) != 0) /* Wait until regulator is */ + ; /* stable. */ + +# if STM32_HSI16_ENABLED // 10.7us + /* HSI activation.*/ + RCC->CR |= RCC_CR_HSION; + while ((RCC->CR & RCC_CR_HSIRDY) == 0) + ; /* Wait until HSI16 is stable. */ +# endif + +# if STM32_CLOCK_HAS_HSI48 // 13us +# if STM32_HSI48_ENABLED + /* HSI activation.*/ + RCC->CRRCR |= RCC_CRRCR_HSI48ON; + while ((RCC->CRRCR & RCC_CRRCR_HSI48RDY) == 0) + ; /* Wait until HSI48 is stable. */ +# endif +# endif + +# if STM32_ACTIVATE_PLL || STM32_ACTIVATE_PLLSAI1 || STM32_ACTIVATE_PLLSAI2 + /* PLLM and PLLSRC are common to all PLLs.*/ +# if defined(STM32L496xx) || defined(STM32L4A6xx) + RCC->PLLCFGR = STM32_PLLPDIV | STM32_PLLR | STM32_PLLREN | STM32_PLLQ | STM32_PLLQEN | STM32_PLLP | STM32_PLLPEN | STM32_PLLN | STM32_PLLM | STM32_PLLSRC; +# else + RCC->PLLCFGR = STM32_PLLR | STM32_PLLREN | STM32_PLLQ | STM32_PLLQEN | STM32_PLLP | STM32_PLLPEN | STM32_PLLN | STM32_PLLM | STM32_PLLSRC; +# endif +# endif + +# if STM32_ACTIVATE_PLL + /* PLL activation.*/ + RCC->CR |= RCC_CR_PLLON; + + /* Waiting for PLL lock.*/ + while ((RCC->CR & RCC_CR_PLLRDY) == 0) + ; +# endif + + /* Set flash WS's for SYSCLK source */ + if (STM32_FLASHBITS > STM32_MSI_FLASHBITS) { + FLASH->ACR = (FLASH->ACR & ~FLASH_ACR_LATENCY_Msk) | STM32_FLASHBITS; + while ((FLASH->ACR & FLASH_ACR_LATENCY_Msk) != (STM32_FLASHBITS & FLASH_ACR_LATENCY_Msk)) { + } + } + + /* Switching to the configured SYSCLK source if it is different from MSI.*/ +# if (STM32_SW != STM32_SW_MSI) + RCC->CFGR |= STM32_SW; /* Switches on the selected clock source. */ + /* Wait until SYSCLK is stable.*/ + while ((RCC->CFGR & RCC_CFGR_SWS) != (STM32_SW << 2)) + ; +# endif + + /* Reduce the flash WS's for SYSCLK source if they are less than MSI WSs */ + if (STM32_FLASHBITS < STM32_MSI_FLASHBITS) { + FLASH->ACR = (FLASH->ACR & ~FLASH_ACR_LATENCY_Msk) | STM32_FLASHBITS; + while ((FLASH->ACR & FLASH_ACR_LATENCY_Msk) != (STM32_FLASHBITS & FLASH_ACR_LATENCY_Msk)) { + } + } +#endif /* STM32_NO_INIT */ + + /* SYSCFG clock enabled here because it is a multi-functional unit shared + among multiple drivers.*/ + rccEnableAPB2(RCC_APB2ENR_SYSCFGEN, true); +} |