1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Low-power mode (LPMx) entry.
//!
//! The MSP430 saves power by switching off clocks and the CPU and waiting for an
//! interrupt to wake back up. Each mode is just a particular set of bits in the
//! **status register** (SR / R2):
//!
//! | Bit | Name | Effect when set |
//! |-----|--------|----------------------------|
//! | 4 | CPUOFF | CPU and MCLK off |
//! | 5 | OSCOFF | LFXT/low-freq oscillator off |
//! | 6 | SCG0 | FLL / DCO off |
//! | 7 | SCG1 | SMCLK off |
//!
//! **LPM3** sets CPUOFF + SCG0 + SCG1 but leaves **OSCOFF = 0**, so the 32.768 kHz
//! crystal (and an ACLK-clocked timer) keeps running while MCLK, SMCLK, and the
//! DCO are all gated. That is what lets a timer measure time, and wake the part,
//! through deep sleep — at microamp power. **LPM4** additionally sets OSCOFF,
//! stopping the crystal too: no clock runs at all, so no timer can wake it —
//! only *asynchronous* events can. Port-pin edge interrupts are exactly that
//! (`PxIFG` latches without a clock), which is why "sleep until a button" is
//! the canonical LPM4 pattern; see [`crate::gpio`]'s interrupt support.
//!
//! # LPMx.5: power off instead of pause
//!
//! Every mode above keeps the **core domain** powered: the PMM's internal
//! regulator keeps VCORE up, so RAM, registers, and the frozen program all
//! retain state, and wake-up resumes execution mid-line in a handful of µs.
//! **LPM3.5** and **LPM4.5** ([`enter_lpm3_5`], [`enter_lpm4_5`]) instead turn
//! the regulator *off* (`PMMREGOFF`): VCORE collapses, and the CPU, RAM, and
//! nearly every peripheral cease to exist as state — which is how the ".5"
//! modes get below LPM4's leakage floor. There is no frozen program left to
//! resume, so **wake-up is architecturally a reset**: the device comes back
//! through a BOR, `main` starts from the top, and `SYSRSTIV` reports
//! [`Lpm5WakeUp`](crate::sys::ResetReason::Lpm5WakeUp) as the cause (drain it
//! with [`crate::sys::ResetReasons::drain`] to tell wake from cold boot).
//! Both entries are therefore `-> !`.
//!
//! What survives the dark interval: **FRAM** (nonvolatile), the **RTC_B
//! domain** (LPM3.5 only — LFXT keeps the calendar counting through the
//! sleep; the wake freezes it, contents intact, until
//! [`crate::rtc::Rtc::attach`] releases it), and the **I/O pins**, which the
//! PMM latches at their last configuration (`LOCKLPM5`). On wake the pins
//! *stay* latched until software clears `LOCKLPM5` ([`crate::gpio::unlock_pins`])
//! — reconfigure the ports first (and re-arm the wake pin's `PxIE` *before*
//! unlocking so its latched `PxIFG` is delivered), then unlock, so outputs never glitch
//! through the reboot.
/// Enter **LPM0** with interrupts enabled, and return once an interrupt has
/// woken the CPU.
///
/// LPM0 stops only the CPU and MCLK — SMCLK, the DCO, and MODOSC-on-demand
/// peripherals keep running. It is the mode for "a peripheral is busy and the
/// CPU has nothing to do until it finishes": a UART receiving on SMCLK, or an
/// ADC12 conversion self-clocking on MODOSC (`start_* → enter_lpm0() →
/// read_result()`). The same atomic GIE+sleep `bis` and the same
/// `#[interrupt(wake_cpu)]` requirement as [`enter_lpm3`] apply.
/// Enter **LPM3** with interrupts enabled, and return once an interrupt has
/// woken the CPU.
///
/// Sets CPUOFF + SCG0 + SCG1 (LPM3) and GIE in one `bis` to the status register:
/// enabling interrupts and sleeping must be atomic, or an interrupt arriving in
/// the gap could be serviced and the part would then sleep forever waiting for
/// an event that already happened. Execution halts at this instruction until an
/// interrupt fires; for the CPU to actually resume here (rather than service the
/// ISR and sleep again) that ISR must clear the low-power bits in the stacked SR
/// — which msp430-rt's `#[interrupt(wake_cpu)]` does.
/// Enter **LPM4** with interrupts enabled, and return once an interrupt has
/// woken the CPU.
///
/// LPM4 is LPM3 plus OSCOFF: every clock on the part stops, including LFXT.
/// Nothing scheduled can wake it — only asynchronous events (a port-pin edge
/// latching `PxIFG`, or RST/NMI) do, so arm a [`crate::gpio`] pin interrupt
/// *before* calling this or the part sleeps until reset. The same atomic
/// GIE+sleep `bis` and the same `#[interrupt(wake_cpu)]` requirement as
/// [`enter_lpm3`] apply.
// --- LPMx.5: regulator-off deep sleep ---------------------------------------
// PMMCTL0 password bytes. Like WDTCTL (see crate::watchdog) and CSCTL0 (see
// crate::clocks), the PMM register file only accepts writes while 0xA5 sits in
// PMMCTL0's high byte — and the SVD does not model the password field, so a
// PAC `modify` would write back the 0x96 the high byte reads as and trigger a
// PMM-password PUC (SYSRSTIV 0x20, sys::ResetReason::PmmPassword). Byte-wise
// raw access sidesteps it: park 0xA5 in the high byte (unlocks the whole PMM
// register file), RMW the low byte, then write anything else to re-lock.
const PMMCTL0_L: *mut u8 = 0x0120 as *mut u8;
const PMMCTL0_H: *mut u8 = 0x0121 as *mut u8;
const PMMPW_H: u8 = 0xA5;
const PMMREGOFF: u8 = 1 << 4;
/// Set `PMMREGOFF` so the next LPM3/LPM4 entry powers the core regulator off
/// (turning the pause modes into the ".5" power-off modes).
///
/// Takes `&pac::Pmm` purely as proof of post-`take()` context; the actual
/// access is byte-wise raw (see the constants above for why the PAC field API
/// cannot be used here).
/// Enter **LPM3.5** — RTC-alive power-off. Never returns: wake-up is a BOR
/// reset back into `main` (see the module docs).
///
/// LPM3.5 is the LPM3 request (`CPUOFF+SCG0+SCG1`, OSCOFF *clear* so LFXT
/// stays available to the RTC domain) with `PMMREGOFF` set: the core domain
/// powers off, but RTC_B keeps its calendar counting on LFXT and can wake the
/// part via an armed RTC interrupt (e.g.
/// [`crate::rtc::Rtc::enable_event_interrupt`]) — the enable bit acts as the
/// wake enable; no ISR ever runs for an LPMx.5 wake. Armed port pins wake it
/// too. Before calling: persist anything you need to FRAM, and `flush()` the
/// UART (SMCLK dies mid-character otherwise). On the rebooted side, adopt the
/// surviving calendar with [`crate::rtc::Rtc::attach`] — the wake freezes it
/// (and clears the RTC interrupt enables) until attach releases it, and
/// [`crate::rtc::event_irq_pending`] holds the wake evidence.
///
/// An interrupt that fires *between* arming and this call is serviced as a
/// normal interrupt (the entry `bis` sets GIE, per the SLAU367 recipe) and
/// will not wake the part later — arm the wake source immediately before
/// entering. If entry is aborted by such a pending event, this function
/// retries forever.
!
/// Enter **LPM4.5** — everything-off power-off (~¼ µA class). Never returns:
/// wake-up is a BOR reset back into `main` (see the module docs).
///
/// LPM4.5 is the LPM4 request (all clocks stopped, LFXT included — an RTC
/// does *not* survive this one) with `PMMREGOFF` set. The only wake sources
/// are armed port-pin edges (`enable_interrupt` on a [`crate::gpio`] input —
/// the `PxIE` bit acts as the wake enable; no ISR ever runs) and the RST pin.
/// Before calling: persist state to FRAM and `flush()` the UART. On the
/// rebooted side, re-arm the pin *before* clearing `LOCKLPM5`
/// ([`crate::gpio::unlock_pins`]) and its latched wake `PxIFG` is delivered
/// for inspection.
///
/// The same pre-entry race note as [`enter_lpm3_5`] applies: arm the wake pin
/// immediately before entering.
!