Linux内核学习23--驱动总线
其实就是两个,一个虚拟总线,一个物理总线。
1 Platform虚拟总线
虚拟总线是静态加载的,就是说这些设备一开始就是集成在SOC里面的,不需要替换也没法替换,启动的时候直接把驱动挂上去就行。
一个简单的例子如下:
DTS
my_device@10000000 {
compatible = "demo,my-platform-device";
reg = <0x10000000 0x1000>;
my-value = <1234>;
};
这里的<0x10000000 0x1000>就是SoC内部IP寄存器的位置和范围,一般来说,也只有直接访问Soc寄存器的,才是Platform设备。
Driver,主要是传入的dev是platform_device。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
static int my_probe(struct platform_device *pdev)
{
u32 value;
dev_info(&pdev->dev, "probe called\n");
if (!of_property_read_u32(
pdev->dev.of_node,
"my-value",
&value)) {
dev_info(&pdev->dev,
"my-value = %d\n",
value);
}
return 0;
}
static int my_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "remove called\n");
return 0;
}
static const struct of_device_id my_match_table[] = {
{
.compatible = "demo,my-platform-device",
},
{},
};
MODULE_DEVICE_TABLE(of, my_match_table);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_platform_driver",
.of_match_table = my_match_table,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("demo");
挂载的位置是在/sys/bus/platform/。
常用的接口大概有这些。
| 类别 | 函数 | 作用 | 常用场景 |
|---|---|---|---|
| 设备注册 | platform_device_register() | 注册一个 platform_device | 手动创建设备 |
platform_device_unregister() | 注销 platform_device | 模块退出 | |
platform_device_register_simple() | 简单创建 platform_device | 测试驱动、老式代码 | |
platform_device_register_data() | 创建设备并附带私有数据 | 非DT设备 | |
| 资源获取 | platform_get_resource() | 获取设备资源(resource) | 获取寄存器地址、IRQ |
platform_get_resource_byname() | 根据名字获取资源 | 多个reg资源时 | |
| 寄存器映射 | devm_platform_ioremap_resource() | 获取并映射MMIO寄存器 | 最常用 |
devm_platform_ioremap_resource_byname() | 根据名字映射寄存器 | 多个寄存器区域 | |
| 中断获取 | platform_get_irq() | 获取第N个IRQ | DTS interrupts |
platform_get_irq_byname() | 根据名字获取IRQ | 多中断设备 | |
| DMA资源 | platform_get_resource() + IORESOURCE_DMA | 获取DMA资源 | DMA设备 |
| 私有数据管理 | platform_set_drvdata() | 保存driver私有数据 | probe保存状态 |
platform_get_drvdata() | 获取driver私有数据 | remove/ioctl使用 | |
| driver注册 | platform_driver_register() | 注册platform_driver | 模块初始化 |
platform_driver_unregister() | 注销platform_driver | 模块退出 | |
module_platform_driver() | 快速注册driver | 推荐写法 | |
| 设备模型 | pdev->dev | 获取通用device对象 | devm接口、日志、电源 |
pdev->name | 获取设备名字 | 调试 | |
pdev->id | 获取设备编号 | 多实例设备 | |
| Device Tree | pdev->dev.of_node | 获取DT节点 | 读取DTS属性 |
of_property_read_u32() | 读取整数属性 | 配置参数 | |
of_property_read_string() | 读取字符串属性 | 配置参数 | |
of_get_named_gpio() | 获取GPIO(旧接口) | GPIO配置 | |
gpiod_get() | 获取GPIO descriptor(新接口) | 推荐方式 |
不过对于现在的Linux框架来说,基本上不用自己去注册device了。
2 物理总线驱动
对于任何物理总线来说,实际上都分为两个部分。一个是控制器controller,这部分是算作platform driver,这里就不多说了。另一个部分是总线上的device,这部分这次写一下。
物理总线有很多,比如USB/PICE/I2C/SPI/UART等等,目前的流程其实是控制器controller先启动。之后controller再去挂载总线下面的设备。
这里也就是说对于设备树,首先解析最公共的部分,然后挨个加载虚拟总线platform下的设备,之后各个控制器扫描并加载自己对应的部分。
下面是一个典型的SPI设备驱动:
DTS
可以看到,节点是挂在某一个spi controller下面。首先使能该控制器,然后再描述这个节点的具体信息。
&qupv3_se0_spi {
status = "okay";
/* 我们的自定义 SPI 子设备 */
my_spi_sensor: my-spi-sensor@0 {
compatible = "demo,my-spi-sensor";
reg = <0>; /* 对应 SPI Chip Select (CS0) */
spi-max-frequency = <10000000>;/* 最大 SPI 时钟频率:10 MHz */
/* 常用可选属性 */
/* spi-cpha; */ /* 时钟相位 (Phase) */
/* spi-cpol; */ /* 时钟极性 (Polarity) */
/* 如果有中断引脚 (GPIO 中断) */
/*
interrupt-parent = <&tlmm>;
interrupts = <12 IRQ_TYPE_EDGE_RISING>;
*/
};
};
Driver,可以看到,这里传入的是spi_device *spi,这个指针在spi子系统里面已经做了初始化,并且赋值controller,
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#define DRIVER_NAME "my_spi_sensor"
/* 驱动结构体 */
struct my_sensor_data {
struct spi_device *spi;
/* 可在此添加 mutex、cdev、kfifo 等 */
};
/* 读取寄存器辅助函数 */
static int my_sensor_read_reg(struct spi_device *spi, u8 reg, u8 *val)
{
int ret;
u8 tx_buf[2] = { reg | 0x80, 0x00 }; /* 假设最高位 1 表示读操作 */
u8 rx_buf[2] = { 0 };
/* 标准同步读写传输 */
ret = spi_write_then_read(spi, &tx_buf[0], 1, rx_buf, 1);
if (ret < 0) {
dev_err(&spi->dev, "Failed to read reg 0x%02x, err: %d\n", reg, ret);
return ret;
}
*val = rx_buf[0];
return 0;
}
/* SPI 匹配成功后调用的 Probe 函数 */
static int my_sensor_probe(struct spi_device *spi)
{
struct my_sensor_data *data;
u8 chip_id = 0;
int ret;
dev_info(&spi->dev, "Probing my_spi_sensor on Qualcomm platform...\n");
/* 1. 设置 SPI 传输模式(如需要重写 DTS 配置) */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
return ret;
}
/* 2. 分配驱动私有数据内存 */
data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->spi = spi;
spi_set_drvdata(spi, data);
/* 3. 尝试与 SPI 芯片通讯(如读取 Chip ID 校验硬件是否存在) */
ret = my_sensor_read_reg(spi, 0x00, &chip_id);
if (ret == 0) {
dev_info(&spi->dev, "Read Chip ID success: 0x%02x\n", chip_id);
}
/* 在此处注册 sysfs、cdev 或 input 设备等给用户态使用 */
return 0;
}
/* 设备解绑/卸载时调用 */
static void my_sensor_remove(struct spi_device *spi)
{
dev_info(&spi->dev, "Removing my_spi_sensor driver\n");
}
/* 1. 设备树匹配表 (OF Matching Table) */
static const struct of_device_id my_sensor_of_match[] = {
{ .compatible = "demo,my-spi-sensor" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_sensor_of_match);
/* 2. SPI 驱动核心结构体 */
static struct spi_driver my_sensor_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = my_sensor_of_match,
},
.probe = my_sensor_probe,
.remove = my_sensor_remove,
};
module_spi_driver(my_sensor_driver);
MODULE_AUTHOR("Qualcomm Developer");
MODULE_DESCRIPTION("Simple SPI Sensor Driver Demo");
MODULE_LICENSE("GPL v2");
这里操作的方法就五花八门了,要看各个具体的控制器定义了。

图片来源见水印。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)