[U-Boot] [PATCH v2 20/31] cros_ec: Implement I2C pass-through

Simon Glass sjg at chromium.org
Thu Feb 27 21:26:14 CET 2014


The Chrome EC has a feature where you can access its I2C buses through a
pass-through arrangement. Add a command to support this, and export the
function for it also.

Reviewed-by: Vadim Bendebury <vbendeb at google.com>
Signed-off-by: Simon Glass <sjg at chromium.org>
---

Changes in v2: None

 drivers/misc/cros_ec.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++++-
 include/cros_ec.h      |  14 +++
 2 files changed, 282 insertions(+), 2 deletions(-)

diff --git a/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c
index 7e8c58f..068373b 100644
--- a/drivers/misc/cros_ec.c
+++ b/drivers/misc/cros_ec.c
@@ -1197,6 +1197,87 @@ int cros_ec_decode_ec_flash(const void *blob, struct fdt_cros_ec *config)
 	return 0;
 }
 
+int cros_ec_i2c_xfer(struct cros_ec_dev *dev, uchar chip, uint addr,
+		     int alen, uchar *buffer, int len, int is_read)
+{
+	union {
+		struct ec_params_i2c_passthru p;
+		uint8_t outbuf[EC_PROTO2_MAX_PARAM_SIZE];
+	} params;
+	union {
+		struct ec_response_i2c_passthru r;
+		uint8_t inbuf[EC_PROTO2_MAX_PARAM_SIZE];
+	} response;
+	struct ec_params_i2c_passthru *p = &params.p;
+	struct ec_response_i2c_passthru *r = &response.r;
+	struct ec_params_i2c_passthru_msg *msg = p->msg;
+	uint8_t *pdata;
+	int read_len, write_len;
+	int size;
+	int rv;
+
+	p->port = 0;
+
+	if (alen != 1) {
+		printf("Unsupported address length %d\n", alen);
+		return -1;
+	}
+	if (is_read) {
+		read_len = len;
+		write_len = alen;
+		p->num_msgs = 2;
+	} else {
+		read_len = 0;
+		write_len = alen + len;
+		p->num_msgs = 1;
+	}
+
+	size = sizeof(*p) + p->num_msgs * sizeof(*msg);
+	if (size + write_len > sizeof(params)) {
+		puts("Params too large for buffer\n");
+		return -1;
+	}
+	if (sizeof(*r) + read_len > sizeof(response)) {
+		puts("Read length too big for buffer\n");
+		return -1;
+	}
+
+	/* Create a message to write the register address and optional data */
+	pdata = (uint8_t *)p + size;
+	msg->addr_flags = chip;
+	msg->len = write_len;
+	pdata[0] = addr;
+	if (!is_read)
+		memcpy(pdata + 1, buffer, len);
+	msg++;
+
+	if (read_len) {
+		msg->addr_flags = chip | EC_I2C_FLAG_READ;
+		msg->len = read_len;
+	}
+
+	rv = ec_command(dev, EC_CMD_I2C_PASSTHRU, 0, p, size + write_len,
+			r, sizeof(*r) + read_len);
+	if (rv < 0)
+		return rv;
+
+	/* Parse response */
+	if (r->i2c_status & EC_I2C_STATUS_ERROR) {
+		printf("Transfer failed with status=0x%x\n", r->i2c_status);
+		return -1;
+	}
+
+	if (rv < sizeof(*r) + read_len) {
+		puts("Truncated read response\n");
+		return -1;
+	}
+
+	if (read_len)
+		memcpy(buffer, r->data, read_len);
+
+	return 0;
+}
+
 #ifdef CONFIG_CMD_CROS_EC
 
 /**
@@ -1252,6 +1333,187 @@ static int do_read_write(struct cros_ec_dev *dev, int is_write, int argc,
 	return 0;
 }
 
+/**
+ * get_alen() - Small parser helper function to get address length
+ *
+ * Returns the address length.
+ */
+static uint get_alen(char *arg)
+{
+	int	j;
+	int	alen;
+
+	alen = 1;
+	for (j = 0; j < 8; j++) {
+		if (arg[j] == '.') {
+			alen = arg[j+1] - '0';
+			break;
+		} else if (arg[j] == '\0') {
+			break;
+		}
+	}
+	return alen;
+}
+
+#define DISP_LINE_LEN	16
+
+/*
+ * TODO(sjg at chromium.org): This code copied almost verbatim from cmd_i2c.c
+ * so we can remove it later.
+ */
+static int cros_ec_i2c_md(struct cros_ec_dev *dev, int flag, int argc,
+			  char * const argv[])
+{
+	u_char	chip;
+	uint	addr, alen, length = 0x10;
+	int	j, nbytes, linebytes;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	if (1 || (flag & CMD_FLAG_REPEAT) == 0) {
+		/*
+		 * New command specified.
+		 */
+
+		/*
+		 * I2C chip address
+		 */
+		chip = simple_strtoul(argv[0], NULL, 16);
+
+		/*
+		 * I2C data address within the chip.  This can be 1 or
+		 * 2 bytes long.  Some day it might be 3 bytes long :-).
+		 */
+		addr = simple_strtoul(argv[1], NULL, 16);
+		alen = get_alen(argv[1]);
+		if (alen > 3)
+			return CMD_RET_USAGE;
+
+		/*
+		 * If another parameter, it is the length to display.
+		 * Length is the number of objects, not number of bytes.
+		 */
+		if (argc > 2)
+			length = simple_strtoul(argv[2], NULL, 16);
+	}
+
+	/*
+	 * Print the lines.
+	 *
+	 * We buffer all read data, so we can make sure data is read only
+	 * once.
+	 */
+	nbytes = length;
+	do {
+		unsigned char	linebuf[DISP_LINE_LEN];
+		unsigned char	*cp;
+
+		linebytes = (nbytes > DISP_LINE_LEN) ? DISP_LINE_LEN : nbytes;
+
+		if (cros_ec_i2c_xfer(dev, chip, addr, alen, linebuf, linebytes,
+				     1))
+			puts("Error reading the chip.\n");
+		else {
+			printf("%04x:", addr);
+			cp = linebuf;
+			for (j = 0; j < linebytes; j++) {
+				printf(" %02x", *cp++);
+				addr++;
+			}
+			puts("    ");
+			cp = linebuf;
+			for (j = 0; j < linebytes; j++) {
+				if ((*cp < 0x20) || (*cp > 0x7e))
+					puts(".");
+				else
+					printf("%c", *cp);
+				cp++;
+			}
+			putc('\n');
+		}
+		nbytes -= linebytes;
+	} while (nbytes > 0);
+
+	return 0;
+}
+
+static int cros_ec_i2c_mw(struct cros_ec_dev *dev, int flag, int argc,
+			  char * const argv[])
+{
+	uchar	chip;
+	ulong	addr;
+	uint	alen;
+	uchar	byte;
+	int	count;
+
+	if ((argc < 3) || (argc > 4))
+		return CMD_RET_USAGE;
+
+	/*
+	 * Chip is always specified.
+	 */
+	chip = simple_strtoul(argv[0], NULL, 16);
+
+	/*
+	 * Address is always specified.
+	 */
+	addr = simple_strtoul(argv[1], NULL, 16);
+	alen = get_alen(argv[1]);
+	if (alen > 3)
+		return CMD_RET_USAGE;
+
+	/*
+	 * Value to write is always specified.
+	 */
+	byte = simple_strtoul(argv[2], NULL, 16);
+
+	/*
+	 * Optional count
+	 */
+	if (argc == 4)
+		count = simple_strtoul(argv[3], NULL, 16);
+	else
+		count = 1;
+
+	while (count-- > 0) {
+		if (cros_ec_i2c_xfer(dev, chip, addr++, alen, &byte, 1, 0))
+			puts("Error writing the chip.\n");
+		/*
+		 * Wait for the write to complete.  The write can take
+		 * up to 10mSec (we allow a little more time).
+		 */
+/*
+ * No write delay with FRAM devices.
+ */
+#if !defined(CONFIG_SYS_I2C_FRAM)
+		udelay(11000);
+#endif
+	}
+
+	return 0;
+}
+
+/* Temporary code until we have driver model and can use the i2c command */
+static int cros_ec_i2c_passthrough(struct cros_ec_dev *dev, int flag,
+				   int argc, char * const argv[])
+{
+	const char *cmd;
+
+	if (argc < 1)
+		return CMD_RET_USAGE;
+	cmd = *argv++;
+	argc--;
+	if (0 == strcmp("md", cmd))
+		cros_ec_i2c_md(dev, flag, argc, argv);
+	else if (0 == strcmp("mw", cmd))
+		cros_ec_i2c_mw(dev, flag, argc, argv);
+	else
+		return CMD_RET_USAGE;
+
+	return 0;
+}
+
 static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 {
 	struct cros_ec_dev *dev = last_dev;
@@ -1495,6 +1757,8 @@ static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 			debug("%s: Could not access LDO%d\n", __func__, index);
 			return ret;
 		}
