亮度调节分析

在Ubuntu下,我们是看不到亮度调节的按钮的(可能大部分用户调节屏幕亮度都是通过显示器按钮直接调节),但是新的银河麒麟和openkylin中,在display设置中出现了亮度控制的按钮可以拖动进行亮度的调节。

其实在ubuntu中,我们还可以通过ddcutil工具来进行屏幕亮度的控制

1
sudo ddcutil setvcp 10 80

通过上述命令就可以将屏幕的亮度调整至80%,如果失败了,可能需要通过apt install来安装ddcutil工具。当然ddcutil不只是控制亮度这一个功能,对于显示器几乎所有的设置都可以使用ddcutil,具体参数使用ddcutil vcpinfo查看。

ddc协议

  1. DDC(Display Data Channel):

    • DDC是一种用于显示器与计算机之间进行通信的协议。它允许计算机通过连接的显示器获取关于显示器的信息,如分辨率、亮度、对比度、色彩设置等。
    • DDC是I2C总线协议的一种应用,通过I2C总线进行数据传输。
    • 最常用的版本是DDC/CI,它允许计算机通过I2C总线向显示器发送控制指令,实现调节显示器参数的功能,如调节亮度、对比度等。
    • DDC通信通常是双向的,计算机可以查询显示器的信息,同时也可以发送控制指令给显示器。
  2. VCP(Virtual Control Panel):

    • VCP是DDC协议中定义的一组控制寄存器和对应的命令,用于对显示器进行远程控制。
    • 通过VCP,计算机可以向支持DDC的显示器发送控制命令,从而调整显示器的参数,如亮度、对比度、色彩设置等。这样,计算机可以实现通过软件远程控制显示器的各种功能。
    • VCP指令的具体定义由VESA制定,是DDC协议的一部分。
  3. EDID(Extended Display Identification Data):

    • 扩展显示器识别数据,一般存储在显示器的EEPROM中。

    • EDID1.0->EDID1.3版本中,EDID的容量是 128Byte,从EDID1.3版本以后,数据容量扩展到 256Byte。

    • 一般通过DDC(或I2C)通道读取EDID,从设备地址是0x50

    EDID是一种由显示器提供的信息,用于描述显示器的特性和功能。每个连接到计算机的显示器都会提供一个EDID数据块,计算机可以通过DDC协议从显示器读取该信息。EDID数据包含了显示器的分辨率、刷新率、尺寸、支持的色彩深度以及其他一些显示器属性。计算机使用EDID来了解显示器的能力和特性,以便正确配置和优化图形输出。

ddcutil通信环境配置

  1. 确保显示器已启用DDC通信
  2. 视频驱动程序

视频驱动程序必须支持i2c总线通信,所有主要的开源驱动程序(noveau,radeno,admdgpu,i915)都支持i2c总线通信,Nvidia专有驱动程序也是如此。

  1. 确保/dev/i2c-N设备存在

通常,i2c总线公开名为/dev/i2c-N的设备,必须加载内核模块i2c-dev才能创建/dev/i2c-N设备

  1. 为代表显示器的/dev/i2c-N设备授予读写权限

ddcutil安装成功但是执行失败,使用ddcutil environment可以探测i2c环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
*** Primary Check 1: Identify video card and driver ***

Obtaining card and driver information from /sys...
Primary video controller at PCI address 0000:01:00.0 (boot_vga flag is not set)
Device class: x030000 VGA compatible controller
Vendor: x10de NVIDIA Corporation
Device: x1382 GM107 [GeForce GTX 745]
Subvendor/Subdevice: 10de/1065 NVIDIA Corporation
Driver name: nouveau
Driver version: Unable to determine
I2C device: i2c-10 name: nvkm-0000:01:00.0-bus-0008
I2C device: i2c-8 name: nvkm-0000:01:00.0-bus-0006
I2C device: i2c-15 name: nvkm-0000:01:00.0-aux-000d
I2C device: i2c-6 name: nvkm-0000:01:00.0-bus-0002
I2C device: i2c-13 name: nvkm-0000:01:00.0-aux-000b
I2C device: i2c-4 name: nvkm-0000:01:00.0-bus-0000
I2C device: i2c-11 name: nvkm-0000:01:00.0-bus-0009
I2C device: i2c-9 name: nvkm-0000:01:00.0-bus-0007
I2C device: i2c-7 name: nvkm-0000:01:00.0-bus-0005
I2C device: i2c-14 name: nvkm-0000:01:00.0-aux-000c
I2C device: i2c-5 name: nvkm-0000:01:00.0-bus-0001
I2C device: i2c-12 name: nvkm-0000:01:00.0-aux-000a
Primary video controller at PCI address 0000:00:02.0 (boot_vga flag is set)
Device class: x030000 VGA compatible controller
Vendor: x8086 Intel Corporation
Device: x5912 HD Graphics 630
Subvendor/Subdevice: 1028/0760 Dell
Driver name: i915
Driver version: Unable to determine
I2C device: i2c-3 name: i915 gmbus dpd
I2C device: i2c-1 name: i915 gmbus dpc
I2C device: i2c-2 name: i915 gmbus dpb

