diff --git a/zynq/examples/zedboard/src/bin/sdio.rs b/zynq/examples/zedboard/src/bin/sdio.rs new file mode 100644 index 0000000..99b17e3 --- /dev/null +++ b/zynq/examples/zedboard/src/bin/sdio.rs @@ -0,0 +1,156 @@ +#![no_std] +#![no_main] + +use aarch32_cpu::asm::nop; +use core::panic::PanicInfo; +use embassy_executor::Spawner; +use embassy_time::{Duration, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use embedded_io::Write; +use log::{error, info}; +use zedboard::PS_CLOCK_FREQUENCY; +use zynq7000_hal::gpio::Input; +use zynq7000_hal::prelude::*; +use zynq7000_hal::{ + BootMode, clocks, gic, gpio, gtc, + sdio::{Sdio, SdioClockConfig}, + uart, +}; + +use zynq7000_rt as _; + +const INIT_STRING: &str = "-- Zynq 7000 Zedboard SDIO example --\n\r"; + +/// Entry point (not called like a normal main function) +#[unsafe(no_mangle)] +pub extern "C" fn boot_core(cpu_id: u32) -> ! { + if cpu_id != 0 { + panic!("unexpected CPU ID {}", cpu_id); + } + main(); +} + +#[embassy_executor::main] +#[unsafe(export_name = "main")] +async fn main(_spawner: Spawner) -> ! { + let periphs = zynq7000_hal::init(zynq7000_hal::Config { + init_l2_cache: true, + level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll), + interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0), + }) + .unwrap(); + // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. + let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + + let gpio_pins = gpio::GpioPins::new(periphs.gpio); + + // Set up global timer counter and embassy time driver. + let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks()); + zynq7000_embassy::init(clocks.arm_clocks(), gtc); + + // Set up the UART, we are logging with it. + let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = uart::Uart::new_with_mio_for_uart_1( + periphs.uart_1, + uart::Config::new_with_clk_config(uart_clk_config), + (gpio_pins.mio.mio48, gpio_pins.mio.mio49), + ) + .unwrap(); + uart.write_all(INIT_STRING.as_bytes()).unwrap(); + // Safety: We are not multi-threaded yet. + unsafe { + zynq7000_hal::log::uart_blocking::init_unsafe_single_core( + uart, + log::LevelFilter::Trace, + false, + ) + }; + + let sdio_clock_config = + SdioClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap(); + let sdio = Sdio::new_for_sdio_0( + periphs.sdio_0, + sdio_clock_config, + gpio_pins.mio.mio40, + gpio_pins.mio.mio41, + ( + gpio_pins.mio.mio42, + gpio_pins.mio.mio43, + gpio_pins.mio.mio44, + gpio_pins.mio.mio45, + ), + ) + .unwrap(); + let card_detect = Input::new_for_mio(gpio_pins.mio.mio47).unwrap(); + let write_protect = Input::new_for_mio(gpio_pins.mio.mio46).unwrap(); + // The card detect being active low makes sense according to the Zedboard docs. Not sure + // about write-protect though.. It seems that write protect on means that the + // the pin is pulled high. + info!("Card detect state: {:?}", card_detect.is_low()); + info!("Write protect state: {:?}", write_protect.is_high()); + + let capabilities = sdio.ll().capabilities(); + info!("SDIO Capabilities: {:?}", capabilities); + + let boot_mode = BootMode::new_from_regs(); + info!("Boot mode: {:?}", boot_mode); + + let mut ticker = Ticker::every(Duration::from_millis(200)); + + let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); + loop { + mio_led.toggle().unwrap(); + + ticker.next().await; // Wait for the next cycle of the ticker + } +} + +#[zynq7000_rt::irq] +fn irq_handler() { + let mut gic_helper = gic::GicInterruptHelper::new(); + let irq_info = gic_helper.acknowledge_interrupt(); + match irq_info.interrupt() { + gic::Interrupt::Sgi(_) => (), + gic::Interrupt::Ppi(ppi_interrupt) => { + if ppi_interrupt == gic::PpiInterrupt::GlobalTimer { + unsafe { + zynq7000_embassy::on_interrupt(); + } + } + } + gic::Interrupt::Spi(_spi_interrupt) => (), + gic::Interrupt::Invalid(_) => (), + gic::Interrupt::Spurious => (), + } + gic_helper.end_of_interrupt(irq_info); +} + +#[zynq7000_rt::exception(DataAbort)] +fn data_abort_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +#[zynq7000_rt::exception(Undefined)] +fn undefined_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +#[zynq7000_rt::exception(PrefetchAbort)] +fn prefetch_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +/// Panic handler +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("Panic: {info:?}"); + loop {} +} diff --git a/zynq/zynq7000-hal/src/eth/ll.rs b/zynq/zynq7000-hal/src/eth/ll.rs index ef5f0cf..25af4a5 100644 --- a/zynq/zynq7000-hal/src/eth/ll.rs +++ b/zynq/zynq7000-hal/src/eth/ll.rs @@ -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 { 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); + }); + } +} diff --git a/zynq/zynq7000-hal/src/lib.rs b/zynq/zynq7000-hal/src/lib.rs index e2cf379..ce9ffc8 100644 --- a/zynq/zynq7000-hal/src/lib.rs +++ b/zynq/zynq7000-hal/src/lib.rs @@ -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; diff --git a/zynq/zynq7000-hal/src/sdio.rs b/zynq/zynq7000-hal/src/sdio.rs new file mode 100644 index 0000000..de367a5 --- /dev/null +++ b/zynq/zynq7000-hal/src/sdio.rs @@ -0,0 +1,491 @@ +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::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, +}; + +#[derive(Debug, thiserror::Error)] +#[error("invalid peripheral instance")] +pub struct InvalidPeripheralError; + +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 {} +impl Sdio0ClockPin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0ClockPin for Pin {} + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0CommandPin for Pin {} +impl Sdio0CommandPin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0CommandPin for Pin {} + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data0Pin for Pin {} +impl Sdio0Data0Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data0Pin for Pin {} + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data1Pin for Pin {} +impl Sdio0Data1Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data1Pin for Pin {} + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data2Pin for Pin {} +impl Sdio0Data2Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data2Pin for Pin {} + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data3Pin for Pin {} +impl Sdio0Data3Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio0Data3Pin for Pin {} + +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 {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1ClockPin for Pin {} +impl Sdio1ClockPin for Pin {} +impl Sdio1ClockPin for Pin {} + +impl Sdio1CommandPin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1CommandPin for Pin {} +impl Sdio1CommandPin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1CommandPin for Pin {} + +impl Sdio1Data0Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data0Pin for Pin {} +impl Sdio1Data0Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data0Pin for Pin {} + +impl Sdio1Data1Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data1Pin for Pin {} +impl Sdio1Data1Pin for Pin {} +impl Sdio1Data1Pin for Pin {} + +impl Sdio1Data2Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data2Pin for Pin {} +impl Sdio1Data2Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data2Pin for Pin {} + +impl Sdio1Data2Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data3Pin for Pin {} +impl Sdio1Data3Pin for Pin {} +#[cfg(not(feature = "7z010-7z007s-clg225"))] +impl Sdio1Data3Pin for Pin {} + +#[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; +} + +impl SdioRegisters for zynq7000::sdio::MmioRegisters<'static> { + #[inline] + fn reg_block(&self) -> zynq7000::sdio::MmioRegisters<'static> { + unsafe { self.clone() } + } + + #[inline] + fn id(&self) -> Option { + 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 { + 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 { + let id = regs.id()?; + Some(Self { id, regs }) + } + + #[inline] + pub fn capabilities(&self) -> zynq7000::sdio::Capabilities { + self.regs.read_capabilities() + } + + /// 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_internal_clock(&mut self) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_internal_clock_enable(true); + val + }); + } + + #[inline] + pub fn enable_sd_clock(&mut self) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_sd_clock_enable(true); + val + }); + } + + #[inline] + pub fn disable_sd_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), + ) -> Result { + let id = regs.id().ok_or(InvalidPeripheralError)?; + if id != SdioId::Sdio0 { + return Err(InvalidPeripheralError); + } + Ok(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), + ) -> Result { + let id = regs.id().ok_or(InvalidPeripheralError)?; + if id != SdioId::Sdio1 { + return Err(InvalidPeripheralError); + } + Ok(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 } + } + + /// Direct access to the low-level SDIO driver. + pub fn ll_mut(&mut self) -> &mut SdioLowLevel { + &mut self.ll + } + + pub fn ll(&self) -> &SdioLowLevel { + &self.ll + } + + fn initialize(ll: &mut SdioLowLevel, clock_config: &SdioClockConfig) { + ll.reset(10); + ll.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_software_reset_for_all(true); + val + }); + ll.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_software_reset_for_all(false); + val + }); + // Explicitely clear the clock bit. + ll.disable_sd_clock(); + ll.configure_clock(clock_config); + // As specified in the TRM, wait until the internal clock is stable before enabling the + // SD clock. + ll.enable_internal_clock(); + while !ll + .regs + .read_clock_timeout_sw_reset_control() + .internal_clock_stable() + {} + ll.enable_sd_clock(); + + // TODO: Perform the regular SDIO setup sequence. + } + + #[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); + }); + } +} diff --git a/zynq/zynq7000/src/lib.rs b/zynq/zynq7000/src/lib.rs index 379654d..1c7134b 100644 --- a/zynq/zynq7000/src/lib.rs +++ b/zynq/zynq7000/src/lib.rs @@ -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(), } } } diff --git a/zynq/zynq7000/src/sdio.rs b/zynq/zynq7000/src/sdio.rs new file mode 100644 index 0000000..367c3ed --- /dev/null +++ b/zynq/zynq7000/src/sdio.rs @@ -0,0 +1,489 @@ +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, + #[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, + #[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, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Capabilities { + #[bit(30, rw)] + spi_block_mode: bool, + #[bit(29, rw)] + spi_mode: bool, + #[bit(28, rw)] + _64_bit_system_bus_support: bool, + #[bit(27, rw)] + interrupt_mode: bool, + #[bit(26, rw)] + voltage_support_1_8v: bool, + #[bit(25, rw)] + voltage_support_3_0v: bool, + #[bit(24, rw)] + voltage_support_3_3v: bool, + #[bit(23, rw)] + suspend_resume_support: bool, + #[bit(22, rw)] + sdma_support: bool, + #[bit(21, rw)] + high_speed_support: bool, + #[bit(19, rw)] + adma2_support: bool, + #[bit(18, rw)] + extended_media_bus_support: bool, + #[bits(16..=17, rw)] + max_block_length: u2, + #[bit(7, rw)] + timeout_clock_unit: 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: Capabilities, + _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::(), 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) } + } +}