aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-vexpress/tc2_pm_psci.c
blob: 5a5e4f568497c9e7763e94661370ac487a6f3c40 (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
/*
 * arch/arm/mach-vexpress/tc2_pm_psci.c - TC2 PSCI support
 *
 * Created by: Achin Gupta, December 2012
 * Copyright:  (C) 2012  ARM Limited
 *
 * Some portions of this file were originally written by Nicolas Pitre
 * Copyright:   (C) 2012  Linaro Limited
 *
 * 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/kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>

#include <asm/mcpm.h>
#include <asm/proc-fns.h>
#include <asm/cacheflush.h>
#include <asm/psci.h>
#include <asm/atomic.h>
#include <asm/cputype.h>
#include <asm/cp15.h>

#include <mach/motherboard.h>
#include <mach/tc2.h>

#include <linux/vexpress.h>

/*
 * Platform specific state id understood by the firmware and used to
 * program the power controller
 */
#define PSCI_POWER_STATE_ID           0

static atomic_t tc2_pm_use_count[TC2_MAX_CPUS][TC2_MAX_CLUSTERS];

static int tc2_pm_psci_power_up(unsigned int cpu, unsigned int cluster)
{
	unsigned int mpidr = (cluster << 8) | cpu;
	int ret = 0;

	BUG_ON(!psci_ops.cpu_on);

	switch (atomic_inc_return(&tc2_pm_use_count[cpu][cluster])) {
	case 1:
		/*
		 * This is a request to power up a cpu that linux thinks has
		 * been powered down. Retries are needed if the firmware has
		 * seen the power down request as yet.
		 */
		do
			ret = psci_ops.cpu_on(mpidr,
					      virt_to_phys(mcpm_entry_point));
		while (ret == -EAGAIN);

		return ret;
	case 2:
		/* This power up request has overtaken a power down request */
		return ret;
	default:
		/* Any other value is a bug */
		BUG();
	}
}

static void tc2_pm_psci_power_down(void)
{
	struct psci_power_state power_state;
	unsigned int mpidr, cpu, cluster;

	mpidr = read_cpuid_mpidr();
	cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
	cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);

	BUG_ON(!psci_ops.cpu_off);

	switch (atomic_dec_return(&tc2_pm_use_count[cpu][cluster])) {
	case 1:
		/*
		 * Overtaken by a power up. Flush caches, exit coherency,
		 * return & fake a reset
		 */
		set_cr(get_cr() & ~CR_C);

		flush_cache_louis();

		asm volatile ("clrex");
		set_auxcr(get_auxcr() & ~(1 << 6));

		return;
	case 0:
		/* A normal request to possibly power down the cluster */
		power_state.id = PSCI_POWER_STATE_ID;
		power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN;
		power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1;

		psci_ops.cpu_off(power_state);

		/* On success this function never returns */
	default:
		/* Any other value is a bug */
		BUG();
	}
}

static void tc2_pm_psci_suspend(u64 unused)
{
	struct psci_power_state power_state;

	BUG_ON(!psci_ops.cpu_suspend);

	/* On TC2 always attempt to power down the cluster */
	power_state.id = PSCI_POWER_STATE_ID;
	power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN;
	power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1;

	psci_ops.cpu_suspend(power_state, virt_to_phys(mcpm_entry_point));

	/* On success this function never returns */
	BUG();
}

static const struct mcpm_platform_ops tc2_pm_power_ops = {
	.power_up      = tc2_pm_psci_power_up,
	.power_down    = tc2_pm_psci_power_down,
	.suspend       = tc2_pm_psci_suspend,
};

static void __init tc2_pm_usage_count_init(void)
{
	unsigned int mpidr, cpu, cluster;

	mpidr = read_cpuid_mpidr();
	cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
	cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);

	pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
	BUG_ON(cluster >= TC2_MAX_CLUSTERS ||
	       cpu >= vexpress_spc_get_nb_cpus(cluster));

	atomic_set(&tc2_pm_use_count[cpu][cluster], 1);
}

static int __init tc2_pm_psci_init(void)
{
	int ret;

	ret = psci_probe();
	if (ret) {
		pr_debug("psci not found. Aborting psci init\n");
		return -ENODEV;
	}

	tc2_pm_usage_count_init();

	ret = mcpm_platform_register(&tc2_pm_power_ops);
	if (!ret)
		ret = mcpm_sync_init(NULL);
	if (!ret)
		pr_info("TC2 power management initialized\n");
	return ret;
}

early_initcall(tc2_pm_psci_init);