MTK LED驱动节点创建和读写流程

简介

本文分析MTK平台对LED(指示灯)驱动节点部分的创建、处理流程。MTK Android平台遵循Linux的规范惯例,和常规的Linux一样,将发光的二极管等设备抽象为一个sysfs class(驱动里面的一种类),诸如指示灯、LED灯、背光等都属于这一类,因此都在同一个class(/sys/class/leds)下管理。MTK平台在该目录下提供了对LED进行操作的驱动节点。

其中,和常规的Linux一样,也是通过trigger节点来控制LED灯的触发。

javascript 复制代码
android:/ # ls /sys/class/leds/*
/sys/class/leds/blue:
brightness device max_brightness power subsystem trigger uevent 

/sys/class/leds/green:
brightness device max_brightness power subsystem trigger uevent 

/sys/class/leds/lcd-backlight:
brightness device max_brightness power subsystem trigger uevent 

/sys/class/leds/red:
brightness device max_brightness power subsystem trigger uevent 

/sys/class/leds/vibrator:
activate brightness device duration max_brightness power state subsystem trigger uevent

Linux标准规范下对LED设备节点名称的命名格式为:devicename:colour:function,而MTK Android平台不遵循这一规则,通常直接用简单直观的名字命名。

1. LED模式、驱动方式和基本数据结构

对于MTK Android平台各个LED驱动节点而言,设备中各LED均按照一个既定的类型和模式来管理并驱动。对于一个LED设备驱动而言,指定了其类型、模式和输出方式,就是完整的一个LED控制方法的配置了。

MTK Android平台将设备中对LED归类为如下几种类型,主要是RGB三色灯、键盘灯按键灯、LCD背光等。

ini 复制代码
enum mt65xx_led_type            
{
        MT65XX_LED_TYPE_RED = 0,
        MT65XX_LED_TYPE_GREEN,
        MT65XX_LED_TYPE_BLUE,
        MT65XX_LED_TYPE_JOGBALL,
        MT65XX_LED_TYPE_KEYBOARD,
        MT65XX_LED_TYPE_BUTTON,
        MT65XX_LED_TYPE_LCD,
        MT65XX_LED_TYPE_TOTAL,
};

而驱动这些能发光的器件,主要是通过GPIO输出、PWM输出、借助PMIC(iSink)输出等几种方式。

ini 复制代码
enum mt65xx_led_mode
{
        MT65XX_LED_MODE_NONE,
        MT65XX_LED_MODE_PWM,
        MT65XX_LED_MODE_GPIO,
        MT65XX_LED_MODE_PMIC,
        MT65XX_LED_MODE_CUST_LCM,
        MT65XX_LED_MODE_CUST_BLS_PWM
};

// PMIC iSink的输出方式
enum mt65xx_led_pmic
{
        MT65XX_LED_PMIC_LCD_ISINK = 0,
        MT65XX_LED_PMIC_NLED_ISINK_MIN = MT65XX_LED_PMIC_LCD_ISINK,
        MT65XX_LED_PMIC_NLED_ISINK0,
        MT65XX_LED_PMIC_NLED_ISINK1,
        MT65XX_LED_PMIC_NLED_ISINK_MAX,
};

// PWM方式的定义
struct PWM_config
{
        int clock_source;
        int div;
        int low_duration;
        int High_duration;
        BOOL pmic_pad;
};

LED设备配置模式的一个完整的描述如下,完整的LED包含名称、模式、输出方式。

markdown 复制代码
/*
 * name : must the same as lights HAL
 * mode : control mode
 * data :
 *    PWM:  pwm number
 *    GPIO: gpio id
 *    PMIC: enum mt65xx_led_pmic
 *    CUST: custom set brightness function pointer
*/
struct cust_mt65xx_led {
        char                 *name;
        enum mt65xx_led_mode  mode;
        int                   data;
        struct PWM_config config_data;
};

对于上述data成员,当mode是MT65XX_LED_MODE_CUST_LCM时,data存储控制屏幕亮度的函数指针(一般这种屏幕是接收BB端的指令来自行调节亮度的,而不是BB端来控制背光,如OLED等自发光的屏幕)。

