/* arch/mips/nxp/pnx833x/common/pm.c
 *
 * Copyright 2008 NXP Semiconductors
 *	Stephen Jones <stephen.jones@nxp.com>
 *
 * NXP pnx833x platform level power management
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/suspend.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <gpio.h>
#include <irq.h>


#define SUSPEND_PRIO 13
#define WAKEUP_INT (PNX833X_PIC_GPIO_INT - PNX833X_PIC_IRQ_BASE)
#define CLEAR_BIT 29
#define GPIO_WAKEUP 3
#define IE_MASK (IE_SW0 | IE_SW1 | IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4 | IE_IRQ5)

extern void pnx833x_pm_board_standby(void);
extern void pnx833x_pm_board_resume(void);

extern int cp0_compare_irq;
static unsigned long keep_priority=0;
static unsigned long keep_wakeup_int=0;
static unsigned long keep_gpio_enable=0;
static unsigned long keep_gpio_pending=0;
static unsigned long keep_mips_int_enables=0;
static unsigned long keep_wakeup_edge=0;
static unsigned long keep_wakeup_hi=0;
static unsigned long keep_wakeup_lo=0;
static unsigned long keep_pwr_dwn_1_ctl=0;
static unsigned long keep_pwr_dwn_2_ctl=0;


static void pnx833x_pm_enable_wakeup_interrupts(void)
{

	/* Store the old interrupt priority then overwrite our wakeup priority*/
	keep_priority = PNX833X_PIC_INT_PRIORITY;
	PNX833X_PIC_INT_PRIORITY=SUSPEND_PRIO;

	/* Store the status of the wakeup int then clear it and set it to wakeup priority*/
	keep_wakeup_int = PNX833X_PIC_INT_REG(WAKEUP_INT);

	PNX833X_PIC_INT_REG(WAKEUP_INT) = (SUSPEND_PRIO+1);
	/*PNX833X_PIC_INT_REG(WAKEUP_INT) |= (1 << CLEAR_BIT);*/

	/* Make sure only the relevant GPIO bit can wake us up (and clear that bit) */
	keep_gpio_enable = PNX833X_PIO_INT_ENABLE;
	keep_gpio_pending = PNX833X_PIO_INT_STATUS;

	/* Store our wakeup gpio's interrupt status */
	PNX833X_PIO_INT_ENABLE = 0;
	keep_wakeup_edge = PNX833X_PIO_INT_EDGE;
	keep_wakeup_hi = PNX833X_PIO_INT_HI;
	keep_wakeup_lo = PNX833X_PIO_INT_LO;

	gpio_setup_irq(GPIO_INT_LEVEL_LOW, GPIO_WAKEUP);
	gpio_clear_irq(GPIO_WAKEUP);
	gpio_enable_irq(GPIO_WAKEUP);


	/* Disable everything except the PIC interrupt to the MIPS */
	keep_mips_int_enables = read_c0_status();
	write_c0_status((keep_mips_int_enables & ~(IE_MASK)) | IE_IRQ2 /*| IE_IRQ5*/);

	/* Turn on global interrupts */
	local_irq_enable();
}

static void pnx833x_pm_disable_wakeup_interrupts(void)
{
	unsigned long temp;
	/* Turn off global interrupts */
	local_irq_disable();
    
	/* Restore the original MIPS interrupt enables */
	temp = read_c0_status();
	write_c0_status(temp | (keep_mips_int_enables & IE_MASK));

	/*Restore GPIO interrupt settings*/
	if (keep_gpio_pending & (1 << GPIO_WAKEUP))
	{
		PNX833X_PIO_INT_SET |= (1 << GPIO_WAKEUP);
	}

	/* Restore wakeup gpio's interrupt status */
	PNX833X_PIO_INT_EDGE = keep_wakeup_edge;
	PNX833X_PIO_INT_HI = keep_wakeup_hi;
	PNX833X_PIO_INT_LO = keep_wakeup_lo;
	PNX833X_PIO_INT_ENABLE = keep_gpio_enable;

	/*Restore GPIO INT setting*/
	PNX833X_PIC_INT_REG(WAKEUP_INT) = keep_wakeup_int;

	/*Restore PIC interrupt priority*/
	PNX833X_PIC_INT_PRIORITY = keep_priority;
}

static void pnx833x_pm_pre_cpu_wait(void)
{

	keep_pwr_dwn_1_ctl = PNX833X_CLOCK_PWR_DWN_1_CTL;
	keep_pwr_dwn_2_ctl = PNX833X_CLOCK_PWR_DWN_2_CTL;

	PNX833X_CLOCK_PWR_DWN_2_CTL = 0x0;
	PNX833X_CLOCK_PWR_DWN_1_CTL = 0x0;
}

static void pnx833x_pm_post_cpu_wait(void)
{
	PNX833X_CLOCK_PWR_DWN_1_CTL = keep_pwr_dwn_1_ctl;
	PNX833X_CLOCK_PWR_DWN_2_CTL = keep_pwr_dwn_2_ctl;
}


void pnx833x_pm_suspend_standby_enter(void)
{

	/* Here we do the other pnx8335 specific hardware stuff that we need
	   to do when we want to go into standby */

	if (cpu_wait)
	{
		/* Do we ignore non-wakeup ints here or mask them?*/
		/* Switch on interrupts so that something can get us out of this state*/
		pnx833x_pm_enable_wakeup_interrupts();

		/* Shut off any hardware which might be unique to our particular board */
		pnx833x_pm_board_standby();

		/* Do the last set of hardware writes before we go to standby*/
		pnx833x_pm_pre_cpu_wait();

		/* Go into low power state until we get our wakeup*/
		(*cpu_wait)();

		/* Do the first set of hardware writes after coming out of standby*/
		pnx833x_pm_post_cpu_wait();

		/* Turn on any hardware which might be unique to our particular board */
		pnx833x_pm_board_resume();

		/* Switch off interrupts in preparation for the resume process */
		pnx833x_pm_disable_wakeup_interrupts();
	}

}


/*
 *	pnx8335_pm_valid - Tell the PM core that we only support the standby sleep
 *			state
 *	@state:		suspend state we're checking.
 *
 */
static int pnx833x_pm_valid(suspend_state_t state)
{
	return (state == PM_SUSPEND_STANDBY);
}

/*
 *	pnx8335_pm_enter - Actually enter a sleep state.
 *	@state:		State we're entering.
 *
 */
static int pnx833x_pm_enter(suspend_state_t state)
{
	switch (state) {
	case PM_SUSPEND_STANDBY:
		pnx833x_pm_suspend_standby_enter();
		break;

	case PM_SUSPEND_MEM:
		return -ENOTSUPP;

	default:
		return -EINVAL;
	}

	return 0;
}


static struct platform_suspend_ops pnx833x_pm_ops = {
	.valid		= pnx833x_pm_valid,
	.enter		= pnx833x_pm_enter,
};

irqreturn_t pm_wakeup_interrupt(int irq, void *dev_id)
{
	/* If you need to do anything on the wakeup interrupt, do it here!*/
	return IRQ_HANDLED;
}

static int __init pnx833x_pm_init(void)
{
	int tmp;
	irq_handler_t fn;
	suspend_set_ops(&pnx833x_pm_ops);

	fn = pm_wakeup_interrupt;

	tmp = request_irq(PNX833X_GPIO_IRQ_BASE + GPIO_WAKEUP, fn,
			  0, "pm_wakeup", NULL);

	if (tmp)
		printk("PM INT REQUEST FAILED %08x\n", tmp);


	return 0;
}

device_initcall(pnx833x_pm_init);
