From 3890795aa95d07f16916cb7a849606407a3aa9b2 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 18 Oct 2025 23:24:36 +0200 Subject: [PATCH] initial SD card support --- firmware/examples/zedboard/Cargo.toml | 1 + firmware/examples/zedboard/src/bin/sdio.rs | 275 ++++ firmware/zynq7000-hal/Cargo.toml | 6 +- firmware/zynq7000-hal/src/eth/ll.rs | 78 +- firmware/zynq7000-hal/src/lib.rs | 1 + firmware/zynq7000-hal/src/qspi/mod.rs | 8 +- firmware/zynq7000-hal/src/sd/commands.rs | 132 ++ firmware/zynq7000-hal/src/sd/mod.rs | 1445 ++++++++++++++++++++ firmware/zynq7000-hal/src/sd/pins.rs | 99 ++ firmware/zynq7000-hal/src/spi/mod.rs | 11 +- firmware/zynq7000-hal/src/uart/mod.rs | 15 +- firmware/zynq7000/CHANGELOG.md | 1 + firmware/zynq7000/src/lib.rs | 5 + firmware/zynq7000/src/sdio.rs | 509 +++++++ firmware/zynq7000/src/slcr/reset.rs | 120 +- 15 files changed, 2640 insertions(+), 66 deletions(-) create mode 100644 firmware/examples/zedboard/src/bin/sdio.rs create mode 100644 firmware/zynq7000-hal/src/sd/commands.rs create mode 100644 firmware/zynq7000-hal/src/sd/mod.rs create mode 100644 firmware/zynq7000-hal/src/sd/pins.rs create mode 100644 firmware/zynq7000/src/sdio.rs diff --git a/firmware/examples/zedboard/Cargo.toml b/firmware/examples/zedboard/Cargo.toml index d5bd2a9..ff02140 100644 --- a/firmware/examples/zedboard/Cargo.toml +++ b/firmware/examples/zedboard/Cargo.toml @@ -35,6 +35,7 @@ rand = { version = "0.10", default-features = false } embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] } embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] } embassy-net = { version = "0.9", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] } +embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" } embassy-sync = { version = "0.8" } heapless = "0.9" axi-uartlite = { version = "0.1" } diff --git a/firmware/examples/zedboard/src/bin/sdio.rs b/firmware/examples/zedboard/src/bin/sdio.rs new file mode 100644 index 0000000..e99879a --- /dev/null +++ b/firmware/examples/zedboard/src/bin/sdio.rs @@ -0,0 +1,275 @@ +#![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; +use zedboard::PS_CLOCK_FREQUENCY; +use zynq7000_hal::gpio::Input; +use zynq7000_hal::prelude::*; +use zynq7000_hal::sd::SdClockConfig; +use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, sd::SdCardUninit, uart}; + +use zynq7000_rt as _; + +const INIT_STRING: &str = "-- Zynq 7000 Zedboard SDIO example --\n\r"; + +// These are off by default because they write to the SD card as well. +const LOW_LEVEL_TESTS: bool = false; +const SDMMC_RS_TESTS: bool = false; + +#[derive(Debug, Clone, Copy)] +pub struct DummyTimeSource; + +impl embedded_sdmmc::TimeSource for DummyTimeSource { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + embedded_sdmmc::Timestamp::from_calendar(1970, 1, 1, 0, 0, 0).unwrap() + } +} + +/// Entry point which calls the embassy main method. +#[zynq7000_rt::entry] +fn entry_point() -> ! { + 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 = + SdClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap(); + log::info!("SDIO clock config: {:?}", sdio_clock_config); + let sd_card_uninit = SdCardUninit::new_for_sdio_0( + periphs.sdio_0, + sdio_clock_config, + // On the zedboard, the bank has a 1.8 V voltage which is shifted up to 3.3 V by a + // level shifter. + zynq7000_hal::sd::IoType::LvCmos18, + 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. + log::info!("Card detect state: {:?}", card_detect.is_low()); + log::info!("Write protect state: {:?}", write_protect.is_high()); + + let capabilities = sd_card_uninit.ll().capabilities(); + log::debug!("SDIO Capabilities: {:?}", capabilities); + + let present_state = sd_card_uninit.ll().read_present_state(); + log::debug!("SD present state: {:?}", present_state); + + let boot_mode = BootMode::new_from_regs(); + log::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); + + let sd_result = sd_card_uninit.initialize(); + let sd_card = match sd_result { + Ok(card) => { + log::info!("SD card info: {:?}", card.card_info()); + card + } + Err(e) => { + panic!("SDIO init error: {e:?}"); + } + }; + + let mut buf: [u8; 4096] = [0; 4096]; + + if LOW_LEVEL_TESTS { + log::info!("doing SD card low-level tests"); + + let mut cache_buf: [u8; 4096] = [0; 4096]; + + // cache the data, will be written back later.. + sd_card + .read_multiple_blocks(&mut cache_buf, 0x1000) + .unwrap(); + + let mut write_data: [u8; 4096] = [0; 4096]; + for chunk in write_data.chunks_mut(u8::MAX as usize) { + for (idx, byte) in chunk.iter_mut().enumerate() { + *byte = idx as u8; + } + } + sd_card.write_multiple_blocks(&write_data, 0x1000).unwrap(); + + sd_card.read_multiple_blocks(&mut buf, 0x1000).unwrap(); + for chunk in buf.chunks(u8::MAX as usize) { + for (idx, byte) in chunk.iter().enumerate() { + assert_eq!(idx as u8, *byte); + } + } + + sd_card.write_multiple_blocks(&cache_buf, 0x1000).unwrap(); + + log::info!("SD card low-level tests success"); + } + + buf.fill(0); + + if SDMMC_RS_TESTS { + log::info!("doing SD card embedded-sdmmc-rs tests"); + + // Now let's look for volumes (also known as partitions) on our block device. + // To do this we need a Volume Manager. It will take ownership of the block device. + let volume_mgr = embedded_sdmmc::VolumeManager::new(sd_card, DummyTimeSource); + // Try and access Volume 0 (i.e. the first partition). + // The volume object holds information about the filesystem on that volume. + let volume0 = volume_mgr + .open_volume(embedded_sdmmc::VolumeIdx(0)) + .unwrap(); + + // Open the root directory (mutably borrows from the volume). + let mut current_dir = volume0.open_root_dir().unwrap(); + log::info!("iterating root directory"); + current_dir + .iterate_dir(|entry| { + log::info!("{:?}", entry); + core::ops::ControlFlow::Continue(()) + }) + .unwrap(); + + let new_file = current_dir + .open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate) + .unwrap(); + let string = "test string\n"; + new_file.write(string.as_bytes()).unwrap(); + new_file.close().unwrap(); + + let read_new = current_dir + .open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadOnly) + .unwrap(); + assert_eq!(read_new.length(), string.len() as u32); + read_new.read(&mut buf).unwrap(); + let buf_as_str = core::str::from_utf8(&buf[0..string.len()]).unwrap(); + assert_eq!(buf_as_str, string); + read_new.close().unwrap(); + + current_dir.delete_entry_in_dir("__T.TXT").unwrap(); + + assert_eq!( + current_dir.find_directory_entry("__T.TXT").unwrap_err(), + embedded_sdmmc::Error::NotFound + ); + + if current_dir.find_directory_entry("_TDIR").is_ok() { + current_dir.delete_entry_in_dir("_TDIR").unwrap(); + } + + current_dir.make_dir_in_dir("_TDIR").unwrap(); + current_dir.change_dir("_TDIR").unwrap(); + current_dir.change_dir("..").unwrap(); + current_dir.delete_entry_in_dir("_TDIR").unwrap(); + + current_dir.close().unwrap(); + + log::info!("SD card embedded-sdmmc-rs success"); + } + + 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/firmware/zynq7000-hal/Cargo.toml b/firmware/zynq7000-hal/Cargo.toml index 527a614..6dac235 100644 --- a/firmware/zynq7000-hal/Cargo.toml +++ b/firmware/zynq7000-hal/Cargo.toml @@ -34,14 +34,16 @@ fugit = "0.3" critical-section = "1" libm = "0.2" log = "0.4" -embassy-sync = "0.7" +embassy-sync = "0.8" embassy-net-driver = "0.2" -smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] } +smoltcp = { version = "0.13", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] } vcell = "0.1" raw-slicee = "0.1" embedded-io-async = "0.7" serde = { version = "1", optional = true, features = ["derive"] } defmt = { version = "1", optional = true } +embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" } +bytemuck = "1.25" [features] std = ["thiserror/std", "alloc"] diff --git a/firmware/zynq7000-hal/src/eth/ll.rs b/firmware/zynq7000-hal/src/eth/ll.rs index ef5f0cf..25af4a5 100644 --- a/firmware/zynq7000-hal/src/eth/ll.rs +++ b/firmware/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/firmware/zynq7000-hal/src/lib.rs b/firmware/zynq7000-hal/src/lib.rs index 9f0cbdf..756b9df 100644 --- a/firmware/zynq7000-hal/src/lib.rs +++ b/firmware/zynq7000-hal/src/lib.rs @@ -38,6 +38,7 @@ pub mod log; pub mod prelude; pub mod priv_tim; pub mod qspi; +pub mod sd; pub mod slcr; pub mod spi; pub mod time; diff --git a/firmware/zynq7000-hal/src/qspi/mod.rs b/firmware/zynq7000-hal/src/qspi/mod.rs index 5046441..f988e78 100644 --- a/firmware/zynq7000-hal/src/qspi/mod.rs +++ b/firmware/zynq7000-hal/src/qspi/mod.rs @@ -8,7 +8,7 @@ use zynq7000::{ BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay, SpiEnable, }, - slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl}, + slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::ResetControlQspiSmc}, }; pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode}; @@ -675,8 +675,8 @@ pub fn reset() { unsafe { Slcr::with(|regs| { regs.reset_ctrl().write_lqspi( - QspiResetControl::builder() - .with_qspi_ref_reset(true) + ResetControlQspiSmc::builder() + .with_ref_reset(true) .with_cpu_1x_reset(true) .build(), ); @@ -684,7 +684,7 @@ pub fn reset() { for _ in 0..3 { aarch32_cpu::asm::nop(); } - regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT); + regs.reset_ctrl().write_lqspi(ResetControlQspiSmc::DEFAULT); }); } } diff --git a/firmware/zynq7000-hal/src/sd/commands.rs b/firmware/zynq7000-hal/src/sd/commands.rs new file mode 100644 index 0000000..81ac681 --- /dev/null +++ b/firmware/zynq7000-hal/src/sd/commands.rs @@ -0,0 +1,132 @@ +use arbitrary_int::u6; +use zynq7000::sdio::{BlockSelect, CommandRegister, ResponseType}; + +use embedded_sdmmc::sdcard::{AcmdId, CmdId}; + +pub struct CommandConfig { + pub id: u6, + pub response_type: ResponseType, + pub index_check: bool, + pub crc_check: bool, +} + +impl CommandConfig { + pub const fn new_no_response(id: u6) -> Self { + Self { + id, + response_type: ResponseType::None, + index_check: false, + crc_check: false, + } + } + + pub const fn new_with_r1_response(id: u6) -> Self { + Self { + id, + response_type: ResponseType::_48bits, + index_check: true, + crc_check: true, + } + } + + pub const fn new_with_r2_response(id: u6) -> Self { + Self { + id, + response_type: ResponseType::_136bits, + index_check: false, + crc_check: true, + } + } + + pub const fn new_with_r3_response(id: u6) -> Self { + Self { + id, + response_type: ResponseType::_48bits, + index_check: false, + crc_check: false, + } + } + + pub const fn new_with_r6_response(id: u6) -> Self { + Self { + id, + response_type: ResponseType::_48bitsWithCheck, + index_check: false, + crc_check: false, + } + } +} + +pub const fn build_command_without_data(config: CommandConfig) -> CommandRegister { + CommandRegister::builder() + .with_command_index(config.id) + .with_command_type(zynq7000::sdio::CommandType::Normal) + .with_data_is_present(false) + .with_command_index_check_enable(config.index_check) + .with_command_crc_check_enable(config.crc_check) + .with_response_type_select(config.response_type) + .with_block_select(zynq7000::sdio::BlockSelect::SingleBlock) + .with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write) + .with_auto_cmd12_enable(false) + .with_block_count_enable(false) + .with_dma_enable(false) + .build() +} + +pub const CMD0_GO_IDLE_MODE: CommandRegister = build_command_without_data( + CommandConfig::new_no_response(CmdId::CMD0_GoIdleState.raw_value()), +); +pub const CMD2_ALL_SEND_CID: CommandRegister = build_command_without_data( + CommandConfig::new_with_r2_response(CmdId::CMD2_AllSendCid.raw_value()), +); +pub const CMD3_SEND_RELATIVE_ADDR: CommandRegister = build_command_without_data( + CommandConfig::new_with_r6_response(CmdId::CMD3_SendRelativeAddr.raw_value()), +); +pub const CMD7_SELECT_SD_CARD: CommandRegister = build_command_without_data( + CommandConfig::new_with_r1_response(CmdId::CMD7_SelectCard.raw_value()), +); +pub const CMD8_SEND_IF_COND: CommandRegister = build_command_without_data( + CommandConfig::new_with_r1_response(CmdId::CMD8_SendIfCond.raw_value()), +); +pub const CMD9_SEND_CSD: CommandRegister = build_command_without_data( + CommandConfig::new_with_r2_response(CmdId::CMD9_SendCsd.raw_value()), +); +pub const CMD13_SEND_STATUS: CommandRegister = build_command_without_data( + CommandConfig::new_with_r1_response(CmdId::CMD13_SendStatus.raw_value()), +); +pub const CMD17_READ_SINGLE_BLOCK: CommandRegister = CommandRegister::builder() + .with_command_index(CmdId::CMD17_ReadSingleBlock.raw_value()) + .with_command_type(zynq7000::sdio::CommandType::Normal) + .with_data_is_present(true) + .with_command_index_check_enable(true) + .with_command_crc_check_enable(true) + .with_response_type_select(ResponseType::_48bits) + .with_block_select(BlockSelect::SingleBlock) + .with_data_transfer_direction(zynq7000::sdio::TransferDirection::Read) + .with_auto_cmd12_enable(false) + .with_block_count_enable(false) + .with_dma_enable(false) + .build(); +pub const CMD24_WRITE_BLOCK: CommandRegister = CommandRegister::builder() + .with_command_index(CmdId::CMD24_WriteBlock.raw_value()) + .with_command_type(zynq7000::sdio::CommandType::Normal) + .with_data_is_present(true) + .with_command_index_check_enable(true) + .with_command_crc_check_enable(true) + .with_response_type_select(ResponseType::_48bits) + .with_block_select(BlockSelect::SingleBlock) + .with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write) + .with_auto_cmd12_enable(false) + .with_block_count_enable(false) + .with_dma_enable(false) + .build(); + +pub const CMD55_APP_CMD: CommandRegister = build_command_without_data( + CommandConfig::new_with_r1_response(CmdId::CMD55_AppCmd.raw_value()), +); +pub const ACMD6_SET_BUS_WIDTH: CommandRegister = build_command_without_data( + CommandConfig::new_with_r1_response(AcmdId::ACMD6_SetBusWidth.raw_value()), +); +pub const ACMD41_SEND_IF_COND: CommandRegister = build_command_without_data( + CommandConfig::new_with_r3_response(AcmdId::ACMD41_SdSendOpCond.raw_value()), +); diff --git a/firmware/zynq7000-hal/src/sd/mod.rs b/firmware/zynq7000-hal/src/sd/mod.rs new file mode 100644 index 0000000..67c57c7 --- /dev/null +++ b/firmware/zynq7000-hal/src/sd/mod.rs @@ -0,0 +1,1445 @@ +use arbitrary_int::{traits::Integer as _, u3, u4, u6, u12}; +use embedded_sdmmc::sdcard::{ + CardType, + argument::{self, OcrLower}, + csd::{Csd, InvalidCsdStructureFieldError}, + response::{self, R1, R6}, +}; +use zynq7000::{ + sdio::{ + BusWidth, CommandRegister, InterruptMask, InterruptStatus, PresentState, SDIO_BASE_ADDR_0, + SDIO_BASE_ADDR_1, SdClockDivisor, + }, + slcr::{clocks::SrcSelIo, reset::DualRefAndClockResetSdio}, +}; + +use crate::{ + clocks::IoClocks, + enable_amba_peripheral_clock, + gpio::{ + IoPeriphPin, + mio::{MioPin, MuxConfig}, + }, + slcr::Slcr, + time::Hertz, +}; + +pub use zynq7000::slcr::mio::IoType; + +pub mod commands; +pub mod pins; + +pub const MUX_CONF: MuxConfig = MuxConfig::new_with_l3(u3::new(0b100)); +pub const BLOCK_LEN: usize = 512; + +/// Negotiated as part of ACMD41 during SD card initialization. +pub const VOLTAGE_LEVEL_CAPABILITIES: OcrLower = OcrLower::builder() + .with__3_5_to_3_6v(false) + .with__3_4_to_3_5v(false) + .with__3_3_to_3_4v(false) + .with__3_2_to_3_3v(true) + .with__3_1_to_3_2v(false) + .with__3_0_to_3_1v(false) + .with__2_9_to_3_0v(false) + .with__2_8_to_2_9v(false) + .with__2_7_to_2_8v(false) + .with_reserved_low_voltage(false) + .build(); + +/// Information retrieved from the SD card during the card initialization process. +#[derive(Debug, Clone)] +pub struct SdCardInfo { + card_type: CardType, + rca: u16, + cid: embedded_sdmmc::sdcard::cid::Cid, + csd: embedded_sdmmc::sdcard::csd::Csd, +} + +impl SdCardInfo { + #[inline] + pub fn card_type(&self) -> CardType { + self.card_type + } + + /// Relative card address (RCA). + #[inline] + pub fn rca(&self) -> u16 { + self.rca + } + + #[inline] + pub fn csd(&self) -> &embedded_sdmmc::sdcard::csd::Csd { + &self.csd + } + + #[inline] + pub fn cid(&self) -> &embedded_sdmmc::sdcard::cid::Cid { + &self.cid + } +} + +#[derive(Debug, thiserror::Error)] +#[error("invalid peripheral instance")] +pub struct InvalidPeripheralError; + +#[derive(Debug, Clone, Copy, thiserror::Error)] +pub enum MciError { + #[error("response CRC")] + ResponseCrc, + #[error("response end bit")] + ResponseEndBit, + #[error("response timeout")] + ResponseTimeout, + #[error("data CRC")] + DataCrc, + #[error("data timeout")] + DataTimeout, + #[error("data end bit")] + DataEndBit, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ResponseErrorBits { + index: bool, + crc: bool, + end_bit: bool, + timeout: bool, +} + +impl ResponseErrorBits { + #[inline] + pub const fn has_error(&self) -> bool { + self.index || self.crc || self.end_bit || self.timeout + } + + #[inline] + pub const fn index(&self) -> bool { + self.index + } + + #[inline] + pub const fn crc(&self) -> bool { + self.crc + } + + #[inline] + pub const fn end_bit(&self) -> bool { + self.end_bit + } + + #[inline] + pub const fn response_timeout(&self) -> bool { + self.timeout + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct DataErrorBits { + crc: bool, + timeout: bool, + end_bit: bool, +} + +impl DataErrorBits { + #[inline] + pub const fn crc(&self) -> bool { + self.crc + } + + #[inline] + pub const fn timeout(&self) -> bool { + self.timeout + } +} + +#[derive(Debug, Copy, Clone)] +pub struct StatusWrapper(pub zynq7000::sdio::InterruptStatus); + +impl StatusWrapper { + #[inline] + pub fn response_errors(&self) -> ResponseErrorBits { + ResponseErrorBits { + index: self.0.command_index_error(), + crc: self.0.command_crc_error(), + end_bit: self.0.command_end_bit_error(), + timeout: self.0.command_timeout_error(), + } + } + + #[inline] + pub fn has_response_errors(&self) -> bool { + self.0.command_index_error() + || self.0.command_crc_error() + || self.0.command_end_bit_error() + || self.0.command_timeout_error() + } + + #[inline] + pub fn has_data_error(&self) -> bool { + self.0.data_timeout_error() || self.0.data_crc_error() || self.0.data_end_bit_error() + } + + #[inline] + pub fn data_errors(&self) -> DataErrorBits { + DataErrorBits { + crc: self.0.data_crc_error(), + timeout: self.0.data_timeout_error(), + end_bit: self.0.data_end_bit_error(), + } + } +} + +#[derive(Debug, Copy, Clone, thiserror::Error)] +pub enum InitializationError { + #[error("response error")] + ResponseError(ResponseErrorBits), + #[error("unexpected response")] + UnexpectedResponse, + #[error("unexpected SD state")] + UnexpectedState, + #[error("invalid CSD: {0}")] + Csd(#[from] InvalidCsdStructureFieldError), + #[error("invalid CID")] + Cid(#[from] embedded_sdmmc::sdcard::cid::ChecksumInvalidError), +} + +#[derive(Debug, Copy, Clone)] +pub struct InitializationErrorWithStep { + step: InitStep, + error: InitializationError, +} + +impl InitializationErrorWithStep { + #[inline] + pub fn error(&self) -> InitializationError { + self.error + } + + #[inline] + pub fn step(&self) -> InitStep { + self.step + } +} + +#[derive(Debug, Copy, Clone)] +pub enum InitStep { + /// CMD0. + IdleCommand, + SendingIfCondCmd8, + SendingIfCondAcmd41, + RequestCid, + RequestCsd, + /// CMD3. + RequestRca, + PutIntoTransferState, + /// ACMD6. + SetBusWidth, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SdioId { + _0, + _1, +} + +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::_0 => zynq7000::sdio::Registers::new_mmio_fixed_0(), + SdioId::_1 => 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::_0); + } else if base_addr == SDIO_BASE_ADDR_1 { + return Some(SdioId::_1); + } + None + } +} + +#[derive(Debug, Clone, Copy)] +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) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SdClockConfig { + /// 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, + /// High speed clock configuration, SD speed higher than 25 MHz or MMC speed higher than 20 MHz. + pub high_speed: bool, +} + +impl SdClockConfig { + pub fn new( + src_sel: SrcSelIo, + ref_clock_divisor: u6, + sdio_clock_divisors: SdioDivisors, + high_speed: bool, + ) -> Self { + Self { + src_sel, + ref_clock_divisor, + sdio_clock_divisors, + high_speed, + } + } + + pub fn calculate_for_io_clock( + io_clocks: &IoClocks, + target_ref_clock: Hertz, + target_sd_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_sd_speed); + Some(Self { + src_sel: SrcSelIo::IoPll, + ref_clock_divisor: u6::new(io_ref_clock_divisor as u8), + sdio_clock_divisors, + high_speed: target_sd_speed > Hertz::MHz(25), + }) + } +} +/// SD low-level interface. +/// +/// Basic building block for higher-level abstractions. +pub struct SdLowLevel { + id: SdioId, + /// Register block. Direct public access is allowed to allow low-level operations. + pub regs: zynq7000::sdio::MmioRegisters<'static>, +} + +impl SdLowLevel { + /// 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 }) + } + + /// # Safety + /// + /// Allows getting multiple handles to the same SDIO peripheral. + pub unsafe fn clone(&self) -> Self { + Self { + id: self.id, + regs: unsafe { self.regs.clone() }, + } + } + + #[inline] + pub fn disable(&mut self, with_reset: bool, internal_clock_disable: bool) { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_sd_clock_enable(false); + val + }); + if with_reset { + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_software_reset_for_all(true); + val.set_internal_clock_enable(!internal_clock_disable); + val + }); + while self + .regs + .read_clock_timeout_sw_reset_control() + .software_reset_for_all() + {} + } + } + + #[inline] + pub fn capabilities(&self) -> zynq7000::sdio::Capabilities { + self.regs.read_capabilities() + } + + #[inline] + pub fn read_status(&self) -> StatusWrapper { + StatusWrapper(self.regs.read_interrupt_status()) + } + + #[inline] + pub fn read_data_word(&self) -> u32 { + self.regs.read_buffer_data_port() + } + + #[inline] + pub fn write_data_word(&mut self, word: u32) { + self.regs.write_buffer_data_port(word) + } + + /// 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: &SdClockConfig) { + 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::_1 { + 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); + } + + pub fn initialize(&mut self, clock_config: &SdClockConfig) { + if self.id == SdioId::_0 { + enable_amba_peripheral_clock(crate::PeriphSelect::Sdio0); + } else { + enable_amba_peripheral_clock(crate::PeriphSelect::Sdio1); + } + self.reset(5); + + // Full software reset. + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_software_reset_for_all(true); + val + }); + while self + .regs + .read_clock_timeout_sw_reset_control() + .software_reset_for_all() + {} + + // Configure the clock which also configures the initial 400 KHz clock. + self.configure_clock(clock_config); + + // Power off. + self.regs + .modify_host_power_blockgap_wakeup_control(|mut val| { + val.set_sd_bus_voltage_select(zynq7000::sdio::SdBusVoltageSelect::Off); + val + }); + + // Explicitely clear the clock bit. + self.disable_sd_clock(); + + // Set the only voltage configuration that is supported. + self.regs + .modify_host_power_blockgap_wakeup_control(|mut val| { + val.set_sd_bus_voltage_select(zynq7000::sdio::SdBusVoltageSelect::_3_3V); + val.set_sd_bus_power(true); + val + }); + + // As specified in the TRM, wait until the internal clock is stable before enabling the + // SD clock. + self.enable_internal_clock(); + while !self + .regs + .read_clock_timeout_sw_reset_control() + .internal_clock_stable() + {} + self.enable_sd_clock(); + + // This is the value configured by the AMD driver. + self.regs.modify_clock_timeout_sw_reset_control(|mut val| { + val.set_data_timeout_counter_value(u4::new(0xE)); + val + }); + // Set standard block size 512 bytes. + self.regs.modify_block_params(|mut val| { + val.set_transfer_block_size(u12::new(512)); + val + }); + + // Enable status bits without card interrupt bit. + self.regs.write_interrupt_status_enable( + InterruptMask::new_with_raw_value(0xFFFF_FFFF).with_card_interrupt(false), + ); + // Disable all interrupts signals. + self.regs.write_interrupt_signal_enable(InterruptMask::ZERO); + + // Clear status bits. + self.clear_all_status_bits(); + } + + #[inline] + pub fn read_u32_response(&self) -> u32 { + // Index valid, unwrap okay. + self.regs.read_responses(0).unwrap() + } + + #[inline] + pub fn read_u128_response(&self, buf: &mut [u32; 4]) -> [u8; 16] { + for (index, word) in buf.iter_mut().rev().enumerate() { + *word = self.regs.read_responses(index).unwrap().to_be() + } + let bytes: &mut [u8; 16] = bytemuck::cast_mut(buf); + // For some reason the hardware withholds the last byte and we have to do some copying + // to ensure the correct layout. + bytes.copy_within(1.., 0); + // The checksum is withheld from us. I have no idea why. Maybe the hardware verifies it + // for us? Did not find docs on this. + bytes[15] = 0; + *bytes + } + + /// 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 + }); + } + + /// Send a command with an argument and wait for command completion. Returns the status + /// register which can be used to check for errors. + /// + /// The response can be read from the response registers, depending on the sent command + /// and the error flags. + pub fn send_command(&mut self, cmd: CommandRegister, arg: u32) -> StatusWrapper { + self.write_command(cmd, arg); + loop { + let status = self.regs.read_interrupt_status(); + if status.command_complete() || status.error_interrupt() { + self.regs.write_interrupt_status(status); + return StatusWrapper(status); + } + } + } + + #[inline] + pub fn read_present_state(&self) -> PresentState { + self.regs.read_present_state() + } + + #[inline] + pub fn write_command(&mut self, command: CommandRegister, arg: u32) { + self.clear_all_status_bits(); + self.regs.write_argument(arg); + self.regs.write_command(command) + } + + #[inline] + pub fn clear_all_status_bits(&mut self) { + self.regs + .write_interrupt_status(InterruptStatus::new_with_raw_value( + InterruptStatus::ALL_BITS, + )); + } + + #[inline] + pub fn clear_status_bits(&mut self, status: InterruptStatus) { + self.regs.write_interrupt_status(status); + } + + /// Reset the SDIO peripheral using the SLCR reset register for SDIO. + pub fn reset(&mut self, cycles: u32) { + reset(self.id, cycles); + } +} + +/// SD card which has not been initialized yet. +/// +/// It configures the physical SD pins correctly and exposes an [Self::initialize]r method +/// which converts itself into a [SdCard]. +pub struct SdCardUninit { + ll: SdLowLevel, + clock_config: SdClockConfig, +} + +impl SdCardUninit { + pub fn new_for_sdio_0< + Sdio0Clock: pins::Sdio0ClockPin, + Sdio0Command: pins::Sdio0CommandPin, + Sdio0Data0: pins::Sdio0Data0Pin, + Sdio0Data1: pins::Sdio0Data1Pin, + Sdio0Data2: pins::Sdio0Data2Pin, + Sdio0Data3: pins::Sdio0Data3Pin, + >( + regs: zynq7000::sdio::MmioRegisters<'static>, + clock_config: SdClockConfig, + voltage_setting: IoType, + clock_pin: Sdio0Clock, + command_pin: Sdio0Command, + data_pins: (Sdio0Data0, Sdio0Data1, Sdio0Data2, Sdio0Data3), + ) -> Result { + let id = regs.id().ok_or(InvalidPeripheralError)?; + if id != SdioId::_0 { + return Err(InvalidPeripheralError); + } + Ok(Self::new( + regs, + clock_config, + voltage_setting, + clock_pin, + command_pin, + data_pins, + )) + } + + pub fn new_for_sdio_1< + Sdio1Clock: pins::Sdio1ClockPin, + Sdio1Command: pins::Sdio1CommandPin, + Sdio1Data0: pins::Sdio1Data0Pin, + Sdio1Data1: pins::Sdio1Data1Pin, + Sdio1Data2: pins::Sdio1Data2Pin, + Sdio1Data3: pins::Sdio1Data3Pin, + >( + regs: zynq7000::sdio::MmioRegisters<'static>, + clock_config: SdClockConfig, + voltage_setting: IoType, + clock_pin: Sdio1Clock, + command_pin: Sdio1Command, + data_pins: (Sdio1Data0, Sdio1Data1, Sdio1Data2, Sdio1Data3), + ) -> Result { + let id = regs.id().ok_or(InvalidPeripheralError)?; + if id != SdioId::_1 { + return Err(InvalidPeripheralError); + } + Ok(Self::new( + regs, + clock_config, + voltage_setting, + clock_pin, + command_pin, + data_pins, + )) + } + + fn new( + regs: zynq7000::sdio::MmioRegisters<'static>, + clock_config: SdClockConfig, + voltage_setting: IoType, + clock_pin: impl MioPin, + command_pin: impl MioPin, + data_pins: (impl MioPin, impl MioPin, impl MioPin, impl MioPin), + ) -> Self { + let ll = SdLowLevel::new(regs).unwrap(); + // Configuration taken from ps7_init.html. + unsafe { + crate::slcr::Slcr::with(|slcr| { + IoPeriphPin::new_with_full_config_and_unlocked_slcr( + clock_pin, + slcr, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage_setting) + .with_speed(zynq7000::slcr::mio::Speed::FastCmosEdge) + .with_l3_sel(MUX_CONF.l3_sel()) + .with_l2_sel(MUX_CONF.l2_sel()) + .with_l1_sel(MUX_CONF.l1_sel()) + .with_l0_sel(MUX_CONF.l0_sel()) + .with_tri_enable(false) + .build(), + ); + IoPeriphPin::new_with_full_config_and_unlocked_slcr( + command_pin, + slcr, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage_setting) + .with_speed(zynq7000::slcr::mio::Speed::FastCmosEdge) + .with_l3_sel(MUX_CONF.l3_sel()) + .with_l2_sel(MUX_CONF.l2_sel()) + .with_l1_sel(MUX_CONF.l1_sel()) + .with_l0_sel(MUX_CONF.l0_sel()) + .with_tri_enable(false) + .build(), + ); + IoPeriphPin::new_with_full_config_and_unlocked_slcr( + data_pins.0, + slcr, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage_setting) + .with_speed(zynq7000::slcr::mio::Speed::FastCmosEdge) + .with_l3_sel(MUX_CONF.l3_sel()) + .with_l2_sel(MUX_CONF.l2_sel()) + .with_l1_sel(MUX_CONF.l1_sel()) + .with_l0_sel(MUX_CONF.l0_sel()) + .with_tri_enable(false) + .build(), + ); + IoPeriphPin::new_with_full_config_and_unlocked_slcr( + data_pins.1, + slcr, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage_setting) + .with_speed(zynq7000::slcr::mio::Speed::FastCmosEdge) + .with_l3_sel(MUX_CONF.l3_sel()) + .with_l2_sel(MUX_CONF.l2_sel()) + .with_l1_sel(MUX_CONF.l1_sel()) + .with_l0_sel(MUX_CONF.l0_sel()) + .with_tri_enable(false) + .build(), + ); + IoPeriphPin::new_with_full_config_and_unlocked_slcr( + data_pins.2, + slcr, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage_setting) + .with_speed(zynq7000::slcr::mio::Speed::FastCmosEdge) + .with_l3_sel(MUX_CONF.l3_sel()) + .with_l2_sel(MUX_CONF.l2_sel()) + .with_l1_sel(MUX_CONF.l1_sel()) + .with_l0_sel(MUX_CONF.l0_sel()) + .with_tri_enable(false) + .build(), + ); + IoPeriphPin::new_with_full_config_and_unlocked_slcr( + data_pins.3, + slcr, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage_setting) + .with_speed(zynq7000::slcr::mio::Speed::FastCmosEdge) + .with_l3_sel(MUX_CONF.l3_sel()) + .with_l2_sel(MUX_CONF.l2_sel()) + .with_l1_sel(MUX_CONF.l1_sel()) + .with_l0_sel(MUX_CONF.l0_sel()) + .with_tri_enable(false) + .build(), + ); + }); + } + Self { ll, clock_config } + } + + /// Direct access to the low-level SDIO driver. + pub fn ll_mut(&mut self) -> &mut SdLowLevel { + &mut self.ll + } + + pub fn ll(&self) -> &SdLowLevel { + &self.ll + } + + /// Performs the initialization sequence and converts itself into a [SdCard]. + pub fn initialize(mut self) -> Result { + let sd_info = initialize_card(&mut self.ll, &self.clock_config)?; + Ok(SdCard { + ll: core::cell::RefCell::new(self.ll), + clock_config: self.clock_config, + sd_info, + }) + } + + #[inline] + pub fn send_command(&mut self, command: CommandRegister, argument: u32) -> StatusWrapper { + self.ll.send_command(command, argument) + } + + #[inline] + pub fn read_u32_response(&self) -> u32 { + self.ll.read_u32_response() + } + + #[inline] + pub fn regs(&mut self) -> &mut zynq7000::sdio::MmioRegisters<'static> { + &mut self.ll.regs + } +} + +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum DeviceError { + #[error("response error")] + ResponseError(ResponseErrorBits), + #[error("data error")] + DataError(DataErrorBits), + #[error("unexpected card state: {0:?}")] + UnexpectedState(response::State), + #[error("unexpected response")] + UnexpectedResponse, + #[error("invalid block size, larger than 512, or not multiple of 512")] + InvalidBlockSize, + #[error("too many blocks for a single multi-block transfer")] + TooManyBlocks, +} + +pub struct SdCard { + ll: core::cell::RefCell, + clock_config: SdClockConfig, + sd_info: SdCardInfo, +} + +impl SdCard { + pub fn reinitialize(&mut self) -> Result<(), InitializationErrorWithStep> { + let mut ll = self.ll.borrow_mut(); + ll.disable(true, false); + ll.initialize(&self.clock_config); + drop(ll); + + let sd_info = initialize_card(self.ll.get_mut(), &self.clock_config)?; + self.sd_info = sd_info; + Ok(()) + } + + /// # Safety + /// + /// Allows to create multiple handles to the same SD card. + pub unsafe fn clone(&self) -> SdCard { + unsafe { + Self { + ll: core::cell::RefCell::new(self.ll.borrow_mut().clone()), + clock_config: self.clock_config, + sd_info: self.sd_info.clone(), + } + } + } + + #[inline] + pub fn ll(&self) -> core::cell::RefMut<'_, SdLowLevel> { + self.ll.borrow_mut() + } + + pub fn send_command(&self, cmd: CommandRegister, arg: u32) -> StatusWrapper { + self.ll.borrow_mut().send_command(cmd, arg) + } + + #[inline] + pub fn card_info(&self) -> &SdCardInfo { + &self.sd_info + } + + #[inline] + pub fn rca(&self) -> u16 { + self.sd_info.rca + } + + #[inline] + pub fn read_u32_response(&self) -> u32 { + self.ll.borrow_mut().read_u32_response() + } + + pub fn read_status(&self) -> Result { + let status = self.send_command( + commands::CMD13_SEND_STATUS, + argument::Cmd13::builder() + .with_rca(self.sd_info.rca) + .with_send_task_status(false) + .build() + .raw_value(), + ); + if status.has_response_errors() { + return Err(DeviceError::ResponseError(status.response_errors())); + } + Ok(R1::new_with_raw_value(self.read_u32_response())) + } + + /// Card IDentification (CID). + #[inline] + pub fn cid(&self) -> &embedded_sdmmc::sdcard::cid::Cid { + &self.sd_info.cid + } + + /// Card specific data (CSD). + #[inline] + pub fn csd(&self) -> &embedded_sdmmc::sdcard::csd::Csd { + &self.sd_info.csd + } + + /// Card type. + #[inline] + pub fn card_type(&self) -> CardType { + self.sd_info.card_type + } + + pub fn verify_sd_card_transfer_state( + &self, + target_state: response::State, + ) -> Result<(), DeviceError> { + let card_status = self.read_status()?; + match card_status.state() { + Ok(state) => { + if state != target_state { + return Err(DeviceError::UnexpectedState(state)); + } + } + Err(_) => return Err(DeviceError::UnexpectedResponse), + } + Ok(()) + } + + /// Read a single block from the SD card at the given address. + pub fn read_single_block(&self, buffer: &mut [u8], addr: u32) -> Result<(), DeviceError> { + pub enum State { + Reading, + WaitForCompletion, + } + + let mut state = State::Reading; + if buffer.len() > BLOCK_LEN { + return Err(DeviceError::InvalidBlockSize); + } + self.verify_sd_card_transfer_state(response::State::Tran)?; + let status = self.send_command(commands::CMD17_READ_SINGLE_BLOCK, addr); + if status.has_response_errors() { + return Err(DeviceError::ResponseError(status.response_errors())); + } + let mut ll = self.ll(); + let mut bytes_read = 0; + loop { + let status = ll.read_status(); + if status.has_data_error() { + ll.clear_status_bits(status.0); + return Err(DeviceError::DataError(status.data_errors())); + } + match state { + State::Reading => { + if status.0.buffer_read_ready() { + ll.clear_status_bits(status.0); + + while bytes_read < BLOCK_LEN { + let word = ll.read_data_word(); + buffer[bytes_read..bytes_read + 4].copy_from_slice(&word.to_ne_bytes()); + bytes_read += 4; + } + state = State::WaitForCompletion; + } + } + State::WaitForCompletion => { + if status.0.transfer_complete() { + ll.clear_status_bits(status.0); + break; + } + } + } + } + Ok(()) + } + + /// Read multiple blocks from the SD card at the given start address. + pub fn read_multiple_blocks( + &self, + buffer: &mut [u8], + mut addr: u32, + ) -> Result<(), DeviceError> { + // TODO: Proper multi-read implementation. + if !buffer.len().is_multiple_of(BLOCK_LEN) { + return Err(DeviceError::InvalidBlockSize); + } + // TODO: Support by doing multiple multi-block transfers? + if (buffer.len() / BLOCK_LEN) > u16::MAX as usize { + return Err(DeviceError::TooManyBlocks); + } + for block in buffer.chunks_mut(BLOCK_LEN) { + self.read_single_block(block, addr)?; + addr = addr.saturating_add(BLOCK_LEN as u32); + } + Ok(()) + } + + /// Write a single block to the SD card at the given address. + pub fn write_single_block(&self, buffer: &[u8], addr: u32) -> Result<(), DeviceError> { + pub enum State { + Writing, + WaitForCompletion, + } + + let mut state = State::Writing; + if buffer.len() > BLOCK_LEN { + return Err(DeviceError::InvalidBlockSize); + } + self.verify_sd_card_transfer_state(response::State::Tran)?; + // Write command first. + let status = self.send_command(commands::CMD24_WRITE_BLOCK, addr); + if status.has_response_errors() { + return Err(DeviceError::ResponseError(status.response_errors())); + } + let mut ll = self.ll(); + // Wait for the FIFO to be ready. + loop { + let status = ll.read_status(); + if status.has_data_error() { + ll.clear_status_bits(status.0); + return Err(DeviceError::DataError(status.data_errors())); + } + match state { + State::Writing => { + if status.0.buffer_write_ready() { + ll.clear_status_bits(status.0); + let mut bytes_written = 0; + while bytes_written < BLOCK_LEN { + ll.write_data_word(u32::from_ne_bytes( + buffer[bytes_written..bytes_written + 4].try_into().unwrap(), + )); + bytes_written += 4; + } + state = State::WaitForCompletion; + } + } + State::WaitForCompletion => { + if status.0.transfer_complete() { + break; + } + } + } + } + + // Wait until the SD card is done with programming. + drop(ll); + loop { + let status = self.read_status()?; + let state = status.state(); + if state.is_err() { + return Err(DeviceError::UnexpectedResponse); + } + if let Ok(state) = state + && state == response::State::Tran + { + return Ok(()); + } + } + } + + /// Write multiple blocks from the SD card at the given start address. + pub fn write_multiple_blocks(&self, buffer: &[u8], mut addr: u32) -> Result<(), DeviceError> { + // TODO: Proper multi-write implementation. + if !buffer.len().is_multiple_of(BLOCK_LEN) { + return Err(DeviceError::InvalidBlockSize); + } + // TODO: Support by doing multiple multi-block transfers? + if (buffer.len() / BLOCK_LEN) > u16::MAX as usize { + return Err(DeviceError::TooManyBlocks); + } + for block in buffer.chunks(BLOCK_LEN) { + self.write_single_block(block, addr)?; + addr = addr.saturating_add(BLOCK_LEN as u32); + } + Ok(()) + } +} + +impl embedded_sdmmc::BlockDevice for SdCard { + type Error = DeviceError; + + fn read( + &self, + blocks: &mut [embedded_sdmmc::Block], + start_block_idx: embedded_sdmmc::BlockIdx, + ) -> Result<(), Self::Error> { + let addr = match self.sd_info.card_type { + CardType::SD1 | CardType::SD2 => start_block_idx.0 * BLOCK_LEN as u32, + CardType::SdhcSdxc => start_block_idx.0, + }; + for block in blocks.iter_mut() { + self.read_single_block(block.as_mut_slice(), addr)?; + } + Ok(()) + } + + fn write( + &self, + blocks: &[embedded_sdmmc::Block], + start_block_idx: embedded_sdmmc::BlockIdx, + ) -> Result<(), Self::Error> { + let (mut addr, increment) = match self.sd_info.card_type { + CardType::SD1 | CardType::SD2 => (start_block_idx.0 * BLOCK_LEN as u32, BLOCK_LEN), + CardType::SdhcSdxc => (start_block_idx.0, 1), + }; + for block in blocks.iter() { + self.write_single_block(block.as_slice(), addr)?; + addr = addr.saturating_add(increment as u32); + } + Ok(()) + } + + fn num_blocks(&self) -> Result { + Ok(embedded_sdmmc::BlockCount( + self.csd().card_capacity_blocks(), + )) + } +} + +/// Card initialization sequence. +/// +/// This performs the steps specified in chapter 4 of the SD card specification. +/// It also retrieves the [SdCardInfo] structure from the SD card during this process. +pub fn initialize_card( + ll: &mut SdLowLevel, + clock_config: &SdClockConfig, +) -> Result { + const CMD8_RETRIES: usize = 2; + + ll.initialize(clock_config); + + let status = ll.send_command(commands::CMD0_GO_IDLE_MODE, 0); + if status.has_response_errors() { + return Err(InitializationErrorWithStep { + step: InitStep::IdleCommand, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + + // Voltage level negotiation. + let mut index = 0; + let mut status_cmd8 = ll.send_command( + commands::CMD8_SEND_IF_COND, + argument::Cmd8::ZERO + .with_voltage_supplied( + embedded_sdmmc::sdcard::argument::VoltageSuppliedSelect::_2_7To3_6V, + ) + .with_check_pattern(0xAA) + .raw_value(), + ); + while status_cmd8.0.command_timeout_error() && index < CMD8_RETRIES { + status_cmd8 = ll.send_command( + commands::CMD8_SEND_IF_COND, + argument::Cmd8::ZERO + .with_voltage_supplied( + embedded_sdmmc::sdcard::argument::VoltageSuppliedSelect::_2_7To3_6V, + ) + .with_check_pattern(0xAA) + .raw_value(), + ); + index += 1; + } + + let responded_to_cmd8 = !status_cmd8.0.command_timeout_error(); + let acmd41_arg = if responded_to_cmd8 { + let r7 = response::R7::new_with_raw_value(ll.read_u32_response()); + if !r7.voltage_accepted().is_ok_and(|val| { + val == embedded_sdmmc::sdcard::argument::VoltageSuppliedSelect::_2_7To3_6V + }) || r7.echo_check_pattern() != 0xAA + { + return Err(InitializationErrorWithStep { + step: InitStep::SendingIfCondCmd8, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + argument::Acmd41::builder() + .with_host_capacity_support( + embedded_sdmmc::sdcard::argument::HostCapacitySupport::SdhcOrSdxc, + ) + .with_fast_boot(false) + .with_xpc(embedded_sdmmc::sdcard::argument::PowerControl::MaximumPerformance) + .with_s18r(false) + .with_ocr(VOLTAGE_LEVEL_CAPABILITIES) + .build() + } else { + argument::Acmd41::builder() + .with_host_capacity_support( + embedded_sdmmc::sdcard::argument::HostCapacitySupport::SdscOnly, + ) + .with_fast_boot(false) + .with_xpc(embedded_sdmmc::sdcard::argument::PowerControl::MaximumPerformance) + .with_s18r(false) + .with_ocr(VOLTAGE_LEVEL_CAPABILITIES) + .build() + }; + send_acmd(ll, commands::ACMD41_SEND_IF_COND, acmd41_arg.raw_value(), 0).map_err(|e| { + InitializationErrorWithStep { + step: InitStep::SendingIfCondAcmd41, + error: e, + } + })?; + + let mut r3 = embedded_sdmmc::sdcard::response::R3::new_with_raw_value(ll.read_u32_response()); + if !r3.initialization_complete() { + r3 = wait_for_amd41_card_ready(ll, 0).map_err(|e| InitializationErrorWithStep { + step: InitStep::SendingIfCondAcmd41, + error: e, + })?; + }; + let card_type = if responded_to_cmd8 { + if r3.card_capacity_status() { + CardType::SdhcSdxc + } else { + CardType::SD2 + } + } else { + CardType::SD1 + }; + + // Retrieve and cache the CID. This puts it into identification mode. + let status = ll.send_command(commands::CMD2_ALL_SEND_CID, 0); + if status.has_response_errors() { + return Err(InitializationErrorWithStep { + step: InitStep::RequestCid, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + let mut cid_buf: [u32; 4] = [0; 4]; + let cid_raw = ll.read_u128_response(&mut cid_buf); + let cid = embedded_sdmmc::sdcard::cid::Cid::new_with_raw_value(u128::from_be_bytes(cid_raw)); + + // Send CMD3 to retrieve RCA required for card addressing, as well as put the card + // into standby mode. + let status = ll.send_command(commands::CMD3_SEND_RELATIVE_ADDR, 0); + if status.has_response_errors() { + return Err(InitializationErrorWithStep { + step: InitStep::RequestRca, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + let r6 = R6::new_with_raw_value(ll.read_u32_response()); + let rca = r6.rca(); + + // Retrieve and cache CSD, which also contains card specific data. + let status = ll.send_command( + commands::CMD9_SEND_CSD, + argument::Cmd9::builder() + .with_rca(r6.rca()) + .build() + .raw_value(), + ); + if status.has_response_errors() { + return Err(InitializationErrorWithStep { + step: InitStep::RequestCsd, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + let mut csd_buf: [u32; 4] = [0; 4]; + let csd_raw = ll.read_u128_response(&mut csd_buf); + let csd = Csd::new_unchecked(&csd_raw).map_err(|e| InitializationErrorWithStep { + step: InitStep::RequestCsd, + error: InitializationError::Csd(e), + })?; + + // CMD7 to put the SD card into transfer state. + let status = ll.send_command( + commands::CMD7_SELECT_SD_CARD, + argument::Cmd7::builder() + .with_rca(r6.rca()) + .build() + .raw_value(), + ); + if status.has_response_errors() { + return Err(InitializationErrorWithStep { + step: InitStep::PutIntoTransferState, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + // Check that the card is in transfer mode. + let status = ll.send_command( + commands::CMD13_SEND_STATUS, + argument::Cmd13::builder() + .with_rca(r6.rca()) + .with_send_task_status(false) + .build() + .raw_value(), + ); + if status.has_response_errors() { + return Err(InitializationErrorWithStep { + step: InitStep::PutIntoTransferState, + error: InitializationError::ResponseError(status.response_errors()), + }); + } + let r1 = R1::new_with_raw_value(ll.read_u32_response()); + if r1.state().is_ok_and(|state| state != response::State::Tran) { + return Err(InitializationErrorWithStep { + step: InitStep::PutIntoTransferState, + error: InitializationError::UnexpectedResponse, + }); + } + + // Put the SD card into 4-bit mode. + send_acmd( + ll, + commands::ACMD6_SET_BUS_WIDTH, + argument::Acmd6::builder() + .with_bus_width(argument::BusWidth::_4bits) + .build() + .raw_value(), + rca, + ) + .map_err(|e| InitializationErrorWithStep { + step: InitStep::SetBusWidth, + error: e, + })?; + ll.regs + .modify_host_power_blockgap_wakeup_control(|mut val| { + val.set_bus_width(BusWidth::_4bits); + val.set_high_speed_enable(clock_config.high_speed); + val + }); + ll.configure_sd_clock_div_normal_phase(&clock_config.sdio_clock_divisors); + + Ok(SdCardInfo { + card_type, + rca, + cid, + csd, + }) +} + +/// Send an ACMD. +pub fn send_acmd( + ll: &mut SdLowLevel, + acmd: CommandRegister, + arg: u32, + rca: u16, +) -> Result { + let status = ll.send_command(commands::CMD55_APP_CMD, (rca as u32) << 16); + if status.has_response_errors() { + return Err(InitializationError::ResponseError(status.response_errors())); + } + let r1 = embedded_sdmmc::sdcard::response::R1::new_with_raw_value(ll.read_u32_response()); + if !r1.app_cmd() { + return Err(InitializationError::UnexpectedResponse); + } + Ok(ll.send_command(acmd, arg)) +} + +pub fn wait_for_amd41_card_ready( + ll: &mut SdLowLevel, + rca: u16, +) -> Result { + loop { + let status = send_acmd(ll, commands::ACMD41_SEND_IF_COND, 0, rca)?; + // Explicitely check only for timeouts here. CRC errors have occured here.. + if status.0.command_timeout_error() { + return Err(InitializationError::ResponseError(status.response_errors())); + } + let r3 = embedded_sdmmc::sdcard::response::R3::new_with_raw_value(ll.read_u32_response()); + if r3.initialization_complete() { + return Ok(r3); + } + } +} + +/// 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::_0 => DualRefAndClockResetSdio::builder() + .with_periph1_ref_rst(false) + .with_periph0_ref_rst(true) + .with_periph1_cpu1x_rst(false) + .with_periph0_cpu1x_rst(true) + .build(), + SdioId::_1 => DualRefAndClockResetSdio::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(DualRefAndClockResetSdio::ZERO); + }); + } +} diff --git a/firmware/zynq7000-hal/src/sd/pins.rs b/firmware/zynq7000-hal/src/sd/pins.rs new file mode 100644 index 0000000..fef4d85 --- /dev/null +++ b/firmware/zynq7000-hal/src/sd/pins.rs @@ -0,0 +1,99 @@ +use crate::gpio::mio::{ + Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34, + Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, MioPin, Pin, +}; +#[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, +}; + +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 {} diff --git a/firmware/zynq7000-hal/src/spi/mod.rs b/firmware/zynq7000-hal/src/spi/mod.rs index b9c5efc..6b9f996 100644 --- a/firmware/zynq7000-hal/src/spi/mod.rs +++ b/firmware/zynq7000-hal/src/spi/mod.rs @@ -18,7 +18,7 @@ use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz}; use arbitrary_int::{prelude::*, u3, u4, u6}; use embedded_hal::delay::DelayNs; pub use embedded_hal::spi::Mode; -use zynq7000::slcr::reset::DualRefAndClockReset; +use zynq7000::slcr::reset::DualRefAndClockResetSpiUart; use zynq7000::spi::{ BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus, MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR, @@ -1113,13 +1113,13 @@ impl embedded_hal::spi::SpiDevice for SpiWithHwCs { #[inline] pub fn reset(id: SpiId) { let assert_reset = match id { - SpiId::Spi0 => DualRefAndClockReset::builder() + SpiId::Spi0 => DualRefAndClockResetSpiUart::builder() .with_periph1_ref_rst(false) .with_periph0_ref_rst(true) .with_periph1_cpu1x_rst(false) .with_periph0_cpu1x_rst(true) .build(), - SpiId::Spi1 => DualRefAndClockReset::builder() + SpiId::Spi1 => DualRefAndClockResetSpiUart::builder() .with_periph1_ref_rst(true) .with_periph0_ref_rst(false) .with_periph1_cpu1x_rst(true) @@ -1131,10 +1131,11 @@ pub fn reset(id: SpiId) { regs.reset_ctrl().write_spi(assert_reset); // Keep it in reset for some cycles.. The TMR just mentions some small delay, // no idea what is meant with that. - for _ in 0..3 { + for _ in 0..5 { aarch32_cpu::asm::nop(); } - regs.reset_ctrl().write_spi(DualRefAndClockReset::DEFAULT); + regs.reset_ctrl() + .write_spi(DualRefAndClockResetSpiUart::ZERO); }); } } diff --git a/firmware/zynq7000-hal/src/uart/mod.rs b/firmware/zynq7000-hal/src/uart/mod.rs index d986696..98fa658 100644 --- a/firmware/zynq7000-hal/src/uart/mod.rs +++ b/firmware/zynq7000-hal/src/uart/mod.rs @@ -13,7 +13,7 @@ use core::convert::Infallible; use arbitrary_int::u3; use libm::round; use zynq7000::{ - slcr::reset::DualRefAndClockReset, + slcr::reset::DualRefAndClockResetSpiUart, uart::{ BaudRateDivisor, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl, MmioRegisters, Mode, UART_0_BASE, UART_1_BASE, @@ -720,13 +720,13 @@ impl embedded_io::Read for Uart { #[inline] pub fn reset(id: UartId) { let assert_reset = match id { - UartId::Uart0 => DualRefAndClockReset::builder() + UartId::Uart0 => DualRefAndClockResetSpiUart::builder() .with_periph1_ref_rst(false) .with_periph0_ref_rst(true) .with_periph1_cpu1x_rst(false) .with_periph0_cpu1x_rst(true) .build(), - UartId::Uart1 => DualRefAndClockReset::builder() + UartId::Uart1 => DualRefAndClockResetSpiUart::builder() .with_periph1_ref_rst(true) .with_periph0_ref_rst(false) .with_periph1_cpu1x_rst(true) @@ -736,9 +736,12 @@ pub fn reset(id: UartId) { unsafe { Slcr::with(|regs| { regs.reset_ctrl().write_uart(assert_reset); - // Keep it in reset for one cycle.. not sure if this is necessary. - aarch32_cpu::asm::nop(); - regs.reset_ctrl().write_uart(DualRefAndClockReset::DEFAULT); + // Keep it in reset for a few cycles.. not sure if this is necessary. + for _ in 0..5 { + aarch32_cpu::asm::nop(); + } + regs.reset_ctrl() + .write_uart(DualRefAndClockResetSpiUart::ZERO); }); } } diff --git a/firmware/zynq7000/CHANGELOG.md b/firmware/zynq7000/CHANGELOG.md index 39ff509..ac98383 100644 --- a/firmware/zynq7000/CHANGELOG.md +++ b/firmware/zynq7000/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Updated IPTR registers in the GIC module to use a custom register type instead of a raw u32. - Added SDIO registers. - Fixed wrong position in QSPI reset register in SLCR Module +- Added some missing reset register definitions. # [v0.1.1] 2025-10-09 diff --git a/firmware/zynq7000/src/lib.rs b/firmware/zynq7000/src/lib.rs index 379654d..1c7134b 100644 --- a/firmware/zynq7000/src/lib.rs +++ b/firmware/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/firmware/zynq7000/src/sdio.rs b/firmware/zynq7000/src/sdio.rs new file mode 100644 index 0000000..312db14 --- /dev/null +++ b/firmware/zynq7000/src/sdio.rs @@ -0,0 +1,509 @@ +use arbitrary_int::{u2, u3, 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(Default, Debug, PartialEq, Eq)] +pub enum CommandType { + #[default] + Normal = 0b00, + Suspend = 0b01, + Resume = 0b10, + Abort = 0b11, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum ResponseType { + // No response. + None = 0b00, + _136bits = 0b01, + _48bits = 0b10, + _48bitsWithCheck = 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, default = 0x0, debug)] +pub struct CommandRegister { + /// 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: ResponseType, + #[bit(5, rw)] + 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 BusWidth { + _1bit = 0, + _4bits = 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 { + Off = 0b000, + _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)] + bus_width: BusWidth, + #[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, +} + +impl InterruptStatus { + pub const ALL_BITS: u32 = 0x33FF06FF; +} + +#[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, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct BlockSizeRegister { + /// Enabled when block count enable in the transfer mode register is set to 1 and only + /// valid for multiple block transfers. + #[bits(16..=31, rw)] + block_counts_for_current_transfer: u16, + #[bits(12..=14, rw)] + host_sdma_buffer_size: u3, + #[bits(0..=11, rw)] + transfer_block_size: u12, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct Registers { + sdma_system_addr: u32, + block_params: BlockSizeRegister, + /// Bit 39-8 of Command-Format. + argument: u32, + command: CommandRegister, + #[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, + _gap_0: u32, + #[mmio(PureRead)] + maximum_current_capabilities: u32, + _gap_1: u32, + force_event_register: u32, + adma_error_status: u32, + adma_system_address: u32, + _gap_2: u32, + boot_timeout_control: u32, + debug_selection: u32, + _gap_3: [u32; 0x22], + spi_interrupt_support: u32, + _gap_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) } + } +} diff --git a/firmware/zynq7000/src/slcr/reset.rs b/firmware/zynq7000/src/slcr/reset.rs index 5bff33d..c9f0d17 100644 --- a/firmware/zynq7000/src/slcr/reset.rs +++ b/firmware/zynq7000/src/slcr/reset.rs @@ -1,3 +1,5 @@ +use arbitrary_int::u17; + use super::{RESET_BLOCK_OFFSET, SLCR_BASE_ADDR}; #[bitbybit::bitfield(u32, default = 0x0, debug)] @@ -11,7 +13,7 @@ pub struct DualClockReset { } #[bitbybit::bitfield(u32, default = 0x0, debug)] -pub struct DualRefAndClockReset { +pub struct DualRefAndClockResetSpiUart { /// Periperal 1 Reference software reset. #[bit(3, rw)] periph1_ref_rst: bool, @@ -26,6 +28,22 @@ pub struct DualRefAndClockReset { periph0_cpu1x_rst: bool, } +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct DualRefAndClockResetSdio { + /// Periperal 1 Reference software reset. + #[bit(5, rw)] + periph1_ref_rst: bool, + /// Peripheral 0 Reference software reset. + #[bit(4, rw)] + periph0_ref_rst: bool, + /// Peripheral 1 AMBA software reset. + #[bit(1, rw)] + periph1_cpu1x_rst: bool, + /// Peripheral 0 AMBA software reset. + #[bit(0, rw)] + periph0_cpu1x_rst: bool, +} + #[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct GpioClockReset { #[bit(0, rw)] @@ -49,38 +67,108 @@ pub struct EthernetReset { } #[bitbybit::bitfield(u32, default = 0x0, debug)] -pub struct QspiResetControl { +pub struct ResetControlQspiSmc { #[bit(1, rw)] - qspi_ref_reset: bool, + ref_reset: bool, #[bit(0, rw)] cpu_1x_reset: bool, } +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct FpgaResetControl { + /// This block always needs to be written with 0. I think it contains some other hidden + /// reset lines. + #[bits(8..=24, rw)] + zero_block_0: u17, + #[bit(3, rw)] + fpga_3: bool, + #[bit(2, rw)] + fpga_2: bool, + #[bit(1, rw)] + fpga_1: bool, + #[bit(0, rw)] + fpga_0: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct CpuResetControl { + #[bit(8, rw)] + peripheral_reset: bool, + #[bit(5, rw)] + cpu1_clockstop: bool, + #[bit(4, rw)] + cpu0_clockstop: bool, + #[bit(1, rw)] + cpu1_reset: bool, + #[bit(0, rw)] + cpu0_reset: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct PsResetControl { + #[bit(0, rw)] + soft_reset: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct ResetControlOcmDdr { + #[bit(0, rw)] + reset: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct ResetControlInterconnect { + /// Care must be taken to ensure that the AXI + /// interconnect does not have outstanding + /// transactions and the bus is idle. + #[bit(0, rw)] + reset: bool, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum ApuWatchdogTarget { + /// Same system level as PS_SRST_B. + PsSrstB = 1, + CpuAssociatedWithWdt = 0, +} + +#[bitbybit::bitfield(u32, default = 0x0, debug)] +pub struct WatchTimerResetControl { + #[bit(1, rw)] + apu_wdt_1_reset_target: ApuWatchdogTarget, + #[bit(0, rw)] + apu_wdt_0_reset_target: ApuWatchdogTarget, +} + +/// Reset control block. +/// +/// All reset signal bits are active high, writing a 1 asserts the reset. #[derive(derive_mmio::Mmio)] #[repr(C)] pub struct ResetControl { - /// PS Software reset control - pss: u32, - ddr: u32, + /// Processing System Software reset control + pss: PsResetControl, + ddr: ResetControlOcmDdr, /// Central interconnect reset control - topsw: u32, + topsw: ResetControlInterconnect, dmac: u32, usb: u32, eth: EthernetReset, - sdio: DualRefAndClockReset, - spi: DualRefAndClockReset, + sdio: DualRefAndClockResetSdio, + spi: DualRefAndClockResetSpiUart, can: DualClockReset, i2c: DualClockReset, - uart: DualRefAndClockReset, + uart: DualRefAndClockResetSpiUart, gpio: GpioClockReset, - lqspi: QspiResetControl, - smc: u32, - ocm: u32, + lqspi: ResetControlQspiSmc, + smc: ResetControlQspiSmc, + ocm: ResetControlOcmDdr, _gap0: u32, - fpga: u32, - a9_cpu: u32, + fpga: FpgaResetControl, + a9_cpu: CpuResetControl, _gap1: u32, - rs_awdt: u32, + rs_awdt: WatchTimerResetControl, } impl ResetControl {