/*
 * 
 * Common boot and setup code.
 *
 * Copyright (C) 2001 PPC64 Team, IBM Corp
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/reboot.h>
#include <linux/delay.h>
#include <linux/blk.h>
#include <linux/ide.h>
#include <linux/seq_file.h>
#include <linux/ioport.h>
#include <linux/console.h>
#include <linux/version.h>
#include <asm/init.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/processor.h>
#include <asm/pgtable.h>
#include <asm/bootinfo.h>
#include <asm/smp.h>
#include <asm/elf.h>
#include <asm/machdep.h>
#include <asm/iSeries/LparData.h>
#include <asm/naca.h>
#include <asm/paca.h>
#include <asm/ppcdebug.h>
#include <asm/time.h>
#include <asm/cputable.h>

extern unsigned long klimit;
/* extern void *stab; */
extern HTAB htab_data;
extern unsigned long loops_per_jiffy;
extern int preferred_console;	/* from kernel/printk.c */

extern unsigned long embedded_sysmap_start;
extern unsigned long embedded_sysmap_end;

int have_of = 1;

extern void  chrp_init(unsigned long r3,
		       unsigned long r4,
		       unsigned long r5,
		       unsigned long r6,
		       unsigned long r7);

extern void chrp_init_map_io_space( void );
extern void iSeries_init( void );
extern void iSeries_init_early( void );
extern void pSeries_init_early( void );
extern void pSeriesLP_init_early(void);
extern void mm_init_ppc64( void ); 
extern void pseries_secondary_smp_init(unsigned long); 

unsigned long decr_overclock = 1;
unsigned long decr_overclock_proc0 = 1;
unsigned long decr_overclock_set = 0;
unsigned long decr_overclock_proc0_set = 0;

#ifdef CONFIG_XMON
extern void xmon_map_scc(void);
#endif

char saved_command_line[256];
unsigned char aux_device_present;

void parse_cmd_line(unsigned long r3, unsigned long r4, unsigned long r5,
		    unsigned long r6, unsigned long r7);
int parse_bootinfo(void);

#ifdef CONFIG_MAGIC_SYSRQ
unsigned long SYSRQ_KEY;
#endif /* CONFIG_MAGIC_SYSRQ */

struct machdep_calls ppc_md;

/*
 * These are used in binfmt_elf.c to put aux entries on the stack
 * for each elf executable being started.
 */
int dcache_bsize;
int icache_bsize;
int ucache_bsize;

struct console udbg_console = {
	name:	"udbg",
	write:	udbg_console_write,
	flags:	CON_PRINTBUFFER,
	index:	-1,
};

/*
 * Do some initial setup of the system.  The parameters are those which 
 * were passed in from the bootloader.
 */
void setup_system(unsigned long r3, unsigned long r4, unsigned long r5,
		  unsigned long r6, unsigned long r7)
{
	unsigned int ret, i;

	/* This should be fixed properly in kernel/resource.c */
	iomem_resource.end = MEM_SPACE_LIMIT;

	/* pSeries systems are identified in prom.c via OF. */
	if ( itLpNaca.xLparInstalled == 1 )
		systemcfg->platform = PLATFORM_ISERIES_LPAR;
	
	switch (systemcfg->platform) {
	case PLATFORM_ISERIES_LPAR:
		iSeries_init_early();
		break;

#ifdef CONFIG_PPC_PSERIES
	case PLATFORM_PSERIES:
		pSeries_init_early();
#ifdef CONFIG_BLK_DEV_INITRD
		initrd_start = initrd_end = 0;
#endif
		parse_bootinfo();
		break;

	case PLATFORM_PSERIES_LPAR:
		pSeriesLP_init_early();
#ifdef CONFIG_BLK_DEV_INITRD
		initrd_start = initrd_end = 0;
#endif
		parse_bootinfo();
		break;
#endif
	}

	if (systemcfg->platform & PLATFORM_PSERIES) {
		register_console(&udbg_console);
		preferred_console = -1;
		udbg_printf("---- start early boot console ----\n");
	}

	if (systemcfg->platform & PLATFORM_PSERIES) {
		finish_device_tree();
		chrp_init(r3, r4, r5, r6, r7);

		/* Start secondary threads on SMT systems */
		for (i = 0; i < NR_CPUS; i++) {
			if(cpu_available(i)  && !cpu_possible(i)) {
				printk("%16.16lx : starting thread\n", i);
				rtas_call(rtas_token("start-cpu"), 3, 1, 
					  (void *)&ret,
					  i, *((unsigned long *)pseries_secondary_smp_init), i);
				paca[i].active = 1;
				systemcfg->processorCount++;
			}
		}
	}

	printk("-----------------------------------------------------\n");
	printk("naca                          = 0x%p\n", naca);
	printk("naca->pftSize                 = 0x%lx\n", naca->pftSize);
	printk("naca->paca                    = 0x%p\n\n", naca->paca);
	printk("systemcfg                     = 0x%p\n", systemcfg);
	printk("systemcfg->platform           = 0x%x\n", systemcfg->platform);
	printk("systemcfg->processor          = 0x%x\n", systemcfg->processor);
	printk("systemcfg->processorCount     = 0x%lx\n", systemcfg->processorCount);
	printk("systemcfg->physicalMemorySize = 0x%lx\n", systemcfg->physicalMemorySize);
	printk("systemcfg->dCacheL1LineSize   = 0x%x\n", systemcfg->dCacheL1LineSize);
	printk("systemcfg->iCacheL1LineSize   = 0x%x\n", systemcfg->iCacheL1LineSize);
	printk("htab_data.htab                = 0x%p\n", htab_data.htab);
	printk("htab_data.num_ptegs           = 0x%lx\n", htab_data.htab_num_ptegs);
	printk("-----------------------------------------------------\n");

	printk("Starting Linux PPC64 %s\n", UTS_RELEASE);

	mm_init_ppc64();
	idle_setup();

	switch (systemcfg->platform) {
	    case PLATFORM_ISERIES_LPAR:
		iSeries_init();
		break;
	    default:
		/* The following relies on the device tree being */
		/* fully configured.                             */
		parse_cmd_line(r3, r4, r5, r6, r7);
	}
	ppc64_boot_msg(0x10, "Setup System");
}

