Linux PWM framework(二)- 背光子系统

了解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\n", 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\n", 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);
  • Get backlight device/Drop backlight reference
 struct backlight_device *devm_of_find_backlight(struct device *dev);
 static void devm_backlight_release(void *data);
  • Enable backlight/Disable backlight
static inline int backlight_enable(struct backlight_device *bd);
static inline int backlight_disable(struct backlight_device *bd)

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);  
        }  
...  
}

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
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

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
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
PWM-GPIO驱动程序是Linux内核中的一个驱动模块,用于控制嵌入式系统中的GPIO引脚产生PWM信号。该驱动程序允许开发人员通过编程的方式来控制GPIO引脚的电平变化,从而产生不同占空比的PWM信号。 在Linux内核中,PWM-GPIO驱动程序通过向用户空间提供了相应的接口来实现PWM信号的控制。开发人员可以通过打开相应的设备节点,并使用相应的系统调用函数来设置PWM的频率、占空比等参数,从而实现对GPIO引脚的PWM信号的控制。 驱动程序的核心部分是一个PWM子系统,它与GPIO子系统紧密集成。PWM子系统负责管理PWM信号的生成和控制,而GPIO子系统负责管理GPIO引脚的配置和操作。PWM-GPIO驱动程序在这两个子系统之间起着桥梁的作用。 PWM-GPIO驱动程序的实现方式与硬件平台相关,每个平台可能有不同的具体实现。在驱动程序的初始化过程中,必须先配置GPIO引脚的功能为PWM模式,并将相应的寄存器映射到内核中,以便能够通过对寄存器的操作来控制GPIO引脚。驱动程序还需要初始化PWM子系统,为每个GPIO引脚分配相应的PWM通道,并根据需求设置PWM的频率、占空比等参数。 通过PWM-GPIO驱动程序,开发人员可以方便地利用Linux内核的功能来实现对嵌入式系统中GPIO引脚产生PWM信号的控制。这为开发PWM驱动、控制舵机、LED等应用提供了便捷的方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值