最简单的Application Framework之灯光系统解析
巨人的肩膀上:https://blog.csdn.net/weixin_37571125/article/details/78092922
1<. 灯光三个属性:
1<. brightness : 0 ~ 255
2<. color : RGB
3<. blink : onMs, offMs
-
[定时器]:
-
Linux LED Class : Linux已经对灯光系统的大部分功能都封装好了函数。
-
<路径>: Linux x.xx.x/drivers/leds
2<. 解析led-class.c
-
1<. 情景分析
-
leds_init
-
class_create : 创建一个class,特殊的地方在于他有以下的设备属性
-
static struct device_attribute led_class_attrs[] = {
-
__ATTR(brightness, 0666, led_brightness_show, led_brightness_store),
-
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
-
#ifdef CONFIG_LEDS_TRIGGERS
-
__ATTR(trigger, 0666, led_trigger_show, led_trigger_store),
-
#endif
-
__ATTR_NULL,
-
};
-
2<. 使用:eg
-
echo 255 > /sys/class/leds/led1/brightness : 就会导致 led_brightness_store()被调用
-
cat /sys/class/leds/led1/brightness : 最终会导致 led_brightness_show() 被调用
-
cat /sys/class/leds/led1/max_brightness : 最终会导致 led_max_brightness_show() 被调用
-
3<. 关于闪烁的情景分析
-
1<. trigger目录
-
echo timer > /sys/class/leds/led1/trigger :最终会导致 led_trigger_store()
-
led_trigger_store(struct device *dev, struct device_attribute *attr,
-
const char *buf, size_t count)
-
/* 1. 首先会把 trigger_name 从 buf 里面取出来 : 就是timer */
-
strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
-
/* 2. 从trigger_list找出名为 "timer" 的 trigger */
-
list_for_each_entry(trig, &trigger_list, next_trig) {
-
if (!strcmp(trigger_name, trig->name)) {
-
/* 3. 调用*/
-
led_trigger_set(led_cdev, trig);
-
/* 4. 把 trigger 放入 led_classdev 的 trig_list中 */
-
list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
-
led_cdev->trigger = trigger;
-
/* 5. */
-
trigger->activate(led_cdev);
-
/* 6. 对于 "Timer" */
-
timer_trig_activate()
-
/* 7. 创建2个文件 :delay_off, delay_on */
-
device_create_file(led_cdev->dev, &dev_attr_delay_on);
-
device_create_file(led_cdev->dev, &dev_attr_delay_off);
-
/* 8. 让LED闪烁, 初始化 */
-
led_blink_set()
-
if (!*delay_on && !*delay_off){
-
*delay_on = *delay_off = 500;
-
}
-
led_set_software_blink(led_cdev, *delay_on, *delay_off);
-
led_set_brightness(led_cdev, led_cdev->blink_brightness);
-
/* 调用软件定时器来实现 */
-
mod_timer(&led_cdev->blink_timer, jiffies + 1);
-
/* [补充]. 在/sys/目录下的文件都对应了读/写函数 */
-
}
-
}
-
/* 当我们访问 delay_off, delay_on 文件时:就会导致led_delay_ox_show(), led_delay_ox_store() 被调用*/
-
static DEVICE_ATTR(delay_on, 0666, led_delay_on_show, led_delay_on_store);
-
static DEVICE_ATTR(delay_off, 0666, led_delay_off_show, led_delay_off_store);
- 1
- 2
- 3
-
2<. delay_on文件
-
echo 1000 > /sys/class/leds/led1/trigger/delay_on;
-
led_delay_on_store()
-
led_blink_set(); // 让LED闪烁
-
led_cdev->blink_delay_on = state;
3<. 怎么写驱动
1<. 分配led_classdev
2<. 设置 :暂时需要设置的参数
1<. led_cdev->max_brightness //最大的亮度
2<. led_cdev->flags //当前状态
3<. led_cdev->brightness //当前亮度
4<. led_cdev->name //创建设备是所用的名字
5<. led_cdev->default_trigger //默认的闪烁方式
6<. led_cdev->brightness_set(); //当应用程序访问灯光时调用的函数3<. 注册:led_classdev_register
4<. 开始狗血的写起代码 : 参考leds-s3c24xx.c
1<. 修改之前的leds_4412.c
2<. 上传linux x.xx.x/drivers/leds
3<. 修改内核Makefile
vim linux x.xx.x/drivers/leds/Makefile
4<. 添加配置项 :
CONFIG_LEDS_CLASS
CONFIG_LEDS_TRIGGERS
CONFIG_LEDS_TRIGGER_TIMERLocation:
-> Device Drivers
-> LED Support (NEW_LEDS [=y])
[*] LED Class Support
[*] LED Trigger Support
<*> LED Timer Support5<. 编译 && 烧写
-
[补充]:
-
ledtrig-timer.c : 定时器的功能由他提供
5<. 调试
-
1<. 查看class目录下面是否存在4个led
-
ls /sys/class/leds
-
![content](https://img-blog.csdn.net/20170807223144261?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM0NDM5ODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
-
2<. 控制
-
点亮:
-
echo 255 > /sys/class/leds/led1/brightness
-
闪烁:
-
1<. 每500ms亮灭一次
-
echo timer > /sys/class/leds/led1/trigger
-
[补充]:同时会发现目录下多了好几个文件
-
![echo 前](https://img-blog.csdn.net/20170807223111237?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM0NDM5ODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
-
![echo 后](https://img-blog.csdn.net/20170807223133288?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM0NDM5ODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
-
2<. 每1000ms亮,2000ms灭
-
echo 1000 > /sys/class/leds/led1/trigger/delay_on;
echo 2000 > /sys/class/leds/led1/trigger/delay_off;
平台:mt6582 + android 4.4
hal层(mediatek/hardware/liblights/lights.c):
如果要点亮一个led灯,例如充电的指示灯(red led),sysfs节点是"/sys/class/leds/red/brightness",我们可以通过echo 255 > /sys/class/leds/red/brightness的方式打开这个led灯,通过echo 0 > /sys/class/leds/red/brightness来关闭这个led灯,那么hal层是如何做的呢? 操作red led灯的函数为blink_red,代码如下:
[cpp] view plain copy
- static int
- blink_red(int level, int onMS, int offMS)
- {
- static int preStatus = 0; // 0: off, 1: blink, 2: no blink
- int nowStatus;
- int i = 0;
- if (level == 0)
- nowStatus = 0;
- else if (onMS && offMS)
- nowStatus = 1;
- else
- nowStatus = 2;
- if (preStatus == nowStatus)
- return -1;
- #ifdef LIGHTS_DBG_ON
- ALOGD("blink_red, level=%d, onMS=%d, offMS=%d\n", level, onMS, offMS);
- #endif
- if (nowStatus == 0) {
- write_int(RED_LED_FILE, 0);
- }
- else if (nowStatus == 1) {
- // write_int(RED_LED_FILE, level); // default full brightness
- write_str(RED_TRIGGER_FILE, "timer");
- while (((access(RED_DELAY_OFF_FILE, F_OK) == -1) || (access(RED_DELAY_OFF_FILE, R_OK|W_OK) == -1)) && i<10) {
- ALOGD("RED_DELAY_OFF_FILE doesn't exist or cannot write!!\n");
- led_wait_delay(5);//sleep 5ms for wait kernel LED class create led delay_off/delay_on node of fs
- i++;
- }
- write_int(RED_DELAY_OFF_FILE, offMS);
- write_int(RED_DELAY_ON_FILE, onMS);
- }
- else {
- write_str(RED_TRIGGER_FILE, "none");
- write_int(RED_LED_FILE, 255); // default full brightness
- }
- preStatus = nowStatus;
- return 0;
- }
这个函数带有三个参数,其中level表示灯亮度的级别,对于led就两个状态,0和255,表示灯灭和灯亮这两种情况,而onMS和offMS这两个参数对应闪烁这种情况,表示灯亮的时间和灯灭的时间,从名字上来看,单位应该是毫秒级。
preStatus和nowStatus两个变量表示led灯之前的状态和现在的状态,所以preStatus加了个static关键字。如果是0表示关闭led灯,如果是1,表示有闪烁,如果是2,表示打开led灯。
既然nowStatus有三个状态,那么这里就要根据传递进来的三个参数做判断了,如果level为0,那就是关闭led灯这个状态,如果如果level不为0,那么又有两个状态,即onMS和offMS都不为0,就是闪烁这个状态,如果为0就是常亮这个状态。
如果nowStatus同preStatus值相同,直接返回,因为同之前状态相同吗,没有什么好修改的。
如果不相同,那么肯定要根据这三个状态来做处理了。首先是0这个状态,直接调用write_int函数去关闭led灯,代码如下:
[cpp] view plain copy
- static int
- write_int(char const* path, int value)
- {
- int fd;
- #ifdef LIGHTS_INFO_ON
- ALOGD("write %d to %s", value, path);
- #endif
- fd = open(path, O_RDWR);
- ALOGD("write_int open fd=%d\n", fd);
- if (fd >= 0) {
- char buffer[20];
- int bytes = sprintf(buffer, "%d\n", value);
- int amt = write(fd, buffer, bytes);
- close(fd);
- return amt == -1 ? -errno : 0;
- } else {
- return -errno;
- }
- }
我们看这就是一个典型的文件操作函数,有打开、有关闭,有写文件操作灯,对应上面的。所以说write_int(RED_LED_FILE, 0);这句就对应echo 0 > /sys/class/leds/red/brightness。
如果是1这种情况,首先向"/sys/class/leds/red/trigger"这个文件写入了"timer"这个字符串信息,从写入字符串这个信息来看应该只是起到一个显示作用,表示此时led灯处于一个什么状态。然后判断"/sys/class/leds/red/delay_off"这文件是否具有可读可以操作。为什么这里不判断"/sys/class/leds/red/delay_on"也是否具有可读可写操作呢,而只单单判断delay_off这个文件呢,暂时还明白作者的意图。
如果具有可读可写操作权限,那么向dealy_off这个文件写入offMS值,向delay_on这个文件写入onMS值,表示熄灭和点亮的时间值。
如果是2这种情况,就直接调用write_int函数往brightness这个文件写入255这个值,点亮led灯,最后保存此时led的状态。
从上面可以看出,在上层点亮一个led灯是很简单的,由于充电指示灯有可能有三个,分别是红、绿、蓝,所以这里还提供了blink_green、blink_blue这两个函数,由于操作方法都是完全相同的,所以这里也不再描述了。
在mtk代码中除了充电led指示灯之外,还包括按键灯、lcd背光灯等等。
按键灯这里提供了两个属性文件"/sys/class/leds/keyboard-backlight/brightness"和"/sys/class/leds/button-backlight/brightness",具体使用哪个要看底层是怎么配置的,如果在配置按键灯时使用的是"keyboard-backlight"这个名字,那操作时就使用前面那个属性文件,如果使用的是"button-backlight"这个名字,那就是用后面那个属性文件。通过代码来看操作这两个属性的文件代码完全一样,所以说应该是通用的,按键灯只有两个状态,即点亮和熄灭这两个状态,对应brightness值就是255和0。
而lcd背光灯的属性文件为"/sys/class/leds/lcd-backlight/brightness",可以往这个文件写入合适的亮度值来调节lcd的背光亮度,这部分代码如下:
[cpp] view plain copy
- static int
- set_light_backlight(struct light_device_t* dev,
- struct light_state_t const* state)
- {
- int err = 0;
- int brightness = rgb_to_brightness(state);
- pthread_mutex_lock(&g_lock);
- g_backlight = brightness;
- err = write_int(LCD_FILE, brightness);
- if (g_haveTrackballLight) {
- handle_trackball_light_locked(dev);
- }
- pthread_mutex_unlock(&g_lock);
- return err;
- }
首先调用reg_to_brightness函数将rgb表示的一个值转换成一个亮度值,而这个亮度值的范围是0~255,最后将这个值写入到brightness这个文件中,注意这里的brightness值不在只有0或255这两个取值了,而是0~255这样一个范围,值越大越亮,而0即关闭lcd背光。
hal层看完了,我们再来看kernel层,kernel模块初始化代码在mediatek/kernel/drivers/leds/leds_drv.c中。首先是模块初始化和卸载函数(注:省略了部分代码,只提取出了主干代码):
[cpp] view plain copy
- static struct platform_driver mt65xx_leds_driver = {
- .driver = {
- .name = "leds-mt65xx",
- .owner = THIS_MODULE,
- },
- .probe = mt65xx_leds_probe,
- .remove = mt65xx_leds_remove,
- .shutdown = mt65xx_leds_shutdown,
- };
- static int __init mt65xx_leds_init(void)
- {
- platform_driver_register(&mt65xx_leds_driver);
- }
- static void __exit mt65xx_leds_exit(void)
- {
- platform_driver_unregister(&mt65xx_leds_driver);
- }
而平台设备定义在mediatek/platform/mt6582/kernel/core/mt_devs.c中:
[cpp] view plain copy
- static struct platform_device mt65xx_leds_device = {
- .name = "leds-mt65xx",
- .id = -1
- };
再来看probe函数:
[cpp] view plain copy
- static int __init mt65xx_leds_probe(struct platform_device *pdev)
- {
- struct cust_mt65xx_led *cust_led_list = mt_get_cust_led_list();
- get_div_array();
- for (i = 0; i < MT65XX_LED_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(strcut mt65xx_led_data), GFP_KERNEL);
- if (!g_leds_data[i]) {
- ret = -EN0MEM;
- 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);
- led_classdev_register(&pdev->dev, &g_leds_data[i]->cdev);
- }
- }
首先调用mt_get_cust_led_list函数,改函数定义在mediatek/platform/mt6582/kernel/drivers/leds/leds.c中:
[cpp] view plain copy
- struct cust_mt65xx_led *mt_get_cust_led_list(void)
- {
- return get_cust_led_list();
- }
而get_cust_led_list函数是和客户定制相关的,也就是作为一个普通的mtk开发者的话,你需要提供这么一个函数,在mediatek/custom/hexing82_cwet_kk/kernel/leds/mt65xx/cust_leds.c中提供了这么一个示例:
[cpp] view plain copy
- static struct cust_mt65xx_led cust_led_list[MT65XX_LED_TYPE_TOTAL] = {
- {"red", MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK1, {0}},
- {"green", MT65XX_LED_MODE_NONE, -1, {0}},
- {"blue", MT65XX_LED_MODE_NONE, -1, {0}},
- {"jogball-backlight", MT65XX_LED_MODE_NONE, -1, {0}},
- {"keyboard-backlight", MT65XX_LED_MODE_NONE, -1, {0}},
- {"button-backlight", MT65XX_LED_MODE_NONE, -1, {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;
- }
即该函数需要返回一个全局的一个数组,那么led部分客户实际上需要做修改的地方也就只有这里,后面再来看客户应该怎么去做修改。
现在我们知道需要返回cust_mt65xx_led类型的一个数组。然后是get_div_array,这个函数主要是干什么的呢,这个函数主要是将leds.c中定义的div_array_hal数组复制给led_drv.c中定义的div_array,而div_array_hal数组是同pwm相关的,是pwm的分频参数,有1、2、4、8等等。
在for循环中,如果cust_led_list中定义的mode为MT65XX_LED_MODE_NONE,则直接跳过,不做任何处理。如果不为NONE,则按照标准的led程序来,其中brightness_set成员赋值为mt65xx_led_set,blink_set成员赋值为mt65xx_blink_set,最后调用led_classdev_register去注册。
ok,我们知道brightness_set就是用于来设置led灯的,所以我们首先来看mt65xx_led_set这个函数。
[cpp] view plain copy
- static void mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)
- {
- struct mt65xx_led_data *led_data =
- container_of(led_cdev, struct mt65xx_led_data, cdev);
- if (strcmp(led_data->cust.name, "lcd_backlight") == 0) {
- #ifdef CONTROL_BL_TEMPERATURE
- mutex_lock(&bl_level_limit_mutex);
- current_level = level;
- if (0 == limit_flag) {
- last_level = level;
- } else {
- if (limit < current_level) {
- level = limit;
- }
- }
- mutex_unlock(&bl_level_limit_mutex);
- #endif
- }
- mt_mt65xx_led_set(led_cdev, level);
- }
在这个函数中,首先判断是否是lcd背光,如果是背光,则对level有加限制,如果level超过limit这个值,那么将level值设置成为limit值,limit值初始化为255,即level值最大只能为255。而level是设置led背光级别的,对于lcd背光来说,范围是0~255。最后调用mt_mt65xx_led_set函数。
mt_mt65xx_led_set函数在led.c中,代码如下:
[cpp] view plain copy
- void mt_mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)
- {
- struct mt65xx_led_data *led_data =
- container_of(led_cdev, struct mt65xx_led_data, cdev);
- #ifdef LED_INCREASE_LED_LEVEL_MTKPATCH
- if (level >> LED_RESERVEBIT_SHIFT) {
- if (LED_RESERVEBIT_PATTERN != (level >> LED_RESERVEBIT_SHIFT)) {
- return;
- }
- if (MT65XX_LED_MODE_CUST_BLS_PWM != led_data->cust.mode) {
- return;
- }
- /* ... */
- } else {
- if (led_data->level != level) {
- led_data->level = level;
- if (strcmp(led_data->cust.name, "lcd-backlight") != 0) {
- schedule_work(&led_data->work);
- } else {
- if (MT65XX_LED_MODE_CUST_BLS_PWM == led_data->cust.mode) {
- mt_mt65xx_led_set_cust(&led_data->cust, ((((1 << MT_LED_INTERNAL_LEVEL_BIT_CNT) - 1)*level + 127)/255));
- } else {
- mt_mt65xx_led_set_cust(&led_data->cust, led_data->level);
- }
- }
- }
- }
- #endif
- }
在这个函数中,"if (level >> LED_RESERVEBIT_SHIFT)"中的if部分是不会被执行的,因为对于lcd背光来说,level值是不会超过255的。然后判断是否同之前的level值相同,如果不相同,则是不会被设置的,这一点在hal层也有这个判断。如果不是lcd背光,则执行led_data中的工作队列work。如果是lcd背光呢,则这里又判断它的mode是否是MT65XX_LED_MODE_CUST_BLS_PWM,如果是MT65XX_LED_MODE_CUST_BLS_PWM,则会对level值做下处理,最后他们都调用的是mt_mt65xx_led_set_cust函数。
如果mode为MT65XX_LED_MODE_CUST_BLS_PWM,那么这个level值计算公式是怎样的呢,为: ((( 1 << 10) - 1)*level + 127 ) / 255,可以看到这个值会明显增大很多。
lcd背光调用的是mt_mt65xx_led_set_cust函数,而对于普通的led,最终也是调用的mt_mt65xx_led_set_cust这个函数:
[cpp] view plain copy
- void mt_mt65xx_led_work(struct work_struct *work)
- {
- mt_mt65xx_led_set_cust(&led_data->cust, led_data->level);
- }
mt_mt65xx_led_set_cust代码如下:
[cpp] view plain copy
- int mt_mt65xx_led_set_cust(struct cust_mt65xx_led *cust, int level)
- {
- switch (cust->mode) {
- case MT65XX_LED_MODE_PWM:
- if (strcmp(cust->name, "lcd-backlight") == 0) {
- if (level == 0) {
- mt_pwm_disable(cust->data, cust->config_data.pmic_pad);
- } else {
- if (BacklightLevelSupport == BACKLIGHT_LEVEL_PWM_256_SUPPORT)
- level = brightness_mapping(tmp_level);
- else
- level = brightness_mapto64(tmp_level);
- mt_backlight_set_pwm(cust->data, level, bl_div_hal, &cust->config_data);
- }
- bl_duty_hal = level;
- } else {
- if (level == 0) {
- led_tmp_setting.nled_mode = NLED_OFF;
- mt_led_set_pwm(cust->data, &led_tmp_setting);
- mt_pwm_disable(cust->data, cust->config_data.pmic_pad);
- } else {
- led_tmp_setting.nled_mode = NLED_ON;
- mt_led_set_pwm(cust->data,&led_tmp_setting);
- }
- }
- return 1;
- case MT65XX_LED_MODE_GPIO:
- return ((cust_set_brightness)(cust->data))(level);
- case MT65XX_LED_MODE_PMIC:
- return mt_brightness_set_pmic(cust->data, level, bl_div_hal);
- case MT65XX_LED_MODE_CUST_LCM:
- return ((cust_brightness_set)(cust->data))(level, bl_div_hal);
- case MT65XX_LED_MODE_CUST_BLS_PWM:
- return ((cust_set_brightness)(cust->data))(level);
- case MT65XX_LED_MODE_NONE:
- default:
- break;
- }
- return -1;
- }
ok,我们一个一个来看。先来看背光的MT65XX_LED_MODE_CUST_BLS_PWM,调用的cust->data这个指针函数,在定义cust_led_list这个数组时,它被赋值成了disp_bls_set_backlight,定义如下(mediatek/platform/mt6582/kernel/drivers/dispsys/ddp_bls.c):
[cpp] view plain copy
- #if !defined(MTK_AAL_SUPPORT)
- int disp_bls_set_backlight(unsigned int level)
- {
- mapped_level = brightness_mapping(level);
- DISP_REG_SET(DISP_REG_BLS_PWM_DUTY, mapped_level);
- if (level != 0) {
- regVal = DISP_REG_GET(DISP_REG_BLS_EN);
- if (!(regVal & 0x10000)) {
- DISP_REG_SET(DISP_REG_BLS_EN, regVal | 0x10000);
- }
- } else {
- regVal = DISP_REG_GET(DISP_REG_BLS_EN);
- if (regVal & 0x10000)
- DISP_REG_SET(DISP_REG_BLS_EN, regVal & 0xffffffff);
- }
- }
- #endif
注意:要使用这段代码,那么在ProjectConfig.mk中MTK_AAL_SUPPORT这个宏不应该被配置的。
首先将level做一下映射,brightness_mapping函数定义如下:
[cpp] view plain copy
- unsigned int brightness_mapping(unsigned int level)
- {
- unsigned int mapped_level;
- mapped_level = level;
- return mapped_level;
- }
还是返回的原来的值,没有做映射处理(注意版本不一样,这里处理结果可能也不一样)。
然后将这个值写入到寄存器DISP_REG_BLS_PWM_DUTY中,如果level不为0,则使能pwm输出,如果level为0,则pwm禁止输出,关闭lcd背光。
再来看MT65XX_LED_MODE_PMIC,最终调用的电源管理那边的操作函数,这里也不在去细看了。
如果是使用MT65XX_LED_MODE_GPIO呢,那么也是需要自定义操作gpio口的函数。
关于led部分代码就先到这里,全文完。