chrome-ec/util/comm-servo-spi.c

358 lines
8.3 KiB
C

/* 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 hdr.result ? -EECRESULT - hdr.result : 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)
ret = get_response(indata, insize);
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;
}