[sd card] sd card初始化流程

以SD 3.0为例。
建议先参考《[sd card] SD card初始化时的总线设置》。

一、sd card初始化流程思路说明

通过《SD_Ver3.00_Final_090416》协议中“4.2 Card Identification Mode ”和“4.3 Data Transfer Mode ”来进行说明。

1、sd card操作模式和卡状态

通过

avatar

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线来传输数据

2、card identification mode流程说明

整个card identification mode都是外部对于sd card的初始化过程。
如下图所示:

avatar

  • 操作条件
    卡识别模式。参考《SD_Ver3.00_Final_090416》“4.2 Card Identification Mode ”
    顾名思义,就是识别card的一个模式。一个mmc总线上可以接多张card,因此需要在这个模式下识别和激活对应的card并使之进入对应的模式。
    在卡识别模式下,host会先复位所有在Card Identification Mode下的card,验证操作电压的范围,辨认card并且分配给其对应的RCA地址。
    这些操作都是在card对应的CMD线上完成,并且在card identification mode模式下的所有通讯都只能在CMD线上完成。
    在卡识别的过程中,card应该工作在f-OD的工作频率下。

avatar
通过上述表格可以看出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域:

avatar

__注意,重要,虽然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进行供电。__
  • card initialization
    card的内部初始化是在card处理非0参数ACMD41命令的过程中完成的。
    • 首先看一下ACMD41的命令格式

avatar

参数位的意义如下: 
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会直接忽略掉。__
  • 在看一下ACMD41的response的格式

avatar

 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地址直到自己满意为止。

3、data transfer mode中和初始化相关的流程

通过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
      状态图如下:

avatar

  • card的group和function
    sd card支持6个function group,每个group最多支持16个function。如下图所示,

avatar

每一个group同时只能有一个function被选中。并且function0是默认的function。
__在设置总线速度的过程中,我们需要关注的就是group1,也就是access mode。__
  • mode 1 operation——set function
    CMD6 mode 1用来切换card的functionality。
    CMD6切换总线模式为HS模式的示例如下

avatar

 参数:bit31——》mode,这里应该设置为1
 对于不需要切换function的group直接设置为0xf,对于要修改function的group、设置其function值即可。

综上,可以在card处于transfer state的情况下,host向card发送CMD6命令,参数设置为“1<<31 | 0xffff00 | 总线速度模式的function”,来实现card的总线速度模式的切换。

  • 总线宽度的设置
    参考“ 4.3.1 Wide Bus Selection/Deselection ”
    上电之后或者执行CMD0命令之后,card的总线宽度模式总是默认设置为1bit模式。
    可以通过ACMD6来设置card的总线宽度模式。ACMD6要求card处于transfer state,并且处于unlock状态。

avatar

二、在sd card初始化过程中,host要做的事情

1、命令流程

参考《SD_Ver3.00_Final_090416》协议中“3.9.4 Bus Speed Modes Selection Sequence ”
从“一、sd card初始化流程思路说明”中,可以得到host在初始化card的过程中,需要向其发送如下命令序列。

avatar

2、host流程说明

