1
0
Fork 0

ARM: S5P64X0: Add Power Management support

Add suspend-to-ram support for SMDK6440/50

Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
hifive-unleashed-5.1
Abhilash Kesavan 2011-10-04 20:30:22 +09:00 committed by Kukjin Kim
parent e2e13621b2
commit 6b6844dd54
10 changed files with 472 additions and 1 deletions

View File

@ -2093,7 +2093,7 @@ menu "Power management options"
source "kernel/power/Kconfig"
config ARCH_SUSPEND_POSSIBLE
depends on !ARCH_S5P64X0 && !ARCH_S5PC100
depends on !ARCH_S5PC100
depends on CPU_ARM920T || CPU_ARM926T || CPU_SA1100 || \
CPU_V6 || CPU_V6K || CPU_V7 || CPU_XSC3 || CPU_XSCALE
def_bool y

View File

@ -11,6 +11,8 @@ config CPU_S5P6440
bool
select SAMSUNG_DMADEV
select S5P_HRT
select S5P_SLEEP if PM
select SAMSUNG_WAKEMASK if PM
help
Enable S5P6440 CPU support
@ -18,6 +20,8 @@ config CPU_S5P6450
bool
select SAMSUNG_DMADEV
select S5P_HRT
select S5P_SLEEP if PM
select SAMSUNG_WAKEMASK if PM
help
Enable S5P6450 CPU support

View File

@ -16,6 +16,7 @@ obj-$(CONFIG_ARCH_S5P64X0) += cpu.o init.o clock.o dma.o
obj-$(CONFIG_ARCH_S5P64X0) += setup-i2c0.o irq-eint.o
obj-$(CONFIG_CPU_S5P6440) += clock-s5p6440.o
obj-$(CONFIG_CPU_S5P6450) += clock-s5p6450.o
obj-$(CONFIG_PM) += pm.o irq-pm.o
# machine support

View File

@ -88,5 +88,6 @@
#define S5P_PA_UART5 S5P6450_PA_UART(5)
#define S5P_SZ_UART SZ_256
#define S3C_VA_UARTx(x) (S3C_VA_UART + ((x) * S3C_UART_OFFSET))
#endif /* __ASM_ARCH_MAP_H */

View File

