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 通过ACPI的“_DDC”来指定EDID

_DDC (Return the EDID for this Device)

This method returns an EDID (Extended Display Identification D ata) s tructure that represents the display output device. This method is required for integrated LCDs that do not have another standard mechanism for returning EDID data.
                《Advanced Configuration and PowerInterface (ACPI) Specification》
                        B.6.5_DDC (Return the EDID for this Device)

对应代码流程

acpi_video_get_edid();
    -> acpi_video_device_EDID();
        -> acpi_evaluate_object(device->dev->handle, "_DDC", &args, &buffer);

3.1.6 查看当前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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值