SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器、传感器、存储设备,SPI设备分为主设备和从设备两种,用于通信和控制的四根线分别是:
- CS 片选信号
- SCK 时钟信号
- MISO 主设备的数据输入、从设备的数据输出脚
- MOSI 主设备的数据输出、从设备的数据输入脚
因为在大多数情况下,CPU或SOC一侧通常都是工作在主设备模式,所以,目前的Linux内核版本中,只实现了主模式的驱动框架。
一、硬件结构
通常,负责发出时钟信号的设备我们称之为主设备,另一方则作为从设备,下图是一个SPI系统的硬件连接示例:
如上图所示,主设备对应SOC芯片中的SPI控制器,通常,一个SOC中可能存在多个SPI控制器,像上面的例子所示,SOC芯片中有3个SPI控制器。每个控制器下可以连接多个SPI从设备,每个从设备有各自独立的CS引脚。每个从设备共享另外3个信号引脚:SCK、MISO、MOSI。任何时刻,只有一个CS引脚处于有效状态,与该有效CS引脚连接的设备此时可以与主设备(SPI控制器)通信,其它的从设备处于等待状态,并且它们的3个引脚必须处于高阻状态。
二、工作时序
按照时钟信号和数据信号之间的相位关系,SPI有4种工作时序模式:
我们用CPOL表示时钟信号的初始电平的状态,CPOL为0表示时钟信号初始状态为低电平,为1表示时钟信号的初始电平是高电平。另外,我们用CPHA来表示在那个时钟沿采样数据,CPHA为0表示在首个时钟变化沿采样数据,而CPHA为1则表示要在第二个时钟变化沿来采样数据。内核用CPOL和CPHA的组合来表示当前SPI需要的工作模式:
- CPOL=0,CPHA=1 模式0
- CPOL=0,CPHA=1 模式1
- CPOL=1,CPHA=0 模式2
- CPOL=1,CPHA=1 模式3
软件架构
在内核的SPI驱动的软件架构中,进行了合理的分层和抽象,如下图所示:
图2.1 SPI驱动的软件架构
SPI控制器驱动程序
SPI控制器不用关心设备的具体功能,它只负责把上层协议驱动准备好的数据按SPI总线的时序要求发送给SPI设备,同时把从设备收到的数据返回给上层的协议驱动,因此,内核把SPI控制器的驱动程序独立出来。SPI控制器驱动负责控制具体的控制器硬件,诸如DMA和中断操作等等,因为多个上层的协议驱动可能会通过控制器请求数据传输操作,所以,SPI控制器驱动同时也要负责对这些请求进行队列管理,保证先进先出的原则。
SPI通用接口封装层
为了简化SPI驱动程序的编程工作,同时也为了降低协议驱动程序和控制器驱动程序的耦合程度,内核把控制器驱动和协议驱动的一些通用操作封装成标准的接口,加上一些通用的逻辑处理操作,组成了SPI通用接口封装层。这样的好处是,对于控制器驱动程序,只要实现标准的接口回调API,并把它注册到通用接口层即可,无需直接和协议层驱动程序进行交互。而对于协议层驱动来说,只需通过通用接口层提供的API即可完成设备和驱动的注册,并通过通用接口层的API完成数据的传输,无需关注SPI控制器驱动的实现细节。
SPI协议驱动程序
上面我们提到,控制器驱动程序并不清楚和关注设备的具体功能,SPI设备的具体功能是由SPI协议驱动程序完成的,SPI协议驱动程序了解设备的功能和通信数据的协议格式。向下,协议驱动通过通用接口层和控制器交换数据,向上,协议驱动通常会根据设备具体的功能和内核的其它子系统进行交互,例如,和MTD层交互以便把SPI接口的存储设备实现为某个文件系统,和TTY子系统交互把SPI设备实现为一个TTY设备,和网络子系统交互以便把一个SPI设备实现为一个网络设备,等等。当然,如果是一个专有的SPI设备,我们也可以按设备的协议要求,实现自己的专有协议驱动。
SPI通用设备驱动程序
有时候,考虑到连接在SPI控制器上的设备的可变性,在内核没有配备相应的协议驱动程序,对于这种情况,内核为我们准备了通用的SPI设备驱动程序,该通用设备驱动程序向用户空间提供了控制SPI控制的控制接口,具体的协议控制和数据传输工作交由用户空间根据具体的设备来完成,在这种方式中,只能采用同步的方式和SPI设备进行通信,所以通常用于一些数据量较少的简单SPI设备。
三、确定驱动文件
SPI作为linux里面比较小的一个子系统,其驱动程序位于/drivers/spi/*目录,首先,我们可以通过Makefile及Kconfig来确定我们需要看的源文件。
- #
- # Makefile for kernel SPI drivers.
- #
- # small core, mostly translating board-specific
- # config declarations into driver model code
- obj-$(CONFIG_SPI_MASTER) += spi.o
- obj-$(CONFIG_SPI_SPIDEV) += spidev.o
- # SPI master controller drivers (bus)
- obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o
- obj-$(CONFIG_SPI_IMX) += spi-imx.o
对应的Kconfig去配置内核
编译生成的目标文件如下
通过以上分析我们知道,spi驱动由三部分组成,分别是core(spi.c),master controller driver (spi_imx.c)以及SPIprotocol drivers (spidev.c)。
四、数据结构分析
Spi驱动涉及的数据结构主要位于/include/linux/spi.h,其中spi.c,spi-imx.c,spidev.c均用到了spi.h里的结构体。
1.spi_master
spi_master代表一个主机控制器,此处表示imx的SPI控制器。一般不需要自己编写spi控制器驱动,但是了解这个结构体还是必要的。
- struct spi_master {
- struct device dev;
-
- struct list_head list;
-
-
-
-
-
-
-
- s16 bus_num;
-
-
-
-
- u16 num_chipselect;
-
-
-
-
- u16 dma_alignment;
-
-
- u16 mode_bits;
-
-
- u32 bits_per_word_mask;
- #define SPI_BPW_MASK(bits) BIT((bits) - 1)
- #define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
- #define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))
-
-
- u32 min_speed_hz;
- u32 max_speed_hz;
-
-
- u16 flags;
- #define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
- #define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
- #define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
- #define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
- #define SPI_MASTER_MUST_TX BIT(4) /* requires tx */
-
-
- spinlock_t bus_lock_spinlock;
- struct mutex bus_lock_mutex;
-
-
- bool bus_lock_flag;
-
-
-
-
-
-
-
- int (*setup)(struct spi_device *spi);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- int (*transfer)(struct spi_device *spi,
- struct spi_message *mesg);
-
-
- void (*cleanup)(struct spi_device *spi);
-
-
-
-
-
-
-
-
- bool (*can_dma)(struct spi_master *master,
- struct spi_device *spi,
- struct spi_transfer *xfer);
-
-
-
-
-
-
-
- bool queued;
- struct kthread_worker kworker;
- struct task_struct *kworker_task;
- struct kthread_work pump_messages;
- spinlock_t queue_lock;
- struct list_head queue;
- struct spi_message *cur_msg;
- bool busy;
- bool running;
- bool rt;
- bool auto_runtime_pm;
- bool cur_msg_prepared;
- bool cur_msg_mapped;
- struct completion xfer_completion;
- size_t max_dma_len;
-
- int (*prepare_transfer_hardware)(struct spi_master *master);
- int (*transfer_one_message)(struct spi_master *master,
- struct spi_message *mesg);
- int (*unprepare_transfer_hardware)(struct spi_master *master);
- int (*prepare_message)(struct spi_master *master,
- struct spi_message *message);
- int (*unprepare_message)(struct spi_master *master,
- struct spi_message *message);
-
-
-
-
-
- void (*set_cs)(struct spi_device *spi, bool enable);
- int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
- struct spi_transfer *transfer);
-
-
- int *cs_gpios;
-
-
- struct dma_chan *dma_tx;
- struct dma_chan *dma_rx;
-
-
- void *dummy_rx;
- void *dummy_tx;
- };
2. spi_device
spi_device代表一个外围spi设备,由master controller driver注册完成后扫描BSP中注册设备产生的设备链表并向spi_bus注册产生。在内核中,每个spi_device代表一个物理的spi设备。
- struct spi_device {
- struct device dev;
- struct spi_master *master;
- u32 max_speed_hz;
- u8 chip_select;
- 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? 为1时片选的有效信号是高电平*/
- #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 */
- u8 bits_per_word;
- int irq;
- void *controller_state;
- void *controller_data;
- char modalias[SPI_NAME_SIZE];
- int cs_gpio;
-
-
-
-
-
-
-
-
-
-
- };
由于一个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作为字长。
最后一个成员并不是设备的名字,而是需要绑定的驱动的名字。
3.spi_driver
spi_driver代表一个SPI protocol drivers,即外设驱动。
- 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);
- int (*suspend)(struct spi_device *spi, pm_message_t mesg);
- int (*resume)(struct spi_device *spi);
- struct device_driver driver;
- };
通常对于从事Linux驱动工作人员来说,spi设备的驱动主要就是实现这个结构体中的各个接口,并将之注册到spi子系统中去。
4.spi_transfer
spi_transfer代表一个读写缓冲对,包含接收缓冲区及发送缓冲区,其实,spi_transfer的发送是通过构建spi_message实现,通过将spi_transfer中的链表transfer_list链接到spi_message中的transfers,再以spi_message形势向底层发送数据。每个spi_transfer都可以对传输的一些参数进行设置,使得master controller按照它要求的参数进行数据发送。
- struct spi_transfer {
-
-
-
-
-
- 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;
- u8 tx_nbits;
- u8 rx_nbits;
- #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;
- };</span>
5.spi_message
spi_message代表spi消息,由多个spi_transfer段组成。
spi_message用来原子的执行spi_transfer表示的一串数组传输请求。
这个传输队列是原子的,这意味着在这个消息完成之前不会有其它消息占用总线。
消息的执行总是按照FIFO的顺序。
向底层提交spi_message的代码要负责管理它的内存空间。未显示初始化的内存需要使用0来初始化。
- struct spi_message {
- struct list_head transfers;
-
- struct spi_device *spi;
-
- unsigned is_dma_mapped:1;
-
-
-
-
-
-
-
-
-
-
-
-
-
- void (*complete)(void *context);
- void *context;
- unsigned frame_length;
- unsigned actual_length;
-
- int status;
-
-
-
-
-
- struct list_head queue;
- void *state;
- };
控制器驱动会先写入tx的数据,然后读取同样长度的数据。长度指示是len。
如果tx_buff是空指针,填充rx_buff的时候会输出0(为了产生接收的时钟),如果rx_buff是NULL,接收到的数据将被丢弃。
只有len长读的数据会被输出和接收。
输出不完整的字长是错误的(比如字长为2字节的时候输出三个字节,最后一个字节凑不成一个整字)。
本地内存中的数据总是使用本地cpu的字节序,无论spi的字节序是大段模式还是小段模式(使用SPI_LSB_FIRS)
当spi_transfer的字长不是8bit的2次幂的整数倍,这些数据字就包含扩展位。在spi通信驱动看来内存中的数据总是刚好对齐的,所以rx中位定义和rx中未使用的比特位总是最高有效位。(比如13bit的字长,每个字占2字节,rx和tx都应该如此存放)
所有的spi传输都以使能相关的片选线为开始。一般来说片选线在本消息结束之前保持有效的状态。驱动可以使用
spi_transfer中的cs_change成员来影响片选:
(i)如果transfer不是message的最后一个,这个标志量可以方便的将片选线置位无效的状态。
有时需要这种方法来告知芯片一个命令的结束并使芯片完成这一批处理任务。
(ii)当这个trasfer是最后一个时,片选可以一直保持有效知道下一个transfer到来。
在多spi从机的总线上没有办法阻止其他设备接收数据,这种方法可以作为一个特别的提示;开始往另一个设备传输信息就要先将本芯片的片选置为无效。但在其他情况下,这可以保证正确性。一些设备后面的信息依赖于前面的信息并且在一个处理序列完成后需要禁用片选线。
上面这段是翻译的,讲的不明白。
再说一下:cs_change影响此transfer完成后是否禁用片选线并调用setup改变配置。(这个标志量就是chip select change片选改变的意思)
没有特殊情况,一个spi_message应该只在最后一个transfer置位该标志量。
6.spi_board_info
spi_device的板信息用spi_board_info结构体描述,该结构体记录着SPI外设使用的主机控制器序号、片选序号、数据比特率、SPI传输模式(即CPOL、CPHA)等。ARM Linux3.x之后的内核在改为设备树之后,不再需要在arch/arm/mach-xxx中编码SPI的板级信息了,而倾向于在SPI控制器节点下填写子节点。
- struct spi_board_info {
-
-
-
-
-
-
-
- char modalias[SPI_NAME_SIZE];
- const void *platform_data;
- void *controller_data;
- int irq;
-
-
- u32 max_speed_hz;
-
-
-
-
-
-
-
-
- u16 bus_num;
- u16 chip_select;
-
-
-
-
- u16 mode;
-
-
-
-
-
-
- };
7.spi_bitbang
spi_bitbang是具体的负责信息传输的数据结构,它维护一个workqueue_struct,每收到一个消息,都会向其中添加一个work_struct,由内核守护进程在将来的某个时间调用该work_struct中的function进行消息发送。
- struct spi_bitbang {
- spinlock_t lock;
- u8 busy;
- u8 use_dma;
- u8 flags;
-
- struct spi_master *master;
-
-
-
-
- int (*setup_transfer)(struct spi_device *spi,
- struct spi_transfer *t);
-
- void (*chipselect)(struct spi_device *spi, int is_on);
- #define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */
- #define BITBANG_CS_INACTIVE 0
-
-
-
-
- int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);
-
-
- u32 (*txrx_word[4])(struct spi_device *spi,
- unsigned nsecs,
- u32 word, u8 bits);
- };