use core::convert::Infallible;
use rivet::sync::Signal;
const CR0: usize = 0x000;
const CR1: usize = 0x004;
const DR: usize = 0x008;
const SR: usize = 0x00C;
const CPSR: usize = 0x010;
const IMSC: usize = 0x014;
const SR_TNF: u32 = 1 << 1; const SR_RNE: u32 = 1 << 2; const SR_BSY: u32 = 1 << 4;
const CR1_LBM: u32 = 1 << 0; const CR1_SSE: u32 = 1 << 1;
const IMSC_RXIM: u32 = 1 << 2;
const FIFO_DEPTH: usize = 8;
pub struct Pl022 {
base: usize,
sig: &'static Signal,
}
impl Pl022 {
pub const unsafe fn new(base: usize, sig: &'static Signal) -> Self {
Self { base, sig }
}
fn reg(&self, offset: usize) -> *mut u32 {
(self.base + offset) as *mut u32
}
pub fn init(&self, loopback: bool) {
unsafe {
core::ptr::write_volatile(self.reg(CR1), 0); core::ptr::write_volatile(self.reg(CPSR), 2); core::ptr::write_volatile(self.reg(CR0), 0x07); let mut cr1 = CR1_SSE;
if loopback {
cr1 |= CR1_LBM;
}
core::ptr::write_volatile(self.reg(CR1), cr1);
}
}
fn tx_byte(&self, b: u8) {
unsafe {
while core::ptr::read_volatile(self.reg(SR)) & SR_TNF == 0 {
core::hint::spin_loop();
}
core::ptr::write_volatile(self.reg(DR), b as u32);
}
}
fn rx_byte_poll(&self) -> u8 {
unsafe {
while core::ptr::read_volatile(self.reg(SR)) & SR_RNE == 0 {
core::hint::spin_loop();
}
core::ptr::read_volatile(self.reg(DR)) as u8
}
}
fn transfer_word_sync(&self, tx: u8) -> u8 {
self.tx_byte(tx);
self.rx_byte_poll()
}
async fn transfer_chunk_async(&self, tx: &[u8], rx: &mut [u8]) {
debug_assert!(tx.len() <= FIFO_DEPTH && rx.len() <= FIFO_DEPTH);
self.sig.reset();
unsafe {
core::ptr::write_volatile(self.reg(IMSC), IMSC_RXIM);
}
for &b in tx {
self.tx_byte(b);
}
self.sig.wait().await;
for slot in rx.iter_mut() {
*slot = self.rx_byte_poll();
}
}
async fn transfer_async(&self, read: &mut [u8], write: &[u8]) {
let n = read.len().max(write.len());
let mut i = 0;
while i < n {
let end = (i + FIFO_DEPTH).min(n);
let chunk_len = end - i;
let mut tx_buf = [0u8; FIFO_DEPTH];
for (k, slot) in tx_buf[..chunk_len].iter_mut().enumerate() {
*slot = write.get(i + k).copied().unwrap_or(0);
}
let mut rx_buf = [0u8; FIFO_DEPTH];
self.transfer_chunk_async(&tx_buf[..chunk_len], &mut rx_buf[..chunk_len])
.await;
for (k, &b) in rx_buf[..chunk_len].iter().enumerate() {
if let Some(slot) = read.get_mut(i + k) {
*slot = b;
}
}
i = end;
}
}
}
pub fn isr_ack(base: usize, sig: &Signal) {
unsafe {
core::ptr::write_volatile((base + IMSC) as *mut u32, 0);
}
sig.signal();
}
#[macro_export]
macro_rules! pl022_instance {
($sig_name:ident, $isr_name:ident, base = $base:expr) => {
static $sig_name: ::rivet::sync::Signal = ::rivet::sync::Signal::new();
fn $isr_name() {
$crate::pl022::isr_ack($base, &$sig_name);
}
};
}
impl embedded_hal::spi::ErrorType for Pl022 {
type Error = Infallible;
}
impl embedded_hal::spi::SpiBus<u8> for Pl022 {
fn read(&mut self, words: &mut [u8]) -> Result<(), Infallible> {
for w in words.iter_mut() {
*w = self.transfer_word_sync(0);
}
Ok(())
}
fn write(&mut self, words: &[u8]) -> Result<(), Infallible> {
for &w in words {
self.transfer_word_sync(w);
}
Ok(())
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Infallible> {
let n = read.len().max(write.len());
for i in 0..n {
let tx = write.get(i).copied().unwrap_or(0);
let rx = self.transfer_word_sync(tx);
if let Some(slot) = read.get_mut(i) {
*slot = rx;
}
}
Ok(())
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Infallible> {
for w in words.iter_mut() {
*w = self.transfer_word_sync(*w);
}
Ok(())
}
fn flush(&mut self) -> Result<(), Infallible> {
unsafe {
while core::ptr::read_volatile(self.reg(SR)) & SR_BSY != 0 {
core::hint::spin_loop();
}
}
Ok(())
}
}
impl embedded_hal_async::spi::SpiBus<u8> for Pl022 {
async fn read(&mut self, words: &mut [u8]) -> Result<(), Infallible> {
self.transfer_async(words, &[]).await;
Ok(())
}
async fn write(&mut self, words: &[u8]) -> Result<(), Infallible> {
self.transfer_async(&mut [], words).await;
Ok(())
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Infallible> {
self.transfer_async(read, write).await;
Ok(())
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Infallible> {
let n = words.len();
let mut i = 0;
while i < n {
let end = (i + FIFO_DEPTH).min(n);
let chunk_len = end - i;
let mut tx_buf = [0u8; FIFO_DEPTH];
tx_buf[..chunk_len].copy_from_slice(&words[i..end]);
let mut rx_buf = [0u8; FIFO_DEPTH];
self.transfer_chunk_async(&tx_buf[..chunk_len], &mut rx_buf[..chunk_len])
.await;
words[i..end].copy_from_slice(&rx_buf[..chunk_len]);
i = end;
}
Ok(())
}
async fn flush(&mut self) -> Result<(), Infallible> {
unsafe {
while core::ptr::read_volatile(self.reg(SR)) & SR_BSY != 0 {
core::hint::spin_loop();
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code)]
fn generic_sync<S: embedded_hal::spi::SpiBus<u8>>(s: &mut S, buf: &mut [u8]) {
let _ = s.transfer_in_place(buf);
}
#[allow(dead_code)]
async fn generic_async<S: embedded_hal_async::spi::SpiBus<u8>>(s: &mut S, buf: &mut [u8]) {
let _ = s.transfer_in_place(buf).await;
}
#[test]
fn type_checks() {
let _ = generic_sync::<Pl022>;
let _ = generic_async::<Pl022>;
}
}