SD/MMC卡, 看了SANDISK的MMC规范文档, 对着看了MMCI host的驱动. 看多了发觉了一个有趣的内核编程"规则",当某个子系统有很多不同的模块, 但是模块之间又有很多相似的功能时,这个子系统就会抽象出一组与特定功能无关的核心代码,供各个模块共用.
如内核(2.6.22.5)里的MMC驱动,在driver里分成三个部分, 分别是block, core和host, 其中这个core就是核心部分且与硬件无关的代码, 当要操作硬件是, core才会通过在host里注册的钩子函数去调用特定的host相关的操作函数.
block是块设备驱动代码, 在看过了ram disk或硬盘等块设备的驱动后这个也是比较容易理解的. 一个块设备所做的主要的工作就是, 初始化一个IO请求队列, 然后当有上层代码(如read/write)IO请求时, 这些请求就会被加入这个队列中. 块设备就是负责从这个队列中去读取请求, 然后处理这些请求. 就是说块设备就是通过这个队列与上层代码相连的.
MMC块设备驱动实现比较有趣, 它是通过一个内核线程去实现, 当队列为空时, 这个线程就进入睡眠状态, 当有请求加入队列时, 请求处理函数就会唤醒这个线程, 让它去处理这个请求.
我们知道, io请求是以request为单位的, 就是说传递到块设备驱动的每个请求为一个request, 这个request中包含了IO需要的所有信息. MMC块设备得到这个request后, 主要的工作就是将这个请求信息转换为MMC主机控制器可以识别的信息, 这就是mmc_command结构. 块设备驱动构造好这个结构后, 就会调用core里的函数, 将请求交由其去处理, 这就是说block驱动是看不到硬件的, 它只和core代码打交道, 之后core才会通过特定host 控制器注册的钩子函数, 将IO请求写入硬件, 发出真正的IO操作.
usb子系统工作过程也是差不多的, 如U盘, 当上层有一个IO请求request时, 她先通过scsi子系统生成一个scsi命令,因为U盘是模拟scsi盘工作的, 然后这个scsi命令会进一步被转换成urb命令, 再通过USB接口发个U盘的控制器.
mmci控制器驱动(2.6.22.5) -- (1) 驱动注册
R.wen
mmci为ARM的sd/mmc主控制器的驱动. 并且这个控制器是挂接在ARM的amba总线之下的, 所以驱动的注册会用到amba总线的一些函数.
1).驱动的注册.
static struct amba_driver mmci_driver = {
.drv = {
.name = DRIVER_NAME,
},
.probe = mmci_probe,
.remove = mmci_remove,
.suspend = mmci_suspend,
.resume = mmci_resume,
.id_table = mmci_ids,
};
static int __init mmci_init(void)
{
return amba_driver_register(&mmci_driver);
}
这个注册函数可以在前面的介绍中可以看到, 它定义了一个amba_driver结构,然后通过amba_driver_register(&mmci_driver)往amba总线上注
册这个驱动.
并且,如我们之前所说的, 这个注册函数最终会调用驱动中的probe函数完成驱动的初始化操作. 这就是mmc_probe():
static int mmci_probe(struct amba_device *dev, void *id)
{
struct mmc_platform_data *plat = dev->dev.platform_data;
struct mmci_host *host;
struct mmc_host *mmc;
int ret;
/* must have platform data */
if (!plat) {
ret = -EINVAL;
goto out;
}
//申请一个内存区
ret = amba_request_regions(dev, DRIVER_NAME);
if (ret)
goto out;
分配一个mmc_host结构, 还有一个特殊的mmci_host结构.
mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
if (!mmc) {
ret = -ENOMEM;
goto rel_regions;
}
host = mmc_priv(mmc); 两个结构关联起来
//以下这段代码获取时钟的频率
//得到一个clk结构
host->clk = clk_get(&dev->dev, "MCLK");
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
host->clk = NULL;
goto host_free;
}
ret = clk_enable(host->clk);
if (ret)
goto clk_free;
host->plat = plat;
host->mclk = clk_get_rate(host->clk); //频率
host->mmc = mmc;
//得到设备寄存器的MMU后的基址
host->base = ioremap(dev->res.start, SZ_4K);
if (!host->base) {
ret = -ENOMEM;
goto clk_disable;
}
//这是请求操作函数结构,最终的对MMC卡的操作都是通过它来完成的.
mmc->ops = &mmci_ops;
mmc->f_min = (host->mclk + 511) / 512;
mmc->f_max = min(host->mclk, fmax);
mmc->ocr_avail = plat->ocr_mask;
mmc->caps = MMC_CAP_MULTIWRITE;
/*
* We can do SGIO
*/
mmc->max_hw_segs = 16;
mmc->max_phys_segs = NR_SG;
/*
* Since we only have a 16-bit data length register, we must
* ensure that we don't exceed 2^16-1 bytes in a single request.
*/
mmc->max_req_size = 65535;
/*
* Set the maximum segment size. Since we aren't doing DMA
* (yet) we are only limited by the data length register.
*/
mmc->max_seg_size = mmc->max_req_size;
/*
* Block size can be up to 2048 bytes, but must be a power of two.
*/
mmc->max_blk_size = 2048;
/*
* No limit on the number of blocks transferred.
*/
mmc->max_blk_count = mmc->max_req_size;
spin_lock_init(&host->lock);
//先关中断
writel(0, host->base + MMCIMASK0);
writel(0, host->base + MMCIMASK1);
writel(0xfff, host->base + MMCICLEAR);
//申请两个中断
ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
if (ret)
goto unmap;
ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
if (ret)
goto irq0_free;
//再开中断
writel(MCI_IRQENABLE, host->base + MMCIMASK0);
//关联结构
amba_set_drvdata(dev, mmc);
mmc_add_host(mmc);
printk(KERN_INFO "%s: MMCI rev %x cfg %02x at 0x%016llx irq %d,%d\n",
mmc_hostname(mmc), amba_rev(dev), amba_config(dev),
(unsigned long long)dev->res.start, dev->irq[0], dev->irq[1]);
//定时扫描控制器
init_timer(&host->timer);
host->timer.data = (unsigned long)host;
host->timer.function = mmci_check_status;
host->timer.expires = jiffies + HZ;
add_timer(&host->timer);
return 0;
irq0_free:
free_irq(dev->irq[0], host);
unmap:
iounmap(host->base);
clk_disable:
clk_disable(host->clk);
clk_free:
clk_put(host->clk);
host_free:
mmc_free_host(mmc);
rel_regions:
amba_release_regions(dev);
out:
return ret;
}
先来看这个函数的最后一部分:
init_timer(&host->timer);
host->timer.data = (unsigned long)host;
host->timer.function = mmci_check_status;
host->timer.expires = jiffies + HZ;
add_timer(&host->timer);
这是建立一个定时器, 时间为1秒, 超时后,它就会执行mmci_check_status函数:
static void mmci_check_status(unsigned long data)
{
struct mmci_host *host = (struct mmci_host *)data;
unsigned int status;
status = host->plat->status(mmc_dev(host->mmc));
if (status ^ host->oldstat)
mmc_detect_change(host->mmc, 0);
host->oldstat = status;
mod_timer(&host->timer, jiffies + HZ);
}
我们可以看到, 这个函数通过mod_timer(), 修改这个定时器, 使之1秒之后再次执行, 这个就是说, MMC控制器是通过轮询的方式,每隔1秒调
用mmci_check_status去查询这个设备的状态有没有改变. 状态的查询是通过 host->plat->status(mmc_dev(host->mmc)去完成的:
static unsigned int realview_mmc_status(struct device *dev)
{
struct amba_device *adev = container_of(dev, struct amba_device, dev);
u32 mask;
if (adev->res.start == REALVIEW_MMCI0_BASE)
mask = 1;
else
mask = 2;
return readl(REALVIEW_SYSMCI) & mask;
}
struct mmc_platform_data realview_mmc0_plat_data = {
.ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34,
.status = realview_mmc_status,
};
它是通过读取相应的状态寄存器位,然后跟原来的状态比较(status ^ host->oldstat), 如果发生了改变, 则调用mmc_detect_change去处理这
个变化.
/**
* mmc_detect_change - process change of state on a MMC socket
* @host: host which changed state.
* @delay: optional delay to wait before detection (jiffies)
*
* All we know is that card(s) have been inserted or removed
* from the socket(s). We don't know which socket or cards.
*/
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
BUG_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
mmc_schedule_delayed_work(&host->detect, delay);
}
显然,他是通过工作队列来做的. 这个host->detect是一个delayed_work结构. 代表一个任务. 这个任务是在probe中通过mmc_alloc_host已经
初始化了.mmc_alloc_host我们后面在看, 这里我们只要知道它有这个这个操作INIT_DELAYED_WORK(&host->detect, mmc_rescan), 就是说每当
发现状态有所改变, MMCI最终会执行这个mmc_rescan().
static void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
u32 ocr;
int err;
mmc_bus_get(host);
if (host->bus_ops == NULL) { //检测SD卡
/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);
mmc_claim_host(host);
mmc_power_up(host);
mmc_go_idle(host);
mmc_send_if_cond(host, host->ocr_avail);
err = mmc_send_app_op_cond(host, 0, &ocr);
if (err == MMC_ERR_NONE) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
} else { //检测MMC卡
/*
* If we fail to detect any SD cards then try
* searching for MMC cards.
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (err == MMC_ERR_NONE) { //如果没有出错
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
} else {
mmc_power_off(host);
mmc_release_host(host);
}
}
} else {
if (host->bus_ops->detect && !host->bus_dead)
host->bus_ops->detect(host);
mmc_bus_put(host);
}
}
看MMC卡的情况, 如果没出错, 则会调用mmc_attach_mmc,这个函数将会为一张MMC卡分配一个mmc_card结构, 初始化她并将她加入队列 host-
>cards中去.
**回头看mmci_probe:
她先通过以下方式申请一个mmc_host和mmci_host结构,
struct mmci_host *host;
struct mmc_host *mmc;
mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
这两个结构是一般和特殊的关系, mmc_host是通用的mmc host控制器的结构, 而特殊的控制器有好多种,这里是arm的MMCI, 还有at91的,
intel的等等. 可以想到, 都是MMC控制器, 每个特殊的控制器肯定有很多相同的属性, 所以这里就抽象出一个父结构,相当于面向对象里的父类
, 这里就是mmc_host了.
/**
* 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)
{
struct mmc_host *host;
host = mmc_alloc_host_sysfs(extra, dev);
if (host) {
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
host->max_hw_segs = 1;
host->max_phys_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;
}
我们看到,首先是分配一个空间的函数, 有个extra参数,就是用于为特殊设备分配额外空间的大小.
如果分配成功, 则初始化一个等待队列和一个工作队列, 而这个工作队列就是我们先前说提到的.
在接下来就是一个控制器的默认参数的设置了.
分配完空间后, 接着就是host = mmc_priv(mmc),
static inline void *mmc_priv(struct mmc_host *host)
{
return (void *)host->private;
}
就是将他们关联起来, 这样通过通用的mmc_host,就可以找到特殊的mmci_host了.
**下一步就是时钟频率的设置, MMC卡的传输速率跟这个频率有直接的关系. 比如, 这个频率是20MHz, 而且MMC卡工作在4位模式, 则它的传输
速率就是20M * 4 = 80M/sec, 就是每秒10M多字节的速度.
host->clk = clk_get(&dev->dev, "MCLK");
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
host->clk = NULL;
goto host_free;
}
ret = clk_enable(host->clk);
if (ret)
goto clk_free;
host->mclk = clk_get_rate(host->clk);
先取出一个时钟结构, 这个是根据MCLK这个名字,然后取出这个相应的结构的. 这个是在系统初始化的时候设置好的.
如何通过clk_get_rate取出一个整型的速率值.
**接着就是
struct mmc_platform_data {
unsigned int ocr_mask; /* available voltages */
u32 (*translate_vdd)(struct device *, unsigned int);
unsigned int (*status)(struct device *);
};
struct mmc_platform_data *plat = dev->dev.platform_data;
host->plat = plat;
这个也是通过dev传过来的参数, 我们在上面已经看到了它的使用, 就是用来检测控制器状态的变化.
mmc控制器操作函数的设置,
mmc->ops = &mmci_ops;
假设一个一个进程要读一个MMC卡上的一个文件, 它就要将这个请求(request)发给mmc块设备(mmc_block), mmc块设备最终调用这个结构中的
函数处理这个请求.
static const struct mmc_host_ops mmci_ops = {
.request = mmci_request,
.set_ios = mmci_set_ios,
};
mmci_request()函数就是直接操作控制器的的寄存器了.
** 还有一些SGIO的设置等等,和如下的一些值的初始化:
mmc->f_min = (host->mclk + 511) / 512;
mmc->f_max = min(host->mclk, fmax);
mmc->ocr_avail = plat->ocr_mask;
mmc->caps = MMC_CAP_MULTIWRITE;
**下一步就是中断的处理了, 做法很简单,就是--关中断, 设置中断处理例程, 开中断.
writel(0, host->base + MMCIMASK0);
writel(0, host->base + MMCIMASK1);
writel(0xfff, host->base + MMCICLEAR);
## 两个IRQ
ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
if (ret)
goto unmap;
ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
if (ret)
goto irq0_free;
writel(MCI_IRQENABLE, host->base + MMCIMASK0);
** 将MMC写会到dev的drvdata中, 关联起来
amba_set_drvdata(dev, mmc);
** 最后一步就是我们一开始所说的定时器设置了.
posted on 2008-08-20 11:24
puppy 阅读(1629)
评论(1) 编辑 收藏 引用