Hello, here a patch to support new sysfs interface for Au1xxx's power management. Now we can put the system into sleeping mode by using: hostname:~# echo mem > /sys/power/state The patch keeps also the file "/proc/sys/pm/freq" from the old interface. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@xxxxxxxxxxxx Linux Device Driver giometti@xxxxxxxxx Embedded Systems giometti@xxxxxxxx UNIX programming phone: +39 349 2432127
--- /home/develop/embedded/mipsel/linux/linux-mips.git/arch/mips/au1000/common/power.c 2006-03-31 16:57:26.000000000 +0200 +++ arch/mips/au1000/common/power.c 2006-04-06 00:08:59.000000000 +0200 @@ -1,6 +1,11 @@ /* * BRIEF MODULE DESCRIPTION - * Au1000 Power Management routines. + * Au1xxx Power Management routines (sysfs and procfs interfaces). + * + * Copyright 2006 Rodolfo Giometti <giometti@xxxxxxxx> + * + * Based on some previous version's functions, so I report below the previous + * copyright message: * * Copyright 2001 MontaVista Software Inc. * Author: MontaVista Software, Inc. @@ -32,7 +37,6 @@ #include <linux/config.h> #include <linux/init.h> #include <linux/pm.h> -#include <linux/pm_legacy.h> #include <linux/slab.h> #include <linux/sysctl.h> #include <linux/jiffies.h> @@ -43,18 +47,17 @@ #include <asm/system.h> #include <asm/cacheflush.h> #include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/power.h> -#ifdef CONFIG_PM +static unsigned long cpu_freq; -#define DEBUG 1 +//#define DEBUG 1 #ifdef DEBUG # define DPRINTK(fmt, args...) printk("%s: " fmt, __FUNCTION__ , ## args) #else # define DPRINTK(fmt, args...) #endif -static void au1000_calibrate_delay(void); - extern void set_au1x00_speed(unsigned int new_freq); extern unsigned int get_au1x00_speed(void); extern unsigned long get_au1x00_uart_baud_base(void); @@ -64,9 +67,9 @@ extern void local_enable_irq(unsigned int irq_nr); /* Quick acpi hack. This will have to change! */ -#define CTL_ACPI 9999 -#define ACPI_S1_SLP_TYP 19 -#define ACPI_SLEEP 21 +#define CTL_ACPI 9999 +#define ACPI_S1_SLP_TYP 19 +#define ACPI_SLEEP 21 static DEFINE_SPINLOCK(pm_lock); @@ -83,6 +86,7 @@ static uint sleep_aux_pll_cntrl; static uint sleep_cpu_pll_cntrl; static uint sleep_pin_function; +static uint sleep_gpio2_enable; static uint sleep_uart0_inten; static uint sleep_uart0_fifoctl; static uint sleep_uart0_linectl; @@ -92,16 +96,45 @@ static uint sleep_usbdev_enable; static uint 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 +/* 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 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; + } +} static void save_core_regs(void) @@ -147,6 +180,7 @@ sleep_cpu_pll_cntrl = au_readl(SYS_CPUPLL); sleep_pin_function = au_readl(SYS_PINFUNC); + sleep_gpio2_enable = au_readl(GPIO2_ENABLE); /* Save the static memory controller configuration. */ @@ -173,6 +207,7 @@ 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(); + au_writel(sleep_gpio2_enable, GPIO2_ENABLE); au_sync(); /* Restore the static memory controller configuration. */ @@ -206,47 +241,48 @@ wakeup_counter0_adjust(); } -unsigned long suspend_mode; - -void wakeup_from_suspend(void) -{ - suspend_mode = 0; -} +void wakeup_counter0_set(int ticks); +int (*board_before_sleep)(void); +void (*board_after_sleep)(int reason); -int au_sleep(void) +int au_sleep(int reason, int force) { + int ticks = 0; unsigned long wakeup, flags; - extern void save_and_sleep(void); + int board_reason = 0; - spin_lock_irqsave(&pm_lock,flags); + /* We need to configure the GPIO or timer interrupts that will bring + * us out of sleep, so we use the specific board wake up source + * (if present) */ + if (board_before_sleep) + board_reason = board_before_sleep(); + if (!force) + reason = board_reason; + + 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 + if (reason == 0) /* machine_halt() */ + wakeup = 0; + else if (reason < 0) { /* TOY wake up */ + ticks = -reason*HZ; + wakeup_counter0_set(ticks); + wakeup = 1<<8; /* turn on match20 as wake up */ + } + else { /* GPIO[0-7] wake up */ + /* FIXME: this setting for GPIO[6] should be into specific + * boards setup... */ + /* We force pin GPIO[6]/SMROMCKE as gpio6 */ + au_writel(au_readl(SYS_PINSTATERD)&~(1<<11), SYS_PINSTATERD); + + wakeup = reason&0x0ff; /* turn on selected gpio as wake up */ + } + printk(KERN_DEBUG "%s - reason %x force %x wakeup %lx ticks %x\n", + __FUNCTION__, reason, force, wakeup, ticks); + au_writel(1, SYS_WAKESRC); /* clear cause */ au_sync(); au_writel(wakeup, SYS_WAKEMSK); @@ -254,102 +290,52 @@ save_and_sleep(); - /* after a wakeup, the cpu vectors back to 0x1fc00000 so + /* after a wake up, 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) -{ - int retval = 0; -#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 - retval = pm_send_all(PM_SUSPEND, (void *) 2); - - if (retval) - return retval; - - au_sleep(); - retval = pm_send_all(PM_RESUME, (void *) 0); - } - return retval; -} -static int pm_do_suspend(ctl_table * ctl, int write, struct file *file, - void __user *buffer, size_t * len, loff_t *ppos) -{ - int retval = 0; + spin_unlock_irqrestore(&pm_lock, flags); - if (!write) { - *len = 0; - } else { - retval = pm_send_all(PM_SUSPEND, (void *) 2); - if (retval) - return retval; - suspend_mode = 1; + /* Get wake up source */ + reason = au_readl(SYS_WAKESRC)>>18; + if (reason&(1<<8)) /* Wake up thanks to TOY */ + reason = -ticks*HZ; + + /* Call specific board routine */ + if (board_after_sleep) + board_after_sleep(reason); - retval = pm_send_all(PM_RESUME, (void *) 0); - } - return retval; + return 0; } - -static int pm_do_freq(ctl_table * ctl, int write, struct file *file, +static int au1xxx_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 *valp = (unsigned long *) ctl->data; + unsigned long pll; unsigned long flags, intc0_mask, intc1_mask; unsigned long old_baud_base, old_cpu_freq, baud_rate, old_clk, old_refresh; unsigned long new_baud_base, new_cpu_freq, new_clk, new_refresh; spin_lock_irqsave(&pm_lock, flags); - if (!write) { + + 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) { + + retval = proc_dointvec(ctl, write, file, buffer, len, ppos); + +#define MAX_CPU_FREQ 396 + if (write) { + /* Check the new frequency */ + if (*valp > MAX_CPU_FREQ) { spin_unlock_irqrestore(&pm_lock, flags); return -EFAULT; } - pll = val / 12; + pll = *valp / 12; if ((pll > 33) || (pll < 7)) { /* 396 MHz max, 84 MHz min */ /* revisit this for higher speed cpus */ spin_unlock_irqrestore(&pm_lock, flags); @@ -424,72 +410,66 @@ return retval; } +/* + * Called after processes are frozen, but before we shut down devices. + */ +int au1xxx_pm_prepare(suspend_state_t state) +{ + DPRINTK("state = %d\n", state); + return 0; +} -static struct ctl_table pm_table[] = { - {ACPI_S1_SLP_TYP, "suspend", NULL, 0, 0600, NULL, &pm_do_suspend}, - {ACPI_SLEEP, "sleep", NULL, 0, 0600, NULL, &pm_do_sleep}, - {CTL_ACPI, "freq", NULL, 0, 0600, NULL, &pm_do_freq}, - {0} -}; - -static struct ctl_table pm_dir_table[] = { - {CTL_ACPI, "pm", NULL, 0, 0555, pm_table}, - {0} -}; +/* + * Called after down devices. + */ +int au1xxx_pm_enter(suspend_state_t state) +{ + DPRINTK("state = %d\n", state); + return au_sleep(1<<6 /* GPIO 6 */, 1); + /* return au_sleep(-3, 1); */ /* wake up after 3 seconds */ +} /* - * Initialize power interface + * Called after devices are re-setup, but before processes are thawed. */ -static int __init pm_init(void) +int au1xxx_pm_finish(suspend_state_t state) { - register_sysctl_table(pm_dir_table, 1); + DPRINTK("state = %d\n", state); return 0; } -__initcall(pm_init); - +/* + * Set to PM_DISK_FIRMWARE so we can quickly veto suspend-to-disk. + */ +static struct pm_ops au1xxx_pm_ops = { + .pm_disk_mode = PM_DISK_FIRMWARE, + .prepare = au1xxx_pm_prepare, + .enter = au1xxx_pm_enter, + .finish = au1xxx_pm_finish, +}; /* - * This is right out of init/main.c + * Set up the old "/proc/sys/pm" interface. */ +static struct ctl_table au1xxx_pm_table[] = { + { CTL_ACPI, "freq", &cpu_freq, sizeof(int), 0600, NULL, &au1xxx_pm_do_freq }, + { 0 }, +}; -/* 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 struct ctl_table pm_dir_table[] = { + { CTL_ACPI, "pm", NULL, 0, 0555, au1xxx_pm_table }, + { 0 }, +}; -static void au1000_calibrate_delay(void) +static int __init au1xxx_pm_init(void) { - unsigned long ticks, loopbit; - int lps_precision = LPS_PREC; - - loops_per_jiffy = (1 << 12); + /* The new interface */ + pm_set_ops(&au1xxx_pm_ops); - 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; - } + /* The old interface */ + register_sysctl_table(pm_dir_table, 1); -/* Do a binary approximation to get loops_per_jiffy set to 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; - } + return 0; } -#endif /* CONFIG_PM */ + +device_initcall(au1xxx_pm_init);