我们首先来看下 mmcsd_core.c 这个文件:
rt_mmcsd_core_init() 初始化函数通过 INIT_PREV_EXPORT(rt_mmcsd_core_init); 被初始化调用,同时初始化用于 mmc、sd、sdio检测的邮箱mmcsd_detect_mb,用于热插拔处理的 mmcsd_hotpluge_mb 以及 mmc、sd、sdio检测线程 mmcsd_detect_thread;
在线程mmcsd_detect_thread 中,等待mmcsd_detect_mb邮箱唤醒;
当SDIO驱动层完成初始化话之后,通过调用 mmcsd_change(host) 函数,将mmcsd_detect_thread线程唤醒,开始进行mmc、sd卡、sdio卡的识别过程
mmcsd_core_init() 函数内容如下:
int rt_mmcsd_core_init(void)
{
rt_err_t ret;
/* initialize detect SD cart thread */
/* initialize mailbox and create detect SD card thread */
ret = rt_mb_init(&mmcsd_detect_mb, “mmcsdmb”,
&mmcsd_detect_mb_pool[0], sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]),
RT_IPC_FLAG_FIFO);
RT_ASSERT(ret == RT_EOK);
ret = rt_mb_init(&mmcsd_hotpluge_mb, “mmcsdhotplugmb”,
&mmcsd_hotpluge_mb_pool[0], sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]),
RT_IPC_FLAG_FIFO);
RT_ASSERT(ret == RT_EOK);
ret = rt_thread_init(&mmcsd_detect_thread, “mmcsd_detect”, mmcsd_detect, RT_NULL,
&mmcsd_stack[0], RT_MMCSD_STACK_SIZE, RT_MMCSD_THREAD_PREORITY, 20);
if (ret == RT_EOK)
{
rt_thread_startup(&mmcsd_detect_thread);
}
rt_sdio_init();
return 0;
}
INIT_PREV_EXPORT(rt_mmcsd_core_init);
mmcsd_detect()线程以及 mmcsd_change() 函数如下:
mmcsd_detect() 函数主要负责完成 SDIO卡、SD卡、MMC卡的初步识别,初步识别确认是哪种类型的卡接入之后,将会调用对应卡驱动文件(SD卡对应sd.c,SDIO卡对应sdio.c,MMC卡对应mmc.c)内的初始化函数,重新完成卡的完整识别流程
如果对于SD卡识别流程不了解,建议先熟悉SD卡识别流程,参考 SD Nand 与 SD卡 SDIO模式应用流程(点击跳转)
具体流程见下述函数描述,对应步骤已补充注释描述
void mmcsd_change(struct rt_mmcsd_host *host)
{
rt_mb_send(&mmcsd_detect_mb, (rt_uint32_t)host);
}
void mmcsd_detect(void *param)
{
struct rt_mmcsd_host *host;
rt_uint32_t ocr;
rt_int32_t err;
while (1)
{
/* 首先等待 mmcsd_detect_mb 信号量,此信号量由 mmcsd_change() 函数发送过来 */
if (rt_mb_recv(&mmcsd_detect_mb, (rt_ubase_t *)&host, RT_WAITING_FOREVER) == RT_EOK)
{
/* 通过判断 host-》card 确认此次操作是识别卡还是移除卡 */
if (host-》card == RT_NULL) /* 识别卡 */
{
mmcsd_host_lock(host); /* 获取锁 */
mmcsd_power_up(host); /* 配置SDIO外设电源控制器,power up, 即卡的时钟开启,同时配置SDIO外设时钟为低速模式 */ mmcsd_go_idle(host); /* 发送CMD0指令,使卡进入空闲状态 */
mmcsd_send_if_cond(host, host-》valid_ocr); /* 发送CMD8命令,查询SD卡接口条件 (获取OCR寄存器) */
/*
* 检测SDIO卡使用,SD卡不用管
*/
err = sdio_io_send_op_cond(host, 0, &ocr); /* 发送CMD5命令,此处是针对SDIO卡使用,SD卡不会响应 */
if (!err) /* SD卡不会响应此指令,因此此条件不会成立 */
{
if (init_sdio(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
continue;
}
/*
* 检测SD卡使用,使用SD卡重点关注此项!!!
*/
err = mmcsd_send_app_op_cond(host, 0, &ocr); /* 发送ACMD41指令(ACMD41:CMD55+CMD41) SD卡将应答此指令 */
if (!err)
{
if (init_sd(host, ocr)) /* 此函数内完成SD卡完整的识别流程 */
mmcsd_power_off(host); /* 设置SDIO外设,电源关闭,卡的时钟停止 */
mmcsd_host_unlock(host); /* 释放锁 */
rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host); /* 发送邮箱,通知热插拔事件 */
continue;
}
/*
* 检测MMC卡检测使用,SD卡不用管
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (!err)
{
if (init_mmc(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);
continue;
}
mmcsd_host_unlock(host); /* 识别失败,释放锁 */
}
else /* 移除卡 */
{
/* card removed */
mmcsd_host_lock(host); /* 获取锁 */
if (host-》card-》sdio_function_num != 0)
{
LOG_W(“unsupport sdio card plug out!”);
}
else
{
rt_mmcsd_blk_remove(host-》card);
rt_free(host-》card);
host-》card = RT_NULL;
}
mmcsd_host_unlock(host); /* 释放锁 */
rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);
}
}
}
}
在 mmcsd_detect() 函数内完成SD卡的初步识别之后,之后将调用sd.c文件内的init_sd() 函数完成 sd 卡的完整识别过程
/*
* Starting point for SD card init.
*/
rt_int32_t init_sd(struct rt_mmcsd_host *host, rt_uint32_t ocr)
{
rt_int32_t err;
rt_uint32_t current_ocr;
/*
* We need to get OCR a different way for SPI.
*/
if (controller_is_spi(host)) /* 判断是否采用SPI模式访问SD卡 */
{
mmcsd_go_idle(host);
err = mmcsd_spi_read_ocr(host, 0, &ocr);
if (err)
goto err;
}
if (ocr & VDD_165_195)
{
LOG_I(“ SD card claims to support the ”
“incompletely defined ‘low voltage range’。 This ”
“will be ignored.”);
ocr &= ~VDD_165_195;
}
current_ocr = mmcsd_select_voltage(host, ocr); /* 配置SDIO外设设置为合适的电压,对于stm32、gd32等相关控制器,实际是不支持不同等级电压配置的,所以这里可以忽略,不过你需要注意你所使用的sd卡的电源在硬件上是匹配的 */
/*
* Can we support the voltage(s) of the card(s)?
*/
if (!current_ocr)
{
err = -RT_ERROR;
goto err;
}
/*
* Detect and init the card.
*/
err = mmcsd_sd_init_card(host, current_ocr); /* 完整的SD卡初始化流程在此函数内实现 */
if (err)
goto err;
mmcsd_host_unlock(host); /* 释放锁 */
err = rt_mmcsd_blk_probe(host-》card); /* 注册块设备 */
if (err) /* 如果注册块设备失败,将移除卡 */
goto remove_card;
mmcsd_host_lock(host); /* 获取锁 */
return 0;
remove_card:
mmcsd_host_lock(host); /* 获取锁 */
rt_mmcsd_blk_remove(host-》card); /* 移除块设备 */
rt_free(host-》card); /* 释放对应的内存 */
host-》card = RT_NULL;
err:
LOG_D(“init SD card failed!”);
return err;
}
调用 mmcsd_sd_init_card() 函数完成SD卡检测以及初始化配置
static rt_int32_t mmcsd_sd_init_card(struct rt_mmcsd_host *host,
rt_uint32_t ocr)
{
struct rt_mmcsd_card *card;
rt_int32_t err;
rt_uint32_t resp[4];
rt_uint32_t max_data_rate;
mmcsd_go_idle(host); /* 发送CMD0,复位SD卡,使卡进入空闲模式 */
/*
* If SD_SEND_IF_COND indicates an SD 2.0
* compliant card and we should set bit 30
* of the ocr to indicate that we can handle
* block-addressed SDHC cards.
*/
err = mmcsd_send_if_cond(host, ocr); /* 发送CMD8指令,判断是否为V2.0或V2.0以上的卡,并获取OCR寄存器值 */
if (!err) /* 如果是V2.0及以上版本的卡,将置为OCR的bit30位,表明主机支持高容量SDHC卡(OCR将在ACMD41指令时作为参数发送给卡) */
ocr |= 1 《《 30;
err = mmcsd_send_app_op_cond(host, ocr, RT_NULL); /* 发送ACMD41(ACMD41 = CMD55+CMD41)指令,发送主机容量支持信息,并询问卡的操作条件 */
if (err)
goto err;
if (controller_is_spi(host)) /* 判断是否使用SPI方式访问SD卡 */
err = mmcsd_get_cid(host, resp); /* 采用SPI方式获取CID寄存器值 */
else
err = mmcsd_all_get_cid(host, resp);/* 发送CMD2命令,获取CID寄存器值 */
if (err)
goto err;
card = rt_malloc(sizeof(struct rt_mmcsd_card)); /* 创建rt_mmcsd_card结构体,用于存储对应SD卡的CID寄存器内容 */
if (!card)
{
LOG_E(“malloc card failed!”);
err = -RT_ENOMEM;
goto err;
}
rt_memset(card, 0, sizeof(struct rt_mmcsd_card));
card-》card_type = CARD_TYPE_SD;
card-》host = host;
rt_memcpy(card-》resp_cid, resp, sizeof(card-》resp_cid));
/*
* For native busses: get card RCA and quit open drain mode.
*/
if (!controller_is_spi(host)) /* 如果不是采用SPI方式访问SD卡 */
{
err = mmcsd_get_card_addr(host, &card-》rca); /* 发送CMD3命令,获取RCA地址 */
if (err)
goto err1;
mmcsd_set_bus_mode(host, MMCSD_BUSMODE_PUSHPULL);/* 设置CMD总线为推挽输出模式,需要注意的是,MMC卡V3.31版本以前的卡,初始化阶段,CMD总线需要为开路模式,对于SD/SD I/O卡和MMC V4.2在初始化时也使用推挽驱动 */
}
err = mmcsd_get_csd(card, card-》resp_csd); /* 发送CMD9命令,获取CSD寄存器值 */
if (err)
goto err1;
err = mmcsd_parse_csd(card); /* 解析CSD寄存器值,将解析完成的数据存放在刚刚申请的card结构体内 */
if (err)
goto err1;
if (!controller_is_spi(host)) /* 如果不是采用SPI方式访问SD卡 */
{
err = mmcsd_select_card(card); /* 发送CMD7命令,选择卡 */
if (err)
goto err1;
}
err = mmcsd_get_scr(card, card-》resp_scr); /* 发送CMD9命令,获取SCR寄存器值,并保存在刚刚申请的card结构体内 */
if (err)
goto err1;
mmcsd_parse_scr(card); /* 解析SCR寄存器的值,并将解析结果存放在在card结构体内 */
if (controller_is_spi(host))
{
err = mmcsd_spi_use_crc(host, 1);
if (err)
goto err1;
}
/*
* change SD card to high-speed, only SD2.0 spec
*/
err = mmcsd_switch(card); /* 发送CMD6指令,切换卡访问速率由默认的12.5MB/Sec为25MB/Sec高速接口 */
if (err)
goto err1;
/* set bus speed */
max_data_rate = (unsigned int)-1;
if (card-》flags & CARD_FLAG_HIGHSPEED)
{
if (max_data_rate 》 card-》hs_max_data_rate)
max_data_rate = card-》hs_max_data_rate;
}
else if (max_data_rate 》 card-》max_data_rate)
{
max_data_rate = card-》max_data_rate;
}
mmcsd_set_clock(host, max_data_rate); /* 修改SDIO外设时钟速度 */
/*switch bus width*/
if ((host-》flags & MMCSD_BUSWIDTH_4) &&
(card-》scr.sd_bus_widths & SD_SCR_BUS_WIDTH_4)) /* 根据SD卡的SCR寄存器反馈的值,判断SD卡是否支持4线宽度访问模式,如果支持则切换为4线宽度访问模式 */
{
err = mmcsd_app_set_bus_width(card, MMCSD_BUS_WIDTH_4); /* 发送ACMD6(ACMD6=CMD55+CMD6)指令,通知SD卡切换为4线访问模式 */
if (err)
goto err1;
mmcsd_set_bus_width(host, MMCSD_BUS_WIDTH_4); /* 修改SDIO外设配置为4线访问模式 */
}
host-》card = card; /* 将card结构体数据与host结构体建立绑定关系 */
return 0;
err1:
rt_free(card);
err:
return err;
}
6. 总结
以上便是SD卡的识别与初始化流程,整体流程简单的梳理一下,大致如下:
由 drv_sdio.c 外设驱动或其他调用 mmcsd_change() 触发 mmcsd_detect() 检测
在 mmcsd_detect () 任务中,实现对SD卡、SD I/O卡、MMC卡的初步识别(发送对应卡特有命令,并判断是否正确响应),之后根据卡片类型调用不同类型卡片驱动文件内的初始化程序
如针对SD卡,则调用sd.c文件内的 init_sd() 函数完成
在init_sd()函数内调用 mmcsd_sd_init_card() 完成SD卡的完整识别流程以及初始化流程,同时同步修改SDIO外设配置
SD卡初始化完成之后,调用 rt_mmcsd_blk_probe() 将sd卡注册为块设备
至此SD的识别与初始化流程顺利完成。
原作者:爱出名的狗腿子