xref: /illumos-gate/usr/src/uts/sun4u/io/ppm/jbusppm.c (revision 19397407)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 
27 #include <sys/types.h>
28 #include <sys/conf.h>
29 #include <sys/open.h>
30 #include <sys/modctl.h>
31 #include <sys/promif.h>
32 #include <sys/stat.h>
33 #include <sys/ddi_impldefs.h>
34 #include <sys/jbusppm.h>
35 #include <sys/ddi.h>
36 #include <sys/sunddi.h>
37 
38 /*
39  *	JBus Power Management Driver
40  *
41  *	jbusppm driver initiates the JBus clock speed change
42  *	as part of the protocol to adjust the clock speed on
43  *	all JBus resident devices.
44  *
45  *	jbusppm driver is loaded because of the explicit dependency
46  *	defined in PPM driver.
47  */
48 
49 /*
50  * Configuration Function prototypes and data structures
51  */
52 static int	jbppm_attach(dev_info_t *, ddi_attach_cmd_t);
53 static int	jbppm_detach(dev_info_t *, ddi_detach_cmd_t);
54 static int	jbppm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
55 static int	jbppm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p);
56 static int	jbppm_close(dev_t dev, int flag, int otyp, cred_t *cred_p);
57 static int	jbppm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
58 
59 /*
60  * Configuration data structures
61  */
62 static struct cb_ops jbppm_cbops = {
63 	jbppm_open,		/* open */
64 	jbppm_close,		/* close */
65 	nodev,			/* strategy */
66 	nodev,			/* print */
67 	nodev,			/* dump */
68 	nodev,			/* read */
69 	nodev,			/* write */
70 	jbppm_ioctl,		/* ioctl */
71 	nodev,			/* devmap */
72 	nodev,			/* mmap */
73 	nodev,			/* segmap */
74 	nochpoll,		/* chpoll */
75 	ddi_prop_op,		/* prop_op */
76 	NULL,			/* stream */
77 	D_MP | D_NEW,		/* flag */
78 	CB_REV,			/* rev */
79 	nodev,			/* aread */
80 	nodev,			/* awrite */
81 };
82 
83 static struct dev_ops jbppm_ops = {
84 	DEVO_REV,		/* devo_rev */
85 	0,			/* refcnt */
86 	jbppm_getinfo,		/* getinfo */
87 	nulldev,		/* identify */
88 	nulldev,		/* probe */
89 	jbppm_attach,		/* attach */
90 	jbppm_detach,		/* detach */
91 	nodev,			/* reset */
92 	&jbppm_cbops,		/* cb_ops */
93 	NULL,			/* bus_ops */
94 	NULL,			/* power */
95 	ddi_quiesce_not_supported,	/* devo_quiesce */
96 };
97 
98 extern struct mod_ops mod_driverops;
99 
100 static struct modldrv modldrv = {
101 	&mod_driverops,
102 	"JBus ppm driver",
103 	&jbppm_ops,
104 };
105 
106 static struct modlinkage modlinkage = {
107 	MODREV_1,
108 	&modldrv,
109 	NULL
110 };
111 
112 /*
113  * Local functions
114  */
115 static void jbppm_next_speed(dev_info_t *, uint_t);
116 static int jbppm_start_next(dev_info_t *, int);
117 
118 /*
119  * Driver global variables
120  *
121  * jbppm_lock synchronize the access of lyr handle to each jbppm
122  * minor device, therefore write to tomatillo device is
123  * sequentialized.  Lyr protocol requires pairing up lyr open
124  * and close, so only a single reference is allowed per minor node.
125  */
126 static void	*jbppm_statep;
127 static kmutex_t  jbppm_lock;
128 
129 /*
130  * bit masks to scale the IO bridge clock in sync with and only with
131  * scaling CPU clock.
132  *
133  * The array index indicates power level (from lowest to highest).
134  */
135 static const uint64_t jbus_clock_masks[] = {
136 	JBUS_ESTAR_CNTL_32,
137 	JBUS_ESTAR_CNTL_2,
138 	JBUS_ESTAR_CNTL_1
139 };
140 
141 int
_init(void)142 _init(void)
143 {
144 	int	error;
145 
146 	if ((error = ddi_soft_state_init(&jbppm_statep,
147 	    sizeof (jbppm_unit), 0)) != DDI_SUCCESS) {
148 		return (error);
149 	}
150 
151 	mutex_init(&jbppm_lock, NULL, MUTEX_DRIVER, NULL);
152 
153 	if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
154 		mutex_destroy(&jbppm_lock);
155 		ddi_soft_state_fini(&jbppm_statep);
156 		return (error);
157 	}
158 
159 	return (error);
160 }
161 
162 int
_fini(void)163 _fini(void)
164 {
165 	int	error;
166 
167 	if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
168 		mutex_destroy(&jbppm_lock);
169 		ddi_soft_state_fini(&jbppm_statep);
170 	}
171 
172 	return (error);
173 
174 }
175 
176 int
_info(struct modinfo * modinfop)177 _info(struct modinfo *modinfop)
178 {
179 	return (mod_info(&modlinkage, modinfop));
180 }
181 
182 
183 
184 /*
185  * Driver attach(9e) entry point
186  */
187 static int
jbppm_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)188 jbppm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
189 {
190 	char	*str = "jbppm_attach";
191 	int		instance;
192 	jbppm_unit	*unitp;
193 	uint64_t	data64;
194 	ddi_device_acc_attr_t	attr;
195 	int		rv = DDI_SUCCESS;
196 
197 	switch (cmd) {
198 	case DDI_ATTACH:
199 		break;
200 	case DDI_RESUME:
201 		return (DDI_SUCCESS);
202 	default:
203 		cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd);
204 		return (DDI_FAILURE);
205 	}
206 
207 	instance = ddi_get_instance(dip);
208 	rv = ddi_soft_state_zalloc(jbppm_statep, instance);
209 	if (rv != DDI_SUCCESS) {
210 		cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)",
211 		    str, ddi_binding_name(dip),
212 		    ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " ");
213 		return (rv);
214 	}
215 
216 	if ((unitp = ddi_get_soft_state(jbppm_statep, instance)) == NULL) {
217 		rv = DDI_FAILURE;
218 		goto doerrs;
219 	}
220 
221 	/*
222 	 * Export "ddi-kernel-ioctl" property - prepared to support
223 	 * kernel ioctls (driver layering).
224 	 */
225 	rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
226 	    DDI_KERNEL_IOCTL, NULL, 0);
227 	if (rv != DDI_PROP_SUCCESS)
228 		goto doerrs;
229 
230 	ddi_report_dev(dip);
231 	unitp->dip = dip;
232 
233 	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
234 	attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
235 	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
236 
237 	rv = ddi_regs_map_setup(dip, 0, (caddr_t *)&unitp->devid_csr, 0, 8,
238 	    &attr, &unitp->devid_hndl);
239 	if (rv != DDI_SUCCESS)
240 		goto doerrs;
241 
242 	rv = ddi_regs_map_setup(dip, 1, (caddr_t *)&unitp->estar_csr, 0, 16,
243 	    &attr, &unitp->estar_hndl);
244 	if (rv != DDI_SUCCESS)
245 		goto doerrs;
246 	unitp->j_chng_csr = (uint64_t *)((caddr_t)unitp->estar_csr +
247 	    J_CHNG_INITIATION_OFFSET);
248 
249 	data64 = ddi_get64(unitp->devid_hndl, (uint64_t *)unitp->devid_csr);
250 	unitp->is_master = (data64 & MASTER_IOBRIDGE_BIT) ? 1 : 0;
251 	unitp->lyropen = 0;
252 
253 	/*
254 	 * create minor node for kernel_ioctl calls
255 	 */
256 	rv = ddi_create_minor_node(dip, "jbus-ppm", S_IFCHR, instance, 0, 0);
257 	if (rv != DDI_SUCCESS)
258 		goto doerrs;
259 
260 	return (rv);
261 
262 doerrs:
263 	if (unitp->devid_hndl != NULL)
264 		ddi_regs_map_free(&unitp->devid_hndl);
265 
266 	if (unitp->estar_csr != NULL)
267 		ddi_regs_map_free(&unitp->estar_hndl);
268 
269 	if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
270 	    DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
271 		ddi_prop_remove_all(dip);
272 
273 	ddi_soft_state_free(jbppm_statep, instance);
274 
275 	return (rv);
276 }
277 
278 
279 /*
280  * Driver getinfo(9e) entry routine
281  */
282 /* ARGSUSED */
283 static int
jbppm_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** result)284 jbppm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
285 {
286 	jbppm_unit	*unitp;
287 	int		instance;
288 
289 	switch (cmd) {
290 	case DDI_INFO_DEVT2DEVINFO:
291 		instance = getminor((dev_t)arg);
292 		unitp = ddi_get_soft_state(jbppm_statep, instance);
293 		if (unitp == NULL) {
294 			return (DDI_FAILURE);
295 		}
296 		*result = (void *) unitp->dip;
297 		return (DDI_SUCCESS);
298 
299 	case DDI_INFO_DEVT2INSTANCE:
300 		instance = getminor((dev_t)arg);
301 		*result = (void *)(uintptr_t)instance;
302 		return (DDI_SUCCESS);
303 
304 	default:
305 		return (DDI_FAILURE);
306 	}
307 }
308 
309 
310 /*
311  * detach(9e)
312  */
313 /* ARGSUSED */
314 static int
jbppm_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)315 jbppm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
316 {
317 	char *str = "jbppm_detach";
318 
319 	switch (cmd) {
320 	case DDI_DETACH:
321 		return (DDI_FAILURE);
322 	case DDI_SUSPEND:
323 		return (DDI_SUCCESS);
324 	default:
325 		cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
326 		return (DDI_FAILURE);
327 	}
328 }
329 
330 
331 /* ARGSUSED */
332 static int
jbppm_open(dev_t * dev_p,int flag,int otyp,cred_t * cred_p)333 jbppm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
334 {
335 	jbppm_unit	*unitp;
336 
337 	/* not intended to allow sysadmin level root process to open it */
338 	if (drv_priv(cred_p) != DDI_SUCCESS)
339 		return (EPERM);
340 
341 	if ((unitp = ddi_get_soft_state(
342 	    jbppm_statep, getminor(*dev_p))) == NULL) {
343 		cmn_err(CE_WARN, "jbppm_open: failed to get soft state!");
344 		return (DDI_FAILURE);
345 	}
346 
347 	mutex_enter(&jbppm_lock);
348 	if (unitp->lyropen != 0) {
349 		mutex_exit(&jbppm_lock);
350 		return (EBUSY);
351 	}
352 	unitp->lyropen++;
353 	mutex_exit(&jbppm_lock);
354 
355 	return (DDI_SUCCESS);
356 }
357 
358 
359 /* ARGSUSED */
360 static int
jbppm_close(dev_t dev,int flag,int otyp,cred_t * cred_p)361 jbppm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
362 {
363 	jbppm_unit	*unitp;
364 
365 	if ((unitp =
366 	    ddi_get_soft_state(jbppm_statep, getminor(dev))) == NULL)
367 		return (DDI_FAILURE);
368 
369 	mutex_enter(&jbppm_lock);
370 	unitp->lyropen = 0;
371 	mutex_exit(&jbppm_lock);
372 
373 	return (DDI_SUCCESS);
374 }
375 
376 
377 #define	JBPPMIOC		('j' << 8)
378 #define	JBPPMIOC_ISMASTER	(JBPPMIOC | 1)	/* no 'arg' */
379 #define	JBPPMIOC_NEXT		(JBPPMIOC | 2)	/* 'arg': next speed level */
380 #define	JBPPMIOC_GO		(JBPPMIOC | 3)	/* 'arg': jbus chng_delay */
381 
382 /* ARGSUSED3 */
383 static int
jbppm_ioctl(dev_t dev,int cmd,intptr_t arg,int flag,cred_t * cred_p,int * rval_p)384 jbppm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
385     cred_t *cred_p, int *rval_p)
386 {
387 	jbppm_unit	*unitp;
388 
389 	if (drv_priv(cred_p) != 0)
390 		return (EPERM);
391 
392 	if ((unitp =
393 	    ddi_get_soft_state(jbppm_statep, getminor(dev))) == NULL)
394 		return (EIO);
395 
396 	switch (cmd) {
397 	case JBPPMIOC_ISMASTER:
398 		if (unitp->is_master)
399 			return (0);
400 		else
401 			return (-1);
402 
403 	case  JBPPMIOC_NEXT:
404 		jbppm_next_speed(unitp->dip, (uint_t)arg);
405 		return (0);
406 
407 	case  JBPPMIOC_GO:
408 		if (!unitp->is_master)
409 			return (EINVAL);
410 		return (jbppm_start_next(unitp->dip, (int)arg));
411 
412 	default:
413 		return (ENOTTY);
414 	}
415 }
416 
417 
418 /*
419  * jbppm_next_speed - program a new speed into IO bridge device prior to
420  * actual speed transition.
421  */
422 static void
jbppm_next_speed(dev_info_t * dip,uint_t lvl_index)423 jbppm_next_speed(dev_info_t *dip, uint_t lvl_index)
424 {
425 	volatile uint64_t	data64;
426 	static jbppm_unit	*unitp;
427 
428 	unitp = ddi_get_soft_state(jbppm_statep, ddi_get_instance(dip));
429 	ASSERT(unitp);
430 
431 	mutex_enter(&jbppm_lock);
432 	data64 = ddi_get64(unitp->estar_hndl, unitp->estar_csr);
433 	data64 &= ~JBUS_ESTAR_CNTL_MASK;
434 	data64 |= jbus_clock_masks[lvl_index];
435 
436 	ddi_put64(unitp->estar_hndl, (uint64_t *)unitp->estar_csr, data64);
437 	data64 = ddi_get64(unitp->estar_hndl, unitp->estar_csr);
438 	mutex_exit(&jbppm_lock);
439 }
440 
441 
442 /*
443  * jbppm_start_next - Initiate JBus speed change on all JBus devices.
444  * chng_delay indicates after master deassert j_chng signal the number of
445  * jbus clock delay before all jbus device start to transit to the new
446  * speed.
447  * Trigger sequence:
448  *      wait while j_chng[1:0] == 10
449  *	write 00 to j_chng
450  *	trigger by writing 10 to j_chng[1:0]
451  *	wait while j_chng[1:0] == 10
452  *	write 00 to j_chng[1:0]
453  * Note: this sequence is not the same as Enchilada spec described, chiefly
454  * because else where (e.g. flush E$ code) may have speed change code. If sw
455  * wait upon j_chng[1:0] == 11 in both places, we'll have problem.  That spec
456  * requires wait on 11 to ensure that trigger has completed. An alternative
457  * way to ensure that is to check and wait upon 10. J_chng[1:0] stays as 10
458  * for only a short period of time that is under HW control, unlike 11 signals
459  * which has to be cleared by sw.
460  */
461 /* ARGSUSED */
462 static int
jbppm_start_next(dev_info_t * dip,int chng_delay)463 jbppm_start_next(dev_info_t *dip, int chng_delay)
464 {
465 	volatile uint64_t	data64;
466 	static jbppm_unit	*unitp;
467 
468 	unitp = ddi_get_soft_state(jbppm_statep, ddi_get_instance(dip));
469 	ASSERT(unitp && unitp->is_master);
470 
471 	mutex_enter(&jbppm_lock);
472 
473 	/* wait while trigger is incomplete */
474 	do {
475 		data64 = ddi_get64(unitp->estar_hndl, unitp->j_chng_csr);
476 	} while ((J_CHNG_INITIATION_MASK & data64) == J_CHNG_START);
477 
478 	/* clear(reset) */
479 	data64 &= ~J_CHNG_INITIATION_MASK;
480 	ddi_put64(unitp->estar_hndl, (uint64_t *)unitp->j_chng_csr, data64);
481 
482 	/* trigger */
483 	data64 &= ~J_CHNG_DELAY_MASK;
484 	data64 |= (J_CHNG_START | chng_delay);
485 	ddi_put64(unitp->estar_hndl, (uint64_t *)unitp->j_chng_csr, data64);
486 
487 	/* wait while trigger is incomplete */
488 	do {
489 		data64 = ddi_get64(unitp->estar_hndl, unitp->j_chng_csr);
490 	} while ((J_CHNG_INITIATION_MASK & data64) == J_CHNG_START);
491 
492 	/* clear(reset) */
493 	data64 &= ~J_CHNG_INITIATION_MASK;
494 	ddi_put64(unitp->estar_hndl, (uint64_t *)unitp->j_chng_csr, data64);
495 	(void) ddi_get64(unitp->estar_hndl, unitp->j_chng_csr);
496 
497 	mutex_exit(&jbppm_lock);
498 	return (DDI_SUCCESS);
499 }
500