您的位置:首页 > 运维架构 > Linux

基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(二)

2014-11-05 09:11 381 查看
转自 http://blog.csdn.net/yj4231/article/details/25920231
在阅读本文之前,请先掌握以下基本知识,不然请略过本文。

预备知识

熟读LDD3前十章节的内容。

熟悉内核驱动模型(sysfs)和platform总线。

简要了解过SD卡规范。

本文的内容基于如下硬件和软件平台:

目标平台:TQ2440

CPU:s3c2440

内核版本:3.12.5

基于SD规范4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。

在阅读MMC子系统时,一个问题随之就会产生:当我们插入一张SD卡时,系统是如何识别到这张SD卡并将它注册进系统的呢?

这一过程,源于MMC控制器驱动的不懈努力。。。。。。下面,我们从控制器驱动开始,来深入挖掘这一过程。

1. MMC控制器驱动

1.1 MMC控制器入口函数及probe方法

本文以三星的s3c2440上的MMC控制器驱动为例,来进行简要的说明。

从MMC控制器驱动的入口函数开始,如下:

下列代码位于:linux/drivers/mmc/host/s3cmci.c

[cpp]
view plaincopy





static struct platform_driver s3cmci_driver = {
.driver = {
.name = "s3c-sdi",
.owner = THIS_MODULE,
.pm = s3cmci_pm_ops,
},
.id_table = s3cmci_driver_ids,
.probe = s3cmci_probe,
.remove = s3cmci_remove,
.shutdown = s3cmci_shutdown,
};

module_platform_driver(s3cmci_driver);

这里直接调用了platform的驱动注册函数来注册一个名为s3c-sdi的驱动,该驱动将绑定mmc主控制器设备。
为了让该驱动成功绑定MMC主控制器设备,需要先进行移植操作,具体可见:S3C2440 Linux驱动移植——SD卡驱动

绑定成功后,立即会调用probe方法,也就是s3cmci_probe函数,我们来看下:

[cpp]
view plaincopy





static int s3cmci_probe(struct platform_device *pdev)
{
struct s3cmci_host *host;
struct mmc_host *mmc;
int ret;
int is2440;
int i;

/* */
is2440 = platform_get_device_id(pdev)->driver_data;

/* 分配struct mmc_host和struct s3cmci_host */
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto probe_out;
}

/* 申请IO管脚*/
for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {
ret = gpio_request(i, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "failed to get gpio %d\n", i);

for (i--; i >= S3C2410_GPE(5); i--)
gpio_free(i);

goto probe_free_host;
}
}

host = mmc_priv(mmc); /* 获取struct s3cmci_host指针*/
host->mmc = mmc; /* 保存mmc指针*/
host->pdev = pdev; /* 保存平台设备指针*/
host->is2440 = is2440; /* 是否为2440*/

host->pdata = pdev->dev.platform_data; /* 保存板级设备信息*/
if (!host->pdata) {
/* 如果没有板级设备信息,给出默认的*/
pdev->dev.platform_data = &s3cmci_def_pdata;
host->pdata = &s3cmci_def_pdata;
}

/* 初始化自旋锁*/
spin_lock_init(&host->complete_lock);
/* 初始化1个tasklet,执行pio_tasklet*/
tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);

if (is2440) {
host->sdiimsk = S3C2440_SDIIMSK;
host->sdidata = S3C2440_SDIDATA;
host->clk_div = 1;
} else {
host->sdiimsk = S3C2410_SDIIMSK;
host->sdidata = S3C2410_SDIDATA;
host->clk_div = 2;
}

host->complete_what = COMPLETION_NONE;
host->pio_active = XFER_NONE;

/* 板级数据可以决定是否使用DMA,也可以通过配置来决定*/
#ifdef CONFIG_MMC_S3C_PIODMA
host->dodma = host->pdata->use_dma;
#endif

