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