at91_twi.c revision 155324
150479Speter/*-
220253Sjoerg * Copyright (c) 2006 M. Warner Losh.  All rights reserved.
320253Sjoerg *
480029Sobrien * Redistribution and use in source and binary forms, with or without
544229Sdavidn * modification, are permitted provided that the following conditions
620253Sjoerg * are met:
720253Sjoerg * 1. Redistributions of source code must retain the above copyright
820253Sjoerg *    notice, this list of conditions and the following disclaimer.
964918Sgreen * 2. Redistributions in binary form must reproduce the above copyright
1064918Sgreen *    notice, this list of conditions and the following disclaimer in the
1120253Sjoerg *    documentation and/or other materials provided with the distribution.
1220253Sjoerg *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include <sys/cdefs.h>
26__FBSDID("$FreeBSD: head/sys/arm/at91/at91_twi.c 155324 2006-02-04 23:32:13Z imp $");
27
28#include <sys/param.h>
29#include <sys/systm.h>
30#include <sys/bus.h>
31#include <sys/conf.h>
32#include <sys/kernel.h>
33#include <sys/lock.h>
34#include <sys/mbuf.h>
35#include <sys/malloc.h>
36#include <sys/module.h>
37#include <sys/mutex.h>
38#include <sys/rman.h>
39#include <machine/bus.h>
40
41#include <arm/at91/at91rm92reg.h>
42#include <arm/at91/at91_twireg.h>
43#include <arm/at91/at91_twiio.h>
44
45struct at91_twi_softc
46{
47	device_t dev;			/* Myself */
48	void *intrhand;			/* Interrupt handle */
49	struct resource *irq_res;	/* IRQ resource */
50	struct resource	*mem_res;	/* Memory resource */
51	struct mtx sc_mtx;		/* basically a perimeter lock */
52	int flags;
53#define XFER_PENDING	1		/* true when transfer taking place */
54#define OPENED		2		/* Device opened */
55#define RXRDY		4
56#define TXCOMP		8
57#define TXRDY		0x10
58	struct cdev *cdev;
59	uint32_t cwgr;
60};
61
62static inline uint32_t
63RD4(struct at91_twi_softc *sc, bus_size_t off)
64{
65	return bus_read_4(sc->mem_res, off);
66}
67
68static inline void
69WR4(struct at91_twi_softc *sc, bus_size_t off, uint32_t val)
70{
71	bus_write_4(sc->mem_res, off, val);
72}
73
74#define AT91_TWI_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
75#define	AT91_TWI_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
76#define AT91_TWI_LOCK_INIT(_sc) \
77	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
78	    "twi", MTX_DEF)
79#define AT91_TWI_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
80#define AT91_TWI_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
81#define AT91_TWI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
82#define CDEV2SOFTC(dev)		((dev)->si_drv1)
83#define TWI_DEF_CLK	100000
84
85static devclass_t at91_twi_devclass;
86
87/* bus entry points */
88
89static int at91_twi_probe(device_t dev);
90static int at91_twi_attach(device_t dev);
91static int at91_twi_detach(device_t dev);
92static void at91_twi_intr(void *);
93
94/* helper routines */
95static int at91_twi_activate(device_t dev);
96static void at91_twi_deactivate(device_t dev);
97
98/* cdev routines */
99static d_open_t at91_twi_open;
100static d_close_t at91_twi_close;
101static d_ioctl_t at91_twi_ioctl;
102
103static struct cdevsw at91_twi_cdevsw =
104{
105	.d_version = D_VERSION,
106	.d_open = at91_twi_open,
107	.d_close = at91_twi_close,
108	.d_ioctl = at91_twi_ioctl
109};
110
111static int
112at91_twi_probe(device_t dev)
113{
114	device_set_desc(dev, "TWI");
115	return (0);
116}
117
118static int
119at91_twi_attach(device_t dev)
120{
121	struct at91_twi_softc *sc = device_get_softc(dev);
122	int err;
123
124	sc->dev = dev;
125	err = at91_twi_activate(dev);
126	if (err)
127		goto out;
128
129	AT91_TWI_LOCK_INIT(sc);
130
131	/*
132	 * Activate the interrupt
133	 */
134	err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
135	    at91_twi_intr, sc, &sc->intrhand);
136	if (err) {
137		AT91_TWI_LOCK_DESTROY(sc);
138		goto out;
139	}
140	sc->cdev = make_dev(&at91_twi_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
141	    "twi%d", device_get_unit(dev));
142	if (sc->cdev == NULL) {
143		err = ENOMEM;
144		goto out;
145	}
146	sc->cdev->si_drv1 = sc;
147	sc->cwgr = TWI_CWGR_CKDIV(1) |
148	    TWI_CWGR_CHDIV(TWI_CWGR_DIV(TWI_DEF_CLK)) |
149	    TWI_CWGR_CLDIV(TWI_CWGR_DIV(TWI_DEF_CLK));
150
151	WR4(sc, TWI_CR, TWI_CR_SWRST);
152	WR4(sc, TWI_CR, TWI_CR_MSEN | TWI_CR_SVDIS);
153	WR4(sc, TWI_CWGR, sc->cwgr);
154out:;
155	if (err)
156		at91_twi_deactivate(dev);
157	return (err);
158}
159
160static int
161at91_twi_detach(device_t dev)
162{
163	return (EBUSY);	/* XXX */
164}
165
166static int
167at91_twi_activate(device_t dev)
168{
169	struct at91_twi_softc *sc;
170	int rid;
171
172	sc = device_get_softc(dev);
173	rid = 0;
174	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
175	    RF_ACTIVE);
176	if (sc->mem_res == NULL)
177		goto errout;
178	rid = 0;
179	sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
180	    RF_ACTIVE);
181	if (sc->mem_res == NULL)
182		goto errout;
183	return (0);
184errout:
185	at91_twi_deactivate(dev);
186	return (ENOMEM);
187}
188
189static void
190at91_twi_deactivate(device_t dev)
191{
192	struct at91_twi_softc *sc;
193
194	sc = device_get_softc(dev);
195	if (sc->intrhand)
196		bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
197	sc->intrhand = 0;
198	bus_generic_detach(sc->dev);
199	if (sc->mem_res)
200		bus_release_resource(dev, SYS_RES_IOPORT,
201		    rman_get_rid(sc->mem_res), sc->mem_res);
202	sc->mem_res = 0;
203	if (sc->irq_res)
204		bus_release_resource(dev, SYS_RES_IRQ,
205		    rman_get_rid(sc->irq_res), sc->irq_res);
206	sc->irq_res = 0;
207	return;
208}
209
210static void
211at91_twi_intr(void *xsc)
212{
213	struct at91_twi_softc *sc = xsc;
214	uint32_t status;
215
216	/* Reading the status also clears the interrupt */
217	status = RD4(sc, TWI_SR);
218	if (status == 0)
219		return;
220	AT91_TWI_LOCK(sc);
221	if (status & TWI_SR_RXRDY)
222		sc->flags |= RXRDY;
223	if (status & TWI_SR_TXCOMP)
224		sc->flags |= TXCOMP;
225	if (status & TWI_SR_TXRDY)
226		sc->flags |= TXRDY;
227	AT91_TWI_UNLOCK(sc);
228	wakeup(sc);
229	return;
230}
231
232static int
233at91_twi_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
234{
235	struct at91_twi_softc *sc;
236
237	sc = CDEV2SOFTC(dev);
238	AT91_TWI_LOCK(sc);
239	if (!(sc->flags & OPENED)) {
240		sc->flags |= OPENED;
241		WR4(sc, TWI_IER, TWI_SR_TXCOMP | TWI_SR_RXRDY | TWI_SR_TXRDY |
242		    TWI_SR_OVRE | TWI_SR_UNRE | TWI_SR_NACK);
243	}
244	AT91_TWI_UNLOCK(sc);
245    	return (0);
246}
247
248static int
249at91_twi_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
250{
251	struct at91_twi_softc *sc;
252
253	sc = CDEV2SOFTC(dev);
254	AT91_TWI_LOCK(sc);
255	sc->flags &= ~OPENED;
256	WR4(sc, TWI_IDR, TWI_SR_TXCOMP | TWI_SR_RXRDY | TWI_SR_TXRDY |
257	    TWI_SR_OVRE | TWI_SR_UNRE | TWI_SR_NACK);
258	AT91_TWI_UNLOCK(sc);
259	return (0);
260}
261
262
263static int
264at91_twi_read_master(struct at91_twi_softc *sc, struct at91_twi_io *xfr)
265{
266	uint8_t *walker;
267	uint8_t buffer[256];
268	size_t len;
269	int err = 0;
270
271	if (xfr->xfer_len > sizeof(buffer))
272		return (EINVAL);
273	walker = buffer;
274	len = xfr->xfer_len;
275	RD4(sc, TWI_RHR);
276	// Master mode, with the right address and interal addr size
277	WR4(sc, TWI_MMR, TWI_MMR_IADRSZ(xfr->iadrsz) | TWI_MMR_MREAD |
278	    TWI_MMR_DADR(xfr->dadr));
279	WR4(sc, TWI_IADR, xfr->iadr);
280	WR4(sc, TWI_CR, TWI_CR_START);
281	while (len-- > 1) {
282		while (!(sc->flags & RXRDY)) {
283			err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "twird",
284			    0);
285			if (err)
286				return (err);
287		}
288		sc->flags &= ~RXRDY;
289		*walker++ = RD4(sc, TWI_RHR) & 0xff;
290	}
291	WR4(sc, TWI_CR, TWI_CR_STOP);
292	while (!(sc->flags & TXCOMP)) {
293		err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "twird2", 0);
294		if (err)
295			return (err);
296	}
297	sc->flags &= ~TXCOMP;
298	*walker = RD4(sc, TWI_RHR) & 0xff;
299	if (xfr->xfer_buf) {
300		AT91_TWI_UNLOCK(sc);
301		err = copyout(buffer, xfr->xfer_buf, xfr->xfer_len);
302		AT91_TWI_LOCK(sc);
303	}
304	return (err);
305}
306
307static int
308at91_twi_write_master(struct at91_twi_softc *sc, struct at91_twi_io *xfr)
309{
310	uint8_t *walker;
311	uint8_t buffer[256];
312	size_t len;
313	int err;
314
315	if (xfr->xfer_len > sizeof(buffer))
316		return (EINVAL);
317	walker = buffer;
318	len = xfr->xfer_len;
319	AT91_TWI_UNLOCK(sc);
320	err = copyin(xfr->xfer_buf, buffer, xfr->xfer_len);
321	AT91_TWI_LOCK(sc);
322	if (err)
323		return (err);
324	/* Setup the xfr for later readback */
325	xfr->xfer_buf = 0;
326	xfr->xfer_len = 1;
327	while (len--) {
328		WR4(sc, TWI_MMR, TWI_MMR_IADRSZ(xfr->iadrsz) | TWI_MMR_MWRITE |
329		    TWI_MMR_DADR(xfr->dadr));
330		WR4(sc, TWI_IADR, xfr->iadr++);
331		WR4(sc, TWI_THR, *walker++);
332		WR4(sc, TWI_CR, TWI_CR_START);
333		/*
334		 * If we get signal while waiting for TXRDY, make sure we
335		 * try to stop this device
336		 */
337		while (!(sc->flags & TXRDY)) {
338			err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "twiwr",
339			    0);
340			if (err)
341				break;
342		}
343		WR4(sc, TWI_CR, TWI_CR_STOP);
344		if (err)
345			return (err);
346		while (!(sc->flags & TXCOMP)) {
347			err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "twiwr2",
348			    0);
349			if (err)
350				return (err);
351		}
352		/* Readback */
353		at91_twi_read_master(sc, xfr);
354	}
355	return (err);
356}
357
358static int
359at91_twi_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
360    struct thread *td)
361{
362	int err = 0;
363	struct at91_twi_softc *sc;
364
365	sc = CDEV2SOFTC(dev);
366	AT91_TWI_LOCK(sc);
367	while (sc->flags & XFER_PENDING) {
368		err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH,
369		    "twiwait", 0);
370		if (err) {
371			AT91_TWI_UNLOCK(sc);
372			return (err);
373		}
374	}
375	sc->flags |= XFER_PENDING;
376
377	switch (cmd)
378	{
379	case TWIIOCXFER:
380	{
381		struct at91_twi_io *xfr = (struct at91_twi_io *)data;
382		switch (xfr->type)
383		{
384		case TWI_IO_READ_MASTER:
385			err = at91_twi_read_master(sc, xfr);
386			break;
387		case TWI_IO_WRITE_MASTER:
388			err = at91_twi_write_master(sc, xfr);
389			break;
390		default:
391			err = EINVAL;
392			break;
393		}
394		break;
395	}
396
397	case TWIIOCSETCLOCK:
398	{
399		struct at91_twi_clock *twick = (struct at91_twi_clock *)data;
400
401		sc->cwgr = TWI_CWGR_CKDIV(twick->ckdiv) |
402		    TWI_CWGR_CHDIV(TWI_CWGR_DIV(twick->high_rate)) |
403		    TWI_CWGR_CLDIV(TWI_CWGR_DIV(twick->low_rate));
404		WR4(sc, TWI_CR, TWI_CR_SWRST);
405		WR4(sc, TWI_CR, TWI_CR_MSEN | TWI_CR_SVDIS);
406		WR4(sc, TWI_CWGR, sc->cwgr);
407		break;
408	}
409	default:
410		err = ENOTTY;
411		break;
412	}
413	sc->flags &= ~XFER_PENDING;
414	AT91_TWI_UNLOCK(sc);
415	wakeup(sc);
416	return err;
417}
418
419static device_method_t at91_twi_methods[] = {
420	/* Device interface */
421	DEVMETHOD(device_probe,		at91_twi_probe),
422	DEVMETHOD(device_attach,	at91_twi_attach),
423	DEVMETHOD(device_detach,	at91_twi_detach),
424
425	{ 0, 0 }
426};
427
428static driver_t at91_twi_driver = {
429	"at91_twi",
430	at91_twi_methods,
431	sizeof(struct at91_twi_softc),
432};
433
434DRIVER_MODULE(at91_twi, atmelarm, at91_twi_driver, at91_twi_devclass, 0, 0);
435