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

本文为该系列的第三篇。
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,功能为停止多块读或者写操作。

3.3.3 核心层

在上文的协议层中,我们发现每个命令函数最终都会调用到mmc_send_cmd函数将命令发送出去,那本节就来讲解该函数的实现以及其他一些初始化过程中会用到的函数,最后探究相比于裸机驱动为什么会多出这个核心层。

3.3.3.1 mmc_send_cmd函数

代码如下:

 int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
 {
 return dm_mmc_send_cmd(mmc->dev, cmd, data);
 }

其中又调用了dm_mmc_send_cmd函数,代码如下:

 int dm_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
     struct mmc_data *data)
 {
 struct mmc *mmc = mmc_get_mmc_dev(dev);
 struct dm_mmc_ops *ops = mmc_get_ops(dev);
 int ret;
 
 mmmc_trace_before_send(mmc, cmd);
 if (ops->send_cmd)
 ret = ops->send_cmd(dev, cmd, data);
 else
 ret = -ENOSYS;
 mmmc_trace_after_send(mmc, cmd, ret);
 
 return ret;
 }

我们发现,该函数最终又调用了struct dm_mmc_ops *ops的send_cmd函数指针,关于struct dm_mmc_ops定义如下:

 struct dm_mmc_ops {
 /**
  * deferred_probe() - Some configurations that need to be deferred
  * to just before enumerating the device
  *
  * @dev:Device to init
  * @return 0 if Ok, -ve if error
  */
 int (*deferred_probe)(struct udevice *dev);
 /**
  * send_cmd() - Send a command to the MMC device
  *
  * @dev:Device to receive the command
  * @cmd:Command to send
  * @data:Additional data to send/receive
  * @return 0 if OK, -ve on error
  */
 int (*send_cmd)(struct udevice *dev, struct mmc_cmd *cmd,
 struct mmc_data *data);
 
 /**
  * set_ios() - Set the I/O speed/width for an MMC device
  *
  * @dev:Device to update
  * @return 0 if OK, -ve on error
  */
 int (*set_ios)(struct udevice *dev);
 
 /**
  * get_cd() - See whether a card is present
  *
  * @dev:Device to check
  * @return 0 if not present, 1 if present, -ve on error
  */
 int (*get_cd)(struct udevice *dev);
 
 /**
  * get_wp() - See whether a card has write-protect enabled
  *
  * @dev:Device to check
  * @return 0 if write-enabled, 1 if write-protected, -ve on error
  */
 int (*get_wp)(struct udevice *dev);
 
 #ifdef MMC_SUPPORTS_TUNING
 /**
  * execute_tuning() - Start the tuning process
  *
  * @dev:Device to start the tuning
  * @opcode:Command opcode to send
  * @return 0 if OK, -ve on error
  */
 int (*execute_tuning)(struct udevice *dev, uint opcode);
 #endif
 
 /**
  * wait_dat0() - wait until dat0 is in the target state
  *(CLK must be running during the wait)
  *
  * @dev:Device to check
  * @state:target state
  * @timeout_us:timeout in us
  * @return 0 if dat0 is in the target state, -ve on error
  */
 int (*wait_dat0)(struct udevice *dev, int state, int timeout_us);
 
 #if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
 /* set_enhanced_strobe() - set HS400 enhanced strobe */
 int (*set_enhanced_strobe)(struct udevice *dev);
 #endif
 
 /**
  * host_power_cycle - host specific tasks in power cycle sequence
  *     Called between mmc_power_off() and
  *     mmc_power_on()
  *
  * @dev:Device to check
  * @return 0 if not present, 1 if present, -ve on error
  */
 int (*host_power_cycle)(struct udevice *dev);
 
 /**
  * get_b_max - get maximum length of single transfer
  *       Called before reading blocks from the card,
  *       useful for system which have e.g. DMA limits
  *       on various memory ranges.
  *
  * @dev:Device to check
  * @dst:Destination buffer in memory
  * @blkcnt:Total number of blocks in this transfer
  * @return maximum number of blocks for this transfer
  */
 int (*get_b_max)(struct udevice *dev, void *dst, lbaint_t blkcnt);
 };

其实,这种调用函数指针的方式同样用在了其他一些用于初始化时的函数,比如下列函数:

3.3.3.2 mmc_set_ios函数

