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

本文为该系列的第五篇。


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);
     bus_width = dev_read_u32_default(dev, "bus-width", 1);
     
     ret = clk_get_by_name(dev, "sdmmc_clk", &priv->clk);
     if (ret) {
         printf("%s(): get sdmmc clk failed!\n", __func__);
         return -ENODEV;
     }

    设备树节点如下:

     sdmmc: mmc@5a000000 {
         compatible = "samsung,s3c2440-mmc";
         reg = <0x5a000000 0x1000>;
     
         clock-names = "sdmmc_clk";
         clocks = <&clk PCLK_SDI>;
     
         pinctrl-names = "default";
         pinctrl-0 = <&sdmmc_pin>,<&cd_pin>;
     
         cd-gpio = <&gpiog 8 GPIO_ACTIVE_LOW>;
         bus-width = <4>;
     };
  2. 使能sd控制器的时钟,该时钟从PCLK而来,并且获得时钟具体的频率,以供后面使用:

     ret = clk_enable(&priv->clk);
     if (ret) {
         printf("%s(): enable sdmmc clk failed!\n", __func__);
         return -EAGAIN;
     }
     
     sdmmc_clk = clk_get_rate(&priv->clk);
     printf("%s(): sdmmc_clk = %d\n", __func__, sdmmc_clk);
  3. 提供sd控制器的能力给核心层驱动:

     cfg->name = dev->name;
     cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
     cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS;
     cfg->f_min = 400000;
     cfg->f_max = sdmmc_clk / 2;
     cfg->b_max = 0x80;
     upriv->mmc = &plat->mmc;
  4. 初始化sd控制器:

     writel(S3C2440_SDICON_SDRESET, &priv->reg->sdicon);
     mdelay(10);
     writel(0x7fffff, &priv->reg->sdidtimer);
     
     writel(MMC_MAX_BLOCK_LEN, &priv->reg->sdibsize);
     writel(0x0, &priv->reg->sdiimsk);
     
     writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE,
            &priv->reg->sdicon);
     
     mdelay(125);


完整代码如下:


 static int s3c2440_mmc_probe(struct udevice *dev)
 {
 struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
 struct s3c2440_mmc_plat *plat = dev_get_platdata(dev);
 struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
 struct mmc_config *cfg = &plat->cfg;
 int bus_width, ret;
 u32 sdmmc_clk;
 
 priv->reg = (void *)dev_read_addr(dev);
 bus_width = dev_read_u32_default(dev, "bus-width", 1);
 
 ret = clk_get_by_name(dev, "sdmmc_clk", &priv->clk);
 if (ret) {
 printf("%s(): get sdmmc clk failed!\n", __func__);
 return -ENODEV;
 }
 
 ret = clk_enable(&priv->clk);
 if (ret) {
 printf("%s(): enable sdmmc clk failed!\n", __func__);
 return -EAGAIN;
 }
 
 sdmmc_clk = clk_get_rate(&priv->clk);
 printf("%s(): sdmmc_clk = %d\n", __func__, sdmmc_clk);
 
 cfg->name = dev->name;
 cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
 cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS;
 cfg->f_min = 400000;
 cfg->f_max = sdmmc_clk / 2;
 cfg->b_max = 0x80;
 upriv->mmc = &plat->mmc;
 
 writel(S3C2440_SDICON_SDRESET, &priv->reg->sdicon);
 mdelay(10);
 writel(0x7fffff, &priv->reg->sdidtimer);
 
 writel(MMC_MAX_BLOCK_LEN, &priv->reg->sdibsize);
 writel(0x0, &priv->reg->sdiimsk);
 
 writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE,
        &priv->reg->sdicon);
 
 mdelay(125);
 
 return 0;
 }


3.3.4.3 ops操作函数集


ops操作函数集中的每个函数均完成一个单独的功能,由核心层进行调用,定义如下:


 static const struct dm_mmc_ops s3c2440_mmc_ops = {
 .send_cmd= s3c2440_mmc_send_cmd,
 .set_ios= s3c2440_mmc_set_ios,
 .get_cd= s3c2440_mmc_getcd,
 .get_wp= s3c2440_mmc_getwp,
 };


