/* 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 <prjconfig.h>
#include <gpio.h>
#include <irq.h>
#include <pm.h>

/*
   ENABLE_PNX833X_GPIO_REGS_DIRECT_ACCESS is a temporary hack...
*/
#define ENABLE_PNX833X_GPIO_REGS_DIRECT_ACCESS
#include <pnx833x.h>


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

#define PNX833X_CONFIG_CPU_COUNTERS_CONTROL	(0x701C)
#define PNX833X_CONFIG_CPU_WATCHDOG		(0x7054)
#define PNX833X_RESET_CONFIG			(0x8008)

extern int cp0_compare_irq;
static unsigned long keep_priority=0;
static unsigned long keep_wakeup_int=0;
static unsigned long keep_wdt_control=0;
static unsigned long keep_wdt_reset_config=0;
static unsigned long keep_mips_int_enables=0;
static unsigned long keep_pwr_dwn_1_ctl=0;
static unsigned long keep_pwr_dwn_2_ctl=0;

#if defined (CONFIG_PNX833X_GPIO_INTERRUPTS)
static unsigned long keep_gpio_enable=0;
static unsigned long keep_gpio_pending=0;
static unsigned long keep_wakeup_edge=0;
static unsigned long keep_wakeup_hi=0;
static unsigned long keep_wakeup_lo=0;
#endif

static unsigned int get_gpio_int=0;

static pnx833x_pm_wakeup pnx833x_pm_ir_wakeup_callback = NULL;

/*
   Board specific code:

   pnx833x_pm_board_standby() - called just before system goes into standby
   pnx833x_pm_board_resume() - called just after system has come out of standby
*/

#if defined (CONFIG_NXP_STB225)

#define GPIO_WAKEUP	3
#define GPIO_LED1	21
#define GPIO_LED2	22

static unsigned int keep_gpio_sel;
static unsigned int keep_gpio_dir;
static unsigned int keep_gpio_out;

void pnx833x_pm_board_standby(void)
{
	/* keep original gpio state */
	keep_gpio_sel = PNX833X_PIO_SEL;
	keep_gpio_dir = PNX833X_PIO_DIR;
	keep_gpio_out = PNX833X_PIO_OUT;

	/* switch off leds */
	PNX833X_PIO_SEL &= ~((1 << GPIO_LED1) | (1 << GPIO_LED2));
	PNX833X_PIO_DIR |=   (1 << GPIO_LED1) | (1 << GPIO_LED2);
	PNX833X_PIO_OUT |=   (1 << GPIO_LED1) | (1 << GPIO_LED2);
}

void pnx833x_pm_board_resume(void)
{
	/* restore pre standby gpio state */
	PNX833X_PIO_OUT = keep_gpio_out;
	PNX833X_PIO_DIR = keep_gpio_dir;
	PNX833X_PIO_SEL = keep_gpio_sel;
}

#else

// #define GPIO_WAKEUP	??

void pnx833x_pm_board_standby(void)
{
}

void pnx833x_pm_board_resume(void)
{
}

#endif

void pnx833x_pm_ir_wakeup_register_callback(pnx833x_pm_wakeup callback)
{
	pnx833x_pm_ir_wakeup_callback = callback;
}

static void pnx833x_pm_enable_wakeup_interrupts(void)
{
	/* Store the watchdog config then disable watchdog to avoid system reboot during standby */
	keep_wdt_control = PNX833X_REG(PNX833X_CONFIG_CPU_COUNTERS_CONTROL);
	keep_wdt_reset_config = PNX833X_REG(PNX833X_RESET_CONFIG);
	PNX833X_REG(PNX833X_CONFIG_CPU_COUNTERS_CONTROL) = (keep_wdt_control & ~(1 << 0));
	PNX833X_REG(PNX833X_RESET_CONFIG) = (keep_wdt_reset_config & ~(1 << 0));

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

#if defined (CONFIG_PNX833X_GPIO_INTERRUPTS)

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

#endif

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

#if defined (CONFIG_PNX833X_GPIO_INTERRUPTS)

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

#endif

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

	/*Restore watchdog config*/
	PNX833X_REG(PNX833X_CONFIG_CPU_COUNTERS_CONTROL) = keep_wdt_control;
	PNX833X_REG(PNX833X_RESET_CONFIG) = keep_wdt_reset_config;
}

static void pnx833x_pm_pre_cpu_wait(void)
{
	unsigned long pwr_dwn_1_ctrl = 0;

	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;
#ifdef CONFIG_IR_INPUT_0
	pwr_dwn_1_ctrl |= (1<<1);
#endif
#ifdef CONFIG_IR_INPUT_1
	pwr_dwn_1_ctrl |= (1<<19);
#endif
	PNX833X_CLOCK_PWR_DWN_1_CTL = pwr_dwn_1_ctrl;
}

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();

		get_gpio_int = 0;
		if(pnx833x_pm_ir_wakeup_callback)
			pnx833x_pm_ir_wakeup_callback();

		/* 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*/
		do{
			(*cpu_wait)();

			if(get_gpio_int)
				break;

			if(pnx833x_pm_ir_wakeup_callback)
				if(pnx833x_pm_ir_wakeup_callback())
					break;

		}while(1);

		/* 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,
};

#if defined (CONFIG_PNX833X_GPIO_INTERRUPTS)
irqreturn_t pm_wakeup_interrupt (int irq, void *dev_id)
{
	/* If you need to do anything on the wakeup interrupt, do it here!*/
	get_gpio_int = 1;
	return IRQ_HANDLED;
}
#endif

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

#if defined (CONFIG_PNX833X_GPIO_INTERRUPTS)
	{
		int tmp;
		irq_handler_t fn = pm_wakeup_interrupt;
		int irq = PNX833X_GPIO_IRQ_BASE + GPIO_WAKEUP;

		tmp = request_irq (irq, fn, 0, "pm_wakeup", NULL);
		if (tmp)
			printk ("PM INT REQUEST FAILED %08x\n", tmp);
	}
#endif

	return 0;
}

device_initcall(pnx833x_pm_init);

EXPORT_SYMBOL(pnx833x_pm_ir_wakeup_register_callback);

