Skip to content

Commit

Permalink
Merge pull request #47 from Foundation-Devices/SFT-3312-rtc-driver
Browse files Browse the repository at this point in the history
SFT-3312: implement RTC driver for UTC mode
  • Loading branch information
badicsalex authored Aug 26, 2024
2 parents 764eb71 + c7e9008 commit 73af63b
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ required-features = ["usb-host"]
name = "shdwc"
required-features = []

[[bin]]
name = "rtc"
required-features = []

[[bin]]
name = "aesb"
required-features = []
Expand Down
9 changes: 9 additions & 0 deletions scripts/debug-rtc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

# SPDX-FileCopyrightText: 2023 Foundation Devices, Inc. <[email protected]>
# SPDX-License-Identifier: MIT OR Apache-2.0

set -e

cargo build --release --bin rtc
arm-none-eabi-gdb -q ../target/armv7a-none-eabi/release/rtc -x init.gdb
136 changes: 136 additions & 0 deletions src/bin/rtc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: MIT OR Apache-2.0

#![no_std]
#![no_main]

use {
atsama5d27::{
aic::{Aic, InterruptEntry, SourceKind},
pmc::{PeripheralId, Pmc},
rtc::Rtc,
uart::{Uart, Uart1},
},
core::{
arch::global_asm,
fmt::Write,
panic::PanicInfo,
ptr::addr_of_mut,
sync::atomic::{compiler_fence, Ordering::SeqCst},
},
};

global_asm!(include_str!("../start.S"));

type UartType = Uart<Uart1>;
const UART_PERIPH_ID: PeripheralId = PeripheralId::Uart1;

#[no_mangle]
fn _entry() -> ! {
extern "C" {
// These symbols come from `link.ld`
static mut _sbss: u32;
static mut _ebss: u32;
}

// Initialize RAM
unsafe {
r0::zero_bss(addr_of_mut!(_sbss), addr_of_mut!(_ebss));
}

atsama5d27::l1cache::disable_dcache();

let mut pmc = Pmc::new();
pmc.enable_peripheral_clock(PeripheralId::Aic);

let mut aic = Aic::new();
aic.init();
aic.set_spurious_handler_fn_ptr(aic_spurious_handler as unsafe extern "C" fn() as usize);

let uart_irq_ptr = uart_irq_handler as unsafe extern "C" fn() as usize;
aic.set_interrupt_handler(InterruptEntry {
peripheral_id: UART_PERIPH_ID,
vector_fn_ptr: uart_irq_ptr,
kind: SourceKind::LevelSensitive,
priority: 0,
});

aic.set_interrupt_handler(InterruptEntry {
peripheral_id: PeripheralId::Sys,

vector_fn_ptr: rtc_irq_handler as unsafe extern "C" fn() as usize,
kind: SourceKind::LevelSensitive,
priority: 0,
});

// Enable interrupts
unsafe {
core::arch::asm!("cpsie if");
}
let mut uart = UartType::new();
uart.set_rx_interrupt(true);
uart.set_rx(true);

let rtc = Rtc::new();
rtc.start();
writeln!(uart, "Current timestamp: {:?}", rtc.time()).ok();
rtc.enable_interrupts();

loop {
armv7::asm::wfi();
}
}

unsafe extern "C" fn aic_spurious_handler() {
core::arch::asm!("bkpt");
}

unsafe extern "C" fn uart_irq_handler() {
let mut uart = UartType::new();
let char = uart.getc() as char;
writeln!(uart, "Received character: {}", char).ok();
}

unsafe extern "C" fn rtc_irq_handler() {
static mut COUNTDOWN: usize = 5;
static mut SET_TIME: Option<u32> = None;
let mut uart = UartType::new();
let rtc = Rtc::new();
unsafe {
if COUNTDOWN > 0 {
COUNTDOWN -= 1;
} else {
SET_TIME = Some(0x10000000 - 5);
COUNTDOWN = 10;
}
}

let set_time_happened = rtc.handle_interrupt(unsafe { SET_TIME });
if set_time_happened {
unsafe { SET_TIME = None }
}

writeln!(
uart,
"RTC interrupt. Time: {:x?}, Countdown: {}, SetTime: {:?}",
rtc.time(),
unsafe { COUNTDOWN },
unsafe { SET_TIME }
)
.ok();
}

#[inline(never)]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
let mut console = Uart::<Uart1>::new();

compiler_fence(SeqCst);
writeln!(console, "{}", _info).ok();

