本文轉載自:https://blog.csdn.net/m0_37870649/article/details/80566131
前言:
在手機充電中常常使用充電指示燈來觀察手機充電狀態,比如說將手機插上USB線充電時指示燈會亮,如果拔出USB,指示燈會滅,在充電時候通常我們設置電池電量0~90%時,指示燈為紅色,電量為90%~100%時候,顯示為綠色。當然充電又分為開機充電和
關機充電,本文着重從關機充電模式講解guide-led的實現機制
一、關機充電下,指示燈實現整體流程框架
在關機下,插入USB充電,系統會上電啟動內核,並且加載相關的服務(Linux 用戶空間進程),其中就有關機充電服務/sytem/bin/charge,其中服務端的啟動定義在開機初始化話文件init.rc中,如下:
- service charge /bin/charge
- user root
- oneshot
其中charge程序由vendor/sprd/open-source/apps/charge/charge.c實現。
實現流程框架圖如下:
該圖可以分為兩個部分,Linux user和kernel 層兩個部分,charge 執行從創建charge線程開始到調用kernel set_brightness完成
對guide-led的控制。
二、關機充電下,指示燈實現具體流程
1. 電池充電主程序入口
文件:vendor/sprd/open-source/apps/charge/charge.c
- int
- main(int argc, char **argv) {
- .....
- ret = pthread_create(&t_1, NULL, charge_thread, NULL);
- if(ret){
- LOGE("thread:charge_thread creat failed\n");
- return -1;
- }
- LOGD("all thread start\n");
- pthread_join(t_1, NULL);
- .....
- LOGD("charge app exit\n");
- return EXIT_SUCCESS;
- }
在主程序中,創建charge_thread用來檢測電池狀態,然后根據充電電量的變化來控制充電指示燈
2. 充電線程的定義
文件:vendor/sprd/open-source/apps/charge/ui.c
- #define WakeFileName "/sys/power/wait_for_fb_wake"
- void *charge_thread(void *cookie)
- {
- .....
- for (;!is_exit;) {
- fd = open(WakeFileName, O_RDONLY, 0);
- if (fd < 0) {
- LOGD("Couldn't open file /sys/power/wait_for_fb_wake\n");
- return NULL;
- }
- do {
- err = read(fd, &buf, 1);
- LOGD("return from WakeFileName err: %d errno: %s\n", err, strerror(errno));
- } while (err < 0 && errno == EINTR);
- close(fd);
- bat_level = battery_capacity();
- update_progress_locked(bat_level);
- usleep(500000);
- }
- ......
- usleep(200);
- return NULL;
- }
改線程中有一個循環體,其中battery_capacity用來獲取電池容量,
將電池容量不斷的傳入update_progress_locked方法中
3.update_progress_locked方法的實現
- static void update_progress_locked(int level)
- {
- ......
- draw_progress_locked(level); // Draw only the progress bar
- }
在update_progress_locked中,又調用draw_progress_locked方法
4.draw_process_locked方法的實現
- #define LED_GREEN 1
- #define LED_RED 2
- #define LED_BLUE 3
- static void draw_progress_locked(int level)
- {
- .....
- if(level > 100)
- level = 100; //處理電池電量的上限
- else if (level < 0)//處理電池電量的下限
- level = 0;
- if(level < 90){
- if(led_flag!= LED_RED){
- led_on(LED_RED);
- led_flag = LED_RED; //如果電池電量低於90亮綠燈
- }
- }else{
- if(led_flag!= LED_GREEN){ //如果電池電量90~100 亮紅燈
- led_on(LED_GREEN); //調用亮燈函數
- led_flag = LED_GREEN;
- }
- }
- .....
- }
5. 亮燈函數led_on的實現
文件:vendor/sprd/open-source/apps/charge/backlight.c
- void led_on(int color)
- {
- if(color == 1){
- eng_led_green_test(max_green_led/2);
- eng_led_red_test(0);
- eng_led_blue_test(0);
- }else if(color == 2){
- eng_led_red_test(max_red_led/2);
- eng_led_green_test(0);
- eng_led_blue_test(0);
- }else if(color == 3){
- eng_led_blue_test(0);
- eng_led_red_test(max_green_led/2);
- eng_led_green_test(max_red_led/2);
- }else
- SPRD_DBG("%s: color is %d invalid\n",__func__,color);
- }
在亮燈函數led_on 中, 通過傳入的參數clor 來區分不能顏色燈,這里以綠燈為例
6. 亮綠燈函數eng_led_green_test的實現
- static int eng_led_green_test(int brightness)
- {
- int fd;
- int ret;
- char buffer[8];
- fd = open(LED_GREEN_DEV, O_RDWR); //打開綠燈設備節點
- if(fd < 0) {
- SPRD_DBG("%s: open %s fail",__func__, LED_GREEN_DEV);
- return -1;
- }
- memset(buffer, 0, sizeof(buffer));
- sprintf(buffer, "%d", brightness);
- ret = write(fd, buffer, strlen(buffer)); //向節點中寫入數據brightness值
- close(fd);
- return 0;
- }
亮綠燈函數eng_led_green_test的實現非常容易,就是向指定的節點中寫入數據brightness值,而brightness值的范圍為0~255 ,這個值直接決定了pwm輸入的占空比,進而影響燈的亮度。查看設備節點定義如下:
#define LED_GREEN_DEV "/sys/class/leds/green/brightness"
#define LED_RED_DEV "/sys/class/leds/red/brightness"
#define LED_BLUE_DEV "/sys/class/leds/blue/brightness"
上面的節點分別對應為紅,綠,藍三色燈對應的控制節點
==========linux driver kernel 部分==========
也就是說當我們調用write接口后,應用層點燈過程就已經結束了,接下來write會通過Linux VFS調用底層的xxx_write函數,
由於這里定義為/sys 目錄下的設備模型節點,所以對應寫函數應該為 xxx_store_xxx才對。
7.驅動層的led_on寫函數實現
文件:kernel/drivers/leds/leds-sprd-bltc-rgb.c
- static ssize_t store_on_off(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t size)
- {
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- unsigned long state;
- ssize_t ret = -EINVAL;
- ret = kstrtoul(buf, 10, &state);
- PRINT_INFO("onoff_state_value:%1ld\n",state);
- onoff_value = state;
- led_cdev->flags = ONOFF;
- sprd_leds_bltc_rgb_set(led_cdev,state);
- return size;
- }
在上述的寫函數中又調用sprd_leds_bltc_rgb_set函數,並且將brightness值傳入
8.sprd_leds_bltc_rgb_set的實現
- static void sprd_leds_bltc_rgb_set(struct led_classdev *bltc_rgb_cdev,enum led_brightness value)
- {
- struct sprd_leds_bltc_rgb *brgb;
- unsigned long flags;
- brgb = to_sprd_bltc_rgb(bltc_rgb_cdev);
- spin_lock_irqsave(&brgb->value_lock, flags);
- brgb->leds_flag = bltc_rgb_cdev->flags;
- brgb->value = value;
- spin_unlock_irqrestore(&brgb->value_lock, flags);
- if(1 == brgb->suspend) {
- PRINT_WARN("Do NOT change brightness in suspend mode\n");
- return;
- }
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_R]) == 0 || \
- strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_G]) == 0 || \
- strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_B]) == 0)
- sprd_leds_rgb_work(brgb);
- else
- sprd_leds_bltc_work(brgb);
- }
在上述的寫函數中又調用sprd_leds_bltc_work函數
9.sprd_leds_bltc_work的實現
- static void sprd_leds_rgb_work(struct sprd_leds_bltc_rgb *brgb)
- {
- unsigned long flags;
- mutex_lock(&brgb->mutex);
- spin_lock_irqsave(&brgb->value_lock, flags);
- if (brgb->value == LED_OFF) {
- spin_unlock_irqrestore(&brgb->value_lock, flags);
- sprd_leds_bltc_rgb_set_brightness(brgb);
- goto out;
- }
- spin_unlock_irqrestore(&brgb->value_lock, flags);
- sprd_leds_bltc_rgb_enable(brgb);
- PRINT_INFO("sprd_leds_bltc_rgb_work_for rgb!\n");
- out:
- mutex_unlock(&brgb->mutex);
- }
緊接着又調用sprd_leds_bltc_rgb_enable接口
10.sprd_leds_bltc_rgb_enable的實現
- static void sprd_leds_bltc_rgb_enable(struct sprd_leds_bltc_rgb *brgb)
- {
- sprd_bltc_rgb_init(brgb);
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_R]) == 0) {
- sci_adi_set(brgb->sprd_bltc_base_addr + BLTC_CTRL, (0x1<<0)|(0x1<<1));
- brgb->bltc_addr = brgb->sprd_bltc_base_addr + BLTC_R_PRESCL + BLTC_DUTY_OFFSET;
- sprd_leds_bltc_rgb_set_brightness(brgb);
- }
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_G]) == 0) {
- sci_adi_set(brgb->sprd_bltc_base_addr + BLTC_CTRL, (0x1<<4)|(0x1<<5));
- brgb->bltc_addr = brgb->sprd_bltc_base_addr + BLTC_G_PRESCL + BLTC_DUTY_OFFSET;
- sprd_leds_bltc_rgb_set_brightness(brgb);
- }
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_B]) == 0) {
- sci_adi_set(brgb->sprd_bltc_base_addr + BLTC_CTRL, (0x1<<8)|(0x1<<9));
- brgb->bltc_addr = brgb->sprd_bltc_base_addr + BLTC_B_PRESCL + BLTC_DUTY_OFFSET;
- sprd_leds_bltc_rgb_set_brightness(brgb);
- }
- .....
- PRINT_INFO("sprd_leds_bltc_rgb_enable\n");
- brgb->enable = 1;
- }
緊接着又調用sprd_leds_bltc_rgb_set_brightness
11.sprd_leds_bltc_rgb_set_brightness的實現
- static void sprd_leds_bltc_rgb_set_brightness(struct sprd_leds_bltc_rgb *brgb)
- {
- unsigned long brightness = brgb->value;
- unsigned long pwm_duty;
- pwm_duty = brightness;
- if(pwm_duty > 255)
- pwm_duty = 255;
- sci_adi_write(brgb->bltc_addr, (pwm_duty<<8)|PWM_MOD_COUNTER,0xffff);
- PRINT_INFO("reg:0x%1LX set_val:0x%08X brightness:%ld brightness_level:%ld(0~15)\n", \
- brgb->bltc_addr, sprd_leds_bltc_rgb_read(brgb->bltc_addr),brightness, pwm_duty);
- }
這里使用最關鍵的一步使用 sci_adi_write 將ISINK 寄存器賦值,直接調整亮度
三、總結
PS:本文側重關機充電,當然也牽扯到部分Kernel 部分,至於kernel部分詳細實現后面開機充電會詳述說明。