/*********************************************************************************

Copyright(c) 2005 Analog Devices, Inc. All Rights Reserved.

This software is proprietary and confidential.  By using this software you agree
to the terms of the associated Analog Devices License Agreement.

*********************************************************************************/
/**************************************************************************************************

Description:    Configures the audio mode through the Audio EZ-Extender virtual driver.
                The audio data is received from the ADC and stored into the buffer iADCdata. 
                The output data is transmitted from the buffer iDACdata.
                One circular buffer, rxBuffer, is provided for the ADC data, and another circular 
                buffer, txBuffer, is for the DAC data. A double buffering scheme 
                is implemented so that data in one half is available for processing while the other 
                half is used for DMA operations.

**************************************************************************************************/

/*********************************************************************

Include files

*********************************************************************/

#include <stdio.h>
#include <services\services.h>
#include <drivers\adi_dev.h>
#include "Talkthrough.h"
#include "AudioDriver.h"
#include "adi_AudioEZExtender.h"

/*********************************************************************

Prototypes

*********************************************************************/
// Audio EZ-Extender operation mode index
#define ANALOG_MODE         5   // This is the only MODE supported by the AD1938 driver right now.
                                // TDM 48khz 8 channel per SPORT primary/secondary each
#define DIGITAL_MODE        6   // Not used currently.  Requires an ADAV801 driver.


/*********************************************************************

Local variables

*********************************************************************/

// handle to the Audio EZ-Extneder driver
static ADI_DEV_DEVICE_HANDLE    DriverHandle;

// device manager handle
static ADI_DEV_MANAGER_HANDLE   DevMgrHandle;    // handle for the device manager

// DMA manager handle
static ADI_DMA_MANAGER_HANDLE   DMAMgrHandle;    // handle for the DMA manager

// circular buffer for processing SPORT1 TDM RX/TX data
static ADI_DEV_CIRCULAR_BUFFER   rxBuffer;
static ADI_DEV_CIRCULAR_BUFFER   txBuffer;
#if (ADAV801_SUPPORT)
// circular buffer for processing SPORT0 I2S RX/TX data
static ADI_DEV_CIRCULAR_BUFFER   rxBuffer2;
static ADI_DEV_CIRCULAR_BUFFER   txBuffer2;
#endif

// interrupt manager memory (base memory + no secondary handler)
static u8 IntMgrMem[(0 * ADI_INT_SECONDARY_MEMORY)];

// storage for DMA manager memory (2 channel(RX/TX) usage)
static u8 DMAMgrMem[ADI_DMA_BASE_MEMORY + (2 * ADI_DMA_CHANNEL_MEMORY)];

#if (ADAV801_SUPPORT)
// device manager memory (base memory + 6 device memory (Audio EZ-Extender, AD1938A&B, ADAV801, SPI, SPORT1, SPORT0)
static u8 DevMgrMem[ADI_DEV_BASE_MEMORY + (6 * ADI_DEV_DEVICE_MEMORY)];
#else
// device manager memory (base memory + 4 device memory (Audio EZ-Extender, AD1938A&B, SPI, SPORT1)
static u8 DevMgrMem[ADI_DEV_BASE_MEMORY + (4 * ADI_DEV_DEVICE_MEMORY)];
#endif

// storage for critical region data
static ADI_INT_CRITICAL_REGION_DATA     CriticalRegionData;

// storage for audio PCM data
static int iADCdata[TDM_FRAME_SIZE * TICK_SAMPLE_SIZE * 2];  // 2: double buffering
static int iDACdata[TDM_FRAME_SIZE * TICK_SAMPLE_SIZE * 2];  // 2: double buffering

/*********************************************************************

Exported buffers

*********************************************************************/

// pointer array that contains base channel pointer of iDACdata for each portion of 2 buffer
int* pOutputSampleBasePtr[VALID_OUTPUT_CHANNEL_SIZE * 2]; // 2: double buffering

// pointer array that contains base channel pointer of iADCdata for each portion of 2 buffer
int* pInputSampleBasePtr[VALID_INPUT_CHANNEL_SIZE * 2];   // 2: double buffering

// single processing buffer for DSP modules
int ProcessingBuffer[VALID_OUTPUT_CHANNEL_SIZE][TICK_SAMPLE_SIZE] = {0};


/*********************************************************************

    Function:       ExceptionHandler
    
    Description:    We should never get an exception,
                    but just in case we'll catch them and simply turn
                    on all the LEDS should one ever occur.

*********************************************************************/
static ADI_INT_HANDLER(ExceptionHandler)
{
    return (ADI_INT_RESULT_PROCESSED);
}

