diff options
author | 1995-10-18 08:37:01 +0000 | |
---|---|---|
committer | 1995-10-18 08:37:01 +0000 | |
commit | df930be708d50e9715f173caa26ffe1b7599b157 (patch) | |
tree | aa317e49e28cb999c9cf3db7f00c20903fe6010a /sys/dev/isa/gus.c | |
download | wireguard-openbsd-df930be708d50e9715f173caa26ffe1b7599b157.tar.xz wireguard-openbsd-df930be708d50e9715f173caa26ffe1b7599b157.zip |
initial import of NetBSD tree
Diffstat (limited to 'sys/dev/isa/gus.c')
-rw-r--r-- | sys/dev/isa/gus.c | 4532 |
1 files changed, 4532 insertions, 0 deletions
diff --git a/sys/dev/isa/gus.c b/sys/dev/isa/gus.c new file mode 100644 index 00000000000..7e846f78275 --- /dev/null +++ b/sys/dev/isa/gus.c @@ -0,0 +1,4532 @@ +/* $NetBSD: gus.c,v 1.2 1995/07/24 05:54:52 cgd Exp $ */ + +/* + * Copyright (c) 1994, 1995 Ken Hornstein. All rights reserved. + * Copyright (c) 1995 John T. Kohl. 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 Ken Hornstein. + * 4. The name of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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. + */ + +/* + * + * TODO: + * . figure out why mixer activity while sound is playing causes problems + * (phantom interrupts?) + * . figure out a better deinterleave strategy that avoids sucking up + * CPU, memory and cache bandwidth. (Maybe a special encoding? + * Maybe use the double-speed sampling/hardware deinterleave trick + * from the GUS SDK?) A 486/33 isn't quite fast enough to keep + * up with 44.1kHz 16-bit stereo output without some drop-outs. + * . use CS4231 for 16-bit sampling, for a-law and mu-law playback. + * . actually test full-duplex sampling(recording) and playback. + */ + +/* + * Gravis UltraSound driver + * + * For more detailed information, see the GUS developers' kit + * available on the net at: + * + * ftp://freedom.nmsu.edu/pub/ultrasound/gravis/util/ + * gusdkXXX.zip (developers' kit--get rev 2.22 or later) + * See ultrawrd.doc inside--it's MS Word (ick), but it's the bible + * + */ + +/* + * The GUS Max has a slightly strange set of connections between the CS4231 + * and the GF1 and the DMA interconnects. It's set up so that the CS4231 can + * be playing while the GF1 is loading patches from the system. + * + * Here's a recreation of the DMA interconnect diagram: + * + * GF1 + * +---------+ digital + * | | record ASIC + * | |--------------+ + * | | | +--------+ + * | | play (dram) | +----+ | | + * | |--------------(------|-\ | | +-+ | + * +---------+ | | >-|----|---|C|--|------ dma chan 1 + * | +---|-/ | | +-+ | + * | | +----+ | | | + * | | +----+ | | | + * +---------+ +-+ +--(---|-\ | | | | + * | | play |8| | | >-|----|----+---|------ dma chan 2 + * | ---C----|--------|/|------(---|-/ | | | + * | ^ |record |1| | +----+ | | + * | | | /----|6|------+ +--------+ + * | ---+----|--/ +-+ + * +---------+ + * CS4231 8-to-16 bit bus conversion, if needed + * + * + * "C" is an optional combiner. + * + */ + +#include "gus.h" +#if NGUS > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/syslog.h> +#include <sys/device.h> +#include <sys/proc.h> +#include <sys/buf.h> +#include <sys/fcntl.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <machine/cpu.h> +#include <machine/pio.h> +#include <machine/cpufunc.h> +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/mulaw.h> + +#include <dev/isa/isavar.h> +#include <dev/isa/isadmavar.h> +#include <i386/isa/icu.h> + +#include <dev/ic/ics2101reg.h> +#include <dev/ic/cs4231reg.h> +#include <dev/ic/ad1848reg.h> +#include <dev/isa/ics2101var.h> +#include <dev/isa/ad1848var.h> +#include "gusreg.h" + +/* + * Software state of a single "voice" on the GUS + */ + +struct gus_voice { + + /* + * Various control bits + */ + + unsigned char voccntl; /* State of voice control register */ + unsigned char volcntl; /* State of volume control register */ + unsigned char pan_pos; /* Position of volume panning (4 bits) */ + int rate; /* Sample rate of voice being played back */ + + /* + * Address of the voice data into the GUS's DRAM. 20 bits each + */ + + u_long start_addr; /* Starting address of voice data loop area */ + u_long end_addr; /* Ending address of voice data loop */ + u_long current_addr; /* Beginning address of voice data + (start playing here) */ + + /* + * linear volume values for the GUS's volume ramp. 0-511 (9 bits). + * These values must be translated into the logarithmic values using + * gus_log_volumes[] + */ + + int start_volume; /* Starting position of volume ramp */ + int current_volume; /* Current position of volume on volume ramp */ + int end_volume; /* Ending position of volume on volume ramp */ +}; + +/* + * Software state of GUS + */ + +struct gus_softc { + struct device sc_dev; /* base device */ + struct isadev sc_id; /* ISA device */ + void *sc_ih; /* interrupt vector */ + + u_short sc_iobase; /* I/O base address */ + u_short sc_irq; /* IRQ used */ + u_short sc_drq; /* DMA channel for play */ + u_short sc_recdrq; /* DMA channel for recording */ + + int sc_flags; /* Various flags about the GUS */ +#define GUS_MIXER_INSTALLED 0x01 /* An ICS mixer is installed */ +#define GUS_LOCKED 0x02 /* GUS is busy doing multi-phase DMA */ +#define GUS_CODEC_INSTALLED 0x04 /* CS4231 installed/MAX */ +#define GUS_PLAYING 0x08 /* GUS is playing a voice */ +#define GUS_DMAOUT_ACTIVE 0x10 /* GUS is busy doing audio DMA */ +#define GUS_DMAIN_ACTIVE 0x20 /* GUS is busy sampling */ +#define GUS_OPEN 0x100 /* GUS is open */ + int sc_dsize; /* Size of GUS DRAM */ + int sc_voices; /* Number of active voices */ + u_char sc_revision; /* Board revision of GUS */ + u_char sc_mixcontrol; /* Value of GUS_MIX_CONTROL register */ + + u_long sc_orate; /* Output sampling rate */ + u_long sc_irate; /* Input sampling rate */ + + int sc_encoding; /* Current data encoding type */ + int sc_precision; /* # of bits of precision */ + int sc_channels; /* Number of active channels */ + int sc_blocksize; /* Current blocksize */ + int sc_chanblocksize; /* Current blocksize for each in-use + channel */ + short sc_nbufs; /* how many on-GUS bufs per-channel */ + short sc_bufcnt; /* how many need to be played */ + void *sc_deintr_buf; /* deinterleave buffer for stereo */ + + int sc_ogain; /* Output gain control */ + u_char sc_out_port; /* Current out port (generic only) */ + u_char sc_in_port; /* keep track of it when no codec */ + + void (*sc_dmaoutintr) __P((void*)); /* DMA completion intr handler */ + void *sc_outarg; /* argument for sc_dmaoutintr() */ + u_char *sc_dmaoutaddr; /* for isa_dmadone */ + u_long sc_gusaddr; /* where did we just put it? */ + int sc_dmaoutcnt; /* for isa_dmadone */ + + void (*sc_dmainintr) __P((void*)); /* DMA completion intr handler */ + void *sc_inarg; /* argument for sc_dmaoutintr() */ + u_char *sc_dmainaddr; /* for isa_dmadone */ + int sc_dmaincnt; /* for isa_dmadone */ + + struct stereo_dma_intr { + void (*intr)__P((void *)); + void *arg; + u_char *buffer; + u_long dmabuf; + int size; + int flags; + } sc_stereo; + + /* + * State information for linear audio layer + */ + + int sc_dmabuf; /* Which ring buffer we're DMA'ing to */ + int sc_playbuf; /* Which ring buffer we're playing */ + + /* + * Voice information array. All voice-specific information is stored + * here + */ + + struct gus_voice sc_voc[32]; /* Voice data for each voice */ + union { + struct ics2101_softc sc_mixer_u; + struct ad1848_softc sc_codec_u; + } u; +#define sc_mixer u.sc_mixer_u +#define sc_codec u.sc_codec_u +}; + +struct ics2101_volume { + u_char left; + u_char right; +}; + +#define HAS_CODEC(sc) ((sc)->sc_flags & GUS_CODEC_INSTALLED) +#define HAS_MIXER(sc) ((sc)->sc_flags & GUS_MIXER_INSTALLED) + +/* + * Mixer devices for ICS2101 + */ +/* MIC IN mute, line in mute, line out mute are first since they can be done + even if no ICS mixer. */ +#define GUSICS_MIC_IN_MUTE 0 +#define GUSICS_LINE_IN_MUTE 1 +#define GUSICS_MASTER_MUTE 2 +#define GUSICS_CD_MUTE 3 +#define GUSICS_DAC_MUTE 4 +#define GUSICS_MIC_IN_LVL 5 +#define GUSICS_LINE_IN_LVL 6 +#define GUSICS_CD_LVL 7 +#define GUSICS_DAC_LVL 8 +#define GUSICS_MASTER_LVL 9 + +#define GUSICS_RECORD_SOURCE 10 + +/* Classes */ +#define GUSICS_INPUT_CLASS 11 +#define GUSICS_OUTPUT_CLASS 12 +#define GUSICS_RECORD_CLASS 13 + +/* + * Mixer & MUX devices for CS4231 + */ +#define GUSMAX_MIX_IN 0 /* input to MUX from mixer output */ +#define GUSMAX_MONO_LVL 1 /* mic input to MUX; + also mono mixer input */ +#define GUSMAX_DAC_LVL 2 /* input to MUX; also mixer input */ +#define GUSMAX_LINE_IN_LVL 3 /* input to MUX; also mixer input */ +#define GUSMAX_CD_LVL 4 /* mixer input only */ +#define GUSMAX_MONITOR_LVL 5 /* digital mix (?) */ +#define GUSMAX_OUT_LVL 6 /* output level. (?) */ +#define GUSMAX_SPEAKER_LVL 7 /* pseudo-device for mute */ +#define GUSMAX_LINE_IN_MUTE 8 /* pre-mixer */ +#define GUSMAX_DAC_MUTE 9 /* pre-mixer */ +#define GUSMAX_CD_MUTE 10 /* pre-mixer */ +#define GUSMAX_MONO_MUTE 11 /* pre-mixer--microphone/mono */ +#define GUSMAX_MONITOR_MUTE 12 /* post-mixer level/mute */ +#define GUSMAX_SPEAKER_MUTE 13 /* speaker mute */ + +#define GUSMAX_REC_LVL 14 /* post-MUX gain */ + +#define GUSMAX_RECORD_SOURCE 15 + +/* Classes */ +#define GUSMAX_INPUT_CLASS 16 +#define GUSMAX_RECORD_CLASS 17 +#define GUSMAX_MONITOR_CLASS 18 +#define GUSMAX_OUTPUT_CLASS 19 + +#ifdef AUDIO_DEBUG +#define GUSPLAYDEBUG /*XXX*/ +extern void Dprintf __P((const char *, ...)); +#define DPRINTF(x) if (gusdebug) Dprintf x +#define DMAPRINTF(x) if (gusdmadebug) Dprintf x +int gusdebug = 0; +int gusdmadebug = 0; +#else +#define DPRINTF(x) +#define DMAPRINTF(x) +#endif +int gus_dostereo = 1; + +#define NDMARECS 2048 +#ifdef GUSPLAYDEBUG +int gusstats = 0; +struct dma_record { + struct timeval tv; + u_long gusaddr; + caddr_t bsdaddr; + u_short count; + u_char channel; + u_char direction; +} dmarecords[NDMARECS]; + +int dmarecord_index = 0; +#endif + +/* + * local routines + */ + +int gusopen __P((dev_t, int)); +void gusclose __P((void *)); +void gusmax_close __P((void *)); +int gusprobe ()/*__P((struct device *, struct device *, void *))*/; +void gusattach __P((struct device *, struct device *, void *)); +int gusintr __P((void *)); +int gus_set_in_gain __P((caddr_t, u_int, u_char)); +int gus_get_in_gain __P((caddr_t)); +int gus_set_out_gain __P((caddr_t, u_int, u_char)); +int gus_get_out_gain __P((caddr_t)); +int gus_set_in_sr __P((void *, u_long)); +u_long gus_get_in_sr __P((void *)); +int gusmax_set_in_sr __P((void *, u_long)); +u_long gusmax_get_in_sr __P((void *)); +int gus_set_out_sr __P((void *, u_long)); +u_long gus_get_out_sr __P((void *)); +int gusmax_set_out_sr __P((void *, u_long)); +u_long gusmax_get_out_sr __P((void *)); +int gus_set_encoding __P((void *, u_int)); +int gus_get_encoding __P((void *)); +int gusmax_set_encoding __P((void *, u_int)); +int gusmax_get_encoding __P((void *)); +int gus_set_precision __P((void *, u_int)); +int gus_get_precision __P((void *)); +int gusmax_set_precision __P((void *, u_int)); +int gusmax_get_precision __P((void *)); +int gus_set_channels __P((void *, int)); +int gus_get_channels __P((void *)); +int gusmax_set_channels __P((void *, int)); +int gusmax_get_channels __P((void *)); +int gus_round_blocksize __P((void *, int)); +int gus_set_out_port __P((void *, int)); +int gus_get_out_port __P((void *)); +int gus_set_in_port __P((void *, int)); +int gus_get_in_port __P((void *)); +int gus_commit_settings __P((void *)); +int gus_dma_output __P((void *, void *, int, void (*)(), void *)); +int gus_dma_input __P((void *, void *, int, void (*)(), void *)); +int gus_halt_out_dma __P((void *)); +int gus_halt_in_dma __P((void *)); +int gus_cont_out_dma __P((void *)); +int gus_cont_in_dma __P((void *)); +int gus_speaker_ctl __P((void *, int)); +int gusmax_set_precision __P((void *, u_int)); +int gusmax_get_precision __P((void *)); +int gusmax_round_blocksize __P((void *, int)); +int gusmax_commit_settings __P((void *)); +int gusmax_dma_output __P((void *, void *, int, void (*)(), void *)); +int gusmax_dma_input __P((void *, void *, int, void (*)(), void *)); +int gusmax_halt_out_dma __P((void *)); +int gusmax_halt_in_dma __P((void *)); +int gusmax_cont_out_dma __P((void *)); +int gusmax_cont_in_dma __P((void *)); +int gusmax_speaker_ctl __P((void *, int)); +int gusmax_set_out_port __P((void *, int)); +int gusmax_get_out_port __P((void *)); +int gusmax_set_in_port __P((void *, int)); +int gusmax_get_in_port __P((void *)); +int gus_getdev __P((void *, struct audio_device *)); + +static void gus_deinterleave __P((struct gus_softc *, void *, int)); +static void gus_expand __P((void *, int, u_char *, int)); +static void gusmax_expand __P((void *, int, u_char *, int)); + +static int gus_mic_ctl __P((void *, int)); +static int gus_linein_ctl __P((void *, int)); +static int gus_test_iobase __P((int)); +static void guspoke __P((int, long, u_char)); +static void gusdmaout __P((struct gus_softc *, int, u_long, caddr_t, int)); +static void gus_init_cs4231 __P((struct gus_softc *)); +static void gus_init_ics2101 __P((struct gus_softc *)); + +static void gus_set_chan_addrs __P((struct gus_softc *)); +static void gusreset __P((struct gus_softc *, int)); +static void gus_set_voices __P((struct gus_softc *, int)); +static void gus_set_volume __P((struct gus_softc *, int, int)); +static void gus_set_samprate __P((struct gus_softc *, int, int)); +static void gus_set_recrate __P((struct gus_softc *, u_long)); +static void gus_start_voice __P((struct gus_softc *, int, int)), + gus_stop_voice __P((struct gus_softc *, int, int)), + gus_set_endaddr __P((struct gus_softc *, int, u_long)), + gus_set_curaddr __P((struct gus_softc *, int, u_long)); +static u_long gus_get_curaddr __P((struct gus_softc *, int)); +static int gus_dmaout_intr __P((struct gus_softc *)); +static void gus_dmaout_dointr __P((struct gus_softc *)); +static void gus_dmaout_timeout __P((void *)); +static int gus_dmain_intr __P((struct gus_softc *)); +static int gus_voice_intr __P((struct gus_softc *)); +static void gus_start_playing __P((struct gus_softc *, int)); +static void gus_continue_playing __P((struct gus_softc *, int)); +static u_char guspeek __P((int, u_long)); +static unsigned long convert_to_16bit(); +static int gus_setfd __P((void *, int)); +static int gus_mixer_set_port __P((void *, mixer_ctrl_t *)); +static int gus_mixer_get_port __P((void *, mixer_ctrl_t *)); +static int gusmax_mixer_set_port __P((void *, mixer_ctrl_t *)); +static int gusmax_mixer_get_port __P((void *, mixer_ctrl_t *)); +static int gus_mixer_query_devinfo __P((void *, mixer_devinfo_t *)); +static int gusmax_mixer_query_devinfo __P((void *, mixer_devinfo_t *)); +static int gus_query_encoding __P((void *, struct audio_encoding *)); + +static void gusics_master_mute __P((struct ics2101_softc *, int)); +static void gusics_dac_mute __P((struct ics2101_softc *, int)); +static void gusics_mic_mute __P((struct ics2101_softc *, int)); +static void gusics_linein_mute __P((struct ics2101_softc *, int)); +static void gusics_cd_mute __P((struct ics2101_softc *, int)); + +/* + * ISA bus driver routines + */ + +struct cfdriver guscd = { + NULL, "gus", gusprobe, gusattach, DV_DULL, sizeof(struct gus_softc) +}; + + +/* + * A mapping from IRQ/DRQ values to the values used in the GUS's internal + * registers. A zero means that the referenced IRQ/DRQ is invalid + */ + +static int gus_irq_map[] = { 0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7 }; +static int gus_drq_map[] = { 0, 1, 0, 2, 0, 3, 4, 5 }; + +/* + * A list of valid base addresses for the GUS + */ + +static u_short gus_base_addrs[] = { 0x210, 0x220, 0x230, 0x240, 0x250, 0x260 }; +static int gus_addrs = sizeof(gus_base_addrs) / sizeof(u_short); + +/* + * Maximum frequency values of the GUS based on the number of currently active + * voices. Since the GUS samples a voice every 1.6 us, the maximum frequency + * is dependent on the number of active voices. Yes, it is pretty weird. + */ + +static int gus_max_frequency[] = { + 44100, /* 14 voices */ + 41160, /* 15 voices */ + 38587, /* 16 voices */ + 36317, /* 17 voices */ + 34300, /* 18 voices */ + 32494, /* 19 voices */ + 30870, /* 20 voices */ + 29400, /* 21 voices */ + 28063, /* 22 voices */ + 26843, /* 23 voices */ + 25725, /* 24 voices */ + 24696, /* 25 voices */ + 23746, /* 26 voices */ + 22866, /* 27 voices */ + 22050, /* 28 voices */ + 21289, /* 29 voices */ + 20580, /* 30 voices */ + 19916, /* 31 voices */ + 19293 /* 32 voices */ +}; +/* + * A mapping of linear volume levels to the logarithmic volume values used + * by the GF1 chip on the GUS. From GUS SDK vol1.c. + */ + +static unsigned short gus_log_volumes[512] = { + 0x0000, + 0x0700, 0x07ff, 0x0880, 0x08ff, 0x0940, 0x0980, 0x09c0, 0x09ff, 0x0a20, + 0x0a40, 0x0a60, 0x0a80, 0x0aa0, 0x0ac0, 0x0ae0, 0x0aff, 0x0b10, 0x0b20, + 0x0b30, 0x0b40, 0x0b50, 0x0b60, 0x0b70, 0x0b80, 0x0b90, 0x0ba0, 0x0bb0, + 0x0bc0, 0x0bd0, 0x0be0, 0x0bf0, 0x0bff, 0x0c08, 0x0c10, 0x0c18, 0x0c20, + 0x0c28, 0x0c30, 0x0c38, 0x0c40, 0x0c48, 0x0c50, 0x0c58, 0x0c60, 0x0c68, + 0x0c70, 0x0c78, 0x0c80, 0x0c88, 0x0c90, 0x0c98, 0x0ca0, 0x0ca8, 0x0cb0, + 0x0cb8, 0x0cc0, 0x0cc8, 0x0cd0, 0x0cd8, 0x0ce0, 0x0ce8, 0x0cf0, 0x0cf8, + 0x0cff, 0x0d04, 0x0d08, 0x0d0c, 0x0d10, 0x0d14, 0x0d18, 0x0d1c, 0x0d20, + 0x0d24, 0x0d28, 0x0d2c, 0x0d30, 0x0d34, 0x0d38, 0x0d3c, 0x0d40, 0x0d44, + 0x0d48, 0x0d4c, 0x0d50, 0x0d54, 0x0d58, 0x0d5c, 0x0d60, 0x0d64, 0x0d68, + 0x0d6c, 0x0d70, 0x0d74, 0x0d78, 0x0d7c, 0x0d80, 0x0d84, 0x0d88, 0x0d8c, + 0x0d90, 0x0d94, 0x0d98, 0x0d9c, 0x0da0, 0x0da4, 0x0da8, 0x0dac, 0x0db0, + 0x0db4, 0x0db8, 0x0dbc, 0x0dc0, 0x0dc4, 0x0dc8, 0x0dcc, 0x0dd0, 0x0dd4, + 0x0dd8, 0x0ddc, 0x0de0, 0x0de4, 0x0de8, 0x0dec, 0x0df0, 0x0df4, 0x0df8, + 0x0dfc, 0x0dff, 0x0e02, 0x0e04, 0x0e06, 0x0e08, 0x0e0a, 0x0e0c, 0x0e0e, + 0x0e10, 0x0e12, 0x0e14, 0x0e16, 0x0e18, 0x0e1a, 0x0e1c, 0x0e1e, 0x0e20, + 0x0e22, 0x0e24, 0x0e26, 0x0e28, 0x0e2a, 0x0e2c, 0x0e2e, 0x0e30, 0x0e32, + 0x0e34, 0x0e36, 0x0e38, 0x0e3a, 0x0e3c, 0x0e3e, 0x0e40, 0x0e42, 0x0e44, + 0x0e46, 0x0e48, 0x0e4a, 0x0e4c, 0x0e4e, 0x0e50, 0x0e52, 0x0e54, 0x0e56, + 0x0e58, 0x0e5a, 0x0e5c, 0x0e5e, 0x0e60, 0x0e62, 0x0e64, 0x0e66, 0x0e68, + 0x0e6a, 0x0e6c, 0x0e6e, 0x0e70, 0x0e72, 0x0e74, 0x0e76, 0x0e78, 0x0e7a, + 0x0e7c, 0x0e7e, 0x0e80, 0x0e82, 0x0e84, 0x0e86, 0x0e88, 0x0e8a, 0x0e8c, + 0x0e8e, 0x0e90, 0x0e92, 0x0e94, 0x0e96, 0x0e98, 0x0e9a, 0x0e9c, 0x0e9e, + 0x0ea0, 0x0ea2, 0x0ea4, 0x0ea6, 0x0ea8, 0x0eaa, 0x0eac, 0x0eae, 0x0eb0, + 0x0eb2, 0x0eb4, 0x0eb6, 0x0eb8, 0x0eba, 0x0ebc, 0x0ebe, 0x0ec0, 0x0ec2, + 0x0ec4, 0x0ec6, 0x0ec8, 0x0eca, 0x0ecc, 0x0ece, 0x0ed0, 0x0ed2, 0x0ed4, + 0x0ed6, 0x0ed8, 0x0eda, 0x0edc, 0x0ede, 0x0ee0, 0x0ee2, 0x0ee4, 0x0ee6, + 0x0ee8, 0x0eea, 0x0eec, 0x0eee, 0x0ef0, 0x0ef2, 0x0ef4, 0x0ef6, 0x0ef8, + 0x0efa, 0x0efc, 0x0efe, 0x0eff, 0x0f01, 0x0f02, 0x0f03, 0x0f04, 0x0f05, + 0x0f06, 0x0f07, 0x0f08, 0x0f09, 0x0f0a, 0x0f0b, 0x0f0c, 0x0f0d, 0x0f0e, + 0x0f0f, 0x0f10, 0x0f11, 0x0f12, 0x0f13, 0x0f14, 0x0f15, 0x0f16, 0x0f17, + 0x0f18, 0x0f19, 0x0f1a, 0x0f1b, 0x0f1c, 0x0f1d, 0x0f1e, 0x0f1f, 0x0f20, + 0x0f21, 0x0f22, 0x0f23, 0x0f24, 0x0f25, 0x0f26, 0x0f27, 0x0f28, 0x0f29, + 0x0f2a, 0x0f2b, 0x0f2c, 0x0f2d, 0x0f2e, 0x0f2f, 0x0f30, 0x0f31, 0x0f32, + 0x0f33, 0x0f34, 0x0f35, 0x0f36, 0x0f37, 0x0f38, 0x0f39, 0x0f3a, 0x0f3b, + 0x0f3c, 0x0f3d, 0x0f3e, 0x0f3f, 0x0f40, 0x0f41, 0x0f42, 0x0f43, 0x0f44, + 0x0f45, 0x0f46, 0x0f47, 0x0f48, 0x0f49, 0x0f4a, 0x0f4b, 0x0f4c, 0x0f4d, + 0x0f4e, 0x0f4f, 0x0f50, 0x0f51, 0x0f52, 0x0f53, 0x0f54, 0x0f55, 0x0f56, + 0x0f57, 0x0f58, 0x0f59, 0x0f5a, 0x0f5b, 0x0f5c, 0x0f5d, 0x0f5e, 0x0f5f, + 0x0f60, 0x0f61, 0x0f62, 0x0f63, 0x0f64, 0x0f65, 0x0f66, 0x0f67, 0x0f68, + 0x0f69, 0x0f6a, 0x0f6b, 0x0f6c, 0x0f6d, 0x0f6e, 0x0f6f, 0x0f70, 0x0f71, + 0x0f72, 0x0f73, 0x0f74, 0x0f75, 0x0f76, 0x0f77, 0x0f78, 0x0f79, 0x0f7a, + 0x0f7b, 0x0f7c, 0x0f7d, 0x0f7e, 0x0f7f, 0x0f80, 0x0f81, 0x0f82, 0x0f83, + 0x0f84, 0x0f85, 0x0f86, 0x0f87, 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c, + 0x0f8d, 0x0f8e, 0x0f8f, 0x0f90, 0x0f91, 0x0f92, 0x0f93, 0x0f94, 0x0f95, + 0x0f96, 0x0f97, 0x0f98, 0x0f99, 0x0f9a, 0x0f9b, 0x0f9c, 0x0f9d, 0x0f9e, + 0x0f9f, 0x0fa0, 0x0fa1, 0x0fa2, 0x0fa3, 0x0fa4, 0x0fa5, 0x0fa6, 0x0fa7, + 0x0fa8, 0x0fa9, 0x0faa, 0x0fab, 0x0fac, 0x0fad, 0x0fae, 0x0faf, 0x0fb0, + 0x0fb1, 0x0fb2, 0x0fb3, 0x0fb4, 0x0fb5, 0x0fb6, 0x0fb7, 0x0fb8, 0x0fb9, + 0x0fba, 0x0fbb, 0x0fbc, 0x0fbd, 0x0fbe, 0x0fbf, 0x0fc0, 0x0fc1, 0x0fc2, + 0x0fc3, 0x0fc4, 0x0fc5, 0x0fc6, 0x0fc7, 0x0fc8, 0x0fc9, 0x0fca, 0x0fcb, + 0x0fcc, 0x0fcd, 0x0fce, 0x0fcf, 0x0fd0, 0x0fd1, 0x0fd2, 0x0fd3, 0x0fd4, + 0x0fd5, 0x0fd6, 0x0fd7, 0x0fd8, 0x0fd9, 0x0fda, 0x0fdb, 0x0fdc, 0x0fdd, + 0x0fde, 0x0fdf, 0x0fe0, 0x0fe1, 0x0fe2, 0x0fe3, 0x0fe4, 0x0fe5, 0x0fe6, + 0x0fe7, 0x0fe8, 0x0fe9, 0x0fea, 0x0feb, 0x0fec, 0x0fed, 0x0fee, 0x0fef, + 0x0ff0, 0x0ff1, 0x0ff2, 0x0ff3, 0x0ff4, 0x0ff5, 0x0ff6, 0x0ff7, 0x0ff8, + 0x0ff9, 0x0ffa, 0x0ffb, 0x0ffc, 0x0ffd, 0x0ffe, 0x0fff}; + +#define SELECT_GUS_REG(port,x) outb(port+GUS_REG_SELECT,x) +#define WHICH_GUS_REG(port) inb(port+GUS_REG_SELECT) +#define ADDR_HIGH(x) (unsigned int) ((x >> 7L) & 0x1fffL) +#define ADDR_LOW(x) (unsigned int) ((x & 0x7fL) << 9L) + +#define GUS_MIN_VOICES 14 /* Minimum possible number of voices */ +#define GUS_MAX_VOICES 32 /* Maximum possible number of voices */ +#define GUS_VOICE_LEFT 0 /* Voice used for left (and mono) playback */ +#define GUS_VOICE_RIGHT 1 /* Voice used for right playback */ +#define GUS_MEM_OFFSET 32 /* Offset into GUS memory to begin of buffer */ +#define GUS_BUFFER_MULTIPLE 1024 /* Audio buffers are multiples of this */ +#define GUS_MEM_FOR_BUFFERS 131072 /* use this many bytes on-GUS */ +#define GUS_LEFT_RIGHT_OFFSET (sc->sc_nbufs * sc->sc_chanblocksize + GUS_MEM_OFFSET) + +#define GUS_PREC_BYTES (sc->sc_precision >> 3) /* precision to bytes */ + +/* splgus() must be splaudio() */ + +#define splgus splaudio + +/* + * Interface to higher level audio driver + */ + +struct audio_hw_if gus_hw_if = { + gusopen, + gusclose, + NULL, /* drain */ + gus_set_in_sr, + gus_get_in_sr, + gus_set_out_sr, + gus_get_out_sr, + + gus_query_encoding, + gus_set_encoding, + gus_get_encoding, + + gus_set_precision, + gus_get_precision, + + gus_set_channels, + gus_get_channels, + + gus_round_blocksize, + + gus_set_out_port, + gus_get_out_port, + gus_set_in_port, + gus_get_in_port, + + gus_commit_settings, + + ad1848_get_silence, + + gus_expand, + mulaw_compress, + + gus_dma_output, + gus_dma_input, + gus_halt_out_dma, + gus_halt_in_dma, + gus_cont_out_dma, + gus_cont_in_dma, + + gus_speaker_ctl, + + gus_getdev, + gus_setfd, + gus_mixer_set_port, + gus_mixer_get_port, + gus_mixer_query_devinfo, + 1, /* full-duplex */ + 0, +}; + + +/* + * Some info about the current audio device + */ + +struct audio_device gus_device = { + "UltraSound", + "", + "gus", +}; + +#define FLIP_REV 5 /* This rev has flipped mixer chans */ + + +int +gusprobe(parent, self, aux) + struct device *parent, *self; + void *aux; +{ + register struct gus_softc *sc = (void *) self; + register struct isa_attach_args *ia = aux; + struct cfdata *cf = sc->sc_dev.dv_cfdata; + register int iobase = ia->ia_iobase; + u_short recdrq = cf->cf_flags; + + int i; + unsigned char s1, s2; + + /* + * Before we do anything else, make sure requested IRQ and DRQ are + * valid for this card. + */ + + if (! gus_irq_map[ia->ia_irq]) { + printf("gus: invalid irq %d, card not probed\n", ia->ia_irq); + return(0); + } + + if (! gus_drq_map[ia->ia_drq]) { + printf("gus: invalid drq %d, card not probed\n", ia->ia_drq); + return(0); + } + + if (recdrq != 0x00) { + if (recdrq > 7 || ! gus_drq_map[recdrq]) { + printf("gus: invalid flag given for second DMA channel (0x%x), card not probed\n", recdrq); + return(0); + } + } else + recdrq = ia->ia_drq; + + if (iobase == IOBASEUNK) { + int i; + for(i = 0; i < gus_addrs; i++) + if (gus_test_iobase(gus_base_addrs[i])) { + iobase = gus_base_addrs[i]; + goto done; + } + return 0; + } else if (! gus_test_iobase(iobase)) + return 0; + +done: + sc->sc_iobase = iobase; + sc->sc_irq = ia->ia_irq; + sc->sc_drq = ia->ia_drq; + sc->sc_recdrq = recdrq; + + ia->ia_iobase = sc->sc_iobase; + ia->ia_iosize = 16; /* XXX */ + return(1); +} + +/* + * Test to see if a particular I/O base is valid for the GUS. Return true + * if it is. + */ + +static int +gus_test_iobase (int iobase) +{ + int i = splgus(); + u_char s1, s2; + + /* + * Reset GUS to an initial state before we do anything. + */ + + delay(500); + + SELECT_GUS_REG(iobase, GUSREG_RESET); + outb(iobase+GUS_DATA_HIGH, 0x00); + + delay(500); + + SELECT_GUS_REG(iobase, GUSREG_RESET); + outb(iobase+GUS_DATA_HIGH, GUSMASK_MASTER_RESET); + + delay(500); + + splx(i); + + /* + * See if we can write to the board's memory + */ + + s1 = guspeek(iobase, 0L); + s2 = guspeek(iobase, 1L); + + guspoke(iobase, 0L, 0xaa); + guspoke(iobase, 1L, 0x55); + + if ((i=(int)guspeek(iobase, 0L)) != 0xaa) { + return(0); + } + + guspoke(iobase, 0L, s1); + guspoke(iobase, 1L, s2); + + return 1; +} + +/* + * Setup the GUS for use; called shortly after probe + */ + +void +gusattach(parent, self, aux) + struct device *parent, *self; + void *aux; +{ + register struct gus_softc *sc = (void *) self; + register struct isa_attach_args *ia = aux; + register u_short port = ia->ia_iobase; + int s,i; + register unsigned char c,d,m; + + /* + * Figure out our board rev, and see if we need to initialize the + * mixer + */ + + delay(500); + + c = inb(port+GUS_BOARD_REV); + if (c != 0xff) + sc->sc_revision = c; + else + sc->sc_revision = 0; + + + SELECT_GUS_REG(port, GUSREG_RESET); + outb(port+GUS_DATA_HIGH, 0x00); + + gusreset(sc, GUS_MAX_VOICES); /* initialize all voices */ + gusreset(sc, GUS_MIN_VOICES); /* then set to just the ones we use */ + + /* + * Setup the IRQ and DRQ lines in software, using values from + * config file + */ + + m = GUSMASK_LINE_IN|GUSMASK_LINE_OUT; /* disable all */ + + c = ((unsigned char) gus_irq_map[ia->ia_irq]) | GUSMASK_BOTH_RQ; + + if (sc->sc_recdrq == sc->sc_drq) + d = (unsigned char) (gus_drq_map[sc->sc_drq] | + GUSMASK_BOTH_RQ); + else + d = (unsigned char) (gus_drq_map[sc->sc_drq] | + gus_drq_map[sc->sc_recdrq] << 3); + + /* + * Program the IRQ and DMA channels on the GUS. Note that we hardwire + * the GUS to only use one IRQ channel, but we give the user the + * option of using two DMA channels (the other one given by the flags + * option in the config file). Two DMA channels are needed for full- + * duplex operation. + * + * The order of these operations is very magical. + */ + + disable_intr(); + + outb(port+GUS_REG_CONTROL, GUS_REG_IRQCTL); + outb(port+GUS_MIX_CONTROL, m); + outb(port+GUS_IRQCTL_CONTROL, 0x00); + outb(port+0x0f, 0x00); + + outb(port+GUS_MIX_CONTROL, m); + outb(port+GUS_DMA_CONTROL, d | 0x80); /* magic reset? */ + + outb(port+GUS_MIX_CONTROL, m | GUSMASK_CONTROL_SEL); + outb(port+GUS_IRQ_CONTROL, c); + + outb(port+GUS_MIX_CONTROL, m); + outb(port+GUS_DMA_CONTROL, d); + + outb(port+GUS_MIX_CONTROL, m | GUSMASK_CONTROL_SEL); + outb(port+GUS_IRQ_CONTROL, c); + + outb(port+GUS_VOICE_SELECT, 0x00); + + /* enable line in, line out. leave mic disabled. */ + outb(port+GUS_MIX_CONTROL, + (m | GUSMASK_LATCHES) & ~(GUSMASK_LINE_OUT|GUSMASK_LINE_IN)); + outb(port+GUS_VOICE_SELECT, 0x00); + + enable_intr(); + + sc->sc_mixcontrol = + (m | GUSMASK_LATCHES) & ~(GUSMASK_LINE_OUT|GUSMASK_LINE_IN); + + + if (sc->sc_revision >= 5 && sc->sc_revision <= 9) { + sc->sc_flags |= GUS_MIXER_INSTALLED; + gus_init_ics2101(sc); + } + if (sc->sc_revision >= 0xa) { + gus_init_cs4231(sc); + } + + SELECT_GUS_REG(port, GUSREG_RESET); + /* + * Check to see how much memory we have on this card; see if any + * "mirroring" occurs. We're assuming at least 256K already exists + * on the card; otherwise the initial probe would have failed + */ + + guspoke(port, 0L, 0x00); + for(i = 1; i < 1024; i++) { + unsigned long loc; + unsigned char val; + + /* + * See if we've run into mirroring yet + */ + + if (guspeek(port, 0L) != 0) + break; + + loc = i << 10; + + guspoke(port, loc, 0xaa); + if (guspeek(port, loc) != 0xaa) + break; + } + + sc->sc_dsize = i; + sprintf(gus_device.version, "3.%d", sc->sc_revision); + + printf("\n <Gravis UltraSound version 3.%d, %dKB DRAM, ", + sc->sc_revision, sc->sc_dsize); + if (HAS_MIXER(sc)) + printf("ICS2101 mixer, "); + if (HAS_CODEC(sc)) + printf("%s codec/mixer, ", sc->sc_codec.chip_name); + if (sc->sc_recdrq == sc->sc_drq) { + printf("half-duplex"); + gus_hw_if.full_duplex = 0; + } else { + printf("full-duplex, record drq %d", sc->sc_recdrq); + gus_hw_if.full_duplex = 1; + } + + printf(">\n"); + + /* + * Setup a default interrupt handler + */ + + /* XXX we shouldn't have to use splgus == splclock, nor should + * we use ISA_IPL_CLOCK. + */ + sc->sc_ih = isa_intr_establish(ia->ia_irq, ISA_IST_EDGE, ISA_IPL_AUDIO, + gusintr, sc /* sc->sc_gusdsp */); + + /* + * Set some default values + */ + + sc->sc_irate = sc->sc_orate = 44100; + sc->sc_encoding = AUDIO_ENCODING_LINEAR; + sc->sc_precision = 16; + sc->sc_voc[GUS_VOICE_LEFT].voccntl |= GUSMASK_DATA_SIZE16; + sc->sc_voc[GUS_VOICE_RIGHT].voccntl |= GUSMASK_DATA_SIZE16; + sc->sc_channels = 1; + sc->sc_ogain = 340; + gus_commit_settings(sc); + + /* + * We always put the left channel full left & right channel + * full right. + * For mono playback, we set up both voices playing the same buffer. + */ + outb(sc->sc_iobase+GUS_VOICE_SELECT, (unsigned char) GUS_VOICE_LEFT); + SELECT_GUS_REG(sc->sc_iobase, GUSREG_PAN_POS); + outb(sc->sc_iobase+GUS_DATA_HIGH, GUS_PAN_FULL_LEFT); + + outb(sc->sc_iobase+GUS_VOICE_SELECT, (unsigned char) GUS_VOICE_RIGHT); + SELECT_GUS_REG(sc->sc_iobase, GUSREG_PAN_POS); + outb(sc->sc_iobase+GUS_DATA_HIGH, GUS_PAN_FULL_RIGHT); + + /* + * Attach to the generic audio layer + */ + + if (audio_hardware_attach(&gus_hw_if, HAS_CODEC(sc) ? (void *)&sc->sc_codec : (void *)sc) != 0) + printf("gus: could not attach to audio pseudo-device driver\n"); +} + +int +gusopen(dev, flags) + dev_t dev; + int flags; +{ + int unit = AUDIOUNIT(dev); + struct gus_softc *sc; + + DPRINTF(("gusopen() called\n")); + + if (unit >= guscd.cd_ndevs) + return ENXIO; + sc = guscd.cd_devs[unit]; + if (!sc) + return ENXIO; + + if (sc->sc_flags & GUS_OPEN) + return EBUSY; + + /* + * Some initialization + */ + + sc->sc_flags |= GUS_OPEN; + sc->sc_dmabuf = 0; + sc->sc_playbuf = -1; + sc->sc_bufcnt = 0; + sc->sc_voc[GUS_VOICE_LEFT].start_addr = GUS_MEM_OFFSET - 1; + sc->sc_voc[GUS_VOICE_LEFT].current_addr = GUS_MEM_OFFSET; + + if (HAS_CODEC(sc)) { + ad1848_open(&sc->sc_codec, dev, flags); + sc->sc_codec.aux1_mute = 0; + ad1848_mute_aux1(&sc->sc_codec, 0); /* turn on DAC output */ + if (flags & FREAD) { + sc->sc_codec.mono_mute = 0; + cs4231_mute_mono(&sc->sc_codec, 0); + } + } else if (flags & FREAD) { + /* enable/unmute the microphone */ + if (HAS_MIXER(sc)) { + gusics_mic_mute(&sc->sc_mixer, 0); + } else + gus_mic_ctl(sc, SPKR_ON); + } + if (sc->sc_nbufs == 0) + gus_round_blocksize(sc, GUS_BUFFER_MULTIPLE); /* default blksiz */ + return 0; +} + +static void +gusmax_expand(hdl, encoding, buf, count) + void *hdl; + int encoding; + u_char *buf; + int count; +{ + register struct ad1848_softc *ac = hdl; + + gus_expand(ac->parent, encoding, buf, count); +} + +static void +gus_expand(hdl, encoding, buf, count) + void *hdl; + int encoding; + u_char *buf; + int count; +{ + struct gus_softc *sc = hdl; + + mulaw_expand(NULL, encoding, buf, count); + /* + * If we need stereo deinterleaving, do it now. + */ + if (sc->sc_channels == 2) + gus_deinterleave(sc, (void *)buf, count); +} + +static void +gus_deinterleave(sc, buf, size) + register struct gus_softc *sc; + void *buf; + int size; +{ + /* deinterleave the stereo data. We can use sc->sc_deintr_buf + for scratch space. */ + register int i; + + /* + * size is in bytes. + */ + if (sc->sc_precision == 16) { + register u_short *dei = sc->sc_deintr_buf; + register u_short *sbuf = buf; + size >>= 1; /* bytecnt to shortcnt */ + /* copy 2nd of each pair of samples to the staging area, while + compacting the 1st of each pair into the original area. */ + for (i = 0; i < size/2-1; i++) { + dei[i] = sbuf[i*2+1]; + sbuf[i+1] = sbuf[i*2+2]; + } + /* + * this has copied one less sample than half of the + * buffer. The first sample of the 1st stream was + * already in place and didn't need copying. + * Therefore, we've moved all of the 1st stream's + * samples into place. We have one sample from 2nd + * stream in the last slot of original area, not + * copied to the staging area (But we don't need to!). + * Copy the remainder of the original stream into place. + */ + bcopy(dei, &sbuf[size/2], i * sizeof(short)); + } else { + register u_char *dei = sc->sc_deintr_buf; + register u_char *sbuf = buf; + for (i = 0; i < size/2-1; i++) { + dei[i] = sbuf[i*2+1]; + sbuf[i+1] = sbuf[i*2+2]; + } + bcopy(dei, &sbuf[size/2], i); + } +} + +/* + * Actually output a buffer to the DSP chip + */ + +int +gusmax_dma_output(addr, buf, size, intr, arg) + void * addr; + void *buf; + int size; + void (*intr)(); + void *arg; +{ + register struct ad1848_softc *ac = addr; + return gus_dma_output(ac->parent, buf, size, intr, arg); +} + +/* + * called at splgus() from interrupt handler. + */ +void +stereo_dmaintr(void *arg) +{ + struct gus_softc *sc = arg; + struct stereo_dma_intr *sa = &sc->sc_stereo; + + DMAPRINTF(("stereo_dmaintr")); + + /* + * Put other half in its place, then call the real interrupt routine :) + */ + + sc->sc_dmaoutintr = sa->intr; + sc->sc_outarg = sa->arg; + +#ifdef GUSPLAYDEBUG + if (gusstats) { + microtime(&dmarecords[dmarecord_index].tv); + dmarecords[dmarecord_index].gusaddr = sa->dmabuf; + dmarecords[dmarecord_index].bsdaddr = sa->buffer; + dmarecords[dmarecord_index].count = sa->size; + dmarecords[dmarecord_index].channel = 1; + dmarecords[dmarecord_index].direction = 1; + dmarecord_index = ++dmarecord_index % NDMARECS; + } +#endif + + gusdmaout(sc, sa->flags, sa->dmabuf, (caddr_t) sa->buffer, sa->size); + + sa->flags = 0; + sa->dmabuf = 0; + sa->buffer = 0; + sa->size = 0; + sa->intr = 0; + sa->arg = 0; +} + +/* + * Start up DMA output to the card. + * Called at splgus/splaudio already, either from intr handler or from + * generic audio code. + */ +int +gus_dma_output(addr, buf, size, intr, arg) + void * addr; + void *buf; + int size; + void (*intr)(); + void *arg; +{ + struct gus_softc *sc = addr; + u_char *buffer = buf; + u_long boarddma; + int i, flags; + + DMAPRINTF(("gus_dma_output %d @ %x\n", size, buf)); + + if (size != sc->sc_blocksize) { + DPRINTF(("gus_dma_output reqsize %d not sc_blocksize %d\n", + size, sc->sc_blocksize)); + return EINVAL; + } + + flags = GUSMASK_DMA_WRITE; + if (sc->sc_precision == 16) + flags |= GUSMASK_DMA_DATA_SIZE; + /* pcm16 is signed, mulaw & pcm8 are unsigned */ + if (sc->sc_encoding == AUDIO_ENCODING_ULAW || + sc->sc_encoding == AUDIO_ENCODING_PCM8) + flags |= GUSMASK_DMA_INVBIT; + + if (sc->sc_channels == 2) { + if (sc->sc_precision == 16) { + if (size & 3) { + DPRINTF(("gus_dma_output: unpaired 16bit samples")); + size &= 3; + } + } else if (size & 1) { + DPRINTF(("gus_dma_output: unpaired samples")); + size &= 1; + } + if (size == 0) + return 0; + size >>= 1; + + boarddma = size * sc->sc_dmabuf + GUS_MEM_OFFSET; + + sc->sc_stereo.intr = intr; + sc->sc_stereo.arg = arg; + sc->sc_stereo.size = size; + sc->sc_stereo.dmabuf = boarddma + GUS_LEFT_RIGHT_OFFSET; + sc->sc_stereo.buffer = buffer + size; + sc->sc_stereo.flags = flags; + if (gus_dostereo) { + intr = stereo_dmaintr; + arg = sc; + } + } else + boarddma = size * sc->sc_dmabuf + GUS_MEM_OFFSET; + + + sc->sc_flags |= GUS_LOCKED; + sc->sc_dmaoutintr = intr; + sc->sc_outarg = arg; + +#ifdef GUSPLAYDEBUG + if (gusstats) { + microtime(&dmarecords[dmarecord_index].tv); + dmarecords[dmarecord_index].gusaddr = boarddma; + dmarecords[dmarecord_index].bsdaddr = buffer; + dmarecords[dmarecord_index].count = size; + dmarecords[dmarecord_index].channel = 0; + dmarecords[dmarecord_index].direction = 1; + dmarecord_index = ++dmarecord_index % NDMARECS; + } +#endif + + gusdmaout(sc, flags, boarddma, (caddr_t) buffer, size); + + return 0; +} + +void +gusmax_close(addr) + void *addr; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; +/* ac->aux1_mute = 1; + ad1848_mute_aux1(ac, 1); /* turn off DAC output */ + ad1848_close(ac); + gusclose(sc); +} + +/* + * Close out device stuff. Called at splgus() from generic audio layer. + */ +void +gusclose(addr) + void *addr; +{ + struct gus_softc *sc = addr; + + DPRINTF(("gus_close: sc=0x%x\n", sc)); + + +/* if (sc->sc_flags & GUS_DMAOUT_ACTIVE) */ { + gus_halt_out_dma(sc); + } +/* if (sc->sc_flags & GUS_DMAIN_ACTIVE) */ { + gus_halt_in_dma(sc); + } + sc->sc_flags &= ~(GUS_OPEN|GUS_LOCKED|GUS_DMAOUT_ACTIVE|GUS_DMAIN_ACTIVE); + + if (sc->sc_deintr_buf) { + FREE(sc->sc_deintr_buf, M_DEVBUF); + sc->sc_deintr_buf = NULL; + } + /* turn off speaker, etc. */ + + /* make sure the voices shut up: */ + gus_stop_voice(sc, GUS_VOICE_LEFT, 1); + gus_stop_voice(sc, GUS_VOICE_RIGHT, 0); +} + +/* + * Service interrupts. Farm them off to helper routines if we are using the + * GUS for simple playback/record + */ + +#ifdef DIAGNOSTIC +int gusintrcnt; +int gusdmaintrcnt; +int gusvocintrcnt; +#endif + +int +gusintr(arg) + void *arg; +{ + register struct gus_softc *sc = arg; + unsigned char intr; + register u_short port = sc->sc_iobase; + int retval = 0; + + DPRINTF(("gusintr\n")); +#ifdef DIAGNOSTIC + gusintrcnt++; +#endif + if (HAS_CODEC(sc)) + retval = ad1848_intr(&sc->sc_codec); + if ((intr = inb(port+GUS_IRQ_STATUS)) & GUSMASK_IRQ_DMATC) { + DMAPRINTF(("gusintr dma flags=%x\n", sc->sc_flags)); +#ifdef DIAGNOSTIC + gusdmaintrcnt++; +#endif + retval += gus_dmaout_intr(sc); + if (sc->sc_flags & GUS_DMAIN_ACTIVE) { + SELECT_GUS_REG(port, GUSREG_SAMPLE_CONTROL); + intr = inb(port+GUS_DATA_HIGH); + if (intr & GUSMASK_SAMPLE_DMATC) { + retval += gus_dmain_intr(sc); + } + } + } + if (intr & (GUSMASK_IRQ_VOICE | GUSMASK_IRQ_VOLUME)) { + DMAPRINTF(("gusintr voice flags=%x\n", sc->sc_flags)); +#ifdef DIAGNOSTIC + gusvocintrcnt++; +#endif + retval += gus_voice_intr(sc); + } + if (retval) + return 1; + return retval; +} + +int gus_bufcnt[GUS_MEM_FOR_BUFFERS / GUS_BUFFER_MULTIPLE]; +int gus_restart; /* how many restarts? */ +int gus_stops; /* how many times did voice stop? */ +int gus_falsestops; /* stopped but not done? */ +int gus_continues; + +struct playcont { + struct timeval tv; + u_int playbuf; + u_int dmabuf; + u_char bufcnt; + u_char vaction; + u_char voccntl; + u_char volcntl; + u_long curaddr; + u_long endaddr; +} playstats[NDMARECS]; + +int playcntr; + +static void +gus_dmaout_timeout(arg) + void *arg; +{ + register struct gus_softc *sc = arg; + register u_short port = sc->sc_iobase; + int s; + + printf("%s: dmaout timeout\n", sc->sc_dev.dv_xname); + /* + * Stop any DMA. + */ + + s = splgus(); + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + outb(sc->sc_iobase+GUS_DATA_HIGH, 0); + +/* isa_dmaabort(sc->sc_drq); /* XXX we will dmadone below? */ + + gus_dmaout_dointr(sc); + splx(s); +} + + +/* + * Service DMA interrupts. This routine will only get called if we're doing + * a DMA transfer for playback/record requests from the audio layer. + */ + +static int +gus_dmaout_intr(sc) + struct gus_softc *sc; +{ + register u_short port = sc->sc_iobase; + + /* + * If we got a DMA transfer complete from the GUS DRAM, then deal + * with it. + */ + + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + if (inb(port+GUS_DATA_HIGH) & GUSMASK_DMA_IRQPEND) { + untimeout(gus_dmaout_timeout, sc); + gus_dmaout_dointr(sc); + return 1; + } + return 0; +} + +static void +gus_dmaout_dointr(sc) + struct gus_softc *sc; +{ + register u_short port = sc->sc_iobase; + + /* sc->sc_dmaoutcnt - 1 because DMA controller counts from zero?. */ + isa_dmadone(B_WRITE, + sc->sc_dmaoutaddr, + sc->sc_dmaoutcnt - 1, + sc->sc_drq); + sc->sc_flags &= ~GUS_DMAOUT_ACTIVE; /* pending DMA is done */ + DMAPRINTF(("gus_dmaout_dointr %d @ %x\n", sc->sc_dmaoutcnt, + sc->sc_dmaoutaddr)); + + /* + * to prevent clicking, we need to copy last sample + * from last buffer to scratch area just before beginning of + * buffer. However, if we're doing formats that are converted by + * the card during the DMA process, we need to pick up the converted + * byte rather than the one we have in memory. + */ + if (sc->sc_dmabuf == sc->sc_nbufs - 1) { + register int i; + switch (sc->sc_encoding) { + case AUDIO_ENCODING_PCM16: + /* we have the native format */ + for (i = 1; i <= 2; i++) + guspoke(port, sc->sc_gusaddr - + (sc->sc_nbufs - 1) * sc->sc_chanblocksize - i, + sc->sc_dmaoutaddr[sc->sc_dmaoutcnt-i]); + break; + case AUDIO_ENCODING_PCM8: + case AUDIO_ENCODING_ULAW: + /* we need to fetch the translated byte, then stuff it. */ + guspoke(port, sc->sc_gusaddr - + (sc->sc_nbufs - 1) * sc->sc_chanblocksize - 1, + guspeek(port, + sc->sc_gusaddr + sc->sc_chanblocksize - 1)); + break; + } + } + /* + * If this is the first half of stereo, "ignore" this one + * and copy out the second half. + */ + if (sc->sc_dmaoutintr == stereo_dmaintr) { + (*sc->sc_dmaoutintr)(sc->sc_outarg); + return; + } + /* + * If the voice is stopped, then start it. Reset the loop + * and roll bits. Call the audio layer routine, since if + * we're starting a stopped voice, that means that the next + * buffer can be filled + */ + + sc->sc_flags &= ~GUS_LOCKED; + if (sc->sc_voc[GUS_VOICE_LEFT].voccntl & + GUSMASK_VOICE_STOPPED) { + if (sc->sc_flags & GUS_PLAYING) { + printf("%s: playing yet stopped?\n", sc->sc_dev.dv_xname); + } + sc->sc_bufcnt++; /* another yet to be played */ + gus_start_playing(sc, sc->sc_dmabuf); + gus_restart++; + } else { + /* + * set the sound action based on which buffer we + * just transferred. If we just transferred buffer 0 + * we want the sound to loop when it gets to the nth + * buffer; if we just transferred + * any other buffer, we want the sound to roll over + * at least one more time. The voice interrupt + * handlers will take care of accounting & + * setting control bits if it's not caught up to us + * yet. + */ + if (++sc->sc_bufcnt == 2) { + /* + * XXX + * If we're too slow in reaction here, + * the voice could be just approaching the + * end of its run. It should be set to stop, + * so these adjustments might not DTRT. + */ + if (sc->sc_dmabuf == 0 && + sc->sc_playbuf == sc->sc_nbufs - 1) { + /* player is just at the last buf, we're at the + first. Turn on looping, turn off rolling. */ + sc->sc_voc[GUS_VOICE_LEFT].voccntl |= GUSMASK_LOOP_ENABLE; + sc->sc_voc[GUS_VOICE_LEFT].volcntl &= ~GUSMASK_VOICE_ROLL; + playstats[playcntr].vaction = 3; + } else { + /* player is at previous buf: + turn on rolling, turn off looping */ + sc->sc_voc[GUS_VOICE_LEFT].voccntl &= ~GUSMASK_LOOP_ENABLE; + sc->sc_voc[GUS_VOICE_LEFT].volcntl |= GUSMASK_VOICE_ROLL; + playstats[playcntr].vaction = 4; + } +#ifdef GUSPLAYDEBUG + if (gusstats) { + microtime(&playstats[playcntr].tv); + playstats[playcntr].endaddr = sc->sc_voc[GUS_VOICE_LEFT].end_addr; + playstats[playcntr].voccntl = sc->sc_voc[GUS_VOICE_LEFT].voccntl; + playstats[playcntr].volcntl = sc->sc_voc[GUS_VOICE_LEFT].volcntl; + playstats[playcntr].playbuf = sc->sc_playbuf; + playstats[playcntr].dmabuf = sc->sc_dmabuf; + playstats[playcntr].bufcnt = sc->sc_bufcnt; + playstats[playcntr].curaddr = gus_get_curaddr(sc, GUS_VOICE_LEFT); + playcntr = ++playcntr % NDMARECS; + } +#endif + outb(port+GUS_VOICE_SELECT, GUS_VOICE_LEFT); + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[GUS_VOICE_LEFT].voccntl); + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[GUS_VOICE_LEFT].volcntl); + } + } + gus_bufcnt[sc->sc_bufcnt-1]++; + /* + * flip to the next DMA buffer + */ + + sc->sc_dmabuf = ++sc->sc_dmabuf % sc->sc_nbufs; + /* + * See comments below about DMA admission control strategy. + * We can call the upper level here if we have an + * idle buffer (not currently playing) to DMA into. + */ + if (sc->sc_dmaoutintr && sc->sc_bufcnt < sc->sc_nbufs) { + /* clean out to prevent double calls */ + void (*pfunc) __P((void *)) = sc->sc_dmaoutintr; + void *arg = sc->sc_outarg; + + sc->sc_outarg = 0; + sc->sc_dmaoutintr = 0; + (*pfunc)(arg); + } +} + +/* + * Service voice interrupts + */ + +static int +gus_voice_intr(sc) + struct gus_softc *sc; +{ + register u_short port = sc->sc_iobase; + int ignore = 0, voice, rval = 0; + unsigned long addr; + unsigned char intr, status; + + /* + * The point of this may not be obvious at first. A voice can + * interrupt more than once; according to the GUS SDK we are supposed + * to ignore multiple interrupts for the same voice. + */ + + while(1) { + SELECT_GUS_REG(port, GUSREG_IRQ_STATUS); + intr = inb(port+GUS_DATA_HIGH); + + if ((intr & (GUSMASK_WIRQ_VOLUME | GUSMASK_WIRQ_VOICE)) + == (GUSMASK_WIRQ_VOLUME | GUSMASK_WIRQ_VOICE)) + /* + * No more interrupts, time to return + */ + return rval; + + if ((intr & GUSMASK_WIRQ_VOICE) == 0) { + + /* + * We've got a voice interrupt. Ignore previous + * interrupts by the same voice. + */ + + rval = 1; + voice = intr & GUSMASK_WIRQ_VOICEMASK; + + if ((1 << voice) & ignore) + break; + + ignore |= 1 << voice; + + /* + * If the voice is stopped, then force it to stop + * (this stops it from continuously generating IRQs) + */ + + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL+0x80); + status = inb(port+GUS_DATA_HIGH); + if (status & GUSMASK_VOICE_STOPPED) { + if (voice != GUS_VOICE_LEFT) { + DMAPRINTF(("%s: spurious voice %d stop?\n", + sc->sc_dev.dv_xname, voice)); + gus_stop_voice(sc, voice, 0); + continue; + } + gus_stop_voice(sc, voice, 1); + /* also kill right voice */ + gus_stop_voice(sc, GUS_VOICE_RIGHT, 0); + sc->sc_bufcnt--; /* it finished a buffer */ + if (sc->sc_bufcnt > 0) { + /* + * probably a race to get here: the voice + * stopped while the DMA code was just trying to + * get the next buffer in place. + * Start the voice again. + */ + printf("%s: stopped voice not drained? (%x)\n", + sc->sc_dev.dv_xname, sc->sc_bufcnt); + gus_falsestops++; + + sc->sc_playbuf = ++sc->sc_playbuf % sc->sc_nbufs; + gus_start_playing(sc, sc->sc_playbuf); + } else if (sc->sc_bufcnt < 0) { +#ifdef DDB + printf("negative bufcnt in stopped voice\n"); + Debugger(); +#else + panic("negative bufcnt in stopped voice"); +#endif + } else { + sc->sc_playbuf = -1; /* none are active */ + gus_stops++; + } + /* fall through to callback and admit another + buffer.... */ + } else if (sc->sc_bufcnt != 0) { + /* + * This should always be taken if the voice + * is not stopped. + */ + gus_continues++; + gus_continue_playing(sc, voice); + } + /* + * call the upper level to send on down another + * block. We do admission rate control as follows: + * + * When starting up output (in the first N + * blocks), call the upper layer after the DMA is + * complete (see above in gus_dmaout_intr()). + * + * When output is already in progress and we have + * no more GUS buffers to use for DMA, the DMA + * output routines do not call the upper layer. + * Instead, we call the DMA completion routine + * here, after the voice interrupts indicating + * that it's finished with a buffer. + * + * However, don't call anything here if the DMA + * output flag is set, (which shouldn't happen) + * because we'll squish somebody else's DMA if + * that's the case. When DMA is done, it will + * call back if there is a spare buffer. + */ + if (sc->sc_dmaoutintr && !(sc->sc_flags & GUS_LOCKED)) { + if (sc->sc_dmaoutintr == stereo_dmaintr) + printf("gusdmaout botch?\n"); + else { + /* clean out to avoid double calls */ + void (*pfunc)() = sc->sc_dmaoutintr; + void *arg = sc->sc_outarg; + + sc->sc_outarg = 0; + sc->sc_dmaoutintr = 0; + (*pfunc)(arg); + } + } + } + + /* + * Ignore other interrupts for now + */ + } +} + +static void +gus_start_playing(sc, bufno) +struct gus_softc *sc; +int bufno; +{ + register u_short port = sc->sc_iobase; + /* + * Start the voices playing, with buffer BUFNO. + */ + + /* + * Loop or roll if we have buffers ready. + */ + + if (sc->sc_bufcnt == 1) { + sc->sc_voc[GUS_VOICE_LEFT].voccntl &= ~(GUSMASK_LOOP_ENABLE); + sc->sc_voc[GUS_VOICE_LEFT].volcntl &= ~(GUSMASK_VOICE_ROLL); + } else { + if (bufno == sc->sc_nbufs - 1) { + sc->sc_voc[GUS_VOICE_LEFT].voccntl |= GUSMASK_LOOP_ENABLE; + sc->sc_voc[GUS_VOICE_LEFT].volcntl &= ~(GUSMASK_VOICE_ROLL); + } else { + sc->sc_voc[GUS_VOICE_LEFT].voccntl &= ~GUSMASK_LOOP_ENABLE; + sc->sc_voc[GUS_VOICE_LEFT].volcntl |= GUSMASK_VOICE_ROLL; + } + } + + outb(port+GUS_VOICE_SELECT, GUS_VOICE_LEFT); + + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[GUS_VOICE_LEFT].voccntl); + + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[GUS_VOICE_LEFT].volcntl); + + sc->sc_voc[GUS_VOICE_LEFT].current_addr = + GUS_MEM_OFFSET + sc->sc_chanblocksize * bufno; + sc->sc_voc[GUS_VOICE_LEFT].end_addr = + sc->sc_voc[GUS_VOICE_LEFT].current_addr + sc->sc_chanblocksize - 1; + sc->sc_voc[GUS_VOICE_RIGHT].current_addr = + sc->sc_voc[GUS_VOICE_LEFT].current_addr + + (gus_dostereo && sc->sc_channels == 2 ? GUS_LEFT_RIGHT_OFFSET : 0); + /* + * set up right channel to just loop forever, no interrupts, + * starting at the buffer we just filled. We'll feed it data + * at the same time as left channel. + */ + sc->sc_voc[GUS_VOICE_RIGHT].voccntl |= GUSMASK_LOOP_ENABLE; + sc->sc_voc[GUS_VOICE_RIGHT].volcntl &= ~(GUSMASK_VOICE_ROLL); + +#ifdef GUSPLAYDEBUG + if (gusstats) { + microtime(&playstats[playcntr].tv); + playstats[playcntr].curaddr = sc->sc_voc[GUS_VOICE_LEFT].current_addr; + + playstats[playcntr].voccntl = sc->sc_voc[GUS_VOICE_LEFT].voccntl; + playstats[playcntr].volcntl = sc->sc_voc[GUS_VOICE_LEFT].volcntl; + playstats[playcntr].endaddr = sc->sc_voc[GUS_VOICE_LEFT].end_addr; + playstats[playcntr].playbuf = bufno; + playstats[playcntr].dmabuf = sc->sc_dmabuf; + playstats[playcntr].bufcnt = sc->sc_bufcnt; + playstats[playcntr].vaction = 5; + playcntr = ++playcntr % NDMARECS; + } +#endif + + outb(port+GUS_VOICE_SELECT, GUS_VOICE_RIGHT); + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[GUS_VOICE_RIGHT].voccntl); + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[GUS_VOICE_RIGHT].volcntl); + + gus_start_voice(sc, GUS_VOICE_RIGHT, 0); + gus_start_voice(sc, GUS_VOICE_LEFT, 1); + if (sc->sc_playbuf == -1) + /* mark start of playing */ + sc->sc_playbuf = bufno; +} + +static void +gus_continue_playing(sc, voice) +register struct gus_softc *sc; +int voice; +{ + register u_short port = sc->sc_iobase; + + /* + * stop this voice from interrupting while we work. + */ + + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].voccntl & ~(GUSMASK_VOICE_IRQ)); + + /* + * update playbuf to point to the buffer the hardware just started + * playing + */ + sc->sc_playbuf = ++sc->sc_playbuf % sc->sc_nbufs; + + /* + * account for buffer just finished + */ + if (--sc->sc_bufcnt == 0) { + DPRINTF(("gus: bufcnt 0 on continuing voice?\n")); + } + if (sc->sc_playbuf == sc->sc_dmabuf && (sc->sc_flags & GUS_LOCKED)) + printf("continue into active dmabuf?\n"); + + /* + * Select the end of the buffer based on the currently active + * buffer, [plus extra contiguous buffers (if ready)]. + */ + + /* + * set endpoint at end of buffer we just started playing. + * + * The total gets -1 because end addrs are one less than you might + * think (the end_addr is the address of the last sample to play) + */ + gus_set_endaddr(sc, voice, GUS_MEM_OFFSET + + sc->sc_chanblocksize * (sc->sc_playbuf + 1) - 1); + + if (sc->sc_bufcnt < 2) { + /* + * Clear out the loop and roll flags, and rotate the currently + * playing buffer. That way, if we don't manage to get more + * data before this buffer finishes, we'll just stop. + */ + sc->sc_voc[voice].voccntl &= ~GUSMASK_LOOP_ENABLE; + sc->sc_voc[voice].volcntl &= ~GUSMASK_VOICE_ROLL; + playstats[playcntr].vaction = 0; + } else { + /* + * We have some buffers to play. set LOOP if we're on the + * last buffer in the ring, otherwise set ROLL. + */ + if (sc->sc_playbuf == sc->sc_nbufs - 1) { + sc->sc_voc[voice].voccntl |= GUSMASK_LOOP_ENABLE; + sc->sc_voc[voice].volcntl &= ~GUSMASK_VOICE_ROLL; + playstats[playcntr].vaction = 1; + } else { + sc->sc_voc[voice].voccntl &= ~GUSMASK_LOOP_ENABLE; + sc->sc_voc[voice].volcntl |= GUSMASK_VOICE_ROLL; + playstats[playcntr].vaction = 2; + } + } +#ifdef GUSPLAYDEBUG + if (gusstats) { + microtime(&playstats[playcntr].tv); + playstats[playcntr].curaddr = gus_get_curaddr(sc, voice); + + playstats[playcntr].voccntl = sc->sc_voc[voice].voccntl; + playstats[playcntr].volcntl = sc->sc_voc[voice].volcntl; + playstats[playcntr].endaddr = sc->sc_voc[voice].end_addr; + playstats[playcntr].playbuf = sc->sc_playbuf; + playstats[playcntr].dmabuf = sc->sc_dmabuf; + playstats[playcntr].bufcnt = sc->sc_bufcnt; + playcntr = ++playcntr % NDMARECS; + } +#endif + + /* + * (re-)set voice parameters. This will reenable interrupts from this + * voice. + */ + + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].voccntl); + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].volcntl); +} + +/* + * Send/receive data into GUS's DRAM using DMA. Called at splgus() + */ + +static void +gusdmaout(sc, flags, gusaddr, buffaddr, length) + struct gus_softc *sc; + int flags, length; + unsigned long gusaddr; + caddr_t buffaddr; +{ + register unsigned char c = (unsigned char) flags; + register u_short port = sc->sc_iobase; + int s; + + DMAPRINTF(("gusdmaout flags=%x scflags=%x\n", flags, sc->sc_flags)); + + sc->sc_gusaddr = gusaddr; + + /* + * If we're using a 16 bit DMA channel, we have to jump through some + * extra hoops; this includes translating the DRAM address a bit + */ + + if (sc->sc_drq >= 4) { + c |= GUSMASK_DMA_WIDTH; + gusaddr = convert_to_16bit(gusaddr); + } + + /* + * Add flag bits that we always set - fast DMA, enable IRQ + */ + + c |= GUSMASK_DMA_ENABLE | GUSMASK_DMA_R0 | GUSMASK_DMA_IRQ; + + /* + * Make sure the GUS _isn't_ setup for DMA + */ + + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + outb(port+GUS_DATA_HIGH, 0); + + /* + * Tell the PC DMA controller to start doing DMA + */ + + sc->sc_dmaoutaddr = (u_char *) buffaddr; + sc->sc_dmaoutcnt = length; + isa_dmastart(B_WRITE, buffaddr, length, sc->sc_drq); + + /* + * Set up DMA address - use the upper 16 bits ONLY + */ + + sc->sc_flags |= GUS_DMAOUT_ACTIVE; + + SELECT_GUS_REG(port, GUSREG_DMA_START); + outw(port+GUS_DATA_LOW, (int) (gusaddr >> 4)); + + /* + * Tell the GUS to start doing DMA + */ + + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + outb(port+GUS_DATA_HIGH, c); + + /* + * XXX If we don't finish in one second, give up... + */ + untimeout(gus_dmaout_timeout, sc); /* flush old one, if there is one */ + timeout(gus_dmaout_timeout, sc, hz); +} + +/* + * Start a voice playing on the GUS. Called from interrupt handler at + * splgus(). + */ + +static void +gus_start_voice(sc, voice, intrs) + struct gus_softc *sc; + int voice; + int intrs; +{ + register u_short port = sc->sc_iobase; + unsigned long start; + unsigned long current; + unsigned long end; + + /* + * Pick all the values for the voice out of the gus_voice struct + * and use those to program the voice + */ + + start = sc->sc_voc[voice].start_addr; + current = sc->sc_voc[voice].current_addr; + end = sc->sc_voc[voice].end_addr; + + /* + * If we're using 16 bit data, mangle the addresses a bit + */ + + if (sc->sc_voc[voice].voccntl & GUSMASK_DATA_SIZE16) { + /* -1 on start so that we get onto sample boundary--other + code always sets it for 1-byte rollover protection */ + start = convert_to_16bit(start-1); + current = convert_to_16bit(current); + end = convert_to_16bit(end); + } + + /* + * Select the voice we want to use, and program the data addresses + */ + + outb(port+GUS_VOICE_SELECT, (unsigned char) voice); + + SELECT_GUS_REG(port, GUSREG_START_ADDR_HIGH); + outw(port+GUS_DATA_LOW, ADDR_HIGH(start)); + SELECT_GUS_REG(port, GUSREG_START_ADDR_LOW); + outw(port+GUS_DATA_LOW, ADDR_LOW(start)); + + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_HIGH); + outw(port+GUS_DATA_LOW, ADDR_HIGH(current)); + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_LOW); + outw(port+GUS_DATA_LOW, ADDR_LOW(current)); + + SELECT_GUS_REG(port, GUSREG_END_ADDR_HIGH); + outw(port+GUS_DATA_LOW, ADDR_HIGH(end)); + SELECT_GUS_REG(port, GUSREG_END_ADDR_LOW); + outw(port+GUS_DATA_LOW, ADDR_LOW(end)); + + /* + * (maybe) enable interrupts, disable voice stopping + */ + + if (intrs) { + sc->sc_flags |= GUS_PLAYING; /* playing is about to start */ + sc->sc_voc[voice].voccntl |= GUSMASK_VOICE_IRQ; + DMAPRINTF(("gus voice playing=%x\n", sc->sc_flags)); + } else + sc->sc_voc[voice].voccntl &= ~GUSMASK_VOICE_IRQ; + sc->sc_voc[voice].voccntl &= ~(GUSMASK_VOICE_STOPPED | + GUSMASK_STOP_VOICE); + + /* + * Tell the GUS about it. Note that we're doing volume ramping here + * from 0 up to the set volume to help reduce clicks. + */ + + SELECT_GUS_REG(port, GUSREG_START_VOLUME); + outb(port+GUS_DATA_HIGH, 0x00); + SELECT_GUS_REG(port, GUSREG_END_VOLUME); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].current_volume >> 4); + SELECT_GUS_REG(port, GUSREG_CUR_VOLUME); + outw(port+GUS_DATA_LOW, 0x00); + SELECT_GUS_REG(port, GUSREG_VOLUME_RATE); + outb(port+GUS_DATA_HIGH, 63); + + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].voccntl); + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, 0x00); + delay(50); + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].voccntl); + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, 0x00); + +} + +/* + * Stop a given voice. called at splgus() + */ + +static void +gus_stop_voice(sc, voice, intrs_too) + struct gus_softc *sc; + int voice; + int intrs_too; +{ + register u_short port = sc->sc_iobase; + + sc->sc_voc[voice].voccntl |= GUSMASK_VOICE_STOPPED | + GUSMASK_STOP_VOICE; + if (intrs_too) { + sc->sc_voc[voice].voccntl &= ~(GUSMASK_VOICE_IRQ); + /* no more DMA to do */ + sc->sc_flags &= ~GUS_PLAYING; + } + DMAPRINTF(("gusintr voice notplaying=%x\n", sc->sc_flags)); + + guspoke(port, 0L, 0); + + outb(port+GUS_VOICE_SELECT, (unsigned char) voice); + + SELECT_GUS_REG(port, GUSREG_CUR_VOLUME); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].voccntl); + delay(100); + SELECT_GUS_REG(port, GUSREG_CUR_VOLUME); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[voice].voccntl); + + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_HIGH); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_LOW); + outw(port+GUS_DATA_LOW, 0x0000); + +} + + +/* + * Set the volume of a given voice. Called at splgus(). + */ +static void +gus_set_volume(sc, voice, volume) + struct gus_softc *sc; + int voice, volume; +{ + register u_short port = sc->sc_iobase; + unsigned int gusvol; + + gusvol = gus_log_volumes[volume < 512 ? volume : 511]; + + sc->sc_voc[voice].current_volume = gusvol; + + outb(port+GUS_VOICE_SELECT, (unsigned char) voice); + + SELECT_GUS_REG(port, GUSREG_START_VOLUME); + outb(port+GUS_DATA_HIGH, (unsigned char) (gusvol >> 4)); + + SELECT_GUS_REG(port, GUSREG_END_VOLUME); + outb(port+GUS_DATA_HIGH, (unsigned char) (gusvol >> 4)); + + SELECT_GUS_REG(port, GUSREG_CUR_VOLUME); + outw(port+GUS_DATA_LOW, gusvol << 4); + delay(500); + outw(port+GUS_DATA_LOW, gusvol << 4); + +} + +/* + * Interface to the audio layer - set the data encoding type + */ + +int +gusmax_set_encoding(addr, encoding) + void * addr; + u_int encoding; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + (void) ad1848_set_encoding(ac, encoding); + return gus_set_encoding(sc, encoding); +} + +int +gus_set_encoding(addr, encoding) + void * addr; + u_int encoding; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_set_encoding called\n")); + + /* XXX todo: add alaw for codec */ + if (encoding != AUDIO_ENCODING_ULAW && + encoding != AUDIO_ENCODING_PCM16 && + encoding != AUDIO_ENCODING_PCM8) + return EINVAL; + + if (encoding != AUDIO_ENCODING_PCM16) + sc->sc_precision = 8; /* XXX force it. */ + + sc->sc_encoding = encoding; + + if (sc->sc_precision == 8) { + sc->sc_voc[GUS_VOICE_LEFT].voccntl &= ~GUSMASK_DATA_SIZE16; + sc->sc_voc[GUS_VOICE_RIGHT].voccntl &= ~GUSMASK_DATA_SIZE16; + } else { + sc->sc_voc[GUS_VOICE_LEFT].voccntl |= GUSMASK_DATA_SIZE16; + sc->sc_voc[GUS_VOICE_RIGHT].voccntl |= GUSMASK_DATA_SIZE16; + } + return 0; +} + +int +gusmax_set_channels(addr, channels) + void * addr; + int channels; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + (void) ad1848_set_channels(ac, channels); + return gus_set_channels(sc, channels); +} + +int +gus_set_channels(addr, channels) + void * addr; + int channels; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_set_channels called\n")); + + if (channels != 1 && channels != 2) + return EINVAL; + + sc->sc_channels = channels; + + return 0; +} + +/* + * Interface to the audio layer - set the data precision + */ + +int +gusmax_set_precision(addr, bits) + void * addr; + u_int bits; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + + (void) ad1848_set_precision(ac, bits); + return gus_set_precision(sc, bits); +} + + +int +gus_set_precision(addr, bits) + void * addr; + u_int bits; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_set_precision called\n")); + + if (bits != 8 && bits != 16) + return EINVAL; + + if (sc->sc_encoding != AUDIO_ENCODING_PCM16 && bits != 8) + /* If we're doing PCM8 or MULAW, it must be 8 bits. */ + return EINVAL; + + sc->sc_precision = bits; + + if (bits == 16) { + sc->sc_voc[GUS_VOICE_LEFT].voccntl |= GUSMASK_DATA_SIZE16; + sc->sc_voc[GUS_VOICE_RIGHT].voccntl |= GUSMASK_DATA_SIZE16; + } else { + sc->sc_voc[GUS_VOICE_LEFT].voccntl &= ~GUSMASK_DATA_SIZE16; + sc->sc_voc[GUS_VOICE_RIGHT].voccntl &= ~GUSMASK_DATA_SIZE16; + } + return 0; +} + +/* + * Interface to the audio layer - set the blocksize to the correct number + * of units + */ + +int +gusmax_round_blocksize(addr, blocksize) + void * addr; + int blocksize; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + +/* blocksize = ad1848_round_blocksize(ac, blocksize);*/ + return gus_round_blocksize(sc, blocksize); +} + +int +gus_round_blocksize(addr, blocksize) + void * addr; + int blocksize; +{ + register struct gus_softc *sc = addr; + register unsigned long i; + + DPRINTF(("gus_round_blocksize called\n")); + + if (sc->sc_encoding == AUDIO_ENCODING_ULAW && blocksize > 32768) + blocksize = 32768; + else if (blocksize > 65536) + blocksize = 65536; + + if ((blocksize % GUS_BUFFER_MULTIPLE) != 0) + blocksize = (blocksize / GUS_BUFFER_MULTIPLE + 1) * + GUS_BUFFER_MULTIPLE; + + /* set up temporary buffer to hold the deinterleave, if necessary + for stereo output */ + if (sc->sc_deintr_buf) { + FREE(sc->sc_deintr_buf, M_DEVBUF); + sc->sc_deintr_buf = NULL; + } + MALLOC(sc->sc_deintr_buf, void *, blocksize>>1, M_DEVBUF, M_WAITOK); + + sc->sc_blocksize = blocksize; + /* multi-buffering not quite working yet. */ + sc->sc_nbufs = /*GUS_MEM_FOR_BUFFERS / blocksize*/ 2; + + gus_set_chan_addrs(sc); + + return blocksize; +} + +/* + * Interfaces to the audio layer - return values from the software config + * struct + */ + +int +gusmax_get_encoding(addr) + void * addr; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + return gus_get_encoding(sc); +} + +int +gus_get_encoding(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_get_encoding called\n")); + + /* XXX TODO: codec stuff */ + return sc->sc_encoding; +} + +int +gusmax_get_channels(addr) + void * addr; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + return gus_get_channels(sc); +} + +int +gus_get_channels(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_get_channels called\n")); + + return sc->sc_channels; +} + +u_long +gus_get_in_sr(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_get_in_sr called\n")); + return sc->sc_irate; +} + +u_long +gusmax_get_in_sr(addr) + void * addr; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + return gus_get_in_sr(sc); +} + +u_long +gusmax_get_out_sr(addr) + void * addr; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + return gus_get_out_sr(sc); +} + +u_long +gus_get_out_sr(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_get_out_sr called\n")); + return sc->sc_orate; +} + +int +gusmax_get_precision(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + return gus_get_precision(sc->parent); +} + +int +gus_get_precision(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_get_precision called\n")); + + return sc->sc_precision; +} + +int +gus_get_out_gain(addr) + caddr_t addr; +{ + register struct gus_softc *sc = (struct gus_softc *) addr; + + DPRINTF(("gus_get_out_gain called\n")); + return sc->sc_ogain / 2; +} + +/* + * Interface to the audio layer - set the sample rate of the output voices + */ + +int +gusmax_set_out_sr(addr, rate) + void * addr; + u_long rate; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + (void) ad1848_set_out_sr(ac, rate); + return gus_set_out_sr(sc, rate); +} + +int +gus_set_out_sr(addr, rate) + void * addr; + u_long rate; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_set_out_sr called\n")); + + if (rate > gus_max_frequency[sc->sc_voices - GUS_MIN_VOICES]) + rate = gus_max_frequency[sc->sc_voices - GUS_MIN_VOICES]; + + sc->sc_orate = rate; + + return 0; +} + +static inline void gus_set_voices(sc, voices) +struct gus_softc *sc; +int voices; +{ + register u_short port = sc->sc_iobase; + /* + * Select the active number of voices + */ + + SELECT_GUS_REG(port, GUSREG_ACTIVE_VOICES); + outb(port+GUS_DATA_HIGH, (voices-1) | 0xc0); + + sc->sc_voices = voices; +} + +/* + * Actually set the settings of various values on the card + */ + +int +gusmax_commit_settings(addr) + void * addr; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + + (void) ad1848_commit_settings(ac); + return gus_commit_settings(sc); +} + +/* + * Commit the settings. Called at normal IPL. + */ +int +gus_commit_settings(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + int s; + + DPRINTF(("gus_commit_settings called (gain = %d)\n",sc->sc_ogain)); + + + s = splgus(); + + gus_set_recrate(sc, sc->sc_irate); + gus_set_volume(sc, GUS_VOICE_LEFT, sc->sc_ogain); + gus_set_volume(sc, GUS_VOICE_RIGHT, sc->sc_ogain); + gus_set_samprate(sc, GUS_VOICE_LEFT, sc->sc_orate); + gus_set_samprate(sc, GUS_VOICE_RIGHT, sc->sc_orate); + splx(s); + gus_set_chan_addrs(sc); + + return 0; +} + +static void +gus_set_chan_addrs(sc) +struct gus_softc *sc; +{ + /* + * We use sc_nbufs * blocksize bytes of storage in the on-board GUS + * ram. + * For mono, each of the sc_nbufs buffers is DMA'd to in one chunk, + * and both left & right channels play the same buffer. + * + * For stereo, each channel gets a contiguous half of the memory, + * and each has sc_nbufs buffers of size blocksize/2. + * Stereo data are deinterleaved in main memory before the DMA out + * routines are called to queue the output. + * + * The blocksize per channel is kept in sc_chanblocksize. + */ + if (sc->sc_channels == 2) + sc->sc_chanblocksize = sc->sc_blocksize/2; + else + sc->sc_chanblocksize = sc->sc_blocksize; + + sc->sc_voc[GUS_VOICE_LEFT].start_addr = GUS_MEM_OFFSET - 1; + sc->sc_voc[GUS_VOICE_RIGHT].start_addr = + (gus_dostereo && sc->sc_channels == 2 ? GUS_LEFT_RIGHT_OFFSET : 0) + + GUS_MEM_OFFSET - 1; + sc->sc_voc[GUS_VOICE_RIGHT].current_addr = + sc->sc_voc[GUS_VOICE_RIGHT].start_addr + 1; + sc->sc_voc[GUS_VOICE_RIGHT].end_addr = + sc->sc_voc[GUS_VOICE_RIGHT].start_addr + + sc->sc_nbufs * sc->sc_chanblocksize; + +} + +/* + * Set the sample rate of the given voice. Called at splgus(). + */ + +static void +gus_set_samprate(sc, voice, freq) + struct gus_softc *sc; + int voice, freq; +{ + register u_short port = sc->sc_iobase; + unsigned int fc; + unsigned long temp, f = (unsigned long) freq; + + /* + * calculate fc based on the number of active voices; + * we need to use longs to preserve enough bits + */ + + temp = (unsigned long) gus_max_frequency[sc->sc_voices-GUS_MIN_VOICES]; + + fc = (unsigned int)(((f << 9L) + (temp >> 1L)) / temp); + + fc <<= 1; + + + /* + * Program the voice frequency, and set it in the voice data record + */ + + outb(port+GUS_VOICE_SELECT, (unsigned char) voice); + SELECT_GUS_REG(port, GUSREG_FREQ_CONTROL); + outw(port+GUS_DATA_LOW, fc); + + sc->sc_voc[voice].rate = freq; + +} + +/* + * Interface to the audio layer - set the recording sampling rate + */ + +int +gusmax_set_in_sr(addr, rate) + void * addr; + u_long rate; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + (void) ad1848_set_in_sr(ac, rate); + return gus_set_in_sr(sc, rate); +} + + +int +gus_set_in_sr(addr, rate) + void *addr; + u_long rate; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gus_set_in_sr called\n")); + + sc->sc_irate = rate; + + return 0; +} +/* + * Set the sample rate of the recording frequency. Formula is from the GUS + * SDK. Called at splgus(). + */ + +static void +gus_set_recrate(sc, rate) + struct gus_softc *sc; + u_long rate; +{ + register u_short port = sc->sc_iobase; + u_char realrate; + int s; + DPRINTF(("gus_set_recrate %lu\n", rate)); + +/* realrate = 9878400/(16*(rate+2)); /* formula from GUS docs */ + realrate = (9878400 >> 4)/rate - 2; /* formula from code, sigh. */ + + SELECT_GUS_REG(port, GUSREG_SAMPLE_FREQ); + outb(port+GUS_DATA_HIGH, realrate); +} + +/* + * Interface to the audio layer - turn the output on or off. Note that some + * of these bits are flipped in the register + */ + +int +gusmax_speaker_ctl(addr, newstate) + void * addr; + int newstate; +{ + register struct ad1848_softc *sc = addr; + return gus_speaker_ctl(sc->parent, newstate); +} + +int +gus_speaker_ctl(addr, newstate) + void * addr; + int newstate; +{ + register struct gus_softc *sc = (struct gus_softc *) addr; + + /* Line out bit is flipped: 0 enables, 1 disables */ + if ((newstate == SPKR_ON) && + (sc->sc_mixcontrol & GUSMASK_LINE_OUT)) { + sc->sc_mixcontrol &= ~GUSMASK_LINE_OUT; + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + } + if ((newstate == SPKR_OFF) && + (sc->sc_mixcontrol & GUSMASK_LINE_OUT) == 0) { + sc->sc_mixcontrol |= GUSMASK_LINE_OUT; + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + } + + return 0; +} + +static int +gus_linein_ctl(addr, newstate) + void * addr; + int newstate; +{ + register struct gus_softc *sc = (struct gus_softc *) addr; + + /* Line in bit is flipped: 0 enables, 1 disables */ + if ((newstate == SPKR_ON) && + (sc->sc_mixcontrol & GUSMASK_LINE_IN)) { + sc->sc_mixcontrol &= ~GUSMASK_LINE_IN; + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + } + if ((newstate == SPKR_OFF) && + (sc->sc_mixcontrol & GUSMASK_LINE_IN) == 0) { + sc->sc_mixcontrol |= GUSMASK_LINE_IN; + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + } + + return 0; +} + +static int +gus_mic_ctl(addr, newstate) + void * addr; + int newstate; +{ + register struct gus_softc *sc = (struct gus_softc *) addr; + + /* Mic bit is normal: 1 enables, 0 disables */ + if ((newstate == SPKR_ON) && + (sc->sc_mixcontrol & GUSMASK_MIC_IN) == 0) { + sc->sc_mixcontrol |= GUSMASK_MIC_IN; + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + } + if ((newstate == SPKR_OFF) && + (sc->sc_mixcontrol & GUSMASK_MIC_IN)) { + sc->sc_mixcontrol &= ~GUSMASK_MIC_IN; + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + } + + return 0; +} + +/* + * Set the end address of a give voice. Called at splgus() + */ + +static void +gus_set_endaddr(sc, voice, addr) + struct gus_softc *sc; + int voice; + unsigned long addr; +{ + register u_short port = sc->sc_iobase; + + sc->sc_voc[voice].end_addr = addr; + + if (sc->sc_voc[voice].voccntl & GUSMASK_DATA_SIZE16) + addr = convert_to_16bit(addr); + + SELECT_GUS_REG(port, GUSREG_END_ADDR_HIGH); + outw(port+GUS_DATA_LOW, ADDR_HIGH(addr)); + SELECT_GUS_REG(port, GUSREG_END_ADDR_LOW); + outw(port+GUS_DATA_LOW, ADDR_LOW(addr)); + +} + +#if 0 +/* + * Set current address. called at splgus() + */ +static void +gus_set_curaddr(sc, voice, addr) + struct gus_softc *sc; + int voice; + unsigned long addr; +{ + register u_short port = sc->sc_iobase; + + sc->sc_voc[voice].current_addr = addr; + + if (sc->sc_voc[voice].voccntl & GUSMASK_DATA_SIZE16) + addr = convert_to_16bit(addr); + + outb(port+GUS_VOICE_SELECT, (unsigned char) voice); + + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_HIGH); + outw(port+GUS_DATA_LOW, ADDR_HIGH(addr)); + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_LOW); + outw(port+GUS_DATA_LOW, ADDR_LOW(addr)); + +} +#endif + +/* + * Get current GUS playback address. Called at splgus(). + */ +static unsigned long +gus_get_curaddr(sc, voice) + struct gus_softc *sc; + int voice; +{ + register u_short port = sc->sc_iobase; + unsigned long addr; + + outb(port+GUS_VOICE_SELECT, (unsigned char) voice); + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_HIGH|GUSREG_READ); + addr = (inw(port+GUS_DATA_LOW) & 0x1fff) << 7; + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_LOW|GUSREG_READ); + addr |= (inw(port+GUS_DATA_LOW) >> 9L) & 0x7f; + + if (sc->sc_voc[voice].voccntl & GUSMASK_DATA_SIZE16) + addr = (addr & 0xc0000) | ((addr & 0x1ffff) << 1); /* undo 16-bit change */ + DPRINTF(("gus voice %d curaddr %d end_addr %d\n", + voice, addr, sc->sc_voc[voice].end_addr)); + /* XXX sanity check the address? */ + + return(addr); +} + +/* + * Convert an address value to a "16 bit" value - why this is necessary I + * have NO idea + */ + +static unsigned long +convert_to_16bit(address) + unsigned long address; +{ + unsigned long old_address; + + old_address = address; + address >>= 1; + address &= 0x0001ffffL; + address |= (old_address & 0x000c0000L); + + return (address); +} + +/* + * Write a value into the GUS's DRAM + */ + +static void +guspoke(port, address, value) + int port; + long address; + unsigned char value; +{ + + /* + * Select the DRAM address + */ + + SELECT_GUS_REG(port, GUSREG_DRAM_ADDR_LOW); + outw(port+GUS_DATA_LOW, (unsigned int) (address & 0xffff)); + SELECT_GUS_REG(port, GUSREG_DRAM_ADDR_HIGH); + outb(port+GUS_DATA_HIGH, (unsigned char) ((address >> 16) & 0xff)); + + /* + * Actually write the data + */ + + outb(port+GUS_DRAM_DATA, value); +} + +/* + * Read a value from the GUS's DRAM + */ + +static unsigned char +guspeek(port, address) + int port; + u_long address; +{ + + /* + * Select the DRAM address + */ + + SELECT_GUS_REG(port, GUSREG_DRAM_ADDR_LOW); + outw(port+GUS_DATA_LOW, (unsigned int) (address & 0xffff)); + SELECT_GUS_REG(port, GUSREG_DRAM_ADDR_HIGH); + outb(port+GUS_DATA_HIGH, (unsigned char) ((address >> 16) & 0xff)); + + /* + * Read in the data from the board + */ + + return (unsigned char) inb(port+GUS_DRAM_DATA); +} + +/* + * Reset the Gravis UltraSound card, completely + */ + +static void +gusreset(sc, voices) + struct gus_softc *sc; + int voices; +{ + register u_short port = sc->sc_iobase; + int i,s; + + s = splgus(); + + /* + * Reset the GF1 chip + */ + + SELECT_GUS_REG(port, GUSREG_RESET); + outb(port+GUS_DATA_HIGH, 0x00); + + delay(500); + + /* + * Release reset + */ + + SELECT_GUS_REG(port, GUSREG_RESET); + outb(port+GUS_DATA_HIGH, GUSMASK_MASTER_RESET); + + delay(500); + + /* + * Reset MIDI port as well + */ + + outb(GUS_MIDI_CONTROL,MIDI_RESET); + + delay(500); + + outb(GUS_MIDI_CONTROL,0x00); + + /* + * Clear interrupts + */ + + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + outb(port+GUS_DATA_HIGH, 0x00); + SELECT_GUS_REG(port, GUSREG_TIMER_CONTROL); + outb(port+GUS_DATA_HIGH, 0x00); + SELECT_GUS_REG(port, GUSREG_SAMPLE_CONTROL); + outb(port+GUS_DATA_HIGH, 0x00); + + gus_set_voices(sc, voices); + + inb(port+GUS_IRQ_STATUS); + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + inb(port+GUS_DATA_HIGH); + SELECT_GUS_REG(port, GUSREG_SAMPLE_CONTROL); + inb(port+GUS_DATA_HIGH); + SELECT_GUS_REG(port, GUSREG_IRQ_STATUS); + inb(port+GUS_DATA_HIGH); + + /* + * Reset voice specific information + */ + + for(i = 0; i < voices; i++) { + outb(port+GUS_VOICE_SELECT, (unsigned char) i); + + SELECT_GUS_REG(port, GUSREG_VOICE_CNTL); + + sc->sc_voc[i].voccntl = GUSMASK_VOICE_STOPPED | + GUSMASK_STOP_VOICE; + + outb(port+GUS_DATA_HIGH, sc->sc_voc[i].voccntl); + + sc->sc_voc[i].volcntl = GUSMASK_VOLUME_STOPPED | + GUSMASK_STOP_VOLUME; + + SELECT_GUS_REG(port, GUSREG_VOLUME_CONTROL); + outb(port+GUS_DATA_HIGH, sc->sc_voc[i].volcntl); + + delay(100); + + gus_set_samprate(sc, i, 8000); + SELECT_GUS_REG(port, GUSREG_START_ADDR_HIGH); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_START_ADDR_LOW); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_END_ADDR_HIGH); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_END_ADDR_LOW); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_VOLUME_RATE); + outb(port+GUS_DATA_HIGH, 0x01); + SELECT_GUS_REG(port, GUSREG_START_VOLUME); + outb(port+GUS_DATA_HIGH, 0x10); + SELECT_GUS_REG(port, GUSREG_END_VOLUME); + outb(port+GUS_DATA_HIGH, 0xe0); + SELECT_GUS_REG(port, GUSREG_CUR_VOLUME); + outw(port+GUS_DATA_LOW, 0x0000); + + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_HIGH); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_CUR_ADDR_LOW); + outw(port+GUS_DATA_LOW, 0x0000); + SELECT_GUS_REG(port, GUSREG_PAN_POS); + outb(port+GUS_DATA_HIGH, 0x07); + } + + /* + * Clear out any pending IRQs + */ + + inb(port+GUS_IRQ_STATUS); + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + inb(port+GUS_DATA_HIGH); + SELECT_GUS_REG(port, GUSREG_SAMPLE_CONTROL); + inb(port+GUS_DATA_HIGH); + SELECT_GUS_REG(port, GUSREG_IRQ_STATUS); + inb(port+GUS_DATA_HIGH); + + SELECT_GUS_REG(port, GUSREG_RESET); + outb(port+GUS_DATA_HIGH, GUSMASK_MASTER_RESET | GUSMASK_DAC_ENABLE | + GUSMASK_IRQ_ENABLE); + + splx(s); +} + + +static void +gus_init_cs4231(sc) + struct gus_softc *sc; +{ + register u_short port = sc->sc_iobase; + u_char ctrl; + + ctrl = (port & 0xf0) >> 4; /* set port address middle nibble */ + /* + * The codec is a bit weird--swapped dma channels. + */ + ctrl |= GUS_MAX_CODEC_ENABLE; + if (sc->sc_drq >= 4) + ctrl |= GUS_MAX_RECCHAN16; + if (sc->sc_recdrq >= 4) + ctrl |= GUS_MAX_PLAYCHAN16; + + outb(port+GUS_MAX_CTRL, ctrl); + + sc->sc_codec.sc_iobase = port+GUS_MAX_CODEC_BASE; + + if (ad1848_probe(&sc->sc_codec) == 0) { + sc->sc_flags &= ~GUS_CODEC_INSTALLED; + } else { + struct ad1848_volume vol = {AUDIO_MAX_GAIN, AUDIO_MAX_GAIN}; + struct audio_hw_if gusmax_hw_if = { + gusopen, + gusmax_close, + NULL, /* drain */ + gusmax_set_in_sr, + gusmax_get_in_sr, + gusmax_set_out_sr, + gusmax_get_out_sr, + + ad1848_query_encoding, /* query encoding */ + gusmax_set_encoding, + gusmax_get_encoding, + + gusmax_set_precision, + gusmax_get_precision, + + gusmax_set_channels, + gusmax_get_channels, + + gusmax_round_blocksize, + + gusmax_set_out_port, + gusmax_get_out_port, + gusmax_set_in_port, + gusmax_get_in_port, + + gusmax_commit_settings, + + ad1848_get_silence, + + gusmax_expand, /* XXX use codec */ + mulaw_compress, + + gusmax_dma_output, + gusmax_dma_input, + gusmax_halt_out_dma, + gusmax_halt_in_dma, + gusmax_cont_out_dma, + gusmax_cont_in_dma, + + gusmax_speaker_ctl, + + gus_getdev, + gus_setfd, + gusmax_mixer_set_port, + gusmax_mixer_get_port, + gusmax_mixer_query_devinfo, + 1, /* full-duplex */ + 0, + }; + sc->sc_flags |= GUS_CODEC_INSTALLED; + sc->sc_codec.parent = sc; + sc->sc_codec.sc_drq = sc->sc_recdrq; + sc->sc_codec.sc_recdrq = sc->sc_drq; + gus_hw_if = gusmax_hw_if; + /* enable line in and mic in the GUS mixer; the codec chip + will do the real mixing for them. */ + sc->sc_mixcontrol &= ~GUSMASK_LINE_IN; /* 0 enables. */ + sc->sc_mixcontrol |= GUSMASK_MIC_IN; /* 1 enables. */ + outb(sc->sc_iobase+GUS_MIX_CONTROL, sc->sc_mixcontrol); + + ad1848_attach(&sc->sc_codec); + /* turn on pre-MUX microphone gain. */ + ad1848_set_mic_gain(&sc->sc_codec, &vol); + } +} + + +/* + * Return info about the audio device, for the AUDIO_GETINFO ioctl + */ + +int +gus_getdev(addr, dev) + void * addr; + struct audio_device *dev; +{ + *dev = gus_device; + return 0; +} + +/* + * stubs (XXX) + */ + +int +gus_set_in_gain(addr, gain, balance) + caddr_t addr; + u_int gain; + u_char balance; +{ + DPRINTF(("gus_set_in_gain called\n")); + return 0; +} + +int +gus_get_in_gain(addr) + caddr_t addr; +{ + DPRINTF(("gus_get_in_gain called\n")); + return 0; +} + +int +gusmax_set_out_port(addr, port) + void * addr; + int port; +{ + register struct ad1848_softc *sc = addr; + return gus_set_out_port(sc->parent, port); +} + +int +gus_set_out_port(addr, port) + void * addr; + int port; +{ + register struct gus_softc *sc = addr; + DPRINTF(("gus_set_out_port called\n")); + sc->sc_out_port = port; + + return 0; +} + +int +gusmax_get_out_port(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + return gus_get_out_port(sc->parent); +} + +int +gus_get_out_port(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + DPRINTF(("gus_get_out_port() called\n")); + return sc->sc_out_port; +} + +int +gusmax_set_in_port(addr, port) + void * addr; + int port; +{ + register struct ad1848_softc *sc = addr; + DPRINTF(("gusmax_set_in_port: %d\n", port)); + + switch(port) { + case GUSMAX_MONO_LVL: + port = MIC_IN_PORT; + break; + case GUSMAX_LINE_IN_LVL: + port = LINE_IN_PORT; + break; + case GUSMAX_DAC_LVL: + port = AUX1_IN_PORT; + break; + case GUSMAX_MIX_IN: + port = DAC_IN_PORT; + break; + default: + return(EINVAL); + /*NOTREACHED*/ + } + return(ad1848_set_rec_port(sc, port)); +} + +int +gusmax_get_in_port(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + int port = GUSMAX_MONO_LVL; + + switch(ad1848_get_rec_port(sc)) { + case MIC_IN_PORT: + port = GUSMAX_MONO_LVL; + break; + case LINE_IN_PORT: + port = GUSMAX_LINE_IN_LVL; + break; + case DAC_IN_PORT: + port = GUSMAX_MIX_IN; + break; + case AUX1_IN_PORT: + port = GUSMAX_DAC_LVL; + break; + } + + DPRINTF(("gusmax_get_in_port: %d\n", port)); + + return(port); +} + +int +gus_set_in_port(addr, port) + void * addr; + int port; +{ + register struct gus_softc *sc = addr; + DPRINTF(("gus_set_in_port called\n")); + /* + * On the GUS with ICS mixer, the ADC input is after the mixer stage, + * so we can't set the input port. + * + * On the GUS with CS4231 codec/mixer, see gusmax_set_in_port(). + */ + sc->sc_in_port = port; + + return 0; +} + + +int +gus_get_in_port(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + DPRINTF(("gus_get_in_port called\n")); + return sc->sc_in_port; +} + + +int +gusmax_dma_input(addr, buf, size, callback, arg) + void * addr; + void *buf; + int size; + void (*callback)(); + void *arg; +{ + register struct ad1848_softc *sc = addr; + return gus_dma_input(sc->parent, buf, size, callback, arg); +} + +/* + * Start sampling the input source into the requested DMA buffer. + * Called at splgus(), either from top-half or from interrupt handler. + */ +int +gus_dma_input(addr, buf, size, callback, arg) + void * addr; + void *buf; + int size; + void (*callback)(); + void *arg; +{ + register struct gus_softc *sc = addr; + register u_short port = sc->sc_iobase; + register u_char dmac; + DMAPRINTF(("gus_dma_input called\n")); + + /* + * Sample SIZE bytes of data from the card, into buffer at BUF. + */ + + if (sc->sc_precision == 16) + return EINVAL; /* XXX */ + + /* set DMA modes */ + dmac = GUSMASK_SAMPLE_IRQ|GUSMASK_SAMPLE_START; + if (sc->sc_recdrq >= 4) + dmac |= GUSMASK_SAMPLE_DATA16; + if (sc->sc_encoding == AUDIO_ENCODING_ULAW || + sc->sc_encoding == AUDIO_ENCODING_PCM8) + dmac |= GUSMASK_SAMPLE_INVBIT; + if (sc->sc_channels == 2) + dmac |= GUSMASK_SAMPLE_STEREO; + isa_dmastart(B_READ, (caddr_t) buf, size, sc->sc_recdrq); + + DMAPRINTF(("gus_dma_input isa_dmastarted\n")); + sc->sc_flags |= GUS_DMAIN_ACTIVE; + sc->sc_dmainintr = callback; + sc->sc_inarg = arg; + sc->sc_dmaincnt = size; + sc->sc_dmainaddr = buf; + + SELECT_GUS_REG(port, GUSREG_SAMPLE_CONTROL); + outb(port+GUS_DATA_HIGH, dmac); /* Go! */ + + + DMAPRINTF(("gus_dma_input returning\n")); + + return 0; +} + +static int +gus_dmain_intr(sc) + struct gus_softc *sc; +{ + void (*callback) __P((void *)); + void *arg; + + DMAPRINTF(("gus_dmain_intr called\n")); + if (sc->sc_dmainintr) { + isa_dmadone(B_READ, sc->sc_dmainaddr, sc->sc_dmaincnt - 1, + sc->sc_recdrq); + callback = sc->sc_dmainintr; + arg = sc->sc_inarg; + + sc->sc_dmainaddr = 0; + sc->sc_dmaincnt = 0; + sc->sc_dmainintr = 0; + sc->sc_inarg = 0; + + sc->sc_flags &= ~GUS_DMAIN_ACTIVE; + DMAPRINTF(("calling dmain_intr callback %x(%x)\n", callback, arg)); + (*callback)(arg); + return 1; + } else { + DMAPRINTF(("gus_dmain_intr false?\n")); + return 0; /* XXX ??? */ + } +} + +int +gusmax_halt_out_dma(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + return gus_halt_out_dma(sc->parent); +} + + +int +gusmax_halt_in_dma(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + return gus_halt_in_dma(sc->parent); +} + +int +gusmax_cont_out_dma(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + return gus_cont_out_dma(sc->parent); +} + +int +gusmax_cont_in_dma(addr) + void * addr; +{ + register struct ad1848_softc *sc = addr; + return gus_cont_in_dma(sc->parent); +} + +/* + * Stop any DMA output. Called at splgus(). + */ +int +gus_halt_out_dma(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + register u_short port = sc->sc_iobase; + + DMAPRINTF(("gus_halt_out_dma called\n")); + /* + * Make sure the GUS _isn't_ setup for DMA + */ + + SELECT_GUS_REG(port, GUSREG_DMA_CONTROL); + outb(sc->sc_iobase+GUS_DATA_HIGH, 0); + + untimeout(gus_dmaout_timeout, sc); + isa_dmaabort(sc->sc_drq); + sc->sc_flags &= ~(GUS_DMAOUT_ACTIVE|GUS_LOCKED); + sc->sc_dmaoutintr = 0; + sc->sc_outarg = 0; + sc->sc_dmaoutaddr = 0; + sc->sc_dmaoutcnt = 0; + sc->sc_dmabuf = 0; + sc->sc_bufcnt = 0; + sc->sc_playbuf = -1; + /* also stop playing */ + gus_stop_voice(sc, GUS_VOICE_LEFT, 1); + gus_stop_voice(sc, GUS_VOICE_RIGHT, 0); + + return 0; +} + +/* + * Stop any DMA output. Called at splgus(). + */ +int +gus_halt_in_dma(addr) + void * addr; +{ + register struct gus_softc *sc = addr; + register u_short port = sc->sc_iobase; + DMAPRINTF(("gus_halt_in_dma called\n")); + + /* + * Make sure the GUS _isn't_ setup for DMA + */ + + SELECT_GUS_REG(port, GUSREG_SAMPLE_CONTROL); + outb(port+GUS_DATA_HIGH, + inb(port+GUS_DATA_HIGH) & ~(GUSMASK_SAMPLE_START|GUSMASK_SAMPLE_IRQ)); + + isa_dmaabort(sc->sc_recdrq); + sc->sc_flags &= ~GUS_DMAIN_ACTIVE; + sc->sc_dmainintr = 0; + sc->sc_inarg = 0; + sc->sc_dmainaddr = 0; + sc->sc_dmaincnt = 0; + + return 0; +} + +int +gus_cont_out_dma(addr) + void * addr; +{ + DPRINTF(("gus_cont_out_dma called\n")); + return EOPNOTSUPP; +} + +int +gus_cont_in_dma(addr) + void * addr; +{ + DPRINTF(("gus_cont_in_dma called\n")); + return EOPNOTSUPP; +} + + +static int +gus_setfd(addr, flag) + void *addr; + int flag; +{ + if (gus_hw_if.full_duplex == 0) + return ENOTTY; + + return(0); /* nothing fancy to do. */ +} + +static inline int +gus_to_vol(cp, vol) + mixer_ctrl_t *cp; + struct ad1848_volume *vol; +{ + if (cp->un.value.num_channels == 1) { + vol->left = vol->right = cp->un.value.level[AUDIO_MIXER_LEVEL_MONO]; + return(1); + } + else if (cp->un.value.num_channels == 2) { + vol->left = cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT]; + vol->right = cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT]; + return(1); + } + return(0); +} + +static inline int +gus_from_vol(cp, vol) + mixer_ctrl_t *cp; + struct ad1848_volume *vol; +{ + if (cp->un.value.num_channels == 1) { + cp->un.value.level[AUDIO_MIXER_LEVEL_MONO] = vol->left; + return(1); + } + else if (cp->un.value.num_channels == 2) { + cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = vol->left; + cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = vol->right; + return(1); + } + return(0); +} + +static int +gusmax_mixer_get_port(addr, cp) + void *addr; + mixer_ctrl_t *cp; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + struct ad1848_volume vol; + u_char eq; + int error = EINVAL; + + DPRINTF(("gusmax_mixer_get_port: port=%d\n", cp->dev)); + + switch (cp->dev) { +#if 0 /* use mono level instead */ + case GUSMAX_MIC_IN_LVL: /* Microphone */ + if (cp->type == AUDIO_MIXER_VALUE) { + error = ad1848_get_mic_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; +#endif + + case GUSMAX_DAC_LVL: /* dac out */ + if (cp->type == AUDIO_MIXER_VALUE) { + error = ad1848_get_aux1_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_LINE_IN_LVL: /* line in */ + if (cp->type == AUDIO_MIXER_VALUE) { + error = cs4231_get_linein_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_MONO_LVL: /* mono */ + if (cp->type == AUDIO_MIXER_VALUE && + cp->un.value.num_channels == 1) { + error = cs4231_get_mono_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_CD_LVL: /* CD */ + if (cp->type == AUDIO_MIXER_VALUE) { + error = ad1848_get_aux2_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_MONITOR_LVL: /* monitor level */ + if (cp->type == AUDIO_MIXER_VALUE && + cp->un.value.num_channels == 1) { + error = ad1848_get_mon_gain(ac, &vol); + if (!error) + cp->un.value.level[AUDIO_MIXER_LEVEL_MONO] = + vol.left; + } + break; + + case GUSMAX_OUT_LVL: /* output level */ + if (cp->type == AUDIO_MIXER_VALUE) { + error = ad1848_get_out_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_SPEAKER_LVL: /* fake speaker for mute naming */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (sc->sc_mixcontrol & GUSMASK_LINE_OUT) + vol.left = vol.right = AUDIO_MAX_GAIN; + else + vol.left = vol.right = AUDIO_MIN_GAIN; + error = 0; + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_LINE_IN_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ac->line_mute; + error = 0; + } + break; + + + case GUSMAX_DAC_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ac->aux1_mute; + error = 0; + } + break; + + case GUSMAX_CD_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ac->aux2_mute; + error = 0; + } + break; + + case GUSMAX_MONO_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ac->mono_mute; + error = 0; + } + break; + + case GUSMAX_MONITOR_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ac->mon_mute; + error = 0; + } + break; + + case GUSMAX_SPEAKER_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = sc->sc_mixcontrol & GUSMASK_LINE_OUT ? 1 : 0; + error = 0; + } + break; + + case GUSMAX_REC_LVL: /* record level */ + if (cp->type == AUDIO_MIXER_VALUE) { + error = ad1848_get_rec_gain(ac, &vol); + if (!error) + gus_from_vol(cp, &vol); + } + break; + + case GUSMAX_RECORD_SOURCE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ad1848_get_rec_port(ac); + error = 0; + } + break; + + default: + error = ENXIO; + break; + } + + return(error); +} + +static int +gus_mixer_get_port(addr, cp) + void *addr; + mixer_ctrl_t *cp; +{ + register struct gus_softc *sc = addr; + register struct ics2101_softc *ic = &sc->sc_mixer; + struct ad1848_volume vol; + int error = EINVAL; + u_int mute; + + DPRINTF(("gus_mixer_get_port: dev=%d type=%d\n", cp->dev, cp->type)); + + if (!HAS_MIXER(sc) && cp->dev > GUSICS_MASTER_MUTE) + return ENXIO; + + switch (cp->dev) { + + case GUSICS_MIC_IN_MUTE: /* Microphone */ + if (cp->type == AUDIO_MIXER_ENUM) { + if (HAS_MIXER(sc)) + cp->un.ord = ic->sc_mute[GUSMIX_CHAN_MIC][ICSMIX_LEFT]; + else + cp->un.ord = + sc->sc_mixcontrol & GUSMASK_MIC_IN ? 0 : 1; + error = 0; + } + break; + + case GUSICS_LINE_IN_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + if (HAS_MIXER(sc)) + cp->un.ord = ic->sc_mute[GUSMIX_CHAN_LINE][ICSMIX_LEFT]; + else + cp->un.ord = + sc->sc_mixcontrol & GUSMASK_LINE_IN ? 1 : 0; + error = 0; + } + break; + + case GUSICS_MASTER_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + if (HAS_MIXER(sc)) + cp->un.ord = ic->sc_mute[GUSMIX_CHAN_MASTER][ICSMIX_LEFT]; + else + cp->un.ord = + sc->sc_mixcontrol & GUSMASK_LINE_OUT ? 1 : 0; + error = 0; + } + break; + + case GUSICS_DAC_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ic->sc_mute[GUSMIX_CHAN_DAC][ICSMIX_LEFT]; + error = 0; + } + break; + + case GUSICS_CD_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + cp->un.ord = ic->sc_mute[GUSMIX_CHAN_CD][ICSMIX_LEFT]; + error = 0; + } + break; + + case GUSICS_MASTER_LVL: + if (cp->type == AUDIO_MIXER_VALUE) { + vol.left = ic->sc_setting[GUSMIX_CHAN_MASTER][ICSMIX_LEFT]; + vol.right = ic->sc_setting[GUSMIX_CHAN_MASTER][ICSMIX_RIGHT]; + if (gus_from_vol(cp, &vol)) + error = 0; + } + break; + + case GUSICS_MIC_IN_LVL: /* Microphone */ + if (cp->type == AUDIO_MIXER_VALUE) { + vol.left = ic->sc_setting[GUSMIX_CHAN_MIC][ICSMIX_LEFT]; + vol.right = ic->sc_setting[GUSMIX_CHAN_MIC][ICSMIX_RIGHT]; + if (gus_from_vol(cp, &vol)) + error = 0; + } + break; + + case GUSICS_LINE_IN_LVL: /* line in */ + if (cp->type == AUDIO_MIXER_VALUE) { + vol.left = ic->sc_setting[GUSMIX_CHAN_LINE][ICSMIX_LEFT]; + vol.right = ic->sc_setting[GUSMIX_CHAN_LINE][ICSMIX_RIGHT]; + if (gus_from_vol(cp, &vol)) + error = 0; + } + break; + + + case GUSICS_CD_LVL: + if (cp->type == AUDIO_MIXER_VALUE) { + vol.left = ic->sc_setting[GUSMIX_CHAN_CD][ICSMIX_LEFT]; + vol.right = ic->sc_setting[GUSMIX_CHAN_CD][ICSMIX_RIGHT]; + if (gus_from_vol(cp, &vol)) + error = 0; + } + break; + + case GUSICS_DAC_LVL: /* dac out */ + if (cp->type == AUDIO_MIXER_VALUE) { + vol.left = ic->sc_setting[GUSMIX_CHAN_DAC][ICSMIX_LEFT]; + vol.right = ic->sc_setting[GUSMIX_CHAN_DAC][ICSMIX_RIGHT]; + if (gus_from_vol(cp, &vol)) + error = 0; + } + break; + + + case GUSICS_RECORD_SOURCE: + if (cp->type == AUDIO_MIXER_ENUM) { + /* Can't set anything else useful, sigh. */ + cp->un.ord = 0; + } + break; + + default: + return ENXIO; + /*NOTREACHED*/ + } + return error; +} + +static void +gusics_master_mute(ic, mute) + struct ics2101_softc *ic; + int mute; +{ + ics2101_mix_mute(ic, GUSMIX_CHAN_MASTER, ICSMIX_LEFT, mute); + ics2101_mix_mute(ic, GUSMIX_CHAN_MASTER, ICSMIX_RIGHT, mute); +} + +static void +gusics_mic_mute(ic, mute) + struct ics2101_softc *ic; + int mute; +{ + ics2101_mix_mute(ic, GUSMIX_CHAN_MIC, ICSMIX_LEFT, mute); + ics2101_mix_mute(ic, GUSMIX_CHAN_MIC, ICSMIX_RIGHT, mute); +} + +static void +gusics_linein_mute(ic, mute) + struct ics2101_softc *ic; + int mute; +{ + ics2101_mix_mute(ic, GUSMIX_CHAN_LINE, ICSMIX_LEFT, mute); + ics2101_mix_mute(ic, GUSMIX_CHAN_LINE, ICSMIX_RIGHT, mute); +} + +static void +gusics_cd_mute(ic, mute) + struct ics2101_softc *ic; + int mute; +{ + ics2101_mix_mute(ic, GUSMIX_CHAN_CD, ICSMIX_LEFT, mute); + ics2101_mix_mute(ic, GUSMIX_CHAN_CD, ICSMIX_RIGHT, mute); +} + +static void +gusics_dac_mute(ic, mute) + struct ics2101_softc *ic; + int mute; +{ + ics2101_mix_mute(ic, GUSMIX_CHAN_DAC, ICSMIX_LEFT, mute); + ics2101_mix_mute(ic, GUSMIX_CHAN_DAC, ICSMIX_RIGHT, mute); +} + +static int +gusmax_mixer_set_port(addr, cp) + void *addr; + mixer_ctrl_t *cp; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + struct ad1848_volume vol; + int error = EINVAL; + + DPRINTF(("gusmax_mixer_set_port: dev=%d type=%d\n", cp->dev, cp->type)); + + switch (cp->dev) { +#if 0 + case GUSMAX_MIC_IN_LVL: /* Microphone */ + if (cp->type == AUDIO_MIXER_VALUE && + cp->un.value.num_channels == 1) { + /* XXX enable/disable pre-MUX fixed gain */ + if (gus_to_vol(cp, &vol)) + error = ad1848_set_mic_gain(ac, &vol); + } + break; +#endif + + case GUSMAX_DAC_LVL: /* dac out */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) + error = ad1848_set_aux1_gain(ac, &vol); + } + break; + + case GUSMAX_LINE_IN_LVL: /* line in */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) + error = cs4231_set_linein_gain(ac, &vol); + } + break; + + case GUSMAX_MONO_LVL: /* mic/mono in */ + if (cp->type == AUDIO_MIXER_VALUE && + cp->un.value.num_channels == 1) { + if (gus_to_vol(cp, &vol)) + error = cs4231_set_mono_gain(ac, &vol); + } + break; + + case GUSMAX_CD_LVL: /* CD: AUX2 */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) + error = ad1848_set_aux2_gain(ac, &vol); + } + break; + + case GUSMAX_MONITOR_LVL: + if (cp->type == AUDIO_MIXER_VALUE && + cp->un.value.num_channels == 1) { + vol.left = cp->un.value.level[AUDIO_MIXER_LEVEL_MONO]; + error = ad1848_set_mon_gain(ac, &vol); + } + break; + + case GUSMAX_OUT_LVL: /* output volume */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) + error = ad1848_set_out_gain(ac, &vol); + } + break; + + case GUSMAX_SPEAKER_LVL: + if (cp->type == AUDIO_MIXER_VALUE && + cp->un.value.num_channels == 1) { + if (gus_to_vol(cp, &vol)) { + gus_speaker_ctl(sc, vol.left > AUDIO_MIN_GAIN ? + SPKR_ON : SPKR_OFF); + error = 0; + } + } + break; + + case GUSMAX_LINE_IN_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + ac->line_mute = cp->un.ord ? 1 : 0; + DPRINTF(("line mute %d\n", cp->un.ord)); + cs4231_mute_line(ac, ac->line_mute); + gus_linein_ctl(sc, ac->line_mute ? SPKR_OFF : SPKR_ON); + error = 0; + } + break; + + case GUSMAX_DAC_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + ac->aux1_mute = cp->un.ord ? 1 : 0; + DPRINTF(("dac mute %d\n", cp->un.ord)); + ad1848_mute_aux1(ac, ac->aux1_mute); + error = 0; + } + break; + + case GUSMAX_CD_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + ac->aux2_mute = cp->un.ord ? 1 : 0; + DPRINTF(("cd mute %d\n", cp->un.ord)); + ad1848_mute_aux2(ac, ac->aux2_mute); + error = 0; + } + break; + + case GUSMAX_MONO_MUTE: /* Microphone */ + if (cp->type == AUDIO_MIXER_ENUM) { + ac->mono_mute = cp->un.ord ? 1 : 0; + DPRINTF(("mono mute %d\n", cp->un.ord)); + cs4231_mute_mono(ac, ac->mono_mute); + gus_mic_ctl(sc, ac->mono_mute ? SPKR_OFF : SPKR_ON); + error = 0; + } + break; + + case GUSMAX_MONITOR_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + ac->mon_mute = cp->un.ord ? 1 : 0; + DPRINTF(("mono mute %d\n", cp->un.ord)); + cs4231_mute_monitor(ac, ac->mon_mute); + error = 0; + } + break; + + case GUSMAX_SPEAKER_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + gus_speaker_ctl(sc, cp->un.ord ? SPKR_OFF : SPKR_ON); + error = 0; + } + break; + + case GUSMAX_REC_LVL: /* record level */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) + error = ad1848_set_rec_gain(ac, &vol); + } + break; + + case GUSMAX_RECORD_SOURCE: + if (cp->type == AUDIO_MIXER_ENUM) { + error = ad1848_set_rec_port(ac, cp->un.ord); + } + break; + + default: + return ENXIO; + /*NOTREACHED*/ + } + return error; +} + +static int +gus_mixer_set_port(addr, cp) + void *addr; + mixer_ctrl_t *cp; +{ + register struct gus_softc *sc = addr; + register struct ics2101_softc *ic = &sc->sc_mixer; + struct ad1848_volume vol; + int error = EINVAL; + u_int mute; + + DPRINTF(("gus_mixer_set_port: dev=%d type=%d\n", cp->dev, cp->type)); + + if (!HAS_MIXER(sc) && cp->dev > GUSICS_MASTER_MUTE) + return ENXIO; + + switch (cp->dev) { + + case GUSICS_MIC_IN_MUTE: /* Microphone */ + if (cp->type == AUDIO_MIXER_ENUM) { + DPRINTF(("mic mute %d\n", cp->un.ord)); + if (HAS_MIXER(sc)) { + gusics_mic_mute(ic, cp->un.ord); + } + gus_mic_ctl(sc, cp->un.ord ? SPKR_OFF : SPKR_ON); + error = 0; + } + break; + + case GUSICS_LINE_IN_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + DPRINTF(("linein mute %d\n", cp->un.ord)); + if (HAS_MIXER(sc)) { + gusics_linein_mute(ic, cp->un.ord); + } + gus_linein_ctl(sc, cp->un.ord ? SPKR_OFF : SPKR_ON); + error = 0; + } + break; + + case GUSICS_MASTER_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + DPRINTF(("master mute %d\n", cp->un.ord)); + if (HAS_MIXER(sc)) { + gusics_master_mute(ic, cp->un.ord); + } + gus_speaker_ctl(sc, cp->un.ord ? SPKR_OFF : SPKR_ON); + error = 0; + } + break; + + case GUSICS_DAC_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + gusics_dac_mute(ic, cp->un.ord); + error = 0; + } + break; + + case GUSICS_CD_MUTE: + if (cp->type == AUDIO_MIXER_ENUM) { + gusics_cd_mute(ic, cp->un.ord); + error = 0; + } + break; + + case GUSICS_MASTER_LVL: + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) { + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MASTER, + ICSMIX_LEFT, + vol.left); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MASTER, + ICSMIX_RIGHT, + vol.right); + error = 0; + } + } + break; + + case GUSICS_MIC_IN_LVL: /* Microphone */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) { + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MIC, + ICSMIX_LEFT, + vol.left); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MIC, + ICSMIX_RIGHT, + vol.right); + error = 0; + } + } + break; + + case GUSICS_LINE_IN_LVL: /* line in */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) { + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_LINE, + ICSMIX_LEFT, + vol.left); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_LINE, + ICSMIX_RIGHT, + vol.right); + error = 0; + } + } + break; + + + case GUSICS_CD_LVL: + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) { + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_CD, + ICSMIX_LEFT, + vol.left); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_CD, + ICSMIX_RIGHT, + vol.right); + error = 0; + } + } + break; + + case GUSICS_DAC_LVL: /* dac out */ + if (cp->type == AUDIO_MIXER_VALUE) { + if (gus_to_vol(cp, &vol)) { + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_DAC, + ICSMIX_LEFT, + vol.left); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_DAC, + ICSMIX_RIGHT, + vol.right); + error = 0; + } + } + break; + + + case GUSICS_RECORD_SOURCE: + if (cp->type == AUDIO_MIXER_ENUM && cp->un.ord == 0) { + /* Can't set anything else useful, sigh. */ + error = 0; + } + break; + + default: + return ENXIO; + /*NOTREACHED*/ + } + return error; +} + +static int +gusmax_mixer_query_devinfo(addr, dip) + void *addr; + register mixer_devinfo_t *dip; +{ + register struct ad1848_softc *ac = addr; + register struct gus_softc *sc = ac->parent; + + DPRINTF(("gusmax_query_devinfo: index=%d\n", dip->index)); + + switch(dip->index) { + case GUSMAX_MIX_IN: /* mixed MUX input */ + dip->type = AUDIO_MIXER_ENUM; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->prev = dip->next = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioNmixerout); + dip->un.e.num_mem = 0; /* XXX */ + break; + +#if 0 + case GUSMAX_MIC_IN_LVL: /* Microphone */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_MIC_IN_MUTE; + strcpy(dip->label.name, AudioNmicrophone); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; +#endif + + case GUSMAX_MONO_LVL: /* mono/microphone mixer */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_MONO_MUTE; + strcpy(dip->label.name, AudioNmicrophone); + dip->un.v.num_channels = 1; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_DAC_LVL: /* dacout */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_DAC_MUTE; + strcpy(dip->label.name, AudioNdac); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_LINE_IN_LVL: /* line */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_LINE_IN_MUTE; + strcpy(dip->label.name, AudioNline); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_CD_LVL: /* cd */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_CD_MUTE; + strcpy(dip->label.name, AudioNcd); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + + case GUSMAX_MONITOR_LVL: /* monitor level */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_MONITOR_CLASS; + dip->next = GUSMAX_MONITOR_MUTE; + dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioNmonitor); + dip->un.v.num_channels = 1; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_OUT_LVL: /* cs4231 output volume: not useful? */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_MONITOR_CLASS; + dip->prev = dip->next = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioNoutput); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_SPEAKER_LVL: /* fake speaker volume */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_MONITOR_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_SPEAKER_MUTE; + strcpy(dip->label.name, AudioNspeaker); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_LINE_IN_MUTE: + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_LINE_IN_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSMAX_DAC_MUTE: + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_DAC_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSMAX_CD_MUTE: + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_CD_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSMAX_MONO_MUTE: + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_MONO_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSMAX_MONITOR_MUTE: + dip->mixer_class = GUSMAX_OUTPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_MONITOR_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSMAX_SPEAKER_MUTE: + dip->mixer_class = GUSMAX_OUTPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_SPEAKER_LVL; + dip->next = AUDIO_MIXER_LAST; + mute: + strcpy(dip->label.name, AudioNmute); + dip->un.e.num_mem = 2; + strcpy(dip->un.e.member[0].label.name, AudioNoff); + dip->un.e.member[0].ord = 0; + strcpy(dip->un.e.member[1].label.name, AudioNon); + dip->un.e.member[1].ord = 1; + break; + + case GUSMAX_REC_LVL: /* record level */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSMAX_RECORD_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSMAX_RECORD_SOURCE; + strcpy(dip->label.name, AudioNrecord); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSMAX_RECORD_SOURCE: + dip->mixer_class = GUSMAX_RECORD_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSMAX_REC_LVL; + dip->next = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioNsource); + dip->un.e.num_mem = 4; + strcpy(dip->un.e.member[0].label.name, AudioNoutput); + dip->un.e.member[0].ord = GUSMAX_MIX_IN; + strcpy(dip->un.e.member[1].label.name, AudioNmicrophone); + dip->un.e.member[1].ord = GUSMAX_MONO_LVL; + strcpy(dip->un.e.member[2].label.name, AudioNdac); + dip->un.e.member[2].ord = GUSMAX_DAC_LVL; + strcpy(dip->un.e.member[3].label.name, AudioNline); + dip->un.e.member[3].ord = GUSMAX_LINE_IN_LVL; + break; + + case GUSMAX_INPUT_CLASS: /* input class descriptor */ + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSMAX_INPUT_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCInputs); + break; + + case GUSMAX_OUTPUT_CLASS: /* output class descriptor */ + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSMAX_OUTPUT_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCOutputs); + break; + + case GUSMAX_MONITOR_CLASS: /* monitor class descriptor */ + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSMAX_MONITOR_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCMonitor); + break; + + case GUSMAX_RECORD_CLASS: /* record source class */ + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSMAX_RECORD_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCRecord); + break; + + default: + return ENXIO; + /*NOTREACHED*/ + } + DPRINTF(("AUDIO_MIXER_DEVINFO: name=%s\n", dip->label.name)); + return 0; +} + +static int +gus_mixer_query_devinfo(addr, dip) + void *addr; + register mixer_devinfo_t *dip; +{ + register struct gus_softc *sc = addr; + + DPRINTF(("gusmax_query_devinfo: index=%d\n", dip->index)); + + if (!HAS_MIXER(sc) && dip->index > GUSICS_MASTER_MUTE) + return ENXIO; + + switch(dip->index) { + + case GUSICS_MIC_IN_LVL: /* Microphone */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSICS_MIC_IN_MUTE; + strcpy(dip->label.name, AudioNmicrophone); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSICS_LINE_IN_LVL: /* line */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSICS_LINE_IN_MUTE; + strcpy(dip->label.name, AudioNline); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSICS_CD_LVL: /* cd */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSICS_CD_MUTE; + strcpy(dip->label.name, AudioNcd); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSICS_DAC_LVL: /* dacout */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSICS_DAC_MUTE; + strcpy(dip->label.name, AudioNdac); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + case GUSICS_MASTER_LVL: /* master output */ + dip->type = AUDIO_MIXER_VALUE; + dip->mixer_class = GUSICS_OUTPUT_CLASS; + dip->prev = AUDIO_MIXER_LAST; + dip->next = GUSICS_MASTER_MUTE; + strcpy(dip->label.name, AudioNvolume); + dip->un.v.num_channels = 2; + strcpy(dip->un.v.units.name, AudioNvolume); + break; + + + case GUSICS_LINE_IN_MUTE: + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSICS_LINE_IN_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSICS_DAC_MUTE: + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSICS_DAC_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSICS_CD_MUTE: + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSICS_CD_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSICS_MIC_IN_MUTE: + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSICS_MIC_IN_LVL; + dip->next = AUDIO_MIXER_LAST; + goto mute; + + case GUSICS_MASTER_MUTE: + dip->mixer_class = GUSICS_OUTPUT_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = GUSICS_MASTER_LVL; + dip->next = AUDIO_MIXER_LAST; +mute: + strcpy(dip->label.name, AudioNmute); + dip->un.e.num_mem = 2; + strcpy(dip->un.e.member[0].label.name, AudioNoff); + dip->un.e.member[0].ord = 0; + strcpy(dip->un.e.member[1].label.name, AudioNon); + dip->un.e.member[1].ord = 1; + break; + + case GUSICS_RECORD_SOURCE: + dip->mixer_class = GUSICS_RECORD_CLASS; + dip->type = AUDIO_MIXER_ENUM; + dip->prev = dip->next = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioNsource); + dip->un.e.num_mem = 1; + strcpy(dip->un.e.member[0].label.name, AudioNoutput); + dip->un.e.member[0].ord = GUSICS_MASTER_LVL; + break; + + case GUSICS_INPUT_CLASS: + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSICS_INPUT_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCInputs); + break; + + case GUSICS_OUTPUT_CLASS: + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSICS_OUTPUT_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCOutputs); + break; + + case GUSICS_RECORD_CLASS: + dip->type = AUDIO_MIXER_CLASS; + dip->mixer_class = GUSICS_RECORD_CLASS; + dip->next = dip->prev = AUDIO_MIXER_LAST; + strcpy(dip->label.name, AudioCRecord); + break; + + default: + return ENXIO; + /*NOTREACHED*/ + } + DPRINTF(("AUDIO_MIXER_DEVINFO: name=%s\n", dip->label.name)); + return 0; +} + +static int +gus_query_encoding(addr, fp) + void *addr; + struct audio_encoding *fp; +{ + register struct gus_softc *sc = addr; + + switch (fp->index) { + case 0: + strcpy(fp->name, AudioEmulaw); + fp->format_id = AUDIO_ENCODING_ULAW; + break; + case 1: + strcpy(fp->name, AudioEpcm16); + fp->format_id = AUDIO_ENCODING_PCM16; + break; + case 2: + strcpy(fp->name, AudioEpcm8); + fp->format_id = AUDIO_ENCODING_PCM8; + break; + default: + return(EINVAL); + /*NOTREACHED*/ + } + return (0); +} + +/* + * Setup the ICS mixer in "transparent" mode: reset everything to a sensible + * level. Levels as suggested by GUS SDK code. + */ + +static void +gus_init_ics2101(sc) + struct gus_softc *sc; +{ + register u_short port = sc->sc_iobase; + register struct ics2101_softc *ic = &sc->sc_mixer; + sc->sc_mixer.sc_selio = port+GUS_MIXER_SELECT; + sc->sc_mixer.sc_dataio = port+GUS_MIXER_DATA; + sc->sc_mixer.sc_flags = (sc->sc_revision == 5) ? ICS_FLIP : 0; + + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MIC, + ICSMIX_LEFT, + ICSMIX_MIN_ATTN); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MIC, + ICSMIX_RIGHT, + ICSMIX_MIN_ATTN); + /* + * Start with microphone muted by the mixer... + */ + gusics_mic_mute(ic, 1); + + /* ... and enabled by the GUS master mix control */ + gus_mic_ctl(sc, SPKR_ON); + + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_LINE, + ICSMIX_LEFT, + ICSMIX_MIN_ATTN); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_LINE, + ICSMIX_RIGHT, + ICSMIX_MIN_ATTN); + + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_CD, + ICSMIX_LEFT, + ICSMIX_MIN_ATTN); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_CD, + ICSMIX_RIGHT, + ICSMIX_MIN_ATTN); + + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_DAC, + ICSMIX_LEFT, + ICSMIX_MIN_ATTN); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_DAC, + ICSMIX_RIGHT, + ICSMIX_MIN_ATTN); + + ics2101_mix_attenuate(ic, + ICSMIX_CHAN_4, + ICSMIX_LEFT, + ICSMIX_MAX_ATTN); + ics2101_mix_attenuate(ic, + ICSMIX_CHAN_4, + ICSMIX_RIGHT, + ICSMIX_MAX_ATTN); + + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MASTER, + ICSMIX_LEFT, + ICSMIX_MIN_ATTN); + ics2101_mix_attenuate(ic, + GUSMIX_CHAN_MASTER, + ICSMIX_RIGHT, + ICSMIX_MIN_ATTN); + /* unmute other stuff: */ + gusics_cd_mute(ic, 0); + gusics_dac_mute(ic, 0); + gusics_linein_mute(ic, 0); + return; +} + + +#endif /* NGUS */ |