前面文章我们简单给大家介绍了如何移植闪光灯芯片sgm3141,该驱动依赖了led子系统和v4l2子系统。
V4L2可以参考前面camera系列文章,本文主要讲述led子系统。

一、LED子系统框架

Linux内核的 led 子系统主要功能:

  • 为每个设备在/sys/class/leds下创建不同的文件节点,用于操作led
  • 抽象出所有的灯基本操作,设置亮、灭,光强、闪烁等

框架所处的位置,正如上图所示,由下往上看:

  • Hardware:
    硬件设备,指的是LED,可以是各种设备上的led灯

  • 硬件驱动层:
    是直接操作硬件的实现,用于驱动硬件,实现相应的功能,并且将硬件设备注册进框架之中。

  • 核心层:
    将LED进行统一管理,向下提供注册接口,向上提供统一访问接口,方便用户访问

  • 用户层:
    用户通过指定的文件节点,能够直接控制LED的亮灭。

不同的led位于不同的外设上,有的可能通过gpio控制,也可能由其他的芯片控制,
有的led只需要控制亮灭,有的需要设置为闪烁,只需要基于架构设置对应的回调函数即可。

二、LED子系统驱动文件

了解完LED子系统框架之后,我们来分析一下其相关的目录结构!

kernel│ └── driver│ │ └── leds│ │ │ ├──Makefile│ │ │ ├──led-core.c*│ │ │ ├──led-gpio.c│ │ │ ├──led-class.c *│ │ │ ├──led-class-flash.c*│ │ │ ├──led-triggers.c *│ │ │ ├──......│ │ │ └── trigger│ │ │ │ ├── ledtrig-cpu.c│ │ │ │ ├── ledtrig-heartbeat.c│ │ │ │ ├── .......include│ └── linux│ │├──leds.h*【*表示核心文件】

上面即为LED子系统的目录结构,其主要核心文件有:

  • led-core.c:核心层实现,抽象软件实现的相关功能,如闪烁,亮度设置等等,并管理LED设备
  • led-gpio.c:直接控制硬件设备,并且将其硬件设备注册进入LED驱动框架
  • led-class.c:定义用户访问的相关接口
  • led-class-flash.c:灯闪烁相关功能函数实现
  • led-triggers.c:LED出发功能的抽象
  • ledtrig-cpu.c:将LED作为CPU灯
  • ledtrig-heartbeat.c:将LED作为心跳灯

打开了LED子系统目录下的kernel/drivers/leds/Makefile,我们看到

# SPDX-License-Identifier: GPL-2.0# LED Coreobj-$(CONFIG_NEW_LEDS)+= led-core.oobj-$(CONFIG_LEDS_CLASS)+= led-class.oobj-$(CONFIG_LEDS_CLASS_FLASH)+= led-class-flash.oobj-$(CONFIG_LEDS_TRIGGERS)+= led-triggers.o

我们必须在内核的配置中,通过 make menuconfig打开LED的相关配置,才支持LED相关功能。

三、查看sysfs文件结构

1. sys/class/leds/

我们在开发板中输入ls /sys/class/leds/,可以查看LED子系统生成的文件信息。

rk3568_r:/ # ls /sys/class/ledsbluegpio-flashgreenmmc0::red
  • blue:板子的RGB灯的蓝色
  • green:板子的RGB灯的绿色
  • red: 板子的RGB灯的红色
  • gpio-flash:camera gpio闪光灯
  • mmc0:: :SD卡指示灯

2. red等子目录

根据打开配置的不同,生成不同的文件节点,比如red目录下信息:

rk3568_r:/sys/class/leds # ls redbrightnessmax_brightnessred_bri_regsubsystemueventdevicepower red_delaytrigger

相关属性文件有:brightness、max_brightness、trigger等

  1. max_brightness:表示LED灯的最大亮度值。
  2. brightness:表示当前LED灯的亮度值,它的可取 值范围为[0~max_brightness],一些LED设备不支持多级亮度,直接以非0值来 表示LED为点亮状态,0值表示灭状态。
