[i2c] [patch] new-style i2c eeprom driver
David Brownell
david-b at pacbell.net
Mon May 14 19:08:26 CEST 2007
Add a new-style driver for most I2C EEPROMs, giving sysfs read/write
access to their contents. Tested with 24LC00, M24C01, AT24C04, 24LC64,
and 24WC256 so far; at multiple I2C clock rates.
Since it doesn't try to use SMBus operations for reads, it's not
currently a replacement for the "eeprom" driver on PCs. And in
the same vein, it only uses page mode writes, which don't match
SMBus bulk primitives.
Signed-off-by: David Brownell <dbrownell at users.sourceforge.net>
---
It's been a while since I posted this ... I thought it was worth
reposting, since it's been quickly updated to be a new-style driver.
This still seems to work, but handling the surplus addresses of e.g.
a 24c00 device is temporarily disabled. I thought it might be worth
adding a generic "dummy" driver to i2c-core which could handle such
device nodes; comments?
Also, I don't much like having a table of chip descriptors in the
driver even if as read-only data. A different approach would use
defined struct constants in a header file that the board-specific
init code would stuff into platform_data; comments?
drivers/i2c/chips/Kconfig | 25 +
drivers/i2c/chips/Makefile | 2
drivers/i2c/chips/at24.c | 627 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 654 insertions(+)
--- g26.orig/drivers/i2c/chips/Kconfig
+++ g26/drivers/i2c/chips/Kconfig
@@ -24,6 +24,31 @@ config SENSORS_DS1374
This driver can also be built as a module. If so, the module
will be called ds1374.
+config I2C_AT24
+ tristate "EEPROMs from most vendors"
+ depends on I2C && SYSFS && EXPERIMENTAL
+ help
+ Enable this driver to get read/write support to most I2C EEPROMs,
+ after you configure the driver to know about each eeprom on on
+ your target board. Use these generic chip names, instead of
+ vendor-specific ones like at24c64 or 24lc02:
+
+ 24c00, 24c01, 24c02, dimm (readonly 24c02), 24c04, 24c08,
+ 24c16, 24c32, 24c64, 24c128, 24c256, 24c512, 24c1024
+
+ Unless you like data loss puzzles, always be sure that any chip
+ you configure as a 24c32 (32 Kbits) or larger is NOT really a
+ 24c16 (16 Kbits) or smaller. (Marking the chip as readonly won't
+ help recover from this.) Also, if your chip has any software
+ write-protect mechanism you may want to make sure this driver
+ won't turn it on by accident.
+
+ The current version of this driver demands full I2C bus support,
+ so it won't yet work on most PCs (limited to SMBUS).
+
+ This driver can also be built as a module. If so, the module
+ will be called at24.
+
config SENSORS_EEPROM
tristate "EEPROM reader"
depends on EXPERIMENTAL
--- g26.orig/drivers/i2c/chips/Makefile
+++ g26/drivers/i2c/chips/Makefile
@@ -4,6 +4,8 @@
obj-$(CONFIG_SENSORS_DS1337) += ds1337.o
obj-$(CONFIG_SENSORS_DS1374) += ds1374.o
+# let 'at24' get a chance before the less powerful 'eeprom'
+obj-$(CONFIG_I2C_AT24) += at24.o
obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o
obj-$(CONFIG_SENSORS_MAX6875) += max6875.o
obj-$(CONFIG_SENSORS_M41T00) += m41t00.o
--- /dev/null
+++ g26/drivers/i2c/chips/at24.c
@@ -0,0 +1,627 @@
+/*
+ * at24.c -- support many I2C EEPROMs, including Atmel AT24C models
+ *
+ * Copyright (C) 2005-2006 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+
+/* I2C EEPROMs from most vendors are inexpensive and mostly interchangeable.
+ * Differences between different vendor product lines (like Atmel AT24C or
+ * MicroChip 24LC, etc) won't much matter for typical read/write access.
+ *
+ * However, misconfiguration can lose data (e.g. "set 16 bit memory address"
+ * could be interpreted as "write data at 8 bit address", or writing with too
+ * big a page size), so board-specific EEPROM configuration should use static
+ * board descriptions rather than dynamic probing when there's much potential
+ * for confusion.
+ *
+ * Most such EEPROMS use addresses 0x50..0x57, but these of course are not
+ * specific to such chips; a pcf8563 RTC uses 0x51, for example.
+ *
+ * So this driver uses "new style" I2C driver binding, and expects to be
+ * told what devices exist, probably arch/XXX/mach-YYY/board-ZZZ.c tables.
+ * Be sure to set the board_info "type" to identify the eeprom type.
+ *
+ * Key current differences from "eeprom" driver:
+ * (a) "at24" also supports write access
+ * (b) "at24" handles all common eeproms, not just 24c02 compatibles
+ * (c) "at24" is a new-styled driver, not a legacy driver
+ * (d) "eeprom" will probe i2c and set up a 24c02 at each address;
+ * "at24" works from a static config, with part types and addresses
+ * (e) "eeprom" handles SMBus-only hardware; "at24" wants (*) real I2C.
+ *
+ * (*) Patches accepted...
+ */
+
+
+/* As seen through Linux I2C, differences between the most common types
+ * of I2C memory include:
+ * - How many I2C addresses the chip consumes: 1, 2, 4, or 8?
+ * - Memory address space for one I2C address: 256 bytes, or 64 KB?
+ * - How full that memory space is: 16 bytes, 256, 32Kb, etc?
+ * - What write page size does it support?
+ */
+struct chip_desc {
+ u32 byte_len; /* of 1..8 i2c addrs, total */
+ char name[10];
+ u16 page_size; /* for writes */
+ u8 i2c_addr_mask; /* for multi-addr chips */
+ u8 flags;
+#define EE_ADDR2 0x0080 /* 16 bit addrs; <= 64 KB */
+#define EE_READONLY 0x0040
+#define EE_24RF08 0x0001 /* SMBUS_QUICK problem */
+};
+
+struct at24_data {
+ struct i2c_client *client;
+ struct semaphore lock;
+ struct bin_attribute bin;
+
+ struct chip_desc chip;
+
+ /* each chip has an internal "memory address" pointer;
+ * we remember it for faster read access.
+ */
+ u32 next_addr;
+
+ /* some chips tie up multiple I2C addresses ... */
+ int users;
+ struct i2c_client extras[];
+};
+
+/* Specs often allow 5 msec for a page write, sometimes 20 msec;
+ * it's important to recover from write timeouts.
+ */
+#define EE_TIMEOUT 25
+
+
+/*-------------------------------------------------------------------------*/
+
+/* REVISIT want a way to remove the enumeration/probe infrastructure
+ * after driver binding, in the common case where the infrastructure
+ * (controllers, their drivers) is statically linked.
+ *
+ * In fact ... all this stuff should just be platform_data, provided
+ * by the board, so that we don't have to maintain the tables.
+ */
+
+static const struct chip_desc chips[] = {
+
+/* this first block of EEPROMS uses 8 bit memory addressing */
+{
+ /* 128 bit chip */
+ .name = "24c00",
+ .byte_len = 128 / 8,
+ .i2c_addr_mask = 0x07, /* I2C A0-A2 ignored */
+ .page_size = 1,
+}, {
+ /* 1 Kbit chip */
+ .name = "24c01",
+ .byte_len = 1024 / 8,
+ /* some have 16 byte pages: */
+ .page_size = 8,
+}, {
+ /* 2 Kbit chip */
+ .name = "24c02",
+ .byte_len = 2048 / 8,
+ /* some have 16 byte pages: */
+ .page_size = 8,
+}, {
+ /* 2 Kbit chip */
+ .name = "dimm", /* 24c02 in memory DIMMs */
+ .byte_len = 2048 / 8,
+ .flags = EE_READONLY,
+ /* some have 16 byte pages: */
+ .page_size = 8,
+}, {
+ /* 4 Kbit chip */
+ .name = "24c04",
+ .byte_len = 4096 / 8,
+ .page_size = 16,
+ .i2c_addr_mask = 0x01, /* I2C A0 is MEM A8 */
+}, {
+ /* 8 Kbit chip */
+ .name = "24c08",
+ .byte_len = 8192 / 8,
+ .page_size = 16,
+ .i2c_addr_mask = 0x03, /* I2C A1-A0 is MEM A9-A8 */
+}, {
+ /* 8 Kbit chip */
+ .name = "24rf08",
+ .byte_len = 8192 / 8,
+ .flags = EE_24RF08,
+ .page_size = 16,
+ .i2c_addr_mask = 0x03, /* I2C A1-A0 is MEM A9-A8 */
+}, {
+ /* 16 Kbit chip */
+ .name = "24c16",
+ .byte_len = 16384 / 8,
+ .page_size = 16,
+ .i2c_addr_mask = 0x07, /* I2C A2-A0 is MEM A10-A8 */
+},
+
+/* this second block of EEPROMS uses 16 bit memory addressing */
+{
+ /* 32 Kbits */
+ .name = "24c32",
+ .byte_len = 32768 / 8,
+ .flags = EE_ADDR2,
+ .page_size = 32,
+}, {
+ /* 64 Kbits */
+ .name = "24c64",
+ .byte_len = 65536 / 8,
+ .flags = EE_ADDR2,
+ .page_size = 32,
+}, {
+ /* 128 Kbits */
+ .name = "24c128",
+ .byte_len = 131072 / 8,
+ .flags = EE_ADDR2,
+ .page_size = 64,
+}, {
+ /* 256 Kbits */
+ .name = "24c256",
+ .byte_len = 262144 / 8,
+ .flags = EE_ADDR2,
+ .page_size = 64,
+}, {
+ /* 512 Kbits */
+ .name = "24c512",
+ .byte_len = 524288 / 8,
+ .flags = EE_ADDR2,
+ .page_size = 128,
+}, {
+ /* 1 Mbits */
+ .name = "24c1024",
+ .byte_len = 1048576 / 8,
+ .flags = EE_ADDR2,
+ .page_size = 256,
+ .i2c_addr_mask = 0x01, /* I2C A0 is MEM A16 */
+}
+
+};
+
+static inline const struct chip_desc *
+find_chip(const char *s)
+{
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(chips); i++)
+ if (strnicmp(s, chips[i].name, sizeof chips[i].name) == 0)
+ return &chips[i];
+ return NULL;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* 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.
+ * VALUE MUST BE A POWER OF TWO so writes will align on pages!!
+ */
+static unsigned io_limit = 128;
+module_param(io_limit, uint, 0);
+MODULE_PARM_DESC(io_limit, "maximum bytes per i/o (default 128)");
+
+static inline int at24_ee_address(
+ const struct chip_desc *chip,
+ struct i2c_msg *msg,
+ unsigned *offset,
+ u32 *next_addr
+)
+{
+ unsigned per_address = 256;
+
+ if (*offset >= chip->byte_len)
+ return -EINVAL;
+
+ /* Nothing to do unless accessing that memory would go to some
+ * later I2C address. In that case, modify the output params.
+ * Yes, it can loop more than once on 24c08 and 24c16.
+ */
+ if (chip->flags & EE_ADDR2)
+ per_address = 64 * 1024;
+ while (*offset >= per_address) {
+ msg->addr++;
+ *offset -= per_address;
+ *next_addr -= per_address;
+ }
+ return 0;
+}
+
+static ssize_t
+at24_ee_read(
+ struct at24_data *at24,
+ char *buf,
+ unsigned offset,
+ size_t count
+)
+{
+ struct i2c_msg msg;
+ ssize_t status;
+ u32 next_addr;
+ size_t transferred = 0;
+
+ down(&at24->lock);
+ msg.addr = at24->client->addr;
+ msg.flags = 0;
+
+ /* maybe adjust i2c address and offset
+ * NOTE: we could now search at24->extras to choose use
+ * some i2c_client other than the main one...
+ */
+ next_addr = at24->next_addr;
+ status = at24_ee_address(&at24->chip, &msg, &offset, &next_addr);
+ if (status < 0)
+ goto done;
+
+ /* Maybe change the current on-chip address with a dummy write */
+ if (next_addr != offset) {
+ u8 tmp[2];
+
+ msg.buf = tmp;
+ tmp[1] = (u8) offset;
+ tmp[0] = (u8) (offset >> 8);
+ if (at24->chip.flags & EE_ADDR2) {
+ /* NOTE: could i2c_smbus_write_byte_data() */
+ msg.len = 2;
+ } else {
+ /* NOTE: could i2c_smbus_write_byte() */
+ msg.len = 1;
+ msg.buf++;
+ }
+ status = i2c_transfer(at24->client->adapter, &msg, 1);
+ dev_dbg(&at24->client->dev,
+ "addr %02x, set current to %d --> %zd\n",
+ msg.addr, offset, status);
+ if (status != 1) {
+ if (status == 0)
+ status = -EIO;
+ goto done;
+ }
+ next_addr = at24->next_addr = offset;
+ }
+
+ /* issue bounded sequential reads for the data bytes, knowing that
+ * read page rollover goes to the next page and/or memory block.
+ */
+ while (count > 0) {
+ unsigned segment;
+
+ /* NOTE: could use many i2c_smbus_read_byte() calls,
+ * if the adapter doesn't support I2C ... or sometimes
+ * i2c_smbus_read_i2c_block_data().
+ */
+ if (count > io_limit)
+ segment = io_limit;
+ else
+ segment = count;
+
+ msg.len = segment;
+ msg.buf = buf;
+ msg.flags = I2C_M_RD;
+ status = i2c_transfer(at24->client->adapter, &msg, 1);
+ dev_dbg(&at24->client->dev, "read %zd, %zd --> %zd\n",
+ transferred, count, status);
+
+ if (status != 1) {
+ if (status == 0)
+ status = -EIO;
+ break;
+ }
+
+ count -= segment;
+
+ at24->next_addr += segment;
+ offset += segment;
+ buf += segment;
+ transferred += segment;
+ }
+done:
+ if (status <= 0)
+ at24->next_addr = at24->bin.size;
+ up(&at24->lock);
+ return transferred ? transferred : status;
+}
+
+static ssize_t
+at24_bin_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+ struct i2c_client *client;
+ struct at24_data *at24;
+
+ client = to_i2c_client(container_of(kobj, struct device, kobj));
+ at24 = i2c_get_clientdata(client);
+
+ if (unlikely(off >= at24->bin.size))
+ return 0;
+ if ((off + count) > at24->bin.size)
+ count = at24->bin.size - off;
+ if (unlikely(!count))
+ return count;
+
+ /* we don't maintain caches for any data: simpler, cheaper */
+ return at24_ee_read(at24, buf, off, count);
+}
+
+
+/* REVISIT: export read and write accessors, so it's easy for
+ * other kernel code to use the EEPROM data.
+ */
+
+
+/* Note that if the hardware write-protect pin is pulled high, the whole
+ * chip is normally write protected. But there are plenty of product
+ * variants here, including OTP fuses and partial chip protect.
+ *
+ * For now, we only support page mode writes; the alternative is sloooow.
+ * SMBus block writes are no help at all; they always embed the length.
+ */
+static ssize_t
+at24_ee_write(struct at24_data *at24, char *buf, loff_t off, size_t count)
+{
+ struct i2c_msg msg;
+ ssize_t status = 0;
+ unsigned written = 0;
+ u32 scratch;
+ unsigned buf_size;
+ unsigned long timeout, retries;
+
+ /* temp buffer lets us stick the address at the beginning */
+ buf_size = at24->chip.page_size;
+ if (buf_size > io_limit)
+ buf_size = io_limit;
+ msg.buf = kmalloc(buf_size + 2, GFP_KERNEL);
+ if (!msg.buf)
+ return -ENOMEM;
+ msg.flags = 0;
+
+ /* For write, rollover is within the page ... so we write at
+ * most one page, then manually roll over to the next page.
+ */
+ down(&at24->lock);
+ timeout = jiffies + msecs_to_jiffies(EE_TIMEOUT);
+ retries = 0;
+ do {
+ unsigned segment;
+ unsigned offset = (unsigned) off;
+
+ /* maybe adjust i2c address and offset */
+ msg.addr = at24->client->addr;
+ status = at24_ee_address(&at24->chip, &msg,
+ &offset, &scratch);
+ if (status < 0)
+ break;
+
+ /* write as much of a page as we can */
+ segment = buf_size - (offset % buf_size);
+ if (segment > count)
+ segment = count;
+ msg.len = segment + 1;
+ if (at24->chip.flags & EE_ADDR2) {
+ msg.len++;
+ msg.buf[1] = (u8) offset;
+ msg.buf[0] = (u8) (offset >> 8);
+ memcpy(&msg.buf[2], buf, segment);
+ } else {
+ msg.buf[0] = offset;
+ memcpy(&msg.buf[1], buf, segment);
+ }
+
+ /* The chip may take a while to finish its previous write;
+ * this write also polls for completion of the last one.
+ * So we always retry a few times.
+ */
+ status = i2c_transfer(at24->client->adapter, &msg, 1);
+ dev_dbg(&at24->client->dev,
+ "write %d bytes to %02x at %d --> %zd (%ld)\n",
+ segment, msg.addr, offset, status, jiffies);
+ if (status != 1) {
+ if (retries++ < 3 || time_after(timeout, jiffies)) {
+ /* REVISIT: at HZ=100, this is sloooow */
+ msleep(1);
+ continue;
+ }
+ dev_err(&at24->client->dev,
+ "write %d bytes offset %d to %02x, "
+ "%ld ticks err %d\n",
+ segment, offset, msg.addr,
+ jiffies - (timeout - EE_TIMEOUT),
+ (int) status);
+ status = -ETIMEDOUT;
+ break;
+ }
+
+ off += segment;
+ buf += segment;
+ count -= segment;
+ written += segment;
+ if (status < 0 || count == 0) {
+ if (written != 0)
+ status = written;
+ break;
+ }
+
+ /* yielding may avoid the losing msleep() path above */
+ yield();
+ timeout = jiffies + msecs_to_jiffies(EE_TIMEOUT);
+ retries = 0;
+ } while (count > 0);
+ at24->next_addr = at24->bin.size;
+ up(&at24->lock);
+
+ kfree(msg.buf);
+ return status;
+}
+
+static ssize_t
+at24_bin_write(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+ struct i2c_client *client;
+ struct at24_data *at24;
+
+ client = to_i2c_client(container_of(kobj, struct device, kobj));
+ at24 = i2c_get_clientdata(client);
+
+ if (unlikely(off >= at24->bin.size))
+ return -EFBIG;
+ if ((off + count) > at24->bin.size)
+ count = at24->bin.size - off;
+ if (unlikely(!count))
+ return count;
+
+ return at24_ee_write(at24, buf, off, count);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int at24_probe(struct i2c_client *client)
+{
+ const struct chip_desc *chip;
+ int writable = 1;
+ struct at24_data *at24;
+ int err;
+
+ /* REVISIT: using SMBus calls would improve portability, though
+ * at the cost of performance (especially for write access).
+ */
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_dbg(&client->dev, "needs real I2C\n");
+ return -EIO;
+ }
+ chip = find_chip(client->name);
+ if (!chip)
+ return -ENODEV;
+
+ /* REVISIT platform_data should be a struct with chip type plus
+ * readonly flag; and we'll need a way to override the r/o flag.
+ */
+
+ if (!(at24 = kcalloc(1, sizeof(struct at24_data)
+ + chip->i2c_addr_mask * sizeof(struct i2c_client),
+ GFP_KERNEL)))
+ return -ENOMEM;
+
+ init_MUTEX(&at24->lock);
+ at24->chip = *chip;
+
+ /* Export the EEPROM bytes through sysfs, since that's convenient.
+ * By default, only root should see the data (maybe passwords etc)
+ */
+ at24->bin.attr.name = "eeprom";
+ at24->bin.attr.mode = S_IRUSR;
+ at24->bin.attr.owner = THIS_MODULE;
+ at24->bin.read = at24_bin_read;
+
+ at24->bin.size = at24->chip.byte_len;
+ at24->next_addr = at24->bin.size;
+ if (writable) {
+ at24->bin.write = at24_bin_write;
+ at24->bin.attr.mode |= S_IWUSR;
+ }
+
+ /* FIXME read a byte, to make sure the chip is actually
+ * present (vs misconfiguration, or not-populated-here)
+ */
+
+ at24->client = client;
+ i2c_set_clientdata(client, at24);
+ at24->users++;
+
+#if 0
+ /* then register any other addresses, so other drivers can't */
+ if (chip->i2c_addr_mask) {
+ unsigned i;
+ struct i2c_client *c;
+
+ for (i = 0; i < chip->i2c_addr_mask; i++) {
+ c = &at24->extras[i];
+ c->addr = client->addr + i + 1;
+ c->adapter = client->adapter;
+ c->driver = &at24_driver;
+
+ i2c_set_clientdata(c, at24);
+ snprintf(c->name, sizeof c->name,
+ "%s address %d", chip->name, i + 2);
+ err = i2c_attach_client(c);
+ if (err == 0)
+ at24->users++;
+ }
+ }
+#endif
+
+#ifdef MODULE
+ err = 0;
+#else
+ err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
+#endif
+ if (err != 0) {
+ dev_dbg(&client->dev, "probe err %d\n", err);
+ kfree(at24);
+ return err;
+ }
+
+ dev_info(&client->dev, "%Zd byte %s EEPROM%s\n",
+ at24->bin.size, chip->name,
+ writable ? " (writable)" : "");
+ dev_dbg(&client->dev, "page_size %d, i2c_addr_mask %d\n",
+ at24->chip.page_size, at24->chip.i2c_addr_mask);
+ return 0;
+
+}
+
+static int __devexit at24_remove(struct i2c_client *client)
+{
+ struct at24_data *at24;
+
+ /* up to eight clients per chip, detached in any order... */
+ at24 = i2c_get_clientdata(client);
+ if (at24->users-- == 0) {
+ sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);
+ kfree(at24);
+ }
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct i2c_driver at24_driver = {
+ .driver = {
+ .name = "at24",
+ .owner = THIS_MODULE,
+ },
+ .probe = at24_probe,
+ .remove = __devexit_p(at24_remove),
+};
+
+static int __init at24_init(void)
+{
+ return i2c_add_driver(&at24_driver);
+}
+module_init(at24_init);
+
+static void __exit at24_exit(void)
+{
+ i2c_del_driver(&at24_driver);
+}
+module_exit(at24_exit);
+
+MODULE_DESCRIPTION("Driver for most I2C EEPROMs");
+MODULE_AUTHOR("David Brownell");
+MODULE_LICENSE("GPL");
+
More information about the i2c
mailing list