[PATCH 4/5] mpc52xx suspend: deep-sleep
Domen Puncer
domen.puncer at telargo.com
Wed Apr 4 17:37:55 EST 2007
Hi!
How about something like the following.
Changes:
- lots of code moved from asm to C
- add compatible "mpc5200" to lite5200x soc (already is this
way on efika). And change Efika's soc device_type to "soc".
- add wakeup supported with RTC (1 to 24*60-1 minutes)
- each board now configures it's wakeup mode and possibly
board suspend and resume functions it needs to call (USB on lite)
This code survived > 70k suspend/resume cycles :-)
Comments?
Implement deep-sleep on MPC52xx.
SDRAM is put into self-refresh with help of SRAM code
(alternatives would be code in FLASH, I-cache).
Interrupt code must also not be in SDRAM, so put it
in I-cache.
MPC52xx core is static, so contents will remain intact even
with clocks turned off.
Signed-off-by: Domen Puncer <domen.puncer at telargo.com>
---
arch/powerpc/boot/dts/lite5200.dts | 1
arch/powerpc/boot/dts/lite5200b.dts | 1
arch/powerpc/kernel/prom_init.c | 2
arch/powerpc/platforms/52xx/Makefile | 2
arch/powerpc/platforms/52xx/efika.c | 8
arch/powerpc/platforms/52xx/lite5200.c | 28 +++
arch/powerpc/platforms/52xx/mpc52xx_pm.c | 230 ++++++++++++++++++++++++++++
arch/powerpc/platforms/52xx/mpc52xx_sleep.S | 161 +++++++++++++++++++
include/asm-powerpc/mpc52xx.h | 63 +++++++
9 files changed, 495 insertions(+), 1 deletion(-)
Index: grant.git/arch/powerpc/platforms/52xx/Makefile
===================================================================
--- grant.git.orig/arch/powerpc/platforms/52xx/Makefile
+++ grant.git/arch/powerpc/platforms/52xx/Makefile
@@ -10,3 +10,5 @@ endif
obj-$(CONFIG_PPC_EFIKA) += efika.o
obj-$(CONFIG_PPC_LITE5200) += lite5200.o
+
+obj-$(CONFIG_PM) += mpc52xx_sleep.o mpc52xx_pm.o
Index: grant.git/arch/powerpc/platforms/52xx/mpc52xx_pm.c
===================================================================
--- /dev/null
+++ grant.git/arch/powerpc/platforms/52xx/mpc52xx_pm.c
@@ -0,0 +1,230 @@
+#include <linux/init.h>
+#include <linux/pm.h>
+#include <linux/io.h>
+#include <asm/time.h>
+#include <asm/cacheflush.h>
+#include <asm/mpc52xx.h>
+#include "bestcomm.h"
+
+#undef DEBUG /* define for 1s wakeups */
+extern void mpc52xx_deep_sleep(void *sram, void *, struct mpc52xx_cdm *, struct mpc52xx_intr *);
+
+static void __iomem *mbar;
+static void __iomem *sdram;
+static struct mpc52xx_cdm __iomem *cdm;
+static struct mpc52xx_intr __iomem *intr;
+static struct mpc52xx_rtc __iomem *rtc;
+static struct mpc52xx_gpio_wkup __iomem *gpiow;
+
+struct mpc52xx_wakeup mpc52xx_wakeup;
+
+static int mpc52xx_pm_valid(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int mpc52xx_set_wakeup_gpio(u8 pin, u8 level)
+{
+ u16 tmp;
+
+ /* enable gpio */
+ out_8(&gpiow->wkup_gpioe, in_8(&gpiow->wkup_gpioe) | (1 << pin));
+ /* set as input */
+ out_8(&gpiow->wkup_ddr, in_8(&gpiow->wkup_ddr) & ~(1 << pin));
+ /* enable deep sleep interrupt */
+ out_8(&gpiow->wkup_inten, in_8(&gpiow->wkup_inten) | (1 << pin));
+ /* low/high level creates wakeup interrupt */
+ tmp = in_be16(&gpiow->wkup_itype);
+ tmp &= ~(0x3 << (pin * 2));
+ tmp |= (!level + 1) << (pin * 2);
+ out_be16(&gpiow->wkup_itype, tmp);
+ /* master enable */
+ out_8(&gpiow->wkup_maste, 1);
+
+ /* enable wakeup gpio interrupt in PIC */
+ out_be32(&intr->main_mask, in_be32(&intr->main_mask) & ~(1 << 8));
+
+ return 0;
+}
+
+static int mpc52xx_set_wakeup_rtc(int delay)
+{
+#ifndef DEBUG
+ u8 hour;
+ u8 minute;
+
+ if (delay < 0)
+ return -EINVAL;
+ else if (delay == 0)
+ return 0;
+
+ hour = in_8(&rtc->hour);
+ minute = in_8(&rtc->minute);
+
+ hour += delay / 60;
+ hour %= 24;
+ minute += delay % 60 + 1;
+ if (minute >= 60) {
+ minute -= 60;
+ hour++;
+ }
+
+ out_8(&rtc->alm_hour_set, hour);
+ out_8(&rtc->alm_min_set, minute);
+ out_8(&rtc->alm_enable, 1);
+#else
+ u8 tmp = in_8(&rtc->int_enable);
+ out_8(&rtc->int_enable, (tmp & ~0x8) | 0x1); /* every second */
+#endif
+
+ return 0;
+}
+
+int mpc52xx_pm_prepare(suspend_state_t state)
+{
+ if (state != PM_SUSPEND_STANDBY)
+ return -EINVAL;
+
+ /* map the whole register space */
+ mbar = mpc52xx_find_and_map("mpc5200");
+ if (!mbar) {
+ printk(KERN_ERR "%s:%i Error mapping registers\n", __func__, __LINE__);
+ return -ENOSYS;
+ }
+ /* these offsets are from mpc5200 users manual */
+ sdram = mbar + 0x100;
+ cdm = mbar + 0x200;
+ intr = mbar + 0x500;
+ rtc = mbar + 0x800;
+ gpiow = mbar + 0xc00;
+
+
+#ifdef DEBUG
+ mpc52xx_wakeup.mask |= WAKEUP_RTC;
+#endif
+ if (mpc52xx_wakeup.mask == 0) {
+ printk(KERN_ALERT "%s: %i don't know how to wake up the board\n",
+ __func__, __LINE__);
+ goto out_unmap;
+ }
+ if (mpc52xx_wakeup.mask & WAKEUP_GPIO) {
+ if (mpc52xx_set_wakeup_gpio(mpc52xx_wakeup.pin, mpc52xx_wakeup.level))
+ goto out_unmap;
+ }
+ if (mpc52xx_wakeup.mask & WAKEUP_RTC) {
+ if (mpc52xx_set_wakeup_rtc(mpc52xx_wakeup.delay))
+ goto out_unmap;
+ }
+ if (mpc52xx_wakeup.mask & WAKEUP_MSCAN) {
+ /* TODO */
+ }
+
+ /* call board suspend code, if applicable */
+ if (mpc52xx_wakeup.board_suspend_prepare)
+ mpc52xx_wakeup.board_suspend_prepare(mbar);
+
+ return 0;
+
+ out_unmap:
+ iounmap(mbar);
+ return -ENOSYS;
+}
+
+extern void mpc52xx_ds_sram(void);
+extern const long mpc52xx_ds_sram_size;
+extern void mpc52xx_ds_cached(void);
+extern const long mpc52xx_ds_cached_size;
+
+static char saved_sram[0x4000];
+
+int mpc52xx_pm_enter(suspend_state_t state)
+{
+ u32 clk_enables;
+ u32 msr, hid0;
+ void __iomem * irq_0x500 = (void *)CONFIG_KERNEL_START + 0x500;
+ unsigned long irq_0x500_stop = (unsigned long)irq_0x500 + mpc52xx_ds_cached_size;
+ char saved_0x500[mpc52xx_ds_cached_size];
+
+ /* don't let DEC expire any time soon */
+ mtspr(SPRN_DEC, 0x7fffffff);
+
+ /* save SRAM */
+ memcpy(saved_sram, sdma.sram, sdma.sram_size);
+
+ /* copy low level suspend code to sram */
+ memcpy(sdma.sram, mpc52xx_ds_sram, mpc52xx_ds_sram_size);
+
+ out_8(&cdm->ccs_sleep_enable, 1);
+ out_8(&cdm->osc_sleep_enable, 1);
+ out_8(&cdm->ccs_qreq_test, 1);
+
+ /* disable all but SDRAM and bestcomm (SRAM) clocks */
+ clk_enables = in_be32(&cdm->clk_enables);
+ out_be32(&cdm->clk_enables, clk_enables & 0x00088000);
+
+ /* disable power management */
+ msr = mfmsr();
+ mtmsr(msr & ~MSR_POW);
+
+ /* enable sleep mode, disable others */
+ hid0 = mfspr(SPRN_HID0);
+ mtspr(SPRN_HID0, (hid0 & ~(HID0_DOZE | HID0_NAP | HID0_DPM)) | HID0_SLEEP);
+
+ /* save original, copy our irq handler, flush from dcache and invalidate icache */
+ memcpy(saved_0x500, irq_0x500, mpc52xx_ds_cached_size);
+ memcpy(irq_0x500, mpc52xx_ds_cached, mpc52xx_ds_cached_size);
+ flush_icache_range((unsigned long)irq_0x500, irq_0x500_stop);
+
+ /* call low-level sleep code */
+ mpc52xx_deep_sleep(sdma.sram, sdram, cdm, intr);
+
+ /* restore original irq handler */
+ memcpy(irq_0x500, saved_0x500, mpc52xx_ds_cached_size);
+ flush_icache_range((unsigned long)irq_0x500, irq_0x500_stop);
+
+ /* restore old power mode */
+ mtmsr(msr & ~MSR_POW);
+ mtspr(SPRN_HID0, hid0);
+ mtmsr(msr);
+
+ out_be32(&cdm->clk_enables, clk_enables);
+ out_8(&cdm->ccs_sleep_enable, 0);
+ out_8(&cdm->osc_sleep_enable, 0);
+
+ /* restore SRAM */
+ memcpy(sdma.sram, saved_sram, sdma.sram_size);
+
+ /* restart jiffies */
+ wakeup_decrementer();
+
+ return 0;
+}
+
+static int mpc52xx_pm_finish(suspend_state_t state)
+{
+ /* call board resume code */
+ if (mpc52xx_wakeup.board_resume_finish)
+ mpc52xx_wakeup.board_resume_finish(mbar);
+
+ iounmap(mbar);
+
+ return 0;
+}
+
+static struct pm_ops mpc52xx_pm_ops = {
+ .valid = mpc52xx_pm_valid,
+ .prepare = mpc52xx_pm_prepare,
+ .enter = mpc52xx_pm_enter,
+ .finish = mpc52xx_pm_finish,
+};
+
+int __init mpc52xx_pm_init(void)
+{
+ pm_set_ops(&mpc52xx_pm_ops);
+ return 0;
+}
Index: grant.git/arch/powerpc/platforms/52xx/mpc52xx_sleep.S
===================================================================
--- /dev/null
+++ grant.git/arch/powerpc/platforms/52xx/mpc52xx_sleep.S
@@ -0,0 +1,161 @@
+#include <asm/reg.h>
+#include <asm/ppc_asm.h>
+#include <asm/processor.h>
+
+
+#undef DEBUG /* doesn't halt the cpu. useful for bdi2000 debugging */
+
+/* mpc5200b puts sdram automatically in self-refresh, previous versions don't */
+#define SELF_REFRESH
+
+.text
+ .globl mpc52xx_deep_sleep
+mpc52xx_deep_sleep: /* args r3-r6: SRAM, SDRAM regs, CDM regs, INTR regs */
+
+ /* enable interrupts */
+ mfmsr r7
+ ori r7, r7, 0x8000 /* EE */
+ mtmsr r7
+ sync; isync;
+
+ li r10, 0 /* flag that irq handler sets */
+
+ /* enable tmr7 (or any other) interrupt */
+ lwz r8, 0x14(r6) /* intr->main_mask */
+ ori r8, r8, 0x1
+ xori r8, r8, 0x1
+ stw r8, 0x14(r6)
+ sync
+
+ /* emulate tmr7 interrupt */
+ li r8, 0x1
+ stw r8, 0x40(r6) /* intr->main_emulate */
+ sync
+
+ /* wait for it to happen */
+1:
+ cmpi cr0, r10, 1
+ bne cr0, 1b
+
+ /* lock icache */
+ mfspr r10, SPRN_HID0
+ ori r10, r10, 0x2000
+ sync; isync;
+ mtspr SPRN_HID0, r10
+ sync; isync;
+
+
+ mflr r9 /* save LR */
+
+ /* jump to sram */
+ mtlr r3
+ blrl
+
+ mtlr r9 /* restore LR */
+
+ /* unlock icache */
+ mfspr r10, SPRN_HID0
+ ori r10, r10, 0x2000
+ xori r10, r10, 0x2000
+ sync; isync;
+ mtspr SPRN_HID0, r10
+ sync; isync;
+
+
+ /* return to C code */
+ blr
+
+
+_GLOBAL(mpc52xx_ds_sram)
+mpc52xx_ds_sram:
+#ifdef SELF_REFRESH
+ lwz r8, 0x4(r4) /* sdram->ctrl */
+
+ oris r8, r8, 0x8000 /* mode_en */
+ stw r8, 0x4(r4)
+ sync
+
+ ori r8, r8, 0x0002 /* soft_pre */
+ stw r8, 0x4(r4)
+ sync
+ xori r8, r8, 0x0002
+
+ xoris r8, r8, 0x8000 /* mode_en */
+ stw r8, 0x4(r4)
+ sync
+
+ oris r8, r8, 0x5000
+ xoris r8, r8, 0x4000 /* ref_en !cke */
+ stw r8, 0x4(r4)
+ sync
+
+ /* disable SDRAM clock */
+ lwz r8, 0x14(r5) /* cdm->clkenable */
+ ori r8, r8, 0x0008
+ xori r8, r8, 0x0008
+ stw r8, 0x14(r5)
+ sync
+#endif
+
+#ifndef DEBUG
+ /* put it to sleep */
+ mfmsr r10
+ oris r10, r10, 0x0004 /* POW = 1 */
+ sync; isync;
+ mtmsr r10
+ sync; isync;
+#endif
+
+#ifdef SELF_REFRESH
+ /* enable clock */
+ lwz r8, 0x14(r5)
+ ori r8, r8, 0x0008
+ stw r8, 0x14(r5)
+ sync
+
+ /* get ram out of self-refresh */
+ lwz r8, 0x4(r4)
+ oris r8, r8, 0x5000 /* cke ref_en */
+ stw r8, 0x4(r4)
+ sync
+#endif
+
+ blr
+_GLOBAL(mpc52xx_ds_sram_size)
+mpc52xx_ds_sram_size:
+ .long $-mpc52xx_ds_sram
+
+
+/* ### interrupt handler for wakeup from deep-sleep ### */
+_GLOBAL(mpc52xx_ds_cached)
+mpc52xx_ds_cached:
+ mtspr SPRN_SPRG0, r7
+ mtspr SPRN_SPRG1, r8
+
+ /* disable emulated interrupt */
+ mfspr r7, 311 /* MBAR */
+ addi r7, r7, 0x540 /* intr->main_emul */
+ li r8, 0
+ stw r8, 0(r7)
+ sync
+ dcbf 0, r7
+
+ /* acknowledge wakeup, so CCS releases power pown */
+ mfspr r7, 311 /* MBAR */
+ addi r7, r7, 0x524 /* intr->enc_status */
+ lwz r8, 0(r7)
+ ori r8, r8, 0x0400
+ stw r8, 0(r7)
+ sync
+ dcbf 0, r7
+
+ /* flag - we handled the interrupt */
+ li r10, 1
+
+ mfspr r8, SPRN_SPRG1
+ mfspr r7, SPRN_SPRG0
+
+ rfi
+_GLOBAL(mpc52xx_ds_cached_size)
+mpc52xx_ds_cached_size:
+ .long $-mpc52xx_ds_cached
Index: grant.git/arch/powerpc/platforms/52xx/efika.c
===================================================================
--- grant.git.orig/arch/powerpc/platforms/52xx/efika.c
+++ grant.git/arch/powerpc/platforms/52xx/efika.c
@@ -200,6 +200,14 @@ static void __init efika_setup_arch(void
efika_pcisetup();
+#ifdef CONFIG_PM
+ mpc52xx_wakeup.mask = WAKEUP_GPIO | WAKEUP_RTC;
+ mpc52xx_wakeup.pin = 4; /* GPIO_WKUP_4 (GPIO_PSC6_0 - IRDA_RX) */
+ mpc52xx_wakeup.level = 1; /* wakeup on high level */
+ /* IOW. to wake it up, short pins 1 and 3 on IRDA connector */
+ mpc52xx_pm_init();
+#endif
+
if (ppc_md.progress)
ppc_md.progress("Linux/PPC " UTS_RELEASE " running on Efika ;-)\n", 0x0);
}
Index: grant.git/arch/powerpc/platforms/52xx/lite5200.c
===================================================================
--- grant.git.orig/arch/powerpc/platforms/52xx/lite5200.c
+++ grant.git/arch/powerpc/platforms/52xx/lite5200.c
@@ -85,6 +85,24 @@ error:
iounmap(gpio);
}
+#ifdef CONFIG_PM
+static u32 descr_a;
+static void lite5200_suspend_prepare(void __iomem *mbar)
+{
+ /*
+ * power down usb port
+ * this needs to be called before of-ohci suspend code
+ */
+ descr_a = in_be32(mbar + 0x1048);
+ out_be32(mbar + 0x1048, (descr_a & ~0x200) | 0x100);
+}
+
+static void lite5200_resume_finish(void __iomem *mbar)
+{
+ out_be32(mbar + 0x1048, descr_a);
+}
+#endif
+
static void __init lite5200_setup_arch(void)
{
struct device_node *np;
@@ -107,6 +125,16 @@ static void __init lite5200_setup_arch(v
mpc52xx_setup_cpu(); /* Generic */
lite5200_setup_cpu(); /* Platorm specific */
+#ifdef CONFIG_PM
+ mpc52xx_wakeup.mask = WAKEUP_GPIO | WAKEUP_RTC;
+ mpc52xx_wakeup.pin = 1; /* GPIO_WKUP_1 (GPIO_PSC2_4) */
+ mpc52xx_wakeup.level = 0; /* wakeup on low level */
+ /* mpc52xx_wakeup.delay = 10; wake after 10 minutes (RTC) */
+ mpc52xx_wakeup.board_suspend_prepare = lite5200_suspend_prepare;
+ mpc52xx_wakeup.board_resume_finish = lite5200_resume_finish;
+ mpc52xx_pm_init();
+#endif
+
#ifdef CONFIG_PCI
np = of_find_node_by_type(np, "pci");
if (np)
Index: grant.git/include/asm-powerpc/mpc52xx.h
===================================================================
--- grant.git.orig/include/asm-powerpc/mpc52xx.h
+++ grant.git/include/asm-powerpc/mpc52xx.h
@@ -232,6 +232,51 @@ struct mpc52xx_cdm {
u16 mclken_div_psc6; /* CDM + 0x36 reg13 byte2,3 */
};
+/* RTC */
+struct mpc52xx_rtc {
+ u8 set_time; /* RTC + 0x00 */
+ u8 hour_set; /* RTC + 0x01 */
+ u8 minute_set; /* RTC + 0x02 */
+ u8 second_set; /* RTC + 0x03 */
+
+ u8 set_date; /* RTC + 0x04 */
+ u8 month_set; /* RTC + 0x05 */
+ u8 weekday_set; /* RTC + 0x06 */
+ u8 date_set; /* RTC + 0x07 */
+
+ u8 write_sw; /* RTC + 0x08 */
+ u8 sw_set; /* RTC + 0x09 */
+ u16 year_set; /* RTC + 0x0a */
+
+ u8 alm_enable; /* RTC + 0x0c */
+ u8 alm_hour_set; /* RTC + 0x0d */
+ u8 alm_min_set; /* RTC + 0x0e */
+ u8 int_enable; /* RTC + 0x0f */
+
+ u8 reserved1;
+ u8 hour; /* RTC + 0x11 */
+ u8 minute; /* RTC + 0x12 */
+ u8 second; /* RTC + 0x13 */
+
+ u8 month; /* RTC + 0x14 */
+ u8 wday_mday; /* RTC + 0x15 */
+ u16 year; /* RTC + 0x16 */
+
+ u8 int_alm; /* RTC + 0x18 */
+ u8 int_sw; /* RTC + 0x19 */
+ u8 alm_status; /* RTC + 0x1a */
+ u8 sw_minute; /* RTC + 0x1b */
+
+ u8 bus_error_1; /* RTC + 0x1c */
+ u8 int_day; /* RTC + 0x1d */
+ u8 int_min; /* RTC + 0x1e */
+ u8 int_sec; /* RTC + 0x1f */
+
+ u8 pterm; /* RTC + 0x20 */
+ u8 eterm; /* RTC + 0x21 */
+ u16 reserved2;
+};
+
#endif /* __ASSEMBLY__ */
@@ -253,5 +298,23 @@ extern int __init mpc52xx_add_bridge(str
#endif /* __ASSEMBLY__ */
+#ifdef CONFIG_PM
+#define WAKEUP_GPIO (1<<1)
+#define WAKEUP_RTC (2<<1)
+#define WAKEUP_MSCAN (3<<1)
+struct mpc52xx_wakeup {
+ u8 mask;
+ u8 pin; /* which wakeup GPIO */
+ u8 level; /* transition to high or to low level */
+ u16 delay; /* after how many minutes (RTC) */
+
+ void (*board_suspend_prepare)(void __iomem *mbar);
+ void (*board_resume_finish)(void __iomem *mbar);
+};
+
+extern struct mpc52xx_wakeup mpc52xx_wakeup;
+int __init mpc52xx_pm_init(void);
+#endif /* CONFIG_PM */
+
#endif /* __ASM_POWERPC_MPC52xx_H__ */
Index: grant.git/arch/powerpc/boot/dts/lite5200.dts
===================================================================
--- grant.git.orig/arch/powerpc/boot/dts/lite5200.dts
+++ grant.git/arch/powerpc/boot/dts/lite5200.dts
@@ -49,6 +49,7 @@
soc5200 at f0000000 {
model = "fsl,mpc5200";
+ compatible = "mpc5200";
revision = "" // from bootloader
#interrupt-cells = <3>;
device_type = "soc";
Index: grant.git/arch/powerpc/boot/dts/lite5200b.dts
===================================================================
--- grant.git.orig/arch/powerpc/boot/dts/lite5200b.dts
+++ grant.git/arch/powerpc/boot/dts/lite5200b.dts
@@ -49,6 +49,7 @@
soc5200 at f0000000 {
model = "fsl,mpc5200b";
+ compatible = "mpc5200";
revision = ""; // from bootloader
#interrupt-cells = <3>;
device_type = "soc";
Index: grant.git/arch/powerpc/kernel/prom_init.c
===================================================================
--- grant.git.orig/arch/powerpc/kernel/prom_init.c
+++ grant.git/arch/powerpc/kernel/prom_init.c
@@ -2142,7 +2142,7 @@ static void __init fixup_device_tree_efi
3,12,0, 3,13,0, 3,14,0, 3,15,0 };
struct subst_entry efika_subst_table[] = {
{ "/", "device_type", prop_cstr("efika") },
- { "/builtin", "compatible", prop_cstr("soc") },
+ { "/builtin", "device_type", prop_cstr("soc") },
{ "/builtin/ata", "compatible", prop_cstr("mpc5200b-ata\0mpc5200-ata"), },
{ "/builtin/bestcomm", "compatible", prop_cstr("mpc5200b-bestcomm\0mpc5200-bestcomm") },
{ "/builtin/bestcomm", "interrupts", prop_bcomm_irq, sizeof(prop_bcomm_irq) },
More information about the Linuxppc-embedded
mailing list