Camera | 10.linux led架構-基於rk3568

一口linux 發佈 2023-05-31T16:12:06.677848+00:00

前面文章我們簡單給大家介紹了如何移植閃光燈晶片sgm3141,該驅動依賴了led子系統和v4l2子系統。 V4L2可以參考前面camera系列文章,本文主要講述led子系統。

前面文章我們簡單給大家介紹了如何移植閃光燈晶片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等

  1. max_brightness:表示LED燈的最大亮度值。
  2. 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,  //最大亮度
};
  1. delay_off、delay_on:trigger為timer時,LED亮滅的時間,單位ms
  2. trigger:則指示了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驅動詳解

看上圖:

  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 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
  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,
 },
};
關鍵字: