/*
 * S3C2410 Power Management Routines
 *
 * Coyright  (c) 2003 Samsung Electronics <hitchcar@sec.samsung.com>
 * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com>
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License.
 *
 * 2001-02-06: Cliff Brake         Initial code
 * 2001-02-25: Sukjae Cho <sjcho@east.isi.edu> & 
 */

/*
 *  Debug macros 
 */
#ifdef DEBUG
#  define DPRINTK(fmt, args...)	printk("%s: " fmt, __FUNCTION__ , ## args)
#else
#  define DPRINTK(fmt, args...)
#endif

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/interrupt.h>

#include <asm/mach-types.h>
#include <asm/hardware.h>
#include <asm/memory.h>
#include <asm/system.h>
#include <asm/hardirq.h>
#include <asm/proc-fns.h>
#include <asm/arch/irqs.h>
#include <asm/arch/power.h>

extern void s3c2410_cpu_resume(void);	
extern void s3c2410_cpu_suspend(void);

static void safe_pm_bh(unsigned long);
static DECLARE_TASKLET_DISABLED(pm_tasklet,safe_pm_bh,(unsigned long)0);

u32 s3c2410_emergency_off = 0;

static int s3c2410_wakeup_prepare(void)
{
	/* Interrupt disable */
	rINTMSK = 0xffffffff;
	rSRCPND = 0xffffffff;
	rINTPND = 0xffffffff;
	rGPFCON = 0x550a;
	rGPGCON = 0x55550100;
	return 0;			/* no wakeup factor */
}

static int s3c2410_wakeup_check(void)
{
	return 0;			/* no wakeup factor */
}

#define CPU_SAVE_SIZE 	66
u32 CPUBackupRegs[CPU_SAVE_SIZE];
int s3c2410_suspend(void)
{
	cli();
	rGSTATUS3 = virt_to_phys(s3c2410_cpu_resume);
	DPRINTK("*** s3c2410_cpu_resume= 0x%08lx\n", virt_to_phys(s3c2410_cpu_resume));
	CPUBackupRegs[CPU_SAVE_SIZE] = xtime.tv_sec;
	CPUSaveRegs(CPUBackupRegs);
	CPULCDOff();
	ConfigStopGPIO();
	ConfigMiscReg();
	s3c2410_wakeup_prepare();
	udelay(10);
	s3c2410_cpu_suspend();
	xtime.tv_sec = CPUBackupRegs[CPU_SAVE_SIZE];
	s3c2410_wakeup_check();
	CPULoadRegs(CPUBackupRegs);
	clear_interrupt();
	pwm_timer_update();	
	cpu_cache_clean_invalidate_all();
	sti();
	DPRINTK("* Come back from POWER_OFF\n");
	return 0;
}

static int pm_retval;
int pm_do_suspend(void)
{
	tasklet_schedule(&pm_tasklet);
	return pm_retval;
}

enum {
	local_irq_cnt=0,
	local_bh_cnt,
	local_save,
	local_restore,
	local_clear,
};

static void banking_softirq(int softirq[], int flag)
{
	switch (flag) {
		case local_save:
			softirq[local_irq_cnt] = local_irq_count(0);
			softirq[local_bh_cnt]  = local_bh_count(0);	
		case local_clear:
			local_irq_count(0) = 0; 
			local_bh_count(0)  = 0; 
			break;
		case  local_restore :
			local_irq_count(0) = softirq[local_irq_cnt];
			local_bh_count(0)  = softirq[local_bh_cnt];
			break;
		default:
			break;
	}
}

static void safe_pm_bh(unsigned long dummy)
{
	int retval;
	int bank[local_bh_cnt];
	
	banking_softirq(bank, local_save);
	DPRINTK(" softirq_pending 0x%08X local_irq_count 0x%08X local_bh_count 0x%08X \n", 
			local_irq_count(0),local_bh_count(0),softirq_pending(0));
	retval = pm_send_all(PM_SUSPEND, (void *)2);
	if (retval) {
		pm_retval = retval;
		banking_softirq(bank, local_restore);
		return;
	}

	DPRINTK("Entering s3c2410_suspend \n");
	udelay(100);
	retval = s3c2410_suspend();
	
	retval = pm_send_all(PM_RESUME, (void *)0);
	pm_retval = 0;
	banking_softirq(bank,local_restore);
}

