xref: /illumos-gate/usr/src/uts/sun4u/io/i2c/clients/ssc050.c (revision a07094369b21309434206d9b3601d162693466fc)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/stat.h>		/* ddi_create_minor_node S_IFCHR */
30 #include <sys/modctl.h>		/* for modldrv */
31 #include <sys/open.h>		/* for open params.	 */
32 #include <sys/types.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 #include <sys/i2c/clients/i2c_client.h>
39 #include <sys/i2c/clients/ssc050.h>
40 
41 #define	SSC050_NUM_PORTS		5
42 #define	SSC050_DATADIRECTION_REG(port)	(0x10 | (port))
43 #define	SSC050_COUNT_REG(port)		(0x32 | ((port) << 2))
44 #define	SSC050_GP_REG(port)		(port)
45 #define	SSC050_BIT_REG(port, bit)	(SSC050_PORT_BIT_REG(port) | (bit))
46 
47 #define	SSC050_FAN_SPEED(div, count)	(1200000 / ((count) * (1<<(div))))
48 #define	SSC050_FAN_CONTROL_ENABLE	0x80
49 #define	SSC050_FAN_CONTROL_DIVISOR	0x03
50 
51 #define	SSC050_DATADIRECTION_BIT	0x02
52 
53 struct ssc050_unit {
54 	kmutex_t		mutex;
55 	int			oflag;
56 	i2c_client_hdl_t	hdl;
57 	char			name[12];
58 };
59 
60 #ifdef DEBUG
61 
62 static int ssc050debug = 0;
63 #define	D1CMN_ERR(ARGS) if (ssc050debug & 0x01) cmn_err ARGS;
64 #define	D2CMN_ERR(ARGS) if (ssc050debug & 0x02) cmn_err ARGS;
65 #define	D3CMN_ERR(ARGS) if (ssc050debug & 0x04) cmn_err ARGS;
66 
67 #else
68 
69 #define	D1CMN_ERR(ARGS)
70 #define	D2CMN_ERR(ARGS)
71 #define	D3CMN_ERR(ARGS)
72 
73 #endif
74 
75 static void *ssc050soft_statep;
76 
77 static int ssc050_do_attach(dev_info_t *);
78 static int ssc050_do_detach(dev_info_t *);
79 static int ssc050_set(struct ssc050_unit *, int, uchar_t);
80 static int ssc050_get(struct ssc050_unit *, int, uchar_t *, int);
81 
82 /*
83  * cb ops
84  */
85 static int ssc050_open(dev_t *, int, int, cred_t *);
86 static int ssc050_close(dev_t, int, int, cred_t *);
87 static int ssc050_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
88 
89 
90 static struct cb_ops ssc050_cbops = {
91 	ssc050_open,			/* open  */
92 	ssc050_close,			/* close */
93 	nodev,				/* strategy */
94 	nodev,				/* print */
95 	nodev,				/* dump */
96 	nodev,				/* read */
97 	nodev,				/* write */
98 	ssc050_ioctl,			/* ioctl */
99 	nodev,				/* devmap */
100 	nodev,				/* mmap */
101 	nodev,				/* segmap */
102 	nochpoll,			/* poll */
103 	ddi_prop_op,			/* cb_prop_op */
104 	NULL,				/* streamtab */
105 	D_NEW | D_MP | D_HOTPLUG,	/* Driver compatibility flag */
106 	CB_REV,				/* rev */
107 	nodev,				/* int (*cb_aread)() */
108 	nodev				/* int (*cb_awrite)() */
109 };
110 
111 /*
112  * dev ops
113  */
114 static int ssc050_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
115 		void **result);
116 static int ssc050_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
117 static int ssc050_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
118 
119 static struct dev_ops ssc050_ops = {
120 	DEVO_REV,
121 	0,
122 	ssc050_info,
123 	nulldev,
124 	nulldev,
125 	ssc050_attach,
126 	ssc050_detach,
127 	nodev,
128 	&ssc050_cbops,
129 	NULL
130 };
131 
132 extern struct mod_ops mod_driverops;
133 
134 static struct modldrv ssc050_modldrv = {
135 	&mod_driverops,			/* type of module - driver */
136 	"SSC050 i2c device driver: v%I%",
137 	&ssc050_ops
138 };
139 
140 static struct modlinkage ssc050_modlinkage = {
141 	MODREV_1,
142 	&ssc050_modldrv,
143 	0
144 };
145 
146 
147 int
148 _init(void)
149 {
150 	int error;
151 
152 	error = mod_install(&ssc050_modlinkage);
153 
154 	if (!error)
155 		(void) ddi_soft_state_init(&ssc050soft_statep,
156 			sizeof (struct ssc050_unit), 1);
157 	return (error);
158 }
159 
160 int
161 _fini(void)
162 {
163 	int error;
164 
165 	error = mod_remove(&ssc050_modlinkage);
166 	if (!error)
167 		ddi_soft_state_fini(&ssc050soft_statep);
168 
169 	return (error);
170 }
171 
172 int
173 _info(struct modinfo *modinfop)
174 {
175 	return (mod_info(&ssc050_modlinkage, modinfop));
176 }
177 
178 static int
179 ssc050_open(dev_t *devp, int flags, int otyp, cred_t *credp)
180 {
181 	_NOTE(ARGUNUSED(credp))
182 
183 	struct ssc050_unit *unitp;
184 	int instance;
185 	int error = 0;
186 
187 	instance = MINOR_TO_INST(getminor(*devp));
188 
189 	if (instance < 0) {
190 		return (ENXIO);
191 	}
192 
193 	unitp = (struct ssc050_unit *)
194 		ddi_get_soft_state(ssc050soft_statep, instance);
195 
196 	if (unitp == NULL) {
197 		return (ENXIO);
198 	}
199 
200 	if (otyp != OTYP_CHR) {
201 		return (EINVAL);
202 	}
203 
204 	mutex_enter(&unitp->mutex);
205 
206 	if (flags & FEXCL) {
207 		if (unitp->oflag != 0) {
208 			error = EBUSY;
209 		} else {
210 			unitp->oflag = FEXCL;
211 		}
212 	} else {
213 		if (unitp->oflag == FEXCL) {
214 			error = EBUSY;
215 		} else {
216 			unitp->oflag = FOPEN;
217 		}
218 	}
219 
220 	mutex_exit(&unitp->mutex);
221 
222 	return (error);
223 }
224 
225 static int
226 ssc050_close(dev_t dev, int flags, int otyp, cred_t *credp)
227 {
228 	_NOTE(ARGUNUSED(flags, otyp, credp))
229 
230 	struct ssc050_unit *unitp;
231 	int instance;
232 
233 	instance = MINOR_TO_INST(getminor(dev));
234 
235 	if (instance < 0) {
236 		return (ENXIO);
237 	}
238 
239 	unitp = (struct ssc050_unit *)
240 		ddi_get_soft_state(ssc050soft_statep, instance);
241 
242 	if (unitp == NULL) {
243 		return (ENXIO);
244 	}
245 
246 	mutex_enter(&unitp->mutex);
247 
248 	unitp->oflag = 0;
249 
250 	mutex_exit(&unitp->mutex);
251 	return (DDI_SUCCESS);
252 }
253 
254 static int
255 ssc050_get(struct ssc050_unit *unitp, int reg, uchar_t *byte, int flags)
256 {
257 	i2c_transfer_t		*i2c_tran_pointer;
258 	int			err;
259 
260 	(void) i2c_transfer_alloc(unitp->hdl, &i2c_tran_pointer,
261 		1, 1, flags);
262 	if (i2c_tran_pointer == NULL) {
263 		return (ENOMEM);
264 	}
265 
266 	i2c_tran_pointer->i2c_flags = I2C_WR_RD;
267 	i2c_tran_pointer->i2c_wbuf[0] = (uchar_t)reg;
268 	err = i2c_transfer(unitp->hdl, i2c_tran_pointer);
269 	if (err) {
270 		D2CMN_ERR((CE_WARN, "%s: ssc050_get failed reg=%x",
271 			unitp->name, reg));
272 	} else {
273 		*byte = i2c_tran_pointer->i2c_rbuf[0];
274 	}
275 
276 	i2c_transfer_free(unitp->hdl, i2c_tran_pointer);
277 	return (err);
278 }
279 
280 static int
281 ssc050_set(struct ssc050_unit *unitp, int reg, uchar_t byte)
282 {
283 	i2c_transfer_t		*i2c_tran_pointer;
284 	int			err;
285 
286 	(void) i2c_transfer_alloc(unitp->hdl, &i2c_tran_pointer,
287 		2, 0, I2C_SLEEP);
288 	if (i2c_tran_pointer == NULL) {
289 		D2CMN_ERR((CE_WARN, "%s: Failed in ssc050_set "
290 			"i2c_tran_pointer not allocated", unitp->name));
291 		return (ENOMEM);
292 	}
293 
294 	i2c_tran_pointer->i2c_flags = I2C_WR;
295 	i2c_tran_pointer->i2c_wbuf[0] = (uchar_t)reg;
296 	i2c_tran_pointer->i2c_wbuf[1] = byte;
297 	D1CMN_ERR((CE_NOTE, "%s: set reg %x to %x", unitp->name, reg, byte));
298 
299 	err = i2c_transfer(unitp->hdl, i2c_tran_pointer);
300 	if (err) {
301 		D2CMN_ERR((CE_WARN, "%s: Failed in the ssc050_set"
302 			" i2c_transfer routine", unitp->name));
303 	}
304 	i2c_transfer_free(unitp->hdl, i2c_tran_pointer);
305 	return (err);
306 }
307 
308 static int
309 ssc050_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
310 		int *rvalp)
311 {
312 	_NOTE(ARGUNUSED(credp, rvalp))
313 
314 	struct ssc050_unit	*unitp;
315 	int			err = 0;
316 	i2c_bit_t		ioctl_bit;
317 	i2c_port_t		ioctl_port;
318 	i2c_reg_t		ioctl_reg;
319 	int port = MINOR_TO_PORT(getminor(dev));
320 	int instance = MINOR_TO_INST(getminor(dev));
321 	uchar_t 		reg, val8;
322 	uchar_t 		control;
323 	uchar_t			fan_count;
324 	int			divisor;
325 	int32_t			fan_speed;
326 	uint8_t			inverted_mask;
327 
328 	if (arg == NULL) {
329 		D2CMN_ERR((CE_WARN, "SSC050: ioctl: arg passed in to ioctl "
330 				"= NULL"));
331 		return (EINVAL);
332 	}
333 	unitp = (struct ssc050_unit *)
334 		ddi_get_soft_state(ssc050soft_statep, instance);
335 
336 	if (unitp == NULL) {
337 		return (ENXIO);
338 	}
339 
340 	mutex_enter(&unitp->mutex);
341 
342 	D3CMN_ERR((CE_NOTE, "%s: ioctl: port = %d", unitp->name, port));
343 
344 	switch (cmd) {
345 	case I2C_GET_PORT:
346 		if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_port,
347 			sizeof (i2c_port_t), mode) != DDI_SUCCESS) {
348 			err = EFAULT;
349 			break;
350 		}
351 
352 		if (ioctl_port.direction == DIR_INPUT) {
353 			reg = SSC050_DATADIRECTION_REG(port);
354 
355 			err = ssc050_get(unitp, reg, &val8, I2C_SLEEP);
356 			if (err != I2C_SUCCESS) {
357 				break;
358 			}
359 
360 			if (val8 != ioctl_port.dir_mask) {
361 				D2CMN_ERR((CE_NOTE, "GET_PORT sleeping! "
362 					"wanted %x, had %x",
363 					ioctl_port.dir_mask, val8));
364 				err = ssc050_set(unitp, reg,
365 				    ioctl_port.dir_mask);
366 				if (err != I2C_SUCCESS) {
367 					break;
368 				}
369 				delay(10);
370 			}
371 		}
372 
373 		err = ssc050_get(unitp, port, &val8, I2C_SLEEP);
374 		if (err != I2C_SUCCESS) {
375 			break;
376 		}
377 		ioctl_port.value = val8;
378 		if (ddi_copyout((caddr_t)&ioctl_port, (caddr_t)arg,
379 			sizeof (i2c_port_t), mode) != DDI_SUCCESS) {
380 			err = EFAULT;
381 		}
382 		break;
383 
384 	case I2C_SET_PORT:
385 		if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_port,
386 			sizeof (i2c_port_t), mode) != DDI_SUCCESS) {
387 			err = EFAULT;
388 			break;
389 		}
390 
391 		reg = SSC050_DATADIRECTION_REG(port);
392 
393 		err = ssc050_get(unitp, reg, &val8, I2C_SLEEP);
394 		if (err != I2C_SUCCESS) {
395 			break;
396 		}
397 
398 		D1CMN_ERR((CE_NOTE, "%s: ioctl: Data Direction Register "
399 			"contains %x", unitp->name, val8));
400 
401 		inverted_mask = ioctl_port.dir_mask ^ 0xff;
402 		val8 = val8 & inverted_mask;
403 
404 		D1CMN_ERR((CE_NOTE, "%s: ioctl: Data Direction Register "
405 			"NOW contains %x", unitp->name, val8));
406 
407 		err = ssc050_set(unitp, reg, val8);
408 		if (err != I2C_SUCCESS) {
409 			break;
410 		}
411 
412 		err = ssc050_get(unitp, port, &val8, I2C_SLEEP);
413 		if (err != I2C_SUCCESS) {
414 			break;
415 		}
416 
417 		D1CMN_ERR((CE_NOTE, "%s: ioctl: GP Register "
418 			"contains %x", unitp->name, val8));
419 
420 		val8 = val8 & inverted_mask;
421 		val8 = val8 | ioctl_port.value;
422 
423 		D1CMN_ERR((CE_NOTE, "%s: ioctl: GP Register "
424 			"NOW contains %x", unitp->name, val8));
425 
426 		err = ssc050_set(unitp, SSC050_GP_REG(port), val8);
427 		break;
428 
429 	case I2C_GET_FAN_SPEED:
430 		err = ssc050_get(unitp, SSC050_FAN_CONTROL_REG(port),
431 		    &control, I2C_SLEEP);
432 		if (err != I2C_SUCCESS) {
433 			break;
434 		}
435 
436 		D1CMN_ERR((CE_NOTE, "%s: port %d: control = %x", unitp->name,
437 			port, control));
438 
439 		if (!(control & SSC050_FAN_CONTROL_ENABLE)) {
440 			err = EIO;
441 			break;
442 		}
443 
444 		err = ssc050_get(unitp, SSC050_COUNT_REG(port), &fan_count,
445 		    I2C_SLEEP);
446 		if (err != I2C_SUCCESS) {
447 			break;
448 		}
449 
450 		if (fan_count == 0) {
451 			D2CMN_ERR((CE_WARN, "%s: Failed in I2C_GET_FAN_SPEED "
452 					"i2c_rbuf = 0", unitp->name));
453 			err = EIO;
454 			break;
455 		}
456 		if (fan_count == 0xff) {
457 			fan_speed = 0;
458 			if (ddi_copyout((caddr_t)&fan_speed, (caddr_t)arg,
459 				sizeof (int32_t), mode) != DDI_SUCCESS) {
460 				err = EFAULT;
461 				break;
462 			}
463 			break;
464 		}
465 
466 		divisor = control & SSC050_FAN_CONTROL_DIVISOR;
467 		fan_speed = SSC050_FAN_SPEED(divisor, fan_count);
468 		if (ddi_copyout((caddr_t)&fan_speed, (caddr_t)arg,
469 			sizeof (int32_t), mode) != DDI_SUCCESS) {
470 			err = EFAULT;
471 		}
472 		break;
473 
474 	case I2C_GET_BIT:
475 		if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_bit,
476 			sizeof (i2c_bit_t), mode) != DDI_SUCCESS) {
477 			err = EFAULT;
478 			break;
479 		}
480 
481 		if (ioctl_bit.bit_num > 7) {
482 			err = EINVAL;
483 			break;
484 		}
485 
486 		reg = (uchar_t)SSC050_BIT_REG(port, ioctl_bit.bit_num);
487 		D3CMN_ERR((CE_NOTE, "%s: reg = %x", unitp->name, reg));
488 
489 		if (ioctl_bit.direction == DIR_INPUT) {
490 			err = ssc050_get(unitp, reg, &val8, I2C_SLEEP);
491 			if (err != I2C_SUCCESS) {
492 				break;
493 			}
494 
495 			if (!(val8 & SSC050_DATADIRECTION_BIT)) {
496 				D2CMN_ERR((CE_NOTE, "GET_PORT sleeping! "
497 					"wanted %x, had %x",
498 					val8 | SSC050_DATADIRECTION_BIT,
499 					val8));
500 				err = ssc050_set(unitp, reg,
501 				    val8 | SSC050_DATADIRECTION_BIT);
502 				    if (err != I2C_SUCCESS) {
503 					    break;
504 				    }
505 				    delay(10);
506 			}
507 		}
508 
509 		err = ssc050_get(unitp, reg, &val8, I2C_SLEEP);
510 		if (err != I2C_SUCCESS) {
511 			break;
512 		}
513 		D3CMN_ERR((CE_NOTE, "byte back from device = %x", val8));
514 		val8 = val8 & 0x01;
515 		ioctl_bit.bit_value = (boolean_t)val8;
516 		if (ddi_copyout((caddr_t)&ioctl_bit, (caddr_t)arg,
517 			sizeof (i2c_bit_t), mode) != DDI_SUCCESS) {
518 			err = EFAULT;
519 		}
520 		break;
521 
522 	case I2C_SET_BIT:
523 		if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_bit,
524 			sizeof (i2c_bit_t), mode) != DDI_SUCCESS) {
525 			err = EFAULT;
526 			break;
527 		}
528 
529 		if (ioctl_bit.bit_num > 7) {
530 			err = EINVAL;
531 			break;
532 		}
533 
534 		reg = (uchar_t)SSC050_BIT_REG(port, ioctl_bit.bit_num);
535 		D3CMN_ERR((CE_NOTE, "%s: reg = %x", unitp->name, reg));
536 
537 		val8 = (uchar_t)ioctl_bit.bit_value;
538 		err = ssc050_set(unitp, reg, val8);
539 		break;
540 
541 	case I2C_GET_REG:
542 		if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_reg,
543 			sizeof (i2c_reg_t), mode) != DDI_SUCCESS) {
544 			err = EFAULT;
545 			break;
546 		}
547 		err = ssc050_get(unitp, ioctl_reg.reg_num, &val8,
548 			I2C_SLEEP);
549 		if (err != I2C_SUCCESS) {
550 			break;
551 		}
552 
553 		ioctl_reg.reg_value = val8;
554 		if (ddi_copyout((caddr_t)&ioctl_reg, (caddr_t)arg,
555 			sizeof (i2c_reg_t), mode) != DDI_SUCCESS) {
556 			err = EFAULT;
557 		}
558 		break;
559 
560 	case I2C_SET_REG:
561 		if (ddi_copyin((caddr_t)arg, (caddr_t)&ioctl_reg,
562 			sizeof (i2c_reg_t), mode) != DDI_SUCCESS) {
563 			err = EFAULT;
564 			break;
565 		}
566 		err = ssc050_set(unitp, ioctl_reg.reg_num,
567 			ioctl_reg.reg_value);
568 		break;
569 
570 	default:
571 		D2CMN_ERR((CE_WARN, "%s: Invalid IOCTL cmd: %x",
572 			unitp->name, cmd));
573 		err = EINVAL;
574 	}
575 
576 	mutex_exit(&unitp->mutex);
577 	return (err);
578 }
579 
580 /* ARGSUSED */
581 static int
582 ssc050_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
583 {
584 	dev_t	dev;
585 	int	instance;
586 
587 	if (infocmd == DDI_INFO_DEVT2INSTANCE) {
588 		dev = (dev_t)arg;
589 		instance = MINOR_TO_INST(getminor(dev));
590 		*result = (void *)(uintptr_t)instance;
591 		return (DDI_SUCCESS);
592 	}
593 	return (DDI_FAILURE);
594 }
595 
596 static int
597 ssc050_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
598 {
599 	switch (cmd) {
600 	case DDI_ATTACH:
601 		return (ssc050_do_attach(dip));
602 	case DDI_RESUME:
603 		return (DDI_SUCCESS);
604 	default:
605 		return (DDI_FAILURE);
606 	}
607 }
608 
609 static int
610 ssc050_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
611 {
612 	switch (cmd) {
613 	case DDI_DETACH:
614 		return (ssc050_do_detach(dip));
615 
616 	case DDI_SUSPEND:
617 		return (DDI_SUCCESS);
618 
619 	default:
620 		return (DDI_FAILURE);
621 	}
622 }
623 
624 static int
625 ssc050_do_attach(dev_info_t *dip)
626 {
627 	struct ssc050_unit	*unitp;
628 	int			instance;
629 	char			name[MAXNAMELEN];
630 	minor_t			minor_number;
631 	int			i;
632 
633 	instance = ddi_get_instance(dip);
634 
635 	if (ddi_soft_state_zalloc(ssc050soft_statep, instance) != 0) {
636 		return (DDI_FAILURE);
637 	}
638 
639 	unitp = ddi_get_soft_state(ssc050soft_statep, instance);
640 
641 	(void) snprintf(unitp->name, sizeof (unitp->name),
642 			"%s%d", ddi_node_name(dip), instance);
643 
644 	for (i = 0; i < SSC050_NUM_PORTS; i++) {
645 		(void) sprintf(name, "port_%d", i);
646 
647 		minor_number = INST_TO_MINOR(instance) |
648 			PORT_TO_MINOR(I2C_PORT(i));
649 
650 		if (ddi_create_minor_node(dip, name, S_IFCHR, minor_number,
651 					"ddi_i2c:ioexp", NULL) == DDI_FAILURE) {
652 			cmn_err(CE_WARN, "%s: failed to create node for %s",
653 			    unitp->name, name);
654 			ddi_soft_state_free(ssc050soft_statep, instance);
655 			return (DDI_FAILURE);
656 		}
657 	}
658 
659 	if (i2c_client_register(dip, &unitp->hdl) != I2C_SUCCESS) {
660 		ddi_remove_minor_node(dip, NULL);
661 		ddi_soft_state_free(ssc050soft_statep, instance);
662 		return (DDI_FAILURE);
663 	}
664 
665 	mutex_init(&unitp->mutex, NULL, MUTEX_DRIVER, NULL);
666 
667 	return (DDI_SUCCESS);
668 }
669 
670 static int
671 ssc050_do_detach(dev_info_t *dip)
672 {
673 	struct ssc050_unit *unitp;
674 	int instance;
675 
676 	instance = ddi_get_instance(dip);
677 	unitp = ddi_get_soft_state(ssc050soft_statep, instance);
678 	i2c_client_unregister(unitp->hdl);
679 	ddi_remove_minor_node(dip, NULL);
680 	mutex_destroy(&unitp->mutex);
681 	ddi_soft_state_free(ssc050soft_statep, instance);
682 
683 	return (DDI_SUCCESS);
684 }
685 
686 int
687 ssc050_get_port_bit(dev_info_t *dip, int port, int bit, uchar_t *rval,
688 			int flags)
689 {
690 	struct ssc050_unit	*unitp;
691 	int			instance;
692 	int			reg = (uchar_t)SSC050_BIT_REG(port, bit);
693 
694 	if (rval == NULL || dip == NULL)
695 		return (EINVAL);
696 
697 	instance = ddi_get_instance(dip);
698 	unitp = ddi_get_soft_state(ssc050soft_statep, instance);
699 	return (ssc050_get(unitp, reg, rval, flags));
700 }
701