#include <stdlib.h>
#include <stdio.h>
#include "ti_drivers_config.h"
#include "ti_board_config.h"

#include <drivers/i2c.h>
#include <drivers/gpio.h>
#include <kernel/dpl/AddrTranslateP.h>
#include <kernel/dpl/DebugP.h>
#include "ti_drivers_open_close.h"
#include "ti_board_open_close.h"
#include <drivers/hw_include/cslr_soc.h>

#include <kernel/dpl/DebugP.h>
#include <kernel/dpl/ClockP.h>
#include <kernel/dpl/AddrTranslateP.h>
#include <kernel/dpl/HwiP.h>
#include "ti_drivers_config.h"
#include "ti_drivers_open_close.h"
#include "ti_board_open_close.h"
#include <drivers/sciclient/include/tisci/tisci_protocol.h>
#include <drivers/sciclient/include/tisci/rm/tisci_rm_irq.h>
#include <drivers/soc/am64x_am243x/soc.h>
#include "hw_gpmc.h"

// TPIC2810 registers
#define TPIC2810_CMD_RD                 (0x11U)
#define TPIC2810_CMD_WR_SHIFT_REG       (0x11U)
#define TPIC2810_CMD_LOAD_OUTPUT        (0x22U)
#define TPIC2810_CMD_WR_IMMEDIATE       (0x44U)


// GPMC memory space address
#define GPMC_MEM_ADDRESS                0x340000UL

// gpio 0 pin definitions
#define TIMING_PIN                      16      // set gpio 16 (chip pin d4, mcu header pin 11) to output to indicate time interval start/end or start of irq handler
#define IRQ_PIN                         15      // set gpio 15 (chip pin e5, mcu header pin 10) to output to use to trigger interrupt on rising edge

typedef enum {
    Initialize,
    Shutdown
} Transition;

typedef enum {
    GPIOTest    = 0x0001,
    I2CTest     = 0x0002,
    DMATest     = 0x0004,
    GPMCTest    = 0x0008,
    DDRTest     = 0x0010,
    MEMTest     = 0x0020,
    IRQTest     = 0x0040,
    DMARegTest  = 0x0080,
    GPMCRegTest = 0x0100
} TestMask;

volatile uint32_t   gpioIntrDone = 0;

//extern void testI2cLed( int iterations );
       void driverInit( Transition direction );
       void runTests( TestMask testMask );
       void gpioTest( void );
       void ddrSpeedTest( void );
       void memSpeedTest( void );
       void dmaRegTest( void );
       void irqTest( void );
       void gpmcRegTest( void );

static void isrHandler( void *args );
static void Sciclient_gpioIrqSet( void );

// ************************************************************
//
// main - program entry point, initialize/shutdown system/board/driver & call tests
//
// ************************************************************

int main()
{
    TestMask testMask;

    testMask  = 0;
 //   testMask |= GPIOTest;
 //   testMask |= MEMTest;
 //   testMask |= DMARegTest;
    testMask |= GPMCRegTest;
 //   testMask |= IRQTest;
 //   testMask |= DDRTest;
 //   testMask |= I2CTest;
//    testMask |= DMATest;
//    testMask |= GPMCTest;

    // all initialiation occurs here
    driverInit( Initialize );

    // perform enabled tests
    runTests( testMask );

    // shutodown and exit
    driverInit( Shutdown );
    return 0;
}

// ************************************************************
//
// driverInit - program entry point, initialize/shutdown system/board/driver & call tests
//
// ************************************************************

void driverInit( Transition direction )
{
    switch( direction ) {
        case Initialize:
            System_init();
            Board_init();
            Drivers_open();
            Board_driversOpen();
            break;

        case Shutdown:
            Board_driversClose();
            Drivers_close();
            Board_deinit();
            System_deinit();
            break;

        default:
            DebugP_logError( "unknown configuration operation\n" );
    }
}

// ************************************************************
//
// runTests - perform enabled tests
//
// ************************************************************

