at91_spi.c revision 183670
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 183670 2008-10-07 17:23:16Z 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/mbuf.h>
34#include <sys/malloc.h>
35#include <sys/module.h>
36#include <sys/mutex.h>
37#include <sys/rman.h>
38#include <machine/bus.h>
39
40#include <arm/at91/at91_spireg.h>
41#include <arm/at91/at91_pdcreg.h>
42
43#include <dev/spibus/spi.h>
44#include "spibus_if.h"
45
46struct at91_spi_softc
47{
48	device_t dev;			/* Myself */
49	void *intrhand;			/* Interrupt handle */
50	struct resource *irq_res;	/* IRQ resource */
51	struct resource	*mem_res;	/* Memory resource */
52	bus_dma_tag_t dmatag;		/* bus dma tag for mbufs */
53	bus_dmamap_t map[4];		/* Maps for the transaction */
54	int rxdone;
55};
56
57static inline uint32_t
58RD4(struct at91_spi_softc *sc, bus_size_t off)
59{
60	return bus_read_4(sc->mem_res, off);
61}
62
63static inline void
64WR4(struct at91_spi_softc *sc, bus_size_t off, uint32_t val)
65{
66	bus_write_4(sc->mem_res, off, val);
67}
68
69/* bus entry points */
70static int at91_spi_probe(device_t dev);
71static int at91_spi_attach(device_t dev);
72static int at91_spi_detach(device_t dev);
73
74/* helper routines */
75static int at91_spi_activate(device_t dev);
76static void at91_spi_deactivate(device_t dev);
77static void at91_spi_intr(void *arg);
78
79static int
80at91_spi_probe(device_t dev)
81{
82	device_set_desc(dev, "SPI");
83	return (0);
84}
85
86static int
87at91_spi_attach(device_t dev)
88{
89	struct at91_spi_softc *sc = device_get_softc(dev);
90	int err, i;
91
92	sc->dev = dev;
93	err = at91_spi_activate(dev);
94	if (err)
95		goto out;
96
97	/*
98	 * Allocate DMA tags and maps
99	 */
100	err = bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0,
101	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, 2058, 1,
102	    2048, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->dmatag);
103	if (err != 0)
104		goto out;
105	for (i = 0; i < 4; i++) {
106		err = bus_dmamap_create(sc->dmatag, 0,  &sc->map[i]);
107		if (err != 0)
108			goto out;
109	}
110
111	// reset the SPI
112	WR4(sc, SPI_CR, SPI_CR_SWRST);
113	WR4(sc, SPI_IDR, 0xffffffff);
114
115	WR4(sc, SPI_MR, (0xf << 24) | SPI_MR_MSTR | SPI_MR_MODFDIS |
116	    (0xE << 16));
117
118	WR4(sc, SPI_CSR0, SPI_CSR_CPOL | (4 << 16) | (2 << 8));
119	WR4(sc, SPI_CR, SPI_CR_SPIEN);
120
121	WR4(sc, PDC_PTCR, PDC_PTCR_TXTDIS);
122	WR4(sc, PDC_PTCR, PDC_PTCR_RXTDIS);
123	WR4(sc, PDC_RNPR, 0);
124	WR4(sc, PDC_RNCR, 0);
125	WR4(sc, PDC_TNPR, 0);
126	WR4(sc, PDC_TNCR, 0);
127	WR4(sc, PDC_RPR, 0);
128	WR4(sc, PDC_RCR, 0);
129	WR4(sc, PDC_TPR, 0);
130	WR4(sc, PDC_TCR, 0);
131	RD4(sc, SPI_RDR);
132	RD4(sc, SPI_SR);
133
134	device_add_child(dev, "spibus", -1);
135	bus_generic_attach(dev);
136out:;
137	if (err)
138		at91_spi_deactivate(dev);
139	return (err);
140}
141
142static int
143at91_spi_detach(device_t dev)
144{
145	return (EBUSY);	/* XXX */
146}
147
148static int
149at91_spi_activate(device_t dev)
150{
151	struct at91_spi_softc *sc;
152	int rid, err = ENOMEM;
153
154	sc = device_get_softc(dev);
155	rid = 0;
156	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
157	    RF_ACTIVE);
158	if (sc->mem_res == NULL)
159		goto errout;
160	rid = 0;
161	sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
162	    RF_ACTIVE);
163	if (sc->irq_res == NULL)
164		goto errout;
165	err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
166	    NULL, at91_spi_intr, sc, &sc->intrhand);
167	if (err != 0)
168		goto errout;
169	return (0);
170errout:
171	at91_spi_deactivate(dev);
172	return (err);
173}
174
175static void
176at91_spi_deactivate(device_t dev)
177{
178	struct at91_spi_softc *sc;
179
180	sc = device_get_softc(dev);
181	if (sc->intrhand)
182		bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
183	sc->intrhand = 0;
184	bus_generic_detach(sc->dev);
185	if (sc->mem_res)
186		bus_release_resource(dev, SYS_RES_IOPORT,
187		    rman_get_rid(sc->mem_res), sc->mem_res);
188	sc->mem_res = 0;
189	if (sc->irq_res)
190		bus_release_resource(dev, SYS_RES_IRQ,
191		    rman_get_rid(sc->irq_res), sc->irq_res);
192	sc->irq_res = 0;
193	return;
194}
195
196static void
197at91_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
198{
199	if (error != 0)
200		return;
201	*(bus_addr_t *)arg = segs[0].ds_addr;
202}
203
204static int
205at91_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
206{
207	struct at91_spi_softc *sc;
208	int i, j, rxdone, err, mode[4];
209	bus_addr_t addr;
210
211	sc = device_get_softc(dev);
212	WR4(sc, PDC_PTCR, PDC_PTCR_TXTDIS | PDC_PTCR_RXTDIS);
213	i = 0;
214	if (bus_dmamap_load(sc->dmatag, sc->map[i], cmd->tx_cmd,
215	    cmd->tx_cmd_sz, at91_getaddr, &addr, 0) != 0)
216		goto out;
217	WR4(sc, PDC_TPR, addr);
218	WR4(sc, PDC_TCR, cmd->tx_cmd_sz);
219	bus_dmamap_sync(sc->dmatag, sc->map[i], BUS_DMASYNC_PREWRITE);
220	mode[i++] = BUS_DMASYNC_POSTWRITE;
221	if (cmd->tx_data_sz > 0) {
222		if (bus_dmamap_load(sc->dmatag, sc->map[i], cmd->tx_data,
223			cmd->tx_data_sz, at91_getaddr, &addr, 0) != 0)
224			goto out;
225		WR4(sc, PDC_TNPR, addr);
226		WR4(sc, PDC_TNCR, cmd->tx_data_sz);
227		bus_dmamap_sync(sc->dmatag, sc->map[i], BUS_DMASYNC_PREWRITE);
228		mode[i++] = BUS_DMASYNC_POSTWRITE;
229	}
230	if (bus_dmamap_load(sc->dmatag, sc->map[i], cmd->rx_cmd,
231	    cmd->tx_cmd_sz, at91_getaddr, &addr, 0) != 0)
232		goto out;
233	WR4(sc, PDC_RPR, addr);
234	WR4(sc, PDC_RCR, cmd->tx_cmd_sz);
235	bus_dmamap_sync(sc->dmatag, sc->map[i], BUS_DMASYNC_PREREAD);
236	mode[i++] = BUS_DMASYNC_POSTREAD;
237	if (cmd->rx_data_sz > 0) {
238		if (bus_dmamap_load(sc->dmatag, sc->map[i], cmd->rx_data,
239			cmd->tx_data_sz, at91_getaddr, &addr, 0) != 0)
240			goto out;
241		WR4(sc, PDC_RNPR, addr);
242		WR4(sc, PDC_RNCR, cmd->rx_data_sz);
243		bus_dmamap_sync(sc->dmatag, sc->map[i], BUS_DMASYNC_PREREAD);
244		mode[i++] = BUS_DMASYNC_POSTREAD;
245	}
246	WR4(sc, SPI_IER, SPI_SR_ENDRX);
247	WR4(sc, PDC_PTCR, PDC_PTCR_TXTEN | PDC_PTCR_RXTEN);
248
249	rxdone = sc->rxdone;
250	do {
251		err = tsleep(&sc->rxdone, PCATCH | PZERO, "spi", hz);
252	} while (rxdone == sc->rxdone && err != EINTR);
253	WR4(sc, PDC_PTCR, PDC_PTCR_TXTDIS | PDC_PTCR_RXTDIS);
254	if (err == 0) {
255		for (j = 0; j < i; j++)
256			bus_dmamap_sync(sc->dmatag, sc->map[j], mode[j]);
257	}
258	for (j = 0; j < i; j++)
259		bus_dmamap_unload(sc->dmatag, sc->map[j]);
260	return (err);
261out:;
262	for (j = 0; j < i; j++)
263		bus_dmamap_unload(sc->dmatag, sc->map[j]);
264	return (EIO);
265}
266
267static void
268at91_spi_intr(void *arg)
269{
270	struct at91_spi_softc *sc = (struct at91_spi_softc*)arg;
271	uint32_t sr;
272
273	sr = RD4(sc, SPI_SR) & RD4(sc, SPI_IMR);
274	if (sr & SPI_SR_ENDRX) {
275		sc->rxdone++;
276		WR4(sc, SPI_IDR, SPI_SR_ENDRX);
277		wakeup(&sc->rxdone);
278	}
279	if (sr & ~SPI_SR_ENDRX) {
280		device_printf(sc->dev, "Unexpected ISR %#x\n", sr);
281		WR4(sc, SPI_IDR, sr & ~SPI_SR_ENDRX);
282	}
283}
284
285static devclass_t at91_spi_devclass;
286
287static device_method_t at91_spi_methods[] = {
288	/* Device interface */
289	DEVMETHOD(device_probe,		at91_spi_probe),
290	DEVMETHOD(device_attach,	at91_spi_attach),
291	DEVMETHOD(device_detach,	at91_spi_detach),
292
293	/* spibus interface */
294	DEVMETHOD(spibus_transfer,	at91_spi_transfer),
295	{ 0, 0 }
296};
297
298static driver_t at91_spi_driver = {
299	"at91_spi",
300	at91_spi_methods,
301	sizeof(struct at91_spi_softc),
302};
303
304DRIVER_MODULE(at91_spi, atmelarm, at91_spi_driver, at91_spi_devclass, 0, 0);
305