diff options
| -rw-r--r-- | drivers/misc/cros_ec.c | 270 | ||||
| -rw-r--r-- | 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 7e8c58f86..068373b94 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 = ¶ms.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@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 1b7c62090..1e4d8db96 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 |