t
Fork of mbed-dev by
Diff: targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/drivers/dma/dma.c
- Revision:
- 15:a81a8d6c1dfe
diff -r d797fbdad187 -r a81a8d6c1dfe targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/drivers/dma/dma.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/drivers/dma/dma.c Wed Nov 04 16:30:11 2015 +0000 @@ -0,0 +1,654 @@ +/* + * \file + * + * \brief SAM Direct Memory Access Controller Driver + * + * Copyright (C) 2014-2015 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_ACTON_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) || (SAMC20) || (SAMC21) + 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 be also 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; +}