Search code examples
cembedded-linuxi2csmbus

How to perform a 256 byte block read with i2c/smbus


I've got this power monitor that I'm attempting to interface with to retrieve it's fault log LTC2977 (data sheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2977.pdf)

I'm new to i2c / pmbus / smbus things so forgive my poor terminology or incorrect descriptions of how things work. On page 18 the data sheet states that I can interface with it like so:

The LTC2977 is a slave device. The master can communicate with the LTC2977 using the following formats:

  • Master transmitter, slave receiver
  • Master receiver, slave transmitter

The following SMBus protocols are supported:

  • Write Byte, Write Word, Send Byte
  • Read Byte, Read Word, Block Read
  • Alert Response Address

Figure 1a-12 illustrate the aforementioned SMBus protocols. All transactions support PEC (packet error check) and GCP (group command protocol). The Block Read supports 255 bytes of returned data. For this reason, the PMBus timeout may be extended using the Mfr_config_all_ longer_pmbus_timeout setting.

The log in question can be accessed with SMBus (PMBus?) command 0xEE, wherein it will spit out 0xFF followed by 255 bytes containing the log data. Now I can see the device and all its internal commands/registers(?) by doing i2cdump, and similarly if I do i2cget ... 0xEE I get the output 0xff, which is what the first entry in the log is supposed to be. I can also do i2cget ... 0xEE w, so outputting as a word to get 0x--ff where the '-'s are values changing everytime I call i2cget, my log data perhaps?

My problem is I'm attempting to read this as a block like the document stated I could do above. I'm not entirely sure how to do this so I tried the following:

device = open("/dev/i2c-0", O_RDWR);
ioctl(device, I2C_SLAVE_FORCE, 0x30); //0x30 is the i2c slave address

unsigned char buf[256] = {0};
buf[0] = 0xEE; //the smbus command of the log
write(device, buf, 1);
read(device, buf, 256);

I've had success in the past with other i2c devices reading them like this, first issuing the command, then reading into a buffer. the largest I've used successfully before was a 4 byte buffer so I figured this would be no different, however the contents of my buffer after this operation is that every entry is now 0xff which I don't believe is correct seeing as how i2cget ... 0xEE w has more than just 0xff in it.

Next I tried to emulate some functionality I've seen others use from <smbus.h>, I don't have this library available to me so I tried to re-create the smbus block read function which turned out like this:

device = open("/dev/i2c-0", O_RDWR);
ioctl(device, I2C_SLAVE_FORCE, 0x30); //0x30 is the i2c slave address

union i2c_smbus_data data;

struct i2c_smbus_ioctl_data args;
args.read_write = I2C_SMBUS_READ;
args.command = 0xEE; //the smbus command of the log
args.size = I2C_SMBUS_BLOCK_DATA;
args.data = &data;

ioctl(device, I2C_SMBUS, &args);

unfortunately this did not work at all, it froze my target and I had to restart it. I'm not sure what went wrong here, but I believe this is the more appropriate or correct way to interface with the device, if I can get it working. One thing I noticed is that the block data size in i2c_smbus_data is only 32 bytes which is confusing since my data sheet seems to indicate it will send me a 256 byte block, could this be what's causing it to crash? I read you can ignore this built in block size and implement it yourself, but I'm not sure where to even start with that, wouldn't it just be the same as my first attempt with write & read into a buffer?

Any help or places to look into how I can correctly use this SMBus or read the log data out would be very much appreciated. I believe worst case scenario I could retrieve a word on the log command 255 times to build it up, but that seems like a very bad approach


Solution

  • okay, I've managed to figure out a solution, kind of a hybrid of the 2. setting I2C_SLAVE is no longer done (could that cause a problem with other devices on the same I2C buffer? I have no idea) and instead a read is performed directly with I2C_RDWR, where 2 i2c messages are passed, the first a write with the smbus command / sub address and the second a read of the data there.

    device = open("/dev/i2c-0", O_RDWR);
    
    unsigned char buf[256] = {0};
    buf[0] = 0xEE; //the smbus command of the log
    
    struct i2c_msg msgs[2];
    msgs[0].addr = 0x30;
    msgs[0].flags = 0;
    msgs[0].len = 1;
    msgs[0].buf = buf;
    
    msgs[1].addr = 0x30;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = 256;
    msgs[1].buf = buf;
    
    struct i2c_rdwr_ioctl_data args;
    args.msgs = msgs;
    args.nmsgs = 2;
    
    ioctl(device, I2C_RDWR, &args);
    

    One problem I'm noticing is the device is raising a bad communication error (despite providing me the correct data). the data sheet states that it is a catch all error for "illegally formed i2c/smbus commands" which I guess means I've done something not quite right. would appreciate any comments on why this might be the case