【学习笔记】Linux内核驱动 -- SPI Device & Controller Driver 数据传输
| 本文主要从利用SPI进行数据传输的角度,总结Linux kernel源码中的 spi-bcmbca-hsspi.c(kernel6.3及之后开源出来的), spi-qup.c, spi-gpio.c这几种SPI控制器,和tpm作为SPI Device的驱动。
技术摘要:
- 驱动注册 -- probe函数
- SPI Contoller和SPI Master基是同一概念
- spi控制器用到了kthread,queue,worker work来进行数据传输和发送,对此本文不做深入介绍。
- 涉及使用DMA的情况本文暂不考虑。
- spi设备驱动和spi控制器驱动 连接起来的两点(“设备驱动的终点” -- “控制器驱动的工作”)
- 设备驱动:一般设备驱动会实现一个xxx_transfer()或者xxx_send()+xxx_recv()接口来作为设备驱动数据传输的“终点” -- 调用的是SPI Core的接口。
- 控制器驱动:不同控制器驱动在注册(probe)时会实现控制器的transfer()(旧)接口或tansfer_one()(新)接口,有的控制器也会直接自己实现tansfer_one_message()接口。这种transfer函数最终控制硬件数据传输。
- 本文涉及的主要驱动源码:
1 /// 以kernel6.10为例,有参考kernel4.19 2 3 /* 1. SPI设备驱动 ‘SPI Device Driver’ */ 4 driver/char/tpm/tpm_tis_core.c 5 driver/char/tpm/tpm_tis_spi_main.c // 4.19版本内核为 tpm_tis_spi.c 6 7 /* 2. SPI控制器驱动 ‘SPI Controller Driver’*/ 8 // GPIO模拟SPI控制器 9 driver/spi/spi-gpio.c 10 driver/spi/spi-bitbang.c 11 // 高通硬件SPI控制器 12 driver/spi/spi-qup.c 13 // 博通硬件SPI控制器 14 driver/spi/spi-bcmbca-hsspi.c 15 16 /* 3. SPI Core APIs */ 17 driver/spi/spi.c 18 driver/spi/spi.h
...
-
SPI数据流框架

1. SPI 设备驱动 -- 数据读写接口
- 主要思路:
- SPI设备驱动定义数据读写的ops -- 不同SPI设备驱动有不同的实现(transfer/send+recv),但最终都是调用SPI Core的接口
- 具体的一组数据会存储在spi_transfer结构体中,几个spi_transfer可以放到一个spi_message结构体中。
- spi_transfer和spi_message 这两个结构体就是SPI数据传输的初始数据单元啦。
- SPI设备驱动在probe(注册)的时候会绑定数据读写的ops (userspace的应用间接调用)
- SPI设备驱动定义数据读写的ops -- 不同SPI设备驱动有不同的实现(transfer/send+recv),但最终都是调用SPI Core的接口
1.1 先定义ops -- 数据传输接口处理函数集
1 /// 设备驱动注册时绑定ops的主要步骤 kernel6.10 2 /* 0. ops */ 3 int tpm_tis_spi_transfer(struct tpm_tis_data *data, u32 addr, u16 len, 4 u8 *in, const u8 *out) 5 { 6 ... 7 // 根据kernel版本不同,这里可能还会有进一步函数调用,但最终主要的数据传输接口及其顺序都是下面这几个 8 spi_message_init(&m); 9 spi_message_add_tail(&spi_xfer, &m); 10 ret = spi_sync_locked(phy->spi_device, &m); // 真正把数据传输给SPI控制器驱动去处理 11 ... 12 } 13 static int tpm_tis_spi_read_bytes(struct tpm_tis_data *data, u32 addr, 14 u16 len, u8 *result, enum tpm_tis_io_mode io_mode) 15 { 16 return tpm_tis_spi_transfer(data, addr, len, result, NULL); 17 } 18 19 static int tpm_tis_spi_write_bytes(struct tpm_tis_data *data, u32 addr, 20 u16 len, const u8 *value, enum tpm_tis_io_mode io_mode) 21 { 22 return tpm_tis_spi_transfer(data, addr, len, NULL, value); 23 } 24 static const struct tpm_tis_phy_ops tpm_spi_phy_ops = { 25 .read_bytes = tpm_tis_spi_read_bytes, 26 .write_bytes = tpm_tis_spi_write_bytes, 27 };
1.2 SPI设备驱动注册时绑定 ops
1 /* 1. probe */ 2 // driver/char/tpm/tpm_tis_spi_main.c 3 tpm_tis_spi_driver_probe(struct spi_device *spi) 4 -->tpm_tis_spi_probe(struct spi_device *dev) 5 --> tpm_tis_spi_init(dev, phy, irq, &tpm_spi_phy_ops); 6 -->tpm_tis_core_init(&spi->dev, &phy->priv/*struct tpm_tis_data *priv*/, irq, phy_ops/*=&tpm_spi_phy_ops*/, NULL); 7 // driver/char/tpm/tpm_tis_core.c 8 -->priv->phy_ops = phy_ops;
2. SPI 控制器驱动 -- 数据读写
- 主要思路:
- 定义和绑定 transfer() / transfer_one() / transfer_one_message() 给 SPI控制器
- 其中transfer() 和 transfer_one() 必须定义一个(transfer() 几乎过时了)
- transfer_one_message()可以不单独实现,SPI Core中有默认 ctlr->transfer_one_message = spi_transfer_one_message;
- transfer() / transfer_one() / transfer_one_message() 基本是软件处理的终点了,里面做的事情就是读写硬件了(SPI硬件控制器或SPI GPIO)
- 初始化queue,worker,work并开始线程处理工作
- 定义和绑定 transfer() / transfer_one() / transfer_one_message() 给 SPI控制器
2.1 GPIO模拟SPI控制器
2.2 高通SPI控制器
2.3 博通SPI控制器

