0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

linux--LED子系统一文读懂

Rice嵌入式开发技术分享 来源:Rice嵌入式 作者:Rice嵌入式 2024-08-02 16:09 次阅读

Led子系统框架

52b284d8-4ed6-11ef-a4b4-92fbcf53809c.png

Linux内核中,LED子系统扮演着控制LED灯的核心角色,它通过一套规范化的驱动架构,简化了LED驱动程序的开发流程,让开发者能够更专注于功能实现而非硬件层面的复杂性。
核心架构

LED子系统基于一个统一的驱动模型,这个模型定义了一系列标准的应用程序接口(API),用以执行LED的基本操作,如点亮、熄灭和调节闪烁频率。此外,该模型还规定了设备树节点的格式标准,使得硬件信息的描述变得规范化和统一化。

驱动类型

LED驱动程序主要分为两大类:

  • LED Class驱动:这是一种普适性的驱动实现,能够跨多种硬件平台工作,支持不同类型的LED。
  • Platform驱动:与特定硬件平台紧密相关,需要根据平台特性定制开发。
开发过程

开发者在编写LED驱动时,需要遵循LED子系统定义的接口规范,实现包括但不限于以下几个关键函数:

  • probe接口:初始化驱动,准备硬件资源。
  • remove接口:卸载驱动,释放资源。
  • set_brightness接口:调整LED的亮度。

同时,硬件相关的信息,如GPIO配置和亮度调节范围,需要在设备树中明确描述,以便驱动程序能够正确地识别和使用这些硬件资源。

设备树集成

设备树的使用,为硬件描述提供了一种结构化的方法,使得LED子系统的驱动程序能够更加灵活地适应不同的硬件环境。

总结

LED子系统通过其标准化的驱动框架,极大地降低了LED驱动程序的开发难度,让开发者可以更加便捷地实现LED控制功能,而无需深入了解底层硬件的具体实现。

Led子系统描述

Led子系统相关描述可在内核源码中的文档中查看:Documentation/leds/leds-class.txt。

led 子系统是一个简单的 Linux 子系统 ,在目录 /sys/class/leds 下展示该子系统设备,每个设备都有自己的属性:

52b9f114-4ed6-11ef-a4b4-92fbcf53809c.png

brightness:设置 LED 亮度,范围0~max_brightness
max_brightness:最大亮度(255或其他数字)
trigger:触发方式,如 heartbeat、mmc0、backlight、gpio
delay_off、delay_on:trigger为timer时,LED亮灭的时间,单位ms

Led子系统头文件:kernel/include/linux/leds.h

enumled_brightness{
LED_OFF=0,//全暗
LED_HALF=127,//一半亮度
LED_FULL=255,//最大亮度
};

Led子系统框架分析

Led子系统框架代码分析

led-class.c
  • led-class.c:led子系统框架的入口
  • 维护 LED 子系统的所有 LED 设备,为 LED 设备提供注册操作函数:
    • led_classdev_register()
    • devm_led_classdev_register()
  • 注销操作函数:
    • led_classdev_unregister()
    • devm_led_classdev_unregister()
  • 电源管理的休眠和恢复操作函数:
    • led_classdev_suspend()
    • led_classdev_resume();
  • 用户态操作接口:brightness 、max_brightness
led-core.c
  • 抽象出 LED 操作逻辑,封装成函数导出,供其它文件使用:
    • 核心初始化:led_init_core()
    • 设置led闪烁时间:led_blink_set()
    • 闪烁一次:led_blink_set_oneshot()
    • led停止闪烁:led_stop_software_blink()
    • 设置led的亮度:led_set_brightness()
    • 更新亮度:led_update_brightness
    • 用户态关闭:led_sysfs_disable
    • 用户态打开:led_sysfs enable
    • leds链表:leds_list
    • leds链表锁:leds_list_lock