小结:通过cust_mt65xx_led来描述一个LED类型和驱动模式。

cust_mt65xx_led只是LED类型和驱动模式的封装,还不是真正使用的驱动的数据结构。

2. 驱动节点配置

为了方便不同的主板对自己的LED进行适配,MTK Android平台提供了dts的方式来允许用户方便的配置支持的LED数量和类型,不需要用户手动去创建和初始化cust_mt65xx_led

其中,当dts没有配置时,会从一个数组(列表)中获取LED配置。如果不进行dts配置,仅修改这个列表,效果也是一样的,但不推荐。

2.1. 列表配置

前面讲过,cust_mt65xx_led即是LED模式和驱动方式的完整抽象,cust_leds.c定义了该类型的数组,用来包含保存所有LED配置实例,外部文件通过get_cust_led_list获得这个列表。

没有定义dts时才会获取这个列表

c 复制代码
static struct cust_mt65xx_led cust_led_list[MT65XX_LED_TYPE_TOTAL] = {
    {"red",               MT65XX_LED_MODE_PWM, 1,{0,0,0,0,1}},
    {"green",             MT65XX_LED_MODE_PWM, 0,{0,0,0,0,1}},
    {"blue",              MT65XX_LED_MODE_PWM, -1,{0,0,0,0,1}},
    {"jogball-backlight", MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
    {"keyboard-backlight",MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
    {"button-backlight",  MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
    {"lcd-backlight",     MT65XX_LED_MODE_CUST_BLS_PWM, (int)disp_bls_set_backlight,{0}},
};

struct cust_mt65xx_led *get_cust_led_list(void)
{
        return cust_led_list;
}

2.2. dts配置

LED dts的配置如下。由于dts中配置的命名非常直观,且和前述LED数据结构变量名都能刚好对应,不再赘述。由于LED节点的创建流程中会首先判断是否存在dts配置,然后再检查列表配置。因此dts配置优先级高于列表配置。

ini 复制代码
&odm {
        led0:led@0 {
                compatible = "mediatek,red";
                led_mode = <1>;
                data = <1>;
                pwm_config = <0 0 0 0 0>;
        };

        led1:led@1 {
                compatible = "mediatek,green";
                led_mode = <1>;
                data = <0>;
                pwm_config = <0 0 0 0 0>;
        };
        ...
};

2.3. dts解析

LED配置的解析和加载是懒加载的形式。即运行流程中发生了对LED相关操作(如mt65xx_leds_brightness_set()函数调用)后,检查是否完成了dts的解析,使得LED dts配置是在使用时才解析,且仅进行首次解析。

scss 复制代码
int mt65xx_leds_brightness_set(enum mt65xx_led_type type, enum led_brightness level)
{
        struct cust_mt65xx_led *cust_led_list = get_cust_led_dtsi();

        // dts没有配置时,加载列表配置
        if (cust_led_list == NULL) {
                cust_led_list = get_cust_led_list();
                LEDS_DEBUG("Cannot not get the LED info from device tree. \n");
        }

        if (type >= MT65XX_LED_TYPE_TOTAL)
                return -1;
        
        ...
}

get_cust_led_dtsi()通过缓存解析结果来实现仅首次解析一次的目的。其中,缓存结果存储到pled_dtsi中。它是以指针类型存储的数组。

ini 复制代码
struct cust_mt65xx_led pled_dtsi[MT65XX_LED_TYPE_TOTAL];

前面dts的配置可以确定LED dts配置的解析路径,以及相匹配的LED名称。

perl 复制代码
char *leds_name[MT65XX_LED_TYPE_TOTAL] = {
        "red",
        "green",
        "blue",
        "jogball-backlight",
        "keyboard-backlight",
        "button-backlight",
        "lcd-backlight",
};

char *leds_node[MT65XX_LED_TYPE_TOTAL] = {
        "/odm/led@0",
        "/odm/led@1",
        "/odm/led@2",
        "/odm/led@3",
        "/odm/led@4",
        "/odm/led@5",
        "/odm/led@6",
};

解析函数如下。它的逻辑很简单,就是按照leds_name[]数组来按顺序解析dts,对数据结构初始化。如果是CUST系列的两个mode,则对data赋值为对应函数指针。

ini 复制代码
struct cust_mt65xx_led *get_cust_led_dtsi(void)
{
        static bool isDTinited = false;
        int i, offset;
        int pwm_config[5] = {0};

#if defined(USE_DTB_NO_DWS)
        if ( isDTinited == true )
                goto out;
        for (i = 0; i < MT65XX_LED_TYPE_TOTAL; i++) {
                pled_dtsi[i].name = leds_name[i];
                offset = fdt_path_offset(get_lk_overlayed_dtb(), leds_node[i]);
                if (offset < 0) {
                        LEDS_DEBUG("[LEDS]LK:Cannot find LED node from dts\n");
                        pled_dtsi[i].mode = 0;
                        pled_dtsi[i].data = -1;
                } else {
                        isDTinited = true;
                        pled_dtsi[i].mode = led_fdt_getprop_u32(get_lk_overlayed_dtb(), offset, "led_mode");
                        pled_dtsi[i].data = led_fdt_getprop_u32(get_lk_overlayed_dtb(), offset, "data");
                        led_fdt_getprop_char_array(get_lk_overlayed_dtb(), offset, "pwm_config", (char *)pwm_config);
                        pled_dtsi[i].config_data.clock_source = pwm_config[0];
                        pled_dtsi[i].config_data.div = pwm_config[1];
                        pled_dtsi[i].config_data.low_duration = pwm_config[2];
                        pled_dtsi[i].config_data.High_duration = pwm_config[3];
                        pled_dtsi[i].config_data.pmic_pad = pwm_config[4];
                        switch (pled_dtsi[i].mode) {
                                case MT65XX_LED_MODE_CUST_LCM:
                                        pled_dtsi[i].data = (long)primary_display_setbacklight;
                                        LEDS_DEBUG("[LEDS]LK:The backlight hw mode is LCM.\n");
                                        break;
                                case MT65XX_LED_MODE_CUST_BLS_PWM:
                                        pled_dtsi[i].data = (long)disp_bls_set_backlight;
                                        LEDS_DEBUG("[LEDS]LK:The backlight hw mode is BLS.\n");
                                        break;
                                default:
                                        break;
                        }
                        LEDS_INFO("[LEDS]LK:led[%d] offset is %d,mode is %d,data is %d .\n",    \
                                  i,offset,pled_dtsi[i].mode,pled_dtsi[i].data);
                }
        }
#endif
        if ( isDTinited == false )
                return NULL;
out:
        return pled_dtsi;
}

3. 驱动的初始化和驱动节点创建

MTK LED驱动是一个独立的module,由CONFIG_MTK_LEDS控制,在系统中注册LED设备驱动,并初始化节点。实际上,完整的LED节点创建过程中,创建LED节点本身是第二步,第一步是Linux原生创建的LED sysfs class。因此Kernel中有一些Linux定义好的用来创建对应class的KConfig配置也和LED驱动节点有关。下表列出相关的一些KConfig配置(这些配置是需要打开以支持LED驱动的),列出的顺序和Module加载顺序是一致的。

除CONFIG_MTK_LEDS是MTK加入的,其他都是Linux原生的

定义 包含模块 加载 简介
CONFIG_NEW_LEDS led-core.o exported symbols 提供LED操作函数的抽象和这些函数的symbol export,定义了leds_list、锁并export。主要是将通用函数和公共全局变量symbol export出来,并挂接到真正的ledclass_cdev实现中。相当于将通用API和实际设备驱动进行胶水层粘合
CONFIG_LEDS_CLASS led-class.o subsys_initcall 加载、初始化LED类(class),让系统中出现这一类的sysfs驱动节点,初始化对应attribute。注册电源管理函数、group attribute等。
CONFIG_LEDS_TRIGGERS led-triggers.o exported symbols 类似led-core,提供并export trigger相关的通用API |
CONFIG_LEDS_TRIGGER_TIMER ledtrig-timer.o module_init 用于trigger实现blink,创建delay_on/off节点并通过它们来运行调用者设置、实现blink
CONFIG_MTK_LEDS mtk_leds_drv.o mtk_leds.o late_initcall,在led-class加载完成后才加载,先加载mtk_leds_drv后加载mtk_leds mtk_leds_drv完成LED dts配置解析,并创建LED在sysfs class的节点,注册了LED brightness、blink等节点设置亮度、blink的处理函数块。其中,mtk_leds_drv注册的各处理函数,都是调用了mtk_leds.o

3.1. sysfs LED Class驱动

在分析LED驱动前,先分析sysfs下LED class。led-class.o在subsys_initcall阶段初始化,主要完成创建class类、注册电源管理函数、注册groups。

3.1.1. 创建sysfs LED class

创建一个class比较简单,调用class_create()就会在对应路径创建class目录了。led-class.o模块通过这个方法在sysfs下创建了leds目录。

3.1.2. 注册sysfs class dev_pm_ops

dev_pm_ops是设备电源管理的操作函数,当LED驱动挂起和恢复时调用,用于LED电源的管理。其中,注册的操作函数包含led_suspendled_resume

其中suspend的过程非常简单,通过dev_get_drvdata(struct device*),根据电源管理子系统传入的device取得存储于device->driver_dataled_classdev指针,然后调用led-core.o定义好的API,把LED的brightness设置为0,关掉了灯即进入了suspend。

如前所述,led class、led core、led device中间通过框架抽象粘合在了一起,这里的suspend过程就是这个框架在发挥作用。led device如何加入到框架中,稍后分析。

resume的流程也是一样的,把之前LED灯的brightness level恢复点亮就行了。其中brightness level(即对应LED device当前生效的亮度)存储在led_classdev->brightness

3.1.3. 注册sysfs class dev_groups,为设备添加属性节点

dev_groups的类型是struct attribute_group,它是class给其包含的设备添加的属性。LED class添加了brightness、max_brightness、trigger等节点,这里仅看看brightness节点。

arduino 复制代码
static ssize_t brightness_show(struct device *dev,                         
                struct device_attribute *attr, char *buf)                  
{                                                                            
        struct led_classdev *led_cdev = dev_get_drvdata(dev);                 

        /* no lock needed for this */
        led_update_brightness(led_cdev);

        return sprintf(buf, "%u\n", led_cdev->brightness);
}

static ssize_t brightness_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t size)
{
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
        unsigned long state;
        ssize_t ret;

        mutex_lock(&led_cdev->led_access);

        if (led_sysfs_is_disabled(led_cdev)) {
                ret = -EBUSY;
                goto unlock;
        }

        ret = kstrtoul(buf, 10, &state);
        if (ret)
                goto unlock;

        if (state == LED_OFF)
                led_trigger_remove(led_cdev);
        led_set_brightness(led_cdev, state);

        ret = size;
unlock:
        mutex_unlock(&led_cdev->led_access);
        return ret;
}
static DEVICE_ATTR_RW(brightness);

brightness_show()的逻辑是通过led_update_brightness()调用LED设备驱动,获取brightness。

brightness_store()的逻辑会进行加锁、拷贝用户空间的输入值,调用led_set_brightness(),他是led-core提供的抽象API。

scss 复制代码
void led_set_brightness(struct led_classdev *led_cdev,
                        enum led_brightness brightness)             
{
        /*                                  
         * If software blink is active, delay brightness setting
         * until the next timer tick.
         */                                                                  
        if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
                /*                         
                 * If we need to disable soft blinking delegate this to the
                 * work queue task to avoid problems in case we are called
                 * from hard irq context.
                 */ 
                if (brightness == LED_OFF) {
                        set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
                        schedule_work(&led_cdev->set_brightness_work);
                } else {       
                        set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
                                &led_cdev->work_flags);
                        led_cdev->new_blink_brightness = brightness;
                }
                return;
        }          

