/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2003 by Jörg Hohensohn 
 * 
 * Second-level bootloader, with dual-boot feature by holding F1/Menu
 * This is the image being descrambled and executed by the boot ROM.
 * It's task is to copy Rockbox from Flash to DRAM.
 * The image(s) in flash may optionally be compressed with UCL 2e
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "sh7034.h"
#include "bootloader.h"


#ifdef NO_ROM
// start with the vector table
UINT32 vectors[] __attribute__ ((section (".vectors"))) = 
{
	(UINT32)_main, // entry point, the copy routine
	(UINT32)(end_stack - 1), // initial stack pointer
	FLASH_BASE + 0x200, // source of image in flash
	(UINT32)total_size, // size of image
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0x03020080 // mask and version (just as a suggestion)
};
#else
// our binary has to start with a vector to the entry point
tpMain start_vector[] __attribute__ ((section (".startvector"))) = {main};
#endif

#ifdef NO_ROM // some code which is only needed for the romless variant
void _main(void)
{
	UINT32* pSrc;
	UINT32* pDest;
	UINT32* pEnd;
/*
	asm volatile ("ldc %0,sr" : : "r"(0xF0)); // disable interrupts
	asm volatile ("mov.l @%0,r15" : : "r"(4)); // load stack
	asm volatile ("ldc %0,vbr" : : "r"(0)); // load vector base
*/
	// copy everything to IRAM and continue there
	pSrc = begin_iramcopy;
	pDest = begin_text;
	pEnd = pDest + (begin_stack - begin_text);

	do
	{
		*pDest++ = *pSrc++;
	}
	while (pDest < pEnd);

	main(); // jump to the real main()
}


void BootInit(void)
{
	// inits from the boot ROM, whether they make sense or not
	PBDR &= 0xFFBF; // LED off (0x131E)
	PBCR2 = 0; // all GPIO
	PBIOR |= 0x40; // LED output
	PBIOR &= 0xFFF1; // LCD lines input

	// init DRAM like the boot ROM does
	PACR2 &= 0xFFFB;
	PACR2 |= 0x0008;
	CASCR  = 0xAF;
	BCR   |= 0x8000;
	WCR1  &= 0xFDFD;
	DCR    = 0x0E00;
	RCR    = 0x5AB0;
	RTCOR  = 0x9605;
	RTCSR  = 0xA518;
}
#endif // #ifdef NO_ROM


int main(void)
{
	int nButton;

	PlatformInit(); // model-specific inits

	nButton = ButtonPressed();

	if (nButton == 3)
	{	// F3 means start monitor
		MiniMon();
	}
	else
	{
		tImage* pImage;
		pImage = GetStartImage(nButton); // which image
		DecompressStart(pImage); // move into place and start it
	}

	return 0; // I guess we won't return  ;-)
}


// init code that is specific to certain platform
void PlatformInit(void)
{
#ifdef NO_ROM
	BootInit(); // if not started by boot ROM, we need to init what it did
#endif

#if defined PLATFORM_PLAYER
	BRR1  =  0x0019; // 14400 Baud for monitor
	if (FW_VERSION > 451) // "new" Player?
	{
		PBDR &= ~0x10; // set PB4 to 0 to power-up the harddisk early
		PBIOR |= 0x10; // make PB4 an output
	}
#elif defined PLATFORM_RECORDER
	BRR1  =  0x0002; // 115200 Baud for monitor
	if (ReadADC(7) > 0x100) // charger plugged?
	{	// switch off the HD, else a flat battery may not start
		PACR2 &= 0xFBFF; // GPIO for PA5
		PAIOR |= 0x20;	 // make PA5 an output (low by default)
	}
#elif defined PLATFORM_FM
	BRR1  =  0x0002; // 115200 Baud for monitor
	PBDR |= 0x20; // set PB5 to keep power (fixes the ON-holding problem)
	PBIOR |= 0x20; // make PB5 an output
	if (ReadADC(0) < 0x1FF) // charger plugged?
	{	// switch off the HD, else a flat battery may not start
		PACR2 &= 0xFBFF; // GPIO for PA5
		PAIOR |= 0x20;	 // make PA5 an output (low by default)
	}
#endif

	// platform-independent inits
	DCR |= 0x1000; // enable burst mode on DRAM
	BCR |= 0x2000; // activate Warp mode (simultaneous internal and external mem access)
}


// Thinned out version of the UCL 2e decompression sourcecode
// Original (C) Markus F.X.J Oberhumer under GNU GPL license
#define GETBIT(bb, src, ilen) \
	(((bb = bb & 0x7f ? bb*2 : ((unsigned)src[ilen++]*2+1)) >> 8) & 1) 

int ucl_nrv2e_decompress_8(
	const UINT8 *src, UINT8 *dst, UINT32* dst_len)
{ 
	UINT32 bb = 0;
	unsigned ilen = 0, olen = 0, last_m_off = 1;

	for (;;)
	{
		unsigned m_off, m_len;

		while (GETBIT(bb,src,ilen))
		{
			dst[olen++] = src[ilen++];
		}
		m_off = 1;
		for (;;)
		{
			m_off = m_off*2 + GETBIT(bb,src,ilen);
			if (GETBIT(bb,src,ilen)) break;
			m_off = (m_off-1)*2 + GETBIT(bb,src,ilen);
		}
		if (m_off == 2)
		{
			m_off = last_m_off;
			m_len = GETBIT(bb,src,ilen);
		}
		else
		{
			m_off = (m_off-3)*256 + src[ilen++];
			if (m_off == 0xffffffff)
				break;
			m_len = (m_off ^ 0xffffffff) & 1;
			m_off >>= 1;
			last_m_off = ++m_off;
		}
		if (m_len)
			m_len = 1 + GETBIT(bb,src,ilen);
		else if (GETBIT(bb,src,ilen))
			m_len = 3 + GETBIT(bb,src,ilen);
		else
		{
			m_len++;
			do {
				m_len = m_len*2 + GETBIT(bb,src,ilen);
			} while (!GETBIT(bb,src,ilen));
			m_len += 3;
		}
		m_len += (m_off > 0x500);
		{
			const UINT8 *m_pos;
			m_pos = dst + olen - m_off;
			dst[olen++] = *m_pos++;
			do dst[olen++] = *m_pos++; while (--m_len > 0);
		}
	}
	*dst_len = olen;

	return ilen;
}


// move the image into place and start it
void DecompressStart(tImage* pImage)
{
	UINT32* pSrc;
	UINT32* pDest;

	pSrc = pImage->image;
	pDest = pImage->pDestination;

	if (pSrc != pDest) // if not linked to that flash address
	{
		if (pImage->flags & IF_UCL_2E)
		{	// UCL compressed, algorithm 2e
			UINT32 dst_len; // dummy
			ucl_nrv2e_decompress_8((UINT8*)pSrc, (UINT8*)pDest, &dst_len);
		}
		else
		{	// uncompressed, copy it
			UINT32 size = pImage->size;
			UINT32* pEnd;
			size = (size + 3) / 4; // round up to 32bit-words
			pEnd = pDest + size;

			do
			{
				*pDest++ = *pSrc++;
			}
			while (pDest < pEnd);
		}
	}

	pImage->pExecute();
}


int ReadADC(int channel)
{
	// after channel 3, the ports wrap and get re-used
	volatile UINT16* pResult = (UINT16*)(ADDRAH_ADDR + 2 * (channel & 0x03)); 
	int timeout = 266; // conversion takes 266 clock cycles

	ADCSR = 0x20 | channel; // start single conversion
	while (((ADCSR & 0x80) == 0) && (--timeout)); // 6 instructions per round

	return (timeout == 0) ? -1 : *pResult>>6;
}


// This function is platform-dependent, 
//	until I figure out how to distinguish at runtime.
int ButtonPressed(void) // return 1,2,3 for F1,F2,F3, 0 if none pressed
{
	int value = ReadADC(CHANNEL);

	if (value >= F1_LOWER && value <= F1_UPPER) // in range
		return 1;
	else if (value >= F2_LOWER && value <= F2_UPPER) // in range
		return 2;
	else if (value >= F3_LOWER && value <= F3_UPPER) // in range
		return 3;
	
	return 0;
}


// Determine the image to be started
tImage* GetStartImage(int nPreferred)
{
	tImage* pImage1;
	tImage* pImage2 = NULL; // default to not present
	UINT32 pos;
	UINT32* pFlash = (UINT32*)FLASH_BASE;

	// determine the first image position
	pos = pFlash[2] + pFlash[3]; // position + size of the bootloader = after it
	pos = (pos + 3) & ~3; // be shure it's 32 bit aligned

	pImage1 = (tImage*)pos;

	if (pImage1->size != 0)
	{	// check for second image
		pos = (UINT32)(&pImage1->image) + pImage1->size;
		pImage2 = (tImage*)pos;

		// does it make sense? (not in FF or 00 erazed space)
		if (pImage2->pDestination == (void*)0xFFFFFFFF
		 || pImage2->size == 0xFFFFFFFF
		 || pImage2->pExecute == (void*)0xFFFFFFFF
		 || pImage2->flags == 0xFFFFFFFF
		 || pImage2->pDestination == NULL) // size, execute and flags can legally be 0		
		{
			pImage2 = NULL; // invalidate
		}
	}

	if (pImage2 == NULL || nPreferred == 1)
	{	// no second image or overridden: return the first
		return pImage1;
	}

	return pImage2; // return second image
}

// diagnostic functions

void SetLed(BOOL bOn)
{
	if (bOn)
		PBDR |= 0x40;
	else
		PBDR &= ~0x40;
}


void UartInit(void)
{
	PBIOR &= 0xFBFF; // input: RXD1 remote pin
	PBCR1 |= 0x00A0; // set PB3+PB2 to UART
	PBCR1 &= 0xFFAF; // clear bits 6, 4 -> UART
	SMR1  =  0x0000; // async format 8N1, baud generator input is CPU clock
	SCR1  =  0x0030; // transmit+receive enable
	PBCR1 &= 0x00FF; // set bit 12...15 as GPIO
	SSR1  &= 0x00BF; // clear bit 6 (RDRF, receive data register full)
}


UINT8 UartRead(void)
{
	UINT8 byte;
	while (!(SSR1 & SCI_RDRF)); // wait for char to be available
	byte = RDR1;
	SSR1 &= ~SCI_RDRF;
	return byte;
}


void UartWrite(UINT8 byte)
{
	while (!(SSR1 & SCI_TDRE)); // wait for transmit buffer empty
	TDR1 = byte;
	SSR1 &= ~SCI_TDRE;
}


// include the mini monitor as a rescue feature, started with F3
void MiniMon(void)
{
	UINT8 cmd;
	UINT32 addr;
	UINT32 size;
	UINT32 content;
	volatile UINT8* paddr = NULL;
	volatile UINT8* pflash = NULL; // flash base address

	UartInit();

	while (1)
	{
		cmd = UartRead();
		switch (cmd)
		{
		case BAUDRATE:
			content = UartRead();
			UartWrite(cmd); // acknowledge by returning the command value
			while (!(SSR1 & SCI_TEND)); // wait for empty shift register, before changing baudrate
			BRR1 = content;
			break;

		case ADDRESS:
			addr = (UartRead() << 24) | (UartRead() << 16) | (UartRead() << 8) | UartRead();
			paddr = (UINT8*)addr;
			pflash = (UINT8*)(addr & 0xFFF80000); // round down to 512k align
			UartWrite(cmd); // acknowledge by returning the command value
			break;

		case BYTE_READ:
			content = *paddr++;
			UartWrite(content); // the content is the ack	
			break;

		case BYTE_WRITE:
			content = UartRead();
			*paddr++ = content;
			UartWrite(cmd); // acknowledge by returning the command value
			break;

		case BYTE_READ16:
			size = 16;
			while (size--)
			{
				content = *paddr++;
				UartWrite(content); // the content is the ack
			}
			break;

		case BYTE_WRITE16:
			size = 16;
			while (size--)
			{
				content = UartRead();
				*paddr++ = content;
			}
			UartWrite(cmd); // acknowledge by returning the command value
			break;

		case BYTE_FLASH:
			content = UartRead();
			pflash[0x5555] = 0xAA; // set flash to command mode
			pflash[0x2AAA] = 0x55;
			pflash[0x5555] = 0xA0; // byte program command
			*paddr++ = content;
			UartWrite(cmd); // acknowledge by returning the command value
			break;

		case BYTE_FLASH16:
			size = 16;
			while (size--)
			{
				content = UartRead();
				pflash[0x5555] = 0xAA; // set flash to command mode
				pflash[0x2AAA] = 0x55;
				pflash[0x5555] = 0xA0; // byte program command
				*paddr++ = content;
			}
			UartWrite(cmd); // acknowledge by returning the command value
			break;

		case HALFWORD_READ:
			content = *(UINT16*)paddr;
			paddr += 2;
			UartWrite(content >> 8); // highbyte
			UartWrite(content & 0xFF); // lowbyte
			break;
		
		case HALFWORD_WRITE:
			content = UartRead() << 8 | UartRead();
			*(UINT16*)paddr = content;
			paddr += 2;
			UartWrite(cmd); // acknowledge by returning the command value
			break;
		
		case EXECUTE:
			{
				tpFunc pFunc = (tpFunc)paddr;
				pFunc();
				UartWrite(cmd); // acknowledge by returning the command value
			}
			break;

		case VERSION:
			UartWrite(1); // return our version number
			break;

		default:
			{
				SetLed(TRUE);
				UartWrite(~cmd); // error acknowledge
			}

		} // case
	} // while (1)
}
