热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

5.[mmcsubsystem]mmccore(第五章)——card相关模块(mmctypecard)

零、说明(重要,需要先搞清楚概念有助于后面的理解)1、mmccorecard相关模块为对应card实现相应的操作,包括初始化

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

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_cardif (err)goto err;/* 将分配到的mmc_card注册到mmc_bus中 */mmc_release_host(host); // 先释放掉host,可能是在mmc_add_card中会获取这个hosterr = 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); // 再次申请hostif (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&#xff1a;表示了host要使用的电压&#xff0c;在mmc_attach_mmc中&#xff0c;已经得到了一个HOST和card都支持的最低电压 struct mmc_card *card;int err &#61; 0;u32 cid[4];u32 rocr;u8 *ext_csd &#61; 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&#xff08;协议的初始化流程&#xff09; */mmc_go_idle(host);// 发送CMD0指令&#xff0c;GO_IDLE_STATE// 使mmc card进入idle state。// 虽然进入到了Idle State&#xff0c;但是上电复位过程并不一定完成了&#xff0c;这主要靠读取OCR的busy位来判断&#xff0c;而流程归结为下一步。/* The extra bit indicates that we support high capacity */err &#61; mmc_send_op_cond(host, ocr | (1 <<30), &rocr);// 发送CMD1指令&#xff0c;SEND_OP_COND// 这里会设置card的工作电压寄存器OCR&#xff0c;并且通过busy位&#xff08;bit31&#xff09;来判断card的上电复位过程是否完成&#xff0c;如果没有完成的话需要重复发送。// 完成之后&#xff0c;mmc card进入ready state。/** Fetch CID from card.*/if (mmc_host_is_spi(host))err &#61; mmc_send_cid(host, cid);elseerr &#61; mmc_all_send_cid(host, cid);// 这里会发送CMD2指令&#xff0c;ALL_SEND_CID// 广播指令&#xff0c;使card回复对应的CID寄存器的值。在这里就相应获得了CID寄存器的值了&#xff0c;存储在cid中。// 完成之后&#xff0c;MMC card会进入Identification State。if (oldcard) {
。。。} else {
/* 调用mmc_alloc_card分配一个mmc_card并进行部分设置 */card &#61; mmc_alloc_card(host, &mmc_type); // 为card配分一个struct mmc_card结构体并进行初始化&#xff0c;在mmc_type中为mmc定义了大量的属性。// 具体参考“《mmc core——bus模块说明》——》mmc_alloc_card”card->type &#61; MMC_TYPE_MMC; // 设置card的type为MMC_TYPE_MMCcard->rca &#61; 1; // 设置card的RCA地址为1memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); // 将读到的CID存储到card->raw_cid&#xff0c;也就是原始CID值中card->reboot_notify.notifier_call &#61; mmc_reboot_notify;host->card &#61; card; // 将mmc_card和mmc_host 进行关联}/* 设置card RCA地址 */if (!mmc_host_is_spi(host)) {err &#61; mmc_set_relative_addr(card);// 发送CMD3指令&#xff0c;SET_RELATIVE_ADDR// 设置该mmc card的关联地址为card->rca&#xff0c;也就是0x0001// 完成之后&#xff0c;该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 &#61; mmc_send_csd(card, card->raw_csd);// 发送CMD9指令&#xff0c;MMC_SEND_CSD// 要求mmc card发送csd寄存器&#xff0c;存储到card->raw_csd中&#xff0c;也就是原始的csd寄存器的值。// 此时mmc card还是处于standby stateerr &#61; mmc_decode_csd(card);// 解析raw_csd&#xff0c;获取到各个bit的值并设置到card->csd中的相应成员上err &#61; mmc_decode_cid(card);// 解析raw_cid&#xff0c;获取到各个bit的值并设置到card->cid中的相应成员上}/** Select card, as all following commands rely on that.*/if (!mmc_host_is_spi(host)) {err &#61; mmc_select_card(card);// 发送CMD7指令&#xff0c;SELECT/DESELECT CARD// 选择或者断开指定的card// 这时卡进入transfer state。后续可以通过各种指令进入到receive-data state或者sending-data state依次来进行数据的传输}if (!oldcard) {err &#61; mmc_get_ext_csd(card, &ext_csd);// 发送CMD8指令&#xff0c;SEND_EXT_CSD// 这里要求处于transfer state的card发送ext_csd寄存器&#xff0c;这里获取之后存放在ext_csd寄存器中// 这里会使card进入sending-data state&#xff0c;完成之后又退出到transfer state。card->cached_ext_csd &#61; ext_csd; // 将ext_csd原始值存储到card->cached_ext_csd&#xff0c;表示用来保存ext_csd的一块缓存&#xff0c;可能还没有和card的ext_csd同步err &#61; mmc_read_ext_csd(card, ext_csd); // 解析ext_csd的值&#xff0c;获取到各个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&#xff0c;扇区里面的擦除字节数&#xff0c;读出来是512Kif (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 >&#61; 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) {err &#61; mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,EXT_CSD_ERASE_GROUP_DEF, 1,card->ext_csd.generic_cmd6_time);// 发送CMD6命令&#xff0c;MMC_SWITCH// 用于设置ext_csd寄存器的某些bit// 当enhanced_area_en 被设置的时候&#xff0c;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 &&#61; ~EXT_CSD_PART_CONFIG_ACC_MASK;err &#61; mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,card->ext_csd.part_config,card->ext_csd.part_time);// 发送CMD6命令&#xff0c;MMC_SWITCH// 用于设置ext_csd寄存器的某些bit// 设置ext_csd寄存器中的EXT_CSD_CMD_SET_NORMAL位为EXT_CSD_PART_CONFIGcard->part_curr &#61; card->ext_csd.part_config &EXT_CSD_PART_CONFIG_ACC_MASK;}if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) &&(card->ext_csd.rev >&#61; 6)) {err &#61; mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,EXT_CSD_POWER_OFF_NOTIFICATION,EXT_CSD_POWER_ON,card->ext_csd.generic_cmd6_time);// 发送CMD6命令&#xff0c;MMC_SWITCH// 用于设置ext_csd寄存器的某些bit// 设置ext_csd寄存器中的EXT_CSD_POWER_OFF_NOTIFICATION位为EXT_CSD_POWER_ON}/* 设置mmc总线时钟频率以及位宽 */err &#61; 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的话&#xff0c;就初始化card->bkops_info.dw工作为mmc_start_idle_time_bkops}}return 0;
}

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