/* This is called just before console_init().
 * It will be obsolete when Linux gets real early console support (2.5?)
 * We need to hack preferred_console to retain the correct behavior
 */
void setup_before_console_init(void)
{
	if (systemcfg->platform & PLATFORM_PSERIES) {
		int save = preferred_console;
		unregister_console(&udbg_console);
		udbg_console.next = NULL;
		preferred_console = save;
		udbg_printf("---- end early boot console ----\n");
	}
}

void machine_restart(char *cmd)
{
	ppc_md.restart(cmd);
}
  
void machine_power_off(void)
{
	ppc_md.power_off();
}
  
void machine_halt(void)
{
	ppc_md.halt();
}

unsigned long ppc_proc_freq;
unsigned long ppc_tb_freq;

static int show_cpuinfo(struct seq_file *m, void *v)
{
	unsigned long cpu_id = (unsigned long)v - 1;
	unsigned int pvr;
	unsigned short maj;
	unsigned short min;

#ifdef CONFIG_SMP
	if (cpu_id == NR_CPUS) {

		if (ppc_md.get_cpuinfo != NULL)
			ppc_md.get_cpuinfo(m);

		return 0;
	}

	if (!(cpu_online_map & (1<<cpu_id)))
		return 0;
#endif

	pvr = paca[cpu_id].pvr;
	maj = (pvr >> 8) & 0xFF;
	min = pvr & 0xFF;

	seq_printf(m, "processor\t: %lu\n", cpu_id);
	seq_printf(m, "cpu\t\t: ");

	pvr = paca[cpu_id].pvr;

	if (cur_cpu_spec->pvr_mask)
		seq_printf(m, "%s", cur_cpu_spec->cpu_name);
	else
		seq_printf(m, "unknown (%08x)", pvr);


	seq_printf(m, "\n");
#if 0
	switch (PVR_VER(pvr)) {
	case PV_NORTHSTAR:
		seq_printf(m, "RS64-II (northstar)\n");
		break;
	case PV_PULSAR:
		seq_printf(m, "RS64-III (pulsar)\n");
		break;
	case PV_POWER4:
		seq_printf(m, "POWER4 (gp)\n");
		break;
	case PV_ICESTAR:
		seq_printf(m, "RS64-III (icestar)\n");
		break;
	case PV_SSTAR:
		seq_printf(m, "RS64-IV (sstar)\n");
		break;
	case PV_630:
		seq_printf(m, "POWER3 (630)\n");
		break;
	case PV_630p:
		seq_printf(m, "POWER3 (630+)\n");
		break;
	case PV_POWER4p:
		seq_printf(m, "POWER4+ (gq)\n");
		break;
	case PV_POWER4ul:
		seq_printf(m, "POWER4+ (gpul)\n");
		break;
	default:
		seq_printf(m, "Unknown (%08x)\n", pvr);
		break;
	}
#endif
	/*
	 * Assume here that all clock rates are the same in a
	 * smp system.  -- Cort
	 */
	if (systemcfg->platform != PLATFORM_ISERIES_LPAR) {
		struct device_node *cpu_node;
		int *fp;

		cpu_node = find_type_devices("cpu");
		if (cpu_node) {
			fp = (int *) get_property(cpu_node, "clock-frequency",
						  NULL);
			if (fp)
				seq_printf(m, "clock\t\t: %dMHz\n",
					   *fp / 1000000);
		}
	}

	if (ppc_md.setup_residual != NULL)
		ppc_md.setup_residual(m, cpu_id);

	seq_printf(m, "revision\t: %hd.%hd\n\n", maj, min);
	
	return 0;
}