        led_set_brightness_nosleep(led_cdev, brightness);
}
EXPORT_SYMBOL_GPL(led_set_brightness);

void led_set_brightness_nopm(struct led_classdev *led_cdev,
                              enum led_brightness value)
{
        /* Use brightness_set op if available, it is guaranteed not to sleep */
        if (!__led_set_brightness(led_cdev, value))
                return;

        /* If brightness setting can sleep, delegate it to a work queue task */
        led_cdev->delayed_set_value = value;
        schedule_work(&led_cdev->set_brightness_work);
}
EXPORT_SYMBOL_GPL(led_set_brightness_nopm);

void led_set_brightness_nosleep(struct led_classdev *led_cdev,
                                enum led_brightness value)
{
        led_cdev->brightness = min(value, led_cdev->max_brightness);

        if (led_cdev->flags & LED_SUSPENDED)
                return;

        led_set_brightness_nopm(led_cdev, led_cdev->brightness);
}
EXPORT_SYMBOL_GPL(led_set_brightness_nosleep);

如果此时LED是软件实现的Blink(通过一个工作队列实现的),则设置对应标志位,由工作队列来更新亮度。否则调用led_set_brightness_nosleep()

led_set_brightness_nosleep()的作用是跳过LED_SUSPENDED状态,设置brightness的操作仅在此之外的状态中生效。实际调用led_set_brightness_nopm()来设置brightness。

