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/types.h>
28#include <sys/time.h>
29#include <sys/errno.h>
30#include <sys/cmn_err.h>
31#include <sys/param.h>
32#include <sys/modctl.h>
33#include <sys/conf.h>
34#include <sys/open.h>
35#include <sys/stat.h>
36#include <sys/clock.h>
37#include <sys/gpio_87317.h>
38#include <sys/ddi.h>
39#include <sys/sunddi.h>
40#include <sys/file.h>
41#ifdef DEBUG
42#include <sys/promif.h>
43#endif
44
45
46/* a non zero value causes debug info to be displayed */
47uint_t gpio_debug_flag = 0;
48
49
50#ifdef DEBUG
51static void gpio_debug(dev_info_t *dip, char *format, uint_t arg1, uint_t arg2,
52    uint_t arg3, uint_t arg4, uint_t arg5);
53
54#define	DBG(dip, format, arg1, arg2, arg3, arg4, arg5) \
55	gpio_debug(dip, format, (uint_t)arg1, (uint_t)arg2, (uint_t)arg3, \
56	    (uint_t)arg4, (uint_t)arg5)
57#else
58#define	DBG(dip, format, arg1, arg2, arg3, arg4, arg5)
59#endif
60
61
62/* Driver soft state structure */
63struct gpio_softc {
64	dev_info_t		*gp_dip;
65	kmutex_t		gp_mutex;
66	int			gp_state;
67	ddi_acc_handle_t	gp_handle;
68	uint8_t			*gp_regs;
69};
70
71#define	getsoftc(minor)	\
72	((struct gpio_softc *)ddi_get_soft_state(statep, (minor)))
73
74/* dev_ops and cb_ops entry point function declarations */
75static int gpio_attach(dev_info_t *, ddi_attach_cmd_t);
76static int gpio_detach(dev_info_t *, ddi_detach_cmd_t);
77static int gpio_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
78static int gpio_open(dev_t *, int, int, cred_t *);
79static int gpio_close(dev_t, int, int, cred_t *);
80static int gpio_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
81
82struct cb_ops gpio_cb_ops = {
83	gpio_open,
84	gpio_close,
85	nodev,
86	nodev,
87	nodev,			/* dump */
88	nodev,
89	nodev,
90	gpio_ioctl,
91	nodev,			/* devmap */
92	nodev,
93	nodev,
94	nochpoll,
95	ddi_prop_op,
96	NULL,			/* for STREAMS drivers */
97	D_NEW | D_MP,		/* driver compatibility flag */
98	CB_REV,
99	nodev,
100	nodev
101};
102
103static struct dev_ops gpio_dev_ops = {
104	DEVO_REV,			/* driver build version */
105	0,				/* device reference count */
106	gpio_getinfo,
107	nulldev,
108	nulldev,			/* probe */
109	gpio_attach,
110	gpio_detach,
111	nulldev,			/* reset */
112	&gpio_cb_ops,
113	(struct bus_ops *)NULL,
114	nulldev,			/* power */
115	ddi_quiesce_not_needed,			/* quiesce */
116};
117
118/* module configuration stuff */
119static void *statep;
120extern struct mod_ops mod_driverops;
121static struct modldrv modldrv = {
122	&mod_driverops,
123	"gpio driver",
124	&gpio_dev_ops
125};
126static struct modlinkage modlinkage = {
127	MODREV_1,
128	&modldrv,
129	0
130};
131
132
133int
134_init(void)
135{
136	int e;
137
138	if (e = ddi_soft_state_init(&statep, sizeof (struct gpio_softc), 1)) {
139		return (e);
140	}
141	if ((e = mod_install(&modlinkage)) != 0) {
142		ddi_soft_state_fini(&statep);
143	}
144
145	return (e);
146}
147
148
149int
150_fini(void)
151{
152	int e;
153
154	if ((e = mod_remove(&modlinkage)) != 0) {
155		return (e);
156	}
157	ddi_soft_state_fini(&statep);
158
159	return (DDI_SUCCESS);
160}
161
162
163int
164_info(struct modinfo *modinfop)
165{
166	return (mod_info(&modlinkage, modinfop));
167}
168
169
170/* ARGSUSED */
171static int
172gpio_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
173{
174	int instance = getminor((dev_t)arg);
175	int retval = DDI_SUCCESS;
176	struct gpio_softc *softc;
177
178	switch (cmd) {
179	case DDI_INFO_DEVT2DEVINFO:
180		if ((softc = getsoftc(instance)) == NULL) {
181			*result = (void *)NULL;
182			retval = DDI_FAILURE;
183		} else
184		*result = (void *)softc->gp_dip;
185		break;
186
187	case DDI_INFO_DEVT2INSTANCE:
188		*result = (void *)(uintptr_t)instance;
189		break;
190
191	default:
192		retval = DDI_FAILURE;
193	}
194
195	return (retval);
196}
197
198
199static int
200gpio_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
201{
202
203	int instance;
204	struct gpio_softc *softc = NULL;
205	ddi_device_acc_attr_t dev_attr;
206
207	switch (cmd) {
208
209	case DDI_ATTACH:
210
211	    /* Allocate and get the soft state structure for this instance. */
212
213		instance = ddi_get_instance(dip);
214		DBG(dip, "attach: instance is %d", instance, 0, 0, 0, 0);
215		if (ddi_soft_state_zalloc(statep, instance) != DDI_SUCCESS)
216			goto attach_failed;
217		softc = getsoftc(instance);
218		softc->gp_dip = dip;
219		softc->gp_state = 0;
220		mutex_init(&softc->gp_mutex, NULL, MUTEX_DRIVER, NULL);
221
222	    /* Map in the gpio device registers. */
223
224		dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
225		dev_attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
226		dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
227		if (ddi_regs_map_setup(dip, 0, (caddr_t *)&softc->gp_regs, 0, 0,
228		    &dev_attr, &softc->gp_handle) != DDI_SUCCESS)
229			goto attach_failed;
230		DBG(dip, "attach: regs=0x%p", (uintptr_t)softc->gp_regs,
231		    0, 0, 0, 0);
232		DBG(dip, "attach: port 1 data is %x",
233		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[0]),
234		    0, 0, 0, 0);
235		DBG(dip, "attach: port 1 direction is %x",
236		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[1]),
237		    0, 0, 0, 0);
238		DBG(dip, "attach: port 1 output type is %x",
239		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[2]),
240		    0, 0, 0, 0);
241		DBG(dip, "attach: port 1 pull up control type is %x",
242		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[3]),
243		    0, 0, 0, 0);
244		DBG(dip, "attach: port 2 data is %x",
245		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[4]),
246		    0, 0, 0, 0);
247		DBG(dip, "attach: port 2 direction is %x",
248		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[5]),
249		    0, 0, 0, 0);
250		DBG(dip, "attach: port 2 output type is %x",
251		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[6]),
252		    0, 0, 0, 0);
253		DBG(dip, "attach: port 2 pull up control type is %x",
254		    (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[7]),
255		    0, 0, 0, 0);
256
257		/* Create device minor nodes. */
258
259		if (ddi_create_minor_node(dip, "gpio", S_IFCHR,
260		    instance, NULL, 0) == DDI_FAILURE) {
261			ddi_regs_map_free(&softc->gp_handle);
262			goto attach_failed;
263		}
264
265		ddi_report_dev(dip);
266		return (DDI_SUCCESS);
267
268	case DDI_RESUME:
269
270		/* Nothing to do for a resume. */
271
272		return (DDI_SUCCESS);
273
274	default:
275		return (DDI_FAILURE);
276	}
277
278attach_failed:
279	if (softc) {
280		mutex_destroy(&softc->gp_mutex);
281		if (softc->gp_handle)
282			ddi_regs_map_free(&softc->gp_handle);
283		ddi_soft_state_free(statep, instance);
284		ddi_remove_minor_node(dip, NULL);
285	}
286	return (DDI_FAILURE);
287}
288
289
290static int
291gpio_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
292{
293	int instance;
294	struct gpio_softc *softc;
295
296	switch (cmd) {
297	case DDI_DETACH:
298		instance = ddi_get_instance(dip);
299		DBG(dip, "detach: instance is %d", instance, 0, 0, 0, 0);
300		if ((softc = getsoftc(instance)) == NULL)
301			return (ENXIO);
302		mutex_destroy(&softc->gp_mutex);
303		ddi_regs_map_free(&softc->gp_handle);
304		ddi_soft_state_free(statep, instance);
305		ddi_remove_minor_node(dip, NULL);
306		return (DDI_SUCCESS);
307
308	case DDI_SUSPEND:
309		/* Nothing to do in the suspend case. */
310		return (DDI_SUCCESS);
311
312	default:
313		return (DDI_FAILURE);
314	}
315}
316
317
318/* ARGSUSED */
319static int
320gpio_open(dev_t *devp, int flag, int otyp, cred_t *credp)
321{
322	int instance = getminor(*devp);
323
324	DBG(NULL, "open: instance is %d", instance, 0, 0, 0, 0);
325	return (getsoftc(instance) == NULL ? ENXIO : 0);
326}
327
328
329/* ARGSUSED */
330static int
331gpio_close(dev_t dev, int flag, int otyp, cred_t *credp)
332{
333	int instance = getminor(dev);
334
335	DBG(NULL, "close: instance is %d", instance, 0, 0, 0, 0);
336	return (getsoftc(instance) == NULL ? ENXIO : 0);
337}
338
339
340/* ARGSUSED */
341static int
342gpio_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
343    int *rvalp)
344{
345	int instance = getminor(dev);
346	struct gpio_softc *softc = getsoftc(instance);
347	gpio_87317_op_t info;
348	uint8_t byte;
349
350	DBG(softc->gp_dip, "ioctl: instance is %d", instance, 0, 0, 0, 0);
351
352	if (softc == NULL)
353		return (ENXIO);
354
355	/* Copy the command from user space. */
356	if (ddi_copyin((caddr_t)arg, (caddr_t)&info, sizeof (gpio_87317_op_t),
357	    mode) != 0)
358		return (EFAULT);
359
360	/* Check the command arguments.  We only support port 1 in bank 0. */
361	if ((info.gpio_bank != 0) ||
362	    (info.gpio_offset != GPIO_87317_PORT1_DATA)) {
363		return (EINVAL);
364	}
365
366	/* Grap the instance's mutex to insure exclusive access. */
367	mutex_enter(&softc->gp_mutex);
368
369	/* Get the contents of the GPIO register we're suppose to modify. */
370	byte = ddi_get8(softc->gp_handle, &softc->gp_regs[info.gpio_offset]);
371
372	switch (cmd) {
373	case GPIO_CMD_SET_BITS:
374		DBG(softc->gp_dip, "ioctl: SET_BITS, byte is %x", byte, 0, 0,
375		    0, 0);
376		byte |= info.gpio_data;
377		ddi_put8(softc->gp_handle, &softc->gp_regs[info.gpio_offset],
378		    byte);
379		byte = ddi_get8(softc->gp_handle,
380		    &softc->gp_regs[info.gpio_offset]);
381		DBG(softc->gp_dip, "ioctl: SET_BITS, byte is %x", byte, 0, 0,
382		    0, 0);
383		break;
384
385	case GPIO_CMD_CLR_BITS:
386		DBG(softc->gp_dip, "ioctl: CLR_BITS, byte is %x", byte, 0, 0,
387		    0, 0);
388		byte &= ~info.gpio_data;
389		ddi_put8(softc->gp_handle, &softc->gp_regs[info.gpio_offset],
390		    byte);
391		byte = ddi_get8(softc->gp_handle,
392		    &softc->gp_regs[info.gpio_offset]);
393		DBG(softc->gp_dip, "ioctl: CLR_BITS, byte is %x", byte, 0, 0,
394		    0, 0);
395		break;
396
397	case GPIO_CMD_GET:
398		DBG(softc->gp_dip, "ioctl: GPIO_CMD_GET", 0, 0, 0, 0, 0);
399		info.gpio_data = byte;
400		if (ddi_copyout((caddr_t)&info, (caddr_t)arg,
401		    sizeof (gpio_87317_op_t), mode) != 0) {
402			mutex_exit(&softc->gp_mutex);
403			return (EFAULT);
404		}
405		break;
406
407	case GPIO_CMD_SET:
408		DBG(softc->gp_dip, "ioctl: GPIO_CMD_SET", 0, 0, 0, 0, 0);
409		ddi_put8(softc->gp_handle, &softc->gp_regs[info.gpio_offset],
410		    info.gpio_data);
411		break;
412
413	default:
414		mutex_exit(&softc->gp_mutex);
415		return (EINVAL);
416	}
417
418	mutex_exit(&softc->gp_mutex);
419	return (0);
420}
421
422
423#ifdef DEBUG
424void
425gpio_debug(dev_info_t *dip, char *format, uint_t arg1, uint_t arg2, uint_t arg3,
426    uint_t arg4, uint_t arg5)
427{
428	if (gpio_debug_flag == 0) {
429		return;
430	}
431
432	if (dip == NULL) {
433		prom_printf("gpio: ");
434	} else {
435		prom_printf("%s%d: ", ddi_driver_name(dip),
436		    ddi_get_instance(dip));
437	}
438	prom_printf(format, arg1, arg2, arg3, arg4, arg5);
439	prom_printf("\n");
440}
441#endif
442