/*
 * OMAP1510 Power Management Routines
 *
 * Original code for the SA11x0:
 * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com>
 *
 * Modified for the PXA250 by Nicolas Pitre:
 * Copyright (c) 2002 Monta Vista Software, Inc.
 *
 * Modified for the OMAP1510 by David Singleton:
 * Copyright (c) 2002 Monta Vista Software, Inc.
 *
 * Modified for the Archos/AV500 by Matthias Welwarsky:
 * Copyright (c) 2003 Archos S.A.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License.
 */

#include <linux/config.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <linux/errno.h>
#include <linux/serial_reg.h>
#include <linux/delay.h>
#include <linux/pm.h>

#include <asm/memory.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/serial.h>
#include <asm/arch/hardware.h>
#include <asm/arch/pm.h>
#include <asm/arch/gpio.h>
#include <asm/arch/hwtimer.h>
#include <asm/fiq.h>
#include <asm/arch/system.h>

static unsigned short arm_sleep_save[ARM_SLEEP_SAVE_SIZE] __sramdata;
static unsigned short ulpd_sleep_save[ULPD_SLEEP_SAVE_SIZE] __sramdata;
static unsigned int mpui_sleep_save[MPUI_SLEEP_SAVE_SIZE] __sramdata;

/*
 * Let's power down on idle, but only if we are really
 * idle, because once we start down the path of
 * going idle we continue to do idle even if we get
 * a clock tick interrupt . .
 */

#ifdef CONFIG_AV500_KEEPALIVE
#define MPUIO_OUTPUT_REG     ((volatile unsigned short*)0xfffb5004)

static inline void wd_toggle(void)
{
	*MPUIO_OUTPUT_REG ^= (1 << 7);
}

static inline void wd_pin_high(void)
{
	*MPUIO_OUTPUT_REG |= (1 << 7);
}

#endif

static void __sram omap1510_idle_loop_suspend_impl(void)
{
        register unsigned short saved_idlect1;

        saved_idlect1 = *ARM_IDLECT1;
        *ARM_IDLECT1  = saved_idlect1|IDLE_LOOP_REQUEST;
        nop();
        nop();
        nop();
        nop();
        nop();
        __asm__ volatile ("mcr	p15, 0, r0, c7, c0, 4");
        *ARM_IDLECT1 = saved_idlect1;

}
static void (*omap1510_idle_loop_suspend)(void);
static unsigned long last_pm_idle;

void omap1510_pm_idle(void)
{
	local_irq_disable();
	
	if (!current->need_resched) {
		
		if ( (unsigned long)(jiffies - last_pm_idle) < (HZ/10) )
			omap1510_idle_loop_suspend();
		else
			__asm__ volatile ("mcr	p15, 0, r0, c7, c0, 4");
	}
	
	last_pm_idle = jiffies;
	
	local_irq_enable();
}


static void __sram omap1510_cpu_suspend_impl(void)
{
        register int i;
        register unsigned short saved_idlect1;
        register unsigned short saved_idlect2;

	/* force DM270 into reset */
	*ULPD_POWER_CTRL_REG &= ~(1<<3); mb();
        
	/*
	 * Warning: BLACK MAGIC AHEAD!
	 *
	 * When going to deep sleep, the OMAP keeps the last data pattern he
	 * has read on his data lines, regardless what you write out later.
	 * So, to make sure that all EMIFS data lines are '0', you need to make
	 * sure the last thing you _read_ on the EMIFS is a '0x0000'.
	 * We do this by simply reading over the FLASH until we find a '0x0000'.
	 * Then we write a '0' to '0x00000000' to clear the address lines.
	 */
	i=0;
	while ( ( i < OMAP_BOOTFLASH_SIZE) && readw(OMAP_BOOTFLASH_BASE+i) )
		i+=2;
	
	writew(0, OMAP_BOOTFLASH_BASE);

        nop();
        nop();
        nop();
        nop();
        nop();
	nop();
	nop();
	nop();

	/* disable EMIFS Power Down */
	*PM_EMIFS_CONFIG_REG &= ~OMAP_EMIFS_CONFIG_PWD_EN; mb();
        /* set EMIFS Power Down Request to 0 */
        *PM_EMIFS_CONFIG_REG &= ~OMAP_EMIFS_CONFIG_PDE; mb();
        /* enable EMIFS Power Down */
        *PM_EMIFS_CONFIG_REG |= OMAP_EMIFS_CONFIG_PWD_EN; mb();
        /* put SDRAM into Power Down and Self Refresh */
        *PM_EMIFF_SDRAM_CONFIG |= SELF_REFRESH_MODE; mb();        
	/* put EMIFS into Power Down */
        *PM_EMIFS_CONFIG_REG |= OMAP_EMIFS_CONFIG_PDE; mb();

	/* put DPLL in bypass */
	*DPLL_CTL_REG &= ~(7UL<<2);

	nop();
        nop();
        nop();
        nop();
        nop();
        nop();
        nop();
        nop();
        nop();
        nop();

        saved_idlect2 = *ARM_IDLECT2;
	saved_idlect1 = *ARM_IDLECT1;
	*ARM_IDLECT2 = IDLE_CLOCK_DOMAINS; mb();
        *ARM_IDLECT1 = DEEP_SLEEP_REQUEST; mb();
        
	/* 20 NOPs after DEEP SLEEP request */
        nop();
        nop();
        nop();
        nop();
        nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();
	nop();

	*ARM_IDLECT2 = saved_idlect2; mb();
        *ARM_IDLECT1 = saved_idlect1; mb();

	/* get DPLL from bybass */
	*DPLL_CTL_REG |= (1UL<<4);
	/* wait for DPLL lock */
	while ( (*DPLL_CTL_REG & 0x0001) == 0)
		;
	
	MPUI_RESTORE(PM_EMIFF_SDRAM_CONFIG);
}
static void (*omap1510_cpu_suspend)(void);