led_set_brightness_nopm()首先通过__led_set_brightness()检查LED是否注册了brightness_set函数。有则调用,完成brightness设置。否则调用set_brightness_work工作队列来完成。

rust 复制代码
static int __led_set_brightness(struct led_classdev *led_cdev,              
                                enum led_brightness value)                 
{                                               
        if (!led_cdev->brightness_set)
                return -ENOTSUPP;                                
                                                                        
        led_cdev->brightness_set(led_cdev, value);
                                                                       
        return 0;                                                                             
}

brightness_set是mtk_leds_drv.o模块在probe时,解析LED dts、创建mt65xx_led_data的时候赋值为mt65xx_led_set()
set_brightness_work是led-class.o在初始化led class时创建的,该延时队列执行的是set_brightness_delayed()。它首先调用和_nopm()相同的__led_set_brightness(),在失败的情况下会再尝试__led_set_brightness_blocking()

3.1.4. 小结

led-class.o创建了LED class,并为即将加入到该class的子节点们准备了brightness、max_brightness、trigger等属性(attribute),并将这些属性与对应的读写函数挂接起来。其中,挂接的函数实际是mtk_leds_drv.o和mtk_leds.o模块实现的。

3.2. LED驱动基础数据结构

我们知道Android启动过程实际上是经历了LK和Kernel两个阶段的。其中Kernel阶段会创建LED驱动节点。Kernel中除了定义了与LK一致的用于抽象LED模式和驱动方式的struct外,主要在此基础上增加了设备驱动模型的抽象,当前亮度等级,以及用于blink的支持。