+	} else if (0 == strcmp("i2c", cmd)) {
+		ret = cros_ec_i2c_passthrough(dev, flag, argc - 2, argv + 2);
 	} else {
 		return CMD_RET_USAGE;
 	}
@@ -1508,7 +1772,7 @@ static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 }
 
 U_BOOT_CMD(
-	crosec,	5,	1,	do_cros_ec,
+	crosec,	6,	1,	do_cros_ec,
 	"CROS-EC utility command",
 	"init                Re-init CROS-EC (done on startup automatically)\n"
 	"crosec id                  Read CROS-EC ID\n"
@@ -1525,6 +1789,8 @@ U_BOOT_CMD(
 	"crosec vbnvcontext [hexstring]        Read [write] VbNvContext from EC\n"
 	"crosec ldo <idx> [<state>] Switch/Read LDO state\n"
 	"crosec test                run tests on cros_ec\n"
-	"crosec version             Read CROS-EC version"
+	"crosec version             Read CROS-EC version\n"
+	"crosec i2c md chip address[.0, .1, .2] [# of objects] - read from I2C passthru\n"
+	"crosec i2c mw chip address[.0, .1, .2] value [count] - write to I2C passthru (fill)"
 );
 #endif
diff --git a/include/cros_ec.h b/include/cros_ec.h
index 1b7c620..1e4d8db 100644
--- a/include/cros_ec.h
+++ b/include/cros_ec.h
@@ -501,4 +501,18 @@ int cros_ec_decode_ec_flash(const void *blob, struct fdt_cros_ec *config);
  */
 void cros_ec_check_keyboard(struct cros_ec_dev *dev);
 
+/*
+ * Tunnel an I2C transfer to the EC
+ *
+ * @param dev		CROS-EC device
+ * @param chip		Chip address (7-bit I2C address)
+ * @param addr		Register address to read/write
+ * @param alen		Length of register address in bytes
+ * @param buffer	Buffer containing data to read/write
+ * @param len		Length of buffer
+ * @param is_read	1 if this is a read, 0 if this is a write
+ */
+int cros_ec_i2c_xfer(struct cros_ec_dev *dev, uchar chip, uint addr,
+		     int alen, uchar *buffer, int len, int is_read);
+
 #endif
-- 
1.9.0.279.gdc9e3eb



More information about the U-Boot mailing list