[ARM] S3C64XX: Initial support for PM (suspend to RAM)

Add the initial support for the S3C64XX based systems to use
suspend-to-RAM to sleep.

Includes basic debugging for use with the SMDK6410 usign the
LEDs on the baseboard.

Signed-off-by: Ben Dooks <ben-linux@fluff.org>
This commit is contained in:
Ben Dooks 2009-03-10 18:19:35 +00:00
parent 4b637dc231
commit bd117bd161
11 changed files with 479 additions and 2 deletions

View File

@ -0,0 +1,16 @@
/* linux/arch/arm/mach-s3c6400/include/mach/regs-clock.h
*
* Copyright 2008 Openmoko, Inc.
* Copyright 2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C64XX - clock register compatibility with s3c24xx
*
* 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 <plat/regs-clock.h>

View File

@ -71,6 +71,15 @@ config S3C2410_PM_DEBUG
Resume code. See <file:Documentation/arm/Samsung-S3C24XX/Suspend.txt>
for more information.
config S3C_PM_DEBUG_LED_SMDK
bool "SMDK LED suspend/resume debugging"
depends on PM && (MACH_SMDK6410)
help
Say Y here to enable the use of the SMDK LEDs on the baseboard
for debugging of the state of the suspend and resume process.
Note, this currently only works for S3C64XX based SMDK boards.
config S3C2410_PM_CHECK
bool "S3C2410 PM Suspend Memory CRC"
depends on PM && CRC32

View File

@ -127,6 +127,18 @@ extern void s3c_pm_dbg(const char *msg, ...);
#define S3C_PMDBG(fmt...) printk(KERN_DEBUG fmt)
#endif
#ifdef CONFIG_S3C_PM_DEBUG_LED_SMDK
/**
* s3c_pm_debug_smdkled() - Debug PM suspend/resume via SMDK Board LEDs
* @set: set bits for the state of the LEDs
* @clear: clear bits for the state of the LEDs.
*/
extern void s3c_pm_debug_smdkled(u32 set, u32 clear);
#else
static inline void s3c_pm_debug_smdkled(u32 set, u32 clear) { }
#endif /* CONFIG_S3C_PM_DEBUG_LED_SMDK */
/* suspend memory checking */
#ifdef CONFIG_S3C2410_PM_CHECK

View File

@ -21,11 +21,10 @@
#include <asm/cacheflush.h>
#include <mach/hardware.h>
#include <mach/map.h>
#include <plat/regs-serial.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <mach/regs-mem.h>
#include <mach/regs-irq.h>
#include <asm/irq.h>
@ -326,6 +325,9 @@ static int s3c_pm_enter(suspend_state_t state)
S3C_PMDBG("%s: post sleep, preparing to return\n", __func__);
/* LEDs should now be 1110 */
s3c_pm_debug_smdkled(1 << 1, 0);
s3c_pm_check_restore();
/* ok, let's return from sleep */

View File

@ -24,6 +24,11 @@ obj-y += gpiolib.o
obj-$(CONFIG_CPU_S3C6400_INIT) += s3c6400-init.o
obj-$(CONFIG_CPU_S3C6400_CLOCK) += s3c6400-clock.o
# PM support
obj-$(CONFIG_PM) += pm.o
obj-$(CONFIG_PM) += sleep.o
# Device setup
obj-$(CONFIG_S3C64XX_SETUP_I2C0) += setup-i2c0.o

View File

@ -157,6 +157,7 @@
#define S3C_EINT(x) ((x) + S3C_IRQ_EINT_BASE)
#define IRQ_EINT(x) S3C_EINT(x)
#define IRQ_EINT_BIT(x) ((x) - S3C_EINT(0))
/* Next the external interrupt groups. These are similar to the IRQ_EINT(x)
* that they are sourced from the GPIO pins but with a different scheme for

View File

@ -0,0 +1,98 @@
/* linux/arch/arm/plat-s3c64xx/include/plat/pm-core.h
*
* Copyright 2008 Openmoko, Inc.
* Copyright 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
* http://armlinux.simtec.co.uk/
*
* S3C64XX - PM core support for arch/arm/plat-s3c/pm.c
*
* 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 <plat/regs-gpio.h>
static inline void s3c_pm_debug_init_uart(void)
{
u32 tmp = __raw_readl(S3C_PCLK_GATE);
/* As a note, since the S3C64XX 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 |= S3C_CLKCON_PCLK_UART0;
tmp |= S3C_CLKCON_PCLK_UART1;
tmp |= S3C_CLKCON_PCLK_UART2;
tmp |= S3C_CLKCON_PCLK_UART3;
__raw_writel(tmp, S3C_PCLK_GATE);
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(S3C64XX_EINT0PEND), S3C64XX_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 << 28) - 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;
/* S3C64XX UART blocks only support level interrupts, so ensure that
* when we restore unused UART blocks we force the level interrupt
* settigs. */
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 an 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;
}
}

