CM4_SDMMC使用参考
REVISION HISTORY¶
Revision No. | Description |
Date |
---|---|---|
1.0 | Initial release | 05/21/2024 |
1. 概述¶
1.1 SD¶
SD卡(Secure Digital Card)使用的是sd卡协议,该协议规定了sd卡的物理接口、通信协议和命令结构,以实现数据的读取和写入。cm4 sd支持sd2.0通信协议.
1.2 SDIO¶
SDIO(Secure Digital Input/Output)通信协议是在sd卡协议基础上扩展而来的,它允许SD卡与外部设备进行输入和输出的通信。sdio通信协议定义了sdio卡的物理接口和通信协议,使得sd卡能够扩展为具备I/O功能的设备。cm4 sdio支持sdio2.0通信协议。
2. 关键字¶
sysdesc:
rtos用于描述外设硬件属性的文件,外设节点中包含的属性值可用于外设的配置,类似Linux的设备树文件
padmux:
引脚复用,用于将模块功能引脚连接到具体的外部引脚,打通信号连接。
sdmmc ip:
连接sd/sdio卡的host engine,cm4设计使用sdio ip。ip可以驱动不同pad mode,根据实际需要使用。
3. 功能描述¶
封装 | SD卡 | 总线带宽 | 时钟范围 | SD3.0支持的速率模式 | SD2.0支持的速率模式 | IP bank | 产品 |
---|---|---|---|---|---|---|---|
BGA/QFN | SD0 Card | 1,4 | 300k-48M | - | Default Speed,High Speed | 0x1413(non-pm) | iford |
BGA/QFN | WIFI Card | 1,4 | 300k,12M,32M,48M | - | Default Speed,High Speed | 0x42(pm) | iford |
注:pm sdio/sd clk仅有4个档位,需要准确设置。
总线带宽设置:
sd/sdio支持配置1 -1bit mode/4 – 4bit mode 两种总线带宽,默认使用4bit mode。sdio卡需要设置带宽为1 bit mode可以设置sysdesc外设硬件属性文件的slot_Sdio_Use_1bit_u8参数。
时钟设置:
iford cm4: sd/sdio card支持配置(300KHz,12MHz,32MHz,48MHz)范围内的时钟频率,可以通过修改外设硬件属性文件中的slot_max_clks_u32参数来设置最大时钟大小,最终设置的clock频率是当前bus speed支持的最大频率。

