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 */