本文共 8618 字,大约阅读时间需要 28 分钟。
最近在一些项目中,接触了一些嵌入式领域的常用显卡。这类显卡一般都是不提供亮度调节等功能的,因此这部分需要自己实现。这也是第一次从头实现背光这一套,还比较简单的,记录一下。
一般来讲,内核驱动的通用套路是,内核把公共的部分抽象出来做好,和设备相关的部分就需要各设备驱动自己做。这也就是我们常说的驱动框架,背光设备的话,肯定是套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_resources
和bl->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-gpu0 -r--r--r-- 1 root root 8.0K 3月 4 13:54 actual_brightness0 -rw-r--r-- 1 root root 8.0K 3月 4 13:54 bl_power0 -rw-r--r-- 1 root root 8.0K 3月 4 13:55 brightness0 lrwxrwxrwx 1 root root 0 3月 4 13:54 device -> ../../card0-DVI-I-10 -r--r--r-- 1 root root 8.0K 3月 13 2020 max_brightness0 drwxr-xr-x 2 root root 0 3月 4 13:54 power0 lrwxrwxrwx 1 root root 0 1月 1 1970 subsystem -> ../../../../../../../../../class/backlight0 -r--r--r-- 1 root root 8.0K 3月 13 2020 type0 -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 backlight
的set 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下connector设备都不见了?
在drm_connector_register
函数中,调用late_register
之后判断,如果返回了非0值,就会走error处理。error处理就会unregister connector之前注册好的接口。
转载地址:http://qsbsi.baihongyu.com/