]> git.kernelconcepts.de Git - karo-tx-uboot.git/blobdiff - drivers/misc/cros_ec.c
sandbox: cros_ec: Support EC_CMD_ENTERING_MODE emulation
[karo-tx-uboot.git] / drivers / misc / cros_ec.c
index 7e8c58f86ae610fa64b62fae79c0392d39cd4b9f..4b6ac6a6c01f75d97db362c0a169654c7377dd35 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <common.h>
 #include <command.h>
+#include <dm.h>
 #include <i2c.h>
 #include <cros_ec.h>
 #include <fdtdec.h>
@@ -24,6 +25,8 @@
 #include <asm/errno.h>
 #include <asm/io.h>
 #include <asm-generic/gpio.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
 
 #ifdef DEBUG_TRACE
 #define debug_trace(fmt, b...) debug(fmt, #b)
@@ -38,8 +41,6 @@ enum {
        CROS_EC_CMD_HASH_TIMEOUT_MS = 2000,
 };
 
-static struct cros_ec_dev static_dev, *last_dev;
-
 DECLARE_GLOBAL_DATA_PTR;
 
 /* Note: depends on enum ec_current_image */
@@ -149,7 +150,9 @@ static int prepare_proto3_response_buffer(struct cros_ec_dev *dev, int din_len)
  * @param dev          CROS-EC device
  * @param dinp          Returns pointer to response data
  * @param din_len       Maximum size of response in bytes
- * @return number of bytes of response data, or <0 if error
+ * @return number of bytes of response data, or <0 if error. Note that error
+ * codes can be from errno.h or -ve EC_RES_INVALID_CHECKSUM values (and they
+ * overlap!)
  */
 static int handle_proto3_response(struct cros_ec_dev *dev,
                                  uint8_t **dinp, int din_len)
