-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from Foundation-Devices/SFT-3312-rtc-driver
SFT-3312: implement RTC driver for UTC mode
- Loading branch information
Showing
5 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |