/**
 * \file   osdrv_pruss.c
 *
 * \brief  
 *
 * 
*/

/*
 * Copyright (c) 2012 Texas Instruments Incorporated ALL RIGHTS RESERVED
 */

/* ========================================================================== */
/*                             Include Files                                  */
/* ========================================================================== */
#include "hw/hw_types.h"
#include "osdrv_pruss.h"
#include "osdrv_prussdefs.h"
#include "osdrv_spiflash.h"
#include "armv7a/am335x/interrupt.h"
#include <ti/sysbios/family/arm/a8/intcps/Hwi.h>
/* ========================================================================== */
/*                           Macros & Typedefs                                */
/* ========================================================================== */
#define PRUSSDRV_PRU_RESET      0
#define PRUSSDRV_PRU_DISABLE    1
#define PRUSSDRV_PRU_ENABLE     2

#define PRUSSDRV_MAX_WAIT_EVENTS	7
/* ========================================================================== */
/*                         Structures and Enums                               */
/* ========================================================================== */

/* ========================================================================== */
/*                 Internal Function Declarations                             */
/* ========================================================================== */ 
/**
 * \brief   Sets System-Channel Map registers: \n
 *
 * \param   sysevt          Channel number.
 * \param   channel	         Host number.
 *
 * \return   None.
 */
static void _PRUSSDRVIntcSetCmr(unsigned char sysevt,
                                unsigned char channel,
                                unsigned char polarity,
                                unsigned char type);
/**
 * \brief    Sets Channel-Host Map registers: \n
 *
 * \param    channel          Channel number.
 * \param   host	          Host number.
 *
 * \return   None.
 */
static void _PRUSSDRVIntcSetHmr(unsigned char channel,
                                unsigned char host);
/**
 * \brief    Detects the Chip Type \n
 *
 * \param    None
 *
 * \return   1 if chip is AM18XX, 2 if AM33XX., and -1 if Unable to detect
 */
int _PRUSSDRVDetectHWVersion(unsigned int *RAMBase);
/**
 * \brief   Routine to 
 *
 * \param   instance    
 *
 *
 * \return  None.
 *
 **/
void PRUSSDRVRHwiIntHandler(unsigned int arg);
/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */
static volatile unsigned int buffLen[2] = {0};
static volatile void* pruBinBuff[2] = {0};
static volatile pruss_irq_fn_map pruEvntOutFnMapArray[PRUSSDRV_MAX_WAIT_EVENTS];
static int pruss_l4fw_refcnt;
/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

int PRUSSDRVPruReset(unsigned int pruNum)
{
    if(pruNum == 0)
        HWREG( PRU0CONTROL_PHYS_BASE)  = PRUSSDRV_PRU_RESET;
    else if(pruNum == 1)
        HWREG( PRU1CONTROL_PHYS_BASE ) = PRUSSDRV_PRU_RESET;                                
    else
        return -1;
    return 0;    

}


int PRUSSDRVPruDisable(unsigned char pruNum)
{
    if(pruNum == 0)
        HWREG( PRU0CONTROL_PHYS_BASE)  = PRUSSDRV_PRU_DISABLE;
    else if(pruNum == 1)
        HWREG( PRU1CONTROL_PHYS_BASE ) = PRUSSDRV_PRU_DISABLE;                                
    else
        return -1;
    return 0;                
}


int PRUSSDRVPruEnable(unsigned char pruNum)
{
    if(pruNum == 0)
        HWREG( PRU0CONTROL_PHYS_BASE)  = PRUSSDRV_PRU_ENABLE;
    else if(pruNum == 1)
        HWREG( PRU1CONTROL_PHYS_BASE ) = PRUSSDRV_PRU_ENABLE;                                
    else
        return -1;
    return 0;    
}



