零、说明(重要,需要先搞清楚概念有助于后面的理解)

1、mmc core

card相关模块为对应card实现相应的操作,包括初始化操作、以及对应的总线操作集合。负责和对应card协议层相关的东西。

这里先学习mmc type card。后续再学习sd type card。

对应代码:

drivers/mmc/core/mmc.c(提供接口),
drivers/mmc/core/mmc-ops.c(提供和mmc type card协议相关的操作),
drivers/mmc/core/mmc-ops.h

2、另外,这里继续强调一下mmc的概念

mmc core是指mmc subsystem的核心实现,这里的mmc是表示mmc总线、接口、设备相关的一种统称,可以理解为一种软件架构。

而mmc type card则是指mmc卡或者emmc。

总之,这里的mmc是两种概念概念,需要自己先消化一下。

3、mmc总线和mmc_bus

在本文里面这两个是不同的概念。

mmc_bus是指mmc core抽象出来的虚拟总线,和mmc设备对应的硬件总线无关,是一种软件概念。

而本文的mmc总线是一种物理概念,是实际的总线,是和host controller直接相关联的。

一、API总览

1、mmc type card匹配相关

  • mmc_attach_mmc

提供给mmc core主模块使用,用于绑定card到host bus上(也就是card和host的绑定)。

通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。

    原型:int mmc_attach_mmc(struct mmc_host *host)

2、mmc type card协议相关操作

  • mmc_ops提供了部分和mmc type

card协议相关操作,这些操作会在mmc.c中mmc的初始化过程中被使用到。

建议先简单了解一下mmc协议的内容。后续会进行总结。

  • mmc_go_idle

发送CMD0指令,GO_IDLE_STATE

使mmc card进入idle state。

虽然进入到了Idle State,但是上电复位过程并不一定完成了,这主要靠读取OCR的busy位来判断,而流程归结为下一步。

  • mmc_send_op_cond

发送CMD1指令,SEND_OP_COND

这里会设置card的工作电压寄存器OCR,并且通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。

完成之后,mmc card进入ready state。

  • mmc_all_send_cid

这里会发送CMD2指令,ALL_SEND_CID

广播指令,使card回复对应的CID寄存器的值。在这里就相应获得了CID寄存器的值了,存储在cid中。

完成之后,MMC card会进入Identification State。

  • mmc_set_relative_addr

发送CMD3指令,SET_RELATIVE_ADDR

设置该mmc card的关联地址为card->rca,也就是0x0001

完成之后,该MMC card进入standby模式。

  • mmc_send_csd

发送CMD9指令,MMC_SEND_CSD

要求mmc card发送csd寄存器,存储到card->raw_csd中,也就是原始的csd寄存器的值。

此时mmc card还是处于standby state

  • mmc_select_card & mmc_deselect_cards

发送CMD7指令,SELECT/DESELECT CARD

选择或者断开指定的card

这时卡进入transfer state。后续可以通过各种指令进入到receive-data state或者sending-data state依次来进行数据的传输

  • mmc_get_ext_csd

发送CMD8指令,SEND_EXT_CSD

这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中

这里会使card进入sending-data state,完成之后又退出到transfer state。

  • mmc_switch

发送CMD6命令,MMC_SWITCH

用于设置ext_csd寄存器的某些bit

  • mmc_send_status

发送CMD13命令,MMC_SEND_STATUS

要求card发送自己当前的状态寄存器

  • mmc_send_cid

发送CMD10命令,MMC_SEND_CID

要求mmc card回复cid寄存器

  • mmc_card_sleepawake

发送CMD5命令,MMC_SLEEP_AWAKE

使card进入或者退出sleep state,由参数决定。关于sleep state是指card的一种状态,具体参考emmc 5.1协议。

先结合协议理解上述几个mmc type card的操作函数有助于理解后续mmc card的初始化代码。具体参考第五节。

二、数据结构

1、mmc_ops & mmc_ops_unsafe

struct mmc_bus_ops表示mmc host在总线上的操作集合,由host的card 设备来决定,mmc type card、sd type card相应的操作集合是不一样的。

mmc_ops和mmc_ops_unsafe则表示mmc type card所属的host对于总线的操作集合。

