at91_twi.c revision 164744
1/*-
2 * Copyright (c) 2006 M. Warner Losh.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
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 164744 2006-11-29 08:15:59Z 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
44#include <dev/iicbus/iiconf.h>
45#include <dev/iicbus/iicbus.h>
46#include "iicbus_if.h"
47
48struct at91_twi_softc
49{
50	device_t dev;			/* Myself */
51	void *intrhand;			/* Interrupt handle */
52	struct resource *irq_res;	/* IRQ resource */
53	struct resource	*mem_res;	/* Memory resource */
54	struct mtx sc_mtx;		/* basically a perimeter lock */
55	volatile uint32_t flags;
56	uint32_t cwgr;
57	int	sc_started;
58	int	twi_addr;
59	device_t iicbus;
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 TWI_DEF_CLK	100000
83
84static devclass_t at91_twi_devclass;
85
86/* bus entry points */
87
88static int at91_twi_probe(device_t dev);
89static int at91_twi_attach(device_t dev);
90static int at91_twi_detach(device_t dev);
91static void at91_twi_intr(void *);
92
93/* helper routines */
94static int at91_twi_activate(device_t dev);
95static void at91_twi_deactivate(device_t dev);
96
97static int
98at91_twi_probe(device_t dev)
99{
100	device_set_desc(dev, "TWI");
101	return (0);
102}
103
104static int
105at91_twi_attach(device_t dev)
106{
107	struct at91_twi_softc *sc = device_get_softc(dev);
108	int err;
109
110	sc->dev = dev;
111	err = at91_twi_activate(dev);
112	if (err)
113		goto out;
114
115	AT91_TWI_LOCK_INIT(sc);
116
117	/*
118	 * Activate the interrupt
119	 */
120	err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
121	    at91_twi_intr, sc, &sc->intrhand);
122	if (err) {
123		AT91_TWI_LOCK_DESTROY(sc);
124		goto out;
125	}
126	sc->cwgr = TWI_CWGR_CKDIV(8 * AT91C_MASTER_CLOCK / 90000) |
127	    TWI_CWGR_CHDIV(TWI_CWGR_DIV(TWI_DEF_CLK)) |
128	    TWI_CWGR_CLDIV(TWI_CWGR_DIV(TWI_DEF_CLK));
129	WR4(sc, TWI_CR, TWI_CR_SWRST);
130	WR4(sc, TWI_CR, TWI_CR_MSEN | TWI_CR_SVDIS);
131	WR4(sc, TWI_CWGR, sc->cwgr);
132
133	if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL)
134		device_printf(dev, "could not allocate iicbus instance\n");
135	/* probe and attach the iicbus */
136	bus_generic_attach(dev);
137out:;
138	if (err)
139		at91_twi_deactivate(dev);
140	return (err);
141}
142
143static int
144at91_twi_detach(device_t dev)
145{
146	struct at91_twi_softc *sc;
147	int rv;
148
149	sc = device_get_softc(dev);
150	at91_twi_deactivate(dev);
151	if (sc->iicbus && (rv = device_delete_child(dev, sc->iicbus)) != 0)
152		return (rv);
153
154	return (0);
155}
156
157static int
158at91_twi_activate(device_t dev)
159{
160	struct at91_twi_softc *sc;
161	int rid;
162
163	sc = device_get_softc(dev);
164	rid = 0;
165	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
166	    RF_ACTIVE);
167	if (sc->mem_res == NULL)
168		goto errout;
169	rid = 0;
170	sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
171	    RF_ACTIVE);
172	if (sc->irq_res == NULL)
173		goto errout;
174	return (0);
175errout:
176	at91_twi_deactivate(dev);
177	return (ENOMEM);
178}
179
180static void
181at91_twi_deactivate(device_t dev)
182{
183	struct at91_twi_softc *sc;
184
185	sc = device_get_softc(dev);
186	if (sc->intrhand)
187		bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
188	sc->intrhand = 0;
189	bus_generic_detach(sc->dev);
190	if (sc->mem_res)
191		bus_release_resource(dev, SYS_RES_IOPORT,
192		    rman_get_rid(sc->mem_res), sc->mem_res);
193	sc->mem_res = 0;
194	if (sc->irq_res)
195		bus_release_resource(dev, SYS_RES_IRQ,
196		    rman_get_rid(sc->irq_res), sc->irq_res);
197	sc->irq_res = 0;
198	return;
199}
200
201static void
202at91_twi_intr(void *xsc)
203{
204	struct at91_twi_softc *sc = xsc;
205	uint32_t status;
206
207	status = RD4(sc, TWI_SR);
208	if (status == 0)
209		return;
210	sc->flags |= status & (TWI_SR_OVRE | TWI_SR_UNRE | TWI_SR_NACK);
211	if (status & TWI_SR_RXRDY)
212		sc->flags |= TWI_SR_RXRDY;
213	if (status & TWI_SR_TXRDY)
214		sc->flags |= TWI_SR_TXRDY;
215	if (status & TWI_SR_TXCOMP)
216		sc->flags |= TWI_SR_TXCOMP;
217	WR4(sc, TWI_IDR, status);
218	wakeup(sc);
219	return;
220}
221
222static int
223at91_twi_wait(struct at91_twi_softc *sc, uint32_t bit)
224{
225	int err = 0;
226	int counter = 100000;
227	uint32_t sr;
228
229	while (!((sr = RD4(sc, TWI_SR)) & bit) && counter-- > 0)
230		continue;
231	if (counter <= 0)
232		err = EBUSY;
233	else if (sr & TWI_SR_NACK)
234		err = EADDRNOTAVAIL;
235	if (sr & ~bit)
236		printf("status is %x\n", sr);
237	return (err);
238}
239
240static int
241at91_twi_rst_card(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
242{
243	struct at91_twi_softc *sc;
244	int clk;
245
246	sc = device_get_softc(dev);
247	if (oldaddr)
248		*oldaddr = sc->twi_addr;
249	sc->twi_addr = addr;
250
251	/*
252	 * speeds are for 1.5kb/s, 45kb/s and 90kb/s.
253	 */
254	switch (speed) {
255	case IIC_SLOW:
256		clk = 1500;
257		break;
258
259	case IIC_FAST:
260		clk = 45000;
261		break;
262
263	case IIC_UNKNOWN:
264	case IIC_FASTEST:
265	default:
266		clk = 90000;
267		break;
268	}
269	sc->cwgr = TWI_CWGR_CKDIV(1) | TWI_CWGR_CHDIV(TWI_CWGR_DIV(clk)) |
270	    TWI_CWGR_CLDIV(TWI_CWGR_DIV(clk));
271	WR4(sc, TWI_CR, TWI_CR_SWRST);
272	WR4(sc, TWI_CR, TWI_CR_MSEN | TWI_CR_SVDIS);
273	WR4(sc, TWI_CWGR, sc->cwgr);
274	printf("setting cwgr to %#x\n", sc->cwgr);
275
276	return 0;
277}
278
279static int
280at91_twi_callback(device_t dev, int index, caddr_t *data)
281{
282	int error = 0;
283
284	switch (index) {
285	case IIC_REQUEST_BUS:
286		break;
287
288	case IIC_RELEASE_BUS:
289		break;
290
291	default:
292		error = EINVAL;
293	}
294
295	return (error);
296}
297
298static int
299at91_twi_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
300{
301	struct at91_twi_softc *sc;
302	int i, len, err;
303	uint32_t rdwr;
304	uint8_t *buf;
305
306	sc = device_get_softc(dev);
307	err = 0;
308	AT91_TWI_LOCK(sc);
309	for (i = 0; i < nmsgs; i++) {
310		/*
311		 * The linux atmel driver doesn't use the internal device
312		 * address feature of twi.  A separate i2c message needs to
313		 * be written to use this.
314		 * See http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2004-September/024411.html
315		 * for details.  Upon reflection, we could use this as an
316		 * optimization, but it is unclear the code bloat will
317		 * result in faster/better operations.
318		 */
319		rdwr = (msgs[i].flags & IIC_M_RD) ? TWI_MMR_MREAD : 0;
320		WR4(sc, TWI_MMR, TWI_MMR_DADR(msgs[i].slave) | rdwr);
321		len = msgs[i].len;
322		buf = msgs[i].buf;
323		if (len != 0 && buf == NULL)
324			return (EINVAL);
325		WR4(sc, TWI_CR, TWI_CR_START);
326		if (msgs[i].flags & IIC_M_RD) {
327			while (len--) {
328				if (len == 0)
329					WR4(sc, TWI_CR, TWI_CR_STOP);
330				if ((err = at91_twi_wait(sc, TWI_SR_RXRDY)))
331					goto out;
332				*buf++ = RD4(sc, TWI_RHR) & 0xff;
333			}
334		} else {
335			while (len--) {
336				WR4(sc, TWI_THR, *buf++);
337				if (len == 0)
338					WR4(sc, TWI_CR, TWI_CR_STOP);
339				if ((err = at91_twi_wait(sc, TWI_SR_TXRDY))) {
340					printf("Len %d\n", len);
341					goto out;
342				}
343			}
344		}
345		if ((err = at91_twi_wait(sc, TWI_SR_TXCOMP)))
346			break;
347	}
348out:;
349	if (err) {
350		WR4(sc, TWI_CR, TWI_CR_STOP);
351		printf("Err is %d\n", err);
352	}
353	AT91_TWI_UNLOCK(sc);
354	return (err);
355}
356
357static device_method_t at91_twi_methods[] = {
358	/* Device interface */
359	DEVMETHOD(device_probe,		at91_twi_probe),
360	DEVMETHOD(device_attach,	at91_twi_attach),
361	DEVMETHOD(device_detach,	at91_twi_detach),
362
363	/* iicbus interface */
364	DEVMETHOD(iicbus_callback,	at91_twi_callback),
365	DEVMETHOD(iicbus_reset,		at91_twi_rst_card),
366	DEVMETHOD(iicbus_transfer,	at91_twi_transfer),
367	{ 0, 0 }
368};
369
370static driver_t at91_twi_driver = {
371	"at91_twi",
372	at91_twi_methods,
373	sizeof(struct at91_twi_softc),
374};
375
376DRIVER_MODULE(at91_twi, atmelarm, at91_twi_driver, at91_twi_devclass, 0, 0);
377DRIVER_MODULE(iicbus, at91_twi, iicbus_driver, iicbus_devclass, 0, 0);
378