一、问题背景
普通的Android设备都有这样的一个feature:长按powerkey 8s就可以让系统自动重启。这样的一个feature是MTK平台本身就有的,目的是为了快速重启或者死机强制重启。但是目前试产的机器有这样的一个gap——产线上生产出来的机器要在没有接LCM的情况下进行长按powerkey的测试,但是产线上的此项测试并不是为了重启。所以现在的解决方案是:在没有接LCM的情况下,长按powerkey不重启;接LCM的情况下,长按powerkey重启。
现在的问题在于,生产组装完成的手机,即已经接了LCM的手机,长按powerkey也不能重启。这个问题基本可以出在两个方面:
(1) 本身重启的逻辑错误。就是说LCM已经检测到了,但是此feature的逻辑混乱了。
(2)LCM虽然接上了,但是detection出现问题。也就是说,feature逻辑没有问题,但是因为detect不到LCM,手机会认为你是在产线上做没有LCM前提下的长按powekey的测试。
二、问题解决
问题的分析原因是有两个方面,所以要从两个方面开始分析。
1. feature的逻辑分析
主要code在bsp/mediatek/platform/mt6589/kernel/drivers/keypad/kpd.c,即keypad的kernel驱动。
(1)添加一个宏开关XZHANG_LONG_POWER_RESET,添加判断LCM是否存在的函数。
#ifdef XZHANG_LONG_POWER_RESET
extern char temp_command_line[1024];
extern int force_get_tbat(void);
#define NO_TEMP_VOLT -20
int get_cmdline_lcm_meta(void)
{
char *p;
p = strstr(temp_command_line, "lcm_meta=1");
if(p == NULL) {
return 0;
} else {
return 1;
}
}
int is_batt_temp_pin(void)
{
if(force_get_tbat() == NO_TEMP_VOLT) {
printk("is_batt_temp_pin NO_TEMP_VOLT");
return 0;
} else {
return 1;
}
}
#endif
(2)enable LONG_POWER_RESET的实现,在boot mode加入长按powerkey的逻辑。
/*long press reboot function realize*/ if(kpd_enable_lprst&& get_boot_mode() == NORMAL_BOOT) { kpd_print("Normal Boot long press reboot selection\n"); kpd_print("Enable normal mode LPRST\n"); if(get_cmdline_lcm_meta()) { kpd_print("enable LPRST\N"); upmu_set_rg_pwrkey_rst_en(0x01);//pmic_config_interface(TOP_RST_MISC, 0x01, PMIC_RG_PWRKEY_RST_EN_MASK, PMIC_RG_PWRKEY_RST_EN_SHIFT); upmu_set_rg_homekey_puen(0x01); upmu_set_rg_homekey_rst_en(0x00); upmu_set_rg_pwrkey_rst_td(KPD_PMIC_LPRST_TD); } else { kpd_print("meta disable other mode LPRST\n"); upmu_set_rg_pwrkey_rst_en(0x00);//pmic package function for long press reboot function setting upmu_set_rg_homekey_rst_en(0x00); upmu_set_rg_homekey_puen(0x01); }
那么下一步可以怀疑temp_command_line中是没有”lcm_meta=1“的,也就是说可能bootloader所检测到的commandline是有问题的,或者就是说传入的过程中有问题。
2. commandline detection
从以上的分析继续往下看的话,完全可以怀疑是bootloader的commandline是有问题的,因为我看过传入的过程,没问题(这部分略过)。如前所述,commandline是由bootloader所获得并传给kernel的。
(1) commandline的获取是由bootable/bootloader/lk/app/mt_boot/mt_boot.c得到的,在boot_linux_from_storage()中,具体看一下:
custom_port_in_kernel(g_boot_mode, commanline); strlen += sprintf(commanline, "%s lcm=%1d-%s", commanline, DISP_IsLcmFound(), mt_disp_get_lcm_id()); #ifdef XZHANG_LONG_POWER_RESET if(mt_disp_compare_lcm_ic_id()){ strlen += sprintf(commanline, "%s lcm_meta=%1d", commanline, 1); }else{ strlen += sprintf(commanline, "%s lcm_meta=%1d", commanline, 0); } #endif strlen += sprintf(commanline, "%s fps=%1d", commanline, mt_disp_get_lcd_time()); strlen += sprintf(commanline, "%s lcm_type=%1d", commanline, mt_disp_get_lcd_type()); boot_linux((void *)CFG_BOOTIMG_LOAD_ADDR, (unsigned *)CFG_BOOTARGS_ADDR, (char *)commanline, board_machtype(), (void *)CFG_RAMDISK_LOAD_ADDR, g_rimg_sz); }
(2) 如果一直往下分析mt_disp_compare_lcm_ic_id()的话,可以找到这里(bsp/mediatek/platform/mt6589/lk/disp_drv.c):
BOOL DISP_Compare_LCM_IC_ID(void) { printf("[LCM_IC_ID] %s \n",__func__); if(lcm_drv && lcm_drv->compare_ic_id) return lcm_drv->compare_ic_id(); else return 0; }
lcm_drv其实是lcm的驱动,即这里要求是lcm驱动是有的,且和硬件LCM是匹配的。lk/disp_drv.c中通过一个函数去得到平台上所定义的LCM的型号,然后得到对应的LCM驱动,从这里可以看得很清楚:
const LCM_DRIVER *disp_drv_get_lcm_driver(const char *lcm_name)
{
LCM_DRIVER *lcm = NULL;
printk("[LCM Auto Detect], we have %d lcm drivers built in\n", lcm_count);
printk("[LCM Auto Detect], try to find driver for [%s]\n",
(lcm_name==NULL)?"unknown":lcm_name);
if(lcm_count ==1)
{
// we need to verify whether the lcm is connected
// even there is only one lcm type defined
lcm = lcm_driver_list[0];
lcm->set_util_funcs(&lcm_utils);
lcm->get_params(&s_lcm_params);
u4IndexOfLCMList = 0;
lcm_params = &s_lcm_params;
lcm_drv = lcm;
{
isLCMFound = TRUE;
}
printk("[LCM Specified]\t[%s]\n", (lcm->name==NULL)?"unknown":lcm->name);
goto done;
}
else
{
int i;
for(i = 0;i < lcm_count;i++)
{
lcm_params = &s_lcm_params;
lcm = lcm_driver_list[i];
printk("[LCM Auto Detect] [%d] - [%s]\t", i, (lcm->name==NULL)?"unknown":lcm->name);
lcm->set_util_funcs(&lcm_utils);
memset((void*)lcm_params, 0, sizeof(LCM_PARAMS));
lcm->get_params(lcm_params);
disp_drv_init_ctrl_if();
disp_drv_set_driving_current(lcm_params);
disp_drv_init_io_pad(lcm_params);
if(lcm_name != NULL)
{
if(!strcmp(lcm_name,lcm->name))
{
printk("\t\t[success]\n");
isLCMFound = TRUE;
u4IndexOfLCMList = i;
lcm_drv = lcm;
goto done;
}
else
{
printk("\t\t[fail]\n");
}
}
else
{
if(LCM_TYPE_DSI == lcm_params->type){
init_dsi(FALSE);
dsi_enable_power(TRUE);
}
if(lcm->compare_id != NULL && lcm->compare_id())
{
printk("\t\t[success]\n");
isLCMFound = TRUE;
lcm_drv = lcm;
u4IndexOfLCMList = i;
goto done;
}
else
{
lcm_drv = lcm;
if(LCM_TYPE_DSI == lcm_params->type)
DSI_Deinit();
printk("\t\t[fail]\n");
}
}
}
}
done:
return lcm_drv;
}
#if defined(NT35595_FHD_DSI_CMD_CHIMEI) &nt35595_fhd_dsi_cmd_chimei_lcm_drv, #endif #if defined(NT35595_FHD_DSI_CMD_LG) &nt35595_fhd_dsi_cmd_lg_lcm_drv,
这里首先得到了lcm_drv,然后通过它去调用对应的compare_ic_id()。因为LCM_DRIVER是标准的,compare_ic_id()只是驱动的一个接口,那么接下来直接看对应的驱动程序的接口就行了。
(3)对于这个问题中的MT6589平台手机,使用的是lg lcm,其实也可以去bsp/mediatek/custom/common/kernel/lcm/目录下看一下平台上有哪些驱动。对于t35595_fhd_dsi_cmd_lg驱动来说,compare_ic_id的接口是lcm_compare_ic_id()。
static unsigned int lcm_compare_ic_id(void) { unsigned int id=0; unsigned char buffer[2]; unsigned int array[16]; array[0] = 0x00023700;// read id return two byte,version and id dsi_set_cmdq(array, 1, 1); read_reg_v2(0xF4, buffer, 2); id = buffer[0]; //we only need ID #ifdef BUILD_LK printf("%s, id = 0x%x\n", __func__, id); #else printk("%s, id = 0x%x\n", __func__, id); #endif if(id == LCM_ID_NT35595) return 1; else return 0; }
read_reg_v2()读到LCM ID,存在buffer中,然后给id。如果id == LCM_ID_NT35595,那么返回1,然后回到最初的地方,"lcm_meta=1"被写入commandline,然后正常传入kernel,“长按powerkey重启”也就能work。
这里的问题会出在:
从code看到,有人将LCM的模式从command mode改成了video mode。然后就和lg LCM的FAE沟通,看这个改动是不是有问题。
沟通结果知道video mode下,在LCM刷完一帧之后,其LCM id是读不出来的。这样,虽然bootloader给kernel传入正确的commandline,但是从kernel中读取commandline时,因为LCM id读取错误,导致了commandline中”lcm_meta=0"被读取了。
(4) 这里的解决思路是,在读取LCM id之前重新向LCM寄存器中写入正确的id,然后再读取其值。这样不管是刷完一帧之前还是之后,都能读取正确的lcm id
这里是重新定义了一个compare_id( ),然后替代compare_ic_id( )函数的功能读取正确LCM id:
static unsigned int lcm_compare_id(void) { unsigned int ret = 0; unsigned char buffer[2]; TC358768_DCS_write_1A_1P(0xFF,0x10);// CMD1 MDELAY(1); read_reg_v2(0xF4, buffer,2); #ifdef BUILD_LK printf("[IC_ID] %s 0xF4 0=0x%x 1=0x%x \n",__func__,buffer[0],buffer[1]); #endif lcm_id = buffer[0]; ret = mt_get_gpio_in(GPIO154); #if defined(BUILD_LK) printf("%s, [IC_ID]nt35595 lg GPIO154 = %d \n", __func__, ret); #endif return (ret == 1)?1:0; }
然后在lcm_compare_ic_id( )中,修改如下:
static unsigned int lcm_compare_ic_id(void)
{
unsigned int id=0;
unsigned char buffer[2];
unsigned int array[16];
array[0] = 0x00023700;// read id return two byte,version and id
dsi_set_cmdq(array, 1, 1);
read_reg_v2(0xF4, buffer, 2);
id = buffer[0]; //we only need ID
#ifdef BUILD_LK
printf("%s, id = 0x%x 0x%x \n", __func__, id,lcm_id);
#else
printk("%s, id = 0x%x 0x%x \n", __func__, id,lcm_id);
#endif
if(lcm_id==LCM_ID_NT35595) return 1; else if(id == LCM_ID_NT35595) return 1;
else
return 0;
}
这样的话,lcm_id中读取到正确值,然后kernel状态下长按powekey就可以重启。
三、小结
最后问题是可以这样解决的,但是lg LCM的FAE说video mode的效果不好,建议重新改回command mode,然后会改回了command mode,问题结束。