/*********************************************************************

    Function:       HWErrorHandler
    
    Description:    We should never get a hardware error,
                    but just in case we'll catch them and simply turn
                    on all the LEDS should one ever occur.

*********************************************************************/
static ADI_INT_HANDLER(HWErrorHandler)
{
    return (ADI_INT_RESULT_PROCESSED);
}


/*********************************************************************

    Function:       resolveChannelPointers
    
    Description:    To simplify the process of reformatting DMA ADC or DAC data
                    into/from the working buffer, it assign each element
                    of an pointer array with valid base channel pointers
                    to iDACdata and iADCdata buffer.
    
*********************************************************************/

// initialize the pointers to the DMA ADC/DAC buffer for double buffering
static void resolveChannelPointers(void)
{
    int i;
    
    //=====================================================
    // Output channels
    //=====================================================
    // Analog channels
    pOutputSampleBasePtr[0] = &iDACdata[INTERNAL_DAC_L1];
    pOutputSampleBasePtr[1] = &iDACdata[INTERNAL_DAC_R1];
    pOutputSampleBasePtr[2] = &iDACdata[INTERNAL_DAC_L2];
    pOutputSampleBasePtr[3] = &iDACdata[INTERNAL_DAC_R2];
    pOutputSampleBasePtr[4] = &iDACdata[INTERNAL_DAC_L3];
    pOutputSampleBasePtr[5] = &iDACdata[INTERNAL_DAC_R3];
    pOutputSampleBasePtr[6] = &iDACdata[INTERNAL_DAC_L4];
    pOutputSampleBasePtr[7] = &iDACdata[INTERNAL_DAC_R4];
    
    pOutputSampleBasePtr[8] = &iDACdata[INTERNAL_DAC_L5];
    pOutputSampleBasePtr[9] = &iDACdata[INTERNAL_DAC_R5];
    pOutputSampleBasePtr[10] = &iDACdata[INTERNAL_DAC_L6];
    pOutputSampleBasePtr[11] = &iDACdata[INTERNAL_DAC_R6];
    pOutputSampleBasePtr[12] = &iDACdata[INTERNAL_DAC_L7];
    pOutputSampleBasePtr[13] = &iDACdata[INTERNAL_DAC_R7];
    pOutputSampleBasePtr[14] = &iDACdata[INTERNAL_DAC_L8];
    pOutputSampleBasePtr[15] = &iDACdata[INTERNAL_DAC_R8];
#if (ADAV801_SUPPORT)
    // Digital channels
    pOutputSampleBasePtr[16] = &iDACdata[INTERNAL_SPDIF_OUT_L];
    pOutputSampleBasePtr[17] = &iDACdata[INTERNAL_SPDIF_OUT_R];
#endif
    // set up the buffer pointers for the second half of the double buffer.
    for (i = 0; i < VALID_OUTPUT_CHANNEL_SIZE; i++)
        pOutputSampleBasePtr[VALID_OUTPUT_CHANNEL_SIZE + i] = 
            pOutputSampleBasePtr[i] + (TDM_FRAME_SIZE * TICK_SAMPLE_SIZE);
            
    //=====================================================
    // Input channels
    //=====================================================            
    // Analog channels
    pInputSampleBasePtr[0] = &iADCdata[INTERNAL_ADC_L1];
    pInputSampleBasePtr[1] = &iADCdata[INTERNAL_ADC_R1];
    pInputSampleBasePtr[2] = &iADCdata[INTERNAL_ADC_L2];
    pInputSampleBasePtr[3] = &iADCdata[INTERNAL_ADC_R2];
    pInputSampleBasePtr[4] = &iADCdata[INTERNAL_ADC_L3];
    pInputSampleBasePtr[5] = &iADCdata[INTERNAL_ADC_R3];
    pInputSampleBasePtr[6] = &iADCdata[INTERNAL_ADC_L4];
    pInputSampleBasePtr[7] = &iADCdata[INTERNAL_ADC_R4];
#if (ADAV801_SUPPORT)
    // Digital channels
    pInputSampleBasePtr[8] = &iADCdata[INTERNAL_SPDIF_IN_L];
    pInputSampleBasePtr[9] = &iADCdata[INTERNAL_SPDIF_IN_R];
#endif
    // set up the buffer pointers for the second half of the double buffer.
    for (i = 0; i < VALID_INPUT_CHANNEL_SIZE; i++)
        pInputSampleBasePtr[VALID_INPUT_CHANNEL_SIZE + i] = 
            pInputSampleBasePtr[i] + (TDM_FRAME_SIZE * TICK_SAMPLE_SIZE);

}

/*********************************************************************
*
*	Function:	    InitSystem
*
*   Description:    This function initialize all the required system
*                   service routines and the device manager.
*               
*********************************************************************/
static void InitSystem(void)
{
    unsigned int result;
    u32 ResponseCount;

    // command pair for configuring the Power Manager
    ADI_PWR_COMMAND_PAIR        PwrMgrConfigTable[] =
    {
        { ADI_PWR_CMD_SET_EZKIT, (void *)PWR_MANGER_EZKIT_PRESET },// configuration for the BlackFin EzKit
        { ADI_PWR_CMD_END,              NULL },
    };
    
    // initialize the Interrupt Manger
    result = adi_int_Init(IntMgrMem,    // pointer to the memory used by the Interrupt Manger
                sizeof(IntMgrMem),      // size of the memory used by the Interrupt Manger
                &ResponseCount,         // response count of the instance
                NULL);                  // NULL for critical section argument
    if (result != ADI_DEV_RESULT_SUCCESS) printf("INT_MGR init failed!\n");
    
    // initialize the Power Manger
    result = adi_pwr_Init(PwrMgrConfigTable);
    if (result != ADI_DEV_RESULT_SUCCESS) printf("PWR_MGR init failed!\n");
    
    // set the PLL frequency to maximum possible
    result = adi_pwr_SetFreq(   0,      // Core clock frequency (MHz)
                                0,      // System clock frequency (MHz) 
                                ADI_PWR_DF_NONE);   // let the module decide for the PLL input divider
    
    // initialize DMA Manger
    result = adi_dma_Init(DMAMgrMem,    // pointer to the memory used by the DMA Manager
                sizeof(DMAMgrMem),      // size of the memory used by the DMA Manager
                &ResponseCount,         // response count of the instance
                &DMAMgrHandle,          // pointer to the DMA Manger handle
                NULL);                  // NULL for critical section argument
    if (result != ADI_DEV_RESULT_SUCCESS) printf("DMA init failed!\n");
    
    // hook the exception interrupt
    adi_int_CECHook(3, ExceptionHandler, NULL, FALSE) ;
    
    // hook the hardware error
    adi_int_CECHook(5, HWErrorHandler, NULL, FALSE); 
#if (SEPARATE_THREAD_FOR_RENDER)
    // hook user ISR0
    adi_int_CECHook(USER_ISR0, FWRender, NULL, FALSE);
#endif
    // initialize Device Manager    
    result = adi_dev_Init(DevMgrMem,    // pointer to memory use by Device Manager
                    sizeof(DevMgrMem),  // size of memory for use by Device Manager
                    &ResponseCount,     // response count of instances
                    &DevMgrHandle,      // pointer to Device Manager handle
                    &CriticalRegionData);   // global critical section argument
    if (result != ADI_DEV_RESULT_SUCCESS) printf("DEV_MGR init failed!\n");
}