/* 获取寄存器资源*/
host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!host->mem) {
dev_err(&pdev->dev,
"failed to get io memory region resource.\n");

ret = -ENOENT;
goto probe_free_gpio;
}
/* 申请IO内存空间*/
host->mem = request_mem_region(host->mem->start,
resource_size(host->mem), pdev->name);

if (!host->mem) {
dev_err(&pdev->dev, "failed to request io memory region.\n");
ret = -ENOENT;
goto probe_free_gpio;
}

/* 映射寄存器*/
host->base = ioremap(host->mem->start, resource_size(host->mem));
if (!host->base) {
dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
ret = -EINVAL;
goto probe_free_mem_region;
}

/*获取IRQ */
host->irq = platform_get_irq(pdev, 0);
if (host->irq == 0) {
dev_err(&pdev->dev, "failed to get interrupt resource.\n");
ret = -EINVAL;
goto probe_iounmap;
}

/* 注册IRQ*/
if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
dev_err(&pdev->dev, "failed to request mci interrupt.\n");
ret = -ENOENT;
goto probe_iounmap;
}

/* We get spurious interrupts even when we have set the IMSK
* register to ignore everything, so use disable_irq() to make
* ensure we don't lock the system with un-serviceable requests. */

/* 禁止中断并设置中断状态*/
disable_irq(host->irq);
host->irq_state = false;

/* 使用detect功能,则申请gpio */
if (!host->pdata->no_detect) {
ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
if (ret) {
dev_err(&pdev->dev, "failed to get detect gpio\n");
goto probe_free_irq;
}
/* 根据gpio获取中断号*/
host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);

/* 中断号有效则注册该中断*/
if (host->irq_cd >= 0) {
if (request_irq(host->irq_cd, s3cmci_irq_cd,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
DRIVER_NAME, host)) {
dev_err(&pdev->dev,
"can't get card detect irq.\n");
ret = -ENOENT;
goto probe_free_gpio_cd;
}
} else {
dev_warn(&pdev->dev,
"host detect has no irq available\n");
gpio_direction_input(host->pdata->gpio_detect);
}
} else
host->irq_cd = -1;


/* 使用wprotect功能,则申请gpio */
if (!host->pdata->no_wprotect) {
ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
if (ret) {
dev_err(&pdev->dev, "failed to get writeprotect\n");
goto probe_free_irq_cd;
}

gpio_direction_input(host->pdata->gpio_wprotect);
}

/* depending on the dma state, get a dma channel to use. */

