linux--LED子系统一文读懂

描述

Led子系统框架

Linux

在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 下展示该子系统设备,每个设备都有自己的属性:

Linux

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

enum led_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
struct led_classdev {
  const char  *name;//名字
  enum led_brightness  brightness;//亮度
  enum led_brightness  max_brightness;//最大亮度
  int    flags;

  /* Lower 16 bits reflect status */
 #define LED_SUSPENDED  (1 << 0)
  /* Upper 16 bits reflect control information */
 #define LED_CORE_SUSPENDRESUME (1 << 16)
 #define LED_BLINK_ONESHOT (1 << 17)
 #define LED_BLINK_ONESHOT_STOP (1 << 18)
 #define LED_BLINK_INVERT (1 << 19)
 #define LED_SYSFS_DISABLE (1 << 20)
 #define SET_BRIGHTNESS_ASYNC (1 << 21)
 #define SET_BRIGHTNESS_SYNC (1 << 22)
 #define LED_DEV_CAP_FLASH (1 << 23)

 //设置亮度API
  void  (*brightness_set)(struct led_classdev *led_cdev,enum led_brightness brightness);
  int  (*brightness_set_sync)(struct led_classdev *led_cdev,enum led_brightness brightness);

 //获取亮度API
  enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

 //闪烁时点亮和熄灭的时间设置
  int  (*blink_set)(struct led_classdev *led_cdev,unsigned long *delay_on,unsigned long *delay_off);

  struct device  *dev;
  const struct attribute_group **groups;

 //leds-list的node
  struct list_head  node;
 //默认trigger的名字
  const char  *default_trigger;
 //闪烁的开关时间
  unsigned long   blink_delay_on, blink_delay_off;
 //闪烁的定时器链表
  struct timer_list  blink_timer;
 //闪烁的亮度
  int    blink_brightness;
  void   (*flash_resume)(struct led_classdev *led_cdev);

  struct work_struct set_brightness_work;
  int   delayed_set_value;

 #ifdef CONFIG_LEDS_TRIGGERS
 //trigger的锁
  struct rw_semaphore  trigger_lock;
 //led的trigger
  struct led_trigger *trigger;
 //trigger的链表
  struct list_head  trig_list;
 //trigger的数据
  void   *trigger_data;
  bool   activated;
 #endif
  struct mutex  led_access;
};
结构体:gpio_led
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;
 /* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
 struct gpio_desc *gpiod;
};
  • 其中:
    • name: led名字
    • default_trigger: LED 灯在Linux 系统中的默认功能,比如作为系统心跳指示灯等等。
    • default_state: 默认状态,如:
#define LEDS_GPIO_DEFSTATE_OFF  0
#define LEDS_GPIO_DEFSTATE_ON  1
#define LEDS_GPIO_DEFSTATE_KEEP  2

Led子系统LED_GPIO

LED_GPIO驱动使能
  • 在内核源码中进入menuconfig
-> Device Drivers
 -> LED Support (NEW_LEDS [=y])
  ->LED Support for GPIO connected LEDs

Linux

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

Linux

Linux自带LED_GPIO驱动

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

Linux

  • 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);
  • LED 驱动的匹配表,此表只有一个匹配项,compatible内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。
gpio_led_probe 函数简析
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) {
  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];

   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, "Skipping unavailable LED gpio %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)
    return ret;
  }
 } else {
  priv = gpio_leds_create(pdev);
  if (IS_ERR(priv))
   return PTR_ERR(priv);
 }

 platform_set_drvdata(pdev, priv);

 return 0;
}
  • 进入probe函数,pdata此时为空,进入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, struct_size(priv, leds, 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;

  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;
  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;
}
  • 函数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 函数简析
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_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;
 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)
  return ret;

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

 return ret;
}
  • 函数create_gpio_led描述:
    • 先获取gpiod信息
    • 配置led_dat属性,包括default_state, brightness等信息
    • 配置gpio方向
    • 将led注册给LED子系统
gpio_led_set函数简析
static void gpio_led_set(struct led_classdev *led_cdev,
 enum led_brightness value)
{
 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,
       NULLNULL);
  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

Linux

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

Linux

 

Linux

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

Linux

 

Linux

  • 闪烁LED-GPIO--输入:
# echo timer > /sys/class/leds/blue1/trigger
# echo 100 > /sys/class/leds/blue1/delay_on
# echo 200 > /sys/class/leds/blue1/delay_off

Linux


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

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分