mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-09 17:11:42 +00:00
Bitfields are now in registers.h where they belong. BUG=chrome-os-partner:20529 BRANCH=none TEST='crosec test' from u-boot still works Change-Id: I726550a32b61111c906c1b10c628c5e47eff74fb Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/60179
273 lines
6.8 KiB
C
273 lines
6.8 KiB
C
/* Copyright (c) 2013 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 "common.h"
|
|
#include "console.h"
|
|
#include "dma.h"
|
|
#include "hooks.h"
|
|
#include "registers.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_DMA, outstr)
|
|
#define CPRINTF(format, args...) cprintf(CC_DMA, format, ## args)
|
|
|
|
/* Task IDs for the interrupt handlers to wake up */
|
|
static task_id_t id[STM32_DMAC_COUNT];
|
|
|
|
/**
|
|
* Return the IRQ for the DMA channel
|
|
*
|
|
* @param channel Channel number
|
|
* @return IRQ for the channel
|
|
*/
|
|
static int dma_get_irq(enum dma_channel channel)
|
|
{
|
|
return STM32_IRQ_DMA_CHANNEL_1 + channel;
|
|
}
|
|
|
|
/*
|
|
* Note, you must decrement the channel value by 1 from what is specified
|
|
* in the datasheets, as they index from 1 and this indexes from 0!
|
|
*/
|
|
stm32_dma_chan_t *dma_get_channel(enum dma_channel channel)
|
|
{
|
|
stm32_dma_regs_t *dma = STM32_DMA1_REGS;
|
|
|
|
return &dma->chan[channel];
|
|
}
|
|
|
|
void dma_disable(enum dma_channel channel)
|
|
{
|
|
stm32_dma_chan_t *chan = dma_get_channel(channel);
|
|
|
|
if (chan->ccr & STM32_DMA_CCR_EN)
|
|
chan->ccr &= ~STM32_DMA_CCR_EN;
|
|
}
|
|
|
|
/**
|
|
* Prepare a channel for use and start it
|
|
*
|
|
* @param chan Channel to read
|
|
* @param count Number of bytes to transfer
|
|
* @param periph Pointer to peripheral data register
|
|
* @param memory Pointer to memory address for receive/transmit
|
|
* @param flags DMA flags for the control register, normally:
|
|
* STM32_DMA_CCR_MINC | STM32_DMA_CCR_DIR for tx
|
|
* 0 for rx
|
|
*/
|
|
static void prepare_channel(stm32_dma_chan_t *chan, unsigned count,
|
|
void *periph, void *memory, unsigned flags)
|
|
{
|
|
uint32_t ccr = STM32_DMA_CCR_PL_VERY_HIGH;
|
|
|
|
if (chan->ccr & STM32_DMA_CCR_EN)
|
|
chan->ccr &= ~STM32_DMA_CCR_EN;
|
|
|
|
/* Following the order in Doc ID 15965 Rev 5 p194 */
|
|
chan->cpar = (uint32_t)periph;
|
|
chan->cmar = (uint32_t)memory;
|
|
chan->cndtr = count;
|
|
chan->ccr = ccr;
|
|
ccr |= flags;
|
|
chan->ccr = ccr;
|
|
}
|
|
|
|
void dma_go(stm32_dma_chan_t *chan)
|
|
{
|
|
/* Fire it up */
|
|
chan->ccr |= STM32_DMA_CCR_EN;
|
|
}
|
|
|
|
void dma_prepare_tx(const struct dma_option *option, unsigned count,
|
|
const void *memory)
|
|
{
|
|
stm32_dma_chan_t *chan = dma_get_channel(option->channel);
|
|
|
|
/*
|
|
* Cast away const for memory pointer; this is ok because we know
|
|
* we're preparing the channel for transmit.
|
|
*/
|
|
prepare_channel(chan, count, option->periph, (void *)memory,
|
|
STM32_DMA_CCR_MINC | STM32_DMA_CCR_DIR |
|
|
option->flags);
|
|
}
|
|
|
|
void dma_start_rx(const struct dma_option *option, unsigned count,
|
|
void *memory)
|
|
{
|
|
stm32_dma_chan_t *chan = dma_get_channel(option->channel);
|
|
|
|
prepare_channel(chan, count, option->periph, memory,
|
|
STM32_DMA_CCR_MINC | option->flags);
|
|
dma_go(chan);
|
|
}
|
|
|
|
int dma_bytes_done(stm32_dma_chan_t *chan, int orig_count)
|
|
{
|
|
if (!(chan->ccr & STM32_DMA_CCR_EN))
|
|
return 0;
|
|
return orig_count - chan->cndtr;
|
|
}
|
|
|
|
#ifdef CONFIG_DMA_HELP
|
|
void dma_dump(enum dma_channel channel)
|
|
{
|
|
stm32_dma_regs_t *dma = STM32_DMA1_REGS;
|
|
stm32_dma_chan_t *chan = dma_get_channel(channel);
|
|
|
|
CPRINTF("ccr=%x, cndtr=%x, cpar=%x, cmar=%x\n", chan->ccr,
|
|
chan->cndtr, chan->cpar, chan->cmar);
|
|
CPRINTF("chan %d, isr=%x, ifcr=%x\n",
|
|
channel,
|
|
(dma->isr >> (channel * 4)) & 0xf,
|
|
(dma->ifcr >> (channel * 4)) & 0xf);
|
|
}
|
|
|
|
void dma_check(enum dma_channel channel, char *buf)
|
|
{
|
|
stm32_dma_chan_t *chan;
|
|
int count;
|
|
int i;
|
|
|
|
chan = dma_get_channel(channel);
|
|
count = chan->cndtr;
|
|
CPRINTF("c=%d\n", count);
|
|
udelay(100 * MSEC);
|
|
CPRINTF("c=%d\n", chan->cndtr);
|
|
for (i = 0; i < count; i++)
|
|
CPRINTF("%02x ", buf[i]);
|
|
udelay(100 * MSEC);
|
|
CPRINTF("c=%d\n", chan->cndtr);
|
|
for (i = 0; i < count; i++)
|
|
CPRINTF("%02x ", buf[i]);
|
|
}
|
|
|
|
/* Run a check of memory-to-memory DMA */
|
|
void dma_test(void)
|
|
{
|
|
enum dma_channel channel = STM32_DMAC_CH4;
|
|
stm32_dma_chan_t *chan = dma_get_channel(channel);
|
|
uint32_t ctrl;
|
|
char periph[16], memory[16];
|
|
unsigned count = sizeof(periph);
|
|
int i;
|
|
|
|
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 */
|
|
chan->cpar = (uint32_t)periph;
|
|
chan->cmar = (uint32_t)memory;
|
|
chan->cndtr = count;
|
|
ctrl = STM32_DMA_CCR_PL_MEDIUM;
|
|
chan->ccr = ctrl;
|
|
|
|
ctrl |= STM32_DMA_CCR_MINC; /* | STM32_DMA_CCR_DIR */;
|
|
ctrl |= STM32_DMA_CCR_MEM2MEM;
|
|
ctrl |= STM32_DMA_CCR_PINC;
|
|
/* ctrl |= STM32_DMA_CCR_MSIZE_32_BIT; */
|
|
/* ctrl |= STM32_DMA_CCR_PSIZE_32_BIT; */
|
|
chan->ccr = ctrl;
|
|
chan->ccr = ctrl | STM32_DMA_CCR_EN;
|
|
|
|
for (i = 0; i < count; i++)
|
|
CPRINTF("%d/%d ", periph[i], memory[i]);
|
|
CPRINTF("\ncount=%d\n", chan->cndtr);
|
|
}
|
|
#endif /* CONFIG_DMA_HELP */
|
|
|
|
static void dma_init(void)
|
|
{
|
|
int i;
|
|
|
|
/* Enable DMA1; current chips don't have DMA2 */
|
|
STM32_RCC_AHBENR |= STM32_RCC_HB_DMA1;
|
|
|
|
/* Initialize data for interrupt handlers */
|
|
for (i = 0; i < STM32_DMAC_COUNT; i++)
|
|
id[i] = TASK_ID_INVALID;
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, dma_init, HOOK_PRIO_INIT_DMA);
|
|
|
|
int dma_wait(enum dma_channel channel)
|
|
{
|
|
stm32_dma_regs_t *dma = STM32_DMA1_REGS;
|
|
const uint32_t mask = STM32_DMA_ISR_TCIF(channel);
|
|
timestamp_t deadline;
|
|
|
|
deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
|
|
while ((dma->isr & mask) != mask) {
|
|
if (deadline.val <= get_time().val)
|
|
return EC_ERROR_TIMEOUT;
|
|
|
|
udelay(DMA_POLLING_INTERVAL_US);
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
void dma_enable_tc_interrupt(enum dma_channel channel)
|
|
{
|
|
stm32_dma_chan_t *chan = dma_get_channel(channel);
|
|
|
|
/* Store task ID so the ISR knows which task to wake */
|
|
id[channel] = task_get_current();
|
|
|
|
chan->ccr |= STM32_DMA_CCR_TCIE;
|
|
task_enable_irq(dma_get_irq(channel));
|
|
}
|
|
|
|
void dma_disable_tc_interrupt(enum dma_channel channel)
|
|
{
|
|
stm32_dma_chan_t *chan = dma_get_channel(channel);
|
|
|
|
id[channel] = TASK_ID_INVALID;
|
|
|
|
chan->ccr &= ~STM32_DMA_CCR_TCIE;
|
|
task_disable_irq(dma_get_irq(channel));
|
|
}
|
|
|
|
void dma_clear_isr(enum dma_channel channel)
|
|
{
|
|
stm32_dma_regs_t *dma = STM32_DMA1_REGS;
|
|
|
|
dma->ifcr |= STM32_DMA_ISR_ALL(channel);
|
|
}
|
|
|
|
static void dma_event_interrupt_channel_4(void)
|
|
{
|
|
dma_clear_isr(STM32_DMAC_CH4);
|
|
if (id[STM32_DMAC_CH4] != TASK_ID_INVALID)
|
|
task_wake(id[STM32_DMAC_CH4]);
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_4, dma_event_interrupt_channel_4, 3);
|
|
|
|
static void dma_event_interrupt_channel_5(void)
|
|
{
|
|
dma_clear_isr(STM32_DMAC_CH5);
|
|
if (id[STM32_DMAC_CH5] != TASK_ID_INVALID)
|
|
task_wake(id[STM32_DMAC_CH5]);
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_5, dma_event_interrupt_channel_5, 3);
|
|
|
|
static void dma_event_interrupt_channel_6(void)
|
|
{
|
|
dma_clear_isr(STM32_DMAC_CH6);
|
|
if (id[STM32_DMAC_CH6] != TASK_ID_INVALID)
|
|
task_wake(id[STM32_DMAC_CH6]);
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_6, dma_event_interrupt_channel_6, 3);
|
|
|
|
static void dma_event_interrupt_channel_7(void)
|
|
{
|
|
dma_clear_isr(STM32_DMAC_CH7);
|
|
if (id[STM32_DMAC_CH7] != TASK_ID_INVALID)
|
|
task_wake(id[STM32_DMAC_CH7]);
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_7, dma_event_interrupt_channel_7, 3);
|