View File

@ -32,6 +32,7 @@
#define S3C_HCLK_GATE S3C_CLKREG(0x30)
#define S3C_PCLK_GATE S3C_CLKREG(0x34)
#define S3C_SCLK_GATE S3C_CLKREG(0x38)
#define S3C_MEM0_GATE S3C_CLKREG(0x3C)
/* CLKDIV0 */
#define S3C6400_CLKDIV0_MFC_MASK (0xf << 28)

View File

@ -14,6 +14,7 @@
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/sysdev.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/io.h>
@ -26,6 +27,7 @@
#include <mach/map.h>
#include <plat/cpu.h>
#include <plat/pm.h>
#define eint_offset(irq) ((irq) - IRQ_EINT(0))
#define eint_irq_to_bit(irq) (1 << eint_offset(irq))
@ -134,6 +136,7 @@ static struct irq_chip s3c_irq_eint = {
.mask_ack = s3c_irq_eint_maskack,
.ack = s3c_irq_eint_ack,
.set_type = s3c_irq_eint_set_type,
.set_wake = s3c_irqext_wake,
};
/* s3c_irq_demux_eint

186
arch/arm/plat-s3c64xx/pm.c Normal file
View File

@ -0,0 +1,186 @@
/* linux/arch/arm/plat-s3c64xx/pm.c
*
* Copyright 2008 Openmoko, Inc.
* Copyright 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
* http://armlinux.simtec.co.uk/
*
* S3C64XX CPU PM support.
*
* 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/init.h>
#include <linux/suspend.h>
#include <linux/serial_core.h>
#include <linux/io.h>
#include <mach/map.h>
#include <plat/pm.h>
#include <plat/regs-sys.h>
#include <plat/regs-gpio.h>
#include <plat/regs-clock.h>
#include <plat/regs-syscon-power.h>
#include <plat/regs-gpio-memport.h>
#ifdef CONFIG_S3C_PM_DEBUG_LED_SMDK
#include <plat/gpio-bank-n.h>
void s3c_pm_debug_smdkled(u32 set, u32 clear)
{
unsigned long flags;
u32 reg;
local_irq_save(flags);
reg = __raw_readl(S3C64XX_GPNCON);
reg &= ~(S3C64XX_GPN_CONMASK(12) | S3C64XX_GPN_CONMASK(13) |
S3C64XX_GPN_CONMASK(14) | S3C64XX_GPN_CONMASK(15));
reg |= S3C64XX_GPN_OUTPUT(12) | S3C64XX_GPN_OUTPUT(13) |
S3C64XX_GPN_OUTPUT(14) | S3C64XX_GPN_OUTPUT(15);
__raw_writel(reg, S3C64XX_GPNCON);
reg = __raw_readl(S3C64XX_GPNDAT);
reg &= ~(clear << 12);
reg |= set << 12;
__raw_writel(reg, S3C64XX_GPNDAT);
local_irq_restore(flags);
}
#endif
static struct sleep_save core_save[] = {
SAVE_ITEM(S3C_APLL_LOCK),
SAVE_ITEM(S3C_MPLL_LOCK),
SAVE_ITEM(S3C_EPLL_LOCK),
SAVE_ITEM(S3C_CLK_SRC),
SAVE_ITEM(S3C_CLK_DIV0),
SAVE_ITEM(S3C_CLK_DIV1),
SAVE_ITEM(S3C_CLK_DIV2),
SAVE_ITEM(S3C_CLK_OUT),
SAVE_ITEM(S3C_HCLK_GATE),
SAVE_ITEM(S3C_PCLK_GATE),
SAVE_ITEM(S3C_SCLK_GATE),
SAVE_ITEM(S3C_MEM0_GATE),
SAVE_ITEM(S3C_EPLL_CON1),
SAVE_ITEM(S3C_EPLL_CON0),
SAVE_ITEM(S3C64XX_MEM0DRVCON),
SAVE_ITEM(S3C64XX_MEM1DRVCON),
#ifndef CONFIG_CPU_FREQ
SAVE_ITEM(S3C_APLL_CON),
SAVE_ITEM(S3C_MPLL_CON),
#endif
};
static struct sleep_save misc_save[] = {
SAVE_ITEM(S3C64XX_AHB_CON0),
SAVE_ITEM(S3C64XX_AHB_CON1),
SAVE_ITEM(S3C64XX_AHB_CON2),
SAVE_ITEM(S3C64XX_SPCON),
SAVE_ITEM(S3C64XX_MEM0CONSTOP),
SAVE_ITEM(S3C64XX_MEM1CONSTOP),
SAVE_ITEM(S3C64XX_MEM0CONSLP0),
SAVE_ITEM(S3C64XX_MEM0CONSLP1),
SAVE_ITEM(S3C64XX_MEM1CONSLP),
};
void s3c_pm_configure_extint(void)
{
__raw_writel(s3c_irqwake_eintmask, S3C64XX_EINT_MASK);
}
void s3c_pm_save_gpios(void)
{
/* currently, unless the bootloader does something really stupid
* the gpio blocks should be maintained over their sleep.
*/
}
void s3c_pm_restore_gpios(void)
{
}
void s3c_pm_restore_core(void)
{
__raw_writel(0, S3C64XX_EINT_MASK);
s3c_pm_debug_smdkled(1 << 2, 0);
s3c_pm_do_restore_core(core_save, ARRAY_SIZE(core_save));
s3c_pm_do_restore(misc_save, ARRAY_SIZE(misc_save));
}
void s3c_pm_save_core(void)
{
s3c_pm_do_save(misc_save, ARRAY_SIZE(misc_save));
s3c_pm_do_save(core_save, ARRAY_SIZE(core_save));
}
/* since both s3c6400 and s3c6410 share the same sleep pm calls, we
* put the per-cpu code in here until any new cpu comes along and changes
* this.
*/
#include <plat/regs-gpio.h>
static void s3c64xx_cpu_suspend(void)
{
unsigned long tmp;
/* set our standby method to sleep */
tmp = __raw_readl(S3C64XX_PWR_CFG);
tmp &= ~S3C64XX_PWRCFG_CFG_WFI_MASK;
tmp |= S3C64XX_PWRCFG_CFG_WFI_SLEEP;
__raw_writel(tmp, S3C64XX_PWR_CFG);
/* clear any old wakeup */
__raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT),
S3C64XX_WAKEUP_STAT);
/* set the LED state to 0110 over sleep */
s3c_pm_debug_smdkled(3 << 1, 0xf);
/* issue the standby signal into the pm unit. Note, we
* issue a write-buffer drain just in case */
tmp = 0;
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?");
}
static void s3c64xx_pm_prepare(void)
{
/* store address of resume. */
__raw_writel(virt_to_phys(s3c_cpu_resume), S3C64XX_INFORM0);
/* ensure previous wakeup state is cleared before sleeping */
__raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT);
}
static int s3c64xx_pm_init(void)
{
pm_cpu_prep = s3c64xx_pm_prepare;
pm_cpu_sleep = s3c64xx_cpu_suspend;
pm_uart_udivslot = 1;
return 0;
}
arch_initcall(s3c64xx_pm_init);

