Skip to content

Commit

Permalink
cpu/msp430: implement power management
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
maribu committed Apr 25, 2024
1 parent 6aed440 commit 29ccdbd
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 7 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
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

0 comments on commit 29ccdbd

Please sign in to comment.