diff options
author | Gerrit <chrome-bot@google.com> | 2012-05-30 14:16:02 -0700 |
---|---|---|
committer | Gerrit Code Review <gerrit@gerrit.golo.chromium.org> | 2012-05-30 14:16:02 -0700 |
commit | defbdc32d3ee94ce3c3c104049f5ec71e6e80d47 (patch) | |
tree | 25fe89fbe97502c26a30cf70625469f55f47aa58 | |
parent | aad3f858a436bc6c763d4ddf8a49fad6465302da (diff) | |
parent | 858d87cfaae5182b1d6cf008a7e33766612ab000 (diff) | |
download | chrome-ec-defbdc32d3ee94ce3c3c104049f5ec71e6e80d47.tar.gz |
Merge "Add basic SPI support to link"
-rw-r--r-- | board/link/board.c | 1 | ||||
-rw-r--r-- | board/link/board.h | 2 | ||||
-rw-r--r-- | chip/lm4/build.mk | 1 | ||||
-rw-r--r-- | chip/lm4/registers.h | 25 | ||||
-rw-r--r-- | chip/lm4/spi.c | 179 | ||||
-rw-r--r-- | common/console.c | 1 | ||||
-rw-r--r-- | include/console.h | 1 | ||||
-rw-r--r-- | include/spi.h | 11 |
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 */ |