1/* $NetBSD: bcm2835_spi.c,v 1.13 2023/09/03 11:36:52 tnn 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.13 2023/09/03 11:36:52 tnn 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 50struct 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 65static int bcmspi_match(device_t, cfdata_t, void *); 66static void bcmspi_attach(device_t, device_t, void *); 67 68static int bcmspi_configure(void *, int, int, int); 69static int bcmspi_transfer(void *, struct spi_transfer *); 70 71static void bcmspi_start(struct bcmspi_softc * const); 72static int bcmspi_intr(void *); 73 74static void bcmspi_send(struct bcmspi_softc * const); 75static void bcmspi_recv(struct bcmspi_softc * const); 76 77CFATTACH_DECL_NEW(bcmspi, sizeof(struct bcmspi_softc), 78 bcmspi_match, bcmspi_attach, NULL, NULL); 79 80static const struct device_compatible_entry compat_data[] = { 81 { .compat = "brcm,bcm2835-spi" }, 82 DEVICE_COMPAT_EOL 83}; 84 85static int 86bcmspi_match(device_t parent, cfdata_t cf, void *aux) 87{ 88 struct fdt_attach_args * const faa = aux; 89 90 return of_compatible_match(faa->faa_phandle, compat_data); 91} 92 93static void 94bcmspi_attach(device_t parent, device_t self, void *aux) 95{ 96 struct bcmspi_softc * const sc = device_private(self); 97 struct fdt_attach_args * const faa = aux; 98 struct spibus_attach_args sba; 99 100 aprint_naive("\n"); 101 aprint_normal(": SPI\n"); 102 103 sc->sc_dev = self; 104 sc->sc_iot = faa->faa_bst; 105 SIMPLEQ_INIT(&sc->sc_q); 106 107 const int phandle = faa->faa_phandle; 108 bus_addr_t addr; 109 bus_size_t size; 110 111 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 112 aprint_error(": missing 'reg' property\n"); 113 return; 114 } 115 116 if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh) != 0) { 117 aprint_error_dev(sc->sc_dev, "unable to map device\n"); 118 return; 119 } 120 121 char intrstr[128]; 122 if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { 123 aprint_error(": failed to decode interrupt\n"); 124 return; 125 } 126 127 sc->sc_intrh = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, 0, 128 bcmspi_intr, sc, device_xname(self)); 129 if (sc->sc_intrh == NULL) { 130 aprint_error_dev(sc->sc_dev, "unable to establish interrupt\n"); 131 return; 132 } 133 aprint_normal_dev(self, "interrupting on %s\n", intrstr); 134 135 sc->sc_spi.sct_cookie = sc; 136 mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_VM); 137 sc->sc_spi.sct_configure = bcmspi_configure; 138 sc->sc_spi.sct_transfer = bcmspi_transfer; 139 sc->sc_spi.sct_nslaves = 3; 140 141 memset(&sba, 0, sizeof(sba)); 142 sba.sba_controller = &sc->sc_spi; 143 144 config_found(self, &sba, spibus_print, CFARGS_NONE); 145} 146 147static int 148bcmspi_configure(void *cookie, int slave, int mode, int speed) 149{ 150 struct bcmspi_softc * const sc = cookie; 151 uint32_t cs, clk; 152 153 cs = SPI_CS_INTR | SPI_CS_INTD; 154 155 if (slave > 2) 156 return EINVAL; 157 158 if (speed <= 0) 159 return EINVAL; 160 161 switch (mode) { 162 case SPI_MODE_0: 163 cs |= 0; 164 break; 165 case SPI_MODE_1: 166 cs |= SPI_CS_CPHA; 167 break; 168 case SPI_MODE_2: 169 cs |= SPI_CS_CPOL; 170 break; 171 case SPI_MODE_3: 172 cs |= SPI_CS_CPHA|SPI_CS_CPOL; 173 break; 174 default: 175 return EINVAL; 176 } 177 178 sc->sc_CS = cs; 179 180 bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, cs); 181 182 clk = 2 * 250000000 / speed; /* XXX 250MHz */ 183 clk = (clk / 2) + (clk & 1); 184 clk = roundup(clk, 2); 185 if (clk >= 0xfffe) 186 clk = 0xfffe; 187 clk = __SHIFTIN(clk, SPI_CLK_CDIV); 188 bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CLK, clk); 189 190 return 0; 191} 192 193static int 194bcmspi_transfer(void *cookie, struct spi_transfer *st) 195{ 196 struct bcmspi_softc * const sc = cookie; 197 198 mutex_enter(&sc->sc_mutex); 199 spi_transq_enqueue(&sc->sc_q, st); 200 if (sc->sc_running == false) { 201 bcmspi_start(sc); 202 } 203 mutex_exit(&sc->sc_mutex); 204 return 0; 205} 206 207static void 208bcmspi_start(struct bcmspi_softc * const sc) 209{ 210 struct spi_transfer *st; 211 uint32_t cs; 212 213 while ((st = spi_transq_first(&sc->sc_q)) != NULL) { 214 215 spi_transq_dequeue(&sc->sc_q); 216 217 KASSERT(sc->sc_transfer == NULL); 218 sc->sc_transfer = st; 219 sc->sc_rchunk = sc->sc_wchunk = st->st_chunks; 220 221 cs = sc->sc_CS; 222 cs |= SPI_CS_TA; 223 cs |= SPI_CS_CLEAR_TX; 224 cs |= SPI_CS_CLEAR_RX; 225 KASSERT(st->st_slave <= 2); 226 cs |= __SHIFTIN(st->st_slave, SPI_CS_CS); 227 sc->sc_running = true; 228 bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, cs); 229 230 if (!cold) 231 return; 232 233 for (;;) { 234 mutex_exit(&sc->sc_mutex); 235 bcmspi_intr(sc); 236 mutex_enter(&sc->sc_mutex); 237 if (ISSET(st->st_flags, SPI_F_DONE)) 238 break; 239 } 240 } 241 242 sc->sc_running = false; 243} 244 245static void 246bcmspi_send(struct bcmspi_softc * const sc) 247{ 248 uint32_t fd; 249 uint32_t cs; 250 struct spi_chunk *chunk; 251 252 while ((chunk = sc->sc_wchunk) != NULL) { 253 while (chunk->chunk_wresid) { 254 cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS); 255 if ((cs & SPI_CS_TXD) == 0) 256 return; 257 if (chunk->chunk_wptr) { 258 fd = *chunk->chunk_wptr++; 259 } else { 260 fd = '\0'; 261 } 262 bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_FIFO, fd); 263 chunk->chunk_wresid--; 264 } 265 sc->sc_wchunk = sc->sc_wchunk->chunk_next; 266 } 267} 268 269static void 270bcmspi_recv(struct bcmspi_softc * const sc) 271{ 272 uint32_t fd; 273 uint32_t cs; 274 struct spi_chunk *chunk; 275 276 while ((chunk = sc->sc_rchunk) != NULL) { 277 while (chunk->chunk_rresid) { 278 cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS); 279 if ((cs & SPI_CS_RXD) == 0) 280 return; 281 fd = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_FIFO); 282 if (chunk->chunk_rptr) { 283 *chunk->chunk_rptr++ = fd & 0xff; 284 } 285 chunk->chunk_rresid--; 286 } 287 sc->sc_rchunk = sc->sc_rchunk->chunk_next; 288 } 289} 290 291static int 292bcmspi_intr(void *cookie) 293{ 294 struct bcmspi_softc * const sc = cookie; 295 struct spi_transfer *st; 296 uint32_t cs; 297 298 mutex_enter(&sc->sc_mutex); 299 cs = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_CS); 300 if (ISSET(cs, SPI_CS_DONE)) { 301 if (sc->sc_wchunk != NULL) { 302 bcmspi_send(sc); 303 } else { 304 bus_space_write_4(sc->sc_iot, sc->sc_ioh, SPI_CS, 305 sc->sc_CS); 306 bcmspi_recv(sc); 307 sc->sc_rchunk = sc->sc_wchunk = NULL; 308 st = sc->sc_transfer; 309 sc->sc_transfer = NULL; 310 KASSERT(st != NULL); 311 spi_done(st, 0); 312 sc->sc_running = false; 313 } 314 } else if (ISSET(cs, SPI_CS_RXR)) { 315 bcmspi_recv(sc); 316 bcmspi_send(sc); 317 } 318 319 mutex_exit(&sc->sc_mutex); 320 return ISSET(cs, SPI_CS_DONE|SPI_CS_RXR); 321} 322