作者: 彭东林
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))
一般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
可以发现,蜂鸣器不响了。
未完待续。