forked from M-Labs/zynq-rs
SDIO module completed
This commit is contained in:
parent
a53ed8acc8
commit
236592ae66
|
@ -17,6 +17,7 @@ use libboard_zynq::{
|
||||||
iface::{NeighborCache, EthernetInterfaceBuilder, Routes},
|
iface::{NeighborCache, EthernetInterfaceBuilder, Routes},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
},
|
},
|
||||||
|
sdio::sd_card::SdCard,
|
||||||
time::Milliseconds,
|
time::Milliseconds,
|
||||||
};
|
};
|
||||||
use libsupport_zynq::{
|
use libsupport_zynq::{
|
||||||
|
@ -52,10 +53,40 @@ pub fn main_core0() {
|
||||||
IoPll::setup(1_000_000_000);
|
IoPll::setup(1_000_000_000);
|
||||||
libboard_zynq::stdio::drop_uart();
|
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");
|
info!("PLLs set up");
|
||||||
let clocks = zynq::clocks::Clocks::get();
|
let clocks = zynq::clocks::Clocks::get();
|
||||||
info!("CPU Clocks: {}/{}/{}/{}", clocks.cpu_6x4x(), clocks.cpu_3x2x(), clocks.cpu_2x(), clocks.cpu_1x());
|
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 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()) };
|
let flash_ram: &[u8] = unsafe { core::slice::from_raw_parts(flash.ptr(), flash.size()) };
|
||||||
for i in 0..=1 {
|
for i in 0..=1 {
|
||||||
|
|
|
@ -104,4 +104,18 @@ impl Clocks {
|
||||||
};
|
};
|
||||||
pll / u32::from(uart_clk_ctrl.divisor())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,436 @@
|
||||||
|
pub mod sd_card;
|
||||||
|
|
||||||
|
mod adma;
|
||||||
|
mod cmd;
|
||||||
mod regs;
|
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<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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 libregister::{register, register_at, register_bit, register_bits, register_bits_typed};
|
||||||
|
use volatile_register::{RO, RW};
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct RegisterBlock {
|
pub struct RegisterBlock {
|
||||||
pub sdma_system_address: RW<u32>,
|
pub sdma_system_address: RW<u32>,
|
||||||
|
@ -14,19 +15,24 @@ pub struct RegisterBlock {
|
||||||
/// Host. power, block gap, wakeup control
|
/// Host. power, block gap, wakeup control
|
||||||
pub control: Control,
|
pub control: Control,
|
||||||
/// Clock and timeout control, and software reset register.
|
/// Clock and timeout control, and software reset register.
|
||||||
pub timing_control: TimingControl,
|
pub clock_control: ClockControl,
|
||||||
pub interrupt_status: InterruptStatus,
|
pub interrupt_status: InterruptStatus,
|
||||||
pub interrupt_status_en: InterruptStatusEn,
|
pub interrupt_status_en: InterruptStatusEn,
|
||||||
pub interrupt_signal_en: InterruptSignalEn,
|
pub interrupt_signal_en: InterruptSignalEn,
|
||||||
pub auto_cmd12_error_status: AutoCmd12ErrorStatus,
|
pub auto_cmd12_error_status: AutoCmd12ErrorStatus,
|
||||||
pub capabilities: Capabilities,
|
pub capabilities: Capabilities,
|
||||||
|
pub unused0: RO<u32>,
|
||||||
pub max_current_capabilities: MaxCurrentCapabilities,
|
pub max_current_capabilities: MaxCurrentCapabilities,
|
||||||
|
pub unused1: RO<u32>,
|
||||||
pub force_event: ForceEvent,
|
pub force_event: ForceEvent,
|
||||||
pub adma_error_status: AdmaErrorStatus,
|
pub adma_error_status: AdmaErrorStatus,
|
||||||
pub adma_system_address: RW<u32>,
|
pub adma_system_address: RW<u32>,
|
||||||
|
pub unused2: RO<u32>,
|
||||||
pub boot_data_timeout_counter: RW<u32>,
|
pub boot_data_timeout_counter: RW<u32>,
|
||||||
pub debug_selection: DebugSelection,
|
pub debug_selection: DebugSelection,
|
||||||
|
pub unused3: [RO<u32>; 34],
|
||||||
pub spi_interrupt_support: SpiInterruptSupport,
|
pub spi_interrupt_support: SpiInterruptSupport,
|
||||||
|
pub unused4: [RO<u32>; 2],
|
||||||
pub misc_reg: MiscReg,
|
pub misc_reg: MiscReg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +56,7 @@ pub enum ResponseTypeSelect {
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum BusVoltage {
|
pub enum BusVoltage {
|
||||||
/// 3.3V
|
/// 3.3V
|
||||||
V33 = 0b111,
|
V33 = 0b111,
|
||||||
|
@ -57,6 +64,8 @@ pub enum BusVoltage {
|
||||||
V30 = 0b110,
|
V30 = 0b110,
|
||||||
/// 1.8V, typ.
|
/// 1.8V, typ.
|
||||||
V18 = 0b101,
|
V18 = 0b101,
|
||||||
|
/// No power,
|
||||||
|
V0 = 0b000,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -64,23 +73,8 @@ pub enum BusVoltage {
|
||||||
pub enum DmaSelect {
|
pub enum DmaSelect {
|
||||||
SDMA = 0b00,
|
SDMA = 0b00,
|
||||||
ADMA1 = 0b01,
|
ADMA1 = 0b01,
|
||||||
ADMA2 = 0b10,
|
ADMA2_32 = 0b10,
|
||||||
ADMA3 = 0b11,
|
ADMA2_64 = 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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -93,13 +87,15 @@ pub enum AdmaErrorState {
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum SpecificationVersion {
|
pub enum SpecificationVersion {
|
||||||
V1 = 0,
|
V1 = 0,
|
||||||
V2 = 1,
|
V2 = 1,
|
||||||
|
V3 = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
register_at!(RegisterBlock, 0xE0100000, sd0);
|
register_at!(RegisterBlock, 0xE0100000, sdio0);
|
||||||
register_at!(RegisterBlock, 0xE0101000, sd1);
|
register_at!(RegisterBlock, 0xE0101000, sdio1);
|
||||||
|
|
||||||
register!(block_size_block_count, BlockSizeBlockCount, RW, u32);
|
register!(block_size_block_count, BlockSizeBlockCount, RW, u32);
|
||||||
register_bits!(
|
register_bits!(
|
||||||
|
@ -328,27 +324,27 @@ register_bit!(
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
register!(timing_control, TimingControl, RW, u32);
|
register!(clock_control, ClockControl, RW, u32);
|
||||||
register_bit!(
|
register_bit!(
|
||||||
timing_control,
|
clock_control,
|
||||||
/// Software reset for DAT line.
|
/// Software reset for DAT line.
|
||||||
software_reset_dat,
|
software_reset_dat,
|
||||||
26
|
26
|
||||||
);
|
);
|
||||||
register_bit!(
|
register_bit!(
|
||||||
timing_control,
|
clock_control,
|
||||||
/// Software reset for CMD line.
|
/// Software reset for CMD line.
|
||||||
software_reset_cmd,
|
software_reset_cmd,
|
||||||
25
|
25
|
||||||
);
|
);
|
||||||
register_bit!(
|
register_bit!(
|
||||||
timing_control,
|
clock_control,
|
||||||
/// Software reset for ALL.
|
/// Software reset for ALL.
|
||||||
software_reset_all,
|
software_reset_all,
|
||||||
24
|
24
|
||||||
);
|
);
|
||||||
register_bits!(
|
register_bits!(
|
||||||
timing_control,
|
clock_control,
|
||||||
/// Determines the interval by which DAT line time-outs are detected.
|
/// Determines the interval by which DAT line time-outs are detected.
|
||||||
/// Interval = TMCLK * 2^(13 + val)
|
/// Interval = TMCLK * 2^(13 + val)
|
||||||
/// Note: 0b1111 is reserved.
|
/// Note: 0b1111 is reserved.
|
||||||
|
@ -357,27 +353,26 @@ register_bits!(
|
||||||
16,
|
16,
|
||||||
19
|
19
|
||||||
);
|
);
|
||||||
register_bits_typed!(
|
register_bits!(
|
||||||
timing_control,
|
clock_control,
|
||||||
/// Selects the frequency divisor, thus the clock frequency for SDCLK.
|
/// Selects the frequency divisor, thus the clock frequency for SDCLK.
|
||||||
/// Choose the smallest possible divisor which results in a clock frequency
|
/// Choose the smallest possible divisor which results in a clock frequency
|
||||||
/// that is less than or equal to the target frequency.
|
/// that is less than or equal to the target frequency.
|
||||||
sdclk_freq_divisor,
|
sdclk_freq_divisor,
|
||||||
u8,
|
u8,
|
||||||
SdclkFreqDivisor,
|
|
||||||
8,
|
8,
|
||||||
15
|
15
|
||||||
);
|
);
|
||||||
register_bit!(timing_control, sd_clk_en, 2);
|
register_bit!(clock_control, sd_clk_en, 2);
|
||||||
register_bit!(
|
register_bit!(
|
||||||
timing_control,
|
clock_control,
|
||||||
/// 1 when SD clock is stable.
|
/// 1 when SD clock is stable.
|
||||||
/// Note that this field is read-only.
|
/// Note that this field is read-only.
|
||||||
internal_clk_stable,
|
internal_clk_stable,
|
||||||
1,
|
1,
|
||||||
RO
|
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!(interrupt_status, InterruptStatus, RW, u32, 1 << 15 | 1 << 8);
|
||||||
register_bit!(interrupt_status, ceata_error, 29, WTC);
|
register_bit!(interrupt_status, ceata_error, 29, WTC);
|
||||||
|
@ -545,3 +540,9 @@ register_bits!(
|
||||||
0,
|
0,
|
||||||
7
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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<CmdTransferError> 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<Self, CardInitializationError> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,7 +94,7 @@ pub struct RegisterBlock {
|
||||||
pub gem1_clk_ctrl: GemClkCtrl,
|
pub gem1_clk_ctrl: GemClkCtrl,
|
||||||
pub smc_clk_ctrl: RW<u32>,
|
pub smc_clk_ctrl: RW<u32>,
|
||||||
pub lqspi_clk_ctrl: LqspiClkCtrl,
|
pub lqspi_clk_ctrl: LqspiClkCtrl,
|
||||||
pub sdio_clk_ctrl: RW<u32>,
|
pub sdio_clk_ctrl: SdioClkCtrl,
|
||||||
pub uart_clk_ctrl: UartClkCtrl,
|
pub uart_clk_ctrl: UartClkCtrl,
|
||||||
pub spi_clk_ctrl: RW<u32>,
|
pub spi_clk_ctrl: RW<u32>,
|
||||||
pub can_clk_ctrl: RW<u32>,
|
pub can_clk_ctrl: RW<u32>,
|
||||||
|
@ -127,7 +127,7 @@ pub struct RegisterBlock {
|
||||||
pub dmac_rst_ctrl: RW<u32>,
|
pub dmac_rst_ctrl: RW<u32>,
|
||||||
pub usb_rst_ctrl: RW<u32>,
|
pub usb_rst_ctrl: RW<u32>,
|
||||||
pub gem_rst_ctrl: RW<u32>,
|
pub gem_rst_ctrl: RW<u32>,
|
||||||
pub sdio_rst_ctrl: RW<u32>,
|
pub sdio_rst_ctrl: SdioRstCtrl,
|
||||||
pub spi_rst_ctrl: RW<u32>,
|
pub spi_rst_ctrl: RW<u32>,
|
||||||
pub can_rst_ctrl: RW<u32>,
|
pub can_rst_ctrl: RW<u32>,
|
||||||
pub i2c_rst_ctrl: RW<u32>,
|
pub i2c_rst_ctrl: RW<u32>,
|
||||||
|
@ -391,6 +391,8 @@ register_bit!(clk_621_true, clk_621_true, 0);
|
||||||
register!(aper_clk_ctrl, AperClkCtrl, RW, u32);
|
register!(aper_clk_ctrl, AperClkCtrl, RW, u32);
|
||||||
register_bit!(aper_clk_ctrl, uart1_cpu_1xclkact, 21);
|
register_bit!(aper_clk_ctrl, uart1_cpu_1xclkact, 21);
|
||||||
register_bit!(aper_clk_ctrl, uart0_cpu_1xclkact, 20);
|
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 {
|
impl AperClkCtrl {
|
||||||
pub fn enable_uart0(&mut self) {
|
pub fn enable_uart0(&mut self) {
|
||||||
self.modify(|_, w| w.uart0_cpu_1xclkact(true));
|
self.modify(|_, w| w.uart0_cpu_1xclkact(true));
|
||||||
|
@ -399,6 +401,14 @@ impl AperClkCtrl {
|
||||||
pub fn enable_uart1(&mut self) {
|
pub fn enable_uart1(&mut self) {
|
||||||
self.modify(|_, w| w.uart1_cpu_1xclkact(true));
|
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);
|
register!(rclk_ctrl, RclkCtrl, RW, u32);
|
||||||
|
@ -423,6 +433,24 @@ register_bit!(gem_clk_ctrl,
|
||||||
/// SMC reference clock control
|
/// SMC reference clock control
|
||||||
clkact, 0);
|
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!(uart_clk_ctrl, UartClkCtrl, RW, u32);
|
||||||
register_bit!(uart_clk_ctrl, clkact0, 0);
|
register_bit!(uart_clk_ctrl, clkact0, 0);
|
||||||
register_bit!(uart_clk_ctrl, clkact1, 1);
|
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!(uart_rst_ctrl, UartRstCtrl, RW, u32);
|
||||||
register_bit!(uart_rst_ctrl, uart0_ref_rst, 3);
|
register_bit!(uart_rst_ctrl, uart0_ref_rst, 3);
|
||||||
register_bit!(uart_rst_ctrl, uart1_ref_rst, 2);
|
register_bit!(uart_rst_ctrl, uart1_ref_rst, 2);
|
||||||
|
|
Loading…
Reference in New Issue