RK3568平台(背光篇)背光驱动代码分析
一.背光驱动设备树DTS
backlight: backlight {compatible = "pwm-backlight";pwms = <&pwm1 0 5555555 1>;brightness-levels = <77 77 78 78 79 79 80 8182 83 84 85 86 87 87 8888 89 90 90 91 91 92 9394 94 95 95 96 96 97 9798 98 99 99 100 100 101 101102 103 104 104 105 106 107 107108 108 109 109 110 111 112 113114 115 116 116 117 117 118 118119 119 120 121 122 123 124 124125 125 126 126 127 127 128 128129 129 130 130 131 131 132 132133 133 134 134 135 135 136 136137 137 138 138 139 139 140 141142 142 143 144 145 146 146 147147 148 148 149 149 150 151 151152 153 153 154 154 155 156 156157 158 158 159 160 161 162 162163 163 164 164 165 165 166 166167 168 168 169 170 171 172 172173 174 175 175 176 177 178 179 180 181 182 182 183 184 185 186 187 188 189 190 190 191 192 193 194 194 195 196 197 198 199 199 200 201 202 203 204 204 205 206 207 208 209 210 211 211 212 213 214 215 216 216 217 217 218 219 219 220 220 221 222 223 224 224 225 225 226 227 228 229 229 230 231 232 232 233 234 235 236 237 238 239 239 240 240 241 242 242 243 244 245 246 246 247 247 248 249 250 251 251 252 253 254 255>;default-brightness-level = <235>;
};
二.背光驱动代码分析
背光驱动部分代码路径:kernel-5.10\drivers\video\backlight\pwm_bl.c
背光驱动入口部分:
static struct platform_driver pwm_backlight_driver = {.driver = {.name = "pwm-backlight",.pm = &pwm_backlight_pm_ops,.of_match_table = of_match_ptr(pwm_backlight_of_match),},.probe = pwm_backlight_probe,.remove = pwm_backlight_remove,.shutdown = pwm_backlight_shutdown,
};module_platform_driver(pwm_backlight_driver);MODULE_DESCRIPTION("PWM based Backlight Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:pwm-backlight");
先看驱动适配函数pwm_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;if (!data) {ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);if (ret < 0) {dev_err(&pdev->dev, "failed to find platform data\n");return ret;}data = &defdata;}if (data->init) {ret = data->init(&pdev->dev);if (ret < 0)return ret;}pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);if (!pb) {ret = -ENOMEM;goto err_alloc;}if (data->levels) {unsigned int i;for (i = 0; i <= data->max_brightness; i++)if (data->levels[i] > pb->scale)pb->scale = data->levels[i];pb->levels = data->levels;} elsepb->scale = data->max_brightness;pb->notify = data->notify;pb->notify_after = data->notify_after;pb->check_fb = data->check_fb;pb->exit = data->exit;pb->dev = &pdev->dev;pb->enabled = false;pb->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable",GPIOD_ASIS);if (IS_ERR(pb->enable_gpio)) {ret = PTR_ERR(pb->enable_gpio);goto err_alloc;}/** 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,GPIOF_OUT_INIT_HIGH, "enable");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);}/** If the GPIO is not known to be already configured as output, that* is, if gpiod_get_direction returns either GPIOF_DIR_IN 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) != GPIOF_DIR_OUT)gpiod_direction_output(pb->enable_gpio, 1);pb->power_supply = devm_regulator_get(&pdev->dev, "power");if (IS_ERR(pb->power_supply)) {ret = PTR_ERR(pb->power_supply);goto err_alloc;}pb->pwm = devm_pwm_get(&pdev->dev, NULL);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");}if (IS_ERR(pb->pwm)) {ret = PTR_ERR(pb->pwm);if (ret != -EPROBE_DEFER)dev_err(&pdev->dev, "unable to request PWM\n");goto err_alloc;}dev_dbg(&pdev->dev, "got pwm for backlight\n");/** FIXME: pwm_apply_args() should be removed when switching to* the atomic PWM API.*/pwm_apply_args(pb->pwm);/** The DT case will set the pwm_period_ns field to 0 and store the* period, parsed from the DT, in the PWM device. For the non-DT case,* set the period from platform data if it has not already been set* via the PWM lookup table.*/pwm_get_args(pb->pwm, &pargs);pb->period = pargs.period;if (!pb->period && (data->pwm_period_ns > 0))pb->period = data->pwm_period_ns;pb->lth_brightness = data->lth_brightness * (pb->period / pb->scale);memset(&props, 0, sizeof(struct backlight_properties));props.type = BACKLIGHT_RAW;props.max_brightness = data->max_brightness;bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,&pwm_backlight_ops, &props);if (IS_ERR(bl)) {dev_err(&pdev->dev, "failed to register backlight\n");ret = PTR_ERR(bl);if (pb->legacy)pwm_free(pb->pwm);goto err_alloc;}if (data->dft_brightness > data->max_brightness) {dev_warn(&pdev->dev,"invalid default brightness level: %u, using %u\n",data->dft_brightness, data->max_brightness);data->dft_brightness = data->max_brightness;}bl->props.brightness = data->dft_brightness;bl->props.power = pwm_backlight_initial_power_state(pb);backlight_update_status(bl);platform_set_drvdata(pdev, bl);return 0;err_alloc:if (data->exit)data->exit(&pdev->dev);return ret;
}
probe里有几个重要的函数,这里按照调用顺序,依次进行阐述:
pwm_backlight_probe-> pwm_backlight_parse_dt //拿到dts里定义的brightness-levels和default-brightness-level,前者用来应用层控制不同的背光亮度,后者在初始化时会使能一个默认的背光亮度-> devm_gpiod_get_optional //实际上就是封装了 gpio_request_one-> devm_gpio_request_one //申请背光使能 gpio-> devm_pwm_get -> /drivers/pwm/core.c //获得一个pwm-> pwm_request //申请pwm,防止其他驱动也会使用.-> backlight_device_register -> /drivers/video/baklight/backlight.c //注册标准背光设备static const struct backlight_ops pwm_backlight_ops = {.update_status = pwm_backlight_update_status,.check_fb = pwm_backlight_check_fb,};-> pwm_backlight_update_status-> compute_duty_cycle //计算占空比-> pwm_backlight_power_on //enable背光-> backlight_update_status -> //用默认值更新.
compute_duty_cycle函数计算占空比:
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];elseduty_cycle = brightness;/*假设这里lth是0,那么公式就是duty_cycle * pb->period / pb->scalepb->period也就是dts节点 pwms 的第三个参数周期值为 25000pb->scale为pb->levels数组中的最大值所以这个公式就是按照将Android的纯数值转换成事件周期值对应的占空比.*/return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
}
pwm_backlight_power_on 函数通过去拉背光的gpio enable背光
static void pwm_backlight_power_on(struct pwm_bl_data *pb)
{struct pwm_state state;int err;pwm_get_state(pb->pwm, &state);if (pb->enabled)return;err = regulator_enable(pb->power_supply);if (err < 0)dev_err(pb->dev, "failed to enable power supply\n");state.enabled = true;pwm_apply_state(pb->pwm, &state);if (pb->post_pwm_on_delay)msleep(pb->post_pwm_on_delay);if (pb->enable_gpio)gpiod_set_value(pb->enable_gpio, 1); pb->enabled = true;
}
LCD背光驱动,导出给应用层控制背光的brightness、bl_power、actual_brightness等节点
路径:kernel-5.10\drivers\video\backlight\backlight.c
再backlight.c文件里面会给应用层控制背光的brightness、bl_power、actual_brightness等节点
对应于/sys/class/backlight,提供属性和操作函数。
/sys/class/backlight/lcd_backlight@0
cat max_brightness //获取最大的亮度:100
cat actual_brightness //获取实际的亮度:80
echo 100 > brightness //设置亮度为100
echo 80 > brightness //设置亮度为80
static ssize_t bl_power_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.power);
}static ssize_t bl_power_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 power, old_power;rc = kstrtoul(buf, 0, &power);if (rc)return rc;rc = -ENXIO;mutex_lock(&bd->ops_lock);if (bd->ops) {pr_debug("set power to %lu\n", power);if (bd->props.power != power) {old_power = bd->props.power;bd->props.power = power;rc = backlight_update_status(bd);if (rc)bd->props.power = old_power;elserc = count;} else {rc = count;}}mutex_unlock(&bd->ops_lock);return rc;
}
static DEVICE_ATTR_RW(bl_power);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_scale.attr,&dev_attr_type.attr,NULL,
};
ATTRIBUTE_GROUPS(bl_device);
以bl_power为例:最终调用backlight_update_status()来调节背光。
三.应用程序调节背光
下面的两种方式都可以使用backlight节点来控制背光。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define LCD_BACKLIGHT "/sys/class/backlight/lcd_backlight@0/brightness"int main(int argc, char *argv[])
{
#if 0int fd;char *testwrite = "/sys/class/backlight/lcd_backlight@0/brightness";ssize_t len_w;char buf_w[] = "10";//if((fd = open("/sys/class/backlight/lcd_backlight@0/brightness", O_RDWR))<0){/* open()可以带2/3个参数 *///if((fd = open(testwrite,O_RDWR,0777))<0){if((fd = open(LCD_BACKLIGHT, O_RDWR, 0777)) <0 ){printf("open %s failed!\n",testwrite);}//len_w = write(fd, "99", 2);len_w = write(fd, buf_w, strlen(buf_w));if(len_w == -1){perror("write");} else{printf("write function ok!\n");}close(fd);#elseFILE * fp =fopen("/sys/class/backlight/lcd_backlight@0/brightness","w");if (fp == NULL)perror("export open filed");elsefprintf(fp,"%d",100);fclose(fp);
#endifreturn 0;
}