pico-sdk/src/rp2_common/hardware_dma/include/hardware/dma.h
2022-06-29 20:56:45 -05:00

841 lines
32 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 processors
* 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 RP2040s 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, dont 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 sniffers 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