arduino 复制代码
struct mt65xx_led_data {
        struct led_classdev cdev;
        struct cust_mt65xx_led cust;
        struct work_struct work;
        int level;
        int delay_on;
        int delay_off;
};

其中led_classdev包含了blink、timer、trigger等除brightness等的功能,比较庞大。

arduino 复制代码
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;

3.3. LED驱动初始化

mtk_leds_drv.o模块的mt65xx_leds_probe完成对LED驱动的初始化。

首先读取LED配置(dts配置或列表配置),为配置的每个LED创建驱动数据结构,设置操作函数指针,初始化工作队列,最后调用LED节点注册函数。

ini 复制代码
static int mt65xx_leds_probe(struct platform_device *pdev)
{                
        int i;
        int ret;
        struct cust_mt65xx_led *cust_led_list = mt_get_cust_led_list();
                                              
        if (!cust_led_list) {               
                pr_info("[LED] get dts fail! Probe exit.\n");
                ret = -1;                                              
                goto err_dts;                                   
        }                                     
                                              
#ifdef CONFIG_BACKLIGHT_SUPPORT_LP8557
        /*i2c_register_board_info(4, &leds_board_info, 1);*/
        if (i2c_add_driver(&led_i2c_driver)) {
                LEDS_DRV_DEBUG("unable to add led-i2c driver.\n");
                ret = -1;
                goto err_dts;
        }
#endif                                                     
 
        for (i = 0; i < TYPE_TOTAL; i++) {
                if (cust_led_list[i].mode == MT65XX_LED_MODE_NONE) {
                        g_leds_data[i] = NULL;
                        continue;
                }

                g_leds_data[i] =
                    kzalloc(sizeof(struct mt65xx_led_data), GFP_KERNEL);
                if (!g_leds_data[i]) {
                        ret = -ENOMEM;
                        goto err;
                }

                g_leds_data[i]->cust.mode = cust_led_list[i].mode;
                g_leds_data[i]->cust.data = cust_led_list[i].data;
                g_leds_data[i]->cust.name = cust_led_list[i].name;

                g_leds_data[i]->cdev.name = cust_led_list[i].name;
                g_leds_data[i]->cust.config_data = cust_led_list[i].config_data;

                g_leds_data[i]->cdev.brightness_set = mt65xx_led_set;
                g_leds_data[i]->cdev.blink_set = mt65xx_blink_set;

                INIT_WORK(&g_leds_data[i]->work, mt_mt65xx_led_work);

                ret = led_classdev_register(&pdev->dev, &g_leds_data[i]->cdev);

                if (ret)
                        goto err;
        }
        ...
}

