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>
This commit is contained in:
David Hendricks
2012-04-07 21:46:06 -07:00
committed by Simon Glass
parent c87611ff57
commit 71030319ec
4 changed files with 231 additions and 2 deletions

View File

@@ -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

View File

@@ -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

188
chip/stm32l/spi.c Normal file
View File

@@ -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;
}

14
include/spi.h Normal file
View File

@@ -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 */