3.8.7. MMC 驱动

本章节描述 MMC 驱动的相关配置和使用。

3.8.7.1. 驱动框架

U-Boot 驱动模型支持 MMC,并且通过块设备接口对 MMC 进行访问。ZX 平台中, SPL 和 U-Boot 阶段都支持 MMC 已经块设备接口。 相关配置为:

  • CONFIG_MMC

  • CONFIG_DM_MMC

  • CONFIG_SPL_DM_MMC

  • CONFIG_BLK

  • CONFIG_SPL_BLK

  • CONFIG_MMC_ZX

相关源码有:

  • include/mmc.h

  • include/blk.h

  • drivers/block/blk-uclass.c

  • drivers/mmc/mmc-uclass.c

  • drivers/mmc/zx_mmc.c

3.8.7.2. 驱动接口

常用驱动接口:

unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start,
            lbaint_t blkcnt, void *buffer);
unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
             lbaint_t blkcnt, const void *buffer);
unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start,
             lbaint_t blkcnt);

struct mmc *mmc_create(const struct mmc_config *cfg, void *priv);
int mmc_bind(struct udevice *dev, struct mmc *mmc,
         const struct mmc_config *cfg);
void mmc_destroy(struct mmc *mmc);
int mmc_unbind(struct udevice *dev);
int mmc_initialize(bd_t *bis);
int mmc_init(struct mmc *mmc);
int mmc_send_tuning(struct mmc *mmc, u32 opcode, int *cmd_error);
int mmc_of_parse(struct udevice *dev, struct mmc_config *cfg);
int mmc_read(struct mmc *mmc, u64 src, uchar *dst, int size);
int mmc_set_clock(struct mmc *mmc, uint clock, bool disable);

3.8.7.3. 初始化和使用

本章节主要介绍 MMC 以及对应的 BLK 设备的初始化流程,以及读写流程。

3.8.7.3.1. 绑定阶段

使用时,MMC 设备通过 BLK 块设备接口进行使用。MMC 设备与 BLK 设备之间的关系如 图 3.6 所示。

../../../_images/mmc_relation.png

图 3.6 MMC 与 BLK 设备的关系

对于每一个 MMC 设备,在绑定阶段,都会创建一个对应的 MMC_BLK 块设备,并且 MMC_BLK 设备的 parent 指向当前 MMC 设备。 ZX MMC 设备绑定对应的 MMC 设备驱动, MMC_BLK 设备绑定 mmc-uclass 中的 mmc_blk 驱动。

读写操作时,通过 mmc_blk 驱动的读写函数,转为对 mmc 的操作。

static const struct blk_ops mmc_blk_ops = { // drivers/mmc/mmc-uclass.c
    .read   = mmc_bread,
#if CONFIG_IS_ENABLED(MMC_WRITE)
    .write  = mmc_bwrite,
    .erase  = mmc_berase,
#endif
    .select_hwpart  = mmc_select_hwpart,
};

下面的绑定流程演示了创建 MMC_BLK 设备,并且进行关联的过程。

使用 PLATDATA 时

在 SPL 中,使能 PLATDATA 时的绑定流程如下。

reset // arch/arm/cpu/armv7/start.S
|-> _main   // arch/arm/lib/crt0.S
    |-> board_init_f(); // arch/arm/mach-zx/spl.c
        |-> spl_early_init() // common/spl/spl.c
            |-> spl_common_init(setup_malloc = true) // common/spl/spl.c
                |-> dm_init_and_scan(!CONFIG_IS_ENABLED(OF_PLATDATA));
                    |-> dm_scan_platdata(pre_reloc_only=false)
                        |-> lists_bind_drivers();
                            |-> device_bind_by_name(parent, false, entry, &dev);
                                |-> drv = lists_driver_lookup_name(info->name);
                                |   // 搜索 U_BOOT_DRIVER(name) 声明的 driver
                                |-> device_bind_common(); // drivers/core/device.c
                                    |-> uclass_get(&uc);
                                    |-> uclass_bind_device(dev);
                                    |-> drv->bind(dev);
                                        aic_dwmmc_bind(dev);
                                            |
         +----------------------------------+
         |