下面我们逐个分析每个函数。


  1. s3c2440_mmc_send_cmd函数

    该函数是整个sd驱动的核心,其通过读写sd控制器中的寄存器,提供发送命令到sd卡的功能,如果命令伴有响应返回则会读取响应值,如果命令伴有数据传输则会进行收发数据。

     /*
      * WARNING: We only support one SD IP block.
      * NOTE: It's not likely there will ever exist an S3C24xx with two,
      *       at least not in this universe all right.
      */
     static int s3c2440_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
           struct mmc_data *data)
     {
     struct s3c2440_mmc_plat *plat = dev_get_platdata(dev);
     struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
     struct mmc *mmc = &plat->mmc;
     
     unsigned int timeout = 100000;
     int ret = 0, xfer_len, data_offset = 0;
     uint32_t sdiccon, sdicsta, sdidcon, sdidsta, sdidat, sdifsta;
     uint32_t sdicsta_wait_bit = S3C2410_SDICMDSTAT_CMDSENT;
     const uint32_t sdidsta_err_mask = S3C2410_SDIDSTA_FIFOFAIL |
     S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL |
     S3C2410_SDIDSTA_DATATIMEOUT;
     
     writel(0xffffffff, &priv->reg->sdicsta);
     writel(0xffffffff, &priv->reg->sdidsta);
     writel(0xffffffff, &priv->reg->sdifsta);
     
     /* Set up data transfer (if applicable). */
     if (data) {
     writel(data->blocksize, &priv->reg->sdibsize);
     
     sdidcon = data->blocks & S3C2410_SDIDCON_BLKNUM;
     sdidcon |= S3C2410_SDIDCON_BLOCKMODE;
     sdidcon |= S3C2440_SDIDCON_DS_WORD | S3C2440_SDIDCON_DATSTART;
     
     if (mmc->bus_width == 4)
     sdidcon |= S3C2410_SDIDCON_WIDEBUS;
     
     if (data->flags & MMC_DATA_READ) {
     sdidcon |= S3C2410_SDIDCON_RXAFTERCMD;
     sdidcon |= S3C2410_SDIDCON_XFER_RXSTART;
     } else {
     sdidcon |= S3C2410_SDIDCON_TXAFTERRESP;
     sdidcon |= S3C2410_SDIDCON_XFER_TXSTART;
     }
     
     writel(sdidcon, &priv->reg->sdidcon);
     }
     
     /* Write CMD arg. */
     writel(cmd->cmdarg, &priv->reg->sdicarg);
     
     /* Write CMD index. */
     sdiccon = cmd->cmdidx & S3C2410_SDICMDCON_INDEX;
     sdiccon |= S3C2410_SDICMDCON_SENDERHOST;
     sdiccon |= S3C2410_SDICMDCON_CMDSTART;
     
     /* Command with short response. */
     if (cmd->resp_type & MMC_RSP_PRESENT) {
     sdiccon |= S3C2410_SDICMDCON_WAITRSP;
     sdicsta_wait_bit = S3C2410_SDICMDSTAT_RSPFIN;
     }
     
     /* Command with long response. */
     if (cmd->resp_type & MMC_RSP_136)
     sdiccon |= S3C2410_SDICMDCON_LONGRSP;
     
     /* Start the command. */
     writel(sdiccon, &priv->reg->sdiccon);
     
     /* Wait for the command to complete or for response. */
     for (timeout = 100000; timeout; timeout--) {
     sdicsta = readl(&priv->reg->sdicsta);
     if (sdicsta & sdicsta_wait_bit)
     break;
     
     if (sdicsta & S3C2410_SDICMDSTAT_CMDTIMEOUT)
     timeout = 1;
     }
     
     /* Clean the status bits. */
     writel(readl(&priv->reg->sdicsta) | (0xf << 9), &priv->reg->sdicsta);
         // setbits_le32(&priv->reg->sdicsta, 0xf << 9);
     
     if (!timeout) {
     printf("S3C SDI: Command timed out!\n");
     ret = -ETIMEDOUT;
     goto error;
     }
     
     /* Read out the response. */
     if (cmd->resp_type & MMC_RSP_136) {
     cmd->response[0] = readl(&priv->reg->sdirsp0);
     cmd->response[1] = readl(&priv->reg->sdirsp1);
     cmd->response[2] = readl(&priv->reg->sdirsp2);
     cmd->response[3] = readl(&priv->reg->sdirsp3);
     } else {
     cmd->response[0] = readl(&priv->reg->sdirsp0);
     }
     
     /* If there are no data, we're done. */
     if (!data)
     return 0;
     
     xfer_len = data->blocksize * data->blocks;
     
     while (xfer_len > 0) {
     sdidsta = readl(&priv->reg->sdidsta);
     sdifsta = readl(&priv->reg->sdifsta);
     
     if (sdidsta & sdidsta_err_mask) {
     printf("S3C SDI: Data error (sdta=0x%08x)\n", sdidsta);
     ret = -EIO;
     goto error;
     }
     
     if (data->flags & MMC_DATA_READ) {
     if ((sdifsta & S3C2410_SDIFSTA_COUNTMASK) < 4)
     continue;
     sdidat = readl(&priv->reg->sdidat);
     put_unaligned_le32(sdidat, data->dest + data_offset);
     } else {/* Write */
     /* TX FIFO half full. */
     if (!(sdifsta & S3C2410_SDIFSTA_TFHALF))
     continue;
     
     /* TX FIFO is below 32b full, write. */
     sdidat = get_unaligned_le32(data->src + data_offset);
     writel(sdidat, &priv->reg->sdidat);
     }
     data_offset += 4;
     xfer_len -= 4;
     }
     
     /* Wait for the command to complete or for response. */
     for (timeout = 100000; timeout; timeout--) {
     sdidsta = readl(&priv->reg->sdidsta);
     if (sdidsta & S3C2410_SDIDSTA_XFERFINISH)
     break;
     
     if (sdidsta & S3C2410_SDIDSTA_DATATIMEOUT)
     timeout = 1;
     }
     
     /* Clear status bits. */
     writel(0x6f8, &priv->reg->sdidsta);
     
     if (!timeout) {
     printf("S3C SDI: Command timed out!\n");
     ret = -ETIMEDOUT;
     goto error;
     }
     
     writel(0, &priv->reg->sdidcon);
     
     return 0;
     error:
     return ret;
     }
  2. s3c2440_mmc_set_ios函数

    该函数获得核心层传来的需要设置的时钟值,然后进行设置。

     static int s3c2440_mmc_set_ios(struct udevice *dev)
     {
     struct s3c2440_mmc_plat *plat = dev_get_platdata(dev);
     struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
     struct mmc *mmc = &plat->mmc;
         uint32_t divider = 0;
     u32 sdmmc_clk;
     
     printf("set ios: bus_width: %x, clock: %d\n",
           mmc->bus_width, mmc->clock);
     
     if (!mmc->clock)
     return -1;
     
     sdmmc_clk = clk_get_rate(&priv->clk);
     printf("%s(): sdmmc_clk = %d\n", __func__, sdmmc_clk);
     
     divider = DIV_ROUND_UP(sdmmc_clk, mmc->clock);
     if (divider)
     divider--;
     
     writel(divider, &priv->reg->sdipre);
     mdelay(125);
     
         return 0;
     }
  3. s3c2440_mmc_getcd函数

    该函数通过读取sd卡的探测引脚的电平值,进而供核心层判断sd卡是否插入。

     static int s3c2440_mmc_getcd(struct udevice *dev)
     {
     struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
     int value, ret;
     
     ret = gpio_request_by_name(dev, "cd-gpio", 0, &priv->cd_gpio, GPIOD_IS_IN);
     if (ret) {
     printf("%s(): gpio_request_by_name failed!\n", __func__);
     return ret;
     }
     
     if (!dm_gpio_is_valid(&priv->cd_gpio)) {
     printf("%s(): dm_gpio_is_valid failed!\n", __func__);
     return ret;
     }
     
     value = dm_gpio_get_value(&priv->cd_gpio);
     printf("%s(): value = %d\n", __func__, value);
     
     
    • 发表于 2020-07-18 00:43
    • 阅读 ( 253 )
    • 分类:经验分享

1 条评论&回复

请先 登录 后评论
渐进
渐进

12 篇文章

作家榜 »

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