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 所示。
对于每一个 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