diff options
author | Ben Dooks <ben-linux@fluff.org> | 2006-09-19 04:51:32 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2006-09-25 05:25:30 -0400 |
commit | 0033a2f0d028400ad04588efbd5740c73f0eb740 (patch) | |
tree | 16ea22f882eac68b61041dc7f3fdc13313fdfa7e /arch/arm/mach-s3c2410 | |
parent | 34348012d6b43eca5e241fe97381420d5758866c (diff) |
[ARM] 3803/2: S3C24XX: PM split S3C2410 out of core pm
Remove the S3C2410 specific items out of the
core PM code. Add sysdev driver for all the
S3C24XX series that used the S3C2410 PM code.
Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/mach-s3c2410')
-rw-r--r-- | arch/arm/mach-s3c2410/Kconfig | 9 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/pm.c | 30 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/pm.h | 5 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/s3c2410-pm.c | 111 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/s3c2410-sleep.S | 68 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/sleep.S | 33 |
7 files changed, 209 insertions, 49 deletions
diff --git a/arch/arm/mach-s3c2410/Kconfig b/arch/arm/mach-s3c2410/Kconfig index 61a359c4aa47..e555bc5f3029 100644 --- a/arch/arm/mach-s3c2410/Kconfig +++ b/arch/arm/mach-s3c2410/Kconfig | |||
@@ -133,6 +133,12 @@ config S3C2410_CLOCK | |||
133 | help | 133 | help |
134 | Clock code for the S3C2410, and similar processors | 134 | Clock code for the S3C2410, and similar processors |
135 | 135 | ||
136 | config S3C2410_PM | ||
137 | bool | ||
138 | depends on CONFIG_PM | ||
139 | help | ||
140 | Power Management code common to S3C2410 and better | ||
141 | |||
136 | config CPU_S3C2410_DMA | 142 | config CPU_S3C2410_DMA |
137 | bool | 143 | bool |
138 | depends on S3C2410_DMA && (CPU_S3C2410 || CPU_S3C2442) | 144 | depends on S3C2410_DMA && (CPU_S3C2410 || CPU_S3C2442) |
@@ -144,6 +150,7 @@ config CPU_S3C2410 | |||
144 | bool | 150 | bool |
145 | depends on ARCH_S3C2410 | 151 | depends on ARCH_S3C2410 |
146 | select S3C2410_CLOCK | 152 | select S3C2410_CLOCK |
153 | select S3C2410_PM | ||
147 | help | 154 | help |
148 | Support for S3C2410 and S3C2410A family from the S3C24XX line | 155 | Support for S3C2410 and S3C2410A family from the S3C24XX line |
149 | of Samsung Mobile CPUs. | 156 | of Samsung Mobile CPUs. |
@@ -172,6 +179,7 @@ config CPU_S3C2440 | |||
172 | bool | 179 | bool |
173 | depends on ARCH_S3C2410 | 180 | depends on ARCH_S3C2410 |
174 | select S3C2410_CLOCK | 181 | select S3C2410_CLOCK |
182 | select S3C2410_PM | ||
175 | select CPU_S3C244X | 183 | select CPU_S3C244X |
176 | help | 184 | help |
177 | Support for S3C2440 Samsung Mobile CPU based systems. | 185 | Support for S3C2440 Samsung Mobile CPU based systems. |
@@ -180,6 +188,7 @@ config CPU_S3C2442 | |||
180 | bool | 188 | bool |
181 | depends on ARCH_S3C2420 | 189 | depends on ARCH_S3C2420 |
182 | select S3C2410_CLOCK | 190 | select S3C2410_CLOCK |
191 | select S3C2410_PM | ||
183 | select CPU_S3C244X | 192 | select CPU_S3C244X |
184 | help | 193 | help |
185 | Support for S3C2442 Samsung Mobile CPU based systems. | 194 | Support for S3C2442 Samsung Mobile CPU based systems. |
diff --git a/arch/arm/mach-s3c2410/Makefile b/arch/arm/mach-s3c2410/Makefile index 0d87a9de0034..758f7e13b488 100644 --- a/arch/arm/mach-s3c2410/Makefile +++ b/arch/arm/mach-s3c2410/Makefile | |||
@@ -23,6 +23,8 @@ obj-$(CONFIG_CPU_S3C2400) += s3c2400-gpio.o | |||
23 | obj-$(CONFIG_CPU_S3C2410) += s3c2410.o | 23 | obj-$(CONFIG_CPU_S3C2410) += s3c2410.o |
24 | obj-$(CONFIG_CPU_S3C2410) += s3c2410-gpio.o | 24 | obj-$(CONFIG_CPU_S3C2410) += s3c2410-gpio.o |
25 | obj-$(CONFIG_CPU_S3C2410) += s3c2410-irq.o | 25 | obj-$(CONFIG_CPU_S3C2410) += s3c2410-irq.o |
26 | |||
27 | obj-$(CONFIG_S3C2410_PM) += s3c2410-pm.o s3c2410-sleep.o | ||
26 | obj-$(CONFIG_CPU_S3C2410_DMA) += s3c2410-dma.o | 28 | obj-$(CONFIG_CPU_S3C2410_DMA) += s3c2410-dma.o |
27 | 29 | ||
28 | # Power Management support | 30 | # Power Management support |
diff --git a/arch/arm/mach-s3c2410/pm.c b/arch/arm/mach-s3c2410/pm.c index 46dedd37f438..9402583a1294 100644 --- a/arch/arm/mach-s3c2410/pm.c +++ b/arch/arm/mach-s3c2410/pm.c | |||
@@ -142,7 +142,7 @@ static struct sleep_save uart_save[] = { | |||
142 | 142 | ||
143 | extern void printascii(const char *); | 143 | extern void printascii(const char *); |
144 | 144 | ||
145 | static void pm_dbg(const char *fmt, ...) | 145 | void pm_dbg(const char *fmt, ...) |
146 | { | 146 | { |
147 | va_list va; | 147 | va_list va; |
148 | char buff[256]; | 148 | char buff[256]; |
@@ -486,6 +486,9 @@ static void s3c2410_pm_configure_extint(void) | |||
486 | } | 486 | } |
487 | } | 487 | } |
488 | 488 | ||
489 | void (*pm_cpu_prep)(void); | ||
490 | void (*pm_cpu_sleep)(void); | ||
491 | |||
489 | #define any_allowed(mask, allow) (((mask) & (allow)) != (allow)) | 492 | #define any_allowed(mask, allow) (((mask) & (allow)) != (allow)) |
490 | 493 | ||
491 | /* s3c2410_pm_enter | 494 | /* s3c2410_pm_enter |
@@ -496,7 +499,6 @@ static void s3c2410_pm_configure_extint(void) | |||
496 | static int s3c2410_pm_enter(suspend_state_t state) | 499 | static int s3c2410_pm_enter(suspend_state_t state) |
497 | { | 500 | { |
498 | unsigned long regs_save[16]; | 501 | unsigned long regs_save[16]; |
499 | unsigned long tmp; | ||
500 | 502 | ||
501 | /* ensure the debug is initialised (if enabled) */ | 503 | /* ensure the debug is initialised (if enabled) */ |
502 | 504 | ||
@@ -504,6 +506,11 @@ static int s3c2410_pm_enter(suspend_state_t state) | |||
504 | 506 | ||
505 | DBG("s3c2410_pm_enter(%d)\n", state); | 507 | DBG("s3c2410_pm_enter(%d)\n", state); |
506 | 508 | ||
509 | if (pm_cpu_prep == NULL || pm_cpu_sleep == NULL) { | ||
510 | printk(KERN_ERR PFX "error: no cpu sleep functions set\n"); | ||
511 | return -EINVAL; | ||
512 | } | ||
513 | |||
507 | if (state != PM_SUSPEND_MEM) { | 514 | if (state != PM_SUSPEND_MEM) { |
508 | printk(KERN_ERR PFX "error: only PM_SUSPEND_MEM supported\n"); | 515 | printk(KERN_ERR PFX "error: only PM_SUSPEND_MEM supported\n"); |
509 | return -EINVAL; | 516 | return -EINVAL; |
@@ -531,13 +538,6 @@ static int s3c2410_pm_enter(suspend_state_t state) | |||
531 | 538 | ||
532 | DBG("s3c2410_sleep_save_phys=0x%08lx\n", s3c2410_sleep_save_phys); | 539 | DBG("s3c2410_sleep_save_phys=0x%08lx\n", s3c2410_sleep_save_phys); |
533 | 540 | ||
534 | /* ensure at least GESTATUS3 has the resume address */ | ||
535 | |||
536 | __raw_writel(virt_to_phys(s3c2410_cpu_resume), S3C2410_GSTATUS3); | ||
537 | |||
538 | DBG("GSTATUS3 0x%08x\n", __raw_readl(S3C2410_GSTATUS3)); | ||
539 | DBG("GSTATUS4 0x%08x\n", __raw_readl(S3C2410_GSTATUS4)); | ||
540 | |||
541 | /* save all necessary core registers not covered by the drivers */ | 541 | /* save all necessary core registers not covered by the drivers */ |
542 | 542 | ||
543 | s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save)); | 543 | s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save)); |
@@ -558,6 +558,10 @@ static int s3c2410_pm_enter(suspend_state_t state) | |||
558 | 558 | ||
559 | __raw_writel(__raw_readl(S3C2410_EINTPEND), S3C2410_EINTPEND); | 559 | __raw_writel(__raw_readl(S3C2410_EINTPEND), S3C2410_EINTPEND); |
560 | 560 | ||
561 | /* call cpu specific preperation */ | ||
562 | |||
563 | pm_cpu_prep(); | ||
564 | |||
561 | /* flush cache back to ram */ | 565 | /* flush cache back to ram */ |
562 | 566 | ||
563 | flush_cache_all(); | 567 | flush_cache_all(); |
@@ -574,19 +578,13 @@ static int s3c2410_pm_enter(suspend_state_t state) | |||
574 | 578 | ||
575 | if (s3c2410_cpu_save(regs_save) == 0) { | 579 | if (s3c2410_cpu_save(regs_save) == 0) { |
576 | flush_cache_all(); | 580 | flush_cache_all(); |
577 | s3c2410_cpu_suspend(); | 581 | pm_cpu_sleep(); |
578 | } | 582 | } |
579 | 583 | ||
580 | /* restore the cpu state */ | 584 | /* restore the cpu state */ |
581 | 585 | ||
582 | cpu_init(); | 586 | cpu_init(); |
583 | 587 | ||
584 | /* unset the return-from-sleep flag, to ensure reset */ | ||
585 | |||
586 | tmp = __raw_readl(S3C2410_GSTATUS2); | ||
587 | tmp &= S3C2410_GSTATUS2_OFFRESET; | ||
588 | __raw_writel(tmp, S3C2410_GSTATUS2); | ||
589 | |||
590 | /* restore the system state */ | 588 | /* restore the system state */ |
591 | 589 | ||
592 | s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); | 590 | s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); |
diff --git a/arch/arm/mach-s3c2410/pm.h b/arch/arm/mach-s3c2410/pm.h index fa8e237cfee8..ffe197a119fb 100644 --- a/arch/arm/mach-s3c2410/pm.h +++ b/arch/arm/mach-s3c2410/pm.h | |||
@@ -34,6 +34,11 @@ extern unsigned long s3c_irqwake_eintmask; | |||
34 | extern unsigned long s3c_irqwake_intallow; | 34 | extern unsigned long s3c_irqwake_intallow; |
35 | extern unsigned long s3c_irqwake_eintallow; | 35 | extern unsigned long s3c_irqwake_eintallow; |
36 | 36 | ||
37 | /* per-cpu sleep functions */ | ||
38 | |||
39 | extern void (*pm_cpu_prep)(void); | ||
40 | extern void (*pm_cpu_sleep)(void); | ||
41 | |||
37 | /* Flags for PM Control */ | 42 | /* Flags for PM Control */ |
38 | 43 | ||
39 | extern unsigned long s3c_pm_flags; | 44 | extern unsigned long s3c_pm_flags; |
diff --git a/arch/arm/mach-s3c2410/s3c2410-pm.c b/arch/arm/mach-s3c2410/s3c2410-pm.c new file mode 100644 index 000000000000..3080d25a12de --- /dev/null +++ b/arch/arm/mach-s3c2410/s3c2410-pm.c | |||
@@ -0,0 +1,111 @@ | |||
1 | /* linux/arch/arm/mach-s3c2410/s3c2410-pm.c | ||
2 | * | ||
3 | * Copyright (c) 2006 Simtec Electronics | ||
4 | * Ben Dooks <ben@simtec.co.uk> | ||
5 | * | ||
6 | * S3C2410 (and compatible) Power Manager (Suspend-To-RAM) support | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | */ | ||
22 | |||
23 | #include <linux/init.h> | ||
24 | #include <linux/suspend.h> | ||
25 | #include <linux/errno.h> | ||
26 | #include <linux/time.h> | ||
27 | #include <linux/sysdev.h> | ||
28 | |||
29 | #include <asm/hardware.h> | ||
30 | #include <asm/io.h> | ||
31 | |||
32 | #include <asm/arch/regs-gpio.h> | ||
33 | |||
34 | #include "cpu.h" | ||
35 | #include "pm.h" | ||
36 | |||
37 | #ifdef CONFIG_S3C2410_PM_DEBUG | ||
38 | extern void pm_dbg(const char *fmt, ...); | ||
39 | #define DBG(fmt...) pm_dbg(fmt) | ||
40 | #else | ||
41 | #define DBG(fmt...) printk(KERN_DEBUG fmt) | ||
42 | #endif | ||
43 | |||
44 | static void s3c2410_pm_prepare(void) | ||
45 | { | ||
46 | /* ensure at least GSTATUS3 has the resume address */ | ||
47 | |||
48 | __raw_writel(virt_to_phys(s3c2410_cpu_resume), S3C2410_GSTATUS3); | ||
49 | |||
50 | DBG("GSTATUS3 0x%08x\n", __raw_readl(S3C2410_GSTATUS3)); | ||
51 | DBG("GSTATUS4 0x%08x\n", __raw_readl(S3C2410_GSTATUS4)); | ||
52 | } | ||
53 | |||
54 | int s3c2410_pm_resume(struct sys_device *dev) | ||
55 | { | ||
56 | unsigned long tmp; | ||
57 | |||
58 | /* unset the return-from-sleep flag, to ensure reset */ | ||
59 | |||
60 | tmp = __raw_readl(S3C2410_GSTATUS2); | ||
61 | tmp &= S3C2410_GSTATUS2_OFFRESET; | ||
62 | __raw_writel(tmp, S3C2410_GSTATUS2); | ||
63 | |||
64 | return 0; | ||
65 | } | ||
66 | |||
67 | static int s3c2410_pm_add(struct sys_device *dev) | ||
68 | { | ||
69 | pm_cpu_prep = s3c2410_pm_prepare; | ||
70 | pm_cpu_sleep = s3c2410_cpu_suspend; | ||
71 | |||
72 | return 0; | ||
73 | } | ||
74 | |||
75 | static struct sysdev_driver s3c2410_pm_driver = { | ||
76 | .add = s3c2410_pm_add, | ||
77 | .resume = s3c2410_pm_resume, | ||
78 | }; | ||
79 | |||
80 | /* register ourselves */ | ||
81 | |||
82 | static int __init s3c2410_pm_drvinit(void) | ||
83 | { | ||
84 | return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_pm_driver); | ||
85 | } | ||
86 | |||
87 | arch_initcall(s3c2410_pm_drvinit); | ||
88 | |||
89 | static struct sysdev_driver s3c2440_pm_driver = { | ||
90 | .add = s3c2410_pm_add, | ||
91 | .resume = s3c2410_pm_resume, | ||
92 | }; | ||
93 | |||
94 | static int __init s3c2440_pm_drvinit(void) | ||
95 | { | ||
96 | return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_pm_driver); | ||
97 | } | ||
98 | |||
99 | arch_initcall(s3c2440_pm_drvinit); | ||
100 | |||
101 | static struct sysdev_driver s3c2442_pm_driver = { | ||
102 | .add = s3c2410_pm_add, | ||
103 | .resume = s3c2410_pm_resume, | ||
104 | }; | ||
105 | |||
106 | static int __init s3c2442_pm_drvinit(void) | ||
107 | { | ||
108 | return sysdev_driver_register(&s3c2442_sysclass, &s3c2442_pm_driver); | ||
109 | } | ||
110 | |||
111 | arch_initcall(s3c2442_pm_drvinit); | ||
diff --git a/arch/arm/mach-s3c2410/s3c2410-sleep.S b/arch/arm/mach-s3c2410/s3c2410-sleep.S new file mode 100644 index 000000000000..9179a1024588 --- /dev/null +++ b/arch/arm/mach-s3c2410/s3c2410-sleep.S | |||
@@ -0,0 +1,68 @@ | |||
1 | /* linux/arch/arm/mach-s3c2410/s3c2410-sleep.S | ||
2 | * | ||
3 | * Copyright (c) 2004 Simtec Electronics | ||
4 | * Ben Dooks <ben@simtec.co.uk> | ||
5 | * | ||
6 | * S3C2410 Power Manager (Suspend-To-RAM) support | ||
7 | * | ||
8 | * Based on PXA/SA1100 sleep code by: | ||
9 | * Nicolas Pitre, (c) 2002 Monta Vista Software Inc | ||
10 | * Cliff Brake, (c) 2001 | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License as published by | ||
14 | * the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | */ | ||
26 | |||
27 | #include <linux/linkage.h> | ||
28 | #include <asm/assembler.h> | ||
29 | #include <asm/hardware.h> | ||
30 | #include <asm/arch/map.h> | ||
31 | |||
32 | #include <asm/arch/regs-gpio.h> | ||
33 | #include <asm/arch/regs-clock.h> | ||
34 | #include <asm/arch/regs-mem.h> | ||
35 | #include <asm/arch/regs-serial.h> | ||
36 | |||
37 | /* s3c2410_cpu_suspend | ||
38 | * | ||
39 | * put the cpu into sleep mode | ||
40 | */ | ||
41 | |||
42 | ENTRY(s3c2410_cpu_suspend) | ||
43 | @@ prepare cpu to sleep | ||
44 | |||
45 | ldr r4, =S3C2410_REFRESH | ||
46 | ldr r5, =S3C24XX_MISCCR | ||
47 | ldr r6, =S3C2410_CLKCON | ||
48 | ldr r7, [ r4 ] @ get REFRESH (and ensure in TLB) | ||
49 | ldr r8, [ r5 ] @ get MISCCR (and ensure in TLB) | ||
50 | ldr r9, [ r6 ] @ get CLKCON (and ensure in TLB) | ||
51 | |||
52 | orr r7, r7, #S3C2410_REFRESH_SELF @ SDRAM sleep command | ||
53 | orr r8, r8, #S3C2410_MISCCR_SDSLEEP @ SDRAM power-down signals | ||
54 | orr r9, r9, #S3C2410_CLKCON_POWER @ power down command | ||
55 | |||
56 | teq pc, #0 @ first as a trial-run to load cache | ||
57 | bl s3c2410_do_sleep | ||
58 | teq r0, r0 @ now do it for real | ||
59 | b s3c2410_do_sleep @ | ||
60 | |||
61 | @@ align next bit of code to cache line | ||
62 | .align 8 | ||
63 | s3c2410_do_sleep: | ||
64 | streq r7, [ r4 ] @ SDRAM sleep command | ||
65 | streq r8, [ r5 ] @ SDRAM power-down config | ||
66 | streq r9, [ r6 ] @ CPU sleep | ||
67 | 1: beq 1b | ||
68 | mov pc, r14 | ||
diff --git a/arch/arm/mach-s3c2410/sleep.S b/arch/arm/mach-s3c2410/sleep.S index e977aa1ffe18..2018c2e1dcc5 100644 --- a/arch/arm/mach-s3c2410/sleep.S +++ b/arch/arm/mach-s3c2410/sleep.S | |||
@@ -75,39 +75,6 @@ ENTRY(s3c2410_cpu_save) | |||
75 | mov r0, #0 | 75 | mov r0, #0 |
76 | ldmfd sp, { r4 - r12, pc } | 76 | ldmfd sp, { r4 - r12, pc } |
77 | 77 | ||
78 | /* s3c2410_cpu_suspend | ||
79 | * | ||
80 | * put the cpu into sleep mode | ||
81 | */ | ||
82 | |||
83 | ENTRY(s3c2410_cpu_suspend) | ||
84 | @@ prepare cpu to sleep | ||
85 | |||
86 | ldr r4, =S3C2410_REFRESH | ||
87 | ldr r5, =S3C24XX_MISCCR | ||
88 | ldr r6, =S3C2410_CLKCON | ||
89 | ldr r7, [ r4 ] @ get REFRESH (and ensure in TLB) | ||
90 | ldr r8, [ r5 ] @ get MISCCR (and ensure in TLB) | ||
91 | ldr r9, [ r6 ] @ get CLKCON (and ensure in TLB) | ||
92 | |||
93 | orr r7, r7, #S3C2410_REFRESH_SELF @ SDRAM sleep command | ||
94 | orr r8, r8, #S3C2410_MISCCR_SDSLEEP @ SDRAM power-down signals | ||
95 | orr r9, r9, #S3C2410_CLKCON_POWER @ power down command | ||
96 | |||
97 | teq pc, #0 @ first as a trial-run to load cache | ||
98 | bl s3c2410_do_sleep | ||
99 | teq r0, r0 @ now do it for real | ||
100 | b s3c2410_do_sleep @ | ||
101 | |||
102 | @@ align next bit of code to cache line | ||
103 | .align 8 | ||
104 | s3c2410_do_sleep: | ||
105 | streq r7, [ r4 ] @ SDRAM sleep command | ||
106 | streq r8, [ r5 ] @ SDRAM power-down config | ||
107 | streq r9, [ r6 ] @ CPU sleep | ||
108 | 1: beq 1b | ||
109 | mov pc, r14 | ||
110 | |||
111 | @@ return to the caller, after having the MMU | 78 | @@ return to the caller, after having the MMU |
112 | @@ turned on, this restores the last bits from the | 79 | @@ turned on, this restores the last bits from the |
113 | @@ stack | 80 | @@ stack |