at91_spi.c revision 155324
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_spi.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/at91_spireg.h>
42#include <arm/at91/at91_spiio.h>
43
44struct at91_spi_softc
45{
46	device_t dev;			/* Myself */
47	void *intrhand;			/* Interrupt handle */
48	struct resource *irq_res;	/* IRQ resource */
49	struct resource	*mem_res;	/* Memory resource */
50	struct mtx sc_mtx;		/* basically a perimeter lock */
51	int flags;
52#define XFER_PENDING	1		/* true when transfer taking place */
53#define OPENED		2		/* Device opened */
54#define RXRDY		4
55#define TXCOMP		8
56#define TXRDY		0x10
57	struct cdev *cdev;
58};
59
60static inline uint32_t
61RD4(struct at91_spi_softc *sc, bus_size_t off)
62{
63	return bus_read_4(sc->mem_res, off);
64}
65
66static inline void
67WR4(struct at91_spi_softc *sc, bus_size_t off, uint32_t val)
68{
69	bus_write_4(sc->mem_res, off, val);
70}
71
72#define AT91_SPI_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
73#define	AT91_SPI_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
74#define AT91_SPI_LOCK_INIT(_sc) \
75	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
76	    "spi", MTX_DEF)
77#define AT91_SPI_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
78#define AT91_SPI_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
79#define AT91_SPI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
80#define CDEV2SOFTC(dev)		((dev)->si_drv1)
81
82static devclass_t at91_spi_devclass;
83
84/* bus entry points */
85
86static int at91_spi_probe(device_t dev);
87static int at91_spi_attach(device_t dev);
88static int at91_spi_detach(device_t dev);
89static void at91_spi_intr(void *);
90
91/* helper routines */
92static int at91_spi_activate(device_t dev);
93static void at91_spi_deactivate(device_t dev);
94
95/* cdev routines */
96static d_open_t at91_spi_open;
97static d_close_t at91_spi_close;
98static d_ioctl_t at91_spi_ioctl;
99
100static struct cdevsw at91_spi_cdevsw =
101{
102	.d_version = D_VERSION,
103	.d_open = at91_spi_open,
104	.d_close = at91_spi_close,
105	.d_ioctl = at91_spi_ioctl
106};
107
108static int
109at91_spi_probe(device_t dev)
110{
111	device_set_desc(dev, "SPI");
112	return (0);
113}
114
115static int
116at91_spi_attach(device_t dev)
117{
118	struct at91_spi_softc *sc = device_get_softc(dev);
119	int err;
120
121	sc->dev = dev;
122	err = at91_spi_activate(dev);
123	if (err)
124		goto out;
125
126	AT91_SPI_LOCK_INIT(sc);
127
128	/*
129	 * Activate the interrupt
130	 */
131	err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
132	    at91_spi_intr, sc, &sc->intrhand);
133	if (err) {
134		AT91_SPI_LOCK_DESTROY(sc);
135		goto out;
136	}
137	sc->cdev = make_dev(&at91_spi_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
138	    "spi%d", device_get_unit(dev));
139	if (sc->cdev == NULL) {
140		err = ENOMEM;
141		goto out;
142	}
143	sc->cdev->si_drv1 = sc;
144#if 0
145	/* init */
146	sc->cwgr = SPI_CWGR_CKDIV(1) |
147	    SPI_CWGR_CHDIV(SPI_CWGR_DIV(SPI_DEF_CLK)) |
148	    SPI_CWGR_CLDIV(SPI_CWGR_DIV(SPI_DEF_CLK));
149
150	WR4(sc, SPI_CR, SPI_CR_SWRST);
151	WR4(sc, SPI_CR, SPI_CR_MSEN | SPI_CR_SVDIS);
152	WR4(sc, SPI_CWGR, sc->cwgr);
153#endif
154out:;
155	if (err)
156		at91_spi_deactivate(dev);
157	return (err);
158}
159
160static int
161at91_spi_detach(device_t dev)
162{
163	return (EBUSY);	/* XXX */
164}
165
166static int
167at91_spi_activate(device_t dev)
168{
169	struct at91_spi_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_spi_deactivate(dev);
186	return (ENOMEM);
187}
188
189static void
190at91_spi_deactivate(device_t dev)
191{
192	struct at91_spi_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_spi_intr(void *xsc)
212{
213	struct at91_spi_softc *sc = xsc;
214#if 0
215	uint32_t status;
216
217	/* Reading the status also clears the interrupt */
218	status = RD4(sc, SPI_SR);
219	if (status == 0)
220		return;
221	AT91_SPI_LOCK(sc);
222	if (status & SPI_SR_RXRDY)
223		sc->flags |= RXRDY;
224	if (status & SPI_SR_TXCOMP)
225		sc->flags |= TXCOMP;
226	if (status & SPI_SR_TXRDY)
227		sc->flags |= TXRDY;
228	AT91_SPI_UNLOCK(sc);
229#endif
230	wakeup(sc);
231	return;
232}
233
234static int
235at91_spi_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
236{
237	struct at91_spi_softc *sc;
238
239	sc = CDEV2SOFTC(dev);
240	AT91_SPI_LOCK(sc);
241	if (!(sc->flags & OPENED)) {
242		sc->flags |= OPENED;
243#if 0
244		WR4(sc, SPI_IER, SPI_SR_TXCOMP | SPI_SR_RXRDY | SPI_SR_TXRDY |
245		    SPI_SR_OVRE | SPI_SR_UNRE | SPI_SR_NACK);
246#endif
247	}
248	AT91_SPI_UNLOCK(sc);
249    	return (0);
250}
251
252static int
253at91_spi_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
254{
255	struct at91_spi_softc *sc;
256
257	sc = CDEV2SOFTC(dev);
258	AT91_SPI_LOCK(sc);
259	sc->flags &= ~OPENED;
260#if 0
261	WR4(sc, SPI_IDR, SPI_SR_TXCOMP | SPI_SR_RXRDY | SPI_SR_TXRDY |
262	    SPI_SR_OVRE | SPI_SR_UNRE | SPI_SR_NACK);
263#endif
264	AT91_SPI_UNLOCK(sc);
265	return (0);
266}
267
268static int
269at91_spi_read_master(struct at91_spi_softc *sc, struct at91_spi_io *xfr)
270{
271#if 1
272    return ENOTTY;
273#else
274	uint8_t *walker;
275	uint8_t buffer[256];
276	size_t len;
277	int err = 0;
278
279	if (xfr->xfer_len > sizeof(buffer))
280		return (EINVAL);
281	walker = buffer;
282	len = xfr->xfer_len;
283	RD4(sc, SPI_RHR);
284	// Master mode, with the right address and interal addr size
285	WR4(sc, SPI_MMR, SPI_MMR_IADRSZ(xfr->iadrsz) | SPI_MMR_MREAD |
286	    SPI_MMR_DADR(xfr->dadr));
287	WR4(sc, SPI_IADR, xfr->iadr);
288	WR4(sc, SPI_CR, SPI_CR_START);
289	while (len-- > 1) {
290		while (!(sc->flags & RXRDY)) {
291			err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spird",
292			    0);
293			if (err)
294				return (err);
295		}
296		sc->flags &= ~RXRDY;
297		*walker++ = RD4(sc, SPI_RHR) & 0xff;
298	}
299	WR4(sc, SPI_CR, SPI_CR_STOP);
300	while (!(sc->flags & TXCOMP)) {
301		err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spird2", 0);
302		if (err)
303			return (err);
304	}
305	sc->flags &= ~TXCOMP;
306	*walker = RD4(sc, SPI_RHR) & 0xff;
307	if (xfr->xfer_buf) {
308		AT91_SPI_UNLOCK(sc);
309		err = copyout(buffer, xfr->xfer_buf, xfr->xfer_len);
310		AT91_SPI_LOCK(sc);
311	}
312	return (err);
313#endif
314}
315
316static int
317at91_spi_write_master(struct at91_spi_softc *sc, struct at91_spi_io *xfr)
318{
319#if 1
320    return ENOTTY;
321#else
322	uint8_t *walker;
323	uint8_t buffer[256];
324	size_t len;
325	int err;
326
327	if (xfr->xfer_len > sizeof(buffer))
328		return (EINVAL);
329	walker = buffer;
330	len = xfr->xfer_len;
331	AT91_SPI_UNLOCK(sc);
332	err = copyin(xfr->xfer_buf, buffer, xfr->xfer_len);
333	AT91_SPI_LOCK(sc);
334	if (err)
335		return (err);
336	/* Setup the xfr for later readback */
337	xfr->xfer_buf = 0;
338	xfr->xfer_len = 1;
339	while (len--) {
340		WR4(sc, SPI_MMR, SPI_MMR_IADRSZ(xfr->iadrsz) | SPI_MMR_MWRITE |
341		    SPI_MMR_DADR(xfr->dadr));
342		WR4(sc, SPI_IADR, xfr->iadr++);
343		WR4(sc, SPI_THR, *walker++);
344		WR4(sc, SPI_CR, SPI_CR_START);
345		/*
346		 * If we get signal while waiting for TXRDY, make sure we
347		 * try to stop this device
348		 */
349		while (!(sc->flags & TXRDY)) {
350			err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spiwr",
351			    0);
352			if (err)
353				break;
354		}
355		WR4(sc, SPI_CR, SPI_CR_STOP);
356		if (err)
357			return (err);
358		while (!(sc->flags & TXCOMP)) {
359			err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH, "spiwr2",
360			    0);
361			if (err)
362				return (err);
363		}
364		/* Readback */
365		at91_spi_read_master(sc, xfr);
366	}
367	return (err);
368#endif
369}
370
371static int
372at91_spi_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
373    struct thread *td)
374{
375	int err = 0;
376	struct at91_spi_softc *sc;
377
378	sc = CDEV2SOFTC(dev);
379	AT91_SPI_LOCK(sc);
380	while (sc->flags & XFER_PENDING) {
381		err = msleep(sc, &sc->sc_mtx, PZERO | PCATCH,
382		    "spiwait", 0);
383		if (err) {
384			AT91_SPI_UNLOCK(sc);
385			return (err);
386		}
387	}
388	sc->flags |= XFER_PENDING;
389
390	switch (cmd)
391	{
392	case SPIIOCXFER:
393	{
394		struct at91_spi_io *xfr = (struct at91_spi_io *)data;
395		switch (xfr->type)
396		{
397		case SPI_IO_READ_MASTER:
398			err = at91_spi_read_master(sc, xfr);
399			break;
400		case SPI_IO_WRITE_MASTER:
401			err = at91_spi_write_master(sc, xfr);
402			break;
403		default:
404			err = EINVAL;
405			break;
406		}
407		break;
408	}
409
410	case SPIIOCSETCLOCK:
411	{
412#if 0
413		struct at91_spi_clock *spick = (struct at91_spi_clock *)data;
414
415		sc->cwgr = SPI_CWGR_CKDIV(spick->ckdiv) |
416		    SPI_CWGR_CHDIV(SPI_CWGR_DIV(spick->high_rate)) |
417		    SPI_CWGR_CLDIV(SPI_CWGR_DIV(spick->low_rate));
418		WR4(sc, SPI_CR, SPI_CR_SWRST);
419		WR4(sc, SPI_CR, SPI_CR_MSEN | SPI_CR_SVDIS);
420		WR4(sc, SPI_CWGR, sc->cwgr);
421#else
422		err = ENOTTY;
423#endif
424		break;
425	}
426	default:
427		err = ENOTTY;
428		break;
429	}
430	sc->flags &= ~XFER_PENDING;
431	AT91_SPI_UNLOCK(sc);
432	wakeup(sc);
433	return err;
434}
435
436static device_method_t at91_spi_methods[] = {
437	/* Device interface */
438	DEVMETHOD(device_probe,		at91_spi_probe),
439	DEVMETHOD(device_attach,	at91_spi_attach),
440	DEVMETHOD(device_detach,	at91_spi_detach),
441
442	{ 0, 0 }
443};
444
445static driver_t at91_spi_driver = {
446	"at91_spi",
447	at91_spi_methods,
448	sizeof(struct at91_spi_softc),
449};
450
451DRIVER_MODULE(at91_spi, atmelarm, at91_spi_driver, at91_spi_devclass, 0, 0);
452