MMC子系统

参考 https://blog.csdn.net/weixin_42262944/category_9758652.html

https://blog.csdn.net/lwj103862095/article/details/38335335

MCI(Multimedia Card Interface)

emmc,SDMMC,sdio是MMC在实际物理接口上的三种应用形式.

分为host,core,card三个驱动层次,
MMC核心层:完成不同协议和规范的实现,为host和设备层提供接口函数.核心层由三个部分组成:MMC,SD,SDIO.分别为三类设备驱动提供接口函数.
host负责控制器的驱动,host驱动要跟进SOC特性实现.
Client设备层:针对不同设备(SD卡,sdio wifi,EMMC存储)的设备驱动.

host;
rk3288.dtsi中sdmmc控制器的定义
sdmmc: dwmmc@ff0c0000 {
compatible = “rockchip,rk3288-dw-mshc”;
clock-freq-min-max = <400000 150000000>;
clocks = <&cru HCLK_SDMMC>, <&cru SCLK_SDMMC>,
<&cru SCLK_SDMMC_DRV>, <&cru SCLK_SDMMC_SAMPLE>;
clock-names = “biu”, “ciu”, “ciu-drive”, “ciu-sample”;
fifo-depth = <0x100>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
reg = <0x0 0xff0c0000 0x0 0x4000>;
status = “disabled”;
};

我们知道,对于控制器这种platform_device,驱动的主要工作就是用一种结构抽象这种device,并且实现一套适用所有控制器的驱动代码,解析硬件dts节点,并且为外部设备提供api,以使用控制器这一硬件资源.

Linux kernel将host抽象为mmc_host,具体的SOC,会根据实际情况再次封装(比如rk3288采用新思designwave的IP),就重新封装为struct dw_mci.

驱动代码:dw_mmc-rockchip.c
host驱动probe中主要做了如下工作:
1.定义了struct dw_mci *host变量,并且根据dts定义初始化之,然后将这个变量赋值 给struct mmc_host 结构变量.并且定义struct mmc_host_ops *ops函数操作集.总的来说:a.调用mmc_alloc_host,分配一个struct mmc_host变量.b.根据硬件特性,填充struct mmc_host;变量的各个字段,例如MMC类型,电压范围,操作函数集等.3.调用mmc_add_host接口,将host组成到MMC core中.
2.为host实现了中断服务器函数,并定义tasklet,以及几个timer回调函数,解析host的状态.mmc_add_host()方法的最后,会激活卡检测的工作队列,循环检测卡状态.
3.实现本地mmm_host_ops 函数集,比如,对于rk3288 soc,定义如下:
static const struct mmc_host_ops dw_mci_ops = {
.request = dw_mci_request,
.pre_req = dw_mci_pre_req,
.post_req = dw_mci_post_req,
.set_ios = dw_mci_set_ios,
.set_sdio_status = dw_mci_set_sdio_status,
.get_ro = dw_mci_get_ro,
.get_cd = dw_mci_get_cd,
.enable_sdio_irq = dw_mci_enable_sdio_irq,
.execute_tuning = dw_mci_execute_tuning,
.card_busy = dw_mci_card_busy,
.start_signal_voltage_switch = dw_mci_switch_voltage,
.init_card = dw_mci_init_card,
.prepare_hs400_tuning = dw_mci_prepare_hs400_tuning,
};
mmc数据的读写,涉及到的结构体:struct request;struct struct command,struct mmmc_data.MMC的数据读写,以命令和请求的方式发送给控制器,控制其返回数据.数据通过存在ioremap的物理地址或者放到DMA,上,然后再传给用户层.