@ -0,0 +1,117 @@
/* linux/arch/arm/mach-s5p64x0/include/mach/pm-core.h
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* S5P64X0 - PM core support for arch/arm/plat-samsung/pm.c
*
* Based on PM core support for S3C64XX by Ben Dooks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <mach/regs-gpio.h>
static inline void s3c_pm_debug_init_uart(void)
{
u32 tmp = __raw_readl(S5P64X0_CLK_GATE_PCLK);
/*
* As a note, since the S5P64X0 UARTs generally have multiple
* clock sources, we simply enable PCLK at the moment and hope
* that the resume settings for the UART are suitable for the
* use with PCLK.
*/
tmp |= S5P64X0_CLK_GATE_PCLK_UART0;
tmp |= S5P64X0_CLK_GATE_PCLK_UART1;
tmp |= S5P64X0_CLK_GATE_PCLK_UART2;
tmp |= S5P64X0_CLK_GATE_PCLK_UART3;
__raw_writel(tmp, S5P64X0_CLK_GATE_PCLK);
udelay(10);
}
static inline void s3c_pm_arch_prepare_irqs(void)
{
/* VIC should have already been taken care of */
/* clear any pending EINT0 interrupts */
__raw_writel(__raw_readl(S5P64X0_EINT0PEND), S5P64X0_EINT0PEND);
}
static inline void s3c_pm_arch_stop_clocks(void) { }
static inline void s3c_pm_arch_show_resume_irqs(void) { }
/*
* make these defines, we currently do not have any need to change
* the IRQ wake controls depending on the CPU we are running on
*/
#define s3c_irqwake_eintallow ((1 << 16) - 1)
#define s3c_irqwake_intallow (~0)
static inline void s3c_pm_arch_update_uart(void __iomem *regs,
struct pm_uart_save *save)
{
u32 ucon = __raw_readl(regs + S3C2410_UCON);
u32 ucon_clk = ucon & S3C6400_UCON_CLKMASK;
u32 save_clk = save->ucon & S3C6400_UCON_CLKMASK;
u32 new_ucon;
u32 delta;
/*
* S5P64X0 UART blocks only support level interrupts, so ensure that
* when we restore unused UART blocks we force the level interrupt
* settings.
*/
save->ucon |= S3C2410_UCON_TXILEVEL | S3C2410_UCON_RXILEVEL;
/*
* We have a constraint on changing the clock type of the UART
* between UCLKx and PCLK, so ensure that when we restore UCON
* that the CLK field is correctly modified if the bootloader
* has changed anything.
*/
if (ucon_clk != save_clk) {
new_ucon = save->ucon;
delta = ucon_clk ^ save_clk;
/*
* change from UCLKx => wrong PCLK,
* either UCLK can be tested for by a bit-test
* with UCLK0
*/
if (ucon_clk & S3C6400_UCON_UCLK0 &&
!(save_clk & S3C6400_UCON_UCLK0) &&
delta & S3C6400_UCON_PCLK2) {
new_ucon &= ~S3C6400_UCON_UCLK0;
} else if (delta == S3C6400_UCON_PCLK2) {
/*
* as a precaution, don't change from
* PCLK2 => PCLK or vice-versa
*/
new_ucon ^= S3C6400_UCON_PCLK2;
}
S3C_PMDBG("ucon change %04x => %04x (save=%04x)\n",
ucon, new_ucon, save->ucon);
save->ucon = new_ucon;
}
}
static inline void s3c_pm_restored_gpios(void)
{
/* ensure sleep mode has been cleared from the system */
__raw_writel(0, S5P64X0_SLPEN);
}
static inline void samsung_pm_saved_gpios(void)
{
/*
* turn on the sleep mode and keep it there, as it seems that during
* suspend the xCON registers get re-set and thus you can end up with
* problems between going to sleep and resuming.
*/
__raw_writel(S5P64X0_SLPEN_USE_xSLP, S5P64X0_SLPEN);
}

View File

@ -41,17 +41,50 @@
#define S5P6450_DPLL_CON S5P_CLKREG(0x50)
#define S5P6450_DPLL_CON_K S5P_CLKREG(0x54)
#define S5P64X0_AHB_CON0 S5P_CLKREG(0x100)
#define S5P64X0_CLK_SRC1 S5P_CLKREG(0x10C)
#define S5P64X0_SYS_ID S5P_CLKREG(0x118)
#define S5P64X0_SYS_OTHERS S5P_CLKREG(0x11C)
#define S5P64X0_PWR_CFG S5P_CLKREG(0x804)
#define S5P64X0_EINT_WAKEUP_MASK S5P_CLKREG(0x808)
#define S5P64X0_SLEEP_CFG S5P_CLKREG(0x818)
#define S5P64X0_PWR_STABLE S5P_CLKREG(0x828)
#define S5P64X0_OTHERS S5P_CLKREG(0x900)
#define S5P64X0_WAKEUP_STAT S5P_CLKREG(0x908)
#define S5P64X0_INFORM0 S5P_CLKREG(0xA00)
#define S5P64X0_CLKDIV0_HCLK_SHIFT (8)
#define S5P64X0_CLKDIV0_HCLK_MASK (0xF << S5P64X0_CLKDIV0_HCLK_SHIFT)
/* HCLK GATE Registers */
#define S5P64X0_CLK_GATE_HCLK1_FIMGVG (1 << 2)
#define S5P64X0_CLK_GATE_SCLK1_FIMGVG (1 << 2)
/* PCLK GATE Registers */
#define S5P64X0_CLK_GATE_PCLK_UART3 (1 << 4)
#define S5P64X0_CLK_GATE_PCLK_UART2 (1 << 3)
#define S5P64X0_CLK_GATE_PCLK_UART1 (1 << 2)
#define S5P64X0_CLK_GATE_PCLK_UART0 (1 << 1)
#define S5P64X0_PWR_CFG_MMC1_DISABLE (1 << 15)
#define S5P64X0_PWR_CFG_MMC0_DISABLE (1 << 14)
#define S5P64X0_PWR_CFG_RTC_TICK_DISABLE (1 << 11)
#define S5P64X0_PWR_CFG_RTC_ALRM_DISABLE (1 << 10)
#define S5P64X0_PWR_CFG_WFI_MASK (3 << 5)
#define S5P64X0_PWR_CFG_WFI_SLEEP (3 << 5)
#define S5P64X0_SLEEP_CFG_OSC_EN (1 << 0)
#define S5P64X0_PWR_STABLE_PWR_CNT_VAL4 (4 << 0)
#define S5P6450_OTHERS_DISABLE_INT (1 << 31)
#define S5P64X0_OTHERS_RET_UART (1 << 26)
#define S5P64X0_OTHERS_RET_MMC1 (1 << 25)
#define S5P64X0_OTHERS_RET_MMC0 (1 << 24)
#define S5P64X0_OTHERS_USB_SIG_MASK (1 << 16)
/* Compatibility defines */

