平台: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,代码如下:
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灯,代码如下:
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的背光亮度,这部分代码如下:
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中。首先是模块初始化和卸载函数(注:省略了部分代码,只提取出了主干代码):
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中:static struct platform_device mt65xx_leds_device = {
.name = "leds-mt65xx",
.id = -1
};
再来看probe函数: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中: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中提供了这么一个示例: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这个函数。
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中,代码如下:
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这个函数:
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代码如下:
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):#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函数定义如下:
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部分代码就先到这里,全文完。