Skip to content

Commit 43f07fa

Browse files
committed
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.
1 parent 01caa19 commit 43f07fa

File tree

6 files changed

+184
-7
lines changed

6 files changed

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

0 commit comments

Comments
 (0)