void runTests( TestMask testMask )
{
    if( testMask & GPIOTest ) {
        gpioTest();
    }

    if( testMask & DMARegTest ) {
        dmaRegTest();
    }

    if( testMask & GPMCRegTest ) {
        gpmcRegTest();
    }

    if( testMask & IRQTest ) {
        irqTest();
    }

    if( testMask & DDRTest ) {
        ddrSpeedTest();
    }

    if( testMask & MEMTest ) {
        memSpeedTest();
    }

    if( testMask & I2CTest ) {
//        testI2cLed( 8 );
    }

    if( testMask & DMATest ) {
//        dmaTest();
    }

    if( testMask & GPMCTest ) {
 //       GPMCTest();
    }
}

// ************************************************************
//
// gpioTest - determine maximum i/o rate
//
// ************************************************************

void gpioTest( void )
{
    uint32_t gpioBaseAddr, pinNum;

    // translate mcu_gpio0 base addr via RAT
    gpioBaseAddr = (uint32_t) AddrTranslateP_getLocalAddr( CSL_MCU_GPIO0_BASE );
    pinNum       = TIMING_PIN;

    // set gpio 15 (chip pin e5, mcu header pin 10) to output, then drive at max rate
    // possible via sdk functions
    GPIO_setDirMode( gpioBaseAddr, pinNum, GPIO_DIRECTION_OUTPUT );
    while( 1 ) {
        GPIO_pinWriteHigh( gpioBaseAddr, pinNum );
        GPIO_pinWriteLow( gpioBaseAddr, pinNum );
    }
}

// ************************************************************
//
// dmaRegTest - test dma control register i/o rate
//
// ************************************************************

void dmaRegTest( void )
{
    uint32_t *dmaBasePtr;
    uint32_t gpioBaseAddr;
    uint32_t pinNum;
    uint32_t value;
    uint32_t trial;
    bool     state;

    // get bsae address of ddr
    dmaBasePtr = (uint32_t *) AddrTranslateP_getLocalAddr( CSL_DMASS0_BCDMA_BCHAN_BASE );

    // translate mcu_gpio0 base addr via RAT
    gpioBaseAddr = (uint32_t) AddrTranslateP_getLocalAddr( CSL_MCU_GPIO0_BASE );
    pinNum       = TIMING_PIN;

    // set gpio to output for timing measurements
    GPIO_setDirMode( gpioBaseAddr, pinNum, GPIO_DIRECTION_OUTPUT );

    state = true;
    while( 1 ) {
        if( state )
            GPIO_pinWriteHigh( gpioBaseAddr, pinNum );
        else
            GPIO_pinWriteLow( gpioBaseAddr, pinNum );

        for( trial = 0 ; trial < 8196 ; trial++ ) {
            value = *dmaBasePtr;
        }

        state = !state;
    }
}

// ************************************************************
//
// gpmcRegTest - configure & test gpmc memory bus cycles
//
// ************************************************************

