在前面几篇文章我们分析了SPI驱动框架模型、SPI总线、SPI MASTER、SPI DEVICE、SPI通信接口等内容,本篇是SPI总线的最后一篇,主要介绍SPI通用字符设备。
spi通用字符设备说明
在SPI驱动模块提供了通用字符设备驱动以及注册接口,我们知道I2C模块的通用字符设备也提
供了通用字符设备,在分析SPI通用字符设备模型之前,我们先来说明下I2C通用字符设备模型与SPI通用字符设备模型的区别,以便我们对SPI通用字符设备模型有一个感性的认识。
- i2c通用字符设备是与i2c adapter所关联(通过该字符设备可访问该控制器下所有挂载的设备);
- 针对每一个i2c adapter,待其注册/注销到i2c总线上时,则借助通知链实现对应字符设备的创建,此为i2c模块已完成的操作,针对每一个注册的i2c adapter,均会为其进行;
- spi通用字符设备代表的是一个spi 设备(该字符设备包含了对应spi设备的片选引脚以及所绑定的spi master);
- spi通用字符设备文件并不会随着spi master或者spi device的注册/注销而创建或消除,该spi通用字符设备需使用者创建特定的spi device方可以实现通用字符设备的创建。
- spi模块为通用字符设备模型注册了一个spi driver,而spi驱动的名称为“spidev”,其针对设备树节点的匹配名称为“rohm,dh2228fv”,spi模块借助该spi driver、名为“spidev”或
以上便是spi通用字符设备与i2c通用字符设备的区别,简单点说就是:spi通用字符设备需自行
创建,且与挂载至spi控制器上的spi设备绑定。
为Spi 通用字符设备创建而注册的spi driver
spi模块为通用字符设备创建的spi driver如下,该驱动实现的功能如下:
- 该驱动可匹配的spi设备有两种:
- spi_device->modalias的值为“spidev”
- 针对支持设备树的内核,设备节点的compatible为“rohm,dh2228fv”的spi设备
- 在spidev_probe接口中,根据传递的spi device参数,完成对应spi通用字符设备的注册,字符设备名称为“"spidevX.Y"”(其中X为spi总线号,Y为spi设备对应的片选序号)
- 在spidev_remove接口中,完成字符设备的注销操作。
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "spidev",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
spi通用字符设备驱动创建流程
SPI 模块提供的通用字符设备是与具体的spi设备向关联的,即我们必须创建一个设备名称或者设备节点名称固定的spi device(spi 设备名称为“spidev”或设备节点名称为“rohm,dh2228fv”),然后与spi驱动模块注册的spi 驱动匹配,方才触发spi 通用字符设备的创建。我们下面来分析下spi通用字符设备的创建流程。
其实spi通用字符设备与我们自己在spi驱动中实现字符设备的流程基本上是一致的,区别是我们会在spi驱动的probe接口进行一些spi设备的初始化操作,而spi通用字符设备创建所对应的spi驱动的probe仅用来创建字符设备。如下图所示,为我们自己创建spi 字符设备的创建流程,将这个流程图中的红色虚线框中的内容去除,即为spi通用字符设备驱动的创建流程。
spi通用字符设备的操作接口
在接口spidev_init中,完成了主设备号为SPIDEV_MAJOR,次设备号为0的spi字符设备的创建,该字符设备文件对应的处理接口为spidev_fops,其定义如下:
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.write = spidev_write,
.read = spidev_read,
.unlocked_ioctl = spidev_ioctl,
.compat_ioctl = spidev_compat_ioctl,
.open = spidev_open,
.release = spidev_release,
.llseek = no_llseek,
};
spidev_open
spidev_open接口主要用于判断所要打开的spi通用字符设备文件,并根据字符设备文件的设备号在device_list链表上查找对应的spidev_data类型的变量,并将其绑定至文件描述的私有变量指针上。(当进行通用字符设备文件节点的创建前,通过注册spidevice并与spidev_spi_driver绑定时,通过调用spidev_probe时,将该spidevice对应的spidev_data类型的变量添加到device_list链表上,同时通过调用device_create接口完成device类型变量的创建,同时向应用层发送uevent,由应用层的udev或mdev完成字符设备节点的创建(通过mknod系统调用))。
如下是struct spidev_data类型的变量,该变量实现了spi通用字符设备节点对应的文件描述符与spi模块的关联:
- 通过spi_device类型的变量,完成了与spi device的关联,从而也完成了与对应spi master的绑定;
- dev_t类型的变量说明了该spi通用字符设备节点的设备号;
- buffer为进行spi通信的buffer。
struct spidev_data {
dev_t devt;
spinlock_t spi_lock;
struct spi_device *spi;
struct list_head device_entry;
/* buffer is NULL unless this device is open (users > 0) */
struct mutex buf_lock;
unsigned users;
u8 *buffer;
};
spidev_read、spidev_write
这两个接口主要通过调用spi通信接口spi_async,完成与具体的spidevice的通信(同步通信)。当spidev_read、spidev_write接口调用spi_async时,借助完成量接口,完成了同步通信(调用wait_for_completion使本进程进入sleep状态,待spi-core完成通信后,则通过执行spidev_complete将该进程唤醒,从而完成了同步通信操作)。
spidev_ioctl、spidev_compat_ioctl
这两个接口主要进行通信速率、通信模式等设置操作,包括SPI_IOC_WR_MODE、SPI_IOC_WR_LSB_FIRST、SPI_IOC_WR_BITS_PER_WORD、SPI_IOC_WR_MAX_SPEED_HZ等接口,也支持与spi device的读写通信等。
以上为spi模块通用字符设备模型相关的内容,个人认为该模型被低估了,该模型为我们实现了字符设备模型接口,而且支持读、写、ioctl等功能,若我们需要编写驱动的spi device,仅仅用于读写状态等信息,并不需要进行特殊的初始化,可以完全使用该通用字符设备模型,而不需要重新编写驱动,只需要注册对应的spidev即可,