...

Env_Accumulator:
architecture: x86_64
distributor_id Ubuntu
Drivers detected: i915, nouveau
/dev/i2c device numbers: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
sysfs_i2c_devices_exist: true
/sys/bus/i2c device numbers: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

ddcutil相关源码

invoke_i2c_writer函数中,亮度调节的过程主要是将表示亮度值的数据以特定格式写入到I2C总线上,以便通过DDC协议向显示器发送亮度设置命令。具体来说,在调节亮度的过程中,涉及以下几个关键步骤:

  1. 将亮度值80%转换为特定的数据格式:调节亮度的数据需要遵循DDC协议,并根据显示器支持的格式进行编码。这个转换过程主要发生在代码中的set_table_vcp_value()函数中,将亮度值80%转换为特定的表格数据,以便通过DDC协议发送给显示器。
  2. 调用invoke_i2c_writer()执行I2C写入操作:一旦数据格式转换完成,代码中会调用invoke_i2c_writer()函数执行实际的I2C写入操作。该函数会根据I2C总线控制器的配置,通过相应的I2C写入函数向I2C总线发送写入命令。
  3. I2C总线发送命令到显示器:I2C总线会将经过转换的数据发送给显示器。这个过程是通过底层的硬件和I2C通信协议实现的,代码中的invoke_i2c_writer()函数负责调用相应的I2C写入函数来完成此操作。
  4. 显示器接收命令并调节亮度:显示器接收到I2C总线发送的命令,并根据命令中包含的亮度值(80%对应的表格数据)来进行亮度调节。这个过程是在显示器的硬件电路中实现的,通过DDC协议进行交互。

ddcutil读写用户空间/dev-i2c设备,该设备提供了类似于I2C总线的文件系统抽象。它将比特操作留给视频设备驱动程序,例如amdgpu、nouveau。

ddcutil通过读写用户空间的/dev/i2c设备与I2C总线进行通信,而不直接进行底层的I2C位操作。相反,ddcutil将底层的I2C通信任务交给显卡设备驱动程序,例如amdgpunouveau等。

EDID 获取

应用程序调用libdrm代码中的drmModeGetConnector()函数,通过ioctl传递宏DRM_IOCTL_MODE_GETCONNECTOR通知 Linux 内核要获取相关数据(包括EDID)。

在 Linux 内核中,如果定义了宏CONFIG_DRM_LOAD_EDID_FIRMWARE,内核会先调用drm_load_edid_firmware()函数,从/lib/firmware路径下的对应文件中读取EDID数据;若读取失败或未定义宏CONFIG_DRM_LOAD_EDID_FIRMWARE,内核通过DDC通道和显示器进行通信,获取EDID数据。

EDID 获取流程

DDC 通信流程

img

x11中xserver-master\hw\xfree86\ddc\ddc.c

gpu相关代码

amdgpu_ddc_probe函数的主要作用是通过I2C总线与连接到AMD GPU上的显示器进行通信,并读取显示器的EDID数据。具体的探测过程如下:

  1. 遍历AMD GPU的所有DDC端口:AMD GPU上通常有多个DDC端口,每个端口对应一个显示器连接口。该函数会遍历所有的DDC端口,尝试与连接的显示器进行通信。
  2. 使用DDC协议进行通信:对于每个DDC端口,函数会尝试通过I2C总线与显示器进行通信,使用DDC协议读取显示器的EDID数据。
  3. 解析EDID数据:一旦成功读取到显示器的EDID数据,函数会对EDID数据进行解析,提取其中的信息,例如显示器的分辨率、刷新率、色彩能力等。
  4. 填充驱动数据结构:根据解析得到的EDID信息,函数会将显示器的相关信息填充到GPU驱动的数据结构中,以供后续使用。

通过amdgpu_ddc_probe函数,AMD GPU驱动可以在系统启动时自动探测并识别连接的显示器,并获取其EDID信息。这样,GPU驱动可以根据显示器的特性来正确配置显示输出,以实现最佳的图形显示效果。