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