關鍵詞:android 電池關機充電 androidboot.mode charger關機充電
充電畫面顯示
平臺信息:
內核:linux2.6/linux3.0
系統:android/android4.0
平臺:S5PV310(samsungexynos 4210)
作者:xubin341719(歡迎轉載,請註明作者)
歡迎指正錯誤,共同學習、共同進步!!
android 電池(二):android關機充電流程、充電畫面顯示
android電池(四):電池 電量計(MAX17040)驅動分析篇
android電池(五):電池 充電IC(PM2301)驅動分析篇上一篇我們講了鋰電池的充放電的流程和電池的一些特性,這一節我們重點說一下android關機充電是怎麼、充電畫面顯示是怎麼實現的,這個在工作中也比較有用,我們開始做這一塊的時候也走了不少的彎路。我記得我們做adnroid2.3的時候,關機狀態和充電logo顯示是在uboot中做的。應該是有兩種做法,回頭我再看下uboot中做畫面顯示那一塊是怎麼做的,這一節我們重點說系統中的充電logo顯示。
一、android正常開機流程、關機充電流程
在寫這篇文章之前我們先看兩個流程:正常開機流程,關機充電系統啓動流程
1、正常開機流程,按開機鍵。
可大致分成三部分
(1)、OS_level:UBOOT、kenrel、init這三步完成系統啓動;
(2)、Android_level:這部分完成android部的初始化;
(3)、Home Screen:這部分就是我們看到的launcher部分。
2、關機充電系統啓動流程
與前面相比,這個流程只走到init這一部分,就沒有往後走了,這部分我們會在後面的代碼中分析。
二、關機充電邏輯硬件邏輯
1、插入DC,charger IC從硬件上喚醒系統,相當於長按開機鍵開機。
下面這部分是charger IC連接系統的控制部分。
三、軟件邏輯。
DC插入,其實相當於關機狀態下“按開機鍵”開機。第一步要走UBOOT、kernel 、android init這一流程。
1、UBOOT
UBOOT啓動代碼我們不在這裏詳細分析,這裏我們只要注意二個問題:
a:如何判斷是DC插入;
b:設定setenv("bootargs", "androidboot.mode=charger"),androidboot.mode這個參數相當重要,這個參數決定系統是正常啓動、還是關機充電狀態。
Uboot/board/samsung/smdk4212/smkd4212.c
int board_late_init (void)
{
int keystate = 0;
printf("check start mode\n");
if ((*(int *)0x10020800==0x19721212) || (*(int *)0x10020804==0x19721212)
|| (*(int *)0x10020808==0x19721212)) //(1)、檢查是否有DC插入;
{
setenv ("bootargs", "");//(2)、沒有DC插入;
} else {//DC插入
int tmp=*(int *)0x11000c08;
*(int *)0x10020800=*(int *)0x10020804=0x19721212;
*(int *)0x11000c08=(tmp&(~0xc000))|0xc000;
udelay(10000);
if ((*(int *)0x11000c04 & 0x80)!=0x80 && INF_REG4_REG != 0xf) {
setenv ("bootargs", "androidboot.mode=charger");//(3)、設定bootargs爲charger狀態
printf("charger mode\n");
} else {
setenv ("bootargs", "");
}
*(int *)0x11000c08=tmp;
}
#ifdef CONFIG_CPU_EXYNOS4X12
int charge_status=CheckBatteryLow();//(4)、檢查電池電量;
keystate=board_key_check();//(5)、檢查按鍵狀態;
// fuse bootloader
if(second_boot_info != 0) {
boot_symbol=1;
INF_REG2_REG =0x8;
run_command(CONFIG_BOOTCMD_FUSE_BOOTLOADER, NULL);
}
if((INF_REG4_REG == 0xd)) {
// reboot default
char buf[10];
sprintf(buf, "%d", CONFIG_BOOTDELAY);
setenv ("bootdelay", buf);
setenv ("reserved", NULL);
saveenv();
} else if((INF_REG4_REG == 0xe) || keystate == (0x1 | 0x2)) {//(6)、按鍵進入fastboot模式;
// reboot bootloader
boot_symbol=1;
INF_REG2_REG =0x8;
printf("BOOTLOADER - FASTBOOT\n");
setenv ("reserved", "fastboot");
setenv ("bootdelay", "0");
} else if((INF_REG4_REG == 0xf) || keystate == (0x1 | 0x2 | 0x4)) {//(7)、按鍵進入recovery模式;
// reboot recovery
printf("BOOTLOADER - RECOVERY\n");
boot_symbol=1;
INF_REG2_REG =0x8;
setenv ("reserved", CONFIG_BOOTCMD_RECOVERY);
setenv ("bootdelay", "0");
} else
if(keystate == (0x1 | 0x4) || second_boot_info != 0 || partition_check()) {//(8)、按鍵進入卡升級模式;
// 2nd boot
printf("BOOTLOADER - 2ND BOOT DEVICE\n");
boot_symbol=1;
INF_REG2_REG =0x8;
setenv ("bootcmd", CONFIG_BOOTCOMMAND);
setenv ("reserved", CONFIG_BOOTCMD_FUSE_RELEASE);
setenv ("bootdelay", "0");
} else {//(9)、正常啓動;
// normal case
char buf[10];
sprintf(buf, "%d", CONFIG_BOOTDELAY);
setenv ("bootdelay", buf);
}
INF_REG4_REG = 0;
return 0;
}
(1)、檢查是否有DC插入;
if ((*(int *)0x10020800==0x19721212) || (*(int *)0x10020804==0x19721212)
|| (*(int *)0x10020808==0x19721212))
這部分檢查寄存器的值。
(2)、沒有DC插入;
(3)、設定bootargs爲charger狀態
if ((*(int *)0x11000c04 & 0x80)!=0x80 && INF_REG4_REG != 0xf) {
setenv ("bootargs", "androidboot.mode=charger");
這是這部分的重點,如果能過寄存器判斷是DC插入,把androidboot.mode設定爲charger狀態。
以下這部分根據需要加入,通過判斷不同的情況進入不同的功能,如fastboot\revovery…………,這部分不做詳細解釋。
(4)、檢查電池電量;
這個在正常開機狀態下,如果檢測電量太低,則不開機,這部分代碼就不做分析。
(5)、檢查按鍵狀態;
我們這個平臺有幾種模式:fastboot\recovery\卡升級等……
(6)、按鍵進入fastboot模式;
(7)、按鍵進入recovery模式;
(8)、按鍵進入卡升級模式
(9)、正常啓動;
2、kernel
這部分和正常啓動是一樣的。
3、init
前面所有的描述其實只有一點和正常啓動不太一樣,那就是在UBOOT中把androidboot.mode設定爲charger狀態,內核正常流程啓動,然後到init時要對charger這種狀態處理。
system\core\init\init.c
int main(int argc, char **argv)
{
………………
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init"); //(1)、顯示initlogo.rle,也就是android第二張圖片;
queue_builtin_action(set_init_properties_action, "set_init_properties");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0) {//(2)、這裏就是UBOOT中設定的bootmode,如果是charger模式,跳過下面初始化;
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
if (!strcmp(bootmode, "charger")) {//(3)、如果爲charger,則調用charger.c。
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
……………………
}
(1)、顯示initlogo.rle,也就是android第二張圖片;
queue_builtin_action(console_init_action,"console_init");調用console_init_action
static int console_init_action(int nargs, char **args)
{
int fd;
char tmp[PROP_VALUE_MAX];
if (console[0]) {
snprintf(tmp, sizeof(tmp), "/dev/%s", console);
console_name = strdup(tmp);
}
fd = open(console_name, O_RDWR);
if (fd >= 0)
have_console = 1;
close(fd);
if( load_565rle_image(INIT_IMAGE_FILE) ) {//這裏定義rle文件的名稱#define INIT_IMAGE_FILE "/initlogo.rle"
fd = open("/dev/tty0", O_WRONLY);
if (fd >= 0) {//如果沒有這張圖片,就顯示android字樣,在屏幕左上角;
const char *msg;
msg = "\n"
"\n"
"\n" // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
}
return 0;
}
(2)、這裏就是UBOOT中設定的bootmode,如果是charger模式,跳過下面初始化;
/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
(3)、如果爲charger,則調用charger.c
action_for_each_trigger("charger", action_add_queue_tail);
我們在後面細分charger這部分。
4、charger.c
這部分就是我們充電部分,充電畫面顯示的實現。
system\core\charger\charger.c
int main(int argc, char **argv)
{
………………
klog_set_level(CHARGER_KLOG_LEVEL);
dump_last_kmsg();
LOGI("--------------- STARTING CHARGER MODE ---------------\n");
gr_init();
gr_font_size(&char_width, &char_height); //(1)、初始化graphics,包括buf大小;
ev_init(input_callback, charger);//(2)初始化按鍵;
fd = uevent_open_socket(64*1024, true);
if (fd >= 0) {
fcntl(fd, F_SETFL, O_NONBLOCK);
ev_add_fd(fd, uevent_callback, charger);
}
charger->uevent_fd = fd;
coldboot(charger, "/sys/class/power_supply", "add");//(3)、創建/sys/class/power_supply結點,把socket信息通知應用層;
ret = res_create_surface("charger/battery_fail", &charger->surf_unknown);
if (ret < 0) {
LOGE("Cannot load image\n");
charger->surf_unknown = NULL;
}
for (i = 0; i < charger->batt_anim->num_frames; i++) {//(4)、這裏是顯示charger logo,res_create_surface顯示圖片函數;
struct frame *frame = &charger->batt_anim->frames[i];
ret = res_create_surface(frame->name, &frame->surface);
if (ret < 0) {
LOGE("Cannot load image %s\n", frame->name);
/* TODO: free the already allocated surfaces... */
charger->batt_anim->num_frames = 0;
charger->batt_anim->num_cycles = 1;
break;
}
}
ev_sync_key_state(set_key_callback, charger);
gr_fb_blank(true);
charger->next_screen_transition = now - 1;
charger->next_key_check = -1;
charger->next_pwr_check = -1;
reset_animation(charger->batt_anim);
kick_animation(charger->batt_anim);
event_loop(charger);//(5)、event_loop循環,電池狀態,檢測按鍵是否按下;
return 0;
}
(1)、初始化graphics,包括buf大小
android/bootable/recovery/minui/graphics.c
gr_init():minui/graphics.c[settty0 to graphic mode, open fb0],設制tty0爲圖形模式,打開fb0;
int gr_init(void)
{
gglInit(&gr_context);
GGLContext *gl = gr_context;
gr_init_font();
gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);
if (gr_vt_fd < 0) {
// This is non-fatal; post-Cupcake kernels don't have tty0.
perror("can't open /dev/tty0");
} else if (ioctl(gr_vt_fd, KDSETMODE, (void*) KD_GRAPHICS)) {
// However, if we do open tty0, we expect the ioctl to work.
perror("failed KDSETMODE to KD_GRAPHICS on tty0");
gr_exit();
return -1;
}
gr_fb_fd = get_framebuffer(gr_framebuffer);
if (gr_fb_fd < 0) {
gr_exit();
return -1;
}
get_memory_surface(&gr_mem_surface);
fprintf(stderr, "framebuffer: fd %d (%d x %d)\n",
gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);
/* start with 0 as front (displayed) and 1 as back (drawing) */
gr_active_fb = 0;
set_active_framebuffer(0);
gl->colorBuffer(gl, &gr_mem_surface);
gl->activeTexture(gl, 0);
gl->enable(gl, GGL_BLEND);
gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
gr_fb_blank(true);
gr_fb_blank(false);
return 0;
}
(2)android/bootable/recovery/minui/events.c
ev_init():minui/events.c[open /dev/input/event*]打開 /dev/input/event*
這部分是在,充電狀態下,按鍵操作的初始化,比如:短按顯示充電logo,長按開機,初始化代碼如下。
int ev_init(ev_callback input_cb, void *data)
{
DIR *dir;
struct dirent *de;
int fd;
dir = opendir("/dev/input");//打開驅動結點;
if(dir != 0) {
while((de = readdir(dir))) {
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
// fprintf(stderr,"/dev/input/%s\n", de->d_name);
if(strncmp(de->d_name,"event",5)) continue;
fd = openat(dirfd(dir), de->d_name, O_RDONLY);
if(fd < 0) continue;
/* read the evbits of the input device */
if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) {
close(fd);
continue;
}
/* TODO: add ability to specify event masks. For now, just assume
* that only EV_KEY and EV_REL event types are ever needed. */
if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits)) {
close(fd);
continue;
}
ev_fds[ev_count].fd = fd;
ev_fds[ev_count].events = POLLIN;
ev_fdinfo[ev_count].cb = input_cb;
ev_fdinfo[ev_count].data = data;
ev_count++;
ev_dev_count++;
if(ev_dev_count == MAX_DEVICES) break;
}
}
return 0;
}
(3)、創建/sys/class/power_supply結點,把socket信息通知應用層
uevent_open_socket這個函數是通過kobject_uevent的方式通知的應用層,就是往一個socket廣播一個消息,只需要在應用層打開socket監聽NETLINK_KOBJECT_UEVENT組的消息,就可以收到了,主要是創建了socket接口獲得uevent的文件描述符,然後觸發/sys/class/power_supply目錄及其子目錄下的uevent,然後接受並創建設備節點,至此設備節點纔算創建。
(4)、這裏顯示charger logo,res_create_surface顯示圖片函數;
res_create_surface:minui/resource.c[create surfaces for all bitmaps used later, include icons, bmps]
創建surface爲所以的位圖,包括圖標、位圖。 這些圖片的位置爲:system\core\charger\images
(5)、event_loop循環,電池狀態,檢測按鍵是否按下;
5、event_loop
這個函數判斷按鍵狀態,DC是否插拔。如果長按開機:執行android_reboot(ANDROID_RB_RESTART,0, 0);如果拔出DC:執行android_reboot(ANDROID_RB_POWEROFF,0, 0);
static void event_loop(struct charger *charger)
{
int ret;
while (true) {
int64_t now = curr_time_ms();//(1)、獲得當前時間;
LOGV("[%lld] event_loop()\n", now);
handle_input_state(charger, now);//(2)、檢查按鍵狀態;
handle_power_supply_state(charger, now);// (3)、檢查DC是否拔出;
/* do screen update last in case any of the above want to start
* screen transitions (animations, etc)
*/
update_screen_state(charger, now);//(4)、對按鍵時間狀態標誌位的判斷,顯示不同電量的充電logo;
wait_next_event(charger, now);
}
}
(1)、獲得當前時間;
int64_t now = curr_time_ms();
這個時間來判斷,有沒有屏幕超時,如果超時關閉屏幕充電logo顯示。
(2)、檢查按鍵狀態;
static void handle_input_state(struct charger *charger, int64_t now)
{
process_key(charger, KEY_POWER, now);
if (charger->next_key_check != -1 && now > charger->next_key_check)
charger->next_key_check = -1;
}
我們再看下:process_key(charger, KEY_POWER, now);
static void process_key(struct charger *charger, int code, int64_t now)
{
………………
if (code == KEY_POWER) {
if (key->down) {
int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;
if (now >= reboot_timeout) {//如果長按power鍵,就重新啓動,也就是重啓開機;
LOGI("[%lld] rebooting\n", now);
android_reboot(ANDROID_RB_RESTART, 0, 0);//重啓命令;
}
………………
}
key->pending = false;
}
(3)、檢查DC是否拔出;
handle_power_supply_state(charger, now);
static void handle_power_supply_state(struct charger *charger, int64_t now)
{
if (charger->num_supplies_online == 0) {
if (charger->next_pwr_check == -1) {
charger->next_pwr_check = now + UNPLUGGED_SHUTDOWN_TIME;
LOGI("[%lld] device unplugged: shutting down in %lld (@ %lld)\n",
now, UNPLUGGED_SHUTDOWN_TIME, charger->next_pwr_check);
} else if (now >= charger->next_pwr_check) {
LOGI("[%lld] shutting down\n", now);
android_reboot(ANDROID_RB_POWEROFF, 0, 0);//如果DC拔出,則關機;
}
………………
}
(4)、對按鍵時間狀態標誌位的判斷,顯示不同電量的充電logo;
update_screen_state(charger, now);
這個函數比較長了,其實做用就是:我們在狀態的過程中,充電logo的電量是要增加的,比如電量是20%時,要從第一格開始閃爍;如果是80%時,則要從第三格開始閃爍,電量顯示就是通過這個函數來計算實現的。
static void update_screen_state(struct charger *charger, int64_t now)
{
struct animation *batt_anim = charger->batt_anim;
int cur_frame;
int disp_time;
if (!batt_anim->run || now < charger->next_screen_transition)
return;
/* animation is over, blank screen and leave */
if (batt_anim->cur_cycle == batt_anim->num_cycles) {
reset_animation(batt_anim);
charger->next_screen_transition = -1;
gr_fb_blank(true);
LOGV("[%lld] animation done\n", now);
return;
}
disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;
/* animation starting, set up the animation */
if (batt_anim->cur_frame == 0) {
int batt_cap;
int ret;
LOGV("[%lld] animation starting\n", now);
batt_cap = get_battery_capacity(charger);
if (batt_cap >= 0 && batt_anim->num_frames != 0) {
int i;
/* find first frame given current capacity */
for (i = 1; i < batt_anim->num_frames; i++) {
if (batt_cap < batt_anim->frames[i].min_capacity)
break;
}
batt_anim->cur_frame = i - 1;
/* show the first frame for twice as long */
disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time * 2;
}
batt_anim->capacity = batt_cap;
}
/* unblank the screen on first cycle */
if (batt_anim->cur_cycle == 0)
gr_fb_blank(false);
/* draw the new frame (@ cur_frame) */
redraw_screen(charger);
/* if we don't have anim frames, we only have one image, so just bump
* the cycle counter and exit
*/
if (batt_anim->num_frames == 0 || batt_anim->capacity < 0) {
LOGV("[%lld] animation missing or unknown battery status\n", now);
charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;
batt_anim->cur_cycle++;
return;
}
/* schedule next screen transition */
charger->next_screen_transition = now + disp_time;
/* advance frame cntr to the next valid frame
* if necessary, advance cycle cntr, and reset frame cntr
*/
batt_anim->cur_frame++;
/* if the frame is used for level-only, that is only show it when it's
* the current level, skip it during the animation.
*/
while (batt_anim->cur_frame < batt_anim->num_frames &&
batt_anim->frames[batt_anim->cur_frame].level_only)
batt_anim->cur_frame++;
if (batt_anim->cur_frame >= batt_anim->num_frames) {
batt_anim->cur_cycle++;
batt_anim->cur_frame = 0;
/* don't reset the cycle counter, since we use that as a signal
* in a test above to check if animation is over
*/
}
}
下面是不能容量時顯示logo的函數:
static struct frame batt_anim_frames[] = {
{
.name = "charger/battery_0",
.disp_time = 750,
.min_capacity = 0,
},
{
.name = "charger/battery_1",
.disp_time = 750,
.min_capacity = 20,
},
{
.name = "charger/battery_2",
.disp_time = 750,
.min_capacity = 40,
},
{
.name = "charger/battery_3",
.disp_time = 750,
.min_capacity = 60,
},
{
.name = "charger/battery_4",
.disp_time = 750,
.min_capacity = 80,
.level_only = true,
},
{
.name = "charger/battery_5",
.disp_time = 750,
.min_capacity = BATTERY_FULL_THRESH,
},
};