I'm writing a driver for Intel HD Graphics (specifically, a HD Graphics 5500 from a HP EliteBook Folio 1040 G2). Right now I'm trying to get the EDID of my laptop's display but the GMBUS seems to get stuck while reading from some ports.
I'm trying to get the EDID as follows:
- Reset the GMBUS by asserting and deasserting bit 31 of GMBUS1 ("Software Clear Interrupt").
- Write either 0x2, 0x4, 0x5 or 0x6 to GMBUS0 (pin pair DAC DDC, DDIC, DDIB, DDID at 100KHz respectively).
- Write 0x468000a1 to GMBUS1 ("Software Ready", Bus cycle "Index, No Stop, Wait", 128 byte count, index 0, address 0x50, read).
- Wait for "Hardware Ready" or "Not Acknowledged".
I get the following values for each pin pair:
- DAC DDC: GMBUS2 reads 0x0800 after setting GMBUS0 ("Hardware Ready"), 0x8200 during read ("INUSE" and "GMBUS Active").
- DDIB: GMBUS2 reads 0x8c00 after setting GMBUS0 ("INUSE", "Hardware Ready" and "NAK Indicator"), same during read.
- DDIC: GMBUS2 reads 0x8000 after setting GMBUS0 ("INUSE"), 0x8c00 during read.
- DDID: GMBUS2 reads 0x8000 after setting GMBUS0, 0x8200 during read.
I would expect to read 0x8a00 ("INUSE", "Hardware Ready" and "GMBUS Active") at some point, though this is evidently not happening. Switching the order of each attempt on each pin doesn't seem to affect the results. Trying to write a byte first like in managarm's driver doesn't seem to do anything either.
You can find my current code here. It is based on:
- managarm's Intel Native Graphics driver
- Intel's GPU documentation for Broadwell chips
- The OSDev's wiki article on Intel HD Graphics
GMBUS never asserts HW_RDY during read
- Demindiro
- Member
- Posts: 96
- Joined: Fri Jun 11, 2021 6:02 am
- Libera.chat IRC: demindiro
- Location: Belgium
- Contact:
Re: GMBUS never asserts HW_RDY during read
I believe the issue is that both of the ports that get stuck are DisplayPort ports: one external and one internal (eDP). I'll read up on DP and try to make I2C work. If it does, then we know that GMBUS can't be used for DP devices
- Demindiro
- Member
- Posts: 96
- Joined: Fri Jun 11, 2021 6:02 am
- Libera.chat IRC: demindiro
- Location: Belgium
- Contact:
Re: GMBUS never asserts HW_RDY during read
I've successfully managed to retrieve the EDID!
(btw, the wiki says that the last byte is "Checksum (Low Byte of 16-bit sum of 00-7Eh)". I don't think this makes it clear that the sum of all 128 bytes mod 256 should be 0.)
I'm going to collect my thoughts here, then later add a wikipage about DisplayPort (and maybe DDC) and update the Intel HD Graphics page.
Here my the current code. I'll clean it up later.
I2C
I2C is a very simple protocol: there is a master and up to 128 slaves. When reading/writing data, bits 7:1 indicate the address and bit 0 indicate a read if set, a write otherwise (i.e. direction). Then bytes are written until a NACK is sent.
DDC2 & EDID
DDC2 is what is used to retrieve the EDID. You must first write the offset, which should be 0, then read 128 bytes. If the data starts with 00 FF FF FF FF FF FF 00 and the sum of the data modulo 256 is 0 you've got it.
DDC/CI
DDC/CI is a protocol that is built on top of I2C. http://boichat.ch/nicolas/ddcci/specs.html
DisplayPort
DisplayPort has two channels: a main channel for video, audio ... and an auxiliary channel for I2C and whatever else.
The AUX CH is a packet-based protocol: each packet is up to 16 (20?) bytes large. The "Source" initiates a request and a "Sink" always replies within a timeout.
Each request starts with a header byte with the following format:
- bit 7: 0 for I2C data, 1 for "native" AUX CH data
- I2C: bit 6: middle-of-transaction (remember, DP packets are max 16 bytes but I2C data can be much longer)
- I2C: bit 5:4: 00 = write, 01 = read, 10 = status_request, 11 = reserved
Each reply also starts with a header byte. For I2C packets it's as follows:
- bit 5:4: 00 = AUX_ACK, 01 = AUX_NACK, 10 = AUX_DEFER, 11 = reserved
- if AUX_ACK: bit 7:6: 00 = I2C_ACK, 01 = I2C_NACK, 10 = I2C_DEFER, 11 = reserved
- if !AUX_ACK: bit 7:6 must be 00
The remaining bits are 0. The DP documentation says the header is in bits 3:0 bit this is misleading since the padding is prepended. The remaining bytes are data.
I2C_DEFER is sent if the I2C device is unable to fill the requested (expected) amount of data before the timeout (300µs). For reading EDID data at >50KHz this shouldn't be an issue if you read byte-by-byte.
The length is encoded as a 4 bit value minus 1 (e.g. 4 bytes of data -> 0b0011)
To read the EDID, the communication looks as follows (> is data sent, < is data received):
http://file.yizimg.com/383992/2014090921252964.pdf section 2.4, 2.4.5.4
Intel HD Graphics DP registers
There are 6 registers of note:
- DDI_AUX_CTL
- DDI_AUX_DATA
- DP_AUX_CTL
- DP_AUX_DATA
- SRD_AUX_CTL
- A seemingly unnamed register at 0xC2020
DP_AUX_* and DDI_AUX_* are exactly the same except that the DDI_* can only be used if all SRD_* registers are set as disabled. *_AUX_DATA are actually 5 registers for a total of 20 bytes of data. It may be necessary to set bit 12 of 0xC2020 which does stuff with the clock apparently.
To send a request, copy the request packet to the data registers starting from the most significant byte of the first register. Then set the message length and the send_busy bit. Wait for the done bit or any of the error bits to be set, then use the message length to determine how much data is available in the data registers.
You should preserve all the bits in the *_CTL registers. The firmware has most likely set them up properly already.
https://01.org/linuxgraphics/documentat ... l-platform Volume 02A
Code: Select all
00 ff ff ff ff ff ff 00 30 e4 0a 04 00 00 00 00
00 17 01 04 95 1f 11 78 ea dc 95 a3 58 55 a0 26
0d 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01
01 01 01 01 01 01 ba 36 80 ac 70 38 24 40 3c 24
35 00 35 af 10 00 00 1a 7c 24 80 ac 70 38 24 40
3c 24 35 00 35 af 10 00 00 1a 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02
00 0c 33 ff 0f 3c 96 19 16 32 96 00 00 00 00 fb
I'm going to collect my thoughts here, then later add a wikipage about DisplayPort (and maybe DDC) and update the Intel HD Graphics page.
Here my the current code. I'll clean it up later.
I2C
I2C is a very simple protocol: there is a master and up to 128 slaves. When reading/writing data, bits 7:1 indicate the address and bit 0 indicate a read if set, a write otherwise (i.e. direction). Then bytes are written until a NACK is sent.
DDC2 & EDID
DDC2 is what is used to retrieve the EDID. You must first write the offset, which should be 0, then read 128 bytes. If the data starts with 00 FF FF FF FF FF FF 00 and the sum of the data modulo 256 is 0 you've got it.
DDC/CI
DDC/CI is a protocol that is built on top of I2C. http://boichat.ch/nicolas/ddcci/specs.html
DisplayPort
DisplayPort has two channels: a main channel for video, audio ... and an auxiliary channel for I2C and whatever else.
The AUX CH is a packet-based protocol: each packet is up to 16 (20?) bytes large. The "Source" initiates a request and a "Sink" always replies within a timeout.
Each request starts with a header byte with the following format:
- bit 7: 0 for I2C data, 1 for "native" AUX CH data
- I2C: bit 6: middle-of-transaction (remember, DP packets are max 16 bytes but I2C data can be much longer)
- I2C: bit 5:4: 00 = write, 01 = read, 10 = status_request, 11 = reserved
Each reply also starts with a header byte. For I2C packets it's as follows:
- bit 5:4: 00 = AUX_ACK, 01 = AUX_NACK, 10 = AUX_DEFER, 11 = reserved
- if AUX_ACK: bit 7:6: 00 = I2C_ACK, 01 = I2C_NACK, 10 = I2C_DEFER, 11 = reserved
- if !AUX_ACK: bit 7:6 must be 00
The remaining bits are 0. The DP documentation says the header is in bits 3:0 bit this is misleading since the padding is prepended. The remaining bytes are data.
I2C_DEFER is sent if the I2C device is unable to fill the requested (expected) amount of data before the timeout (300µs). For reading EDID data at >50KHz this shouldn't be an issue if you read byte-by-byte.
The length is encoded as a 4 bit value minus 1 (e.g. 4 bytes of data -> 0b0011)
To read the EDID, the communication looks as follows (> is data sent, < is data received):
Code: Select all
> 0b0_1_00_0000, 0, 0x50 (I2C, MOT = 1, write, address 0x50)
< 0b00_00_0000 (AUX_ACK, I2C_ACK)
> 0b0_1_00_0000, 0, 0x50, 0, 0 (I2C, MOT = 1, write, address 0x50, length 1, data 0)
< 0b00_00_0000 (AUX_ACK, I2C_ACK)
> 0b0_1_01_0000, 0, 0x50 (I2C, MOT = 1, read, address 0x50)
< 0b00_00_0000 (AUX_ACK, I2C_ACK)
loop 128 times:
> 0b0_1_01_0000, 0, 0x50, 0 (I2C, MOT = 1, read, address 0x50, length 1)
< 0b00_00_0000, x (AUX_ACK, I2C_ACK, data x)
repeat loop
> 0b0_0_01_0000, 0, 0x50 (I2C, MOT = 0, read, address 0x50)
< 0b00_00_0000 (AUX_ACK, I2C_ACK)
Intel HD Graphics DP registers
There are 6 registers of note:
- DDI_AUX_CTL
- DDI_AUX_DATA
- DP_AUX_CTL
- DP_AUX_DATA
- SRD_AUX_CTL
- A seemingly unnamed register at 0xC2020
DP_AUX_* and DDI_AUX_* are exactly the same except that the DDI_* can only be used if all SRD_* registers are set as disabled. *_AUX_DATA are actually 5 registers for a total of 20 bytes of data. It may be necessary to set bit 12 of 0xC2020 which does stuff with the clock apparently.
To send a request, copy the request packet to the data registers starting from the most significant byte of the first register. Then set the message length and the send_busy bit. Wait for the done bit or any of the error bits to be set, then use the message length to determine how much data is available in the data registers.
You should preserve all the bits in the *_CTL registers. The firmware has most likely set them up properly already.
https://01.org/linuxgraphics/documentat ... l-platform Volume 02A