led-triggers.c
  • 维护 LED 子系统的所有触发器,为触发器提供注册操作函数:

    • led_trigger_register()
    • devm_led_trigger_register()
    • led_trigger_register_simple()
  • 注销操作函数:

    • led_trigger_unregister()
    • led_trigger_unregister_simple()
  • 以及其它触发器相关的操作函数

ledtrig-timer.c、ledtrig-xxx.c
  • 以 leds-gpio.c 为例:
  1. 通过设备树匹配到设备信息后,将调用 probe() 函数,
  2. 根据设备信息设置led_classdev,
  3. 调用 devm_led_classdev_register() 注册 LED 设备。

Led子系统框架结构体分析

结构体:led_classdev
structled_classdev{
constchar*name;//名字
enumled_brightnessbrightness;//亮度
enumled_brightnessmax_brightness;//最大亮度
intflags;

/*Lower16bitsreflectstatus*/
#defineLED_SUSPENDED(1<< 0)
/*Upper16bitsreflectcontrolinformation*/
#defineLED_CORE_SUSPENDRESUME(1<< 16)
#defineLED_BLINK_ONESHOT(1<< 17)
#defineLED_BLINK_ONESHOT_STOP(1<< 18)
#defineLED_BLINK_INVERT(1<< 19)
#defineLED_SYSFS_DISABLE(1<< 20)
#defineSET_BRIGHTNESS_ASYNC(1<< 21)
#defineSET_BRIGHTNESS_SYNC(1<< 22)
#defineLED_DEV_CAP_FLASH(1<< 23)

//设置亮度API
void(*brightness_set)(structled_classdev*led_cdev,enumled_brightnessbrightness);
int(*brightness_set_sync)(structled_classdev*led_cdev,enumled_brightnessbrightness);

//获取亮度API
enumled_brightness(*brightness_get)(structled_classdev*led_cdev);

//闪烁时点亮和熄灭的时间设置
int(*blink_set)(structled_classdev*led_cdev,unsignedlong*delay_on,unsignedlong*delay_off);

structdevice*dev;
conststructattribute_group**groups;

//leds-list的node
structlist_headnode;
//默认trigger的名字
constchar*default_trigger;
//闪烁的开关时间
unsignedlongblink_delay_on,blink_delay_off;
//闪烁的定时器链表
structtimer_listblink_timer;
//闪烁的亮度
intblink_brightness;
void(*flash_resume)(structled_classdev*led_cdev);

structwork_structset_brightness_work;
intdelayed_set_value;

#ifdefCONFIG_LEDS_TRIGGERS
//trigger的锁
structrw_semaphoretrigger_lock;
//led的trigger
structled_trigger*trigger;
//trigger的链表
structlist_headtrig_list;
//trigger的数据
void*trigger_data;
boolactivated;
#endif
structmutexled_access;
};
结构体:gpio_led
structgpio_led{
constchar*name;
constchar*default_trigger;
unsignedgpio;
unsignedactive_low:1;
unsignedretain_state_suspended:1;
unsignedpanic_indicator:1;
unsigneddefault_state:2;
/*default_stateshouldbeoneofLEDS_GPIO_DEFSTATE_(ON|OFF|KEEP)*/
structgpio_desc*gpiod;
};
  • 其中:
    • name: led名字
    • default_trigger: LED 灯在Linux 系统中的默认功能,比如作为系统心跳指示灯等等。
    • default_state: 默认状态,如:
#defineLEDS_GPIO_DEFSTATE_OFF0
#defineLEDS_GPIO_DEFSTATE_ON1
#defineLEDS_GPIO_DEFSTATE_KEEP2

Led子系统LED_GPIO

LED_GPIO驱动使能
  • 在内核源码中进入menuconfig
->DeviceDrivers
->LEDSupport(NEW_LEDS[=y])
->LEDSupportforGPIOconnectedLEDs

52bdba92-4ed6-11ef-a4b4-92fbcf53809c.png

可以看出,把Linux内部自带的LED灯驱动编译进内核以后,CONFIG_LEDS_GPIO就会等于‘y’:

52c6bc5a-4ed6-11ef-a4b4-92fbcf53809c.png

