#![no_std]
#![allow(clippy::missing_safety_doc)]
use core::{marker::PhantomData, num::NonZeroUsize, ptr::NonNull};
mod command;
mod dma;
mod host;
mod regs;
pub use dma::{ADMA2_DESC_ALIGN, ADMA2_DESC_COUNT, BlockRequest, BlockRequestSlot, RequestId};
pub use host::{HostClock, Sdhci};
pub use sdmmc_protocol::block::{
BlockBufferConfig, BlockPoll, BlockRequestId, BlockTransferDirection, BlockTransferMode,
BlockTransferState,
};
use sdmmc_protocol::{
DataCommandPoll,
cmd::{Command, DataDirection},
error::{Error, ErrorContext, Phase},
sdio::{
BusWidth, ClockSpeed, HostEvent, HostEventKind, HostEventSource, SdioHost, SignalVoltage,
},
};
use crate::regs::*;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Event {
#[default]
None,
CommandComplete,
TransferComplete,
Error { normal: u16, error: u16 },
Other { normal: u16, error: u16 },
}
pub struct DataRequest<'a> {
id: RequestId,
request: Option<BlockRequest>,
slot: BlockRequestSlot,
_buffer: PhantomData<&'a [u8]>,
}
impl SdioHost for Sdhci {
type Event = Event;
type DataRequest<'a> = DataRequest<'a>;
fn submit_command(&mut self, cmd: &Command) -> Result<(), Error> {
Sdhci::submit_command(self, cmd)
}
fn poll_command_response(&mut self) -> Result<sdmmc_protocol::CommandResponsePoll, Error> {
Sdhci::poll_command_response(self)
}
fn submit_read_data<'a>(
&mut self,
cmd: &Command,
buf: &'a mut [u8],
block_size: u32,
block_count: u32,
) -> Result<Self::DataRequest<'a>, Error> {
let buffer = NonNull::new(buf.as_mut_ptr()).ok_or(Error::InvalidArgument)?;
let mut slot = BlockRequestSlot::default();
let request = submit_read_with_dma_fifo_fallback(
self,
cmd,
buffer,
buf.len(),
block_size,
block_count,
&mut slot,
)?;
let id = request.id();
Ok(DataRequest {
id,
request: Some(request),
slot,
_buffer: PhantomData,
})
}
fn submit_write_data<'a>(
&mut self,
cmd: &Command,
buf: &'a [u8],
block_size: u32,
block_count: u32,
) -> Result<Self::DataRequest<'a>, Error> {
let buffer = NonNull::new(buf.as_ptr() as *mut u8).ok_or(Error::InvalidArgument)?;
let mut slot = BlockRequestSlot::default();
let request = submit_write_with_dma_fifo_fallback(
self,
cmd,
buffer,
buf.len(),
block_size,
block_count,
&mut slot,
)?;
let id = request.id();
Ok(DataRequest {
id,
request: Some(request),
slot,
_buffer: PhantomData,
})
}
fn poll_data_request<'a>(
&mut self,
request: &mut Self::DataRequest<'a>,
) -> Result<DataCommandPoll, Error> {
self.poll_block_request_response(&mut request.request, request.id, &mut request.slot)
}
fn set_bus_width(&mut self, width: BusWidth) -> Result<(), Error> {
let mut ctrl = self.read_u8(REG_HOST_CONTROL1);
ctrl &= !(HOST_CTRL1_4BIT | HOST_CTRL1_8BIT);
match width {
BusWidth::Bit1 => {}
BusWidth::Bit4 => ctrl |= HOST_CTRL1_4BIT,
BusWidth::Bit8 => return Err(Error::UnsupportedCommand),
_ => return Err(Error::UnsupportedCommand),
}
self.write_u8(REG_HOST_CONTROL1, ctrl);
Ok(())
}
fn set_clock(&mut self, speed: ClockSpeed) -> Result<(), Error> {
let target_hz = match speed {
ClockSpeed::Identification => 400_000,
ClockSpeed::Default | ClockSpeed::Sdr12 => 25_000_000,
ClockSpeed::HighSpeed | ClockSpeed::Sdr25 => 50_000_000,
ClockSpeed::Sdr50 | ClockSpeed::Ddr50 => 50_000_000,
ClockSpeed::Sdr104 => 104_000_000,
ClockSpeed::Hs200 => 200_000_000,
_ => return Err(Error::UnsupportedCommand),
};
let mut ctrl = self.read_u8(REG_HOST_CONTROL1);
if matches!(
speed,
ClockSpeed::Identification | ClockSpeed::Default | ClockSpeed::Sdr12
) {
ctrl &= !HOST_CTRL1_HIGH_SPEED;
} else {
ctrl |= HOST_CTRL1_HIGH_SPEED;
}
self.write_u8(REG_HOST_CONTROL1, ctrl);
if let Some(cb) = self.ext_clock {
self.disable_sd_clock();
cb.set_clock(target_hz)?;
return self.enable_clock_external();
}
let base = self.base_clock_hz();
if base == 0 {
return Err(Error::BadResponse(ErrorContext::new(Phase::Init)));
}
self.enable_clock(base, target_hz)
}
fn switch_voltage(&mut self, voltage: SignalVoltage) -> Result<(), Error> {
if matches!(voltage, SignalVoltage::V180) && !self.support_1v8 {
return Err(Error::UnsupportedCommand);
}
self.disable_sd_clock();
let mut ctrl2 = self.read_u16(REG_HOST_CONTROL2);
match voltage {
SignalVoltage::V330 => {
ctrl2 &= !HOST_CTRL2_1V8_SIGNALING;
self.set_power(POWER_330);
}
SignalVoltage::V180 => {
ctrl2 |= HOST_CTRL2_1V8_SIGNALING;
self.set_power(POWER_180);
}
SignalVoltage::V120 => return Err(Error::UnsupportedCommand),
_ => return Err(Error::UnsupportedCommand),
}
self.write_u16(REG_HOST_CONTROL2, ctrl2);
let cur = self.read_u16(REG_CLOCK_CONTROL);
self.write_u16(REG_CLOCK_CONTROL, cur | CLOCK_SD_ENABLE);
Ok(())
}
fn execute_tuning(&mut self, cmd_index: u8) -> Result<(), Error> {
if cmd_index != 19 && cmd_index != 21 {
return Err(Error::InvalidArgument);
}
let block_size: u16 =
if cmd_index == 21 && self.read_u8(REG_HOST_CONTROL1) & HOST_CTRL1_8BIT != 0 {
128
} else {
64
};
self.write_u16(REG_BLOCK_SIZE, block_size & 0x0FFF);
self.write_u16(REG_BLOCK_COUNT, 1);
self.write_u8(REG_TIMEOUT_CONTROL, 0x0E);
self.write_u16(
REG_TRANSFER_MODE,
XFER_MODE_BLOCK_COUNT_ENABLE | XFER_MODE_READ,
);
let mut ctrl2 = self.read_u16(REG_HOST_CONTROL2);
ctrl2 |= HOST_CTRL2_EXECUTE_TUNING;
self.write_u16(REG_HOST_CONTROL2, ctrl2);
const TUNING_POLLS: u32 = 1_000_000;
let mut last_status = 0u16;
for _ in 0..TUNING_POLLS {
last_status = self.read_u16(REG_HOST_CONTROL2);
if last_status & HOST_CTRL2_EXECUTE_TUNING == 0 {
if last_status & HOST_CTRL2_SAMPLING_CLOCK_SELECT != 0 {
return Ok(());
}
return Err(Error::BadResponse(ErrorContext::for_cmd(
Phase::Init,
cmd_index,
)));
}
core::hint::spin_loop();
}
let cleared = last_status & !HOST_CTRL2_EXECUTE_TUNING;
self.write_u16(REG_HOST_CONTROL2, cleared);
Err(Error::Timeout(ErrorContext::for_cmd(
Phase::Init,
cmd_index,
)))
}
fn enable_completion_irq(&mut self) -> Result<(), Error> {
Sdhci::enable_completion_irq(self);
Ok(())
}
fn disable_completion_irq(&mut self) -> Result<(), Error> {
Sdhci::disable_completion_irq(self);
Ok(())
}
fn handle_irq(&mut self) -> Self::Event {
Sdhci::handle_irq(self)
}
}
fn submit_read_with_dma_fifo_fallback(
host: &mut Sdhci,
cmd: &Command,
buffer: NonNull<u8>,
len: usize,
block_size: u32,
block_count: u32,
slot: &mut BlockRequestSlot,
) -> Result<BlockRequest, Error> {
if should_try_dma(cmd, block_size, block_count, len, DataDirection::Read)
&& let Some(dma) = host.dma.clone()
{
match host.submit_read_blocks(
cmd.arg,
buffer,
NonZeroUsize::new(len).ok_or(Error::InvalidArgument)?,
Some(&dma),
BlockTransferMode::Dma,
slot,
) {
Ok(request) => return Ok(request),
Err(err) if can_fallback_to_fifo(err) => {}
Err(err) => return Err(err),
}
}
host.submit_fifo_data_request(
cmd,
buffer,
len,
block_size,
block_count,
DataDirection::Read,
slot,
)
}
fn submit_write_with_dma_fifo_fallback(
host: &mut Sdhci,
cmd: &Command,
buffer: NonNull<u8>,
len: usize,
block_size: u32,
block_count: u32,
slot: &mut BlockRequestSlot,
) -> Result<BlockRequest, Error> {
if should_try_dma(cmd, block_size, block_count, len, DataDirection::Write)
&& let Some(dma) = host.dma.clone()
{
match host.submit_write_blocks(
cmd.arg,
buffer,
NonZeroUsize::new(len).ok_or(Error::InvalidArgument)?,
Some(&dma),
BlockTransferMode::Dma,
slot,
) {
Ok(request) => return Ok(request),
Err(err) if can_fallback_to_fifo(err) => {}
Err(err) => return Err(err),
}
}
host.submit_fifo_data_request(
cmd,
buffer,
len,
block_size,
block_count,
DataDirection::Write,
slot,
)
}
fn should_try_dma(
cmd: &Command,
block_size: u32,
block_count: u32,
len: usize,
direction: DataDirection,
) -> bool {
block_size == 512
&& len == block_count as usize * 512
&& matches!(
(direction, cmd.cmd),
(DataDirection::Read, 17 | 18) | (DataDirection::Write, 24 | 25)
)
}
fn can_fallback_to_fifo(err: Error) -> bool {
matches!(
err,
Error::UnsupportedCommand | Error::InvalidArgument | Error::Misaligned
)
}
pub(crate) fn event_from_status(normal: u16, error: u16) -> Event {
if normal & NORMAL_INT_ERROR != 0 {
Event::Error { normal, error }
} else if normal & NORMAL_INT_CMD_COMPLETE != 0 {
Event::CommandComplete
} else if normal & NORMAL_INT_XFER_COMPLETE != 0 {
Event::TransferComplete
} else if normal != 0 || error != 0 {
Event::Other { normal, error }
} else {
Event::None
}
}
impl HostEvent for Event {
fn kind(&self) -> HostEventKind {
match self {
Event::None => HostEventKind::None,
Event::CommandComplete => HostEventKind::CommandComplete,
Event::TransferComplete => HostEventKind::TransferComplete,
Event::Error { .. } => HostEventKind::Error,
Event::Other { .. } => HostEventKind::Other,
}
}
fn source(&self) -> HostEventSource {
match self {
Event::CommandComplete => HostEventSource::Command,
Event::TransferComplete => HostEventSource::Data,
Event::None | Event::Error { .. } | Event::Other { .. } => HostEventSource::Controller,
}
}
fn queue_id(&self) -> Option<BlockRequestId> {
match self {
Event::TransferComplete => Some(BlockRequestId::new(0)),
Event::None | Event::CommandComplete | Event::Error { .. } | Event::Other { .. } => {
None
}
}
}
}
impl Sdhci {
pub fn block_buffer_config(&self, mode: BlockTransferMode) -> BlockBufferConfig {
match mode {
BlockTransferMode::Fifo => {
BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 1, None)
}
BlockTransferMode::Dma => {
BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 512, Some(self.dma_mask))
}
_ => BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 1, None),
}
}
pub fn handle_irq(&mut self) -> Event {
let normal = self.read_u16(REG_NORMAL_INT_STATUS);
let error = if normal & NORMAL_INT_ERROR != 0 {
self.read_u16(REG_ERROR_INT_STATUS)
} else {
0
};
if normal != 0 {
self.write_u16(REG_NORMAL_INT_STATUS, normal);
}
if error != 0 {
self.write_u16(REG_ERROR_INT_STATUS, error);
}
self.irq_pending_normal |= normal;
self.irq_pending_error |= error;
event_from_status(normal, error)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn event_reports_command_completion_without_os_wakeup_policy() {
assert_eq!(
event_from_status(NORMAL_INT_CMD_COMPLETE, 0),
Event::CommandComplete
);
}
#[test]
fn event_reports_data_completion_without_os_wakeup_policy() {
assert_eq!(
event_from_status(NORMAL_INT_XFER_COMPLETE, 0),
Event::TransferComplete
);
}
#[test]
fn event_reports_error_status_without_translating_to_os_action() {
assert_eq!(
event_from_status(NORMAL_INT_ERROR, ERROR_INT_DATA_TIMEOUT),
Event::Error {
normal: NORMAL_INT_ERROR,
error: ERROR_INT_DATA_TIMEOUT,
}
);
}
#[test]
fn event_reports_data_completion_source_for_runtime_wakeup() {
use sdmmc_protocol::sdio::{HostEvent, HostEventKind, HostEventSource};
let event = event_from_status(NORMAL_INT_XFER_COMPLETE, 0);
assert_eq!(event.kind(), HostEventKind::TransferComplete);
assert_eq!(event.source(), HostEventSource::Data);
assert_eq!(event.queue_id(), Some(BlockRequestId::new(0)));
}
#[test]
fn exposes_block_buffer_constraints() {
let host = unsafe { Sdhci::new_from_addr(0x1000_0000) };
let dma = host.block_buffer_config(BlockTransferMode::Dma);
assert_eq!(dma.block_size.get(), 512);
assert_eq!(dma.align, 512);
assert_eq!(dma.dma_mask, Some(u32::MAX as u64));
}
}