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#ifdef HAVE_KERNEL_OPTION_HEADERS
30#include "opt_compat.h"
31#endif
32
33#include <sys/param.h>
34#include <sys/kernel.h>
35#include <sys/systm.h>
36#include <sys/module.h>
37#include <sys/bus.h>
38#include <sys/conf.h>
39#include <sys/uio.h>
40#include <sys/fcntl.h>
41
42#include <dev/smbus/smbconf.h>
43#include <dev/smbus/smbus.h>
44#include <dev/smbus/smb.h>
45
46#include "smbus_if.h"
47
48#define BUFSIZE 1024
49
50struct smb_softc {
51	device_t sc_dev;
52	int sc_count;			/* >0 if device opened */
53	struct cdev *sc_devnode;
54	struct mtx sc_lock;
55};
56
57static void smb_identify(driver_t *driver, device_t parent);
58static int smb_probe(device_t);
59static int smb_attach(device_t);
60static int smb_detach(device_t);
61
62static devclass_t smb_devclass;
63
64static device_method_t smb_methods[] = {
65	/* device interface */
66	DEVMETHOD(device_identify,	smb_identify),
67	DEVMETHOD(device_probe,		smb_probe),
68	DEVMETHOD(device_attach,	smb_attach),
69	DEVMETHOD(device_detach,	smb_detach),
70
71	/* smbus interface */
72	DEVMETHOD(smbus_intr,		smbus_generic_intr),
73
74	{ 0, 0 }
75};
76
77static driver_t smb_driver = {
78	"smb",
79	smb_methods,
80	sizeof(struct smb_softc),
81};
82
83static	d_open_t	smbopen;
84static	d_close_t	smbclose;
85static	d_ioctl_t	smbioctl;
86
87static struct cdevsw smb_cdevsw = {
88	.d_version =	D_VERSION,
89	.d_flags =	D_TRACKCLOSE,
90	.d_open =	smbopen,
91	.d_close =	smbclose,
92	.d_ioctl =	smbioctl,
93	.d_name =	"smb",
94};
95
96static void
97smb_identify(driver_t *driver, device_t parent)
98{
99
100	if (device_find_child(parent, "smb", -1) == NULL)
101		BUS_ADD_CHILD(parent, 0, "smb", -1);
102}
103
104static int
105smb_probe(device_t dev)
106{
107	device_set_desc(dev, "SMBus generic I/O");
108
109	return (0);
110}
111
112static int
113smb_attach(device_t dev)
114{
115	struct smb_softc *sc = device_get_softc(dev);
116
117	sc->sc_dev = dev;
118	sc->sc_devnode = make_dev(&smb_cdevsw, device_get_unit(dev),
119	    UID_ROOT, GID_WHEEL, 0600, "smb%d", device_get_unit(dev));
120	sc->sc_devnode->si_drv1 = sc;
121	mtx_init(&sc->sc_lock, device_get_nameunit(dev), NULL, MTX_DEF);
122
123	return (0);
124}
125
126static int
127smb_detach(device_t dev)
128{
129	struct smb_softc *sc = (struct smb_softc *)device_get_softc(dev);
130
131	if (sc->sc_devnode)
132		destroy_dev(sc->sc_devnode);
133	mtx_destroy(&sc->sc_lock);
134
135	return (0);
136}
137
138static int
139smbopen(struct cdev *dev, int flags, int fmt, struct thread *td)
140{
141	struct smb_softc *sc = dev->si_drv1;
142
143	mtx_lock(&sc->sc_lock);
144	if (sc->sc_count != 0) {
145		mtx_unlock(&sc->sc_lock);
146		return (EBUSY);
147	}
148
149	sc->sc_count++;
150	mtx_unlock(&sc->sc_lock);
151
152	return (0);
153}
154
155static int
156smbclose(struct cdev *dev, int flags, int fmt, struct thread *td)
157{
158	struct smb_softc *sc = dev->si_drv1;
159
160	mtx_lock(&sc->sc_lock);
161	KASSERT(sc->sc_count == 1, ("device not busy"));
162	sc->sc_count--;
163	mtx_unlock(&sc->sc_lock);
164
165	return (0);
166}
167
168static int
169smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td)
170{
171	char buf[SMB_MAXBLOCKSIZE];
172	device_t parent;
173	struct smbcmd *s = (struct smbcmd *)data;
174	struct smb_softc *sc = dev->si_drv1;
175	device_t smbdev = sc->sc_dev;
176	int error;
177	short w;
178	u_char count;
179	char c;
180
181	parent = device_get_parent(smbdev);
182
183	/* Make sure that LSB bit is cleared. */
184	if (s->slave & 0x1)
185		return (EINVAL);
186
187	/* Allocate the bus. */
188	if ((error = smbus_request_bus(parent, smbdev,
189			(flags & O_NONBLOCK) ? SMB_DONTWAIT : (SMB_WAIT | SMB_INTR))))
190		return (error);
191
192	switch (cmd) {
193	case SMB_QUICK_WRITE:
194		error = smbus_error(smbus_quick(parent, s->slave, SMB_QWRITE));
195		break;
196
197	case SMB_QUICK_READ:
198		error = smbus_error(smbus_quick(parent, s->slave, SMB_QREAD));
199		break;
200
201	case SMB_SENDB:
202		error = smbus_error(smbus_sendb(parent, s->slave, s->cmd));
203		break;
204
205	case SMB_RECVB:
206		error = smbus_error(smbus_recvb(parent, s->slave, &s->cmd));
207		break;
208
209	case SMB_WRITEB:
210		error = smbus_error(smbus_writeb(parent, s->slave, s->cmd,
211						s->data.byte));
212		break;
213
214	case SMB_WRITEW:
215		error = smbus_error(smbus_writew(parent, s->slave,
216						s->cmd, s->data.word));
217		break;
218
219	case SMB_READB:
220		if (s->data.byte_ptr) {
221			error = smbus_error(smbus_readb(parent, s->slave,
222						s->cmd, &c));
223			if (error)
224				break;
225			error = copyout(&c, s->data.byte_ptr,
226					sizeof(*(s->data.byte_ptr)));
227		}
228		break;
229
230	case SMB_READW:
231		if (s->data.word_ptr) {
232			error = smbus_error(smbus_readw(parent, s->slave,
233						s->cmd, &w));
234			if (error == 0) {
235				error = copyout(&w, s->data.word_ptr,
236						sizeof(*(s->data.word_ptr)));
237			}
238		}
239		break;
240
241	case SMB_PCALL:
242		if (s->data.process.rdata) {
243
244			error = smbus_error(smbus_pcall(parent, s->slave, s->cmd,
245				s->data.process.sdata, &w));
246			if (error)
247				break;
248			error = copyout(&w, s->data.process.rdata,
249					sizeof(*(s->data.process.rdata)));
250		}
251
252		break;
253
254	case SMB_BWRITE:
255		if (s->count && s->data.byte_ptr) {
256			if (s->count > SMB_MAXBLOCKSIZE)
257				s->count = SMB_MAXBLOCKSIZE;
258			error = copyin(s->data.byte_ptr, buf, s->count);
259			if (error)
260				break;
261			error = smbus_error(smbus_bwrite(parent, s->slave,
262						s->cmd, s->count, buf));
263		}
264		break;
265
266#if defined(COMPAT_FREEBSD4) || defined(COMPAT_FREEBSD5) || defined(COMPAT_FREEBSD6)
267	case SMB_OLD_BREAD:
268#endif
269	case SMB_BREAD:
270		if (s->count && s->data.byte_ptr) {
271			count = min(s->count, SMB_MAXBLOCKSIZE);
272			error = smbus_error(smbus_bread(parent, s->slave,
273						s->cmd, &count, buf));
274			if (error)
275				break;
276			error = copyout(buf, s->data.byte_ptr,
277			    min(count, s->count));
278			s->count = count;
279		}
280		break;
281
282	default:
283		error = ENOTTY;
284	}
285
286	smbus_release_bus(parent, smbdev);
287
288	return (error);
289}
290
291DRIVER_MODULE(smb, smbus, smb_driver, smb_devclass, 0, 0);
292MODULE_DEPEND(smb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
293MODULE_VERSION(smb, 1);
294