SPI控制器(spi_master)、SPI设备、SPI总线(spi_device与spi_driver的匹配机制)、SPI万能驱动`spidev.c`

前言说明

如果前面对Plartform总线和I2C总线有详细认真的学习的话,那么理解SPI总线那就很容易了,所以可以先去回顾一下Plartform总线和I2C总线。

关于Platform总线的原理和结构,详情见下面三篇博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/145023181
https://blog.youkuaiyun.com/wenhao_ir/article/details/145018442
https://blog.youkuaiyun.com/wenhao_ir/article/details/145030037

关于I2C总线的原理和结构,详情见下面这篇博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/146405656

视频和讲义资料可以看一看

关于Linux的SPI总线的原理和结构,视频里已经讲得比较清楚了,百度网盘搜索“1-3_03_SPI总线设备驱动模型”,然后从头开始看。当然视频刚开始花了5分钟左右来回顾Plartform总线,如果赶时间的话,可以跳过,直接从第5分钟10秒开始看对SPI总线的介绍。
可以配套下面这个MD文档来观看视频:
https://pan.baidu.com/s/15Zu_9f2YnNjJix3Wd48uJw?pwd=j9f5

看完视频和讲义资料后,大致的关键点提取如下文所述。

SPI控制器与SPI设备之间的关系结构

下面幅图可以单独开个窗口查看。
在这里插入图片描述
从上面这幅图我们可以看出,SPI系统在硬件上分为两部分,一部是SPI控制器,另一部分是SPI设备。
SPI控制器的驱动通过Plartform总线实现、SPI设备的驱动通过SPI总线实现。
一个SPI控制器对应于一个spi_master的结构体的实例、一个SPI设备对应于一个spi_device的结构体。
在这里插入图片描述
设备文件的节点举例如下:
在这里插入图片描述
上面的截图是关于SPI子系统的设备文件的节点举例,在截图的代码中,spi3是一个SPI控制器(spi_master)、它下面有一个名叫gpio_spi@0的SPI设备。
值得说明的一点是:SPI设备节点的解析工作是由SPI控制器(spi_master)驱动的probe函数来完成的。

关于SPI控制器的驱动

SPI控制器的驱动程序走得是Plartform总线,提供SPI的底层传输能力,Linux内核中通过spi_master结构体来描述一个SPI控制器。
来源:include\linux\spi\spi.h
在这里插入图片描述
截图中用红框圈中的transfer成员函数是最关键的函数,在它里面实现了SPI控制器对数据的收发操作。

SPI控制器的probe函数有解析设备树中SPI控制器下的子节点的作用

由上一个目录知道,SPI控制器走的是Plartform总线,在它的probe函数里除了生成spi_master的相关设备树文件,它还要解析SPI控制器下的子节点,比如博文https://blog.youkuaiyun.com/wenhao_ir/article/details/146455604 我在&ecspi1下写的子节点spidac: spidac@0就由它来进行解析。

SPI子系统的接口函数和基本原理

当SPI控制器的驱动有了并注册进SPI子系统后,SPI子系统就能发挥其作用了,它提供了一系列便于操作SPI总线的接口函数,SPI设备的驱动程序就可以利用这些接口函数实现,关于Linux的SPI子系统的原理和相关接口函数的介绍,请参考博文
https://blog.youkuaiyun.com/wenhao_ir/article/details/146551375

SPI总线介绍

SPI总线的结构图

下面这幅图就不用多说什么了,如果了解了Plartform总线和I2C总线,那下面这幅图就很好理解了。
在这里插入图片描述

spi_device结构体

spi_device结构体的截图如下:
来源:include\linux\spi\spi.h
在这里插入图片描述
spi_device结构体可以来自设备树、也可以来自C文件,对应于博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/146417363 对“i2c_client的实现和生成”的方式二和方式三。

spi_driver结构体

来源:include\linux\spi\spi.h
在这里插入图片描述

SPI总线的match函数分析(匹配机制分析)

SPI总线的match函数会赋值给结构体实例spi_bus_type,如下图所示:
\Linux-4.9.88\drivers\spi\spi.c
在这里插入图片描述
然后我们进入函数spi_match_device来进行分析:
在这里插入图片描述
spi_match_device() 按照以下顺序尝试匹配 SPI 设备和驱动:

第1优先级的匹配——设备树(Device Tree, DT)匹配

if (of_driver_match_device(dev, drv))
    return 1;
  • 适用于 ARM 及其他基于设备树的平台(如 IMX6ULL)。
  • 如果设备树 (.dts) 中的 compatible 字符串 与驱动的 of_match_table 匹配,则匹配成功。

示例
驱动代码:

static const struct of_device_id my_spi_dt_ids[] = {
    { .compatible = "myvendor,myspi" },
    { }
};
MODULE_DEVICE_TABLE(of, my_spi_dt_ids);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
        .of_match_table = my_spi_dt_ids,  // 设备树匹配表
    },
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

注意:在定义了my_spi_dt_ids后,要用代码MODULE_DEVICE_TABLE(of, my_spi_dt_ids);my_spi_dt_ids 导出到内核的设备表,这样才能真正的进行设备树匹配,否则my_spi_dt_ids 就只是my_spi_dt_ids ,起不到进行设备树匹配的作用。

