/*---------------------------------------------------------------------------*
  Project:  NitroSDK - CARD - libraries
  File:     card_spi.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_spi.c,v $
  Revision 1.10  2004/09/10 10:33:26  yosizaki
  set CARDi_WaitPrevCommand timeout 50ms.

  Revision 1.9  2004/09/06 09:20:49  yosizaki
  fix backup access error at 1st time.

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

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

  Revision 1.6  2004/08/18 08:19:02  yosizaki
  change CARDBackupType format.

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

  Revision 1.4  2004/07/23 08:28:42  yosizaki
  change backup-routine to ARM7 only.

  Revision 1.3  2004/07/19 13:15:42  yosizaki
  add CARDi_ProgramBackupCore.

  Revision 1.2  2004/07/05 08:37:21  yosizaki
  change CardBackupType to CARDBackupType.

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

 *---------------------------------------------------------------------------*/


#include <nitro.h>

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

/* CARD-SPI Controls */


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

/* Each bit of reg_MI_MCCNT0 */

#define MCCNT0_SPI_CLK_MASK	0x0003	/* Baud rate configuration mask */
#define MCCNT0_SPI_CLK_4M	0x0000	/* Baud rate  4.19MHz */
#define MCCNT0_SPI_CLK_2M	0x0001	/* Baud rate  2.10MHz */
#define MCCNT0_SPI_CLK_1M	0x0002	/* Baud rate  1.05MHz */
#define MCCNT0_SPI_CLK_524K	0x0003	/* Baud rate  524kHz */
#define MCCNT0_SPI_BUSY		0x0080	/* SPI busy flag */
#define	MMCNT0_SEL_MASK		0x2000	/* CARD ROM / SPI selection mask */
#define	MMCNT0_SEL_CARD		0x0000	/* CARD ROM selection  */
#define	MMCNT0_SEL_SPI		0x2000	/* CARD SPI selection  */
#define MCCNT0_INT_MASK		0x4000	/* Transmission complete interrupt mask */
#define MCCNT0_INT_ON		0x4000	/* Transmission complete interrupt valid  */
#define MCCNT0_INT_OFF		0x0000	/* Transmission complete interrupt invalid */
#define MCCNT0_MASTER_MASK	0x8000	/* CARD master mask */
#define MCCNT0_MASTER_ON	0x8000	/* CARD master  ON */
#define MCCNT0_MASTER_OFF	0x0000	/* CARD master  OFF */

/* Macro for calculating the V-count time */
#define	V_SYNC_FREQ		59.8983
#define	V_BLANK_LINE	160
#define	H_LINE_MAX		263

/* Structure for measuring the time with the V-count */
typedef	struct
{
	u16	v;
	u16	reserved;
	int	rest;
} CARDiVCountTimer;


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

typedef	struct
{	/* SPI internal management parameters */
	u32	addr_width;	/* Number of address expression bytes */
	u32	rest_comm;	/* Total number of bytes left to be sent */
	u32	src;	/* Send source  */
	u32	dst;	/* Send destination  */
	BOOL	cmp;	/* Comparison results  */
} CARDiParam;

static CARDiParam	cardi_param;

const CardDeviceInfo	*cardi_device;


#if	!defined(CARD_DEBUG)

static inline void	CARD_DEBUG(const char *s, ...)
{
	(void)s;
}

#endif



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

void	CARDi_CommArray(const void *src, void *dst, u32 n, 	void (*func)(CARDiParam*));
void	CARDi_CommReadCore(CARDiParam *p);
void	CARDi_CommWriteCore(CARDiParam *p);
void	CARDi_CommVerifyCore(CARDiParam *p);

static inline void	CARDi_CommArrayRead(void *dst, u32 n)
{
	CARDi_CommArray(NULL, dst, n, CARDi_CommReadCore);
}
static inline void	CARDi_CommArrayWrite(const void *src, u32 n)
{
	CARDi_CommArray(src, NULL, n, CARDi_CommWriteCore);
}
static inline void	CARDi_CommArrayVerify(const void *src, u32 n)
{
	CARDi_CommArray(src, NULL, n, CARDi_CommVerifyCore);
}



/* Validate the CARD-SPI */
static inline void	CARDi_EnableSpi(u32 cont)
{
	/*
	 * If in the future a device with a slow clock speed is introduced,
	 * MCCNT0_SPI_CLK_4M is added to the property array and changed to dynamic.
	 */
	const u16	ctrl = (u16)(MCCNT0_MASTER_ON |
		 MCCNT0_INT_OFF | MMCNT0_SEL_SPI | MCCNT0_SPI_CLK_4M | cont);
	reg_MI_MCCNT0 = ctrl;
}

/* Wait for completion */
static inline void	CARDi_WaitBusy(void)
{
    while((reg_MI_MCCNT0 & MCCNT0_SPI_BUSY) != 0)
		;
}

/* Initialization of the Countdown */
static inline void	CARDi_InitVCountTimer(CARDiVCountTimer *p, int line)
{
	p->v = *(vu16*)REG_VCOUNT_ADDR;
	p->rest = line;
}

/*
 * Countdown update.
 * If the update interval is open more than one frame,
 * it is ignored with a 1 frame unit (the timeout is prolonged)
 */
static BOOL	CARDi_UpdateVCountTimer(CARDiVCountTimer *p)
{
	const u16	new_v = *(vu16*)REG_VCOUNT_ADDR;
	const int	dif = new_v - p->v;
	if(dif) {
		p->v = new_v;
		p->rest -= dif;
		if(dif < 0)
			p->rest -= H_LINE_MAX;
	}
	return	(p->rest > 0);
}


/* Determine whether the busy has been removed in the read status */
static BOOL	CARDi_CommandCheckBusy(void)
{
	const u8	buf = COMM_READ_STATUS;
	u8	dst;
	CARDi_CommandBegin(1 + 1);
	CARDi_CommArrayWrite(&buf, 1);
	CARDi_CommArrayRead(&dst, 1);
	CARDi_CommandEnd(0, 0);
	return	((dst & COMM_STATUS_WIP_BIT) == 0);
}

/* Configuration of the size of the command that performs the communication from this point */
void	CARDi_CommandBegin(int len)
{
	cardi_param.rest_comm = (u32)len;
}

/*  command send completion process */
void	CARDi_CommandEnd(u32 force_wait, u32 timeout)
{
	BOOL	ret = TRUE;

	if(force_wait + timeout > 0) {
		/*
		 * If there is a forced wait, only wait for that part.
		 * Unlike what was first talked about, the FLASH wait was also forced, so 
		 * cardi_device->force_loop member was gotten rid of.
		 */
		if(force_wait > 0) {
			CARDiVCountTimer	timer[1];
			CARDi_InitVCountTimer(timer, (int)force_wait);
			while(CARDi_UpdateVCountTimer(timer))
				OS_SpinWait((u32)(V_SYNC_FREQ * H_LINE_MAX / HW_CPU_CLOCK));
		}
		if(timeout > 0) {
			/*
			 * Only the PageWrite command is "forced wait only in the first half", so
			 * this kind of special loop will happen.
			 */
			CARDiVCountTimer	timer[1];
			CARDi_InitVCountTimer(timer, (int)(timeout - force_wait));
			ret = FALSE;
			while(!ret && CARDi_UpdateVCountTimer(timer)) {
				OS_SpinWait((u32)(V_SYNC_FREQ * H_LINE_MAX / HW_CPU_CLOCK));
				ret = CARDi_CommandCheckBusy();
			}
		} else {
			/*
			 * Other commands have waited the designated time, so
			 * ReadStatus is good only one time.
			 */
			ret = CARDi_CommandCheckBusy();
		}
	}
	/* Timeout if coming up to here and FALSE  */
	if(!ret) {
		cardi_common.cmd->result = CARD_RESULT_TIMEOUT;
	}
}

/* busy check before command */
static void	CARDi_WaitPrevCommand(void)
{
	/* 50[ms] wait */
	CARDi_CommandEnd(0, 790);
}


/* shared processes for continuous communication */
void	CARDi_CommArray(const void *src, void *dst, u32 n,
	void (*func)(CARDiParam*))
{
	CARDiParam	* const p = &cardi_param;
	p->src = (u32)src;
	p->dst = (u32)dst;
	CARDi_EnableSpi(CSPI_CONTINUOUS_ON);
	for( ; n > 0 ; --n ) {
		if(!--p->rest_comm)
			CARDi_EnableSpi(CSPI_CONTINUOUS_OFF);
		CARDi_WaitBusy();
		(*func)(p);
	}
	if(!p->rest_comm) {
		reg_MI_MCCNT0 = (u16)(MCCNT0_MASTER_OFF | MCCNT0_INT_OFF);
	}
}

/* continuous read communication   */
void	CARDi_CommReadCore(CARDiParam *p)
{
	reg_MI_MCD0 = 0;
	CARDi_WaitBusy();
	MI_WriteByte((void*)p->dst, (u8)reg_MI_MCD0);
	++p->dst;
}

/* continuous write communication   */
void	CARDi_CommWriteCore(CARDiParam *p)
{
	vu16	tmp;
	reg_MI_MCD0 = (u16)MI_ReadByte((void*)p->src);
	++p->src;
	CARDi_WaitBusy();
	tmp = reg_MI_MCD0;
}

/* continuous verify communication  */
void	CARDi_CommVerifyCore(CARDiParam *p)
{
	reg_MI_MCD0 = 0;
	CARDi_WaitBusy();
	/*
	 * Read, and disconnect if inconsistent. 
	 * However, the continuous transmission must be removed, so
	 * it must be read at least one more time.
	 */
	if((u8)reg_MI_MCD0 != MI_ReadByte((void*)p->src)) {
		p->cmp = FALSE;
		if(p->rest_comm > 1)
			p->rest_comm = 1;
	}
	++p->src;
}

/* validate. (needed in front of every write-type command) */
static void	CARDi_WriteEnable(void)
{
	const u8	command = COMM_WRITE_ENABLE;
	CARDi_CommandBegin(1);
	CARDi_CommArrayWrite(&command, 1);
	CARDi_CommandEnd(0, 0);
}

/* Configuration of the address designation command  */
static void	CARDi_SendSpiAddressingCommand(u32 addr, u32 mode)
{
	const u32	width = cardi_param.addr_width;
	u32	addr_cmd;
	switch(width) {
	case 1:
		/* [A:8] becomes a part of the command for the 4kbit device */
		addr_cmd = (u32)(mode |
			((addr >> 5) & 0x8) |
			((addr & 0xFF) << 8));
		break;
	case 2:
		addr_cmd = (u32)(mode |
			(addr & 0xFF00) |
			((addr & 0xFF) << 16));
		break;
	case 3:
		addr_cmd = (u32)(mode |
			((addr >> 8) & 0xFF00) |
			((addr & 0xFF00) << 8) |
			((addr & 0xFF) << 24));
		break;
	default:
		SDK_ASSERT(FALSE);
		break;
	}
	CARDi_CommArrayWrite(&addr_cmd, (u32)(1 + width));
}


/********************************************************************/
/*
 * Direct internal processing.
 * Exclusions, requests, etc. are all already completed with this step.
 * The limits on the size have also been considered. 
 */

/*  device type specification */
void	CARDi_IdentifyBackupCore(CARDBackupType type)
{
	/* The validity of this type has already been determined */
	cardi_device = CARDi_FindDeviceInfo(type);
	cardi_param.addr_width = cardi_device->addr_width;
}

/* read  */
void	CARDi_ReadBackupCore(u32 src, void *dst, u32 len)
{
	/* Process all at once, since there are no limits on the page boundary with regards to the read */
	CARDi_WaitPrevCommand();
	CARDi_WriteEnable();
	CARDi_CommandBegin(1 + cardi_param.addr_width + len);
	CARDi_SendSpiAddressingCommand(src, COMM_READ_ARRAY);
	CARDi_CommArrayRead(dst, len);
	CARDi_CommandEnd(0, 0);
}

/* program */
void	CARDi_ProgramBackupCore(u32 dst, const void *src, u32 len)
{
	const u32	page = cardi_device->page_size;

	CARDi_WaitPrevCommand();

	/* The processing is split so that the write does not cross over the page boundary */
	while(len > 0) {
		const u32	mod = (u32)(dst & (page - 1));
		u32	size = page - mod;
		if(size > len)	size = len;

		CARDi_WriteEnable();
		CARDi_CommandBegin(1 + cardi_param.addr_width + size);
		CARDi_SendSpiAddressingCommand(dst, COMM_PROGRAM_PAGE);
		CARDi_CommArrayWrite(src, size);
		CARDi_CommandEnd(cardi_device->spec.program_page, 0);

		if(cardi_common.cmd->result != CARD_RESULT_SUCCESS)
			break;

		src = (const void*)((u32)src + size);
		dst += size;
		len -= size;
	}
}

/* page write. (erase  + write) (FLASH) */
void	CARDi_WriteBackupCore(u32 dst, const void *src, u32 len)
{
	const u32	page = cardi_device->page_size;

	CARDi_WaitPrevCommand();

	/* The processing is split so that the write does not cross over the page boundary */
	while(len > 0) {
		const u32	mod = (u32)(dst & (page - 1));
		u32	size = page - mod;
		if(size > len)	size = len;

		CARDi_WriteEnable();
		CARDi_CommandBegin(1 + cardi_param.addr_width + size);
		CARDi_SendSpiAddressingCommand(dst, COMM_PAGE_WRITE);
		CARDi_CommArrayWrite(src, size);
		CARDi_CommandEnd(cardi_device->spec.write_page, cardi_device->spec.write_page_total);

		if(cardi_common.cmd->result != CARD_RESULT_SUCCESS)
			break;

		src = (const void*)((u32)src + size);
		dst += size;
		len -= size;
	}
}

/* verify  */
void	CARDi_VerifyBackupCore(u32 dst, const void *src, u32 len)
{
	CARDi_WaitPrevCommand();

	cardi_param.cmp = TRUE;
	/* Process all at once, since there are no limits on the page boundary with regards to the read */
	CARDi_WriteEnable();
	CARDi_CommandBegin(1 + cardi_param.addr_width + len);
	CARDi_SendSpiAddressingCommand(dst, COMM_READ_ARRAY);
	CARDi_CommArrayVerify(src, len);
	CARDi_CommandEnd(0, 0);
	if((cardi_common.cmd->result == CARD_RESULT_SUCCESS) &&
		!cardi_param.cmp) {
		cardi_common.cmd->result = CARD_RESULT_FAILURE;
	}
}

/* sector erase  (FLASH) */
void	CARDi_EraseBackupSectorCore(u32 dst)
{
	CARDi_WaitPrevCommand();

	CARDi_WriteEnable();
	CARDi_CommandBegin(1 + cardi_param.addr_width + 0);
	CARDi_SendSpiAddressingCommand(dst, COMM_SECTOR_ERASE);
	CARDi_CommandEnd(cardi_device->spec.erase_sector, 0);
}

/* write protect configuration  */
void	CARDi_SetWriteProtectCore(u16 stat)
{
	const u16	src = (u16)(COMM_WRITE_STATUS | ((stat & 0xF0) << 8));

	CARDi_WaitPrevCommand();

	CARDi_WriteEnable();
	CARDi_CommandBegin(1 + 1);
	CARDi_CommArrayWrite(&src, 2);
	CARDi_CommandEnd(0, 0);
}


#if	0

/********************************************************************/
/********************************************************************/
/* Commands unique to the verifying device **************************/
/********************************************************************/
/********************************************************************/


/* ID read  */
static void	CARDi_ReadIdCore(void)
{
    /*
	 * These commands do not exist in EEPROM or FRAM.
	 * Do they also not exist in the ST 2Mbit FLASH?
	 * The operation definitions are missing when sending incompatible command bytes.
	 * If so, are these commands always made unusable when 2Mbit, or
	 * are they broken up in a manner such as CARD_BACKUP_TYPE_FLASH_2MBITS_SANYO?
	 * Whichever it is, even though they are already divided up to that point, 
	 * would there be a scene that needs an ID?
	 * If there is any reserve capacity, we plan to use it internally to determine the validity.
	 */
	cardi_common.cmd->result = CARD_RESULT_UNSUPPORTED;
}

/* chip erase  (FLASH) */
static void	CARDi_EraseChipCore(void)
{
	const u8	command = COMM_CHIP_ERASE;

	CARDi_WriteEnable();
	CARDi_CommandBegin(1);
	CARDi_CommArrayWrite(&command, 1);
	CARDi_CommandEnd(cardi_device->spec.erase_chip, 0);
}


/* Commands that can use only 2M FLASH  **********************************/

/* page erase  (FLASH) */
static void	CARDi_ErasePageCore(u32 dst)
{
	CARDi_WriteEnable();
	CARDi_CommandBegin(1 + cardi_param.addr_width + 0);
	CARDi_SendSpiAddressingCommand(dst, COMM_PAGE_ERASE);
	CARDi_CommandEnd(cardi_device->spec.erase_page, 0);
}


#endif
