2143 lines
52 KiB
C
2143 lines
52 KiB
C
/* Copyright 2013 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*
|
|
* ITE83xx SoC in-system programming tool
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ftdi.h>
|
|
#include <getopt.h>
|
|
#include <linux/i2c-dev.h>
|
|
#include <linux/i2c.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "compile_time_macros.h"
|
|
#include "usb_if.h"
|
|
|
|
/* Default FTDI device : Servo v2. */
|
|
#define SERVO_USB_VID 0x18d1
|
|
#define SERVO_USB_PID 0x5002
|
|
#define SERVO_INTERFACE INTERFACE_B
|
|
|
|
/* Default CCD device: Cr50. */
|
|
#define CR50_USB_VID 0x18d1
|
|
#define CR50_USB_PID 0x5014
|
|
|
|
/* Cr50 exposed properties of the USB I2C endpoint. */
|
|
#define CR50_I2C_SUBCLASS 82
|
|
#define CR50_I2C_PROTOCOL 1
|
|
|
|
#define CROS_CMD_ADDR 0x78 /* USB_I2C_CMD_ADDR 0xF0 >> 1 */
|
|
#define CROS_CMD_ITE_SYNC 0
|
|
|
|
/* DBGR I2C addresses */
|
|
#define I2C_CMD_ADDR 0x5A
|
|
#define I2C_DATA_ADDR 0x35
|
|
#define I2C_BLOCK_ADDR 0x79
|
|
|
|
#define FTDI_I2C_FREQ 400000
|
|
|
|
/* I2C pins on the FTDI interface */
|
|
#define SCL_BIT BIT(0)
|
|
#define SDA_BIT BIT(1)
|
|
|
|
/* Chip ID register value */
|
|
#define CHIP_ID 0x8380
|
|
|
|
/* Embedded flash page size */
|
|
#define PAGE_SIZE (1<<8)
|
|
|
|
/* Embedded flash block write size for different programming modes. */
|
|
#define FTDI_BLOCK_WRITE_SIZE (1<<16)
|
|
|
|
/* Embedded flash number of pages in a sector erase */
|
|
#define SECTOR_ERASE_PAGES 4
|
|
|
|
/* JEDEC SPI Flash commands */
|
|
#define SPI_CMD_PAGE_PROGRAM 0x02
|
|
#define SPI_CMD_WRITE_DISABLE 0x04
|
|
#define SPI_CMD_READ_STATUS 0x05
|
|
#define SPI_CMD_WRITE_ENABLE 0x06
|
|
#define SPI_CMD_FAST_READ 0x0B
|
|
#define SPI_CMD_CHIP_ERASE 0x60
|
|
#define SPI_CMD_SECTOR_ERASE 0xD7
|
|
#define SPI_CMD_WORD_PROGRAM 0xAD
|
|
#define SPI_CMD_EWSR 0x50 /* Enable Write Status Register */
|
|
#define SPI_CMD_WRSR 0x01 /* Write Status Register */
|
|
|
|
/* Size for FTDI outgoing buffer */
|
|
#define FTDI_CMD_BUF_SIZE (1<<12)
|
|
|
|
/* Reset Status */
|
|
#define RSTS_VCCDO_PW_ON 0x40
|
|
#define RSTS_VFSPIPG 0x20
|
|
#define RSTS_HGRST 0x08
|
|
#define RSTS_GRST 0x04
|
|
|
|
/* I2C MUX Configuration: TCA9543 or PCA9546 */
|
|
#define I2C_MUX_CMD_ADDR 0x70
|
|
#define I2C_MUX_CMD_NONE 0x00
|
|
#define I2C_MUX_CMD_INAS 0x01
|
|
#define I2C_MUX_CMD_EC 0x02
|
|
|
|
static volatile sig_atomic_t exit_requested;
|
|
|
|
struct i2c_interface;
|
|
|
|
/* Config mostly comes from the command line. Defaults are set in main(). */
|
|
struct iteflash_config {
|
|
char *input_filename;
|
|
char *output_filename;
|
|
int send_waveform; /* boolean */
|
|
int erase; /* boolean */
|
|
int i2c_mux; /* boolean */
|
|
int debug; /* boolean */
|
|
int disable_watchdog; /* boolean */
|
|
int disable_protect_path; /* boolean */
|
|
int block_write_size;
|
|
int usb_interface;
|
|
int usb_vid;
|
|
int usb_pid;
|
|
char *usb_serial;
|
|
char *i2c_dev_path;
|
|
const struct i2c_interface *i2c_if;
|
|
size_t range_base;
|
|
size_t range_size;
|
|
};
|
|
|
|
struct common_hnd {
|
|
struct iteflash_config conf;
|
|
int flash_size;
|
|
int flash_cmd_v2; /* boolean */
|
|
int dbgr_addr_3bytes; /* boolean */
|
|
union {
|
|
int i2c_dev_fd;
|
|
struct usb_endpoint uep;
|
|
struct ftdi_context *ftdi_hnd;
|
|
};
|
|
};
|
|
|
|
/* For all callback return values, zero indicates success, non-zero failure. */
|
|
struct i2c_interface {
|
|
/* Optional, may be NULL. */
|
|
int (*interface_init)(struct common_hnd *chnd);
|
|
/* Always called if non-NULL, even if special waveform is skipped! */
|
|
/* Optional, may be NULL. */
|
|
int (*interface_post_waveform)(struct common_hnd *chnd);
|
|
/* Called exactly once if and only if interface_init() succeeded. */
|
|
/* Optional, may be NULL. */
|
|
int (*interface_shutdown)(struct common_hnd *chnd);
|
|
/* Optional, may be NULL (unsupported for this I2C interface type). */
|
|
int (*send_special_waveform)(struct common_hnd *chnd);
|
|
/* Required, must not be NULL. */
|
|
int (*byte_transfer)(struct common_hnd *chnd, uint8_t addr,
|
|
uint8_t *data, int write, int numbytes);
|
|
/* Required, must be positive. */
|
|
int default_block_write_size;
|
|
};
|
|
|
|
static void null_and_free(void **ptr)
|
|
{
|
|
void *holder;
|
|
|
|
if (*ptr) {
|
|
holder = *ptr;
|
|
*ptr = NULL;
|
|
free(holder);
|
|
}
|
|
}
|
|
|
|
/* This releases any memory owned by *conf. This does NOT free conf itself! */
|
|
/* Not all pointers in conf necessarily point to memory owned by it. */
|
|
static void config_release(struct iteflash_config *conf)
|
|
{
|
|
null_and_free((void **)&conf->input_filename);
|
|
null_and_free((void **)&conf->output_filename);
|
|
null_and_free((void **)&conf->usb_serial);
|
|
null_and_free((void **)&conf->i2c_dev_path);
|
|
}
|
|
|
|
/* number of bytes to send consecutively before checking for ACKs */
|
|
#define FTDI_TX_BUFFER_LIMIT 32
|
|
|
|
static inline int i2c_byte_transfer(struct common_hnd *chnd, uint8_t addr,
|
|
uint8_t *data, int write, int numbytes)
|
|
{
|
|
return chnd->conf.i2c_if->byte_transfer(chnd, addr, data, write,
|
|
numbytes);
|
|
}
|
|
|
|
static int linux_i2c_byte_transfer(struct common_hnd *chnd, uint8_t addr,
|
|
uint8_t *data, int write, int numbytes)
|
|
{
|
|
static const int nmsgs = 1;
|
|
int ret, extra_int;
|
|
struct i2c_msg i2cmsg = {};
|
|
struct i2c_rdwr_ioctl_data msgset = {};
|
|
|
|
i2cmsg.addr = addr;
|
|
if (!write)
|
|
i2cmsg.flags |= I2C_M_RD;
|
|
i2cmsg.buf = data;
|
|
i2cmsg.len = numbytes;
|
|
|
|
msgset.msgs = &i2cmsg;
|
|
msgset.nmsgs = nmsgs;
|
|
|
|
ret = ioctl(chnd->i2c_dev_fd, I2C_RDWR, &msgset);
|
|
if (ret < 0) {
|
|
extra_int = errno;
|
|
fprintf(stderr, "%s: ioctl() failed with return value %d and "
|
|
"errno %d\n", __func__, ret, extra_int);
|
|
if (ret == -1 && extra_int)
|
|
ret = -abs(extra_int);
|
|
} else if (ret < nmsgs) {
|
|
fprintf(stderr, "%s: failed to send %d of %d I2C messages\n",
|
|
__func__, (nmsgs - ret), nmsgs);
|
|
ret = -1;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_add_send_byte(struct ftdi_context *ftdi, uint8_t *buf,
|
|
uint8_t *ptr, uint8_t *tbuf, int tcnt, int debug)
|
|
{
|
|
int ret, i, j, remaining_data, ack_idx;
|
|
int tx_buffered = 0;
|
|
static uint8_t ack[FTDI_TX_BUFFER_LIMIT];
|
|
uint8_t *b = ptr;
|
|
uint8_t failed_ack = 0;
|
|
|
|
for (i = 0; i < tcnt; i++) {
|
|
/* If we got a termination signal, stop sending data */
|
|
if (exit_requested)
|
|
return -1;
|
|
|
|
/* WORKAROUND: force SDA before sending the next byte */
|
|
*b++ = SET_BITS_LOW; *b++ = SDA_BIT; *b++ = SCL_BIT | SDA_BIT;
|
|
/* write byte */
|
|
*b++ = MPSSE_DO_WRITE | MPSSE_BITMODE | MPSSE_WRITE_NEG;
|
|
*b++ = 0x07; *b++ = *tbuf++;
|
|
/* prepare for ACK */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SCL_BIT;
|
|
/* read ACK */
|
|
*b++ = MPSSE_DO_READ | MPSSE_BITMODE | MPSSE_LSB;
|
|
*b++ = 0;
|
|
*b++ = SEND_IMMEDIATE;
|
|
|
|
tx_buffered++;
|
|
|
|
/*
|
|
* On the last byte, or every FTDI_TX_BUFFER_LIMIT bytes, read
|
|
* the ACK bits.
|
|
*/
|
|
if (i == tcnt-1 || (tx_buffered == FTDI_TX_BUFFER_LIMIT)) {
|
|
/* write data */
|
|
ret = ftdi_write_data(ftdi, buf, b - buf);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "failed to write byte\n");
|
|
return ret;
|
|
}
|
|
|
|
/* read ACK bits */
|
|
remaining_data = tx_buffered;
|
|
ack_idx = 0;
|
|
do {
|
|
ret = ftdi_read_data(ftdi, &ack[ack_idx],
|
|
remaining_data);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "read ACK failed\n");
|
|
return ret;
|
|
}
|
|
remaining_data -= ret;
|
|
ack_idx += ret;
|
|
} while (remaining_data);
|
|
for (j = 0; j < tx_buffered; j++) {
|
|
if ((ack[j] & 0x80) != 0)
|
|
failed_ack = ack[j];
|
|
}
|
|
|
|
/* check ACK bits */
|
|
if (ret < 0 || failed_ack) {
|
|
if (debug)
|
|
fprintf(stderr,
|
|
"write ACK fail: %d, 0x%02x\n",
|
|
ret, failed_ack);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* reset for next set of transactions */
|
|
b = ptr;
|
|
tx_buffered = 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_add_recv_bytes(struct ftdi_context *ftdi, uint8_t *buf,
|
|
uint8_t *ptr, uint8_t *rbuf, int rcnt)
|
|
{
|
|
int ret, i, rbuf_idx;
|
|
uint8_t *b = ptr;
|
|
|
|
for (i = 0; i < rcnt; i++) {
|
|
/* set SCL low */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SCL_BIT;
|
|
/* read the byte on the wire */
|
|
*b++ = MPSSE_DO_READ; *b++ = 0; *b++ = 0;
|
|
|
|
if (i == rcnt - 1) {
|
|
/* NACK last byte */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SCL_BIT;
|
|
*b++ = MPSSE_DO_WRITE | MPSSE_BITMODE | MPSSE_WRITE_NEG;
|
|
*b++ = 0; *b++ = 0xff; *b++ = SEND_IMMEDIATE;
|
|
} else {
|
|
/* ACK all other bytes */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SCL_BIT | SDA_BIT;
|
|
*b++ = MPSSE_DO_WRITE | MPSSE_BITMODE | MPSSE_WRITE_NEG;
|
|
*b++ = 0; *b++ = 0; *b++ = SEND_IMMEDIATE;
|
|
}
|
|
}
|
|
|
|
ret = ftdi_write_data(ftdi, buf, b - buf);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "failed to prepare read\n");
|
|
return ret;
|
|
}
|
|
|
|
rbuf_idx = 0;
|
|
do {
|
|
/* If we got a termination signal, stop sending data */
|
|
if (exit_requested)
|
|
return -1;
|
|
|
|
ret = ftdi_read_data(ftdi, &rbuf[rbuf_idx], rcnt);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "read byte failed\n");
|
|
break;
|
|
}
|
|
rcnt -= ret;
|
|
rbuf_idx += ret;
|
|
} while (rcnt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define USB_I2C_HEADER_SIZE 4
|
|
static int ccd_i2c_byte_transfer(struct common_hnd *chnd, uint8_t addr,
|
|
uint8_t *data, int write, int numbytes)
|
|
{
|
|
uint8_t usb_buffer[USB_I2C_HEADER_SIZE + numbytes +
|
|
(((!write * numbytes) > 0x7f) ? 2 : 0)];
|
|
size_t response_size;
|
|
size_t extra = 0;
|
|
|
|
/* Do nothing if user wants to quit. */
|
|
if (exit_requested)
|
|
return -1;
|
|
|
|
/*
|
|
* Build a message following format described in ./include/usb_i2c.h.
|
|
*
|
|
* Hardcode port, the lowest 4 bits of the first byte, to 0; may need
|
|
* to make this a command line option.
|
|
*/
|
|
usb_buffer[0] = 0;
|
|
|
|
usb_buffer[1] = addr;
|
|
if (write) {
|
|
/*
|
|
* Write count might spill over into the top 4 bits of the
|
|
* first byte. We trust the caller not to pass numbytes
|
|
* exceeding (2^12 - 1).
|
|
*/
|
|
if (numbytes > 255)
|
|
usb_buffer[0] |= (numbytes >> 4) & 0xf0;
|
|
usb_buffer[2] = numbytes & 0xff;
|
|
usb_buffer[3] = 0;
|
|
memcpy(usb_buffer + USB_I2C_HEADER_SIZE, data, numbytes);
|
|
} else {
|
|
usb_buffer[2] = 0;
|
|
if (numbytes < 0x80) {
|
|
usb_buffer[3] = numbytes;
|
|
} else {
|
|
usb_buffer[3] = (numbytes & 0x7f) | 0x80;
|
|
usb_buffer[4] = numbytes >> 7;
|
|
usb_buffer[5] = 0;
|
|
extra = 2;
|
|
}
|
|
}
|
|
|
|
response_size = 0;
|
|
usb_trx(&chnd->uep, usb_buffer,
|
|
write ? sizeof(usb_buffer) : USB_I2C_HEADER_SIZE + extra,
|
|
usb_buffer, sizeof(usb_buffer), 1, &response_size);
|
|
|
|
if (response_size < (USB_I2C_HEADER_SIZE + (write ? 0 : numbytes))) {
|
|
fprintf(stderr, "%s: got too few bytes (%zd) in response\n",
|
|
__func__, response_size);
|
|
return -1;
|
|
}
|
|
|
|
if (usb_buffer[0]) {
|
|
uint32_t rv;
|
|
|
|
/*
|
|
* Error is reported as a 16 bit value in little endian byte
|
|
* order.
|
|
*/
|
|
rv = usb_buffer[1];
|
|
rv = (rv << 8) + usb_buffer[0];
|
|
|
|
fprintf(stderr, "%s: usb i2c error %d\n",
|
|
__func__,
|
|
(((uint16_t)usb_buffer[1]) << 8) + usb_buffer[0]);
|
|
|
|
return -rv;
|
|
}
|
|
|
|
if (!write)
|
|
memcpy(data, usb_buffer + USB_I2C_HEADER_SIZE, numbytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ftdi_i2c_byte_transfer(struct common_hnd *chnd, uint8_t addr,
|
|
uint8_t *data, int write, int numbytes)
|
|
{
|
|
int ret, rets;
|
|
static uint8_t buf[FTDI_CMD_BUF_SIZE];
|
|
uint8_t *b;
|
|
uint8_t slave_addr;
|
|
struct ftdi_context *ftdi;
|
|
|
|
ret = 0;
|
|
b = buf;
|
|
ftdi = chnd->ftdi_hnd;
|
|
|
|
/* START condition */
|
|
/* SCL & SDA high */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = 0;
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = 0;
|
|
/* SCL high, SDA low */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SDA_BIT;
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SDA_BIT;
|
|
/* SCL low, SDA low */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SCL_BIT | SDA_BIT;
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SCL_BIT | SDA_BIT;
|
|
|
|
/* send address */
|
|
slave_addr = (addr << 1) | (write ? 0 : 1);
|
|
ret = i2c_add_send_byte(ftdi, buf, b, &slave_addr, 1, chnd->conf.debug);
|
|
if (ret < 0) {
|
|
if (chnd->conf.debug)
|
|
fprintf(stderr, "address %02x failed\n", addr);
|
|
ret = -ENXIO;
|
|
goto exit_xfer;
|
|
}
|
|
|
|
b = buf;
|
|
if (write) /* write data */
|
|
ret = i2c_add_send_byte(ftdi, buf, b, data, numbytes,
|
|
chnd->conf.debug);
|
|
else /* read data */
|
|
ret = i2c_add_recv_bytes(ftdi, buf, b, data, numbytes);
|
|
|
|
exit_xfer:
|
|
b = buf;
|
|
/* STOP condition */
|
|
/* SCL high, SDA low */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SDA_BIT;
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = SDA_BIT;
|
|
/* SCL high, SDA high */
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = 0;
|
|
*b++ = SET_BITS_LOW; *b++ = 0; *b++ = 0;
|
|
|
|
rets = ftdi_write_data(ftdi, buf, b - buf);
|
|
if (rets < 0)
|
|
fprintf(stderr, "failed to send STOP\n");
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_write_byte(struct common_hnd *chnd, uint8_t cmd, uint8_t data)
|
|
{
|
|
int ret;
|
|
|
|
ret = i2c_byte_transfer(chnd, I2C_CMD_ADDR, &cmd, 1, 1);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
ret = i2c_byte_transfer(chnd, I2C_DATA_ADDR, &data, 1, 1);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_read_byte(struct common_hnd *chnd, uint8_t cmd, uint8_t *data)
|
|
{
|
|
int ret;
|
|
|
|
ret = i2c_byte_transfer(chnd, I2C_CMD_ADDR, &cmd, 1, 1);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
ret = i2c_byte_transfer(chnd, I2C_DATA_ADDR, data, 0, 1);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Configure I2C MUX to choose EC Prog channel */
|
|
static int config_i2c_mux(struct common_hnd *chnd, uint8_t cmd)
|
|
{
|
|
int ret;
|
|
|
|
ret = i2c_byte_transfer(chnd, I2C_MUX_CMD_ADDR, &cmd, 1, 1);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Failed to configure I2C MUX.");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Get 3rd Byte Chip ID */
|
|
static int get_3rd_chip_id_byte(struct common_hnd *chnd, uint8_t *chip_id)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = i2c_write_byte(chnd, 0x80, 0xf0);
|
|
ret |= i2c_write_byte(chnd, 0x2f, 0x20);
|
|
ret |= i2c_write_byte(chnd, 0x2e, 0x85);
|
|
ret |= i2c_read_byte(chnd, 0x30, chip_id);
|
|
|
|
if (ret < 0)
|
|
fprintf(stderr, "Failed to get id of 3rd byte.");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Fills in chnd->flash_size */
|
|
static int check_chipid(struct common_hnd *chnd)
|
|
{
|
|
int ret;
|
|
uint8_t ver = 0xff;
|
|
uint32_t id = 0xffff;
|
|
uint16_t v2[5] = {128, 192, 256, 384, 512};
|
|
/*
|
|
* Chip Version is mapping from bit 3-0
|
|
* Flash size is mapping from bit 7-4
|
|
*
|
|
* Chip Version (bit 3-0)
|
|
* 0: AX
|
|
* 1: BX
|
|
* 2: CX
|
|
* 3: DX
|
|
*
|
|
* CX before flash size (bit 7-4)
|
|
* 0:128KB
|
|
* 4:192KB
|
|
* 8:256KB
|
|
*
|
|
* DX flash size(bit 7-4)
|
|
* 0:128KB
|
|
* 2:192KB
|
|
* 4:256KB
|
|
* 6:384KB
|
|
* 8:512KB
|
|
*
|
|
* flash size(bit 7-4) of it8xxx1 or it8xxx2 series
|
|
* 0:128KB
|
|
* 2:192KB
|
|
* 4:256KB
|
|
* 6:384KB
|
|
* 8:512KB
|
|
*/
|
|
|
|
ret = i2c_read_byte(chnd, 0x00, (uint8_t *)&id + 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = i2c_read_byte(chnd, 0x01, (uint8_t *)&id);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = i2c_read_byte(chnd, 0x02, &ver);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((id & 0xff00) != (CHIP_ID & 0xff00)) {
|
|
id |= 0xff0000;
|
|
ret = get_3rd_chip_id_byte(chnd, (uint8_t *)&id+2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((id & 0xf000f) == 0x80001 || (id & 0xf000f) == 0x80002) {
|
|
chnd->flash_cmd_v2 = 1;
|
|
chnd->dbgr_addr_3bytes = 1;
|
|
} else {
|
|
fprintf(stderr, "Invalid chip id: %05x\n", id);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
chnd->dbgr_addr_3bytes = 0;
|
|
if ((ver & 0x0f) >= 0x03)
|
|
chnd->flash_cmd_v2 = 1;
|
|
else
|
|
chnd->flash_cmd_v2 = 0;
|
|
}
|
|
/* compute embedded flash size from CHIPVER field */
|
|
if (chnd->flash_cmd_v2)
|
|
chnd->flash_size = v2[(ver & 0xF0)>>5] * 1024;
|
|
else
|
|
chnd->flash_size = (128 + (ver & 0xF0)) * 1024;
|
|
|
|
printf("CHIPID %05x, CHIPVER %02x, Flash size %d kB\n", id, ver,
|
|
chnd->flash_size / 1024);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* DBGR Reset */
|
|
static int dbgr_reset(struct common_hnd *chnd, unsigned char val)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Reset CPU only, and we keep power state until flashing is done. */
|
|
if (chnd->dbgr_addr_3bytes)
|
|
ret |= i2c_write_byte(chnd, 0x80, 0xf0);
|
|
|
|
ret |= i2c_write_byte(chnd, 0x2f, 0x20);
|
|
ret |= i2c_write_byte(chnd, 0x2e, 0x06);
|
|
|
|
/* Enable the Reset Status by val */
|
|
ret |= i2c_write_byte(chnd, 0x30, val);
|
|
|
|
ret |= i2c_write_byte(chnd, 0x27, 0x80);
|
|
if (ret < 0)
|
|
fprintf(stderr, "DBGR RESET FAILED\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* disable watchdog */
|
|
static int dbgr_disable_watchdog(struct common_hnd *chnd)
|
|
{
|
|
int ret = 0;
|
|
|
|
printf("Disabling watchdog...\n");
|
|
if (chnd->dbgr_addr_3bytes)
|
|
ret |= i2c_write_byte(chnd, 0x80, 0xf0);
|
|
|
|
ret |= i2c_write_byte(chnd, 0x2f, 0x1f);
|
|
ret |= i2c_write_byte(chnd, 0x2e, 0x05);
|
|
ret |= i2c_write_byte(chnd, 0x30, 0x30);
|
|
|
|
if (ret < 0)
|
|
fprintf(stderr, "DBGR DISABLE WATCHDOG FAILED!\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* disable protect path from DBGR */
|
|
static int dbgr_disable_protect_path(struct common_hnd *chnd)
|
|
{
|
|
int ret = 0, i;
|
|
|
|
printf("Disabling protect path...\n");
|
|
|
|
if (chnd->dbgr_addr_3bytes)
|
|
ret |= i2c_write_byte(chnd, 0x80, 0xf0);
|
|
|
|
ret |= i2c_write_byte(chnd, 0x2f, 0x20);
|
|
for (i = 0; i < 32; i++) {
|
|
ret |= i2c_write_byte(chnd, 0x2e, 0xa0+i);
|
|
ret |= i2c_write_byte(chnd, 0x30, 0);
|
|
}
|
|
|
|
if (ret < 0)
|
|
fprintf(stderr, "DISABLE PROTECT PATH FROM DBGR FAILED!\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Enter follow mode and FSCE# high level */
|
|
static int spi_flash_follow_mode(struct common_hnd *chnd, char *desc)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret |= i2c_write_byte(chnd, 0x07, 0x7f);
|
|
ret |= i2c_write_byte(chnd, 0x06, 0xff);
|
|
ret |= i2c_write_byte(chnd, 0x05, 0xfe);
|
|
ret |= i2c_write_byte(chnd, 0x04, 0x00);
|
|
ret |= i2c_write_byte(chnd, 0x08, 0x00);
|
|
|
|
ret = (ret ? -EIO : 0);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Flash %s enter follow mode FAILED (%d)\n",
|
|
desc, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Exit follow mode */
|
|
static int spi_flash_follow_mode_exit(struct common_hnd *chnd, char *desc)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret |= i2c_write_byte(chnd, 0x07, 0x00);
|
|
ret |= i2c_write_byte(chnd, 0x06, 0x00);
|
|
|
|
ret = (ret ? -EIO : 0);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Flash %s exit follow mode FAILED (%d)\n",
|
|
desc, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* SPI Flash generic command, short version */
|
|
static int spi_flash_command_short(struct common_hnd *chnd,
|
|
uint8_t cmd, char *desc)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret |= i2c_write_byte(chnd, 0x05, 0xfe);
|
|
ret |= i2c_write_byte(chnd, 0x08, 0x00);
|
|
ret |= i2c_write_byte(chnd, 0x05, 0xfd);
|
|
ret |= i2c_write_byte(chnd, 0x08, cmd);
|
|
|
|
ret = (ret ? -EIO : 0);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Flash CMD %s FAILED (%d)\n", desc, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* SPI Flash set erase page */
|
|
static int spi_flash_set_erase_page(struct common_hnd *chnd,
|
|
int page, char *desc)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret |= i2c_write_byte(chnd, 0x08, page >> 8);
|
|
ret |= i2c_write_byte(chnd, 0x08, page & 0xff);
|
|
ret |= i2c_write_byte(chnd, 0x08, 0);
|
|
|
|
ret = (ret ? -EIO : 0);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Flash %s set page FAILED (%d)\n", desc, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Poll SPI Flash Read Status register until BUSY is reset */
|
|
static int spi_poll_busy(struct common_hnd *chnd, char *desc)
|
|
{
|
|
uint8_t reg = 0xff;
|
|
int ret = -EIO;
|
|
|
|
if (spi_flash_command_short(chnd, SPI_CMD_READ_STATUS,
|
|
"read status for busy bit") < 0) {
|
|
fprintf(stderr, "Flash %s wait busy cleared FAILED\n", desc);
|
|
goto failed_read_status;
|
|
}
|
|
|
|
while (1) {
|
|
if (i2c_byte_transfer(chnd, I2C_DATA_ADDR, ®, 0, 1) < 0) {
|
|
fprintf(stderr, "Flash polling busy cleared FAILED\n");
|
|
break;
|
|
}
|
|
|
|
if ((reg & 0x01) == 0) {
|
|
/* busy bit cleared */
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
failed_read_status:
|
|
return ret;
|
|
}
|
|
|
|
static int spi_check_write_enable(struct common_hnd *chnd, char *desc)
|
|
{
|
|
uint8_t reg = 0xff;
|
|
int ret = -EIO;
|
|
|
|
if (spi_flash_command_short(chnd, SPI_CMD_READ_STATUS,
|
|
"read status for write enable bit") < 0) {
|
|
fprintf(stderr, "Flash %s wait WE FAILED\n", desc);
|
|
goto failed_read_status;
|
|
}
|
|
|
|
while (1) {
|
|
if (i2c_byte_transfer(chnd, I2C_DATA_ADDR, ®, 0, 1) < 0) {
|
|
fprintf(stderr, "Flash polling WE FAILED\n");
|
|
break;
|
|
}
|
|
|
|
if ((reg & 0x03) == 2) {
|
|
/* busy bit cleared and WE bit set */
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
failed_read_status:
|
|
return ret;
|
|
}
|
|
|
|
static int ftdi_config_i2c(struct ftdi_context *ftdi)
|
|
{
|
|
int ret;
|
|
static const uint16_t divisor =
|
|
60000000 / (2 * FTDI_I2C_FREQ * 3 / 2 /* 3-phase CLK */) - 1;
|
|
uint8_t clock_buf[] = {
|
|
EN_3_PHASE,
|
|
DIS_DIV_5,
|
|
TCK_DIVISOR,
|
|
divisor & 0xff,
|
|
divisor >> 8};
|
|
|
|
ret = ftdi_set_latency_timer(ftdi, 16 /* ms */);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Cannot set latency\n");
|
|
|
|
ret = ftdi_set_bitmode(ftdi, 0, BITMODE_RESET);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Cannot reset MPSSE\n");
|
|
return -EIO;
|
|
}
|
|
ret = ftdi_set_bitmode(ftdi, 0, BITMODE_MPSSE);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Cannot enable MPSSE\n");
|
|
return -EIO;
|
|
}
|
|
|
|
ret = ftdi_usb_purge_buffers(ftdi);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Cannot purge buffers\n");
|
|
|
|
/* configure the clock */
|
|
ret = ftdi_write_data(ftdi, clock_buf, sizeof(clock_buf));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Special waveform definition */
|
|
#define SPECIAL_LEN_USEC 50000ULL /* us */
|
|
#define SPECIAL_FREQ 400000ULL
|
|
|
|
#define SPECIAL_PATTERN 0x0000020301010302ULL
|
|
#define SPECIAL_PATTERN_SDA_L_SCL_L 0x0000000000000000ULL
|
|
#define SPECIAL_PATTERN_SDA_H_SCL_L 0x0202020202020202ULL
|
|
#define SPECIAL_PATTERN_SDA_L_SCL_H 0x0101010101010101ULL
|
|
#define SPECIAL_PATTERN_SDA_H_SCL_H 0x0303030303030303ULL
|
|
#define TICK_COUNT 24
|
|
|
|
#define MSEC 1000
|
|
#define USEC 1000000
|
|
|
|
#define SPECIAL_BUFFER_SIZE \
|
|
(((SPECIAL_LEN_USEC * SPECIAL_FREQ * 2 / USEC) + 7) & ~7)
|
|
|
|
static int connect_to_ccd_i2c_bridge(struct common_hnd *chnd)
|
|
{
|
|
int rv;
|
|
|
|
rv = usb_findit(chnd->conf.usb_serial, chnd->conf.usb_vid,
|
|
chnd->conf.usb_pid, CR50_I2C_SUBCLASS,
|
|
CR50_I2C_PROTOCOL, &chnd->uep);
|
|
|
|
if (rv) {
|
|
fprintf(stderr, "%s: usb_findit returned error %d\n",
|
|
__func__, rv);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int ccd_trigger_special_waveform(struct common_hnd *chnd)
|
|
{
|
|
uint8_t response[20];
|
|
size_t rsize;
|
|
uint8_t req[] = {
|
|
0, /* Port 0. Might be necessary to modify. */
|
|
CROS_CMD_ADDR, /* Chrome OS dedicated address. */
|
|
1, /* Will send a single byte command. */
|
|
0, /* No need to read back anything. */
|
|
CROS_CMD_ITE_SYNC
|
|
};
|
|
|
|
usb_trx(&chnd->uep, req, sizeof(req), response, sizeof(response), 1,
|
|
&rsize);
|
|
|
|
if (rsize < USB_I2C_HEADER_SIZE)
|
|
return -1;
|
|
|
|
if (response[0])
|
|
return -response[0];
|
|
/*
|
|
* The target is about to get reset, let's shut down the USB
|
|
* connection.
|
|
*/
|
|
usb_shut_down(&chnd->uep);
|
|
|
|
sleep(3);
|
|
|
|
return connect_to_ccd_i2c_bridge(chnd);
|
|
}
|
|
|
|
static int ftdi_send_special_waveform(struct common_hnd *chnd)
|
|
{
|
|
int ret;
|
|
int i;
|
|
uint64_t *wave;
|
|
struct ftdi_context *ftdi = chnd->ftdi_hnd;
|
|
uint8_t release_lines[] = {SET_BITS_LOW, 0, 0};
|
|
|
|
wave = malloc(SPECIAL_BUFFER_SIZE);
|
|
if (!wave) {
|
|
fprintf(stderr, "malloc(%zu) failed\n",
|
|
(size_t)SPECIAL_BUFFER_SIZE);
|
|
return -1;
|
|
}
|
|
|
|
/* Reset the FTDI into a known state */
|
|
ret = ftdi_set_bitmode(ftdi, 0xFF, BITMODE_RESET);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to reset FTDI\n");
|
|
goto free_and_return;
|
|
}
|
|
|
|
/*
|
|
* set the clock divider, so we output a new bitbang value every
|
|
* 2.5us.
|
|
*/
|
|
ret = ftdi_set_baudrate(ftdi, 160000);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to set bitbang clock\n");
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* Enable asynchronous bit-bang mode */
|
|
ret = ftdi_set_bitmode(ftdi, 0xFF, BITMODE_BITBANG);
|
|
if (ret) {
|
|
fprintf(stderr, "failed to set bitbang mode\n");
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* do usb special waveform */
|
|
wave[0] = 0x0;
|
|
ftdi_write_data(ftdi, (uint8_t *)wave, 1);
|
|
usleep(5000);
|
|
|
|
/* program each special tick */
|
|
for (i = 0; i < TICK_COUNT; ) {
|
|
wave[i++] = SPECIAL_PATTERN_SDA_L_SCL_L;
|
|
wave[i++] = SPECIAL_PATTERN_SDA_H_SCL_L;
|
|
wave[i++] = SPECIAL_PATTERN_SDA_L_SCL_L;
|
|
}
|
|
wave[19] = SPECIAL_PATTERN_SDA_H_SCL_H;
|
|
|
|
/* fill the buffer with the waveform pattern */
|
|
for (i = TICK_COUNT; i < SPECIAL_BUFFER_SIZE / sizeof(uint64_t); i++)
|
|
wave[i] = SPECIAL_PATTERN;
|
|
|
|
ret = ftdi_write_data(ftdi, (uint8_t *)wave, SPECIAL_BUFFER_SIZE);
|
|
if (ret < 0)
|
|
fprintf(stderr, "Cannot output special waveform\n");
|
|
else
|
|
/* no error */
|
|
ret = 0;
|
|
|
|
/* clean everything to go back to regular I2C communication */
|
|
ftdi_usb_purge_buffers(ftdi);
|
|
ftdi_set_bitmode(ftdi, 0xff, BITMODE_RESET);
|
|
ftdi_config_i2c(ftdi);
|
|
ftdi_write_data(ftdi, release_lines, sizeof(release_lines));
|
|
|
|
free_and_return:
|
|
free(wave);
|
|
return ret;
|
|
}
|
|
|
|
static int send_special_waveform(struct common_hnd *chnd)
|
|
{
|
|
const int max_iterations = 10;
|
|
int ret;
|
|
int iterations;
|
|
|
|
if (!chnd->conf.i2c_if->send_special_waveform) {
|
|
fprintf(stderr, "This binary does not support sending the ITE "
|
|
"special waveform with the chosen I2C interface.\n");
|
|
return -1;
|
|
}
|
|
|
|
iterations = 0;
|
|
|
|
do {
|
|
ret = chnd->conf.i2c_if->send_special_waveform(chnd);
|
|
if (ret)
|
|
break;
|
|
|
|
/* wait for PLL stable for 5ms (plus remaining USB transfers) */
|
|
usleep(10 * MSEC);
|
|
|
|
if (spi_flash_follow_mode(chnd, "enter follow mode") >= 0) {
|
|
spi_flash_follow_mode_exit(chnd, "exit follow mode");
|
|
/*
|
|
* If we can talk to chip, then we can break the retry
|
|
* loop.
|
|
*/
|
|
ret = check_chipid(chnd);
|
|
} else {
|
|
ret = -1;
|
|
if (!(iterations % max_iterations))
|
|
fprintf(stderr, "!please reset EC if flashing"
|
|
" sequence is not starting!\n");
|
|
}
|
|
} while (ret && (iterations++ < max_iterations));
|
|
|
|
if (ret)
|
|
fprintf(stderr, "Failed to send special waveform!\n");
|
|
else
|
|
printf("Done with sending special waveform.\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int windex;
|
|
static const char wheel[] = {'|', '/', '-', '\\' };
|
|
static void draw_spinner(uint32_t remaining, uint32_t size)
|
|
{
|
|
int percent = (size - remaining)*100/size;
|
|
fprintf(stderr, "\r%c%3d%%", wheel[windex++], percent);
|
|
windex %= sizeof(wheel);
|
|
}
|
|
|
|
static int command_read_pages(struct common_hnd *chnd, uint32_t address,
|
|
uint32_t size, uint8_t *buffer)
|
|
{
|
|
int res = -EIO;
|
|
uint32_t remaining = size;
|
|
int cnt;
|
|
uint8_t addr_H, addr_M;
|
|
|
|
if (address & 0xFF) {
|
|
fprintf(stderr, "page read requested at non-page boundary: "
|
|
"0x%X\n", address);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spi_flash_follow_mode(chnd, "fast read") < 0)
|
|
goto failed_read;
|
|
|
|
while (remaining) {
|
|
uint8_t cmd = 0x9;
|
|
|
|
cnt = (remaining > PAGE_SIZE) ? PAGE_SIZE : remaining;
|
|
addr_H = (address >> 16) & 0xFF;
|
|
addr_M = (address >> 8) & 0xFF;
|
|
|
|
draw_spinner(remaining, size);
|
|
|
|
/* Fast Read command */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_FAST_READ,
|
|
"fast read") < 0)
|
|
goto failed_read;
|
|
res = i2c_write_byte(chnd, 0x08, addr_H);
|
|
res += i2c_write_byte(chnd, 0x08, addr_M);
|
|
res += i2c_write_byte(chnd, 0x08, 0x00); /* addr_L */
|
|
res += i2c_write_byte(chnd, 0x08, 0x00);
|
|
if (res < 0) {
|
|
fprintf(stderr, "page address set failed\n");
|
|
goto failed_read;
|
|
}
|
|
|
|
/* read page data */
|
|
res = i2c_byte_transfer(chnd, I2C_CMD_ADDR, &cmd, 1, 1);
|
|
res = i2c_byte_transfer(chnd, I2C_BLOCK_ADDR, buffer, 0, cnt);
|
|
if (res < 0) {
|
|
fprintf(stderr, "page data read failed\n");
|
|
goto failed_read;
|
|
}
|
|
|
|
address += cnt;
|
|
remaining -= cnt;
|
|
buffer += cnt;
|
|
}
|
|
/* No error so far */
|
|
res = size;
|
|
failed_read:
|
|
if (spi_flash_follow_mode_exit(chnd, "fast read") < 0)
|
|
res = -EIO;
|
|
|
|
return res;
|
|
}
|
|
|
|
static int command_write_pages(struct common_hnd *chnd, uint32_t address,
|
|
uint32_t size, uint8_t *buffer)
|
|
{
|
|
int res = -EIO;
|
|
int block_write_size = chnd->conf.block_write_size;
|
|
uint32_t remaining = size;
|
|
int cnt;
|
|
uint8_t addr_H, addr_M, addr_L;
|
|
uint8_t data;
|
|
|
|
if (spi_flash_follow_mode(chnd, "AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
while (remaining) {
|
|
cnt = (remaining > block_write_size) ?
|
|
block_write_size : remaining;
|
|
addr_H = (address >> 16) & 0xFF;
|
|
addr_M = (address >> 8) & 0xFF;
|
|
addr_L = address & 0xFF;
|
|
|
|
draw_spinner(remaining, size);
|
|
|
|
/* Write enable */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_ENABLE,
|
|
"write enable for AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
/* Check write enable bit */
|
|
if (spi_check_write_enable(chnd, "AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
/* Setup write */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WORD_PROGRAM,
|
|
"AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
/* Set eflash page address */
|
|
res = i2c_byte_transfer(chnd, I2C_DATA_ADDR, &addr_H, 1, 1);
|
|
res |= i2c_byte_transfer(chnd, I2C_DATA_ADDR, &addr_M, 1, 1);
|
|
res |= i2c_byte_transfer(chnd, I2C_DATA_ADDR, &addr_L, 1, 1);
|
|
if (res < 0) {
|
|
fprintf(stderr, "Flash write set page FAILED (%d)\n",
|
|
res);
|
|
goto failed_write;
|
|
}
|
|
|
|
/* Wait until not busy */
|
|
if (spi_poll_busy(chnd, "AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
/* Write up to block_write_size data */
|
|
res = i2c_write_byte(chnd, 0x10, 0x20);
|
|
res = i2c_byte_transfer(chnd, I2C_BLOCK_ADDR, buffer, 1, cnt);
|
|
buffer += cnt;
|
|
|
|
if (res < 0) {
|
|
fprintf(stderr, "Flash data write failed\n");
|
|
goto failed_write;
|
|
}
|
|
|
|
data = 0xFF;
|
|
res = i2c_byte_transfer(chnd, I2C_DATA_ADDR, &data, 1, 1);
|
|
res |= i2c_write_byte(chnd, 0x10, 0x00);
|
|
if (res < 0) {
|
|
fprintf(stderr, "Flash end data write FAILED (%d)\n",
|
|
res);
|
|
goto failed_write;
|
|
}
|
|
|
|
/* Write disable */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable for AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
/* Wait until available */
|
|
if (spi_poll_busy(chnd, "write disable for AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
address += cnt;
|
|
remaining -= cnt;
|
|
}
|
|
draw_spinner(remaining, size);
|
|
/* No error so far */
|
|
res = size;
|
|
failed_write:
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable exit AAI write") < 0)
|
|
res = -EIO;
|
|
|
|
if (spi_flash_follow_mode_exit(chnd, "AAI write") < 0)
|
|
res = -EIO;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Write another program flow to match the
|
|
* original ITE 8903 Download board.
|
|
*/
|
|
static int command_write_pages2(struct common_hnd *chnd, uint32_t address,
|
|
uint32_t size, uint8_t *buffer,
|
|
int block_write_size)
|
|
{
|
|
int res = 0;
|
|
uint8_t addr_H, addr_M, addr_L;
|
|
uint8_t data;
|
|
|
|
res |= i2c_write_byte(chnd, 0x07, 0x7f);
|
|
res |= i2c_write_byte(chnd, 0x06, 0xff);
|
|
res |= i2c_write_byte(chnd, 0x04, 0xFF);
|
|
|
|
/* SMB_SPI_Flash_Enable_Write_Status */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_EWSR,
|
|
"Enable Write Status Register") < 0) {
|
|
res = -EIO;
|
|
goto failed_write;
|
|
}
|
|
|
|
/* SMB_SPI_Flash_Write_Status_Reg */
|
|
res |= i2c_write_byte(chnd, 0x05, 0xfe);
|
|
res |= i2c_write_byte(chnd, 0x08, 0x00);
|
|
res |= i2c_write_byte(chnd, 0x05, 0xfd);
|
|
res |= i2c_write_byte(chnd, 0x08, 0x01);
|
|
res |= i2c_write_byte(chnd, 0x08, 0x00);
|
|
|
|
/* SMB_SPI_Flash_Write_Enable */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_ENABLE,
|
|
"SPI Command Write Enable") < 0) {
|
|
res = -EIO;
|
|
goto failed_write;
|
|
}
|
|
|
|
/* SMB_SST_SPI_Flash_AAI2_Program */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WORD_PROGRAM,
|
|
"SPI AAI2 Program") < 0) {
|
|
res = -EIO;
|
|
goto failed_write;
|
|
}
|
|
|
|
addr_H = (address >> 16) & 0xFF;
|
|
addr_M = (address >> 8) & 0xFF;
|
|
addr_L = address & 0xFF;
|
|
|
|
res = i2c_byte_transfer(chnd, I2C_DATA_ADDR, &addr_H, 1, 1);
|
|
res |= i2c_byte_transfer(chnd, I2C_DATA_ADDR, &addr_M, 1, 1);
|
|
res |= i2c_byte_transfer(chnd, I2C_DATA_ADDR, &addr_L, 1, 1);
|
|
res |= i2c_byte_transfer(chnd, I2C_DATA_ADDR, buffer++, 1, 1);
|
|
res |= i2c_byte_transfer(chnd, I2C_DATA_ADDR, buffer++, 1, 1);
|
|
|
|
/* Wait until not busy */
|
|
if (spi_poll_busy(chnd, "AAI write") < 0)
|
|
goto failed_write;
|
|
|
|
res = i2c_write_byte(chnd, 0x10, 0x20);
|
|
res = i2c_byte_transfer(chnd, I2C_BLOCK_ADDR, buffer, 1,
|
|
block_write_size-2);
|
|
|
|
/* No error so far */
|
|
res = size;
|
|
|
|
data = 0xFF;
|
|
res = i2c_byte_transfer(chnd, I2C_DATA_ADDR, &data, 1, 1);
|
|
res = i2c_write_byte(chnd, 0x10, 0x00);
|
|
|
|
failed_write:
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable exit AAI write") < 0)
|
|
res = -EIO;
|
|
|
|
return res;
|
|
}
|
|
|
|
static int command_erase(struct common_hnd *chnd, uint32_t len, uint32_t off)
|
|
{
|
|
int res = -EIO;
|
|
int page = 0;
|
|
uint32_t remaining = len;
|
|
|
|
printf("Erasing chip...\n");
|
|
|
|
if (off != 0 || len != chnd->flash_size) {
|
|
fprintf(stderr, "Only full chip erase is supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spi_flash_follow_mode(chnd, "erase") < 0)
|
|
goto failed_erase;
|
|
|
|
while (remaining) {
|
|
draw_spinner(remaining, len);
|
|
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_ENABLE,
|
|
"write enable for erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_check_write_enable(chnd, "erase") < 0)
|
|
goto failed_erase;
|
|
|
|
/* do chip erase */
|
|
if (remaining == chnd->flash_size) {
|
|
if (spi_flash_command_short(chnd, SPI_CMD_CHIP_ERASE,
|
|
"chip erase") < 0)
|
|
goto failed_erase;
|
|
goto wait_busy_cleared;
|
|
}
|
|
|
|
/* do sector erase */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_SECTOR_ERASE,
|
|
"sector erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_flash_set_erase_page(chnd, page, "sector erase") < 0)
|
|
goto failed_erase;
|
|
|
|
wait_busy_cleared:
|
|
if (spi_poll_busy(chnd, "erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable for erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (remaining == chnd->flash_size) {
|
|
remaining = 0;
|
|
draw_spinner(remaining, len);
|
|
} else {
|
|
page += SECTOR_ERASE_PAGES;
|
|
remaining -= SECTOR_ERASE_PAGES * PAGE_SIZE;
|
|
}
|
|
}
|
|
|
|
/* No error so far */
|
|
printf("\n\rErasing Done.\n");
|
|
res = 0;
|
|
|
|
failed_erase:
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable exit erase") < 0)
|
|
res = -EIO;
|
|
|
|
if (spi_flash_follow_mode_exit(chnd, "erase") < 0)
|
|
res = -EIO;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* This function can Erase First Sector or Erase All Sector by reset value
|
|
* Some F/W will produce the H/W watchdog reset and it will happen
|
|
* reset issue while flash.
|
|
* Add such function to prevent the reset issue.
|
|
*/
|
|
static int command_erase2(struct common_hnd *chnd, uint32_t len,
|
|
uint32_t off, uint32_t reset)
|
|
{
|
|
int res = -EIO;
|
|
int page = 0;
|
|
uint32_t remaining = len;
|
|
|
|
/*
|
|
* TODOD(b/<>):
|
|
* Using sector erase instead of chip erase
|
|
* For some new chip , the chip erase may not work
|
|
* well on the original flow
|
|
*/
|
|
|
|
printf("Erasing flash...erase size=%d\n", len);
|
|
|
|
if (off != 0 || len != chnd->flash_size) {
|
|
fprintf(stderr, "Only full chip erase is supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spi_flash_follow_mode(chnd, "erase") < 0)
|
|
goto failed_erase;
|
|
|
|
while (remaining) {
|
|
|
|
draw_spinner(remaining, len);
|
|
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_ENABLE,
|
|
"write enable for erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_check_write_enable(chnd, "erase") < 0)
|
|
goto failed_erase;
|
|
|
|
/* do sector erase */
|
|
if (spi_flash_command_short(chnd, SPI_CMD_SECTOR_ERASE,
|
|
"sector erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_flash_set_erase_page(chnd, page, "sector erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_poll_busy(chnd, "erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable for erase") < 0)
|
|
goto failed_erase;
|
|
|
|
if (reset) {
|
|
printf("\n\rreset to prevent the watchdog reset...\n");
|
|
break;
|
|
}
|
|
|
|
page += SECTOR_ERASE_PAGES;
|
|
remaining -= SECTOR_ERASE_PAGES * PAGE_SIZE;
|
|
draw_spinner(remaining, len);
|
|
|
|
}
|
|
|
|
/* No error so far */
|
|
printf("\n\rErasing Done.\n");
|
|
res = 0;
|
|
|
|
failed_erase:
|
|
if (spi_flash_command_short(chnd, SPI_CMD_WRITE_DISABLE,
|
|
"write disable exit erase") < 0)
|
|
res = -EIO;
|
|
|
|
if (spi_flash_follow_mode_exit(chnd, "erase") < 0)
|
|
res = -EIO;
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Return zero on success, a negative error value on failures. */
|
|
static int read_flash(struct common_hnd *chnd)
|
|
{
|
|
int res;
|
|
FILE *hnd;
|
|
uint8_t *buffer;
|
|
const char *filename = chnd->conf.input_filename;
|
|
size_t offset = chnd->conf.range_base;
|
|
size_t size;
|
|
|
|
if (!offset && !chnd->conf.range_size) {
|
|
size = chnd->flash_size;
|
|
} else {
|
|
/*
|
|
* Zero conf.range_size means the user did not enter range
|
|
* size in the command line.
|
|
*/
|
|
if (chnd->conf.range_size)
|
|
size = chnd->conf.range_size;
|
|
else
|
|
size = chnd->flash_size - offset;
|
|
|
|
if (!size) {
|
|
fprintf(stderr,
|
|
"Error: not reading a zero sized range!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((size + offset) > chnd->flash_size) {
|
|
fprintf(stderr,
|
|
"Error: Read range exceeds flash size!\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
buffer = malloc(size);
|
|
if (!buffer) {
|
|
fprintf(stderr, "Cannot allocate %zd bytes\n", size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hnd = fopen(filename, "w");
|
|
if (!hnd) {
|
|
fprintf(stderr, "Cannot open file %s for writing\n", filename);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
|
|
printf("Reading %zd bytes at %#08zx\n", size, offset);
|
|
res = command_read_pages(chnd, offset, size, buffer);
|
|
if (res > 0) {
|
|
if (fwrite(buffer, res, 1, hnd) != 1)
|
|
fprintf(stderr, "Cannot write %s\n", filename);
|
|
}
|
|
printf("\r %d bytes read.\n", res);
|
|
|
|
fclose(hnd);
|
|
free(buffer);
|
|
return (res < 0) ? res : 0;
|
|
}
|
|
|
|
/* Return zero on success, a negative error value on failures. */
|
|
static int write_flash(struct common_hnd *chnd, const char *filename,
|
|
uint32_t offset)
|
|
{
|
|
int res, written;
|
|
FILE *hnd;
|
|
int size = chnd->flash_size;
|
|
uint8_t *buffer = malloc(size);
|
|
|
|
if (!buffer) {
|
|
fprintf(stderr, "%s: Cannot allocate %d bytes\n", __func__,
|
|
size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hnd = fopen(filename, "r");
|
|
if (!hnd) {
|
|
fprintf(stderr, "%s: Cannot open file %s for reading\n",
|
|
__func__, filename);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
res = fread(buffer, 1, size, hnd);
|
|
if (res <= 0) {
|
|
fprintf(stderr, "%s: Failed to read %d bytes from %s with "
|
|
"ferror() %d\n", __func__, size, filename, ferror(hnd));
|
|
free(buffer);
|
|
fclose(hnd);
|
|
return -EIO;
|
|
}
|
|
fclose(hnd);
|
|
|
|
printf("Writing %d bytes at 0x%08x\n", res, offset);
|
|
written = command_write_pages(chnd, offset, res, buffer);
|
|
if (written != res) {
|
|
fprintf(stderr, "%s: Error writing to flash\n", __func__);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
printf("\n\rWriting Done.\n");
|
|
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Return zero on success, a negative error value on failures.
|
|
*
|
|
* Change the program command to match the ITE Download
|
|
* The original flow may not work on the DX chip.
|
|
*
|
|
*/
|
|
static int write_flash2(struct common_hnd *chnd, const char *filename,
|
|
uint32_t offset)
|
|
{
|
|
int res, written;
|
|
int block_write_size = chnd->conf.block_write_size;
|
|
FILE *hnd;
|
|
int size = chnd->flash_size;
|
|
int cnt;
|
|
uint8_t *buffer = malloc(size);
|
|
|
|
if (!buffer) {
|
|
fprintf(stderr, "%s: Cannot allocate %d bytes\n", __func__,
|
|
size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hnd = fopen(filename, "r");
|
|
if (!hnd) {
|
|
fprintf(stderr, "%s: Cannot open file %s for reading\n",
|
|
__func__, filename);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
res = fread(buffer, 1, size, hnd);
|
|
if (res <= 0) {
|
|
fprintf(stderr, "%s: Failed to read %d bytes from %s with "
|
|
"ferror() %d\n", __func__, size, filename, ferror(hnd));
|
|
fclose(hnd);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
fclose(hnd);
|
|
|
|
offset = 0;
|
|
printf("Writing %d bytes at 0x%08x.......\n", res, offset);
|
|
while (res) {
|
|
cnt = (res > block_write_size) ? block_write_size : res;
|
|
written = command_write_pages2(chnd, offset, cnt,
|
|
&buffer[offset], block_write_size);
|
|
if (written == -EIO)
|
|
goto failed_write;
|
|
|
|
res -= cnt;
|
|
offset += cnt;
|
|
draw_spinner(res, res + offset);
|
|
}
|
|
|
|
if (written != res) {
|
|
failed_write:
|
|
fprintf(stderr, "%s: Error writing to flash\n", __func__);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
printf("\n\rWriting Done.\n");
|
|
free(buffer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Return zero on success, a non-zero value on failures. */
|
|
static int verify_flash(struct common_hnd *chnd, const char *filename,
|
|
uint32_t offset)
|
|
{
|
|
int res;
|
|
int file_size;
|
|
FILE *hnd;
|
|
uint8_t *buffer = malloc(chnd->flash_size);
|
|
uint8_t *buffer2 = malloc(chnd->flash_size);
|
|
|
|
if (!buffer || !buffer2) {
|
|
fprintf(stderr, "%s: Cannot allocate %d bytes\n", __func__,
|
|
chnd->flash_size);
|
|
free(buffer);
|
|
free(buffer2);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hnd = fopen(filename, "r");
|
|
if (!hnd) {
|
|
fprintf(stderr, "%s: Cannot open file %s for reading\n",
|
|
__func__, filename);
|
|
res = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
file_size = fread(buffer, 1, chnd->flash_size, hnd);
|
|
if (file_size <= 0) {
|
|
fprintf(stderr, "%s: Failed to read %d bytes from %s with "
|
|
"ferror() %d\n", __func__, chnd->flash_size, filename,
|
|
ferror(hnd));
|
|
fclose(hnd);
|
|
res = -EIO;
|
|
goto exit;
|
|
}
|
|
fclose(hnd);
|
|
|
|
printf("Verify %d bytes at 0x%08x\n", file_size, offset);
|
|
res = command_read_pages(chnd, offset, chnd->flash_size, buffer2);
|
|
if (res > 0)
|
|
res = memcmp(buffer, buffer2, file_size);
|
|
|
|
printf("\n\rVerify %s\n", res ? "Failed!" : "Done.");
|
|
|
|
exit:
|
|
free(buffer);
|
|
free(buffer2);
|
|
return res;
|
|
}
|
|
|
|
static struct ftdi_context *open_ftdi_device(int vid, int pid,
|
|
int interface, const char *serial)
|
|
{
|
|
struct ftdi_context *ftdi;
|
|
int ret;
|
|
|
|
ftdi = ftdi_new();
|
|
if (!ftdi) {
|
|
fprintf(stderr, "Cannot allocate context memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
ret = ftdi_set_interface(ftdi, interface);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "cannot set ftdi interface %d: %s(%d)\n",
|
|
interface, ftdi_get_error_string(ftdi), ret);
|
|
goto open_failed;
|
|
}
|
|
ret = ftdi_usb_open_desc(ftdi, vid, pid, NULL, serial);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "unable to open ftdi device: %s(%d)\n",
|
|
ftdi_get_error_string(ftdi), ret);
|
|
goto open_failed;
|
|
}
|
|
return ftdi;
|
|
|
|
open_failed:
|
|
ftdi_free(ftdi);
|
|
return NULL;
|
|
}
|
|
|
|
static int linux_i2c_interface_init(struct common_hnd *chnd)
|
|
{
|
|
int err;
|
|
|
|
if (!chnd->conf.i2c_dev_path) {
|
|
fprintf(stderr, "Must set --i2c_dev_path when using "
|
|
"Linux i2c-dev interface.\n");
|
|
return -1;
|
|
}
|
|
printf("Attempting to open Linux i2c-dev path %s\n",
|
|
chnd->conf.i2c_dev_path);
|
|
chnd->i2c_dev_fd = open(chnd->conf.i2c_dev_path, O_RDWR);
|
|
if (chnd->i2c_dev_fd < 0) {
|
|
err = errno;
|
|
perror("Failed to open Linux i2c-dev file path with error");
|
|
fprintf(stderr, "Linux i2c-dev file path from --i2c_dev_path "
|
|
"is: %s\n", chnd->conf.i2c_dev_path);
|
|
return err ? err : -1;
|
|
}
|
|
printf("Successfully opened Linux i2c-dev path %s\n",
|
|
chnd->conf.i2c_dev_path);
|
|
return 0;
|
|
}
|
|
|
|
static int linux_i2c_interface_shutdown(struct common_hnd *chnd)
|
|
{
|
|
int err;
|
|
|
|
printf("Attempting to close Linux i2c-dev file descriptor %d\n",
|
|
chnd->i2c_dev_fd);
|
|
if (close(chnd->i2c_dev_fd)) {
|
|
err = errno;
|
|
perror("Failed to close Linux i2c-dev file descriptor with "
|
|
"error");
|
|
return err ? err : -1;
|
|
}
|
|
printf("Successfully closed Linux i2c-dev file descriptor %d\n",
|
|
chnd->i2c_dev_fd);
|
|
return 0;
|
|
}
|
|
|
|
static int ccd_i2c_interface_init(struct common_hnd *chnd)
|
|
{
|
|
chnd->conf.usb_vid = CR50_USB_VID;
|
|
chnd->conf.usb_pid = CR50_USB_PID;
|
|
return connect_to_ccd_i2c_bridge(chnd);
|
|
}
|
|
|
|
static int ccd_i2c_interface_shutdown(struct common_hnd *chnd)
|
|
{
|
|
usb_shut_down(&chnd->uep);
|
|
return 0;
|
|
}
|
|
|
|
static int ftdi_i2c_interface_init(struct common_hnd *chnd)
|
|
{
|
|
chnd->ftdi_hnd = open_ftdi_device(chnd->conf.usb_vid,
|
|
chnd->conf.usb_pid, chnd->conf.usb_interface,
|
|
chnd->conf.usb_serial);
|
|
if (chnd->ftdi_hnd == NULL)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int ftdi_i2c_interface_post_waveform(struct common_hnd *chnd)
|
|
{
|
|
return chnd->conf.send_waveform ? 0 : ftdi_config_i2c(chnd->ftdi_hnd);
|
|
}
|
|
|
|
/* Close the FTDI USB handle */
|
|
static int ftdi_i2c_interface_shutdown(struct common_hnd *chnd)
|
|
{
|
|
ftdi_usb_close(chnd->ftdi_hnd);
|
|
ftdi_free(chnd->ftdi_hnd);
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_interface linux_i2c_interface = {
|
|
.interface_init = linux_i2c_interface_init,
|
|
.interface_shutdown = linux_i2c_interface_shutdown,
|
|
.byte_transfer = linux_i2c_byte_transfer,
|
|
/*
|
|
* 254 bytes is the largest size that works with Servo Micro as of
|
|
* 2018-11-30. Odd numbers up to 255 result in corruption, and 256 or
|
|
* greater fails with a timeout from the I2C bus. Fixing that so this
|
|
* can be increased to match FTDI_BLOCK_WRITE_SIZE would be a useful
|
|
* speedup.
|
|
*
|
|
* 254 byte block sizes cause corruption with Ampton (using any kind of
|
|
* servo). 128 bytes is the largest block_write_size compatible with
|
|
* both Ampton and Servo Micro.
|
|
*
|
|
* See https://issuetracker.google.com/79684405 for background.
|
|
*/
|
|
.default_block_write_size = 128,
|
|
};
|
|
|
|
static const struct i2c_interface ccd_i2c_interface = {
|
|
.interface_init = ccd_i2c_interface_init,
|
|
.interface_shutdown = ccd_i2c_interface_shutdown,
|
|
.send_special_waveform = ccd_trigger_special_waveform,
|
|
.byte_transfer = ccd_i2c_byte_transfer,
|
|
.default_block_write_size = PAGE_SIZE,
|
|
};
|
|
|
|
static const struct i2c_interface ftdi_i2c_interface = {
|
|
.interface_init = ftdi_i2c_interface_init,
|
|
.interface_post_waveform = ftdi_i2c_interface_post_waveform,
|
|
.interface_shutdown = ftdi_i2c_interface_shutdown,
|
|
.send_special_waveform = ftdi_send_special_waveform,
|
|
.byte_transfer = ftdi_i2c_byte_transfer,
|
|
.default_block_write_size = FTDI_BLOCK_WRITE_SIZE,
|
|
};
|
|
|
|
static int post_waveform_work(struct common_hnd *chnd)
|
|
{
|
|
int ret;
|
|
|
|
if (chnd->conf.i2c_if->interface_post_waveform) {
|
|
ret = chnd->conf.i2c_if->interface_post_waveform(chnd);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (chnd->conf.disable_watchdog) {
|
|
ret = dbgr_disable_watchdog(chnd);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (chnd->conf.disable_protect_path) {
|
|
ret = dbgr_disable_protect_path(chnd);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int strdup_with_errmsg(const char *source, char **dest, const char *name)
|
|
{
|
|
int ret = 0;
|
|
*dest = strdup(source);
|
|
if (!(*dest)) {
|
|
ret = errno ? errno : -1;
|
|
fprintf(stderr, "strdup() of %zu size string from %s failed.\n",
|
|
strlen(source), name);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const struct option longopts[] = {
|
|
{"block-write-size", 1, 0, 'b'},
|
|
{"debug", 0, 0, 'd'},
|
|
{"erase", 0, 0, 'e'},
|
|
{"help", 0, 0, 'h'},
|
|
{"i2c-dev-path", 1, 0, 'D'},
|
|
{"i2c-interface", 1, 0, 'c'},
|
|
{"i2c-mux", 0, 0, 'm'},
|
|
{"interface", 1, 0, 'i'},
|
|
{"nodisable-protect-path", 0, 0, 'Z'},
|
|
{"nodisable-watchdog", 0, 0, 'z'},
|
|
{"product", 1, 0, 'p'},
|
|
{"range", 1, 0, 'R'},
|
|
{"read", 1, 0, 'r'},
|
|
{"send-waveform", 1, 0, 'W'},
|
|
{"serial", 1, 0, 's'},
|
|
{"vendor", 1, 0, 'v'},
|
|
{"write", 1, 0, 'w'},
|
|
{NULL, 0, 0, 0}
|
|
};
|
|
|
|
static void display_usage(const char *program)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-d] [-v <VID>] [-p <PID>] \\\n"
|
|
"\t[-c <linux|ccd|ftdi>] [-D /dev/i2c-<N>] [-i <1|2>] [-S] \\\n"
|
|
"\t[-s <serial>] [-e] [-r <file>] [-W <0|1|false|true>] \\\n"
|
|
"\t[-w <file>] [-R base[:size]] [-m] [-b <size>]\n",
|
|
program);
|
|
fprintf(stderr, "-d, --debug : Output debug traces.\n");
|
|
fprintf(stderr, "-e, --erase : Erase all the flash content.\n");
|
|
fprintf(stderr, "-c, --i2c-interface <linux|ccd|ftdi> : I2C interface "
|
|
"to use\n");
|
|
fprintf(stderr, "-D, --i2c-dev-path /dev/i2c-<N> : Path to "
|
|
"Linux i2c-dev file e.g. /dev/i2c-5;\n"
|
|
"\tonly applicable with --i2c-interface=linux\n");
|
|
fprintf(stderr, "-i, --interface <1> : FTDI interface: A=1, B=2,"
|
|
" ...\n");
|
|
fprintf(stderr, "-m, --i2c-mux : Enable i2c-mux (to EC).\n"
|
|
"\tSpecify this flag only if the board has an I2C MUX and\n"
|
|
"\tyou are not using servod.\n");
|
|
fprintf(stderr, "-b, --block-write-size <size> : Perform writes in\n"
|
|
"\tblocks of this many bytes.\n");
|
|
fprintf(stderr, "-p, --product <0x1234> : USB product ID\n");
|
|
fprintf(stderr, "-R, --range base[:size] : Allow to read or write"
|
|
" just a slice\n"
|
|
"\tof the file, starting at <base>:<size> bytes, or til\n"
|
|
"\tthe end of the file if <size> is not specified, expressed\n"
|
|
"\tin hex.\n");
|
|
fprintf(stderr, "-r, --read <file> : Read the flash content and"
|
|
" write it into <file>.\n");
|
|
fprintf(stderr, "-s, --serial <serialname> : USB serial string\n");
|
|
fprintf(stderr, "-v, --vendor <0x1234> : USB vendor ID\n");
|
|
fprintf(stderr, "-W, --send-waveform <0|1|false|true> : Send the"
|
|
" special waveform.\n"
|
|
"\tDefault is true. Set to false if ITE direct firmware\n"
|
|
"\tupdate mode has already been enabled.\n");
|
|
fprintf(stderr, "-w, --write <file> : Write <file> to flash.\n");
|
|
fprintf(stderr, "-z, --nodisable-watchdog : Do *not* disable EC "
|
|
"watchdog.\n");
|
|
fprintf(stderr, "-Z, --nodisable-protect-path : Do *not* disable EC "
|
|
"protect path.\n");
|
|
}
|
|
|
|
/*
|
|
* Parses -R command line option parameter, returns zero on success and
|
|
* -1 on errors (non hex values, missing values, etc.).
|
|
*/
|
|
static int parse_range_options(char *str, struct iteflash_config *conf)
|
|
{
|
|
char *size;
|
|
|
|
if (!str) {
|
|
fprintf(stderr, "missing range base address specification\n");
|
|
return -1;
|
|
}
|
|
|
|
conf->range_base = strtoul(str, &size, 16);
|
|
if (!size || !*size)
|
|
return 0;
|
|
|
|
if (*size++ != ':') {
|
|
fprintf(stderr, "wrong range base address specification\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!*size) {
|
|
fprintf(stderr, "missing range size specification\n");
|
|
return -1;
|
|
}
|
|
|
|
conf->range_size = strtoul(size, &size, 16);
|
|
if ((size && *size) || !conf->range_size) {
|
|
fprintf(stderr, "wrong range size specification\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_parameters(int argc, char **argv, struct iteflash_config *conf)
|
|
{
|
|
int opt, idx, ret = 0;
|
|
|
|
while (!ret &&
|
|
(opt = getopt_long(argc, argv, "?b:c:D:dehi:mp:R:r:s:uv:W:w:Zz",
|
|
longopts, &idx)) != -1) {
|
|
switch (opt) {
|
|
case 'b':
|
|
conf->block_write_size = strtol(optarg, NULL, 10);
|
|
break;
|
|
case 'c':
|
|
if (!strcasecmp(optarg, "linux")) {
|
|
conf->i2c_if = &linux_i2c_interface;
|
|
} else if (!strcasecmp(optarg, "ccd")) {
|
|
conf->i2c_if = &ccd_i2c_interface;
|
|
} else if (!strcasecmp(optarg, "ftdi")) {
|
|
conf->i2c_if = &ftdi_i2c_interface;
|
|
} else {
|
|
fprintf(stderr, "Unexpected -c / "
|
|
"--i2c-interface value: %s\n", optarg);
|
|
ret = -1;
|
|
}
|
|
break;
|
|
case 'D':
|
|
ret = strdup_with_errmsg(optarg, &conf->i2c_dev_path,
|
|
"-D / --i2c-dev-path");
|
|
break;
|
|
case 'd':
|
|
conf->debug = 1;
|
|
break;
|
|
case 'e':
|
|
conf->erase = 1;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
display_usage(argv[0]);
|
|
ret = 2;
|
|
break;
|
|
case 'i':
|
|
conf->usb_interface = strtol(optarg, NULL, 10);
|
|
break;
|
|
case 'm':
|
|
conf->i2c_mux = 1;
|
|
break;
|
|
case 'p':
|
|
conf->usb_pid = strtol(optarg, NULL, 16);
|
|
break;
|
|
case 'R':
|
|
ret = parse_range_options(optarg, conf);
|
|
break;
|
|
case 'r':
|
|
ret = strdup_with_errmsg(optarg, &conf->input_filename,
|
|
"-r / --read");
|
|
break;
|
|
case 's':
|
|
ret = strdup_with_errmsg(optarg, &conf->usb_serial,
|
|
"-s / --serial");
|
|
break;
|
|
case 'v':
|
|
conf->usb_vid = strtol(optarg, NULL, 16);
|
|
break;
|
|
case 'W':
|
|
if (!strcmp(optarg, "0") ||
|
|
!strcasecmp(optarg, "false")) {
|
|
conf->send_waveform = 0;
|
|
break;
|
|
}
|
|
if (!strcmp(optarg, "1") ||
|
|
!strcasecmp(optarg, "true")) {
|
|
conf->send_waveform = 1;
|
|
break;
|
|
}
|
|
fprintf(stderr, "Unexpected -W / --special-waveform "
|
|
"value: %s\n", optarg);
|
|
ret = -1;
|
|
break;
|
|
case 'w':
|
|
ret = strdup_with_errmsg(optarg, &conf->output_filename,
|
|
"-w / --write");
|
|
break;
|
|
case 'z':
|
|
conf->disable_watchdog = 0;
|
|
break;
|
|
case 'Z':
|
|
conf->disable_protect_path = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
config_release(conf);
|
|
return ret;
|
|
}
|
|
|
|
static void sighandler(int signum)
|
|
{
|
|
printf("\nCaught signal %d: %s\nExiting...\n",
|
|
signum, sys_siglist[signum]);
|
|
exit_requested = 1;
|
|
}
|
|
|
|
static void register_sigaction(void)
|
|
{
|
|
struct sigaction sigact;
|
|
|
|
sigact.sa_handler = sighandler;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigact.sa_flags = 0;
|
|
sigaction(SIGINT, &sigact, NULL);
|
|
sigaction(SIGTERM, &sigact, NULL);
|
|
sigaction(SIGQUIT, &sigact, NULL);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ret = 1, other_ret;
|
|
struct common_hnd chnd = {
|
|
/* Default flag settings. */
|
|
.conf = {
|
|
.send_waveform = 1,
|
|
.disable_watchdog = 1,
|
|
.disable_protect_path = 1,
|
|
.usb_interface = SERVO_INTERFACE,
|
|
.usb_vid = SERVO_USB_VID,
|
|
.usb_pid = SERVO_USB_PID,
|
|
.i2c_if = &ftdi_i2c_interface,
|
|
},
|
|
};
|
|
|
|
/* Parse command line options */
|
|
other_ret = parse_parameters(argc, argv, &chnd.conf);
|
|
if (other_ret)
|
|
return other_ret;
|
|
|
|
/* Fill in block_write_size if not set from command line. */
|
|
if (!chnd.conf.block_write_size)
|
|
chnd.conf.block_write_size =
|
|
chnd.conf.i2c_if->default_block_write_size;
|
|
|
|
/* Open the communications channel. */
|
|
if (chnd.conf.i2c_if->interface_init &&
|
|
chnd.conf.i2c_if->interface_init(&chnd))
|
|
goto return_after_parse;
|
|
|
|
/* Register signal handler after opening the communications channel. */
|
|
register_sigaction();
|
|
|
|
if (chnd.conf.i2c_mux) {
|
|
printf("configuring I2C MUX to EC.\n");
|
|
|
|
if (config_i2c_mux(&chnd, I2C_MUX_CMD_EC))
|
|
goto return_after_init;
|
|
}
|
|
|
|
/* Trigger embedded monitor detection */
|
|
if (chnd.conf.send_waveform) {
|
|
if (send_special_waveform(&chnd))
|
|
goto return_after_init;
|
|
} else {
|
|
ret = check_chipid(&chnd);
|
|
if (ret) {
|
|
fprintf(stderr, "Failed to get ITE chip ID. This "
|
|
"could be because the ITE direct firmware "
|
|
"update (DFU) mode is not enabled.\n");
|
|
goto return_after_init;
|
|
}
|
|
}
|
|
|
|
ret = post_waveform_work(&chnd);
|
|
if (ret)
|
|
goto return_after_init;
|
|
|
|
if (chnd.conf.input_filename) {
|
|
ret = read_flash(&chnd);
|
|
if (ret)
|
|
goto return_after_init;
|
|
}
|
|
|
|
if (chnd.conf.erase) {
|
|
if (chnd.flash_cmd_v2)
|
|
/* Do Normal Erase Function */
|
|
command_erase2(&chnd, chnd.flash_size, 0, 0);
|
|
else
|
|
command_erase(&chnd, chnd.flash_size, 0);
|
|
/* Call DBGR Rest to clear the EC lock status after erasing */
|
|
dbgr_reset(&chnd, RSTS_VCCDO_PW_ON|RSTS_HGRST|RSTS_GRST);
|
|
}
|
|
|
|
if (chnd.conf.output_filename) {
|
|
if (chnd.flash_cmd_v2)
|
|
ret = write_flash2(&chnd, chnd.conf.output_filename, 0);
|
|
else
|
|
ret = write_flash(&chnd, chnd.conf.output_filename, 0);
|
|
if (ret)
|
|
goto return_after_init;
|
|
ret = verify_flash(&chnd, chnd.conf.output_filename, 0);
|
|
if (ret)
|
|
goto return_after_init;
|
|
}
|
|
|
|
/* Normal exit */
|
|
ret = 0;
|
|
|
|
return_after_init:
|
|
/* Enable EC Host Global Reset to reset EC resource and EC domain. */
|
|
dbgr_reset(&chnd, RSTS_VCCDO_PW_ON|RSTS_HGRST|RSTS_GRST);
|
|
|
|
if (chnd.conf.i2c_mux) {
|
|
printf("configuring I2C MUX to none.\n");
|
|
config_i2c_mux(&chnd, I2C_MUX_CMD_NONE);
|
|
}
|
|
|
|
if (chnd.conf.i2c_if->interface_shutdown) {
|
|
other_ret = chnd.conf.i2c_if->interface_shutdown(&chnd);
|
|
if (!ret && other_ret)
|
|
ret = other_ret;
|
|
}
|
|
|
|
return_after_parse:
|
|
config_release(&chnd.conf);
|
|
return ret;
|
|
}
|