static const struct mmc_bus_ops mmc_ops = {
.awake = mmc_awake,
.sleep = mmc_sleep,
.remove = mmc_remove,
.detect = mmc_detect,
.suspend = NULL,
.resume = NULL,
.power_restore = mmc_power_restore,
.alive = mmc_alive,
.change_bus_speed = mmc_change_bus_speed,
}; static const struct mmc_bus_ops mmc_ops_unsafe = {
.awake = mmc_awake, // 使mmc总线上的mmc type card退出sleep state
.sleep = mmc_sleep, // 使mmc总线的mmc type card进入sleep state
.remove = mmc_remove, // 释放mmc type card
.detect = mmc_detect, // 检测mmc总线的mmc type card是否拔出
.suspend = mmc_suspend, // suspend掉mmc总线上的mmc type card,注意不仅仅会使card进入sleep state,还会对clock以及mmc cache进行操作
.resume = mmc_resume, // resume上mmc总线上的mmc type card
.power_restore = mmc_power_restore, // 恢复mmc总线上的mmc type card的电源状态
.alive = mmc_alive, // 检测mmc总线上的mmc type card状态是否正常
.change_bus_speed = mmc_change_bus_speed, // 修改mmc总线时钟频率
};

mmc_ops_unsafe和mmc_ops的区别在于是否实现suspend和resume方法。

对于card不可移除的host来说,需要使用mmc_ops_unsafe这个mmc_bus_ops来支持suspend和resume。

之所以在上述注释中不断说明mmc总线,是为了强调应该和mmc_bus虚拟总线区分开来,这里的mmc总线是物理概念、是和host controller直接相关联的。

2、mmc_type

struct device_type mmc_type中为mmc_card定义了很多属性,可以在sysfs中进行查看。

/sys/class/mmc_host/mmc0/mmc0:0001
或者/sys/bus/mmc/devices/mmc0:0001下可以查看到如下属性
block cid csd date driver enhanced_area_offset enhanced_area_size erase_size fwrev hwrev
manfid name oemid power preferred_erase_size prv raw_rpmb_size_mult rel_sectors
runtime_pm_timeout serial subsystem type uevent

mmc_type对应实现如下:

static struct device_type mmc_type = {
.groups = mmc_attr_groups,
}; static const struct attribute_group *mmc_attr_groups[] = {
&mmc_std_attr_group,
NULL,
}; static struct attribute_group mmc_std_attr_group = {
.attrs = mmc_std_attrs,
}; MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
card->raw_cid[2], card->raw_cid[3]);
MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
card->raw_csd[2], card->raw_csd[3]);
//...................略过一些
MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult);
MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors); static struct attribute *mmc_std_attrs[] = {
&dev_attr_cid.attr,
&dev_attr_csd.attr,
&dev_attr_date.attr,
&dev_attr_erase_size.attr,
&dev_attr_preferred_erase_size.attr,
&dev_attr_fwrev.attr,
&dev_attr_hwrev.attr,
//.....................略过一些
&dev_attr_rel_sectors.attr,
NULL,
};

补充说明,可以发现这些信息都是从mmc_card的cid寄存器和ext_csd寄存器中读取的。

三、接口代码说明

1、mmc_attach_mmc实现

用于通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。

  • 主要工作:

    • 设置总线模式

    • 选择一个card和host都支持的最低工作电压

    • 对于不同type的card,相应mmc总线上的操作协议也可能有所不同。所以需要设置相应的总线操作集合(mmc_host->bus_ops)

    • 初始化card使其进入工作状态(mmc_init_card)

    • 为card构造对应的mmc_card并且注册到mmc_bus中(mmc_add_card,具体参考《mmc core——bus模块说明》)

代码如下:

int mmc_attach_mmc(struct mmc_host *host)
{
int err;
u32 ocr; BUG_ON(!host);
WARN_ON(!host->claimed); /* Set correct bus mode for MMC before attempting attach */
/* 在尝试匹配之前,先设置正确的总线模式 */
if (!mmc_host_is_spi(host))
mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); /* 获取card的ocr寄存器 */
err = mmc_send_op_cond(host, 0, &ocr);
// 发送CMD1命令(MMC_SEND_OP_COND),并且参数为0
// 这里获取OCR(Operation condition register)32位的OCR包含卡设备支持的工作电压表,存储到ocr变量中
// 如果Host的IO电压可调整,那调整前需要读取OCR。为了不使卡误进入Inactive State,可以给MMC卡发送不带参数的CMD1,这样可以仅获取OCR寄存器,而不会改变卡的状态。 /* 对于不同type的card,相应mmc总线上的操作协议也可能有所不同 */
/* 所以这里设置mmc_host的总线操作集合,为mmc_ops_unsafe或者mmc_ops,上述已经说明 */
mmc_attach_bus_ops(host);
// 设置host->bus_ops,也就是会为host的bus选择一个操作集,对于non-removable的host来说,这里对应应该为mmc_ops_unsafe /* 为card选择一个HOST和card都支持的最低电压 */
if (host->ocr_avail_mmc)
host->ocr_avail = host->ocr_avail_mmc; // 选择mmc的可用ocr值作为host的ocr_avail值 if (ocr & 0x7F) {
ocr &= ~0x7F; // 在标准MMC协议中,OCR寄存器的bit6-0位是属于保留位,并不会使用,所以这里对应将其清零
}
host->ocr = mmc_select_voltage(host, ocr); // 通过OCR寄存器选择一个HOST和card都支持的最低电压 /* 调用mmc_init_card初始化该mmc type card,这里是核心函数,后续会继续说明 */
err = mmc_init_card(host, host->ocr, NULL); // 初始化该mmc type card,并为其分配和初始化一个对应的mmc_card
if (err)
goto err; /* 将分配到的mmc_card注册到mmc_bus中 */
mmc_release_host(host); // 先释放掉host,可能是在mmc_add_card中会获取这个host
err = mmc_add_card(host->card);
// 调用到mmc_add_card,将card注册到设备驱动模型中。
// 这时候该mmc_card就挂在了mmc_bus上,会和mmc_bus上的block这类mmc driver匹配起来。具体再学习mmc card driver的时候再说明。
mmc_claim_host(host); // 再次申请host
if (err)
goto remove_card; /* clock scaling相关的东西,这里暂时先不关心 */
mmc_init_clk_scaling(host); register_reboot_notifier(&host->card->reboot_notify); return 0; remove_card:
mmc_release_host(host);
mmc_remove_card(host->card);
mmc_claim_host(host);
host->card = NULL;
err:
mmc_detach_bus(host); pr_err("%s: error %d whilst initialising MMC card\n",
mmc_hostname(host), err); return err;
}

重点说明

(1)在attach过程中,有一个很重要的函数mmc_init_card,第四节就要围绕这个函数进行展开。

(2)调用了mmc_add_card之后mmc_card就挂在了mmc_bus上,会和mmc_bus上的block(mmc_driver)匹配起来。相应block(mmc_driver)就会进行probe,驱动card,实现card的实际功能(也就是存储设备的功能)。会对接到块设备子系统中。具体在学习mmc card driver的时候再说明。

四、mmc type card内部核心代码说明

1、mmc_init_card

在第三节中,可以看出mmc_attach_mmc中的一个核心函数就是mmc_init_card,用于对mmc type card进行实质性的初始化,并为其分配和初始化一个对应的mmc_card。这部分和协议相关,需要先学习一下mmc协议。

  • 主要工作

    • 根据协议初始化mmc type card,使其进入相应状态(standby state)
    • 为mmc type card构造对应mmc_card并进行设置
    • 从card的csd寄存器以及ext_csd寄存器获取card信息并设置到mmc_card的相应成员中
    • 根据host属性以及一些需求修改ext_csd寄存器的值
    • 设置mmc总线时钟频率以及位宽
  • 代码如下

