summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Hendricks <dhendrix@chromium.org>2012-04-07 21:46:06 -0700
committerSimon Glass <sjg@chromium.org>2012-04-12 15:52:07 -0700
commit71030319ec40c3fb299733d3abd4dcede28f1386 (patch)
treed0df3b58a1b93517bdb12f8e71c998444174efca
parentc87611ff5796fb1f322f3350398985ad26002c26 (diff)
downloadchrome-ec-71030319ec40c3fb299733d3abd4dcede28f1386.tar.gz
stm32l: Add basic SPI driver
Add a SPI driver which can receive and process commands, and provide responses using the message interface. BUG=chromium-os:28925 TEST=build on daisy and discovery; run on daisy Change-Id: I286da803b85640525607de6c4d41f0629f7006dc Signed-off-by: Simon Glass <sjg@chromium.org>
-rw-r--r--chip/stm32l/build.mk1
-rw-r--r--chip/stm32l/registers.h30
-rw-r--r--chip/stm32l/spi.c188
-rw-r--r--include/spi.h14
4 files changed, 231 insertions, 2 deletions
diff --git a/chip/stm32l/build.mk b/chip/stm32l/build.mk
index d1fc7832bf..976f1565f2 100644
--- a/chip/stm32l/build.mk
+++ b/chip/stm32l/build.mk
@@ -9,5 +9,6 @@
CORE:=cortex-m
chip-y=clock.o dma.o gpio.o hwtimer.o jtag.o system.o uart.o
+chip-$(CONFIG_SPI)+=spi.o
chip-$(CONFIG_TASK_WATCHDOG)+=watchdog.o
chip-$(CONFIG_TASK_KEYSCAN)+=keyboard_scan.o
diff --git a/chip/stm32l/registers.h b/chip/stm32l/registers.h
index 43e57c1d5d..634adeaa00 100644
--- a/chip/stm32l/registers.h
+++ b/chip/stm32l/registers.h
@@ -251,6 +251,34 @@
#define STM32L_RTC_TAFCR REG32(STM32L_RTC_BASE + 0x40)
#define STM32L_RTC_BACKUP(n) REG32(STM32L_RTC_BASE + 0x50 + 4 * (n))
+/* --- SPI --- */
+#define STM32L_SPI1_BASE 0x40013000
+#define STM32L_SPI2_BASE 0x40003800
+
+#define STM32L_SPI1_PORT 0
+#define STM32L_SPI2_PORT 1
+
+/*
+ * TODO(vpalatin):
+ * For whatever reason, our toolchain is substandard and generate a
+ * function every time you are using this inline function.
+ *
+ * That's why I have not used inline stuff in the registers definition.
+ */
+#define stm32l_spi_addr(port, offset) \
+ ((port == 0) ? \
+ (STM32L_SPI1_BASE + offset) : \
+ (STM32L_SPI2_BASE + offset))
+
+#define STM32L_SPI_REG16(p, l) REG16(stm32l_spi_addr((p), l))
+#define STM32L_SPI_CR1(p) STM32L_SPI_REG16((p), 0x00)
+#define STM32L_SPI_CR2(p) STM32L_SPI_REG16((p), 0x04)
+#define STM32L_SPI_SR(p) STM32L_SPI_REG16((p), 0x08)
+#define STM32L_SPI_DR(p) STM32L_SPI_REG16((p), 0x0c)
+#define STM32L_SPI_CRCPR(p) STM32L_SPI_REG16((p), 0x10)
+#define STM32L_SPI_RXCRCR(p) STM32L_SPI_REG16((p), 0x14)
+#define STM32L_SPI_TXCRCR(p) STM32L_SPI_REG16((p), 0x18)
+
/* --- Debug --- */
#define STM32L_DBGMCU_BASE 0xE0042000
@@ -283,8 +311,6 @@
#define STM32L_ADC_BASE 0x40012700
#define STM32L_COMP_BASE 0x40007C00
#define STM32L_DAC_BASE 0x40007400
-#define STM32L_SPI1_BASE 0x40013000
-#define STM32L_SPI2_BASE 0x40003800
#define STM32L_CRC_BASE 0x40023000
#define STM32L_LCD_BASE 0x40002400
#define STM32L_DMA1_BASE 0x40026000
diff --git a/chip/stm32l/spi.c b/chip/stm32l/spi.c
new file mode 100644
index 0000000000..c3878b50d2
--- /dev/null
+++ b/chip/stm32l/spi.c
@@ -0,0 +1,188 @@
+/*
+ * SPI driver for Chrome EC.
+ *
+ * This uses DMA although not in an optimal way yet.
+ *
+ * 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.
+ */
+
+#include "console.h"
+#include "dma.h"
+#include "gpio.h"
+#include "message.h"
+#include "registers.h"
+#include "spi.h"
+#include "task.h"
+#include "timer.h"
+#include "uart.h"
+#include "util.h"
+
+
+/* Status register flags that we use */
+enum {
+ SR_RXNE = 1 << 0,
+ SR_TXE = 1 << 1,
+ SR_BSY = 1 << 7,
+
+ CR1_SPE = 1 << 6,
+
+ CR2_RXDMAEN = 1 << 0,
+ CR2_TXDMAEN = 1 << 1,
+ CR2_RXNEIE = 1 << 6,
+};
+
+/*
+ * Our input and output buffers. These must be large enough for our largest
+ * message, including protocol overhead.
+ */
+static char out_msg[32 + MSG_PROTO_BYTES];
+static char in_msg[32 + MSG_PROTO_BYTES];
+
+/**
+ * Monitor the SPI bus
+ *
+ * At present this function is very simple - it hangs the system until we
+ * have sent the response, then clears things away. This is equivalent to
+ * not using DMA at all.
+ *
+ * TODO(sjg): Use an interrupt on NSS to triggler this function.
+ *
+ */
+void spi_work_task(void)
+{
+ int port = STM32L_SPI1_PORT;
+
+ while (1) {
+ task_wait_event(-1);
+
+ /* Wait for the master to let go of our slave select */
+ while (!gpio_get_level(GPIO_SPI1_NSS))
+ ;
+
+ /* Transfer is now complete, so reset everything */
+ dma_disable(DMA_CHANNEL_FOR_SPI_RX(port));
+ dma_disable(DMA_CHANNEL_FOR_SPI_TX(port));
+ STM32L_SPI_CR2(port) &= ~CR2_TXDMAEN;
+ STM32L_SPI_DR(port) = 0xff;
+ }
+}
+
+/**
+ * Send a reply on a given port.
+ *
+ * The format of a reply is as per the command interface, with a number of
+ * preamble bytes before it.
+ *
+ * The preamble is typically 2 bytes, but can be longer if the STM takes ages
+ * to react to the incoming message. Since we send our first byte as the AP
+ * sends us the command, we clearly can't send anything sensible for that
+ * byte. The second byte must be written to the output register just when the
+ * command byte is ready (I think), so we can't do anything there either.
+ * Any processing we do may increase this delay. That's the reason for the
+ * preamble.
+ *
+ * It is interesting to note that it seems to be possible to run the SPI
+ * interface faster than the CPU clock with this approach.
+ *
+ * @param port Port to send reply back on (STM32L_SPI0/1_PORT)
+ * @param msg Message to send
+ * @param msg_len Length of message in bytes
+ */
+static void reply(int port, char *msg, int msg_len)
+{
+ int dmac;
+
+ /*
+ * This method is not really suitable for very large messages. If
+ * we need these, we should set up a second DMA transfer to do
+ * the message, and then a third to do the trailer, rather than
+ * copying the message around.
+ */
+ STM32L_SPI_CR2(port) |= CR2_TXDMAEN;
+ dmac = DMA_CHANNEL_FOR_SPI_TX(port);
+ dma_start_tx(dmac, msg_len, (void *)&STM32L_SPI_DR(port), out_msg);
+}
+
+/**
+ * Handles an interrupt on the specified port.
+ *
+ * This signals the start of a transfer. We read the command byte (which is
+ * the first byte), star the RX DMA and set up our reply accordingly.
+ *
+ * We will not get interrupts on subsequent bytes since the DMA will handle
+ * the incoming data.
+ *
+ * @param port Port that the interrupt came in on (STM32L_SPI0/1_PORT)
+ */
+static void spi_interrupt(int port)
+{
+ int msg_len;
+ char *buff;
+ int dmac;
+ int cmd;
+
+ /* Make sure there is a byte available */
+ if (!(STM32L_SPI_SR(port) & SR_RXNE))
+ return;
+
+ /* Get the command byte */
+ cmd = STM32L_SPI_DR(port);
+
+ /* Read the rest of the message - for now we do nothing with it */
+ dmac = DMA_CHANNEL_FOR_SPI_RX(port);
+ dma_start_rx(dmac, sizeof(in_msg), (void *)&STM32L_SPI_DR(port),
+ in_msg);
+
+ /*
+ * Process the command and send the reply. We provide our output
+ * buffer as a suggested location for reply, since this may stop us
+ * needing to copy the message.
+ */
+ buff = out_msg;
+ msg_len = message_process_cmd(cmd, out_msg, sizeof(out_msg));
+ if (msg_len >= 0)
+ reply(port, buff, msg_len);
+
+ /* Wake up the task that watches for end of the incoming message */
+ task_wake(TASK_ID_SPI_WORK);
+}
+
+/* The interrupt code cannot pass a parameters, so handle this here */
+static void spi1_interrupt(void) { spi_interrupt(STM32L_SPI1_PORT); };
+
+DECLARE_IRQ(STM32L_IRQ_SPI1, spi1_interrupt, 2);
+
+int spi_init(void)
+{
+ int port;
+
+#if defined(BOARD_discovery) || defined(BOARD_daisy)
+ /**
+ * SPI1
+ * PA7: SPI1_MOSI
+ * PA6: SPI1_MISO
+ * PA5: SPI1_SCK
+ * PA4: SPI1_NSS
+ *
+ * 8-bit data, master mode, full-duplex, clock is fpclk / 2
+ */
+ port = STM32L_SPI1_PORT;
+
+ /* enable rx buffer not empty interrupt, and rx DMA */
+ STM32L_SPI_CR2(port) |= CR2_RXNEIE | CR2_RXDMAEN;
+
+ /* set up an interrupt when we get the first byte of a packet */
+ task_enable_irq(STM32L_IRQ_SPI1);
+
+ /* write 0xff which will be our default output value */
+ STM32L_SPI_DR(port) = 0xff;
+
+ /* enable the SPI peripheral */
+ STM32L_SPI_CR1(port) |= CR1_SPE;
+#else
+#error "Need to know how to set up SPI for this board"
+#endif
+ return EC_SUCCESS;
+}
diff --git a/include/spi.h b/include/spi.h
new file mode 100644
index 0000000000..45ea75231f
--- /dev/null
+++ b/include/spi.h
@@ -0,0 +1,14 @@
+/* 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 interface for Chrome EC */
+
+#ifndef __CROS_EC_SPI_H
+#define __CROS_EC_SPI_H
+
+/* Initialize the SPI module ready for use */
+extern int spi_init(void);
+
+#endif /* __CROS_EC_SPI_H */