/*
 * Copyright 2017-2020 NXP
 * This software is owned or controlled by NXP and may only be used strictly
 * in accordance with the applicable license terms.  By expressly accepting
 * such terms or by downloading, installing, activating and/or otherwise using
 * the software, you are agreeing that you have read, and that you agree to
 * comply with and are bound by, such license terms.  If you do not agree to
 * be bound by the applicable license terms, then you may not retain, install,
 * activate or otherwise use the software.
 */

#include <stdint.h>
#include <stdbool.h>
#include "fsl_sai.h"
#include "fsl_dma.h"
#include "fsl_dmamux.h"
#include "utils.h"
#include "error.h"
#include "board.h"
#include "dma_interface.h"
#include "audio.h"
#include "audio_rx.h"
#include "audio_common.h"
#include "audio_ringbuffer.h"
#include "audio_internal.h"
#include "clock_config.h"
#include "critical.h"

/* ---------------------------------------------------------------------------- */
/* Defines                                                                      */
/* ---------------------------------------------------------------------------- */
/** Audio buffer size */
#define AUDIO_INPUT_RING_BUFFER_SIZE    (2048)
/** Audio Input DMA Configuration type */
#define AUDIO_INPUT_DMA_MODULO          (kDMA_Modulo2KBytes)
/** Audio in buffer half marker */
#define AUDIO_IN_RING_BUFFER_HALF_FILL  (AUDIO_INPUT_RING_BUFFER_SIZE / 2)

/** Current audio Service status */
static uint32_t s_audioService_RxStatus = 0;

/** Set a status flag of the audio Service */
#define AUDIO_SERVICE_SET_STATUS(x)  (s_audioService_RxStatus |= ((x)))

/** Clears a status flag of the audio Service */
#define AUDIO_SERVICE_CLEAR_STATUS(x)  (s_audioService_RxStatus &= ~((x)))

/** Check the current status of the audio Service */
#define AUDIO_SERVICE_CHECK_STATUS(x)  (s_audioService_RxStatus & ((x)))

/* -------------------------------------------------------------------------
 * Local function prototypes
 * ------------------------------------------------------------------------- */

/**
 * @brief Configure Receiver DMA
 *
 * @param DmaChannel DMA Channel id
 * @param DmaLinkChannel DMA link/next channel id
 * @return void.
 */
static void audio_ConfigRxDMA(uint8_t DmaChannel, uint8_t DmaLinkChannel);

/**
 * @brief Configure Receiver Link DMA
 *
 * @param DmaChannel DMA Channel id
 * @return void.
 */
static void audio_ConfigureLinkRxDma(uint8_t DmaChannel);

/**
 * @brief Call back function of DMA Rx
 *
 * @param handle pointer to DMA handle
 * @param userData pointer to Callback userdata
 * @return void.
 */
void audio_DMARxCallback(struct _dma_handle *handle, void *userData);

/* ---------------------------------------------------------------------------- */
/* Local variables                                                              */
/* ---------------------------------------------------------------------------- */

/** ring buffer required to allocate the input samples of the application. */
phRingBuffer_t s_audioService_RingBufferIn;

RAM_1K_ALIGNED_SECTION uint8_t ALIGN(AUDIO_INPUT_RING_BUFFER_SIZE) s_audioService_BufferIn[AUDIO_INPUT_RING_BUFFER_SIZE];

static dma_handle_t s_audioService_DMARxHandle;

static dma_handle_t s_audioService_DMALinkRxHandle;

static dma_transfer_config_t s_audioService_DMARxConfig;

static dma_transfer_config_t s_audioService_DMALinkRxConfig;

static dma_channel_link_config_t s_audioService_DMAChannelRxConfig;

/** Required to control the buffer input receptions */
static uint32_t s_audioService_RxFillThreshold = 0;

/** Buffer required to transfer the data used by the linked channel*/
static uint32_t s_audioService_DMALinkData[2] = { DMA_DSR_BCR_DONE_MASK, RX_AUDIO_DMA_TRANSFER_SIZE };


/* ---------------------------------------------------------------------------- */
/* Local functions-RX                                                           */
/* ---------------------------------------------------------------------------- */

static void AudioRxLatencyDebug(bool receiveAudioFromI2s)
{
    if (g_AudioServiceCtxt.userConfig.enableLatencyDebug) {
        //AUDIO_LatencyDebug_ReceivingAudioFromI2s(receiveAudioFromI2s);
    }
}

/**
 * Configure Rx DMA
 */
