6 Commits

Author SHA1 Message Date
Robin Mueller
337b00a442 continue sdio
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2025-11-03 11:46:30 +01:00
Robin Mueller
848e2113a0 continue with SDIO 2025-11-02 19:40:13 +01:00
Robin Mueller
82296ecc79 continue SDIO 2025-11-02 19:40:13 +01:00
Robin Mueller
3533faa7cc continue 2025-11-02 19:40:13 +01:00
Robin Mueller
5235eb422d add SDIO support 2025-11-02 19:40:13 +01:00
Robin Mueller
0d227e7f68 rename register blocks 2025-11-02 19:40:11 +01:00
6 changed files with 962 additions and 35 deletions

View File

@@ -8,11 +8,6 @@ use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::He
use super::{EthernetId, PsEthernet as _};
pub struct EthernetLowLevel {
id: EthernetId,
pub regs: zynq7000::eth::MmioRegisters<'static>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Speed {
Mbps10,
@@ -52,7 +47,10 @@ impl ClockDivisors {
/// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
/// which is the default clock for the Ethernet module.
pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) {
pub fn calculate_for_rgmii_and_io_clock(
io_clks: &IoClocks,
target_speed: Speed,
) -> (Self, u32) {
Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed)
}
@@ -174,8 +172,17 @@ impl ClockDivSet {
/// Ethernet low-level interface.
///
/// Basic building block for higher-level abstraction.
pub struct EthernetLowLevel {
id: EthernetId,
/// Register block. Direct public access is allowed to allow low-level operations.
pub regs: zynq7000::eth::MmioRegisters<'static>,
}
impl EthernetLowLevel {
/// Creates a new instance of the Ethernet low-level interface.
///
/// Returns [None] if the given registers block base address does not correspond to a valid
/// Ethernet peripheral.
#[inline]
pub fn new(regs: zynq7000::eth::MmioRegisters<'static>) -> Option<Self> {
regs.id()?;
@@ -204,33 +211,7 @@ impl EthernetLowLevel {
}
pub fn reset(&mut self, cycles: usize) {
let assert_reset = match self.id {
EthernetId::Eth0 => EthernetReset::builder()
.with_gem1_ref_rst(false)
.with_gem0_ref_rst(true)
.with_gem1_rx_rst(false)
.with_gem0_rx_rst(true)
.with_gem1_cpu1x_rst(false)
.with_gem0_cpu1x_rst(true)
.build(),
EthernetId::Eth1 => EthernetReset::builder()
.with_gem1_ref_rst(true)
.with_gem0_ref_rst(false)
.with_gem1_rx_rst(true)
.with_gem0_rx_rst(false)
.with_gem1_cpu1x_rst(true)
.with_gem0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_eth(assert_reset);
for _ in 0..cycles {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
});
}
reset(self.id, cycles);
}
#[inline]
@@ -383,3 +364,34 @@ impl EthernetLowLevel {
self.id
}
}
/// Resets the Ethernet peripheral with the given ID.
pub fn reset(id: EthernetId, cycles: usize) {
let assert_reset = match id {
EthernetId::Eth0 => EthernetReset::builder()
.with_gem1_ref_rst(false)
.with_gem0_ref_rst(true)
.with_gem1_rx_rst(false)
.with_gem0_rx_rst(true)
.with_gem1_cpu1x_rst(false)
.with_gem0_cpu1x_rst(true)
.build(),
EthernetId::Eth1 => EthernetReset::builder()
.with_gem1_ref_rst(true)
.with_gem0_ref_rst(false)
.with_gem1_rx_rst(true)
.with_gem0_rx_rst(false)
.with_gem1_cpu1x_rst(true)
.with_gem0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_eth(assert_reset);
for _ in 0..cycles {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
});
}
}

View File

@@ -470,7 +470,7 @@ impl Ethernet {
});
});
}
ll.configure_clock(config.clk_config_1000_mbps, true);
ll.configure_peripheral_clock(config.clk_config_1000_mbps, true);
let mut mdio = mdio::Mdio::new(&ll, true);
mdio.configure_clock_div(config.mdc_clk_div);
ll.regs.modify_net_ctrl(|mut val| {
@@ -491,7 +491,7 @@ impl Ethernet {
pub fn new(mut ll: EthernetLowLevel, config: EthernetConfig) -> Self {
Self::common_init(&mut ll, config.mac_address);
ll.configure_clock(config.clk_config_1000_mbps, true);
ll.configure_peripheral_clock(config.clk_config_1000_mbps, true);
let mut mdio = mdio::Mdio::new(&ll, true);
mdio.configure_clock_div(config.mdc_clk_div);
Ethernet {

View File

@@ -38,6 +38,7 @@ pub mod log;
pub mod prelude;
pub mod priv_tim;
pub mod qspi;
pub mod sdio;
pub mod slcr;
pub mod spi;
pub mod time;

View File

@@ -0,0 +1,452 @@
use arbitrary_int::{traits::Integer as _, u3, u6};
use zynq7000::{
sdio::{SDIO_BASE_ADDR_0, SDIO_BASE_ADDR_1, SdClockDivisor},
slcr::{clocks::SrcSelIo, reset::DualRefAndClockReset},
};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::mio::{
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
};
use crate::{
clocks::{Clocks, IoClocks},
gpio::{
IoPeriphPin,
mio::{
Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33,
Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, MioPin, MuxConfig, Pin,
},
},
slcr::Slcr,
time::Hertz,
};
pub const MUX_CONF: MuxConfig = MuxConfig::new_with_l3(u3::new(0b100));
pub trait Sdio0ClockPin: MioPin {}
pub trait Sdio0CommandPin: MioPin {}
pub trait Sdio0Data0Pin: MioPin {}
pub trait Sdio0Data1Pin: MioPin {}
pub trait Sdio0Data2Pin: MioPin {}
pub trait Sdio0Data3Pin: MioPin {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0ClockPin for Pin<Mio16> {}
impl Sdio0ClockPin for Pin<Mio28> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0ClockPin for Pin<Mio40> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0CommandPin for Pin<Mio17> {}
impl Sdio0CommandPin for Pin<Mio29> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0CommandPin for Pin<Mio41> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data0Pin for Pin<Mio18> {}
impl Sdio0Data0Pin for Pin<Mio30> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data0Pin for Pin<Mio42> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data1Pin for Pin<Mio19> {}
impl Sdio0Data1Pin for Pin<Mio31> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data1Pin for Pin<Mio43> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data2Pin for Pin<Mio20> {}
impl Sdio0Data2Pin for Pin<Mio32> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data2Pin for Pin<Mio44> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data3Pin for Pin<Mio21> {}
impl Sdio0Data3Pin for Pin<Mio33> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio0Data3Pin for Pin<Mio45> {}
pub trait Sdio1ClockPin: MioPin {}
pub trait Sdio1CommandPin: MioPin {}
pub trait Sdio1Data0Pin: MioPin {}
pub trait Sdio1Data1Pin: MioPin {}
pub trait Sdio1Data2Pin: MioPin {}
pub trait Sdio1Data3Pin: MioPin {}
impl Sdio1ClockPin for Pin<Mio12> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1ClockPin for Pin<Mio24> {}
impl Sdio1ClockPin for Pin<Mio36> {}
impl Sdio1ClockPin for Pin<Mio48> {}
impl Sdio1CommandPin for Pin<Mio11> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1CommandPin for Pin<Mio23> {}
impl Sdio1CommandPin for Pin<Mio35> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1CommandPin for Pin<Mio47> {}
impl Sdio1Data0Pin for Pin<Mio10> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data0Pin for Pin<Mio22> {}
impl Sdio1Data0Pin for Pin<Mio34> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data0Pin for Pin<Mio46> {}
impl Sdio1Data1Pin for Pin<Mio13> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data1Pin for Pin<Mio25> {}
impl Sdio1Data1Pin for Pin<Mio37> {}
impl Sdio1Data1Pin for Pin<Mio49> {}
impl Sdio1Data2Pin for Pin<Mio14> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data2Pin for Pin<Mio26> {}
impl Sdio1Data2Pin for Pin<Mio38> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data2Pin for Pin<Mio50> {}
impl Sdio1Data2Pin for Pin<Mio15> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data3Pin for Pin<Mio27> {}
impl Sdio1Data3Pin for Pin<Mio39> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl Sdio1Data3Pin for Pin<Mio51> {}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SdioId {
Sdio0,
Sdio1,
}
impl SdioId {
/// Steal the ethernet register block for the given ethernet ID.
///
/// # Safety
///
/// Circumvents ownership and safety guarantees of the HAL.
pub const unsafe fn steal_regs(&self) -> zynq7000::sdio::MmioRegisters<'static> {
unsafe {
match self {
SdioId::Sdio0 => zynq7000::sdio::Registers::new_mmio_fixed_0(),
SdioId::Sdio1 => zynq7000::sdio::Registers::new_mmio_fixed_1(),
}
}
}
}
pub trait SdioRegisters {
fn reg_block(&self) -> zynq7000::sdio::MmioRegisters<'static>;
fn id(&self) -> Option<SdioId>;
}
impl SdioRegisters for zynq7000::sdio::MmioRegisters<'static> {
#[inline]
fn reg_block(&self) -> zynq7000::sdio::MmioRegisters<'static> {
unsafe { self.clone() }
}
#[inline]
fn id(&self) -> Option<SdioId> {
let base_addr = unsafe { self.ptr() } as usize;
if base_addr == SDIO_BASE_ADDR_0 {
return Some(SdioId::Sdio0);
} else if base_addr == SDIO_BASE_ADDR_1 {
return Some(SdioId::Sdio1);
}
None
}
}
pub struct SdioDivisors {
/// Divisor which will be used during the initialization phase when ACMD41 is issued.
///
/// The SD card specification mentions that the clock needs to be between 100 and 400 kHz for
/// that phase.
pub divisor_init_phase: SdClockDivisor,
/// Divisor for the regular data transfer phase. Common target speeds are 25 MHz or 50 MHz.
pub divisor_normal: SdClockDivisor,
}
impl SdioDivisors {
// Calculate the SDIO clock divisors for the given SDIO reference clock and target speed.
pub fn calculate(ref_clk: Hertz, target_speed: Hertz) -> Self {
const INIT_CLOCK_HZ: u32 = 400_000;
let divisor_select_from_value = |value: u32| match value {
0..=1 => SdClockDivisor::Div1,
2 => SdClockDivisor::Div2,
3..=4 => SdClockDivisor::Div4,
5..=8 => SdClockDivisor::Div8,
9..=16 => SdClockDivisor::Div16,
17..=32 => SdClockDivisor::Div32,
33..=64 => SdClockDivisor::Div64,
65..=128 => SdClockDivisor::Div128,
129.. => SdClockDivisor::Div256,
};
Self {
divisor_init_phase: divisor_select_from_value(ref_clk.raw().div_ceil(INIT_CLOCK_HZ)),
divisor_normal: divisor_select_from_value(ref_clk.raw().div_ceil(target_speed.raw())),
}
}
/// Calculate divisors for a regular clock configuration which configures the IO clock as
/// source.
pub fn calculate_for_io_clock(io_clocks: &IoClocks, target_speed: Hertz) -> Self {
Self::calculate(io_clocks.sdio_clk(), target_speed)
}
}
pub struct SdioClockConfig {
/// Selects the source clock for the SDIO peripheral reference clock.
pub src_sel: SrcSelIo,
/// Selects the divisor which divies the source clock to create the SDIO peripheral
/// reference clock.
pub ref_clock_divisor: u6,
/// The SDIO peripheral reference clock is divided again to create the SDIO clock.
pub sdio_clock_divisors: SdioDivisors,
}
impl SdioClockConfig {
pub fn new(
src_sel: SrcSelIo,
ref_clock_divisor: u6,
sdio_clock_divisors: SdioDivisors,
) -> Self {
Self {
src_sel,
ref_clock_divisor,
sdio_clock_divisors,
}
}
pub fn calculate_for_io_clock(
io_clocks: &IoClocks,
target_ref_clock: Hertz,
target_sdio_speed: Hertz,
) -> Option<Self> {
let ref_clk = io_clocks.ref_clk();
let io_ref_clock_divisor = ref_clk.raw().div_ceil(target_ref_clock.raw());
if io_ref_clock_divisor > u6::MAX.as_u32() {
return None;
}
let target_speed = ref_clk / io_ref_clock_divisor;
let sdio_clock_divisors = SdioDivisors::calculate(target_speed, target_sdio_speed);
Some(Self {
src_sel: SrcSelIo::IoPll,
ref_clock_divisor: u6::new(io_ref_clock_divisor as u8),
sdio_clock_divisors,
})
}
}
/// SDIO low-level interface.
///
/// Basic building block for higher-level abstraction.
pub struct SdioLowLevel {
id: SdioId,
/// Register block. Direct public access is allowed to allow low-level operations.
pub regs: zynq7000::sdio::MmioRegisters<'static>,
}
impl SdioLowLevel {
/// Create a new SDIO low-level interface from the given register block.
///
/// Returns [None] if the given registers block base address does not correspond to a valid
/// Ethernet peripheral.
pub fn new(regs: zynq7000::sdio::MmioRegisters<'static>) -> Option<Self> {
let id = regs.id()?;
Some(Self { id, regs })
}
/// Common SDIO clock configuration routine which should be called once before using the SDIO.
///
/// This does NOT disable the clock, which should be done before changing the clock
/// configuration. It also does NOT enable the clock.
///
/// It will configure the SDIO peripheral clock as well as initializing the SD clock frequency
/// divisor based on the initial phase divider specified in the [SdioDivisors] field of the
/// configuration.
pub fn configure_clock(&mut self, clock_config: &SdioClockConfig) {
unsafe {
Slcr::with(|slcr| {
slcr.clk_ctrl().modify_sdio_clk_ctrl(|mut val| {
val.set_srcsel(clock_config.src_sel);
val.set_divisor(clock_config.ref_clock_divisor);
if self.id == SdioId::Sdio1 {
val.set_clk_1_act(true);
} else {
val.set_clk_0_act(true);
}
val
});
});
}
self.configure_sd_clock_div_init_phase(&clock_config.sdio_clock_divisors);
}
/// Configure the SD clock divisor for the initialization phase (400 kHz target clock).
pub fn configure_sd_clock_div_init_phase(&mut self, divs: &SdioDivisors) {
self.regs.modify_clock_timeout_sw_reset_control(|mut val| {
val.set_sdclk_frequency_select(divs.divisor_init_phase);
val
});
}
/// Configure the SD clock divisor for the normal phase (regular SDIO speed clock).
pub fn configure_sd_clock_div_normal_phase(&mut self, divs: &SdioDivisors) {
self.regs.modify_clock_timeout_sw_reset_control(|mut val| {
val.set_sdclk_frequency_select(divs.divisor_normal);
val
});
}
#[inline]
pub fn enable_clock(&mut self) {
self.regs.modify_clock_timeout_sw_reset_control(|mut val| {
val.set_sd_clock_enable(true);
val
});
}
#[inline]
pub fn disable_clock(&mut self) {
self.regs.modify_clock_timeout_sw_reset_control(|mut val| {
val.set_sd_clock_enable(false);
val
});
}
/// Reset the SDIO peripheral using the SLCR reset register for SDIO.
pub fn reset(&mut self, cycles: u32) {
reset(self.id, cycles);
}
}
pub struct Sdio {
ll: SdioLowLevel,
}
impl Sdio {
pub fn new_for_sdio_0<
Sdio0Clock: Sdio0ClockPin,
Sdio0Command: Sdio0CommandPin,
Sdio0Data0: Sdio0Data0Pin,
Sdio0Data1: Sdio0Data1Pin,
Sdio0Data2: Sdio0Data2Pin,
Sdio0Data3: Sdio0Data3Pin,
>(
regs: zynq7000::sdio::MmioRegisters<'static>,
clock_config: SdioClockConfig,
clock_pin: Sdio0Clock,
command_pin: Sdio0Command,
data_pins: (Sdio0Data0, Sdio0Data1, Sdio0Data2, Sdio0Data3),
) -> Option<Self> {
let id = regs.id()?;
if id != SdioId::Sdio1 {
return None;
}
Some(Self::new(
regs,
clock_config,
clock_pin,
command_pin,
data_pins,
))
}
pub fn new_for_sdio_1<
Sdio1Clock: Sdio1ClockPin,
Sdio1Command: Sdio1CommandPin,
Sdio1Data0: Sdio1Data0Pin,
Sdio1Data1: Sdio1Data1Pin,
Sdio1Data2: Sdio1Data2Pin,
Sdio1Data3: Sdio1Data3Pin,
>(
regs: zynq7000::sdio::MmioRegisters<'static>,
clock_config: SdioClockConfig,
clock_pin: Sdio1Clock,
command_pin: Sdio1Command,
data_pins: (Sdio1Data0, Sdio1Data1, Sdio1Data2, Sdio1Data3),
) -> Option<Self> {
let id = regs.id()?;
if id != SdioId::Sdio1 {
return None;
}
Some(Self::new(
regs,
clock_config,
clock_pin,
command_pin,
data_pins,
))
}
fn new(
regs: zynq7000::sdio::MmioRegisters<'static>,
clock_config: SdioClockConfig,
clock_pin: impl MioPin,
command_pin: impl MioPin,
data_pins: (impl MioPin, impl MioPin, impl MioPin, impl MioPin),
) -> Self {
let mut ll = SdioLowLevel::new(regs).unwrap();
Self::initialize(&mut ll, &clock_config);
IoPeriphPin::new(clock_pin, MUX_CONF, None);
IoPeriphPin::new(command_pin, MUX_CONF, None);
IoPeriphPin::new(data_pins.0, MUX_CONF, None);
IoPeriphPin::new(data_pins.1, MUX_CONF, None);
IoPeriphPin::new(data_pins.2, MUX_CONF, None);
IoPeriphPin::new(data_pins.3, MUX_CONF, None);
Self { ll }
}
fn initialize(ll: &mut SdioLowLevel, clock_config: &SdioClockConfig) {
ll.reset(10);
// TODO: SW reset for all?
// TODO: Internal clock?
ll.disable_clock();
ll.configure_clock(clock_config);
ll.enable_clock();
// TODO: There is probably some other configuration necessary.. the docs really are not
// complete here..
unsafe {}
}
#[inline]
pub fn regs(&mut self) -> &mut zynq7000::sdio::MmioRegisters<'static> {
&mut self.ll.regs
}
}
/// Reset the SDIO peripheral using the SLCR reset register for SDIO.
///
/// Please note that this function will interfere with an already configured
/// SDIO instance.
#[inline]
pub fn reset(id: SdioId, cycles: u32) {
let assert_reset = match id {
SdioId::Sdio0 => DualRefAndClockReset::builder()
.with_periph1_ref_rst(false)
.with_periph0_ref_rst(true)
.with_periph1_cpu1x_rst(false)
.with_periph0_cpu1x_rst(true)
.build(),
SdioId::Sdio1 => DualRefAndClockReset::builder()
.with_periph1_ref_rst(true)
.with_periph0_ref_rst(false)
.with_periph1_cpu1x_rst(true)
.with_periph0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_sdio(assert_reset);
// Keep it in reset for a few cycle.. not sure if this is necessary.
for _ in 0..cycles {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl().write_sdio(DualRefAndClockReset::DEFAULT);
});
}
}

View File

@@ -29,6 +29,7 @@ pub mod l2_cache;
pub mod mpcore;
pub mod priv_tim;
pub mod qspi;
pub mod sdio;
pub mod slcr;
pub mod spi;
pub mod ttc;
@@ -63,6 +64,8 @@ pub struct Peripherals {
pub qspi: qspi::MmioRegisters<'static>,
pub devcfg: devcfg::MmioRegisters<'static>,
pub xadc: xadc::MmioRegisters<'static>,
pub sdio_0: sdio::MmioRegisters<'static>,
pub sdio_1: sdio::MmioRegisters<'static>,
}
impl Peripherals {
@@ -103,6 +106,8 @@ impl Peripherals {
qspi: qspi::Registers::new_mmio_fixed(),
devcfg: devcfg::Registers::new_mmio_fixed(),
xadc: xadc::Registers::new_mmio_fixed(),
sdio_0: sdio::Registers::new_mmio_fixed_0(),
sdio_1: sdio::Registers::new_mmio_fixed_1(),
}
}
}

