/* $OpenBSD: xlights.c,v 1.11 2022/03/13 12:33:01 mpi Exp $ */ /* * Copyright (c) 2007 Gordon Willem Klok * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include struct xlights_softc { struct device sc_dev; int sc_node; int sc_intr; uint32_t sc_freq; int sc_dmasts; u_char *sc_reg; dbdma_regmap_t *sc_dma; bus_dma_tag_t sc_dmat; bus_dmamap_t sc_bufmap; bus_dma_segment_t sc_bufseg[1]; dbdma_t sc_dbdma; dbdma_command_t *sc_dmacmd; uint32_t *sc_buf; uint32_t *sc_bufpos; struct timeout sc_tmo; }; int xlights_match(struct device *, void *, void *); void xlights_attach(struct device *, struct device *, void *); int xlights_intr(void *); void xlights_startdma(struct xlights_softc *); void xlights_deferred(void *); void xlights_theosDOT(void *); void xlights_timeout(void *); const struct cfattach xlights_ca = { sizeof(struct xlights_softc), xlights_match, xlights_attach }; struct cfdriver xlights_cd = { NULL, "xlights", DV_DULL }; #define BL_BUFSZ PAGE_SIZE #define BL_DBDMA_CMDS 2 int xlights_match(struct device *parent, void *arg, void *aux) { struct confargs *ca = aux; int soundbus, soundchip, error; char compat[32]; if (strcmp(ca->ca_name, "i2s") != 0) return 0; if ((soundbus = OF_child(ca->ca_node)) == 0) return 0; if ((soundchip = OF_child(soundbus)) == 0) return 0; error = OF_getprop(soundchip, "virtual", compat, sizeof(compat)); if (error == -1) { error = OF_getprop(soundchip, "name", compat, sizeof(compat)); if (error == -1 || (strcmp(compat, "lightshow")) != 0) return 0; } /* we require at least 4 registers */ if (ca->ca_nreg / sizeof(int) < 4) return 0; /* we require at least 3 interrupts */ if (ca->ca_nintr / sizeof(int) < 6) return 0; return 1; } void xlights_attach(struct device *parent, struct device *self, void *aux) { struct xlights_softc *sc = (struct xlights_softc *)self; struct confargs *ca = aux; int nseg, error, intr[6]; u_int32_t reg[4]; int type; sc->sc_node = OF_child(ca->ca_node); OF_getprop(sc->sc_node, "reg", reg, sizeof(reg)); ca->ca_reg[0] += ca->ca_baseaddr; ca->ca_reg[2] += ca->ca_baseaddr; if ((sc->sc_reg = mapiodev(ca->ca_reg[0], ca->ca_reg[1])) == NULL) { printf(": cannot map registers\n"); return; } sc->sc_dmat = ca->ca_dmat; if ((sc->sc_dma = mapiodev(ca->ca_reg[2], ca->ca_reg[3])) == NULL) { printf(": cannot map DMA registers\n"); goto nodma; } if ((sc->sc_dbdma = dbdma_alloc(sc->sc_dmat, BL_DBDMA_CMDS)) == NULL) { printf(": cannot alloc DMA descriptors\n"); goto nodbdma; } sc->sc_dmacmd = sc->sc_dbdma->d_addr; if ((error = bus_dmamem_alloc(sc->sc_dmat, BL_BUFSZ, 0, 0, sc->sc_bufseg, 1, &nseg, BUS_DMA_NOWAIT))) { printf(": cannot allocate DMA mem (%d)\n", error); goto nodmamem; } if ((error = bus_dmamem_map(sc->sc_dmat, sc->sc_bufseg, nseg, BL_BUFSZ, (caddr_t *)&sc->sc_buf, BUS_DMA_NOWAIT))) { printf(": cannot map DMA mem (%d)\n", error); goto nodmamap; } sc->sc_bufpos = sc->sc_buf; if ((error = bus_dmamap_create(sc->sc_dmat, BL_BUFSZ, 1, BL_BUFSZ, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_bufmap))) { printf(": cannot create DMA map (%d)\n", error); goto nodmacreate; } if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_bufmap, sc->sc_buf, BL_BUFSZ, NULL, BUS_DMA_NOWAIT))) { printf(": cannot load DMA map (%d)\n", error); goto nodmaload; } /* XXX: Should probably extract this from the clock data * property of the soundchip node */ sc->sc_freq = 16384; OF_getprop(sc->sc_node, "interrupts", intr, sizeof(intr)); /* output interrupt */ sc->sc_intr = intr[2]; type = intr[3] ? IST_LEVEL : IST_EDGE; printf(": irq %d\n", sc->sc_intr); macobio_enable(I2SClockOffset, I2S0EN); out32rb(sc->sc_reg + I2S_INT, I2S_INT_CLKSTOPPEND); macobio_disable(I2SClockOffset, I2S0CLKEN); for (error = 0; error < 1000; error++) { if (in32rb(sc->sc_reg + I2S_INT) & I2S_INT_CLKSTOPPEND) { error = 0; break; } delay(1); } if (error) { printf("%s: i2s timeout\n", sc->sc_dev.dv_xname); goto nodmaload; } mac_intr_establish(parent, sc->sc_intr, intr[3] ? IST_LEVEL : type, IPL_TTY, xlights_intr, sc, sc->sc_dev.dv_xname); out32rb(sc->sc_reg + I2S_FORMAT, CLKSRC_VS); macobio_enable(I2SClockOffset, I2S0CLKEN); kthread_create_deferred(xlights_deferred, sc); timeout_set(&sc->sc_tmo, xlights_timeout, sc); return; nodmaload: bus_dmamap_destroy(sc->sc_dmat, sc->sc_bufmap); nodmacreate: bus_dmamem_unmap(sc->sc_dmat, (caddr_t)sc->sc_buf, BL_BUFSZ); nodmamap: bus_dmamem_free(sc->sc_dmat, sc->sc_bufseg, nseg); nodmamem: dbdma_free(sc->sc_dbdma); nodbdma: unmapiodev((void *)sc->sc_dma, ca->ca_reg[3]); nodma: unmapiodev(sc->sc_reg, ca->ca_reg[1]); } void xlights_deferred(void *v) { struct xlights_softc *sc = (struct xlights_softc *)v; kthread_create(xlights_theosDOT, v, NULL, sc->sc_dev.dv_xname); } /* * xserv has two rows of leds laid out as follows * 25 26 27 28 29 30 31 00 * 17 18 19 20 21 22 23 24 */ char ledfollow_0[16] = { 25, 26, 27, 28, 29, 30, 31, 00, 24, 23, 22, 21, 20, 19, 18, 17 }; char ledfollow_1[16] = { 17, 25, 26, 27, 28, 29, 30, 31, 00, 24, 23, 22, 21, 20, 19, 18 }; char ledfollow_2[16] = { 18, 17, 25, 26, 27, 28, 29, 30, 31, 00, 24, 23, 22, 21, 20, 19 }; void xlights_theosDOT(void *v) { struct xlights_softc *sc = (struct xlights_softc *)v; uint32_t *p; int k, nsamp; int ledpos, ledpos_high, ledpos_med, ledpos_dim; uint32_t val; while (1) { /* * ldavg 0 - .5 sec -> (8192 / 16) * ldavg 1 - 1 sec -> (16384 / 16) * ldavg 2 - 1.5 sec -> (24576 / 16) */ nsamp = sc->sc_freq + sc->sc_freq / FSCALE * averunnable.ldavg[0]; nsamp /= 16; /* scale, per led */ nsamp /= 4; /* scale, why?, sizeof(uint32_t)? */ for (ledpos = 0; ledpos < 16; ledpos++) { ledpos_high = ledfollow_0[ledpos]; ledpos_med = ledfollow_1[ledpos]; ledpos_dim = ledfollow_2[ledpos]; p = sc->sc_bufpos; for (k = 0; k < nsamp;) { if (p - sc->sc_buf < BL_BUFSZ / sizeof(uint32_t)) { val = (1 << ledpos_high); if ((k % 4) == 0) val |= (1 << ledpos_med); if ((k % 16) == 0) val |= (1 << ledpos_dim); *p = val; p++; k++; } else { xlights_startdma(sc); while (sc->sc_dmasts) tsleep_nsec(sc->sc_buf, PWAIT, "blinken", INFSLP); p = sc->sc_buf; } } sc->sc_bufpos = p; } } } void xlights_startdma(struct xlights_softc *sc) { dbdma_command_t *cmdp = sc->sc_dmacmd; sc->sc_dmasts = 1; timeout_add_msec(&sc->sc_tmo, 2500); DBDMA_BUILD(cmdp, DBDMA_CMD_OUT_LAST, 0, sc->sc_bufmap->dm_segs[0].ds_len, sc->sc_bufmap->dm_segs[0].ds_addr, DBDMA_INT_ALWAYS, DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER); cmdp++; DBDMA_BUILD(cmdp, DBDMA_CMD_STOP, 0, 0, 0, DBDMA_INT_NEVER, DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER); dbdma_start(sc->sc_dma, sc->sc_dbdma); } void xlights_timeout(void *v) { struct xlights_softc *sc = (struct xlights_softc *)v; dbdma_reset(sc->sc_dma); timeout_del(&sc->sc_tmo); sc->sc_dmasts = 0; wakeup(sc->sc_buf); } int xlights_intr(void *v) { struct xlights_softc *sc = (struct xlights_softc *)v; int status; dbdma_command_t *cmd; cmd = sc->sc_dmacmd; status = dbdma_ld16(&cmd->d_status); if (sc->sc_dmasts) { sc->sc_dmasts = 0; timeout_del(&sc->sc_tmo); wakeup(sc->sc_buf); return (1); } return (0); }