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

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

⑥可以设置“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 //打开 LED0
echo 1 > /sys/class/leds/green/brightness //打开 LED1
echo 0 > /sys/class/leds/red/brightness //关闭 LED0
echo 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 就会闪烁,作为系统心跳指示灯,表示系统正在运行

STM32MP157驱动开发——Linux自带的LED灯驱动相关推荐

  1. Linux 自带的LED 灯驱动实验

    目录 Linux 内核自带LED 驱动使能 Linux 内核自带LED 驱动简介 LED 灯驱动框架分析 module_platform_driver 函数简析 gpio_led_probe 函数简析 ...

  2. Linux 自带的 LED 灯驱动

    1 Linux 内核自带 LED 驱动使能 Linux 内核已经自带了 LED 灯驱动,要使用 Linux 内核自带的 LED 灯驱动首先得先配置 Linux 内核,使能自带的 LED 灯驱动,输入如 ...

  3. linux自带的LED灯驱动实验

    文章目录 一.linux内核自带LED驱动使能 二.linux内核自带LED驱动简介 1.LED灯驱动框架分析 2.module_platform_driver函数解析 3.gpio_led_prob ...

  4. Linux 自带的 LED 灯驱动实验

    目录 一.配置内核 二.设备树节点编写 1.确定compatible 属性值 2.编写节点 三.验证 测试 其实 Linux 内核已经自带了 LED 灯驱动,要使用 Linux 内核自带的 LED 灯 ...

  5. Linux 内核自带的 LED 灯驱动

    系列文章 I.MX6ULL 手册查找使用方法 实战点亮LED(寄存器版) I.MX6ULL 手册查找使用方法 实战点亮LED(固件库版本) linux 字符设备驱动实战 linux LED设备驱动文件 ...

  6. 驱动开发指南 第八章 汇编LED灯实验

    <I.MX6U 嵌入式 x Linux 驱动开发指南 V1.6 6>第八章 汇编LED灯实验 正点原子[第二期]手把手教你学Linux之ARM(MX6U)裸机篇 视频 选集 时间 P6 第 ...

  7. 驱动开发--创建设备文件--控制LED灯

    目录 1.手动创建设备文件 2.应用程序如何将数据传递给驱动 3.控制LED灯: 4.应用层控制灯 5.自动创建设备节点 1.手动创建设备文件 cat  /proc/devices 查看主设备号 su ...

  8. ARM裸机开发——Linux环境搭建和LED灯闪烁实验

    写在前面  本文为学校开展的嵌入式系统设计课程,本文主要是记录课程中的相关作业与学习记录,本课程采用了S3C2440A嵌入式系统开发板,由于主要以学校开展课程为主,本文内容可能有一定的课程资料辅助以及 ...

  9. Linux驱动开发学习笔记【12】:Linux自带LED灯驱动

    目录 一.内核自带LED驱动使能 二.内核自带LED驱动分析 三.内核自带LED驱动使用 一.内核自带LED驱动使能 在Linux内核中,已经自带了LED灯的驱动程序,使用的就是platform平台驱 ...

最新文章

  1. CSS三栏布局的四种方法
  2. Cookie,Session
  3. Redis三种特殊类型
  4. 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
  5. socket编程中的异常处理
  6. c++ template(8)模版多态
  7. C语言探秘:C代码在内存中的分布
  8. 比特飞使用的是什么主题
  9. struts验证框架失效
  10. 博客“评论王”评选活动开始啦!
  11. 入手 M1 Mac 之前,你可以通过这个工具了解常用软件兼容性
  12. sketchup(草图大师)-倒圆角-避免破面现象
  13. Spark删除redis千万级别set集合数据
  14. C# DataGridView控件选中行获取其值
  15. 概率统计笔记:共轭分布
  16. C++学习——基于binance的AS做市模型
  17. 初识Latex及模板套用
  18. Redis5.0.x集群搭建
  19. 锁模光纤激光器 RP Fiber Power
  20. Package jdk.jshell

热门文章

  1. 2.1 Git介绍、安装与使用
  2. java基于ssm的学校教务管理系统的设计与实现论文-计算机毕业设计
  3. PHP strtotime() 函数将任何英文文本的日期或时间描述解析为 Unix 时间戳(自 January 1 1970 00:00:00 GMT 起的秒数)。
  4. java web 流媒体播放_实时流(直播流)播放、上墙(大屏播放)解决方案
  5. 数据库无法打开的原因及解决办法
  6. 解决《获取My Document目录错误,可能”我的文档“目录不存在》
  7. 2个月面试腾讯、B站、网易等11家公司的面经总结!经典好文
  8. MATLAB人脸识别疫情防护门禁系统GUI设计与实现
  9. 数据结构 哈希表 ASL 失败查找
  10. HDOJ 1495非常可乐