第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > 【Linux驱动篇】LCD背光驱动分析

【Linux驱动篇】LCD背光驱动分析

时间:2018-12-07 02:47:58

相关推荐

【Linux驱动篇】LCD背光驱动分析

LCD背光亮度调节和背光电源控制

一、概述二、设备树添加三、源码分析pwm_bl.cbacklight.c

一、概述

Linux驱动中,可以通过pwm_bl + backlight的方式,实现LCD的背光调节、以及背光电源控制,对应的源码路径为:kernel/drivers/video/backlight/pwm_bl.c和kernel/drivers/video/backlight/backlight.c

需要注意的是,背光由PWM模块调节,背光电源采用regulator模块控制的方式,所以接下来会对两者分别进行简单的阐述。

事实上,背光电源也可以不用regulator模块控制,根据pwm_bl.c的probe函数中,pb->enable_gpio =devm_gpiod_get_optional(&pdev->dev,“enable”,GPIOD_ASIS);可知,在dts中定义enble-gpios=<&gpio2 RK_PA4 GPIO_ACTIVE_HIGH>也可以达到背光电源控制的目的

二、设备树添加

// 配置LCD背光电源,加入到regulator电源管理/ {lcd_bl_en: lcd_bl-en {compatible = "regulator-fixed";regulator-name = "lcd_bl_en"; //此名字会被backlight里的power-supply用到gpio = <&gpio2 RK_PA4 GPIO_ACTIVE_HIGH>;regulator-boot-on;enable-active-high;};};backlight: backlight {compatible = "pwm-backlight";status = "okay";pwms = <&pwm8 0 25000 0>;power-supply = <&lcd_bl_en>; //定义背光电源使用lcd_bl_en(regulator-name)),在pwm_bl.c中会用到brightness-levels = <255 254 253 252 251 250 249 248......7 6 5 4 3 2 1 0>;default-brightness-level = <50>;};&dsi {status = "okay";dsi_panel: panel@0 {compatible = "simple-panel-dsi";status = "okay";reg = <0>;backlight = <&backlight>;reset-gpio = <&gpio0 RK_PA5 GPIO_ACTIVE_LOW>;prepare-delay-ms = <2>;reset-delay-ms = <10>;init-delay-ms = <120>;enable-delay-ms = <120>;disable-delay-ms = <50>;unprepare-delay-ms = <20>;... ...}

首先需要对几个属性进行简单描述:

regulator-boot-on:在boot开机阶段,就开始上电,即对定义的gpio进行拉高或者拉低操作。regulator-always-on:除了跟regulator-boot-on有一样的功能外,还代表了禁止对此电源掉电,也就是无法对该gpio进行后续的控制。regulator-name:若其他模块用到这个regulator,则使用该名字power-supply:根据第3点所说,这里定义为lcd_bl_en,详情见pwm_bl.c中pwm_backlight_probe->pb->power_supply = devm_regulator_get(&pdev->dev, “power”);会查找是否有定义power-supply。(若未定义power-supply。系统也会自动分配一个dummy regulator对象,可跟踪源码kernel/drivers/regulator/core.c:_regulator_get函数)

驱动在加载时,会解析lcd_bl_en,加入到regulator电源管理,详情见kernel/drivers/regulator/fixed.c,调用顺序为:

regulator_fixed_voltage_init

–> reg_fixed_voltage_probe

----> devm_regulator_register

------> regulator_register

在regulator_register函数中,会调用到set_machine_constraints函数,部分代码如下:

static int set_machine_constraints(struct regulator_dev *rdev,const struct regulation_constraints *constraints){int ret = 0;const struct regulator_ops *ops = rdev->desc->ops;.../* If the constraints say the regulator should be on at this point* and we have control then make sure it is enabled.*/if (rdev->constraints->always_on || rdev->constraints->boot_on) {ret = _regulator_do_enable(rdev);if (ret < 0 && ret != -EINVAL) {rdev_err(rdev, "failed to enable\n");return ret;}}...print_constraints(rdev);return 0;}

