anx7688: Add support for ANX7688
authorNicolas Boichat <drinkcat@google.com>
Tue, 19 Apr 2016 04:11:51 +0000 (12:11 +0800)
committerchrome-bot <chrome-bot@chromium.org>
Thu, 9 Jun 2016 07:22:47 +0000 (00:22 -0700)
ANX7688 is a PD + HDMI->DP converter. It contains a firmware
that we update from the AP-FW, at boot time, which is the only
reason to have a driver for it in depthcharge.

We reuse the PD software sync mechanism, with some caveats as
the chip does not have proper RO/RW sections.

BRANCH=none
BUG=chrome-os-partner:52434
TEST=Boot elm-rev0

Change-Id: I4e16dfdfea236a0de5c9f6881e39513457402971
Signed-off-by: Nicolas Boichat <drinkcat@google.com>
Reviewed-on: https://chromium-review.googlesource.com/339497
Commit-Ready: Nicolas Boichat <drinkcat@chromium.org>
Tested-by: Nicolas Boichat <drinkcat@chromium.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
src/drivers/Kconfig
src/drivers/ec/Makefile.inc
src/drivers/ec/anx7688/Kconfig [new file with mode: 0644]
src/drivers/ec/anx7688/Makefile.inc [new file with mode: 0644]
src/drivers/ec/anx7688/anx7688.c [new file with mode: 0644]
src/drivers/ec/anx7688/anx7688.h [new file with mode: 0644]

index 6f54073..83659ef 100644 (file)
@@ -12,6 +12,7 @@
 
 source src/drivers/bus/Kconfig
 source src/drivers/dma/Kconfig
+source src/drivers/ec/anx7688/Kconfig
 source src/drivers/ec/cros/Kconfig
 source src/drivers/flash/Kconfig
 source src/drivers/gpio/Kconfig
index e024937..c0f65e7 100644 (file)
@@ -12,5 +12,6 @@
 ##
 
 subdirs-$(CONFIG_DRIVER_EC_CROS) += cros
+subdirs-$(CONFIG_DRIVER_EC_ANX7688) += anx7688
 
 depthcharge-y += vboot_ec.c