/* 如果使用DMA则申请相应的物理DMA通道*/
if (s3cmci_host_usedma(host)) {
/* 返回值为通道号*/
host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
host);
if (host->dma < 0) {
/* DMA申请失败,尝试使用IO操作*/
dev_err(&pdev->dev, "cannot get DMA channel.\n");
if (!s3cmci_host_canpio()) {
ret = -EBUSY;
goto probe_free_gpio_wp;
} else {
dev_warn(&pdev->dev, "falling back to PIO.\n");
host->dodma = 0; /* 表示不使用DMA?/
}
}
}
/* 获取clk*/
host->clk = clk_get(&pdev->dev, "sdi");
if (IS_ERR(host->clk)) {
dev_err(&pdev->dev, "failed to find clock source.\n");
ret = PTR_ERR(host->clk);
host->clk = NULL;
goto probe_free_dma;
}

/*使能clk*/
ret = clk_enable(host->clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable clock source.\n");
goto clk_free;
}

host->clk_rate = clk_get_rate(host->clk); /* 保存时钟频率*/

/*开始初始化mmc当中的字段*/
mmc->ops = &s3cmci_ops; /* 给出控制器operation函数集*/
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
mmc->caps = MMC_CAP_4_BIT_DATA;
#endif
/* 计算最大和最小的工作频率*/
mmc->f_min = host->clk_rate / (host->clk_div * 256);
mmc->f_max = host->clk_rate / host->clk_div;

if (host->pdata->ocr_avail)
mmc->ocr_avail = host->pdata->ocr_avail;

mmc->max_blk_count = 4095; /* 一个请求的最大block数*/
mmc->max_blk_size = 4095; /* block的最大容量*/
mmc->max_req_size = 4095 * 512; /* 一个请求的最大字节数*/
mmc->max_seg_size = mmc->max_req_size;

mmc->max_segs = 128;

dbg(host, dbg_debug,
"probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u.\n",
(host->is2440?"2440":""),
host->base, host->irq, host->irq_cd, host->dma);

ret = s3cmci_cpufreq_register(host);
if (ret) {
dev_err(&pdev->dev, "failed to register cpufreq\n");
goto free_dmabuf;
}

/* 注册该mmc 控制器到子系统中*/
ret = mmc_add_host(mmc);
if (ret) {
dev_err(&pdev->dev, "failed to add mmc host.\n");
goto free_cpufreq;
}

s3cmci_debugfs_attach(host);

/* 设置驱动数据*/
platform_set_drvdata(pdev, mmc);
dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
s3cmci_host_usedma(host) ? "dma" : "pio",
mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");

return 0;

free_cpufreq:
s3cmci_cpufreq_deregister(host);

free_dmabuf:
clk_disable(host->clk);

clk_free:
clk_put(host->clk);

probe_free_dma:
if (s3cmci_host_usedma(host))
s3c2410_dma_free(host->dma, &s3cmci_dma_client);

probe_free_gpio_wp:
if (!host->pdata->no_wprotect)
gpio_free(host->pdata->gpio_wprotect);

probe_free_gpio_cd:
if (!host->pdata->no_detect)
gpio_free(host->pdata->gpio_detect);

probe_free_irq_cd:
if (host->irq_cd >= 0)
free_irq(host->irq_cd, host);

probe_free_irq:
free_irq(host->irq, host);

probe_iounmap:
iounmap(host->base);

probe_free_mem_region:
release_mem_region(host->mem->start, resource_size(host->mem));

probe_free_gpio:
for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
gpio_free(i);

probe_free_host:
mmc_free_host(mmc);

probe_out:
return ret;
}

这个函数想当的长,差不多300行,我们来简要分析下:
第一步,该函数首先调用mmc_alloc_host分配了struct mmc_host和truct s3cmci_host和两个结构体的内存空间,其中前者包含后者,而后者有一个指针指向前者。

该函数的详细的实现如下:

[cpp]
view plaincopy





/**
* mmc_alloc_host - initialise the per-host structure.
* @extra: sizeof private data structure
* @dev: pointer to host device model structure
*
* Initialise the per-host structure.
*/
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
int err;
struct mmc_host *host;

host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;

/* scanning will be enabled when we're ready */
host->rescan_disable = 1;
idr_preload(GFP_KERNEL);
/* 获取一个idr,通过自旋锁进行互斥保护*/
spin_lock(&mmc_host_lock);
err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
if (err >= 0)
host->index = err;
spin_unlock(&mmc_host_lock);
idr_preload_end();
if (err < 0)
goto free;

/* 设置设备名*/
dev_set_name(&host->class_dev, "mmc%d", host->index);

host->parent = dev;
host->class_dev.parent = dev; /* 设置父设备*/
host->class_dev.class = &mmc_host_class; /* 设置设备所属的类*/
device_initialize(&host->class_dev);

mmc_host_clk_init(host);

mutex_init(&host->slot.lock);
host->slot.cd_irq = -EINVAL;

spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan); /* 创建延时工作队列*/
#ifdef CONFIG_PM
host->pm_notify.notifier_call = mmc_pm_notify; /* 通知链回调函数*/
#endif

/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
host->max_segs = 1;
host->max_seg_size = PAGE_CACHE_SIZE;

host->max_req_size = PAGE_CACHE_SIZE;
host->max_blk_size = 512;
host->max_blk_count = PAGE_CACHE_SIZE / 512;

