/*
 * arch/arm/mach-cxd4105/pm.c
 *
 * pm callback for cxd4105
 *
 * Copyright 2004,2005,2006,2007 Sony Corporation
 *
 * This code is based on arch/arm/mach-sa1100/pm.c.
 */
/*
 * SA1100 Power Management Routines
 *
 * 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.
 *
 * History:
 *
 * 2001-02-06:	Cliff Brake         Initial code
 *
 * 2001-02-25:	Sukjae Cho <sjcho@east.isi.edu> &
 * 		Chester Kuo <chester@linux.org.tw>
 * 			Save more value for the resume function! Support
 * 			Bitsy/Assabet/Freebird board
 *
 * 2001-08-29:	Nicolas Pitre <nico@cam.org>
 * 			Cleaned up, pushed platform dependent stuff
 * 			in the platform specific files.
 *
 * 2002-05-27:	Nicolas Pitre	Killed sleep.h and the kmalloced save array.
 * 				Storage is local on the stack now.
 */
#include <linux/init.h>
#include <linux/suspend.h>
#include <linux/time.h>
#include <linux/bootmem.h>

#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/setup.h>
#include <asm/mach-types.h>
#include <asm/page.h>
#include <asm/mach/arch.h>
#include <asm/arch/system.h>
#include <asm/mach/time.h>
#include <asm/arch/time.h>
#include <asm/arch/pm.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/mach/irq.h>
#include <linux/delay.h>
#include <asm/arch/regs-dmac.h>
#include <asm/arch/regs-clk.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-timer.h>
#include <asm/arch/hardware.h>
#include <asm/arch/memory.h>

#include <asm/mach/warmboot.h>

extern int cxd4105_cpu_suspend(int);
extern void cxd4105_cpu_resume(void);

#ifdef CONFIG_CXD4105_PM1
static void cxd4105_enable_sio(void)
{
	u32 temp = __raw_readl(VA_GPIO(3) + GPIOE_RESEN) & ~0x0FUL;
	
	__raw_writel(temp, VA_GPIO(3) + GPIOE_RESEN);

	__raw_writel(1, VA_GPIO(3)+GPIOE_PORTSEL(0));
	__raw_writel(1, VA_GPIO(3)+GPIOE_PORTSEL(1));
	__raw_writel(1, VA_GPIO(3)+GPIOE_PORTSEL(2));
	__raw_writel(1, VA_GPIO(3)+GPIOE_PORTSEL(3));

	__raw_writel(1, VA_CLKBLK_BASE + CLKBLK_PSCKSIO);
}
#endif /* CONFIG_CXD4105_PM1 */

static int _cxd4105_pm_enter(int mode)
{
	int err=0;
#ifdef CONFIG_SNSC_DEBUG_PROFILE_INITIAL_SUSPEND
	extern void profile_soft_init(void);
#endif
	
	printk("resume vector: %08x\n", CONFIG_RESUME_VECTOR);
	
#ifdef CONFIG_CXD4105_PM1
	cxd4105_enable_sio();
#endif /* CONFIG_CXD4105_PM1 */
	/* go zzz */
	if(cxd4105_cpu_suspend(mode)) {
		err = -1;
		printk("suspend error!\n");
	}

	cpu_init();

#ifdef CONFIG_SNSC_DEBUG_PROFILE_INITIAL_SUSPEND
	profile_soft_init();
#endif
	cxd4105_timer_early_init();
	
	return err;
}

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

#ifdef CONFIG_CXD4105_PM1
/*
  -------------
  category id
  -------------
  reserved
  -------------
  parity MSB
  ------------
  parity LSB
  ------------
  backup powr
  ------------
  system state
  -----------
 */
#define CATEGORY_ID   (0x90)
#define CATEGORY(buf)	(buf[0])
#define RESERVE(buf)	(buf[1])
#define PAR1(buf)	(buf[2])
#define PAR2(buf)	(buf[3])
#define BK_PWR(buf)	(buf[4])
#define SYS_STATE(buf)	(buf[5])
#define      CMD_HOT    (2)
#define      CMD_WARM   (1)
#define      CMD_COLD   (0)
#define RB_REASON(buf)  (buf[6])
#define RB_TMR_H(buf)	(buf[7])
#define RB_TMR_L(buf)   (buf[8])
#define BK_TMR_H(buf)   (buf[9])
#define BK_TMR_L(buf)   (buf[10])

