diff --git a/Cargo.toml b/Cargo.toml index 156f03f..541d1a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,4 +37,5 @@ members = [ "allwinner-rt", "allwinner-rt/macros", "examples/nezha-d1", + "examples/sdmmc", ] diff --git a/examples/nezha-d1/src/bin/uart.rs b/examples/nezha-d1/src/bin/uart.rs index e0fd820..97bd1f4 100644 --- a/examples/nezha-d1/src/bin/uart.rs +++ b/examples/nezha-d1/src/bin/uart.rs @@ -8,8 +8,8 @@ use panic_halt as _; #[entry] fn main(p: Peripherals, c: Clocks) { - let tx = p.gpio.pb8.into_function::<7>(); - let rx = p.gpio.pb9.into_function::<7>(); + let tx = p.gpio.pb8.into_function::<6>(); + let rx = p.gpio.pb9.into_function::<6>(); let mut serial = Serial::new(p.uart0, (tx, rx), Config::default(), &c, &p.ccu); writeln!(serial, "Hello World!").ok(); diff --git a/examples/sdmmc/Cargo.toml b/examples/sdmmc/Cargo.toml new file mode 100644 index 0000000..749852a --- /dev/null +++ b/examples/sdmmc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sdmmc-example" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +allwinner-hal = { path = "../.." } +allwinner-rt = { path = "../../allwinner-rt" } +panic-halt = "0.2.0" +embedded-io = "0.6.1" +embedded-sdmmc = "0.5.0" +embedded-time = "0.12.1" \ No newline at end of file diff --git a/examples/sdmmc/build.rs b/examples/sdmmc/build.rs new file mode 100644 index 0000000..48a6818 --- /dev/null +++ b/examples/sdmmc/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tallwinner-rt.ld"); +} diff --git a/examples/sdmmc/src/main.rs b/examples/sdmmc/src/main.rs new file mode 100644 index 0000000..4828708 --- /dev/null +++ b/examples/sdmmc/src/main.rs @@ -0,0 +1,271 @@ +#![no_std] +#![no_main] + +use core::arch::asm; + +use allwinner_hal::{ + ccu::{FactorN, SmhcClockSource}, + smhc::{BusWidthBits, TransferDirection}, + uart::{Config, Serial}, +}; +use allwinner_rt::{entry, Clocks, Peripherals}; +use embedded_io::Write; +use embedded_sdmmc::sdcard::proto; +use embedded_sdmmc::Block; +use embedded_time::rate::*; +use panic_halt as _; + +#[entry] +fn main(p: Peripherals, c: Clocks) { + let tx = p.gpio.pb8.into_function::<6>(); + let rx = p.gpio.pb9.into_function::<6>(); + let mut serial = Serial::new(p.uart0, (tx, rx), Config::default(), &c, &p.ccu); + + writeln!(serial, "Hello World!").ok(); + + writeln!(serial, "initialize sdmmc pins...").ok(); + let _sdmmc_pins = { + let sdc0_d1 = p.gpio.pf0.into_function::<2>(); + let sdc0_d0 = p.gpio.pf1.into_function::<2>(); + let sdc0_clk = p.gpio.pf2.into_function::<2>(); + let sdc0_cmd = p.gpio.pf3.into_function::<2>(); + let sdc0_d3 = p.gpio.pf4.into_function::<2>(); + let sdc0_d2 = p.gpio.pf5.into_function::<2>(); + (sdc0_d1, sdc0_d0, sdc0_clk, sdc0_cmd, sdc0_d3, sdc0_d2) + }; + + writeln!(serial, "initialize smhc...").ok(); + const SMHC_IDX: usize = 0; + let smhc = &p.smhc0; + let divider = 2; + let (factor_n, factor_m) = calc_clock_factor(20_000_000.Hz(), c.psi); + unsafe { + smhc.clock_control.modify(|val| val.disable_card_clock()); + + p.ccu.smhc_bgr.modify(|val| val.assert_reset::()); + p.ccu.smhc_bgr.modify(|val| val.gate_mask::()); + p.ccu.smhc_clk[SMHC_IDX].modify(|val| { + val.set_clock_source(SmhcClockSource::PllPeri1x) + .set_factor_n(factor_n) + .set_factor_m(factor_m) + .enable_clock_gating() + }); + p.ccu + .smhc_bgr + .modify(|val| val.deassert_reset::()); + p.ccu.smhc_bgr.modify(|val| val.gate_pass::()); + + smhc.global_control.modify(|val| val.set_software_reset()); + while !smhc.global_control.read().is_software_reset_cleared() {} + smhc.global_control.modify(|val| val.set_fifo_reset()); + while !smhc.global_control.read().is_fifo_reset_cleared() {} + smhc.global_control.modify(|val| val.disable_interrupt()); + + smhc.command.modify(|val| { + val.enable_wait_for_complete() + .enable_change_clock() + .set_command_start() + }); + while !smhc.command.read().is_command_start_cleared() {} + + smhc.clock_control + .modify(|val| val.set_card_clock_divider(divider - 1)); + smhc.sample_delay_control.modify(|val| { + val.set_sample_delay_software(0) + .enable_sample_delay_software() + }); + smhc.clock_control.modify(|val| val.enable_card_clock()); + + smhc.command.modify(|val| { + val.enable_wait_for_complete() + .enable_change_clock() + .set_command_start() + }); + while !smhc.command.read().is_command_start_cleared() {} + + smhc.bus_width + .modify(|val| val.set_bus_width(BusWidthBits::OneBit)); + smhc.block_size + .modify(|val| val.set_block_size(Block::LEN as u16)); + } + + writeln!(serial, "initializing SD card...").ok(); + // CMD0(reset) -> CMD8(check voltage and sdcard version) + // -> CMD55+ACMD41(init and read OCR) -> CMD2(read CID) + /// Host supports high capacity + const OCR_HCS: u32 = 0x40000000; + /// Card has finished power up routine if bit is high + const OCR_NBUSY: u32 = 0x80000000; + /// Valid bits for voltage setting + const OCR_VOLTAGE_MASK: u32 = 0x007FFF80; + send_card_command( + smhc, + proto::CMD0, + 0, + TransferMode::Disable, + ResponseMode::Disable, + false, + ); + sleep(100); // TODO: wait for interrupt instead of sleep + send_card_command( + smhc, + proto::CMD8, + 0x1AA, + TransferMode::Disable, + ResponseMode::Short, + true, + ); + sleep(100); + let data = smhc.responses[0].read(); + if data != 0x1AA { + writeln!( + serial, + "unexpected response to CMD8: {:#010X}, expected 0x1AA", + data + ) + .ok(); + loop {} + } + loop { + send_card_command( + smhc, + proto::CMD55, + 0, + TransferMode::Disable, + ResponseMode::Short, + true, + ); + sleep(100); + send_card_command( + smhc, + proto::ACMD41, + OCR_VOLTAGE_MASK & 0x00ff8000 | OCR_HCS, + TransferMode::Disable, + ResponseMode::Short, + false, + ); + sleep(100); + let ocr = smhc.responses[0].read(); + if (ocr & OCR_NBUSY) == OCR_NBUSY { + break; + } + } + const CMD2: u8 = 0x02; // TODO: should be added in `embedded_sdmmc` + send_card_command( + smhc, + CMD2, + 0, + TransferMode::Disable, + ResponseMode::Long, + true, + ); + sleep(100); + let cid: u128 = { + let mut cid = 0u128; + for i in 0..4 { + cid |= (smhc.responses[i].read() as u128) << (32 * i); + } + cid + }; + writeln!(serial, "initialize SD card success. CID={:032X}", cid).ok(); + // CID decoder: https://archive.goughlui.com/static/cidecode.htm + + // TODO: support read and write operations + + loop {} +} + +#[inline(always)] +fn sleep(n: u32) { + for _ in 0..n * 100_000 { + unsafe { asm!("nop") } + } +} + +#[inline(always)] +fn send_card_command( + smhc: &allwinner_hal::smhc::RegisterBlock, + cmd: u8, + arg: u32, + transfer_mode: TransferMode, + response_mode: ResponseMode, + crc_check: bool, +) { + let (data_trans, trans_dir) = match transfer_mode { + TransferMode::Disable => (false, TransferDirection::Read), + TransferMode::Read => (true, TransferDirection::Read), + TransferMode::Write => (true, TransferDirection::Write), + }; + let (resp_recv, resp_size) = match response_mode { + ResponseMode::Disable => (false, false), + ResponseMode::Short => (true, false), + ResponseMode::Long => (true, true), + }; + unsafe { + smhc.argument.modify(|val| val.set_argument(arg)); + smhc.command.write({ + let mut val = allwinner_hal::smhc::Command::new() + .set_command_start() + .set_command_index(cmd) + .set_transfer_direction(trans_dir) + .enable_wait_for_complete() + .enable_auto_stop(); + if data_trans { + val = val.enable_data_transfer(); + } + if crc_check { + val = val.enable_check_response_crc(); + } + if resp_recv { + val = val.enable_response_receive(); + } + if resp_size { + val = val.enable_long_response(); + } + val + }); + }; +} + +#[inline(always)] +fn calc_clock_factor(freq: Hertz, psi: Hertz) -> (FactorN, u8) { + let mut err = psi; + let (mut best_n, mut best_m) = (0, 0); + for m in 1u8..=16 { + for n in [1, 2, 4, 8] { + let actual = psi / n / m as u32; + let diff = { + if actual > freq { + actual - freq + } else { + freq - actual + } + }; + if diff < err { + err = diff; + (best_n, best_m) = (n, m); + } + } + } + let factor_n = match best_n { + 1 => FactorN::N1, + 2 => FactorN::N2, + 4 => FactorN::N4, + 8 => FactorN::N8, + _ => unreachable!(), + }; + let factor_m = best_m - 1; + (factor_n, factor_m) +} + +enum TransferMode { + Disable, + Read, + Write, +} + +enum ResponseMode { + Disable, + Short, + Long, +} diff --git a/src/ccu.rs b/src/ccu.rs index 158c8b9..70e897e 100644 --- a/src/ccu.rs +++ b/src/ccu.rs @@ -926,6 +926,8 @@ impl SmhcClock { const CLK_SRC_SEL: u32 = 0x7 << 24; const FACTOR_N: u32 = 0x3 << 8; const FACTOR_M: u32 = 0xf << 0; + const CLK_GATING: u32 = 1 << 31; + /// Get SMHC clock source. #[inline] pub const fn clock_source(self) -> SmhcClockSource { @@ -982,6 +984,21 @@ impl SmhcClock { pub const fn set_factor_m(self, val: u8) -> Self { Self((self.0 & !Self::FACTOR_M) | val as u32) } + /// Enable clock gating. + #[inline] + pub const fn enable_clock_gating(self) -> Self { + Self(self.0 | Self::CLK_GATING) + } + /// Disable clock gating. + #[inline] + pub const fn disable_clock_gating(self) -> Self { + Self(self.0 & !Self::CLK_GATING) + } + /// Get if clock gating is enabled. + #[inline] + pub const fn is_clock_gating_enabled(self) -> bool { + self.0 & Self::CLK_GATING != 0 + } } /// SMHC clock source. diff --git a/src/smhc.rs b/src/smhc.rs index 91e708d..509dfcd 100644 --- a/src/smhc.rs +++ b/src/smhc.rs @@ -47,7 +47,9 @@ pub struct RegisterBlock { _reserved2: [u32; 44], /// 0x140 - Drive Delay Control register. pub drive_delay_control: RW, - _reserved3: [u32; 16], + /// 0x144 - Sample Delay Control Register + pub sample_delay_control: RW, + _reserved3: [u32; 15], /// 0x184 - deskew control control register. pub skew_control: RW, _reserved4: [u32; 30], @@ -192,7 +194,7 @@ pub struct ClockControl(u32); impl ClockControl { const MASK_DATA0: u32 = 1 << 31; - const CCLK_CTRL: u32 = 1 << 17; + const CCLK_CTRL: u32 = 1 << 16; const CCLK_DIV: u32 = 0xFF << 0; /// If mask data0 is enabled. #[inline] @@ -356,6 +358,11 @@ impl Command { const RESP_RCV: u32 = 0x1 << 6; const CMD_IDX: u32 = 0x3F << 0; + /// Create a new command register. + #[inline] + pub const fn new() -> Self { + Self(0) + } /// Start command. #[inline] pub const fn set_command_start(self) -> Self { @@ -988,6 +995,42 @@ impl DriveDelayControl { } } +/// Sample Delay Control Register. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct SampleDelayControl(u32); + +impl SampleDelayControl { + const SAMP_DL_SW: u32 = 0x3F << 0; + const SAMP_DL_SW_EN: u32 = 1 << 7; + + /// Set sample delay software. + #[inline] + pub const fn set_sample_delay_software(self, delay: u8) -> Self { + Self((self.0 & !Self::SAMP_DL_SW) | ((delay as u32) << 0)) + } + /// Get sample delay software. + #[inline] + pub const fn sample_delay_software(self) -> u8 { + ((self.0 & Self::SAMP_DL_SW) >> 0) as u8 + } + /// Enable sample delay software. + #[inline] + pub const fn enable_sample_delay_software(self) -> Self { + Self(self.0 | Self::SAMP_DL_SW_EN) + } + /// Disable sample delay software. + #[inline] + pub const fn disable_sample_delay_software(self) -> Self { + Self(self.0 & !Self::SAMP_DL_SW_EN) + } + /// Get if sample delay software is enabled. + #[inline] + pub const fn is_sample_delay_software_enabled(self) -> bool { + (self.0 & Self::SAMP_DL_SW_EN) != 0 + } +} + /// Clock signal pad. pub trait Clk {} @@ -1030,6 +1073,7 @@ mod tests { assert_eq!(offset_of!(RegisterBlock, dma_state), 0x88); assert_eq!(offset_of!(RegisterBlock, dma_interrupt_enable), 0x8C); assert_eq!(offset_of!(RegisterBlock, drive_delay_control), 0x140); + assert_eq!(offset_of!(RegisterBlock, sample_delay_control), 0x144); assert_eq!(offset_of!(RegisterBlock, skew_control), 0x184); assert_eq!(offset_of!(RegisterBlock, fifo), 0x200); } @@ -1108,7 +1152,7 @@ mod tests { val = val.enable_card_clock(); assert!(val.is_card_clock_enabled()); - assert_eq!(val.0, 0x00020000); + assert_eq!(val.0, 0x00010000); val = val.disable_card_clock(); assert!(!val.is_card_clock_enabled());