分类: LINUX
2014-09-26 06:53:18
原文地址:编写自己的SD/MMC Host驱动(一):注册 作者:fychit
网上已经有很多文章写了Linux SD/MMC的驱动的分析了,尤其是SAMSUNG系列的,估计用汗牛充栋来描写都不过分。俺只能说点我自己写的基于CBP的EVB板子的SD/MMC controller的驱动了,这个驱动没有采用DMA,虽然那是我最擅长的。使用的是pio模式,其实就是CPU读写了,但是竟然也没有使用tasklet,也就没有中断的上下半部的说法了,应该不是一个很好的习惯,虽然tasklet其实就是启动一个内核运行队列,去运行pio的读写等等,本质上没有大的区别,但没有使用就是没有使用,实事求是而已。
那就说说Linux SD/MMC驱动的工作过程吧:
首先按照platform驱动的方式,注册probe函数,然后系统就会调用probe函数,具体方法和原理请参考本人写的以及转载的plateform的文章,就不在赘述了。
在probe里面首先分配一个mmc_host的结构,Linux以后发起各种操作的时候,参数都会通过这个结构传递。
mmc=mmc_alloc_host(sizeof(struct cbpmci_host), &pdev->dev);
mmc_alloc_host这个函数比较有意思,其中传入的第一个参数是一个长度,这个长度是在mmc_hos后面额外连续分配的一块数据,我这句的意思就是紧跟着mmc_host分配了一个cbpmci_host的结构,看mmc_host的定义如下:
struct mmc_host {
struct device *parent;
………省略若干行………………
unsigned long private[0] ____cacheline_aligned;
};
其实cbpmci_host的位置就是private[0]的位置,巧妙吧?经常看看Linux代码能学到不少Linux大牛们的方法。这样就可以通过这个函数来从mmc_host中得到自己带入的私有数据了:
static inline void *mmc_priv(struct mmc_host *host)
{
return (void *)host->private;
}
按照Linux驱动的惯例,此时应该注册SD/MMC的操作函数了,SD/MMC的操作函数恐怕是Linux驱动里面最少的之一了,只有如下几个:
struct mmc_host_ops {
int (*enable)(struct mmc_host *host);
int (*disable)(struct mmc_host *host, int lazy);
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);
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
};
我的定义:
static struct mmc_host_ops cbpmci_ops = {
.request = cbpmci_request,
.set_ios = cbpmci_set_ios,
.get_ro = cbpmci_get_ro,
.get_cd = cbpmci_card_present,
.enable_sdio_irq = cbpmci_enable_sdio_irq,
};
将cbpmci_ops 赋值给mmc->ops即可
mmc->ops = &cbpmci_ops;
为啥连读写都没有呢,因为他是通过request里面的参数来进行标识的。Request函数主要处理Linux发到驱动的命令请求,包括带数据的和不带数据的,带数据的一般就是read/write, 其实ext CSD之类的东东也是通过读来完成的。
其中的set_ios是设置一些控制参数的,比如时钟频率,电源开/关,总线宽度等等。
get_ro是获取SD/MMC的写保护标志的,就是SD卡上的那个小开关的状态,如果返回0则是可写的,不然为只读。
get_cd是获取卡的插入状态的,返回0则有卡,不然返回1。
接着当然是要申请对应的SD的中断了,在中断函数中将处理命令完成,CRC错误等等中断。
然后再初始化一些mmc_host的参数就OK了,比如最大最小频率,总线宽度,最大的block count等等。然后调用 mmc_add_host将host驱动加入。
ret = mmc_add_host(mmc);
如果驱动加入没有错误,系统将会调用相应的初始化过程!