| /***************************************************************************** |
| * Copyright 2003 - 2008 Broadcom Corporation. All rights reserved. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2, available at |
| * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a |
| * license other than the GPL, without Broadcom's express prior written |
| * consent. |
| *****************************************************************************/ |
| |
| /****************************************************************************/ |
| /** |
| * @file dmacHw.c |
| * |
| * @brief Low level DMA controller driver routines |
| * |
| * @note |
| * |
| * These routines provide basic DMA functionality only. |
| */ |
| /****************************************************************************/ |
| |
| /* ---- Include Files ---------------------------------------------------- */ |
| #include <csp/stdint.h> |
| #include <csp/string.h> |
| #include <stddef.h> |
| |
| #include <csp/dmacHw.h> |
| #include <mach/csp/dmacHw_reg.h> |
| #include <mach/csp/dmacHw_priv.h> |
| #include <mach/csp/chipcHw_inline.h> |
| |
| /* ---- External Function Prototypes ------------------------------------- */ |
| |
| /* Allocate DMA control blocks */ |
| dmacHw_CBLK_t dmacHw_gCblk[dmacHw_MAX_CHANNEL_COUNT]; |
| |
| uint32_t dmaChannelCount_0 = dmacHw_MAX_CHANNEL_COUNT / 2; |
| uint32_t dmaChannelCount_1 = dmacHw_MAX_CHANNEL_COUNT / 2; |
| |
| /****************************************************************************/ |
| /** |
| * @brief Get maximum FIFO for a DMA channel |
| * |
| * @return Maximum allowable FIFO size |
| * |
| * |
| */ |
| /****************************************************************************/ |
| static uint32_t GetFifoSize(dmacHw_HANDLE_t handle /* [ IN ] DMA Channel handle */ |
| ) { |
| uint32_t val = 0; |
| dmacHw_CBLK_t *pCblk = dmacHw_HANDLE_TO_CBLK(handle); |
| dmacHw_MISC_t *pMiscReg = |
| (dmacHw_MISC_t *) dmacHw_REG_MISC_BASE(pCblk->module); |
| |
| switch (pCblk->channel) { |
| case 0: |
| val = (pMiscReg->CompParm2.lo & 0x70000000) >> 28; |
| break; |
| case 1: |
| val = (pMiscReg->CompParm3.hi & 0x70000000) >> 28; |
| break; |
| case 2: |
| val = (pMiscReg->CompParm3.lo & 0x70000000) >> 28; |
| break; |
| case 3: |
| val = (pMiscReg->CompParm4.hi & 0x70000000) >> 28; |
| break; |
| case 4: |
| val = (pMiscReg->CompParm4.lo & 0x70000000) >> 28; |
| break; |
| case 5: |
| val = (pMiscReg->CompParm5.hi & 0x70000000) >> 28; |
| break; |
| case 6: |
| val = (pMiscReg->CompParm5.lo & 0x70000000) >> 28; |
| break; |
| case 7: |
| val = (pMiscReg->CompParm6.hi & 0x70000000) >> 28; |
| break; |
| } |
| |
| if (val <= 0x4) { |
| return 8 << val; |
| } else { |
| dmacHw_ASSERT(0); |
| } |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Program channel register to initiate transfer |
| * |
| * @return void |
| * |
| * |
| * @note |
| * - Descriptor buffer MUST ALWAYS be flushed before calling this function |
| * - This function should also be called from ISR to program the channel with |
| * pending descriptors |
| */ |
| /****************************************************************************/ |
| void dmacHw_initiateTransfer(dmacHw_HANDLE_t handle, /* [ IN ] DMA Channel handle */ |
| dmacHw_CONFIG_t *pConfig, /* [ IN ] Configuration settings */ |
| void *pDescriptor /* [ IN ] Descriptor buffer */ |
| ) { |
| dmacHw_DESC_RING_t *pRing; |
| dmacHw_DESC_t *pProg; |
| dmacHw_CBLK_t *pCblk; |
| |
| pCblk = dmacHw_HANDLE_TO_CBLK(handle); |
| pRing = dmacHw_GET_DESC_RING(pDescriptor); |
| |
| if (CHANNEL_BUSY(pCblk->module, pCblk->channel)) { |
| /* Not safe yet to program the channel */ |
| return; |
| } |
| |
| if (pCblk->varDataStarted) { |
| if (pCblk->descUpdated) { |
| pCblk->descUpdated = 0; |
| pProg = |
| (dmacHw_DESC_t *) ((uint32_t) |
| dmacHw_REG_LLP(pCblk->module, |
| pCblk->channel) + |
| pRing->virt2PhyOffset); |
| |
| /* Load descriptor if not loaded */ |
| if (!(pProg->ctl.hi & dmacHw_REG_CTL_DONE)) { |
| dmacHw_SET_SAR(pCblk->module, pCblk->channel, |
| pProg->sar); |
| dmacHw_SET_DAR(pCblk->module, pCblk->channel, |
| pProg->dar); |
| dmacHw_REG_CTL_LO(pCblk->module, |
| pCblk->channel) = |
| pProg->ctl.lo; |
| dmacHw_REG_CTL_HI(pCblk->module, |
| pCblk->channel) = |
| pProg->ctl.hi; |
| } else if (pProg == (dmacHw_DESC_t *) pRing->pEnd->llp) { |
| /* Return as end descriptor is processed */ |
| return; |
| } else { |
| dmacHw_ASSERT(0); |
| } |
| } else { |
| return; |
| } |
| } else { |
| if (pConfig->transferMode == dmacHw_TRANSFER_MODE_PERIODIC) { |
| /* Do not make a single chain, rather process one descriptor at a time */ |
| pProg = pRing->pHead; |
| /* Point to the next descriptor for next iteration */ |
| dmacHw_NEXT_DESC(pRing, pHead); |
| } else { |
| /* Return if no more pending descriptor */ |
| if (pRing->pEnd == NULL) { |
| return; |
| } |
| |
| pProg = pRing->pProg; |
| if (pConfig->transferMode == |
| dmacHw_TRANSFER_MODE_CONTINUOUS) { |
| /* Make sure a complete ring can be formed */ |
| dmacHw_ASSERT((dmacHw_DESC_t *) pRing->pEnd-> |
| llp == pRing->pProg); |
| /* Make sure pProg pointing to the pHead */ |
| dmacHw_ASSERT((dmacHw_DESC_t *) pRing->pProg == |
| pRing->pHead); |
| /* Make a complete ring */ |
| do { |
| pRing->pProg->ctl.lo |= |
| (dmacHw_REG_CTL_LLP_DST_EN | |
| dmacHw_REG_CTL_LLP_SRC_EN); |
| pRing->pProg = |
| (dmacHw_DESC_t *) pRing->pProg->llp; |
| } while (pRing->pProg != pRing->pHead); |
| } else { |
| /* Make a single long chain */ |
| while (pRing->pProg != pRing->pEnd) { |
| pRing->pProg->ctl.lo |= |
| (dmacHw_REG_CTL_LLP_DST_EN | |
| dmacHw_REG_CTL_LLP_SRC_EN); |
| pRing->pProg = |
| (dmacHw_DESC_t *) pRing->pProg->llp; |
| } |
| } |
| } |
| |
| /* Program the channel registers */ |
| dmacHw_SET_SAR(pCblk->module, pCblk->channel, pProg->sar); |
| dmacHw_SET_DAR(pCblk->module, pCblk->channel, pProg->dar); |
| dmacHw_SET_LLP(pCblk->module, pCblk->channel, |
| (uint32_t) pProg - pRing->virt2PhyOffset); |
| dmacHw_REG_CTL_LO(pCblk->module, pCblk->channel) = |
| pProg->ctl.lo; |
| dmacHw_REG_CTL_HI(pCblk->module, pCblk->channel) = |
| pProg->ctl.hi; |
| if (pRing->pEnd) { |
| /* Remember the descriptor to use next */ |
| pRing->pProg = (dmacHw_DESC_t *) pRing->pEnd->llp; |
| } |
| /* Indicate no more pending descriptor */ |
| pRing->pEnd = (dmacHw_DESC_t *) NULL; |
| } |
| /* Start DMA operation */ |
| dmacHw_DMA_START(pCblk->module, pCblk->channel); |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Initializes DMA |
| * |
| * This function initializes DMA CSP driver |
| * |
| * @note |
| * Must be called before using any DMA channel |
| */ |
| /****************************************************************************/ |
| void dmacHw_initDma(void) |
| { |
| |
| uint32_t i = 0; |
| |
| dmaChannelCount_0 = dmacHw_GET_NUM_CHANNEL(0); |
| dmaChannelCount_1 = dmacHw_GET_NUM_CHANNEL(1); |
| |
| /* Enable access to the DMA block */ |
| chipcHw_busInterfaceClockEnable(chipcHw_REG_BUS_CLOCK_DMAC0); |
| chipcHw_busInterfaceClockEnable(chipcHw_REG_BUS_CLOCK_DMAC1); |
| |
| if ((dmaChannelCount_0 + dmaChannelCount_1) > dmacHw_MAX_CHANNEL_COUNT) { |
| dmacHw_ASSERT(0); |
| } |
| |
| memset((void *)dmacHw_gCblk, 0, |
| sizeof(dmacHw_CBLK_t) * (dmaChannelCount_0 + dmaChannelCount_1)); |
| for (i = 0; i < dmaChannelCount_0; i++) { |
| dmacHw_gCblk[i].module = 0; |
| dmacHw_gCblk[i].channel = i; |
| } |
| for (i = 0; i < dmaChannelCount_1; i++) { |
| dmacHw_gCblk[i + dmaChannelCount_0].module = 1; |
| dmacHw_gCblk[i + dmaChannelCount_0].channel = i; |
| } |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Exit function for DMA |
| * |
| * This function isolates DMA from the system |
| * |
| */ |
| /****************************************************************************/ |
| void dmacHw_exitDma(void) |
| { |
| /* Disable access to the DMA block */ |
| chipcHw_busInterfaceClockDisable(chipcHw_REG_BUS_CLOCK_DMAC0); |
| chipcHw_busInterfaceClockDisable(chipcHw_REG_BUS_CLOCK_DMAC1); |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Gets a handle to a DMA channel |
| * |
| * This function returns a handle, representing a control block of a particular DMA channel |
| * |
| * @return -1 - On Failure |
| * handle - On Success, representing a channel control block |
| * |
| * @note |
| * None Channel ID must be created using "dmacHw_MAKE_CHANNEL_ID" macro |
| */ |
| /****************************************************************************/ |
| dmacHw_HANDLE_t dmacHw_getChannelHandle(dmacHw_ID_t channelId /* [ IN ] DMA Channel Id */ |
| ) { |
| int idx; |
| |
| switch ((channelId >> 8)) { |
| case 0: |
| dmacHw_ASSERT((channelId & 0xff) < dmaChannelCount_0); |
| idx = (channelId & 0xff); |
| break; |
| case 1: |
| dmacHw_ASSERT((channelId & 0xff) < dmaChannelCount_1); |
| idx = dmaChannelCount_0 + (channelId & 0xff); |
| break; |
| default: |
| dmacHw_ASSERT(0); |
| return (dmacHw_HANDLE_t) -1; |
| } |
| |
| return dmacHw_CBLK_TO_HANDLE(&dmacHw_gCblk[idx]); |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Initializes a DMA channel for use |
| * |
| * This function initializes and resets a DMA channel for use |
| * |
| * @return -1 - On Failure |
| * 0 - On Success |
| * |
| * @note |
| * None |
| */ |
| /****************************************************************************/ |
| int dmacHw_initChannel(dmacHw_HANDLE_t handle /* [ IN ] DMA Channel handle */ |
| ) { |
| dmacHw_CBLK_t *pCblk = dmacHw_HANDLE_TO_CBLK(handle); |
| int module = pCblk->module; |
| int channel = pCblk->channel; |
| |
| /* Reinitialize the control block */ |
| memset((void *)pCblk, 0, sizeof(dmacHw_CBLK_t)); |
| pCblk->module = module; |
| pCblk->channel = channel; |
| |
| /* Enable DMA controller */ |
| dmacHw_DMA_ENABLE(pCblk->module); |
| /* Reset DMA channel */ |
| dmacHw_RESET_CONTROL_LO(pCblk->module, pCblk->channel); |
| dmacHw_RESET_CONTROL_HI(pCblk->module, pCblk->channel); |
| dmacHw_RESET_CONFIG_LO(pCblk->module, pCblk->channel); |
| dmacHw_RESET_CONFIG_HI(pCblk->module, pCblk->channel); |
| |
| /* Clear all raw interrupt status */ |
| dmacHw_TRAN_INT_CLEAR(pCblk->module, pCblk->channel); |
| dmacHw_BLOCK_INT_CLEAR(pCblk->module, pCblk->channel); |
| dmacHw_ERROR_INT_CLEAR(pCblk->module, pCblk->channel); |
| |
| /* Mask event specific interrupts */ |
| dmacHw_TRAN_INT_DISABLE(pCblk->module, pCblk->channel); |
| dmacHw_BLOCK_INT_DISABLE(pCblk->module, pCblk->channel); |
| dmacHw_STRAN_INT_DISABLE(pCblk->module, pCblk->channel); |
| dmacHw_DTRAN_INT_DISABLE(pCblk->module, pCblk->channel); |
| dmacHw_ERROR_INT_DISABLE(pCblk->module, pCblk->channel); |
| |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Finds amount of memory required to form a descriptor ring |
| * |
| * |
| * @return Number of bytes required to form a descriptor ring |
| * |
| * |
| */ |
| /****************************************************************************/ |
| uint32_t dmacHw_descriptorLen(uint32_t descCnt /* [ IN ] Number of descriptor in the ring */ |
| ) { |
| /* Need extra 4 byte to ensure 32 bit alignment */ |
| return (descCnt * sizeof(dmacHw_DESC_t)) + sizeof(dmacHw_DESC_RING_t) + |
| sizeof(uint32_t); |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Initializes descriptor ring |
| * |
| * This function will initializes the descriptor ring of a DMA channel |
| * |
| * |
| * @return -1 - On failure |
| * 0 - On success |
| * @note |
| * - "len" parameter should be obtained from "dmacHw_descriptorLen" |
| * - Descriptor buffer MUST be 32 bit aligned and uncached as it is |
| * accessed by ARM and DMA |
| */ |
| /****************************************************************************/ |
| int dmacHw_initDescriptor(void *pDescriptorVirt, /* [ IN ] Virtual address of uncahced buffer allocated to form descriptor ring */ |
| uint32_t descriptorPhyAddr, /* [ IN ] Physical address of pDescriptorVirt (descriptor buffer) */ |
| uint32_t len, /* [ IN ] Size of the pBuf */ |
| uint32_t num /* [ IN ] Number of descriptor in the ring */ |
| ) { |
| uint32_t i; |
| dmacHw_DESC_RING_t *pRing; |
| dmacHw_DESC_t *pDesc; |
| |
| /* Check the alignment of the descriptor */ |
| if ((uint32_t) pDescriptorVirt & 0x00000003) { |
| dmacHw_ASSERT(0); |
| return -1; |
| } |
| |
| /* Check if enough space has been allocated for descriptor ring */ |
| if (len < dmacHw_descriptorLen(num)) { |
| return -1; |
| } |
| |
| pRing = dmacHw_GET_DESC_RING(pDescriptorVirt); |
| pRing->pHead = |
| (dmacHw_DESC_t *) ((uint32_t) pRing + sizeof(dmacHw_DESC_RING_t)); |
| pRing->pFree = pRing->pTail = pRing->pEnd = pRing->pHead; |
| pRing->pProg = dmacHw_DESC_INIT; |
| /* Initialize link item chain, starting from the head */ |
| pDesc = pRing->pHead; |
| /* Find the offset between virtual to physical address */ |
| pRing->virt2PhyOffset = (uint32_t) pDescriptorVirt - descriptorPhyAddr; |
| |
| /* Form the descriptor ring */ |
| for (i = 0; i < num - 1; i++) { |
| /* Clear link list item */ |
| memset((void *)pDesc, 0, sizeof(dmacHw_DESC_t)); |
| /* Point to the next item in the physical address */ |
| pDesc->llpPhy = (uint32_t) (pDesc + 1) - pRing->virt2PhyOffset; |
| /* Point to the next item in the virtual address */ |
| pDesc->llp = (uint32_t) (pDesc + 1); |
| /* Mark descriptor is ready to use */ |
| pDesc->ctl.hi = dmacHw_DESC_FREE; |
| /* Look into next link list item */ |
| pDesc++; |
| } |
| |
| /* Clear last link list item */ |
| memset((void *)pDesc, 0, sizeof(dmacHw_DESC_t)); |
| /* Last item pointing to the first item in the |
| physical address to complete the ring */ |
| pDesc->llpPhy = (uint32_t) pRing->pHead - pRing->virt2PhyOffset; |
| /* Last item pointing to the first item in the |
| virtual address to complete the ring |
| */ |
| pDesc->llp = (uint32_t) pRing->pHead; |
| /* Mark descriptor is ready to use */ |
| pDesc->ctl.hi = dmacHw_DESC_FREE; |
| /* Set the number of descriptors in the ring */ |
| pRing->num = num; |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Configure DMA channel |
| * |
| * @return 0 : On success |
| * -1 : On failure |
| */ |
| /****************************************************************************/ |
| int dmacHw_configChannel(dmacHw_HANDLE_t handle, /* [ IN ] DMA Channel handle */ |
| dmacHw_CONFIG_t *pConfig /* [ IN ] Configuration settings */ |
| ) { |
| dmacHw_CBLK_t *pCblk = dmacHw_HANDLE_TO_CBLK(handle); |
| uint32_t cfgHigh = 0; |
| int srcTrSize; |
| int dstTrSize; |
| |
| pCblk->varDataStarted = 0; |
| pCblk->userData = NULL; |
| |
| /* Configure |
| - Burst transaction when enough data in available in FIFO |
| - AHB Access protection 1 |
| - Source and destination peripheral ports |
| */ |
| cfgHigh = |
| dmacHw_REG_CFG_HI_FIFO_ENOUGH | dmacHw_REG_CFG_HI_AHB_HPROT_1 | |
| dmacHw_SRC_PERI_INTF(pConfig-> |
| srcPeripheralPort) | |
| dmacHw_DST_PERI_INTF(pConfig->dstPeripheralPort); |
| /* Set priority */ |
| dmacHw_SET_CHANNEL_PRIORITY(pCblk->module, pCblk->channel, |
| pConfig->channelPriority); |
| |
| if (pConfig->dstStatusRegisterAddress != 0) { |
| /* Destination status update enable */ |
| cfgHigh |= dmacHw_REG_CFG_HI_UPDATE_DST_STAT; |
| /* Configure status registers */ |
| dmacHw_SET_DSTATAR(pCblk->module, pCblk->channel, |
| pConfig->dstStatusRegisterAddress); |
| } |
| |
| if (pConfig->srcStatusRegisterAddress != 0) { |
| /* Source status update enable */ |
| cfgHigh |= dmacHw_REG_CFG_HI_UPDATE_SRC_STAT; |
| /* Source status update enable */ |
| dmacHw_SET_SSTATAR(pCblk->module, pCblk->channel, |
| pConfig->srcStatusRegisterAddress); |
| } |
| /* Configure the config high register */ |
| dmacHw_GET_CONFIG_HI(pCblk->module, pCblk->channel) = cfgHigh; |
| |
| /* Clear all raw interrupt status */ |
| dmacHw_TRAN_INT_CLEAR(pCblk->module, pCblk->channel); |
| dmacHw_BLOCK_INT_CLEAR(pCblk->module, pCblk->channel); |
| dmacHw_ERROR_INT_CLEAR(pCblk->module, pCblk->channel); |
| |
| /* Configure block interrupt */ |
| if (pConfig->blockTransferInterrupt == dmacHw_INTERRUPT_ENABLE) { |
| dmacHw_BLOCK_INT_ENABLE(pCblk->module, pCblk->channel); |
| } else { |
| dmacHw_BLOCK_INT_DISABLE(pCblk->module, pCblk->channel); |
| } |
| /* Configure complete transfer interrupt */ |
| if (pConfig->completeTransferInterrupt == dmacHw_INTERRUPT_ENABLE) { |
| dmacHw_TRAN_INT_ENABLE(pCblk->module, pCblk->channel); |
| } else { |
| dmacHw_TRAN_INT_DISABLE(pCblk->module, pCblk->channel); |
| } |
| /* Configure error interrupt */ |
| if (pConfig->errorInterrupt == dmacHw_INTERRUPT_ENABLE) { |
| dmacHw_ERROR_INT_ENABLE(pCblk->module, pCblk->channel); |
| } else { |
| dmacHw_ERROR_INT_DISABLE(pCblk->module, pCblk->channel); |
| } |
| /* Configure gather register */ |
| if (pConfig->srcGatherWidth) { |
| srcTrSize = |
| dmacHw_GetTrWidthInBytes(pConfig->srcMaxTransactionWidth); |
| if (! |
| ((pConfig->srcGatherWidth % srcTrSize) |
| && (pConfig->srcGatherJump % srcTrSize))) { |
| dmacHw_REG_SGR_LO(pCblk->module, pCblk->channel) = |
| ((pConfig->srcGatherWidth / |
| srcTrSize) << 20) | (pConfig->srcGatherJump / |
| srcTrSize); |
| } else { |
| return -1; |
| } |
| } |
| /* Configure scatter register */ |
| if (pConfig->dstScatterWidth) { |
| dstTrSize = |
| dmacHw_GetTrWidthInBytes(pConfig->dstMaxTransactionWidth); |
| if (! |
| ((pConfig->dstScatterWidth % dstTrSize) |
| && (pConfig->dstScatterJump % dstTrSize))) { |
| dmacHw_REG_DSR_LO(pCblk->module, pCblk->channel) = |
| ((pConfig->dstScatterWidth / |
| dstTrSize) << 20) | (pConfig->dstScatterJump / |
| dstTrSize); |
| } else { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Indicates whether DMA transfer is in progress or completed |
| * |
| * @return DMA transfer status |
| * dmacHw_TRANSFER_STATUS_BUSY: DMA Transfer ongoing |
| * dmacHw_TRANSFER_STATUS_DONE: DMA Transfer completed |
| * dmacHw_TRANSFER_STATUS_ERROR: DMA Transfer error |
| * |
| */ |
| /****************************************************************************/ |
| dmacHw_TRANSFER_STATUS_e dmacHw_transferCompleted(dmacHw_HANDLE_t handle /* [ IN ] DMA Channel handle */ |
| ) { |
| dmacHw_CBLK_t *pCblk = dmacHw_HANDLE_TO_CBLK(handle); |
| |
| if (CHANNEL_BUSY(pCblk->module, pCblk->channel)) { |
| return dmacHw_TRANSFER_STATUS_BUSY; |
| } else if (dmacHw_REG_INT_RAW_ERROR(pCblk->module) & |
| (0x00000001 << pCblk->channel)) { |
| return dmacHw_TRANSFER_STATUS_ERROR; |
| } |
| |
| return dmacHw_TRANSFER_STATUS_DONE; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Set descriptors for known data length |
| * |
| * When DMA has to work as a flow controller, this function prepares the |
| * descriptor chain to transfer data |
| * |
| * from: |
| * - Memory to memory |
| * - Peripheral to memory |
| * - Memory to Peripheral |
| * - Peripheral to Peripheral |
| * |
| * @return -1 - On failure |
| * 0 - On success |
| * |
| */ |
| /****************************************************************************/ |
| int dmacHw_setDataDescriptor(dmacHw_CONFIG_t *pConfig, /* [ IN ] Configuration settings */ |
| void *pDescriptor, /* [ IN ] Descriptor buffer */ |
| void *pSrcAddr, /* [ IN ] Source (Peripheral/Memory) address */ |
| void *pDstAddr, /* [ IN ] Destination (Peripheral/Memory) address */ |
| size_t dataLen /* [ IN ] Data length in bytes */ |
| ) { |
| dmacHw_TRANSACTION_WIDTH_e dstTrWidth; |
| dmacHw_TRANSACTION_WIDTH_e srcTrWidth; |
| dmacHw_DESC_RING_t *pRing = dmacHw_GET_DESC_RING(pDescriptor); |
| dmacHw_DESC_t *pStart; |
| dmacHw_DESC_t *pProg; |
| int srcTs = 0; |
| int blkTs = 0; |
| int oddSize = 0; |
| int descCount = 0; |
| int count = 0; |
| int dstTrSize = 0; |
| int srcTrSize = 0; |
| uint32_t maxBlockSize = dmacHw_MAX_BLOCKSIZE; |
| |
| dstTrSize = dmacHw_GetTrWidthInBytes(pConfig->dstMaxTransactionWidth); |
| srcTrSize = dmacHw_GetTrWidthInBytes(pConfig->srcMaxTransactionWidth); |
| |
| /* Skip Tx if buffer is NULL or length is unknown */ |
| if ((pSrcAddr == NULL) || (pDstAddr == NULL) || (dataLen == 0)) { |
| /* Do not initiate transfer */ |
| return -1; |
| } |
| |
| /* Ensure scatter and gather are transaction aligned */ |
| if ((pConfig->srcGatherWidth % srcTrSize) |
| || (pConfig->dstScatterWidth % dstTrSize)) { |
| return -2; |
| } |
| |
| /* |
| Background 1: DMAC can not perform DMA if source and destination addresses are |
| not properly aligned with the channel's transaction width. So, for successful |
| DMA transfer, transaction width must be set according to the alignment of the |
| source and destination address. |
| */ |
| |
| /* Adjust destination transaction width if destination address is not aligned properly */ |
| dstTrWidth = pConfig->dstMaxTransactionWidth; |
| while (dmacHw_ADDRESS_MASK(dstTrSize) & (uint32_t) pDstAddr) { |
| dstTrWidth = dmacHw_GetNextTrWidth(dstTrWidth); |
| dstTrSize = dmacHw_GetTrWidthInBytes(dstTrWidth); |
| } |
| |
| /* Adjust source transaction width if source address is not aligned properly */ |
| srcTrWidth = pConfig->srcMaxTransactionWidth; |
| while (dmacHw_ADDRESS_MASK(srcTrSize) & (uint32_t) pSrcAddr) { |
| srcTrWidth = dmacHw_GetNextTrWidth(srcTrWidth); |
| srcTrSize = dmacHw_GetTrWidthInBytes(srcTrWidth); |
| } |
| |
| /* Find the maximum transaction per descriptor */ |
| if (pConfig->maxDataPerBlock |
| && ((pConfig->maxDataPerBlock / srcTrSize) < |
| dmacHw_MAX_BLOCKSIZE)) { |
| maxBlockSize = pConfig->maxDataPerBlock / srcTrSize; |
| } |
| |
| /* Find number of source transactions needed to complete the DMA transfer */ |
| srcTs = dataLen / srcTrSize; |
| /* Find the odd number of bytes that need to be transferred as single byte transaction width */ |
| if (srcTs && (dstTrSize > srcTrSize)) { |
| oddSize = dataLen % dstTrSize; |
| /* Adjust source transaction count due to "oddSize" */ |
| srcTs = srcTs - (oddSize / srcTrSize); |
| } else { |
| oddSize = dataLen % srcTrSize; |
| } |
| /* Adjust "descCount" due to "oddSize" */ |
| if (oddSize) { |
| descCount++; |
| } |
| /* Find the number of descriptor needed for total "srcTs" */ |
| if (srcTs) { |
| descCount += ((srcTs - 1) / maxBlockSize) + 1; |
| } |
| |
| /* Check the availability of "descCount" discriptors in the ring */ |
| pProg = pRing->pHead; |
| for (count = 0; (descCount <= pRing->num) && (count < descCount); |
| count++) { |
| if ((pProg->ctl.hi & dmacHw_DESC_FREE) == 0) { |
| /* Sufficient descriptors are not available */ |
| return -3; |
| } |
| pProg = (dmacHw_DESC_t *) pProg->llp; |
| } |
| |
| /* Remember the link list item to program the channel registers */ |
| pStart = pProg = pRing->pHead; |
| /* Make a link list with "descCount(=count)" number of descriptors */ |
| while (count) { |
| /* Reset channel control information */ |
| pProg->ctl.lo = 0; |
| /* Enable source gather if configured */ |
| if (pConfig->srcGatherWidth) { |
| pProg->ctl.lo |= dmacHw_REG_CTL_SG_ENABLE; |
| } |
| /* Enable destination scatter if configured */ |
| if (pConfig->dstScatterWidth) { |
| pProg->ctl.lo |= dmacHw_REG_CTL_DS_ENABLE; |
| } |
| /* Set source and destination address */ |
| pProg->sar = (uint32_t) pSrcAddr; |
| pProg->dar = (uint32_t) pDstAddr; |
| /* Use "devCtl" to mark that user memory need to be freed later if needed */ |
| if (pProg == pRing->pHead) { |
| pProg->devCtl = dmacHw_FREE_USER_MEMORY; |
| } else { |
| pProg->devCtl = 0; |
| } |
| |
| blkTs = srcTs; |
| |
| /* Special treatmeant for last descriptor */ |
| if (count == 1) { |
| /* Mark the last descriptor */ |
| pProg->ctl.lo &= |
| ~(dmacHw_REG_CTL_LLP_DST_EN | |
| dmacHw_REG_CTL_LLP_SRC_EN); |
| /* Treatment for odd data bytes */ |
| if (oddSize) { |
| /* Adjust for single byte transaction width */ |
| switch (pConfig->transferType) { |
| case dmacHw_TRANSFER_TYPE_PERIPHERAL_TO_MEM: |
| dstTrWidth = |
| dmacHw_DST_TRANSACTION_WIDTH_8; |
| blkTs = |
| (oddSize / srcTrSize) + |
| ((oddSize % srcTrSize) ? 1 : 0); |
| break; |
| case dmacHw_TRANSFER_TYPE_MEM_TO_PERIPHERAL: |
| srcTrWidth = |
| dmacHw_SRC_TRANSACTION_WIDTH_8; |
| blkTs = oddSize; |
| break; |
| case dmacHw_TRANSFER_TYPE_MEM_TO_MEM: |
| srcTrWidth = |
| dmacHw_SRC_TRANSACTION_WIDTH_8; |
| dstTrWidth = |
| dmacHw_DST_TRANSACTION_WIDTH_8; |
| blkTs = oddSize; |
| break; |
| case dmacHw_TRANSFER_TYPE_PERIPHERAL_TO_PERIPHERAL: |
| /* Do not adjust the transaction width */ |
| break; |
| } |
| } else { |
| srcTs -= blkTs; |
| } |
| } else { |
| if (srcTs / maxBlockSize) { |
| blkTs = maxBlockSize; |
| } |
| /* Remaining source transactions for next iteration */ |
| srcTs -= blkTs; |
| } |
| /* Must have a valid source transactions */ |
| dmacHw_ASSERT(blkTs > 0); |
| /* Set control information */ |
| if (pConfig->flowControler == dmacHw_FLOW_CONTROL_DMA) { |
| pProg->ctl.lo |= pConfig->transferType | |
| pConfig->srcUpdate | |
| pConfig->dstUpdate | |
| srcTrWidth | |
| dstTrWidth | |
| pConfig->srcMaxBurstWidth | |
| pConfig->dstMaxBurstWidth | |
| pConfig->srcMasterInterface | |
| pConfig->dstMasterInterface | dmacHw_REG_CTL_INT_EN; |
| } else { |
| uint32_t transferType = 0; |
| switch (pConfig->transferType) { |
| case dmacHw_TRANSFER_TYPE_PERIPHERAL_TO_MEM: |
| transferType = dmacHw_REG_CTL_TTFC_PM_PERI; |
| break; |
| case dmacHw_TRANSFER_TYPE_MEM_TO_PERIPHERAL: |
| transferType = dmacHw_REG_CTL_TTFC_MP_PERI; |
| break; |
| default: |
| dmacHw_ASSERT(0); |
| } |
| pProg->ctl.lo |= transferType | |
| pConfig->srcUpdate | |
| pConfig->dstUpdate | |
| srcTrWidth | |
| dstTrWidth | |
| pConfig->srcMaxBurstWidth | |
| pConfig->dstMaxBurstWidth | |
| pConfig->srcMasterInterface | |
| pConfig->dstMasterInterface | dmacHw_REG_CTL_INT_EN; |
| } |
| |
| /* Set block transaction size */ |
| pProg->ctl.hi = blkTs & dmacHw_REG_CTL_BLOCK_TS_MASK; |
| /* Look for next descriptor */ |
| if (count > 1) { |
| /* Point to the next descriptor */ |
| pProg = (dmacHw_DESC_t *) pProg->llp; |
| |
| /* Update source and destination address for next iteration */ |
| switch (pConfig->transferType) { |
| case dmacHw_TRANSFER_TYPE_PERIPHERAL_TO_MEM: |
| if (pConfig->dstScatterWidth) { |
| pDstAddr = |
| (char *)pDstAddr + |
| blkTs * srcTrSize + |
| (((blkTs * srcTrSize) / |
| pConfig->dstScatterWidth) * |
| pConfig->dstScatterJump); |
| } else { |
| pDstAddr = |
| (char *)pDstAddr + |
| blkTs * srcTrSize; |
| } |
| break; |
| case dmacHw_TRANSFER_TYPE_MEM_TO_PERIPHERAL: |
| if (pConfig->srcGatherWidth) { |
| pSrcAddr = |
| (char *)pDstAddr + |
| blkTs * srcTrSize + |
| (((blkTs * srcTrSize) / |
| pConfig->srcGatherWidth) * |
| pConfig->srcGatherJump); |
| } else { |
| pSrcAddr = |
| (char *)pSrcAddr + |
| blkTs * srcTrSize; |
| } |
| break; |
| case dmacHw_TRANSFER_TYPE_MEM_TO_MEM: |
| if (pConfig->dstScatterWidth) { |
| pDstAddr = |
| (char *)pDstAddr + |
| blkTs * srcTrSize + |
| (((blkTs * srcTrSize) / |
| pConfig->dstScatterWidth) * |
| pConfig->dstScatterJump); |
| } else { |
| pDstAddr = |
| (char *)pDstAddr + |
| blkTs * srcTrSize; |
| } |
| |
| if (pConfig->srcGatherWidth) { |
| pSrcAddr = |
| (char *)pDstAddr + |
| blkTs * srcTrSize + |
| (((blkTs * srcTrSize) / |
| pConfig->srcGatherWidth) * |
| pConfig->srcGatherJump); |
| } else { |
| pSrcAddr = |
| (char *)pSrcAddr + |
| blkTs * srcTrSize; |
| } |
| break; |
| case dmacHw_TRANSFER_TYPE_PERIPHERAL_TO_PERIPHERAL: |
| /* Do not adjust the address */ |
| break; |
| default: |
| dmacHw_ASSERT(0); |
| } |
| } else { |
| /* At the end of transfer "srcTs" must be zero */ |
| dmacHw_ASSERT(srcTs == 0); |
| } |
| count--; |
| } |
| |
| /* Remember the descriptor to initialize the registers */ |
| if (pRing->pProg == dmacHw_DESC_INIT) { |
| pRing->pProg = pStart; |
| } |
| /* Indicate that the descriptor is updated */ |
| pRing->pEnd = pProg; |
| /* Head pointing to the next descriptor */ |
| pRing->pHead = (dmacHw_DESC_t *) pProg->llp; |
| /* Update Tail pointer if destination is a peripheral, |
| because no one is going to read from the pTail |
| */ |
| if (!dmacHw_DST_IS_MEMORY(pConfig->transferType)) { |
| pRing->pTail = pRing->pHead; |
| } |
| return 0; |
| } |
| |
| /****************************************************************************/ |
| /** |
| * @brief Provides DMA controller attributes |
| * |
| * |
| * @return DMA controller attributes |
| * |
| * @note |
| * None |
| */ |
| /****************************************************************************/ |
| uint32_t dmacHw_getDmaControllerAttribute(dmacHw_HANDLE_t handle, /* [ IN ] DMA Channel handle */ |
| dmacHw_CONTROLLER_ATTRIB_e attr /* [ IN ] DMA Controller attribute of type dmacHw_CONTROLLER_ATTRIB_e */ |
| ) { |
| dmacHw_CBLK_t *pCblk = dmacHw_HANDLE_TO_CBLK(handle); |
| |
| switch (attr) { |
| case dmacHw_CONTROLLER_ATTRIB_CHANNEL_NUM: |
| return dmacHw_GET_NUM_CHANNEL(pCblk->module); |
| case dmacHw_CONTROLLER_ATTRIB_CHANNEL_MAX_BLOCK_SIZE: |
| return (1 << |
| (dmacHw_GET_MAX_BLOCK_SIZE |
| (pCblk->module, pCblk->module) + 2)) - 8; |
| case dmacHw_CONTROLLER_ATTRIB_MASTER_INTF_NUM: |
| return dmacHw_GET_NUM_INTERFACE(pCblk->module); |
| case dmacHw_CONTROLLER_ATTRIB_CHANNEL_BUS_WIDTH: |
| return 32 << dmacHw_GET_CHANNEL_DATA_WIDTH(pCblk->module, |
| pCblk->channel); |
| case dmacHw_CONTROLLER_ATTRIB_CHANNEL_FIFO_SIZE: |
| return GetFifoSize(handle); |
| } |
| dmacHw_ASSERT(0); |
| return 0; |
| } |