static void cxd4105_mk_pwr_pkt(u8 cmd, u8 reason, u16 timer, u16 bk_timer)
{
	extern u8 suscmd;
	u8 *suscmd_b = &suscmd;
	u16 *suscmd_w = (u16 *)&suscmd;
	u16 parity;
	int i;

	memset(suscmd_b, 0, 128);
	CATEGORY(suscmd_b) = CATEGORY_ID;
	SYS_STATE(suscmd_b) = cmd;
	BK_PWR(suscmd_b) = (cmd == CMD_HOT);
	/* reboot timer */
	RB_REASON(suscmd_b) = reason;
	RB_TMR_H(suscmd_b) = (u8)((timer>>8)&0xFF);
	RB_TMR_L(suscmd_b) = (u8)(timer&0xFF);
	/* backup timer */
	BK_TMR_H(suscmd_b) = (u8)((bk_timer>>8)&0xFF);
	BK_TMR_L(suscmd_b) = (u8)(bk_timer&0xFF);

	parity = cpu_to_be16(suscmd_w[0]) ^ cpu_to_be16(suscmd_w[2]);
	for(i = 3; i < 128/2; i++) {
		parity ^= cpu_to_be16(suscmd_w[i]);
	}
	parity ^= cpu_to_be16(0x0f0f);

	PAR1(suscmd_b) = (u8)((parity >> 8) & 0xFF);
	PAR2(suscmd_b) = (u8)(parity & 0xFF);
	
	printk("command: ");
	for(i=0; i<11; i++) {
		printk("%02x ",suscmd_b[i]);
	}
	printk("\n");
}
#endif /* CONFIG_CXD4105_PM1 */

static u8 reboot_reason;
static u16 reboot_timer;
static u16 state_timer;

static void cxd4105_pm_halt(int reboot)
{
#ifdef CONFIG_ARCH_CXD4105
# ifdef CONFIG_CXD4105_PM1
	cxd4105_mk_pwr_pkt(CMD_COLD, reboot, 0, 0);
	cxd4105_enable_sio();
# endif /* CONFIG_CXD4105_PM1 */
	cxd4105_cpu_suspend((reboot)?PM_ENTER_RESET:PM_ENTER_POWEROFF);
#endif /* CONFIG_ARCH_CXD4105 */
#ifdef CONFIG_ARCH_CXD4108
	cxd4105_cpu_suspend((reboot)?PM_ENTER_RESET:PM_ENTER_ERROR);
#endif /* CONFIG_ARCH_CXD4108 */

	/* never reached */
}

void pm_suspend_error(int mode)
{
#ifdef CONFIG_ARCH_CXD4105
	cxd4105_pm_halt((mode == PM_ERROR_REBOOT));
#endif
#ifdef CONFIG_ARCH_CXD4108
	cxd4105_pm_halt(0);
#endif
	/* never reached */
}
		
static void cxd4105_prepare_cmd(suspend_state_t state)
{
#ifdef CONFIG_CXD4105_PM1
	cxd4105_mk_pwr_pkt((state == PM_SUSPEND_MEM? CMD_HOT:CMD_WARM),
			   reboot_reason,
			   reboot_timer,
			   state_timer);
#endif /* CONFIG_CXD4105_PM1 */
}

/*
 * Called after processes are frozen, but before we shut down devices.
 */
static int cxd4105_pm_prepare(suspend_state_t state)
{
	cxd4105_prepare_cmd(state);
	return warm_boot_pm_prepare(state);
}

static int cxd4105_pm_enter(suspend_state_t state)
{
	return _cxd4105_pm_enter(PM_ENTER_NORMAL);
}

/*
 * Called after devices are re-setup, but before processes are thawed.
 */
static int cxd4105_pm_finish(suspend_state_t state)
{
	reboot_reason = 0;
	reboot_timer = 0;
	state_timer = 0;

	return warm_boot_pm_finish(state);
}

static int cxd4105_valid(suspend_state_t state)
{
	return state == PM_SUSPEND_MEM || state == PM_SUSPEND_DISK;
}

/*
 * Set to PM_DISK_FIRMWARE so we can quickly veto suspend-to-disk.
 */
