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