/*
 * arch/arm/mach-psionw/psionw-power.c - Psion power managemenent
 *
 * Copyright (C) 2002 Tony Lindgren <tony@atomide.com>
 *
 * Contrast restore after sleep fix (C) 2002 by Simon Howard
 */

#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware/psionw.h>
#include <asm/arch/irqs.h>
#include <asm/arch/psionw-power.h>
#include <linux/proc_fs.h>
#include <linux/module.h>

extern void disable_irq(unsigned int irq);
extern void enable_irq(unsigned int irq);
extern void psionw_lcd_powerdown(int lock);
extern void psionw_lcd_powerup(int lock);
extern void etna_powerdown(int lock);
extern void etna_powerup(int lock);
extern void ssi5mx_powerdown(int lock);
extern void ssi5mx_powerup(int lock);
extern int psion_set_contrast(int new_contrast, int lock);
extern int psion_get_contrast(void);


static int psion_rtc_off;

/*
 * Stops the DC to DC converter
 */
void stop_pump(int lock)
{
	long flags = 0;

	if (lock)
		save_flags_cli(flags);

	psionw_writel(PUMP_STOP_VAL,PUMPCON);
	psionw_writeb(psionw_readb(PDDR) & ~PDDR_PUMP_PWR1, PDDR);

	if (lock)
		restore_flags(flags);
}

void stop_timer(int lock)
{
	unsigned int cfg;
	unsigned long goto_sleep;
	long flags = 0;

	if (lock)
		save_flags_cli(flags);

	psion_rtc_off = xtime.tv_sec - RTCTIME;

	goto_sleep = jiffies;
	do {
		disable_irq(IRQ_TC2OI);
	} while (time_after(jiffies, goto_sleep + HZ/50));
	cfg = psionw_readl(TC2CTRL);
	cfg &= ~TC_ENABLE;
	psionw_writel(cfg, TC2CTRL);
	
	if (lock)
		restore_flags(flags);
}

void start_timer(int lock)
{
	unsigned int cfg;
	long flags = 0;

	if (lock)
		save_flags_cli(flags);

	cfg = psionw_readl(TC2CTRL);

	/* These bits need to initialized to 0 */
	cfg &= ~TC_BIT2;
	cfg &= ~TC_BIT4;
	cfg &= ~TC_BIT5;
	cfg |= TC_CLKSEL;		// 512kHz mode
	cfg |= TC_MODE;
	cfg |= TC_ENABLE;

	psionw_writel(cfg, TC2CTRL);
	psionw_writel(5119, TC2LOAD);	/* 512kHz / 100Hz - 1 */

	psionw_writel(1, TC2EOI);
	enable_irq(IRQ_TC2OI);

	if (lock)
		restore_flags(flags);

	/* Set the correct time from RTC */
	xtime.tv_sec = RTCTIME;
	xtime.tv_sec += psion_rtc_off;
}

/*
 * Puts the Psion into sleep mode until a keyboard, serial,
 * touch panel, or RTC interrupt is received.
 */
