使用pwm_bl驱动和backlight class实现背光调整
上节中梳理了dts中lvds_backlight设备节点的解析注册过程,以及pwm_bl驱动注册过程,由平台总线对设备与驱动进行匹配,调用probe回调函数,最终实现设备的初始化。
本次梳理驱动的具体实现,从probe调用到用户空间实现对设备节点的操作,即调整背光亮度。
1. 设备树的重新修改
背光控制由两个IO口,一个作为GPIO,给背光芯片提供使能信号;一个作为PWM输出,给背光芯片提供不同占空比的PWM波形,实现亮度控制。之前设备树节点配置中,没有配置GPIO使能信号,不能通过控制使能而打开/关闭背光。因此重新在设备节点中增加以下三行,加入GPIO节点属性,实现使能信号的控制。
pinctrl_bl_power: bl_power{
fsl,pins = <
SC_P_LVDS0_I2C0_SCL_LSIO_GPIO1_IO06 0x00000021
>;
};
...
lvds_backlight0: lvds_backlight {
...
pinctrl-names = "default";
pinctrl-0 = ;
bl-power = ;
...
};
pinctrl-0 pinctrl的驱动解析,配置管脚属性,复用状态等
其中SC_P_LVDS0_I2C0_SCL_LSIO_GPIO1_IO06为宏,定义在include/dt-bindings/pinctrl/pads-imx8qm.h头文件中,具体如下:
...
#define SC_P_LVDS0_I2C0_SCL 52 /* LVDS0.I2C0.SCL, LVDS0.GPIO0.IO02, LSIO.GPIO1.IO06 */ //端子号
...
#define SC_P_LVDS0_I2C0_SCL_LSIO_GPIO1_IO06 SC_P_LVDS0_I2C0_SCL 3 //复用功能
...
0x00000021为pad/mux 寄存器设置,需要根据电路设计情况进行设置。参考手册,摘取手册部分寄存器配置如下:
// bit filed
6-5
Pull Down Pull Up
Pull Down Pull Up
00b - prohibited
01b - pull-up
10b - pull-down
11b - pull disabled
4-1
—
reserved
reserved
0
PDRV
Drive
0b - high drive strength
1b - low drive strength
根据寄存器手册,0x21设置状态为:pull-up,low drive strength。
根据pinctrl-0节点属性,初始化时,内核将该复用端子初始化成GPIO。
bl-power节点属性由gpio驱动解析,标识出gpio组与组中的id,可以使用gpio模块的接口通过此节点获取gpio,从而实现对gpio的输入输出设置与输出状态设置。
2. pwm_bl的驱动实现
2.1 probe实现
pwm_bl.c文件是对lvds_backlight驱动的实现,当注册驱动后,匹配到设备,调用probe函数,函数内容如下:
static int pwm_backlight_probe(struct platform_device *pdev)
{
struct platform_pwm_backlight_data *data = dev_get_platdata(&pdev->dev);
struct platform_pwm_backlight_data defdata;
struct backlight_properties props;
struct backlight_device *bl;
struct device_node *node = pdev->dev.of_node;
struct pwm_bl_data *pb;
struct pwm_args pargs;
int ret;
pr_debug("enter probe +%s\n","1");
if (!data) {
ret = pwm_backlight_parse_dt(&pdev->dev, &defdata); // 解析dts节点中的数据,包括默认背光,背光等级等
if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
}
data = &defdata;
pr_debug("parser dtb data \n");
}
pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL); // 申请一块内存用于保存pwm_bl_data数据
data->enable_gpio = of_get_named_gpio(pdev->dev.of_node, "bl-power", 0); // 使用of函数得到gpio的id,提供给gpio的api使用
/*
* Compatibility fallback for drivers still using the integer GPIO
* platform data. Must go away soon.
*/
if (!pb->enable_gpio && gpio_is_valid(data->enable_gpio)) {
ret = devm_gpio_request_one(&pdev->dev, data->enable_gpio, // 申请gpio
GPIOF_OUT_INIT_HIGH, "bl-power");
if (ret < 0) {
dev_err(&pdev->dev, "failed to request GPIO#%d: %d\n",
data->enable_gpio, ret);
goto err_alloc;
}
pb->enable_gpio = gpio_to_desc(data->enable_gpio); // 将gpio number转成其描述符,gpiolib的api使用
}
/*
* If the GPIO is not known to be already configured as output, that
* is, if gpiod_get_direction returns either 1 or -EINVAL, change the
* direction to output and set the GPIO as active.
* Do not force the GPIO to active when it was already output as it
* could cause backlight flickering or we would enable the backlight too
* early. Leave the decision of the initial backlight state for later.
*/
if (pb->enable_gpio &&
gpiod_get_direction(pb->enable_gpio) != 0) // 设置gpio为输出
gpiod_direction_output(pb->enable_gpio, 0); // 设置gpio输出电平为低
pb->power_supply = devm_regulator_get(&pdev->dev, "power"); // 获取power资源
if (IS_ERR(pb->power_supply)) {
ret = PTR_ERR(pb->power_supply);
goto err_alloc;
}
pb->pwm = devm_pwm_get(&pdev->dev, NULL); // 获取pwm资源,由于devm_*类接口是后来增加的获取资源的接口形式,如果获取失败,则使用传统接口获取
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;
pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
}
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = data->max_brightness;
pr_debug("register device, name is: %s\n", dev_name(&pdev->dev));
// 使用backlight class接口注册设备
bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev), pdev->dev.parent, pb,
&pwm_backlight_ops, &props);
// 更新占背光信息
bl->props.brightness = data->dft_brightness;
bl->props.power = pwm_backlight_initial_power_state(pb);
backlight_update_status(bl);
}
devm_backlight_device_register是backlight class提供的接口函数,将设备注册到backlight class上,属性文件向文件系统注册,读写操作的实现均在backlight中实现,最终的gpio和pwm控制通过在pwm_backlight_ops中设置的回调实现。
2.2 为backlight class注册设置回调
上节提到的设置回调函数的结构体变量定义如下,其中有update_status成员变量,通过后续的backlight分析可知,在更新背光亮度和开关状态时,会调用此函数:
static const struct backlight_ops pwm_backlight_ops = {
.update_status= pwm_backlight_update_status,
.check_fb= pwm_backlight_check_fb,
};
函数参数是一个backlight_device的结构指针。函数实现如下:
static int pwm_backlight_update_status(struct backlight_device *bl)
{
struct pwm_bl_data *pb = bl_get_data(bl);
int brightness = bl->props.brightness; // 亮度值
int duty_cycle;
if (bl->props.power != 1 ||//FB_BLANK_UNBLANK || // 电源状态
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & BL_CORE_FBBLANK)
brightness = 0;
if (pb->notify)
{
brightness = pb->notify(pb->dev, brightness);
pr_debug("invoke notify function\n");
}
pr_debug("update backlight brightenss %d\n", brightness);
if (brightness > 0) {
duty_cycle = compute_duty_cycle(pb, brightness);
pwm_config(pb->pwm, duty_cycle, pb->period); // 更新亮度
pwm_backlight_power_on(pb, brightness); // 拉高bl_power, 打开pwm power
} else{
pwm_backlight_power_off(pb); // 拉低bl_power gpio,关闭pwm power
}
if (pb->notify_after)
pb->notify_after(pb->dev, brightness);
return 0;
}
从设备结构指针中获取pwm的数据结构,根据power及其他属性状态,对背光亮度和开关状态进行设定。
3 backlight class
3.1 backlight class属性导出
backlight将bl_power,brightness,type等属性导出至用户空间,根据导出属性的读写权限,为属性编写配置*_show() *_store()函数,以brightness为例:
// 读导出的属性文件时的回调函数
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.brightness);
}
// 写导出的属性文件时的回调函数
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct backlight_device *bd = to_backlight_device(dev);
unsigned long brightness;
rc = kstrtoul(buf, 0, &brightness);
if (rc)
return rc;
rc = backlight_device_set_brightness(bd, brightness);
return rc ? rc : count;
}
static DEVICE_ATTR_RW(brightness); // 宏,导出属性
其中static DEVICE_ATTR_RW(brightness)是宏,展开后如下:
static struct device_attribute dev_attr_brightness =
{
.attr = {
.name = "brightness", // 属性名
.mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | (S_IRUSR|S_IRGRP|S_IROTH))) // 属性文件的读写属性
},
.show = brightness_show, // 读回调函数
.store = brightness_store, // 写回调函数
};
引用brightness属性的属性列表
// 引用brightness属性结构体变量的属性列表
static struct attribute *bl_device_attrs[] = {
&dev_attr_bl_power.attr,
&dev_attr_brightness.attr,
&dev_attr_actual_brightness.attr,
&dev_attr_max_brightness.attr,
&dev_attr_type.attr,
NULL,
};
// 宏
ATTRIBUTE_GROUPS(bl_device);
对上述宏展开如下:
static const struct attribute_group bl_device_group = {
.attrs = bl_device_attrs,
};
static const struct attribute_group *bl_device_groups[] = {
&bl_device_group,
((void *)0), // 空指针,标志设备的属性组配置完成
};
其中bl_device_groups作为backlight class的默认属性配置在初始化时赋值给class的结构体变量:
static int __init backlight_class_init(void)
{
backlight_class = class_create(THIS_MODULE, "backlight");
if (IS_ERR(backlight_class)) {
pr_warn("Unable to create backlight class; errno = %ld\n",
PTR_ERR(backlight_class));
return PTR_ERR(backlight_class);
}
backlight_class->dev_groups = bl_device_groups;
backlight_class->pm = &backlight_class_dev_pm_ops;
INIT_LIST_HEAD(&backlight_dev_list);
mutex_init(&backlight_dev_list_mutex);
BLOCKING_INIT_NOTIFIER_HEAD(&backlight_notifier);
return 0;
}
至此,当backlight class初始化完成后,将注册的backlight device的属性导出到用户空间,并设置读写属性函数,导出的属性与其路径如下:
root:~# ls /sys/devices/platform/backlight/lvds_backlight/ -lh
total 0
-r--r--r-- 1 root root 4.0K Jan 1 00:00 actual_brightness
-rw-r--r-- 1 root root 4.0K Jan 1 00:00 bl_power
-rw-r--r-- 1 root root 4.0K Jan 1 00:00 brightness
lrwxrwxrwx 1 root root 0 Jan 1 00:00 device -> ../../../platform
-r--r--r-- 1 root root 4.0K Jan 1 00:00 max_brightness
drwxr-xr-x 2 root root 0 Jan 1 00:00 power
lrwxrwxrwx 1 root root 0 Jan 1 00:00 subsystem -> ../../../../class/backlight
-r--r--r-- 1 root root 4.0K Jan 1 00:00 type
-rw-r--r-- 1 root root 4.0K Jan 1 00:00 uevent
3.2 设置背光状态与调整背光
通过对bl_power和brightness文件的修改与读取,便能够控制背光的开关,设置背光亮度与获取当前背光的状态和亮度。当执行写操作时,如:
echo 80 > /sys/devices/platform/backlight/lvds_backlight/brightness
则调用brightness_store函数,更新pwm占空比,此时函数调用栈关系为:brightness_store --> backlight_device_set_brightness --> backlight_update_status --> 回调update_status,最后回调函数即为在bl_power中注册的回调函数,最终调用pwm的接口,实现对pwm占空比的调整,从而实现对背光亮度的控制。