@@ -204,6 +207,7 @@ static int send_command_proto3(struct cros_ec_dev *dev,
                               const void *dout, int dout_len,
                               uint8_t **dinp, int din_len)
 {
+       struct dm_cros_ec_ops *ops;
        int out_bytes, in_bytes;
        int rv;
 
@@ -218,23 +222,8 @@ static int send_command_proto3(struct cros_ec_dev *dev,
        if (in_bytes < 0)
                return in_bytes;
 
-       switch (dev->interface) {
-#ifdef CONFIG_CROS_EC_SPI
-       case CROS_EC_IF_SPI:
-               rv = cros_ec_spi_packet(dev, out_bytes, in_bytes);
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_SANDBOX
-       case CROS_EC_IF_SANDBOX:
-               rv = cros_ec_sandbox_packet(dev, out_bytes, in_bytes);
-               break;
-#endif
-       case CROS_EC_IF_NONE:
-       /* TODO: support protocol 3 for LPC, I2C; for now fall through */
-       default:
-               debug("%s: Unsupported interface\n", __func__);
-               rv = -1;
-       }
+       ops = dm_cros_ec_get_ops(dev->dev);
+       rv = ops->packet ? ops->packet(dev->dev, out_bytes, in_bytes) : -ENOSYS;
        if (rv < 0)
                return rv;
 
@@ -246,6 +235,7 @@ static int send_command(struct cros_ec_dev *dev, uint8_t cmd, int cmd_version,
                        const void *dout, int dout_len,
                        uint8_t **dinp, int din_len)
 {
+       struct dm_cros_ec_ops *ops;
        int ret = -1;
 
        /* Handle protocol version 3 support */
@@ -254,32 +244,9 @@ static int send_command(struct cros_ec_dev *dev, uint8_t cmd, int cmd_version,
                                           dout, dout_len, dinp, din_len);
        }
 
-       switch (dev->interface) {
-#ifdef CONFIG_CROS_EC_SPI
-       case CROS_EC_IF_SPI:
-               ret = cros_ec_spi_command(dev, cmd, cmd_version,
-                                       (const uint8_t *)dout, dout_len,
-                                       dinp, din_len);
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_I2C
-       case CROS_EC_IF_I2C:
-               ret = cros_ec_i2c_command(dev, cmd, cmd_version,
-                                       (const uint8_t *)dout, dout_len,
-                                       dinp, din_len);
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_LPC
-       case CROS_EC_IF_LPC:
-               ret = cros_ec_lpc_command(dev, cmd, cmd_version,
-                                       (const uint8_t *)dout, dout_len,
-                                       dinp, din_len);
-               break;
-#endif
-       case CROS_EC_IF_NONE:
-       default:
-               ret = -1;
-       }
+       ops = dm_cros_ec_get_ops(dev->dev);
+       ret = ops->command(dev->dev, cmd, cmd_version,
+                          (const uint8_t *)dout, dout_len, dinp, din_len);
 
        return ret;
 }
@@ -298,7 +265,7 @@ static int send_command(struct cros_ec_dev *dev, uint8_t cmd, int cmd_version,
  *                     If not NULL, it will be updated to point to the data
  *                     and will always be double word aligned (64-bits)
  * @param din_len       Maximum size of response in bytes
- * @return number of bytes in response, or -1 on error
+ * @return number of bytes in response, or -ve on error
  */
 static int ec_command_inptr(struct cros_ec_dev *dev, uint8_t cmd,
                int cmd_version, const void *dout, int dout_len, uint8_t **dinp,
@@ -365,7 +332,7 @@ static int ec_command_inptr(struct cros_ec_dev *dev, uint8_t cmd,
  *                     It not NULL, it is a place for ec_command() to copy the
  *      data to.
  * @param din_len       Maximum size of response in bytes
- * @return number of bytes in response, or -1 on error
+ * @return number of bytes in response, or -ve on error
  */
 static int ec_command(struct cros_ec_dev *dev, uint8_t cmd, int cmd_version,
                      const void *dout, int dout_len,
@@ -584,10 +551,10 @@ int cros_ec_reboot(struct cros_ec_dev *dev, enum ec_reboot_cmd cmd,
 int cros_ec_interrupt_pending(struct cros_ec_dev *dev)
 {
        /* no interrupt support : always poll */
-       if (!fdt_gpio_isvalid(&dev->ec_int))
+       if (!dm_gpio_is_valid(&dev->ec_int))
                return -ENOENT;
 
-       return !gpio_get_value(dev->ec_int.gpio);
+       return dm_gpio_get_value(&dev->ec_int);
 }
 
 int cros_ec_info(struct cros_ec_dev *dev, struct ec_response_mkbp_info *info)
@@ -657,11 +624,15 @@ static int cros_ec_check_version(struct cros_ec_dev *dev)
        struct ec_params_hello req;
        struct ec_response_hello *resp;
 
-#ifdef CONFIG_CROS_EC_LPC
-       /* LPC has its own way of doing this */
-       if (dev->interface == CROS_EC_IF_LPC)
-               return cros_ec_lpc_check_version(dev);
-#endif
+       struct dm_cros_ec_ops *ops;
+       int ret;
+
+       ops = dm_cros_ec_get_ops(dev->dev);
+       if (ops->check_version) {
+               ret = ops->check_version(dev->dev);
+               if (ret)
+                       return ret;
+       }
 
        /*
         * TODO(sjg@chromium.org).
@@ -679,6 +650,7 @@ static int cros_ec_check_version(struct cros_ec_dev *dev)
 
        /* Try sending a version 3 packet */
        dev->protocol_version = 3;
+       req.in_data = 0;
        if (ec_command_inptr(dev, EC_CMD_HELLO, 0, &req, sizeof(req),
                             (uint8_t **)&resp, sizeof(*resp)) > 0) {
                return 0;
@@ -990,143 +962,32 @@ int cros_ec_get_ldo(struct cros_ec_dev *dev, uint8_t index, uint8_t *state)
        return 0;
 }
 
-/**
- * Decode EC interface details from the device tree and allocate a suitable
- * device.
- *
- * @param blob         Device tree blob
- * @param node         Node to decode from
- * @param devp         Returns a pointer to the new allocated device
- * @return 0 if ok, -1 on error
- */
-static int cros_ec_decode_fdt(const void *blob, int node,
-               struct cros_ec_dev **devp)
-{
-       enum fdt_compat_id compat;
-       struct cros_ec_dev *dev;
-       int parent;
-
-       /* See what type of parent we are inside (this is expensive) */
-       parent = fdt_parent_offset(blob, node);
-       if (parent < 0) {
-               debug("%s: Cannot find node parent\n", __func__);
-               return -1;
-       }
-
-       dev = &static_dev;
-       dev->node = node;
-       dev->parent_node = parent;
-
-       compat = fdtdec_lookup(blob, parent);
-       switch (compat) {
-#ifdef CONFIG_CROS_EC_SPI
-       case COMPAT_SAMSUNG_EXYNOS_SPI:
-               dev->interface = CROS_EC_IF_SPI;
-               if (cros_ec_spi_decode_fdt(dev, blob))
-                       return -1;
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_I2C
-       case COMPAT_SAMSUNG_S3C2440_I2C:
-               dev->interface = CROS_EC_IF_I2C;
-               if (cros_ec_i2c_decode_fdt(dev, blob))
-                       return -1;
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_LPC
-       case COMPAT_INTEL_LPC:
-               dev->interface = CROS_EC_IF_LPC;
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_SANDBOX
-       case COMPAT_SANDBOX_HOST_EMULATION:
-               dev->interface = CROS_EC_IF_SANDBOX;
-               break;
-#endif
-       default:
-               debug("%s: Unknown compat id %d\n", __func__, compat);
-               return -1;
-       }
-
-       fdtdec_decode_gpio(blob, node, "ec-interrupt", &dev->ec_int);
-       dev->optimise_flash_write = fdtdec_get_bool(blob, node,
-                                                   "optimise-flash-write");
-       *devp = dev;
-
-       return 0;
-}
-
-int cros_ec_init(const void *blob, struct cros_ec_dev **cros_ecp)
+int cros_ec_register(struct udevice *dev)
 {
+       struct cros_ec_dev *cdev = dev_get_uclass_priv(dev);
+       const void *blob = gd->fdt_blob;
+       int node = dev->of_offset;
        char id[MSG_BYTES];
-       struct cros_ec_dev *dev;
-       int node = 0;
-
-       *cros_ecp = NULL;
-       do {
-               node = fdtdec_next_compatible(blob, node,
-                                             COMPAT_GOOGLE_CROS_EC);
-               if (node < 0) {
-                       debug("%s: Node not found\n", __func__);
-                       return 0;
-               }
-       } while (!fdtdec_get_is_enabled(blob, node));
-
-       if (cros_ec_decode_fdt(blob, node, &dev)) {
-               debug("%s: Failed to decode device.\n", __func__);
-               return -CROS_EC_ERR_FDT_DECODE;
-       }
-
-       switch (dev->interface) {
-#ifdef CONFIG_CROS_EC_SPI
-       case CROS_EC_IF_SPI:
-               if (cros_ec_spi_init(dev, blob)) {
-                       debug("%s: Could not setup SPI interface\n", __func__);
-                       return -CROS_EC_ERR_DEV_INIT;
-               }
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_I2C
-       case CROS_EC_IF_I2C:
-               if (cros_ec_i2c_init(dev, blob))
-                       return -CROS_EC_ERR_DEV_INIT;
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_LPC
-       case CROS_EC_IF_LPC:
-               if (cros_ec_lpc_init(dev, blob))
-                       return -CROS_EC_ERR_DEV_INIT;
-               break;
-#endif
-#ifdef CONFIG_CROS_EC_SANDBOX
-       case CROS_EC_IF_SANDBOX:
-               if (cros_ec_sandbox_init(dev, blob))
-                       return -CROS_EC_ERR_DEV_INIT;
-               break;
-#endif
-       case CROS_EC_IF_NONE:
-       default:
-               return 0;
-       }
 
-       /* we will poll the EC interrupt line */
-       fdtdec_setup_gpio(&dev->ec_int);
-       if (fdt_gpio_isvalid(&dev->ec_int))
-               gpio_direction_input(dev->ec_int.gpio);
+       cdev->dev = dev;
+       gpio_request_by_name(dev, "ec-interrupt", 0, &cdev->ec_int,
+                            GPIOD_IS_IN);
+       cdev->optimise_flash_write = fdtdec_get_bool(blob, node,
+                                                    "optimise-flash-write");
 
-       if (cros_ec_check_version(dev)) {
+       if (cros_ec_check_version(cdev)) {
                debug("%s: Could not detect CROS-EC version\n", __func__);
                return -CROS_EC_ERR_CHECK_VERSION;
        }
 
-       if (cros_ec_read_id(dev, id, sizeof(id))) {
+       if (cros_ec_read_id(cdev, id, sizeof(id))) {
                debug("%s: Could not read KBC ID\n", __func__);
                return -CROS_EC_ERR_READ_ID;
        }
 
        /* Remember this device for use by the cros_ec command */
-       last_dev = *cros_ecp = dev;
-       debug("Google Chrome EC CROS-EC driver ready, id '%s'\n", id);
+       debug("Google Chrome EC v%d CROS-EC driver ready, id '%s'\n",
+             cdev->protocol_version, id);
 
        return 0;
 }
@@ -1147,15 +1008,10 @@ int cros_ec_decode_region(int argc, char * const argv[])
        return -1;
 }
 
-int cros_ec_decode_ec_flash(const void *blob, struct fdt_cros_ec *config)
+int cros_ec_decode_ec_flash(const void *blob, int node,
+                           struct fdt_cros_ec *config)
 {
-       int flash_node, node;
-
-       node = fdtdec_next_compatible(blob, 0, COMPAT_GOOGLE_CROS_EC);
-       if (node < 0) {
-               debug("Failed to find chrome-ec node'\n");
-               return -1;
-       }
+       int flash_node;
 
        flash_node = fdt_subnode_offset(blob, node, "flash");
        if (flash_node < 0) {
@@ -1197,6 +1053,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,9 +1189,191 @@ 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;
+       struct cros_ec_dev *dev;
+       struct udevice *udev;
        const char *cmd;
        int ret = 0;
 
@@ -1263,7 +1382,11 @@ static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 
        cmd = argv[1];
        if (0 == strcmp("init", cmd)) {
-               ret = cros_ec_init(gd->fdt_blob, &dev);
+               /* Remove any existing device */
+               ret = uclass_find_device(UCLASS_CROS_EC, 0, &udev);
+               if (!ret)
+                       device_remove(udev);
+               ret = uclass_get_device(UCLASS_CROS_EC, 0, &udev);
                if (ret) {
                        printf("Could not init cros_ec device (err %d)\n", ret);
                        return 1;
@@ -1271,11 +1394,12 @@ static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
                return 0;
        }
 
-       /* Just use the last allocated device; there should be only one */
-       if (!last_dev) {
-               printf("No CROS-EC device available\n");
+       ret = uclass_get_device(UCLASS_CROS_EC, 0, &udev);
+       if (ret) {
+               printf("Cannot get cros-ec device (err=%d)\n", ret);
                return 1;
        }
+       dev = dev_get_uclass_priv(udev);
        if (0 == strcmp("id", cmd)) {
                char id[MSG_BYTES];
 
@@ -1495,6 +1619,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 +1634,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 +1651,14 @@ 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
+
+UCLASS_DRIVER(cros_ec) = {
+       .id             = UCLASS_CROS_EC,
+       .name           = "cros_ec",
+       .per_device_auto_alloc_size = sizeof(struct cros_ec_dev),
+};