mtk led

平台: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部分代码就先到这里,全文完。

你可能感兴趣的:(mtk led)