mirror of
https://github.com/Keychron/qmk_firmware.git
synced 2024-12-12 13:17:38 +06:00
324 lines
10 KiB
C
324 lines
10 KiB
C
/* 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"
|
|
#include "stm32_bd.inc"
|
|
|
|
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 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) {
|
|
chSysLock();
|
|
stm32_clock_fast_init();
|
|
chSysUnlock();
|
|
|
|
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;
|
|
|
|
/* 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);
|
|
|
|
#ifdef USB_POWER_SENSE_PIN
|
|
palDisableLineEvent(USB_POWER_SENSE_PIN);
|
|
|
|
# if defined(KEEP_USB_CONNECTION_IN_BLUETOOTH_MODE)
|
|
if (usb_power_connected()) {
|
|
hsi48_init();
|
|
/* Remove USB isolation.*/
|
|
// PWR->CR2 |= PWR_CR2_USV; /* PWR_CR2_USV is available on STM32L4x2xx and STM32L4x3xx devices only. */
|
|
usb_power_connect();
|
|
usb_start(&USBD1);
|
|
}
|
|
# endif
|
|
|
|
#endif
|
|
|
|
#if defined(DIP_SWITCH_PINS)
|
|
dip_switch_init();
|
|
dip_switch_read(true);
|
|
#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 data
|
|
* coming when wakeup pin interrupt of MCU is triggerred.
|
|
* Here we reduce clock init time to less than 100us.
|
|
*/
|
|
void stm32_clock_fast_init(void) {
|
|
#if !STM32_NO_INIT
|
|
/* Clocks setup.*/
|
|
msi_init(); // 6.x us
|
|
hsi16_init(); // 4.x us
|
|
|
|
/* PLLs activation, if required.*/
|
|
pll_init();
|
|
pllsai1_init();
|
|
pllsai2_init();
|
|
/* clang-format off */
|
|
/* Other clock-related settings (dividers, MCO etc).*/
|
|
RCC->CFGR = STM32_MCOPRE | STM32_MCOSEL | STM32_STOPWUCK |
|
|
STM32_PPRE2 | STM32_PPRE1 | STM32_HPRE;
|
|
/* CCIPR register initialization, note, must take care of the _OFF
|
|
pseudo settings.*/
|
|
{
|
|
uint32_t ccipr = STM32_DFSDMSEL | STM32_SWPMI1SEL | STM32_ADCSEL |
|
|
STM32_CLK48SEL | STM32_LPTIM2SEL | STM32_LPTIM1SEL |
|
|
STM32_I2C3SEL | STM32_I2C2SEL | STM32_I2C1SEL |
|
|
STM32_UART5SEL | STM32_UART4SEL | STM32_USART3SEL |
|
|
STM32_USART2SEL | STM32_USART1SEL | STM32_LPUART1SEL;
|
|
/* clang-format on */
|
|
# if STM32_SAI2SEL != STM32_SAI2SEL_OFF
|
|
ccipr |= STM32_SAI2SEL;
|
|
# endif
|
|
# if STM32_SAI1SEL != STM32_SAI1SEL_OFF
|
|
ccipr |= STM32_SAI1SEL;
|
|
# endif
|
|
RCC->CCIPR = ccipr;
|
|
}
|
|
|
|
/* 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 */
|
|
}
|