可以看到,当dts配置了regulator-always-on或者regulator-boot-on属性时,在regulator注册时就会自动执行_regulator_do_enable将GPIO进行拉高或拉低

三、源码分析

pwm_bl.c

背光控制的实际操作函数,背光亮度和背光电源都在本文件中实现。

先看驱动适配函数pwm_backlight_probe

static int pwm_backlight_parse_dt(struct device *dev,struct platform_pwm_backlight_data *data){struct device_node *node = dev->of_node;struct property *prop;int length;u32 value;int ret;if (!node)return -ENODEV;memset(data, 0, sizeof(*data));/* determine the number of brightness levels */prop = of_find_property(node, "brightness-levels", &length);if (!prop)return -EINVAL;data->max_brightness = length / sizeof(u32);/* read brightness levels from DT property */if (data->max_brightness > 0) {size_t size = sizeof(*data->levels) * data->max_brightness;data->levels = devm_kzalloc(dev, size, GFP_KERNEL);if (!data->levels)return -ENOMEM;ret = of_property_read_u32_array(node, "brightness-levels",data->levels,data->max_brightness);if (ret < 0)return ret;ret = of_property_read_u32(node, "default-brightness-level",&value);if (ret < 0)return ret;data->dft_brightness = value;data->max_brightness--;}data->enable_gpio = -EINVAL;return 0;}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_parse_dt:拿到dts里定义的brightness-levels和default-brightness-level,前者用来应用层控制不同的背光亮度,后者在初始化时会使能一个默认的背光亮度,即probe函数最后调用的backlight_update_statusdevm_gpiod_get_optional:从dts里获取到命名为"enble-gpio"或者"enable-gpios"的引脚,此函数是对devm_gpiod_get_index_optional(dev, con_id, 0, flags);的一个封装,引脚号index参数固定为0,只取第一个GPIO。调用顺序为:

devm_gpiod_get_optional

–> devm_gpiod_get_index_optional

----> devm_gpiod_get_index

------> gpiod_get_index

--------> of_find_gpio

其核心函数为of_find_gpio,源码路径为kernel/drivers/gpio/gpiolib-of.c

/* gpio suffixes used for ACPI and device tree lookup */static const char * const gpio_suffixes[] = { "gpios", "gpio" };struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id,unsigned int idx,enum gpio_lookup_flags *flags){char prop_name[32]; /* 32 is max size of property name */enum of_gpio_flags of_flags;struct gpio_desc *desc;unsigned int i;for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {if (con_id)snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id,gpio_suffixes[i]);elsesnprintf(prop_name, sizeof(prop_name), "%s",gpio_suffixes[i]);desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx,&of_flags);if (!IS_ERR(desc) || (PTR_ERR(desc) != -ENOENT))break;}if (IS_ERR(desc))return desc;if (of_flags & OF_GPIO_ACTIVE_LOW)*flags |= GPIO_ACTIVE_LOW;if (of_flags & OF_GPIO_SINGLE_ENDED) {if (of_flags & OF_GPIO_OPEN_DRAIN)*flags |= GPIO_OPEN_DRAIN;else*flags |= GPIO_OPEN_SOURCE;}if (of_flags & OF_GPIO_SLEEP_MAY_LOSE_VALUE)*flags |= GPIO_SLEEP_MAY_LOSE_VALUE;return desc;}

我这里用到的是regulator控制背光电源,所以dts里没有定义enble-gpio

devm_regulator_get:获取regulator,参数为"power",即dts里定义的"power-supply=<&lcd_bl_en>"。调用顺序为:

devm_regulator_get

–> _devm_regulator_get

----> _regulator_get

------> regulator_dev_lookup

--------> of_get_regulator

其核心获取函数为of_get_regulator,源码路径为kernel/drivers/regulator/core.c

