Add SDIO support #21
@@ -35,6 +35,7 @@ rand = { version = "0.10", default-features = false }
|
||||
embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] }
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
|
||||
embassy-net = { version = "0.9", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] }
|
||||
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
|
||||
embassy-sync = { version = "0.8" }
|
||||
heapless = "0.9"
|
||||
axi-uartlite = { version = "0.1" }
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::error;
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::gpio::Input;
|
||||
use zynq7000_hal::prelude::*;
|
||||
use zynq7000_hal::sd::SdClockConfig;
|
||||
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, sd::SdCardUninit, uart};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard SDIO example --\n\r";
|
||||
|
||||
// These are off by default because they write to the SD card as well.
|
||||
const LOW_LEVEL_TESTS: bool = false;
|
||||
const SDMMC_RS_TESTS: bool = false;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DummyTimeSource;
|
||||
|
||||
impl embedded_sdmmc::TimeSource for DummyTimeSource {
|
||||
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
|
||||
embedded_sdmmc::Timestamp::from_calendar(1970, 1, 1, 0, 0, 0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
let sdio_clock_config =
|
||||
SdClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap();
|
||||
log::info!("SDIO clock config: {:?}", sdio_clock_config);
|
||||
let sd_card_uninit = SdCardUninit::new_for_sdio_0(
|
||||
periphs.sdio_0,
|
||||
sdio_clock_config,
|
||||
// On the zedboard, the bank has a 1.8 V voltage which is shifted up to 3.3 V by a
|
||||
// level shifter.
|
||||
zynq7000_hal::sd::IoType::LvCmos18,
|
||||
gpio_pins.mio.mio40,
|
||||
gpio_pins.mio.mio41,
|
||||
(
|
||||
gpio_pins.mio.mio42,
|
||||
gpio_pins.mio.mio43,
|
||||
gpio_pins.mio.mio44,
|
||||
gpio_pins.mio.mio45,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let card_detect = Input::new_for_mio(gpio_pins.mio.mio47).unwrap();
|
||||
let write_protect = Input::new_for_mio(gpio_pins.mio.mio46).unwrap();
|
||||
// The card detect being active low makes sense according to the Zedboard docs. Not sure
|
||||
// about write-protect though.. It seems that write protect on means that the
|
||||
// the pin is pulled high.
|
||||
log::info!("Card detect state: {:?}", card_detect.is_low());
|
||||
log::info!("Write protect state: {:?}", write_protect.is_high());
|
||||
|
||||
let capabilities = sd_card_uninit.ll().capabilities();
|
||||
log::debug!("SDIO Capabilities: {:?}", capabilities);
|
||||
|
||||
let present_state = sd_card_uninit.ll().read_present_state();
|
||||
log::debug!("SD present state: {:?}", present_state);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
log::info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
|
||||
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
|
||||
let sd_result = sd_card_uninit.initialize();
|
||||
let sd_card = match sd_result {
|
||||
Ok(card) => {
|
||||
log::info!("SD card info: {:?}", card.card_info());
|
||||
card
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("SDIO init error: {e:?}");
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
|
||||
if LOW_LEVEL_TESTS {
|
||||
log::info!("doing SD card low-level tests");
|
||||
|
||||
let mut cache_buf: [u8; 4096] = [0; 4096];
|
||||
|
||||
// cache the data, will be written back later..
|
||||
sd_card
|
||||
.read_multiple_blocks(&mut cache_buf, 0x1000)
|
||||
.unwrap();
|
||||
|
||||
let mut write_data: [u8; 4096] = [0; 4096];
|
||||
for chunk in write_data.chunks_mut(u8::MAX as usize) {
|
||||
for (idx, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte = idx as u8;
|
||||
}
|
||||
}
|
||||
sd_card.write_multiple_blocks(&write_data, 0x1000).unwrap();
|
||||
|
||||
sd_card.read_multiple_blocks(&mut buf, 0x1000).unwrap();
|
||||
for chunk in buf.chunks(u8::MAX as usize) {
|
||||
for (idx, byte) in chunk.iter().enumerate() {
|
||||
assert_eq!(idx as u8, *byte);
|
||||
}
|
||||
}
|
||||
|
||||
sd_card.write_multiple_blocks(&cache_buf, 0x1000).unwrap();
|
||||
|
||||
log::info!("SD card low-level tests success");
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
|
||||
if SDMMC_RS_TESTS {
|
||||
log::info!("doing SD card embedded-sdmmc-rs tests");
|
||||
|
||||
// Now let's look for volumes (also known as partitions) on our block device.
|
||||
// To do this we need a Volume Manager. It will take ownership of the block device.
|
||||
let volume_mgr = embedded_sdmmc::VolumeManager::new(sd_card, DummyTimeSource);
|
||||
// Try and access Volume 0 (i.e. the first partition).
|
||||
// The volume object holds information about the filesystem on that volume.
|
||||
let volume0 = volume_mgr
|
||||
.open_volume(embedded_sdmmc::VolumeIdx(0))
|
||||
.unwrap();
|
||||
|
||||
// Open the root directory (mutably borrows from the volume).
|
||||
let mut current_dir = volume0.open_root_dir().unwrap();
|
||||
log::info!("iterating root directory");
|
||||
current_dir
|
||||
.iterate_dir(|entry| {
|
||||
log::info!("{:?}", entry);
|
||||
core::ops::ControlFlow::Continue(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let new_file = current_dir
|
||||
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
|
||||
.unwrap();
|
||||
let string = "test string\n";
|
||||
new_file.write(string.as_bytes()).unwrap();
|
||||
new_file.close().unwrap();
|
||||
|
||||
let read_new = current_dir
|
||||
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadOnly)
|
||||
.unwrap();
|
||||
assert_eq!(read_new.length(), string.len() as u32);
|
||||
read_new.read(&mut buf).unwrap();
|
||||
let buf_as_str = core::str::from_utf8(&buf[0..string.len()]).unwrap();
|
||||
assert_eq!(buf_as_str, string);
|
||||
read_new.close().unwrap();
|
||||
|
||||
current_dir.delete_entry_in_dir("__T.TXT").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
current_dir.find_directory_entry("__T.TXT").unwrap_err(),
|
||||
embedded_sdmmc::Error::NotFound
|
||||
);
|
||||
|
||||
if current_dir.find_directory_entry("_TDIR").is_ok() {
|
||||
current_dir.delete_entry_in_dir("_TDIR").unwrap();
|
||||
}
|
||||
|
||||
current_dir.make_dir_in_dir("_TDIR").unwrap();
|
||||
current_dir.change_dir("_TDIR").unwrap();
|
||||
current_dir.change_dir("..").unwrap();
|
||||
current_dir.delete_entry_in_dir("_TDIR").unwrap();
|
||||
|
||||
current_dir.close().unwrap();
|
||||
|
||||
log::info!("SD card embedded-sdmmc-rs success");
|
||||
}
|
||||
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
ticker.next().await; // Wait for the next cycle of the ticker
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = gic::GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
gic::Interrupt::Sgi(_) => (),
|
||||
gic::Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
@@ -34,14 +34,16 @@ fugit = "0.3"
|
||||
critical-section = "1"
|
||||
libm = "0.2"
|
||||
log = "0.4"
|
||||
embassy-sync = "0.7"
|
||||
embassy-sync = "0.8"
|
||||
embassy-net-driver = "0.2"
|
||||
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||
smoltcp = { version = "0.13", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||
vcell = "0.1"
|
||||
raw-slicee = "0.1"
|
||||
embedded-io-async = "0.7"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
defmt = { version = "1", optional = true }
|
||||
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
|
||||
bytemuck = "1.25"
|
||||
|
||||
[features]
|
||||
std = ["thiserror/std", "alloc"]
|
||||
|
||||
@@ -8,11 +8,6 @@ use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::He
|
||||
|
||||
use super::{EthernetId, PsEthernet as _};
|
||||
|
||||
pub struct EthernetLowLevel {
|
||||
id: EthernetId,
|
||||
pub regs: zynq7000::eth::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Speed {
|
||||
Mbps10,
|
||||
@@ -52,7 +47,10 @@ impl ClockDivisors {
|
||||
|
||||
/// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
|
||||
/// which is the default clock for the Ethernet module.
|
||||
pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) {
|
||||
pub fn calculate_for_rgmii_and_io_clock(
|
||||
io_clks: &IoClocks,
|
||||
target_speed: Speed,
|
||||
) -> (Self, u32) {
|
||||
Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed)
|
||||
}
|
||||
|
||||
@@ -174,8 +172,17 @@ impl ClockDivSet {
|
||||
/// Ethernet low-level interface.
|
||||
///
|
||||
/// Basic building block for higher-level abstraction.
|
||||
pub struct EthernetLowLevel {
|
||||
id: EthernetId,
|
||||
/// Register block. Direct public access is allowed to allow low-level operations.
|
||||
pub regs: zynq7000::eth::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
impl EthernetLowLevel {
|
||||
/// Creates a new instance of the Ethernet low-level interface.
|
||||
///
|
||||
/// Returns [None] if the given registers block base address does not correspond to a valid
|
||||
/// Ethernet peripheral.
|
||||
#[inline]
|
||||
pub fn new(regs: zynq7000::eth::MmioRegisters<'static>) -> Option<Self> {
|
||||
regs.id()?;
|
||||
@@ -204,33 +211,7 @@ impl EthernetLowLevel {
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, cycles: usize) {
|
||||
let assert_reset = match self.id {
|
||||
EthernetId::Eth0 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(false)
|
||||
.with_gem0_ref_rst(true)
|
||||
.with_gem1_rx_rst(false)
|
||||
.with_gem0_rx_rst(true)
|
||||
.with_gem1_cpu1x_rst(false)
|
||||
.with_gem0_cpu1x_rst(true)
|
||||
.build(),
|
||||
EthernetId::Eth1 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(true)
|
||||
.with_gem0_ref_rst(false)
|
||||
.with_gem1_rx_rst(true)
|
||||
.with_gem0_rx_rst(false)
|
||||
.with_gem1_cpu1x_rst(true)
|
||||
.with_gem0_cpu1x_rst(false)
|
||||
.build(),
|
||||
};
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_eth(assert_reset);
|
||||
for _ in 0..cycles {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
|
||||
});
|
||||
}
|
||||
reset(self.id, cycles);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -383,3 +364,34 @@ impl EthernetLowLevel {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the Ethernet peripheral with the given ID.
|
||||
pub fn reset(id: EthernetId, cycles: usize) {
|
||||
let assert_reset = match id {
|
||||
EthernetId::Eth0 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(false)
|
||||
.with_gem0_ref_rst(true)
|
||||
.with_gem1_rx_rst(false)
|
||||
.with_gem0_rx_rst(true)
|
||||
.with_gem1_cpu1x_rst(false)
|
||||
.with_gem0_cpu1x_rst(true)
|
||||
.build(),
|
||||
EthernetId::Eth1 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(true)
|
||||
.with_gem0_ref_rst(false)
|
||||
.with_gem1_rx_rst(true)
|
||||
.with_gem0_rx_rst(false)
|
||||
.with_gem1_cpu1x_rst(true)
|
||||
.with_gem0_cpu1x_rst(false)
|
||||
.build(),
|
||||
};
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_eth(assert_reset);
|
||||
for _ in 0..cycles {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ pub mod log;
|
||||
pub mod prelude;
|
||||
pub mod priv_tim;
|
||||
pub mod qspi;
|
||||
pub mod sd;
|
||||
pub mod slcr;
|
||||
pub mod spi;
|
||||
pub mod time;
|
||||
|
||||
@@ -8,7 +8,7 @@ use zynq7000::{
|
||||
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
|
||||
SpiEnable,
|
||||
},
|
||||
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl},
|
||||
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::ResetControlQspiSmc},
|
||||
};
|
||||
|
||||
pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode};
|
||||
@@ -675,8 +675,8 @@ pub fn reset() {
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_lqspi(
|
||||
QspiResetControl::builder()
|
||||
.with_qspi_ref_reset(true)
|
||||
ResetControlQspiSmc::builder()
|
||||
.with_ref_reset(true)
|
||||
.with_cpu_1x_reset(true)
|
||||
.build(),
|
||||
);
|
||||
@@ -684,7 +684,7 @@ pub fn reset() {
|
||||
for _ in 0..3 {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT);
|
||||
regs.reset_ctrl().write_lqspi(ResetControlQspiSmc::DEFAULT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
use arbitrary_int::u6;
|
||||
use zynq7000::sdio::{BlockSelect, CommandRegister, ResponseType};
|
||||
|
||||
use embedded_sdmmc::sdcard::{AcmdId, CmdId};
|
||||
|
||||
pub struct CommandConfig {
|
||||
pub id: u6,
|
||||
pub response_type: ResponseType,
|
||||
pub index_check: bool,
|
||||
pub crc_check: bool,
|
||||
}
|
||||
|
||||
impl CommandConfig {
|
||||
pub const fn new_no_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::None,
|
||||
index_check: false,
|
||||
crc_check: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r1_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_48bits,
|
||||
index_check: true,
|
||||
crc_check: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r2_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_136bits,
|
||||
index_check: false,
|
||||
crc_check: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r3_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_48bits,
|
||||
index_check: false,
|
||||
crc_check: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r6_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_48bitsWithCheck,
|
||||
index_check: false,
|
||||
crc_check: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn build_command_without_data(config: CommandConfig) -> CommandRegister {
|
||||
CommandRegister::builder()
|
||||
.with_command_index(config.id)
|
||||
.with_command_type(zynq7000::sdio::CommandType::Normal)
|
||||
.with_data_is_present(false)
|
||||
.with_command_index_check_enable(config.index_check)
|
||||
.with_command_crc_check_enable(config.crc_check)
|
||||
.with_response_type_select(config.response_type)
|
||||
.with_block_select(zynq7000::sdio::BlockSelect::SingleBlock)
|
||||
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write)
|
||||
.with_auto_cmd12_enable(false)
|
||||
.with_block_count_enable(false)
|
||||
.with_dma_enable(false)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub const CMD0_GO_IDLE_MODE: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_no_response(CmdId::CMD0_GoIdleState.raw_value()),
|
||||
);
|
||||
pub const CMD2_ALL_SEND_CID: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r2_response(CmdId::CMD2_AllSendCid.raw_value()),
|
||||
);
|
||||
pub const CMD3_SEND_RELATIVE_ADDR: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r6_response(CmdId::CMD3_SendRelativeAddr.raw_value()),
|
||||
);
|
||||
pub const CMD7_SELECT_SD_CARD: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD7_SelectCard.raw_value()),
|
||||
);
|
||||
pub const CMD8_SEND_IF_COND: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD8_SendIfCond.raw_value()),
|
||||
);
|
||||
pub const CMD9_SEND_CSD: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r2_response(CmdId::CMD9_SendCsd.raw_value()),
|
||||
);
|
||||
pub const CMD13_SEND_STATUS: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD13_SendStatus.raw_value()),
|
||||
);
|
||||
pub const CMD17_READ_SINGLE_BLOCK: CommandRegister = CommandRegister::builder()
|
||||
.with_command_index(CmdId::CMD17_ReadSingleBlock.raw_value())
|
||||
.with_command_type(zynq7000::sdio::CommandType::Normal)
|
||||
.with_data_is_present(true)
|
||||
.with_command_index_check_enable(true)
|
||||
.with_command_crc_check_enable(true)
|
||||
.with_response_type_select(ResponseType::_48bits)
|
||||
.with_block_select(BlockSelect::SingleBlock)
|
||||
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Read)
|
||||
.with_auto_cmd12_enable(false)
|
||||
.with_block_count_enable(false)
|
||||
.with_dma_enable(false)
|
||||
.build();
|
||||
pub const CMD24_WRITE_BLOCK: CommandRegister = CommandRegister::builder()
|
||||
.with_command_index(CmdId::CMD24_WriteBlock.raw_value())
|
||||
.with_command_type(zynq7000::sdio::CommandType::Normal)
|
||||
.with_data_is_present(true)
|
||||
.with_command_index_check_enable(true)
|
||||
.with_command_crc_check_enable(true)
|
||||
.with_response_type_select(ResponseType::_48bits)
|
||||
.with_block_select(BlockSelect::SingleBlock)
|
||||
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write)
|
||||
.with_auto_cmd12_enable(false)
|
||||
.with_block_count_enable(false)
|
||||
.with_dma_enable(false)
|
||||
.build();
|
||||
|
||||
pub const CMD55_APP_CMD: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD55_AppCmd.raw_value()),
|
||||
);
|
||||
pub const ACMD6_SET_BUS_WIDTH: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(AcmdId::ACMD6_SetBusWidth.raw_value()),
|
||||
);
|
||||
pub const ACMD41_SEND_IF_COND: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r3_response(AcmdId::ACMD41_SdSendOpCond.raw_value()),
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
use crate::gpio::mio::{
|
||||
Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34,
|
||||
Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, MioPin, Pin,
|
||||
};
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
use crate::gpio::mio::{
|
||||
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
|
||||
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
|
||||
};
|
||||
|
||||
pub trait Sdio0ClockPin: MioPin {}
|
||||
pub trait Sdio0CommandPin: MioPin {}
|
||||
pub trait Sdio0Data0Pin: MioPin {}
|
||||
pub trait Sdio0Data1Pin: MioPin {}
|
||||
pub trait Sdio0Data2Pin: MioPin {}
|
||||
pub trait Sdio0Data3Pin: MioPin {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0ClockPin for Pin<Mio16> {}
|
||||
impl Sdio0ClockPin for Pin<Mio28> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0ClockPin for Pin<Mio40> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0CommandPin for Pin<Mio17> {}
|
||||
impl Sdio0CommandPin for Pin<Mio29> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0CommandPin for Pin<Mio41> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data0Pin for Pin<Mio18> {}
|
||||
impl Sdio0Data0Pin for Pin<Mio30> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data0Pin for Pin<Mio42> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data1Pin for Pin<Mio19> {}
|
||||
impl Sdio0Data1Pin for Pin<Mio31> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data1Pin for Pin<Mio43> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data2Pin for Pin<Mio20> {}
|
||||
impl Sdio0Data2Pin for Pin<Mio32> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data2Pin for Pin<Mio44> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data3Pin for Pin<Mio21> {}
|
||||
impl Sdio0Data3Pin for Pin<Mio33> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data3Pin for Pin<Mio45> {}
|
||||
|
||||
pub trait Sdio1ClockPin: MioPin {}
|
||||
pub trait Sdio1CommandPin: MioPin {}
|
||||
pub trait Sdio1Data0Pin: MioPin {}
|
||||
pub trait Sdio1Data1Pin: MioPin {}
|
||||
pub trait Sdio1Data2Pin: MioPin {}
|
||||
pub trait Sdio1Data3Pin: MioPin {}
|
||||
|
||||
impl Sdio1ClockPin for Pin<Mio12> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1ClockPin for Pin<Mio24> {}
|
||||
impl Sdio1ClockPin for Pin<Mio36> {}
|
||||
impl Sdio1ClockPin for Pin<Mio48> {}
|
||||
|
||||
impl Sdio1CommandPin for Pin<Mio11> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1CommandPin for Pin<Mio23> {}
|
||||
impl Sdio1CommandPin for Pin<Mio35> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1CommandPin for Pin<Mio47> {}
|
||||
|
||||
impl Sdio1Data0Pin for Pin<Mio10> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data0Pin for Pin<Mio22> {}
|
||||
impl Sdio1Data0Pin for Pin<Mio34> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data0Pin for Pin<Mio46> {}
|
||||
|
||||
impl Sdio1Data1Pin for Pin<Mio13> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data1Pin for Pin<Mio25> {}
|
||||
impl Sdio1Data1Pin for Pin<Mio37> {}
|
||||
impl Sdio1Data1Pin for Pin<Mio49> {}
|
||||
|
||||
impl Sdio1Data2Pin for Pin<Mio14> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data2Pin for Pin<Mio26> {}
|
||||
impl Sdio1Data2Pin for Pin<Mio38> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data2Pin for Pin<Mio50> {}
|
||||
|
||||
impl Sdio1Data2Pin for Pin<Mio15> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data3Pin for Pin<Mio27> {}
|
||||
impl Sdio1Data3Pin for Pin<Mio39> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data3Pin for Pin<Mio51> {}
|
||||
@@ -18,7 +18,7 @@ use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz};
|
||||
use arbitrary_int::{prelude::*, u3, u4, u6};
|
||||
use embedded_hal::delay::DelayNs;
|
||||
pub use embedded_hal::spi::Mode;
|
||||
use zynq7000::slcr::reset::DualRefAndClockReset;
|
||||
use zynq7000::slcr::reset::DualRefAndClockResetSpiUart;
|
||||
use zynq7000::spi::{
|
||||
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus,
|
||||
MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
|
||||
@@ -1113,13 +1113,13 @@ impl<Delay: DelayNs> embedded_hal::spi::SpiDevice for SpiWithHwCs<Delay> {
|
||||
#[inline]
|
||||
pub fn reset(id: SpiId) {
|
||||
let assert_reset = match id {
|
||||
SpiId::Spi0 => DualRefAndClockReset::builder()
|
||||
SpiId::Spi0 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(false)
|
||||
.with_periph0_ref_rst(true)
|
||||
.with_periph1_cpu1x_rst(false)
|
||||
.with_periph0_cpu1x_rst(true)
|
||||
.build(),
|
||||
SpiId::Spi1 => DualRefAndClockReset::builder()
|
||||
SpiId::Spi1 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(true)
|
||||
.with_periph0_ref_rst(false)
|
||||
.with_periph1_cpu1x_rst(true)
|
||||
@@ -1131,10 +1131,11 @@ pub fn reset(id: SpiId) {
|
||||
regs.reset_ctrl().write_spi(assert_reset);
|
||||
// Keep it in reset for some cycles.. The TMR just mentions some small delay,
|
||||
// no idea what is meant with that.
|
||||
for _ in 0..3 {
|
||||
for _ in 0..5 {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_spi(DualRefAndClockReset::DEFAULT);
|
||||
regs.reset_ctrl()
|
||||
.write_spi(DualRefAndClockResetSpiUart::ZERO);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use core::convert::Infallible;
|
||||
use arbitrary_int::u3;
|
||||
use libm::round;
|
||||
use zynq7000::{
|
||||
slcr::reset::DualRefAndClockReset,
|
||||
slcr::reset::DualRefAndClockResetSpiUart,
|
||||
uart::{
|
||||
BaudRateDivisor, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl,
|
||||
MmioRegisters, Mode, UART_0_BASE, UART_1_BASE,
|
||||
@@ -720,13 +720,13 @@ impl embedded_io::Read for Uart {
|
||||
#[inline]
|
||||
pub fn reset(id: UartId) {
|
||||
let assert_reset = match id {
|
||||
UartId::Uart0 => DualRefAndClockReset::builder()
|
||||
UartId::Uart0 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(false)
|
||||
.with_periph0_ref_rst(true)
|
||||
.with_periph1_cpu1x_rst(false)
|
||||
.with_periph0_cpu1x_rst(true)
|
||||
.build(),
|
||||
UartId::Uart1 => DualRefAndClockReset::builder()
|
||||
UartId::Uart1 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(true)
|
||||
.with_periph0_ref_rst(false)
|
||||
.with_periph1_cpu1x_rst(true)
|
||||
@@ -736,9 +736,12 @@ pub fn reset(id: UartId) {
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_uart(assert_reset);
|
||||
// Keep it in reset for one cycle.. not sure if this is necessary.
|
||||
aarch32_cpu::asm::nop();
|
||||
regs.reset_ctrl().write_uart(DualRefAndClockReset::DEFAULT);
|
||||
// Keep it in reset for a few cycles.. not sure if this is necessary.
|
||||
for _ in 0..5 {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl()
|
||||
.write_uart(DualRefAndClockResetSpiUart::ZERO);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Updated IPTR registers in the GIC module to use a custom register type instead of a raw u32.
|
||||
- Added SDIO registers.
|
||||
- Fixed wrong position in QSPI reset register in SLCR Module
|
||||
- Added some missing reset register definitions.
|
||||
|
||||
# [v0.1.1] 2025-10-09
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ pub mod l2_cache;
|
||||
pub mod mpcore;
|
||||
pub mod priv_tim;
|
||||
pub mod qspi;
|
||||
pub mod sdio;
|
||||
pub mod slcr;
|
||||
pub mod spi;
|
||||
pub mod ttc;
|
||||
@@ -63,6 +64,8 @@ pub struct Peripherals {
|
||||
pub qspi: qspi::MmioRegisters<'static>,
|
||||
pub devcfg: devcfg::MmioRegisters<'static>,
|
||||
pub xadc: xadc::MmioRegisters<'static>,
|
||||
pub sdio_0: sdio::MmioRegisters<'static>,
|
||||
pub sdio_1: sdio::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
impl Peripherals {
|
||||
@@ -103,6 +106,8 @@ impl Peripherals {
|
||||
qspi: qspi::Registers::new_mmio_fixed(),
|
||||
devcfg: devcfg::Registers::new_mmio_fixed(),
|
||||
xadc: xadc::Registers::new_mmio_fixed(),
|
||||
sdio_0: sdio::Registers::new_mmio_fixed_0(),
|
||||
sdio_1: sdio::Registers::new_mmio_fixed_1(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,509 @@
|
||||
use arbitrary_int::{u2, u3, u4, u6, u12};
|
||||
|
||||
pub const SDIO_BASE_ADDR_0: usize = 0xE010_0000;
|
||||
pub const SDIO_BASE_ADDR_1: usize = 0xE010_1000;
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BufferSize {
|
||||
_4kB = 0b000,
|
||||
_8kB = 0b001,
|
||||
_16kB = 0b010,
|
||||
_32kB = 0b011,
|
||||
_64kB = 0b100,
|
||||
_128kB = 0b101,
|
||||
_256kB = 0b110,
|
||||
_512kB = 0b111,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct BlockParams {
|
||||
#[bits(16..=31, rw)]
|
||||
blocks_count: u16,
|
||||
#[bits(12..=14, rw)]
|
||||
buffer_size: BufferSize,
|
||||
#[bits(0..=11, rw)]
|
||||
block_size: u12,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub enum CommandType {
|
||||
#[default]
|
||||
Normal = 0b00,
|
||||
Suspend = 0b01,
|
||||
Resume = 0b10,
|
||||
Abort = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ResponseType {
|
||||
// No response.
|
||||
None = 0b00,
|
||||
_136bits = 0b01,
|
||||
_48bits = 0b10,
|
||||
_48bitsWithCheck = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BlockSelect {
|
||||
SingleBlock = 0,
|
||||
MultiBlock = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TransferDirection {
|
||||
/// Host to card.
|
||||
Write = 0,
|
||||
/// Card to host.
|
||||
Read = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct CommandRegister {
|
||||
/// Set to command number (CMD0-63, ACMD0-63)
|
||||
#[bits(24..=29, rw)]
|
||||
command_index: u6,
|
||||
#[bits(22..=23, rw)]
|
||||
command_type: CommandType,
|
||||
/// Set to [false] for the following:
|
||||
///
|
||||
/// 1. Commands using only CMD line (ex. CMD52).
|
||||
/// 2. Commands with no data transfer but using busy signal on DAT\[0\].
|
||||
/// 3. Resume Command.
|
||||
#[bit(21, rw)]
|
||||
data_is_present: bool,
|
||||
/// When 1, the host controller checks the index field in the response to see if it has the
|
||||
/// same value as the command index.
|
||||
#[bit(20, rw)]
|
||||
command_index_check_enable: bool,
|
||||
/// When 1, the host controller checks the CRC field in the response.
|
||||
#[bit(18, rw)]
|
||||
command_crc_check_enable: bool,
|
||||
#[bits(16..=17, rw)]
|
||||
response_type_select: ResponseType,
|
||||
#[bit(5, rw)]
|
||||
block_select: BlockSelect,
|
||||
#[bit(4, rw)]
|
||||
data_transfer_direction: TransferDirection,
|
||||
/// Multiple block transfers for memory require CMD12 to stop the transaction. When this bit is
|
||||
/// 1, the host controller issues CMD12 automatically when completing the last block tranfer.
|
||||
#[bit(2, rw)]
|
||||
auto_cmd12_enable: bool,
|
||||
/// Enable block count register, which is only relevant for multiple block transfers.
|
||||
#[bit(1, rw)]
|
||||
block_count_enable: bool,
|
||||
#[bit(0, rw)]
|
||||
dma_enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct PresentState {
|
||||
#[bit(24, r)]
|
||||
cmd_line_signal_level: bool,
|
||||
#[bits(20..=23, r)]
|
||||
data_line_signal_level: u4,
|
||||
/// The Write Protect Switch is supported for memory and combo cards. This bit reflects the
|
||||
/// inversion of the SDx_WP pin.
|
||||
#[bit(19, r)]
|
||||
write_protect_switch_level: bool,
|
||||
/// This bit reflects the inverse value of the SDx_CDn pin.
|
||||
#[bit(18, r)]
|
||||
card_detect_pin_level: bool,
|
||||
/// This bit is used for testing. If it is 0, the Card Detect Pin Level is not stable. If this
|
||||
/// bit is set to 1, it means the Card Detect Pin Level is stable. The Software Reset For All
|
||||
/// in the Software Reset Register shall not affect this bit.
|
||||
#[bit(17, r)]
|
||||
card_state_stable: bool,
|
||||
/// This bit indicates whether a card has been inserted. Changing from 0 to 1 generates a Card
|
||||
/// Insertion interrupt in the Normal Interrupt Status register and changing from 1 to 0
|
||||
/// generates a Card Removal Interrupt in the Normal Interrupt Status register. The Software
|
||||
/// Reset For All in the Software Reset register shall not affect this bit. If a Card is
|
||||
/// removed while its power is on and its clock is oscillating, the HC shall clear SD Bus Power
|
||||
/// in the Power Control register and SD Clock Enable in the Clock control register. In
|
||||
/// addition the HD should clear the HC by the Software Reset For All in Software register. The
|
||||
/// card detect is active regardless of the SD Bus Power.
|
||||
#[bit(16, r)]
|
||||
card_inserted: bool,
|
||||
/// This status is used for non-DMA read transfers. This read only flag indicates that valid
|
||||
/// data exists in the host side buffer status. If this bit is 1, readable data exists in the
|
||||
/// buffer. A change of this bit from 1 to 0 occurs when all the block data is read from the
|
||||
/// buffer. A change of this bit from 0 to 1 occurs when all the block data is ready in the
|
||||
/// buffer and generates the Buffer Read Ready Interrupt.
|
||||
#[bit(11, r)]
|
||||
buffer_readable: bool,
|
||||
/// This status is used for non-DMA write transfers. This read only flag indicates if space is
|
||||
/// available for write data. If this bit is 1, data can be written to the buffer. A change of
|
||||
/// this bit from 1 to 0 occurs when all the block data is written to the buffer. A change of
|
||||
/// this bit from 0 to 1 occurs when top of block data can be written to the buffer and
|
||||
/// generates the Buffer Write Ready Interrupt.
|
||||
#[bit(10, r)]
|
||||
buffer_writable: bool,
|
||||
/// This status is used for detecting completion of a read transfer. This bit is set to 1 for
|
||||
/// either of the following conditions:
|
||||
///
|
||||
/// 1. After the end bit of the read command
|
||||
/// 2. When writing a 1 to continue Request in the Block Gap Control register to restart a read
|
||||
/// transfer.
|
||||
///
|
||||
/// This bit is cleared to 0 for either of the following conditions:
|
||||
///
|
||||
/// 1. When the last data block as specified by block length is transferred to the system.
|
||||
/// 2. When all valid data blocks have been transferred to the system and no current block
|
||||
/// transfers are being sent as a result of the Stop At Block Gap Request set to 1. A transfer
|
||||
/// complete interrupt is generated when this bit changes to 0.
|
||||
#[bit(9, r)]
|
||||
read_transfer_active: bool,
|
||||
/// This status indicates a write transfer is active. If this bit is 0, it means no valid write
|
||||
/// data exists in the HC. This bit is set in either of the following cases: 1. After the end
|
||||
/// bit of the write command. 2. When writing a 1 to Continue Request in the Block Gap Control
|
||||
/// register to restart a write transfer.
|
||||
///
|
||||
/// This bit is cleared in either of the following cases:
|
||||
///
|
||||
/// 1. After getting the CRC status of the last data block as specified by the transfer count
|
||||
/// (Single or Multiple)
|
||||
/// 2. After getting a CRC status of any block where data transmission is about to be stopped
|
||||
/// by a Stop At Block Gap Request.
|
||||
///
|
||||
/// During a write transaction, a Block Gap Event interrupt is generated when this bit is
|
||||
/// changed to 0, as a result of the Stop At Block Gap Request being set. This status is useful
|
||||
/// for the HD in determining when to issue commands during write busy.
|
||||
#[bit(8, r)]
|
||||
write_transfer_active: bool,
|
||||
#[bit(2, r)]
|
||||
dat_line_active: bool,
|
||||
/// This status bit is generated if either the DAT Line Active or the Read transfer Active is
|
||||
/// set to 1. If this bit is 0, it indicates the HC can issue the next SD command. Commands
|
||||
/// with busy signal belong to Command Inhibit (DAT) (ex. R1b, R5b type). Changing from 1 to 0
|
||||
/// generates a Transfer Complete interrupt in the Normal interrupt status register.
|
||||
#[bit(1, r)]
|
||||
command_inhibit_dat: bool,
|
||||
/// 0 indicates the CMD line is not in use and the host controller can issue a SD command
|
||||
/// using the CMD line. This bit is set immediately after the Command register (00Fh) is
|
||||
/// written. This bit is cleared when the command response is received. Even if the Command
|
||||
/// Inhibit (DAT) is set to 1, Commands using only the CMD line can be issued if this bit is 0.
|
||||
/// Changing from 1 to 0 generates a Command complete interrupt in the Normal Interrupt Status
|
||||
/// register. If the HC cannot issue the command because of a command conflict error or because
|
||||
/// of Command Not Issued By Auto CMD12 Error, this bit shall remain 1 and the Command Complete
|
||||
/// is not set. Status issuing Auto CMD12 is not read from this bit.
|
||||
#[bit(0, r)]
|
||||
command_inhibit_cmd: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BusWidth {
|
||||
_1bit = 0,
|
||||
_4bits = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DmaSelect {
|
||||
Sdma = 0b00,
|
||||
Adma1_32bits = 0b01,
|
||||
Adma2_32bits = 0b10,
|
||||
Adma2_64bits = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SdBusVoltageSelect {
|
||||
Off = 0b000,
|
||||
_1_8V = 0b101,
|
||||
_3_0V = 0b110,
|
||||
_3_3V = 0b111,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct HostPowerBlockgapWakeupControl {
|
||||
#[bit(26, rw)]
|
||||
wakeup_event_enable_on_sd_card_removal: bool,
|
||||
#[bit(25, rw)]
|
||||
wakeup_event_enable_on_sd_card_insertion: bool,
|
||||
#[bit(24, rw)]
|
||||
wakeup_event_enable_on_card_interrupt: bool,
|
||||
#[bit(19, rw)]
|
||||
interrupt_at_block_gap: bool,
|
||||
#[bit(18, rw)]
|
||||
read_wait_control: bool,
|
||||
#[bit(17, rw)]
|
||||
continue_request: bool,
|
||||
#[bit(16, rw)]
|
||||
stop_as_block_gap_request: bool,
|
||||
#[bits(9..=11, rw)]
|
||||
sd_bus_voltage_select: Option<SdBusVoltageSelect>,
|
||||
#[bit(8, rw)]
|
||||
sd_bus_power: bool,
|
||||
#[bit(7, rw)]
|
||||
card_detect_signal_detection: bool,
|
||||
#[bit(6, rw)]
|
||||
card_detetect_test_level: bool,
|
||||
#[bits(3..=4, rw)]
|
||||
dma_select: DmaSelect,
|
||||
#[bit(2, rw)]
|
||||
high_speed_enable: bool,
|
||||
#[bit(1, rw)]
|
||||
bus_width: BusWidth,
|
||||
#[bit(0, rw)]
|
||||
led_control: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u8, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SdClockDivisor {
|
||||
Div256 = 0x80,
|
||||
Div128 = 0x40,
|
||||
Div64 = 0x20,
|
||||
Div32 = 0x10,
|
||||
Div16 = 0x08,
|
||||
Div8 = 0x04,
|
||||
Div4 = 0x02,
|
||||
Div2 = 0x01,
|
||||
Div1 = 0x00,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct ClockAndTimeoutAndSwResetControl {
|
||||
#[bit(26, rw)]
|
||||
software_reset_for_dat_line: bool,
|
||||
#[bit(25, rw)]
|
||||
software_reset_for_cmd_line: bool,
|
||||
#[bit(24, rw)]
|
||||
software_reset_for_all: bool,
|
||||
/// Interval: TMCLK * 2^(13 + register value)
|
||||
///
|
||||
/// 0b1111 is reserved.
|
||||
#[bits(16..=19, rw)]
|
||||
data_timeout_counter_value: u4,
|
||||
#[bits(8..=15, rw)]
|
||||
sdclk_frequency_select: Option<SdClockDivisor>,
|
||||
#[bit(2, rw)]
|
||||
sd_clock_enable: bool,
|
||||
#[bit(1, r)]
|
||||
internal_clock_stable: bool,
|
||||
#[bit(0, rw)]
|
||||
internal_clock_enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct InterruptStatus {
|
||||
#[bit(29, rw)]
|
||||
ceata_error_status: bool,
|
||||
#[bit(28, rw)]
|
||||
target_response_error: bool,
|
||||
#[bit(25, rw)]
|
||||
adma_error: bool,
|
||||
#[bit(24, rw)]
|
||||
auto_cmd12_error: bool,
|
||||
#[bit(23, rw)]
|
||||
current_limit_error: bool,
|
||||
#[bit(22, rw)]
|
||||
data_end_bit_error: bool,
|
||||
#[bit(21, rw)]
|
||||
data_crc_error: bool,
|
||||
#[bit(20, rw)]
|
||||
data_timeout_error: bool,
|
||||
#[bit(19, rw)]
|
||||
command_index_error: bool,
|
||||
#[bit(18, rw)]
|
||||
command_end_bit_error: bool,
|
||||
#[bit(17, rw)]
|
||||
command_crc_error: bool,
|
||||
#[bit(16, rw)]
|
||||
command_timeout_error: bool,
|
||||
#[bit(15, r)]
|
||||
error_interrupt: bool,
|
||||
#[bit(10, rw)]
|
||||
boot_terminate: bool,
|
||||
#[bit(9, rw)]
|
||||
boot_ack_recv: bool,
|
||||
#[bit(8, r)]
|
||||
card_interrupt: bool,
|
||||
#[bit(7, rw)]
|
||||
card_removal: bool,
|
||||
#[bit(6, rw)]
|
||||
card_insertion: bool,
|
||||
#[bit(5, rw)]
|
||||
buffer_read_ready: bool,
|
||||
#[bit(4, rw)]
|
||||
buffer_write_ready: bool,
|
||||
#[bit(3, rw)]
|
||||
dma_interrupt: bool,
|
||||
#[bit(2, rw)]
|
||||
blockgap_event: bool,
|
||||
#[bit(1, rw)]
|
||||
transfer_complete: bool,
|
||||
#[bit(0, rw)]
|
||||
command_complete: bool,
|
||||
}
|
||||
|
||||
impl InterruptStatus {
|
||||
pub const ALL_BITS: u32 = 0x33FF06FF;
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct InterruptMask {
|
||||
#[bit(29, rw)]
|
||||
ceata_error_status: bool,
|
||||
#[bit(28, rw)]
|
||||
target_response_error: bool,
|
||||
#[bit(25, rw)]
|
||||
adma_error: bool,
|
||||
#[bit(24, rw)]
|
||||
auto_cmd12_error: bool,
|
||||
#[bit(23, rw)]
|
||||
current_limit_error: bool,
|
||||
#[bit(22, rw)]
|
||||
data_end_bit_error: bool,
|
||||
#[bit(21, rw)]
|
||||
data_crc_error: bool,
|
||||
#[bit(20, rw)]
|
||||
data_timeout_error: bool,
|
||||
#[bit(19, rw)]
|
||||
command_index_error: bool,
|
||||
#[bit(18, rw)]
|
||||
command_end_bit_error: bool,
|
||||
#[bit(17, rw)]
|
||||
command_crc_error: bool,
|
||||
#[bit(16, rw)]
|
||||
command_timeout_error: bool,
|
||||
#[bit(15, rw)]
|
||||
error_interrupt: bool,
|
||||
#[bit(10, rw)]
|
||||
boot_terminate: bool,
|
||||
#[bit(9, rw)]
|
||||
boot_ack_recv: bool,
|
||||
#[bit(8, rw)]
|
||||
card_interrupt: bool,
|
||||
#[bit(7, rw)]
|
||||
card_removal: bool,
|
||||
#[bit(6, rw)]
|
||||
card_insertion: bool,
|
||||
#[bit(5, rw)]
|
||||
buffer_read_ready: bool,
|
||||
#[bit(4, rw)]
|
||||
buffer_write_ready: bool,
|
||||
#[bit(3, rw)]
|
||||
dma_interrupt: bool,
|
||||
#[bit(2, rw)]
|
||||
blockgap_event: bool,
|
||||
#[bit(1, rw)]
|
||||
transfer_complete: bool,
|
||||
#[bit(0, rw)]
|
||||
command_complete: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct Capabilities {
|
||||
#[bit(30, rw)]
|
||||
spi_block_mode: bool,
|
||||
#[bit(29, rw)]
|
||||
spi_mode: bool,
|
||||
#[bit(28, rw)]
|
||||
_64_bit_system_bus_support: bool,
|
||||
#[bit(27, rw)]
|
||||
interrupt_mode: bool,
|
||||
#[bit(26, rw)]
|
||||
voltage_support_1_8v: bool,
|
||||
#[bit(25, rw)]
|
||||
voltage_support_3_0v: bool,
|
||||
#[bit(24, rw)]
|
||||
voltage_support_3_3v: bool,
|
||||
#[bit(23, rw)]
|
||||
suspend_resume_support: bool,
|
||||
#[bit(22, rw)]
|
||||
sdma_support: bool,
|
||||
#[bit(21, rw)]
|
||||
high_speed_support: bool,
|
||||
#[bit(19, rw)]
|
||||
adma2_support: bool,
|
||||
#[bit(18, rw)]
|
||||
extended_media_bus_support: bool,
|
||||
#[bits(16..=17, rw)]
|
||||
max_block_length: u2,
|
||||
#[bit(7, rw)]
|
||||
timeout_clock_unit: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct BlockSizeRegister {
|
||||
/// Enabled when block count enable in the transfer mode register is set to 1 and only
|
||||
/// valid for multiple block transfers.
|
||||
#[bits(16..=31, rw)]
|
||||
block_counts_for_current_transfer: u16,
|
||||
#[bits(12..=14, rw)]
|
||||
host_sdma_buffer_size: u3,
|
||||
#[bits(0..=11, rw)]
|
||||
transfer_block_size: u12,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct Registers {
|
||||
sdma_system_addr: u32,
|
||||
block_params: BlockSizeRegister,
|
||||
/// Bit 39-8 of Command-Format.
|
||||
argument: u32,
|
||||
command: CommandRegister,
|
||||
#[mmio(PureRead)]
|
||||
responses: [u32; 4],
|
||||
buffer_data_port: u32,
|
||||
#[mmio(PureRead)]
|
||||
present_state: PresentState,
|
||||
host_power_blockgap_wakeup_control: HostPowerBlockgapWakeupControl,
|
||||
clock_timeout_sw_reset_control: ClockAndTimeoutAndSwResetControl,
|
||||
interrupt_status: InterruptStatus,
|
||||
interrupt_status_enable: InterruptMask,
|
||||
interrupt_signal_enable: InterruptMask,
|
||||
#[mmio(PureRead)]
|
||||
auto_cmd12_error_status: u32,
|
||||
#[mmio(PureRead)]
|
||||
capabilities: Capabilities,
|
||||
_gap_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
maximum_current_capabilities: u32,
|
||||
_gap_1: u32,
|
||||
force_event_register: u32,
|
||||
adma_error_status: u32,
|
||||
adma_system_address: u32,
|
||||
_gap_2: u32,
|
||||
boot_timeout_control: u32,
|
||||
debug_selection: u32,
|
||||
_gap_3: [u32; 0x22],
|
||||
spi_interrupt_support: u32,
|
||||
_gap_4: [u32; 0x2],
|
||||
slot_interrupt_status_host_controll_version: u32,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Registers>(), 0x100);
|
||||
|
||||
impl Registers {
|
||||
/// Create a new SDIO MMIO instance for SDIO 0 at address [SDIO_BASE_ADDR_0].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
||||
/// interfere with each other.
|
||||
#[inline]
|
||||
pub const unsafe fn new_mmio_fixed_0() -> MmioRegisters<'static> {
|
||||
unsafe { Self::new_mmio_at(SDIO_BASE_ADDR_0) }
|
||||
}
|
||||
|
||||
/// Create a new SDIO MMIO instance for SDIO 1 at address [SDIO_BASE_ADDR_1].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
||||
/// interfere with each other.
|
||||
#[inline]
|
||||
pub const unsafe fn new_mmio_fixed_1() -> MmioRegisters<'static> {
|
||||
unsafe { Self::new_mmio_at(SDIO_BASE_ADDR_1) }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use arbitrary_int::u17;
|
||||
|
||||
use super::{RESET_BLOCK_OFFSET, SLCR_BASE_ADDR};
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
@@ -11,7 +13,7 @@ pub struct DualClockReset {
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct DualRefAndClockReset {
|
||||
pub struct DualRefAndClockResetSpiUart {
|
||||
/// Periperal 1 Reference software reset.
|
||||
#[bit(3, rw)]
|
||||
periph1_ref_rst: bool,
|
||||
@@ -26,6 +28,22 @@ pub struct DualRefAndClockReset {
|
||||
periph0_cpu1x_rst: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct DualRefAndClockResetSdio {
|
||||
/// Periperal 1 Reference software reset.
|
||||
#[bit(5, rw)]
|
||||
periph1_ref_rst: bool,
|
||||
/// Peripheral 0 Reference software reset.
|
||||
#[bit(4, rw)]
|
||||
periph0_ref_rst: bool,
|
||||
/// Peripheral 1 AMBA software reset.
|
||||
#[bit(1, rw)]
|
||||
periph1_cpu1x_rst: bool,
|
||||
/// Peripheral 0 AMBA software reset.
|
||||
#[bit(0, rw)]
|
||||
periph0_cpu1x_rst: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct GpioClockReset {
|
||||
#[bit(0, rw)]
|
||||
@@ -49,38 +67,108 @@ pub struct EthernetReset {
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct QspiResetControl {
|
||||
pub struct ResetControlQspiSmc {
|
||||
#[bit(1, rw)]
|
||||
qspi_ref_reset: bool,
|
||||
ref_reset: bool,
|
||||
#[bit(0, rw)]
|
||||
cpu_1x_reset: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct FpgaResetControl {
|
||||
/// This block always needs to be written with 0. I think it contains some other hidden
|
||||
/// reset lines.
|
||||
#[bits(8..=24, rw)]
|
||||
zero_block_0: u17,
|
||||
#[bit(3, rw)]
|
||||
fpga_3: bool,
|
||||
#[bit(2, rw)]
|
||||
fpga_2: bool,
|
||||
#[bit(1, rw)]
|
||||
fpga_1: bool,
|
||||
#[bit(0, rw)]
|
||||
fpga_0: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct CpuResetControl {
|
||||
#[bit(8, rw)]
|
||||
peripheral_reset: bool,
|
||||
#[bit(5, rw)]
|
||||
cpu1_clockstop: bool,
|
||||
#[bit(4, rw)]
|
||||
cpu0_clockstop: bool,
|
||||
#[bit(1, rw)]
|
||||
cpu1_reset: bool,
|
||||
#[bit(0, rw)]
|
||||
cpu0_reset: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct PsResetControl {
|
||||
#[bit(0, rw)]
|
||||
soft_reset: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct ResetControlOcmDdr {
|
||||
#[bit(0, rw)]
|
||||
reset: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct ResetControlInterconnect {
|
||||
/// Care must be taken to ensure that the AXI
|
||||
/// interconnect does not have outstanding
|
||||
/// transactions and the bus is idle.
|
||||
#[bit(0, rw)]
|
||||
reset: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ApuWatchdogTarget {
|
||||
/// Same system level as PS_SRST_B.
|
||||
PsSrstB = 1,
|
||||
CpuAssociatedWithWdt = 0,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
pub struct WatchTimerResetControl {
|
||||
#[bit(1, rw)]
|
||||
apu_wdt_1_reset_target: ApuWatchdogTarget,
|
||||
#[bit(0, rw)]
|
||||
apu_wdt_0_reset_target: ApuWatchdogTarget,
|
||||
}
|
||||
|
||||
/// Reset control block.
|
||||
///
|
||||
/// All reset signal bits are active high, writing a 1 asserts the reset.
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct ResetControl {
|
||||
/// PS Software reset control
|
||||
pss: u32,
|
||||
ddr: u32,
|
||||
/// Processing System Software reset control
|
||||
pss: PsResetControl,
|
||||
ddr: ResetControlOcmDdr,
|
||||
/// Central interconnect reset control
|
||||
topsw: u32,
|
||||
topsw: ResetControlInterconnect,
|
||||
dmac: u32,
|
||||
usb: u32,
|
||||
eth: EthernetReset,
|
||||
sdio: DualRefAndClockReset,
|
||||
spi: DualRefAndClockReset,
|
||||
sdio: DualRefAndClockResetSdio,
|
||||
spi: DualRefAndClockResetSpiUart,
|
||||
can: DualClockReset,
|
||||
i2c: DualClockReset,
|
||||
uart: DualRefAndClockReset,
|
||||
uart: DualRefAndClockResetSpiUart,
|
||||
gpio: GpioClockReset,
|
||||
lqspi: QspiResetControl,
|
||||
smc: u32,
|
||||
ocm: u32,
|
||||
lqspi: ResetControlQspiSmc,
|
||||
smc: ResetControlQspiSmc,
|
||||
ocm: ResetControlOcmDdr,
|
||||
_gap0: u32,
|
||||
fpga: u32,
|
||||
a9_cpu: u32,
|
||||
fpga: FpgaResetControl,
|
||||
a9_cpu: CpuResetControl,
|
||||
_gap1: u32,
|
||||
rs_awdt: u32,
|
||||
rs_awdt: WatchTimerResetControl,
|
||||
}
|
||||
|
||||
impl ResetControl {
|
||||
|
||||
Reference in New Issue
Block a user