Linux显卡驱动亮度调节功能书写指南.md

综述

最近在一些项目中,接触了一些嵌入式领域的常用显卡。这类显卡一般都是不提供亮度调节等功能的,因此这部分需要自己实现。这也是第一次从头实现背光这一套,还比较简单的,记录一下。

一般来讲,内核驱动的通用套路是,内核把公共的部分抽象出来做好,和设备相关的部分就需要各设备驱动自己做。这也就是我们常说的驱动框架,背光设备的话,肯定是套drm驱动里面的东西。背光设备初始化,一定是在显卡初始化里面做的,初始化好的的背光设备,会在/sys/class/backlight/XXX,上层调用这个接口,驱动处理相关的事件。

因此背光设备主要做的有两部分:设备初始化和上层是事件的处理。

初始化

drm专门提供了一个接口使用从来初始化背光设备的,定义在include/drm/drm_connector.h中的struct drm_connector_funcs结构体里。

    /**
     * @late_register:
     *
     * This optional hook can be used to register additional userspace
     * interfaces attached to the connector, light backlight control, i2c,
     * DP aux or similar interfaces. It is called late in the driver load
     * sequence from drm_connector_register() when registering all the
     * core drm connector interfaces. Everything added from this callback
     * should be unregistered in the early_unregister callback.
     *
     * This is called while holding &drm_connector.mutex.
     *
     * Returns:
     *
     * 0 on success, or a negative error code on failure.
     */
    int (*late_register)(struct drm_connector *connector);

如果这个函数实现为空,则驱动认为没有背光设备,不执行。请注意,这个函数广义上的操作是在sys下注册背光设备接口,已经在drm_connector_register里面写好了调用,不要自己写调用也不要问我是怎么知道的。drm_connector_register这个函数的主要作用是在sys下注册每一个connector,一般注册路径是/sys/devices/pciXXX/.../drm/这里,之后会check看看显卡驱动是否实现了late_register接口。

int drm_connector_register(struct drm_connector *connector)

{

    int ret = 0;
    ret = drm_sysfs_connector_add(connector);
    if (ret)
        goto unlock;

    if (connector->funcs->late_register) {
        ret = connector->funcs->late_register(connector);
        if (ret)
            goto err_debugfs;
    }

    drm_mode_object_register(connector->dev, &connector->base);
    connector->registered = true;
    goto unlock;

err_debugfs:
    drm_debugfs_connector_remove(connector);
err_sysfs:
    drm_sysfs_connector_remove(connector);
}

上面的函数是删减过得, 可以看到注册完connector的函数之后,判断显卡驱动是否实现了late _register接口,如果实现了就调用。这里请注意**late_register返回非0认为注册失败,会卸载connector的sys接口**,也不要问我为啥要强调这句话。实现背光驱动的第一步,实现late_register函数,并填充在drm_connector_funs中。以龙芯的背光设备为例:

/**

 * These provide the minimum set of functions required to handle a connector
 *
 * Control connectors on a given device.
 * The functions below allow the core DRM code to control connectors,
 * enumerate available modes and so on.
 */
static const struct drm_connector_funcs loongson_connector_funcs = {
...
    .detect = loongson_connector_detect,
    .late_register = loongson_connector_late_register,
    .fill_modes = drm_helper_probe_single_connector_modes,
...
};