@kernel/include/linux/leds.henum led_brightness { LED_OFF= 0,//全暗 LED_HALF = 127,//一半亮度 LED_FULL = 255,//最大亮度};
  1. delay_off、delay_on:trigger为timer时,LED亮灭的时间,单位ms
  2. trigger:则指示了LED灯的触发方式,查看该文件的内容时,该文件会 列出它的所有可用触方式,而当前使用的触发方式会以“[]”符号括起。

常见的触 发方式如下表所示:

触发方式说明
none无触发方式
disk-activity硬盘活动
nand-disknand flash活动
mtdmtd设备活动
timer定时器
heartbeat系统心跳
1)点亮 LED
echo 255 > /sys/class/leds/red/brightnesscat /sys/class/leds/red/brightnesscat /sys/class/leds/red/max_brightness
2)关闭led
echo 0 > /sys/class/leds/red/delay_on或echo 0 > /sys/class/leds/red/brightness
3)这几个文件节点由下面宏表示,
@drivers/leds/led-class.cstatic DEVICE_ATTR_RO(max_brightness);#ifdef CONFIG_LEDS_TRIGGERSstatic DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);static struct attribute *led_trigger_attrs[] = {&dev_attr_trigger.attr,NULL,};static const struct attribute_group led_trigger_group = {.attrs = led_trigger_attrs,};#endifstatic struct attribute *led_class_attrs[] = {&dev_attr_brightness.attr,&dev_attr_max_brightness.attr,NULL,};static const struct attribute_group led_group = {.attrs = led_class_attrs,};static const struct attribute_group *led_groups[] = {&led_group,#ifdef CONFIG_LEDS_TRIGGERS&led_trigger_group,#endifNULL,};

创建位置:

int of_led_classdev_register(struct device *parent, struct device_node *np,struct led_classdev *led_cdev){……led_cdev->dev = device_create_with_groups(leds_class, parent, 0,led_cdev, led_cdev->groups, "%s", name);……}

3. gpio-flash闪光灯目录

rk3568_r:/sys/class/leds/gpio-flash # lsbrightnessflash_strobe max_brightness powertriggerdeviceflash_timeoutmax_flash_timeoutsubsystemuevent

创建代码:

@drivers/leds/led-class-flash.cstatic struct attribute *led_flash_strobe_attrs[] = {&dev_attr_flash_strobe.attr,NULL,};static struct attribute *led_flash_timeout_attrs[] = {&dev_attr_flash_timeout.attr,&dev_attr_max_flash_timeout.attr,NULL,};static struct attribute *led_flash_brightness_attrs[] = {&dev_attr_flash_brightness.attr,&dev_attr_max_flash_brightness.attr,NULL,};static struct attribute *led_flash_fault_attrs[] = {&dev_attr_flash_fault.attr,NULL,};static const struct attribute_group led_flash_strobe_group = {.attrs = led_flash_strobe_attrs,};static const struct attribute_group led_flash_timeout_group = {.attrs = led_flash_timeout_attrs,};static const struct attribute_group led_flash_brightness_group = {.attrs = led_flash_brightness_attrs,};static const struct attribute_group led_flash_fault_group = {.attrs = led_flash_fault_attrs,};

注册代码

int led_classdev_flash_register(struct device *parent,struct led_classdev_flash *fled_cdev){……if (led_cdev->flags & LED_DEV_CAP_FLASH) {……/* Select the sysfs attributes to be created for the device */led_flash_init_sysfs_groups(fled_cdev);}/* Register led class device */ret = led_classdev_register(parent, led_cdev);……}

测试gpio闪光灯

echo 1 > /sys/class/leds/gpio-flash/flash_strobe

注意,实际操作摄像头闪光灯,并不是通过sysfs下的文件节点操作,而是通过v4l2架构下发ioctl的命令来实现的

四、驱动解析

1. 结构体和注册函数

下面介绍led相关的重要的结构体

struct led_classdev {const char*name;enum led_brightness brightness; //光强enum led_brightness max_brightness; //最大光强int flags;…………/* set_brightness_work / blink_timer flags, atomic, private. */unsigned longwork_flags; …………/* Set LED brightness level * Must not sleep. Use brightness_set_blocking for drivers * that can sleep while setting brightness. */void(*brightness_set)(struct led_classdev *led_cdev,enum led_brightness brightness);//设置光强/* * Set LED brightness level immediately - it can block the caller for * the time required for accessing a LED device register. */int (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness);/* Get LED brightness level */enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); //获取光强/* * Activate hardware accelerated blink, delays are in milliseconds * and if both are zero then a sensible default should be chosen. * The call should adjust the timings in that case and if it can't * match the values specified exactly. * Deactivate blinking again when the brightness is set to LED_OFF * via the brightness_set() callback. */int(*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);struct device*dev;const struct attribute_group**groups;struct list_head node;/* LED Device list */const char*default_trigger;/* Trigger to use */unsigned long blink_delay_on, blink_delay_off;struct timer_list blink_timer;int blink_brightness;int new_blink_brightness;void(*flash_resume)(struct led_classdev *led_cdev);struct work_structset_brightness_work;intdelayed_set_value;#ifdef CONFIG_LEDS_TRIGGERS/* Protects the trigger data below */struct rw_semaphore trigger_lock;struct led_trigger*trigger;struct list_head trig_list;void*trigger_data;/* true if activated - deactivate routine uses it to do cleanup */boolactivated;#endif#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGEDint brightness_hw_changed;struct kernfs_node*brightness_hw_changed_kn;#endif/* Ensures consistent access to the LED Flash Class device */struct mutexled_access;};

该结构体包括led操作的所有信息,和回调函数

注册struct led_classdev结构图变量:

#define led_classdev_register(parent, led_cdev)\of_led_classdev_register(parent, NULL, led_cdev)

对于gpio闪光灯,则需要填充一下结构体:

struct led_classdev_flash {/* led class device */struct led_classdev led_cdev;/* flash led specific ops */const struct led_flash_ops *ops;/* flash brightness value in microamperes along with its constraints */struct led_flash_setting brightness;/* flash timeout value in microseconds along with its constraints */struct led_flash_setting timeout;/* LED Flash class sysfs groups */const struct attribute_group *sysfs_groups[LED_FLASH_SYSFS_GROUPS_SIZE];};

gpio闪光灯注册函数:

int led_classdev_flash_register(struct device *parent,struct led_classdev_flash *fled_cdev)

2. gpio闪光灯sgm3141驱动详解


看上图:

