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