static void audio_ConfigRxDMA(uint8_t DmaChannel, uint8_t DmaLinkChannel)
{
    static uint8_t isDmaRxConfigured = false;
    uint32_t I2sDataRegisterAddress;

    if (isDmaRxConfigured == false) {
        dma_ConfigureDMA(&s_audioService_DMARxHandle, DmaChannel, AUDIO_SERVICE_DMA_RX_MUX_SOURCE);
        isDmaRxConfigured = true;
    }

    I2sDataRegisterAddress = SAI_RxGetDataRegisterAddress(AUDIO_SERVICE_I2S_BASEADDR, 0);

    /** Request to move the equivalent of 1ms of audio AUDIO_SERVICE_SYSTEM_SAMPLING_RATE*AUDIO_RX_SAMPLE_SIZE_BYTES*AUDIO_CHANNELS */
    DMA_PrepareTransfer(&s_audioService_DMARxConfig, (void *)I2sDataRegisterAddress, AUDIO_RX_SAMPLE_SIZE_BYTES, \
                        (void *)(&s_audioService_BufferIn[0]), AUDIO_RX_SAMPLE_SIZE_BYTES, RX_AUDIO_DMA_TRANSFER_SIZE,
                        kDMA_PeripheralToMemory);

    DMA_SubmitTransfer(&s_audioService_DMARxHandle, &s_audioService_DMARxConfig, kDMA_NoOptions);

    dma_DisableHwRequest(AUDIO_SERVICE_DMA_BASE, DmaChannel, false);

    /** enable cycle steal to make a sample size transfer on each I2S request */
    DMA_EnableCycleSteal(AUDIO_SERVICE_DMA_BASE, DmaChannel, true);

    /** Modulo only for destination, source is peripheral, hence, disabled */
    DMA_SetModulo(AUDIO_SERVICE_DMA_BASE, DmaChannel, kDMA_ModuloDisable, AUDIO_INPUT_DMA_MODULO);

    /* configure the link channel to enable BCR reload by DMA itself        */
    /* this is required due to bandwidth issues at servicing the DMA ISR    */
    /* and the next I2S request                                             */
    s_audioService_DMAChannelRxConfig.linkType = kDMA_ChannelLinkChannel1AfterBCR0;
    s_audioService_DMAChannelRxConfig.channel1 = DmaLinkChannel;

    DMA_SetChannelLinkConfig(AUDIO_SERVICE_DMA_BASE, DmaChannel, &s_audioService_DMAChannelRxConfig);
}

/**
 * Configure Rx Link DMA
 */
static void audio_ConfigureLinkRxDma(uint8_t DmaChannel)
{
    static uint8_t isRxLinkChannelConfigured = false;
    uint32_t LinkDmaBcrRegisterAddress;

    if (isRxLinkChannelConfigured == false) {
        /** Sets the Callback and Initialize DMA Mux */
        dma_ConfigureLinkDMA(&s_audioService_DMALinkRxHandle, DmaChannel, audio_DMARxCallback);

        isRxLinkChannelConfigured = true;
    }

    /** Gets the DMA Destination address */
    LinkDmaBcrRegisterAddress = dma_GetBcrRegisterAddress(AUDIO_SERVICE_DMA_BASE, AUDIO_SERVICE_DMA_CHANNEL_IN);

    /** Request to move the equivalent of 1ms of audio AUDIO_SERVICE_SYSTEM_SAMPLING_RATE * AUDIO_RX_SAMPLE_SIZE_BYTES * AUDIO_CHANNELS */
    DMA_PrepareTransfer(&s_audioService_DMALinkRxConfig, (void *)(&s_audioService_DMALinkData[0]), sizeof(uint32_t),
                        (void *)LinkDmaBcrRegisterAddress, sizeof(uint32_t), sizeof(s_audioService_DMALinkData), kDMA_MemoryToPeripheral);

    /** Submits the transfer request */
    DMA_SubmitTransfer(&s_audioService_DMALinkRxHandle, &s_audioService_DMALinkRxConfig, kDMA_EnableInterrupt);

    /** Sets the Transfer cycle type False-Continues: True-steps */
    DMA_EnableCycleSteal(AUDIO_SERVICE_DMA_BASE, DmaChannel, false);
}

/**
 * Call back function of DMA Rx
 */
void audio_DMARxCallback(struct _dma_handle *handle, void *userData)
{
    (void)handle;
    (void)userData;

    AudioRxLatencyDebug(true);

    /** re-configure the dma */
    audio_ConfigureLinkRxDma(AUDIO_SERVICE_DMA_CHANNEL_LINK_IN);
    /** update the input ring buffer address */
    critical_Enter(); /* Ring buffer is shared resource between DMA and USB. */
    s_audioService_RingBufferIn.pWritePointer = (uint8_t *)dma_GetDestinationAddress(AUDIO_SERVICE_DMA_BASE, AUDIO_SERVICE_DMA_CHANNEL_IN);
    critical_Exit();
    /** trigger proper events */
    AUDIO_SERVICE_SET_STATUS(AUDIO_SERVICE_RX_COMPLETE_MASK);

    AudioRxLatencyDebug(false);
}

