diff options
Diffstat (limited to '')
-rw-r--r-- | bsps/arm/imxrt/nxp/devices/MIMXRT1052/drivers/fsl_csi.c | 1395 |
1 files changed, 1395 insertions, 0 deletions
diff --git a/bsps/arm/imxrt/nxp/devices/MIMXRT1052/drivers/fsl_csi.c b/bsps/arm/imxrt/nxp/devices/MIMXRT1052/drivers/fsl_csi.c new file mode 100644 index 0000000000..2eea263e5f --- /dev/null +++ b/bsps/arm/imxrt/nxp/devices/MIMXRT1052/drivers/fsl_csi.c @@ -0,0 +1,1395 @@ +/* + * Copyright 2017-2019 NXP + * All rights reserved. + * + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "fsl_csi.h" +#if CSI_DRIVER_FRAG_MODE +#include "fsl_cache.h" +#endif + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.csi" +#endif + +/* Two frame buffer loaded to CSI register at most. */ +#define CSI_MAX_ACTIVE_FRAME_NUM 2U + +/* CSI driver only support RGB565 and YUV422 in fragment mode, 2 bytes per pixel. */ +#define CSI_FRAG_INPUT_BYTES_PER_PIXEL 2U + +/*! + * @brief Used for conversion between `void*` and `uint32_t`. + */ +typedef union pvoid_to_u32 +{ + void *pvoid; + uint32_t u32; +} pvoid_to_u32_t; + +/******************************************************************************* + * Prototypes + ******************************************************************************/ + +/*! + * @brief Get the instance from the base address + * + * @param base CSI peripheral base address + * + * @return The CSI module instance + */ +static uint32_t CSI_GetInstance(CSI_Type *base); + +#if !CSI_DRIVER_FRAG_MODE +/*! + * @brief Get the delta value of two index in queue. + * + * @param startIdx Start index. + * @param endIdx End index. + * + * @return The delta between startIdx and endIdx in queue. + */ +static uint8_t CSI_TransferGetQueueDelta(uint8_t startIdx, uint8_t endIdx); + +/*! + * @brief Increase a index value in queue. + * + * This function increases the index value in the queue, if the index is out of + * the queue range, it is reset to 0. + * + * @param idx The index value to increase. + * + * @return The index value after increase. + */ +static uint8_t CSI_TransferIncreaseQueueIdx(uint8_t idx); + +/*! + * @brief Get the empty frame buffer count in queue. + * + * @param base CSI peripheral base address + * @param handle Pointer to CSI driver handle. + * + * @return Number of the empty frame buffer count in queue. + */ +static uint32_t CSI_TransferGetEmptyBufferCount(csi_handle_t *handle); + +/*! + * @brief Get the empty frame buffer. + * + * This function should only be called when frame buffer count larger than 0. + * + * @param handle Pointer to CSI driver handle. + * + * @return Empty buffer + */ +static uint32_t CSI_TransferGetEmptyBuffer(csi_handle_t *handle); + +/*! + * @brief Put the empty frame buffer. + * + * @param handle Pointer to CSI driver handle. + * @param buffer The empty buffer to put. + */ +static void CSI_TransferPutEmptyBuffer(csi_handle_t *handle, uint32_t buffer); + +/*! + * @brief Get the RX frame buffer address. + * + * @param base CSI peripheral base address. + * @param index Buffer index. + * @return Frame buffer address. + */ +static uint32_t CSI_GetRxBufferAddr(CSI_Type *base, uint8_t index); + +/* Typedef for interrupt handler. */ +typedef void (*csi_isr_t)(CSI_Type *base, csi_handle_t *handle); + +#else + +/* Typedef for interrupt handler to work in fragment mode. */ +typedef void (*csi_isr_t)(CSI_Type *base, csi_frag_handle_t *handle); +#endif /* CSI_DRIVER_FRAG_MODE */ + +/******************************************************************************* + * Variables + ******************************************************************************/ +/*! @brief Pointers to CSI bases for each instance. */ +static CSI_Type *const s_csiBases[] = CSI_BASE_PTRS; + +#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) +/*! @brief Pointers to CSI clocks for each CSI submodule. */ +static const clock_ip_name_t s_csiClocks[] = CSI_CLOCKS; +#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ + +/* Array for the CSI driver handle. */ +#if !CSI_DRIVER_FRAG_MODE +static csi_handle_t *s_csiHandle[ARRAY_SIZE(s_csiBases)]; +#else +static csi_frag_handle_t *s_csiHandle[ARRAY_SIZE(s_csiBases)]; +#endif + +/* Array of CSI IRQ number. */ +static const IRQn_Type s_csiIRQ[] = CSI_IRQS; + +/* CSI ISR for transactional APIs. */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) +static csi_isr_t s_csiIsr = (csi_isr_t)DefaultISR; +#else +static csi_isr_t s_csiIsr; +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +static uint32_t CSI_GetInstance(CSI_Type *base) +{ + uint32_t instance; + + /* Find the instance index from base address mappings. */ + for (instance = 0; instance < ARRAY_SIZE(s_csiBases); instance++) + { + if (s_csiBases[instance] == base) + { + break; + } + } + + assert(instance < ARRAY_SIZE(s_csiBases)); + + return instance; +} + +#if !CSI_DRIVER_FRAG_MODE +static uint8_t CSI_TransferGetQueueDelta(uint8_t startIdx, uint8_t endIdx) +{ + uint8_t ret; + + if (endIdx >= startIdx) + { + ret = endIdx - startIdx; + } + else + { + ret = (uint8_t)(endIdx + CSI_DRIVER_ACTUAL_QUEUE_SIZE - startIdx); + } + + return ret; +} + +static uint8_t CSI_TransferIncreaseQueueIdx(uint8_t idx) +{ + uint8_t ret; + + /* + * Here not use the method: + * ret = (idx+1) % CSI_DRIVER_ACTUAL_QUEUE_SIZE; + * + * Because the mod function might be slow. + */ + + ret = idx + 1U; + + if (ret >= CSI_DRIVER_ACTUAL_QUEUE_SIZE) + { + ret = 0U; + } + + return ret; +} + +static uint32_t CSI_TransferGetEmptyBufferCount(csi_handle_t *handle) +{ + return handle->emptyBufferCnt; +} + +static uint32_t CSI_TransferGetEmptyBuffer(csi_handle_t *handle) +{ + pvoid_to_u32_t buf; + + buf.pvoid = handle->emptyBuffer; + handle->emptyBufferCnt--; + handle->emptyBuffer = *(void **)(buf.pvoid); + + return buf.u32; +} + +static void CSI_TransferPutEmptyBuffer(csi_handle_t *handle, uint32_t buffer) +{ + pvoid_to_u32_t buf; + buf.u32 = buffer; + + *(void **)(buf.pvoid) = handle->emptyBuffer; + handle->emptyBuffer = buf.pvoid; + handle->emptyBufferCnt++; +} + +static uint32_t CSI_GetRxBufferAddr(CSI_Type *base, uint8_t index) +{ + uint32_t addr; + + if (index != 0U) + { + addr = base->CSIDMASA_FB2; + } + else + { + addr = base->CSIDMASA_FB1; + } + + return addr; +} + +#endif /* CSI_DRIVER_FRAG_MODE */ + +/*! + * brief Initialize the CSI. + * + * This function enables the CSI peripheral clock, and resets the CSI registers. + * + * param base CSI peripheral base address. + * param config Pointer to the configuration structure. + * + * retval kStatus_Success Initialize successfully. + * retval kStatus_InvalidArgument Initialize failed because of invalid argument. + */ +status_t CSI_Init(CSI_Type *base, const csi_config_t *config) +{ + assert(NULL != config); + uint32_t reg; + uint32_t imgWidth_Bytes; + uint8_t busCyclePerPixel; + + imgWidth_Bytes = (uint32_t)config->width * (uint32_t)config->bytesPerPixel; + + /* The image width and frame buffer pitch should be multiple of 8-bytes. */ + if ((0U != (imgWidth_Bytes & 0x07U)) || (0U != ((uint32_t)config->linePitch_Bytes & 0x07U))) + { + return kStatus_InvalidArgument; + } + +#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + uint32_t instance = CSI_GetInstance(base); + CLOCK_EnableClock(s_csiClocks[instance]); +#endif + + CSI_Reset(base); + + /* Configure CSICR1. CSICR1 has been reset to the default value, so could write it directly. */ + reg = ((uint32_t)config->workMode) | config->polarityFlags | CSI_CSICR1_FCC_MASK; + + if (config->useExtVsync) + { + reg |= CSI_CSICR1_EXT_VSYNC_MASK; + } + + base->CSICR1 = reg; + + /* + * Generally, CSIIMAG_PARA[IMAGE_WIDTH] indicates how many data bus cycles per line. + * One special case is when receiving 24-bit pixels through 8-bit data bus. + * In this case, the CSIIMAG_PARA[IMAGE_WIDTH] should be set to the pixel number per line. + */ + if ((kCSI_DataBus8Bit == config->dataBus) && (2U == config->bytesPerPixel)) + { + busCyclePerPixel = 2U; + } + else + { + busCyclePerPixel = 1U; + } + + if (4U == config->bytesPerPixel) + { + base->CSICR18 |= CSI_CSICR18_PARALLEL24_EN_MASK; + } + + if (kCSI_DataBus16Bit == config->dataBus) + { + base->CSICR3 |= CSI_CSICR3_TWO_8BIT_SENSOR_MASK; + } + + /* Image parameter. */ + base->CSIIMAG_PARA = + (((uint32_t)config->width * (uint32_t)busCyclePerPixel) << CSI_CSIIMAG_PARA_IMAGE_WIDTH_SHIFT) | + ((uint32_t)(config->height) << CSI_CSIIMAG_PARA_IMAGE_HEIGHT_SHIFT); + + /* The CSI frame buffer bus is 8-byte width. */ + base->CSIFBUF_PARA = (uint32_t)((config->linePitch_Bytes - imgWidth_Bytes) / 8U) + << CSI_CSIFBUF_PARA_FBUF_STRIDE_SHIFT; + + /* Enable auto ECC. */ + base->CSICR3 |= CSI_CSICR3_ECC_AUTO_EN_MASK; + + /* + * For better performance. + * The DMA burst size could be set to 16 * 8 byte, 8 * 8 byte, or 4 * 8 byte, + * choose the best burst size based on bytes per line. + */ + if (0U == (imgWidth_Bytes % (8U * 16U))) + { + base->CSICR2 = CSI_CSICR2_DMA_BURST_TYPE_RFF(3U); + base->CSICR3 = (CSI->CSICR3 & ~CSI_CSICR3_RxFF_LEVEL_MASK) | ((2U << CSI_CSICR3_RxFF_LEVEL_SHIFT)); + } + else if (0U == (imgWidth_Bytes % (8U * 8U))) + { + base->CSICR2 = CSI_CSICR2_DMA_BURST_TYPE_RFF(2U); + base->CSICR3 = (CSI->CSICR3 & ~CSI_CSICR3_RxFF_LEVEL_MASK) | ((1U << CSI_CSICR3_RxFF_LEVEL_SHIFT)); + } + else + { + base->CSICR2 = CSI_CSICR2_DMA_BURST_TYPE_RFF(1U); + base->CSICR3 = (CSI->CSICR3 & ~CSI_CSICR3_RxFF_LEVEL_MASK) | ((0U << CSI_CSICR3_RxFF_LEVEL_SHIFT)); + } + + CSI_ReflashFifoDma(base, kCSI_RxFifo); + + return kStatus_Success; +} + +/*! + * brief De-initialize the CSI. + * + * This function disables the CSI peripheral clock. + * + * param base CSI peripheral base address. + */ +void CSI_Deinit(CSI_Type *base) +{ + /* Disable transfer first. */ + CSI_Stop(base); +#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + uint32_t instance = CSI_GetInstance(base); + CLOCK_DisableClock(s_csiClocks[instance]); +#endif +} + +/*! + * brief Reset the CSI. + * + * This function resets the CSI peripheral registers to default status. + * + * param base CSI peripheral base address. + */ +void CSI_Reset(CSI_Type *base) +{ + uint32_t csisr; + + /* Disable transfer first. */ + CSI_Stop(base); + + /* Disable DMA request. */ + base->CSICR3 = 0U; + + /* Reset the fame count. */ + base->CSICR3 |= CSI_CSICR3_FRMCNT_RST_MASK; + while (0U != (base->CSICR3 & CSI_CSICR3_FRMCNT_RST_MASK)) + { + } + + /* Clear the RX FIFO. */ + CSI_ClearFifo(base, kCSI_AllFifo); + + /* Reflash DMA. */ + CSI_ReflashFifoDma(base, kCSI_AllFifo); + + /* Clear the status. */ + csisr = base->CSISR; + base->CSISR = csisr; + + /* Set the control registers to default value. */ + base->CSICR1 = CSI_CSICR1_HSYNC_POL_MASK | CSI_CSICR1_EXT_VSYNC_MASK; + base->CSICR2 = 0U; + base->CSICR3 = 0U; +#if defined(CSI_CSICR18_CSI_LCDIF_BUFFER_LINES) + base->CSICR18 = CSI_CSICR18_AHB_HPROT(0x0DU) | CSI_CSICR18_CSI_LCDIF_BUFFER_LINES(0x02U); +#else + base->CSICR18 = CSI_CSICR18_AHB_HPROT(0x0DU); +#endif + base->CSIFBUF_PARA = 0U; + base->CSIIMAG_PARA = 0U; +} + +/*! + * brief Get the default configuration for to initialize the CSI. + * + * The default configuration value is: + * + * code + config->width = 320U; + config->height = 240U; + config->polarityFlags = kCSI_HsyncActiveHigh | kCSI_DataLatchOnRisingEdge; + config->bytesPerPixel = 2U; + config->linePitch_Bytes = 320U * 2U; + config->workMode = kCSI_GatedClockMode; + config->dataBus = kCSI_DataBus8Bit; + config->useExtVsync = true; + endcode + * + * param config Pointer to the CSI configuration. + */ +void CSI_GetDefaultConfig(csi_config_t *config) +{ + assert(NULL != config); + + /* Initializes the configure structure to zero. */ + (void)memset(config, 0, sizeof(*config)); + + config->width = 320U; + config->height = 240U; + config->polarityFlags = (uint32_t)kCSI_HsyncActiveHigh | (uint32_t)kCSI_DataLatchOnRisingEdge; + config->bytesPerPixel = 2U; + config->linePitch_Bytes = 320U * 2U; + config->workMode = kCSI_GatedClockMode; + config->dataBus = kCSI_DataBus8Bit; + config->useExtVsync = true; +} + +/*! + * brief Set the RX frame buffer address. + * + * param base CSI peripheral base address. + * param index Buffer index. + * param addr Frame buffer address to set. + */ +void CSI_SetRxBufferAddr(CSI_Type *base, uint8_t index, uint32_t addr) +{ + if (0U != index) + { + base->CSIDMASA_FB2 = addr; + } + else + { + base->CSIDMASA_FB1 = addr; + } +} + +/*! + * brief Clear the CSI FIFO. + * + * This function clears the CSI FIFO. + * + * param base CSI peripheral base address. + * param fifo The FIFO to clear. + */ +void CSI_ClearFifo(CSI_Type *base, csi_fifo_t fifo) +{ + uint32_t cr1; + uint32_t mask = 0U; + + /* The FIFO could only be cleared when CSICR1[FCC] = 0, so first clear the FCC. */ + cr1 = base->CSICR1; + base->CSICR1 = (cr1 & ~CSI_CSICR1_FCC_MASK); + + if (0U != ((uint32_t)fifo & (uint32_t)kCSI_RxFifo)) + { + mask |= CSI_CSICR1_CLR_RXFIFO_MASK; + } + + if (0U != ((uint32_t)fifo & (uint32_t)kCSI_StatFifo)) + { + mask |= CSI_CSICR1_CLR_STATFIFO_MASK; + } + + base->CSICR1 = (cr1 & ~CSI_CSICR1_FCC_MASK) | mask; + + /* Wait clear completed. */ + while (0U != (base->CSICR1 & mask)) + { + } + + /* Recover the FCC. */ + base->CSICR1 = cr1; +} + +/*! + * brief Reflash the CSI FIFO DMA. + * + * This function reflashes the CSI FIFO DMA. + * + * For RXFIFO, there are two frame buffers. When the CSI module started, it saves + * the frames to frame buffer 0 then frame buffer 1, the two buffers will be + * written by turns. After reflash DMA using this function, the CSI is reset to + * save frame to buffer 0. + * + * param base CSI peripheral base address. + * param fifo The FIFO DMA to reflash. + */ +void CSI_ReflashFifoDma(CSI_Type *base, csi_fifo_t fifo) +{ + uint32_t cr3 = 0U; + + if (0U != ((uint32_t)fifo & (uint32_t)kCSI_RxFifo)) + { + cr3 |= CSI_CSICR3_DMA_REFLASH_RFF_MASK; + } + + if (0U != ((uint32_t)fifo & (uint32_t)kCSI_StatFifo)) + { + cr3 |= CSI_CSICR3_DMA_REFLASH_SFF_MASK; + } + + base->CSICR3 |= cr3; + + /* Wait clear completed. */ + while (0U != (base->CSICR3 & cr3)) + { + } +} + +/*! + * brief Enable or disable the CSI FIFO DMA request. + * + * param base CSI peripheral base address. + * param fifo The FIFO DMA reques to enable or disable. + * param enable True to enable, false to disable. + */ +void CSI_EnableFifoDmaRequest(CSI_Type *base, csi_fifo_t fifo, bool enable) +{ + uint32_t cr3 = 0U; + + if (0U != ((uint32_t)fifo & (uint32_t)kCSI_RxFifo)) + { + cr3 |= CSI_CSICR3_DMA_REQ_EN_RFF_MASK; + } + + if (0U != ((uint32_t)fifo & (uint32_t)kCSI_StatFifo)) + { + cr3 |= CSI_CSICR3_DMA_REQ_EN_SFF_MASK; + } + + if (enable) + { + base->CSICR3 |= cr3; + } + else + { + base->CSICR3 &= ~cr3; + } +} + +/*! + * brief Enables CSI interrupt requests. + * + * param base CSI peripheral base address. + * param mask The interrupts to enable, pass in as OR'ed value of ref _csi_interrupt_enable. + */ +void CSI_EnableInterrupts(CSI_Type *base, uint32_t mask) +{ + base->CSICR1 |= (mask & CSI_CSICR1_INT_EN_MASK); + base->CSICR3 |= (mask & CSI_CSICR3_INT_EN_MASK); + base->CSICR18 |= ((mask & CSI_CSICR18_INT_EN_MASK) >> 6U); +} + +/*! + * brief Disable CSI interrupt requests. + * + * param base CSI peripheral base address. + * param mask The interrupts to disable, pass in as OR'ed value of ref _csi_interrupt_enable. + */ +void CSI_DisableInterrupts(CSI_Type *base, uint32_t mask) +{ + base->CSICR1 &= ~(mask & CSI_CSICR1_INT_EN_MASK); + base->CSICR3 &= ~(mask & CSI_CSICR3_INT_EN_MASK); + base->CSICR18 &= ~((mask & CSI_CSICR18_INT_EN_MASK) >> 6U); +} + +#if !CSI_DRIVER_FRAG_MODE +/*! + * brief Initializes the CSI handle. + * + * This function initializes CSI handle, it should be called before any other + * CSI transactional functions. + * + * param base CSI peripheral base address. + * param handle Pointer to the handle structure. + * param callback Callback function for CSI transfer. + * param userData Callback function parameter. + * + * retval kStatus_Success Handle created successfully. + */ +status_t CSI_TransferCreateHandle(CSI_Type *base, + csi_handle_t *handle, + csi_transfer_callback_t callback, + void *userData) +{ + assert(NULL != handle); + uint32_t instance; + + (void)memset(handle, 0, sizeof(*handle)); + + /* Set the callback and user data. */ + handle->callback = callback; + handle->userData = userData; + + /* Get instance from peripheral base address. */ + instance = CSI_GetInstance(base); + + /* Save the handle in global variables to support the double weak mechanism. */ + s_csiHandle[instance] = handle; + + s_csiIsr = CSI_TransferHandleIRQ; + + /* Enable interrupt. */ + (void)EnableIRQ(s_csiIRQ[instance]); + + return kStatus_Success; +} + +/*! + * brief Start the transfer using transactional functions. + * + * When the empty frame buffers have been submit to CSI driver using function + * ref CSI_TransferSubmitEmptyBuffer, user could call this function to start + * the transfer. The incoming frame will be saved to the empty frame buffer, + * and user could be optionally notified through callback function. + * + * param base CSI peripheral base address. + * param handle Pointer to the handle structure. + * + * retval kStatus_Success Started successfully. + * retval kStatus_CSI_NoEmptyBuffer Could not start because no empty frame buffer in queue. + */ +status_t CSI_TransferStart(CSI_Type *base, csi_handle_t *handle) +{ + assert(NULL != handle); + + uint32_t emptyBufferCount; + + emptyBufferCount = CSI_TransferGetEmptyBufferCount(handle); + + if (emptyBufferCount < 2U) + { + return kStatus_CSI_NoEmptyBuffer; + } + + /* + * Write to memory from first completed frame. + * DMA base addr switch at the edge of the first data of each frame, thus + * if one frame is broken, it could be reset at the next frame. + */ + base->CSICR18 = (base->CSICR18 & ~CSI_CSICR18_MASK_OPTION_MASK) | CSI_CSICR18_MASK_OPTION(0) | + CSI_CSICR18_BASEADDR_SWITCH_SEL_MASK | CSI_CSICR18_BASEADDR_SWITCH_EN_MASK; + + /* Load the frame buffer to CSI register, there are at least two empty buffers. */ + base->CSIDMASA_FB1 = CSI_TransferGetEmptyBuffer(handle); + base->CSIDMASA_FB2 = CSI_TransferGetEmptyBuffer(handle); + + handle->activeBufferNum = CSI_MAX_ACTIVE_FRAME_NUM; + + /* After reflash DMA, the CSI saves frame to frame buffer 0. */ + CSI_ReflashFifoDma(base, kCSI_RxFifo); + + handle->transferStarted = true; + + CSI_EnableInterrupts( + base, (uint32_t)kCSI_RxBuffer1DmaDoneInterruptEnable | (uint32_t)kCSI_RxBuffer0DmaDoneInterruptEnable); + + CSI_Start(base); + + return kStatus_Success; +} + +/*! + * brief Stop the transfer using transactional functions. + * + * The driver does not clean the full frame buffers in queue. In other words, after + * calling this function, user still could get the full frame buffers in queue + * using function ref CSI_TransferGetFullBuffer. + * + * param base CSI peripheral base address. + * param handle Pointer to the handle structure. + * + * retval kStatus_Success Stoped successfully. + */ +status_t CSI_TransferStop(CSI_Type *base, csi_handle_t *handle) +{ + assert(NULL != handle); + uint8_t activeBufferNum; + uint8_t bufIdx; + + CSI_Stop(base); + CSI_DisableInterrupts( + base, (uint32_t)kCSI_RxBuffer1DmaDoneInterruptEnable | (uint32_t)kCSI_RxBuffer0DmaDoneInterruptEnable); + + activeBufferNum = handle->activeBufferNum; + + handle->transferStarted = false; + handle->activeBufferNum = 0; + + /* + * Put active buffers to empty queue. + * + * If there is only one active frame buffers, then FB0 and FB1 use the same address, + * put FB0 to empty buffer queue is OK. + */ + for (bufIdx = 0; bufIdx < activeBufferNum; bufIdx++) + { + CSI_TransferPutEmptyBuffer(handle, CSI_GetRxBufferAddr(base, bufIdx)); + } + + return kStatus_Success; +} + +/*! + * brief Submit empty frame buffer to queue. + * + * This function could be called before ref CSI_TransferStart or after ref + * CSI_TransferStart. If there is no room in queue to store the empty frame + * buffer, this function returns error. + * + * param base CSI peripheral base address. + * param handle Pointer to the handle structure. + * param frameBuffer Empty frame buffer to submit. + * + * retval kStatus_Success Started successfully. + * retval kStatus_CSI_QueueFull Could not submit because there is no room in queue. + */ +status_t CSI_TransferSubmitEmptyBuffer(CSI_Type *base, csi_handle_t *handle, uint32_t frameBuffer) +{ + uint32_t csicr1; + + /* Disable the interrupt to protect the index information in handle. */ + csicr1 = base->CSICR1; + + base->CSICR1 = (csicr1 & ~(CSI_CSICR1_FB2_DMA_DONE_INTEN_MASK | CSI_CSICR1_FB1_DMA_DONE_INTEN_MASK)); + + /* Save the empty frame buffer address to queue. */ + CSI_TransferPutEmptyBuffer(handle, frameBuffer); + + base->CSICR1 = csicr1; + + return kStatus_Success; +} + +/*! + * brief Get one full frame buffer from queue. + * + * After the transfer started using function ref CSI_TransferStart, the incoming + * frames will be saved to the empty frame buffers in queue. This function gets + * the full-filled frame buffer from the queue. If there is no full frame buffer + * in queue, this function returns error. + * + * param base CSI peripheral base address. + * param handle Pointer to the handle structure. + * param frameBuffer Full frame buffer. + * + * retval kStatus_Success Started successfully. + * retval kStatus_CSI_NoFullBuffer There is no full frame buffer in queue. + */ +status_t CSI_TransferGetFullBuffer(CSI_Type *base, csi_handle_t *handle, uint32_t *frameBuffer) +{ + uint32_t csicr1; + status_t status; + uint8_t queueReadIdx; + uint8_t queueWriteIdx; + + queueReadIdx = handle->queueReadIdx; + queueWriteIdx = handle->queueWriteIdx; + + /* No full frame buffer. */ + if (queueReadIdx == queueWriteIdx) + { + status = kStatus_CSI_NoFullBuffer; + } + else + { + /* Disable the interrupt to protect the index information in handle. */ + csicr1 = base->CSICR1; + + base->CSICR1 = (csicr1 & ~(CSI_CSICR1_FB2_DMA_DONE_INTEN_MASK | CSI_CSICR1_FB1_DMA_DONE_INTEN_MASK)); + + *frameBuffer = handle->frameBufferQueue[handle->queueReadIdx]; + + handle->queueReadIdx = CSI_TransferIncreaseQueueIdx(handle->queueReadIdx); + + base->CSICR1 = csicr1; + + status = kStatus_Success; + } + + return status; +} + +/*! + * brief CSI IRQ handle function. + * + * This function handles the CSI IRQ request to work with CSI driver transactional + * APIs. + * + * param base CSI peripheral base address. + * param handle CSI handle pointer. + */ +void CSI_TransferHandleIRQ(CSI_Type *base, csi_handle_t *handle) +{ + uint8_t queueWriteIdx; + uint8_t queueReadIdx; + uint8_t dmaDoneBufferIdx; + uint32_t frameBuffer; + uint32_t csisr = base->CSISR; + + /* Clear the error flags. */ + base->CSISR = csisr; + + /* + * If both frame buffer 0 and frame buffer 1 flags assert, driver does not + * know which frame buffer ready just now, so skip them. + */ + if ((csisr & (CSI_CSISR_DMA_TSF_DONE_FB2_MASK | CSI_CSISR_DMA_TSF_DONE_FB1_MASK)) == + (CSI_CSISR_DMA_TSF_DONE_FB2_MASK | CSI_CSISR_DMA_TSF_DONE_FB1_MASK)) + { + ; /* Skip the frames. */ + } + else if (0U != (csisr & (CSI_CSISR_DMA_TSF_DONE_FB2_MASK | CSI_CSISR_DMA_TSF_DONE_FB1_MASK))) + { + if (0U != (csisr & CSI_CSISR_DMA_TSF_DONE_FB2_MASK)) + { + dmaDoneBufferIdx = 1; + } + else + { + dmaDoneBufferIdx = 0; + } + + if (handle->activeBufferNum == CSI_MAX_ACTIVE_FRAME_NUM) + { + queueWriteIdx = handle->queueWriteIdx; + queueReadIdx = handle->queueReadIdx; + + if (CSI_TransferGetQueueDelta(queueReadIdx, queueWriteIdx) < CSI_DRIVER_QUEUE_SIZE) + { + /* Put the full frame buffer to full buffer queue. */ + frameBuffer = CSI_GetRxBufferAddr(base, dmaDoneBufferIdx); + handle->frameBufferQueue[queueWriteIdx] = frameBuffer; + + handle->queueWriteIdx = CSI_TransferIncreaseQueueIdx(queueWriteIdx); + + handle->activeBufferNum--; + + if (NULL != handle->callback) + { + handle->callback(base, handle, kStatus_CSI_FrameDone, handle->userData); + } + } + else + { + } + } + + /* + * User may submit new frame buffer in callback, so recheck activeBufferNum here, + * if there is only one active buffer in CSI device, the two buffer registers + * are both set to the frame buffer address. + */ + if (handle->activeBufferNum < CSI_MAX_ACTIVE_FRAME_NUM) + { + if (CSI_TransferGetEmptyBufferCount(handle) > 0U) + { + /* Get the empty frameBuffer, and submit to CSI device. */ + CSI_SetRxBufferAddr(base, dmaDoneBufferIdx, CSI_TransferGetEmptyBuffer(handle)); + handle->activeBufferNum++; + } + else + { + /* If there is only one active frame buffer, then the two CSI + * output buffer address are all set to this frame buffer. + */ + frameBuffer = CSI_GetRxBufferAddr(base, dmaDoneBufferIdx ^ 1U); + CSI_SetRxBufferAddr(base, dmaDoneBufferIdx, frameBuffer); + } + } + } + else + { + } +} + +#else /* CSI_DRIVER_FRAG_MODE */ + +#if defined(__CC_ARM) +__asm void CSI_ExtractYFromYUYV(void *datBase, const void *dmaBase, size_t count) +{ + /* clang-format off */ + push {r4-r7, lr} +10 + LDMIA R1!, {r3-r6} + bfi r7, r3, #0, #8 /* Y0 */ + bfi ip, r5, #0, #8 /* Y4 */ + lsr r3, r3, #16 + lsr r5, r5, #16 + bfi r7, r3, #8, #8 /* Y1 */ + bfi ip, r5, #8, #8 /* Y5 */ + bfi r7, r4, #16, #8 /* Y2 */ + bfi ip, r6, #16, #8 /* Y6 */ + lsr r4, r4, #16 + lsr r6, r6, #16 + bfi r7, r4, #24, #8 /* Y3 */ + bfi ip, r6, #24, #8 /* Y7 */ + STMIA r0!, {r7, ip} + subs r2, #8 + bne %b10 + pop {r4-r7, pc} + /* clang-format on */ +} + +__asm void CSI_ExtractYFromUYVY(void *datBase, const void *dmaBase, size_t count) +{ + /* clang-format off */ + push {r4-r7, lr} +10 + LDMIA R1!, {r3-r6} + lsr r3, r3, #8 + lsr r5, r5, #8 + bfi r7, r3, #0, #8 /* Y0 */ + bfi ip, r5, #0, #8 /* Y4 */ + lsr r3, r3, #16 + lsr r5, r5, #16 + bfi r7, r3, #8, #8 /* Y1 */ + bfi ip, r5, #8, #8 /* Y5 */ + lsr r4, r4, #8 + lsr r6, r6, #8 + bfi r7, r4, #16, #8 /* Y2 */ + bfi ip, r6, #16, #8 /* Y6 */ + lsr r4, r4, #16 + lsr r6, r6, #16 + bfi r7, r4, #24, #8 /* Y3 */ + bfi ip, r6, #24, #8 /* Y7 */ + STMIA r0!, {r7, ip} + subs r2, #8 + bne %b10 + pop {r4-r7, pc} + /* clang-format on */ +} + +#elif (defined(__GNUC__) || defined(__ICCARM__)) || defined(__ARMCC_VERSION) +#if defined(__ICCARM__) +#pragma diag_suppress = Pe940 +#endif +__attribute__((naked)) void CSI_ExtractYFromYUYV(void *datBase, const void *dmaBase, size_t count); +void CSI_ExtractYFromYUYV(void *datBase, const void *dmaBase, size_t count) +{ + /* clang-format off */ + __asm volatile( + " push {r1-r7, r12, lr} \n" + "loop0: \n" + " ldmia r1!, {r3-r6} \n" + " bfi r7, r3, #0, #8 \n" /* Y0 */ + " bfi r12, r5, #0, #8 \n" /* Y4 */ + " lsr r3, r3, #16 \n" + " lsr r5, r5, #16 \n" + " bfi r7, r3, #8, #8 \n" /* Y1 */ + " bfi r12, r5, #8, #8 \n" /* Y5 */ + " bfi r7, r4, #16, #8 \n" /* Y2 */ + " bfi r12, r6, #16, #8 \n" /* Y6 */ + " lsr r4, r4, #16 \n" + " lsr r6, r6, #16 \n" + " bfi r7, r4, #24, #8 \n" /* Y3 */ + " bfi r12, r6, #24, #8 \n" /* Y7 */ + " stmia r0!, {r7, r12} \n" + " subs r2, #8 \n" + " bne loop0 \n" + " pop {r1-r7, r12, pc} \n"); + /* clang-format on */ +} + +__attribute__((naked)) void CSI_ExtractYFromUYVY(void *datBase, const void *dmaBase, size_t count); +void CSI_ExtractYFromUYVY(void *datBase, const void *dmaBase, size_t count) +{ + /* clang-format off */ + __asm volatile( + " push {r1-r7, r12, lr} \n" + "loop1: \n" + " ldmia r1!, {r3-r6} \n" + " lsr r3, r3, #8 \n" + " lsr r5, r5, #8 \n" + " bfi r7, r3, #0, #8 \n" /* Y0 */ + " bfi r12, r5, #0, #8 \n" /* Y4 */ + " lsr r3, r3, #16 \n" + " lsr r5, r5, #16 \n" + " bfi r7, r3, #8, #8 \n" /* Y1 */ + " bfi r12, r5, #8, #8 \n" /* Y5 */ + " lsr r4, r4, #8 \n" + " lsr r6, r6, #8 \n" + " bfi r7, r4, #16, #8 \n" /* Y2 */ + " bfi r12, r6, #16, #8 \n" /* Y6 */ + " lsr r4, r4, #16 \n" + " lsr r6, r6, #16 \n" + " bfi r7, r4, #24, #8 \n" /* Y3 */ + " bfi r12, r6, #24, #8 \n" /* Y7 */ + " stmia r0!, {r7, r12} \n" + " subs r2, #8 \n" + " bne loop1 \n" + " pop {r1-r7, r12, pc} \n"); + /* clang-format on */ +} +#if defined(__ICCARM__) +#pragma diag_default = Pe940 +#endif +#else +#error Toolchain not supported. +#endif + +static void CSI_MemCopy(void *pDest, const void *pSrc, size_t cnt) +{ + (void)memcpy(pDest, pSrc, cnt); +} + +/*! + * brief Initialize the CSI to work in fragment mode. + * + * This function enables the CSI peripheral clock, and resets the CSI registers. + * + * param base CSI peripheral base address. + */ +void CSI_FragModeInit(CSI_Type *base) +{ +#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + uint32_t instance = CSI_GetInstance(base); + CLOCK_EnableClock(s_csiClocks[instance]); +#endif + + CSI_Reset(base); +} + +/*! + * brief De-initialize the CSI. + * + * This function disables the CSI peripheral clock. + * + * param base CSI peripheral base address. + */ +void CSI_FragModeDeinit(CSI_Type *base) +{ + CSI_Deinit(base); +} + +/*! + * brief Create handle for CSI work in fragment mode. + * + * param base CSI peripheral base address. + * param handle Pointer to the transactional handle. + * param config Pointer to the configuration structure. + * param callback Callback function for CSI transfer. + * param userData Callback function parameter. + * + * retval kStatus_Success Initialize successfully. + * retval kStatus_InvalidArgument Initialize failed because of invalid argument. + */ +status_t CSI_FragModeCreateHandle(CSI_Type *base, + csi_frag_handle_t *handle, + const csi_frag_config_t *config, + csi_frag_transfer_callback_t callback, + void *userData) +{ + assert(NULL != config); + uint32_t reg; + uint32_t instance; + uint32_t imgWidth_Bytes; + + if (config->dataBus != kCSI_DataBus8Bit) + { + return kStatus_InvalidArgument; + } + + imgWidth_Bytes = (uint32_t)config->width * CSI_FRAG_INPUT_BYTES_PER_PIXEL; + + /* The image buffer line width should be multiple of 8-bytes. */ + if ((imgWidth_Bytes & 0x07U) != 0U) + { + return kStatus_InvalidArgument; + } + + /* Camera frame height must be dividable by DMA buffer line. */ + if (config->height % config->dmaBufferLine != 0U) + { + return kStatus_InvalidArgument; + } + + (void)memset(handle, 0, sizeof(*handle)); + handle->callback = callback; + handle->userData = userData; + handle->height = config->height; + handle->width = config->width; + handle->maxLinePerFrag = config->dmaBufferLine; + handle->dmaBytePerLine = config->width * CSI_FRAG_INPUT_BYTES_PER_PIXEL; + handle->isDmaBufferCachable = config->isDmaBufferCachable; + + /* Get instance from peripheral base address. */ + instance = CSI_GetInstance(base); + /* Save the handle in global variables to support the double weak mechanism. */ + s_csiHandle[instance] = handle; + + s_csiIsr = CSI_FragModeTransferHandleIRQ; + + EnableIRQ(s_csiIRQ[instance]); + + /* Configure CSICR1. CSICR1 has been reset to the default value, so could write it directly. */ + reg = ((uint32_t)config->workMode) | config->polarityFlags | CSI_CSICR1_FCC_MASK; + + if (config->useExtVsync) + { + reg |= CSI_CSICR1_EXT_VSYNC_MASK; + } + + base->CSICR1 = reg; + + /* No stride. */ + base->CSIFBUF_PARA = 0; + + /* Enable auto ECC. */ + base->CSICR3 |= CSI_CSICR3_ECC_AUTO_EN_MASK; + + /* + * For better performance. + * The DMA burst size could be set to 16 * 8 byte, 8 * 8 byte, or 4 * 8 byte, + * choose the best burst size based on bytes per line. + */ + if (0U == (imgWidth_Bytes % (8U * 16U))) + { + base->CSICR2 = CSI_CSICR2_DMA_BURST_TYPE_RFF(3U); + base->CSICR3 = (CSI->CSICR3 & ~CSI_CSICR3_RxFF_LEVEL_MASK) | ((2U << CSI_CSICR3_RxFF_LEVEL_SHIFT)); + } + else if (0U == (imgWidth_Bytes % (8U * 8U))) + { + base->CSICR2 = CSI_CSICR2_DMA_BURST_TYPE_RFF(2U); + base->CSICR3 = (CSI->CSICR3 & ~CSI_CSICR3_RxFF_LEVEL_MASK) | ((1U << CSI_CSICR3_RxFF_LEVEL_SHIFT)); + } + else + { + base->CSICR2 = CSI_CSICR2_DMA_BURST_TYPE_RFF(1U); + base->CSICR3 = (CSI->CSICR3 & ~CSI_CSICR3_RxFF_LEVEL_MASK) | ((0U << CSI_CSICR3_RxFF_LEVEL_SHIFT)); + } + + base->CSIDMASA_FB1 = config->dmaBufferAddr0; + base->CSIDMASA_FB2 = config->dmaBufferAddr1; + + if (handle->isDmaBufferCachable) + { + DCACHE_CleanInvalidateByRange( + config->dmaBufferAddr0, + (uint32_t)config->dmaBufferLine * (uint32_t)config->width * CSI_FRAG_INPUT_BYTES_PER_PIXEL); + DCACHE_CleanInvalidateByRange( + config->dmaBufferAddr1, + (uint32_t)config->dmaBufferLine * (uint32_t)config->width * CSI_FRAG_INPUT_BYTES_PER_PIXEL); + } + + return kStatus_Success; +} + +/*! + * brief Start to capture a image. + * + * param base CSI peripheral base address. + * param handle Pointer to the transactional handle. + * param config Pointer to the capture configuration. + * + * retval kStatus_Success Initialize successfully. + * retval kStatus_InvalidArgument Initialize failed because of invalid argument. + */ +status_t CSI_FragModeTransferCaptureImage(CSI_Type *base, + csi_frag_handle_t *handle, + const csi_frag_capture_config_t *config) +{ + assert(NULL != config); + + uint16_t windowWidth; + + /* + * If no special window setting, capture full frame. + * If capture window, then capture 1 one each fragment. + */ + if (config->window != NULL) + { + handle->windowULX = config->window->windowULX; + handle->windowULY = config->window->windowULY; + handle->windowLRX = config->window->windowLRX; + handle->windowLRY = config->window->windowLRY; + handle->linePerFrag = 1; + } + else + { + handle->windowULX = 0; + handle->windowULY = 0; + handle->windowLRX = handle->width - 1U; + handle->windowLRY = handle->height - 1U; + handle->linePerFrag = handle->maxLinePerFrag; + } + + windowWidth = handle->windowLRX - handle->windowULX + 1U; + + if (config->outputGrayScale) + { + /* When output format is gray, the window width must be multiple value of 8. */ + if (windowWidth % 8U != 0U) + { + return kStatus_InvalidArgument; + } + + handle->datBytePerLine = windowWidth; + if (handle->inputFormat == kCSI_FragInputYUYV) + { + handle->copyFunc = CSI_ExtractYFromYUYV; + } + else + { + handle->copyFunc = CSI_ExtractYFromUYVY; + } + } + else + { + handle->datBytePerLine = windowWidth * CSI_FRAG_INPUT_BYTES_PER_PIXEL; + handle->copyFunc = CSI_MemCopy; + } + + handle->dmaCurLine = 0; + handle->outputBuffer = (uint32_t)config->buffer; + handle->datCurWriteAddr = (uint32_t)config->buffer; + + /* Image parameter. */ + base->CSIIMAG_PARA = + (((uint32_t)handle->width * CSI_FRAG_INPUT_BYTES_PER_PIXEL) << CSI_CSIIMAG_PARA_IMAGE_WIDTH_SHIFT) | + ((uint32_t)(handle->linePerFrag) << CSI_CSIIMAG_PARA_IMAGE_HEIGHT_SHIFT); + + /* + * Write to memory from first completed frame. + * DMA base addr switch at dma transfer done. + */ + base->CSICR18 = (base->CSICR18 & ~CSI_CSICR18_MASK_OPTION_MASK) | CSI_CSICR18_MASK_OPTION(0); + + CSI_EnableInterrupts(base, (uint32_t)kCSI_StartOfFrameInterruptEnable | + (uint32_t)kCSI_RxBuffer1DmaDoneInterruptEnable | + (uint32_t)kCSI_RxBuffer0DmaDoneInterruptEnable); + + return kStatus_Success; +} + +/*! + * brief Abort image capture. + * + * Abort image capture initialized by ref CSI_FragModeTransferCaptureImage. + * + * param base CSI peripheral base address. + * param handle Pointer to the transactional handle. + */ +void CSI_FragModeTransferAbortCaptureImage(CSI_Type *base, csi_frag_handle_t *handle) +{ + CSI_Stop(base); + CSI_DisableInterrupts(base, (uint32_t)kCSI_StartOfFrameInterruptEnable | + (uint32_t)kCSI_RxBuffer1DmaDoneInterruptEnable | + (uint32_t)kCSI_RxBuffer0DmaDoneInterruptEnable); +} + +/*! + * brief CSI IRQ handle function. + * + * This function handles the CSI IRQ request to work with CSI driver fragment mode + * APIs. + * + * param base CSI peripheral base address. + * param handle CSI handle pointer. + */ +void CSI_FragModeTransferHandleIRQ(CSI_Type *base, csi_frag_handle_t *handle) +{ + uint32_t csisr = base->CSISR; + uint32_t dmaBufAddr; + uint16_t line; + pvoid_to_u32_t memSrc; + pvoid_to_u32_t memDest; + + /* Clear the error flags. */ + base->CSISR = csisr; + + /* Start of frame, clear the FIFO and start receiving. */ + if (0U != (csisr & (uint32_t)kCSI_StartOfFrameFlag)) + { + /* Reflash the DMA and enable RX DMA request. */ + base->CSICR3 |= (CSI_CSICR3_DMA_REFLASH_RFF_MASK | CSI_CSICR3_DMA_REQ_EN_RFF_MASK); + CSI_Start(base); + handle->dmaCurLine = 0; + handle->datCurWriteAddr = handle->outputBuffer; + } + else if ((csisr & (CSI_CSISR_DMA_TSF_DONE_FB2_MASK | CSI_CSISR_DMA_TSF_DONE_FB1_MASK)) != 0U) + { + if ((csisr & CSI_CSISR_DMA_TSF_DONE_FB1_MASK) == CSI_CSISR_DMA_TSF_DONE_FB1_MASK) + { + dmaBufAddr = base->CSIDMASA_FB1; + } + else + { + dmaBufAddr = base->CSIDMASA_FB2; + } + + if (handle->isDmaBufferCachable) + { + DCACHE_InvalidateByRange(dmaBufAddr, (uint32_t)handle->dmaBytePerLine * (uint32_t)handle->linePerFrag); + } + + /* Copy from DMA buffer to user data buffer. */ + dmaBufAddr += ((uint32_t)handle->windowULX * CSI_FRAG_INPUT_BYTES_PER_PIXEL); + + for (line = 0; line < handle->linePerFrag; line++) + { + if (handle->dmaCurLine + line > handle->windowLRY) + { + /* out of window range */ + break; + } + else if (handle->dmaCurLine + line >= handle->windowULY) + { + memDest.u32 = handle->datCurWriteAddr; + memSrc.u32 = dmaBufAddr; + + handle->copyFunc(memDest.pvoid, memSrc.pvoid, handle->datBytePerLine); + handle->datCurWriteAddr += handle->datBytePerLine; + dmaBufAddr += handle->dmaBytePerLine; + } + else + { + ; /* For MISRA C-2012 Rule 15.7 */ + } + } + + handle->dmaCurLine += handle->linePerFrag; + + if (handle->dmaCurLine >= handle->height) + { + CSI_Stop(base); + CSI_DisableInterrupts(base, (uint32_t)kCSI_StartOfFrameInterruptEnable | + (uint32_t)kCSI_RxBuffer1DmaDoneInterruptEnable | + (uint32_t)kCSI_RxBuffer0DmaDoneInterruptEnable); + + /* Image captured. Stop the CSI. */ + if (NULL != handle->callback) + { + handle->callback(base, handle, kStatus_CSI_FrameDone, handle->userData); + } + } + } + else + { + } +} +#endif /* CSI_DRIVER_FRAG_MODE */ + +#if defined(CSI) +void CSI_DriverIRQHandler(void) +{ + s_csiIsr(CSI, s_csiHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(CSI0) +void CSI0_DriverIRQHandler(void) +{ + s_csiIsr(CSI, s_csiHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +#endif |