zynq7000_hal/
ttc.rs

1//! # Triple-timer counter (TTC) high-level driver
2//!
3//! This module also contains support for PWM and output waveform generation.
4//!
5//! ## Examples
6//!
7//! - [PWM](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/examples/embassy/src/bin/pwm.rs)
8
9use core::convert::Infallible;
10
11use arbitrary_int::{prelude::*, u3, u4};
12use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR};
13
14#[cfg(not(feature = "7z010-7z007s-clg225"))]
15use crate::gpio::mio::{Mio16, Mio17, Mio18, Mio19, Mio40, Mio41, Mio42, Mio43};
16use crate::{
17    clocks::ArmClocks,
18    gpio::{
19        IoPeriphPin,
20        mio::{Mio28, Mio29, Mio30, Mio31, MioPin, MuxConfig, Pin},
21    },
22    time::Hertz,
23};
24
25/// Each TTC consists of three independent timers/counters.
26#[derive(Debug, Copy, Clone)]
27pub enum TtcId {
28    Ttc0 = 0,
29    Ttc1 = 1,
30}
31
32#[derive(Debug, Copy, Clone)]
33pub enum ChannelId {
34    Ch0 = 0,
35    Ch1 = 1,
36    Ch2 = 2,
37}
38
39pub trait PsTtc {
40    fn reg_block(&self) -> MmioTtc<'static>;
41    fn id(&self) -> Option<TtcId>;
42}
43
44impl PsTtc for MmioTtc<'static> {
45    #[inline]
46    fn reg_block(&self) -> MmioTtc<'static> {
47        unsafe { self.clone() }
48    }
49
50    #[inline]
51    fn id(&self) -> Option<TtcId> {
52        let base_addr = unsafe { self.ptr() } as usize;
53        if base_addr == TTC_0_BASE_ADDR {
54            return Some(TtcId::Ttc0);
55        } else if base_addr == TTC_1_BASE_ADDR {
56            return Some(TtcId::Ttc1);
57        }
58        None
59    }
60}
61
62pub const TTC_MUX_CONF: MuxConfig = MuxConfig::new_with_l3(u3::new(0b110));
63
64pub trait ClockInPin: MioPin {
65    const ID: TtcId;
66}
67
68pub trait WaveOutPin: MioPin {
69    const ID: TtcId;
70}
71
72// TTC0 pin trait implementations.
73
74impl ClockInPin for Pin<Mio19> {
75    const ID: TtcId = TtcId::Ttc0;
76}
77#[cfg(not(feature = "7z010-7z007s-clg225"))]
78impl ClockInPin for Pin<Mio31> {
79    const ID: TtcId = TtcId::Ttc0;
80}
81#[cfg(not(feature = "7z010-7z007s-clg225"))]
82impl ClockInPin for Pin<Mio43> {
83    const ID: TtcId = TtcId::Ttc0;
84}
85
86impl WaveOutPin for Pin<Mio18> {
87    const ID: TtcId = TtcId::Ttc0;
88}
89#[cfg(not(feature = "7z010-7z007s-clg225"))]
90impl WaveOutPin for Pin<Mio30> {
91    const ID: TtcId = TtcId::Ttc0;
92}
93#[cfg(not(feature = "7z010-7z007s-clg225"))]
94impl WaveOutPin for Pin<Mio42> {
95    const ID: TtcId = TtcId::Ttc0;
96}
97
98// TTC1 pin trait implementations.
99
100impl ClockInPin for Pin<Mio17> {
101    const ID: TtcId = TtcId::Ttc1;
102}
103#[cfg(not(feature = "7z010-7z007s-clg225"))]
104impl ClockInPin for Pin<Mio29> {
105    const ID: TtcId = TtcId::Ttc1;
106}
107#[cfg(not(feature = "7z010-7z007s-clg225"))]
108impl ClockInPin for Pin<Mio41> {
109    const ID: TtcId = TtcId::Ttc1;
110}
111
112impl WaveOutPin for Pin<Mio16> {
113    const ID: TtcId = TtcId::Ttc1;
114}
115#[cfg(not(feature = "7z010-7z007s-clg225"))]
116impl WaveOutPin for Pin<Mio28> {
117    const ID: TtcId = TtcId::Ttc1;
118}
119#[cfg(not(feature = "7z010-7z007s-clg225"))]
120impl WaveOutPin for Pin<Mio40> {
121    const ID: TtcId = TtcId::Ttc1;
122}
123
124pub struct Ttc {
125    pub ch0: TtcChannel,
126    pub ch1: TtcChannel,
127    pub ch2: TtcChannel,
128}
129
130impl Ttc {
131    /// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid
132    /// processing system TTC peripheral.
133    ///
134    /// Returns [None] if the passed peripheral block does not have a valid PS TTC address.
135    pub fn new(ps_ttc: impl PsTtc) -> Option<Self> {
136        ps_ttc.id()?;
137        let regs = ps_ttc.reg_block();
138        let ch0 = TtcChannel {
139            regs: unsafe { regs.clone() },
140            id: ChannelId::Ch0,
141        };
142        let ch1 = TtcChannel {
143            regs: unsafe { regs.clone() },
144            id: ChannelId::Ch1,
145        };
146        let ch2 = TtcChannel {
147            regs,
148            id: ChannelId::Ch2,
149        };
150        Some(Self { ch0, ch1, ch2 })
151    }
152}
153
154pub struct TtcChannel {
155    regs: MmioTtc<'static>,
156    id: ChannelId,
157}
158
159impl TtcChannel {
160    pub fn regs_mut(&mut self) -> &mut MmioTtc<'static> {
161        &mut self.regs
162    }
163
164    #[inline]
165    pub fn read_counter(&self) -> u16 {
166        self.regs
167            .read_current_counter(self.id as usize)
168            .unwrap()
169            .count()
170    }
171
172    pub fn id(&self) -> ChannelId {
173        self.id
174    }
175}
176
177#[derive(Debug, thiserror::Error)]
178#[error("invalid TTC pin configuration")]
179pub struct InvalidTtcPinConfigError(pub MuxConfig);
180
181#[derive(Debug, thiserror::Error)]
182#[error("frequency is zero")]
183pub struct FrequencyIsZeroError;
184
185#[derive(Debug, thiserror::Error)]
186pub enum TtcConstructionError {
187    #[error("invalid TTC pin configuration")]
188    InvalidTtcPinConfig(#[from] InvalidTtcPinConfigError),
189    #[error("frequency is zero")]
190    FrequencyIsZero(#[from] FrequencyIsZeroError),
191}
192
193pub fn calc_prescaler_reg_and_interval_ticks(mut ref_clk: Hertz, freq: Hertz) -> (Option<u4>, u16) {
194    // TODO: Can this be optimized?
195    let mut prescaler: Option<u32> = None;
196    let mut tick_val = ref_clk / freq;
197    while tick_val > u16::MAX as u32 {
198        ref_clk /= 2;
199        match prescaler {
200            Some(val) => {
201                if val == u4::MAX.as_u32() {
202                    break;
203                }
204                prescaler = Some(val + 1);
205            }
206            None => prescaler = Some(0),
207        }
208        tick_val = ref_clk / freq;
209    }
210    (prescaler.map(|v| u4::new(v as u8)), tick_val as u16)
211}
212
213pub struct Pwm {
214    channel: TtcChannel,
215    ref_clk: Hertz,
216}
217
218impl Pwm {
219    /// Create a new PWM instance which uses the CPU 1x clock as the clock source and also uses
220    /// a MIO output pin for the waveform output.
221    pub fn new_with_cpu_clk_and_mio_waveout(
222        channel: TtcChannel,
223        arm_clocks: &ArmClocks,
224        freq: Hertz,
225        wave_out: impl WaveOutPin,
226    ) -> Result<Self, TtcConstructionError> {
227        IoPeriphPin::new(wave_out, TTC_MUX_CONF, None);
228        Ok(Self::new_with_cpu_clk(channel, arm_clocks, freq)?)
229    }
230
231    /// Create a new PWM instance which uses the CPU 1x clock as the clock source.
232    pub fn new_with_cpu_clk(
233        channel: TtcChannel,
234        arm_clocks: &ArmClocks,
235        freq: Hertz,
236    ) -> Result<Self, FrequencyIsZeroError> {
237        Self::new_generic(channel, arm_clocks.cpu_1x_clk(), freq)
238    }
239
240    /// Create a new PWM instance based on a reference clock source.
241    pub fn new_generic(
242        channel: TtcChannel,
243        ref_clk: Hertz,
244        freq: Hertz,
245    ) -> Result<Self, FrequencyIsZeroError> {
246        if freq.raw() == 0 {
247            return Err(FrequencyIsZeroError);
248        }
249        let (prescaler_reg, tick_val) = calc_prescaler_reg_and_interval_ticks(ref_clk, freq);
250        let id = channel.id() as usize;
251        let mut pwm = Self { channel, ref_clk };
252        pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
253        Ok(pwm)
254    }
255
256    /// Set a new frequency for the PWM cycle.
257    ///
258    /// This resets the duty cycle to 0%.
259    pub fn set_frequency(&mut self, freq: Hertz) -> Result<(), FrequencyIsZeroError> {
260        if freq.raw() == 0 {
261            return Err(FrequencyIsZeroError);
262        }
263        let id = self.channel.id() as usize;
264        let (prescaler_reg, tick_val) = calc_prescaler_reg_and_interval_ticks(self.ref_clk, freq);
265        self.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
266        Ok(())
267    }
268
269    #[inline]
270    pub fn ttc_channel_mut(&mut self) -> &mut TtcChannel {
271        &mut self.channel
272    }
273
274    #[inline]
275    pub fn max_duty_cycle(&self) -> u16 {
276        self.channel
277            .regs
278            .read_interval_value(self.channel.id() as usize)
279            .unwrap()
280            .value()
281    }
282
283    #[inline]
284    pub fn set_duty_cycle(&mut self, duty: u16) {
285        let id = self.channel.id() as usize;
286        self.channel
287            .regs
288            .modify_cnt_ctrl(id, |mut val| {
289                val.set_disable(true);
290                val
291            })
292            .unwrap();
293        self.channel
294            .regs
295            .write_match_value_0(
296                self.channel.id() as usize,
297                zynq7000::ttc::RwValue::new_with_raw_value(duty as u32),
298            )
299            .unwrap();
300        self.channel
301            .regs
302            .modify_cnt_ctrl(id, |mut val| {
303                val.set_disable(false);
304                val.set_reset(true);
305                val
306            })
307            .unwrap();
308    }
309
310    fn set_up_and_configure_pwm(&mut self, id: usize, prescaler_reg: Option<u4>, tick_val: u16) {
311        // Disable the counter first.
312        self.channel
313            .regs
314            .write_cnt_ctrl(id, zynq7000::ttc::CounterControl::new_with_raw_value(1))
315            .unwrap();
316
317        // Clock configuration
318        self.channel
319            .regs
320            .write_clk_cntr(
321                id,
322                zynq7000::ttc::ClockControl::builder()
323                    .with_ext_clk_edge(false)
324                    .with_clk_src(zynq7000::ttc::ClockSource::Pclk)
325                    .with_prescaler(prescaler_reg.unwrap_or(u4::new(0)))
326                    .with_prescale_enable(prescaler_reg.is_some())
327                    .build(),
328            )
329            .unwrap();
330        self.channel
331            .regs
332            .write_interval_value(
333                id,
334                zynq7000::ttc::RwValue::new_with_raw_value(tick_val as u32),
335            )
336            .unwrap();
337        // Corresponds to duty cycle 0.
338        self.channel
339            .regs
340            .write_match_value_0(id, zynq7000::ttc::RwValue::new_with_raw_value(0))
341            .unwrap();
342        self.channel
343            .regs
344            .write_cnt_ctrl(
345                id,
346                zynq7000::ttc::CounterControl::builder()
347                    .with_wave_polarity(zynq7000::ttc::WavePolarity::LowToHighOnMatch1)
348                    .with_wave_enable_n(zynq7000::ttc::WaveEnable::Enable)
349                    .with_reset(true)
350                    .with_match_enable(true)
351                    .with_decrementing(false)
352                    .with_mode(zynq7000::ttc::Mode::Interval)
353                    .with_disable(false)
354                    .build(),
355            )
356            .unwrap();
357    }
358}
359
360impl embedded_hal::pwm::ErrorType for Pwm {
361    type Error = Infallible;
362}
363
364impl embedded_hal::pwm::SetDutyCycle for Pwm {
365    #[inline]
366    fn max_duty_cycle(&self) -> u16 {
367        self.max_duty_cycle()
368    }
369
370    #[inline]
371    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
372        self.set_duty_cycle(duty);
373        Ok(())
374    }
375}