CrosEC: Add support for talking to the EC over SPI using protocol version 2.

The only devices which communicate with the EC over SPI also support protocol
version 3, but we don't yet support version 3 in depthcharge at all and this
is a reasonable stepping stone in that direction.

There are two oustanding issues with the SPI interface. First, transactions
with the EC can't be too close together or it gets tripped up at the start of
the later transaction and never responds. Second, when writing a new EC image
as part of EC software sync, bytes are occasionally dropped. We haven't yet
determined whether those bytes were send correctly but lost by the EC, or of
they were never sent in the first place somehow.

BUG=chrome-os-partner:19420
TEST=Built and booted on Pit with this driver and EC software sync enabled.
Saw that the EC was initialized successfully and commands to it completed as
expected.
BRANCH=None

Change-Id: I43b97cb728a0c4fa0eaa9dd3710a92389b2a8085
Signed-off-by: Gabe Black <gabeblack@google.com>
Reviewed-on: https://gerrit.chromium.org/gerrit/62893
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Tested-by: Gabe Black <gabeblack@chromium.org>
Commit-Queue: Gabe Black <gabeblack@chromium.org>
diff --git a/src/drivers/ec/cros/Makefile.inc b/src/drivers/ec/cros/Makefile.inc
index df40c19..90d0e3a 100644
--- a/src/drivers/ec/cros/Makefile.inc
+++ b/src/drivers/ec/cros/Makefile.inc
@@ -18,5 +18,6 @@
 ##
 
 depthcharge-$(CONFIG_DRIVER_EC_CROS) += ec.c
-depthcharge-$(CONFIG_DRIVER_EC_CROS_LPC) += lpc.c
 depthcharge-$(CONFIG_DRIVER_EC_CROS_I2C) += i2c.c
