1 /* $NetBSD: auspi.c,v 1.10 2021/04/24 23:36:42 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2006 Urbana-Champaign Independent Media Center. 5 * Copyright (c) 2006 Garrett D'Amore. 6 * All rights reserved. 7 * 8 * Portions of this code were written by Garrett D'Amore for the 9 * Champaign-Urbana Community Wireless Network Project. 10 * 11 * Redistribution and use in source and binary forms, with or 12 * without modification, are permitted provided that the following 13 * conditions are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above 17 * copyright notice, this list of conditions and the following 18 * disclaimer in the documentation and/or other materials provided 19 * with the distribution. 20 * 3. All advertising materials mentioning features or use of this 21 * software must display the following acknowledgements: 22 * This product includes software developed by the Urbana-Champaign 23 * Independent Media Center. 24 * This product includes software developed by Garrett D'Amore. 25 * 4. Urbana-Champaign Independent Media Center's name and Garrett 26 * D'Amore's name may not be used to endorse or promote products 27 * derived from this software without specific prior written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT 30 * MEDIA CENTER AND GARRETT D'AMORE ``AS IS'' AND ANY EXPRESS OR 31 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 32 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE URBANA-CHAMPAIGN INDEPENDENT 34 * MEDIA CENTER OR GARRETT D'AMORE BE LIABLE FOR ANY DIRECT, INDIRECT, 35 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 44 #include <sys/cdefs.h> 45 __KERNEL_RCSID(0, "$NetBSD: auspi.c,v 1.10 2021/04/24 23:36:42 thorpej Exp $"); 46 47 #include "locators.h" 48 49 #include <sys/param.h> 50 #include <sys/bus.h> 51 #include <sys/cpu.h> 52 #include <sys/device.h> 53 #include <sys/errno.h> 54 #include <sys/kernel.h> 55 #include <sys/proc.h> 56 #include <sys/systm.h> 57 58 #include <mips/alchemy/include/aubusvar.h> 59 #include <mips/alchemy/include/auvar.h> 60 61 #include <mips/alchemy/dev/aupscreg.h> 62 #include <mips/alchemy/dev/aupscvar.h> 63 #include <mips/alchemy/dev/auspireg.h> 64 #include <mips/alchemy/dev/auspivar.h> 65 66 #include <dev/spi/spivar.h> 67 68 struct auspi_softc { 69 device_t sc_dev; 70 struct aupsc_controller sc_psc; /* parent controller ops */ 71 struct spi_controller sc_spi; /* SPI implementation ops */ 72 struct auspi_machdep sc_md; /* board-specific support */ 73 struct auspi_job *sc_job; /* current job */ 74 struct spi_chunk *sc_wchunk; 75 struct spi_chunk *sc_rchunk; 76 void *sc_ih; /* interrupt handler */ 77 78 struct spi_transfer *sc_transfer; 79 bool sc_running; /* is it processing stuff? */ 80 81 SIMPLEQ_HEAD(,spi_transfer) sc_q; 82 }; 83 84 #define auspi_select(sc, slave) \ 85 (sc)->sc_md.am_select((sc)->sc_md.am_cookie, (slave)) 86 87 #define STATIC 88 89 STATIC int auspi_match(device_t, struct cfdata *, void *); 90 STATIC void auspi_attach(device_t, device_t, void *); 91 STATIC int auspi_intr(void *); 92 93 CFATTACH_DECL_NEW(auspi, sizeof(struct auspi_softc), 94 auspi_match, auspi_attach, NULL, NULL); 95 96 /* SPI service routines */ 97 STATIC int auspi_configure(void *, int, int, int); 98 STATIC int auspi_transfer(void *, struct spi_transfer *); 99 100 /* internal stuff */ 101 STATIC void auspi_done(struct auspi_softc *, int); 102 STATIC void auspi_send(struct auspi_softc *); 103 STATIC void auspi_recv(struct auspi_softc *); 104 STATIC void auspi_sched(struct auspi_softc *); 105 106 #define GETREG(sc, x) \ 107 bus_space_read_4(sc->sc_psc.psc_bust, sc->sc_psc.psc_bush, x) 108 #define PUTREG(sc, x, v) \ 109 bus_space_write_4(sc->sc_psc.psc_bust, sc->sc_psc.psc_bush, x, v) 110 111 int 112 auspi_match(device_t parent, struct cfdata *cf, void *aux) 113 { 114 struct aupsc_attach_args *aa = aux; 115 116 if (strcmp(aa->aupsc_name, cf->cf_name) != 0) 117 return 0; 118 119 return 1; 120 } 121 122 void 123 auspi_attach(device_t parent, device_t self, void *aux) 124 { 125 struct auspi_softc *sc = device_private(self); 126 struct aupsc_attach_args *aa = aux; 127 struct spibus_attach_args sba; 128 const struct auspi_machdep *md; 129 130 sc->sc_dev = self; 131 132 if ((md = auspi_machdep(aa->aupsc_addr)) != NULL) { 133 sc->sc_md = *md; 134 } 135 136 aprint_normal(": Alchemy PSC SPI protocol\n"); 137 138 sc->sc_psc = aa->aupsc_ctrl; 139 140 /* 141 * Initialize SPI controller 142 */ 143 sc->sc_spi.sct_cookie = sc; 144 sc->sc_spi.sct_configure = auspi_configure; 145 sc->sc_spi.sct_transfer = auspi_transfer; 146 147 /* fix this! */ 148 sc->sc_spi.sct_nslaves = sc->sc_md.am_nslaves; 149 150 memset(&sba, 0, sizeof(sba)); 151 sba.sba_controller = &sc->sc_spi; 152 153 /* enable SPI mode */ 154 sc->sc_psc.psc_enable(sc, AUPSC_SEL_SPI); 155 156 /* initialize the queue */ 157 SIMPLEQ_INIT(&sc->sc_q); 158 159 /* make sure interrupts disabled at the SPI */ 160 PUTREG(sc, AUPSC_SPIMSK, SPIMSK_ALL); 161 162 /* enable device interrupts */ 163 sc->sc_ih = au_intr_establish(aa->aupsc_irq, 0, IPL_BIO, IST_LEVEL, 164 auspi_intr, sc); 165 166 config_found(self, &sba, spibus_print, CFARG_EOL); 167 } 168 169 int 170 auspi_configure(void *arg, int slave, int mode, int speed) 171 { 172 struct auspi_softc *sc = arg; 173 int brg, i; 174 uint32_t reg; 175 176 /* setup interrupt registers */ 177 PUTREG(sc, AUPSC_SPIMSK, SPIMSK_NORM); 178 179 reg = GETREG(sc, AUPSC_SPICFG); 180 181 reg &= ~(SPICFG_BRG_MASK); /* clear BRG */ 182 reg &= ~(SPICFG_DIV_MASK); /* use pscn_mainclock/2 */ 183 reg &= ~(SPICFG_PSE); /* disable port swap */ 184 reg &= ~(SPICFG_BI); /* clear bit clock invert */ 185 reg &= ~(SPICFG_CDE); /* clear clock phase delay */ 186 reg &= ~(SPICFG_CGE); /* clear clock gate enable */ 187 //reg |= SPICFG_MO; /* master-only mode */ 188 reg |= SPICFG_DE; /* device enable */ 189 reg |= SPICFG_DD; /* disable DMA */ 190 reg |= SPICFG_RT_1; /* 1 byte rx fifo threshold */ 191 reg |= SPICFG_TT_1; /* 1 byte tx fifo threshold */ 192 reg |= ((8-1) << SPICFG_LEN_SHIFT);/* always work in 8-bit chunks */ 193 194 /* 195 * We assume a base clock of 48MHz has been established by the 196 * platform code. The clock divider reduces this to 24MHz. 197 * Next we have to figure out the BRG 198 */ 199 #define BASECLK 24000000 200 for (brg = 0; brg < 64; brg++) { 201 if (speed >= (BASECLK / ((brg + 1) * 2))) { 202 break; 203 } 204 } 205 206 /* 207 * Does the device want to go even slower? Our minimum speed without 208 * changing other assumptions, and complicating the code even further, 209 * is 24MHz/128, or 187.5kHz. That should be slow enough for any 210 * device we're likely to encounter. 211 */ 212 if (speed < (BASECLK / ((brg + 1) * 2))) { 213 return EINVAL; 214 } 215 reg &= ~SPICFG_BRG_MASK; 216 reg |= (brg << SPICFG_BRG_SHIFT); 217 218 /* 219 * I'm not entirely confident that these values are correct. 220 * But at least mode 0 appears to work properly with the 221 * devices I have tested. The documentation seems to suggest 222 * that I have the meaning of the clock delay bit inverted. 223 */ 224 switch (mode) { 225 case SPI_MODE_0: 226 reg |= 0; /* CPHA = 0, CPOL = 0 */ 227 break; 228 case SPI_MODE_1: 229 reg |= SPICFG_CDE; /* CPHA = 1, CPOL = 0 */ 230 break; 231 case SPI_MODE_2: 232 reg |= SPICFG_BI; /* CPHA = 0, CPOL = 1 */ 233 break; 234 case SPI_MODE_3: 235 reg |= SPICFG_CDE | SPICFG_BI; /* CPHA = 1, CPOL = 1 */ 236 break; 237 default: 238 return EINVAL; 239 } 240 241 PUTREG(sc, AUPSC_SPICFG, reg); 242 243 for (i = 1000000; i; i -= 10) { 244 if (GETREG(sc, AUPSC_SPISTAT) & SPISTAT_DR) { 245 return 0; 246 } 247 } 248 249 return ETIMEDOUT; 250 } 251 252 void 253 auspi_send(struct auspi_softc *sc) 254 { 255 uint32_t data; 256 struct spi_chunk *chunk; 257 258 /* fill the fifo */ 259 while ((chunk = sc->sc_wchunk) != NULL) { 260 261 while (chunk->chunk_wresid) { 262 263 /* transmit fifo full? */ 264 if (GETREG(sc, AUPSC_SPISTAT) & SPISTAT_TF) { 265 return; 266 } 267 268 if (chunk->chunk_wptr) { 269 data = *chunk->chunk_wptr++; 270 } else { 271 data = 0; 272 } 273 chunk->chunk_wresid--; 274 275 /* if the last outbound character, mark it */ 276 if ((chunk->chunk_wresid == 0) && 277 (chunk->chunk_next == NULL)) { 278 data |= SPITXRX_LC; 279 } 280 PUTREG(sc, AUPSC_SPITXRX, data); 281 } 282 283 /* advance to next transfer */ 284 sc->sc_wchunk = sc->sc_wchunk->chunk_next; 285 } 286 } 287 288 void 289 auspi_recv(struct auspi_softc *sc) 290 { 291 uint32_t data; 292 struct spi_chunk *chunk; 293 294 while ((chunk = sc->sc_rchunk) != NULL) { 295 while (chunk->chunk_rresid) { 296 297 /* rx fifo empty? */ 298 if ((GETREG(sc, AUPSC_SPISTAT) & SPISTAT_RE) != 0) { 299 return; 300 } 301 302 /* collect rx data */ 303 data = GETREG(sc, AUPSC_SPITXRX); 304 if (chunk->chunk_rptr) { 305 *chunk->chunk_rptr++ = data & 0xff; 306 } 307 308 chunk->chunk_rresid--; 309 } 310 311 /* advance next to next transfer */ 312 sc->sc_rchunk = sc->sc_rchunk->chunk_next; 313 } 314 } 315 316 void 317 auspi_sched(struct auspi_softc *sc) 318 { 319 struct spi_transfer *st; 320 int err; 321 322 while ((st = spi_transq_first(&sc->sc_q)) != NULL) { 323 324 /* remove the item */ 325 spi_transq_dequeue(&sc->sc_q); 326 327 /* note that we are working on it */ 328 sc->sc_transfer = st; 329 330 if ((err = auspi_select(sc, st->st_slave)) != 0) { 331 spi_done(st, err); 332 continue; 333 } 334 335 /* clear the fifos */ 336 PUTREG(sc, AUPSC_SPIPCR, SPIPCR_RC | SPIPCR_TC); 337 /* setup chunks */ 338 sc->sc_rchunk = sc->sc_wchunk = st->st_chunks; 339 auspi_send(sc); 340 /* now kick the master start to get the chip running */ 341 PUTREG(sc, AUPSC_SPIPCR, SPIPCR_MS); 342 sc->sc_running = true; 343 return; 344 } 345 auspi_select(sc, -1); 346 sc->sc_running = false; 347 } 348 349 void 350 auspi_done(struct auspi_softc *sc, int err) 351 { 352 struct spi_transfer *st; 353 354 /* called from interrupt handler */ 355 if ((st = sc->sc_transfer) != NULL) { 356 sc->sc_transfer = NULL; 357 spi_done(st, err); 358 } 359 /* make sure we clear these bits out */ 360 sc->sc_wchunk = sc->sc_rchunk = NULL; 361 auspi_sched(sc); 362 } 363 364 int 365 auspi_intr(void *arg) 366 { 367 struct auspi_softc *sc = arg; 368 uint32_t ev; 369 int err = 0; 370 371 372 if ((GETREG(sc, AUPSC_SPISTAT) & SPISTAT_DI) == 0) { 373 return 0; 374 } 375 376 ev = GETREG(sc, AUPSC_SPIEVNT); 377 378 if (ev & SPIMSK_MM) { 379 printf("%s: multiple masters detected!\n", 380 device_xname(sc->sc_dev)); 381 err = EIO; 382 } 383 if (ev & SPIMSK_RO) { 384 printf("%s: receive overflow\n", device_xname(sc->sc_dev)); 385 err = EIO; 386 } 387 if (ev & SPIMSK_TU) { 388 printf("%s: transmit underflow\n", device_xname(sc->sc_dev)); 389 err = EIO; 390 } 391 if (err) { 392 /* clear errors */ 393 PUTREG(sc, AUPSC_SPIEVNT, 394 ev & (SPIMSK_MM | SPIMSK_RO | SPIMSK_TU)); 395 /* clear the fifos */ 396 PUTREG(sc, AUPSC_SPIPCR, SPIPCR_RC | SPIPCR_TC); 397 auspi_done(sc, err); 398 399 } else { 400 401 /* do all data exchanges */ 402 auspi_send(sc); 403 auspi_recv(sc); 404 405 /* 406 * if the master done bit is set, make sure we do the 407 * right processing. 408 */ 409 if (ev & SPIMSK_MD) { 410 if ((sc->sc_wchunk != NULL) || 411 (sc->sc_rchunk != NULL)) { 412 printf("%s: partial transfer?\n", 413 device_xname(sc->sc_dev)); 414 err = EIO; 415 } 416 auspi_done(sc, err); 417 } 418 /* clear interrupts */ 419 PUTREG(sc, AUPSC_SPIEVNT, 420 ev & (SPIMSK_TR | SPIMSK_RR | SPIMSK_MD)); 421 } 422 423 return 1; 424 } 425 426 int 427 auspi_transfer(void *arg, struct spi_transfer *st) 428 { 429 struct auspi_softc *sc = arg; 430 int s; 431 432 /* make sure we select the right chip */ 433 s = splbio(); 434 spi_transq_enqueue(&sc->sc_q, st); 435 if (sc->sc_running == 0) { 436 auspi_sched(sc); 437 } 438 splx(s); 439 return 0; 440 } 441 442