All of lore.kernel.org
 help / color / mirror / Atom feed
From: wellsk40@gmail.com (Kevin Wells)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 06/10] ARM: LPC32XX: power and event management
Date: Tue, 26 Jan 2010 16:20:16 -0800	[thread overview]
Message-ID: <1264551616.6528.9@usb10132> (raw)

LPC32xx power and event management


Signed-off-by: Kevin Wells <kevin.wells@nxp.com>
---
  arch/arm/mach-lpc32xx/pm.c        |  170 +++++++++++++++
  arch/arm/mach-lpc32xx/pm.h        |  140 ++++++++++++
  arch/arm/mach-lpc32xx/pm_events.c |  426  
+++++++++++++++++++++++++++++++++++++
  arch/arm/mach-lpc32xx/suspend.S   |  155 ++++++++++++++
  4 files changed, 891 insertions(+), 0 deletions(-)

diff --git a/arch/arm/mach-lpc32xx/pm.c b/arch/arm/mach-lpc32xx/pm.c
new file mode 100644
index 0000000..8535e99
--- /dev/null
+++ b/arch/arm/mach-lpc32xx/pm.c
@@ -0,0 +1,170 @@
+/*
+ * arch/arm/mach-lpc32xx/pm.c
+ *
+ * Author: Kevin Wells <kevin.wells@nxp.com>
+ * Based in part on PNX4008 power management code
+ *
+ * Copyright (C) 2010 NXP Semiconductors
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA   
02111-1307  USA
+ */
+
+/*
+ * LPC32XX CPU and system power management
+ *
+ * The LCP32XX has three CPU modes for controlling system power: run,
+ * direct-run, and halt modes. When switching between halt and run  
modes,
+ * the CPU transistions through direct-run mode. For Linux, direct-run
+ * mode is not used in normal operation. Halt mode is used when the
+ * system is fully suspended.
+ *
+ * Run mode:
+ * The ARM CPU clock (HCLK_PLL), HCLK bus clock, and PCLK bus clocks  
are
+ * derived from the HCLK PLL. The HCLK and PCLK bus rates are divided  
from
+ * the HCLK_PLL rate. Linux runs in this mode.
+ *
+ * Direct-run mode:
+ * The ARM CPU clock, HCLK bus clock, and PCLK bus clocks are driven  
from
+ * SYSCLK. SYSCLK is usually around 13MHz, but may vary based on SYSCLK
+ * source or the frequency of the main oscillator. In this mode, the
+ * HCLK_PLL can be safely enabled, changed, or disabled.
+ *
+ * Halt mode:
+ * SYSCLK is gated off and the CPU and system clocks are halted.
+ * Peripherals based on the 32KHz oscillator clock (ie, RTC, touch,
+ * key scanner, etc.) still operate if enabled. In this state, an  
enabled
+ * system event (ie, GPIO state change, RTC match, key press, etc.)  
will
+ * wake the system up back into direct-run mode.
+ *
+ * DRAM refresh
+ * DRAM clocking and refresh are slightly different for systems with  
DDR
+ * DRAM or regular SDRAM devices. If SDRAM is used in the system, the
+ * SDRAM will still be accessible in direct-run mode. In DDR based  
systems,
+ * a transistion to direct-run mode will stop all DDR accesses (no  
clocks).
+ * Because of this, the code to switch power modes and the code to  
enter
+ * and exit DRAM self-refresh modes must not be executed in DRAM. A  
small
+ * section of IRAM is used instead for this.
+ *
+ * Suspend is handled with the following logic:
+ *  Backup a small area of IRAM used for the suspend code
+ *  Copy suspend code to IRAM
+ *  Transfer control to code in IRAM
+ *  Places DRAMs in self-refresh mode
+ *  Enter direct-run mode
+ *  Save state of HCLK_PLL PLL
+ *  Disable HCLK_PLL PLL
+ *  Enter halt mode - CPU and buses will stop
+ *  System enters direct-run mode when an enabled event occurs
+ *  HCLK PLL state is restored
+ *  Run mode is entered
+ *  DRAMS are placed back into normal mode
+ *  Code execution returns from IRAM
+ *  IRAM code are used for suspend is restored
+ *  Suspend mode is exited
+ */
+
+#include <linux/suspend.h>
+#include <linux/io.h>
+
+#include <asm/irq.h>
+#include <asm/atomic.h>
+#include <asm/mach/time.h>
+#include <asm/mach/irq.h>
+#include <asm/cacheflush.h>
+
+#include <mach/hardware.h>
+#include <mach/platform.h>
+#include "common.h"
+#include "clock.h"
+#include "pm.h"
+
+#define TEMP_IRAM_AREA  io_p2v(IRAM_BASE)
+
+static void *iram_swap_area;
+
+static int lpc32xx_pm_valid_state(suspend_state_t state)
+{
+	return (state == PM_SUSPEND_STANDBY) ||
+	       (state == PM_SUSPEND_MEM);
+}
+
+/*
+ * Both STANDBY and MEM suspend states are handled the same with no
+ * loss of CPU or memory state
+ */
+static int lpc32xx_pm_enter(suspend_state_t state)
+{
+	int (*lpc32xx_suspend_ptr) (void);
+
+	if (state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY) {
+		/* The CPU and MEM suspend code do the same thing.  
Wakeup
+		   from both states is very quick. */
+
+		/* Backup a small area of IRAM used for the suspend  
code */
+		memcpy(iram_swap_area, (void *) TEMP_IRAM_AREA,
+			lpc32xx_sys_suspend_sz);
+		flush_cache_all();
+
+		/* Copy code to suspend system into IRAM. The suspend  
code
+		   needs to run from IRAM as DRAM may no longer be  
available
+		   when the PLL is stopped. */
+		memcpy((void *) TEMP_IRAM_AREA, &lpc32xx_sys_suspend,
+			lpc32xx_sys_suspend_sz);
+
+		/* Transfer to suspend code in IRAM */
+		lpc32xx_suspend_ptr = (void *) TEMP_IRAM_AREA;
+		(void) lpc32xx_suspend_ptr();
+
+		/* Restore original IRAM contents */
+		memcpy((void *) TEMP_IRAM_AREA, iram_swap_area,
+			lpc32xx_sys_suspend_sz);
+	} else
+		return -ENOTSUPP;
+
+	/* Clear any pending wakeup events */
+	lpc32xx_event_clear_all();
+
+	return 0;
+}
+
+static struct platform_suspend_ops lpc32xx_pm_ops = {
+	.valid	= lpc32xx_pm_valid_state,
+	.enter	= lpc32xx_pm_enter,
+};
+
+#define EMC_DYN_MEM_CTRL_OFS 0x20
+#define EMC_SRMMC           (1 << 3)
+
+static int __init lpc32xx_pm_init(void)
+{
+	/* Setup SDRAM self-refresh clock to automatically
+	   disable on start of self-refresh */
+	writel(readl(io_p2v(EMC_BASE) + EMC_DYN_MEM_CTRL_OFS) |  
EMC_SRMMC,
+		io_p2v(EMC_BASE) + EMC_DYN_MEM_CTRL_OFS);
+
+	/* Allocate some space for temporary IRAM storage */
+	iram_swap_area = kmalloc(lpc32xx_sys_suspend_sz, GFP_ATOMIC);
+	if (!iram_swap_area) {
+		printk(KERN_ERR
+		       "PM Suspend: cannot allocate memory to save  
portion "
+			"of SRAM\n");
+		return -ENOMEM;
+	}
+
+	suspend_set_ops(&lpc32xx_pm_ops);
+
+	return 0;
+}
+arch_initcall(lpc32xx_pm_init);
diff --git a/arch/arm/mach-lpc32xx/pm.h b/arch/arm/mach-lpc32xx/pm.h
new file mode 100644
index 0000000..9d7d548
--- /dev/null
+++ b/arch/arm/mach-lpc32xx/pm.h
@@ -0,0 +1,140 @@
+/*
+ * arch/arm/mach-lpc32xx/pm.h
+ *
+ * Author: Kevin Wells <kevin.wells@nxp.com>
+ *
+ * Copyright (C) 2010 NXP Semiconductors
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA   
02111-1307  USA
+ */
+
+#ifndef __LPC32XX__PM_H
+#define __LPC32XX__PM_H
+
+/*
+ * This file allows the enable and disable of events that can wake up
+ * the system when in sleep mode. Events fire on either the low or
+ * high edge transistion and can be configured in hardware.
+ */
+
+enum lpc32xx_events {
+	/* For the following group, an active transistion on the pin
+	   associated with the event will wakeup the chip if the
+	   event is enabled. */
+	LPC32XX_WKUP_GPI_08,
+	LPC32XX_WKUP_GPI_09,
+	LPC32XX_WKUP_GPI_19,
+	LPC32XX_WKUP_SPI2_DATIN,
+	LPC32XX_WKUP_GPI_07,
+	LPC32XX_WKUP_SPI1_DATIN,
+	LPC32XX_WKUP_SYSCLKEN,
+	LPC32XX_WKUP_GPI00, /* Input mode only */
+	LPC32XX_WKUP_GPI01, /* Input mode only */
+	LPC32XX_WKUP_GPI02, /* Input mode only */
+	LPC32XX_WKUP_GPI03, /* Input mode only */
+	LPC32XX_WKUP_GPI04, /* Input mode only */
+	LPC32XX_WKUP_GPI05, /* Input mode only */
+	LPC32XX_WKUP_GPI06, /* Input mode only */
+	LPC32XX_WKUP_MSDIO_START, /* All SD data signals Or'ed */
+	LPC32XX_WKUP_SDIO_INT_N, /* SDIO D1 only */
+	LPC32XX_WKUP_U1_RX,
+	LPC32XX_WKUP_U2_RX,
+	LPC32XX_WKUP_U2_HCTS,
+	LPC32XX_WKUP_U3_RX,
+	LPC32XX_WKUP_GPI_28,
+	LPC32XX_WKUP_U5_RX,
+	LPC32XX_WKUP_U6_IRRX,
+	LPC32XX_WKUP_U7_HCTS,
+	LPC32XX_WKUP_U7_RX,
+
+	/* For the following group, an interrupt for the peripheral
+	   will wakeup the chip if the event is enabled. */
+	LPC32XX_WKUP_GPIO_00,
+	LPC32XX_WKUP_GPIO_01,
+	LPC32XX_WKUP_GPIO_02,
+	LPC32XX_WKUP_GPIO_03,
+	LPC32XX_WKUP_GPIO_04,
+	LPC32XX_WKUP_GPIO_05,
+	LPC32XX_WKUP_P0_P1_ALL, /* One or more sources in P0/P1 */
+	LPC32XX_WKUP_MAC_START,
+	LPC32XX_WKUP_KEY_IRQ,
+	LPC32XX_WKUP_USB_OTG_ATX_INT_N,
+	LPC32XX_WKUP_USB_OTG_TIMER,
+	LPC32XX_WKUP_USB_I2C_INT,
+	LPC32XX_WKUP_USB_INT,
+	LPC32XX_WKUP_USB_NEED_CLK,
+	LPC32XX_WKUP_RTC_INT,
+	LPC32XX_WKUP_MSTIMER_INT,
+	LPC32XX_WKUP_USB_AHC_NEED_CLK,
+	LPC32XX_WKUP_TS_AUX_INT,
+	LPC32XX_WKUP_TS_P_INT,
+	LPC32XX_WKUP_TS_INT,
+
+	/* P0/P1 group signals. These signals are all routed through the
+	   LPC32XX_WKUP_P0_P1_ALL event in the previous group, so be  
sure
+	   to enable LPC32XX_WKUP_P0_P1_ALL if any of these are used.  
Any
+	   one enabled and active event will generate the
+	   LPC32XX_WKUP_P0_P1_ALL event */
+	LPC32XX_WKUP_P0_0,
+	LPC32XX_WKUP_P0_1,
+	LPC32XX_WKUP_P0_2,
+	LPC32XX_WKUP_P0_3,
+	LPC32XX_WKUP_P0_4,
+	LPC32XX_WKUP_P0_5,
+	LPC32XX_WKUP_P0_6,
+	LPC32XX_WKUP_P0_7,
+	LPC32XX_WKUP_P1_3,
+	LPC32XX_WKUP_P1_4,
+	LPC32XX_WKUP_P1_5,
+	LPC32XX_WKUP_P1_6,
+	LPC32XX_WKUP_P1_7,
+	LPC32XX_WKUP_P1_8,
+	LPC32XX_WKUP_P1_9,
+	LPC32XX_WKUP_P1_10,
+	LPC32XX_WKUP_P1_11,
+	LPC32XX_WKUP_P1_12,
+	LPC32XX_WKUP_P1_13,
+	LPC32XX_WKUP_P1_14,
+	LPC32XX_WKUP_P1_15,
+	LPC32XX_WKUP_P1_16,
+	LPC32XX_WKUP_P1_17,
+	LPC32XX_WKUP_P1_18,
+	LPC32XX_WKUP_P1_19,
+	LPC32XX_WKUP_P1_20,
+	LPC32XX_WKUP_P1_21,
+	LPC32XX_WKUP_P1_22,
+	LPC32XX_WKUP_P1_23,
+	LPC32XX_LAST_EVENT = LPC32XX_WKUP_P1_23
+};
+
+/*
+ * Wakeup event support
+ */
+extern void lpc32xx_event_init(void);
+extern void lpc32xx_event_enable(enum lpc32xx_events event_id);
+extern void lpc32xx_event_disable(enum lpc32xx_events event_id);
+extern int lpc32xx_event_set(enum lpc32xx_events event_id,
+	int high_edge);
+extern int lpc32xx_event_enabled(enum lpc32xx_events event_id);
+extern void lpc32xx_event_clear(enum lpc32xx_events event_id);
+extern void lpc32xx_event_clear_all(void);
+
+/*
+ * Pointers used for sizing and copying suspend function data
+ */
+extern int lpc32xx_sys_suspend(void);
+extern int lpc32xx_sys_suspend_sz;
+
+#endif
diff --git a/arch/arm/mach-lpc32xx/pm_events.c  
b/arch/arm/mach-lpc32xx/pm_events.c
new file mode 100644
index 0000000..d3b1b54
--- /dev/null
+++ b/arch/arm/mach-lpc32xx/pm_events.c
@@ -0,0 +1,426 @@
+/*
+ * arch/arm/mach-lpc32xx/pm_events.c
+ *
+ * Author: Kevin Wells <kevin.wells@nxp.com>
+ *
+ * Copyright (C) 2010 NXP Semiconductors
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA   
02111-1307  USA
+ */
+
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/err.h>
+
+#include <mach/hardware.h>
+#include <mach/platform.h>
+#include "common.h"
+#include "pm.h"
+
+struct lpc32xx_event_info {
+	u32 offs;
+	u32 mask;
+};
+
+static const struct lpc32xx_event_info events[LPC32XX_LAST_EVENT + 1]  
= {
+	[LPC32XX_WKUP_GPI_08] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O8_BIT,
+	},
+	[LPC32XX_WKUP_GPI_09] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O9_BIT,
+	},
+	[LPC32XX_WKUP_GPI_19] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_19_BIT,
+	},
+	[LPC32XX_WKUP_SPI2_DATIN] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_SPI2_DATIN_BIT,
+	},
+	[LPC32XX_WKUP_GPI_07] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O7_BIT,
+	},
+	[LPC32XX_WKUP_SPI1_DATIN] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_SPI1_DATIN_BIT,
+	},
+	[LPC32XX_WKUP_SYSCLKEN] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_SYSCLKEN_BIT,
+	},
+	[LPC32XX_WKUP_GPI00] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O0_BIT,
+	},
+	[LPC32XX_WKUP_GPI01] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O1_BIT,
+	},
+	[LPC32XX_WKUP_GPI02] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O2_BIT,
+	},
+	[LPC32XX_WKUP_GPI03] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O3_BIT,
+	},
+	[LPC32XX_WKUP_GPI04] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O4_BIT,
+	},
+	[LPC32XX_WKUP_GPI05] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O5_BIT,
+	},
+	[LPC32XX_WKUP_GPI06] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPIO_O6_BIT,
+	},
+	[LPC32XX_WKUP_MSDIO_START] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_MSDIO_SRT_BIT,
+	},
+	[LPC32XX_WKUP_SDIO_INT_N] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_MSDIO_INT_BIT,
+	},
+	[LPC32XX_WKUP_U1_RX] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U1_RX_BIT,
+	},
+	[LPC32XX_WKUP_U2_RX] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U2_RX_BIT,
+	},
+	[LPC32XX_WKUP_U2_HCTS] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U2_HCTS_BIT,
+	},
+	[LPC32XX_WKUP_U3_RX] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U3_RX_BIT,
+	},
+	[LPC32XX_WKUP_GPI_28] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_GPI_11_BIT,
+	},
+	[LPC32XX_WKUP_U5_RX] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U5_RX_BIT,
+	},
+	[LPC32XX_WKUP_U6_IRRX] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U6_IRRX_BIT,
+	},
+	[LPC32XX_WKUP_U7_HCTS] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U7_HCTS_BIT,
+	},
+	[LPC32XX_WKUP_U7_RX] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_EXTSRC_U7_RX_BIT,
+	},
+
+	[LPC32XX_WKUP_GPIO_00] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_GPIO_00_BIT,
+	},
+	[LPC32XX_WKUP_GPIO_01] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_GPIO_01_BIT,
+	},
+	[LPC32XX_WKUP_GPIO_02] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_GPIO_02_BIT,
+	},
+	[LPC32XX_WKUP_GPIO_03] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_GPIO_03_BIT,
+	},
+	[LPC32XX_WKUP_GPIO_04] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_GPIO_04_BIT,
+	},
+	[LPC32XX_WKUP_GPIO_05] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_GPIO_05_BIT,
+	},
+	[LPC32XX_WKUP_P0_P1_ALL] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_P0P1_BIT,
+	},
+	[LPC32XX_WKUP_MAC_START] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_MAC_BIT,
+	},
+	[LPC32XX_WKUP_KEY_IRQ] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_KEY_BIT,
+	},
+	[LPC32XX_WKUP_USB_OTG_ATX_INT_N] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_USBATXINT_BIT,
+	},
+	[LPC32XX_WKUP_USB_OTG_TIMER] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_USBOTGTIMER_BIT,
+	},
+	[LPC32XX_WKUP_USB_I2C_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_I2C_BIT,
+	},
+	[LPC32XX_WKUP_USB_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_USB_BIT,
+	},
+	[LPC32XX_WKUP_USB_NEED_CLK] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_USBNEEDCLK_BIT,
+	},
+	[LPC32XX_WKUP_RTC_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_RTC_BIT,
+	},
+	[LPC32XX_WKUP_MSTIMER_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_MSTIMER_BIT,
+	},
+	[LPC32XX_WKUP_USB_AHC_NEED_CLK] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_USBAHNEEDCLK_BIT,
+	},
+	[LPC32XX_WKUP_TS_AUX_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_TS_AUX_BIT,
+	},
+	[LPC32XX_WKUP_TS_P_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_TS_P_BIT,
+	},
+	[LPC32XX_WKUP_TS_INT] = {
+		.offs = CLKPWR_INT_ER(0),
+		.mask = CLKPWR_INTSRC_ADC_BIT,
+	},
+	[LPC32XX_WKUP_P0_0] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO0_BIT,
+	},
+	[LPC32XX_WKUP_P0_1] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO1_BIT,
+	},
+	[LPC32XX_WKUP_P0_2] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO2_BIT,
+	},
+	[LPC32XX_WKUP_P0_3] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO3_BIT,
+	},
+	[LPC32XX_WKUP_P0_4] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO4_BIT,
+	},
+	[LPC32XX_WKUP_P0_5] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO5_BIT,
+	},
+	[LPC32XX_WKUP_P0_6] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO6_BIT,
+	},
+	[LPC32XX_WKUP_P0_7] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P0IO7_BIT,
+	},
+	[LPC32XX_WKUP_P1_3] = {
+		.offs = CLKPWR_PIN_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO3_BIT,
+	},
+	[LPC32XX_WKUP_P1_4] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO4_BIT,
+	},
+	[LPC32XX_WKUP_P1_5] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO5_BIT,
+	},
+	[LPC32XX_WKUP_P1_6] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO6_BIT,
+	},
+	[LPC32XX_WKUP_P1_7] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO7_BIT,
+	},
+	[LPC32XX_WKUP_P1_8] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO8_BIT,
+	},
+	[LPC32XX_WKUP_P1_9] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO9_BIT,
+	},
+	[LPC32XX_WKUP_P1_10] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO10_BIT,
+	},
+	[LPC32XX_WKUP_P1_11] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO11_BIT,
+	},
+	[LPC32XX_WKUP_P1_12] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO12_BIT,
+	},
+	[LPC32XX_WKUP_P1_13] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO13_BIT,
+	},
+	[LPC32XX_WKUP_P1_14] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO14_BIT,
+	},
+	[LPC32XX_WKUP_P1_15] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO15_BIT,
+	},
+	[LPC32XX_WKUP_P1_16] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO16_BIT,
+	},
+	[LPC32XX_WKUP_P1_17] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO17_BIT,
+	},
+	[LPC32XX_WKUP_P1_18] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO18_BIT,
+	},
+	[LPC32XX_WKUP_P1_19] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO19_BIT,
+	},
+	[LPC32XX_WKUP_P1_20] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO20_BIT,
+	},
+	[LPC32XX_WKUP_P1_21] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO21_BIT,
+	},
+	[LPC32XX_WKUP_P1_22] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO22_BIT,
+	},
+	[LPC32XX_WKUP_P1_23] = {
+		.offs = CLKPWR_P01_ER(0),
+		.mask = CLKPWR_GPIOSRC_P1IO23_BIT,
+	},
+};
+
+void lpc32xx_event_init(void)
+{
+	/* Initially disable all events, set all events to default
+	   type and polarity per chip User guide, and clear any
+	   pending event statuses */
+	writel(0, CLKPWR_P01_ER(CLKPWR_IOBASE));
+	writel(0, CLKPWR_INT_ER(CLKPWR_IOBASE));
+	writel(0, CLKPWR_PIN_ER(CLKPWR_IOBASE));
+
+	/* Default activation polarities, all pin sources are low edge
+	   triggered */
+	writel(CLKPWR_INTSRC_TS_P_BIT | CLKPWR_INTSRC_MSTIMER_BIT |
+		CLKPWR_INTSRC_RTC_BIT, CLKPWR_INT_AP(CLKPWR_IOBASE));
+	writel(0, CLKPWR_PIN_AP(CLKPWR_IOBASE));
+
+	/* Clear latched event states */
+	writel(readl(CLKPWR_PIN_RS(CLKPWR_IOBASE)),
+		CLKPWR_PIN_RS(CLKPWR_IOBASE));
+	writel(readl(CLKPWR_INT_RS(CLKPWR_IOBASE)),
+		CLKPWR_INT_RS(CLKPWR_IOBASE));
+}
+
+void lpc32xx_event_enable(enum lpc32xx_events event_id)
+{
+	writel(readl(CLKPWR_IOBASE + events[event_id].offs) |
+		events[event_id].mask, CLKPWR_IOBASE +  
events[event_id].offs);
+}
+
+void lpc32xx_event_disable(enum lpc32xx_events event_id)
+{
+	writel(readl(CLKPWR_IOBASE + events[event_id].offs) &
+		~events[event_id].mask, CLKPWR_IOBASE +  
events[event_id].offs);
+}
+
+extern int lpc32xx_event_set(enum lpc32xx_events event_id,
+	int high_edge)
+{
+	u32 tmp;
+
+	if (event_id <= LPC32XX_WKUP_U7_RX) {
+		tmp = readl(CLKPWR_PIN_AP(CLKPWR_IOBASE));
+
+		if (high_edge)
+			tmp |= events[event_id].mask;
+		else
+			tmp &= ~events[event_id].mask;
+
+		writel(tmp, CLKPWR_PIN_AP(CLKPWR_IOBASE));
+	} else if (event_id <= LPC32XX_WKUP_TS_INT) {
+		tmp = readl(CLKPWR_INT_AP(CLKPWR_IOBASE));
+
+		if (high_edge)
+			tmp |= events[event_id].mask;
+		else
+			tmp &= ~events[event_id].mask;
+
+		writel(tmp, CLKPWR_INT_AP(CLKPWR_IOBASE));
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
+int lpc32xx_event_enabled(enum lpc32xx_events event_id)
+{
+	u32 tmp;
+
+	tmp = readl(CLKPWR_IOBASE + events[event_id].offs) &
+		events[event_id].mask;
+
+	return (tmp != 0);
+}
+
+void lpc32xx_event_clear(enum lpc32xx_events event_id)
+{
+	if (event_id <= LPC32XX_WKUP_U7_RX)
+		writel(events[event_id].mask,  
CLKPWR_PIN_RS(CLKPWR_IOBASE));
+	else if (event_id <= LPC32XX_WKUP_TS_INT)
+		writel(events[event_id].mask,  
CLKPWR_INT_RS(CLKPWR_IOBASE));
+}
+
+void lpc32xx_event_clear_all(void)
+{
+	/* Clear all latched event states */
+	writel(readl(CLKPWR_PIN_RS(CLKPWR_IOBASE)),
+		CLKPWR_PIN_RS(CLKPWR_IOBASE));
+	writel(readl(CLKPWR_INT_RS(CLKPWR_IOBASE)),
+		CLKPWR_INT_RS(CLKPWR_IOBASE));
+}
+
diff --git a/arch/arm/mach-lpc32xx/suspend.S  
b/arch/arm/mach-lpc32xx/suspend.S
new file mode 100644
index 0000000..075708b
--- /dev/null
+++ b/arch/arm/mach-lpc32xx/suspend.S
@@ -0,0 +1,155 @@
+/*
+ * arch/arm/mach-lpc32xx/suspend.S
+ *
+ * Author: Kevin Wells <kevin.wells@nxp.com>
+ * Based in part on PNX4008 power management code
+ *
+ * Copyright (C) 2010 NXP Semiconductors
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA   
02111-1307  USA
+ */
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <mach/platform.h>
+#include <mach/hardware.h>
+
+/* Using named register defines makes the code easier to follow */
+#define WORK1_REG		r0
+#define WORK2_REG		r1
+#define SAVED_HCLK_DIV_REG	r2
+#define SAVED_HCLK_PLL_REG	r3
+#define SAVED_DRAM_CLKCTRL_REG	r4
+#define SAVED_PWR_CTRL_REG	r5
+#define CLKPWRBASE_REG		r6
+#define EMCBASE_REG		r7
+
+#define EMC_STATUS_OFFS		0x04
+#define EMC_STATUS_BUSY		0x1
+#define EMC_STATUS_SELF_RFSH	0x4
+
+	.text
+
+ENTRY(lpc32xx_sys_suspend)
+	@ Save a copy of the used registers in IRAM, r0 is corrupted
+	adr	r0, tmp_stack_end
+	stmfd	r0!, {r1 - r7, sp, lr}
+
+	@ Load a few common register addresses
+	adr	WORK1_REG, reg_bases
+	ldr	CLKPWRBASE_REG, [WORK1_REG, #0]
+	ldr	EMCBASE_REG, [WORK1_REG, #4]
+
+	ldr	SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,  
#CLKPWR_PWR_CTRL(0)]
+	orr	WORK1_REG, SAVED_PWR_CTRL_REG, #CLKPWR_SDRAM_SELF_RFSH
+
+	@ Wait for SDRAM busy status to go busy and then idle
+	@ This guarantees a small windows where DRAM isn't busy
+1:
+	ldr	WORK2_REG, [EMCBASE_REG, #EMC_STATUS_OFFS]
+	and	WORK2_REG, WORK2_REG, #EMC_STATUS_BUSY
+	cmp	WORK2_REG, #EMC_STATUS_BUSY
+	bne	1b @ Branch while idle
+2:
+	ldr	WORK2_REG, [EMCBASE_REG, #EMC_STATUS_OFFS]
+	and	WORK2_REG, WORK2_REG, #EMC_STATUS_BUSY
+	cmp	WORK2_REG, #EMC_STATUS_BUSY
+	beq	2b @ Branch until idle
+
+	@ Setup self-refresh with support for manual exit of
+	@ self-refresh mode
+	str	WORK1_REG, [CLKPWRBASE_REG, #CLKPWR_PWR_CTRL(0)]
+	orr	WORK2_REG, WORK1_REG, #CLKPWR_UPD_SDRAM_SELF_RFSH
+	str	WORK2_REG, [CLKPWRBASE_REG, #CLKPWR_PWR_CTRL(0)]
+	str	WORK1_REG, [CLKPWRBASE_REG, #CLKPWR_PWR_CTRL(0)]
+
+	@ Wait for self-refresh acknowledge, clocks to the DRAM device
+	@ will automatically stop on start of self-refresh
+3:
+	ldr	WORK2_REG, [EMCBASE_REG, #EMC_STATUS_OFFS]
+	and	WORK2_REG, WORK2_REG, #EMC_STATUS_SELF_RFSH
+	cmp	WORK2_REG, #EMC_STATUS_SELF_RFSH
+	bne	3b @ Branch until self-refresh mode starts
+
+	@ Enter direct-run mode from run mode
+	bic	WORK1_REG, WORK1_REG, #CLKPWR_SELECT_RUN_MODE
+	str	WORK1_REG, [CLKPWRBASE_REG, #CLKPWR_PWR_CTRL(0)]
+
+	@ Safe disable of DRAM clock in EMC block, prevents DDR sync
+	@ issues on restart
+	ldr	SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,  
#CLKPWR_HCLK_DIV(0)]
+	and	WORK2_REG, SAVED_HCLK_DIV_REG, #0xFFFFFE7F
+	str	WORK2_REG, [CLKPWRBASE_REG, #CLKPWR_HCLK_DIV(0)]
+
+	@ Save HCLK PLL state and disable HCLK PLL
+	ldr	SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,  
#CLKPWR_HCLKPLL_CTRL(0)]
+	bic	WORK2_REG, SAVED_HCLK_PLL_REG, #CLKPWR_HCLKPLL_POWER_UP
+	str	WORK2_REG, [CLKPWRBASE_REG, #CLKPWR_HCLKPLL_CTRL(0)]
+
+	@ Enter stop mode until an enabled event occurs
+	orr	WORK1_REG, WORK1_REG, #CLKPWR_STOP_MODE_CTRL
+	str	WORK1_REG, [CLKPWRBASE_REG, #CLKPWR_PWR_CTRL(0)]
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+
+	@ Clear stop status
+	bic	WORK1_REG, WORK1_REG, #CLKPWR_STOP_MODE_CTRL
+
+	@ Restore original HCLK PLL value and wait for PLL lock
+	str	SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,  
#CLKPWR_HCLKPLL_CTRL(0)]
+4:
+	ldr	WORK2_REG, [CLKPWRBASE_REG, #CLKPWR_HCLKPLL_CTRL(0)]
+	and	WORK2_REG, WORK2_REG, #CLKPWR_HCLKPLL_PLL_STS
+	bne	4b
+
+	@ Re-enter run mode with self-refresh flag cleared, but no DRAM
+	@ update yet. DRAM is still in self-refresh
+	str	SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,  
#CLKPWR_PWR_CTRL(0)]
+
+	@ Restore original DRAM clock mode to restore DRAM clocks
+	str	SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,  
#CLKPWR_HCLK_DIV(0)]
+
+	@ Clear self-refresh mode
+	orr	WORK1_REG, SAVED_PWR_CTRL_REG,  
#CLKPWR_UPD_SDRAM_SELF_RFSH
+	str	WORK1_REG, [CLKPWRBASE_REG, #CLKPWR_PWR_CTRL(0)]
+	str	SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,  
#CLKPWR_PWR_CTRL(0)]
+
+	@ Wait for EMC to clear self-refresh mode
+5:
+	ldr	WORK2_REG, [EMCBASE_REG, #EMC_STATUS_OFFS]
+	and	WORK2_REG, WORK2_REG, #EMC_STATUS_SELF_RFSH
+	bne	5b @ Branch until self-refresh has exited
+
+	@ restore regs and return
+	adr	r0, tmp_stack
+	ldmfd	r0!, {r1 - r7, sp, pc}
+
+reg_bases:
+	.long	IO_ADDRESS(CLK_PM_BASE)
+	.long	IO_ADDRESS(EMC_BASE)
+
+tmp_stack:
+	.long	0, 0, 0, 0, 0, 0, 0, 0, 0
+tmp_stack_end:
+
+ENTRY(lpc32xx_sys_suspend_sz)
+	.word	. - lpc32xx_sys_suspend
+
-- 
1.6.6

                 reply	other threads:[~2010-01-27  0:20 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1264551616.6528.9@usb10132 \
    --to=wellsk40@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.