Linux自带LED_GPIO驱动

LED_GPIO灯驱动文件为/drivers/leds/leds-gpio.c,可以通过makefile文件查看:/drivers/leds/Makefile:

52d0c95c-4ed6-11ef-a4b4-92fbcf53809c.png

  • leds-gpio.c驱动文件:
staticconststructof_device_idof_gpio_leds_match[]={
{.compatible="gpio-leds",},
{},
};
......
staticstructplatform_drivergpio_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);
  • LED 驱动的匹配表,此表只有一个匹配项,compatible内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。
gpio_led_probe 函数简析
staticintgpio_led_probe(structplatform_device*pdev)
{
structgpio_led_platform_data*pdata=dev_get_platdata(&pdev->dev);
structgpio_leds_priv*priv;
inti,ret=0;

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++){
conststructgpio_led*template=&pdata->leds[i];
structgpio_led_data*led_dat=&priv->leds[i];

if(template->gpiod)
led_dat->gpiod=template->gpiod;
else
led_dat->gpiod=
gpio_led_get_gpiod(&pdev->dev,
i,template);
if(IS_ERR(led_dat->gpiod)){
dev_info(&pdev->dev,"SkippingunavailableLEDgpio%d(%s)n",
template->gpio,template->name);
continue;
}

ret=create_gpio_led(template,led_dat,
&pdev->dev,NULL,
pdata->gpio_blink_set);
if(ret< 0)
returnret;
}
}else{
priv=gpio_leds_create(pdev);
if(IS_ERR(priv))
returnPTR_ERR(priv);
}

platform_set_drvdata(pdev,priv);

return0;
}
  • 进入probe函数,pdata此时为空,进入gpio_leds_create:
staticstructgpio_leds_priv*gpio_leds_create(structplatform_device*pdev)
{
structdevice*dev=&pdev->dev;
structfwnode_handle*child;
structgpio_leds_priv*priv;
intcount,ret;

count=device_get_child_node_count(dev);
if(!count)
returnERR_PTR(-ENODEV);

priv=devm_kzalloc(dev,struct_size(priv,leds,count),GFP_KERNEL);
if(!priv)
returnERR_PTR(-ENOMEM);

device_for_each_child_node(dev,child){
structgpio_led_data*led_dat=&priv->leds[priv->num_leds];
structgpio_ledled={};
constchar*state=NULL;

/*
*AcquiregpiodfromDTwithuninitializedlabel,which
*willbeupdatedafterLEDclassdeviceisregistered,
*OnlythenthefinalLEDnameisknown.
*/
led.gpiod=devm_fwnode_get_gpiod_from_child(dev,NULL,child,
GPIOD_ASIS,
NULL);
if(IS_ERR(led.gpiod)){
fwnode_handle_put(child);
returnERR_CAST(led.gpiod);
}

led_dat->gpiod=led.gpiod;

if(!fwnode_property_read_string(child,"default-state",
&state)){
if(!strcmp(state,"keep"))
led.default_state=LEDS_GPIO_DEFSTATE_KEEP;
elseif(!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,child,NULL);
if(ret< 0){
fwnode_handle_put(child);
returnERR_PTR(ret);
}
/*SetgpiodlabeltomatchthecorrespondingLEDname.*/
gpiod_set_consumer_name(led_dat->gpiod,
led_dat->cdev.dev->kobj.name);
priv->num_leds++;
}

returnpriv;
}
  • 函数gpio_leds_create描述:
  1. 调用device_get_child_node_count函数统计子节点数量,一般在在设备树中创建一个节点表示LED灯,然后在这个节点下面为每个LED灯创建一个子节点。因此子节点数量也是LED灯的数量。
  2. 遍历每个子节点,获取每个子节点的信息:
  • devm_get_gpiod_from_child获取每个gpio灯的gpio_desc信息。
  • 获取label属性,label作为led的名字
  • 获取“linux,default-trigger”属性,可以通过此属性设置某个 LED 灯在Linux 系统中的默认功能,比如作为系统心跳指示灯等等。
  • 获取“default-state”属性值,也就是 LED 灯的默认状态属性
  • create_gpio_led 函数创建 LED 相关的 io,常用gpio操作
create_gpio_led 函数简析
staticintcreate_gpio_led(conststructgpio_led*template,
structgpio_led_data*led_dat,structdevice*parent,
structfwnode_handle*fwnode,gpio_blink_set_tblink_set)
{
structled_init_datainit_data={};
intret,state;

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)
returnstate;
}else{
state=(template->default_state==LEDS_GPIO_DEFSTATE_ON);
}
led_dat->cdev.brightness=state?LED_FULL:LED_OFF;
if(!template->retain_state_suspended)
led_dat->cdev.flags|=LED_CORE_SUSPENDRESUME;
if(template->panic_indicator)
led_dat->cdev.flags|=LED_PANIC_INDICATOR;
if(template->retain_state_shutdown)
led_dat->cdev.flags|=LED_RETAIN_AT_SHUTDOWN;

