summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEwout van Bekkum <ewout@google.com>2015-12-09 13:30:28 -0800
committerchrome-bot <chrome-bot@chromium.org>2015-12-21 14:58:54 -0800
commit34f226422733c8d98bcfee946e1994997fa7c1e8 (patch)
tree8aeefdd3908b895eba66810eaba1481f2d41b39e
parentee4e0763a17908c9cb058bfadaab0ac803062e18 (diff)
downloadchrome-ec-34f226422733c8d98bcfee946e1994997fa7c1e8.tar.gz
cr50: adds the SPI master driver
Adds the SPI master driver with support for both SPI masters with support for using GPIOs as chip selects or using the hardware's dedicated chip selects. Note this has not been enabled in the cr50 board. BRANCH=none BUG=none TEST=verified through use of the SPI_FLASH module on cr51 Change-Id: I88719f8d03e217ab44249172b1340011fdcfdad5 Signed-off-by: Ewout van Bekkum <ewout@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/317329 Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
-rw-r--r--chip/g/build.mk2
-rw-r--r--chip/g/spi_master.c224
-rw-r--r--include/config.h4
-rw-r--r--include/spi.h2
4 files changed, 231 insertions, 1 deletions
diff --git a/chip/g/build.mk b/chip/g/build.mk
index 7e06a2dbf7..33436921d8 100644
--- a/chip/g/build.mk
+++ b/chip/g/build.mk
@@ -29,6 +29,8 @@ chip-$(CONFIG_DCRYPTO)+= dcrypto/aes.o
chip-$(CONFIG_DCRYPTO)+= dcrypto/sha1.o
chip-$(CONFIG_DCRYPTO)+= dcrypto/sha256.o
+chip-$(CONFIG_SPI_MASTER)+=spi_master.o
+
chip-y+= pmu.o
chip-y+= trng.o
chip-$(CONFIG_SPS)+= sps.o
diff --git a/chip/g/spi_master.c b/chip/g/spi_master.c
new file mode 100644
index 0000000000..322d112797
--- /dev/null
+++ b/chip/g/spi_master.c
@@ -0,0 +1,224 @@
+/* Copyright 2015 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.
+ */
+
+#include "common.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "registers.h"
+#include "spi.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Not defined in the hardware register spec, the RX and TX buffers are 128B. */
+#define SPI_BUF_SIZE 0x80
+
+/* This timeout should allow a full buffer transaction at the lowest SPI speed
+ * by using the largest uint8_t clock divider of 256 (~235kHz). */
+#define SPI_TRANSACTION_TIMEOUT_USEC (5 * MSEC)
+
+/* There are two SPI masters or ports on this chip. */
+#define SPI_NUM_PORTS 2
+
+static struct mutex spi_mutex[SPI_NUM_PORTS];
+static enum spi_clock_mode clock_mode[SPI_NUM_PORTS];
+
+/* The Cr50 SPI master is not DMA auto-fill/drain capable, so async and flush
+ * are not defined on purpose. */
+int spi_transaction(const struct spi_device_t *spi_device,
+ const uint8_t *txdata, int txlen,
+ uint8_t *rxdata, int rxlen)
+{
+ int port = spi_device->port;
+ int rv = EC_SUCCESS;
+ timestamp_t timeout;
+
+ /* If SPI0's passthrough is enabled, SPI0 is not available unless the
+ * SPS's BUSY bit is set. */
+ if (port == 0) {
+ if (GREAD_FIELD_I(SPI, port, CTRL, ENPASSTHRU) &&
+ !GREAD(SPS, EEPROM_BUSY_STATUS))
+ return EC_ERROR_BUSY;
+ }
+
+ /* Ensure it'll fit inside of the RX and TX buffers. Note that although
+ * the buffers are separate, the total transmission size must fit in
+ * the rx buffer. */
+ if (txlen + rxlen > SPI_BUF_SIZE)
+ return EC_ERROR_INVAL;
+
+ /* Grab the port's mutex. */
+ mutex_lock(&spi_mutex[port]);
+
+
+ /* Copy the txdata into the 128B Transmit Buffer. */
+ memmove((uint8_t *)GREG32_ADDR_I(SPI, port, TX_DATA), txdata, txlen);
+
+#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
+ /* Drive chip select low. */
+ gpio_set_level(spi_device->gpio_cs, 0);
+#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
+
+ /* Initiate the transaction. */
+ GWRITE_FIELD_I(SPI, port, XACT, SIZE, rxlen + txlen - 1);
+ GWRITE_FIELD_I(SPI, port, XACT, START, 1);
+
+ /* Wait for the SPI master to finish the transaction. */
+ timeout.val = get_time().val + SPI_TRANSACTION_TIMEOUT_USEC;
+ while (!GREAD_FIELD_I(SPI, port, ISTATE, TXDONE)) {
+ /* Give up if the deadline has been exceeded. */
+ if (get_time().val > timeout.val) {
+ rv = EC_ERROR_TIMEOUT;
+ goto err_cs_high;
+ }
+ }
+ GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1);
+
+ /* Copy the result. */
+ memmove(rxdata, &((uint8_t *)GREG32_ADDR_I(SPI, port, RX_DATA))[txlen],
+ rxlen);
+
+err_cs_high:
+#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
+ /* Drive chip select high. */
+ gpio_set_level(spi_device->gpio_cs, 1);
+#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
+
+ /* Release the port's mutex. */
+ mutex_unlock(&spi_mutex[port]);
+ return rv;
+}
+
+/*
+ * Configure the SPI port's clock mode. The SPI port must be re-enabled after
+ * changing the clocking mode.
+ */
+void set_spi_clock_mode(int port, enum spi_clock_mode mode)
+{
+ clock_mode[port] = mode;
+}
+
+/*
+ * Configure the SPI0 master's passthrough mode. Note:
+ * 1) This must be called after the SPI port is enabled.
+ * 2) Passthrough cannot be safely disabled while the SPI slave port is active
+ * and the SPI slave port's status register's BUSY bit is not set.
+ */
+void configure_spi0_passthrough(int enable)
+{
+ int port = 0;
+
+ /* Grab the port's mutex. */
+ mutex_lock(&spi_mutex[port]);
+
+ GWRITE_FIELD_I(SPI, port, CTRL, ENPASSTHRU, enable);
+
+ /* Release the port's mutex. */
+ mutex_unlock(&spi_mutex[port]);
+}
+
+int spi_enable(int port, int enable)
+{
+ int i;
+
+ if (enable) {
+ int spi_device_found = 0;
+ uint8_t max_div = 0;
+
+#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
+ gpio_config_module(MODULE_SPI, 1);
+#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
+ for (i = 0; i < spi_devices_used; i++) {
+ if (spi_devices[i].port != port)
+ continue;
+
+ spi_device_found = 1;
+
+#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
+ /* Deassert CS# */
+ gpio_set_flags(spi_devices[i].gpio_cs, GPIO_OUTPUT);
+ gpio_set_level(spi_devices[i].gpio_cs, 1);
+#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
+
+ /* Find the port's largest DIV (lowest frequency). */
+ if (spi_devices[i].div > max_div)
+ max_div = spi_devices[i].div;
+ }
+
+ /* Ensure there is at least one device behind the SPI port. */
+ if (!spi_device_found)
+ return EC_ERROR_INVAL;
+
+ /* configure the SPI clock mode */
+ GWRITE_FIELD_I(SPI, port, CTRL, CPOL,
+ (clock_mode[port] == SPI_CLOCK_MODE2) ||
+ (clock_mode[port] == SPI_CLOCK_MODE3));
+ GWRITE_FIELD_I(SPI, port, CTRL, CPHA,
+ (clock_mode[port] == SPI_CLOCK_MODE1) ||
+ (clock_mode[port] == SPI_CLOCK_MODE3));
+
+ /* Enforce the default setup and hold times. */
+ GWRITE_FIELD_I(SPI, port, CTRL, CSBSU, 0);
+ GWRITE_FIELD_I(SPI, port, CTRL, CSBHLD, 0);
+
+ /* Set the clock divider, where freq / (div + 1). */
+ GWRITE_FIELD_I(SPI, port, CTRL, IDIV, max_div);
+
+ /* Master's CS is active low. */
+ GWRITE_FIELD_I(SPI, port, CTRL, CSBPOL, 0);
+
+ /* Byte 0 bit 7 is first in each double word in the buffers. */
+ GWRITE_FIELD_I(SPI, port, CTRL, TXBITOR, 1);
+ GWRITE_FIELD_I(SPI, port, CTRL, TXBYTOR, 0);
+ GWRITE_FIELD_I(SPI, port, CTRL, RXBITOR, 1);
+ GWRITE_FIELD_I(SPI, port, CTRL, RXBYTOR, 0);
+
+ /* Disable passthrough by default. */
+ if (port == 0)
+ configure_spi0_passthrough(0);
+
+ /* Disable the TXDONE interrupt, we'll busy poll instead. */
+ GWRITE_FIELD_I(SPI, port, ICTRL, TXDONE, 0);
+
+ } else {
+ for (i = 0; i < spi_devices_used; i++) {
+ if (spi_devices[i].port != port)
+ continue;
+
+#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
+ /* Make sure CS# is deaserted and disabled. */
+ gpio_set_level(spi_devices[i].gpio_cs, 1);
+ gpio_set_flags(spi_devices[i].gpio_cs, GPIO_ODR_HIGH);
+#endif /* CONFIG_SPI_MASTER_NO_CS_GPIOS */
+ }
+
+ /* Disable passthrough. */
+ if (port == 0)
+ configure_spi0_passthrough(0);
+
+ gpio_config_module(MODULE_SPI, 1);
+ }
+
+ return EC_SUCCESS;
+}
+
+/******************************************************************************/
+/* Hooks */
+
+static void spi_init(void)
+{
+ size_t i;
+
+ for (i = 0; i < SPI_NUM_PORTS; i++) {
+ /* Configure the SPI ports to default to mode0. */
+ set_spi_clock_mode(i, SPI_CLOCK_MODE0);
+
+ /* Ensure the SPI ports are disabled to prevent us from
+ * interfering with the main chipset when we're not explicitly
+ * using the SPI bus. */
+ spi_enable(i, 0);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT);
diff --git a/include/config.h b/include/config.h
index 545d83d39e..1ca46f1d98 100644
--- a/include/config.h
+++ b/include/config.h
@@ -1576,6 +1576,10 @@
/* SPI master feature */
#undef CONFIG_SPI_MASTER
+/* Support SPI masters without GPIO-specified Chip Selects, instead rely on the
+ * SPI master port's hardwired CS pin. */
+#undef CONFIG_SPI_MASTER_NO_CS_GPIOS
+
/* Support testing SPI slave controller driver. */
#undef CONFIG_SPS_TEST
diff --git a/include/spi.h b/include/spi.h
index 21629ddd4d..8fd7b2e311 100644
--- a/include/spi.h
+++ b/include/spi.h
@@ -49,7 +49,7 @@ extern const struct spi_device_t spi_devices[];
extern const unsigned int spi_devices_used;
/*
- * The first port in spi_ports define the port to access the SPI flash.
+ * The first port in spi_devices defines the port to access the SPI flash.
* The first gpio defines the CS GPIO to access the flash,
* if used.
*/