View File

@ -37,15 +37,32 @@
#define S5P64X0_SPCON0 (S5P_VA_GPIO + 0x1A0)
#define S5P64X0_SPCON0_LCD_SEL_MASK (0x3 << 0)
#define S5P64X0_SPCON0_LCD_SEL_RGB (0x1 << 0)
#define S5P64X0_SPCON1 (S5P_VA_GPIO + 0x2B0)
#define S5P64X0_MEM0CONSLP0 (S5P_VA_GPIO + 0x1C0)
#define S5P64X0_MEM0CONSLP1 (S5P_VA_GPIO + 0x1C4)
#define S5P64X0_MEM0DRVCON (S5P_VA_GPIO + 0x1D0)
#define S5P64X0_MEM1DRVCON (S5P_VA_GPIO + 0x1D4)
#define S5P64X0_EINT12CON (S5P_VA_GPIO + 0x200)
#define S5P64X0_EINT12FLTCON (S5P_VA_GPIO + 0x220)
#define S5P64X0_EINT12MASK (S5P_VA_GPIO + 0x240)
/* External interrupt control registers for group0 */
#define EINT0CON0_OFFSET (0x900)
#define EINT0FLTCON0_OFFSET (0x910)
#define EINT0FLTCON1_OFFSET (0x914)
#define EINT0MASK_OFFSET (0x920)
#define EINT0PEND_OFFSET (0x924)
#define S5P64X0_EINT0CON0 (S5P_VA_GPIO + EINT0CON0_OFFSET)
#define S5P64X0_EINT0FLTCON0 (S5P_VA_GPIO + EINT0FLTCON0_OFFSET)
#define S5P64X0_EINT0FLTCON1 (S5P_VA_GPIO + EINT0FLTCON1_OFFSET)
#define S5P64X0_EINT0MASK (S5P_VA_GPIO + EINT0MASK_OFFSET)
#define S5P64X0_EINT0PEND (S5P_VA_GPIO + EINT0PEND_OFFSET)
#define S5P64X0_SLPEN (S5P_VA_GPIO + 0x930)
#define S5P64X0_SLPEN_USE_xSLP (1 << 0)
#endif /* __ASM_ARCH_REGS_GPIO_H */

View File

