/*  Copyright (C) 2006 yopyop
    yopyop156@ifrance.com
    yopyop156.ifrance.com

    This file is part of DeSmuME

    DeSmuME is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    DeSmuME is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DeSmuME; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>

#include "cp15.h"
#include "debug.h"
#include "MMU.h"

armcp15_t *armcp15_new(armcpu_t * c)
{
	int i;
	armcp15_t *armcp15 = (armcp15_t*)malloc(sizeof(armcp15_t));
	if(!armcp15) return NULL;
	
	armcp15->cpu = c;
	armcp15->IDCode = 0x41049460;
	armcp15->cacheType = 0x0F0D2112;
	armcp15->TCMSize = 0x00140140;
	armcp15->ctrl = 0x00000000;
	armcp15->DCConfig = 0x0;    
	armcp15->ICConfig = 0x0;    
	armcp15->writeBuffCtrl = 0x0;
	armcp15->und = 0x0;
	armcp15->DaccessPerm = 0x22222222;
	armcp15->IaccessPerm = 0x22222222;
	armcp15->protectBaseSize0 = 0x0;
	armcp15->protectBaseSize1 = 0x0;
	armcp15->protectBaseSize2 = 0x0;
	armcp15->protectBaseSize3 = 0x0;
	armcp15->protectBaseSize4 = 0x0;
	armcp15->protectBaseSize5 = 0x0;
	armcp15->protectBaseSize6 = 0x0;
	armcp15->protectBaseSize7 = 0x0;
	armcp15->cacheOp = 0x0;
	armcp15->DcacheLock = 0x0;
	armcp15->IcacheLock = 0x0;
	armcp15->ITCMRegion = 0x0C;
	armcp15->DTCMRegion = 0x0080000A;
	armcp15->processID = 0;

    /* preset calculated regionmasks */	
	for (i=0;i<8;i++) {
        armcp15->regionWriteMask_USR[i] = 0 ;
        armcp15->regionWriteMask_SYS[i] = 0 ;
        armcp15->regionReadMask_USR[i] = 0 ;
        armcp15->regionReadMask_SYS[i] = 0 ;
        armcp15->regionExecuteMask_USR[i] = 0 ;
        armcp15->regionExecuteMask_SYS[i] = 0 ;
        armcp15->regionWriteSet_USR[i] = 0 ;
        armcp15->regionWriteSet_SYS[i] = 0 ;
        armcp15->regionReadSet_USR[i] = 0 ;
        armcp15->regionReadSet_SYS[i] = 0 ;
        armcp15->regionExecuteSet_USR[i] = 0 ;
        armcp15->regionExecuteSet_SYS[i] = 0 ;
    } ;
	
	return armcp15;
}

#define ACCESSTYPE(val,n)   (((val) >> (4*n)) & 0x0F)
#define SIZEIDENTIFIER(val) ((((val) >> 1) & 0x1F))
#define SIZEBINARY(val)     (1 << (SIZEIDENTIFIER(val)+1))
#define MASKFROMREG(val)    (~((SIZEBINARY(val)-1) | 0x3F))
#define SETFROMREG(val)     ((val) & MASKFROMREG(val))
/* sets the precalculated regions to mask,set for the affected accesstypes */
static void armcp15_setSingleRegionAccess(armcp15_t *armcp15,u32 dAccess,u32 iAccess,unsigned char num, u32 mask,u32 set) {

	switch (ACCESSTYPE(dAccess,num)) {
		case 4: /* UNP */
		case 7: /* UNP */
		case 8: /* UNP */
		case 9: /* UNP */
		case 10: /* UNP */
		case 11: /* UNP */
		case 12: /* UNP */
		case 13: /* UNP */
		case 14: /* UNP */
		case 15: /* UNP */
		case 0: /* no access at all */
			armcp15->regionWriteMask_USR[num] = 0 ;
			armcp15->regionWriteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_USR[num] = 0 ;
			armcp15->regionReadSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionWriteMask_SYS[num] = 0 ;
			armcp15->regionWriteSet_SYS[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_SYS[num] = 0 ;
			armcp15->regionReadSet_SYS[num] = 0xFFFFFFFF ;
			break ;
		case 1: /* no access at USR, all to sys */
			armcp15->regionWriteMask_USR[num] = 0 ;
			armcp15->regionWriteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_USR[num] = 0 ;
			armcp15->regionReadSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionWriteMask_SYS[num] = mask ;
			armcp15->regionWriteSet_SYS[num] = set ;
			armcp15->regionReadMask_SYS[num] = mask ;
			armcp15->regionReadSet_SYS[num] = set ;
			break ;
		case 2: /* read at USR, all to sys */
			armcp15->regionWriteMask_USR[num] = 0 ;
			armcp15->regionWriteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_USR[num] = mask ;
			armcp15->regionReadSet_USR[num] = set ;
			armcp15->regionWriteMask_SYS[num] = mask ;
			armcp15->regionWriteSet_SYS[num] = set ;
			armcp15->regionReadMask_SYS[num] = mask ;
			armcp15->regionReadSet_SYS[num] = set ;
			break ;
		case 3: /* all to USR, all to sys */
			armcp15->regionWriteMask_USR[num] = mask ;
			armcp15->regionWriteSet_USR[num] = set ;
			armcp15->regionReadMask_USR[num] = mask ;
			armcp15->regionReadSet_USR[num] = set ;
			armcp15->regionWriteMask_SYS[num] = mask ;
			armcp15->regionWriteSet_SYS[num] = set ;
			armcp15->regionReadMask_SYS[num] = mask ;
			armcp15->regionReadSet_SYS[num] = set ;
			break ;
		case 5: /* no access at USR, read to sys */
			armcp15->regionWriteMask_USR[num] = 0 ;
			armcp15->regionWriteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_USR[num] = 0 ;
			armcp15->regionReadSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionWriteMask_SYS[num] = 0 ;
			armcp15->regionWriteSet_SYS[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_SYS[num] = mask ;
			armcp15->regionReadSet_SYS[num] = set ;
			break ;
		case 6: /* read at USR, read to sys */
			armcp15->regionWriteMask_USR[num] = 0 ;
			armcp15->regionWriteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_USR[num] = mask ;
			armcp15->regionReadSet_USR[num] = set ;
			armcp15->regionWriteMask_SYS[num] = 0 ;
			armcp15->regionWriteSet_SYS[num] = 0xFFFFFFFF ;
			armcp15->regionReadMask_SYS[num] = mask ;
			armcp15->regionReadSet_SYS[num] = set ;
			break ;
	}
	switch (ACCESSTYPE(iAccess,num)) {
		case 4: /* UNP */
		case 7: /* UNP */
		case 8: /* UNP */
		case 9: /* UNP */
		case 10: /* UNP */
		case 11: /* UNP */
		case 12: /* UNP */
		case 13: /* UNP */
		case 14: /* UNP */
		case 15: /* UNP */
		case 0: /* no access at all */
			armcp15->regionExecuteMask_USR[num] = 0 ;
			armcp15->regionExecuteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionExecuteMask_SYS[num] = 0 ;
			armcp15->regionExecuteSet_SYS[num] = 0xFFFFFFFF ;
			break ;
		case 1:
			armcp15->regionExecuteMask_USR[num] = 0 ;
			armcp15->regionExecuteSet_USR[num] = 0xFFFFFFFF ;
			armcp15->regionExecuteMask_SYS[num] = mask ;
			armcp15->regionExecuteSet_SYS[num] = set ;
			break ;
		case 2:
		case 3:
		case 6:
			armcp15->regionExecuteMask_USR[num] = mask ;
			armcp15->regionExecuteSet_USR[num] = set ;
			armcp15->regionExecuteMask_SYS[num] = mask ;
			armcp15->regionExecuteSet_SYS[num] = set ;
			break ;
	}
} ;

/* precalculate region masks/sets from cp15 register */
static void armcp15_maskPrecalc(armcp15_t *armcp15)
{
	#define precalc(num) {  \
	u32 mask = 0, set = 0xFFFFFFFF ; /* (x & 0) == 0xFF..FF is allways false (disabled) */  \
	if (BIT_N(armcp15->protectBaseSize##num,0)) /* if region is enabled */ \
	{    /* reason for this define: naming includes var */  \
		mask = MASKFROMREG(armcp15->protectBaseSize##num) ;   \
		set = SETFROMREG(armcp15->protectBaseSize##num) ; \
		if (SIZEIDENTIFIER(armcp15->protectBaseSize##num)==0x1F)  \
		{   /* for the 4GB region, u32 suffers wraparound */   \
		mask = 0 ; set = 0 ;   /* (x & 0) == 0  is allways true (enabled) */  \
		} \
	}  \
		armcp15_setSingleRegionAccess(armcp15,armcp15->DaccessPerm,armcp15->IaccessPerm,num,mask,set) ;  \
	}
	precalc(0) ;
	precalc(1) ;
	precalc(2) ;
	precalc(3) ;
	precalc(4) ;
	precalc(5) ;
	precalc(6) ;
	precalc(7) ;
}

INLINE BOOL armcp15_isAccessAllowed(armcp15_t *armcp15,u32 address,u32 access)
{
	int i ;
	if (!(armcp15->ctrl & 1)) return TRUE ;        /* protection checking is not enabled */
	for (i=0;i<8;i++) {
		switch (access) {
		case CP15_ACCESS_WRITEUSR:
			if ((address & armcp15->regionWriteMask_USR[i]) == armcp15->regionWriteSet_USR[i]) return TRUE ;
			break ;
		case CP15_ACCESS_WRITESYS:
			if ((address & armcp15->regionWriteMask_SYS[i]) == armcp15->regionWriteSet_SYS[i]) return TRUE ;
			break ;
		case CP15_ACCESS_READUSR:
			if ((address & armcp15->regionReadMask_USR[i]) == armcp15->regionReadSet_USR[i]) return TRUE ;
			break ;
		case CP15_ACCESS_READSYS:
			if ((address & armcp15->regionReadMask_SYS[i]) == armcp15->regionReadSet_SYS[i]) return TRUE ;
			break ;
		case CP15_ACCESS_EXECUSR:
			if ((address & armcp15->regionExecuteMask_USR[i]) == armcp15->regionExecuteSet_USR[i]) return TRUE ;
			break ;
		case CP15_ACCESS_EXECSYS:
			if ((address & armcp15->regionExecuteMask_SYS[i]) == armcp15->regionExecuteSet_SYS[i]) return TRUE ;
			break ;
		}
	}
	/* when protections are enabled, but no region allows access, deny access */
	return FALSE ;
}

BOOL armcp15_dataProcess(armcp15_t *armcp15, u8 CRd, u8 CRn, u8 CRm, u8 opcode1, u8 opcode2)
{
		LOG("Unsupported CP15 operation : DataProcess\n");
   return FALSE;
}

BOOL armcp15_load(armcp15_t *armcp15, u8 CRd, u8 adr)
{
	LOG("Unsupported CP15 operation : Load\n");
   return FALSE;
}

BOOL armcp15_store(armcp15_t *armcp15, u8 CRd, u8 adr)
{
	LOG("Unsupported CP15 operation : Store\n");
   return FALSE;
}

BOOL armcp15_moveCP2ARM(armcp15_t *armcp15, u32 * R, u8 CRn, u8 CRm, u8 opcode1, u8 opcode2)
{
	if(armcp15->cpu->CPSR.bits.mode == USR) return FALSE;
	
	switch(CRn)
	{
		case 0 :
			if((opcode1 == 0)&&(CRm==0))
			{
			switch(opcode2)
			{
				case 1 :
					*R = armcp15->cacheType;
					return TRUE;
				case 2 :
					*R = armcp15->TCMSize;
					return TRUE;
				default :
					*R = armcp15->IDCode;
					return TRUE;
			}
			}
			return FALSE;
		case 1 :
			if((opcode1==0) && (opcode2==0) && (CRm==0))
			{
				*R = armcp15->ctrl;
				return TRUE;
			}
			return FALSE;
			
		case 2 :
			if((opcode1==0) && (CRm==0))
			{
				switch(opcode2)
				{
					case 0 :
						*R = armcp15->DCConfig;
					return TRUE;
					case 1 :
						*R = armcp15->ICConfig;
					return TRUE;
					default :
					return FALSE;
				}
			}
			return FALSE;
		case 3 :
			if((opcode1==0) && (opcode2==0) && (CRm==0))
			{
				*R = armcp15->writeBuffCtrl;
				return TRUE;
			}
			return FALSE;
		case 5 :
			if((opcode1==0) && (CRm==0))
			{
				switch(opcode2)
				{
					case 2 :
						*R = armcp15->DaccessPerm;
					    return TRUE;
					case 3 :
						*R = armcp15->IaccessPerm;
					return TRUE;
					default :
					return FALSE;
				}
			}
			return FALSE;
		case 6 :
			if((opcode1==0) && (opcode2==0))
			{
				switch(CRm)
				{
					case 0 :
						*R = armcp15->protectBaseSize0;
					return TRUE;
					case 1 :
						*R = armcp15->protectBaseSize1;
					return TRUE;
					case 2 :
						*R = armcp15->protectBaseSize2;
					return TRUE;
					case 3 :
						*R = armcp15->protectBaseSize3;
					return TRUE;
					case 4 :
						*R = armcp15->protectBaseSize4;
					return TRUE;
					case 5 :
						*R = armcp15->protectBaseSize5;
					return TRUE;
					case 6 :
						*R = armcp15->protectBaseSize6;
					return TRUE;
					case 7 :
						*R = armcp15->protectBaseSize7;
					return TRUE;
					default :
					return FALSE;
				}
			}
			return FALSE;
		case 9 :
			if((opcode1==0))
			{
				switch(CRm)
				{
					case 0 :
						switch(opcode2)
						{
							case 0 :
							*R = armcp15->DcacheLock;
							return TRUE;
							case 1 :
							*R = armcp15->IcacheLock;
							return TRUE;
							default :
								return FALSE;
						}
					case 1 :
						switch(opcode2)
						{
						case 0 :
						*R = armcp15->DTCMRegion;
						return TRUE;
						case 1 :
						*R = armcp15->ITCMRegion;
						return TRUE;
						default :
							return FALSE;
						}
				}
			}
		return FALSE;
		default :
			LOG("Unsupported CP15 operation : MRC\n");
			return FALSE;
	}
}


static u32 CP15wait4IRQ(armcpu_t *cpu)
{
	u32 instructAddr = cpu->instruct_adr;
	/* on the first call, wirq is not set */
	if(cpu->wirq)
	{
		/* check wether an irq was issued */
		if(!cpu->waitIRQ)
		{
			cpu->waitIRQ = 0;
			cpu->wirq = 0;
			return 1;   /* return execution */
		}
		/* otherwise, repeat this instruction */
		cpu->R[15] = instructAddr;
		cpu->next_instruction = instructAddr;
		return 1;
	}
	/* first run, set us into waiting state */
	cpu->waitIRQ = 1;
	cpu->wirq = 1;
	/* and set next instruction to repeat this */
	cpu->R[15] = instructAddr;
	cpu->next_instruction = instructAddr;
	/* CHECKME: IME shouldn't be modified (?) */
	MMU.reg_IME[0] = 1;
	return 1;
}

BOOL armcp15_moveARM2CP(armcp15_t *armcp15, u32 val, u8 CRn, u8 CRm, u8 opcode1, u8 opcode2)
{
	if(armcp15->cpu->CPSR.bits.mode == USR) return FALSE;
	
	switch(CRn)
	{
		case 1 :
		if((opcode1==0) && (opcode2==0) && (CRm==0))
		{
			armcp15->ctrl = val;
			MMU.ARM9_RW_MODE = BIT7(val);
			armcp15->cpu->intVector = 0x0FFF0000 * (BIT13(val));
			armcp15->cpu->LDTBit = !BIT15(val); //TBit
			/*if(BIT17(val))
			{
				log::ajouter("outch !!!!!!!");
			}
			if(BIT19(val))
			{
				log::ajouter("outch !!!!!!!");
			}*/
			return TRUE;
		}
		return FALSE;
		case 2 :
		if((opcode1==0) && (CRm==0))
		{
			switch(opcode2)
			{
				case 0 :
					armcp15->DCConfig = val;
					return TRUE;
				case 1 :
					armcp15->ICConfig = val;
					return TRUE;
				default :
					return FALSE;
			}
		}
		return FALSE;
		case 3 :
		if((opcode1==0) && (opcode2==0) && (CRm==0))
		{
			armcp15->writeBuffCtrl = val;
			return TRUE;
		}
		return FALSE;
		case 5 :
		if((opcode1==0) && (CRm==0))
		{
			switch(opcode2)
			{
				case 2 :
					armcp15->DaccessPerm = val;
					armcp15_maskPrecalc(armcp15);
 					return TRUE;
				case 3 :
					armcp15->IaccessPerm = val;
					armcp15_maskPrecalc(armcp15);
					return TRUE;
				default :
					return FALSE;
			}
		}
		return FALSE;
		case 6 :
		if((opcode1==0) && (opcode2==0))
		{
			switch(CRm)
			{
				case 0 :
					armcp15->protectBaseSize0 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 1 :
					armcp15->protectBaseSize1 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 2 :
					armcp15->protectBaseSize2 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 3 :
					armcp15->protectBaseSize3 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 4 :
					armcp15->protectBaseSize4 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 5 :
					armcp15->protectBaseSize5 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 6 :
					armcp15->protectBaseSize6 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				case 7 :
					armcp15->protectBaseSize7 = val;
					armcp15_maskPrecalc(armcp15) ;
					return TRUE;
				default :
					return FALSE;
			}
		}
		return FALSE;
		case 7 :
		if((CRm==0)&&(opcode1==0)&&((opcode2==4)))
		{
			CP15wait4IRQ(armcp15->cpu);
			return TRUE;
		}
		return FALSE;
		case 9 :
		if((opcode1==0))
		{
			switch(CRm)
			{
				case 0 :
				switch(opcode2)
				{
					case 0 :
						armcp15->DcacheLock = val;
						return TRUE;
					case 1 :
						armcp15->IcacheLock = val;
						return TRUE;
					default :
						return FALSE;
			}
			case 1 :
			switch(opcode2)
			{
				case 0 :
					armcp15->DTCMRegion = val;
					MMU.DTCMRegion = val & 0x0FFFFFFC0;
					/*sprintf(logbuf, "%08X", val);
					log::ajouter(logbuf);*/
					return TRUE;
				case 1 :
					armcp15->ITCMRegion = val;
					/* ITCM base is not writeable! */
					MMU.ITCMRegion = 0;
					return TRUE;
				default :
					return FALSE;
				}
			}
		}
		return FALSE;
		default :
			return FALSE;
	}
}



