use crate::response::ResponseType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DataDirection {
None,
Read,
Write,
}
impl DataDirection {
pub const fn is_none(self) -> bool {
matches!(self, DataDirection::None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Command {
pub cmd: u8,
pub arg: u32,
pub resp_type: ResponseType,
}
impl Command {
pub const fn new(cmd: u8, arg: u32, resp_type: ResponseType) -> Self {
Self {
cmd,
arg,
resp_type,
}
}
pub const fn with_resp_type(self, resp_type: ResponseType) -> Self {
Self { resp_type, ..self }
}
pub fn index(&self) -> u8 {
self.cmd
}
pub fn argument(&self) -> u32 {
self.arg
}
pub const fn data_direction(&self) -> DataDirection {
match self.cmd {
17 | 18 => DataDirection::Read,
24 | 25 => DataDirection::Write,
_ => DataDirection::None,
}
}
pub const fn data_block_size(&self) -> Option<u32> {
match self.cmd {
17 | 18 | 24 | 25 => Some(512),
_ => None,
}
}
pub fn crc7(&self) -> u8 {
let mut crc: u8 = 0;
let token: u8 = 0x40 | (self.cmd & 0x3F);
crc = crc7_update(crc, token);
for byte in self.arg.to_be_bytes() {
crc = crc7_update(crc, byte);
}
(crc << 1) | 1 }
pub fn to_spi_bytes(&self) -> [u8; 6] {
let crc = self.crc7();
let token = 0x40 | (self.cmd & 0x3F);
let arg = self.arg.to_be_bytes();
[token, arg[0], arg[1], arg[2], arg[3], crc]
}
}
fn crc7_update(crc: u8, byte: u8) -> u8 {
let mut crc = crc;
let mut data = byte;
for _ in 0..8 {
crc <<= 1;
if (crc ^ data) & 0x80 != 0 {
crc ^= 0x89;
}
data <<= 1;
}
crc
}
pub const CMD0: Command = Command::new(0, 0, ResponseType::None);
pub const CMD2: Command = Command::new(2, 0, ResponseType::R2);
pub const CMD3_SD: Command = Command::new(3, 0, ResponseType::R6);
pub fn cmd3_mmc(rca: u16) -> Command {
Command::new(3, (rca as u32) << 16, ResponseType::R1)
}
pub fn cmd4(dsr: u16) -> Command {
Command::new(4, (dsr as u32) << 16, ResponseType::None)
}
pub fn cmd6(arg: u32) -> Command {
Command::new(6, arg, ResponseType::R1)
}
pub fn cmd6_high_speed(switch: bool) -> Command {
cmd6_sd_access_mode(switch, 1)
}
pub fn cmd6_sd_access_mode(switch: bool, function: u8) -> Command {
let mode = if switch { 1u32 << 31 } else { 0 };
let groups = 0x00FF_FFF0u32 | u32::from(function & 0xF);
Command::new(6, mode | groups, ResponseType::R1)
}
pub fn cmd7(rca: u16) -> Command {
Command::new(7, (rca as u32) << 16, ResponseType::R1b)
}
pub fn cmd8(voltage: u8, check_pattern: u8) -> Command {
let arg = ((voltage as u32) << 8) | check_pattern as u32;
Command::new(8, arg, ResponseType::R7)
}
pub fn cmd9(rca: u16) -> Command {
Command::new(9, (rca as u32) << 16, ResponseType::R2)
}
pub fn cmd10(rca: u16) -> Command {
Command::new(10, (rca as u32) << 16, ResponseType::R2)
}
pub const CMD11: Command = Command::new(11, 0, ResponseType::R1);
pub const CMD12: Command = Command::new(12, 0, ResponseType::R1b);
pub fn cmd13(rca: u16) -> Command {
Command::new(13, (rca as u32) << 16, ResponseType::R1)
}
pub fn cmd16(block_len: u32) -> Command {
Command::new(16, block_len, ResponseType::R1)
}
pub fn cmd17(addr: u32) -> Command {
Command::new(17, addr, ResponseType::R1)
}
pub fn cmd18(addr: u32) -> Command {
Command::new(18, addr, ResponseType::R1)
}
pub fn cmd24(addr: u32) -> Command {
Command::new(24, addr, ResponseType::R1)
}
pub fn cmd25(addr: u32) -> Command {
Command::new(25, addr, ResponseType::R1)
}
pub const CMD19: Command = Command::new(19, 0, ResponseType::R1);
pub const CMD21: Command = Command::new(21, 0, ResponseType::R1);
pub const SD_TUNING_BLOCK_SIZE: u32 = 64;
pub const MMC_TUNING_BLOCK_SIZE_4BIT: u32 = 64;
pub const MMC_TUNING_BLOCK_SIZE_8BIT: u32 = 128;
pub fn cmd32(addr: u32) -> Command {
Command::new(32, addr, ResponseType::R1)
}
pub fn cmd33(addr: u32) -> Command {
Command::new(33, addr, ResponseType::R1)
}
pub const CMD38: Command = Command::new(38, 0, ResponseType::R1b);
pub fn cmd41(hcs: bool, voltage_window: u32) -> Command {
cmd41_with_s18r(hcs, voltage_window, false)
}
pub fn cmd41_with_s18r(hcs: bool, voltage_window: u32, s18r: bool) -> Command {
let arg = if hcs { 0x4000_0000 } else { 0 }
| if s18r { 1 << 24 } else { 0 }
| (voltage_window & 0x00FF_F800);
Command::new(41, arg, ResponseType::R3)
}
pub fn cmd55(rca: u16) -> Command {
Command::new(55, (rca as u32) << 16, ResponseType::R1)
}
pub const CMD58: Command = Command::new(58, 0, ResponseType::R3);
pub fn cmd1(voltage_window: u32) -> Command {
Command::new(1, voltage_window, ResponseType::R3)
}
pub fn cmd6_mmc_switch(access: u8, index: u8, value: u8) -> Command {
let arg = ((access as u32) << 24) | ((index as u32) << 16) | ((value as u32) << 8);
Command::new(6, arg, ResponseType::R1b)
}
pub const CMD8_MMC: Command = Command::new(8, 0, ResponseType::R1);
pub mod ext_csd {
pub const DEVICE_TYPE: usize = 196;
pub const HS_TIMING: usize = 185;
pub const BUS_WIDTH: usize = 183;
pub const SEC_COUNT: usize = 212;
pub mod device_type {
pub const HS_26: u8 = 1 << 0;
pub const HS_52: u8 = 1 << 1;
pub const HS200_18V: u8 = 1 << 4;
pub const HS200_12V: u8 = 1 << 5;
}
}
pub const CMD5: Command = Command::new(5, 0, ResponseType::R4);
pub fn cmd52(write: bool, function: u8, raw: bool, addr: u32, data: u8) -> Command {
let arg = (write as u32) << 31
| ((function as u32) & 0x7) << 28
| (raw as u32) << 27
| (addr & 0x1_FFFF) << 9
| (data as u32);
Command::new(52, arg, ResponseType::R5)
}
pub fn cmd53(
write: bool,
function: u8,
block_mode: bool,
addr: u32,
op_code: bool,
count: u16,
) -> Command {
let arg = (write as u32) << 31
| ((function as u32) & 0x7) << 28
| (block_mode as u32) << 27
| (op_code as u32) << 26
| (addr & 0x1_FFFF) << 9
| (count as u32 & 0x1FF);
Command::new(53, arg, ResponseType::R5)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cmd0_crc() {
let bytes = CMD0.to_spi_bytes();
assert_eq!(bytes[0], 0x40);
assert_eq!(bytes[5], 0x95);
}
#[test]
fn test_cmd8_spi_bytes() {
let cmd = cmd8(0x01, 0xAA);
let bytes = cmd.to_spi_bytes();
assert_eq!(bytes[0], 0x48); assert_eq!(bytes[1], 0x00);
assert_eq!(bytes[2], 0x00);
assert_eq!(bytes[3], 0x01);
assert_eq!(bytes[4], 0xAA);
}
#[test]
fn cmd52_encodes_full_17_bit_address() {
let cmd = cmd52(true, 1, false, 0x1_ABCD, 0x55);
let expected = (1u32 << 31) | (1u32 << 28) | (0x1_ABCDu32 << 9) | 0x55;
assert_eq!(cmd.arg, expected);
assert_eq!(cmd.cmd, 52);
}
#[test]
fn cmd53_encodes_full_17_bit_address_and_count() {
let cmd = cmd53(false, 2, true, 0x1_FFFF, true, 0x1FF);
let expected = (2u32 << 28) | (1u32 << 27) | (1u32 << 26) | (0x1_FFFFu32 << 9) | 0x1FF;
assert_eq!(cmd.arg, expected);
assert_eq!(cmd.cmd, 53);
}
#[test]
fn data_direction_classifies_block_commands() {
assert_eq!(cmd17(0).data_direction(), DataDirection::Read);
assert_eq!(cmd18(0).data_direction(), DataDirection::Read);
assert_eq!(cmd24(0).data_direction(), DataDirection::Write);
assert_eq!(cmd25(0).data_direction(), DataDirection::Write);
assert_eq!(cmd6(0).data_direction(), DataDirection::None);
assert_eq!(CMD0.data_direction(), DataDirection::None);
assert_eq!(CMD12.data_direction(), DataDirection::None);
assert!(CMD0.data_direction().is_none());
}
#[test]
fn data_block_size_reports_known_lengths() {
assert_eq!(cmd17(0).data_block_size(), Some(512));
assert_eq!(cmd18(0).data_block_size(), Some(512));
assert_eq!(cmd24(0).data_block_size(), Some(512));
assert_eq!(cmd25(0).data_block_size(), Some(512));
assert_eq!(cmd6(0).data_block_size(), None);
assert_eq!(CMD0.data_block_size(), None);
assert_eq!(CMD12.data_block_size(), None);
}
#[test]
fn cmd6_high_speed_arg_matches_spec() {
let switch = cmd6_high_speed(true);
assert_eq!(switch.cmd, 6);
assert_eq!(switch.arg, 0x80FF_FFF1);
let check = cmd6_high_speed(false);
assert_eq!(check.arg, 0x00FF_FFF1);
}
#[test]
fn cmd6_sd_access_mode_arg_selects_group1_function() {
let sdr104 = cmd6_sd_access_mode(true, 3);
assert_eq!(sdr104.cmd, 6);
assert_eq!(sdr104.arg, 0x80FF_FFF3);
let ddr50 = cmd6_sd_access_mode(false, 4);
assert_eq!(ddr50.arg, 0x00FF_FFF4);
}
#[test]
fn cmd41_with_s18r_sets_1v8_request_bit() {
let cmd = cmd41_with_s18r(true, 0xFF8000, true);
assert_eq!(cmd.arg, 0x4100_0000 | 0x00FF_8000);
}
#[test]
fn with_resp_type_overrides_only_resp_type() {
let original = cmd41(true, 0xFF8000);
let overridden = original.with_resp_type(ResponseType::R1);
assert_eq!(overridden.cmd, original.cmd);
assert_eq!(overridden.arg, original.arg);
assert_eq!(overridden.resp_type, ResponseType::R1);
assert_eq!(original.resp_type, ResponseType::R3);
}
}