use crate::mailbox::{ClockId, Mailbox, PowerDeviceId};
use crate::pac::GPIO;
use crate::timer::Timer;
const BASE: usize = crate::soc::PERIPHERAL_BASE as usize + 0x0020_2000;
const SDCMD: *mut u32 = BASE as *mut u32;
const SDARG: *mut u32 = (BASE + 0x04) as *mut u32;
const SDTOUT: *mut u32 = (BASE + 0x08) as *mut u32;
const SDCDIV: *mut u32 = (BASE + 0x0c) as *mut u32;
const SDRSP0: *mut u32 = (BASE + 0x10) as *mut u32;
const SDHSTS: *mut u32 = (BASE + 0x20) as *mut u32;
const SDVDD: *mut u32 = (BASE + 0x30) as *mut u32;
const SDEDM: *mut u32 = (BASE + 0x34) as *mut u32;
const SDHCFG: *mut u32 = (BASE + 0x38) as *mut u32;
const SDHBCT: *mut u32 = (BASE + 0x3c) as *mut u32;
const SDDATA: *mut u32 = (BASE + 0x40) as *mut u32;
const SDHBLC: *mut u32 = (BASE + 0x50) as *mut u32;
const SDCMD_NEW_FLAG: u32 = 0x8000;
const SDCMD_FAIL_FLAG: u32 = 0x4000;
const SDCMD_BUSYWAIT: u32 = 0x800;
const SDCMD_NO_RESPONSE: u32 = 0x400;
const SDCMD_LONG_RESPONSE: u32 = 0x200;
const SDCMD_WRITE_CMD: u32 = 0x80;
const SDCMD_READ_CMD: u32 = 0x40;
const SDHSTS_BUSY_IRPT: u32 = 0x400;
const SDHSTS_REW_TIME_OUT: u32 = 0x80;
const SDHSTS_CMD_TIME_OUT: u32 = 0x40;
const SDHSTS_CRC16_ERROR: u32 = 0x20;
const SDHSTS_CRC7_ERROR: u32 = 0x10;
const SDHSTS_FIFO_ERROR: u32 = 0x08;
const SDHSTS_ERROR_MASK: u32 = SDHSTS_CMD_TIME_OUT
| SDHSTS_REW_TIME_OUT
| SDHSTS_CRC16_ERROR
| SDHSTS_CRC7_ERROR
| SDHSTS_FIFO_ERROR;
const SDHSTS_CLEAR_MASK: u32 = 0x7f8;
const SDHCFG_BUSY_IRPT_EN: u32 = 1 << 10;
const SDHCFG_SLOW_CARD: u32 = 1 << 3;
const SDHCFG_WIDE_EXT_BUS: u32 = 1 << 2;
const SDHCFG_WIDE_INT_BUS: u32 = 1 << 1;
const SDEDM_FORCE_DATA_MODE: u32 = 1 << 19;
const SDEDM_FSM_MASK: u32 = 0xf;
const SDEDM_FSM_IDENTMODE: u32 = 0x0;
const SDEDM_FSM_DATAMODE: u32 = 0x1;
const SDEDM_FSM_READWAIT: u32 = 0x4;
const SDEDM_FSM_WRITESTART1: u32 = 0xa;
const SDEDM_FIFO_LEVEL_SHIFT: u32 = 4;
const SDEDM_FIFO_LEVEL_MASK: u32 = 0x1f;
const SDEDM_WRITE_THRESHOLD_SHIFT: u32 = 9;
const SDEDM_READ_THRESHOLD_SHIFT: u32 = 14;
const SDEDM_THRESHOLD_MASK: u32 = 0x1f;
const FIFO_WORDS: u32 = 16;
const FIFO_BURST_WORDS: u32 = 8;
const FIFO_THRESHOLD: u32 = 4;
const SETUP_CLOCK_HZ: u32 = 400_000;
const TRANSFER_CLOCK_HZ: u32 = 25_000_000;
const SDCDIV_MAX: u32 = 0x7ff;
const COMMAND_TIMEOUT_US: u64 = 1_000_000;
const FIFO_TIMEOUT_US: u64 = 500_000;
const TRANSFER_END_TIMEOUT_US: u64 = 500_000;
const PROGRAMMING_TIMEOUT_US: u64 = 5_000_000;
const CARD_STATUS_READY_FOR_DATA: u32 = 1 << 8;
const CARD_STATUS_STATE_SHIFT: u32 = 9;
const CARD_STATUS_STATE_MASK: u32 = 0xf;
const CARD_STATE_TRANSFER: u32 = 4;
const GPIO_ALT_SDHOST: u8 = 0b100;
pub type Block = [u8; 512];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
NoCard,
ControllerBusy,
CommandFailed {
command: u8,
status: u32,
},
TransferFailed {
status: u32,
edm: u32,
},
UnusableCard,
PowerOnFailed,
TooManyBlocks,
}
pub struct Sdhost {
high_capacity: bool,
rca: u32,
four_bit_bus: bool,
base_clock_hz: u32,
}
impl Sdhost {
pub fn init(gpio: &GPIO, mailbox: &mut Mailbox, timer: &Timer) -> Result<Self, Error> {
if !matches!(
mailbox.set_power_state(PowerDeviceId::SdCard, true),
Ok(true)
) {
return Err(Error::PowerOnFailed);
}
let base_clock_hz = mailbox
.clock_rate_hz(ClockId::Core)
.map_err(|_| Error::PowerOnFailed)?;
route_gpio_to_sdhost(gpio);
reset(timer);
let sdhost = Self {
high_capacity: false,
rca: 0,
four_bit_bus: false,
base_clock_hz,
};
write_reg(
SDHCFG,
SDHCFG_BUSY_IRPT_EN | SDHCFG_WIDE_INT_BUS | SDHCFG_SLOW_CARD,
);
sdhost.set_clock(SETUP_CLOCK_HZ);
sdhost.command(CMD_GO_IDLE, 0, timer)?;
if sdhost
.command(CMD_SEND_IF_COND, 0x0000_01aa, timer)
.is_err()
{
return Err(Error::NoCard);
}
let start = timer.now_micros();
let response = loop {
let response = sdhost.app_command(CMD_SEND_OP_COND, ACMD41_ARG_HC, timer)?;
if response & ACMD41_COMPLETE != 0 {
break response;
}
if timer.now_micros() - start > 1_000_000 {
return Err(Error::NoCard);
}
timer.delay_ms(10);
};
if response & ACMD41_VOLTAGE_WINDOW == 0 {
return Err(Error::UnusableCard);
}
let high_capacity = response & ACMD41_HIGH_CAPACITY != 0;
sdhost.command(CMD_ALL_SEND_CID, 0, timer)?;
let rca = sdhost.command(CMD_SEND_REL_ADDR, 0, timer)? & 0xffff_0000;
let sdhost = Self {
high_capacity,
rca,
..sdhost
};
sdhost.command(CMD_CARD_SELECT, rca, timer)?;
sdhost.set_clock(TRANSFER_CLOCK_HZ);
let four_bit_bus = sdhost.negotiate_four_bit_bus(timer).unwrap_or(false);
Ok(Self {
four_bit_bus,
..sdhost
})
}
pub fn high_capacity(&self) -> bool {
self.high_capacity
}
pub fn four_bit_bus(&self) -> bool {
self.four_bit_bus
}
pub fn read_block(
&self,
block_index: u32,
buf: &mut Block,
timer: &Timer,
) -> Result<(), Error> {
self.read_blocks(block_index, core::slice::from_mut(buf), timer)
}
pub fn read_blocks(
&self,
block_index: u32,
blocks: &mut [Block],
timer: &Timer,
) -> Result<(), Error> {
if blocks.is_empty() {
return Ok(());
}
let count = u32::try_from(blocks.len()).map_err(|_| Error::TooManyBlocks)?;
if count > u32::from(u16::MAX) {
return Err(Error::TooManyBlocks);
}
self.start_transfer(count, timer)?;
let command = if count == 1 {
CMD_READ_SINGLE
} else {
CMD_READ_MULTI
};
self.command_with_data(command, self.address(block_index), true, timer)?;
for block in blocks.iter_mut() {
read_fifo(block, timer)?;
}
self.end_transfer(count, true, timer)
}
pub fn write_block(&self, block_index: u32, buf: &Block, timer: &Timer) -> Result<(), Error> {
self.write_blocks(block_index, core::slice::from_ref(buf), timer)
}
pub fn write_blocks(
&self,
block_index: u32,
blocks: &[Block],
timer: &Timer,
) -> Result<(), Error> {
if blocks.is_empty() {
return Ok(());
}
let count = u32::try_from(blocks.len()).map_err(|_| Error::TooManyBlocks)?;
if count > u32::from(u16::MAX) {
return Err(Error::TooManyBlocks);
}
self.start_transfer(count, timer)?;
let command = if count == 1 {
CMD_WRITE_SINGLE
} else {
CMD_WRITE_MULTI
};
self.command_with_data(command, self.address(block_index), false, timer)?;
for block in blocks {
write_fifo(block, timer)?;
}
self.end_transfer(count, false, timer)
}
fn end_transfer(&self, count: u32, read: bool, timer: &Timer) -> Result<(), Error> {
if read {
if count > 1 {
self.command(CMD_STOP_TRANSMISSION, 0, timer)?;
} else {
force_data_mode(true, timer)?;
}
return check_transfer();
}
force_data_mode(false, timer)?;
if count > 1 {
self.command(CMD_STOP_TRANSMISSION, 0, timer)?;
}
check_transfer()?;
self.wait_ready(timer)
}
fn wait_ready(&self, timer: &Timer) -> Result<(), Error> {
let start = timer.now_micros();
loop {
let status = self.command(CMD_SEND_STATUS, self.rca, timer)?;
if status & CARD_STATUS_READY_FOR_DATA != 0
&& (status >> CARD_STATUS_STATE_SHIFT) & CARD_STATUS_STATE_MASK
== CARD_STATE_TRANSFER
{
return Ok(());
}
if timer.now_micros() - start > PROGRAMMING_TIMEOUT_US {
return Err(Error::TransferFailed {
status: read_reg(SDHSTS),
edm: read_reg(SDEDM),
});
}
}
}
fn address(&self, block_index: u32) -> u32 {
if self.high_capacity {
block_index
} else {
block_index * 512
}
}
fn start_transfer(&self, blocks: u32, timer: &Timer) -> Result<(), Error> {
wait_command_idle(timer)?;
write_reg(SDHBCT, 512);
write_reg(SDHBLC, blocks);
Ok(())
}
fn command(&self, command: Command, arg: u32, timer: &Timer) -> Result<u32, Error> {
self.issue(command, arg, None, timer)
}
fn command_with_data(
&self,
command: Command,
arg: u32,
read: bool,
timer: &Timer,
) -> Result<u32, Error> {
self.issue(command, arg, Some(read), timer)
}
fn app_command(&self, command: Command, arg: u32, timer: &Timer) -> Result<u32, Error> {
self.command(CMD_APP_CMD, self.rca, timer)?;
self.command(command, arg, timer)
}
fn issue(
&self,
command: Command,
arg: u32,
data: Option<bool>,
timer: &Timer,
) -> Result<u32, Error> {
wait_command_idle(timer)?;
write_reg(SDHSTS, SDHSTS_CLEAR_MASK);
let mut sdcmd = u32::from(command.index);
match command.response {
Response::None => sdcmd |= SDCMD_NO_RESPONSE,
Response::Short => {}
Response::ShortBusy => sdcmd |= SDCMD_BUSYWAIT,
Response::Long => sdcmd |= SDCMD_LONG_RESPONSE,
}
match data {
Some(true) => sdcmd |= SDCMD_READ_CMD,
Some(false) => sdcmd |= SDCMD_WRITE_CMD,
None => {}
}
write_reg(SDARG, arg);
write_reg(SDCMD, sdcmd | SDCMD_NEW_FLAG);
let start = timer.now_micros();
let sdcmd = loop {
let sdcmd = read_reg(SDCMD);
if sdcmd & SDCMD_NEW_FLAG == 0 {
break sdcmd;
}
if timer.now_micros() - start > COMMAND_TIMEOUT_US {
return Err(Error::ControllerBusy);
}
};
let status = read_reg(SDHSTS);
if sdcmd & SDCMD_FAIL_FLAG != 0 || status & SDHSTS_ERROR_MASK != 0 {
return Err(Error::CommandFailed {
command: command.index,
status,
});
}
if matches!(command.response, Response::ShortBusy) {
self.wait_not_busy(command.index, timer)?;
}
Ok(match command.response {
Response::None => 0,
_ => read_reg(SDRSP0),
})
}
fn wait_not_busy(&self, command: u8, timer: &Timer) -> Result<(), Error> {
let start = timer.now_micros();
loop {
let status = read_reg(SDHSTS);
if status & SDHSTS_ERROR_MASK != 0 {
return Err(Error::CommandFailed { command, status });
}
if status & SDHSTS_BUSY_IRPT != 0 {
write_reg(SDHSTS, SDHSTS_BUSY_IRPT);
return Ok(());
}
if timer.now_micros() - start > PROGRAMMING_TIMEOUT_US {
return Err(Error::CommandFailed { command, status });
}
}
}
fn negotiate_four_bit_bus(&self, timer: &Timer) -> Result<bool, Error> {
wait_command_idle(timer)?;
write_reg(SDHBCT, 8);
write_reg(SDHBLC, 1);
self.command(CMD_APP_CMD, self.rca, timer)?;
self.command_with_data(CMD_SEND_SCR, 0, true, timer)?;
let mut scr = [0u8; 8];
read_fifo(&mut scr, timer)?;
force_data_mode(true, timer)?;
check_transfer()?;
if u32::from_be_bytes([scr[0], scr[1], scr[2], scr[3]]) & (1 << 18) == 0 {
return Ok(false);
}
self.command(CMD_APP_CMD, self.rca, timer)?;
self.command(CMD_SET_BUS_WIDTH, 2, timer)?;
write_reg(SDHCFG, read_reg(SDHCFG) | SDHCFG_WIDE_EXT_BUS);
Ok(true)
}
fn set_clock(&self, target_hz: u32) {
let mut divider = self.base_clock_hz / target_hz.max(1);
if divider < 2 {
divider = 2;
}
if self.base_clock_hz / divider > target_hz {
divider += 1;
}
let cdiv = (divider - 2).min(SDCDIV_MAX);
write_reg(SDCDIV, cdiv);
let actual_hz = self.base_clock_hz / (cdiv + 2);
write_reg(SDTOUT, actual_hz / 2);
}
}
#[derive(Clone, Copy)]
enum Response {
None,
Short,
ShortBusy,
Long,
}
#[derive(Clone, Copy)]
struct Command {
index: u8,
response: Response,
}
const CMD_GO_IDLE: Command = Command {
index: 0,
response: Response::None,
};
const CMD_ALL_SEND_CID: Command = Command {
index: 2,
response: Response::Long,
};
const CMD_SEND_REL_ADDR: Command = Command {
index: 3,
response: Response::Short,
};
const CMD_SET_BUS_WIDTH: Command = Command {
index: 6,
response: Response::Short,
};
const CMD_CARD_SELECT: Command = Command {
index: 7,
response: Response::ShortBusy,
};
const CMD_SEND_IF_COND: Command = Command {
index: 8,
response: Response::Short,
};
const CMD_STOP_TRANSMISSION: Command = Command {
index: 12,
response: Response::ShortBusy,
};
const CMD_SEND_STATUS: Command = Command {
index: 13,
response: Response::Short,
};
const CMD_READ_SINGLE: Command = Command {
index: 17,
response: Response::Short,
};
const CMD_READ_MULTI: Command = Command {
index: 18,
response: Response::Short,
};
const CMD_WRITE_SINGLE: Command = Command {
index: 24,
response: Response::Short,
};
const CMD_WRITE_MULTI: Command = Command {
index: 25,
response: Response::Short,
};
const CMD_SEND_OP_COND: Command = Command {
index: 41,
response: Response::Short,
};
const CMD_SEND_SCR: Command = Command {
index: 51,
response: Response::Short,
};
const CMD_APP_CMD: Command = Command {
index: 55,
response: Response::Short,
};
const ACMD41_ARG_HC: u32 = 0x51ff_8000;
const ACMD41_COMPLETE: u32 = 0x8000_0000;
const ACMD41_VOLTAGE_WINDOW: u32 = 0x00ff_8000;
const ACMD41_HIGH_CAPACITY: u32 = 0x4000_0000;
fn route_gpio_to_sdhost(gpio: &GPIO) {
gpio.gpfsel4().modify(|_, w| {
w.fsel48()
.bits(GPIO_ALT_SDHOST)
.fsel49()
.bits(GPIO_ALT_SDHOST)
});
gpio.gpfsel5().modify(|_, w| {
w.fsel50()
.bits(GPIO_ALT_SDHOST)
.fsel51()
.bits(GPIO_ALT_SDHOST)
.fsel52()
.bits(GPIO_ALT_SDHOST)
.fsel53()
.bits(GPIO_ALT_SDHOST)
});
crate::gpio::set_pull_bank(gpio, 1, 0x003f_0000, crate::gpio::Pull::Up);
}
fn reset(timer: &Timer) {
write_reg(SDVDD, 0);
write_reg(SDCMD, 0);
write_reg(SDARG, 0);
write_reg(SDTOUT, 0x00f0_0000);
write_reg(SDCDIV, 0);
write_reg(SDHSTS, SDHSTS_CLEAR_MASK);
write_reg(SDHCFG, 0);
write_reg(SDHBCT, 0);
write_reg(SDHBLC, 0);
let edm = read_reg(SDEDM);
let cleared = edm
& !((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT)
| (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT));
write_reg(
SDEDM,
cleared
| (FIFO_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT)
| (FIFO_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT),
);
timer.delay_ms(10);
write_reg(SDVDD, 1);
timer.delay_ms(10);
}
fn wait_command_idle(timer: &Timer) -> Result<(), Error> {
let start = timer.now_micros();
while read_reg(SDCMD) & SDCMD_NEW_FLAG != 0 {
if timer.now_micros() - start > COMMAND_TIMEOUT_US {
return Err(Error::ControllerBusy);
}
}
Ok(())
}
fn read_fifo(buf: &mut [u8], timer: &Timer) -> Result<(), Error> {
let start = timer.now_micros();
let mut at = 0;
while at < buf.len() {
let remaining = ((buf.len() - at) / 4) as u32;
let burst = remaining.min(FIFO_BURST_WORDS);
let edm = read_reg(SDEDM);
let available = (edm >> SDEDM_FIFO_LEVEL_SHIFT) & SDEDM_FIFO_LEVEL_MASK;
if available < burst {
let status = read_reg(SDHSTS);
if status & SDHSTS_ERROR_MASK != 0 || timer.now_micros() - start > FIFO_TIMEOUT_US {
return Err(Error::TransferFailed { status, edm });
}
continue;
}
let words = available.min(remaining);
for _ in 0..words {
let word = read_reg(SDDATA);
buf[at..at + 4].copy_from_slice(&word.to_le_bytes());
at += 4;
}
}
Ok(())
}
fn write_fifo(buf: &[u8], timer: &Timer) -> Result<(), Error> {
let start = timer.now_micros();
let mut at = 0;
while at < buf.len() {
let remaining = ((buf.len() - at) / 4) as u32;
let burst = remaining.min(FIFO_BURST_WORDS);
let edm = read_reg(SDEDM);
let room = FIFO_WORDS - ((edm >> SDEDM_FIFO_LEVEL_SHIFT) & SDEDM_FIFO_LEVEL_MASK);
if room < burst {
let status = read_reg(SDHSTS);
if status & SDHSTS_ERROR_MASK != 0 || timer.now_micros() - start > FIFO_TIMEOUT_US {
return Err(Error::TransferFailed { status, edm });
}
continue;
}
let words = room.min(remaining);
for _ in 0..words {
let word = u32::from_le_bytes([buf[at], buf[at + 1], buf[at + 2], buf[at + 3]]);
write_reg(SDDATA, word);
at += 4;
}
}
Ok(())
}
fn force_data_mode(read: bool, timer: &Timer) -> Result<(), Error> {
let parked = if read {
SDEDM_FSM_READWAIT
} else {
SDEDM_FSM_WRITESTART1
};
let start = timer.now_micros();
loop {
let edm = read_reg(SDEDM);
let fsm = edm & SDEDM_FSM_MASK;
if fsm == SDEDM_FSM_IDENTMODE || fsm == SDEDM_FSM_DATAMODE {
return Ok(());
}
if fsm == parked {
write_reg(SDEDM, edm | SDEDM_FORCE_DATA_MODE);
return Ok(());
}
if timer.now_micros() - start > TRANSFER_END_TIMEOUT_US {
return Err(Error::TransferFailed {
status: read_reg(SDHSTS),
edm,
});
}
}
}
fn check_transfer() -> Result<(), Error> {
let status = read_reg(SDHSTS);
if status & SDHSTS_ERROR_MASK != 0 {
return Err(Error::TransferFailed {
status,
edm: read_reg(SDEDM),
});
}
Ok(())
}
#[cfg(feature = "resident-fat")]
pub struct SdhostBlockDevice<'t> {
sdhost: Sdhost,
timer: &'t Timer,
}
#[cfg(feature = "resident-fat")]
impl<'t> SdhostBlockDevice<'t> {
pub fn new(sdhost: Sdhost, timer: &'t Timer) -> Self {
Self { sdhost, timer }
}
pub fn inner(&self) -> &Sdhost {
&self.sdhost
}
pub fn into_inner(self) -> Sdhost {
self.sdhost
}
}
#[cfg(feature = "resident-fat")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SdhostBlockDeviceError {
Sdhost(Error),
BlockOutOfRange {
start_block: u64,
},
}
#[cfg(feature = "resident-fat")]
impl From<Error> for SdhostBlockDeviceError {
fn from(e: Error) -> Self {
SdhostBlockDeviceError::Sdhost(e)
}
}
#[cfg(feature = "resident-fat")]
const BLOCK_LEN: usize = resident_fat::BLOCK_SIZE;
#[cfg(feature = "resident-fat")]
const _: () = assert!(BLOCK_LEN == core::mem::size_of::<Block>());
#[cfg(feature = "resident-fat")]
impl resident_fat::BlockDevice for SdhostBlockDevice<'_> {
type Error = SdhostBlockDeviceError;
fn read(&mut self, start_block: u64, blocks: &mut [u8]) -> Result<(), Self::Error> {
let index = u32::try_from(start_block)
.map_err(|_| SdhostBlockDeviceError::BlockOutOfRange { start_block })?;
let (blocks, rest) = blocks.as_chunks_mut::<BLOCK_LEN>();
assert!(rest.is_empty(), "transfer length must be a multiple of 512");
self.sdhost.read_blocks(index, blocks, self.timer)?;
Ok(())
}
fn write(&mut self, start_block: u64, blocks: &[u8]) -> Result<(), Self::Error> {
let index = u32::try_from(start_block)
.map_err(|_| SdhostBlockDeviceError::BlockOutOfRange { start_block })?;
let (blocks, rest) = blocks.as_chunks::<BLOCK_LEN>();
assert!(rest.is_empty(), "transfer length must be a multiple of 512");
self.sdhost.write_blocks(index, blocks, self.timer)?;
Ok(())
}
fn block_count(&mut self) -> Result<Option<u64>, Self::Error> {
Ok(None)
}
fn max_transfer_blocks(&self) -> u64 {
u64::from(u16::MAX)
}
}
fn read_reg(reg: *mut u32) -> u32 {
unsafe { reg.read_volatile() }
}
fn write_reg(reg: *mut u32, value: u32) {
unsafe { reg.write_volatile(value) }
}