作者信息
作者: 彭東林
QQ:405728433
平臺簡介
開發板:tiny4412ADK + S700 + 4GB Flash
要移植的內核版本:Linux-4.4.0 (支持device tree)
u-boot版本:友善之臂自帶的 U-Boot 2010.12 (為支持uImage啟動,做了少許改動)
busybox版本:busybox 1.25
交叉編譯工具鏈: arm-none-linux-gnueabi-gcc
(gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29))
實驗二、用蜂鳴器測試backlight
一般LCD的背光的亮度調節都是通過控制輸入給背光控制晶元的占空比來實現的,由於目前還沒有移植LCD驅動,我們先用蜂鳴器來模擬,實現的效果是:向/sys/class/backlight/backlight/brightness寫入不同的亮度值,蜂鳴器會發出相應的響聲。(註:這裡的蜂鳴器的頻率並不會改變,因為backlight實現的是控制PWM波的占空比,而不是頻率,所以我們能聽到的不同是蜂鳴器發出響聲的維持時間在變化)。
修改設備樹文件:arch/arm/boot/dts/exynos4412-tiny4412.dts
diff --git a/arch/arm/boot/dts/exynos4412-tiny4412.dts b/arch/arm/boot/dts/exynos4412-tiny4412.dts
index 18ad4cd..5fb1fd0 100644
--- a/arch/arm/boot/dts/exynos4412-tiny4412.dts
+++ b/arch/arm/boot/dts/exynos4412-tiny4412.dts
@@ -15,6 +15,7 @@
#include "exynos4412.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/usb4640/usb4640.h>
+#include <dt-bindings/pwm/pwm.h>
/ {
model = "FriendlyARM TINY4412 board based on Exynos4412";
@@ -90,6 +91,18 @@
regulator-max-microvolt = <2800000>;
};
};
+
+ bl: backlight {
+ compatible = "pwm-backlight";
+ pwms = <&pwm 0 1000000000 PWM_POLARITY_NORMAL>;
+ pwm-names = "backlight";
+ brightness-levels = <0 1 2 3 4 5 6 7 8 9 10>;
+ default-brightness-level = <3>;
+#if 0
+ pinctrl-0 = <&pwm0_out>;
+ pinctrl-names = "default";
+#endif
+ };
};
&rtc {
內核文檔對這些屬性有解釋:
Documentation/devicetree/bindings/pwm/pwm.txt
Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt
這裡有幾點需要註意:
1、tiny4412上控制LCD背光的實際是PWM1,這裡我們只是為了得到更直觀的效果,採用了PWM0;
2、由於PWM0的GPIO復用功能在pwm里已經進行了設置,所以在backlight這個節點中不需要對pwm0_out設置,可以看到,我用#if 0 … #endif 註釋掉了,否則,內核在啟動時會出現錯誤,因為這個gpio資源在pwm里已經占了,所以在backlight中就不能再申請了;(在設備樹里支持C/C++的代碼註釋規範)
3、然後就是pwms參數的理解: 在arch/arm/boot/dts/exynos4.dtsi中有對pwm的定義,如下所示:
pwm: pwm@139D0000 {
compatible = "samsung,exynos4210-pwm";
reg = <0x139D0000 0x1000>;
interrupts = <0 37 0>, <0 38 0>, <0 39 0>, <0 40 0>, <0 41 0>;
clocks = <&clock CLK_PWM>;
clock-names = "timers";
#pwm-cells = <3>;
status = "disabled";
};
註意其中的 #pwm-cells 的值是3,表示在其他地方可以引用pwm,並且可以傳遞3個參數,所以
pwms = <&pwm 0 1000000000 PWM_POLARITY_NORMAL>;
其中給pwm傳遞了三個參數,分別是:0、1000000000和PWM_POLARITY_NORMAL,具體如何解析這幾個參數,要看驅動程式的實現,每個廠家可以不一樣,對於我們的tiny4412使用了Linux系統提供的一個解析函數,這個函數在註冊pwmchip的時候賦值的,在內核文件drivers/pwm/pwm-samsung.c中:
if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
ret = pwm_samsung_parse_dt(chip);
if (ret)
return ret;
chip->chip.of_xlate = of_pwm_xlate_with_flags;
chip->chip.of_pwm_n_cells = 3;
} else {
if (!pdev->dev.platform_data) {
其中of_pwm_xlate_with_flags定義如下(drivers/pwm/core.c):
1: struct pwm_device *
2: of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
3: {
4: struct pwm_device *pwm;
5:
6: if (pc->of_pwm_n_cells < 3)
7: return ERR_PTR(-EINVAL);
8:
9: if (args->args[0] >= pc->npwm)
10: return ERR_PTR(-EINVAL);
11:
12: pwm = pwm_request_from_chip(pc, args->args[0], NULL);
13: if (IS_ERR(pwm))
14: return pwm;
15:
16: pwm_set_period(pwm, args->args[1]);
17:
18: if (args->args[2] & PWM_POLARITY_INVERTED)
19: pwm_set_polarity(pwm, PWM_POLARITY_INVERSED);
20: else
21: pwm_set_polarity(pwm, PWM_POLARITY_NORMAL);
22:
23: return pwm;
24: }
25: EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags);
可以看到:
第8行和第12行,將args[0]與npwm比較,以及根據args[0]獲得相應的pwm_device,可以知道args[0]表示的是使用的那個PWM,這裡我們傳遞的是0,表示獲得PWM0對應的pwm_device;
第16行,根據args[1]設置PWM0的周期,可以知道,args[1]表示的就是周期,這裡我們傳遞的是1000000000;
第19和第21行,可以知道args[2]表示的是否invert;
那麼是怎麼把這幾個參數放入args中的呢?這裡以backlight為例列出了函數調用過程:
用pwm控制的背光燈的代碼路徑:drivers/video/backlight/pwm_bl.c
pwm_backlight_probe
--> devm_pwm_get
--> pwm_get
--> of_pwm_get
--> of_property_match_string
--> of_parse_phandle_with_args [從設備樹中解析傳給pwm的參數,放到args中]
--> of_node_to_pwmchip
--> pc->of_xlate(pc, &args) [這裡就調用到了of_pwm_xlate_with_flags]
4、接下來是對brightness-levels的理解
1: + brightness-levels = <0 1 2 3 4 5 6 7 8 9 10>;
2: + default-brightness-level = <3>;
上面我們把PWM0的周期設置為了1000000000納秒,也就是1秒。
這裡的brightness-levels將占空比設置為了10個級別(0表示的是關閉背光),每個級別都對應一個占空比,其中default-brightness-level的是預設的級別,這隻是一個索引號,跟brightness-levels中的3不是一個概念,將brightness-levels看成一個數組,而default-brightness-level是數組的下標索引號,將來我們向brightness中寫入的也是索引號。那麼,系統是如何將brightness-levels中的級別設置為具體的占空比的呢?下麵是代碼(drivers/video/backlight/pwm_bl.c):
1: static void pwm_backlight_power_on(struct pwm_bl_data *pb, int brightness)
2: {
3: int err;
4:
5: if (pb->enabled)
6: return;
7:
8: err = regulator_enable(pb->power_supply);
9: if (err < 0)
10: dev_err(pb->dev, "failed to enable power supply\n");
11:
12: if (pb->enable_gpio)
13: gpiod_set_value(pb->enable_gpio, 1);
14:
15: pwm_enable(pb->pwm);
16: pb->enabled = true;
17: }
18:
19: static void pwm_backlight_power_off(struct pwm_bl_data *pb)
20: {
21: if (!pb->enabled)
22: return;
23:
24: pwm_config(pb->pwm, 0, pb->period);
25: pwm_disable(pb->pwm);
26:
27: if (pb->enable_gpio)
28: gpiod_set_value(pb->enable_gpio, 0);
29:
30: regulator_disable(pb->power_supply);
31: pb->enabled = false;
32: }
33:
34: static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
35: {
36: unsigned int lth = pb->lth_brightness;
37: int duty_cycle;
38:
39: if (pb->levels)
40: duty_cycle = pb->levels[brightness];
41: else
42: duty_cycle = brightness;
43:
44: return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
45: }
46:
47: static int pwm_backlight_update_status(struct backlight_device *bl)
48: {
49: struct pwm_bl_data *pb = bl_get_data(bl);
50: int brightness = bl->props.brightness;
51: int duty_cycle;
52:
53: if (bl->props.power != FB_BLANK_UNBLANK ||
54: bl->props.fb_blank != FB_BLANK_UNBLANK ||
55: bl->props.state & BL_CORE_FBBLANK)
56: brightness = 0;
57:
58: if (pb->notify)
59: brightness = pb->notify(pb->dev, brightness);
60:
61: if (brightness > 0) {
62: duty_cycle = compute_duty_cycle(pb, brightness);
63: pwm_config(pb->pwm, duty_cycle, pb->period);
64: pwm_backlight_power_on(pb, brightness);
65: } else
66: pwm_backlight_power_off(pb);
67:
68: if (pb->notify_after)
69: pb->notify_after(pb->dev, brightness);
70:
71: return 0;
72: }
我們重點看pwm_backlight_update_status和compute_duty_cycle。
當向brightness中寫入合法的亮度索引後,就會調用到pwm_backlight_update_status,其中的變數brightness就是寫入的所用值,如果不為0的話(為0的話,會調用pwm_backlight_power_off關閉背光),就會調用compute_duty_cycle,這個函數將索引轉換為duty_cycle(在normal模式下表示的是高電平的持續時間,用來控制占空比),然後調用pwm_config配置tcmpb寄存器,實現占空比的改變。
1: static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
2: {
3: unsigned int lth = pb->lth_brightness;
4: int duty_cycle;
5:
6: if (pb->levels)
7: duty_cycle = pb->levels[brightness];
8: else
9: duty_cycle = brightness;
10:
11: return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
12: }
其中:
第7行,將索引號brightness轉化為具體的level值(即1 2 3 4 5 6 7 8 9 10 中的一個);
第11行就是轉換函數了,其中lth是0,period是1000000000,scale是數組的最大值10,剩下的就是一個簡單的線性比例關係了,很好理解。
測試
重新編譯設備樹
1: make dtbs
編譯內核
make uImage LOADADDR=0x40008000 -j2
啟動內核,系統起來後,進入 /sys/class/backlight:
1: [root@tiny4412 root]# cd /sys/class/backlight/
2: [root@tiny4412 backlight]# ls
3: backlight
4: [root@tiny4412 backlight]# cd backlight/
5: [root@tiny4412 backlight]# ls
6: actual_brightness device subsystem
7: bl_power max_brightness type
8: brightness power uevent
向brightness中寫入亮度索引:
1: [root@tiny4412 backlight]# cat max_brightness
2: 10
3: [root@tiny4412 backlight]# cat actual_brightness
4: 3
5: [root@tiny4412 backlight]# cat brightness
6: 3
7: [root@tiny4412 backlight]# echo 9 > brightness
此時可以聽到蜂鳴器的聲音變了。
關閉背光
1: [root@tiny4412 backlight]# echo 0 > brightness
可以發現,蜂鳴器不響了。
未完待續。