forked from M-Labs/zynq-rs
450 lines
15 KiB
Rust
450 lines
15 KiB
Rust
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::{trace, 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),
|
|
}
|
|
|
|
impl core::fmt::Display for CmdTransferError {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
use CmdTransferError::*;
|
|
write!(f, "Command transfer error: ")?;
|
|
match self {
|
|
CmdInhibited => write!(f, "Command line inhibited."),
|
|
DatLineInhibited => write!(f, "Data line inhibited, possibly due to ongonging data transfer."),
|
|
CmdTimeout => write!(f, "Command timeout, check if the card is inserted properly."),
|
|
Other(x) => write!(f, "Unknown Error, interrupt status = 0x{:0X}", x.inner),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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: unsafe { super::timer::GlobalTimer::get() }.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!("Changing 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> {
|
|
trace!("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<CardType, CmdTransferError> {
|
|
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(())
|
|
}
|
|
}
|