S3C2440上MMC/SD卡驱动实例开发讲解
嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能 给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。
目录 |
一、开发环境
主 机:VMWare--Fedora 9
开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
编译器:arm-linux-gcc-4.3.2
二、MMC/SD介绍及SDI主机控制器
首先我们来理清几个概念:
1 MMC:(Multi Media Card)由西门子公司和首推CF的SanDisk于1997年推出的多媒体记忆卡标准。
2 SD:(Secure Digital Memory Card)由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制的新一代记忆卡标准,已完全兼容MMC标准。
3 SDIO:(Secure Digital Input and Output Card)安全数字输入输出卡。SDIO是在SD标 准上定义了一种外设接口,通过SD的I/O接脚来连接外围设备,并且通过SD上的 I/O数据接位
与这些外围设备进行数据传输。是目前较热门的技术,如下图中的一些设备:GPS、相机、Wi-Fi、调频广播、条形码读卡器、蓝牙等。
4 工作模式:工作模式是针对主机控制器来说的。也就是说,S3C2440中的SDI 控制器可以在符合MMC的标准下工作,或者可以在符合SD的标准下工作,或者可以在符合SDIO的标准下工作。故就分别简称为:MMC模式、SD模式和 SDIO模式。
5 传输模式:传输模式也是针对主机控制器来说的,指控制器与卡之间数据的传输模式, 或者说是总线类型。S3C2440中的SDI控制器可支持SPI、1位和4位的三种传输模式(总线类型)。那么什么又是SPI呢?请参考这里:SPI协议简介;至于1位和4位又是什么意思呢?他们是指传输数据总线的线宽,具体参考数据手册。
下面使用表格列出了MMC、SD、SDIO的电气特性及性能和不同工作模式下支持的传输模式情况:
那么,我们现在怎样让主机控制器在我们所要求的工作模式和传输模式上工作呢?很简单,就是对主机控制器的各个寄存器进行相应的配置 即可。下面来简单介绍一下SDI主机控制器的结构和各寄存器的用途。
S3C2440内的SDI主机控制器结构图如下:
如上图所示,SDI主机控制器是使用1个串行时钟线与5条数据线同步进行信息移位和采样。传输频率通过设定SDIPRE寄存器的相应位的设定来 控制,可以修改频率来调节波特率数据寄存器的值。
各主要寄存器介绍,对于具体的寄存器位的设置就参考数据手册:
SDICON:控制寄存器,完成SD卡基础配置,包括大小端,中断允许,模式选择,时钟使能 等。
SDIPRE:波特率预定标器寄存器,对SDCLK的配置。
SDICmdArg:指令参数寄存器,指令的参数存放在这里。
SDICCON:控制指令形式的寄存器,配置SPI还是SDI指令,指令的反馈长 度,是否等待反馈,是否运行指令,指令的索引等。
SDICmdSta:指令状态寄存器,指令是否超时,传送,结束,CRC是否正确 等。
SDIRSP0-3:反映SD的状态。
SDIDTimer:设置超时时间。
SDIBSize:模块大小寄存器。
SDIDatCon:数据控制寄存器,配置是几线传输,数据发送方向,数据传送方 式等。
SDIDatSta:数据状态寄存器,数据是否发送完,CRC效验,超时等。
SDIFSTA:FIFO状态寄存器,DMA传输是否判断FIFO。
SDIIntMsk:中断屏蔽寄存器。
SDIDAT:SDI数据寄存器。
SDI主机控制器在SD/MMC工作模式下的设置步骤:(注意:因为SD模式兼容MMC模式,所以我们只需了解SD模式的即可,而SDIO的工作模 式则是针对SDIO设备的,所以这里就不讨论了)
设置SDICON寄存器来配置适当的时钟及中断使能;
设置SDIPRE寄存器适当的值;
等待74个SDCLK时钟以初始化卡;
命令操作步骤:
a. 写命令参数32位到SDICmdArg寄存器;
b. 设置命令类型并通过设置SDICCON寄存器开始命令传输;
c. 当SDICSTA寄存器的特殊标志被置位,确认命令操作完成;
d. 如果命令类型相应,标志是RspFin,否则标志是CmdSend;
e. 通过对相应位写1,清除SDICmdSta的标志。
数据操作步骤:
a. 写数据超时时间到SDIDTimer寄存器;
b. 写模块大小到SDIBSize寄存器(通常是0x80字节);
c. 确定模块模式、总线线宽、DMA等且通过设置SDIDatCon寄存器开始数据传输;
d. 发送数据->写数据到SDIDAT寄存器,当发送FIFO有效(TFDET置位),或一半(TFHalf置位),或空(TFEmpty置位);
e. 接收数据->从数据寄存器SDIDAT读数据,当接收FIFO有效(RFDET置位),或满(RFFull置位),或一半(RFHalf置位),或 准备最后数据(RFLast置位);
f. 当SDIDatSta寄存器的DatFin标志置位,确认数据操作完成;
g. 通过对相应位写1,清除SDIDatSta的标志。
三、MMC/SD协议
这里我并不是要讨论MMC/SD的整个通信协议(详细的协议请看MMC/SD规范),而是遵循MMC/SD协议了解一下MMC/SD在被驱动的 过程中卡所处的各种阶段和状态。根据协议,MMC/SD卡的驱动被分为:卡识别阶段和数据传输阶段。在卡识别阶段通过命令使MMC/SD处于:空闲 (idle)、准备(ready)、识别(ident)、等待(stby)、不活动(ina)几种不同的状态;而在数据传输阶段通过命令使MMC/SD处 于:发送(data)、传输(tran)、接收(rcv)、程序(prg)、断开连接(dis)几种不同的状态。所以可以总结MMC/SD在工作的整个过 程中分为两个阶段和十种状态。下面使用图形来描述一下在两个阶段中这十种状态之间的转换关系。
卡识别阶段,如下图:
数据传输阶段,如下图:
四、MMC/SD设备驱动在Linux中的结构层次
我们翻开MMC/SD设备驱动代码在Linux源码中的位置 /linux-2.6.30.4/drivers/mmc/,乍一看,分别有card、core和host三个文件夹,那哪一个文件才是我们要找的驱动代 码文件啊?答案是他们都是。不是吧,听起来有些可怕,三个文件夹下有多少代码啊。呵呵,还是先放下对庞大而又神秘代码的恐惧感吧,因为在实际驱动开发中, 其实只需要在host文件夹下实现你具体的MMC/SD设备驱动部分代码,现在你的心情是不是要好点了。具体的MMC/SD设备是什么意思呢?他包括 RAM芯片中的SDI控制器(支持对MMC/SD卡的控制,俗称MMC/SD主机控制器)和SDI控制器与MMC/SD卡的硬件接口电路。
那为什么刚才又说card、core和host都是MMC/SD设备的驱动呢?这就好比我们建房子,建房子首先要的是什么,地皮对吧, 有了地皮然后要到政府部门备案,备案后才能开始建,是这样的吧。在Linux中MMC/SD卡的记忆体都当作块设备。那么,我们这里的card层就是要把 操作的数据以块设备的处理方式写到记忆体上或从记忆体上读取,就好比是在地皮上填沙石、挖地基等;core层则是将数据以何种格式,何种方式在 MMC/SD主机控制器与MMC/SD卡的记忆体(即块设备)之间进行传递,这种格式、方式被称之为规范或协议,就好比到政府部门备案,备案就会要求你的 房子应该按照怎样的行业标准进行建造;最后只剩下host层了,上面也讲到了,host层下的代码就是你要动手实现的具体MMC/SD设备驱动了,就好比 现在地皮买好挖好了,建房的标准也定好了,剩下的就需要人开始动工了。
那么,card、core和host这三层的关系,我们用一幅图来进行描述,图如下:
从这幅图中的关系可以看出,整个MMC/SD模块中最重要的部分是Core核心层,他提供了一系列的接口函数,对上提供了将主机驱动注册到系统,给应用程 序提供设备访问接口,对下提供了对主机控制器控制的方法及块设备请求的支持。对于主机控制器的操作就是对相关寄存器进行读写,而对于MMC/SD设备的请 求处理则比较复杂。那么在主机驱动层中的一个请求处理是怎么通过核心层提交到块设备请求层的呢?
在网上找到一副图来说明他们之间的关联和处理流程,如下图:
命令、数据发送流程如下图:
其中,黑色粗线部分为命令发送或者数据发送都要经过的流程,橙色方框部分判断所有类型的请 求是否完成。
下面我们就来具体实例分析一个MMC/SD卡设备驱动程序。
五、实例分析MMC/SD卡设备驱动程序
1 Mini2440开发板的MMC/SD硬件接口电路原路图如下:
从电路原理图上可以看出,SD分别使用S3C2440的复用IO端口GPE7-10作为4根数据信号线、使用 GPE6作命令信号线、使用GPE5作时钟信号线,使用复用端口GPG8的外部中断功能来作SD卡的插拔检测,使用GPH8端口来判断SD卡是否写有保 护。
2 MMC/SD卡驱动程序的重要数据结构,该结果位于Core核心层,主要用于核心 层与主机驱动层的数据交换处理。定义在/include/linux/mmc/host.h中:
struct mmc_host { struct device *parent; struct device class_dev; int index; const struct mmc_host_ops *ops; unsigned int f_min; unsigned int f_max; u32 ocr_avail; #define MMC_VDD_165_195 0x00000080 /* VDD voltage 1.65 - 1.95 */ #define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */ #define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */ #define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */ #define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */ #define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */ #define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */ #define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */ #define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */ #define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */ #define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */ #define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */ #define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */ #define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */ #define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */ #define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */ #define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */ unsigned long caps; /* Host capabilities */ #define MMC_CAP_4_BIT_DATA (1 << 0)/* Can the host do 4 bit transfers */ #define MMC_CAP_MMC_HIGHSPEED (1 << 1)/* Can do MMC high-speed timing */ #define MMC_CAP_SD_HIGHSPEED (1 << 2)/* Can do SD high-speed timing */ #define MMC_CAP_SDIO_IRQ (1 << 3)/* Can signal pending SDIO IRQs */ #define MMC_CAP_SPI (1 << 4)/* Talks only SPI protocols */ #define MMC_CAP_NEEDS_POLL (1 << 5)/* Needs polling for card-detection */ #define MMC_CAP_8_BIT_DATA (1 << 6)/* Can the host do 8 bit transfers */ /* host specific block data */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ unsigned short max_hw_segs; /* see blk_queue_max_hw_segments */ unsigned short max_phys_segs; /* see blk_queue_max_phys_segments */ unsigned short unused; unsigned int max_req_size; /* maximum number of bytes in one req */ unsigned int max_blk_size; /* maximum size of one mmc block */ unsigned int max_blk_count; /* maximum number of blocks in one req */ /* private data */ spinlock_t lock; /* lock for claim and bus ops */ struct mmc_ios ios; /* current io bus settings */ u32 ocr; /* the current OCR setting */ /* group bitfields together to minimize padding */ unsigned int use_spi_crc:1; unsigned int claimed:1; /* host exclusively claimed */ unsigned int bus_dead:1; /* bus has been released */ #ifdef CONFIG_MMC_DEBUG unsigned int removed:1; /* host is being removed */ #endif struct mmc_card *card; /* device attached to this host */ wait_queue_head_t wq; struct delayed_work detect; const struct mmc_bus_ops *bus_ops; /* current bus driver */ unsigned int bus_refs; /* reference counter */ unsigned int sdio_irqs; struct task_struct *sdio_irq_thread; atomic_t sdio_irq_thread_abort; #ifdef CONFIG_LEDS_TRIGGERS struct led_trigger *led; /* activity led */ #endif struct dentry *debugfs_root; unsigned long private[0] ____cacheline_aligned; };
3 MMC/SD卡驱动程序的头文件中一些变量的定 义,这些变量在驱动中都会用到。先不用看这些变量将用做什么,等驱动中用到时自然就明白了。代码如下:
#define S3CMCI_DMA 0 enum s3cmci_waitfor { COMPLETION_NONE, COMPLETION_FINALIZE, COMPLETION_CMDSENT, COMPLETION_RSPFIN, COMPLETION_XFERFINISH, COMPLETION_XFERFINISH_RSPFIN, }; struct s3cmci_host { struct platform_device *pdev; struct s3c24xx_mci_pdata *pdata; struct mmc_host *mmc; struct resource *mem; struct clk *clk; void __iomem *base; int irq; int irq_cd; int dma; unsigned long clk_rate; unsigned long clk_div; unsigned long real_rate; u8 prescaler; unsigned sdiimsk; unsigned sdidata; int dodma; int dmatogo; struct mmc_request *mrq; int cmd_is_stop; spinlock_t complete_lock; enum s3cmci_waitfor complete_what; int dma_complete; u32 pio_sgptr; u32 pio_bytes; u32 pio_count; u32 *pio_ptr; #define XFER_NONE 0 #define XFER_READ 1 #define XFER_WRITE 2 u32 pio_active; int bus_width; char dbgmsg_cmd[301]; char dbgmsg_dat[301]; char *status; unsigned int ccnt, dcnt; struct tasklet_struct pio_tasklet; #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; #endif };
4 MMC/SD卡驱动程序的加载与卸载部分:
在 Linux中,MMC/SD设备是被作为平台设备添加到系统的。可以查看内核代码:/arch/arm/plat-s3c24xx/devs.c中为 MMC/SD主机控制器SDI定义了平台设备和平台设备资源,然后在/arch/arm/mach-s3c2440/mach-smdk2440.c中的 系统初始化的时候添加到系统中。如下:
// 平台设备资源 static struct resource s3c_sdi_resource[] = { [0] = { .start = S3C24XX_PA_SDI, .end = S3C24XX_PA_SDI + S3C24XX_SZ_SDI - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_SDI, .end = IRQ_SDI, .flags = IORESOURCE_IRQ, } }; //定义 SDI平台设备 struct platform_device s3c_device_sdi = { .name = "s3c2410-sdi", .id = -1, .num_resources = ARRAY_SIZE(s3c_sdi_resource), .resource = s3c_sdi_resource, }; EXPORT_SYMBOL(s3c_device_sdi); //添加 SDI平台设备到平台设备列表 static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_sdi, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_rtc, &s3c_device_dm9000, . . . }; //平台 设备添加到系统 static void __init smdk2440_machine_init(void) { . . . platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); smdk_machine_init(); }
所以,MMC/SD设备驱动程序的加载和卸载部分很简单,就是注册和注销平台设备,代码如下:
//加载 static int __init s3cmci_init(void) { platform_driver_register(&s3cmci_driver); return 0; } //卸载 static void __exit s3cmci_exit(void) { platform_driver_unregister(&s3cmci_driver); } //平台设备操作结构体 static struct platform_driver s3cmci_driver = { .driver.name = "s3c2410-sdi",// 名称和平台设备定义中的对应 .driver.owner = THIS_MODULE, .probe = s3cmci_probe,// 平台设备探测接口函数 .remove = __devexit_p(s3cmci_remove),//__devexit_p的作用以前将过 .shutdown = s3cmci_shutdown, .suspend = s3cmci_suspend, .resume = s3cmci_resume, };
平台探测函数s3cmci_probe的讲解:
static int __devinit s3cmci_probe(struct platform_device *pdev) { //该结构体定义在头文件中,现在实例 一个名为host的结构体指针为结构体中的成员赋值做准备 struct s3cmci_host *host; //实例一个名为mmc的结构体指针, 用于与Core核心层中的mmc_host结构体指针相关联 struct mmc_host *mmc; int ret; //初始化一个名为 complete_lock的自旋锁以备后用,该自旋锁的定义在s3cmci_host结构体中 spin_lock_init(&host->complete_lock); //初始化一个名为pio_tasklet的tasklet,用于实现中断的底半部 机制,底半部服务函数为pio_tasklet, //将host结构体变量作为服务函数的参数。注意:这里tasklet的变量名与 服务函数名称同名了(这是可以的)。 tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host); //分配 mmc_host结构体指针的内存空间大小,该函数在host.c中实现,这里要注意一点,为什么参数 //是s3cmci_host结构体 的大小,到host.c中看,实际这里分配的是mmc_host加s3cmci_host的大小。 mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); if (!mmc) { ret = -ENOMEM; goto probe_out; } //调用mmc_priv函数将 mmc_host和s3cmci_host结构体的对象关联起来,mmc_priv定义在host.h中 host = mmc_priv(mmc); //下面就开始初始化 s3cmci_host结构体的各成员 host->mmc = mmc; host->pdev = pdev; host->pdata = pdev->dev.platform_data; //SDI主机控制器的中断屏蔽寄存 器和数据寄存器,他们定义在mach-s3c2410/include/mach/regs-sdi.h中 host->sdiimsk = S3C2440_SDIIMSK; host->sdidata = S3C2440_SDIDATA; //complete_what定义 在s3cmci_host结构体中,用来记录请求处理所处的当前状态,这里初始化为 //COMPLETION_NONE即 无状态,定义在头文件的s3cmci_waitfor中,里面枚举了6种状态。 host->complete_what = COMPLETION_NONE; //pio_active定义在 s3cmci_host结构体中,用来标记请求处理数据在FIFO方式下的数据方向是读还是写 host->pio_active = XFER_NONE; //dodma和dma方便用于标记 是否要使用DMA数据传输方式和DMA通道资源,0表示不使用DMA功能 host->dodma = 0; host->dma = S3CMCI_DMA; //从SDI平台设备资源中获取 SDI的IO端口资源,该资源在plat-s3c24xx/devs.c的s3c_sdi_resource中指定的 host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!host->mem) { dev_err(&pdev->dev, "failed to get io memory region resouce.\n"); ret = -ENOENT; goto probe_free_host; } //申请SDI的IO端口资源所占用的IO空间(要注意理解IO空间和内存空间的区 别) host->mem = request_mem_region(host->mem->start, RESSIZE(host->mem), pdev->name); if (!host->mem) { dev_err(&pdev->dev, "failed to request io memory region.\n"); ret = -ENOENT; goto probe_free_host; } //将SDI的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定 义在io.h中。 //注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作。 host->base = ioremap(host->mem->start, RESSIZE(host->mem)); if (!host->base) { dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); ret = -EINVAL; goto probe_free_mem_region; } //同样从SDI平台设备资源中获取SDI的中断号 host->irq = platform_get_irq(pdev, 0); if (host->irq == 0) { dev_err(&pdev->dev, "failed to get interrupt resouce.\n"); ret = -EINVAL; goto probe_iounmap; } //申请SDI的中断服务,服务函数为 s3cmci_irq,主要参数为host 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; } //在SDI未准备好之前先屏蔽SDI的中断功能 disable_irq(host->irq); //根据开发板原理图分别设置GPG8、GPH8端口为SD卡插入拔出的检测和有无 写保护的检查, //注意:其实有没有写保护就是检查SD卡侧面有个移动按钮的开关,MMC卡无此功能 host->pdata->gpio_detect = S3C2410_GPG8; host->pdata->gpio_wprotect = S3C2410_GPH8; // 获取GPG8复用端口中断功能的中断号 host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect); //GPG8是复用端口,要使用中断功 能则要配置成中断功能,GPG8对应的中断功能是外部中断EINT16,这个数据手册上有讲到 s3c2410_gpio_cfgpin(S3C2410_GPG8, S3C2410_GPG8_EINT16); //申请SDI的卡检测中断服务,服 务函数为s3cmci_irq_cd,主要参数也为host 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_irq; } //获取DMA通道并申请DMA中 断,S3C2440中DMA的使用在后续的文章中再了解 if (s3c2410_dma_request(S3CMCI_DMA, &s3cmci_dma_client, NULL) < 0) { dev_err(&pdev->dev, "unable to get DMA channel.\n"); ret = -EBUSY; goto probe_free_irq_cd; } //从平台时钟队列中获取SDI的时钟源,在arch/arm/plat- s3c24xx/s3c2410-clock.c中有定义 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_host; } //启动获取的时钟源 ret = clk_enable(host->clk); if (ret) { dev_err(&pdev->dev, "failed to enable clock source.\n"); goto clk_free; } //通过SDI的时钟源获取CPU的 PCLK频率,这里为什么要获得CPU的PCLK频率呢, //通过数据手册SDI控制器的方框图得知,SDI的时钟频率 (SDCLK)=PCLK/(Prescaler+1) host->clk_rate = clk_get_rate(host->clk); host->clk_div = 1;//设置预分频值,即:Prescaler的值 //下面对mmc_host进行初始化 mmc->ops = &s3cmci_ops; //SDI主机控制器操作结构体 mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; //设置工作电压范围 mmc->caps = MMC_CAP_4_BIT_DATA; //设置总线宽度为4位 mmc->f_min = host->clk_rate / (host->clk_div * 256); //设置最小工作频率 mmc->f_max = host->clk_rate / host->clk_div; //设置最大工作频率 mmc->max_blk_count = 4095; mmc->max_blk_size = 4095; mmc->max_req_size = 4095 * 512; mmc->max_seg_size = mmc->max_req_size; mmc->max_phys_segs = 128; mmc->max_hw_segs = 128; //Linux的通知链机制,实现到后面再讲 ret = s3cmci_cpufreq_register(host); if (ret) { dev_err(&pdev->dev, "failed to register cpufreq\n"); goto free_dmabuf; } //将SDI host设备注册到系统中 ret = mmc_add_host(mmc); if (ret) { dev_err(&pdev->dev, "failed to add mmc host.\n"); goto free_cpufreq; } //将SDI host设备的数据赋值给系统平台设备 platform_set_drvdata(pdev, mmc); return 0; //以下是错误处理 free_cpufreq: s3cmci_cpufreq_deregister(host); free_dmabuf: clk_disable(host->clk); clk_free: clk_put(host->clk); 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, RESSIZE(host->mem)); probe_free_host: mmc_free_host(mmc); probe_out: return ret; } Linux 的通知链机制 <source lang="c"> //Linux的通知链机制,需要内核配置时的支持。 //这个通知链机制在MMC/SD卡驱动中应用的目的是,当CPU频率发生改变时,MMC/SD时钟频率也要改变。 //具体通知链机制的原理请看下一篇转载的文章。 #ifdef CONFIG_CPU_FREQ static int s3cmci_cpufreq_transition(struct notifier_block *nb, unsigned long val, void *data) { struct s3cmci_host *host; struct mmc_host *mmc; unsigned long newclk; unsigned long flags; host = container_of(nb, struct s3cmci_host, freq_transition); newclk = clk_get_rate(host->clk); mmc = host->mmc; if ((val == CPUFREQ_PRECHANGE && newclk > host->clk_rate) || (val == CPUFREQ_POSTCHANGE && newclk < host->clk_rate)) { spin_lock_irqsave(&mmc->lock, flags); host->clk_rate = newclk; if (mmc->ios.power_mode != MMC_POWER_OFF && mmc->ios.clock != 0) s3cmci_set_clk(host, &mmc->ios); spin_unlock_irqrestore(&mmc->lock, flags); } return 0; } static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) { host->freq_transition.notifier_call = s3cmci_cpufreq_transition; return cpufreq_register_notifier(&host->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); } static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host) { cpufreq_unregister_notifier(&host->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); } #else//如果内核配置时没有选择此功能支持,则其实现为空即可 static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) { return 0; }
从 探测函数中可以看到,我们接下来要实现的功能就很清晰了。他们分别是:
a. s3cmci_ops SDI主机控制器操作接口函数功能;
b. s3cmci_irq_cd SDI的卡检测中断服务功能;
c. s3cmci_irq SDI的中断服务功能;
</source>
6. s3cmci_ops SDI主机控制器操作接口函数功能分析:
static struct mmc_host_ops s3cmci_ops = { .request = s3cmci_request,//实现host的请求处理(即:命令和数据的发送和接收) .set_ios = s3cmci_set_ios,//通过核心层传递过来的ios,配置host寄存器(使能时钟、总线带宽等) .get_ro = s3cmci_get_ro,//通过读取GPIO端口来判断卡是否写有保护 .get_cd = s3cmci_card_present,//通过读取GPIO端口来判断卡是否存在 };
mmc_host_ops结构体定义了对host主机进行操作的各种方法,其定义在Core核心层的host.h中,也就是Core核心层对Host主机层提供的接口函数。这里各种方法的函数原型如下:
void (*request)(struct mmc_host *host, struct mmc_request *req); void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); int (*get_ro)(struct mmc_host *host); int (*get_cd)(struct mmc_host *host);
从各函数原型上看,他们都将mmc_host结构体作为参数,所以我在刚开始的时候就说过mmc_host结构体是MMC/SD卡驱动中比较重要的数据结构。 可以这样说,他是Core层与Host层进行数据交换的载体。那么,这些接口函数何时会被调用呢?答案可以在Core层的core.c和sd.c中找到,我们可以看到如下部分代码:
static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) { ...... host->ops->request(host, mrq);//导致s3cmci_request被调用 } static inline void mmc_set_ios(struct mmc_host *host) { ...... host->ops->set_ios(host, ios);//导致s3cmci_set_ios被调用 } void mmc_rescan(struct work_struct *work) { ......//导致s3cmci_card_present被调用 if (host->ops->get_cd && host->ops->get_cd(host) == 0) goto out; ...... } static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard) { ...... /* Check if read-only switch is active.*/ if (!oldcard) { //导致s3cmci_get_ro被调用 if (!host->ops->get_ro || host->ops->get_ro(host) < 0) { printk(KERN_WARNING "%s: host does not " "support reading read-only " "switch. assuming write-enable.\n", mmc_hostname(host)); } else { if (host->ops->get_ro(host) > 0) mmc_card_set_readonly(card); } } ...... }
好了,我们开始分析每个接口函数的具体实现吧,从简单的开始吧。 判断卡是否存在,如下代码:
static int s3cmci_card_present(struct mmc_host *mmc) { //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的 struct s3cmci_host *host = mmc_priv(mmc); struct s3c24xx_mci_pdata *pdata = host->pdata; int ret; //判断有无设置卡检测引脚端口,引脚在s3cmci_probe函数中已设置 if (pdata->gpio_detect == 0) return -ENOSYS; //从设置的卡检测引脚中读出当前的电平值,来判断卡是插入存在的还是被拔出不存在的 ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1; return ret ^ pdata->detect_invert; }
获取卡是否写有保护,其实实现跟卡检查类似,代码如下:
static int s3cmci_get_ro(struct mmc_host *mmc) { //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的 struct s3cmci_host *host = mmc_priv(mmc); struct s3c24xx_mci_pdata *pdata = host->pdata; int ret; //判断有无设置卡写保护引脚端口,引脚在s3cmci_probe函数中已设置 if (pdata->gpio_wprotect == 0) return 0; //从设置的卡写保护引脚中读出当前的电平值,来判断卡是否写有保护 ret = s3c2410_gpio_getpin(pdata->gpio_wprotect); if (pdata->wprotect_invert) ret = !ret; return ret; }
配置host寄存器的时钟和总线宽度,代码如下:
static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的 struct s3cmci_host *host = mmc_priv(mmc); u32 mci_con; //读取SDI控制寄存器的值 mci_con = readl(host->base + S3C2410_SDICON); //ios结构体参数从Core层传递过来,根据不同的电源状态来配置SDI各寄存器 switch (ios->power_mode) { case MMC_POWER_ON: case MMC_POWER_UP: //根据开发板引脚连接情况配置SDI控制器的各信号线,包括:时钟线、命令线和四条数据线 s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK); s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD); s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0); s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1); s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2); s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3); if (host->pdata->set_power) host->pdata->set_power(ios->power_mode, ios->vdd); break; case MMC_POWER_OFF: default: //如果电源状态为关闭或者默认情况下,关闭SDI的时钟信号 s3c2410_gpio_setpin(S3C2410_GPE5, 0); s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP); //根据数据手册的SDICON寄存器位的介绍,此处是将整个sdmmc时钟复位 mci_con |= S3C2440_SDICON_SDRESET; if (host->pdata->set_power) host->pdata->set_power(ios->power_mode, ios->vdd); break; } //设置SDI波特率预定标器寄存器以确定时钟,看其定义部分 s3cmci_set_clk(host, ios); //根据SDI当前的时钟频率来设置寄存器的使能时钟位 if (ios->clock) mci_con |= S3C2410_SDICON_CLOCKTYPE; else mci_con &= ~S3C2410_SDICON_CLOCKTYPE; //将计算好的值写回SDI控制寄存器 writel(mci_con, host->base + S3C2410_SDICON); //下面只是一些调试信息,可以不要 if ((ios->power_mode == MMC_POWER_ON) || (ios->power_mode == MMC_POWER_UP)) { dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n", host->real_rate/1000, ios->clock/1000); } else { dbg(host, dbg_conf, "powered down.\n"); } //设置总线宽度 host->bus_width = ios->bus_width; } //设置SDI波特率预定标器寄存器以确定时钟 static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios) { u32 mci_psc; //根据SDI工作时钟频率范围来确定时钟预分频器值 for (mci_psc = 0; mci_psc < 255; mci_psc++) { host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1)); if (host->real_rate <= ios->clock) break; } //根据数据手册描述,SDI波特率预定标器寄存器只有8个位,所以最大值为255 if (mci_psc > 255) mci_psc = 255; host->prescaler = mci_psc;//确定的预分频器值 //将预分频器值写于SDI波特率预定标器寄存器中 writel(host->prescaler, host->base + S3C2410_SDIPRE); if (ios->clock == 0) host->real_rate = 0; }
MMC/SD请求处理,这是Host驱动中比较重要的一部分。请求处理的整个流程请参考(一)中的流程图,他很好的描述了一个请求是怎样从Host层发出,通过Core层提交到Card层被块设备处理的。下面看代码:
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的 struct s3cmci_host *host = mmc_priv(mmc); //s3cmci_host结构体定义的status主要是记录请求过程所处的阶段及状态,方便调试时使用 host->status = "mmc request"; //请求处理主要包括MMC/SD命令和数据处理,所以定义cmd_is_stop来区分是哪种请求 host->cmd_is_stop = 0; //将Core层的mmc_request对象保存到Host层中以备使用 host->mrq = mrq; //在开始发出一个请求前先要检测一下卡是否还存在,否则提交到了块设备层而没有请求处理的对象发生错误 if (s3cmci_card_present(mmc) == 0) { dbg(host, dbg_err, "%s: no medium present\n", __func__); host->mrq->cmd->error = -ENOMEDIUM; mmc_request_done(mmc, mrq);//如果卡不存在则马上结束这次请求 } else { s3cmci_send_request(mmc);//如果卡还存在则发出请求 } } //发送请求 static void s3cmci_send_request(struct mmc_host *mmc) { //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的 struct s3cmci_host *host = mmc_priv(mmc); //取出在s3cmci_request函数中保存的mmc_request对象以使用 struct mmc_request *mrq = host->mrq; //在s3cmci_request函数中设置的cmd_is_stop初始值为0,表示当前是命令请求 struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; //清空SDI命令状态寄存器、数据状态寄存器和FIFO状态寄存器 writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT); writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA); writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA); //如果当前这次的请求是数据请求 if (cmd->data) { //进入数据请求处理设置,主要是数据控制寄存器的配置 int res = s3cmci_setup_data(host, cmd->data); if (res) { //如果在数据请求设置中出现异常,则马上结束这次请求 dbg(host, dbg_err, "setup data error %d\n", res); cmd->error = res; cmd->data->error = res; mmc_request_done(mmc, mrq); return; } //判断数据处理的方式是DAM还是FIFO,在s3cmci_probe函数中初始的是0,所以没有使用DMA的方式 if (host->dodma) res = s3cmci_prepare_dma(host, cmd->data); else res = s3cmci_prepare_pio(host, cmd->data); if (res) { //如果请求处理数据失败则也要马上结束这次请求 dbg(host, dbg_err, "data prepare error %d\n", res); cmd->error = res; cmd->data->error = res; mmc_request_done(mmc, mrq); return; } } //否则这次请求是命令请求 s3cmci_send_command(host, cmd); //还记得在s3cmci_probe中SDI未准备好是屏蔽了SD中断,所以这里就使能中断 enable_irq(host->irq); }