1
0
Fork 1
mirror of https://github.com/wezm/wezm.net.git synced 2024-12-25 13:39:53 +00:00
wezm.net/v2/content/posts/2020/linux-amdgpu-pixel-format.md

7 KiB

+++ title = "Setting the amdgpu HDMI Pixel Format on Linux" date = 2020-05-30T08:48:30+10:00

[extra] updated = 2021-05-15T10:15:08+10:00 +++

This week I discovered some details of digital display technology that I was previously unaware of: pixel formats. I have two Dell P2415Q displays connected to my computer. One via DisplayPort, the other via HDMI. The HDMI connected one was misbehaving and showing a dull picture. It turned out I needed to force the HDMI port of my RX560 graphics card to use RGB output instead of YCbCr. However, the amdgpu driver does not expose a means to do this. So, I used an EDID hack to make it look like the display only supported RGB.

tl;dr You can't easily configure the pixel format of the Linux amdgpu driver but you can hack the EDID of your display so the driver chooses RGB. Jump to the instructions.

Previously I had one display at work and one at home, both using DisplayPort and all was well. However, when I started working from home at the start of 2020 (pre-pandemic) the HDMI connected one has always been a bit flakey. The screen would go blank for second, then come back on. I tried 3 different HDMI cables each more premium (and hopefully shielded than the last) without success.

This week the frustration boiled over and I vented to some friends. I was on the brink of just rage buying a new graphics card with multiple DisplayPorts, since I'd never had any trouble with that connection. I received one suggestion to swap the cables between the two, to rule out a fault with the HDMI connected display. I was quite confident the display was ok but it was a sensible thing to try before dropping cash on a new graphics card. So I swapped the cables over.

After performing the magical incantation to enable HDMI 2.0 and get 4K 60Hz on the newly HDMI connected display I immediately noticed lag. I even captured it in a slow motion video on my phone to prove I wasn't going crazy. Despite xrandr reporting a 60Hz connection it seemed as though it was updating at less than that. This led me to compare the menus of the two displays. It was here I noticed that the good one reported an input colour format of RGB, the other YPbPr.

This led to more reading about pixel formats in digital displays — a thing I was not previously aware of. Turns out that ports like HDMI support multiple ways of encoding the pixel data, some sacrificing dynamic range for lower bandwidth. I found this article particularly helpful, DisplayPort vs. HDMI: Which Is Better For Gaming?.

My hypothesis at this point was that the lag was being introduced by my display converting the YPbPr input to its native RGB. So, I looked for a way to change the pixel format output from the HDMI port of my RX560 graphics card. Turns out this is super easy on Windows, but the amdgpu driver on Linux does not support changing it.

In trying various suggestions in that bug report I rebooted a few times and the lag mysteriously went away but the pixel format remained the same. At this point I noticed the display had a grey cast to it, especially on areas of white. This had been present on the other display when it was connected via HDMI too but I just put it down to being a couple of years older than the other one. With my new pixel format knowledge in hand I knew this was was the source of lack of brightness. So, I was still determined to find a way to force the HDMI output to RGB.

The Fix

It was at this point I found this Reddit post describing a terrible hack, originally described by Parker Reed in this YouTube video: Copy the EDID of the display and modify it to make it seem like the display only supports RGB. The amdgpu driver then chooses that format instead. Amazingly enough it worked! I also haven't experienced the screen blanking issue since swapping cables. I can't say for sure if that is fixed but the HDMI cable is now further away from interference from my Wi-Fi router, so perhaps that helped.

The following are the steps I took on Arch Linux to use a modified EDID:

  1. Install wxEDID from the AUR.
  2. Make a copy of the EDID data: cp /sys/devices/pci0000:00/0000:00:03.1/0000:09:00.0/drm/card0/card0-HDMI-A-1/edid Documents/edid.bin
  3. Edit edid.bin with wxEDID and change these values:
    1. Find SPF: Supported features -> vsig_format -> replace 0b01 wih 0b00
    2. Find CHD: CEA-861 header -> change the value of YCbCr420 and YCbCr444 to 0
    3. Recalculate the checksum: Options > Recalc Checksum.
    4. Save the file.

Note: I had to attempt editing the file a few times as wxEDID kept segfaulting. Eventually it saved without crashing though.

Now we need to get the kernel to use the modified file:

  1. sudo mkdir /lib/firmware/edid

  2. sudo mv edid.bin /lib/firmware/edid/edid.bin

  3. Edit the kernel command line. I use systemd-boot, so I edited /boot/loader/entries/arch.conf and added drm.edid_firmware=HDMI-A-1:edid/edid.bin to the command line, making the full file look like this:

     title   Arch Linux
     linux   /vmlinuz-linux
     initrd  /amd-ucode.img
     initrd  /initramfs-linux.img
     options root=PARTUUID=2f693946-c278-ed44-8ba2-67b07c3b6074 resume=UUID=524c0604-c307-4106-97e4-1b9799baa7d5 resume_offset=4564992 drm.edid_firmware=HDMI-A-1:edid/edid.bin rw
    

    Note 1: HDMI-A-1 is the connector to apply the EDID firmware to. It was determined from the /sys path above:
    /sys/devices/pci0000:00/0000:00:03.1/0000:09:00.0/drm/card0/card0-HDMI-A-1/edid

    Note 2: Older kernels (before 4.15 I think) used drm_kms_helper.edid_firmware as the command line argument instead of drm.edid_firmware.

  4. Regenerate the initial RAM disk: sudo mkinitcpio -p linux

  5. Reboot

After rebooting the display confirmed it was now using RGB and visually it was looking much brighter! 🤞 the display blanking issue remains fixed as well. You can also check the that kernel applied the firmware by looking at the kernel log (dmesg) for a line like:

[    4.956349] [drm] Got external EDID base block and 1 extension from "edid/edid.bin" for connector "HDMI-A-1"