前面文章我们简单给大家介绍了如何移植闪光灯芯片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?Core
obj-$(CONFIG_NEW_LEDS)+=?led-core.o
obj-$(CONFIG_LEDS_CLASS)+=?led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH)+=?led-class-flash.o
obj-$(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/leds
blue??gpio-flash??green??mmc0::??red??
- blue:板子的RGB灯的蓝色green:板子的RGB灯的绿色red: 板子的RGB灯的红色gpio-flash:camera gpio闪光灯mmc0:: :SD卡指示灯
2. red等子目录
根据打开配置的不同,生成不同的文件节点,比如red目录下信息:
rk3568_r:/sys/class/leds?#?ls?red
brightness??max_brightness??red_bri_reg??subsystem??uevent
device??????power???????????red_delay????trigger
相关属性文件有:brightness、max_brightness、trigger等
- max_brightness:表示LED灯的最大亮度值。brightness:表示当前LED灯的亮度值,它的可取 值范围为[0~max_brightness],一些LED设备不支持多级亮度,直接以非0值来 表示LED为点亮状态,0值表示灭状态。
@kernel/include/linux/leds.h
enum?led_brightness?{
?LED_OFF??=?0,????//全暗
?LED_HALF?=?127,??//一半亮度
?LED_FULL?=?255,??//最大亮度
};
- delay_off、delay_on:trigger为timer时,LED亮灭的时间,单位mstrigger:则指示了LED灯的触发方式,查看该文件的内容时,该文件会 列出它的所有可用触方式,而当前使用的触发方式会以“[]”符号括起。
常见的触 发方式如下表所示:
| 触发方式 | 说明 |
|---|---|
| none | 无触发方式 |
| disk-activity | 硬盘活动 |
| nand-disk | nand flash活动 |
| mtd | mtd设备活动 |
| timer | 定时器 |
| heartbeat | 系统心跳 |
1)点亮 LED
echo?255?>?/sys/class/leds/red/brightness
cat?/sys/class/leds/red/brightness
cat?/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.c
static?DEVICE_ATTR_RO(max_brightness);
#ifdef?CONFIG_LEDS_TRIGGERS
static?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,
};
#endif
static?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,
#endif
?NULL,
};
创建位置:
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?#?ls
brightness??flash_strobe???max_brightness?????power??????trigger
device??????flash_timeout??max_flash_timeout??subsystem??uevent
创建代码:
@drivers/leds/led-class-flash.c
static?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?long??work_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_struct?set_brightness_work;
?int???delayed_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?*/
?bool???activated;
#endif
#ifdef?CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
?int????brightness_hw_changed;
?struct?kernfs_node?*brightness_hw_changed_kn;
#endif
?/*?Ensures?consistent?access?to?the?LED?Flash?Class?device?*/
?struct?mutex??led_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驱动详解
看上图:
- sgm3141驱动通过函数led_classdev_flash_register()->led_classdev_register()向led子系统注册该设备sgm3141驱动通过函数v4l2_async_register_subdev()向v4l2子系统注册该设备如果用户直接通过/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?
-
- 如果用户的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 0
V4L2_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
-
- 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,
?},
};
1257