/*- * Copyright 2006 John-Mark Gurney * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id$ */ #include /* defines used in kernel.h */ #include /* types used in module initialization */ #include /* uprintf */ #include #include /* cdevsw struct */ #include /* uio struct */ #include #include #include #include #include /* structs, prototypes for pci bus stuff */ #include #include #include #include #include #include #if __FreeBSD_version >= 500014 #include #else #include #endif #if 0 #include #include #endif #include #include #include #include #include #include #include #include #include /* For get_pci macros! */ #include #define BKTRAU_MAXBUFSIZE 65536 #define BKTRAU_MAXNSEGS (atop(BKTRAU_MAXBUFSIZE)) /* Resources */ struct resource_spec bktrau_res_spec[] = { #define BKTRAU_RES_REGIDX 0 { .type = SYS_RES_MEMORY, .rid = PCIR_BAR(0), .flags = RF_ACTIVE }, #define BKTRAU_RES_IRQIDX 1 { .type = SYS_RES_IRQ, .rid = 0, .flags = RF_SHAREABLE | RF_ACTIVE }, { .type = -1 }, }; #define BKTRAU_RES_SPEC_CNT (sizeof bktrau_res_spec / sizeof *bktrau_res_spec) #include #include /* Prototypes */ static void bktrau_stop(struct bktrau_softc *bktrau); static size_t bktrau_progdwords(u_int nbufs, u_int afp_len, u_int alp_len); static void bktrau_genericaudio(u_int32_t *dmaprog, bus_addr_t base, u_int nbufs, struct bktrau_segment *bufs, u_int afp_len, u_int alp_len, u_int32_t *offs); static int bktrau_mapbufs(struct bktrau_softc *bktrau, u_int cnt, caddr_t *bufptr, size_t newbufsize, struct thread *td); static void bktrau_cleanuprisc(struct bktrau_softc *bktrau); static int bktrau_makerisc(struct bktrau_softc *bktrau, size_t risclen); static int bktrau_probe(device_t dev); static int bktrau_attach(device_t dev); static int bktrau_shutdown(device_t dev); static int bktrau_detach(device_t dev); static int bktrau_suspend(device_t dev); static int bktrau_resume(device_t dev); static void bktrau_intr(void *arg); static d_open_t bktrau_os_open; static d_close_t bktrau_os_close; static d_ioctl_t bktrau_os_ioctl; static d_poll_t bktrau_os_poll; static devclass_t bktrau_devclass; /* Data Structures & Defs */ static struct cdevsw bktrau_cdevsw = { .d_version = D_VERSION, .d_flags = D_TRACKCLOSE, .d_open = bktrau_os_open, .d_close = bktrau_os_close, .d_ioctl = bktrau_os_ioctl, .d_poll = bktrau_os_poll, .d_name = "bktrau", }; static device_method_t bktrau_methods[] = { /* device interface */ DEVMETHOD(device_probe, bktrau_probe), DEVMETHOD(device_attach, bktrau_attach), DEVMETHOD(device_detach, bktrau_detach), DEVMETHOD(device_shutdown, bktrau_shutdown), DEVMETHOD(device_suspend, bktrau_suspend), DEVMETHOD(device_resume, bktrau_resume), { 0, 0 } }; static driver_t bktrau_driver = { "bktrau", bktrau_methods, sizeof(struct bktrau_softc), }; /* Module Definitions */ DRIVER_MODULE(bktrau, pci, bktrau_driver, bktrau_devclass, 0, 0); MODULE_VERSION(bktrau, 1); MODULE_DEPEND(bktrau, pci, 1, 1, 1); MALLOC_DEFINE(M_BKTRAU, "bktrau", "Fusion878A Audio device"); /* Functions */ /* Module/Device Functions */ static int bktrau_probe(device_t dev) { switch (pci_get_vendor(dev)) { case PCI_VENDOR_BROOKTREE: /* now Conexant */ switch (pci_get_device(dev)) { case PCI_PRODUCT_BROOKTREE_BT848_AU: device_set_desc(dev, "Fusion878A Audio"); return 0; } } return ENXIO; } /* * Attach does the following: * sets up PCI bus * maps ASRS * sets up interupt */ static int bktrau_attach(device_t dev) { int error = 0; BKTRAU_SOFTC(dev); bktrau->bktrau_self = dev; mtx_init(&bktrau->bktrau_lock, device_get_name(dev), NULL, MTX_DEF); bktrau->bktrau_res[0] = bktrau->bktrau_res[1] = NULL; bktrau->bktrau_dev = NULL; bktrau->bktrau_status = BKTRAU_S_UNSET; bktrau->bktrau_nbufs = bktrau->bktrau_nbufsalloced = 0; bktrau->bktrau_maps = NULL; bktrau->bktrau_addrs = NULL; bktrau->bktrau_ubufaddrs = NULL; bktrau->bktrau_bufoffs = NULL; bktrau->bktrau_scerr = bktrau->bktrau_ocerr = bktrau->bktrau_pabort = bktrau->bktrau_riperr = bktrau->bktrau_pperr = bktrau->bktrau_fdsr = bktrau->bktrau_ftrgt = bktrau->bktrau_fbus = bktrau->bktrau_oflow = bktrau->bktrau_droppedbuf = 0; /* * Enable bus mastering and Memory Mapped device */ pci_enable_busmaster(dev); /* * Map ASRS and allocate IRQ */ if ((error = bus_alloc_resources(dev, bktrau_res_spec, bktrau->bktrau_res))) { device_printf(dev, "failed to allocate resources\n"); goto fail; } bktrau_stop(bktrau); error = bus_setup_intr(dev, bktrau->bktrau_irq, INTR_TYPE_TTY|INTR_MPSAFE, bktrau_intr, bktrau, &bktrau->bktrau_irqh); if (error) { device_printf(dev, "could not setup irq\n"); goto fail; } if ((error = bus_dma_tag_create(NULL, 4, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, BKTRAU_MAXBUFSIZE, BKTRAU_MAXNSEGS, BKTRAU_MAXBUFSIZE, 0, busdma_lock_mutex, &bktrau->bktrau_lock, &bktrau->bktrau_tag))) { device_printf(dev, "tag create\n"); goto fail; } bktrau->bktrau_dev = make_dev(&bktrau_cdevsw, device_get_unit(dev), 0, 0, 0640, device_get_nameunit(dev)); return 0; fail: bus_release_resources(dev, bktrau_res_spec, bktrau->bktrau_res); mtx_destroy(&bktrau->bktrau_lock); return error; } static int bktrau_shutdown(device_t dev) { BKTRAU_SOFTC(dev); bktrau_stop(bktrau); return 0; } /* * Detach does the following: * disable ints * tear down int * release irq and memory */ static int bktrau_detach(device_t dev) { int i, error; BKTRAU_SOFTC(dev); bktrau_stop(bktrau); /* remove devfs entry */ destroy_dev(bktrau->bktrau_dev); bktrau->bktrau_dev = NULL; for (i = 0; i < bktrau->bktrau_nbufsalloced; i++) { free(bktrau->bktrau_addrs[i].bs_segs, M_BKTRAU); bus_dmamap_destroy(bktrau->bktrau_tag, bktrau->bktrau_maps[i]); } bus_dma_tag_destroy(bktrau->bktrau_tag); free(bktrau->bktrau_maps, M_BKTRAU); free(bktrau->bktrau_addrs, M_BKTRAU); free(bktrau->bktrau_ubufaddrs, M_BKTRAU); free(bktrau->bktrau_bufoffs, M_BKTRAU); /* remove resources */ if ((error = bus_teardown_intr(dev, bktrau->bktrau_irq, bktrau->bktrau_irqh))) return error; bus_release_resources(dev, bktrau_res_spec, bktrau->bktrau_res); /* * disable bus mastering and Memory Mapped device */ pci_disable_busmaster(dev); mtx_destroy(&bktrau->bktrau_lock); return 0; } static int bktrau_suspend(device_t dev) { return EBUSY; } static int bktrau_resume(device_t dev) { return EBUSY; } static void bktrau_intr(void *arg) { struct bktrau_softc *bktrau = (struct bktrau_softc *)arg; u_int32_t val, pc; int i, newhead, didwakeup; if (bktrau->bktrau_status != BKTRAU_S_RUNNING) return; BKTRAU_LOCK(bktrau); val = BKTRAU_READ(bktrau, BKTRAU_INT_STAT); if (val & BKTRAU_IS_SCERR) bktrau->bktrau_scerr++; if (val & BKTRAU_IS_OCERR) bktrau->bktrau_ocerr++; if (val & BKTRAU_IS_PABORT) bktrau->bktrau_pabort++; if (val & BKTRAU_IS_RIPERR) bktrau->bktrau_riperr++; if (val & BKTRAU_IS_PPERR) bktrau->bktrau_pperr++; if (val & BKTRAU_IS_FDSR) bktrau->bktrau_fdsr++; if (val & BKTRAU_IS_FTRGT) bktrau->bktrau_ftrgt++; if (val & BKTRAU_IS_FBUS) bktrau->bktrau_fbus++; if (val & BKTRAU_IS_RISCI) { pc = BKTRAU_READ(bktrau, BKTRAU_RISC_COUNT); for (i = 0; i < bktrau->bktrau_nbufs; i++) { if (pc < bktrau->bktrau_riscaddr + bktrau->bktrau_bufoffs[i]) break; } newhead = i % bktrau->bktrau_nbufs; didwakeup = 0; /* sync the buffers */ while (bktrau->bktrau_head != newhead) { bus_dmamap_sync(bktrau->bktrau_tag, bktrau->bktrau_maps[bktrau->bktrau_head], BUS_DMASYNC_POSTWRITE); bktrau->bktrau_head = (bktrau->bktrau_head + 1) % bktrau->bktrau_nbufs; if (bktrau->bktrau_avail == bktrau->bktrau_head) { bktrau->bktrau_avail = (bktrau->bktrau_avail + 1) % bktrau->bktrau_nbufs; bktrau->bktrau_droppedbuf++; } if (!didwakeup) { selwakeup(&bktrau->bktrau_sel); didwakeup = 1; } } } if (val & BKTRAU_IS_OFLOW) bktrau->bktrau_oflow++; /* ack all ints */ BKTRAU_WRITE(bktrau, BKTRAU_INT_STAT, val); BKTRAU_UNLOCK(bktrau); } static int bktrau_os_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { int error; BKTRAU_DEV_SOFTC(dev); if (bktrau == NULL) return ENXIO; error = 0; device_busy(bktrau->bktrau_self); return error; } static int bktrau_os_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { int error; BKTRAU_DEV_SOFTC(dev); error = 0; if (bktrau == NULL) return ENXIO; BKTRAU_LOCK(bktrau); /* this shouldn't happen */ while (bktrau->bktrau_status == BKTRAU_S_SETTINGUP) msleep(bktrau, &bktrau->bktrau_lock, PRIBIO, "bkaucl", hz); bktrau_stop(bktrau); bktrau->bktrau_status = BKTRAU_S_SETTINGUP; BKTRAU_UNLOCK(bktrau); bktrau_cleanuprisc(bktrau); bktrau_mapbufs(bktrau, 0, NULL, 0, td); BKTRAU_LOCK(bktrau); bktrau->bktrau_status = BKTRAU_S_UNSET; BKTRAU_UNLOCK(bktrau); wakeup(bktrau); device_unbusy(bktrau->bktrau_self); return error; } static void bktrau_risc_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct bktrau_softc *bktrau; bktrau = (struct bktrau_softc *)arg; if (error) { bktrau->bktrau_mapdoing = -error; return; } if (nsegs != 1) panic("bktrau_risc_cb: There can be only one!"); bktrau->bktrau_riscaddr = segs->ds_addr; } static void bktrau_uio_cb(void *arg, bus_dma_segment_t *segs, int nsegs, bus_size_t mapsize, int error) { struct bktrau_softc *bktrau; int i; bktrau = (struct bktrau_softc *)arg; if (error) { bktrau->bktrau_mapdoing = -error; return; } bktrau->bktrau_addrs[bktrau->bktrau_mapdoing].bs_cnt = nsegs; for (i = 0; i < nsegs; i++) bktrau->bktrau_addrs[bktrau->bktrau_mapdoing].bs_segs[i] = segs[i]; } /* * Do all the magic of locking userspace pages, and creating and mapping the * pages. nbufs doubles as cnt of ubufaddrs we have locked. We destroy * state, so if we return an error, you cannot go back to previous state of * operation (return to BKTRAU_S_UNSET). The lock must not be held, as we * do blocking memory allocations, so change state to BKTRAU_S_SETTINGUP, * and unlock before calling. */ static int bktrau_mapbufs(struct bktrau_softc *bktrau, u_int cnt, caddr_t *bufptr, size_t newbufsize, struct thread *td) { struct iovec iov; struct uio u; void **ubuf; struct bktrau_segment *busaddrs; bus_dmamap_t *ptr; int i, error; error = 0; if (cnt != 0) BKTRAU_ASSERTNL(bktrau); if (cnt > bktrau->bktrau_nbufsalloced) { ptr = realloc(bktrau->bktrau_maps, sizeof *ptr * cnt, M_BKTRAU, M_WAITOK); busaddrs = realloc(bktrau->bktrau_addrs, sizeof *busaddrs * cnt, M_BKTRAU, M_WAITOK); for (i = bktrau->bktrau_nbufsalloced; i < cnt; i++) { busaddrs[i].bs_segs = malloc(sizeof *busaddrs[i].bs_segs * BKTRAU_MAXNSEGS, M_BKTRAU, M_WAITOK); if ((error = bus_dmamap_create(bktrau->bktrau_tag, 0, &ptr[i]))) break; } ubuf = realloc(bktrau->bktrau_ubufaddrs, sizeof *ubuf * cnt, M_BKTRAU, M_WAITOK); bktrau->bktrau_nbufsalloced = i; bktrau->bktrau_maps = ptr; bktrau->bktrau_addrs = busaddrs; bktrau->bktrau_ubufaddrs = ubuf; if (i != cnt) goto out; } for (i = 0; i < bktrau->bktrau_nbufs; i++) vsunlock(bktrau->bktrau_ubufaddrs[i], bktrau->bktrau_bufsize); bktrau->bktrau_nbufs = 0; if ((error = copyin(bufptr, bktrau->bktrau_ubufaddrs, sizeof *bktrau->bktrau_ubufaddrs * cnt))) goto out; bktrau->bktrau_bufsize = newbufsize; for (i = 0; i < cnt; i++) if ((error = vslock(bktrau->bktrau_ubufaddrs[i], bktrau->bktrau_bufsize))) break; if (i != cnt) { bktrau->bktrau_nbufs = i; goto out; } u.uio_iov = &iov; u.uio_iovcnt = 1; u.uio_segflg = UIO_USERSPACE; u.uio_rw = UIO_WRITE; u.uio_td = td; bktrau->bktrau_nbufs = cnt; for (i = 0; error == 0 && i < cnt; i++) { /* load the buffers */ iov.iov_base = bktrau->bktrau_ubufaddrs[i]; iov.iov_len = bktrau->bktrau_bufsize; u.uio_offset = 0; u.uio_resid = bktrau->bktrau_bufsize; bktrau->bktrau_mapdoing = i; error = bus_dmamap_load_uio(bktrau->bktrau_tag, bktrau->bktrau_maps[i], &u, bktrau_uio_cb, (void *)bktrau, BUS_DMA_WAITOK); if (bktrau->bktrau_mapdoing < 0) error = -bktrau->bktrau_mapdoing; else bus_dmamap_sync(bktrau->bktrau_tag, bktrau->bktrau_maps[bktrau->bktrau_head], BUS_DMASYNC_PREWRITE); } out: return error; } static void bktrau_stop(struct bktrau_softc *bktrau) { /* disable ints */ BKTRAU_WRITE(bktrau, BKTRAU_INT_MASK, 0); /* stop the FIFO and RISC engines */ BKTRAU_WRITE(bktrau, BKTRAU_GPIO_DMA_CTL, BKTRAU_READ(bktrau, BKTRAU_GPIO_DMA_CTL) & ~(BKTRAU_GDC_RISC_ENABLE|BKTRAU_GDC_FIFO_ENABLE)); } static int bktrau_os_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { BKTRAU_DEV_SOFTC(dev); struct bktrau_audio *ba; struct bktrau_stats *bs; u_int *ptr; size_t risclen; u_int32_t val; int error; if (bktrau == NULL) return ENXIO; if (td == NULL) return EFAULT; error = 0; BKTRAU_LOCK(bktrau); if (bktrau->bktrau_status == BKTRAU_S_SETTINGUP) { error = EBUSY; goto out; } switch (cmd) { case BKTRAU_SETAUDIO: if (bktrau->bktrau_status == BKTRAU_S_RUNNING) { error = EBUSY; break; } ba = (struct bktrau_audio *)data; if (ba->ba_gain > 15 || ba->ba_sel > 3 || ba->ba_lrd > 31 || ba->ba_sdr > 15 || ba->ba_iom > 1 || ba->ba_pktp > 2 || ba->ba_lines == 0 || ba->ba_bytes == 0 || ba->ba_lines > 255 || ba->ba_bytes > 4095 || ba->ba_bytes > BKTRAU_MAXBUFSIZE / ba->ba_lines || ba->ba_nbufs == 0 || ba->ba_nbufs > 256) { error = EINVAL; break; } bktrau_stop(bktrau); if ((ptr = realloc(bktrau->bktrau_bufoffs, sizeof *ptr * ba->ba_nbufs, M_BKTRAU, M_NOWAIT)) == NULL) { error = ENOMEM; break; } bktrau->bktrau_bufoffs = ptr; risclen = bktrau_progdwords(ba->ba_nbufs, ba->ba_lines, ba->ba_bytes) * 4; bktrau->bktrau_status = BKTRAU_S_SETTINGUP; BKTRAU_UNLOCK(bktrau); if ((error = bktrau_makerisc(bktrau, risclen)) == 0) { /* try and map the buffers */ error = bktrau_mapbufs(bktrau, ba->ba_nbufs, ba->ba_bufs, ba->ba_lines * ba->ba_bytes, td); } BKTRAU_LOCK(bktrau); if (error) { bktrau->bktrau_status = BKTRAU_S_UNSET; bktrau_cleanuprisc(bktrau); break; } bktrau_genericaudio(bktrau->bktrau_risc, bktrau->bktrau_riscaddr, ba->ba_nbufs, bktrau->bktrau_addrs, ba->ba_lines, ba->ba_bytes, bktrau->bktrau_bufoffs); bus_dmamap_sync(bktrau->bktrau_risctag, bktrau->bktrau_riscmap, BUS_DMASYNC_PREREAD); bktrau->bktrau_status = BKTRAU_S_STOPPED; wakeup(bktrau); /* setup parameters */ val = (ba->ba_gain << BKTRAU_GDC_A_GAIN_SHIFT) | (ba->ba_g2x ? BKTRAU_GDC_A_G2X : 0) | (ba->ba_pwrdn ? BKTRAU_GDC_A_PWRDN : 0) | (ba->ba_sel << BKTRAU_GDC_A_SEL_SHIFT) | (ba->ba_sce ? BKTRAU_GDC_DA_SCE : 0) | (ba->ba_lri ? BKTRAU_GDC_DA_LRI : 0) | (ba->ba_mlb ? BKTRAU_GDC_DA_MLB : 0) | (ba->ba_lrd << BKTRAU_GDC_DA_LRD_SHIFT) | (ba->ba_dpm ? BKTRAU_GDC_DA_DPM : 0) | (ba->ba_sbr ? BKTRAU_GDC_DA_SBR : 0) | (ba->ba_es2 ? BKTRAU_GDC_DA_ES2 : 0) | (ba->ba_lmt ? BKTRAU_GDC_DA_LMT : 0) | (ba->ba_sdr << BKTRAU_GDC_DA_SDR_SHIFT) | (ba->ba_iom << BKTRAU_GDC_DA_IOM_SHIFT) | (ba->ba_app ? BKTRAU_GDC_DA_APP : 0) | (ba->ba_pktp << BKTRAU_GDC_PKTP_SHIFT); BKTRAU_WRITE(bktrau, BKTRAU_GPIO_DMA_CTL, val); val = (ba->ba_lines << BKTRAU_APL_LINES_SHIFT) | (ba->ba_bytes << BKTRAU_APL_BYTES_SHIFT); BKTRAU_WRITE(bktrau, BKTRAU_AUDIO_PKT_LEN, val); BKTRAU_WRITE(bktrau, BKTRAU_RISC_STRT_ADD, bktrau->bktrau_riscaddr); break; case BKTRAU_GETAUDIO: error = ENOENT; break; case BKTRAU_START: if (bktrau->bktrau_status != BKTRAU_S_STOPPED) { error = EINVAL; break; } bktrau->bktrau_status = BKTRAU_S_RUNNING; bktrau->bktrau_avail = 0; bktrau->bktrau_head = 0; /* clear previous ints */ BKTRAU_WRITE(bktrau, BKTRAU_INT_STAT, BKTRAU_READ(bktrau, BKTRAU_INT_STAT)); /* enable all ints */ BKTRAU_WRITE(bktrau, BKTRAU_INT_MASK, BKTRAU_IS_SCERR | BKTRAU_IS_OCERR | BKTRAU_IS_PABORT | BKTRAU_IS_RIPERR | BKTRAU_IS_PPERR | BKTRAU_IS_FDSR | BKTRAU_IS_FTRGT | BKTRAU_IS_FBUS | BKTRAU_IS_RISCI); /*BKTRAU_IS_FBUS | BKTRAU_IS_RISCI | BKTRAU_IS_OFLOW);*/ /* kick it all off */ BKTRAU_WRITE(bktrau, BKTRAU_GPIO_DMA_CTL, BKTRAU_READ(bktrau, BKTRAU_GPIO_DMA_CTL) | BKTRAU_GDC_ACAP_EN | BKTRAU_GDC_RISC_ENABLE | BKTRAU_GDC_FIFO_ENABLE); break; case BKTRAU_STOP: if (bktrau->bktrau_status != BKTRAU_S_RUNNING) { error = EINVAL; break; } bktrau_stop(bktrau); bktrau->bktrau_status = BKTRAU_S_SETTINGUP; BKTRAU_UNLOCK(bktrau); bktrau_mapbufs(bktrau, 0, NULL, 0, td); BKTRAU_LOCK(bktrau); bktrau->bktrau_status = BKTRAU_S_UNSET; wakeup(bktrau); break; case BKTRAU_GETVBUF: if (bktrau->bktrau_avail == bktrau->bktrau_head) error = EAGAIN; else *(unsigned int *)data = bktrau->bktrau_head; break; case BKTRAU_SETCBUF: ptr = (unsigned int *)data; if (*ptr > bktrau->bktrau_nbufs) error = EINVAL; else bktrau->bktrau_avail = *ptr; break; case BKTRAU_GETSTATS: bs = (struct bktrau_stats *)data; bs->bs_scerr = bktrau->bktrau_scerr; bs->bs_ocerr = bktrau->bktrau_ocerr; bs->bs_pabort = bktrau->bktrau_pabort; bs->bs_riperr = bktrau->bktrau_riperr; bs->bs_pperr = bktrau->bktrau_pperr; bs->bs_fdsr = bktrau->bktrau_fdsr; bs->bs_ftrgt = bktrau->bktrau_ftrgt; bs->bs_fbus = bktrau->bktrau_fbus; bs->bs_oflow = bktrau->bktrau_oflow; bs->bs_droppedbuf = bktrau->bktrau_droppedbuf; break; default: error = ENOTTY; } out: BKTRAU_UNLOCK(bktrau); return error; } static int bktrau_os_poll(struct cdev *dev, int events, struct thread *td) { BKTRAU_DEV_SOFTC(dev); int revents; revents = 0; if (bktrau->bktrau_status == BKTRAU_S_RUNNING) { if (events & (POLLIN|POLLRDNORM)) { if (bktrau->bktrau_head != bktrau->bktrau_avail) revents = events & (POLLIN|POLLRDNORM); else selrecord(td, &bktrau->bktrau_sel); } } return revents; } /* * build write instruction */ #define BKTR_FM1 0x6 /* packed data to follow */ #define BKTR_FM3 0xe /* planar data to follow */ #define BKTR_VRE 0x4 /* Marks the end of the even field */ #define BKTR_VRO 0xc /* Marks the end of the odd field */ #define BKTR_PXV 0x0 /* valid word (never used) */ #define BKTR_EOL 0x1 /* last dword, 4 bytes */ #define BKTR_SOL 0x2 /* first dword */ #define OP_WRITE (0x1 << 28) #define OP_SKIP (0x2 << 28) #define OP_WRITEC (0x5 << 28) #define OP_JUMP (0x7 << 28) #define OP_SYNC (0x8 << 28) #define OP_WRITE123 (0x9 << 28) #define OP_SKIP123 (0xa << 28) #define OP_WRITES123 (0xb << 28) #define OP_SOL (1 << 27) /* first instr for scanline */ #define OP_EOL (1 << 26) #define BKTR_GEN_IRQ (1 << 24) #define BKTR_RESYNC (1 << 15) static size_t bktrau_progdwords(u_int nbufs, u_int afp_len, u_int alp_len) { /* * min pages for bytes: atop(roundup(nbytes + pagemask)) */ return (((afp_len + atop(round_page(afp_len * alp_len + PAGE_MASK))) * 2/*OP_WRITE*/ + 4/*OP_SYNC*/) * nbufs + 2/*OP_JUMP*/); } /* * Don't forget to adjust the size calculator above! */ static void bktrau_genericaudio(u_int32_t *dmaprog, bus_addr_t base, u_int nbufs, struct bktrau_segment *bufs, u_int afp_len, u_int alp_len, u_int32_t *offs) { u_int32_t *start; int i, j, k, segpos, lleft, x; start = dmaprog; for (i = 0; i < nbufs; i++, bufs++) { *dmaprog++ = OP_SYNC | BKTR_FM1; *dmaprog++ = 0; /* rsvrd */ j = k = 0; segpos = 0; while (j < afp_len) { lleft = alp_len; while (lleft) { if (segpos == bufs->bs_segs[k].ds_len) { k++; KASSERT(k < bufs->bs_cnt, ("past end of segments")); segpos = 0; } x = min(lleft, bufs->bs_segs[k].ds_len - segpos); *dmaprog++ = OP_WRITE | (lleft == alp_len ? OP_SOL : 0) | (x == lleft ? OP_EOL : 0) | x; *dmaprog++ = bufs->bs_segs[k].ds_addr + segpos; segpos += x; lleft -= x; } j++; } *offs++ = dmaprog - start; *dmaprog++ = OP_SYNC | BKTR_VRO | BKTR_GEN_IRQ; *dmaprog++ = 0; /* rsvrd */ } *dmaprog++ = OP_JUMP; *dmaprog++ = base; if (dmaprog > start + bktrau_progdwords(nbufs, afp_len, alp_len)) panic("bktrau_progsize doesn't match bktrau_genericaudio"); } /* * cleanup the tag and other risc engine related items */ static void bktrau_cleanuprisc(struct bktrau_softc *bktrau) { if (bktrau->bktrau_riscalloced) { bus_dmamap_unload(bktrau->bktrau_risctag, bktrau->bktrau_riscmap); bus_dmamem_free(bktrau->bktrau_risctag, bktrau->bktrau_risc, bktrau->bktrau_riscmap); bus_dma_tag_destroy(bktrau->bktrau_risctag); bktrau->bktrau_riscalloced = 0; } } /* * Must be called w/o lock held if risclen != 0. */ static int bktrau_makerisc(struct bktrau_softc *bktrau, size_t risclen) { int error; if (bktrau->bktrau_riscalloced) bktrau_cleanuprisc(bktrau); if ((error = bus_dma_tag_create(NULL, 4, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, risclen, 1, risclen, 0, busdma_lock_mutex, &bktrau->bktrau_lock, &bktrau->bktrau_risctag))) return error; if ((error = bus_dmamem_alloc(bktrau->bktrau_risctag, &bktrau->bktrau_risc, BUS_DMA_WAITOK, &bktrau->bktrau_riscmap))) { bus_dma_tag_destroy(bktrau->bktrau_risctag); return error; } if ((error = bus_dmamap_load(bktrau->bktrau_risctag, bktrau->bktrau_riscmap, bktrau->bktrau_risc, risclen, bktrau_risc_cb, bktrau, BUS_DMA_WAITOK))) { bus_dmamem_free(bktrau->bktrau_risctag, bktrau->bktrau_risc, bktrau->bktrau_riscmap); bus_dma_tag_destroy(bktrau->bktrau_risctag); return error; } bktrau->bktrau_riscalloced = risclen; return error; }