static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
// struct mmc_host *host:该mmc card使用的host
// ocr:表示了host要使用的电压,在mmc_attach_mmc中,已经得到了一个HOST和card都支持的最低电压 struct mmc_card *card;
int err = 0;
u32 cid[4];
u32 rocr;
u8 *ext_csd = NULL; BUG_ON(!host);
WARN_ON(!host->claimed); /* Set correct bus mode for MMC before attempting init */
if (!mmc_host_is_spi(host))
mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); // 设置总线模式为开漏模式 /* 根据mmc协议从mmc总线上选中一张card(协议的初始化流程) */
mmc_go_idle(host);
// 发送CMD0指令,GO_IDLE_STATE
// 使mmc card进入idle state。
// 虽然进入到了Idle State,但是上电复位过程并不一定完成了,这主要靠读取OCR的busy位来判断,而流程归结为下一步。 /* The extra bit indicates that we support high capacity */
err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
// 发送CMD1指令,SEND_OP_COND
// 这里会设置card的工作电压寄存器OCR,并且通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。
// 完成之后,mmc card进入ready state。 /*
* Fetch CID from card.
*/
if (mmc_host_is_spi(host))
err = mmc_send_cid(host, cid);
else
err = mmc_all_send_cid(host, cid);
// 这里会发送CMD2指令,ALL_SEND_CID
// 广播指令,使card回复对应的CID寄存器的值。在这里就相应获得了CID寄存器的值了,存储在cid中。
// 完成之后,MMC card会进入Identification State。 if (oldcard) {
。。。
} else {
/* 调用mmc_alloc_card分配一个mmc_card并进行部分设置 */
card = mmc_alloc_card(host, &mmc_type);
// 为card配分一个struct mmc_card结构体并进行初始化,在mmc_type中为mmc定义了大量的属性。
// 具体参考“《mmc core——bus模块说明》——》mmc_alloc_card”
card->type = MMC_TYPE_MMC; // 设置card的type为MMC_TYPE_MMC
card->rca = 1; // 设置card的RCA地址为1
memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); // 将读到的CID存储到card->raw_cid,也就是原始CID值中
card->reboot_notify.notifier_call = mmc_reboot_notify;
host->card = card; // 将mmc_card和mmc_host 进行关联
} /* 设置card RCA地址 */
if (!mmc_host_is_spi(host)) {
err = mmc_set_relative_addr(card);
// 发送CMD3指令,SET_RELATIVE_ADDR
// 设置该mmc card的关联地址为card->rca,也就是0x0001
// 完成之后,该MMC card进入standby模式。 mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
// 设置总线模式为MMC_BUSMODE_PUSHPULL
} /* 从card的csd寄存器以及ext_csd寄存器获取信息并设置到mmc_card的相应成员中 */
if (!oldcard) {
/*
* Fetch CSD from card.
*/
err = mmc_send_csd(card, card->raw_csd);
// 发送CMD9指令,MMC_SEND_CSD
// 要求mmc card发送csd寄存器,存储到card->raw_csd中,也就是原始的csd寄存器的值。
// 此时mmc card还是处于standby state err = mmc_decode_csd(card);
// 解析raw_csd,获取到各个bit的值并设置到card->csd中的相应成员上 err = mmc_decode_cid(card);
// 解析raw_cid,获取到各个bit的值并设置到card->cid中的相应成员上
} /*
* Select card, as all following commands rely on that.
*/
if (!mmc_host_is_spi(host)) {
err = mmc_select_card(card);
// 发送CMD7指令,SELECT/DESELECT CARD
// 选择或者断开指定的card
// 这时卡进入transfer state。后续可以通过各种指令进入到receive-data state或者sending-data state依次来进行数据的传输
} if (!oldcard) {
err = mmc_get_ext_csd(card, &ext_csd);
// 发送CMD8指令,SEND_EXT_CSD
// 这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中
// 这里会使card进入sending-data state,完成之后又退出到transfer state。 card->cached_ext_csd = ext_csd; // 将ext_csd原始值存储到card->cached_ext_csd,表示用来保存ext_csd的一块缓存,可能还没有和card的ext_csd同步
err = mmc_read_ext_csd(card, ext_csd); // 解析ext_csd的值,获取到各个bit的值并设置到card->ext_csd中的相应成员上 if (!(mmc_card_blockaddr(card)) && (rocr & (1<<30)))
mmc_card_set_blockaddr(card); /* Erase size depends on CSD and Extended CSD */
mmc_set_erase_size(card); // 设置card的erase_size,扇区里面的擦除字节数,读出来是512K if (card->ext_csd.sectors && (rocr & MMC_CARD_SECTOR_ADDR))
mmc_card_set_blockaddr(card);
} /* 根据host属性以及一些需求修改ext_csd寄存器的值 */
/*
* If enhanced_area_en is TRUE, host needs to enable ERASE_GRP_DEF
* bit. This bit will be lost every time after a reset or power off.
*/
if (card->ext_csd.enhanced_area_en ||
(card->ext_csd.rev >= 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) {
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_ERASE_GROUP_DEF, 1,
card->ext_csd.generic_cmd6_time);
// 发送CMD6命令,MMC_SWITCH
// 用于设置ext_csd寄存器的某些bit
// 当enhanced_area_en 被设置的时候,host需要去设置ext_csd寄存器中的EXT_CSD_ERASE_GROUP_DEF位为1
} if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) {
card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,
card->ext_csd.part_config,
card->ext_csd.part_time);
// 发送CMD6命令,MMC_SWITCH
// 用于设置ext_csd寄存器的某些bit
// 设置ext_csd寄存器中的EXT_CSD_CMD_SET_NORMAL位为EXT_CSD_PART_CONFIG
card->part_curr = card->ext_csd.part_config &
EXT_CSD_PART_CONFIG_ACC_MASK;
} if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) &&
(card->ext_csd.rev >= 6)) {
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_POWER_OFF_NOTIFICATION,
EXT_CSD_POWER_ON,
card->ext_csd.generic_cmd6_time);
// 发送CMD6命令,MMC_SWITCH
// 用于设置ext_csd寄存器的某些bit
// 设置ext_csd寄存器中的EXT_CSD_POWER_OFF_NOTIFICATION位为EXT_CSD_POWER_ON
} /* 设置mmc总线时钟频率以及位宽 */
err = mmc_select_bus_speed(card, ext_csd); // 激活host和card都支持的最大总线速度
//.........这里过滤掉一些设置ext_csd的代码
if (!oldcard) { if (card->ext_csd.bkops_en) {
INIT_DELAYED_WORK(&card->bkops_info.dw,
mmc_start_idle_time_bkops);
// 如果emmc支持bkops的话,就初始化card->bkops_info.dw工作为mmc_start_idle_time_bkops
}
} return 0;
}

