本文将基于emmc驱动来描述系统是如何检测到emmc设备,并进行初始化操作的。
一、中断处理函数mmci_cd_irq
在前面关于mmc驱动的系列文章emmc/sd host层解析中有关于mmci的的分析,在文章中有分析过一个名为mmci_probe的函数,该函数比较长,这里就不完全贴出来了,只贴出跟emmc检测相关的代码,如下:
/*
* A gpio pin that will detect cards when inserted and removed
* will most likely want to trigger on the edges if it is
* 0 when ejected and 1 when inserted (or mutatis mutandis
* for the inverted case) so we request triggers on both
* edges.
*/
ret = request_any_context_irq(gpio_to_irq(plat->gpio_cd),
mmci_cd_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
DRIVER_NAME " (cd)", host);
注释说:一个gpio的pin脚会检测卡的插入和移除。
这段代码是注册一个中断号对应的中断函数,而这个中断就是对应卡插入或者移除的。
下面来看一下中断函数的实现(mmci.c):
static irqreturn_t mmci_cd_irq(int irq, void *dev_id)
{
struct mmci_host *host = dev_id;
mmc_detect_change(host->mmc, msecs_to_jiffies(500));
return IRQ_HANDLED;
}
里面调用了mmc_detect_change(core/core.c)函数来检测拔插变化,实现如下:
/**
* mmc_detect_change - process change of state on a MMC socket
* @host: host which changed state.
* @delay: optional delay to wait before detection (jiffies)
*
* MMC drivers should call this when they detect a card has been
* inserted or removed. The MMC layer will confirm that any
* present card is still functional, and initialize any newly
* inserted.
*/
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
_mmc_detect_change(host, delay, true);
}
再看_mmc_detect_change(core.c):
static void _mmc_detect_change(struct mmc_host *host, unsigned long delay,
bool cd_irq)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
/*
* If the device is configured as wakeup, we prevent a new sleep for
* 5 s to give provision for user space to consume the event.
*/
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;
mmc_schedule_delayed_work(&host->detect, delay);
}
这里,重点看下最后一行代码。
二、mmc_schedule_delayed_work(&host->detect, delay)
mmc_schedule_delayed_work(&host->detect, delay);
这里延时调度了一个工作队列,再展开看一下:
/*
* Internal function. Schedule delayed work in the MMC work queue.
*/
static int mmc_schedule_delayed_work(struct delayed_work *work,
unsigned long delay)
{
return queue_delayed_work(workqueue, work, delay);
}
从传递的参数类型我们可以知道,host->detect是个delayed_work类型的变量,那么它是在什么时候被赋值的呢?
这个我们得从mmci_probe函数说起,该函数中有这样一段代码:
mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
而mmc_alloc_host函数中有如下代码:
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
这里将mmc_rescan处理函数赋值给了detect下的工作队列处理函数指针,也就是说如果以后调度detect队列时,就会使用mmc_rescan函数去处理。
也就是说mmc_schedule_delayed_work实际调用了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;
/* If there is a non-removable card registered, only scan once */
if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
return;
host->rescan_entered = 1;
mmc_bus_get(host);
/*
* if there is a _removable_ card registered, check whether it is
* still present
*/
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
&& !(host->caps & MMC_CAP_NONREMOVABLE))
host->bus_ops->detect(host);
host->detect_change = 0;
/*
* Let mmc_bus_put() free the bus/bus_ops if we've found that
* the card is no longer present.
*/
mmc_bus_put(host);
mmc_bus_get(host);
/* if there still is a card present, stop here */
if (host->bus_ops != NULL) {
mmc_bus_put(host);
goto out;
}
/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);
if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
mmc_claim_host(host);
mmc_power_off(host);
mmc_release_host(host);
goto out;
}
mmc_claim_host(host);
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
mmc_release_host(host);
out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
第46行之前,自己看注释;主要是将host的bus_ops置空;
第46行,调用了host的ops的get_cd函数指针对应的函数,在这里就不分析ops指向了谁,直接上函数指针对应的函数(mmci.c):
static int mmci_get_cd(struct mmc_host *mmc)
{
struct mmci_host *host = mmc_priv(mmc);
struct mmci_platform_data *plat = host->plat;
unsigned int status;
if (host->gpio_cd == -ENOSYS) {
if (!plat->status)
return 1; /* Assume always present */
status = plat->status(mmc_dev(host->mmc));
} else
status = !!gpio_get_value_cansleep(host->gpio_cd)
^ plat->cd_invert;
/*
* Use positive logic throughout - status is zero for no card,
* non-zero for card inserted.
*/
return status;
}
从最后的注释可以知道,如果函数返回0表示没有卡插入,如果返回非0表示有卡插入。
回到mmc_rescan的第46行,如果此时没有卡则进入if语句中,关闭电源并退出,否则执行下面的流程。
第54--59行:这里出现了一个名为freqs的数组,先看下它的定义:
static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
这个应该是一个时钟频率数组。
看第55行的mmc_rescan_try_freq:
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
#ifdef CONFIG_MMC_DEBUG
pr_info("%s: %s: trying to init card at %u Hz\n",
mmc_hostname(host), __func__, host->f_init);
#endif
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);
/*
* sdio_reset sends CMD52 to reset card. Since we do not know
* if the card is being re-initialized, just send it. CMD52
* should be ignored by SD/eMMC cards.
*/
sdio_reset(host);
mmc_go_idle(host);
mmc_send_if_cond(host, host->ocr_avail);
/* 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;
}
第9行:给设备上电;
第15行:给emmc设备硬件重置;
第22行:如果是sdio重置;
第23行:发送CMD0命令,设置idle状态;
第25行:发送SD_SEND_IF_COND(SD卡的CMD8)命令,判断当前工作电压是否符合要求。emmc不会响应。
第28行:内部通过发送SD_IO_SEND_OP_COND(CMD5)检测是否为sdio设备,是调用mmc_sdio_init_card进行初始化;
第30行:内部通过发送SD_APP_OP_COND(CMD41)检测是否为sd设备,是调用mmc_sd_init_card进行初始化;
第32行:内部通过发送MMC_SEND_OP_COND(CMD1)检测是否为emmc设备,是则调用mmc_init_card函数进行初始化,关于mmc_init_card函数在linux关机时emmc驱动处理流程文章的第十节有详细讲解,有兴趣的自行阅读。
如果上面的三种检测操作有一个操作成功就直接返回0。如果不 成功则执行第35行的下电操作,并返回非0.
到此我们的emmc初始化完毕了。