pub mod controller;
#[cfg(test)]
mod tests;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::sync::{AtomicBool, AtomicU32, LockRank, Mutex, Ordering};
use crate::core::wire::{Level, WireId, WireSink, WireSource};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct ChipSelect(pub u8);
impl fmt::Display for ChipSelect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cs{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Mode {
#[default]
Mode0,
Mode1,
Mode2,
Mode3,
}
impl Mode {
#[must_use]
pub const fn from_number(n: u8) -> Option<Mode> {
match n {
0 => Some(Mode::Mode0),
1 => Some(Mode::Mode1),
2 => Some(Mode::Mode2),
3 => Some(Mode::Mode3),
_ => None,
}
}
#[must_use]
pub const fn number(self) -> u8 {
match self {
Mode::Mode0 => 0,
Mode::Mode1 => 1,
Mode::Mode2 => 2,
Mode::Mode3 => 3,
}
}
#[must_use]
pub const fn from_cpol_cpha(cpol: bool, cpha: bool) -> Mode {
match (cpol, cpha) {
(false, false) => Mode::Mode0,
(false, true) => Mode::Mode1,
(true, false) => Mode::Mode2,
(true, true) => Mode::Mode3,
}
}
#[must_use]
pub const fn cpol(self) -> bool {
matches!(self, Mode::Mode2 | Mode::Mode3)
}
#[must_use]
pub const fn cpha(self) -> bool {
matches!(self, Mode::Mode1 | Mode::Mode3)
}
#[must_use]
pub const fn idle_level(self) -> Level {
if self.cpol() { Level::High } else { Level::Low }
}
#[must_use]
pub const fn samples_on(self, to: Level) -> bool {
let away_from_idle = to.is_high() != self.cpol();
away_from_idle != self.cpha()
}
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mode{}", self.number())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum BitOrder {
#[default]
MsbFirst,
LsbFirst,
}
impl fmt::Display for BitOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
BitOrder::MsbFirst => "msb-first",
BitOrder::LsbFirst => "lsb-first",
})
}
}
pub const MIN_WORD_BITS: u8 = 1;
pub const MAX_WORD_BITS: u8 = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Format {
pub mode: Mode,
pub bits: u8,
pub order: BitOrder,
}
impl Format {
pub const DEFAULT: Format = Format {
mode: Mode::Mode0,
bits: 8,
order: BitOrder::MsbFirst,
};
#[must_use]
pub const fn new(mode: Mode, bits: u8, order: BitOrder) -> Format {
let bits = if bits < MIN_WORD_BITS {
MIN_WORD_BITS
} else if bits > MAX_WORD_BITS {
MAX_WORD_BITS
} else {
bits
};
Format { mode, bits, order }
}
#[must_use]
pub const fn mask(self) -> u32 {
if self.bits >= 32 {
u32::MAX
} else {
(1u32 << self.bits) - 1
}
}
#[must_use]
pub const fn truncate(self, word: u32) -> u32 {
word & self.mask()
}
}
impl Default for Format {
fn default() -> Format {
Format::DEFAULT
}
}
impl fmt::Display for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}-bit {}", self.mode, self.bits, self.order)
}
}
pub trait SpiSlave: Send + Sync + fmt::Debug {
fn format(&self) -> Format;
fn select(&self, selected: bool);
fn transfer(&self, mosi: u32) -> u32;
fn peek(&self) -> u32 {
u32::MAX
}
fn turnaround(&self) -> Option<u8> {
None
}
fn partial(&self, bits: u8, received: u32) -> Option<u32> {
let _ = (bits, received);
None
}
}
pub fn exchange(slave: &dyn SpiSlave, mosi: u32) -> u32 {
let format = slave.format();
let turn = match (slave.turnaround(), format.order) {
(Some(n), BitOrder::MsbFirst) if n > 0 && n < format.bits => Some(n),
_ => None,
};
let spliced = turn.and_then(|n| {
let remaining = format.bits - n;
slave
.partial(n, mosi >> remaining)
.map(|word| (remaining, word))
});
let presented = format.truncate(slave.transfer(mosi));
match spliced {
Some((remaining, word)) => {
let mask = if remaining >= 32 {
u32::MAX
} else {
(1u32 << remaining) - 1
};
(presented & !mask) | (word & mask)
}
None => presented,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Link {
#[default]
Transactional,
Wired,
}
impl Link {
#[must_use]
pub fn from_name(name: &str) -> Option<Link> {
match name {
"transactional" => Some(Link::Transactional),
"wired" => Some(Link::Wired),
_ => None,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Link::Transactional => "transactional",
Link::Wired => "wired",
}
}
pub const NAMES: &'static [&'static str] = &["transactional", "wired"];
}
impl fmt::Display for Link {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
pub const FABRIC_RANK: LockRank = LockRank::new(0x4400);
pub const SHIFTER_RANK: LockRank = LockRank::new(0x4800);
pub const MAX_CHIP_SELECTS: usize = 8;
pub struct SpiBus {
slaves: Mutex<[Option<Arc<dyn SpiSlave>>; MAX_CHIP_SELECTS]>,
active: AtomicU32,
}
impl fmt::Debug for SpiBus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("SpiBus");
s.field("active", &self.active.load(Ordering::Relaxed));
match self.slaves.try_lock() {
Some(slaves) => s.field("attached", &slaves.iter().filter(|s| s.is_some()).count()),
None => s.field("attached", &"<in use>"),
};
s.finish()
}
}
const NO_SELECTION: u32 = MAX_CHIP_SELECTS as u32;
impl SpiBus {
#[must_use]
pub fn new() -> SpiBus {
SpiBus {
slaves: Mutex::with_rank(FABRIC_RANK, Default::default()),
active: AtomicU32::new(NO_SELECTION),
}
}
pub fn attach(&self, cs: ChipSelect, slave: Arc<dyn SpiSlave>) -> crate::Result<()> {
let index = usize::from(cs.0);
if index >= MAX_CHIP_SELECTS {
return Err(crate::Error::Config {
at: alloc::format!("{cs}"),
message: alloc::format!("an SPI bus routes {MAX_CHIP_SELECTS} chip selects"),
});
}
let mut slaves = self.slaves.lock();
if slaves[index].is_some() {
return Err(crate::Error::Config {
at: alloc::format!("{cs}"),
message: alloc::string::String::from(
"two devices on one SPI chip select; give one of them another `cs`",
),
});
}
slaves[index] = Some(slave);
Ok(())
}
pub fn detach(&self, cs: ChipSelect) -> bool {
let index = usize::from(cs.0);
if index >= MAX_CHIP_SELECTS {
return false;
}
self.slaves.lock()[index].take().is_some()
}
#[must_use]
pub fn slave(&self, cs: ChipSelect) -> Option<Arc<dyn SpiSlave>> {
let index = usize::from(cs.0);
if index >= MAX_CHIP_SELECTS {
return None;
}
self.slaves.lock()[index].clone()
}
#[must_use]
pub fn attached(&self) -> Vec<ChipSelect> {
let slaves = self.slaves.lock();
(0..MAX_CHIP_SELECTS)
.filter(|i| slaves[*i].is_some())
.map(|i| ChipSelect(i as u8))
.collect()
}
#[must_use]
pub fn selected(&self) -> Option<ChipSelect> {
match self.active.load(Ordering::Relaxed) {
NO_SELECTION => None,
n => Some(ChipSelect(n as u8)),
}
}
pub fn select(&self, cs: Option<ChipSelect>) {
let want = cs.map_or(NO_SELECTION, |c| u32::from(c.0));
let had = self.active.swap(want, Ordering::Relaxed);
if had == want {
return;
}
let (old, new) = {
let slaves = self.slaves.lock();
let old = (had != NO_SELECTION)
.then(|| slaves[had as usize].clone())
.flatten();
let new = (want != NO_SELECTION)
.then(|| slaves[want as usize].clone())
.flatten();
(old, new)
};
if let Some(slave) = old {
slave.select(false);
}
if let Some(slave) = new {
slave.select(true);
}
}
pub fn transfer(&self, word: u32) -> u32 {
let Some(cs) = self.selected() else {
return u32::MAX;
};
let Some(slave) = self.slave(cs) else {
return u32::MAX;
};
exchange(&*slave, word)
}
#[must_use]
pub fn peek(&self) -> u32 {
self.selected()
.and_then(|cs| self.slave(cs))
.map_or(u32::MAX, |s| s.peek())
}
#[must_use]
pub fn check_format(&self, format: Format) -> Option<ChipSelect> {
let slaves = self.slaves.lock();
(0..MAX_CHIP_SELECTS).find_map(|i| {
let slave = slaves[i].as_ref()?;
(slave.format() != format).then_some(ChipSelect(i as u8))
})
}
}
impl Default for SpiBus {
fn default() -> SpiBus {
SpiBus::new()
}
}
pub mod buses {
use super::SpiBus;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::sync::{Global, LockRank};
static TABLE: Global<BTreeMap<String, Arc<SpiBus>>> =
Global::with_rank(LockRank::LEAF, BTreeMap::new());
#[must_use]
pub fn open(name: &str) -> Arc<SpiBus> {
let mut table = TABLE.lock();
if let Some(bus) = table.get(name) {
return Arc::clone(bus);
}
let bus = Arc::new(SpiBus::new());
table.insert(name.to_string(), Arc::clone(&bus));
bus
}
#[must_use]
pub fn get(name: &str) -> Option<Arc<SpiBus>> {
TABLE.lock().get(name).map(Arc::clone)
}
pub fn close(name: &str) -> bool {
TABLE.lock().remove(name).is_some()
}
#[must_use]
pub fn names() -> Vec<String> {
TABLE.lock().keys().cloned().collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shifted {
Partial {
bits: u8,
received: u32,
},
Edge,
Word {
mosi: u32,
miso: u32,
},
Idle,
}
#[derive(Debug)]
pub struct Shifter {
format: Format,
rx: u32,
tx: u32,
count: u8,
selected: bool,
sck: Level,
mosi: Level,
loaded: bool,
}
impl Shifter {
#[must_use]
pub fn new(format: Format) -> Shifter {
Shifter {
format,
rx: 0,
tx: 0,
count: 0,
selected: false,
sck: format.mode.idle_level(),
mosi: Level::Low,
loaded: false,
}
}
#[must_use]
pub const fn format(&self) -> Format {
self.format
}
pub fn set_format(&mut self, format: Format) {
self.format = format;
self.sck = format.mode.idle_level();
self.abandon();
}
#[must_use]
pub const fn in_word(&self) -> bool {
self.count > 0
}
#[must_use]
pub const fn bit_count(&self) -> u8 {
self.count
}
#[must_use]
pub const fn selected(&self) -> bool {
self.selected
}
#[must_use]
pub fn miso(&self) -> Level {
if !self.selected || !self.loaded {
return Level::High;
}
let bit = match self.format.order {
BitOrder::MsbFirst => {
let shift = self.format.bits - 1 - self.count.min(self.format.bits - 1);
(self.tx >> shift) & 1
}
BitOrder::LsbFirst => (self.tx >> self.count.min(self.format.bits - 1)) & 1,
};
Level::from_bool(bit != 0)
}
pub fn set_mosi(&mut self, level: Level) {
self.mosi = level;
}
pub fn preload(&mut self, word: u32) {
self.tx = self.format.truncate(word);
self.loaded = true;
}
pub fn set_select(&mut self, selected: bool) -> Option<u32> {
if selected == self.selected {
return None;
}
self.selected = selected;
let partial = self.in_word().then_some(self.rx);
self.abandon();
partial
}
pub fn set_sck(&mut self, level: Level, mut exchange: impl FnMut(u32) -> u32) -> Shifted {
if level == self.sck {
return Shifted::Idle;
}
self.sck = level;
if !self.selected {
return Shifted::Idle;
}
if !self.format.mode.samples_on(level) {
return Shifted::Edge;
}
match self.format.order {
BitOrder::MsbFirst => {
self.rx = (self.rx << 1) | u32::from(self.mosi.as_bool());
}
BitOrder::LsbFirst => {
self.rx |= u32::from(self.mosi.as_bool()) << self.count;
}
}
self.count += 1;
if self.count < self.format.bits {
return Shifted::Partial {
bits: self.count,
received: self.rx,
};
}
let mosi = self.format.truncate(self.rx);
let miso = self.tx;
self.rx = 0;
self.count = 0;
self.preload(exchange(mosi));
Shifted::Word { mosi, miso }
}
pub fn abandon(&mut self) {
self.rx = 0;
self.tx = 0;
self.count = 0;
self.loaded = false;
}
#[must_use]
pub const fn snapshot(&self) -> (u32, u32, u8, bool, bool, bool, bool) {
(
self.rx,
self.tx,
self.count,
self.selected,
self.sck.is_high(),
self.mosi.is_high(),
self.loaded,
)
}
pub fn restore(&mut self, state: (u32, u32, u8, bool, bool, bool, bool)) {
let (rx, tx, count, selected, sck, mosi, loaded) = state;
self.rx = rx;
self.tx = tx;
self.count = count.min(self.format.bits);
self.selected = selected;
self.sck = Level::from_bool(sck);
self.mosi = Level::from_bool(mosi);
self.loaded = loaded;
}
}
pub mod pin {
pub const SCK: u32 = 0;
pub const MOSI: u32 = 1;
pub const CS: u32 = 2;
pub const SCK_NAME: &str = "sck";
pub const MOSI_NAME: &str = "mosi";
pub const CS_NAME: &str = "cs";
pub const MISO_NAME: &str = "miso";
}
pub struct SlavePins {
slave: Arc<dyn SpiSlave>,
shifter: Mutex<Shifter>,
miso: Mutex<Option<WireSource>>,
miso_level: AtomicBool,
pins: Mutex<Vec<Arc<PinSink>>>,
}
impl fmt::Debug for SlavePins {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SlavePins")
.field("slave", &self.slave)
.field(
"miso",
&Level::from_bool(self.miso_level.load(Ordering::Relaxed)),
)
.finish_non_exhaustive()
}
}
impl SlavePins {
#[must_use]
pub fn new(slave: Arc<dyn SpiSlave>) -> SlavePins {
let format = slave.format();
SlavePins {
slave,
shifter: Mutex::with_rank(SHIFTER_RANK, Shifter::new(format)),
miso: Mutex::with_rank(LockRank::WIRE, None),
miso_level: AtomicBool::new(true),
pins: Mutex::with_rank(LockRank::WIRE, Vec::new()),
}
}
#[must_use]
pub fn slave(&self) -> &Arc<dyn SpiSlave> {
&self.slave
}
pub fn connect_miso(&self, source: WireSource) {
*self.miso.lock() = Some(source);
self.publish_miso();
}
#[must_use]
pub fn miso_level(&self) -> Level {
Level::from_bool(self.miso_level.load(Ordering::Relaxed))
}
pub fn publish_miso(&self) {
let level = self.shifter.lock().miso();
self.miso_level.store(level.is_high(), Ordering::Relaxed);
let port = self.miso.lock().clone();
if let Some(port) = port {
port.set(level);
}
}
pub fn drive(&self, line: u32, level: Level) {
match line {
pin::MOSI => {
self.shifter.lock().set_mosi(level);
return;
}
pin::CS => {
let selected = level.is_low();
let moved = {
let mut shifter = self.shifter.lock();
let was = shifter.selected();
shifter.set_select(selected);
(was != shifter.selected()).then(|| shifter.selected())
};
let Some(moved) = moved else {
return;
};
self.slave.select(moved);
if moved {
let word = self.slave.peek();
self.shifter.lock().preload(word);
}
self.publish_miso();
return;
}
pin::SCK => {}
_ => return,
}
let shifted = {
let mut shifter = self.shifter.lock();
let slave = &self.slave;
shifter.set_sck(level, |word| {
slave.transfer(word);
slave.peek()
})
};
if let Shifted::Partial { bits, received } = shifted {
let msb_first = self.shifter.lock().format().order == BitOrder::MsbFirst;
if msb_first && self.slave.turnaround() == Some(bits) {
if let Some(word) = self.slave.partial(bits, received) {
self.shifter.lock().preload(word);
}
}
}
self.publish_miso();
}
#[must_use]
pub fn sink(self: &Arc<Self>, line: u32) -> Arc<dyn WireSink> {
let pin = Arc::new(PinSink {
pins: Arc::clone(self),
line,
});
self.pins.lock().push(Arc::clone(&pin));
pin as Arc<dyn WireSink>
}
pub fn reset(&self) {
let format = self.slave.format();
{
let mut shifter = self.shifter.lock();
shifter.set_format(format);
shifter.set_select(false);
}
self.publish_miso();
}
#[must_use]
pub fn snapshot(&self) -> (u32, u32, u8, bool, bool, bool, bool) {
self.shifter.lock().snapshot()
}
pub fn restore(&self, state: (u32, u32, u8, bool, bool, bool, bool)) {
self.shifter.lock().restore(state);
self.publish_miso();
}
}
struct PinSink {
pins: Arc<SlavePins>,
line: u32,
}
impl fmt::Debug for PinSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PinSink").field("line", &self.line).finish()
}
}
impl WireSink for PinSink {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.pins.drive(self.line, level);
}
}