427 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use core::cell::{Cell, RefCell};
 | |
| use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
 | |
| use core::task::Waker;
 | |
| 
 | |
| use critical_section::{CriticalSection, Mutex};
 | |
| use embassy_time_driver::Driver;
 | |
| use embassy_time_queue_utils::Queue;
 | |
| use mspm0_metapac::interrupt;
 | |
| use mspm0_metapac::tim::vals::{Cm, Cvae, CxC, EvtCfg, PwrenKey, Ratio, Repeat, ResetKey};
 | |
| use mspm0_metapac::tim::{Counterregs16, Tim};
 | |
| 
 | |
| use crate::peripherals;
 | |
| use crate::timer::SealedTimer;
 | |
| 
 | |
| #[cfg(any(time_driver_timg12, time_driver_timg13))]
 | |
| compile_error!("TIMG12 and TIMG13 are not supported by the time driver yet");
 | |
| 
 | |
| // Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers.
 | |
| #[cfg(time_driver_timg0)]
 | |
| type T = peripherals::TIMG0;
 | |
| #[cfg(time_driver_timg1)]
 | |
| type T = peripherals::TIMG1;
 | |
| #[cfg(time_driver_timg2)]
 | |
| type T = peripherals::TIMG2;
 | |
| #[cfg(time_driver_timg3)]
 | |
| type T = peripherals::TIMG3;
 | |
| #[cfg(time_driver_timg4)]
 | |
| type T = peripherals::TIMG4;
 | |
| #[cfg(time_driver_timg5)]
 | |
| type T = peripherals::TIMG5;
 | |
| #[cfg(time_driver_timg6)]
 | |
| type T = peripherals::TIMG6;
 | |
| #[cfg(time_driver_timg7)]
 | |
| type T = peripherals::TIMG7;
 | |
| #[cfg(time_driver_timg8)]
 | |
| type T = peripherals::TIMG8;
 | |
| #[cfg(time_driver_timg9)]
 | |
| type T = peripherals::TIMG9;
 | |
| #[cfg(time_driver_timg10)]
 | |
| type T = peripherals::TIMG10;
 | |
| #[cfg(time_driver_timg11)]
 | |
| type T = peripherals::TIMG11;
 | |
| #[cfg(time_driver_timg14)]
 | |
| type T = peripherals::TIMG14;
 | |
| #[cfg(time_driver_tima0)]
 | |
| type T = peripherals::TIMA0;
 | |
| #[cfg(time_driver_tima1)]
 | |
| type T = peripherals::TIMA1;
 | |
| 
 | |
| // TODO: RTC
 | |
| 
 | |
| fn regs() -> Tim {
 | |
|     unsafe { Tim::from_ptr(T::regs()) }
 | |
| }
 | |
| 
 | |
| fn regs_counter(tim: Tim) -> Counterregs16 {
 | |
|     unsafe { Counterregs16::from_ptr(tim.counterregs(0).as_ptr()) }
 | |
| }
 | |
| 
 | |
| /// Clock timekeeping works with something we call "periods", which are time intervals
 | |
| /// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods.
 | |
| fn calc_now(period: u32, counter: u16) -> u64 {
 | |
|     ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64)
 | |
| }
 | |
| 
 | |
| /// The TIMx driver uses one of the `TIMG` or `TIMA` timer instances to implement a timer with a 32.768 kHz
 | |
| /// tick rate. (TODO: Allow setting the tick rate)
 | |
| ///
 | |
| /// This driver defines a period to be 2^15 ticks. 16-bit timers of course count to 2^16 ticks.
 | |
| ///
 | |
| /// To generate a period every 2^15 ticks, the CC0 value is set to 2^15 and the load value set to 2^16.
 | |
| /// Incrementing the period on a CCU0 and load results in the a period of 2^15 ticks.
 | |
| ///
 | |
| /// For a specific timestamp, load the lower 16 bits into the CC1 value. When the period where the timestamp
 | |
