mirror of
				https://github.com/linux-sunxi/u-boot-sunxi.git
				synced 2024-02-12 11:16:03 +08:00 
			
		
		
		
	cros_ec: Implement I2C pass-through
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@google.com> Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
		@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user