static void *c_start(struct seq_file *m, loff_t *pos)
{
	return *pos <= NR_CPUS ? (void *)((*pos)+1) : NULL;
}
static void *c_next(struct seq_file *m, void *v, loff_t *pos)
{
	++*pos;
	return c_start(m, pos);
}
static void c_stop(struct seq_file *m, void *v)
{
}
struct seq_operations cpuinfo_op = {
	.start =c_start,
	.next =	c_next,
	.stop =	c_stop,
	.show =	show_cpuinfo,
};

/*
 * Fetch the cmd_line from open firmware. 
 */
void parse_cmd_line(unsigned long r3, unsigned long r4, unsigned long r5,
		  unsigned long r6, unsigned long r7)
{
	char *p;

#ifdef CONFIG_BLK_DEV_INITRD
	if ((initrd_start == 0) && r3 && r4 && r4 != 0xdeadbeef) {
		initrd_start = (r3 >= KERNELBASE) ? r3 : (unsigned long)__va(r3);
		initrd_end = initrd_start + r4;
		initrd_below_start_ok = 1;
		lmb_reserve(__pa(initrd_start),r4);
	}
#endif

	/* Hack -- add console=ttySn,9600 if necessary */
	if(strstr(cmd_line, "console=") == NULL) {
		struct device_node *prom_stdout = find_path_device(of_stdout_device);
		u32 *reg;
		int i;
		char *name, *val = NULL;
		printk("of_stdout_device %s\n", of_stdout_device);
		if (prom_stdout) {
			name = (char *)get_property(prom_stdout, "name", NULL);
			if (name) {
				if (strcmp(name, "serial") == 0) {
					reg = (u32 *)get_property(prom_stdout, "reg", &i);
					if (i > 8) {
						switch (reg[1]) {
						    case 0x3f8: val = "ttyS0,9600"; break; 
						    case 0x2f8: val = "ttyS1,9600"; break; 
						    case 0x898: val = "ttyS2,9600"; break; 
						    case 0x890: val = "ttyS3,9600"; break; 
						}
					}
				} else if (strcmp(name, "vty") == 0) {
					/* pSeries LPAR virtual console */
					val = "hvc0";
				}
				if (val) {
					char tmp_cmd_line[CMD_LINE_SIZE];
					snprintf(tmp_cmd_line, CMD_LINE_SIZE, 
						 "AUTOCONSOLE console=%s %s",
						 val, cmd_line);
					memcpy(cmd_line, tmp_cmd_line, CMD_LINE_SIZE);
					printk("console= not found, add console=%s\n", val);

				}
			}
		}
	}	

	/* Look for mem= option on command line */
	if (strstr(cmd_line, "mem=")) {
		char *q;
		unsigned long maxmem = 0;
		extern unsigned long __max_memory;

		for (q = cmd_line; (p = strstr(q, "mem=")) != 0; ) {
			q = p + 4;
			if (p > cmd_line && p[-1] != ' ')
				continue;
			maxmem = simple_strtoul(q, &q, 0);
			if (*q == 'k' || *q == 'K') {
				maxmem <<= 10;
				++q;
			} else if (*q == 'm' || *q == 'M') {
				maxmem <<= 20;
				++q;
			}
		}
		__max_memory = maxmem;
	}

	/* Look for mem= option on command line */
	if (strstr(cmd_line, "HZ=")) {
		char *q;
		unsigned long hz = 0;

		for (q = cmd_line; (p = strstr(q, "HZ=")) != 0; ) {
			q = p + 3;
			if (p > cmd_line && p[-1] != ' ')
				continue;
			hz = simple_strtoul(q, &q, 0);
			if (*q == 'k' || *q == 'K') {
				hz <<= 10;
				++q;
			} else if (*q == 'm' || *q == 'M') {
				hz <<= 20;
				++q;
			}
		}
		init_HZ(hz);
	}

	if (strstr(cmd_line, "desktop"))
		init_desktop();
}

