mbed library sources
Fork of mbed-src by
Diff: targets/hal/TARGET_Atmel/TARGET_SAM21/drivers/dma/dma.c
- Revision:
- 613:bc40b8d2aec4
- Parent:
- 612:fba1c7dc54c0
- Child:
- 614:9d86c2ae5de0
--- a/targets/hal/TARGET_Atmel/TARGET_SAM21/drivers/dma/dma.c Tue Aug 18 15:00:09 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,607 +0,0 @@ -#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]; - -/** - * \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) - 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_TERR | - DMAC_CHINTENSET_TCMPL | DMAC_CHINTENSET_SUSP; - - /* 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; -}