xref: /netbsd-src/sys/arch/arm/broadcom/bcm2835_spi.c (revision cef8759bd76c1b621f8eab8faa6f208faabc2e15)
1 /*	$NetBSD: bcm2835_spi.c,v 1.7 2020/08/04 13:20:45 kardel Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 Jonathan A. Kollasch
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: bcm2835_spi.c,v 1.7 2020/08/04 13:20:45 kardel Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/device.h>
34 #include <sys/systm.h>
35 #include <sys/mutex.h>
36 #include <sys/bus.h>
37 #include <sys/intr.h>
38 #include <sys/kernel.h>
39 
40 #include <sys/bitops.h>
41 #include <dev/spi/spivar.h>
42 
43 #include <arm/broadcom/bcm2835reg.h>
44 #include <arm/broadcom/bcm2835_spireg.h>
45 
46 #include <dev/fdt/fdtvar.h>
47 
48 #include <arm/fdt/arm_fdtvar.h>
49 
50 struct bcmspi_softc {
51 	device_t		sc_dev;
52 	bus_space_tag_t		sc_iot;
53 	bus_space_handle_t	sc_ioh;
54 	void			*sc_intrh;
55 	struct spi_controller	sc_spi;
56 	kmutex_t                sc_mutex;
57 	SIMPLEQ_HEAD(,spi_transfer) sc_q;
58 	struct spi_transfer	*sc_transfer;
59 	struct spi_chunk	*sc_wchunk;
60 	struct spi_chunk	*sc_rchunk;
61 	uint32_t		sc_CS;
62 	volatile bool		sc_running;
63 };
64 
65 static int bcmspi_match(device_t, cfdata_t, void *);
66 static void bcmspi_attach(device_t, device_t, void *);
67 
68 static int bcmspi_configure(void *, int, int, int);
69 static int bcmspi_transfer(void *, struct spi_transfer *);
70 
71 static void bcmspi_start(struct bcmspi_softc * const);
72 static int bcmspi_intr(void *);
73 
74 static void bcmspi_send(struct bcmspi_softc * const);
75 static void bcmspi_recv(struct bcmspi_softc * const);
76 
77 CFATTACH_DECL_NEW(bcmspi, sizeof(struct bcmspi_softc),
78     bcmspi_match, bcmspi_attach, NULL, NULL);
79 
80 static int
81 bcmspi_match(device_t parent, cfdata_t cf, void *aux)
82 {
83 	const char * const compatible[] = {
84 		"brcm,bcm2835-spi",
85 		NULL
86 	};
87 	struct fdt_attach_args * const faa = aux;
88 
89 	return of_match_compatible(faa->faa_phandle, compatible);
90 }
91 
92 static void
93 bcmspi_attach(device_t parent, device_t self, void *aux)
94 {
95 	struct bcmspi_softc * const sc = device_private(self);
96 	struct fdt_attach_args * const faa = aux;
97 	struct spibus_attach_args sba;
98 
99 	aprint_naive("\n");
100 	aprint_normal(": SPI\n");
101 
102 	sc->sc_dev = self;
103 	sc->sc_iot = faa->faa_bst;
104 	SIMPLEQ_INIT(&sc->sc_q);
105 
106 	const int phandle = faa->faa_phandle;
107 	bus_addr_t addr;
108 	bus_size_t size;
109 
110 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
111 		aprint_error(": missing 'reg' property\n");
112 		return;
113 	}
114 
115 	if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh) != 0) {
116 		aprint_error_dev(sc->sc_dev, "unable to map device\n");
117 		return;
118 	}
119 
120 	char intrstr[128];
121 	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
122 		aprint_error(": failed to decode interrupt\n");
123 		return;
124 	}
125 
126 	sc->sc_intrh = fdtbus_intr_establish(phandle, 0, IPL_VM, 0,
127 	    bcmspi_intr, sc);
128 	if (sc->sc_intrh == NULL) {
129 		aprint_error_dev(sc->sc_dev, "unable to establish interrupt\n");
130 		return;
131 	}
132 	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
133 
134 	sc->sc_spi.sct_cookie = sc;
135 	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_VM);
136 	sc->sc_spi.sct_configure = bcmspi_configure;
137 	sc->sc_spi.sct_transfer = bcmspi_transfer;
138 	sc->sc_spi.sct_nslaves = 3;
139 
140 	memset(&sba, 0, sizeof(sba));
141 	sba.sba_controller = &sc->sc_spi;
142 
143 	(void) config_found_ia(self, "spibus", &sba, spibus_print);
144 }
145 
146 static int
147 bcmspi_configure(void *cookie, int slave, int mode, int speed)
148 {
149 	struct bcmspi_softc * const sc = cookie;
150 	uint32_t cs, clk;
151 
152 	cs = SPI_CS_INTR | SPI_CS_INTD;
153 
154 	if (slave > 2)
155 		return EINVAL;
156 
157 	if (speed <= 0)
158 		return EINVAL;
159 
160 	switch (mode) {
161 	case SPI_MODE_0:
162 		cs |= 0;
163 		break;
164 	case SPI_MODE_1:
165 		cs |= SPI_CS_CPHA;
166 		break;
167 	case SPI_MODE_2:
168 		cs |= SPI_CS_CPOL;
169 		break;
170 	case SPI_MODE_3:
171 		cs |= SPI_CS_CPHA|SPI_CS_CPOL;
172 		break;
173 	default:
174 		return EINVAL;
175 	}
176 
177 	sc->sc_CS = cs;
178 
179 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, cs);
180 
181 	clk = 2 * 250000000 / speed; /* XXX 250MHz */
182 	clk = (clk / 2) + (clk & 1);
183 	clk = roundup(clk, 2);
184 	clk = __SHIFTIN(clk, SPI_CLK_CDIV);
185 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CLK, clk);
186 
187 	return 0;
188 }
189 
190 static int
191 bcmspi_transfer(void *cookie, struct spi_transfer *st)
192 {
193 	struct bcmspi_softc * const sc = cookie;
194 
195 	mutex_enter(&sc->sc_mutex);
196 	spi_transq_enqueue(&sc->sc_q, st);
197 	if (sc->sc_running == false) {
198 		bcmspi_start(sc);
199 	}
200 	mutex_exit(&sc->sc_mutex);
201 	return 0;
202 }
203 
204 static void
205 bcmspi_start(struct bcmspi_softc * const sc)
206 {
207 	struct spi_transfer *st;
208 	uint32_t cs;
209 
210 	while ((st = spi_transq_first(&sc->sc_q)) != NULL) {
211 
212 		spi_transq_dequeue(&sc->sc_q);
213 
214 		KASSERT(sc->sc_transfer == NULL);
215 		sc->sc_transfer = st;
216 		sc->sc_rchunk = sc->sc_wchunk = st->st_chunks;
217 
218 		cs = sc->sc_CS;
219 		cs |= SPI_CS_TA;
220 		cs |= SPI_CS_CLEAR_TX;
221 		cs |= SPI_CS_CLEAR_RX;
222 		KASSERT(st->st_slave <= 2);
223 		cs |= __SHIFTIN(st->st_slave, SPI_CS_CS);
224 		sc->sc_running = true;
225 		bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, cs);
226 
227 		if (!cold)
228 			return;
229 
230 		for (;;) {
231 		        mutex_exit(&sc->sc_mutex);
232 			bcmspi_intr(sc);
233 			mutex_enter(&sc->sc_mutex);
234 			if (ISSET(st->st_flags, SPI_F_DONE))
235 				break;
236 		}
237 	}
238 
239 	sc->sc_running = false;
240 }
241 
242 static void
243 bcmspi_send(struct bcmspi_softc * const sc)
244 {
245 	uint32_t fd;
246 	uint32_t cs;
247 	struct spi_chunk *chunk;
248 
249 	while ((chunk = sc->sc_wchunk) != NULL) {
250 		while (chunk->chunk_wresid) {
251 			cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS);
252 			if ((cs & SPI_CS_TXD) == 0)
253 				return;
254 			if (chunk->chunk_wptr) {
255 				fd = *chunk->chunk_wptr++;
256 			} else {
257 				fd = '\0';
258 			}
259 			bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_FIFO, fd);
260 			chunk->chunk_wresid--;
261 		}
262 		sc->sc_wchunk = sc->sc_wchunk->chunk_next;
263 	}
264 }
265 
266 static void
267 bcmspi_recv(struct bcmspi_softc * const sc)
268 {
269 	uint32_t fd;
270 	uint32_t cs;
271 	struct spi_chunk *chunk;
272 
273 	while ((chunk = sc->sc_rchunk) != NULL) {
274 		while (chunk->chunk_rresid) {
275 			cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS);
276 			if ((cs & SPI_CS_RXD) == 0)
277 				return;
278 			fd = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_FIFO);
279 			if (chunk->chunk_rptr) {
280 				*chunk->chunk_rptr++ = fd & 0xff;
281 			}
282 			chunk->chunk_rresid--;
283 		}
284 		sc->sc_rchunk = sc->sc_rchunk->chunk_next;
285 	}
286 }
287 
288 static int
289 bcmspi_intr(void *cookie)
290 {
291 	struct bcmspi_softc * const sc = cookie;
292 	struct spi_transfer *st;
293 	uint32_t cs;
294 
295 	mutex_enter(&sc->sc_mutex);
296 	cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS);
297 	if (ISSET(cs, SPI_CS_DONE)) {
298 		if (sc->sc_wchunk != NULL) {
299 			bcmspi_send(sc);
300 			goto end;
301 		} else {
302 			bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS,
303 			    sc->sc_CS);
304 			bcmspi_recv(sc);
305 			sc->sc_rchunk = sc->sc_wchunk = NULL;
306 			st = sc->sc_transfer;
307 			sc->sc_transfer = NULL;
308 			KASSERT(st != NULL);
309 			spi_done(st, 0);
310 			sc->sc_running = false;
311 			goto end;
312 		}
313 	} else if (ISSET(cs, SPI_CS_RXR)) {
314 		bcmspi_recv(sc);
315 		bcmspi_send(sc);
316 	}
317 
318 end:
319 	mutex_exit(&sc->sc_mutex);
320 	return ISSET(cs, SPI_CS_DONE|SPI_CS_RXR);
321 }
322