该函数的功能为设置sd控制器的时钟以及数据线宽度。

 int mmc_set_ios(struct mmc *mmc)
 {
 return dm_mmc_set_ios(mmc->dev);
 }
 
 int dm_mmc_set_ios(struct udevice *dev)
 {
 struct dm_mmc_ops *ops = mmc_get_ops(dev);
 
 if (!ops->set_ios)
 return -ENOSYS;
 return ops->set_ios(dev);
 }
3.3.3.3 mmc_getwp函数

该函数的功能为获取sd卡的写保护功能是否使能。

 int mmc_getwp(struct mmc *mmc)
 {
 return dm_mmc_get_wp(mmc->dev);
 }
 
 int dm_mmc_get_wp(struct udevice *dev)
 {
 struct dm_mmc_ops *ops = mmc_get_ops(dev);
 
 if (!ops->get_wp)
 return -ENOSYS;
 return ops->get_wp(dev);
 }
3.3.3.4 mmc_getcd函数

该函数的功能为探测sd卡是否插入。

 int mmc_getcd(struct mmc *mmc)
 {
 return dm_mmc_get_cd(mmc->dev);
 }
 
 int dm_mmc_get_cd(struct udevice *dev)
 {
 struct dm_mmc_ops *ops = mmc_get_ops(dev);
 
 if (!ops->get_cd)
 return -ENOSYS;
 return ops->get_cd(dev);
 }
3.3.3.5 增加核心层的原因

上述一系列函数都只是调用了struct dm_mmc_ops结构体中的函数指针,我们可以想一下,为什么要这样做呢?

我们要知道,u-boot需要支持不同的sd控制器,而每种sd控制器针对上述的一系列函数的实现均不同,这样,我们就需要在上文提到的协议层与对sd控制器的控制(也就是我们后面的驱动层的内容)之间增加一层核心层,作用便是解除协议层与驱动层之间的耦合。

当需要支持不同的sd控制器时,可以针对每个sd控制器分别实现一套上述的一系列函数,然后将每一套函数均赋值到自己的struct dm_mmc_ops结构体的各个函数指针中。当需要调用时,只需找到针对每个sd控制器的struct dm_mmc_ops结构体,然后调用其中的函数指针即可。

这里我们可以猜想到,驱动层的主要工作应该就是针对每个sd控制器,去实现具体的struct dm_mmc_ops结构体中的函数指针对应的函数,事实的确如此,接下来我们便开始进入驱动层的世界。

3.3.4 驱动层

注:该驱动主要内容来自u-boot 2016版本中的sd驱动,在此基础上我做了如下工作:

  1. 由于u-boot 2016版本中的sd驱动并未支持dm驱动模型,故增加了适配dm驱动模型的代码

  2. 利用pinctrl子系统配置sd控制器接入到sd卡的引脚为sd模式

  3. 利用clock子系统使能时钟,获得时钟的频率

  4. 利用gpio子系统获得sd卡探测引脚的电平,以探测sd卡是否插入

如3.3.3.5一节中分析的那样,驱动层的工作主要便是实现struct dm_mmc_ops结构体中的需要的函数指针对应的函数,此外还需要实现一个probe函数,用于初始化sd控制器,而这一切又包含于驱动模型之中,对于驱动模型不熟悉的朋友请参考3.3.1一节。

3.3.4.1 驱动入口

下面是sd卡驱动符合驱动模型的入口:

 static const struct udevice_id s3c2440_mmc_ids[] = {
 { .compatible = "samsung,s3c2440-mmc"},
 { /* sentinel */ }
 };
 
 U_BOOT_DRIVER(s3c2440_mmc_drv) = {
 .name= "s3c2440_mmc",
 .id= UCLASS_MMC,
 .of_match= s3c2440_mmc_ids,
 .bind= s3c2440_mmc_bind,
 .probe= s3c2440_mmc_probe,
 .ops= &s3c2440_mmc_ops,
 .platdata_auto_alloc_size = sizeof(struct s3c2440_mmc_plat),
 .priv_auto_alloc_size = sizeof(struct s3c2440_mmc_priv),
 };
3.3.4.2 probe函数

probe函数的作用主要是初始化该驱动,下面我们分功能来了解该函数。

  1. 从设备树中获取sd控制器中寄存器的基地址、sd控制器支持的数据线宽度、sd控制器的时钟:

    代码如下:

     priv->reg = (void *)dev_read_addr(dev
                        
    • 发表于 2020-07-18 00:26
    • 阅读 ( 193 )
    • 分类:经验分享

1 条评论&回复

请先 登录 后评论
渐进
渐进

12 篇文章

作家榜 »

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