学习Linux设备树API的最权威和最好的资源是官方文档。主要有:
与设备树相关的主要数据结构体有:
1、 struct device_node: 表示一个设备树节点,包含节点名、设备类型、兼容字符串、父节点指针、子节点列表、属性列表等信息。这是最重要的设备树数据结构。
2、struct of_device_id: 表示一个设备兼容字符串,常用于of_match_node()等函数来匹配节点。
3、 struct property: 表示一个设备树属性,包含属性名、属性数据、数据长度等信息。属性通过链表连接到设备节点上。
4、 struct of_gpio_chip: 代表一个GPIO控制器,用于GPIO的设备树绑定,将GPIO控制器和设备树节点关联起来。
5、 struct of_phandle_args: 用于表示一个phandle属性指向的节点中的多个参数(interrupts属性等)。
6、 struct of_reconfig_data: 用于描述设备的reconfigure能力,比如电源管理相关的重新配置(性能状态变更)。
7、 struct of_device: 表示从设备树节点转换来的设备,通过of_platform_device_create()或者of_device_alloc()等函数从device_node得到。然后可以通过of_device转换为普通的platform_device。
8、 struct of_dma_filter_info: 用于描述设备树节点上的dma-ranges属性,用于设置DMA区域过滤
struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct device_node *next; /* next device of same type */
struct device_node *allnext; /* next in list of all nodes */
struct proc_dir_entry *pde; /* this node's proc directory */
struct kref kref;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
struct property {
char *name;
int length;
void *value;
struct property *next;
unsigned long _flags;
unsigned int unique_id;
};
include/linux/of.h: 这是设备树API的主要头文件,包含所有的API函数原型。这些函数的具体定义在drivers/of/目录下,有address.c , base.c , device.c , irq.c ,platform.c等程序文件里
以下这些函数在/include/linux/of.h头文件中定义
/include/linux/of.h
struct device_node *of_find_node_by_path(const char *path);
struct device_node *uart_node;
uart_node = of_find_node_by_path("/soc/uart@40013000");
/include/linux/of.h
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
struct device_node *uart_node;
uart_node = of_find_node_by_name(NULL, "uart");
这会在整个设备树中查找uart节点。
而:
struct device_node *soc_node;
soc_node = of_find_node_by_path("/soc");
uart_node = of_find_node_by_name(soc_node, "uart");
这会在soc节点下查找uart节点。
所以,of_find_node_by_name()提供了一种简单的方式来根据名称查找设备树节点,同样提高了代码的易读性。
和of_find_node_by_path()一起,这两个函数提供了根据路径和名称两种最常用的方式来查找设备树节点,大大简化了设备树解析的代码。
struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);
struct device_node *pci_node;
pci_node = of_find_node_by_type(NULL, "pci");
这会在整个设备树中查找类型为pci的第一个节点。
而:
struct device_node *soc_node;
soc_node = of_find_node_by_path("/soc");
pci_node = of_find_node_by_type(soc_node, "pci");
这会在soc节点下查找类型为pci的第一个节点。
所以,of_find_node_by_type()提供了一种简单的方式来根据类型查找设备树节点,同样提高了代码的易读性。
和of_find_node_by_path()、of_find_node_by_name()一起,这三个函数提供了根据路径、名称和类型三种最常用的方式来查找设备树节点,可以覆盖大部分节点查找需求。
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);
struct device_node *spi_node;
spi_node = of_find_compatible_node(NULL, "spi", "spidev");
这会在整个设备树中查找类型为spi,且compatible属性包含"spidev"的第一个节点。
而:
struct device_node *soc_node;
soc_node = of_find_node_by_path("/soc");
spi_node = of_find_compatible_node(soc_node, "spi", "spidev");
这会在soc节点下查找该节点。
所以,of_find_compatible_node()提供了一种根据类型和兼容性查找设备树节点的方法。这在需要查找某类兼容设备的节点时非常有用,如I2C、SPI、USB等总线下的设备节点。
struct device_node *of_find_node_by_phandle(phandle handle);
spi1: spi@10010000 {
compatible = "vendor,spi";
reg = <0x10010000 0x1000>;
phandle = <1>;
};
我们可以通过phandle的值1来查找这个节点:
struct device_node *spi_node;
spi_node = of_find_node_by_phandle(1);
这会返回spi1节点的设备节点struct device_node *。
所以,of_find_node_by_phandle()给我们提供了另一种根据唯一标识来查找设备树节点的方法。
优点:
struct device_node *of_get_child_by_name(const struct device_node *node,
const char *name);
dts
soc {
serial1: serial@10010000 { ... };
serial2: serial@10020000 { ... };
};
我们可以如下查找serial1节点:
struct device_node *soc_np, *serial1_np;
soc_np = of_find_node_by_path("/soc");
serial1_np = of_get_child_by_name(soc_np, "serial1");
这会在soc节点的子节点中查找名为"serial1"的第一个节点,并返回。
所以,of_get_child_by_name()为我们提供了一种在父节点下根据名称查找子节点的方法。
struct device_node *of_find_node_with_property(
struct device_node *from, const char *prop_name);
dts
soc {
serial1: serial@10010000 {
status = "okay";
};
serial2: serial@10020000 {
status = "disabled";
};
};
我们可以如下查找第一个状态为okay的serial节点:
struct device_node *soc_np, *serial1_np;
soc_np = of_find_node_by_path("/soc");
serial1_np = of_find_node_with_property(soc_np, "status", "okay");
这会在soc节点下查找第一个具有status属性且status值为okay的节点,并返回serial1节点。
所以,of_find_node_with_property()提供了一种根据节点属性查找设备树节点的方法。这在需要查找某类具有特定属性的节点时非常有用。
struct device_node *of_get_parent(const struct device_node *node);
dts
soc {
serial1: serial@10010000 { ... };
};
我们可以如下获取serial1节点的父节点soc:
struct device_node *serial1_np, *soc_np;
serial1_np = of_find_node_by_path("/soc/serial1");
soc_np = of_get_parent(serial1_np);
这会返回serial1节点的父节点soc的设备节点struct device_node *。
所以,of_get_parent()为我们提供了一种简单获取设备树节点父节点的方法。
struct device_node *of_get_next_parent(struct device_node *node);
dts
soc {
serial1: serial@10010000 { ... };
};
我们可以如下获取serial1节点的祖父节点:
struct device_node *serial1_np, *soc_np, *parent_np;
serial1_np = of_find_node_by_path("/soc/serial1");
soc_np = of_get_parent(serial1_np); // 获取父节点soc
parent_np = of_get_next_parent(serial1_np); // 获取祖父节点
这会返回soc节点的父节点,这里为NULL,因为soc已经是最高层节点。
如果设备树更为复杂:
dts
root {
soc {
serial1: serial@10010000 { ... };
};
};
那么上述代码会返回root节点,作为serial1的祖父节点。
所以,of_get_next_parent()为我们提供了一种简单获取设备树节点的下一级父节点(即祖父节点)的方法。
与of_get_parent()相比,其主要优势是可以直接获取更高层的祖先节点,而不需多次调用。但其局限也更大,仅能获取下一级父节点,
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
of_get_next_child()是一个用来获取设备树节点的下一个子节点的函数。
其函数原型为:
c
struct device_node *of_get_next_child(const struct device_node *np,
struct device_node *child)
dts
soc {
serial1: serial@10010000 { ... };
serial2: serial@10020000 { ... };
};
我们可以如下依次获取soc节点的所有子节点:
struct device_node *soc_np, *child_np;
soc_np = of_find_node_by_path("/soc");
child_np = of_get_next_child(soc_np, NULL); // 获取第一个子节点serial1
child_np = of_get_next_child(soc_np, child_np); // 获取下一个子节点serial2
child_np = of_get_next_child(soc_np, child_np); // 获取下一个子节点,NULL
这会依次返回serial1、serial2和NULL三个设备节点。
所以,of_get_next_child()为我们提供了一种简单的获取设备树节点的下一个子节点的方法。通过循环调用,我们可以遍历某父节点下的所有子节点。
struct device_node *of_get_next_available_child(
const struct device_node *node, struct device_node *prev);
dts
soc {
serial1: serial@10010000 {
status = "okay";
};
serial2: serial@10020000 {
status = "disabled";
};
serial3: serial@10030000;
};
我们可以如下依次获取soc节点的所有可用子节点:
struct device_node *soc_np, *child_np;
soc_np = of_find_node_by_path("/soc");
child_np = of_get_next_available_child(soc_np, NULL);
// 获取第一个可用子节点serial1
child_np = of_get_next_available_child(soc_np, child_np);
// 获取下一个可用子节点serial3
child_np = of_get_next_available_child(soc_np, child_np); // 返回NULL
这会依次返回serial1和serial3两个设备节点,而跳过disabled的serial2节点。
所以,of_get_next_available_child()为我们提供了一种简单的获取设备树节点的下一个可用子节点的方法。通过循环调用,我们可以遍历某父节点下的所有可用子节点。
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
serial1: serial@10010000 {
status = "okay";
clock-frequency = <2048000>;
};
我们可以如下查找serial1节点的status属性:
struct device_node *serial1_np;
struct property *prop;
int len;
serial1_np = of_find_node_by_path("/serial1");
prop = of_find_property(serial1_np, "status", &len);
这会在serial1节点的属性中查找名为status的第一个属性,返回该属性的struct property *,同时len会被赋值为6(“okay”的长度)。
所以,of_find_property()为我们提供了一种在设备树节点中查找指定属性的方法。这对需要解析设备树节点的属性非常有用。
of_property_read_bool()是一个用来读取设备树节点的bool类型属性的函数。
其函数原型为:
int of_property_read_bool(const struct device_node *np, const char *propname)
serial1: serial@10010000 {
status = "okay";
enabled = <1>;
};
我们可以如下读取serial1节点的enabled bool属性:
struct device_node *serial1_np;
int enabled;
serial1_np = of_find_node_by_path("/serial1");
enabled = of_property_read_bool(serial1_np, "enabled");
这会在serial1节点的属性列表中查找名为enabled的bool属性,并返回其值为1,所以enabled将被赋值为1。
所以,of_property_read_bool()为我们提供了一种简单读取设备树节点bool类型属性的方法。
int of_property_read_u8_array(const struct device_node *np,
const char *propname, u8 *out_values, size_t sz);
int of_property_read_u16_array(const struct device_node *np,
const char *propname, u16 *out_values, size_t sz);
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz);
int of_property_read_u64(const struct device_node *np,
const char *propname, u64 *out_value);
serial1: serial@10010000 {
ints = <1 2 3 4>;
reg = <0x01 0x20 0x101 0x4000>;
};
我们可以如下读取serial1节点的数组属性:
u32 ints[4];
u32 reg[4];
struct device_node *serial1_np;
serial1_np = of_find_node_by_path("/serial1");
of_property_read_u32_array(serial1_np, "ints", ints, 4);
of_property_read_u32_array(serial1_np, "reg", reg, 4);
这会读取ints属性值为{1, 2, 3, 4}和reg属性值为{0x01, 0x20, 0x101, 0x4000},并存入数组ints和reg。
所以,这四个函数为我们提供了一种简单读取设备树节点u8/u16/u32/u64数组属性的方法。
int of_property_read_string(const struct device_node *np, const char *propname,
const char **out_string);
int of_property_match_string(const struct device_node *np, const char *propname,
const char *string);
int of_property_read_string_helper(const struct device_node *np,
const char *propname, char *out_string,
size_t sz, int index);
这三个函数的主要作用及区别如下:
of_property_read_string():读取np节点的propname字符串属性,并通过out_string返回其值指针。
of_property_match_string():在np节点的propname字符串属性值中匹配string,匹配成功则返回0,否则返回-EINVAL。
of_property_read_string_helper():读取np节点的propname字符串属性中索引为index的字符串,存入out_string缓存区,大小为sz。
所以,它们分别提供了读取设备树字符串属性、匹配字符串属性及读取字符串属性数组成员的方法。
例如,如果有如下设备树:
serial1: serial@10010000 {
status = "okay";
strings = "str1", "str2";
};
我们可以如下使用这三个函数:
const char *status;
const char str1[10];
of_property_read_string(serial1_np, "status", &status);
// status = "okay"
ret = of_property_match_string(serial1_np, "status", "okay");
// ret = 0
ret = of_property_read_string_helper(serial1_np, "strings",
str1, 10, 0);
// str1 = "str1", ret = 0
这会分别读取status为"okay"、匹配status值为"okay"成功及读取strings属性第0个字符串为"str1"。
所以,这三个函数为我们提供了一种简单处理设备树字符串属性的方法。
of_add_property()是一个用来向设备树节点添加属性的函数。
其函数原型为:
int of_add_property(struct device_node *np, const char *name,
const void *value, int length)
serial1: serial@10010000 {
};
我们可以如下向serial1节点添加两个属性:
struct device_node *serial1_np;
serial1_np = of_find_node_by_path("/serial1");
of_add_property(serial1_np, "status", "okay", 6);
of_add_property(serial1_np, "int", &val, 4); // val = 0x12345678
这会向serial1节点添加:
serial1: serial@10010000 {
status = "okay";
int = <0x12345678>;
};
所以,of_add_property()为我们提供了一种向已经存在的设备树节点添加属性的方法。这对需要动态生成或修改设备树非常有用。
of_remove_property()是一个用来从设备树节点删除属性的函数。
其函数原型为:
int of_remove_property(struct device_node *np, const char *name);
serial1: serial@10010000 {
status = "okay";
int = <0x12345678>;
};
我们可以如下从serial1节点删除status属性:
struct device_node *serial1_np;
serial1_np = of_find_node_by_path("/serial1");
of_remove_property(serial1_np, "status");
这会从serial1节点删除名称为status的属性。删除成功后,设备树会变为:
serial1: serial@10010000 {
int = <0x12345678>;
};
所以,of_remove_property()为我们提供了一种从已经存在的设备树节点删除属性的方法。这对需要动态生成或修改设备树非常有用。
of_update_property()是一个用来更新设备树节点属性的函数。
其函数原型为:
int of_update_property(struct device_node *np, const char *name,
const void *value, int length)
serial1: serial@10010000 {
status = "okay";
int = <0x12345678>;
};
我们可以如下更新serial1节点的status属性,并添加一个new_prop属性:
struct device_node *serial1_np;
serial1_np = of_find_node_by_path("/serial1");
of_update_property(serial1_np, "status", "working", 8);
of_update_property(serial1_np, "new_prop", "prop_val", 10);
这会更新status属性值为"working",并添加new_prop属性值为"prop_val"。更新成功后,设备树会变为:
serial1: serial@10010000 {
status = "working";
int = <0x12345678>;
new_prop = "prop_val";
};
所以,of_update_property()为我们提供了一种更新已经存在的设备树节点属性或添加新属性的方法。这对需要动态生成或修改设备树非常有用。
of_get_property()函数定义在include/linux/of.h头文件中。
函数原型为:
const void *of_get_property(const struct device_node *np, const char *name, int *lenp)
参数:
const __be32 *reg;
int len;
reg = of_get_property(np, "reg", &len);
if (reg && len > 0) {
// 处理reg属性的值
}
这个例子获取np节点的reg属性的值,如果成功获取,则reg指向属性值,len包含其字节长度。
of_get_property()函数是访问设备树属性的基本接口之一。通过这个函数,可以获取任意节点的任意属性的值和长度,这为后续解析设备树数据提供基础。
特殊地,如果一个属性显式地定义了0长度,那么这个函数仍然会返回其值,只是len的值为0。这可以用于节点中定义"标志"属性。
如果一个属性不存在,或者由于某种原因无法访问,该函数返回NULL,这需要调用者进行检查和处理。
of_device_is_compatible()是用来检查设备树节点与给定的设备兼容性列表是否匹配的函数。
其函数原型为:
int of_device_is_compatible(const struct device_node *device,
const char * const *compat)
serial1: serial@10010000 {
compatible = "acme,serial";
status = "okay";
};
我们可以如下检查serial1节点与"acme,serial"和"foo,bar"的兼容性:
struct device_node *serial1_np;
const char *compat[] = {"acme,serial", "foo,bar"};
serial1_np = of_find_node_by_path("/serial1");
ret = of_device_is_compatible(serial1_np, compat);
这会返回ret = 1,因为serial1节点的兼容性属性"acme,serial"在compat列表中。
如果再检查与"bar,foo"的兼容性:
const char *compat[] = {"bar,foo"};
ret = of_device_is_compatible(serial1_np, compat);
则会返回ret = 0,因为"bar,foo"不在serial1节点的兼容性属性列表中。
所以,of_device_is_compatible()为我们提供了一种简单检查设备树节点与给定设备兼容性列表的匹配关系的方法。
of_machine_is_compatible()是用来检查机器设备树节点与给定的机器兼容性列表是否匹配的函数。
其函数原型为:
int of_machine_is_compatible(const char * const *compat)
/ {
model = "ACME board";
compatible = "acme,board", "generic-board";
};
我们可以如下检查与"acme,board"和"foo,bar"的兼容性:
const char *compat[] = {"acme,board", "foo,bar"};
ret = of_machine_is_compatible(compat);
这会返回ret = 1,因为机器设备树根节点的兼容性属性"acme,board"在compat列表中。
如果检查与"bar,foo"的兼容性:
const char *compat[] = {"bar,foo"};
ret = of_machine_is_compatible(compat);
则会返回ret = 0,因为"bar,foo"不在根节点的兼容性属性列表中。
所以,of_machine_is_compatible()为我们提供了一种简单检查机器设备树与给定机器兼容性列表的匹配关系的方法。
/include/linux/of_gpio.h
of_get_named_gpio()是用于从设备树中获取命名GPIO资源的函数。
其函数原型为:
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
gpio1: gpio@10010000 {
gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>,
<&gpio2 20 GPIO_ACTIVE_LOW>;
};
我们可以如下获取gpio1节点gpios属性中的第二个GPIO资源:
struct device_node *gpio1_np;
int gpio;
gpio1_np = of_find_node_by_path("/gpio1");
gpio = of_get_named_gpio(gpio1_np, "gpios", 1);
这会返回gpio = 20,即gpios属性第二个GPIO资源。
如果索引为0,则会返回第一个GPIO资源gpio = 10。
如果索引超出范围,则会返回负的错误码。
所以,of_get_named_gpio()为我们提供了一种从设备树中获取命名GPIO资源的简单方法。
of_get_named_gpio()返回的GPIO编号并不是随机的,而是基于Devices Tree中GPIO信息,参考内核中的GPIO控制器注册信息计算得到的。通过这个编号,我们就可以申请和控制对应的GPIO口。
GPIO的使用需要明确理解编号与硬件的对应关系。不同的硬件平台,这些信息是不一样的。
of_get_gpio()是用于从设备树中获取任意GPIO资源(命名或非命名)的函数。
其函数原型为:
int of_get_gpio(struct device_node *np, int index)
gpio1: gpio@10010000 {
gpios = <10 GPIO_ACTIVE_HIGH>,
<20 GPIO_ACTIVE_LOW>;
gpio-hog; // non-named GPIO
};
我们可以如下获取gpio1节点的第2个和第3个GPIO资源:
struct device_node *gpio1_np;
int gpio;
gpio1_np = of_find_node_by_path("/gpio1");
gpio = of_get_gpio(gpio1_np, 1); // Get 2nd GPIO (20)
gpio = of_get_gpio(gpio1_np, 2); // Get 3rd GPIO (from gpio-hog)
这会返回gpio = 20(命名GPIO)和gpio = 非命名GPIO。
如果索引超出gpio1节点的GPIO数,则会返回负的错误码。
所以,of_get_gpio()为我们提供了一种从设备树中获取任意GPIO资源(命名或非命名)的简单方法。
of_gpio_named_count()是用于获取设备树节点中命名GPIO的数量的函数。
其函数原型为:
int of_gpio_named_count(struct device_node *np, const char *propname)
gpio1: gpio@10010000 {
gpios = <10 GPIO_ACTIVE_HIGH>,
<20 GPIO_ACTIVE_LOW>;
gpio-hog;
};
我们可以如下获取gpio1节点gpios属性中的GPIO数量:
struct device_node *gpio1_np;
int count;
gpio1_np = of_find_node_by_path("/gpio1");
count = of_gpio_named_count(gpio1_np, "gpios");
这会返回count = 2,因为gpios属性定义了两个GPIO资源。
如果propname为其他名称,则会返回错误码,因为gpio1节点没有其他命名GPIO属性。
所以,of_gpio_named_count()为我们提供了一种简单获取设备树节点中命名GPIO数量的方法。
of_gpio_count()是用于获取设备树节点中所有GPIO(命名或非命名)的数量的函数。
其函数原型为:
int of_gpio_count(struct device_node *np)
gpio1: gpio@10010000 {
gpios = <10 GPIO_ACTIVE_HIGH>,
<20 GPIO_ACTIVE_LOW>;
gpio-hog;
};
我们可以如下获取gpio1节点的全部GPIO数量:
struct device_node *gpio1_np;
int count;
gpio1_np = of_find_node_by_path("/gpio1");
count = of_gpio_count(gpio1_np);
这会返回count = 3,因为gpio1节点定义了2个命名GPIO和1个非命名GPIO。
所以,of_gpio_count()为我们提供了一种简单获取设备树节点中全部GPIO(命名和非命名)数量的方法。
以下函数在include/linux/gpio.h中声明
gpio_request()是申请使用GPIO的函数。
其函数原型为:
int gpio_request(unsigned int gpio, const char *label)
#define GPIO_LED 10
ret = gpio_request(GPIO_LED, "sys_led"); // Request GPIO_LED
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return -1;
}
gpio_direction_output(GPIO_LED, 0); // Set GPIO_LED as output
gpio_set_value(GPIO_LED, 1); // Turn on GPIO_LED
首先申请使用GPIO_LED,如果成功,则配置为输出并点亮LED。
其他驱动在该GPIO被申请后, gpio_request()将返回忙错误,无法再申请使用。
所以,gpio_request()为我们提供了一种简单的申请使用某个GPIO并避免资源冲突的机制。
gpio_request_one()是申请使用单个GPIO的简化函数。
其函数原型为:
int gpio_request_one(unsigned int gpio, unsigned long flags, const char *label)
这个函数会申请使用GPIO,同时根据flags设置GPIO方向和电平极性,
如果成功,则返回0,否则返回错误码。
与gpio_request()相比,gpio_request_one()在申请GPIO的同时完成了初始化配置,简化了GPIO申请和设置的步骤。其他方面,包括申请目的、成功/失败返回值、限制等都与gpio_request()相同。
例如:
#define GPIO_LED 10
ret = gpio_request_one(GPIO_LED, GPIOF_OUT_INIT_LOW, "sys_led");
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return -1;
}
gpio_set_value(GPIO_LED, 1); // Turn on GPIO_LED
这会申请GPIO_LED,同时配置为输出且初始化为低电平。所以点亮LED后,无须再调用gpio_direction_output()配置方向。
所以,gpio_request_one()为我们提供了一种简单的申请使用某个GPIO,同时完成初始化配置的机制。这是正确使用GPIO的最简单方式。
gpio_request_array()是申请使用GPIO数组的函数。
其函数原型为:
int gpio_request_array(const struct gpio *array, size_t num)
struct gpio {
unsigned int gpio;
unsigned long flags;
const char *label;
};
struct gpio leds[] = {
{10, GPIOF_OUT_INIT_LOW, "led1"},
{20, GPIOF_OUT_INIT_HIGH, "led2"},
{30, GPIOF_IN, "button1"}
};
ret = gpio_request_array(leds, ARRAY_SIZE(leds));
if (ret) {
printk("Failed to request GPIOs,error=%d\n", ret);
return -1;
}
这会申请3个GPIO,GPIO10和GPIO20配置为输出,GPIO30配置为输入。
所以,gpio_request_array()为我们提供了一种简单的批量申请多个GPIO并简单初始化的机制。
gpio_free()是释放已申请的GPIO的函数。
其函数原型为:
void gpio_free(unsigned int gpio)
#define GPIO_LED 10
ret = gpio_request(GPIO_LED, "sys_led"); // Request GPIO_LED
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return -1;
}
// ...
gpio_set_value(GPIO_LED, 0); // Turn off GPIO_LED
gpio_free(GPIO_LED); // Free GPIO_LED
首先申请使用GPIO_LED,然后在使用完成后释放之。
其他驱动在该GPIO被释放后,gpio_request()将再次申请成功, regain 使用权。
所以,gpio_free()为我们提供了一种简单的释放某个申请使用过的GPIO资源的机制。这是正确使用GPIO的最后一步。
gpio_free_array()是释放已申请的GPIO数组的函数。
其函数原型为:
void gpio_free_array(const struct gpio *array, size_t num)
struct gpio leds[] = {
{10, GPIOF_OUT_INIT_LOW, "led1"},
{20, GPIOF_OUT_INIT_HIGH, "led2"},
{30, GPIOF_OUT_INIT_LOW, "led3"},
};
ret = gpio_request_array(leds, ARRAY_SIZE(leds));
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", ret, ret);
return -1;
}
// ...
gpio_set_value(10, 0); // Turn off led1
gpio_set_value(20, 0); // Turn off led2
gpio_set_value(30, 0); // Turn off led3
gpio_free_array(leds, ARRAY_SIZE(leds)); // Free leds
这会释放之前通过gpio_request_array()申请的GPIO 10、20和30。
所以,gpio_free_array()为我们提供了一种简单的批量释放一组GPIO资源的机制。
gpio_direction_input()是设置GPIO方向为输入的函数。
其函数原型为:
int gpio_direction_input(unsigned int gpio)
#define GPIO_BUTTON 10
ret = gpio_request(GPIO_BUTTON, "sys_button"); // Request GPIO_BUTTON
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_BUTTON, ret);
return -1;
}
gpio_direction_input(GPIO_BUTTON); // Set GPIO_BUTTON as input
val = gpio_get_value(GPIO_BUTTON); // Read GPIO_BUTTON input level
首先申请使用GPIO_BUTTON,然后配置为输入方向,最后读取其输入电平值。
其他驱动无法再配置该GPIO的方向,直到其被释放。所以,该GPIO一旦被设置为输入,则保持输入状态直到资源释放。
所以,gpio_direction_input()为我们提供了一种简单的将GPIO配置为输入模式的机制。这是正确使用GPIO输入的第一步。
gpio_direction_output()是设置GPIO方向为输出的函数。
其函数原型为:
int gpio_direction_output(unsigned int gpio, int value)
#define GPIO_LED 10
ret = gpio_request(GPIO_LED, "sys_led"); // Request GPIO_LED
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return -1;
}
gpio_direction_output(GPIO_LED, 0); // Set GPIO_LED as output and low
gpio_set_value(GPIO_LED, 1); // Set GPIO_LED to high
首先申请使用GPIO_LED,然后配置为输出方向且初始值为低电平,最后设置为高电平点亮LED。
其他驱动无法再配置该GPIO的方向,直到其被释放。所以,该GPIO一旦被设置为输出,则保持输出状态直到资源释放。
所以,gpio_direction_output()为我们提供了一种简单的将GPIO配置为输出模式的机制。这是正确使用GPIO输出的第一步。
gpio_set_debounce()是设置GPIO防抖时间的函数。
其函数原型为:
int gpio_set_debounce(unsigned gpio, unsigned debounce)
#define GPIO_BUTTON 10
ret = gpio_request(GPIO_BUTTON, "sys_button"); // Request GPIO_BUTTON
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_BUTTON, ret);
return -1;
}
gpio_direction_input(GPIO_BUTTON); // Set GPIO_BUTTON as input
gpio_set_debounce(GPIO_BUTTON, 200); // Set debounce time as 200ms
val = gpio_get_value(GPIO_BUTTON); // Read stable input level
首先申请GPIO_BUTTON并配置为输入,然后设置防抖时间为200ms,最后读取稳定的输入值。
如果BUTTON在短于200ms内出现多次抖动,将被过滤,读取到的val值不变。只有在BUTTON输入稳定200ms后,val值才随BUTTON真实变化而改变。
所以,gpio_set_debounce()为我们提供了一种简单的为GPIO输入设置软件防抖的机制。这有助于提高GPIO输入读取值的正确性。
gpio_get_value()是读取GPIO输入值的函数。
其函数原型为:
int gpio_get_value(unsigned int gpio)
#define GPIO_BUTTON 10
ret = gpio_request(GPIO_BUTTON, "sys_button"); // Request GPIO_BUTTON
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_BUTTON, ret);
return -1;
}
gpio_direction_input(GPIO_BUTTON); // Set GPIO_BUTTON as input
val = gpio_get_value(GPIO_BUTTON); // Read GPIO_BUTTON input level
if (val)
printk("Button pressed!\n");
else
printk("Button released!\n");
首先申请GPIO_BUTTON并配置为输入,然后读取其输入值,并根据不同值判断按钮是否被按下。
GPIO输入值的变化可被随时读取,仅需再次调用gpio_get_value()。但当GPIO配置为输出方向时,读取的值始终为输出值,而非输入。
所以,gpio_get_value()为我们提供了一种简单的读取GPIO输入电平值的机制。这是使用GPIO输入的主要方法之一。
gpio_set_value()是设置GPIO输出值的函数。
其函数原型为:
void gpio_set_value(unsigned int gpio, int value)
#define GPIO_LED 10
ret = gpio_request(GPIO_LED, "sys_led"); // Request GPIO_LED
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return -1;
}
gpio_direction_output(GPIO_LED, 0); // Set GPIO_LED as output and low
gpio_set_value(GPIO_LED, 1); // Turn on GPIO_LED
msleep(500); // Delay 0.5s
gpio_set_value(GPIO_LED, 0); // Turn off GPIO_LED
首先申请GPIO_LED并配置为输出,然后设置输出值为高电平点亮LED,延时0.5s后再设置输出值为低电平熄灭LED。
GPIO输出值可被随时修改,仅需再次调用gpio_set_value()。但当GPIO配置为输入方向时,设置的值不会起任何作用。
所以,gpio_set_value()为我们提供了一种简单的设置GPIO输出电平值的机制。这是使用GPIO输出的主要方法之一。
gpio_cansleep()是判断GPIO是否允许睡眠的函数。
其函数原型为:
int gpio_cansleep(unsigned int gpio)
#define GPIO_LED 10
void led_blink(void)
{
ret = gpio_request(GPIO_LED, "sys_led"); // Request GPIO
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return;
}
if (gpio_cansleep(GPIO_LED)) { // In sleep context
gpio_direction_output(GPIO_LED, 0); // Set as output
// Use GPIO ...
} else {
gpio_set_value(GPIO_LED, 0); // Limited use in non-sleep context
}
gpio_free(GPIO_LED); // Release GPIO
}
可以看到,我首先申请了GPIO,然后判断上下文,根据判断配置方向或设置值,最后释放GPIO。
gpio_get_value_cansleep()是在睡眠上下文中读取GPIO输入值的函数。
其函数原型为:
int gpio_get_value_cansleep(unsigned int gpio)
#define GPIO_BUTTON 10
void button_pressed(void)
{
ret = gpio_request(GPIO_BUTTON, "sys_button"); // Request GPIO_BUTTON
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_BUTTON, ret);
return;
}
gpio_direction_input(GPIO_BUTTON); // Set GPIO_BUTTON as input
if (gpio_get_value_cansleep(GPIO_BUTTON)) // Read input level,in sleep context
printk("Button pressed!\n");
}
在button_pressed()进程上下文函数中,首先申请GPIO_BUTTON并配置为输入,然后读取其输入值,并根据不同值判断按钮是否被按下。
由于此函数只能在睡眠上下文调用,所以如果在中断上下文中调用button_pressed(),则会出错。这是与gpio_get_value()的主要区别。
所以,gpio_get_value_cansleep()为我们提供了一种在睡眠上下文中读取GPIO输入值的方法。这有助于区分GPIO的不同访问上下文。
gpio_set_value_cansleep()是在睡眠上下文中设置GPIO输出值的函数。
其函数原型为:
void gpio_set_value_cansleep(unsigned int gpio, int value)
#define GPIO_LED 10
void led_blink(void)
{
ret = gpio_request(GPIO_LED, "sys_led"); // Request GPIO_LED
if (ret) {
printk("Failed to request GPIO_%d,error=%d\n", GPIO_LED, ret);
return;
}
gpio_direction_output(GPIO_LED, 0); // Set as output
gpio_set_value_cansleep(GPIO_LED, 1); // Turn on LED, in sleep context
msleep(500); // Delay 0.5s
gpio_set_value_cansleep(GPIO_LED, 0); // Turn off LED
}
在led_blink()进程上下文函数中,首先申请GPIO_LED并配置为输出,然后设置输出值为高电平点亮LED,延时0.5s后再设置输出值为低电平熄灭LED。
由于此函数只能在睡眠上下文调用,所以如果在中断上下文中调用led_blink(),则会出错。这是与gpio_set_value()的主要区别。
所以,gpio_set_value_cansleep()为我们提供了一种在睡眠上下文中设置GPIO输出值的方法。这有助于区分GPIO的不同访问上下文。
/include/linux/of_irq.h
include/linux/of.h头文件中,它的原型为:
struct device_node *of_irq_find_parent(struct device_node *child);
该函数的作用是:查找child节点中interrupt-parent属性指定的节点,或查找其自然父节点。并从这些节点中找到#interrupt-cells属性,那么该节点就是中断父节点,并被返回。
of_irq_find_parent()会返回找到的中断父控制器节点的设备节点指针,如果没有找到则返回NULL。
该函数的使用方法是:
struct device_node *node, *parent;
node = of_find_node_by_path("/interrupt"); // 获取中断节点
parent = of_irq_find_parent(node); // 查找其中断父节点
if (parent)
/* 从parent中获取中断控制器信息,执行相关操作 */
else
pr_err("No interrupt parent found!\n");
以上代码会查找名为/interrupt的节点的中断父控制器,并执行相关操作。
of_irq_find_parent()函数的工作机制是:
/include/linux/of_irq.h
irq_of_parse_and_map()是从设备树中解析中断信息并映射中断号的函数。
其函数原型为:
int irq_of_parse_and_map(struct device_node *dev, int index)
dts
/* exynos4412.dtsi */
interrupt-parent = <&gic>; /* The GIC */
/* samsung-i2c.dtsi */
i2c3: i2c@13820000 {
interrupts = <0 25 4>, <1 25 4>; /* I2C0 and I2C1 share IRQ */
/* IRQ number is 25 + 4 = 29 */
};
可以使用此函数如下解析和映射i2c@13820000的中断:
struct device_node *np = of_find_compatible_node(NULL, NULL, "samsung,i2c");
int irq = irq_of_parse_and_map(np, 0); /* Parse the first interrupt */
/* irq will be 29 */
此例中,irq_of_parse_and_map()解析了np设备节点(即i2c@13820000)的第一个中断描述<0 25 4>,计算出实际的中断号29并返回。
所以,irq_of_parse_and_map()为我们提供了一种从设备树中解析中断信息并映射中断号的机制。这对中断的申请和注册至关重要。
这里每个SOC上的GPIO引脚(GPX2_7 ,GPX1_0,GPF_4,GPF3_5)分别控制着LED2-LED5。当GPIO引脚高电平时,开关三极管导通,LED二级管发光。反之则关闭。因此,驱动程序主要是通过控制GPIO引脚输出低电平或高电平来控制LED的亮灭。
在此,因为要引入了设备树,不需要像【嵌入式环境下linux内核及驱动学习笔记-(10-内核内存管理)】3.3所述的作法,这里不需要知道每个gpio寄存器的地址及操作方法。
在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)
…/linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
fs4412-leds {
compatible = "fs4412,led2-5";
led2-gpio = <&gpx2 7 0>;
led3-gpio = <&gpx1 0 0>;
led4-gpio = <&gpf3 4 0>;
led5-gpio = <&gpf3 5 0>;
};
/*************************************************************************
> File Name: led-tree.c
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "public.h"
/*1、定义重要的变量及结构体*/
struct x_dev_t {
struct cdev my_dev; //cdev设备描述结构体变量
atomic_t have_open; //记录驱动是否被打开的原子变量
int led2gpio, led3gpio , led4gpio, led5gpio; //设备树gpio编号
};
struct x_dev_t *pcdev;
/*所有驱动函数声明*/
long unlocked_ioctl (struct file *, unsigned int, unsigned long);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
.owner = THIS_MODULE,
.open = open,
.release = release,
.unlocked_ioctl = unlocked_ioctl,
};
int led_init(struct x_dev_t *pdev){
//从设备树里读出节点,以及gpio号
struct device_node *dnode = of_find_node_by_path("/fs4412-led");
if (!dnode){
printk("driver: fs4412-led node is failed\n");
return -1;
}
pdev->led2gpio = of_get_named_gpio(dnode , "led2-gpio" , 0);
if (pdev->led2gpio > 0){
gpio_request_one(pdev->led2gpio , GPIOF_OUT_INIT_LOW , "led2");
}
pdev->led3gpio = of_get_named_gpio(dnode , "led3-gpio" , 0);
if (pdev->led3gpio > 0){
gpio_request_one(pdev->led3gpio , GPIOF_OUT_INIT_LOW , "led3");
}
pdev->led4gpio = of_get_named_gpio(dnode , "led4-gpio" , 0);
if (pdev->led4gpio > 0){
gpio_request_one(pdev->led4gpio , GPIOF_OUT_INIT_LOW , "led4");
}
pdev->led5gpio = of_get_named_gpio(dnode , "led5-gpio" , 0);
if (pdev->led5gpio > 0){
gpio_request_one(pdev->led5gpio , GPIOF_OUT_INIT_LOW , "led5");
}
return 0;
}
void led_cntl(struct x_dev_t *p , int on_off){
gpio_set_value(p->led2gpio , on_off);
gpio_set_value(p->led3gpio , on_off);
gpio_set_value(p->led4gpio , on_off);
gpio_set_value(p->led5gpio , on_off);
}
static int __init my_init(void){
int unsucc =0;
dev_t devno;
int major,minor;
pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);
/*2、创建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , 1 , "led2");
if (unsucc){
printk(" creating devno faild\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
printk("devno major = %d ; minor = %d;\n",major , minor);
/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
cdev_init(&pcdev->my_dev , &fops);
pcdev->my_dev.owner = THIS_MODULE;
/*4、注册cdev结构体到内核链表中*/
unsucc = cdev_add(&pcdev->my_dev,devno,1);
if (unsucc){
printk("cdev add faild \n");
return -1;
}
//初始化原子量have_open为1
atomic_set(&pcdev->have_open,1);
//初始化led2
if (led_init(pcdev)){
printk("driver: initalization led is failed\n");
return -1;
}else{
printk("the driver led2 initalization completed\n");
}
return 0;
}
static void __exit my_exit(void)
{
cdev_del(&pcdev->my_dev);
unregister_chrdev_region(pcdev->my_dev.dev , 1);
gpio_free(pcdev->led2gpio);
gpio_free(pcdev->led3gpio);
gpio_free(pcdev->led4gpio);
gpio_free(pcdev->led5gpio);
printk("***************the driver timer-second exit************\n");
}
/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){
struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);
pf->private_data = (void *)p;
//在open函数中对原子量have_open进行减1并检测。=0,允许打开文件,<0则不允许打开
if (atomic_dec_and_test(&p->have_open)){
printk("led2 driver is opened\n");
return 0 ;
}else{
printk("device led2 can't be opened again\n");
atomic_inc(&p->have_open);//原子量=-1,记得这里要把原子量加回到0
return -1;
}
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
struct x_dev_t *p = (struct x_dev_t *)pf->private_data;
printk("led2 is closed \n");
atomic_set(&p->have_open,1);
return 0;
}
long unlocked_ioctl (struct file *pf, unsigned int cmd, unsigned long arg){
struct x_dev_t *p = pf->private_data;
switch(cmd){
case LED_ON:
led_cntl(p,1);
break;
case LED_OFF:
led_cntl(p,0);
break;
default:
break;
}
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
#ifndef _H_PUBLIC_
#define _H_PUBLIC_
#include
#define LED_ON _IO('a',1)
#define LED_OFF _IO('a',0)
#endif
/*************************************************************************
> File Name: test.c
************************************************************************/
#include
#include
#include
#include
#include
#include "public.h"
int main (int argc , char **argv){
int fd0,fd1;
if (argc <2){
printf("argument is too less\n");
return -1;
}else{
fd0 = open(argv[1] , O_RDONLY );
while (fd0){
printf("led on......\n");
ioctl(fd0,LED_ON);
sleep(2);
printf("led off......\n");
ioctl(fd0,LED_OFF);
sleep(2);
}
}
close(fd0);
return 0;
}
1、修改设备树并编译
2、运行步骤
3、效果
开发板上的4个led应该每1秒闪灭一次
DTB(Device Tree Blob)文件是编译后的设备树文件,它以二进制格式存储。我们可以通过如下步骤将其反编译回DTS(Device Tree Source)文件,用于读取与编辑:
1、 安装Device Tree Compiler(dtc)工具。dtc是内核工具,用于编译DTS文件生成DTB文件和反编译DTB文件生成DTS文件。
sudo apt install device-tree-compiler
2、 获取目标系统的DTB文件,一般在启动分区或内核镜像中。可以通过U盘启动系统后,从proc/device-tree获取,或通过其他方式读取该文件。
3、 运行dtc工具的反编译命令,反编译DTB文件。命令格式为:
dtc -I dtb -O dts -o output.dts input.dtb
-I dtb:输入文件格式为dtb (大写的i)
-O dts:输出文件格式为dts
-o output.dts:输出的DTS文件名称
input.dtb:输入的DTB文件名称
例如,反编译名为exynos4412-odroidxu.dtb的文件,命令为:
dtc -I dtb -O dts -o exynos4412-odroidxu.dts exynos4412-odroidxu.dtb
4、 反编译成功后,会生成exynos4412-odroidxu.dts文件,这就是对应于exynos4412-odroidxu.dtb的原始DTS设备树文件,可以打开查看与编辑。
5、 编辑DTS文件后,可以再次使用DTC工具编译为DTB文件,然后替换系统原有的DTB文件,实现对设备树的修改。