第九章 Linux DTS应用
这一章我们讨论如何在驱动代码里访问DTS参数。
9.1 DTS APIs
在 Linux 内核中,有许多 API 用于访问设备树(Device Tree, DTS)。这些 API 可以根据其功能分类为以下几类:
1). 获取设备节点
这些函数用于获取设备树中的节点。
• of_find_node_by_name: 根据节点名称查找设备节点。
• of_find_node_by_path: 根据路径查找设备节点。
• of_find_compatible_node: 根据兼容性字符串查找设备节点。
• of_find_node_with_property: 查找具有特定属性的设备节点。
2). 读取设备树属性
这些函数用于读取设备树节点的属性。
• of_property_read_u32: 读取 u32 类型的属性。
• of_property_read_u64: 读取 u64 类型的属性。
• of_property_read_string: 读取字符串类型的属性。
• of_property_read_u32_array: 读取 u32 数组类型的属性。
• of_property_read_u64_array: 读取 u64 数组类型的属性。
• of_property_read_string_array: 读取字符串数组类型的属性。
3). 处理 GPIO
这些函数用于处理设备树中的 GPIO 属性。
• of_get_named_gpio: 获取命名的 GPIO。
• of_get_gpio: 获取 GPIO。
• of_gpio_named_count: 获取命名的 GPIO 数量。
4). 处理中断
这些函数用于处理设备树中的中断属性。
• of_irq_get: 获取中断号。
• of_irq_to_resource: 将中断号转换为资源。
• of_irq_count: 获取中断数量。
5). 处理时钟
这些函数用于处理设备树中的时钟属性。
• of_clk_get: 获取时钟。
• of_clk_get_by_name: 根据名称获取时钟。
• of_clk_get_parent_name: 获取时钟的父名称。
6). 其他常用函数
这些函数用于其他常见的设备树操作。
• of_device_is_available: 检查设备节点是否可用。
• of_device_is_compatible: 检查设备节点是否兼容。
• of_property_present: 检查属性是否存在。
• of_property_count_elems_of_size: 计算属性中的元素数量。
9.2 GPIO驱动DTS设置
创建一支GPIO驱动, 首先需要添加DTS节点。如下:
gpio-export {
status="okay";
compatible = "neardi,gpio-export";
pinctrl-names = "default";
neardi,gpio-name = "gpio_export";
/**
* below gpio's name were based on neardi ld160 board, please see the declarelation of board.
*/
neardi,gpio-export = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
};
&pinctrl {
export_gpio {
CTRL_2: CTRL_2 { rockchip,pins = <1 RK_PB0 RK_FUNC_GPIO &pcfg_pull_down>; };
};
};
这里告诉kernel有一个gpio-export外设, 使用驱动gpio-export来匹配。 驱动匹配代码片段如下:
static struct platform_driver export_gpio_driver = {
.probe = export_gpio_probe,
.remove = export_gpio_remove,
.driver = {
.name = "gpio-export",
.of_match_table = of_export_gpio_match,
},
};
module_platform_driver(export_gpio_driver);
9.3 GPIO驱动
设计一个自定义的GPIO驱动, 对特定的PIN管脚进行设置。 首先创建一个gpio_export.c文件, 内容如下:
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
static int export_gpio_probe(struct platform_device *pdev) {
struct device_node *node;
int length;
int gpio_temp[10], i;
const char *name;
enum of_gpio_flags flags;
node = of_find_node_by_name(NULL, "gpio-export");
if (IS_ERR_OR_NULL(node)) {
dev_err(&pdev->dev, "%s dev node err\n", __func__);
return -ENODEV;
}
length = of_gpio_named_count(node, "neardi,gpio-export");
if (length < 0) {
dev_err(&pdev->dev, "%s gpio-export count err\n", __func__);
return -ENODEV;
}
if (length > 0 && length < 10) {
for (i = 0; i < length; i++) {
gpio_temp[i] = of_get_named_gpio_flags(node, "neardi,gpio-export", i, &flags);
if (!gpio_is_valid(gpio_temp[i])) {
dev_err(&pdev->dev, " %d gpio invalid %s err\n", gpio_temp[i], __func__);
break;
}
if (of_property_read_string_index(node, "neardi,gpio-name", i, &name)) {
dev_err(&pdev->dev, "Failed to read gpio name\n");
break;
}
devm_gpio_request_one(&pdev->dev, gpio_temp[i],
((flags & OF_GPIO_ACTIVE_LOW) ?
GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH)
| GPIOF_EXPORT, name);
// Export the GPIO to sysfs
gpio_export(gpio_temp[i], false);
}
}
return 0;
}
static int export_gpio_remove(struct platform_device *pdev) {
// Add cleanup code if necessary
return 0;
}
static const struct of_device_id of_export_gpio_match[] = {
{ .compatible = "neardi,gpio-export", },
{ },
};
MODULE_DEVICE_TABLE( of, of_export_gpio_match);
static struct platform_driver export_gpio_driver = {
.probe = export_gpio_probe,
.remove = export_gpio_remove,
.driver = {
.name = "gpio-export",
.of_match_table = of_export_gpio_match, },
};
module_platform_driver( export_gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx zhang");
MODULE_DESCRIPTION("GPIO Export Driver");
驱动初始化时, 调用of_find_node_by_name查找DTS里的gpio-export节点, 如下:
node = of_find_node_by_name(NULL, "gpio-export");
之后获取对应的属性值, 这里就不赘述。 下面这行代码是导出gpio驱动到sysfs里:
gpio_export(gpio_temp[i], false);
驱动加载成功后, 可以查看sysfs里的gpio状态, 如下:
neardi@LPA3588:~$ sudo cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-31, parent: platform/fd8a0000.gpio, gpio0:
gpio-0 ( |bt_default_wake_host) in lo
gpio-3 ( |vsel ) out lo
gpio-17 ( |vsel ) out lo
gpio-20 ( |reset ) out hi ACTIVE LOW
gpio-21 ( |bt_default_wake ) out hi
gpio-22 ( |bt_default_reset ) out hi
gpio-30 ( |vsel ) out lo
gpiochip1: GPIOs 32-63, parent: platform/fec20000.gpio, gpio1:
gpio-40 ( |gpio_export ) out lo
gpio-41 ( |vcc-mipidphy1-regula) out lo
gpio-42 ( |vcc-mipidphy0-regula) out lo
gpio-44 ( |reset ) out hi
gpio-52 ( |hp-det ) in hi ACTIVE LOW
gpio-54 ( |work1 ) out hi ACTIVE LOW
gpio-58 ( |vcc5v0_host_pwren ) out hi
gpio-61 ( |hdmirx-det ) in hi ACTIVE LOW
gpio-62 ( |enable ) out hi
gpio-63 ( |reset ) out hi
这里就看见了gpio-40, 名称: gpio_export, 作为out, 当前是低电平(lo)。
9.4 编译驱动
9.4.1 Makefile
创建一个Makefile文件, 如下:
obj-m += gpio_export.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
9.4.2 编译
在设备上, 打开命令行输入如下命令:
neardi@LPA3588:~/drivers/gpio$ make
make -C /lib/modules/5.10.110/build M=/home/neardi/drivers/gpio modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.110'
CC [M] /home/neardi/drivers/gpio/gpio_export.o
MODPOST /home/neardi/drivers/gpio/Module.symvers
CC [M] /home/neardi/drivers/gpio/gpio_export.mod.o
LD [M] /home/neardi/drivers/gpio/gpio_export.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'
编译成功, 生成了gpio_export.ko。
9.5 测试GPIO驱动
驱动加载成功, 则会生成如下目录:
neardi@LPA3588:/sys/class/gpio/gpio40$ ls -l
total 0
-rw-r--r-- 1 root root 4096 Aug 28 16:24 active_low
lrwxrwxrwx 1 root root 0 Aug 28 16:24 device -> ../../../gpiochip1
drwxr-xr-x 2 root root 0 Aug 28 16:24 power
lrwxrwxrwx 1 root root 0 Aug 28 16:06 subsystem -> ../../../../../../../class/gpio
-rw-r--r-- 1 root root 4096 Aug 28 16:06 uevent
-rw-r--r-- 1 root root 4096 Aug 28 16:24 value
查看当前的GPIO状态, 在命令行, 输入如下
neardi@LPA3588:/sys/class/gpio/gpio40$ sudo cat value
0
更改GPIO状态, 如下:
neardi@LPA3588:/sys/class/gpio/gpio40$ sudo -i
root@LPA3588:~# echo 1 > /sys/class/gpio/gpio40/value
root@LPA3588:~# cat /sys/class/gpio/gpio40/value
1