4. RTOS用法介绍¶
sdio通讯的时候,需要保证基本的如下步骤:
- 硬件设备连接正常,供电稳定;
- CONFIG支持sdio驱动;
- SYSDESC文件配置需求属性;
- PADMUX设定;
- API调用,执行通讯。
4.1 DRIVER PATH¶
sc/driver/sysdriver/sdmmc/drv/inc sc/driver/sysdriver/sdmmc/drv/pub sc/driver/sysdriver/sdmmc/drv/src sc/driver/sysdriver/sdmmc/drv/sdmmc_irq.h sc/driver/sysdriver/sdmmc/drv/sdmmc_irq.c sc/driver/sysdriver/sdmmc/drv/rtk_cfg.h sc/driver/sysdriver/sdmmc/drv/sd.c sc/driver/sysdriver/sdmmc/drv/sdio_cfg.h sc/driver/sysdriver/sdmmc/drv/sdio.c sc/driver/sysdriver/sdmmc/drv/sdio_test.c sc/driver/sysdriver/sdmmc/drv/ms_sdmmc_lnx.c
4.2 CONFIG配置¶
#make menuconfig BSP Driver Options ---> <*> Support SDIO Card
4.3 SYSDESC配置¶
chipname.sys文件位于sc/driver/sysdriver/sysdesc/hal/chipname/pub
<sdmmc> [camclk_u8] CAMCLK_sd, CAMCLK_VOID; [slotnum_u8] 1; [revcdz_u8] 0; [slot_ip_orders_u8] 0; [slot_max_clks_u32] 48000000; [slot_intcdzs_u8] 0; [slot_pwr_gpios_u16] PAD_PM_GPIO12; [slot_pwr_off_delay_u8] 130; [slot_pwr_on_delay_u8] 130; [slot_fakecdzs_u8] 1; [slot_pad_orders_u8] 0; [slot_cdzs_gpios_u16] PAD_PM_SDIO_INT; [slot_sdio_use_u8] 1; [slot_removable_u8] 0; [slot_mie_intr_u16] INT_IRQ_SDIO; [slot_cdz_intr_u16] INT_FIQ_PAD2SDIO_SD_CDZ; [slot_Sdio_Use_1bit_u8] 0;
sdio驱动中支持配置的属性如下表:
属性 | 描述 | 设定值 | 备注 |
---|---|---|---|
camclk_u8 | sdio使用的clock源 | 不需要修改 | |
slotnum_u8 | sdio槽位数 | 不需要更改,当前cm4 sdio只有一个槽位 | |
revcdz_u8 | 配置是否颠倒Card detect电平条件 | 1-开启;0-关闭 | 可根据需要修改 |
slot_ip_orders_u8 | 配置对应卡槽的sdio engine编号 | 不需要修改,当前cm4 sdio只有一个槽位 | |
slot_max_clks_u32 | 配置对应卡槽支持的最大时钟频率 | 支持300KHz, 12MHz, 32MHz, 48MHz | 可根据需要修改 |
slot_intcdzs_u8 | 配置是否使用cdz中断 | 1-开启;0-关闭 | 可根据需要修改 |
slot_pwr_gpios_u16 | 配置sdio电源pad | 不需要修改 | |
slot_pwr_off_delay_u8 | 配置sdio的下电延迟时间 | 初始化过程下电动作保持时间 | |
slot_pwr_on_delay_u8 | 配置sdio的上电延迟时间 | 初始化过程上电动作保持时间 | SDIO设备一般需要配置delay时间以便SDIO设备加载固件及状态ready,具体时间以SDIO设备厂家的建议为准 |
slot_fakecdzs_u8 | 配置是否忽视Card Detect | 开启后默认non-removable | 可根据需要修改,与slot_removable搭配使用 |
slot_pad_orders_u8 | 配置对应卡槽的padmux mode | 不需要更改,当前cm4 sdio只有一个槽位 | |
slot_cdzs_gpios_u16 | 配置对应卡槽的CDZ pin脚 | 不需要修改 | |
slot_sdio_use_u8 | 配置是否使用sdio device | 1-enable;0-disable | 可根据需要修改 |
slot_removable_u8 | 配置是否removable设备 | 1-enable;0-disable | 可根据需要修改,与slot_fakecdz搭配使用 |
slot_mie_intr_u16 | 配置sdio中断信息 | 不需要修改,此为sdio传输过程使用中断来获取事件 | |
slot_cdz_intr_u16 | 配置sdio热插拔中断信息 | 不需要修改 | |
slot_Sdio_Use_1bit_u8 | 配置sdio总线位宽 | 1-1bit 0-4bit, default 4bit | 可根据需要修改 |
详细说明:
根据cm4时钟源定制,当前sdio可选时钟范围:
slot_max_clks_u32 == 48000000, 时钟源为50MHz;
slot_max_clks_u32 == 32000000, 时钟源为33.3MHz;
slot_max_clks_u32 == 12000000, 时钟源为12MHz;
slot_max_clks_u32 == 300000, 时钟源为300KHz;
4.4 SD card API说明¶
头文件位于sc/driver/sysdriver/sdmmc/drv/pub/sd.h
4.4.0 sd 结构体定义¶
struct msSt_SDIO_Data { U16_T u16BlkCnt; U16_T u16BlkSize; volatile char *pu8Buf; U16_T u16StorType; }; typedef enum { EV_IMI = 0x0, EV_PSRAM = 0x1, EV_MIU = 0x2, } IPStorageEmType;
4.4.1. sd_card_init¶
-
描述
sd host识别device初始化
-
语法
int sd_card_init(u8 u8_slot);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.2. sd_card_reset¶
-
描述
sd host重新初始化识别device
-
语法
int sd_card_reset(u8 u8_slot);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.3. sd_card_remove¶
-
描述
sd host移除device
-
语法
void sd_card_remove(u8 u8_slot);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.4. sd_read_block¶
-
描述
sd host读取单块block
-
语法
int sd_read_block(u8 slot, u32 addr, volatile u8* r_buf, u16 u16StorType);
-
参数
参数名称 描述 slot sdio使用槽位,默认为0 addr 读取device的blk地址 r_buf 存放data的buf u16StorType 指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.5. sd_write_block¶
-
描述
sd host写入单块block
-
语法
int sd_write_block(u8 slot, u32 addr, volatile u8* r_buf, u16 u16StorType);
-
参数
参数名称 描述 slot sdio使用槽位,默认为0 addr 写入device的blk地址 r_buf 存放data的buf u16StorType 指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.6. sd_read_block_multi¶
-
描述
sd host读取多块block
-
语法
int sd_read_block_multi(u8 slot, u32 addr, u16 u61BlkCnt, volatile u8* r_buf, u16 u16StorType);
-
参数
参数名称 描述 slot sdio使用槽位,默认为0 addr 读取device的blk地址 u61BlkCnt 读取device的blk数 r_buf 存放data的buf u16StorType 指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.7. sd_write_block_multi¶
-
描述
sd host写入多块block
-
语法
int sd_write_block_multi(u8 slot, u32 addr, u16 u16BlkCnt, volatile u8* r_buf, u16 u16StorType);
-
参数
参数名称 描述 slot sdio使用槽位,默认为0 addr 写入device的blk地址 u61BlkCnt 写入device的blk数 r_buf 存放data的buf u16StorType 指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.4.8. sd_get_capacity¶
-
描述
sd host获取device容量
-
语法
u64 sd_get_capacity(u8 slot);
-
参数
参数名称 描述 slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回容量值 失败 返回-1
4.5 SDIO card API说明¶
头文件位于sc/driver/sysdriver/sdmmc/drv/pub/sdio.h
4.5.0 sdio 数据结构¶
struct msSt_SDIO_Data { U16_T u16BlkCnt; U16_T u16BlkSize; volatile char *pu8Buf; U16_T u16StorType; }; typedef int sdio_irq_callback(u8 u8_slot);
4.5.1. sdio_card_init¶
-
描述
sdio host识别device初始化
-
语法
int sdio_card_init(u8 u8_slot);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.2. sdio_card_reset¶
-
描述
sdio host重新初始化识别device
-
语法
int sdio_card_reset(u8 u8_slot);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.3. sdio_read_byte¶
-
描述
sdio host读取寄存器信息,非data传输
-
语法
int sdio_read_byte(u8 u8_slot, u8 func, u32 addr, u8* r_buf);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 func 选择sdio func addr 读取device的寄存器地址 r_buf 存放结果的buf -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.4. sdio_read_byte¶
-
描述
sdio host设置寄存器信息,非data传输
-
语法
int sdio_write_byte(u8 u8_slot, u8 func, u32 addr, u8 w_value);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 func 选择sdio func addr 写入device的寄存器地址 w_value 待设值 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.5. sdio_read_byte_multi¶
-
描述
sdio host按字节读取数据
-
语法
int sdio_read_byte_multi(u8 u8_slot, u8 func, u8 op_code, u32 addr, struct msSt_SDIO_Data *pstData);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 func 选择sdio func op_code 选择地址模式:0-固定地址 1-递增地址 addr 读取device i/o的起始地址 pstData 传输参数: u16BlkCnt:[0,0x1ff]; u16BlkSize:512; pu8Buf:存放结果的buf; u16StorType:指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.6. sdio_write_byte_multi¶
-
描述
sdio host按字节写入数据
-
语法
int sdio_write_byte_multi(u8 u8_slot, u8 func, u8 op_code, u32 addr, struct msSt_SDIO_Data *pstData);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 func 选择sdio func op_code 选择地址模式:0-固定地址 1-递增地址 addr 写入device i/o的起始地址 pstData 传输参数: u16BlkCnt:[0,0x1ff]; u16BlkSize:512; pu8Buf:存放结果的buf; u16StorType:指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.7. sdio_read_block_multi¶
-
描述
sdio host按block读取数据
-
语法
int sdio_read_block_multi(u8 u8_slot, u8 func, u8 op_code, u32 addr, struct msSt_SDIO_Data *pstData);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 func 选择sdio func op_code 选择地址模式:0-固定地址 1-递增地址 addr 读取device i/o的起始地址 pstData 传输参数: u16BlkCnt:[0,0x1ff]; u16BlkSize:512; pu8Buf:存放结果的buf; u16StorType:指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.8. sdio_write_block_multi¶
-
描述
sdio host按block写入数据
-
语法
int sdio_write_block_multi(u8 u8_slot, u8 func, u8 op_code, u32 addr, struct msSt_SDIO_Data *pstData);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 func 选择sdio func op_code 选择地址模式:0-固定地址 1-递增地址 addr 读取device i/o的起始地址 pstData 传输参数: u16BlkCnt:[0,0x1ff]; u16BlkSize:512; pu8Buf:存放结果的buf; u16StorType:指定当前cm4使用的storage类型,参见IPStorageEmType -
返回值
结果 描述 成功 返回容量值 失败 返回-1
4.5.9. sdio_irq_enable¶
-
描述
sdio host使能sdio中断
-
语法
int sdio_irq_enable(u8 u8_slot, int enable);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.5.10. sdio_set_irq_callback¶
-
描述
sdio host注册sdio中断回调
-
语法
int sdio_set_irq_callback(u8 u8_slot, sdio_irq_callback* irq_cb);
-
参数
参数名称 描述 u8_slot sdio使用槽位,默认为0 irq_cb sdio中断回调函数 -
返回值
结果 描述 成功 返回0 失败 返回非0
4.6 DEMO¶
demo源码位于sc/driver/sysdriver/sdmmc/drv/sdio_test.c
void sdtest(U8_T u8_slot) { U64_T sd_capt; U32_T sd_addr, err, blkcnt, loop = 0, i; U8_T *r_buf, *w_buf, flag, compare = 0; IPStorageEmType eStor[] = { EV_IMI, EV_IMI, EV_PSRAM }; // card detect if(MS_SD_Card_Detect(u8_slot)) { CamOsPrintf("card alive !\n"); sd_capt = sd_get_capacity(u8_slot); CamOsPrintf(" !\n"); if (sd_capt == -1) { CamOsPrintf("card init fail !\n"); return; } CamOsPrintf("SD card capacity: %lld %s -- %d %s \n", (unsigned long long)sd_capt, "B", (int)(sd_capt/1024/1024/1024) , "GB"); } // alloc buf sd_addr = 0x233; r_buf = CamOsMemAlloc(512); w_buf = CamOsMemAlloc(512); // set buf pattern for(i = 0; i < 512; i++) { r_buf[i] = i; w_buf[i] = ~i; } // write single block err = sd_write_block(u8_slot, sd_addr, w_buf, eStor[loop]); if(err) CamOsPrintf("write block error\n"); // read single block err = sd_read_block(u8_slot, sd_addr, r_buf, eStor[loop]); if(err) CamOsPrintf("read block error\n"); // check result for(i = 0; i < 512; i++) { if(r_buf[i] != w_buf[i]) { compare = 1; break; } } if(compare) { compare = 0; _DUMP_MEM_ARR(w_buf, 512); _DUMP_MEM_ARR(r_buf, 512); CamOsPrintf("signal block r/w test fail \n"); } else CamOsPrintf("signal block r/w test pass \n"); // release buf CamOsMemRelease(r_buf); CamOsMemRelease(w_buf); // test multi block sd_addr = 0x122; blkcnt = 2; r_buf = CamOsMemAlloc(512*blkcnt); w_buf = CamOsMemAlloc(512*blkcnt); for(i = 0; i < 512*blkcnt; i++) { r_buf[i] = i; w_buf[i] = ~i; } err = sd_write_block_multi(u8_slot, sd_addr, blkcnt, w_buf, eStor[loop]); if(err) CamOsPrintf("write block error\n"); err = sd_read_block_multi(u8_slot, sd_addr, blkcnt, r_buf, eStor[loop]); if(err) CamOsPrintf("read block error\n"); for(i = 0; i < 512*blkcnt; i++) { if(r_buf[i] != w_buf[i]) { compare = 1; break; } } if(compare) { compare = 0; CamOsPrintf("multi block r/w test fail \n"); } else CamOsPrintf("multi block r/w test pass \n"); CamOsMemRelease(r_buf); CamOsMemRelease(w_buf); } void SdioTest(U8_T u8_slot) { unsigned int test_loop = 0, test_temp = 0, i = 0; unsigned int test_loop = 0; unsigned short ret; u8 *r_buf, *w_buf; u8 func; u32 addr; struct msSt_SDIO_Data stData; u32 count; u32 i; u8 op_code; u32 blk_size; CamOsPrintf("****** SDIO Test ******\r\n"); // register sdio intr callback and enable sdio_set_irq_callback(u8_slot, Sdio_test_irq_callback); sdio_irq_enable(u8_slot, TRUE); // sdio init if (sdio_card_init(u8_slot) != 0) { CamOsPrintf("--- SDIO Init fail ---\r\n"); return; } CamOsPrintf("--- SDIO Init OK ---\r\n"); // alloc buf r_buf = CamOsMemAlloc(512); w_buf = CamOsMemAlloc(512); // read register info func = 0; addr = 0x13; ret = sdio_read_byte(u8_slot, func, addr, r_buf); if (ret) { CamOsPrintf("- sdio_read_byte ERROR\r\n"); } // write register info w_buf[0] = r_buf[0]; func = 0; addr = 0x13; ret = sdio_write_byte(u8_slot, func, addr, w_buf[0]); if (ret) { CamOsPrintf("- sdio_write_byte ERROR\r\n"); } // write by byte and addr fixed memset(w_buf, 0, 512); memset(&stData, 0, sizeof(struct msSt_SDIO_Data)); func = 0; addr = 0xf0; //16 op_code = 0; count = 16; stData.u16BlkCnt = count; stData.pu8Buf = (volatile char *)w_buf; stData.u16StorType = EV_IMI; // storage type for (i = 0; i < count; i++) { w_buf[i] = test_loop + i; } ret = sdio_write_byte_multi(u8_slot, func, op_code, addr, &stData); if (ret) { CamOsPrintf("- sdio_write_byte_multi ERROR\r\n"); } // read by byte and addr increment memset(r_buf, 0, 512); memset(&stData, 0, sizeof(struct msSt_SDIO_Data)); func = 1; addr = (0x09); op_code = 1; count = 3; stData.u16BlkCnt = count; stData.pu8Buf = (volatile char *)r_buf; stData.u16StorType = EV_IMI; ret = sdio_read_byte_multi(u8_slot, func, op_code, addr, &stData); if (ret) { CamOsPrintf("- sdio_read_byte_multi ERROR\r\n"); } // write by block memset(w_buf, 0, 512); memset(&stData, 0, sizeof(struct msSt_SDIO_Data)); func = 1; addr = (0x1c); //1; op_code = 1; count = 1;//12; // 12 2; blk_size = 512; stData.u16BlkCnt = count; stData.u16BlkSize = blk_size; stData.pu8Buf = (volatile char *)w_buf; stData.u16StorType = EV_IMI; for (i = 0; i < (count * blk_size); i++) { w_buf[i] = test_loop + i; } ret = sdio_write_block_multi(u8_slot, func, op_code, addr, &stData); if (ret) { CamOsPrintf("- sdio_write_block_multi ERROR\r\n"); } // read by block memset(w_buf, 0, 512); memset(&stData, 0, sizeof(struct msSt_SDIO_Data)); func = 1; addr = 0x1c; //1; op_code = 1; count = 1; blk_size = 512; stData.u16BlkCnt = count; stData.u16BlkSize = blk_size; stData.pu8Buf = (volatile char *)r_buf; stData.u16StorType = EV_IMI; ret = sdio_read_block_multi(u8_slot, func, op_code, addr, &stData); if (ret) { CamOsPrintf("- sdio_read_block_multi ERROR\r\n"); } release buf CamOsMemRelease(r_buf); CamOsMemRelease(w_buf); return; }
5. DEBUG方法¶
可以根据驱动报错log中的(E: 0xXXXX)信息查找对应的SD/SDIO卡状态来判断目前卡出现了什么问题。
根据SD/SDIO卡实际可能遇到的问题,分为以下几个类型:
识别卡失败
若卡识别失败,需要确定是响应获取失败还是传输信号不好有CRC问题,区分问题可以通过驱动打印的日志确定,具体区别以及debug方法如下:
-
SD/SDIO卡不回复命令响应
现象:驱动log报(E: 0x0008)信息
debug方法:首先确定电压和clock是否正常,其次可以抓波形看host是否有command发出去,若前面两个都没有问题,再确定卡是否有回复响应,若没有响应检查device状态。
相关日志:
-
命令响应存在CRC问题
现象:驱动log报(E: 0x0010)信息
debug方法:若有CRC问题,先需要排除硬件问题,比如:device接触是否良好,是否有外接干扰等。然后再尝试更改pad driving,若还有问题则考虑调整clock phase等。
相关日志:
读写失败
若是在正常读写过程中遇到问题,需要确定是读写超时问题,还是信号不好有CRC问题,区分问题可以通过log确定,超时问题有timeout的字眼,debug方法如下:
-
读写超时
现象:驱动报错log里有timeout的字眼
debug方法:首先需要确定当前clock频率以及bus width是否是期望配置的值,其次可以将驱动里的超时时间再加大试试,若还有超时问题,则需要抓波形详细分析。
读写超时时间在驱动对应芯片目录的hal_card_platform_config.h文件中设定:
-
读写CRC问题
现象:驱动log报(E: 0x0001)或(E: 0x0002)信息
debug方法:debug方法见前文,另外读写过程中的CRC问题,若对速率要求不是很高,可以考虑降频或者降bus width。
相关日志:
卡读写速度慢
首先检查卡时钟频率和bus width是否正常,另外可以测试对比下不同型号卡的读写速度。
注:SD卡读写受命令及其响应的传输时间,卡busy时间,停止命令时间等多重因素影响,实际读写速率是低于其理论速率的; 读写速度测试应该测试应用层的读写带宽,即传输一个大文件进行测试。