cdriverembedded-linuxeepromsmbus

read/write on eeprom from linux userspace


I am working on an custom embedded device based on iMX8MP MPU. I need to read the first 16 bits of the EEPROM connected to address 0x50 on the i2c-0 bus in Linux from user space.

In the first place, I wrote on my eeprom thanks to u-boot as follow:

u-boot=> i2c mw 0x50 0x00.2 57
u-boot=> i2c mw 0x50 0x01.2 69
u-boot=> i2c mw 0x50 0x02.2 74
u-boot=> i2c mw 0x50 0x03.2 65
u-boot=> i2c mw 0x50 0x04.2 6B
u-boot=> i2c mw 0x50 0x05.2 69
u-boot=> i2c mw 0x50 0x06.2 6F

Then I checked that the value are correctly written in eeprom after a reboot as follow:

u-boot=> i2c md 0x50 0x0.2 B
0000: 57 69 74 65 6b 69 6f 20 53 41 53

I wrote a code that uses ioctls with I2C_SLAVE_FORCE and I2C_SMBUS request to communicate with EEPROM. However, values displayed are not correct and I can not figure out why.

#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>

#define I2C_ADDRESS 0x50
#define I2C_BUS "/dev/i2c-0"

int main(void)
{
    int file;
    char filename[20];
    int res;
    unsigned char data[16];

    snprintf(filename, 19, "%s", I2C_BUS);
    file = open(filename, O_RDWR);
    if (file < 0) {
        perror("open");
        exit(1);
    }

    res = ioctl(file, I2C_SLAVE_FORCE, I2C_ADDRESS);
    if (res < 0) {
        perror("ioctl");
        exit(1);
    }

    struct i2c_smbus_ioctl_data ioctl_data = {
        .read_write = I2C_SMBUS_READ,
        .command = 0x00, /* read start address */
        .size = I2C_SMBUS_WORD_DATA,
        .data = data,
    };

    res = ioctl(file, I2C_SMBUS, &ioctl_data);
    if (res < 0) {
        perror("ioctl");
        exit(1);
    }
    printf("Data read: ");
    for (int i = 0; i < 16; i++) {
        printf("%02x ", data[i]);
    }
    printf("\n");
    close(file);
    return 0;
}

The output is:

data read : ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00

At this point I have no clue why it is not working. Any hint would be appreciated


Solution

  • There is a much easier way to read / write on eeprom. Indeed, If there is a file named "eeprom" for instance under /sys/devices/platform/soc@0/30800000.bus/30a20000.i2c/i2c-0/0-0050 it means that there is a kernel driver for this eeprom.

    A simple cat to /sys/devices/platform/soc@0/30800000.bus/30a20000.i2c/i2c-0/0-0050/uevent indicate the corresponding driver:

    DRIVER=at24
    OF_NAME=eeprom
    OF_FULLNAME=/soc@0/bus@30800000/i2c@30a20000/eeprom@50
    OF_COMPATIBLE_0=atmel,24c32
    OF_COMPATIBLE_N=1
    MODALIAS=of:NeepromT(null)Catmel,24c32
    

    at24.c is the kernel driver for my eeprom. I can operate read and write operation simply by accessing the eeprom file /sys/devices/platform/soc@0/30800000.bus/30a20000.i2c/i2c-0/0-0050/eeprom.

    You can find below a dummy C program that allow me to read write on eeprom.

    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    #define EEPROM_ARGUMENT 2
    #define RDWR_ARGUMENT 3
    #define MINIMAL_ARGUMENT 4
    #define OFFSET_ARGUMENT 4
    
    #define EEPROM_SOM "/sys/devices/platform/soc@0/30800000.bus/30a20000.i2c/i2c-0/0-0050/eeprom"
    
    void read_eeprom(int fd, uint16_t offset, unsigned long read_count) {
        char *buff = (char*) malloc(read_count * sizeof(char));
        if (pread(fd, buff, read_count, offset) != read_count) {
            printf("Error: could not read data from device\n");
            close(fd);
            exit(-1);
        }
        printf("Data read: ");
        for (int i = 0; i < read_count; i++) {
            printf("%02x ", buff[i]);
        }
        printf("\n");
        close(fd);
        free(buff);
        exit(0);
    }
    
    void write_eeprom(int fd, uint16_t offset, const char* write_word) {
        if (pwrite(fd, write_word, strlen(write_word),offset) != strlen(write_word)) {
            printf("Error: could not write data to device\n");
            exit(-1);
        }
        close(fd);
        exit(0);
    }
    
    int main(int argc, char* argv[]) {
        unsigned long read_count;
        const char *mode = argv[1];
        const char *write_word = argv[RDWR_ARGUMENT];
        const char *eeprom;
        uint16_t offset = 0x00;
        int fd;
    
        if (argc < MINIMAL_ARGUMENT) goto usage;
        if (argv[OFFSET_ARGUMENT]) 
            offset = strtoul(argv[OFFSET_ARGUMENT], NULL, 0);
    
        if (!strcmp(argv[EEPROM_ARGUMENT], "EEPROM_SOM"))
            eeprom = EEPROM_SOM;
        else goto usage;
    
        if (!strcmp(mode, "read")) {
            read_count = strtoul(argv[RDWR_ARGUMENT], NULL, 0);
            fd = open(eeprom, O_RDONLY);
            if (fd < 0) 
                goto open_error;
            read_eeprom(fd, offset, read_count);
        }
    
        else if (!strcmp(mode, "write")) {
            fd = open(eeprom, O_WRONLY);
            if (fd < 0) goto open_error;
            write_eeprom(fd, offset, write_word);
        }
        else goto usage;
    
        return 0;
    
    usage:
        errx(-1, "[read/write] [eeprom] [bytes to read/word to write] optional:[offset]\nSupported eeprom: EEPROM_SOM");
    
    open_error:
        printf("Error: could not open device file %s\n", eeprom);
        return -1;
    }