TinkerBoardR支持三路PWM输出,如下图所示。分别为PWM0,PWM1,PWM3A。各自通过IO口引出。
PWM 全称是 PulseWidth Modulation,也就是脉冲宽度调制。利用它我们可以控制灯光的亮度、电机的转速、屏幕背光亮度等。
假如一个IO高电平可以点亮一盏led灯,那么此时的亮度是最亮,当输出低电平时led熄灭。如果不停的开关led灯,那么只要速度足够快,我们人眼就无法发现led灭了,但是可以发现亮度改变。利用这种原理我们就可以控制屏幕背光的亮度。
PWM有两个关键术语,频率和占空比
(1)、在要使用 PWM 控制的设备驱动文件中包含以下头文件:
#include <linux/pwm.h>
该头文件主要包含 PWM 的函数接口。
(2)、申请 PWM使用
struct pwm_device *pwm_request(int pwm_id, const char *label);
函数申请 PWM。 例如:
struct pwm_device * pwm1 = NULL;pwm0 = pwm_request(1, “firefly-pwm”);
(3)、配置 PWM使用
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
配置 PWM 的占空比, 例如:
pwm_config(pwm0, 500000, 1000000);
(4)、使能PWM 函数
int pwm_enable(struct pwm_device *pwm);
用于使能 PWM,例如:
pwm_enable(pwm0);
(5)控制 PWM 输出主要使用以下接口函数:
struct pwm_device *pwm_request(int pwm_id, const char *label);
功能:用于申请 pwm
void pwm_free(struct pwm_device *pwm);
功能:用于释放所申请的 pwm
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
功能:用于配置 pwm 的占空比
int pwm_enable(struct pwm_device *pwm);
功能:使能 pwm
void pwm_disable(struct pwm_device *pwm);
功能:禁止 pwm
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm0 0 25000 0>;
brightness-levels = <
0 20 20 21 21 22 22 23
23 24 24 25 25 26 26 27
....
>;
default-brightness-level = <200>;
enable-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;
status = "okay";
};
pwms = <pwms = <&pwm0 0 25000 0>;>
default-brightness-level = <50>
enable-gpios
驱动注册入口
//kernel/drivers/video/backlight/pwm_bl.c
static struct platform_driver pwm_backlight_driver = {
.driver = {
.name = "pwm-backlight",
.pm = &pwm_backlight_pm_ops,
.of_match_table = of_match_ptr(pwm_backlight_of_match),
},
.probe = pwm_backlight_probe,
.remove = pwm_backlight_remove,
.shutdown = pwm_backlight_shutdown,
};
module_platform_driver(pwm_backlight_driver);
驱动注册成功之后会和设备树进行匹配传入参数,下面主要解析probe函数
//kernel/drivers/video/backlight/pwm_bl.c
static int pwm_backlight_probe(struct platform_device *pdev)
{
...
if (!data) {
//这里解析 dts 中的 brightness-levels、default-brightness-level
ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
}
data = &defdata;
}
....
//申请背光使能 gpio
pb->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable",
GPIOD_ASIS);
if (IS_ERR(pb->enable_gpio)) {
ret = PTR_ERR(pb->enable_gpio);
goto err_alloc;
}
...
//获得一个pwm设备
pb->pwm = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(pb->pwm) && PTR_ERR(pb->pwm) != -EPROBE_DEFER && !node) {
dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
pb->legacy = true;
//申请pwm,防止其他驱动也会使用
pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
}
...
//注册标准背光设备到内核
bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
&pwm_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
ret = PTR_ERR(bl);
if (pb->legacy)
pwm_free(pb->pwm);
goto err_alloc;
}
...
//使用默认参数
backlight_update_status(bl);
....
计算占空比相关函数
//kernel/drivers/video/backlight/pwm_bl.c
static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
{
//一般情况下这个值都为0
unsigned int lth = pb->lth_brightness;
//占空比
u64 duty_cycle;
//pb->levels这个表格就是从dts节点brightness-levels中获取的,
//假设进来的参数brightness是254,那么得到的duty_cycle就是1,
//如果没有这个表格,那么就直接是进来的亮度值
if (pb->levels)
duty_cycle = pb->levels[brightness];
else
duty_cycle = brightness;
duty_cycle *= pb->period - lth;
do_div(duty_cycle, pb->scale);
//假设这里lth是0,那么公式就是duty_cycle * pb->period / pb->scale
//pb->period也就是dts节点 pwms 的第三个参数周期值为 25000
//pb->scale为pb->levels数组中的最大值
//所以这个公式就是按照将Android的纯数值转换成事件周期值对应的占空比
return duty_cycle + lth;
}
一样的套路,只需配置设备树即可
&pwm0 {
status = "okay";
};
&pwm2 {
status = "okay";
};
接下来可以通过内核debug接口去查看PWM的注册状态。如果没成功,那么需要检查PWM输出相关的IO口是否被其他资源占用。
cat /sys/kernel/debug/pwm