From 9e50df36b31a46fac4c8731a3e71ec6406f7e090 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 3 Apr 2012 16:17:55 -0700 Subject: [PATCH] Add support for DMA controller Add some basic functions to start DMA operations for transmit and receive. BUG=chromium-os:28925 TEST=build on daisy and discovery; run on daisy Change-Id: Ifceeed2af80cf5f00e1ce1a49b1139a76585b0bf Signed-off-by: Simon Glass --- chip/stm32l/build.mk | 2 +- chip/stm32l/dma.c | 167 ++++++++++++++++++++++++++++++++++++++++ chip/stm32l/dma.h | 127 ++++++++++++++++++++++++++++++ chip/stm32l/registers.h | 2 + 4 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 chip/stm32l/dma.c create mode 100644 chip/stm32l/dma.h diff --git a/chip/stm32l/build.mk b/chip/stm32l/build.mk index 6d46ed91f6..d1fc7832bf 100644 --- a/chip/stm32l/build.mk +++ b/chip/stm32l/build.mk @@ -8,6 +8,6 @@ # STM32L15xx SoC family has a Cortex-M3 ARM core CORE:=cortex-m -chip-y=clock.o gpio.o hwtimer.o jtag.o system.o uart.o +chip-y=clock.o dma.o gpio.o hwtimer.o jtag.o system.o uart.o chip-$(CONFIG_TASK_WATCHDOG)+=watchdog.o chip-$(CONFIG_TASK_KEYSCAN)+=keyboard_scan.o diff --git a/chip/stm32l/dma.c b/chip/stm32l/dma.c new file mode 100644 index 0000000000..6ab971d34b --- /dev/null +++ b/chip/stm32l/dma.c @@ -0,0 +1,167 @@ +/* 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 "dma.h" +#include "registers.h" +#include "timer.h" +#include "uart.h" +#include "util.h" + + +/** + * Get a pointer to a DMA channel. + * + * @param channel Channel number to read (DMAC_...) + * @return pointer to DMA channel registers + */ +static struct dma_channel *get_channel(int channel) +{ + struct dma_channel *chan; + struct dma_ctlr *dma; + + /* Get a pointer to the correct controller and channel */ + ASSERT(channel < DMA_NUM_CHANNELS); + if (channel < DMA1_NUM_CHANNELS) { + dma = (struct dma_ctlr *)STM32L_DMA1_BASE; + chan = &dma->chan[channel]; + } else { + dma = (struct dma_ctlr *)STM32L_DMA2_BASE; + chan = &dma->chan[channel - DMA1_NUM_CHANNELS]; + } + + return chan; +} + +void dma_disable(unsigned channel) +{ + struct dma_channel *chan; + + chan = get_channel(channel); + + if (REG32(&chan->ccr) & DMA_EN) + REG32(&chan->ccr) &= ~DMA_EN; +} + +/** + * Prepare a channel for use and start it + * + * @param channel Channel number to read (DMAC_...) + * @param count Number of bytes to transfer + * @param periph Pointer to peripheral data register + * @param memory Pointer to memory address + * @param flags DMA flags for the control register, normally: + * DMA_DIR_FROM_MEM_MASK for tx + * 0 for rx + * @return 0 if ok, -1 on error + */ +static int prepare_channel(unsigned channel, unsigned count, void *periph, + const void *memory, unsigned flags) +{ + struct dma_channel *chan; + uint32_t ctrl; + + chan = get_channel(channel); + + if (REG32(&chan->ccr) & DMA_EN) + REG32(&chan->ccr) &= ~DMA_EN; + + /* Following the order in Doc ID 15965 Rev 5 p194 */ + REG32(&chan->cpar) = (uint32_t)periph; + REG32(&chan->cmar) = (uint32_t)memory; + REG32(&chan->cndtr) = count; + ctrl = DMA_PL_VERY_HIGH << DMA_PL_SHIFT; + REG32(&chan->ccr) = ctrl; + + ctrl |= DMA_MINC_MASK | flags; + ctrl |= 0 << 10; /* MSIZE (memory size in bytes) */ + ctrl |= 1 << 8; /* PSIZE (16-bits for now) */ + REG32(&chan->ccr) = ctrl; + + /* Fire it up */ + ctrl |= DMA_EN; + REG32(&chan->ccr) = ctrl; + + return 0; +} + +int dma_start_tx(unsigned channel, unsigned count, void *periph, + const void *memory) +{ + return prepare_channel(channel, count, periph, memory, + DMA_DIR_FROM_MEM_MASK); +} + +int dma_start_rx(unsigned channel, unsigned count, void *periph, + const void *memory) +{ + return prepare_channel(channel, count, periph, memory, 0); +} + +/* Hide this code behind an undefined CONFIG for now */ +#ifdef CONFIG_DMA_TEST + +void dma_check(int channel, char *buff) +{ + struct dma_channel *chan; + int count; + int i; + + chan = get_channel(channel); + count = REG32(&chan->cndtr); + uart_printf("c=%d\n", count); + udelay(1000 * 100); + uart_printf("c=%d\n", + REG32(&chan->cndtr)); + for (i = 0; i < count; i++) + uart_printf("%02x ", buff[i]); + udelay(1000 * 100); + uart_printf("c=%d\n", + REG32(&chan->cndtr)); + for (i = 0; i < count; i++) + uart_printf("%02x ", buff[i]); +} + +/* Run a check of memory-to-memory DMA */ +void dma_test(void) +{ + unsigned channel = 3; + struct dma_channel *chan; + uint32_t ctrl; + char periph[16], memory[16]; + unsigned count = sizeof(periph); + int i; + + chan = get_channel(channel); + memset(memory, '\0', sizeof(memory)); + for (i = 0; i < count; i++) + periph[i] = 10 + i; + + /* Following the order in Doc ID 15965 Rev 5 p194 */ + REG32(&chan->cpar) = (uint32_t)periph; + REG32(&chan->cmar) = (uint32_t)memory; + REG32(&chan->cndtr) = count; + ctrl = DMA_PL_MEDIUM << DMA_PL_SHIFT; + REG32(&chan->ccr) = ctrl; + + ctrl |= DMA_MINC_MASK; /* | DMA_DIR_FROM_MEM_MASK */; + ctrl |= 1 << 14; /* MEM2MEM */ + ctrl |= 1 << 6; /* PINC */ +/* ctrl |= 2 << 10; */ +/* ctrl |= 2 << 8; */ + REG32(&chan->ccr) = ctrl; + + ctrl |= DMA_EN; + REG32(&chan->ccr) = ctrl; + for (i = 0; i < count; i++) + uart_printf("%d/%d ", periph[i], memory[i]); + uart_printf("\ncount=%d\n", REG32(&chan->cndtr)); +} +#endif /* CONFIG_TEST */ + +void dma_init(void) +{ + /* Enable DMA1, we don't support DMA2 yet */ + STM32L_RCC_AHBENR |= 1 << 24; +} diff --git a/chip/stm32l/dma.h b/chip/stm32l/dma.h new file mode 100644 index 0000000000..a35786da34 --- /dev/null +++ b/chip/stm32l/dma.h @@ -0,0 +1,127 @@ +/* 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. + * + * Register map and API for STM32L processor dma registers + */ + +#ifndef __STM32L_DMA +#define __STM32L_DMA + +#include "common.h" + +/* + * Available DMA channels, numbered from 0 + * + * Note: The STM datasheet tends to number things from 1. We should ask + * the European elevator engineers to talk to MCU engineer counterparts + * about this. + */ +enum { + DMAC_ADC, + DMAC_SPI1_RX, + DMAC_SPI1_TX, + DMAC_SPI2_RX, + DMAC_SPI2_TX, + + /* DMA1 has 7 channels, DMA2 has 5 */ + DMA1_NUM_CHANNELS = 7, + DMA2_NUM_CHANNELS = 5, + DMA_NUM_CHANNELS = DMA1_NUM_CHANNELS + DMA2_NUM_CHANNELS, +}; + +/* A single channel of the DMA controller */ +struct dma_channel { + uint32_t ccr; /* Control */ + uint32_t cndtr; /* Number of data to transfer */ + uint32_t cpar; /* Peripheral address */ + uint32_t cmar; /* Memory address */ + uint32_t reserved; +}; + +/* Registers for the DMA controller */ +struct dma_ctlr { + uint32_t isr; + uint32_t ifcr; + struct dma_channel chan[DMA_NUM_CHANNELS]; +}; + +/* Defines for accessing DMA ccr */ +#define DMA_PL_SHIFT 12 +#define DMA_PL_MASK (3 << DMA_PL_SHIFT) +enum { + DMA_PL_LOW, + DMA_PL_MEDIUM, + DMA_PL_HIGH, + DMA_PL_VERY_HIGH, +}; + +#define DMA_MINC_MASK (1 << 7) +#define DMA_DIR_FROM_MEM_MASK (1 << 4) +#define DMA_EN (1 << 0) + +/* + * Certain DMA channels must be used for certain peripherals and transfer + * directions. We provide an easy way for drivers to select the correct + * channel. + */ + +/** + * @param spi SPI port to request: STM32L_SPI1_PORT or STM32L_SPI2_PORT + * @return DMA channel to use for rx / tx on that port + */ +#define DMA_CHANNEL_FOR_SPI_RX(spi) \ + ((spi) == STM32L_SPI1_PORT ? DMAC_SPI1_RX : DMAC_SPI2_RX) +#define DMA_CHANNEL_FOR_SPI_TX(spi) \ + ((spi) == STM32L_SPI1_PORT ? DMAC_SPI1_TX : DMAC_SPI2_TX) + +/** + * Start a DMA transfer to transmit data from memory to a peripheral + * + * @param channel Channel number to read (DMAC_...) + * @param count Number of bytes to transfer + * @param periph Pointer to peripheral data register + * @param memory Pointer to memory address + */ +int dma_start_tx(unsigned channel, unsigned count, void *periph, + const void *memory); + +/** + * Start a DMA transfer to receive data to memory from a peripheral + * + * @param channel Channel number to read (DMAC_...) + * @param count Number of bytes to transfer + * @param periph Pointer to peripheral data register + * @param memory Pointer to memory address + */ +int dma_start_rx(unsigned channel, unsigned count, void *periph, + const void *memory); + +/** + * Stop a DMA transfer on a channel + * + * Disable the DMA channel and immediate stop all transfers on it. + * + * @param channel Channel number to stop (DMAC_...) + */ +void dma_disable(unsigned channel); + +/** + * Testing: Print out the data transferred by a channel + * + * @param channel Channel number to read (DMAC_...) + * @param buff Start of DMA buffer + */ +void dma_check(int channel, char *buff); + +/** + * Testing: Test that DMA works correctly for memory to memory transfers + */ +void dma_test(void); + +/** + * Init DMA peripheral ready for use + */ +void dma_init(void); + +#endif diff --git a/chip/stm32l/registers.h b/chip/stm32l/registers.h index 99017990ae..43e57c1d5d 100644 --- a/chip/stm32l/registers.h +++ b/chip/stm32l/registers.h @@ -287,5 +287,7 @@ #define STM32L_SPI2_BASE 0x40003800 #define STM32L_CRC_BASE 0x40023000 #define STM32L_LCD_BASE 0x40002400 +#define STM32L_DMA1_BASE 0x40026000 +#define STM32L_DMA2_BASE 0x40026400 #endif /* __STM32L_REGISTERS */