Linux背光管理框架用于控制和管理设备的背光亮度。该框架提供了统一的接口,允许不同类型的背光设备(如 LCD 屏幕、键盘背光等)通过标准的方式进行亮度调节。对于硬件的亮度调节,主要有以下几种模式:
本例中主要使用PWM调光模式,因此PWM作为背光驱动框架的provider,关联背光和PWM驱动,最终通过PWM输出到LCD的背光引脚,实现背光亮度的调节。
本节目录如下。
对于背光驱动管理框架,主要是像系统注册背光设备的接口,具体如下。
// 注册背光设备
// @name : 背光设备名称
// @dev : 背光设备的父设备
// @devdata : 背光设备的私有数据
// @ops : 背光设备的操作函数集合
// @props : 背光设备的属性信息
// 返回值 : 注册成功返回背光设备的指针,失败返回NULL
struct backlight_device *
backlight_device_register(const char *name, struct device *dev, void *devdata,
const struct backlight_ops *ops,
const struct backlight_properties *props);
// ------ 背光设备操作函数集合 ------
struct backlight_ops {
// 操作选项,例如 BL_CORE_SUSPENDRESUME 表示支持挂起和恢复操作
unsigned int options;
#define BL_CORE_SUSPENDRESUME (1 << 0)
// 更新背光设备状态的函数指针
int (*update_status)(struct backlight_device *);
// 获取背光设备的最大亮度值
int (*get_brightness)(struct backlight_device *);
// 检查帧缓冲是否与背光设备关联
int (*check_fb)(struct backlight_device *bd, struct fb_info *info);
};
// ------ 用于注册背光管理设备的属性结构 ------
struct backlight_properties {
int brightness; //当前设置的背光等级,初始化时调用
int max_brightness; //设备允许的最大背光等级
int power; //背光功率等级,0:全开,1~3:低功耗 4:全关,使能时设置为FB_BLANK_UNBLANK
int fb_blank; //由fb控制的背光等级,废弃,不应该使用
enum backlight_type type; //背光控制模式,初始化时设置
unsigned int state; //背光状态,由内核维护,驱动不应该修改
#define BL_CORE_SUSPENDED (1 << 0) /* backlight is suspended */
#define BL_CORE_FBBLANK (1 << 1) /* backlight is under an fb blank event */
enum backlight_scale scale; //亮度等级的类型
};
// ------ 背光控制模式 ------
enum backlight_type {
BACKLIGHT_RAW = 1, // 使用硬件寄存器控制背光
BACKLIGHT_PLATFORM, // 使用平台接口控制背光
BACKLIGHT_FIRMWARE, // 使用标准firmware接口管理背光
BACKLIGHT_TYPE_MAX, // 背光访问接口的支持数目(最大值)
};
// ------ 亮度等级的类型 ------
enum backlight_scale {
BACKLIGHT_SCALE_UNKNOWN = 0, //亮度等级类型未知
BACKLIGHT_SCALE_LINEAR, //亮度等级步进线性,每次增加相同步进值
BACKLIGHT_SCALE_NON_LINEAR, //亮度等级步进非线性,这个一般用于满足眼睛感知的非线性尺度
};
// 注销背光设备
// @bd : 要注销的背光设备的指针
// 返回值 : 无
void backlight_device_unregister(struct backlight_device *bd);
// 更新背光设备状态(执行update_status函数)
// @bd : 要更新状态的背光设备的指针
// 返回值 : 更新成功返回0,失败返回错误码
static inline int backlight_update_status(struct backlight_device *bd)
// 使能背光状态(执行backlight_update_status函数)
// @bd : 要使能的背光设备的指针
// 返回值 : 使能成功返回0,失败返回错误码
static inline int backlight_enable(struct backlight_device *bd)
// 禁用背光状态(执行backlight_update_status函数)
// @bd : 要禁用的背光设备的指针
// 返回值 : 禁用成功返回0,失败返回错误码
static inline int backlight_disable(struct backlight_device *bd)
// 获取背光设备的当前亮度值
// @bd : 要获取亮度值的背光设备的指针
// 返回值 : 获取成功返回亮度值,其它返回0
static inline int backlight_get_brightness(const struct backlight_device *bd)
上述就是backlight注册和设置时使用的接口,注册成功后,返回的结构为struct backlight_device,结构如下。
struct backlight_device {
// 背光设备的属性信息,包含亮度、最大亮度、功率等级等
struct backlight_properties props;
// 用于保护更新操作的互斥锁,确保在更新背光状态时不会发生并发问题
struct mutex update_lock;
// 用于保护操作函数调用的互斥锁,确保在调用操作函数时不会发生并发问题
struct mutex ops_lock;
// 指向背光设备操作函数集合的指针,包含了更新状态、获取亮度等操作函数
const struct backlight_ops *ops;
// 用于帧缓冲通知的通知块,当帧缓冲状态发生变化时会触发相应的通知
struct notifier_block fb_notif;
// 用于将该背光设备加入链表的节点,方便对多个背光设备进行管理
struct list_head entry;
// 表示该背光设备的通用设备结构体,包含了设备的基本信息和操作接口
struct device dev;
// 用于记录每个帧缓冲的背光开关状态,FB_MAX 表示最大的帧缓冲数量
bool fb_bl_on[FB_MAX];
// 记录该背光设备的使用计数,用于管理设备的生命周期
int use_count;
};
上面讲解了brightness的结构,这里讲解驱动如何实现的流程,主要流程如下。
backlight是控制pwm模块的设备节点;一个完整的backlight设备节点,除了标准的compatible和status属性用于驱动匹配和节点管理外。还需要定义控制的pwm节点,背光支持等级和默认的背光等级。
这里展示自定义的背光设备节点。
/ {
//....
//自定义背光设备节点
usr-backlight {
compatible = "rmk,pwm-bl"; //backlight标签,用于设备树匹配
pwms = <&pwm7 0 5000000>; //背光访问的pwm模块,pwm7,0通道,频率5000000
brightness-levels = <0 4 8 16 32 64 128 255>; //背光等级支持的档位
default-brightness-level = <6>; //默认背光等级
status = "okay"; //设备节点状态
};
};
对于背光的驱动代码实现,主要分为以下几个部分。
// 驱动中访问设备树节点的代码如下
// 匹配设备的节点
// 匹配相应pwm节点的实现
static const struct of_device_id pwm_bl_of_match[] = {
{ .compatible = "rmk,pwm-bl" },
{ }
};
MODULE_DEVICE_TABLE(of, pwm_bl_of_match);
static struct platform_driver pwm_bl_driver = {
.driver = {
.name = "pwm-bl",
.of_match_table = of_match_ptr(pwm_bl_of_match),
},
.probe = pwm_bl_probe,
.remove = pwm_bl_remove,
};
module_platform_driver(pwm_bl_driver);
MODULE_AUTHOR("wzdxf");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("platform driver for backlight-pwm");
MODULE_ALIAS("kernel pwm bl driver");
// 获取设备树节点的代码
// 查找brightness-levels属性,获取内部参数数量
prop = of_find_property(node, "brightness-levels", &length);
if (!prop)
return 0;
num_levels = length / sizeof(u32);
/* read brightness levels from DT property */
if (num_levels > 0) {
data->levels = devm_kcalloc(dev, num_levels, sizeof(*data->levels), GFP_KERNEL);
if (!data->levels)
return -ENOMEM;
// 读取brightness-levels, 写入到数组中
ret = of_property_read_u32_array(node, "brightness-levels",
data->levels,
num_levels);
if (ret < 0)
return ret;
//读取default-brightness-level, 写入到变量中
ret = of_property_read_u32(node, "default-brightness-level",
&value);
if (ret < 0)
return ret;
data->dft_brightness = value;
data->max_brightness = num_levels - 1;
}
// 获取PWM属性
pbl->pwm = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(pbl->pwm) && PTR_ERR(pbl->pwm) != -EPROBE_DEFER && !node) {
dev_err(&pdev->dev, "unable to request PWM\n");
goto err_alloc;
}
// 获取当前pwm信息,来自设备树的pwm选项
pwm_init_state(pbl->pwm, &(pbl->state));
static const struct backlight_ops pwm_bl_ops = {
.update_status = pwm_bl_update_status,
.get_brightness = pwm_get_current_brightness,
};
static int pwm_bl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
//...
// 设置背光属性参数
memset(&props, 0, sizeof(struct backlight_properties));
if(pwm_bl_is_linear(&(pbl->data))) //判断是否线性,线性则使用线性的亮度等级
props.scale = BACKLIGHT_SCALE_LINEAR;
else
props.scale = BACKLIGHT_SCALE_NON_LINEAR;
props.type = BACKLIGHT_RAW;
props.max_brightness = pbl->data.max_brightness;
props.brightness = pbl->data.dft_brightness;
// 根据props属性注册backlight设备
bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pbl,
&pwm_bl_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
ret = PTR_ERR(bl);
goto err_alloc;
}
platform_set_drvdata(pdev, bl);
pwm_bl_update_status(bl);
}
static int pwm_bl_update_status(struct backlight_device *bl)
{
struct pwm_bl_data *pb = bl_get_data(bl);
int brightness = backlight_get_brightness(bl);
struct pwm_state state;
if (brightness > 0) {
pwm_get_state(pb->pwm, &state);
state.duty_cycle = compute_duty_cycle(pb, brightness);
pwm_apply_state(pb->pwm, &state);
pwm_bl_power_on(pb);
dev_info(pb->dev, "pwm_bl duty_cycle:%lld\n", state.duty_cycle);
} else {
pwm_bl_power_off(pb);
dev_info(pb->dev, "pwm off\n");
}
return 0;
}
static int pwm_get_current_brightness(struct backlight_device *bl)
{
int brightness = backlight_get_brightness(bl);
return brightness;
}
另外则都是操作PWM的代码,这部分可以参考PWM章节。
关于背光驱动代码详细如下。
编译后加载驱动,可通过文件访问查看backlight设备节点;常见命令如下所示。
# 查看backlight设备节点
ls /sys/class/backlight/usr-backlight/
# 查看backlight最大亮度等级
cat /sys/class/backlight/usr-backlight/max_brightness
# 设置backlight亮度等级
echo 6 > /sys/class/backlight/usr-backlight/brightness
# 查看backlight当前亮度等级
cat /sys/class/backlight/usr-backlight/actual_brightness
详细显示如下所示。
对于backlight设备的应用层访问,通过标准文件接口进行访问。
// 打开文件,并返回一个文件流
FILE *fopen(const char *__filename, const char *__modes);
// 格式化输入函数(用于文件流)
int fscanf(FILE *stream, const char *format,...);
// 格式化输出函数(用于文件流)
int fprintf(FILE *stream, const char *format,...);
// 关闭文件流
int fclose(FILE *stream);
应用程序使用backlight驱动,主要访问上面提到的文件,具体内容如下。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BRIGHTNESS "/sys/class/backlight/usr-backlight/brightness"
#define ACTUAL_BRIGHTNESS "/sys/class/backlight/usr-backlight/actual_brightness"
// 读取文件内容的函数
int read_file(const char *filename)
{
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Failed to open file");
return -1;
}
int value;
if (fscanf(file, "%d", &value) != 1) {
perror("Failed to read value");
fclose(file);
return -1;
}
fclose(file);
return value;
}
// 写入亮度值到文件的函数
int write_brightness(int brightness)
{
FILE *file = fopen(BRIGHTNESS, "w");
if (file == NULL) {
perror("Failed to open brightness file for writing");
return -1;
}
if (fprintf(file, "%d", brightness) < 0) {
perror("Failed to write brightness value");
fclose(file);
return -1;
}
fclose(file);
return 0;
}
int main(int argc, char *argv[])
{
int set_brightness = 3;
if (argc == 2) {
set_brightness = atoi(argv[1]);
}
// 设置背光等级
if (write_brightness(set_brightness) == 0) {
printf("Successfully set brightness to %d\n", set_brightness);
}
// 读取设置的背光等级
int brightness = read_file(BRIGHTNESS);
if (brightness != -1) {
printf("Brightness: %d\n", brightness);
}
// 读取当前的背光等级
int actual_brightness = read_file(ACTUAL_BRIGHTNESS);
if (actual_brightness != -1) {
printf("Actual Brightness: %d\n", actual_brightness);
}
return 0;
}
详细的背光应用操作的代码见backlight应用。
具体执行如下:
本文前面章节主要为了描述和使用背光子系统,所以自己实现了驱动;事实上在系统中已经集成了backlight驱动,不需要自己实现;只需要添加相应选项支持即可。
# 添加支持backlight的配置项
CONFIG_PWM=y
CONFIG_BACKLIGHT_PWM=y
# menuconfig界面增加backlight
Device Drivers -> Graphic support -> Backlight & LCD device support -> LoWlevel Backlight controls -> Generic PWM Base Backlight Driver
对应代码:linux/drivers/video/backlight/pwm_bl.c
代码内添加设备树。
/ {
// 背光设备节点
backlight-display {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "okay";
};
}
之后可以参考上面章节访问设备节点即可。
直接开始下一节说明: 系统温控驱动管理框架