/****************************************************************************
 * ppc_boot.c -- This program initializes the PPC on a Phase5 PowerUp board
 *               and boots the kernel.
 *
 * Copyright 1997, 1998 by Jesper Skov (jskov@cygnus.co.uk)
 ***************************************************************************/

#define SYMBOL_NAME_STR(X) #X

#define INTENA 0xdff09a
#define INTREQ 0xdff09c

#define CHIP_PTR (0xfff00)

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <powerup/ppclib/interface.h>
#include <powerup/gcclib/powerup_protos.h>

void goSupervisor(void);
void relocate(void);
extern char relocateEnd;

void progress_init (void);
void progress (char);

unsigned int main(void)
{
	int i;
	unsigned long *s, *d;
	unsigned long ptr;

	register unsigned long r0 __asm("r0");
	register unsigned long r3 __asm("r3");

	progress_init ();
	progress ('a');
	
	{
		/* Wait for the m68k to complete its business (shutting up
		 * boards).
		 * It would be better to do proper synchronization, but my
		 * attempts to do something "nice" failed. I think it's
		 * because the m68k drops everything on the floor when it
		 * attempts to write the same address the PPC is polling.
		 * The PPC uKernel coule be used to send a signal, but then
		 * there's no way to be sure "where" the m68k is when the
		 * PPC disables interrupts. Therefore this loop for now.
		 */
		int fingers_crossed = 1;
		for (i = 0; i < 1000000;i++);

	}

	progress ('b');

	{
		unsigned short* intena_p = (unsigned short*) INTENA;
		unsigned short* intreq_p = (unsigned short*) INTREQ;
		/* Disable IRQs. */
		*intena_p = 0x7fff;
		*intreq_p = 0x7fff;
	}

	progress ('c');

	/* Get the PR bit set. */
	goSupervisor();

	progress ('d');
	
	PPCCacheFlushAll();
	__asm __volatile (
		/* Disable caches. */
		"mfspr 0,1008       \n\t" /* HID0 */
		"lis   3,0          \n\t"
		"ori   3,3,0xc000   \n\t" /* HID0_ICE|HID0_DCE */
		"andc  0,0,3        \n\t"
		"sync               \n\t"
		"isync              \n\t"
		"mtspr 1008,0       \n\t" /* HID0 */
		/* Disable address translation. */
		"mfmsr 0            \n\t"
		"li    3,0b110000   \n\t" /* MSR_IR|MSR_DR */
		"andc  0,0,3        \n\t"
		"isync              \n\t"
		"mtmsr 0            \n\t"
		"sync               \n\t"
		:: "r" (r0), "r" (r3));

		
	progress ('e');

	/* Get info-ptr from CHIP_PTR. 
	 * Relocate code to memory just above info. 
	 */
	ptr = (unsigned long) *((unsigned long*) CHIP_PTR);
	ptr += 0x0200;
	ptr = ptr + 4 - (ptr % 4);

	s = (unsigned long*) &relocate;
	d = (unsigned long*) ptr;
	for(i = (((unsigned long)&relocateEnd) - ((unsigned long)s))/4;
		i >= 0; i--){
		*d = *s;
		d++;
		s++;
	}

	progress ('f');
	
	/* Jump to relocated code. */
	r0 = ptr;
	__asm __volatile (
		"mtlr  0             \n\t"
		"blr    \n\t"
		: /* no outputs */
		: "r" (r0), "r" (r3)
		/* no return */);
	
	/* fake a noreturn */
	for (;;);
		
	return;
}

void relocate(void)
{
	const unsigned long *info;
	unsigned char *s, *d, *e;

	progress ('g');

	/* Get info-ptr from CHIP_PTR. */
	info = (const unsigned long*) *((unsigned long*) CHIP_PTR);
	
	/* Copy kernel to memstart */
	s = (unsigned char *) info[0];
	d = (unsigned char *) info[1];
	e = (unsigned char *) (info[1] + info[2] + info[3]);
	while(d != e){
		*d = *s;
		s++;
		d++;
	}

	progress ('h');

	/* Copy ramdisk to memory end. */
	d = (unsigned char *) info[1] + info[5];
	e = d - info[4];
	s = s + info[4];
	while(d != e){
		s--;
		d--;
		*d = *s;
	}

	progress ('i');

	/* Let 0xfff00000 contain sum used for VTOP/PTOV in head.S. */
	/* Also put it at kernel start since kernel copies the vectors
	 * to their proper location after modifying a few instructions.
	 */
#define KERNELBASE 0xC0000000
	*((unsigned long *)0xfff00000) = info[1] - KERNELBASE;
	*((unsigned long *) info[1]) = info[1] - KERNELBASE;

	progress ('K');

	{
		register unsigned long r3 __asm("r3");
		register unsigned long r4 __asm("r4");
		register unsigned long r5 __asm("r5");
		register unsigned long r6 __asm("r6");
		register unsigned long r7 __asm("r7");

		/* initrd pointers (virtual addresses) */
		if (info[4]){
			/* end of rd */
			r5 = info[5] + KERNELBASE;
			/* start of rd */
			r4 = r5 - info[4];
		} else
			r4 = 0;
		
		/* Make it easy to identify APUS in identify_machine. */
		r3 = 0x61707573;
		
		/* Jump to kernel start. */
		r7 = info[1] + 0xc;
		__asm __volatile (
			"mtlr 7 \n\t"
			"blr    \n\t"
			: /* no outputs */
			: "r" (r3), "r" (r4), "r" (r5), "r" (r6), "r" (r7)
			/* no return */);
	}
	
	/* fake a noreturn */
	for (;;);
}

/* Play that jive... */
extern char setPR, setPRend;
void goSupervisor(void)
{
	unsigned long *s, *d;
	int len = ((int) &setPRend - (int) &setPR) / 4;

	/* Copy code to TRAP entry */
	d = (unsigned long*) 0xfff00700;
	s = (unsigned long*) &setPR;
	for(;len > 0;len--){
		*d = *s;
		d++;
		s++;
	}

	PPCCacheFlushAll();
	
	/* Execute code */
	__asm__ (
		"trap \n\t"
		);
}

void progress_init (void)
{
	const unsigned long *info;
	unsigned long *mesg_base;
	unsigned long* p;
	info = (const unsigned long*) *((unsigned long*) CHIP_PTR);
	mesg_base = (unsigned long*) info[6];
	p = mesg_base;
	
	*p++ = 'SAVE';
	*p++ = 'BOOT';
	*p++ = (unsigned long) mesg_base;
	*p++ = 0;
}
	

void progress (char c)
{
	const unsigned long *info;
	char progress_state;
	char* progress_ptr;
	unsigned long* mesg_base;
	info = (const unsigned long*) *((unsigned long*) CHIP_PTR);

	mesg_base = (unsigned long*) info[6];

	progress_ptr = mesg_base[3] + (char*)&mesg_base[4];
	*progress_ptr = c;
	mesg_base[3]++;
}

asm(
	".text\n"
	".align 4\n"
	SYMBOL_NAME_STR(setPR) ":
/* Increase the return EA so we get to the next instruction */
mtsprg       1,1
mfsrr0       1
addi         1,1,4
mtsrr0       1
mfsprg       1,1
/* Fiddle the PR bit */
mfsrr1       0
rlwinm       0,0,0,18,16
mtsrr1       0
rfi
"
	SYMBOL_NAME_STR(setPRend) ":
");

asm(
	".text\n"
	".align 4\n"
	SYMBOL_NAME_STR(relocateEnd) ":
nop
"
	);
