一、gpio 设备树
label:\] node-name\[@unit-address\] {
\[properties definitions\];
\[child nodes\];
};
在Linux内核驱动开发中, 没有直接的API可以获取设备树节点前面的标签(label) 。这是因为标签在设备树源文件(.dts)编译为二进制DTB文件后, 不会作为节点的独立属性保留 ,而是仅用于建立节点间的引用关系。
所以这个 \[label:\] 得不到
```cpp
/* GPIO控制器定义 - 只出现一次 */
gpio1: gpio@10010000 {
compatible = "vendor,gpio-controller";
reg = <0x10010000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
/* 多个使用该GPIO控制器的设备 - 引用关系 */
led1 {
compatible = "my-company,led";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
button1 {
compatible = "my-company,button";
gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
};
```
```cpp
- #gpio-cells = <2>; → gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; (2个参数)
- #gpio-cells = <3>; → gpios = <&gpio1 5 0 GPIO_ACTIVE_HIGH>; (3个参数)
- #gpio-cells = <1>; → gpios = <&gpio1 5>; (1个参数)
```
## 二、解析成私有数据:
```cpp
/* LED设备的私有数据结构 */
struct led_private_data {
int gpio; // GPIO编号
int active_high; // 是否高电平有效
struct timer_list timer; // 用于控制闪烁的定时器
bool state; // 当前LED状态
char name[32]; // 设备名称
};
/* 按键设备的私有数据结构 */
struct button_private_data {
int gpio; // GPIO编号
int active_low; // 是否低电平有效
int irq; // 中断号
char name[32]; // 设备名称
struct work_struct work; // 用于中断处理的工作队列
};
/* GPIO控制器的私有数据结构(驱动内部使用) */
struct gpio_controller_data {
void __iomem *base; // 寄存器基地址
int irq; // 中断号
spinlock_t lock; // 自旋锁,保护并发访问
struct gpio_chip chip; // 用于向内核注册GPIO控制器
};
static int led_probe(struct platform_device *pdev)
{
struct led_private_data *priv;
struct device_node *np = pdev->dev.of_node;
enum of_gpio_flags flags;
int ret;
/* 1. 分配私有数据内存 */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "Failed to allocate private data\n");
return -ENOMEM;
}
/* 2. 解析设备树中的GPIO信息 */
priv->gpio = of_get_named_gpio_flags(np, "gpios", 0, &flags);
if (priv->gpio < 0) {
dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
return priv->gpio;
}
/* 3. 确定GPIO有效电平 */
priv->active_high = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
/* 4. 获取设备名称 */
of_property_read_string(np, "label", &priv->name);
if (!priv->name)
snprintf(priv->name, sizeof(priv->name), "led-%d", priv->gpio);
/* 5. 申请GPIO资源 */
ret = gpio_request(priv->gpio, priv->name);
if (ret) {
dev_err(&pdev->dev, "Failed to request GPIO %d\n", priv->gpio);
return ret;
}
/* 6. 设置GPIO为输出模式 */
ret = gpio_direction_output(priv->gpio, priv->active_high ? 0 : 1);
if (ret) {
dev_err(&pdev->dev, "Failed to set GPIO direction\n");
goto err_gpio;
}
/* 7. 初始化定时器 */
setup_timer(&priv->timer, led_timer_callback, (unsigned long)priv);
priv->state = false;
/* 8. 将私有数据关联到platform_device */
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "LED driver probed successfully\n");
return 0;
err_gpio:
gpio_free(priv->gpio);
return ret;
}
static int button_probe(struct platform_device *pdev)
{
struct button_private_data *priv;
struct device_node *np = pdev->dev.of_node;
enum of_gpio_flags flags;
int ret;
/* 1. 分配私有数据内存 */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "Failed to allocate private data\n");
return -ENOMEM;
}
/* 2. 解析设备树中的GPIO信息 */
priv->gpio = of_get_named_gpio_flags(np, "gpios", 0, &flags);
if (priv->gpio < 0) {
dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
return priv->gpio;
}
/* 3. 确定有效电平 */
priv->active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0;
/* 4. 获取设备名称 */
of_property_read_string(np, "label", &priv->name);
if (!priv->name)
snprintf(priv->name, sizeof(priv->name), "button-%d", priv->gpio);
/* 5. 获取中断信息 */
priv->irq = gpio_to_irq(priv->gpio);
if (priv->irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ number\n");
return priv->irq;
}
/* 6. 初始化工作队列 */
INIT_WORK(&priv->work, button_work_handler);
/* 7. 申请GPIO资源 */
ret = gpio_request(priv->gpio, priv->name);
if (ret) {
dev_err(&pdev->dev, "Failed to request GPIO %d\n", priv->gpio);
return ret;
}
/* 8. 设置GPIO为输入模式 */
ret = gpio_direction_input(priv->gpio);
if (ret) {
dev_err(&pdev->dev, "Failed to set GPIO direction\n");
goto err_gpio;
}
/* 9. 注册中断处理函数 */
ret = request_irq(priv->irq, button_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
priv->name, priv);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", priv->irq);
goto err_gpio;
}
/* 10. 将私有数据关联到platform_device */
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "Button driver probed successfully\n");
return 0;
err_gpio:
gpio_free(priv->gpio);
return ret;
}
static int gpio_controller_probe(struct platform_device *pdev)
{
struct gpio_controller_data *gc_data;
struct device_node *np = pdev->dev.of_node;
int ret;
/* 1. 分配私有数据内存 */
gc_data = devm_kzalloc(&pdev->dev, sizeof(*gc_data), GFP_KERNEL);
if (!gc_data) {
dev_err(&pdev->dev, "Failed to allocate private data\n");
return -ENOMEM;
}
/* 2. 解析设备树中的寄存器信息 */
ret = of_address_to_resource(np, 0, &gc_data->res);
if (ret) {
dev_err(&pdev->dev, "Failed to get register address\n");
return ret;
}
/* 3. 映射寄存器地址 */
gc_data->base = devm_ioremap_resource(&pdev->dev, &gc_data->res);
if (IS_ERR(gc_data->base)) {
dev_err(&pdev->dev, "Failed to map registers\n");
return PTR_ERR(gc_data->base);
}
/* 4. 解析中断信息 */
gc_data->irq = irq_of_parse_and_map(np, 0);
if (!gc_data->irq) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return -EINVAL;
}
/* 5. 初始化自旋锁 */
spin_lock_init(&gc_data->lock);
/* 6. 设置GPIO芯片结构体 */
gc_data->chip.label = np->name;
gc_data->chip.dev = &pdev->dev;
gc_data->chip.owner = THIS_MODULE;
gc_data->chip.base = -1; /* 动态分配GPIO基址 */
gc_data->chip.ngpio = 32; /* 假设有32个GPIO */
gc_data->chip.direction_input = gc_gpio_dir_in;
gc_data->chip.direction_output = gc_gpio_dir_out;
gc_data->chip.get = gc_gpio_get;
gc_data->chip.set = gc_gpio_set;
/* 7. 注册GPIO控制器到内核 */
ret = gpiochip_add(&gc_data->chip);
if (ret) {
dev_err(&pdev->dev, "Failed to register GPIO chip\n");
return ret;
}
/* 8. 将私有数据关联到platform_device */
platform_set_drvdata(pdev, gc_data);
dev_info(&pdev->dev, "GPIO controller probed successfully\n");
return 0;
}
```
const char \*node_name = of_node_get_name(np); // 这里会得到 "led1"
```cpp
led1 {
compatible = "gpio-leds";
label = "user_led";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
- 使用 of_node_get_name(np) 会得到 "led1" (节点名称)
- 使用 of_property_read_string(np, "label", &name) 会得到 "user_led" (label属性值)
```
是的,在platform_driver驱动开发中,私有数据完全可以包含自定义的内容。这是驱动开发中非常常见且灵活的做法。
三、 platform_driver与私有数据
在Linux平台驱动模型中,私有数据的管理主要通过以下方式实现:
1. platform_device的数据 :这部分通常来自设备树解析或平台特定代码设置
2. platform_driver自定义的私有数据 :这部分是驱动开发者根据需要添加的自定义内容
## 三、多个设备共用一个led驱动的方法
````cpp
## 方法一:修改驱动代码
在驱动程序中扩展 of_match_table ,使其支持多个不同的 compatible 字符串:
```
static const struct of_device_id led_of_match[] = {
{ .compatible = "my-company,led" },
{ .compatible = "my-company,led2" }, // 添加新的compatible值
{ /* 结束标记 */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
```
这样,无论设备树中的设备使用哪个compatible字符串,只要在这个列表中定义了,就能匹配到同一个驱动。
# 驱动共用实现方法
是的,您可以通过两种方式实现让不同的设备共用同一驱动程序:
## 方法一:修改驱动代码
在驱动程序中扩展 of_match_table ,使其支持多个不同的 compatible 字符串:
```
static const struct of_device_id led_of_match[] = {
{ .compatible = "my-company,led" },
{ .compatible = "my-company,led2" }, // 添加新的compatible值
{ /* 结束标记 */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
```
这样,无论设备树中的设备使用哪个compatible字符串,只要在这个列表中定义了,就能匹配到同一个驱动。
## 方法二:修改设备树
将所有设备节点的 compatible 属性修改为相同的值:
```
led1 {
compatible = "my-company,led";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
led2 {
compatible = "my-company,led"; // 修改为与led1相同的值
gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
};
```
````
## 四、多个LED设备共用同一个驱动模块
当多个LED设备共用同一个驱动模块时,互斥锁的使用需要遵循以下原则:
### 每个设备实例应有独立的锁
需要加锁,但每个设备实例应该有自己独立的互斥锁 ,而不是所有设备共用一个全局锁。具体来说:
1. 锁应该在私有数据结构中 :
struct led_drv_priv {
struct gpio_desc *gpiod;
struct timer_list timer;
bool state;
int blink_interval;
struct mutex lock; /* 每个设备实例独立的锁 */
struct cdev cdev;
};
仿止竞争
```cpp
#include