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