loop {
unsafe {
core::arch::asm!("bkpt");
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod pio;
pub mod pit;
pub mod pmc;
pub mod rstc;
pub mod rtc;
pub mod sckc;
pub mod sdmmc;
pub mod sfr;
Expand Down
117 changes: 117 additions & 0 deletions src/rtc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Reset Controller (RTC)

use utralib::{utra::rtc::*, HW_RTC_BASE, *};

pub struct Rtc {
base_addr: u32,
}

impl Default for Rtc {
fn default() -> Self {
Rtc::new()
}
}

impl Rtc {
pub fn new() -> Self {
Self {
base_addr: HW_RTC_BASE as u32,
}
}

/// Creates RTC instance with a different base address. Used with virtual memory
pub fn with_alt_base_addr(base_addr: u32) -> Self {
Self { base_addr }
}

/// Get seconds elapsed since the Unix epoch (disregarding the existence of leap
/// seconds) Returns None if the clock is not yet configured.
pub fn time(&self) -> Option<u32> {
let rtc_csr = CSR::new(self.base_addr as *mut u32);
if rtc_csr.rf(MR_UTC) == 0 {
// Only UTC time is supported, because we don't use date alarms, will only use
// this mode in all code and use rust or chrono Date for all actual operations,
// so Gregorian mode would be a lot of relatively complicated dead code.
return None;
}
let result1 = rtc_csr.r(TIMR);
let result2 = rtc_csr.r(TIMR);
if result1 == result2 {
Some(result1)
} else {
// We ran into an async update, the third access should yield a stable result.
Some(rtc_csr.r(TIMR))
}
}

/// Enables the per-second and ACKUPD interrupt. Interrupt handler
/// is called at 1Hz, or more if time is being set.
pub fn enable_interrupts(&self) {
let mut rtc_csr = CSR::new(self.base_addr as *mut u32);
rtc_csr.wfo(IER_ACKEN, 1);
rtc_csr.wfo(IER_SECEN, 1);
}

/// Set seconds elapsed since the Unix epoch.
/// Should only be called when the RTC is stopped (ACKUPD is true)
fn set_time(&self, timestamp: u32) {
let mut rtc_csr = CSR::new(self.base_addr as *mut u32);
if rtc_csr.rf(MR_UTC) == 0 {
rtc_csr.rmwf(MR_UTC, 1);
}
rtc_csr.wo(TIMR, timestamp);
}

/// The RTC timer has stopped, time and date can be modified.
fn stopped(&self) -> bool {
let rtc_csr = CSR::new(self.base_addr as *mut u32);
rtc_csr.rf(SR_ACKUPD) != 0
}

/// Request the stop of the RTC timer. Does not stop right away,
/// stopped has to be polled, or the ACKUPD interrupt has to be enabled.
fn request_stop(&self) {
let mut rtc_csr = CSR::new(self.base_addr as *mut u32);
let mut cr = rtc_csr.r(CR);
cr |= rtc_csr.ms(CR_UPDTIM, 1);
// This shouldn't be necessary according to the docs, but if this bit is not set the upper
// bits of the TIMR value cannot be set either.
cr |= rtc_csr.ms(CR_UPDCAL, 1);
rtc_csr.wo(CR, cr)
}

/// Restart the RTC timer after stopping.
pub fn start(&self) {
let mut rtc_csr = CSR::new(self.base_addr as *mut u32);
let mut cr = rtc_csr.r(CR);
cr = rtc_csr.zf(CR_UPDTIM, cr);
cr = rtc_csr.zf(CR_UPDCAL, cr);
rtc_csr.wo(CR, cr)
}

/// Clear all event flags set currently
fn clear_events(&self) {
let mut rtc_csr = CSR::new(self.base_addr as *mut u32);
let events = rtc_csr.r(SR);
rtc_csr.wo(SCCR, events);
}

/// Helper function to call from an interrupt handler that also does the set time
/// logic as described in the SAMA5D2 docs. Returns if time was successfully set.
pub fn handle_interrupt(&self, set_time: Option<u32>) -> bool {
let mut set_time_happened = false;
let stopped = self.stopped();
self.clear_events();
if let Some(timestamp) = set_time {
if !stopped {
self.request_stop();
// We should get retriggered soon with the ACKUPD interrupt
} else {
self.set_time(timestamp);
self.start();
set_time_happened = true;
}
};
set_time_happened
}
}

0 comments on commit 73af63b

Please sign in to comment.