1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28/*
29 * Platform Power Management master pseudo driver platform support.
30 */
31
32#include <sys/file.h>
33#include <sys/ddi.h>
34#include <sys/sunddi.h>
35#include <sys/ppmvar.h>
36
37/*
38 * This flag disables vcore/vid feature by default.
39 */
40uint_t	ppm_do_vcore = 0;
41
42/*
43 * PPMDC_CPU_NEXT operation
44 */
45static int
46ppm_cpu_next(ppm_domain_t *domp, int level)
47{
48#ifdef DEBUG
49	char *str = "ppm_cpu_next";
50#endif
51	ppm_dc_t *dc;
52	int index = level - 1;
53	int ret = 0;
54
55	dc = ppm_lookup_dc(domp, PPMDC_CPU_NEXT);
56	for (; dc && (dc->cmd == PPMDC_CPU_NEXT); dc = dc->next) {
57		switch (dc->method) {
58		case PPMDC_CPUSPEEDKIO:
59			ret = ldi_ioctl(dc->lh, dc->m_un.cpu.iowr,
60			    (intptr_t)index, FWRITE | FKIOCTL, kcred, NULL);
61			if (ret)
62				return (ret);
63			break;
64
65		default:
66			PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
67			    str, dc->method))
68			return (-1);
69		}
70	}
71	return (ret);
72}
73
74/*
75 * PPMDC_PRE_CHNG operation
76 */
77static int
78ppm_cpu_pre_chng(ppm_domain_t *domp, int oldl, int speedup)
79{
80#ifdef DEBUG
81	char *str = "ppm_cpu_pre_chng";
82#endif
83	ppm_dc_t *dc;
84	int lowest;
85	int ret = 0;
86
87	dc = ppm_lookup_dc(domp, PPMDC_PRE_CHNG);
88	for (; dc && (dc->cmd == PPMDC_PRE_CHNG); dc = dc->next) {
89
90		switch (dc->method) {
91		case PPMDC_VCORE:
92			lowest = domp->devlist->lowest;
93			if ((oldl != lowest) || (speedup != 1))
94				break;
95
96			/* raise core voltage */
97			if (ppm_do_vcore > 0) {
98				ret = ldi_ioctl(dc->lh,
99				    dc->m_un.cpu.iowr,
100				    (intptr_t)&dc->m_un.cpu.val,
101				    FWRITE | FKIOCTL, kcred, NULL);
102				if (ret != 0)
103					return (ret);
104				if (dc->m_un.cpu.delay > 0)
105					drv_usecwait(dc->m_un.cpu.delay);
106			}
107			break;
108
109		default:
110			PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
111			    str, dc->method))
112			return (-1);
113		}
114	}
115
116	return (ret);
117}
118
119/*
120 * PPMDC_CPU_GO operation
121 */
122/* ARGSUSED */
123static int
124ppm_cpu_go(ppm_domain_t *domp, int level)
125{
126	ppm_dc_t *dc;
127	int ret = 0;
128
129	dc = ppm_lookup_dc(domp, PPMDC_CPU_GO);
130	if (dc == NULL) {
131		return (ret);
132	}
133	switch (dc->method) {
134	case PPMDC_KIO:
135		ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
136		    (intptr_t)dc->m_un.kio.val, FWRITE | FKIOCTL,
137		    kcred, NULL);
138		break;
139	default:
140		return (-1);
141	}
142
143	return (ret);
144}
145
146/*
147 * PPMDC_POST_CHNG operation
148 */
149static int
150ppm_cpu_post_chng(ppm_domain_t *domp, int newl, int speedup)
151{
152#ifdef DEBUG
153	char *str = "ppm_cpu_post_chng";
154#endif
155	ppm_dc_t *dc;
156	int	lowest;
157	int ret = 0;
158
159	dc = ppm_lookup_dc(domp, PPMDC_POST_CHNG);
160	for (; dc && (dc->cmd == PPMDC_POST_CHNG); dc = dc->next) {
161
162		switch (dc->method) {
163		case PPMDC_VCORE:
164			lowest = domp->devlist->lowest;
165			if ((newl != lowest) || (speedup != 0))
166				break;
167
168			/* lower core voltage */
169			if (ppm_do_vcore > 0) {
170				ret = ldi_ioctl(dc->lh,
171				    dc->m_un.cpu.iowr,
172				    (intptr_t)&dc->m_un.cpu.val,
173				    FWRITE | FKIOCTL, kcred, NULL);
174				if (ret != 0)
175					return (ret);
176				if (dc->m_un.cpu.delay > 0)
177					drv_usecwait(dc->m_un.cpu.delay);
178			}
179			break;
180
181		default:
182			PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
183			    str, dc->method))
184			return (-1);
185		}
186	}
187	return (ret);
188}
189
190/*
191 * The effective cpu estar model is: program all cpus to be ready to go
192 * the same next(or new) speed level, program all other system bus resident
193 * devices to the same next speed level.  At last, pull the trigger to
194 * initiate the speed change for all system bus resident devices
195 * simultaneously.
196 *
197 * On Excalibur, the Safari bus resident devices are Cheetah/Cheetah+ and
198 * Schizo.  On Enchilada, the JBus resident devides are Jalapeno(s) and
199 * Tomatillo(s).
200 */
201int
202ppm_change_cpu_power(ppm_dev_t *ppmd, int newlevel)
203{
204#ifdef DEBUG
205	char *str = "ppm_change_cpu_power";
206#endif
207	ppm_unit_t *unitp;
208	ppm_domain_t *domp;
209	ppm_dev_t *cpup;
210	dev_info_t *dip;
211	int level, oldlevel;
212	int speedup, incr, lowest, highest;
213	char *chstr;
214	int ret;
215
216	unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
217	ASSERT(unitp);
218	domp = ppmd->domp;
219	cpup = domp->devlist;
220	lowest = cpup->lowest;
221	highest = cpup->highest;
222
223	/*
224	 * Not all cpus may have transitioned to a known level by this time
225	 */
226	oldlevel = (cpup->level == PM_LEVEL_UNKNOWN) ? highest : cpup->level;
227	dip = cpup->dip;
228	ASSERT(dip);
229
230	PPMD(D_CPU, ("%s: old %d, new %d, highest %d, lowest %d\n",
231	    str, oldlevel, newlevel, highest, lowest))
232
233	if (newlevel > oldlevel) {
234		chstr = "UP";
235		speedup = 1;
236		incr = 1;
237	} else if (newlevel < oldlevel) {
238		chstr = "DOWN";
239		speedup = 0;
240		incr = -1;
241	} else
242		return (DDI_SUCCESS);
243
244	/*
245	 * This loop will execute 1x or 2x depending on
246	 * number of times we need to change clock rates
247	 */
248	for (level = oldlevel+incr; level != newlevel+incr; level += incr) {
249		/* bring each cpu to next level */
250		for (; cpup; cpup = cpup->next) {
251			if (cpup->level == level)
252				continue;
253
254			ret = pm_power(cpup->dip, 0, level);
255			PPMD(D_CPU, ("%s: \"%s\", %s to level %d, ret %d\n",
256			    str, cpup->path, chstr, level, ret))
257			if (ret == DDI_SUCCESS) {
258				cpup->level = level;
259				cpup->rplvl = PM_LEVEL_UNKNOWN;
260				continue;
261			}
262
263			/*
264			 * if the driver was unable to lower cpu speed,
265			 * the cpu probably got busy; set the previous
266			 * cpus back to the original level
267			 */
268			if (speedup == 0)
269				ret = ppm_revert_cpu_power(cpup, level - incr);
270			return (ret);
271		}
272		cpup = domp->devlist;
273
274		/*
275		 * set bus resident devices at next speed level
276		 */
277		ret = ppm_cpu_next(domp, level);
278		if (ret != 0) {
279			(void) ppm_revert_cpu_power(cpup, level - incr);
280			return (ret);
281		}
282
283		/*
284		 * platform dependent various operations before
285		 * initiating cpu speed change
286		 */
287		ret = ppm_cpu_pre_chng(domp, level - incr, speedup);
288		if (ret != 0) {
289			(void) ppm_revert_cpu_power(cpup, level - incr);
290			(void) ppm_cpu_next(domp, level - incr);
291			return (ret);
292		}
293
294		/*
295		 * the following 1us delay is actually required for us3i only.
296		 * on us3i system, entering estar mode from full requires
297		 * to set mcu to single fsm state followed by 1us delay
298		 * before trigger actual transition.  The mcu part is
299		 * handled in us_drv, the delay is here.
300		 */
301		if ((oldlevel == highest) && (speedup == 0))
302			drv_usecwait(1);
303
304		/*
305		 * initiate cpu speed change
306		 */
307		ret = ppm_cpu_go(domp, level);
308		if (ret != 0) {
309			(void) ppm_revert_cpu_power(cpup, level - incr);
310			(void) ppm_cpu_next(domp, level - incr);
311			return (ret);
312		}
313
314		/*
315		 * platform dependent operations post cpu speed change
316		 */
317		ret = ppm_cpu_post_chng(domp, level, speedup);
318		if (ret != 0)
319			return (ret);
320
321	}   /* end of looping each level */
322
323	return (DDI_SUCCESS);
324}
325
326/*
327 * This handles the power-on case where cpu power level is
328 * PM_LEVEL_UNKNOWN.  Per agreement with OBP, cpus always
329 * boot up at full speed.  In fact, we must not making calls
330 * into tomtppm or schppm to trigger cpu speed change to a
331 * different level at early boot time since some cpu may not
332 * be ready, causing xc_one() to fail silently.
333 *
334 * Here we simply call pm_power() to get the power level updated
335 * in pm and ppm. Had xc_one() failed silently inside us_power()
336 * at this time we're unaffected.
337 */
338boolean_t
339ppm_manage_early_cpus(dev_info_t *dip, int new, int *result)
340{
341	ppm_dev_t *ppmd = PPM_GET_PRIVATE(dip);
342	int ret;
343
344	if (ppmd->level == PM_LEVEL_UNKNOWN && new == ppmd->highest) {
345		ret = pm_power(dip, 0, new);
346		if (ret != DDI_SUCCESS) {
347			PPMD(D_CPU, ("ppm_manage_early_cpus: pm_power() "
348			    "failed to change power level to %d", new))
349		} else {
350			ppmd->level = new;
351			ppmd->rplvl = PM_LEVEL_UNKNOWN;
352		}
353		*result = ret;
354		return (B_TRUE);
355	}
356	return (B_FALSE);
357}
358