MMC
本章节描述 MMC 驱动的相关配置和使用。
驱动框架
U-Boot 驱动模型支持 MMC,并且通过块设备接口对 MMC 进行访问。ArtInChip 平台中, SPL 和 U-Boot 阶段都支持 MMC 已经块设备接口。 相关配置为:
-
CONFIG_MMC
-
CONFIG_DM_MMC
-
CONFIG_SPL_DM_MMC
-
CONFIG_BLK
-
CONFIG_SPL_BLK
-
CONFIG_MMC_ARTINCHIP
相关源码有:
-
include/mmc.h
-
include/blk.h
-
drivers/block/blk-uclass.c
-
drivers/mmc/mmc-uclass.c
-
drivers/mmc/artinchip_mmc.c
驱动接口
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);
初始化和使用
本章节主要介绍 MMC 以及对应的 BLK 设备的初始化流程,以及读写流程。
绑定阶段
使用时,MMC 设备通过 BLK 块设备接口进行使用。MMC 设备与 BLK 设备之间的关系如 所示。
对于每一个 MMC 设备,在绑定阶段,都会创建一个对应的 MMC_BLK 块设备,并且 MMC_BLK 设备的 parent 指向当前 MMC 设备。 ArtInChip MMC 设备绑定对应的 MMC 设备驱动, MMC_BLK 设备绑定 mmc-uclass 中的 mmc_blk 驱动。
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 时
reset // arch/arm/cpu/armv7/start.S |-> _main // arch/arm/lib/crt0.S |-> board_init_f(); // arch/arm/mach-artinchip/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/artinchip_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() 中处理。
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/artinchip_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);
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 流程如下。
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
读写流程
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