summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGerrit <chrome-bot@google.com>2012-05-30 14:16:02 -0700
committerGerrit Code Review <gerrit@gerrit.golo.chromium.org>2012-05-30 14:16:02 -0700
commitdefbdc32d3ee94ce3c3c104049f5ec71e6e80d47 (patch)
tree25fe89fbe97502c26a30cf70625469f55f47aa58
parentaad3f858a436bc6c763d4ddf8a49fad6465302da (diff)
parent858d87cfaae5182b1d6cf008a7e33766612ab000 (diff)
downloadchrome-ec-defbdc32d3ee94ce3c3c104049f5ec71e6e80d47.tar.gz
Merge "Add basic SPI support to link"
-rw-r--r--board/link/board.c1
-rw-r--r--board/link/board.h2
-rw-r--r--chip/lm4/build.mk1
-rw-r--r--chip/lm4/registers.h25
-rw-r--r--chip/lm4/spi.c179
-rw-r--r--common/console.c1
-rw-r--r--include/console.h1
-rw-r--r--include/spi.h11
8 files changed, 221 insertions, 0 deletions
diff --git a/board/link/board.c b/board/link/board.c
index 3ff559c1a9..2a39e76eab 100644
--- a/board/link/board.c
+++ b/board/link/board.c
@@ -101,6 +101,7 @@ const struct gpio_info gpio_list[GPIO_COUNT] = {
{"PCH_SUSACKn", LM4_GPIO_F, (1<<3), GPIO_OUT_HIGH, NULL},
{"RADIO_ENABLE_WLAN", LM4_GPIO_D, (1<<0), GPIO_OUT_LOW, NULL},
{"RADIO_ENABLE_BT", LM4_GPIO_D, (1<<1), GPIO_OUT_LOW, NULL},
+ {"SPI_CSn", LM4_GPIO_A, (1<<3), GPIO_HI_Z, NULL},
{"TOUCHSCREEN_RESETn", LM4_GPIO_B, (1<<0), GPIO_OUT_LOW, NULL},
{"USB1_CTL1", LM4_GPIO_E, (1<<2), GPIO_OUT_LOW, NULL},
{"USB1_CTL2", LM4_GPIO_E, (1<<3), GPIO_OUT_LOW, NULL},
diff --git a/board/link/board.h b/board/link/board.h
index e7cdda0ac1..ac8ead8ed6 100644
--- a/board/link/board.h
+++ b/board/link/board.h
@@ -19,6 +19,7 @@
#define CONFIG_PECI
#define CONFIG_POWER_LED
#define CONFIG_PSTORE
+#define CONFIG_SPI
#define CONFIG_TASK_PROFILING
#define CONFIG_TMP006
#define CONFIG_USB_CHARGE
@@ -165,6 +166,7 @@ enum gpio_signal {
GPIO_PCH_SUSACKn, /* Acknowledge PCH SUSWARN# signal */
GPIO_RADIO_ENABLE_WLAN, /* Enable WLAN radio */
GPIO_RADIO_ENABLE_BT, /* Enable bluetooth radio */
+ GPIO_SPI_CSn, /* SPI chip select */
GPIO_TOUCHSCREEN_RESETn, /* Reset touch screen (Proto1+) */
GPIO_USB1_CTL1, /* USB charger port 1 CTL1 output */
GPIO_USB1_CTL2, /* USB charger port 1 CTL2 output */
diff --git a/chip/lm4/build.mk b/chip/lm4/build.mk
index fe9afa1971..143c76f88e 100644
--- a/chip/lm4/build.mk
+++ b/chip/lm4/build.mk
@@ -20,6 +20,7 @@ chip-$(CONFIG_I2C)+=i2c.o
chip-$(CONFIG_LPC)+=lpc.o
chip-$(CONFIG_ONEWIRE)+=onewire.o
chip-$(CONFIG_PECI)+=peci.o
+chip-$(CONFIG_SPI)+=spi.o
chip-$(CONFIG_TASK_PWM)+=pwm.o
chip-$(CONFIG_TASK_KEYSCAN)+=keyboard_scan.o
chip-$(CONFIG_TASK_POWERBTN)+=power_button.o
diff --git a/chip/lm4/registers.h b/chip/lm4/registers.h
index 23203a77d6..63bb3dbeca 100644
--- a/chip/lm4/registers.h
+++ b/chip/lm4/registers.h
@@ -33,6 +33,30 @@ static inline int lm4_uart_addr(int ch, int offset)
#define LM4_UART_DMACTL(ch) LM4UARTREG(ch, 0x048)
#define LM4_UART_CC(ch) LM4UARTREG(ch, 0xfc8)
+#define LM4_SSI_BASE 0x40008000
+#define LM4_SSI_CH_SEP 0x40001000
+static inline int lm4_spi_addr(int ch, int offset)
+{
+ return offset + LM4_SSI_BASE + LM4_SSI_CH_SEP * ch;
+}
+#define LM4SSIREG(ch, offset) LM4REG(lm4_spi_addr(ch, offset))
+#define LM4_SSI_CR0(ch) LM4SSIREG(ch, 0x000)
+#define LM4_SSI_CR1(ch) LM4SSIREG(ch, 0x004)
+#define LM4_SSI_DR(ch) LM4SSIREG(ch, 0x008)
+#define LM4_SSI_SR(ch) LM4SSIREG(ch, 0x00c)
+#define LM4_SSI_SR_TFE (1 << 0) /* Transmit FIFO empty */
+#define LM4_SSI_SR_TNF (1 << 1) /* Transmit FIFO not full */
+#define LM4_SSI_SR_RNE (1 << 2) /* Receive FIFO not empty */
+#define LM4_SSI_SR_RFF (1 << 3) /* Receive FIFO full */
+#define LM4_SSI_SR_BSY (1 << 4) /* Busy */
+#define LM4_SSI_CPSR(ch) LM4SSIREG(ch, 0x010)
+#define LM4_SSI_IM(ch) LM4SSIREG(ch, 0x014)
+#define LM4_SSI_RIS(ch) LM4SSIREG(ch, 0x018)
+#define LM4_SSI_MIS(ch) LM4SSIREG(ch, 0x01c)
+#define LM4_SSI_ICR(ch) LM4SSIREG(ch, 0x020)
+#define LM4_SSI_DMACTL(ch) LM4SSIREG(ch, 0x024)
+#define LM4_SSI_CC(ch) LM4SSIREG(ch, 0xfc8)
+
#define LM4_ADC_ADCACTSS LM4REG(0x40038000)
#define LM4_ADC_ADCRIS LM4REG(0x40038004)
#define LM4_ADC_ADCIM LM4REG(0x40038008)
@@ -222,6 +246,7 @@ static inline int lm4_fan_addr(int ch, int offset)
#define LM4_SYSTEM_RCGCDMA LM4REG(0x400fe60c)
#define LM4_SYSTEM_RCGCHIB LM4REG(0x400fe614)
#define LM4_SYSTEM_RCGCUART LM4REG(0x400fe618)
+#define LM4_SYSTEM_RCGCSSI LM4REG(0x400fe61c)
#define LM4_SYSTEM_RCGCI2C LM4REG(0x400fe620)
#define LM4_SYSTEM_RCGCADC LM4REG(0x400fe638)
#define LM4_SYSTEM_RCGCLPC LM4REG(0x400fe648)
diff --git a/chip/lm4/spi.c b/chip/lm4/spi.c
new file mode 100644
index 0000000000..09a5ec03a8
--- /dev/null
+++ b/chip/lm4/spi.c
@@ -0,0 +1,179 @@
+/* Copyright (c) 2012 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.
+ */
+
+/* SPI module for Chrome EC */
+
+#include "board.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "registers.h"
+#include "spi.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_SPI, outstr)
+#define CPRINTF(format, args...) cprintf(CC_SPI, format, ## args)
+
+
+int spi_enable(int enable)
+{
+ if (enable) {
+ /* SSI0 on PA2(CLK), PA4(RX), PA5(TX) alternate function 2 */
+ gpio_set_alternate_function(LM4_GPIO_A, 0x34, 2);
+ /* Don't use the SSI0 frame output. CS# is a GPIO so we can
+ * keep it low during an entire transaction. */
+ gpio_set_flags(GPIO_SPI_CSn, GPIO_OUTPUT);
+ gpio_set_level(GPIO_SPI_CSn, 1);
+
+ /* Enable SSI port */
+ LM4_SSI_CR1(0) |= 0x02;
+ } else {
+ /* Disable SSI port */
+ LM4_SSI_CR1(0) &= ~0x02;
+
+ /* Make sure CS# is deselected */
+ gpio_set_level(GPIO_SPI_CSn, 1);
+ gpio_set_flags(GPIO_SPI_CSn, GPIO_HI_Z);
+
+ /* PA2,4,5 normal function (high-Z GPIOs) */
+ gpio_set_alternate_function(LM4_GPIO_A, 0x34, 0);
+ }
+
+ return EC_SUCCESS;
+}
+
+
+int spi_transaction(const uint8_t *txdata, int txlen,
+ uint8_t *rxdata, int rxlen)
+{
+ int totallen = txlen + rxlen;
+ int txcount = 0, rxcount = 0;
+ volatile uint32_t dummy __attribute__((unused));
+
+ /* Empty the receive FIFO */
+ while (LM4_SSI_SR(0) & LM4_SSI_SR_RNE)
+ dummy = LM4_SSI_DR(0);
+
+ /* Start transaction. Need to do this explicitly because the LM4
+ * SSI controller pulses its frame select every byte, and the EEPROM
+ * wants the chip select held low during the entire transaction. */
+ gpio_set_level(GPIO_SPI_CSn, 0);
+
+ while (rxcount < totallen) {
+ /* Handle received bytes if any. We just checked rxcount <
+ * totallen, so we don't need to worry about overflowing the
+ * receive buffer. */
+ if (LM4_SSI_SR(0) & LM4_SSI_SR_RNE) {
+ if (rxcount < txlen) {
+ /* Throw away bytes received while we were
+ transmitting */
+ dummy = LM4_SSI_DR(0);
+ } else
+ *(rxdata++) = LM4_SSI_DR(0);
+ rxcount++;
+ }
+
+ /* Transmit another byte if needed */
+ if ((LM4_SSI_SR(0) & LM4_SSI_SR_TNF) && txcount < totallen) {
+ if (txcount < txlen)
+ LM4_SSI_DR(0) = *(txdata++);
+ else {
+ /* Clock out dummy byte so we can clock in the
+ * response byte */
+ LM4_SSI_DR(0) = 0;
+ }
+ txcount++;
+ }
+ }
+
+ /* End transaction */
+ gpio_set_level(GPIO_SPI_CSn, 1);
+
+ return EC_SUCCESS;
+}
+
+/*****************************************************************************/
+/* Hooks */
+
+static int spi_init(void)
+{
+ volatile uint32_t scratch __attribute__((unused));
+
+ /* Enable the SPI module and delay a few clocks */
+ LM4_SYSTEM_RCGCSSI = 1;
+ scratch = LM4_SYSTEM_RCGCSSI;
+
+ LM4_SSI_CR1(0) = 0; /* Disable SSI */
+ LM4_SSI_CR0(0) = 0x0007; /* SCR=0, SPH=0, SPO=0, FRF=SPI, 8-bit */
+
+ /* Use PIOSC for clock. This limits us to 8MHz (PIOSC/2), but is
+ * simpler to configure and we don't need to worry about clock
+ * frequency changing when the PLL is disabled. If we really start
+ * using this, might be worth using the system clock and handling
+ * frequency change (like we do with PECI) so we can go faster. */
+ LM4_SSI_CC(0) = 1;
+ /* SSICLK = PIOSC / (CPSDVSR * (1 + SCR)
+ * = 16 MHz / (2 * (1 + 0))
+ * = 8 MHz */
+ LM4_SSI_CPSR(0) = 2;
+
+ /* Ensure the SPI port is disabled. This keeps us from interfering
+ * with the main chipset when we're not explicitly using the SPI
+ * bus. */
+ spi_enable(0);
+
+ return EC_SUCCESS;
+}
+DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT);
+
+/*****************************************************************************/
+/* Console commands */
+
+static int printrx(const char *desc, const uint8_t *txdata, int txlen,
+ int rxlen)
+{
+ uint8_t rxdata[32];
+ int rv;
+ int i;
+
+ rv = spi_transaction(txdata, txlen, rxdata, rxlen);
+ if (rv)
+ return rv;
+
+ ccprintf("%-12s:", desc);
+ for (i = 0; i < rxlen; i++)
+ ccprintf(" 0x%02x", rxdata[i]);
+ ccputs("\n");
+ return EC_SUCCESS;
+}
+
+
+static int command_spirom(int argc, char **argv)
+{
+ uint8_t txmandev[] = {0x90, 0x00, 0x00, 0x00};
+ uint8_t txjedec[] = {0x9f};
+ uint8_t txunique[] = {0x4b, 0x00, 0x00, 0x00, 0x00};
+ uint8_t txsr1[] = {0x05};
+ uint8_t txsr2[] = {0x35};
+
+ spi_enable(1);
+
+ printrx("Man/Dev ID", txmandev, sizeof(txmandev), 2);
+ printrx("JEDEC ID", txjedec, sizeof(txjedec), 3);
+ printrx("Unique ID", txunique, sizeof(txunique), 8);
+ printrx("Status reg 1", txsr1, sizeof(txsr1), 1);
+ printrx("Status reg 2", txsr2, sizeof(txsr2), 1);
+
+ spi_enable(0);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(spirom, command_spirom,
+ NULL,
+ "Test reading SPI EEPROM",
+ NULL);
diff --git a/common/console.c b/common/console.c
index e1c1365f11..eae942444b 100644
--- a/common/console.c
+++ b/common/console.c
@@ -41,6 +41,7 @@ static const char *channel_names[CC_CHANNEL_COUNT] = {
"port80",
"powerbtn",
"pwm",
+ "spi",
"system",
"task",
"usbcharge",
diff --git a/include/console.h b/include/console.h
index 3ad4150fe4..f0d131249d 100644
--- a/include/console.h
+++ b/include/console.h
@@ -44,6 +44,7 @@ enum console_channel {
CC_PORT80,
CC_POWERBTN,
CC_PWM,
+ CC_SPI,
CC_SYSTEM,
CC_TASK,
CC_USBCHARGE,
diff --git a/include/spi.h b/include/spi.h
index 00482db7e2..4af03bc365 100644
--- a/include/spi.h
+++ b/include/spi.h
@@ -8,4 +8,15 @@
#ifndef __CROS_EC_SPI_H
#define __CROS_EC_SPI_H
+/* Enable / disable the SPI port. When the port is disabled, all its I/O lines
+ * are high-Z so the EC won't interfere with other devices on the SPI bus. */
+int spi_enable(int enable);
+
+/* Issue a SPI transaction. Assumes SPI port has already been enabled.
+ * Transmits <txlen> bytes from <txdata>, throwing away the corresponding
+ * received data, then transmits <rxlen> dummy bytes, saving the received data
+ * in <rxdata>. */
+int spi_transaction(const uint8_t *txdata, int txlen,
+ uint8_t *rxdata, int rxlen);
+
#endif /* __CROS_EC_SPI_H */