后续会有一篇文章《结合log分析emmc初始化过程中的命令流程》来说明上述mmc_init_card的实际流程。

2、mmc_ops_unsafe相关函数实现

选择几个重点的进行说明:

static const struct mmc_bus_ops mmc_ops_unsafe = {
.awake = mmc_awake, // 使mmc总线上的mmc type card退出sleep state
.sleep = mmc_sleep, // 使mmc总线的mmc type card进入sleep state
.remove = mmc_remove, // 释放mmc type card
.detect = mmc_detect, // 检测mmc总线的mmc type card是否拔出
.suspend = mmc_suspend, // suspend掉mmc总线上的mmc type card,注意不仅仅会使card进入sleep state,还会对clock以及mmc cache进行操作
.resume = mmc_resume, // resume上mmc总线上的mmc type card
.power_restore = mmc_power_restore, // 恢复mmc总线上的mmc type card的电源状态
.alive = mmc_alive, // 检测mmc总线上的mmc type card状态是否正常
.change_bus_speed = mmc_change_bus_speed, // 修改mmc总线时钟频率
}; /**********************使mmc总线上的mmc type card退出sleep state************************/
static int mmc_awake(struct mmc_host *host)
{
//...
if (card && card->ext_csd.rev >= 3) { // 判断版本是否大于3
err = mmc_card_sleepawake(host, 0);
// 发送CMD5指令,MMC_SLEEP_AWAKE,参数为0,表示退出sleep state.(如果参数为1就是进入sleep state)
// 完成之后,该MMC card从sleep state进入standby模式。
}
//...
} /**********************检测mmc总线的mmc type card是否拔出************************/
static void mmc_detect(struct mmc_host *host)
{
int err;
mmc_rpm_hold(host, &host->card->dev);
mmc_claim_host(host); /* 检测card是否被拔出 */
err = _mmc_detect_card_removed(host); mmc_release_host(host); /*
* if detect fails, the device would be removed anyway;
* the rpm framework would mark the device state suspended.
*/
/* card并没有被拔出,说明出现异常了,标记card的rpm状态为suspend */
if (!err)
mmc_rpm_release(host, &host->card->dev); /* card确实被拔出,正常释放card */
if (err) {
mmc_remove(host); mmc_claim_host(host);
mmc_detach_bus(host);
mmc_power_off(host);
mmc_release_host(host);
}
} /********************** 修改mmc总线时钟频率************************/
/**
* mmc_change_bus_speed() - Change MMC card bus frequency at runtime
* @host: pointer to mmc host structure
* @freq: pointer to desired frequency to be set
*
* Change the MMC card bus frequency at runtime after the card is
* initialized. Callers are expected to make sure of the card's
* state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime.
*/
static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq)
{
int err = 0;
struct mmc_card *card; mmc_claim_host(host);
/*
* Assign card pointer after claiming host to avoid race
* conditions that may arise during removal of the card.
*/
card = host->card; if (!card || !freq) {
err = -EINVAL;
goto out;
} /* 确定出一个可用频率 */
if (mmc_card_highspeed(card) || mmc_card_hs200(card)
|| mmc_card_ddr_mode(card)
|| mmc_card_hs400(card)) {
if (*freq > card->ext_csd.hs_max_dtr)
*freq = card->ext_csd.hs_max_dtr;
} else if (*freq > card->csd.max_dtr) {
*freq = card->csd.max_dtr;
} if (*freq < host->f_min)
*freq = host->f_min; /* 根据实际要设置的频率值来设置时钟 */
if (mmc_card_hs400(card)) {
err = mmc_set_clock_bus_speed(card, *freq);
if (err)
goto out;
} else {
mmc_set_clock(host, (unsigned int) (*freq));
} /* 对于hs200来说,修改完频率之后需要执行execute_tuning来选择一个合适的采样点 */
if (mmc_card_hs200(card) && card->host->ops->execute_tuning) {
/*
* We try to probe host driver for tuning for any
* frequency, it is host driver responsibility to
* perform actual tuning only when required.
*/
mmc_host_clk_hold(card->host);
err = card->host->ops->execute_tuning(card->host,
MMC_SEND_TUNING_BLOCK_HS200);
mmc_host_clk_release(card->host); if (err) {
pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n",
mmc_hostname(card->host), __func__, err,
host->clk_scaling.curr_freq);
mmc_set_clock(host, host->clk_scaling.curr_freq); // 采样失败,设置回原来的时钟频率
}
}
out:
mmc_release_host(host);
return err;
}

