[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