/* * \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 Atmel Support */ #include #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<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; }