使用pwm_bl驱动和backlight class实现背光调整

上节中梳理了dtslvds_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 = <&pinctrl_

bl_power>; bl-power = <&gpio1 6 GPIO_ACTIVE_HIGH>;

...

};

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_powerbrightness文件的修改与读取,便能够控制背光的开关,设置背光亮度与获取当前背光的状态和亮度。当执行写操作时,如:

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在case DSI_BACKLIGHT_EXTERNAL下同时并发执行lcd_bl_set_led_brightness和lcd_bias_set_led_brightness两个函数来设置背光亮度,可以使用多线程来实现。以下是一个简单的示例代码: ```c int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl) { int rc = 0; struct dsi_backlight_config *bl = &panel->bl_config; if (panel->host_config.ext_bridge_mode) return 0; DSI_DEBUG("backlight type:%d lvl:%d\n", bl->type, bl_lvl); switch (bl->type) { case DSI_BACKLIGHT_WLED: rc = backlight_device_set_brightness(bl->raw_bd, bl_lvl); break; case DSI_BACKLIGHT_DCS: rc = dsi_panel_update_backlight(panel, bl_lvl); break; case DSI_BACKLIGHT_EXTERNAL: { pthread_t thread1, thread2; int bl_lvl1 = bl_lvl, bl_lvl2 = bl_lvl; // 创建两个线程,分别执行lcd_bl_set_led_brightness和lcd_bias_set_led_brightness函数 pthread_create(&thread1, NULL, lcd_bl_set_led_brightness, (void *)&bl_lvl1); pthread_create(&thread2, NULL, lcd_bias_set_led_brightness, (void *)&bl_lvl2); // 等待线程结束 pthread_join(thread1, NULL); pthread_join(thread2, NULL); } break; case DSI_BACKLIGHT_PWM: rc = dsi_panel_update_pwm_backlight(panel, bl_lvl); break; default: DSI_ERR("Backlight type(%d) not supported\n", bl->type); rc = -ENOTSUPP; } return rc; } ``` 在case DSI_BACKLIGHT_EXTERNAL下,我们创建了两个线程,分别执行lcd_bl_set_led_brightness和lcd_bias_set_led_brightness函数。线程的创建和等待使用了pthread库中的函数pthread_create和pthread_join。注意,在多线程编程中,需要注意线程安全问题,例如共享变量的访问等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值