| /* |
| * Copyright (c) 2015, Freescale Semiconductor, Inc. |
| * Copyright 2016-2017 NXP |
| * |
| * Redistribution and use in source and binary forms, with or without modification, |
| * are permitted provided that the following conditions are met: |
| * |
| * o Redistributions of source code must retain the above copyright notice, this list |
| * of conditions and the following disclaimer. |
| * |
| * o 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. |
| * |
| * o Neither the name of the copyright holder nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. |
| */ |
| |
| #include "fsl_lpi2c_edma.h" |
| #include <stdlib.h> |
| #include <string.h> |
| |
| /******************************************************************************* |
| * Definitions |
| ******************************************************************************/ |
| |
| /* @brief Mask to align an address to 32 bytes. */ |
| #define ALIGN_32_MASK (0x1fU) |
| |
| /*! @brief Common sets of flags used by the driver. */ |
| enum _lpi2c_flag_constants |
| { |
| /*! All flags which are cleared by the driver upon starting a transfer. */ |
| kMasterClearFlags = kLPI2C_MasterEndOfPacketFlag | kLPI2C_MasterStopDetectFlag | kLPI2C_MasterNackDetectFlag | |
| kLPI2C_MasterArbitrationLostFlag | kLPI2C_MasterFifoErrFlag | kLPI2C_MasterPinLowTimeoutFlag | |
| kLPI2C_MasterDataMatchFlag, |
| |
| /*! IRQ sources enabled by the non-blocking transactional API. */ |
| kMasterIrqFlags = kLPI2C_MasterArbitrationLostFlag | kLPI2C_MasterTxReadyFlag | kLPI2C_MasterRxReadyFlag | |
| kLPI2C_MasterStopDetectFlag | kLPI2C_MasterNackDetectFlag | kLPI2C_MasterPinLowTimeoutFlag | |
| kLPI2C_MasterFifoErrFlag, |
| |
| /*! Errors to check for. */ |
| kMasterErrorFlags = kLPI2C_MasterNackDetectFlag | kLPI2C_MasterArbitrationLostFlag | kLPI2C_MasterFifoErrFlag | |
| kLPI2C_MasterPinLowTimeoutFlag, |
| |
| /*! All flags which are cleared by the driver upon starting a transfer. */ |
| kSlaveClearFlags = kLPI2C_SlaveRepeatedStartDetectFlag | kLPI2C_SlaveStopDetectFlag | kLPI2C_SlaveBitErrFlag | |
| kLPI2C_SlaveFifoErrFlag, |
| |
| /*! IRQ sources enabled by the non-blocking transactional API. */ |
| kSlaveIrqFlags = kLPI2C_SlaveTxReadyFlag | kLPI2C_SlaveRxReadyFlag | kLPI2C_SlaveStopDetectFlag | |
| kLPI2C_SlaveRepeatedStartDetectFlag | kLPI2C_SlaveFifoErrFlag | kLPI2C_SlaveBitErrFlag | |
| kLPI2C_SlaveTransmitAckFlag | kLPI2C_SlaveAddressValidFlag, |
| |
| /*! Errors to check for. */ |
| kSlaveErrorFlags = kLPI2C_SlaveFifoErrFlag | kLPI2C_SlaveBitErrFlag, |
| }; |
| |
| /* ! @brief LPI2C master fifo commands. */ |
| enum _lpi2c_master_fifo_cmd |
| { |
| kTxDataCmd = LPI2C_MTDR_CMD(0x0U), /*!< Transmit DATA[7:0] */ |
| kRxDataCmd = LPI2C_MTDR_CMD(0X1U), /*!< Receive (DATA[7:0] + 1) bytes */ |
| kStopCmd = LPI2C_MTDR_CMD(0x2U), /*!< Generate STOP condition */ |
| kStartCmd = LPI2C_MTDR_CMD(0x4U), /*!< Generate(repeated) START and transmit address in DATA[[7:0] */ |
| }; |
| |
| /*! @brief States for the state machine used by transactional APIs. */ |
| enum _lpi2c_transfer_states |
| { |
| kIdleState = 0, |
| kSendCommandState, |
| kIssueReadCommandState, |
| kTransferDataState, |
| kStopState, |
| kWaitForCompletionState, |
| }; |
| |
| /*! @brief Typedef for interrupt handler. */ |
| typedef void (*lpi2c_isr_t)(LPI2C_Type *base, void *handle); |
| |
| /******************************************************************************* |
| * Prototypes |
| ******************************************************************************/ |
| |
| /* Defined in fsl_lpi2c.c. */ |
| extern status_t LPI2C_CheckForBusyBus(LPI2C_Type *base); |
| |
| /* Defined in fsl_lpi2c.c. */ |
| extern status_t LPI2C_MasterCheckAndClearError(LPI2C_Type *base, uint32_t status); |
| |
| static uint32_t LPI2C_GenerateCommands(lpi2c_master_edma_handle_t *handle); |
| |
| static void LPI2C_MasterEDMACallback(edma_handle_t *dmaHandle, void *userData, bool isTransferDone, uint32_t tcds); |
| |
| /******************************************************************************* |
| * Code |
| ******************************************************************************/ |
| |
| void LPI2C_MasterCreateEDMAHandle(LPI2C_Type *base, |
| lpi2c_master_edma_handle_t *handle, |
| edma_handle_t *rxDmaHandle, |
| edma_handle_t *txDmaHandle, |
| lpi2c_master_edma_transfer_callback_t callback, |
| void *userData) |
| { |
| assert(handle); |
| assert(rxDmaHandle); |
| assert(txDmaHandle); |
| |
| /* Clear out the handle. */ |
| memset(handle, 0, sizeof(*handle)); |
| |
| /* Set up the handle. For combined rx/tx DMA requests, the tx channel handle is set to the rx handle */ |
| /* in order to make the transfer API code simpler. */ |
| handle->base = base; |
| handle->completionCallback = callback; |
| handle->userData = userData; |
| handle->rx = rxDmaHandle; |
| handle->tx = FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base) ? txDmaHandle : rxDmaHandle; |
| |
| /* Set DMA channel completion callbacks. */ |
| EDMA_SetCallback(handle->rx, LPI2C_MasterEDMACallback, handle); |
| if (FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| EDMA_SetCallback(handle->tx, LPI2C_MasterEDMACallback, handle); |
| } |
| } |
| |
| /*! |
| * @brief Prepares the command buffer with the sequence of commands needed to send the requested transaction. |
| * @param handle Master DMA driver handle. |
| * @return Number of command words. |
| */ |
| static uint32_t LPI2C_GenerateCommands(lpi2c_master_edma_handle_t *handle) |
| { |
| lpi2c_master_transfer_t *xfer = &handle->transfer; |
| uint16_t *cmd = (uint16_t *)&handle->commandBuffer; |
| uint32_t cmdCount = 0; |
| |
| /* Handle no start option. */ |
| if (xfer->flags & kLPI2C_TransferNoStartFlag) |
| { |
| if (xfer->direction == kLPI2C_Read) |
| { |
| /* Need to issue read command first. */ |
| cmd[cmdCount++] = kRxDataCmd | LPI2C_MTDR_DATA(xfer->dataSize - 1); |
| } |
| } |
| else |
| { |
| /* |
| * Initial direction depends on whether a subaddress was provided, and of course the actual |
| * data transfer direction. |
| */ |
| lpi2c_direction_t direction = xfer->subaddressSize ? kLPI2C_Write : xfer->direction; |
| |
| /* Start command. */ |
| cmd[cmdCount++] = |
| (uint16_t)kStartCmd | (uint16_t)((uint16_t)((uint16_t)xfer->slaveAddress << 1U) | (uint16_t)direction); |
| |
| /* Subaddress, MSB first. */ |
| if (xfer->subaddressSize) |
| { |
| uint32_t subaddressRemaining = xfer->subaddressSize; |
| while (subaddressRemaining--) |
| { |
| uint8_t subaddressByte = (xfer->subaddress >> (8 * subaddressRemaining)) & 0xff; |
| cmd[cmdCount++] = subaddressByte; |
| } |
| } |
| |
| /* Reads need special handling because we have to issue a read command and maybe a repeated start. */ |
| if ((xfer->dataSize) && (xfer->direction == kLPI2C_Read)) |
| { |
| /* Need to send repeated start if switching directions to read. */ |
| if (direction == kLPI2C_Write) |
| { |
| cmd[cmdCount++] = (uint16_t)kStartCmd | |
| (uint16_t)((uint16_t)((uint16_t)xfer->slaveAddress << 1U) | (uint16_t)kLPI2C_Read); |
| } |
| |
| /* Read command. */ |
| cmd[cmdCount++] = kRxDataCmd | LPI2C_MTDR_DATA(xfer->dataSize - 1); |
| } |
| } |
| |
| return cmdCount; |
| } |
| |
| status_t LPI2C_MasterTransferEDMA(LPI2C_Type *base, |
| lpi2c_master_edma_handle_t *handle, |
| lpi2c_master_transfer_t *transfer) |
| { |
| status_t result; |
| |
| assert(handle); |
| assert(transfer); |
| assert(transfer->subaddressSize <= sizeof(transfer->subaddress)); |
| |
| /* Return busy if another transaction is in progress. */ |
| if (handle->isBusy) |
| { |
| return kStatus_LPI2C_Busy; |
| } |
| |
| /* Return an error if the bus is already in use not by us. */ |
| result = LPI2C_CheckForBusyBus(base); |
| if (result) |
| { |
| return result; |
| } |
| |
| /* We're now busy. */ |
| handle->isBusy = true; |
| |
| /* Disable LPI2C IRQ and DMA sources while we configure stuff. */ |
| LPI2C_MasterDisableInterrupts(base, kMasterIrqFlags); |
| LPI2C_MasterEnableDMA(base, false, false); |
| |
| /* Clear all flags. */ |
| LPI2C_MasterClearStatusFlags(base, kMasterClearFlags); |
| |
| /* Save transfer into handle. */ |
| handle->transfer = *transfer; |
| |
| /* Generate commands to send. */ |
| uint32_t commandCount = LPI2C_GenerateCommands(handle); |
| |
| /* If the user is transmitting no data with no start or stop, then just go ahead and invoke the callback. */ |
| if ((!commandCount) && (transfer->dataSize == 0)) |
| { |
| if (handle->completionCallback) |
| { |
| handle->completionCallback(base, handle, kStatus_Success, handle->userData); |
| } |
| return kStatus_Success; |
| } |
| |
| /* Reset DMA channels. */ |
| EDMA_ResetChannel(handle->rx->base, handle->rx->channel); |
| if (FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| EDMA_ResetChannel(handle->tx->base, handle->tx->channel); |
| } |
| |
| /* Get a 32-byte aligned TCD pointer. */ |
| edma_tcd_t *tcd = (edma_tcd_t *)((uint32_t)(&handle->tcds[1]) & (~ALIGN_32_MASK)); |
| |
| bool hasSendData = (transfer->direction == kLPI2C_Write) && (transfer->dataSize); |
| bool hasReceiveData = (transfer->direction == kLPI2C_Read) && (transfer->dataSize); |
| |
| edma_transfer_config_t transferConfig; |
| edma_tcd_t *linkTcd = NULL; |
| |
| /* Set up data transmit. */ |
| if (hasSendData) |
| { |
| transferConfig.srcAddr = (uint32_t)transfer->data; |
| transferConfig.destAddr = (uint32_t)LPI2C_MasterGetTxFifoAddress(base); |
| transferConfig.srcTransferSize = kEDMA_TransferSize1Bytes; |
| transferConfig.destTransferSize = kEDMA_TransferSize1Bytes; |
| transferConfig.srcOffset = sizeof(uint8_t); |
| transferConfig.destOffset = 0; |
| transferConfig.minorLoopBytes = sizeof(uint8_t); /* TODO optimize to fill fifo */ |
| transferConfig.majorLoopCounts = transfer->dataSize; |
| |
| /* Store the initially configured eDMA minor byte transfer count into the LPI2C handle */ |
| handle->nbytes = transferConfig.minorLoopBytes; |
| |
| if (commandCount) |
| { |
| /* Create a software TCD, which will be chained after the commands. */ |
| EDMA_TcdReset(tcd); |
| EDMA_TcdSetTransferConfig(tcd, &transferConfig, NULL); |
| EDMA_TcdEnableInterrupts(tcd, kEDMA_MajorInterruptEnable); |
| linkTcd = tcd; |
| } |
| else |
| { |
| /* User is only transmitting data with no required commands, so this transfer can stand alone. */ |
| EDMA_SetTransferConfig(handle->tx->base, handle->tx->channel, &transferConfig, NULL); |
| EDMA_EnableChannelInterrupts(handle->tx->base, handle->tx->channel, kEDMA_MajorInterruptEnable); |
| } |
| } |
| else if (hasReceiveData) |
| { |
| /* Set up data receive. */ |
| transferConfig.srcAddr = (uint32_t)LPI2C_MasterGetRxFifoAddress(base); |
| transferConfig.destAddr = (uint32_t)transfer->data; |
| transferConfig.srcTransferSize = kEDMA_TransferSize1Bytes; |
| transferConfig.destTransferSize = kEDMA_TransferSize1Bytes; |
| transferConfig.srcOffset = 0; |
| transferConfig.destOffset = sizeof(uint8_t); |
| transferConfig.minorLoopBytes = sizeof(uint8_t); /* TODO optimize to empty fifo */ |
| transferConfig.majorLoopCounts = transfer->dataSize; |
| |
| /* Store the initially configured eDMA minor byte transfer count into the LPI2C handle */ |
| handle->nbytes = transferConfig.minorLoopBytes; |
| |
| if (FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base) || (!commandCount)) |
| { |
| /* We can put this receive transfer on its own DMA channel. */ |
| EDMA_SetTransferConfig(handle->rx->base, handle->rx->channel, &transferConfig, NULL); |
| EDMA_EnableChannelInterrupts(handle->rx->base, handle->rx->channel, kEDMA_MajorInterruptEnable); |
| } |
| else |
| { |
| /* For shared rx/tx DMA requests when there are commands, create a software TCD which will be */ |
| /* chained onto the commands transfer, notice that in this situation assume tx/rx uses same channel */ |
| EDMA_TcdReset(tcd); |
| EDMA_TcdSetTransferConfig(tcd, &transferConfig, NULL); |
| EDMA_TcdEnableInterrupts(tcd, kEDMA_MajorInterruptEnable); |
| linkTcd = tcd; |
| } |
| } |
| else |
| { |
| /* No data to send */ |
| } |
| |
| /* Set up commands transfer. */ |
| if (commandCount) |
| { |
| transferConfig.srcAddr = (uint32_t)handle->commandBuffer; |
| transferConfig.destAddr = (uint32_t)LPI2C_MasterGetTxFifoAddress(base); |
| transferConfig.srcTransferSize = kEDMA_TransferSize2Bytes; |
| transferConfig.destTransferSize = kEDMA_TransferSize2Bytes; |
| transferConfig.srcOffset = sizeof(uint16_t); |
| transferConfig.destOffset = 0; |
| transferConfig.minorLoopBytes = sizeof(uint16_t); /* TODO optimize to fill fifo */ |
| transferConfig.majorLoopCounts = commandCount; |
| |
| EDMA_SetTransferConfig(handle->tx->base, handle->tx->channel, &transferConfig, linkTcd); |
| } |
| |
| /* Start DMA transfer. */ |
| if (hasReceiveData || !FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| EDMA_StartTransfer(handle->rx); |
| } |
| |
| if (hasReceiveData && !FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| EDMA_EnableChannelInterrupts(handle->tx->base, handle->tx->channel, kEDMA_MajorInterruptEnable); |
| } |
| |
| if ((hasSendData || commandCount) && FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| EDMA_StartTransfer(handle->tx); |
| } |
| |
| /* Enable DMA in both directions. This actually kicks of the transfer. */ |
| LPI2C_MasterEnableDMA(base, true, true); |
| |
| return result; |
| } |
| |
| status_t LPI2C_MasterTransferGetCountEDMA(LPI2C_Type *base, lpi2c_master_edma_handle_t *handle, size_t *count) |
| { |
| assert(handle); |
| |
| if (!count) |
| { |
| return kStatus_InvalidArgument; |
| } |
| |
| /* Catch when there is not an active transfer. */ |
| if (!handle->isBusy) |
| { |
| *count = 0; |
| return kStatus_NoTransferInProgress; |
| } |
| |
| uint32_t remaining = handle->transfer.dataSize; |
| |
| /* If the DMA is still on a commands transfer that chains to the actual data transfer, */ |
| /* we do nothing and return the number of transferred bytes as zero. */ |
| if (EDMA_GetNextTCDAddress(handle->tx) == 0) |
| { |
| if (handle->transfer.direction == kLPI2C_Write) |
| { |
| remaining = |
| (uint32_t)handle->nbytes * EDMA_GetRemainingMajorLoopCount(handle->tx->base, handle->tx->channel); |
| } |
| else |
| { |
| remaining = |
| (uint32_t)handle->nbytes * EDMA_GetRemainingMajorLoopCount(handle->rx->base, handle->rx->channel); |
| } |
| } |
| |
| *count = handle->transfer.dataSize - remaining; |
| |
| return kStatus_Success; |
| } |
| |
| status_t LPI2C_MasterTransferAbortEDMA(LPI2C_Type *base, lpi2c_master_edma_handle_t *handle) |
| { |
| /* Catch when there is not an active transfer. */ |
| if (!handle->isBusy) |
| { |
| return kStatus_LPI2C_Idle; |
| } |
| |
| /* Terminate DMA transfers. */ |
| EDMA_AbortTransfer(handle->rx); |
| if (FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| EDMA_AbortTransfer(handle->tx); |
| } |
| |
| /* Reset fifos. */ |
| base->MCR |= LPI2C_MCR_RRF_MASK | LPI2C_MCR_RTF_MASK; |
| |
| /* Send a stop command to finalize the transfer. */ |
| base->MTDR = kStopCmd; |
| |
| /* Reset handle. */ |
| handle->isBusy = false; |
| |
| return kStatus_Success; |
| } |
| |
| /*! |
| * @brief DMA completion callback. |
| * @param dmaHandle DMA channel handle for the channel that completed. |
| * @param userData User data associated with the channel handle. For this callback, the user data is the |
| * LPI2C DMA driver handle. |
| * @param isTransferDone Whether the DMA transfer has completed. |
| * @param tcds Number of TCDs that completed. |
| */ |
| static void LPI2C_MasterEDMACallback(edma_handle_t *dmaHandle, void *userData, bool isTransferDone, uint32_t tcds) |
| { |
| lpi2c_master_edma_handle_t *handle = (lpi2c_master_edma_handle_t *)userData; |
| bool hasReceiveData = (handle->transfer.direction == kLPI2C_Read) && (handle->transfer.dataSize); |
| if (hasReceiveData && !FSL_FEATURE_LPI2C_HAS_SEPARATE_DMA_RX_TX_REQn(base)) |
| { |
| if (EDMA_GetNextTCDAddress(handle->tx) != 0) |
| { |
| LPI2C_MasterEnableDMA(handle->base, false, true); |
| } |
| } |
| |
| if (!handle) |
| { |
| return; |
| } |
| |
| /* Check for errors. */ |
| status_t result = LPI2C_MasterCheckAndClearError(handle->base, LPI2C_MasterGetStatusFlags(handle->base)); |
| |
| /* Done with this transaction. */ |
| handle->isBusy = false; |
| |
| if (!(handle->transfer.flags & kLPI2C_TransferNoStopFlag)) |
| { |
| /* Send a stop command to finalize the transfer. */ |
| handle->base->MTDR = kStopCmd; |
| } |
| |
| /* Invoke callback. */ |
| if (handle->completionCallback) |
| { |
| handle->completionCallback(handle->base, handle, result, handle->userData); |
| } |
| } |