From 43f07fa261e6f5ed3512af5eb5147010dc247cd3 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 22 Apr 2024 15:01:46 +0200 Subject: [PATCH] cpu/msp430: implement power management This implements `pm_set_lowest()` for the MSP430. Unlike most other platforms, it intentionally does not use pm_layered. It is pretty similar to `pm_layered` in that is does use reference counters, but it uses them for two independent clock sources. The main difference is that the low frequency clock domain can be disabled even when the high frequency clock is still active. With the layers, disabling layer n-1 while layer n is still blocked would not work. --- cpu/msp430/clock.c | 57 ++++++++++++++++++++++++++ cpu/msp430/include/cpu.h | 13 ++++++ cpu/msp430/include/periph_cpu_common.h | 33 +++++++++++++++ cpu/msp430/periph/timer.c | 33 ++++++++++++--- cpu/msp430/periph/usart.c | 32 ++++++++++++++- cpu/msp430/periph/usci.c | 23 +++++++++++ 6 files changed, 184 insertions(+), 7 deletions(-) diff --git a/cpu/msp430/clock.c b/cpu/msp430/clock.c index 6f90ac3f1633..6a76bedee907 100644 --- a/cpu/msp430/clock.c +++ b/cpu/msp430/clock.c @@ -20,9 +20,11 @@ * @} */ +#include #include #include +#include "atomic_utils.h" #include "busy_wait.h" #include "macros/math.h" #include "macros/units.h" @@ -38,6 +40,7 @@ #endif uint32_t msp430_dco_freq; +static uint8_t msp430_clock_refcounts[MSP430_CLOCK_NUMOF]; static inline bool is_dco_in_use(const msp430_clock_params_t *params) { @@ -361,3 +364,57 @@ uint32_t PURE msp430_auxiliary_clock_freq(void) uint16_t shift = (clock_params.auxiliary_clock_divier >> 4) & 0x3; return clock_params.lfxt1_frequency >> shift; } + +void msp430_clock_acquire(msp430_clock_t clock) +{ + assume((unsigned)clock < MSP430_CLOCK_NUMOF); + uint8_t before = atomic_fetch_add_u8(&msp430_clock_refcounts[clock], 1); + (void)before; + assert(before < UINT8_MAX); +} + +void msp430_clock_release(msp430_clock_t clock) +{ + assume((unsigned)clock < MSP430_CLOCK_NUMOF); + uint8_t before = atomic_fetch_sub_u8(&msp430_clock_refcounts[clock], 1); + (void)before; + assert(before > 0); +} + +void pm_set_lowest(void) +{ + /* disable IRQs, wait two cycles for this to take effect, backup + * state register */ + uint16_t state; + __asm__ volatile( + "bic %[gie], SR" "\n\t" + "nop" "\n\t" + "nop" "\n\t" + "mov.w SR, %[state]" "\n\t" + : [state] "=r"(state) + : [gie] "i"(GIE) + : "memory" + ); + + /* When applying the power safe mode, we want to be able to wake up again. + * So set global interrupt enable then. */ + state |= GIE; + /* disabling CPU works always, even when keeping the clocks running */ + state |= CPUOFF | SCG0; + + if (msp430_clock_refcounts[MSP430_CLOCK_SUBMAIN] == 0) { + state |= SCG1; + } + + if (msp430_clock_refcounts[MSP430_CLOCK_AUXILIARY] == 0) { + state |= OSCOFF; + } + + /* write new state */ + __asm__ volatile( + "mov.w %[state], SR" "\n\t" + : /* no outputs */ + : [state] "r"(state) + : "memory" + ); +} diff --git a/cpu/msp430/include/cpu.h b/cpu/msp430/include/cpu.h index 216b2e26a4b6..6f3895cdcf2f 100644 --- a/cpu/msp430/include/cpu.h +++ b/cpu/msp430/include/cpu.h @@ -36,6 +36,11 @@ extern "C" { */ #define WORDSIZE 16 +/** + * @brief MSP430 has power management support + */ +#define PROVIDES_PM_SET_LOWEST + /** * @brief Macro for defining interrupt service routines */ @@ -94,6 +99,14 @@ static inline void __attribute__((always_inline)) __restore_context(void) */ static inline void __attribute__((always_inline)) __enter_isr(void) { + /* modify state register pushed to stack to not got to power saving + * mode right again */ + __asm__ volatile( + "bic %[mask], 0(SP)" "\n\t" + : /* no outputs */ + : [mask] "i"(CPUOFF | SCG0 | SCG1 | OSCOFF) + : "memory" + ); extern char __stack; /* defined by linker script to end of RAM */ __save_context(); __asm__("mov.w %0,r1" : : "i"(&__stack)); diff --git a/cpu/msp430/include/periph_cpu_common.h b/cpu/msp430/include/periph_cpu_common.h index 8034e0d79dd2..f23099cfd055 100644 --- a/cpu/msp430/include/periph_cpu_common.h +++ b/cpu/msp430/include/periph_cpu_common.h @@ -311,6 +311,17 @@ typedef enum { TIMER_CLOCK_SOURCE_INCLK = TXSSEL_INCLK, /**< External INCLK as clock source */ } msp430_timer_clock_source_t; +/** + * @brief IDs of the different clock domains on the MSP430 + * + * These can be used as internal clock sources for peripherals + */ +typedef enum { + MSP430_CLOCK_SUBMAIN, /**< Subsystem main clock */ + MSP430_CLOCK_AUXILIARY, /**< Auxiliary clock */ + MSP430_CLOCK_NUMOF, /**< Number of clock domains */ +} msp430_clock_t; + /** * @brief Timer configuration on an MSP430 timer */ @@ -367,6 +378,28 @@ uint32_t PURE msp430_submain_clock_freq(void); */ uint32_t PURE msp430_auxiliary_clock_freq(void); +/** + * @brief Increase the refcount of the given clock + * + * @param[in] clock clock domain to acquire + * + * @warning This is an internal function and must only be called from + * peripheral drivers + * @note An assertion will blow when the count exceeds capacity + */ +void msp430_clock_acquire(msp430_clock_t clock); + +/** + * @brief Decrease the refcount of the subsystem main clock + * + * @param[in] clock clock domain to acquire + * + * @warning This is an internal function and must only be called from + * peripheral drivers + * @note An assertion will blow when the count drops below zero + */ +void msp430_clock_release(msp430_clock_t clock); + #ifdef __cplusplus } #endif diff --git a/cpu/msp430/periph/timer.c b/cpu/msp430/periph/timer.c index b22b5dd5243f..10734926987d 100644 --- a/cpu/msp430/periph/timer.c +++ b/cpu/msp430/periph/timer.c @@ -30,8 +30,6 @@ #include "compiler_hints.h" #include "cpu.h" #include "periph/timer.h" -#include "periph_conf.h" -#include "periph_cpu.h" /** * @brief Interrupt context for each configured timer @@ -114,8 +112,8 @@ int timer_init(tim_t dev, uint32_t freq, timer_cb_t cb, void *arg) for (unsigned i = 0; i < timer_query_channel_numof(dev); i++) { msptimer->CCTL[i] = 0; } - /* start the timer in continuous mode */ - msptimer->CTL = ctl | TXMC_CONT; + + timer_start(dev); return 0; } @@ -157,6 +155,18 @@ void timer_start(tim_t dev) { assume((unsigned)dev < TIMER_NUMOF); msp430_timer_t *msptimer = timer_conf[dev].timer; + /* acquire clock */ + switch (timer_conf[dev].clock_source) { + case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK: + msp430_clock_acquire(MSP430_CLOCK_SUBMAIN); + break; + case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK: + msp430_clock_acquire(MSP430_CLOCK_AUXILIARY); + break; + default: + /* external clock source, safe to disable internal clocks */ + break; + } msptimer->CTL |= TXMC_CONT; } @@ -164,7 +174,20 @@ void timer_stop(tim_t dev) { assume((unsigned)dev < TIMER_NUMOF); msp430_timer_t *msptimer = timer_conf[dev].timer; - msptimer->CTL &= ~(TXMC_MASK); + msptimer->CTL &= ~(TXMC_CONT); + + /* release clock */ + switch (timer_conf[dev].clock_source) { + case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK: + msp430_clock_release(MSP430_CLOCK_SUBMAIN); + break; + case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK: + msp430_clock_release(MSP430_CLOCK_AUXILIARY); + break; + default: + /* external clock source, nothing to release */ + break; + } } __attribute__((pure)) diff --git a/cpu/msp430/periph/usart.c b/cpu/msp430/periph/usart.c index 27585cd269b5..269156ba2114 100644 --- a/cpu/msp430/periph/usart.c +++ b/cpu/msp430/periph/usart.c @@ -64,9 +64,12 @@ static mutex_t usart_locks[USART_NUMOF] = { MUTEX_INIT, }; +/* store the clock acquired by each USART, so it can be release again */ +static msp430_usart_clk_t _clocks_acquired[USART_NUMOF]; + void msp430_usart_acquire(const msp430_usart_params_t *params, - const msp430_usart_conf_t *conf, - uint8_t enable_mask) + const msp430_usart_conf_t *conf, + uint8_t enable_mask) { assume(params->num < USART_NUMOF); @@ -74,6 +77,19 @@ void msp430_usart_acquire(const msp430_usart_params_t *params, msp430_usart_t *dev = params->dev; msp430_usart_sfr_t *sfr = params->sfr; + _clocks_acquired[params->num] = conf->prescaler.clk_source; + switch (_clocks_acquired[params->num]) { + case USART_CLK_SUBMAIN: + msp430_clock_acquire(MSP430_CLOCK_SUBMAIN); + break; + case USART_CLK_AUX: + msp430_clock_acquire(MSP430_CLOCK_AUXILIARY); + break; + default: + /* external clock from GPIO, safe to disable internal clocks */ + break; + } + /* first, make sure USART is off before reconfiguring it */ sfr->ME = 0; /* reset USART */ @@ -105,6 +121,18 @@ void msp430_usart_release(const msp430_usart_params_t *params) sfr->IE = 0; sfr->IFG = 0; + switch (_clocks_acquired[params->num]) { + case USART_CLK_SUBMAIN: + msp430_clock_release(MSP430_CLOCK_SUBMAIN); + break; + case USART_CLK_AUX: + msp430_clock_release(MSP430_CLOCK_AUXILIARY); + break; + default: + /* external clock from GPIO, not managed here */ + break; + } + /* Release mutex */ mutex_unlock(&usart_locks[params->num]); } diff --git a/cpu/msp430/periph/usci.c b/cpu/msp430/periph/usci.c index 9ebbf183e42e..eb5daa57099e 100644 --- a/cpu/msp430/periph/usci.c +++ b/cpu/msp430/periph/usci.c @@ -93,6 +93,8 @@ static mutex_t _usci_locks[MSP430_USCI_ID_NUMOF] = { MUTEX_INIT, }; +static uint8_t _auxiliary_clock_acquired; + void msp430_usci_acquire(const msp430_usci_params_t *params, const msp430_usci_conf_t *conf) { @@ -101,6 +103,23 @@ void msp430_usci_acquire(const msp430_usci_params_t *params, mutex_lock(&_usci_locks[params->id]); msp430_usci_b_t *dev = params->dev; + /* We only need to acquire the auxiliary (low frequency) clock domain, as + * the subsystem main clock (SMCLK) will be acquired on-demand when activity + * is detected on RXD, as per datasheet: + * + * > The USCI module provides automatic clock activation for SMCLK for use + * > with low-power modes. + */ + switch (conf->prescaler.clk_source) { + case USCI_CLK_AUX: + msp430_clock_acquire(MSP430_CLOCK_AUXILIARY); + _auxiliary_clock_acquired |= 1U << params->id; + break; + default: + _auxiliary_clock_acquired &= ~(1U << params->id); + break; + } + /* put device in disabled/reset state */ dev->CTL1 = UCSWRST; @@ -133,6 +152,10 @@ void msp430_usci_release(const msp430_usci_params_t *params) unsigned irq_mask = irq_disable(); *params->interrupt_enable &= clear_irq_mask; *params->interrupt_flag &= clear_irq_mask; + + if (_auxiliary_clock_acquired & (1U << params->id)) { + msp430_clock_release(MSP430_CLOCK_AUXILIARY); + } irq_restore(irq_mask); /* Release mutex */