700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > STM32MP157驱动开发——Linux自带的LED灯驱动

STM32MP157驱动开发——Linux自带的LED灯驱动

时间:2023-02-09 05:44:54

相关推荐

STM32MP157驱动开发——Linux自带的LED灯驱动

STM32MP157驱动开发——Linux自带的LED灯驱动

0.前言一、Linux 内核自带 LED 驱动使能二、驱动简介1.LED灯驱动框架分析2.module_platform_driver 函数简析3.gpio_led_probe 函数简析三、设备树节点编写四、运行测试

0.前言

学习了一个月的驱动开发了,今天仍然要开发一个 LED 灯设备驱动。和以往的字符型、新字符型、设备树下的字符驱动、gpio 子系统的开发方式都不同,今天使用 platform 驱动框架进行开发,这也是现在 Linux 驱动开发的最常用的方式。并且本次开发中,将一次性对开发板上的两个 LED 灯进行开发。

一、Linux 内核自带 LED 驱动使能

在上一节中编写过基于设备树的 platform LED 灯驱动,但其实在 Linux 内核中已经自带了相关的 LED 驱动。不过要使用的话需要先配置 Linux 内核。在内核源码目录下使用make menuconfig命令打开 Linux 配置菜单,在Device Drivers --> LED Support(NEW_LEDS [=y]) --> LED Support for GPIO connected LEDs路径下,将该选项使能并编译进内核。按下"?"按键并输入"LED_GPIO"则可以查看相关的帮助。

重新编译内核,将编译出来的 uImage 镜像用于启动开发板。

二、驱动简介

1.LED灯驱动框架分析

LED 灯驱动文件为/drivers/leds/leds-gpio.c,在/drivers/leds/Makefile 这个文件中包含了相关驱动的编译信息:

# SPDX-License-Identifier: GPL-2.0# LED Coreobj-$(CONFIG_NEW_LEDS) += led-core.o......obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.oobj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.oobj-$(CONFIG_LEDS_GPIO) += leds-gpio.oobj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o......

如果定义了 CONFIG_LEDS_GPIO 就会编译 leds-gpio.c 这个文件,在.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,.shutdown = gpio_led_shutdown,.driver = {.name = "leds-gpio",.of_match_table = of_gpio_leds_match,},};module_platform_driver(gpio_led_driver);

of_gpio_leds_match为 LED 驱动的匹配表,此表只有一个匹配项, compatible 内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。

platform_driver驱动结构体变量为 Linux 内核自带的 LED 驱动,采用了 platform 框架。probe 函数为 gpio_led_probe,因此当驱动和设备匹配成功后 gpio_led_probe 函数会执行。驱动名字为“leds-gpio”,因此匹配成功后会在 /sys/bus/platform/drivers 目录下存在一个名为“leds-gpio”的文件。

module_platform_driver函数向 Linux 内核注册 gpio_led_driver 这个 platform 驱动。

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

所以,将module_platform_driver(gpio_led_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);

因此 module_platform_driver 函数的功能就是完成 platform 驱动的注册和删除。

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) {/* 非设备树方式 *//* 获取 platform_device 信息 */.....} 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;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;/** Acquire gpiod from DT with uninitialized label, which* will be updated after LED class device is registered,* Only then the final LED name is known.*/led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child, GPIOD_ASIS, NULL);if (IS_ERR(led.gpiod)) {fwnode_handle_put(child);return ERR_CAST(led.gpiod);}led_dat->gpiod = 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;elseled.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, child, NULL);if (ret < 0) {fwnode_handle_put(child);return ERR_PTR(ret);}/* Set gpiod label to match the corresponding LED name. */gpiod_set_consumer_name(led_dat->gpiod, led_dat->cdev.dev->kobj.name);priv->num_leds++;}return priv;}

①调用 device_get_child_node_count 函数统计子节点数量,一般在设备树中创建一个节点表示 LED 灯,然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是 LED 灯的数量

②遍历每个子节点,获取每个子节点的信息

③获取 LED 灯所使用的 GPIO 信息

④使用 fwnode_property_read_string 函数获取“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 的操作函数等内容

⑦使用 label 属性作为 LED 的名字,led_dat->cdev.dev->kobj.name 指向设备树里的 LED 灯节点下的 label 属性

总结:gpio_led_probe 函数主要功能就是获取 LED 灯的设备信息,然后根据这些信息来初始化对应的 IO,设置为输出

三、设备树节点编写

在 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 这个文档来查看可选功能:

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

⑦另外还有一些其他的可选属性,比如 led-sources、color、function 等属性,可以查阅相关文档自行设置

在正点原子的STM32MP157开发板上,两个 LED 分别连接在 PI0 和 PF3 引脚上,所以首先修改设备树文件stm32mp15-pinctrl.dtsi,在之前创建的 led_pins_a 节点下添加一个 LED 子节点:

&pinctrl {led_pins_a: gpioled-0 {pins {pinmux = <STM32_PINMUX('I', 0, GPIO)>,/*LED0*/<STM32_PINMUX('F', 3, GPIO)>;/*LED1*/driver-push-pull;bias-pull-up;output-high;slew-rate = <0>;};};......};

然后在设备树文件 stm32mp157d-atk.dts 中按照上文的文档要求,在根节点/下添加设备子节点:

dtsleds {compatible = "gpio-leds";pinctrl-0 = <&led_pins_a>;led0 {label = "red";gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;default-state = "off";};led1 {label = "green";gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;default-state = "off";};};

①设置 LED 的 pinctrl 节点为 led_pins_a,compatible 值为"gpio-leds",内核的LED驱动属性匹配值

②设置 led0 和 led1 的名称、引脚和默认状态

修改完成后,使用make dtbs命令编译出设备树文件,用于启动开发板。

四、运行测试

使用编译出的新 uImage 和设备树 .dtb 文件启动开发板,查看 /sys/bus/platform/devices/dtsleds 这个目录是否存在:

在该目录下存在一个 leds 目录,里面就有新创建的两个 led 设备:

再对两个 LED 设备进行测试,在 sys/class/leds 目录下,存在 red/brightness 和 green/brightness 两个文件,通过操作这两个文件即可控制两个 LED 的亮灭:

echo 1 > /sys/class/leds/red/brightness //打开 LED0echo 1 > /sys/class/leds/green/brightness //打开 LED1echo 0 > /sys/class/leds/red/brightness //关闭 LED0echo 0 > /sys/class/leds/green/brightness //关闭 LED1

如果能正常的打开和关闭两个 LED 灯就说明 Linux 内核自带的 LED 灯驱动工作正常。一般会将一个 LED 灯作为系统指示灯,系统运行正常的话这个 LED 指示灯就会闪烁。这里设置 LED0 作为系统指示灯,在 dtsleds/led0 这个设备节点中加入“linux,defaulttrigger”属性信息即可,属性值为“heartbeat”,修改完以后的 dtsleds 节点内容如下:

dtsleds {compatible = "gpio-leds";pinctrl-0 = <&led_pins_a>;led0 {label = "red";gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;linux,default-trigger = "heartbeat";default-state = "on";};led1 {label = "green";gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;default-state = "off";};};

设置 LED0 为 heartbeat,默认打开 LED0。重新编译设备树并且使用新的设备树启动 Linux 系统,启动以后 LED0 就会闪烁,作为系统心跳指示灯,表示系统正在运行

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。