三,Linux SDIO驱动框架之扫卡流程

 本文以kernel-4.4,以MTK平台分析host端初始化的扫卡流程
 mtk平台host驱动代码在drivers/mmc/host/mtk-sdio.c,sd card,mmc和sdio card的相关核心代码都存在drivers/mmc/目录中,本文只关心host是如何扫描到card的,下面分析host扫卡流程。

1,host驱动初始化

 平台驱动代码中注册platform_driver,匹配到device调用到probe函数int msdc_drv_probe(struct platform_device *pdev),下面代码省略了很多host的初始化的细节,只截取了我们关心的部分:

static int msdc_drv_probe(struct platform_device *pdev) 
{
   
   
    ...
    在mmc core中分配一个mmc_host结构体
    mmc = mmc_alloc_host(sizeof(struct msdc_host), &pdev->dev);
    ...
    ret = mmc_add_host(mmc);
}

如上所示代码中主要调用两个接口来向mmc core注册host驱动

  • 调用 mmc_alloc_host用于分配一个struct mmc_host 结构体,在该函数中初始化了一个延时工作队列INIT_DELAYED_WORK(&host->detect, mmc_rescan);后续的扫卡工作主要是靠mmc_rescan来完成,这点在本文之后会分析。
  • mmc_add_host,主要的工作是初始化host,并对sdio总线上进行一次detect用于扫描总线的card
    下面对mmc_add_host进行分析
int mmc_add_host(struct mmc_host *host)
{
   
   
    int err;

    err = device_add(&host->class_dev);
    if (err)
        return err;

    mmc_start_host(host);

    return 0;                                                           
}


void mmc_start_host(struct mmc_host *host) 
{
   
   
    host->f_init = max(freqs[0], host->f_min);
    host->rescan_disable = 0;
    host->ios.power_mode = MMC_POWER_UNDEFINED;

    mmc_claim_host(host);                         
    if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)
        mmc_power_off(host);
    else
        mmc_power_up(host, host->ocr_avail);
    mmc_release_host(host);

    mmc_gpiod_request_cd_irq(host);
    _mmc_detect_change(host, 0, false);  detect SDIO总线上是否存在card
}

void _mmc_detect_change(struct mmc_host *host, unsigned long delay,
                bool cd_irq)
{
   
   
    
    host->detect_change = 1;
    mmc_schedule_delayed_work(&host->detect, delay);
}
从中可以看出最终调用到的是在mmc_alloc_host中初始化的延迟工作队列host->detect,
所以mmc_rescan函数会被调用

从中我们总结出HOST在初始化阶段的detect 流程:
platform_driver probe接口msdc_drv_probe
 >mmc_alloc_host
  >> 初始化延迟工作队列host->detect,设置工作队列work func为mmc_rescan

 >mmc_start_host
  >>mmc_schedule_delayed_work(&host->detect, delay); 最终会调用到host->detect延迟工作队列work func mmc_rescan

2,mmc_rescan接口分析

 下面是具体的扫卡过程

void mmc_rescan(struct work_struct *work)
{
   
   

    mmc_claim_host(host);
    for (i = 0; i < ARRAY_SIZE(freqs); i++) {
   
   
        //unsigned freqs[] = { 400000, 300000, 200000, 100000 };扫卡频率
        if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
            break;
        if (freqs[i] <= host->f_min)
            break;
    }
}
scan card设置的频率是在400k到host支持的最低频率,逐个频率之间进行扫描
比如说400k频率扫卡失败了,接着进行300k的频率进行扫描,直到host支持的最低频率,或者100k为止

int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
   
   
    host->f_init = freq;

	这个power_up接口主要做两件事:
    1)根据DTS中mmc-pwrseq中的配置对card进行上电操作
    2)对sdio host controller进行上电,设置3.3或者1.8v信号电压
    mmc_power_up(host, host->ocr_avail);

    /*
     * Some eMMCs (with VCCQ always on) may not be reset after power up, so
     * do a hardware reset if possible.
     */
    mmc_hw_reset_for_init(host);
	
	在power up上电之后通过command对card进行复位设置初始状态
    发送CMD52 REG6 ,对card进行reset
    sdio_reset(host);
    发送CMD0 设置card为idle状态
    mmc_go_idle(host);
    发送CMD8 询问card是否支持host的电压域,如果不是memory card那么不回复
    mmc_send_if_cond(host, host->ocr_avail);

    //依次进行匹配是否是SDIO,SD card,MMC设备
    /* Order's important: probe SDIO, then SD, then MMC */
    if (!mmc_attach_sdio(host))
        return 0;
    if (!mmc_attach_sd(host))
        return 0;
    if (!mmc_attach_mmc(host))
        return 0;

    mmc_power_off(host);
    return -EIO;
}

 我们只分析SDIO card,所有接下来分析mmc_attach_sdio(host)

int mmc_attach_sdio(struct mmc_host *host)
{
   
   
    int err, i, funcs;
    u32 ocr, rocr;
    struct mmc_card *card;

    获取sdio card的电压域
    err = mmc_send_io_op_cond(host, 0, &ocr);
    if (err)
        return err;

    mmc_attach_bus(host, &mmc_sdio_ops);
    if (host->ocr_avail_sdio)
        host->ocr_avail = host->ocr_avail_sdio;

    选择host和card都支持的电压rocr
    rocr = mmc_select_voltage(host, ocr);

   检测并初始化card
    err = mmc_sdio_init_card(host, rocr, NULL, 0);
    if (err)
        goto err;

    card = host->card;

    /*
     * The number of functions on the card is encoded inside
     * the ocr.
     */
    funcs = (ocr & 0x70000000) >> 28; 从OCR中获取card中的function number
    card->sdio_funcs = 0;

    /*
     * Initialize (but don't add) all present functions.
     */
	for (i = 0; i < funcs; i++, card->sdio_funcs++) {
   
   
     err = sdio_init_func(host->card, i + 1);
     if (err)
		goto remove;
	}

    //dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);
    //调用device_add(&card->dev)
    err = mmc_add_card(host->card);
    if (err)                                               
        goto remove_added;

    /*
     * ...then the SDIO functions.
     */
    for (i = 0;i < funcs;i++) {
   
   
        //调用dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);//设置name
        //调用device_add(&func->dev);
        err = sdio_add_func(host->card->sdio_func[i]);
        if (err)
            goto remove_added;
    }

    return err;
}

 这个函数比较长,拆分为几部分分析:

  1. mmc_send_io_op_cond通过CMD5,获取card支持的电压域,根据host所支持的电压域,得到host和card都支持的电压域rocr,并将rocr传入mmc_sdio_init_card。有关CMD的说明可以参考:二,sdio总线之cmd
  2. mmc_sdio_init_card,这个函数用于检测,并初始化card,从CCCR寄存器获取card支持的speed模式,bus width。从CIS Area中获取CIS 信息,比如vendor id,device id等。这个函数接下来会分析。
  3. 根据在CMD5 response中获取到的function number初始化每个function,,获取每个function的CIS信息,并为每个function分配一个device,设置bus type为sdio_bus_type,拥有匹配sdio_driver。
  4. mmc_add_card调用device_add(&card->dev),将card->dev添加到device框架中,如果成功扫到了card,会有这么一句打印:

mmc1: new ultra high speed SDR104 SDIO card at address 0001(从log中可以看出card支持UHS mode,支持速率为SDR104,RCA为1)

  1. sdio_add_func()将所有的function device调用device_add(&func->dev);添加到device架构中。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值