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 28 #include <sys/stat.h> /* ddi_create_minor_node S_IFCHR */ 29 #include <sys/modctl.h> /* for modldrv */ 30 #include <sys/open.h> /* for open params. */ 31 #include <sys/types.h> 32 #include <sys/kmem.h> 33 #include <sys/sunddi.h> 34 #include <sys/conf.h> /* req. by dev_ops flags MTSAFE etc. */ 35 #include <sys/ddi.h> 36 #include <sys/file.h> 37 #include <sys/note.h> 38 39 #include <sys/i2c/clients/pcf8574_impl.h> 40 41 static void *pcf8574soft_statep; 42 43 static int pcf8574_do_attach(dev_info_t *); 44 static int pcf8574_do_detach(dev_info_t *); 45 static int pcf8574_do_resume(void); 46 static int pcf8574_do_suspend(void); 47 static int pcf8574_get(struct pcf8574_unit *, uchar_t *); 48 static int pcf8574_set(struct pcf8574_unit *, uchar_t); 49 50 static void littleneck_abort_seq_handler(char *msg); 51 extern void (*abort_seq_handler)(); 52 53 static void littleneck_ks_poll(void *); 54 55 /* 56 * cb ops (only need ioctl) 57 */ 58 static int pcf8574_open(dev_t *, int, int, cred_t *); 59 static int pcf8574_close(dev_t, int, int, cred_t *); 60 static int pcf8574_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 61 62 static struct cb_ops pcf8574_cbops = { 63 pcf8574_open, /* open */ 64 pcf8574_close, /* close */ 65 nodev, /* strategy */ 66 nodev, /* print */ 67 nodev, /* dump */ 68 nodev, /* read */ 69 nodev, /* write */ 70 pcf8574_ioctl, /* ioctl */ 71 nodev, /* devmap */ 72 nodev, /* mmap */ 73 nodev, /* segmap */ 74 nochpoll, /* poll */ 75 ddi_prop_op, /* cb_prop_op */ 76 NULL, /* streamtab */ 77 D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */ 78 CB_REV, /* rev */ 79 nodev, /* int (*cb_aread)() */ 80 nodev /* int (*cb_awrite)() */ 81 }; 82 83 /* 84 * dev ops 85 */ 86 static int pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); 87 static int pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); 88 89 static struct dev_ops pcf8574_ops = { 90 DEVO_REV, 91 0, 92 ddi_getinfo_1to1, 93 nulldev, 94 nulldev, 95 pcf8574_attach, 96 pcf8574_detach, 97 nodev, 98 &pcf8574_cbops, 99 NULL, /* bus_ops */ 100 NULL, /* power */ 101 ddi_quiesce_not_needed, /* quiesce */ 102 }; 103 104 extern struct mod_ops mod_driverops; 105 106 static struct modldrv pcf8574_modldrv = { 107 &mod_driverops, /* type of module - driver */ 108 "PCF8574 i2c device driver: v1.9", 109 &pcf8574_ops 110 }; 111 112 static struct modlinkage pcf8574_modlinkage = { 113 MODREV_1, 114 &pcf8574_modldrv, 115 0 116 }; 117 118 #define LNECK_KEY_POLL_BIT 5 119 #define LNECK_KEY_POLL_INTVL 10 /* 10 seconds poll interval */ 120 static timeout_id_t keypoll_timeout_id; 121 static clock_t keypoll_timeout_hz; 122 static boolean_t key_locked_bit; 123 124 125 int 126 _init(void) 127 { 128 int error; 129 130 error = mod_install(&pcf8574_modlinkage); 131 132 if (!error) 133 (void) ddi_soft_state_init(&pcf8574soft_statep, 134 sizeof (struct pcf8574_unit), 1); 135 return (error); 136 } 137 138 int 139 _fini(void) 140 { 141 int error; 142 143 error = mod_remove(&pcf8574_modlinkage); 144 if (!error) 145 ddi_soft_state_fini(&pcf8574soft_statep); 146 147 return (error); 148 } 149 150 int 151 _info(struct modinfo *modinfop) 152 { 153 return (mod_info(&pcf8574_modlinkage, modinfop)); 154 } 155 156 static int 157 pcf8574_open(dev_t *devp, int flags, int otyp, cred_t *credp) 158 { 159 _NOTE(ARGUNUSED(credp)) 160 struct pcf8574_unit *unitp; 161 int instance; 162 int error = 0; 163 164 D1CMN_ERR((CE_WARN, "Opening the PCF8574 device\n")); 165 166 instance = getminor(*devp); 167 168 if (instance < 0) { 169 return (ENXIO); 170 } 171 172 unitp = (struct pcf8574_unit *) 173 ddi_get_soft_state(pcf8574soft_statep, instance); 174 175 if (unitp == NULL) { 176 return (ENXIO); 177 } 178 179 if (otyp != OTYP_CHR) { 180 return (EINVAL); 181 } 182 183 mutex_enter(&unitp->pcf8574_mutex); 184 185 if (flags & FEXCL) { 186 if (unitp->pcf8574_oflag != 0) { 187 error = EBUSY; 188 } else { 189 unitp->pcf8574_oflag = FEXCL; 190 } 191 } else { 192 if (unitp->pcf8574_oflag == FEXCL) { 193 error = EBUSY; 194 } else { 195 unitp->pcf8574_oflag = FOPEN; 196 } 197 } 198 199 mutex_exit(&unitp->pcf8574_mutex); 200 201 return (error); 202 } 203 204 static int 205 pcf8574_close(dev_t dev, int flags, int otyp, cred_t *credp) 206 { 207 _NOTE(ARGUNUSED(flags, otyp, credp)) 208 struct pcf8574_unit *unitp; 209 int instance; 210 211 instance = getminor(dev); 212 213 if (instance < 0) { 214 return (ENXIO); 215 } 216 unitp = (struct pcf8574_unit *) 217 ddi_get_soft_state(pcf8574soft_statep, instance); 218 219 if (unitp == NULL) { 220 return (ENXIO); 221 } 222 223 mutex_enter(&unitp->pcf8574_mutex); 224 225 unitp->pcf8574_oflag = 0; 226 227 mutex_exit(&unitp->pcf8574_mutex); 228 return (DDI_SUCCESS); 229 } 230 231 static int 232 pcf8574_get(struct pcf8574_unit *unitp, uchar_t *byte) { 233 i2c_transfer_t *i2c_tran_pointer; 234 int err = I2C_SUCCESS; 235 236 D1CMN_ERR((CE_WARN, "Entered the pcf8574_get routine\n")); 237 238 (void) i2c_transfer_alloc(unitp->pcf8574_hdl, &i2c_tran_pointer, 239 0, 1, I2C_SLEEP); 240 if (i2c_tran_pointer == NULL) { 241 D2CMN_ERR((CE_WARN, "%s: Failed in pcf8574_get " 242 "i2c_tran_pointer not allocated\n", 243 unitp->pcf8574_name)); 244 return (ENOMEM); 245 } 246 247 i2c_tran_pointer->i2c_flags = I2C_RD; 248 249 err = i2c_transfer(unitp->pcf8574_hdl, i2c_tran_pointer); 250 if (err) { 251 D2CMN_ERR((CE_WARN, "%s: Failed in the i2c_transfer routine\n", 252 unitp->pcf8574_name)); 253 i2c_transfer_free(unitp->pcf8574_hdl, i2c_tran_pointer); 254 return (err); 255 } 256 257 D1CMN_ERR((CE_WARN, "Back from a transfer value is %x\n", 258 i2c_tran_pointer->i2c_rbuf[0])); 259 *byte = i2c_tran_pointer->i2c_rbuf[0]; 260 261 i2c_transfer_free(unitp->pcf8574_hdl, i2c_tran_pointer); 262 return (err); 263 } 264 265 static int 266 pcf8574_set(struct pcf8574_unit *unitp, uchar_t byte) { 267 i2c_transfer_t *i2c_tran_pointer; 268 int err = I2C_SUCCESS; 269 270 (void) i2c_transfer_alloc(unitp->pcf8574_hdl, &i2c_tran_pointer, 271 1, 0, I2C_SLEEP); 272 if (i2c_tran_pointer == NULL) { 273 D2CMN_ERR((CE_WARN, "%s: Failed in pcf8574_set " 274 "i2c_tran_pointer not allocated\n", 275 unitp->pcf8574_name)); 276 return (ENOMEM); 277 } 278 279 i2c_tran_pointer->i2c_flags = I2C_WR; 280 i2c_tran_pointer->i2c_wbuf[0] = byte; 281 282 D1CMN_ERR((CE_NOTE, "%s: contains %x\n", unitp->pcf8574_name, 283 i2c_tran_pointer->i2c_wbuf[0])); 284 285 err = i2c_transfer(unitp->pcf8574_hdl, i2c_tran_pointer); 286 if (err) { 287 D2CMN_ERR((CE_WARN, "%s: Failed in the pcf8574_set" 288 " i2c_transfer routine\n", 289 unitp->pcf8574_name)); 290 i2c_transfer_free(unitp->pcf8574_hdl, i2c_tran_pointer); 291 return (err); 292 } 293 i2c_transfer_free(unitp->pcf8574_hdl, i2c_tran_pointer); 294 return (err); 295 } 296 297 static int 298 pcf8574_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, 299 cred_t *credp, int *rvalp) 300 { 301 _NOTE(ARGUNUSED(credp, rvalp)) 302 struct pcf8574_unit *unitp; 303 int instance; 304 int err = 0; 305 i2c_bit_t ioctl_bit; 306 i2c_port_t ioctl_port; 307 uchar_t byte; 308 309 if (arg == NULL) { 310 D2CMN_ERR((CE_WARN, "PCF8574: ioctl: arg passed in to ioctl " 311 "= NULL\n")); 312 err = EINVAL; 313 return (err); 314 } 315 316 instance = getminor(dev); 317 unitp = (struct pcf8574_unit *) 318 ddi_get_soft_state(pcf8574soft_statep, instance); 319 if (unitp == NULL) { 320 cmn_err(CE_WARN, "PCF8574: ioctl: unitp not filled\n"); 321 return (ENOMEM); 322 } 323 324 mutex_enter(&unitp->pcf8574_mutex); 325 326 switch (cmd) { 327 case I2C_GET_PORT: 328 if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_port, 329 sizeof (i2c_port_t), mode) != DDI_SUCCESS) { 330 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_GET_PORT" 331 " ddi_copyin routine\n", 332 unitp->pcf8574_name)); 333 err = EFAULT; 334 break; 335 } 336 337 err = pcf8574_get(unitp, &byte); 338 if (err != I2C_SUCCESS) { 339 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_GET_PORT" 340 " pcf8574_get routine\n", 341 unitp->pcf8574_name)); 342 break; 343 } 344 345 ioctl_port.value = byte; 346 if (ddi_copyout((caddr_t)&ioctl_port, (caddr_t)arg, 347 sizeof (i2c_port_t), mode) != DDI_SUCCESS) { 348 D2CMN_ERR((CE_WARN, "%s: Failed in I2C_GET_PORT " 349 "ddi_copyout routine\n", 350 unitp->pcf8574_name)); 351 err = EFAULT; 352 } 353 354 D1CMN_ERR((CE_NOTE, "%s: contains %x\n", unitp->pcf8574_name, 355 byte)); 356 break; 357 358 case I2C_SET_PORT: 359 if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_port, 360 sizeof (uint8_t), mode) != DDI_SUCCESS) { 361 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_SET_PORT" 362 "ddi_cpoyin routine\n", 363 unitp->pcf8574_name)); 364 err = EFAULT; 365 break; 366 } 367 368 err = pcf8574_set(unitp, ioctl_port.value); 369 if (err != I2C_SUCCESS) { 370 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_SET_PORT" 371 " pcf8574_set routine\n", 372 unitp->pcf8574_name)); 373 break; 374 } 375 break; 376 377 case I2C_GET_BIT: 378 if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_bit, 379 sizeof (i2c_bit_t), mode) != DDI_SUCCESS) { 380 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_GET_BIT" 381 " ddi_copyin routine\n", 382 unitp->pcf8574_name)); 383 err = EFAULT; 384 break; 385 } 386 387 if (ioctl_bit.bit_num > 7) { 388 D2CMN_ERR((CE_WARN, "%s: In I2C_GET_BIT bit num" 389 " was not between 0 and 7\n", 390 unitp->pcf8574_name)); 391 err = EIO; 392 break; 393 } 394 395 err = pcf8574_get(unitp, &byte); 396 if (err != I2C_SUCCESS) { 397 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_GET_BIT" 398 " pcf8574_get routine\n", 399 unitp->pcf8574_name)); 400 break; 401 } 402 403 D1CMN_ERR((CE_NOTE, "%s: byte returned from device is %x\n", 404 unitp->pcf8574_name, byte)); 405 ioctl_bit.bit_value = (boolean_t)PCF8574_BIT_READ_MASK(byte, 406 ioctl_bit.bit_num); 407 D1CMN_ERR((CE_NOTE, "%s: byte now contains %x\n", 408 unitp->pcf8574_name, byte)); 409 410 if (ddi_copyout((caddr_t)&ioctl_bit, (caddr_t)arg, 411 sizeof (i2c_bit_t), mode) != DDI_SUCCESS) { 412 D2CMN_ERR((CE_WARN, "%s: Failed in I2C_GET_BIT" 413 " ddi_copyout routine\n", 414 unitp->pcf8574_name)); 415 err = EFAULT; 416 } 417 break; 418 419 case I2C_SET_BIT: 420 if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_bit, 421 sizeof (i2c_bit_t), mode) != DDI_SUCCESS) { 422 D2CMN_ERR((CE_WARN, "%s: Failed in I2C_SET_BIT" 423 " ddi_copyin routine\n", 424 unitp->pcf8574_name)); 425 err = EFAULT; 426 break; 427 } 428 429 if (ioctl_bit.bit_num > 7) { 430 D2CMN_ERR((CE_WARN, "%s: I2C_SET_BIT: bit_num sent" 431 " in was not between 0 and 7", 432 unitp->pcf8574_name)); 433 err = EIO; 434 break; 435 } 436 437 err = pcf8574_get(unitp, &byte); 438 if (err != I2C_SUCCESS) { 439 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_SET_BIT" 440 " pcf8574_get routine\n", 441 unitp->pcf8574_name)); 442 break; 443 } 444 445 D1CMN_ERR((CE_NOTE, "%s: byte returned from device is %x\n", 446 unitp->pcf8574_name, byte)); 447 byte = PCF8574_BIT_WRITE_MASK(byte, ioctl_bit.bit_num, 448 ioctl_bit.bit_value); 449 D1CMN_ERR((CE_NOTE, "%s: byte after shifting is %x\n", 450 unitp->pcf8574_name, byte)); 451 452 err = pcf8574_set(unitp, byte); 453 if (err != I2C_SUCCESS) { 454 D2CMN_ERR((CE_WARN, "%s: Failed in the I2C_SET_BIT" 455 " pcf8574_set routine\n", 456 unitp->pcf8574_name)); 457 break; 458 } 459 break; 460 461 default: 462 D2CMN_ERR((CE_WARN, "%s: Invalid IOCTL cmd: %x\n", 463 unitp->pcf8574_name, cmd)); 464 err = EINVAL; 465 } 466 467 mutex_exit(&unitp->pcf8574_mutex); 468 return (err); 469 } 470 471 static int 472 pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 473 { 474 switch (cmd) { 475 case DDI_ATTACH: 476 return (pcf8574_do_attach(dip)); 477 case DDI_RESUME: 478 return (pcf8574_do_resume()); 479 default: 480 return (DDI_FAILURE); 481 } 482 } 483 484 static int 485 pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 486 { 487 switch (cmd) { 488 case DDI_DETACH: 489 return (pcf8574_do_detach(dip)); 490 case DDI_SUSPEND: 491 return (pcf8574_do_suspend()); 492 default: 493 return (DDI_FAILURE); 494 } 495 } 496 497 static int 498 pcf8574_do_attach(dev_info_t *dip) 499 { 500 struct pcf8574_unit *unitp; 501 int instance, err; 502 uint_t len; 503 int *regs; 504 505 instance = ddi_get_instance(dip); 506 507 if (ddi_soft_state_zalloc(pcf8574soft_statep, instance) != 0) { 508 cmn_err(CE_WARN, "%s%d: failed to zalloc softstate\n", 509 ddi_get_name(dip), instance); 510 return (DDI_FAILURE); 511 } 512 513 unitp = ddi_get_soft_state(pcf8574soft_statep, instance); 514 515 if (unitp == NULL) { 516 cmn_err(CE_WARN, "%s%d: unitp not filled\n", 517 ddi_get_name(dip), instance); 518 return (ENOMEM); 519 } 520 521 (void) snprintf(unitp->pcf8574_name, sizeof (unitp->pcf8574_name), 522 "%s%d", ddi_node_name(dip), instance); 523 524 525 if (ddi_create_minor_node(dip, "pcf8574", S_IFCHR, instance, 526 "ddi_i2c:ioexp", NULL) == DDI_FAILURE) { 527 cmn_err(CE_WARN, "%s ddi_create_minor_node failed for " 528 "%s\n", unitp->pcf8574_name, "pcf8574"); 529 ddi_soft_state_free(pcf8574soft_statep, instance); 530 531 return (DDI_FAILURE); 532 } 533 534 if (i2c_client_register(dip, &unitp->pcf8574_hdl) != I2C_SUCCESS) { 535 ddi_remove_minor_node(dip, NULL); 536 ddi_soft_state_free(pcf8574soft_statep, instance); 537 538 return (DDI_FAILURE); 539 } 540 541 err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 542 DDI_PROP_DONTPASS, 543 "reg", (int **)®s, &len); 544 if (err != DDI_PROP_SUCCESS) { 545 return (DDI_FAILURE); 546 } 547 548 /* 549 * regs[0] contains the bus number and regs[1] contains the device 550 * address of the i2c device. 0x7c is the device address of the 551 * i2c device from which the key switch position is read. 552 */ 553 if (regs[0] == 0 && regs[1] == 0x7c) { 554 abort_seq_handler = littleneck_abort_seq_handler; 555 keypoll_timeout_hz = 556 drv_usectohz(LNECK_KEY_POLL_INTVL * MICROSEC); 557 littleneck_ks_poll(unitp); 558 } 559 560 ddi_prop_free(regs); 561 562 mutex_init(&unitp->pcf8574_mutex, NULL, MUTEX_DRIVER, NULL); 563 564 return (DDI_SUCCESS); 565 } 566 567 static int 568 pcf8574_do_resume() 569 { 570 int ret = DDI_SUCCESS; 571 572 return (ret); 573 } 574 575 static int 576 pcf8574_do_suspend() 577 { 578 int ret = DDI_SUCCESS; 579 580 return (ret); 581 } 582 583 static int 584 pcf8574_do_detach(dev_info_t *dip) 585 { 586 struct pcf8574_unit *unitp; 587 int instance; 588 589 instance = ddi_get_instance(dip); 590 591 unitp = ddi_get_soft_state(pcf8574soft_statep, instance); 592 593 if (unitp == NULL) { 594 cmn_err(CE_WARN, "%s%d: unitp not filled\n", 595 ddi_get_name(dip), instance); 596 return (ENOMEM); 597 } 598 599 (void) untimeout(keypoll_timeout_id); 600 601 i2c_client_unregister(unitp->pcf8574_hdl); 602 603 ddi_remove_minor_node(dip, NULL); 604 605 mutex_destroy(&unitp->pcf8574_mutex); 606 607 ddi_soft_state_free(pcf8574soft_statep, instance); 608 609 return (DDI_SUCCESS); 610 611 } 612 613 static void 614 littleneck_ks_poll(void *arg) 615 { 616 struct pcf8574_unit *unitp = (struct pcf8574_unit *)arg; 617 uint8_t byte; 618 619 mutex_enter(&unitp->pcf8574_mutex); 620 621 if (pcf8574_get(unitp, &byte) != I2C_SUCCESS) { 622 D2CMN_ERR((CE_WARN, "%s: Failed in littleneck_ks_poll" 623 " pcf8574_get routine\n", unitp->pcf8574_name)); 624 mutex_exit(&unitp->pcf8574_mutex); 625 return; 626 } 627 628 /* 629 * 5th bit in the byte is the key LOCKED position 630 */ 631 key_locked_bit = (boolean_t)PCF8574_BIT_READ_MASK(byte, 632 LNECK_KEY_POLL_BIT); 633 634 keypoll_timeout_id = (timeout(littleneck_ks_poll, 635 (caddr_t)unitp, keypoll_timeout_hz)); 636 637 mutex_exit(&unitp->pcf8574_mutex); 638 } 639 640 static void 641 littleneck_abort_seq_handler(char *msg) 642 { 643 644 if (key_locked_bit == 0) 645 cmn_err(CE_CONT, "KEY in LOCKED position, " 646 "ignoring debug enter sequence\n"); 647 else { 648 D1CMN_ERR((CE_CONT, "debug enter sequence \n")); 649 debug_enter(msg); 650 } 651 } 652