#if 0
char *bi_tag2str(unsigned long tag)
{
	switch (tag) {
	case BI_FIRST:
		return "BI_FIRST";
	case BI_LAST:
		return "BI_LAST";
	case BI_CMD_LINE:
		return "BI_CMD_LINE";
	case BI_BOOTLOADER_ID:
		return "BI_BOOTLOADER_ID";
	case BI_INITRD:
		return "BI_INITRD";
	case BI_SYSMAP:
		return "BI_SYSMAP";
	case BI_MACHTYPE:
		return "BI_MACHTYPE";
	default:
		return "BI_UNKNOWN";
	}
}
#endif

int parse_bootinfo(void)
{
	struct bi_record *rec;
	extern char *sysmap;
	extern unsigned long sysmap_size;

	rec = prom.bi_recs;

	if ( rec == NULL || rec->tag != BI_FIRST )
		return -1;

	for ( ; rec->tag != BI_LAST ; rec = bi_rec_next(rec) ) {
		switch (rec->tag) {
		case BI_CMD_LINE:
			memcpy(cmd_line, (void *)rec->data, rec->size);
			break;
		case BI_SYSMAP:
			sysmap = (char *)((rec->data[0] >= (KERNELBASE))
					? rec->data[0] : (unsigned long)__va(rec->data[0]));
			sysmap_size = rec->data[1];
			break;
#ifdef CONFIG_BLK_DEV_INITRD
		case BI_INITRD:
			initrd_start = (unsigned long)__va(rec->data[0]);
			initrd_end = initrd_start + rec->data[1];
			initrd_below_start_ok = 1;
			break;
#endif /* CONFIG_BLK_DEV_INITRD */
		}
	}

	return 0;
}

void __init ppc_init(void)
{
	/* clear the progress line */
	ppc_md.progress(" ", 0xffff);

	if (ppc_md.init != NULL) {
		ppc_md.init();
	}
}

void __init ppc64_calibrate_delay(void)
{
	loops_per_jiffy = tb_ticks_per_jiffy;

	printk("Calibrating delay loop... %lu.%02lu BogoMips\n",
			       loops_per_jiffy/(500000/HZ),
			       loops_per_jiffy/(5000/HZ) % 100);
}	

extern void (*calibrate_delay)(void);
extern void sort_exception_table(void);

/*
 * Called into from start_kernel, after lock_kernel has been called.
 * Initializes bootmem, which is unsed to manage page allocation until
 * mem_init is called.
 */
void __init setup_arch(char **cmdline_p)
{
	extern int panic_timeout;
	extern char _etext[], _edata[];
	extern void do_init_bootmem(void);

	calibrate_delay = ppc64_calibrate_delay;

	ppc64_boot_msg(0x12, "Setup Arch");
#ifdef CONFIG_XMON
	xmon_map_scc();
	if (strstr(cmd_line, "xmon"))
		xmon(0);
#endif /* CONFIG_XMON */

#if defined(CONFIG_KGDB)
	kgdb_map_scc();
	set_debug_traps();
	breakpoint();
#endif
	/*
	 * Set cache line size based on type of cpu as a default.
	 * Systems with OF can look in the properties on the cpu node(s)
	 * for a possibly more accurate value.
	 */
	dcache_bsize = systemcfg->dCacheL1LineSize; 
	icache_bsize = systemcfg->iCacheL1LineSize; 

	/* reboot on panic */
	panic_timeout = 180;

	init_mm.start_code = PAGE_OFFSET;
	init_mm.end_code = (unsigned long) _etext;
	init_mm.end_data = (unsigned long) _edata;
	init_mm.brk = (unsigned long) klimit;
	
	/* Save unparsed command line copy for /proc/cmdline */
	strcpy(saved_command_line, cmd_line);
	*cmdline_p = cmd_line;

	/* set up the bootmem stuff with available memory */
	do_init_bootmem();

	ppc_md.setup_arch();

	paging_init();
	sort_exception_table();
	ppc64_boot_msg(0x15, "Setup Done");
}


/* ToDo: do something useful if ppc_md is not yet setup. */
#define PPC64_LINUX_FUNCTION 0x0f000000
#define PPC64_IPL_MESSAGE 0xc0000000
#define PPC64_TERM_MESSAGE 0xb0000000
#define PPC64_ATTN_MESSAGE 0xa0000000
#define PPC64_DUMP_MESSAGE 0xd0000000

static void ppc64_do_msg(unsigned int src, const char *msg)
{
	if (ppc_md.progress) {
		char buf[32];

		sprintf(buf, "%08x        \n", src);
		ppc_md.progress(buf, 0);
		sprintf(buf, "%-16s", msg);
		ppc_md.progress(buf, 0);
	}
}