View File

@ -0,0 +1,144 @@
/* linux/0arch/arm/plat-s3c64xx/sleep.S
*
* Copyright 2008 Openmoko, Inc.
* Copyright 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
* http://armlinux.simtec.co.uk/
*
* S3C64XX CPU sleep code
*
* 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/linkage.h>
#include <asm/assembler.h>
#include <mach/map.h>
#undef S3C64XX_VA_GPIO
#define S3C64XX_VA_GPIO (0x0)
#include <plat/regs-gpio.h>
#include <plat/gpio-bank-n.h>
#define LL_UART (S3C_PA_UART + (0x400 * CONFIG_S3C_LOWLEVEL_UART_PORT))
.text
/* s3c_cpu_save
*
* Save enough processor state to allow the restart of the pm.c
* code after resume.
*
* entry:
* r0 = pointer to the save block
*/
ENTRY(s3c_cpu_save)
stmfd sp!, { r4 - r12, lr }
mrc p15, 0, r4, c13, c0, 0 @ FCSE/PID
mrc p15, 0, r5, c3, c0, 0 @ Domain ID
mrc p15, 0, r6, c2, c0, 0 @ Translation Table BASE0
mrc p15, 0, r7, c2, c0, 1 @ Translation Table BASE1
mrc p15, 0, r8, c2, c0, 2 @ Translation Table Control
mrc p15, 0, r9, c1, c0, 0 @ Control register
mrc p15, 0, r10, c1, c0, 1 @ Auxiliary control register
mrc p15, 0, r11, c1, c0, 2 @ Co-processor access controls
stmia r0, { r4 - r13 } @ Save CP registers and SP
@@ save our state to ram
bl s3c_pm_cb_flushcache
@@ call final suspend code
ldr r0, =pm_cpu_sleep
ldr pc, [r0]
@@ return to the caller, after the MMU is turned on.
@@ restore the last bits of the stack and return.
resume_with_mmu:
ldmfd sp!, { r4 - r12, pc } @ return, from sp from s3c_cpu_save
.data
/* the next bit is code, but it requires easy access to the
* s3c_sleep_save_phys data before the MMU is switched on, so
* we store the code that needs this variable in the .data where
* the value can be written to (the .text segment is RO).
*/
.global s3c_sleep_save_phys
s3c_sleep_save_phys:
.word 0
/* Sleep magic, the word before the resume entry point so that the
* bootloader can check for a resumeable image. */
.word 0x2bedf00d
/* s3c_cpu_reusme
*
* This is the entry point, stored by whatever method the bootloader
* requires to get the kernel runnign again. This code expects to be
* entered with no caches live and the MMU disabled. It will then
* restore the MMU and other basic CP registers saved and restart
* the kernel C code to finish the resume code.
*/
ENTRY(s3c_cpu_resume)
msr cpsr_c, #PSR_I_BIT | PSR_F_BIT | SVC_MODE
ldr r2, =LL_UART /* for debug */
#ifdef CONFIG_S3C_PM_DEBUG_LED_SMDK
/* Initialise the GPIO state if we are debugging via the SMDK LEDs,
* as the uboot version supplied resets these to inputs during the
* resume checks.
*/
ldr r3, =S3C64XX_PA_GPIO
ldr r0, [ r3, #S3C64XX_GPNCON ]
bic r0, r0, #(S3C64XX_GPN_CONMASK(12) | S3C64XX_GPN_CONMASK(13) | \
S3C64XX_GPN_CONMASK(14) | S3C64XX_GPN_CONMASK(15))
orr r0, r0, #(S3C64XX_GPN_OUTPUT(12) | S3C64XX_GPN_OUTPUT(13) | \
S3C64XX_GPN_OUTPUT(14) | S3C64XX_GPN_OUTPUT(15))
str r0, [ r3, #S3C64XX_GPNCON ]
ldr r0, [ r3, #S3C64XX_GPNDAT ]
bic r0, r0, #0xf << 12 @ GPN12..15
orr r0, r0, #1 << 15 @ GPN15
str r0, [ r3, #S3C64XX_GPNDAT ]
#endif
/* __v6_setup from arch/arm/mm/proc-v6.S, ensure that the caches
* are thoroughly cleaned just in case the bootloader didn't do it
* for us. */
mov r0, #0
mcr p15, 0, r0, c7, c14, 0 @ clean+invalidate D cache
mcr p15, 0, r0, c7, c5, 0 @ invalidate I cache
mcr p15, 0, r0, c7, c15, 0 @ clean+invalidate cache
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
@@mcr p15, 0, r0, c8, c7, 0 @ invalidate I + D TLBs
@@mcr p15, 0, r0, c7, c7, 0 @ Invalidate I + D caches
ldr r0, s3c_sleep_save_phys
ldmia r0, { r4 - r13 }
mcr p15, 0, r4, c13, c0, 0 @ FCSE/PID
mcr p15, 0, r5, c3, c0, 0 @ Domain ID
mcr p15, 0, r6, c2, c0, 0 @ Translation Table BASE0
mcr p15, 0, r7, c2, c0, 1 @ Translation Table BASE1
mcr p15, 0, r8, c2, c0, 2 @ Translation Table Control
mcr p15, 0, r10, c1, c0, 1 @ Auxiliary control register
mov r0, #0 @ restore copro access controls
mcr p15, 0, r11, c1, c0, 2 @ Co-processor access controls
mcr p15, 0, r0, c7, c5, 4
ldr r2, =resume_with_mmu
mcr p15, 0, r9, c1, c0, 0 /* turn mmu back on */
nop
mov pc, r2 /* jump back */
.end