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占空比的調整,從而實現對背光亮度的控制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM