123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654 |
- /*
- * \file
- *
- * \brief SAM Direct Memory Access Controller Driver
- *
- * Copyright (C) 2014-2016 Atmel Corporation. All rights reserved.
- *
- * \asf_license_start
- *
- * \page License
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * 3. The name of Atmel may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * 4. This software may only be redistributed and used in connection with an
- * Atmel microcontroller product.
- *
- * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
- * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * \asf_license_stop
- *
- */
- /*
- * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
- */
- #include <string.h>
- #include "dma.h"
- #include "clock.h"
- #include "system_interrupt.h"
- struct _dma_module {
- volatile bool _dma_init;
- volatile uint32_t allocated_channels;
- uint8_t free_channels;
- };
- struct _dma_module _dma_inst = {
- ._dma_init = false,
- .allocated_channels = 0,
- .free_channels = CONF_MAX_USED_CHANNEL_NUM,
- };
- /** Maximum retry counter for resuming a job transfer. */
- #define MAX_JOB_RESUME_COUNT 10000
- /** DMA channel mask. */
- #define DMA_CHANNEL_MASK (0x1f)
- COMPILER_ALIGNED(16)
- DmacDescriptor descriptor_section[CONF_MAX_USED_CHANNEL_NUM] SECTION_DMAC_DESCRIPTOR;
- /** Initial write back memory section. */
- COMPILER_ALIGNED(16)
- static DmacDescriptor _write_back_section[CONF_MAX_USED_CHANNEL_NUM] SECTION_DMAC_DESCRIPTOR;
- /** Internal DMA resource pool. */
- static struct dma_resource* _dma_active_resource[CONF_MAX_USED_CHANNEL_NUM];
- /* DMA channel interrup flag. */
- uint8_t g_chan_interrupt_flag[CONF_MAX_USED_CHANNEL_NUM]={0};
- /**
- * \brief Find a free channel for a DMA resource.
- *
- * Find a channel for the requested DMA resource.
- *
- * \return Status of channel allocation.
- * \retval DMA_INVALID_CHANNEL No channel available
- * \retval count Allocated channel for the DMA resource
- */
- static uint8_t _dma_find_first_free_channel_and_allocate(void)
- {
- uint8_t count;
- uint32_t tmp;
- bool allocated = false;
- system_interrupt_enter_critical_section();
- tmp = _dma_inst.allocated_channels;
- for (count = 0; count < CONF_MAX_USED_CHANNEL_NUM; ++count) {
- if (!(tmp & 0x00000001)) {
- /* If free channel found, set as allocated and return
- *number */
- _dma_inst.allocated_channels |= 1 << count;
- _dma_inst.free_channels--;
- allocated = true;
- break;
- }
- tmp = tmp >> 1;
- }
- system_interrupt_leave_critical_section();
- if (!allocated) {
- return DMA_INVALID_CHANNEL;
- } else {
- return count;
- }
- }
- /**
- * \brief Release an allocated DMA channel.
- *
- * \param[in] channel Channel id to be released
- *
- */
- static void _dma_release_channel(uint8_t channel)
- {
- _dma_inst.allocated_channels &= ~(1 << channel);
- _dma_inst.free_channels++;
- }
- /**
- * \brief Configure the DMA resource.
- *
- * \param[in] dma_resource Pointer to a DMA resource instance
- * \param[out] resource_config Configurations of the DMA resource
- *
- */
- static void _dma_set_config(struct dma_resource *resource,
- struct dma_resource_config *resource_config)
- {
- Assert(resource);
- Assert(resource_config);
- uint32_t temp_CHCTRLB_reg;
- system_interrupt_enter_critical_section();
- /** Select the DMA channel and clear software trigger */
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << resource->channel_id));
- temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(resource_config->priority) | \
- DMAC_CHCTRLB_TRIGSRC(resource_config->peripheral_trigger) | \
- DMAC_CHCTRLB_TRIGACT(resource_config->trigger_action);
- if(resource_config->event_config.input_action){
- temp_CHCTRLB_reg |= DMAC_CHCTRLB_EVIE | DMAC_CHCTRLB_EVACT(
- resource_config->event_config.input_action);
- }
- /** Enable event output, the event output selection is configured in
- * each transfer descriptor */
- if (resource_config->event_config.event_output_enable) {
- temp_CHCTRLB_reg |= DMAC_CHCTRLB_EVOE;
- }
- /* Write config to CTRLB register */
- DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
- system_interrupt_leave_critical_section();
- }
- /**
- * \brief DMA interrupt service routine.
- *
- */
- void DMAC_Handler( void )
- {
- uint8_t active_channel;
- struct dma_resource *resource;
- uint8_t isr;
- uint32_t write_size;
- uint32_t total_size;
- system_interrupt_enter_critical_section();
- /* Get Pending channel */
- active_channel = DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk;
- Assert(_dma_active_resource[active_channel]);
- /* Get active DMA resource based on channel */
- resource = _dma_active_resource[active_channel];
- /* Select the active channel */
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- isr = DMAC->CHINTFLAG.reg;
- /* Calculate block transfer size of the DMA transfer */
- total_size = descriptor_section[resource->channel_id].BTCNT.reg;
- write_size = _write_back_section[resource->channel_id].BTCNT.reg;
- resource->transfered_size = total_size - write_size;
- /* DMA channel interrupt handler */
- if (isr & DMAC_CHINTENCLR_TERR) {
- /* Clear transfer error flag */
- DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
- /* Set I/O ERROR status */
- resource->job_status = STATUS_ERR_IO;
- /* Execute the callback function */
- if ((resource->callback_enable & (1<<DMA_CALLBACK_TRANSFER_ERROR)) &&
- (resource->callback[DMA_CALLBACK_TRANSFER_ERROR])) {
- resource->callback[DMA_CALLBACK_TRANSFER_ERROR](resource);
- }
- } else if (isr & DMAC_CHINTENCLR_TCMPL) {
- /* Clear the transfer complete flag */
- DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL;
- /* Set job status */
- resource->job_status = STATUS_OK;
- /* Execute the callback function */
- if ((resource->callback_enable & (1 << DMA_CALLBACK_TRANSFER_DONE)) &&
- (resource->callback[DMA_CALLBACK_TRANSFER_DONE])) {
- resource->callback[DMA_CALLBACK_TRANSFER_DONE](resource);
- }
- } else if (isr & DMAC_CHINTENCLR_SUSP) {
- /* Clear channel suspend flag */
- DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
- /* Set job status */
- resource->job_status = STATUS_SUSPEND;
- /* Execute the callback function */
- if ((resource->callback_enable & (1 << DMA_CALLBACK_CHANNEL_SUSPEND)) &&
- (resource->callback[DMA_CALLBACK_CHANNEL_SUSPEND])){
- resource->callback[DMA_CALLBACK_CHANNEL_SUSPEND](resource);
- }
- }
- system_interrupt_leave_critical_section();
- }
- /**
- * \brief Initializes config with predefined default values.
- *
- * This function will initialize a given DMA configuration structure to
- * a set of known default values. This function should be called on
- * any new instance of the configuration structure before being
- * modified by the user application.
- *
- * The default configuration is as follows:
- * \li Software trigger is used as the transfer trigger
- * \li Priority level 0
- * \li Only software/event trigger
- * \li Requires a trigger for each transaction
- * \li No event input /output
- * \li DMA channel is disabled during sleep mode (if has the feature)
- * \param[out] config Pointer to the configuration
- *
- */
- void dma_get_config_defaults(struct dma_resource_config *config)
- {
- Assert(config);
- /* Set as priority 0 */
- config->priority = DMA_PRIORITY_LEVEL_0;
- /* Only software/event trigger */
- config->peripheral_trigger = 0;
- /* Transaction trigger */
- config->trigger_action = DMA_TRIGGER_ACTION_TRANSACTION;
- /* Event configurations, no event input/output */
- config->event_config.input_action = DMA_EVENT_INPUT_NOACT;
- config->event_config.event_output_enable = false;
- #ifdef FEATURE_DMA_CHANNEL_STANDBY
- config->run_in_standby = false;
- #endif
- }
- /**
- * \brief Allocate a DMA with configurations.
- *
- * This function will allocate a proper channel for a DMA transfer request.
- *
- * \param[in,out] dma_resource Pointer to a DMA resource instance
- * \param[in] transfer_config Configurations of the DMA transfer
- *
- * \return Status of the allocation procedure.
- *
- * \retval STATUS_OK The DMA resource was allocated successfully
- * \retval STATUS_ERR_NOT_FOUND DMA resource allocation failed
- */
- enum status_code dma_allocate(struct dma_resource *resource,
- struct dma_resource_config *config)
- {
- uint8_t new_channel;
- Assert(resource);
- system_interrupt_enter_critical_section();
- if (!_dma_inst._dma_init) {
- /* Initialize clocks for DMA */
- #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30)
- system_ahb_clock_set_mask(MCLK_AHBMASK_DMAC);
- #else
- system_ahb_clock_set_mask(PM_AHBMASK_DMAC);
- system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB,
- PM_APBBMASK_DMAC);
- #endif
- /* Perform a software reset before enable DMA controller */
- DMAC->CTRL.reg &= ~DMAC_CTRL_DMAENABLE;
- DMAC->CTRL.reg = DMAC_CTRL_SWRST;
- /* Setup descriptor base address and write back section base
- * address */
- DMAC->BASEADDR.reg = (uint32_t)descriptor_section;
- DMAC->WRBADDR.reg = (uint32_t)_write_back_section;
- /* Enable all priority level at the same time */
- DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
- _dma_inst._dma_init = true;
- }
- /* Find the proper channel */
- new_channel = _dma_find_first_free_channel_and_allocate();
- /* If no channel available, return not found */
- if (new_channel == DMA_INVALID_CHANNEL) {
- system_interrupt_leave_critical_section();
- return STATUS_ERR_NOT_FOUND;
- }
- /* Set the channel */
- resource->channel_id = new_channel;
- /** Perform a reset for the allocated channel */
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
- DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
- #ifdef FEATURE_DMA_CHANNEL_STANDBY
- if(config->run_in_standby){
- DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_RUNSTDBY;
- }
- #endif
- /** Configure the DMA control,channel registers and descriptors here */
- _dma_set_config(resource, config);
- resource->descriptor = NULL;
- /* Log the DMA resource into the internal DMA resource pool */
- _dma_active_resource[resource->channel_id] = resource;
- system_interrupt_leave_critical_section();
- return STATUS_OK;
- }
- /**
- * \brief Free an allocated DMA resource.
- *
- * This function will free an allocated DMA resource.
- *
- * \param[in,out] resource Pointer to the DMA resource
- *
- * \return Status of the free procedure.
- *
- * \retval STATUS_OK The DMA resource was freed successfully
- * \retval STATUS_BUSY The DMA resource was busy and can't be freed
- * \retval STATUS_ERR_NOT_INITIALIZED DMA resource was not initialized
- */
- enum status_code dma_free(struct dma_resource *resource)
- {
- Assert(resource);
- Assert(resource->channel_id != DMA_INVALID_CHANNEL);
- system_interrupt_enter_critical_section();
- /* Check if channel is busy */
- if (dma_is_busy(resource)) {
- system_interrupt_leave_critical_section();
- return STATUS_BUSY;
- }
- /* Check if DMA resource was not allocated */
- if (!(_dma_inst.allocated_channels & (1 << resource->channel_id))) {
- system_interrupt_leave_critical_section();
- return STATUS_ERR_NOT_INITIALIZED;
- }
- /* Release the DMA resource */
- _dma_release_channel(resource->channel_id);
- /* Reset the item in the DMA resource pool */
- _dma_active_resource[resource->channel_id] = NULL;
- system_interrupt_leave_critical_section();
- return STATUS_OK;
- }
- /**
- * \brief Start a DMA transfer.
- *
- * This function will start a DMA transfer through an allocated DMA resource.
- *
- * \param[in,out] resource Pointer to the DMA resource
- *
- * \return Status of the transfer start procedure.
- *
- * \retval STATUS_OK The transfer was started successfully
- * \retval STATUS_BUSY The DMA resource was busy and the transfer was not started
- * \retval STATUS_ERR_INVALID_ARG Transfer size is 0 and transfer was not started
- */
- enum status_code dma_start_transfer_job(struct dma_resource *resource)
- {
- Assert(resource);
- Assert(resource->channel_id != DMA_INVALID_CHANNEL);
- system_interrupt_enter_critical_section();
- /* Check if resource was busy */
- if (resource->job_status == STATUS_BUSY) {
- system_interrupt_leave_critical_section();
- return STATUS_BUSY;
- }
- /* Check if transfer size is valid */
- if (resource->descriptor->BTCNT.reg == 0) {
- system_interrupt_leave_critical_section();
- return STATUS_ERR_INVALID_ARG;
- }
- /* Enable DMA interrupt */
- system_interrupt_enable(SYSTEM_INTERRUPT_MODULE_DMA);
- /* Set the interrupt flag */
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- DMAC->CHINTENSET.reg = (DMAC_CHINTENSET_MASK & g_chan_interrupt_flag[resource->channel_id]);
- /* Set job status */
- resource->job_status = STATUS_BUSY;
- /* Set channel x descriptor 0 to the descriptor base address */
- memcpy(&descriptor_section[resource->channel_id], resource->descriptor,
- sizeof(DmacDescriptor));
- /* Enable the transfer channel */
- DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
- system_interrupt_leave_critical_section();
- return STATUS_OK;
- }
- /**
- * \brief Abort a DMA transfer.
- *
- * This function will abort a DMA transfer. The DMA channel used for the DMA
- * resource will be disabled.
- * The block transfer count will also be calculated and written to the DMA
- * resource structure.
- *
- * \note The DMA resource will not be freed after calling this function.
- * The function \ref dma_free() can be used to free an allocated resource.
- *
- * \param[in,out] resource Pointer to the DMA resource
- *
- */
- void dma_abort_job(struct dma_resource *resource)
- {
- uint32_t write_size;
- uint32_t total_size;
- Assert(resource);
- Assert(resource->channel_id != DMA_INVALID_CHANNEL);
- system_interrupt_enter_critical_section();
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- DMAC->CHCTRLA.reg = 0;
- system_interrupt_leave_critical_section();
- /* Get transferred size */
- total_size = descriptor_section[resource->channel_id].BTCNT.reg;
- write_size = _write_back_section[resource->channel_id].BTCNT.reg;
- resource->transfered_size = total_size - write_size;
- resource->job_status = STATUS_ABORTED;
- }
- /**
- * \brief Suspend a DMA transfer.
- *
- * This function will request to suspend the transfer of the DMA resource.
- * The channel is kept enabled, can receive transfer triggers (the transfer
- * pending bit will be set), but will be removed from the arbitration scheme.
- * The channel operation can be resumed by calling \ref dma_resume_job().
- *
- * \note This function sets the command to suspend the DMA channel
- * associated with a DMA resource. The channel suspend interrupt flag
- * indicates whether the transfer is truly suspended.
- *
- * \param[in] resource Pointer to the DMA resource
- *
- */
- void dma_suspend_job(struct dma_resource *resource)
- {
- Assert(resource);
- Assert(resource->channel_id != DMA_INVALID_CHANNEL);
- system_interrupt_enter_critical_section();
- /* Select the channel */
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- /* Send the suspend request */
- DMAC->CHCTRLB.reg |= DMAC_CHCTRLB_CMD_SUSPEND;
- system_interrupt_leave_critical_section();
- }
- /**
- * \brief Resume a suspended DMA transfer.
- *
- * This function try to resume a suspended transfer of a DMA resource.
- *
- * \param[in] resource Pointer to the DMA resource
- *
- */
- void dma_resume_job(struct dma_resource *resource)
- {
- uint32_t bitmap_channel;
- uint32_t count = 0;
- Assert(resource);
- Assert(resource->channel_id != DMA_INVALID_CHANNEL);
- /* Get bitmap of the allocated DMA channel */
- bitmap_channel = (1 << resource->channel_id);
- /* Check if channel was suspended */
- if (resource->job_status != STATUS_SUSPEND) {
- return;
- }
- system_interrupt_enter_critical_section();
- /* Send resume request */
- DMAC->CHID.reg = DMAC_CHID_ID(resource->channel_id);
- DMAC->CHCTRLB.reg |= DMAC_CHCTRLB_CMD_RESUME;
- system_interrupt_leave_critical_section();
- /* Check if transfer job resumed */
- for (count = 0; count < MAX_JOB_RESUME_COUNT; count++) {
- if ((DMAC->BUSYCH.reg & bitmap_channel) == bitmap_channel) {
- break;
- }
- }
- if (count < MAX_JOB_RESUME_COUNT) {
- /* Job resumed */
- resource->job_status = STATUS_BUSY;
- } else {
- /* Job resume timeout */
- resource->job_status = STATUS_ERR_TIMEOUT;
- }
- }
- /**
- * \brief Create a DMA transfer descriptor with configurations.
- *
- * This function will set the transfer configurations to the DMA transfer
- * descriptor.
- *
- * \param[in] descriptor Pointer to the DMA transfer descriptor
- * \param[in] config Pointer to the descriptor configuration structure
- *
- */
- void dma_descriptor_create(DmacDescriptor* descriptor,
- struct dma_descriptor_config *config)
- {
- /* Set block transfer control */
- descriptor->BTCTRL.bit.VALID = config->descriptor_valid;
- descriptor->BTCTRL.bit.EVOSEL = config->event_output_selection;
- descriptor->BTCTRL.bit.BLOCKACT = config->block_action;
- descriptor->BTCTRL.bit.BEATSIZE = config->beat_size;
- descriptor->BTCTRL.bit.SRCINC = config->src_increment_enable;
- descriptor->BTCTRL.bit.DSTINC = config->dst_increment_enable;
- descriptor->BTCTRL.bit.STEPSEL = config->step_selection;
- descriptor->BTCTRL.bit.STEPSIZE = config->step_size;
- /* Set transfer size, source address and destination address */
- descriptor->BTCNT.reg = config->block_transfer_count;
- descriptor->SRCADDR.reg = config->source_address;
- descriptor->DSTADDR.reg = config->destination_address;
- /* Set next transfer descriptor address */
- descriptor->DESCADDR.reg = config->next_descriptor_address;
- }
- /**
- * \brief Add a DMA transfer descriptor to a DMA resource.
- *
- * This function will add a DMA transfer descriptor to a DMA resource.
- * If there was a transfer descriptor already allocated to the DMA resource,
- * the descriptor will be linked to the next descriptor address.
- *
- * \param[in] resource Pointer to the DMA resource
- * \param[in] descriptor Pointer to the transfer descriptor
- *
- * \retval STATUS_OK The descriptor is added to the DMA resource
- * \retval STATUS_BUSY The DMA resource was busy and the descriptor is not added
- */
- enum status_code dma_add_descriptor(struct dma_resource *resource,
- DmacDescriptor* descriptor)
- {
- DmacDescriptor* desc = resource->descriptor;
- if (resource->job_status == STATUS_BUSY) {
- return STATUS_BUSY;
- }
- /* Look up for an empty space for the descriptor */
- if (desc == NULL) {
- resource->descriptor = descriptor;
- } else {
- /* Looking for end of descriptor link */
- while(desc->DESCADDR.reg != 0) {
- desc = (DmacDescriptor*)(desc->DESCADDR.reg);
- }
- /* Set to the end of descriptor list */
- desc->DESCADDR.reg = (uint32_t)descriptor;
- }
- return STATUS_OK;
- }
|