sunxi_pm.c 7.49 KB
Newer Older
1
/*
2
 * Copyright (c) 2017-2021, ARM Limited and Contributors. All rights reserved.
3
4
5
6
7
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
8

9
#include <platform_def.h>
10
11
12

#include <arch_helpers.h>
#include <common/debug.h>
13
#include <drivers/arm/css/css_scpi.h>
14
15
16
17
18
19
#include <drivers/arm/gicv2.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#include <lib/psci/psci.h>
#include <plat/common/platform.h>

20
#include <sunxi_cpucfg.h>
21
#include <sunxi_def.h>
22
23
#include <sunxi_mmap.h>
#include <sunxi_private.h>
24

25
26
27
#define SUNXI_WDOG0_CTRL_REG		(SUNXI_R_WDOG_BASE + 0x0010)
#define SUNXI_WDOG0_CFG_REG		(SUNXI_R_WDOG_BASE + 0x0014)
#define SUNXI_WDOG0_MODE_REG		(SUNXI_R_WDOG_BASE + 0x0018)
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
#define CPU_PWR_LVL			MPIDR_AFFLVL0
#define CLUSTER_PWR_LVL			MPIDR_AFFLVL1
#define SYSTEM_PWR_LVL			MPIDR_AFFLVL2

#define CPU_PWR_STATE(state) \
	((state)->pwr_domain_state[CPU_PWR_LVL])
#define CLUSTER_PWR_STATE(state) \
	((state)->pwr_domain_state[CLUSTER_PWR_LVL])
#define SYSTEM_PWR_STATE(state) \
	((state)->pwr_domain_state[SYSTEM_PWR_LVL])

/*
 * The addresses for the SCP exception vectors are defined in the or1k
 * architecture specification.
 */
#define OR1K_VEC_FIRST			0x01
#define OR1K_VEC_LAST			0x0e
#define OR1K_VEC_ADDR(n)		(0x100 * (n))

/*
 * This magic value is the little-endian representation of the or1k
 * instruction "l.mfspr r2, r0, 0x12", which is guaranteed to be the
 * first instruction in the SCP firmware.
 */
#define SCP_FIRMWARE_MAGIC		0xb4400012

static bool scpi_available;

static inline scpi_power_state_t scpi_map_state(plat_local_state_t psci_state)
{
	if (is_local_state_run(psci_state))
		return scpi_power_on;
	if (is_local_state_retn(psci_state))
		return scpi_power_retention;
	return scpi_power_off;
}

static void sunxi_cpu_standby(plat_local_state_t cpu_state)
{
	u_register_t scr = read_scr_el3();

	assert(is_local_state_retn(cpu_state));

	write_scr_el3(scr | SCR_IRQ_BIT);
	wfi();
	write_scr_el3(scr);
}
76
77
78

static int sunxi_pwr_domain_on(u_register_t mpidr)
{
79
80
81
82
83
84
85
86
	if (scpi_available) {
		scpi_set_css_power_state(mpidr,
					 scpi_power_on,
					 scpi_power_on,
					 scpi_power_on);
	} else {
		sunxi_cpu_on(mpidr);
	}
87
88
89
90
91
92

	return PSCI_E_SUCCESS;
}

static void sunxi_pwr_domain_off(const psci_power_state_t *target_state)
{
93
94
95
96
97
98
99
100
101
102
103
104
	plat_local_state_t cpu_pwr_state     = CPU_PWR_STATE(target_state);
	plat_local_state_t cluster_pwr_state = CLUSTER_PWR_STATE(target_state);
	plat_local_state_t system_pwr_state  = SYSTEM_PWR_STATE(target_state);

	if (is_local_state_off(cpu_pwr_state))
		gicv2_cpuif_disable();

	if (scpi_available) {
		scpi_set_css_power_state(read_mpidr(),
					 scpi_map_state(cpu_pwr_state),
					 scpi_map_state(cluster_pwr_state),
					 scpi_map_state(system_pwr_state));
105
106
	} else {
		sunxi_cpu_power_off_self();
107
	}
108
109
110
111
}

static void sunxi_pwr_domain_on_finish(const psci_power_state_t *target_state)
{
112
113
114
115
116
117
	if (is_local_state_off(SYSTEM_PWR_STATE(target_state)))
		gicv2_distif_init();
	if (is_local_state_off(CPU_PWR_STATE(target_state))) {
		gicv2_pcpu_distif_init();
		gicv2_cpuif_enable();
	}
118
119
}

120
121
static void __dead2 sunxi_system_off(void)
{
122
123
124
125
126
127
	gicv2_cpuif_disable();

	if (scpi_available) {
		/* Send the power down request to the SCP */
		uint32_t ret = scpi_sys_power_state(scpi_system_shutdown);

128
129
130
		if (ret == SCP_OK) {
			wfi();
		}
131

132
133
		ERROR("PSCI: SCPI %s failed: %d\n", "shutdown", ret);
	}
134

135
	/* Attempt to power down the board (may not return) */
136
	sunxi_power_down();
137

138
139
140
141
	/* Turn off all CPUs */
	sunxi_cpu_power_off_others();
	sunxi_cpu_power_off_self();
	psci_power_down_wfi();
142
143
144
145
}

static void __dead2 sunxi_system_reset(void)
{
146
147
148
149
150
151
	gicv2_cpuif_disable();

	if (scpi_available) {
		/* Send the system reset request to the SCP */
		uint32_t ret = scpi_sys_power_state(scpi_system_reboot);

152
153
154
155
156
		if (ret == SCP_OK) {
			wfi();
		}

		ERROR("PSCI: SCPI %s failed: %d\n", "reboot", ret);
157
158
	}

159
160
161
162
163
164
165
166
167
168
169
	/* Reset the whole system when the watchdog times out */
	mmio_write_32(SUNXI_WDOG0_CFG_REG, 1);
	/* Enable the watchdog with the shortest timeout (0.5 seconds) */
	mmio_write_32(SUNXI_WDOG0_MODE_REG, (0 << 4) | 1);
	/* Wait for twice the watchdog timeout before panicking */
	mdelay(1000);

	ERROR("PSCI: System reset failed\n");
	panic();
}

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
static int sunxi_validate_power_state(unsigned int power_state,
				      psci_power_state_t *req_state)
{
	unsigned int power_level = psci_get_pstate_pwrlvl(power_state);
	unsigned int type = psci_get_pstate_type(power_state);

	assert(req_state != NULL);

	if (power_level > PLAT_MAX_PWR_LVL)
		return PSCI_E_INVALID_PARAMS;

	if (type == PSTATE_TYPE_STANDBY) {
		/* Only one retention power state is supported. */
		if (psci_get_pstate_id(power_state) > 0)
			return PSCI_E_INVALID_PARAMS;
		/* The SoC cannot be suspended without losing state */
		if (power_level == SYSTEM_PWR_LVL)
			return PSCI_E_INVALID_PARAMS;
		for (unsigned int i = 0; i <= power_level; ++i)
			req_state->pwr_domain_state[i] = PLAT_MAX_RET_STATE;
	} else {
		/* Only one off power state is supported. */
		if (psci_get_pstate_id(power_state) > 0)
			return PSCI_E_INVALID_PARAMS;
		for (unsigned int i = 0; i <= power_level; ++i)
			req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
	}
	/* Higher power domain levels should all remain running */
	for (unsigned int i = power_level + 1; i <= PLAT_MAX_PWR_LVL; ++i)
		req_state->pwr_domain_state[i] = PSCI_LOCAL_STATE_RUN;

	return PSCI_E_SUCCESS;
}

204
205
206
static int sunxi_validate_ns_entrypoint(uintptr_t ns_entrypoint)
{
	/* The non-secure entry point must be in DRAM */
207
208
209
	if (ns_entrypoint < SUNXI_DRAM_BASE) {
		return PSCI_E_INVALID_ADDRESS;
	}
210

211
	return PSCI_E_SUCCESS;
212
213
}

214
215
216
217
218
219
220
221
static void sunxi_get_sys_suspend_power_state(psci_power_state_t *req_state)
{
	assert(req_state);

	for (unsigned int i = 0; i <= PLAT_MAX_PWR_LVL; ++i)
		req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
}

222
static plat_psci_ops_t sunxi_psci_ops = {
223
	.cpu_standby			= sunxi_cpu_standby,
224
225
226
	.pwr_domain_on			= sunxi_pwr_domain_on,
	.pwr_domain_off			= sunxi_pwr_domain_off,
	.pwr_domain_on_finish		= sunxi_pwr_domain_on_finish,
227
228
	.system_off			= sunxi_system_off,
	.system_reset			= sunxi_system_reset,
229
	.validate_power_state		= sunxi_validate_power_state,
230
	.validate_ns_entrypoint		= sunxi_validate_ns_entrypoint,
231
232
233
234
235
236
237
};

int plat_setup_psci_ops(uintptr_t sec_entrypoint,
			const plat_psci_ops_t **psci_ops)
{
	assert(psci_ops);

238
239
	/* Program all CPU entry points. */
	for (unsigned int cpu = 0; cpu < PLATFORM_CORE_COUNT; ++cpu) {
240
241
242
243
244
245
		mmio_write_32(SUNXI_CPUCFG_RVBAR_LO_REG(cpu),
			      sec_entrypoint & 0xffffffff);
		mmio_write_32(SUNXI_CPUCFG_RVBAR_HI_REG(cpu),
			      sec_entrypoint >> 32);
	}

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
	/* Check for a valid SCP firmware, and boot the SCP if found. */
	if (mmio_read_32(SUNXI_SCP_BASE) == SCP_FIRMWARE_MAGIC) {
		/* Program SCP exception vectors to the firmware entrypoint. */
		for (unsigned int i = OR1K_VEC_FIRST; i <= OR1K_VEC_LAST; ++i) {
			uint32_t vector = SUNXI_SRAM_A2_BASE + OR1K_VEC_ADDR(i);
			uint32_t offset = SUNXI_SCP_BASE - vector;

			mmio_write_32(vector, offset >> 2);
			clean_dcache_range(vector, sizeof(uint32_t));
		}
		/* Take the SCP out of reset. */
		mmio_setbits_32(SUNXI_R_CPUCFG_BASE, BIT(0));
		/* Wait for the SCP firmware to boot. */
		if (scpi_wait_ready() == 0)
			scpi_available = true;
	}

	NOTICE("PSCI: System suspend is %s\n",
	       scpi_available ? "available via SCPI" : "unavailable");
	if (scpi_available) {
		/* Suspend is only available via SCPI. */
		sunxi_psci_ops.pwr_domain_suspend = sunxi_pwr_domain_off;
		sunxi_psci_ops.pwr_domain_suspend_finish = sunxi_pwr_domain_on_finish;
		sunxi_psci_ops.get_sys_suspend_power_state = sunxi_get_sys_suspend_power_state;
	}

272
273
274
275
	*psci_ops = &sunxi_psci_ops;

	return 0;
}