aoa.c revision 187692
1218885Sdim/*- 2218885Sdim * Copyright 2008 by Marco Trillo. All rights reserved. 3218885Sdim * 4218885Sdim * Redistribution and use in source and binary forms, with or without 5218885Sdim * modification, are permitted provided that the following conditions 6218885Sdim * are met: 7218885Sdim * 1. Redistributions of source code must retain the above copyright 8218885Sdim * notice, this list of conditions and the following disclaimer. 9218885Sdim * 2. Redistributions in binary form must reproduce the above copyright 10218885Sdim * notice, this list of conditions and the following disclaimer in the 11218885Sdim * documentation and/or other materials provided with the distribution. 12218885Sdim * 13218885Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14218885Sdim * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15218885Sdim * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16218885Sdim * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17218885Sdim * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 18218885Sdim * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19218885Sdim * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 20218885Sdim * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21218885Sdim * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22218885Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23218885Sdim * SUCH DAMAGE. 24218885Sdim * 25218885Sdim * $FreeBSD: head/sys/dev/sound/macio/aoa.c 187692 2009-01-25 18:20:15Z nwhitehorn $ 26218885Sdim */ 27218885Sdim 28218885Sdim/* 29218885Sdim * Apple Onboard Audio (AOA). 30218885Sdim */ 31218885Sdim 32218885Sdim#include <sys/cdefs.h> 33218885Sdim 34218885Sdim#include <sys/param.h> 35218885Sdim#include <sys/systm.h> 36218885Sdim#include <sys/kernel.h> 37218885Sdim#include <sys/bus.h> 38218885Sdim#include <sys/malloc.h> 39218885Sdim#include <sys/lock.h> 40218885Sdim#include <sys/mutex.h> 41218885Sdim#include <machine/dbdma.h> 42218885Sdim#include <machine/resource.h> 43218885Sdim#include <machine/bus.h> 44218885Sdim#include <sys/rman.h> 45218885Sdim#include <dev/ofw/ofw_bus.h> 46218885Sdim#include <dev/sound/pcm/sound.h> 47218885Sdim#include <dev/sound/macio/aoa.h> 48218885Sdim#include "mixer_if.h" 49218885Sdim 50218885Sdimstruct aoa_dma { 51218885Sdim struct mtx mutex; 52218885Sdim struct resource *reg; /* DBDMA registers */ 53218885Sdim dbdma_channel_t *channel; /* DBDMA channel */ 54218885Sdim bus_dma_tag_t tag; /* bus_dma tag */ 55218885Sdim struct pcm_channel *pcm; /* PCM channel */ 56218885Sdim struct snd_dbuf *buf; /* PCM buffer */ 57218885Sdim u_int slots; /* # of slots */ 58218885Sdim u_int slot; /* current slot */ 59218885Sdim u_int bufsz; /* buffer size */ 60218885Sdim u_int blksz; /* block size */ 61218885Sdim int running; 62218885Sdim}; 63218885Sdim 64218885Sdimstatic void 65218885Sdimaoa_dma_set_program(struct aoa_dma *dma) 66218885Sdim{ 67218885Sdim u_int32_t addr; 68218885Sdim int i; 69218885Sdim 70218885Sdim addr = (u_int32_t) sndbuf_getbufaddr(dma->buf); 71218885Sdim KASSERT(dma->bufsz == sndbuf_getsize(dma->buf), ("bad size")); 72218885Sdim 73218885Sdim dma->slots = dma->bufsz / dma->blksz; 74218885Sdim 75218885Sdim for (i = 0; i < dma->slots; ++i) { 76218885Sdim dbdma_insert_command(dma->channel, 77218885Sdim i, /* slot */ 78218885Sdim DBDMA_OUTPUT_MORE, /* command */ 79218885Sdim 0, /* stream */ 80218885Sdim addr, /* data */ 81218885Sdim dma->blksz, /* count */ 82218885Sdim DBDMA_ALWAYS, /* interrupt */ 83218885Sdim DBDMA_COND_TRUE, /* branch */ 84218885Sdim DBDMA_NEVER, /* wait */ 85218885Sdim dma->slots + 1 /* branch_slot */ 86218885Sdim ); 87218885Sdim 88218885Sdim addr += dma->blksz; 89218885Sdim } 90218885Sdim 91218885Sdim /* Branch back to beginning. */ 92218885Sdim dbdma_insert_branch(dma->channel, dma->slots, 0); 93218885Sdim 94218885Sdim /* STOP command to branch when S0 is asserted. */ 95218885Sdim dbdma_insert_stop(dma->channel, dma->slots + 1); 96218885Sdim 97218885Sdim /* Set S0 as the condition to branch to STOP. */ 98218885Sdim dbdma_set_branch_selector(dma->channel, 1 << 0, 1 << 0); 99218885Sdim dbdma_set_device_status(dma->channel, 1 << 0, 0); 100218885Sdim 101218885Sdim dbdma_sync_commands(dma->channel, BUS_DMASYNC_PREWRITE); 102218885Sdim} 103218885Sdim 104218885Sdim#define AOA_BUFFER_SIZE 65536 105218885Sdim 106218885Sdimstatic struct aoa_dma * 107218885Sdimaoa_dma_create(device_t self) 108218885Sdim{ 109218885Sdim struct aoa_softc *sc = device_get_softc(self); 110218885Sdim struct aoa_dma *dma; 111218885Sdim bus_dma_tag_t tag; 112218885Sdim int err; 113218885Sdim 114218885Sdim err = bus_dma_tag_create(bus_get_dma_tag(self), 115218885Sdim 4, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, 116218885Sdim AOA_BUFFER_SIZE, 1, AOA_BUFFER_SIZE, 0, NULL, NULL, &tag); 117218885Sdim if (err != 0) 118218885Sdim return (NULL); 119218885Sdim 120218885Sdim dma = malloc(sizeof(*dma), M_DEVBUF, M_WAITOK | M_ZERO); 121218885Sdim dma->tag = tag; 122218885Sdim dma->bufsz = AOA_BUFFER_SIZE; 123218885Sdim dma->blksz = PAGE_SIZE; /* initial blocksize */ 124218885Sdim 125218885Sdim mtx_init(&dma->mutex, "AOA", NULL, MTX_DEF); 126218885Sdim 127218885Sdim sc->sc_intrp = dma; 128218885Sdim 129218885Sdim return (dma); 130218885Sdim} 131218885Sdim 132218885Sdimstatic void 133218885Sdimaoa_dma_delete(struct aoa_dma *dma) 134218885Sdim{ 135218885Sdim bus_dma_tag_destroy(dma->tag); 136218885Sdim mtx_destroy(&dma->mutex); 137218885Sdim free(dma, M_DEVBUF); 138218885Sdim} 139218885Sdim 140218885Sdimstatic int 141218885Sdimaoa_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksz) 142218885Sdim{ 143218885Sdim struct aoa_dma *dma = data; 144218885Sdim int err, lz; 145218885Sdim 146218885Sdim DPRINTF(("aoa_chan_setblocksize: blocksz = %u, dma->blksz = %u\n", 147218885Sdim blocksz, dma->blksz)); 148218885Sdim KASSERT(!dma->running, ("dma is running")); 149218885Sdim KASSERT(blocksz > 0, ("bad blocksz")); 150218885Sdim 151218885Sdim /* Round blocksz down to a power of two... */ 152218885Sdim __asm volatile ("cntlzw %0,%1" : "=r"(lz) : "r"(blocksz)); 153218885Sdim blocksz = 1 << (31 - lz); 154218885Sdim DPRINTF(("blocksz = %u\n", blocksz)); 155218885Sdim 156218885Sdim /* ...but no more than the buffer. */ 157218885Sdim if (blocksz > dma->bufsz) 158218885Sdim blocksz = dma->bufsz; 159218885Sdim 160218885Sdim err = sndbuf_resize(dma->buf, dma->bufsz / blocksz, blocksz); 161218885Sdim if (err != 0) { 162218885Sdim DPRINTF(("sndbuf_resize returned %d\n", err)); 163218885Sdim return (0); 164218885Sdim } 165218885Sdim 166218885Sdim if (blocksz == dma->blksz) 167218885Sdim return (dma->blksz); 168218885Sdim 169218885Sdim /* One slot per block plus branch to 0 plus STOP. */ 170218885Sdim err = dbdma_resize_channel(dma->channel, 2 + dma->bufsz / blocksz); 171218885Sdim if (err != 0) { 172218885Sdim DPRINTF(("dbdma_resize_channel returned %d\n", err)); 173218885Sdim return (0); 174218885Sdim } 175218885Sdim 176218885Sdim /* Set the new blocksize. */ 177218885Sdim dma->blksz = blocksz; 178218885Sdim aoa_dma_set_program(dma); 179218885Sdim 180218885Sdim return (dma->blksz); 181218885Sdim} 182218885Sdim 183218885Sdimstatic int 184218885Sdimaoa_chan_setformat(kobj_t obj, void *data, u_int32_t format) 185218885Sdim{ 186218885Sdim DPRINTF(("aoa_chan_setformat: format = %u\n", format)); 187218885Sdim 188218885Sdim if (format != (AFMT_STEREO | AFMT_S16_BE)) 189218885Sdim return (EINVAL); 190218885Sdim 191218885Sdim return (0); 192218885Sdim} 193218885Sdim 194218885Sdimstatic int 195218885Sdimaoa_chan_setspeed(kobj_t obj, void *data, u_int32_t speed) 196218885Sdim{ 197218885Sdim DPRINTF(("aoa_chan_setspeed: speed = %u\n", speed)); 198218885Sdim 199218885Sdim return (44100); 200218885Sdim} 201218885Sdim 202218885Sdimstatic int 203218885Sdimaoa_chan_getptr(kobj_t obj, void *data) 204218885Sdim{ 205218885Sdim struct aoa_dma *dma = data; 206218885Sdim 207218885Sdim if (!dma->running) 208218885Sdim return (0); 209218885Sdim 210218885Sdim return (dma->slot * dma->blksz); 211218885Sdim} 212218885Sdim 213218885Sdimstatic void * 214218885Sdimaoa_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 215218885Sdim struct pcm_channel *c, int dir) 216218885Sdim{ 217218885Sdim device_t self = devinfo; 218218885Sdim struct aoa_softc *sc = device_get_softc(self); 219218885Sdim struct aoa_dma *dma; 220218885Sdim int max_slots, err; 221218885Sdim 222218885Sdim KASSERT(dir == PCMDIR_PLAY, ("bad dir")); 223218885Sdim 224218885Sdim dma = aoa_dma_create(self); 225218885Sdim if (!dma) 226218885Sdim return (NULL); 227218885Sdim dma->pcm = c; 228218885Sdim dma->buf = b; 229218885Sdim dma->reg = sc->sc_odma; 230218885Sdim 231218885Sdim /* One slot per block, plus branch to 0 plus STOP. */ 232218885Sdim max_slots = 2 + dma->bufsz / dma->blksz; 233218885Sdim err = dbdma_allocate_channel(dma->reg, 0, bus_get_dma_tag(self), 234218885Sdim max_slots, &dma->channel ); 235218885Sdim if (err != 0) { 236218885Sdim aoa_dma_delete(dma); 237218885Sdim return (NULL); 238218885Sdim } 239218885Sdim 240218885Sdim if (sndbuf_alloc(dma->buf, dma->tag, 0, dma->bufsz) != 0) { 241218885Sdim dbdma_free_channel(dma->channel); 242218885Sdim aoa_dma_delete(dma); 243218885Sdim return (NULL); 244218885Sdim } 245218885Sdim 246218885Sdim aoa_dma_set_program(dma); 247218885Sdim 248218885Sdim return (dma); 249218885Sdim} 250218885Sdim 251218885Sdimstatic int 252218885Sdimaoa_chan_trigger(kobj_t obj, void *data, int go) 253218885Sdim{ 254218885Sdim struct aoa_dma *dma = data; 255218885Sdim int i; 256218885Sdim 257218885Sdim switch (go) { 258218885Sdim case PCMTRIG_START: 259218885Sdim 260218885Sdim /* Start the DMA. */ 261218885Sdim dma->running = 1; 262218885Sdim 263218885Sdim dma->slot = 0; 264218885Sdim dbdma_set_current_cmd(dma->channel, dma->slot); 265218885Sdim 266218885Sdim dbdma_run(dma->channel); 267218885Sdim 268218885Sdim return (0); 269218885Sdim 270218885Sdim case PCMTRIG_STOP: 271218885Sdim case PCMTRIG_ABORT: 272218885Sdim 273218885Sdim mtx_lock(&dma->mutex); 274218885Sdim 275218885Sdim dma->running = 0; 276218885Sdim 277218885Sdim /* Make it branch to the STOP command. */ 278218885Sdim dbdma_set_device_status(dma->channel, 1 << 0, 1 << 0); 279218885Sdim 280218885Sdim /* XXX should wait for DBDMA_ACTIVE to clear. */ 281218885Sdim DELAY(40000); 282218885Sdim 283218885Sdim /* Reset the DMA. */ 284218885Sdim dbdma_stop(dma->channel); 285218885Sdim dbdma_set_device_status(dma->channel, 1 << 0, 0); 286218885Sdim 287218885Sdim for (i = 0; i < dma->slots; ++i) 288218885Sdim dbdma_clear_cmd_status(dma->channel, i); 289218885Sdim 290218885Sdim mtx_unlock(&dma->mutex); 291218885Sdim 292218885Sdim return (0); 293218885Sdim } 294218885Sdim 295218885Sdim return (0); 296218885Sdim} 297218885Sdim 298218885Sdimstatic int 299218885Sdimaoa_chan_free(kobj_t obj, void *data) 300218885Sdim{ 301218885Sdim struct aoa_dma *dma = data; 302218885Sdim 303218885Sdim sndbuf_free(dma->buf); 304218885Sdim dbdma_free_channel(dma->channel); 305218885Sdim aoa_dma_delete(dma); 306218885Sdim 307218885Sdim return (0); 308218885Sdim} 309218885Sdim 310218885Sdimvoid 311218885Sdimaoa_interrupt(void *arg) 312218885Sdim{ 313218885Sdim struct aoa_softc *sc = arg; 314218885Sdim struct aoa_dma *dma; 315218885Sdim 316218885Sdim if (!(dma = sc->sc_intrp) || !dma->running) 317218885Sdim return; 318218885Sdim 319218885Sdim mtx_lock(&dma->mutex); 320218885Sdim 321218885Sdim while (dbdma_get_cmd_status(dma->channel, dma->slot)) { 322218885Sdim 323218885Sdim dbdma_clear_cmd_status(dma->channel, dma->slot); 324218885Sdim dma->slot = (dma->slot + 1) % dma->slots; 325218885Sdim 326218885Sdim mtx_unlock(&dma->mutex); 327218885Sdim chn_intr(dma->pcm); 328218885Sdim mtx_lock(&dma->mutex); 329218885Sdim } 330218885Sdim 331218885Sdim mtx_unlock(&dma->mutex); 332218885Sdim} 333218885Sdim 334218885Sdimstatic u_int32_t sc_fmt[] = { 335218885Sdim AFMT_S16_BE | AFMT_STEREO, 336218885Sdim 0 337218885Sdim}; 338218885Sdimstatic struct pcmchan_caps aoa_caps = {44100, 44100, sc_fmt, 0}; 339218885Sdim 340218885Sdimstatic struct pcmchan_caps * 341218885Sdimaoa_chan_getcaps(kobj_t obj, void *data) 342218885Sdim{ 343218885Sdim return (&aoa_caps); 344218885Sdim} 345218885Sdim 346218885Sdimstatic kobj_method_t aoa_chan_methods[] = { 347218885Sdim KOBJMETHOD(channel_init, aoa_chan_init), 348218885Sdim KOBJMETHOD(channel_free, aoa_chan_free), 349218885Sdim KOBJMETHOD(channel_setformat, aoa_chan_setformat), 350218885Sdim KOBJMETHOD(channel_setspeed, aoa_chan_setspeed), 351218885Sdim KOBJMETHOD(channel_setblocksize,aoa_chan_setblocksize), 352218885Sdim KOBJMETHOD(channel_trigger, aoa_chan_trigger), 353218885Sdim KOBJMETHOD(channel_getptr, aoa_chan_getptr), 354218885Sdim KOBJMETHOD(channel_getcaps, aoa_chan_getcaps), 355218885Sdim { 0, 0 } 356218885Sdim}; 357218885SdimCHANNEL_DECLARE(aoa_chan); 358218885Sdim 359218885Sdimint 360218885Sdimaoa_attach(device_t self) 361218885Sdim{ 362218885Sdim char status[SND_STATUSLEN]; 363218885Sdim int err; 364218885Sdim 365218885Sdim if (pcm_register(self, self, 1, 0)) 366218885Sdim return (ENXIO); 367218885Sdim 368218885Sdim err = pcm_getbuffersize(self, AOA_BUFFER_SIZE, AOA_BUFFER_SIZE, 369218885Sdim AOA_BUFFER_SIZE); 370218885Sdim DPRINTF(("pcm_getbuffersize returned %d\n", err)); 371218885Sdim 372218885Sdim pcm_addchan(self, PCMDIR_PLAY, &aoa_chan_class, self); 373218885Sdim 374218885Sdim snprintf(status, sizeof(status), "at %s", ofw_bus_get_name(self)); 375218885Sdim pcm_setstatus(self, status); 376218885Sdim 377218885Sdim return (0); 378218885Sdim} 379218885Sdim 380218885Sdim