summaryrefslogtreecommitdiff
path: root/driver/wpc/cps8100.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/wpc/cps8100.c')
-rw-r--r--driver/wpc/cps8100.c487
1 files changed, 449 insertions, 38 deletions
diff --git a/driver/wpc/cps8100.c b/driver/wpc/cps8100.c
index bac911d167..006a42f131 100644
--- a/driver/wpc/cps8100.c
+++ b/driver/wpc/cps8100.c
@@ -6,6 +6,8 @@
#include "chipset.h"
#include "common.h"
#include "console.h"
+#include "cps8200_bootloader.h"
+#include "crc.h"
#include "gpio.h"
#include "i2c.h"
#include "peripheral_charger.h"
@@ -56,11 +58,36 @@
#define CPS8200_REG_I2C_ENABLE 0xFFFFFF00
#define CPS8200_REG_PASSWORD 0x400140FC
+/* Firmware update */
+#define CPS8200_ADDR_SRAM 0x20000000
+#define CPS8200_ADDR_BUFFER0 0x20002800
+#define CPS8200_ADDR_BUFFER1 0x20003000
+#define CPS8200_ADDR_CMD 0x200038F8
+#define CPS8200_ADDR_CMD_STATUS 0x200038FC
+#define CPS8200_ADDR_BUF_SIZE 0x200038F4
+#define CFG_BUFF_SIZE 128
+#define CPS8200_CMD_TIMEOUT (3 * SECOND)
+/* CMD and CMD status. Lower 4 bits are for command ID */
+#define CMD_PGM_BUFFER0 (0x1 << 4)
+#define CMD_PGM_BUFFER1 (0x2 << 4)
+#define CMD_PGM_WR_FLAG (0x8 << 4)
+#define CMD_CACL_CRC_APP (0x9 << 4)
+#define CMD_CACL_CRC_BOOT (0xB << 4)
+#define CMD_STATUS_RUNNING (0x1 << 4)
+#define CMD_STATUS_PASS (0x2 << 4)
+#define CMD_STATUS_FAIL (0x3 << 4)
+#define CMD_STATUS_ILLEGAL (0x4 << 4)
+
#define CPS8100_STATUS_PROFILE(r) (((r)&GENMASK(5, 4)) >> 4)
#define CPS8100_STATUS_CHARGE(r) ((r)&BIT(6))
#define CPS8100_STATUS_DEVICE(r) ((r)&BIT(7))
#define CPS8100_STATUS_BATTERY(r) (((r)&GENMASK(15, 8)) >> 8)
#define CPS8100_IRQ_TYPE(r) (((r)&GENMASK(23, 20)) >> 20)
+#define CPS8200_CMD_STATUS(flag) ((flag)&GENMASK(7, 4))
+#define CPS8X00_GET_FW_VER(value) ((value)&GENMASK(7, 0))
+#define CPS8X00_BAD_FW_VERSION 0xff
+#define CPS8200_CMD_MASK GENMASK(7, 4)
+#define CPS8200_CMD_ID_MASK GENMASK(3, 0)
/* Status flags in ALERT_INFO register */
#define CPS8100_STATUS_FOD BIT(0)
@@ -72,7 +99,15 @@
/* Buffer size for i2c read & write */
#define CPS8100_MESSAGE_BUFFER_SIZE 0x20
+/*
+ * CPS8100 needs 100~120ms delay and CPS8200 needs 40~50ms delay between
+ * reset and the first access to I2C register.
+ */
+#define CPS8200_POWER_ON_DELAY_MS 50
+#define CPS8100_POWER_ON_DELAY_MS 120
+
static uint32_t chip_id;
+static const int short_sleep_ms = 2;
/* TODO: Check datasheet how to wake up and how long it takes to wake up. */
static const int cps8100_wake_up_delay_ms = 10;
@@ -305,6 +340,23 @@ static int cps8100_read32(int port, uint32_t reg, uint32_t *val)
sizeof(*val));
}
+static int cps8200_write32(int port, uint32_t reg, uint32_t val)
+{
+ uint8_t buf[8];
+
+ buf[0] = (reg >> 24) & 0xff;
+ buf[1] = (reg >> 16) & 0xff;
+ buf[2] = (reg >> 8) & 0xff;
+ buf[3] = (reg >> 0) & 0xff;
+
+ buf[4] = (val >> 0) & 0xff;
+ buf[5] = (val >> 8) & 0xff;
+ buf[6] = (val >> 16) & 0xff;
+ buf[7] = (val >> 24) & 0xff;
+
+ return cps8100_i2c_write(port, CPS8200_I2C_ADDR, buf, sizeof(buf));
+}
+
static int cps8200_read32(int port, uint32_t reg, uint32_t *val)
{
uint8_t buf[4];
@@ -314,10 +366,36 @@ static int cps8200_read32(int port, uint32_t reg, uint32_t *val)
buf[2] = (reg >> 8) & 0xff;
buf[3] = (reg >> 0) & 0xff;
- return i2c_xfer(port, CPS8200_I2C_ADDR, buf, 4, (void *)val,
+ return i2c_xfer(port, CPS8200_I2C_ADDR, buf, sizeof(buf), (void *)val,
sizeof(*val));
}
+static int cps8200_write_mem(int port, uint32_t addr, uint8_t *data, size_t len)
+{
+ int rv;
+ uint8_t buf[4];
+
+ buf[0] = (addr >> 24) & 0xff;
+ buf[1] = (addr >> 16) & 0xff;
+ buf[2] = (addr >> 8) & 0xff;
+ buf[3] = (addr >> 0) & 0xff;
+
+ i2c_lock(port, 1);
+ /* Addr */
+ rv = i2c_xfer_unlocked(port, CPS8200_I2C_ADDR, buf, sizeof(buf), NULL,
+ 0, I2C_XFER_START);
+ if (rv) {
+ i2c_lock(port, 0);
+ return rv;
+ }
+ /* Data */
+ rv = i2c_xfer_unlocked(port, CPS8200_I2C_ADDR, data, len, NULL, 0,
+ I2C_XFER_STOP);
+ i2c_lock(port, 0);
+
+ return rv;
+}
+
static int cps8100_unlock(int port)
{
int rv;
@@ -334,7 +412,80 @@ static int cps8200_unlock(int port)
return rv ? rv : cps8200_set_unlock(port);
}
-static int cps8100_reset(struct pchg *ctx)
+/*
+ * Send command to CPS8200 by writing command to CPS8200_ADDR_CMD register.
+ * command = cmd (higher 4 bits) + id (lower 4 bits), id needs to be increased
+ * every time.
+ */
+static uint8_t cps8200_send_cmd(struct pchg *ctx, uint8_t cmd, uint8_t *id)
+{
+ uint8_t port = ctx->cfg->i2c_port;
+ struct cps8x00_update *upd = &(ctx->update.driver_data.cps8200_update);
+
+ *id = upd->cmd_id;
+ cmd &= CPS8200_CMD_MASK;
+ cmd |= *id;
+ upd->cmd_id++;
+ upd->cmd_id &= CPS8200_CMD_ID_MASK;
+
+ return cps8200_write32(port, CPS8200_ADDR_CMD, (uint32_t)cmd);
+}
+
+/*
+ * Read the response of command by reading CPS8200_ADDR_CMD_STATUS register.
+ * Return EC_SUCCESS if the status is CMD_STATUS_PASS and the id matches to
+ * the id which is expected.
+ */
+static int cps8200_wait_cmd_done(int port, uint8_t id)
+{
+ int rv;
+ uint32_t u32;
+ timestamp_t deadline;
+
+ deadline.val = get_time().val + CPS8200_CMD_TIMEOUT;
+ while (1) {
+ msleep(10);
+ rv = cps8200_read32(port, CPS8200_ADDR_CMD_STATUS, &u32);
+ if (rv)
+ return EC_ERROR_UNKNOWN;
+
+ if ((CMD_STATUS_PASS | id) == (u32 & 0x00ff))
+ break;
+ if (CPS8200_CMD_STATUS(u32) == CMD_STATUS_FAIL ||
+ CPS8200_CMD_STATUS(u32) == CMD_STATUS_ILLEGAL) {
+ CPRINTS("Command failed or illegal: %02x",
+ CPS8200_CMD_STATUS(u32));
+ return EC_ERROR_UNKNOWN;
+ }
+
+ rv = timestamp_expired(deadline, NULL);
+ if (rv) {
+ CPRINTS("Command timeout!");
+ return EC_ERROR_TIMEOUT;
+ }
+ }
+ return EC_SUCCESS;
+}
+
+static int cps8x00_read_firmware_ver(struct pchg *ctx)
+{
+ uint32_t u32;
+ int port = ctx->cfg->i2c_port;
+ int rv;
+
+ rv = cps8x00_read32(port, CPS8100_REG_FW_INFO, &u32);
+ if (!rv) {
+ ctx->fw_version = CPS8X00_GET_FW_VER(u32);
+ } else {
+ ctx->fw_version = CPS8X00_BAD_FW_VERSION;
+ CPRINTS("Failed to read FW info: %d", rv);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
+}
+
+static int cps8x00_reset(struct pchg *ctx)
{
gpio_set_level(GPIO_QI_RESET_L, 0);
cps8100_status_update(ctx, 0);
@@ -344,7 +495,7 @@ static int cps8100_reset(struct pchg *ctx)
return EC_SUCCESS;
}
-static int cps8100_init(struct pchg *ctx)
+static int cps8x00_init(struct pchg *ctx)
{
int port = ctx->cfg->i2c_port;
@@ -357,7 +508,7 @@ static int cps8100_init(struct pchg *ctx)
return EC_ERROR_UNKNOWN;
}
-static int cps8100_enable(struct pchg *ctx, bool enable)
+static int cps8x00_enable(struct pchg *ctx, bool enable)
{
return EC_SUCCESS;
}
@@ -375,30 +526,26 @@ static int cps8100_get_alert_info(struct pchg *ctx, uint32_t *reg)
return EC_SUCCESS;
}
-static int cps8100_get_chip_info(struct pchg *ctx)
+static int cps8x00_get_chip_info(struct pchg *ctx)
{
uint32_t u32;
int port = ctx->cfg->i2c_port;
int rv = EC_ERROR_UNKNOWN;
- /*
- * CPS8100 needs 100~120ms delay, CPS8200 needs 40~50ms delay
- * between reset and the first access to I2C register.
- */
if (chip_id == CPS8100_CHIPID) {
/*
* already probed but unlock again in case it's turned
* off.
*/
- msleep(120);
+ msleep(CPS8100_POWER_ON_DELAY_MS);
return cps8100_unlock(port);
} else if (chip_id == CPS8200_CHIPID) {
- msleep(50);
+ msleep(CPS8200_POWER_ON_DELAY_MS);
return cps8200_unlock(port);
}
/* not probed yet, need to unlock blindly first. */
- msleep(120);
+ msleep(MAX(CPS8100_POWER_ON_DELAY_MS, CPS8200_POWER_ON_DELAY_MS));
if (!cps8100_unlock(port))
rv = cps8100_read32(port, CPS8100_REG_IC_INFO, &u32);
else if (!cps8200_unlock(port))
@@ -409,7 +556,7 @@ static int cps8100_get_chip_info(struct pchg *ctx)
return rv;
}
- /* Probe */;
+ /* Probe */
CPRINTS("IC=0x%08x", u32);
if ((u32 & 0xffff) == CPS8100_CHIPID) {
cps8x00_read32 = cps8100_read32;
@@ -422,12 +569,9 @@ static int cps8100_get_chip_info(struct pchg *ctx)
return EC_ERROR_UNKNOWN;
}
- if (!cps8x00_read32(port, CPS8100_REG_FW_INFO, &u32)) {
- CPRINTS("FW=0x%08x", u32);
- } else {
- CPRINTS("Failed to read FW info!");
- return EC_ERROR_UNKNOWN;
- }
+ rv = cps8x00_read_firmware_ver(ctx);
+ if (!rv)
+ CPRINTS("FW=0x%02x", ctx->fw_version);
return EC_SUCCESS;
}
@@ -445,7 +589,7 @@ static void cps8100_print_alert_info(uint32_t reg)
CPRINTFP("Battery: %d%%\n", CPS8100_STATUS_BATTERY(reg));
}
-static int cps8100_get_event(struct pchg *ctx)
+static int cps8x00_get_event(struct pchg *ctx)
{
uint32_t r1, r2;
int rv;
@@ -486,38 +630,305 @@ static int cps8100_get_event(struct pchg *ctx)
return EC_SUCCESS;
}
-static int cps8100_get_soc(struct pchg *ctx)
+static int cps8x00_get_soc(struct pchg *ctx)
{
ctx->battery_percent = CPS8100_STATUS_BATTERY(cps8100_state.reg);
return EC_SUCCESS;
}
-static int cps8100_update_open(struct pchg *ctx)
+/*
+ * This function does the preparation for firmware update, the steps are:
+ * 1. Enable i2c and unlock.
+ * 2. Reset MCU, reset watchdog, disable DCDC and reset MCU clock.
+ * 3. Program bootloader to SRAM.
+ * 4. Run bootloader.
+ * 5. Check CRC of bootloader.
+ * 6. Ready for firmware download. Configure buffer size for firmware download.
+ */
+static int cps8200_update_open(struct pchg *ctx)
{
- return EC_ERROR_UNIMPLEMENTED;
+ uint32_t u32;
+ uint8_t id;
+ int port = ctx->cfg->i2c_port;
+ int rv;
+ struct cps8x00_update *upd = &(ctx->update.driver_data.cps8200_update);
+
+ upd->cmd_id = 0;
+ upd->crc = 0;
+ upd->firmware_len = 0;
+
+ /* enable i2c and unlock */
+ rv = cps8200_unlock(port);
+ if (rv)
+ return rv;
+
+ /*
+ * The value and address of the registers are from the sample code
+ * and programming guide provided by the vendor in
+ * https://issuetracker.google.com/issues/195708351#comment32
+ */
+
+ /* Reset MCU and halt */
+ rv = cps8200_write32(port, 0x40014028, 0x00010000);
+ if (rv)
+ return rv;
+ msleep(50);
+
+ /* Reset watchdog */
+ rv = cps8200_write32(port, 0x40008400, 0x1ACCE551);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+ rv = cps8200_write32(port, 0x40008008, 0x0);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* Disable DCDC module */
+ rv = cps8200_write32(port, 0x4000F0A4, 0x0);
+ if (rv)
+ return rv;
+ msleep(50);
+
+ /* Reset MCU clock */
+ rv = cps8200_write32(port, 0x40014020, 0x0);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+ rv = cps8200_write32(port, 0x40014024, 0x0);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+ rv = cps8200_write32(port, 0x400140A8, 0x0);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* Program bootloader to SRAM */
+ CPRINTS("Loading bootloader hex!");
+ if (cps8200_write_mem(port, CPS8200_ADDR_SRAM, (uint8_t *)&boot_hex,
+ boot_hex_len * 4)) {
+ CPRINTS("Failed to write bootloader!");
+ return EC_ERROR_UNKNOWN;
+ }
+ msleep(short_sleep_ms);
+
+ /* disable trim */
+ rv = cps8200_write32(port, 0x4001F01C, 0x0);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* enable address remap */
+ rv = cps8200_write32(port, 0x4001F030, 0xFFFFFF00);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+ rv = cps8200_write32(port, 0x4001F034, 0xFFFFFFFF);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+ rv = cps8200_write32(port, 0x4001F038, 0xFFFFFFFF);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+ rv = cps8200_write32(port, 0x4001F03C, 0xFFFFFFFF);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* disable mcu halt, run bootloader */
+ rv = cps8200_write32(port, 0x40014028, 0x101);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* enable i2c and unlock */
+ rv = cps8200_i2c_enable(port);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* write bootloader length */
+ rv = cps8200_write32(port, CPS8200_ADDR_BUFFER0, boot_hex_len * 4);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* calculate CRC of bootloader */
+ rv = cps8200_send_cmd(ctx, CMD_CACL_CRC_BOOT, &id);
+ if (rv)
+ return rv;
+
+ /* check command status */
+ rv = cps8200_wait_cmd_done(port, id);
+ if (rv)
+ return rv;
+ msleep(100);
+
+ /* check CRC */
+ rv = cps8200_read32(port, CPS8200_ADDR_BUFFER0, &u32);
+ if (rv)
+ return rv;
+ upd->crc = cros_crc16((uint8_t *)boot_hex, boot_hex_len * 4, 0);
+ if (upd->crc != (u32 & 0x0000ffff)) {
+ CPRINTS("crc = %04x, expect %04x", u32, upd->crc);
+ CPRINTS("CRC of bootloader is wroing!");
+ return EC_ERROR_UNKNOWN;
+ }
+ CPRINTS("Successfully load bootloader!");
+
+ upd->crc = 0x0000;
+ /* Prepare to download firmware and program flash, change buffer size */
+ rv = cps8200_write32(port, CPS8200_ADDR_BUF_SIZE,
+ ctx->cfg->block_size / 4);
+ if (rv) {
+ CPRINTS("Failed to change buffer size (%d)", rv);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
}
-static int cps8100_update_write(struct pchg *ctx)
+/*
+ * This function writes the firmware block to the chip and returns only after
+ * the write is complete. The steps are:
+ * 1. Write firmware block to the chip buffer.
+ * 2. Send command to the chip.
+ * 3. The chip program the flash.
+ * 4. Calculate and update CRC.
+ */
+static int cps8200_update_write(struct pchg *ctx)
{
- return EC_ERROR_UNIMPLEMENTED;
+ int port = ctx->cfg->i2c_port;
+ int rv;
+ uint8_t id;
+ uint8_t *buf = ctx->update.data;
+ struct cps8x00_update *upd = &(ctx->update.driver_data.cps8200_update);
+
+ /* Write data to buffer */
+ if (cps8200_write_mem(port, CPS8200_ADDR_BUFFER0, buf,
+ ctx->update.size))
+ return EC_ERROR_UNKNOWN;
+ msleep(short_sleep_ms);
+
+ /* Write buffer to flash */
+ rv = cps8200_send_cmd(ctx, CMD_PGM_BUFFER0, &id);
+ if (rv)
+ return rv;
+
+ /* Check the program result */
+ rv = cps8200_wait_cmd_done(port, id);
+ if (rv) {
+ CPRINTS("Failed to write flash : %d", rv);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ /* Calculate and update CRC */
+ upd->firmware_len += ctx->update.size;
+ upd->crc = cros_crc16(ctx->update.data, ctx->update.size, upd->crc);
+
+ return EC_SUCCESS;
}
-static int cps8100_update_close(struct pchg *ctx)
+/*
+ * This function checks firmware update result, power on and power off the chip
+ * if firmware update is successful. The steps are:
+ * 1. Send command to the chip to calculate the firmware CRC.
+ * 2. Read the CRC value from the chip and compare.
+ * 3. If CRC is correct, power off and power on the chip.
+ */
+static int cps8200_update_close(struct pchg *ctx)
{
- return EC_ERROR_UNIMPLEMENTED;
+ int port = ctx->cfg->i2c_port;
+ int rv;
+ uint32_t u32;
+ uint8_t id;
+ struct cps8x00_update *upd = &(ctx->update.driver_data.cps8200_update);
+ uint32_t len = upd->firmware_len;
+
+ /* Write firmware length */
+ rv = cps8200_write32(port, CPS8200_ADDR_BUFFER0, len);
+ if (rv)
+ return rv;
+ msleep(short_sleep_ms);
+
+ /* Check firmware CRC */
+ CPRINTS("Checking Firmware CRC...");
+ rv = cps8200_send_cmd(ctx, CMD_CACL_CRC_APP, &id);
+ if (rv)
+ return rv;
+
+ rv = cps8200_wait_cmd_done(port, id);
+ if (rv) {
+ CPRINTS("Command to calculate CRC timeout or failed: %d", rv);
+ return EC_ERROR_UNKNOWN;
+ }
+ msleep(100);
+
+ cps8200_read32(port, CPS8200_ADDR_BUFFER0, &u32);
+ if (upd->crc != (u32 & 0x0000ffff)) {
+ CPRINTS("crc = %04x, expect %04x", u32, upd->crc);
+ CPRINTS("CRC of firmware is wroing!");
+ return EC_ERROR_UNKNOWN;
+ }
+ CPRINTS("Firmware CRC is correct!");
+ CPRINTS("Successfully update the firmware");
+
+ rv = cps8200_send_cmd(ctx, CMD_PGM_WR_FLAG, &id);
+ if (rv)
+ return rv;
+
+ rv = cps8200_wait_cmd_done(port, id);
+ if (rv) {
+ CPRINTS("Command to program flash failed: %d", rv);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ /*
+ * Due to the CPS8200 issue
+ * (https://issuetracker.google.com/issues/258093708), we can't simply
+ * reset CPS8200. CPS8200 needs to be powered off to completely reset
+ * its internal modules after completing the firmware update, and then
+ * power on after some delay. We need 50ms delay after power on to
+ * ensure there is enough delay before first I2C command
+ */
+
+ /* power off MCU */
+ board_pchg_power_on(PCHG_CTX_TO_PORT(ctx), 0);
+ msleep(short_sleep_ms);
+ /* power on MCU */
+ board_pchg_power_on(PCHG_CTX_TO_PORT(ctx), 1);
+ msleep(CPS8200_POWER_ON_DELAY_MS);
+ /* Update the information of firmware version */
+ cps8200_unlock(port);
+ rv = cps8x00_read_firmware_ver(ctx);
+ if (!rv)
+ CPRINTS("FW=0x%02x", ctx->fw_version);
+
+ return EC_SUCCESS;
}
const struct pchg_drv cps8100_drv = {
- .reset = cps8100_reset,
- .init = cps8100_init,
- .enable = cps8100_enable,
- .get_chip_info = cps8100_get_chip_info,
- .get_event = cps8100_get_event,
- .get_soc = cps8100_get_soc,
- .update_open = cps8100_update_open,
- .update_write = cps8100_update_write,
- .update_close = cps8100_update_close,
+ .reset = cps8x00_reset,
+ .init = cps8x00_init,
+ .enable = cps8x00_enable,
+ .get_chip_info = cps8x00_get_chip_info,
+ .get_event = cps8x00_get_event,
+ .get_soc = cps8x00_get_soc,
+};
+
+const struct pchg_drv cps8200_drv = {
+ .reset = cps8x00_reset,
+ .init = cps8x00_init,
+ .enable = cps8x00_enable,
+ .get_chip_info = cps8x00_get_chip_info,
+ .get_event = cps8x00_get_event,
+ .get_soc = cps8x00_get_soc,
+ .update_open = cps8200_update_open,
+ .update_write = cps8200_update_write,
+ .update_close = cps8200_update_close,
};
static void cps8100_dump(struct pchg *ctx)
@@ -555,8 +966,8 @@ static int cc_cps8100(int argc, const char **argv)
}
if (!strcasecmp(argv[2], "reset")) {
- cps8100_reset(ctx);
- cps8100_init(ctx);
+ cps8x00_reset(ctx);
+ cps8x00_init(ctx);
} else {
return EC_ERROR_PARAM3;
}