MMC的体系结构,其分为三层
/dev下设备文件访问MMC/SD/SDIO
用户空间 |
--------------------|-----------------------------------------------------
内核空间 \ /
MMC Card层(对应具体的设备驱动,如MMC/SD卡块设备驱动,SDIO UART)
|
\ /
MMC core层(为上次设备驱动实现提供操作接口,和下层host注册提供机制)
|
\ /
Host层(具体MMC/SD/SDIO控制器驱动层。如S3C2440 MMC/SD控制器驱动)
|
\ /
-----------------------------------------------------------------------------
硬件层
MMC/SD模块中最重要的部分是Core核心层,他提供了一系列的接口函数,对上提供了将主机驱动注册到系统,给应用程序提供设备访问接口,对下提供了对主机控制器控制的方法及块设备请求的支持。对于主机控制器的操作就是对相关寄存器进行读写,而对于MMC/SD设备的请求处理则比较复杂。
在主机驱动层中的一个请求处理的提交转换过程:
命令、数据发送流程如下图:
分析MMC/SD卡设备驱动程序
Mini2440 MMC/SD硬件接口电路原理图如下:
从Mini2440原理图可以看出,Mini2440 SDI使用的GPE7-GPE10作为4根数据信号线,使用GPE6作为命令信号线,使用GPE5作为时钟信号线。另外,使用GPG8的外部中断功能来作SD卡的插拨检测,使用GPH8来判断SD卡是否有写保护。
在arch/arm/mach-s3c24xx/mach-mini2440.c文件中,进行设备注册:
static struct platform_device *mini2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_rtc,
&s3c_device_usbgadget,
&mini2440_device_eth,
&mini2440_led1,
&mini2440_led2,
&mini2440_led3,
&mini2440_led4,
&mini2440_button_device,
&s3c_device_nand,
&s3c_device_sdi, //Mini2440的SDI控制器
&s3c_device_iis,
&uda1340_codec,
&mini2440_audio,
};
s3c_device_sdi
定义在arch/arm/plat-samsung/devs.c文件中:
struct platform_device s3c_device_sdi = {
.name = "s3c2410-sdi",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_sdi_resource),
.resource = s3c_sdi_resource,
};
其中,s3c_sdi_resource
定义在arch/arm/plat-samsung/devs.c文件中:
static struct resource s3c_sdi_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_SDI, S3C24XX_SZ_SDI),
[1] = DEFINE_RES_IRQ(IRQ_SDI),
};
宏S3C24XX_PA_SDI
定义在arch/arm/mach-s3c24xx/include/mach/map.h文件中:
#define S3C24XX_PA_SDI S3C2410_PA_SDI
宏S3C2410_PA_SDI
定义在arch/arm/mach-s3c24xx/include/mach/map.h文件中:
#define S3C24XX_SZ_SDI SZ_1M
宏S3C2410_PA_SDI
定义在arch/arm/mach-s3c24xx/include/mach/map.h文件中:
/* SDI */
#define S3C2410_PA_SDI (0x5A000000)
0x5A000000
是S3C2440 SDICON寄存器的地址。
宏IRQ_SDI
定义在arch/arm/mach-s3c24xx/include/mach/irqs.h文件中:
#define S3C2410_CPUIRQ_OFFSET (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
.....
#define IRQ_SDI S3C2410_IRQ(21)
在Mini2440的平台初始化中将调用 platform_add_devices
将mini2440_devices
,中的设备资源全部注册为平台设备,也包括s3c_device_sdi
。
Mini2440的SDI驱动定义在drivers/mmc/host/s3cmci.c文件中:
static struct platform_driver s3cmci_driver = {
.driver = {
.name = "s3c-sdi",
},
.id_table = s3cmci_driver_ids,
.probe = s3cmci_probe,
.remove = s3cmci_remove,
.shutdown = s3cmci_shutdown,
};
s3cmci_driver_ids
定义在drivers/mmc/host/s3cmci.c文件中:
static const struct platform_device_id s3cmci_driver_ids[] = {
{
.name = "s3c2410-sdi",
.driver_data = 0,
}, {
.name = "s3c2412-sdi",
.driver_data = 1,
}, {
.name = "s3c2440-sdi",
.driver_data = 1,
},
{ }
};
注册s3cmci_driver
的过程中,会触发s3cmci_probe
函数的执行。
s3cmci_probe
函数,它定义在drivers/mmc/host/s3cmci.c文件中,其内容如下:
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; //由s3cmci_driver_ids,可知driver_data=1
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);//初始化host,被分配mmc
if (!mmc) {
ret = -ENOMEM;
goto probe_out;
}
for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {
ret = gpio_request(i, dev_name(&pdev->dev)); //请获取GPE5-GPE10,从Mini2440原理图可以看出,Mini2440SDI使用的GPE7-GPE10作为4根数据信号线,使用GPE6作为命令信号线,使用GPE5作为时钟信号线。另外,使用GPG8的外部中断功能来作SD卡的插拨检测,使用GPH8来判断SD卡是否有写保护。
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); //取得s3cmci_host指针变量host
host->mmc = mmc;
host->pdev = pdev;
host->is2440 = is2440;
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);
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;
host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);//取得IORESOURCE_MEM类型资源
if (!host->mem) {
dev_err(&pdev->dev,
"failed to get io memory region resource.\n");
ret = -ENOENT;
goto probe_free_gpio;
}
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;
}
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;
}
if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
//申请中断,中断处理函数是s3cmci_irq
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;
if (!host->pdata->no_detect) {
//处理SD卡探测
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;
}
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;
if (!host->pdata->no_wprotect) {
//处理SD卡写保护
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. */
if (s3cmci_host_usedma(host)) { //处理DMA
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
host->dma = dma_request_slave_channel_compat(mask,
s3c24xx_dma_filter, (