ddc-macos-rs
ddc-macos-rs copied to clipboard
i2c traits
I haven't dug too deeply into this yet so these are mostly just suggestions, but wondering whether it would make sense to impl i2c::ReadWrite for Monitor? Some of the <Monitor as DdcCommand>::execute
logic seems redundant if the ddc-i2c
crate could wrap the type like it does on the other platforms. It might also be a good way support both transaction types, since then the ddc trait impls could still exist but use kIOI2CDDCciReplyTransactionType
directly? It might be a little awkward though, dunno.
Also since kIOI2CSimpleTransactionType
supports request+reply in a single transaction, it probably would also impl i2c::BulkTransfer
I... don't know right now. To me, it seems that IOI2CSendRequest
and IOI2CRequest implement both reading and/or writing at the same time, and this does not map nicely to i2c::ReadWrite, which are lower-level protocols.
I also don't know I2C and DDC all that well, and might be wrong. My current plan is to have an MVP and expand/enhance it later, when I understand what I'm doing here better, and have the code here tested somewhat better.
Well ReadWrite is just two halves of a request, so it looks like a i2c_read()
or i2c_write()
would just be a IOI2CRequest
with either sendTransactionType
or replyTransactionType
set to kIOI2CNoTransactionType
, send/reply buffers empty, etc. And reply delay just wouldn't be relevant.
BulkTransfer::i2c_transfer()
and BlockTransfer::i2c_{read,write}_block_data()
are the request+reply transaction traits, though probably would be a kIOI2CCombinedTransactionType
instead of simple so that it issues a proper i2c request and not two separate operations. BlockTransfer might also get away with just using the subAddress
fields though, if those are equivalent to the command
argument.
What you don't get with IOI2CSendRequest is control of where the start, stop, ack, and nack, bits go. You can get the E-DDC, DDC-CI, MCCS, and ACCESS.Bus specs from the VESA website free downloads section.
E-DDC
From the E-DDC spec, these type of requests/transactions exist:
- DDC Read at the Current Address: S, A1h, ←ACK, ←Data, Nack, P
- DDC Random Read: S, A0h, ←ACK, offset, ←ACK, S, A1h, ←ACK, ←Data, Nack, P
- DDC Sequential Read: S, A0h, ←ACK, offset, ←ACK, S, A1h, ←ACK, {←Data, ACK}*, ←Data, Nack, P
- E-DDC Random Read: S, 60h, ←R1, segment, ←R2, S, A0h, ←ACK, offset, ←ACK, S, A1h, ←ACK, ←Data, Nack, P
- E-DDC Sequential Read: S, 60h, ←R1, segment, ←R2, S, A0h, ←ACK, offset, ←ACK, S, A1h, ←ACK, {←Data, ACK}*, ←Data, Nack, P
but only the first 3 are possible with IOI2CSendRequest. The first uses reply only. It has a start bit and a stop bit. The next two are basically the same and use send / reply. There's a start bit before the send and before the reply and a stop bit at the end.
The E-DDC transactions are not possible because they have three start bits and only one stop bit. This makes it nearly impossible to read more than 256 bytes of an EDID. I have had success with a E-DDC send / reply request that doesn't include the second send part S, A0h, ←ACK, offset, ←ACK
part, like this:
- E-DDC Current Address: S, 60h, ←R1, segment, ←R2, S, A1h, ←ACK, {←Data, ACK}*, ←Data, Nack, P
which works with Intel 630 and Nvidia GTX 680 but not AMD W5700. It can't be done with two separate requests because a stop bit would undo the 60h segment pointer setting so reading from A1h would read EDID bytes only from segment 0 (the first 256 bytes of the EDID).
DDC/CI
The send and reply of a DDC/CI (MCCS) request are each supposed to have a start and stop bit. This means using two separate IOI2CSendRequest calls using kIOI2CSimpleTransactionType or a single IOI2CSendRequest using kIOI2CSimpleTransactionType for send and kIOI2CDDCciReplyTransactionType for reply.
For example, the "Get VCP Feature & VCP Feature Reply" request has these two parts:
send: S-6Ea-51a-82a-01a-CPa-CHKa-P
reply: S-6Fa-6Ea-88a-02a-RCa-CPa-TPa-MHa-MLa-SHa-SLa-CHK’n-P
Each part has a start bit and a stop bit. If you used send and reply both with kIOI2CSimpleTransactionType in the same transaction, then you would be missing the first stop bit. Maybe it will work - maybe just the start bit before the reply is required? But I wouldn't depend on that.
The Apple open source code shows how i2c works for displays https://github.com/apple-oss-distributions/IOGraphics/blob/main/IOGraphicsFamily/IOFramebuffer.cpp but it doesn't have any info on how kIOI2CCombinedTransactionType
modifies the behaviour.
One other strange thing I've found is that minReplyDelay
is supposed to use AbsoluteTime units according to Apple documentation (which is something like nanoseconds) but is actually milliseconds (at least for one GPU I tried). That's one more reason to avoid kIOI2CDDCciReplyTransactionType
. With two separate IOI2CSendRequest calls, I can put a usleep
in between the calls to act as the reply delay (at least for kIOI2CDDCciReplyTransactionType
where the extra stop bit is expected).
@joevt thank you for this comment, very helpful! I agree with everything you say, it's not a great situation... To add to this:
-
AFAIK
minReplyDeplay
is interpreted differently depending on what graphics adapter you have. With some Intel adapters, on older Macbook Airs, getting it wrong results in a system-wide hard lockup, which requires power off/on to resolve. -
On M1 macs, there are
IOAVServiceWriteI2C
andIOAVServiceReadI2C
-- which are supposedly required to get DDC working there, the previous method withIOI2CSendRequest
is not working on M1 SoCs.
Experimenting with IOAVServiceWriteI2C
is on my to-do list, I've got an M1 MBP in December but still haven't got time to play with it... My hope is that this newer API maps better to how DDC and I2C actually work, and we can use it on non-M1 systems as well.
- AFAIK
minReplyDeplay
is interpreted differently depending on what graphics adapter you have. With some Intel adapters, on older Macbook Airs, getting it wrong results in a system-wide hard lockup, which requires power off/on to resolve. Right - if you pass 6000 microseconds and the driver interprets it as 6 seconds then it will appear as a lockup but only for 6 seconds. If the driver interprets it as 6000 seconds, then you'll be waiting a long time or need to power off/on.
- On M1 macs, there are
IOAVServiceWriteI2C
andIOAVServiceReadI2C
-- which are supposedly required to get DDC working there, the previous method withIOI2CSendRequest
is not working on M1 SoCs.Experimenting with
IOAVServiceWriteI2C
is on my to-do list, I've got an M1 MBP in December but still haven't got time to play with it... My hope is that this newer API maps better to how DDC and I2C actually work, and we can use it on non-M1 systems as well. I have read a little bit about this.
Intel Macs use IOFramebuffer, one for each port. Each IOFamebuffer has a IOFramebufferI2CInterface. It may also have an IODisplay if a display is attached. I suppose a tiled display like the LG UltraFine 5K will use more than one IOFramebuffer but there's only one IODisplay.
M1 Macs use IOMobileFramebuffer, one for each display. A tiled display uses only one IOMobileFramebuffer just like a single tile display. The i2c stuff is separate. A display will have a DCPAVServiceProxy (I suppose this is just for E-DDC and DDC/CI?). It will also have a DCPDPServiceProxy (I suppose this is for DisplayPort). A tiled display has two of each of those (one for each tile which are each a separate DisplayPort connection)
I'm working on a utility called AllRez to dump all the info for displays including display modes, E-DDC, DDC/CI (MCCS), and DisplayPort. I haven't added much M1 Mac stuff to it yet. It will be useful if I can get it to read DisplayPort DPCD since AGDCDiagnose doesn't work on M1 Macs (and it doesn't retrieve DPCD registers for some older GPUs in Intel Macs). I want the DisplayPort stuff to also get the MST Hub topology and info, HDMI adapter info (I think one my adapters has a log that can be read through DPCD), etc.