zoukankan      html  css  js  c++  java
  • linux SD卡初始化---mmc_sd_init_card函数

    为了学习SD/SDIO协议,看了一下linux中初始化SD卡的流程,结合代码更容易SD初始化是怎么做的。

    下面图截自:"SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10"

    SD卡在sd模式下的初始化流程图,sd协议还有spi模式暂不研究。

    这个流程图对应于linux 代码就在

    /driver/mmc/core/sd.c

    static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard)

    传入参数

    truct mmc_host *host mmc/sd/sdio主机控器的结构,成员用到再说明

    u32 ocr 这个比较重要,与ACMD41和sd卡中ocr寄存器相关。调用mmc_sd_init_card之前

    linux已经做过一些工作,就是发送ACMD41获取SD卡工作电压,与Host支持电压匹配并设置host的电压,然后调用mmc_sd_init_card,并把电压信息通过ocr传递下来。流程图开始时这些工作已经做好。

    struct mmc_card *oldcard  新插入的卡初始化时该值为NULL。

    linux版本3.7 mmc_sd_init_card函数:

      1 /*
      2  * Handle the detection and initialisation of a card.
      3  *
      4  * In the case of a resume, "oldcard" will contain the card
      5  * we're trying to reinitialise.
      6  */
      7 static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
      8     struct mmc_card *oldcard)
      9 {
     10     struct mmc_card *card;
     11     int err;
     12     u32 cid[4];
     13     u32 rocr = 0;
     14 
     15     BUG_ON(!host);
     16     WARN_ON(!host->claimed);
     17 
     18     err = mmc_sd_get_cid(host, ocr, cid, &rocr);
     19     if (err)
     20         return err;
     21 
     22     if (oldcard) {
     23         if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0)
     24             return -ENOENT;
     25 
     26         card = oldcard;
     27     } else {
     28         /*
     29          * Allocate card structure.
     30          */
     31         card = mmc_alloc_card(host, &sd_type);
     32         if (IS_ERR(card))
     33             return PTR_ERR(card);
     34 
     35         card->type = MMC_TYPE_SD;
     36         memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
     37     }
     38 
     39     /*
     40      * For native busses:  get card RCA and quit open drain mode.
     41      */
     42     if (!mmc_host_is_spi(host)) {
     43         err = mmc_send_relative_addr(host, &card->rca);
     44         if (err)
     45             return err;
     46     }
     47 
     48     if (!oldcard) {
     49         err = mmc_sd_get_csd(host, card);
     50         if (err)
     51             return err;
     52 
     53         mmc_decode_cid(card);
     54     }
     55 
     56     /*
     57      * Select card, as all following commands rely on that.
     58      */
     59     if (!mmc_host_is_spi(host)) {
     60         err = mmc_select_card(card);
     61         if (err)
     62             return err;
     63     }
     64 
     65     err = mmc_sd_setup_card(host, card, oldcard != NULL);
     66     if (err)
     67         goto free_card;
     68 
     69     /* Initialization sequence for UHS-I cards */
     70     if (rocr & SD_ROCR_S18A) {
     71         err = mmc_sd_init_uhs_card(card);
     72         if (err)
     73             goto free_card;
     74 
     75         /* Card is an ultra-high-speed card */
     76         mmc_card_set_uhs(card);
     77 
     78         /*
     79          * Since initialization is now complete, enable preset
     80          * value registers for UHS-I cards.
     81          */
     82         if (host->ops->enable_preset_value) {
     83             mmc_host_clk_hold(card->host);
     84             host->ops->enable_preset_value(host, true);
     85             mmc_host_clk_release(card->host);
     86         }
     87     } else {
     88         /*
     89          * Attempt to change to high-speed (if supported)
     90          */
     91         err = mmc_sd_switch_hs(card);
     92         if (err > 0)
     93             mmc_sd_go_highspeed(card);
     94         else if (err)
     95             goto free_card;
     96 
     97         /*
     98          * Set bus speed.
     99          */
    100         mmc_set_clock(host, mmc_sd_get_max_clock(card));
    101 
    102         /*
    103          * Switch to wider bus (if supported).
    104          */
    105         if ((host->caps & MMC_CAP_4_BIT_DATA) &&
    106             (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
    107             err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
    108             if (err)
    109                 goto free_card;
    110 
    111             mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
    112         }
    113     }
    114 
    115     host->card = card;
    116     return 0;
    117 
    118 free_card:
    119     if (!oldcard)
    120         mmc_remove_card(card);
    121 
    122     return err;
    123 }

    18行,err = mmc_sd_get_cid(host, ocr, cid, &rocr);字面意思就是获取CID,对照着流程图给该函数注释:

     1 /*
     2  * Fetch CID from card.
     3  */
     4 int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr)
     5 {
     6     int err;
     7     u32 max_current;
     8 
     9     /*
    10      * Since we're changing the OCR value, we seem to
    11      * need to tell some cards to go back to the idle
    12      * state.  We wait 1ms to give cards time to
    13      * respond.
    14      */
    15     mmc_go_idle(host);//发送CMD0
    16 
    17     /*
    18      * If SD_SEND_IF_COND indicates an SD 2.0
    19      * compliant card and we should set bit 30
    20      * of the ocr to indicate that we can handle
    21      * block-addressed SDHC cards.
    22      */
    23     err = mmc_send_if_cond(host, ocr);//发送CMD8,
    24 if (!err) 25 ocr |= SD_OCR_CCS;//如果返回失败,说明卡不是SD2.0或之后的版本,如果是2.0的SD卡,把ocr的30位置1,即协议里ACMD41命令中的HCS.
    26 表示支持SDHC或SDXC,ACMD41命令见下面图示
    27 /* 28 * If the host supports one of UHS-I modes, request the card 29 * to switch to 1.8V signaling level. 30 */ 31 if (host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | 32 MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50)) 33 ocr |= SD_OCR_S18R; //判断主机控制器是否支持UHS-I host->caps通常在主机控制器驱动的probe函数里面初始化,值和具体主机控制器
                        有关.如果支持那么就把ocr的24位置1,即ACMD41命令的S18R.表示要求SD卡准备切换到1.8V电压模式
    34 35 /* 36 * If the host can supply more than 150mA at current voltage, 37 * XPC should be set to 1. 38 */ 39 max_current = sd_get_host_max_current(host);
    40 if (max_current > 150) 41 ocr |= SD_OCR_XPC;//SDXC中Power Control相关的项,ocr第28位,ACMD41中的XPC
    42 43 try_again: 44 err = mmc_send_app_op_cond(host, ocr, rocr);//发送ACMD41,这个函数你可以进去看一下,会发现循环检验ACMD41的,应答值的31位,与
    协议流程图中符合.rocr就是卡对ACMD41的应答值.另外ACMD41属于app cmd需要先发送CMD55,这个linux都封装在了函数里,并且流程图上也简化掉了.
                                    
    45 if (err) 46 return err; 47 48 /* 49 * In case CCS and S18A in the response is set, start Signal Voltage 50 * Switch procedure. SPI mode doesn't support CMD11. 51 */ 52 if (!mmc_host_is_spi(host) && rocr && 53 ((*rocr & 0x41000000) == 0x41000000)) {//这个if语句,先判断是不是spi模式,是spi模式就不继续,我只分析SD模式所以继续
    判断rocr即ACMD41的应答值的CSS(30位)和S18A(24位)这两位具体意义见代码下面的图表.只有当sd卡是SDHC或SDXC,并且SD卡准备好切换电压模式,才切换
    SDSC不进行切换.

    54 err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, true);//发送CMD11切换,主机端也做相应的处理.
    55 if (err) { 56 ocr &= ~SD_OCR_S18R; 57 goto try_again; 58 } 59 } 60 61 if (mmc_host_is_spi(host)) 62 err = mmc_send_cid(host, cid); 63 else 64 err = mmc_all_send_cid(host, cid);//发送CMD2.获取cid,即Card IDentification register,存放了一些卡的信息,
    到这里发现已经到了流程图的底部,只剩CMD3了

    65 66 return err; 67 }

    ACMD41命令:


     

    图中红线标出的就是代码中rocr的值.关于该命令更多的内容见SD的spec.

    回到mmc_sd_init_card函数,

    22~37行与协议无关,主要是初始化一个struct mmc_card *card结构体.这个结构体就相当于这张卡的身份证,从卡的CID,CSD..寄存器拿到的值都要填到该结构中备用.

    43行 err = mmc_send_relative_addr(host, &card->rca),发送CMD3获取RCA,得到卡的地址.流程图到这里就结束了. mmc_sd_init_card却没有结束.

    接下来还要获取SD卡的CSD寄存器的值,来填充struct mmc_card *card结构体.

    49行 err = mmc_sd_get_csd(host, card);发送CMD9获取CSD并解析,填充到card.CSD寄存器保存了大量卡的信息.

    53行 mmc_decode_cid(card); 解析前面获得的CID并填充到card,这一步为什么不在前面获得cid的时候做? 是因为sd卡协议有不同版本而版本信息放在CSD中,所以需要先得到CSD,获得版本号,在根据版本号解析CID中的数据. 详细内容见spec中寄存器部分.

    60行 err = mmc_select_card(card);发送CMD7使用上面得到的地址选择卡

    65行 err = mmc_sd_setup_card(host, card, oldcard != NULL);发送ACMD51获取SCR寄存器值,发送ACMD13获取SD卡状态信息,解析并填充card结构,SCR寄存器是对CSD的补充.
    最后69~113行 if else语句,判断是否是UHS-I,分别进行处理.先看一下esle中的代码

    先调用 err = mmc_sd_switch_hs(card);,支持高速就发命令把卡设置到高速

     1 int mmc_sd_switch_hs(struct mmc_card *card)
     2 {
     3     int err;
     4     u8 *status;
     5 
     6     if (card->scr.sda_vsn < SCR_SPEC_VER_1)//判断卡的版本,SD1.1和之后的版本支持高速
     7         return 0;
     8 
     9     if (!(card->csd.cmdclass & CCC_SWITCH))//判断是否支持class10命令
    10         return 0;
    11 
    12     if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))//判断主机控制器是否支持高速卡
    13         return 0;
    14 
    15     if (card->sw_caps.hs_max_dtr == 0)/*这个值什么情况下会为0? 该值赋值是在mmc_read_switch函数中,发送CMD6命令时赋值的,如果不支持HIGHSPEED就没有赋值.
    16 通常sd卡驱动中使用CMD6命令,都会发送两次,第一次查询卡是否支持接下来要做的切换(在mmc_read_switch中).
    17 第二次执行切换操作.CMD6命令详细内容见spec*/
    18         return 0;
    19 
    20     err = -EIO;
    21 
    22     status = kmalloc(64, GFP_KERNEL);//分配64bytes的空间.用于接收CMD6的应答数据.
    23     if (!status) {
    24         pr_err("%s: could not allocate a buffer for "
    25             "switch capabilities.
    ", mmc_hostname(card->host));
    26         return -ENOMEM;
    27     }
    28 
    29     err = mmc_sd_switch(card, 1, 0, 1, status);//发送CMD6切换卡到高速
    30     if (err)
    31         goto out;
    32 
    33     if ((status[16] & 0xF) != 1) {//切换结果在第17字节,也是spec中规定的
    34         pr_warning("%s: Problem switching card "
    35             "into high-speed mode!
    ",
    36             mmc_hostname(card->host));
    37         err = 0;
    38     } else {
    39         err = 1;
    40     }
    41 
    42 out:
    43     kfree(status);
    44 
    45     return err;
    46 }

    93行,设置主机这边,最终会调用host驱动,与你host硬件有关.

    100行,mmc_set_clock(host, mmc_sd_get_max_clock(card));//设置时钟频率,mmc_sd_get_max_clock(card)是用的card->sw_caps.hs_max_dtr或card->csd.max_dtr.如果支持高速linux会设置为card->sw_caps.hs_max_dtr,这个值在之前被赋为

    50000000.

    105~111 同样需要host和card同时支持4bit宽度,才能设置,并且要同时设置host和card

    err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);设置卡的总线宽度,使用的命令是ACMD6

    至此,mmc_sd_init_card函数就结束了. linux中的初始化处理过程基本上与spec中的流程图相符. 之后还有设置时钟,设置数据线宽度等操作.

    还有一个关于UHS-I的分支没有分析,这个放到下篇分析.并且其中又涉及到CMD6,顺便简单分析学习一下CMD6的使用.

  • 相关阅读:
    java跳过构造方法新建对象
    java实现类似qq的窗口对聊
    NoSql的产生
    C语言跳出循环
    C语言for循环
    C语言while语句
    C语言条件运算符
    C语言switch语句
    C语言逻辑运算符
    C语言关系运算符
  • 原文地址:https://www.cnblogs.com/fengeryi/p/3469782.html
Copyright © 2011-2022 走看看