/* 2009-08-20: File added and changed by Sony Corporation */
/*
 * linux/arch/arm/mach-mp200/pm.c
 *
 * M2 Power Management Routines
 *
 * Copyright (C) 2006 Nokia Corporation
 * Tony Lindgren <tony@atomide.com>
 *
 * Copyright (C) 2005 Texas Instruments, Inc.
 * Richard Woodruff <r-woodruff2@ti.com>
 *
 * Copyright (C) 2007 NEC Electronics Corporation
 *
 * Based on pm.c for omap2
 *
 * 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/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/mach/time.h>
#include <asm/arch/system.h>
#include <asm/arch/smu.h>

#include "cpu.h"

#include <asm/arch/gpio.h>


#include <linux/irq.h>

int mp200_sleep_while_idle = 0;

EXPORT_SYMBOL(mp200_sleep_while_idle);

/* refered by mp200_target() */
void mp200_change_normalx(unsigned int normal_div)
{
	ulong auto_frq_change;

	if ((inl(SMU_CLK_MODE_SEL) & 0x00000F00) == (normal_div << 8))
		return;

	/* auto freq change off */
	auto_frq_change = inl(SMU_AUTO_FRQ_CHANGE);
	outl((auto_frq_change & ~0x1), SMU_AUTO_FRQ_CHANGE);

	outl(normal_div, SMU_CLK_MODE_SEL);
	while ((inl(SMU_CLK_MODE_SEL) & 0x00000F00) != (normal_div << 8));

	/* restore auto freq change */
	outl(auto_frq_change, SMU_AUTO_FRQ_CHANGE);
}

#ifdef CONFIG_PM

#ifndef CONFIG_MP200_QR
#error CONFIG_MP200_QR not defined
#endif

/* for get_next_timer_interrupt */
#if 0
#if !defined(CONFIG_NO_IDLE_HZ) && !defined(CONFIG_NO_HZ)
#error CONFIG_NO_IDLE_HZ or CONFIG_NO_HZ not defined
#endif
#endif

#include <linux/cpu.h>
#include <linux/swap.h>
#include <linux/vmstat.h>
#include <asm/arch/hardware.h>
#include <asm/arch/pm.h>
#include <asm/arch/pmu.h>
#include <asm/arch/dma.h>
#include "pm_pmu.h"

static int acpu_div = 0;
static int pm_acpu_div = 0;
static unsigned long pm_loops_per_jiffy = 0;
static int sleep_while_idle_enable = 0;
static unsigned int sleep_while_idle_count = 0;
static unsigned int pm_idle_count = 0;
static unsigned int skip_ktimer_idle_count = 0;
static int pdma_enable = 1;
static unsigned int suspend_enable = 0;
extern struct kset power_subsys;
static int mp200_can_sleep(void);
static int mp200_enter_state(suspend_state_t state);
static int mp200_suspend_prepare(suspend_state_t state);
static int mp200_suspend_enter(suspend_state_t state);
static void mp200_suspend_finish(suspend_state_t state);
static int mp200_pm_enter(suspend_state_t state);
static int mp200_pm_prepare(suspend_state_t state);
static int mp200_pm_finish(suspend_state_t state);

/*This is just an arbitrary number */
#define FREE_PAGE_NUMBER (100)
/* #define PM_DEBUG_STATUS */

void mp200_pm_power_off(void)
{
	unsigned long irq_flags;

	/* int mask */
	local_irq_save(irq_flags);

	/* poweroff setting function*/
	mp200_pm_do_poweroff();

	printk(KERN_INFO "%s(): AFTER PMU POWER OFF.\n", __FUNCTION__);
	/* int unmask */
	local_irq_restore(irq_flags);	  /* not call */
}


