/* * Copyright (c) 2016, Freescale Semiconductor, Inc. * Copyright 2016-2019 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include "fsl_i2c_dma.h" /******************************************************************************* * Definitions ******************************************************************************/ /* Component ID definition, used by tools. */ #ifndef FSL_COMPONENT_ID #define FSL_COMPONENT_ID "platform.drivers.lpc_i2c_dma" #endif /*transfer = *xfer; transfer = &(handle->transfer); handle->transferCount = 0; handle->remainingBytesDMA = 0; handle->buf = (uint8_t *)transfer->data; handle->remainingSubaddr = 0; if (transfer->flags & kI2C_TransferNoStartFlag) { /* Start condition shall be ommited, switch directly to next phase */ if (transfer->dataSize == 0) { handle->state = kStopState; } else if (handle->transfer.direction == kI2C_Write) { handle->state = kTransmitDataState; } else if (handle->transfer.direction == kI2C_Read) { handle->state = (xfer->dataSize == 1) ? kReceiveLastDataState : kReceiveDataState; } else { return kStatus_I2C_InvalidParameter; } } else { if (transfer->subaddressSize != 0) { int i; uint32_t subaddress; if (transfer->subaddressSize > sizeof(handle->subaddrBuf)) { return kStatus_I2C_InvalidParameter; } /* Prepare subaddress transmit buffer, most significant byte is stored at the lowest address */ subaddress = xfer->subaddress; for (i = xfer->subaddressSize - 1; i >= 0; i--) { handle->subaddrBuf[i] = subaddress & 0xff; subaddress >>= 8; } handle->remainingSubaddr = transfer->subaddressSize; } handle->state = kStartState; } return kStatus_Success; } static void I2C_RunDMATransfer(I2C_Type *base, i2c_master_dma_handle_t *handle) { int transfer_size; dma_transfer_config_t xferConfig; /* Update transfer count */ handle->transferCount = handle->buf - (uint8_t *)handle->transfer.data; /* Check if there is anything to be transferred at all */ if (handle->remainingBytesDMA == 0) { /* No data to be transferrred, disable DMA */ base->MSTCTL = 0; return; } /* Calculate transfer size */ transfer_size = handle->remainingBytesDMA; if (transfer_size > I2C_MAX_DMA_TRANSFER_COUNT) { transfer_size = I2C_MAX_DMA_TRANSFER_COUNT; } switch (handle->transfer.direction) { case kI2C_Write: DMA_PrepareTransfer(&xferConfig, handle->buf, (void *)&base->MSTDAT, sizeof(uint8_t), transfer_size, kDMA_MemoryToPeripheral, NULL); break; case kI2C_Read: DMA_PrepareTransfer(&xferConfig, (void *)&base->MSTDAT, handle->buf, sizeof(uint8_t), transfer_size, kDMA_PeripheralToMemory, NULL); break; default: /* This should never happen */ assert(0); break; } DMA_SubmitTransfer(handle->dmaHandle, &xferConfig); DMA_StartTransfer(handle->dmaHandle); handle->remainingBytesDMA -= transfer_size; handle->buf += transfer_size; } /*! * @brief Execute states until the transfer is done. * @param handle Master nonblocking driver handle. * @param[out] isDone Set to true if the transfer has completed. * @retval #kStatus_Success * @retval #kStatus_I2C_ArbitrationLost * @retval #kStatus_I2C_Nak */ static status_t I2C_RunTransferStateMachineDMA(I2C_Type *base, i2c_master_dma_handle_t *handle, bool *isDone) { uint32_t status; uint32_t master_state; struct _i2c_master_transfer *transfer; dma_transfer_config_t xferConfig; status_t err; uint32_t start_flag = 0; transfer = &(handle->transfer); *isDone = false; status = I2C_GetStatusFlags(base); if (status & I2C_STAT_MSTARBLOSS_MASK) { I2C_MasterClearStatusFlags(base, I2C_STAT_MSTARBLOSS_MASK); DMA_AbortTransfer(handle->dmaHandle); base->MSTCTL = 0; return kStatus_I2C_ArbitrationLost; } if (status & I2C_STAT_MSTSTSTPERR_MASK) { I2C_MasterClearStatusFlags(base, I2C_STAT_MSTSTSTPERR_MASK); DMA_AbortTransfer(handle->dmaHandle); base->MSTCTL = 0; return kStatus_I2C_StartStopError; } if ((status & I2C_STAT_MSTPENDING_MASK) == 0) { return kStatus_I2C_Busy; } /* Get the state of the I2C module */ master_state = (status & I2C_STAT_MSTSTATE_MASK) >> I2C_STAT_MSTSTATE_SHIFT; if ((master_state == I2C_STAT_MSTCODE_NACKADR) || (master_state == I2C_STAT_MSTCODE_NACKDAT)) { /* Slave NACKed last byte, issue stop and return error */ DMA_AbortTransfer(handle->dmaHandle); base->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK; handle->state = kWaitForCompletionState; return kStatus_I2C_Nak; } err = kStatus_Success; if (handle->state == kStartState) { /* set start flag for later use */ start_flag = I2C_MSTCTL_MSTSTART_MASK; if (handle->remainingSubaddr) { base->MSTDAT = (uint32_t)transfer->slaveAddress << 1; handle->state = kTransmitSubaddrState; } else if (transfer->direction == kI2C_Write) { base->MSTDAT = (uint32_t)transfer->slaveAddress << 1; if (transfer->dataSize == 0) { /* No data to be transferred, initiate start and schedule stop */ base->MSTCTL = I2C_MSTCTL_MSTSTART_MASK; handle->state = kStopState; return err; } handle->state = kTransmitDataState; } else if ((transfer->direction == kI2C_Read) && (transfer->dataSize > 0)) { base->MSTDAT = ((uint32_t)transfer->slaveAddress << 1) | 1u; if (transfer->dataSize == 1) { /* The very last byte is always received by means of SW */ base->MSTCTL = I2C_MSTCTL_MSTSTART_MASK; handle->state = kReceiveLastDataState; return err; } handle->state = kReceiveDataState; } else { handle->state = kIdleState; err = kStatus_I2C_UnexpectedState; return err; } } switch (handle->state) { case kTransmitSubaddrState: if ((master_state != I2C_STAT_MSTCODE_TXREADY) && (!start_flag)) { return kStatus_I2C_UnexpectedState; } base->MSTCTL = start_flag | I2C_MSTCTL_MSTDMA_MASK; /* Prepare and submit DMA transfer. */ DMA_PrepareTransfer(&xferConfig, handle->subaddrBuf, (void *)&base->MSTDAT, sizeof(uint8_t), handle->remainingSubaddr, kDMA_MemoryToPeripheral, NULL); DMA_SubmitTransfer(handle->dmaHandle, &xferConfig); DMA_StartTransfer(handle->dmaHandle); handle->remainingSubaddr = 0; if (transfer->dataSize) { /* There is data to be transferred, if there is write to read turnaround it is necessary to perform * repeated start */ handle->state = (transfer->direction == kI2C_Read) ? kStartState : kTransmitDataState; } else { /* No more data, schedule stop condition */ handle->state = kStopState; } break; case kTransmitDataState: if ((master_state != I2C_STAT_MSTCODE_TXREADY) && (!start_flag)) { return kStatus_I2C_UnexpectedState; } base->MSTCTL = start_flag | I2C_MSTCTL_MSTDMA_MASK; handle->remainingBytesDMA = handle->transfer.dataSize; I2C_RunDMATransfer(base, handle); /* Schedule stop condition */ handle->state = kStopState; break; case kReceiveDataState: if ((master_state != I2C_STAT_MSTCODE_RXREADY) && (!start_flag)) { if (!(transfer->flags & kI2C_TransferNoStartFlag)) { return kStatus_I2C_UnexpectedState; } } base->MSTCTL = start_flag | I2C_MSTCTL_MSTDMA_MASK; handle->remainingBytesDMA = handle->transfer.dataSize - 1; if (transfer->flags & kI2C_TransferNoStartFlag) { /* Read the master data register to avoid the data be read again */ uint8_t dummy = base->MSTDAT; /* Use dummy at least once to avoid coverity issue */ dummy ++; } I2C_RunDMATransfer(base, handle); /* Schedule reception of last data byte */ handle->state = kReceiveLastDataState; break; case kReceiveLastDataState: if (master_state != I2C_STAT_MSTCODE_RXREADY) { return kStatus_I2C_UnexpectedState; } ((uint8_t *)transfer->data)[transfer->dataSize - 1] = base->MSTDAT; handle->transferCount++; /* No more data expected, issue NACK and STOP right away */ if (!(transfer->flags & kI2C_TransferNoStopFlag)) { base->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK; } handle->state = kWaitForCompletionState; break; case kStopState: if (transfer->flags & kI2C_TransferNoStopFlag) { /* Stop condition is omitted, we are done */ *isDone = true; handle->state = kIdleState; break; } /* Send stop condition */ base->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK; handle->state = kWaitForCompletionState; break; case kWaitForCompletionState: *isDone = true; handle->state = kIdleState; break; case kStartState: case kIdleState: default: /* State machine shall not be invoked again once it enters the idle state */ err = kStatus_I2C_UnexpectedState; break; } return err; } void I2C_MasterTransferDMAHandleIRQ(I2C_Type *base, void *i2cHandle) { assert(i2cHandle); bool isDone; status_t result; i2c_master_dma_handle_t *handle = (i2c_master_dma_handle_t *)i2cHandle; /* Don't do anything if we don't have a valid handle. */ if (!handle) { return; } result = I2C_RunTransferStateMachineDMA(base, handle, &isDone); if (isDone || (result != kStatus_Success)) { /* Restore handle to idle state. */ handle->state = kIdleState; /* Disable internal IRQ enables. */ I2C_DisableInterrupts(base, I2C_INTSTAT_MSTPENDING_MASK | I2C_INTSTAT_MSTARBLOSS_MASK | I2C_INTSTAT_MSTSTSTPERR_MASK); /* Invoke callback. */ if (handle->completionCallback) { handle->completionCallback(base, handle, result, handle->userData); } } } static void I2C_MasterTransferCallbackDMA(dma_handle_t *handle, void *userData) { i2c_master_dma_private_handle_t *dmaPrivateHandle; /* Don't do anything if we don't have a valid handle. */ if (!handle) { return; } dmaPrivateHandle = (i2c_master_dma_private_handle_t *)userData; I2C_RunDMATransfer(dmaPrivateHandle->base, dmaPrivateHandle->handle); } /*! * brief Init the I2C handle which is used in transactional functions * * param base I2C peripheral base address * param handle pointer to i2c_master_dma_handle_t structure * param callback pointer to user callback function * param userData user param passed to the callback function * param dmaHandle DMA handle pointer */ void I2C_MasterTransferCreateHandleDMA(I2C_Type *base, i2c_master_dma_handle_t *handle, i2c_master_dma_transfer_callback_t callback, void *userData, dma_handle_t *dmaHandle) { uint32_t instance; assert(handle); assert(dmaHandle); /* Zero handle. */ memset(handle, 0, sizeof(*handle)); /* Look up instance number */ instance = I2C_GetInstance(base); /* Set the user callback and userData. */ handle->completionCallback = callback; handle->userData = userData; /* Save the context in global variables to support the double weak mechanism. */ s_i2cHandle[instance] = handle; /* Save master interrupt handler. */ s_i2cMasterIsr = I2C_MasterTransferDMAHandleIRQ; /* Clear internal IRQ enables and enable NVIC IRQ. */ I2C_DisableInterrupts(base, I2C_INTSTAT_MSTPENDING_MASK | I2C_INTSTAT_MSTARBLOSS_MASK | I2C_INTSTAT_MSTSTSTPERR_MASK); EnableIRQ(s_i2cIRQ[instance]); /* Set the handle for DMA. */ handle->dmaHandle = dmaHandle; s_dmaPrivateHandle[instance].base = base; s_dmaPrivateHandle[instance].handle = handle; DMA_SetCallback(dmaHandle, (dma_callback)I2C_MasterTransferCallbackDMA, &s_dmaPrivateHandle[instance]); } /*! * brief Performs a master dma non-blocking transfer on the I2C bus * * param base I2C peripheral base address * param handle pointer to i2c_master_dma_handle_t structure * param xfer pointer to transfer structure of i2c_master_transfer_t * retval kStatus_Success Sucessully complete the data transmission. * retval kStatus_I2C_Busy Previous transmission still not finished. * retval kStatus_I2C_Timeout Transfer error, wait signal timeout. * retval kStatus_I2C_ArbitrationLost Transfer error, arbitration lost. * retval kStataus_I2C_Nak Transfer error, receive Nak during transfer. */ status_t I2C_MasterTransferDMA(I2C_Type *base, i2c_master_dma_handle_t *handle, i2c_master_transfer_t *xfer) { status_t result; assert(handle); assert(xfer); assert(xfer->subaddressSize <= sizeof(xfer->subaddress)); /* Return busy if another transaction is in progress. */ if (handle->state != kIdleState) { return kStatus_I2C_Busy; } /* Prepare transfer state machine. */ result = I2C_InitTransferStateMachineDMA(base, handle, xfer); /* Clear error flags. */ I2C_MasterClearStatusFlags(base, I2C_STAT_MSTARBLOSS_MASK | I2C_STAT_MSTSTSTPERR_MASK); /* Enable I2C internal IRQ sources */ I2C_EnableInterrupts(base, I2C_INTSTAT_MSTARBLOSS_MASK | I2C_INTSTAT_MSTSTSTPERR_MASK | I2C_INTSTAT_MSTPENDING_MASK); return result; } /*! * brief Get master transfer status during a dma non-blocking transfer * * param base I2C peripheral base address * param handle pointer to i2c_master_dma_handle_t structure * param count Number of bytes transferred so far by the non-blocking transaction. */ status_t I2C_MasterTransferGetCountDMA(I2C_Type *base, i2c_master_dma_handle_t *handle, size_t *count) { assert(handle); if (!count) { return kStatus_InvalidArgument; } /* Catch when there is not an active transfer. */ if (handle->state == kIdleState) { *count = 0; return kStatus_NoTransferInProgress; } /* There is no necessity to disable interrupts as we read a single integer value */ *count = handle->transferCount; return kStatus_Success; } /*! * brief Abort a master dma non-blocking transfer in a early time * * param base I2C peripheral base address * param handle pointer to i2c_master_dma_handle_t structure */ void I2C_MasterTransferAbortDMA(I2C_Type *base, i2c_master_dma_handle_t *handle) { uint32_t status; uint32_t master_state; if (handle->state != kIdleState) { DMA_AbortTransfer(handle->dmaHandle); /* Disable DMA */ base->MSTCTL = 0; /* Disable internal IRQ enables. */ I2C_DisableInterrupts(base, I2C_INTSTAT_MSTPENDING_MASK | I2C_INTSTAT_MSTARBLOSS_MASK | I2C_INTSTAT_MSTSTSTPERR_MASK); /* Wait until module is ready */ do { status = I2C_GetStatusFlags(base); } while ((status & I2C_STAT_MSTPENDING_MASK) == 0); /* Clear controller state. */ I2C_MasterClearStatusFlags(base, I2C_STAT_MSTARBLOSS_MASK | I2C_STAT_MSTSTSTPERR_MASK); /* Get the state of the I2C module */ master_state = (status & I2C_STAT_MSTSTATE_MASK) >> I2C_STAT_MSTSTATE_SHIFT; if (master_state != I2C_STAT_MSTCODE_IDLE) { /* Send a stop command to finalize the transfer. */ base->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK; /* Wait until module is ready */ do { status = I2C_GetStatusFlags(base); } while ((status & I2C_STAT_MSTPENDING_MASK) == 0); /* Clear controller state. */ I2C_MasterClearStatusFlags(base, I2C_STAT_MSTARBLOSS_MASK | I2C_STAT_MSTSTSTPERR_MASK); } /* Reset the state to idle. */ handle->state = kIdleState; } }