static void audio_RxSpaceWarningCb(uint32_t Filling, void *pCtxt)
{
    (void)Filling;    /* make release build happy */
    (void)pCtxt;      /* make release build happy */

    if (AUDIO_SERVICE_CHECK_STATUS(AUDIO_SERVICE_RX_STARTED_MASK)) {
        /* Restart the audio */
        audio_StopRx();
        /*
         * Don't call audio_StartRx().
         * The audio will be automatically started when the
         * ring buffer filling threshold is reached.
         */
        PDBG("Restart RX Audio");
    }
    PDBG("RxSpaceWarning, F=%d", Filling);
}

inline static sai_master_slave_t audio_GetMasterSlave(audioCtrlFormat_t audioFormat)
{
    return (audioFormat == AUDIO_SERVICE_FORMAT_I2S_MASTER) ? kSAI_Master : kSAI_Slave;
}

/* ---------------------------------------------------------------------------- */
/* Public Functions-RX                                                          */
/* ---------------------------------------------------------------------------- */
/**
 * Initializes and starts the I2S-Rx as part of audio-SAI
 */
void audio_InitRx(void)
{
    sai_transceiver_t audioService_RxConfig = { 0 };
    /* I2S slave in asynch mode */
    SAI_GetClassicI2SConfig(&audioService_RxConfig,
                            AUDIO_IF_WORD_LENGTH_16_BITS,
                            kSAI_MonoLeft,
                            kSAI_Channel0Mask);

    audioService_RxConfig.masterSlave = audio_GetMasterSlave(g_AudioServiceCtxt.userConfig.audioFormat);
    audioService_RxConfig.syncMode = kSAI_ModeSync;
    SAI_RxSetConfig(AUDIO_SERVICE_I2S_BASEADDR, &audioService_RxConfig);

    audio_SetRxI2sConfig(g_AudioServiceCtxt.userConfig.audioIfWordLength, audioService_RxConfig.masterSlave);

    SAI_RxSetChannelFIFOMask(AUDIO_SERVICE_I2S_BASEADDR, 0);

    SAI_RxEnable(AUDIO_SERVICE_I2S_BASEADDR, true);

    audio_ConfigRxDMA(AUDIO_SERVICE_DMA_CHANNEL_IN, AUDIO_SERVICE_DMA_CHANNEL_LINK_IN);

    audio_ConfigureLinkRxDma(AUDIO_SERVICE_DMA_CHANNEL_LINK_IN);

    AUDIO_SERVICE_SET_STATUS(AUDIO_SERVICE_RX_COMPLETE_MASK);
}

void audio_StartRx(void)
{
    /** Start the DMA and the SAI RX */
    DMA_EnableChannelRequest(AUDIO_SERVICE_DMA_BASE, AUDIO_SERVICE_DMA_CHANNEL_IN);
    SAI_RxEnableDMA(AUDIO_SERVICE_I2S_BASEADDR, kSAI_FIFOWarningDMAEnable, true);
    SAI_RxSetChannelFIFOMask(AUDIO_SERVICE_I2S_BASEADDR, 1);
}

void audio_ReadBuffer(uint8_t *pOutBuffer, uint16_t NbSamples)
{
    s_audioService_RxFillThreshold += (NbSamples * AUDIO_RX_SAMPLE_SIZE_BYTES);

    critical_Enter(); /* Ring buffer is shared resource between DMA and USB. */
    audio_ringbuffer_Read(&s_audioService_RingBufferIn, (uint8_t *)pOutBuffer, (NbSamples * AUDIO_RX_SAMPLE_SIZE_BYTES));
    critical_Exit();

    /** Check if Rx has not already started, Cannot proceed if RX has started*/
    if (!AUDIO_SERVICE_CHECK_STATUS(AUDIO_SERVICE_RX_STARTED_MASK)) {
        AUDIO_SERVICE_SET_STATUS(AUDIO_SERVICE_RX_COMPLETE_MASK);
        /** check if the I2S Rx is enabled, if not, that means the ring buffer needs to be filled up to 50% */
        if (s_audioService_RxFillThreshold >= AUDIO_IN_RING_BUFFER_HALF_FILL) {
            audio_StartRx();
            /** set the internal flags */
            AUDIO_SERVICE_SET_STATUS(AUDIO_SERVICE_RX_STARTED_MASK);
            AUDIO_SERVICE_CLEAR_STATUS(AUDIO_SERVICE_RX_COMPLETE_MASK);
        }
    }
}

void audio_StopRx(void)
{
    /** resets the audio buffer address to start*/
    critical_Enter(); /* Ring buffer is shared resource between DMA and USB. */
    audio_ringbuffer_Reset(&s_audioService_RingBufferIn);
    critical_Exit();
    s_audioService_RxFillThreshold = 0;
    /** Disables the Rx DMA */
    SAI_RxEnableDMA(AUDIO_SERVICE_I2S_BASEADDR, kSAI_FIFOWarningDMAEnable, false);
    /** Disables the Rx data channel */
    SAI_RxSetChannelFIFOMask(AUDIO_SERVICE_I2S_BASEADDR, 0);
    /** Aborts DMA transfer */
    DMA_AbortTransfer(&s_audioService_DMARxHandle);
    /** Re-Configure Rx DMA */
    audio_ConfigRxDMA(AUDIO_SERVICE_DMA_CHANNEL_IN, AUDIO_SERVICE_DMA_CHANNEL_LINK_IN);
    /** Re-Configure Rx Link DMA */
    audio_ConfigureLinkRxDma(AUDIO_SERVICE_DMA_CHANNEL_LINK_IN);
    /** Resets the internal receiver logic including the FIFO pointers */
    SAI_RxSoftwareReset(AUDIO_SERVICE_I2S_BASEADDR, kSAI_ResetAll);
    /** Resets the AudioService status */
    AUDIO_SERVICE_CLEAR_STATUS(AUDIO_SERVICE_RX_STARTED_MASK);
    AUDIO_SERVICE_SET_STATUS(AUDIO_SERVICE_RX_COMPLETE_MASK);
}

uint32_t audio_RxAvailableSpace(void)
{
    uint32_t Filling;

    critical_Enter(); /* Ring buffer is shared resource between DMA and USB. */
    Filling = audio_ringbuffer_GetFilling(&s_audioService_RingBufferIn);
    critical_Exit();

    /* return the rx available space */
    return Filling;
}

uint32_t audio_GetRxBufferSize(void)
{
    /** returns the current buffer size */
    return((uint32_t)s_audioService_RingBufferIn.BufferSize);
}

void audio_ResetRx(void)
{
    SAI_RxSoftwareReset(AUDIO_SERVICE_I2S_BASEADDR, kSAI_ResetTypeSoftware);
}

void audio_RingbufferRxInit(void)
{
    /** Initialize the ring buffer for rx */
    critical_Enter(); /* Ring buffer is shared resource between DMA and USB. */
    audio_ringbuffer_Init(&s_audioService_RingBufferIn, (uint8_t *)(&s_audioService_BufferIn[0]), sizeof(s_audioService_BufferIn));
    critical_Exit();

    audio_ringbuffer_ConfigureFillingWarning(
        &s_audioService_RingBufferIn,
        RX_AUDIO_DMA_TRANSFER_SIZE,
        audio_RxSpaceWarningCb,
        NULL);
}

error_t audio_SetRxI2sConfig(audioIfWordLength_t audioIfWordLength, sai_master_slave_t masterSlave)
{
    uint8_t wordLength;
    switch (audioIfWordLength) {
        case AUDIO_SERVICE_AUDIO_IF_WORD_LENGTH_32_BITS:
            wordLength = AUDIO_IF_WORD_LENGTH_32_BITS;
            break;
        case AUDIO_SERVICE_AUDIO_IF_WORD_LENGTH_16_BITS:
            wordLength = AUDIO_IF_WORD_LENGTH_16_BITS;
            break;
        default:
            return kERROR_Fail;
            break;
    }

    /* RX is Mono.
     * To keep the clock rate same between tx and rx,
     * we keep the channel number 2
     */
    SAI_RxSetBitClockRate(AUDIO_SERVICE_I2S_BASEADDR,
                          //BOARD_EXT_XTAL_CLK_HZ,
                          48000000,
                          (AUDIO_SERVICE_SYSTEM_SAMPLING_RATE * 1000),
                          wordLength,
                          RX_AUDIO_CHANNELS);

    sai_serial_data_t config = {
        .dataOrder = kSAI_DataMSB,
        .dataWord0Length = wordLength,
        .dataWordNLength = wordLength,
        .dataFirstBitShifted = AUDIO_IF_WORD_LENGTH_16_BITS, /* always 16bits on the back channel */
        .dataWordNum = AUDIO_I2S_FRAME_SIZE,
        .dataMaskedWord = kSAI_MonoLeft,
    };
    SAI_RxSetSerialDataConfig(AUDIO_SERVICE_I2S_BASEADDR, &config);

    sai_frame_sync_t frameSyncConfig = {
        .frameSyncWidth = wordLength,
        .frameSyncEarly = true,
        .frameSyncPolarity = kSAI_PolarityActiveLow
    };
    SAI_RxSetFrameSyncConfig(AUDIO_SERVICE_I2S_BASEADDR, masterSlave, &frameSyncConfig);

    return kERROR_Ok;
}

/** @} */