int loongson_connector_late_register(struct drm_connector *connector)
{
...
        backlight_pwm_register(ls_connector);
        ret = loongson_connector_pwm_init(ls_connector);
        if (ret == 0) {
            ret = loongson_connector_backlight_register(
                ls_connector);
...

一般late_register是实现一些和自己硬件相关的内容,然后再注册背光设备。在龙芯驱动里,先是初始化了相关的寄存器什么的。所以龙芯显卡的背光调节是通过pwm做的。这里每个显卡硬件上设计都不一样,实现也不一样,不具备什么通用性。先看backlight_pwm_register函数

void backlight_pwm_register(struct loongson_connector *ls_connector)
{
    ls_connector->bl.min = LOONGSON_BL_MIN_LEVEL;
    ls_connector->bl.max = LOONGSON_BL_MAX_LEVEL;
    
    ls_connector->bl.get_resource = loongson_connector_pwm_get_resource;
    ls_connector->bl.free_resource = loongson_connector_pwm_free_resource;
    ls_connector->bl.setup = loongson_connector_pwm_setup;
    ls_connector->bl.get_brightness = loongson_connector_pwm_get;
    ls_connector->bl.set_brightness = loongson_connector_pwm_set;
    ls_connector->bl.enable = loongson_connector_bl_enable;
    ls_connector->bl.disable = loongson_connector_bl_disable;
    ls_connector->bl.power = loongson_connector_lvds_power;
}

struct loongson_backlight {

    struct backlight_device *device;
    struct pwm_device *pwm;
    u32 pwm_id;
    u32 pwm_polarity;
    u32 pwm_period;
    bool present;
    bool hw_enabled;
    unsigned int level, max, min;

    int (*get_resource)(struct loongson_connector *ls_connector);
    void (*free_resource)(struct loongson_connector *ls_connector);
    int (*setup)(struct loongson_connector *ls_connector);
    unsigned int (*get_brightness)(struct loongson_connector *ls_connector);
    void (*set_brightness)(struct loongson_connector *ls_connector,
                   unsigned int level);
    void (*enable)(struct loongson_connector *ls_connector);
    void (*disable)(struct loongson_connector *ls_connector);
    void (*power)(struct loongson_connector *ls_connector, bool enable);

};

初始化connector中的loongson_backlight结构,这个结构都是和硬件的相关的,基本围绕都是就是读写pwm,使能禁用pwm等等。loongson_connector_pwm_init函数 中就是调用bl->get_resourcesbl->setup,也都是和硬件相关的。接下来主要是loongson_connector_backlight_register函数。

/**
 * loongson_connector_backlight_register
 * @ls_connector loongson drm connector
 * @return 0 is ok .
 * */

int loongson_connector_backlight_register(
    struct loongson_connector *ls_connector)
{
    struct backlight_properties props;

    memset(&props, 0, sizeof(props));
    props.type = BACKLIGHT_RAW;
    props.max_brightness = ls_connector->bl.max;
    props.brightness = ls_connector->bl.level;

    ls_connector->bl.device =
        backlight_device_register("loongson-gpu",
                      ls_connector->base.kdev, ls_connector,
                      &ls_backlight_device_ops, &props);
...
    return 0;
}

初始化backlight设备的属性,之后调用backlight_device_register函数在刚刚注册好的connector sys接口下注册一个loongson_gpu接口,同时在sys/class/backlight下创建一个连接文件指向刚刚创建的接口。注册函数的参数分别是:背光设备名称、父母设备、connector、背光设备操作函数,背光设备属性。

uos@uos-PC:~$ ls /sys/class/backlight/loongson-gpu
0 -r--r--r-- 1 root root 8.0K 3月 4 13:54 actual_brightness
0 -rw-r--r-- 1 root root 8.0K 3月 4 13:54 bl_power
0 -rw-r--r-- 1 root root 8.0K 3月 4 13:55 brightness
0 lrwxrwxrwx 1 root root 0 3月 4 13:54 device -> ../../card0-DVI-I-1
0 -r--r--r-- 1 root root 8.0K 3月 13 2020 max_brightness
0 drwxr-xr-x 2 root root 0 3月 4 13:54 power
0 lrwxrwxrwx 1 root root 0 1月 1 1970 subsystem -> ../../../../../../../../../class/backlight
0 -r--r--r-- 1 root root 8.0K 3月 13 2020 type
0 -rw-r--r-- 1 root root 8.0K 1月 1 1970 uevent

关注和brightness相关的几个接口,actual_brightness max_brightness和brightness,前两个所有用户只有读权限,最后一个brightness超级用户拥有写权限。max_brightness表示显卡支持的最大亮度,接口一旦注册好之后,内容不会变。actual_brightness表示当前亮度,随着用户设置而改变的。

事件处理

这部分就是真正硬件相关了,要从特定寄存器中读出和设置亮度。内核提供了一组backlight操作函数——struct backlight_ops

struct backlight_ops {
    unsigned int options;

#define BL_CORE_SUSPENDRESUME (1 << 0)

    /* Notify the backlight driver some property has changed */
    int (*update_status)(struct backlight_device *);
    /* Return the current backlight brightness (accounting for power,
       fb_blank etc.) */
    int (*get_brightness)(struct backlight_device *);
    /* Check if given framebuffer device is the one bound to this backlight;
       return 0 if not, !=0 if it is. If NULL, backlight always matches the fb. */
    int (*check_fb)(struct backlight_device *, struct fb_info *);
};

第一个操作函数update_status函数是用来设置亮度的,echo 100 > brightness就是调用这个函数。当执行cat /sys/class/backlight/loongson-gpu/actual_brightness就会调到get_brightness里,用来获得目前的亮度值。这两个功能都是显卡 硬件有关,因此这两个操作函数需要在显卡驱动中自己实现。以龙芯显卡为例,update_status接口实现:

static int loongson_connector_backlight_update(struct backlight_device *bd)
{
    bool enable;
    struct loongson_connector *ls_connector = bl_get_data(bd);
    struct loongson_backlight *backlight = &ls_connector->bl;

    enable = bd->props.power == FB_BLANK_UNBLANK;
    if (enable) {
        /*Only enable hw once*/
        if (!backlight->hw_enabled)
            ls_connector->bl.enable(ls_connector);
    } else
        ls_connector->bl.disable(ls_connector);

    backlight->level = bd->props.brightness;
    ls_connector->bl.set_brightness(ls_connector, backlight->level);
    return 0;
}

从代码里看出,在调用之前实现的loongson backlightset brightness之前先使能pwm,pwm使能和写亮度,一般来讲都是写寄存器或者写什么io,查手册就能看到。获取亮度函数也和这个类似。


static int loongson_connector_get_brightness(struct backlight_device *bd)
{
    struct loongson_connector *ls_connector = bl_get_data(bd);

    if (ls_connector->bl.get_brightness)
        return ls_connector->bl.get_brightness(ls_connector);

    return -ENOEXEC;
}

unsigned int loongson_connector_pwm_get(struct loongson_connector *ls_connector)
{
    u16 duty_ns, period_ns;
    u32 level;

    if (IS_ERR(ls_connector->bl.pwm))
        return 0;

    period_ns = ls_connector->bl.pwm_period;
    duty_ns = pwm_get_duty_cycle(ls_connector->bl.pwm);

    level = DIV_ROUND_UP((duty_ns * ls_connector->bl.max), period_ns);
    level = clamp(level, ls_connector->bl.min, ls_connector->bl.max);
    return level;
}

这里真正的实现不用太关注,在实现的时候,只要清楚手里的这块显卡亮度是怎么读出来和设置就够啦。

总结

实现一个显卡背光模块非常简单,主要分为两部分:

  • 实现背光获取和更新函数
  • 初始化背光设备,定义属性,并在sys中注册接口,
    drm已经做好了绝大部分的通用模块,只需要查号手册确定寄存器什么的就成功啦。

常见问题

  • 为什么注册背光设备之后,sys下connector设备都不见了?

    drm_connector_register函数中,调用late_register之后判断,如果返回了非0值,就会走error处理。error处理就会unregister connector之前注册好的接口。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 领航者zynq之linux驱动开发指南_v1.3.pdf 是一份介绍如何在zynq芯片上进行linux驱动开发的文档,包括驱动编写、编译、安装和测试等方面的内容。 该文档首先介绍了zynq芯片的基本概念和结构,以及如何搭建linux开发环境。接着,详细讲解了linux设备驱动框架的基本原理和驱动开发流程。 在具体的驱动编写方面,文档提供了大量的示例代码和详细的注释,让开发者能够更好地理解驱动程序的实现原理。同时,也介绍了驱动编译、安装和调试等方面的基本操作。 总的来说,该文档是一份非常实用和全面的linux驱动开发指南,适合于想要在zynq芯片上进行linux驱动开发的工程师和开发者使用。它不仅提供了理论上的知识,还提供了大量的实践操作和示例代码,让读者能够更好地掌握驱动开发的技巧和方法,提高开发效率。 ### 回答2: 《领航者Zynq之Linux驱动开发指南_v1.3.pdf》是一本关于Zynq处理器的Linux驱动开发指南。该指南详细介绍了如何开发驱动程序以在Zynq平台上与硬件设备进行通信。 该指南包含以下内容: 1. Zynq处理器概述:介绍了Zynq处理器的架构、特性和基本原理。 2. 设备树(Device Tree):介绍了设备树的概念、原理和使用方法,让读者了解如何用设备树描述硬件设备和编写驱动程序。 3. 驱动程序框架:介绍了驱动程序的基本框架和编写方法,以及如何使用Linux内核提供的API进行编程。 4. 驱动程序的调试和优化:介绍了Linux内核的调试方法和工具,以及如何优化驱动程序的性能和可靠性。 该指南是一本非常实用的学习资源,适合需要在Zynq平台上进行Linux驱动开发的工程师和学生使用。它详细讲解了Zynq处理器的基本知识和驱动程序开发的方法,为读者提供了全面的指导和参考。 ### 回答3: 《领航者zynq之linux驱动开发指南_v1.3.pdf》是一本关于针对领航者Zynq芯片进行Linux驱动开发的指南。该指南详细介绍了如何使用Zynq芯片的板级支持包(BSP)创建Linux操作系统,并在此基础上进行驱动开发。指南中包含了丰富的实例代码和图示,能够帮助读者深入理解驱动开发过程。 该指南主要方法是以实际应用场景为基础,通过分析应用场景中所需实现的功能,从而引出需要编写的驱动程序,并给出了具体的实现方法。指南中所介绍的应用场景包括LED灯控制、按键输入、串口通信、定时器等,这些场景涵盖了嵌入式系统开发的一般需求,可以帮助读者快速掌握Linux驱动开发的方法和技巧。 同时,指南中也提到了一些开发工具的使用方法,如Xilinx的Vivado开发套件和Linux内核调试工具。这些开发工具的运用有助于提高开发效率,并且可以帮助开发者快速解决问题。 总体来说,该指南是一本实用性很强的Linux驱动开发指南。无论是对于Zynq芯片的开发者,还是对于嵌入式Linux操作系统的开发者都是很有参考价值的。通过学习该指南,读者可以掌握一些基本的驱动开发方法和理念,并有助于开发出更加稳定和优秀的嵌入式系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值