分类: LINUX
2010-03-22 10:55:10
Linux 2.6下SPI设备模型 --------基于AT91RM9200分析
Atmel公司的ARM AT系列,其SPI驱动在kernel SPI Driver(legacy) for at91rm9200 processor是保留选项,为了兼容以前版本。如果同时选中SPI Driver(legacy) for at91rm9200 processor,则在/sys里无法注册类spidev,也就无法将设备和驱动联系在一起。与现有atmel spi驱动发生冲突。
各选项对应的编译情况如下: [*]SPI support ---- Config_SPI 开启SPI功能 [*]Debug support for SPI drivers ---- config SPI_DEBUG 开启SPI debug调试 ----SPI Master Controller Drivers ---- depends on SPI_MASTER 生成spi.o <*>Atmel SPI Controller ---- config SPI_ATMEL 生成atmel_spi.o <*>Bitbanging SPI master ---- config SPI_BITBANG 生成spi_bitbang.o <*>AT91RM9200 Bitbang SPI Master ---- CONFIG_SPI_AT91 spi_at91_bitbang.o ---- SPI Protocol Masters ---- depends on SPI_MASTER < >SPI EEPROMs from most vendors ---- config SPI_AT25 生成at25.o <*>User mode SPI device driver support ---- config SPI_SPIDEV 生成spidev.o 总线 注册SPI总线 #spi.c struct bus_type spi_bus_type = { .name = "spi", // spi总线名称 .dev_attrs = spi_dev_attrs, .match = spi_match_device, .uevent = spi_uevent, .suspend = spi_suspend, .resume = spi_resume, }; spi总线将在sysfs/bus下显示。 其bus_type 结构表示总线,它的定义在 struct bus_type { const char * name; struct module * owner; struct kset subsys; struct kset drivers; struct kset devices; struct klist klist_devices; struct klist klist_drivers; struct blocking_notifier_head bus_notifier; struct bus_attribute * bus_attrs; struct device_attribute * dev_attrs; struct driver_attribute * drv_attrs; struct bus_attribute drivers_autoprobe_attr; struct bus_attribute drivers_probe_attr; int (*match)(struct device * dev, struct device_driver * drv); int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); int (*probe)(struct device * dev); int (*remove)(struct device * dev); void (*shutdown)(struct device * dev); int (*suspend)(struct device * dev, pm_message_t state); int (*suspend_late)(struct device * dev, pm_message_t state); int (*resume_early)(struct device * dev); int (*resume)(struct device * dev); unsigned int drivers_autoprobe:1; }; 其中,当一个总线上的新设备或者新驱动被添加时,*match 函数会被调用。如果指定的驱动程序能够处理指定的设备,该函数返回非零值。 对于spi总线,我们必须调用bus_register(&spi_bus_type)进行注册。调用如果成功,SPI总线子系统将被添加到系统中,在sysfs的/sys/bus目录下可以看到。然后,我们就可以向这个总线添加设备了。代码见下: static int __init spi_init(void) { int status; buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); if (!buf) { status = -ENOMEM; goto err0; } status = bus_register(&spi_bus_type); if (status < 0) goto err1; status = class_register(&spi_master_class); if (status < 0) goto err2; return 0; err2: bus_unregister(&spi_bus_type); err1: kfree(buf); buf = NULL; err0: return status; } 设备 spi设备的结构如下: #spi.h struct spi_device { struct device dev; struct spi_master *master; u32 max_speed_hz; u8 chip_select; u8 mode; #define SPI_CPHA 0x01 /* clock phase */ #define SPI_CPOL 0x02 /* clock polarity */ #define SPI_MODE_0 (0|0) /* (original MicroWire) */ #define SPI_MODE_1 (0|SPI_CPHA) #define SPI_MODE_2 (SPI_CPOL|0) #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) #define SPI_CS_HIGH 0x04 /* chipselect active high? */ #define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */ #define SPI_3WIRE 0x10 /* SI/SO signals shared */ #define SPI_LOOP 0x20 /* loopback mode */ u8 bits_per_word; int irq; void *controller_state; void *controller_data; const char *modalias; /* * likely need more hooks for more protocol options affecting how * the controller talks to each chip, like: * - memory packing (12 bit samples into low bits, others zeroed) * - priority * - drop chipselect after each word * - chipselect delays * - ... */ }; device结构中包含了设备模型核心用来模拟系统的信息。spidev还有设备的其他信息,因此spi设备结构包含在spidev_data结构里。 struct spidev_data { struct device dev; struct spi_device *spi; struct list_head device_entry; struct mutex buf_lock; unsigned users; u8 *buffer; }; 注册spi设备, #spidev.c static int spidev_probe(struct spi_device *spi) { … … status = device_register(&spidev->dev); … … } 完成这个调用之后,我们就可以在sysfs中看到它了。 SPI设备驱动程序 spi驱动程序结构如下: struct spi_driver { int (*probe)(struct spi_device *spi); int (*remove)(struct spi_device *spi); void (*shutdown)(struct spi_device *spi); int (*suspend)(struct spi_device *spi, pm_message_t mesg); int (*resume)(struct spi_device *spi); struct device_driver driver; }; spi驱动程序注册函数如下: int spi_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; return driver_register(&sdrv->driver); } spidev的驱动名如下: static struct spi_driver spidev_spi = { .driver = { .name = "spidev", .owner = THIS_MODULE, }, .probe = spidev_probe, .remove = __devexit_p(spidev_remove), }; 一个spi_register_driver调用将spidev添加到系统中。一旦初始化完成,就可以在sysfs中看到驱动程序信息。 类 spidev类结构如下: static struct class spidev_class = { .name = "spidev", .owner = THIS_MODULE, .dev_release = spidev_classdev_release, }; AT91RM9200 SPIDEV初始化 AT91RM9200的spi驱动,对于EK板,原先的SPI是用于dataflash的。其代码如下: static struct spi_board_info ek_spi_devices[] = { { /* DataFlash chip */ .modalias = "mtd_dataflash", .chip_select = 0, .max_speed_hz = 15 * 1000 * 1000, }, 我们需要将.modalias改成我们自己的spi设备名 在spi设备初始化代码中,class_register(&spidev_class)注册类,spi_register_driver(&spidev_spi)注册spidev驱动。 #drivers/spi/spidev.c static int __init spidev_init(void) { int status; /* Claim our 256 reserved device numbers. Then register a class * that will key udev/mdev to add/remove /dev nodes. Last, register * the driver which manages those device numbers. */ BUILD_BUG_ON(N_SPI_MINORS > 256); status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); if (status < 0) return status; status = class_register(&spidev_class); if (status < 0) { unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); return status; } status = spi_register_driver(&spidev_spi); if (status < 0) { class_unregister(&spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); } return status; } 挂载/sys mount –t sysfs sysfs /sys 可以看到有/sys/class/spidev/spidev0.0,表明设备已经挂载在总线上了,同时与驱动联系起来。 使用mdev –s,可以在/dev下看到spidev0.0这个设备了。 自此,spi设备驱动就可以工作了。 测试程序:
备注: 如果要设置模式,速率等,则可仿照以下语句: speed =10*1000*1000; //10MHz if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) { perror("SPI max_speed_hz"); return; } 默认spi_io_transfer时,每个字节之间有延时。在atmel_spi_setup.c文件里去掉该延时语句: /* TODO: DLYBS and DLYBCT */ //csr |= SPI_BF(DLYBS, 10); //csr |= SPI_BF(DLYBCT, 10); 这样就可以达到无间隙快速传输批量数据。 标准read(),write()两个函数仅适用于半双工传输,。在传输之间不激活片选。而SPI_IOC_MESSAGE(N)则是全双工传输,并且片选始终激活。 SPI_IOC_MESSAGE传输长度有限制,默认是一页的长度,但是可以更改。 spi_ioc_transfer结构的spi长度 是字节长度,16位传输的时候要注意 |