接下来通过led_classdev_register()注册LED设备驱动,注册每个LED驱动所需要的同步锁、驱动节点。

其中,每个LED是分别在for循环里面逐个单独地调用led_classdev_register()进行注册的。

函数如下。这个函数较长,包含几个关键步骤。下面先展示这个函数后再分解分析。

scss 复制代码
#define led_classdev_register(parent, led_cdev)                         \
        of_led_classdev_register(parent, NULL, led_cdev)



/**
 * of_led_classdev_register - register a new object of led_classdev class. 
 *                                                       
 * @parent: parent of LED device                                             
 * @led_cdev: the led_classdev structure for this device.                 
 * @np: DT node describing this LED       
 */                                                     
int of_led_classdev_register(struct device *parent, struct device_node *np,
                            struct led_classdev *led_cdev)                    
{                                                          
        char name[LED_MAX_NAME_SIZE];
        int ret;           
                                                            
        ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));     
        if (ret < 0)                                                          
                return ret;               
                                                         
        led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
                                led_cdev, led_cdev->groups, "%s", name);
        if (IS_ERR(led_cdev->dev))
                return PTR_ERR(led_cdev->dev);
        led_cdev->dev->of_node = np;

        if (ret)
                dev_warn(parent, "Led %s renamed to %s due to name collision",
                                led_cdev->name, dev_name(led_cdev->dev));

        if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
                ret = led_add_brightness_hw_changed(led_cdev);
                if (ret) {
                        device_unregister(led_cdev->dev);
                        return ret;
                }
        }

        led_cdev->work_flags = 0;
        #ifdef CONFIG_LEDS_TRIGGERS
        init_rwsem(&led_cdev->trigger_lock);
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
        led_cdev->brightness_hw_changed = -1;
#endif
        mutex_init(&led_cdev->led_access);
        /* add to the list of leds */
        down_write(&leds_list_lock);
        list_add_tail(&led_cdev->node, &leds_list);
        up_write(&leds_list_lock);

        if (!led_cdev->max_brightness)
                led_cdev->max_brightness = LED_FULL;

        led_update_brightness(led_cdev);

        led_init_core(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
        led_trigger_set_default(led_cdev);
#endif

        dev_dbg(parent, "Registered led device: %s\n",
                        led_cdev->name);

        return 0;
}
EXPORT_SYMBOL_GPL(of_led_classdev_register);

of_led_classdev_register()可以分解为如下几个最主要的流程。

3.3.1. 查找并移除重名LED节点

led_classdev_next_name()查找LED class内重复注册同一个名称(函数参数里面的name)的节点。class中如果重复注册,第一个节点命名为LED名称(如"red"),其余重复的命名为"red_0"。这个函数通过class_find_device()查找到同名class节点后,通过put_device()将其从class节点集合中移除。

这里传入的参数中len == sizeof(name),因此只会将直接重名的LED class移除(如"red"),而不会移除按序号再命名的class节点如("red_0")。

