十三、(正点原子)Linux自带的LED灯驱动

        Linux 内核已经集成了像 LED 灯这样非常基础的设备驱动。Linux 内核的 LED 灯驱动采用platform 框架,因此我们只需要按照要求在设备树文件中添加相应的 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”上按下‘?’ 可以打开此选项的帮助信息:

         把 Linux 内 部 自 带 的 LED 灯 驱 动 编 译 进 内 核 以 后 ,CONFIG_LEDS_GPIO 就会等于‘y’, Linux 会根据 CONFIG_LEDS_GPIO 的值来选择如何编译LED 灯驱动,如果为‘y’就将其编译进 Linux 内核。
        配置好 Linux 内核以后退出配置界面,打开.config 文件,会找到“CONFIG_LEDS_GPIO=y”这一行,如图所示:

        重新编译 Linux 内核,然后使用新编译出来的 zImage 镜像启动开发板,开发板内就会LED灯驱动。

二、Linux内核自带LED驱动简介

        1、LED灯驱动框架分析

        LED 灯驱动文件为/drivers/leds/leds-gpio.c,可以打开/drivers/leds/Makefile 这个文件,找到如下所示内容:

# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
.....
obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
2obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
......

        如果定义了 CONFIG_LEDS_GPIO 的话就会编译 leds-gpio.c 这个文件,选择将 LED 驱动编译进 Linux 内核,在.config 文件中就会有“CONFIG_LEDS_GPIO=y”这一行,因此 leds-gpio.c 驱动文件就会被编译。
        接下来我们看一下 leds-gpio.c 这个驱动文件,找到驱动注册函数:

static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "gpio-leds", },
	{},
};

static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.remove		= gpio_led_remove,
	.driver		= {
		.name	= "leds-gpio",
		.of_match_table = of_gpio_leds_match,
	},
};

/* 驱动注册函数 */
module_platform_driver(gpio_led_driver);

        of_gpio_leds_match:为LED驱动的匹配表,compatible属性就是匹配的对象,我们在设备中LED灯节点的compatible属性要与of_gpio_leds_match匹配表一致,gpio_led_probe函数才能执行,否者驱动没法工作。

        gpio_led_driver:Linux 内核自带的 LED 驱动采用了 platform 框架。

        driver->name:驱动名字,在/sys/bus/platform/drivers 目录下存在一个名为“leds-gpio”的文件。

         2、module_platform_driver 函数简析

        LED 驱动会采用 module_platform_driver 函数向 Linux 内核注册platform 驱动,其实在 Linux 内核中会大量采用 module_platform_driver 来完成向 Linux 内核注册 platform 驱动的操作。 module_platform_driver 定义在 include/linux/platform_device.h 文件中,为一个宏,定义如下:

#define module_platform_driver(__platform_driver) \
            module_driver(__platform_driver, platform_driver_register, \
                            platform_driver_unregister)

        可以看出, module_platform_driver 依赖 module_driver, module_driver 也是一个宏,定义在include/linux/device.h 文件中,内容如下:

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

        将函数展开后得到:

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);

         这就是我们前面使用的注册驱动模块的框架。

        3、gpio_led_probe 函数简析

        当驱动和设备匹配以后 gpio_led_probe 函数就会执行,此函数主要是从设备树中获取 LED灯的 GPIO 信息,函数内容如下所示:

static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	int i, ret = 0;

	if (pdata && pdata->num_leds) {            /* 非设备树情况 */
        。。。。

	} else {                                /* 使用设备树情况 */
		priv = gpio_leds_create(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);
	return 0;
}

        如果使用设备树的话,使用 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;
	struct device_node *np;

    /* 统计子结点数量 */
	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 led = {};
		const char *state = NULL;
        
        /* 获取 LED 灯所使用的 GPIO 信息 */
		led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
		if (IS_ERR(led.gpiod)) {
			fwnode_handle_put(child);
			ret = PTR_ERR(led.gpiod);
			goto err;
		}

		np = of_node(child);
        
        /* 读取子节点 label 属性值,因为使用 label 属性作为 LED 的名字 */
		if (fwnode_property_present(child, "label")) {
			fwnode_property_read_string(child, "label", &led.name);
		} else {
			if (IS_ENABLED(CONFIG_OF) && !led.name && np)
				led.name = np->name;
			if (!led.name)
				return ERR_PTR(-EINVAL);
		}
        
        /* 获取“linux,default-trigger”属性值 */
		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

        /* 获取“default-state”属性值 */
		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;

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      dev, NULL);
		if (ret < 0) {
			fwnode_handle_put(child);
			goto err;
		}
	}

	return priv;