/*********************************************************************
*
*	Function:	    InitAudioDriver
*
*   Description:    This function open the Audio EZ-Extender device instance.
*                   It uses the device instance to initialize registers in the AD1938A&B and ADAV801
*                   codecs, and configures them to the TDM 8 channel transferring mode.
*                   Two buffers (RX and TX) are required and configured 
*                   for the 2-dimensional DMA transfer buffer allowing double buffering.
*               
*********************************************************************/
static void InitAudioDriver(void)
{
    int i;
    unsigned int result;
    u32 ResponseCount;
    
     *pPORTFIO_DIR = 0x0000;
    *pPORTFIO_INEN = 0x0000;
    *pPORTFIO_CLEAR = (0xFFFF);
   
    
    // open Audio EZ-Extender driver
    result = adi_dev_Open(DevMgrHandle,
                    &ADIAudioEZExtenderEntryPoint,  // entry point for the instance API
                    0,                              // device instance index (CODEC A&B)
                    NULL,                           // client handle callback identifier
                    &DriverHandle,                  // device handle for AD1938
                    ADI_DEV_DIRECTION_BIDIRECTIONAL,// direction for AD1938A&B
                    DMAMgrHandle,                   // DMA handle for SPORT
                    NULL,                           // Doesn't use deferred callback handle
                    TalkthroughCallback);           // client's callback function from SPORT-DMA
    if (result != ADI_DEV_RESULT_SUCCESS) printf("Failed to open Audio EZ-Extender!\n");
    
    // configure the Audio EZ-Extender operation mode
    result = adi_dev_Control(DriverHandle, 
                    ADI_AUDIOEZEXTENDER_CMD_SET_OPERATION_MODE, 
                    (void*)((ANALOG_MODE << 16) | (DIGITAL_MODE << 0))); // analog + digital mode together
    if (result != ADI_DEV_RESULT_SUCCESS) printf("Audio EZ-Extender cmd-set-mode failed!\n"); 
    
   
    /******************* Buffer preparation ***********************************************/	
    // Make sure the CallbackParameter as non-null to get the callback function working
    // This is only needed for RX
    
    rxBuffer.Data = &iADCdata[0];   // point to DMA RX buffer
    rxBuffer.SubBufferCount = 2;    // current DMA only supports 2D
    rxBuffer.SubBufferElementCount = TDM_FRAME_SIZE * TICK_SAMPLE_SIZE; // size of the first dimension of the DMA buffer
    rxBuffer.ElementWidth = 4;
    rxBuffer.CallbackType = ADI_DEV_CIRC_SUB_BUFFER;   // get interrupt/callback at each half of the buffer
    rxBuffer.pAdditionalInfo = NULL;
    
    txBuffer.Data = &iDACdata[0];   // point to DMA TX buffer
    txBuffer.SubBufferCount = 2;    // current DMA only supports 2D
    txBuffer.SubBufferElementCount = TDM_FRAME_SIZE * TICK_SAMPLE_SIZE; // size of the first dimension of the DMA buffer
    txBuffer.ElementWidth = 4;
    txBuffer.CallbackType = ADI_DEV_CIRC_NO_CALLBACK;   // No callback
    txBuffer.pAdditionalInfo = NULL;

#if (ADAV801_SUPPORT)
    // buffer perparation for digital codec is not supported for now
#endif
    
    // redirect device selection to the ad1938 codec
    result = adi_dev_Control(DriverHandle, 
                    ADI_AUDIOEZEXTENDER_CMD_DEVICE_SELECT,
                    (void*)ADI_AD1938);
    if (result != ADI_DEV_RESULT_SUCCESS) printf("Audio EZ-Extender device selection failed!\n"); 
    
    			
    // configure the Audio EZ-Extender dataflow method to circular(autobuffer) mode
    result = adi_dev_Control(DriverHandle, ADI_DEV_CMD_SET_DATAFLOW_METHOD, (void*)ADI_DEV_MODE_CIRCULAR);
    
    // assign rxBuffer and txBuffer for the Audio EZ-Extender driver (AD1938 codec)
    result = adi_dev_Read(DriverHandle, ADI_DEV_CIRC, (ADI_DEV_BUFFER *)&rxBuffer);
    result = adi_dev_Write(DriverHandle, ADI_DEV_CIRC, (ADI_DEV_BUFFER *)&txBuffer);

#if (ADAV801_SUPPORT)
    // buffer perparation for digital codec is not supported for now
    // redirect device selection to the ADAV801 codec
    result = adi_dev_Control(DriverHandle, 
                    ADI_AUDIOEZEXTENDER_CMD_DEVICE_SELECT,
                    (void*)ADI_ADAV801);
    if (result != ADI_DEV_RESULT_SUCCESS) printf("Audio EZ-Extender device selection failed!\n"); 
    
    // give the Audio EZ-Extender driver (ADAV801 codec) with the rxBuffer and txBuffer to work with.
    // buffer perparation for digital codec is not supported for now
#endif

    /******************* End of Buffer preparation ***********************************************/	
    
    // start the data flow
    result = adi_dev_Control(DriverHandle, ADI_DEV_CMD_SET_DATAFLOW, (void*)TRUE);
}


/*********************************************************************
*
*   Function:   Close_Audio
*
*********************************************************************/
// Never called since it runs as infinite loop
void Close_Audio(void)
{
    // disable data flow
    adi_dev_Control(DriverHandle, ADI_DEV_CMD_SET_DATAFLOW, (void*)FALSE);
    
    // close the device
    adi_dev_Close(DriverHandle);
}


/*********************************************************************
*
*   Function:   Init_Audio
*
*********************************************************************/
void Init_Audio(void)
{
    // initialize pointer array which is used in Audio callback function
    resolveChannelPointers();

    // initialize system services and device manager
    InitSystem();

    // initialize AudioEZExtender and run talkthrough mode
    InitAudioDriver();
}
