1/*-
2 * Copyright (c) 1998, 2001 Nicolas Souchu
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 *
28 */
29#include <sys/param.h>
30#include <sys/bus.h>
31#include <sys/conf.h>
32#include <sys/fcntl.h>
33#include <sys/lock.h>
34#include <sys/kernel.h>
35#include <sys/malloc.h>
36#include <sys/module.h>
37#include <sys/sx.h>
38#include <sys/systm.h>
39#include <sys/uio.h>
40
41#include <dev/iicbus/iiconf.h>
42#include <dev/iicbus/iicbus.h>
43#include <dev/iicbus/iic.h>
44
45#include "iicbus_if.h"
46
47#define BUFSIZE 1024
48
49struct iic_softc {
50
51	device_t sc_dev;
52	u_char sc_addr;			/* 7 bit address on iicbus */
53	int sc_count;			/* >0 if device opened */
54
55	char sc_buffer[BUFSIZE];	/* output buffer */
56	char sc_inbuf[BUFSIZE];		/* input buffer */
57
58	struct cdev *sc_devnode;
59	struct sx sc_lock;
60};
61
62#define	IIC_LOCK(sc)			sx_xlock(&(sc)->sc_lock)
63#define	IIC_UNLOCK(sc)			sx_xunlock(&(sc)->sc_lock)
64
65static int iic_probe(device_t);
66static int iic_attach(device_t);
67static int iic_detach(device_t);
68static void iic_identify(driver_t *driver, device_t parent);
69
70static devclass_t iic_devclass;
71
72static device_method_t iic_methods[] = {
73	/* device interface */
74	DEVMETHOD(device_identify,	iic_identify),
75	DEVMETHOD(device_probe,		iic_probe),
76	DEVMETHOD(device_attach,	iic_attach),
77	DEVMETHOD(device_detach,	iic_detach),
78
79	/* iicbus interface */
80	DEVMETHOD(iicbus_intr,		iicbus_generic_intr),
81
82	{ 0, 0 }
83};
84
85static driver_t iic_driver = {
86	"iic",
87	iic_methods,
88	sizeof(struct iic_softc),
89};
90
91static	d_open_t	iicopen;
92static	d_close_t	iicclose;
93static	d_write_t	iicwrite;
94static	d_read_t	iicread;
95static	d_ioctl_t	iicioctl;
96
97static struct cdevsw iic_cdevsw = {
98	.d_version =	D_VERSION,
99	.d_flags =	D_TRACKCLOSE,
100	.d_open =	iicopen,
101	.d_close =	iicclose,
102	.d_read =	iicread,
103	.d_write =	iicwrite,
104	.d_ioctl =	iicioctl,
105	.d_name =	"iic",
106};
107
108static void
109iic_identify(driver_t *driver, device_t parent)
110{
111
112	if (device_find_child(parent, "iic", -1) == NULL)
113		BUS_ADD_CHILD(parent, 0, "iic", -1);
114}
115
116static int
117iic_probe(device_t dev)
118{
119	if (iicbus_get_addr(dev) > 0)
120		return (ENXIO);
121
122	device_set_desc(dev, "I2C generic I/O");
123
124	return (0);
125}
126
127static int
128iic_attach(device_t dev)
129{
130	struct iic_softc *sc = (struct iic_softc *)device_get_softc(dev);
131
132	sc->sc_dev = dev;
133	sx_init(&sc->sc_lock, "iic");
134	sc->sc_devnode = make_dev(&iic_cdevsw, device_get_unit(dev),
135			UID_ROOT, GID_WHEEL,
136			0600, "iic%d", device_get_unit(dev));
137	if (sc->sc_devnode == NULL) {
138		device_printf(dev, "failed to create character device\n");
139		sx_destroy(&sc->sc_lock);
140		return (ENXIO);
141	}
142	sc->sc_devnode->si_drv1 = sc;
143
144	return (0);
145}
146
147static int
148iic_detach(device_t dev)
149{
150	struct iic_softc *sc = (struct iic_softc *)device_get_softc(dev);
151
152	if (sc->sc_devnode)
153		destroy_dev(sc->sc_devnode);
154	sx_destroy(&sc->sc_lock);
155
156	return (0);
157}
158
159static int
160iicopen(struct cdev *dev, int flags, int fmt, struct thread *td)
161{
162	struct iic_softc *sc = dev->si_drv1;
163
164	IIC_LOCK(sc);
165	if (sc->sc_count > 0) {
166		IIC_UNLOCK(sc);
167		return (EBUSY);
168	}
169
170	sc->sc_count++;
171	IIC_UNLOCK(sc);
172
173	return (0);
174}
175
176static int
177iicclose(struct cdev *dev, int flags, int fmt, struct thread *td)
178{
179	struct iic_softc *sc = dev->si_drv1;
180
181	IIC_LOCK(sc);
182	if (!sc->sc_count) {
183		/* XXX: I don't think this can happen. */
184		IIC_UNLOCK(sc);
185		return (EINVAL);
186	}
187
188	sc->sc_count--;
189
190	if (sc->sc_count < 0)
191		panic("%s: iic_count < 0!", __func__);
192	IIC_UNLOCK(sc);
193
194	return (0);
195}
196
197static int
198iicwrite(struct cdev *dev, struct uio * uio, int ioflag)
199{
200	struct iic_softc *sc = dev->si_drv1;
201	device_t iicdev = sc->sc_dev;
202	int sent, error, count;
203
204	IIC_LOCK(sc);
205	if (!sc->sc_addr) {
206		IIC_UNLOCK(sc);
207		return (EINVAL);
208	}
209
210	if (sc->sc_count == 0) {
211		/* XXX: I don't think this can happen. */
212		IIC_UNLOCK(sc);
213		return (EINVAL);
214	}
215
216	error = iicbus_request_bus(device_get_parent(iicdev), iicdev,
217	    IIC_DONTWAIT);
218	if (error) {
219		IIC_UNLOCK(sc);
220		return (error);
221	}
222
223	count = min(uio->uio_resid, BUFSIZE);
224	error = uiomove(sc->sc_buffer, count, uio);
225	if (error) {
226		IIC_UNLOCK(sc);
227		return (error);
228	}
229
230	error = iicbus_block_write(device_get_parent(iicdev), sc->sc_addr,
231					sc->sc_buffer, count, &sent);
232
233	iicbus_release_bus(device_get_parent(iicdev), iicdev);
234	IIC_UNLOCK(sc);
235
236	return (error);
237}
238
239static int
240iicread(struct cdev *dev, struct uio * uio, int ioflag)
241{
242	struct iic_softc *sc = dev->si_drv1;
243	device_t iicdev = sc->sc_dev;
244	int len, error = 0;
245	int bufsize;
246
247	IIC_LOCK(sc);
248	if (!sc->sc_addr) {
249		IIC_UNLOCK(sc);
250		return (EINVAL);
251	}
252
253	if (sc->sc_count == 0) {
254		/* XXX: I don't think this can happen. */
255		IIC_UNLOCK(sc);
256		return (EINVAL);
257	}
258
259	error = iicbus_request_bus(device_get_parent(iicdev), iicdev,
260	    IIC_DONTWAIT);
261	if (error) {
262		IIC_UNLOCK(sc);
263		return (error);
264	}
265
266	/* max amount of data to read */
267	len = min(uio->uio_resid, BUFSIZE);
268
269	error = iicbus_block_read(device_get_parent(iicdev), sc->sc_addr,
270	    sc->sc_inbuf, len, &bufsize);
271	if (error) {
272		IIC_UNLOCK(sc);
273		return (error);
274	}
275
276	if (bufsize > uio->uio_resid)
277		panic("%s: too much data read!", __func__);
278
279	iicbus_release_bus(device_get_parent(iicdev), iicdev);
280
281	error = uiomove(sc->sc_inbuf, bufsize, uio);
282	IIC_UNLOCK(sc);
283	return (error);
284}
285
286static int
287iicioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td)
288{
289	struct iic_softc *sc = dev->si_drv1;
290	device_t iicdev = sc->sc_dev;
291	device_t parent = device_get_parent(iicdev);
292	struct iiccmd *s = (struct iiccmd *)data;
293	struct iic_rdwr_data *d = (struct iic_rdwr_data *)data;
294	struct iic_msg *m;
295	int error, count, i;
296	char *buf = NULL;
297	void **usrbufs = NULL;
298
299	if ((error = iicbus_request_bus(parent, iicdev,
300	    (flags & O_NONBLOCK) ? IIC_DONTWAIT : (IIC_WAIT | IIC_INTR))))
301		return (error);
302
303	switch (cmd) {
304	case I2CSTART:
305		IIC_LOCK(sc);
306		error = iicbus_start(parent, s->slave, 0);
307
308		/*
309		 * Implicitly set the chip addr to the slave addr passed as
310		 * parameter. Consequently, start/stop shall be called before
311		 * the read or the write of a block.
312		 */
313		if (!error)
314			sc->sc_addr = s->slave;
315		IIC_UNLOCK(sc);
316
317		break;
318
319	case I2CSTOP:
320		error = iicbus_stop(parent);
321		break;
322
323	case I2CRSTCARD:
324		error = iicbus_reset(parent, IIC_UNKNOWN, 0, NULL);
325		break;
326
327	case I2CWRITE:
328		if (s->count <= 0) {
329			error = EINVAL;
330			break;
331		}
332		buf = malloc((unsigned long)s->count, M_TEMP, M_WAITOK);
333		error = copyin(s->buf, buf, s->count);
334		if (error)
335			break;
336		error = iicbus_write(parent, buf, s->count, &count, 10);
337		break;
338
339	case I2CREAD:
340		if (s->count <= 0) {
341			error = EINVAL;
342			break;
343		}
344		buf = malloc((unsigned long)s->count, M_TEMP, M_WAITOK);
345		error = iicbus_read(parent, buf, s->count, &count, s->last, 10);
346		if (error)
347			break;
348		error = copyout(buf, s->buf, s->count);
349		break;
350
351	case I2CRDWR:
352		buf = malloc(sizeof(*d->msgs) * d->nmsgs, M_TEMP, M_WAITOK);
353		error = copyin(d->msgs, buf, sizeof(*d->msgs) * d->nmsgs);
354		if (error)
355			break;
356		/* Alloc kernel buffers for userland data, copyin write data */
357		usrbufs = malloc(sizeof(void *) * d->nmsgs, M_TEMP, M_ZERO | M_WAITOK);
358		for (i = 0; i < d->nmsgs; i++) {
359			m = &((struct iic_msg *)buf)[i];
360			usrbufs[i] = m->buf;
361			m->buf = malloc(m->len, M_TEMP, M_WAITOK);
362			if (!(m->flags & IIC_M_RD))
363				copyin(usrbufs[i], m->buf, m->len);
364		}
365		error = iicbus_transfer(iicdev, (struct iic_msg *)buf, d->nmsgs);
366		/* Copyout all read segments, free up kernel buffers */
367		for (i = 0; i < d->nmsgs; i++) {
368			m = &((struct iic_msg *)buf)[i];
369			if (m->flags & IIC_M_RD)
370				copyout(m->buf, usrbufs[i], m->len);
371			free(m->buf, M_TEMP);
372		}
373		free(usrbufs, M_TEMP);
374		break;
375
376	case I2CRPTSTART:
377		error = iicbus_repeated_start(parent, s->slave, 0);
378		break;
379
380	default:
381		error = ENOTTY;
382	}
383
384	iicbus_release_bus(parent, iicdev);
385
386	if (buf != NULL)
387		free(buf, M_TEMP);
388	return (error);
389}
390
391DRIVER_MODULE(iic, iicbus, iic_driver, iic_devclass, 0, 0);
392MODULE_DEPEND(iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
393MODULE_VERSION(iic, 1);
394