diff options
Diffstat (limited to 'arch/mips/alchemy/common/power.c')
| -rw-r--r-- | arch/mips/alchemy/common/power.c | 465 | 
1 files changed, 465 insertions, 0 deletions
diff --git a/arch/mips/alchemy/common/power.c b/arch/mips/alchemy/common/power.c new file mode 100644 index 00000000000..bd854a6d1d8 --- /dev/null +++ b/arch/mips/alchemy/common/power.c @@ -0,0 +1,465 @@ +/* + * BRIEF MODULE DESCRIPTION + *	Au1xx0 Power Management routines. + * + * Copyright 2001, 2008 MontaVista Software Inc. + * Author: MontaVista Software, Inc. <source@mvista.com> + * + *  Some of the routines are right out of init/main.c, whose + *  copyrights apply here. + * + *  This program is free software; you can redistribute	 it and/or modify it + *  under  the terms of	 the GNU General  Public License as published by the + *  Free Software Foundation;  either version 2 of the	License, or (at your + *  option) any later version. + * + *  THIS  SOFTWARE  IS PROVIDED	  ``AS	IS'' AND   ANY	EXPRESS OR IMPLIED + *  WARRANTIES,	  INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN + *  NO	EVENT  SHALL   THE AUTHOR  BE	 LIABLE FOR ANY	  DIRECT, INDIRECT, + *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + *  NOT LIMITED	  TO, PROCUREMENT OF  SUBSTITUTE GOODS	OR SERVICES; LOSS OF + *  USE, DATA,	OR PROFITS; OR	BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + *  ANY THEORY OF LIABILITY, WHETHER IN	 CONTRACT, STRICT LIABILITY, OR TORT + *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *  You should have received a copy of the  GNU General Public License along + *  with this program; if not, write  to the Free Software Foundation, Inc., + *  675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/sysctl.h> +#include <linux/jiffies.h> + +#include <asm/uaccess.h> +#include <asm/cacheflush.h> +#include <asm/mach-au1x00/au1000.h> + +#ifdef CONFIG_PM + +#define DEBUG 1 +#ifdef	DEBUG +#define DPRINTK(fmt, args...)	printk(KERN_DEBUG "%s: " fmt, __func__, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +static void au1000_calibrate_delay(void); + +extern unsigned long save_local_and_disable(int controller); +extern void restore_local_and_enable(int controller, unsigned long mask); +extern void local_enable_irq(unsigned int irq_nr); + +static DEFINE_SPINLOCK(pm_lock); + +/* + * We need to save/restore a bunch of core registers that are + * either volatile or reset to some state across a processor sleep. + * If reading a register doesn't provide a proper result for a + * later restore, we have to provide a function for loading that + * register and save a copy. + * + * We only have to save/restore registers that aren't otherwise + * done as part of a driver pm_* function. + */ +static unsigned int	sleep_aux_pll_cntrl; +static unsigned int	sleep_cpu_pll_cntrl; +static unsigned int	sleep_pin_function; +static unsigned int	sleep_uart0_inten; +static unsigned int	sleep_uart0_fifoctl; +static unsigned int	sleep_uart0_linectl; +static unsigned int	sleep_uart0_clkdiv; +static unsigned int	sleep_uart0_enable; +static unsigned int	sleep_usbhost_enable; +static unsigned int	sleep_usbdev_enable; +static unsigned int	sleep_static_memctlr[4][3]; + +/* + * Define this to cause the value you write to /proc/sys/pm/sleep to + * set the TOY timer for the amount of time you want to sleep. + * This is done mainly for testing, but may be useful in other cases. + * The value is number of 32KHz ticks to sleep. + */ +#define SLEEP_TEST_TIMEOUT 1 +#ifdef	SLEEP_TEST_TIMEOUT +static int sleep_ticks; +void wakeup_counter0_set(int ticks); +#endif + +static void save_core_regs(void) +{ +	extern void save_au1xxx_intctl(void); +	extern void pm_eth0_shutdown(void); + +	/* +	 * Do the serial ports.....these really should be a pm_* +	 * registered function by the driver......but of course the +	 * standard serial driver doesn't understand our Au1xxx +	 * unique registers. +	 */ +	sleep_uart0_inten = au_readl(UART0_ADDR + UART_IER); +	sleep_uart0_fifoctl = au_readl(UART0_ADDR + UART_FCR); +	sleep_uart0_linectl = au_readl(UART0_ADDR + UART_LCR); +	sleep_uart0_clkdiv = au_readl(UART0_ADDR + UART_CLK); +	sleep_uart0_enable = au_readl(UART0_ADDR + UART_MOD_CNTRL); + +	/* Shutdown USB host/device. */ +	sleep_usbhost_enable = au_readl(USB_HOST_CONFIG); + +	/* There appears to be some undocumented reset register.... */ +	au_writel(0, 0xb0100004); au_sync(); +	au_writel(0, USB_HOST_CONFIG); au_sync(); + +	sleep_usbdev_enable = au_readl(USBD_ENABLE); +	au_writel(0, USBD_ENABLE); au_sync(); + +	/* Save interrupt controller state. */ +	save_au1xxx_intctl(); + +	/* Clocks and PLLs. */ +	sleep_aux_pll_cntrl = au_readl(SYS_AUXPLL); + +	/* +	 * We don't really need to do this one, but unless we +	 * write it again it won't have a valid value if we +	 * happen to read it. +	 */ +	sleep_cpu_pll_cntrl = au_readl(SYS_CPUPLL); + +	sleep_pin_function = au_readl(SYS_PINFUNC); + +	/* Save the static memory controller configuration. */ +	sleep_static_memctlr[0][0] = au_readl(MEM_STCFG0); +	sleep_static_memctlr[0][1] = au_readl(MEM_STTIME0); +	sleep_static_memctlr[0][2] = au_readl(MEM_STADDR0); +	sleep_static_memctlr[1][0] = au_readl(MEM_STCFG1); +	sleep_static_memctlr[1][1] = au_readl(MEM_STTIME1); +	sleep_static_memctlr[1][2] = au_readl(MEM_STADDR1); +	sleep_static_memctlr[2][0] = au_readl(MEM_STCFG2); +	sleep_static_memctlr[2][1] = au_readl(MEM_STTIME2); +	sleep_static_memctlr[2][2] = au_readl(MEM_STADDR2); +	sleep_static_memctlr[3][0] = au_readl(MEM_STCFG3); +	sleep_static_memctlr[3][1] = au_readl(MEM_STTIME3); +	sleep_static_memctlr[3][2] = au_readl(MEM_STADDR3); +} + +static void restore_core_regs(void) +{ +	extern void restore_au1xxx_intctl(void); +	extern void wakeup_counter0_adjust(void); + +	au_writel(sleep_aux_pll_cntrl, SYS_AUXPLL); au_sync(); +	au_writel(sleep_cpu_pll_cntrl, SYS_CPUPLL); au_sync(); +	au_writel(sleep_pin_function, SYS_PINFUNC); au_sync(); + +	/* Restore the static memory controller configuration. */ +	au_writel(sleep_static_memctlr[0][0], MEM_STCFG0); +	au_writel(sleep_static_memctlr[0][1], MEM_STTIME0); +	au_writel(sleep_static_memctlr[0][2], MEM_STADDR0); +	au_writel(sleep_static_memctlr[1][0], MEM_STCFG1); +	au_writel(sleep_static_memctlr[1][1], MEM_STTIME1); +	au_writel(sleep_static_memctlr[1][2], MEM_STADDR1); +	au_writel(sleep_static_memctlr[2][0], MEM_STCFG2); +	au_writel(sleep_static_memctlr[2][1], MEM_STTIME2); +	au_writel(sleep_static_memctlr[2][2], MEM_STADDR2); +	au_writel(sleep_static_memctlr[3][0], MEM_STCFG3); +	au_writel(sleep_static_memctlr[3][1], MEM_STTIME3); +	au_writel(sleep_static_memctlr[3][2], MEM_STADDR3); + +	/* +	 * Enable the UART if it was enabled before sleep. +	 * I guess I should define module control bits........ +	 */ +	if (sleep_uart0_enable & 0x02) { +		au_writel(0, UART0_ADDR + UART_MOD_CNTRL); au_sync(); +		au_writel(1, UART0_ADDR + UART_MOD_CNTRL); au_sync(); +		au_writel(3, UART0_ADDR + UART_MOD_CNTRL); au_sync(); +		au_writel(sleep_uart0_inten, UART0_ADDR + UART_IER); au_sync(); +		au_writel(sleep_uart0_fifoctl, UART0_ADDR + UART_FCR); au_sync(); +		au_writel(sleep_uart0_linectl, UART0_ADDR + UART_LCR); au_sync(); +		au_writel(sleep_uart0_clkdiv, UART0_ADDR + UART_CLK); au_sync(); +	} + +	restore_au1xxx_intctl(); +	wakeup_counter0_adjust(); +} + +unsigned long suspend_mode; + +void wakeup_from_suspend(void) +{ +	suspend_mode = 0; +} + +int au_sleep(void) +{ +	unsigned long wakeup, flags; +	extern void save_and_sleep(void); + +	spin_lock_irqsave(&pm_lock, flags); + +	save_core_regs(); + +	flush_cache_all(); + +	/** +	 ** The code below is all system dependent and we should probably +	 ** have a function call out of here to set this up.  You need +	 ** to configure the GPIO or timer interrupts that will bring +	 ** you out of sleep. +	 ** For testing, the TOY counter wakeup is useful. +	 **/ +#if 0 +	au_writel(au_readl(SYS_PINSTATERD) & ~(1 << 11), SYS_PINSTATERD); + +	/* GPIO 6 can cause a wake up event */ +	wakeup = au_readl(SYS_WAKEMSK); +	wakeup &= ~(1 << 8);	/* turn off match20 wakeup */ +	wakeup |= 1 << 6;	/* turn on  GPIO  6 wakeup */ +#else +	/* For testing, allow match20 to wake us up. */ +#ifdef SLEEP_TEST_TIMEOUT +	wakeup_counter0_set(sleep_ticks); +#endif +	wakeup = 1 << 8;	/* turn on match20 wakeup   */ +	wakeup = 0; +#endif +	au_writel(1, SYS_WAKESRC);	/* clear cause */ +	au_sync(); +	au_writel(wakeup, SYS_WAKEMSK); +	au_sync(); + +	save_and_sleep(); + +	/* +	 * After a wakeup, the cpu vectors back to 0x1fc00000, so +	 * it's up to the boot code to get us back here. +	 */ +	restore_core_regs(); +	spin_unlock_irqrestore(&pm_lock, flags); +	return 0; +} + +static int pm_do_sleep(ctl_table *ctl, int write, struct file *file, +		       void __user *buffer, size_t *len, loff_t *ppos) +{ +#ifdef SLEEP_TEST_TIMEOUT +#define TMPBUFLEN2 16 +	char buf[TMPBUFLEN2], *p; +#endif + +	if (!write) +		*len = 0; +	else { +#ifdef SLEEP_TEST_TIMEOUT +		if (*len > TMPBUFLEN2 - 1) +			return -EFAULT; +		if (copy_from_user(buf, buffer, *len)) +			return -EFAULT; +		buf[*len] = 0; +		p = buf; +		sleep_ticks = simple_strtoul(p, &p, 0); +#endif + +		au_sleep(); +	} +	return 0; +} + +static int pm_do_freq(ctl_table *ctl, int write, struct file *file, +		      void __user *buffer, size_t *len, loff_t *ppos) +{ +	int retval = 0, i; +	unsigned long val, pll; +#define TMPBUFLEN 64 +#define MAX_CPU_FREQ 396 +	char buf[TMPBUFLEN], *p; +	unsigned long flags, intc0_mask, intc1_mask; +	unsigned long old_baud_base, old_cpu_freq, old_clk, old_refresh; +	unsigned long new_baud_base, new_cpu_freq, new_clk, new_refresh; +	unsigned long baud_rate; + +	spin_lock_irqsave(&pm_lock, flags); +	if (!write) +		*len = 0; +	else { +		/* Parse the new frequency */ +		if (*len > TMPBUFLEN - 1) { +			spin_unlock_irqrestore(&pm_lock, flags); +			return -EFAULT; +		} +		if (copy_from_user(buf, buffer, *len)) { +			spin_unlock_irqrestore(&pm_lock, flags); +			return -EFAULT; +		} +		buf[*len] = 0; +		p = buf; +		val = simple_strtoul(p, &p, 0); +		if (val > MAX_CPU_FREQ) { +			spin_unlock_irqrestore(&pm_lock, flags); +			return -EFAULT; +		} + +		pll = val / 12; +		if ((pll > 33) || (pll < 7)) {	/* 396 MHz max, 84 MHz min */ +			/* Revisit this for higher speed CPUs */ +			spin_unlock_irqrestore(&pm_lock, flags); +			return -EFAULT; +		} + +		old_baud_base = get_au1x00_uart_baud_base(); +		old_cpu_freq = get_au1x00_speed(); + +		new_cpu_freq = pll * 12 * 1000000; +	        new_baud_base = (new_cpu_freq / (2 * ((int)(au_readl(SYS_POWERCTRL) +							    & 0x03) + 2) * 16)); +		set_au1x00_speed(new_cpu_freq); +		set_au1x00_uart_baud_base(new_baud_base); + +		old_refresh = au_readl(MEM_SDREFCFG) & 0x1ffffff; +		new_refresh = ((old_refresh * new_cpu_freq) / old_cpu_freq) | +			      (au_readl(MEM_SDREFCFG) & ~0x1ffffff); + +		au_writel(pll, SYS_CPUPLL); +		au_sync_delay(1); +		au_writel(new_refresh, MEM_SDREFCFG); +		au_sync_delay(1); + +		for (i = 0; i < 4; i++) +			if (au_readl(UART_BASE + UART_MOD_CNTRL + +				     i * 0x00100000) == 3) { +				old_clk = au_readl(UART_BASE + UART_CLK + +						   i * 0x00100000); +				baud_rate = old_baud_base / old_clk; +				/* +				 * We won't get an exact baud rate and the error +				 * could be significant enough that our new +				 * calculation will result in a clock that will +				 * give us a baud rate that's too far off from +				 * what we really want. +				 */ +				if (baud_rate > 100000) +					baud_rate = 115200; +				else if (baud_rate > 50000) +					baud_rate = 57600; +				else if (baud_rate > 30000) +					baud_rate = 38400; +				else if (baud_rate > 17000) +					baud_rate = 19200; +				else +					baud_rate = 9600; +				new_clk = new_baud_base / baud_rate; +				au_writel(new_clk, UART_BASE + UART_CLK + +					  i * 0x00100000); +				au_sync_delay(10); +			} +	} + +	/* +	 * We don't want _any_ interrupts other than match20. Otherwise our +	 * au1000_calibrate_delay() calculation will be off, potentially a lot. +	 */ +	intc0_mask = save_local_and_disable(0); +	intc1_mask = save_local_and_disable(1); +	local_enable_irq(AU1000_TOY_MATCH2_INT); +	spin_unlock_irqrestore(&pm_lock, flags); +	au1000_calibrate_delay(); +	restore_local_and_enable(0, intc0_mask); +	restore_local_and_enable(1, intc1_mask); + +	return retval; +} + + +static struct ctl_table pm_table[] = { +	{ +		.ctl_name	= CTL_UNNUMBERED, +		.procname	= "sleep", +		.data		= NULL, +		.maxlen		= 0, +		.mode		= 0600, +		.proc_handler	= &pm_do_sleep +	}, +	{ +		.ctl_name	= CTL_UNNUMBERED, +		.procname	= "freq", +		.data		= NULL, +		.maxlen		= 0, +		.mode		= 0600, +		.proc_handler	= &pm_do_freq +	}, +	{} +}; + +static struct ctl_table pm_dir_table[] = { +	{ +		.ctl_name	= CTL_UNNUMBERED, +		.procname	= "pm", +		.mode		= 0555, +		.child		= pm_table +	}, +	{} +}; + +/* + * Initialize power interface + */ +static int __init pm_init(void) +{ +	register_sysctl_table(pm_dir_table); +	return 0; +} + +__initcall(pm_init); + +/* + * This is right out of init/main.c + */ + +/* + * This is the number of bits of precision for the loops_per_jiffy. + * Each bit takes on average 1.5/HZ seconds.  This (like the original) + * is a little better than 1%. + */ +#define LPS_PREC 8 + +static void au1000_calibrate_delay(void) +{ +	unsigned long ticks, loopbit; +	int lps_precision = LPS_PREC; + +	loops_per_jiffy = 1 << 12; + +	while (loops_per_jiffy <<= 1) { +		/* Wait for "start of" clock tick */ +		ticks = jiffies; +		while (ticks == jiffies) +			/* nothing */ ; +		/* Go ... */ +		ticks = jiffies; +		__delay(loops_per_jiffy); +		ticks = jiffies - ticks; +		if (ticks) +			break; +	} + +	/* +	 * Do a binary approximation to get loops_per_jiffy set to be equal +	 * one clock (up to lps_precision bits) +	 */ +	loops_per_jiffy >>= 1; +	loopbit = loops_per_jiffy; +	while (lps_precision-- && (loopbit >>= 1)) { +		loops_per_jiffy |= loopbit; +		ticks = jiffies; +		while (ticks == jiffies); +		ticks = jiffies; +		__delay(loops_per_jiffy); +		if (jiffies != ticks)	/* longer than 1 tick */ +			loops_per_jiffy &= ~loopbit; +	} +} +#endif	/* CONFIG_PM */  | 
