/* * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #ifndef _HARDWARE_DMA_H_ #define _HARDWARE_DMA_H_ #include "pico.h" #include "hardware/structs/dma.h" #include "hardware/regs/dreq.h" #include "pico/assert.h" #ifdef __cplusplus extern "C" { #endif /** \file hardware/dma.h * \defgroup hardware_dma hardware_dma * * DMA Controller API * * The RP2040 Direct Memory Access (DMA) master performs bulk data transfers on a processor’s * behalf. This leaves processors free to attend to other tasks, or enter low-power sleep states. The * data throughput of the DMA is also significantly higher than one of RP2040’s processors. * * The DMA can perform one read access and one write access, up to 32 bits in size, every clock cycle. * There are 12 independent channels, which each supervise a sequence of bus transfers, usually in * one of the following scenarios: * * * Memory to peripheral * * Peripheral to memory * * Memory to memory */ // these are not defined in generated dreq.h #define DREQ_DMA_TIMER0 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER0 #define DREQ_DMA_TIMER1 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER1 #define DREQ_DMA_TIMER2 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER2 #define DREQ_DMA_TIMER3 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER3 #define DREQ_FORCE DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT // PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_DMA, Enable/disable DMA assertions, type=bool, default=0, group=hardware_dma #ifndef PARAM_ASSERTIONS_ENABLED_DMA #define PARAM_ASSERTIONS_ENABLED_DMA 0 #endif static inline void check_dma_channel_param(__unused uint channel) { #if PARAM_ASSERTIONS_ENABLED(DMA) // this method is used a lot by inline functions so avoid code bloat by deferring to function extern void check_dma_channel_param_impl(uint channel); check_dma_channel_param_impl(channel); #endif } static inline void check_dma_timer_param(__unused uint timer_num) { valid_params_if(DMA, timer_num < NUM_DMA_TIMERS); } inline static dma_channel_hw_t *dma_channel_hw_addr(uint channel) { check_dma_channel_param(channel); return &dma_hw->ch[channel]; } /*! \brief Mark a dma channel as used * \ingroup hardware_dma * * Method for cooperative claiming of hardware. Will cause a panic if the channel * is already claimed. Use of this method by libraries detects accidental * configurations that would fail in unpredictable ways. * * \param channel the dma channel */ void dma_channel_claim(uint channel); /*! \brief Mark multiple dma channels as used * \ingroup hardware_dma * * Method for cooperative claiming of hardware. Will cause a panic if any of the channels * are already claimed. Use of this method by libraries detects accidental * configurations that would fail in unpredictable ways. * * \param channel_mask Bitfield of all required channels to claim (bit 0 == channel 0, bit 1 == channel 1 etc) */ void dma_claim_mask(uint32_t channel_mask); /*! \brief Mark a dma channel as no longer used * \ingroup hardware_dma * * Method for cooperative claiming of hardware. * * \param channel the dma channel to release */ void dma_channel_unclaim(uint channel); /*! \brief Claim a free dma channel * \ingroup hardware_dma * * \param required if true the function will panic if none are available * \return the dma channel number or -1 if required was false, and none were free */ int dma_claim_unused_channel(bool required); /*! \brief Determine if a dma channel is claimed * \ingroup hardware_dma * * \param channel the dma channel * \return true if the channel is claimed, false otherwise * \see dma_channel_claim * \see dma_channel_claim_mask */ bool dma_channel_is_claimed(uint channel); /** \brief DMA channel configuration * \defgroup channel_config channel_config * \ingroup hardware_dma * * A DMA channel needs to be configured, these functions provide handy helpers to set up configuration * structures. See \ref dma_channel_config */ /*! \brief Enumeration of available DMA channel transfer sizes. * \ingroup hardware_dma * * Names indicate the number of bits. */ enum dma_channel_transfer_size { DMA_SIZE_8 = 0, ///< Byte transfer (8 bits) DMA_SIZE_16 = 1, ///< Half word transfer (16 bits) DMA_SIZE_32 = 2 ///< Word transfer (32 bits) }; typedef struct { uint32_t ctrl; } dma_channel_config; /*! \brief Set DMA channel read increment in a channel configuration object * \ingroup channel_config * * \param c Pointer to channel configuration object * \param incr True to enable read address increments, if false, each read will be from the same address * Usually disabled for peripheral to memory transfers */ static inline void channel_config_set_read_increment(dma_channel_config *c, bool incr) { c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_READ_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_READ_BITS); } /*! \brief Set DMA channel write increment in a channel configuration object * \ingroup channel_config * * \param c Pointer to channel configuration object * \param incr True to enable write address increments, if false, each write will be to the same address * Usually disabled for memory to peripheral transfers * Usually disabled for memory to peripheral transfers */ static inline void channel_config_set_write_increment(dma_channel_config *c, bool incr) { c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); } /*! \brief Select a transfer request signal in a channel configuration object * \ingroup channel_config * * The channel uses the transfer request signal to pace its data transfer rate. * Sources for TREQ signals are internal (TIMERS) or external (DREQ, a Data Request from the system). * 0x0 to 0x3a -> select DREQ n as TREQ * 0x3b -> Select Timer 0 as TREQ * 0x3c -> Select Timer 1 as TREQ * 0x3d -> Select Timer 2 as TREQ (Optional) * 0x3e -> Select Timer 3 as TREQ (Optional) * 0x3f -> Permanent request, for unpaced transfers. * * \param c Pointer to channel configuration data * \param dreq Source (see description) */ static inline void channel_config_set_dreq(dma_channel_config *c, uint dreq) { assert(dreq <= DREQ_FORCE); c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS) | (dreq << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB); } /*! \brief Set DMA channel chain_to channel in a channel configuration object * \ingroup channel_config * * When this channel completes, it will trigger the channel indicated by chain_to. Disable by * setting chain_to to itself (the same channel) * * \param c Pointer to channel configuration object * \param chain_to Channel to trigger when this channel completes. */ static inline void channel_config_set_chain_to(dma_channel_config *c, uint chain_to) { assert(chain_to <= NUM_DMA_CHANNELS); c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (chain_to << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); } /*! \brief Set the size of each DMA bus transfer in a channel configuration object * \ingroup channel_config * * Set the size of each bus transfer (byte/halfword/word). The read and write addresses * advance by the specific amount (1/2/4 bytes) with each transfer. * * \param c Pointer to channel configuration object * \param size See enum for possible values. */ static inline void channel_config_set_transfer_data_size(dma_channel_config *c, enum dma_channel_transfer_size size) { assert(size == DMA_SIZE_8 || size == DMA_SIZE_16 || size == DMA_SIZE_32); c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) | (((uint)size) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); } /*! \brief Set address wrapping parameters in a channel configuration object * \ingroup channel_config * * Size of address wrap region. If 0, don’t wrap. For values n > 0, only the lower n bits of the address * will change. This wraps the address on a (1 << n) byte boundary, facilitating access to naturally-aligned * ring buffers. * Ring sizes between 2 and 32768 bytes are possible (size_bits from 1 - 15) * * 0x0 -> No wrapping. * * \param c Pointer to channel configuration object * \param write True to apply to write addresses, false to apply to read addresses * \param size_bits 0 to disable wrapping. Otherwise the size in bits of the changing part of the address. * Effectively wraps the address on a (1 << size_bits) byte boundary. */ static inline void channel_config_set_ring(dma_channel_config *c, bool write, uint size_bits) { assert(size_bits < 32); c->ctrl = (c->ctrl & ~(DMA_CH0_CTRL_TRIG_RING_SIZE_BITS | DMA_CH0_CTRL_TRIG_RING_SEL_BITS)) | (size_bits << DMA_CH0_CTRL_TRIG_RING_SIZE_LSB) | (write ? DMA_CH0_CTRL_TRIG_RING_SEL_BITS : 0); } /*! \brief Set DMA byte swapping config in a channel configuration object * \ingroup channel_config * * No effect for byte data, for halfword data, the two bytes of each halfword are * swapped. For word data, the four bytes of each word are swapped to reverse their order. * * \param c Pointer to channel configuration object * \param bswap True to enable byte swapping */ static inline void channel_config_set_bswap(dma_channel_config *c, bool bswap) { c->ctrl = bswap ? (c->ctrl | DMA_CH0_CTRL_TRIG_BSWAP_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_BSWAP_BITS); } /*! \brief Set IRQ quiet mode in a channel configuration object * \ingroup channel_config * * In QUIET mode, the channel does not generate IRQs at the end of every transfer block. Instead, * an IRQ is raised when NULL is written to a trigger register, indicating the end of a control * block chain. * * \param c Pointer to channel configuration object * \param irq_quiet True to enable quiet mode, false to disable. */ static inline void channel_config_set_irq_quiet(dma_channel_config *c, bool irq_quiet) { c->ctrl = irq_quiet ? (c->ctrl | DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS); } /*! * \brief Set the channel priority in a channel configuration object * \ingroup channel_config * * When true, gives a channel preferential treatment in issue scheduling: in each scheduling round, * all high priority channels are considered first, and then only a single low * priority channel, before returning to the high priority channels. * * This only affects the order in which the DMA schedules channels. The DMA's bus priority is not changed. * If the DMA is not saturated then a low priority channel will see no loss of throughput. * * \param c Pointer to channel configuration object * \param high_priority True to enable high priority */ static inline void channel_config_set_high_priority(dma_channel_config *c, bool high_priority) { c->ctrl = high_priority ? (c->ctrl | DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS); } /*! * \brief Enable/Disable the DMA channel in a channel configuration object * \ingroup channel_config * * When false, the channel will ignore triggers, stop issuing transfers, and pause the current transfer sequence (i.e. BUSY will * remain high if already high) * * \param c Pointer to channel configuration object * \param enable True to enable the DMA channel. When enabled, the channel will respond to triggering events, and start transferring data. * */ static inline void channel_config_set_enable(dma_channel_config *c, bool enable) { c->ctrl = enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_EN_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_EN_BITS); } /*! \brief Enable access to channel by sniff hardware in a channel configuration object * \ingroup channel_config * * Sniff HW must be enabled and have this channel selected. * * \param c Pointer to channel configuration object * \param sniff_enable True to enable the Sniff HW access to this DMA channel. */ static inline void channel_config_set_sniff_enable(dma_channel_config *c, bool sniff_enable) { c->ctrl = sniff_enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS); } /*! \brief Get the default channel configuration for a given channel * \ingroup channel_config * * Setting | Default * --------|-------- * Read Increment | true * Write Increment | false * DReq | DREQ_FORCE * Chain to | self * Data size | DMA_SIZE_32 * Ring | write=false, size=0 (i.e. off) * Byte Swap | false * Quiet IRQs | false * High Priority | false * Channel Enable | true * Sniff Enable | false * * \param channel DMA channel * \return the default configuration which can then be modified. */ static inline dma_channel_config dma_channel_get_default_config(uint channel) { dma_channel_config c = {0}; channel_config_set_read_increment(&c, true); channel_config_set_write_increment(&c, false); channel_config_set_dreq(&c, DREQ_FORCE); channel_config_set_chain_to(&c, channel); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); channel_config_set_ring(&c, false, 0); channel_config_set_bswap(&c, false); channel_config_set_irq_quiet(&c, false); channel_config_set_enable(&c, true); channel_config_set_sniff_enable(&c, false); channel_config_set_high_priority( &c, false); return c; } /*! \brief Get the current configuration for the specified channel. * \ingroup channel_config * * \param channel DMA channel * \return The current configuration as read from the HW register (not cached) */ static inline dma_channel_config dma_get_channel_config(uint channel) { dma_channel_config c; c.ctrl = dma_channel_hw_addr(channel)->ctrl_trig; return c; } /*! \brief Get the raw configuration register from a channel configuration * \ingroup channel_config * * \param config Pointer to a config structure. * \return Register content */ static inline uint32_t channel_config_get_ctrl_value(const dma_channel_config *config) { return config->ctrl; } /*! \brief Set a channel configuration * \ingroup hardware_dma * * \param channel DMA channel * \param config Pointer to a config structure with required configuration * \param trigger True to trigger the transfer immediately */ static inline void dma_channel_set_config(uint channel, const dma_channel_config *config, bool trigger) { // Don't use CTRL_TRIG since we don't want to start a transfer if (!trigger) { dma_channel_hw_addr(channel)->al1_ctrl = channel_config_get_ctrl_value(config); } else { dma_channel_hw_addr(channel)->ctrl_trig = channel_config_get_ctrl_value(config); } } /*! \brief Set the DMA initial read address. * \ingroup hardware_dma * * \param channel DMA channel * \param read_addr Initial read address of transfer. * \param trigger True to start the transfer immediately */ static inline void dma_channel_set_read_addr(uint channel, const volatile void *read_addr, bool trigger) { if (!trigger) { dma_channel_hw_addr(channel)->read_addr = (uintptr_t) read_addr; } else { dma_channel_hw_addr(channel)->al3_read_addr_trig = (uintptr_t) read_addr; } } /*! \brief Set the DMA initial write address * \ingroup hardware_dma * * \param channel DMA channel * \param write_addr Initial write address of transfer. * \param trigger True to start the transfer immediately */ static inline void dma_channel_set_write_addr(uint channel, volatile void *write_addr, bool trigger) { if (!trigger) { dma_channel_hw_addr(channel)->write_addr = (uintptr_t) write_addr; } else { dma_channel_hw_addr(channel)->al2_write_addr_trig = (uintptr_t) write_addr; } } /*! \brief Set the number of bus transfers the channel will do * \ingroup hardware_dma * * \param channel DMA channel * \param trans_count The number of transfers (not NOT bytes, see channel_config_set_transfer_data_size) * \param trigger True to start the transfer immediately */ static inline void dma_channel_set_trans_count(uint channel, uint32_t trans_count, bool trigger) { if (!trigger) { dma_channel_hw_addr(channel)->transfer_count = trans_count; } else { dma_channel_hw_addr(channel)->al1_transfer_count_trig = trans_count; } } /*! \brief Configure all DMA parameters and optionally start transfer * \ingroup hardware_dma * * \param channel DMA channel * \param config Pointer to DMA config structure * \param write_addr Initial write address * \param read_addr Initial read address * \param transfer_count Number of transfers to perform * \param trigger True to start the transfer immediately */ static inline void dma_channel_configure(uint channel, const dma_channel_config *config, volatile void *write_addr, const volatile void *read_addr, uint transfer_count, bool trigger) { dma_channel_set_read_addr(channel, read_addr, false); dma_channel_set_write_addr(channel, write_addr, false); dma_channel_set_trans_count(channel, transfer_count, false); dma_channel_set_config(channel, config, trigger); } /*! \brief Start a DMA transfer from a buffer immediately * \ingroup hardware_dma * * \param channel DMA channel * \param read_addr Sets the initial read address * \param transfer_count Number of transfers to make. Not bytes, but the number of transfers of channel_config_set_transfer_data_size() to be sent. */ inline static void __attribute__((always_inline)) dma_channel_transfer_from_buffer_now(uint channel, const volatile void *read_addr, uint32_t transfer_count) { // check_dma_channel_param(channel); dma_channel_hw_t *hw = dma_channel_hw_addr(channel); hw->read_addr = (uintptr_t) read_addr; hw->al1_transfer_count_trig = transfer_count; } /*! \brief Start a DMA transfer to a buffer immediately * \ingroup hardware_dma * * \param channel DMA channel * \param write_addr Sets the initial write address * \param transfer_count Number of transfers to make. Not bytes, but the number of transfers of channel_config_set_transfer_data_size() to be sent. */ inline static void dma_channel_transfer_to_buffer_now(uint channel, volatile void *write_addr, uint32_t transfer_count) { dma_channel_hw_t *hw = dma_channel_hw_addr(channel); hw->write_addr = (uintptr_t) write_addr; hw->al1_transfer_count_trig = transfer_count; } /*! \brief Start one or more channels simultaneously * \ingroup hardware_dma * * \param chan_mask Bitmask of all the channels requiring starting. Channel 0 = bit 0, channel 1 = bit 1 etc. */ static inline void dma_start_channel_mask(uint32_t chan_mask) { valid_params_if(DMA, chan_mask && chan_mask < (1u << NUM_DMA_CHANNELS)); dma_hw->multi_channel_trigger = chan_mask; } /*! \brief Start a single DMA channel * \ingroup hardware_dma * * \param channel DMA channel */ static inline void dma_channel_start(uint channel) { dma_start_channel_mask(1u << channel); } /*! \brief Stop a DMA transfer * \ingroup hardware_dma * * Function will only return once the DMA has stopped. * * Note that due to errata RP2040-E13, aborting a channel which has transfers * in-flight (i.e. an individual read has taken place but the corresponding write has not), the ABORT * status bit will clear prematurely, and subsequently the in-flight * transfers will trigger a completion interrupt once they complete. * * The effect of this is that you \em may see a spurious completion interrupt * on the channel as a result of calling this method. * * The calling code should be sure to ignore a completion IRQ as a result of this method. This may * not require any additional work, as aborting a channel which may be about to complete, when you have a completion * IRQ handler registered, is inherently race-prone, and so code is likely needed to disambiguate the two occurrences. * * If that is not the case, but you do have a channel completion IRQ handler registered, you can simply * disable/re-enable the IRQ around the call to this method as shown by this code fragment (using DMA IRQ0). * * \code * // disable the channel on IRQ0 * dma_channel_set_irq0_enabled(channel, false); * // abort the channel * dma_channel_abort(channel); * // clear the spurious IRQ (if there was one) * dma_channel_acknowledge_irq0(channel); * // re-enable the channel on IRQ0 * dma_channel_set_irq0_enabled(channel, true); *\endcode * * \param channel DMA channel */ static inline void dma_channel_abort(uint channel) { check_dma_channel_param(channel); dma_hw->abort = 1u << channel; // Bit will go 0 once channel has reached safe state // (i.e. any in-flight transfers have retired) while (dma_hw->ch[channel].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents(); } /*! \brief Enable single DMA channel's interrupt via DMA_IRQ_0 * \ingroup hardware_dma * * \param channel DMA channel * \param enabled true to enable interrupt 0 on specified channel, false to disable. */ static inline void dma_channel_set_irq0_enabled(uint channel, bool enabled) { check_dma_channel_param(channel); check_hw_layout(dma_hw_t, inte0, DMA_INTE0_OFFSET); if (enabled) hw_set_bits(&dma_hw->inte0, 1u << channel); else hw_clear_bits(&dma_hw->inte0, 1u << channel); } /*! \brief Enable multiple DMA channels' interrupts via DMA_IRQ_0 * \ingroup hardware_dma * * \param channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc. * \param enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask. */ static inline void dma_set_irq0_channel_mask_enabled(uint32_t channel_mask, bool enabled) { if (enabled) { hw_set_bits(&dma_hw->inte0, channel_mask); } else { hw_clear_bits(&dma_hw->inte0, channel_mask); } } /*! \brief Enable single DMA channel's interrupt via DMA_IRQ_1 * \ingroup hardware_dma * * \param channel DMA channel * \param enabled true to enable interrupt 1 on specified channel, false to disable. */ static inline void dma_channel_set_irq1_enabled(uint channel, bool enabled) { check_dma_channel_param(channel); check_hw_layout(dma_hw_t, inte1, DMA_INTE1_OFFSET); if (enabled) hw_set_bits(&dma_hw->inte1, 1u << channel); else hw_clear_bits(&dma_hw->inte1, 1u << channel); } /*! \brief Enable multiple DMA channels' interrupts via DMA_IRQ_1 * \ingroup hardware_dma * * \param channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc. * \param enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask. */ static inline void dma_set_irq1_channel_mask_enabled(uint32_t channel_mask, bool enabled) { if (enabled) { hw_set_bits(&dma_hw->inte1, channel_mask); } else { hw_clear_bits(&dma_hw->inte1, channel_mask); } } /*! \brief Enable single DMA channel interrupt on either DMA_IRQ_0 or DMA_IRQ_1 * \ingroup hardware_dma * * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 * \param channel DMA channel * \param enabled true to enable interrupt via irq_index for specified channel, false to disable. */ static inline void dma_irqn_set_channel_enabled(uint irq_index, uint channel, bool enabled) { invalid_params_if(DMA, irq_index > 1); if (irq_index) { dma_channel_set_irq1_enabled(channel, enabled); } else { dma_channel_set_irq0_enabled(channel, enabled); } } /*! \brief Enable multiple DMA channels' interrupt via either DMA_IRQ_0 or DMA_IRQ_1 * \ingroup hardware_dma * * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 * \param channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc. * \param enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask. */ static inline void dma_irqn_set_channel_mask_enabled(uint irq_index, uint32_t channel_mask, bool enabled) { invalid_params_if(DMA, irq_index > 1); if (irq_index) { dma_set_irq1_channel_mask_enabled(channel_mask, enabled); } else { dma_set_irq0_channel_mask_enabled(channel_mask, enabled); } } /*! \brief Determine if a particular channel is a cause of DMA_IRQ_0 * \ingroup hardware_dma * * \param channel DMA channel * \return true if the channel is a cause of DMA_IRQ_0, false otherwise */ static inline bool dma_channel_get_irq0_status(uint channel) { check_dma_channel_param(channel); return dma_hw->ints0 & (1u << channel); } /*! \brief Determine if a particular channel is a cause of DMA_IRQ_1 * \ingroup hardware_dma * * \param channel DMA channel * \return true if the channel is a cause of DMA_IRQ_1, false otherwise */ static inline bool dma_channel_get_irq1_status(uint channel) { check_dma_channel_param(channel); return dma_hw->ints1 & (1u << channel); } /*! \brief Determine if a particular channel is a cause of DMA_IRQ_N * \ingroup hardware_dma * * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 * \param channel DMA channel * \return true if the channel is a cause of the DMA_IRQ_N, false otherwise */ static inline bool dma_irqn_get_channel_status(uint irq_index, uint channel) { invalid_params_if(DMA, irq_index > 1); check_dma_channel_param(channel); return (irq_index ? dma_hw->ints1 : dma_hw->ints0) & (1u << channel); } /*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_0 * \ingroup hardware_dma * * \param channel DMA channel */ static inline void dma_channel_acknowledge_irq0(uint channel) { check_dma_channel_param(channel); hw_set_bits(&dma_hw->ints0, (1u << channel)); } /*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_1 * \ingroup hardware_dma * * \param channel DMA channel */ static inline void dma_channel_acknowledge_irq1(uint channel) { check_dma_channel_param(channel); hw_set_bits(&dma_hw->ints1, (1u << channel)); } /*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_N * \ingroup hardware_dma * * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 * \param channel DMA channel */ static inline void dma_irqn_acknowledge_channel(uint irq_index, uint channel) { invalid_params_if(DMA, irq_index > 1); check_dma_channel_param(channel); hw_set_bits(irq_index ? &dma_hw->ints1 : &dma_hw->ints0, (1u << channel)); } /*! \brief Check if DMA channel is busy * \ingroup hardware_dma * * \param channel DMA channel * \return true if the channel is currently busy */ inline static bool dma_channel_is_busy(uint channel) { check_dma_channel_param(channel); return !!(dma_hw->ch[channel].al1_ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS); } /*! \brief Wait for a DMA channel transfer to complete * \ingroup hardware_dma * * \param channel DMA channel */ inline static void dma_channel_wait_for_finish_blocking(uint channel) { while (dma_channel_is_busy(channel)) tight_loop_contents(); // stop the compiler hoisting a non volatile buffer access above the DMA completion. __compiler_memory_barrier(); } /*! \brief Enable the DMA sniffing targeting the specified channel * \ingroup hardware_dma * * The mode can be one of the following: * * Mode | Function * -----|--------- * 0x0 | Calculate a CRC-32 (IEEE802.3 polynomial) * 0x1 | Calculate a CRC-32 (IEEE802.3 polynomial) with bit reversed data * 0x2 | Calculate a CRC-16-CCITT * 0x3 | Calculate a CRC-16-CCITT with bit reversed data * 0xe | XOR reduction over all data. == 1 if the total 1 population count is odd. * 0xf | Calculate a simple 32-bit checksum (addition with a 32 bit accumulator) * * \param channel DMA channel * \param mode See description * \param force_channel_enable Set true to also turn on sniffing in the channel configuration (this * is usually what you want, but sometimes you might have a chain DMA with only certain segments * of the chain sniffed, in which case you might pass false). */ inline static void dma_sniffer_enable(uint channel, uint mode, bool force_channel_enable) { check_dma_channel_param(channel); check_hw_layout(dma_hw_t, sniff_ctrl, DMA_SNIFF_CTRL_OFFSET); if (force_channel_enable) { hw_set_bits(&dma_hw->ch[channel].al1_ctrl, DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS); } dma_hw->sniff_ctrl = ((channel << DMA_SNIFF_CTRL_DMACH_LSB) & DMA_SNIFF_CTRL_DMACH_BITS) | ((mode << DMA_SNIFF_CTRL_CALC_LSB) & DMA_SNIFF_CTRL_CALC_BITS) | DMA_SNIFF_CTRL_EN_BITS; } /*! \brief Enable the Sniffer byte swap function * \ingroup hardware_dma * * Locally perform a byte reverse on the sniffed data, before feeding into checksum. * * Note that the sniff hardware is downstream of the DMA channel byteswap performed in the * read master: if channel_config_set_bswap() and dma_sniffer_set_byte_swap_enabled() are both enabled, * their effects cancel from the sniffer’s point of view. * * \param swap Set true to enable byte swapping */ inline static void dma_sniffer_set_byte_swap_enabled(bool swap) { if (swap) hw_set_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_BSWAP_BITS); else hw_clear_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_BSWAP_BITS); } /*! \brief Disable the DMA sniffer * \ingroup hardware_dma * */ inline static void dma_sniffer_disable(void) { dma_hw->sniff_ctrl = 0; } /*! \brief Mark a dma timer as used * \ingroup hardware_dma * * Method for cooperative claiming of hardware. Will cause a panic if the timer * is already claimed. Use of this method by libraries detects accidental * configurations that would fail in unpredictable ways. * * \param timer the dma timer */ void dma_timer_claim(uint timer); /*! \brief Mark a dma timer as no longer used * \ingroup hardware_dma * * Method for cooperative claiming of hardware. * * \param timer the dma timer to release */ void dma_timer_unclaim(uint timer); /*! \brief Claim a free dma timer * \ingroup hardware_dma * * \param required if true the function will panic if none are available * \return the dma timer number or -1 if required was false, and none were free */ int dma_claim_unused_timer(bool required); /*! \brief Determine if a dma timer is claimed * \ingroup hardware_dma * * \param timer the dma timer * \return true if the timer is claimed, false otherwise * \see dma_timer_claim */ bool dma_timer_is_claimed(uint timer); /*! \brief Set the divider for the given DMA timer * \ingroup hardware_dma * * The timer will run at the system_clock_freq * numerator / denominator, so this is the speed * that data elements will be transferred at via a DMA channel using this timer as a DREQ * * \param timer the dma timer * \param numerator the fraction's numerator * \param denominator the fraction's denominator */ static inline void dma_timer_set_fraction(uint timer, uint16_t numerator, uint16_t denominator) { check_dma_timer_param(timer); dma_hw->timer[timer] = (((uint32_t)numerator) << DMA_TIMER0_X_LSB) | (((uint32_t)denominator) << DMA_TIMER0_Y_LSB); } /*! \brief Return the DREQ number for a given DMA timer * \ingroup hardware_dma * * \param timer_num DMA timer number 0-3 */ static inline uint dma_get_timer_dreq(uint timer_num) { static_assert(DREQ_DMA_TIMER1 == DREQ_DMA_TIMER0 + 1, ""); static_assert(DREQ_DMA_TIMER2 == DREQ_DMA_TIMER0 + 2, ""); static_assert(DREQ_DMA_TIMER3 == DREQ_DMA_TIMER0 + 3, ""); check_dma_timer_param(timer_num); return DREQ_DMA_TIMER0 + timer_num; } #ifndef NDEBUG void print_dma_ctrl(dma_channel_hw_t *channel); #endif #ifdef __cplusplus } #endif #endif