ddc/ci over i2c

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I've been trying to use i2c-dev to read and control monitor settings, using the DDC/CI protocol to communicate with address 0x37 on device /dev/i2c-n. Things are sort of working (details below), But it's not clear to me to what extent I'm hitting real limits, or if I just don't know what I'm doing. Perhaps I shouldn't even be trying to use i2c-dev for this application. Advice appreciated. And if there's a more appropriate place to post this question, I'd appreciate hearing that as well.

To review: A monitor is accessed via device /dev/i2c-n, created by the video device driver. EDID data is found by reading address 0x50. The monitor's settings are read and written at address 0x37.

I can communicate with the monitor if either the open-source nouveau or radeon drivers are loaded. Both support i2c_smbus_read_i2c_block_data() and i2c_smbus_write_i2c_block_data(), which put the right bytes on the wire and handle the ack and nack bits as per the DDC specification. (See documentation file i2c/smbus-protocol.)

For the nouveau driver running on Fedora 20, "lsmod | grep i2c" reports:

i2c_piix4              22155  0
i2c_algo_bit           13257  1 nouveau
i2c_dev                14027  0
i2c_core 38656 6 drm,i2c_dev,i2c_piix4,drm_kms_helper,i2c_algo_bit,nouveau


Here's a simplified example (minimal error checking) of how I'm reading the monitor's brightness setting:

   int fh = open("/dev/i2c-0",  O_NONBLOCK|O_RDWR);
   ioctl(fh, I2C_SLAVE, 0x37);

   unsigned char zeroByte = 0x00;
write(fh, &zeroByte, 1); // seems to be necessary to reset monitor state

   unsigned char ddc_cmd_bytes[] = {
      0x6e,              // address 0x37, shifted left 1 bit
      0x51,              // source address
      0x02 | 0x80,       // number of DDC data bytes, with high bit set
      0x01,              // DDC Get Feature Command
      0x10,              // Feature, Luminosity
      0x00,              // checksum, to be set
   };
ddc_cmd_bytes[5] = ddc_checksum(ddc_cmd_bytes, 5); // calculate DDC checksum on all bytes i2c_smbus_write_i2c_block_data(fh, ddc_cmd_bytes[1], sizeof(ddc_cmd_bytes)-2, ddc_cmd_bytes+2); // alt: write(fh, ddc_cmd_bytes+1, sizeof(ddc_cmd_bytes)-1); // see below
   usleep(5000);

   unsigned char ddc_response_bytes[12];
   unsigned char cmd_byte = 0x00;   // apparently ignored, can be anything
   i2c_smbus_read_i2c_block_data(fh, cmd_byte, 11, readbuf+1);
   // alt read(fh, readbuf+1, 11);   // see below
   ddc_response_bytes[0] = 0x50;     // for proper checksum calculation
   int calculated_checksum = ddc_checksum(readbuf, 11);
   assert(readbuf[11] == calculated_checksum);
int response_len = ddc_response_bytes[2] & 0x7f; // always 8 for DDC Get Value response
   // now parse the response data


When issuing the DDC get feature command (code 0x01), a fixed data block of 12 data bytes is returned as shown above (as counted from the i2c_cmsbus_read_i2c_block_data() perspective. However, the DDC Get Capabilities request (0xf3), can return up to 39 bytes (fixed DDC data of 7 bytes plus a "fragment" of up to 32 bytes, depending on the monitor being communicated with.). This is greater than the 32 byte max data size supported by i2c_smbus_read_i2c_block_data() (constant I2C_SMBUS_I2C_BLOCK_MAX in i2c-dev.h). And indeed, I've seen i2c_smbus_read_i2c_block_data() return truncated responses.

Now things get interesting.

Simply using write() and read() seems to work, when the i2c_smbus_..._i2c_block_data() calls in the above code are replaced by the commented out write() and read() lines. So apparently apparently write() and read() are handling the algorithm bits (start, top, ack, nack) properly.


Now come the key questions:

Am I just getting lucky here, or is the i2c_dev driver (or one of the drivers it calls) really doing the right thing for managing the algorithm bits for the i2c DDC protocol?

Is there a better way to use lower level services in i2c-dev?

Should I really be writing a device driver?


Finally, the proprietary video drivers.

The proprietary nvidia driver creates the /dev/i2c-n devices. I can read the monitor EDID information on bus address 0x50. and get the functionality flags using ioctl I2C_FUNCS. Functions ic2_smbus_read_i2c_block_data() and is2_smbus_write_i2c_block_data() are not supported (flags I2C_FUNC_SMBUS_READ_I2C_BLOCK and I2C_FUNC_SMBUS_WRITE_I2C_BLOCK are not set). Attempting to call these functions fails with errno=22 (EINVAL - invalid argument) if module i2c_algo_bit has not been loaded, and errno=5 (EIO - IO Error) if it has.

Trying to use write() and read() as described above also fails. write() appears to succeed, but read() returns invalid data.

Appendix F of the Nvidia driver README discusses i2c support. It appears to be quite dated, referring to the 2.4 and 2.6 kernels. According to this document, only the following functionality is supported, which would be consistent with what I've seen: I2C_FUNC_I2C, I2C_FUNC_SMBUS_QUICK, I2C_FUNC_SMBUS_BYTE, I2C_FUNC_SMBUS_BYTE_DATA, I2C_FUNC_SMBUS_WORD_DATA


As for the proprietary fglrx driver, it doesn't even create the /dev/i2c-n devices. End of story.
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux