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
_init(void)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
_fini(void)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
_info(struct modinfo * modinfop)151 _info(struct modinfo *modinfop)
152 {
153 return (mod_info(&pcf8574_modlinkage, modinfop));
154 }
155
156 static int
pcf8574_open(dev_t * devp,int flags,int otyp,cred_t * credp)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
pcf8574_close(dev_t dev,int flags,int otyp,cred_t * credp)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
pcf8574_get(struct pcf8574_unit * unitp,uchar_t * byte)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
pcf8574_set(struct pcf8574_unit * unitp,uchar_t byte)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
pcf8574_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)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
pcf8574_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
pcf8574_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
pcf8574_do_attach(dev_info_t * dip)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
pcf8574_do_resume()568 pcf8574_do_resume()
569 {
570 int ret = DDI_SUCCESS;
571
572 return (ret);
573 }
574
575 static int
pcf8574_do_suspend()576 pcf8574_do_suspend()
577 {
578 int ret = DDI_SUCCESS;
579
580 return (ret);
581 }
582
583 static int
pcf8574_do_detach(dev_info_t * dip)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
littleneck_ks_poll(void * arg)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
littleneck_abort_seq_handler(char * msg)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