MTK平台--Android P SD卡检测

SD卡检测

前言

   最近公司拿到了Android P的代码,需要在原有的项目基础上将Android 8.1升级为Android 9.0;最为一个职场新人,独挑大梁是不现实的,不过帮大佬"打杂"还是可以的,在配置SD卡的时候,过程比较有意思,学到了一些东西,在此做个记录。

目录

   对于一个Android项目来说,SD卡应该是属于比较简单的一个模块,需要配置的地方也不多,如下:
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 <MSDC1_CLKSRC_187MHZ>;
 	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 <MSDC_SD>;  //表明被用作sd;
	cd_level = /bits/ 8 <MSDC_CD_HIGH>;  //如果被设置为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 <miki8321p2_9709/cust.dtsi>   //这个文件和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这个属性,但可能因为编译方式的问题导致没有生效,再加上没有代码做依据就没有在这个属性上面坚持,导致后面又绕了不少圈子,不过也学到了一些东西。以后再遇到类似的问题,首选看代码,看似费时间,实际上是最快的解决方式,一切的问题,始于代码,也终于代码。

image

猜你喜欢

转载自blog.csdn.net/qq_40658985/article/details/86133865