static unsigned int keypad_read(void)
{
	int i;
	u32 keys = 0;

	outw(0xff, OMAP1510_MPUIO_KBC_REG);
	udelay(1);

	for (i = 0; i < 4; i += 1) {
		outw(~(1 << i) & 0xff, OMAP1510_MPUIO_KBC_REG);
		udelay(1);
		keys |= ((~inw(OMAP1510_MPUIO_KBR_LATCH)) & 0xf) << (i << 2);
		inw(OMAP1510_32kHz_TIMER_BASE);	/* BTS_Errata.22 */
	}

	outw(0x0, OMAP1510_MPUIO_KBC_REG);
	udelay(1);

	return keys;
}


void omap_pm_suspend(void)
{
	/*
	 * Step 0: turn off interrupts
	 */

	cli();
	clf();
	MPUI_SAVE(IRQ_MIR1);
	MPUI_SAVE(IRQ_MIR2);

	/*
	 * The omap is a strange/beautiful device.  The caches, memory
	 * and regsigter state are preserved across power saves.
	 * We have to save and restore very little register state to
	 * idle the omap.
	 */

	/*
	 * Save MPUI control registers for wake up.
	 */

	MPUI_SAVE(MPUI_CTRL_REG);
	MPUI_SAVE(MPUI_DSP_BOOT_CONFIG);
	MPUI_SAVE(MPUI_DSP_API_CONFIG);
	MPUI_SAVE(MPUI_DSP_STATUS_REG);
        MPUI_SAVE(PM_EMIFS_CONFIG_REG);
        MPUI_SAVE(PM_EMIFF_SDRAM_CONFIG);

	/*
	 * Save ARM control registers
	 */

	ARM_SAVE(ARM_CKCTL);
	ARM_SAVE(ARM_IDLECT1);
	ARM_SAVE(ARM_IDLECT2);
	ARM_SAVE(ARM_EWUPCT);
	ARM_SAVE(ARM_RSTCT1);
	ARM_SAVE(ARM_RSTCT2);
	ARM_SAVE(ARM_SYSST);
	ULPD_SAVE(ULPD_IT_STATUS_REG);
	ULPD_SAVE(ULPD_CLOCK_CTRL_REG);
	ULPD_SAVE(ULPD_SOFT_REQ_REG);
	ULPD_SAVE(ULPD_DPLL_CTRL_REG);
	ULPD_SAVE(ULPD_POWER_CTRL_REG);

	//omap1510_dsp_idle();
	
        *ARM_RSTCT1  |= 0x2;
        *ARM_RSTCT1  &= ~0x2;
        *ARM_CKCTL   &= ~0x2000;
        *DSP_IDLECT2  = 0;

        /* set default MPUI configuration */
	*MPUI_CTRL_REG = DEFAULT_MPUI_CONFIG;

        /* close the SARAM window to the DSP */
	*MPUI_DSP_API_CONFIG = 0;

        /* disable all software clock requests  */
        *ULPD_SOFT_REQ_REG = 0;
	*ULPD_CLOCK_CTRL_REG = 0;

	/* switch R10 pin function to EXT_MASTER_REQ, MCLKREQ input will go LOW and disable the request */
	outl( (inl(FUNC_MUX_CTRL_B) & ~(7<<18))|(1<<18), FUNC_MUX_CTRL_B);

#ifdef CONFIG_AV500_REV10
	/* switch of power converters for CF slot */
	omap_gpio_dir(4, GPIO_DIR_OUT);
	omap_gpio_set(4);
	omap_gpio_dir(3, GPIO_DIR_OUT);
	omap_gpio_set(3);

	/* UART2.RTS is SPDIF_RST */
	outb(inb(OMAP1510_UART2_BASE+0x10) | (0x02UL), OMAP1510_UART2_BASE+0x10);

        /* UART3.RTS is IRDA_ENABLE */
        outb(inb(OMAP1510_UART3_BASE+0x10) & ~(0x02UL), OMAP1510_UART3_BASE+0x10);

        /* UART1.RTS is OMAP_TX_OE */
        outb(inb(OMAP1510_UART1_BASE+0x10) & ~(0x02UL), OMAP1510_UART1_BASE+0x10);
	
	/* set SPDIF_OE to high */
	omap_mpuio_dir(0, GPIO_DIR_OUT);
	omap_mpuio_set(0);

        /* assert SPEAKER_OFF */
        omap_gpio_dir(13, GPIO_DIR_OUT);
        omap_gpio_set(13);
#else
	/* set all external control lines to '0' */
	omap_gpio_clr(3);
	omap_gpio_clr(4);
	outb(inb(OMAP1510_UART1_BASE+0x10) | (0x02UL), OMAP1510_UART1_BASE+0x10);
	outb(inb(OMAP1510_UART2_BASE+0x10) | (0x02UL), OMAP1510_UART2_BASE+0x10);
	outb(inb(OMAP1510_UART3_BASE+0x10) | (0x02UL), OMAP1510_UART3_BASE+0x10);
	omap_mpuio_clr(0);
	omap_gpio_clr(13);
#endif

	// this is read only!
	ULPD_SAVE(ULPD_STATUS_REQ_REG);
	
	/* turn off all interrupts but RTC_ALARM, KEYBOARD and MPUIO */
	/* i.e. enable IRQ0 on IH1 */
        outl(~(1UL<<0), OMAP_IH1_BASE+IRQ_MIR);

	/* ... enable IRQ1, IRQ5 and IRQ26 on IH2 */
	outl(~( (1UL<<1)|(1UL<<26)|(1UL<<5) ), OMAP_IH2_BASE+IRQ_MIR);

	/* ... disable all MPUIO interrupts but the one used for IRR (4) */
	outw(~(1UL<<4), ARMIO_GPIO_MASKIT);
	//outw(0xffff, ARMIO_GPIO_MASKIT);

	/* switch off power LED */
	omap_mpuio_set(12);
	
	do {
		unsigned int on_key_pressed = 0;

		/*
		 * acknowledge pending IRQs.
		 */
		inw(ARMIO_INPUT_LATCH);
		outl(~(1UL<<0), OMAP_IH1_BASE+IRQ_ITR);
		outl(~( (1UL<<1)|(1UL<<26)|(1UL<<5) ), OMAP_IH2_BASE+IRQ_ITR);
		outl(0x1, OMAP_IH1_BASE+IRQ_CONTROL_REG);
		outl(0x1, OMAP_IH2_BASE+IRQ_CONTROL_REG);
		
		// tristate HD_LED output
		omap_gpio_dir(9, GPIO_DIR_IN);
		wd_pin_high();
		omap1510_cpu_suspend();
		omap_gpio_dir(9, GPIO_DIR_OUT);
		
		wd_toggle();
		/* stay awake for at least 1 ms */
		mdelay(1);
		wd_toggle();

		/* if it's a RTC or MPUIO IRQ, break out immediately */
		if ( (inl(OMAP_IH2_BASE+IRQ_ITR) & ((1UL<<26)|(1UL<<5))) )
			break;
			
		/* else, wait until the ON key was pressed long enough */
		while (keypad_read() & 0x4000) {
			mdelay(1);
			wd_toggle();
			on_key_pressed++;
			if (on_key_pressed > 1000)
				break;
		}

		if (on_key_pressed > 1000)
			break;
		
	} while(1);

        /*
	 * restore ARM state
	 */
	ARM_RESTORE(ARM_CKCTL);
	ARM_RESTORE(ARM_EWUPCT);
	ARM_RESTORE(ARM_RSTCT1);
	ARM_RESTORE(ARM_RSTCT2);

	/*
	 * Restore MPUI control registers.
	 */

	MPUI_RESTORE(MPUI_CTRL_REG);
	MPUI_RESTORE(MPUI_DSP_BOOT_CONFIG);
	MPUI_RESTORE(MPUI_DSP_API_CONFIG);
/*	MPUI_RESTORE(PM_EMIFF_SDRAM_CONFIG);*/
        MPUI_RESTORE(PM_EMIFS_CONFIG_REG);
        MPUI_RESTORE(IRQ_MIR1);
	MPUI_RESTORE(IRQ_MIR2);

        /*
         * restore ULPD registers 
	 */
	ULPD_RESTORE(ULPD_CLOCK_CTRL_REG);
	ULPD_RESTORE(ULPD_SOFT_REQ_REG);

	/* switch R10 pin function to MCLKREQ, pin will go HIGH due to external pullup and enable the request */
	outl( inl(FUNC_MUX_CTRL_B) & ~(7<<18), FUNC_MUX_CTRL_B);

#ifdef CONFIG_AV500_REV10
        /* UART1.RTS is OMAP_TX_OE - enable */
        outb(inb(OMAP1510_UART1_BASE+0x10) | (0x02UL), OMAP1510_UART1_BASE+0x10);
#endif

        /* switch on power LED */
        omap_mpuio_clr(12);

        sti();
	stf();

	return;
}