int PRUSSDRVPruWriteMemory(unsigned int pruMem,
                           unsigned int wordoffset,
                           unsigned int *source_mem,
                           unsigned int bytelength)
{
	unsigned int i, wordlength;

	switch(pruMem)
	{
	case PRUSS0_PRU0_DATARAM:
		pruMem = DATARAM0_PHYS_BASE;
		break;
	case PRUSS0_PRU1_DATARAM:
		pruMem = DATARAM1_PHYS_BASE;		
		break;
	case PRUSS0_PRU0_IRAM:
		pruMem = PRU0IRAM_PHYS_BASE;		
		break;
	case PRUSS0_PRU1_IRAM:
		pruMem = PRU1IRAM_PHYS_BASE;		
		break;
	case PRUSS0_SHARED_DATARAM:
		pruMem = PRUSS_SHAREDRAM_BASE;
	default:
		return -1;
	}
	
    //Adjust length as multiple of 4 bytes
    wordlength = (bytelength + 3) >> 2; 
    
    for (i = 0; i < wordlength; i++)
    {
        HWREG( pruMem + (i << 2) + wordoffset ) = source_mem[i];
    }
    return wordlength;
}


int PRUSSDRVPruExecProgram(int pruNum, char *filename)
{
	if((pruNum > 2) || (pruBinBuff[pruNum] == 0) || (buffLen[pruNum] == 0))
		return -1;
		
		
	PRUSSDRVPruDisable(pruNum);
	
	PRUSSDRVPruWriteMemory(PRUSS0_PRU0_IRAM+pruNum,0,
						  (unsigned int *) pruBinBuff[pruNum],
						  buffLen[pruNum]);
	PRUSSDRVPruEnable(pruNum);
	return 0;
}

signed char PRUSSDRVLoadBinary (unsigned char pru_instance,unsigned int spi_offset)
{
	unsigned short  itr = 0;

	unsigned int t_pru_mem = 0;
	unsigned int image_size = 0,bytes_to_be_written = 0;
	char  t_spi_buff[256];
	unsigned int t_write_bytes = 0;
	PRU_FIRMWARE_BIN_HEADER* pru_bin_hdr = NULL;

	if(pru_instance == 0 )
	{
		//spi_offset = SPI_PRU0_BINARY_OFFSET;
		t_pru_mem = PRUSS0_PRU0_IRAM;
	}
	else if (pru_instance == 1)
	{
		//spi_offset = SPI_PRU1_BINARY_OFFSET;
		t_pru_mem = PRUSS0_PRU1_IRAM;
	}
	else
		return -1;
	/*Read first 256 bytes and validate the header */

	if( SpiFlashRead(spi_offset,256,(Uint8*)t_spi_buff) == -1)
		return -1;

	pru_bin_hdr = (PRU_FIRMWARE_BIN_HEADER*)t_spi_buff;
	if(MAGIC_NUM_PRU_BIN == pru_bin_hdr->magic_number)
	{
		if((pru_bin_hdr->image_size <= 0) ||
		   (pru_bin_hdr->image_size > (0x2000 + sizeof(PRU_FIRMWARE_BIN_HEADER))))
		{
			return -1;
		}
		image_size = bytes_to_be_written = (pru_bin_hdr->image_size - sizeof(PRU_FIRMWARE_BIN_HEADER));
	}
	else
		return -1;

	PRUSSDRVPruDisable(pru_instance);

	t_write_bytes = (bytes_to_be_written/240) ? 240 : bytes_to_be_written;
	PRUSSDRVPruWriteMemory(	t_pru_mem, 0 ,
	                        (Uint32*)(t_spi_buff+sizeof(PRU_FIRMWARE_BIN_HEADER)),
							t_write_bytes);
	bytes_to_be_written -= t_write_bytes;

	for(itr = 1 ; bytes_to_be_written	> 0 ; itr++ )
	{
		if( SpiFlashRead(spi_offset + (itr * 256) ,256,(Uint8*)t_spi_buff) == -1)
			return -1;
		t_write_bytes = (bytes_to_be_written/256) ? 256 : bytes_to_be_written;
		PRUSSDRVPruWriteMemory(	t_pru_mem,
							(image_size - bytes_to_be_written) ,
	                        (Uint32*)t_spi_buff,
							t_write_bytes);
		bytes_to_be_written -= 	t_write_bytes;
	}
	PRUSSDRVPruEnable(pru_instance);
	return 0;

}