五、mmc ops接口说明

1、说明

  • mmc_ops提供了部分和mmc type card协议相关操作,这些操作会在mmc.c中mmc的初始化过程中被使用到。
  • 这些操作都会发起mmc请求,因此会调用mmc core主模块的mmc请求API,会在mmc core中进行说明。
  • 建议先简单了解一下mmc协议的内容。后续会进行总结。

2、代码说明

以下说明比较典型和比较特殊的接口

  • mmc_send_status(典型)

发送CMD13命令,MMC_SEND_STATUS

要求card发送自己当前的状态寄存器

int mmc_send_status(struct mmc_card *card, u32 *status)
{
int err;
struct mmc_command cmd = {0}; BUG_ON(!card);
BUG_ON(!card->host); /* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = MMC_SEND_STATUS; // 设置命令操作码opcode,这里设置为MMC_SEND_STATUS,也就是CMD13
if (!mmc_host_is_spi(card->host))
cmd.arg = card->rca << 16; // 设置命令的对应参数,这里设置为card的RCA地址
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; // 设置请求的一些标识,包括命令类型,response类型等等 /* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
if (err)
return err; /* NOTE: callers are required to understand the difference
* between "native" and SPI format status words!
*/
/* 对response的处理 */
if (status)
*status = cmd.resp[0]; // 依照协议,response[0]存储了status,因此这里提取cmd.resp[0] return 0;
}
  • mmc_send_op_cond(特殊)

发送CMD1指令,SEND_OP_COND

在idle状态时,向卡传送Host支持的电压范围,卡回复OCR的值以及上电复位的状态。如果发送的电压参数为0,则卡仅传回OCR的值,并不进行判断。如果发送的电压参数存在,则和卡本身的OCR对比,若不符合,则卡进入Inactive State,符合,则返回OCR寄存器的值。

