From cc456bd7df06225819258dec9d4a5047e8da4952 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 3 Aug 2015 08:19:23 -0600 Subject: dm: cros_ec: Convert the I2C tunnel code to use driver model The Chrome OS EC supports tunnelling through to an I2C bus on the EC. This currently uses a copy of the I2C command code and a special 'crosec' sub-command. With driver model we can define an I2C bus which tunnels through to the EC, and use the normal 'i2c' command to access it. This simplifies the code and removes some duplication. Add an I2C driver which tunnels through to the EC. Adjust the EC code to support binding child devices so that it can be set up. Adjust the existing I2C xfer function to fit driver model better. For now the old code remains to allow things to still work. It will be removed in a later patch once the new flow is fully enabled. Signed-off-by: Simon Glass --- drivers/misc/cros_ec.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 5 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c index 4b6ac6a6c01..ae525616095 100644 --- a/drivers/misc/cros_ec.c +++ b/drivers/misc/cros_ec.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifdef DEBUG_TRACE @@ -1053,8 +1054,8 @@ int cros_ec_decode_ec_flash(const void *blob, int node, 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) +int cros_ec_i2c_xfer_old(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; @@ -1134,6 +1135,81 @@ int cros_ec_i2c_xfer(struct cros_ec_dev *dev, uchar chip, uint addr, return 0; } +int cros_ec_i2c_tunnel(struct udevice *dev, struct i2c_msg *in, int nmsgs) +{ + struct cros_ec_dev *cdev = dev_get_uclass_priv(dev); + 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; + uint8_t *pdata, *read_ptr = NULL; + int read_len; + int size; + int rv; + int i; + + p->port = 0; + + p->num_msgs = nmsgs; + size = sizeof(*p) + p->num_msgs * sizeof(*msg); + + /* Create a message to write the register address and optional data */ + pdata = (uint8_t *)p + size; + + read_len = 0; + for (i = 0, msg = p->msg; i < nmsgs; i++, msg++, in++) { + bool is_read = in->flags & I2C_M_RD; + + msg->addr_flags = in->addr; + msg->len = in->len; + if (is_read) { + msg->addr_flags |= EC_I2C_FLAG_READ; + read_len += in->len; + read_ptr = in->buf; + if (sizeof(*r) + read_len > sizeof(response)) { + puts("Read length too big for buffer\n"); + return -1; + } + } else { + if (pdata - (uint8_t *)p + in->len > sizeof(params)) { + puts("Params too large for buffer\n"); + return -1; + } + memcpy(pdata, in->buf, in->len); + pdata += in->len; + } + } + + rv = ec_command(cdev, EC_CMD_I2C_PASSTHRU, 0, p, pdata - (uint8_t *)p, + 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; + } + + /* We only support a single read message for each transfer */ + if (read_len) + memcpy(read_ptr, r->data, read_len); + + return 0; +} + #ifdef CONFIG_CMD_CROS_EC /** @@ -1267,8 +1343,8 @@ static int cros_ec_i2c_md(struct cros_ec_dev *dev, int flag, int argc, linebytes = (nbytes > DISP_LINE_LEN) ? DISP_LINE_LEN : nbytes; - if (cros_ec_i2c_xfer(dev, chip, addr, alen, linebuf, linebytes, - 1)) + if (cros_ec_i2c_xfer_old(dev, chip, addr, alen, linebuf, + linebytes, 1)) puts("Error reading the chip.\n"); else { printf("%04x:", addr); @@ -1333,7 +1409,7 @@ static int cros_ec_i2c_mw(struct cros_ec_dev *dev, int flag, int argc, count = 1; while (count-- > 0) { - if (cros_ec_i2c_xfer(dev, chip, addr++, alen, &byte, 1, 0)) + if (cros_ec_i2c_xfer_old(dev, chip, addr++, alen, &byte, 1, 0)) puts("Error writing the chip.\n"); /* * Wait for the write to complete. The write can take @@ -1633,6 +1709,12 @@ static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return ret; } +int cros_ec_post_bind(struct udevice *dev) +{ + /* Scan for available EC devices (e.g. I2C tunnel) */ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); +} + U_BOOT_CMD( crosec, 6, 1, do_cros_ec, "CROS-EC utility command", @@ -1661,4 +1743,5 @@ UCLASS_DRIVER(cros_ec) = { .id = UCLASS_CROS_EC, .name = "cros_ec", .per_device_auto_alloc_size = sizeof(struct cros_ec_dev), + .post_bind = cros_ec_post_bind, }; -- cgit v1.3.1 From f48eaf01b2f7212987166aae970b895c7e215466 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 3 Aug 2015 08:19:24 -0600 Subject: cros_ec: Support the LDO access method used by spring Add a driver to support the special LDO access used by spring. This is a custom method in the cros_ec protocol - it does not use an I2C pass-through. There are two implementation choices: 1. Write a special LDO driver which can talk across the EC. Duplicate all the logic from TPS65090 for retrying when the LDO fails to come up. 2. Write a special I2C bus driver which pretends to be a TPS65090 and transfers reads and writes using the LDO message. Either is distasteful. The latter method is chosen since it results in less code duplication and a fairly simple (30-line) implementation of the core logic. The crosec 'ldo' subcommand could be removed (since i2c md/mw will work instead) but is retained as a convenience. Signed-off-by: Simon Glass --- drivers/i2c/Kconfig | 13 ++++++++ drivers/i2c/Makefile | 1 + drivers/i2c/cros_ec_ldo.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/cros_ec.c | 21 +++++++------ include/cros_ec.h | 4 +-- 5 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 drivers/i2c/cros_ec_ldo.c (limited to 'drivers/misc') diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index e861b536863..9a62ddd9ca2 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -29,6 +29,19 @@ config I2C_CROS_EC_TUNNEL I2C or LPC). Some Chromebooks use this when the hardware design does not allow direct access to the main PMIC from the AP. +config I2C_CROS_EC_LDO + bool "Provide access to LDOs on the Chrome OS EC" + depends on CROS_EC + ---help--- + On many Chromebooks the main PMIC is inaccessible to the AP. This is + often dealt with by using an I2C pass-through interface provided by + the EC. On some unfortunate models (e.g. Spring) the pass-through + is not available, and an LDO message is available instead. This + option enables a driver which provides very basic access to those + regulators, via the EC. We implement this as an I2C bus which + emulates just the TPS65090 messages we know about. This is done to + avoid duplicating the logic in the TPS65090 regulator driver for + enabling/disabling an LDO. config DM_I2C_GPIO bool "Enable Driver Model for software emulated I2C bus driver" diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 7f01fce2e74..9b45248e2e2 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_DM_I2C) += i2c-uclass.o obj-$(CONFIG_DM_I2C_COMPAT) += i2c-uclass-compat.o obj-$(CONFIG_DM_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o +obj-$(CONFIG_I2C_CROS_EC_LDO) += cros_ec_ldo.o obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.o obj-$(CONFIG_I2C_MV) += mv_i2c.o diff --git a/drivers/i2c/cros_ec_ldo.c b/drivers/i2c/cros_ec_ldo.c new file mode 100644 index 00000000000..b817c61f1c5 --- /dev/null +++ b/drivers/i2c/cros_ec_ldo.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015 Google, Inc + * Written by Simon Glass + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include + +static int cros_ec_ldo_set_bus_speed(struct udevice *dev, unsigned int speed) +{ + return 0; +} + +static int cros_ec_ldo_xfer(struct udevice *dev, struct i2c_msg *msg, + int nmsgs) +{ + bool is_read = nmsgs > 1; + int fet_id, ret; + + /* + * Look for reads and writes of the LDO registers. In either case the + * first message is a write with the register number as the first byte. + */ + if (!nmsgs || !msg->len || (msg->flags & I2C_M_RD)) { + debug("%s: Invalid message\n", __func__); + goto err; + } + + fet_id = msg->buf[0] - REG_FET_BASE; + if (fet_id < 1 || fet_id > MAX_FET_NUM) { + debug("%s: Invalid FET %d\n", __func__, fet_id); + goto err; + } + + if (is_read) { + uint8_t state; + + ret = cros_ec_get_ldo(dev->parent, fet_id, &state); + if (!ret) + msg[1].buf[0] = state ? + FET_CTRL_ENFET | FET_CTRL_PGFET : 0; + } else { + bool on = msg->buf[1] & FET_CTRL_ENFET; + + ret = cros_ec_set_ldo(dev->parent, fet_id, on); + } + + return ret; + +err: + /* Indicate that the message is unimplemented */ + return -ENOSYS; +} + +static const struct dm_i2c_ops cros_ec_i2c_ops = { + .xfer = cros_ec_ldo_xfer, + .set_bus_speed = cros_ec_ldo_set_bus_speed, +}; + +static const struct udevice_id cros_ec_i2c_ids[] = { + { .compatible = "google,cros-ec-ldo-tunnel" }, + { } +}; + +U_BOOT_DRIVER(cros_ec_ldo) = { + .name = "cros_ec_ldo_tunnel", + .id = UCLASS_I2C, + .of_match = cros_ec_i2c_ids, + .per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), + .ops = &cros_ec_i2c_ops, +}; diff --git a/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c index ae525616095..6027177d5d2 100644 --- a/drivers/misc/cros_ec.c +++ b/drivers/misc/cros_ec.c @@ -931,31 +931,32 @@ int cros_ec_write_vbnvcontext(struct cros_ec_dev *dev, const uint8_t *block) return 0; } -int cros_ec_set_ldo(struct cros_ec_dev *dev, uint8_t index, uint8_t state) +int cros_ec_set_ldo(struct udevice *dev, uint8_t index, uint8_t state) { + struct cros_ec_dev *cdev = dev_get_uclass_priv(dev); struct ec_params_ldo_set params; params.index = index; params.state = state; - if (ec_command_inptr(dev, EC_CMD_LDO_SET, 0, - ¶ms, sizeof(params), - NULL, 0)) + if (ec_command_inptr(cdev, EC_CMD_LDO_SET, 0, ¶ms, sizeof(params), + NULL, 0)) return -1; return 0; } -int cros_ec_get_ldo(struct cros_ec_dev *dev, uint8_t index, uint8_t *state) +int cros_ec_get_ldo(struct udevice *dev, uint8_t index, uint8_t *state) { + struct cros_ec_dev *cdev = dev_get_uclass_priv(dev); struct ec_params_ldo_get params; struct ec_response_ldo_get *resp; params.index = index; - if (ec_command_inptr(dev, EC_CMD_LDO_GET, 0, - ¶ms, sizeof(params), - (uint8_t **)&resp, sizeof(*resp)) != sizeof(*resp)) + if (ec_command_inptr(cdev, EC_CMD_LDO_GET, 0, ¶ms, sizeof(params), + (uint8_t **)&resp, sizeof(*resp)) != + sizeof(*resp)) return -1; *state = resp->state; @@ -1681,9 +1682,9 @@ static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) state = simple_strtoul(argv[3], &endp, 10); if (*argv[3] == 0 || *endp != 0) return CMD_RET_USAGE; - ret = cros_ec_set_ldo(dev, index, state); + ret = cros_ec_set_ldo(udev, index, state); } else { - ret = cros_ec_get_ldo(dev, index, &state); + ret = cros_ec_get_ldo(udev, index, &state); if (!ret) { printf("LDO%d: %s\n", index, state == EC_LDO_STATE_ON ? diff --git a/include/cros_ec.h b/include/cros_ec.h index 41951c39b65..0ad9d81243e 100644 --- a/include/cros_ec.h +++ b/include/cros_ec.h @@ -350,7 +350,7 @@ int cros_ec_read_build_info(struct cros_ec_dev *dev, char **strp); * @param state new state of the LDO/FET : EC_LDO_STATE_ON|OFF * @return 0 if ok, -1 on error */ -int cros_ec_set_ldo(struct cros_ec_dev *dev, uint8_t index, uint8_t state); +int cros_ec_set_ldo(struct udevice *dev, uint8_t index, uint8_t state); /** * Read back a LDO / FET current state. @@ -360,7 +360,7 @@ int cros_ec_set_ldo(struct cros_ec_dev *dev, uint8_t index, uint8_t state); * @param state current state of the LDO/FET : EC_LDO_STATE_ON|OFF * @return 0 if ok, -1 on error */ -int cros_ec_get_ldo(struct cros_ec_dev *dev, uint8_t index, uint8_t *state); +int cros_ec_get_ldo(struct udevice *dev, uint8_t index, uint8_t *state); /** * Get access to the error reported when cros_ec_board_init() was called -- cgit v1.3.1 From 24696e2f214b3a14a7fbaae032800fb8f8fc848b Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 3 Aug 2015 08:19:33 -0600 Subject: cros_ec: Remove the old tunnel code This is not needed with driver mode. Remove it. Signed-off-by: Simon Glass --- drivers/misc/cros_ec.c | 268 +------------------------------------------------ include/cros_ec.h | 14 --- 2 files changed, 1 insertion(+), 281 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c index 6027177d5d2..ba36795c650 100644 --- a/drivers/misc/cros_ec.c +++ b/drivers/misc/cros_ec.c @@ -1055,87 +1055,6 @@ int cros_ec_decode_ec_flash(const void *blob, int node, return 0; } -int cros_ec_i2c_xfer_old(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; -} - int cros_ec_i2c_tunnel(struct udevice *dev, struct i2c_msg *in, int nmsgs) { struct cros_ec_dev *cdev = dev_get_uclass_priv(dev); @@ -1266,187 +1185,6 @@ 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_old(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_old(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; @@ -1696,8 +1434,6 @@ 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; } @@ -1734,9 +1470,7 @@ U_BOOT_CMD( "crosec vbnvcontext [hexstring] Read [write] VbNvContext from EC\n" "crosec ldo [] Switch/Read LDO state\n" "crosec test run tests on cros_ec\n" - "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)" + "crosec version Read CROS-EC version" ); #endif diff --git a/include/cros_ec.h b/include/cros_ec.h index 0ad9d81243e..b9269341c39 100644 --- a/include/cros_ec.h +++ b/include/cros_ec.h @@ -400,18 +400,4 @@ struct i2c_msg; */ int cros_ec_i2c_tunnel(struct udevice *dev, struct i2c_msg *msg, int nmsgs); -/* - * 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_old(struct cros_ec_dev *dev, uchar chip, uint addr, - int alen, uchar *buffer, int len, int is_read); - #endif -- cgit v1.3.1