AT24 I2C EEPROM解析及测试

您所在的位置:网站首页 RTL8187L是什么 AT24 I2C EEPROM解析及测试

AT24 I2C EEPROM解析及测试

2024-05-21 06:40| 来源: 网络整理| 查看: 265

 关键词: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 = ; interrupts = ; clocks = ; pinctrl-names = "default"; pinctrl-0 = ; #address-cells = ; #size-cells = ;        clock-frequency = ;----------------------调整对应i2c的频率,其中400K和1M需要设置时序才能正确使用。 at24@50 { compatible = "at24,24cm02"; reg = ; }; };

 

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)!\n"); if (!chip.page_size) { dev_err(&client->dev, "page_size must not be 0!\n"); return -EINVAL; } if (!is_power_of_2(chip.page_size)) dev_warn(&client->dev, "page_size looks suspicious (no power of 2)!\n"); /* 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\n", 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)\n", 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)\n", 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 #include #include #include #include #include #include //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\n"); fflush(stdout); if(argc < 3){ printf("arg error\n"); 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\n"); 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\n"); return -1; } clock_gettime(CLOCK_MONOTONIC, &time_0); printf("%8d.%8d Write %d to 0x00 for %d bytes.\n", 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.\n", 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.\n", time_2.tv_sec, time_2.tv_nsec); pFile = fopen("eeprom.bin","wb"); if(pFile < 0){ printf("device open failed\n"); 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' ..., 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=, nvmem=, bytes=, val=, offset=) at drivers/nvmem/core.c:74 #3 bin_attr_nvmem_read (filp=, kobj=, attr=, buf=, pos=0, count=4096) at drivers/nvmem/core.c:114 #4 0x800fc0b2 in sysfs_kf_bin_read (of=, buf=, count=, pos=-9218947567704862720) at fs/sysfs/file.c:102 #5 0x800fb808 in kernfs_file_direct_read (ppos=, count=, user_buf=, of=) at fs/kernfs/file.c:214 #6 kernfs_fop_read (file=, user_buf=0xbdbc4380 '\177' ..., count=, ppos=0xc80) at fs/kernfs/file.c:254 #7 0x800a9436 in __vfs_read (file=0xbda6b18c, buf=, count=, pos=0xc80) at fs/read_write.c:452 #8 0x800a9fe8 in vfs_read (file=0xbdb2ed20, buf=0xbdbc4380 '\177' ..., count=262144, pos=0xbdb71f44) at fs/read_write.c:475 #9 0x800aad02 in SYSC_read (count=, buf=, fd=) at fs/read_write.c:591 #10 SyS_read (fd=, 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=, nvmem=, bytes=, val=, offset=) at drivers/nvmem/core.c:83 #3 bin_attr_nvmem_write (filp=, kobj=, attr=, buf=, pos=0, count=1) at drivers/nvmem/core.c:148 #4 0x800fc1a6 in sysfs_kf_bin_write (of=, buf=, count=, pos=-9218948675806429183) at fs/sysfs/file.c:163 #5 0x800fb706 in kernfs_fop_write (file=, user_buf=, count=1, ppos=0xbdb71f44) at fs/kernfs/file.c:316 #6 0x800a9506 in __vfs_write (file=0xbda6b18c, p=, count=, pos=0x1) at fs/read_write.c:510 #7 0x800aa106 in vfs_write (file=0xbdb2ed20, buf=0xbdb9a2c0 "\177", count=, pos=0xbdb71f44) at fs/read_write.c:560 #8 0x800aad9e in SYSC_write (count=, buf=, fd=) at fs/read_write.c:607 #9 SyS_write (fd=, 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读写测试》



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3