Hot-patching a buggy TV's EDID under Linux

Posted on 21/03/2014 in misc

Disclaimer: Try the following at your own risk, it may brick your TV/monitor

My TV set (an "old" LG 32LG5000 model) never played well with Linux. Whenever I connected to it via an HDMI port, the kernel would complain:

kernel: [  869.677850] [drm:drm_edid_block_valid] *ERROR* EDI  has major version 2, instead of 1

and then would completely ignore the TV.

Because I didn't use it all that much anyway (and the VGA still worked, although at 1360x768), I hadn't really bothered to find out what was wrong. Today however, I wanted to watch a movie and the laptop's VGA output quality really sucked, so I decided to have a look and see if I could get things to work.

Analyzing the EDID

For those unfamiliar with the terms, the EDID is a small piece of data that encodes a monitor's capabilities, most notably the resolutions and timings it supports, allowing zero-configuration use of monitors. The above error is particularly interesting, in that there (almost) never was a version 2.0 EDID: according to Wikipedia,

EDID structure versions range from v1.0 to v1.4; all these define upwards-compatible 128-byte structures. EDID structure v2.0 defined a new 256-byte structure, but subsequently has been deprecated and replaced by v1.3.

It seems that EDID 2.0 was deprecated around 2000, and it's funny that a TV set manufactured in 2008 reports this version.

After googling a bit, I found some sporadic references that this model was erroneously labeled as having a version 2.0 EDID, while in fact its EDID was 1.3. I tried a firmware update to the latest version, but it didn't fix the issue, so at this point I had 2 options:

  1. Patch the kernel and remove the major version check. This was ugly, plus I would have to compile at least the drm module every time I upgraded my kernel.
  2. Patch the EDID and try to get either the TV or the kernel to use it.

Feeling a bit adventurous, I opted for the latter. Loading the i2c-dev module gives us access to the raw EDID data, using get-edid (part of the read-edid package in Debian):

# get-edid > /tmp/edid.bin
This is read-edid version 3.0.1. Prepare for some fun.
Attempting to use i2c interface
No EDID on bus 0
No EDID on bus 1
No EDID on bus 2
No EDID on bus 4
No EDID on bus 5
No EDID on bus 7
2 potential busses found: 3 6
Will scan through until the first EDID is found.
Pass a bus number as an option to this program to go only for that one.
256-byte EDID successfully retrieved from i2c bus 3
Looks like i2c was successful. Have a good day.

Note that we got the EDID from i2c bus 3. Let's see now what's in there:

# parse-edid < /tmp/edid.bin
Checksum Correct

Section "Monitor"
        Identifier "32LG5000"
        ModelName "32LG5000"
        VendorName "GSM"
        # Monitor Manufactured week 9 of 2008
        # EDID version 2.0
        # Digital Display
        DisplaySize 700 390
        Gamma 2.20
        Option "DPMS" "true"
        Horizsync 28-67
        VertRefresh 50-75
        # Maximum pixel clock is 150MHz
        #Not giving standard mode: 640x480, 60Hz
        #Not giving standard mode: 800x600, 60Hz
        #Not giving standard mode: 1024x768, 60Hz
        #Not giving standard mode: 1920x1080, 60Hz

        #Extension block found. Parsing...
        Modeline        "Mode 16" +hsync +vsync
        Modeline        "Mode 0" -hsync -vsync
        Modeline        "Mode 1" -hsync +vsync
        Modeline        "Mode 2" 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
        Modeline        "Mode 3" 27.000 720 732 796 864 576 581 586 625 -hsync -vsync
        Modeline        "Mode 4" 25.200 640 656 752 800 480 490 492 525 -hsync -vsync
        Modeline        "Mode 5" 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
        Modeline        "Mode 6" 27.000 720 732 796 864 576 581 586 625 -hsync -vsync
        Modeline        "Mode 7" 74.250 1280 1720 1760 1980 720 725 730 750 +hsync +vsync
        Modeline        "Mode 8" 74.250 1280 1390 1420 1650 720 725 730 750 +hsync +vsync
        Modeline        "Mode 9" 74.250 1920 2448 2492 2640 1080 1082 1089 1125 +hsync +vsync interlace
        Modeline        "Mode 10" 74.250 1920 2008 2052 2200 1080 1082 1087 1125 +hsync +vsync interlace
        Modeline        "Mode 11" 74.250 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
        Modeline        "Mode 12" 148.500 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
        Modeline        "Mode 13" 74.250 1920 2558 2602 2750 1080 1084 1089 1125 +hsync +vsync
        Modeline        "Mode 14" 74.250 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
        Modeline        "Mode 15" 148.500 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
        Modeline        "Mode 17" +hsync +vsync
        Modeline        "Mode 18" +hsync +vsync interlace
        Modeline        "Mode 19" -hsync -vsync
        Option "PreferredMode" "Mode 16"
EndSection

The model name confirms we have the right EDID. Note the # EDID version 2.0 string that parse-edid reports, which agrees with what the kernel complains about. Also note that parse-edid found an extension block as well, one specifying the timings and resolutions supported by the TV. Let's have a look at the raw data now:

# hd /tmp/edid.bin
00000000  00 ff ff ff ff ff ff 00  1e 6d f0 75 01 01 01 01  |.........m.u....|
00000010  09 12 02 00 80 46 27 78  ea d9 b0 a3 57 49 9c 25  |.....F'x....WI.%|
00000020  11 49 4b a5 6e 00 31 40  45 40 61 40 d1 c0 01 01  |.IK.n.1@E@a@....|
00000030  01 01 01 01 01 01 26 36  80 a0 70 38 1f 40 50 20  |......&6..p8.@P |
00000040  85 00 bc 86 21 00 00 18  1b 21 50 a0 51 00 1e 30  |....!....!P.Q..0|
00000050  48 88 35 00 bc 86 21 00  00 1c 00 00 00 fc 00 33  |H.5...!........3|
00000060  32 4c 47 35 30 30 30 0a  20 20 20 20 00 00 00 fd  |2LG5000.    ....|
00000070  00 32 4b 1c 43 0f 00 0a  20 20 20 20 20 20 01 f9  |.2K.C...      ..|

00000080  02 03 21 f1 4e 02 11 01  03 12 13 04 14 05 21 1f  |..!.N.........!.|
00000090  20 22 10 23 09 07 07 83  01 00 00 65 03 0c 00 30  | ".#.......e...0|
000000a0  00 01 1d 00 bc 52 d0 1e  20 b8 28 55 40 c4 8e 21  |.....R.. .(U@..!|
000000b0  00 00 1e 01 1d 00 72 51  d0 1e 20 6e 28 55 00 c4  |......rQ.. n(U..|
000000c0  8e 21 00 00 1e 01 1d 80  d0 72 1c 16 20 10 2c 25  |.!.......r.. .,%|
000000d0  80 c4 8e 21 00 00 9e 8c  0a d0 90 20 40 31 20 0c  |...!....... @1 .|
000000e0  40 55 00 c4 8e 21 00 00  18 4e 1f 00 80 51 00 1e  |@U...!...N...Q..|
000000f0  30 40 80 37 00 bc 88 21  00 00 18 00 00 00 00 ac  |0@.7...!........|
00000100

The raw EDID data comprises two sections, 128 bytes each (remember, this is an EDID 1.3 blob, incorrectly labeled as 2.0). The first section is the main one and the second one is the extension, an EIA/CEA-861 extension block containing the native resolutions and timings. Using the Wikipedia article as a reference, we quickly find out that bytes 0x12 and 0x13 are the EDID version (with values 0x02 and 0x00 in our case) and the checksum byte 0xf7 (with a value of 0xf9).

Hot-patching the EDID

In theory, if we replace the values of bytes 0x12 and 0x13 with 0x01 and 0x03 respectively and adjust the checksum, we should end up with a fixed EDID. This can be trivially done using a hex editor, and we will have to change the checksum byte's value from 0xf9 to 0xf7 (the sum of all 128 bytes in the section modulo 256 must be 0).

Let's try to parse the modified EDID:

$ diff -u <(parse-edid </tmp/edid.bin) <(parse-edid </tmp/edid-fixed.bin)
Checksum Correct

Checksum Correct

--- /proc/self/fd/11    2014-03-22 02:18:52.614474422 +0200
+++ /proc/self/fd/12    2014-03-22 02:18:52.614474422 +0200
@@ -3,7 +3,7 @@
        ModelName "32LG5000"
        VendorName "GSM"
        # Monitor Manufactured week 9 of 2008
-       # EDID version 2.0
+       # EDID version 1.3
        # Digital Display
        DisplaySize 700 390
        Gamma 2.20

Now that we have a fixed, valid EDID perhaps we can write it to the TV (it turns out there are people who have succesfully done this). The i2c-tools Debian package provides a handy pair of utilities, i2cget and i2cset that can read and write bytes to an I²C device. Remember that get-edid retrieved the EDID from bus 3 and it seems like the chip address for EDID is always 0x50, so let's try to read the bytes in question directly:

# for byte in 0x12 0x13 0x7f; do i2cget -y 3 0x50 $byte; done
0x02
0x00
0xf9

Works! Accepting the risk of possibly bricking the (either way useless) HDMI port, I decided to try and overwrite the bytes in place:

Disclaimer: Try this at your own risk and only if you understand the dangers involved. You have been warned.
# i2cset -y 3 0x50 0x12 0x01
# i2cset -y 3 0x50 0x13 0x03
# i2cset -y 3 0x50 0x7f 0xf7

No errors reported, so let's see if it worked:

$ xrandr -q
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 8192 x 8192
eDP1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 294mm x 165mm
   1920x1080      60.0*+   59.9     40.0
   1680x1050      60.0     59.9
   1600x1024      60.2
   1400x1050      60.0
   1280x1024      60.0
   1440x900       59.9
   1280x960       60.0
   1360x768       59.8     60.0
   1152x864       60.0
   1024x768       60.0
   800x600        60.3     56.2
   640x480        59.9
VGA1 disconnected (normal left inverted right x axis y axis)
HDMI1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 700mm x 390mm
   1920x1080      60.0*+   60.0     50.0     59.9     30.0     25.0     24.0     30.0     24.0
   1920x1080i     60.1     50.0     60.0
   1360x768       59.8
   1280x768       60.4
   1280x720       60.0     50.0     59.9
   1024x768       75.1     70.1     60.0
   832x624        74.6
   800x600        75.0     60.3
   720x576        50.0
   720x480        60.0     59.9
   640x480        75.0     60.0     59.9     59.9
   720x400        70.1
DP1 disconnected (normal left inverted right x axis y axis)

It did! Using xrandr I was finally able to get full 1080p output on the TV (HDMI1). It also seems that the change is permanent, so no need to re-flash every time the TV is turned off and on :-)