linux-3.0.1下spi设备mx25l的驱动(基于OK6410)
总述
学linux也快有一年了,这半年断断续续,忙着杂七杂八的事情,一直没全身心的投入的学。作为一个初学者对复杂而博大精深的linux有太多的话要吐槽:linux涉及的东西太多,即使写一个很简单的驱动也要涉及很多知识。看资料时,一会看看这块,一会又看看另一块,此时又忘了前几天看的那块是什么了,总有种按下葫芦起来瓢的感觉……。某某Jim Collins曾经说过:"if you have more than three priorities, then you don't have any." 所以经过一段时间的积累,我决定不能再这样漂浮在表面了,得自己亲自动手写个完整的驱动。就先从spi驱动下手吧,正好以前也用过具有spi接口的串行flash mx25l3205d。
SPI驱动分为两类:
控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。这些驱动涉及硬件寄存器,可能使用DMA。或它们使用GPIO引脚成为PIO bitbangers。这部分通常会由特定的开发板提供商提供,不用自己写。
协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。这部分涉及具体的SPI从设备,通常需要自己编写。
[参考]: Linux社区 作者:cskywit链接:http://www.linuxidc.com/Linux/2011-04/35262.htm
那么特定的目标板如何让Linux 操控SPI设备?下面以自己编写的MX25LxxxxD系列串行flash设备驱动为例,Linux内核版本为3.0.1,开发板为飞凌的OK6410。本文不涉及控制器及spi总线分析。
linux下让硬件跑起来通常需要注册设备和驱动两部分,设备提供硬件描述,驱动控制硬件工作流程。最简单的方法就是利用系统自带的spidev.c的驱动,只要将spi设备的名字改为:spidev,然后选上spidev设备支持,系统启动后会在/dev/下出现spidev0.0设备,利用/document/下的spidev_test.c测试就可以打开并操作设备,不过设备的具体操作步骤就要在应用程序中编写,驱动简单但应用程序复杂多了(看来世界的矛盾性无处不在,想偷懒还是不行的)。
设备注册篇
其中设备信息在:/arch/arm/mach-s3c64xx/mach-smdk6410.c中,
static structspi_board_info __initdata forlinx6410_mx25l_info[] = {
{
.modalias ="mx25l",//"spidev", 设备名,bus就是用此来match,非常关键的哦
.platform_data = &mx25l_info,//&spidev_info,
.irq = IRQ_EINT(16),
.max_speed_hz = 10*1000*1000,
.bus_num =0, //总线号
.chip_select = 0,//片选号
.mode = SPI_MODE_0,
.controller_data=&s3c64xx_spi0_csinfo,
},
};
static structmx25l_data mx25l_info = {//spidev_info = {
.oscillator_frequency = 8000000,
.board_specific_setup =mx25l_ioSetup,
.transceiver_enable = NULL,
.power_enable = NULL,
};
static structs3c64xx_spi_csinfos3c64xx_spi0_csinfo = {
.fb_delay=0x3,
.line=S3C64XX_GPC(3),
.set_level=cs_set_level,
};
static intmx25l_ioSetup(struct spi_device *spi)
{
printk(KERN_INFO "mx25l: setupgpio pins CS and External Int\n");
s3c_gpio_setpull(S3C64XX_GPC(3), S3C_GPIO_PULL_NONE);//Manual chip select pin asused in 6410_set_cs
s3c_gpio_cfgpin(S3C64XX_GPC(3), S3C_GPIO_OUTPUT);// Manual chip select pin as usedin 6410_set_cs
return 0;
}
这里我们做修改,其中SPI0的片选CS为GPC3脚,并且没有用到中断。对于其他的spi协议里的参数,大家用过spi的肯定都知道,就不再介绍了。
在系统: include/linux/spi/下加入mx25l.h,加入定义struct mx25l_data为mx25l_info使用。
struct mx25l_data {
unsigned long oscillator_frequency;
unsigned long irq_flags;
int (*board_specific_setup)(structspi_device *spi);
int (*transceiver_enable)(int enable);
int (*power_enable) (int enable);
};
在文件头加入:#include <linux/spi/mx25l.h>,路径要对应好。
至此spi设备已全部注册完成,在系统启动的过程中会扫描设备信息加入到系统的设备链表中,默默地等待与match的驱动相连。
加入设备信息到系统设备链表的程序如下:
static void __initsmdk6410_machine_init(void)
{
……
spi_register_board_info(forlinx6410_mx25l_info,ARRAY_SIZE(forlinx6410_mx25l_info));
}
其中还会匹配设备与主控制器的总线号,如果匹配成功会spi_new_device(master, bi),建立主控器的设备,等待主控制器的驱动(主控制器也是遵循这个流程,先注册设备,然后注册驱动,其中主控制的设备由struct spi_master描述,master还带有一个中重要的函数int (*transfer) (struct spi_device *spi, struct spi_message*mesg),然后transfer把要传输的内容放到一个队列里,最后调用一种类似底半部的机制进行真正的传输。设备程序可以调用transfer函数将spi_message交给spi总线驱动,总线驱动再将message传到底半部排队,实现串行化传输)。
驱动注册篇
这里mx25lxxxxd和sst25l相似,都是SPI串行flash,用到了mtd设备模型。
一般的设备都是利用platform注册时,用platform_driver做驱动,而spi作为单独的一种总线形式,为自己定义了spi_driver,而这两个却又相同,真让人有种乱花渐欲迷人眼的感觉。
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(structspi_device *spi);
int (*remove)(structspi_device *spi);
void (*shutdown)(structspi_device *spi);
int (*suspend)(structspi_device *spi, pm_message_t mesg);
int (*resume)(structspi_device *spi);
struct device_driver driver;
};
我们可以与platform_driver对比下:
struct platform_driver{
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device*);
void (*shutdown)(struct platform_device*);
int (*suspend)(struct platform_device*, pm_message_t state);
int (*resume)(struct platform_device*);
struct device_driver driver;
const struct platform_device_id*id_table;
};
可以发现内容完全相同,只是调整了成员顺序,并且各个成员函数的传递参数改为struct spi_device *spi。所以spi驱动的注册原理和大家熟悉的驱动注册是一样的,大家可要睁大眼睛,不要被这个换汤不换药的老中医蒙骗了。
mx25lxx驱动spi定义并初始化为:
staticstruct spi_driver mx25l_driver = {
.driver = {
.name = "mx25l",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe =mx25l_probe,
.remove =__devexit_p(mx25l_remove),
};
在这个驱动实体定义好后,就可以用它开始注册驱动了,首先要执行的函数是:
static int __initmx25l_init(void)
{
return spi_register_driver(&mx25l_driver);
}
在spi_register_driver(&mx25l_driver)中又给mx25l_driver的driver添加了2个成员(这里第3个为空):
intspi_register_driver(struct spi_driver *sdrv)
{
sdrv->driver.bus =&spi_bus_type;
if (sdrv->probe)
sdrv->driver.probe =spi_drv_probe;
if (sdrv->remove)
sdrv->driver.remove =spi_drv_remove;
if (sdrv->shutdown)
sdrv->driver.shutdown =spi_drv_shutdown;
returndriver_register(&sdrv->driver);
}
此时static struct spi_driver mx25l_driver变为:
static structspi_driver mx25l _driver = {
.driver = {
.name = " mx25l",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.probe = spi_drv_probe,//后来加入,spi.c里的函数
.remove = spi_drv_remove,//后来加入,spi.c里的函数
.shutdown = spi_drv_shutdown;//后来加入,spi.c里的函数
},
.probe =mx25l_probe,
.remove =__devexit_p(mx25l_remove),
};
其中的spi_drv_probe/remove/shutdown为spi.c里的函数,原函数(以spi_drv_probe为例):
static intspi_drv_probe(struct device *dev)
{
const struct spi_driver *sdrv =to_spi_driver(dev->driver);
//根据dev->driver找到驱动的地址,这里的spi_driver类型就是前面提到的类型,与定义的mx25l_driver一致。
returnsdrv->probe(to_spi_device(dev));
}
其中to_spi_driver定义如下:
static inlinestruct spi_driver *to_spi_driver(struct device_driver *drv)
{
return drv ? container_of(drv, structspi_driver, driver) : NULL;
}
如果drv不为空,就会顺着drv找到driver的地址(下一篇分析container_of的实现原理),然后返回找到的地址。
to_spi_device定义如下:
static inlinestruct spi_device *to_spi_device(struct device *dev)
{
return dev ? container_of(dev, structspi_device, dev) : NULL;
}
我们看下spi_device的结构:
struct spi_device
struct device dev |
struct spi_master *master |
u32 max_speed_hz |
u8 chip_select |
u8 mode |
u8 bits_per_word |
int irq |
void *controller_state |
void *controller_data |
char modalias[SPI_NAME_SIZE] |
其中struct device dev是第一个成员,dev的地址与实参spi_device *dev的地址是一样一样的,或许有人会问这不浪费资源,浪费时间,浪费生命吗?我觉得这样用的原因有两个:1.规范,与to_spi_driver一样,形式上保持一致,让人不会有突兀的感觉,一看到这个函数就知道是做什么用的,也不用再继续深入内核查看了,利于阅读源码。2.安全,谁又能保证不会有某些人再利用struct spi_device封装成其他类型的设备_device,万一他们在封装的时候调整了成员顺序怎么办(就像我们spi驱动重新封装platform_drive一样)。这样就不用再调整spi.c中的代码,保证了我们写个驱动不用考虑的太复杂,否则指针乱了,内核运行乱了,剩下的只有找不出原因的我们在风中凌乱了。
之后继续执行driver_register(&sdrv->driver):
intdriver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
……//检查参数
other = driver_find(drv->name,drv->bus);
if (other) {
put_driver(other);
printk(KERN_ERR "Error:Driver '%s' is already registered, ""aborting...\n",drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv);
if (ret)
return ret;
ret = driver_add_groups(drv,drv->groups);
if (ret)
bus_remove_driver(drv);
return ret;
}
这是很多设备注册时常用的注册函数driver_register,至此spi设备驱动注册回到“正轨”上来。其中通过层层调用最终会调用关键的static int __devinit mx25l_probe(structspi_device *spi)函数(插句废话:驱动名字必须和设备名字match才会调用probe)。至于调用过程可用Source Insight追踪,详细过程可参考下一篇分析。
probe函数如下:
static int__devinit mx25l_probe(struct spi_device *spi)
{
struct flash_info *flash_info;
struct mx25l_flash *flash;
struct flash_platform_data *data;
int ret, i;
struct mtd_partition *parts = NULL;
int nr_parts = 0;
flash_info = mx25l_match_device(spi);
if (!flash_info)
return -ENODEV;
flash = kzalloc(sizeof(structmx25l_flash), GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->spi = spi;
mutex_init(&flash->lock);
dev_set_drvdata(&spi->dev,flash);
data = spi->dev.platform_data;
if (data && data->name)
flash->mtd.name =data->name;
else
flash->mtd.name =dev_name(&spi->dev);
flash->mtd.type = MTD_NORFLASH;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.erasesize = flash_info->erase_size;
flash->mtd.writesize = flash_info->page_size;
flash->mtd.size = flash_info->page_size *flash_info->nr_pages;
flash->mtd.erase =mx25l_erase;
flash->mtd.read = mx25l_read;
flash->mtd.write = mx25l_write;
dev_info(&spi->dev, "%s(%lld KiB)\n", flash_info->name,
(long long)flash->mtd.size >> 10);
DEBUG(MTD_DEBUG_LEVEL2,
"mtd .name = %s, .size = 0x%llx (%lldMiB) "
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
flash->mtd.name,
(long long)flash->mtd.size, (long long)(flash->mtd.size >>20),
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
flash->mtd.numeraseregions);
if (mtd_has_cmdlinepart()) {
static const char *part_probes[]= {"cmdlinepart", NULL};
nr_parts =parse_mtd_partitions(&flash->mtd,
part_probes,
&parts,0);
}
if (nr_parts <= 0 && data&& data->parts) {
parts = data->parts;
nr_parts = data->nr_parts;
}
if (nr_parts > 0) {
for (i = 0; i < nr_parts;i++) {
DEBUG(MTD_DEBUG_LEVEL2,"partitions[%d] = "
"{.name = %s, .offset = 0x%llx,"
".size = 0x%llx (%lldKiB) }\n",
i, parts[i].name,
(long long)parts[i].offset,
(long long)parts[i].size,
(long long)(parts[i].size >> 10));
}
flash->partitioned = 1;
returnmtd_device_register(&flash->mtd, parts,
nr_parts);
}
ret =mtd_device_register(&flash->mtd, NULL, 0);
if (ret == 1) {
kfree(flash);
dev_set_drvdata(&spi->dev,NULL);
return -ENODEV;
}
return 0;
}
其中的match函数如下:
static structflash_info *__devinit mx25l_match_device(struct spi_device *spi)
{
struct flash_info *flash_info = NULL;
struct spi_message m;
struct spi_transfer t;
unsigned char cmd_resp[6];
int i, err;
uint16_t id;
spi_message_init(&m);
memset(&t, 0, sizeof(structspi_transfer));
cmd_resp[0] = MX25L_CMD_READREMS;
cmd_resp[1] = 0;
cmd_resp[2] = 0;
cmd_resp[3] = 0;
cmd_resp[4] = 0xff;
cmd_resp[5] = 0xff;
t.tx_buf = cmd_resp;
t.rx_buf = cmd_resp;
t.len = sizeof(cmd_resp);
spi_message_add_tail(&t, &m);
err = spi_sync(spi, &m);
if (err < 0) {
dev_err(&spi->dev,"error reading device id\n");
return NULL;
}
id = (cmd_resp[4] << 8) |cmd_resp[5];
for (i = 0; i < ARRAY_SIZE(mx25l_flash_info);i++)
if(mx25l_flash_info[i].device_id == id)
flash_info =&mx25l_flash_info[i];
if (!flash_info)
dev_err(&spi->dev, "unknownid %.4x\n", id);
return flash_info;
}
其中结构体为:
struct mx25l_flash{
struct spi_device *spi;
struct mutex lock;
struct mtd_info mtd;
int partitioned;
};
struct flash_info {
const char *name;
uint16_t device_id;
unsigned page_size;
unsigned nr_pages;
unsigned erase_size;
};
#defineto_mx25l_flash(x) container_of(x, struct mx25l_flash, mtd)
static structflash_info __devinitdata mx25l_flash_info[] = {
{"mx25l1605d", 0xbf8c,MX25L_PAGE_SIZE, MX25L_NUMSECTORS, MX25L_EACHSECTOR_SIZE},
{"mx25l3205d", 0xbf8d,MX25L_PAGE_SIZE, MX25L_NUMSECTORS, MX25L_EACHSECTOR_SIZE},
{"mx25l6405d", 0xbf8e,MX25L_PAGE_SIZE, MX25L_NUMSECTORS, MX25L_EACHSECTOR_SIZE},
};
这里的匹配函数要参考MX25LxxD的datasheet:
首先要使用REMS命令0x90,然后继续读5个字节,最后两个字节即为Manufacturer ID 和 Device ID,其中用到struct spi_message m和struct spi_transfer t两个结构体,m用来存放要发送的命令、发送及接收的数据的地址,以及长度等信息,然后调用 spi_message_add_tail(&t, &m)将m增加到t的末尾(当然这里t队列里只有一个m),最后调用spi_sync(spi, &m)发送。所以match函数就是实现这个流程,同理erase、read、write函数也类似,终于又找到那种不用操作系统裸奔时完全hold住全场的感觉了!
其中read,write,erase函数需要根据mx25l的具体命令做相应的细微调整。驱动编写好后,make,然后insmod mx25l.ko,驱动就被安装到系统中,我这里使用的是mx25l3205d,在match的时候我打印了芯片的id:0xbf25。
安装操作篇
驱动安装成功后要手动建立节点:mknod /dev/mx25l c 90 6,我这里的mtd设备信息如下:
字符类mtd设备的主设备号是:90,块mtd设备的主设备号是:31,这里的三个block设备里分别存放的uboot,linux-3.0.1内核,yaffs文件系统,我们的小容量串行flash就不要去凑热闹了,而字符类中已经存在了5个设备,所以次设备号选为6。
测试程序用普通的open,read,write函数就可以实现对/dev/mx25l设备的操作。
spi设备不像USB设备那样支持热插拔,所以通常都是系统板子上的一部分,故其驱动也常常是随系统一起启动。所以驱动调试完成后,可以修改driver/spi/下的Kconfig,Makefile,在系统make menuconfig时选上mx25l即可。
Questions:
1. 没有实现自动注册设备名,每次都要手动注册char类的mtd设备,下一步要看如何实现自动注册。
2. 关于主控的部分未涉及,所以下一步要看设备驱动提交message之后,调用transfer发送与主控器驱动的关系。
3. 使用单片机时对mx25l擦除是在写时,根据开始地址和写入长度来判断是否到新页来擦除扇区。而这里用到了mtd设备方法,对mtd设备调用过程不详,如:erase函数不知何时调用,可能也是在写入时当遇到新页时会被调用,也需要认真分析。