3. SPI Core APIs -- 数据传输
3.1 数据传输到队列
-
- 主要思路:
- 首先,SPI设备驱动调用 spi_message_init() 和 spi_message_add_tail() 来组成SPI数据信息
- 然后,调用SPI Core的xxx_sync之类的函数来将数据放入数据传输工作队列中
- 主要思路:

3.2 队列中的数据处理接口 spi_pump_messages(struct kthread_work *work)
-
- 思路:调用ctlr->transfer_one_message(ctlr, ctlr->cur_msg);

4. 其他内容
4.1 cs_change 对CS片选信号的控制逻辑
| 写在前面:不同内核版本和不同SPI控制器驱动的实现都可能存在稍有差异的时序逻辑,研究SPI时序时千万不要固定思维,最好还是先了解一下源码逻辑。除了SPI控制器对对CS的处理逻辑稍有差异,SPI 设备对CS的要求也各不相同,像TPM芯片对CS的时序就有自己的严格要求。
| 虽然CS是由SPI控制器来控制拉高拉低的,但是SPI设备驱动也可以通过spi_transfer结构体中的cs_change成员间接控制CS GPIO。
笼统的有两种:
4.1.1 spi_transfer_one_message() -- SPI Core默认逻辑
-
-
- cs_change=0
- 不是最后一个spi_transfer,并且当前spi_transfer和下一个spi_transfer的cs_off不同时,改变CS Line极性。
- cs_change=1
- 如果是spi_message中的最后一个spi_transfer,则保持CS Line状态不变。
- 如果不是最后一个spi_transfer,则 disable->延时(常见的是10us)-> enable 一次CS Line。
- cs_change=0
-

-
-
- cs_change=0
- 不操作CS Line,保持CS Line状态不变。
- cs_change=1
- 如果是spi_message中的最后一个spi_transfer,则保持CS Line状态不变。
- 如果不是最后一个spi_transfer,则 disable->延时(常见的是10us)-> enable 一次CS Line。
- cs_change=0
-
4.1.2 自定义实现 -- 这里以博通的spi-bcmbca-hsspi.c驱动为例

|L| 一步一步向目标靠近,永远记得我要去哪。

浙公网安备 33010602011771号