/*

	disc_io.c

	uniformed io-interface to work with Chishm's FAT library

	Written by MightyMax

	Modified by Chishm:
	2005-11-06
		* Added WAIT_CR modifications for NDS

	Modified by www.neoflash.com:
	2006-02-03
		* Added SUPPORT_* defines, comment out any of the SUPPORT_* defines in disc_io.h to remove support
		  for the given interface and stop code being linked to the binary

	    * Added support for MK2 MMC interface

		* Added disc_Cache* functions

	Modified by Chishm:
	2006-02-05
		* Added Supercard SD support

	Modified by CyteX:
	2006-02-26
		* Added EFA2 support
*/

#ifdef NDS
	#include <nds.h>
#endif

#include "disc_io.h"


// Include known io-interfaces:
#ifdef SUPPORT_MPCF
 #include "io_mpcf.h"
#endif

#ifdef SUPPORT_M3CF
 #include "io_m3cf.h"
#endif

#ifdef SUPPORT_M3SD
 #include "io_m3sd.h"
#endif

#ifdef SUPPORT_SCCF
 #include "io_sccf.h"
#endif

#ifdef SUPPORT_SCSD
 #include "io_scsd.h"
#endif

#ifdef SUPPORT_FCSR
 #include "io_fcsr.h"
#endif

#ifdef SUPPORT_NMMC
 #include "io_nmmc.h"
#endif

#ifdef SUPPORT_EFA2
 #include "io_efa2.h"
#endif

#ifdef SUPPORT_NJSD
 #include "io_njsd.h"
#endif

#ifdef SUPPORT_MMCF
 #include "io_mmcf.h"
#endif

#include "io_dldi.h"

// Keep a pointer to the active interface
LPIO_INTERFACE active_interface = 0;


/*

	Disc Cache functions
	2006-02-03:
		Added by www.neoflash.com

*/
int discDetect = 0;

int dldiFound = FALSE;

#ifdef DISC_CACHE

#include <string.h>

#define CACHE_FREE 0xFFFFFFFF

static u8 cacheBuffer[ DISC_CACHE_COUNT * 512 ];

static struct {
	u32 sector;
	u32 dirty;
	u32 count;
} cache[ DISC_CACHE_COUNT ];

FATDevice currentDevice;

static u32 disc_CacheFind(u32 sector) {
	u32 i;

	for( i = 0; i < DISC_CACHE_COUNT; i++ )	{
		if( cache[ i ].sector == sector )
			return i;
	}

	return CACHE_FREE;
}

static u32 disc_CacheFindFree(void) {

	u32 i = 0, j;
	u32 count = -1;

	for( j = 0; j < DISC_CACHE_COUNT; j++ )	{

		if( cache[ j ].sector == CACHE_FREE ) {
			i = j;
			break;
		}

		if( cache[ j ].count < count ) {
			count = cache[ j ].count;
			i = j;
		}
	}
	/*
	if( cache[ i ].sector != CACHE_FREE && cache[i].dirty != 0 ) {

		active_interface->fn_WriteSectors( cache[ i ].sector, 1, &cacheBuffer[ i * 512 ] );
		// todo: handle write error here

		cache[ i ].sector = CACHE_FREE;
		cache[ i ].dirty = 0;
		cache[ i ].count = 0;
	}*/

	return i;
}

void disc_CacheInit(void)	{

	u32 i;

	for( i = 0; i < DISC_CACHE_COUNT; i++ )	{
		cache[ i ].sector = CACHE_FREE;
		cache[ i ].dirty = 0;
		cache[ i ].count = 0;
	}

}

bool disc_CacheFlush(void)	{

	u32 i;

	if( !active_interface )	return false;

	for( i = 0; i < DISC_CACHE_COUNT; i++ )	{
		if( cache[ i ].sector != CACHE_FREE && cache[ i ].dirty != 0 )	{
			if( active_interface->fn_WriteSectors( cache[ i ].sector, 1, &cacheBuffer[ i * 512 ] ) == false )
				return false;

			cache[ i ].dirty = 0;
		}
	}
	return true;
}

bool disc_CacheReadSector( void *buffer, u32 sector) {
	u32 i = disc_CacheFind( sector );
	if( i == CACHE_FREE ) {
		i = disc_CacheFindFree();
		cache[ i ].sector = sector;
		if( active_interface->fn_ReadSectors( sector, 1, &cacheBuffer[ i * 512 ] ) == false )
			return false;
	}
#ifdef DISK_CACHE_DMA
		DMA3_SRC = (u32)&cacheBuffer[ i * 512 ]
		DMA3_DEST = (u32)buffer;
		DMA3_CR = 128 | DMA_COPY_WORDS;
#else
	memcpy( buffer, &cacheBuffer[ i * 512 ], 512 );
#endif
	cache[ i ].count++;
	return true;
}

bool disc_CacheWriteSector( void *buffer, u32 sector ) {
	u32 i = disc_CacheFind( sector );
	if( i == CACHE_FREE ) {
		i = disc_CacheFindFree();
		cache [ i ].sector = sector;
	}
#ifdef DISK_CACHE_DMA
		DMA3_SRC = (u32)buffer;
		DMA3_DEST = (u32)&cacheBuffer[ i * 512 ];
		DMA3_CR = 128 | DMA_COPY_WORDS;
#else
	memcpy( &cacheBuffer[ i * 512 ], buffer, 512 );
#endif
	cache[ i ].dirty=1;
	cache[ i ].count++;
	return true;
}

#endif

/*

	Hardware level disc funtions

*/

void disc_setEnable(int disc) {
	discDetect = disc;
}

bool disc_setGbaSlotInterface (void)
{
	// If running on an NDS, make sure the correct CPU can access
	// the GBA cart. First implemented by SaTa.
#ifdef NDS
 #ifdef ARM9
//	WAIT_CR &= ~(0x8080);
 #endif
 #ifdef ARM7
//	WAIT_CR |= (0x8080);
 #endif
#endif


#ifdef SUPPORT_M3SD
	if (discDetect == 1)	{
		// check if we have a M3 perfect SD plugged in
		active_interface = M3SD_GetInterface() ;
		if (active_interface->fn_StartUp())
		{
			// set M3 SD as default IO
			currentDevice = DEVICE_M3SD;
			return true ;
		} ;
	}
#endif




#ifdef SUPPORT_MMCF
	// check if we have a GBA Flash Cart plugged in
	active_interface = MMCF_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set MMCF as default IO
		currentDevice = DEVICE_MMCF;
		return true ;
	} ;
#endif



#ifdef SUPPORT_M3CF
	// check if we have a M3 perfect CF plugged in
	active_interface = M3CF_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set M3 CF as default IO
		currentDevice = DEVICE_M3CF;
		return true ;
	} ;
#endif


#ifdef SUPPORT_MPCF
	// check if we have a GBA Movie Player plugged in
	active_interface = MPCF_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set GBAMP as default IO
		currentDevice = DEVICE_MPCF;
		return true ;
	} ;
#endif


#ifdef SUPPORT_SCCF
	// check if we have a SuperCard CF plugged in
	active_interface = SCCF_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set SC CF as default IO
		currentDevice = DEVICE_SCCF;
		return true ;
	} ;
#endif



#ifdef SUPPORT_EFA2
	// check if we have a EFA2 plugged in
	active_interface = EFA2_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		return true ;
	} ;
#endif


#ifdef SUPPORT_FCSR
	// check if we have a GBA Flash Cart plugged in
	active_interface = FCSR_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set FC as default IO
		return true ;
	} ;
#endif




	return false;
}


FATDevice disc_getDeviceId() {
	return currentDevice;
}

void disc_getDldiId(char* id) {
	char* driverId = (char *) &_io_dldi;
	id[0] = driverId[0];
	id[1] = driverId[1];
	id[2] = driverId[2];
	id[3] = driverId[3];
	id[4] = '\0';
}

#ifdef NDS
// Check the DS card slot for a valid memory card interface
// If an interface is found, it is set as the default interace
// and it returns true. Otherwise the default interface is left
// untouched and it returns false.


bool disc_setDsSlotInterface (void)
{
#ifdef ARM9
	REG_EXEMEMCNT &= ~(1<<11);
#endif
#ifdef ARM7
	REG_EXEMEMCNT |= (1<<11);
#endif

	active_interface = DLDI_GetInterface();

	if (stricmp((char *)(&_dldi_driver_name), "Default (No interface)")) {
		char name[48];
		memcpy(name, &_dldi_driver_name, 48);
		name[47] = '\0';
		consolePrintf("DLDI Device:\n'%s'\n", name);
		dldiFound = TRUE;
	} else {
		consolePrintf("DLDI Driver not patched!\n");
		dldiFound = FALSE;
	}

	if (active_interface->fn_StartUp()) {
		consolePrintf("DLDI Driver Initialised OK!\n");
		currentDevice = DEVICE_DLDI;
		return true;
	} else {
		consolePrintf("DLDI Initialise failed.\n");
	}

#ifdef SUPPORT_SCSD
	// check if we have a SuperCard SD plugged in
	if (discDetect == 2) {
		active_interface = SCSD_GetInterface() ;
		consolePrintf("SCSD!");
		if (active_interface->fn_StartUp())
		{
			// set SC SD as default IO
			currentDevice = DEVICE_SCSD;
			return true ;
		} ;
	}
#endif

#ifdef SUPPORT_NJSD
	// check if we have a GBA Flash Cart plugged in
	active_interface = NJSD_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set NJSD as default IO
		currentDevice = DEVICE_NJSD;
		return true ;
	} ;
#endif

#ifdef SUPPORT_NMMC
	// check if we have a Neoflash MK2 / MK3 plugged in
	active_interface = NMMC_GetInterface() ;
	if (active_interface->fn_StartUp())
	{
		// set Neoflash MK2 / MK3 as default IO
		currentDevice = DEVICE_NMMC;
		return true ;
	} ;
#endif




	return false;
}
#endif


bool disc_Init(void)
{
#ifdef DISC_CACHE
	disc_CacheInit();
#endif


	if (active_interface != 0) {
		return true;
	}

#ifdef NDS
	if (disc_setDsSlotInterface()) {
		return true;
	}
#endif

	if (disc_setGbaSlotInterface()) {
		return true;
	}

	// could not find a working IO Interface
	active_interface = 0 ;
	return false ;
}

bool disc_IsInserted(void)
{
	if (active_interface) return active_interface->fn_IsInserted() ;
	return false ;
}

bool disc_ReadSectors(u32 sector, u8 numSecs, void* buffer)
{
#ifdef DISC_CACHE
	u8 *p=(u8*)buffer;
	u32 i;
	u32 inumSecs=numSecs;
	if(numSecs==0)
		inumSecs=256;
	for( i = 0; i<inumSecs; i++)	{
		if( disc_CacheReadSector( &p[i*512], sector + i ) == false )
			return false;
	}
	return true;
#else
	if (active_interface) return active_interface->fn_ReadSectors(sector,numSecs,buffer) ;
	return false ;
#endif
}

bool disc_WriteSectors(u32 sector, u8 numSecs, void* buffer)
{
/*#ifdef DISC_CACHE
	u8 *p=(u8*)buffer;
	u32 i;
	u32 inumSecs=numSecs;
	if(numSecs==0)
		inumSecs=256;
	for( i = 0; i<inumSecs; i++)	{
		if( disc_CacheWriteSector( &p[i*512], sector + i ) == false )
			return false;
	}
	return true;
#else*/
#ifdef DISC_CACHE
	disc_CacheInit();
#endif

#define MISALIGNMENT_BODGE

#ifdef MISALIGNMENT_BODGE
	// This bodge works around problems with some card reader drivers which require data to be
	// aligned to 2- or 4-byte boundaries it varies which one they require.  This bodge sorts
	// it but also reduces write speed as it doesn't use the multi-sector write capability any
	// more.  A better fix will be written for a future version.

	if (active_interface) {
		u8 sectorBuffer[512];
		int r;

		for (r = 0; r < numSecs; r++) {
			memcpy(sectorBuffer, &((char *)buffer)[r * 512], 512);

			if (!active_interface->fn_WriteSectors(sector + r, 1, sectorBuffer))
			{
				return false;
			}
		}


		return true;
	}

#else
	if (active_interface) return active_interface->fn_WriteSectors(sector,numSecs,buffer) ;
#endif
//#endif
	return false ;
}

bool disc_ClearStatus(void)
{
	if (active_interface) return active_interface->fn_ClearStatus() ;
	return false ;
}

bool disc_Shutdown(void)
{
#ifdef DISC_CACHE
	disc_CacheFlush();
#endif
	if (active_interface) active_interface->fn_Shutdown() ;
	active_interface = 0 ;
	return true ;
}

u32	disc_HostType (void)
{
	if (active_interface) {
		return active_interface->ul_ioType;
	} else {
		return 0;
	}
}