I2C: Add 'transfer' to the Generic I2C Interface

This introduces transfer, which is a more primitive operation than read and
write. It handles i2c transactions by segment. See i2c.h for details.

Existing read and write operations assume all slave devices behave in a certain
way, which is not the case. For example, MAX98090 expects a read request in one
i2c frame while SLB9660 expects two separate frames. Thus, transfer operation
should replace these and slave drivers should use transfer directly or
indirectly (as done for MAX98090 in this patch). This way, the knowledge about a
slave device behavior will reside in each slave driver.

TEST=Booted Nyan. Verified MAX98090's hardware revision is printed on console
and Max98090 can beep (with a fix coming up for chrome-os-partner:26609). Built
Rambi.
BUG=chrome-os-partner:27097
BRANCH=none
Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
Change-Id: I07fe5b903aa8bfc7afa7db58df79615b2225fe72
Reviewed-on: https://chromium-review.googlesource.com/191190
diff --git a/src/drivers/bus/i2c/Makefile.inc b/src/drivers/bus/i2c/Makefile.inc
index 4090a9c..a3ca6a7 100644
--- a/src/drivers/bus/i2c/Makefile.inc
+++ b/src/drivers/bus/i2c/Makefile.inc
@@ -15,6 +15,7 @@
 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 ##
 
+depthcharge-y += i2c.c
 depthcharge-$(CONFIG_DRIVER_BUS_I2C_S3C24X0) += s3c24x0.c
 depthcharge-$(CONFIG_DRIVER_BUS_I2C_EXYNOS5_USI) += exynos5_usi.c
 depthcharge-$(CONFIG_DRIVER_BUS_I2C_TEGRA) += tegra.c
diff --git a/src/drivers/bus/i2c/i2c.c b/src/drivers/bus/i2c/i2c.c
new file mode 100644
index 0000000..eac5832
--- /dev/null
+++ b/src/drivers/bus/i2c/i2c.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 Google Inc.  All rights reserved.
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+#include <libpayload.h>
+#include "drivers/bus/i2c/i2c.h"
+
+int i2c_readb(I2cOps *ops, uint8_t chip, uint8_t reg, uint8_t *data)
+{
+	struct I2cSeg seg[2];
+
+	seg[0].read = 0;
+	seg[0].chip = chip;
+	seg[0].buf = &reg;
+	seg[0].len = 1;
+	seg[1].read = 1;
+	seg[1].chip = chip;
+	seg[1].buf = data;
+	seg[1].len = 1;
+
+	return ops->transfer(ops, seg, ARRAY_SIZE(seg));
+}
+
+int i2c_writeb(I2cOps *ops, uint8_t chip, uint8_t reg, uint8_t data)
+{
+	struct I2cSeg seg;
+	uint8_t buf[] = {reg, data};
+
+	seg.read = 0;
+	seg.chip = chip;
+	seg.buf = buf;
+	seg.len = ARRAY_SIZE(buf);
+
+	return ops->transfer(ops, &seg, 1);
+}
diff --git a/src/drivers/bus/i2c/i2c.h b/src/drivers/bus/i2c/i2c.h
index a47773d..10c3be5 100644
--- a/src/drivers/bus/i2c/i2c.h
+++ b/src/drivers/bus/i2c/i2c.h
@@ -20,14 +20,49 @@
 
 #include <stdint.h>
 
+/*
+ * transfer operation consumes I2cSeg struct one by one and finishes a
+ * transaction by a stop bit at the end.
+ *
+ *   frame = [segment] ... [segment][stop]
+ *   segment = [start][slave addr][r/w][ack][data][ack][data][ack] ...
+ *
+ * Two adjacent segments are connected by a repeated start bit.
+ */
+typedef struct I2cSeg
+{
+	int read;	// 0:write 1:read
+	uint8_t chip;	// slave address
+	uint8_t *buf;	// buffer for read or write
+	int len;	// buffer size
+} I2cSeg;
+
 typedef struct I2cOps
 {
+	/*
+	 * read and write will be deprecated. New i2c drivers should implement
+	 * transfer and new slave drivers should use transfer (or utility funcs
+	 * in i2c.c).
+	 */
 	int (*read)(struct I2cOps *me, uint8_t chip,
-		    uint32_t addr, int addr_len,
-		    uint8_t *data, int data_len);
+		    uint32_t addr, int addr_len, uint8_t *data, int data_len);
 	int (*write)(struct I2cOps *me, uint8_t chip,
-		     uint32_t addr, int addr_len,
-		     uint8_t *data, int data_len);
+		     uint32_t addr, int addr_len, uint8_t *data, int data_len);
+	int (*transfer)(struct I2cOps *me, I2cSeg *segments, int seg_count);
 } I2cOps;
 
+/**
+ * Read a byte by two segments in one frame
+ *
+ * [start][slave addr][w][register addr][start][slave addr][r][data][stop]
+ */
+int i2c_readb(I2cOps *ops, uint8_t chip, uint8_t reg, uint8_t *data);
+
+/**
+ * Write a byte by one segment in one frame.
+ *
+ * [start][slave addr][w][register addr][data][stop]
+ */
+int i2c_writeb(I2cOps *ops, uint8_t chip, uint8_t reg, uint8_t data);
+
 #endif /* __DRIVERS_BUS_I2C_I2C_H__ */
