clinuxi2c

Reading I2C register from userspace via syscall returns 0


I'm currently trying to read out the registers of the LTC2992 power monitor with a userspace application and did notice a behavior I can't really explain while reading out a register from the device.

So, after writing a value to a register on the LTC2992 either using a write() syscall, i2cset or I2C_RDWR I can read back the value using i2cget or I2C_RDWR. However, when using the write() and read() syscall I only get 0 returned.

This is the code using write() and read() syscalls for reading a value

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

int main() {
    
    ...

    int file = open(device, O_RDWR);
    if (file < 0) {
        perror("Failed to open I2C bus");
        return 1;
    }

    if (ioctl(file, I2C_SLAVE, addr) < 0) {
        perror("Failed to set I2C address");
        close(file);
        return 1;
    }

    if (write(file, &reg, 1) != 1) {
        perror("Failed to write register address");
        close(file);
        return 1;
    }

    if (read(file, &data, 1) != 1) {
        perror("Failed to read from register");
        close(file);
        return 1;
    }

    close(file);
    return 0;
}

And this is the code were I used I2C_RDWR

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <string.h>

int main() {
   
    ...    

    int file = open(device, O_RDWR);
    if (file < 0) {
        perror("Failed to open I2C device");
        return 1;
    }

    struct i2c_rdwr_ioctl_data packets;
    struct i2c_msg messages[2];

    messages[0].addr  = addr;
    messages[0].flags = 0;
    messages[0].len   = 1;
    messages[0].buf   = &reg;

    messages[1].addr  = addr;
    messages[1].flags = I2C_M_RD;
    messages[1].len   = 1;
    messages[1].buf   = &data;

    packets.msgs  = messages;
    packets.nmsgs = 2;

    if (ioctl(file, I2C_RDWR, &packets) < 0) {
        perror("I2C_RDWR ioctl failed");
        close(file);
        return 1;
    }

    close(file);
    return 0;
}

To my understanding, reading back a value expects a repeated start condition, so my guess is that the first method may fail in generating this condition in that the write() syscall may generate the following transaction

START -> [ADDR+W] -> [REG] -> STOP

and the read() syscall this transaction

START -> [ADDR+R] -> [DATA] -> STOP

meaning there is a STOP condition before the next START condition.

However, in this tutorial someone uses similar method to the first one where he succeeds in reading out a value. Looking at the datasheet of the device he uses, the device also expects a repeated start condition. So, I'm not sure if my intuition is right here, or if this may be a timing issue, or if the two methods may have some crucial differences?


Solution

  • Looks like your device resets the address on a STOP. This is on page 19 of the datasheet. "A STOP condition resets the register address pointer to 0x00." The read() is always going to read from address 0 which happens to have a reset value of 0.