拿到一个开发板,板子上有led灯,如何点亮led?
基于linux5.10
Linux内核提供了pinctrl和gpio子系统用于GPIO驱动
一、pinctrl 子系统
pinctrl 子系统主要用于管理设备的引脚控制功能。它提供了一种统一的方式来配置和管理芯片引脚的复用、电气特性(如驱动能力、上下拉电阻等)以及引脚的状态(如输入、输出等)。
在 Linux 内核中,pinctrl 子系统使得驱动开发者能够方便地对引脚进行配置,而无需直接操作硬件寄存器。它抽象了不同芯片厂商的引脚控制差异,提高了代码的可移植性。
例如,在一个 SoC 中,某个引脚可能既可以作为通用输入输出(GPIO),也可以作为特定的外设功能引脚(如 UART 发送引脚)。pinctrl 子系统可以帮助开发者轻松地在这些不同的功能之间进行切换和配置。
二、gpio 子系统
gpio 子系统提供了对通用输入输出引脚的操作接口。它使得驱动开发者能够方便地对 GPIO 引脚进行读写操作,设置引脚的方向(输入或输出),以及获取引脚的当前状态。
通过 gpio 子系统,开发者可以以一种统一的方式来处理不同硬件平台上的 GPIO 操作,而无需关心底层硬件的具体实现细节。
1. 设备树
以imx8mm-evk.dtsi为例
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_led>;
status {
label = "led";
gpios = <&gpio3 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "on";
};
};
有以下属性:
default-state:
LEDS_GPIO_DEFSTATE_KEEP;keep
LEDS_GPIO_DEFSTATE_ON; on
LEDS_GPIO_DEFSTATE_OFF;
retain-state-suspended
led.retain_state_suspended = 1;
retain-state-shutdown
led.retain_state_shutdown = 1;
linux,default-trigger = "heartbeat"; 心跳,可以闪烁led
2. 驱动源码
leds驱动位于drivers/leds目录,leds-gpio驱动名称为“leds-gpio”
驱动文件为drivers/leds/leds-gpio.c
// 定义一个名为 gpio_led_data 的结构体,用于存储与 GPIO 控制的 LED 相关的数据
struct gpio_led_data {
// 用于 LED 设备的通用描述
struct led_classdev cdev;
// 指向 GPIO 描述符的指针
struct gpio_desc *gpiod;
// 表示是否能够在睡眠状态下操作
u8 can_sleep;
// 表示是否处于闪烁状态
u8 Blinking;
// 用于设置 GPIO 闪烁的函数指针
gpio_blink_set_t platform_gpio_blink_set;
};
// 内联函数,用于将 led_classdev 结构体指针转换为 gpio_led_data 结构体指针
static inline struct gpio_led_data *
cdev_to_gpio_led_data(struct led_classdev *led_cdev)
{
// 使用 container_of 宏进行结构体指针的转换
return container_of(led_cdev, struct gpio_led_data, cdev);
}
// 设置 LED 亮度的函数
static void gpio_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
// 通过转换函数获取 gpio_led_data 结构体指针
struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
int level;
// 根据亮度值设置电平
if (value == LED_OFF)
level = 0;
else
level = 1;
// 如果处于闪烁状态
if (led_dat->blinking) {
// 调用特定的闪烁设置函数并停止闪烁
led_dat->platform_gpio_blink_set(led_dat->gpiod, level,
NULL, NULL);
led_dat->blinking = 0;
} else {
// 根据是否能睡眠选择相应的设置值函数
if (led_dat->can_sleep)
gpiod_set_value_cansleep(led_dat->gpiod, level);
else
gpiod_set_value(led_dat->gpiod, level);
}
}
// 阻塞式设置 LED 亮度的函数
static int gpio_led_set_blocking(struct led_classdev *led_cdev,
enum led_brightness value)
{
// 调用普通的设置函数
gpio_led_set(led_cdev, value);
return 0;
}
// 设置 LED 闪烁的函数
static int gpio_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
// 通过转换函数获取 gpio_led_data 结构体指针
struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
// 设置为闪烁状态
led_dat->blinking = 1;
// 调用特定的闪烁设置函数
return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK,
delay_on, delay_off);
}
// 创建 GPIO 控制的 LED 设备的函数
static int create_gpio_led(const struct gpio_led *template,
struct gpio_led_data *led_dat, struct device *parent,
struct fwnode_handle *fwnode, gpio_blink_set_t blink_set)
{
struct led_init_data init_data = {};
int ret, state;
// 设置 LED 的默认触发方式
led_dat->cdev.default_trigger = template->default_trigger;
// 获取是否能睡眠的状态
led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
// 根据是否能睡眠设置亮度设置函数
if (!led_dat->can_sleep)
led_dat->cdev.brightness_set = gpio_led_set;
else
led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
// 初始化为不闪烁
led_dat->blinking = 0;
// 如果提供了闪烁设置函数
if (blink_set) {
// 设置相应的成员
led_dat->platform_gpio_blink_set = blink_set;
led_dat->cdev.blink_set = gpio_blink_set;
}
// 根据默认状态设置初始亮度
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
state = gpiod_get_value_cansleep(led_dat->gpiod);
if (state < 0)
return state;
} else {
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
}
// 设置亮度
led_dat->cdev.brightness = state? LED_FULL : LED_OFF;
// 根据是否保留 suspend 状态设置标志
if (!template->retain_state_suspended)
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
// 根据是否为 panic 指示器设置标志
if (template->panic_indicator)
led_dat->cdev.flags |= LED_PANIC_INDICATOR;
// 根据是否保留关机状态设置标志
if (template->retain_state_shutdown)
led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
// 设置 GPIO 的方向和输出值
ret = gpiod_direction_output(led_dat->gpiod, state);
if (ret < 0)
return ret;
// 如果提供了名称
if (template->name) {
// 设置 LED 名称
led_dat->cdev.name = template->name;
// 注册 LED 设备
ret = devm_led_classdev_register(parent, &led_dat->cdev);
} else {
// 初始化一些数据
init_data.fwnode = fwnode;
// 扩展注册 LED 设备
ret = devm_led_classdev_register_ext(parent, &led_dat->cdev,
&init_data);
}
return ret;
}
// 用于存储多个 GPIO 控制的 LED 数据的结构体
struct gpio_leds_priv {
// LED 的数量
int num_leds;
// 存储 gpio_led_data 结构体的数组
struct gpio_led_data leds[];
};
// 创建多个 GPIO 控制的 LED 数据结构的函数
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
// 获取平台设备对应的设备结构体
struct device *dev = &pdev->dev;
// 定义用于存储设备子节点的指针
struct fwnode_handle *child;
// 分配 gpio_leds_priv 结构体的内存
struct gpio_leds_priv *priv;
int count, ret;
count = device_get_child_node_count(dev);
// 如果没有子节点,返回错误 -ENODEV
if (!count)
return ERR_PTR(-ENODEV);
priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
// 如果分配内存失败,返回错误 -ENOMEM
if (!priv)
return ERR_PTR(-ENOMEM);
device_for_each_child_node(dev, child) {
// 获取 priv 中当前位置的 gpio_led_data 指针
struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
// 初始化 gpio_led 结构体
struct gpio_led led = {};
// 用于存储读取到的默认状态字符串
const char *state = NULL;
/*
* 从设备树的子节点获取未初始化标签的 gpiod,
* 其标签将在 LED 类设备注册后更新,
* 只有那时最终的 LED 名称才可知。
*/
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
NULL);
// 如果获取 gpiod 失败
if (IS_ERR(led.gpiod)) {
// 释放子节点的句柄
fwnode_handle_put(child);
// 返回错误
return ERR_CAST(led.gpiod);
}
// 将获取到的 gpiod 赋值给 led_dat
led_dat->gpiod = led.gpiod;
// 尝试从子节点读取 "default-state" 属性的字符串
if (!fwnode_property_read_string(child, "default-state",
&state)) {
// 根据读取到的字符串设置 led 的默认状态
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;
}
// 如果子节点存在 "retain-state-suspended" 属性,设置 led 的相应标志
if (fwnode_property_present(child, "retain-state-suspended"))
led.retain_state_suspended = 1;
// 如果子节点存在 "retain-state-shutdown" 属性,设置 led 的相应标志
if (fwnode_property_present(child, "retain-state-shutdown"))
led.retain_state_shutdown = 1;
// 如果子节点存在 "panic-indicator" 属性,设置 led 的相应标志
if (fwnode_property_present(child, "panic-indicator"))
led.panic_indicator = 1;
// 创建 GPIO 控制的 LED 设备
ret = create_gpio_led(&led, led_dat, dev, child, NULL);
// 如果创建失败
if (ret < 0) {
// 释放子节点的句柄
fwnode_handle_put(child);
// 返回错误
return ERR_PTR(ret);
}
/* 设置 gpiod 的消费者名称以匹配相应的 LED 名称 */
gpiod_set_consumer_name(led_dat->gpiod,
led_dat->cdev.dev->kobj.name);
// 增加已处理的 LED 数量
priv->num_leds++;
}
// 返回创建的 gpio_leds_priv 结构体指针
return priv;
}
// 定义与设备树兼容的匹配信息
static const struct of_device_id of_gpio_leds_match[] = {
{.compatible = "gpio-leds", },
{},
};
// 将匹配信息注册为设备表
MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
// 获取 GPIO 描述符的函数
static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
const struct gpio_led *template)
{
struct gpio_desc *gpiod;
unsigned long flags = GPIOF_OUT_INIT_LOW;
int ret;
/*
* 这意味着 LED 不是来自设备树或 ACPI,
* 所以让我们尝试通过索引从设备获取,
* 这将命中板级文件(如果有的话)并从那里获取 GPIO。
*/
gpiod = devm_gpiod_get_index(dev, NULL, idx, GPIOD_OUT_LOW);
// 如果获取成功
if (!IS_ERR(gpiod)) {
// 设置消费者名称
gpiod_set_consumer_name(gpiod, template->name);
return gpiod;
}
// 如果获取失败且错误不是 -ENOENT
if (PTR_ERR(gpiod)!= -ENOENT)
return gpiod;
/*
* 这是仍然使用 GPIO 编号的平台代码的遗留代码路径。
* 最终,我们希望完全摆脱这个代码块。
*/
/* 跳过不可用的 LED */
if (!gpio_is_valid(template->gpio))
return ERR_PTR(-ENOENT);
// 如果是低电平有效
if (template->active_low)
flags |= GPIOF_ACTIVE_LOW;
// 请求一个 GPIO
ret = devm_gpio_request_one(dev, template->gpio, flags,
template->name);
// 如果请求失败
if (ret < 0)
return ERR_PTR(ret);
// 将 GPIO 转换为描述符
gpiod = gpio_to_desc(template->gpio);
// 如果转换失败
if (!gpiod)
return ERR_PTR(-EINVAL);
return gpiod;
}
// 平台设备的探测函数
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;
// 如果有平台数据且包含 LED 数量
if (pdata && pdata->num_leds) {
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, pdata->num_leds),
GFP_KERNEL);
// 如果分配内存失败
if (!priv)
return -ENOMEM;
priv->num_leds = pdata->num_leds;
for (i = 0; i < priv->num_leds; i++) {
const struct gpio_led *template = &pdata->leds[i];
struct gpio_led_data *led_dat = &priv->leds[i];
// 如果模板中有 gpiod
if (template->gpiod)
led_dat->gpiod = template->gpiod;
else
// 否则获取 gpiod
led_dat->gpiod =
gpio_led_get_gpiod(&pdev->dev,
i, template);
// 如果获取失败
if (IS_ERR(led_dat->gpiod)) {
dev_info(&pdev->dev, "Skipping unavailable LED gpio %d (%s)\n",
template->gpio, template->name);
continue;
}
// 创建 GPIO 控制的 LED 设备
ret = create_gpio_led(template, led_dat,
&pdev->dev, NULL,
pdata->gpio_blink_set);
// 如果创建失败
if (ret < 0)
return ret;
}
} else {
// 创建多个 GPIO 控制的 LED 数据结构
priv = gpio_leds_create(pdev);
// 如果创建失败
if (IS_ERR(priv))
return PTR_ERR(priv);
}
// 设置平台设备的驱动数据
platform_set_drvdata(pdev, priv);
return 0;
}
gpio_led结构体:
/* For the leds-gpio driver */
struct gpio_led {
const char *name;
const char *default_trigger;
unsigned gpio;
unsigned active_low : 1;
unsigned retain_state_suspended : 1;
unsigned panic_indicator : 1;
unsigned default_state : 2;
unsigned retain_state_shutdown : 1;
/* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
struct gpio_desc *gpiod;
};
3. led闪烁
LED并且使能几种触发模式:timer oneshot heartbeat backlight gpio
编译烧写到系统启动就可以看到LED闪烁了。
查看源码 kernel\drivers\leds\trigger\ledtrig-heartbeat.c
#include <linux/module.h> // 包含 Linux 模块相关的头文件
#include <linux/kernel.h> // 包含 Linux 内核相关的头文件
#include <linux/init.h> // 包含 Linux 模块初始化相关的头文件
#include <linux/slab.h> // 包含内核内存分配相关的头文件
#include <linux/timer.h> // 包含 Linux 内核定时器相关的头文件
#include <linux/sched.h> // 包含进程调度相关的头文件
#include <linux/sched/loadavg.h> // 包含负载平均值相关的头文件
#include <linux/leds.h> // 包含 LED 相关的头文件
#include <linux/reboot.h> // 包含重启相关的头文件
#include "../leds.h" // 包含自定义的 LED 相关头文件
static int panic_heartbeats; // 定义一个静态整型变量,用于表示心跳恐慌标志
struct heartbeat_trig_data { // 定义一个结构体,用于存储心跳触发相关的数据
struct led_classdev *led_cdev; // 指向 LED 设备类的指针
unsigned int phase; // 心跳阶段
unsigned int period; // 心跳周期
struct timer_list timer; // 定时器
unsigned int invert; // 反转标志
};
static void led_heartbeat_function(struct timer_list *t) // 定义心跳函数
{
struct heartbeat_trig_data *heartbeat_data = // 从定时器获取心跳数据结构体指针
from_timer(heartbeat_data, t, timer);
struct led_classdev *led_cdev; // 定义 LED 设备类指针
unsigned long brightness = LED_OFF; // 定义亮度为关闭
unsigned long delay = 0; // 定义延迟为 0
led_cdev = heartbeat_data->led_cdev; // 获取 LED 设备类指针
if (unlikely(panic_heartbeats)) { // 如果处于心跳恐慌状态
led_set_brightness_nosleep(led_cdev, LED_OFF); // 设置 LED 亮度为关闭
return; // 返回
}
if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags)) // 测试并清除标志位
led_cdev->blink_brightness = led_cdev->new_blink_brightness; // 更新亮度
/* acts like an actual heart beat -- ie thump-thump-pause... */
switch (heartbeat_data->phase) { // 根据心跳阶段进行处理
case 0: // 阶段 0
/*
* The hyperbolic function below modifies the
* heartbeat period length in dependency of the
* current (1min) load. It goes through the points
* f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
*/
heartbeat_data->period = 300 + // 计算新的心跳周期
(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
heartbeat_data->period = // 将周期转换为节拍
msecs_to_jiffies(heartbeat_data->period);
delay = msecs_to_jiffies(70); // 设置延迟
heartbeat_data->phase++; // 阶段递增
if (!heartbeat_data->invert) // 如果不反转
brightness = led_cdev->blink_brightness; // 设置亮度
break;
case 1: // 阶段 1
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); // 计算延迟
heartbeat_data->phase++; // 阶段递增
if (heartbeat_data->invert) // 如果反转
brightness = led_cdev->blink_brightness; // 设置亮度
break;
case 2: // 阶段 2
delay = msecs_to_jiffies(70); // 设置延迟
heartbeat_data->phase++; // 阶段递增
if (!heartbeat_data->invert) // 如果不反转
brightness = led_cdev->blink_brightness; // 设置亮度
break;
default: // 其他默认情况
delay = heartbeat_data->period - heartbeat_data->period / 4 - // 计算延迟
msecs_to_jiffies(70);
heartbeat_data->phase = 0; // 阶段重置为 0
if (heartbeat_data->invert) // 如果反转
brightness = led_cdev->blink_brightness; // 设置亮度
break;
}
led_set_brightness_nosleep(led_cdev, brightness); // 设置 LED 亮度
mod_timer(&heartbeat_data->timer, jiffies + delay); // 修改定时器
}
static ssize_t led_invert_show(struct device *dev, // 定义显示反转属性的函数
struct device_attribute *attr, char *buf)
{
struct heartbeat_trig_data *heartbeat_data = // 获取心跳数据结构体指针
led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", heartbeat_data->invert); // 将反转标志写入缓冲区并返回长度
}
static ssize_t led_invert_store(struct device *dev, // 定义存储反转属性的函数
struct device_attribute *attr, const char *buf, size_t size)
{
struct heartbeat_trig_data *heartbeat_data = // 获取心跳数据结构体指针
led_trigger_get_drvdata(dev);
unsigned long state; // 定义状态变量
int ret; // 定义返回值变量
ret = kstrtoul(buf, 0, &state); // 将字符串转换为无符号长整型
if (ret) // 如果转换出错
return ret; // 返回错误码
heartbeat_data->invert =!!state; // 设置反转标志
return size; // 返回写入的大小
}
static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); // 定义反转属性
static struct attribute *heartbeat_trig_attrs[] = { // 定义心跳触发属性数组
&dev_attr_invert.attr,
NULL
};
ATTRIBUTE_GROUPS(heartbeat_trig); // 定义属性组
static int heartbeat_trig_activate(struct led_classdev *led_cdev) // 定义激活心跳触发的函数
{
struct heartbeat_trig_data *heartbeat_data; // 定义心跳数据结构体指针
heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL); // 分配内存
if (!heartbeat_data) // 如果分配失败
return -ENOMEM; // 返回内存不足错误码
led_set_trigger_data(led_cdev, heartbeat_data); // 设置 LED 触发数据
heartbeat_data->led_cdev = led_cdev; // 设置 LED 设备类指针
timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0); // 初始化定时器
heartbeat_data->phase = 0; // 初始化阶段为 0
if (!led_cdev->blink_brightness) // 如果亮度未设置
led_cdev->blink_brightness = led_cdev->max_brightness; // 设置为最大亮度
led_heartbeat_function(&heartbeat_data->timer); // 调用心跳函数
set_bit(LED_BLINK_SW, &led_cdev->work_flags); // 设置标志位
return 0; // 返回成功
}
static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) // 定义停用心跳触发的函数
{
struct heartbeat_trig_data *heartbeat_data = // 获取心跳数据结构体指针
led_get_trigger_data(led_cdev);
del_timer_sync(&heartbeat_data->timer); // 同步删除定时器
kfree(heartbeat_data); // 释放内存
clear_bit(LED_BLINK_SW, &led_cdev->work_flags); // 清除标志位
}
static struct led_trigger heartbeat_led_trigger = { // 定义 LED 触发结构体
.name = "heartbeat", // 名称为 "heartbeat"
.activate = heartbeat_trig_activate, // 激活函数指针
.deactivate = heartbeat_trig_deactivate, // 停用函数指针
.groups = heartbeat_trig_groups, // 属性组
};
static int heartbeat_reboot_notifier(struct notifier_block *nb, // 定义重启通知函数
unsigned long code, void *unused)
{
led_trigger_unregister(&heartbeat_led_trigger); // 注销 LED 触发
return NOTIFY_DONE; // 返回通知完成
}
static int heartbeat_panic_notifier(struct notifier_block *nb, // 定义心跳恐慌通知函数
unsigned long code, void *unused)
{
panic_heartbeats = 1; // 设置心跳恐慌标志
return NOTIFY_DONE; // 返回通知完成
}
static struct notifier_block heartbeat_reboot_nb = { // 定义重启通知块
.notifier_call = heartbeat_reboot_notifier, // 通知函数指针
};
static struct notifier_block heartbeat_panic_nb = { // 定义心跳恐慌通知块
.notifier_call = heartbeat_panic_notifier, // 通知函数指针
};
static int __init heartbeat_trig_init(void)
{
int rc = led_trigger_register(&heartbeat_led_trigger);
if (!rc) {
atomic_notifier_chain_register(&panic_notifier_list,
&heartbeat_panic_nb);
register_reboot_notifier(&heartbeat_reboot_nb);
}
return rc;
}
static void __exit heartbeat_trig_exit(void)
{
unregister_reboot_notifier(&heartbeat_reboot_nb);
atomic_notifier_chain_unregister(&panic_notifier_list,
&heartbeat_panic_nb);
led_trigger_unregister(&heartbeat_led_trigger);
}
module_init(heartbeat_trig_init);
module_exit(heartbeat_trig_exit);
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
MODULE_DESCRIPTION("Heartbeat LED trigger");
MODULE_LICENSE("GPL v2");
led_heartbeat_function函数的延时规则:
heartbeat_data->phase 通过这个值的变化来控制
打印如下
[ 34.358286] ---1----phase= 2
[ 34.582286] ---2----phase= 3
[ 34.662286] ---d----phase= 0
[ 35.494286] ---0----phase= 1
[ 35.574286] ---1----phase= 2
[ 35.798286] ---2----phase= 3
[ 35.878287] ---d----phase= 0
[ 36.710286] ---0----phase= 1
[ 36.790286] ---1----phase= 2
[ 37.022286] ---2----phase= 3
[ 37.102286] ---d----phase= 0
[ 37.926287] ---0----phase= 1
[ 38.006287] ---1----phase= 2
[ 38.238286] ---2----phase= 3
[ 38.318286] ---d----phase= 0
[ 39.142286] ---0----phase= 1
[ 39.222286] ---1----phase= 2
[ 39.454286] ---2----phase= 3
[ 39.534286] ---d----phase= 0
[ 40.358286] ---0----phase= 1
[ 40.438287] ---1----phase= 2
[ 40.670286] ---2----phase= 3
[ 40.750286] ---d----phase= 0
[ 41.574286] ---0----phase= 1
[ 41.654286] ---1----phase= 2
[ 41.886286] ---2----phase= 3
[ 41.966286] ---d----phase= 0
[ 42.790286] ---0----phase= 1
[ 42.870286] ---1----phase= 2
[ 43.102286] ---2----phase= 3
[ 43.182286] ---d----phase= 0
[ 44.006286] ---0----phase= 1
[ 44.086286] ---1----phase= 2
[ 44.318286] ---2----phase= 3
[ 44.398286] ---d----phase= 0
[ 45.222286] ---0----phase= 1
[ 45.302286] ---1----phase= 2
[ 45.534286] ---2----phase= 3
[ 45.614286] ---d----phase= 0
三、内核配置
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_ONESHOT=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_BACKLIGHT=y
CONFIG_LEDS_TRIGGER_GPIO=y
四、用户层使用
//10Hz 闪烁
//echo timer > /sys/class/leds/led/trigger
//echo 50> /sys/class/leds/led/delay_on
//echo 50> /sys/class/leds/led/delay_off
改变led 的 显示极性
linux5.10只提供了这样一个接口,修改极性,没有上边的接口
static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
echo 1 > /sys/devices/platform/leds/leds/led/invert