EXPORT_SYMBOL(s3c2410_suspend);
EXPORT_SYMBOL(pm_do_suspend);

unsigned long sleep_phys_sp(void *sp)
{
	return virt_to_phys(sp);
}

#ifdef DEBUG_COR
unsigned long printCR(void *reg)
{
	u32 cr;

	__asm__("mrc p15, 0, %0, c1, c0, 0" : "=r" (cr));
	DPRINTK("CP15_R1 0x%08X  \n",cr);

	__asm__("mrc p15, 0, %0, c2, c0, 0" : "=r" (cr));
	DPRINTK("CP15_R2 0x%08X  \n",cr);
	
	__asm__("mrc p15, 0, %0, c3, c0, 0" : "=r" (cr));
	DPRINTK("CP15_R3 0x%08X  \n",cr);

	__asm__("mrc p15, 0, %0, c5, c0, 0" : "=r" (cr));
	DPRINTK("CP15_R5 0x%08X  \n",cr);

	__asm__("mrc p15, 0, %0, c6, c0, 0" : "=r" (cr));
	DPRINTK("CP15_R6 0x%08X  \n",cr);

	__asm__("mrc p15, 0, %0, c13, c0, 0" : "=r" (cr));
	DPRINTK("CP15_R13 0x%08X  \n",cr);
	return (unsigned long ) cr;
}
#endif

static struct ctl_table pm_table[] = 
{
	{ACPI_S1_SLP_TYP, "suspend", NULL, 0, 0600, NULL, (proc_handler *)&pm_do_suspend},
	{0}
};

static struct ctl_table pm_dir_table[] =
{
	{CTL_ACPI, "pm", NULL, 0, 0555, pm_table},
	{0}
};

void s3c2410_restart(void)
{
	/* Watch Dog Reset as like arch_reset() func */
}

void s3c2410_button_suspend(int irq, void *dev_id, struct pt_regs *regs)
{
	s3c2410_suspend();
}

void s3c2410_emerge_off(int irq, void *dev_id, struct pt_regs *regs)
{
	s3c2410_emergency_off = 1;
	pm_do_suspend();
	s3c2410_restart();
}

/* EINT0 Buttone (lefest button ) */
static void config_button(int but_num)
{
	rGPFCON  &= ~(0x3<<0); 	/* Set EINT0(GPF0) as EINT0) */
	rGPFCON  |=  (0x2<<0);
	rEXTINT0 &= ~(0x7<<0); 	/* Configure EINT0 as Falling Edge Mode */
	rEXTINT0 |=  (0x2<<0);
	DPRINTK("rEXTINT0(0x%08X) rGPFCON (0x%08X)\n",rEXTINT0,rGPFCON );
}

/*
 * Initialize power interface
 * EINT0 -> Suspend Mode Test
 */
static int __init pm_init(void)
{
	register_sysctl_table(pm_dir_table, 1);
	tasklet_enable(&pm_tasklet);

	printk(KERN_INFO "Power Management Module Installed \n");
	/* this registration can be done in init/main.c. */
	if(1){
		int err;
		err = request_irq(IRQ_BAT_FLT,s3c2410_emerge_off,SA_INTERRUPT,"BATT_FLT",NULL);
		if( err ){
			printk(KERN_INFO "BATT_FLT INT install error %d\n",err);
		}
		config_button(0);
		err = request_irq(IRQ_EINT0,s3c2410_emerge_off,SA_INTERRUPT,"BUTTON",NULL);
		if( err ){
			printk(KERN_INFO "Suspend Button install error %d\n",err);
		}
	}

	return 0;
}

__initcall(pm_init);