ret=gpiod_direction_output(led_dat->gpiod,state);
if(ret< 0)
returnret;

if(template->name){
led_dat->cdev.name=template->name;
ret=devm_led_classdev_register(parent,&led_dat->cdev);
}else{
init_data.fwnode=fwnode;
ret=devm_led_classdev_register_ext(parent,&led_dat->cdev,
&init_data);
}

returnret;
}
  • 函数create_gpio_led描述:
    • 先获取gpiod信息
    • 配置led_dat属性,包括default_state, brightness等信息
    • 配置gpio方向
    • 将led注册给LED子系统
gpio_led_set函数简析
staticvoidgpio_led_set(structled_classdev*led_cdev,
enumled_brightnessvalue)
{
structgpio_led_data*led_dat=cdev_to_gpio_led_data(led_cdev);
intlevel;

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);
}
}
  • 函数gpio_led_set描述:
    • 先获取led_gpio信息
    • 获取led 的brightness的value
    • 通过gpiod_set_value设置gpio的电平
以YY3568为例YY3568开发板,板载两颗蓝色LED灯供用户使用,我们将其命名为blue1blue2

52d8d8c2-4ed6-11ef-a4b4-92fbcf53809c.png

DTS编写

gpio_leds:gpio-leds{
compatible="gpio-leds";
led@1{
gpios=<&gpio3 RK_PA4 GPIO_ACTIVE_HIGH>;
label="blue1";//Blue1
retain-state-suspended;
};

led@2{
gpios=<&gpio2 RK_PB2 GPIO_ACTIVE_HIGH>;
label="blue2";//Blue2
retain-state-suspended;
};
};
  • 创建一个节点表示 LED 灯设备,比如gpio_leds,如果板子上有多个 LED 灯的话每个 LED灯都作为gpio_leds的子节点。
  • gpio_leds节点的 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”属性值,可以设置为 on、off 或 keep,为 on 的时候 LED 灯默认打开,为 off 的话 LED 灯默认关闭,为 keep 的话 LED 灯保持当前模式。

基于Sysfs操作Led-gpio

关于sysfs相关文章:linux--sysfs文件系统

  • 点亮LED-GPIO--输入:echo 255 > /sys/class/leds/blue1/brightness

52e489ec-4ed6-11ef-a4b4-92fbcf53809c.png

52ee760a-4ed6-11ef-a4b4-92fbcf53809c.png

  • 关闭LED-GPIO--输入:echo 0 > /sys/class/leds/blue1/brightness

52f5e37c-4ed6-11ef-a4b4-92fbcf53809c.png

52fbc468-4ed6-11ef-a4b4-92fbcf53809c.png

  • 闪烁LED-GPIO--输入:
#echotimer>/sys/class/leds/blue1/trigger
#echo100>/sys/class/leds/blue1/delay_on
#echo200>/sys/class/leds/blue1/delay_off

