Skip to content

Commit

Permalink
sdmmc: implement BlockDevice trait and update sdmmc example (#18)
Browse files Browse the repository at this point in the history
Signed-off-by: Junxing Zhu <[email protected]>
jakezhu9 authored Jan 8, 2025

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent 2297d7e commit fb6d65f
Showing 4 changed files with 284 additions and 236 deletions.
1 change: 1 addition & 0 deletions allwinner-hal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ embedded-io = "0.6.1"
embedded-time = "0.12.1"
uart16550 = "0.0.1"
plic = "0.0.2"
embedded-sdmmc = "0.8.1"

[dev-dependencies]
memoffset = "0.8"
244 changes: 242 additions & 2 deletions allwinner-hal/src/smhc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! SD/MMC Host Controller peripheral.
use core::arch::asm;
use embedded_sdmmc::{Block, BlockDevice, BlockIdx};
use volatile_register::{RO, RW};

use crate::ccu::{self, Clocks, SmhcClockSource};
@@ -1058,7 +1060,12 @@ pub struct Smhc<SMHC, PADS> {
impl<SMHC: AsRef<RegisterBlock>, PADS> Smhc<SMHC, PADS> {
/// Create an SMHC instance.
#[inline]
pub fn new(smhc: SMHC, pads: PADS, clocks: &Clocks, ccu: &ccu::RegisterBlock) -> Self {
pub fn new<const SMHC_IDX: usize>(
smhc: SMHC,
pads: PADS,
clocks: &Clocks,
ccu: &ccu::RegisterBlock,
) -> Self {
let divider = 2;
let (factor_n, factor_m) =
ccu::calculate_best_peripheral_factors_nm(clocks.psi.0, 20_000_000);
@@ -1068,7 +1075,6 @@ impl<SMHC: AsRef<RegisterBlock>, PADS> Smhc<SMHC, PADS> {
.modify(|val| val.disable_card_clock());
}
unsafe {
const SMHC_IDX: usize = 0; // TODO
ccu.smhc_bgr.modify(|val| val.assert_reset::<SMHC_IDX>());
ccu.smhc_bgr.modify(|val| val.gate_mask::<SMHC_IDX>());
ccu.smhc_clk[SMHC_IDX].modify(|val| {
@@ -1152,6 +1158,240 @@ impl<SMHC: AsRef<RegisterBlock>, PADS> Smhc<SMHC, PADS> {
}
(self.smhc, self.pads)
}
/// Send a command to the card.
#[inline]
pub fn send_card_command(
&self,
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),
};
let smhc = self.smhc.as_ref();
if data_trans {
unsafe {
smhc.byte_count.modify(|w| w.set_byte_count(512)); // TODO
smhc.global_control
.modify(|w| w.set_access_mode(AccessMode::Ahb));
}
}
unsafe {
smhc.argument.modify(|val| val.set_argument(arg));
smhc.command.write({
let mut val = Command::default()
.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
});
};
}
/// Read the response from the card.
#[inline]
pub fn read_response(&self) -> u128 {
let smhc = self.smhc.as_ref();
let mut response = 0u128;
for i in 0..4 {
response |= (smhc.responses[i].read() as u128) << (32 * i);
}
response
}
/// Read data from first-in-first-out buffer.
#[inline]
pub fn read_data(&self, buf: &mut [u8]) {
let smhc = self.smhc.as_ref();
for i in 0..buf.len() / 4 {
while smhc.status.read().fifo_empty() {
core::hint::spin_loop();
}
let data = smhc.fifo.read();
buf[i * 4] = (data & 0xff) as u8;
buf[i * 4 + 1] = ((data >> 8) & 0xff) as u8;
buf[i * 4 + 2] = ((data >> 16) & 0xff) as u8;
buf[i * 4 + 3] = ((data >> 24) & 0xff) as u8;
}
}
}

/// Transfer mode.
pub enum TransferMode {
/// No data transfer.
Disable,
/// Read data.
Read,
/// Write data.
Write,
}

/// Response mode.
pub enum ResponseMode {
/// No response.
Disable,
/// Short response.
Short,
/// Long response.
Long,
}

pub struct SdCard<S: AsRef<RegisterBlock>, P> {
smhc: Smhc<S, P>,
block_count: u32,
}

#[derive(Debug)]
pub enum SdCardError {
Unknown,
UnexpectedResponse(u8, u128),
}

impl<S: AsRef<RegisterBlock>, P> SdCard<S, P> {
/// Create an SD card instance.
#[inline]
pub fn new(smhc: Smhc<S, P>) -> Result<Self, SdCardError> {
/// 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;

// CMD0(reset) -> CMD8(check voltage and sdcard version)
// -> CMD55+ACMD41(init and read OCR)
smhc.send_card_command(0, 0, TransferMode::Disable, ResponseMode::Disable, false);
Self::sleep(100); // TODO: wait for interrupt instead of sleep
smhc.send_card_command(8, 0x1AA, TransferMode::Disable, ResponseMode::Short, true);
Self::sleep(100);
let data = smhc.read_response();
if data != 0x1AA {
return Err(SdCardError::UnexpectedResponse(8, data));
}
loop {
smhc.send_card_command(55, 0, TransferMode::Disable, ResponseMode::Short, true);
Self::sleep(100);
smhc.send_card_command(
41,
OCR_VOLTAGE_MASK & 0x00ff8000 | OCR_HCS,
TransferMode::Disable,
ResponseMode::Short,
false,
);
Self::sleep(100);
let ocr = smhc.read_response() as u32;
if (ocr & OCR_NBUSY) == OCR_NBUSY {
break;
}
}

// Send CMD2 to get CID.
smhc.send_card_command(2, 0, TransferMode::Disable, ResponseMode::Long, true);
Self::sleep(100);
let _cid = smhc.read_response();

// Send CMD3 to get RCA.
smhc.send_card_command(3, 0, TransferMode::Disable, ResponseMode::Short, true);
Self::sleep(100);
let rca = smhc.read_response() as u32;

// Send CMD9 to get CSD.
smhc.send_card_command(9, rca, TransferMode::Disable, ResponseMode::Long, true);
Self::sleep(100);
let csd_raw = smhc.read_response();
let fixed_csd_raw = csd_raw >> 8; // FIXME: 8bit shift for long response, why?
let (csd_structure, c_size) = Self::parse_csd_v2(fixed_csd_raw);
if csd_structure != 1 {
return Err(SdCardError::UnexpectedResponse(9, csd_raw));
}

// Send CMD7 to select card.
smhc.send_card_command(7, rca, TransferMode::Disable, ResponseMode::Short, true);
Self::sleep(100);

// Set 1 data len, CMD55 -> ACMD6.
smhc.send_card_command(55, rca, TransferMode::Disable, ResponseMode::Short, true);
Self::sleep(100);
smhc.send_card_command(6, 0, TransferMode::Disable, ResponseMode::Short, true);
Self::sleep(100);

Ok(SdCard {
smhc,
block_count: (c_size + 1) * 1024,
})
}
/// Get the size of the SD card in kilobytes.
#[inline]
pub fn get_size_kb(&self) -> f64 {
(self.block_count as f64) * (512 as f64) / 1024.0
}
/// Read a block from the SD card.
#[inline]
pub fn read_block(&self, block: &mut Block, block_idx: u32) {
self.smhc
.send_card_command(17, block_idx, TransferMode::Read, ResponseMode::Short, true);
self.smhc.read_data(&mut block.contents);
}
/// Parse CSD register version 2.
#[inline]
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)
}
/// Sleep for a number of cycles.
#[inline]
fn sleep(n: u32) {
for _ in 0..n * 100_000 {
unsafe { asm!("nop") }
}
}
}

impl<S: AsRef<RegisterBlock>, P> BlockDevice for SdCard<S, P> {
type Error = core::convert::Infallible;
#[inline]
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(())
}
#[inline]
fn write(&self, _blocks: &[Block], _start_block_idx: BlockIdx) -> Result<(), Self::Error> {
todo!();
}
#[inline]
fn num_blocks(&self) -> Result<embedded_sdmmc::BlockCount, Self::Error> {
Ok(embedded_sdmmc::BlockCount(self.block_count))
}
}

/// Clock signal pad.
2 changes: 1 addition & 1 deletion examples/sdmmc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -11,5 +11,5 @@ allwinner-hal = { path = "../../allwinner-hal" }
allwinner-rt = { path = "../../allwinner-rt" }
panic-halt = "0.2.0"
embedded-io = "0.6.1"
embedded-sdmmc = "0.5.0"
embedded-sdmmc = "0.8.1"
embedded-time = "0.12.1"
273 changes: 40 additions & 233 deletions examples/sdmmc/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
#![no_std]
#![no_main]

use core::arch::asm;

use allwinner_hal::{
ccu::{PeriFactorN, SmhcClockSource},
smhc::{BusWidth, TransferDirection},
smhc::{SdCard, Smhc},
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 embedded_sdmmc::VolumeManager;
use panic_halt as _;

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) {
let tx = p.gpio.pb8.into_function::<6>();
@@ -24,7 +28,7 @@ fn main(p: Peripherals, c: Clocks) {
writeln!(serial, "Hello World!").ok();

writeln!(serial, "initialize sdmmc pins...").ok();
let _sdmmc_pins = {
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>();
@@ -35,237 +39,40 @@ fn main(p: Peripherals, c: Clocks) {
};

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::<SMHC_IDX>());
p.ccu.smhc_bgr.modify(|val| val.gate_mask::<SMHC_IDX>());
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::<SMHC_IDX>());
p.ccu.smhc_bgr.modify(|val| val.gate_pass::<SMHC_IDX>());

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.card_type
.modify(|val| val.set_bus_width(BusWidth::OneBit));
smhc.block_size
.modify(|val| val.set_block_size(Block::LEN as u16));
}
let smhc = Smhc::new::<0>(p.smhc0, sdmmc_pins, &c, &p.ccu);

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);
let sdcard = match SdCard::new(smhc) {
Ok(card) => card,
Err(e) => {
writeln!(serial, "Failed to initialize SD card: {:?}", e).ok();
loop {}
}
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") }
writeln!(
serial,
"SD card initialized, size: {:.2}GB",
sdcard.get_size_kb() / 1024.0 / 1024.0
)
.ok();

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();

#[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::default()
.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) -> (PeriFactorN, 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 => PeriFactorN::N1,
2 => PeriFactorN::N2,
4 => PeriFactorN::N4,
8 => PeriFactorN::N8,
_ => unreachable!(),
};
let factor_m = best_m - 1;
(factor_n, factor_m)
}
volume_mgr
.iterate_dir(root_dir, |entry| {
writeln!(serial, "Entry: {:?}", entry).ok();
})
.unwrap();

enum TransferMode {
Disable,
Read,
Write,
}
volume_mgr.close_dir(root_dir).unwrap();

enum ResponseMode {
Disable,
Short,
Long,
loop {}
}

0 comments on commit fb6d65f

Please sign in to comment.