函数返回小于0的时候,即枚举重名节点期间的缓冲区大小不够(注意缓冲区要有空间给字符串后面补'\0'才算满足大小;直接传入sizeof()大小是满足要求的。),此时没有可靠地检查并移除重名函数,因此提前退出,不创建节点。

函数返回值大于等于0时表示发现并移除了对应数量的重名和再命名节点。

arduino 复制代码
static int led_classdev_next_name(const char *init_name, char *name,
                                  size_t len)
{
        unsigned int i = 0;
        int ret = 0;
        struct device *dev;

        strlcpy(name, init_name, len);

        while ((ret < len) &&
               (dev = class_find_device(leds_class, NULL, name, match_name))) {
                put_device(dev);
                ret = snprintf(name, len, "%s_%u", init_name, ++i);
        }

        if (ret >= len)
                return -ENOMEM;

        return i;
}

3.3.2. 创建LED节点

在移除同名节点后,通过device_create_with_groups创建sysfs LED class路径下的节点。它是Linux系统特地为创建sysfs class节点专用的函数。

javascript 复制代码
/**
 * device_create_with_groups - creates a device and registers it with sysfs
 * @class: pointer to the struct class that this device should be registered to
 * @parent: pointer to the parent struct device of this new device, if any
 * @devt: the dev_t for the char device to be added
 * @drvdata: the data to be added to the device for callbacks
 * @groups: NULL-terminated list of attribute groups to be created
 * @fmt: string for the device's name
 *
 * This function can be used by char device classes.  A struct device
 * will be created in sysfs, registered to the specified class.
 * Additional attributes specified in the groups parameter will also
 * be created automatically.
 *
 * A "dev" file will be created, showing the dev_t for the device, if
 * the dev_t is not 0,0.
 * If a pointer to a parent struct device is passed in, the newly created
 * struct device will be a child of that device in sysfs.
 * The pointer to the struct device will be returned from the call.
 * Any further sysfs files that might be required can be created using this
 * pointer.
 *
 * Returns &struct device pointer on success, or ERR_PTR() on error.
 *
 * Note: the struct class passed to this function must have previously
 * been created with a call to class_create().
 */
struct device *device_create_with_groups(struct class *class,
                                         struct device *parent, dev_t devt,
                                         void *drvdata,
                                         const struct attribute_group **groups,
                                         const char *fmt, ...)
{
    ...
}
EXPORT_SYMBOL_GPL(device_create_with_groups);

LED创建它的sysfs LED class子节点的调用方式如下。创建的device指针存储在mt65xx_led_data->led_classdev->dev这一struct device*中。

ini 复制代码
led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
                    led_cdev, led_cdev->groups, "%s", name);

至此,mtk_leds_drv.o就借助mt65xx_leds_probe()完成了MTK LED驱动的sysfs class注册和初始化了。

总结

MTK LED采用了Linux LED sysfs class,它通过dts进行配置,借助built-in module的方式加载。其中,LED class提供了各LED设备的attribute来实现brightness、trigger等节点的创建,并将store、show等读写函数挂接到mtk_leds_drv.o这一真正的LED class device中,实现了LED节点的创建、读写流程。

类图和流程图

相关推荐
水瓶丫头站住4 小时前
安卓APP如何适配不同的手机分辨率
android·智能手机
xvch4 小时前
Kotlin 2.1.0 入门教程(五)
android·kotlin
xvch8 小时前
Kotlin 2.1.0 入门教程(七)
android·kotlin
望风的懒蜗牛8 小时前
编译Android平台使用的FFmpeg库
android
浩宇软件开发9 小时前
Android开发,待办事项提醒App的设计与实现(个人中心页)
android·android studio·android开发
ac-er88889 小时前
Yii框架中的多语言支持:如何实现国际化
android·开发语言·php
苏金标10 小时前
The maximum compatible Gradle JVM version is 17.
android
zhangphil10 小时前
Android BitmapShader简洁实现马赛克,Kotlin(一)
android·kotlin
iofomo15 小时前
Android平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环,SVC系统调用拦截。
android
我叫特踏实16 小时前
SensorManager开发参考
android·sensormanager