其实和典型的接口类似,但是其特殊之处在于需要通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。

int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd = {0};
int i, err = 0; BUG_ON(!host); /* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = MMC_SEND_OP_COND;
cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR; /* 需要判断status的busy(bit31)来判断上电复位是否完成,如果没有完成的话需要重复发送。 */
for (i = 100; i; i--) {
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
break; /* if we're just probing, do a single pass */
if (ocr == 0) // ocr为0,说明只是读取ocr寄存器的值,不进行判断
break; /* otherwise wait until reset completes */
if (mmc_host_is_spi(host)) {
if (!(cmd.resp[0] & R1_SPI_IDLE))
break;
} else {
if (cmd.resp[0] & MMC_CARD_BUSY)
break;
// 如果发送的电压参数存在,则和卡本身的OCR对比,若不符合,则卡进入Inactive State,符合,则返回OCR寄存器的值。
// 同时,需要判断OCR寄存器的busy位来判断上电复位是否完成。
} err = -ETIMEDOUT; mmc_delay(10);
} if (rocr && !mmc_host_is_spi(host))
*rocr = cmd.resp[0]; return err;
}
  • mmc_send_ext_csd

发送CMD8指令,SEND_EXT_CSD

这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中

这里会使card进入sending-data state,完成之后又退出到transfer state。

特殊之处在于涉及到了DATA线上的数据传输,会调用到内部接口mmc_send_cxd_data。

如下:

int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
unsigned int timeout_ms)
{
return __mmc_switch(card, set, index, value, timeout_ms, true, false);
} int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
unsigned int timeout_ms, bool use_busy_signal,
bool ignore_timeout)
{
int err;
struct mmc_command cmd = {0};
unsigned long timeout;
u32 status; BUG_ON(!card);
BUG_ON(!card->host); /* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = MMC_SWITCH;
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
(index << 16) |
(value << 8) |
set;
cmd.flags = MMC_CMD_AC;
if (use_busy_signal)
cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
else
cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
cmd.cmd_timeout_ms = timeout_ms;
cmd.ignore_timeout = ignore_timeout; /* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
if (err)
return err; /* No need to check card status in case of unblocking command */
if (!use_busy_signal)
return 0; /* 调用mmc_send_status发送CMD13获取card status,等待card退出programming state。 */
timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
do {
err = mmc_send_status(card, &status);
if (err)
return err;
if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
break;
if (mmc_host_is_spi(card->host))
break; /* Timeout if the device never leaves the program state. */
if (time_after(jiffies, timeout)) {
pr_err("%s: Card stuck in programming state! %s\n",
mmc_hostname(card->host), __func__);
return -ETIMEDOUT;
}
} while (R1_CURRENT_STATE(status) == R1_STATE_PRG); if (mmc_host_is_spi(card->host)) {
if (status & R1_SPI_ILLEGAL_COMMAND)
return -EBADMSG;
} else {
if (status & 0xFDFFA000)
pr_warning("%s: unexpected status %#x after "
"switch", mmc_hostname(card->host), status);
if (status & R1_SWITCH_ERROR)
return -EBADMSG;
} return 0;
}

后续会有一篇文章《结合log分析emmc初始化过程中的命令流程》来说明上述mmc_init_card的实际流程。