/* Print a boot progress message. */
void ppc64_boot_msg(unsigned int src, const char *msg)
{
	ppc64_do_msg(PPC64_LINUX_FUNCTION|PPC64_IPL_MESSAGE|src, msg);
	udbg_printf("[boot]%04x %s\n", src, msg);
}

/* Print a termination message (print only -- does not stop the kernel) */
void ppc64_terminate_msg(unsigned int src, const char *msg)
{
	ppc64_do_msg(PPC64_LINUX_FUNCTION|PPC64_TERM_MESSAGE|src, msg);
	udbg_printf("[terminate]%04x %s\n", src, msg);
}

/* Print something that needs attention (device error, etc) */
void ppc64_attention_msg(unsigned int src, const char *msg)
{
	ppc64_do_msg(PPC64_LINUX_FUNCTION|PPC64_ATTN_MESSAGE|src, msg);
	udbg_printf("[attention]%04x %s\n", src, msg);
}

/* Print a dump progress message. */
void ppc64_dump_msg(unsigned int src, const char *msg)
{
	ppc64_do_msg(PPC64_LINUX_FUNCTION|PPC64_DUMP_MESSAGE|src, msg);
	udbg_printf("[dump]%04x %s\n", src, msg);
}


void exception_trace(unsigned long trap)
{
	unsigned long x, srr0, srr1, reg20, reg1, reg21;

	asm("mflr %0" : "=r" (x) :);
	asm("mfspr %0,0x1a" : "=r" (srr0) :);
	asm("mfspr %0,0x1b" : "=r" (srr1) :);
	asm("mr %0,1" : "=r" (reg1) :);
	asm("mr %0,20" : "=r" (reg20) :);
	asm("mr %0,21" : "=r" (reg21) :);

	udbg_puts("\n");
	udbg_puts("Took an exception : "); udbg_puthex(x); udbg_puts("\n");
	udbg_puts("   "); udbg_puthex(reg1); udbg_puts("\n");
	udbg_puts("   "); udbg_puthex(reg20); udbg_puts("\n");
	udbg_puts("   "); udbg_puthex(reg21); udbg_puts("\n");
	udbg_puts("   "); udbg_puthex(srr0); udbg_puts("\n");
	udbg_puts("   "); udbg_puthex(srr1); udbg_puts("\n");
}

void do_spread_lpevents(unsigned long n)
{
	unsigned long i,m;

	if (n < MAX_PACAS)
		m = n;
	else
		m = MAX_PACAS;

	for (i=1; i<m; ++i)
		paca[i].lpQueuePtr = paca[0].lpQueuePtr;

	printk("lpevent processing spread over %ld processors\n", n);
}

/* 
 * The parameter is the number of processors to share in 
 * processing lp events 
 */
int set_spread_lpevents(char * str)
{
	unsigned long val = simple_strtoul( str, NULL, 0 );
	if ((val > 0) && (val <= MAX_PACAS)) {
		do_spread_lpevents(val);
	} else
		printk("invalid spreaqd_lpevents %ld\n", val);

	return 1;
}	

/* This should only be called on processor 0 during calibrate decr */
void setup_default_decr(void)
{
	struct paca_struct *lpaca = get_paca();

	if ( decr_overclock_set && !decr_overclock_proc0_set )
		decr_overclock_proc0 = decr_overclock;

	lpaca->default_decr = tb_ticks_per_jiffy / decr_overclock_proc0;	
	lpaca->next_jiffy_update_tb = get_tb() + tb_ticks_per_jiffy;
}

int set_decr_overclock_proc0( char * str )
{
	unsigned long val = simple_strtoul( str, NULL, 0 );
	if ( ( val >= 1 ) && ( val <= 48 ) ) {
		decr_overclock_proc0_set = 1;
		decr_overclock_proc0 = val;
		printk("proc 0 decrementer overclock factor of %ld\n", val);
	}
	else
		printk("invalid proc 0 decrementer overclock factor of %ld\n", val);
	return 1;
}

int set_decr_overclock( char * str )
{
	unsigned long val = simple_strtoul( str, NULL, 0 );
	if ( ( val >= 1 ) && ( val <= 48 ) ) {
		decr_overclock_set = 1;
		decr_overclock = val;
		printk("decrementer overclock factor of %ld\n", val);
	}
	else
		printk("invalid decrementer overclock factor of %ld\n", val);
	return 1;

}

__setup("spread_lpevents=", set_spread_lpevents );
__setup("decr_overclock_proc0=", set_decr_overclock_proc0 );
__setup("decr_overclock=", set_decr_overclock );
