use alloc::format;
use core::time::Duration;
use log::{info, warn};
use rdrive::{
probe::{OnProbeError, fdt::ResourcePrepareConfig},
register::ProbeFdt,
};
use sdmmc_protocol::{
Error, OperationPoll,
error::Phase,
rdif::config::BlockConfig,
sdio::{
BusWidth,
card::{CardInfo, SdioSdmmc},
host2::SdioHost2Adapter,
init::{CardInitPreference, SdioInitScratch},
},
};
use starfive_jh7110_dwmmc::{
JH7110_STABLE_REFERENCE_CLOCK_HZ, Jh7110DwMmc, Jh7110DwMmcConfig, rdif as starfive_rdif,
};
use crate::{block::ProbeFdtBlock, mmio::iomap};
type StarFiveDwMmc = SdioSdmmc<SdioHost2Adapter<Jh7110DwMmc>>;
crate::model_register!(
name: "StarFive JH7110 MMC",
level: ProbeLevel::PostKernel,
priority: ProbePriority::DEFAULT,
probe_kinds: &[
ProbeKind::Fdt {
compatibles: &["starfive,jh7110-mmc"],
on_probe: probe
}
],
);
fn probe(probe: ProbeFdt<'_>) -> Result<(), OnProbeError> {
let info = probe.info();
let base_reg = info
.node
.regs()
.into_iter()
.next()
.ok_or(OnProbeError::other(format!(
"[{}] has no reg",
info.node.name()
)))?;
let address = base_reg.address;
let mmio_size = base_reg.size.unwrap_or(0x1000);
info!(
"starfive-jh7110-dwmmc probe: node={}, addr={:#x}, size={:#x}",
info.node.name(),
address,
mmio_size
);
let resources = info.prepare_resources(
ResourcePrepareConfig::default()
.without_assigned_clocks()
.with_named_clock_rate("ciu"),
)?;
let reference_clock_hz = prepared_reference_clock_hz(resources.clock_rate("ciu"));
let profile = StarFiveMmcNodeProfile::from_info(info, reference_clock_hz);
let mmio_base = iomap(address as usize, mmio_size as usize)?;
let mut host = unsafe { Jh7110DwMmc::new(mmio_base, profile.host_config) };
info!("starfive-jh7110-dwmmc: reset controller");
host.reset_and_init()
.map_err(|err| init_error(address, mmio_size, err))?;
info!("starfive-jh7110-dwmmc: initialize card");
let mut sd = SdioSdmmc::new_host2(host);
sd.set_sd_speed_selection_enabled(false);
let card_info = poll_card_init(&mut sd, profile.init_preference).map_err(|err| {
warn!("starfive-jh7110-dwmmc: card init failed: {:?}", err);
card_init_error(address, mmio_size, err)
})?;
info!(
"starfive-jh7110-dwmmc card: kind={:?} high_capacity={} rca={} ocr={:#010x} \
capacity_blocks={:?} cid={} ext_csd={}",
card_info.kind,
card_info.high_capacity,
card_info.rca,
card_info.ocr,
card_info.capacity_blocks,
card_info.cid.is_some(),
card_info.ext_csd.is_some()
);
let dev = starfive_rdif::device(
sd,
starfive_block_config(card_info.capacity_blocks.unwrap_or(0)),
);
let irq = probe.register_block(dev)?;
info!("starfive-jh7110-mmc block device registered irq={:?}", irq);
Ok(())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct StarFiveMmcNodeProfile {
host_config: Jh7110DwMmcConfig,
init_preference: CardInitPreference,
}
impl StarFiveMmcNodeProfile {
fn from_info(info: &rdrive::probe::fdt::FdtInfo<'_>, reference_clock_hz: u32) -> Self {
let node = info.node.as_node();
Self::from_dt_flags(
reference_clock_hz,
node.get_property("bus-width")
.and_then(|prop| prop.get_u32())
.unwrap_or(1),
node.get_property("mmc-hs200-1_8v").is_some()
|| node.get_property("mmc-ddr-1_8v").is_some()
|| node.get_property("sd-uhs-sdr104").is_some()
|| node.get_property("sd-uhs-sdr50").is_some()
|| node.get_property("sd-uhs-ddr50").is_some()
|| node.get_property("sd-uhs-sdr25").is_some()
|| node.get_property("sd-uhs-sdr12").is_some(),
node.get_property("no-sd").is_some(),
node.get_property("no-mmc").is_some(),
node.get_property("non-removable").is_some(),
node.get_property("cap-mmc-hw-reset").is_some()
|| node.get_property("mmc-hs200-1_8v").is_some()
|| node.get_property("mmc-hs400-1_8v").is_some()
|| node.get_property("mmc-ddr-1_8v").is_some(),
)
}
fn from_dt_flags(
reference_clock_hz: u32,
bus_width: u32,
supports_1v8: bool,
no_sd: bool,
no_mmc: bool,
non_removable: bool,
has_mmc_capability: bool,
) -> Self {
let max_bus_width = match bus_width {
8.. => BusWidth::Bit8,
4.. => BusWidth::Bit4,
_ => BusWidth::Bit1,
};
let host_config = Jh7110DwMmcConfig::default()
.with_reference_clock_hz(reference_clock_hz)
.with_max_bus_width(max_bus_width)
.with_1v8_support(supports_1v8);
let init_preference = if no_mmc {
CardInitPreference::SdOnly
} else if no_sd
|| matches!(max_bus_width, BusWidth::Bit8)
|| (non_removable && has_mmc_capability)
{
CardInitPreference::MmcFirst
} else {
CardInitPreference::SdFirst
};
Self {
host_config,
init_preference,
}
}
}
fn starfive_block_config(capacity_blocks: u64) -> BlockConfig {
starfive_rdif::fifo_config(capacity_blocks, true)
}
fn prepared_reference_clock_hz(clock_rate: Option<u64>) -> u32 {
let reference_clock_hz = clock_rate
.and_then(|hz| u32::try_from(hz).ok())
.filter(|hz| *hz != 0)
.unwrap_or(JH7110_STABLE_REFERENCE_CLOCK_HZ);
info!(
"starfive-jh7110-dwmmc: using {} Hz ciu reference clock prepared rate {:?}",
reference_clock_hz, clock_rate
);
reference_clock_hz
}
fn poll_card_init(
sd: &mut StarFiveDwMmc,
init_preference: CardInitPreference,
) -> Result<CardInfo, Error> {
let mut scratch = SdioInitScratch::new();
let mut request = sd.submit_init_with_preference(init_preference, &mut scratch)?;
loop {
match sd.poll_init_request(&mut request)? {
OperationPoll::Pending => {
if request.take_needs_pace() {
axklib::time::busy_wait(Duration::from_millis(10));
} else {
core::hint::spin_loop();
}
}
OperationPoll::Complete(info) => return Ok(info),
_ => return Err(Error::UnsupportedCommand),
}
}
}
fn init_error(address: u64, size: u64, err: Error) -> OnProbeError {
OnProbeError::other(format!(
"failed to initialize StarFive JH7110 DWMMC device at [PA:{:?}, SZ:0x{:x}): {err:?}",
address, size
))
}
fn card_init_error(address: u64, size: u64, err: Error) -> OnProbeError {
if is_absent_card_init_error(err) {
warn!(
"starfive-jh7110-dwmmc: no responsive card at [PA:{:?}, SZ:0x{:x}); skipping \
controller: {err:?}",
address, size
);
return OnProbeError::NotMatch;
}
init_error(address, size, err)
}
fn is_absent_card_init_error(err: Error) -> bool {
match err {
Error::NoCard => true,
Error::Timeout(ctx) | Error::Crc(ctx) | Error::BadResponse(ctx) => {
matches!(ctx.phase, Phase::Unspecified) && ctx.cmd.is_none()
|| ctx.cmd.is_some()
&& matches!(
ctx.phase,
Phase::CommandSend | Phase::ResponseWait | Phase::Init
)
}
_ => false,
}
}
#[cfg(test)]
mod tests {
#[cfg(not(feature = "pci"))]
use axklib::{
AxError, AxResult, BoxedIrqHandler, ConcurrentBoxedIrqHandler, IrqCpuMask, IrqHandle,
IrqId, Klib, PhysAddr, VirtAddr, impl_trait,
};
use super::*;
#[cfg(not(feature = "pci"))]
struct KlibImpl;
#[cfg(not(feature = "pci"))]
impl_trait! {
impl Klib for KlibImpl {
fn mem_iomap(_addr: PhysAddr, _size: usize) -> AxResult<VirtAddr> {
Err(AxError::Unsupported)
}
fn mem_virt_to_phys(addr: VirtAddr) -> PhysAddr {
PhysAddr::from_usize(addr.as_usize())
}
fn mem_make_dma_coherent_uncached(_addr: VirtAddr, _size: usize) -> AxResult {
Err(AxError::Unsupported)
}
fn mem_restore_dma_cached(_addr: VirtAddr, _size: usize) -> AxResult {
Err(AxError::Unsupported)
}
fn dma_alloc_pages(
_dma_mask: u64,
_num_pages: usize,
_align: usize,
) -> AxResult<VirtAddr> {
Err(AxError::Unsupported)
}
fn dma_dealloc_pages(_addr: VirtAddr, _num_pages: usize) {}
fn time_busy_wait(_dur: core::time::Duration) {}
fn time_monotonic_nanos() -> u64 {
0
}
fn time_try_init_epoch_offset(_epoch_time_nanos: u64) -> bool {
false
}
fn irq_set_enable(_irq: IrqId, _enabled: bool) -> AxResult {
Ok(())
}
fn irq_request_shared(
_irq: IrqId,
_handler: BoxedIrqHandler,
) -> AxResult<IrqHandle> {
Err(AxError::Unsupported)
}
fn irq_request_shared_disabled(
_irq: IrqId,
_handler: BoxedIrqHandler,
) -> AxResult<IrqHandle> {
Err(AxError::Unsupported)
}
fn irq_request_percpu(
_irq: IrqId,
_cpus: IrqCpuMask,
_handler: ConcurrentBoxedIrqHandler,
) -> AxResult<IrqHandle> {
Err(AxError::Unsupported)
}
fn irq_free(_handle: IrqHandle) -> AxResult {
Err(AxError::Unsupported)
}
fn irq_enable(_handle: IrqHandle) -> AxResult {
Err(AxError::Unsupported)
}
fn irq_disable(_handle: IrqHandle) -> AxResult {
Err(AxError::Unsupported)
}
}
}
#[test]
fn starfive_block_config_is_irq_driven_fifo() {
let config = starfive_block_config(16);
assert_eq!(config.name, "starfive-jh7110-mmc");
assert_eq!(config.capacity_blocks, 16);
assert!(config.irq_driven);
assert!(!config.uses_dma());
}
#[test]
fn starfive_profiles_are_dt_capability_driven_not_base_driven() {
let emmc =
StarFiveMmcNodeProfile::from_dt_flags(50_000_000, 8, true, false, false, true, true);
let microsd =
StarFiveMmcNodeProfile::from_dt_flags(50_000_000, 4, false, false, true, false, false);
assert_eq!(emmc.host_config.max_bus_width(), BusWidth::Bit8);
assert!(emmc.host_config.supports_1v8());
assert_eq!(emmc.init_preference, CardInitPreference::MmcFirst);
assert_eq!(microsd.host_config.max_bus_width(), BusWidth::Bit4);
assert!(!microsd.host_config.supports_1v8());
assert_eq!(microsd.init_preference, CardInitPreference::SdOnly);
}
#[test]
fn contextless_init_timeout_is_treated_as_absent_card() {
assert!(is_absent_card_init_error(Error::Timeout(
sdmmc_protocol::ErrorContext::default()
)));
}
}