| /// should be enabled is reached, then the CCU1 (CC1 up) interrupt runs to actually wake the timer.
 | |
| ///
 | |
| /// TODO: Compensate for per part variance. This can supposedly be done with the FCC system.
 | |
| /// TODO: Allow using 32-bit timers (TIMG12 and TIMG13).
 | |
| struct TimxDriver {
 | |
|     /// Number of 2^15 periods elapsed since boot.
 | |
|     period: AtomicU32,
 | |
|     /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
 | |
|     alarm: Mutex<Cell<u64>>,
 | |
|     queue: Mutex<RefCell<Queue>>,
 | |
| }
 | |
| 
 | |
| impl TimxDriver {
 | |
|     #[inline(never)]
 | |
|     fn init(&'static self, _cs: CriticalSection) {
 | |
|         // Clock config
 | |
|         // TODO: Configurable tick rate up to 4 MHz (32 kHz for now)
 | |
|         let regs = regs();
 | |
| 
 | |
|         // Reset timer
 | |
|         regs.gprcm(0).rstctl().write(|w| {
 | |
|             w.set_resetassert(true);
 | |
|             w.set_key(ResetKey::KEY);
 | |
|             w.set_resetstkyclr(true);
 | |
|         });
 | |
| 
 | |
|         // Power up timer
 | |
|         regs.gprcm(0).pwren().write(|w| {
 | |
|             w.set_enable(true);
 | |
|             w.set_key(PwrenKey::KEY);
 | |
|         });
 | |
| 
 | |
|         // Following the instructions according to SLAU847D 23.2.1: TIMCLK Configuration
 | |
| 
 | |
|         // 1. Select TIMCLK source
 | |
|         regs.clksel().modify(|w| {
 | |
|             // Use LFCLK for a 32.768kHz tick rate
 | |
|             w.set_lfclk_sel(true);
 | |
|             // TODO: Allow MFCLK for configurable tick rate up to 4 MHz
 | |
|             // w.set_mfclk_sel(ClkSel::ENABLE);
 | |
|         });
 | |
| 
 | |
|         // 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate
 | |
|         regs.clkdiv().modify(|w| {
 | |
|             w.set_ratio(Ratio::DIV_BY_1);
 | |
|         });
 | |
| 
 | |
|         // 3. To be generic across timer instances, we do not use the prescaler.
 | |
|         // TODO: mspm0-sdk always sets this, regardless of timer width?
 | |
|         regs.commonregs(0).cps().modify(|w| {
 | |
|             w.set_pcnt(0);
 | |
|         });
 | |
| 
 | |
|         regs.pdbgctl().modify(|w| {
 | |
|             w.set_free(true);
 | |
|         });
 | |
| 
 | |
|         // 4. Enable the TIMCLK.
 | |
|         regs.commonregs(0).cclkctl().modify(|w| {
 | |
|             w.set_clken(true);
 | |
|         });
 | |
| 
 | |
|         regs.counterregs(0).ctrctl().modify(|w| {
 | |
|             // allow counting during debug
 | |
|             w.set_repeat(Repeat::REPEAT_3);
 | |
|             w.set_cvae(Cvae::ZEROVAL);
 | |
|             w.set_cm(Cm::UP);
 | |
| 
 | |
|             // Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count.
 | |
|             //
 | |
|             // The reset value of these registers is 0x07, which is a reserved value.
 | |
|             //
 | |
|             // Looking at a bit representation of the reset value, this appears to be an AND
 | |
|             // of 2-input QEI mode and CCCTL_3 ACOND. Given that TIMG14 and TIMA0 have no QEI
 | |
|             // and 4 capture and compare channels, this works by accident for those timer units.
 | |
|             w.set_czc(CxC::CCTL0);
 | |
|             w.set_cac(CxC::CCTL0);
 | |
|             w.set_clc(CxC::CCTL0);
 | |
|         });
 | |
| 
 | |
|         // Setup the period
 | |
|         let ctr = regs_counter(regs);
 | |
| 
 | |
|         // Middle
 | |
|         ctr.cc(0).modify(|w| {
 | |
|             w.set_ccval(0x7FFF);
 | |
|         });
 | |
| 
 | |
|         ctr.load().modify(|w| {
 | |
|             w.set_ld(u16::MAX);
 | |
|         });
 | |
| 
 | |
|         // Enable the period interrupts
 | |
|         //
 | |
|         // This does not appear to ever be set for CPU_INT in the TI SDK and is not technically needed.
 | |
|         regs.evt_mode().modify(|w| {
 | |
|             w.set_evt_cfg(0, EvtCfg::SOFTWARE);
 | |
|         });
 | |
| 
 | |
|         regs.int_event(0).imask().modify(|w| {
 | |
|             w.set_l(true);
 | |
|             w.set_ccu0(true);
 | |
|         });
 | |
| 
 | |
|         unsafe { T::enable_interrupt() };
 | |
| 
 | |
|         // Allow the counter to start counting.
 | |
|         regs.counterregs(0).ctrctl().modify(|w| {
 | |
|             w.set_en(true);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     #[inline(never)]
 | |
|     fn next_period(&self) {
 | |
|         let r = regs();
 | |
| 
 | |
|         // We only modify the period from the timer interrupt, so we know this can't race.
 | |
|         let period = self.period.load(Ordering::Relaxed) + 1;
 | |
|         self.period.store(period, Ordering::Relaxed);
 | |
|         let t = (period as u64) << 15;
 | |
| 
 | |
|         critical_section::with(move |cs| {
 | |
|             r.int_event(0).imask().modify(move |w| {
 | |
|                 let alarm = self.alarm.borrow(cs);
 | |
|                 let at = alarm.get();
 | |
| 
 | |
|                 if at < t + 0xC000 {
 | |
|                     // just enable it. `set_alarm` has already set the correct CC1 val.
 | |
|                     w.set_ccu1(true);
 | |
|                 }
 | |
|             })
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     #[inline(never)]
 | |
|     fn on_interrupt(&self) {
 | |
|         let r = regs();
 | |
| 
 | |
|         critical_section::with(|cs| {
 | |
|             let mis = r.int_event(0).mis().read();
 | |
| 
 | |
|             // Advance to next period if overflowed
 | |
|             if mis.l() {
 | |
|                 self.next_period();
 | |
| 
 | |
|                 r.int_event(0).iclr().write(|w| {
 | |
|                     w.set_l(true);
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             if mis.ccu0() {
 | |
|                 self.next_period();
 | |
| 
 | |
|                 r.int_event(0).iclr().write(|w| {
 | |
|                     w.set_ccu0(true);
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             if mis.ccu1() {
 | |
|                 r.int_event(0).iclr().write(|w| {
 | |
|                     w.set_ccu1(true);
 | |
|                 });
 | |
| 
 | |
|                 self.trigger_alarm(cs);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     fn trigger_alarm(&self, cs: CriticalSection) {
 | |
|         let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
 | |
| 
 | |
|         while !self.set_alarm(cs, next) {
 | |
|             next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
 | |
|         let r = regs();
 | |
|         let ctr = regs_counter(r);
 | |
| 
 | |
|         self.alarm.borrow(cs).set(timestamp);
 | |
| 
 | |
|         let t = self.now();
 | |
| 
 | |
|         if timestamp <= t {
 | |
|             // If alarm timestamp has passed the alarm will not fire.
 | |
|             // Disarm the alarm and return `false` to indicate that.
 | |
|             r.int_event(0).imask().modify(|w| w.set_ccu1(false));
 | |
| 
 | |
|             self.alarm.borrow(cs).set(u64::MAX);
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // Write the CC1 value regardless of whether we're going to enable it now or not.
 | |
|         // This way, when we enable it later, the right value is already set.
 | |
|         ctr.cc(1).write(|w| {
 | |
|             w.set_ccval(timestamp as u16);
 | |
|         });
 | |
| 
 | |
|         // Enable it if it'll happen soon. Otherwise, `next_period` will enable it.
 | |
|         let diff = timestamp - t;
 | |
|         r.int_event(0).imask().modify(|w| w.set_ccu1(diff < 0xC000));
 | |
| 
 | |
|         // Reevaluate if the alarm timestamp is still in the future
 | |
|         let t = self.now();
 | |
|         if timestamp <= t {
 | |
|             // If alarm timestamp has passed since we set it, we have a race condition and
 | |
|             // the alarm may or may not have fired.
 | |
|             // Disarm the alarm and return `false` to indicate that.
 | |
|             // It is the caller's responsibility to handle this ambiguity.
 | |
|             r.int_event(0).imask().modify(|w| w.set_ccu1(false));
 | |
| 
 | |
|             self.alarm.borrow(cs).set(u64::MAX);
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // We're confident the alarm will ring in the future.
 | |
|         true
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Driver for TimxDriver {
 | |
|     fn now(&self) -> u64 {
 | |
|         let regs = regs();
 | |
| 
 | |
|         let period = self.period.load(Ordering::Relaxed);
 | |
|         // Ensure the compiler does not read the counter before the period.
 | |
|         compiler_fence(Ordering::Acquire);
 | |
| 
 | |
|         let counter = regs_counter(regs).ctr().read().cctr() as u16;
 | |
| 
 | |
|         calc_now(period, counter)
 | |
|     }
 | |
| 
 | |
|     fn schedule_wake(&self, at: u64, waker: &Waker) {
 | |
|         critical_section::with(|cs| {
 | |
|             let mut queue = self.queue.borrow(cs).borrow_mut();
 | |
| 
 | |
|             if queue.schedule_wake(at, waker) {
 | |
|                 let mut next = queue.next_expiration(self.now());
 | |
| 
 | |
|                 while !self.set_alarm(cs, next) {
 | |
|                     next = queue.next_expiration(self.now());
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| }
 | |
| 
 | |
| embassy_time_driver::time_driver_impl!(static DRIVER: TimxDriver = TimxDriver {
 | |
|     period: AtomicU32::new(0),
 | |
|     alarm: Mutex::new(Cell::new(u64::MAX)),
 | |
|     queue: Mutex::new(RefCell::new(Queue::new()))
 | |
| });
 | |
| 
 | |
| pub(crate) fn init(cs: CriticalSection) {
 | |
|     DRIVER.init(cs);
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg0)]
 | |
| #[interrupt]
 | |
| fn TIMG0() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg1)]
 | |
| #[interrupt]
 | |
| fn TIMG1() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg2)]
 | |
| #[interrupt]
 | |
| fn TIMG2() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg3)]
 | |
| #[interrupt]
 | |
| fn TIMG3() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg4)]
 | |
| #[interrupt]
 | |
| fn TIMG4() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg5)]
 | |
| #[interrupt]
 | |
| fn TIMG5() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg6)]
 | |
| #[interrupt]
 | |
| fn TIMG6() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg7)]
 | |
| #[interrupt]
 | |
| fn TIMG7() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg8)]
 | |
| #[interrupt]
 | |
| fn TIMG8() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg9)]
 | |
| #[interrupt]
 | |
| fn TIMG9() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg10)]
 | |
| #[interrupt]
 | |
| fn TIMG10() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_timg11)]
 | |
| #[interrupt]
 | |
| fn TIMG11() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| // TODO: TIMG12 and TIMG13
 | |
| 
 | |
| #[cfg(time_driver_timg14)]
 | |
| #[interrupt]
 | |
| fn TIMG14() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_tima0)]
 | |
| #[interrupt]
 | |
| fn TIMA0() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 | |
| 
 | |
| #[cfg(time_driver_tima1)]
 | |
| #[interrupt]
 | |
| fn TIMA1() {
 | |
|     DRIVER.on_interrupt();
 | |
| }
 |