1 /* $NetBSD: sun6i_spi.c,v 1.11 2025/09/10 01:55:07 thorpej Exp $ */ 2 3 /* 4 * Copyright (c) 2019 Tobias Nygren 5 * Copyright (c) 2018 Jonathan A. Kollasch 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 24 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 27 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __KERNEL_RCSID(0, "$NetBSD: sun6i_spi.c,v 1.11 2025/09/10 01:55:07 thorpej Exp $"); 32 33 #include <sys/param.h> 34 #include <sys/device.h> 35 #include <sys/systm.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/sunxi/sun6i_spireg.h> 44 45 #include <dev/fdt/fdtvar.h> 46 47 #include <arm/fdt/arm_fdtvar.h> 48 49 #define SPI_IER_DEFAULT (SPI_IER_TC_INT_EN | SPI_IER_TF_UDR_INT_EN | \ 50 SPI_IER_TF_OVF_INT_EN | SPI_IER_RF_UDR_INT_EN | SPI_IER_RF_OVF_INT_EN) 51 52 struct sun6ispi_softc { 53 device_t sc_dev; 54 bus_space_tag_t sc_iot; 55 bus_space_handle_t sc_ioh; 56 void *sc_intrh; 57 struct spi_controller sc_spi; 58 SIMPLEQ_HEAD(,spi_transfer) sc_q; 59 struct spi_transfer *sc_transfer; 60 struct spi_chunk *sc_wchunk; 61 struct spi_chunk *sc_rchunk; 62 uint32_t sc_TCR; 63 u_int sc_modclkrate; 64 volatile bool sc_running; 65 }; 66 67 #define SPIREG_READ(sc, reg) \ 68 bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) 69 #define SPIREG_WRITE(sc, reg, val) \ 70 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 71 72 static int sun6ispi_match(device_t, cfdata_t, void *); 73 static void sun6ispi_attach(device_t, device_t, void *); 74 75 static int sun6ispi_configure(void *, int, int, int); 76 static int sun6ispi_transfer(void *, struct spi_transfer *); 77 78 static void sun6ispi_start(struct sun6ispi_softc * const); 79 static int sun6ispi_intr(void *); 80 81 static void sun6ispi_send(struct sun6ispi_softc * const); 82 static void sun6ispi_recv(struct sun6ispi_softc * const); 83 84 CFATTACH_DECL_NEW(sun6i_spi, sizeof(struct sun6ispi_softc), 85 sun6ispi_match, sun6ispi_attach, NULL, NULL); 86 87 static const struct device_compatible_entry compat_data[] = { 88 { .compat = "allwinner,sun8i-h3-spi" }, 89 DEVICE_COMPAT_EOL 90 }; 91 92 static int 93 sun6ispi_match(device_t parent, cfdata_t cf, void *aux) 94 { 95 struct fdt_attach_args * const faa = aux; 96 97 return of_compatible_match(faa->faa_phandle, compat_data); 98 } 99 100 static void 101 sun6ispi_attach(device_t parent, device_t self, void *aux) 102 { 103 struct sun6ispi_softc * const sc = device_private(self); 104 struct fdt_attach_args * const faa = aux; 105 const int phandle = faa->faa_phandle; 106 bus_addr_t addr; 107 bus_size_t size; 108 struct fdtbus_reset *rst; 109 struct clk *clk, *modclk; 110 uint32_t gcr, isr; 111 char intrstr[128]; 112 113 sc->sc_dev = self; 114 sc->sc_iot = faa->faa_bst; 115 SIMPLEQ_INIT(&sc->sc_q); 116 117 if ((clk = fdtbus_clock_get_index(phandle, 0)) == NULL 118 || clk_enable(clk) != 0) { 119 aprint_error(": couldn't enable clock\n"); 120 return; 121 } 122 123 /* 200MHz max on H3,H5 */ 124 if ((modclk = fdtbus_clock_get(phandle, "mod")) == NULL 125 || clk_set_rate(modclk, 200000000) != 0 126 || clk_enable(modclk) != 0) { 127 aprint_error(": couldn't enable module clock\n"); 128 return; 129 } 130 sc->sc_modclkrate = clk_get_rate(modclk); 131 132 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0 133 || bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh) != 0) { 134 aprint_error(": couldn't map registers\n"); 135 return; 136 } 137 138 if ((rst = fdtbus_reset_get_index(phandle, 0)) != NULL) 139 if (fdtbus_reset_deassert(rst) != 0) { 140 aprint_error(": couldn't de-assert reset\n"); 141 return; 142 } 143 144 isr = SPIREG_READ(sc, SPI_INT_STA); 145 SPIREG_WRITE(sc, SPI_INT_STA, isr); 146 147 if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { 148 aprint_error(": failed to decode interrupt\n"); 149 return; 150 } 151 152 sc->sc_intrh = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, 0, 153 sun6ispi_intr, sc, device_xname(self)); 154 if (sc->sc_intrh == NULL) { 155 aprint_error(": unable to establish interrupt\n"); 156 return; 157 } 158 159 aprint_naive("\n"); 160 aprint_normal(": SPI\n"); 161 162 aprint_normal_dev(self, "interrupting on %s\n", intrstr); 163 164 gcr = SPI_GCR_SRST; 165 SPIREG_WRITE(sc, SPI_GCR, gcr); 166 for (u_int i = 0; ; i++) { 167 if (i >= 1000000) { 168 aprint_error_dev(self, "reset timeout\n"); 169 return; 170 } 171 gcr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SPI_GCR); 172 if ((gcr & SPI_GCR_SRST) == 0) 173 break; 174 else 175 DELAY(1); 176 } 177 gcr = SPI_GCR_TP_EN | SPI_GCR_MODE | SPI_GCR_EN; 178 SPIREG_WRITE(sc, SPI_GCR, gcr); 179 180 SPIREG_WRITE(sc, SPI_IER, SPI_IER_DEFAULT); 181 182 sc->sc_spi.sct_cookie = sc; 183 sc->sc_spi.sct_configure = sun6ispi_configure; 184 sc->sc_spi.sct_transfer = sun6ispi_transfer; 185 sc->sc_spi.sct_nslaves = 4; 186 187 spibus_attach(self, &sc->sc_spi); 188 } 189 190 static int 191 sun6ispi_configure(void *cookie, int slave, int mode, int speed) 192 { 193 struct sun6ispi_softc * const sc = cookie; 194 uint32_t tcr, cctl; 195 uint32_t minfreq, maxfreq; 196 197 minfreq = sc->sc_modclkrate >> 16; 198 maxfreq = sc->sc_modclkrate >> 1; 199 200 if (speed <= 0 || speed < minfreq || speed > maxfreq) 201 return EINVAL; 202 203 if (slave >= sc->sc_spi.sct_nslaves) 204 return EINVAL; 205 206 tcr = SPI_TCR_SS_LEVEL | SPI_TCR_SPOL; 207 208 switch (mode) { 209 case SPI_MODE_0: 210 tcr |= 0; 211 break; 212 case SPI_MODE_1: 213 tcr |= SPI_TCR_CPHA; 214 break; 215 case SPI_MODE_2: 216 tcr |= SPI_TCR_CPOL; 217 break; 218 case SPI_MODE_3: 219 tcr |= SPI_TCR_CPHA|SPI_TCR_CPOL; 220 break; 221 default: 222 return EINVAL; 223 } 224 225 sc->sc_TCR = tcr; 226 227 if (speed < sc->sc_modclkrate / 512) { 228 for (cctl = 0; cctl <= __SHIFTOUT_MASK(SPI_CCTL_CDR1); cctl++) { 229 if ((sc->sc_modclkrate / (1<<cctl)) <= speed) 230 goto cdr1_found; 231 } 232 return EINVAL; 233 cdr1_found: 234 cctl = __SHIFTIN(cctl, SPI_CCTL_CDR1); 235 } else { 236 cctl = howmany(sc->sc_modclkrate, 2 * speed) - 1; 237 cctl = SPI_CCTL_DRS|__SHIFTIN(cctl, SPI_CCTL_CDR2); 238 } 239 240 device_printf(sc->sc_dev, "tcr 0x%x, cctl 0x%x, CLK %uHz, SCLK %uHz\n", 241 tcr, cctl, sc->sc_modclkrate, 242 (cctl & SPI_CCTL_DRS) 243 ? (sc->sc_modclkrate / (u_int)(2 * (__SHIFTOUT(cctl, SPI_CCTL_CDR2) + 1))) 244 : (sc->sc_modclkrate >> (__SHIFTOUT(cctl, SPI_CCTL_CDR1) + 1)) 245 ); 246 247 SPIREG_WRITE(sc, SPI_CCTL, cctl); 248 249 return 0; 250 } 251 252 static int 253 sun6ispi_transfer(void *cookie, struct spi_transfer *st) 254 { 255 struct sun6ispi_softc * const sc = cookie; 256 int s; 257 258 s = splbio(); 259 spi_transq_enqueue(&sc->sc_q, st); 260 if (sc->sc_running == false) { 261 sun6ispi_start(sc); 262 } 263 splx(s); 264 return 0; 265 } 266 267 static void 268 sun6ispi_start(struct sun6ispi_softc * const sc) 269 { 270 struct spi_transfer *st; 271 uint32_t isr, tcr; 272 struct spi_chunk *chunk; 273 size_t burstcount; 274 275 while ((st = spi_transq_first(&sc->sc_q)) != NULL) { 276 277 spi_transq_dequeue(&sc->sc_q); 278 279 KASSERT(sc->sc_transfer == NULL); 280 sc->sc_transfer = st; 281 sc->sc_rchunk = sc->sc_wchunk = st->st_chunks; 282 sc->sc_running = true; 283 284 isr = SPIREG_READ(sc, SPI_INT_STA); 285 SPIREG_WRITE(sc, SPI_INT_STA, isr); 286 287 burstcount = 0; 288 for (chunk = st->st_chunks; chunk; chunk = chunk->chunk_next) { 289 burstcount += chunk->chunk_count; 290 } 291 KASSERT(burstcount <= SPI_BC_MBC); 292 SPIREG_WRITE(sc, SPI_BC, __SHIFTIN(burstcount, SPI_BC_MBC)); 293 SPIREG_WRITE(sc, SPI_TC, __SHIFTIN(burstcount, SPI_TC_MWTC)); 294 SPIREG_WRITE(sc, SPI_BCC, __SHIFTIN(burstcount, SPI_BCC_STC)); 295 296 KASSERT(st->st_slave <= 3); 297 tcr = sc->sc_TCR | __SHIFTIN(st->st_slave, SPI_TCR_SS_SEL); 298 299 sun6ispi_send(sc); 300 301 const uint32_t ier = SPI_IER_DEFAULT | SPI_IER_RF_RDY_INT_EN | SPI_IER_TX_ERQ_INT_EN; 302 SPIREG_WRITE(sc, SPI_IER, ier); 303 304 SPIREG_WRITE(sc, SPI_TCR, tcr|SPI_TCR_XCH); 305 306 if (!cold) 307 return; 308 309 for (;;) { 310 sun6ispi_intr(sc); 311 if (ISSET(st->st_flags, SPI_F_DONE)) 312 break; 313 } 314 } 315 316 sc->sc_running = false; 317 } 318 319 static void 320 sun6ispi_send(struct sun6ispi_softc * const sc) 321 { 322 uint8_t fd; 323 uint32_t fsr; 324 struct spi_chunk *chunk; 325 326 while ((chunk = sc->sc_wchunk) != NULL) { 327 while (chunk->chunk_wresid) { 328 fsr = SPIREG_READ(sc, SPI_FSR); 329 if (__SHIFTOUT(fsr, SPI_FSR_TF_CNT) >= 64) { 330 return; 331 } 332 if (chunk->chunk_wptr) { 333 fd = *chunk->chunk_wptr++; 334 } else { 335 fd = '\0'; 336 } 337 bus_space_write_1(sc->sc_iot, sc->sc_ioh, SPI_TXD, fd); 338 chunk->chunk_wresid--; 339 } 340 sc->sc_wchunk = sc->sc_wchunk->chunk_next; 341 } 342 } 343 344 static void 345 sun6ispi_recv(struct sun6ispi_softc * const sc) 346 { 347 uint8_t fd; 348 uint32_t fsr; 349 struct spi_chunk *chunk; 350 351 while ((chunk = sc->sc_rchunk) != NULL) { 352 while (chunk->chunk_rresid) { 353 fsr = SPIREG_READ(sc, SPI_FSR); 354 if (__SHIFTOUT(fsr, SPI_FSR_RF_CNT) == 0) { 355 return; 356 } 357 fd = bus_space_read_1(sc->sc_iot, sc->sc_ioh, SPI_RXD); 358 if (chunk->chunk_rptr) { 359 *chunk->chunk_rptr++ = fd; 360 } 361 chunk->chunk_rresid--; 362 } 363 sc->sc_rchunk = sc->sc_rchunk->chunk_next; 364 } 365 } 366 367 static int 368 sun6ispi_intr(void *cookie) 369 { 370 struct sun6ispi_softc * const sc = cookie; 371 struct spi_transfer *st; 372 uint32_t isr; 373 374 isr = SPIREG_READ(sc, SPI_INT_STA); 375 SPIREG_WRITE(sc, SPI_INT_STA, isr); 376 377 if (ISSET(isr, SPI_ISR_RX_RDY)) { 378 sun6ispi_recv(sc); 379 sun6ispi_send(sc); 380 } 381 382 if (ISSET(isr, SPI_ISR_TC)) { 383 SPIREG_WRITE(sc, SPI_IER, SPI_IER_DEFAULT); 384 385 sc->sc_rchunk = sc->sc_wchunk = NULL; 386 st = sc->sc_transfer; 387 sc->sc_transfer = NULL; 388 KASSERT(st != NULL); 389 spi_done(st, 0); 390 sc->sc_running = false; 391 } 392 393 return isr; 394 } 395