/*---------------------------------------------------------------------------*
  Project:  NitroSDK - CARD - libraries
  File:     card_rom.c

  Copyright 2003-2004 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: card_rom.c,v $
  Revision 1.19  2004/11/22 07:38:29  yosizaki
  add cardi_rom_base.

  Revision 1.18  2004/11/12 05:45:10  yosizaki
  add CARDi_CheckValidity().

  Revision 1.17  2004/10/18 00:22:19  yosizaki
  change latency1 of READ-ID.

  Revision 1.16  2004/10/08 11:58:20  yosizaki
  move CARD-DMA setting to mi/mi_dma_card.c.
  delete unused functions.

  Revision 1.15  2004/10/04 04:39:13  yada
  avoid to call CARD_InitPulledOutCallback() twice
  even if CARD_Init() and FS_Init() are called.

  Revision 1.14  2004/09/27 13:45:08  yada
  consider for SMALL_BUILD

  Revision 1.13  2004/09/15 05:54:30  yada
  add card pulled off callback setting

  Revision 1.12  2004/09/02 11:54:42  yosizaki
  fix CARD include directives.

  Revision 1.11  2004/08/31 10:59:28  yosizaki
  add errorcode CARD_RESULT_SUCCESS in some functions.

  Revision 1.10  2004/08/30 04:33:22  yada
  remove MI_XXX definitions because those are already defined in dma.h

  Revision 1.9  2004/08/23 10:38:10  yosizaki
  add write-command.
  add retry count on writing timeout.

  Revision 1.8  2004/07/28 13:18:06  yosizaki
  asynchronous mode support.

  Revision 1.7  2004/07/23 08:29:06  yosizaki
  fix CARD_ReadRomID.

  Revision 1.6  2004/07/19 13:15:15  yosizaki
  move thread to card_common.c

  Revision 1.5  2004/07/13 07:59:25  yosizaki
  fix CARD-asynchronous operation.

  Revision 1.4  2004/07/10 10:19:50  yosizaki
  add card_common.c

  Revision 1.3  2004/07/09 08:40:27  yosizaki
  change implement of synchronous mode.

  Revision 1.2  2004/07/08 13:43:56  yosizaki
  change ROM-access (support all case)

  Revision 1.1  2004/06/28 01:52:48  yosizaki
  (none)

  $NoKeywords: $
 *---------------------------------------------------------------------------*/


#include <nitro.h>


#include "../include/card_spi.h"
#include "../include/card_common.h"


/*
 * Reference: Transfer speed
 * [MI_CpuCopy(src_align32, dst_align32, 512)  (InvalidateAll completed)]
 *   MI_CpuCopy8    : 61[us]
 *   MI_CpuCopy16   : 85[us]
 *   MI_CpuCopy32   : 57[us]
 *   MI_CpuCopyFast : 32[us]
 *
 * [MI_CpuCopy(src_align2, dst_align32, 512)  (InvalidateAll completed)]
 *   MI_CpuCopy8    : 85[us]
 *   MI_CpuCopy16   : 84[us]
 *
 * [MI_CpuCopy(src_align1, dst_align32, 512)  (InvalidateAll completed)]
 *   MI_CpuCopy8    : 84[us]
 *
 * [MI_CpuCopy(src_align32, dst_align1, 512)  (InvalidateAll completed)]
 *   MI_CpuCopy8    : 84[us]
 */


/********************************************************************/
/* Variables */

#pragma	pack(push, 4)

/* Structure for status control common to CARD / CARTRIDGE / PXI  */
typedef	struct	CARDRomStat
{
	/* Parameters for ROM access */
	void	(*read_func)(struct CARDRomStat*);
	u32	ctrl;	/* Specific to CARD: control flag */
	u8	*cache_page;	/* Current cache page */

	/* Wait for PXI reception */
	u32	dummy[5];
	/* 32 bytes alignment */

	/* Cache the most recently transferred page  (PXI/CARD) */
	u8	cache_buf[CARD_ROM_PAGE_SIZE];

} CARDRomStat;

#pragma	pack(pop)

static CARDRomStat	rom_stat ATTRIBUTE_ALIGN(32);

/* (Normally 0) */
extern	u32	cardi_rom_base;
u32	cardi_rom_base;

SDK_ASSERT_ON_COMPILE(sizeof(rom_stat) % 32 == 0);



/********************************************************************/
/* Functions */

/* Common *************************************************************/

/*
 * Determines whether specified range is in TCM range.
 * Becomes false also when included internally.
 */
static inline BOOL	CARDi_IsTcm(u32 dst, u32 len)
{
#if	defined(SDK_ARM9)
	const u32	i = HW_ITCM/*OS_GetITCMAddress()*/;
	const u32	d = OS_GetDTCMAddress();
	return
		((i < dst + len) && (i + HW_ITCM_SIZE > dst)) ||
		((d < dst + len) && (d + HW_DTCM_SIZE > dst));
#else
	(void)dst;
	(void)len;
	return	FALSE;
#endif
}

/* Processing after reading direct transfer destination */
static inline BOOL	CARDi_OnReadPageDirect(CARDRomStat *arg)
{
	CARDiCommon	*p = &cardi_common;
	(void)arg;
	p->src += CARD_ROM_PAGE_SIZE;
	p->dst += CARD_ROM_PAGE_SIZE;
	p->len -= CARD_ROM_PAGE_SIZE;
	return	(p->len > 0);
}

/* determines previous cache */
static BOOL	CARDi_ReadFromCache(CARDRomStat	*p)
{
	CARDiCommon	*c = &cardi_common;
	const u32	cur_page = CARD_ALIGN_HI_BIT(c->src);
	if(cur_page == (u32)p->cache_page) {
		const u32	mod = c->src - cur_page;
		u32	len = CARD_ROM_PAGE_SIZE - mod;
		if(len > c->len)	len = c->len;
		MI_CpuCopy8(p->cache_buf + mod, (void*)c->dst, len);
		c->src += len;
		c->dst += len;
		c->len -= len;
	}
	return	(c->len > 0);
}

/* Processing for ROM access completion */
static void	CARDi_ReadEnd(void)
{
	CARDRomStat	* const p = &rom_stat;
	CARDiCommon	* const c = &cardi_common;
	const MIDmaCallback	func = c->callback;
	void	* const arg = c->callback_arg;

	c->cmd->result = CARD_RESULT_SUCCESS;
	CARDi_EndTask(c);
	if(func)	(*func)(arg);
}


/********************************************************************
 * PXI
 ********************************************************************/

/* Transfer requests from PXI are only generated from ARM9 to ARM7 on TEG version. */
#if	defined(SDK_TEG)


/********************************************************************/
/* function */

#if	defined(SDK_ARM9)

/* CARD read request from PXI  */
static void	CARDi_ReadPxi(CARDRomStat *p)
{
	CARDiCommon	* const c = &cardi_common;
	do {
		/* PXI read request in page units */
		p->cache_page = (u8*)CARD_ALIGN_HI_BIT(c->src);
		DC_InvalidateRange(p->cache_buf, sizeof(p->cache_buf));
		c->cmd->src = (u32)p->cache_page;
		c->cmd->dst = (u32)p->cache_buf;
		c->cmd->len = sizeof(p->cache_buf);

		if(!CARDi_Request(c, CARD_REQ_READ_ROM, 1))
			break;
		{	/* Copy from cache */
			const u32	mod = (u32)(c->src - (u32)p->cache_page);
			u32	len = CARD_ROM_PAGE_SIZE - mod;
			if(len > c->len)	len = c->len;
			MI_CpuCopy8(p->cache_buf + mod, (void*)c->dst, len);
			c->src += len;
			c->dst += len;
			c->len -= len;
		}
	} while(c->len > 0);
}

#endif	/* SDK_ARM9 */


#endif	/* SDK_TEG */
/********************************************************************/


/********************************************************************
 * CARTRIDGE
 ********************************************************************/

#if	defined(SDK_TEG)

/********************************************************************/
/* Functions */

/*
 * Card read request from cartridge.
 * Tuning priority is very low, as it is not part of the product environment.
 */
static void	CARDi_ReadCartridge(CARDRomStat *p)
{
	CARDiCommon	* const c = &cardi_common;
	/* Statically determine if 32bit DMA is available */
	const BOOL	is_dma_static =
#if	defined(SDK_ARM9)
		!CARDi_IsTcm(c->dst, c->len) &&
#endif
		(c->dma <= MI_DMA_MAX_NUM) &&
		(((c->src ^ c->dst) & 3) == 0);
	/* Repeat direct transfer in page units  */
	const u8	*src = (const u8*)(c->src + HW_CTRDG_ROM);
	u8	*dst = (u8*)c->dst;
	u32	len = c->len;

	(void)p;

	while(len > 0) {
		u32	size = (u32)(CARD_ROM_PAGE_SIZE - ((u32)dst & 31));
		if(size >= len)	size = len;
		if(is_dma_static && !(((u32)dst | size) & 31)) {
#if	defined(SDK_ARM9)
			DC_InvalidateRange(dst, size);
			IC_InvalidateRange(dst, size);
#endif
			MI_DmaCopy32(c->dma, src, dst, size);
		} else {
			MI_CpuCopy8(src, dst, size);
		}
		src += size;
		dst += size;
		len -= size;
	}
}

#endif	/* SDK_TEG */
/********************************************************************/


/********************************************************************
 * CARD
 ********************************************************************/

#if	defined(SDK_TS) || defined(SDK_ARM7)


/********************************************************************/
/* Constants */

#define REG_CARD_MASTER_CNT	(HW_REG_BASE + 0x1A1)
#define REG_CARDCNT			(HW_REG_BASE + 0x1A4)
#define REG_CARD_CMD		(HW_REG_BASE + 0x1A8)
#define REG_CARD_DATA		(HW_REG_BASE + 0x100010)

#define CARDMST_SEL_ROM		0x00
#define CARDMST_IF_ENABLE	0x40
#define CARDMST_ENABLE		0x80

/* Hardware only supports units of one page */
#define	CARD_DATA_READY		0x00800000
#define	CARD_COMMAND_PAGE	0x01000000
#define	CARD_COMMAND_ID		0x07000000
#define	CARD_COMMAND_MASK	0x07000000
#define	CARD_RESET_HI		0x20000000
#define	CARD_RESET_LO		0x00000000
#define	CARD_ACCESS_MODE	0x40000000
#define	CARD_READ_MODE		0x00000000
#define	CARD_WRITE_MODE		0x40000000
#define	CARD_START			0x80000000
#define CARD_LATENCY1_MASK  0x00001fff

#define MROMOP_G_READ_ID	0xB8000000
#define MROMOP_G_READ_PAGE	0xB7000000

#define	MROMCNT_GAME_BUF	(HW_ROM_HEADER_BUF + 0x60)

/********************************************************************/
/* Functions */

/* Retrieve card ROM control parameters */
static inline u32	CARDi_GetRomFlag(u32 flag)
{
	return	(u32)(
		(*(vu32*)MROMCNT_GAME_BUF & ~CARD_COMMAND_MASK) | flag |
		CARD_READ_MODE | CARD_START | CARD_RESET_HI);
}

/* Card command settings  */
static void	CARDi_SetRomOp(u32 cmd1, u32 cmd2)
{
	/* Wait for card ROM completion (just in case) */
	while((*(vu32*)REG_CARDCNT & CARD_START) != 0)	;
	/* Master enable */
	*(vu8*)REG_CARD_MASTER_CNT =
		CARDMST_SEL_ROM | CARDMST_ENABLE | CARDMST_IF_ENABLE;
	{	/* Command settings */
		vu8	* const p_cmd = (vu8*)REG_CARD_CMD;
		p_cmd[0] = (u8)(cmd1 >> (8 * 3));
		p_cmd[1] = (u8)(cmd1 >> (8 * 2));
		p_cmd[2] = (u8)(cmd1 >> (8 * 1));
		p_cmd[3] = (u8)(cmd1 >> (8 * 0));
		p_cmd[4] = (u8)(cmd2 >> (8 * 3));
		p_cmd[5] = (u8)(cmd2 >> (8 * 2));
		p_cmd[6] = (u8)(cmd2 >> (8 * 1));
		p_cmd[7] = (u8)(cmd2 >> (8 * 0));
	}
}

/* 1 page read command */
static inline void	CARDi_SetRomOpReadPage1(u32 src)
{
	CARDi_SetRomOp(
		(u32)(MROMOP_G_READ_PAGE | (src >> 8)),
		(u32)(src << 24));
}

/* Start CARD-DMA for one page */
static void	CARDi_SetCardDma(void)
{
	CARDiCommon	* const c = &cardi_common;
	CARDRomStat	* const p = &rom_stat;
	/* first, perform CARD-DMA settings */
	MIi_CardDmaCopy32(c->dma, (const void*)REG_CARD_DATA, (void*)c->dst, CARD_ROM_PAGE_SIZE);
	/* next, perform CARD-ROM settings */
	CARDi_SetRomOpReadPage1(c->src);
	*(vu32*)REG_CARDCNT = p->ctrl;
	/* asynchronous start from here */
}

/* CARD-DMA complete handler */
static void	CARDi_OnReadCard(void)
{
	CARDRomStat	* const p = &rom_stat;
	CARDiCommon	* const c = &cardi_common;
	MI_StopDma(c->dma);
	if(!CARDi_OnReadPageDirect(p)) {
		(void)OS_DisableIrqMask(OS_IE_CARD_DATA);
		(void)OS_ResetRequestIrqMask(OS_IE_CARD_DATA);
		CARDi_ReadEnd();
	} else {
		CARDi_SetCardDma();
	}
}

/*If possible with CARD-DMA, configure and return TRUE. */
static BOOL	CARDi_TryReadCardDma(CARDRomStat *p)
{
	CARDiCommon	* const c = &cardi_common;
	BOOL	is_aync = !((u32)c->dst & 3) &&		/* 32bit */
		(c->dma <= MI_DMA_MAX_NUM) &&			/* DMA */
		!CARDi_IsTcm(c->dst, c->len) &&			/* !TCM */
		!CARD_ALIGN_LO_BIT(c->src | c->len) &&	/* page */
		(c->len > 0);
	p->ctrl = CARDi_GetRomFlag(CARD_COMMAND_PAGE);
	if(is_aync) {
		/* Configure for asynchronous processing if completed only with DMA */
		OSIntrMode	bak_psr = OS_DisableInterrupts();
#if	defined(SDK_ARM9)
		DC_FlushRange((void*)c->dst, c->len);
		IC_InvalidateRange((void*)c->dst, c->len);
#endif
		(void)OS_SetIrqFunction(OS_IE_CARD_DATA, CARDi_OnReadCard);
		(void)OS_ResetRequestIrqMask(OS_IE_CARD_DATA);
		(void)OS_EnableIrqMask(OS_IE_CARD_DATA);
#if	defined(SDK_ARM9)
		DC_WaitWriteBufferEmpty();
#endif
		(void)OS_RestoreInterrupts(bak_psr);
		CARDi_SetCardDma();
	}
	return	is_aync;
}


/* Card read request from card */
static void	CARDi_ReadCard(CARDRomStat *p)
{
	/*
	 * Synchronous transfer with CPU (and DMA, if possible).
	 *  If aligned to 4 bytes
	 *  If misaligned by 1 byte
	 *  If misaligned by 2 bytes
	 *  If misaligned by 3 bytes
	 * Transfer as fast as possible using misalignment correction.
	 * For now, move to temporary memory, and rely on MI_CpuCopy8.
	 *
	 * If the card's busy time per word is long enough,
	 * performance will not be affected by always reading the last page into
	 * temporary memory.
	 */
	CARDiCommon	* const c = &cardi_common;
	for( ; ; ) {
		const u32	len = CARD_ROM_PAGE_SIZE;
		u32	src = CARD_ALIGN_HI_BIT(c->src);
		u32	dst;
		/* Determine if cache usage is necessary */
		if((src != c->src) ||
			((c->dst & 3) != 0) ||
			(c->len < len)) {
			dst = (u32)p->cache_buf;
			p->cache_page = (void*)src;
		} else {
			dst = c->dst;
		}
		/* Make the current settings */
		CARDi_SetRomOpReadPage1(src);
		{	/* CPU transfer (repeat loop) */
			u32	pos = 0;
			*(vu32*)REG_CARDCNT = p->ctrl;
			for( ; ; ) {
				const u32	ctrl = *(vu32*)REG_CARDCNT;
				if((ctrl & CARD_DATA_READY) != 0) {
					/* Store only the specified amount */
					u32 data = *(vu32*)REG_CARD_DATA;
					if(pos < len)
						((u32*)dst)[pos++] = data;
				}
				if(!(ctrl & CARD_START))	break;
			}
		}
		/* Advance the distance that has already been transferred */
		if(dst == c->dst) {
			if(!CARDi_OnReadPageDirect(p))
				break;
		} else {
			if(!CARDi_ReadFromCache(p))
				break;
		}
	}
}

/* Read the card ID */
u32	CARDi_ReadRomIDCore(void)
{
	CARDi_SetRomOp(MROMOP_G_READ_ID, 0);
	*(vu32*)REG_CARDCNT = (u32)(CARDi_GetRomFlag(CARD_COMMAND_ID) & ~CARD_LATENCY1_MASK);
    while(!(*(vu32*)REG_CARDCNT & CARD_DATA_READY))	;
	return	*(vu32*)REG_CARD_DATA;
}

#endif	/* defined(SDK_TS) || defined(SDK_ARM7) */


/********************************************************************/


#if	defined(SDK_ARM7)

/*
 * Card transfer resulting from request.
 * Always process synchronously.
 */
void	CARDi_ReadRomCore(u32 dma, const void *src, void *dst, u32 len)
{
	CARDRomStat	* const p = &rom_stat;
	CARDiCommon	* const c = &cardi_common;

	/* Set the current transfer parameters */
	c->dma = dma;
	c->src = (u32)src;
	c->dst = (u32)dst;
	c->len = (u32)len;
	c->callback = NULL;
	c->callback_arg = NULL;
	if(dma <= MI_DMA_MAX_NUM)	MI_StopDma(dma);
	p->ctrl = CARDi_GetRomFlag(CARD_COMMAND_PAGE);

	cardi_common.cur_th = OS_GetCurrentThread();
	CARDi_ReadCard(p);
}

#endif


/* Read the card ID */
u32	CARD_ReadRomID(void)
{
	CARDRomStat	* const p = &rom_stat;
	CARDiCommon	* const c = &cardi_common;

	u32	ret = 0;

	SDK_ASSERT(CARD_IsAvailable());
	SDK_ASSERT(CARDi_GetTargetMode() == CARD_TARGET_ROM);

	CARDi_WaitTask(c);
	c->callback = NULL;
	c->callback_arg = NULL;

#if	defined(SDK_TEG)
	/* Call not possible using cartridge with TEG */
	SDK_ASSERT(CARDi_IsTrueRom());
#endif

#if	defined(SDK_TEG) && defined(SDK_ARM9)
	/* If ARM9 on TEG, resolve with request */
	cardi_common.cur_th = OS_GetCurrentThread();

	(void)CARDi_Request(c, CARD_REQ_READ_ID, 1);

	ret = (u32)c->cmd->id;
#else
	/* Since direct access is possible, perform access here */
	ret = CARDi_ReadRomIDCore();
#endif
	CARDi_ReadEnd();
	return	ret;

}

/* Common processing for synchronous card read  */
static void	CARDi_ReadRomSyncCore(CARDiCommon *c)
{
	CARDRomStat	* const p = &rom_stat;
	(void)c;
	/* If it can be resolved by reading cache, stop  */
	if(!CARDi_ReadFromCache(p))
		;
	/* If not, perform access. (Card / cartridge / PXI) */
	else
		(*p->read_func)(p);
	CARDi_ReadEnd();
}

/* Read card */
void	CARDi_ReadRom(u32 dma,
	const void *src, void *dst, u32 len,
	MIDmaCallback callback, void *arg, BOOL is_async)
{
	CARDRomStat	* const p = &rom_stat;
	CARDiCommon	* const c = &cardi_common;

	SDK_ASSERT(CARD_IsAvailable());
	SDK_ASSERT(CARDi_GetTargetMode() == CARD_TARGET_ROM);

	/* Validation for ROM */
	CARD_VALIDITY_ASSERTION();

	CARDi_WaitTask(c);

	/* Set current transfer parameters */
	c->dma = dma;
	c->src = (u32)((u32)src + cardi_rom_base);
	c->dst = (u32)dst;
	c->len = (u32)len;
	c->callback = callback;
	c->callback_arg = arg;
	if(dma <= MI_DMA_MAX_NUM)	MI_StopDma(dma);

#if	defined(SDK_TS) || defined(SDK_ARM7)
	if(CARDi_TryReadCardDma(p)) {
		if(!is_async)
			CARD_WaitRomAsync();
	}
	else
#endif
	{
		if(is_async) {
			CARDi_SetTask(CARDi_ReadRomSyncCore);
		} else {
			c->cur_th = OS_GetCurrentThread();
			CARDi_ReadRomSyncCore(c);
		}
	}
}


void	CARD_WaitRomAsync(void)
{
	(void)CARDi_WaitAsync();
}

BOOL	CARD_TryWaitRomAsync(void)
{
	return	CARDi_TryWaitAsync();
}

void	CARD_Init(void)
{
	CARDiCommon	* const p = &cardi_common;

	if(!p->flag) {
		p->flag = CARD_STAT_INIT;
		p->src = p->dst = p->len = 0;
		p->dma = (u32)~0;
		p->callback = NULL;
		p->callback_arg = NULL;

		cardi_rom_base = 0;

		CARDi_InitCommon();

		{
			CARDRomStat	* const p = &rom_stat;
			/*
			 * Determine whether card, cartridge or PXI is being used.
			 *
			 * +-------------------++--------------------+
			 * |         TS        ||                    |
			 * +-----+------+------++   CARD (direct)    |
			 * |     |      | ARM7 ||                    |
			 * |     | CARD |------++--------------------+
			 * | TEG |      | ARM9 ||   PXI (to ARM7)    |
			 * |     |------+------++--------------------+
			 * |     |  CARTRIDGE  || CARTRIDGE (direct) |
			 * +-----+-------------++--------------------+
			 *
			 *Also, there are a total of 9 access methods, depending on whether
			 * the transfer destination is synchronous or asynchronous
			 *
			 */
#if	defined(SDK_TS)
			/* If TS, always use card */
			p->read_func = CARDi_ReadCard;
#else
			/* If TEG, cartridge could also be used */
			if(!CARDi_IsTrueRom())
				p->read_func = CARDi_ReadCartridge;
			/* If card with TEG, access is possible only from the ARM7 */
			else {
#if	defined(SDK_ARM9)
				/* Thus, from the ARM9, access via PXI */
				p->read_func = CARDi_ReadPxi;
#else
				/* Thus, from the ARM7 access directly */
				p->read_func = CARDi_ReadCard;
#endif	/* SDK_ARM9 */
			}

#endif	/* SDK_TS */
		}

#ifndef SDK_SMALL_BUILD
		//---- for detect pulled out card
		CARD_InitPulledOutCallback();
#endif
	}
}
