回顾一下前面的知识,MMC 子系统范围三个部分:
HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。
CORE 部分: 这是整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为HOST 层的驱动提供了接口函数。
CARD 部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 卡如何实现为块设备的。
它们分布于下面的文件夹中 Linux/drivers/mmc中
其中,card(区块层) 与core(核心层)是linux系统封装好了部分,我们不需要修改,host(主控制器层)中提供与各芯片构架相关的文件,这才是我们所要开发的部分
核心层根据需要构造各种MMC/SD命令,这些命令怎么发送给MMC/SD卡呢?这通过主机控制器层来实现。这层是架构相关的,里面针对各款CPU提供一个文件,目前支持的CPU还很少。
以本节即将移植的s3cmci.c为例,它首先进行一些低层设置,比如设置MMC/SD/SDIO控制器使用到的CPIO引脚、使能控制器、注册中断处理函数等,然后向上面的核心层增加一个主机(Host),这样核心层就能调用s3cmci.c提供的函数来识别、使用具体存储卡了。
在向核心层增加主机之前,s3cmci.c 设置了一个mmc_host_ops结构体,它实现两个函数:
a -- 发起访问请求的request函数
b -- 进行一些属性设置(时钟频率、数据线位宽等)的set_ios函数。
下面列出识别存储卡、区块层发起操作请求两种情况下函数的主要调用关系:
1)识别存储卡
2)区块层发起操作请求
以后上次对存储卡的操作都通过调用这两个函数来完成。下面对HOST层进行分析(Linux内核版本:Linux-3.14)。
一、struct mmc_host 结构体
主要用来描述卡控制器位, 结构体mmc_host定义于/include/linux/mmc/host.c,可以认为是linux为SD卡控制器专门准备的一个类,该类里面的成员是所有SD卡控制器都需要的,放之四海而皆准的数据结构,在本例芯片控制器的驱动程序s3cmci.c中,则为该类具体化了一个对象struct mmc_host *mmc,此mmc指针即指代着该ARM芯片SD卡控制器的一个具体化对象(可以看出虽然C是面向过程的语言,但还是用到了一些面向对象思想的)。
在linux/driver/mmc/host/s3cmci.h下定义
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;
int is2440;
unsigned sdiimsk;
unsigned sdidata;
int dodma;
int dmatogo;
bool irq_disabled;
bool irq_enabled;
bool irq_state;
int sdio_irqen;
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_DEBUG_FS
struct dentry *debug_root;
struct dentry *debug_state;
struct dentry *debug_regs;
#endif
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};
其中struct mmc_host(linux/include/linux/mmc/host.h)用于与core层的命令请求,数据 传输等信息,这里代码较长,展示部分
struct mmc_host
{
const struct mmc_host_ops *ops; // SD卡主控制器的操作函数,即该控制器所具备的驱动能力
const struct mmc_bus_ops *bus_ops; // SD总线驱动的操作函数,即SD总线所具备的驱动能力
struct mmc_ios ios; // 配置时钟、总线、电源、片选、时序等
struct mmc_card *card; // 连接到此主控制器的SD卡设备
... ...
};
本文中struct mmc_host_ops *ops定义
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,
.enable_sdio_irq = s3cmci_enable_sdio_irq,
};
二、SD控制器之初始化(linux/driver/mmc/host)
这一层讲述硬件与硬件之间将要发生的故事,也是最底层驱动的核心。通常所谓的驱动程序设计的任务将落实到这一层上,所以关注host故事的发展也将成为移植整个SD类设备驱动的核心。在host 目录中有各种平台下SD 卡主机驱动器的实例,这里我们选择s3c2440平台作为分析的重点。参看Kconfig和Makefile即可获得相应信息,这里对应的文件即是s3cmci.c。
1、设备的注册
旧瓶装新酒,还是那个module_init,不一样的是其中的入口函数。在s3cmci.c中对应的是module_init(s3cmci_init);
module_platform_driver(s3cmci_driver);
这里是不是很奇怪,不应该是module_init 与 module_exit 吗?module_platform_driver其实是一个宏定义,定义在include/linux/platform_device.h文件中:
/* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
宏module_driver定义在include/linux/device.h文件中,其内容如下:
/**
* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
可以看出,最终还是调用
module_init 与 module_exit。这里注册了一个平台驱动,这个前面分析的串口驱动等是一样的原理。这是新版内核中引入的一个虚拟的平台总线。对应的平台设备早在内核启动时通过platform_add_devices加入到了内核,相关的具体内容前面已经分析的挺多了,这里就不在详细说明。
该句调用的结果会导致s3cmci_driver中的probe方法得以调用,由此也就把我们引入了host的世界。
2、probe函数
在驱动的接口函数中s3cmci_probe() 函数,用于分配mmc_host ,s3cmci_host结构体,并对结构体进行设置,在SDI主机控制器操作接口函数mmc_host_ops中会调用s3cmci_host结构体,申请中断并设置中断服务函数,将结构体mmc_host添加到主机。
SD主控制器驱动程序的初始化函数probe(struct platform_device *pdev),概括地讲,主要完成五大任务,
-
初始化设备的数据结构,并将数据挂载到pdev->dev.driver_data下