use core::task::Waker;
use log::{debug, info, warn};
pub use crate::cmd::DataDirection;
use crate::{
block::{BlockRequestId, CommandResponsePoll, DataCommandPoll, OperationPoll},
cmd::Command,
common::block_addr_of,
error::{Error, ErrorContext, Phase},
response::{
CardState, CidResponse, CsdResponse, OcrResponse, Response, ResponseType, SwitchStatus,
},
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum HostEventKind {
#[default]
None,
CommandComplete,
TransferComplete,
ReceiveReady,
TransmitReady,
Error,
Other,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum HostEventSource {
#[default]
Controller,
Command,
Data,
}
pub trait HostEvent {
fn kind(&self) -> HostEventKind;
fn source(&self) -> HostEventSource {
HostEventSource::Controller
}
fn queue_id(&self) -> Option<BlockRequestId> {
None
}
}
impl HostEvent for () {
fn kind(&self) -> HostEventKind {
HostEventKind::None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BusWidth {
Bit1,
Bit4,
Bit8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockSpeed {
Identification,
Default,
HighSpeed,
Sdr12,
Sdr25,
Sdr50,
Sdr104,
Ddr50,
Hs200,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignalVoltage {
V330,
V180,
V120,
}
pub trait SdioHost {
type Event: HostEvent + Default;
fn submit_command(&mut self, cmd: &Command) -> Result<(), Error>;
fn poll_command_response(&mut self) -> Result<CommandResponsePoll, Error>;
type DataRequest<'a>
where
Self: 'a;
fn submit_read_data<'a>(
&mut self,
cmd: &Command,
buf: &'a mut [u8],
block_size: u32,
block_count: u32,
) -> Result<Self::DataRequest<'a>, Error>;
fn submit_write_data<'a>(
&mut self,
cmd: &Command,
buf: &'a [u8],
block_size: u32,
block_count: u32,
) -> Result<Self::DataRequest<'a>, Error>;
fn poll_data_request<'a>(
&mut self,
request: &mut Self::DataRequest<'a>,
) -> Result<DataCommandPoll, Error>;
fn set_bus_width(&mut self, width: BusWidth) -> Result<(), Error>;
fn set_clock(&mut self, speed: ClockSpeed) -> Result<(), Error>;
fn switch_voltage(&mut self, _voltage: SignalVoltage) -> Result<(), Error> {
Err(Error::UnsupportedCommand)
}
fn execute_tuning(&mut self, _cmd_index: u8) -> Result<(), Error> {
Err(Error::UnsupportedCommand)
}
fn enable_completion_irq(&mut self) -> Result<(), Error> {
Ok(())
}
fn disable_completion_irq(&mut self) -> Result<(), Error> {
Ok(())
}
fn handle_irq(&mut self) -> Self::Event {
Self::Event::default()
}
fn register_waker(&mut self, _waker: &Waker) {}
}
struct SdioInitTiming;
impl SdioInitTiming {
const TIMEOUT_MS: u32 = 1_000;
const POLL_MS: u32 = 10;
}
struct MmcSwitchTiming;
impl MmcSwitchTiming {
const TIMEOUT_MS: u32 = 250;
const POLL_MS: u32 = SdioInitTiming::POLL_MS;
}
pub struct SdioSdmmc<H: SdioHost> {
host: H,
rca: u16,
high_capacity: bool,
bus_width: BusWidth,
kind: CardKind,
sd_speed_selection_enabled: bool,
}
pub struct SdioDataRequest<'a, H: SdioHost + 'a> {
inner: H::DataRequest<'a>,
}
pub struct SdioCommandRequest;
pub struct SdioStatusRequest {
inner: SdioCommandRequest,
}
pub struct ExtCsdRequest<'a, H: SdioHost + 'a> {
inner: SdioDataRequest<'a, H>,
}
pub struct SwitchFunctionRequest<'a, H: SdioHost + 'a> {
inner: SdioDataRequest<'a, H>,
}
pub struct MmcSwitchRequest {
rca: u16,
index: u8,
value: u8,
elapsed_ms: u32,
state: MmcSwitchRequestState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MmcSwitchRequestState {
PollSwitch,
PollStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CardInitPreference {
SdFirst,
MmcFirst,
}
pub struct SdioInitScratch {
ext_csd: [u8; 512],
switch_status: [u8; 64],
}
impl SdioInitScratch {
pub const fn new() -> Self {
Self {
ext_csd: [0; 512],
switch_status: [0; 64],
}
}
}
impl Default for SdioInitScratch {
fn default() -> Self {
Self::new()
}
}
pub struct SdioInitRequest<'a, H: SdioHost + 'a> {
state: SdioInitState,
preference: CardInitPreference,
sd_v2: bool,
kind: Option<CardKind>,
ocr: Option<OcrResponse>,
cid: Option<CidResponse>,
capacity_blocks: Option<u64>,
parsed_ext_csd: Option<crate::ext_csd::ExtCsd>,
acmd41_elapsed_ms: u32,
mmc_elapsed_ms: u32,
mmc_ocr_arg: u32,
needs_pace: bool,
ext_csd_buf: core::ptr::NonNull<[u8; 512]>,
switch_status_buf: core::ptr::NonNull<[u8; 64]>,
ext_csd_request: Option<ExtCsdRequest<'a, H>>,
switch_function_request: Option<SwitchFunctionRequest<'a, H>>,
mmc_switch_request: Option<MmcSwitchRequest>,
status_request: Option<SdioStatusRequest>,
command_request: Option<SdioCommandRequest>,
current_bus_width: BusWidth,
current_access_mode: Option<SdAccessMode>,
sd_access_index: usize,
mmc_hs200_attempted: bool,
_scratch: core::marker::PhantomData<&'a mut SdioInitScratch>,
}
impl<'a, H: SdioHost + 'a> SdioInitRequest<'a, H> {
fn new(preference: CardInitPreference, scratch: &'a mut SdioInitScratch) -> Self {
Self {
state: SdioInitState::PollCmd0,
preference,
sd_v2: false,
kind: None,
ocr: None,
cid: None,
capacity_blocks: None,
parsed_ext_csd: None,
acmd41_elapsed_ms: 0,
mmc_elapsed_ms: 0,
mmc_ocr_arg: 0,
needs_pace: false,
ext_csd_buf: core::ptr::NonNull::from(&mut scratch.ext_csd),
switch_status_buf: core::ptr::NonNull::from(&mut scratch.switch_status),
ext_csd_request: None,
switch_function_request: None,
mmc_switch_request: None,
status_request: None,
command_request: None,
current_bus_width: BusWidth::Bit1,
current_access_mode: None,
sd_access_index: 0,
mmc_hs200_attempted: false,
_scratch: core::marker::PhantomData,
}
}
pub fn take_needs_pace(&mut self) -> bool {
let needs_pace = self.needs_pace;
self.needs_pace = false;
needs_pace
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SdioInitState {
PollCmd0,
PollCmd8,
PollAcmd41Cmd55,
PollAcmd41,
PollMmcInitial,
PollMmcReady,
PollCmd2,
PollCmd3,
PollCmd9,
PollCmd7,
PollSdBusWidthCmd55,
PollSdBusWidthAcmd6,
FinishCardSetup,
PollMmcExtCsd,
PollMmcBusWidth,
PrepareMmcSpeed,
PollMmcHs200Switch,
PollMmcHs200Status,
PollMmcHs52Switch,
PrepareSdSpeed,
PollSdSwitchFunctionCheck,
PollSdVoltageSwitch,
PollSdSetAccessMode,
PollSdStatus,
Complete,
}
#[derive(Debug, Clone, Copy)]
enum SdAccessMode {
HighSpeed,
Sdr50,
Sdr104,
Ddr50,
}
impl SdAccessMode {
fn function(self) -> u8 {
match self {
Self::HighSpeed => 1,
Self::Sdr50 => 2,
Self::Sdr104 => 3,
Self::Ddr50 => 4,
}
}
fn clock(self) -> ClockSpeed {
match self {
Self::HighSpeed => ClockSpeed::HighSpeed,
Self::Sdr50 => ClockSpeed::Sdr50,
Self::Sdr104 => ClockSpeed::Sdr104,
Self::Ddr50 => ClockSpeed::Ddr50,
}
}
const fn name(self) -> &'static str {
match self {
Self::HighSpeed => "HighSpeed",
Self::Sdr50 => "SDR50",
Self::Sdr104 => "SDR104",
Self::Ddr50 => "DDR50",
}
}
}
impl<H: SdioHost> SdioSdmmc<H> {
pub fn new(host: H) -> Self {
Self {
host,
rca: 0,
high_capacity: false,
bus_width: BusWidth::Bit1,
kind: CardKind::Sd,
sd_speed_selection_enabled: true,
}
}
pub fn host_mut(&mut self) -> &mut H {
&mut self.host
}
pub fn is_high_capacity(&self) -> bool {
self.high_capacity
}
pub fn set_sd_speed_selection_enabled(&mut self, enabled: bool) {
self.sd_speed_selection_enabled = enabled;
}
pub fn kind(&self) -> CardKind {
self.kind
}
pub fn rca(&self) -> u16 {
self.rca
}
pub fn submit_read_blocks_into<'a>(
&mut self,
addr: u32,
buf: &'a mut [u8],
) -> Result<SdioDataRequest<'a, H>, Error>
where
H: 'a,
{
let count = block_count_from_len(buf.len())?;
let block_addr = block_addr_of(addr, self.high_capacity);
let cmd = if count == 1 {
crate::cmd::cmd17(block_addr)
} else {
crate::cmd::cmd18(block_addr)
};
let inner = self.host.submit_read_data(&cmd, buf, 512, count)?;
Ok(SdioDataRequest { inner })
}
pub fn submit_write_blocks_from<'a>(
&mut self,
addr: u32,
buf: &'a [u8],
) -> Result<SdioDataRequest<'a, H>, Error>
where
H: 'a,
{
let count = block_count_from_len(buf.len())?;
let block_addr = block_addr_of(addr, self.high_capacity);
let cmd = if count == 1 {
crate::cmd::cmd24(block_addr)
} else {
crate::cmd::cmd25(block_addr)
};
let inner = self.host.submit_write_data(&cmd, buf, 512, count)?;
Ok(SdioDataRequest { inner })
}
pub fn poll_data_request<'a>(
&mut self,
request: &mut SdioDataRequest<'a, H>,
) -> Result<DataCommandPoll, Error>
where
H: 'a,
{
self.host.poll_data_request(&mut request.inner)
}
pub fn submit_command_request(&mut self, cmd: &Command) -> Result<SdioCommandRequest, Error> {
self.host.submit_command(cmd)?;
Ok(SdioCommandRequest)
}
pub fn poll_command_request(
&mut self,
_request: &mut SdioCommandRequest,
) -> Result<CommandResponsePoll, Error> {
self.host.poll_command_response()
}
pub fn submit_status(&mut self) -> Result<SdioStatusRequest, Error> {
let cmd = crate::cmd::cmd13(self.rca);
let inner = self.submit_command_request(&cmd)?;
Ok(SdioStatusRequest { inner })
}
pub fn poll_status_request(
&mut self,
request: &mut SdioStatusRequest,
) -> Result<OperationPoll<CardState>, Error> {
match self.poll_command_request(&mut request.inner)? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(Response::R1(r1)) => {
Ok(OperationPoll::Complete(r1.current_state()))
}
CommandResponsePoll::Complete(_) => Err(Error::BadResponse(ErrorContext::for_cmd(
Phase::ResponseWait,
13,
))),
}
}
pub fn submit_read_data_command<'a>(
&mut self,
cmd: &Command,
buf: &'a mut [u8],
block_size: u32,
block_count: u32,
) -> Result<SdioDataRequest<'a, H>, Error>
where
H: 'a,
{
let inner = self
.host
.submit_read_data(cmd, buf, block_size, block_count)?;
Ok(SdioDataRequest { inner })
}
pub fn submit_read_ext_csd<'a>(
&mut self,
buf: &'a mut [u8; 512],
) -> Result<ExtCsdRequest<'a, H>, Error>
where
H: 'a,
{
let inner = self.submit_read_data_command(&crate::cmd::CMD8_MMC, buf, 512, 1)?;
Ok(ExtCsdRequest { inner })
}
pub fn poll_ext_csd_request<'a>(
&mut self,
request: &mut ExtCsdRequest<'a, H>,
) -> Result<OperationPoll<()>, Error>
where
H: 'a,
{
match self.poll_data_request(&mut request.inner)? {
DataCommandPoll::Pending => Ok(OperationPoll::Pending),
DataCommandPoll::Complete(_) => Ok(OperationPoll::Complete(())),
}
}
pub fn submit_switch_function<'a>(
&mut self,
cmd: &Command,
buf: &'a mut [u8; 64],
) -> Result<SwitchFunctionRequest<'a, H>, Error>
where
H: 'a,
{
let inner = self.submit_read_data_command(cmd, buf, 64, 1)?;
Ok(SwitchFunctionRequest { inner })
}
pub fn poll_switch_function_request<'a>(
&mut self,
request: &mut SwitchFunctionRequest<'a, H>,
) -> Result<OperationPoll<()>, Error>
where
H: 'a,
{
match self.poll_data_request(&mut request.inner)? {
DataCommandPoll::Pending => Ok(OperationPoll::Pending),
DataCommandPoll::Complete(_) => Ok(OperationPoll::Complete(())),
}
}
pub fn submit_mmc_switch(
&mut self,
access: u8,
index: u8,
value: u8,
) -> Result<MmcSwitchRequest, Error> {
let cmd = crate::cmd::cmd6_mmc_switch(access, index, value);
self.host.submit_command(&cmd)?;
Ok(MmcSwitchRequest {
rca: self.rca,
index,
value,
elapsed_ms: 0,
state: MmcSwitchRequestState::PollSwitch,
})
}
pub fn poll_mmc_switch_request(
&mut self,
request: &mut MmcSwitchRequest,
) -> Result<OperationPoll<()>, Error> {
match request.state {
MmcSwitchRequestState::PollSwitch => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(_) => {
let cmd = crate::cmd::cmd13(request.rca);
self.host.submit_command(&cmd)?;
request.state = MmcSwitchRequestState::PollStatus;
Ok(OperationPoll::Pending)
}
},
MmcSwitchRequestState::PollStatus => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(Response::R1(r1)) => {
if r1.switch_error() {
warn!(
"sdio: SWITCH_ERROR after CMD6 idx={} val={}",
request.index, request.value
);
return Err(Error::CardError(crate::error::CardError::IllegalCommand));
}
if r1.ready_for_data() && matches!(r1.current_state(), CardState::Transfer) {
return Ok(OperationPoll::Complete(()));
}
if request.elapsed_ms >= MmcSwitchTiming::TIMEOUT_MS {
return Err(Error::Timeout(ErrorContext::for_cmd(Phase::Init, 6)));
}
request.elapsed_ms =
request.elapsed_ms.saturating_add(MmcSwitchTiming::POLL_MS);
let cmd = crate::cmd::cmd13(request.rca);
self.host.submit_command(&cmd)?;
Ok(OperationPoll::Pending)
}
CommandResponsePoll::Complete(_) => {
if request.elapsed_ms >= MmcSwitchTiming::TIMEOUT_MS {
return Err(Error::Timeout(ErrorContext::for_cmd(Phase::Init, 6)));
}
request.elapsed_ms =
request.elapsed_ms.saturating_add(MmcSwitchTiming::POLL_MS);
let cmd = crate::cmd::cmd13(request.rca);
self.host.submit_command(&cmd)?;
Ok(OperationPoll::Pending)
}
},
}
}
}
impl<H: SdioHost> SdioSdmmc<H> {
pub fn submit_init<'a>(
&mut self,
scratch: &'a mut SdioInitScratch,
) -> Result<SdioInitRequest<'a, H>, Error>
where
H: 'a,
{
self.submit_init_with_preference(CardInitPreference::SdFirst, scratch)
}
pub fn submit_init_with_preference<'a>(
&mut self,
preference: CardInitPreference,
scratch: &'a mut SdioInitScratch,
) -> Result<SdioInitRequest<'a, H>, Error>
where
H: 'a,
{
info!("sdio: init starting");
self.host.set_bus_width(BusWidth::Bit1)?;
self.host.set_clock(ClockSpeed::Identification)?;
info!("sdio: CMD0 reset");
self.host.submit_command(&crate::cmd::CMD0)?;
Ok(SdioInitRequest::new(preference, scratch))
}
pub fn poll_init_request<'a>(
&mut self,
request: &mut SdioInitRequest<'a, H>,
) -> Result<OperationPoll<CardInfo>, Error> {
const MMC_HCS: u32 = 1 << 30;
const MMC_VOLTAGE_MASK: u32 = 0x00FF_8000;
const MMC_ACCESS_MODE_MASK: u32 = 0x6000_0000;
match request.state {
SdioInitState::PollCmd0 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(_) => {
match request.preference {
CardInitPreference::SdFirst => {
let cmd = crate::cmd::cmd8(0x01, 0xAA);
self.host.submit_command(&cmd)?;
request.state = SdioInitState::PollCmd8;
}
CardInitPreference::MmcFirst => {
info!("sdio: MMC-first init, trying CMD1");
self.host.submit_command(&crate::cmd::cmd1(0))?;
request.state = SdioInitState::PollMmcInitial;
}
}
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollCmd8 => match self.host.poll_command_response() {
Ok(CommandResponsePoll::Pending) => Ok(OperationPoll::Pending),
Ok(CommandResponsePoll::Complete(Response::R7(resp))) => {
request.sd_v2 = resp.verify(0x01, 0xAA);
info!("sdio: CMD8 sd_v2={}", request.sd_v2);
let cmd55 = crate::cmd::cmd55(0);
self.host.submit_command(&cmd55)?;
request.state = SdioInitState::PollAcmd41Cmd55;
Ok(OperationPoll::Pending)
}
Ok(CommandResponsePoll::Complete(_))
| Err(Error::Timeout(_))
| Err(Error::BadResponse(_))
| Err(Error::Crc(_)) => {
request.sd_v2 = false;
info!("sdio: CMD8 sd_v2=false");
let cmd55 = crate::cmd::cmd55(0);
self.host.submit_command(&cmd55)?;
request.state = SdioInitState::PollAcmd41Cmd55;
Ok(OperationPoll::Pending)
}
Err(e) => Err(e),
},
SdioInitState::PollAcmd41Cmd55 => match self.host.poll_command_response() {
Ok(CommandResponsePoll::Pending) => Ok(OperationPoll::Pending),
Ok(CommandResponsePoll::Complete(_)) => {
let acmd41 = crate::cmd::cmd41_with_s18r(request.sd_v2, 0xFF8000, true);
self.host.submit_command(&acmd41)?;
request.state = SdioInitState::PollAcmd41;
Ok(OperationPoll::Pending)
}
Err(_sd_err) => {
info!(
"sdio: ACMD41 prologue failed ({:?}), trying MMC CMD1",
_sd_err
);
self.host.submit_command(&crate::cmd::cmd1(0))?;
request.state = SdioInitState::PollMmcInitial;
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollAcmd41 => match self.host.poll_command_response() {
Ok(CommandResponsePoll::Pending) => Ok(OperationPoll::Pending),
Ok(CommandResponsePoll::Complete(Response::R3(ocr))) => {
if ocr.card_powered_up() {
request.kind = Some(CardKind::Sd);
request.ocr = Some(ocr);
self.kind = CardKind::Sd;
info!("sdio: detected {:?} ocr={:#010x}", CardKind::Sd, ocr.raw);
self.host.submit_command(&crate::cmd::CMD2)?;
request.state = SdioInitState::PollCmd2;
} else {
if request.acmd41_elapsed_ms >= SdioInitTiming::TIMEOUT_MS {
warn!(
"sdio: ACMD41 timed out after {}ms, trying MMC CMD1",
request.acmd41_elapsed_ms
);
self.host.submit_command(&crate::cmd::cmd1(0))?;
request.state = SdioInitState::PollMmcInitial;
return Ok(OperationPoll::Pending);
}
request.acmd41_elapsed_ms = request
.acmd41_elapsed_ms
.saturating_add(SdioInitTiming::POLL_MS);
let cmd55 = crate::cmd::cmd55(0);
self.host.submit_command(&cmd55)?;
request.state = SdioInitState::PollAcmd41Cmd55;
request.needs_pace = true;
}
Ok(OperationPoll::Pending)
}
Ok(CommandResponsePoll::Complete(_)) => {
info!("sdio: ACMD41 returned bad response, trying MMC CMD1");
self.host.submit_command(&crate::cmd::cmd1(0))?;
request.state = SdioInitState::PollMmcInitial;
Ok(OperationPoll::Pending)
}
Err(_sd_err) => {
info!("sdio: ACMD41 failed ({:?}), trying MMC CMD1", _sd_err);
self.host.submit_command(&crate::cmd::cmd1(0))?;
request.state = SdioInitState::PollMmcInitial;
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollMmcInitial => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(Response::R3(ocr)) => {
if ocr.card_powered_up() {
request.kind = Some(CardKind::Mmc);
request.ocr = Some(ocr);
self.kind = CardKind::Mmc;
info!("sdio: detected {:?} ocr={:#010x}", CardKind::Mmc, ocr.raw);
self.host.submit_command(&crate::cmd::CMD2)?;
request.state = SdioInitState::PollCmd2;
} else {
let voltage = ocr.raw & MMC_VOLTAGE_MASK;
let voltage = if voltage == 0 {
MMC_VOLTAGE_MASK
} else {
voltage
};
request.mmc_ocr_arg = MMC_HCS | voltage | (ocr.raw & MMC_ACCESS_MODE_MASK);
let cmd = crate::cmd::cmd1(request.mmc_ocr_arg);
self.host.submit_command(&cmd)?;
request.state = SdioInitState::PollMmcReady;
}
Ok(OperationPoll::Pending)
}
CommandResponsePoll::Complete(_) => {
Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 1)))
}
},
SdioInitState::PollMmcReady => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(Response::R3(ocr)) => {
if ocr.card_powered_up() {
request.kind = Some(CardKind::Mmc);
request.ocr = Some(ocr);
self.kind = CardKind::Mmc;
info!("sdio: detected {:?} ocr={:#010x}", CardKind::Mmc, ocr.raw);
self.host.submit_command(&crate::cmd::CMD2)?;
request.state = SdioInitState::PollCmd2;
} else {
if request.mmc_elapsed_ms >= SdioInitTiming::TIMEOUT_MS {
warn!("sdio: CMD1 timed out after {}ms", request.mmc_elapsed_ms);
return Err(Error::Timeout(ErrorContext::for_cmd(Phase::Init, 1)));
}
request.mmc_elapsed_ms = request
.mmc_elapsed_ms
.saturating_add(SdioInitTiming::POLL_MS);
let cmd = crate::cmd::cmd1(request.mmc_ocr_arg);
self.host.submit_command(&cmd)?;
request.needs_pace = true;
}
Ok(OperationPoll::Pending)
}
CommandResponsePoll::Complete(_) => {
Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 1)))
}
},
SdioInitState::PollCmd2 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(response) => {
if let Response::R2(raw) = response {
request.cid = Some(CidResponse::from_raw(raw));
} else {
request.cid = None;
}
match request.kind.ok_or(Error::InvalidArgument)? {
CardKind::Sd => self.host.submit_command(&crate::cmd::CMD3_SD)?,
CardKind::Mmc => self.host.submit_command(&crate::cmd::cmd3_mmc(1))?,
}
request.state = SdioInitState::PollCmd3;
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollCmd3 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(response) => {
self.rca = match (request.kind.ok_or(Error::InvalidArgument)?, response) {
(CardKind::Sd, Response::R6(resp)) => resp.rca(),
(CardKind::Mmc, Response::R1(_)) => 1,
_ => {
return Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 3)));
}
};
info!("sdio: CMD3 rca={:#x}", self.rca);
let cmd9 = crate::cmd::cmd9(self.rca);
self.host.submit_command(&cmd9)?;
request.state = SdioInitState::PollCmd9;
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollCmd9 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(response) => {
request.capacity_blocks = match response {
Response::R2(raw) => CsdResponse::from_raw(raw).capacity_blocks(),
_ => None,
};
info!("sdio: CSD capacity_blocks={:?}", request.capacity_blocks);
let cmd7 = crate::cmd::cmd7(self.rca);
self.host.submit_command(&cmd7)?;
request.state = SdioInitState::PollCmd7;
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollCmd7 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(_) => {
let ocr = request.ocr.ok_or(Error::InvalidArgument)?;
self.high_capacity = ocr.ccs();
match request.kind.ok_or(Error::InvalidArgument)? {
CardKind::Sd => {
info!("sdio: switch SD bus width to 4-bit");
let cmd55 = crate::cmd::cmd55(self.rca);
self.host.submit_command(&cmd55)?;
request.state = SdioInitState::PollSdBusWidthCmd55;
}
CardKind::Mmc => {
request.state = SdioInitState::FinishCardSetup;
}
}
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollSdBusWidthCmd55 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(_) => {
let acmd6 = Command::new(6, sd_acmd6_arg(BusWidth::Bit4)?, ResponseType::R1);
self.host.submit_command(&acmd6)?;
request.state = SdioInitState::PollSdBusWidthAcmd6;
Ok(OperationPoll::Pending)
}
},
SdioInitState::PollSdBusWidthAcmd6 => match self.host.poll_command_response()? {
CommandResponsePoll::Pending => Ok(OperationPoll::Pending),
CommandResponsePoll::Complete(_) => {
self.host.set_bus_width(BusWidth::Bit4)?;
self.bus_width = BusWidth::Bit4;
request.state = SdioInitState::FinishCardSetup;
Ok(OperationPoll::Pending)
}
},
SdioInitState::FinishCardSetup => {
let kind = request.kind.ok_or(Error::InvalidArgument)?;
match kind {
CardKind::Sd => {
if self.sd_speed_selection_enabled {
request.state = SdioInitState::PrepareSdSpeed;
} else {
self.host.set_clock(ClockSpeed::Default)?;
info!("sdio: SD speed selection disabled; staying at default speed");
request.state = SdioInitState::Complete;
}
Ok(OperationPoll::Pending)
}
CardKind::Mmc => {
info!("sdio: read MMC EXT_CSD");
let ext_csd = unsafe { request.ext_csd_buf.as_mut() };
request.ext_csd_request = Some(self.submit_read_ext_csd(ext_csd)?);
request.state = SdioInitState::PollMmcExtCsd;
Ok(OperationPoll::Pending)
}
}
}
SdioInitState::PollMmcExtCsd => {
let ext_request = request
.ext_csd_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_ext_csd_request(ext_request)? {
OperationPoll::Pending => Ok(OperationPoll::Pending),
OperationPoll::Complete(()) => {
request.ext_csd_request = None;
let csd = crate::ext_csd::ExtCsd::from_bytes(unsafe {
*request.ext_csd_buf.as_ref()
});
if let Some(sectors) = csd.sector_count() {
request.capacity_blocks = Some(sectors as u64);
info!("sdio: EXT_CSD sector_count={}", sectors);
}
request.parsed_ext_csd = Some(csd);
submit_mmc_bus_width_or_continue(self, request, BusWidth::Bit8)
}
}
}
SdioInitState::PollMmcBusWidth => {
let switch_request = request
.mmc_switch_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_mmc_switch_request(switch_request) {
Ok(OperationPoll::Pending) => Ok(OperationPoll::Pending),
Ok(OperationPoll::Complete(())) => {
request.mmc_switch_request = None;
match self.host.set_bus_width(request.current_bus_width) {
Ok(()) => {
self.bus_width = request.current_bus_width;
request.state = SdioInitState::PrepareMmcSpeed;
Ok(OperationPoll::Pending)
}
Err(err) if matches!(request.current_bus_width, BusWidth::Bit8) => {
info!("sdio: 8-bit refused ({:?}), trying 4-bit", err);
submit_mmc_bus_width_or_continue(self, request, BusWidth::Bit4)
}
Err(err) if matches!(request.current_bus_width, BusWidth::Bit4) => {
info!("sdio: 4-bit refused ({:?}), staying at 1-bit", err);
request.state = SdioInitState::PrepareMmcSpeed;
Ok(OperationPoll::Pending)
}
Err(err) => Err(err),
}
}
Err(err) if matches!(request.current_bus_width, BusWidth::Bit8) => {
request.mmc_switch_request = None;
info!("sdio: 8-bit refused ({:?}), trying 4-bit", err);
submit_mmc_bus_width_or_continue(self, request, BusWidth::Bit4)
}
Err(err) if matches!(request.current_bus_width, BusWidth::Bit4) => {
request.mmc_switch_request = None;
info!("sdio: 4-bit refused ({:?}), staying at 1-bit", err);
request.state = SdioInitState::PrepareMmcSpeed;
Ok(OperationPoll::Pending)
}
Err(err) => Err(err),
}
}
SdioInitState::PrepareMmcSpeed => {
let Some(csd) = request.parsed_ext_csd.as_ref() else {
return Err(Error::InvalidArgument);
};
let dt = csd.device_type();
if !request.mmc_hs200_attempted
&& !matches!(self.bus_width, BusWidth::Bit1)
&& dt.supports_hs200()
{
request.mmc_hs200_attempted = true;
match self.host.switch_voltage(SignalVoltage::V180) {
Ok(()) | Err(Error::UnsupportedCommand) => {
let switch_request = self.submit_mmc_switch(
0b11,
crate::cmd::ext_csd::HS_TIMING as u8,
0x02,
)?;
request.mmc_switch_request = Some(switch_request);
request.state = SdioInitState::PollMmcHs200Switch;
return Ok(OperationPoll::Pending);
}
Err(err) => debug!("sdio: switch_voltage(V180) failed ({:?})", err),
}
self.rollback_to_hs_compat();
}
if dt.supports_hs_52() {
let switch_request =
self.submit_mmc_switch(0b11, crate::cmd::ext_csd::HS_TIMING as u8, 1)?;
request.mmc_switch_request = Some(switch_request);
request.state = SdioInitState::PollMmcHs52Switch;
} else {
request.state = SdioInitState::Complete;
}
Ok(OperationPoll::Pending)
}
SdioInitState::PollMmcHs200Switch => {
let switch_request = request
.mmc_switch_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_mmc_switch_request(switch_request) {
Ok(OperationPoll::Pending) => Ok(OperationPoll::Pending),
Ok(OperationPoll::Complete(())) => {
request.mmc_switch_request = None;
if self.host.set_clock(ClockSpeed::Hs200).is_ok()
&& self.host.execute_tuning(21).is_ok()
{
let status_request = self.submit_status()?;
request.status_request = Some(status_request);
request.state = SdioInitState::PollMmcHs200Status;
} else {
self.rollback_to_hs_compat();
request.state = SdioInitState::PrepareMmcSpeed;
}
Ok(OperationPoll::Pending)
}
Err(err) => {
request.mmc_switch_request = None;
debug!("sdio: MMC HS200 switch refused ({:?})", err);
self.rollback_to_hs_compat();
request.state = SdioInitState::PrepareMmcSpeed;
Ok(OperationPoll::Pending)
}
}
}
SdioInitState::PollMmcHs200Status => {
let status_request = request
.status_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_status_request(status_request)? {
OperationPoll::Pending => Ok(OperationPoll::Pending),
OperationPoll::Complete(CardState::Transfer) => {
request.status_request = None;
info!("sdio: HS200 entry succeeded");
request.state = SdioInitState::Complete;
Ok(OperationPoll::Pending)
}
OperationPoll::Complete(_) => {
request.status_request = None;
self.rollback_to_hs_compat();
request.state = SdioInitState::PrepareMmcSpeed;
Ok(OperationPoll::Pending)
}
}
}
SdioInitState::PollMmcHs52Switch => {
let switch_request = request
.mmc_switch_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_mmc_switch_request(switch_request) {
Ok(OperationPoll::Pending) => Ok(OperationPoll::Pending),
Ok(OperationPoll::Complete(())) => {
request.mmc_switch_request = None;
if let Err(_e) = self.host.set_clock(ClockSpeed::HighSpeed) {
info!("sdio: host refused HighSpeed clock ({:?})", _e);
}
request.state = SdioInitState::Complete;
Ok(OperationPoll::Pending)
}
Err(_e) => {
request.mmc_switch_request = None;
info!("sdio: MMC HS_TIMING switch refused ({:?})", _e);
request.state = SdioInitState::Complete;
Ok(OperationPoll::Pending)
}
}
}
SdioInitState::PrepareSdSpeed => {
let buf = unsafe { request.switch_status_buf.as_mut() };
let switch_request =
self.submit_switch_function(&crate::cmd::cmd6_sd_access_mode(false, 0), buf)?;
request.switch_function_request = Some(switch_request);
request.state = SdioInitState::PollSdSwitchFunctionCheck;
Ok(OperationPoll::Pending)
}
SdioInitState::PollSdSwitchFunctionCheck => {
let switch_request = request
.switch_function_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_switch_function_request(switch_request) {
Ok(OperationPoll::Pending) => Ok(OperationPoll::Pending),
Ok(OperationPoll::Complete(())) => {
request.switch_function_request = None;
let status =
SwitchStatus::from_raw(unsafe { *request.switch_status_buf.as_ref() });
info!(
"sdio: SD access mode support hs={} sdr50={} sdr104={} ddr50={} \
s18a={}",
status.access_mode_supported(SdAccessMode::HighSpeed.function()),
status.access_mode_supported(SdAccessMode::Sdr50.function()),
status.access_mode_supported(SdAccessMode::Sdr104.function()),
status.access_mode_supported(SdAccessMode::Ddr50.function()),
request.ocr.ok_or(Error::InvalidArgument)?.s18a()
);
request.sd_access_index = 0;
submit_next_sd_access_mode(self, request, status)
}
Err(err) => {
request.switch_function_request = None;
warn!("sdio: SD speed selection skipped ({:?})", err);
request.state = SdioInitState::Complete;
Ok(OperationPoll::Pending)
}
}
}
SdioInitState::PollSdVoltageSwitch => {
let cmd = request
.command_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
let mode = request.current_access_mode.ok_or(Error::InvalidArgument)?;
match self.poll_command_request(cmd) {
Ok(CommandResponsePoll::Pending) => Ok(OperationPoll::Pending),
Ok(CommandResponsePoll::Complete(_)) => {
request.command_request = None;
match self.host.switch_voltage(SignalVoltage::V180) {
Ok(()) => submit_sd_access_mode_switch(self, request, mode),
Err(err) => {
warn!("sdio: SD {} failed ({:?})", mode.name(), err);
let status = SwitchStatus::from_raw(unsafe {
*request.switch_status_buf.as_ref()
});
submit_next_sd_access_mode(self, request, status)
}
}
}
Err(err) => {
request.command_request = None;
warn!("sdio: SD {} failed ({:?})", mode.name(), err);
let status =
SwitchStatus::from_raw(unsafe { *request.switch_status_buf.as_ref() });
submit_next_sd_access_mode(self, request, status)
}
}
}
SdioInitState::PollSdSetAccessMode => {
let mode = request.current_access_mode.ok_or(Error::InvalidArgument)?;
let switch_request = request
.switch_function_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_switch_function_request(switch_request) {
Ok(OperationPoll::Pending) => Ok(OperationPoll::Pending),
Ok(OperationPoll::Complete(())) => {
request.switch_function_request = None;
let status =
SwitchStatus::from_raw(unsafe { *request.switch_status_buf.as_ref() });
if status.selected_function(1) != mode.function() {
warn!("sdio: SD {} failed (function mismatch)", mode.name());
submit_next_sd_access_mode(self, request, status)
} else {
self.host.set_clock(mode.clock())?;
if matches!(mode, SdAccessMode::Sdr50 | SdAccessMode::Sdr104) {
self.host.execute_tuning(19)?;
}
let status_request = self.submit_status()?;
request.status_request = Some(status_request);
request.state = SdioInitState::PollSdStatus;
Ok(OperationPoll::Pending)
}
}
Err(err) => {
request.switch_function_request = None;
warn!("sdio: SD {} failed ({:?})", mode.name(), err);
let status =
SwitchStatus::from_raw(unsafe { *request.switch_status_buf.as_ref() });
submit_next_sd_access_mode(self, request, status)
}
}
}
SdioInitState::PollSdStatus => {
let mode = request.current_access_mode.ok_or(Error::InvalidArgument)?;
let status_request = request
.status_request
.as_mut()
.ok_or(Error::InvalidArgument)?;
match self.poll_status_request(status_request)? {
OperationPoll::Pending => Ok(OperationPoll::Pending),
OperationPoll::Complete(CardState::Transfer) => {
request.status_request = None;
info!("sdio: SD speed selected {:?}", mode.clock());
request.state = SdioInitState::Complete;
Ok(OperationPoll::Pending)
}
OperationPoll::Complete(_) => {
request.status_request = None;
warn!("sdio: SD {} failed (bad status)", mode.name());
let status =
SwitchStatus::from_raw(unsafe { *request.switch_status_buf.as_ref() });
submit_next_sd_access_mode(self, request, status)
}
}
}
SdioInitState::Complete => {
let kind = request.kind.ok_or(Error::InvalidArgument)?;
let ocr = request.ocr.ok_or(Error::InvalidArgument)?;
info!(
"sdio: init done kind={:?} sd_v2={} high_capacity={} rca={:#x} ocr={:#x}",
kind, request.sd_v2, self.high_capacity, self.rca, ocr.raw
);
Ok(OperationPoll::Complete(CardInfo {
kind,
sd_v2: request.sd_v2,
high_capacity: self.high_capacity,
ocr: ocr.raw,
rca: self.rca,
capacity_blocks: request.capacity_blocks,
cid: request.cid,
ext_csd: request.parsed_ext_csd.take(),
}))
}
}
}
fn rollback_to_hs_compat(&mut self) {
let _ = self.host.set_clock(ClockSpeed::Default);
}
}
fn submit_mmc_bus_width_or_continue<'a, H: SdioHost + 'a>(
driver: &mut SdioSdmmc<H>,
request: &mut SdioInitRequest<'a, H>,
width: BusWidth,
) -> Result<OperationPoll<CardInfo>, Error> {
let value: u8 = match width {
BusWidth::Bit1 => 0,
BusWidth::Bit4 => 1,
BusWidth::Bit8 => 2,
};
request.current_bus_width = width;
request.mmc_switch_request =
Some(driver.submit_mmc_switch(0b11, crate::cmd::ext_csd::BUS_WIDTH as u8, value)?);
request.state = SdioInitState::PollMmcBusWidth;
Ok(OperationPoll::Pending)
}
fn submit_next_sd_access_mode<'a, H: SdioHost + 'a>(
driver: &mut SdioSdmmc<H>,
request: &mut SdioInitRequest<'a, H>,
status: SwitchStatus,
) -> Result<OperationPoll<CardInfo>, Error> {
let ocr = request.ocr.ok_or(Error::InvalidArgument)?;
let candidates = if ocr.s18a() {
&[
SdAccessMode::Sdr104,
SdAccessMode::Sdr50,
SdAccessMode::Ddr50,
SdAccessMode::HighSpeed,
][..]
} else {
&[SdAccessMode::HighSpeed][..]
};
while request.sd_access_index < candidates.len() {
let mode = candidates[request.sd_access_index];
request.sd_access_index += 1;
if !status.access_mode_supported(mode.function()) {
continue;
}
if matches!(mode, SdAccessMode::HighSpeed) {
info!("sdio: trying SD HighSpeed");
} else {
info!("sdio: trying SD {}", mode.name());
}
return submit_sd_access_mode(driver, request, mode);
}
info!("sdio: SD card stayed at default speed");
request.state = SdioInitState::Complete;
Ok(OperationPoll::Pending)
}
fn submit_sd_access_mode<'a, H: SdioHost + 'a>(
driver: &mut SdioSdmmc<H>,
request: &mut SdioInitRequest<'a, H>,
mode: SdAccessMode,
) -> Result<OperationPoll<CardInfo>, Error> {
request.current_access_mode = Some(mode);
if !matches!(mode, SdAccessMode::HighSpeed) && request.ocr.ok_or(Error::InvalidArgument)?.s18a()
{
let cmd = crate::cmd::CMD11;
request.command_request = Some(driver.submit_command_request(&cmd)?);
request.state = SdioInitState::PollSdVoltageSwitch;
return Ok(OperationPoll::Pending);
}
submit_sd_access_mode_switch(driver, request, mode)
}
fn submit_sd_access_mode_switch<'a, H: SdioHost + 'a>(
driver: &mut SdioSdmmc<H>,
request: &mut SdioInitRequest<'a, H>,
mode: SdAccessMode,
) -> Result<OperationPoll<CardInfo>, Error> {
let buf = unsafe { request.switch_status_buf.as_mut() };
request.switch_function_request = Some(
driver
.submit_switch_function(&crate::cmd::cmd6_sd_access_mode(true, mode.function()), buf)?,
);
request.state = SdioInitState::PollSdSetAccessMode;
Ok(OperationPoll::Pending)
}
fn block_count_from_len(len: usize) -> Result<u32, Error> {
if len == 0 || !len.is_multiple_of(512) {
return Err(Error::Misaligned);
}
u32::try_from(len / 512).map_err(|_| Error::InvalidArgument)
}
fn sd_acmd6_arg(width: BusWidth) -> Result<u32, Error> {
match width {
BusWidth::Bit1 => Ok(0),
BusWidth::Bit4 => Ok(2),
BusWidth::Bit8 => Err(Error::UnsupportedCommand),
}
}
#[derive(Debug, Clone)]
pub struct CardInfo {
pub kind: CardKind,
pub sd_v2: bool,
pub high_capacity: bool,
pub ocr: u32,
pub rca: u16,
pub capacity_blocks: Option<u64>,
pub cid: Option<CidResponse>,
pub ext_csd: Option<crate::ext_csd::ExtCsd>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CardKind {
Sd,
Mmc,
}
#[cfg(test)]
mod tests {
extern crate std;
use std::vec::Vec;
use super::*;
use crate::response::{IfCondResponse, OcrResponse, R1Response, RcaResponse};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MockEvent {
Command(Command),
Clock(ClockSpeed),
}
struct MockHost {
replies: Vec<Result<Response, Error>>,
commands: Vec<Command>,
events: Vec<MockEvent>,
bus_width: Option<BusWidth>,
data_requests: Vec<(DataDirection, u32, u32)>,
next_read_payload: Option<Vec<u8>>,
read_payloads: Vec<Vec<u8>>,
writes: Vec<Vec<u8>>,
reject_bit8: bool,
last_clock: Option<ClockSpeed>,
last_voltage: Option<SignalVoltage>,
voltage_switch_result: Option<Error>,
tuning_result: Option<Error>,
last_tuning_cmd: Option<u8>,
pending_polls: usize,
}
struct MockDataRequest<'a> {
response: Option<Response>,
_marker: core::marker::PhantomData<&'a ()>,
}
impl MockHost {
fn new(replies: Vec<Response>) -> Self {
Self {
replies: replies.into_iter().map(Ok).collect(),
commands: Vec::new(),
events: Vec::new(),
bus_width: None,
data_requests: Vec::new(),
next_read_payload: None,
read_payloads: Vec::new(),
writes: Vec::new(),
reject_bit8: false,
last_clock: None,
last_voltage: None,
voltage_switch_result: None,
tuning_result: None,
last_tuning_cmd: None,
pending_polls: 0,
}
}
fn with_results(replies: Vec<Result<Response, Error>>) -> Self {
Self {
replies,
commands: Vec::new(),
events: Vec::new(),
bus_width: None,
data_requests: Vec::new(),
next_read_payload: None,
read_payloads: Vec::new(),
writes: Vec::new(),
reject_bit8: false,
last_clock: None,
last_voltage: None,
voltage_switch_result: None,
tuning_result: None,
last_tuning_cmd: None,
pending_polls: 0,
}
}
}
impl SdioHost for MockHost {
type Event = ();
type DataRequest<'a> = MockDataRequest<'a>;
fn submit_command(&mut self, cmd: &Command) -> Result<(), Error> {
self.commands.push(*cmd);
self.events.push(MockEvent::Command(*cmd));
Ok(())
}
fn poll_command_response(&mut self) -> Result<CommandResponsePoll, Error> {
if self.pending_polls > 0 {
self.pending_polls -= 1;
return Ok(CommandResponsePoll::Pending);
}
if self.replies.is_empty() {
return Err(Error::Timeout(ErrorContext::default()));
}
self.replies.remove(0).map(CommandResponsePoll::Complete)
}
fn submit_read_data<'a>(
&mut self,
cmd: &Command,
buf: &'a mut [u8],
block_size: u32,
block_count: u32,
) -> Result<Self::DataRequest<'a>, Error> {
self.data_requests
.push((DataDirection::Read, block_size, block_count));
self.submit_command(cmd)?;
let CommandResponsePoll::Complete(response) = self.poll_command_response()? else {
return Err(Error::Timeout(ErrorContext::default()));
};
let payload = if self.read_payloads.is_empty() {
self.next_read_payload.take()
} else {
Some(self.read_payloads.remove(0))
};
match payload {
Some(data) if data.len() == buf.len() => {
buf.copy_from_slice(&data);
Ok(MockDataRequest {
response: Some(response),
_marker: core::marker::PhantomData,
})
}
_ => Err(Error::UnsupportedCommand),
}
}
fn submit_write_data<'a>(
&mut self,
cmd: &Command,
buf: &'a [u8],
block_size: u32,
block_count: u32,
) -> Result<Self::DataRequest<'a>, Error> {
self.data_requests
.push((DataDirection::Write, block_size, block_count));
self.submit_command(cmd)?;
let CommandResponsePoll::Complete(response) = self.poll_command_response()? else {
return Err(Error::Timeout(ErrorContext::default()));
};
self.writes.push(buf.to_vec());
Ok(MockDataRequest {
response: Some(response),
_marker: core::marker::PhantomData,
})
}
fn poll_data_request<'a>(
&mut self,
request: &mut Self::DataRequest<'a>,
) -> Result<DataCommandPoll, Error> {
request
.response
.take()
.map(DataCommandPoll::Complete)
.ok_or(Error::InvalidArgument)
}
fn set_bus_width(&mut self, width: BusWidth) -> Result<(), Error> {
if self.reject_bit8 && matches!(width, BusWidth::Bit8) {
return Err(Error::UnsupportedCommand);
}
self.bus_width = Some(width);
Ok(())
}
fn set_clock(&mut self, speed: ClockSpeed) -> Result<(), Error> {
self.last_clock = Some(speed);
self.events.push(MockEvent::Clock(speed));
Ok(())
}
fn switch_voltage(&mut self, v: SignalVoltage) -> Result<(), Error> {
self.last_voltage = Some(v);
if let Some(e) = self.voltage_switch_result {
return Err(e);
}
Ok(())
}
fn execute_tuning(&mut self, cmd_index: u8) -> Result<(), Error> {
self.last_tuning_cmd = Some(cmd_index);
if let Some(e) = self.tuning_result {
return Err(e);
}
Ok(())
}
}
#[test]
fn sdio_host_irq_methods_default_to_noop() {
let mut host = MockHost::new(Vec::new());
assert_eq!(host.enable_completion_irq(), Ok(()));
assert_eq!(host.disable_completion_irq(), Ok(()));
assert_eq!(host.handle_irq(), ());
}
#[test]
fn unit_irq_event_reports_no_runtime_action() {
let event = ();
assert_eq!(event.kind(), HostEventKind::None);
assert_eq!(event.source(), HostEventSource::Controller);
assert_eq!(event.queue_id(), None);
}
fn ok_r1() -> Response {
Response::R1(R1Response::from_native_raw(0).unwrap())
}
fn rca_response(rca: u16) -> Response {
Response::R6(RcaResponse::from_raw((rca as u32) << 16))
}
fn ocr_ready_sdhc() -> Response {
Response::R3(OcrResponse::from_raw(0xC0FF_8000))
}
fn ocr_ready_sdhc_s18a() -> Response {
Response::R3(OcrResponse::from_raw(0xC1FF_8000))
}
fn csd_v2_response() -> Response {
let mut raw = [0u8; 16];
raw[0] = 0x40;
raw[7] = 0x00;
raw[8] = 0x0F;
raw[9] = 0x0F;
Response::R2(raw)
}
fn cid_response() -> Response {
let mut raw = [0u8; 16];
raw[0] = 0x03;
raw[1] = b'S';
raw[2] = b'D';
raw[3] = b'A';
raw[4] = b'B';
raw[5] = b'C';
raw[6] = b'1';
raw[7] = b'2';
Response::R2(raw)
}
fn sd_init_replies() -> Vec<Result<Response, Error>> {
sd_init_replies_with_ocr(ocr_ready_sdhc())
}
fn disable_speed_selection(driver: &mut SdioSdmmc<MockHost>) {
driver.set_sd_speed_selection_enabled(false);
}
fn sd_init_replies_with_ocr(ocr: Response) -> Vec<Result<Response, Error>> {
std::vec![
Ok(ok_r1()), Ok(Response::R7(IfCondResponse::from_raw(0x0000_01AA))), Ok(ok_r1()), Ok(ocr), Ok(cid_response()), Ok(rca_response(0x1234)), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), ]
}
fn switch_status_payload(function: u8, supported: u8) -> Vec<u8> {
let mut status = std::vec![0u8; 64];
status[13] = supported;
status[16] = function & 0x0f;
status
}
fn poll_init_to_completion<H: SdioHost>(driver: &mut SdioSdmmc<H>) -> Result<CardInfo, Error> {
poll_init_to_completion_with_preference(driver, CardInitPreference::SdFirst)
}
fn poll_init_to_completion_with_preference<H: SdioHost>(
driver: &mut SdioSdmmc<H>,
preference: CardInitPreference,
) -> Result<CardInfo, Error> {
let mut scratch = SdioInitScratch::new();
let mut request = driver.submit_init_with_preference(preference, &mut scratch)?;
loop {
match driver.poll_init_request(&mut request)? {
OperationPoll::Pending => {}
OperationPoll::Complete(info) => return Ok(info),
}
}
}
#[test]
fn init_records_rca_in_driver_state() {
let replies = sd_init_replies();
let host = MockHost::with_results(replies);
let mut driver = SdioSdmmc::new(host);
disable_speed_selection(&mut driver);
let info = poll_init_to_completion(&mut driver).unwrap();
assert_eq!(info.rca, 0x1234);
assert_eq!(driver.rca(), 0x1234);
assert!(info.high_capacity);
assert_eq!(info.kind, CardKind::Sd);
assert_eq!(info.capacity_blocks, Some((0x0F0F + 1) * 1024));
let cid = info.cid.expect("CID captured in init");
assert_eq!(cid.manufacturer_id(), 0x03);
assert_eq!(&cid.product_name(), b"ABC12");
assert_eq!(driver.host.bus_width, Some(BusWidth::Bit4));
let cmd7 = driver
.host
.commands
.iter()
.find(|c| c.cmd == 7)
.expect("CMD7 issued");
assert_eq!(cmd7.arg, (0x1234u32) << 16);
}
#[test]
fn submit_init_starts_request_without_spinning_past_pending_cmd0() {
let mut host = MockHost::with_results(std::vec![Ok(ok_r1())]);
host.pending_polls = 1;
let mut driver = SdioSdmmc::new(host);
let mut scratch = SdioInitScratch::new();
let mut request = driver.submit_init(&mut scratch).unwrap();
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![0]
);
assert!(matches!(
driver.poll_init_request(&mut request).unwrap(),
OperationPoll::Pending
));
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![0]
);
}
#[test]
fn poll_init_request_returns_after_submitting_next_command() {
let mut driver = SdioSdmmc::new(MockHost::with_results(std::vec![
Ok(ok_r1()), Ok(Response::R7(IfCondResponse::from_raw(0x0000_01AA))), ]));
let mut scratch = SdioInitScratch::new();
let mut request = driver.submit_init(&mut scratch).unwrap();
assert!(matches!(
driver.poll_init_request(&mut request).unwrap(),
OperationPoll::Pending
));
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![0, 8]
);
assert!(matches!(
driver.poll_init_request(&mut request).unwrap(),
OperationPoll::Pending
));
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![0, 8, 55]
);
}
#[test]
fn poll_init_request_falls_back_to_cmd1_after_acmd41_not_ready_timeout() {
let mut driver = SdioSdmmc::new(MockHost::with_results(std::vec![
Ok(Response::R3(OcrResponse::from_raw(0x00FF_8000))),
Ok(ok_r1()),
]));
let mut scratch = SdioInitScratch::new();
let mut request = SdioInitRequest::new(CardInitPreference::SdFirst, &mut scratch);
request.state = SdioInitState::PollAcmd41;
request.sd_v2 = false;
request.acmd41_elapsed_ms = SdioInitTiming::TIMEOUT_MS;
assert!(matches!(
driver.poll_init_request(&mut request).unwrap(),
OperationPoll::Pending
));
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![1]
);
}
#[test]
fn submit_init_with_mmc_preference_skips_sd_probe_after_cmd0() {
let mut driver = SdioSdmmc::new(MockHost::with_results(std::vec![Ok(ok_r1())]));
let mut scratch = SdioInitScratch::new();
let mut request = driver
.submit_init_with_preference(CardInitPreference::MmcFirst, &mut scratch)
.unwrap();
assert!(matches!(
driver.poll_init_request(&mut request).unwrap(),
OperationPoll::Pending
));
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![0, 1]
);
}
#[test]
fn submit_mmc_switch_returns_before_polling_status() {
let mut driver = SdioSdmmc::new(MockHost::with_results(std::vec![
Ok(ok_r1()), Ok(r1_tran_ready()), ]));
driver.rca = 1;
let mut request = driver
.submit_mmc_switch(0b11, crate::cmd::ext_csd::HS_TIMING as u8, 1)
.unwrap();
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![6]
);
assert!(matches!(
driver.poll_mmc_switch_request(&mut request).unwrap(),
OperationPoll::Pending
));
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![6, 13]
);
assert!(matches!(
driver.poll_mmc_switch_request(&mut request).unwrap(),
OperationPoll::Complete(())
));
}
#[test]
fn submit_status_returns_before_polling_cmd13_response() {
let mut driver = SdioSdmmc::new(MockHost::with_results(std::vec![Ok(r1_tran_ready())]));
driver.rca = 0x1234;
let mut request = driver.submit_status().unwrap();
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![13]
);
assert_eq!(driver.host.commands[0].arg, 0x1234 << 16);
assert!(matches!(
driver.poll_status_request(&mut request).unwrap(),
OperationPoll::Complete(CardState::Transfer)
));
}
#[test]
fn submit_read_ext_csd_uses_caller_buffer_and_poll_completion() {
let mut host = MockHost::new(std::vec![ok_r1()]);
let payload = ext_csd_blob();
host.next_read_payload = Some(payload.clone());
let mut driver = SdioSdmmc::new(host);
let mut buf = [0u8; 512];
let mut request = driver.submit_read_ext_csd(&mut buf).unwrap();
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![8]
);
assert!(matches!(
driver.poll_ext_csd_request(&mut request).unwrap(),
OperationPoll::Complete(())
));
drop(request);
assert_eq!(&buf[..], payload.as_slice());
}
#[test]
fn submit_switch_function_uses_caller_buffer_and_poll_completion() {
let mut host = MockHost::new(std::vec![ok_r1()]);
let payload = switch_status_payload(1, 1 << 1);
host.next_read_payload = Some(payload.clone());
let mut driver = SdioSdmmc::new(host);
let mut buf = [0u8; 64];
let mut request = driver
.submit_switch_function(&crate::cmd::cmd6_high_speed(true), &mut buf)
.unwrap();
assert_eq!(
driver
.host
.commands
.iter()
.map(|cmd| cmd.cmd)
.collect::<Vec<_>>(),
std::vec![6]
);
assert!(matches!(
driver.poll_switch_function_request(&mut request).unwrap(),
OperationPoll::Complete(())
));
drop(request);
assert_eq!(&buf[..], payload.as_slice());
}
#[test]
fn poll_init_request_skips_pace_hint_on_ready_path() {
let replies = sd_init_replies();
let host = MockHost::with_results(replies);
let mut driver = SdioSdmmc::new(host);
disable_speed_selection(&mut driver);
let mut scratch = SdioInitScratch::new();
let mut request = driver.submit_init(&mut scratch).unwrap();
let mut pace_hints = 0;
let info = loop {
match driver.poll_init_request(&mut request).unwrap() {
OperationPoll::Pending => {
if request.take_needs_pace() {
pace_hints += 1;
}
}
OperationPoll::Complete(info) => break info,
}
};
assert_eq!(info.rca, 0x1234);
assert_eq!(pace_hints, 0);
}
#[test]
fn poll_init_request_sets_pace_hint_for_power_up_retry() {
let replies = std::vec![
Ok(ok_r1()), Ok(Response::R7(IfCondResponse::from_raw(0x0000_01AA))), Ok(ok_r1()), Ok(Response::R3(OcrResponse::from_raw(0x00FF_8000))), Ok(ok_r1()), Ok(ocr_ready_sdhc()), Ok(cid_response()), Ok(rca_response(0x1234)), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), ];
let host = MockHost::with_results(replies);
let mut driver = SdioSdmmc::new(host);
disable_speed_selection(&mut driver);
let mut scratch = SdioInitScratch::new();
let mut request = driver.submit_init(&mut scratch).unwrap();
let mut power_up_retries = 0;
let info = loop {
match driver.poll_init_request(&mut request).unwrap() {
OperationPoll::Pending => {
if request.take_needs_pace() {
power_up_retries += 1;
}
}
OperationPoll::Complete(info) => break info,
}
};
assert_eq!(info.rca, 0x1234);
assert_eq!(power_up_retries, 1);
}
#[test]
fn sd_init_automatically_selects_sdr104_when_card_and_host_agree() {
let mut replies = sd_init_replies_with_ocr(ocr_ready_sdhc_s18a());
replies.extend([
Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), Ok(r1_tran_ready()), ]);
let mut host = MockHost::with_results(replies);
host.read_payloads = std::vec![
switch_status_payload(0, 1 << 3),
switch_status_payload(3, 1 << 3),
];
let mut driver = SdioSdmmc::new(host);
poll_init_to_completion(&mut driver).expect("SD init succeeds with SDR104");
assert_eq!(driver.host.last_voltage, Some(SignalVoltage::V180));
assert_eq!(driver.host.last_clock, Some(ClockSpeed::Sdr104));
assert_eq!(driver.host.last_tuning_cmd, Some(19));
assert!(
driver.host.commands.iter().any(|c| c.cmd == 11),
"CMD11 issued before host voltage switch"
);
assert!(
driver
.host
.commands
.iter()
.any(|c| c.cmd == 6 && c.arg == 0x80FF_FFF3),
"CMD6 switched group 1 to SDR104"
);
}
#[test]
fn sd_init_falls_back_to_high_speed_when_uhs_voltage_switch_fails() {
let mut replies = sd_init_replies_with_ocr(ocr_ready_sdhc_s18a());
replies.extend([
Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), Ok(r1_tran_ready()), ]);
let mut host = MockHost::with_results(replies);
host.read_payloads = std::vec![
switch_status_payload(0, (1 << 3) | (1 << 1)),
switch_status_payload(1, 1 << 1),
];
host.voltage_switch_result = Some(Error::UnsupportedCommand);
let mut driver = SdioSdmmc::new(host);
poll_init_to_completion(&mut driver)
.expect("SD init falls back when UHS voltage switch fails");
assert_eq!(driver.host.last_voltage, Some(SignalVoltage::V180));
assert_eq!(driver.host.last_clock, Some(ClockSpeed::HighSpeed));
assert_eq!(driver.host.last_tuning_cmd, None);
assert!(
driver
.host
.commands
.iter()
.any(|c| c.cmd == 6 && c.arg == 0x80FF_FFF1),
"CMD6 switched group 1 to HighSpeed after UHS fallback"
);
}
#[test]
fn sd_speed_selection_can_be_disabled_for_default_speed_bringup() {
let replies = sd_init_replies_with_ocr(ocr_ready_sdhc_s18a());
let host = MockHost::with_results(replies);
let mut driver = SdioSdmmc::new(host);
driver.set_sd_speed_selection_enabled(false);
poll_init_to_completion(&mut driver)
.expect("SD init succeeds without CMD6 speed switching");
assert_eq!(driver.host.bus_width, Some(BusWidth::Bit4));
assert_eq!(driver.host.last_clock, Some(ClockSpeed::Default));
assert!(
driver
.host
.commands
.iter()
.filter(|c| c.cmd == 6)
.all(|c| c.arg == 2),
"only ACMD6 bus-width switch is issued; no CMD6 SWITCH_FUNC"
);
assert_eq!(driver.host.last_voltage, None);
assert_eq!(driver.host.last_tuning_cmd, None);
}
fn ocr_ready_mmc_sector() -> Response {
Response::R3(OcrResponse::from_raw(0xC0FF_8000))
}
fn cmd8_timeout() -> Result<Response, Error> {
Err(Error::Timeout(ErrorContext::for_cmd(Phase::CommandSend, 8)))
}
fn acmd41_timeout() -> Result<Response, Error> {
Err(Error::Timeout(ErrorContext::for_cmd(
Phase::CommandSend,
41,
)))
}
fn r1_tran_ready() -> Response {
Response::R1(R1Response::from_native_raw((1 << 8) | (4 << 9)).unwrap())
}
fn ext_csd_blob() -> Vec<u8> {
use crate::cmd::ext_csd as e;
let mut buf = std::vec![0u8; 512];
buf[e::SEC_COUNT] = 0x00;
buf[e::SEC_COUNT + 1] = 0x00;
buf[e::SEC_COUNT + 2] = 0x80;
buf[e::SEC_COUNT + 3] = 0x00;
buf[e::DEVICE_TYPE] = e::device_type::HS_26 | e::device_type::HS_52;
buf[e::BUS_WIDTH] = 0;
buf[e::HS_TIMING] = 0;
buf
}
#[test]
fn init_falls_back_to_mmc_when_cmd8_and_acmd41_fail() {
let replies = std::vec![
Ok(ok_r1()), cmd8_timeout(), Ok(ok_r1()), acmd41_timeout(), Ok(ocr_ready_mmc_sector()), Ok(cid_response()), Ok(ok_r1()), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(ok_r1()), Ok(r1_tran_ready()), ];
let mut host = MockHost::with_results(replies);
host.next_read_payload = Some(ext_csd_blob());
let mut driver = SdioSdmmc::new(host);
let info = poll_init_to_completion(&mut driver).expect("eMMC init succeeds");
assert_eq!(info.kind, CardKind::Mmc);
assert_eq!(driver.kind(), CardKind::Mmc);
assert!(!info.sd_v2);
assert!(info.high_capacity, "OCR bit 30 set → sector mode");
assert_eq!(info.rca, 1);
assert_eq!(info.capacity_blocks, Some(0x0080_0000));
assert!(info.ext_csd.is_some());
let cmds = &driver.host.commands;
let cmd3 = cmds.iter().find(|c| c.cmd == 3).expect("CMD3 issued");
assert_eq!(cmd3.arg, 1u32 << 16);
assert!(cmds.iter().any(|c| c.cmd == 1), "CMD1 issued");
let cmd6s: Vec<&Command> = cmds.iter().filter(|c| c.cmd == 6).collect();
assert_eq!(cmd6s.len(), 2, "two CMD6 SWITCHes (BUS_WIDTH + HS_TIMING)");
let bw_arg = (0b11u32 << 24) | ((183u32) << 16) | (2u32 << 8);
assert_eq!(cmd6s[0].arg, bw_arg, "BUS_WIDTH=8-bit");
let hs_arg = (0b11u32 << 24) | ((185u32) << 16) | (1u32 << 8);
assert_eq!(cmd6s[1].arg, hs_arg, "HS_TIMING=1");
assert_eq!(driver.host.bus_width, Some(BusWidth::Bit8));
}
#[test]
fn mmc_init_falls_back_to_4bit_when_host_refuses_8bit() {
let replies = std::vec![
Ok(ok_r1()), cmd8_timeout(), Ok(ok_r1()), acmd41_timeout(), Ok(ocr_ready_mmc_sector()), Ok(cid_response()), Ok(ok_r1()), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(ok_r1()), Ok(r1_tran_ready()), ];
let mut host = MockHost::with_results(replies);
host.next_read_payload = Some(ext_csd_blob());
host.reject_bit8 = true;
let mut driver = SdioSdmmc::new(host);
let _info =
poll_init_to_completion(&mut driver).expect("eMMC init succeeds with 4-bit fallback");
assert_eq!(driver.host.bus_width, Some(BusWidth::Bit4));
}
#[test]
fn init_treats_sd_v1_correctly_when_cmd8_times_out_but_acmd41_succeeds() {
let replies = std::vec![
Ok(ok_r1()), cmd8_timeout(), Ok(ok_r1()), Ok(Response::R3(OcrResponse::from_raw(0x80FF_8000))),
Ok(cid_response()), Ok(rca_response(0x4321)), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), ];
let host = MockHost::with_results(replies);
let mut driver = SdioSdmmc::new(host);
disable_speed_selection(&mut driver);
let info = poll_init_to_completion(&mut driver).expect("SD v1 init succeeds");
assert_eq!(info.kind, CardKind::Sd, "ACMD41 success → SD, not MMC");
assert!(!info.sd_v2);
assert!(!info.high_capacity);
assert_eq!(info.rca, 0x4321);
assert_eq!(driver.host.bus_width, Some(BusWidth::Bit4));
}
fn ext_csd_blob_hs200() -> Vec<u8> {
use crate::cmd::ext_csd as e;
let mut buf = ext_csd_blob();
buf[e::DEVICE_TYPE] |= e::device_type::HS200_18V;
buf
}
#[test]
fn mmc_init_picks_hs200_when_card_and_host_agree() {
let replies = std::vec![
Ok(ok_r1()), cmd8_timeout(), Ok(ok_r1()), acmd41_timeout(), Ok(ocr_ready_mmc_sector()), Ok(cid_response()), Ok(ok_r1()), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(r1_tran_ready()), ];
let mut host = MockHost::with_results(replies);
host.next_read_payload = Some(ext_csd_blob_hs200());
let mut driver = SdioSdmmc::new(host);
let _info = poll_init_to_completion(&mut driver).expect("HS200 init succeeds");
let cmd6s: Vec<&Command> = driver.host.commands.iter().filter(|c| c.cmd == 6).collect();
assert_eq!(cmd6s.len(), 2);
let hs_timing_arg = (0b11u32 << 24) | ((185u32) << 16) | (0x02u32 << 8);
assert_eq!(cmd6s[1].arg, hs_timing_arg, "HS_TIMING=2 (HS200)");
assert_eq!(driver.host.last_voltage, Some(SignalVoltage::V180));
assert_eq!(driver.host.last_clock, Some(ClockSpeed::Hs200));
assert_eq!(driver.host.last_tuning_cmd, Some(21));
let hs200_clock_pos = driver
.host
.events
.iter()
.position(|event| matches!(event, MockEvent::Clock(ClockSpeed::Hs200)))
.expect("host clock is raised to HS200");
let hs200_switch_pos = driver
.host
.events
.iter()
.position(|event| {
matches!(
event,
MockEvent::Command(Command {
cmd: 6,
arg,
..
}) if *arg == hs_timing_arg
)
})
.expect("HS_TIMING=2 is programmed");
assert!(
hs200_switch_pos < hs200_clock_pos,
"EXT_CSD HS_TIMING=2 must be programmed before raising host clock to HS200"
);
}
#[test]
fn mmc_init_falls_back_to_hs52_when_tuning_fails() {
let replies = std::vec![
Ok(ok_r1()), cmd8_timeout(), Ok(ok_r1()), acmd41_timeout(), Ok(ocr_ready_mmc_sector()), Ok(cid_response()), Ok(ok_r1()), Ok(csd_v2_response()), Ok(ok_r1()), Ok(ok_r1()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(ok_r1()), Ok(r1_tran_ready()), Ok(ok_r1()), Ok(r1_tran_ready()), ];
let mut host = MockHost::with_results(replies);
host.next_read_payload = Some(ext_csd_blob_hs200());
host.tuning_result = Some(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 21)));
let mut driver = SdioSdmmc::new(host);
let _info = poll_init_to_completion(&mut driver)
.expect("init succeeds even when HS200 tuning fails");
assert_eq!(driver.host.last_voltage, Some(SignalVoltage::V180));
assert_eq!(driver.host.last_tuning_cmd, Some(21));
assert_eq!(driver.host.last_clock, Some(ClockSpeed::HighSpeed));
let hs_timing_writes: Vec<u8> = driver
.host
.commands
.iter()
.filter(|c| c.cmd == 6 && ((c.arg >> 16) & 0xFF) as u8 == 185)
.map(|c| ((c.arg >> 8) & 0xFF) as u8)
.collect();
assert_eq!(hs_timing_writes, std::vec![0x02, 0x01]);
}
#[test]
fn set_bus_width_bit8_is_unsupported_via_acmd6() {
assert_eq!(sd_acmd6_arg(BusWidth::Bit8), Err(Error::UnsupportedCommand));
}
#[test]
fn submit_read_blocks_into_leaves_multi_block_stop_to_host_request() {
let mut host = MockHost::new(std::vec![ok_r1()]);
let expected: Vec<u8> = (0..1024).map(|i| (i % 251) as u8).collect();
host.next_read_payload = Some(expected.clone());
let mut driver = SdioSdmmc::new(host);
driver.high_capacity = true;
let mut buf = [0u8; 1024];
let mut request = driver.submit_read_blocks_into(7, &mut buf).unwrap();
assert!(matches!(
driver.poll_data_request(&mut request).unwrap(),
DataCommandPoll::Complete(_)
));
assert_eq!(&buf[..], &expected[..]);
assert_eq!(
driver.host.data_requests,
std::vec![(DataDirection::Read, 512, 2)]
);
assert_eq!(
driver
.host
.commands
.iter()
.map(|c| c.cmd)
.collect::<Vec<_>>(),
std::vec![18]
);
assert_eq!(driver.host.commands[0].arg, 7);
}
#[test]
fn submit_write_blocks_from_leaves_multi_block_stop_to_host_request() {
let host = MockHost::new(std::vec![ok_r1()]);
let mut driver = SdioSdmmc::new(host);
driver.high_capacity = true;
let buf = [0x5au8; 1024];
let mut request = driver.submit_write_blocks_from(11, &buf).unwrap();
assert!(matches!(
driver.poll_data_request(&mut request).unwrap(),
DataCommandPoll::Complete(_)
));
assert_eq!(
driver.host.data_requests,
std::vec![(DataDirection::Write, 512, 2)]
);
assert_eq!(
driver
.host
.commands
.iter()
.map(|c| c.cmd)
.collect::<Vec<_>>(),
std::vec![25]
);
assert_eq!(driver.host.commands[0].arg, 11);
assert_eq!(driver.host.writes, std::vec![buf.to_vec()]);
}
#[test]
fn submit_block_io_rejects_misaligned_buffers() {
let host = MockHost::new(std::vec![]);
let mut driver = SdioSdmmc::new(host);
let mut read_buf = [0u8; 513];
let write_buf = [0u8; 513];
assert_eq!(
driver.submit_read_blocks_into(0, &mut read_buf).map(|_| ()),
Err(Error::Misaligned)
);
assert_eq!(
driver.submit_write_blocks_from(0, &write_buf).map(|_| ()),
Err(Error::Misaligned)
);
assert!(driver.host.commands.is_empty());
}
}