从内核中最简单的驱动程序入手,描述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驱动代码实现