static struct device_node *of_get_regulator(struct device *dev, const char *supply){struct device_node *regnode = NULL;char prop_name[32]; /* 32 is max size of property name */dev_dbg(dev, "Looking up %s-supply from device tree\n", supply);snprintf(prop_name, 32, "%s-supply", supply);regnode = of_parse_phandle(dev->of_node, prop_name, 0);if (!regnode) {dev_dbg(dev, "Looking up %s property in node %pOF failed\n",prop_name, dev->of_node);return NULL;}return regnode;}

backlight_device_register:向backlight驱动注册,也是最核心的一个函数,接下来重点介绍一下。 再来看一下背光控制的实际操作函数pwm_backlight_update_status,此函数为pwm_backlight_ops的update_status指针,在调用backlight_device_register时,将pwm_backlight_ops作为参数传入,赋值给backlight,其后backlight_update_status函数实际就是直接调用到了pwm_backlight_update_status函数控制背光

static void pwm_backlight_power_on(struct pwm_bl_data *pb, int brightness){int err;if (pb->enabled)return;err = regulator_enable(pb->power_supply);if (err < 0)dev_err(pb->dev, "failed to enable power supply\n");if (pb->enable_gpio)gpiod_set_value_cansleep(pb->enable_gpio, 1);pwm_enable(pb->pwm);pb->enabled = true;}static void pwm_backlight_power_off(struct pwm_bl_data *pb){if (!pb->enabled)return;pwm_config(pb->pwm, 0, pb->period);pwm_disable(pb->pwm);if (pb->enable_gpio)gpiod_set_value_cansleep(pb->enable_gpio, 0);regulator_disable(pb->power_supply);pb->enabled = false;}static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness){unsigned int lth = pb->lth_brightness;u64 duty_cycle;if (pb->levels)duty_cycle = pb->levels[brightness];elseduty_cycle = brightness;duty_cycle *= pb->period - lth;do_div(duty_cycle, pb->scale);return duty_cycle + lth;}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);} elsepwm_backlight_power_off(pb);if (pb->notify_after)pb->notify_after(pb->dev, brightness);return 0;}static const struct backlight_ops pwm_backlight_ops = {.update_status= pwm_backlight_update_status,.check_fb= pwm_backlight_check_fb,};

pwm_backlight_update_status中通过pwm_config函数调节占空比,从而调节LCD背光亮度,而pwm_backlight_power_on和pwm_backlight_power_off则是对LCD背光电源进行控制了,这里可以看到当设置的brightness大于0或者小于等于0时,会自动控制背光电源的开启和关闭,所以并不需要应用层人为的控制了。

在pwm_backlight_power_on函数中,调用了regulator_enable,此函数就是对电源进行控制,参数为pb->power_supply,即前面通过devm_regulator_get获取到的dts中配置的power-supply。regulator_enable函数调用流程为:

regulator_enable

–> _regulator_enable

----> _regulator_do_enable

其核心函数为_regulator_do_enable,调用regulator_ena_gpio_ctrl函数对定义的电源引脚进行使能控制。同理在pwm_backlight_power_off函数中调用的_regulator_disable则最终调用到_regulator_do_disable对禁用控制。

其后若定义了pb->enable_gpio引脚,也会对此引脚进行使能或禁用,所以这个引脚也可以用来作为背光电源控制。

backlight.c

LCD背光驱动,导出给应用层控制背光的brightness、bl_power、actual_brightness等节点,并注册device设备

初始化,其中需要重点注意一下backlight_class->dev_groups = bl_device_groups;

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;}/** if this is compiled into the kernel, we need to ensure that the* class is registered before users of the class try to register lcd's*/postcore_initcall(backlight_class_init);

