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>
master
Simon Glass 10 years ago
parent 86bf601d04
commit b2a668b523
  1. 270
      drivers/misc/cros_ec.c
  2. 14
      include/cros_ec.h

@ -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@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

Loading…
Cancel
Save