[PATCH] power-management elements for 603e/fsl (version 2)
Guennadi Liakhovetski
g.liakhovetski at gmx.de
Sun May 20 05:22:10 EST 2007
Hi all
Below is the second version of the standby patch for ppc linkstation
systems, which also introduces suspend / resume methods for Freescale
CPUs. It is now based on a recent (post .22-rc1) powerpc.git snapshot, and
it depends on the "don't link timer.o for powerpc systems using generic
rtc" patch (sorry, didn't cc maintainers on that one)
http://ozlabs.org/pipermail/linuxppc-dev/2007-May/036319.html. Also
addressed comments from Johannes Berg. Please, consider for inclusion.
Paul, sorry, I called it standby, if you don't mind... Somehow I don't
quite feel like calling this STR.
Thanks
Guennadi
---
Guennadi Liakhovetski
linkstation: implement standby for linkstation ppc.
We put the PCI bus and the core to SLEEP and wait for a button to be
pressed for wake up. Requires
http://ozlabs.org/pipermail/linuxppc-dev/2007-May/036319.html as we use
generic rtc and its suspend/resume code. Note: PM is not enabled by
default in linkstation_defconfig.
Signed-off-by: G. Liakhovetski <g.liakhovetski at gmx.de>
diff --git a/arch/powerpc/platforms/embedded6xx/linkstation.c b/arch/powerpc/platforms/embedded6xx/linkstation.c
index b412f00..a37ae97 100644
--- a/arch/powerpc/platforms/embedded6xx/linkstation.c
+++ b/arch/powerpc/platforms/embedded6xx/linkstation.c
@@ -11,15 +11,15 @@
*/
#include <linux/kernel.h>
-#include <linux/pci.h>
#include <linux/initrd.h>
#include <linux/mtd/physmap.h>
+#include <linux/serial_reg.h>
+#include <sysdev/fsl_soc.h>
#include <asm/time.h>
#include <asm/prom.h>
#include <asm/mpic.h>
#include <asm/mpc10x.h>
-#include <asm/pci-bridge.h>
static struct mtd_partition linkstation_physmap_partitions[] = {
{
@@ -134,6 +134,7 @@ static void __init linkstation_init_IRQ(void)
extern void avr_uart_configure(void);
extern void avr_uart_send(const char);
+extern int avr_uart_ier_swap(const char, char *);
static void linkstation_restart(char *cmd)
{
@@ -197,3 +198,73 @@ define_machine(linkstation){
.halt = linkstation_halt,
.calibrate_decr = generic_calibrate_decr,
};
+
+#ifdef CONFIG_PM
+
+static int ls_pm_valid(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int ls_pm_enter(suspend_state_t state)
+{
+ char ier;
+ int ret = 0;
+ u64 tb;
+
+ /* Stop preemption */
+ preempt_disable();
+
+ if ((ret = fsl_suspend(state)) < 0) {
+ preempt_enable();
+ return ret;
+ }
+
+ local_irq_disable();
+
+ avr_uart_configure();
+ ret = avr_uart_ier_swap(UART_IER_RDI | UART_IER_RLSI/* | UART_IER_THRI*/, &ier);
+ if (ret < 0)
+ goto fail;
+
+ /* Get timebase */
+ tb = get_tb();
+
+ /* go zzzzz... (re-enabling interrupts) */
+ fsl_low_sleep();
+
+ local_irq_disable();
+
+ set_tb(tb >> 32, tb & 0xfffffffful);
+
+ (void)avr_uart_ier_swap(ier, NULL);
+fail:
+
+ /* Re-enable local CPU interrupts */
+ local_irq_enable();
+
+ preempt_enable();
+
+ fsl_resume(state);
+
+ return ret;
+}
+
+static struct pm_ops ls_pm_ops = {
+ .valid = ls_pm_valid,
+ .enter = ls_pm_enter,
+};
+
+static int __init ls_pm_init(void)
+{
+ pm_set_ops(&ls_pm_ops);
+ return 0;
+}
+
+device_initcall(ls_pm_init);
+#endif
diff --git a/arch/powerpc/platforms/embedded6xx/ls_uart.c b/arch/powerpc/platforms/embedded6xx/ls_uart.c
index d0bee9f..b1086e9 100644
--- a/arch/powerpc/platforms/embedded6xx/ls_uart.c
+++ b/arch/powerpc/platforms/embedded6xx/ls_uart.c
@@ -1,3 +1,14 @@
+/*
+ * AVR power-management chip interface for the Buffalo Linkstation /
+ * Kurobox Platform.
+ *
+ * Author: 2006 (c) G. Liakhovetski
+ * g.liakhovetski at gmx.de
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of
+ * any kind, whether express or implied.
+ */
#include <linux/workqueue.h>
#include <linux/string.h>
#include <linux/delay.h>
@@ -11,6 +22,7 @@
static void __iomem *avr_addr;
static unsigned long avr_clock;
+static unsigned int avr_virq;
static struct work_struct wd_work;
@@ -42,6 +54,37 @@ static void wd_stop(struct work_struct *unused)
printk("\n");
}
+static irqreturn_t avr_handler(int irq, void *id)
+{
+ (void) in_8(avr_addr + UART_LSR);
+ (void) in_8(avr_addr + UART_RX);
+ (void) in_8(avr_addr + UART_IIR);
+ (void) in_8(avr_addr + UART_MSR);
+
+ return IRQ_HANDLED;
+}
+
+int avr_uart_ier_swap(const char new, char *old)
+{
+ int ret = 0;
+
+ if (!avr_addr || !avr_clock || avr_virq == NO_IRQ)
+ return -EIO;
+
+ if (old)
+ ret = request_irq(avr_virq, avr_handler, 0, "avr_wakeup", NULL);
+ else
+ free_irq(avr_virq, NULL);
+
+ if (ret >= 0) {
+ if (old)
+ *old = in_8(avr_addr + UART_IER);
+ out_8(avr_addr + UART_IER, new);
+ }
+
+ return ret;
+}
+
#define AVR_QUOT(clock) ((clock) + 8 * 9600) / (16 * 9600)
void avr_uart_configure(void)
@@ -104,7 +147,7 @@ static int __init ls_uarts_init(void)
{
struct device_node *avr;
phys_addr_t phys_addr;
- int len;
+ int len, irq;
avr = of_find_node_by_path("/soc10x/serial at 80004500");
if (!avr)
@@ -112,10 +155,15 @@ static int __init ls_uarts_init(void)
avr_clock = *(u32*)of_get_property(avr, "clock-frequency", &len);
phys_addr = ((u32*)of_get_property(avr, "reg", &len))[0];
+ irq = ((u32*)get_property(avr, "interrupts", &len))[0];
if (!avr_clock || !phys_addr)
return -EINVAL;
+ avr_virq = irq_find_mapping(NULL, irq);
+ if (avr_virq == NO_IRQ)
+ return -EIO;
+
avr_addr = ioremap(phys_addr, 32);
if (!avr_addr)
return -EFAULT;
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index c3ce0bd..745c6f6 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -13,6 +13,9 @@ obj-$(CONFIG_PPC_PMI) += pmi.o
obj-$(CONFIG_U3_DART) += dart_iommu.o
obj-$(CONFIG_MMIO_NVRAM) += mmio_nvram.o
obj-$(CONFIG_FSL_SOC) += fsl_soc.o
+ifeq ($(CONFIG_PM),y)
+obj-$(CONFIG_FSL_SOC) += fsl_pm.o
+endif
obj-$(CONFIG_FSL_PCIE) += fsl_pcie.o
obj-$(CONFIG_TSI108_BRIDGE) += tsi108_pci.o tsi108_dev.o
obj-$(CONFIG_QUICC_ENGINE) += qe_lib/
diff --git a/arch/powerpc/sysdev/fsl_soc.c b/arch/powerpc/sysdev/fsl_soc.c
index cad1757..9588b60 100644
--- a/arch/powerpc/sysdev/fsl_soc.c
+++ b/arch/powerpc/sysdev/fsl_soc.c
@@ -1103,3 +1103,65 @@ err:
arch_initcall(cpm_smc_uart_of_init);
#endif /* CONFIG_8xx */
+
+#ifdef CONFIG_PM
+#include <linux/pci.h>
+#include <asm/pci-bridge.h>
+
+#define MPC10X_LP_REF_EN (1<<12)
+#define MPC10X_PM (1<<7)
+#define MPC10X_DOZE (1<<5)
+#define MPC10X_NAP (1<<4)
+#define MPC10X_SLEEP (1<<3)
+
+int fsl_suspend(suspend_state_t state)
+{
+ struct pci_dev *bridge;
+ unsigned long flags;
+ u16 pmcr1;
+
+ bridge = pci_find_slot(0, 0);
+ if (!bridge)
+ return -ENODEV;
+
+ pci_read_config_word(bridge, 0x70, &pmcr1);
+ local_irq_save(flags);
+ /* Apparently, MacOS uses NAP mode for Grackle ??? */
+ pmcr1 &= ~(MPC10X_DOZE | MPC10X_NAP);
+ pmcr1 |= MPC10X_PM | MPC10X_SLEEP | MPC10X_LP_REF_EN;
+ pci_write_config_word(bridge, 0x70, pmcr1);
+ local_irq_restore(flags);
+
+ /* Make sure the decrementer won't interrupt us */
+ asm volatile("mtdec %0" : : "r" (0x7fffffff));
+ /* Make sure any pending DEC interrupt occurring while we did
+ * the above didn't re-enable the DEC */
+ mb();
+ asm volatile("mtdec %0" : : "r" (0x7fffffff)); /* 8 seconds */
+
+ return 0;
+}
+
+int fsl_resume(suspend_state_t state)
+{
+ struct pci_dev *bridge;
+ unsigned long flags;
+ u16 pmcr1;
+
+ bridge = pci_find_slot(0, 0);
+ if (!bridge)
+ return -ENODEV;
+
+ local_irq_save(flags);
+ /* We're awake again, stop grackle PM */
+ pci_read_config_word(bridge, 0x70, &pmcr1);
+ pmcr1 &= ~(MPC10X_PM | MPC10X_DOZE | MPC10X_SLEEP | MPC10X_NAP | MPC10X_LP_REF_EN);
+ pci_write_config_word(bridge, 0x70, pmcr1);
+ local_irq_restore(flags);
+
+ /* Restart jiffies & scheduling */
+ wakeup_decrementer();
+
+ return 0;
+}
+#endif
diff --git a/arch/powerpc/sysdev/fsl_soc.h b/arch/powerpc/sysdev/fsl_soc.h
index 04e145b..9853913 100644
--- a/arch/powerpc/sysdev/fsl_soc.h
+++ b/arch/powerpc/sysdev/fsl_soc.h
@@ -8,5 +8,12 @@ extern phys_addr_t get_immrbase(void);
extern u32 get_brgfreq(void);
extern u32 get_baudrate(void);
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+extern int fsl_suspend(suspend_state_t state);
+extern int fsl_resume(suspend_state_t state);
+extern void fsl_low_sleep(void);
+#endif
+
#endif
#endif
diff -u /dev/null b/arch/powerpc/sysdev/fsl_pm.S
--- /dev/null 2005-08-21 16:20:22.000000000 +0200
+++ b/arch/powerpc/sysdev/fsl_pm.S 2007-05-18 19:34:22.000000000 +0200
@@ -0,0 +1,19 @@
+#include <asm/reg.h>
+#include <asm/ppc_asm.h>
+
+_GLOBAL(fsl_low_sleep)
+ isync /* Set the HID0 and MSR for sleep. */
+ mfspr r3,SPRN_HID0
+ rlwinm r3,r3,0,10,7 /* clear doze, nap */
+ oris r3,r3,HID0_SLEEP at h /* r3 |= HID0_SLEEP & (0xffff << 16) */
+ sync
+ isync
+ mtspr SPRN_HID0,r3
+ sync
+ mfmsr r3
+ ori r3,r3,MSR_EE /* Enable interrupts to wake us up */
+ oris r3,r3,MSR_POW at h
+ sync
+ mtmsr r3
+ isync
+ blr
More information about the Linuxppc-dev
mailing list