Linux内核DRM显示功能框架中,获取分辨率、刷新率等参数的方式

1 简介

不同的显示屏支持的显示时序也是不同的,linux内核需要获取这些显示时序参数来适用不同的显示屏。

显示时序的具体解释请看:【RGB-时序分析】_rgb时序-CSDN博客

本文的内核代码版本时kernel-5.4.18

2 数据结构

2.1 struct drm_connector;

struct drm_connector {
    ......
    /**  
     * @modes:
     * Modes available on this connector (from fill_modes() + user).
     * Protected by &drm_mode_config.mutex.
     */
    struct list_head modes;

    ......

    /**  
     * @probed_modes:
     * These are modes added by probing with DDC or the BIOS, before
     * filtering is applied. Used by the probe helpers. Protected by
     * &drm_mode_config.mutex.
     */
    struct list_head probed_modes;
    .....
};

drm_mode_probed_add()函数会将struct drm_display_mode添加到链表probed_modes上。
drm_connector_list_update()会把链表 probed_modes上的struct drm_display_mode移动到链表modes上。

2.2 struct drm_display_mode;

/* 
 * ......
 * The horizontal and vertical timings are defined per the following diagram.
 *
 * ::
 *
 *
 *               Active                 Front           Sync           Back
 *              Region                 Porch                          Porch
 *     <-----------------------><----------------><-------------><-------------->
 *       //|
 *      // |
 *     //  |..................               ................
 *                                                _______________
 *     <----- [hv]display ----->
 *     <------------- [hv]sync_start ------------>
 *     <--------------------- [hv]sync_end --------------------->
 *     <-------------------------------- [hv]total ----------------------------->*
 *
 * ......
 */

struct drm_display_mode {
    /** 
     * @head:
     *
     * struct list_head for mode lists.
     */
    struct list_head head;
    ......
    /**
     * @clock:
     *
     * Pixel clock in kHz.
     */
    int clock;      /* in kHz */
    int hdisplay;
    int hsync_start;
    int hsync_end;
    int htotal;
    int hskew;
    int vdisplay;
    int vsync_start;
    int vsync_end;
    int vtotal;
    int vscan;
    ......
};

3 获取显示参数的方式

3.1 通过EDID数据获取显示参数

3.1.1 简介

常见的外接显示器中都会包含一个EDID数据,用来表示本显示器支持的显示时序。
VGA和HDMI等显示接口中都有I2C总线,CPU可以通过这个I2C总线读取外接显示器的EDID信息,通过解析EDID信息来获取显示参数。
VGA和HDMI接口中的I2C总线被称为DDC(Display Data Channel)。

通过I2C总线直接读取EDID是最常见的方式,除此之外,还有两种方式:

  • 通过firmware方式指定EDID
  • 通过debugfs指定EDID

3.1.2 通过I2C总线获取EDID的代码

3.1.2.1 通过I2C总线读取EDID数据 
drm_get_edid();
    -> drm_do_get_edid();
        -> get_edid_block();
            -> drm_do_probe_ddc_edid();    //get EDID information via I2C
3.1.2.2 将EDID数据解析成struct drm_display_mode

drm_get_edid()函数返回的是struct edid,可以使用 drm_add_edid_modes()函数将struct edid解析成struct drm_display_mode,并添加到struct drm_connector->probed_modes链表。

        drm_add_edid_modes();         //drivers/gpu/drm/drm_edid.c
            -> add_detailed_modes();
            -> add_cvt_modes();
                -> do_cvt_mode();
                    -> drm_cvt_modes();
                        -> drm_cvt_mode();
                        -> drm_mode_probed_add();
            -> add_standard_modes(); 
                -> drm_mode_std();  
                    -> drm_cvt_mode(); / drm_mode_find_dmt(); / drm_gtf_mode();
                -> drm_mode_probed_add();
            -> add_established_modes();
                -> drm_mode_probed_add();
            -> add_cea_modes();
            -> add_alternate_cea_modes();
                -> drm_mode_probed_add();
 3.1.2.3 整体流程

以上流程一般是在connector_funcs->get_modes()函数里完成,例如

amdgpu_connector_vga_get_modes();
    -> amdgpu_connector_get_edid();
        -> drm_get_edid();
    -> amdgpu_connector_ddc_get_modes();
        -> drm_add_edid_modes();

3.1.3 通过firmware方式指定EDID

linux-5.4.18内核强制输出图形显示信号 并通过firmware文件指定分辨率_video=lvds-1-CSDN博客

3.1.4 通过debugfs指定EDID

将EDID数据写到下面的文件里
        /sys/kernel/debug/dri/0/<connector_name>/edid_override

对应的内核处理流程

edid_write();
    -> connector->override_edid = XXX;
    -> drm_connector_update_edid_property();
        -> connector->edid_blob_ptr;

 3.1.5 查看当前connector的EDID

方法一、hexdump /sys/class/drm/card0/<connector>/edid

方法二、通过/var/log/Xorg.0.log文件查看
可以在/var/log/Xorg.0.log文件中看到以下信息

[   543.902] (II) modeset(0): EDID (in hex):
[   543.902] (II) modeset(0):   00ffffffffffff0030e4960600000000
[   543.902] (II) modeset(0):   001e0104a51d127807ed85a7544c9c26
[   543.902] (II) modeset(0):   0e505400000001010101010101010101
[   543.902] (II) modeset(0):   010101010101353c80a070b023403020
[   543.902] (II) modeset(0):   36001eb31000001a0000000000000000
[   543.902] (II) modeset(0):   00000000000000000000000000fe004c
[   543.902] (II) modeset(0):   4720446973706c61790a2020000000fe
[   543.902] (II) modeset(0):   004c503133335755312d5350423100ec

 3.2 没有EDID时使用内核自带的显示参数

当内核无法获取EDID时,内核会使用drm_dmt_modes[]中的显示参数(1024x768)

内核代码中自带了一组显示参数,通过数组drm_dmt_modes[]记录。

/*
 * Autogenerated from the DMT spec.
 * This table is copied from xfree86/modes/xf86EdidModes.c.
 */
static const struct drm_display_mode drm_dmt_modes[] = {
    /* 0x01 - 640x350@85Hz */
    { DRM_MODE("640x350", DRM_MODE_TYPE_DRIVER, 31500, 640, 672, 
           736, 832, 0, 350, 382, 385, 445, 0,
           DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
    /* 0x02 - 640x400@85Hz */
    { DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 31500, 640, 672, 
           736, 832, 0, 400, 401, 404, 445, 0,
           DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
    /* 0x03 - 720x400@85Hz */
    { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 756, 
           828, 936, 0, 400, 401, 404, 446, 0,
           DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
......

3.3 通过启动参数(video=XXX)获取显示参数

可以在内核启动参数里通过 "video=XXX" 指定显示的参数,例如:video=VGA-1:1920x1080M@60

解析 "video=XXX" 的函数是

drm_mode_parse_command_line_for_connector();
    -> drm_mode_parse_cmdline_extra();
        -> case 'i':    mode->interlace = true;
        -> case 'm':  mode->margins = true;
        -> case 'D':   mode->force = DRM_FORCE_ON 或者 DRM_FORCE_ON_DIGITAL;
        -> case 'd':   mode->force = DRM_FORCE_OFF;
        -> case 'e':   mode->force = DRM_FORCE_ON;
    -> drm_mode_parse_cmdline_res_mode();
        -> xres = simple_strtol(str, &end_ptr, 10);
        -> yres = simple_strtol(str, &end_ptr, 10);
        -> case 'M':   cvt = true;
        -> case 'R':    rb = true;
        -> mode->xres = xres;
        -> mode->yres = yres;
        -> mode->cvt = cvt;
        -> mode->rb = rb;

3.4 通过设备树获取显示参数

3.4.1 设备树示例

//arch/arm/boot/dts/wm8650-mid.dts
&fb {
    bits-per-pixel = <16>;

    display-timings {
        native-mode = <&timing0>;
        timing0: 800x480 {
            clock-frequency = <0>; /* unused but required */
            hactive = <800>;
            vactive = <480>;
            hfront-porch = <40>;
            hback-porch = <88>;
            hsync-len = <0>;
            vback-porch = <32>;
            vfront-porch = <11>;
            vsync-len = <1>;
        };
    };  
};

3.4.2 解析设备树的函数

of_get_drm_display_mode();
    -> of_get_videomode();
        -> of_get_display_timings();
            -> timings_np = of_get_child_by_name(np, "display-timings");
            -> entry = of_parse_phandle(timings_np, "native-mode", 0);
            -> of_parse_display_timing();
                -> parse_timing_property(np, "hback-porch", &dt->hback_porch);
                -> parse_timing_property(np, "hfront-porch", &dt->hfront_porch);
                -> parse_timing_property(np, "hactive", &dt->hactive);
                -> parse_timing_property(np, "hsync-len", &dt->hsync_len);
                ......

 

4 整体代码流程

内核中经常使用drm_helper_probe_single_connector_modes()函数来获取显示参数

drm_helper_probe_single_connector_modes();
    -> (*connector_funcs->get_modes)(connector);        //显卡驱动通过I2C总线读取EDID
    -> drm_add_override_edid_modes();                   //获取debugfs中 和 firmware中的EDID
    -> drm_add_modes_noedid(connector, 1024, 768);      //使用内核自带的显示参数(1024x768)
    -> drm_helper_probe_add_cmdline_mode(connector);    //使用启动参数video=XXX指定的显示参数
    -> drm_connector_list_update(connector);

5 查看connector当前使用的显示参数

方法一、/sys/class/drm/card0/<connector>/modes

方法二、执行命令:xrandr

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值