summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound/pcm/channel.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/sound/pcm/channel.c')
-rw-r--r--sys/dev/sound/pcm/channel.c737
1 files changed, 737 insertions, 0 deletions
diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c
new file mode 100644
index 0000000..dd383e5
--- /dev/null
+++ b/sys/dev/sound/pcm/channel.c
@@ -0,0 +1,737 @@
+/*
+ * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
+ * Portions Copyright by Luigi Rizzo - 1997-99
+ * 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 <dev/pcm/sound.h>
+
+#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */
+#define DMA_ALIGN_THRESHOLD 4
+#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1))
+
+#define ISA_DMA(b) (((b)->chan >= 0 && (b)->chan != 4 && (b)->chan < 8))
+#define CANCHANGE(c) (!(c)->buffer.dl)
+
+static int chn_reinit(pcm_channel *c);
+static void chn_stintr(pcm_channel *c);
+/*
+ * SOUND OUTPUT
+
+We use a circular buffer to store samples directed to the DAC.
+The buffer is split into two variable-size regions, each identified
+by an offset in the buffer (rp,fp) and a length (rl,fl):
+
+ 0 rp,rl fp,fl bufsize
+ |__________>____________>________|
+ FREE d READY w FREE
+
+ READY: data written from the process and ready to be sent to the DAC;
+ FREE: free part of the buffer.
+
+Both regions can wrap around the end of the buffer. At initialization,
+READY is empty, FREE takes all the available space, and dma is
+idle. dl contains the length of the current DMA transfer, dl=0
+means that the dma is idle.
+
+The two boundaries (rp,fp) in the buffers are advanced by DMA [d]
+and write() [w] operations. The first portion of the READY region
+is used for DMA transfers. The transfer is started at rp and with
+chunks of length dl. During DMA operations, dsp_wr_dmaupdate()
+updates rp, rl and fl tracking the ISA DMA engine as the transfer
+makes progress.
+When a new block is written, fp advances and rl,fl are updated
+accordingly.
+
+The code works as follows: the user write routine dsp_write_body()
+fills up the READY region with new data (reclaiming space from the
+FREE region) and starts the write DMA engine if inactive. When a
+DMA transfer is complete, an interrupt causes dsp_wrintr() to be
+called which extends the FREE region and possibly starts the next
+transfer.
+
+In some cases, the code tries to track the current status of DMA
+operations by calling dsp_wr_dmaupdate() which changes rp, rl and fl.
+
+The sistem tries to make all DMA transfers use the same size,
+play_blocksize or rec_blocksize. The size is either selected by
+the user, or computed by the system to correspond to about .25s of
+audio. The blocksize must be within a range which is currently:
+
+ min(5ms, 40 bytes) ... 1/2 buffer size.
+
+When there aren't enough data (write) or space (read), a transfer
+is started with a reduced size.
+
+To reduce problems in case of overruns, the routine which fills up
+the buffer should initialize (e.g. by repeating the last value) a
+reasonably long area after the last block so that no noise is
+produced on overruns.
+
+ *
+ */
+
+
+/* XXX this is broken: in the event a bounce buffer is used, data never
+ * gets copied in or out of the real buffer. fix requires mods to isa_dma.c
+ * and possibly fixes to other autodma mode clients
+ */
+static void
+chn_isadmabounce(pcm_channel *c)
+{
+ if (ISA_DMA(&c->buffer)) {
+ /* tell isa_dma to bounce data in/out */
+ } else panic("chn_isadmabounce called on invalid channel");
+}
+
+static int
+chn_polltrigger(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+ unsigned lim = (c->flags & CHN_F_HAS_SIZE)? c->blocksize : 1;
+ int trig = 0;
+
+ if (c->flags & CHN_F_MAPPED)
+ trig = ((b->int_count > b->prev_int_count) || b->first_poll);
+ else trig = (((c->direction == PCMDIR_PLAY)? b->fl : b->rl) >= lim);
+ return trig;
+}
+
+static int
+chn_pollreset(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+
+ if (c->flags & CHN_F_MAPPED) b->prev_int_count = b->int_count;
+ b->first_poll = 0;
+ return 1;
+}
+
+/*
+ * chn_dmadone() updates pointers and wakes up any process sleeping
+ * or waiting on a select().
+ * Must be called at spltty().
+ */
+static void
+chn_dmadone(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+
+ chn_dmaupdate(c);
+ if (ISA_DMA(b)) chn_isadmabounce(c); /* sync bounce buffer */
+ wakeup(b);
+ b->int_count++;
+ if (b->sel.si_pid && chn_polltrigger(c)) selwakeup(&b->sel);
+}
+
+/*
+ * chn_dmaupdate() tracks the status of a dma transfer,
+ * updating pointers. It must be called at spltty().
+ *
+ * NOTE: when we are using auto dma in the device, rl might become
+ * negative.
+ */
+void
+chn_dmaupdate(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+ int delta, hwptr = chn_getptr(c);
+
+ if (c->direction == PCMDIR_PLAY) {
+ delta = (b->bufsize + hwptr - b->rp) % b->bufsize;
+ b->rp = hwptr;
+ b->rl -= delta;
+ b->fl += delta;
+ } else {
+ delta = (b->bufsize + hwptr - b->fp) % b->bufsize;
+ b->fp = hwptr;
+ b->rl += delta;
+ b->fl -= delta;
+ }
+ b->total += delta;
+}
+
+/*
+ * Write interrupt routine. Can be called from other places (e.g.
+ * to start a paused transfer), but with interrupts disabled.
+ */
+static void
+chn_wrintr(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+ int start;
+
+ if (b->dl) chn_dmadone(c);
+
+ /*
+ * start another dma operation only if have ready data in the buffer,
+ * there is no pending abort, have a full-duplex device, or have a
+ * half duplex device and there is no pending op on the other side.
+ *
+ * Force transfers to be aligned to a boundary of 4, which is
+ * needed when doing stereo and 16-bit.
+ */
+ if (c->flags & CHN_F_MAPPED) start = c->flags & CHN_F_TRIGGERED;
+ else start = (b->rl >= DMA_ALIGN_THRESHOLD && !(c->flags & CHN_F_ABORTING));
+ if (start) {
+ int l;
+ chn_dmaupdate(c);
+ l = min(b->rl, c->blocksize) & DMA_ALIGN_MASK;
+ if (c->flags & CHN_F_MAPPED) l = c->blocksize;
+ /*
+ * check if we need to reprogram the DMA on the sound card.
+ * This happens if the size has changed _and_ the new size
+ * is smaller, or it matches the blocksize.
+ *
+ * 0 <= l <= blocksize
+ * 0 <= dl <= blocksize
+ * reprog if (dl == 0 || l != dl)
+ * was:
+ * l != b->dl && (b->dl == 0 || l < b->dl || l == c->blocksize)
+ */
+ if (b->dl == 0 || l != b->dl) {
+ /* size has changed. Stop and restart */
+ DEB(printf("wrintr: bsz %d -> %d, rp %d rl %d\n",
+ b->dl, l, b->rp, b->rl));
+ if (b->dl) chn_trigger(c, PCMTRIG_STOP);
+ b->dl = l; /* record new transfer size */
+ chn_trigger(c, PCMTRIG_START);
+ }
+ } else {
+ /* cannot start a new dma transfer */
+ DEB(printf("cannot start wr-dma flags 0x%08x rp %d rl %d\n",
+ c->flags, b->rp, b->rl));
+ if (b->dl) { /* was active */
+ b->dl = 0;
+ chn_trigger(c, PCMTRIG_STOP);
+#if 0
+ if (c->flags & CHN_F_WRITING)
+ DEB(printf("got wrint while reloading\n"));
+ else if (b->rl <= 0) /* XXX added 980110 lr */
+ chn_resetbuf(c);
+#endif
+ }
+ }
+}
+
+/*
+ * user write routine
+ *
+ * advance the boundary between READY and FREE, fill the space with
+ * uiomove(), and possibly start DMA. Do the above until the transfer
+ * is complete.
+ *
+ * To minimize latency in case a pending DMA transfer is about to end,
+ * we do the transfer in pieces of increasing sizes, extending the
+ * READY area at every checkpoint. In the (necessary) assumption that
+ * memory bandwidth is larger than the rate at which the dma consumes
+ * data, we reduce the latency to something proportional to the length
+ * of the first piece, while keeping the overhead low and being able
+ * to feed the DMA with large blocks.
+ */
+
+int
+chn_write(pcm_channel *c, struct uio *buf)
+{
+ int l, w, timeout, ret = 0;
+ long s;
+ snd_dbuf *b = &c->buffer;
+
+ if (c->flags & CHN_F_WRITING) {
+ /* This shouldn't happen and is actually silly
+ * - will never wake up, just timeout; why not sleep on b?
+ */
+ tsleep(&s, PZERO, "pcmwrW", hz);
+ return EBUSY;
+ }
+ c->flags |= CHN_F_WRITING;
+ while (buf->uio_resid >= DMA_ALIGN_THRESHOLD) {
+ s = spltty();
+ chn_dmaupdate(c);
+ splx(s);
+ if (b->fl < DMA_ALIGN_THRESHOLD) {
+ if (c->flags & CHN_F_NBIO) break;
+ timeout = (buf->uio_resid >= b->dl)? hz : 1;
+ ret = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout);
+ if (ret == EINTR) chn_abort(c);
+ if (ret == EINTR || ret == ERESTART) break;
+ ret = 0;
+ continue;
+ }
+ /* ensure we always have a whole number of samples */
+ l = min(b->fl, b->bufsize - b->fp) & DMA_ALIGN_MASK;
+ w = c->feeder->feed(c->feeder, b->buf + b->fp, l, buf);
+ s = spltty();
+ b->rl += w;
+ b->fl -= w;
+ b->fp = (b->fp + w) % b->bufsize;
+ splx(s);
+ if (b->rl && !b->dl) chn_stintr(c);
+ }
+ c->flags &= ~CHN_F_WRITING;
+ return ret;
+}
+
+/*
+ * SOUND INPUT
+ *
+
+The input part is similar to the output one, with a circular buffer
+split in two regions, and boundaries advancing because of read() calls
+[r] or dma operation [d]. At initialization, as for the write
+routine, READY is empty, and FREE takes all the space.
+
+ 0 rp,rl fp,fl bufsize
+ |__________>____________>________|
+ FREE r READY d FREE
+
+Operation is as follows: upon user read (dsp_read_body()) a DMA read
+is started if not already active (marked by b->dl > 0),
+then as soon as data are available in the READY region they are
+transferred to the user buffer, thus advancing the boundary between FREE
+and READY. Upon interrupts, caused by a completion of a DMA transfer,
+the READY region is extended and possibly a new transfer is started.
+
+When necessary, dsp_rd_dmaupdate() is called to advance fp (and update
+rl,fl accordingly). Upon user reads, rp is advanced and rl,fl are
+updated accordingly.
+
+The rules to choose the size of the new DMA area are similar to
+the other case, with a preferred constant transfer size equal to
+rec_blocksize, and fallback to smaller sizes if no space is available.
+
+ */
+
+/* read interrupt routine. Must be called with interrupts blocked. */
+static void
+chn_rdintr(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+ int start;
+
+ if (b->dl) chn_dmadone(c);
+
+ DEB(printf("rdintr: start dl %d, rp:rl %d:%d, fp:fl %d:%d\n",
+ b->dl, b->rp, b->rl, b->fp, b->fl));
+ /* Restart if have enough free space to absorb overruns */
+ if (c->flags & CHN_F_MAPPED) start = c->flags & CHN_F_TRIGGERED;
+ else start = (b->fl > 0x200 && !(c->flags & CHN_F_ABORTING));
+ if (start) {
+ int l = min(b->fl - 0x100, c->blocksize);
+ if (c->flags & CHN_F_MAPPED) l = c->blocksize;
+ l &= DMA_ALIGN_MASK ; /* realign sizes */
+
+ DEB(printf("rdintr: dl %d -> %d\n", b->dl, l);)
+ if (l != b->dl) {
+ /* size has changed. Stop and restart */
+ if (b->dl) {
+ chn_trigger(c, PCMTRIG_STOP);
+ chn_dmaupdate(c);
+ l = min(b->fl - 0x100, c->blocksize);
+ l &= DMA_ALIGN_MASK ; /* realign sizes */
+ }
+ b->dl = l;
+ chn_trigger(c, PCMTRIG_START);
+ }
+ } else {
+ if (b->dl) { /* was active */
+ b->dl = 0;
+ chn_dmaupdate(c);
+ chn_trigger(c, PCMTRIG_STOP);
+ }
+ }
+}
+
+/*
+ * body of user-read routine
+ *
+ * Start DMA if not active; wait for READY not empty.
+ * Transfer data from READY region using uiomove(), advance boundary
+ * between FREE and READY. Repeat until transfer is complete.
+ *
+ * To avoid excessive latency in freeing up space for the DMA
+ * engine, transfers are done in blocks of increasing size, so that
+ * the latency is proportional to the size of the smallest block, but
+ * we have a low overhead and are able to feed the dma engine with
+ * large blocks.
+ *
+ * NOTE: in the current version, read will not return more than
+ * blocksize bytes at once (unless more are already available), to
+ * avoid that requests using very large buffers block for too long.
+ */
+
+int
+chn_read(pcm_channel *c, struct uio *buf)
+{
+ int w, l, timeout, limit, ret = 0;
+ long s;
+ snd_dbuf *b = &c->buffer;
+
+ if (c->flags & CHN_F_READING) {
+ /* This shouldn't happen and is actually silly */
+ tsleep(&s, PZERO, "pcmrdR", hz);
+ return (EBUSY);
+ }
+
+ if (!b->rl & !b->dl) chn_stintr(c);
+ c->flags |= CHN_F_READING;
+ limit = buf->uio_resid - c->blocksize;
+ if (limit < 0) limit = 0;
+ while (buf->uio_resid > limit) {
+ s = spltty();
+ chn_dmaupdate(c);
+ splx(s);
+ if (b->rl < DMA_ALIGN_THRESHOLD) {
+ if (c->flags & CHN_F_NBIO) break;
+ timeout = (buf->uio_resid - limit >= b->dl)? hz : 1;
+ ret = tsleep(b, PRIBIO | PCATCH, "pcmrd", timeout);
+ if (ret == EINTR) chn_abort(c);
+ if (ret == EINTR || ret == ERESTART) break;
+ ret = 0;
+ continue;
+ }
+ /* ensure we always have a whole number of samples */
+ l = min(b->rl, b->bufsize - b->rp) & DMA_ALIGN_MASK;
+ w = c->feeder->feed(c->feeder, b->buf + b->rp, l, buf);
+ s = spltty();
+ b->rl -= w;
+ b->fl += w;
+ b->rp = (b->rp + w) % b->bufsize;
+ splx(s);
+ }
+ c->flags &= ~CHN_F_READING;
+ return ret;
+}
+
+void
+chn_intr(pcm_channel *c)
+{
+ if (!c->buffer.dl) chn_reinit(c);
+ if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c);
+}
+
+static void
+chn_stintr(pcm_channel *c)
+{
+ u_long s;
+ s = spltty();
+ chn_intr(c);
+ splx(s);
+}
+
+static void
+chn_dma_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+ snd_dbuf *b = (snd_dbuf *)arg;
+
+ if (bootverbose) {
+ printf("pcm: setmap %lx, %lx; ", (unsigned long)segs->ds_addr,
+ (unsigned long)segs->ds_len);
+ printf("%p -> %lx\n", b->buf, (unsigned long)vtophys(b->buf));
+ }
+}
+
+int
+chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat)
+{
+ if (bus_dmamem_alloc(parent_dmat, (void **)&b->buf,
+ BUS_DMA_NOWAIT, &b->dmamap)) return -1;
+ if (bus_dmamap_load(parent_dmat, b->dmamap, b->buf,
+ b->bufsize, chn_dma_setmap, b, 0)) return -1;
+ return 0;
+}
+
+void
+chn_resetbuf(pcm_channel *c)
+{
+ snd_dbuf *b = &c->buffer;
+ u_int16_t data, *p;
+ u_int32_t i;
+
+ c->buffer.sample_size = 1;
+ c->buffer.sample_size <<= (c->hwfmt & AFMT_STEREO)? 1 : 0;
+ c->buffer.sample_size <<= (c->hwfmt & AFMT_16BIT)? 1 : 0;
+ /* rely on bufsize & 3 == 0 */
+ if (c->hwfmt & AFMT_SIGNED) data = 0x00; else data = 0x80;
+ if (c->hwfmt & AFMT_16BIT) data <<= 8; else data |= data << 8;
+ if (c->hwfmt & AFMT_BIGENDIAN)
+ data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00);
+ for (i = 0, p = (u_int16_t *)b->buf; i < b->bufsize; i += 2)
+ *p++ = data;
+ b->rp = b->fp = 0;
+ b->dl = b->rl = 0;
+ b->prev_total = b->total = 0;
+ b->prev_int_count = b->int_count = 0;
+ b->first_poll = 1;
+ b->fl = b->bufsize;
+}
+
+void
+buf_isadma(snd_dbuf *b, int go)
+{
+ if (ISA_DMA(b)) {
+ if (go == PCMTRIG_START) isa_dmastart(b->dir | B_RAW, b->buf,
+ b->bufsize, b->chan);
+ else {
+ isa_dmastop(b->chan);
+ isa_dmadone(b->dir | B_RAW, b->buf, b->bufsize,
+ b->chan);
+ }
+ } else panic("buf_isadma called on invalid channel");
+}
+
+int
+buf_isadmaptr(snd_dbuf *b)
+{
+ if (ISA_DMA(b)) {
+ int i = b->dl? isa_dmastatus(b->chan) : b->bufsize;
+ if (i < 0) i = 0;
+ return b->bufsize - i;
+ } else panic("buf_isadmaptr called on invalid channel");
+ return -1;
+}
+
+/*
+ * snd_sync waits until the space in the given channel goes above
+ * a threshold. The threshold is checked against fl or rl respectively.
+ * Assume that the condition can become true, do not check here...
+ */
+int
+chn_sync(pcm_channel *c, int threshold)
+{
+ u_long s, rdy;
+ int ret;
+ snd_dbuf *b = &c->buffer;
+
+ for (;;) {
+ s = spltty();
+ chn_dmaupdate(c);
+ rdy = (c->direction == PCMDIR_PLAY)? b->fl : b->rl;
+ if (rdy <= threshold) {
+ ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmsyn", 1);
+ splx(s);
+ if (ret == ERESTART || ret == EINTR) {
+ printf("tsleep returns %d\n", ret);
+ return -1;
+ }
+ } else break;
+ }
+ splx(s);
+ return 0;
+}
+
+int
+chn_poll(pcm_channel *c, int ev, struct proc *p)
+{
+ snd_dbuf *b = &c->buffer;
+ u_long s = spltty();
+ if (b->dl) chn_dmaupdate(c);
+ splx(s);
+ if (chn_polltrigger(c) && chn_pollreset(c)) return ev;
+ else {
+ selrecord(p, &b->sel);
+ return 0;
+ }
+}
+
+/*
+ * chn_abort is a non-blocking function which aborts a pending
+ * DMA transfer and flushes the buffers.
+ * It returns the number of bytes that have not been transferred.
+ */
+int
+chn_abort(pcm_channel *c)
+{
+ long s;
+ int missing = 0;
+ snd_dbuf *b = &c->buffer;
+
+ s = spltty();
+ if (b->dl) {
+ b->dl = 0;
+ c->flags &= ~((c->direction == PCMDIR_PLAY)? CHN_F_WRITING : CHN_F_READING);
+ chn_trigger(c, PCMTRIG_ABORT);
+ chn_dmadone(c);
+ }
+ missing = b->rl;
+ splx(s);
+ return missing;
+}
+
+/*
+ * this routine tries to flush the dma transfer. It is called
+ * on a close. We immediately abort any read DMA
+ * operation, and then wait for the play buffer to drain.
+ */
+
+int
+chn_flush(pcm_channel *c)
+{
+ int ret, count = 10;
+ snd_dbuf *b = &c->buffer;
+
+ DEB(printf("snd_flush c->flags 0x%08x\n", c->flags));
+ c->flags |= CHN_F_CLOSING;
+ if (c->direction != PCMDIR_PLAY) chn_abort(c);
+ else while (b->dl) {
+ /* still pending output data. */
+ ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmflu", hz);
+ chn_dmaupdate(c);
+ DEB(printf("snd_sync: now rl : fl %d : %d\n", b->rl, b->fl));
+ if (ret == EINTR) {
+ printf("tsleep returns %d\n", ret);
+ return -1;
+ }
+ if (ret && --count == 0) {
+ printf("timeout flushing dbuf_out, cnt 0x%x flags 0x%x\n",
+ b->rl, c->flags);
+ break;
+ }
+ }
+ c->flags &= ~CHN_F_CLOSING;
+ if (c->direction == PCMDIR_PLAY) chn_abort(c);
+ return 0;
+}
+
+int
+chn_reset(pcm_channel *c)
+{
+ chn_abort(c);
+ c->flags &= CHN_F_RESET;
+ chn_resetbuf(c);
+ c->flags |= CHN_F_INIT;
+ return 0;
+}
+
+static int
+chn_reinit(pcm_channel *c)
+{
+ if ((c->flags & CHN_F_INIT) && CANCHANGE(c)) {
+ chn_setformat(c, c->format);
+ chn_setspeed(c, c->speed);
+ chn_setblocksize(c, c->blocksize);
+ chn_setvolume(c, (c->volume >> 8) & 0xff, c->volume & 0xff);
+ c->flags &= ~CHN_F_INIT;
+ return 1;
+ }
+ return 0;
+}
+
+int
+chn_init(pcm_channel *c, void *devinfo, int dir)
+{
+ c->flags = 0;
+ c->feeder = &feeder_root;
+ c->buffer.chan = -1;
+ c->devinfo = c->init(devinfo, &c->buffer, c, dir);
+ chn_setdir(c, dir);
+ return 0;
+}
+
+int
+chn_setdir(pcm_channel *c, int dir)
+{
+ c->direction = dir;
+ if (ISA_DMA(&c->buffer))
+ c->buffer.dir = (dir == PCMDIR_PLAY)? B_WRITE : B_READ;
+ return c->setdir(c->devinfo, c->direction);
+}
+
+int
+chn_setvolume(pcm_channel *c, int left, int right)
+{
+ /* could add a feeder for volume changing if channel returns -1 */
+ if (CANCHANGE(c)) {
+ return -1;
+ }
+ c->volume = (left << 8) | right;
+ c->flags |= CHN_F_INIT;
+ return 0;
+}
+
+int
+chn_setspeed(pcm_channel *c, int speed)
+{
+ /* could add a feeder for rate conversion */
+ if (CANCHANGE(c)) {
+ c->speed = c->setspeed(c->devinfo, speed);
+ return c->speed;
+ }
+ c->speed = speed;
+ c->flags |= CHN_F_INIT;
+ return 0;
+}
+
+int
+chn_setformat(pcm_channel *c, u_int32_t fmt)
+{
+ if (CANCHANGE(c)) {
+ c->hwfmt = c->format = fmt;
+ c->hwfmt = chn_feedchain(c);
+ chn_resetbuf(c);
+ c->setformat(c->devinfo, c->hwfmt);
+ return fmt;
+ }
+ c->format = fmt;
+ c->flags |= CHN_F_INIT;
+ return 0;
+}
+
+int
+chn_setblocksize(pcm_channel *c, int blksz)
+{
+ if (CANCHANGE(c)) {
+ c->flags &= ~CHN_F_HAS_SIZE;
+ if (blksz >= 2) c->flags |= CHN_F_HAS_SIZE;
+ blksz = abs(blksz);
+ if (blksz < 2) blksz = (c->buffer.sample_size * c->speed) >> 2;
+ RANGE(blksz, 1024, c->buffer.bufsize / 4);
+ blksz &= ~3;
+ c->blocksize = c->setblocksize(c->devinfo, blksz);
+ return c->blocksize;
+ }
+ c->blocksize = blksz;
+ c->flags |= CHN_F_INIT;
+ return 0;
+}
+
+int
+chn_trigger(pcm_channel *c, int go)
+{
+ return c->trigger(c->devinfo, go);
+}
+
+int
+chn_getptr(pcm_channel *c)
+{
+ return c->getptr(c->devinfo);
+}
+
+pcmchan_caps *
+chn_getcaps(pcm_channel *c)
+{
+ return c->getcaps(c->devinfo);
+}
OpenPOWER on IntegriCloud