2、mmc_ops_unsafe相关函数实现

选择几个重点的进行说明&#xff1a;

static const struct mmc_bus_ops mmc_ops_unsafe &#61; {.awake &#61; mmc_awake, // 使mmc总线上的mmc type card退出sleep state.sleep &#61; mmc_sleep, // 使mmc总线的mmc type card进入sleep state.remove &#61; mmc_remove, // 释放mmc type card.detect &#61; mmc_detect, // 检测mmc总线的mmc type card是否拔出.suspend &#61; mmc_suspend, // suspend掉mmc总线上的mmc type card&#xff0c;注意不仅仅会使card进入sleep state&#xff0c;还会对clock以及mmc cache进行操作.resume &#61; mmc_resume, // resume上mmc总线上的mmc type card.power_restore &#61; mmc_power_restore, // 恢复mmc总线上的mmc type card的电源状态.alive &#61; mmc_alive, // 检测mmc总线上的mmc type card状态是否正常.change_bus_speed &#61; 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 >&#61; 3) { // 判断版本是否大于3err &#61; mmc_card_sleepawake(host, 0); // 发送CMD5指令&#xff0c;MMC_SLEEP_AWAKE&#xff0c;参数为0&#xff0c;表示退出sleep state.&#xff08;如果参数为1就是进入sleep state&#xff09;// 完成之后&#xff0c;该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 &#61; _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并没有被拔出&#xff0c;说明出现异常了&#xff0c;标记card的rpm状态为suspend */if (!err)mmc_rpm_release(host, &host->card->dev);/* card确实被拔出&#xff0c;正常释放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* &#64;host: pointer to mmc host structure* &#64;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&#39;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 &#61; 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 &#61; host->card;if (!card || !freq) {err &#61; -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 &#61; card->ext_csd.hs_max_dtr;} else if (*freq > card->csd.max_dtr) {*freq &#61; card->csd.max_dtr;}if (*freq f_min)*freq &#61; host->f_min;/* 根据实际要设置的频率值来设置时钟 */if (mmc_card_hs400(card)) {err &#61; mmc_set_clock_bus_speed(card, *freq);if (err)goto out;} else {mmc_set_clock(host, (unsigned int) (*freq));}/* 对于hs200来说&#xff0c;修改完频率之后需要执行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 &#61; 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); // 采样失败&#xff0c;设置回原来的时钟频率}}
out:mmc_release_host(host);return err;
}

五、mmc ops接口说明

1、说明

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

2、代码说明

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

  • mmc_send_status&#xff08;典型&#xff09;

发送CMD13命令&#xff0c;MMC_SEND_STATUS

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

int mmc_send_status(struct mmc_card *card, u32 *status)
{int err;struct mmc_command cmd &#61; {0};BUG_ON(!card);BUG_ON(!card->host);/* 主要是根据对应命令构造struct mmc_command */cmd.opcode &#61; MMC_SEND_STATUS; // 设置命令操作码opcode&#xff0c;这里设置为MMC_SEND_STATUS&#xff0c;也就是CMD13if (!mmc_host_is_spi(card->host))cmd.arg &#61; card->rca <<16; // 设置命令的对应参数&#xff0c;这里设置为card的RCA地址cmd.flags &#61; MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; // 设置请求的一些标识&#xff0c;包括命令类型&#xff0c;response类型等等/* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */err &#61; 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 &#61; cmd.resp[0]; // 依照协议&#xff0c;response[0]存储了status&#xff0c;因此这里提取cmd.resp[0]return 0;
}

  • mmc_send_op_cond&#xff08;特殊&#xff09;

发送CMD1指令&#xff0c;SEND_OP_COND

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

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

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

  • mmc_send_ext_csd

发送CMD8指令&#xff0c;SEND_EXT_CSD

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

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

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

如下&#xff1a;

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 &#61; {0};unsigned long timeout;u32 status;BUG_ON(!card);BUG_ON(!card->host);/* 主要是根据对应命令构造struct mmc_command */cmd.opcode &#61; MMC_SWITCH;cmd.arg &#61; (MMC_SWITCH_MODE_WRITE_BYTE <<24) |(index <<16) |(value <<8) |set;cmd.flags &#61; MMC_CMD_AC;if (use_busy_signal)cmd.flags |&#61; MMC_RSP_SPI_R1B | MMC_RSP_R1B;elsecmd.flags |&#61; MMC_RSP_SPI_R1 | MMC_RSP_R1;cmd.cmd_timeout_ms &#61; timeout_ms;cmd.ignore_timeout &#61; ignore_timeout;/* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */err &#61; 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&#xff0c;等待card退出programming state。 */timeout &#61; jiffies &#43; msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);do {err &#61; 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) &#61;&#61; 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的实际流程。


转载于:https://www.cnblogs.com/linhaostudy/p/10811290.html


推荐阅读
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
author-avatar
happy雨之泪
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有