static struct pm_ops cxd4105_pm_ops = {
	.prepare	= cxd4105_pm_prepare,
	.enter		= cxd4105_pm_enter,
	.finish		= cxd4105_pm_finish,
	.valid		= cxd4105_valid,
};

extern struct kset power_subsys;

#define wb_attr(_name) \
static ssize_t _name##_show(struct kset * s, char * buf)            \
{                                                                        \
	return sprintf(buf, "%u\n", _name);                              \
}                                                                        \
static ssize_t _name##_store(struct kset * s,                       \
			     const char * buf, size_t n)                 \
{                                                                        \
	char *endp;                                                      \
	int value;                                                       \
	                                                                 \
	value = simple_strtoul(buf, &endp, 0);                           \
	if(*endp)                                                        \
		return -EINVAL;                                          \
                                                                         \
	_name = value;                                                   \
	return n;                                                        \
}                                                                        \
static struct subsys_attribute _name##_attr = {	                         \
	.attr	= {				                         \
		.name = __stringify(_name),	                         \
		.mode = 0644,			                         \
	},					                         \
	.show	= _name##_show,			                         \
	.store	= _name##_store,		                         \
}

wb_attr(reboot_reason);
wb_attr(reboot_timer);
wb_attr(state_timer);

static int __init cxd4105_pm_init(void)
{
	int error = 0;

	error = subsys_create_file(&power_subsys, &reboot_reason_attr);
	if (error) {
		goto out;
	}
	error = subsys_create_file(&power_subsys, &reboot_timer_attr);
	if (error) {
		goto out;
	}
	error = subsys_create_file(&power_subsys, &state_timer_attr);
	if (error) {
		goto out;
	}
	
	pm_set_ops(&cxd4105_pm_ops);
 out:
	return error;
}

late_initcall(cxd4105_pm_init);

#ifdef CONFIG_WARM_BOOT_IMAGE
int cxd4105_create_warmbootimage(void)
{
	return cxd_create_warmbootimage((unsigned long)cxd4105_cpu_resume, 0);
}
#else
int cxd4105_create_warmbootimage(void)
{
	return 0;
}
#endif

static void cxd4105_reset(void)
{
#ifdef CONFIG_CXD4105_PM1
	cxd4105_pm_halt(1);
#endif /* CONFIG_CXD4105_PM1 */
	local_irq_disable();
#ifdef CONFIG_CXD4105_PM2
	/* assert STABLE_RUN(TEST_OUT) 
	     STABLE_RUN is dedicated to output.
	     Not affected by port direction.
	 */
	writel(STABLERUN_MASK, VA_GPIO(GPIO_STABLERUN)+GPIO_DATA_CLEAR);
#endif /* CONFIG_CXD4105_PM2 */
#ifdef CONFIG_ARCH_CXD4108
	/* assert XRESET_REQ */
	writel(1, VA_MISCCTRL(MISCCTRL_XRESETREQ)+MISCCTRL_CLR);
#endif /* CONFIG_ARCH_CXD4108 */
	while (1) ;
}
void (*pm_machine_reset)(void) = cxd4105_reset;
EXPORT_SYMBOL(pm_machine_reset);

void cxd4105_poweroff(void)
{
#ifdef CONFIG_CXD4105_PM1
	cxd4105_pm_halt(0);
#endif /* CONFIG_CXD4105_PM1 */
	local_irq_disable();
#ifdef CONFIG_CXD4105_PM2
	/* assert XPOWER_OFF */
	writel(XPOWER_OFF, VA_GPIO(GPIO_POWEROFF)+GPIO_DATA_CLEAR);
#endif /* CONFIG_CXD4105_PM2 */
#ifdef CONFIG_ARCH_CXD4108
	/* DATA=L */
	writel(XPOWER_OFF, VA_GPIO(GPIO_POWEROFF)+GPIO_DATA_CLEAR);
	/* DIR=Output */
	gpio_set(GPIO_POWEROFF, GPIO_DIR, XPOWER_OFF_BIT);
#endif /* CONFIG_ARCH_CXD4108 */
	while (1) ;
}

static void cxd4105_safe_reboot(void)
{
	cxd4105_pm_halt(1);
}
void (*pm_safe_reboot)(void) = cxd4105_safe_reboot;
EXPORT_SYMBOL(pm_safe_reboot);
