use std::collections::VecDeque;
use crate::chip::sd::{Answer, Card, Outcome};
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::stm32f427::generated::sdio::{Register, clkcr, cmd, dctrl, icr, power, sta};
use crate::time::{EventId, VirtualNanos};
const NANOS_PER_SECOND: u128 = 1_000_000_000;
const FIFO_WORDS: usize = 32;
const HALF: usize = 8;
const BYTES_PER_WORD: u64 = 4;
const GLOBAL: usize = 0;
pub const TRANSFER_REQUEST: usize = 0;
pub const LAST_DATA_REQUEST: usize = 1;
const POWER_ON: u64 = 0b11;
const WAIT_NO_RESPONSE: u64 = 0b00;
const WAIT_SHORT: u64 = 0b01;
const WAIT_NO_RESPONSE_TOO: u64 = 0b10;
const WAIT_LONG: u64 = 0b11;
const FOUR_LINES: u64 = 0b01;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
ToCard,
FromCard,
}
#[derive(Debug)]
struct Transfer {
direction: Direction,
block: u64,
started: VirtualNanos,
per_word: u128,
moved: u64,
remaining: u64,
buffer: Vec<u8>,
taken: usize,
timeout: u128,
since: VirtualNanos,
stalled: bool,
}
#[derive(Debug)]
pub struct Sdio {
clock_hz: u64,
card: Option<Box<dyn Card>>,
fifo: VecDeque<u32>,
transfer: Option<Transfer>,
request: Level,
last_data: Level,
received_last: bool,
interrupt: Level,
wakeup: Option<(VirtualNanos, EventId)>,
}
impl Sdio {
#[must_use]
pub fn with_clock(clock_hz: u64) -> Self {
Self {
clock_hz,
card: None,
fifo: VecDeque::with_capacity(FIFO_WORDS),
transfer: None,
request: Level::Low,
last_data: Level::Low,
received_last: false,
interrupt: Level::Low,
wakeup: None,
}
}
pub fn attach(&mut self, card: impl Card + 'static) {
self.card = Some(Box::new(card));
}
#[must_use]
pub fn card(&self) -> Option<&dyn Card> {
self.card.as_deref()
}
pub fn inherit(&mut self, from: &mut Self) {
self.card = from.card.take();
}
#[must_use]
pub fn attached(&self) -> bool {
self.card.is_some()
}
fn card_clock(&self, registers: &RegisterFile) -> u64 {
let control = registers.get(Register::Clkcr);
if clkcr::BYPASS.is_set(control) {
return self.clock_hz.max(1);
}
let divider = clkcr::CLKDIV.get(control) + 2;
(self.clock_hz / divider).max(1)
}
fn lines(registers: &RegisterFile) -> u64 {
if clkcr::WIDBUS.get(registers.get(Register::Clkcr)) == FOUR_LINES {
4
} else {
1
}
}
fn powered(registers: &RegisterFile) -> bool {
power::PWRCTRL.get(registers.get(Register::Power)) == POWER_ON
}
fn flag(registers: &mut RegisterFile, field: &crate::soc::FieldSpec, set: bool) {
let status = registers.get(Register::Sta);
registers.set(Register::Sta, field.set(status, u64::from(set)));
}
fn standing(registers: &RegisterFile, field: &crate::soc::FieldSpec) -> bool {
field.is_set(registers.get(Register::Sta))
}
}
impl Sdio {
fn send(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !cmd::CPSMEN.is_set(written) || !Self::powered(registers) {
return;
}
let index = u8::try_from(cmd::CMDINDEX.get(written)).unwrap_or(0);
let argument = u32::try_from(registers.get(Register::Arg) & u64::from(u32::MAX))
.expect("masked to a word");
let wants = cmd::WAITRESP.get(written);
let answer = self
.card
.as_deref_mut()
.map_or(Answer::None, |card| card.command(index, argument, context));
match (wants, answer) {
(WAIT_NO_RESPONSE | WAIT_NO_RESPONSE_TOO, _) => {
Self::flag(registers, sta::CMDSENT, true);
}
(_, Answer::None) => Self::flag(registers, sta::CTIMEOUT, true),
(WAIT_SHORT, Answer::Short(value)) => {
registers.set(Register::Resp1, u64::from(value));
registers.set(Register::Respcmd, u64::from(index));
Self::flag(registers, sta::CMDREND, true);
if index_answers_without_a_check(index) {
Self::flag(registers, sta::CCRCFAIL, true);
}
}
(WAIT_LONG, Answer::Long(words)) => {
for (register, word) in [
Register::Resp1,
Register::Resp2,
Register::Resp3,
Register::Resp4,
]
.into_iter()
.zip(words)
{
registers.set(register, u64::from(word));
}
registers.set(Register::Respcmd, u64::from(index));
Self::flag(registers, sta::CMDREND, true);
}
_ => Self::flag(registers, sta::CCRCFAIL, true),
}
self.settle(registers, context);
}
fn write_control(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !dctrl::DTEN.is_set(written) {
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
return;
}
if self.transfer.is_some() || !Self::powered(registers) {
return;
}
let direction = if dctrl::DTDIR.is_set(written) {
Direction::FromCard
} else {
Direction::ToCard
};
let block = 1_u64 << dctrl::DBLOCKSIZE.get(written);
let length = registers.get(Register::Dlen);
let clock = self.card_clock(registers);
let per_word = u128::from(BYTES_PER_WORD * 8 / Self::lines(registers)) * NANOS_PER_SECOND
/ u128::from(clock);
self.transfer = Some(Transfer {
direction,
block,
started: context.now(),
per_word,
moved: 0,
remaining: length,
buffer: Vec::with_capacity(usize::try_from(block).unwrap_or(0)),
taken: 0,
timeout: u128::from(registers.get(Register::Dtimer)) * NANOS_PER_SECOND
/ u128::from(clock),
since: context.now(),
stalled: false,
});
registers.set(Register::Dcount, length);
Self::flag(
registers,
match direction {
Direction::FromCard => sta::RXACT,
Direction::ToCard => sta::TXACT,
},
true,
);
self.settle(registers, context);
}
fn settle(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.run(registers, context);
self.refresh_fifo_flags(registers);
self.drive_request(registers, context);
self.drive_last_data(registers, context);
self.raise(registers, context);
self.schedule(context);
}
fn run(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let now = context.now();
loop {
let Some(transfer) = self.transfer.as_ref() else {
return;
};
if transfer.remaining == 0 {
self.finish(registers);
return;
}
if transfer.stalled {
if u128::from(now.0 - transfer.since.0) >= transfer.timeout {
Self::flag(registers, sta::DTIMEOUT, true);
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
}
return;
}
let due = transfer.started.0
+ u64::try_from((u128::from(transfer.moved) + 1) * transfer.per_word)
.unwrap_or(u64::MAX);
if due > now.0 {
return;
}
if !self.move_one_word(registers, now) {
return;
}
}
}
fn move_one_word(&mut self, registers: &mut RegisterFile, now: VirtualNanos) -> bool {
let Some(transfer) = self.transfer.as_mut() else {
return false;
};
match transfer.direction {
Direction::FromCard => {
if transfer.taken == transfer.buffer.len() {
let mut block = vec![0; usize::try_from(transfer.block).unwrap_or(0)];
let outcome = self
.card
.as_deref_mut()
.map_or(Outcome::Ended, |card| card.read(&mut block));
match outcome {
Outcome::Ended => {
transfer.stalled = true;
return false;
}
Outcome::Corrupt => Self::flag(registers, sta::DCRCFAIL, true),
Outcome::Whole => {}
}
transfer.buffer = block;
transfer.taken = 0;
}
if self.fifo.len() == FIFO_WORDS {
Self::flag(registers, sta::RXOVERR, true);
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
return false;
}
let mut word = [0_u8; 4];
for byte in &mut word {
*byte = transfer.buffer.get(transfer.taken).copied().unwrap_or(0);
transfer.taken += 1;
}
self.fifo.push_back(u32::from_le_bytes(word));
}
Direction::ToCard => {
let Some(word) = self.fifo.pop_front() else {
Self::flag(registers, sta::TXUNDERR, true);
self.transfer = None;
Self::flag(registers, sta::TXACT, false);
return false;
};
transfer.buffer.extend_from_slice(&word.to_le_bytes());
if u64::try_from(transfer.buffer.len()).unwrap_or(0) >= transfer.block {
let block = std::mem::take(&mut transfer.buffer);
let outcome = self
.card
.as_deref_mut()
.map_or(Outcome::Ended, |card| card.write(&block));
match outcome {
Outcome::Ended => {
transfer.stalled = true;
return false;
}
Outcome::Corrupt => Self::flag(registers, sta::DCRCFAIL, true),
Outcome::Whole => {}
}
Self::flag(registers, sta::DBCKEND, true);
transfer.since = now;
}
}
}
let Some(transfer) = self.transfer.as_mut() else {
return false;
};
transfer.moved += 1;
transfer.remaining = transfer.remaining.saturating_sub(BYTES_PER_WORD);
if transfer.direction == Direction::FromCard && transfer.taken == transfer.buffer.len() {
Self::flag(registers, sta::DBCKEND, true);
transfer.since = now;
}
registers.set(Register::Dcount, transfer.remaining);
true
}
fn finish(&mut self, registers: &mut RegisterFile) {
self.received_last = self
.transfer
.take()
.is_some_and(|transfer| transfer.direction == Direction::FromCard);
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
Self::flag(registers, sta::DATAEND, true);
}
fn refresh_fifo_flags(&mut self, registers: &mut RegisterFile) {
let held = self.fifo.len();
let room = FIFO_WORDS - held;
Self::flag(registers, sta::RXFIFOE, held == 0);
Self::flag(registers, sta::RXFIFOF, held == FIFO_WORDS);
Self::flag(registers, sta::RXFIFOHF, held >= HALF);
Self::flag(registers, sta::RXDAVL, held > 0);
Self::flag(registers, sta::TXFIFOE, held == 0);
Self::flag(registers, sta::TXFIFOF, held == FIFO_WORDS);
Self::flag(registers, sta::TXFIFOHE, room >= HALF);
Self::flag(registers, sta::TXDAVL, held > 0);
registers.set(
Register::Fifocnt,
self.transfer
.as_ref()
.map_or(0, |transfer| transfer.remaining.div_ceil(BYTES_PER_WORD)),
);
}
fn drive_request(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let armed = dctrl::DMAEN.is_set(registers.get(Register::Dctrl));
let queued = u64::try_from(self.fifo.len()).expect("thirty-two words") * BYTES_PER_WORD;
let level = match self.transfer.as_ref() {
Some(transfer)
if transfer.direction == Direction::FromCard
&& armed
&& Self::standing(registers, sta::RXFIFOHF) =>
{
Level::High
}
Some(transfer)
if transfer.direction == Direction::ToCard
&& armed
&& Self::standing(registers, sta::TXFIFOHE)
&& queued < transfer.remaining =>
{
Level::High
}
None if armed
&& self.received_last
&& Self::standing(registers, sta::DATAEND)
&& Self::standing(registers, sta::RXDAVL) =>
{
Level::High
}
_ => Level::Low,
};
if level != self.request {
self.request = level;
context.request(TRANSFER_REQUEST, level);
}
}
fn drive_last_data(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let level = Level::from(Self::standing(registers, sta::DATAEND));
if level != self.last_data {
self.last_data = level;
context.request(LAST_DATA_REQUEST, level);
}
}
fn raise(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let standing = registers.get(Register::Sta) & registers.get(Register::Mask);
let level = if standing == 0 {
Level::Low
} else {
Level::High
};
if level != self.interrupt {
self.interrupt = level;
context.interrupt(GLOBAL, level);
}
}
fn schedule(&mut self, context: &mut DeviceContext<'_>) {
let next = self.transfer.as_ref().map(|transfer| {
if transfer.stalled {
return VirtualNanos(
transfer
.since
.0
.saturating_add(u64::try_from(transfer.timeout).unwrap_or(u64::MAX)),
);
}
let due = u64::try_from((u128::from(transfer.moved) + 1) * transfer.per_word)
.unwrap_or(u64::MAX);
VirtualNanos(transfer.started.0.saturating_add(due))
});
if next != self.wakeup.map(|(at, _)| at) {
self.wakeup = next.map(|at| (at, context.schedule(at)));
}
}
}
fn index_answers_without_a_check(index: u8) -> bool {
index == crate::chip::sd::command::SD_SEND_OP_COND
}
impl Device for Sdio {
type Register = Register;
fn on_clock(
&mut self,
hz: u64,
_registers: &mut RegisterFile,
_context: &mut DeviceContext<'_>,
) {
self.clock_hz = hz;
}
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
self.settle(registers, context);
let answer = match register {
Register::Fifo => {
let word = self.fifo.pop_front().map_or(0, u64::from);
Some(word)
}
_ => None,
};
self.settle(registers, context);
answer
}
fn write(
&mut self,
register: Register,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
self.settle(registers, context);
match register {
Register::Cmd => self.send(written, registers, context),
Register::Dctrl => self.write_control(written, registers, context),
Register::Fifo => {
if self.fifo.len() < FIFO_WORDS {
self.fifo
.push_back(u32::try_from(written & u64::from(u32::MAX)).unwrap_or(0));
}
self.settle(registers, context);
}
Register::Icr => {
let status = registers.get(Register::Sta);
let cleared = status & !clears(written);
registers.set(Register::Sta, cleared);
self.settle(registers, context);
}
_ => self.settle(registers, context),
}
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if self.wakeup.is_some_and(|(_, pending)| pending == event) {
self.wakeup = None;
self.settle(registers, context);
}
}
}
fn clears(written: u64) -> u64 {
let pairs: [(&crate::soc::FieldSpec, &crate::soc::FieldSpec); 11] = [
(icr::CCRCFAILC, sta::CCRCFAIL),
(icr::DCRCFAILC, sta::DCRCFAIL),
(icr::CTIMEOUTC, sta::CTIMEOUT),
(icr::DTIMEOUTC, sta::DTIMEOUT),
(icr::TXUNDERRC, sta::TXUNDERR),
(icr::RXOVERRC, sta::RXOVERR),
(icr::CMDRENDC, sta::CMDREND),
(icr::CMDSENTC, sta::CMDSENT),
(icr::DATAENDC, sta::DATAEND),
(icr::STBITERRC, sta::STBITERR),
(icr::DBCKENDC, sta::DBCKEND),
];
pairs.into_iter().fold(0, |clearing, (clear, flag)| {
if clear.is_set(written) {
clearing | flag.mask()
} else {
clearing
}
})
}