summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Palatin <vpalatin@chromium.org>2018-01-12 10:22:52 +0100
committerchrome-bot <chrome-bot@chromium.org>2018-01-17 06:20:37 -0800
commit9d38e4664150579503f70b03b35e1dd1bedb7920 (patch)
tree64b9f0d008d1a499b16114f6650a6b21ea711ec3
parent1e1b5d4463c2f273c593f90fd1f60b7c06139f86 (diff)
downloadchrome-ec-9d38e4664150579503f70b03b35e1dd1bedb7920.tar.gz
ectool: add servo v2 spi support
Add a new transport for ectool to send host command V3 over the Servo v2 SPI interface using libftdi. Build this new communication mechanism only for the 'build' architecture as it has a dependency on libftdi, the new ectool binary is called ectool_servo. Fix the 'build' tools build if they don't have a source file matching their binary name. The serial number of the servo board can be passed in the 'name' parameter, e.g. : sudo ectool_servo --name=905537-00474 version Signed-off-by: Vincent Palatin <vpalatin@chromium.org> BRANCH=none BUG=b:70320279 TEST=with ZerbleBarn connected to a servo V2, run: sudo ectool_servo version Change-Id: Ia7067d465a42f76695fed5932f32fac9a6d0988e Reviewed-on: https://chromium-review.googlesource.com/864164 Commit-Ready: Vincent Palatin <vpalatin@chromium.org> Tested-by: Vincent Palatin <vpalatin@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org> Reviewed-by: Shawn N <shawnn@chromium.org>
-rw-r--r--Makefile.rules4
-rw-r--r--util/build.mk5
-rw-r--r--util/comm-host.c5
-rw-r--r--util/comm-host.h1
-rw-r--r--util/comm-servo-spi.c358
-rw-r--r--util/ectool.c2
6 files changed, 373 insertions, 2 deletions
diff --git a/Makefile.rules b/Makefile.rules
index 4d12ef7113..ad8db5dfd8 100644
--- a/Makefile.rules
+++ b/Makefile.rules
@@ -13,7 +13,7 @@ BUILD_DIR := $(firstword $(subst /, ,$(out)))
build-utils := $(foreach u,$(build-util-bin),$(out)/util/$(u))
host-utils := $(foreach u,$(host-util-bin),$(out)/util/$(u))
build-art := $(foreach u,$(build-util-art),$(out)/$(u))
-build-srcs := $(foreach u,$(build-util-bin),$(sort $($(u)-objs:%.o=util/%.c) util/$(u).c))
+build-srcs := $(foreach u,$(build-util-bin),$(sort $($(u)-objs:%.o=util/%.c) $(wildcard util/$(u).c)))
host-srcs := $(foreach u,$(host-util-bin),$(sort $($(u)-objs:%.o=util/%.c) util/$(u).c))
# Don't do a build test on the following boards:
@@ -74,7 +74,7 @@ cmd_elf = $(CC) $(objs) $(libsharedobjs_elf-y) $(LDFLAGS) \
cmd_exe = $(CC) $(ro-objs) $(HOST_TEST_LDFLAGS) -o $@
cmd_c_to_o = $(CC) $(CFLAGS) -MMD -MP -MF $@.d -c $< -o $(@D)/$(@F)
cmd_c_to_build = $(BUILDCC) $(BUILD_CFLAGS) \
- $(sort $(foreach c,$($(*F)-objs),util/$(c:%.o=%.c)) $*.c) \
+ $(sort $(foreach c,$($(*F)-objs),util/$(c:%.o=%.c)) $(wildcard $*.c)) \
$(BUILD_LDFLAGS) \
-MMD -MF $@.d -o $@
cmd_c_to_vif = $(BUILDCC) $(BUILD_CFLAGS) $(STANDALONE_FLAGS) \
diff --git a/util/build.mk b/util/build.mk
index c7a3b5b522..e92f76a8e7 100644
--- a/util/build.mk
+++ b/util/build.mk
@@ -13,11 +13,16 @@ build-util-art+=util/export_taskinfo.so
ifeq ($(CHIP),npcx)
build-util-bin+=ecst
endif
+# Build on a limited subset of boards to save build time
+ifeq ($(BOARD),meowth_fp)
+build-util-bin+=ectool_servo
+endif
comm-objs=$(util-lock-objs:%=lock/%) comm-host.o comm-dev.o
comm-objs+=comm-lpc.o comm-i2c.o misc_util.o
ectool-objs=ectool.o ectool_keyscan.o ec_flash.o ec_panicinfo.o $(comm-objs)
+ectool_servo-objs=$(ectool-objs) comm-servo-spi.o
ec_sb_firmware_update-objs=ec_sb_firmware_update.o $(comm-objs) misc_util.o
ec_sb_firmware_update-objs+=powerd_lock.o
lbplay-objs=lbplay.o $(comm-objs)
diff --git a/util/comm-host.c b/util/comm-host.c
index 9fc65ab634..ad3d5053d0 100644
--- a/util/comm-host.c
+++ b/util/comm-host.c
@@ -29,6 +29,7 @@ static int command_offset;
int comm_init_dev(const char *device_name) __attribute__((weak));
int comm_init_lpc(void) __attribute__((weak));
int comm_init_i2c(void) __attribute__((weak));
+int comm_init_servo_spi(const char *device_name) __attribute__((weak));
static int fake_readmem(int offset, int bytes, void *dest)
{
@@ -97,6 +98,10 @@ int comm_init(int interfaces, const char *device_name)
!comm_init_dev(device_name))
goto init_ok;
+ if ((interfaces & COMM_SERVO) && comm_init_servo_spi &&
+ !comm_init_servo_spi(device_name))
+ goto init_ok;
+
/* Do not fallback to other communication methods if target is not a
* cros_ec device */
if (strcmp(CROS_EC_DEV_NAME, device_name))
diff --git a/util/comm-host.h b/util/comm-host.h
index 1d3cb9e36d..a30f0f4adc 100644
--- a/util/comm-host.h
+++ b/util/comm-host.h
@@ -30,6 +30,7 @@ enum comm_interface {
COMM_DEV = (1 << 0),
COMM_LPC = (1 << 1),
COMM_I2C = (1 << 2),
+ COMM_SERVO = (1 << 3),
COMM_ALL = -1
};
diff --git a/util/comm-servo-spi.c b/util/comm-servo-spi.c
new file mode 100644
index 0000000000..6d9d095be4
--- /dev/null
+++ b/util/comm-servo-spi.c
@@ -0,0 +1,358 @@
+/* Copyright 2018 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.
+ */
+/*
+ * Transport using the Servo V2 SPI1 interface through the FT4232 MPSSE
+ * hardware engine (driven by libftdi) in order to send host commands V3
+ * directly to a MCU slave SPI controller.
+ *
+ * It allows to drive a MCU with the cros_ec host SPI interface directly from
+ * a developer workstation or another test system.
+ *
+ * The USB serial number of the servo board can be passed in the 'name'
+ * parameter, e.g. :
+ * sudo ectool_servo --name=905537-00474 version
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libftdi1/ftdi.h>
+
+#include "comm-host.h"
+#include "cros_ec_dev.h"
+
+/* Servo V2 SPI1 interface identifiers */
+#define SERVO_V2_USB_VID 0x18d1
+#define SERVO_V2_USB_PID 0x5003
+#define SERVO_V2_USB_SPI1_INTERFACE INTERFACE_B
+
+/* SPI clock frequency in Hz */
+#define SPI_CLOCK_FREQ 1000000
+
+#define FTDI_LATENCY_1MS 2
+
+/* Timeout when waiting for the EC answer to our request */
+#define RESP_TIMEOUT 2 /* second */
+
+#ifdef DEBUG
+#define debug(format, arg...) printf(format, ##arg)
+#else
+#define debug(...)
+#endif
+
+/* Communication context */
+static struct ftdi_context ftdi;
+
+/* Size of a MPSSE command packet */
+#define MPSSE_CMD_SIZE 3
+
+enum mpsse_commands {
+ ENABLE_ADAPTIVE_CLOCK = 0x96,
+ DISABLE_ADAPTIVE_CLOCK = 0x97,
+ TCK_X5 = 0x8A,
+ TCK_D5 = 0x8B,
+ TRISTATE_IO = 0x9E,
+};
+
+enum mpsse_pins {
+ SCLK = 1,
+ MOSI = 2,
+ MISO = 4,
+ CS_L = 8,
+};
+/* SCLK/MOSI/CS_L are outputs, MISO is an input */
+#define PINS_DIR (SCLK | MOSI | CS_L)
+
+/* SPI mode 0:
+ * propagates data on the falling edge
+ * and reads data on the rising edge of the clock.
+ */
+#define SPI_CMD_TX (MPSSE_DO_WRITE | MPSSE_WRITE_NEG)
+#define SPI_CMD_RX (MPSSE_DO_READ)
+#define SPI_CMD_TXRX (MPSSE_DO_WRITE | MPSSE_DO_READ | MPSSE_WRITE_NEG)
+
+static int raw_read(uint8_t *buf, int size)
+{
+ int rlen;
+
+ while (size) {
+ rlen = ftdi_read_data(&ftdi, buf, size);
+ if (rlen < 0)
+ break;
+ buf += rlen;
+ size -= rlen;
+ }
+ return !!size;
+}
+
+static int mpsse_set_pins(uint8_t levels)
+{
+ uint8_t buf[MPSSE_CMD_SIZE] = {0};
+
+ buf[0] = SET_BITS_LOW;
+ buf[1] = levels;
+ buf[2] = PINS_DIR;
+
+ return ftdi_write_data(&ftdi, buf, sizeof(buf)) != sizeof(buf);
+}
+
+static int send_request(int cmd, int version,
+ const uint8_t *outdata, size_t outsize)
+{
+ uint8_t *txbuf;
+ struct ec_host_request *request;
+ size_t i;
+ int ret = -EC_RES_ERROR;
+ uint8_t csum = 0;
+ size_t block_size = sizeof(struct ec_host_request) + outsize;
+ size_t total_len = MPSSE_CMD_SIZE + block_size;
+
+ txbuf = calloc(1, total_len);
+ if (!txbuf)
+ return -ENOMEM;
+
+ /* MPSSE block size is the full command minus 1 byte */
+ txbuf[0] = SPI_CMD_TXRX;
+ txbuf[1] = ((block_size - 1) & 0xFF);
+ txbuf[2] = (((block_size - 1) >> 8) & 0xFF);
+
+ /* Command header first */
+ request = (struct ec_host_request *)(txbuf + MPSSE_CMD_SIZE);
+ request->struct_version = EC_HOST_REQUEST_VERSION;
+ request->checksum = 0;
+ request->command = cmd;
+ request->command_version = version;
+ request->reserved = 0;
+ request->data_len = outsize;
+
+ /* copy the data to transmit after the command header */
+ memcpy(txbuf + MPSSE_CMD_SIZE + sizeof(struct ec_host_request),
+ outdata, outsize);
+
+ /* Compute the checksum */
+ for (i = MPSSE_CMD_SIZE; i < total_len; i++)
+ csum += txbuf[i];
+ request->checksum = -csum;
+
+ if (ftdi_write_data(&ftdi, txbuf, total_len) != total_len)
+ goto free_request;
+
+ if (raw_read(txbuf, block_size) != 0)
+ goto free_request;
+
+ /* Make sure the EC was listening */
+ ret = 0;
+ for (i = 0; i < block_size; i++) {
+ switch (txbuf[i]) {
+ case EC_SPI_PAST_END:
+ case EC_SPI_RX_BAD_DATA:
+ case EC_SPI_NOT_READY:
+ ret = txbuf[i];
+ /* Fall-through */
+ default:
+ break;
+ }
+ if (ret)
+ break;
+ }
+
+free_request:
+ free(txbuf);
+ return ret;
+}
+
+static int spi_read(void *buf, size_t size)
+{
+ uint8_t cmd[MPSSE_CMD_SIZE];
+
+ cmd[0] = SPI_CMD_RX;
+ cmd[1] = ((size - 1) & 0xFF);
+ cmd[2] = (((size - 1) >> 8) & 0xFF);
+
+ if (ftdi_write_data(&ftdi, cmd, sizeof(cmd)) != sizeof(cmd))
+ return -EC_RES_ERROR;
+
+ return raw_read(buf, size) != 0;
+}
+
+static int get_response(uint8_t *bodydest, size_t bodylen)
+{
+ uint8_t sum = 0;
+ size_t i;
+ struct ec_host_response hdr;
+ uint8_t status;
+ time_t deadline = time(NULL) + RESP_TIMEOUT;
+
+ /*
+ * Read a byte at a time until we see the start of the frame.
+ * This is slow, but often still faster than the EC.
+ */
+ while (time(NULL) < deadline) {
+ if (spi_read(&status, sizeof(status)))
+ goto read_error;
+ if (status == EC_SPI_FRAME_START)
+ break;
+ }
+ if (status != EC_SPI_FRAME_START) {
+ fprintf(stderr, "timeout wait for response\n");
+ return -EC_RES_ERROR;
+ }
+
+ /* Now read the response header */
+ if (spi_read(&hdr, sizeof(hdr)))
+ goto read_error;
+
+ /* Check the header */
+ if (hdr.struct_version != EC_HOST_RESPONSE_VERSION) {
+ fprintf(stderr, "response version %d (should be %d)\n",
+ hdr.struct_version,
+ EC_HOST_RESPONSE_VERSION);
+ return -EC_RES_ERROR;
+ }
+ if (hdr.data_len > bodylen) {
+ fprintf(stderr, "response data_len %d is > %zd\n",
+ hdr.data_len, bodylen);
+ return -EC_RES_ERROR;
+ }
+
+ /* Read the data if needed */
+ if (hdr.data_len && spi_read(bodydest, hdr.data_len))
+ goto read_error;
+
+ /* Verify the checksum */
+ for (i = 0; i < sizeof(struct ec_host_response); i++)
+ sum += ((uint8_t *)&hdr)[i];
+ for (i = 0; i < hdr.data_len; i++)
+ sum += bodydest[i];
+ if (sum) {
+ fprintf(stderr, "Checksum invalid\n");
+ return -EC_RES_ERROR;
+ }
+
+ return 0;
+
+read_error:
+ fprintf(stderr, "Read failed: %s\n", ftdi_get_error_string(&ftdi));
+ return -EC_RES_ERROR;
+}
+
+static int ec_command_servo_spi(int cmd, int version,
+ const void *outdata, int outsize,
+ void *indata, int insize)
+{
+ int ret = -EC_RES_ERROR;
+
+ /* Set the chip select low */
+ if (mpsse_set_pins(0) != 0) {
+ fprintf(stderr, "Start failed: %s\n",
+ ftdi_get_error_string(&ftdi));
+ return -EC_RES_ERROR;
+ }
+
+ if (send_request(cmd, version, outdata, outsize) == 0 &&
+ get_response(indata, insize) == 0)
+ ret = 0;
+
+ if (mpsse_set_pins(CS_L) != 0) {
+ fprintf(stderr, "Stop failed: %s\n",
+ ftdi_get_error_string(&ftdi));
+ return -EC_RES_ERROR;
+ }
+ /* SPI protocol gap ... */
+ usleep(10);
+
+ return ret;
+}
+
+static int mpsse_set_clock(uint32_t freq)
+{
+ uint32_t system_clock = 0;
+ uint16_t divisor = 0;
+ uint8_t buf[MPSSE_CMD_SIZE] = {0};
+
+ if (freq > 6000000) {
+ buf[0] = TCK_X5;
+ system_clock = 60000000;
+ } else {
+ buf[0] = TCK_D5;
+ system_clock = 12000000;
+ }
+
+ if (ftdi_write_data(&ftdi, buf, 1) != 1)
+ return -EC_RES_ERROR;
+
+ divisor = (((system_clock / freq) / 2) - 1);
+
+ buf[0] = TCK_DIVISOR;
+ buf[1] = (divisor & 0xFF);
+ buf[2] = ((divisor >> 8) & 0xFF);
+
+ return ftdi_write_data(&ftdi, buf, MPSSE_CMD_SIZE) != MPSSE_CMD_SIZE;
+}
+
+static void servo_spi_close(void)
+{
+ ftdi_set_bitmode(&ftdi, 0, BITMODE_RESET);
+ ftdi_usb_close(&ftdi);
+ ftdi_deinit(&ftdi);
+}
+
+int comm_init_servo_spi(const char *device_name)
+{
+ int status;
+ uint8_t buf[MPSSE_CMD_SIZE] = {0};
+ /* if the user mentioned a device name, use it as serial string */
+ const char *serial = strcmp(CROS_EC_DEV_NAME, device_name) ?
+ device_name : NULL;
+
+ if (ftdi_init(&ftdi))
+ return -EC_RES_ERROR;
+ ftdi_set_interface(&ftdi, SERVO_V2_USB_SPI1_INTERFACE);
+
+ status = ftdi_usb_open_desc(&ftdi, SERVO_V2_USB_VID, SERVO_V2_USB_PID,
+ NULL, serial);
+ if (status) {
+ debug("Can't find a Servo v2 USB device\n");
+ return -EC_RES_ERROR;
+ }
+
+ status |= ftdi_usb_reset(&ftdi);
+ status |= ftdi_set_latency_timer(&ftdi, FTDI_LATENCY_1MS);
+ status |= ftdi_set_bitmode(&ftdi, 0, BITMODE_RESET);
+ if (status)
+ goto err_close;
+
+ ftdi_set_bitmode(&ftdi, 0, BITMODE_MPSSE);
+ if (mpsse_set_clock(SPI_CLOCK_FREQ))
+ goto err_close;
+
+ /* Disable FTDI internal loopback */
+ buf[0] = LOOPBACK_END;
+ if (ftdi_write_data(&ftdi, buf, 1) != 1)
+ goto err_close;
+ /* Ensure adaptive clock is disabled */
+ buf[0] = DISABLE_ADAPTIVE_CLOCK;
+ if (ftdi_write_data(&ftdi, buf, 1) != 1)
+ goto err_close;
+ /* Set the idle pin states */
+ if (mpsse_set_pins(CS_L) != 0)
+ goto err_close;
+
+ ec_command_proto = ec_command_servo_spi;
+ /* Set temporary size, will be updated later. */
+ ec_max_outsize = EC_PROTO2_MAX_PARAM_SIZE - 8;
+ ec_max_insize = EC_PROTO2_MAX_PARAM_SIZE;
+
+ return 0;
+
+err_close:
+ servo_spi_close();
+ return -EC_RES_ERROR;
+}
diff --git a/util/ectool.c b/util/ectool.c
index 1099071991..200b2faacf 100644
--- a/util/ectool.c
+++ b/util/ectool.c
@@ -7502,6 +7502,8 @@ int main(int argc, char *argv[])
interfaces = COMM_LPC;
} else if (!strcasecmp(optarg, "i2c")) {
interfaces = COMM_I2C;
+ } else if (!strcasecmp(optarg, "servo")) {
+ interfaces = COMM_SERVO;
} else {
fprintf(stderr, "Invalid --interface\n");
parse_error = 1;