NanoPi NEO Air使用九:使用Linux内核自带的LED驱动
Posted qlexcel
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NanoPi NEO Air使用九:使用Linux内核自带的LED驱动相关的知识,希望对你有一定的参考价值。
LED灯的驱动文件
一般Linux 内核会自带了LED 灯驱动,要使用 Linux 内核自带的 LED 灯驱动首先得先配置 Linux 内核,使能自带的 LED 灯驱动,输入如下命令打开 Linux 配置菜单:make menuconfig
按照如下路径打开 LED 驱动配置项:
-> Device Drivers
-> LED Support (NEW_LEDS [=y])
->LED Support for GPIO connected LEDs
按照上述路径,选择“LED Support for GPIO connected LEDs”,将其编译进 Linux 内核,也即是在此选项上按下“Y”键,使此选项前面变为“<*>”,如图:
在“LED Support for GPIO connected LEDs”上按下‘?’ 可以打开此选项的帮助信息,如图:
可以看到Symbol值为:LEDS_GPIO
。我们知道make menuconfig
进行的修改会保存到.config文件中,我们把“LED Support for GPIO connected LEDs”使能后,可以在.config文件中搜索LEDS_GPIO
找到:
CONFIG_LEDS_GPIO
赋值为y。
make menuconfig
后会进行make
,此时会调用/drivers/leds/Makefile 这个文件,打开它搜索CONFIG_LEDS_GPIO
可以看见:
可以看见/drivers/leds/leds-gpio.c这个文件会被编译进内核,他就是LED的驱动文件。
打开leds-gpio.c文件:
236 static const struct of_device_id of_gpio_leds_match[] = {
237 { .compatible = "gpio-leds", },
238 {},
239 };
......
290 static struct platform_driver gpio_led_driver = {
291 .probe = gpio_led_probe,
292 .remove = gpio_led_remove,
293 .driver = {
294 .name = "leds-gpio",
295 .of_match_table = of_gpio_leds_match,
296 },
297 };
298
299 module_platform_driver(gpio_led_driver);
第 236-239 行, LED 驱动的匹配表,此表只有一个匹配项, compatible 内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。
第 290-296 行, platform_driver 驱动结构体变量,可以看出, Linux 内核自带的 LED 驱动采用了 platform 框架。第 291 行可以看出 probe 函数为 gpio_led_probe,因此当驱动和设备匹配成功以后 gpio_led_probe 函数就会执行。从 294 行可以看出,驱动名字为“leds-gpio”,因此会在/sys/bus/platform/drivers 目录下存在一个名为“leds-gpio”的文件,如图所示:
第 299 行通过 module_platform_driver 函数向 Linux 内核注册 gpio_led_driver 这个 platform驱动。
module_platform_driver 是宏定义,展开后发现是驱动入口和出口的合集,如下:
static int __init gpio_led_driver_init(void)
{
return platform_driver_register (&(gpio_led_driver));
}
module_init(gpio_led_driver_init);
static void __exit gpio_led_driver_exit(void)
{
platform_driver_unregister (&(gpio_led_driver) );
}
module_exit(gpio_led_driver_exit);
当驱动和设备匹配以后 gpio_led_probe 函数就会执行,此函数主要是从设备树中获取 LED灯的 GPIO 信息,缩减后的函数内容如下:
243 static int gpio_led_probe(struct platform_device *pdev)
244 {
245 struct gpio_led_platform_data *pdata =dev_get_platdata(&pdev->dev);
246 struct gpio_leds_priv *priv;
247 int i, ret = 0;
248
249 if (pdata && pdata->num_leds) { /* 非设备树方式 */
/* 获取 platform_device 信息 */
......
268 } else { /* 采用设备树 */
269 priv = gpio_leds_create(pdev);
270 if (IS_ERR(priv))
271 return PTR_ERR(priv);
272 }
273
274 platform_set_drvdata(pdev, priv);
275
276 return 0;
277 }
第 269-271 行,如果使用设备树的话,使用 gpio_leds_create 函数从设备树中提取设备信息,获取到的 LED 灯 GPIO 信息保存在返回值中, gpio_leds_create 函数内容如下:
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *child;
struct gpio_leds_priv *priv;
int count, ret;
count = device_get_child_node_count(dev);
if (!count)
return ERR_PTR(-ENODEV);
priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
device_for_each_child_node(dev, child) {
struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
struct gpio_led led = {};
const char *state = NULL;
struct device_node *np = to_of_node(child);
ret = fwnode_property_read_string(child, "label", &led.name);
if (ret && IS_ENABLED(CONFIG_OF) && np)
led.name = np->name;
if (!led.name) {
fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
led.name);
if (IS_ERR(led.gpiod)) {
fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}
fwnode_property_read_string(child, "linux,default-trigger",
&led.default_trigger);
if (!fwnode_property_read_string(child, "default-state",
&state)) {
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
else if (!strcmp(state, "on"))
led.default_state = LEDS_GPIO_DEFSTATE_ON;
else
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
}
if (fwnode_property_present(child, "retain-state-suspended"))
led.retain_state_suspended = 1;
if (fwnode_property_present(child, "retain-state-shutdown"))
led.retain_state_shutdown = 1;
if (fwnode_property_present(child, "panic-indicator"))
led.panic_indicator = 1;
ret = create_gpio_led(&led, led_dat, dev, np, NULL);
if (ret < 0) {
fwnode_handle_put(child);
return ERR_PTR(ret);
}
led_dat->cdev.dev->of_node = np;
priv->num_leds++;
}
return priv;
}
第 8行,调用 device_get_child_node_count 函数统计子节点数量,一般在在设备树中创建一个节点表示 LED 灯,然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是 LED 灯的数量。
第 8行,为子节点分配内存。
第 16行,遍历每个子节点,获取每个子节点的信息。device_for_each_child_node是宏定义,其实是个for循环。
第 22 行,读取子节点 label 属性值,因为使用 label 属性作为 LED 的名字。
第 30行,获取 LED 灯所使用的 GPIO 信息。
第 38 行,获取“linux,default-trigger”属性值,可以通过此属性设置某个 LED 灯在Linux 系统中的默认功能,比如作为系统心跳指示灯等等。
第 41-49行,获取“default-state”属性值,也就是 LED 灯的默认状态属性。
第 58行,调用 create_gpio_led 函数创建 LED 相关的 io,其实就是设置 LED 所使用的 io为输出之类的。 create_gpio_led 函数主要是初始化 led_dat 这个 gpio_led_data 结构体类型变量,led_dat 保存了 LED 的操作函数等内容。
关于 gpio_led_probe 函数就分析到这里, gpio_led_probe 函数主要功能就是获取 LED 灯的设备信息,然后根据这些信息来初始化对应的 IO,设置为输出等。
进入系统后,可以通过ls /sys/bus/platform/drivers
命令查看linux内核编译进了那些驱动文件:
修改设备树
从U-boot的加载信息可以知道,当前使用的设备树文件为sun8i-h3-nanopi-neo-air.dtb
dtb是由/home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi-neo-air.dts
编译得到的。
platform驱动文件与设备是靠compatible属性匹配的,platform驱动文件的compatible属性为gpio-leds
,因此打开设备树文件sun8i-h3-nanopi-neo-air.dts,再打开该文件的头文件,一层一层的搜索,最终在arch\\arm\\boot\\dts\\sun8i-h3-nanopi.dtsi
文件中找到:
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&leds_npi>, <&leds_r_npi>;
status {
label = "status_led";
gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
pwr {
label = "LED2";
gpios = <&r_pio 0 10 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
这是在根节点下一个名为leds
的设备节点,然后该节点又有两个名为status
和pwr
的子节点。两个子节点有label 属性,相当于别称,用来表示节点的具体功能,比如status_led、red、green等。
可以通过ls /sys/bus/platform/devices/
查看目前根节点上的设备:
进入该目录继续查看它的子节点:
这两个子目录的名字就是我们在设备树中设置的 label 属性值。
我们发现“status_led”灯的“linux,default-trigger”属性值为 “heartbeat”,说明这个灯的默认功能为心跳指示灯。我们现在要手动控制这个灯的亮灭,因此把“linux,default-trigger”注释掉,变为:
status {
label = "status_led";
gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
/*linux,default-trigger = "heartbeat";*/
};
重新编译设备树并更新到TF卡或emmc中,然后重启开发板。
运行测试
首先查看一下系统中有没有“/sys/class/leds/status_led/brightness”这个文件,如果有的话就输入如下命令打开这个 LED 灯:
echo 1 > /sys/class/leds/status_led/brightness
关闭这个 LED 灯的命令如下:
echo 0 > /sys/class/leds/status_led/brightness
以上是关于NanoPi NEO Air使用九:使用Linux内核自带的LED驱动的主要内容,如果未能解决你的问题,请参考以下文章
NanoPi NEO Air使用十:自己编写驱动来控制LED
NanoPi NEO Air使用七:获取并编译U-boot和Linux的源码
NanoPi NEO Air使用五:安装Xfce和xrdp,实现远程访问
NanoPi NEO Air使用十四:FrameBuffer的理解和使用