spi在应用层的体现
spi 分为主机模式和从机模式,一般soc 自带的spi 控制器,我们都将它用作主机模式与外挂的从设备通信。从设备例如 oled芯片、flash芯片、陀螺仪芯片等等。
那么spi 驱动和设备,自然也就分为主机驱动、设备和从机驱动、设备。那么如何在Linux 下查看这些信息呢?
首先查看spi 控制器的驱动和设备信息:
spi 控制器的驱动和设备在内核中由platform 总线来管理,使用platform_device 表示设备,platform_driver 表示驱动。所以查找/sys/bus/platform/devices/
目录就可以发现spi 控制器的设备信息,设备名字由设备树的节点名来决定:
在设备树中找到以下的设备节点,“ecspi@02010000” 是它的节点名
所以在/sys/bus/platform/devices/ 目录下就可以找到2010000.ecspi 这个文件夹。
它的driver 软链接指向/sys/bus/platform/drivers/spi_imx 就代表它已经匹配上了它的驱动就是spi_imx。
另外还可以通过查看spi_master 文件夹下的文件夹名字确认 spi_master的总线号(spi_master->bus_num)
spi_imx 就是imx6ull平台上的spi 控制器驱动,名字根据platform_driver->driver->name 决定。
如果/sys/bus/platform/devices/driver 没有链接到驱动目录,也就是说设备 和驱动没有匹配成功我们也可以到/sys/bus/platform/drivers/ 目录下根据名字来查找。
除了soc 自带的spi 控制器外,内核还提供了gpio 模拟spi的驱动,以下是它的设备与驱动体现。
(这个gpio 模拟的spi控制器,spi总线号是327666,为啥这么奇怪呢,这个会在之后的spi master驱动中讲解)
spi 从设备的设备信息:
spi 从设备的驱动和设备由spi 总线模型管理,使用spi_driver 表示驱动、spi_device 表示设备。
可以在/sys/bus/spi/devices/ 目录下找到所有的spi 从设备。
它们名字的定义是:spiB.D,B表示spi_master 的总线号,D表示该设备是这条spi总线上的第几个设备。(D的值来源于dtb spi从设备节点的reg属性,也表示它使用的是spi 总线的第几个片选)
同样的设备文件夹中的driver 也链接到驱动文件夹,所有的spi 从设备驱动都在"/sys/bus/spi/drivers" 文件夹下。
spi 子系统中的重要数据结构
spi 硬件连接框图,如上图spi 设备有主设备、从设备之分,一般将soc 自带的spi 控制器作为主设备,外接的flash、oled 等芯片作为从设备。
spi_master
内核使用一个struct spi_master
来描述一个spi 主设备(这里也可以默认把它理解成spi 控制器);
对于一个spi 控制器来说,最关心的就是发送和接收数据,所以在spi_master 中最重要的成员就是传输函数spi_master->transfer
//定义于 include\linux\spi\spi.h
以下内核提供的回调函数都是在spi_master 注册过程中设置的。
(下面标出的回调函数在spi_sync中都会调用到,尤其是master->transfer_one_message、master->transfer_one 这两个函数极为重要)
spi_device
使用struct spi_device
来描述一个spi 从设备(主要就是一些硬件信息),里面记录有设备的片选引脚、最大传输速率、挂在哪个SPI控制器下面、工作模式等等。(可以参考设备树节点内容来记忆,因为它们的值大多都来自设备树)
在注册spi_master 的过程中会扫描spi_master->device.of_node(spi 控制器设备树节点) 下的所有子节点,将每个子节点转化成一个spi_device 并注册。
//include\linux\spi\spi.h
spi_transfer
知道了如何描述图中的主设备、从设备,该如何描述主设备与从设备之间传输的数据?
描述一次传输用spi_transfer
:
在spi_transfer 中最重要的就是tx_buf 和rx_buf,它们分别用于指向发送和接收的buf地址(buf 内存需要自己分配),len 则是长度。
transfer_list:添加到spi_message->transfers 链表中的节点。
(注意这里的长度并非单指rx_buf 的长度或tx_buf 的长度,而是以它们中最长的为准。
比如读一个寄存器,寄存器地址长度1字节,读1个字节长度的内容。你可以有两种读法:
- 用一个spi_transfer 来完成,让tx_buf 指向一个 1字节长度的buf(保存着reg地址),让rx_buf 指向一个 2字节长度的buf(读取到的值将会保存在buf的第二个字节中,对于第一个字节不需要关心它的内容),那么就要设置len 为2,也就是rx_buf 的长度。
- 用两个spi_transfer 来完成,第一个spi_transfer 让tx_buf 指向一个 1字节长度的buf(保存着reg地址),len 设置为1,不关心rx_buf;随后第二个spi_transfer 让rx_buf 指向一个 1字节长度的buf(读到的数据将会保存在其中),len 设置为1,不关心tx_buf。)
spi_message
spi_message 可以理解为一个消息:
一次动作可能会需要传输多个spi_transfer(比如上述第二种读reg 的情况),为了管理这些spi_transfer,定义了一个spi_message 结构体。
(详细的传输过程参考下面的 SPI传输原理和 spi_sync 解析章节)
spi_driver
与其它驱动一样,spi 从设备驱动也要按照分离原则设计,spi_driver 就是用来描述一个spi 从设备驱动。
//include\linux\spi\spi.h
spi 从设备驱动使用spi 总线驱动模型来管理,当spi_device 与spi_driver 匹配时就会调用spi_driver->probe 来执行从设备的驱动代码。
设备树解读
参考内核设备树绑定文档:Documentation\devicetree\bindings\spi\spi-bus.txt
spi 控制器节点有以下 4个必须的属性:
(#address-cells、#size-cells 这两个属性一般是用来描述子节点需要用几个cells 来描述一段地址,#address-cells 表示起始地址、#size-cells 表示长度。但是在spi 控制器节点中它们有不同的定义)
#address-cells:描述子节点需要用几个cells 来定义片选 (一般情况等于1)。(用子节点的reg属性来定义使用哪个片选)
#size-cells:必须为0。
compatible:描述与该设备兼容的驱动。
其它比较重要的可选属性:
cs-gpios:描述被用作片选的gpio 引脚。(可能会有一个或多个gpio引脚)
如下图GPIO1_IO00 表示第一个片选的gpio引脚,GPIO1_IO01 表示第二个片选的gpio引脚,以此类推。
num-cs:片选引脚总数
spi 从设备的设备树属性如下:
spi 从设备节点必须是spi 主设备节点的子节点。
必要属性:
- reg:用来指示该从设备使用第几个片选引脚。
- compatible:指示与该设备兼容的驱动。
- spi-max-frequency:表示从设备所能支持的最大时钟频率。
可选属性:
- spi-cpol:表示时钟的极性(空闲时是高电平还是低电平)。它是一个空属性(没有值,spi-cpol; 即可不用写= 多少),这个属性存在时表示cpol=1,不存在时表示cpol=0;
- spi-cpha:表示时钟相位(数据线在时钟第一个跳变沿采集数据,还是第二个跳变沿采集数据)。同上;
- spi-cs-high:大多数从设备片选都是低电平备选中,有一些特殊的芯片相反是片选高电平选中。当存在spi-cs-high 属性时表示该设备为片选高电平选中。
- spi-3wire:这是一个空属性(没有值),表示使用SPI 三线模式
- spi-lsb-first:这是一个空属性(没有值),表示使用SPI传输数据时先传输最低位(LSB)
- spi-tx-bus-width:表示有几条MOSI引脚;没有这个属性时默认只有1条MOSI引脚
- spi-rx-bus-width:表示有几条MISO引脚;没有这个属性时默认只有1条MISO引脚
- spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久
- spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久
IMX6ULL SPI 设备树节点实例:
spi_device 成员解释:
我们知道spi_device 用来描述一个spi从设备,里面包含从设备的硬件信息,硬件信息有用设备树来描述,那么设备中的属性对应spi_device 中的哪些成员。
master: 表示从设备挂在哪个控制器(哪条spi 总线)下。
max_speed_hz: 最大的时钟速率,来自spi-max-frequency属性。
chip_select: 表示使用第几个片选信号线,来自reg属性。
bits_per_word: 来自应用层传递的参数,表示每次传输至少需要几位。8/12/16 bit等等。
cs-gpio:保存着从设备片选对应的gpio编号,设备树节点中cs-gpios 属性会被解析成cs_gpios[] 数组,可以用cs_gpios[spi_device。chip_select] 来获取它。
mode 相当于flag,u16 中每一个bit 都有特殊的意义:
SPI_CPHA:在第1个周期采样,在第2个周期采样?
SPI_CPOL:平时时钟极性
SPI_CPHA和SPI_CPOL组合起来就可以得到4种模式
SPI_MODE_0:平时SCK为低(SPI_CPOL为0),在第1个周期采样(SPI_CPHA为0)
SPI_MODE_1:平时SCK为低(SPI_CPOL为0),在第2个周期采样(SPI_CPHA为1)
SPI_MODE_2:平时SCK为高(SPI_CPOL为1),在第1个周期采样(SPI_CPHA为0)
SPI_MODE_3:平时SCK为高(SPI_CPOL为1),在第2个周期采样(SPI_CPHA为1)
SPI_CS_HIGH:一般来说片选引脚时低电平有效,SPI_CS_HIGH表示高电平有效
SPI_LSB_FIRST:一般来说先传输MSB(最高位),SPI_LSB_FIRST表示先传LSB(最低位);很多SPI控制器并不支持SPI_LSB_FIRST
SPI_3WIRE:三线模式,SO、SI共用一条线
SPI_LOOP:回环模式,就是SO、SI连接在一起
SPI_NO_CS:只有一个SPI设备,没有片选信号,也不需要片选信号
SPI_READY:SPI从设备可以拉低信号,表示暂停、表示未就绪
SPI_TX_DUAL:发送数据时有2条信号线
SPI_TX_QUAD:发送数据时有4条信号线
SPI_RX_DUAL:接收数据时有2条信号线
SPI_RX_QUAD:接收数据时有4条信号线
spi 驱动框架
spi 的驱动框架分为控制器驱动 和从设备驱动。
spi 控制器驱动使用"platform 驱动模型" 来管理,在设备树中会有如下图的dtb节点来描述一个spi 控制器,由内核解析成一个platform_device,在spi控制器驱动代码中会注册一个platform_driver,当platform_device 与platform_driver 匹配时就会调用platform_driver->probe。
在probe 函数中,会根据设备树的硬件信息 初始化spi控制器,并构建一个spi_master,调用spi_register_master
来注册一个spi_mster,在注册过程中会再此解析设备树,遍历spi 控制器节点下的所有子节点,把它们转化为一个个spi_device,注册spi_device。
spi 从设备驱动由spi 总线驱动模型来管理,spi_device 在控制器驱动中已经被解析并注册好了,在spi 从设备驱动代码中会构建一个spi_driver 并注册,在注册过程中会查找系统中所有的spi_deivce 进行比较,当匹配成功就调用spi_dirver->probe(在probe中实现从设备的功能)。
(没有设备树的版本中,设备信息使用.c 文件(结构体) 来描述)
spi 总线驱动模型
在driver/spi/spi.c 的spi_init() 函数中调用bus_register()
注册了spi_bus_type
。
spi 总线驱动模型与 platform类似,都是基于设备-总线-驱动 模型来设计的(匹配原理和过程参考 platform总线)。
在spi 总线驱动模型中 需要调用spi_add_device
来注册spi_device,调用spi_register_driver
来注册spi_driver;
spi_register_driver 中设置spi_driver->bus = spi_bus_type,设置spi_driver->device_driver->probe = spi_drv_probe,然后调用driver_register 注册spi_driver->device_driver。
在spi_add_device 中,最后会调用device_add 向内核注册spi_device->dev (struct device)
device 与device_driver 的注册过程就是开始走入设备驱动模型那一套匹配流程。
注册device 时会将device添加到bus_type->p->klist_devices链表,然后遍历bus_type->p->klist_drivers 链表,与链表中的每一个device_driver 一一比较,比较的方法就是调用bus_type->match。
注册device_driver 时会将device_driver添加到bus_type->p->klist_drivers链表,然后遍历bus_type->p->klist_devices 链表,与链表中的每一个device 比较,调用bus_type->match 进行匹配。
详细的匹配过程参考:设备-总线-驱动模型
如果匹配则调用device_driver->probe(因为没有提供bus_type->probe,所以会直接调用device_driver->probe),而在spi_register_driver 函数中device_driver->probe 被设为了spi_drv_probe,在spi_drv_probe 里最终会调用spi_driver->probe。
简单的看一下spi_bus_type->match——spi_match_device()
spi 从设备驱动
spi从设备驱动框架
一个spi 从设备驱动主要分为两部分(遵从硬件信息、驱动代码分离原则):硬件信息(spi_device)和驱动代码(spi_driver)。
(在3.x 以后的内核版本spi_device 使用设备树来描述)
所以编写一个从设备驱动有以下两个步骤:
- 首先,你需要在设备树spi控制器下添加spi 从设备的子节点。(在spi控制器驱动注册spi_master 时会扫描控制器节点,将每一个子节点创建成一个spi_device)
- 其次,你需要编写spi从设备驱动,构建一个spi_driver 并注册它。当spi_device 与spi_driver 匹配时就会调用spi_driver->probe 执行任何你想要做的事,比如调用内核封装的spi 通信函数读写从设备,创建字符设备等等。(spi_device 与spi_driver 匹配过程参考前面的spi 总线驱动模型)
spi 从设备框架示例代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
static int example_probe(struct spi_device *spi)
{
return 0;
}
static int example_remove(struct spi_device *spi)
{
return 0;
}
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "example,spi_drv" },
{},
};
static struct spi_driver example_spi_driver = {
.driver = {
.name = "example_spi_drv",
.of_match_table = of_match_ptr(example_dt_ids),
},
.probe = example_probe,
.remove = example_remove,
};
static int __init example_init(void)
{
return spi_register_driver(&example_spi_driver);
}
static void __exit example_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
关于spi 核心层提供的一些spi 通信函数
spi 核心层主要封装了2 个spi 通信的函数:spi_sync 和spi_async
,定义于driver/spi/spi.c。int spi_sync(struct spi_device *spi, struct spi_message *message)
int spi_async(struct spi_device *spi, struct spi_message *message)
它们俩的区别是spi_sync 为同步传输函数,在其中会等待传输完成(可能会睡眠),返回时已经传输完成了(失败或者成功);
spi_async 为异步传输函数,返回时并不代表spi 传输结束,可能还在传输中。
(spi_sync 与spi_async 的使用可以参考前面的spidev 驱动解析 read 章节)
spi_sync
与 spi_async
使用示例:
我们可以参考内核封装的spi_read 与spi_write 代码,对于读数据需要填充rx_buf,对于写数据需要填充tx_buf。
spi_read、spi_write是最基本的使用方法,根据我们的需要也可以构建多个spi_transfer 结构体添加到spi_message 链表中,最后调用spi_sync 传输(根据需求自由发挥)。
spi_message_init 与spi_message_add_tail 代码解析
spi 核心层还在spi_sync 和spi_async 的基础上封装了读写函数,更方便驱动编写(省去 struct spi_transfer 与struct spi_message相关的操作)。
定义于include/spi/spi.h
读取n 个字节static inline int spi_read(struct spi_device *spi, void *buf, size_t len)
写n个字节static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)
先写后读n个字节int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx);
应用层读写spi 设备驱动:spidev
在i2c 子系统有自带的drivers/i2c/i2c-dev.c 驱动为应用层创建了/dev/i2c-0、/dev/i2c-1 等等的设备节点,注册了字符设备;我们可以直接使用文件IO 访问设备节点来读写i2c 从设备。
那么在i2c 子系统中有没有类似的驱动呢,有——drivers/spi/spidev.c
字符设备相关的内容参考
spidev 实际上也是一个spi从设备驱动,相比于普通的从设备驱动,
它多注册了字符设备,将SPI IO函数(spi_sync) 封装在file_operations的read、write、ioctl 函数中,这样我们可以在应用层通过文件IO 读写spi数据。
spidev 注册流程
先来简单的看一下spidev.c 中的内容:
从驱动的入口为spidev_init开始,在spidev_init 中它首先调用了register_chardev
申请了SPIDEV_MAJOR 这个主设备号下的0~255 个次设备号,并且使用这个256个次设备号注册了同一个cdev(这说明这256个设备号都使用同一个cdev、file_operations)。
然后创建了一个类。
按照以往的经验来说,它还会为每个设备号创建一个struct device 并注册它(创建/dev/ 目录下的设备节点),但是这里却没有——而是注册了spi_driver。
这是一个spi 从设备驱动,只有存在与其匹配的spi_device 是才会调用spi_driver->probe;先不管从设备从何而来,我们先看看probe 函数中会做什么。
spi_driver 的实例为spidev_spi_driver,它的probe函数是spidev_probe。
假设有一个spi 从设备spi_device 与这个spi_driver匹配,调用了spi_driver->probe,传入的参数就是匹配的这个spi_device;
在probe 中首先创建了一个spidev_data 结构体,然后把spi_device 记录到spidev_data->spi 上,初始化了spidev_data->device_entry 链表节点。
(在spidev.c 中spidev_data 是非常重要的数据结构,每一个spidev_data 都对应着一个spi_device ,只有当spi_device 与spidev_spi_driver 匹配后,才会为它创建。
在spidev_data 中描述了它对应着哪个从设备、设备号等等。)
在spidev_init 中我们申请了主设备号SPIDEV_MAJOR 下的0~255 个次设备号,在这里它获取0-N_SPI_MINORS 范围内第一个空闲的次设备号,然后赋值到spidev_data->devt。
调用device_create
根据设备号devt、class 创建一个struct device
,并向内核注册。
(创建/dev/设备节点,class 就是spidev_init 中所创建的类
名字是spidev.B.D:B 表示这个从设备挂在那一条总线(控制器)下面、D代表它是spi控制器下第n个设备(该设备使用的是控制器的第n个片选引脚) )。
将spidev_data->device_entry 节点添加到一个全局的链表device_list,之后在调用file_operations->open函数时就可以凭借设备号从链表中找出对应的spidev_data。
(在Linux中每一个文件都有唯一的inode
结构体来描述,对于设备文件来说在inode->i_rdev 中记录着设备节点的设备号)
这下我们就清楚了,spidev.c 注册过程中会申请多个设备号并注册字符设备cdev,创建一个类,还会为每个与spidev_spi_driver 匹配的spi_device 创建一个设备节点。
要知道每个spi_device 就代表一个spi 从设备,有了cdev(里面包含file_operations),又有/dev/ 目录下的设备节点,我们不就可以使用文件IO 来读写spi从设备了嘛(按照i2c_dev.c 驱动的经验在file_operations 的read、write、ioctl 函数中肯定会调用spi 的读写函数来访问spi 从设备)。
那么到底什么样的从设备可以使用spidev 这个驱动来访问?问题的关键就是spi_device 如何与这个spi_drver 匹配。
我们知道spi_device 是在注册spi_master 的过程中遍历spi master设备树节点的所有子节点生成的,那么如何匹配——就是用compatible 来匹配。
查看spidev_spi_driver(struct spi_driver) 的匹配条件,compatible 为"rohm,dh2228fv" ,所以我们只需要在控制器节点下创建一个从设备节点,compatible 值写为"rohm,dh2228fv" 就可以。
以imx6ull 为例,在设备树中添加如下节点后生成/dev/spidev2.0 节点。
既然/dev/spidev2.0 是字符设备那么我们就可以用open、read、write… 去访问它,先看看底层的file_operations 中的open、read、write…是怎么写的。
spidev open流程
当应用层调用open打开/dev/spidevB.D 设备节点时,会调用到spidev_open
。
spidev_open 中根据inode->i_rdev 记录的设备号在device_list 链表中找到spidev_data,然后为其申请tx_buffer、rx_buffer 的内存空间,并将它设置为file 的私有数据。这样在后面的read、write 等函数中都可以通过私有数据获取到spidev_data。
spidev read流程
当应用层调用read 读取/dev/spidevB.D 设备节点时,会调用到spidev_read
。
在spidev_read 函数中从file 私有数据获取到spidev_data,调用spidev_sync_read 读取spi从设备数据,读到的数据会保存到spidev_data->rx_buffer,最后将数据拷贝到应用层buf,返回读取到的数据长度(字节)。
spidev_sync_read:
spi 传输需要构建一个spi_transfer 结构体用来承载发送和接收的数据,然后将spi_transfer 添加到spi_message 中,调用spidev_sync传输spi_message。(spidev_sync_read 与spidev_sync 都是spidev.c 定义,并非核心层)
(spi_transfer 和spi_message 是用来描述数据的,都很关键)
spidev_sync:
spidev_sync 实质是调用spi_async (由spi核心层 driver/spi/spi.c 定义)传输数据:int spi_async(struct spi_device *spi, struct spi_message *message)
spi_device:指明要向哪个从设备传输数据;
spi_messge:用来描述需要传输的数据;
spidev write流程
write 的流程和read 是差不多的,只不过读方向换成了写方向,调用spidev_sync_write 来发送数据
spidev ioctl流程
ioctl 的内容分为三部分:读取当前spi 设备通信的模式、设置spi 设备通信的模式、向spi 从设备传输数据。
前面的内容和read、write类似,获取spidev_data、spi_device。
如果应用层传递的cmd 是下列的值,那么就是读取spi 从设备的传输模式,__put_user 会把spi->mode 的值会放入到arg,然后返回应用层。
如果cmd 的值是下列的值,那么就是设置spi 的从设备传输模式。
__get_user 会把arg 的值拷贝到tmp中,调用spi_setup 设置spi 从设备模式。
最后默认的cmd 值进行传输数据,arg 中保存的数据是struct spi_ioc_transfer(类似于spi_transfer),调用spidev_message 传输数据,n_ioc 是spi_ioc_transfer 结构体的个数。
(ioc 也是spi_ioc_transfer 类型,spidev_get_ioc_message 只不过是将数据从应用层的spi_ioc_transfer 拷贝到内核层的spi_ioc_transfer)
(spidev_message 最终调用核心层提供的spidev_sync
传输数据)
spidev close流程
close 对应的是file_operations 中的release,即spidev_release。
在spidev_release 中就是做open的反向操作:清除file 的私有数据、释放申请的tx_buffer、rx_buffer 的内存
spi 应用层读写示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
int fd;
/*
关于读寄存器,可以使用两个transfer(第一个写reg地址,第二个接收数据;参考read_reg_byte),也可以只使用一个transfer(参考read_reg_byte1)
int read_reg_byte1(unsigned char reg,unsigned char *pvalue)
{
struct spi_ioc_transfer xfer;
char rxbuf[2] = {0};
int status;
memset(&xfer, 0,sizeof(xfer));
xfer.tx_buf = (unsigned long)®
xfer.len = 2;
xfer.rx_buf = (unsigned long)rxbuf;
status = ioctl(fd, SPI_IOC_MESSAGE(1), &xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return -1;
}
*pvalue = rxbuf[1];
return 0;
}
*/
/*读取寄存器1个字节,成功返回0,失败返回负数
* */
int read_reg_byte(unsigned char reg,unsigned char *pvalue)
{
struct spi_ioc_transfer xfer[2];
unsigned char filler = 0xff;
int status;
memset(xfer, 0,sizeof(xfer));
xfer[0].tx_buf = (unsigned long)®
xfer[0].len = 1;
xfer[1].rx_buf = (unsigned long)pvalue;
xfer[1].len = 1;
status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return -1;
}
return 0;
}
/*写入寄存器1个字节,成功返回0,失败返回负数
* */
int write_reg_byte(unsigned char reg,unsigned char value)
{
struct spi_ioc_transfer xfer;
unsigned char buf[2] = {0};
int status;
buf[0] = reg;
buf[1] = value;
memset(&xfer, 0,sizeof(xfer));
xfer.tx_buf = (unsigned long)buf;
xfer.len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), &xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return -1;
}
return 0;
}
/* usege:
* read:./spi_reg /dev/spidevB.D reg
* write:/spi_reg /dev/spidevB.D reg value
*
* 读写长度默认是1字节*/
int main(int argc,char** argv)
{
unsigned char reg = 0,value = 0;
int ret;
if(argc == 3)
{
reg = (unsigned char)strtoul(argv[2],NULL,0);
}else if(argc == 4)
{
reg = (unsigned char)strtoul(argv[2],NULL,0);
value = (unsigned char)strtoul(argv[3],NULL,0);
}else{
printf("usege:\n read:./spi_reg /dev/spidevB.D reg\n write:/spi_reg /dev/spidevB.D reg value\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
/*
*本示例用于读写icm20608芯片,该芯片reg地址只有7bit,最高位为读写位(读1,写0)
* */
if(argc == 3)
{
ret = read_reg_byte(reg | 0x80,&value);
if(ret)
return -1;
printf("read value 0x%x\n",value);
}else if(argc == 4)
{
ret = write_reg_byte(reg & (~0x80),value);
if(ret)
return -1;
}
close(fd);
return 0;
}
SPI传输原理
使用SPI传输时,最小的传输单位是"spi_transfer",
对于一个设备,可以发起多个spi_transfer,
这些spi_transfer,会放入一个spi_message里,
属于同一spi_master 的spi_message 会放入一个queue 队列里。
-
spi_transfer:指定tx_buf、rx_buf、len
-
一次动作的一个或多个spi_transfer,使用spi_message来管理(添加到spi_message->transfers 链表里):
-
同一个SPI Master下的spi_message,放在一个队列里(spi_master->queue):
所以,反过来,SPI传输的流程是这样的: -
从spi_master的队列里取出每一个spi_message
- 从spi_message的队列里取出一个spi_transfer
- 处理spi_transfer
- 从spi_message的队列里取出一个spi_transfer
spi_sync 传输的过程就是根据以上原理来编写的。
spi_sync 解析
spi_sync 是spi 核心层提供的spi 数据传输函数,它只是一些纯软件代码,最终传输数据还是需要调用spi 控制器驱动提供的传输函数。
对于spi 控制器驱动来说,编写方法有两种(一种老方法,一种新方法),因此由于spi 控制器驱动的编写方法不同spi_sync 的处理方式也会不同。
那么接下来就来看看这两种方法。
老方法:
int spi_sync(struct spi_device *spi, struct spi_message *message)
spi_sync 传递的参数有spi_device 和spi_message;spi_device 表示向哪个从设备传输,spi_message 包含有需要传输的数据。
第一步,spi_sync 会通过spi_device 得到spi_master 也就是spi的控制器,调用spi_master->transfer 来传输数据:
在spi_master->transfer 函数中,会调用list_add_tail 将要传输的数据spi_message 放入spi_master->queue 队列尾部,然后执行schedule_work() 调度工作队列启动传输(注意:这里只是启动传输,接下去transfer 会立马返回,然后进入休眠等待spi_message传输完成)
第二步,work_struct->func 被调用:
它需要取出 spi_master->queue 队列里的每一个spi_message,对每一个spi_message 进行传输。(被取出的spi_message 从master->queue 中删除)
一个spi_message 中可能包含有多个spi_transfer,所以对于一个message 的处理,需要遍历message->queue 链表里的每一个spi_transfer,处理每一个spi_transfer。
处理一个spi_transfer:取出tx_buffer、rx_buffer 开始发送、接收数据。
发送:将tx_buffer 数据一个字节一个字节放入发送数据寄存器(txFIFO)。当txFIFO 满时就停下来等待,等到txFIFO 中的数据被发出去,就会产生硬件中断,在中断处理函数中唤醒休眠,继续放入数据,如此反复,直到tx_buffer 中所有的数据被发送完成。
接收:发送的过程中,spi 总线上的数据同时也会被放入rxFIFO,从rxFIFO 中将数据读入rx_buffer。
当一个spi_message 中所有的spi_transfer 都被处理完了,那么就代表这个message 处理好了,需要调用msg->complete(msg->context) 来唤醒spi_sync 中的休眠线程。
(spi_message->context 是一个struct completion 类型的,每一个msg对应的completion 都是唯一的,调用msg->complete(msg->context) 时只会唤醒传输当前的msg 那个线程)
(对于老方法:spi_master->transfer函数和work_struct->func 都是由spi master驱动提供的(work_struct 需要在probe中提前初始化好))
代码解析
在__spi_sync 中会设置spi_message->complete(当传输完成时会调用complete唤醒休眠的线程),然后调用spi_async_locked 开始传输数据(这里只是调度工作队列开始传输流程,并没有真正的传输),从spi_async_locked 返回后,如果数据没有传输完成,该线程就会进入休眠。
调用__spi_async
调用spi_master->transfer,该函数是由spi 控制器驱动编写的。
使用老方法的spi 控制器驱动并不多,参考 drivers/spi/spi-sh.c。
找到spi-sh.c 中的spi_master->transfer 函数——spi_sh_transfer
在spi_sh_transfer 中将spi_message 添加到spi_master->queue 链表里,然后调度工作线程(工作线程会执行work_struct->func 我们提供的工作函数)。
在spi_sh_probe 中初始化了ss->ws(struct work_struct) 和ss->workqueue,ss->ws的工作函数为spi_sh_work。
在spi_sh_work 函数中依次取出spi_master->queue 链表中的spi_message,对每个spi_message 遍历message->transfers 链表中的每一个spi_transfer 进行传输,调用spi_sh_send 发送数据或调用spi_sh_receive 接收数据,当一个spi_message 中所有的spi_transfer 被发送完成后就会调用spi_message->complete 唤醒在__spi_sync 中被休眠的线程。
在spi_sh_send 中执行真正的硬件发送工作:
首先计算出当前txFIFO 中剩余的空间cur_len,先将spi_transfer->tx_buffer 中cur_len 长度的数据一个字节一个字节的写入到发送数据寄存器。然后判断是否发送完所有的数据,如果还有数据没被发送,那么就进入休眠等待,等到上一次被放入txfifo的数据发送完成就会长生中断,在中断中唤醒休眠的发送线程,接着发送剩余的数据。
如果当前的spi_transfer 是spi_message 中的最后一个transfer,那么就需要等待最后一组数据发送完成才能返回。
spi_sh_receive 接收函数
新方法5.4:
**pump: ** 译为抽出、注入等意思。这里理解成将msg从master->queue 队列中抽出,然后传输msg。
新方法相比老方法在框架上复杂了很多,理解更难,但是对编写控制器驱动来说会方便一些。
对于老方法来说,将msg 放入队列、再从队列取出msg、再取出msg中的每一个spi_transfer、处理spi_transfer 这些操作都是由控制器驱动编写的spi_master->transfer 和工作函数来完成的。
对于新方法,__spi_queued_transfer 函数会负责将msg 添加到master->queue 队列;
__spi_pump_messages函数,它有两种情况:
它先判断spi_master->cur_msg是否为空,空那么代表当前spi_master 没有在处理任何msg,那么它就会从队列抽出第一个msg,然后处理msg。
如果spi_master->cur_msg 不为空,那么就直接返回,并等待。(它已经把msg 放入到队列里,自然会有work func 来处理msg)
在处理msg前多了几步准备工作:执行master->prepare_transfer_hardware(它是由spi核心层提供的);另外如果你的驱动提供了master->prepare_message 那么就会执行prepare_message。
执行spi_map_msg。
然后在__spi_pump_messages 函数中会调用master->transfer_one_message (它代表了处理一个spi_message,它是由内核提供的)。
怎么处理一个msg呢?处理msg 中的每一个spi_transfer。
传输spi_transfer 前,对于同属一个spi_message的spi_transfer,它们肯定是发向同一个spi_device 的,所以在这里拉下片选。
然后开始遍历spi_transfer,传输每一个spi_transfer。
为了保证每个spi_transfer 都能被同步的传输,在传输spi_transfer的前后要加上reinit_complete(&master->xfer_completion) 和wait_for_completion_timeout(&master->xfer_completion,timeout)(等待),一个spi_transfer 传输完成后要调用spi_finalize_current_transfer(master) 来提示完成一个spi_transfer 传输,spi_finalize_current_transfer 中会调用complete(&master->xfer_completion) 唤醒等待。
那么怎么传输一个spi_transfer 呢?调用master->transfer_one(它代表处理一个spi_transfer,它也是由内核提供的)。
在transfer_one 中:
调用bitbang->setup_transfer(它是传输前的硬件设置,由spi master 驱动提供,也可不提供不执行)
调用bitbang->txrx_bufs 来传输spi_transfer 中的数据。(这是真正的数据发送、接收函数,由spi master 驱动提供)
最后调用spi_finalize_current_transfer 来结束一个spi_transfer 传输。
如此循环的执行红色方框中的内容,直到所有的spi_transfer 都被传输完毕,代表一个spi_message 处理完毕,此时拉起片选。
一个spi_message 传输完毕则要调用spi_finalize_current_message 提示spi_message 传输完毕。
在spi_finalize_current_message 中需要做准备的反向工作:
spi_unmap_msg(master,mesg);
master->unprepare_message(master,mesg);
kthread_queue_work(&master->kworker,&master->pump_messages); //待研究
msg->complete(msg->context); //唤醒__spi_sync 中的等待
(在bitbang->txrx_bufs 传输数据的过程中,还需要添加额外的等待、唤醒函数(由中断唤醒))
代码解析:
新方法调用__spi_queued_transfer 将spi_message 添加到spi_master->queue 后立即返回__spi_sync
__spi_sync 调用__spi_pump_messages 开始处理spi_message。
__spi_pump_messages 的处理状态有两种:
1、如果spi_master->cur_msg 不为空,表示已经有一个msg在处理,那么就会直接返回,等待msg处理完成。(msg 已经被添加到队列中,spi_master->kworker 线程会从队列中一一取出msg,并处理它们)
2、如果spi_master->cur_msg == NULL,那么代表当前没有msg 正在处理,那么就会取出队列的第一个msg 处理它。(处理完第一个msg后还会启动spi_master->kworker 线程,让它负责处理剩余的msg。)
从队列中取出第一个msg 赋值到master->cur_msg,并将这个msg 从队列移除。
__spi_pump_messages 传输msg 前做准备工作:
ctlr->prepare_transfer_hardware
ctlr->prepare_message
spi_map_msg
最后调用ctlr->transfer_one_message 传输一个msg,即master->cur_msg。
在kernel 5.4版本中ctlr->transfer_one_message 被设置为了spi_transfer_one_message
spi_bitbang_start //注册spi_bitbang
->spi_register_master //注册spi_master
->spi_controller_initialize_queue
查看spi_transfer_one_message
函数,它的目的就是传输一个spi_message。
所谓的传输msg,就是传输msg中的每一个spi_transfer,在一个msg里的transfer 毫无疑问都是发给同一个spi_device 的,所以在开始传输transfer前设置片选。
遍历spi_message->transfers 链表中的所有spi_transfer,遍历过程中调用ctlr->transfer_one 来传输一个spi_transfer。(在kernel5.4 中ctlr->transfer_one 被设置为spi_bitbang_transfer_one,它是内核提供的)
在ctlr->transfer_one 后需要等待transfer 传输完成。
每当一个transfer传输完成就需要统计以处理的长度 msg->actual_length 加上当前transfer的数据长度。
当msg中的所有spi_transfer 被传输完后,取消片选、设置msg->status = 0、调用spi_finalize_current_message 结束一个spi_message 传输流程。
查看spi_bitbang_transfer_one
如何传输一个spi_transfer:
在传输spi_transfer 前,你可以做一些准备工作bitbang->setup_transfer(由spi_master驱动提供),比如在spi-imx.c 中它会根据spi_transfer 的位数长度、主从模式等设置发送、接收函数。
接着调用bitbang->txrx_bufs 传输spi_transfer。(真正的数据发送、接收函数,是由spi_master驱动提供)
最后调用spi_finalize_current_transfer 结束一个spi_transfer 传输。
bitbang->setup_transfer
以spi-imx.c 为例bitbang->txrx_bufs 是spi_imx_transfer
spi_transfer数据已经在txrx_bufs 函数中传输完成,查看spi_finalize_current_transfer
结束一个spi_transfer 传输需要哪些收尾工作:
这不就是提示spi_transfer 完成的函数(执行过complete后spi_transfer_one_message 中的等待函数便不会等待,返回后直接走过。)
spi_transfer_one_message
就这样不停的遍历spi_transfer,不停的调用ctlr->transfer_one 传输spi_transfer,所有的transfer 都遍历完了,一个spi_message 的数据就处理完了。
看看spi_finalize_current_message
有哪些收尾工作:
spi_register_master 注册spi_master 时master->pump_messages 的work 函数被设置为spi_pump_message
linux 5.4 内核spi_sync 传输流程解析完毕。
遗留问题:队列中的其它msg 交由worker线程执行__spi_pump_messages 是如何处理的?
涉及到completion 等待、__spi_pump_messages 处理流程等问题。
新方法4.1.15
在4.x 与5.x 的内核中spi_sync 这块略有不同:
对于5.x 的内核它提供了master->transfer_one_message
(spi_transfer_one_message
) 表示传输一个spi_message,在spi_transfer_one_message
中会遍历一个spi_message中的所有spi_transfer 反复调用master->transfer_one
(spi_bitbang_transfer_one
) 表示传输一个spi_transfer,在spi_bitbang_transfer_one
中会调用spi_bitbang->txrx_bufs 来真正的传输一个spi_transfer。
而对于4.x 的内核master->transfer_one_message
被设置为spi_bitbang_transfer_one
,在spi_bitbang_transfer_one
中会遍历一个spi_message 中的所有 spi_transfer,然后直接调用spi_bitbang->txrx_bufs 来传输spi_transfer。
以上只是内核的框架代码变了,对于驱动编写来说还是一样的,最终实现还是调用spi_bitbang.chipselect、bitbang.setup_transfer、bitbang.txrx_bufs 这几个函数,我们只要提供它们就好了。
4.1.15 master->transfer_one_message (spi_bitbang_transfer_one) 代码解析
spi控制器驱动解析
(以imx6ull spi 控制器驱动为例,它使用的是新版spi 驱动框架)
在spi 控制器驱动中,我们主要的任务就是构建spi_master、spi_bitbang、填充它们,最后注册它们,这一过程主要在probe 函数中进行。
在spi_bitbang 中最重要的成员:为了实现spi 数据的传输,我们需要实现片选设置回调函数(bitbang.chipselect)、传输设置回调函数(bitbang.setup_transfer)、数据发送接收回调函数(bitbang.txrx_bufs)等等。
下面以spi-imx 驱动为例,看看具体是怎么做的:
spi_imx_probe
spi_imx_chipselect 片选设置函数
spi_imx_transfer spi数据传输函数
spi_master 注册流程
我们将struct spi_master
理解成为一个spi 控制器,在控制器驱动probe 函数中需要构建、设置、注册一个spi_master。调用spi_register_master
注册。
对于新版本的spi 控制器驱动,spi_master 被包含在struct spi_bitbang
内,所以我们需要构建、设置spi_master 之外,还需要构建、设置、注册一个spi_bitbang。调用spi_bitbang_start
注册spi_bitbang,其中会调用spi_register_master 注册master,所以probe 中无需注册master。
(代码参考Linux-5.4.47)
spi_bitbang_start
接下来就分析spi_bitbang_start
函数的调用过程:
在spi_bitbang_init 中设置了master的几个重要的回调函数:
master->prepare_transfer_hardware
master->unprepare_transfer_hardware
master->transfer_one
master->set_cs
它们在spi_sync 中都有调用到。
接下来真正的spi_master 注册流程就开始了:spi_register_master
在5.4 内核中,它将spi_master 改名为spi_controller,所以注册函数也改名为spi_register_controller。#define spi_register_master(_ctlr) spi_register_controller(_ctlr)
spi_register_controller
在spi_register_controller 中首先会设置spi_master->num_bus。
有两种方式可以获取master->bus_num:
- 通过设备树的别名获取,bus_num 2 就是通过设备树别名获得。(使用这种方式要在probe中设置master->bus_num = -1)
- 通过idr_alloc 获取,这种方式是利用(1<<15)-1 = 32767的方式来计算的,会根据系统中的总线依次减小32766、32765…,下图中的32766 就是用这种方式得出的。
初始化spi_master 的链表、锁、completion等等。master->xfer_completion 是用来保证spi_transfer 同步传输。(参考spi_sync 解析)
获取设备树cs-gpios 属性中描述的片选引脚gpio 编号,保存到spi_master->cs_gpios。(它是一个int* 型指针指向一个数组,数组的每一项保存着每个片选引脚gpio 编号)
调用device_add
注册spi_master->dev (struct device),注册这个device 应该是在spi_mater 文件夹下生成spi2 文件夹。
设置spi_master->transfer 和spi_master->transfer_one_message (代表传输一个spi_message,参考spi_sync)。
如果你在驱动中设置了spi_master->transfer 那么你就是使用的老方法,不会走入else 分支。
spi_master->transfer_one_message 被设置为了spi_transfer_one_message 。
内核为了管理这些spi_master,建立了一个全局链表spi_controller_list,注册spi_master 的时候就会把它添加到链表中。
重点of_register_spi_devices ,它会遍历master->dev.of_node 所有子节点,并按照每个子节点生成spi_device(描述一个spi从设备)。
对于这种检索子节点,然后生成从设备的形式,其它总线也有类似的,比如i2c、mdio 等等,我们可以把这种套路记忆下来,对类似代码比较容易理解。
在of_register_spi_device
中会根据of_node 创建一个spi_device,解析设备树的各个属性值,填充到spi_device 中,最终调用spi_add_device 注册spi_device。
spi_add_device 这里就开始走入总线、设备、驱动模型了,它会调用device_add 添加spi_device->dev,然后与bus_type 下的driver 进行匹配,匹配后调用driver->probe 或bus_type->probe。(详细参考spi总线驱动模型)spi_alloc_device
of_modalias_node
of_spi_parse_dt
关于片选,我们在控制的时候就可以通过spi_master->cs_gpios[spi_device->chip_select] 来获取从设备对应的片选gpio编号。spi_add_device
注册一个spi_device,注册过程参考前面的spi总线驱动模型。
spi 控制器虚拟驱动编写
参照老方法编写一个虚拟的spi 控制器驱动
在设备树中创建一个虚拟的spi master 节点,同时包含一个spi_device 节点 (compatible设置为spidev.c 的值)。
编写一个虚拟的spi master驱动代码,注册一个platform_driver,在probe中 注册spi_master。
内核启动后spi master设备树节点会被转化成一个platform_device。
当我们加载驱动时,如果platform_driver与platform_device 匹配就会调用platform_driver->probe 注册spi_master,master注册过程中创建子节点对应的spi_device。
spi_device 与spidev.c 驱动(spi_driver) 匹配,就会注册字符设备、创建/dev/spidev.B.D 设备节点,这样我们就可以使用应用程序来控制发送和接收。
效果如下:
文章来源:https://www.toymoban.com/news/detail-498738.html
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#define DRIVER_NAME "virt_spi"
static struct spi_master *master;
static struct work_struct* ws;
/*
* 在work 函数中需要:
* 取出spi_master->queue 链表中的每一个spi_message,处理spi_message。
*
* 对于处理一个spi_message 需要:
* 遍历spi_message->queue 链表中的每一个spi_transfer,处理spi_transfer.
*
* 处理一个spi_transfer:
* 将spi_transfer->tx_buffer数据一个个字节放入发送数据寄存器(txFIFO)。
* (当txFIFO满,来不及发送时需要休眠等待,
* txfifo中的数据被发送出去后会产生中断,由中断唤醒work中的等待线程)
* 并且将接收数据寄存器中的数据读到rx_buffer。
*
* 当处理完一个spi_message 之后要调用mesg->complete(mesg->context) 唤醒spi_sync 中的线程。
* work 函数一直处理,直到master->queue 中所有的mesg被处理完。
*
* */
static void spi_virt_work(struct work_struct *work)
{
struct spi_message *mesg;
struct spi_transfer *t;
while(!list_empty(&master->queue)) //判断链表是否不为空
{
//获取master->queue 链表中第一个节点对应的spi_message
mesg = list_entry(master->queue.next,struct spi_message,queue);
list_del_init(&mesg->queue); //将mesg 节点从master->queue 链表中删除
list_for_each_entry(t,&mesg->transfers,transfer_list) //遍历每一个spi_transfer
{
//发送接收数据略过(对于具体硬件的发送、接收过程中还会涉及到等待、唤醒等操作)
//tx 将数据放入txfifo 然后等待发送完成,发送完成后产生中断,在中断处理函数中唤醒等待,继续发送...
//rx ...
if (t->tx_buf) {
printk("%s tx\n",__func__);
}
if (t->rx_buf) {
printk("%s rx\n",__func__);
}
mesg->actual_length += t->len; //已处理的长度++
}
//处理完成一个spi_message
mesg->status = 0;
mesg->complete(mesg->context);
}
}
int virt_spi_transfer(struct spi_device *spi,struct spi_message *mesg)
{
mesg->actual_length = 0; //actual_length 表示已处理的数据长度
mesg->status = -EINPROGRESS; //-EINPROGRESS 表示正在处理中
list_add_tail(&mesg->queue,&spi->master->queue);
schedule_work(ws);
return 0;
}
/*在probe 函数中需要创建、设置、注册一个spi_master
*
* */
static int virt_spi_probe(struct platform_device *pdev)
{
int ret;
ws = devm_kzalloc(&pdev->dev,sizeof(struct work_struct),GFP_KERNEL);
//创建spi_master
//spi_alloc_master 第二个参数为申请一段私有数据的内存,我们不需要
master = spi_alloc_master(&pdev->dev,0);
if (master == NULL) {
dev_err(&pdev->dev, "spi_alloc_master error.\n");
return -ENOMEM;
}
//根据spi_sync 分析,老方法需要提供spi_master->transfer 函数,传输数据
master->transfer = virt_spi_transfer;
//注册spi_master 过程中,需要扫描spi 控制器节点下的所有子节点,转化为spi_device。
//所以需要将spi_master->dev.of_node 设置为pdev->dev.of_node。
//如果不设置spi_master->dev.of_node,那么将不会创建spi_device。(不会报错!!!)
master->dev.of_node = pdev->dev.of_node;
INIT_WORK(ws, spi_virt_work); //初始化work_struct->func 工作函数
INIT_LIST_HEAD(&master->queue);
ret = spi_register_master(master);
if (ret < 0) {
printk(KERN_ERR "spi_register_master error.\n");
spi_master_put(master);
return ret;
}
return 0;
}
/*
* 在remove 中需要注销spi_master
* */
static int virt_spi_remove(struct platform_device *pdev)
{
spi_unregister_master(master);
return 0;
}
static const struct platform_device_id virt_spi_devtype[] = {
{ .name = DRIVER_NAME, },
{ /* sentinel */}
};
static const struct of_device_id virt_spi_dt_ids[] = {
{ .compatible = DRIVER_NAME, },
{ /* sentinel */ }
};
static struct platform_driver virt_spi_platform_driver = {
.probe = virt_spi_probe,
.remove = virt_spi_remove,
.id_table = virt_spi_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = virt_spi_dt_ids,
},
};
static int __init virt_spi_init(void)
{
return platform_driver_register(&virt_spi_platform_driver);
}
static void __exit virt_spi_exit(void)
{
platform_driver_unregister(&virt_spi_platform_driver);
}
module_init(virt_spi_init);
module_exit(virt_spi_exit);
MODULE_LICENSE("GPL");
参照新方法编写一个虚拟的spi 控制器驱动
与老方法不同的是,在新方法probe 中需要构建、注册一个spi_bitbang,注册spi_bitbang 时会注册spi_master。
设置bitbang 时比较重要的成员:
spi_bitbang.master
bitbang.chipselect = spi_virt_chipselect; //片选设置函数(必须)
bitbang.txrx_bufs = spi_virt_transfer; //硬件传输函数(必须)
bitbang.setup_transfer //传输前的硬件设置函数(根据硬件需要选择是否提供)文章来源地址https://www.toymoban.com/news/detail-498738.html
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#define DRIVER_NAME "virt_spi"
struct virt_spi{
struct spi_bitbang bitbang;
struct completion xfer_done; //确保一个spi_transfer 传输完成
};
static struct spi_master *master;
static struct virt_spi *virt_spi;
//spi_bitbang->txrx_bufs 数据传输函数
static int spi_virt_transfer(struct spi_device *spi,struct spi_transfer *transfer)
{
reinit_completion(&virt_spi->xfer_done);
//假装发送
printk("%s \n",__func__);
//唤醒completion,对于真实的硬件驱动,唤醒工作应该由中断来做,为了简单直接在这里执行完成
complete(&virt_spi->xfer_done);
wait_for_completion(&virt_spi->xfer_done); //等待从completion 完成
return transfer->len;
}
//spi_bitbang->chipselect 片选设置函数
static void spi_virt_chipselect(struct spi_device *spi, int is_active)
{
}
/*在probe 函数中需要:
* 创建、设置、注册一个spi_master
* 创建、设置、注册一个spi_bitbang
*
* */
static int virt_spi_probe(struct platform_device *pdev)
{
int ret;
//创建spi_master、spi_bitbang
//创建spi_master + 私有数据,私有数据中包含了spi_bitbang
master = spi_alloc_master(&pdev->dev,sizeof(struct virt_spi));
if (master == NULL) {
dev_err(&pdev->dev, "spi_alloc_master error.\n");
return -ENOMEM;
}
virt_spi = spi_master_get_devdata(master); //获取spi_master私有数据,master[1]
virt_spi->bitbang.master = master;
virt_spi->bitbang.chipselect = spi_virt_chipselect; //片选设置函数
virt_spi->bitbang.txrx_bufs = spi_virt_transfer; //传输函数
init_completion(&virt_spi->xfer_done); //completion 使用前,先要初始化它
//注册spi_master 过程中,需要扫描spi 控制器节点下的所有子节点,转化为spi_device。
//所以需要将spi_master->dev.of_node 设置为pdev->dev.of_node
master->dev.of_node = pdev->dev.of_node;
//注册spi_bitbang 时会调用spi_register_master 注册spi_master
ret = spi_bitbang_start(&virt_spi->bitbang);
if (ret) {
dev_err(&pdev->dev, "bitbang start failed with %d\n", ret);
}
return 0;
}
/*
* 在remove 中需要注销spi_bitbang、释放spi_master
* 注销spi_bitbang 时会注销spi_master
* */
static int virt_spi_remove(struct platform_device *pdev)
{
spi_bitbang_stop(&virt_spi->bitbang);
spi_master_put(master);
return 0;
}
static const struct platform_device_id virt_spi_devtype[] = {
{ .name = DRIVER_NAME, },
{ /* sentinel */}
};
static const struct of_device_id virt_spi_dt_ids[] = {
{ .compatible = DRIVER_NAME, },
{ /* sentinel */ }
};
static struct platform_driver virt_spi_platform_driver = {
.probe = virt_spi_probe,
.remove = virt_spi_remove,
.id_table = virt_spi_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = virt_spi_dt_ids,
},
};
static int __init virt_spi_init(void)
{
return platform_driver_register(&virt_spi_platform_driver);
}
static void __exit virt_spi_exit(void)
{
platform_driver_unregister(&virt_spi_platform_driver);
}
module_init(virt_spi_init);
module_exit(virt_spi_exit);
MODULE_LICENSE("GPL");
到了这里,关于spi 子系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!