【IMX6ULL学习笔记】十七、总线驱动框架-Platform、IIC、SPI等
一、总线
Linux 总线驱动模型主要可以分为三个部分:总线、设备、驱动。Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。Linux内核在启动时会向系统注册总线,比如 IIC总线、SPI总线、SDIO总线、Platform总线等。总线是与硬件平台无关的,由Linux系统提供并进行注册,无需用户关心。总线的职责就是将驱动与设备进行匹配,Linux系统内核使用 bus_type 结构体表示总线,此结构体定义在文件include/linux/device.h,bus_type 结构体内容如下:
struct bus_type {
const char *name; /* 总线名字 */
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups; /* 总线属性 */
const struct attribute_group **dev_groups; /* 设备属性 */
const struct attribute_group **drv_groups; /* 驱动属性 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
第 10 行:match 函数,此函数很重要,此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。
Linux 还提供了两个 API 用于总线的注册与注销(由Linux完成,用户无需关心),定义在 Drive/base/bus.c中,如下:
bus_register(struct bus_type *bus);
bus_unregister(struct bus_type *bus);
参数:对应总线结构体变量
各种总线如IIC、SPI、SDIO总线等都是 bus_type 的一个实例。
内核初始化会先调用 platform_bus_init() 初始化 platform_bus 总线,之后由 _initcall 的优先级顺序可知先初始化各种实际总线例如 spi、i2c。之后注册 platform_device 到 platform_bus 上,然后注册 platform_driver 到platform_bus。
在 linux 引进设备树之前设备信息都存放在内核 arch 目录下,引进设备树以后设备信息由设备树描述(此举为了删除linux内核大量和平台设备相关的代码)内核初始化会扫描设备树文件将设备信息保存到设备链表,提供链表的头指针,在注册设备时调用of_xxx(操作设备树链表的api)来获得设备信息。
注册 platform_device 会将设备绑定到 platform_bus,之后会遍历 platform_bus 上是否有与之匹配的驱动程序(调用 platform_bus_type 的 match 函数进行匹配),如果存在,匹配成功调用驱动的probe对设备进行初始化的配置。
注册 platform_driver 到 platform_bus 会遍历 platform_bus 上是否有与之匹配的设备(调用 platform_bus_type 的 match 函数进行匹配),如果存在,匹配成功调用驱动的 probe 对设备进行初始化的配置。如果平台设备本身是实际的总线例如spi、i2c等,此时在驱动程序中会注册总线设备到到相应的总线上,之后会遍历xxx_bus上是否与之匹配的驱动程序,如果有会执行驱动的 probe 进行初始化相关配置。
下面以 SPI 总线驱动为例子做一个详细讲解:
①、SPI 驱动分为 SPI 控制器驱动、SPI 设备驱动。SPI 控制器挂在 Linux 的虚拟总线 Platform Bus 上,包括 SPI 控制器设备和 SPI 控制器设备的驱动,在内核代码里分别由结构体 platform_device 和 platform_driver 表示,内核初始化时先注册 platform_device(SPI 控制器设备,定义在设备树 DTS 中的 SPI 控制器设备节点)到 platform bus,此时会遍历总线上是否存在匹配的 SPI 控制器驱动程序(根据兼容性、设备id、设备名),匹配成功会调用控制器驱动的 probe 对控制器进行相关的配置。
②、之后会注册 SPI 控制器的驱动程序(定义在内核源码中,由 SOC 产商编写)到 platform bus 上,此时会遍历总线上是否存在匹配的 SPI 控制器设备(根据兼容性、设备id、设备名),匹配成功会调用控制器驱动的 probe 对控制器进行相关的配置。在 SPI 控制器驱动的 probe 函数中会注册 spi device(设备树 DTS 中定义在 SPI 控制器设备节点下的 SPI 设备)到 spi bus(spi bus此时已经初始化),此时会遍历 spi 总线上是否有与之匹配的设备驱动程序,如果有,调用 spi 设备驱动的 probe 函数对 spi device 进行配置。spi 设备驱动(由用户编写,如IIC OLED、Sensor IIC等驱动)注册到 spi bus 时也会同样去匹配是否有匹配的设备。
扩展:
Linux 内核的 sysfs 子系统用来管理 Linux 设备驱动模型,参考下图:
1、I2C 总线
I2C 总线结构体变量为 i2c_bus_type,该结构体变量是 bus_type 结构体的一个实例,IIC 设备和驱动的匹配以及注册都是由 I2C 总线完成的,IIC总线结构体和IIC驱动设备注册 API 都定义在 drivers/i2c/i2c-core.c 文件中,i2c_bus_type 内容如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
.match 就是 I2C 总线的设备和驱动匹配函数,也就是 i2c_device_match 这个函数,此函数内容如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
第 11 行:of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C 设备和驱动匹配。
第 15 行:acpi_driver_match_device 函数用于 ACPI 形式的匹配。
第 21 行:i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C 设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。
IIC设备驱动的注册与注销 API 也定义在 drivers/i2c/i2c-core.c 文件中,API 函数如下:
1、i2c_adapter 注册/注销函数(主机驱动API)
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2、i2c_driver 注册/注销函数(设备驱动API)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
结论:
i2c-core.c 定义了 IIC 总线结构体变量,并调用 bus.c 提供的总线注册与注销API,将 IIC 总线注册到系统中。同时,向下提供了 IIC 主机驱动和设备驱动的注册、注销相关 API。
2、SPI 总线
再如 SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 I2C 等驱动一样,SPI 总线为 spi_bus_type,定义在 drivers/spi/spi.c 中,内容如下:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
可以看出,SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
spi_match_device 函数和 i2c_match_device 函数的对于设备和驱动的匹配过程基本一样。
第 7 行:of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。
第 11 行:acpi_driver_match_device 函数用于 ACPI 形式的匹配。
第 15 行:spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI 设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。
第 17 行:比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。
SPI总线提供SPI驱动的注册与注销相关API,也定义在 drivers/spi/spi.c 中。
1、主机驱动API
SPI 主机驱动的核心是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册 spi_master,主机驱动注册与注销相关 API 如下:
①、spi_master 申请与释放
spi_alloc_master 函数用于申请 spi_master,函数原型如下:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
函数参数和返回值含义如下:
dev:设备,一般是 platform_device 中的 dev 成员变量。
size:私有数据大小,可通过 spi_master_get_devdata 函数获取这些私有数据。
返回值:申请到的 spi_master。
spi_master 的释放通过 spi_master_put 函数来完成,当删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master,spi_master_put 函数如下:
void spi_master_put(struct spi_master *master)
函数参数和返回值含义如下:
master:要释放的 spi_master。
返回值:无。
②、spi_master 的注册与注销
当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为 spi_register_master,函数原型如下:
int spi_register_master(struct spi_master *master)
函数参数和返回值含义如下:
master:要注册的 spi_master。
返回值:0,成功;负值,失败。
I.MX6U 的 SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册,spi_bitbang_start 函数内部其实也是通过调用 spi_register_master 函数来完成 spi_master 的注册。
如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:
void spi_unregister_master(struct spi_master *master)
函数参数和返回值含义如下:
master:要注销的 spi_master。
返回值:无。
如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉 spi_master。
2、设备驱动API
spi_driver 初始化完成以后需要向 Linux 内核注册,spi_driver 注册函数为 spi_register_driver,函数原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
sdrv:要注册的 spi_driver。
返回值:0,注册成功;赋值,注册失败。
注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函
数完成 spi_driver 的注销,函数原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
sdrv:要注销的 spi_driver。
返回值:无。
结论:
spi.c 定义了 spi 总线结构体变量,并调用 bus.c 提供的总线注册与注销API,将 spi 总线注册到系统中。同时,向下提供了 spi 主机驱动和设备驱动的注册、注销相关 API。
3、platform 总线
在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型,因此 Linux 提出了 platform 这个虚拟总线。platform 总线也是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c 文件中,如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。=platform_match 函数定义在文件 drivers/base/platform.c 中,函
数内容如下所示:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/*When driver_override is set,only bind to the matching driver*/
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
驱动和设备的匹配有四种方法,我们依次来看一下:
第 11~12 行:第一种匹配方式,OF 类型的匹配,也就是设备树采用的匹配方式,
of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数
就会执行。
第 15~16 行:第二种匹配方式,ACPI 匹配方式。
第 19~20 行:第三种匹配方式,id_table 匹配,每个 platform_driver 结构体有一个 id_table 成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
第 23 行:第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段。
platform 总线也提供了 platform 驱动注册与注销相关 API 函数,调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数原型如下所示:
int platform_driver_register (struct platform_driver *driver)
函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。
通过 platform_driver_unregister 函数卸载 platform 驱动,
platform_driver_unregister 函数原型如下:
void platform_driver_unregister(struct platform_driver *drv)
函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值:无。
结论:
platform.c 定义了 platform 总线结构体变量,并调用 bus.c 提供的总线注册与注销API,将 platform 总线注册到系统中。同时,向下提供了 platform 驱动的注册、注销相关 API。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界