本文以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;
}
这个函数比较长,拆分为几部分分析:
- mmc_send_io_op_cond通过CMD5,获取card支持的电压域,根据host所支持的电压域,得到host和card都支持的电压域rocr,并将rocr传入mmc_sdio_init_card。有关CMD的说明可以参考:二,sdio总线之cmd
- mmc_sdio_init_card,这个函数用于检测,并初始化card,从CCCR寄存器获取card支持的speed模式,bus width。从CIS Area中获取CIS 信息,比如vendor id,device id等。这个函数接下来会分析。
- 根据在CMD5 response中获取到的function number初始化每个function,,获取每个function的CIS信息,并为每个function分配一个device,设置bus type为sdio_bus_type,拥有匹配sdio_driver。
- 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)
- sdio_add_func()将所有的function device调用device_add(&func->dev);添加到device架构中。

最低0.47元/天 解锁文章
2398





