[PATCH 09/13] ARM: LPC32XX: power and event management

Uwe Kleine-König u.kleine-koenig at pengutronix.de
Wed Feb 3 11:44:25 EST 2010


Hello Kevin,

On Wed, Jan 27, 2010 at 05:43:27PM -0800, wellsk40 at gmail.com wrote:
> From: Kevin Wells <wellsk40 at gmail.com>
> 
> Power mode suspend code and event management code used for device,
> system, and memory sleep/wakeup
> 
> Signed-off-by: Kevin Wells <wellsk40 at gmail.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 at 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);
> +}
Why support both standby and mem and let both do the same?

> +/*
> + * 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;
I don't know much about PM, but I think it's not necessary to test state
if valid only accepts PM_SUSPEND_MEM and PM_SUSPEND_STANDBY.

> +	/* 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);
Why atomic memory?  Why don't you allocate the needed memory only when
you're going down?

> +	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 at 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
s/transistion/transition/

> +	   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);
This is usually plugged into set_irq_wake.  Isn't that generic enough
for you?

> +
> +/*
> + * 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 at 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 at 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
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
> 

-- 
Pengutronix e.K.                              | Uwe Kleine-König            |
Industrial Linux Solutions                    | http://www.pengutronix.de/  |



More information about the linux-arm-kernel mailing list