|
@@ -19,6 +19,7 @@
|
|
|
#include <linux/pm.h>
|
|
|
#include <linux/platform_device.h>
|
|
|
#include <linux/irq.h>
|
|
|
+#include <linux/io.h>
|
|
|
|
|
|
#include <asm/hardware.h>
|
|
|
#include <asm/arch/pxa3xx-regs.h>
|
|
@@ -201,6 +202,225 @@ static struct clk pxa3xx_clks[] = {
|
|
|
PXA3xx_CKEN("MMCCLK", MMC3, 19500000, 0, &pxa3xx_device_mci3.dev),
|
|
|
};
|
|
|
|
|
|
+#ifdef CONFIG_PM
|
|
|
+#define SLEEP_SAVE_SIZE 4
|
|
|
+
|
|
|
+#define ISRAM_START 0x5c000000
|
|
|
+#define ISRAM_SIZE SZ_256K
|
|
|
+
|
|
|
+static void __iomem *sram;
|
|
|
+static unsigned long wakeup_src;
|
|
|
+
|
|
|
+static void pxa3xx_cpu_pm_save(unsigned long *sleep_save)
|
|
|
+{
|
|
|
+ pr_debug("PM: CKENA=%08x CKENB=%08x\n", CKENA, CKENB);
|
|
|
+
|
|
|
+ if (CKENA & (1 << CKEN_USBH)) {
|
|
|
+ printk(KERN_ERR "PM: USB host clock not stopped?\n");
|
|
|
+ CKENA &= ~(1 << CKEN_USBH);
|
|
|
+ }
|
|
|
+// CKENA |= 1 << (CKEN_ISC & 31);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Low power modes require the HSIO2 clock to be enabled.
|
|
|
+ */
|
|
|
+ CKENB |= 1 << (CKEN_HSIO2 & 31);
|
|
|
+}
|
|
|
+
|
|
|
+static void pxa3xx_cpu_pm_restore(unsigned long *sleep_save)
|
|
|
+{
|
|
|
+ CKENB &= ~(1 << (CKEN_HSIO2 & 31));
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Enter a standby mode (S0D1C2 or S0D2C2). Upon wakeup, the dynamic
|
|
|
+ * memory controller has to be reinitialised, so we place some code
|
|
|
+ * in the SRAM to perform this function.
|
|
|
+ *
|
|
|
+ * We disable FIQs across the standby - otherwise, we might receive a
|
|
|
+ * FIQ while the SDRAM is unavailable.
|
|
|
+ */
|
|
|
+static void pxa3xx_cpu_standby(unsigned int pwrmode)
|
|
|
+{
|
|
|
+ extern const char pm_enter_standby_start[], pm_enter_standby_end[];
|
|
|
+ void (*fn)(unsigned int) = (void __force *)(sram + 0x8000);
|
|
|
+
|
|
|
+ memcpy_toio(sram + 0x8000, pm_enter_standby_start,
|
|
|
+ pm_enter_standby_end - pm_enter_standby_start);
|
|
|
+
|
|
|
+ AD2D0SR = ~0;
|
|
|
+ AD2D1SR = ~0;
|
|
|
+ AD2D0ER = wakeup_src;
|
|
|
+ AD2D1ER = 0;
|
|
|
+ ASCR = ASCR;
|
|
|
+ ARSR = ARSR;
|
|
|
+
|
|
|
+ local_fiq_disable();
|
|
|
+ fn(pwrmode);
|
|
|
+ local_fiq_enable();
|
|
|
+
|
|
|
+ AD2D0ER = 0;
|
|
|
+ AD2D1ER = 0;
|
|
|
+
|
|
|
+ printk("PM: AD2D0SR=%08x ASCR=%08x\n", AD2D0SR, ASCR);
|
|
|
+}
|
|
|
+
|
|
|
+static void pxa3xx_cpu_pm_enter(suspend_state_t state)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * Don't sleep if no wakeup sources are defined
|
|
|
+ */
|
|
|
+ if (wakeup_src == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ switch (state) {
|
|
|
+ case PM_SUSPEND_STANDBY:
|
|
|
+ pxa3xx_cpu_standby(PXA3xx_PM_S0D2C2);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PM_SUSPEND_MEM:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int pxa3xx_cpu_pm_valid(suspend_state_t state)
|
|
|
+{
|
|
|
+ return state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY;
|
|
|
+}
|
|
|
+
|
|
|
+static struct pxa_cpu_pm_fns pxa3xx_cpu_pm_fns = {
|
|
|
+ .save_size = SLEEP_SAVE_SIZE,
|
|
|
+ .save = pxa3xx_cpu_pm_save,
|
|
|
+ .restore = pxa3xx_cpu_pm_restore,
|
|
|
+ .valid = pxa3xx_cpu_pm_valid,
|
|
|
+ .enter = pxa3xx_cpu_pm_enter,
|
|
|
+};
|
|
|
+
|
|
|
+static void __init pxa3xx_init_pm(void)
|
|
|
+{
|
|
|
+ sram = ioremap(ISRAM_START, ISRAM_SIZE);
|
|
|
+ if (!sram) {
|
|
|
+ printk(KERN_ERR "Unable to map ISRAM: disabling standby/suspend\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Since we copy wakeup code into the SRAM, we need to ensure
|
|
|
+ * that it is preserved over the low power modes. Note: bit 8
|
|
|
+ * is undocumented in the developer manual, but must be set.
|
|
|
+ */
|
|
|
+ AD1R |= ADXR_L2 | ADXR_R0;
|
|
|
+ AD2R |= ADXR_L2 | ADXR_R0;
|
|
|
+ AD3R |= ADXR_L2 | ADXR_R0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Clear the resume enable registers.
|
|
|
+ */
|
|
|
+ AD1D0ER = 0;
|
|
|
+ AD2D0ER = 0;
|
|
|
+ AD2D1ER = 0;
|
|
|
+ AD3ER = 0;
|
|
|
+
|
|
|
+ pxa_cpu_pm_fns = &pxa3xx_cpu_pm_fns;
|
|
|
+}
|
|
|
+
|
|
|
+static int pxa3xx_set_wake(unsigned int irq, unsigned int on)
|
|
|
+{
|
|
|
+ unsigned long flags, mask = 0;
|
|
|
+
|
|
|
+ switch (irq) {
|
|
|
+ case IRQ_SSP3:
|
|
|
+ mask = ADXER_MFP_WSSP3;
|
|
|
+ break;
|
|
|
+ case IRQ_MSL:
|
|
|
+ mask = ADXER_WMSL0;
|
|
|
+ break;
|
|
|
+ case IRQ_USBH2:
|
|
|
+ case IRQ_USBH1:
|
|
|
+ mask = ADXER_WUSBH;
|
|
|
+ break;
|
|
|
+ case IRQ_KEYPAD:
|
|
|
+ mask = ADXER_WKP;
|
|
|
+ break;
|
|
|
+ case IRQ_AC97:
|
|
|
+ mask = ADXER_MFP_WAC97;
|
|
|
+ break;
|
|
|
+ case IRQ_USIM:
|
|
|
+ mask = ADXER_WUSIM0;
|
|
|
+ break;
|
|
|
+ case IRQ_SSP2:
|
|
|
+ mask = ADXER_MFP_WSSP2;
|
|
|
+ break;
|
|
|
+ case IRQ_I2C:
|
|
|
+ mask = ADXER_MFP_WI2C;
|
|
|
+ break;
|
|
|
+ case IRQ_STUART:
|
|
|
+ mask = ADXER_MFP_WUART3;
|
|
|
+ break;
|
|
|
+ case IRQ_BTUART:
|
|
|
+ mask = ADXER_MFP_WUART2;
|
|
|
+ break;
|
|
|
+ case IRQ_FFUART:
|
|
|
+ mask = ADXER_MFP_WUART1;
|
|
|
+ break;
|
|
|
+ case IRQ_MMC:
|
|
|
+ mask = ADXER_MFP_WMMC1;
|
|
|
+ break;
|
|
|
+ case IRQ_SSP:
|
|
|
+ mask = ADXER_MFP_WSSP1;
|
|
|
+ break;
|
|
|
+ case IRQ_RTCAlrm:
|
|
|
+ mask = ADXER_WRTC;
|
|
|
+ break;
|
|
|
+ case IRQ_SSP4:
|
|
|
+ mask = ADXER_MFP_WSSP4;
|
|
|
+ break;
|
|
|
+ case IRQ_TSI:
|
|
|
+ mask = ADXER_WTSI;
|
|
|
+ break;
|
|
|
+ case IRQ_USIM2:
|
|
|
+ mask = ADXER_WUSIM1;
|
|
|
+ break;
|
|
|
+ case IRQ_MMC2:
|
|
|
+ mask = ADXER_MFP_WMMC2;
|
|
|
+ break;
|
|
|
+ case IRQ_NAND:
|
|
|
+ mask = ADXER_MFP_WFLASH;
|
|
|
+ break;
|
|
|
+ case IRQ_USB2:
|
|
|
+ mask = ADXER_WUSB2;
|
|
|
+ break;
|
|
|
+ case IRQ_WAKEUP0:
|
|
|
+ mask = ADXER_WEXTWAKE0;
|
|
|
+ break;
|
|
|
+ case IRQ_WAKEUP1:
|
|
|
+ mask = ADXER_WEXTWAKE1;
|
|
|
+ break;
|
|
|
+ case IRQ_MMC3:
|
|
|
+ mask = ADXER_MFP_GEN12;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+ if (on)
|
|
|
+ wakeup_src |= mask;
|
|
|
+ else
|
|
|
+ wakeup_src &= ~mask;
|
|
|
+ local_irq_restore(flags);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void pxa3xx_init_irq_pm(void)
|
|
|
+{
|
|
|
+ pxa_init_irq_set_wake(pxa3xx_set_wake);
|
|
|
+}
|
|
|
+
|
|
|
+#else
|
|
|
+static inline void pxa3xx_init_pm(void) {}
|
|
|
+static inline void pxa3xx_init_irq_pm(void) {}
|
|
|
+#endif
|
|
|
+
|
|
|
void __init pxa3xx_init_irq(void)
|
|
|
{
|
|
|
/* enable CP6 access */
|
|
@@ -212,6 +432,7 @@ void __init pxa3xx_init_irq(void)
|
|
|
pxa_init_irq_low();
|
|
|
pxa_init_irq_high();
|
|
|
pxa_init_irq_gpio(128);
|
|
|
+ pxa3xx_init_irq_pm();
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -241,6 +462,8 @@ static int __init pxa3xx_init(void)
|
|
|
if ((ret = pxa_init_dma(32)))
|
|
|
return ret;
|
|
|
|
|
|
+ pxa3xx_init_pm();
|
|
|
+
|
|
|
return platform_add_devices(devices, ARRAY_SIZE(devices));
|
|
|
}
|
|
|
return 0;
|