#ifdef CONFIG_PROC_FS
static int g_read_completed;
/*
 * Writing to /proc/pm puts the CPU in sleep mode
 */
static ssize_t
omap_pm_write_proc(struct file * file, const char * buf, size_t count,
    loff_t *ppos)
{
	int rc;
	rc = pm_send_all(PM_SUSPEND, (void *)3);

	if (rc == 0) {
		omap_pm_suspend();

		pm_send_all(PM_RESUME, (void *)0);
	}
	return count;
}


static int omap_pm_read_proc(
	char *page_buffer,
	char **my_first_byte,
	off_t virtual_start,
	int length,
	int *eof,
	void *data)
{
	int my_buffer_offset = 0;
	char * const my_base = page_buffer;

#if 0
	/* disabled so that we can see the state of these registers immediately
         * before a suspend request */
	ARM_SAVE(ARM_CKCTL);
	ARM_SAVE(ARM_IDLECT1);
	ARM_SAVE(ARM_IDLECT2);
	ARM_SAVE(ARM_EWUPCT);
	ARM_SAVE(ARM_RSTCT1);
	ARM_SAVE(ARM_RSTCT2);
	ARM_SAVE(ARM_SYSST);

	ULPD_SAVE(ULPD_IT_STATUS_REG);
	ULPD_SAVE(ULPD_CLOCK_CTRL_REG);
	ULPD_SAVE(ULPD_SOFT_REQ_REG);
	ULPD_SAVE(ULPD_STATUS_REQ_REG);
	ULPD_SAVE(ULPD_DPLL_CTRL_REG);
	ULPD_SAVE(ULPD_POWER_CTRL_REG);

	MPUI_SAVE(MPUI_CTRL_REG);
	MPUI_SAVE(MPUI_DSP_STATUS_REG);
	MPUI_SAVE(MPUI_DSP_BOOT_CONFIG);
	MPUI_SAVE(MPUI_DSP_API_CONFIG);
	MPUI_SAVE(PM_EMIFF_SDRAM_CONFIG);
	MPUI_SAVE(PM_EMIFS_CONFIG_REG);
#endif

	if (virtual_start == 0) {
		g_read_completed = 0;

		my_buffer_offset += sprintf(my_base + my_buffer_offset,
		   "ARM_CKCTL:            0x%-4x     (0x%p)\n"
		   "ARM_IDLECT1:          0x%-4x     (0x%p)\n"
		   "ARM_IDLECT2:          0x%-4x     (0x%p)\n"
		   "ARM_EWUPCT:           0x%-4x     (0x%p)\n"
		   "ARM_RSTCT1:           0x%-4x     (0x%p)\n"
		   "ARM_RSTCT2:           0x%-4x     (0x%p)\n"
		   "ARM_SYSST:            0x%-4x     (0x%p)\n"
		   "ULPD_IT_STATUS_REG:   0x%-4x     (0x%p)\n"
		   "ULPD_CLOCK_CTRL_REG:  0x%-4x     (0x%p)\n"
		   "ULPD_SOFT_REQ_REG:    0x%-4x     (0x%p)\n"
		   "ULPD_DPLL_CTRL_REG:   0x%-4x     (0x%p)\n"
		   "ULPD_STATUS_REQ_REG:  0x%-4x     (0x%p)\n"
		   "ULPD_POWER_CTRL_REG:  0x%-4x     (0x%p)\n"
		   "MPUI_CTRL_REG         0x%-8x (0x%p)\n"
		   "MPUI_DSP_STATUS_REG:  0x%-8x (0x%p)\n"
		   "MPUI_DSP_BOOT_CONFIG: 0x%-8x (0x%p)\n"
		   "MPUI_DSP_API_CONFIG:  0x%-8x (0x%p)\n"
		   "PM_MPUI_SDRAM_CONFIG:    0x%-8x (0x%p)\n"
		   "PM_MPUI_EMIFS_CONFIG:    0x%-8x (0x%p)\n",
		   ARM_SHOW(ARM_CKCTL), ARM_CKCTL,
		   ARM_SHOW(ARM_IDLECT1), ARM_IDLECT1,
		   ARM_SHOW(ARM_IDLECT2), ARM_IDLECT2,
		   ARM_SHOW(ARM_EWUPCT), ARM_EWUPCT,
		   ARM_SHOW(ARM_RSTCT1), ARM_RSTCT1,
		   ARM_SHOW(ARM_RSTCT2), ARM_RSTCT2,
		   ARM_SHOW(ARM_SYSST), ARM_SYSST,
		   ULPD_SHOW(ULPD_IT_STATUS_REG), ULPD_IT_STATUS_REG,
		   ULPD_SHOW(ULPD_CLOCK_CTRL_REG), ULPD_CLOCK_CTRL_REG,
		   ULPD_SHOW(ULPD_SOFT_REQ_REG), ULPD_SOFT_REQ_REG,
		   ULPD_SHOW(ULPD_DPLL_CTRL_REG), ULPD_DPLL_CTRL_REG,
		   ULPD_SHOW(ULPD_STATUS_REQ_REG), ULPD_STATUS_REQ_REG,
		   ULPD_SHOW(ULPD_POWER_CTRL_REG), ULPD_POWER_CTRL_REG,
		   MPUI_SHOW(MPUI_CTRL_REG), MPUI_CTRL_REG,
		   MPUI_SHOW(MPUI_DSP_STATUS_REG), MPUI_DSP_STATUS_REG,
		   MPUI_SHOW(MPUI_DSP_BOOT_CONFIG), MPUI_DSP_BOOT_CONFIG,
		   MPUI_SHOW(MPUI_DSP_API_CONFIG), MPUI_DSP_API_CONFIG,
		   MPUI_SHOW(PM_EMIFF_SDRAM_CONFIG), PM_EMIFF_SDRAM_CONFIG,
		   MPUI_SHOW(PM_EMIFS_CONFIG_REG), PM_EMIFS_CONFIG_REG);
		g_read_completed++;
	} else if (g_read_completed >= 1) {
		 *eof = 1;
		 return 0;
	}
	g_read_completed++;

	*my_first_byte = page_buffer;
	return  my_buffer_offset;
}

