summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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.
*/