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的设备节点,然后该节点又有两个名为statuspwr的子节点。两个子节点有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的理解和使用

NanoPi NEO Air使用十四:FrameBuffer的理解和使用

NanoPi NEO Air使用二:固件烧录