use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt;
use crate::bus::spi::{ChipSelect, Link, MAX_CHIP_SELECTS, SpiBus, buses};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicBool, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::machine::realize::Instance;
use crate::machine::validate::{ClassSchema, PropSchema};
pub const CLASS_NAME: &str = "stm32.octospi";
const STATE_VERSION: u32 = 1;
pub const REGISTER_BYTES: u64 = 0x400;
pub const WINDOW_BYTES: u64 = 256 * 1024 * 1024;
const CR_EN: u32 = 1 << 0;
const CR_ABORT: u32 = 1 << 1;
const CR_FTHRES_SHIFT: u32 = 8;
const CR_FTHRES_MASK: u32 = 0x1f;
const CR_TEIE: u32 = 1 << 16;
const CR_TCIE: u32 = 1 << 17;
const CR_FTIE: u32 = 1 << 18;
const CR_SMIE: u32 = 1 << 19;
const CR_TOIE: u32 = 1 << 20;
const CR_APMS: u32 = 1 << 22;
const CR_PMM: u32 = 1 << 23;
const CR_FMODE_SHIFT: u32 = 28;
const CR_FMODE_MASK: u32 = 0x3;
const FMODE_WRITE: u32 = 0;
const FMODE_READ: u32 = 1;
const FMODE_POLL: u32 = 2;
const FMODE_MAPPED: u32 = 3;
const DCR1_DEVSIZE_SHIFT: u32 = 16;
const DCR1_DEVSIZE_MASK: u32 = 0x1f;
const SR_TEF: u32 = 1 << 0;
const SR_TCF: u32 = 1 << 1;
const SR_FTF: u32 = 1 << 2;
const SR_SMF: u32 = 1 << 3;
const SR_TOF: u32 = 1 << 4;
const SR_BUSY: u32 = 1 << 5;
const SR_FLEVEL_SHIFT: u32 = 8;
const SR_FLEVEL_MASK: u32 = 0x3f;
const FCR_MASK: u32 = SR_TEF | SR_TCF | SR_SMF | SR_TOF;
const FIFO_BYTES: u64 = 32;
const CCR_IMODE_SHIFT: u32 = 0;
const CCR_ISIZE_SHIFT: u32 = 4;
const CCR_ADMODE_SHIFT: u32 = 8;
const CCR_ADSIZE_SHIFT: u32 = 12;
const CCR_ABMODE_SHIFT: u32 = 16;
const CCR_ABSIZE_SHIFT: u32 = 20;
const CCR_DMODE_SHIFT: u32 = 24;
const MODE_MASK: u32 = 0x7;
const SIZE_MASK: u32 = 0x3;
const TCR_DCYC_MASK: u32 = 0x1f;
const IDLE_BYTE: u8 = 0xff;
fn config(message: String) -> Error {
Error::Config {
at: CLASS_NAME.to_string(),
message,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Command {
instruction: Option<(u32, u8)>,
address_bytes: u8,
alternate: Option<(u32, u8)>,
dummy_bytes: u8,
has_data: bool,
}
impl Command {
fn decode(ccr: u32, tcr: u32, ir: u32, abr: u32) -> Command {
let field = |shift: u32, mask: u32| (ccr >> shift) & mask;
let imode = field(CCR_IMODE_SHIFT, MODE_MASK);
let admode = field(CCR_ADMODE_SHIFT, MODE_MASK);
let abmode = field(CCR_ABMODE_SHIFT, MODE_MASK);
let dmode = field(CCR_DMODE_SHIFT, MODE_MASK);
let isize = (field(CCR_ISIZE_SHIFT, SIZE_MASK) + 1) as u8;
let asize = (field(CCR_ADSIZE_SHIFT, SIZE_MASK) + 1) as u8;
let absize = (field(CCR_ABSIZE_SHIFT, SIZE_MASK) + 1) as u8;
Command {
instruction: (imode != 0).then_some((ir, isize)),
address_bytes: if admode != 0 { asize } else { 0 },
alternate: (abmode != 0).then_some((abr, absize)),
dummy_bytes: ((tcr & TCR_DCYC_MASK).div_ceil(8)) as u8,
has_data: dmode != 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct State {
cr: u32,
dcr1: u32,
dcr2: u32,
dcr3: u32,
dcr4: u32,
sr: u32,
dlr: u32,
ar: u32,
psmkr: u32,
psmar: u32,
pir: u32,
ccr: u32,
tcr: u32,
ir: u32,
abr: u32,
lptr: u32,
wpccr: u32,
wptcr: u32,
wpir: u32,
wpabr: u32,
wccr: u32,
wtcr: u32,
wir: u32,
wabr: u32,
hlcr: u32,
open: bool,
writing: bool,
remaining: u64,
}
impl State {
const fn fmode(&self) -> u32 {
(self.cr >> CR_FMODE_SHIFT) & CR_FMODE_MASK
}
const fn device_bytes(&self) -> u64 {
1u64 << (((self.dcr1 >> DCR1_DEVSIZE_SHIFT) & DCR1_DEVSIZE_MASK) + 1)
}
const fn fifo_threshold(&self) -> u64 {
(((self.cr >> CR_FTHRES_SHIFT) & CR_FTHRES_MASK) + 1) as u64
}
fn status(&self) -> u32 {
let mut sr = self.sr & !(SR_BUSY | SR_FTF | (SR_FLEVEL_MASK << SR_FLEVEL_SHIFT));
if self.open {
sr |= SR_BUSY;
}
let level = if self.open && !self.writing {
self.remaining.min(FIFO_BYTES)
} else {
0
};
sr |= (level as u32 & SR_FLEVEL_MASK) << SR_FLEVEL_SHIFT;
if self.open && (self.writing || level >= self.fifo_threshold()) {
sr |= SR_FTF;
}
sr
}
fn read_command(&self) -> Command {
Command::decode(self.ccr, self.tcr, self.ir, self.abr)
}
fn write_command(&self) -> Command {
Command::decode(self.wccr, self.wtcr, self.wir, self.wabr)
}
}
#[derive(Debug)]
pub struct Octospi {
shared: Arc<Shared>,
regs: RegionRef,
window: RegionRef,
}
struct Shared {
state: Mutex<State>,
bus: Option<Arc<SpiBus>>,
cs: ChipSelect,
window: u64,
irq: Mutex<Option<WireSource>>,
irq_level: AtomicBool,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Shared");
s.field("cs", &self.cs).field("window", &self.window);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
impl Shared {
fn irq_state(state: &State) -> bool {
let sr = state.status();
let cr = state.cr;
(cr & CR_TEIE != 0 && sr & SR_TEF != 0)
|| (cr & CR_TCIE != 0 && sr & SR_TCF != 0)
|| (cr & CR_FTIE != 0 && sr & SR_FTF != 0)
|| (cr & CR_SMIE != 0 && sr & SR_SMF != 0)
|| (cr & CR_TOIE != 0 && sr & SR_TOF != 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 byte(&self, out: u8) -> u8 {
self.bus
.as_ref()
.map_or(IDLE_BYTE, |bus| bus.transfer(u32::from(out)) as u8)
}
fn open(&self, cmd: &Command, address: u32) {
if let Some(bus) = &self.bus {
bus.select(Some(self.cs));
}
if let Some((instruction, bytes)) = cmd.instruction {
for i in (0..bytes).rev() {
self.byte((instruction >> (8 * u32::from(i))) as u8);
}
}
for i in (0..cmd.address_bytes).rev() {
self.byte((address >> (8 * u32::from(i))) as u8);
}
if let Some((alternate, bytes)) = cmd.alternate {
for i in (0..bytes).rev() {
self.byte((alternate >> (8 * u32::from(i))) as u8);
}
}
for _ in 0..cmd.dummy_bytes {
self.byte(IDLE_BYTE);
}
}
fn close(&self) {
if let Some(bus) = &self.bus {
bus.select(None);
}
}
fn frame(&self, cmd: &Command, address: u32, data: &mut [u8], writing: bool) {
self.open(cmd, address);
if cmd.has_data {
for byte in data.iter_mut() {
let got = self.byte(if writing { *byte } else { IDLE_BYTE });
if !writing {
*byte = got;
}
}
}
self.close();
}
}
impl Octospi {
pub fn new(props: &Props) -> Result<Octospi> {
let mut r = props.reader();
let link_name = r.require_str("link")?.to_string();
let bus_name = r.require_str("bus")?.to_string();
let cs = r.or_range("cs", 0u64, 0..=(MAX_CHIP_SELECTS as u64 - 1))?;
let window = r.or_size("window", WINDOW_BYTES)?;
r.finish()?;
let link = Link::from_name(&link_name).ok_or_else(|| {
config(alloc::format!(
"`link` is `{link_name}`; it must be one of {:?} — see docs/buses/low-speed.md",
Link::NAMES
))
})?;
if link != Link::Transactional {
return Err(config(String::from(
"OCTOSPI can only be modelled `transactional`, and the property is still \
required so the machine file says so out loud: a memory-mapped access happens \
inside a guest load, and a `wired` link would have to pace edges through the \
scheduler while the CPU is mid-access — which the core cannot express. Put a \
`spi.controller` or an `stm32.spi` on the bus if you want the edges.",
)));
}
if !window.is_power_of_two() || window > WINDOW_BYTES {
return Err(config(alloc::format!(
"`window` is {window:#x}; the aperture is a power of two of at most \
{WINDOW_BYTES:#x} (AN5050 §3.3.3's 256 MiB per instance)"
)));
}
let bus = buses::attach(props, &bus_name)?;
Ok(Octospi::with_bus(Some(bus), ChipSelect(cs as u8), window))
}
#[must_use]
pub fn with_bus(bus: Option<Arc<SpiBus>>, cs: ChipSelect, window: u64) -> Octospi {
let shared = Arc::new(Shared {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
bus,
cs,
window,
irq: Mutex::with_rank(LockRank::WIRE, None),
irq_level: AtomicBool::new(false),
});
let regs: RegionRef = Arc::new(Region::io(
"octospi",
REGISTER_BYTES,
Arc::new(RegisterBlock {
shared: Arc::clone(&shared),
}) as Arc<dyn MemOps>,
));
let window_region: RegionRef = Arc::new(Region::io(
"octospi-mem",
window,
Arc::new(MappedWindow {
shared: Arc::clone(&shared),
}) as Arc<dyn MemOps>,
));
Octospi {
shared,
regs,
window: window_region,
}
}
#[must_use]
pub fn bus(&self) -> Option<&Arc<SpiBus>> {
self.shared.bus.as_ref()
}
#[must_use]
pub fn window_bytes(&self) -> u64 {
self.shared.window
}
#[must_use]
pub fn status(&self) -> u32 {
self.shared.state.lock().status()
}
#[must_use]
pub fn busy(&self) -> bool {
self.shared.state.lock().open
}
#[must_use]
pub fn irq_asserted(&self) -> bool {
self.shared.irq_level.load(Ordering::Relaxed)
}
}
struct RegisterBlock {
shared: Arc<Shared>,
}
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, PartialEq, Eq)]
enum After {
Nothing,
Start {
cmd: Command,
address: u32,
writing: bool,
},
Close,
Poll {
cmd: Command,
address: u32,
},
}
impl RegisterBlock {
fn read_register(&self, offset: u64, debug: bool) -> u32 {
let state = self.shared.state.lock();
match offset {
0x000 => state.cr,
0x008 => state.dcr1,
0x00c => state.dcr2,
0x010 => state.dcr3,
0x014 => state.dcr4,
0x020 => state.status(),
0x040 => state.dlr,
0x048 => state.ar,
0x050 => 0,
0x080 => state.psmkr,
0x088 => state.psmar,
0x090 => state.pir,
0x100 => state.ccr,
0x108 => state.tcr,
0x110 => state.ir,
0x120 => state.abr,
0x130 => state.lptr,
0x140 => state.wpccr,
0x148 => state.wptcr,
0x150 => state.wpir,
0x160 => state.wpabr,
0x180 => state.wccr,
0x188 => state.wtcr,
0x190 => state.wir,
0x1a0 => state.wabr,
0x200 => state.hlcr,
_ => {
let _ = debug;
0
}
}
}
fn write_register(&self, offset: u64, value: u32) -> After {
let mut state = self.shared.state.lock();
match offset {
0x000 => {
let was_enabled = state.cr & CR_EN != 0;
state.cr = value & !CR_ABORT;
if value & CR_ABORT != 0 || (was_enabled && state.cr & CR_EN == 0) {
if state.open {
state.open = false;
state.remaining = 0;
state.sr |= SR_TCF;
return After::Close;
}
}
After::Nothing
}
0x008 => {
state.dcr1 = value;
After::Nothing
}
0x00c => {
state.dcr2 = value;
After::Nothing
}
0x010 => {
state.dcr3 = value;
After::Nothing
}
0x014 => {
state.dcr4 = value;
After::Nothing
}
0x020 => After::Nothing,
0x024 => {
state.sr &= !(value & FCR_MASK);
After::Nothing
}
0x040 => {
state.dlr = value;
After::Nothing
}
0x048 => {
state.ar = value;
self.trigger(&mut state, value)
}
0x080 => {
state.psmkr = value;
After::Nothing
}
0x088 => {
state.psmar = value;
After::Nothing
}
0x090 => {
state.pir = value;
After::Nothing
}
0x100 => {
state.ccr = value;
After::Nothing
}
0x108 => {
state.tcr = value;
After::Nothing
}
0x110 => {
state.ir = value;
if state.read_command().address_bytes == 0 {
self.trigger(&mut state, 0)
} else {
After::Nothing
}
}
0x120 => {
state.abr = value;
After::Nothing
}
0x130 => {
state.lptr = value;
After::Nothing
}
0x140 => {
state.wpccr = value;
After::Nothing
}
0x148 => {
state.wptcr = value;
After::Nothing
}
0x150 => {
state.wpir = value;
After::Nothing
}
0x160 => {
state.wpabr = value;
After::Nothing
}
0x180 => {
state.wccr = value;
After::Nothing
}
0x188 => {
state.wtcr = value;
After::Nothing
}
0x190 => {
state.wir = value;
After::Nothing
}
0x1a0 => {
state.wabr = value;
After::Nothing
}
0x200 => {
state.hlcr = value;
After::Nothing
}
_ => After::Nothing,
}
}
fn trigger(&self, state: &mut State, address: u32) -> After {
if state.cr & CR_EN == 0 || state.open {
return After::Nothing;
}
let cmd = state.read_command();
match state.fmode() {
FMODE_POLL => After::Poll { cmd, address },
mode @ (FMODE_READ | FMODE_WRITE) => {
let writing = mode == FMODE_WRITE;
if !cmd.has_data {
state.sr |= SR_TCF;
return After::Start {
cmd,
address,
writing,
};
}
state.remaining = u64::from(state.dlr) + 1;
state.open = true;
state.writing = writing;
After::Start {
cmd,
address,
writing,
}
}
_ => After::Nothing,
}
}
}
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 {
return Err(BusError::BadAccess);
}
if register == 0x050 {
let result = self.read_data(dst, attrs);
if !attrs.debug {
self.shared.publish_irq();
}
return result;
}
let bytes = self.read_register(register, attrs.debug).to_le_bytes();
for (i, byte) in dst.iter_mut().enumerate() {
*byte = bytes[within as usize + i];
}
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 {
return Err(BusError::BadAccess);
}
if attrs.debug {
return Err(BusError::BadAccess);
}
if register == 0x050 {
return self.write_data(src);
}
let old = self.read_register(register, true);
let mut bytes = old.to_le_bytes();
for (i, byte) in src.iter().enumerate() {
bytes[within as usize + i] = *byte;
}
let after = self.write_register(register, u32::from_le_bytes(bytes));
self.settle(after);
self.shared.publish_irq();
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::ANY
.with_widths(Width::U8, Width::U32)
.with_endian(Endian::Little)
}
}
impl RegisterBlock {
fn settle(&self, after: After) {
match after {
After::Nothing => {}
After::Start {
cmd,
address,
writing,
} => {
self.shared.open(&cmd, address);
let close = {
let state = self.shared.state.lock();
!state.open
};
if close {
self.shared.close();
}
let _ = writing;
}
After::Close => self.shared.close(),
After::Poll { cmd, address } => self.poll(&cmd, address),
}
}
fn poll(&self, cmd: &Command, address: u32) {
let (bytes, mask, expect, any, stop) = {
let state = self.shared.state.lock();
(
(u64::from(state.dlr) + 1).min(4) as usize,
state.psmkr,
state.psmar,
state.cr & CR_PMM != 0,
state.cr & CR_APMS != 0,
)
};
let mut buf = alloc::vec![0u8; bytes];
self.shared.frame(cmd, address, &mut buf, false);
let mut got = 0u32;
for (i, byte) in buf.iter().enumerate() {
got |= u32::from(*byte) << (8 * i);
}
let matched = if any {
(got ^ expect) & mask != mask
} else {
got & mask == expect & mask
};
let mut state = self.shared.state.lock();
if matched {
state.sr |= SR_SMF;
if stop {
state.sr |= SR_TCF;
}
}
}
fn read_data(&self, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
dst.fill(0);
return Ok(());
}
let mut close = false;
for byte in dst.iter_mut() {
let take = {
let mut state = self.shared.state.lock();
if !state.open || state.writing || state.remaining == 0 {
false
} else {
state.remaining -= 1;
if state.remaining == 0 {
state.open = false;
state.sr |= SR_TCF;
close = true;
}
true
}
};
*byte = if take { self.shared.byte(IDLE_BYTE) } else { 0 };
}
if close {
self.shared.close();
}
Ok(())
}
fn write_data(&self, src: &[u8]) -> MemResult {
let mut close = false;
for byte in src {
let push = {
let mut state = self.shared.state.lock();
if !state.open || !state.writing || state.remaining == 0 {
false
} else {
state.remaining -= 1;
if state.remaining == 0 {
state.open = false;
state.sr |= SR_TCF;
close = true;
}
true
}
};
if push {
self.shared.byte(*byte);
}
}
if close {
self.shared.close();
}
Ok(())
}
}
struct MappedWindow {
shared: Arc<Shared>,
}
impl fmt::Debug for MappedWindow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MappedWindow").finish_non_exhaustive()
}
}
impl MappedWindow {
fn armed(&self, writing: bool) -> Option<Command> {
let state = self.shared.state.lock();
if state.cr & CR_EN == 0 || state.fmode() != FMODE_MAPPED || state.open {
return None;
}
Some(if writing {
state.write_command()
} else {
state.read_command()
})
}
fn in_device(&self, offset: u64, len: u64) -> bool {
let state = self.shared.state.lock();
offset.saturating_add(len) <= state.device_bytes()
}
fn fault(&self) -> BusError {
self.shared.state.lock().sr |= SR_TEF;
BusError::BadAccess
}
}
impl MemOps for MappedWindow {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
let Some(cmd) = self.armed(false) else {
return Err(BusError::Unassigned);
};
if !self.in_device(offset, dst.len() as u64) {
return Err(self.fault());
}
let Ok(address) = u32::try_from(offset) else {
return Err(self.fault());
};
self.shared.frame(&cmd, address, dst, false);
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
let Some(cmd) = self.armed(true) else {
return Err(BusError::Unassigned);
};
if !self.in_device(offset, src.len() as u64) {
return Err(self.fault());
}
let Ok(address) = u32::try_from(offset) else {
return Err(self.fault());
};
let mut buf = src.to_vec();
self.shared.frame(&cmd, address, &mut buf, true);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::ANY
.with_widths(Width::U8, Width::U64)
.with_endian(Endian::Little)
}
}
impl Device for Octospi {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != "irq" {
return Err(Error::Config {
at: String::from(port),
message: String::from("an OCTOSPI drives only `irq`"),
});
}
*self.shared.irq.lock() = Some(source);
self.shared.publish_irq();
Ok(())
}
fn announce(&self, _port: &str) {
self.shared.publish_irq();
}
fn reset(&self, _kind: ResetKind) {
let was_open = {
let mut state = self.shared.state.lock();
let open = state.open;
*state = State::default();
open
};
if was_open {
self.shared.close();
}
self.shared.publish_irq();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = *self.shared.state.lock();
for value in [
state.cr,
state.dcr1,
state.dcr2,
state.dcr3,
state.dcr4,
state.sr,
state.dlr,
state.ar,
state.psmkr,
state.psmar,
state.pir,
state.ccr,
state.tcr,
state.ir,
state.abr,
state.lptr,
state.wpccr,
state.wptcr,
state.wpir,
state.wpabr,
state.wccr,
state.wtcr,
state.wir,
state.wabr,
state.hlcr,
] {
w.write_u32(value)?;
}
w.write_bool(state.open)?;
w.write_bool(state.writing)?;
w.write_u64(state.remaining)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut values = [0u32; 25];
for value in &mut values {
*value = r.read_u32()?;
}
let open = r.read_bool()?;
let writing = r.read_bool()?;
let remaining = r.read_u64()?;
let state = State {
cr: values[0],
dcr1: values[1],
dcr2: values[2],
dcr3: values[3],
dcr4: values[4],
sr: values[5],
dlr: values[6],
ar: values[7],
psmkr: values[8],
psmar: values[9],
pir: values[10],
ccr: values[11],
tcr: values[12],
ir: values[13],
abr: values[14],
lptr: values[15],
wpccr: values[16],
wptcr: values[17],
wpir: values[18],
wpabr: values[19],
wccr: values[20],
wtcr: values[21],
wir: values[22],
wabr: values[23],
hlcr: values[24],
open,
writing,
remaining: if open {
remaining.min(u64::from(values[6]) + 1)
} else {
0
},
};
let was_open = {
let mut slot = self.shared.state.lock();
let was = slot.open;
*slot = state;
was
};
if was_open && !state.open {
self.shared.close();
}
self.shared.publish_irq();
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
match name {
"" | "regs" => Some(Arc::clone(&self.regs)),
"mem" | "flash" => Some(Arc::clone(&self.window)),
_ => None,
}
}
}
impl Instance for Octospi {}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "STM32 OCTOSPI: indirect read and write, automatic status polling, and a \
memory-mapped window a CPU can execute out of",
properties: &[
PropertySpec {
name: "link",
kind: ValueKind::Str,
required: true,
summary: "`transactional`; required so the machine file states the choice",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: true,
summary: "the named SPI bus the external memory sits on",
},
PropertySpec {
name: "cs",
kind: ValueKind::Uint,
required: false,
summary: "which chip select the memory answers on (default 0)",
},
PropertySpec {
name: "window",
kind: ValueKind::Size,
required: false,
summary: "how much address space the memory-mapped aperture decodes (default 256M)",
},
],
construct: |props| Ok(Box::new(Octospi::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(Octospi::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).required())
.prop(PropSchema::new("cs", ValueKind::Uint).range(0, MAX_CHIP_SELECTS as u64 - 1))
.prop(PropSchema::new("window", ValueKind::Size))
.port("irq", crate::machine::validate::PortDir::Out)
.region("")
.region("regs")
.region("mem")
.region("flash")
}
#[cfg(test)]
mod tests;