zoukankan      html  css  js  c++  java
  • Linux PWM framework(二)- 背光子系统【转】

    转自:https://blog.csdn.net/weixin_41028621/article/details/103542751?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-3

    了解backlight driver.
    1.Backlight Framework


    1.1.用户空间

      背光设备文件对应于/sys/class/backlight/目录下的文件。/sys/class/backlight是注册的背光设备类型,而在/sys/class/backlight/目录下的文件就是所注册的背光设备。系统完成背光设备类型的注册,代码如下:

    drivers/video/backlight/backlight.c:
    662 static int __init backlight_class_init(void)
    663 {
    664 backlight_class = class_create(THIS_MODULE, "backlight"); //注册背光设备类型;
    670
    671 backlight_class->dev_groups = bl_device_groups; //指定背光设备类型的属性文件;
    672 backlight_class->pm = &backlight_class_dev_pm_ops;
    673 INIT_LIST_HEAD(&backlight_dev_list);
    674 mutex_init(&backlight_dev_list_mutex);
    675 BLOCKING_INIT_NOTIFIER_HEAD(&backlight_notifier);
    676
    677 return 0;
    678 }

    289 static struct attribute *bl_device_attrs[] = {
    290 &dev_attr_bl_power.attr,
    291 &dev_attr_brightness.attr,
    292 &dev_attr_actual_brightness.attr,
    293 &dev_attr_max_brightness.attr,
    294 &dev_attr_type.attr,
    295 NULL,
    296 };
    297 ATTRIBUTE_GROUPS(bl_device);


      backlight背光子系统的主要就是靠 bl_device_attrs这个类属性,当设置背光值就是向类属性中某个成员写背光值,这个类属性就是给用户的一种接口。

      backlight创建bl_power,brightness,actural_brightness,max_brightness四个成员,其中brightness是当前亮度,max_brightness是最大亮度。当用户层通过cat或者echo命令就会触发这些成员。对于这些属性的读写函数,以函数backlight_show_max_brightness为例:

    static ssize_t backlight_show_max_brightness(struct device *dev,
    struct device_attribute *attr, char *buf)
    {
    struct backlight_device *bd = to_backlight_device(dev);
    return sprintf(buf, "%d ", bd->props.max_brightness); //输出最大亮度
    }

      这个函数很简单,重点是引入了几个backlight背光子系统的几个重要的数据结构。

    1.2.struct backlight_device

    89 struct backlight_device {
    90 /* Backlight properties */
    91 struct backlight_properties props;
    92
    93 /* Serialise access to update_status method */
    94 struct mutex update_lock;
    95
    96 /* This protects the 'ops' field. If 'ops' is NULL, the driver that
    97 registered this device has been unloaded, and if class_get_devdata()
    98 points to something in the body of that driver, it is also invalid. */
    99 struct mutex ops_lock;
    100 const struct backlight_ops *ops; //背光设备的相关操作函数
    101
    102 /* The framebuffer notifier block */
    103 struct notifier_block fb_notif;
    104
    105 /* list entry of all registered backlight devices */
    106 struct list_head entry;
    107
    108 struct device dev;
    109
    110 /* Multiple framebuffers may share one backlight device */
    111 bool fb_bl_on[FB_MAX];
    112
    113 int use_count;
    114 };

      其中backlight_properties和backlight_ops结构体定义如下:

    67 /* This structure defines all the properties of a backlight */
    68 struct backlight_properties {
    70 int brightness;
    72 int max_brightness;
    75 int power;
    78 int fb_blank;
    80 enum backlight_type type;
    82 unsigned int state;
    87 };

    52 struct backlight_ops {
    53 unsigned int options;
    58 int (*update_status)(struct backlight_device *); //更新背光设备亮度等属性
    61 int (*get_brightness)(struct backlight_device *); //获取背光设备亮度
    64 int (*check_fb)(struct backlight_device *, struct fb_info *);
    65 };

      继续看backlight类属性中写的函数,例如设置当前背光值函数backlight_store_brightness:

    static ssize_t backlight_store_brightness(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 = strict_strtoul(buf, 0, &brightness);
    if (rc)
    return rc;

    rc = -ENXIO;
    mutex_lock(&bd->ops_lock);
    if (bd->ops) {
    if (brightness > bd->props.max_brightness)
    rc = -EINVAL;
    else {
    pr_debug("backlight: set brightness to %lu ", brightness);
    bd->props.brightness =brightness; //传入背光值
    backlight_update_status(bd); //调用backlight_update_status设备背光值
    rc = count;
    }
    }
    mutex_unlock(&bd->ops_lock);
    backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);
    return rc;
    }

    static inline void backlight_update_status(struct backlight_device *bd)
    {
    mutex_lock(&bd->update_lock);
    if (bd->ops && bd->ops->update_status)
    bd->ops->update_status(bd); //调用背光操作函数中改变背光状态函数update_status
    mutex_unlock(&bd->update_lock);
    }

    留个悬念: bd->ops->update_status(bd); 会调用哪个函数?

    1.3.Backlight APIs:

    Register/unregister backlight device
    在/sys/class/backlight/目录下注册和移除具体的背光设备:
    struct backlight_device *backlight_device_register(const char *name,struct device *dev, void *devdata, struct backlight_ops *ops);
    void backlight_device_unregister(struct backlight_device *bd);
    1
    2
    3
    Get backlight device/Drop backlight reference
    struct backlight_device *devm_of_find_backlight(struct device *dev);
    static void devm_backlight_release(void *data);
    1
    2
    Enable backlight/Disable backlight
    static inline int backlight_enable(struct backlight_device *bd);
    static inline int backlight_disable(struct backlight_device *bd)
    1
    2
    2.PWM Backlight

    2.1.结构体

    struct platform_pwm_backlight_data:

    10 struct platform_pwm_backlight_data {
    11 int pwm_id;
    12 unsigned int max_brightness;
    13 unsigned int dft_brightness;
    14 unsigned int lth_brightness;
    15 unsigned int pwm_period_ns;
    16 unsigned int *levels;
    17 unsigned int post_pwm_on_delay;
    18 unsigned int pwm_off_delay;
    19 /* TODO remove once all users are switched to gpiod_* API */
    20 int enable_gpio;
    21 int (*init)(struct device *dev);
    22 int (*notify)(struct device *dev, int brightness);
    23 void (*notify_after)(struct device *dev, int brightness);
    24 void (*exit)(struct device *dev);
    25 int (*check_fb)(struct device *dev, struct fb_info *info);
    26 };

    struct pwm_bl_data:

    28 struct pwm_bl_data {
    29 struct pwm_device *pwm;
    30 struct device *dev;
    31 unsigned int period;
    32 unsigned int lth_brightness;
    33 unsigned int *levels;
    34 bool enabled;
    35 struct regulator *power_supply;
    36 struct gpio_desc *enable_gpio;
    37 unsigned int scale;
    38 bool legacy;
    39 unsigned int post_pwm_on_delay;
    40 unsigned int pwm_off_delay;
    41 int (*notify)(struct device *,
    42 int brightness);
    43 void (*notify_after)(struct device *,
    44 int brightness);
    45 int (*check_fb)(struct device *, struct fb_info *);
    46 void (*exit)(struct device *);
    47 };

    2.2.sysfs

      在/sys/class/backlight/目录下创建pwm backlight node,并注册pwm_backlight_ops。

    drivers/video/backlight/pwm_bl.c:
    bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
    &pwm_backlight_ops, &props);

    static const struct backlight_ops pwm_backlight_ops = {
    .update_status = pwm_backlight_update_status, //更新背光亮度
    .get_brightness = pwm_backlight_get_brightness, //获取背光亮度
    };

      其中.update_status = pwm_backlight_update_status, 就是bd->ops->update_status(bd); 调用的函数。

    2.3.驱动分析

    drivers/video/backlight/pwm_bl.c
    pwm_backlight_probe
    pwm_backlight_parse_dt //解析 dts 中的 brightness-levels、default-brightness-level
    //RK3288 还会在这里解析 enable-gpios ,但是 3399 没有,3399 是在 probe 里面用 devm_gpiod_get_optional //获取 enable-gpio 状态的
    devm_gpiod_get_optional //实际上就是封装了 gpio_request_one
    devm_gpio_request_one //申请背光使能 gpio
    devm_pwm_get -> /drivers/pwm/core.c //获得一个pwm
    pwm_get ->
    of_pwm_get ->
    of_parse_phandle_with_args 解析上面dts中的pwms属性.
    of_node_to_pwmchip
    pwm = pc->of_xlate //最终生成struct pwm_device类型.
    pwm_request //申请pwm,防止其他驱动也会使用.
    pwm_set_period //pb->pwm->period = data->pwm_period_ns
    pwm_get_period //获取period.
    dev_set_name(&pdev->dev, "rk28_bl"); //name不能改,用户空间会被用到:/sys/class/backlight/rk28_bl
    backlight_device_register -> /drivers/video/baklight/backlight.c //注册标准背光设备
    device_register
    backlight_register_fb ->
    fb_register_client //callback 是 fb_notifier_callback
    fb_register_client // 注册内核通知链
    backlight_update_status -> //用默认值更新.
    bd->ops->update_status ->
    pwm_backlight_update_status -> //更新背光亮度
    compute_duty_cycle //计算占空比
    pwm_config //配置pwm
    pwm_backlight_power_on //enable背光
    platform_set_drvdata //可以将 pdev 保存成平台总线设备的私有数据,以后再要使用它时只需调用 platform_get_drvdata

    2.3.1.backlight_device_register

    backlight_register_fb
    指定背光通知回调函数fb_notifier_callback,并注册到通知链里。
    Linux 内核中每个模块之间都是独立的,如果模块需要感知其他模块的事件,就需要用到内核通知链。最典型的通知链应用就是 LCD 和 TP 之间,TP 需要根据 LCD 的亮灭来控制是否打开关闭触摸功能。通俗的讲,LCD 会创建一个函数链表,TP 会将 suspend 和 resume 函数添加到链表中,当 LCD 发生亮灭变化时,会根据情况执行链表上所有对应的函数,函数会根据不同的动作执行 TP 的 suspend 和 resume 函数。

    1>.TP 驱动背光通知回调函数

      probe 函数里指定背光通知回调函数gtp_fb_notifier_callback,并注册到通知链里。gtp_fb_notifier_callback函数根据收到的通知event信息调用TS resume 或suspend 函数。

    ts->notifier.notifier_call = gtp_fb_notifier_callback;
    fb_register_client(&ts->notifier);

    /* frame buffer notifier block control the suspend/resume procedure */
    static int gtp_fb_notifier_callback(struct notifier_block *noti, unsigned long event, void *data)
    {
    struct fb_event *ev_data = data;
    struct goodix_ts_data *ts = container_of(noti, struct goodix_ts_data, notifier);
    int *blank;

    if (ev_data && ev_data->data && event == FB_EVENT_BLANK && ts) {
    blank = ev_data->data;
    if (*blank == FB_BLANK_UNBLANK) {
    GTP_DEBUG("Resume by fb notifier.");
    goodix_ts_resume(ts);

    }
    else if (*blank == FB_BLANK_POWERDOWN) {
    GTP_DEBUG("Suspend by fb notifier.");
    goodix_ts_suspend(ts);
    }
    }

    return 0;
    }

    2.3.2.pwm_backlight_update_status

    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 != 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);

    if (brightness > 0) {
    duty_cycle = compute_duty_cycle(pb, brightness);
    pwm_config(pb->pwm, duty_cycle, pb->period);
    pwm_backlight_power_on(pb, brightness);
    } else
    pwm_backlight_power_off(pb);

    if (pb->notify_after)
    pb->notify_after(pb->dev, brightness);

    return 0;
    }

    /include/uapi/linux/fb.h:
    enum {
    /* screen: unblanked, hsync: on, vsync: on */
    FB_BLANK_UNBLANK = VESA_NO_BLANKING,

    /* screen: blanked, hsync: on, vsync: on */
    FB_BLANK_NORMAL = VESA_NO_BLANKING + 1,

    /* screen: blanked, hsync: on, vsync: off */
    FB_BLANK_VSYNC_SUSPEND = VESA_VSYNC_SUSPEND + 1,

    /* screen: blanked, hsync: off, vsync: on */
    FB_BLANK_HSYNC_SUSPEND = VESA_HSYNC_SUSPEND + 1,

    /* screen: blanked, hsync: off, vsync: off */
    FB_BLANK_POWERDOWN = VESA_POWERDOWN + 1
    };

    2.3.3.计算占空比

    static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
    {
    /*一般情况下这个值都为0*/
    unsigned int lth = pb->lth_brightness;
    /*占空比*/
    int duty_cycle;
    /*pb->levels这个表格就是从dts节点brightness-levels中获取的,
    假设进来的参数brightness是254,那么得到的duty_cycle就是1,
    如果没有这个表格,那么就直接是进来的亮度值.*/
    if (pb->levels)
    duty_cycle = pb->levels[brightness];
    else
    duty_cycle = brightness;

    /*假设这里lth是0,那么公式就是duty_cycle * pb->period / pb->scale
    pb->period也就是dts节点 pwms 的第三个参数周期值为 25000
    pb->scale为pb->levels数组中的最大值
    所以这个公式就是按照将Android的纯数值转换成事件周期值对应的占空比.*/
    return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
    }

    2.3.4.更新背光

    pwm_backlight_probe 函数,解析dts,调用backlight_update_status来改变背光;
    sysfs - brightness_store(drivers/video/backlight/backlight.c),设置brightness,然后调用backlight_update_status来改变背光;
    backlight_suspend/backlight_resume;
    backlight_register_fb(new_bd); ->fb_notifier_callback 调用backlight_update_status来改变背光; (drivers/video/backlight/backlight.c)
    static int fb_notifier_callback(struct notifier_block *self,
    unsigned long event, void *data)
    {
    ...
    /*只处理亮屏和灭屏事件.*/
    /* If we aren't interested in this event, skip it immediately ... */
    if (event != FB_EVENT_BLANK && event != FB_EVENT_CONBLANK)
    return 0;
    ...
    if (bd->ops)
    if (!bd->ops->check_fb ||
    bd->ops->check_fb(bd, evdata->info)) {
    bd->props.fb_blank = *(int *)evdata->data;
    //亮屏情况
    if (bd->props.fb_blank == FB_BLANK_UNBLANK)
    bd->props.state &= ~BL_CORE_FBBLANK;
    //灭屏时
    else
    bd->props.state |= BL_CORE_FBBLANK;
    backlight_update_status(bd);
    }
    ...
    }

    2.4.Dtsi settings

    backlight: backlight {
    status = "disabled";
    compatible = "pwm-backlight";
    pwms = <&pwm0 0 25000 0>;
    pwm-names = <backlight>;

    /*开机初始化默认等级,Android起来之后会改变它.*/
    default-brightness-level = <50>;
    enable-gpios = <&gpio 71 GPIO_ACTIVE_HIGH>;

    /*背光可调等级,比如这里是255级,实际反应到占空比就是当前值和数组中最大值的比值,
    例如当前是200,那么最终duty cycle就是200/255.*/
    brightness-levels = <
    0 1 2 3 4 5 6 7
    8 9 10 11 12 13 14 15
    16 17 18 19 20 21 22 23
    24 25 26 27 28 29 30 31
    32 33 34 35 36 37 38 39
    40 41 42 43 44 45 46 47
    48 49 50 51 52 53 54 55
    56 57 58 59 60 61 62 63
    64 65 66 67 68 69 70 71
    72 73 74 75 76 77 78 79
    80 81 82 83 84 85 86 87
    88 89 90 91 92 93 94 95
    96 97 98 99 100 101 102 103
    104 105 106 107 108 109 110 111
    112 113 114 115 116 117 118 119
    120 121 122 123 124 125 126 127
    128 129 130 131 132 133 134 135
    136 137 138 139 140 141 142 143
    144 145 146 147 148 149 150 151
    152 153 154 155 156 157 158 159
    160 161 162 163 164 165 166 167
    168 169 170 171 172 173 174 175
    176 177 178 179 180 181 182 183
    184 185 186 187 188 189 190 191
    192 193 194 195 196 197 198 199
    200 201 202 203 204 205 206 207
    208 209 210 211 212 213 214 215
    216 217 218 219 220 221 222 223
    224 225 226 227 228 229 230 231
    232 233 234 235 236 237 238 239
    240 241 242 243 244 245 246 247
    248 249 250 251 252 253 254 255>;
    };

    说明:

    pwms = <&pwm0 0 25000 0>;

    第一个参数 表示此背光接在 pwm0 上;
    第二个参数 表示 index 为 0,pwm0 下只有 1个 pwm,所以填 0
    第三个参数 表示周期为 25000ns,即频率 为 40k
    第四个参数 表示极性,0 正极性,1 负极性
    正极性 0 表示 背光为正极 0~255 ,占空比从 0~100% 变化
    负极性 1 表示 背光为负极 255~0 ,占空比从 100~0% 变化
    default-brightness-level = <50>;
    表示默认的背光,它存在于开机时候背光初始化到安卓。设置下来新的背光这段时间,default-brightness-level = < 50 > 表示为第 50 个元素的背光亮度。

    enable-gpios = <&gpio 71 GPIO_ACTIVE_HIGH>;
    enable-gpios;表示背光使能脚,这个根据电路原理图配置即可;有的硬件没有这个背光使能脚,那么将这个配置删除,背光驱动通过配置 brightness-levels 数组的第 0 个元素将显示调黑。

    2.4.1.enable-gpios 代码解析

    devm_gpiod_get_optional(&pdev->dev, "enable",GPIOD_ASIS);
    -> of_find_gpio(dev, con_id, idx, &lookupflags);
    ->of_get_named_gpiod_flags
    ->of_find_gpiochip_by_xlate
    ->chip->of_xlate //gpio 驱动optional 实现,如果驱动没有实现,则调用系统实现的of_gpio_simple_xlate
    ->of_xlate_and_get_gpiod_flags
    ->gpiochip_get_desc //获取gpio desc
    1
    2
    3
    4
    5
    6
    7
    3.代码

    drivers/video/backlight/backlight.c
    drivers/video/backlight/pwm_bl.c
    drivers/pwm/sysfs.c
    3.1.CONFIG

    CONFIG_BACKLIGHT_CLASS_DEVICE=y
    CONFIG_LCD_CLASS_DEVICE=y
    CONFIG_BACKLIGHT_LCD_SUPPORT=y
    CONFIG_BACKLIGH_PWM=y
    3.2.Debug

    /sys/class/backlight (drivers/pwm/sysfs.c)
    /sys/device/platform/backlight (drivers/video/backlight/backlight.c)
    /sys/devices/platform/backlight/backlight/backlight

    actual_brightness:这个节点只读,可以通过读取这个节点,获取LCD实际的亮度值。
    brightness:这个节点可读可写,向这个节点写入不同值,可调节LCD亮度。
    max_brightness:这个节点只读,通过读取此节点,获取可以设置的最大亮度级别。
    Note:调节brightness 改变占空比。

    3.3.shell脚本调节背光:

    #!/system/bin/sh or #!/bin/sh
    i=0
    while [ $i -le 255 ]
    do
    echo $i
    echo $i > /sys/class/backlight/backlight/brightness
    i=$((i+1))
    usleep 100000
    done
    1
    2
    3
    4
    5
    6
    7
    8
    9
    refer to

    https://www.dazhuanlan.com/2019/10/25/5db29684e6460/
    https://blog.csdn.net/kris_fei/article/details/52485635
    https://developer.ridgerun.com/wiki/index.php?title=Linux_PWM_Pulse_Width_Modulator
    ————————————————
    版权声明:本文为CSDN博主「Hacker_Albert」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_41028621/article/details/103542751

  • 相关阅读:
    集合
    字典
    Visual Studio 2017 发布
    表现设身处地的方法:杜彬(Ben Duffy)方法
    Can RemObjects SDK parameters be passed via the URI?
    转:RemObjects SDK 简介
    转:RemObject 服务器端自调用的方法
    转: Delphi多层开发方案比较
    Embed
    log4d 的使用(转)
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13068570.html
Copyright © 2011-2022 走看看