Skip to content

Commit 59956fd

Browse files
authored
Merge pull request #20613 from maribu/cpu/msp430/pm
cpu/msp430: implement power management
2 parents 2e9ce4d + d5839ca commit 59956fd

File tree

25 files changed

+193
-46
lines changed

25 files changed

+193
-46
lines changed

cpu/msp430/clock.c

+57
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
* @}
2121
*/
2222

23+
#include <assert.h>
2324
#include <stdbool.h>
2425
#include <stdint.h>
2526

27+
#include "atomic_utils.h"
2628
#include "busy_wait.h"
2729
#include "macros/math.h"
2830
#include "macros/units.h"
@@ -38,6 +40,7 @@
3840
#endif
3941

4042
uint32_t msp430_dco_freq;
43+
static uint8_t msp430_clock_refcounts[MSP430_CLOCK_NUMOF];
4144

4245
static inline bool is_dco_in_use(const msp430_clock_params_t *params)
4346
{
@@ -361,3 +364,57 @@ uint32_t PURE msp430_auxiliary_clock_freq(void)
361364
uint16_t shift = (clock_params.auxiliary_clock_divier >> 4) & 0x3;
362365
return clock_params.lfxt1_frequency >> shift;
363366
}
367+
368+
void msp430_clock_acquire(msp430_clock_t clock)
369+
{
370+
assume((unsigned)clock < MSP430_CLOCK_NUMOF);
371+
uint8_t before = atomic_fetch_add_u8(&msp430_clock_refcounts[clock], 1);
372+
(void)before;
373+
assert(before < UINT8_MAX);
374+
}
375+
376+
void msp430_clock_release(msp430_clock_t clock)
377+
{
378+
assume((unsigned)clock < MSP430_CLOCK_NUMOF);
379+
uint8_t before = atomic_fetch_sub_u8(&msp430_clock_refcounts[clock], 1);
380+
(void)before;
381+
assert(before > 0);
382+
}
383+
384+
void pm_set_lowest(void)
385+
{
386+
/* disable IRQs, wait two cycles for this to take effect, backup
387+
* state register */
388+
uint16_t state;
389+
__asm__ volatile(
390+
"bic %[gie], SR" "\n\t"
391+
"nop" "\n\t"
392+
"nop" "\n\t"
393+
"mov.w SR, %[state]" "\n\t"
394+
: [state] "=r"(state)
395+
: [gie] "i"(GIE)
396+
: "memory"
397+
);
398+
399+
/* When applying the power safe mode, we want to be able to wake up again.
400+
* So set global interrupt enable then. */
401+
state |= GIE;
402+
/* disabling CPU works always, even when keeping the clocks running */
403+
state |= CPUOFF | SCG0;
404+
405+
if (msp430_clock_refcounts[MSP430_CLOCK_SUBMAIN] == 0) {
406+
state |= SCG1;
407+
}
408+
409+
if (msp430_clock_refcounts[MSP430_CLOCK_AUXILIARY] == 0) {
410+
state |= OSCOFF;
411+
}
412+
413+
/* write new state */
414+
__asm__ volatile(
415+
"mov.w %[state], SR" "\n\t"
416+
: /* no outputs */
417+
: [state] "r"(state)
418+
: "memory"
419+
);
420+
}

cpu/msp430/include/cpu.h

+13
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ extern "C" {
3636
*/
3737
#define WORDSIZE 16
3838

39+
/**
40+
* @brief MSP430 has power management support
41+
*/
42+
#define PROVIDES_PM_SET_LOWEST
43+
3944
/**
4045
* @brief Macro for defining interrupt service routines
4146
*/
@@ -94,6 +99,14 @@ static inline void __attribute__((always_inline)) __restore_context(void)
9499
*/
95100
static inline void __attribute__((always_inline)) __enter_isr(void)
96101
{
102+
/* modify state register pushed to stack to not got to power saving
103+
* mode right again */
104+
__asm__ volatile(
105+
"bic %[mask], 0(SP)" "\n\t"
106+
: /* no outputs */
107+
: [mask] "i"(CPUOFF | SCG0 | SCG1 | OSCOFF)
108+
: "memory"
109+
);
97110
extern char __stack; /* defined by linker script to end of RAM */
98111
__save_context();
99112
__asm__("mov.w %0,r1" : : "i"(&__stack));

cpu/msp430/include/irq_arch.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ __attribute__((always_inline)) static inline unsigned int irq_disable(void)
4444
{
4545
unsigned int state;
4646
__asm__ volatile(
47-
"mov.w r2, %[state]" "\n\t"
48-
"bic %[gie], r2" "\n\t"
47+
"mov.w SR, %[state]" "\n\t"
48+
"bic %[gie], SR" "\n\t"
4949
"nop" "\n\t"
5050
"and %[gie], %[state]" "\n\t"
5151
: [state] "=r"(state)
@@ -60,9 +60,9 @@ __attribute__((always_inline)) static inline unsigned int irq_enable(void)
6060
{
6161
unsigned int state;
6262
__asm__ volatile(
63-
"mov.w r2, %[state]" "\n\t"
63+
"mov.w SR, %[state]" "\n\t"
6464
"nop" "\n\t"
65-
"bis %[gie], r2" "\n\t"
65+
"bis %[gie], SR" "\n\t"
6666
"nop" "\n\t"
6767
"and %[gie], %[state]" "\n\t"
6868
: [state] "=r"(state)
@@ -76,7 +76,7 @@ __attribute__((always_inline)) static inline unsigned int irq_enable(void)
7676
__attribute__((always_inline)) static inline void irq_restore(unsigned int state)
7777
{
7878
__asm__ volatile(
79-
"bis %[state], r2" "\n\t"
79+
"bis %[state], SR" "\n\t"
8080
"nop" "\n\t"
8181
: /* no outputs */
8282
: [state] "r"(state)
@@ -93,7 +93,7 @@ __attribute__((always_inline)) static inline bool irq_is_enabled(void)
9393
{
9494
unsigned int state;
9595
__asm__ volatile(
96-
"mov.w r2,%[state]" "\n\t"
96+
"mov.w SR,%[state]" "\n\t"
9797
: [state] "=r"(state)
9898
: /* no inputs */
9999
: "memory"

cpu/msp430/include/periph_cpu_common.h

+33
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ typedef enum {
311311
TIMER_CLOCK_SOURCE_INCLK = TXSSEL_INCLK, /**< External INCLK as clock source */
312312
} msp430_timer_clock_source_t;
313313

314+
/**
315+
* @brief IDs of the different clock domains on the MSP430
316+
*
317+
* These can be used as internal clock sources for peripherals
318+
*/
319+
typedef enum {
320+
MSP430_CLOCK_SUBMAIN, /**< Subsystem main clock */
321+
MSP430_CLOCK_AUXILIARY, /**< Auxiliary clock */
322+
MSP430_CLOCK_NUMOF, /**< Number of clock domains */
323+
} msp430_clock_t;
324+
314325
/**
315326
* @brief Timer configuration on an MSP430 timer
316327
*/
@@ -367,6 +378,28 @@ uint32_t PURE msp430_submain_clock_freq(void);
367378
*/
368379
uint32_t PURE msp430_auxiliary_clock_freq(void);
369380

381+
/**
382+
* @brief Increase the refcount of the given clock
383+
*
384+
* @param[in] clock clock domain to acquire
385+
*
386+
* @warning This is an internal function and must only be called from
387+
* peripheral drivers
388+
* @note An assertion will blow when the count exceeds capacity
389+
*/
390+
void msp430_clock_acquire(msp430_clock_t clock);
391+
392+
/**
393+
* @brief Decrease the refcount of the subsystem main clock
394+
*
395+
* @param[in] clock clock domain to acquire
396+
*
397+
* @warning This is an internal function and must only be called from
398+
* peripheral drivers
399+
* @note An assertion will blow when the count drops below zero
400+
*/
401+
void msp430_clock_release(msp430_clock_t clock);
402+
370403
#ifdef __cplusplus
371404
}
372405
#endif

cpu/msp430/periph/timer.c

+28-5
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
#include "compiler_hints.h"
3131
#include "cpu.h"
3232
#include "periph/timer.h"
33-
#include "periph_conf.h"
34-
#include "periph_cpu.h"
3533

3634
/**
3735
* @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)
114112
for (unsigned i = 0; i < timer_query_channel_numof(dev); i++) {
115113
msptimer->CCTL[i] = 0;
116114
}
117-
/* start the timer in continuous mode */
118-
msptimer->CTL = ctl | TXMC_CONT;
115+
116+
timer_start(dev);
119117
return 0;
120118
}
121119

@@ -157,14 +155,39 @@ void timer_start(tim_t dev)
157155
{
158156
assume((unsigned)dev < TIMER_NUMOF);
159157
msp430_timer_t *msptimer = timer_conf[dev].timer;
158+
/* acquire clock */
159+
switch (timer_conf[dev].clock_source) {
160+
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
161+
msp430_clock_acquire(MSP430_CLOCK_SUBMAIN);
162+
break;
163+
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
164+
msp430_clock_acquire(MSP430_CLOCK_AUXILIARY);
165+
break;
166+
default:
167+
/* external clock source, safe to disable internal clocks */
168+
break;
169+
}
160170
msptimer->CTL |= TXMC_CONT;
161171
}
162172

163173
void timer_stop(tim_t dev)
164174
{
165175
assume((unsigned)dev < TIMER_NUMOF);
166176
msp430_timer_t *msptimer = timer_conf[dev].timer;
167-
msptimer->CTL &= ~(TXMC_MASK);
177+
msptimer->CTL &= ~(TXMC_CONT);
178+
179+
/* release clock */
180+
switch (timer_conf[dev].clock_source) {
181+
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
182+
msp430_clock_release(MSP430_CLOCK_SUBMAIN);
183+
break;
184+
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
185+
msp430_clock_release(MSP430_CLOCK_AUXILIARY);
186+
break;
187+
default:
188+
/* external clock source, nothing to release */
189+
break;
190+
}
168191
}
169192

170193
__attribute__((pure))

cpu/msp430/periph/usart.c

+30-2
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,32 @@ static mutex_t usart_locks[USART_NUMOF] = {
6464
MUTEX_INIT,
6565
};
6666

67+
/* store the clock acquired by each USART, so it can be release again */
68+
static msp430_usart_clk_t _clocks_acquired[USART_NUMOF];
69+
6770
void msp430_usart_acquire(const msp430_usart_params_t *params,
68-
const msp430_usart_conf_t *conf,
69-
uint8_t enable_mask)
71+
const msp430_usart_conf_t *conf,
72+
uint8_t enable_mask)
7073
{
7174
assume(params->num < USART_NUMOF);
7275

7376
mutex_lock(&usart_locks[params->num]);
7477
msp430_usart_t *dev = params->dev;
7578
msp430_usart_sfr_t *sfr = params->sfr;
7679

80+
_clocks_acquired[params->num] = conf->prescaler.clk_source;
81+
switch (_clocks_acquired[params->num]) {
82+
case USART_CLK_SUBMAIN:
83+
msp430_clock_acquire(MSP430_CLOCK_SUBMAIN);
84+
break;
85+
case USART_CLK_AUX:
86+
msp430_clock_acquire(MSP430_CLOCK_AUXILIARY);
87+
break;
88+
default:
89+
/* external clock from GPIO, safe to disable internal clocks */
90+
break;
91+
}
92+
7793
/* first, make sure USART is off before reconfiguring it */
7894
sfr->ME = 0;
7995
/* reset USART */
@@ -105,6 +121,18 @@ void msp430_usart_release(const msp430_usart_params_t *params)
105121
sfr->IE = 0;
106122
sfr->IFG = 0;
107123

124+
switch (_clocks_acquired[params->num]) {
125+
case USART_CLK_SUBMAIN:
126+
msp430_clock_release(MSP430_CLOCK_SUBMAIN);
127+
break;
128+
case USART_CLK_AUX:
129+
msp430_clock_release(MSP430_CLOCK_AUXILIARY);
130+
break;
131+
default:
132+
/* external clock from GPIO, not managed here */
133+
break;
134+
}
135+
108136
/* Release mutex */
109137
mutex_unlock(&usart_locks[params->num]);
110138
}

cpu/msp430/periph/usci.c

+23
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ static mutex_t _usci_locks[MSP430_USCI_ID_NUMOF] = {
9393
MUTEX_INIT,
9494
};
9595

96+
static uint8_t _auxiliary_clock_acquired;
97+
9698
void msp430_usci_acquire(const msp430_usci_params_t *params,
9799
const msp430_usci_conf_t *conf)
98100
{
@@ -101,6 +103,23 @@ void msp430_usci_acquire(const msp430_usci_params_t *params,
101103
mutex_lock(&_usci_locks[params->id]);
102104
msp430_usci_b_t *dev = params->dev;
103105

106+
/* We only need to acquire the auxiliary (low frequency) clock domain, as
107+
* the subsystem main clock (SMCLK) will be acquired on-demand when activity
108+
* is detected on RXD, as per datasheet:
109+
*
110+
* > The USCI module provides automatic clock activation for SMCLK for use
111+
* > with low-power modes.
112+
*/
113+
switch (conf->prescaler.clk_source) {
114+
case USCI_CLK_AUX:
115+
msp430_clock_acquire(MSP430_CLOCK_AUXILIARY);
116+
_auxiliary_clock_acquired |= 1U << params->id;
117+
break;
118+
default:
119+
_auxiliary_clock_acquired &= ~(1U << params->id);
120+
break;
121+
}
122+
104123
/* put device in disabled/reset state */
105124
dev->CTL1 = UCSWRST;
106125

@@ -133,6 +152,10 @@ void msp430_usci_release(const msp430_usci_params_t *params)
133152
unsigned irq_mask = irq_disable();
134153
*params->interrupt_enable &= clear_irq_mask;
135154
*params->interrupt_flag &= clear_irq_mask;
155+
156+
if (_auxiliary_clock_acquired & (1U << params->id)) {
157+
msp430_clock_release(MSP430_CLOCK_AUXILIARY);
158+
}
136159
irq_restore(irq_mask);
137160

138161
/* Release mutex */

examples/gnrc_lorawan/Makefile.ci

-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ BOARD_INSUFFICIENT_MEMORY := \
66
atmega328p \
77
atmega328p-xplained-mini \
88
atmega8 \
9-
msb-430 \
10-
msb-430h \
119
nucleo-c031c6 \
1210
nucleo-f031k6 \
1311
nucleo-f042k6 \
@@ -17,6 +15,5 @@ BOARD_INSUFFICIENT_MEMORY := \
1715
stk3200 \
1816
stm32f030f4-demo \
1917
stm32f0discovery \
20-
telosb \
2118
weact-g030f6 \
2219
#

examples/suit_update/Makefile.ci

-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ BOARD_INSUFFICIENT_MEMORY := \
88
e104-bt5011a-tb \
99
gd32vf103c-start \
1010
lsn50 \
11-
msb-430 \
12-
msb-430h \
1311
nucleo-c031c6 \
1412
nucleo-f030r8 \
1513
nucleo-f031k6 \
@@ -32,5 +30,4 @@ BOARD_INSUFFICIENT_MEMORY := \
3230
slstk3400a \
3331
stk3200 \
3432
stm32f0discovery \
35-
telosb \
3633
#

tests/drivers/atwinc15x0/Makefile.ci

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ BOARD_INSUFFICIENT_MEMORY := \
99
atmega8 \
1010
bluepill-stm32f030c8 \
1111
i-nucleo-lrwan1 \
12-
msb-430 \
13-
msb-430h \
1412
nucleo-c031c6 \
1513
nucleo-f030r8 \
1614
nucleo-f031k6 \

0 commit comments

Comments
 (0)