创建文件节点,以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);}int backlight_device_set_brightness(struct backlight_device *bd,unsigned long brightness){int rc = -ENXIO;mutex_lock(&bd->ops_lock);if (bd->ops) {if (brightness > bd->props.max_brightness)rc = -EINVAL;else {pr_debug("set brightness to %lu\n", brightness);bd->props.brightness = brightness;rc = backlight_update_status(bd);}}mutex_unlock(&bd->ops_lock);backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);return rc;}EXPORT_SYMBOL(backlight_device_set_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 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 DEVICE_ATTR_RW(brightness)将brightness节点设置为可读写文件节点,展开如下:

#define __ATTR(_name, _mode, _show, _store) {\.attr = {.name = __stringify(_name),\.mode = VERIFY_OCTAL_PERMISSIONS(_mode) },\.show= _show,\.store= _store,\}#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),\_name##_show, _name##_store)#define DEVICE_ATTR_RW(_name) \struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

所以对应于brightness_show和brightness_store函数,在驱动注册时,会生成brightness节点,应用层可以用echo和cat命令对其读写。读操作调用brightness_show,写操作调用brightness_store,进而调用backlight_device_set_brightness进行设置背光亮度。

接下来看ATTRIBUTE_GROUPS(bl_device)定义,首先来看ATTRIBUTE_GROUPS定义。

#define __ATTRIBUTE_GROUPS(_name)\static const struct attribute_group *_name##_groups[] = {\&_name##_group,\NULL,\}#define ATTRIBUTE_GROUPS(_name)\static const struct attribute_group _name##_group = {\.attrs = _name##_attrs,\};\__ATTRIBUTE_GROUPS(_name)

由此可以看出相当于定义了一个bl_device_groups变量,而在初始化时,有这么一行代码:backlight_class->dev_groups = bl_device_groups; 在其后创建文件节点时,会将bl_device_groups变量里的attr,也就是bl_device_attrs全部生成相应的节点。

static const struct attribute_group *bl_device_groups[]={{.attrs = bl_device_attrs},NULL,}

此时介绍一下最重要的backlight_device_register函数,定义如下:

struct backlight_device *backlight_device_register(const char *name,struct device *parent, void *devdata, const struct backlight_ops *ops,const struct backlight_properties *props){struct backlight_device *new_bd;int rc;pr_debug("backlight_device_register: name=%s\n", name);new_bd = kzalloc(sizeof(struct backlight_device), GFP_KERNEL);if (!new_bd)return ERR_PTR(-ENOMEM);mutex_init(&new_bd->update_lock);mutex_init(&new_bd->ops_lock);new_bd->dev.class = backlight_class;new_bd->dev.parent = parent;new_bd->dev.release = bl_device_release;dev_set_name(&new_bd->dev, "%s", name);dev_set_drvdata(&new_bd->dev, devdata);/* Set default properties */if (props) {memcpy(&new_bd->props, props,sizeof(struct backlight_properties));if (props->type <= 0 || props->type >= BACKLIGHT_TYPE_MAX) {WARN(1, "%s: invalid backlight type", name);new_bd->props.type = BACKLIGHT_RAW;}} else {new_bd->props.type = BACKLIGHT_RAW;}rc = device_register(&new_bd->dev);if (rc) {put_device(&new_bd->dev);return ERR_PTR(rc);}rc = backlight_register_fb(new_bd);if (rc) {device_unregister(&new_bd->dev);return ERR_PTR(rc);}new_bd->ops = ops;#ifdef CONFIG_PMAC_BACKLIGHTmutex_lock(&pmac_backlight_mutex);if (!pmac_backlight)pmac_backlight = new_bd;mutex_unlock(&pmac_backlight_mutex);#endifmutex_lock(&backlight_dev_list_mutex);list_add(&new_bd->entry, &backlight_dev_list);mutex_unlock(&backlight_dev_list_mutex);blocking_notifier_call_chain(&backlight_notifier,BACKLIGHT_REGISTERED, new_bd);return new_bd;}EXPORT_SYMBOL(backlight_device_register);

其中调用了device_register函数,在这个函数中添加设备的操作,其中包括了导出设备节点下的文件,即以上描述的brightness、bl_power等节点,其调用流程为:

device_register

–> device_add

----> device_add_attrs

------> device_add_groups

--------> sysfs_create_groups

在sysfs_create_groups函数中最终将传入的bl_device_groups里的attr导出为文件,此时/sys/class/backlight/backlight/目录下能看到brightness、bl_power等文件节点(实际上是/sys/devices/platform/backlight/backlight/backlight/的软链接)。

No pains, no gains.

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。