简介
本文分析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_suspend
和led_resume
。
其中suspend的过程非常简单,通过dev_get_drvdata(struct device*)
,根据电源管理子系统传入的device
取得存储于device->driver_data
的led_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节点的创建、读写流程。