summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2012-05-29 16:53:05 -0700
committerRandall Spangler <rspangler@chromium.org>2012-05-30 13:12:34 -0700
commit858d87cfaae5182b1d6cf008a7e33766612ab000 (patch)
treead49348fae3e76b587b4446cbb9af01f70386697
parent6654374f31f562dfd0012f83ca4d58735458e4b2 (diff)
downloadchrome-ec-858d87cfaae5182b1d6cf008a7e33766612ab000.tar.gz
Add basic SPI support to link
This adds SPI transaction support, and a debug command to read a few values from the SPI EEPROM. Note that the SPI controller is normally *disabled* with all its I/Os high-Z, so this will not interfere with main processor or Servo on the SPI bus. The bus is only enabled during the SPIROM command itself. BUG=chrome-os-partner:7844 TEST=manual 1) Reboot system 2) on EC console, 'spirom'. Should print Man/Dev ID : 0xef 0x16 JEDEC ID : 0xef 0x40 0x17 Unique ID : 0xd1 0x61 0x44 0xb0 0x63 0x5d 0x40 0x32 Status reg 1: 0x00 Status reg 2: 0x00 Note that unique ID is, well, unique, so it won't match my value. But it should still be something not all 0xff's. 3) Power on the system. x86 should still boot normally, indicating that the EC isn't interfering with the SPI bus. Change-Id: I53bf5fdbbe7a37949375d0463e30e408cc6fb6a8
-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 */