详解sd协议以及裸机和u-boot中的sd卡驱动(3)

本文为该系列的第三篇。

3.3 u-boot中的sd卡驱动

相比于裸机,u-boot中的sd卡驱动在对sd控制器中的各个寄存器的操作以及对sd协议的遵循两方面基本相同,主要区别在于拥有良好的扩展性。所以,相比于寄存器与协议两方面,这里会更着重分析其组织框架,探究其支持良好扩展性的原因。

3.3.1 框架概述

下图是我总结的sd驱动的整体框架图:

attachments-2020-07-VPVtgQCd5f11d3c9ec568.png

其中,

  • sd协议层

    sd协议相关的内容,由于sd协议硬件无关,相比于3.2 裸机中的sd卡驱动,u-boot中将该内容单独作为一层,并且已经作为公共部分实现好,任何的sd卡驱动均可以复用该层的内容,也就是说在sd卡驱动中不必涉及协议相关的内容,只需关注于对sd控制器中的寄存器操作,这样,便实现了硬件操作与sd协议相分离

  • sd核心层

    由于不同的sd控制器针对sd协议中发送命令、接收响应等操作需要不同的硬件操作,所以sd核心层的作用在于向下屏蔽不同sd控制器所带来的不同的硬件操作,向上提供统一的接口给sd协议层

  • sd驱动层

    sd卡驱动在上述框架中的位置为sd driver,不具有通用性,需要针对具体的sd控制器进行编写,这也是移植sd卡驱动时需要做的部分

关于框架图中其他的内容,主要涉及u-boot中的驱动模型,请参考这篇博客:https://blog.csdn.net/ooonebook/article/details/53234020,讲的非常好。

3.3.2 协议层

如3.2.1所述,u-boot将sd协议单独设为一层,与sd卡驱动进行了分离,那么本节我们就开始探寻u-boot中对于sd协议是如何实现的。

3.3.2.1 初始化

初始化主要有两个函数,分别用于不同的阶段,下面进行说明:

  1. mmc_initialize函数

    该函数在u-boot的board_r阶段初始化时被调用

     #ifdef CONFIG_MMC
     static int initr_mmc(void)
     {
     puts("MMC:   ");
     mmc_initialize(gd->bd);
     return 0;
     }
     #endif

    主要完成sd卡早期的初始化工作,

    1. 调用sd卡驱动中的probe函数,进行sd控制器层面的初始化

    2. 按照sd协议发送一些命令,以探测是否有卡插入以及是sd卡还是mmc卡等

    代码如下:

     int mmc_initialize(bd_t *bis)
     {
     static int initialized = 0;
     int ret;
     if (initialized)/* Avoid initializing mmc multiple times */
     return 0;
     initialized = 1;
     
     #if !CONFIG_IS_ENABLED(BLK)
     #if !CONFIG_IS_ENABLED(MMC_TINY)
     mmc_list_init();
     #endif
     #endif
     ret = mmc_probe(bis);
     if (ret)
     return ret;
     
     #ifndef CONFIG_SPL_BUILD
     print_mmc_devices(',');
     #endif
     
     mmc_do_preinit();
     return 0;
     }
  2. mmc_init函数

    该函数在u-boot启动之后的多个地方被调用,比如mmc命令、块设备驱动、dfu驱动等等,相比于mmc_initialize函数的早期初始化功能,该函数按照sd协议进行完全的初始化,具体代码如下:

     int mmc_init(struct mmc *mmc)
     {
     int err = 0;
     __maybe_unused ulong start;
     #if CONFIG_IS_ENABLED(DM_MMC)
     struct mmc_uclass_priv *upriv = dev_get_uclass_priv(mmc->dev);
     
     upriv->mmc = mmc;
     #endif
     if (mmc->has_init)
     return 0;
     
     start = get_timer(0);
     
     if (!mmc->init_in_progress)
     err = mmc_start_init(mmc);
     
     if (!err)
     err = mmc_complete_init(mmc);
     if (err)
     pr_info("%s: %d, time %lu\n", __func__, err, get_timer(start));
     
     return err;
     }
  3. mmc_start_init函数

    这里我们选择两个初始化函数都会调用的mmc_start_init函数进行说明,一路追踪下去发现,最终会调用如下核心内容:

        ......
           
        /* Reset the Card */
        err = mmc_go_idle(mmc);
       
        if (err)
        return err;
       
        /* The internal partition reset to user partition(0) at every CMD0*/
        mmc_get_blk_desc(mmc)->hwpart = 0;
       
        /* Test for SD version 2 */
        err = mmc_send_if_cond(mmc);
       
        /* Now try to get the SD card's operating condition */
        err = sd_send_op_cond(mmc, uhs_en);
        if (err && uhs_en) {
        uhs_en = false;
        mmc_power_cycle(mmc);
        goto retry;
        }
       
        /* If the command timed out, we check for an MMC card */
        if (err == -ETIMEDOUT) {
        err = mmc_send_op_cond(mmc);
       
        if (err) {
        #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
        pr_err("Card did not respond to voltage select!\n");
        #endif
        return -EOPNOTSUPP;
        }
        }
       
        ......

    这里我们重点关注如下函数:

    • mmc_go_idle

    • mmc_send_if_cond

    • sd_send_op_cond

    • mmc_send_op_cond

    至于函数的具体实现,请参考3.2.2.4