void gpmcRegTest( void )
{
    volatile uint32_t *gpmcDataAddr;
    uint32_t gpmcBaseAddr;
    uint32_t value;

    // turn on the gpmc clock - no register access until this is done
    SOC_moduleClockEnable( TISCI_DEV_GPMC0, 1 );

    // get base address of gpmc using RAT translation
    gpmcBaseAddr = (uint32_t) AddrTranslateP_getLocalAddr( CSL_GPMC0_CFG_BASE );

    // get gpmc status, revision
    value = HW_RD_REG32( gpmcBaseAddr + GPMC_STATUS   );
    value = HW_RD_REG32( gpmcBaseAddr + GPMC_REVISION );

    // reset
    HW_WR_REG32(  gpmcBaseAddr + GPMC_SYSCONFIG, GPMC_SYSCONFIG_SOFTRESET_RESET << GPMC_SYSCONFIG_SOFTRESET_SHIFT );
    CSL_REG32_WR( gpmcBaseAddr + GPMC_SYSCONFIG, GPMC_SYSCONFIG_SOFTRESET_RESET << GPMC_SYSCONFIG_SOFTRESET_SHIFT );

    // wait until reset complete
    while( ( HW_RD_REG32( gpmcBaseAddr + GPMC_SYSSTATUS ) & GPMC_SYSSTATUS_RESETDONE ) != GPMC_SYSSTATUS_RESETDONE_RSTDONE );

    // config1 register settings (bus behavior)
    value = ( GPMC_CONFIG1_0_READMULTIPLE_RDMULTIPLE  << GPMC_CONFIG1_0_READMULTIPLE_SHIFT        )   // enable read burst mode if readtype is set for synchronous
          | ( GPMC_CONFIG1_0_READTYPE_RDSYNC          << GPMC_CONFIG1_0_READTYPE_SHIFT            )   // selects read synchronous
          | ( GPMC_CONFIG1_0_WRITEMULTIPLE_WRMULTIPLE << GPMC_CONFIG1_0_WRITEMULTIPLE_SHIFT       )   // enable write burst mode if writetype is set for synchronous
          | ( GPMC_CONFIG1_0_WRITETYPE_WRSYNC         << GPMC_CONFIG1_0_WRITETYPE_SHIFT           )   // selects write synchronous
          | ( GPMC_CONFIG1_0_DEVICESIZE_SIXTEENBITS   << GPMC_CONFIG1_0_DEVICESIZE_SHIFT          )   // attached device is 16 bits
          | ( GPMC_CONFIG1_0_DEVICETYPE_NORLIKE       << GPMC_CONFIG1_0_DEVICETYPE_SHIFT          )   // attached device NOR-like (random r/w)
          | ( GPMC_CONFIG1_0_MUXADDDATA_MUX           << GPMC_CONFIG1_0_MUXADDDATA_SHIFT          )   // address/data is muxed
          | ( GPMC_CONFIG1_0_TIMEPARAGRANULARITY_X2   << GPMC_CONFIG1_0_TIMEPARAGRANULARITY_SHIFT )   // ext clock is x2 states
          | ( GPMC_CONFIG1_0_GPMCFCLKDIVIDER_DIVBY4   << GPMC_CONFIG1_0_GPMCFCLKDIVIDER_SHIFT     );  // divide fclk by 4 (lets start slow)
    HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG1(0), value );

    // config2 register settings (cs timings)
//  value = ;
//  HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG2(0), value );

    // config3 register settings (adv timings)
//  value = ;
//  HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG3(0), value );

    // config4 register settings (we/oe timings)
//  value = ;
//  HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG4(0), value );

    // config5 register settings (rd/wr timings)
//  value = ;
//  HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG5(0), value );

    // config6 register settings (wr/cycle timings)
//  value = ;
//  HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG6(0), value );

    // config7 register settings (cs/mask/base addr)
    value = ( 0x000000UL & GPMC_CONFIG7_0_BASEADDRESS )
          | ( GPMC_CONFIG7_0_CSVALID_CSENABLED << GPMC_CONFIG7_0_CSVALID_SHIFT );
    HW_WR_REG32( gpmcBaseAddr + GPMC_CONFIG7(0), value );

    // drive bus cycles for timing verification
    gpmcDataAddr = (uint32_t *) AddrTranslateP_getLocalAddr( CSL_GPMC0_DATA_BASE );
    value        = 0x00000000;
    while( 1 ) {
        *gpmcDataAddr++ = value++;
        gpmcDataAddr    = (uint32_t *) ( (uint32_t) gpmcDataPtr & 0x0000ffff );
        printf( "write\n" );
    }
}

// ************************************************************
//
// memSpeedTest - determine maximum i/o rate to MCU local memory
//
// ************************************************************

void memSpeedTest( void )
{
    uint32_t gpioBaseAddr, pinNum;
    uint32_t *buffer;
    uint32_t bufferSize;
    uint32_t value;
    uint32_t index;

    // allocate buffer
    bufferSize = 8196;
    buffer     = malloc( sizeof( uint32_t ) * bufferSize );
    if( buffer == NULL ) {
        printf( "*** malloc failure in ddrTest\n" );
        return;
    }

    // translate mcu_gpio0 base addr via RAT
    gpioBaseAddr = (uint32_t) AddrTranslateP_getLocalAddr( CSL_MCU_GPIO0_BASE );
    pinNum       = TIMING_PIN;

    // set gpio to output for timing measurements
    GPIO_setDirMode( gpioBaseAddr, pinNum, GPIO_DIRECTION_OUTPUT );

    // fill memory buffer, toggling i/o line high during writing and low during reading
    while( 1 ) {
        GPIO_pinWriteHigh( gpioBaseAddr, pinNum );
        for( index = 0 ; index < bufferSize ; index++ ) {
            buffer[ index ] = 0x12345678;
        }

        GPIO_pinWriteLow( gpioBaseAddr, pinNum );
        for( index = 0 ; index < bufferSize ; index++ ) {
            value = buffer[ index ];
            if( value != (uint32_t) 0x123456798 )
                printf( "fail\n" );
        }
   }
}