5. [mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)的更多相关文章

  1. 10. [mmc subsystem] host(第四章)——host实例(sdhci-msm说明)

    一.说明 sdhci-msm是指高通的mmc host,其使用了标准SDHC标准.故可以使用前面说的<host(第二章)--sdhci>和<host(第三章)--sdhci-pltf ...

  2. 9. [mmc subsystem] host(第三章)——sdhci-pltfm说明

    一.sdhci-pltfm说明 sdhci-pltfm并不是实际某个host的driver. sdhci-pltfm是指在sdhci core的基础上,提供了统一对sdhci_host的必要属性进行解 ...

  3. 深度学习框架PyTorch一书的学习-第五章-常用工具模块

    https://github.com/chenyuntc/pytorch-book/blob/v1.0/chapter5-常用工具/chapter5.ipynb 希望大家直接到上面的网址去查看代码,下 ...

  4. java并发编程实战:第五章----基础构建模块

    委托是创建线程安全类的一个最有效的策略:只需让现有的线程安全类管理所有的状态即可. 一.同步容器类 1.同步容器类的问题 同步容器类都是线程安全的,容器本身内置的复合操作能够保证原子性,但是当在其上进 ...

  5. Kettle解决方案: 第五章 ETL相关知识

    早期, ETL知识作为BI系统的一部分来介绍. 后来在The Data Warehouse ETL Tooket一书中, 系统性的整理了ETL的相关内容, 形成了一篇"ETL里的34个子系统 ...

  6. 6. [mmc subsystem] mmc core(第六章)——mmc core主模块

    一.说明 1.mmc core概述 mmc core主模块是mmc core的实现核心.也是本章的重点内容. 对应代码位置drivers/mmc/core/core.c. 其主要负责如下功能: mmc ...

  7. 1. [mmc subsystem] 概念与框架

    一.概念 1.mmc的概念 mmc有很多种意义,具体如下: mmc MultiMedia Card,多媒体存储卡, 但后续泛指一个接口协定(一种卡式),能符合这接口的内存器都可称作mmc储存体. 主要 ...

  8. 3. [mmc subsystem] mmc core(第三章)——bus模块说明

    零.说明 对应代码drivers/mmc/core/bus.c. 抽象出虚拟mmc bus,实现mmc bus的操作. 一.API总览 1.mmc bus相关 mmc_register_bus &am ...

  9. 8. [mmc subsystem] host(第二章)——sdhci

    一.sdhci core说明 1.sdhci说明 具体参考<host(第一章)--概述> SDHC:Secure Digital(SD) Host Controller,是指一套sd ho ...

随机推荐

  1. Python入门基础学习(列表/元组/字典/集合)

    Python基础学习笔记(二) 列表list---[ ](打了激素的数组,可以放入混合类型) list1 = [1,2,'请多指教',0.5] 公共的功能: len(list1) #/获取元素 lis ...

  2. nginx代理ambassador出现426错误

    现在ambassador文档啃得差不多了.进入实战阶段. 一开始,就偶遇426错误. 网络结构大致如下: 浏览器访问nginx, nginx代理到k8s内的ambassador, ambassador ...

  3. 02vuex-modules

    01===> module的理解:将一个大的系统进行拆分 拆分成若干个细的模块 给个模块都有自己的 state mutations 等属性 这样可以在自己的小模块中进行修改 方便维护 modul ...

  4. jQuery中Ajax(三)

    1. jQuery.ajaxSetup([options]), 设置全局 AJAX 默认选项. 参数见 'jQuery.ajax(url,[settings])' 说明. 2. jQuery.ajax ...

  5. Keras:

    https://keras.io/zh/layers/core/ keras使用稀疏输入进行训练 2018.06.14 12:55:46字数 902阅读 760 稀疏矩阵 稀疏矩阵是指矩阵中数值为0的 ...

  6. 补充: Nginx

    1. 定义: Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器: 特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现 ...

  7. MySQL实战45讲学习笔记:第四十三讲

    一.本节概述 我经常被问到这样一个问题:分区表有什么问题,为什么公司规范不让使用分区表呢?今天,我们就来聊聊分区表的使用行为,然后再一起回答这个问题. 二.分区表是什么? 为了说明分区表的组织形式,我 ...

  8. Kubernetes集群的安全机制

    集群的安全性需要考虑以下几个目标: 1.保证容器与其所在宿主机的隔离 2.限制容器给基础设施及其他容器带来的消极影响的能力 3.最小权限原则——合理限制所有组件的权限,确保组件只执行它被授权的行为 4 ...

  9. [2019BUAA软工助教]助教学期总结

    [2019BUAA软工助教]助教学期总结 一.量化自评 线上 博客点评:https://www.cnblogs.com/ChildishChange/MyComments.html 共 106 条 博 ...

  10. LeetCode 225:用队列实现栈 Implement Stack using Queues

    题目: 使用队列实现栈的下列操作: push(x) -- 元素 x 入栈 pop() -- 移除栈顶元素 top() -- 获取栈顶元素 empty() -- 返回栈是否为空 Implement th ...