return host;

free:
kfree(host);
return NULL;
}

EXPORT_SYMBOL(mmc_alloc_host);

其大致过程如下:分配相应的结构体,设置标志位rescan_disable为1,表示禁止扫描SD卡。
随后利用idr分配了一个编号给该MMC控制器,并初始化了MMC控制器设备对象(device),

初始化了控制器时钟信息。

很重要的一步,初始化了一个工作(host->detect)为mmc_rescan,该函数将负责执行对SD卡的扫描,该函数将在后面描述。

最后,初始化了MMC控制器的访问块大小的能力。

第二步,根据控制器所使用的引脚,向gpio子系统申请该引脚。

第三步,获取板级设备信息(pdev->dev.platform_data),并初始化struct s3cmci_host中的一些字段。

第四步,控制器驱动决定时候需要使用DMA进行传输(host->dodma)。

第五步,和其他驱动一样,获得寄存器空间,申请IO内存,并完成映射工作。

第六步,和其他驱动一样,获取IRQ号,并注册该irq,中断服务程序(ISR)为s3cmci_irq函数,并且关闭中断。

第七步,根据板级设备信息,判断是否使用探测(detect)功能,如果使用则向gpio子系统申请该引脚,并为该gpio注册中断服务程序。

中断服务程序为s3cmci_irq_cd函数,上下沿有效,该函数就是用来判断SD卡是否插入卡槽中的。。

第八步,根据板级设备信息,判断是否使用写保护(no_wprotect)功能,如果使用则向gpio子系统申请该引脚。

第九步,如果使用DMA传输,则对DMA进行相关的初始化工作。

第十步,获取clk,并使能,然后获取时钟速率。

第十一步,设置控制器的访问函数集为s3cmci_ops,该结构体包括了MMC控制器行为的抽象,非常重要,我们来看下:

[cpp]
view plaincopy





static struct mmc_host_ops s3cmci_ops = {
.request = s3cmci_request,
.set_ios = s3cmci_set_ios,
.get_ro = s3cmci_get_ro, /* 判断是否只读*/
.get_cd = s3cmci_card_present, /* 判断card是否存在*/
.enable_sdio_irq = s3cmci_enable_sdio_irq,
};

struct mmc_host_ops其中包含了很多MMC控制器行为的抽象,S3C2440的MMC控制器驱动只使用了5个,功能如下:
第一个是请求函数,用于向SD卡发送命令,并获取应答。

第二个用于设置MMC控制器参数。

第三个用于判断SD卡是否为只读。

第四个用于判断SD卡是否在卡槽内。

第五个用户使能sdio中断。

接下来开始初始化struct mmc_host 结构体里的字段,这些字段的具体含义就不细说了。

第十二步,调用s3cmci_cpufreq_register注册一个通知链,有关通知链的东西就不在这里赘述了。

第十三步,调用mmc_add_host注册MMC控制器,该函数将在1.2小结叙说。

第十三步,调用s3cmci_debugfs_attach向debuf文件系统注册有关访问接口,debugfs有关的我们就略过了。

第十四步,保存mmc至驱动的私有平台数据中(dpev->dev->p->driver_data)。

作为MMC控制器的probe方法,自然而然的需要注册MMC控制器,从上面我们可以看到在进行大量的初始化工作后,最终在第十三步注册该控制器驱动。

下以小结我们来看看这个函数干了点什么。

1.2 函数mmc_add_host的使命

下列代码位于:linux/drivers/mmc/core/host.c

[cpp]
view plaincopy





/**
* mmc_add_host - initialise host hardware
* @host: mmc host
*
* Register the host with the driver model. The host must be
* prepared to start servicing requests before this function
* completes.
*/
int mmc_add_host(struct mmc_host *host)
{
int err;

WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);

/* 注册控制器设备实例*/
err = device_add(&host->class_dev);
if (err)
return err;