// ************************************************************
//
// ddrSpeedTest - determine maximum i/o rate to ddr memory
//
// ************************************************************

void ddrSpeedTest( void )
{
    uint32_t gpioBaseAddr;
    uint32_t *ddrBaseAddr;
    uint32_t pinNum;
    uint32_t bufferSize;
    uint32_t value;
    uint32_t index;

    // allocate buffer
    bufferSize = 0x1f;

    // get base address of ddr
    ddrBaseAddr = (uint32_t *) AddrTranslateP_getLocalAddr( 0 );

    // translate mcu_gpio0 base addr via RAT
    gpioBaseAddr = (uint32_t) AddrTranslateP_getLocalAddr( CSL_MCU_GPIO0_BASE );
    pinNum       = TIMING_PIN;

    // set gpio to output for timing measurements
    GPIO_setDirMode( gpioBaseAddr, pinNum, GPIO_DIRECTION_OUTPUT );

    // fill memory buffer, toggling i/o line high during writing and low during reading
    ddrBaseAddr += 0x10000000;
    while( 1 ) {
        GPIO_pinWriteHigh( gpioBaseAddr, pinNum );
        for( index = 0 ; index < bufferSize ; index++ ) {
            ddrBaseAddr[ index ] = 0x12345678;
        }

        GPIO_pinWriteLow( gpioBaseAddr, pinNum );
        for( index = 0 ; index < bufferSize ; index++ ) {
            value = ddrBaseAddr[ index ];
            if( value != (uint32_t) 0x12345678 )
                printf( "fail\n" );
        }
   }
}



extern void Board_gpioInit( void );
extern void Board_gpioDeinit( void );
extern uint32_t Board_getGpioButtonIntrNum( void );
extern uint32_t Board_getGpioButtonSwitchNum( void );

// ************************************************************
//
// irqTest - determine irq latency
//
// ************************************************************
// On M4F, interrupt number as specified in TRM is input to the NVIC but from M4 point of view there are 16 internal interrupts
// and then the NVIC input interrupts start, hence we need to add +16 to the value specified by TRM

//#define BOARD_GPIO_INTR_NUM      (CSLR_irqGpioBaseAddrP_MCU_GPIOMUX_INTROUTER0_OUTP_6 + 16u)
#define BOARD_GPIO_INTR_NUM      (CSLR_MCU_M4FSS0_CORE0_NVIC_WKUP_MCU_GPIOMUX_INTROUTER0_OUTP_6 + 16u)

HwiP_Object     gpioHwiObject;
uint32_t        irqGpioBaseAddr;

void irqTest( void )
{
 /*   int32_t         retVal;
    uint32_t        intrPinNum, intrNum;
    uint32_t        gpioPinNum;
    uint32_t        bankNum, waitCount = 5;
    HwiP_Params     hwiPrms;

    Sciclient_gpioIrqSet();

    // interrupt pin is gpio pin 16, package pin D4, MCU HDR connection pin 11
    gpioPinNum      = TIMING_PIN;
    intrPinNum      = IRQ_PIN;
    intrNum         = BOARD_GPIO_INTR_NUM;
    bankNum         = GPIO_GET_BANK_INDEX( intrPinNum );

    // get gpio address translation value from RAT and assign to global scope for use in isr
    // this also is the gpio base addr for the isr indicator output pin
    irqGpioBaseAddr = (uint32_t) AddrTranslateP_getLocalAddr( CSL_MCU_GPIO0_BASE );

    // gpio used for timing output signal
    GPIO_setDirMode( irqGpioBaseAddr, gpioPinNum, GPIO_DIRECTION_OUTPUT );
    GPIO_pinWriteLow( irqGpioBaseAddr, gpioPinNum );

    // configurep gpio for interrupt generation
    GPIO_setDirMode( irqGpioBaseAddr, intrPinNum, GPIO_DIRECTION_INPUT );
    GPIO_setTrigType( irqGpioBaseAddr, intrPinNum, GPIO_TRIG_TYPE_RISE_EDGE );
    GPIO_bankIntrEnable( irqGpioBaseAddr, bankNum );

    // Register pin interrupt
    HwiP_Params_init( &hwiPrms );
    hwiPrms.intNum   = intrNum;
    hwiPrms.callback = &isrHandler;
    hwiPrms.args     = (void *) intrPinNum;
    retVal           = HwiP_construct( &gpioHwiObject, &hwiPrms );
    DebugP_assert( retVal == SystemP_SUCCESS );

    // loop for required irq completions
    while( gpioIntrDone < waitCount ) {
        retVal = GPIO_pinRead( irqGpioBaseAddr, intrPinNum );
        printf( "irq = 0x%08x\n", retVal );
    }

    // Unregister interrupt
    GPIO_bankIntrDisable( irqGpioBaseAddr, bankNum );
    GPIO_setTrigType( irqGpioBaseAddr, intrPinNum, GPIO_TRIG_TYPE_NONE );
    GPIO_clearIntrStatus( irqGpioBaseAddr, intrPinNum );
    HwiP_destruct( &gpioHwiObject );*/
}