diff --git a/src/drivers/bus/i2c/tegra.c b/src/drivers/bus/i2c/tegra.c
index dd01fbc..e0e999e 100644
--- a/src/drivers/bus/i2c/tegra.c
+++ b/src/drivers/bus/i2c/tegra.c
@@ -116,41 +116,46 @@
 				   data, data_len);
 }
 
-static int i2c_readwrite(TegraI2c *bus, uint8_t chip, uint32_t addr,
-			 int alen, uint8_t *buf, int len, int read)
+static int i2c_transfer_segment(TegraI2c *bus, uint8_t chip,
+				int restart, int read, void *buf, int len)
 {
 	const uint32_t max_payload =
 		(IOHEADER_PAYLOADSIZE_MASK + 1) >> IOHEADER_PAYLOADSIZE_SHIFT;
-	uint8_t abuf[sizeof(addr)];
-
-	int i;
-	for (i = 0; i < alen; i++)
-		abuf[i] = addr >> ((alen - i - 1) * 8);
-
-	if (tegra_i2c_request(bus, chip, !read, 0, 0, abuf, alen))
-		return -1;
-
 	while (len) {
 		int todo = MIN(len, max_payload);
 		int cont = (todo < len);
-		if (tegra_i2c_request(bus, chip, cont, 0, read, buf, todo)) {
+		if (tegra_i2c_request(bus, chip,
+				      cont, restart, read, buf, todo))
 			// We should reset the controller here.
 			return -1;
-		}
 		len -= todo;
 		buf += todo;
 	}
 	return 0;
 }
 
-void i2c_init(TegraI2c *bus)
+static int i2c_readwrite(TegraI2c *bus, uint8_t chip, uint32_t addr,
+			 int alen, uint8_t *buf, int len, int read)
+{
+	uint8_t abuf[sizeof(addr)];
+
+	for (int i = 0; i < alen; i++)
+		abuf[i] = addr >> ((alen - i - 1) * 8);
+
+	if (tegra_i2c_request(bus, chip, !read, 0, 0, abuf, alen))
+		return -1;
+
+	return i2c_transfer_segment(bus, chip, 0, read, buf, len);
+}
+
+static void i2c_init(TegraI2c *bus)
 {
 	TegraI2cRegs *regs = bus->regs;
 	writel(I2C_CNFG_PACKET_MODE_EN, &regs->cnfg);
 	bus->initialized = 1;
 }
 
-int i2c_read(I2cOps *me, uint8_t chip, uint32_t addr,
+static int i2c_read(I2cOps *me, uint8_t chip, uint32_t addr,
 	     int addr_len, uint8_t *data, int data_len)
 {
 	TegraI2c *bus = container_of(me, TegraI2c, ops);
@@ -159,7 +164,7 @@
 	return i2c_readwrite(bus, chip, addr, addr_len, data, data_len, 1);
 }
 
-int i2c_write(I2cOps *me, uint8_t chip, uint32_t addr,
+static int i2c_write(I2cOps *me, uint8_t chip, uint32_t addr,
 	      int addr_len, uint8_t *data, int data_len)
 {
 	TegraI2c *bus = container_of(me, TegraI2c, ops);
@@ -168,11 +173,28 @@
 	return i2c_readwrite(bus, chip, addr, addr_len, data, data_len, 0);
 }
 
+static int i2c_transfer(I2cOps *me, I2cSeg *segments, int seg_count)
+{
+	I2cSeg *seg = segments;
+	TegraI2c *bus = container_of(me, TegraI2c, ops);
+
+	if (!bus->initialized)
+		i2c_init(bus);
+
+	for (int i = 0; i < seg_count; seg++, i++) {
+		if (i2c_transfer_segment(bus, seg->chip, i < seg_count - 1,
+				       seg->read, seg->buf, seg->len))
+			return -1;
+	}
+	return 0;
+}
+
 TegraI2c *new_tegra_i2c(void *regs, int controller_id)
 {
 	TegraI2c *bus = xzalloc(sizeof(*bus));
 	bus->ops.read = i2c_read;
 	bus->ops.write = i2c_write;
+	bus->ops.transfer = i2c_transfer;
 	bus->regs = regs;
 	bus->controller_id = controller_id;
 	return bus;
diff --git a/src/drivers/sound/max98090.c b/src/drivers/sound/max98090.c
index 9a07b92..de80dfc 100644
--- a/src/drivers/sound/max98090.c
+++ b/src/drivers/sound/max98090.c
@@ -16,12 +16,20 @@
 
 static int max98090_i2c_write(Max98090Codec *codec, uint8_t reg, uint8_t data)
 {
-	return codec->i2c->write(codec->i2c, codec->chip, reg, 1, &data, 1);
+	if (codec->i2c->transfer)
+		return i2c_writeb(codec->i2c, codec->chip, reg, data);
+	else
+		return codec->i2c->write(codec->i2c, codec->chip, reg, 1,
+					 &data, 1);
 }
 
 static int max98090_i2c_read(Max98090Codec *codec, uint8_t reg, uint8_t *data)
 {
-	return codec->i2c->read(codec->i2c, codec->chip, reg, 1, data, 1);
+	if (codec->i2c->transfer)
+		return i2c_readb(codec->i2c, codec->chip, reg, data);
+	else
+		return codec->i2c->read(codec->i2c, codec->chip, reg, 1,
+					data, 1);
 }
 
 static int max98090_update_bits(Max98090Codec *codec, uint8_t reg,