pcr.c revision 194679
1/*-
2 * Copyright (c) 2009 Nathan Whitehorn
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/powerpc/cpufreq/pcr.c 194679 2009-06-23 04:28:32Z nwhitehorn $");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/bus.h>
33#include <sys/cpu.h>
34#include <sys/kernel.h>
35#include <sys/module.h>
36
37#include <dev/ofw/ofw_bus.h>
38
39#include "cpufreq_if.h"
40
41struct pcr_softc {
42	device_t dev;
43	uint32_t pcr_vals[3];
44	int nmodes;
45};
46
47static void	pcr_identify(driver_t *driver, device_t parent);
48static int	pcr_probe(device_t dev);
49static int	pcr_attach(device_t dev);
50static int	pcr_settings(device_t dev, struct cf_setting *sets, int *count);
51static int	pcr_set(device_t dev, const struct cf_setting *set);
52static int	pcr_get(device_t dev, struct cf_setting *set);
53static int	pcr_type(device_t dev, int *type);
54
55static device_method_t pcr_methods[] = {
56	/* Device interface */
57	DEVMETHOD(device_identify,	pcr_identify),
58	DEVMETHOD(device_probe,		pcr_probe),
59	DEVMETHOD(device_attach,	pcr_attach),
60
61	/* cpufreq interface */
62	DEVMETHOD(cpufreq_drv_set,	pcr_set),
63	DEVMETHOD(cpufreq_drv_get,	pcr_get),
64	DEVMETHOD(cpufreq_drv_type,	pcr_type),
65	DEVMETHOD(cpufreq_drv_settings,	pcr_settings),
66
67	{0, 0}
68};
69
70static driver_t pcr_driver = {
71	"pcr",
72	pcr_methods,
73	sizeof(struct pcr_softc)
74};
75
76static devclass_t pcr_devclass;
77DRIVER_MODULE(pcr, cpu, pcr_driver, pcr_devclass, 0, 0);
78
79/*
80 * States
81 */
82
83#define PCR_TO_FREQ(a)	((a >> 17) & 3)
84
85#define	PCR_FULL	0
86#define PCR_HALF	1
87#define PCR_QUARTER	2		/* Only on 970MP */
88
89#define PSR_RECEIVED	(1ULL << 61)
90#define PSR_COMPLETED	(1ULL << 61)
91
92/*
93 * SCOM addresses
94 */
95
96#define	SCOM_PCR	0x0aa00100	/* Power Control Register */
97#define SCOM_PCR_BIT	0x80000000	/* Data bit for PCR */
98#define SCOM_PSR	0x40800100	/* Power Status Register */
99
100/*
101 * SCOM Glue
102 */
103
104#define SCOMC_READ	0x00008000
105#define SCOMC_WRITE	0x00000000
106
107static void
108write_scom(register_t address, uint64_t value)
109{
110	register_t msr;
111	register_t hi, lo, scratch;
112
113	hi = (value >> 32) & 0xffffffff;
114	lo = value & 0xffffffff;
115
116	msr = mfmsr();
117	mtmsr(msr & ~PSL_EE); isync();
118
119	mtspr64(SPR_SCOMD, hi, lo, scratch);
120	isync();
121	mtspr(SPR_SCOMC, address | SCOMC_WRITE);
122	isync();
123
124	mtmsr(msr); isync();
125}
126
127static uint64_t
128read_scom(register_t address)
129{
130	register_t msr;
131	uint64_t ret;
132
133	msr = mfmsr();
134	mtmsr(msr & ~PSL_EE); isync();
135
136	mtspr(SPR_SCOMC, address | SCOMC_READ);
137	isync();
138
139	__asm __volatile ("mfspr %0,%1;"
140            " mr %0+1, %0; srdi %0,%0,32" : "=r" (ret) : "K" (SPR_SCOMD));
141
142	(void)mfspr(SPR_SCOMC); /* Complete transcation */
143
144	mtmsr(msr); isync();
145
146	return (ret);
147}
148
149static void
150pcr_identify(driver_t *driver, device_t parent)
151{
152	uint16_t vers;
153	vers = mfpvr() >> 16;
154
155	/* Check for an IBM 970-class CPU */
156	switch (vers) {
157		case IBM970FX:
158		case IBM970GX:
159		case IBM970MP:
160			break;
161		default:
162			return;
163	}
164
165	/* Make sure we're not being doubly invoked. */
166	if (device_find_child(parent, "pcr", -1) != NULL)
167		return;
168
169	/*
170	 * We attach a child for every CPU since settings need to
171	 * be performed on every CPU in the SMP case.
172	 */
173	if (BUS_ADD_CHILD(parent, 10, "pcr", -1) == NULL)
174		device_printf(parent, "add pcr child failed\n");
175}
176
177static int
178pcr_probe(device_t dev)
179{
180	if (resource_disabled("pcr", 0))
181		return (ENXIO);
182
183	device_set_desc(dev, "PPC 970 Power Control Register");
184	return (0);
185}
186
187static int
188pcr_attach(device_t dev)
189{
190	struct pcr_softc *sc;
191	phandle_t cpu;
192	uint32_t modes[3];
193	int i;
194
195	sc = device_get_softc(dev);
196	sc->dev = dev;
197
198	cpu = ofw_bus_get_node(device_get_parent(dev));
199
200	if (cpu <= 0) {
201		device_printf(dev,"No CPU device tree node!\n");
202		return (ENXIO);
203	}
204
205	/*
206	 * Collect the PCR values for each mode from the device tree.
207	 * These include bus timing information, and so cannot be
208	 * directly computed.
209	 */
210	sc->nmodes = OF_getproplen(cpu, "power-mode-data");
211	if (sc->nmodes <= 0 || sc->nmodes > sizeof(sc->pcr_vals)) {
212		device_printf(dev,"No power mode data in device tree!\n");
213		return (ENXIO);
214	}
215	OF_getprop(cpu, "power-mode-data", modes, sc->nmodes);
216	sc->nmodes /= sizeof(modes[0]);
217
218	/* Sort the modes */
219	for (i = 0; i < sc->nmodes; i++)
220		sc->pcr_vals[PCR_TO_FREQ(modes[i])] = modes[i];
221
222	cpufreq_register(dev);
223	return (0);
224}
225
226static int
227pcr_settings(device_t dev, struct cf_setting *sets, int *count)
228{
229	struct pcr_softc *sc;
230
231	sc = device_get_softc(dev);
232	if (sets == NULL || count == NULL)
233		return (EINVAL);
234	if (*count < sc->nmodes)
235		return (E2BIG);
236
237	/* Return a list of valid settings for this driver. */
238	memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * sc->nmodes);
239
240	sets[0].freq = 10000; sets[0].dev = dev;
241	sets[1].freq = 5000; sets[1].dev = dev;
242	if (sc->nmodes > 2)
243		sets[2].freq = 2500; sets[2].dev = dev;
244	*count = sc->nmodes;
245
246	return (0);
247}
248
249static int
250pcr_set(device_t dev, const struct cf_setting *set)
251{
252	struct pcr_softc *sc;
253	register_t pcr, msr;
254	uint64_t psr;
255
256	if (set == NULL)
257		return (EINVAL);
258	sc = device_get_softc(dev);
259
260	/* Construct the new PCR */
261
262	pcr = SCOM_PCR_BIT;
263
264	if (set->freq == 10000)
265		pcr |= sc->pcr_vals[0];
266	else if (set->freq == 5000)
267		pcr |= sc->pcr_vals[1];
268	else if (set->freq == 2500)
269		pcr |= sc->pcr_vals[2];
270
271	msr = mfmsr();
272	mtmsr(msr & ~PSL_EE); isync();
273
274	/* 970MP requires PCR and PCRH to be cleared first */
275
276	write_scom(SCOM_PCR,0);			/* Clear PCRH */
277	write_scom(SCOM_PCR,SCOM_PCR_BIT);	/* Clear PCR */
278
279	/* Set PCR */
280
281	write_scom(SCOM_PCR, pcr);
282
283	/* Wait for completion */
284
285	do {
286		DELAY(100);
287		psr = read_scom(SCOM_PSR);
288	} while ((psr & PSR_RECEIVED) && !(psr & PSR_COMPLETED));
289
290	mtmsr(msr); isync();
291
292	return (0);
293}
294
295static int
296pcr_get(device_t dev, struct cf_setting *set)
297{
298	struct pcr_softc *sc;
299	uint64_t psr;
300
301	if (set == NULL)
302		return (EINVAL);
303	sc = device_get_softc(dev);
304
305	memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set));
306
307	psr = read_scom(SCOM_PSR);
308
309	/* We want bits 6 and 7 */
310	psr = (psr >> 56) & 3;
311
312	set->freq = 10000;
313	if (psr == PCR_HALF)
314		set->freq = 5000;
315	else if (psr == PCR_QUARTER)
316		set->freq = 2500;
317
318	set->dev = dev;
319
320	return (0);
321}
322
323static int
324pcr_type(device_t dev, int *type)
325{
326
327	if (type == NULL)
328		return (EINVAL);
329
330	*type = CPUFREQ_TYPE_RELATIVE;
331	return (0);
332}
333
334