@ -20,6 +20,7 @@
#include <plat/cpu.h>
#include <plat/regs-irqtype.h>
#include <plat/gpio-cfg.h>
#include <plat/pm.h>
#include <mach/regs-gpio.h>
#include <mach/regs-clock.h>
@ -134,6 +135,7 @@ static int s5p64x0_alloc_gc(void)
ct->chip.irq_mask = irq_gc_mask_set_bit;
ct->chip.irq_unmask = irq_gc_mask_clr_bit;
ct->chip.irq_set_type = s5p64x0_irq_eint_set_type;
ct->chip.irq_set_wake = s3c_irqext_wake;
ct->regs.ack = EINT0PEND_OFFSET;
ct->regs.mask = EINT0MASK_OFFSET;
irq_setup_generic_chip(gc, IRQ_MSK(16), IRQ_GC_INIT_MASK_CACHE,

View File

@ -0,0 +1,92 @@
/* linux/arch/arm/mach-s5p64x0/irq-pm.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* S5P64X0 - Interrupt handling Power Management
*
* Based on arch/arm/mach-s3c64xx/irq-pm.c by Ben Dooks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/syscore_ops.h>
#include <linux/serial_core.h>
#include <linux/io.h>
#include <plat/regs-serial.h>
#include <plat/pm.h>
#include <mach/regs-gpio.h>
static struct sleep_save irq_save[] = {
SAVE_ITEM(S5P64X0_EINT0CON0),
SAVE_ITEM(S5P64X0_EINT0FLTCON0),
SAVE_ITEM(S5P64X0_EINT0FLTCON1),
SAVE_ITEM(S5P64X0_EINT0MASK),
};
static struct irq_grp_save {
u32 con;
u32 fltcon;
u32 mask;
} eint_grp_save[4];
static u32 irq_uart_mask[CONFIG_SERIAL_SAMSUNG_UARTS];
static int s5p64x0_irq_pm_suspend(void)
{
struct irq_grp_save *grp = eint_grp_save;
int i;
S3C_PMDBG("%s: suspending IRQs\n", __func__);
s3c_pm_do_save(irq_save, ARRAY_SIZE(irq_save));
for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++)
irq_uart_mask[i] = __raw_readl(S3C_VA_UARTx(i) + S3C64XX_UINTM);
for (i = 0; i < ARRAY_SIZE(eint_grp_save); i++, grp++) {
grp->con = __raw_readl(S5P64X0_EINT12CON + (i * 4));
grp->mask = __raw_readl(S5P64X0_EINT12MASK + (i * 4));
grp->fltcon = __raw_readl(S5P64X0_EINT12FLTCON + (i * 4));
}
return 0;
}
static void s5p64x0_irq_pm_resume(void)
{
struct irq_grp_save *grp = eint_grp_save;
int i;
S3C_PMDBG("%s: resuming IRQs\n", __func__);
s3c_pm_do_restore(irq_save, ARRAY_SIZE(irq_save));
for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++)
__raw_writel(irq_uart_mask[i], S3C_VA_UARTx(i) + S3C64XX_UINTM);
for (i = 0; i < ARRAY_SIZE(eint_grp_save); i++, grp++) {
__raw_writel(grp->con, S5P64X0_EINT12CON + (i * 4));
__raw_writel(grp->mask, S5P64X0_EINT12MASK + (i * 4));
__raw_writel(grp->fltcon, S5P64X0_EINT12FLTCON + (i * 4));
}
S3C_PMDBG("%s: IRQ configuration restored\n", __func__);
}
static struct syscore_ops s5p64x0_irq_syscore_ops = {
.suspend = s5p64x0_irq_pm_suspend,
.resume = s5p64x0_irq_pm_resume,
};
static int __init s5p64x0_syscore_init(void)
{
register_syscore_ops(&s5p64x0_irq_syscore_ops);
return 0;
}
core_initcall(s5p64x0_syscore_init);

View File

@ -0,0 +1,204 @@
/* linux/arch/arm/mach-s5p64x0/pm.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* S5P64X0 Power Management Support
*
* Based on arch/arm/mach-s3c64xx/pm.c by Ben Dooks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/io.h>
#include <plat/cpu.h>
#include <plat/pm.h>
#include <plat/regs-timer.h>
#include <plat/wakeup-mask.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
static struct sleep_save s5p64x0_core_save[] = {
SAVE_ITEM(S5P64X0_APLL_CON),
SAVE_ITEM(S5P64X0_MPLL_CON),
SAVE_ITEM(S5P64X0_EPLL_CON),
SAVE_ITEM(S5P64X0_EPLL_CON_K),
SAVE_ITEM(S5P64X0_CLK_SRC0),
SAVE_ITEM(S5P64X0_CLK_SRC1),
SAVE_ITEM(S5P64X0_CLK_DIV0),
SAVE_ITEM(S5P64X0_CLK_DIV1),
SAVE_ITEM(S5P64X0_CLK_DIV2),
SAVE_ITEM(S5P64X0_CLK_DIV3),
SAVE_ITEM(S5P64X0_CLK_GATE_MEM0),
SAVE_ITEM(S5P64X0_CLK_GATE_HCLK1),
SAVE_ITEM(S5P64X0_CLK_GATE_SCLK1),
};
static struct sleep_save s5p64x0_misc_save[] = {
SAVE_ITEM(S5P64X0_AHB_CON0),
SAVE_ITEM(S5P64X0_SPCON0),
SAVE_ITEM(S5P64X0_SPCON1),
SAVE_ITEM(S5P64X0_MEM0CONSLP0),
SAVE_ITEM(S5P64X0_MEM0CONSLP1),
SAVE_ITEM(S5P64X0_MEM0DRVCON),
SAVE_ITEM(S5P64X0_MEM1DRVCON),
SAVE_ITEM(S3C64XX_TINT_CSTAT),
};
/* DPLL is present only in S5P6450 */
static struct sleep_save s5p6450_core_save[] = {
SAVE_ITEM(S5P6450_DPLL_CON),
SAVE_ITEM(S5P6450_DPLL_CON_K),
};
void s3c_pm_configure_extint(void)
{
__raw_writel(s3c_irqwake_eintmask, S5P64X0_EINT_WAKEUP_MASK);
}
void s3c_pm_restore_core(void)
{
__raw_writel(0, S5P64X0_EINT_WAKEUP_MASK);
s3c_pm_do_restore_core(s5p64x0_core_save,
ARRAY_SIZE(s5p64x0_core_save));
if (soc_is_s5p6450())
s3c_pm_do_restore_core(s5p6450_core_save,
ARRAY_SIZE(s5p6450_core_save));
s3c_pm_do_restore(s5p64x0_misc_save, ARRAY_SIZE(s5p64x0_misc_save));
}
void s3c_pm_save_core(void)
{
s3c_pm_do_save(s5p64x0_misc_save, ARRAY_SIZE(s5p64x0_misc_save));
if (soc_is_s5p6450())
s3c_pm_do_save(s5p6450_core_save,
ARRAY_SIZE(s5p6450_core_save));
s3c_pm_do_save(s5p64x0_core_save, ARRAY_SIZE(s5p64x0_core_save));
}
static int s5p64x0_cpu_suspend(unsigned long arg)
{
unsigned long tmp = 0;
/*
* Issue the standby signal into the pm unit. Note, we
* issue a write-buffer drain just in case.
*/
asm("b 1f\n\t"
".align 5\n\t"
"1:\n\t"
"mcr p15, 0, %0, c7, c10, 5\n\t"
"mcr p15, 0, %0, c7, c10, 4\n\t"
"mcr p15, 0, %0, c7, c0, 4" : : "r" (tmp));
/* we should never get past here */
panic("sleep resumed to originator?");
}
/* mapping of interrupts to parts of the wakeup mask */
static struct samsung_wakeup_mask s5p64x0_wake_irqs[] = {
{ .irq = IRQ_RTC_ALARM, .bit = S5P64X0_PWR_CFG_RTC_ALRM_DISABLE, },
{ .irq = IRQ_RTC_TIC, .bit = S5P64X0_PWR_CFG_RTC_TICK_DISABLE, },
{ .irq = IRQ_HSMMC0, .bit = S5P64X0_PWR_CFG_MMC0_DISABLE, },
{ .irq = IRQ_HSMMC1, .bit = S5P64X0_PWR_CFG_MMC1_DISABLE, },
};
static void s5p64x0_pm_prepare(void)
{
u32 tmp;
samsung_sync_wakemask(S5P64X0_PWR_CFG,
s5p64x0_wake_irqs, ARRAY_SIZE(s5p64x0_wake_irqs));
/* store the resume address in INFORM0 register */
__raw_writel(virt_to_phys(s3c_cpu_resume), S5P64X0_INFORM0);
/* setup clock gating for FIMGVG block */
__raw_writel((__raw_readl(S5P64X0_CLK_GATE_HCLK1) | \
(S5P64X0_CLK_GATE_HCLK1_FIMGVG)), S5P64X0_CLK_GATE_HCLK1);
__raw_writel((__raw_readl(S5P64X0_CLK_GATE_SCLK1) | \
(S5P64X0_CLK_GATE_SCLK1_FIMGVG)), S5P64X0_CLK_GATE_SCLK1);
/* Configure the stabilization counter with wait time required */
__raw_writel(S5P64X0_PWR_STABLE_PWR_CNT_VAL4, S5P64X0_PWR_STABLE);
/* set WFI to SLEEP mode configuration */
tmp = __raw_readl(S5P64X0_SLEEP_CFG);
tmp &= ~(S5P64X0_SLEEP_CFG_OSC_EN);
__raw_writel(tmp, S5P64X0_SLEEP_CFG);
tmp = __raw_readl(S5P64X0_PWR_CFG);
tmp &= ~(S5P64X0_PWR_CFG_WFI_MASK);
tmp |= S5P64X0_PWR_CFG_WFI_SLEEP;
__raw_writel(tmp, S5P64X0_PWR_CFG);
/*
* set OTHERS register to disable interrupt before going to
* sleep. This bit is present only in S5P6450, it is reserved
* in S5P6440.
*/
if (soc_is_s5p6450()) {
tmp = __raw_readl(S5P64X0_OTHERS);
tmp |= S5P6450_OTHERS_DISABLE_INT;
__raw_writel(tmp, S5P64X0_OTHERS);
}
/* ensure previous wakeup state is cleared before sleeping */
__raw_writel(__raw_readl(S5P64X0_WAKEUP_STAT), S5P64X0_WAKEUP_STAT);
}
static int s5p64x0_pm_add(struct sys_device *sysdev)
{
pm_cpu_prep = s5p64x0_pm_prepare;
pm_cpu_sleep = s5p64x0_cpu_suspend;
pm_uart_udivslot = 1;
return 0;
}
static struct sysdev_driver s5p64x0_pm_driver = {
.add = s5p64x0_pm_add,
};
static __init int s5p64x0_pm_drvinit(void)
{
s3c_pm_init();
return sysdev_driver_register(&s5p64x0_sysclass, &s5p64x0_pm_driver);
}
arch_initcall(s5p64x0_pm_drvinit);
static void s5p64x0_pm_resume(void)
{
u32 tmp;
tmp = __raw_readl(S5P64X0_OTHERS);
tmp |= (S5P64X0_OTHERS_RET_MMC0 | S5P64X0_OTHERS_RET_MMC1 | \
S5P64X0_OTHERS_RET_UART);
__raw_writel(tmp , S5P64X0_OTHERS);
}
static struct syscore_ops s5p64x0_pm_syscore_ops = {
.resume = s5p64x0_pm_resume,
};
static __init int s5p64x0_pm_syscore_init(void)
{
register_syscore_ops(&s5p64x0_pm_syscore_ops);
return 0;
}
arch_initcall(s5p64x0_pm_syscore_init);