平台: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这两个参数对应闪烁这种情况,表示灯亮的时间和灯灭的时间,从名字上来看,单位应该是毫秒级。
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。
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背光。
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部分客户实际上需要做修改的地方也就只有这里,后面再来看客户应该怎么去做修改。
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函数。
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函数。
void mt_mt65xx_led_work(struct work_struct *work) { mt_mt65xx_led_set_cust(&led_data->cust, led_data->level); }
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这个宏不应该被配置的。
unsigned int brightness_mapping(unsigned int level) { unsigned int mapped_level; mapped_level = level; return mapped_level; }还是返回的原来的值,没有做映射处理(注意版本不一样,这里处理结果可能也不一样)。