2025-04-11 20:39:36 +02:00

377 lines
10 KiB
Rust

//! Triple-timer counter (TTC) high-level driver.
//!
//! This module also contains support for PWM and output waveform generation.
use core::convert::Infallible;
use arbitrary_int::{Number, u3, u4};
use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::mio::{Mio16, Mio17, Mio18, Mio19, Mio40, Mio41, Mio42, Mio43};
use crate::{
clocks::ArmClocks,
gpio::{
IoPeriphPin,
mio::{Mio28, Mio29, Mio30, Mio31, MioPinMarker, MuxConf, Pin},
},
time::Hertz,
};
/// Each TTC consists of three independent timers/counters.
#[derive(Debug, Copy, Clone)]
pub enum TtcId {
Ttc0 = 0,
Ttc1 = 1,
}
#[derive(Debug, Copy, Clone)]
pub enum ChannelId {
Ch0 = 0,
Ch1 = 1,
Ch2 = 2,
}
pub trait PsTtc {
fn reg_block(&self) -> MmioTtc<'static>;
fn id(&self) -> Option<TtcId>;
}
impl PsTtc for MmioTtc<'static> {
#[inline]
fn reg_block(&self) -> MmioTtc<'static> {
unsafe { self.clone() }
}
#[inline]
fn id(&self) -> Option<TtcId> {
let base_addr = unsafe { self.ptr() } as usize;
if base_addr == TTC_0_BASE_ADDR {
return Some(TtcId::Ttc0);
} else if base_addr == TTC_1_BASE_ADDR {
return Some(TtcId::Ttc1);
}
None
}
}
pub const TTC_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b110));
pub trait ClockInPin: MioPinMarker {
const ID: TtcId;
}
pub trait WaveOutPin: MioPinMarker {
const ID: TtcId;
}
// TTC0 pin trait implementations.
impl ClockInPin for Pin<Mio19> {
const ID: TtcId = TtcId::Ttc0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl ClockInPin for Pin<Mio31> {
const ID: TtcId = TtcId::Ttc0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl ClockInPin for Pin<Mio43> {
const ID: TtcId = TtcId::Ttc0;
}
impl WaveOutPin for Pin<Mio18> {
const ID: TtcId = TtcId::Ttc0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl WaveOutPin for Pin<Mio30> {
const ID: TtcId = TtcId::Ttc0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl WaveOutPin for Pin<Mio42> {
const ID: TtcId = TtcId::Ttc0;
}
// TTC1 pin trait implementations.
impl ClockInPin for Pin<Mio17> {
const ID: TtcId = TtcId::Ttc1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl ClockInPin for Pin<Mio29> {
const ID: TtcId = TtcId::Ttc1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl ClockInPin for Pin<Mio41> {
const ID: TtcId = TtcId::Ttc1;
}
impl WaveOutPin for Pin<Mio16> {
const ID: TtcId = TtcId::Ttc1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl WaveOutPin for Pin<Mio28> {
const ID: TtcId = TtcId::Ttc1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl WaveOutPin for Pin<Mio40> {
const ID: TtcId = TtcId::Ttc1;
}
pub struct Ttc {
pub ch0: TtcChannel,
pub ch1: TtcChannel,
pub ch2: TtcChannel,
}
impl Ttc {
/// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid
/// processing system TTC peripheral.
///
/// Returns [None] if the passed peripheral block does not have a valid PS TTC address.
pub fn new(ps_ttc: impl PsTtc) -> Option<Self> {
ps_ttc.id()?;
let regs = ps_ttc.reg_block();
let ch0 = TtcChannel {
regs: unsafe { regs.clone() },
id: ChannelId::Ch0,
};
let ch1 = TtcChannel {
regs: unsafe { regs.clone() },
id: ChannelId::Ch1,
};
let ch2 = TtcChannel {
regs,
id: ChannelId::Ch2,
};
Some(Self { ch0, ch1, ch2 })
}
}
pub struct TtcChannel {
regs: MmioTtc<'static>,
id: ChannelId,
}
impl TtcChannel {
pub fn regs_mut(&mut self) -> &mut MmioTtc<'static> {
&mut self.regs
}
#[inline]
pub fn read_counter(&self) -> u16 {
self.regs
.read_current_counter(self.id as usize)
.unwrap()
.count()
}
pub fn id(&self) -> ChannelId {
self.id
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid TTC pin configuration")]
pub struct InvalidTtcPinConfigError(pub MuxConf);
#[derive(Debug, thiserror::Error)]
#[error("frequency is zero")]
pub struct FrequencyIsZeroError;
#[derive(Debug, thiserror::Error)]
pub enum TtcConstructionError {
#[error("invalid TTC pin configuration")]
InvalidTtcPinConfig(#[from] InvalidTtcPinConfigError),
#[error("frequency is zero")]
FrequencyIsZero(#[from] FrequencyIsZeroError),
}
pub fn calculate_prescaler_reg_and_interval_ticks(
mut ref_clk: Hertz,
freq: Hertz,
) -> (Option<u4>, u16) {
// TODO: Can this be optimized?
let mut prescaler_reg: Option<u4> = None;
let mut tick_val = ref_clk / freq;
while tick_val > u16::MAX as u32 {
ref_clk /= 2;
if let Some(prescaler_reg) = prescaler_reg {
// TODO: Better error handling for this case? Can this even happen?
if prescaler_reg.value() == u4::MAX.value() {
break;
} else {
prescaler_reg.checked_add(u4::new(1));
}
} else {
prescaler_reg = Some(u4::new(0));
}
tick_val = ref_clk / freq;
}
(prescaler_reg, tick_val as u16)
}
pub struct Pwm {
channel: TtcChannel,
ref_clk: Hertz,
}
impl Pwm {
/// Create a new PWM instance which uses the CPU 1x clock as the clock source and also uses
/// a MIO output pin for the waveform output.
pub fn new_with_cpu_clk_and_mio_waveout(
channel: TtcChannel,
arm_clocks: &ArmClocks,
freq: Hertz,
wave_out: impl WaveOutPin,
) -> Result<Self, TtcConstructionError> {
IoPeriphPin::new(wave_out, TTC_MUX_CONF, None);
Ok(Self::new_with_cpu_clk(channel, arm_clocks, freq)?)
}
/// Create a new PWM instance which uses the CPU 1x clock as the clock source.
pub fn new_with_cpu_clk(
channel: TtcChannel,
arm_clocks: &ArmClocks,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
Self::new_generic(channel, arm_clocks.cpu_1x_clk(), freq)
}
/// Create a new PWM instance based on a reference clock source.
pub fn new_generic(
channel: TtcChannel,
ref_clk: Hertz,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
if freq.raw() == 0 {
return Err(FrequencyIsZeroError);
}
let (prescaler_reg, tick_val) = calculate_prescaler_reg_and_interval_ticks(ref_clk, freq);
let id = channel.id() as usize;
let mut pwm = Self { channel, ref_clk };
pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
Ok(pwm)
}
/// Set a new frequency for the PWM cycle.
///
/// This resets the duty cycle to 0%.
pub fn set_frequency(&mut self, freq: Hertz) -> Result<(), FrequencyIsZeroError> {
if freq.raw() == 0 {
return Err(FrequencyIsZeroError);
}
let id = self.channel.id() as usize;
let (prescaler_reg, tick_val) =
calculate_prescaler_reg_and_interval_ticks(self.ref_clk, freq);
self.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
Ok(())
}
#[inline]
pub fn ttc_channel_mut(&mut self) -> &mut TtcChannel {
&mut self.channel
}
#[inline]
pub fn max_duty_cycle(&self) -> u16 {
self.channel
.regs
.read_interval_value(self.channel.id() as usize)
.unwrap()
.value()
}
#[inline]
pub fn set_duty_cycle(&mut self, duty: u16) {
let id = self.channel.id() as usize;
self.channel
.regs
.modify_cnt_ctrl(id, |mut val| {
val.set_disable(true);
val
})
.unwrap();
self.channel
.regs
.write_match_value_0(
self.channel.id() as usize,
zynq7000::ttc::RwValue::new_with_raw_value(duty as u32),
)
.unwrap();
self.channel
.regs
.modify_cnt_ctrl(id, |mut val| {
val.set_disable(false);
val.set_reset(true);
val
})
.unwrap();
}
fn set_up_and_configure_pwm(&mut self, id: usize, prescaler_reg: Option<u4>, tick_val: u16) {
// Disable the counter first.
self.channel
.regs
.write_cnt_ctrl(id, zynq7000::ttc::CounterControl::new_with_raw_value(1))
.unwrap();
// Clock configuration
self.channel
.regs
.write_clk_cntr(
id,
zynq7000::ttc::ClockControl::builder()
.with_ext_clk_edge(false)
.with_clk_src(zynq7000::ttc::ClockSource::Pclk)
.with_prescaler(prescaler_reg.unwrap_or(u4::new(0)))
.with_prescale_enable(prescaler_reg.is_some())
.build(),
)
.unwrap();
self.channel
.regs
.write_interval_value(
id,
zynq7000::ttc::RwValue::new_with_raw_value(tick_val as u32),
)
.unwrap();
// Corresponds to duty cycle 0.
self.channel
.regs
.write_match_value_0(id, zynq7000::ttc::RwValue::new_with_raw_value(0))
.unwrap();
self.channel
.regs
.write_cnt_ctrl(
id,
zynq7000::ttc::CounterControl::builder()
.with_wave_polarity(zynq7000::ttc::WavePolarity::LowToHighOnMatch1)
.with_wave_enable_n(zynq7000::ttc::WaveEnable::Enable)
.with_reset(true)
.with_match_enable(true)
.with_decrementing(false)
.with_mode(zynq7000::ttc::Mode::Interval)
.with_disable(false)
.build(),
)
.unwrap();
}
}
impl embedded_hal::pwm::ErrorType for Pwm {
type Error = Infallible;
}
impl embedded_hal::pwm::SetDutyCycle for Pwm {
#[inline]
fn max_duty_cycle(&self) -> u16 {
self.max_duty_cycle()
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.set_duty_cycle(duty);
Ok(())
}
}