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 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 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 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 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 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 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 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 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 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 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 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