void mp200_pm_idle(void)
{
	int suspend_req=0;

	local_irq_disable();
	local_fiq_disable();
	if (need_resched()) {
		local_fiq_enable();
		local_irq_enable();
		return;
	}

	pm_idle_count++;

	/* for PCM Direct */
	if( suspend_enable && pdma_enable ) {
		if( !(inl(SMU_POWERSW_STATUS) & 0x00000002) ) {
			suspend_req = 1;
		}
	}
	if (suspend_req) {
		mp200_sleep_while_idle = 1;
		if (mp200_can_sleep()) {
			mp200_enter_state(PM_SUSPEND_STANDBY);	/* L1 OFF */
			mp200_sleep_while_idle = 0;
			local_fiq_enable();
			local_irq_enable();
			return;
		}
		mp200_sleep_while_idle = 0;
	} else if (sleep_while_idle_enable) {
		mp200_sleep_while_idle = 1;
		if (mp200_can_sleep()) {
			mp200_enter_state(PM_SUSPEND_STANDBY);
			mp200_sleep_while_idle = 0;
			sleep_while_idle_count++;
			local_fiq_enable();
			local_irq_enable();
			return;
		}
		mp200_sleep_while_idle = 0;
	}
	
	/*
	 * Since an interrupt may set up a timer, we don't want to
	 * reprogram the hardware timer with interrupts enabled.
	 * Re-enable interrupts only after returning from idle.
	 */
	timer_dyn_reprogram();
	/* QR */
	outl(0x00530103, SMU_QR_ENA);
	arch_idle();
	local_fiq_enable();
	local_irq_enable();
}

#define GCLKCTRL4_CHECK_BIT	 (\
	(1 << (MP200_CLOCKGATE_SDIA_CLK & SMU_REG_BIT_MASK)) | \
	(1 << (MP200_CLOCKGATE_SDIC_CLK & SMU_REG_BIT_MASK)) \
)
/*	(1 << (MP200_CLOCKGATE_SDIB_CLK & SMU_REG_BIT_MASK)) | \ */

#define LCD_BASE             IO_ADDRESS(MP200_LCD_BASE)
#define LCD_LCDOUT           (LCD_BASE + 0x10)


static int check_transfer_state(void)
{
	if (inl(LCD_LCDOUT)) {
		return 1;
	}
	if (inl(SMU_GCLKCTRL4) & GCLKCTRL4_CHECK_BIT) {
		return 1;
	}
	if (mp200_dma_busy()) {
		return 1;
	}

	return 0;
}


#define MP200_SLEEP_THRESHOLD 1
extern struct mutex pm_mutex;

static int mp200_can_sleep(void)
{
	struct pm_message message = { .event = DEV_SUSPEND_IDLE_1, };
#if 0
	unsigned long last_jiffies, delta_jiffies;
#endif
	int ret = 0;

	if (!mutex_trylock(&pm_mutex))
		return 0;
#if 0
	/* check kernel timer */
	last_jiffies = jiffies;
	delta_jiffies = get_next_timer_interrupt(last_jiffies) - last_jiffies;
	if (delta_jiffies <= MP200_SLEEP_THRESHOLD) {
		skip_ktimer_idle_count++;
		goto Unlock;
	}
#endif
	if (check_transfer_state()) {
		goto Unlock;
	}

	if (device_suspend(message)) {
		goto Unlock;
	}

	device_resume(); /* restore dpm_list */

	ret = 1;

 Unlock:
	mutex_unlock(&pm_mutex);
	return ret;
}

static int mp200_enter_state(suspend_state_t state)
{
	int error;

	if (!mutex_trylock(&pm_mutex))
		return -EBUSY;

	if ((error = mp200_suspend_prepare(state)))
		goto Unlock;

	error = mp200_suspend_enter(state);

	mp200_suspend_finish(state);
 Unlock:
	mutex_unlock(&pm_mutex);
	return error;
}

