From 236592ae66b6e21f33af1891ad04b159046ece1a Mon Sep 17 00:00:00 2001 From: pca006132 Date: Fri, 5 Jun 2020 11:47:06 +0800 Subject: [PATCH] SDIO module completed --- experiments/src/main.rs | 31 +++ libboard_zynq/src/clocks/mod.rs | 14 + libboard_zynq/src/sdio/adma.rs | 101 +++++++ libboard_zynq/src/sdio/cmd.rs | 133 +++++++++ libboard_zynq/src/sdio/mod.rs | 437 +++++++++++++++++++++++++++++- libboard_zynq/src/sdio/regs.rs | 67 ++--- libboard_zynq/src/sdio/sd_card.rs | 350 ++++++++++++++++++++++++ libboard_zynq/src/slcr.rs | 60 +++- 8 files changed, 1157 insertions(+), 36 deletions(-) create mode 100644 libboard_zynq/src/sdio/adma.rs create mode 100644 libboard_zynq/src/sdio/cmd.rs create mode 100644 libboard_zynq/src/sdio/sd_card.rs diff --git a/experiments/src/main.rs b/experiments/src/main.rs index db1bb24..e655663 100644 --- a/experiments/src/main.rs +++ b/experiments/src/main.rs @@ -17,6 +17,7 @@ use libboard_zynq::{ iface::{NeighborCache, EthernetInterfaceBuilder, Routes}, time::Instant, }, + sdio::sd_card::SdCard, time::Milliseconds, }; use libsupport_zynq::{ @@ -52,10 +53,40 @@ pub fn main_core0() { IoPll::setup(1_000_000_000); libboard_zynq::stdio::drop_uart(); } + #[cfg(feature = "target_cora_z7_10")] + { + IoPll::setup(1_000_000_000); + libboard_zynq::stdio::drop_uart(); + } info!("PLLs set up"); let clocks = zynq::clocks::Clocks::get(); info!("CPU Clocks: {}/{}/{}/{}", clocks.cpu_6x4x(), clocks.cpu_3x2x(), clocks.cpu_2x(), clocks.cpu_1x()); + let mut sd = libboard_zynq::sdio::SDIO::sdio0(true); + // only test SD card if it is inserted + if sd.is_card_inserted() { + let result = SdCard::from_sdio(sd); + match &result { + Ok(_) => info!("OK!"), + Err(a) => info!("{:?}", a), + }; + const SIZE: usize = 512 / 2; + let mut sd_card = result.unwrap(); + let mut buffer: [u32; SIZE] = [0; SIZE]; + for i in 0..buffer.len() { + buffer[i] = (i % 16) as u32; + } + sd_card.write_block(0x0, 2, &mut buffer).unwrap(); + for i in 0..buffer.len() { + buffer[i] = 0; + } + sd_card.read_block(0x1, 2, &mut buffer).unwrap(); + for i in 0..buffer.len() { + info!("buffer[{}] = {}", i, buffer[i]); + } + info!("End"); + } + let mut flash = zynq::flash::Flash::new(200_000_000).linear_addressing_mode(); let flash_ram: &[u8] = unsafe { core::slice::from_raw_parts(flash.ptr(), flash.size()) }; for i in 0..=1 { diff --git a/libboard_zynq/src/clocks/mod.rs b/libboard_zynq/src/clocks/mod.rs index fef755a..925768c 100644 --- a/libboard_zynq/src/clocks/mod.rs +++ b/libboard_zynq/src/clocks/mod.rs @@ -104,4 +104,18 @@ impl Clocks { }; pll / u32::from(uart_clk_ctrl.divisor()) } + + pub fn sdio_ref_clk(&self) -> u32 { + let regs = slcr::RegisterBlock::new(); + let sdio_clk_ctrl = regs.sdio_clk_ctrl.read(); + let pll = match sdio_clk_ctrl.srcsel() { + slcr::PllSource::ArmPll => + self.arm, + slcr::PllSource::DdrPll => + self.ddr, + slcr::PllSource::IoPll => + self.io, + }; + pll / u32::from(sdio_clk_ctrl.divisor()) + } } diff --git a/libboard_zynq/src/sdio/adma.rs b/libboard_zynq/src/sdio/adma.rs new file mode 100644 index 0000000..3d6f4a3 --- /dev/null +++ b/libboard_zynq/src/sdio/adma.rs @@ -0,0 +1,101 @@ +/// ADMA library +use super::SDIO; +use libcortex_a9::cache; +use libregister::RegisterR; + +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub struct Adma2Desc32 { + attribute: u16, + length: u16, + address: u32, +} + +// Default::default() cannot be used as it is not a constant function... +static mut ADMA2_DESCR32_TABLE: [Adma2Desc32; 32] = [Adma2Desc32 { + attribute: 0, + length: 0, + address: 0, +}; 32]; + +#[allow(unused)] +const DESC_MAX_LENGTH: u32 = 65536; +#[allow(unused)] +const DESC_TRANS: u16 = 0x2 << 4; +#[allow(unused)] +const DESC_INT: u16 = 0x1 << 2; +#[allow(unused)] +const DESC_END: u16 = 0x1 << 1; +#[allow(unused)] +const DESC_VALID: u16 = 0x1 << 0; + +#[allow(unused)] +impl Adma2Desc32 { + pub fn set_attribute(&mut self, attribute: u16) { + unsafe { + core::ptr::write_volatile(&mut self.attribute as *mut u16, attribute); + } + } + + pub fn get_attribute(&mut self) -> u16 { + unsafe { core::ptr::read_volatile(&mut self.attribute as *mut u16) } + } + + pub fn set_length(&mut self, length: u16) { + unsafe { + core::ptr::write_volatile(&mut self.length as *mut u16, length); + } + } + + pub fn get_length(&mut self) -> u16 { + unsafe { core::ptr::read_volatile(&mut self.length as *mut u16) } + } + + pub fn set_address(&mut self, address: u32) { + unsafe { + core::ptr::write_volatile(&mut self.address as *mut u32, address); + } + } + + pub fn get_address(&mut self) -> u32 { + unsafe { core::ptr::read_volatile(&mut self.address as *mut u32) } + } +} + +pub fn setup_adma2_descr32(sdio: &mut SDIO, blk_cnt: u32, buffer: &mut [u32]) { + let descr_table = unsafe { &mut ADMA2_DESCR32_TABLE }; + let blk_size = sdio + .regs + .block_size_block_count + .read() + .transfer_block_size() as u32; + + let total_desc_lines = if blk_size * blk_cnt < DESC_MAX_LENGTH { + 1 + } else { + blk_size * blk_cnt / DESC_MAX_LENGTH + + if (blk_size * blk_cnt) % DESC_MAX_LENGTH == 0 { + 0 + } else { + 1 + } + } as usize; + + let ptr = buffer.as_ptr() as u32; + for desc_num in 0..total_desc_lines { + descr_table[desc_num].set_address(ptr + (desc_num as u32) * DESC_MAX_LENGTH); + descr_table[desc_num].set_attribute(DESC_TRANS | DESC_VALID); + // 0 is the max length (65536) + descr_table[desc_num].set_length(0); + } + descr_table[total_desc_lines - 1].set_attribute(DESC_TRANS | DESC_VALID | DESC_END); + descr_table[total_desc_lines - 1].set_length( + (blk_cnt * blk_size - ((total_desc_lines as u32) - 1) * DESC_MAX_LENGTH) as u16, + ); + unsafe { + sdio.regs + .adma_system_address + .write(descr_table.as_ptr() as u32); + } + cache::dcci_slice(descr_table); +} diff --git a/libboard_zynq/src/sdio/cmd.rs b/libboard_zynq/src/sdio/cmd.rs new file mode 100644 index 0000000..8bf9921 --- /dev/null +++ b/libboard_zynq/src/sdio/cmd.rs @@ -0,0 +1,133 @@ +use super::regs; + +const APP_CMD_PREFIX: u8 = 0x80; +#[allow(unused)] +pub mod args { + pub const CMD8_VOL_PATTERN: u32 = 0x1AA; + pub const RESPOCR_READY: u32 = 0x80000000; + pub const ACMD41_HCS: u32 = 0x40000000; + pub const ACMD41_3V3: u32 = 0x00300000; + pub const CMD1_HIGH_VOL: u32 = 0x00FF8000; + pub const OCR_S18: u32 = 1 << 24; +} + +#[allow(unused)] +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum SdCmd { + CMD0 = 0x00, + CMD1 = 0x01, + CMD2 = 0x02, + CMD3 = 0x03, + CMD4 = 0x04, + CMD5 = 0x05, + CMD6 = 0x06, + ACMD6 = APP_CMD_PREFIX + 0x06, + CMD7 = 0x07, + CMD8 = 0x08, + CMD9 = 0x09, + CMD10 = 0x0A, + CMD11 = 0x0B, + CMD12 = 0x0C, + ACMD13 = APP_CMD_PREFIX + 0x0D, + CMD16 = 0x10, + CMD17 = 0x11, + CMD18 = 0x12, + CMD19 = 0x13, + CMD21 = 0x15, + CMD23 = 0x17, + ACMD23 = APP_CMD_PREFIX + 0x17, + CMD24 = 0x18, + CMD25 = 0x19, + CMD41 = 0x29, + ACMD41 = APP_CMD_PREFIX + 0x29, + ACMD42 = APP_CMD_PREFIX + 0x2A, + ACMD51 = APP_CMD_PREFIX + 0x33, + CMD52 = 0x34, + CMD55 = 0x37, + CMD58 = 0x3A, +} + +pub fn require_dat(cmd: SdCmd, is_sd_card: bool) -> bool { + use SdCmd::*; + match cmd { + CMD6 => is_sd_card, + CMD8 => !is_sd_card, + ACMD13 | CMD17 | CMD18 | CMD19 | CMD21 | CMD23 | ACMD23 | CMD24 | CMD25 | ACMD51 => true, + _ => false, + } +} + +type CmdReg = regs::transfer_mode_command::Write; + +fn resp_r1(w: CmdReg) -> CmdReg { + w.response_type_select(regs::ResponseTypeSelect::Length48) + .crc_check_en(true) + .index_check_en(true) +} + +fn resp_r1b(w: CmdReg) -> CmdReg { + w.response_type_select(regs::ResponseTypeSelect::Legnth48Check) + .crc_check_en(true) + .index_check_en(true) +} + +fn resp_r2(w: CmdReg) -> CmdReg { + w.response_type_select(regs::ResponseTypeSelect::Length136) + .crc_check_en(true) +} + +fn resp_r3(w: CmdReg) -> CmdReg { + w.response_type_select(regs::ResponseTypeSelect::Length48) +} + +fn resp_r6(w: CmdReg) -> CmdReg { + w.response_type_select(regs::ResponseTypeSelect::Legnth48Check) + .crc_check_en(true) + .index_check_en(true) +} + +pub fn set_cmd_reg(cmd: SdCmd, is_sd_card: bool, w: CmdReg) -> CmdReg { + use SdCmd::*; + let w = w.command_index(cmd as u8 & 0x3F); + match cmd { + CMD1 => resp_r3(w), + CMD2 => resp_r2(w), + CMD3 => { + if is_sd_card { + resp_r6(w) + } else { + resp_r1(w) + } + } + CMD5 => resp_r1b(w), + CMD6 => { + if is_sd_card { + resp_r1(w).data_present_select(true) + } else { + resp_r1b(w) + } + } + ACMD6 => resp_r1(w), + CMD7 => resp_r1(w), + CMD8 => { + if is_sd_card { + resp_r1(w) + } else { + resp_r1(w).data_present_select(true) + } + } + CMD9 => resp_r2(w), + CMD10 | CMD11 | CMD12 => resp_r1(w), + ACMD13 => resp_r1(w).data_present_select(true), + CMD16 => resp_r1(w), + CMD17 | CMD18 | CMD19 | CMD21 | CMD23 | ACMD23 | CMD24 | CMD25 => { + resp_r1(w).data_present_select(true) + } + ACMD41 => resp_r3(w), + ACMD42 => resp_r1(w), + ACMD51 => resp_r1(w).data_present_select(true), + CMD52 | CMD55 => resp_r1(w), + _ => w, + } +} diff --git a/libboard_zynq/src/sdio/mod.rs b/libboard_zynq/src/sdio/mod.rs index 77e8519..9ed9d6e 100644 --- a/libboard_zynq/src/sdio/mod.rs +++ b/libboard_zynq/src/sdio/mod.rs @@ -1 +1,436 @@ -mod regs; \ No newline at end of file +pub mod sd_card; + +mod adma; +mod cmd; +mod regs; +use super::clocks::Clocks; +use super::slcr; +use super::time::Milliseconds; +use embedded_hal::timer::CountDown; +use libregister::{RegisterR, RegisterRW, RegisterW}; +use log::debug; +use nb; + +/// Basic SDIO Struct with common low-level functions. +pub struct SDIO { + regs: &'static mut regs::RegisterBlock, + count_down: super::timer::global::CountDown, + input_clk_hz: u32, + card_type: CardType, + card_detect: bool, +} + +#[derive(Debug)] +pub enum CmdTransferError { + CmdInhibited, + DatLineInhibited, + CmdTimeout, + Other(regs::interrupt_status::Read), +} + +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum CardType { + CardNone, + CardSd, + CardMmc, +} + +impl SDIO { + /// Initialize SDIO0 + /// card_detect means if we would use the card detect pin, + /// false to disable card detection (assume there is card inserted) + pub fn sdio0(card_detect: bool) -> Self { + // initialization according to ps7_init.c + slcr::RegisterBlock::unlocked(|slcr| { + slcr.mio_pin_40.write( + slcr::MioPin40::zeroed() + .l3_sel(0b100) + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + slcr.mio_pin_41.write( + slcr::MioPin41::zeroed() + .l3_sel(0b100) + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + slcr.mio_pin_42.write( + slcr::MioPin42::zeroed() + .l3_sel(0b100) + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + slcr.mio_pin_43.write( + slcr::MioPin43::zeroed() + .l3_sel(0b100) + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + slcr.mio_pin_44.write( + slcr::MioPin44::zeroed() + .l3_sel(0b100) + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + slcr.mio_pin_45.write( + slcr::MioPin45::zeroed() + .l3_sel(0b100) + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + // zc706 card detect pin + #[cfg(feature = "target_zc706")] + { + unsafe { + slcr.sd0_wp_cd_sel.write(0x000E000F); + } + slcr.mio_pin_14.write( + slcr::MioPin14::zeroed() + .io_type(slcr::IoBufferType::Lvcmos18) + .pullup(true) + .tri_enable(true), + ); + } + // cora card detect pin + #[cfg(feature = "target_cora_z7_10")] + { + unsafe { + slcr.sd0_wp_cd_sel.write(47 << 16); + } + slcr.mio_pin_47.write( + slcr::MioPin47::zeroed() + .io_type(slcr::IoBufferType::Lvcmos18) + .speed(true), + ); + } + slcr.sdio_rst_ctrl.reset_sdio0(); + slcr.aper_clk_ctrl.enable_sdio0(); + slcr.sdio_clk_ctrl.enable_sdio0(); + }); + let clocks = Clocks::get(); + let mut self_ = SDIO { + regs: regs::RegisterBlock::sdio0(), + count_down: super::timer::GlobalTimer::start().countdown(), + input_clk_hz: clocks.sdio_ref_clk(), + card_type: CardType::CardNone, + card_detect, + }; + self_.init(); + self_ + } + + /// Change clock frequency to the value less than or equal to the given value. + /// From XSdPs_Change_ClkFreq in xsdps_options.c. SPEC_V3 related code is removed as + /// our board would only be V1 or V2. + fn change_clk_freq(&mut self, freq: u32) { + debug!("Change clock frequency to {}", freq); + self.regs + .clock_control + .modify(|_, w| w.sd_clk_en(false).internal_clk_en(false)); + + const XSDPS_CC_MAX_DIV_CNT: u32 = 256; + // calculate clock divisor + let mut div_cnt: u32 = 0x1; + let mut divisor = 0; + while div_cnt <= XSDPS_CC_MAX_DIV_CNT { + if (self.input_clk_hz / div_cnt) <= freq { + divisor = div_cnt / 2; + break; + } + div_cnt <<= 1; + } + if div_cnt > XSDPS_CC_MAX_DIV_CNT { + panic!("No valid divisor!"); + } + // enable internal clock + self.regs + .clock_control + .modify(|_, w| w.sdclk_freq_divisor(divisor as u8).internal_clk_en(true)); + while !self.regs.clock_control.read().internal_clk_stable() {} + + // enable SD clock + self.regs.clock_control.modify(|_, w| w.sd_clk_en(true)); + } + + /// Initialization based on XSdPs_CfgInitialize function in xsdps.c + fn init(&mut self) { + // poweroff + self.regs + .control + .modify(|_, w| w.bus_voltage(regs::BusVoltage::V0).bus_power(false)); + + if self.regs.misc_reg.read().spec_ver() == regs::SpecificationVersion::V3 { + // The documentation said the field can only be V1 or V2, + // so the code is written for V1 and V2. V3 requires special handling + // which is currently not implemented. + // I hope that this would never trigger but it is safer to put a check here. + panic!("The code written is for V1 and V2"); + } + // delay to poweroff card + self.delay(1); + + // reset all + debug!("Reset SDIO!"); + self.regs + .clock_control + .modify(|_, w| w.software_reset_all(true)); + while self.regs.clock_control.read().software_reset_all() {} + + // set power to 3.3V + self.regs + .control + .modify(|_, w| w.bus_voltage(regs::BusVoltage::V33).bus_power(true)); + // set clock frequency + self.change_clk_freq(400_000); + // select voltage + let capabilities = self.regs.capabilities.read(); + let voltage = if capabilities.voltage_3_3() { + regs::BusVoltage::V33 + } else if capabilities.voltage_3_0() { + regs::BusVoltage::V30 + } else if capabilities.voltage_1_8() { + regs::BusVoltage::V18 + } else { + regs::BusVoltage::V0 + }; + self.regs.control.modify(|_, w| w.bus_voltage(voltage)); + + self.regs + .control + .modify(|_, w| w.dma_select(regs::DmaSelect::ADMA2_32)); + + // enable all interrupt status except card interrupt + self.regs.interrupt_status_en.write( + (regs::interrupt_status_en::Write { inner: 0xFFFFFFFF }) + .card_interrupt_status_en(false), + ); + + // disable all interrupt signals + self.regs + .interrupt_signal_en + .write(regs::InterruptSignalEn::zeroed()); + + // set block size to 512 by default + self.regs + .block_size_block_count + .modify(|_, w| w.transfer_block_size(512)); + } + + /// Delay for SDIO operations, simple wrapper for nb. + pub fn delay(&mut self, ms: u64) { + self.count_down.start(Milliseconds(ms)); + nb::block!(self.count_down.wait()).unwrap(); + } + + /// Send SD command. Basically `cmd_transfer_with_mode` with mode + /// `regs::TransferModeCommand::zeroed()`. + /// Return: Ok if success, Err(status) if failed. + fn cmd_transfer( + &mut self, + cmd: cmd::SdCmd, + arg: u32, + block_cnt: u16, + ) -> Result<(), CmdTransferError> { + self.cmd_transfer_with_mode(cmd, arg, block_cnt, regs::TransferModeCommand::zeroed()) + } + + /// Send SD Command with additional transfer mode. + /// This function would block until response is ready. + /// Return: Ok if success, Err(status) if failed. + fn cmd_transfer_with_mode( + &mut self, + cmd: cmd::SdCmd, + arg: u32, + block_cnt: u16, + transfer_mode: regs::transfer_mode_command::Write, + ) -> Result<(), CmdTransferError> { + debug!("Send Cmd {:?}", cmd); + let state = self.regs.present_state.read(); + if state.command_inhibit_cmd() { + return Err(CmdTransferError::CmdInhibited); + } + self.regs + .block_size_block_count + .modify(|_, w| w.blocks_count(block_cnt)); + self.regs + .clock_control + .modify(|_, w| w.timeout_counter_value(0xE)); + unsafe { + self.regs.argument.write(arg); + } + self.regs + .interrupt_status_en + .write(regs::interrupt_status_en::Write { inner: 0xFFFFFFFF }); + + let is_sd_card = self.card_type == CardType::CardSd; + // Check DAT Line + if cmd != cmd::SdCmd::CMD21 && cmd != cmd::SdCmd::CMD19 { + if self.regs.present_state.read().command_inhibit_dat() + && cmd::require_dat(cmd, is_sd_card) + { + return Err(CmdTransferError::DatLineInhibited); + } + } + + // Set the command registers. + self.regs + .transfer_mode_command + .write(cmd::set_cmd_reg(cmd, is_sd_card, transfer_mode)); + + // polling for response + loop { + let status = self.regs.interrupt_status.read(); + if cmd == cmd::SdCmd::CMD21 || cmd == cmd::SdCmd::CMD19 { + if status.buffer_read_ready() { + self.regs + .interrupt_status + .modify(|_, w| w.buffer_read_ready()); + break; + } + } + if status.command_complete() { + break; + } + self.check_error(&status)?; + } + // wait for command complete + while !self.regs.interrupt_status.read().command_complete() {} + self.regs + .interrupt_status + .modify(|_, w| w.command_complete()); + Ok(()) + } + + /// Check if card is inserted. + pub fn is_card_inserted(&self) -> bool { + !self.card_detect || self.regs.present_state.read().card_inserted() + } + + /// Switch voltage from 3.3V to 1.8V. + fn switch_voltage(&mut self) -> Result<(), CmdTransferError> { + use cmd::SdCmd::*; + // send switch voltage command + self.cmd_transfer(CMD11, 0, 0)?; + // wait for the lines to go low + let mut state = self.regs.present_state.read(); + while state.cmd_line_level() + || state.dat0_level() + || state.dat1_level() + || state.dat2_level() + || state.dat3_level() + { + state = self.regs.present_state.read(); + } + // stop the clock + self.regs + .clock_control + .modify(|_, w| w.sd_clk_en(false).internal_clk_en(false)); + // enabling 1.8v in controller + self.regs + .control + .modify(|_, w| w.bus_voltage(regs::BusVoltage::V18)); + + // wait minimum 5ms + self.delay(5); + + if self.regs.control.read().bus_voltage() != regs::BusVoltage::V18 { + // I should not wrap the error of this function into another type later. + // actually this is not correct. + return Err(CmdTransferError::CmdTimeout); + } + + // wait for internal clock to stabilize + self.regs + .clock_control + .modify(|_, w| w.internal_clk_en(true)); + while !self.regs.clock_control.read().internal_clk_stable() {} + + // enable SD clock + self.regs.clock_control.modify(|_, w| w.sd_clk_en(true)); + + // wait for 1ms + self.delay(1); + + // wait for CMD and DATA line to go high + state = self.regs.present_state.read(); + while !state.cmd_line_level() + || !state.dat0_level() + || !state.dat1_level() + || !state.dat2_level() + || !state.dat3_level() + { + state = self.regs.present_state.read(); + } + + Ok(()) + } + + /// Detect inserted card type, and set the corresponding field. + /// Return Ok(CardType) on success, Err(CmdTransferError) when failed to identify. + pub fn identify_card(&mut self) -> Result { + use cmd::{args::*, SdCmd::*}; + // actually the delay for this one is unclear in the xilinx code. + self.delay(10); + self.cmd_transfer(CMD0, 0, 0)?; + + self.card_type = match self.cmd_transfer(CMD1, ACMD41_HCS | CMD1_HIGH_VOL, 0) { + Ok(()) => CardType::CardMmc, + Err(_) => CardType::CardSd, + }; + // clear all status + self.regs + .interrupt_status + .write(regs::interrupt_status::Write { inner: 0xF3FFFFFF }); + self.regs + .clock_control + .modify(|_, w| w.software_reset_cmd(true)); + // wait for reset completion + while self.regs.clock_control.read().software_reset_cmd() {} + Ok(self.card_type) + } + + /// Modify transfer block size. + fn set_block_size(&mut self, block_size: u16) -> Result<(), CmdTransferError> { + use cmd::SdCmd::*; + let state = self.regs.present_state.read(); + if state.command_inhibit_cmd() + || state.command_inhibit_dat() + || state.write_transfer_active() + || state.read_transfer_active() + { + return Err(CmdTransferError::CmdInhibited); + } + + debug!("Set block size to {}", block_size); + // send block write command + self.cmd_transfer(CMD16, block_size as u32, 0)?; + // set block size + self.regs + .block_size_block_count + .modify(|_, w| w.transfer_block_size(block_size)); + Ok(()) + } + + /// Check if error occured, and reset the error status. + /// Return Err(CmdTransferError) if error occured, Ok(()) otherwise. + fn check_error( + &mut self, + status: ®s::interrupt_status::Read, + ) -> Result<(), CmdTransferError> { + if status.error_interrupt() { + let err_status = if status.inner & 0xFFFE0000 == 0 { + CmdTransferError::CmdTimeout + } else { + CmdTransferError::Other(regs::interrupt_status::Read { + inner: status.inner, + }) + }; + // reset all error status + self.regs + .interrupt_status + .write(regs::interrupt_status::Write { inner: 0xF3FF0000 }); + return Err(err_status); + } + Ok(()) + } +} diff --git a/libboard_zynq/src/sdio/regs.rs b/libboard_zynq/src/sdio/regs.rs index 0cd47cb..208fba4 100644 --- a/libboard_zynq/src/sdio/regs.rs +++ b/libboard_zynq/src/sdio/regs.rs @@ -1,7 +1,8 @@ -use volatile_register::{RO, RW, WO}; - +use core::fmt; use libregister::{register, register_at, register_bit, register_bits, register_bits_typed}; +use volatile_register::{RO, RW}; +#[allow(unused)] #[repr(C)] pub struct RegisterBlock { pub sdma_system_address: RW, @@ -14,19 +15,24 @@ pub struct RegisterBlock { /// Host. power, block gap, wakeup control pub control: Control, /// Clock and timeout control, and software reset register. - pub timing_control: TimingControl, + pub clock_control: ClockControl, pub interrupt_status: InterruptStatus, pub interrupt_status_en: InterruptStatusEn, pub interrupt_signal_en: InterruptSignalEn, pub auto_cmd12_error_status: AutoCmd12ErrorStatus, pub capabilities: Capabilities, + pub unused0: RO, pub max_current_capabilities: MaxCurrentCapabilities, + pub unused1: RO, pub force_event: ForceEvent, pub adma_error_status: AdmaErrorStatus, pub adma_system_address: RW, + pub unused2: RO, pub boot_data_timeout_counter: RW, pub debug_selection: DebugSelection, + pub unused3: [RO; 34], pub spi_interrupt_support: SpiInterruptSupport, + pub unused4: [RO; 2], pub misc_reg: MiscReg, } @@ -50,6 +56,7 @@ pub enum ResponseTypeSelect { #[allow(unused)] #[repr(u8)] +#[derive(PartialEq, Debug)] pub enum BusVoltage { /// 3.3V V33 = 0b111, @@ -57,6 +64,8 @@ pub enum BusVoltage { V30 = 0b110, /// 1.8V, typ. V18 = 0b101, + /// No power, + V0 = 0b000, } #[allow(unused)] @@ -64,23 +73,8 @@ pub enum BusVoltage { pub enum DmaSelect { SDMA = 0b00, ADMA1 = 0b01, - ADMA2 = 0b10, - ADMA3 = 0b11, -} - -#[allow(unused)] -#[repr(u8)] -/// SDCLK Frequency divisor, d(number) means baseclock divided by (number). -pub enum SdclkFreqDivisor { - D256 = 0x80, - D128 = 0x40, - D64 = 0x20, - D32 = 0x10, - D16 = 0x08, - D8 = 0x04, - D4 = 0x02, - D2 = 0x01, - D1 = 0x00, + ADMA2_32 = 0b10, + ADMA2_64 = 0b11, } #[allow(unused)] @@ -93,13 +87,15 @@ pub enum AdmaErrorState { #[allow(unused)] #[repr(u8)] +#[derive(PartialEq)] pub enum SpecificationVersion { V1 = 0, V2 = 1, + V3 = 2, } -register_at!(RegisterBlock, 0xE0100000, sd0); -register_at!(RegisterBlock, 0xE0101000, sd1); +register_at!(RegisterBlock, 0xE0100000, sdio0); +register_at!(RegisterBlock, 0xE0101000, sdio1); register!(block_size_block_count, BlockSizeBlockCount, RW, u32); register_bits!( @@ -328,27 +324,27 @@ register_bit!( 0 ); -register!(timing_control, TimingControl, RW, u32); +register!(clock_control, ClockControl, RW, u32); register_bit!( - timing_control, + clock_control, /// Software reset for DAT line. software_reset_dat, 26 ); register_bit!( - timing_control, + clock_control, /// Software reset for CMD line. software_reset_cmd, 25 ); register_bit!( - timing_control, + clock_control, /// Software reset for ALL. software_reset_all, 24 ); register_bits!( - timing_control, + clock_control, /// Determines the interval by which DAT line time-outs are detected. /// Interval = TMCLK * 2^(13 + val) /// Note: 0b1111 is reserved. @@ -357,27 +353,26 @@ register_bits!( 16, 19 ); -register_bits_typed!( - timing_control, +register_bits!( + clock_control, /// Selects the frequency divisor, thus the clock frequency for SDCLK. /// Choose the smallest possible divisor which results in a clock frequency /// that is less than or equal to the target frequency. sdclk_freq_divisor, u8, - SdclkFreqDivisor, 8, 15 ); -register_bit!(timing_control, sd_clk_en, 2); +register_bit!(clock_control, sd_clk_en, 2); register_bit!( - timing_control, + clock_control, /// 1 when SD clock is stable. /// Note that this field is read-only. internal_clk_stable, 1, RO ); -register_bit!(timing_control, internal_clk_en, 0); +register_bit!(clock_control, internal_clk_en, 0); register!(interrupt_status, InterruptStatus, RW, u32, 1 << 15 | 1 << 8); register_bit!(interrupt_status, ceata_error, 29, WTC); @@ -545,3 +540,9 @@ register_bits!( 0, 7 ); + +impl fmt::Debug for interrupt_status::Read { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_fmt(format_args!("status: {:0X}", self.inner)) + } +} diff --git a/libboard_zynq/src/sdio/sd_card.rs b/libboard_zynq/src/sdio/sd_card.rs new file mode 100644 index 0000000..26824f9 --- /dev/null +++ b/libboard_zynq/src/sdio/sd_card.rs @@ -0,0 +1,350 @@ +use super::{adma::setup_adma2_descr32, cmd, CardType, CmdTransferError, SDIO}; +use libcortex_a9::cache; +use libregister::{RegisterR, RegisterRW, RegisterW}; +use log::debug; + +#[derive(Debug)] +pub enum CardInitializationError { + AlreadyInitialized, + NoCardInserted, + InitializationFailedOther, + InitializationFailedCmd(CmdTransferError), +} + +impl From for CardInitializationError { + fn from(error: CmdTransferError) -> Self { + CardInitializationError::InitializationFailedCmd(error) + } +} + +#[derive(Debug)] +enum CardVersion { + SdVer1, + SdVer2, +} + +pub struct SdCard { + sdio: SDIO, + card_version: CardVersion, + hcs: bool, + card_id: [u32; 4], + rel_card_addr: u32, + sector_cnt: u32, + switch_1v8: bool, + width_4_bit: bool, +} + +const BLK_SIZE_MASK: u16 = 0x00000FFF; + +impl core::fmt::Display for SdCard { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "SdCard: \n card version: {:?}\n hcs: {}\n card id: {:?}\n rel card addr: {}\n sector count: {}", + self.card_version, self.hcs, self.card_id, self.rel_card_addr, self.sector_cnt) + } +} + +impl SdCard { + fn sd_card_initialize(&mut self) -> Result<(), CardInitializationError> { + use cmd::{args::*, SdCmd::*}; + if !self.sdio.is_card_inserted() { + return Err(CardInitializationError::NoCardInserted); + } + // CMD0 + self.sdio.cmd_transfer(CMD0, 0, 0)?; + match self.sdio.cmd_transfer(CMD8, CMD8_VOL_PATTERN, 0) { + Err(CmdTransferError::CmdTimeout) => { + // reset + self.sdio + .regs + .clock_control + .modify(|_, w| w.software_reset_cmd(true)); + // wait until reset is completed + while self.sdio.regs.clock_control.read().software_reset_cmd() {} + } + // for other error, return initialization failed + Err(e) => return Err(CardInitializationError::from(e)), + _ => (), + } + + self.card_version = if self.sdio.regs.responses[0].read() != CMD8_VOL_PATTERN { + CardVersion::SdVer1 + } else { + CardVersion::SdVer2 + }; + + // send ACMD41 while card is still busy with power up + loop { + self.sdio.cmd_transfer(CMD55, 0, 0)?; + self.sdio + .cmd_transfer(ACMD41, ACMD41_HCS | ACMD41_3V3 | (0x1FF << 15), 0)?; + + if (self.sdio.regs.responses[0].read() & RESPOCR_READY) != 0 { + break; + } + } + + let response = self.sdio.regs.responses[0].read(); + // update HCS support flag + self.hcs = (response & ACMD41_HCS) != 0; + if (response & OCR_S18) != 0 { + self.switch_1v8 = true; + self.sdio.switch_voltage()?; + } + + self.sdio.cmd_transfer(CMD2, 0, 0)?; + for i in 0..=3 { + self.card_id[i] = self.sdio.regs.responses[i].read(); + } + + self.rel_card_addr = 0; + while self.rel_card_addr == 0 { + self.sdio.cmd_transfer(CMD3, 0, 0)?; + self.rel_card_addr = self.sdio.regs.responses[0].read() & 0xFFFF0000; + } + + self.sdio.cmd_transfer(CMD9, self.rel_card_addr, 0)?; + self.sdio + .regs + .interrupt_status + .modify(|_, w| w.transfer_complete()); + + let mut csd: [u32; 4] = [0, 0, 0, 0]; + for i in 0..=3 { + csd[i] = self.sdio.regs.responses[i].read(); + debug!("CSD[{}] = {:0X}", i, csd[i]); + } + + const CSD_STRUCT_MSK: u32 = 0x00C00000; + const C_SIZE_MULT_MASK: u32 = 0x00000380; + const C_SIZE_LOWER_MASK: u32 = 0xFFC00000; + const C_SIZE_UPPER_MASK: u32 = 0x00000003; + const READ_BLK_LEN_MASK: u32 = 0x00000F00; + const CSD_V2_C_SIZE_MASK: u32 = 0x3FFFFF00; + const XSDPS_BLK_SIZE_512_MASK: u32 = 0x200; + if ((csd[3] & CSD_STRUCT_MSK) >> 22) == 0 { + let blk_len = 1 << ((csd[2] & READ_BLK_LEN_MASK) >> 8); + let mult = 1 << (((csd[1] & C_SIZE_MULT_MASK) >> 7) + 2); + let mut device_size = (csd[1] & C_SIZE_LOWER_MASK) >> 22; + device_size |= (csd[2] & C_SIZE_UPPER_MASK) << 10; + device_size = (device_size + 1) * mult; + device_size = device_size * blk_len; + self.sector_cnt = device_size / XSDPS_BLK_SIZE_512_MASK; + } else if ((csd[3] & CSD_STRUCT_MSK) >> 22) == 1 { + self.sector_cnt = (((csd[1] & CSD_V2_C_SIZE_MASK) >> 8) + 1) * 1024; + } else { + return Err(CardInitializationError::InitializationFailedOther); + } + + self.sdio.change_clk_freq(25_000_000); + // CMD7: select card + self.sdio.cmd_transfer(CMD7, self.rel_card_addr, 0)?; + + // pull up + self.sdio.cmd_transfer(CMD55, self.rel_card_addr, 0)?; + self.sdio.cmd_transfer(ACMD42, 0, 0)?; + + let mut scr: [u32; 8] = [0; 8]; + self.get_bus_width(&mut scr)?; + debug!("{:?}", scr); + if scr[1] & 0x4 != 0 { + // 4bit support + debug!("4 bit support"); + self.change_bus_width()?; + } + + self.sdio.set_block_size(512)?; + + Ok(()) + } + + /// Convert SDIO into SdCard struct, error if no card inserted or it is not an SD card. + pub fn from_sdio(mut sdio: SDIO) -> Result { + match sdio.identify_card()? { + CardType::CardSd => (), + _ => return Err(CardInitializationError::NoCardInserted), + }; + let mut _self = SdCard { + sdio, + card_version: CardVersion::SdVer1, + hcs: false, + card_id: [0, 0, 0, 0], + rel_card_addr: 0, + sector_cnt: 0, + switch_1v8: false, + width_4_bit: false, + }; + _self.sd_card_initialize()?; + Ok(_self) + } + + /// Convert SdCard struct back to SDIO struct. + pub fn to_sdio(self) -> SDIO { + self.sdio + } + + /// read blocks starting from an address. Each block has length 512 byte. + /// Note that the address is block address, i.e. 0 for 0~512, 1 for 512~1024, etc. + pub fn read_block( + &mut self, + address: u32, + block_cnt: u16, + buffer: &mut [u32], + ) -> Result<(), CmdTransferError> { + assert!(buffer.len() >= (block_cnt as usize) * (512 / 4)); + // set block size if not set already + if self + .sdio + .regs + .block_size_block_count + .read() + .transfer_block_size() + != 512 + { + self.sdio.set_block_size(512)?; + } + + setup_adma2_descr32(&mut self.sdio, block_cnt as u32, buffer); + // invalidate D cache, required for ZC706, not sure for Cora Z7 10 + cache::dcci_slice(buffer); + + let cmd = if block_cnt == 1 { + cmd::SdCmd::CMD17 + } else { + cmd::SdCmd::CMD18 + }; + + let mode = if block_cnt == 1 { + super::regs::TransferModeCommand::zeroed() + .block_count_en(true) + .direction_select(true) + .dma_en(true) + } else { + super::regs::TransferModeCommand::zeroed() + .auto_cmd12_en(true) + .block_count_en(true) + .direction_select(true) + .multi_block_en(true) + .dma_en(true) + }; + + self.sdio + .cmd_transfer_with_mode(cmd, address, block_cnt, mode)?; + + self.wait_transfer_complete()?; + cache::dcci_slice(buffer); + Ok(()) + } + + /// write blocks starting from an address. Each block has length 512 byte. + /// Note that the address is block address, i.e. 0 for 0~512, 1 for 512~1024, etc. + pub fn write_block( + &mut self, + address: u32, + block_cnt: u16, + buffer: &mut [u32], + ) -> Result<(), CmdTransferError> { + assert!(buffer.len() >= (block_cnt as usize) * (512 / 4)); + // set block size if not set already + if self + .sdio + .regs + .block_size_block_count + .read() + .transfer_block_size() + != 512 + { + self.sdio.set_block_size(512)?; + } + + setup_adma2_descr32(&mut self.sdio, block_cnt as u32, buffer); + // invalidate D cache, required for ZC706, not sure for Cora Z7 10 + cache::dcci_slice(buffer); + + let cmd = if block_cnt == 1 { + cmd::SdCmd::CMD24 + } else { + cmd::SdCmd::CMD25 + }; + + let mode = if block_cnt == 1 { + super::regs::TransferModeCommand::zeroed() + .block_count_en(true) + .dma_en(true) + } else { + super::regs::TransferModeCommand::zeroed() + .auto_cmd12_en(true) + .block_count_en(true) + .multi_block_en(true) + .dma_en(true) + }; + + self.sdio + .cmd_transfer_with_mode(cmd, address, block_cnt, mode)?; + // wait for transfer complete interrupt + self.wait_transfer_complete()?; + + cache::dcci_slice(buffer); + Ok(()) + } + + fn get_bus_width(&mut self, buf: &mut [u32]) -> Result<(), CmdTransferError> { + use cmd::SdCmd::*; + debug!("Getting bus width"); + for i in 0..8 { + buf[i] = 0; + } + // send block write command + self.sdio.cmd_transfer(CMD55, self.rel_card_addr, 0)?; + + let blk_cnt: u16 = 1; + let blk_size: u16 = 8 & BLK_SIZE_MASK; + self.sdio + .regs + .block_size_block_count + .modify(|_, w| w.transfer_block_size(blk_size)); + + setup_adma2_descr32(&mut self.sdio, blk_cnt as u32, buf); + cache::dcci_slice(buf); + self.sdio.cmd_transfer_with_mode( + ACMD51, + 0, + blk_cnt, + super::regs::TransferModeCommand::zeroed() + .dma_en(true) + .direction_select(true), + )?; + + self.wait_transfer_complete()?; + cache::dcci_slice(buf); + Ok(()) + } + + fn change_bus_width(&mut self) -> Result<(), CmdTransferError> { + use cmd::SdCmd::*; + debug!("Changing bus speed"); + self.sdio.cmd_transfer(CMD55, self.rel_card_addr, 0)?; + self.width_4_bit = true; + self.sdio.cmd_transfer(ACMD6, 0x2, 0)?; + self.sdio.delay(1); + self.sdio + .regs + .control + .modify(|_, w| w.data_width_select(true)); + Ok(()) + } + + fn wait_transfer_complete(&mut self) -> Result<(), CmdTransferError> { + debug!("Wait for transfer complete"); + let mut status = self.sdio.regs.interrupt_status.read(); + while !status.transfer_complete() { + self.sdio.check_error(&status)?; + status = self.sdio.regs.interrupt_status.read(); + } + debug!("Clearing transfer complete"); + self.sdio + .regs + .interrupt_status + .modify(|_, w| w.transfer_complete()); + Ok(()) + } +} diff --git a/libboard_zynq/src/slcr.rs b/libboard_zynq/src/slcr.rs index c037def..f933bec 100644 --- a/libboard_zynq/src/slcr.rs +++ b/libboard_zynq/src/slcr.rs @@ -94,7 +94,7 @@ pub struct RegisterBlock { pub gem1_clk_ctrl: GemClkCtrl, pub smc_clk_ctrl: RW, pub lqspi_clk_ctrl: LqspiClkCtrl, - pub sdio_clk_ctrl: RW, + pub sdio_clk_ctrl: SdioClkCtrl, pub uart_clk_ctrl: UartClkCtrl, pub spi_clk_ctrl: RW, pub can_clk_ctrl: RW, @@ -127,7 +127,7 @@ pub struct RegisterBlock { pub dmac_rst_ctrl: RW, pub usb_rst_ctrl: RW, pub gem_rst_ctrl: RW, - pub sdio_rst_ctrl: RW, + pub sdio_rst_ctrl: SdioRstCtrl, pub spi_rst_ctrl: RW, pub can_rst_ctrl: RW, pub i2c_rst_ctrl: RW, @@ -391,6 +391,8 @@ register_bit!(clk_621_true, clk_621_true, 0); register!(aper_clk_ctrl, AperClkCtrl, RW, u32); register_bit!(aper_clk_ctrl, uart1_cpu_1xclkact, 21); register_bit!(aper_clk_ctrl, uart0_cpu_1xclkact, 20); +register_bit!(aper_clk_ctrl, sdio1_cpu_1xclkact, 11); +register_bit!(aper_clk_ctrl, sdio0_cpu_1xclkact, 10); impl AperClkCtrl { pub fn enable_uart0(&mut self) { self.modify(|_, w| w.uart0_cpu_1xclkact(true)); @@ -399,6 +401,14 @@ impl AperClkCtrl { pub fn enable_uart1(&mut self) { self.modify(|_, w| w.uart1_cpu_1xclkact(true)); } + + pub fn enable_sdio0(&mut self) { + self.modify(|_, w| w.sdio0_cpu_1xclkact(true)); + } + + pub fn enable_sdio1(&mut self) { + self.modify(|_, w| w.sdio1_cpu_1xclkact(true)); + } } register!(rclk_ctrl, RclkCtrl, RW, u32); @@ -423,6 +433,24 @@ register_bit!(gem_clk_ctrl, /// SMC reference clock control clkact, 0); +register!(sdio_clk_ctrl, SdioClkCtrl, RW, u32); +register_bit!(sdio_clk_ctrl, clkact0, 0); +register_bit!(sdio_clk_ctrl, clkact1, 1); +register_bits!(sdio_clk_ctrl, divisor, u8, 8, 13); +register_bits_typed!(sdio_clk_ctrl, srcsel, u8, PllSource, 4, 5); +impl SdioClkCtrl { + pub fn enable_sdio0(&mut self) { + self.modify(|_, w| { + w.divisor(0x14).srcsel(PllSource::IoPll).clkact0(true) + }) + } + pub fn enable_sdio1(&mut self) { + self.modify(|_, w| { + w.divisor(0x14).srcsel(PllSource::IoPll).clkact1(true) + }) + } +} + register!(uart_clk_ctrl, UartClkCtrl, RW, u32); register_bit!(uart_clk_ctrl, clkact0, 0); register_bit!(uart_clk_ctrl, clkact1, 1); @@ -453,6 +481,34 @@ impl UartClkCtrl { } } +register!(sdio_rst_ctrl, SdioRstCtrl, RW, u32); +register_bit!(sdio_rst_ctrl, sdio1_ref_rst, 5); +register_bit!(sdio_rst_ctrl, sdio0_ref_rst, 4); +register_bit!(sdio_rst_ctrl, sdio1_cpu1x_rst, 1); +register_bit!(sdio_rst_ctrl, sdio0_cpu1x_rst, 0); +impl SdioRstCtrl { + pub fn reset_sdio0(&mut self) { + self.modify(|_, w| + w.sdio0_ref_rst(true) + .sdio0_cpu1x_rst(true) + ); + self.modify(|_, w| + w.sdio0_ref_rst(false) + .sdio0_cpu1x_rst(false) + ); + } + pub fn reset_sdio1(&mut self) { + self.modify(|_, w| + w.sdio1_ref_rst(true) + .sdio1_cpu1x_rst(true) + ); + self.modify(|_, w| + w.sdio1_ref_rst(false) + .sdio1_cpu1x_rst(false) + ); + } +} + register!(uart_rst_ctrl, UartRstCtrl, RW, u32); register_bit!(uart_rst_ctrl, uart0_ref_rst, 3); register_bit!(uart_rst_ctrl, uart1_ref_rst, 2);