diff --git a/src/drivers/ec/anx7688/Kconfig b/src/drivers/ec/anx7688/Kconfig
new file mode 100644 (file)
index 0000000..1f6154b
--- /dev/null
@@ -0,0 +1,17 @@
+##
+## Copyright 2016 Google Inc.  All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; version 2 of the License.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+
+config DRIVER_EC_ANX7688
+       bool "ANX7688 TCPC and HDMI->DP converter"
+       default n
+       help
+         Add support for ANX7688 TCPC and HDMI->DP converter
diff --git a/src/drivers/ec/anx7688/Makefile.inc b/src/drivers/ec/anx7688/Makefile.inc
new file mode 100644 (file)
index 0000000..4480305
--- /dev/null
@@ -0,0 +1,14 @@
+##
+## Copyright 2016 Google Inc.
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; version 2 of the License.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+depthcharge-y += anx7688.c
diff --git a/src/drivers/ec/anx7688/anx7688.c b/src/drivers/ec/anx7688/anx7688.c
new file mode 100644 (file)
index 0000000..332d468
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <assert.h>
+#include <cbfs.h>
+#include <endian.h>
+#include <libpayload.h>
+
+#include "base/container_of.h"
+#include "drivers/ec/anx7688/anx7688.h"
+#include "drivers/flash/cbfs.h"
+
+/* ANX7688 FW address (8-bit: 0x50) */
+#define CHIP_FW 0x28
+/* ANX7688 TCPC address (8-bit: 0x58) */
+#define CHIP_TCPC 0x2c
+
+#define VENDOR 0x291f
+#define PRODUCT 0x8876
+
+#define RW_TIMEOUT_US 10000
+/* Version can take up to 100ms to appear after reset */
+#define VERSION_TIMEOUT_US 300000
+/* Whole firmware can take up to 750ms to load */
+#define WAIT_TIMEOUT_US 2000000
+
+/* Wait for ANX7688 FW to fully boot (or stop loading due to FW corruption).
+ * crc_ok returns information on whether ANX7688 FW loader CRC checks passed
+ * (bits below all 1).
+ *
+ * This requires the bus to be unlocked!
+ *
+ * CHIP_FW 0xe7 provides the following information
+ * Bit Name           Description
+ * 6   BOOT_LOAD_DONE 1:boot data load completed and crc32 check ok
+ * 5   CRC_OK         1:crc32 check ok when EEPROM data load completed
+ * 4   LOAD_DONE      1:EEPROM data load completed
+ */
+static int anx7688_wait_fw(struct Anx7688 *me, int *crc_ok)
+{
+       uint8_t status;
+       uint64_t start = timer_us(0);
+
+       do {
+               if (timer_us(start) > WAIT_TIMEOUT_US) {
+                       printf("%s: Timeout\n", __func__);
+                       return -1;
+               }
+               if (i2c_readb(&me->bus->ops, CHIP_FW, 0xE7, &status) < 0) {
+                       printf("%s: Read failure\n", __func__);
+                       return -1;
+               }
+       } while ((status & 0x10) != 0x10);
+
+       /* Check if there was a CRC failure during load. */
+       if (crc_ok)
+               *crc_ok = ((status & 0x70) == 0x70);
+
+       printf("%s: Waited for %lld us (%02x).\n", __func__,
+               timer_us(start), status);
+
+       return 0;
+}
+
+/* Detect if chip is indeed ANX7688 */
+int anx7688_detect(struct Anx7688 *me)
+{
+       uint16_t vendor, product, device;
+
+       if (i2c_readw(&me->bus->ops, CHIP_TCPC, 0x00, &vendor) < 0 ||
+           i2c_readw(&me->bus->ops, CHIP_TCPC, 0x02, &product) < 0 ||
+           i2c_readw(&me->bus->ops, CHIP_TCPC, 0x04, &device) < 0) {
+               printf("ANX7688 vendor/product cannot be read\n");
+               return 0;
+       }
+
+       printf("ANX7688 %04x/%04x/%04x detected\n", vendor, product, device);
+
+       return (vendor == VENDOR && product == PRODUCT);
+}
+
+/* Returns -1 on error, 0 if the block does not match, 1 if it matches. */
+static int verify_block(I2cOps *bus, int address, const uint8_t *block)
+{
+       uint8_t status;
+       uint8_t buffer[16];
+       uint64_t start;
+
+       printf("V");
+
+       /* Write address and read command */
+       uint8_t cmdbuf[3] = {address >> 8, address & 0xFF, 0x06};
+
+       if (i2c_writeblock(bus, CHIP_FW, 0xE0, cmdbuf, sizeof(cmdbuf)) < 0)
+               return -1;
+
+       start = timer_us(0);
+       do {
+               if (timer_us(start) > RW_TIMEOUT_US) {
+                       printf("%s: Timeout\n", __func__);
+                       return -1;
+               }
+               if (i2c_readb(bus, CHIP_FW, 0xE2, &status) < 0)
+                       return -1;
+       } while ((status & 0x08) == 0);
+
+       /* Read data */
+       if (i2c_readblock(bus, CHIP_FW, 0xD0, buffer, 16) < 0)
+               return -1;
+
+       if (memcmp(buffer, block, 16) != 0)
+               return 0;
+
+       return 1;
+}
+
+static int write_block(I2cOps *bus, int address, const uint8_t *block)
+{
+       uint8_t status;
+       uint64_t start;
+
+       printf("W");
+
+       /* Write data, address and write command */
+       uint8_t cmdbuf[16+3];
+
+       memcpy(cmdbuf, block, 16);
+       cmdbuf[16] = address >> 8;
+       cmdbuf[17] = address & 0xFF;
+       cmdbuf[18] = 0x01;
+
+       if (i2c_writeblock(bus, CHIP_FW, 0xD0, cmdbuf, sizeof(cmdbuf)) < 0)
+               return -1;
+
+       start = timer_us(0);
+       do {
+               if (timer_us(start) > RW_TIMEOUT_US) {
+                       printf("%s: Timeout\n", __func__);
+                       return -1;
+               }
+               if (i2c_readb(bus, CHIP_FW, 0xE2, &status) < 0)
+                       return -1;
+       } while ((status & 0x08) == 0x00);
+
+       /* Wait for internal programming cycle to complete */
+       mdelay(5);
+
+       return 0;
+}
+
+/* Verify, compare, then (possibly) write and verify again a 16-byte block. */
+static int update_block(I2cOps *bus, int address, const uint8_t *block)
+{
+       int ret;
+
+       ret = verify_block(bus, address, block);
+       if (ret < 0)
+               return ret;
+
+       if (ret == 0) {
+               ret = write_block(bus, address, block);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/* Returns version (or 0x0000 on error) */
+static uint16_t anx7688_verify(struct Anx7688 *me)
+{
+       uint16_t status;
+       uint16_t version;
+       uint64_t start = timer_us(0);
+
+       /* Wait for version to appear. */
+       do {
+               if (timer_us(start) > VERSION_TIMEOUT_US) {
+                       printf("%s: Timeout waiting for version\n", __func__);
+                       return 0x0000;
+               }
+
+               i2c_readw(&me->bus->ops, CHIP_TCPC, 0x80, &version);
+       } while (version == 0x0000);
+
+       printf("ANX7688 version: %04x\n", version);
+
+       /* Read load and CRC32 status */
+       while(1) {
+               if (timer_us(start) > WAIT_TIMEOUT_US) {
+                       printf("%s: Timeout waiting for CRC32\n", __func__);
+                       return 0x0000;
+               }
+
+               if (i2c_readw(&me->bus->ops, CHIP_TCPC, 0x90, &status) < 0)
+                       continue;
+
+               uint8_t crc_status = (status >> 8) & 0x0f;
+               uint8_t load_status = status & 0x0f;
+
+               /* Check that load status == CRC status (CRC status is updated
+                * before load status, to avoid race condition). */
+               if ((crc_status & load_status) != load_status) {
+                       printf("%s: Bad CRC (%04x)\n", __func__, status);
+                       /* FIXME: Current firmware 1.12 updates load status
+                          before CRC status, so this might not be fatal. */
+                       //return 0x0000;
+               }
+
+               /* Fully loaded */
+               if ((status & 0x0f0f) == 0x0f0f)
+                       break;
+       }
+
+       return version;
+}
+
+static int anx7688_pd_control(struct Anx7688 *me, enum ec_pd_control_cmd cmd)
+{
+       struct ec_params_pd_control p;
+       int ret;
+
+       p.chip = 0;
+       p.subcmd = cmd;
+
+       ret = ec_command(me->bus->ec, EC_CMD_PD_CONTROL, 0,
+                        &p, sizeof(p), NULL, 0);
+
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+/* Update ANX7688 firmware.
+ * Image file binary format (little-endian)
+ * Header:
+ *  - 2-byte version
+ *  - 1-byte number of CRC addresses
+ *  - 1-byte number of regions
+ *  - List of CRC32 addresses (2-byte: address)
+ *  - List of regions to flash:
+ *    - 2-byte: start
+ *    - 2-byte: end
+ * Payload (flat binary)
+ */
+static VbError_t anx7688_update(struct Anx7688 *me,
+                               const uint8_t *data, int len)
+{
+       int i, ret = 0;
+       uint16_t address;
+
+       /* Version takes bytes 0-1 */
+       uint8_t num_crc32 = data[2];
+       uint8_t num_regions = data[3];
+       int offset = 4;
+       const uint16_t *crc32data = (uint16_t *)&data[offset];
+       offset += num_crc32*2;
+       const uint16_t *regionsdata = (uint16_t *)&data[offset];
+       offset += num_regions*4;
+       const uint8_t *rawdata = &data[offset];
+       int rawlen = len - offset;
+
+       printf("Updating ANX7688 firmware...\n");
+       uint64_t start = timer_us(0);
+
+       /* Lock ANX7688. */
+       if (anx7688_pd_control(me, PD_SUSPEND) < 0)
+               return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
+
+       /* Wait for ANX7688 FW load to complete. */
+       ret = anx7688_wait_fw(me, NULL);
+       if (ret < 0)
+               goto out;
+
+       /* For some reason, status register read above is unreliable, wait a
+        * bit more, to be safe. See crosbug.com/p/53754 */
+       mdelay(1000);
+
+       /* Reset OCM */
+       ret = i2c_set_bits(&me->bus->ops, CHIP_FW, 0x05, 0x10);
+       if (ret < 0)
+               goto out;
+
+       /* Write password */
+       ret = i2c_set_bits(&me->bus->ops, CHIP_FW, 0x3F, 0x20);
+       if (ret < 0)
+               goto out;
+       ret = i2c_set_bits(&me->bus->ops, CHIP_FW, 0x44, 0x81);
+       if (ret < 0)
+               goto out;
+       ret = i2c_set_bits(&me->bus->ops, CHIP_FW, 0x66, 0x08);
+       if (ret < 0)
+               goto out;
+
+       /* First update all the CRCs, to make corruption more likely in case
+        * of failed update. */
+       for (i = 0; i < num_crc32; i++) {
+               /* Align to 16 byte boundary */
+               address = ALIGN_DOWN(le16toh(crc32data[i]), 16);
+               assert(address+16 <= rawlen);
+               ret = update_block(&me->bus->ops, address, &rawdata[address]);
+
+               if (ret < 0)
+                       goto out;
+       }
+
+       for (i = num_regions-1; i >= 0; i--) {
+               uint16_t start = ALIGN_DOWN(le16toh(regionsdata[2*i]), 16);
+               uint16_t end = ALIGN_UP(le16toh(regionsdata[2*i+1]), 16);
+               /* Update from high to low addresses, to make sure the boot
+                * block (0x0010-0x080F) stays corrupted in case of a partial
+                * update. */
+               for (address = end-16; address >= start; address -= 16) {
+                       assert(address+16 <= rawlen);
+                       ret = update_block(&me->bus->ops,
+                                          address, &rawdata[address]);
+
+                       if (ret < 0)
+                               goto out;
+               }
+       }
+
+out:
+       anx7688_pd_control(me, PD_RESET);
+       anx7688_pd_control(me, PD_RESUME);
+       mdelay(100);
+
+       if (ret < 0) {
+               printf("ANX7688 FW update error!\n");
+               return VBERROR_UNKNOWN;
+       }
+
+       printf("ANX7688 FW updated successfully (%lld ms)!\n",
+              timer_us(start)/1000);
+       return VBERROR_SUCCESS;
+}
+
+VbError_t vboot_hash_image(VbootEcOps *vbec, enum VbSelectFirmware_t select,
+                    const uint8_t **hash, int *hash_size)
+{
+       Anx7688 *me = container_of(vbec, Anx7688, vboot);
+       /* Actually, hash is just the version */
+       static uint16_t hash_version;
+
+       /* Return version as hash */
+       hash_version = anx7688_verify(me);
+
+       *hash_size = sizeof(hash_version);
+       *hash = (uint8_t *)&hash_version;
+
+       return VBERROR_SUCCESS;
+}
+
+static VbError_t vboot_running_rw(VbootEcOps *vbec, int *in_rw)
+{
+       Anx7688 *me = container_of(vbec, Anx7688, vboot);
+
+       if (cros_ec_tunnel_i2c_protect_status(me->bus, in_rw)) {
+               /* We probably need a new EC */
+               return VBERROR_UNKNOWN;
+       }
+
+       return VBERROR_SUCCESS;
+}
+
+static VbError_t vboot_reboot_to_ro(VbootEcOps *vbec)
+{
+       return VBERROR_SUCCESS;
+}
+
+static VbError_t vboot_jump_to_rw(VbootEcOps *vbec)
+{
+       return VBERROR_SUCCESS;
+}
+
+static VbError_t vboot_disable_jump(VbootEcOps *vbec)
+{
+       Anx7688 *me = container_of(vbec, Anx7688, vboot);
+
+       if (cros_ec_tunnel_i2c_protect(me->bus)) {
+               /* We probably need a new EC */
+               return VBERROR_UNKNOWN;
+       }
+
+       if (anx7688_pd_control(me, PD_CONTROL_DISABLE) < 0) {
+               /* We probably need a new EC */
+               return VBERROR_UNKNOWN;
+       }
+
+       return VBERROR_SUCCESS;
+}
+
+static VbError_t vboot_entering_mode(VbootEcOps *vbec, enum VbEcBootMode_t vbm)
+{
+       /* Noop success (should not be called) */
+
+       return VBERROR_SUCCESS;
+}
+
+static VbError_t vboot_update_image(VbootEcOps *vbec,
+       enum VbSelectFirmware_t select, const uint8_t *image, int image_size)
+{
+       Anx7688 *me = container_of(vbec, Anx7688, vboot);
+       int protected;
+
+       if (cros_ec_tunnel_i2c_protect_status(me->bus, &protected)) {
+               /* We probably need a new EC */
+               return VBERROR_UNKNOWN;
+       }
+
+       if (protected)
+               return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
+
+       /* Double-check we are really trying to update the right chip. */
+       if (!anx7688_detect(me))
+               return VBERROR_UNKNOWN;
+
+       /* Force update */
+       return anx7688_update(me, image, image_size);
+}
+
+static VbError_t vboot_protect(VbootEcOps *vbec, enum VbSelectFirmware_t select)
+{
+       /* Noop success */
+
+       return VBERROR_SUCCESS;
+}
+
+Anx7688 *new_anx7688(CrosECTunnelI2c *bus)
+{
+       Anx7688 *bridge = xzalloc(sizeof(*bridge));
+
+       bridge->bus = bus;
+
+       bridge->vboot.running_rw = vboot_running_rw;
+       bridge->vboot.jump_to_rw = vboot_jump_to_rw;
+       bridge->vboot.disable_jump = vboot_disable_jump;
+       bridge->vboot.hash_image = vboot_hash_image;
+       bridge->vboot.update_image = vboot_update_image;
+       bridge->vboot.protect = vboot_protect;
+       bridge->vboot.entering_mode = vboot_entering_mode;
+       bridge->vboot.reboot_to_ro = vboot_reboot_to_ro;
+
+       return bridge;
+}
diff --git a/src/drivers/ec/anx7688/anx7688.h b/src/drivers/ec/anx7688/anx7688.h
new file mode 100644 (file)
index 0000000..6fc5248
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __DRIVERS_MISC_ANX7688_H__
+#define __DRIVERS_MISC_ANX7688_H__
+
+#include <stdint.h>
+
+#include "drivers/bus/i2c/i2c.h"
+#include "drivers/ec/cros/ec.h"
+#include "drivers/ec/vboot_ec.h"
+#include "drivers/bus/i2c/cros_ec_tunnel.h"
+
+typedef struct Anx7688
+{
+       VbootEcOps vboot;
+       CrosECTunnelI2c *bus;
+} Anx7688;
+
+
+Anx7688 *new_anx7688(CrosECTunnelI2c *bus);
+
+#endif /* __DRIVERS_POWER_TPS65913_H__ */