| /* 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 "board.h" |
| #include "console.h" |
| #include "dma.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[DMA_NUM_CHANNELS]; |
| |
| /* |
| * 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! |
| */ |
| struct dma_channel *dma_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 *)STM32_DMA1_BASE; |
| chan = &dma->chan[channel]; |
| } else { |
| dma = (struct dma_ctlr *)STM32_DMA2_BASE; |
| chan = &dma->chan[channel - DMA1_NUM_CHANNELS]; |
| } |
| |
| return chan; |
| } |
| |
| void dma_disable(unsigned channel) |
| { |
| struct dma_channel *chan; |
| |
| chan = dma_get_channel(channel); |
| |
| if (REG32(&chan->ccr) & DMA_EN) |
| REG32(&chan->ccr) &= ~DMA_EN; |
| } |
| |
| /** |
| * Prepare a channel for use and start it |
| * |
| * @param chan 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_MINC_MASK | |
| * (DMA_DIR_FROM_MEM_MASK for tx |
| * 0 for rx) |
| */ |
| static void prepare_channel(struct dma_channel *chan, unsigned count, |
| void *periph, const void *memory, unsigned flags) |
| { |
| uint32_t ctrl; |
| |
| 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 |= flags; |
| ctrl |= 0 << 10; /* MSIZE (memory size in bytes) */ |
| ctrl |= 1 << 8; /* PSIZE (16-bits for now) */ |
| REG32(&chan->ccr) = ctrl; |
| } |
| |
| void dma_go(struct dma_channel *chan) |
| { |
| /* Fire it up */ |
| REG32(&chan->ccr) |= DMA_EN; |
| } |
| |
| void dma_prepare_tx(struct dma_channel *chan, unsigned count, void *periph, |
| const void *memory) |
| { |
| prepare_channel(chan, count, periph, memory, |
| DMA_MINC_MASK | DMA_DIR_FROM_MEM_MASK); |
| } |
| |
| int dma_start_rx(unsigned channel, unsigned count, void *periph, |
| const void *memory) |
| { |
| struct dma_channel *chan = dma_get_channel(channel); |
| |
| prepare_channel(chan, count, periph, memory, DMA_MINC_MASK); |
| dma_go(chan); |
| return 0; |
| } |
| |
| int dma_bytes_done(struct dma_channel *chan, int orig_count) |
| { |
| if (!(REG32(&chan->ccr) & DMA_EN)) |
| return 0; |
| return orig_count - REG32(&chan->cndtr); |
| } |
| |
| |
| #ifdef CONFIG_DMA_HELP |
| void dma_dump(unsigned channel) |
| { |
| struct dma_channel *chan; |
| struct dma_ctlr *dma; |
| |
| /* Get a pointer to the correct controller and channel */ |
| ASSERT(channel < DMA_NUM_CHANNELS); |
| dma = (struct dma_ctlr *)STM32_DMA1_BASE; |
| |
| 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(int channel, char *buff) |
| { |
| struct dma_channel *chan; |
| int count; |
| int i; |
| |
| chan = dma_get_channel(channel); |
| count = REG32(&chan->cndtr); |
| CPRINTF("c=%d\n", count); |
| udelay(1000 * 100); |
| CPRINTF("c=%d\n", |
| REG32(&chan->cndtr)); |
| for (i = 0; i < count; i++) |
| CPRINTF("%02x ", buff[i]); |
| udelay(1000 * 100); |
| CPRINTF("c=%d\n", |
| REG32(&chan->cndtr)); |
| for (i = 0; i < count; i++) |
| CPRINTF("%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 = dma_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++) |
| CPRINTF("%d/%d ", periph[i], memory[i]); |
| CPRINTF("\ncount=%d\n", REG32(&chan->cndtr)); |
| } |
| #endif /* CONFIG_DMA_HELP */ |
| |
| void dma_init(void) |
| { |
| int i; |
| |
| /* Enable DMA1, we don't support DMA2 yet */ |
| STM32_RCC_AHBENR |= RCC_AHBENR_DMA1EN; |
| |
| /* Initialize data for interrupt handlers */ |
| for (i = 0; i < DMA_NUM_CHANNELS; i++) |
| id[i] = TASK_ID_INVALID; |
| } |
| |
| int dma_wait(int channel) |
| { |
| struct dma_ctlr *dma; |
| uint32_t mask; |
| timestamp_t deadline; |
| |
| dma = dma_get_ctlr(channel); |
| mask = DMA_TCIF(channel); |
| |
| deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US; |
| while ((REG32(&dma->isr) & mask) != mask) { |
| if (deadline.val <= get_time().val) |
| return -1; |
| else |
| udelay(DMA_POLLING_INTERVAL_US); |
| } |
| return 0; |
| } |
| |
| int dma_get_irq(int channel) |
| { |
| ASSERT(channel < DMA_NUM_CHANNELS); |
| if (channel < DMA1_NUM_CHANNELS) |
| return STM32_IRQ_DMA_CHANNEL_1 + channel; |
| else |
| return STM32_IRQ_DMA2_CHANNEL1 + channel - |
| DMA1_NUM_CHANNELS; |
| } |
| |
| void dma_enable_tc_interrupt(int channel) |
| { |
| struct dma_channel *chan; |
| chan = dma_get_channel(channel); |
| |
| /* Storing task ID's so the ISRs knows which task to wake */ |
| id[channel] = task_get_current(); |
| |
| REG32(&chan->ccr) |= DMA_TCIE; |
| task_enable_irq(dma_get_irq(channel)); |
| } |
| |
| void dma_disable_tc_interrupt(int channel) |
| { |
| struct dma_channel *chan; |
| chan = dma_get_channel(channel); |
| |
| id[channel] = TASK_ID_INVALID; |
| |
| REG32(&chan->ccr) &= ~DMA_TCIE; |
| task_disable_irq(dma_get_irq(channel)); |
| } |
| |
| void dma_clear_isr(int channel) |
| { |
| struct dma_ctlr *dma; |
| |
| dma = dma_get_ctlr(channel); |
| /* Adjusting the channel number if it's from the second DMA */ |
| if (channel > DMA1_NUM_CHANNELS) |
| channel -= DMA1_NUM_CHANNELS; |
| REG32(&dma->ifcr) |= 0x0f << (4 * channel); |
| } |
| |
| struct dma_ctlr *dma_get_ctlr(int channel) |
| { |
| ASSERT(channel < DMA_NUM_CHANNELS); |
| if (channel < DMA1_NUM_CHANNELS) |
| return (struct dma_ctlr *)STM32_DMA1_BASE; |
| else |
| return (struct dma_ctlr *)STM32_DMA2_BASE; |
| } |
| |
| static void dma_event_interrupt_channel_4(void) |
| { |
| dma_clear_isr(DMAC_I2C_TX); |
| if (id[DMAC_I2C_TX] != TASK_ID_INVALID) |
| task_wake(id[DMAC_I2C_TX]); |
| } |
| DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_4, dma_event_interrupt_channel_4, 3); |
| |
| static void dma_event_interrupt_channel_5(void) |
| { |
| dma_clear_isr(DMAC_I2C_RX); |
| if (id[DMAC_I2C_RX] != TASK_ID_INVALID) |
| task_wake(id[DMAC_I2C_RX]); |
| } |
| DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_5, dma_event_interrupt_channel_5, 3); |