Skip to content

Commit

Permalink
Merge pull request #20613 from maribu/cpu/msp430/pm
Browse files Browse the repository at this point in the history
cpu/msp430: implement power management
  • Loading branch information
maribu authored May 7, 2024
2 parents 2e9ce4d + d5839ca commit 59956fd
Show file tree
Hide file tree
Showing 25 changed files with 193 additions and 46 deletions.
57 changes: 57 additions & 0 deletions cpu/msp430/clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
* @}
*/

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>

#include "atomic_utils.h"
#include "busy_wait.h"
#include "macros/math.h"
#include "macros/units.h"
Expand All @@ -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)
{
Expand Down Expand Up @@ -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"
);
}
13 changes: 13 additions & 0 deletions cpu/msp430/include/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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));
Expand Down
12 changes: 6 additions & 6 deletions cpu/msp430/include/irq_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ __attribute__((always_inline)) static inline unsigned int irq_disable(void)
{
unsigned int state;
__asm__ volatile(
"mov.w r2, %[state]" "\n\t"
"bic %[gie], r2" "\n\t"
"mov.w SR, %[state]" "\n\t"
"bic %[gie], SR" "\n\t"
"nop" "\n\t"
"and %[gie], %[state]" "\n\t"
: [state] "=r"(state)
Expand All @@ -60,9 +60,9 @@ __attribute__((always_inline)) static inline unsigned int irq_enable(void)
{
unsigned int state;
__asm__ volatile(
"mov.w r2, %[state]" "\n\t"
"mov.w SR, %[state]" "\n\t"
"nop" "\n\t"
"bis %[gie], r2" "\n\t"
"bis %[gie], SR" "\n\t"
"nop" "\n\t"
"and %[gie], %[state]" "\n\t"
: [state] "=r"(state)
Expand All @@ -76,7 +76,7 @@ __attribute__((always_inline)) static inline unsigned int irq_enable(void)
__attribute__((always_inline)) static inline void irq_restore(unsigned int state)
{
__asm__ volatile(
"bis %[state], r2" "\n\t"
"bis %[state], SR" "\n\t"
"nop" "\n\t"
: /* no outputs */
: [state] "r"(state)
Expand All @@ -93,7 +93,7 @@ __attribute__((always_inline)) static inline bool irq_is_enabled(void)
{
unsigned int state;
__asm__ volatile(
"mov.w r2,%[state]" "\n\t"
"mov.w SR,%[state]" "\n\t"
: [state] "=r"(state)
: /* no inputs */
: "memory"
Expand Down
33 changes: 33 additions & 0 deletions cpu/msp430/include/periph_cpu_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down
33 changes: 28 additions & 5 deletions cpu/msp430/periph/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -157,14 +155,39 @@ 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;
}

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))
Expand Down
32 changes: 30 additions & 2 deletions cpu/msp430/periph/usart.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,32 @@ 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);

mutex_lock(&usart_locks[params->num]);
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 */
Expand Down Expand Up @@ -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]);
}
Expand Down
23 changes: 23 additions & 0 deletions cpu/msp430/periph/usci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;

Expand Down Expand Up @@ -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 */
Expand Down
3 changes: 0 additions & 3 deletions examples/gnrc_lorawan/Makefile.ci
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ BOARD_INSUFFICIENT_MEMORY := \
atmega328p \
atmega328p-xplained-mini \
atmega8 \
msb-430 \
msb-430h \
nucleo-c031c6 \
nucleo-f031k6 \
nucleo-f042k6 \
Expand All @@ -17,6 +15,5 @@ BOARD_INSUFFICIENT_MEMORY := \
stk3200 \
stm32f030f4-demo \
stm32f0discovery \
telosb \
weact-g030f6 \
#
3 changes: 0 additions & 3 deletions examples/suit_update/Makefile.ci
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ BOARD_INSUFFICIENT_MEMORY := \
e104-bt5011a-tb \
gd32vf103c-start \
lsn50 \
msb-430 \
msb-430h \
nucleo-c031c6 \
nucleo-f030r8 \
nucleo-f031k6 \
Expand All @@ -32,5 +30,4 @@ BOARD_INSUFFICIENT_MEMORY := \
slstk3400a \
stk3200 \
stm32f0discovery \
telosb \
#
2 changes: 0 additions & 2 deletions tests/drivers/atwinc15x0/Makefile.ci
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ BOARD_INSUFFICIENT_MEMORY := \
atmega8 \
bluepill-stm32f030c8 \
i-nucleo-lrwan1 \
msb-430 \
msb-430h \
nucleo-c031c6 \
nucleo-f030r8 \
nucleo-f031k6 \
Expand Down
Loading

0 comments on commit 59956fd

Please sign in to comment.