aic_dwmmc_bind(dev); // drivers/mmc/zx_dw_mmc.c
|-> dwmci_bind(dev, ...); // drivers/mmc/dw_mmc.c
    |
    |-> mmc_bind(dev, &plat->mmc, &plat->cfg) // drivers/mmc/mmc-uclass.c
        |  // 绑定一个 IF_TYPE_MMC 的 Block 子设备,这样可以通过块设备的接口
        |  // 使用 MMC。
        |
        |-> blk_create_devicef(dev, "mmc_blk", "blk",IF_TYPE_MMC, devnum,
        |   |                   512, 0, &bdev); // drivers/block/blk-uclass.c
        |   |-> blk_create_device(parent, "mmc_blk", dev_name, if_type,
        |       |                 devnum, blksz, lba, devp);
        |       |-> device_bind_driver(parent, drv_name, name, &dev);
        |           |   // drivers/core/lists.c
        |           |-> ....
        |               |-> device_bind_common(dm_root, ...);
        |                   |-> uclass_get(drv->id, &uc); id = UCLASS_BLK
        |                   |-> dev = calloc(1, sizeof(struct udevice));
        |                   |   dev->name = name // 块设备名字
        |                   |   dev->parent = parent // 指向 MMC 设备
        |                   |   dev->driver = drv // "mmc_blk" driver
        |                   |   dev->uclass = uc // UCLASS_BLK
        |                   |   // 创建设备 mmc_blk
        |                   |
        |                   |-> uclass_bind_device(dev);
        |                       // 将设备添加到 UCLASS_BLK 列表中
        |
        |-> dev_get_uclass_platdata(bdev);

使用 DTS 时

使用 DTS 时,SPL 和 U-Boot 中的绑定流程如下。在 DTS 中,MMC 控制器是 soc 的子节点, 挂载到 simple-bus 中,因此相关绑定在 soc 设备绑定 simple-bus 驱动后被触发, 因此在 simple_bus_post_build() 中处理。

U-Boot 阶段的 MMC 绑定在 initf_dm 中进行。

simple_bus_post_bind(); // drivers/core/simple-bus.c
|-> dm_scan_fdt_dev(dev); // drivers/core/root.c
    |-> dm_scan_fdt_node();
        |-> lists_bind_fdt(); // drivers/core/lists.c
            |   // 通过 compatible 匹配设备和驱动
            |-> device_bind_with_driver_data();
                |-> device_bind_common(); // drivers/core/device.c
                    |-> uclass_get(&uc)
                    |-> uclass_bind_device(dev)
                    |-> drv->bind(dev)
                        aic_dwmmc_bind(dev);
                            |
         +------------------+
         |
aic_dwmmc_bind(dev); // drivers/mmc/zx_dw_mmc.c
|-> dwmci_bind(dev, ...); // drivers/mmc/dw_mmc.c
    |
    |-> mmc_bind(dev, &plat->mmc, &plat->cfg); // drivers/mmc/mmc-uclass.c
        |  // 绑定一个 IF_TYPE_MMC 的 Block 子设备,这样可以通过块设备的接口
        |  // 使用 MMC。
        |
        |-> blk_create_devicef(dev, "mmc_blk", "blk",IF_TYPE_MMC, devnum,
        |   |                   512, 0, &bdev); // drivers/block/blk-uclass.c
        |   |-> blk_create_device(parent, "mmc_blk", dev_name, if_type,
        |       |                   devnum, blksz, lba, devp);
        |       |-> device_bind_driver(parent, drv_name, name, &dev);
        |           |   // drivers/core/lists.c
        |           |-> ....
        |               |-> device_bind_common(dm_root, ...);
        |                   |-> uclass_get(drv->id, &uc); id = UCLASS_BLK
        |                   |-> dev = calloc(1, sizeof(struct udevice));
        |                   |   dev->name = name; // 块设备名字
        |                   |   dev->parent = parent; // 指向 MMC 设备
        |                   |   dev->driver = drv; // "mmc_blk" driver
        |                   |   dev->uclass = uc; // UCLASS_BLK
        |                   |   // 创建设备 mmc_blk
        |                   |
        |                   |-> uclass_bind_device(dev);
        |                       // 将设备添加到 UCLASS_BLK 列表中
        |
        |-> dev_get_uclass_platdata(bdev);

3.8.7.3.2. Probe 流程

SPL 中的 Probe 流程如下。由于 BLK 设备的 probe 的目的是调用 mmc_init() , 这里直接调用,所以不需要单独的 Probe 了。