int PRUSSDRVSetPRUBuffer(unsigned int pruNum, 
						 void *buffer,	
						 unsigned int numBytes)
{
	if((pruNum > 2 ) || (buffer == 0))
		return -1;

	pruBinBuff[pruNum] = buffer;
	buffLen[pruNum] = numBytes;
	return 0;
}			



int PRUSSDRVPruSendEvent(unsigned int eventnum)
{
	if(eventnum < 32)
		HWREG(INTC_PHYS_BASE + PRU_INTC_SRSR1_REG) = 1 << eventnum ;
	else
		HWREG(INTC_PHYS_BASE + PRU_INTC_SRSR2_REG) = 1 << (eventnum - 32);
	return  0;
}


int PRUSSDRVPruClearEvent(unsigned int eventnum)
{
	HWREG(INTC_PHYS_BASE + PRU_INTC_SICR_REG) = eventnum ;
	return 0;
}


int PRUSSDRVPruWaitEvent(unsigned int pruEvtoutNum)
{
	unsigned char index = 0;
    
    index = pruEvtoutNum - SYS_INT_PRUSS1_EVTOUT0;

    if(index >= PRUSSDRV_MAX_WAIT_EVENTS)
    	return -1;
    if(pruEvntOutFnMapArray[index].semHandle != NULL)
    	Semaphore_pend(pruEvntOutFnMapArray[index].semHandle,BIOS_WAIT_FOREVER);

	return  0;
}



int PRUSSDRVPruSendWaitCearEvent(unsigned int sendEventNum, 
										unsigned int pruEvtoutNum,
										unsigned int ackEventNum)
{
	PRUSSDRVPruSendEvent(sendEventNum);
	PRUSSDRVPruWaitEvent(pruEvtoutNum);
	PRUSSDRVPruClearEvent(ackEventNum);
	return 0;
}

int PRUSSDRVMapPruMem(unsigned int pru_ram_id, void **address)
{
    switch (pru_ram_id) {
    case PRUSS0_PRU0_DATARAM:
        *address = (void*)DATARAM0_PHYS_BASE;
        break;
    case PRUSS0_PRU1_DATARAM:
        *address = (void*)DATARAM1_PHYS_BASE;
        break;
#ifdef	AM33XX	
    case PRUSS0_SHARED_DATARAM:
        *address = (void*) PRUSS_SHAREDRAM_BASE;
        break;
#endif      
    default:
        *address = 0;
        return -1;
    }
    return 0;
}




int PRUSSDRVMapPeripheralIO(unsigned int per_id, void **address)
{
    switch (per_id)
	{
    case PRUSS0_CFG:
        *address = (void*)PRUSS_CFG_BASE;
        break;
    case PRUSS0_UART:
        *address = (void*)PRUSS_UART_BASE;
        break;
    case PRUSS0_IEP:
        *address = (void*)PRUSS_IEP_BASE;
        break;
    case PRUSS0_ECAP:
        *address = (void*)PRUSS_ECAP_BASE;
        break;
    case PRUSS0_MII_RT:
        *address = (void*)PRUSS_MIIRT_BASE;
        break;
    case PRUSS0_MDIO:
        *address = (void*)PRUSS_MDIO_BASE;
        break;
    default:
        *address = 0;
        return -1;
    }
    return 0;
}