// ************************************************************
//
// irqTest - determine irq latency
//
// ************************************************************

static void isrHandler( void *args )
{
    uint32_t    pinNum = (uint32_t) args;
    uint32_t    bankNum =  GPIO_GET_BANK_INDEX( pinNum );
    uint32_t    intrStatus, pinMask = GPIO_GET_BANK_BIT_MASK( pinNum );

    // get/clear bank interrupt status
    intrStatus = GPIO_getBankIntrStatus( irqGpioBaseAddr, bankNum );
    GPIO_clearBankIntrStatus( irqGpioBaseAddr, bankNum, intrStatus );

    // update pin interrupt count
    if( intrStatus & pinMask ) {
        gpioIntrDone++;
    }
}

#define TISCI_MSG_VALUE_RM_UNUSED_SECONDARY_HOST (0xFFu)
#define SRC_IDX_BASE_MCU_GPIO_bank_0       (30U)
#define SRC_IDX_BASE_MCU_GPIO_bank_1       (31U)
#define MCU_GPIO_MUX_introuter_ID          (5U)
#define mcu_gpiomux_introuter_mcu_0_outp_4 (4U)
#define mcu_gpiomux_introuter_mcu_0_outp_5 (5U)
#define mcu_gpiomux_introuter_mcu_0_outp_6 (6U)
#define mcu_gpiomux_introuter_mcu_0_outp_7 (7U)

static void Sciclient_gpioIrqSet( void )
{
    int32_t                             retVal;
    struct tisci_msg_rm_irq_set_req     rmIrqReq;
    struct tisci_msg_rm_irq_set_resp    rmIrqResp;

    rmIrqReq.valid_params           = 0U;
    rmIrqReq.valid_params          |= TISCI_MSG_VALUE_RM_DST_ID_VALID;
    rmIrqReq.valid_params          |= TISCI_MSG_VALUE_RM_DST_HOST_IRQ_VALID;
    rmIrqReq.global_event           = 0U;
    rmIrqReq.src_id                 = MCU_GPIO_MUX_introuter_ID;
    rmIrqReq.src_index              = SRC_IDX_BASE_MCU_GPIO_bank_0;
    rmIrqReq.dst_id                 = MCU_GPIO_MUX_introuter_ID;
    rmIrqReq.dst_host_irq           = mcu_gpiomux_introuter_mcu_0_outp_6;
    rmIrqReq.ia_id                  = 0U;
    rmIrqReq.vint                   = 0U;
    rmIrqReq.vint_status_bit_index  = 0U;
    rmIrqReq.secondary_host         = TISCI_MSG_VALUE_RM_UNUSED_SECONDARY_HOST;

    retVal = Sciclient_rmIrqSetRaw(&rmIrqReq, &rmIrqResp, SystemP_WAIT_FOREVER);
    if(0 != retVal)
    {
        DebugP_log("[Error] Sciclient event config failed!!!\r\n");
        DebugP_assert(FALSE);
    }

    return;
}