spl_mmc_load(); // common/spl/spl_mmc.c
|-> spl_mmc_find_device(&mmc, bootdev->boot_device);
|   |-> mmc_initialize(NULL); // drivers/mmc/mmc.c
|   |   |-> mmc_probe(bis);
|           |-> uclass_get(UCLASS_MMC, &uc);
|           |-> device_probe(dev); // drivers/core/device.c
|               |  // 这里对 UCLASS_MMC 列表中的设备逐个调用
|               |  // device_probe(dev)
|               |
|               |--> aic_dwmmc_probe(...) // 具体驱动的 probe
|
|-> mmc_init(mmc);

U-Boot 中 MMC 设备和对应的 BLK 设备 Probe 流程如下。

MMC 设备的 Probe 在 board_init_r() 调用 initr_mmc() 时进行。对应的 BLK 设备的 Probe 在第一次使用时进行,通常是 initr_env() ,该函数加载 MMC 上的环境变量。

board_init_r(gd_t *new_gd, ulong dest_addr)
|-> ...
|-> initr_dm(void)
|-> ...
|-> initr_mmc(void)
|-> initr_env(void)
initr_mmc(void)
|-> mmc_initialize(gd->bd); // drivers/mmc/mmc.c
    |-> mmc_probe(bis = gd->bd);
        |-> uclass_get(UCLASS_MMC, &uc);
        |-> device_probe(dev); // drivers/core/device.c
            |  // 这里对 UCLASS_MMC 列表中的设备逐个调用
            |  // device_probe(dev)
            |
            |--> aic_dwmmc_probe(...) // 具体驱动的 probe
initr_env(void) // common/board_r.c
|
|-> env_relocate(void) // env/common.c
    |-> env_load(void) // env/env.c
        | // 这个函数执行读取环境变量的动作
        |
        |-> drv = env_driver_lookup(ENVOP_LOAD, prio)
        |   // u-boot 通过 U_BOOT_ENV_LOCATION 宏定义了各种可以用于加载
        |   // 环境变量的驱动 (struct env_driver),并且在 lds 中将这些
        |   // 驱动收集到一个固定的段中,这里遍历各个驱动,尝试加载 ENV
        |
        |-> drv->load()/env_mmc_load(void) // env/mmc.c
            env_mmc_load();
                    |
    +---------------+
    |
env_mmc_load(); // env/mmc.c
|--> devno = mmc_get_env_dev();
|--> mmc = find_mmc_device(devno);
     |--> init_mmc_for_env(mmc)
          |--> blk_get_from_parent(mmc->dev, &dev)
               |--> device_find_first_child(parent, &blkdev);
               |    // 获取 mmc_blk 设备
               |
               |--> device_probe(blkdev)
                    |--> mmc_blk_probe(...) // drivers/mmc/mmc-uclass.c
                         |--> mmc_init(mmc) // drivers/mmc/mmc.c

3.8.7.3.3. 读写流程

通过 BLK 接口对 MMC 进行读写的调用流程如下。

blk_dread(mmc_get_blk_desc(mmc), blk, cnt, addr);
|    // drivers/block/blk-uclass.c
|
|--> ops->read(dev, start, blkcnt, buffer);
     mmc_bread(dev, start, blkcnt, buffer); // drivers/mmc/mmc.c
     |--> mmc_read_blocks(mmc, dst, start, cur); // drivers/mmc/mmc.c
          |--> mmc_send_cmd(mmc, &cmd, &data) //drivers/mmc/mmc-uclass.c
               |--> ops->send_cmd(dev, cmd, data);
                    dwmci_send_cmd(dev, cmd, data); // drivers/mmc/dw_mmc.c
blk_dwrite(mmc_get_blk_desc(mmc), blk, cnt, addr);
|    // drivers/block/blk-uclass.c
|
|--> ops->write(dev, start, blkcnt, buffer);
     mmc_bwrite(dev, start, blkcnt, buffer); // drivers/mmc/mmc_write.c
     |-> mmc_write_blocks(mmc, start, cur, src); // drivers/mmc/mmc_write.c
          |--> mmc_send_cmd(mmc, &cmd, &data) //drivers/mmc/mmc-uclass.c
               |--> ops->send_cmd(dev, cmd, data);
                    dwmci_send_cmd(dev, cmd, data); // drivers/mmc/dw_mmc.c