int PRUSSDRVPruIntcInit(tpruss_intc_initdata * prussintc_init_data)
{
    unsigned int i = 0, mask1 = 0, mask2 = 0;
   	 
    HWREG(INTC_PHYS_BASE + PRU_INTC_SIPR1_REG ) = 0xFFFFFFFF;
    HWREG(INTC_PHYS_BASE + PRU_INTC_SIPR2_REG ) = 0xFFFFFFFF;
    HWREG( INTC_PHYS_BASE + PRU_INTC_SITR1_REG ) = 0xFFFFFFFF;
    HWREG( INTC_PHYS_BASE + PRU_INTC_SITR2_REG ) = 0xFFFFFFFF;


    //Intialize ChannelMapRegsiter value to zero
    for (i = 0; i < (NUM_PRU_SYS_EVTS + 3) >> 2; i++)
        HWREG( INTC_PHYS_BASE + PRU_INTC_CMR1_REG  + (i << 2)) = 0;

    for (i = 0;
        ((prussintc_init_data->sysevt_to_channel_map[i].sysevt != 0xFF)
          && (prussintc_init_data->sysevt_to_channel_map[i].channel != 0xFF));
        i++)
    {
        _PRUSSDRVIntcSetCmr(prussintc_init_data->sysevt_to_channel_map[i].sysevt,
                            prussintc_init_data->sysevt_to_channel_map[i].channel,
                            (~prussintc_init_data->sysevt_to_channel_map[i].polarity)&0x01,
                            (~prussintc_init_data->sysevt_to_channel_map[i].type)&0x01);
    }

    //Intialize HostMapRegsiter value to zero
    for (i = 0; i < (NUM_PRU_HOSTS + 3) >> 2; i++)
        HWREG( INTC_PHYS_BASE +  PRU_INTC_HMR1_REG + (i << 2)) = 0;
        
    for (i = 0;
         ((prussintc_init_data->channel_to_host_map[i].channel != 0xFF)
          && (prussintc_init_data->channel_to_host_map[i].host != 0xFF));
         i++) 
    {
        _PRUSSDRVIntcSetHmr(prussintc_init_data->channel_to_host_map[i].channel,
                            prussintc_init_data->channel_to_host_map[i].host);
    }


    mask1 = mask2 = 0;
    for (i = 0; prussintc_init_data->sysevts_enabled[i] != 0xFF; i++)
    {
        if (prussintc_init_data->sysevts_enabled[i] < 32)
        {
            mask1 = mask1 + (1 << (prussintc_init_data->sysevts_enabled[i]));
        }
        else if (prussintc_init_data->sysevts_enabled[i] < 64)
        {
            mask2 = mask2 + (1 << (prussintc_init_data->sysevts_enabled[i] - 32));
        } 
        else
        {
            /* Error */ 
            return -1;
        }
    }
    HWREG( INTC_PHYS_BASE + PRU_INTC_ESR1_REG )  = mask1;
    HWREG( INTC_PHYS_BASE + PRU_INTC_SECR1_REG ) = mask1;
    HWREG( INTC_PHYS_BASE + PRU_INTC_ESR2_REG )  = mask2;
    HWREG( INTC_PHYS_BASE + PRU_INTC_SECR2_REG ) = mask2;

    for (i=0; i < MAX_HOSTS_SUPPORTED; i++)
    {
        if (prussintc_init_data->host_enable_bitmask & (1 << i)) 
        {
            HWREG( INTC_PHYS_BASE + PRU_INTC_HIEISR_REG ) = i;      
        }
    }
    HWREG( INTC_PHYS_BASE + PRU_INTC_GER_REG ) = 0x1;

    return 0;
}



int PRUSSDRVRegisterIrqHandler(unsigned int pruEvtoutNum, 
						   unsigned char waitEnable,
						   PRUSSDRV_IRQ_HANDLER irqHandler)
{
	Semaphore_Handle semHandle = NULL;
	Semaphore_Params semParams;
	unsigned char index = 0;
    Hwi_Handle hwiHandle;
    Hwi_Params hwiParams;

    index = pruEvtoutNum - SYS_INT_PRUSS1_EVTOUT0;

    if ((index >= PRUSSDRV_MAX_WAIT_EVENTS) || (irqHandler == NULL))
    	return -1;

    /* create a Semaphore Instance */
	if(waitEnable)
	{
		Semaphore_Params_init(&semParams);
		semParams.mode = Semaphore_Mode_BINARY;
		semHandle = Semaphore_create(0, &semParams, NULL);

		if(NULL == semHandle)
			return -1;
	}

    Hwi_Params_init(&hwiParams);
    hwiParams.arg = pruEvtoutNum;
    hwiParams.priority = pruEvtoutNum;
    hwiParams.maskSetting = Hwi_MaskingOption_LOWER;
    hwiParams.enableInt = false;

    hwiHandle = Hwi_create(pruEvtoutNum, PRUSSDRVRHwiIntHandler, &hwiParams, NULL);

    if (hwiHandle == NULL)
    {
    	Semaphore_delete(&semHandle);
    	return -1;
    }

    pruEvntOutFnMapArray[index].semHandle = semHandle;
    pruEvntOutFnMapArray[index].irqHandler = irqHandler;
    
    Hwi_enableInterrupt(pruEvtoutNum);
	return 0;
}						   

void PRUSSDRVFireWallInit (void)
{
    HWREG(0x4a000208) = 8; //Protection Group 1: Exclusive access for ICSS and disable Host CPU access
    HWREG(0x4a000210) = 9; //Protection Group 2: Access for ICSS and Host CPU
    pruss_l4fw_refcnt = 0;
}

void PRUSSDRVFireWallHostAccessEnable (void)
{
   if (pruss_l4fw_refcnt <= 0)
       HWREG(0x4a00034c) = 0x200000; //Set Protection Group2  in L4F Region9 for ICSS
    pruss_l4fw_refcnt++; 
}

void PRUSSDRVFireWallHostAccessDisable (void)
{
   pruss_l4fw_refcnt--;
   if (pruss_l4fw_refcnt == 0)   
       HWREG(0x4a00034c) = 0x100000; //Set Protection Group1 in L4F Region9 for ICSS
}


/* -------------------------------------------------------------------------- */
/*                 Internal Function Definitions                              */
/* -------------------------------------------------------------------------- */

void PRUSSDRVRHwiIntHandler(unsigned int arg)
{
	unsigned char index = 0;
    index = arg - SYS_INT_PRUSS1_EVTOUT0;

    if(index < PRUSSDRV_MAX_WAIT_EVENTS)
    {
    	pruEvntOutFnMapArray[index].irqHandler();
    	if(pruEvntOutFnMapArray[index].semHandle != NULL)
    		Semaphore_post(pruEvntOutFnMapArray[index].semHandle);
    }

}

static void _PRUSSDRVIntcSetCmr(unsigned char sysevt,
                                unsigned char channel,
                                unsigned char polarity,
                                unsigned char type)
{
    HWREG( INTC_PHYS_BASE + (PRU_INTC_CMR1_REG + (sysevt & ~(0x3))) )
                             |= ((channel & 0xF) << ((sysevt & 0x3) << 3));
                             
    if(sysevt < 32)
    {
        HWREG( INTC_PHYS_BASE + PRU_INTC_SIPR1_REG ) &= ~(polarity << sysevt);
        HWREG( INTC_PHYS_BASE + PRU_INTC_SITR1_REG ) &= ~(type << sysevt);
    }
    else
    {
        HWREG( INTC_PHYS_BASE + PRU_INTC_SIPR2_REG ) &= ~(polarity << (sysevt - 32));
        HWREG( INTC_PHYS_BASE + PRU_INTC_SITR2_REG ) &= ~(type << (sysevt - 32));
    }
    
}


static void _PRUSSDRVIntcSetHmr(unsigned char channel,
                                unsigned char host)
{
    HWREG(INTC_PHYS_BASE + (PRU_INTC_HMR1_REG + (channel & ~(0x3))) )
                = HWREG(INTC_PHYS_BASE + (PRU_INTC_HMR1_REG + (channel & ~(0x3))) ) 
                        | (((host) & 0xF) << (((channel) & 0x3) << 3));
}


int _PRUSSDRVDetectHWVersion(unsigned int *RAMBase)
{
	if (HWREG(RAMBase + (AM18XX_INTC_PHYS_BASE - AM18XX_DATARAM0_PHYS_BASE)) 
        == AM18XX_PRUSS_INTC_REV)
        return PRUSS_V1;
    else 
	{ 
        if (HWREG(RAMBase +(AM33XX_INTC_PHYS_BASE - AM33XX_DATARAM0_PHYS_BASE))  ==
            AM33XX_PRUSS_INTC_REV)
            return PRUSS_V2;
        else
            return -1;
    }
}
