1.1 重要的数据结构
1. spi_device
虽然用户空间不须要直接用到spi_device结构体,可是这个结构体和用户空间的程序有密切的关系,理解它的成员有助于理解SPI设备节点的IOCTL命令,因此首先来介绍它。
在内核中,每一个spi_device表明一个物理的SPI设备。它的成员如程序清单 1.1所示。
程序清单 1.1 spi_device
数组
struct spi_device { structdevice dev; structspi_master *master; u32 max_speed_hz; /* 通讯时钟最大频率 */ u8 chip_select; /* 片选号 */ u8 mode; /*SPI设备的模式,下面的宏是它各bit的含义 */ #define SPI_CPHA 0x01 /* 采样的时钟相位 */ #define SPI_CPOL 0x02 /* 时钟信号起始相位:高或者是低电平*/ #define SPI_MODE_0 (0|0) #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 /* 为1时片选的有效信号是高电平*/ #define SPI_LSB_FIRST 0x08 /* 发送时低比特在前 */ #define SPI_3WIRE 0x10 /* 输入输出信号使用同一根信号线 */ #define SPI_LOOP 0x20 /* 回环模式 */ u8 bits_per_word; /* 每一个通讯字的字长(比特数) */ int irq; /*使用到的中断 */ void *controller_state; void *controller_data; char modalias[32]; /* 设备驱动的名字*/ };
因为一个SPI总线上能够有多个SPI设备,所以须要片选号来区分它们,SPI控制器根据片选号来选择不一样的片选线,从而实现每次只同一个设备通讯。
spi_device的mode成员有两个比特位含义很重要。SPI_CPHA选择对数据线采样的时机,0选择每一个时钟周期的第一个沿跳变时采样数据,1选择第二个时钟沿采样数据;SPI_CPOL选择每一个时钟周期开始的极性,0表示时钟以低电平开始,1选择高电平开始。这两个比特有四种组合,对应SPI_MODE_0~SPI_MODE_3。
另外一个比较重要的成员是bits_per_word。这个成员指定每次读写的字长,单位是比特。虽然大部分SPI接口的字长是8或者16,仍然会有一些特殊的例子。须要说明的是,若是这个成员为零的话,默认使用8做为字长。
最后一个成员并非设备的名字,而是须要绑定的驱动的名字。数据结构
2. spi_ioc_transfer
在用户使用设备节点的IOCTL命令传输数据的时候,须要用到 spi_ioc_transfer结构体,它的成员如程序清单 1.2所示。
程序清单 1.2 spi_ioc_transfer
ide
struct spi_ioc_transfer { __u64 tx_buf; /* 写数据缓冲 */ __u64 rx_buf; /* 读数据缓冲 */ __u32 len; /* 缓冲的长度 */ __u32 speed_hz; /* 通讯的时钟频率 */ __u16 delay_usecs; /* 两个spi_ioc_transfer之间的延时 */ __u8 bits_per_word; /* 字长(比特数) */ __u8 cs_change; /* 是否改变片选 */ __u32 pad; };
每一个 spi_ioc_transfer均可以包含读和写的请求,其中读和写的长度必须相等。因此成员len不是tx_buf和rx_buf缓冲的长度之和,而是它们各自的长度。SPI控制器驱动会先将tx_buf写到SPI总线上,而后再读取len长度的内容到rx_buf。若是只想进行一个方向的传输,把另外一个方向的缓冲置为0就能够了。
speed_hz和bits_per_word这两个成员能够为每次通讯配置不一样的通讯速率(必须小于spi_device的max_speed_hz)和字长,若是它们为0的话就会使用spi_device中的配置。
delay_usecs能够指定两个spi_ioc_transfer之间的延时,单位是微妙。通常不用定义。
cs_change指定这个cs_change结束以后是否须要改变片选线。通常针对同一设备的连续的几个spi_ioc_transfer,只有最后一个须要将这个成员置位。这样省去了来回改变片选线的时间,有助于提升通讯速率。函数
1.2 得到同SPI设备通讯的设备节点
为了在用户空间得到和SPI设备直接通讯的设备节点,必须有两个条件要知足:首先要有SPI控制器驱动,其次是要在内核初始化的时候注册一个spi_board_info,它的modalias成员必须为“spidev”。有了这两个条件,就能够和SPI设备进行通讯了。控制器的驱动通常由芯片厂家提供,开发者只需提供第二个条件。
spi_board_info的定义如程序清单 1.3所示。
程序清单 1.3 struct spi_board_info
ui
struct spi_board_info { char modalias[32]; /* 要绑定的驱动的名字 */ constvoid *platform_data; void *controller_data; int irq; u32 max_speed_hz; /* 通讯时钟最大速率 */ u16 bus_num; /* 总线编号 */ u16 chip_select; /* 片选号 */ u8 mode; /* 和spi_device中的mode成员相似 */ };
要了解这个结构体各个成员的意义请参考程序清单 1.1。
定义并注册structspi_board_info的位置通常是内核的arch/xxx/mach-xxxx/board-xxxx.c,好比3250的内核,这个文件是arch/arm/mach-lpc32xx/board-smartarm3250.c。定义并注册struct spi_board_info的代码如程序清单 1.4所示。
程序清单 1.4 定义并注册spi_board_info
this
static int __init smartarm3250_spi_usp_register(void) { structspi_board_info info = { .modalias= "spidev", .max_speed_hz= 5000000, .bus_num= 0, .chip_select= 0, }; returnspi_register_board_info(&info, 1); } arch_initcall(smartarm3250_spi_usp_register);
因为3250内核代码在arch/arm/mach-lpc32xx/board-smartarm3250.c已经定义了一个smartarm3250_spi_eeprom_register函数,所以在增长程序清单 1.4代码前先将这个函数注释掉。
程序清单 1.4注册了一个挂在0号SPI总线上的设备信息,它的片选号为0。增长完这段代码后将内核从新编译。在内核启动的时候,会为这个设备创建一个spi_device并和0号SPI总线的驱动进行绑定。同时内核会为这个设备申请一个主设备号为153的的设备号,次设备号和注册的顺序有关,最多支持32个同类设备。
内核从新编译并重启以后,若是系统中运行了udev,/dev下就会生成一个spidevX.D设备节点,其中X是总线编号,D是片选号。对于程序清单 1.4的代码应该自动生成的设备节点是spidev0.0。
通常SPI控制器驱动由芯片厂商提供,开发者所要在内核作的工做就是添加相似程序清单 1.4的内容。这样内核空间的工做减小了,用户空间的工做量加大了,由于用户空间的开发者须要全面了解SPI设备的工做方式和接口协议。spa
1.3 用户空间同设备节点的接口
对于/dev/spidevX.D设备节点,能够进行各类操做,这一小节介绍它支持的函数接口。
1. open/close
打开和关闭设备节点没有特别之处,直接使用open/write就能够了。
2. read/write
读写SPI设备能够直接使用read/write函数,可是每次读或者写的大小不能大于4096Byte。
3. IOCTL命令
用户空间对spidev设备节点使用IOCTL命令失败会返回-1。
l SPI_IOC_RD_MODE
读取SPI设备对应的spi_device.mode,mode的含义请参考程序清单 1.1。使用的方法以下:
ioctl(fd,SPI_IOC_RD_MODE, &mode);
其中第三个参数是一个uint8_t类型的变量。
l SPI_IOC_WR_MODE
设置SPI设备对应的spi_device.mode。使用的方式以下:
ioctl(fd,SPI_IOC_WR_MODE, &mode);
l SPI_IOC_RD_LSB_FIRST
查看设备传输的时候是否先传输低比特位。若是是的话,返回1。使用的方式以下:
ioctl(fd,SPI_IOC_RD_LSB_FIRST, &lsb);
其中lsb是一个uint8_t类型的变量。返回的结果存在lsb中。
l SPI_IOC_WR_LSB_FIRST
设置设备传输的时候是否先传输低比特位。当传入非零的时候,低比特在前,当传入0的时候高比特在前(默认)。使用的方式以下:
ioctl(fd,SPI_IOC_WR_LSB_FIRST, &lsb);
l SPI_IOC_RD_BITS_PER_WORD
读取SPI设备的字长。使用的方式以下:
ioctl(fd,SPI_IOC_RD_BITS_PER_WORD, &bits);
其中bits是一个uibt8_t类型的变量。返回的结果保存在bits中。
l SPI_IOC_WR_BITS_PER_WORD
设置SPI通讯的字长。使用的方式以下:
ioctl(fd,SPI_IOC_WR_BITS_PER_WORD, &bits);
l SPI_IOC_RD_MAX_SPEED_HZ
读取SPI设备的通讯的最大时钟频率。使用的方式以下:
ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ, &speed);
其中speed是一个uint32_t类型的变量。返回的结果保存在speed中。
l SPI_IOC_WR_MAX_SPEED_HZ
设置SPI设备的通讯的最大时钟频率。使用的方式以下:
ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ, &speed);
l SPI_IOC_MESSAGE(N)
一次进行双向/屡次读写操做。使用的方式以下:
structspi_ioc_transfer xfer[2];
......
status= ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
其中N是本次通讯中xfer的数组长度。spi_ioc_transfer的信息请参考程序清单 1.2。
/************************************************************************************/
若是想要在用户空间编写spi驱动,这就要在内核的arch/.../mach-*/board-*.c 中声明一个spi_board_info,
它的名字必定要是“spidev”,好比:
线程
struct spi_board_info info = { .modalias = "spidev", .max_speed_hz = 5000000, .bus_num = 0, .chip_select = 0, };
return spi_register_board_info(&info, 1);
这样只要控制器驱动加载了,spidev模块就会和这个设备绑定,并为设备申请一个设备号,主设备号为153,次设备号和设备加载的次序有关。
目前spidev支持最多32个设备。设备的名字是spidevX.D,其中X是总线编号,D是设备的片选号。若是正确安装并配置了udev,/dev目录下便会生成spidevX.D
设备节点。直接对这些设备节点操做就好了。
spidev的设备节点的接口包括open/close/read/write/ioctl。
~~~~~~~~~~~~~~~~~~~~~~~~~
其中open/close没有什么特别之处。
read/write的话有大小的限制,读写的大小默认不能超过4096字节。这个大小是一个模块加载参数,能够修改。
容许多个用户同时打开设备节点,spidev使用mutext进行互斥,多个用户同时读写时只有一个活动的用户,其余用户睡眠。
spidev的ioctl命令。
~~~~~~~~
SPI_IOC_RD_MODE:读取spi_device的mode。
SPI_IOC_RD_LSB_FIRST:若是是SPI_LSB_FIRST的方式则返回1。
SPI_IOC_RD_BITS_PER_WORD:读取spi_device的bits_per_word.
SPI_IOC_RD_MAX_SPEED_HZ:读取spi_device的max_speed_hz.
SPI_IOC_WR_MODE:设置spi_device的mode,并调用spi_setup当即使设置生效。
SPI_IOC_WR_LSB_FIRST:设置spi使用SPI_LSB_FIRST的传输模式。当即生效。
SPI_IOC_WR_BITS_PER_WORD:读取字长。
SPI_IOC_WR_MAX_SPEED_HZ:设置时钟速率。
不管读取,用户传输的第三个参数都被看成缓冲地址指针。读取时存放结果,写入时存放要写的内容。
SPI_IOC_MESSAGE:这个命令用来进行复杂的通讯。参数涉及到一个结构体。各个成员的意义与spi_transfer一致。
指针
struct spi_ioc_transfer { __u64 tx_buf; __u64 rx_buf; __u32 len; __u32 speed_hz; __u16 delay_usecs; __u8 bits_per_word; __u8 cs_change; __u32 pad; /* If the contents of 'struct spi_ioc_transfer' ever change * incompatibly, then the ioctl number (currently 0) must change; * ioctls with constant size fields get a bit more in the way of * error checking than ones (like this) where that field varies. * * NOTE: struct layout is the same in 64bit and 32bit userspace. */ };
内核文档中一个例子:
code
static void do_msg(int fd, int len) { struct spi_ioc_transfer xfer[2]; unsigned char buf[32], *bp; int status; memset(xfer, 0, sizeof xfer); memset(buf, 0, sizeof buf); if (len > sizeof buf) len = sizeof buf; buf[0] = 0xaa; xfer[0].tx_buf = (__u64) buf; xfer[0].len = 1; xfer[1].rx_buf = (__u64) buf; xfer[1].len = len; status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer); if (status < 0) { perror("SPI_IOC_MESSAGE"); return; } printf("response(%2d, %2d): ", len, status); for (bp = buf; len; len--) printf(" %02x", *bp++); printf("/n"); }
内核在documentation/spi目录下有spidev的例子。
注意
~~~~
虽然多个用户不能同一时刻对spi进行设置或读写,可是同一用户却没法组织其余用户修改同一设备的设置。
举例来讲,usr1打开设备节点,而后使用ioctl设置了时钟速率,此时usr1线程被调度出去,而后usr2操做同一个设备,将它的时钟设为另外一个值。
此时usr1从新调度去使用read函数,则达不到预期的效果。
建议不要有两个程序操做spidevX.D设备节点。