3.3.2.2 读

代码如下:

 #if CONFIG_IS_ENABLED(BLK)
 ulong mmc_bread(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, void *dst)
 #else
 ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
 void *dst)
 #endif
 {
 #if CONFIG_IS_ENABLED(BLK)
 struct blk_desc *block_dev = dev_get_uclass_platdata(dev);
 #endif
 int dev_num = block_dev->devnum;
 int err;
 lbaint_t cur, blocks_todo = blkcnt;
 uint b_max;
 
 if (blkcnt == 0)
 return 0;
 
 struct mmc *mmc = find_mmc_device(dev_num);
 if (!mmc)
 return 0;
 
 if (CONFIG_IS_ENABLED(MMC_TINY))
 err = mmc_switch_part(mmc, block_dev->hwpart);
 else
 err = blk_dselect_hwpart(block_dev, block_dev->hwpart);
 
 if (err < 0)
 return 0;
 
 if ((start + blkcnt) > block_dev->lba) {
 #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
 pr_err("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n",
        start + blkcnt, block_dev->lba);
 #endif
 return 0;
 }
 
 if (mmc_set_blocklen(mmc, mmc->read_bl_len)) {
 pr_debug("%s: Failed to set blocklen\n", __func__);
 return 0;
 }
 
 b_max = mmc_get_b_max(mmc, dst, blkcnt);
 
 do {
 cur = (blocks_todo > b_max) ? b_max : blocks_todo;
 if (mmc_read_blocks(mmc, dst, start, cur) != cur) {
 pr_debug("%s: Failed to read blocks\n", __func__);
 return 0;
 }
 blocks_todo -= cur;
 start += cur;
 dst += cur * mmc->read_bl_len;
 } while (blocks_todo > 0);
 
 return blkcnt;
 }

这里我们重点关注如下函数:

  • mmc_set_blocklen

  • mmc_read_blocks

至于函数的具体实现,请参考3.2.2.4

3.3.2.3 写

代码如下:

 #if CONFIG_IS_ENABLED(BLK)
 ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt,
  const void *src)
 #else
 ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
  const void *src)
 #endif
 {
 #if CONFIG_IS_ENABLED(BLK)
 struct blk_desc *block_dev = dev_get_uclass_platdata(dev);
 #endif
 int dev_num = block_dev->devnum;
 lbaint_t cur, blocks_todo = blkcnt;
 int err;
 
 struct mmc *mmc = find_mmc_device(dev_num);
 if (!mmc)
 return 0;
 
 err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, block_dev->hwpart);
 if (err < 0)
 return 0;
 
 if (mmc_set_blocklen(mmc, mmc->write_bl_len))
 return 0;
 
 do {
 cur = (blocks_todo > mmc->cfg->b_max) ?
 mmc->cfg->b_max : blocks_todo;
 if (mmc_write_blocks(mmc, start, cur, src) != cur)
 return 0;
 blocks_todo -= cur;
 start += cur;
 src += cur * mmc->write_bl_len;
 } while (blocks_todo > 0);
 
 return blkcnt;
 }

这里我们重点关注如下函数:

  • mmc_set_blocklen

  • mmc_write_blocks

至于函数的具体实现,请参考3.2.2.4

3.3.2.4 具体命令实现举例