457
zynq/zynq7000/src/sdio.rs Normal file
View File

@@ -0,0 +1,457 @@
use arbitrary_int::{u2, u4, u6, u12};
pub const SDIO_BASE_ADDR_0: usize = 0xE010_0000;
pub const SDIO_BASE_ADDR_1: usize = 0xE010_1000;
#[bitbybit::bitenum(u3, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum BufferSize {
_4kB = 0b000,
_8kB = 0b001,
_16kB = 0b010,
_32kB = 0b011,
_64kB = 0b100,
_128kB = 0b101,
_256kB = 0b110,
_512kB = 0b111,
}
#[bitbybit::bitfield(u32, debug)]
pub struct BlockParams {
#[bits(16..=31, rw)]
blocks_count: u16,
#[bits(12..=14, rw)]
buffer_size: BufferSize,
#[bits(0..=11, rw)]
block_size: u12,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum CommandType {
Normal = 0b00,
Suspend = 0b01,
Resume = 0b10,
Abort = 0b11,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum ResponseLength {
NoResponse = 0b00,
ResponseLength136 = 0b01,
ResponseLength48 = 0b10,
ResponseLength48Check = 0b11,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum BlockSelect {
SingleBlock = 0,
MultiBlock = 1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum TransferDirection {
/// Host to card.
Write = 0,
/// Card to host.
Read = 1,
}
#[bitbybit::bitfield(u32, debug)]
pub struct TransferModeAndCommand {
/// Set to command number (CMD0-63, ACMD0-63)
#[bits(24..=29, rw)]
command_index: u6,
#[bits(22..=23, rw)]
command_type: CommandType,
/// Set to [false] for the following:
///
/// 1. Commands using only CMD line (ex. CMD52).
/// 2. Commands with no data transfer but using busy signal on DAT\[0\].
/// 3. Resume Command.
#[bit(21, rw)]
data_is_present: bool,
/// When 1, the host controller checks the index field in the response to see if it has the
/// same value as the command index.
#[bit(20, rw)]
command_index_check_enable: bool,
/// When 1, the host controller checks the CRC field in the response.
#[bit(18, rw)]
command_crc_check_enable: bool,
#[bits(16..=17, rw)]
response_type_select: u2,
#[bit(5, rw)]
multi_single_block_select: BlockSelect,
#[bit(4, rw)]
data_transfer_direction: TransferDirection,
/// Multiple block transfers for memory require CMD12 to stop the transaction. When this bit is
/// 1, the host controller issues CMD12 automatically when completing the last block tranfer.
#[bit(2, rw)]
auto_cmd12_enable: bool,
/// Enable block count register, which is only relevant for multiple block transfers.
#[bit(1, rw)]
block_count_enable: bool,
#[bit(0, rw)]
dma_enable: bool,
}
#[bitbybit::bitfield(u32, debug)]
pub struct PresentState {
#[bit(24, r)]
cmd_line_signal_level: bool,
#[bits(20..=23, r)]
data_line_signal_level: u4,
/// The Write Protect Switch is supported for memory and combo cards. This bit reflects the
/// inversion of the SDx_WP pin.
#[bit(19, r)]
write_protect_switch_level: bool,
/// This bit reflects the inverse value of the SDx_CDn pin.
#[bit(18, r)]
card_detect_pin_level: bool,
/// This bit is used for testing. If it is 0, the Card Detect Pin Level is not stable. If this
/// bit is set to 1, it means the Card Detect Pin Level is stable. The Software Reset For All
/// in the Software Reset Register shall not affect this bit.
#[bit(17, r)]
card_state_stable: bool,
/// This bit indicates whether a card has been inserted. Changing from 0 to 1 generates a Card
/// Insertion interrupt in the Normal Interrupt Status register and changing from 1 to 0
/// generates a Card Removal Interrupt in the Normal Interrupt Status register. The Software
/// Reset For All in the Software Reset register shall not affect this bit. If a Card is
/// removed while its power is on and its clock is oscillating, the HC shall clear SD Bus Power
/// in the Power Control register and SD Clock Enable in the Clock control register. In
/// addition the HD should clear the HC by the Software Reset For All in Software register. The
/// card detect is active regardless of the SD Bus Power.
#[bit(16, r)]
card_inserted: bool,
/// This status is used for non-DMA read transfers. This read only flag indicates that valid
/// data exists in the host side buffer status. If this bit is 1, readable data exists in the
/// buffer. A change of this bit from 1 to 0 occurs when all the block data is read from the
/// buffer. A change of this bit from 0 to 1 occurs when all the block data is ready in the
/// buffer and generates the Buffer Read Ready Interrupt.
#[bit(11, r)]
buffer_readable: bool,
/// This status is used for non-DMA write transfers. This read only flag indicates if space is
/// available for write data. If this bit is 1, data can be written to the buffer. A change of
/// this bit from 1 to 0 occurs when all the block data is written to the buffer. A change of
/// this bit from 0 to 1 occurs when top of block data can be written to the buffer and
/// generates the Buffer Write Ready Interrupt.
#[bit(10, r)]
buffer_writable: bool,
/// This status is used for detecting completion of a read transfer. This bit is set to 1 for
/// either of the following conditions:
///
/// 1. After the end bit of the read command
/// 2. When writing a 1 to continue Request in the Block Gap Control register to restart a read
/// transfer.
///
/// This bit is cleared to 0 for either of the following conditions:
///
/// 1. When the last data block as specified by block length is transferred to the system.
/// 2. When all valid data blocks have been transferred to the system and no current block
/// transfers are being sent as a result of the Stop At Block Gap Request set to 1. A transfer
/// complete interrupt is generated when this bit changes to 0.
#[bit(9, r)]
read_transfer_active: bool,
/// This status indicates a write transfer is active. If this bit is 0, it means no valid write
/// data exists in the HC. This bit is set in either of the following cases: 1. After the end
/// bit of the write command. 2. When writing a 1 to Continue Request in the Block Gap Control
/// register to restart a write transfer.
///
/// This bit is cleared in either of the following cases:
///
/// 1. After getting the CRC status of the last data block as specified by the transfer count
/// (Single or Multiple)
/// 2. After getting a CRC status of any block where data transmission is about to be stopped
/// by a Stop At Block Gap Request.
///
/// During a write transaction, a Block Gap Event interrupt is generated when this bit is
/// changed to 0, as a result of the Stop At Block Gap Request being set. This status is useful
/// for the HD in determining when to issue commands during write busy.
#[bit(8, r)]
write_transfer_active: bool,
#[bit(2, r)]
dat_line_active: bool,
/// This status bit is generated if either the DAT Line Active or the Read transfer Active is
/// set to 1. If this bit is 0, it indicates the HC can issue the next SD command. Commands
/// with busy signal belong to Command Inhibit (DAT) (ex. R1b, R5b type). Changing from 1 to 0
/// generates a Transfer Complete interrupt in the Normal interrupt status register.
#[bit(1, r)]
command_inhibit_dat: bool,
/// 0 indicates the CMD line is not in use and the host controller can issue a SD command
/// using the CMD line. This bit is set immediately after the Command register (00Fh) is
/// written. This bit is cleared when the command response is received. Even if the Command
/// Inhibit (DAT) is set to 1, Commands using only the CMD line can be issued if this bit is 0.
/// Changing from 1 to 0 generates a Command complete interrupt in the Normal Interrupt Status
/// register. If the HC cannot issue the command because of a command conflict error or because
/// of Command Not Issued By Auto CMD12 Error, this bit shall remain 1 and the Command Complete
/// is not set. Status issuing Auto CMD12 is not read from this bit.
#[bit(0, r)]
command_inhibit_cmd: bool,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum DataTransferWidth {
_1bit = 0,
_4bit = 1,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum DmaSelect {
Sdma = 0b00,
Adma1_32bits = 0b01,
Adma2_32bits = 0b10,
Adma2_64bits = 0b11,
}
#[bitbybit::bitenum(u3, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
pub enum SdBusVoltageSelect {
_1_8V = 0b101,
_3_0V = 0b110,
_3_3V = 0b111,
}
#[bitbybit::bitfield(u32, debug)]
pub struct HostPowerBlockgapWakeupControl {
#[bit(26, rw)]
wakeup_event_enable_on_sd_card_removal: bool,
#[bit(25, rw)]
wakeup_event_enable_on_sd_card_insertion: bool,
#[bit(24, rw)]
wakeup_event_enable_on_card_interrupt: bool,
#[bit(19, rw)]
interrupt_at_block_gap: bool,
#[bit(18, rw)]
read_wait_control: bool,
#[bit(17, rw)]
continue_request: bool,
#[bit(16, rw)]
stop_as_block_gap_request: bool,
#[bits(9..=11, rw)]
sd_bus_voltage_select: Option<SdBusVoltageSelect>,
#[bit(8, rw)]
sd_bus_power: bool,
#[bit(7, rw)]
card_detect_signal_detection: bool,
#[bit(6, rw)]
card_detetect_test_level: bool,
#[bits(3..=4, rw)]
dma_select: DmaSelect,
#[bit(2, rw)]
high_speed_enable: bool,
#[bit(1, rw)]
data_transfer_width: DataTransferWidth,
#[bit(0, rw)]
led_control: bool,
}
#[bitbybit::bitenum(u8, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
pub enum SdClockDivisor {
Div256 = 0x80,
Div128 = 0x40,
Div64 = 0x20,
Div32 = 0x10,
Div16 = 0x08,
Div8 = 0x04,
Div4 = 0x02,
Div2 = 0x01,
Div1 = 0x00,
}
#[bitbybit::bitfield(u32, debug)]
pub struct ClockAndTimeoutAndSwResetControl {
#[bit(26, rw)]
software_reset_for_dat_line: bool,
#[bit(25, rw)]
software_reset_for_cmd_line: bool,
#[bit(24, rw)]
software_reset_for_all: bool,
/// Interval: TMCLK * 2^(13 + register value)
///
/// 0b1111 is reserved.
#[bits(16..=19, rw)]
data_timeout_counter_value: u4,
#[bits(8..=15, rw)]
sdclk_frequency_select: Option<SdClockDivisor>,
#[bit(2, rw)]
sd_clock_enable: bool,
#[bit(1, r)]
internal_clock_stable: bool,
#[bit(0, rw)]
internal_clock_enable: bool,
}
#[bitbybit::bitfield(u32, debug)]
pub struct InterruptStatus {
#[bit(29, rw)]
ceata_error_status: bool,
#[bit(28, rw)]
target_response_error: bool,
#[bit(25, rw)]
adma_error: bool,
#[bit(24, rw)]
auto_cmd12_error: bool,
#[bit(23, rw)]
current_limit_error: bool,
#[bit(22, rw)]
data_end_bit_error: bool,
#[bit(21, rw)]
data_crc_error: bool,
#[bit(20, rw)]
data_timeout_error: bool,
#[bit(19, rw)]
command_index_error: bool,
#[bit(18, rw)]
command_end_bit_error: bool,
#[bit(17, rw)]
command_crc_error: bool,
#[bit(16, rw)]
command_timeout_error: bool,
#[bit(15, r)]
error_interrupt: bool,
#[bit(10, rw)]
boot_terminate: bool,
#[bit(9, rw)]
boot_ack_recv: bool,
#[bit(8, r)]
card_interrupt: bool,
#[bit(7, rw)]
card_removal: bool,
#[bit(6, rw)]
card_insertion: bool,
#[bit(5, rw)]
buffer_read_ready: bool,
#[bit(4, rw)]
buffer_write_ready: bool,
#[bit(3, rw)]
dma_interrupt: bool,
#[bit(2, rw)]
blockgap_event: bool,
#[bit(1, rw)]
transfer_complete: bool,
#[bit(0, rw)]
command_complete: bool,
}
#[bitbybit::bitfield(u32, debug)]
pub struct InterruptMask {
#[bit(29, rw)]
ceata_error_status: bool,
#[bit(28, rw)]
target_response_error: bool,
#[bit(25, rw)]
adma_error: bool,
#[bit(24, rw)]
auto_cmd12_error: bool,
#[bit(23, rw)]
current_limit_error: bool,
#[bit(22, rw)]
data_end_bit_error: bool,
#[bit(21, rw)]
data_crc_error: bool,
#[bit(20, rw)]
data_timeout_error: bool,
#[bit(19, rw)]
command_index_error: bool,
#[bit(18, rw)]
command_end_bit_error: bool,
#[bit(17, rw)]
command_crc_error: bool,
#[bit(16, rw)]
command_timeout_error: bool,
#[bit(15, rw)]
error_interrupt: bool,
#[bit(10, rw)]
boot_terminate: bool,
#[bit(9, rw)]
boot_ack_recv: bool,
#[bit(8, rw)]
card_interrupt: bool,
#[bit(7, rw)]
card_removal: bool,
#[bit(6, rw)]
card_insertion: bool,
#[bit(5, rw)]
buffer_read_ready: bool,
#[bit(4, rw)]
buffer_write_ready: bool,
#[bit(3, rw)]
dma_interrupt: bool,
#[bit(2, rw)]
blockgap_event: bool,
#[bit(1, rw)]
transfer_complete: bool,
#[bit(0, rw)]
command_complete: bool,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Registers {
sdma_system_addr: u32,
block_params: u32,
/// Bit 39-8 of Command-Format.
argument: u32,
transfer_mode_and_command: TransferModeAndCommand,
#[mmio(PureRead)]
responses: [u32; 4],
buffer_data_port: u32,
#[mmio(PureRead)]
present_state: PresentState,
host_power_blockgap_wakeup_control: HostPowerBlockgapWakeupControl,
clock_timeout_sw_reset_control: ClockAndTimeoutAndSwResetControl,
interrupt_status: InterruptStatus,
interrupt_status_enable: InterruptMask,
interrupt_signal_enable: InterruptMask,
#[mmio(PureRead)]
auto_cmd12_error_status: u32,
#[mmio(PureRead)]
capabilities: u32,
_reserved_0: u32,
#[mmio(PureRead)]
maximum_current_capabilities: u32,
_reserved_1: u32,
force_event_register: u32,
adma_error_status: u32,
adma_system_address: u32,
_reserved_2: u32,
boot_timeout_control: u32,
debug_selection: u32,
_reserved_3: [u32; 0x22],
spi_interrupt_support: u32,
_reserved_4: [u32; 0x2],
slot_interrupt_status_host_controll_version: u32,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Registers>(), 0x100);
impl Registers {
/// Create a new SDIO MMIO instance for SDIO 0 at address [SDIO_BASE_ADDR_0].
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed_0() -> MmioRegisters<'static> {
unsafe { Self::new_mmio_at(SDIO_BASE_ADDR_0) }
}
/// Create a new SDIO MMIO instance for SDIO 1 at address [SDIO_BASE_ADDR_1].
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed_1() -> MmioRegisters<'static> {
unsafe { Self::new_mmio_at(SDIO_BASE_ADDR_1) }
}
}