imxrt_hal/chip/drivers/pit.rs
1//! Periodic interrupt timers.
2//!
3//! Each PIT has four channels, all running at the same frequency.
4//! PIT channels count down to zero from a starting load value.
5//! When a channel elapses, it automatically restarts from the load value.
6//! All four channels share an interrupt.
7//!
8//! You can chain channels together using [`Pit::enable_chaining`].
9//! This increases the width of the timer by having a channel count down
10//! each time the previous channel expires. You can chain more than two
11//! channels together. Keep in mind that you'll need to handle cases of
12//! timer overflow in software.
13//!
14//! When channel 0 and 1 are chained together, the lifetime register is
15//! enabled. Reads from the lifetime registers will automatically handle
16//! overflow without a software loop.
17//!
18//! # Example
19//!
20//! Note that these examples do not demonstrate how to configure the PIT
21//! clock gates, or PERCLK. For more information, see [the CCM peripheral clock
22//! module](crate::ccm::perclk_clk).
23//!
24//! Acquire the PIT driver:
25//!
26//! ```no_run
27//! use imxrt_hal::pit::{Pit, Channel};
28//! use imxrt_ral::pit::PIT;
29//!
30//! let mut pit = Pit::new(unsafe { PIT::instance() });
31//! ```
32//!
33//! Use channel 0 to implement a blocking delay:
34//!
35//! ```no_run
36//! # use imxrt_hal::pit::{Pit, Channel};
37//! # use imxrt_ral::pit::PIT;
38//! # let mut pit = Pit::new(unsafe { PIT::instance() });
39//! # const DELAY_MS: u32 = 1;
40//! pit.set_load_timer_value(Channel::Chan0, DELAY_MS);
41//! pit.enable(Channel::Chan0);
42//!
43//! loop {
44//! while !pit.is_elapsed(Channel::Chan0) {}
45//! pit.clear_elapsed(Channel::Chan0);
46//! // Do work...
47//! }
48//! ```
49//!
50//! Chain channels 0 and 1 together for a 64-bit timer:
51//!
52//! ```no_run
53//! # use imxrt_hal::pit::{Pit, Channel};
54//! # use imxrt_ral::pit::PIT;
55//! # let mut pit = Pit::new(unsafe { PIT::instance() });
56//! // Channel 1 will decrement when Channel 0 expires.
57//! pit.enable_chaining(Channel::Chan1).unwrap();
58//! ```
59
60use crate::ral;
61
62/// Any PIT instance.
63type AnyPitInstance = crate::AnyInstance<ral::pit::RegisterBlock>;
64
65/// A PIT channel.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68#[repr(usize)]
69pub enum Channel {
70 /// Channel 0.
71 Chan0 = 0,
72 /// Channel 1.
73 Chan1 = 1,
74 /// Channel 2.
75 Chan2 = 2,
76 /// Channel 3.
77 Chan3 = 3,
78}
79
80/// Error returned when a channel cannot be chained.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82#[cfg_attr(feature = "defmt", derive(defmt::Format))]
83pub struct CannotChainError(());
84
85/// A periodic interrupt timer (PIT) driver.
86///
87/// The PIT has four independent timer channels that share a single
88/// interrupt. Use the [`Channel`] enum to select which channel to operate on.
89pub struct Pit {
90 pit: AnyPitInstance,
91}
92
93impl Pit {
94 /// Create a new PIT driver from the RAL's PIT instance.
95 ///
96 /// When `new` returns, all channels are disabled and reset.
97 /// The `FRZ` bit in `MCR` is not modified.
98 pub fn new<const N: u8>(pit: ral::pit::Instance<N>) -> Self {
99 new(crate::into_any(pit))
100 }
101
102 fn timer(&self, channel: Channel) -> &ral::pit::timer::RegisterBlock {
103 &self.pit.TIMER[channel as usize]
104 }
105
106 /// Enable (true) or disable (false) interrupt generation for a channel.
107 pub fn set_interrupt_enable(&mut self, channel: Channel, enable: bool) {
108 ral::modify_reg!(ral::pit::timer, self.timer(channel), TCTRL, TIE: enable as u32);
109 }
110
111 /// Indicates if timeouts will (true) or will not (false) generate interrupts.
112 pub fn is_interrupt_enabled(&self, channel: Channel) -> bool {
113 ral::read_reg!(ral::pit::timer, self.timer(channel), TCTRL, TIE == 1)
114 }
115
116 /// Reads the current timer value, in clock ticks.
117 ///
118 /// Returns `0` if the channel is disabled.
119 pub fn current_timer_value(&self, channel: Channel) -> u32 {
120 if self.is_enabled(channel) {
121 // Note in CVAL register docs: don't read CVAL if the timer
122 // is disabled "because the value is unreliable."
123 ral::read_reg!(ral::pit::timer, self.timer(channel), CVAL)
124 } else {
125 0
126 }
127 }
128
129 /// Loads the timer value for the next timer run.
130 ///
131 /// `ticks` is in clock ticks.
132 pub fn set_load_timer_value(&self, channel: Channel, ticks: u32) {
133 ral::write_reg!(
134 ral::pit::timer,
135 self.timer(channel),
136 LDVAL,
137 ticks.saturating_sub(1)
138 );
139 }
140
141 /// Returns the load timer value for the next timer run, in clock ticks.
142 pub fn load_timer_value(&self, channel: Channel) -> u32 {
143 ral::read_reg!(ral::pit::timer, self.timer(channel), LDVAL).saturating_add(1)
144 }
145
146 /// Enable a timer channel.
147 pub fn enable(&mut self, channel: Channel) {
148 ral::modify_reg!(ral::pit::timer, self.timer(channel), TCTRL, TEN: 1);
149 }
150
151 /// Disable a timer channel.
152 pub fn disable(&mut self, channel: Channel) {
153 ral::modify_reg!(ral::pit::timer, self.timer(channel), TCTRL, TEN: 0);
154 }
155
156 /// Returns `true` if the channel is enabled.
157 pub fn is_enabled(&self, channel: Channel) -> bool {
158 ral::read_reg!(ral::pit::timer, self.timer(channel), TCTRL, TEN == 1)
159 }
160
161 /// Returns `true` if the timer has elapsed.
162 pub fn is_elapsed(&self, channel: Channel) -> bool {
163 ral::read_reg!(ral::pit::timer, self.timer(channel), TFLG, TIF == 1)
164 }
165
166 /// Clear the elapsed flag.
167 pub fn clear_elapsed(&self, channel: Channel) {
168 ral::write_reg!(ral::pit::timer, self.timer(channel), TFLG, TIF: 1);
169 }
170
171 /// Chain adjacent channels together, forming a larger timer.
172 ///
173 /// A chained timer will decrement by one each time the previous channel
174 /// expires. The previous channel is the channel with a lower number.
175 ///
176 /// For example, to chain `Chan2` to `Chan1`, supply `Chan2` as an argument.
177 /// Then, every time `Chan1` expires, `Chan2` decrements by one.
178 ///
179 /// You may chain multiple channels together, forming 96-bit and 128-bit
180 /// timers. When reading the timer values, take care to handle overflows.
181 ///
182 /// If you're generating interrupts from a chained timer, the channel with
183 /// the larger number should generate that interrupt.
184 ///
185 /// When querying for the time tracked by chained timers, make sure you
186 /// account for rollover in software. Keep in mind that the lifetime timer
187 /// will handle rollover automatically; see [`lifetime_value`](Self::lifetime_value)
188 /// for more information.
189 ///
190 /// # Errors
191 ///
192 /// Returns [`CannotChainError`] if `channel` is `Chan0`, since there is
193 /// no previous channel to chain to.
194 pub fn enable_chaining(&mut self, channel: Channel) -> Result<(), CannotChainError> {
195 if channel == Channel::Chan0 {
196 return Err(CannotChainError(()));
197 }
198 ral::modify_reg!(ral::pit::timer, self.timer(channel), TCTRL, CHN: 1);
199 Ok(())
200 }
201
202 /// Disable chaining for a channel.
203 ///
204 /// See [`enable_chaining`](Self::enable_chaining) for more information.
205 ///
206 /// # Errors
207 ///
208 /// Returns [`CannotChainError`] if `channel` is `Chan0`, since `Chan0`
209 /// cannot be chained.
210 pub fn disable_chaining(&mut self, channel: Channel) -> Result<(), CannotChainError> {
211 if channel == Channel::Chan0 {
212 return Err(CannotChainError(()));
213 }
214 ral::modify_reg!(ral::pit::timer, self.timer(channel), TCTRL, CHN: 0);
215 Ok(())
216 }
217
218 /// Returns `true` if a channel is chained to the previous channel.
219 ///
220 /// See [`enable_chaining`](Self::enable_chaining) for more information.
221 ///
222 /// Always returns `false` for `Chan0`.
223 pub fn is_chained(&self, channel: Channel) -> bool {
224 if channel == Channel::Chan0 {
225 return false;
226 }
227 ral::read_reg!(ral::pit::timer, self.timer(channel), TCTRL, CHN == 1)
228 }
229
230 /// Read the lifetime register value.
231 ///
232 /// The lifetime register is a 64-bit register that combines channels 0 and 1.
233 /// It is only valid when channel 1 is chained to channel 0.
234 ///
235 /// The method assumes that channel 1 is chained to channel 0. See
236 /// [`enable_chaining`](Self::enable_chaining) for more information. Additionally,
237 /// the method assumes both channels 0 and 1 are enabled.
238 ///
239 /// Given the hardware support, this call does not require a loop to account for
240 /// rollover.
241 ///
242 /// This method implements the recommended fix for errata ERR050130.
243 pub fn lifetime_value(&self) -> u64 {
244 let mut high = self.pit.LTMR64H.read();
245 let mut low = self.pit.LTMR64L.read();
246
247 let ldval0 = self.pit.TIMER[0].LDVAL.read();
248 if low == ldval0 {
249 high = self.pit.LTMR64H.read();
250 low = self.pit.LTMR64L.read();
251 }
252
253 (u64::from(high) << 32) + u64::from(low)
254 }
255}
256
257fn new(pit: AnyPitInstance) -> Pit {
258 ral::modify_reg!(ral::pit, pit, MCR, MDIS: MDIS_0);
259 // Reset all PIT channels
260 //
261 // PIT channels may be used by a system's boot ROM, or another
262 // user. Set them to a known, good state.
263 ral::write_reg!(ral::pit::timer, &pit.TIMER[0], TCTRL, 0);
264 ral::write_reg!(ral::pit::timer, &pit.TIMER[1], TCTRL, 0);
265 ral::write_reg!(ral::pit::timer, &pit.TIMER[2], TCTRL, 0);
266 ral::write_reg!(ral::pit::timer, &pit.TIMER[3], TCTRL, 0);
267
268 Pit { pit }
269}
270
271/// ```compile_fail
272/// use imxrt_hal::pit::Pit;
273/// fn not_sync<T: Sync>() {}
274///
275/// not_sync::<Pit>();
276/// ```
277#[cfg(doctest)]
278struct PitNotSync;
279
280/// ```send
281/// use imxrt_hal::pit::Pit;
282/// fn is_send<T: Send>() {}
283///
284/// is_send::<Pit>();
285/// ```
286#[cfg(doctest)]
287struct PitSend;