open-gpu-kernel-modules
open-gpu-kernel-modules copied to clipboard
NVIDIA I2C driver issues
Copy/paste from an email I sent to [email protected], I haven't verified this against this new open driver, but it has been an issue for a while with the proprietary kernel driver.
I am the lead developer of OpenRGB, an open source RGB lighting control application for Windows and Linux. Our goal is to create a universal RGB control app, talking directly to as many RGB lighting devices as possible. As RGB control is often only supported by official software in Windows, Linux users get left out. That's where we come in.
As you are probably aware, a lot of graphics cards have built-in RGB lighting that features software control. Most cards implement RGB control using the GPU's I2C interface. We're facing an issue controlling certain RGB devices over the NVIDIA GPU's I2C interface in Linux with the proprietary NVIDIA driver. The same code is working fine using NvAPI on Windows and using the Nouveau I2C implementation on Linux, so we believe this to be an issue specific to NVIDIA's proprietary Linux driver.
The cards we've been focused on lately are the ASUS 3xxx series cards, which all use a similar I2C RGB chip that is also found on some ASUS motherboards and various manufacturers' RGB DRAM modules. The chip comes from ENE. The I2C protocol used by this chip is a 16-bit address scheme where you first write a 16-bit address to the 0x00 register of the ENE chip, then perform either a read or write operation to a fixed chip register. This chip appears to be an SMBus chip, so we're using the SMBus functions in the Linux kernel as shown in these two accessor functions:
(ene_dev_id is the 8-bit I2C address of the ENE chip, ene_register is the 16-bit address in the chip that we are reading or writing)
unsigned char ENESMBusInterface_i2c_smbus::ENERegisterRead(ene_dev_id dev, ene_register reg)
{
//Write ENE register
bus->i2c_smbus_write_word_data(dev, 0x00, ((reg << 8) & 0xFF00) | ((reg >> 8) & 0x00FF));
//Read ENE value
return(bus->i2c_smbus_read_byte_data(dev, 0x81));
}
void ENESMBusInterface_i2c_smbus::ENERegisterWrite(ene_dev_id dev, ene_register reg, unsigned char val)
{
//Write ENE register
bus->i2c_smbus_write_word_data(dev, 0x00, ((reg << 8) & 0xFF00) | ((reg >> 8) & 0x00FF));
//Write ENE value
bus->i2c_smbus_write_byte_data(dev, 0x01, val);
}
The issue here appears to be with regards to i2c_smbus_write_word_data. To detect and verify that the chip exists on the bus, we do a series of i2c_smbus_read_byte_data calls and these all work fine on the NVIDIA proprietary Linux driver. However, after detecting the chip we then write a 16-bit address and attempt to read from it. Specifically, we try to read a region of the ENE chip's memory known to contain a version string. The expectation is that the word data write puts the 16-bit address of the byte we want to read into the chip, and the following byte data read from 0x81 returns one byte from the 16-bit address.
With other SMBus host controllers (Intel and AMD chipsets) as well as the NVIDIA GPU I2C on both Windows (NvAPI) and Linux (Nouveau), this works fine and we successfully retrieve the ENE RGB controller's version string. With the NVIDIA proprietary Linux driver, we read garbage. Since we know the i2c_smbus_read_byte_data function works with other manufacturers' NVIDIA GPU boards and for detecting the chip, I can only assume the issue is that the i2c_smbus_write_word_data function isn't working correctly. Note that i2c_smbus_write_byte_data does appear to work on several other manufacturers' GPU RGB chips so I have to assume it's specific to word data.
We have also observed issues with SMBus block operations, though doing the same block operations using I2C_RDWR ioctl (thus avoiding the SMBus layer) seem to work on the NVIDIA proprietary driver.