EMMC/SD/SDIO设备的检测以及初始化
如上所说,当mmc_alloc_host回申请workqueue,mmc_add_host()的最后,会激活这个workqueue,使其不断检测MMC设备:mmc_rescan->mmc_rescan_try_freq.这里面会使用不同的频率分别给不同的host发出读设备命令,根据返回值确认设备的类型,然后再分别具体初始化.
if (host->restrict_caps &
(RESTRICT_CARD_TYPE_SDIO | RESTRICT_CARD_TYPE_SD))
mmc_send_if_cond(host, host->ocr_avail);
/* Order’s important: probe SDIO, then SD, then MMC */
if ((host->restrict_caps & RESTRICT_CARD_TYPE_SDIO) &&
!mmc_attach_sdio(host))
return 0;
if ((host->restrict_caps & RESTRICT_CARD_TYPE_SD) &&
!mmc_attach_sd(host))
return 0;
if ((host->restrict_caps & RESTRICT_CARD_TYPE_EMMC) &&
!mmc_attach_mmc(host))
return 0;
以上接口在sdio.c,mmc.c,sd.c中实现.
以sd.c中的mmc_atach_sd()为例
int mmc_attach_sd(struct mmc_host *host)
{…
err =mmc_attach_bus(host, &mmc_sd_ops);
err = mmc_sd_init_card(host, rocr, NULL);.//card->type = MMC_TYPE_SD; 指定了type为MMC_TYPE_SD,又在mmc_sd_init_card()中指定了card->dev.bus = &mmc_bus_type;
err = mmc_add_card(host->card);
}
mmc_add_card里面会先对type做判断,然后调用device_add()
int mmc_add_card(struct mmc_card *card)
{
switch (card->type) {
case MMC_TYPE_MMC:
type = “MMC”;
break;
case MMC_TYPE_SD:
type = “SD”;
if (mmc_card_blockaddr(card)) {
if (mmc_card_ext_capacity(card))
type = “SDXC”;
else
type = “SDHC”;
}
break;
case MMC_TYPE_SDIO:
type = “SDIO”;
break;
case MMC_TYPE_SD_COMBO:
type = “SD-combo”;
if (mmc_card_blockaddr(card))
type = “SDHC-combo”;
break;
default:
type = “?”;
break;
}
device_add(&card->dev);
}
我们指定device_add()实现里面会调用bus的mactch函数,如果match ok会调用bus->probe–>drv->probe().
因为在mmc_bus_probe里面,Linux把挂载到mmc_bus_type总线上的设备驱动都转成了mmc_driver.那么mmc_driver的probe又是谁呢?
直接搜索struct mmc_driver关键字,可以在block.c中找到,
故,这个probe就是block.c中的mmc_blk_probe().
综上所述,mmc_attach_sd/mmm_attach_mmc最后都会对应到block.c这个driver上来.
static int mmc_blk_probe(struct mmc_card *card)
{
md = mmc_blk_alloc(card);
if (mmc_blk_alloc_parts(card, md))
if (mmc_add_disk(md))
}
mmc_driver->probe中主要包含了alloc-disk和出书和队列的,然后调用mmc_add_disk实现块设备的注册.注册之后,即可在/dev/mmcblkx下找到对应的分区信息.然后mount到文件系统节点,即完成了块设备的加载.

同理,对于mmc_attach_sdio().经过一系列初始化等之后,会调用sdio_but_type的match方法(sdio_bus.c),这个match会根据id_table进行匹配,然后调用这个包含具体id_table的probe.(在sdio驱动注册的时候,会指定driver的id_table,然后和设备加载时候,系统读到的硬件id相信匹配.),然后实现具体的硬件功能,比如将模块注册驱动为网卡设备.

2.client端.
如上sd和sdio过程可知,Linux kernel将MMC card具体设备(比如EMMC,SD卡)抽象为strcut mmc_card.其驱动抽象为struct mmc_driver.以driver/mmc/block.c为例.

至此,我们就走通了SD卡从被检测到被组成为blk设备的过程.
同理也可以理解,SDIO 接口的wifi 模块被识别或者被注册成网卡的过程.

MMC还是有些代码的,有个卡点是,SD卡在mmc_sd_init_card里面其设备总线被设备成了mmc_bus_type,然后在这个总线的match中,driver又被强制转成了mmc_driver.最后调用mmc_driver–>probe.

SD卡的热插拔:
SD卡插拔的时候,会导致MCI中断发生,中断服务函数dw_mci_interrupt会读取MCI寄存器状态,如果判断是CD-GPIO状态变化,调用dw_mci_handle_cd,这函数会schedule delayed work,运行mmc_rescan.

if (pending & SDMMC_INT_CD) {
mci_writel(host, RINTSTS, SDMMC_INT_CD);
dw_mci_handle_cd(host);–>mmc_detect_change—>mmc_schedule_delayed_work---->mmc_rescan
}

暂时写道这里,如果又大佬看了我的笔记,有错误的请指出.

驱动交流:QQ 1617890720

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九月天-深圳专业软硬件开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值