/****************************************************************************
 * 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 DMACON 0xdff096

#define CHIP_PTR (0xfff00)

#define REG_INTREG (0xf60028)
#define REGINTREG_SETRESET  (0x80)
#define REGINTREG_INTMASTER (0x01)

#define REG_INTLVL (0xf60038)
#define REGINTLVL_SETRESET (0x80)
#define REGINTLVL_LVL7     (0x40)

#define REG_SHADOW (0xf60018)
#define REGSHADOW_SETRESET (0x80)
#define REGSHADOW_SHADOW   (0x01)

#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>
#include <powerup/ppclib/tasks.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 ();

	/* Wait for signal from m68k. */
	progress ('W');
	{
		void			*PPCPort;

		if (PPCPort=(void*) PPCGetTaskAttr(PPCTASKTAG_MSGPORT))
		{
			progress ('w'); /* Wait */
			PPCWaitPort(PPCPort);
			progress ('r'); /* Received */
		} else {
			progress ('X');
			for (;;);
		}
	}
	progress ('!');

	/* Start Shutdown. */
	progress ('S');

	progress ('d');	/* Disable IRQs. */
	*(unsigned short*) INTENA = 0x7fff;
	progress ('d');	/* (Stall for a bit.) */
	*(unsigned short*) INTREQ = 0x7fff;
	progress ('d');	/* Disable DMA. */
	*(unsigned short*) DMACON = 0x03ff;

	
	progress ('s');	/* Get the PR bit set. */
	goSupervisor();
	
	progress ('i');	/* Change interrupt master. */
	*(unsigned char*) REG_INTREG = REGINTREG_INTMASTER;

	progress ('w');	/* Give the m68k a wedgie. */
	__asm __volatile ("eieio;sync;");
	*(unsigned char*) REG_INTLVL = REGINTLVL_LVL7;
	__asm __volatile ("eieio;sync;");
	
	/* Stall a bit to allow the m68k to disable its MMU. */
	{
		int i;
		for (i = 0; i < 50000; i++);
	}
	progress ('!');	/* Done! PPC owns the Amiga. */

    progress ('1');
	PPCCacheFlushAll();
	__asm __volatile (
		/* Disable caches. */
		"mfspr 4,1008       \n\t" /* HID0 */
		"lis   3,0          \n\t"
		"ori   3,3,0xc000   \n\t" /* HID0_ICE|HID0_DCE */
		"andc  4,4,3        \n\t"
		"sync               \n\t"
		"isync              \n\t"
		"mtspr 1008,4       \n\t" /* HID0 */
		/* Invalidate caches. */
		"mfspr 4,1008       \n\t" /* HID0 */
		"lis   3,4          \n\t"
		"ori   3,3,0x0c00   \n\t" /* HID0_ICFI|HID0_DCFI */
		"or    3,3,4        \n\t"
		"isync              \n\t"
		"sync               \n\t"
		"mtspr 1008,3       \n\t" /* HID0 */
		"mtspr 1008,4       \n\t" /* HID0 */
		/* Disable address translation and interrupts. */
		"mfmsr 4            \n\t"
		"lis   3,0          \n\t"
		"ori   3,3,0x8030   \n\t" /* MSR_EE|MSR_IR|MSR_DR */
		"andc  4,4,3        \n\t"
		"isync              \n\t"
		"mtmsr 4            \n\t"
		"sync               \n\t"
		::: "r3", "r4");

    progress ('2');

#if 0 
    { 
		/* I don't know if this works so it is disabled until someone with a
		   blizzard verifies the effect. mm/init.c will need to be adjusted
		   if this code is activated (init makes presumptions about
		   shadowing based on memory size). */ 
		unsigned char* reg_shadow =	(unsigned char*) REG_SHADOW; 
		*reg_shadow = (REGSHADOW_SETRESET |	REGSHADOW_SHADOW);
	} 
#endif 

    progress ('3');

	{
		const unsigned long *info;
		/* Get info-ptr from CHIP_PTR. */
		info = (const unsigned long*) *((unsigned long*) CHIP_PTR);

		/* Relocate code to memory just above info. */
		ptr = (unsigned long)info + 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 ('4');
	
		/* Jump to relocated code. */
		__asm __volatile (
			     /* Change stack. */
				 "mr   1,%0 \n\t"
				 "mtlr %1   \n\t"
				 "blr       \n\t"
				 : /* no outputs */
				 : "r" (info[8]), "r" (ptr)
				 /* no return */);
	
	}

	/* fake a noreturn */
	for (;;);
}

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

	/* Get info-ptr from CHIP_PTR. */
	info = (const unsigned long*) *((unsigned long*) CHIP_PTR);

	/* Verify checksum. */
	if (info[7])
	{
		int i = (info[2] + info[3]) / 4;
		unsigned char* k_p = (unsigned char*) info[0];
		unsigned long kcs = 0;
		
		while (i--)
		{
			unsigned long w = 0;
				
			w |= *k_p++;
			w <<= 8;
			w |= *k_p++;
			w <<= 8;
			w |= *k_p++;
			w <<= 8;
			w |= *k_p++;

				
			kcs = kcs ^ i;
			kcs = kcs ^ w;
		}
		progress ('C');
		progress ('1');
		if (info[7] == kcs)
			progress ('+');
		else
			progress ('-');
	}

	/* 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 ('6');

	/* Verify checksum. */
	if (info[7])
	{
		int i = (info[2] + info[3]) / 4;
		unsigned char* k_p = (unsigned char*) info[1];
		unsigned long kcs = 0;
		
		while (i--)
		{
			unsigned long w = 0;
				
			w |= *k_p++;
			w <<= 8;
			w |= *k_p++;
			w <<= 8;
			w |= *k_p++;
			w <<= 8;
			w |= *k_p++;

				
			kcs = kcs ^ i;
			kcs = kcs ^ w;
		}
		progress ('C');
		progress ('2');
		if (info[7] == kcs)
			progress ('+');
		else
			progress ('-');
	}
	
	/* 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 ('7');

	/* 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
"
	);
