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/sunddi.h>
31 #include <sys/conf.h> /* req. by dev_ops flags MTSAFE etc. */
32 #include <sys/ddi.h>
33 #include <sys/file.h>
34 #include <sys/note.h>
35 #include <sys/i2c/clients/i2c_client.h>
36 #include <sys/i2c/clients/ssc050.h>
37
38 #define SSC050_NUM_PORTS 5
39 #define SSC050_DATADIRECTION_REG(port) (0x10 | (port))
40 #define SSC050_COUNT_REG(port) (0x32 | ((port) << 2))
41 #define SSC050_GP_REG(port) (port)
42 #define SSC050_BIT_REG(port, bit) (SSC050_PORT_BIT_REG(port) | (bit))
43
44 #define SSC050_FAN_SPEED(div, count) (1200000 / ((count) * (1<<(div))))
45 #define SSC050_FAN_CONTROL_ENABLE 0x80
46 #define SSC050_FAN_CONTROL_DIVISOR 0x03
47
48 #define SSC050_DATADIRECTION_BIT 0x02
49
50 struct ssc050_unit {
51 kmutex_t mutex;
52 int oflag;
53 i2c_client_hdl_t hdl;
54 char name[12];
55 };
56
57 #ifdef DEBUG
58
59 static int ssc050debug = 0;
60 #define D1CMN_ERR(ARGS) if (ssc050debug & 0x01) cmn_err ARGS;
61 #define D2CMN_ERR(ARGS) if (ssc050debug & 0x02) cmn_err ARGS;
62 #define D3CMN_ERR(ARGS) if (ssc050debug & 0x04) cmn_err ARGS;
63
64 #else
65
66 #define D1CMN_ERR(ARGS)
67 #define D2CMN_ERR(ARGS)
68 #define D3CMN_ERR(ARGS)
69
70 #endif
71
72 static void *ssc050soft_statep;
73
74 static int ssc050_do_attach(dev_info_t *);
75 static int ssc050_do_detach(dev_info_t *);
76 static int ssc050_set(struct ssc050_unit *, int, uchar_t);
77 static int ssc050_get(struct ssc050_unit *, int, uchar_t *, int);
78
79 /*
80 * cb ops
81 */
82 static int ssc050_open(dev_t *, int, int, cred_t *);
83 static int ssc050_close(dev_t, int, int, cred_t *);
84 static int ssc050_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
85
86
87 static struct cb_ops ssc050_cbops = {
88 ssc050_open, /* open */
89 ssc050_close, /* close */
90 nodev, /* strategy */
91 nodev, /* print */
92 nodev, /* dump */
93 nodev, /* read */
94 nodev, /* write */
95 ssc050_ioctl, /* ioctl */
96 nodev, /* devmap */
97 nodev, /* mmap */
98 nodev, /* segmap */
99 nochpoll, /* poll */
100 ddi_prop_op, /* cb_prop_op */
101 NULL, /* streamtab */
102 D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */
103 CB_REV, /* rev */
104 nodev, /* int (*cb_aread)() */
105 nodev /* int (*cb_awrite)() */
106 };
107
108 /*
109 * dev ops
110 */
111 static int ssc050_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
112 void **result);
113 static int ssc050_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
114 static int ssc050_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
115
116 static struct dev_ops ssc050_ops = {
117 DEVO_REV,
118 0,
119 ssc050_info,
120 nulldev,
121 nulldev,
122 ssc050_attach,
123 ssc050_detach,
124 nodev,
125 &ssc050_cbops,
126 NULL,
127 NULL,
128 ddi_quiesce_not_needed, /* quiesce */
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",
136 &ssc050_ops
137 };
138
139 static struct modlinkage ssc050_modlinkage = {
140 MODREV_1,
141 &ssc050_modldrv,
142 0
143 };
144
145
146 int
_init(void)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
_fini(void)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
_info(struct modinfo * modinfop)172 _info(struct modinfo *modinfop)
173 {
174 return (mod_info(&ssc050_modlinkage, modinfop));
175 }
176
177 static int
ssc050_open(dev_t * devp,int flags,int otyp,cred_t * credp)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
ssc050_close(dev_t dev,int flags,int otyp,cred_t * credp)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
ssc050_get(struct ssc050_unit * unitp,int reg,uchar_t * byte,int flags)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
ssc050_set(struct ssc050_unit * unitp,int reg,uchar_t byte)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
ssc050_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)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
ssc050_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)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
ssc050_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
ssc050_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
ssc050_do_attach(dev_info_t * dip)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
ssc050_do_detach(dev_info_t * dip)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
ssc050_get_port_bit(dev_info_t * dip,int port,int bit,uchar_t * rval,int flags)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