diff options
author | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
---|---|---|
committer | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
commit | f07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch) | |
tree | 12b07c7e578fc1a5f53dbfde2632408491ff2a70 /chips/audio.c | |
download | gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.tar.gz gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.tar.bz2 gnumach-f07a4c844da9f0ecae5bbee1ab94be56505f26f7.zip |
Initial source
Diffstat (limited to 'chips/audio.c')
-rw-r--r-- | chips/audio.c | 733 |
1 files changed, 733 insertions, 0 deletions
diff --git a/chips/audio.c b/chips/audio.c new file mode 100644 index 00000000..00bf2be9 --- /dev/null +++ b/chips/audio.c @@ -0,0 +1,733 @@ +/* + * Mach Operating System + * Copyright (c) 1993 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/*- + * Copyright (c) 1991, 1992 The Regents of the University of California. + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. The name of the Laboratory may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 <audio.h> +#if NAUDIO > 0 + +#include <mach_kdb.h> +#include <platforms.h> + +#include <mach/std_types.h> +#include <machine/machspl.h> +#include <kern/kalloc.h> +#include <kern/sched_prim.h> +#include <chips/busses.h> + +#include <device/device_types.h> +#include <device/io_req.h> +#include <device/ds_routines.h> +#include <device/audio_status.h> /* user interface */ +#include <chips/audio_defs.h> /* chip interface */ +#include <chips/audio_config.h> /* machdep config */ + +#define private static + +/* + * Exported functions and data structures + * [see header file for listing] + */ +int audio_blocksize = DEFBLKSIZE; /* patchable */ +int audio_backlog = 400; /* 50ms in samples */ + +/* + * Software state, per AMD79C30 audio chip. + */ +private +struct audio_softc { + void *hw; /* chip status */ + audio_switch_t *ops; /* chip operations */ + au_io_t *sc_au; /* recv and xmit buffers, etc */ + + + unsigned int sc_wseek; /* timestamp of last frame written */ + unsigned int sc_rseek; /* timestamp of last frame read */ +#if 0 + struct selinfo sc_wsel; /* write selector */ + struct selinfo sc_rsel; /* read selector */ +#endif + +} audio_softc_data[NAUDIO]; + +#define unit_to_softc(u) &audio_softc_data[u] + + +/* forward declarations */ +private int audio_sleep (au_cb_t *cb, int thresh); +private void audio_swintr (struct audio_softc *sc); + +/* + * Audio chip found. + */ +void +audio_attach( + void *hw, /* IN, chip status */ + audio_switch_t *ops, + void **audio_status) /* OUT, audio status */ +{ + register struct audio_softc *sc; + static int next = 0; + + if (next >= NAUDIO) + panic("Please configure more than %d audio devices\n", NAUDIO); + sc = &audio_softc_data[next++]; + + printf(" audio"); + + sc->hw = hw; + sc->ops = ops; + + *audio_status = (void *)sc; +} + + +private int audio_setinfo (struct audio_softc *, audio_info_t *); +private int audio_getinfo (struct audio_softc *, audio_info_t *); + +io_return_t +audio_open( + int unit, + int mode, + io_req_t req) +{ + register struct audio_softc *sc; + register au_io_t *au; + + sc = unit_to_softc(unit); + if (unit > NAUDIO || (!sc->hw)) + return (D_NO_SUCH_DEVICE); + + if (!sc->sc_au) { + sc->sc_au = (au_io_t *) kalloc(sizeof(au_io_t)); + bzero(sc->sc_au, sizeof(au_io_t)); + } + au = sc->sc_au; + + au->au_lowat = audio_blocksize; + au->au_hiwat = AUCB_SIZE - au->au_lowat; + au->au_blksize = audio_blocksize; + au->au_backlog = audio_backlog; + + /* set up read and write blocks and `dead sound' zero value. */ + AUCB_INIT(&au->au_rb); + au->au_rb.cb_thresh = AUCB_SIZE; + AUCB_INIT(&au->au_wb); + au->au_wb.cb_thresh = -1; + + /* nothing read or written yet */ + sc->sc_rseek = 0; + sc->sc_wseek = 0; + + (*sc->ops->init)(sc->hw); + + return (0); +} + +private int +audio_drain( + register au_io_t *au) +{ + register int error; + + while (!AUCB_EMPTY(&au->au_wb)) + if ((error = audio_sleep(&au->au_wb, 0)) != 0) + return (error); + return (0); +} + +/* + * Close an audio chip. + */ +/* ARGSUSED */ +io_return_t +audio_close( + int unit) +{ + register struct audio_softc *sc = unit_to_softc(unit); + register au_cb_t *cb; + register spl_t s; + + /* + * Block until output drains, but allow ^C interrupt. + */ + sc->sc_au->au_lowat = 0; /* avoid excessive wakeups */ + + /* + * If there is pending output, let it drain (unless + * the output is paused). + */ + cb = &sc->sc_au->au_wb; + s = splaudio(); + if (!AUCB_EMPTY(cb) && !cb->cb_pause) + (void)audio_drain(sc->sc_au); + /* + * Disable interrupts, and done. + */ + (*sc->ops->close)(sc->hw); + splx(s); + return (D_SUCCESS); +} + +private int +audio_sleep( + register au_cb_t *cb, + register int thresh) +{ + register spl_t s = splaudio(); + + cb->cb_thresh = thresh; + assert_wait((event_t)cb, TRUE); + splx(s); + thread_block((void (*)()) 0); + return (0); /* XXXX */ +} + +io_return_t +audio_read( + int unit, + io_req_t ior) +{ + register struct audio_softc *sc = unit_to_softc(unit); + register au_cb_t *cb; + register int n, head, taildata; + register int blocksize = sc->sc_au->au_blksize; + io_return_t rc; + unsigned char *data; + + /* + * Allocate read buffer + */ + rc = device_read_alloc(ior, (vm_size_t)ior->io_count); + if (rc != KERN_SUCCESS) + return rc; + data = (unsigned char *) ior->io_data; + ior->io_residual = ior->io_count; + + cb = &sc->sc_au->au_rb; + cb->cb_drops = 0; + sc->sc_rseek = sc->sc_au->au_stamp - AUCB_LEN(cb); + do { + while (AUCB_LEN(cb) < blocksize) { + + if (ior->io_mode & D_NODELAY) + return (D_WOULD_BLOCK); + + if ((rc = audio_sleep(cb, blocksize)) != 0) + return(rc); + } + /* + * The space calculation can only err on the short + * side if an interrupt occurs during processing: + * only cb_tail is altered in the interrupt code. + */ + head = cb->cb_head; + if ((n = AUCB_LEN(cb)) > ior->io_residual) + n = ior->io_residual; + taildata = AUCB_SIZE - head; + + if (n > taildata) { + bcopy(cb->cb_data + head, data, taildata); + bcopy(cb->cb_data, data + taildata, n - taildata); + } else + bcopy(cb->cb_data + head, data, n); + data += n; + ior->io_residual -= n; + + head = AUCB_MOD(head + n); + cb->cb_head = head; + } while (ior->io_residual >= blocksize); + + return (rc); +} + +io_return_t +audio_write( + int unit, + io_req_t ior) +{ + register struct audio_softc *sc = unit_to_softc(unit); + register au_io_t *au = sc->sc_au; + register au_cb_t *cb = &au->au_wb; + register int n, tail, tailspace, first, watermark; + io_return_t rc; + unsigned char *data; + vm_offset_t addr = 0; + + if (!(ior->io_op & IO_INBAND)) { + /* + * Copy out-of-line data into kernel address space. + * Since data is copied as page list, it will be + * accessible. + */ + vm_map_copy_t copy = (vm_map_copy_t) ior->io_data; + kern_return_t kr; + + kr = vm_map_copyout(device_io_map, &addr, copy); + if (kr != KERN_SUCCESS) + return kr; + data = (unsigned char *) addr; + } else + data = (unsigned char *) ior->io_data; + ior->io_residual = ior->io_count; + + rc = D_SUCCESS; + first = 1; + while (ior->io_residual > 0) { + watermark = au->au_hiwat; + while (AUCB_LEN(cb) > watermark) { + + if (ior->io_mode & D_NODELAY) { + rc = D_WOULD_BLOCK; + goto out; + } + + if ((rc = audio_sleep(cb, watermark)) != 0) + goto out; + + watermark = au->au_lowat; + } + /* + * The only value that can change on an interrupt is + * cb->cb_head. We only pull that out once to decide + * how much to write into cb_data; if we lose a race + * and cb_head changes, we will merely be overly + * conservative. For a legitimate time stamp, + * however, we need to synchronize the accesses to + * au_stamp and cb_head at a high ipl below. + */ + tail = cb->cb_tail; + if ((n = (AUCB_SIZE - 1) - AUCB_LEN(cb)) > ior->io_residual) { + n = ior->io_residual; + if (cb->cb_head == tail && + n <= au->au_blksize && + au->au_stamp - sc->sc_wseek > 400) { + /* + * the write is 'small', the buffer is empty + * and we have been silent for at least 50ms + * so we might be dealing with an application + * that writes frames synchronously with + * reading them. If so, we need an output + * backlog to cover scheduling delays or + * there will be gaps in the sound output. + * Also take this opportunity to reset the + * buffer pointers in case we ended up on + * a bad boundary (odd byte, blksize bytes + * from end, etc.). + */ + register unsigned long *ip; + register unsigned long muzero; + spl_t s; + register int i; + + s = splaudio(); + cb->cb_head = cb->cb_tail = 0; + splx(s); + + tail = au->au_backlog; + ip = (unsigned long *)cb->cb_data; + muzero = sample_rpt_long(0x7fL); + for (i = tail / sizeof muzero; --i >= 0; ) + *ip++ = muzero; + } + } + tailspace = AUCB_SIZE - tail; + if (n > tailspace) { + /* write first part at tail and rest at head */ + bcopy(data, cb->cb_data + tail, tailspace); + bcopy(data + tailspace, cb->cb_data, + n - tailspace); + } else + bcopy(data, cb->cb_data + tail, n); + data += n; + ior->io_residual -= n; + + tail = AUCB_MOD(tail + n); + if (first) { + register spl_t s = splaudio(); + sc->sc_wseek = AUCB_LEN(cb) + au->au_stamp + 1; + /* + * To guarantee that a write is contiguous in the + * sample space, we clear the drop count the first + * time through. If we later get drops, we will + * break out of the loop below, before writing + * a new frame. + */ + cb->cb_drops = 0; + cb->cb_tail = tail; + splx(s); + first = 0; + } else { +#if 0 + if (cb->cb_drops != 0) + break; +#endif + cb->cb_tail = tail; + } + } +out: + if (!(ior->io_op & IO_INBAND)) + (void) vm_deallocate(device_io_map, addr, ior->io_count); + return (rc); +} + +#include <sys/ioctl.h> + +io_return_t +audio_get_status( + int unit, + dev_flavor_t flavor, + dev_status_t status, + natural_t *status_count) +{ + register struct audio_softc *sc = unit_to_softc(unit); + register au_io_t *au = sc->sc_au; + io_return_t rc = D_SUCCESS; + spl_t s; + + switch (flavor) { + + case AUDIO_GETMAP: + case AUDIOGETREG: + rc = (*sc->ops->getstate)(sc->hw, flavor, + (void *)status, status_count); + break; + + /* + * Number of read samples dropped. We don't know where or + * when they were dropped. + */ + case AUDIO_RERROR: + *(int *)status = au->au_rb.cb_drops; + *status_count = 1; + break; + + case AUDIO_WERROR: + *(int *)status = au->au_wb.cb_drops; + *status_count = 1; + break; + + /* + * How many samples will elapse until mike hears the first + * sample of what we last wrote? + */ + case AUDIO_WSEEK: + s = splaudio(); + *(unsigned int *)status = sc->sc_wseek - au->au_stamp + + AUCB_LEN(&au->au_rb); + splx(s); + *status_count = 1; + break; + + case AUDIO_GETINFO: + rc = audio_getinfo(sc, (audio_info_t *)status); + *status_count = sizeof(audio_info_t) / sizeof(int); + break; + + default: + rc = D_INVALID_OPERATION; + break; + } + return (rc); +} + +io_return_t +audio_set_status( + int unit, + dev_flavor_t flavor, + dev_status_t status, + natural_t status_count) +{ + register struct audio_softc *sc = unit_to_softc(unit); + register au_io_t *au = sc->sc_au; + io_return_t rc = D_SUCCESS; + spl_t s; + + switch (flavor) { + + case AUDIO_SETMAP: + case AUDIOSETREG: + rc = (*sc->ops->setstate)(sc->hw, flavor, + (void *)status, status_count); + break; + + case AUDIO_FLUSH: + s = splaudio(); + AUCB_INIT(&au->au_rb); + AUCB_INIT(&au->au_wb); + au->au_stamp = 0; + splx(s); + sc->sc_wseek = 0; + sc->sc_rseek = 0; + break; + + case AUDIO_SETINFO: + rc = audio_setinfo(sc, (audio_info_t *)status); + break; + + case AUDIO_DRAIN: + rc = audio_drain(au); + break; + + default: + rc = D_INVALID_OPERATION; + break; + } + return (rc); +} + + +/* + * Interrupt routine + */ +boolean_t +audio_hwintr( + void *status, + unsigned int s_in, + unsigned int *s_out) +{ + register au_io_t *au = ((struct audio_softc *) status)->sc_au; + register au_cb_t *cb; + register int h, t, k; + register boolean_t wakeit = FALSE; + + ++au->au_stamp; + + /* receive incoming data */ + cb = &au->au_rb; + h = cb->cb_head; + t = cb->cb_tail; + k = AUCB_MOD(t + 1); + if (h == k) + cb->cb_drops++; + else if (cb->cb_pause != 0) + cb->cb_pdrops++; + else { + cb->cb_data[t] = s_in; + cb->cb_tail = t = k; + } + if (AUCB_MOD(t - h) >= cb->cb_thresh) { + cb->cb_thresh = AUCB_SIZE; + cb->cb_waking = 1; + wakeit = TRUE; + } + /* send outgoing data */ + cb = &au->au_wb; + h = cb->cb_head; + t = cb->cb_tail; + k = 0; + if (h == t) + cb->cb_drops++; + else if (cb->cb_pause != 0) + cb->cb_pdrops++; + else { + cb->cb_head = h = AUCB_MOD(h + 1); + *s_out = cb->cb_data[h]; + k = 1; + } + if (AUCB_MOD(t - h) <= cb->cb_thresh) { + cb->cb_thresh = -1; + cb->cb_waking = 1; + wakeit = TRUE; + } + if (wakeit) + audio_swintr((struct audio_softc *) status); + return (k == 1); +} + +private void +audio_swintr( + register struct audio_softc *sc) +{ + register au_io_t *au = sc->sc_au; + + if (au->au_rb.cb_waking != 0) { + au->au_rb.cb_waking = 0; + wakeup(&au->au_rb); + } + if (au->au_wb.cb_waking != 0) { + au->au_wb.cb_waking = 0; + wakeup(&au->au_wb); + } +} + +private int +audio_setinfo( + struct audio_softc *sc, + audio_info_t *ai) +{ + struct audio_prinfo *r = &ai->record, *p = &ai->play; + register int bsize; + register au_io_t *au = sc->sc_au; + spl_t s; + + (*sc->ops->setgains)(sc->hw, p->gain, r->gain, ai->monitor_gain ); + + if (p->pause != (unsigned char)~0) + au->au_wb.cb_pause = p->pause; + if (r->pause != (unsigned char)~0) + au->au_rb.cb_pause = r->pause; + + if (p->port != ~0) + (*sc->ops->setport)(sc->hw, p->port); + + if (ai->blocksize != ~0) { + if (ai->blocksize == 0) + bsize = ai->blocksize = DEFBLKSIZE; + else if (ai->blocksize > MAXBLKSIZE) + bsize = ai->blocksize = MAXBLKSIZE; + else + bsize = ai->blocksize; + + s = splaudio(); + au->au_blksize = bsize; + /* AUDIO_FLUSH */ + AUCB_INIT(&au->au_rb); + AUCB_INIT(&au->au_wb); + splx(s); + + } + if (ai->hiwat != ~0 && (unsigned)ai->hiwat < AUCB_SIZE) + au->au_hiwat = ai->hiwat; + if (ai->lowat != ~0 && ai->lowat < AUCB_SIZE) + au->au_lowat = ai->lowat; + if (ai->backlog != ~0 && ai->backlog < (AUCB_SIZE/2)) + au->au_backlog = ai->backlog; + + return (0); +} + +private int +audio_getinfo( + struct audio_softc *sc, + audio_info_t *ai) +{ + struct audio_prinfo *r = &ai->record, *p = &ai->play; + register au_io_t *au = sc->sc_au; + + p->sample_rate = r->sample_rate = 8000; + p->channels = r->channels = 1; + p->precision = r->precision = 8; + p->encoding = r->encoding = AUDIO_ENCODING_ULAW; + + (*sc->ops->getgains)(sc->hw, &p->gain, &r->gain, &ai->monitor_gain ); + + r->port = AUDIO_MIKE; + p->port = (*sc->ops->getport)(sc->hw); + + p->pause = au->au_wb.cb_pause; + r->pause = au->au_rb.cb_pause; + p->error = au->au_wb.cb_drops != 0; + r->error = au->au_rb.cb_drops != 0; + + /* Now this is funny. If you got here it means you must have + opened the device, so how could it possibly be closed ? + Unless we upgrade the berkeley code to check if the chip + is currently playing and/or recording... Later. */ + p->open = TRUE; + r->open = TRUE; + + p->samples = au->au_stamp - au->au_wb.cb_pdrops; + r->samples = au->au_stamp - au->au_rb.cb_pdrops; + + p->seek = sc->sc_wseek; + r->seek = sc->sc_rseek; + + ai->blocksize = au->au_blksize; + ai->hiwat = au->au_hiwat; + ai->lowat = au->au_lowat; + ai->backlog = au->au_backlog; + + return (0); +} + +#if MACH_KDB +#include <ddb/db_output.h> + +void audio_queue_status( au_cb_t *cb, char *logo) +{ + db_printf("%s ring status:\n", logo); + db_printf(" h %x t %x sh %x w %d p %d d %x pd %x\n", + cb->cb_head, cb->cb_tail, cb->cb_thresh, + cb->cb_waking, cb->cb_pause, (long)cb->cb_drops, + (long)cb->cb_pdrops); +} + +int audio_status(int unit) +{ + struct audio_softc *sc = unit_to_softc(unit); + au_io_t *au; + + if (!sc) { + db_printf("No such thing\n"); + return 0; + } + db_printf("@%lx: wseek %d rseek %d, au @%lx\n", + sc, sc->sc_wseek, sc->sc_rseek, sc->sc_au); + if (!(au = sc->sc_au)) return 0; + + db_printf("au: stamp %x lo %x hi %x blk %x blg %x\n", + au->au_stamp, au->au_lowat, au->au_hiwat, + au->au_blksize, au->au_backlog); + audio_queue_status(&au->au_rb, "read"); + audio_queue_status(&au->au_wb, "write"); + + return 0; +} +#endif /* MACH_KDB */ + +#endif /* NAUDIO > 0 */ + |