aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-zynq/suspend.S
blob: f3f8440e801889ddc986d081bd3cae0999083fee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*
 * Suspend support for Zynq
 *
 *  Copyright (C) 2012 Xilinx
 *
 *  Soren Brinkmann <soren.brinkmann@xilinx.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 */

#include <linux/linkage.h>

#define ARMPLL_CTRL_OFFS	0x100
#define DDRPLL_CTRL_OFFS	0x104
#define PLLSTATUS_OFFS		0x10c
#define DDR_CLK_CTRL_OFFS	0x124
#define DCI_CLK_CTRL_OFFS	0x128
#define MODE_STS_OFFS		0x54

#define PLL_RESET_MASK		1
#define PLL_PWRDWN_MASK		(1 << 1)
#define PLL_BYPASS_MASK		(1 << 4)
#define DCICLK_ENABLE_MASK	1
#define DDRCLK_ENABLE_MASK	3
#define ARM_LOCK_MASK		(1 << 0)
#define DDR_LOCK_MASK		(1 << 1)
#define DDRC_STATUS_MASK	7

#define DDRC_OPMODE_SR		3
#define MAXTRIES		100

	.text
	.align 3

/**
 * zynq_sys_suspend - Enter suspend
 * @ddrc_base:	Base address of the DDRC
 * @slcr_base:	Base address of the SLCR
 * Returns -1 if DRAM subsystem is not gated off, 0 otherwise.
 *
 * This function is moved into OCM and finishes the suspend operation. I.e. DDR
 * related clocks are gated off and the DDR PLL is bypassed.
 */
ENTRY(zynq_sys_suspend)
	push	{r4 - r7}

	/* Check DDRC is in self-refresh mode */
	ldr	r2, [r0, #MODE_STS_OFFS]
	and	r2, #DDRC_STATUS_MASK
	cmp	r2, #DDRC_OPMODE_SR
	movweq	r3, #0xff00
	bne	suspend

	mov	r3, #MAXTRIES
	movw	r4, #0xfff0
	movt	r4, #0x1f
	/* Wait for command queue empty */
1:	subs	r3, #1
	movweq	r3, #0xff00
	beq	suspend
	dsb	sy
	ldr	r2, [r0, #MODE_STS_OFFS]
	ands	r2, r4
	bne	1b

	dsb	sy

	/*
	 * Wait for DDRC pipeline/queues to drain.
	 * We should wait ~40 DDR cycles. DDR is still at full speed while the
	 * CPU might already run in PLL bypass mode. The fastest speed the CPU
	 * runs at is ~1 GHz ~ 2 * DDR speed.
	 */
	mov	r3, #160
1:	nop
	subs	r3, #1
	bne	1b

	dsb	sy

	/* read back CAM status once more */
	ldr	r2, [r0, #MODE_STS_OFFS]
	ands	r2, r4
	movwne	r3, #0xff00
	bne	suspend

	/* Stop DDR clocks */
	ldr	r2, [r1, #DDR_CLK_CTRL_OFFS]
	bic	r2, #DDRCLK_ENABLE_MASK
	str	r2, [r1, #DDR_CLK_CTRL_OFFS]

	dmb	st

	ldr	r2, [r1, #DCI_CLK_CTRL_OFFS]
	bic	r2, #DCICLK_ENABLE_MASK
	str	r2, [r1, #DCI_CLK_CTRL_OFFS]

	dmb	st

	/* Bypass and powerdown DDR PLL */
	ldr	r2, [r1, #DDRPLL_CTRL_OFFS]
	orr	r2, #PLL_BYPASS_MASK
	str	r2, [r1, #DDRPLL_CTRL_OFFS]
	orr	r2, #(PLL_PWRDWN_MASK | PLL_RESET_MASK)
	str	r2, [r1, #DDRPLL_CTRL_OFFS]

	/* Bypass and powerdown ARM PLL */
	ldr	r2, [r1, #ARMPLL_CTRL_OFFS]
	orr	r2, #PLL_BYPASS_MASK
	str	r2, [r1, #ARMPLL_CTRL_OFFS]
	orr	r2, #(PLL_PWRDWN_MASK | PLL_RESET_MASK)
	str	r2, [r1, #ARMPLL_CTRL_OFFS]

suspend:
	dsb	sy
	wfi
	dsb	sy
	cmp	r3, #0xff00
	moveq	r0, #-1
	beq	exit

	/* Power up ARM PLL */
	ldr	r2, [r1, #ARMPLL_CTRL_OFFS]
	bic	r2, #(PLL_PWRDWN_MASK | PLL_RESET_MASK)
	str	r2, [r1, #ARMPLL_CTRL_OFFS]
	/* wait for lock */
1:	ldr	r2, [r1, #PLLSTATUS_OFFS]
	ands	r2, #ARM_LOCK_MASK
	beq	1b

	dsb	sy

	/* Disable ARM PLL bypass */
	ldr	r2, [r1, #ARMPLL_CTRL_OFFS]
	bic	r2, #PLL_BYPASS_MASK
	str	r2, [r1, #ARMPLL_CTRL_OFFS]

	dmb	st

	/* Power up DDR PLL */
	ldr	r2, [r1, #DDRPLL_CTRL_OFFS]
	bic	r2, #(PLL_PWRDWN_MASK | PLL_RESET_MASK)
	str	r2, [r1, #DDRPLL_CTRL_OFFS]
	/* wait for lock */
1:	ldr	r2, [r1, #PLLSTATUS_OFFS]
	ands	r2, #DDR_LOCK_MASK
	beq	1b

	dsb	sy

	/* Disable DDR PLL bypass */
	ldr	r2, [r1, #DDRPLL_CTRL_OFFS]
	bic	r2, #PLL_BYPASS_MASK
	str	r2, [r1, #DDRPLL_CTRL_OFFS]

	dmb	st

	/* Start DDR clocks */
	ldr	r2, [r1, #DCI_CLK_CTRL_OFFS]
	orr	r2, #DCICLK_ENABLE_MASK
	str	r2, [r1, #DCI_CLK_CTRL_OFFS]

	dmb	st

	ldr	r2, [r1, #DDR_CLK_CTRL_OFFS]
	orr	r2, #DDRCLK_ENABLE_MASK
	str	r2, [r1, #DDR_CLK_CTRL_OFFS]

	dsb	sy

	mov	r0, #0
exit:	pop	{r4 - r7}
	bx	lr

ENTRY(zynq_sys_suspend_sz)
	.word	. - zynq_sys_suspend

	ENDPROC(zynq_sys_suspend)