MCUXpresso_MCXA153/devices/MCXA153/drivers/fsl_i3c_edma.c
Yilin Sun 35e678394a
Initial commit v2.14.2
Signed-off-by: Yilin Sun <imi415@imi.moe>
2024-02-11 19:33:06 +08:00

973 lines
34 KiB
C

/*
* Copyright 2022 NXP
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "fsl_i3c_edma.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.i3c_edma"
#endif
/*! @brief States for the state machine used by transactional APIs. */
enum _i3c_edma_transfer_states
{
kIdleState = 0,
kIBIWonState,
kSlaveStartState,
kSendCommandState,
kWaitRepeatedStartCompleteState,
kTransmitDataState,
kReceiveDataState,
kStopState,
kWaitForCompletionState,
kAddressMatchState,
};
/*! @brief Common sets of flags used by the driver. */
enum _i3c_edma_flag_constants
{
/*! All flags which are cleared by the driver upon starting a transfer. */
kMasterClearFlags = kI3C_MasterSlaveStartFlag | kI3C_MasterControlDoneFlag | kI3C_MasterCompleteFlag |
kI3C_MasterArbitrationWonFlag | kI3C_MasterSlave2MasterFlag | kI3C_MasterErrorFlag,
/*! IRQ sources enabled by the non-blocking transactional API. */
kMasterDMAIrqFlags = kI3C_MasterSlaveStartFlag | kI3C_MasterControlDoneFlag | kI3C_MasterCompleteFlag |
kI3C_MasterArbitrationWonFlag | kI3C_MasterErrorFlag | kI3C_MasterSlave2MasterFlag,
/*! Errors to check for. */
kMasterErrorFlags = kI3C_MasterErrorNackFlag | kI3C_MasterErrorWriteAbortFlag |
#if !defined(FSL_FEATURE_I3C_HAS_NO_MERRWARN_TERM) || (!FSL_FEATURE_I3C_HAS_NO_MERRWARN_TERM)
kI3C_MasterErrorTermFlag |
#endif
kI3C_MasterErrorParityFlag | kI3C_MasterErrorCrcFlag | kI3C_MasterErrorReadFlag |
kI3C_MasterErrorWriteFlag | kI3C_MasterErrorMsgFlag | kI3C_MasterErrorInvalidReqFlag |
kI3C_MasterErrorTimeoutFlag,
/*! All flags which are cleared by the driver upon starting a transfer. */
kSlaveClearFlags = kI3C_SlaveBusStartFlag | kI3C_SlaveMatchedFlag | kI3C_SlaveBusStopFlag,
/*! IRQ sources enabled by the non-blocking transactional API. */
kSlaveDMAIrqFlags = kI3C_SlaveBusStartFlag | kI3C_SlaveMatchedFlag |
kI3C_SlaveBusStopFlag | /*kI3C_SlaveRxReadyFlag |*/
kI3C_SlaveDynamicAddrChangedFlag | kI3C_SlaveReceivedCCCFlag | kI3C_SlaveErrorFlag |
kI3C_SlaveHDRCommandMatchFlag | kI3C_SlaveCCCHandledFlag | kI3C_SlaveEventSentFlag,
/*! Errors to check for. */
kSlaveErrorFlags = kI3C_SlaveErrorOverrunFlag | kI3C_SlaveErrorUnderrunFlag | kI3C_SlaveErrorUnderrunNakFlag |
kI3C_SlaveErrorTermFlag | kI3C_SlaveErrorInvalidStartFlag | kI3C_SlaveErrorSdrParityFlag |
kI3C_SlaveErrorHdrParityFlag | kI3C_SlaveErrorHdrCRCFlag | kI3C_SlaveErrorS0S1Flag |
kI3C_SlaveErrorOverreadFlag | kI3C_SlaveErrorOverwriteFlag,
};
/*******************************************************************************
* Variables
******************************************************************************/
/*******************************************************************************
* Prototypes
******************************************************************************/
static void I3C_MasterRunEDMATransfer(
I3C_Type *base, i3c_master_edma_handle_t *handle, void *data, size_t dataSize, i3c_direction_t direction);
/*******************************************************************************
* Code
******************************************************************************/
static void I3C_MasterTransferEDMACallbackRx(edma_handle_t *dmaHandle, void *param, bool transferDone, uint32_t tcds)
{
i3c_master_edma_handle_t *i3cHandle = (i3c_master_edma_handle_t *)param;
if (transferDone)
{
/* Read last data byte */
i3cHandle->base->MCTRL |= I3C_MCTRL_RDTERM(1U);
size_t rxCount = 0U;
while (rxCount == 0U)
{
I3C_MasterGetFifoCounts(i3cHandle->base, &rxCount, NULL);
};
*(uint8_t *)((uint32_t)((uint32_t *)i3cHandle->transfer.data) + i3cHandle->transfer.dataSize - 1U) =
(uint8_t)i3cHandle->base->MRDATAB;
/* Disable I3C Rx DMA */
i3cHandle->base->MDATACTRL &= ~I3C_MDMACTRL_DMAFB_MASK;
i3cHandle->state = (uint8_t)kStopState;
I3C_MasterTransferEDMAHandleIRQ(i3cHandle->base, i3cHandle);
}
}
static void I3C_MasterTransferEDMACallbackTx(edma_handle_t *dmaHandle, void *param, bool transferDone, uint32_t tcds)
{
i3c_master_edma_handle_t *i3cHandle = (i3c_master_edma_handle_t *)param;
if (transferDone)
{
/* Disable I3C Tx DMA */
i3cHandle->base->MDATACTRL &= ~I3C_MDMACTRL_DMATB_MASK;
i3cHandle->state = (uint8_t)kStopState;
size_t txCount = 0U;
do
{
I3C_MasterGetFifoCounts(i3cHandle->base, NULL, &txCount);
} while (txCount != 0U);
I3C_MasterTransferEDMAHandleIRQ(i3cHandle->base, i3cHandle);
}
}
/*!
* brief Prepares the transfer state machine and fills in the command buffer.
* param handle Master nonblocking driver handle.
*/
static status_t I3C_MasterInitTransferStateMachineEDMA(I3C_Type *base, i3c_master_edma_handle_t *handle)
{
i3c_master_transfer_t *xfer = &handle->transfer;
status_t result = kStatus_Success;
i3c_direction_t direction = xfer->direction;
/* Calculate command count and put into command buffer. */
handle->subaddressCount = 0U;
if (xfer->subaddressSize != 0U)
{
for (uint32_t i = xfer->subaddressSize; i > 0U; i--)
{
handle->subaddressBuffer[handle->subaddressCount++] = (uint8_t)((xfer->subaddress) >> (8U * (i - 1U)));
}
}
/* Start condition shall be ommited, switch directly to next phase */
if (xfer->dataSize == 0U)
{
handle->state = (uint8_t)kStopState;
}
/* Handle no start option. */
if (0U != (xfer->flags & (uint32_t)kI3C_TransferNoStartFlag))
{
/* No need to send start flag, directly go to send command or data */
if (xfer->subaddressSize > 0UL)
{
handle->state = (uint8_t)kSendCommandState;
}
else
{
if (direction == kI3C_Write)
{
/* Next state, send data. */
handle->state = (uint8_t)kTransmitDataState;
}
else
{
/* Only support write with no stop signal. */
return kStatus_InvalidArgument;
}
}
}
else
{
if (xfer->subaddressSize != 0U)
{
handle->state = (uint8_t)kSendCommandState;
}
else
{
if (handle->transfer.direction == kI3C_Write)
{
handle->state = (uint8_t)kTransmitDataState;
}
else if (handle->transfer.direction == kI3C_Read)
{
handle->state = (uint8_t)kReceiveDataState;
}
else
{
return kStatus_InvalidArgument;
}
}
if (handle->transfer.direction == kI3C_Read)
{
I3C_MasterRunEDMATransfer(base, handle, xfer->data, xfer->dataSize - 1U, kI3C_Read);
}
if (handle->state != (uint8_t)kStopState)
{
/* If repeated start is requested, send repeated start. */
if (0U != (xfer->flags & (uint32_t)kI3C_TransferRepeatedStartFlag))
{
result = I3C_MasterRepeatedStart(base, xfer->busType, xfer->slaveAddress, direction);
}
else /* For normal transfer, send start. */
{
result = I3C_MasterStart(base, xfer->busType, xfer->slaveAddress, direction);
}
}
}
I3C_MasterTransferEDMAHandleIRQ(base, handle);
return result;
}
static void I3C_MasterRunEDMATransfer(
I3C_Type *base, i3c_master_edma_handle_t *handle, void *data, size_t dataSize, i3c_direction_t direction)
{
edma_transfer_config_t xferConfig;
uint32_t address;
bool isEnableTxDMA = false;
bool isEnableRxDMA = false;
uint32_t width;
handle->transferCount = dataSize;
switch (direction)
{
case kI3C_Write:
address = (uint32_t)&base->MWDATAB1;
EDMA_PrepareTransfer(&xferConfig, data, sizeof(uint8_t), (uint32_t *)address, sizeof(uint8_t), 1, dataSize,
kEDMA_MemoryToPeripheral);
(void)EDMA_SubmitTransfer(handle->txDmaHandle, &xferConfig);
EDMA_StartTransfer(handle->txDmaHandle);
isEnableTxDMA = true;
width = 1U;
break;
case kI3C_Read:
address = (uint32_t)&base->MRDATAB;
EDMA_PrepareTransfer(&xferConfig, (uint32_t *)address, sizeof(uint8_t), data, sizeof(uint8_t), 1, dataSize,
kEDMA_PeripheralToMemory);
(void)EDMA_SubmitTransfer(handle->rxDmaHandle, &xferConfig);
EDMA_StartTransfer(handle->rxDmaHandle);
isEnableRxDMA = true;
width = 1U;
break;
default:
/* This should never happen */
assert(0);
break;
}
I3C_MasterEnableDMA(base, isEnableTxDMA, isEnableRxDMA, width);
}
static status_t I3C_MasterRunTransferStateMachineEDMA(I3C_Type *base, i3c_master_edma_handle_t *handle, bool *isDone)
{
uint32_t status;
uint32_t errStatus;
status_t result = kStatus_Success;
i3c_master_transfer_t *xfer;
size_t rxCount = 0;
bool state_complete = false;
/* Set default isDone return value. */
*isDone = false;
/* Check for errors. */
status = (uint32_t)I3C_MasterGetPendingInterrupts(base);
I3C_MasterClearStatusFlags(base, status);
i3c_master_state_t masterState = I3C_MasterGetState(base);
errStatus = I3C_MasterGetErrorStatusFlags(base);
result = I3C_MasterCheckAndClearError(base, errStatus);
if (kStatus_Success != result)
{
return result;
}
if (0UL != (status & (uint32_t)kI3C_MasterSlave2MasterFlag))
{
if (handle->callback.slave2Master != NULL)
{
handle->callback.slave2Master(base, handle->userData);
}
}
if ((0UL != (status & (uint32_t)kI3C_MasterSlaveStartFlag)) && (handle->transfer.busType != kI3C_TypeI2C))
{
handle->state = (uint8_t)kSlaveStartState;
}
if ((masterState == kI3C_MasterStateIbiRcv) || (masterState == kI3C_MasterStateIbiAck))
{
handle->state = (uint8_t)kIBIWonState;
}
if (handle->state == (uint8_t)kIdleState)
{
return result;
}
if (handle->state == (uint8_t)kIBIWonState)
{
/* Get fifo counts and compute room in tx fifo. */
rxCount = (base->MDATACTRL & I3C_MDATACTRL_RXCOUNT_MASK) >> I3C_MDATACTRL_RXCOUNT_SHIFT;
}
/* Get pointer to private data. */
xfer = &handle->transfer;
while (!state_complete)
{
/* Execute the state. */
switch (handle->state)
{
case (uint8_t)kSlaveStartState:
/* Emit start + 0x7E */
I3C_MasterEmitRequest(base, kI3C_RequestAutoIbi);
handle->state = (uint8_t)kIBIWonState;
state_complete = true;
break;
case (uint8_t)kIBIWonState:
if (masterState == kI3C_MasterStateIbiAck)
{
handle->ibiType = I3C_GetIBIType(base);
if (handle->callback.ibiCallback != NULL)
{
handle->callback.ibiCallback(base, handle, handle->ibiType, kI3C_IbiAckNackPending);
}
else
{
I3C_MasterEmitIBIResponse(base, kI3C_IbiRespNack);
}
}
/* Make sure there is data in the rx fifo. */
if (0UL != rxCount)
{
if ((handle->ibiBuff == NULL) && (handle->callback.ibiCallback != NULL))
{
handle->callback.ibiCallback(base, handle, kI3C_IbiNormal, kI3C_IbiDataBuffNeed);
}
uint8_t tempData = (uint8_t)base->MRDATAB;
if (handle->ibiBuff != NULL)
{
handle->ibiBuff[handle->ibiPayloadSize++] = tempData;
}
rxCount--;
break;
}
else if (0UL != (status & (uint32_t)kI3C_MasterCompleteFlag))
{
handle->ibiType = I3C_GetIBIType(base);
handle->ibiAddress = I3C_GetIBIAddress(base);
state_complete = true;
result = kStatus_I3C_IBIWon;
}
else
{
state_complete = true;
}
break;
case (uint8_t)kSendCommandState:
/* Calculate command count and put into command buffer. */
if (xfer->dataSize == 0U)
{
*isDone = true;
}
I3C_MasterRunEDMATransfer(base, handle, handle->subaddressBuffer, handle->subaddressCount, kI3C_Write);
if ((xfer->direction == kI3C_Read) || (0UL == xfer->dataSize))
{
if (0UL == xfer->dataSize)
{
handle->state = (uint8_t)kWaitForCompletionState;
}
else
{
/* xfer->dataSize != 0U, xfer->direction = kI3C_Read */
handle->state = (uint8_t)kWaitRepeatedStartCompleteState;
}
}
else
{
/* Next state, transfer data. */
handle->state = (uint8_t)kTransmitDataState;
}
state_complete = true;
break;
case (uint8_t)kWaitRepeatedStartCompleteState:
/* We stay in this state until the maste complete. */
if (0UL != (status & (uint32_t)kI3C_MasterCompleteFlag))
{
handle->state = (uint8_t)kReceiveDataState;
/* Send repeated start and slave address. */
result = I3C_MasterRepeatedStart(base, xfer->busType, xfer->slaveAddress, kI3C_Read);
}
state_complete = true;
break;
case (uint8_t)kTransmitDataState:
I3C_MasterRunEDMATransfer(base, handle, xfer->data, xfer->dataSize, kI3C_Write);
handle->state = (uint8_t)kWaitForCompletionState;
state_complete = true;
break;
case (uint8_t)kReceiveDataState:
/* Do DMA read. */
handle->state = (uint8_t)kWaitForCompletionState;
state_complete = true;
break;
case (uint8_t)kWaitForCompletionState:
/* We stay in this state until the maste complete. */
if (0UL != (status & (uint32_t)kI3C_MasterCompleteFlag))
{
handle->state = (uint8_t)kStopState;
}
else
{
state_complete = true;
}
break;
case (uint8_t)kStopState:
/* Only issue a stop transition if the caller requested it. */
if (0UL == (xfer->flags & (uint32_t)kI3C_TransferNoStopFlag))
{
if (xfer->busType == kI3C_TypeI3CDdr)
{
I3C_MasterEmitRequest(base, kI3C_RequestForceExit);
}
else
{
I3C_MasterEmitRequest(base, kI3C_RequestEmitStop);
}
}
*isDone = true;
state_complete = true;
break;
default:
assert(false);
break;
}
}
return result;
}
void I3C_MasterTransferCreateHandleEDMA(I3C_Type *base,
i3c_master_edma_handle_t *handle,
const i3c_master_edma_callback_t *callback,
void *userData,
edma_handle_t *rxDmaHandle,
edma_handle_t *txDmaHandle)
{
uint32_t instance;
assert(NULL != handle);
/* Clear out the handle. */
(void)memset(handle, 0, sizeof(*handle));
/* Look up instance number */
instance = I3C_GetInstance(base);
handle->base = base;
handle->txDmaHandle = txDmaHandle;
handle->rxDmaHandle = rxDmaHandle;
handle->callback = *callback;
handle->userData = userData;
/* Save this handle for IRQ use. */
s_i3cMasterHandle[instance] = handle;
/* Set irq handler. */
s_i3cMasterIsr = I3C_MasterTransferEDMAHandleIRQ;
EDMA_SetCallback(handle->rxDmaHandle, I3C_MasterTransferEDMACallbackRx, handle);
EDMA_SetCallback(handle->txDmaHandle, I3C_MasterTransferEDMACallbackTx, handle);
/* Clear all flags. */
I3C_MasterClearErrorStatusFlags(base, (uint32_t)kMasterErrorFlags);
I3C_MasterClearStatusFlags(base, (uint32_t)kMasterClearFlags);
/* Reset fifos. These flags clear automatically. */
base->MDATACTRL |= I3C_MDATACTRL_FLUSHTB_MASK | I3C_MDATACTRL_FLUSHFB_MASK;
/* Enable NVIC IRQ, this only enables the IRQ directly connected to the NVIC.
In some cases the I3C IRQ is configured through INTMUX, user needs to enable
INTMUX IRQ in application code. */
(void)EnableIRQ(kI3cIrqs[instance]);
/* Clear internal IRQ enables and enable NVIC IRQ. */
I3C_MasterEnableInterrupts(base, (uint32_t)kMasterDMAIrqFlags);
}
/*!
* brief Performs a non-blocking DMA transaction on the I2C/I3C bus.
*
* param base The I3C peripheral base address.
* param handle Pointer to the I3C master driver handle.
* param transfer The pointer to the transfer descriptor.
* retval #kStatus_Success The transaction was started successfully.
* retval #kStatus_I3C_Busy Either another master is currently utilizing the bus, or a non-blocking
* transaction is already in progress.
*/
status_t I3C_MasterTransferEDMA(I3C_Type *base, i3c_master_edma_handle_t *handle, i3c_master_transfer_t *transfer)
{
assert(NULL != handle);
assert(NULL != transfer);
assert(transfer->subaddressSize <= sizeof(transfer->subaddress));
i3c_master_state_t masterState = I3C_MasterGetState(base);
bool checkDdrState = false;
/* Return busy if another transaction is in progress. */
if (handle->state != (uint8_t)kIdleState)
{
return kStatus_I3C_Busy;
}
/* Return an error if the bus is already in use not by us. */
checkDdrState = (transfer->busType == kI3C_TypeI3CDdr) ? (masterState != kI3C_MasterStateDdr) : true;
if ((masterState != kI3C_MasterStateIdle) && (masterState != kI3C_MasterStateNormAct) && checkDdrState)
{
return kStatus_I3C_Busy;
}
/* Disable I3C IRQ sources while we configure stuff. */
I3C_MasterDisableInterrupts(
base, ((uint32_t)kMasterDMAIrqFlags | (uint32_t)kI3C_MasterRxReadyFlag | (uint32_t)kI3C_MasterTxReadyFlag));
/* Save transfer into handle. */
handle->transfer = *transfer;
/* Configure IBI response type. */
base->MCTRL &= ~I3C_MCTRL_IBIRESP_MASK;
base->MCTRL |= I3C_MCTRL_IBIRESP(transfer->ibiResponse);
/* Clear all flags. */
I3C_MasterClearErrorStatusFlags(base, (uint32_t)kMasterErrorFlags);
I3C_MasterClearStatusFlags(base, (uint32_t)kMasterClearFlags);
/* Reset fifos. These flags clear automatically. */
base->MDATACTRL |= I3C_MDATACTRL_FLUSHTB_MASK | I3C_MDATACTRL_FLUSHFB_MASK;
/* Generate commands to send. */
(void)I3C_MasterInitTransferStateMachineEDMA(base, handle);
/* Enable I3C internal IRQ sources. NVIC IRQ was enabled in CreateHandle() */
I3C_MasterEnableInterrupts(base, (uint32_t)(kMasterDMAIrqFlags));
if (transfer->busType == kI3C_TypeI2C)
{
I3C_MasterDisableInterrupts(base, (uint32_t)kI3C_MasterSlaveStartFlag);
}
return kStatus_Success;
}
void I3C_MasterTransferEDMAHandleIRQ(I3C_Type *base, void *i3cHandle)
{
i3c_master_edma_handle_t *handle = (i3c_master_edma_handle_t *)i3cHandle;
bool isDone;
status_t result;
/* Don't do anything if we don't have a valid handle. */
if (NULL == handle)
{
return;
}
result = I3C_MasterRunTransferStateMachineEDMA(base, handle, &isDone);
if (handle->state == (uint8_t)kIdleState)
{
return;
}
if (isDone || (result != kStatus_Success))
{
/* XXX need to handle data that may be in rx fifo below watermark level? */
/* XXX handle error, terminate xfer */
if ((result == kStatus_I3C_Nak) || (result == kStatus_I3C_IBIWon))
{
I3C_MasterEmitRequest(base, kI3C_RequestEmitStop);
}
/* Set handle to idle state. */
handle->state = (uint8_t)kIdleState;
/* Invoke IBI user callback. */
if ((result == kStatus_I3C_IBIWon) && (handle->callback.ibiCallback != NULL))
{
handle->callback.ibiCallback(base, handle, handle->ibiType, kI3C_IbiReady);
handle->ibiPayloadSize = 0;
}
/* Invoke callback. */
if (NULL != handle->callback.transferComplete)
{
handle->callback.transferComplete(base, handle, result, handle->userData);
}
}
}
/*!
* brief Get master transfer status during a dma non-blocking transfer
*
* param base I3C peripheral base address
* param handle pointer to i2c_master_edma_handle_t structure
* param count Number of bytes transferred so far by the non-blocking transaction.
*/
status_t I3C_MasterTransferGetCountEDMA(I3C_Type *base, i3c_master_edma_handle_t *handle, size_t *count)
{
assert(handle != NULL);
if (NULL == count)
{
return kStatus_InvalidArgument;
}
/* Catch when there is not an active transfer. */
if (handle->state == (uint8_t)kIdleState)
{
*count = 0;
return kStatus_NoTransferInProgress;
}
/* There is no necessity to disable interrupts as we read a single integer value */
i3c_direction_t dir = handle->transfer.direction;
if (dir == kI3C_Read)
{
*count = handle->transferCount -
1U * EDMA_GetRemainingMajorLoopCount(handle->rxDmaHandle->base, handle->rxDmaHandle->channel);
}
else
{
*count = handle->transferCount -
1U * EDMA_GetRemainingMajorLoopCount(handle->txDmaHandle->base, handle->txDmaHandle->channel);
}
return kStatus_Success;
}
/*!
* brief Abort a master edma non-blocking transfer in a early time
*
* param base I3C peripheral base address
* param handle pointer to i2c_master_edma_handle_t structure
*/
void I3C_MasterTransferAbortEDMA(I3C_Type *base, i3c_master_edma_handle_t *handle)
{
if (handle->state != (uint8_t)kIdleState)
{
EDMA_AbortTransfer(handle->txDmaHandle);
EDMA_AbortTransfer(handle->rxDmaHandle);
I3C_MasterEnableDMA(base, false, false, 0);
/* Reset fifos. These flags clear automatically. */
base->MDATACTRL |= I3C_MDATACTRL_FLUSHTB_MASK | I3C_MDATACTRL_FLUSHFB_MASK;
/* Send a stop command to finalize the transfer. */
(void)I3C_MasterStop(base);
/* Reset handle. */
handle->state = (uint8_t)kIdleState;
}
}
static void I3C_SlaveTransferEDMACallback(edma_handle_t *dmaHandle, void *param, bool transferDone, uint32_t tcds)
{
i3c_slave_edma_handle_t *i3cHandle = (i3c_slave_edma_handle_t *)param;
if (transferDone)
{
/* Simply disable dma enablement */
if (i3cHandle->txDmaHandle == dmaHandle)
{
i3cHandle->base->SDMACTRL &= ~I3C_SDMACTRL_DMATB_MASK;
if (i3cHandle->transfer.txDataSize > 1U)
{
/* Send the last byte. */
i3cHandle->base->SWDATABE = *(uint8_t *)((uintptr_t)i3cHandle->transfer.txData + i3cHandle->transfer.txDataSize - 1U);
}
}
else
{
i3cHandle->base->SDMACTRL &= ~I3C_SDMACTRL_DMAFB_MASK;
}
}
}
/*!
* brief Create a new handle for the I3C slave DMA APIs.
*
* The creation of a handle is for use with the DMA APIs. Once a handle
* is created, there is not a corresponding destroy handle. If the user wants to
* terminate a transfer, the I3C_SlaveTransferAbortDMA() API shall be called.
*
* For devices where the I3C send and receive DMA requests are OR'd together, the @a txDmaHandle
* parameter is ignored and may be set to NULL.
*
* param base The I3C peripheral base address.
* param handle Pointer to the I3C slave driver handle.
* param callback User provided pointer to the asynchronous callback function.
* param userData User provided pointer to the application callback data.
* param rxDmaHandle Handle for the DMA receive channel. Created by the user prior to calling this function.
* param txDmaHandle Handle for the DMA transmit channel. Created by the user prior to calling this function.
*/
void I3C_SlaveTransferCreateHandleEDMA(I3C_Type *base,
i3c_slave_edma_handle_t *handle,
i3c_slave_edma_callback_t callback,
void *userData,
edma_handle_t *rxDmaHandle,
edma_handle_t *txDmaHandle)
{
uint32_t instance;
assert(NULL != handle);
/* Clear out the handle. */
(void)memset(handle, 0, sizeof(*handle));
/* Look up instance number */
instance = I3C_GetInstance(base);
handle->base = base;
handle->txDmaHandle = txDmaHandle;
handle->rxDmaHandle = rxDmaHandle;
handle->callback = callback;
handle->userData = userData;
/* Save this handle for IRQ use. */
s_i3cSlaveHandle[instance] = handle;
/* Set irq handler. */
s_i3cSlaveIsr = I3C_SlaveTransferEDMAHandleIRQ;
EDMA_SetCallback(handle->rxDmaHandle, I3C_SlaveTransferEDMACallback, handle);
EDMA_SetCallback(handle->txDmaHandle, I3C_SlaveTransferEDMACallback, handle);
/* Clear internal IRQ enables and enable NVIC IRQ. */
I3C_SlaveDisableInterrupts(base, (uint32_t)kSlaveDMAIrqFlags);
/* Enable NVIC IRQ, this only enables the IRQ directly connected to the NVIC.
In some cases the I3C IRQ is configured through INTMUX, user needs to enable
INTMUX IRQ in application code. */
(void)EnableIRQ(kI3cIrqs[instance]);
/* Enable IRQ. */
I3C_SlaveEnableInterrupts(base, (uint32_t)kSlaveDMAIrqFlags);
}
static void I3C_SlavePrepareTxEDMA(I3C_Type *base, i3c_slave_edma_handle_t *handle)
{
edma_transfer_config_t txConfig;
uint32_t *txFifoBase;
i3c_slave_edma_transfer_t *xfer = &handle->transfer;
if (xfer->txDataSize == 1U)
{
txFifoBase = (uint32_t *)(uintptr_t)&base->SWDATABE;
EDMA_PrepareTransfer(&txConfig, xfer->txData, 1, (void *)txFifoBase, 1, 1, xfer->txDataSize,
kEDMA_MemoryToPeripheral);
}
else
{
txFifoBase = (uint32_t *)(uintptr_t)&base->SWDATAB;
EDMA_PrepareTransfer(&txConfig, xfer->txData, 1, (void *)txFifoBase, 1, 1, xfer->txDataSize - 1U,
kEDMA_MemoryToPeripheral);
}
(void)EDMA_SubmitTransfer(handle->txDmaHandle, &txConfig);
EDMA_StartTransfer(handle->txDmaHandle);
}
static void I3C_SlavePrepareRxEDMA(I3C_Type *base, i3c_slave_edma_handle_t *handle)
{
edma_transfer_config_t rxConfig;
uint32_t *rxFifoBase = (uint32_t *)(uintptr_t)&base->SRDATAB;
i3c_slave_edma_transfer_t *xfer = &handle->transfer;
EDMA_PrepareTransfer(&rxConfig, (void *)rxFifoBase, 1, xfer->rxData, 1, 1, xfer->rxDataSize,
kEDMA_PeripheralToMemory);
(void)EDMA_SubmitTransfer(handle->rxDmaHandle, &rxConfig);
EDMA_StartTransfer(handle->rxDmaHandle);
}
/*!
* brief Prepares for a non-blocking DMA-based transaction on the I3C bus.
*
* The API will do DMA configuration according to the input transfer descriptor, and the data will be transferred when
* there's bus master requesting transfer from/to this slave. So the timing of call to this API need be aligned
* with master application to ensure the transfer is executed as expected.
* Callback specified when the @a handle was created is invoked when the transaction has completed.
*
* param base The I3C peripheral base address.
* param handle Pointer to the I3C slave driver handle.
* param transfer The pointer to the transfer descriptor.
* param eventMask Bit mask formed by OR'ing together #i3c_slave_transfer_event_t enumerators to specify
* which events to send to the callback. The transmit and receive events is not allowed to be enabled.
* retval kStatus_Success The transaction was started successfully.
* retval #kStatus_I3C_Busy Either another master is currently utilizing the bus, or another DMA
* transaction is already in progress.
* retval #kStatus_Fail The transaction can't be set.
*/
status_t I3C_SlaveTransferEDMA(I3C_Type *base,
i3c_slave_edma_handle_t *handle,
i3c_slave_edma_transfer_t *transfer,
uint32_t eventMask)
{
assert(NULL != handle);
assert(NULL != transfer);
bool txDmaEn = false, rxDmaEn = false;
uint32_t width;
if (handle->isBusy)
{
return kStatus_I3C_Busy;
}
/* Clear all flags. */
I3C_SlaveClearErrorStatusFlags(base, (uint32_t)kSlaveErrorFlags);
I3C_SlaveClearStatusFlags(base, (uint32_t)kSlaveClearFlags);
/* Reset fifos. These flags clear automatically. */
base->SDATACTRL |= I3C_SDATACTRL_FLUSHTB_MASK | I3C_SDATACTRL_FLUSHFB_MASK;
handle->transfer = *transfer;
/* Set up event mask. */
handle->eventMask = eventMask;
if ((transfer->txData != NULL) && (transfer->txDataSize != 0U))
{
I3C_SlavePrepareTxEDMA(base, handle);
txDmaEn = true;
width = 1U;
}
if ((transfer->rxData != NULL) && (transfer->rxDataSize != 0U))
{
I3C_SlavePrepareRxEDMA(base, handle);
rxDmaEn = true;
width = 1U;
}
if (txDmaEn || rxDmaEn)
{
I3C_SlaveEnableDMA(base, txDmaEn, rxDmaEn, width);
return kStatus_Success;
}
else
{
return kStatus_Fail;
}
}
void I3C_SlaveTransferEDMAHandleIRQ(I3C_Type *base, void *i3cHandle)
{
uint32_t flags;
uint32_t errFlags;
i3c_slave_edma_transfer_t *xfer;
i3c_slave_edma_handle_t *handle = (i3c_slave_edma_handle_t *)i3cHandle;
/* Check for a valid handle in case of a spurious interrupt. */
if (NULL == handle)
{
return;
}
xfer = &handle->transfer;
/* Get status flags. */
flags = I3C_SlaveGetStatusFlags(base);
errFlags = I3C_SlaveGetErrorStatusFlags(base);
/* Clear status flags. */
I3C_SlaveClearStatusFlags(base, flags);
if (0UL != (errFlags & (uint32_t)kSlaveErrorFlags))
{
xfer->event = (uint32_t)kI3C_SlaveCompletionEvent;
xfer->completionStatus = I3C_SlaveCheckAndClearError(base, errFlags);
if ((0UL != (handle->eventMask & (uint32_t)kI3C_SlaveCompletionEvent)) && (NULL != handle->callback))
{
handle->callback(base, xfer, handle->userData);
}
return;
}
if (0UL != (flags & (uint32_t)kI3C_SlaveEventSentFlag))
{
xfer->event = (uint32_t)kI3C_SlaveRequestSentEvent;
if ((0UL != (handle->eventMask & xfer->event)) && (NULL != handle->callback))
{
handle->callback(base, xfer, handle->userData);
}
}
if (0UL != (flags & (uint32_t)kI3C_SlaveReceivedCCCFlag))
{
handle->isBusy = true;
xfer->event = (uint32_t)kI3C_SlaveReceivedCCCEvent;
if ((0UL != (handle->eventMask & xfer->event)) && (NULL != handle->callback))
{
handle->callback(base, xfer, handle->userData);
}
}
if (0UL != (flags & (uint32_t)kI3C_SlaveBusStopFlag))
{
if (handle->isBusy == true)
{
xfer->event = (uint32_t)kI3C_SlaveCompletionEvent;
xfer->completionStatus = kStatus_Success;
handle->isBusy = false;
if ((0UL != (handle->eventMask & xfer->event)) && (NULL != handle->callback))
{
handle->callback(base, xfer, handle->userData);
}
I3C_SlaveTransferAbortEDMA(base, handle);
}
else
{
return;
}
}
if (0UL != (flags & (uint32_t)kI3C_SlaveMatchedFlag))
{
xfer->event = (uint32_t)kI3C_SlaveAddressMatchEvent;
handle->isBusy = true;
if ((0UL != (handle->eventMask & (uint32_t)kI3C_SlaveAddressMatchEvent)) && (NULL != handle->callback))
{
handle->callback(base, xfer, handle->userData);
}
}
}
/*!
* brief Abort a slave dma non-blocking transfer in a early time
*
* param base I3C peripheral base address
* param handle pointer to i3c_slave_edma_handle_t structure
*/
void I3C_SlaveTransferAbortEDMA(I3C_Type *base, i3c_slave_edma_handle_t *handle)
{
if (handle->isBusy != false)
{
EDMA_AbortTransfer(handle->txDmaHandle);
EDMA_AbortTransfer(handle->rxDmaHandle);
I3C_SlaveEnableDMA(base, false, false, 0);
/* Reset fifos. These flags clear automatically. */
base->SDATACTRL |= I3C_SDATACTRL_FLUSHTB_MASK | I3C_SDATACTRL_FLUSHFB_MASK;
}
}