最簡單的Application Framework之燈光系統解析
1<. 燈光三個屬性:
1<. brightness : 0 ~ 255
2<. color : RGB
3<. blink : onMs, offMs
[定時器]:
Linux LED Class : Linux已經對燈光系統的大部分功能都封裝好了函數。
<路徑>: Linux x.xx.x/drivers/leds
2<. 解析led-class.c
1<. 情景分析
leds_init
class_create : 創建一個class,特殊的地方在於他有以下的設備屬性
static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0666, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0666, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};
2<. 使用:eg
echo 255 > /sys/class/leds/led1/brightness : 就會導致 led_brightness_store()被調用
cat /sys/class/leds/led1/brightness : 最終會導致 led_brightness_show() 被調用
cat /sys/class/leds/led1/max_brightness : 最終會導致 led_max_brightness_show() 被調用
3<. 關於閃爍的情景分析
1<. trigger目錄
echo timer > /sys/class/leds/led1/trigger :最終會導致 led_trigger_store()
led_trigger_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
/* 1. 首先會把 trigger_name 從 buf 裏面取出來 : 就是timer */
strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
/* 2. 從trigger_list找出名爲 "timer" 的 trigger */
list_for_each_entry(trig, &trigger_list, next_trig) {
if (!strcmp(trigger_name, trig->name)) {
/* 3. 調用*/
led_trigger_set(led_cdev, trig);
/* 4. 把 trigger 放入 led_classdev 的 trig_list中 */
list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
led_cdev->trigger = trigger;
/* 5. */
trigger->activate(led_cdev);
/* 6. 對於 "Timer" */
timer_trig_activate()
/* 7. 創建2個文件 :delay_off, delay_on */
device_create_file(led_cdev->dev, &dev_attr_delay_on);
device_create_file(led_cdev->dev, &dev_attr_delay_off);
/* 8. 讓LED閃爍, 初始化 */
led_blink_set()
if (!*delay_on && !*delay_off){
*delay_on = *delay_off = 500;
}
led_set_software_blink(led_cdev, *delay_on, *delay_off);
led_set_brightness(led_cdev, led_cdev->blink_brightness);
/* 調用軟件定時器來實現 */
mod_timer(&led_cdev->blink_timer, jiffies + 1);
/* [補充]. 在/sys/目錄下的文件都對應了讀/寫函數 */
}
}
/* 當我們訪問 delay_off, delay_on 文件時:就會導致led_delay_ox_show(), led_delay_ox_store() 被調用*/
static DEVICE_ATTR(delay_on, 0666, led_delay_on_show, led_delay_on_store);
static DEVICE_ATTR(delay_off, 0666, led_delay_off_show, led_delay_off_store);
- 1
- 2
- 3
2<. delay_on文件
echo 1000 > /sys/class/leds/led1/trigger/delay_on;
led_delay_on_store()
led_blink_set(); // 讓LED閃爍
led_cdev->blink_delay_on = state;
3<. 怎麼寫驅動
1<. 分配led_classdev
2<. 設置 :暫時需要設置的參數
1<. led_cdev->max_brightness //最大的亮度
2<. led_cdev->flags //當前狀態
3<. led_cdev->brightness //當前亮度
4<. led_cdev->name //創建設備是所用的名字
5<. led_cdev->default_trigger //默認的閃爍方式
6<. led_cdev->brightness_set(); //當應用程序訪問燈光時調用的函數3<. 註冊:led_classdev_register
4<. 開始狗血的寫起代碼 : 參考leds-s3c24xx.c
1<. 修改之前的leds_4412.c
2<. 上傳linux x.xx.x/drivers/leds
3<. 修改內核Makefile
vim linux x.xx.x/drivers/leds/Makefile
4<. 添加配置項 :
CONFIG_LEDS_CLASS
CONFIG_LEDS_TRIGGERS
CONFIG_LEDS_TRIGGER_TIMERLocation:
-> Device Drivers
-> LED Support (NEW_LEDS [=y])
[*] LED Class Support
[*] LED Trigger Support
<*> LED Timer Support5<. 編譯 && 燒寫
[補充]:
ledtrig-timer.c : 定時器的功能由他提供
5<. 調試
1<. 查看class目錄下面是否存在4個led ls /sys/class/leds ![content](https://img-blog.csdn.net/20170807223144261?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM0NDM5ODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 2<. 控制 點亮: echo 255 > /sys/class/leds/led1/brightness 閃爍: 1<. 每500ms亮滅一次 echo timer > /sys/class/leds/led1/trigger [補充]:同時會發現目錄下多了好幾個文件 ![echo 前](https://img-blog.csdn.net/20170807223111237?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM0NDM5ODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ![echo 後](https://img-blog.csdn.net/20170807223133288?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzM0NDM5ODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 2<. 每1000ms亮,2000ms滅 echo 1000 > /sys/class/leds/led1/trigger/delay_on;
echo 2000 > /sys/class/leds/led1/trigger/delay_off;
平臺: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,代碼如下:
這個函數帶有三個參數,其中level表示燈亮度的級別,對於led就兩個狀態,0和255,表示燈滅和燈亮這兩種情況,而onMS和offMS這兩個參數對應閃爍這種情況,表示燈亮的時間和燈滅的時間,從名字上來看,單位應該是毫秒級。[cpp] view plain copy
- 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;
- }
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燈,代碼如下:我們看這就是一個典型的文件操作函數,有打開、有關閉,有寫文件操作燈,對應上面的。所以說write_int(RED_LED_FILE, 0);這句就對應echo 0 > /sys/class/leds/red/brightness。[cpp] view plain copy
- 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;
- }
- }
如果是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的背光亮度,這部分代碼如下:首先調用reg_to_brightness函數將rgb表示的一個值轉換成一個亮度值,而這個亮度值的範圍是0~255,最後將這個值寫入到brightness這個文件中,注意這裏的brightness值不在只有0或255這兩個取值了,而是0~255這樣一個範圍,值越大越亮,而0即關閉lcd背光。[cpp] view plain copy
- 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;
- }
hal層看完了,我們再來看kernel層,kernel模塊初始化代碼在mediatek/kernel/drivers/leds/leds_drv.c中。首先是模塊初始化和卸載函數(注:省略了部分代碼,只提取出了主幹代碼):而平臺設備定義在mediatek/platform/mt6582/kernel/core/mt_devs.c中:[cpp] view plain copy
- 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);
- }
再來看probe函數:[cpp] view plain copy
- static struct platform_device mt65xx_leds_device = {
- .name = "leds-mt65xx",
- .id = -1
- };
首先調用mt_get_cust_led_list函數,改函數定義在mediatek/platform/mt6582/kernel/drivers/leds/leds.c中:[cpp] view plain copy
- 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);
- }
- }
而get_cust_led_list函數是和客戶定製相關的,也就是作爲一個普通的mtk開發者的話,你需要提供這麼一個函數,在mediatek/custom/hexing82_cwet_kk/kernel/leds/mt65xx/cust_leds.c中提供了這麼一個示例:[cpp] view plain copy
- struct cust_mt65xx_led *mt_get_cust_led_list(void)
- {
- return get_cust_led_list();
- }
即該函數需要返回一個全局的一個數組,那麼led部分客戶實際上需要做修改的地方也就只有這裏,後面再來看客戶應該怎麼去做修改。[cpp] view plain copy
- 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;
- }
現在我們知道需要返回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這個函數。在這個函數中,首先判斷是否是lcd背光,如果是背光,則對level有加限制,如果level超過limit這個值,那麼將level值設置成爲limit值,limit值初始化爲255,即level值最大隻能爲255。而level是設置led背光級別的,對於lcd背光來說,範圍是0~255。最後調用mt_mt65xx_led_set函數。[cpp] view plain copy
- 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);
- }
mt_mt65xx_led_set函數在led.c中,代碼如下:在這個函數中,"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函數。[cpp] view plain copy
- 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
- }
如果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這個函數:[cpp] view plain copy
- 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代碼如下: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):[cpp] view plain copy
- 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;
- }
注意:要使用這段代碼,那麼在ProjectConfig.mk中MTK_AAL_SUPPORT這個宏不應該被配置的。[cpp] view plain copy
- #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
首先將level做一下映射,brightness_mapping函數定義如下:還是返回的原來的值,沒有做映射處理(注意版本不一樣,這裏處理結果可能也不一樣)。[cpp] view plain copy
- 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部分代碼就先到這裏,全文完。