#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <asm/ioctl.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/pagemap.h>
#include "sep_driver_hw_defs.h"
#include "sep_driver_config.h"
#include "sep_driver_api.h"
#include "sep_driver_ext_api.h"


#if SEP_DRIVER_ARM_DEBUG_MODE

#define  CRYS_SEP_ROM_length                  0x2000

#define  CRYS_SEP_ROM_start_address           0x8000C000UL

#define  CRYS_SEP_ROM_start_address_offset    0xC000UL

#define  SEP_ROM_BANK_register                0x80008420UL

#define  SEP_ROM_BANK_register_offset         0x8420UL

#define SEP_RAR_IO_MEM_REGION_START_ADDRESS   0x82000000

/* 2M size */
#define SEP_RAR_IO_MEM_REGION_SIZE            (1024*1024*2)

static unsigned long CRYS_SEP_ROM[] = {
   #include "SEP_ROM_image.h"
};

#else

/*-------------
  THOSE 2 definitions are specific to the board - must be defined during integration
---------------*/
#define SEP_RAR_IO_MEM_REGION_START_ADDRESS   0xC0000000

/* 2M size */
#define SEP_RAR_IO_MEM_REGION_SIZE            (1024*1024*2)

#endif /* SEP_DRIVER_ARM_DEBUG_MODE */


static unsigned long    rar_region_addr;

/* kernel address of the external cache */
static unsigned long    ext_cache_addr;

/* physical address of the external cache */
static unsigned long    ext_cache_phys_addr;


extern int              sepDebug;

/* start address of the access to the SEP registers from driver */
extern unsigned long    g_sep_reg_base_address;

/*
  interrupt handler function 
*/
extern irqreturn_t sep_inthandler(int irq , void* dev_id);


/*
  This functions locks the area of the resisnd and cache sep code 
*/
void sep_lock_cache_resident_area(void)
{
  return;
}


/*
  This functions copies the i-cache and resident from their source location into 
  destination memory, which is external to Linux VM and is given as physical address.
  It also allocates memory on the external RAM for d-cache 
*/
int sep_copy_cache_resident_to_area(unsigned long   src_cache_addr,
                                    unsigned long   cache_size_in_bytes,
                                    unsigned long   src_resident_addr,
                                    unsigned long   resident_size_in_bytes,
                                    unsigned long   dcache_size_in_bytes,
                                    unsigned long*  dst_new_cache_addr_ptr,
                                    unsigned long*  dst_new_resident_addr_ptr,
                                    unsigned long*  dst_new_dcache_addr_ptr)
{
  /* resident address in user space */
  unsigned long resident_addr;
  
  /* cahce address in user space */
  unsigned long cache_addr;
  
  /* error */
  int           error;
  
  /*--------------------------------
      CODE
  -------------------------------------*/
  
  /* kernel addresses */
  resident_addr = rar_region_addr + (1024 * 10);
  
  /* cache address must be aligned to 4K boundary */
  cache_addr = (resident_addr + (1024 * 24) + 1024 *4) & 0xFFFFF000;
  
  /* ext cache place */
  ext_cache_addr = cache_addr + (1024 * 370);
  
  
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:resident_size_in_bytes is %lu\n",resident_size_in_bytes);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:cache_size_in_bytes is %lu\n",cache_size_in_bytes);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:resident_addr is %lu\n",resident_addr);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:cache_addr is %lu\n",cache_addr);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:ext cache_addr is %lu\n",ext_cache_addr);
  
  
  error = copy_from_user((void *)resident_addr , (void*)src_resident_addr , resident_size_in_bytes);
  if(error)
  {
    goto end_function;
  }
  
  error = copy_from_user((void *)cache_addr , (void*)src_cache_addr , cache_size_in_bytes);
  if(error)
  {
    goto end_function;
  }
  
  /* physical addresses */
  *dst_new_resident_addr_ptr = SEP_RAR_IO_MEM_REGION_START_ADDRESS +(1024 * 10);
  *dst_new_cache_addr_ptr = (*dst_new_resident_addr_ptr + (1024 * 24)+ (1024 *4)) & 0xFFFFF000;
  
  /* i-cache is of maximum 370 k */
  ext_cache_phys_addr = *dst_new_cache_addr_ptr + (370 * 1024);
  
  /* leave a place for external cache */
  *dst_new_dcache_addr_ptr = ext_cache_phys_addr + (1024 * 150);

end_function:

  return error;
}

/*
  This functions copies the external cache (3rd party) from it's source location into 
  destination memory, which is external to Linux VM and is given as physical address.
  It is supposed to be adjustent to the i-cache boundary
*/
int sep_copy_ext_cache_to_area(unsigned long   src_ext_cache_addr,
                               unsigned long   ext_cache_size_in_bytes,
                               unsigned long*  dst_new_ext_cache_addr_ptr)
{
  /* error */
  int           error;
  
  /*-------------------------------
    CODE
  -----------------------------------*/
  
  error = 0;
  
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:ext_cache_addr is %lu\n",ext_cache_addr);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:ext_cache_phys_addr is %lu\n",ext_cache_phys_addr);
  
  if(ext_cache_size_in_bytes != 0)
  {
    error = copy_from_user((void *)ext_cache_addr , (void*)src_ext_cache_addr , ext_cache_size_in_bytes);
    if(error)
    {
      goto end_function;
    }
  }
  
  /* set the output paramater, if provided */
  if(dst_new_ext_cache_addr_ptr != 0)
  {
    *dst_new_ext_cache_addr_ptr = ext_cache_phys_addr;
  }
  
end_function:

  return error;
}

/*
  This functions maps and allocates the shared area on the  external RAM (device)
  The input is shared_area_size - the size of the memory to allocate. The outputs are kernel_shared_area_addr_ptr - the kerenl address of the mapped and allocated 
  shared area, and phys_shared_area_addr_ptr - the physical address of the shared area 
*/
int sep_map_and_alloc_shared_area(unsigned long   shared_area_size,
                                  unsigned long*  kernel_shared_area_addr_ptr,
                                  unsigned long*  phys_shared_area_addr_ptr)
{

  /* map the 0x82000000 region */
  rar_region_addr = (unsigned long)ioremap_nocache( SEP_RAR_IO_MEM_REGION_START_ADDRESS , SEP_RAR_IO_MEM_REGION_SIZE);
  if(rar_region_addr == 0)
  {
    DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "sep_driver:rar io memory remap failed\n");
    return -1;
  }
  
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver:rar_region_addr is %lu\n",rar_region_addr);
  
  *kernel_shared_area_addr_ptr = rar_region_addr;
  /* set the physical address of the shared area */
  *phys_shared_area_addr_ptr = SEP_RAR_IO_MEM_REGION_START_ADDRESS;
  return 0;
}

/*
  This functions unmaps and deallocates the shared area on the  external RAM (device)
  The input is shared_area_size - the size of the memory to deallocate,kernel_shared_area_addr_ptr - the kernel address of the mapped and allocated 
  shared area,phys_shared_area_addr_ptr - the physical address of the shared area 
*/
void sep_unmap_and_free_shared_area(unsigned long   shared_area_size,
                                    unsigned long   kernel_shared_area_addr,
                                    unsigned long   phys_shared_area_addr)
{
  iounmap((void*)kernel_shared_area_addr);
  return;
}

/*
  This functions returns the physical address inside shared area according to the virtual address. It can be either on the externa RAM device (ioremapped), or on the system RAM
  This implementation is for the external RAM
*/
unsigned long sep_shared_area_virt_to_phys(unsigned long virt_address)
{
  return SEP_RAR_IO_MEM_REGION_START_ADDRESS + (virt_address - rar_region_addr);
}

/*
  This functions returns the physical address inside shared area according to the virtual address. It can be either on the externa RAM device (ioremapped), or on the system RAM
  This implementation is for the external RAM
*/
unsigned long sep_shared_area_phys_to_virt(unsigned long phys_address)
{
  return rar_region_addr + (phys_address - SEP_RAR_IO_MEM_REGION_START_ADDRESS);
}

/*
  this function registers th driver to the device subsystem( either PCI, USB, etc)
*/
int sep_register_driver_to_device(void)
{
  /* error */
  int error;
  
  /*-------------------------
      CODE
  -----------------------------*/
  
  error = 0;
  
 #if !SEP_DRIVER_POLLING_MODE
  /* get the interrupt line */
  error = request_irq(SEP_DIRVER_IRQ_NUM , sep_inthandler , IRQF_SHARED  , "sep_driver" , &g_sep_reg_base_address);
  if(error)
  {
    DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: request_irq failed\n");
  
    goto end_function;
  }

  /* set the IMR register - open only GPR 2 */
  SEP_WRITE_REGISTER(g_sep_reg_base_address + HW_HOST_IMR_REG_ADDR , (~(0x1 << 13)));
  
end_function:

#endif

  return error;
}



void sep_load_rom_code()
{
#if SEP_DRIVER_ARM_DEBUG_MODE
  /* Index variables */   
  unsigned long i, k,j;
  unsigned long regVal;
  unsigned long Error;
  unsigned long warning;
   
  /* Loading ROM from SEP_ROM_image.h file */
  k = sizeof(CRYS_SEP_ROM);
  
  DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: DX_CC_TST_SepRomLoader start\n");
  
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: k is %lu\n",k);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: g_sep_reg_base_address is %lu\n",g_sep_reg_base_address);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: CRYS_SEP_ROM_start_address_offset is %lu\n",CRYS_SEP_ROM_start_address_offset);
  DEBUG_PRINT_1(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: CRYS_SEP_ROM_length is %lu\n",(unsigned long)CRYS_SEP_ROM_length);

  for(i = 0;i < 2; i++)
  {
      /* write bank */
  	  SEP_WRITE_REGISTER(g_sep_reg_base_address + SEP_ROM_BANK_register_offset ,i);
  	  
  	  for(j = 0;j < CRYS_SEP_ROM_length / 2;j++)
  	  {
	      SEP_WRITE_REGISTER(g_sep_reg_base_address + CRYS_SEP_ROM_start_address_offset + 4*j, CRYS_SEP_ROM[i * 0x1000 + j]); 
  		  k= k - 4;
  		  if(k==0)
      	{
          j= CRYS_SEP_ROM_length;
        	i=4;
        }
  	  }
  }

  /* reset the SEP*/
  SEP_WRITE_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_SW_RST_REG_ADDR , 0x1); 

  /* poll for SEP ROM boot finish */
  do
  {
    SEP_READ_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_HOST_GPR3_REG_ADDR , regVal);
  }while(!regVal);
  
  DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling ended\n");
  
  switch(regVal)
  {
  	case 0x1:
	    /* fatal error - read erro status from GPRO */
	    SEP_READ_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_HOST_GPR0_REG_ADDR , Error); 
	    DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling case 1\n");
		  break;
  	case 0x2:
	    /* Boot First Phase ended  */
	    SEP_READ_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_HOST_GPR0_REG_ADDR , warning);
	    DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling case 2\n"); 
		
		  break;
  	case 0x4:
	    /* Cold boot ended successfully  */
	    SEP_READ_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_HOST_GPR0_REG_ADDR , warning); 
		  DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling case 4\n");
		  Error = 0;
		  break;
  	case 0x8:
	    /* Warmboot ended successfully */
	    SEP_READ_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_HOST_GPR0_REG_ADDR , warning); 
		  DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling case 8\n");
		  Error = 0;
		  break;
  	case 0x10:
	    /* ColdWarm boot ended successfully */
	    SEP_READ_REGISTER(g_sep_reg_base_address + HW_HOST_SEP_HOST_GPR0_REG_ADDR , warning); 
	    DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling case 16\n");
		
		  Error = 0;
		  break;
  	case 0x20:
	    DEBUG_PRINT_0(SEP_DEBUG_LEVEL_EXTENDED , "SEP Driver: ROM polling case 32\n");
	  	break;
  }

#endif
}