  1. sgm3141驱动通过函数led_classdev_flash_register()->led_classdev_register()向led子系统注册该设备
  2. sgm3141驱动通过函数v4l2_async_register_subdev()向v4l2子系统注册该设备
  3. 如果用户直接通过/sys/class/leds/gpio-flash/flash_strobe文件操作led灯,则会直接调用struct led_flash_ops flash_ops的 .strobe_set方法,即sgm3141_led_flash_strobe_set()

操作log:

[492.026391] sgm3141_led_flash_strobe_set+0x24/0x78[492.026453] flash_strobe_store+0x88/0xd8[492.026517] dev_attr_store+0x18/0x28[492.026571] sysfs_kf_write+0x48/0x58[492.026620] kernfs_fop_write+0xf4/0x220 [492.026683] __vfs_write+0x34/0x158[492.026733] vfs_write+0xb0/0x1d0[492.026784] ksys_write+0x64/0xe0[492.026833] __arm64_sys_write+0x14/0x20 [492.026867] el0_svc_common.constprop.0+0x64/0x178 [492.026912] el0_svc_handler+0x28/0x78 [492.026966] el0_svc+0x8/0xc 
  1. 如果用户的app拍照时操作闪光灯,则是通过v4l2子系统调用下发ioctl命令
    命令序列:
V4L2_CID_FLASH_LED_MODE :设置led mod为 V4L2_FLASH_LED_MODE_TORCH(2),并点灯V4L2_CID_FLASH_LED_MODE:到达指定超时时间(2.7秒),设置led mod为 V4L2_FLASH_LED_MODE_NONE 0V4L2_CID_FLASH_LED_MODE:在此设置led mod为V4L2_FLASH_LED_MODE_FLASH(1)V4L2_CID_FLASH_STROBE_STOP:停止闪光

操作log:

[ 90.246203] sgm3141 V4L2_CID_FLASH_LED_MODE 2[ 90.246251] sgm3141_set_ctrl(),376[ 90.246262] sgm3141_set_output(),78 0[ 90.246277] sgm3141_set_output(),78 1[ 92.902746] sgm3141 V4L2_CID_FLASH_LED_MODE 0[ 92.902775] sgm3141_set_ctrl(),376[ 92.902781] sgm3141_set_output(),78 0[ 93.034903] sgm3141 V4L2_CID_FLASH_LED_MODE 1[ 93.034929] sgm3141_set_ctrl(),376[ 93.034934] sgm3141_set_output(),78 0[ 93.034943] sgm3141_led_flash_strobe_set(),166 state=1[ 93.034959] sgm3141_set_output(),78 1[ 93.034977] sgm3141 V4L2_CID_FLASH_STROBE_STOP 1[ 93.034988] sgm3141_set_ctrl(),406[ 93.034993] sgm3141_led_flash_strobe_set(),166 state=0[ 93.035002] sgm3141_set_output(),78 0[ 93.035058] sgm3141_timeout_work(),117
  1. sgm驱动注册流程分析
    驱动架构基于platform总线,platform_driver 结构体如下:
static const struct of_device_id sgm3141_led_dt_match[] = {{ .compatible = "sgmicro,sgm3141" },{},};MODULE_DEVICE_TABLE(of, sgm3141_led_dt_match);static struct platform_driver sgm3141_led_driver = {.probe= sgm3141_led_probe,.remove= sgm3141_led_remove,.driver= {.name= "sgm3141-flash",.of_match_table = sgm3141_led_dt_match,},};

点击下面图标,
扫描二维码,关注一口君
学习嵌入式、驱动、Linux