xref: /netbsd-src/sys/arch/arm/broadcom/bcm2835_spi.c (revision e187ab084f96d30e16c2efb15e682b478d4cc07d)
1*e187ab08Stnn /*	$NetBSD: bcm2835_spi.c,v 1.13 2023/09/03 11:36:52 tnn Exp $	*/
274dad322Sjakllsch 
374dad322Sjakllsch /*
474dad322Sjakllsch  * Copyright (c) 2012 Jonathan A. Kollasch
574dad322Sjakllsch  * All rights reserved.
674dad322Sjakllsch  *
774dad322Sjakllsch  * Redistribution and use in source and binary forms, with or without
874dad322Sjakllsch  * modification, are permitted provided that the following conditions
974dad322Sjakllsch  * are met:
1074dad322Sjakllsch  * 1. Redistributions of source code must retain the above copyright
1174dad322Sjakllsch  *    notice, this list of conditions and the following disclaimer.
1274dad322Sjakllsch  * 2. Redistributions in binary form must reproduce the above copyright
1374dad322Sjakllsch  *    notice, this list of conditions and the following disclaimer in the
1474dad322Sjakllsch  *    documentation and/or other materials provided with the distribution.
1574dad322Sjakllsch  *
1674dad322Sjakllsch  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1774dad322Sjakllsch  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
1874dad322Sjakllsch  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1974dad322Sjakllsch  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
2074dad322Sjakllsch  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
2174dad322Sjakllsch  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
2274dad322Sjakllsch  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
2374dad322Sjakllsch  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2474dad322Sjakllsch  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
2574dad322Sjakllsch  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2674dad322Sjakllsch  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2774dad322Sjakllsch  */
2874dad322Sjakllsch 
2974dad322Sjakllsch #include <sys/cdefs.h>
30*e187ab08Stnn __KERNEL_RCSID(0, "$NetBSD: bcm2835_spi.c,v 1.13 2023/09/03 11:36:52 tnn Exp $");
3174dad322Sjakllsch 
3274dad322Sjakllsch #include <sys/param.h>
3374dad322Sjakllsch #include <sys/device.h>
3474dad322Sjakllsch #include <sys/systm.h>
3574dad322Sjakllsch #include <sys/mutex.h>
3674dad322Sjakllsch #include <sys/bus.h>
3774dad322Sjakllsch #include <sys/intr.h>
3874dad322Sjakllsch #include <sys/kernel.h>
3974dad322Sjakllsch 
4074dad322Sjakllsch #include <sys/bitops.h>
4174dad322Sjakllsch #include <dev/spi/spivar.h>
4274dad322Sjakllsch 
4374dad322Sjakllsch #include <arm/broadcom/bcm2835reg.h>
4474dad322Sjakllsch #include <arm/broadcom/bcm2835_spireg.h>
45ee91b1e5Sskrll 
46ee91b1e5Sskrll #include <dev/fdt/fdtvar.h>
47ee91b1e5Sskrll 
48ee91b1e5Sskrll #include <arm/fdt/arm_fdtvar.h>
4974dad322Sjakllsch 
5074dad322Sjakllsch struct bcmspi_softc {
5174dad322Sjakllsch 	device_t		sc_dev;
5274dad322Sjakllsch 	bus_space_tag_t		sc_iot;
5374dad322Sjakllsch 	bus_space_handle_t	sc_ioh;
5474dad322Sjakllsch 	void			*sc_intrh;
5574dad322Sjakllsch 	struct spi_controller	sc_spi;
5637614169Skardel 	kmutex_t                sc_mutex;
5774dad322Sjakllsch 	SIMPLEQ_HEAD(,spi_transfer) sc_q;
5874dad322Sjakllsch 	struct spi_transfer	*sc_transfer;
5974dad322Sjakllsch 	struct spi_chunk	*sc_wchunk;
6074dad322Sjakllsch 	struct spi_chunk	*sc_rchunk;
6174dad322Sjakllsch 	uint32_t		sc_CS;
6274dad322Sjakllsch 	volatile bool		sc_running;
6374dad322Sjakllsch };
6474dad322Sjakllsch 
6574dad322Sjakllsch static int bcmspi_match(device_t, cfdata_t, void *);
6674dad322Sjakllsch static void bcmspi_attach(device_t, device_t, void *);
6774dad322Sjakllsch 
6874dad322Sjakllsch static int bcmspi_configure(void *, int, int, int);
6974dad322Sjakllsch static int bcmspi_transfer(void *, struct spi_transfer *);
7074dad322Sjakllsch 
7174dad322Sjakllsch static void bcmspi_start(struct bcmspi_softc * const);
7274dad322Sjakllsch static int bcmspi_intr(void *);
7374dad322Sjakllsch 
7474dad322Sjakllsch static void bcmspi_send(struct bcmspi_softc * const);
7574dad322Sjakllsch static void bcmspi_recv(struct bcmspi_softc * const);
7674dad322Sjakllsch 
7774dad322Sjakllsch CFATTACH_DECL_NEW(bcmspi, sizeof(struct bcmspi_softc),
7874dad322Sjakllsch     bcmspi_match, bcmspi_attach, NULL, NULL);
7974dad322Sjakllsch 
806e54367aSthorpej static const struct device_compatible_entry compat_data[] = {
816e54367aSthorpej 	{ .compat = "brcm,bcm2835-spi" },
826e54367aSthorpej 	DEVICE_COMPAT_EOL
836e54367aSthorpej };
846e54367aSthorpej 
8574dad322Sjakllsch static int
bcmspi_match(device_t parent,cfdata_t cf,void * aux)8674dad322Sjakllsch bcmspi_match(device_t parent, cfdata_t cf, void *aux)
8774dad322Sjakllsch {
88ee91b1e5Sskrll 	struct fdt_attach_args * const faa = aux;
8974dad322Sjakllsch 
906e54367aSthorpej 	return of_compatible_match(faa->faa_phandle, compat_data);
9174dad322Sjakllsch }
9274dad322Sjakllsch 
9374dad322Sjakllsch static void
bcmspi_attach(device_t parent,device_t self,void * aux)9474dad322Sjakllsch bcmspi_attach(device_t parent, device_t self, void *aux)
9574dad322Sjakllsch {
9674dad322Sjakllsch 	struct bcmspi_softc * const sc = device_private(self);
97ee91b1e5Sskrll 	struct fdt_attach_args * const faa = aux;
9874dad322Sjakllsch 	struct spibus_attach_args sba;
9974dad322Sjakllsch 
10074dad322Sjakllsch 	aprint_naive("\n");
10174dad322Sjakllsch 	aprint_normal(": SPI\n");
10274dad322Sjakllsch 
10374dad322Sjakllsch 	sc->sc_dev = self;
104ee91b1e5Sskrll 	sc->sc_iot = faa->faa_bst;
10574dad322Sjakllsch 	SIMPLEQ_INIT(&sc->sc_q);
106ee91b1e5Sskrll 
107ee91b1e5Sskrll 	const int phandle = faa->faa_phandle;
108ee91b1e5Sskrll 	bus_addr_t addr;
109ee91b1e5Sskrll 	bus_size_t size;
110ee91b1e5Sskrll 
111ee91b1e5Sskrll 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
112ee91b1e5Sskrll 		aprint_error(": missing 'reg' property\n");
113ee91b1e5Sskrll 		return;
114ee91b1e5Sskrll 	}
115ee91b1e5Sskrll 
116ee91b1e5Sskrll 	if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh) != 0) {
11774dad322Sjakllsch 		aprint_error_dev(sc->sc_dev, "unable to map device\n");
11874dad322Sjakllsch 		return;
11974dad322Sjakllsch 	}
12074dad322Sjakllsch 
121ee91b1e5Sskrll 	char intrstr[128];
122ee91b1e5Sskrll 	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
123ee91b1e5Sskrll 		aprint_error(": failed to decode interrupt\n");
124ee91b1e5Sskrll 		return;
125ee91b1e5Sskrll 	}
12674dad322Sjakllsch 
127bd99f6a8Sskrll 	sc->sc_intrh = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, 0,
128bd99f6a8Sskrll 	    bcmspi_intr, sc, device_xname(self));
12974dad322Sjakllsch 	if (sc->sc_intrh == NULL) {
13074dad322Sjakllsch 		aprint_error_dev(sc->sc_dev, "unable to establish interrupt\n");
13174dad322Sjakllsch 		return;
13274dad322Sjakllsch 	}
133ee91b1e5Sskrll 	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
13474dad322Sjakllsch 
13574dad322Sjakllsch 	sc->sc_spi.sct_cookie = sc;
13637614169Skardel 	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_VM);
13774dad322Sjakllsch 	sc->sc_spi.sct_configure = bcmspi_configure;
13874dad322Sjakllsch 	sc->sc_spi.sct_transfer = bcmspi_transfer;
13974dad322Sjakllsch 	sc->sc_spi.sct_nslaves = 3;
14074dad322Sjakllsch 
1419555f417Stnn 	memset(&sba, 0, sizeof(sba));
14274dad322Sjakllsch 	sba.sba_controller = &sc->sc_spi;
14374dad322Sjakllsch 
144c7fb772bSthorpej 	config_found(self, &sba, spibus_print, CFARGS_NONE);
14574dad322Sjakllsch }
14674dad322Sjakllsch 
14774dad322Sjakllsch static int
bcmspi_configure(void * cookie,int slave,int mode,int speed)14874dad322Sjakllsch bcmspi_configure(void *cookie, int slave, int mode, int speed)
14974dad322Sjakllsch {
15074dad322Sjakllsch 	struct bcmspi_softc * const sc = cookie;
15174dad322Sjakllsch 	uint32_t cs, clk;
15274dad322Sjakllsch 
15374dad322Sjakllsch 	cs = SPI_CS_INTR | SPI_CS_INTD;
15474dad322Sjakllsch 
15574dad322Sjakllsch 	if (slave > 2)
15674dad322Sjakllsch 		return EINVAL;
15774dad322Sjakllsch 
15874dad322Sjakllsch 	if (speed <= 0)
15974dad322Sjakllsch 		return EINVAL;
16074dad322Sjakllsch 
16174dad322Sjakllsch 	switch (mode) {
16274dad322Sjakllsch 	case SPI_MODE_0:
16374dad322Sjakllsch 		cs |= 0;
16474dad322Sjakllsch 		break;
16574dad322Sjakllsch 	case SPI_MODE_1:
16674dad322Sjakllsch 		cs |= SPI_CS_CPHA;
16774dad322Sjakllsch 		break;
16874dad322Sjakllsch 	case SPI_MODE_2:
16974dad322Sjakllsch 		cs |= SPI_CS_CPOL;
17074dad322Sjakllsch 		break;
17174dad322Sjakllsch 	case SPI_MODE_3:
17274dad322Sjakllsch 		cs |= SPI_CS_CPHA|SPI_CS_CPOL;
17374dad322Sjakllsch 		break;
17474dad322Sjakllsch 	default:
17574dad322Sjakllsch 		return EINVAL;
17674dad322Sjakllsch 	}
17774dad322Sjakllsch 
17874dad322Sjakllsch 	sc->sc_CS = cs;
17974dad322Sjakllsch 
18074dad322Sjakllsch 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, cs);
18174dad322Sjakllsch 
18274dad322Sjakllsch 	clk = 2 * 250000000 / speed; /* XXX 250MHz */
18374dad322Sjakllsch 	clk = (clk / 2) + (clk & 1);
18474dad322Sjakllsch 	clk = roundup(clk, 2);
185*e187ab08Stnn 	if (clk >= 0xfffe)
186*e187ab08Stnn 		clk = 0xfffe;
18774dad322Sjakllsch 	clk = __SHIFTIN(clk, SPI_CLK_CDIV);
18874dad322Sjakllsch 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CLK, clk);
18974dad322Sjakllsch 
19074dad322Sjakllsch 	return 0;
19174dad322Sjakllsch }
19274dad322Sjakllsch 
19374dad322Sjakllsch static int
bcmspi_transfer(void * cookie,struct spi_transfer * st)19474dad322Sjakllsch bcmspi_transfer(void *cookie, struct spi_transfer *st)
19574dad322Sjakllsch {
19674dad322Sjakllsch 	struct bcmspi_softc * const sc = cookie;
19774dad322Sjakllsch 
19837614169Skardel 	mutex_enter(&sc->sc_mutex);
19974dad322Sjakllsch 	spi_transq_enqueue(&sc->sc_q, st);
20074dad322Sjakllsch 	if (sc->sc_running == false) {
20174dad322Sjakllsch 		bcmspi_start(sc);
20274dad322Sjakllsch 	}
20337614169Skardel 	mutex_exit(&sc->sc_mutex);
20474dad322Sjakllsch 	return 0;
20574dad322Sjakllsch }
20674dad322Sjakllsch 
20774dad322Sjakllsch static void
bcmspi_start(struct bcmspi_softc * const sc)20874dad322Sjakllsch bcmspi_start(struct bcmspi_softc * const sc)
20974dad322Sjakllsch {
21074dad322Sjakllsch 	struct spi_transfer *st;
21174dad322Sjakllsch 	uint32_t cs;
21274dad322Sjakllsch 
21374dad322Sjakllsch 	while ((st = spi_transq_first(&sc->sc_q)) != NULL) {
21474dad322Sjakllsch 
21574dad322Sjakllsch 		spi_transq_dequeue(&sc->sc_q);
21674dad322Sjakllsch 
21774dad322Sjakllsch 		KASSERT(sc->sc_transfer == NULL);
21874dad322Sjakllsch 		sc->sc_transfer = st;
21974dad322Sjakllsch 		sc->sc_rchunk = sc->sc_wchunk = st->st_chunks;
22074dad322Sjakllsch 
22174dad322Sjakllsch 		cs = sc->sc_CS;
22274dad322Sjakllsch 		cs |= SPI_CS_TA;
22374dad322Sjakllsch 		cs |= SPI_CS_CLEAR_TX;
22474dad322Sjakllsch 		cs |= SPI_CS_CLEAR_RX;
22574dad322Sjakllsch 		KASSERT(st->st_slave <= 2);
22674dad322Sjakllsch 		cs |= __SHIFTIN(st->st_slave, SPI_CS_CS);
22774dad322Sjakllsch 		sc->sc_running = true;
22874dad322Sjakllsch 		bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, cs);
22974dad322Sjakllsch 
23074dad322Sjakllsch 		if (!cold)
23174dad322Sjakllsch 			return;
23274dad322Sjakllsch 
23374dad322Sjakllsch 		for (;;) {
23437614169Skardel 		        mutex_exit(&sc->sc_mutex);
23574dad322Sjakllsch 			bcmspi_intr(sc);
23637614169Skardel 			mutex_enter(&sc->sc_mutex);
23774dad322Sjakllsch 			if (ISSET(st->st_flags, SPI_F_DONE))
23874dad322Sjakllsch 				break;
23974dad322Sjakllsch 		}
24074dad322Sjakllsch 	}
24174dad322Sjakllsch 
24274dad322Sjakllsch 	sc->sc_running = false;
24374dad322Sjakllsch }
24474dad322Sjakllsch 
24574dad322Sjakllsch static void
bcmspi_send(struct bcmspi_softc * const sc)24674dad322Sjakllsch bcmspi_send(struct bcmspi_softc * const sc)
24774dad322Sjakllsch {
24874dad322Sjakllsch 	uint32_t fd;
24974dad322Sjakllsch 	uint32_t cs;
25074dad322Sjakllsch 	struct spi_chunk *chunk;
25174dad322Sjakllsch 
25274dad322Sjakllsch 	while ((chunk = sc->sc_wchunk) != NULL) {
25374dad322Sjakllsch 		while (chunk->chunk_wresid) {
25474dad322Sjakllsch 			cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS);
25574dad322Sjakllsch 			if ((cs & SPI_CS_TXD) == 0)
25674dad322Sjakllsch 				return;
25774dad322Sjakllsch 			if (chunk->chunk_wptr) {
25874dad322Sjakllsch 				fd = *chunk->chunk_wptr++;
25974dad322Sjakllsch 			} else {
26074dad322Sjakllsch 				fd = '\0';
26174dad322Sjakllsch 			}
26274dad322Sjakllsch 			bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_FIFO, fd);
26374dad322Sjakllsch 			chunk->chunk_wresid--;
26474dad322Sjakllsch 		}
26574dad322Sjakllsch 		sc->sc_wchunk = sc->sc_wchunk->chunk_next;
26674dad322Sjakllsch 	}
26774dad322Sjakllsch }
26874dad322Sjakllsch 
26974dad322Sjakllsch static void
bcmspi_recv(struct bcmspi_softc * const sc)27074dad322Sjakllsch bcmspi_recv(struct bcmspi_softc * const sc)
27174dad322Sjakllsch {
27274dad322Sjakllsch 	uint32_t fd;
27374dad322Sjakllsch 	uint32_t cs;
27474dad322Sjakllsch 	struct spi_chunk *chunk;
27574dad322Sjakllsch 
27674dad322Sjakllsch 	while ((chunk = sc->sc_rchunk) != NULL) {
27774dad322Sjakllsch 		while (chunk->chunk_rresid) {
27874dad322Sjakllsch 			cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS);
27974dad322Sjakllsch 			if ((cs & SPI_CS_RXD) == 0)
28074dad322Sjakllsch 				return;
28174dad322Sjakllsch 			fd = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_FIFO);
28274dad322Sjakllsch 			if (chunk->chunk_rptr) {
28374dad322Sjakllsch 				*chunk->chunk_rptr++ = fd & 0xff;
28474dad322Sjakllsch 			}
28574dad322Sjakllsch 			chunk->chunk_rresid--;
28674dad322Sjakllsch 		}
28774dad322Sjakllsch 		sc->sc_rchunk = sc->sc_rchunk->chunk_next;
28874dad322Sjakllsch 	}
28974dad322Sjakllsch }
29074dad322Sjakllsch 
29174dad322Sjakllsch static int
bcmspi_intr(void * cookie)29274dad322Sjakllsch bcmspi_intr(void *cookie)
29374dad322Sjakllsch {
29474dad322Sjakllsch 	struct bcmspi_softc * const sc = cookie;
29574dad322Sjakllsch 	struct spi_transfer *st;
29674dad322Sjakllsch 	uint32_t cs;
29774dad322Sjakllsch 
29837614169Skardel 	mutex_enter(&sc->sc_mutex);
29974dad322Sjakllsch 	cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS);
30074dad322Sjakllsch 	if (ISSET(cs, SPI_CS_DONE)) {
30174dad322Sjakllsch 		if (sc->sc_wchunk != NULL) {
30274dad322Sjakllsch 			bcmspi_send(sc);
30374dad322Sjakllsch 		} else {
30474dad322Sjakllsch 			bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS,
30574dad322Sjakllsch 			    sc->sc_CS);
30674dad322Sjakllsch 			bcmspi_recv(sc);
30774dad322Sjakllsch 			sc->sc_rchunk = sc->sc_wchunk = NULL;
30874dad322Sjakllsch 			st = sc->sc_transfer;
30974dad322Sjakllsch 			sc->sc_transfer = NULL;
31074dad322Sjakllsch 			KASSERT(st != NULL);
31174dad322Sjakllsch 			spi_done(st, 0);
31274dad322Sjakllsch 			sc->sc_running = false;
31374dad322Sjakllsch 		}
31474dad322Sjakllsch 	} else if (ISSET(cs, SPI_CS_RXR)) {
31574dad322Sjakllsch 		bcmspi_recv(sc);
31674dad322Sjakllsch 		bcmspi_send(sc);
31774dad322Sjakllsch 	}
31874dad322Sjakllsch 
31937614169Skardel 	mutex_exit(&sc->sc_mutex);
32074dad322Sjakllsch 	return ISSET(cs, SPI_CS_DONE|SPI_CS_RXR);
32174dad322Sjakllsch }
322