1、kernel-4.9-lc/arch/arm/boot/dts/[project].dts
2、kernel-4.9-lc/arch/arm/boot/dts/mtxxx.dts
3、kernel-4.9-lc/arch/arm/boot/dts/cust_mtxxxx_msdc.dtsi
4、kernel-4.9-lc/drivers/misc/mediatek/dws/mt6580/[project].dws
5、vendor/mediatek/proprietary/bootable/bootloader/lk/target/[project]/dct/dct/codegen.dws
6、vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/[project]/dct/dct/codegen.dws
从上面所列代码路径来看,相关的dts文件有三个,[project].dts只作用于特定项目,SD相关代码如下:
&msdc1 {
index = /bits/ 8 <1>;
clk_src = /bits/ 8 ;
bus-width = <4>;
max-frequency = <200000000>;
sd_need_power; //是否配置这条属性决定了当mmc_rescan函数没有检测到SD卡时是否下电,就是因为这个属性,让我兜兜转转转了一圈,后面详细说;
cap-sd-highspeed;
sd-uhs-sdr12;
sd-uhs-sdr25;
sd-uhs-sdr50;
sd-uhs-sdr104;
sd-uhs-ddr50;
no-mmc; //这两条标明不能作为mmc和sdio;
no-sdio;
pinctrl-names = "default","insert_cfg";
pinctrl-0 = <&mmc1_pins_insert_default>; //insert和中断相关,即检测脚相关
pinctrl-1 = <&mmc1_pins_insert_cfg>;
pinctl = <&msdc1_pins_default>;
pinctl_sdr104 = <&msdc1_pins_sdr104>;
pinctl_sdr50 = <&msdc1_pins_sdr50>;
pinctl_ddr50 = <&msdc1_pins_ddr50>;
register_setting = <&msdc1_register_setting_default>;
host_function = /bits/ 8 ; //表明被用作sd;
cd_level = /bits/ 8 ; //如果被设置为0,表示低有效(即正确识卡的时候检测脚为低电平);反之则高电平有效;这个由软件和硬件上共同决定
cd-gpios = <&pio 14 0>; //设置中断脚为GPIO14;
vmmc-supply = <&mt_pmic_vmch_ldo_reg>; //供电,代码中会用到vmmc-supply和vqmmc-supply;
vqmmc-supply = <&mt_pmic_vmc_ldo_reg>;
}
#include //这个文件和dws相关
/*End of this file, DO NOT ADD ANYTHING HERE*/
mtxxxx.dts和cust_mtxxxx_msdc.dtsi这两个文件和平台相关,当在项目文件中也定义了相同的内容,最后编译的时候项目中的定义会覆盖掉平台目录下的;所以在此我们只关注项目相关的dts文件;dts文件里面只是一些属性的配置,至于这些属性怎么用?在哪里用?这就得去看代码了,当然,看代码也是要讲究方法的,稍微对SD有了解的童鞋都应该知道,SD卡的代码架构分三层:core、host和card,想要在短时间内完全弄懂肯定是不现实的,所以,首先要明确自己想要从代码里面知道什么,然后再找到一个方向切入进去;在我准备看代码之前,通过对dts文件的配置,冷启动已经可以识别到卡了,但是热插拔无论怎么配置dts都不行,那么,就需要先去了解热插拔的原理。
热插拔实现原理可以从两个方向来说:
1、当硬件底层检测到SD卡状态发生变化时,某个GPIO中断脚产生中断,中断处理函数调用mmc_detect_change函数进行中断脚的检测;
2、host要求轮询sd card插入状态的情况下,所进行的轮询操作(sd card插入状态监控),在mmc_rescan函数中有所体现;
首先,先去看_mmc_detect_change函数里面都干了哪些事情,往下看:
static void _mmc_detect_change(struct mmc_host *host, unsigned long delay,bool cd_irq)
{
if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) &&
device_can_wakeup(mmc_dev(host)))
pm_wakeup_event(mmc_dev(host), 5000);
host->detect_change = 1; //将host->detect_change赋值为1,第一次扫描的时候需要
mmc_schedule_delayed_work(&host->detect, delay); //唤醒host->detect
}
那么这个host_>detect是在哪里被设置的呢?在mmc_alloc_host函数中,如下:
struct mmc_host *mmc_alloc_host(int extra, struct device *dev){
……
INIT_DELAYED_WORK(&host->detect, mmc_rescan ); //将host_>detect初始化为mmc_rescan,并加入工作队列,等待以后调用
……
}
即在_mmc_detect_change函数中会去调用mmc_rescan函数,那就去看看mmc_rescan里面都做了哪些事情,上代码:
void mmc_rescan(struct work_struct *work){
struct mmc_host *host =container_of(work, struct mmc_host, detect.work);
int i;
if (host->rescan_disable)
return; //rescan_disable表示初始化还没完成,不能进行扫描的操作,直接返回
/* If there is a non-removable card registered, only scan once */
if (!mmc_card_is_removable(host) && host->rescan_entered)
return; //当non-removable属性被设置时,表示只需要进行一次扫描就OK了
host->rescan_entered = 1;
/**省略部分代码***/
/*SD卡检测相关,代码走到这儿说明之前卡槽上是没有卡的,此时,调用get_cd函数去检测是否有卡,若是,返回1;若不是,返回0;*/
if (mmc_card_is_removable(host) && host->ops->get_cd && host->ops->get_cd(host) == 0) {
mmc_power_off(host);
mmc_release_host(host);
goto out; //若没有卡,跳转到out,实现循环检测
}
/**省略部分代码***/
out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ); //间隔指定时间循环检测卡的状态是否有变化
}
总的来说,在代码中会不停的调用mmc_rescan这个函数去扫描mmc总线以检测SD卡的状态是否发生变化,然后做出相应的操作。通过在函数中加调试信息,打印log分析,在热插拔的过程中,mmc_rescan函数执行到SD卡检测那里就挂掉了,而且没有循环去跑这个函数,通过进一步分析,get_cd的返回值为0,就是说没有检测到有卡插入,get_cd的代码如下:
static int msdc_ops_get_cd(struct mmc_host *mmc){
int level = 1;
/*省略部分代码*/
if (mmc->caps & MMC_CAP_NONREMOVABLE) {
host->card_inserted = 1; //若是配置为不可热插拔,直接将host->card_insert设置为1;
goto end;
} else {
#ifdef CONFIG_GPIOLIB
level = __gpio_get_value(cd_gpio); //获取中断引脚的值
#endif
host->card_inserted = (host->hw->cd_level == level) ? 1 : 0; //host->hw->cd_level为dts里面设置的cd_level,若中断引脚的电平与cd_level相同,则将card_insert的值设为1,反之为0;
}
/*省略部分代码*/
return host->card_inserted; //返回card_insert的值
}
在此次项目中,规定为高有效,即插卡后中断检测脚应该为高电平,而此时从代码中来看检测脚的电平为低……此时此刻万用表该上场了,毕竟不会使用万用表的驱动工程师是假的驱动工程师,量了检测脚的电压,果然为0,硬件的同事说再量量供电脚的电压,量了一下,也为0!难怪,没有电压还要要求检测脚正常工作,典型的又要让马儿跑又不给马儿吃草,我估计没有循环的去跑mmc_rescan函数也是因为没有电压,可是为什么没有电压呢?冷启动可以正常识卡说明肯定是有给SD卡上电的,难道是在检测到没卡的时候方下电了,于把下电的功能暂时去掉,编译,刷机,验证:供电引脚一直保持有电压,插拔卡时中断检测脚有电平变化,现在可以明确的定位是下电这里有问题,可以在mmc_rescan函数中看到当没有检测到卡时会调用mmc_power_off函数,函数功能如下,省略部分,直接看重点:
void mmc_power_off(struct mmc_host *host){
if (host->ios.power_mode == MMC_POWER_OFF)
return; //若已经处于MMC_POWER_OFF模式,直接返回
......
host->ios.power_mode = MMC_POWER_OFF; //设置为MMC_POWER_OFF模式
......
}
设置为这个模式是为哪里做准备呢?在msdc_ops_set_ios会被用到,如下,还是直接看重点:
void msdc_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios){
.......
switch (ios->power_mode) {
case MMC_POWER_OFF:
spin_unlock(&host->lock);
msdc_set_power_mode(host, ios->power_mode);
spin_lock(&host->lock);
break;
case MMC_POWER_UP:
spin_unlock(&host->lock);
msdc_init_hw(host);
msdc_set_power_mode(host, ios->power_mode);
spin_lock(&host->lock);
break;
case MMC_POWER_ON:
if (host->hw->host_function == MSDC_SD)
msdc_sd_clock_run(host);
break;
default:
break;
}
...........
}
从上面代码中可以看出来,无论是MMC_POWER_OFF模式还是MMC_POWER_ON模式,都调用了msdc_set_power_mode,看看这个函数里面干了什么?看重点:
static void msdc_set_power_mode(struct msdc_host *host, u8 mode){
......
/*无论上下电都调用到了host->power_control这个函数*/
if (host->power_mode == MMC_POWER_OFF && mode != MMC_POWER_OFF){
.....
if (host->power_control)
host->power_control(host, 1);
.....
}else if (host->power_mode != MMC_POWER_OFF && mode == MMC_POWER_OFF){
.....
if (host->power_control)
host->power_control(host, 0);
......
}
到现在都没有看到和vmmc或者vqmmc相关的代码,继续往下看,在msdc_set_host_power_control函数中将msdc_sd_power赋值给host->power_control,所以,在msdc_set_power_mode函数中实际上调用的是msdc_sd_powe函数,如下:
void msdc_set_host_power_control(struct msdc_host *host)
{
if (host->hw->host_function == MSDC_EMMC) {
host->power_control = msdc_emmc_power;
} else if (host->hw->host_function == MSDC_SD) { //在dts里面已经将host_function赋值为了MSDC_SD
host->power_control = msdc_sd_power;
host->power_switch = msdc_sd_power_switch;
/*省略多余代码*/
}
/*省略多余代码*/
}
void msdc_sd_power(struct msdc_host *host, u32 on)
{
#if !defined(CONFIG_MTK_MSDC_BRING_UP_BYPASS)
u32 card_on = on;
switch (host->id) {
case 1:
msdc_set_driving(host, &host->hw->driving);
msdc_set_tdsel(host, MSDC_TDRDSEL_3V, 0);
msdc_set_rdsel(host, MSDC_TDRDSEL_3V, 0);
if (host->hw->flags & MSDC_SD_NEED_POWER)
card_on = 1;
/* VMCH VOLSEL */
msdc_ldo_power(card_on, host->mmc->supply.vmmc, VOL_3300,
&host->power_flash);
msdc_ldo_power(on, host->mmc->supply.vqmmc, VOL_3300,
&host->power_io);
if (on)
msdc_set_sr(host, 0, 0, 0, 0, 0);
break;
default:
break;
}
#endif
#ifdef MTK_MSDC_BRINGUP_DEBUG
msdc_dump_ldo_sts(NULL, 0, NULL, host);
#endif
}
在msdc_sd_power函数中终于看到心心念念的vmmc和vqmmc了,从dts的配置中看出来,vmmc-supply = <&mt_pmic_vmch_ldo_reg>;硬件的同事说供电脚的供电源带了vmch的字样,从代码中可以看到msdc_ldo_power这个函数根据card_on的值来决定是否上电,card_on的值又由是否具有MSDC_SD_NEED_POWER这个标志决定。最后确定是由于缺少MSDC_SD_NEED_POWER这个东西,导致SD卡下电,然后无法检测中断引脚实现热插拔。
在dts节点里面加上sd_need_power;这个属性,然后在msdc_of_parse函数中添加如下代码:
int msdc_of_parse(struct platform_device *pdev, struct mmc_host *mmc){
int len;
......
if (of_find_property(np, "sd_need_power", &len))
host->hw->flags |= MSDC_SD_NEED_POWER;
......
}
问题到此解决,分析也到此结束……
最后的解决方法很简单,加起来总共三行代码,但是其实过程还是挺曲折、挺磨人的,在dts上面花费了两天的时间,想破脑袋也想不出来,最开始也加过sd_need_power这个属性,但可能因为编译方式的问题导致没有生效,再加上没有代码做依据就没有在这个属性上面坚持,导致后面又绕了不少圈子,不过也学到了一些东西。以后再遇到类似的问题,首选看代码,看似费时间,实际上是最快的解决方式,一切的问题,始于代码,也终于代码。