aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/test-usart-dma.cpp127
-rw-r--r--libmaple/dma.c397
-rw-r--r--libmaple/dma.h445
-rw-r--r--libmaple/nvic.h1
-rw-r--r--notes/dma.txt99
5 files changed, 921 insertions, 148 deletions
diff --git a/examples/test-usart-dma.cpp b/examples/test-usart-dma.cpp
new file mode 100644
index 0000000..b9c03f1
--- /dev/null
+++ b/examples/test-usart-dma.cpp
@@ -0,0 +1,127 @@
+/**
+ * @file test-usart-dma.cpp
+ * @author Marti Bolivar <mbolivar@leaflabs.com>
+ *
+ * Simple test of DMA used with a USART receiver.
+ *
+ * Configures a USART receiver for use with DMA. Received bytes are
+ * placed into a buffer, with an interrupt firing when the buffer is
+ * full. At that point, the USART transmitter will print the contents
+ * of the byte buffer. The buffer is continually filled and refilled
+ * in this manner.
+ *
+ * This example isn't very robust; don't use it in production. In
+ * particular, since the buffer keeps filling (DMA_CIRC_MODE is set),
+ * if you keep typing after filling the buffer, you'll overwrite
+ * earlier bytes; this may happen before those earlier bytes are done
+ * printing.
+ *
+ * This code is released into the public domain.
+ */
+
+#include "dma.h"
+#include "usart.h"
+#include "gpio.h"
+
+#include "wirish.h"
+
+#define BAUD 9600
+
+#define USART USART2
+#define USART_HWSER Serial2
+#define USART_DMA_DEV DMA1
+#define USART_RX_DMA_CHANNEL DMA_CH6
+#define USART_TX BOARD_USART2_TX_PIN
+#define USART_RX BOARD_USART2_RX_PIN
+
+#define BUF_SIZE 8
+uint8 rx_buf[BUF_SIZE];
+
+dma_irq_cause irq_cause;
+
+__io uint32 irq_fired = 0;
+
+void init_usart(void);
+void init_dma_xfer(void);
+void rx_dma_irq(void);
+
+void setup(void) {
+ pinMode(BOARD_LED_PIN, OUTPUT);
+
+ init_dma_xfer();
+ init_usart();
+}
+
+void loop(void) {
+ toggleLED();
+ delay(100);
+
+ dma_channel_reg_map *ch_regs = dma_channel_regs(USART_DMA_DEV,
+ USART_RX_DMA_CHANNEL);
+ if (irq_fired) {
+ USART_HWSER.println("** IRQ **");
+ while (true)
+ ;
+ }
+ USART_HWSER.print("[");
+ USART_HWSER.print(millis());
+ USART_HWSER.print("]\tISR bits: 0x");
+ uint8 isr_bits = dma_get_isr_bits(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
+ USART_HWSER.print((int32)isr_bits, HEX);
+ USART_HWSER.print("\tCCR: 0x");
+ USART_HWSER.print((int64)ch_regs->CCR, HEX);
+ USART_HWSER.print("\tCNDTR: 0x");
+ USART_HWSER.print((int64)ch_regs->CNDTR, HEX);
+ USART_HWSER.print("\tBuffer contents: ");
+ for (int i = 0; i < BUF_SIZE; i++) {
+ USART_HWSER.print('\'');
+ USART_HWSER.print(rx_buf[i]);
+ USART_HWSER.print('\'');
+ if (i < BUF_SIZE - 1) USART_HWSER.print(", ");
+ }
+ USART_HWSER.println();
+ if (isr_bits == 0x7) {
+ USART_HWSER.println("** Clearing ISR bits.");
+ dma_clear_isr_bits(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
+ }
+
+ irq_fired = 0;
+}
+
+/* Configure USART receiver for use with DMA */
+void init_usart(void) {
+ USART_HWSER.begin(BAUD);
+ USART->regs->CR3 = USART_CR3_DMAR;
+}
+
+/* Configure DMA transmission */
+void init_dma_xfer(void) {
+ dma_init(USART_DMA_DEV);
+ dma_setup_transfer(USART_DMA_DEV, USART_RX_DMA_CHANNEL,
+ &USART->regs->DR, DMA_SIZE_8BITS,
+ rx_buf, DMA_SIZE_8BITS,
+ (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_TRNS_CMPLT
+ ));
+ dma_set_num_transfers(USART_DMA_DEV, USART_RX_DMA_CHANNEL, BUF_SIZE);
+ // Currently not working:
+ // dma_attach_interrupt(USART_DMA_DEV, USART_RX_DMA_CHANNEL, rx_dma_irq);
+ dma_enable(USART_DMA_DEV, USART_RX_DMA_CHANNEL);
+}
+
+void rx_dma_irq(void) {
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
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
diff --git a/libmaple/dma.h b/libmaple/dma.h
index 3417f02..c763672 100644
--- a/libmaple/dma.h
+++ b/libmaple/dma.h
@@ -23,97 +23,418 @@
*****************************************************************************/
/**
- * @file dma.h
+ * @file dma.h
*
- * @brief Direct Memory Access peripheral support
+ * @author Marti Bolivar <mbolivar@leaflabs.com>;
+ * Original implementation by Michael Hope
*
- * TODO: add DMA2 support for high-density devices.
+ * @brief Direct Memory Access peripheral support
+ */
+
+/*
+ * See /notes/dma.txt for more information.
*/
#ifndef _DMA_H_
#define _DMA_H_
#include "libmaple_types.h"
+#include "rcc.h"
+#include "nvic.h"
#ifdef __cplusplus
extern "C"{
#endif
-/** Base address of the DMA1 peripheral */
-#define DMA1_BASE 0x40020000
-/** DMA Interrupt Status Register */
-#define DMA1_ISR (DMA1_BASE + 0x00)
-/** DMA Interrupt Flag Clear Register */
-#define DMA1_IFCR (DMA1_BASE + 0x04)
-/** DMA Channel Configuration Register */
-#define DMA1_CCR1 (DMA1_BASE + 0x08)
-/** DMA Channel Number of Data Register */
-#define DMA1_CNDTR1 (DMA1_BASE + 0x0C)
-/** DMA Channel Peripheral Address Register */
-#define DMA1_CPAR1 (DMA1_BASE + 0x10)
-/** DMA Channel Memory Address Register */
-#define DMA1_CMAR1 (DMA1_BASE + 0x14)
-/** Spacing between channel registers */
-#define DMA_CHANNEL_STRIDE 20
+/*
+ * Register maps
+ */
+
+/**
+ * @brief DMA register map type.
+ *
+ * Note that DMA controller 2 (register map base pointer DMA2_BASE)
+ * only supports channels 1--5.
+ */
+typedef struct dma_reg_map {
+ __io uint32 ISR; /**< Interrupt status register */
+ __io uint32 IFCR; /**< Interrupt flag clear register */
+ __io uint32 CCR1; /**< Channel 1 configuration register */
+ __io uint32 CNDTR1; /**< Channel 1 number of data register */
+ __io uint32 CPAR1; /**< Channel 1 peripheral address register */
+ __io uint32 CMAR1; /**< Channel 1 memory address register */
+ const uint32 RESERVED1; /**< Reserved. */
+ __io uint32 CCR2; /**< Channel 2 configuration register */
+ __io uint32 CNDTR2; /**< Channel 2 number of data register */
+ __io uint32 CPAR2; /**< Channel 2 peripheral address register */
+ __io uint32 CMAR2; /**< Channel 2 memory address register */
+ const uint32 RESERVED2; /**< Reserved. */
+ __io uint32 CCR3; /**< Channel 3 configuration register */
+ __io uint32 CNDTR3; /**< Channel 3 number of data register */
+ __io uint32 CPAR3; /**< Channel 3 peripheral address register */
+ __io uint32 CMAR3; /**< Channel 3 memory address register */
+ const uint32 RESERVED3; /**< Reserved. */
+ __io uint32 CCR4; /**< Channel 4 configuration register */
+ __io uint32 CNDTR4; /**< Channel 4 number of data register */
+ __io uint32 CPAR4; /**< Channel 4 peripheral address register */
+ __io uint32 CMAR4; /**< Channel 4 memory address register */
+ const uint32 RESERVED4; /**< Reserved. */
+ __io uint32 CCR5; /**< Channel 5 configuration register */
+ __io uint32 CNDTR5; /**< Channel 5 number of data register */
+ __io uint32 CPAR5; /**< Channel 5 peripheral address register */
+ __io uint32 CMAR5; /**< Channel 5 memory address register */
+ const uint32 RESERVED5; /**< Reserved. */
+ __io uint32 CCR6; /**< Channel 6 configuration register */
+ __io uint32 CNDTR6; /**< Channel 6 number of data register */
+ __io uint32 CPAR6; /**< Channel 6 peripheral address register */
+ __io uint32 CMAR6; /**< Channel 6 memory address register */
+ const uint32 RESERVED6; /**< Reserved. */
+ __io uint32 CCR7; /**< Channel 7 configuration register */
+ __io uint32 CNDTR7; /**< Channel 7 number of data register */
+ __io uint32 CPAR7; /**< Channel 7 peripheral address register */
+ __io uint32 CMAR7; /**< Channel 7 memory address register */
+ const uint32 RESERVED7; /**< Reserved. */
+} dma_reg_map;
+
+/** DMA controller 1 register map base pointer */
+#define DMA1_BASE ((struct dma_reg_map*)0x40020000)
+
+#ifdef STM32_HIGH_DENSITY
+/** DMA controller 2 register map base pointer */
+#define DMA2_BASE ((struct dma_reg_map*)0x40020400)
+#endif
+
+/*
+ * Register bit definitions
+ */
+
+/* Interrupt status register */
+
+#define DMA_ISR_TEIF7_BIT 27
+#define DMA_ISR_HTIF7_BIT 26
+#define DMA_ISR_TCIF7_BIT 25
+#define DMA_ISR_GIF7_BIT 24
+#define DMA_ISR_TEIF6_BIT 23
+#define DMA_ISR_HTIF6_BIT 22
+#define DMA_ISR_TCIF6_BIT 21
+#define DMA_ISR_GIF6_BIT 20
+#define DMA_ISR_TEIF5_BIT 19
+#define DMA_ISR_HTIF5_BIT 18
+#define DMA_ISR_TCIF5_BIT 17
+#define DMA_ISR_GIF5_BIT 16
+#define DMA_ISR_TEIF4_BIT 15
+#define DMA_ISR_HTIF4_BIT 14
+#define DMA_ISR_TCIF4_BIT 13
+#define DMA_ISR_GIF4_BIT 12
+#define DMA_ISR_TEIF3_BIT 11
+#define DMA_ISR_HTIF3_BIT 10
+#define DMA_ISR_TCIF3_BIT 9
+#define DMA_ISR_GIF3_BIT 8
+#define DMA_ISR_TEIF2_BIT 7
+#define DMA_ISR_HTIF2_BIT 6
+#define DMA_ISR_TCIF2_BIT 5
+#define DMA_ISR_GIF2_BIT 4
+#define DMA_ISR_TEIF1_BIT 3
+#define DMA_ISR_HTIF1_BIT 2
+#define DMA_ISR_TCIF1_BIT 1
+#define DMA_ISR_GIF1_BIT 0
+
+#define DMA_ISR_TEIF7 BIT(DMA_ISR_TEIF7_BIT)
+#define DMA_ISR_HTIF7 BIT(DMA_ISR_HTIF7_BIT)
+#define DMA_ISR_TCIF7 BIT(DMA_ISR_TCIF7_BIT)
+#define DMA_ISR_GIF7 BIT(DMA_ISR_GIF7_BIT)
+#define DMA_ISR_TEIF6 BIT(DMA_ISR_TEIF6_BIT)
+#define DMA_ISR_HTIF6 BIT(DMA_ISR_HTIF6_BIT)
+#define DMA_ISR_TCIF6 BIT(DMA_ISR_TCIF6_BIT)
+#define DMA_ISR_GIF6 BIT(DMA_ISR_GIF6_BIT)
+#define DMA_ISR_TEIF5 BIT(DMA_ISR_TEIF5_BIT)
+#define DMA_ISR_HTIF5 BIT(DMA_ISR_HTIF5_BIT)
+#define DMA_ISR_TCIF5 BIT(DMA_ISR_TCIF5_BIT)
+#define DMA_ISR_GIF5 BIT(DMA_ISR_GIF5_BIT)
+#define DMA_ISR_TEIF4 BIT(DMA_ISR_TEIF4_BIT)
+#define DMA_ISR_HTIF4 BIT(DMA_ISR_HTIF4_BIT)
+#define DMA_ISR_TCIF4 BIT(DMA_ISR_TCIF4_BIT)
+#define DMA_ISR_GIF4 BIT(DMA_ISR_GIF4_BIT)
+#define DMA_ISR_TEIF3 BIT(DMA_ISR_TEIF3_BIT)
+#define DMA_ISR_HTIF3 BIT(DMA_ISR_HTIF3_BIT)
+#define DMA_ISR_TCIF3 BIT(DMA_ISR_TCIF3_BIT)
+#define DMA_ISR_GIF3 BIT(DMA_ISR_GIF3_BIT)
+#define DMA_ISR_TEIF2 BIT(DMA_ISR_TEIF2_BIT)
+#define DMA_ISR_HTIF2 BIT(DMA_ISR_HTIF2_BIT)
+#define DMA_ISR_TCIF2 BIT(DMA_ISR_TCIF2_BIT)
+#define DMA_ISR_GIF2 BIT(DMA_ISR_GIF2_BIT)
+#define DMA_ISR_TEIF1 BIT(DMA_ISR_TEIF1_BIT)
+#define DMA_ISR_HTIF1 BIT(DMA_ISR_HTIF1_BIT)
+#define DMA_ISR_TCIF1 BIT(DMA_ISR_TCIF1_BIT)
+#define DMA_ISR_GIF1 BIT(DMA_ISR_GIF1_BIT)
+
+/* Interrupt flag clear register */
+
+#define DMA_IFCR_CTEIF7_BIT 27
+#define DMA_IFCR_CHTIF7_BIT 26
+#define DMA_IFCR_CTCIF7_BIT 25
+#define DMA_IFCR_CGIF7_BIT 24
+#define DMA_IFCR_CTEIF6_BIT 23
+#define DMA_IFCR_CHTIF6_BIT 22
+#define DMA_IFCR_CTCIF6_BIT 21
+#define DMA_IFCR_CGIF6_BIT 20
+#define DMA_IFCR_CTEIF5_BIT 19
+#define DMA_IFCR_CHTIF5_BIT 18
+#define DMA_IFCR_CTCIF5_BIT 17
+#define DMA_IFCR_CGIF5_BIT 16
+#define DMA_IFCR_CTEIF4_BIT 15
+#define DMA_IFCR_CHTIF4_BIT 14
+#define DMA_IFCR_CTCIF4_BIT 13
+#define DMA_IFCR_CGIF4_BIT 12
+#define DMA_IFCR_CTEIF3_BIT 11
+#define DMA_IFCR_CHTIF3_BIT 10
+#define DMA_IFCR_CTCIF3_BIT 9
+#define DMA_IFCR_CGIF3_BIT 8
+#define DMA_IFCR_CTEIF2_BIT 7
+#define DMA_IFCR_CHTIF2_BIT 6
+#define DMA_IFCR_CTCIF2_BIT 5
+#define DMA_IFCR_CGIF2_BIT 4
+#define DMA_IFCR_CTEIF1_BIT 3
+#define DMA_IFCR_CHTIF1_BIT 2
+#define DMA_IFCR_CTCIF1_BIT 1
+#define DMA_IFCR_CGIF1_BIT 0
+
+#define DMA_IFCR_CTEIF7 BIT(DMA_IFCR_CTEIF7_BIT)
+#define DMA_IFCR_CHTIF7 BIT(DMA_IFCR_CHTIF7_BIT)
+#define DMA_IFCR_CTCIF7 BIT(DMA_IFCR_CTCIF7_BIT)
+#define DMA_IFCR_CGIF7 BIT(DMA_IFCR_CGIF7_BIT)
+#define DMA_IFCR_CTEIF6 BIT(DMA_IFCR_CTEIF6_BIT)
+#define DMA_IFCR_CHTIF6 BIT(DMA_IFCR_CHTIF6_BIT)
+#define DMA_IFCR_CTCIF6 BIT(DMA_IFCR_CTCIF6_BIT)
+#define DMA_IFCR_CGIF6 BIT(DMA_IFCR_CGIF6_BIT)
+#define DMA_IFCR_CTEIF5 BIT(DMA_IFCR_CTEIF5_BIT)
+#define DMA_IFCR_CHTIF5 BIT(DMA_IFCR_CHTIF5_BIT)
+#define DMA_IFCR_CTCIF5 BIT(DMA_IFCR_CTCIF5_BIT)
+#define DMA_IFCR_CGIF5 BIT(DMA_IFCR_CGIF5_BIT)
+#define DMA_IFCR_CTEIF4 BIT(DMA_IFCR_CTEIF4_BIT)
+#define DMA_IFCR_CHTIF4 BIT(DMA_IFCR_CHTIF4_BIT)
+#define DMA_IFCR_CTCIF4 BIT(DMA_IFCR_CTCIF4_BIT)
+#define DMA_IFCR_CGIF4 BIT(DMA_IFCR_CGIF4_BIT)
+#define DMA_IFCR_CTEIF3 BIT(DMA_IFCR_CTEIF3_BIT)
+#define DMA_IFCR_CHTIF3 BIT(DMA_IFCR_CHTIF3_BIT)
+#define DMA_IFCR_CTCIF3 BIT(DMA_IFCR_CTCIF3_BIT)
+#define DMA_IFCR_CGIF3 BIT(DMA_IFCR_CGIF3_BIT)
+#define DMA_IFCR_CTEIF2 BIT(DMA_IFCR_CTEIF2_BIT)
+#define DMA_IFCR_CHTIF2 BIT(DMA_IFCR_CHTIF2_BIT)
+#define DMA_IFCR_CTCIF2 BIT(DMA_IFCR_CTCIF2_BIT)
+#define DMA_IFCR_CGIF2 BIT(DMA_IFCR_CGIF2_BIT)
+#define DMA_IFCR_CTEIF1 BIT(DMA_IFCR_CTEIF1_BIT)
+#define DMA_IFCR_CHTIF1 BIT(DMA_IFCR_CHTIF1_BIT)
+#define DMA_IFCR_CTCIF1 BIT(DMA_IFCR_CTCIF1_BIT)
+#define DMA_IFCR_CGIF1 BIT(DMA_IFCR_CGIF1_BIT)
+
+/* Channel configuration register */
+
+#define DMA_CCR_MEM2MEM_BIT 14
+#define DMA_CCR_MINC_BIT 7
+#define DMA_CCR_PINC_BIT 6
+#define DMA_CCR_CIRC_BIT 5
+#define DMA_CCR_DIR_BIT 4
+#define DMA_CCR_TEIE_BIT 3
+#define DMA_CCR_HTIE_BIT 2
+#define DMA_CCR_TCIE_BIT 1
+#define DMA_CCR_EN_BIT 0
+
+#define DMA_CCR_MEM2MEM BIT(DMA_CCR_MEM2MEM_BIT)
+#define DMA_CCR_PL (0x3 << 12)
+#define DMA_CCR_PL_LOW (0x0 << 12)
+#define DMA_CCR_PL_MEDIUM (0x1 << 12)
+#define DMA_CCR_PL_HIGH (0x2 << 12)
+#define DMA_CCR_PL_VERY_HIGH (0x3 << 12)
+#define DMA_CCR_MSIZE (0x3 << 10)
+#define DMA_CCR_MSIZE_8BITS (0x0 << 10)
+#define DMA_CCR_MSIZE_16BITS (0x1 << 10)
+#define DMA_CCR_MSIZE_32BITS (0x2 << 10)
+#define DMA_CCR_PSIZE (0x3 << 8)
+#define DMA_CCR_PSIZE_8BITS (0x0 << 8)
+#define DMA_CCR_PSIZE_16BITS (0x1 << 8)
+#define DMA_CCR_PSIZE_32BITS (0x2 << 8)
+#define DMA_CCR_MINC BIT(DMA_CCR_MINC_BIT)
+#define DMA_CCR_PINC BIT(DMA_CCR_PINC_BIT)
+#define DMA_CCR_CIRC BIT(DMA_CCR_CIRC_BIT)
+#define DMA_CCR_DIR BIT(DMA_CCR_DIR_BIT)
+#define DMA_CCR_TEIE BIT(DMA_CCR_TEIE_BIT)
+#define DMA_CCR_HTIE BIT(DMA_CCR_HTIE_BIT)
+#define DMA_CCR_TCIE BIT(DMA_CCR_TCIE_BIT)
+#define DMA_CCR_EN BIT(DMA_CCR_EN_BIT)
+
+/*
+ * Devices
+ */
+
+/** Encapsulates state related to a DMA channel interrupt. */
+typedef struct dma_handler_config {
+ void (*handler)(void);
+ nvic_irq_num irq_line;
+} dma_handler_config;
+
+/** DMA device type */
+typedef struct dma_dev {
+ dma_reg_map *regs; /**< Register map */
+ rcc_clk_id clk_id; /**< Clock ID */
+ dma_handler_config handlers[]; /**< IRQ handlers and NVIC numbers. */
+} dma_dev;
+
+/** DMA1 device */
+extern dma_dev *DMA1;
+#ifdef STM32_HIGH_DENSITY
+/** DMA2 device */
+extern dma_dev *DMA2;
+#endif
+
+/*
+ * Convenience functions
+ */
+
+void dma_init(dma_dev *dev);
/** Flags for DMA transfer configuration. */
typedef enum dma_mode_flags {
- DMA_MINC_MODE = 1 << 7, /**< Auto-increment memory address */
- DMA_PINC_MODE = 1 << 6, /**< Auto-increment peripheral address */
- DMA_CIRC_MODE = 1 << 5, /**< Circular mode */
- DMA_FROM_MEM = 1 << 4, /**< Read from memory to peripheral */
- DMA_TRNS_ERR = 1 << 3, /**< Interrupt on transfer error */
- DMA_HALF_TRNS = 1 << 2, /**< Interrupt on half-transfer */
- DMA_TRNS_CMPLT = 1 << 1 /**< Interrupt on transfer completion */
+ DMA_MEM_2_MEM = 1 << 14, /**< Memory to memory mode */
+ DMA_MINC_MODE = 1 << 7, /**< Auto-increment memory address */
+ DMA_PINC_MODE = 1 << 6, /**< Auto-increment peripheral address */
+ DMA_CIRC_MODE = 1 << 5, /**< Circular mode */
+ DMA_FROM_MEM = 1 << 4, /**< Read from memory to peripheral */
+ DMA_TRNS_ERR = 1 << 3, /**< Interrupt on transfer error */
+ DMA_HALF_TRNS = 1 << 2, /**< Interrupt on half-transfer */
+ DMA_TRNS_CMPLT = 1 << 1 /**< Interrupt on transfer completion */
} dma_mode_flags;
/** Source and destination transfer sizes. */
-typedef enum dma_transfer_size {
- DMA_SIZE_8BITS = 0,
- DMA_SIZE_16BITS = 1,
- DMA_SIZE_32BITS = 2
-} dma_transfer_size;
+typedef enum dma_xfer_size {
+ DMA_SIZE_8BITS = 0, /**< 8-bit transfers */
+ DMA_SIZE_16BITS = 1, /**< 16-bit transfers */
+ DMA_SIZE_32BITS = 2 /**< 32-bit transfers */
+} dma_xfer_size;
+
+/** DMA channel */
+typedef enum dma_channel {
+ DMA_CH1 = 1, /**< Channel 1 */
+ DMA_CH2 = 2, /**< Channel 2 */
+ DMA_CH3 = 3, /**< Channel 3 */
+ DMA_CH4 = 4, /**< Channel 4 */
+ DMA_CH5 = 5, /**< Channel 5 */
+ DMA_CH6 = 6, /**< Channel 6 */
+ DMA_CH7 = 7, /**< Channel 7 */
+} dma_channel;
+
+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);
+
+void dma_set_num_transfers(dma_dev *dev,
+ dma_channel channel,
+ uint16 num_transfers);
+
+/** DMA transfer priority. */
+typedef enum dma_priority {
+ DMA_PRIORITY_LOW = DMA_CCR_PL_LOW, /**< Low priority */
+ DMA_PRIORITY_MEDIUM = DMA_CCR_PL_MEDIUM, /**< Medium priority */
+ DMA_PRIORITY_HIGH = DMA_CCR_PL_HIGH, /**< High priority */
+ DMA_PRIORITY_VERY_HIGH = DMA_CCR_PL_VERY_HIGH /**< Very high priority */
+} dma_priority;
+
+void dma_set_priority(dma_dev *dev,
+ dma_channel channel,
+ dma_priority priority);
+
+void dma_attach_interrupt(dma_dev *dev,
+ dma_channel channel,
+ void (*handler)(void));
+void dma_detach_interrupt(dma_dev *dev, dma_channel channel);
+
+/**
+ * Encodes the reason why a DMA interrupt was called.
+ * @see dma_get_irq_cause()
+ */
+typedef enum dma_irq_cause {
+ DMA_TRANSFER_COMPLETE, /**< Transfer is complete. */
+ DMA_TRANSFER_HALF_COMPLETE, /**< Transfer is half complete. */
+ DMA_TRANSFER_ERROR, /**< Error occurred during transfer. */
+} dma_irq_cause;
+
+dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_channel channel);
+
+void dma_enable(dma_dev *dev, dma_channel channel);
+void dma_disable(dma_dev *dev, dma_channel channel);
+
+void dma_set_mem_addr(dma_dev *dev, dma_channel channel, __io void *address);
+void dma_set_per_addr(dma_dev *dev, dma_channel channel, __io void *address);
/**
- * Initialize a DMA channel. If desired, attach an interrupt handler
- * using dma_attach_interrupt(). Start the transfer using
- * dma_start().
+ * @brief DMA channel register map type.
*
- * @param channel the channel number (1..7)
- * @param paddr address of the peripheral
- * @param psize peripheral size
- * @param msize memory size
- * @param mode OR of the dma_mode_flags
- * @see dma_mode_flags
- * @see dma_attach_interrupt()
- * @see dma_start() */
-void dma_init(uint8 channel, volatile void *paddr,
- dma_transfer_size psize, dma_transfer_size msize,
- int mode);
+ * Provides access to an individual channel's registers.
+ */
+typedef struct dma_channel_reg_map {
+ __io uint32 CCR; /**< Channel configuration register */
+ __io uint32 CNDTR; /**< Channel number of data register */
+ __io uint32 CPAR; /**< Channel peripheral address register */
+ __io uint32 CMAR; /**< Channel memory address register */
+} dma_channel_reg_map;
+
+#define DMA_CHANNEL_NREGS 5
/**
- * Begin a DMA transfer initialized with dma_init().
+ * @brief Obtain a pointer to an individual DMA channel's registers.
*
- * @param channel Channel transfer to start.
- * @param buffer Buffer to write to (unless DMA_FROM_MEM was set in
- * mode argument to dma_init(), in which case, buffer
- * to read from). This must be aligned with msize
- * argument to dma_init().
- * @param count Number of elements to transfer.
- * @see dma_init() */
-void dma_start(uint8 channel, volatile void *buffer, uint16 count);
+ * For example, dma_channel_regs(DMA1, DMA_CH1)->CCR is DMA1_BASE->CCR1.
+ *
+ * @param dev DMA device
+ * @param channel DMA channel whose channel register map to obtain.
+ */
+static inline dma_channel_reg_map* dma_channel_regs(dma_dev *dev,
+ dma_channel channel) {
+ __io uint32 *ccr1 = &dev->regs->CCR1;
+ return (dma_channel_reg_map*)(ccr1 + DMA_CHANNEL_NREGS * (channel - 1));
+}
+
+/**
+ * @brief Check if a DMA channel is enabled
+ * @param dev DMA device
+ * @param channel Channel whose enabled bit to check.
+ */
+static inline uint8 dma_is_channel_enabled(dma_dev *dev, dma_channel channel) {
+ return (uint8)(dma_channel_regs(dev, channel)->CCR & DMA_CCR_EN);
+}
/**
- * Attach an interrupt handler for the given DMA channel.
- * @param channel DMA channel (1..7)
- * @param handler Interrupt handler to attach
- * @see voidFuncPtr */
-void dma_attach_interrupt(uint8 channel, voidFuncPtr handler);
+ * @brief Get the ISR status bits for a DMA channel.
+ *
+ * The bits are returned right-aligned, in the following order:
+ * transfer error flag, half-transfer flag, transfer complete flag,
+ * global interrupt flag.
+ *
+ * If you're attempting to figure out why a DMA interrupt fired; you
+ * may find dma_get_irq_cause() more convenient.
+ *
+ * @see dma_get_irq_cause().
+ */
+static inline uint8 dma_get_isr_bits(dma_dev *dev, dma_channel channel) {
+ uint8 shift = (channel - 1) * 4;
+ return (dev->regs->ISR >> shift) & 0xF;
+}
/**
- * Detach any handler associated with the given DMA channel.
- * @param channel Channel whose interrupt handler to detach. */
-void dma_detach_interrupt(uint8 channel);
+ * @brief Clear the ISR status bits for a given DMA channel.
+ *
+ * If you're attempting to clean up after yourself in a DMA interrupt,
+ * you may find dma_get_irq_cause() more convenient.
+ *
+ * @see dma_get_irq_cause()
+ */
+static inline void dma_clear_isr_bits(dma_dev *dev, dma_channel channel) {
+ dev->regs->IFCR = BIT(4 * (channel - 1));
+}
#ifdef __cplusplus
} // extern "C"
diff --git a/libmaple/nvic.h b/libmaple/nvic.h
index df3461b..2e98c9f 100644
--- a/libmaple/nvic.h
+++ b/libmaple/nvic.h
@@ -31,6 +31,7 @@
#define _NVIC_H_
#include "libmaple_types.h"
+#include "util.h"
#ifdef __cplusplus
extern "C"{
diff --git a/notes/dma.txt b/notes/dma.txt
new file mode 100644
index 0000000..97b23a0
--- /dev/null
+++ b/notes/dma.txt
@@ -0,0 +1,99 @@
+DMA Notes
+=========
+
+Medium-density devices have one DMA controller, DMA1. High-density
+devices and up also have DMA2. DMA1 has 7 channels; DMA2 has 5. Each
+channel multiplexes DMA requests from various peripherals, like so:
+
+Channel Capabilities
+--------------------
+
+DMA1:
+
+ * Channel 1: ADC1, TIM2_CH3, TIM4_CH1
+ * Channel 2: USART3_TX, TIM1_CH1, TIM2_UP, TIM3_CH3, SPI1_RX
+ * Channel 3: USART3_RX, TIM1_CH2, TIM3_CH4, TIM3_UP, SPI1_TX
+ * Channel 4: USART1_TX, TIM1_CH4, TIM1_TRIG, TIM1_COM, TIM4_CH2,
+ SPI2/I2S2_RX, I2C2_TX
+ * Channel 5: USART1_RX, TIM1_UP, TIM2_CH1, TIM4_CH3,
+ SPI2/I2S2_TX, I2C2_RX
+ * Channel 6: USART2_RX, TIM1_CH3, TIM3_CH1, TIM3_TRIG, I2C1_TX
+ * Channel 7: USART2_TX, TIM2_CH2, TIM2_CH4, TIM4_UP, I2C1_RX
+
+DMA2:
+
+ * Channel 1: TIM5_CH4, TIM5_TRIG, TIM8_CH3, TIM8_UP, SPI/I2S3_RX
+ * Channel 2: TIM8_CH4, TIM8_TRIG, TIM8_COM, TIM5_CH3, TIM5_UP, SPI/I2S3_TX
+ * Channel 3: TIM8_CH1, UART4_RX, TIM6_UP/DAC_CH1
+ * Channel 4: TIM5_CH2, SDIO, TIM7_UP/DAC_CH2
+ * Channel 5: ADC3, TIM8_CH2, TIM5_CH1, UART4_TX
+
+An example usage: via DMA1, channel 1, you can have ADC1 periodically
+dump converted data into an array in memory. The DMA controller can
+then interrupt you when the array is half-full and full, and if any
+error occurred.
+
+Since channels are multiplexed in hardware, you can't simultaneously
+use the same DMA channel to serve requests from two of its peripherals
+at the same time. For example, if you are using DMA 1 channel 1 to
+serve DMA requests from ADC1, you can't also serve requests from Timer
+2 channel 3.
+
+Channel Priority
+----------------
+
+An arbiter prioritizes simultaneous channel DMA requests. Channel
+priority levels are configurable (4 levels of priority). Ties within
+a DMA controller are broken by choosing the lower channel number;
+between the controllers, DMA1 has higher priority than DMA2.
+
+Interrupts
+----------
+
+You can cause an interrupt to fire once half the transfers are
+complete, when all the transfers are complete, if an error occurs
+during transfer, or any combination of the three.
+
+If an error occurs, the transfer is automatically disabled.
+
+Configuration
+-------------
+
+In order to configure a DMA transfer for DMA controller n, channel x,
+ST RM0008 says you should do the following:
+
+ A. Set the peripheral register address in DMAn_BASE->CPARx.
+ B. Set the memory address in DMAn_BASE->CMARx.
+ C. Set the number of data to be transferred in DMAn_BASE->CNDTRx.
+ D. Set the channel priority via the PL bits in DMAn_BASE->CCRx.
+ E. Configure various other things (e.g. data transfer sizes, what
+ events cause channel interrupts) in DMAn_BASE->CCRx as desired.
+ F. Activate the channel by setting ENABLE bit in DMAn_BASE->CCRx.
+
+The channel will start serving DMA requests as soon as it's activated.
+
+The DMA library lets you accomplish these tasks as follows:
+
+ **Setup transfer**
+
+ Do (A), (B), and (E) using dma_setup_transfer().
+
+ This also does (D), but chooses the lowest priority by default.
+
+ **Perform any other desired configuration**
+
+ You can do (C) using dma_set_num_transfers().
+
+ You can do (D) using dma_set_priority().
+
+ You can attach interrupt handlers with dma_attach_interrupt().
+
+ **Activate the channel**
+
+ Do (F) with dma_enable().
+
+Once you're all done, you can dma_disable() the channel. If you
+dma_detach_interrupt() an interrupt handler, the channel interrupts
+will stop firing, but the transfer itself won't stop until it's done
+(which never happens if you set the DMA_CIRC_MODE flag when you called
+dma_setup_transfer()).