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.c1213
1 files changed, 1213 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..60af109
--- /dev/null
+++ b/sys/dev/sound/pcm/channel.c
@@ -0,0 +1,1213 @@
+/*
+ * 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.
+ */
+
+#include <dev/sound/pcm/sound.h>
+
+#include "feeder_if.h"
+
+SND_DECLARE_FILE("$FreeBSD$");
+
+#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */
+#define DMA_ALIGN_THRESHOLD 4
+#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1))
+
+#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED))
+
+/*
+#define DEB(x) x
+*/
+
+static int chn_targetirqrate = 32;
+TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate);
+
+static int
+sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS)
+{
+ int err, val;
+
+ val = chn_targetirqrate;
+ err = sysctl_handle_int(oidp, &val, sizeof(val), req);
+ if (val < 16 || val > 512)
+ err = EINVAL;
+ else
+ chn_targetirqrate = val;
+
+ return err;
+}
+SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW,
+ 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", "");
+static int report_soft_formats = 1;
+SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW,
+ &report_soft_formats, 1, "report software-emulated formats");
+
+static int chn_buildfeeder(struct pcm_channel *c);
+
+static void
+chn_lockinit(struct pcm_channel *c)
+{
+ c->lock = snd_mtxcreate(c->name, "pcm channel");
+}
+
+static void
+chn_lockdestroy(struct pcm_channel *c)
+{
+ snd_mtxfree(c->lock);
+}
+
+static int
+chn_polltrigger(struct pcm_channel *c)
+{
+ struct snd_dbuf *bs = c->bufsoft;
+ unsigned amt, lim;
+
+ CHN_LOCKASSERT(c);
+ if (c->flags & CHN_F_MAPPED) {
+ if (sndbuf_getprevblocks(bs) == 0)
+ return 1;
+ else
+ return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0;
+ } else {
+ amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs);
+ lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1;
+ lim = 1;
+ return (amt >= lim)? 1 : 0;
+ }
+ return 0;
+}
+
+static int
+chn_pollreset(struct pcm_channel *c)
+{
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ sndbuf_updateprevtotal(bs);
+ return 1;
+}
+
+static void
+chn_wakeup(struct pcm_channel *c)
+{
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c))
+ selwakeup(sndbuf_getsel(bs));
+ wakeup(bs);
+}
+
+static int
+chn_sleep(struct pcm_channel *c, char *str, int timeout)
+{
+ struct snd_dbuf *bs = c->bufsoft;
+ int ret;
+
+ CHN_LOCKASSERT(c);
+#ifdef USING_MUTEX
+ ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout);
+#else
+ ret = tsleep(bs, PRIBIO | PCATCH, str, timeout);
+#endif
+
+ return ret;
+}
+
+/*
+ * chn_dmaupdate() tracks the status of a dma transfer,
+ * updating pointers. It must be called at spltty().
+ */
+
+static unsigned int
+chn_dmaupdate(struct pcm_channel *c)
+{
+ struct snd_dbuf *b = c->bufhard;
+ unsigned int delta, old, hwptr, amt;
+
+ KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0"));
+ CHN_LOCKASSERT(c);
+
+ old = sndbuf_gethwptr(b);
+ hwptr = chn_getptr(c);
+ delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b);
+ sndbuf_sethwptr(b, hwptr);
+
+ DEB(
+ if (delta >= ((sndbuf_getsize(b) * 15) / 16)) {
+ if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING)))
+ device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr);
+ }
+ );
+
+ if (c->direction == PCMDIR_PLAY) {
+ amt = MIN(delta, sndbuf_getready(b));
+ if (amt > 0)
+ sndbuf_dispose(b, NULL, amt);
+ } else {
+ amt = MIN(delta, sndbuf_getfree(b));
+ if (amt > 0)
+ sndbuf_acquire(b, NULL, amt);
+ }
+
+ return delta;
+}
+
+void
+chn_wrupdate(struct pcm_channel *c)
+{
+ int ret;
+
+ CHN_LOCKASSERT(c);
+ KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel"));
+
+ if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED))
+ return;
+ chn_dmaupdate(c);
+ ret = chn_wrfeed(c);
+ /* tell the driver we've updated the primary buffer */
+ chn_trigger(c, PCMTRIG_EMLDMAWR);
+ DEB(if (ret)
+ printf("chn_wrupdate: chn_wrfeed returned %d\n", ret);)
+
+}
+
+int
+chn_wrfeed(struct pcm_channel *c)
+{
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+ unsigned int ret, amt;
+
+ CHN_LOCKASSERT(c);
+ DEB(
+ if (c->flags & CHN_F_CLOSING) {
+ sndbuf_dump(b, "b", 0x02);
+ sndbuf_dump(bs, "bs", 0x02);
+ })
+
+ if (c->flags & CHN_F_MAPPED)
+ sndbuf_acquire(bs, NULL, sndbuf_getfree(bs));
+
+ amt = sndbuf_getfree(b);
+ if (sndbuf_getready(bs) < amt)
+ c->xruns++;
+
+ ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC;
+ if (ret == 0 && sndbuf_getfree(b) < amt)
+ chn_wakeup(c);
+
+ return ret;
+}
+
+static void
+chn_wrintr(struct pcm_channel *c)
+{
+ int ret;
+
+ CHN_LOCKASSERT(c);
+ /* update pointers in primary buffer */
+ chn_dmaupdate(c);
+ /* ...and feed from secondary to primary */
+ ret = chn_wrfeed(c);
+ /* tell the driver we've updated the primary buffer */
+ chn_trigger(c, PCMTRIG_EMLDMAWR);
+ DEB(if (ret)
+ printf("chn_wrintr: chn_wrfeed returned %d\n", ret);)
+}
+
+/*
+ * user write routine - uiomove data into secondary buffer, trigger if necessary
+ * if blocking, sleep, rinse and repeat.
+ *
+ * called externally, so must handle locking
+ */
+
+int
+chn_write(struct pcm_channel *c, struct uio *buf)
+{
+ int ret, timeout, newsize, count, sz;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ /*
+ * XXX Certain applications attempt to write larger size
+ * of pcm data than c->blocksize2nd without blocking,
+ * resulting partial write. Expand the block size so that
+ * the write operation avoids blocking.
+ */
+ if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) {
+ DEB(device_printf(c->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n",
+ buf->uio_resid, sndbuf_getblksz(bs)));
+ newsize = 16;
+ while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2))
+ newsize <<= 1;
+ chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize);
+ DEB(device_printf(c->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs)));
+ }
+
+ ret = 0;
+ count = hz;
+ while (!ret && (buf->uio_resid > 0) && (count > 0)) {
+ sz = sndbuf_getfree(bs);
+ if (sz == 0) {
+ if (c->flags & CHN_F_NBIO)
+ ret = EWOULDBLOCK;
+ else {
+ timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs));
+ if (timeout < 1)
+ timeout = 1;
+ timeout = 1;
+ ret = chn_sleep(c, "pcmwr", timeout);
+ if (ret == EWOULDBLOCK) {
+ count -= timeout;
+ ret = 0;
+ } else if (ret == 0)
+ count = hz;
+ }
+ } else {
+ sz = MIN(sz, buf->uio_resid);
+ KASSERT(sz > 0, ("confusion in chn_write"));
+ /* printf("sz: %d\n", sz); */
+ ret = sndbuf_uiomove(bs, buf, sz);
+ if (ret == 0 && !(c->flags & CHN_F_TRIGGERED))
+ chn_start(c, 0);
+ }
+ }
+ /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */
+
+ if (count <= 0) {
+ c->flags |= CHN_F_DEAD;
+ printf("%s: play interrupt timeout, channel dead\n", c->name);
+ }
+
+ return ret;
+}
+
+static int
+chn_rddump(struct pcm_channel *c, unsigned int cnt)
+{
+ struct snd_dbuf *b = c->bufhard;
+
+ CHN_LOCKASSERT(c);
+ sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt);
+ return sndbuf_dispose(b, NULL, cnt);
+}
+
+/*
+ * Feed new data from the read buffer. Can be called in the bottom half.
+ * Hence must be called at spltty.
+ */
+int
+chn_rdfeed(struct pcm_channel *c)
+{
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+ unsigned int ret, amt;
+
+ CHN_LOCKASSERT(c);
+ DEB(
+ if (c->flags & CHN_F_CLOSING) {
+ sndbuf_dump(b, "b", 0x02);
+ sndbuf_dump(bs, "bs", 0x02);
+ })
+
+ amt = sndbuf_getready(b);
+ if (sndbuf_getfree(bs) < amt) {
+ c->xruns++;
+ amt = sndbuf_getfree(bs);
+ }
+ ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0;
+
+ amt = sndbuf_getready(b);
+ if (amt > 0)
+ chn_rddump(c, amt);
+
+ chn_wakeup(c);
+
+ return ret;
+}
+
+void
+chn_rdupdate(struct pcm_channel *c)
+{
+ int ret;
+
+ CHN_LOCKASSERT(c);
+ KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel"));
+
+ if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED))
+ return;
+ chn_trigger(c, PCMTRIG_EMLDMARD);
+ chn_dmaupdate(c);
+ ret = chn_rdfeed(c);
+ if (ret)
+ printf("chn_rdfeed: %d\n", ret);
+
+}
+
+/* read interrupt routine. Must be called with interrupts blocked. */
+static void
+chn_rdintr(struct pcm_channel *c)
+{
+ int ret;
+
+ CHN_LOCKASSERT(c);
+ /* tell the driver to update the primary buffer if non-dma */
+ chn_trigger(c, PCMTRIG_EMLDMARD);
+ /* update pointers in primary buffer */
+ chn_dmaupdate(c);
+ /* ...and feed from primary to secondary */
+ ret = chn_rdfeed(c);
+}
+
+/*
+ * user read routine - trigger if necessary, uiomove data from secondary buffer
+ * if blocking, sleep, rinse and repeat.
+ *
+ * called externally, so must handle locking
+ */
+
+int
+chn_read(struct pcm_channel *c, struct uio *buf)
+{
+ int ret, timeout, sz, count;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ if (!(c->flags & CHN_F_TRIGGERED))
+ chn_start(c, 0);
+
+ ret = 0;
+ count = hz;
+ while (!ret && (buf->uio_resid > 0) && (count > 0)) {
+ sz = MIN(buf->uio_resid, sndbuf_getready(bs));
+
+ if (sz > 0) {
+ ret = sndbuf_uiomove(bs, buf, sz);
+ } else {
+ if (c->flags & CHN_F_NBIO) {
+ ret = EWOULDBLOCK;
+ } else {
+ timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs));
+ if (timeout < 1)
+ timeout = 1;
+ ret = chn_sleep(c, "pcmrd", timeout);
+ if (ret == EWOULDBLOCK) {
+ count -= timeout;
+ ret = 0;
+ } else {
+ count = hz;
+ }
+
+ }
+ }
+ }
+
+ if (count <= 0) {
+ c->flags |= CHN_F_DEAD;
+ printf("%s: record interrupt timeout, channel dead\n", c->name);
+ }
+
+ return ret;
+}
+
+void
+chn_intr(struct pcm_channel *c)
+{
+ CHN_LOCK(c);
+ c->interrupts++;
+ if (c->direction == PCMDIR_PLAY)
+ chn_wrintr(c);
+ else
+ chn_rdintr(c);
+ CHN_UNLOCK(c);
+}
+
+u_int32_t
+chn_start(struct pcm_channel *c, int force)
+{
+ u_int32_t i, j;
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ /* if we're running, or if we're prevented from triggering, bail */
+ if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force))
+ return EINVAL;
+
+ i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs);
+ j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b);
+ if (force || (i >= j)) {
+ c->flags |= CHN_F_TRIGGERED;
+ /*
+ * if we're starting because a vchan started, don't feed any data
+ * or it becomes impossible to start vchans synchronised with the
+ * first one. the hardbuf should be empty so we top it up with
+ * silence to give it something to chew. the real data will be
+ * fed at the first irq.
+ */
+ if (c->direction == PCMDIR_PLAY) {
+ if (SLIST_EMPTY(&c->children))
+ chn_wrfeed(c);
+ else
+ sndbuf_fillsilence(b);
+ }
+ sndbuf_setrun(b, 1);
+ c->xruns = 0;
+ chn_trigger(c, PCMTRIG_START);
+ return 0;
+ }
+
+ return 0;
+}
+
+void
+chn_resetbuf(struct pcm_channel *c)
+{
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ c->blocks = 0;
+ sndbuf_reset(b);
+ sndbuf_reset(bs);
+}
+
+/*
+ * chn_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(struct pcm_channel *c, int threshold)
+{
+ u_long rdy;
+ int ret;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ for (;;) {
+ rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs);
+ if (rdy <= threshold) {
+ ret = chn_sleep(c, "pcmsyn", 1);
+ if (ret == ERESTART || ret == EINTR) {
+ DEB(printf("chn_sync: tsleep returns %d\n", ret));
+ return -1;
+ }
+ } else
+ break;
+ }
+ return 0;
+}
+
+/* called externally, handle locking */
+int
+chn_poll(struct pcm_channel *c, int ev, struct thread *td)
+{
+ struct snd_dbuf *bs = c->bufsoft;
+ int ret;
+
+ CHN_LOCKASSERT(c);
+ if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED))
+ chn_start(c, 1);
+ ret = 0;
+ if (chn_polltrigger(c) && chn_pollreset(c))
+ ret = ev;
+ else
+ selrecord(td, sndbuf_getsel(bs));
+ return ret;
+}
+
+/*
+ * chn_abort terminates a running dma transfer. it may sleep up to 200ms.
+ * it returns the number of bytes that have not been transferred.
+ *
+ * called from: dsp_close, dsp_ioctl, with channel locked
+ */
+int
+chn_abort(struct pcm_channel *c)
+{
+ int missing = 0;
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ if (!(c->flags & CHN_F_TRIGGERED))
+ return 0;
+ c->flags |= CHN_F_ABORTING;
+
+ c->flags &= ~CHN_F_TRIGGERED;
+ /* kill the channel */
+ chn_trigger(c, PCMTRIG_ABORT);
+ sndbuf_setrun(b, 0);
+ if (!(c->flags & CHN_F_VIRTUAL))
+ chn_dmaupdate(c);
+ missing = sndbuf_getready(bs) + sndbuf_getready(b);
+
+ c->flags &= ~CHN_F_ABORTING;
+ 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.
+ *
+ * called from: dsp_close
+ */
+
+int
+chn_flush(struct pcm_channel *c)
+{
+ int ret, count, resid, resid_p;
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ CHN_LOCKASSERT(c);
+ KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel"));
+ DEB(printf("chn_flush c->flags 0x%08x\n", c->flags));
+ if (!(c->flags & CHN_F_TRIGGERED))
+ return 0;
+
+ c->flags |= CHN_F_CLOSING;
+ resid = sndbuf_getready(bs) + sndbuf_getready(b);
+ resid_p = resid;
+ count = 10;
+ ret = 0;
+ while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) {
+ /* still pending output data. */
+ ret = chn_sleep(c, "pcmflu", hz / 10);
+ if (ret == EWOULDBLOCK)
+ ret = 0;
+ if (ret == 0) {
+ resid = sndbuf_getready(bs) + sndbuf_getready(b);
+ if (resid >= resid_p)
+ count--;
+ resid_p = resid;
+ }
+ }
+ if (count == 0)
+ DEB(printf("chn_flush: timeout\n"));
+
+ c->flags &= ~CHN_F_TRIGGERED;
+ /* kill the channel */
+ chn_trigger(c, PCMTRIG_ABORT);
+ sndbuf_setrun(b, 0);
+
+ c->flags &= ~CHN_F_CLOSING;
+ return 0;
+}
+
+int
+fmtvalid(u_int32_t fmt, u_int32_t *fmtlist)
+{
+ int i;
+
+ for (i = 0; fmtlist[i]; i++)
+ if (fmt == fmtlist[i])
+ return 1;
+ return 0;
+}
+
+int
+chn_reset(struct pcm_channel *c, u_int32_t fmt)
+{
+ int hwspd, r;
+
+ CHN_LOCKASSERT(c);
+ c->flags &= CHN_F_RESET;
+ c->interrupts = 0;
+ c->xruns = 0;
+
+ r = CHANNEL_RESET(c->methods, c->devinfo);
+ if (fmt != 0) {
+ hwspd = DSP_DEFAULT_SPEED;
+ /* only do this on a record channel until feederbuilder works */
+ if (c->direction == PCMDIR_REC)
+ RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed);
+ c->speed = hwspd;
+
+ if (r == 0)
+ r = chn_setformat(c, fmt);
+ if (r == 0)
+ r = chn_setspeed(c, hwspd);
+ if (r == 0)
+ r = chn_setvolume(c, 100, 100);
+ }
+ if (r == 0)
+ r = chn_setblocksize(c, 0, 0);
+ if (r == 0) {
+ chn_resetbuf(c);
+ r = CHANNEL_RESETDONE(c->methods, c->devinfo);
+ }
+ return r;
+}
+
+int
+chn_init(struct pcm_channel *c, void *devinfo, int dir)
+{
+ struct feeder_class *fc;
+ struct snd_dbuf *b, *bs;
+ int ret;
+
+ chn_lockinit(c);
+
+ b = NULL;
+ bs = NULL;
+ c->devinfo = NULL;
+ c->feeder = NULL;
+
+ ret = EINVAL;
+ fc = feeder_getclass(NULL);
+ if (fc == NULL)
+ goto out;
+ if (chn_addfeeder(c, fc, NULL))
+ goto out;
+
+ ret = ENOMEM;
+ b = sndbuf_create(c->dev, c->name, "primary");
+ if (b == NULL)
+ goto out;
+ bs = sndbuf_create(c->dev, c->name, "secondary");
+ if (bs == NULL)
+ goto out;
+ sndbuf_setup(bs, NULL, 0);
+ c->bufhard = b;
+ c->bufsoft = bs;
+ c->flags = 0;
+ c->feederflags = 0;
+
+ ret = ENODEV;
+ c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, dir);
+ if (c->devinfo == NULL)
+ goto out;
+
+ ret = ENOMEM;
+ if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0))
+ goto out;
+
+ ret = chn_setdir(c, dir);
+ if (ret)
+ goto out;
+
+ ret = sndbuf_setfmt(b, AFMT_U8);
+ if (ret)
+ goto out;
+
+ ret = sndbuf_setfmt(bs, AFMT_U8);
+ if (ret)
+ goto out;
+
+
+out:
+ if (ret) {
+ if (c->devinfo) {
+ if (CHANNEL_FREE(c->methods, c->devinfo))
+ sndbuf_free(b);
+ }
+ if (bs)
+ sndbuf_destroy(bs);
+ if (b)
+ sndbuf_destroy(b);
+ c->flags |= CHN_F_DEAD;
+ chn_lockdestroy(c);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+int
+chn_kill(struct pcm_channel *c)
+{
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+
+ if (c->flags & CHN_F_TRIGGERED)
+ chn_trigger(c, PCMTRIG_ABORT);
+ while (chn_removefeeder(c) == 0);
+ if (CHANNEL_FREE(c->methods, c->devinfo))
+ sndbuf_free(b);
+ c->flags |= CHN_F_DEAD;
+ sndbuf_destroy(bs);
+ sndbuf_destroy(b);
+ chn_lockdestroy(c);
+ return 0;
+}
+
+int
+chn_setdir(struct pcm_channel *c, int dir)
+{
+ struct snd_dbuf *b = c->bufhard;
+ int r;
+
+ CHN_LOCKASSERT(c);
+ c->direction = dir;
+ r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction);
+ if (!r && SND_DMA(b))
+ sndbuf_dmasetdir(b, c->direction);
+ return r;
+}
+
+int
+chn_setvolume(struct pcm_channel *c, int left, int right)
+{
+ CHN_LOCKASSERT(c);
+ /* could add a feeder for volume changing if channel returns -1 */
+ c->volume = (left << 8) | right;
+ return 0;
+}
+
+static int
+chn_tryspeed(struct pcm_channel *c, int speed)
+{
+ struct pcm_feeder *f;
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+ struct snd_dbuf *x;
+ int r, delta;
+
+ CHN_LOCKASSERT(c);
+ DEB(printf("setspeed, channel %s\n", c->name));
+ DEB(printf("want speed %d, ", speed));
+ if (speed <= 0)
+ return EINVAL;
+ if (CANCHANGE(c)) {
+ r = 0;
+ c->speed = speed;
+ sndbuf_setspd(bs, speed);
+ RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed);
+ DEB(printf("try speed %d, ", speed));
+ sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed));
+ DEB(printf("got speed %d\n", sndbuf_getspd(b)));
+
+ delta = sndbuf_getspd(b) - sndbuf_getspd(bs);
+ if (delta < 0)
+ delta = -delta;
+
+ c->feederflags &= ~(1 << FEEDER_RATE);
+ if (delta > 500)
+ c->feederflags |= 1 << FEEDER_RATE;
+ else
+ sndbuf_setspd(bs, sndbuf_getspd(b));
+
+ r = chn_buildfeeder(c);
+ DEB(printf("r = %d\n", r));
+ if (r)
+ goto out;
+
+ r = chn_setblocksize(c, 0, 0);
+ if (r)
+ goto out;
+
+ if (!(c->feederflags & (1 << FEEDER_RATE)))
+ goto out;
+
+ r = EINVAL;
+ f = chn_findfeeder(c, FEEDER_RATE);
+ DEB(printf("feedrate = %p\n", f));
+ if (f == NULL)
+ goto out;
+
+ x = (c->direction == PCMDIR_REC)? b : bs;
+ r = FEEDER_SET(f, FEEDRATE_SRC, sndbuf_getspd(x));
+ DEB(printf("feeder_set(FEEDRATE_SRC, %d) = %d\n", sndbuf_getspd(x), r));
+ if (r)
+ goto out;
+
+ x = (c->direction == PCMDIR_REC)? bs : b;
+ r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x));
+ DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r));
+out:
+ DEB(printf("setspeed done, r = %d\n", r));
+ return r;
+ } else
+ return EINVAL;
+}
+
+int
+chn_setspeed(struct pcm_channel *c, int speed)
+{
+ int r, oldspeed = c->speed;
+
+ r = chn_tryspeed(c, speed);
+ if (r) {
+ DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed));
+ r = chn_tryspeed(c, oldspeed);
+ }
+ return r;
+}
+
+static int
+chn_tryformat(struct pcm_channel *c, u_int32_t fmt)
+{
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+ int r;
+
+ CHN_LOCKASSERT(c);
+ if (CANCHANGE(c)) {
+ DEB(printf("want format %d\n", fmt));
+ c->format = fmt;
+ r = chn_buildfeeder(c);
+ if (r == 0) {
+ sndbuf_setfmt(bs, c->format);
+ chn_resetbuf(c);
+ r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b));
+ if (r == 0)
+ r = chn_tryspeed(c, c->speed);
+ }
+ return r;
+ } else
+ return EINVAL;
+}
+
+int
+chn_setformat(struct pcm_channel *c, u_int32_t fmt)
+{
+ u_int32_t oldfmt = c->format;
+ int r;
+
+ r = chn_tryformat(c, fmt);
+ if (r) {
+ DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt));
+ chn_tryformat(c, oldfmt);
+ }
+ return r;
+}
+
+int
+chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz)
+{
+ struct snd_dbuf *b = c->bufhard;
+ struct snd_dbuf *bs = c->bufsoft;
+ int bufsz, irqhz, tmp, ret;
+
+ CHN_LOCKASSERT(c);
+ if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED))
+ return EINVAL;
+
+ ret = 0;
+ DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz));
+ if (blksz == 0 || blksz == -1) {
+ if (blksz == -1)
+ c->flags &= ~CHN_F_HAS_SIZE;
+ if (!(c->flags & CHN_F_HAS_SIZE)) {
+ blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / chn_targetirqrate;
+ tmp = 32;
+ while (tmp <= blksz)
+ tmp <<= 1;
+ tmp >>= 1;
+ blksz = tmp;
+ blkcnt = CHN_2NDBUFMAXSIZE / blksz;
+
+ RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2);
+ RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz);
+ DEB(printf("%s: defaulting to (%d, %d)\n", __func__, blkcnt, blksz));
+ } else {
+ blkcnt = sndbuf_getblkcnt(bs);
+ blksz = sndbuf_getblksz(bs);
+ DEB(printf("%s: updating (%d, %d)\n", __func__, blkcnt, blksz));
+ }
+ } else {
+ ret = EINVAL;
+ if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE))
+ goto out;
+ ret = 0;
+ c->flags |= CHN_F_HAS_SIZE;
+ }
+
+ bufsz = blkcnt * blksz;
+
+ ret = sndbuf_remalloc(bs, blkcnt, blksz);
+ if (ret)
+ goto out;
+
+ /* adjust for different hw format/speed */
+ irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / sndbuf_getblksz(bs);
+ DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __func__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz));
+ RANGE(irqhz, 16, 512);
+
+ sndbuf_setblksz(b, (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz);
+
+ /* round down to 2^x */
+ blksz = 32;
+ while (blksz <= sndbuf_getblksz(b))
+ blksz <<= 1;
+ blksz >>= 1;
+
+ /* round down to fit hw buffer size */
+ RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2);
+ DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __func__, blksz, sndbuf_getmaxsize(b)));
+
+ sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz));
+
+ irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b);
+ DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz));
+
+ chn_resetbuf(c);
+out:
+ return ret;
+}
+
+int
+chn_trigger(struct pcm_channel *c, int go)
+{
+ struct snd_dbuf *b = c->bufhard;
+ int ret;
+
+ CHN_LOCKASSERT(c);
+ if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD))
+ sndbuf_dmabounce(b);
+ ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go);
+
+ return ret;
+}
+
+int
+chn_getptr(struct pcm_channel *c)
+{
+ int hwptr;
+ int a = (1 << c->align) - 1;
+
+ CHN_LOCKASSERT(c);
+ hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0;
+ /* don't allow unaligned values in the hwa ptr */
+#if 1
+ hwptr &= ~a ; /* Apply channel align mask */
+#endif
+ hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */
+ return hwptr;
+}
+
+struct pcmchan_caps *
+chn_getcaps(struct pcm_channel *c)
+{
+ CHN_LOCKASSERT(c);
+ return CHANNEL_GETCAPS(c->methods, c->devinfo);
+}
+
+u_int32_t
+chn_getformats(struct pcm_channel *c)
+{
+ u_int32_t *fmtlist, fmts;
+ int i;
+
+ fmtlist = chn_getcaps(c)->fmtlist;
+ fmts = 0;
+ for (i = 0; fmtlist[i]; i++)
+ fmts |= fmtlist[i];
+
+ /* report software-supported formats */
+ if (report_soft_formats)
+ fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U16_LE|AFMT_U16_BE|
+ AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8;
+
+ return fmts;
+}
+
+static int
+chn_buildfeeder(struct pcm_channel *c)
+{
+ struct feeder_class *fc;
+ struct pcm_feederdesc desc;
+ u_int32_t tmp[2], type, flags, hwfmt;
+ int err;
+
+ CHN_LOCKASSERT(c);
+ while (chn_removefeeder(c) == 0);
+ KASSERT((c->feeder == NULL), ("feeder chain not empty"));
+
+ c->align = sndbuf_getalign(c->bufsoft);
+
+ if (SLIST_EMPTY(&c->children)) {
+ fc = feeder_getclass(NULL);
+ KASSERT(fc != NULL, ("can't find root feeder"));
+
+ err = chn_addfeeder(c, fc, NULL);
+ if (err) {
+ DEB(printf("can't add root feeder, err %d\n", err));
+
+ return err;
+ }
+ c->feeder->desc->out = c->format;
+ } else {
+ desc.type = FEEDER_MIXER;
+ desc.in = 0;
+ desc.out = c->format;
+ desc.flags = 0;
+ fc = feeder_getclass(&desc);
+ if (fc == NULL) {
+ DEB(printf("can't find vchan feeder\n"));
+
+ return EOPNOTSUPP;
+ }
+
+ err = chn_addfeeder(c, fc, &desc);
+ if (err) {
+ DEB(printf("can't add vchan feeder, err %d\n", err));
+
+ return err;
+ }
+ }
+ flags = c->feederflags;
+
+ DEB(printf("not mapped, feederflags %x\n", flags));
+
+ for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) {
+ if (flags & (1 << type)) {
+ desc.type = type;
+ desc.in = 0;
+ desc.out = 0;
+ desc.flags = 0;
+ DEB(printf("find feeder type %d, ", type));
+ fc = feeder_getclass(&desc);
+ DEB(printf("got %p\n", fc));
+ if (fc == NULL) {
+ DEB(printf("can't find required feeder type %d\n", type));
+
+ return EOPNOTSUPP;
+ }
+
+ if (c->feeder->desc->out != fc->desc->in) {
+ DEB(printf("build fmtchain from %x to %x: ", c->feeder->desc->out, fc->desc->in));
+ tmp[0] = fc->desc->in;
+ tmp[1] = 0;
+ if (chn_fmtchain(c, tmp) == 0) {
+ DEB(printf("failed\n"));
+
+ return ENODEV;
+ }
+ DEB(printf("ok\n"));
+ }
+
+ err = chn_addfeeder(c, fc, fc->desc);
+ if (err) {
+ DEB(printf("can't add feeder %p, output %x, err %d\n", fc, fc->desc->out, err));
+
+ return err;
+ }
+ DEB(printf("added feeder %p, output %x\n", fc, c->feeder->desc->out));
+ }
+ }
+
+ if (fmtvalid(c->feeder->desc->out, chn_getcaps(c)->fmtlist)) {
+ hwfmt = c->feeder->desc->out;
+ } else {
+ if (c->direction == PCMDIR_REC) {
+ tmp[0] = c->format;
+ tmp[1] = 0;
+ hwfmt = chn_fmtchain(c, tmp);
+ } else {
+#if 0
+ u_int32_t *x = chn_getcaps(c)->fmtlist;
+ printf("acceptable formats for %s:\n", c->name);
+ while (*x) {
+ printf("[%8x] ", *x);
+ x++;
+ }
+#endif
+ hwfmt = chn_fmtchain(c, chn_getcaps(c)->fmtlist);
+ }
+ }
+
+ if (hwfmt == 0)
+ return ENODEV;
+
+ sndbuf_setfmt(c->bufhard, hwfmt);
+
+ return 0;
+}
+
+int
+chn_notify(struct pcm_channel *c, u_int32_t flags)
+{
+ struct pcmchan_children *pce;
+ struct pcm_channel *child;
+ int run;
+
+ if (SLIST_EMPTY(&c->children))
+ return ENODEV;
+
+ run = (c->flags & CHN_F_TRIGGERED)? 1 : 0;
+ /*
+ * if the hwchan is running, we can't change its rate, format or
+ * blocksize
+ */
+ if (run)
+ flags &= CHN_N_VOLUME | CHN_N_TRIGGER;
+
+ if (flags & CHN_N_RATE) {
+ /*
+ * we could do something here, like scan children and decide on
+ * the most appropriate rate to mix at, but we don't for now
+ */
+ }
+ if (flags & CHN_N_FORMAT) {
+ /*
+ * we could do something here, like scan children and decide on
+ * the most appropriate mixer feeder to use, but we don't for now
+ */
+ }
+ if (flags & CHN_N_VOLUME) {
+ /*
+ * we could do something here but we don't for now
+ */
+ }
+ if (flags & CHN_N_BLOCKSIZE) {
+ int blksz;
+ /*
+ * scan the children, find the lowest blocksize and use that
+ * for the hard blocksize
+ */
+ blksz = sndbuf_getmaxsize(c->bufhard) / 2;
+ SLIST_FOREACH(pce, &c->children, link) {
+ child = pce->channel;
+ if (sndbuf_getblksz(child->bufhard) < blksz)
+ blksz = sndbuf_getblksz(child->bufhard);
+ }
+ chn_setblocksize(c, 2, blksz);
+ }
+ if (flags & CHN_N_TRIGGER) {
+ int nrun;
+ /*
+ * scan the children, and figure out if any are running
+ * if so, we need to be running, otherwise we need to be stopped
+ * if we aren't in our target sstate, move to it
+ */
+ nrun = 0;
+ SLIST_FOREACH(pce, &c->children, link) {
+ child = pce->channel;
+ if (child->flags & CHN_F_TRIGGERED)
+ nrun = 1;
+ }
+ if (nrun && !run)
+ chn_start(c, 1);
+ if (!nrun && run)
+ chn_abort(c);
+ }
+ return 0;
+}
OpenPOWER on IntegriCloud