设备树 .dts

&ecspi1 {
    my_spi_device@0 {
        compatible = "myvendor,myspi";  // 匹配 of_match_table
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
};

如果 compatible 匹配 of_match_table,匹配成功,返回 1


第2优先级的匹配——ACPI 设备匹配

if (acpi_driver_match_device(dev, drv))
    return 1;
  • 适用于 x86 及支持 ACPI 的 ARM64 设备
  • ACPI 匹配表 acpi_match_table 用于 匹配 ACPI DSDT 表中的 _HID(Hardware ID)

示例
驱动代码:

static const struct acpi_device_id my_spi_acpi_ids[] = {
    { "MYSP1000", 0 },  // ACPI 设备 ID
    { }
};
MODULE_DEVICE_TABLE(acpi, my_spi_acpi_ids);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
        .acpi_match_table = ACPI_PTR(my_spi_acpi_ids),
    },
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

注意:在定义了my_spi_acpi_ids后,要用代码MODULE_DEVICE_TABLE(acpi, my_spi_acpi_ids);my_spi_acpi_ids 导出到内核的设备表,这样才能真正的进行ACPI 匹配,否则my_spi_acpi_ids 就只是my_spi_acpi_ids ,起不到进行ACPI 匹配的作用。

ACPI DSDT 表:

Device (SPI1)
{
    Name (_HID, "MYSP1000")  // 硬件 ID
}

如果 _HID 匹配 acpi_match_table,匹配成功,返回 1


第3优先级的匹配—— id_table 设备 ID 匹配

if (sdrv->id_table)
    return !!spi_match_id(sdrv->id_table, spi);
  • 适用于 传统的 SPI 设备驱动,主要用于 没有设备树或 ACPI 的情况。
  • id_table 包含 驱动支持的 SPI 设备列表,通常用于 手动注册的 SPI 设备

其中spi_match_id函数的代码如下:
\Linux-4.9.88\drivers\spi\spi.c

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
						const struct spi_device *sdev)
{
	while (id->name[0]) {
		if (!strcmp(sdev->modalias, id->name))
			return id;
		id++;
	}
	return NULL;
}

可见是把设备描述中的 modaliasid_tablename进行比较。
spi_device结构体的定义前面已经给出过了,里面就有一项是 modalias
在这里插入图片描述

示例
驱动代码:

static const struct spi_device_id my_spi_id_table[] = {
    { "my_spi_device", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, my_spi_id_table);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
    },
    .id_table = my_spi_id_table,
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

手动注册设备:

struct spi_board_info spi_device_info = {
    .modalias = "my_spi_device",  // 匹配 id_table
    .max_speed_hz = 1000000,
    .bus_num = 1,
    .chip_select = 0,
    .mode = SPI_MODE_0,
};

如果 modalias 匹配 id_tablename,匹配成功,返回 1


第4优先级的匹配—— modalias 设备名称匹配

return strcmp(spi->modalias, drv->name) == 0;
  • 适用于 SPI 设备 ID 表为空的情况,作为最后的匹配方式。
  • 设备的 modalias 必须与驱动的 name 完全一致 才能匹配。

spi_device结构体的定义前面已经给出过了,里面就有一项是 modalias
在这里插入图片描述

示例

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
    },
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

设备:

spi_device->modalias = "my_spi_device";

如果 modalias driver.name 完全一致,匹配成功,返回 1

SPI总线 匹配机制总结

匹配方式优先级适用场景匹配机制
设备树(DT)最高ARM 设备compatible 字符串匹配 of_match_table
ACPIx86 / 部分 ARM64_HID 字符串匹配 acpi_match_table
id_table手动注册设备id_table[].name 匹配 spi->modalias
modalias最低默认匹配driver.name 必须等于 spi->modalias

万能SPI驱动spidev.c的来由

在I2C子系统中,我们有了I2C的控制器驱动后,就可以直接在应用层通过I2C控制器的驱动操作各I2C设备了,而不需要先接入I2C总线的驱动,再去使用I2C的驱动。

但SPI设备不一样,SPI设备比I2C设备要复杂,比如每个SPI设备有自己的片选控制引脚、有自己的最大时钟值、有自己的传输方式(任意时刻是只读或只写还是既读又写),所以SPI设备直接使用SPI控制器的驱动是很困难的。要想使用一个SPI设备,通常都不是直接去使用SPI控制器的驱动来操作SPI设备,而是通过SPI总线来为具体的SPI设备提供驱动。

Linux内核心中有一个特殊的SPI总线驱动,它的字名叫spidev.c,它被称为SPI的万能驱动,它其实是把SPI控制器的底层传输方法进行了轻度封装,给应用层提供常用的利用SPI控制器进行数据读写的方法。由于它的封装是轻度封装,是不针对某个SPI设备的封装,所以它被称为是万能驱动。

SPI总线的设备树文件的节点举例及解析函数

在这里插入图片描述
截图就是关于SPI子系统的设备树文件的节点举例,在截图的代码中,spi3是一个SPI控制器(spi_master)、它下面有一个名叫gpio_spi@0的SPI设备。
值得说明的一点是:SPI设备节点的解析工作是由SPI控制器(spi_master)驱动的probe函数来完成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值