当前位置: 首页 > 工具软件 > Backlight > 使用案例 >

linux pwm 调屏_linux驱动---bl_pwm驱动与backlight class实现背光调整

尚安平
2023-12-01

使用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占空比的调整,从而实现对背光亮度的控制。

 类似资料: