From 71030319ec40c3fb299733d3abd4dcede28f1386 Mon Sep 17 00:00:00 2001 From: David Hendricks Date: Sat, 7 Apr 2012 21:46:06 -0700 Subject: 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 --- chip/stm32l/build.mk | 1 + chip/stm32l/registers.h | 30 +++++++- chip/stm32l/spi.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++++ include/spi.h | 14 ++++ 4 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 chip/stm32l/spi.c create mode 100644 include/spi.h 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 */ -- cgit v1.2.1