这里用几个命令作为例子来讲解下其具体的实现,其他命令的实现原理均是类似的。

  1. mmc_go_idle函数

     static int mmc_go_idle(struct mmc *mmc)
     {
     struct mmc_cmd cmd;
     int err;
     
     udelay(1000);
     
     cmd.cmdidx = MMC_CMD_GO_IDLE_STATE;
     cmd.cmdarg = 0;
     cmd.resp_type = MMC_RSP_NONE;
     
     err = mmc_send_cmd(mmc, &cmd, NULL);
     
     if (err)
     return err;
     
     udelay(2000);
     
     return 0;
     }

    u-boot中将命令抽象成了一个结构体struct mmc_cmd,具体定义及变量解释如下:

     struct mmc_cmd {
     ushort cmdidx;/* 命令编号 */
     uint resp_type;/* 命令返回的相应类型 */
     uint cmdarg;/* 命令参数 */
     uint response[4];/* 命令返回的响应值 */
     };

    我们看到mmc_go_idle中对应的命令编号为MMC_CMD_GO_IDLE_STATE,这个宏定义对应的值为0,然后命令参数为0,响应类型为无响应,最后调用了mmc_send_cmd函数。

    mmc_send_cmd函数的作用便是负责发送一个命令到sd卡,其具体的实现请参看后面的3.2.3一节。

  2. mmc_send_if_cond函数

     static int mmc_send_if_cond(struct mmc *mmc)
     {
     struct mmc_cmd cmd;
     int err;
     
     cmd.cmdidx = SD_CMD_SEND_IF_COND;
     /* We set the bit if the host supports voltages between 2.7 and 3.6 V */
     cmd.cmdarg = ((mmc->cfg->voltages & 0xff8000) != 0) << 8 | 0xaa;
     cmd.resp_type = MMC_RSP_R7;
     
     err = mmc_send_cmd(mmc, &cmd, NULL);
     
     if (err)
     return err;
     
     if ((cmd.response[0] & 0xff) != 0xaa)
     return -EOPNOTSUPP;
     else
     mmc->version = SD_VERSION_2;
     
     return 0;
     }

    我们看到,mmc_send_if_cond函数与mmc_go_idle函数的结构非常相似。

    通过查看宏定义SD_CMD_SEND_IF_COND可知,该函数对应的命令为CMD8,然后也是调用mmc_send_cmd函数将该命令发送出去,最后又读取了一下响应值。

  3. mmc_read_blocks函数

     static int mmc_read_blocks(struct mmc *mmc, void *dst, lbaint_t start,
        lbaint_t blkcnt)
     {
     struct mmc_cmd cmd;
     struct mmc_data data;
     
     if (blkcnt > 1)
     cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK;
     else
     cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK;
     
     if (mmc->high_capacity)
     cmd.cmdarg = start;
     else
     cmd.cmdarg = start * mmc->read_bl_len;
     
     cmd.resp_type = MMC_RSP_R1;
     
     data.dest = dst;
     data.blocks = blkcnt;
     data.blocksize = mmc->read_bl_len;
     data.flags = MMC_DATA_READ;
     
     if (mmc_send_cmd(mmc, &cmd, &data))
     return 0;
     
     if (blkcnt > 1) {
     cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
     cmd.cmdarg = 0;
     cmd.resp_type = MMC_RSP_R1b;
     if (mmc_send_cmd(mmc, &cmd, NULL)) {
     #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
     pr_err("mmc fail to send stop cmd\n");
     #endif
     return 0;
     }
     }
     
     return blkcnt;
     }

    通过参数MMC_CMD_READ_SINGLE_BLOCK以及MMC_CMD_READ_MULTIPLE_BLOCK,可知该函数对应的命令为CMD17或者CMD18,功能为读取sd卡的一个或多个块数据,因为这两个命令是带有数据要返回的,针对sd协议中的数据,u-boot定义了结构体struct mmc_data,具体定义及变量解释如下:

     struct mmc_data {
     union {
     char *dest;/* 针对读,数据目的地址 */
     const char *src; /* 针对写,数据源地址 */
     };
     uint flags;/* 读或者写标志 */
     uint blocks;/* 读或者写的块数量 */
     uint blocksize;/* 读或者写的块大小 */
     };

    然后对struct mmc_data结构体赋值后,连同struct mmc_cmd结构体一并作为参数调用mmc_send_cmd函数将命令发送出去。因为是读,当mmc_send_cmd函数执行完成返回后,读取到的数据便写到了struct mmc_data结构体中赋好值的目的地址上了。

    最后,如果是多块读,又调用了MMC_CMD_STOP_TRANSMISSION,也就是CMD12,功能为停止多块读或者写操作。

  • 发表于 2020-07-18 00:38
  • 阅读 ( 210 )
  • 分类:经验分享

1 条评论&回复

请先 登录 后评论
渐进
渐进

12 篇文章

作家榜 »

  1. 百问网-周老师 18 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. 谢工 5 文章
  7. Litchi_Zheng 5 文章
  8. 星星之火 5 文章