static int mp200_suspend_prepare(suspend_state_t state)
{
	int error;
	unsigned int free_pages;

	if ((free_pages = global_page_state(NR_FREE_PAGES))
			< FREE_PAGE_NUMBER) {
		pr_debug("PM: free some memory\n");
		shrink_all_memory(FREE_PAGE_NUMBER - free_pages);
		if (nr_free_pages() < FREE_PAGE_NUMBER) {
			error = -ENOMEM;
			printk(KERN_ERR "PM: No enough memory\n");
			goto Thaw;
		}
	}

	if ((error = mp200_pm_prepare(state)))
		goto Thaw;

	error = device_suspend(PMSG_SUSPEND);
	if (error) {
		goto Resume_devices;
	}
	error = disable_nonboot_cpus();
	if (!error)
		return 0;

	enable_nonboot_cpus();
 Resume_devices:
	mp200_pm_finish(state);
	device_resume();
 Thaw:
	return error;
}

static int mp200_suspend_enter(suspend_state_t state)
{
	int error = 0;

	if ((error = device_power_down(PMSG_SUSPEND))) {
		printk(KERN_ERR "Some devices failed to power down\n");
		goto Done;
	}

	error = mp200_pm_enter(state);
	device_power_up();
 Done:
	return error;
}

static void mp200_suspend_finish(suspend_state_t state)
{
	enable_nonboot_cpus();
	mp200_pm_finish(state);
	device_resume();
}

static ssize_t sleep_while_idle_show(struct kset * subsys, char *buf)
{
	return sprintf(buf, "%d %u %u %u\n",
		sleep_while_idle_enable,
		sleep_while_idle_count,
		skip_ktimer_idle_count,
		pm_idle_count
		);
}

static ssize_t sleep_while_idle_store(struct kset * subsys,
					      const char * buf,
					      size_t n)
{
	int value;
	if (sscanf(buf, "%d", &value) != 1 ||
	    (value != 0 && value != 1)) {
		printk(KERN_ERR "idle_sleep_store: Invalid value\n");
		return -EINVAL;
	}
	sleep_while_idle_enable = value;
	return n;
}

static struct subsys_attribute sleep_while_idle_attr = {
	.attr	= {
		.name = __stringify(sleep_while_idle),
		.mode = 0644,
	},
	.show	= sleep_while_idle_show,
	.store	= sleep_while_idle_store,
};

static ssize_t pdma_enable_show(struct kset * subsys, char *buf)
{
	return sprintf(buf, "%d\n",pdma_enable);
}

static ssize_t pdma_enable_store(struct kset * subsys, 
					const char * buf, size_t n)
{
	int value;
	if (sscanf(buf, "%d", &value) != 1 ||
	    (value != 0 && value != 1)) {
		printk(KERN_ERR "pdma_enable_store: Invalid value\n");
		return -EINVAL;
	}
	pdma_enable = value;
	return n;
}

static struct subsys_attribute pdma_enable_attr = {
	.attr	= {
		.name = __stringify(pdma_enable),
		.mode = 0644,
	},
	.show	= pdma_enable_show,
	.store	= pdma_enable_store,
};

/* div_id=0...NORMALA / 1...NORMALB  / 2...NORMALC */
static int pm_get_acpu_div(int div_id)
{
	ulong div;

	switch(div_id) {
	case 0:  div = (inl(SMU_NORMALA_DIV) & 0x7); break;
	case 1:  div = (inl(SMU_NORMALB_DIV) & 0x7); break;
	default: div = (inl(SMU_NORMALC_DIV) & 0x7); break;
	}

	switch(div) {
	case 0:
	case 1:
	case 2:
	case 3: div += 1; break;
	case 4: div = 6;  break;
	case 5: div = 8;  break;
	default: div = 2; break;
	}

	return div;
}

void pm_change_normalA(void)
{
	mp200_change_normalx(NORMAL_A);

	loops_per_jiffy = pm_loops_per_jiffy;
}