5302ead6-4ed6-11ef-a4b4-92fbcf53809c.png


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • led
    led
    +关注

    关注

    239

    文章

    22879

    浏览量

    649624
  • Linux
    +关注

    关注

    87

    文章

    11116

    浏览量

    207772
  • 子系统
    +关注

    关注

    0

    文章

    108

    浏览量

    12336
收藏 人收藏

    评论

    相关推荐

    Linux驱动分析之input子系统

    Linux内核为了能够处理各种不同类型的输入设备,比如: 触摸屏 ,鼠标 , 键盘 , 操纵杆等设备 ,设计并实现了Linux 输入子系统 ,它为驱动和应用提供了统一的接口函数,方便实
    发表于 02-01 10:38 473次阅读

    搞懂Linux pinctrl/gpio子系统

    注: 所有文章基于linux-3.13以上,本系列主要介绍 GPIO的些基本知识,驱动操作GPIO的接口,应用层通过sysfs操作GPIO的接口,GPIO些debug信息查看,以及对高通相关
    发表于 06-09 09:52 2241次阅读

    Linux LED子系统详解

    Linux LED子系统详解
    的头像 发表于 06-10 10:37 1273次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>LED</b><b class='flag-5'>子系统</b>详解

    读懂接口模块的组合应用有哪些?

    读懂接口模块的组合应用有哪些?
    发表于 05-17 07:15

    读懂什么是NEC协议

    读懂什么是NEC协议?
    发表于 10-15 09:22

    Linux内核输入子系统的驱动研究

    Linux内核输入子系统的驱动研究
    发表于 10-31 14:41 14次下载
    <b class='flag-5'>Linux</b>内核输入<b class='flag-5'>子系统</b>的驱动研究

    读懂ARM微处理器指令系统

    叫你如何读懂ARM微处理器指令系统
    发表于 03-26 14:30 53次下载

    Windows 子系统助力 Linux 2.0

    Windows 子系统助力 Linux 2.0
    的头像 发表于 01-04 11:17 547次阅读

    Linux系统中NFC子系统架构分析

    目前在Linux系统中,每个厂家都使用不同的方式实现NFC驱动,然后自己在应用层上面做适配。但是Linux也已经推出NFC子系统,很多厂家也逐步在
    发表于 01-04 14:01 1749次阅读

    Linux Led子系统代码框架分析

    代码框架分析 led-class.c (led 子系统框架的入口) 维护 LED 子系统的所有 LED
    的头像 发表于 07-20 10:36 605次阅读

    Linux内核之LED子系统

    Linux内核的LED子系统种重要的框架,用于管理和控制设备上的LED指示灯。在嵌入式系统
    发表于 10-02 16:53 831次阅读
    <b class='flag-5'>Linux</b>内核之<b class='flag-5'>LED</b><b class='flag-5'>子系统</b>(<b class='flag-5'>一</b>)

    Linux内核之LED子系统(二)

    这里说LED子系统些核心源代码文件,是如何实现LED子系统
    发表于 10-02 16:55 572次阅读
    <b class='flag-5'>Linux</b>内核之<b class='flag-5'>LED</b><b class='flag-5'>子系统</b>(二)

    Linux reset子系统有什么功能

    Linux reset子系统 reset子系统非常简单,与clock子系统非常类似,但在驱动实现上,reset驱动更简单。 因为clock驱动主要是时钟的实现,涉及到固定时钟、分频、门
    的头像 发表于 09-27 14:06 606次阅读
    <b class='flag-5'>Linux</b> reset<b class='flag-5'>子系统</b>有什么功能

    Linux网络子系统的实现

    Linux网络子系统的分层 Linux网络子系统实现需要: l 支持不同的协议族 ( INET, INET6, UNIX, NETLINK...) l 支持不同的网络设备 l 支持
    的头像 发表于 11-11 11:25 1003次阅读
    <b class='flag-5'>Linux</b>网络<b class='flag-5'>子系统</b>的实现

    读懂,什么是BLE?

    读懂,什么是BLE?
    的头像 发表于 11-27 17:11 1788次阅读
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>读懂</b>,什么是BLE?