From 118b1a737c936c22dc32280869a561fb78425f3e Mon Sep 17 00:00:00 2001 From: Junxing Zhu Date: Sat, 7 Dec 2024 17:10:23 +0800 Subject: [PATCH] sdh: add example for initializing and reading sdcard (#11) Signed-off-by: Junxing Zhu --- Cargo.toml | 1 + bouffalo-hal/src/gpio.rs | 31 +++ examples/peripherals/sdh-demo/Cargo.toml | 19 ++ examples/peripherals/sdh-demo/README.md | 8 + examples/peripherals/sdh-demo/build.rs | 3 + examples/peripherals/sdh-demo/src/main.rs | 93 +++++++ examples/peripherals/sdh-demo/src/sdh.rs | 314 ++++++++++++++++++++++ 7 files changed, 469 insertions(+) create mode 100644 examples/peripherals/sdh-demo/Cargo.toml create mode 100644 examples/peripherals/sdh-demo/README.md create mode 100644 examples/peripherals/sdh-demo/build.rs create mode 100644 examples/peripherals/sdh-demo/src/main.rs create mode 100644 examples/peripherals/sdh-demo/src/sdh.rs diff --git a/Cargo.toml b/Cargo.toml index 77a78ca..c5ef549 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,6 @@ members = [ "examples/peripherals/sdcard-demo", "examples/peripherals/sdcard-gpt-demo", "examples/peripherals/psram-demo", + "examples/peripherals/sdh-demo", ] resolver = "2" diff --git a/bouffalo-hal/src/gpio.rs b/bouffalo-hal/src/gpio.rs index e36e797..efd08d9 100644 --- a/bouffalo-hal/src/gpio.rs +++ b/bouffalo-hal/src/gpio.rs @@ -472,6 +472,37 @@ impl Alternate for Spi<1> { const F: v2::Function = v2::Function::Spi1; } +#[cfg(feature = "glb-v2")] +impl, const N: usize, M: Alternate> Pad { + /// Configures the pin to operate as a SDH pin. + #[inline] + pub fn into_sdh(self) -> Pad { + let config = v2::GpioConfig::RESET_VALUE + .enable_input() + .disable_output() + .enable_schmitt() + .set_pull(v2::Pull::Up) + .set_drive(v2::Drive::Drive0) + .set_function(Sdh::F); + unsafe { + self.base.gpio_config[N].write(config); + } + + Pad { + base: self.base, + _mode: PhantomData, + } + } +} + +/// SD Host mode (type state). +pub struct Sdh; + +impl Alternate for Sdh { + #[cfg(feature = "glb-v2")] + const F: v2::Function = v2::Function::Sdh; +} + impl, const N: usize, M: Alternate> Pad { /// Configures the pin to operate as a pull up output pin. #[inline] diff --git a/examples/peripherals/sdh-demo/Cargo.toml b/examples/peripherals/sdh-demo/Cargo.toml new file mode 100644 index 0000000..4b049df --- /dev/null +++ b/examples/peripherals/sdh-demo/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sdh-demo" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bouffalo-hal = { path = "../../../bouffalo-hal", features = ["bl808"] } +bouffalo-rt = { path = "../../../bouffalo-rt", features = ["bl808-dsp"] } +panic-halt = "1.0.0" +embedded-time = "0.12.1" +embedded-io = "0.6.1" +embedded-sdmmc = "0.8.1" + +[[bin]] +name = "sdh-demo" +test = false diff --git a/examples/peripherals/sdh-demo/README.md b/examples/peripherals/sdh-demo/README.md new file mode 100644 index 0000000..d38e43a --- /dev/null +++ b/examples/peripherals/sdh-demo/README.md @@ -0,0 +1,8 @@ +psram demo + +Build this example with: + +``` +rustup target install riscv64imac-unknown-none-elf +cargo build --target riscv64imac-unknown-none-elf --release -p sdh-demo +``` diff --git a/examples/peripherals/sdh-demo/build.rs b/examples/peripherals/sdh-demo/build.rs new file mode 100644 index 0000000..569839a --- /dev/null +++ b/examples/peripherals/sdh-demo/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tbouffalo-rt.ld"); +} diff --git a/examples/peripherals/sdh-demo/src/main.rs b/examples/peripherals/sdh-demo/src/main.rs new file mode 100644 index 0000000..4f886a0 --- /dev/null +++ b/examples/peripherals/sdh-demo/src/main.rs @@ -0,0 +1,93 @@ +#![no_std] +#![no_main] + +use core::{arch::asm, ptr}; + +use bouffalo_hal::{prelude::*, uart::Config}; +use bouffalo_rt::{entry, Clocks, Peripherals}; +use embedded_sdmmc::{Block, BlockCount, BlockDevice, BlockIdx, VolumeManager}; +use embedded_time::rate::*; +use panic_halt as _; +use sdh::*; + +mod sdh; + +struct MyTimeSource {} + +impl embedded_sdmmc::TimeSource for MyTimeSource { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + // TODO + embedded_sdmmc::Timestamp::from_calendar(2023, 1, 1, 0, 0, 0).unwrap() + } +} + +#[entry] +fn main(p: Peripherals, c: Clocks) -> ! { + // light up led + let mut led = p.gpio.io8.into_floating_output(); + let mut led_state = PinState::Low; + led.set_state(led_state).ok(); + + // init serial + let tx = p.gpio.io14.into_uart(); + let rx = p.gpio.io15.into_uart(); + let sig2 = p.uart_muxes.sig2.into_transmit::<0>(); + let sig3 = p.uart_muxes.sig3.into_receive::<0>(); + + let config = Config::default().set_baudrate(2000000.Bd()); + let mut serial = p.uart0.freerun(config, ((tx, sig2), (rx, sig3)), &c); + + writeln!(serial, "Welcome to sdh-demo!").ok(); + + // sdh gpio init + p.gpio.io0.into_sdh(); + p.gpio.io1.into_sdh(); + p.gpio.io2.into_sdh(); + p.gpio.io3.into_sdh(); + p.gpio.io4.into_sdh(); + p.gpio.io5.into_sdh(); + + // sdh init + let sdcard = sdh_init(&mut serial); + let time_source = MyTimeSource {}; + let mut volume_mgr = VolumeManager::new(sdcard, time_source); + let volume_res = volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(0)); + if let Err(e) = volume_res { + writeln!(serial, "Failed to open volume: {:?}", e).ok(); + loop {} + } + let volume0 = volume_res.unwrap(); + let root_dir = volume_mgr.open_root_dir(volume0).unwrap(); + + volume_mgr + .iterate_dir(root_dir, |entry| { + writeln!(serial, "Entry: {:?}", entry).ok(); + }) + .unwrap(); + + volume_mgr.close_dir(root_dir).unwrap(); + + loop { + led.set_state(led_state).ok(); + led_state = !led_state; + sleep_ms(1000); + } +} + +#[inline] +pub(crate) fn set_bits(val: u32, pos: u32, len: u32, val_in: u32) -> u32 { + let mask = ((1 << len) - 1) << pos; + (val & !mask) | ((val_in << pos) & mask) +} + +#[inline] +pub(crate) fn is_bit_set(val: u32, pos: u32) -> bool { + (val & (1 << pos)) != 0 +} + +#[inline] +pub(crate) fn sleep_ms(n: u32) { + for _ in 0..n * 125 { + unsafe { asm!("nop") } + } +} diff --git a/examples/peripherals/sdh-demo/src/sdh.rs b/examples/peripherals/sdh-demo/src/sdh.rs new file mode 100644 index 0000000..e7188b7 --- /dev/null +++ b/examples/peripherals/sdh-demo/src/sdh.rs @@ -0,0 +1,314 @@ +//! sdh + +use crate::*; +use embedded_io::Write; + +const GLB_BASE: u32 = 0x20000000; +const SDH_BASE: u32 = 0x20060000; + +pub struct MySdCard { + block_count: u32, +} + +impl MySdCard { + fn read_block(&self, block: &mut Block, block_idx: u32) { + let mut tmp_val = read_memory16(SDH_BASE + 0x0C); // SDH_SD_TRANSFER_MODE + tmp_val = set_bits(tmp_val, 4, 1, 1); // SDH_TO_HOST_DIR + tmp_val = set_bits(tmp_val, 2, 2, 0); // SDH_AUTO_CMD_EN + tmp_val = set_bits(tmp_val, 5, 1, 0); // SDH_MULTI_BLK_SEL + write_memory16(SDH_BASE + 0x0C, tmp_val); + write_memory16(SDH_BASE + 0x04, 512); // block_size + write_memory16(SDH_BASE + 0x06, 1); // block_count + write_memory(SDH_BASE + 0x30, 0x00000020); // SDH_ClearIntStatus(SDH_INT_BUFFER_READ_READY) + sdh_send_command(SDHResp::R1, SDHCmdType::Normal, 17, block_idx, true); + tmp_val = read_memory(SDH_BASE + 0x30); + while !is_bit_set(tmp_val, 5) { + tmp_val = read_memory(SDH_BASE + 0x30); + } + for j in 0..Block::LEN / 4 { + let val = read_memory(SDH_BASE + 0x20); + block[j * 4 + 0] = (val >> 0) as u8; + block[j * 4 + 1] = (val >> 8) as u8; + block[j * 4 + 2] = (val >> 16) as u8; + block[j * 4 + 3] = (val >> 24) as u8; + } + } +} + +impl BlockDevice for MySdCard { + type Error = core::convert::Infallible; + fn read( + &self, + blocks: &mut [Block], + start_block_idx: BlockIdx, + _reason: &str, + ) -> Result<(), Self::Error> { + for (i, block) in blocks.iter_mut().enumerate() { + self.read_block(block, start_block_idx.0 + i as u32); + } + Ok(()) + } + fn write(&self, _blocks: &[Block], _start_block_idx: BlockIdx) -> Result<(), Self::Error> { + unimplemented!(); + } + fn num_blocks(&self) -> Result { + Ok(embedded_sdmmc::BlockCount(self.block_count)) + } +} + +pub fn sdh_init(w: &mut W) -> MySdCard { + let mut tmp_val; + + // SDH_RESET + tmp_val = read_memory16(SDH_BASE + 0x2e); + tmp_val = set_bits(tmp_val, 8, 1, 1); + write_memory16(SDH_BASE + 0x2e, tmp_val); + while is_bit_set(tmp_val, 8) { + tmp_val = read_memory16(SDH_BASE + 0x2e); + } + + // GLB_Set_SDH_CLK + tmp_val = read_memory(GLB_BASE + 0x430); + tmp_val = set_bits(tmp_val, 13, 1, 0); // GLB_REG_SDH_CLK_EN + write_memory(GLB_BASE + 0x430, tmp_val); + tmp_val = read_memory(GLB_BASE + 0x430); + tmp_val = set_bits(tmp_val, 12, 1, 0); // GLB_REG_SDH_CLK_SEL + tmp_val = set_bits(tmp_val, 9, 3, 7); // GLB_REG_SDH_CLK_DIV + write_memory(GLB_BASE + 0x430, tmp_val); + let mut tmp_val = read_memory(GLB_BASE + 0x430); + tmp_val = set_bits(tmp_val, 13, 1, 1); // GLB_REG_SDH_CLK_EN + write_memory(GLB_BASE + 0x430, tmp_val); + + // SDH_Ctrl_Init + tmp_val = read_memory16(SDH_BASE + 0x2c); + tmp_val = set_bits(tmp_val, 8, 8, 0); // SDH_SD_FREQ_SEL_LO + tmp_val = set_bits(tmp_val, 6, 2, 0); // SDH_SD_FREQ_SEL_HI + tmp_val = set_bits(tmp_val, 5, 1, 0); // SDH_CLK_GEN_SEL + tmp_val = set_bits(tmp_val, 0, 1, 1); // SDH_INT_CLK_EN + tmp_val = set_bits(tmp_val, 2, 1, 1); // SDH_SD_CLK_EN + write_memory16(SDH_BASE + 0x2c, tmp_val); + tmp_val = read_memory16(SDH_BASE + 0x2c); + while !is_bit_set(tmp_val, 1) { + tmp_val = read_memory16(SDH_BASE + 0x2c); + } + tmp_val = read_memory16(SDH_BASE + 0xc); + tmp_val = set_bits(tmp_val, 0, 1, 0); // SDH_DMA_EN + write_memory16(SDH_BASE + 0xc, tmp_val); + tmp_val = read_memory16(SDH_BASE + 0x28); + tmp_val = set_bits(tmp_val, 5, 1, 0); // SDH_EX_DATA_WIDTH + tmp_val = set_bits(tmp_val, 1, 1, 0); // SDH_DATA_WIDTH + tmp_val = set_bits(tmp_val, 2, 1, 1); // SDH_HI_SPEED_EN + tmp_val = set_bits(tmp_val, 9, 3, 7); // SDH_SD_BUS_VLT + write_memory16(SDH_BASE + 0x28, tmp_val); + tmp_val = read_memory(SDH_BASE + 0x118); + tmp_val = set_bits(tmp_val, 30, 1, 1); // SDH_TX_INT_CLK_SEL + write_memory(SDH_BASE + 0x118, tmp_val); + + // SDH Enable Interrupt + tmp_val = 0; + tmp_val = set_bits(tmp_val, 5, 1, 1); // SDH_INT_BUFFER_READ_READY + write_memory(SDH_BASE + 0x34, tmp_val); + + // SDH_Set_Timeout + tmp_val = read_memory16(SDH_BASE + 0x2e); + tmp_val = set_bits(tmp_val, 0, 4, 0x0e); // SDH_TIMEOUT_VALUE + write_memory16(SDH_BASE + 0x2e, tmp_val); + + // SDH_Powon + tmp_val = read_memory16(SDH_BASE + 0x28); + tmp_val = set_bits(tmp_val, 8, 1, 1); // SDH_SD_BUS_POWER + write_memory16(SDH_BASE + 0x28, tmp_val); + + // sdcard idle + sdh_send_command(SDHResp::None, SDHCmdType::Normal, 0, 0, false); + sleep_ms(100); + + // send CMD8 + sdh_send_command(SDHResp::R7, SDHCmdType::Normal, 8, 0x1AA, false); + sleep_ms(100); + let data: u128 = sdh_get_resp(); + if data != 0x1AA { + writeln!( + *w, + "unexpected response to CMD8: {:#010X}, expected 0x1AA", + data + ) + .ok(); + loop {} + } + + loop { + const OCR_NBUSY: u32 = 0x80000000; + const OCR_VOLTAGE_MASK: u32 = 0x007FFF80; + const OCR_HCS: u32 = 0x40000000; + sdh_send_command(SDHResp::R1, SDHCmdType::Normal, 55, 0, false); + sleep_ms(100); + sdh_send_command( + SDHResp::R3, + SDHCmdType::Normal, + 41, + OCR_VOLTAGE_MASK & 0x00ff8000 | OCR_HCS, + false, + ); + sleep_ms(100); + let ocr = sdh_get_resp(); + if (ocr as u32 & OCR_NBUSY) == OCR_NBUSY { + break; + } + sleep_ms(100); + } + + // send CMD2 to get CID + sdh_send_command(SDHResp::R2, SDHCmdType::Normal, 2, 0, false); + sleep_ms(100); + let cid = sdh_get_resp(); + writeln!(*w, "cid: {:#034X}", cid).ok(); + + // send CMD3 to get RCA + sdh_send_command(SDHResp::R6, SDHCmdType::Normal, 3, 0, false); + sleep_ms(100); + let rca = sdh_get_resp() as u32 >> 16; + writeln!(*w, "rca: {:#010X}", rca).ok(); + + // send CMD9 to get CSD + sdh_send_command(SDHResp::R2, SDHCmdType::Normal, 9, rca << 16, false); + sleep_ms(100); + let csd_raw = sdh_get_resp(); + let (csd_structure, c_size) = parse_csd_v2(csd_raw); + if csd_structure != 1 { + writeln!(*w, "unexpected CSD: {:#034X}", csd_raw).ok(); + loop {} + } + + writeln!(*w, "csd: {:#034X}, c_size: {}", csd_raw, c_size).ok(); + + let block_size = 512; + let block_count = (c_size + 1) * 1024; + + // send CMD7 to select card + sdh_send_command(SDHResp::R1B, SDHCmdType::Normal, 7, rca << 16, false); + sleep_ms(100); + + // set 1 data len, CMD55 -> ACMD6 + sdh_send_command(SDHResp::R1, SDHCmdType::Normal, 55, rca << 16, false); + sleep_ms(100); + sdh_send_command(SDHResp::R1, SDHCmdType::Normal, 6, 0x0, false); + sleep_ms(100); + + writeln!( + *w, + "sdcard init done, size: {} MB", + block_count as u128 * block_size / 1024 / 1024 + ) + .ok(); + MySdCard { block_count } +} + +fn sdh_send_command( + resp_type: SDHResp, + cmd_type: SDHCmdType, + cmd_idx: u32, + argument: u32, + has_data: bool, +) { + let mut tmp_val; + let mut flag = SDHTransFlag::None as u32; + if has_data { + flag |= SDHTransFlag::DataPresent as u32; + } + match resp_type { + SDHResp::None => {} + SDHResp::R1 | SDHResp::R5 | SDHResp::R6 | SDHResp::R7 => { + flag |= SDHTransFlag::Resp48Bits as u32 + | SDHTransFlag::EnCrcCheck as u32 + | SDHTransFlag::EnIndexCheck as u32; + } + SDHResp::R1B | SDHResp::R5B => { + flag |= SDHTransFlag::Resp48BitsWithBusy as u32 + | SDHTransFlag::EnCrcCheck as u32 + | SDHTransFlag::EnIndexCheck as u32; + } + SDHResp::R2 => { + flag |= SDHTransFlag::Resp136Bits as u32 | SDHTransFlag::EnCrcCheck as u32; + } + SDHResp::R3 | SDHResp::R4 => { + flag |= SDHTransFlag::Resp48Bits as u32; + } + } + tmp_val = flag >> 16; + + tmp_val = set_bits(tmp_val, 6, 2, cmd_type as u32); + tmp_val = set_bits(tmp_val, 8, 6, cmd_idx); + write_memory(SDH_BASE + 0x08, argument); + write_memory16(SDH_BASE + 0x0E, tmp_val); +} + +fn sdh_get_resp() -> u128 { + let a = read_memory(SDH_BASE + 0x10); + let b = read_memory(SDH_BASE + 0x14); + let c = read_memory(SDH_BASE + 0x18); + let d = read_memory(SDH_BASE + 0x1C); + (a as u128) | ((b as u128) << 32) | ((c as u128) << 64) | ((d as u128) << 96) +} + +enum SDHCmdType { + Normal, + Suspend, + Resume, + Abort, + Empty, +} + +enum SDHTransFlag { + None = 0x00000000, + EnDma = 0x00000001, // Enable DMA + EnBlkCount = 0x00000002, // Enable block count + EnAutoCmd12 = 0x00000004, // Enable auto CMD12 + EnAutoCmd23 = 0x00000008, // Enable auto CMD23 + ReadData = 0x00000010, // Enable read data + MultiBlk = 0x00000020, // Enable multi-block data operation + Resp136Bits = 0x00010000, // Response is 136 bits length + Resp48Bits = 0x00020000, // Response is 48 bits length + Resp48BitsWithBusy = 0x00030000, // Response is 48 bits length with busy status + EnCrcCheck = 0x00080000, // Enable CRC check + EnIndexCheck = 0x00100000, // Enable index check + DataPresent = 0x00200000, // Data present + Suspend = 0x00400000, // Suspend command + Resume = 0x00800000, // Resume command + Abort = 0x00C00000, // Abort command +} + +enum SDHResp { + None, + R1, + R5, + R6, + R7, + R1B, + R5B, + R2, + R3, + R4, +} + +fn write_memory16(addr: u32, val: u32) { + unsafe { ptr::write_volatile(addr as *mut u16, val as u16) } +} + +fn read_memory16(addr: u32) -> u32 { + unsafe { ptr::read_volatile(addr as *const u16) as u32 } +} + +pub fn read_memory(addr: u32) -> u32 { + unsafe { ptr::read_volatile(addr as *const u32) } +} + +pub fn write_memory(addr: u32, val: u32) { + unsafe { ptr::write_volatile(addr as *mut u32, val) } +} + +fn parse_csd_v2(csd: u128) -> (u32, u32) { + let csd_structure = (((csd >> (32 * 3)) & 0xC00000) >> 22) as u32; + let c_size = (((csd >> 32) & 0x3FFFFF00) >> 8) as u32; + (csd_structure, c_size) +}