diff options
Diffstat (limited to 'libmaple/dma.c')
| -rw-r--r-- | libmaple/dma.c | 399 | 
1 files changed, 313 insertions, 86 deletions
| diff --git a/libmaple/dma.c b/libmaple/dma.c index 15c96e1..04cdcea 100644 --- a/libmaple/dma.c +++ b/libmaple/dma.c @@ -23,126 +23,353 @@   *****************************************************************************/  /** - *  @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;  } -void DMAChannel1_IRQHandler(void) { -    dispatch_handler(0); +/** + * @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; +} + +/** + * @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 DMAChannel2_IRQHandler(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 DMAChannel3_IRQHandler(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 + * @sideeffect 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 DMAChannel4_IRQHandler(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 DMAChannel5_IRQHandler(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 DMAChannel6_IRQHandler(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 DMAChannel7_IRQHandler(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. + * @param addr Memory base address to use. + */ +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. + * @param addr Peripheral memory base address to use. + */ +void dma_set_per_addr(dma_dev *dev, dma_channel channel, __io void *addr) { +    dma_channel_reg_map *chan_regs; -    if (regs != NULL) { -        rcc_clk_enable(RCC_DMA1); +    ASSERT_FAULT(!dma_is_channel_enabled(dev, channel)); -        regs->CCR = ((0 << 12)        /* Low priority */ -                     | (msize << 10) -                     | (psize <<  8) -                     | (0 << 0)       /* Disabled (until started) */ -                     | mode); +    chan_regs = dma_channel_regs(dev, channel); +    chan_regs->CPAR = (uint32)addr; +} + +/* + * 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 __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 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_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 | 
