来到公司的第一个 bug 就是关于背光的问题。
在梳理代码时,感觉 MTK 的代码中公共部分和客制化部分分的还是很清楚的。
首先说明一下文档的结构,我们先介绍我们客制化的地方,因为这个才是我们实际调试及解决 bug 时真正要关心的,而平台端不需要客制化的代码只需要梳理清楚就行。
背光流程中,客制化与否的分界文件是
cust_leds.c (vendor\vendor\mediatek\proprietary\bootable\bootloader\lk\target$(project))
其中
staticstruct cust_mt65xx_led cust_led_list[MT65XX_LED_TYPE_TOTAL] = {
{"red", MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK0,{0,0,0,0,0}},
{"green", MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK1,{0,0,0,0,0}},
{"blue", MT65XX_LED_MODE_NONE, -1, {0,0,0,0,0}},
{"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_LCM,(int)primary_display_setbacklight,{0}}, };
这个结构体就是客制化led子系统(包括呼吸灯、键盘灯、按键灯、背光等)中的模块具体调用方式的,比如
{"lcd-backlight", MT65XX_LED_MODE_CUST_LCM,(int)primary_display_setbacklight,{0}},
第一个成员就是定义操作的模块, lcd-backlight 就代表背光,
第二个成员表示对该模块的操作方式,mode,
第三个成员就是代表控制该模块的具体函数,
第四个成员就是代表一些配置, config;
这些成员的含义可以通过 cust_mt65xx_led 的声明来得知,在 cust_leds.h 中。
我们需要根据项目需要客制化模块背光控制函数,可以有很多选择,具体分析下函数怎么执行就行。
在背光控制中,有很多方式比如 disp_bls_set_backlight 、primary_display_setbacklight 等等,网上可以搜到的比较多的是 disp_bls_set_backlight ,客制化为这个函数的朋友可以搜一下,由于我没有看过primary_display_setbacklight 的相关介绍,正好项目中用到了,那我就写一下这种控制背光方式的原理。
primary_display_setbacklight 主要是针对设置了 cabc (根据画面内容实时调节背光亮度)的项目执行的背光控制函数。
我们来看
primary_display_setbacklight 函数 (kernel-3.18\kernel-3.18\drivers\misc\mediatek\video\mt6735\primary_display.c)
int primary_display_setbacklight(unsigned int level) /*可以看到传入的参数就是亮度等级level*/
{
……/*前面一堆设置,我们也没必要搞懂*/
if(primary_display_cmdq_enabled()) {/*判断cmdq是否开启*/
if(primary_display_is_video_mode()) {/*判断lcm的配置是否为video_mode*/
disp_lcm_set_backlight(pgc->plcm,level);/*这里就直接调用到lcm驱动文件中的lcm_setbacklight的函数,也是传递level值*/
} else {
…………/*后面一堆其他的不重要的操作*/
}
到这里就直接调到底层的操作了,客制化部分就结束了。
然后我们看一下平台端走的客制化控制函数之前怎么走的。
Led 子系统的驱动文件是 leds_drv.c,
模块注册等等都是字符设备注册方式:
驱动结构体 platform_driver mt65xx_leds_driver 和设备结构体 platform_device mt65xx_leds_device 的name 相同时就会触发探测函数 mt65xx_leds_probe
我们来看这个函数的内容:
第一个重要的函数就是 struct cust_mt65xx_led * cust_led_list = mt_get_cust_led_list();
调用 mt_get_cust_led_list(),这个函数调用到 leds.c 中的
struct cust_mt65xx_led *mt_get_cust_led_list(void),再调用到 leds.c 中的
struct cust_mt65xx_led *get_cust_led_dtsi(void)
这个函数就厉害了,我们可以看到这个函数的注释:get the leds info from device tree,即从 devicetree 读取 leds 的节点信息。
pled_dtsi = kmalloc(MT65XX_LED_TYPE_TOTAL * sizeof(struct cust_mt65xx_led), GFP_KERNEL);
首先我们看到申请了一个结构体,内存大小是前面我们需要客制化的结构体 cust_mt65xx_led 的大小。
之后开始遍历 led 子系统中的各个模块,每个模块都会执行下列步骤:
pled_dtsi[i].name= leds_name[i]; 得到模块名称
led_node = of_find_compatible_node(NULL,NULL, strncat(node_name, leds_name[i], (sizeof(node_name)-strlen(node_name)-1)));
读取节点信息, of_find_compatible_node 函数式 device tree 读取节点信息的函数。
ret =of_property_read_u32(led_node,"led_mode",&mode);
读取模块工作模式,赋值给 mode;
ret =of_property_read_u32(led_node, "data", &data);
读取模块模块控制函数,赋值给 data;
ret =of_property_read_u32_array(led_node, "pwm_config", pwm_config, ARRAY_SIZE(pwm_config));
读取模块配置参数,赋值给 config;
switch(pled_dtsi[i].mode)
然后根据工作模式,
case MT65XX_LED_MODE_CUST_LCM:
pled_dtsi[i].data =(long)mtkfb_set_backlight_level;
break;
选取并指定模块控制方式函数。
这里我们客制化的是 MT65XX_LED_MODE_CUST_LCM,所以背光控制函数指定为mtkfb_set_backlight_level。
到这里第一个重要的函数结束,继续回到 leds_drv.c 走 probe 函数,
i2c_add_driver(&led_i2c_driver) ;注册 i2c 驱动
get_div_array(); 获取分频信息,这个具体的功能可能跟 pwm 配置有关;
然后又进行一次 leds 子系统各模块的遍历,将上面读取的 device tree 中的信息赋值给 g_leds_data 数组,只不过这个数组是 mt65xx_led_data 类型,会有更多的操作函数
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;
/*这里可以看到亮度等级控制函数指定为mt65xx_led_set;*/
g_leds_data[i]->cdev.blink_set= mt65xx_blink_set;
/*这里是指定闪烁控制函数,主要是对呼吸灯的定制*/
INIT_WORK(&g_leds_data[i]->work,mt_mt65xx_led_work);
/*增加一个工作队列mt_mt65xx_led_work。*/
ret =led_classdev_register(&pdev->dev, &g_leds_data[i]->cdev);(注册设备)
/*……后面是一些其他的操作*/
这样的话控制背光亮度就可以确定为 mt65xx_led_set;
我们来看这个函数:
static void mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)(leds_drv.c)
传入参数是设备和亮度等级,开始!
node =of_find_compatible_node(NULL, NULL, “mediatek,lcd-backlight”);读取device tree中lcd-backlight 的信息,即背光;
前面是对对 level 的一些普通的判断操作,包括限制大小,然后
if (level ==0) {
……
gpio_direction_output(I2C_SET_FOR_BACKLIGHT,0);
}
if(!last_level1 && level) {
……
gpio_direction_output(I2C_SET_FOR_BACKLIGHT,1);
……
}
根据亮度等级置高置低引脚,这里可能是为了省电,在亮度为 0 时直接关闭那个 pin 脚,厉害了。
然后结束就是 mt_mt65xx_led_set(led_cdev,level);
这个就是在对传进的 level 参数处理完成之后再次传入一个背光控制函数,
我们看 void mt_mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)
#ifdef CONFIG_MTK_AAL_SUPPORT
使用 AAl 这个宏来控制代码,项目中没有打开这个宏,就走 else 里的代码
然后就是一番逻辑判断之后
mt_mt65xx_led_set_cust(&led_data->cust,level);
在看 int mt_mt65xx_led_set_cust(struct cust_mt65xx_led *cust, int level) 这个函数:
switch(cust->mode) 判断 device tree 中的背光控制模式,
case MT65XX_LED_MODE_CUST_LCM:
if (strcmp(cust->name,"lcd-backlight") == 0)
bl_brightness_hal =level;
/* warning for this APIrevork */
return ((cust_brightness_set)(cust->data)) (level, bl_div_hal);
这里最后一句话 ((cust_brightness_set)(cust->data)) 是使用 typedef 进行代码简化,想深究的话就搜一下 typedef 的用法
((cust_brightness_set)(cust->data)) (level, bl_div_hal); 这句话的意思就是执行
客制化的模块控制函数 primary_display_setbacklight();
到这里就和前面的客制化内容相互对接了。
其实这个 switch(cust->mode) 就是为了根据客制化的 mode 来判断是走平台的背光控制还是走 lcd 本身的背光控制函数。
这里整个流程就通了。
吃水不忘挖井人:
http://blog.csdn.net/gabbzang/article/details/17918709
http://blog.csdn.net/macxen_gunter/article/details/10591017