aboutsummaryrefslogtreecommitdiffstats
path: root/libmaple/dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmaple/dma.c')
-rw-r--r--libmaple/dma.c397
1 files changed, 311 insertions, 86 deletions
diff --git a/libmaple/dma.c b/libmaple/dma.c
index c71e52c..6ddabcc 100644
--- a/libmaple/dma.c
+++ b/libmaple/dma.c
@@ -23,126 +23,351 @@
*****************************************************************************/
/**
- * @file dma.c
- *
- * @brief Direct Memory Access peripheral support
+ * @file dma.c
+ * @author Marti Bolivar <mbolivar@leaflabs.com>;
+ * Original implementation by Michael Hope
+ * @brief Direct Memory Access peripheral support
*/
-#include "libmaple.h"
#include "dma.h"
-#include "rcc.h"
-#include "nvic.h"
-
-#define DMA_EN BIT(0)
-
-typedef struct dma_regs {
- uint32 CCR;
- uint32 CNDTR;
- uint32 CPAR;
- uint32 CMAR;
-} dma_regs;
-
-typedef struct DMAChannel {
- void (*handler)(void);
- uint32 irq_line;
-} DMAChannel;
-
-volatile static DMAChannel dma_channels[] = {
- { .handler = NULL, .irq_line = NVIC_DMA_CH1 },
- { .handler = NULL, .irq_line = NVIC_DMA_CH2 },
- { .handler = NULL, .irq_line = NVIC_DMA_CH3 },
- { .handler = NULL, .irq_line = NVIC_DMA_CH4 },
- { .handler = NULL, .irq_line = NVIC_DMA_CH5 },
- { .handler = NULL, .irq_line = NVIC_DMA_CH6 },
- { .handler = NULL, .irq_line = NVIC_DMA_CH7 }
+#include "bitband.h"
+#include "util.h"
+
+/*
+ * Devices
+ */
+
+static dma_dev dma1 = {
+ .regs = DMA1_BASE,
+ .clk_id = RCC_DMA1,
+ .handlers = {{ .handler = NULL, .irq_line = NVIC_DMA_CH1 },
+ { .handler = NULL, .irq_line = NVIC_DMA_CH2 },
+ { .handler = NULL, .irq_line = NVIC_DMA_CH3 },
+ { .handler = NULL, .irq_line = NVIC_DMA_CH4 },
+ { .handler = NULL, .irq_line = NVIC_DMA_CH5 },
+ { .handler = NULL, .irq_line = NVIC_DMA_CH6 },
+ { .handler = NULL, .irq_line = NVIC_DMA_CH7 }}
+};
+dma_dev *DMA1 = &dma1;
+
+#ifdef STM32_HIGH_DENSITY
+static dma_dev dma2 = {
+ .regs = DMA2_BASE,
+ .clk_id = RCC_DMA2,
+ .handlers = {{ .handler = NULL, .irq_line = NVIC_DMA2_CH1 },
+ { .handler = NULL, .irq_line = NVIC_DMA2_CH2 },
+ { .handler = NULL, .irq_line = NVIC_DMA2_CH3 },
+ { .handler = NULL, .irq_line = NVIC_DMA2_CH_4_5 },
+ { .handler = NULL, .irq_line = NVIC_DMA2_CH_4_5 }} /* !@#$ */
};
+dma_dev *DMA2 = &dma2;
+#endif
-/** Get the base address of the given channel, asserting and returning
- * NULL on illegal
+/*
+ * Convenience routines
*/
-static dma_regs *dma_get_regs(uint8 channel) {
- if (channel >= 1 && channel <= 7) {
- return (dma_regs *)(DMA1_CCR1 + DMA_CHANNEL_STRIDE * (channel-1));
- } else {
- ASSERT(0);
- return NULL;
- }
+
+/**
+ * @brief Initialize a DMA device.
+ * @param dev Device to initialize.
+ */
+void dma_init(dma_dev *dev) {
+ rcc_clk_enable(dev->clk_id);
}
-/* Zero-based indexing */
-static inline void dispatch_handler(uint8 channel_idx) {
- ASSERT(dma_channels[channel_idx].handler);
- if (dma_channels[channel_idx].handler) {
- (dma_channels[channel_idx].handler)();
- }
+/**
+ * @brief Set up a DMA transfer.
+ *
+ * The channel will be disabled before being reconfigured. The
+ * transfer will have low priority by default. You may choose another
+ * priority before the transfer begins using dma_set_priority(), as
+ * well as performing any other configuration you desire. When the
+ * channel is configured to your liking, enable it using dma_enable().
+ *
+ * @param dev DMA device.
+ * @param channel DMA channel.
+ * @param peripheral_address Base address of peripheral data register
+ * involved in the transfer.
+ * @param peripheral_size Peripheral data transfer size.
+ * @param memory_address Base memory address involved in the transfer.
+ * @param memory_size Memory data transfer size.
+ * @param mode Logical OR of dma_mode_flags
+ * @sideeffect Disables the given DMA channel.
+ * @see dma_xfer_size
+ * @see dma_mode_flags
+ * @see dma_set_num_transfers()
+ * @see dma_set_priority()
+ * @see dma_attach_interrupt()
+ * @see dma_enable()
+ */
+void dma_setup_transfer(dma_dev *dev,
+ dma_channel channel,
+ __io void *peripheral_address,
+ dma_xfer_size peripheral_size,
+ __io void *memory_address,
+ dma_xfer_size memory_size,
+ uint32 mode) {
+ dma_channel_reg_map *channel_regs = dma_channel_regs(dev, channel);
+
+ dma_disable(dev, channel); /* can't write to CMAR/CPAR otherwise */
+ channel_regs->CCR = (memory_size << 10) | (peripheral_size << 8) | mode;
+ channel_regs->CMAR = (uint32)memory_address;
+ channel_regs->CPAR = (uint32)peripheral_address;
+}
+
+/**
+ * @brief Set the number of data to be transferred on a DMA channel.
+ *
+ * You may not call this function while the channel is enabled.
+ *
+ * @param dev DMA device
+ * @param channel Channel through which the transfer occurs.
+ * @param num_transfers
+ */
+void dma_set_num_transfers(dma_dev *dev,
+ dma_channel channel,
+ uint16 num_transfers) {
+ dma_channel_reg_map *channel_regs;
+
+ ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));
+
+ channel_regs = dma_channel_regs(dev, channel);
+ channel_regs->CNDTR = num_transfers;
}
-void __irq_dma1_channel1(void) {
- dispatch_handler(0);
+/**
+ * @brief Set the priority of a DMA transfer.
+ *
+ * You may not call this function while the channel is enabled.
+ *
+ * @param dev DMA device
+ * @param channel DMA channel
+ * @param priority priority to set.
+ */
+void dma_set_priority(dma_dev *dev,
+ dma_channel channel,
+ dma_priority priority) {
+ dma_channel_reg_map *channel_regs;
+ uint32 ccr;
+
+ ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));
+
+ channel_regs = dma_channel_regs(dev, channel);
+ ccr = channel_regs->CCR;
+ ccr &= ~DMA_CCR_PL;
+ ccr |= priority;
+ channel_regs->CCR = ccr;
}
-void __irq_dma1_channel2(void) {
- dispatch_handler(1);
+/**
+ * @brief Attach an interrupt to a DMA transfer.
+ *
+ * Interrupts are enabled using appropriate mode flags in
+ * dma_setup_transfer().
+ *
+ * @param dev DMA device
+ * @param channel Channel to attach handler to
+ * @param handler Interrupt handler to call when channel interrupt fires.
+ * @see dma_setup_transfer()
+ * @see dma_get_irq_cause()
+ * @see dma_detach_interrupt()
+ */
+void dma_attach_interrupt(dma_dev *dev,
+ dma_channel channel,
+ void (*handler)(void)) {
+ dev->handlers[channel - 1].handler = handler;
+ nvic_irq_enable(dev->handlers[channel - 1].irq_line);
}
-void __irq_dma2_channel3(void) {
- dispatch_handler(2);
+/**
+ * @brief Detach a DMA transfer interrupt handler.
+ *
+ * After calling this function, the given channel's interrupts will be
+ * disabled.
+ *
+ * @param dev DMA device
+ * @param channel Channel whose handler to detach
+ * @sideffect Clears interrupt enable bits in the channel's CCR register.
+ * @see dma_attach_interrupt()
+ */
+void dma_detach_interrupt(dma_dev *dev, dma_channel channel) {
+ /* Don't use nvic_irq_disable()! Think about DMA2 channels 4 and 5. */
+ dma_channel_regs(dev, channel)->CCR &= ~0xF;
+ dev->handlers[channel - 1].handler = NULL;
}
-void __irq_dma2_channel4(void) {
- dispatch_handler(3);
+/**
+ * @brief Discover the reason why a DMA interrupt was called.
+ *
+ * You may only call this function within an attached interrupt
+ * handler for the given channel.
+ *
+ * This function resets the internal DMA register state which encodes
+ * the cause of the interrupt; consequently, it can only be called
+ * once per interrupt handler invocation.
+ *
+ * @brief dev DMA device
+ * @brief channel Channel whose interrupt is being handled.
+ * @return Reason why the interrupt fired.
+ * @sideeffect Clears channel status flags in dev->regs->ISR.
+ * @see dma_attach_interrupt()
+ * @see dma_irq_cause
+ */
+dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_channel channel) {
+ uint8 status_bits = dma_get_isr_bits(dev, channel);
+
+ /* If the channel global interrupt flag is cleared, then
+ * something's very wrong. */
+ ASSERT(status_bits & BIT(0));
+
+ dma_clear_isr_bits(dev, channel);
+
+ /* ISR flags get set even if the corresponding interrupt enable
+ * bits in the channel's configuration register are cleared, so we
+ * can't use a switch here.
+ *
+ * Don't change the order of these if statements. */
+ if (status_bits & BIT(3)) {
+ return DMA_TRANSFER_ERROR;
+ } else if (status_bits & BIT(1)) {
+ return DMA_TRANSFER_COMPLETE;
+ } else if (status_bits & BIT(2)) {
+ return DMA_TRANSFER_HALF_COMPLETE;
+ } else if (status_bits & BIT(0)) {
+ /* Shouldn't happen (unless someone messed up an IFCR write). */
+ throb();
+ }
+#if DEBUG_LEVEL < DEBUG_ALL
+ else {
+ /* We shouldn't have been called, but the debug level is too
+ * low for the above ASSERT() to have had any effect. In
+ * order to fail fast, mimic the DMA controller's behavior
+ * when an error occurs. */
+ dma_disable(dev, channel);
+ return DMA_TRANSFER_ERROR;
+ }
+#endif
}
-void __irq_dma2_channel5(void) {
- dispatch_handler(4);
+/**
+ * @brief Enable a DMA channel.
+ * @param dev DMA device
+ * @param channel Channel to enable
+ */
+void dma_enable(dma_dev *dev, dma_channel channel) {
+ dma_channel_reg_map *chan_regs = dma_channel_regs(dev, channel);
+ bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_EN_BIT, 1);
}
-void __irq_dma2_channel6(void) {
- dispatch_handler(5);
+/**
+ * @brief Disable a DMA channel.
+ * @param dev DMA device
+ * @param channel Channel to disable
+ */
+void dma_disable(dma_dev *dev, dma_channel channel) {
+ dma_channel_reg_map *chan_regs = dma_channel_regs(dev, channel);
+ bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_EN_BIT, 0);
}
-void __irq_dma2_channel7(void) {
- dispatch_handler(6);
+/**
+ * @brief Set the base memory address where data will be read from or
+ * written to.
+ *
+ * You must not call this function while the channel is enabled.
+ *
+ * If the DMA memory size is 16 bits, the address is automatically
+ * aligned to a half-word. If the DMA memory size is 32 bits, the
+ * address is aligned to a word.
+ *
+ * @param dev DMA Device
+ * @param channel Channel whose base memory address to set.
+ */
+void dma_set_mem_addr(dma_dev *dev, dma_channel channel, __io void *addr) {
+ dma_channel_reg_map *chan_regs;
+
+ ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));
+
+ chan_regs = dma_channel_regs(dev, channel);
+ chan_regs->CMAR = (uint32)addr;
}
-void dma_init(uint8 channel, volatile void *paddr,
- dma_transfer_size psize, dma_transfer_size msize,
- int mode) {
- volatile dma_regs *regs = dma_get_regs(channel);
+/**
+ * @brief Set the base peripheral address where data will be read from
+ * or written to.
+ *
+ * You must not call this function while the channel is enabled.
+ *
+ * If the DMA peripheral size is 16 bits, the address is automatically
+ * aligned to a half-word. If the DMA peripheral size is 32 bits, the
+ * address is aligned to a word.
+ *
+ * @param dev DMA Device
+ * @param channel Channel whose peripheral data register base address to set.
+ */
+void dma_set_per_addr(dma_dev *dev, dma_channel channel, __io void *addr) {
+ dma_channel_reg_map *chan_regs;
+
+ ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));
- if (regs != NULL) {
- rcc_clk_enable(RCC_DMA1);
+ chan_regs = dma_channel_regs(dev, channel);
+ chan_regs->CPAR = (uint32)addr;
+}
- regs->CCR = ((0 << 12) /* Low priority */
- | (msize << 10)
- | (psize << 8)
- | (0 << 0) /* Disabled (until started) */
- | mode);
+/*
+ * IRQ handlers
+ */
- regs->CPAR = (uint32)paddr;
+static inline void dispatch_handler(dma_dev *dev, dma_channel channel) {
+ void (*handler)(void) = dev->handlers[channel - 1].handler;
+ if (handler) {
+ handler();
+ dma_clear_isr_bits(dev, channel); /* in case handler doesn't */
}
}
-void dma_start(uint8 channel, volatile void *buffer, uint16 count) {
- volatile dma_regs *regs = dma_get_regs(channel);
+void __irq_dma_channel1(void) {
+ dispatch_handler(DMA1, DMA_CH1);
+}
- if (regs != NULL) {
- regs->CCR &= ~DMA_EN; /* CMAR may not be written with EN set */
- regs->CMAR = (uint32)buffer;
- regs->CNDTR = count;
+void __irq_dma_channel2(void) {
+ dispatch_handler(DMA1, DMA_CH2);
+}
- regs->CCR |= DMA_EN;
- }
+void __irq_dma_channel3(void) {
+ dispatch_handler(DMA1, DMA_CH3);
}
-void dma_attach_interrupt(uint8 channel, voidFuncPtr handler) {
- channel--; /* 1-based -> 0-based indexing */
- dma_channels[channel].handler = handler;
- nvic_irq_enable(dma_channels[channel].irq_line);
+void __irq_dma_channel4(void) {
+ dispatch_handler(DMA1, DMA_CH4);
+}
+
+void __irq_dma_channel5(void) {
+ dispatch_handler(DMA1, DMA_CH5);
+}
+
+void __irq_dma_channel6(void) {
+ dispatch_handler(DMA1, DMA_CH6);
+}
+
+void __irq_dma_channel7(void) {
+ dispatch_handler(DMA1, DMA_CH7);
+}
+
+#ifdef STM32_HIGH_DENSITY
+void __irq_dma2_channel1(void) {
+ dispatch_handler(DMA2, DMA_CH1);
+}
+
+void __irq_dma2_channel2(void) {
+ dispatch_handler(DMA2, DMA_CH2);
+}
+
+void __irq_dma2_channel3(void) {
+ dispatch_handler(DMA2, DMA_CH3);
}
-void dma_detach_interrupt(uint8 channel) {
- channel--;
- nvic_irq_disable(dma_channels[channel].irq_line);
- dma_channels[channel].handler = NULL;
+void __irq_dma2_channel4_5(void) {
+ dispatch_handler(DMA2, DMA_CH4);
+ dispatch_handler(DMA2, DMA_CH5);
}
+#endif