以SD 3.0为例。
建议先参考《[sd card] SD card初始化时的总线设置》。
通过《SD_Ver3.00_Final_090416》协议中“4.2 Card Identification Mode ”和“4.3 Data Transfer Mode ”来进行说明。
通过
sd card有如上card状态以及对应的操作模式。
inactive mode
非激活模式。当host提供的电压不在card的电压的可用范围之内时,会进入这种状态。
这种状态下,card不会响应任何命令。
card identification mode
card识别模式,host激活card和识别card的模式。
这些操作都是在card对应的CMD线上完成,并且在card identification mode模式下的所有通讯都只能在CMD线上完成。
在卡识别的过程中,card应该工作在f-OD的工作频率下。
data transefer mode
数据传输模式,在这种模式下,host和card可以根据data线来传输数据
整个card identification mode都是外部对于sd card的初始化过程。
如下图所示:
通过上述表格可以看出Card Identification Mode下,时钟频率不能超过400kHz。
card identification mode下有如下几种操作
card reset
GO_IDLE_STATE(CMD0)命令是软件复位命令,并且是一个广播指令,可以使所有的card进入idle状态(除了处于inactive状态的card)。
如果是通过上电操作进行的复位,那么所有的card都会直接进入idle状态,也包括之前处于inactive状态的card。
不论是CMD0或这是power-on进行的复位,所有被复位的card的CMD线都会处于input状态,等待下一次命令的起始位。同时,所有card会简单的初始化,将其RCA地址设置为0x0000,并且设置一个默认的驱动强度模式。
operation condition validation
这个操作是用于验证card的操作条件。
在开始host和card的通讯之前,host并不知道card支持的工作电压,并且card也不知道当前host提供的电压是多少。在这种情况下,host会先假设card支持某个电压并且将card的工作电压设置成该电压,然后发送给card复位命令CMD0。
为了验证host假定的电压是否被card支持,SD 2.0协议中定义了一个新的命令CMD8。SEND_IF_COND(CMD8)命令是用来验证sd card接口的操作条件。card会通过分析SEND_IF_COND(CMD8)命令的参数来判断host的操作条件的正确性(也就是工作电压是否在card支持的范围之内),card把SEND_IF_COND(CMD8)命令的参数中的VHS域(注意,同时只能有1个bit被设置成1)当作当前host提供的工作电压以此来判断是否当前的供电电压是否符合。如果card检测到供电电压符合操作条件的话,会在response中返回自己支持的工作电压。如果不符合的话,不会返回response并且仍处于idle状态。host也会根据SEND_IF_COND(CMD8)命令的response中的VHS域来获取card支持的工作电压。注意,CMD8并不会因此card状态发生变化,这种功能也可以通过ACMD41来实现!!!
ocr寄存器如下,主要关注VHS域:
__注意,重要,虽然ACMD41也可以用来获取card支持的工作电压,但是必须在发送ACMD41之前先发送CMD8。因为当card收到CMD8之后,就会知道host是支持SD 2.0协议的,便会使能自己的一些符合SD 2.0的新功能。同时,对于低电压host来说,先发送CMD8也是必须的。因为如果一个双电压card(支持高电压和低电压)没有接收到CMD8的话,只会工作在高电压模式,此时如果该card收到了ACMD41会误以为工作电压不符合操作条件,就会进入inactive模式。__
SD_SEND_OP_CON(ACMD41)是让card用来验证工作电压、并且拒绝不符合操作条件的host的命令。host会将自己的提供的供电范围作为ACMD41的参数,card收到ACMD41命令之后如果检测到供电范围不符合自己的标准,就会进入到inactive状态。后面会继续说明的。
__当ACMD41的参数为0时,所有的card都会返回自己支持的工作电压(ocr)并且不会进到inactive模式。所以,host可以先设定一个比较通用的工作点发送一个参数为0的ACMD41、从得到的response中获取card支持的工作电压,此时card并不会进行其他操作。然后host根据card支持的工作电压、选择一个合适的电压为card进行供电。__
参数位的意义如下:
bit23-8:ocr,当前使用的工作电压
bit24:S18R,当host支持1.8V的信号电压时,设置这个bit。card如果自己也支持1.8V的信号电压的模式的话,会在response中设置同样位置的位。
bit28:SDXC的电源控制功能。并没有详细说明。
bit30:HCS,host capacity support,当设置为0时,表示host只支持SDSC类型的card,当设置为1时,说明host支持处理SDHC和SDXC类型的card。
__host向card发送SD_SEND_OP_CON(ACMD41)命令可以触发card的内部初始化流程。而card会根据上述的位设置进行响应的处理。注意,是在card收到CMD8命令后的前提下,否则,上述有些位card会直接忽略掉。__
bit24:S18A,当card收到的ACMD41的S18R为1时,会在这个位设置自己是否支持切换到1.8V的信号电压的模式下,如果支持,那么设置S18A为1.
bit30:CCS,card capacity status,card的容量状态。当card收到的ACMD41的HCS为1时(也就是host支持处理SDHC或者SDXC card),如果该card是SDHC或者SDXC,那么就将这个位设置为1,否则,设置为0.
bit31:busy,当card收到参数不为0的ACMD41命令时,就开始进行内部初始化,如果返回response时初始化还没有完成,那么这个位设置为0。否则,设置为1。
注意,host必须根据自己的状态来设置ACMD41的参数。并且不断发送ACMD41给card直到检测到busy为1,此时的CCS和S18A才是可靠的。随后,card进入了ready state。
card bus signal voltage switch
通常card刚上电的情况下,其信号电压一般都是处于3.3V的模式。当card进入ready状态后,为了节省功耗,首先需要考虑是是否需要切换信号电压到1.8V。
前面说过,如果host支持输出1.8V的信号电压的话,会将ACMD41的参数的S18R(bit24)设置为1来告诉card。当card收到这个ACMD41时,如果自己允许切换到1.8V的信号电压模式,那么就设置response的S18A(bit24)设置为1,否则设置为0。
当host从response的S18A(bit24)解析出1的时候,可以向card发送CMD11命令,来通知card准备切换到1.8V的信号电压模式了。随后,host就可以将自己的输出的信号电压切换到1.8V了。
此时,card还是处于ready state。
card identification process
card识别过程。
host会向card发送ALL_SEND_CID(CMD2)命令来要求card发送它们各自独一无二的cid寄存器的值。一旦card收到这条命令并且向host发送了自己的CID值,就会直接进入到identification state。
随后,host会通过SEND_RELATIVE_ADDR(CMD3)命令来要求card自己编一个RCA地址并通过response返回给host,随后card就会进入stand-by模式。而RCA地址则会作为在transfer mode中,该card的通讯地址。注意,如果host对于card自己发布的这个地址不满意,可以重复发送CMD2要求card修改RCA地址直到自己满意为止。
通过identification mode之后,工作电压和信号电压都已经设置完成。但是总线宽度和总线速度模式并没有设置。刚power-on或者是刚执行CMD0命令的card的总线宽度为1,总线速度模式为DS模式。而这部分内容的设置则是在data transfer mode中实现的!!!
总线速度模式的设置
参考《SD_Ver3.00_Final_090416》协议中“4.2 Card Identification Mode ”和“4.3 Data Transfer Mode ”来进行说明。
总线速度模式的设置主要以依赖于CMD6。
说明
switch function command(CMD6)是用来切换或者扩展card的function。当前有四个function组定义如下:
(1)Access mode:访问模式,用于选择SD总线接口的速度模式(也就是我们这里的目标)
(2)Command system:命令系统,可以通过共享命令集来扩展和控制一个特殊的功能
(3)Driver strength:驱动强度,在UHS-I模式下用于选择一个合适的驱动信号强度,取决于host的环境
(4)Current limit:电流限制,在UHS-I模式下设置card的最大电流,由host的供电属性决定
CMD6只有在transfer state下才是可用的。一旦card复位之后,所有group默认都选中function0.
card会返回R1 response(CMD线)以及512bit的状态数据(DAT线)作为对host的CMD6的响应。从sd传输标准上看,CMD6相当于一个单块读命令、超时时间是100ms。
card对于对于CMD6的切换动作会在状态数据传输完之后的8个时钟之内完成。当CMD6导致总线行为(例如总线速度模式)发生变化后,host要求至少要等CMD6传输完成之后的8个时钟之后才允许使用新的总线行为进行通讯。
CMD6的模式
CMD6有两种模式,分别是check function模式和set function模式。
(1)check function模式用来查询card所支持的function
(2)set function模式用来切换card的functionality
状态图如下:
每一个group同时只能有一个function被选中。并且function0是默认的function。
__在设置总线速度的过程中,我们需要关注的就是group1,也就是access mode。__
参数:bit31——》mode,这里应该设置为1
对于不需要切换function的group直接设置为0xf,对于要修改function的group、设置其function值即可。
综上,可以在card处于transfer state的情况下,host向card发送CMD6命令,参数设置为“1<<31 | 0xffff00 | 总线速度模式的function”,来实现card的总线速度模式的切换。
参考《SD_Ver3.00_Final_090416》协议中“3.9.4 Bus Speed Modes Selection Sequence ”
从“一、sd card初始化流程思路说明”中,可以得到host在初始化card的过程中,需要向其发送如下命令序列。
根据card的外部初始化流程,可以简单整理出host在sd各个状态下需要做的操作流程如下(黑体部分是我们这里重点关心的部分):
未上电状态
idle state
ready state
identification state
stand-by state——》transfer state
到此,host对于sd card的初始化就完成了。
整个代码设计是围绕着“在sd card初始化过程中,host要做的事情”的思想来设计的。
因此,可以看代码的过程中,回头看看前面的设计思想。了解了上述的初始化流程之后再来看代码会感觉比较容易理解。
对应代码drivers/mmc/core/sd.c、drivers/mmc/driver/core/sd-ops.c
在《[mmc subsystem] mmc core(第六章)——mmc core主模块》中已经说明过了当host检测到card插入的情况下,最终会调用mmc_rescan_try_freq来识别和初始化card。
和sd相关的部分如下:
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq; // 设置初始化频率,取决于host的频率表中的最低频率,一般是400KHz
/** 给card上电的准备动作 **/
mmc_power_up(host);
// 对应上述“未上电状态”,主要的设置有(都是和协议中默认card上电之后的状态是一致的,这样才能发起通讯):
// 1、host->ios.vdd,选择host的可以提供的最低的输出电压,作为card的工作电压
// 2、host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN,设置总线模式为开漏模式
// 3、host->ios.power_mode = MMC_POWER_UP,设置电源状态为上电模式
// 4、host->ios.bus_width = MMC_BUS_WIDTH_1,设置总线宽度为1bit模式
// 5、host->ios.timing = MMC_TIMING_LEGACY,设置总线速度模式为传统模式,对于SD来说,就是DS模式
// 6、host->ios.clock = host->f_init,设置时钟频率,取决于host的频率表中的最低频率,一般是400KHz。由SD2.0协议可以知道不能超过400KHz
// 7、host->ios.signal_voltage = MMC_SIGNAL_VOLTAGE_330,设置信号电压为3.3V模式
// 8、以上设置完成后,card的上电工作已经完成,设置电源状态为MMC_POWER_ON模式
/** 以下开始做card的初始化操作 **/
mmc_go_idle(host);
// host发送CMD0命令进行复位
mmc_send_if_cond(host, host->ocr_avail);
// host发送CMD8命令,告诉card,host可以支持SD2.0。card收到CMD8命令之后会使能自己符合SD2.0的一些新功能
/* Order's important: probe SDIO, then SD, then MMC */
if (!mmc_attach_sdio(host)) // 先尝试将card当作sdio设备进行识别和初始化,通过CMD5命令进行区分
return 0;
/** mmc_attach_sd就是对sd card进行识别和初始化的入口动作 **/
if (!mmc_attach_sd(host)) // 再尝试将card当作sd设备进行识别和初始化,通过ACMD41命令进行区分
return 0;
if (!mmc_attach_mmc(host)) // 最后尝试将card当作sd设备进行识别和初始化
return 0;
mmc_power_off(host);
return -EIO;
去掉无关的代码部分如下:
int mmc_attach_sd(struct mmc_host *host)
{
int err;
u32 ocr;
int retries;
/** 以下部分,连同mmc_rescan_try_freq中的mmc_go_idle和mmc_send_if_cond一起构成了
“尝试获取一个合适的工作电压” 的任务 **/
err = mmc_send_app_op_cond(host, 0, &ocr);
// host发送参数为0的ACMD41命令,提取response中的VHS,得到card支持的工作电压范围
mmc_sd_attach_bus_ops(host); // 设置bus操作集为mmc_sd_ops_unsafe或者mmc_sd_ops,mmc subsystem的内容,这里我们不关心
if (host->ocr_avail_sd)
host->ocr_avail = host->ocr_avail_sd;
host->ocr = mmc_select_voltage(host, ocr);
// host选择一个card和host都支持的最低的工作电压,并将host提供给card的工作电压设置为这个值。
// 后续就以host->ocr作为工作电压对sd card进行初始化
retries = 5;
while (retries && !host->rescan_disable) {
/** 上述已经完成了card的识别操作,并且为card选择了一个合适的工作电压 **/
/** 后续调用mmc_sd_init_card对sd card进行初始化,也就是代码核心 **/
err = mmc_sd_init_card(host, host->ocr, NULL);
if (err) {
retries--;
mmc_power_off(host); // 如果初始化失败的情况下,需要重新掉电并上电,再尝试进行初始化
usleep_range(5000, 5500);
mmc_power_up(host);
mmc_select_voltage(host, host->ocr);
continue;
}
break;
}
mmc_release_host(host);
err = mmc_add_card(host->card);
mmc_claim_host(host);
mmc_init_clk_scaling(host);
return 0;
}
mmc_sd_init_card是在已经确定了host提供给card的工作电压值的情况下,用来对sd card进行初始化的操作。
* 结合第二节,可以知道mmc_sd_init_card主要有如下工作(黑体的部分是我们重点关心的部分):
- 重新复位,完成card的内部初始化
- 设置信号电压,包括card和host的设置
- 获取card的CID值
- 获取card的RCA值
- 获取sd card的特殊数据寄存器
- 切换到transfer state模式
- 获取sd card的配置寄存器和状态寄存器
- 读取card 的switch状态,也就是其支持的function
- 切换总线宽度,包括card和host的设置
- 选择合适的总线速度模式、驱动强度、以及限流并进行设置,包括card和host的设置
- 执行tuning操作
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
struct mmc_card *card;
int err;
u32 cid[4];
u32 rocr = 0;
BUG_ON(!host);
WARN_ON(!host->claimed);
/** 在mmc_sd_get_cid中完成如下工作::: **/
/** 重新复位,完成card的内部初始化 **/
/** 设置信号电压,包括card和host的设置 **/
/** 获取card的CID值 **/
err = mmc_sd_get_cid(host, ocr, cid, &rocr);
// 调用mmc_sd_get_cid进行复位、内部初始化,设置信号电压,然后获取CID值,最终card进入了identification state。
// mmc_sd_get_cid看后续说明
if (oldcard) {
if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0)
return -ENOENT;
card = oldcard;
} else {
card = mmc_alloc_card(host, &sd_type);
card->type = MMC_TYPE_SD;
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
// 和mmc subsystem相关的内容,为sd card分配一个mmc_card结构体,这里我们不关心
}
/** 获取card的RCA值 **/
if (!mmc_host_is_spi(host)) {
err = mmc_send_relative_addr(host, &card->rca);
// 调用mmc_send_relative_addr发送CMD3命令,要求card回复其RCA值。
// 一旦card通过response返回这个RCA之后,进入stand-by state
host->card = card;
}
/** 获取sd card的特殊数据寄存器 **/
if (!oldcard) {
err = mmc_sd_get_csd(host, card);
// csd寄存器中存储了sd card的一些信息。
// host发送CMD9命令,要求card回去其CSD寄存器(card specific data)的值
// 此时card扔处于stand-by state
mmc_decode_cid(card);
}
/** 选中sdcard,切换到transfer state模式 **/
if (!mmc_host_is_spi(host)) {
err = mmc_select_card(card);
// 后续的初始化操作需要在transfer state下进行,所以需要发送CMD7命令选中对应的card,将card切换到transfer state
}
/** 获取sd card的配置寄存器和状态寄存器 **/
/** 读取card 的switch状态,也就是其支持的function **/
err = mmc_sd_setup_card(host, card, oldcard != NULL);
// host发送ACMD51命令,要求card回复其SCR寄存器(SD configuration register)的值
// host发送ACMD13命令,要求card回复其SSR寄存器(SD status regiter)的值
// host发送CMD6命令来读取card switch status。
// 通过card switch status可以得到card支持的总线速度模式以及驱动强度类型。
/** 切换总线宽度,包括card和host的设置 **/
/** 选择合适的总线速度模式、驱动强度、以及限流并进行设置,包括card和host的设置 **/
/** 执行tuning操作 **/
/* Initialization sequence for UHS-I cards */
if (rocr & SD_ROCR_S18A) {
err = mmc_sd_init_uhs_card(card); // 后续说明
/* Card is an ultra-high-speed card */
mmc_card_set_uhs(card);
} else { // 对于非uhs card来说,直接尝试切换到HS模式
// 对于非uhs card,不需要切换其信号电压,因为其一直工作在3.3V
// 也不需要切换其信号驱动类型、执行tuning操作等等
err = mmc_sd_switch_hs(card); // 发送CMD6命令尝试将card的总线速度模式切换到HS模式
if (err > 0)
mmc_sd_go_highspeed(card); // 如果切换成功,将host的总线速度模式也切换到HS模式
else if (err)
goto free_card;
mmc_set_clock(host, mmc_sd_get_max_clock(card)); // 设置时钟为相应总线速度模式下支持的最大频率
if ((host->caps & MMC_CAP_4_BIT_DATA) &&
(card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
// 如果需要切换到4bit总线宽度模式,发送ACMD11通知card准备切换到4bit模式
mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
// 设置host自身的总线宽度模式
}
}
return 0;
}
在上面mmc_sd_init_card中被调用。从idle state到identification state的一个过程。
* 在mmc_sd_get_cid中的重要工作如下
- 重新复位,完成card的内部初始化
- 设置信号电压,包括card和host的设置
- 获取card的CID值
int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr)
{
int err;
u32 max_current;
int retries = 10;
try_again:
/** 重新复位,完成card的内部初始化 **/
mmc_go_idle(host); // host发送CMD0命令对card进行复位
err = mmc_send_if_cond(host, ocr);
// host发送CMD8命令,告诉card,host可以支持SD2.0。card收到CMD8命令之后会使能自己符合SD2.0的一些新功能
// 同时获取card的ocr值
if (!err)
ocr |= SD_OCR_CCS; // 当前代码是支持SDHC和SDXC的处理的,所以这里设置ocr中的HCS位
if (retries && mmc_host_uhs(host))
ocr |= SD_OCR_S18R; // 如果host支持UHS模式,那么自然就支持1.8的信号电压的输出了
err = mmc_send_app_op_cond(host, ocr, rocr);
// host根据host是否支持SDHC来设置ocr的HCS、是否支持1.8V来设置ocr的S18R,将设置好的ocr作为ACMD41的参数,发送给card。
// 当ACMD41处理完成值,card就进入到了ready state了。
/** 设置信号电压,包括card和host的设置 **/
if (!mmc_host_is_spi(host) && rocr &&
((*rocr & 0x41000000) == 0x41000000)) {
err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180);
// 调用mmc_set_signal_voltage切换信号电压到1.8V
// 会先发送CMD11来通知card准备切换到信号电压为1.8V的模式下
// 然后调用host->ops->start_signal_voltage_switch来切换host输出的信号电压
if (err == -EAGAIN) {
// host读取ACMD41的busy位来判断card的内部初始化是否完成,如果没有完成继续发送ACMD41
retries--;
goto try_again;
} else if (err) {
retries = 0;
goto try_again;
}
}
/** 获取card的CID值 **/
if (mmc_host_is_spi(host))
err = mmc_send_cid(host, cid);
else
err = mmc_all_send_cid(host, cid);
// host发送CMD2命令,要求card回复其CID寄存器的值。
// 一旦card返回response之后,进入identification state。
return err;
}
在上面mmc_sd_init_card中被调用。用来初始化uhs card的总线工作模式。
主要工作如下:
代码如下:
static int mmc_sd_init_uhs_card(struct mmc_card *card)
{
int err;
u8 *status;
status = kmalloc(64, GFP_KERNEL);
/** 切换总线宽度,包括card和host的设置 **/
// uhs都是工作在4bit的总线位宽的模式下,因此,在设置uhs的总线速度模式之前,必须先切换到4bit总线宽度模式
if ((card->host->caps & MMC_CAP_4_BIT_DATA) &&
(card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
// 发送ACMD11,告诉card准备切换的4bit总线宽度模式
if (err)
goto out;
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
// 切换host的总线宽度为4bit模式
}
/** 选择合适的总线速度模式、驱动强度、以及限流并进行设置,包括card和host的设置 **/
sd_update_bus_speed_mode(card);
// 根据card和host都支持的总线速度模式,选择一个最佳的总线速度模式
/* Set the driver strength for the card */
err = sd_select_driver_type(card, status);
// 根据要选择的总线速度模式,切换驱动信号强度
// 发送CMD6,告诉card准备切换驱动信号强度,命令格式如下:
// mmc_sd_switch(card, 1, 2, drive_strength, status),属于group2
// 然后就是设置host的驱动信号强度
// mmc_set_driver_type(card->host, drive_strength);
/* Set current limit for the card */
err = sd_set_current_limit(card, status);
// 根据要选择的总线速度模式,切换限流值
// 发送CMD6,告诉card准备切换限流值,命令格式如下:
// mmc_sd_switch(card, 1, 3, current_limit, status),属于group3
/* Set bus speed mode of the card */
err = sd_set_bus_speed_mode(card, status);
// 这里进行总线速度模式的切换,先查询host关于该总线速度模式对应的时序模式
// card——》总线速度模式,card->sd_bus_speed = host——》时序,host->ios.timing,二者是对应的
// 发送CMD6,告诉card准备切换总线速度模式,命令格式如下:
// mmc_sd_switch(card, 1, 0, card->sd_bus_speed, status),属于group0
// 然后就是设置host的时序模式以及时钟
// mmc_set_timing(card->host, timing);
// mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr);
/** 执行tuning操作 **/
/* SPI mode doesn't define CMD19 */
if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning) {
mmc_host_clk_hold(card->host);
err = card->host->ops->execute_tuning(card->host, MMC_SEND_TUNING_BLOCK);
// 对于UHS-I的card来说,如果处于uhs的速度模式,host需要发送CMD19执行tuning操作以获取一个最佳的采样点。
mmc_host_clk_release(card->host);
}
out:
kfree(status);
return err;
}
以上总线设置部分的详细内容建议参考《[sd card] SD card初始化时的总线设置》