zoukankan      html  css  js  c++  java
  • AT24 I2C EEPROM解析及测试

     关键词:AT24、I2C、nvmem、EEPROM。

    1. AT24C介绍

    AT24C是一款采用I2C通信的EEPROM,相关驱动涉及到I2C和nvmem。

    I2C是读写数据的通道,nvmem将AT24C注册为nvmem设备。

    2.源码分析

    2.1 DTS

    at24是挂在i2c总线下的设备,硬件接到哪个i2c,DTS中也需要对应修改。

    其中需要注意的是,status不能是disabled,pinctrl需要配置。

    其中at24的campatible需要和代码对应。

            i2c3 {
                compatible = "snps,designware-i2c";
                //status = "disabled";--------------------------注释掉disabled才能使用此i2c。
                reg = <0xfc407000 0x1000>;
                interrupts = <27>;
                clocks = <&high_apb_clk>;
                pinctrl-names = "default";
                pinctrl-0 = <&i2c3_2>;
                #address-cells = <1>;
                #size-cells = <0>;
           clock-frequency = <400000>;----------------------调整对应i2c的频率,其中400K和1M需要设置时序才能正确使用。
                at24@50 {
                    compatible = "at24,24cm02";
                    reg = <0x50>;
                };
            };

    2.2 AT24初始化

    at24的probe主要做数据有效检查、根据i2c的capability决定读写函数、读1字节验证功能、最后注册对应的nvmem设备。

    static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
    {
        struct at24_platform_data chip;
        kernel_ulong_t magic = 0;
        bool writable;
        int use_smbus = 0;
        int use_smbus_write = 0;
        struct at24_data *at24;
        int err;
        unsigned i, num_addresses;
        u8 test_byte;
    
    ...
        if (!is_power_of_2(chip.byte_len))-----------------------------------------数据检查
            dev_warn(&client->dev,
                "byte_len looks suspicious (no power of 2)!
    ");
        if (!chip.page_size) {
            dev_err(&client->dev, "page_size must not be 0!
    ");
            return -EINVAL;
        }
        if (!is_power_of_2(chip.page_size))
            dev_warn(&client->dev,
                "page_size looks suspicious (no power of 2)!
    ");
    
        /* Use I2C operations unless we're stuck with SMBus extensions. */
        if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {-------------是否具备I2C_FUNC_I2C,如果不具备则判断是否具备下面能力。然后决定是否使用SMBus。
    ...
        }
    
        if (chip.flags & AT24_FLAG_TAKE8ADDR)
            num_addresses = 8;
        else
            num_addresses =    DIV_ROUND_UP(chip.byte_len,
                (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);
    
        at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +
            num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);
        if (!at24)
            return -ENOMEM;
    
        mutex_init(&at24->lock);
        at24->use_smbus = use_smbus;
        at24->use_smbus_write = use_smbus_write;
        at24->chip = chip;
        at24->num_addresses = num_addresses;
    
        if ((chip.flags & AT24_FLAG_SERIAL) && (chip.flags & AT24_FLAG_MAC)) {
            dev_err(&client->dev,
                "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC.");
            return -EINVAL;
        }
    
        if (chip.flags & AT24_FLAG_SERIAL) {--------------------------------------根据flags,以及是否使用SMBus,选择合适的read_func()/write_func()。
            at24->read_func = at24_eeprom_read_serial;
        } else if (chip.flags & AT24_FLAG_MAC) {
            at24->read_func = at24_eeprom_read_mac;
        } else {
            at24->read_func = at24->use_smbus ? at24_eeprom_read_smbus
                              : at24_eeprom_read_i2c;
        }
    
        if (at24->use_smbus) {
            if (at24->use_smbus_write == I2C_SMBUS_I2C_BLOCK_DATA)
                at24->write_func = at24_eeprom_write_smbus_block;
            else
                at24->write_func = at24_eeprom_write_smbus_byte;
        } else {
            at24->write_func = at24_eeprom_write_i2c;
        }
    
        writable = !(chip.flags & AT24_FLAG_READONLY);
        if (writable) {---------------------------------------------------------------------------------write_max决定后面i2c写大小,大于此数字会被拆分。
            if (!use_smbus || use_smbus_write) {
    
                unsigned write_max = chip.page_size;
    
                if (write_max > io_limit)
                    write_max = io_limit;
                if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
                    write_max = I2C_SMBUS_BLOCK_MAX;
                at24->write_max = write_max;
    
                /* buffer (data + address at the beginning) */
                at24->writebuf = devm_kzalloc(&client->dev,
                    write_max + 2, GFP_KERNEL);
                if (!at24->writebuf)
                    return -ENOMEM;
            } else {
                dev_warn(&client->dev,
                    "cannot write due to controller restrictions.");
            }
        }
    
        at24->client[0] = client;
    
        /* use dummy devices for multiple-address chips */
        for (i = 1; i < num_addresses; i++) {
            at24->client[i] = i2c_new_dummy(client->adapter,
                        client->addr + i);
            if (!at24->client[i]) {
                dev_err(&client->dev, "address 0x%02x unavailable
    ",
                        client->addr + i);
                err = -EADDRINUSE;
                goto err_clients;
            }
        }
    
        i2c_set_clientdata(client, at24);
    
        /*
         * Perform a one-byte test read to verify that the
         * chip is functional.
         */
        err = at24_read(at24, 0, &test_byte, 1);--------------------------------读一字节进行测试。
        if (err) {
            err = -ENODEV;
            goto err_clients;
        }
    
        at24->nvmem_config.name = dev_name(&client->dev);-----------------------注册nvmem设备,对应设备/sys/bus/i2c/devices/x-0050/eeprom。
        at24->nvmem_config.dev = &client->dev;
        at24->nvmem_config.read_only = !writable;
        at24->nvmem_config.root_only = true;
        at24->nvmem_config.owner = THIS_MODULE;
        at24->nvmem_config.compat = true;
        at24->nvmem_config.base_dev = &client->dev;
        at24->nvmem_config.reg_read = at24_read;--------------------------------对于非SMBus设备,对应at24_eeprom_read_i2c()。
        at24->nvmem_config.reg_write = at24_write;------------------------------对非SMBus设备,对应at24_eeprom_write_i2c()。
        at24->nvmem_config.priv = at24;
        at24->nvmem_config.stride = 4;
        at24->nvmem_config.word_size = 1;
        at24->nvmem_config.size = chip.byte_len;
    
        at24->nvmem = nvmem_register(&at24->nvmem_config);
    
        if (IS_ERR(at24->nvmem)) {
            err = PTR_ERR(at24->nvmem);
            goto err_clients;
        }
    ...
        /* export data to kernel code */
        if (chip.setup)
            chip.setup(at24->nvmem, chip.context);
    
        return 0;
    
    err_clients:
        for (i = 1; i < num_addresses; i++)
            if (at24->client[i])
                i2c_unregister_device(at24->client[i]);
    
        return err;
    }
    
    static int at24_remove(struct i2c_client *client)
    {
        struct at24_data *at24;
        int i;
    
        at24 = i2c_get_clientdata(client);
    
        nvmem_unregister(at24->nvmem);
    
        for (i = 1; i < at24->num_addresses; i++)
            i2c_unregister_device(at24->client[i]);
    
        return 0;
    }
    
    static struct i2c_driver at24_driver = {
        .driver = {
            .name = "at24",
            .acpi_match_table = ACPI_PTR(at24_acpi_ids),
        },
        .probe = at24_probe,
        .remove = at24_remove,
        .id_table = at24_ids,
    };

    2.3 I2C读写

    上层应用的读写,在底层是有限制的。

    write就变成了一字节一字节写,read一次只能读取一个128字节大小。

    static int at24_read(void *priv, unsigned int off, void *val, size_t count)
    {
        struct at24_data *at24 = priv;
        char *buf = val;
    
        if (unlikely(!count))
            return count;
    
        mutex_lock(&at24->lock);
    
        while (count) {---------------------------------------------------上层传入的count大小,交给i2c进行去读。但是每次读取多少字节受限于i2c,一次循环。
            int    status;
    
            status = at24->read_func(at24, buf, off, count);--------------每次i2c读取操作,buf、off递增,count递减。
            if (status < 0) {
                mutex_unlock(&at24->lock);
                return status;
            }
            buf += status;
            off += status;
            count -= status;
        }
    
        mutex_unlock(&at24->lock);
    
        return 0;
    }
    
    static int at24_write(void *priv, unsigned int off, void *val, size_t count)
    {
        struct at24_data *at24 = priv;
        char *buf = val;
    
        if (unlikely(!count))
            return -EINVAL;
    
        /*
         * Write data to chip, protecting against concurrent updates
         * from this host, but not from other I2C masters.
         */
        mutex_lock(&at24->lock);
    
        while (count) {---------------------------------------------------上层传下来的count,不一定一次写完。此处进行loop。
            int status;
    
            status = at24->write_func(at24, buf, off, count);--------------每次只写1字节,所以效率很低。
            if (status < 0) {
                mutex_unlock(&at24->lock);
                return status;
            }
            buf += status;
            off += status;
            count -= status;
        }
    
        mutex_unlock(&at24->lock);
    
        return 0;
    }
    
    static ssize_t at24_eeprom_read_i2c(struct at24_data *at24, char *buf,
                        unsigned int offset, size_t count)
    {
        unsigned long timeout, read_time;
        struct i2c_client *client;
        struct i2c_msg msg[2];
        int status, i;
        u8 msgbuf[2];
    
        memset(msg, 0, sizeof(msg));
        client = at24_translate_offset(at24, &offset);
    
        if (count > io_limit)
            count = io_limit;------------------------------------------------不管上层传入读取多大数据,都受限于io_limit,这里为一个128字节。
    
        /*
         * When we have a better choice than SMBus calls, use a combined I2C
         * message. Write address; then read up to io_limit data bytes. Note
         * that read page rollover helps us here (unlike writes). msgbuf is
         * u8 and will cast to our needs.
         */
        i = 0;
        if (at24->chip.flags & AT24_FLAG_ADDR16)
            msgbuf[i++] = offset >> 8;
        msgbuf[i++] = offset;
    
        msg[0].addr = client->addr;
        msg[0].buf = msgbuf;
        msg[0].len = i;
    
        msg[1].addr = client->addr;
        msg[1].flags = I2C_M_RD;
        msg[1].buf = buf;
        msg[1].len = count;
    
        loop_until_timeout(timeout, read_time) {
            status = i2c_transfer(client->adapter, msg, 2);
            if (status == 2)
                status = count;
    
            dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)
    ",
                    count, offset, status, jiffies);
    
            if (status == count)
                return count;
        }
    
        return -ETIMEDOUT;
    }
    
    static ssize_t at24_eeprom_write_i2c(struct at24_data *at24, const char *buf,
                         unsigned int offset, size_t count)
    {
        unsigned long timeout, write_time;
        struct i2c_client *client;
        struct i2c_msg msg;
        ssize_t status = 0;
        int i = 0;
    
        client = at24_translate_offset(at24, &offset);
        count = at24_adjust_write_count(at24, offset, count);
    
        msg.addr = client->addr;
        msg.flags = 0;
    
        /* msg.buf is u8 and casts will mask the values */
        msg.buf = at24->writebuf;
        if (at24->chip.flags & AT24_FLAG_ADDR16)
            msg.buf[i++] = offset >> 8;
    
        msg.buf[i++] = offset;
        memcpy(&msg.buf[i], buf, count);
        msg.len = i + count;
    
        loop_until_timeout(timeout, write_time) {
            status = i2c_transfer(client->adapter, &msg, 1);
            if (status == 1)
                status = count;
    
            dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)
    ",
                    count, offset, status, jiffies);
    
            if (status == count)
                return count;
        }
    
        return -ETIMEDOUT;
    }

    3. eeprom测试

    不同频率总线,对应的速率通过read较好体现;编写测试程序进行速率验证,中间经过文件系统一些限制。

    3.1 测试预期

    在at24.c文件中有关于不同速率测试预期,测试读要比写更纯粹一点,因为写只能一个字节一个自己,每次写完地址,再写一个字节。

    /*
     * This parameter is to help this driver avoid blocking other drivers out
     * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C
     * clock, one 256 byte read takes about 1/43 second which is excessive;
     * but the 1/170 second it takes at 400 kHz may be quite reasonable; and
     * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible.
     *
     * This value is forced to be a power of two so that writes align on pages.
     */

    可以看出一次读256字节,1MHz速率耗时2.3ms;400KHz速率耗时5.9ms;100KHz速率耗时23.3ms

    修改io_limit可以改变一次io字节数。

    3.2 测试程序

    源代码如下,编译后执行“”teeprom /sys/bus/i2c/devices/1-0050/eeprom value count”即可:

    #include <stdio.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    #include <linux/i2c-dev.h>
    #include <errno.h>
    #include <time.h>
    
    //teeprom device value size
    #define EEPROM_SIZE 262144
    #define PAGE_SIZE   4096
    #define EEPROM_PAGES (EEPROM_SIZE/PAGE_SIZE)
    int main(int argc, char *argv[])
    {
        int num, value;
        int fd, pFile, page_index = 0;
        char *device_name, *buff, *out_buf;
        struct timespec time_0, time_1, time_2;
    
        printf("Please input as:");
        printf("teeprom device_name value size
    ");
        fflush(stdout);
        
        if(argc < 3){
            printf("arg error
    ");
            return -1;    
        }
    
        device_name = argv[1];
        value = atoi(argv[2]);
        num = atoi(argv[3]);
     
        buff = calloc(sizeof(char), num);
        if(buff < 0){
            printf("alloc failed
    ");
            return -1;
        }
        memset(buff, value, num);
    
        out_buf = calloc(sizeof(char), EEPROM_SIZE);
    
        fd = open(device_name,O_RDWR);
        if(fd < 0){
            printf("device open failed
    ");
            return -1;    
        }
        clock_gettime(CLOCK_MONOTONIC, &time_0);
        printf("%8d.%8d Write %d to 0x00 for %d bytes.
    ", time_0.tv_sec, time_0.tv_nsec, value, num);
        write(fd, buff, num);----------------------------------虽然此处希望写入num个字节,但是在系统调用到i2c写入中间有些限制了大小。
    
        clock_gettime(CLOCK_MONOTONIC, &time_1);
        printf("%8d.%8d Read from 0x00 for %d bytes.
    ", time_1.tv_sec, time_1.tv_nsec, EEPROM_SIZE);
        lseek(fd, 0, SEEK_SET);
        for(page_index = 0; page_index < EEPROM_PAGES; page_index++)
        {
            read(fd, out_buf, PAGE_SIZE);----------------------对于读也同样的有限制,只能循环读page大小。
        }
        close(fd);
    
        clock_gettime(CLOCK_MONOTONIC, &time_2);
        printf("%8d.%8d Write to eeprom.bin.
    ", time_2.tv_sec, time_2.tv_nsec);
        pFile = fopen("eeprom.bin","wb");
        if(pFile < 0){
            printf("device open failed
    ");
            return -1;    
        }
        fwrite(out_buf,EEPROM_SIZE,1,pFile);
        fclose(pFile);
    
        return 0;
    }

    3.3 结果分析

    eeprom对应的nvmem节点如下,可以直接对其open/read/write/close操作。

    /sys/bus/i2c/devices/1-0050/eeprom

    /sys/bus/i2c/devices/3-0050/eeprom

    对eeprom进行读写的backtrace如下:

    #0  at24_eeprom_read_i2c (at24=0xbda6b18c, buf=0xbdbc4380 '177' <repeats 200 times>..., offset=896, count=3200) at drivers/misc/eeprom/at24.c:254
    #1  0x802673fe in at24_read (priv=0xbda6b18c, off=3183231872, val=0x380, count=3200) at drivers/misc/eeprom/at24.c:519
    #2  0x803847f0 in nvmem_reg_read (nvmem=<optimized out>, nvmem=<optimized out>, bytes=<optimized out>, val=<optimized out>, offset=<optimized out>) at drivers/nvmem/core.c:74
    #3  bin_attr_nvmem_read (filp=<optimized out>, kobj=<optimized out>, attr=<optimized out>, buf=<optimized out>, pos=0, count=4096) at drivers/nvmem/core.c:114
    #4  0x800fc0b2 in sysfs_kf_bin_read (of=<optimized out>, buf=<optimized out>, count=<optimized out>, pos=-9218947567704862720) at fs/sysfs/file.c:102
    #5  0x800fb808 in kernfs_file_direct_read (ppos=<optimized out>, count=<optimized out>, user_buf=<optimized out>, of=<optimized out>) at fs/kernfs/file.c:214
    #6  kernfs_fop_read (file=<optimized out>, user_buf=0xbdbc4380 '177' <repeats 200 times>..., count=<optimized out>, ppos=0xc80) at fs/kernfs/file.c:254
    #7  0x800a9436 in __vfs_read (file=0xbda6b18c, buf=<optimized out>, count=<optimized out>, pos=0xc80) at fs/read_write.c:452
    #8  0x800a9fe8 in vfs_read (file=0xbdb2ed20, buf=0xbdbc4380 '177' <repeats 200 times>..., count=262144, pos=0xbdb71f44) at fs/read_write.c:475
    #9  0x800aad02 in SYSC_read (count=<optimized out>, buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:591
    #10 SyS_read (fd=<optimized out>, buf=717357064, count=262144) at fs/read_write.c:584
    #11 0x80020b40 in csky_systemcall () at arch/csky/kernel/entry.S:154
    #12 0x2ac20008 in ?? ()
    
    
    #0  at24_eeprom_write_i2c (at24=0xbda6b18c, buf=0xbdb9a2c0 "177", offset=0, count=1) at drivers/misc/eeprom/at24.c:465
    #1  0x8026739e in at24_write (priv=0xbda6b18c, off=3183059648, val=0x0, count=1) at drivers/misc/eeprom/at24.c:551
    #2  0x80384898 in nvmem_reg_write (nvmem=<optimized out>, nvmem=<optimized out>, bytes=<optimized out>, val=<optimized out>, offset=<optimized out>) at drivers/nvmem/core.c:83
    #3  bin_attr_nvmem_write (filp=<optimized out>, kobj=<optimized out>, attr=<optimized out>, buf=<optimized out>, pos=0, count=1) at drivers/nvmem/core.c:148
    #4  0x800fc1a6 in sysfs_kf_bin_write (of=<optimized out>, buf=<optimized out>, count=<optimized out>, pos=-9218948675806429183) at fs/sysfs/file.c:163
    #5  0x800fb706 in kernfs_fop_write (file=<optimized out>, user_buf=<optimized out>, count=1, ppos=0xbdb71f44) at fs/kernfs/file.c:316
    #6  0x800a9506 in __vfs_write (file=0xbda6b18c, p=<optimized out>, count=<optimized out>, pos=0x1) at fs/read_write.c:510
    #7  0x800aa106 in vfs_write (file=0xbdb2ed20, buf=0xbdb9a2c0 "177", count=<optimized out>, pos=0xbdb71f44) at fs/read_write.c:560
    #8  0x800aad9e in SYSC_write (count=<optimized out>, buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:607
    #9  SyS_write (fd=<optimized out>, buf=49496, count=1) at fs/read_write.c:599
    #10 0x80020b40 in csky_systemcall () at arch/csky/kernel/entry.S:154
    #11 0x0000c158 in ?? ()

    csky_systemcall()->SyS_read()->SYSC_read()->vfs_read()->__vfs_read()->kernfs_fop_read()->kernfs_file_direct_read()->sysfs_kf_bin_read()->bin_attr_nvmem_read()->nvmem_reg_read()->at24_read()->at24_eeprom_read_i2c()

    其中kernfs_file_direct_read()对读的大小进行了限制:

    static ssize_t kernfs_file_direct_read(struct kernfs_open_file *of,
                           char __user *user_buf, size_t count,
                           loff_t *ppos)
    {
        ssize_t len = min_t(size_t, count, PAGE_SIZE);-------------------------可以看出实际读大小取count和PAGE_SIZE小者。
    ...
        of->event = atomic_read(&of->kn->attr.open->event);
        ops = kernfs_ops(of->kn);
       if (ops->read)
         len = ops->read(of, buf, len, *ppos);
      else
         len = -EINVAL;
    ...
    }

    到at24这一层,at24_eeprom_read_i2c()又对read进行了限制,每次只能读取最大io_limit个字节。

    at24_read()中循环读取。

    cky_systemcall()->SyS_write()->SYSC_write()->vfs_write()->__vfs_write()->kernfs_fop_write()->sysfs_kf_bin_write()->bin_attr_nvmem_write()->nvmem_reg_write()->at24_write()->at24_eeprom_write_i2c()

    写实际上也限制了大小:

    static ssize_t kernfs_fop_write(struct file *file, const char __user *user_buf,
                    size_t count, loff_t *ppos)
    {
    ...
        if (of->atomic_write_len) {
            len = count;
            if (len > of->atomic_write_len)
                return -E2BIG;
        } else {
            len = min_t(size_t, count, PAGE_SIZE);-----------------------------此处可以看出实际写大小取count和PAGE_SIZE小者。
        }
    ...
        ops = kernfs_ops(of->kn);
        if (ops->write)
            len = ops->write(of, buf, len, *ppos);
        else
            len = -EINVAL;
    ...
    }

    at24_eeprom_write_i2c()中每次只能写入一个字节,at24_write()循环写入。

    100KHz读256字节大概23.3ms,下面实际速度在22ms左右,符合预期。

    [ 1105.099343] at24 1-0050: read 256@0 --> 256 (201274)
    [ 1105.121371] at24 1-0050: read 256@256 --> 256 (201279)
    [ 1105.143395] at24 1-0050: read 256@512 --> 256 (201285)
    [ 1105.165428] at24 1-0050: read 256@768 --> 256 (201290)

    400KHz读256字节大概在5.9ms,下面实际速度在6.25ms,符合预期。

    [ 3616.209011] at24 3-0050: read 256@0 --> 256 (829051)
    [ 3616.215264] at24 3-0050: read 256@256 --> 256 (829053)
    [ 3616.221512] at24 3-0050: read 256@512 --> 256 (829054)
    [ 3616.227760] at24 3-0050: read 256@768 --> 256 (829056)
    [ 3616.234007] at24 3-0050: read 256@1024 --> 256 (829057)

    在实际测试中,虽然上层希望读写一大块内容,但是底层对其进行了几次拆分。

    实际结果可能会合期望有较大差别,这时候就需要跟踪读写流程。

    参考文档:《I2C子系统之at24c02读写测试

  • 相关阅读:
    Qt之使用CQU库快速开发统一风格界面
    Springboot中使用redis进行api防刷限流
    SpringBoot使用拦截器、过滤器、监听器
    ElasticSearch中文分词器-IK分词器的使用
    里式替换原则——面向对象程序设计原则
    使用Spring中的PropertyPlaceholderConfigurer读取文件
    新版本SpringCloud sleuth整合zipkin
    解放双手,在PC端进行Android真机调试
    破解Android设备无法联调的谜题
    打python&adb组合拳,实现微信读书永久免费读
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/10012250.html
Copyright © 2011-2022 走看看