/* 注册一个led trigger*/
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);

#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
mmc_host_clk_sysfs_init(host);


mmc_start_host(host);

/* 注册一个通知链*/
register_pm_notifier(&host->pm_notify);

return 0;
}

EXPORT_SYMBOL(mmc_add_host);

该函数首先调用device_add注册了一个设备实例,随后注册了一个led trigger。
并调用mmc_host_clk_sysfs_init,后者利用sysfs向用户提供了门控相关的信息,

接着调用mmc_start_host来启动MMC控制器的时钟,并且判断SD卡是否已在卡槽中。

最后,调用register_pm_notifier向注册了一个用于电源管理的通知链。

很明显,这里调用的5个函数,我们需要关心的是mmc_start_host函数。

[cpp]
view plaincopy





void mmc_start_host(struct mmc_host *host)
{
host->f_init = max(freqs[0], host->f_min);
host->rescan_disable = 0;
/* 必须重新scan才能上电,则关闭mmc控制器*/
if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)
mmc_power_off(host); /* 关闭MMC控制器*/
else
mmc_power_up(host); /* 打卡MMC控制器*/
mmc_detect_change(host, 0);
}

MMC_CAP2_NO_PRESCAN_POWERUP 表示需要在上电前扫面SD卡,
如果定义了该宏,则需要关闭MMC控制器的电压,否则打开电源,该宏默认是不定义的。

mmc_power_up函数还是比较复杂的,不过它不是我们关心的重点,简单说句,该函数最后会调用在1.1小结中MMC控制器的抽象行为函数中的

set_ios来使能MMC控制器的电源。

该函数最后调用mmc_detect_change来探测SD卡是否存在。

[cpp]
view plaincopy





/**
* 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)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
host->detect_change = 1;
mmc_schedule_delayed_work(&host->detect, delay);
}

最开始的宏中的代码可以直接略过了。

将标志位detect_change置1后,函数紧接着调用了mmc_schedule_delayed_work函数,该函数如下:

[cpp]
view plaincopy





/*
* 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);
}

该函数简单调用了queue_delayed_work来添加一个工作到工作队列workqueue中。
基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(一)中的第三章,MMC子系统在初始化时就注册了一个工作队列,并保存到全局变量workqueue中,

同时函数将detect工作添加到了该工作队列中,而detect工作的执行函数即为在1.1中分析时说道的mmc_rescan函数。

因此该函数的作用就是将detec表示的工作函数mmc_rescan添加到了工作队列中。

创建完工作队列后,mmc_start_host函数的使命也算结束了,它的存在极其短暂,但是它却调用了一个非常关键的函数mmc_detect_change,

后者创建的工作函数mmc_rescan将会扫描是否有SD卡插入。

2. SD卡扫描函数mmc_rescan

整个mmc_rescan函数执行分为两个部分。

第一部分,探测SD卡是否存在。

第二部分,按照SD规范,初始化SD卡。

2.1 detect SD卡

下列代码位于:linux/drivers/mmc/core/core.c

[cpp]
view plaincopy





void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;

/* host禁止再次scan,则直接返回*/
if (host->rescan_disable)
return;

/* If there is a non-removable card registered, only scan once */
/* 是不可插拔的sd卡,则只scan一次*/
if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
return;
host->rescan_entered = 1; /* 表示至少scan一次了*/

mmc_bus_get(host); /* 增加总线引用技术*/

/*
* if there is a _removable_ card registered, check whether it is
* still present
*/
/* 如果总线提供detect方法则调用*/
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 */
/* 有card存在,无需继续了*/
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);

/* 调用get_cd方法,判断card是否存在*/
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);
}

该函数首先判断rescan_disable是否为真,如果为真则不用扫描SD卡,直接推出了。
在mmc_start_host时,该变量被置为0,因此这里函数不返回,程序继续往下走。



未完,待续。。。。。。。。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: