/*
	Quick and dirty bootloader
	Oliver Ford - ipaqlinux<at>oliford.co.uk

	Loads kernel for the HP iPAQ 214. Should be compiled to single image
	which is put on an active FAT16 parition on an MMC/SD card and named
	'BLDIAG.NBO'. Resetting/turning on the iPAQ with the left two keys
	(calendar and start) held down causes the OEM Boot Module (OBM) to
	load it into 0x83900000 in RAM and execute it.

	TODO: 
		1X) Put tags creating in code, rather than the static table
		2X) Remove nops from MFPs
		3X) Change vars like ram_start etc to #defines so they're consts in ASM
		4X) Try to move target to 0xa0000000 as it prefers and then 
			define both blocks in the tags
		5) Try loading the zImage instead
		6) Maybe the zImage can be loaded somewhere else and the copying isnt needed?
		7) Remove the rest of the debugging

*/
#define MACH_TYPE_HPIPAQ214            1653
#define uint32 unsigned long

#define OFF	0x00,0x28
#define	BLUE	0x08,0x20
#define	GREEN	0x20,0x08

#define RAM_START 0xa0000000
#define TAGS_OFFSET 0x0100
#define KERNEL_OFFSET 0x8000

#define KERN_CHECKSUM 0x7142ee78

uint32 copyMem(volatile uint32 *src, volatile uint32 *target, uint32 len);

void setLEDs(uint32 on, uint32 off);
void error(uint32 code, int freeze);
void readUInt(uint32 val);
void setup_tags(void *params);
void setupLEDs(void);

extern boot();

extern char kernel_start[];
extern char kernel_end[];

//control passed here from bootTestCrt.S after creating stack
int boot(){	
	uint32 *STORE1 = (uint32 *)0x4090002c;	//Use RTC alarms as
	uint32 *STORE2 = (uint32 *)0x40900030;	// storage for debugging
	
	int i,j;

	*STORE1 = 0x98765;
	
	setupLEDs();
	setLEDs(GREEN);
		
	uint32 *kernel = (uint32 *)(RAM_START + KERNEL_OFFSET);
	uint32 *tags = (uint32 *)(RAM_START + TAGS_OFFSET);

	uint32 cs;
	
	setLEDs(GREEN);

	//now we need to copy the kernel code to where it likes to be
	cs = copyMem((uint32*)kernel_start,kernel, (kernel_end - kernel_start));

	if(cs != KERN_CHECKSUM)
		error(5,0);

	setup_tags(tags);

	setLEDs(BLUE);

	uint32 mtype = MACH_TYPE_HPIPAQ214;

	void (*theKernel)(uint32 zero, uint32 mtype, uint32 *tags);
	theKernel = (void (*)(uint32, uint32, uint32 *))kernel; /* set the kernel address */

	theKernel(0, mtype, tags);    /* jump to kernel with register set */

death:	__asm("nop");
	goto death;

}

uint32 copyMem(volatile uint32 *src, volatile uint32 *target, uint32 len){
	uint32 *STORE2 = (uint32 *)0x40900030;
	
	uint32 i,j;
	uint32 checkSum = 0;
	j=0;

	setLEDs(GREEN);
	for(i=0;i<len;i+=4){
		*STORE2 = i;	//store how far got if it dies

		*target = *src;
		if( *target != *src ){
			error(7, 0);
			readUInt(i);
			readUInt(*target);
			readUInt(*src);
		}		
		src++;
		checkSum += *target++;

		j++;
		if(j==20000)
			setLEDs(BLUE);
		else if(j >= 40000){
			setLEDs(GREEN);
			j=0;
		}
	}
	
	return checkSum;
}
