summaryrefslogtreecommitdiff
path: root/drivers/misc/eeprom/at24.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/eeprom/at24.c')
-rw-r--r--drivers/misc/eeprom/at24.c498
1 files changed, 343 insertions, 155 deletions
diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
index 9ceb63b62be5..3cdf8e1ca0ad 100644
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -58,6 +58,10 @@ struct at24_data {
int use_smbus;
int use_smbus_write;
+ ssize_t (*read_func)(struct at24_data *, char *, unsigned int, size_t);
+ ssize_t (*write_func)(struct at24_data *,
+ const char *, unsigned int, size_t);
+
/*
* Lock protects against activities from other Linux tasks,
* but not from changes by other I2C masters.
@@ -109,25 +113,63 @@ MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)");
((1 << AT24_SIZE_FLAGS | (_flags)) \
<< AT24_SIZE_BYTELEN | ilog2(_len))
+/*
+ * Both reads and writes fail if the previous write didn't complete yet. This
+ * macro loops a few times waiting at least long enough for one entire page
+ * write to work while making sure that at least one iteration is run before
+ * checking the break condition.
+ *
+ * It takes two parameters: a variable in which the future timeout in jiffies
+ * will be stored and a temporary variable holding the time of the last
+ * iteration of processing the request. Both should be unsigned integers
+ * holding at least 32 bits.
+ */
+#define loop_until_timeout(tout, op_time) \
+ for (tout = jiffies + msecs_to_jiffies(write_timeout), op_time = 0; \
+ op_time ? time_before(op_time, tout) : true; \
+ usleep_range(1000, 1500), op_time = jiffies)
+
static const struct i2c_device_id at24_ids[] = {
/* needs 8 addresses as A0-A2 are ignored */
- { "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
+ { "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
/* old variants can't be handled with this generic entry! */
- { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
- { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
+ { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
+ { "24cs01", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
+ { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
+ { "24cs02", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
+ { "24mac402", AT24_DEVICE_MAGIC(48 / 8,
+ AT24_FLAG_MAC | AT24_FLAG_READONLY) },
+ { "24mac602", AT24_DEVICE_MAGIC(64 / 8,
+ AT24_FLAG_MAC | AT24_FLAG_READONLY) },
/* spd is a 24c02 in memory DIMMs */
- { "spd", AT24_DEVICE_MAGIC(2048 / 8,
- AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
- { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
+ { "spd", AT24_DEVICE_MAGIC(2048 / 8,
+ AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
+ { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
+ { "24cs04", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
/* 24rf08 quirk is handled at i2c-core */
- { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
- { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
- { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
- { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
- { "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
- { "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
- { "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
- { "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
+ { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
+ { "24cs08", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
+ { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
+ { "24cs16", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
+ { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
+ { "24cs32", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_ADDR16 |
+ AT24_FLAG_SERIAL |
+ AT24_FLAG_READONLY) },
+ { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
+ { "24cs64", AT24_DEVICE_MAGIC(16,
+ AT24_FLAG_ADDR16 |
+ AT24_FLAG_SERIAL |
+ AT24_FLAG_READONLY) },
+ { "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
+ { "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
+ { "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
+ { "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
{ "at24", 0 },
{ /* END OF LIST */ }
};
@@ -145,9 +187,22 @@ MODULE_DEVICE_TABLE(acpi, at24_acpi_ids);
* This routine supports chips which consume multiple I2C addresses. It
* computes the addressing information to be used for a given r/w request.
* Assumes that sanity checks for offset happened at sysfs-layer.
+ *
+ * Slave address and byte offset derive from the offset. Always
+ * set the byte address; on a multi-master board, another master
+ * may have changed the chip's "current" address pointer.
+ *
+ * REVISIT some multi-address chips don't rollover page reads to
+ * the next slave address, so we may need to truncate the count.
+ * Those chips might need another quirk flag.
+ *
+ * If the real hardware used four adjacent 24c02 chips and that
+ * were misconfigured as one 24c08, that would be a similar effect:
+ * one "eeprom" file not four, but larger reads would fail when
+ * they crossed certain pages.
*/
static struct i2c_client *at24_translate_offset(struct at24_data *at24,
- unsigned *offset)
+ unsigned int *offset)
{
unsigned i;
@@ -162,123 +217,168 @@ static struct i2c_client *at24_translate_offset(struct at24_data *at24,
return at24->client[i];
}
-static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
- unsigned offset, size_t count)
+static ssize_t at24_eeprom_read_smbus(struct at24_data *at24, char *buf,
+ unsigned int offset, size_t count)
{
- struct i2c_msg msg[2];
- u8 msgbuf[2];
+ unsigned long timeout, read_time;
struct i2c_client *client;
+ int status;
+
+ client = at24_translate_offset(at24, &offset);
+
+ if (count > io_limit)
+ count = io_limit;
+
+ /* Smaller eeproms can work given some SMBus extension calls */
+ if (count > I2C_SMBUS_BLOCK_MAX)
+ count = I2C_SMBUS_BLOCK_MAX;
+
+ loop_until_timeout(timeout, read_time) {
+ status = i2c_smbus_read_i2c_block_data_or_emulated(client,
+ offset,
+ count, buf);
+
+ 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_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));
-
- /*
- * REVISIT some multi-address chips don't rollover page reads to
- * the next slave address, so we may need to truncate the count.
- * Those chips might need another quirk flag.
- *
- * If the real hardware used four adjacent 24c02 chips and that
- * were misconfigured as one 24c08, that would be a similar effect:
- * one "eeprom" file not four, but larger reads would fail when
- * they crossed certain pages.
- */
-
- /*
- * Slave address and byte offset derive from the offset. Always
- * set the byte address; on a multi-master board, another master
- * may have changed the chip's "current" address pointer.
- */
client = at24_translate_offset(at24, &offset);
if (count > io_limit)
count = io_limit;
- if (at24->use_smbus) {
- /* Smaller eeproms can work given some SMBus extension calls */
- if (count > I2C_SMBUS_BLOCK_MAX)
- count = I2C_SMBUS_BLOCK_MAX;
- } else {
- /*
- * 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;
- }
-
/*
- * Reads fail if the previous write didn't complete yet. We may
- * loop a few times until this one succeeds, waiting at least
- * long enough for one entire page write to work.
+ * 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.
*/
- timeout = jiffies + msecs_to_jiffies(write_timeout);
- do {
- read_time = jiffies;
- if (at24->use_smbus) {
- status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,
- count, buf);
- } else {
- status = i2c_transfer(client->adapter, msg, 2);
- if (status == 2)
- status = count;
- }
+ 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;
-
- usleep_range(1000, 1500);
- } while (time_before(read_time, timeout));
+ }
return -ETIMEDOUT;
}
-static int at24_read(void *priv, unsigned int off, void *val, size_t count)
+static ssize_t at24_eeprom_read_serial(struct at24_data *at24, char *buf,
+ unsigned int offset, size_t count)
{
- struct at24_data *at24 = priv;
- char *buf = val;
+ unsigned long timeout, read_time;
+ struct i2c_client *client;
+ struct i2c_msg msg[2];
+ u8 addrbuf[2];
+ int status;
- if (unlikely(!count))
- return count;
+ client = at24_translate_offset(at24, &offset);
+
+ memset(msg, 0, sizeof(msg));
+ msg[0].addr = client->addr;
+ msg[0].buf = addrbuf;
/*
- * Read data from chip, protecting against concurrent updates
- * from this host, but not from other I2C masters.
+ * The address pointer of the device is shared between the regular
+ * EEPROM array and the serial number block. The dummy write (part of
+ * the sequential read protocol) ensures the address pointer is reset
+ * to the desired position.
*/
- mutex_lock(&at24->lock);
+ if (at24->chip.flags & AT24_FLAG_ADDR16) {
+ /*
+ * For 16 bit address pointers, the word address must contain
+ * a '10' sequence in bits 11 and 10 regardless of the
+ * intended position of the address pointer.
+ */
+ addrbuf[0] = 0x08;
+ addrbuf[1] = offset;
+ msg[0].len = 2;
+ } else {
+ /*
+ * Otherwise the word address must begin with a '10' sequence,
+ * regardless of the intended address.
+ */
+ addrbuf[0] = 0x80 + offset;
+ msg[0].len = 1;
+ }
- while (count) {
- int status;
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = buf;
+ msg[1].len = count;
- status = at24_eeprom_read(at24, buf, off, count);
- if (status < 0) {
- mutex_unlock(&at24->lock);
- return status;
- }
- buf += status;
- off += status;
- count -= status;
+ loop_until_timeout(timeout, read_time) {
+ status = i2c_transfer(client->adapter, msg, 2);
+ if (status == 2)
+ return count;
}
- mutex_unlock(&at24->lock);
+ return -ETIMEDOUT;
+}
- return 0;
+static ssize_t at24_eeprom_read_mac(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];
+ u8 addrbuf[2];
+ int status;
+
+ client = at24_translate_offset(at24, &offset);
+
+ memset(msg, 0, sizeof(msg));
+ msg[0].addr = client->addr;
+ msg[0].buf = addrbuf;
+ addrbuf[0] = 0x90 + offset;
+ msg[0].len = 1;
+ 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)
+ return count;
+ }
+
+ return -ETIMEDOUT;
}
/*
@@ -286,21 +386,15 @@ static int at24_read(void *priv, unsigned int off, void *val, size_t count)
* chip is normally write protected. But there are plenty of product
* variants here, including OTP fuses and partial chip protect.
*
- * We only use page mode writes; the alternative is sloooow. This routine
- * writes at most one page.
+ * We only use page mode writes; the alternative is sloooow. These routines
+ * write at most one page.
*/
-static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
- unsigned offset, size_t count)
+
+static size_t at24_adjust_write_count(struct at24_data *at24,
+ unsigned int offset, size_t count)
{
- struct i2c_client *client;
- struct i2c_msg msg;
- ssize_t status = 0;
- unsigned long timeout, write_time;
unsigned next_page;
- /* Get corresponding I2C address and adjust offset */
- client = at24_translate_offset(at24, &offset);
-
/* write_max is at most a page */
if (count > at24->write_max)
count = at24->write_max;
@@ -310,62 +404,132 @@ static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
if (offset + count > next_page)
count = next_page - offset;
- /* If we'll use I2C calls for I/O, set up the message */
- if (!at24->use_smbus) {
- int i = 0;
+ return count;
+}
- msg.addr = client->addr;
- msg.flags = 0;
+static ssize_t at24_eeprom_write_smbus_block(struct at24_data *at24,
+ const char *buf,
+ unsigned int offset, size_t count)
+{
+ unsigned long timeout, write_time;
+ struct i2c_client *client;
+ ssize_t status = 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;
+ client = at24_translate_offset(at24, &offset);
+ count = at24_adjust_write_count(at24, offset, count);
+
+ loop_until_timeout(timeout, write_time) {
+ status = i2c_smbus_write_i2c_block_data(client,
+ offset, count, buf);
+ if (status == 0)
+ status = count;
+
+ dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
+ count, offset, status, jiffies);
- msg.buf[i++] = offset;
- memcpy(&msg.buf[i], buf, count);
- msg.len = i + count;
+ if (status == count)
+ return count;
}
- /*
- * Writes fail if the previous one didn't complete yet. We may
- * loop a few times until this one succeeds, waiting at least
- * long enough for one entire page write to work.
- */
- timeout = jiffies + msecs_to_jiffies(write_timeout);
- do {
- write_time = jiffies;
- if (at24->use_smbus_write) {
- switch (at24->use_smbus_write) {
- case I2C_SMBUS_I2C_BLOCK_DATA:
- status = i2c_smbus_write_i2c_block_data(client,
- offset, count, buf);
- break;
- case I2C_SMBUS_BYTE_DATA:
- status = i2c_smbus_write_byte_data(client,
- offset, buf[0]);
- break;
- }
-
- if (status == 0)
- status = count;
- } else {
- status = i2c_transfer(client->adapter, &msg, 1);
- if (status == 1)
- status = count;
- }
+ return -ETIMEDOUT;
+}
+
+static ssize_t at24_eeprom_write_smbus_byte(struct at24_data *at24,
+ const char *buf,
+ unsigned int offset, size_t count)
+{
+ unsigned long timeout, write_time;
+ struct i2c_client *client;
+ ssize_t status = 0;
+
+ client = at24_translate_offset(at24, &offset);
+
+ loop_until_timeout(timeout, write_time) {
+ status = i2c_smbus_write_byte_data(client, offset, buf[0]);
+ if (status == 0)
+ status = count;
+
dev_dbg(&client->dev, "write %zu@%d --> %zd (%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;
- usleep_range(1000, 1500);
- } while (time_before(write_time, timeout));
+ 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;
}
+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;
+
+ /*
+ * Read data from chip, protecting against concurrent updates
+ * from this host, but not from other I2C masters.
+ */
+ mutex_lock(&at24->lock);
+
+ while (count) {
+ int status;
+
+ status = at24->read_func(at24, 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;
@@ -383,7 +547,7 @@ static int at24_write(void *priv, unsigned int off, void *val, size_t count)
while (count) {
int status;
- status = at24_eeprom_write(at24, buf, off, count);
+ status = at24->write_func(at24, buf, off, count);
if (status < 0) {
mutex_unlock(&at24->lock);
return status;
@@ -400,7 +564,7 @@ static int at24_write(void *priv, unsigned int off, void *val, size_t count)
#ifdef CONFIG_OF
static void at24_get_ofdata(struct i2c_client *client,
- struct at24_platform_data *chip)
+ struct at24_platform_data *chip)
{
const __be32 *val;
struct device_node *node = client->dev.of_node;
@@ -415,7 +579,7 @@ static void at24_get_ofdata(struct i2c_client *client,
}
#else
static void at24_get_ofdata(struct i2c_client *client,
- struct at24_platform_data *chip)
+ struct at24_platform_data *chip)
{ }
#endif /* CONFIG_OF */
@@ -518,6 +682,30 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
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) {
+ 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) {
if (!use_smbus || use_smbus_write) {