static void pm_change_normalB(void)
{
	mp200_change_normalx(NORMAL_B);

	pm_acpu_div = pm_get_acpu_div(1);
	loops_per_jiffy = pm_loops_per_jiffy/(pm_acpu_div/acpu_div);
}

static void mp200_pm_suspend(suspend_state_t state)
{
	unsigned int sleep_mode;

	switch (state) {
	case PM_SUSPEND_STANDBY:
		sleep_mode = (MP200_PMU_CLK_SLEEP);
		break;
	case PM_SUSPEND_MEM:
	default:
		sleep_mode = (MP200_PMU_CLK_POWERDOWN);
		break;
	}

	mp200_pmu_sleep(sleep_mode);

}

static int mp200_pm_valid(suspend_state_t state)
{
	return ((state == PM_SUSPEND_STANDBY) || (state == PM_SUSPEND_MEM));
}

static int mp200_pm_prepare(suspend_state_t state)
{
	switch (state) {
	case PM_SUSPEND_STANDBY:
	case PM_SUSPEND_MEM:
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static int mp200_pm_enter(suspend_state_t state)
{
	switch (state) {
	case PM_SUSPEND_STANDBY:
	case PM_SUSPEND_MEM:
		mp200_pm_suspend(state);
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static int mp200_pm_finish(suspend_state_t state)
{
	return 0;
}

static struct pm_ops mp200_pm_ops = {
	.valid = mp200_pm_valid,
	.prepare = mp200_pm_prepare,
	.enter = mp200_pm_enter,
	.finish = mp200_pm_finish,
};


int mp200_pm_sleep(unsigned int mode)
{
	suspend_state_t state;
	int ret;

	switch (mode) {
	case PM_SLEEP_MODE_S1:
	case PM_SLEEP_MODE_S2:
		state = PM_SUSPEND_STANDBY;
		break;
	case PM_SLEEP_MODE_S3:
		state = PM_SUSPEND_MEM;
		break;
	default:
		return -EINVAL;
	}

	ret = pm_suspend(state);

	return ret;
}

void mp200_pm_pdma_suspend_enable(void)
{
	suspend_enable = 1;
}

void mp200_pm_pdma_suspend_disable(void)
{
	suspend_enable = 0;
}

unsigned int mp200_pm_pdma_suspend_status(void)
{
	return suspend_enable;
}

EXPORT_SYMBOL(mp200_pm_pdma_suspend_enable);
EXPORT_SYMBOL(mp200_pm_pdma_suspend_disable);
EXPORT_SYMBOL(mp200_pm_pdma_suspend_status);

#ifdef CONFIG_PROC_FS

/*
 * Writing to /proc/pm puts the CPU in sleep mode
 */
static ssize_t mp200_pm_write_proc(struct file *file, const char *buffer,
				   size_t count, loff_t * ppos)
{
	char *buf;
	char *tok;
	char *s;
	char *str;
	char *whitespace = " \t\r\n";
	int ret = 0;

	if (current->uid != 0) {
		return -EPERM;
	}
	if (count == 0) {
		return 0;
	}

	buf = kmalloc(count + 1, GFP_KERNEL);
	if (buf == NULL) {
		ret = -ENOMEM;
		goto exit_2;
	}
	if (copy_from_user(buf, buffer, count) != 0) {
		ret = -EFAULT;
		goto exit_1;
	}
	buf[count] = '\0';
	s = buf + strspn(buf, whitespace);
	tok = strsep(&s, whitespace);

	str = "M500";
	if (strnicmp(tok, str, strlen(str) + 1) == 0) {
		printk("500MHz...\n");
		pm_change_normalA();
		goto exit_1;
	}
	str = "M166";
	if (strnicmp(tok, str, strlen(str) + 1) == 0) {
		printk("166MHz...\n");
		pm_change_normalB();
		goto exit_1;
	}
	str = "S1";
	if (strnicmp(tok, str, strlen(str) + 1) == 0) {
		mp200_pm_sleep(PM_SLEEP_MODE_S1);
		goto exit_1;
	}
	str = "S2";
	if (strnicmp(tok, str, strlen(str) + 1) == 0) {
		mp200_pm_sleep(PM_SLEEP_MODE_S2);
		goto exit_1;
	}
	str = "S3";
	if (strnicmp(tok, str, strlen(str) + 1) == 0) {
		mp200_pm_sleep(PM_SLEEP_MODE_S3);
		goto exit_1;
	}
	str = "PD";
	if (strnicmp(tok, str, strlen(str) + 1) == 0) {
		mp200_pm_power_off();
		goto exit_1;
	}
	ret = -EINVAL;

      exit_1:
	kfree(buf);
      exit_2:

	if (ret == 0) {
		return count;
	}

	return ret;
}

static int mp200_pm_read_proc(char *page, char **start, off_t off, int count,
			      int *eof, void *data)
{
#ifdef PM_DEBUG_REG
	printk("** SMU registers dump ** \n");
	printk("SMU_AUTO_FRQ_CHANGE = 0x%08x \n", inl(SMU_AUTO_FRQ_CHANGE));
	printk("SMU_CLK_MODE_SEL    = 0x%08x \n", inl(SMU_CLK_MODE_SEL));
	printk("SMU_NORMALA_DIV     = 0x%08x \n", inl(SMU_NORMALA_DIV));
	printk("SMU_NORMALB_DIV     = 0x%08x \n", inl(SMU_NORMALB_DIV));

	printk("\n");
	printk("loops_per_jiffy     = 0x%08x \n",
	       (unsigned int)loops_per_jiffy);
#endif				/* PM_DEBUG_REG */

	return 0;
}

#endif				/* CONFIG_PROC_FS */

int __init mp200_pm_init(void)
{
	int ret;

	printk("Power Management for MP200.\n");

	pm_idle = mp200_pm_idle;
	pm_set_ops(&mp200_pm_ops);
	pm_loops_per_jiffy = loops_per_jiffy;
	acpu_div = pm_get_acpu_div(0);

	ret = subsys_create_file(&power_subsys, &sleep_while_idle_attr);
	if (ret) {
		printk(KERN_ERR "subsys_create_file failed: %d\n", ret);
	}

	ret = subsys_create_file(&power_subsys, &pdma_enable_attr);
	if (ret) {
		printk(KERN_ERR "subsys_create_file failed : pdma_enable : %d\n", ret);
	}
	
#ifdef CONFIG_PROC_FS
	{
		struct proc_dir_entry *entry;
		entry = create_proc_read_entry("pm", S_IWUSR | S_IRUGO, NULL,
					       mp200_pm_read_proc, 0);
		if (entry == NULL) {
			return -ENOENT;
		}
		entry->write_proc = (write_proc_t *) mp200_pm_write_proc;
	}
#endif

	pm_power_off = mp200_pm_power_off;

	return 0;
}

EXPORT_SYMBOL(mp200_pm_sleep);

#else /* CONFIG_PM */

void mp200_pm_idle(void)
{
	local_irq_disable();
	local_fiq_disable();
	if (need_resched()) {
		local_fiq_enable();
		local_irq_enable();
		return;
	}

	/*
	 * Since an interrupt may set up a timer, we don't want to
	 * reprogram the hardware timer with interrupts enabled.
	 * Re-enable interrupts only after returning from idle.
	 */
	timer_dyn_reprogram();
#ifdef CONFIG_MP200_QR
	outl(0x00530103, SMU_QR_ENA);
#else
	outl(0x00530100, SMU_QR_ENA);
#endif
	arch_idle();
	local_fiq_enable();
	local_irq_enable();
}

int __init mp200_pm_init(void)
{
	printk("Power Management for MP200.\n");

	pm_idle = mp200_pm_idle;

	return 0;
}

#endif /* CONFIG_PM */

__initcall(mp200_pm_init);
