358 lines
8.3 KiB
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;
|
||
|
}
|