int __init
omap_pm_init(void)
{
	struct proc_dir_entry *entry;

 	pm_idle = omap1510_pm_idle;

        omap1510_idle_loop_suspend = omap1510_idle_loop_suspend_impl;
        omap1510_cpu_suspend = omap1510_cpu_suspend_impl;

	printk("Power Management for the AV500 is initialized\n");
	entry = create_proc_read_entry("pm", S_IWUSR | S_IRUGO, NULL,
	    omap_pm_read_proc, 0);
	if (entry) {
		entry->write_proc = (write_proc_t *)omap_pm_write_proc;
	}
	
        return 0;
}
__initcall(omap_pm_init);

#endif /* CONFIG_PROC_FS */

#ifdef CONFIG_SYSCTL

/*
 * ARGH!  ACPI people defined CTL_ACPI in linux/acpi.h rather than
 * linux/sysctl.h.
 */

#define CTL_ACPI 9999
#define APM_S1_SLP_TYP 19

/*
 * Send us to sleep.
 */

static int sysctl_omap_pm_suspend(void)
{
	int retval;

	retval = pm_send_all(PM_SUSPEND, (void *)3);

	if (retval == 0) {
		omap_pm_suspend();

		pm_send_all(PM_RESUME, (void *)0);
	}

	return retval;
}

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

	{0}
};

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

/*
 * Initialize power interface
 */
static int __init pm_init(void)
{
	register_sysctl_table(pm_dir_table, 1);
	return 0;
}

__initcall(pm_init);

#endif /* CONFIG_SYSCTL */