err:
	for (count = priv->num_leds - 2; count >= 0; count--)
		delete_gpio_led(&priv->leds[count]);
	return ERR_PTR(ret);
}

        调用 device_get_child_node_count 函数统计子节点数量,一般在在设备树中创建一个节点表示 LED 灯,然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是 LED 灯的数量。遍历每个子节点,获取每个子节点的信息。读取子节点 label 属性值,因为使用 label 属性作为 LED 的名字,获取“linux,default-trigger”属性值,可以通过此属性设置某个 LED 灯在Linux 系统中的默认功能,比如作为系统心跳指示灯等等,获取“default-state”属性值,也就是 LED 灯的默认状态属性,调用 create_gpio_led 函数创建 LED 相关的 io,其实就是设置 LED 所使用的 io为输出之类的。 create_gpio_led 函数主要是初始化 led_dat 这个 gpio_led_data 结构体类型变量,led_dat 保存了 LED 的操作函数等内容。

        知道了驱动获取哪些节点信息后,我们就可以使用时候添加节点时,我们就可以知道如何添加节点信息,添加哪些节点信息。

二、设备树节点编写

        打开文档 Documentation/devicetree/bindings/leds/leds-gpio.txt,此文档详细的讲解了 Linux自带驱动对应的设备树节点该如何编写,我们在编写设备节点的时候要注意以下几点:

        ①、 创建一个节点表示 LED 灯设备,比如 dtsleds,如果板子上有多个 LED 灯的话每个LED灯都作为 dtsleds 的子节点。

        ②、dtsleds 节点的 compatible 属性值一定要为“gpio-leds”。
        ③、设置 label 属性,此属性为可选,每个子节点都有一个 label 属性, label 属性一般表示LED 灯的名字,比如以颜色区分的话就是 red、 green 等等。

        ④、每个子节点必须要设置 gpios 属性值,表示此 LED 所使用的 GPIO 引脚!

        ⑤、可以设置“linux,default-trigger”属性值,也就是设置 LED 灯的默认功能,可以查阅
Documentation/devicetree/bindings/leds/common.txt 这个文档来查看可选功能,比如:

        backlight:          LED 灯作为背光。
        default-on:        LED 灯打开
        heartbeat:         LED 灯作为心跳指示灯,可以作为系统运行提示灯。
        ide-disk:            LED 灯作为硬盘活动指示灯。
        timer:                LED 灯周期性闪烁,由定时器驱动,闪烁频率可以修改

        ⑥、可以设置“default-state”属性值,可以设置为 onoff keep,为 on 的时候 LED 灯默认打开,为 off 的话 LED 灯默认关闭,为 keep 的话 LED 灯保持当前模式。

        示例:

        在dtsled节点里面,添加了Linux内核LED驱动需要的节点信息,还包括了使用pinctrl子系统和gpio子系统的节点信息可以参考前面的 六、(正点原子)pinctrl子系统和gpio子系统_正点原子开发板gpio-CSDN博客

         编译设备树文件,开发板使用新的设备树文件启动。

 三、验证

        在LED驱动添加后在/sys/bus/platform/driver里面会有配置的leds-gpio驱动文件:

         在/sys/bus/platform/devices里面会有设备树添加的节点设备:

         在启动内核时,我们的LED灯设置为  heartbeat模式,所以LED灯会像心跳一样闪动。,执行如下指令改变当前触发模式,改成[none]模式就可以通过指令来控制 LED 的亮灭了

echo none > /sys/class/leds/sys-led/trigger     // 改变 LED 的触发模式
echo 1    > /sys/class/leds/sys-led/brightness  // 点亮 LED
echo 0    > /sys/class/leds/sys-led/brightness  // 熄灭 LED
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tofu_Cabbage

你的打赏是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值