use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::bus::spi::{
BitOrder, ChipSelect, Format, Link, MAX_CHIP_SELECTS, Mode, SlavePins, SpiBus, SpiSlave, buses,
pin as slave_pin,
};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{AccessKind, LazyHandle};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicBool, AtomicU64, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireId, WireSink, WireSource};
use crate::machine::realize::Instance;
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
pub const CLASS_NAME: &str = "stm32.spi";
const STATE_VERSION: u32 = 1;
pub const REGISTER_BYTES: u64 = 0x400;
const LAST_REGISTER: u64 = 0x20;
const CR1_CPHA: u16 = 1 << 0;
const CR1_CPOL: u16 = 1 << 1;
const CR1_MSTR: u16 = 1 << 2;
const CR1_BR_SHIFT: u32 = 3;
const CR1_BR_MASK: u16 = 0x7;
const CR1_SPE: u16 = 1 << 6;
const CR1_LSBFIRST: u16 = 1 << 7;
const CR1_SSI: u16 = 1 << 8;
const CR1_SSM: u16 = 1 << 9;
const CR1_RXONLY: u16 = 1 << 10;
const CR1_DFF: u16 = 1 << 11;
const CR1_CRCNEXT: u16 = 1 << 12;
const CR1_CRCEN: u16 = 1 << 13;
const CR1_BIDIOE: u16 = 1 << 14;
const CR1_BIDIMODE: u16 = 1 << 15;
const CR2_RXDMAEN: u16 = 1 << 0;
const CR2_TXDMAEN: u16 = 1 << 1;
const CR2_SSOE: u16 = 1 << 2;
const CR2_FRF: u16 = 1 << 4;
const CR2_ERRIE: u16 = 1 << 5;
const CR2_RXNEIE: u16 = 1 << 6;
const CR2_TXEIE: u16 = 1 << 7;
const CR2_MASK: u16 =
CR2_RXDMAEN | CR2_TXDMAEN | CR2_SSOE | CR2_FRF | CR2_ERRIE | CR2_RXNEIE | CR2_TXEIE;
const SR_RXNE: u16 = 1 << 0;
const SR_TXE: u16 = 1 << 1;
const SR_UDR: u16 = 1 << 3;
const SR_CRCERR: u16 = 1 << 4;
const SR_MODF: u16 = 1 << 5;
const SR_OVR: u16 = 1 << 6;
const SR_BSY: u16 = 1 << 7;
const SR_FRE: u16 = 1 << 8;
const SR_RESET: u16 = SR_TXE;
const CRCPR_RESET: u16 = 0x0007;
const I2SPR_RESET: u16 = 0x0002;
const NO_EVENT: u64 = u64::MAX;
pub mod pin {
pub const SCK: &str = "sck";
pub const MOSI: &str = "mosi";
pub const MISO: &str = "miso";
pub const NSS: &str = "nss";
pub const NSS_IN: &str = "nss-in";
pub const SCK_IN: &str = "sck-in";
pub const MOSI_IN: &str = "mosi-in";
pub const MISO_OUT: &str = "miso-out";
pub const MISO_LINE: u32 = 0;
pub const NSS_IN_LINE: u32 = 1;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct State {
ticks: u64,
cr1: u16,
cr2: u16,
sr: u16,
tx: u16,
rx: u16,
crcpr: u16,
rxcrc: u16,
txcrc: u16,
i2scfgr: u16,
i2spr: u16,
tx_pending: bool,
shift: u16,
busy: bool,
crc_frame: bool,
started: u64,
edges: u32,
shift_in: u32,
nss_low: bool,
nss_in: Level,
ovr_dr_read: bool,
modf_sr_seen: bool,
}
impl Default for State {
fn default() -> State {
State {
ticks: 0,
cr1: 0,
cr2: 0,
sr: SR_RESET,
tx: 0,
rx: 0,
crcpr: CRCPR_RESET,
rxcrc: 0,
txcrc: 0,
i2scfgr: 0,
i2spr: I2SPR_RESET,
tx_pending: false,
shift: 0,
busy: false,
crc_frame: false,
started: 0,
edges: 0,
shift_in: 0,
nss_low: false,
nss_in: Level::High,
ovr_dr_read: false,
modf_sr_seen: false,
}
}
}
impl State {
fn format(&self) -> Format {
Format::new(
Mode::from_cpol_cpha(self.cr1 & CR1_CPOL != 0, self.cr1 & CR1_CPHA != 0),
if self.cr1 & CR1_DFF != 0 { 16 } else { 8 },
if self.cr1 & CR1_LSBFIRST != 0 {
BitOrder::LsbFirst
} else {
BitOrder::MsbFirst
},
)
}
fn half_period(&self) -> u64 {
1u64 << ((self.cr1 >> CR1_BR_SHIFT) & CR1_BR_MASK)
}
fn total_edges(&self) -> u32 {
u32::from(self.format().bits) * 2
}
fn end_tick(&self) -> u64 {
self.started
.saturating_add(u64::from(self.total_edges()) * self.half_period())
}
fn is_master(&self) -> bool {
self.cr1 & CR1_MSTR != 0
}
fn is_enabled(&self) -> bool {
self.cr1 & CR1_SPE != 0
}
fn receive_only(&self) -> bool {
self.cr1 & CR1_RXONLY != 0 || (self.cr1 & CR1_BIDIMODE != 0 && self.cr1 & CR1_BIDIOE == 0)
}
fn transmit_only(&self) -> bool {
self.cr1 & CR1_BIDIMODE != 0 && self.cr1 & CR1_BIDIOE != 0
}
fn nss_level(&self) -> Level {
if self.cr1 & CR1_SSM != 0 {
Level::from_bool(self.cr1 & CR1_SSI != 0)
} else {
self.nss_in
}
}
fn mode_fault_due(&self) -> bool {
self.is_master()
&& !(self.cr1 & CR1_SSM == 0 && self.cr2 & CR2_SSOE != 0)
&& self.nss_level().is_low()
}
fn nss_output_low(&self) -> bool {
self.is_master() && self.is_enabled() && self.cr1 & CR1_SSM == 0 && self.cr2 & CR2_SSOE != 0
}
}
fn crc_step(crc: u16, data: u16, bits: u8, poly: u16) -> u16 {
let width = u32::from(bits);
let mask: u32 = if width >= 32 {
u32::MAX
} else {
(1u32 << width) - 1
};
let top = 1u32 << (width - 1);
let mut acc = u32::from(crc) & mask;
for i in (0..width).rev() {
let bit = (u32::from(data) >> i) & 1;
let msb = acc & top != 0;
acc = (acc << 1) & mask;
if msb != (bit != 0) {
acc ^= u32::from(poly) & mask;
}
}
acc as u16
}
#[derive(Debug)]
pub struct Stm32Spi {
shared: Arc<Shared>,
pins: Arc<SlavePins>,
region: RegionRef,
}
#[derive(Debug, Default)]
struct Pins {
sck: Option<WireSource>,
mosi: Option<WireSource>,
nss: Option<WireSource>,
}
struct Shared {
state: Mutex<State>,
link: Link,
bus: Option<Arc<SpiBus>>,
cs: ChipSelect,
ticks: AtomicU64,
next_event: AtomicU64,
miso: AtomicBool,
irq_level: AtomicBool,
pins: Mutex<Pins>,
irq: Mutex<Option<WireSource>>,
sinks: Mutex<Vec<Arc<InputSink>>>,
lazy: Mutex<Option<LazyHandle>>,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Shared");
s.field("link", &self.link).field("cs", &self.cs);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
impl Stm32Spi {
pub fn new(props: &Props) -> Result<Stm32Spi> {
let mut r = props.reader();
let link_name = r.require_str("link")?.to_string();
let bus_name = r.optional_str("bus")?.map(String::from);
let cs = r.or_range("cs", 0u64, 0..=(MAX_CHIP_SELECTS as u64 - 1))?;
r.finish()?;
let link = Link::from_name(&link_name).ok_or_else(|| Error::Config {
at: String::from(CLASS_NAME),
message: alloc::format!(
"`link` is `{link_name}`; it must be one of {:?} — see docs/buses/low-speed.md \
for which to pick",
Link::NAMES
),
})?;
if link == Link::Transactional && bus_name.is_none() {
return Err(Error::Config {
at: String::from(CLASS_NAME),
message: String::from(
"a `transactional` SPI master reaches its slaves through a named bus; give \
it `bus = \"spi1\"` and name the same bus on each slave",
),
});
}
let bus = bus_name
.as_deref()
.map(|name| buses::attach(props, name))
.transpose()?;
Ok(Stm32Spi::with_bus(link, bus, ChipSelect(cs as u8)))
}
#[must_use]
pub fn with_bus(link: Link, bus: Option<Arc<SpiBus>>, cs: ChipSelect) -> Stm32Spi {
let shared = Arc::new(Shared {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
link,
bus,
cs,
ticks: AtomicU64::new(0),
next_event: AtomicU64::new(NO_EVENT),
miso: AtomicBool::new(true),
irq_level: AtomicBool::new(false),
pins: Mutex::with_rank(LockRank::WIRE, Pins::default()),
irq: Mutex::with_rank(LockRank::WIRE, None),
sinks: Mutex::with_rank(LockRank::WIRE, Vec::new()),
lazy: Mutex::with_rank(LockRank::WIRE, None),
});
let pins = Arc::new(SlavePins::new(Arc::clone(&shared) as Arc<dyn SpiSlave>));
let port = Arc::new(RegisterBlock {
shared: Arc::clone(&shared),
pins: Arc::clone(&pins),
});
let region = Arc::new(Region::io(
"stm32-spi",
REGISTER_BYTES,
port as Arc<dyn MemOps>,
));
Stm32Spi {
shared,
pins,
region,
}
}
#[must_use]
pub fn link(&self) -> Link {
self.shared.link
}
#[must_use]
pub fn bus(&self) -> Option<&Arc<SpiBus>> {
self.shared.bus.as_ref()
}
#[must_use]
pub fn pins(&self) -> &Arc<SlavePins> {
&self.pins
}
#[must_use]
pub fn ticks(&self) -> u64 {
self.shared.ticks.load(Ordering::Relaxed)
}
#[must_use]
pub fn status(&self) -> u16 {
self.shared.state.lock().sr
}
#[must_use]
pub fn format(&self) -> Format {
self.shared.state.lock().format()
}
#[must_use]
pub fn irq_asserted(&self) -> bool {
self.shared.irq_level.load(Ordering::Relaxed)
}
pub fn advance_to(&self, target: u64) {
self.shared.advance_to(target);
}
}
#[derive(Debug, Clone, Copy)]
enum Emit {
Sck(Level),
Mosi(Level),
Nss(Level),
}
impl Shared {
fn publish(&self, state: &State) {
self.ticks.store(state.ticks, Ordering::Relaxed);
self.next_event
.store(Shared::next_event(state), Ordering::Relaxed);
}
fn next_event(state: &State) -> u64 {
if !state.busy {
if state.is_master() && state.is_enabled() && state.receive_only() {
return state.ticks.saturating_add(1);
}
return NO_EVENT;
}
let half = state.half_period();
let next = state
.started
.saturating_add((u64::from(state.edges) + 1) * half);
next.max(state.ticks.saturating_add(1))
}
fn sync(&self, attrs: MemAttrs) {
let handle = self.lazy.lock().clone();
let Some(handle) = handle else {
return;
};
let kind = if attrs.debug {
AccessKind::Debug
} else {
AccessKind::Guest
};
let _ = handle.sync(kind);
}
fn emit(&self, action: Emit) {
let port = {
let pins = self.pins.lock();
match action {
Emit::Sck(_) => pins.sck.clone(),
Emit::Mosi(_) => pins.mosi.clone(),
Emit::Nss(_) => pins.nss.clone(),
}
};
let level = match action {
Emit::Sck(l) | Emit::Mosi(l) | Emit::Nss(l) => l,
};
if let Some(port) = port {
port.set(level);
}
}
fn announce_all(&self) {
let (sck, nss) = {
let state = self.state.lock();
(
state.format().mode.idle_level(),
Level::from_bool(!state.nss_low),
)
};
self.emit(Emit::Sck(sck));
self.emit(Emit::Nss(nss));
self.publish_irq();
}
fn irq_state(state: &State) -> bool {
let sr = state.sr;
let cr2 = state.cr2;
(cr2 & CR2_TXEIE != 0 && sr & SR_TXE != 0)
|| (cr2 & CR2_RXNEIE != 0 && sr & SR_RXNE != 0)
|| (cr2 & CR2_ERRIE != 0 && sr & (SR_MODF | SR_OVR | SR_CRCERR | SR_FRE | SR_UDR) != 0)
}
fn publish_irq(&self) {
let level = Level::from_bool(Shared::irq_state(&self.state.lock()));
self.irq_level.store(level.is_high(), Ordering::Relaxed);
let port = self.irq.lock().clone();
if let Some(port) = port {
port.set(level);
}
}
fn drive_nss(&self, low: bool) {
self.emit(Emit::Nss(Level::from_bool(!low)));
if self.link == Link::Transactional
&& let Some(bus) = &self.bus
{
bus.select(low.then_some(self.cs));
}
}
fn begin(state: &mut State) -> bool {
if state.busy || !state.is_enabled() || !state.is_master() {
return false;
}
let crc_frame = state.cr1 & CR1_CRCEN != 0 && state.cr1 & CR1_CRCNEXT != 0;
if crc_frame {
state.shift = state.txcrc;
state.cr1 &= !CR1_CRCNEXT;
} else if state.receive_only() {
state.shift = 0xffff;
} else if state.tx_pending {
state.shift = state.tx;
state.tx_pending = false;
} else {
return false;
}
state.crc_frame = crc_frame;
state.sr |= SR_TXE;
state.sr |= SR_BSY;
state.busy = true;
state.started = state.ticks;
state.edges = 0;
state.shift_in = 0;
true
}
fn finish(state: &mut State, received: u16) {
let format = state.format();
let received = (received as u32 & format.mask()) as u16;
state.busy = false;
state.sr &= !SR_BSY;
if state.crc_frame {
if received != state.rxcrc {
state.sr |= SR_CRCERR;
}
state.crc_frame = false;
return;
}
if state.cr1 & CR1_CRCEN != 0 {
let poly = state.crcpr;
let bits = format.bits;
state.txcrc = crc_step(state.txcrc, state.shift, bits, poly);
state.rxcrc = crc_step(state.rxcrc, received, bits, poly);
}
if state.transmit_only() {
return;
}
if state.sr & SR_RXNE != 0 {
state.sr |= SR_OVR;
state.ovr_dr_read = false;
} else {
state.rx = received;
state.sr |= SR_RXNE;
}
}
fn tx_bit(state: &State, n: u32) -> Level {
let format = state.format();
let bit = match format.order {
BitOrder::MsbFirst => (state.shift >> (u32::from(format.bits) - 1 - n)) & 1,
BitOrder::LsbFirst => (state.shift >> n) & 1,
};
Level::from_bool(bit != 0)
}
fn capture(state: &mut State, n: u32, level: Level) {
if !level.as_bool() {
return;
}
let format = state.format();
match format.order {
BitOrder::MsbFirst => state.shift_in |= 1 << (u32::from(format.bits) - 1 - n),
BitOrder::LsbFirst => state.shift_in |= 1 << n,
}
}
fn advance_to(&self, target: u64) {
loop {
enum Step {
Done,
Edges(Vec<Emit>),
Word(u16),
Present,
}
let step = {
let mut state = self.state.lock();
if !state.busy {
if state.ticks < target && Shared::begin(&mut state) {
self.publish(&state);
Step::Present
} else {
state.ticks = state.ticks.max(target);
self.publish(&state);
Step::Done
}
} else {
let half = state.half_period();
match self.link {
Link::Transactional => {
let end = state.end_tick();
if end > target {
state.ticks = target;
self.publish(&state);
Step::Done
} else {
state.ticks = end;
Step::Word(state.shift)
}
}
Link::Wired => {
let edge_at = state
.started
.saturating_add((u64::from(state.edges) + 1) * half);
if edge_at > target {
state.ticks = target;
self.publish(&state);
Step::Done
} else {
state.ticks = edge_at;
let k = state.edges;
let format = state.format();
let idle = format.mode.idle_level();
let level = if k.is_multiple_of(2) {
idle.inverted()
} else {
idle
};
let bit = k / 2;
let mut out = Vec::new();
if format.mode.samples_on(level) {
let miso = Level::from_bool(self.miso.load(Ordering::Relaxed));
Shared::capture(&mut state, bit, miso);
} else {
let next = k.div_ceil(2).min(u32::from(format.bits) - 1);
out.push(Emit::Mosi(Shared::tx_bit(&state, next)));
}
out.push(Emit::Sck(level));
state.edges += 1;
if state.edges >= state.total_edges() {
let received = state.shift_in as u16;
Shared::finish(&mut state, received);
}
self.publish(&state);
Step::Edges(out)
}
}
}
}
};
match step {
Step::Done => {
self.publish_irq();
return;
}
Step::Edges(actions) => {
for action in actions {
self.emit(action);
}
self.publish_irq();
}
Step::Word(word) => {
let reply = self
.bus
.as_ref()
.map_or(0xffff, |bus| bus.transfer(u32::from(word)) as u16);
{
let mut state = self.state.lock();
Shared::finish(&mut state, reply);
self.publish(&state);
}
self.publish_irq();
}
Step::Present => {
self.present_first_bit();
self.publish_irq();
}
}
}
}
fn present_first_bit(&self) {
if self.link != Link::Wired {
return;
}
let level = {
let state = self.state.lock();
if !state.busy {
return;
}
Shared::tx_bit(&state, 0)
};
self.emit(Emit::Mosi(level));
}
fn check_mode_fault(state: &mut State) -> bool {
if !state.mode_fault_due() {
return false;
}
state.sr |= SR_MODF;
state.cr1 &= !(CR1_SPE | CR1_MSTR);
state.busy = false;
state.sr &= !SR_BSY;
state.nss_low = false;
state.modf_sr_seen = false;
true
}
}
struct RegisterBlock {
shared: Arc<Shared>,
pins: Arc<SlavePins>,
}
impl fmt::Debug for RegisterBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RegisterBlock").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, Default)]
struct After {
nss: Option<bool>,
reframe: bool,
started: bool,
announce: bool,
}
impl RegisterBlock {
fn read_register(&self, offset: u64, debug: bool) -> u16 {
let mut state = self.shared.state.lock();
match offset {
0x00 => state.cr1,
0x04 => state.cr2,
0x08 => {
let value = state.sr;
if !debug {
if state.sr & SR_MODF != 0 {
state.modf_sr_seen = true;
}
if state.sr & SR_OVR != 0 && state.ovr_dr_read {
state.sr &= !SR_OVR;
state.ovr_dr_read = false;
}
state.sr &= !SR_FRE;
}
value
}
0x0c => {
let value = state.rx;
if !debug {
state.sr &= !SR_RXNE;
if state.sr & SR_OVR != 0 {
state.ovr_dr_read = true;
}
}
if state.cr1 & CR1_DFF == 0 {
value & 0xff
} else {
value
}
}
0x10 => state.crcpr,
0x14 => state.rxcrc,
0x18 => state.txcrc,
0x1c => state.i2scfgr,
0x20 => state.i2spr,
_ => 0,
}
}
fn write_register(&self, offset: u64, value: u16) -> After {
let mut after = After::default();
let mut state = self.shared.state.lock();
match offset {
0x00 => {
let was_format = state.format();
let was_nss = state.nss_output_low();
let modf = state.sr & SR_MODF != 0;
let mut next = value;
if modf {
next &= !(CR1_SPE | CR1_MSTR);
if state.modf_sr_seen {
state.sr &= !SR_MODF;
state.modf_sr_seen = false;
}
}
let crc_rising = next & CR1_CRCEN != 0 && state.cr1 & CR1_CRCEN == 0;
state.cr1 = next;
if crc_rising {
state.rxcrc = 0;
state.txcrc = 0;
}
if state.cr1 & CR1_SPE == 0 {
state.busy = false;
state.sr &= !SR_BSY;
}
Shared::check_mode_fault(&mut state);
if state.format() != was_format {
after.reframe = true;
}
let nss = state.nss_output_low();
state.nss_low = nss;
if nss != was_nss {
after.nss = Some(nss);
}
after.started = Shared::begin(&mut state);
after.announce = true;
self.shared.publish(&state);
}
0x04 => {
let was_nss = state.nss_output_low();
state.cr2 = value & CR2_MASK;
Shared::check_mode_fault(&mut state);
let nss = state.nss_output_low();
state.nss_low = nss;
if nss != was_nss {
after.nss = Some(nss);
}
self.shared.publish(&state);
}
0x08 => {
if state.sr & SR_MODF != 0 {
state.modf_sr_seen = true;
}
if value & SR_CRCERR == 0 {
state.sr &= !SR_CRCERR;
}
}
0x0c => {
let format = state.format();
state.tx = format.truncate(u32::from(value)) as u16;
state.tx_pending = true;
state.sr &= !SR_TXE;
after.started = Shared::begin(&mut state);
self.shared.publish(&state);
}
0x10 => state.crcpr = value,
0x14 | 0x18 => {}
0x1c => state.i2scfgr = value,
0x20 => state.i2spr = value,
_ => {}
}
after
}
fn settle(&self, after: After) {
if after.reframe {
self.pins.reset();
}
if let Some(low) = after.nss {
self.shared.drive_nss(low);
}
if after.started {
self.shared.present_first_bit();
}
if after.announce {
self.shared.announce_all();
}
self.shared.publish_irq();
}
}
impl MemOps for RegisterBlock {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
if !matches!(dst.len(), 1 | 2 | 4) {
return Err(BusError::BadAccess);
}
let register = offset & !3;
let within = offset - register;
if within + dst.len() as u64 > 4 || (dst.len() == 4 && within != 0) {
return Err(BusError::BadAccess);
}
if register > LAST_REGISTER {
dst.fill(0);
return Ok(());
}
self.shared.sync(attrs);
let value = u32::from(self.read_register(register, attrs.debug));
let bytes = value.to_le_bytes();
for (i, byte) in dst.iter_mut().enumerate() {
*byte = bytes[(within as usize + i).min(3)];
}
if !attrs.debug {
self.shared.publish_irq();
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if !matches!(src.len(), 1 | 2 | 4) {
return Err(BusError::BadAccess);
}
let register = offset & !3;
let within = offset - register;
if within + src.len() as u64 > 4 || (src.len() == 4 && within != 0) {
return Err(BusError::BadAccess);
}
if attrs.debug {
return Err(BusError::BadAccess);
}
if register > LAST_REGISTER {
return Ok(());
}
self.shared.sync(attrs);
let value = if src.len() == 1 && within == 1 {
let old = self.read_register(register, true);
(old & 0x00ff) | (u16::from(src[0]) << 8)
} else if src.len() == 1 {
let old = self.read_register(register, true);
(old & 0xff00) | u16::from(src[0])
} else {
u16::from(src[0]) | (u16::from(src[1]) << 8)
};
let after = self.write_register(register, value);
self.settle(after);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::ANY
.with_widths(Width::U8, Width::U32)
.with_endian(Endian::Little)
}
}
impl SpiSlave for Shared {
fn format(&self) -> Format {
self.state.lock().format()
}
fn select(&self, selected: bool) {
let mut state = self.state.lock();
state.nss_in = Level::from_bool(!selected);
Shared::check_mode_fault(&mut state);
}
fn transfer(&self, mosi: u32) -> u32 {
let mut state = self.state.lock();
if state.is_master() || !state.is_enabled() {
return u32::MAX;
}
let out = state.shift;
state.shift = state.tx;
state.tx_pending = false;
state.sr |= SR_TXE;
Shared::finish(&mut state, mosi as u16);
u32::from(out)
}
fn peek(&self) -> u32 {
let state = self.state.lock();
if state.is_master() || !state.is_enabled() {
return u32::MAX;
}
u32::from(state.tx)
}
}
struct InputSink {
shared: Arc<Shared>,
pins: Arc<SlavePins>,
line: u32,
}
impl fmt::Debug for InputSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InputSink")
.field("line", &self.line)
.finish()
}
}
impl WireSink for InputSink {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
match self.line {
pin::MISO_LINE => {
self.shared.miso.store(level.as_bool(), Ordering::Relaxed);
}
pin::NSS_IN_LINE => {
self.pins.drive(slave_pin::CS, level);
let faulted = {
let mut state = self.shared.state.lock();
state.nss_in = level;
Shared::check_mode_fault(&mut state)
};
if faulted {
self.shared.drive_nss(false);
self.shared.publish_irq();
}
}
_ => {}
}
}
}
impl Device for Stm32Spi {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.shared.state.lock();
let ticks = state.ticks;
let nss_in = state.nss_in;
*state = State {
ticks,
nss_in,
..State::default()
};
self.shared.publish(&state);
}
self.shared.miso.store(true, Ordering::Relaxed);
self.pins.reset();
self.shared.drive_nss(false);
self.shared.announce_all();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = *self.shared.state.lock();
w.write_u64(state.ticks)?;
w.write_u16(state.cr1)?;
w.write_u16(state.cr2)?;
w.write_u16(state.sr)?;
w.write_u16(state.tx)?;
w.write_u16(state.rx)?;
w.write_u16(state.crcpr)?;
w.write_u16(state.rxcrc)?;
w.write_u16(state.txcrc)?;
w.write_u16(state.i2scfgr)?;
w.write_u16(state.i2spr)?;
w.write_bool(state.tx_pending)?;
w.write_u16(state.shift)?;
w.write_bool(state.busy)?;
w.write_bool(state.crc_frame)?;
w.write_u64(state.started)?;
w.write_u32(state.edges)?;
w.write_u32(state.shift_in)?;
w.write_bool(state.nss_low)?;
w.write_bool(state.ovr_dr_read)?;
w.write_bool(state.modf_sr_seen)?;
let (rx, tx, count, selected, sck, mosi, loaded) = self.pins.snapshot();
w.write_u32(rx)?;
w.write_u32(tx)?;
w.write_u8(count)?;
w.write_bool(selected)?;
w.write_bool(sck)?;
w.write_bool(mosi)?;
w.write_bool(loaded)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State {
ticks: r.read_u64()?,
cr1: r.read_u16()?,
cr2: r.read_u16()?,
sr: r.read_u16()?,
tx: r.read_u16()?,
rx: r.read_u16()?,
crcpr: r.read_u16()?,
rxcrc: r.read_u16()?,
txcrc: r.read_u16()?,
i2scfgr: r.read_u16()?,
i2spr: r.read_u16()?,
tx_pending: r.read_bool()?,
shift: r.read_u16()?,
busy: r.read_bool()?,
crc_frame: r.read_bool()?,
started: r.read_u64()?,
edges: r.read_u32()?,
shift_in: r.read_u32()?,
nss_low: r.read_bool()?,
nss_in: Level::High,
ovr_dr_read: r.read_bool()?,
modf_sr_seen: r.read_bool()?,
};
let pins = (
r.read_u32()?,
r.read_u32()?,
r.read_u8()?,
r.read_bool()?,
r.read_bool()?,
r.read_bool()?,
r.read_bool()?,
);
{
let mut slot = self.shared.state.lock();
state.nss_in = slot.nss_in;
*slot = state;
self.shared.publish(&slot);
}
self.pins.restore(pins);
self.shared.drive_nss(state.nss_low);
self.shared.announce_all();
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn sink(&self, port: &str, _sources: &[WireId]) -> Option<SinkPin> {
let slave_line = match port {
pin::SCK_IN => Some(slave_pin::SCK),
pin::MOSI_IN => Some(slave_pin::MOSI),
_ => None,
};
if let Some(line) = slave_line {
return Some(SinkPin {
sink: self.pins.sink(line),
line,
});
}
let line = match port {
pin::MISO => pin::MISO_LINE,
pin::NSS_IN => pin::NSS_IN_LINE,
_ => return None,
};
let sink = Arc::new(InputSink {
shared: Arc::clone(&self.shared),
pins: Arc::clone(&self.pins),
line,
});
self.shared.sinks.lock().push(Arc::clone(&sink));
Some(SinkPin {
sink: sink as Arc<dyn WireSink>,
line,
})
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
match port {
pin::MISO_OUT => {
self.pins.connect_miso(source);
return Ok(());
}
"irq" => {
*self.shared.irq.lock() = Some(source);
self.shared.publish_irq();
return Ok(());
}
_ => {}
}
let mut pins = self.shared.pins.lock();
match port {
pin::SCK => pins.sck = Some(source),
pin::MOSI => pins.mosi = Some(source),
pin::NSS => pins.nss = Some(source),
_ => {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"an STM32 SPI drives `{}`, `{}`, `{}`, `{}` and `irq`",
pin::SCK,
pin::MOSI,
pin::NSS,
pin::MISO_OUT
),
});
}
}
drop(pins);
self.shared.announce_all();
Ok(())
}
fn announce(&self, port: &str) {
if port == pin::MISO_OUT {
self.pins.publish_miso();
} else {
self.shared.announce_all();
}
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.shared.ticks.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
Stm32Spi::advance_to(self, tick);
}
fn next_event_tick(&self) -> Option<u64> {
match self.shared.next_event.load(Ordering::Relaxed) {
NO_EVENT => None,
tick => Some(tick),
}
}
fn attach_lazy(&self, handle: LazyHandle) {
*self.shared.lazy.lock() = Some(handle);
}
}
impl Instance for Stm32Spi {}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "STM32F4 SPI (RM0090 §28): CR1/CR2/SR/DR/CRCPR, master and slave, the four \
CPOL/CPHA modes, 8- and 16-bit frames, SSM/SSI/SSOE and the mode fault",
properties: &[
PropertySpec {
name: "link",
kind: ValueKind::Str,
required: true,
summary: "how words reach the slaves: `transactional` or `wired`",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the named SPI bus this peripheral masters, for `transactional`",
},
PropertySpec {
name: "cs",
kind: ValueKind::Uint,
required: false,
summary: "which chip select on that bus the NSS output stands for (default 0)",
},
],
construct: |props| Ok(Box::new(Stm32Spi::new(props)?)),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Stm32Spi::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
ClassSchema::new(CLASS_NAME)
.prop(
PropSchema::new("link", ValueKind::Str)
.required()
.values(Link::NAMES),
)
.prop(PropSchema::new("bus", ValueKind::Str))
.prop(PropSchema::new("cs", ValueKind::Uint).range(0, MAX_CHIP_SELECTS as u64 - 1))
.port(pin::SCK, PortDir::Out)
.port(pin::MOSI, PortDir::Out)
.port(pin::NSS, PortDir::Out)
.port(pin::MISO, PortDir::In)
.port(pin::SCK_IN, PortDir::In)
.port(pin::MOSI_IN, PortDir::In)
.port(pin::NSS_IN, PortDir::In)
.port(pin::MISO_OUT, PortDir::Out)
.port("irq", PortDir::Out)
.region("")
.region("regs")
}
#[cfg(test)]
mod tests;