ssc050.c revision 7656:2621e50fdf4a
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#include <sys/stat.h>		/* ddi_create_minor_node S_IFCHR */
28#include <sys/modctl.h>		/* for modldrv */
29#include <sys/open.h>		/* for open params.	 */
30#include <sys/types.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#include <sys/i2c/clients/i2c_client.h>
37#include <sys/i2c/clients/ssc050.h>
38
39#define	SSC050_NUM_PORTS		5
40#define	SSC050_DATADIRECTION_REG(port)	(0x10 | (port))
41#define	SSC050_COUNT_REG(port)		(0x32 | ((port) << 2))
42#define	SSC050_GP_REG(port)		(port)
43#define	SSC050_BIT_REG(port, bit)	(SSC050_PORT_BIT_REG(port) | (bit))
44
45#define	SSC050_FAN_SPEED(div, count)	(1200000 / ((count) * (1<<(div))))
46#define	SSC050_FAN_CONTROL_ENABLE	0x80
47#define	SSC050_FAN_CONTROL_DIVISOR	0x03
48
49#define	SSC050_DATADIRECTION_BIT	0x02
50
51struct ssc050_unit {
52	kmutex_t		mutex;
53	int			oflag;
54	i2c_client_hdl_t	hdl;
55	char			name[12];
56};
57
58#ifdef DEBUG
59
60static int ssc050debug = 0;
61#define	D1CMN_ERR(ARGS) if (ssc050debug & 0x01) cmn_err ARGS;
62#define	D2CMN_ERR(ARGS) if (ssc050debug & 0x02) cmn_err ARGS;
63#define	D3CMN_ERR(ARGS) if (ssc050debug & 0x04) cmn_err ARGS;
64
65#else
66
67#define	D1CMN_ERR(ARGS)
68#define	D2CMN_ERR(ARGS)
69#define	D3CMN_ERR(ARGS)
70
71#endif
72
73static void *ssc050soft_statep;
74
75static int ssc050_do_attach(dev_info_t *);
76static int ssc050_do_detach(dev_info_t *);
77static int ssc050_set(struct ssc050_unit *, int, uchar_t);
78static int ssc050_get(struct ssc050_unit *, int, uchar_t *, int);
79
80/*
81 * cb ops
82 */
83static int ssc050_open(dev_t *, int, int, cred_t *);
84static int ssc050_close(dev_t, int, int, cred_t *);
85static int ssc050_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
86
87
88static struct cb_ops ssc050_cbops = {
89	ssc050_open,			/* open  */
90	ssc050_close,			/* close */
91	nodev,				/* strategy */
92	nodev,				/* print */
93	nodev,				/* dump */
94	nodev,				/* read */
95	nodev,				/* write */
96	ssc050_ioctl,			/* ioctl */
97	nodev,				/* devmap */
98	nodev,				/* mmap */
99	nodev,				/* segmap */
100	nochpoll,			/* poll */
101	ddi_prop_op,			/* cb_prop_op */
102	NULL,				/* streamtab */
103	D_NEW | D_MP | D_HOTPLUG,	/* Driver compatibility flag */
104	CB_REV,				/* rev */
105	nodev,				/* int (*cb_aread)() */
106	nodev				/* int (*cb_awrite)() */
107};
108
109/*
110 * dev ops
111 */
112static int ssc050_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
113		void **result);
114static int ssc050_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
115static int ssc050_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
116
117static struct dev_ops ssc050_ops = {
118	DEVO_REV,
119	0,
120	ssc050_info,
121	nulldev,
122	nulldev,
123	ssc050_attach,
124	ssc050_detach,
125	nodev,
126	&ssc050_cbops,
127	NULL,
128	NULL,
129	ddi_quiesce_not_needed,		/* quiesce */
130};
131
132extern struct mod_ops mod_driverops;
133
134static struct modldrv ssc050_modldrv = {
135	&mod_driverops,			/* type of module - driver */
136	"SSC050 i2c device driver: v1.10",
137	&ssc050_ops
138};
139
140static struct modlinkage ssc050_modlinkage = {
141	MODREV_1,
142	&ssc050_modldrv,
143	0
144};
145
146
147int
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
160int
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
172int
173_info(struct modinfo *modinfop)
174{
175	return (mod_info(&ssc050_modlinkage, modinfop));
176}
177
178static int
179ssc050_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
225static int
226ssc050_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
254static int
255ssc050_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
280static int
281ssc050_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
308static int
309ssc050_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 */
581static int
582ssc050_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
596static int
597ssc050_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
609static int
610ssc050_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
624static int
625ssc050_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
670static int
671ssc050_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
686int
687ssc050_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	if (unitp == NULL) {
700		return (ENXIO);
701	}
702	return (ssc050_get(unitp, reg, rval, flags));
703}
704