linux驱动---bl_pwm驱动与backlight class实现背光调整 - guoyw

时间:2024-02-17 14:32:19

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

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