[PATCH v2] at24: Fall back to byte or word reads if needed

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Increase the portability of the at24 driver by letting it read from
EEPROM chips connected to cheap SMBus controllers that support neither
raw I2C messages nor even I2C block reads. All SMBus controllers
should support either word reads or byte reads, so read support
becomes universal, much like with the legacy "eeprom" driver.

Obviously, this only works with EEPROM chips up to AT24C16, that use
8-bit offset addressing. 16-bit offset addressing is almost impossible
to support on SMBus controllers.

I did not add universal support for writes, as I had no immediate need
for this, but it could be added later if needed (with the same
performance issue as byte and word reads have, of course.)

Signed-off-by: Jean Delvare <khali@xxxxxxxxxxxx>
Cc: Wolfram Sang <w.sang@xxxxxxxxxxxxxx>
Cc: Konstantin Lazarev <klazarev@xxxxxxxxxxxxx>
---
Changes from V1:
 * Use switch/case instead of if/else.

 drivers/misc/eeprom/at24.c |   59 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 48 insertions(+), 11 deletions(-)

--- linux-2.6.33.orig/drivers/misc/eeprom/at24.c	2010-03-08 15:57:03.000000000 +0100
+++ linux-2.6.33/drivers/misc/eeprom/at24.c	2010-03-08 16:11:19.000000000 +0100
@@ -54,7 +54,7 @@
 struct at24_data {
 	struct at24_platform_data chip;
 	struct memory_accessor macc;
-	bool use_smbus;
+	int use_smbus;
 
 	/*
 	 * Lock protects against activities from other Linux tasks,
@@ -184,11 +184,19 @@ static ssize_t at24_eeprom_read(struct a
 	if (count > io_limit)
 		count = io_limit;
 
-	if (at24->use_smbus) {
+	switch (at24->use_smbus) {
+	case I2C_SMBUS_I2C_BLOCK_DATA:
 		/* Smaller eeproms can work given some SMBus extension calls */
 		if (count > I2C_SMBUS_BLOCK_MAX)
 			count = I2C_SMBUS_BLOCK_MAX;
-	} else {
+		break;
+	case I2C_SMBUS_WORD_DATA:
+		count = 2;
+		break;
+	case I2C_SMBUS_BYTE_DATA:
+		count = 1;
+		break;
+	default:
 		/*
 		 * When we have a better choice than SMBus calls, use a
 		 * combined I2C message. Write address; then read up to
@@ -219,10 +227,27 @@ static ssize_t at24_eeprom_read(struct a
 	timeout = jiffies + msecs_to_jiffies(write_timeout);
 	do {
 		read_time = jiffies;
-		if (at24->use_smbus) {
+		switch (at24->use_smbus) {
+		case I2C_SMBUS_I2C_BLOCK_DATA:
 			status = i2c_smbus_read_i2c_block_data(client, offset,
 					count, buf);
-		} else {
+			break;
+		case I2C_SMBUS_WORD_DATA:
+			status = i2c_smbus_read_word_data(client, offset);
+			if (status >= 0) {
+				buf[0] = status & 0xff;
+				buf[1] = status >> 8;
+				status = count;
+			}
+			break;
+		case I2C_SMBUS_BYTE_DATA:
+			status = i2c_smbus_read_byte_data(client, offset);
+			if (status >= 0) {
+				buf[0] = status;
+				status = count;
+			}
+			break;
+		default:
 			status = i2c_transfer(client->adapter, msg, 2);
 			if (status == 2)
 				status = count;
@@ -434,7 +459,7 @@ static int at24_probe(struct i2c_client
 {
 	struct at24_platform_data chip;
 	bool writable;
-	bool use_smbus = false;
+	int use_smbus = 0;
 	struct at24_data *at24;
 	int err;
 	unsigned i, num_addresses;
@@ -475,12 +500,19 @@ static int at24_probe(struct i2c_client
 			err = -EPFNOSUPPORT;
 			goto err_out;
 		}
-		if (!i2c_check_functionality(client->adapter,
+		if (i2c_check_functionality(client->adapter,
 				I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+			use_smbus = I2C_SMBUS_I2C_BLOCK_DATA;
+		} else if (i2c_check_functionality(client->adapter,
+				I2C_FUNC_SMBUS_READ_WORD_DATA)) {
+			use_smbus = I2C_SMBUS_WORD_DATA;
+		} else if (i2c_check_functionality(client->adapter,
+				I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+			use_smbus = I2C_SMBUS_BYTE_DATA;
+		} else {
 			err = -EPFNOSUPPORT;
 			goto err_out;
 		}
-		use_smbus = true;
 	}
 
 	if (chip.flags & AT24_FLAG_TAKE8ADDR)
@@ -565,11 +597,16 @@ static int at24_probe(struct i2c_client
 	dev_info(&client->dev, "%zu byte %s EEPROM %s\n",
 		at24->bin.size, client->name,
 		writable ? "(writable)" : "(read-only)");
+	if (use_smbus == I2C_SMBUS_WORD_DATA ||
+	    use_smbus == I2C_SMBUS_BYTE_DATA) {
+		dev_notice(&client->dev, "Falling back to %s reads, "
+			   "performance will suffer\n", use_smbus ==
+			   I2C_SMBUS_WORD_DATA ? "word" : "byte");
+	}
 	dev_dbg(&client->dev,
-		"page_size %d, num_addresses %d, write_max %d%s\n",
+		"page_size %d, num_addresses %d, write_max %d, use_smbus %d\n",
 		chip.page_size, num_addresses,
-		at24->write_max,
-		use_smbus ? ", use_smbus" : "");
+		at24->write_max, use_smbus);
 
 	/* export data to kernel code */
 	if (chip.setup)


-- 
Jean Delvare
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux