关于spi的学习,我认为还是看Linux的源代码最好。 主要是驱动程序/SPI/SPI.c (h )、spidev.c(h ) h )。 spi dev的示例可找到at25.c,而spi总线的示例可找到omap_uwire或spi_s3c24xx.c和spi_s3c24xx_gpio.c。 在查看这些代码之前,您需要了解Linux的设备型号。
此外,网上还有两个教程。 《linux spi子系统驱动分析》和《linux spi子系统驱动分析续》。 百度可以直接搜索。 我会在这里发布我找到的链接,但是不知道是转载的还是原创的。
以下是我整理的关于SPI的心得。 内核版本2.6.29。
SPI子系统
在spi子系统中,spi设备用structspi_dev编写,其驱动程序用structspi_driver编写。 spi总线设备用structspi_master编写。 此外,还有两个重要的全局变量:
struct bus_type spi_bus_type={
. name='spi ',
. dev_attrs=spi_dev_attrs,
. match=spi_match_device,
. uevent=spi_uevent,
. suspend=spi_suspend,
. resume=spi_resume,
(;
staticstructclassspi _ master _ class={
. name='spi_master ',
. owner=THIS_MODULE,
. dev_release=spi_master_release,
(;
spi_bus_type支持sys中的spi bus总线,Linux设备型号详细介绍了该结构。
与所有spi_master对应的spi总线都属于spi_master_class,即虚拟设备,其父设备可能是物理设备,如platform_device。 s3c2410是这种情况。
SPI设备
SPI设备的驱动程序通过spi_register_driver在SPI子系统中注册,驱动程序类型为struct spi_driver。 典型的例子是at25.c。
staticstructspi _ driver at 25 _ driver={
. driver={
. name='at25 ',
. owner=THIS_MODULE,
(,
. probe=at25_probe,
. remove=__devexit_p(at25_remove ),
(;
由于spi总线不支持spi设备的自动发现,因此spi设备的初始化通常不是通过spi的probe函数来检测是否存在设备。
spi驱动程序可以调用以下函数来执行spi传输操作:
staticinlineintspi _ write (结构SPI _ device * SPI,const u8 *buf,size_t len );
staticinlineintspi _ read (struct SPI _ device * SPI,u8 *buf,size_t len );
externintspi _ write _ then _ read (结构SPI _ device * SPI,const u8 *txbuf,unsigned n_tx,
u8 *rxbuf,unsigned n_rx;
staticinlinessize _ tspi _ w 8r8 (struct SPI _ device * SPI,u8 cmd );
staticinlinessize _ tspi _ w8r 16 (struct SPI _ device * SPI,u8 cmd );
由于spi设备无法在spi总线上动态扫描,因此spi子系统使用了另一种方法:使用spi_register_board_info函数在系统中静态注册spi设备。
int _ init SPI _ register _ board _ info (struct SPI _ board _ info const * info,unsigned n );
struct spi_board_info {
charmodalias[32]; //设备名称
常数void *平台_ data; //专用数据被设置在spi_device.dev.platform_data中
void *控制器_ data; //专用数据被设置在spi_device.controller_data中
intirq; //中断号码
u32max_speed_hz; //最大速率
u16bus_num; 为了关联spi_master
u 16芯片
_select;//与片选有关u8mode;// spi_device.mode
};
在具体平台的文件中,可以定义struct spi_board_info的结构体,然后通过spi_register_board_info函数保存这些结构体,最后在scan_boardinfo函数中根据这些保存的结构体创建spi设备(spi_new_device)。
spi_new_device用于登记spi设备,这里面又分两步,首先是spi_alloc_device,然后是spi_add_device。
struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)
spi_dev* pdev = spi_alloc(master);
proxy->chip_select = chip->chip_select;
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
proxy->irq = chip->irq;
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
proxy->dev.platform_data = (void *) chip->platform_data;
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;
spi_add_device(proxy);
struct spi_device *spi_alloc_device(struct spi_master *master)
struct device * dev = master->dev.parent;
struct spi_dev * spi = kzalloc(sizeof *spi, GFP_KERNEL);
spi->master = master;
spi->dev.parent = dev;
spi->dev.bus = &spi_bus_type;
spi->dev.release = spidev_release;
device_initialize(&spi->dev);
这里spi_dev的父设备被指定为master的父设备,而master是spi总线设备,拥有class,是一个虚拟设备。也就是说,spi设备和与之对应的总线设备拥有同一个父设备,这个父设备一般来说是一个物理设备。
int spi_add_device(struct spi_device *spi)
snprintf(spi->dev.bus_id, sizeof spi->dev.bus_id, "%s.%u", spi->master->dev.bus_id,
spi->chip_select);
status = spi->master->setup(spi);
status = device_add(&spi->dev);
spi总线
struct spi_master {
struct devicedev;
s16bus_num;//总线号,如果板子上有多个spi总线,靠这个域区分;另外,spi_dev中也有bus_num,spi_dev通过这个域找到它所属的总线。
u16num_chipselect;//片选号,如果一个spi总线有多个设备,
/* setup mode and clock, etc (spi driver may call many times) */
int(*setup)(struct spi_device *spi);
int(*transfer)(struct spi_device *spi, struct spi_message *mesg);
/* called on release() to free memory provided by spi_master */
void(*cleanup)(struct spi_device *spi);
};
登记spi总线
struct spi_master *spi_alloc_master(struct device *dev, unsigned size);
int spi_register_master(struct spi_master *master);
scan_boardinfo(master);
spi_register_master中会调用scan_boardinfo。scan_boardinfo中,会扫描前面保存的boardinfo,看新注册的master中的bus_num是否与boardinfo中bus_num匹配,如果匹配,那就调用spi_new_device创建spi设备,并登记到spi子系统中。
setup函数
setup函数会做一些初始化工作。比如,根据spi设备的速率,设备paster的位传输定时器;设置spi传输类型;等等。
spi_add_device函数中,会先调用setup函数,然后再调用device_add。这是因为device_add中会调用到driver的probe函数,而probe函数中可能会对spi设备做IO操作。所以spi子系统就先调用setup为可能的IO操作做好准备。
但是,在代码中,setup函数似乎也就只在这一个地方被调用。具体传输过程中切换spi设备时也要做配置工作,但这里的配置工作就由具体传输的实现代码决定了,可以看看spi_bitbang.c中的函数bitbang_work。
cleanup函数
cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数。
transfer函数
transfer函数用于spi的IO传输。但是,transfer函数一般不会执行真正的传输操作,而是把要传输的内容放到一个队列里,然后调用一种类似底半部的机制进行真正的传输。这是因为,spi总线一般会连多个spi设备,而spi设备间的访问可能会并发。如果直接在transfer函数中实现传输,那么会产生竞态,spi设备互相间会干扰。
所以,真正的spi传输与具体的spi控制器的实现有关,spi的框架代码中没有涉及。像spi设备的片选、根据具体设备进行时钟调整等等都在实现传输的代码中被调用。
SPI的传输命令都是通过结构体spi_message定义。设备程序调用transfer函数将spi_message交给spi总线驱动,总线驱动再将message传到底半部排队,实现串行化传输。
struct spi_message {
struct list_headtransfers;
struct spi_device*spi;
unsignedis_dma_mapped:1;
void(*complete)(void *context);
void*context;
unsignedactual_length;
intstatus;
struct list_headqueue;
void*state;
};
spi_message中,有一个transfers队列,spi_transfer结构体通过这个队列挂到spi_message中。一个spi_message代表一次传输会话,spi_transfer代表一次单独的IO操作。比如,有些spi设备需要先读后写,那么这个读写过程就是一次spi会话,里面包括两个transfer,一个定义写操作的参数,另一个定义读操作的参数。
spidev.c
如果不想为自己的SPI设备写驱动,那么可以用Linux自带的spidev.c提供的驱动程序。要使用spidev.c的驱动,只要在登记设备时,把设备名设置成spidev就可以。spidev.c会在device目录下自动为每一个匹配的SPI设备创建设备节点,节点名”spi%d”。之后,用户程序可以通过字符型设备的通用接口控制SPI设备。
需要注意的是,spidev创建的设备在设备模型中属于虚拟设备,它的class是spidev_class。它的父设备是在boardinfo中定义的spi设备。