void psion_off(void)
{
	int adc_clock = 0, rfdiv = 0, rtcdiv1 = 0, rtcdiv2 = 0;
	int uart1 = 0, uart2 = 0;
	long flags;

	if (psionw_readl(PDDR) & PDDR_PUMP_PWR1) {

		save_flags_cli(flags);
		if (psionw_readb(PCDR) & PCDR_UART1) {
			uart1 = 1;
			//printk("Looks like UART1 is on\n");
			psionw_writeb(psionw_readb(PCDR) & ~PCDR_UART1, PCDR);
		}

		if (psionw_readb(PCDR) & PCDR_UART2) {
			uart2 = 1;
			//printk("Looks like UART2 is on\n");
			psionw_writeb(psionw_readb(PCDR) & ~PCDR_UART2, PCDR);
		}
		restore_flags(flags);

		/* First we must stop the timer */
		stop_timer(1);

		/* 
		 * We establish a long lock for the whole sleep-wake procedure
		 */
		save_flags_cli(flags);

		psionw_writeb(psionw_readb(PDDR) & ~PDDR_SLED, PDDR);
		psionw_writel(0, KSCAN);
		ssi5mx_powerdown(0);		/* SSI, saves only about 0.1mA */

#ifdef CONFIG_PCMCIA_ETNA
		etna_powerdown(0);
#endif

		psionw_lcd_powerdown(0);
		stop_pump(0);			/* DC to DC, saves about 0.5mA */

		/* Lower the DRAM refresh to 1 KHz, no real savings */
		rfdiv = psionw_readl(DRAM_CFG) & 0x7f;
		psionw_writel(psionw_readl(DRAM_CFG) | 0x7f, DRAM_CFG);

		/* Disable the ADC clock, no real savings */
		adc_clock = (psionw_readl(PWRCNT) >> 8) & 0xff;
		psionw_writel(psionw_readl(PWRCNT) | (0xff << 8), PWRCNT);

		cpu_cache_clean_invalidate_all();

		rtcdiv1 = psionw_readb(PWRSR) & 0x3f;
		while(rtcdiv1 == rtcdiv2) {
			rtcdiv2 = psionw_readb(PWRSR) & 0x3f;
		}
		while(rtcdiv2 == rtcdiv1) {
			rtcdiv1 = psionw_readb(PWRSR) & 0x3f;
		}

		/* Now go to sleep */
		psionw_writel(1, STBY);
		__asm__ __volatile__("\n"
				     "mov     r0, r0\n"
				     "mov     r0, r0\n"
				     "mov     r0, r0\n"
				     );

		/* We're back from sleep */

		/* Restore the ADC clock */
		psionw_writel(psionw_readl(PWRCNT) &  ~(0xff << 8), PWRCNT);
		psionw_writel(psionw_readl(PWRCNT) | (adc_clock << 8), PWRCNT);

		/* Restore DRAM refresh rate */
		psionw_writel(psionw_readl(DRAM_CFG) & ~0x7f, DRAM_CFG);
		psionw_writel(psionw_readl(DRAM_CFG) | rfdiv, DRAM_CFG);

		start_pump(0);
		psionw_lcd_powerup(0);

		/* Restore contrast */
		psion_set_contrast(psion_get_contrast(), 1);

#ifdef CONFIG_PCMCIA_ETNA
		etna_powerup(0);
#endif

		ssi5mx_powerup(0);

		/* 
		 * And finally we release the long lock 
		 */
		restore_flags(flags);
		start_timer(1);

		save_flags_cli(flags);
		if (uart1)
			psionw_writeb(psionw_readb(PCDR) | PCDR_UART1, PCDR);

		if (uart2)
			psionw_writeb(psionw_readb(PCDR) | PCDR_UART2, PCDR);
		restore_flags(flags);

	} else {
		/* Just in case the LCD is off */
		psionw_lcd_powerup(1);
	}
}

/*
 * Returns the CPU speed
 */
unsigned int psionw_get_cpu_speed(int cpu)
{
	int speed;
	speed = (psionw_readl(PWRCNT) & 0x7) >> 2;
	if (speed) {
		return 36864000;
	} else {
		return 11432000;
	}
}

/*
 * Changes the CPU speed
 */
void psionw_set_cpu_speed(int speed)
{
	int pwrcnt;
	long flags = 0;

	save_flags_cli(flags);
	cpu_cache_clean_invalidate_all();
	pwrcnt = psionw_readl(PWRCNT);
	if (speed) {
		pwrcnt |= (1 << 2);
		printk("Setting the CPU speed to 36Mhz\n");
		psionw_writel(pwrcnt, PWRCNT);
	} else {
		pwrcnt &= ~(1 << 2);
		printk("Setting the CPU speed to 18Mhz\n");
		psionw_writel(pwrcnt, PWRCNT);
	}
	restore_flags(flags);
}		   
