jbusppm.c revision 7656:2621e50fdf4a
1277323Sdim/* 2277323Sdim * CDDL HEADER START 3353358Sdim * 4353358Sdim * The contents of this file are subject to the terms of the 5353358Sdim * Common Development and Distribution License (the "License"). 6277323Sdim * You may not use this file except in compliance with the License. 7277323Sdim * 8277323Sdim * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9277323Sdim * or http://www.opensolaris.org/os/licensing. 10277323Sdim * See the License for the specific language governing permissions 11277323Sdim * and limitations under the License. 12341825Sdim * 13277323Sdim * When distributing Covered Code, include this CDDL HEADER in each 14309124Sdim * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15277323Sdim * If applicable, add the following below this CDDL HEADER, with the 16277323Sdim * fields enclosed by brackets "[]" replaced with your own identifying 17277323Sdim * information: Portions Copyright [yyyy] [name of copyright owner] 18341825Sdim * 19277323Sdim * CDDL HEADER END 20309124Sdim */ 21309124Sdim/* 22344779Sdim * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23344779Sdim * Use is subject to license terms. 24309124Sdim */ 25309124Sdim 26277323Sdim 27277323Sdim#include <sys/types.h> 28277323Sdim#include <sys/conf.h> 29277323Sdim#include <sys/open.h> 30277323Sdim#include <sys/modctl.h> 31277323Sdim#include <sys/promif.h> 32277323Sdim#include <sys/stat.h> 33296417Sdim#include <sys/ddi_impldefs.h> 34327952Sdim#include <sys/jbusppm.h> 35327952Sdim#include <sys/ddi.h> 36327952Sdim#include <sys/sunddi.h> 37353358Sdim 38353358Sdim/* 39309124Sdim * JBus Power Management Driver 40309124Sdim * 41309124Sdim * jbusppm driver initiates the JBus clock speed change 42314564Sdim * as part of the protocol to adjust the clock speed on 43314564Sdim * all JBus resident devices. 44314564Sdim * 45341825Sdim * jbusppm driver is loaded because of the explicit dependency 46277323Sdim * defined in PPM driver. 47341825Sdim */ 48277323Sdim 49277323Sdim/* 50277323Sdim * Configuration Function prototypes and data structures 51277323Sdim */ 52309124Sdimstatic int jbppm_attach(dev_info_t *, ddi_attach_cmd_t); 53341825Sdimstatic int jbppm_detach(dev_info_t *, ddi_detach_cmd_t); 54309124Sdimstatic int jbppm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 55309124Sdimstatic int jbppm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p); 56341825Sdimstatic int jbppm_close(dev_t dev, int flag, int otyp, cred_t *cred_p); 57309124Sdimstatic int jbppm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 58314564Sdim 59341825Sdim/* 60314564Sdim * Configuration data structures 61314564Sdim */ 62341825Sdimstatic struct cb_ops jbppm_cbops = { 63314564Sdim jbppm_open, /* open */ 64314564Sdim jbppm_close, /* close */ 65341825Sdim nodev, /* strategy */ 66314564Sdim nodev, /* print */ 67314564Sdim nodev, /* dump */ 68314564Sdim nodev, /* read */ 69314564Sdim nodev, /* write */ 70314564Sdim jbppm_ioctl, /* ioctl */ 71277323Sdim nodev, /* devmap */ 72277323Sdim nodev, /* mmap */ 73277323Sdim nodev, /* segmap */ 74277323Sdim 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 83static 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 98extern struct mod_ops mod_driverops; 99 100static struct modldrv modldrv = { 101 &mod_driverops, 102 "JBus ppm driver", 103 &jbppm_ops, 104}; 105 106static struct modlinkage modlinkage = { 107 MODREV_1, 108 &modldrv, 109 NULL 110}; 111 112/* 113 * Local functions 114 */ 115static void jbppm_next_speed(dev_info_t *, uint_t); 116static 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 */ 126static void *jbppm_statep; 127static 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 */ 135static const uint64_t jbus_clock_masks[] = { 136 JBUS_ESTAR_CNTL_32, 137 JBUS_ESTAR_CNTL_2, 138 JBUS_ESTAR_CNTL_1 139}; 140 141int 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 162int 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 176int 177_info(struct modinfo *modinfop) 178{ 179 return (mod_info(&modlinkage, modinfop)); 180} 181 182 183 184/* 185 * Driver attach(9e) entry point 186 */ 187static int 188jbppm_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 262doerrs: 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 */ 283static int 284jbppm_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 */ 314static int 315jbppm_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 */ 332static int 333jbppm_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 */ 360static int 361jbppm_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 */ 383static int 384jbppm_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 */ 422static void 423jbppm_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 */ 462static int 463jbppm_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