根据card的外部初始化流程,可以简单整理出host在sd各个状态下需要做的操作流程如下(黑体部分是我们这里重点关心的部分):

  • 未上电状态

    • (1)准备好工作电压、信号电压,上电
    • (2)准备好时钟(400kHz)
  • idle state

    • (1)尝试获取一个合适的工作电压
      host发送CMD0命令进行复位
      host发送CMD8命令,告诉card,host可以支持SD2.0。card收到CMD8命令之后会使能自己符合SD2.0的一些新功能
      host发送参数为0的ACMD41命令,提取response中的VHS,得到card支持的工作电压范围
      host选择一个card和host都支持的最低的工作电压,并将host提供给card的工作电压设置为这个值。
      这个值就是合法的,后续就以这个ocr作为工作电压重新复位开始对sd card真正的初始化过程.
    • (2)重新复位,完成card的内部初始化
      host发送CMD0命令进行复位
      host发送CMD8命令,告诉card,host可以支持SD2.0。card收到CMD8命令之后会使能自己符合SD2.0的一些新功能。同时,获取到ocr寄存器的值。
      host根据host是否支持SDHC来设置ocr的HCS、是否支持1.8V来设置ocr的S18R,将设置好的ocr作为ACMD41的参数,发送给card。
      host读取ACMD41的busy位来判断card的内部初始化是否完成,如果没有完成继续发送ACMD41
      一旦card的内部初始化完成,则card进入ready state。
  • ready state

    • (1)设置信号电压
      host根据ACMD41的response提取对应的S18A,如果为1,说明card支持切换到信号电压为1.8V的模式。
      host发送CMD11命令,要求card切换到1.8V的信号电压模式。
      host切换提供给card的信号电压为1.8V。
    • (2)获取card的CID值
      host发送CMD2命令,要求card回复其CID寄存器的值。
      一旦card返回response之后,进入identification state。
  • identification state

    • (1)获取card的RCA值
      host发送CMD3命令,要求card回复其RCA值。
      一旦card通过response返回这个RCA之后,进入stand-by state。
      identification mode也就完成了。
  • stand-by state——》transfer state

    • (1)获取sd card的特殊数据寄存器
      csd寄存器中存储了sd card的一些信息。
      host发送CMD9命令,要求card回去其CSD寄存器(card specific data)的值
    • (2)切换到transfer state模式
      后续的初始化操作需要在transfer state下进行,所以需要发送CMD7命令选中对应的card,将card切换到transfer state
    • (3)获取sd card的配置寄存器和状态寄存器
      host发送ACMD51命令,要求card回复其SCR寄存器(SD configuration register)的值
      host发送ACMD13命令,要求card回复其SSR寄存器(SD status regiter)的值
    • (4)读取card 的switch状态,也就是其支持的function
      host发送CMD6命令来读取card switch status。
      通过card switch status可以得到card支持的总线速度模式以及驱动强度类型。
    • (5)切换总线宽度
      host发送ACMD11命令,要求card将总线宽度切换到4bit模式
      设置host自身的总线宽度为4bit模式
    • (6)选择合适的总线速度模式、驱动强度、以及限流并进行设置
      host从host和card都支持的总线速度模式中选择一个最优的模式。
      host根据选择的总线速度模式,来选择对应的驱动类型以及限流值,通过CMD6命令让card进行相应值的切换。
      host发送CMD6命令、并且mode=1、group=0、function=总线速度模式码,card收到命令之后会切换到相应的总线速度模式上。
      设置host自身的总线速度模式(时序,timing)。
    • (7)执行tuning操作
      对于UHS-I的card来说,如果处于uhs的速度模式,host需要发送CMD19执行tuning操作以获取一个最佳的采样点。

到此,host对于sd card的初始化就完成了。

三、host初始化sd card代码分析

整个代码设计是围绕着“在sd card初始化过程中,host要做的事情”的思想来设计的。
因此,可以看代码的过程中,回头看看前面的设计思想。了解了上述的初始化流程之后再来看代码会感觉比较容易理解。
对应代码drivers/mmc/core/sd.c、drivers/mmc/driver/core/sd-ops.c

1、mmc_rescan_try_freq

在《[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;

2、mmc_attach_sd

去掉无关的代码部分如下:

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;
}

3、mmc_sd_init_card

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;
}

4、mmc_sd_get_cid

在上面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;
}

5、mmc_sd_init_uhs_card

在上面mmc_sd_init_card中被调用。用来初始化uhs card的总线工作模式。

  • 主要工作如下:

    • 切换总线宽度,包括card和host的设置
    • 选择合适的总线速度模式、驱动强度、以及限流并进行设置,包括card和host的设置
    • 执行tuning操作
  • 代码如下:

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初始化时的总线设置

猜你喜欢

转载自blog.csdn.net/ooonebook/article/details/60613452
sd