从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知
SPI是串行外设接口(Serial Peripheral Interface)的缩写,由Motorola公司开发,SPI是同步四线制高速的全双工同步通讯总线,目前速率最高可达50MHz,也属于主从式结构所有的传输都是通过主机发起的,但和I2C总线不一样的是主机上只能有主机控制器,各个从机通过不同的片选线来进行选择,典型的连接如下图所示:
图中Master是主机,有4个片选信号SS0,SS1,SS2,SS3(上面的横杠表示低电平有效)分别连接四个从机,由片选信号来决定哪个从机被选中,从而与之通信。
SCLK:串行时钟线,由主机发出。
MISO(SDI):Master input,Slave output,主入从出,即从机发送数据,主机接收数据。
MOSI(SDO):Master output,Slave input,主出从入,即主机发送数据,从机接收数据。
SS:Slave select,从机选择线,由主机发出低电平有效。 一个简单的增加片选的方法是使用GPIO来模拟SPI_CSn信号,在每传输一个数据之前,将相应的GPIO置低(假设从设备片选信号为低有效),选中对应的SPI从设备,传输结束后再将GPIO置高。
因为主机通过MOSI发送数据的同时也可以通过MISO接收数据,所以SPI总线是全双工的。所有的数据通过SCLK进行同步,所以它也是同步的总线。SPI典型时序如下图所示:
图中 CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。
CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。
spi四种模式SPI的相位(CPHA)和极性(CPOL)分别可以为0或1,对应的4种组合构成了SPI的4种模式(mode),其中使用的最为广泛的是SPI0和SPI3方式 。
Mode0 CPOL=0, CPHA=0 SCLK平时为低电平,在SCLK的上升沿采样数据,下降沿输出数据 ;
Mode1 CPOL=0, CPHA=1 SCLK平时为低电平,在SCLK的上升沿输出数据,下降沿采样数据;
Mode2 CPOL=1, CPHA=0 SCLK平时为高电平,在SCLK的上升沿输出数据,下降沿采样数据;
Mode3 CPOL=1, CPHA=1 SCLK平时为高电平,在SCLK的上升沿采样数据,下降沿输出数据。
SPI有四种不同的工作模式,不同的从设备可能在出厂时就配置为某种模式,这是不能够改变的,但是通信双方必须工作在同一模式下,所以我们可以对属设备SPI模式进行配置。
SPI驱动包含主机控制器,SPI Core和SPI设备驱动。SPI主机控制器一般是由SCO芯片厂商来实现的,在实际开发中更多关注的是SPI的设备驱动,SPI的设备表示方法如下:(include\linux\spi\spi.h)
struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
u16 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 */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
/* the statistics */
struct spi_statistics statistics;
/*
* 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
* - ...
*/
};
主要设备成员如下:
struct spi_master *master; // 所连接的SPI主机控制器
u32 max_speed_hz; // 设备工作的最高频率
u8 chip_select; // 片选信号
u8 bits_per_word; // SPI的字长
u16 mode; // SPI的工作模式
内核中使用设备树来描述设备节点,本文以AM335x的开发版为例,讲解SPI设备的设备树的描写方法。
首先查看AM3358的原理图和芯片手册,对SPI1的相关引脚设置为SPI功能,其中截取的部门原理图如下图所示:
AM3358原理图和芯片手册下载链接见AM3358芯片手册和AM3358原理图
将相应的引脚设置为SPI功能,设备树中代码如下
&am33xx_pinmux {
spi1_pins: pinmux_spi1_pins {
pinctrl-single,pins = <
AM33XX_IOPAD(0x990, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_aclkx.spi1_cslk */
AM33XX_IOPAD(0x994, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_fsx.spi1_d0 */
AM33XX_IOPAD(0x998, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_axr0_spi1_d1 */
AM33XX_IOPAD(0x99c, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_ahclkr.spi1.cs0 */
>;
};
};
引脚复用功能设定好之后,建立SPI1主控制器的节点,pinctrl指定了主控制器所使用的管脚;spidev0: spidev0@0是子节点,表示在这个SPI主控制器上的设备,compatible用于匹配驱动
&spi1 {
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins>;
status = "okay";
spidev0: spidev0@0{
spi-max-frequency = <25000000>;
reg = <0>;
compatible = "ti,spidev0";
};
};
内核在启动过程中会自动把上面的SPI设备树节点解析为 struct spi_device,当有匹配的驱动时,会调用驱动中的probe函数,在内核中spi_driver结构体定义如下:(include\linux\spi\spi.h)
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
在驱动文件的最开始,应该首先实现SPI驱动的注册和注销函数,SPI驱动的注册函数定义如下(include\linux\spi\spi.h)
/* use a define to avoid include chaining to get THIS_MODULE */
#define spi_register_driver(driver) __spi_register_driver(THIS_MODULE, driver)
&emap;SPI驱动的注销函数定义如下(include\linux\spi\spi.h)
static inline void spi_unregister_driver(struct spi_driver *sdrv)
{
if (sdrv)
driver_unregister(&sdrv->driver);
}
除此之外内核中还定义了一个宏来简化SPI驱动注册和注销的函数,定义如下(include\linux\spi\spi.h)
#define module_spi_driver(__spi_driver) \
module_driver(__spi_driver, spi_register_driver, \
spi_unregister_driver)
其中module_drive定义在(include\linux\device.h)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
分析上面module_spi_driver宏可以发现这个宏可以同时实现SPI驱动的注册和注销函数,示例如下:
struct spi_driver my_spi_drv; // 定义SPI驱动结构体
module_spi_driver(my_spi_drv); // 注册和注销SPI驱动
SPI是全双工的,通常需要一边发送数据一边接收数据,并且发送和接收的数据通常字节数相等。为了描述这一对缓冲区,SPI定义了一个结构体如下:(include\linux\spi\spi.h),主要的变量已在结构体中注释。
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
const void *tx_buf; // 发送缓冲区
void *rx_buf; // 接收缓冲区
unsigned len; // 缓冲区长度
dma_addr_t tx_dma;
dma_addr_t rx_dma;
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1;
unsigned tx_nbits:3;
unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
struct list_head transfer_list;
};
struct spi_transfer结构对象构成一个传输事务,多个传输事务构成一条消息,消息中的传输事务以链表的形式组织在一起,消息的结构类型为struct spi_message,SPI消息传输中主要使用的函数接口如下:
static inline void spi_message_init(struct spi_message *m); // 初始化消息m
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m); // 将传输事务t加入到消息m中的链表
int spi_sync(struct spi_device *spi, struct spi_message *message); // 发起SPI设备上的事务传输,同步等待所有事务完成。成功返回0失败返回错误码
static inline void spi_transfer_del(struct spi_transfer *t); // 从链表中删除事务t
使用传输事务和消息进行传输的典型代码如下:
int ret;
struct spi_message m;
int rx, tx;
struct spi_transfer t = {
.tx_buf = &tx,
.rx_buf = &rx,
.len = 4;
};
spi_message_init(&m); // 初始化消息m
spi_message_add_tail(&t, &m); // 将传输事务t加入到消息m的链表
ret = spi_sync(&my_spidev, &m); // 发起传输,同步等待所有事件完成
if (ret < 0)
{
printk("spi_sync failed.\n");
return -EIO; // I/O error
}
内核中也提供了一下简化的SPI数据传输函数,如下:
// 将buf中的数据写len个字节到spi设备。成功返回0,失败返回错误码
static inline int spi_write(struct spi_device * spi, const void * buf, size_t len);
// 从spi设备中读len个字节到buf。成功返回0,失败返回错误码
static unsigned int spi_read(struct spi_device * spi, void * buf, size_t len);
// 将txbuf中的数据写n_tx个字节到spi设备,再从spi设备中读n_rx个字节数据到rxbuf。成功返回0,失败返回错误码
int spi_write_then_read(struct spi_device * spi, const void * txbuf, unsigned n_tx, void * rxbuf, unsigned n_rx);
查看内核源码可以看出,简化的spi传输函数也采用了传输事务和消息,只是将其封装在简化的函数中,spi_write函数的内核中源码如下:
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
return spi_sync_transfer(spi, &t, 1);
}
static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers)
{
struct spi_message msg;
spi_message_init_with_transfers(&msg, xfers, num_xfers);
return spi_sync(spi, &msg);
}
static inline void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers)
{
unsigned int i;
spi_message_init(m);
for (i = 0; i < num_xfers; ++i)
spi_message_add_tail(&xfers[i], m);
}
spi应用层主要使用ioctl对SPI设备进行控制,内核中定义了常用的操作命令如下(include\uapi\linux\spi\spidev.h)
#define SPI_IOC_MAGIC 'k'
/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */
#define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8)
/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8)
/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8)
/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32)
/* Read / Write of the SPI mode field */
#define SPI_IOC_RD_MODE32 _IOR(SPI_IOC_MAGIC, 5, __u32)
#define SPI_IOC_WR_MODE32 _IOW(SPI_IOC_MAGIC, 5, __u32)
本节主要对SPI协议和内核中SPI相关的结构体,设备树和操作函数进行分析,具体实现代码将在下一节进行描述,链接如下SPI驱动代码实现