+depthcharge-$(CONFIG_DRIVER_EC_CROS_LPC) += lpc.c
+depthcharge-$(CONFIG_DRIVER_EC_CROS_SPI) += spi.c
diff --git a/src/drivers/ec/cros/spi.c b/src/drivers/ec/cros/spi.c
new file mode 100644
index 0000000..ffe9bbc
--- /dev/null
+++ b/src/drivers/ec/cros/spi.c
@@ -0,0 +1,201 @@
+/*
+ * Chromium OS EC driver - SPI interface
+ *
+ * Copyright 2012 Google Inc.
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+/*
+ * The Matrix Keyboard Protocol driver handles talking to the keyboard
+ * controller chip. Mostly this is for keyboard functions, but some other
+ * things have slipped in, so we provide generic services to talk to the
+ * KBC.
+ */
+
+#include <assert.h>
+#include <libpayload.h>
+
+#include "base/list.h"
+#include "base/time.h"
+#include "config.h"
+#include "drivers/bus/spi/spi.h"
+#include "drivers/ec/cros/ec.h"
+#include "drivers/ec/cros/spi.h"
+
+// How long we have to leave CS deasserted between transactions.
+static const uint64_t CsCooldownUs = 200;
+
+static const uint8_t EcFramingByte = 0xec;
+
+static int send_command(CrosEcBusOps *me, uint8_t cmd, int cmd_version,
+			const void *dout, uint32_t dout_len,
+			void *din, uint32_t din_len)
+{
+	CrosEcSpiBus *bus = container_of(me, CrosEcSpiBus, ops);
+	uint8_t *bytes;
+
+	// Header + data + checksum.
+	int out_bytes = CROS_EC_SPI_OUT_HDR_SIZE + dout_len + 1;
+	int in_bytes = CROS_EC_SPI_IN_HDR_SIZE + din_len + 1;
+
+	/*
+	 * Sanity-check I/O sizes given transaction overhead in internal
+	 * buffers.
+	 */
+	if (out_bytes > bus->out_size) {
+		printf("%s: Cannot send %d bytes\n", __func__, dout_len);
+		return -1;
+	}
+	if (in_bytes > bus->in_size) {
+		printf("%s: Cannot receive %d bytes\n", __func__, din_len);
+		return -1;
+	}
+	assert(dout_len >= 0);
+
+	// Prepare the output.
+	bytes = bus->buf;
+	*bytes++ = EC_CMD_VERSION0 + cmd_version;
+	*bytes++ = cmd;
+	*bytes++ = dout_len;
+	memcpy(bytes, dout, dout_len);
+	bytes += dout_len;
+
+	*bytes++ = cros_ec_calc_checksum(bus->buf,
+					 CROS_EC_SPI_OUT_HDR_SIZE + dout_len);
+
+	assert(out_bytes == bytes - (uint8_t *)bus->buf);
+
+	// Send the output.
+	cros_ec_dump_data("out", -1, bus->buf, out_bytes);
+
+	while (timer_us(bus->last_transfer) < CsCooldownUs)
+		;
+
+	if (bus->spi->start(bus->spi))
+		return -1;
+
+	if (bus->spi->transfer(bus->spi, NULL, bus->buf, out_bytes)) {
+		bus->spi->stop(bus->spi);
+		return -1;
+	}
+
+	// Wait until the EC is ready.
+	uint8_t byte;
+	do {
+		if (bus->spi->transfer(bus->spi, &byte, NULL, 1)) {
+			bus->spi->stop(bus->spi);
+			return -1;
+		}
+	} while (byte != EcFramingByte);
+
+	// Read the response code and the data length.
+	bytes = bus->buf;
+	if (bus->spi->transfer(bus->spi, bytes, NULL, 2)) {
+		bus->spi->stop(bus->spi);
+		bus->last_transfer = timer_us(0);
+		return -1;
+	}
+	uint8_t result = *bytes++;
+	uint8_t length = *bytes++;
+
+	// Make sure there's enough room for the data.
+	if (CROS_EC_SPI_IN_HDR_SIZE + length + 1 > bus->in_size) {
+		printf("%s: Received length %#02x too large\n",
+		       __func__, length);
+		bus->spi->stop(bus->spi);
+		bus->last_transfer = timer_us(0);
+		return -1;
+	}
+
+	// Read the data and the checksum, and finish up.
+	if (bus->spi->transfer(bus->spi, bytes, NULL, length + 1)) {
+		bus->spi->stop(bus->spi);
+		bus->last_transfer = timer_us(0);
+		return -1;
+	}
+	bytes += length;
+	int expected = *bytes++;
+	bus->spi->stop(bus->spi);
+	bus->last_transfer = timer_us(0);
+
+	// Check the integrity of the response.
+	if (result != EC_RES_SUCCESS) {
+		printf("%s: Received bad result code %d\n", __func__, result);
+		return -result;
+	}
+
+	int csum = cros_ec_calc_checksum(bus->buf,
+					 CROS_EC_SPI_IN_HDR_SIZE + length);
+
+	if (csum != expected) {
+		printf("%s: Invalid checksum rx %#02x, calced %#02x\n",
+		       __func__, expected, csum);
+		return -1;
+	}
+	cros_ec_dump_data("in", -1, bus->buf,
+			  CROS_EC_SPI_IN_HDR_SIZE + din_len + 1);
+
+	// If the caller wants the response, copy it out for them.
+	din_len = MIN(din_len, length);
+	if (din) {
+		memcpy(din, (uint8_t *)bus->buf + CROS_EC_SPI_IN_HDR_SIZE,
+		       din_len);
+	}
+
+	return din_len;
+}
+
+static int set_size(CrosEcBusOps *me, uint32_t max_request_size,
+		    uint32_t max_reply_size)
+{
+	CrosEcSpiBus *bus = container_of(me, CrosEcSpiBus, ops);
+
+	free(bus->buf);
+	bus->in_size = max_reply_size;
+	bus->out_size = max_request_size;
+	bus->buf = malloc(MAX(bus->in_size, bus->out_size));
+	if (!bus->buf) {
+		printf("Failed to allocate buffer.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+CrosEcSpiBus *new_cros_ec_spi_bus(SpiOps *spi)
+{
+	assert(spi);
+
+	CrosEcSpiBus *bus = malloc(sizeof(*bus));
+	if (!bus) {
+		printf("Failed to allocate ChromeOS EC SPI object.\n");
+		return NULL;
+	}
+	memset(bus, 0, sizeof(*bus));
+	bus->ops.send_command = &send_command;
+	bus->ops.set_max_sizes = &set_size;
+	bus->spi = spi;
+	if (set_size(&bus->ops, CROS_EC_SPI_DEFAULT_MAX_SIZE,
+		     CROS_EC_SPI_DEFAULT_MAX_SIZE)) {
+		free(bus);
+		return NULL;
+	}
+
+	return bus;
+}
diff --git a/src/drivers/ec/cros/spi.h b/src/drivers/ec/cros/spi.h
new file mode 100644
index 0000000..4f404e8
--- /dev/null
+++ b/src/drivers/ec/cros/spi.h
@@ -0,0 +1,60 @@
+/*
+ * Chromium OS EC driver - SPI interface
+ *
+ * Copyright 2013 Google Inc.
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#ifndef __DRIVERS_EC_CROS_SPI_H__
+#define __DRIVERS_EC_CROS_SPI_H__
+
+#include "drivers/bus/spi/spi.h"
+#include "drivers/ec/cros/message.h"
+#include "drivers/ec/cros/ec.h"
+
+struct SpiOps;
+typedef struct SpiOps SpiOps;
+
+typedef struct
+{
+	CrosEcBusOps ops;
+	SpiOps *spi;
+
+	// Time the last transfer ended.
+	uint64_t last_transfer;
+
+	void *buf;
+	int in_size;
+	int out_size;
+} CrosEcSpiBus;
+
+enum {
+	// response, arglen
+	CROS_EC_SPI_IN_HDR_SIZE = 2,
+	// version, cmd, arglen
+	CROS_EC_SPI_OUT_HDR_SIZE = 3
+};
+
+enum {
+	CROS_EC_SPI_DEFAULT_MAX_SIZE = 0x100
+};
+
+CrosEcSpiBus *new_cros_ec_spi_bus(SpiOps *spi);
+
+#endif /* __DRIVERS_EC_CROS_SPI_H__ */