/* $NetBSD: wss.c,v 1.22 1997/04/06 00:54:27 augustss Exp $ */ /* * Copyright (c) 1994 John Brezak * Copyright (c) 1991-1993 Regents of the University of California. * All rights reserved. * * MAD support: * Copyright (c) 1996 Lennart Augustsson * Based on code which is * Copyright (c) 1995 Hannu Savolainen * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the Computer Systems * Engineering Group at Lawrence Berkeley Laboratory. * 4. Neither the name of the University nor of the Laboratory may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* * Copyright by Hannu Savolainen 1994 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Mixer devices */ #define WSS_MIC_IN_LVL 0 #define WSS_LINE_IN_LVL 1 #define WSS_DAC_LVL 2 #define WSS_REC_LVL 3 #define WSS_MON_LVL 4 #define WSS_MIC_IN_MUTE 5 #define WSS_LINE_IN_MUTE 6 #define WSS_DAC_MUTE 7 #define WSS_RECORD_SOURCE 8 /* Classes */ #define WSS_INPUT_CLASS 9 #define WSS_RECORD_CLASS 10 #define WSS_MONITOR_CLASS 11 #ifdef AUDIO_DEBUG #define DPRINTF(x) if (wssdebug) printf x int wssdebug = 0; #else #define DPRINTF(x) #endif struct wss_softc { struct device sc_dev; /* base device */ struct isadev sc_id; /* ISA device */ void *sc_ih; /* interrupt vectoring */ bus_space_tag_t sc_iot; /* tag */ bus_space_handle_t sc_ioh; /* handle */ struct ad1848_softc sc_ad1848; #define wss_irq sc_ad1848.sc_irq #define wss_drq sc_ad1848.sc_drq int mic_mute, cd_mute, dac_mute; int mad_chip_type; /* chip type if MAD emulation of WSS */ bus_space_handle_t sc_mad_ioh; /* handle */ }; struct audio_device wss_device = { "wss,ad1848", "", "WSS" }; int wssopen __P((dev_t, int)); int wss_getdev __P((void *, struct audio_device *)); int wss_setfd __P((void *, int)); int wss_set_out_port __P((void *, int)); int wss_get_out_port __P((void *)); int wss_set_in_port __P((void *, int)); int wss_get_in_port __P((void *)); int wss_mixer_set_port __P((void *, mixer_ctrl_t *)); int wss_mixer_get_port __P((void *, mixer_ctrl_t *)); int wss_query_devinfo __P((void *, mixer_devinfo_t *)); static int wss_to_vol __P((mixer_ctrl_t *, struct ad1848_volume *)); static int wss_from_vol __P((mixer_ctrl_t *, struct ad1848_volume *)); static int madprobe __P((struct wss_softc *, int)); static void madprobedone __P((struct wss_softc *)); /* * Define our interface to the higher level audio driver. */ struct audio_hw_if wss_hw_if = { wssopen, ad1848_close, NULL, ad1848_set_in_sr, ad1848_get_in_sr, ad1848_set_out_sr, ad1848_get_out_sr, ad1848_query_encoding, ad1848_set_format, ad1848_get_encoding, ad1848_get_precision, ad1848_set_channels, ad1848_get_channels, ad1848_round_blocksize, wss_set_out_port, wss_get_out_port, wss_set_in_port, wss_get_in_port, ad1848_commit_settings, NULL, NULL, ad1848_dma_output, ad1848_dma_input, ad1848_halt_out_dma, ad1848_halt_in_dma, ad1848_cont_out_dma, ad1848_cont_in_dma, NULL, wss_getdev, wss_setfd, wss_mixer_set_port, wss_mixer_get_port, wss_query_devinfo, 0, /* not full-duplex */ 0 }; int wssprobe __P((struct device *, void *, void *)); void wssattach __P((struct device *, struct device *, void *)); struct cfattach wss_ca = { sizeof(struct wss_softc), wssprobe, wssattach }; struct cfdriver wss_cd = { NULL, "wss", DV_DULL }; /* * Probe for the Microsoft Sound System hardware. */ int wssprobe(parent, match, aux) struct device *parent; void *match, *aux; { register struct wss_softc *sc = match; register struct isa_attach_args *ia = aux; static u_char interrupt_bits[12] = { -1, -1, -1, -1, -1, -1, -1, 0x08, -1, 0x10, 0x18, 0x20 }; static u_char dma_bits[4] = {1, 2, 0, 3}; sc->sc_iot = ia->ia_iot; if (sc->sc_dev.dv_cfdata->cf_flags & 1) sc->mad_chip_type = madprobe(sc, ia->ia_iobase); else sc->mad_chip_type = MAD_NONE; if (!WSS_BASE_VALID(ia->ia_iobase)) { DPRINTF(("wss: configured iobase %x invalid\n", ia->ia_iobase)); return 0; } /* map the ports upto the AD1488 port */ if (bus_space_map(sc->sc_iot, ia->ia_iobase, WSS_CODEC, 0, &sc->sc_ioh)) return 0; sc->sc_ad1848.sc_iot = sc->sc_iot; sc->sc_ad1848.sc_iobase = ia->ia_iobase + WSS_CODEC; /* Is there an ad1848 chip at (WSS iobase + WSS_CODEC)? */ if (ad1848_probe(&sc->sc_ad1848) == 0) goto bad; ia->ia_iosize = WSS_NPORT; /* Setup WSS interrupt and DMA */ if (!WSS_DRQ_VALID(ia->ia_drq)) { DPRINTF(("wss: configured dma chan %d invalid\n", ia->ia_drq)); goto bad; } sc->wss_drq = ia->ia_drq; #ifdef NEWCONFIG /* * If the IRQ wasn't compiled in, auto-detect it. */ if (ia->ia_irq == IRQUNK) { ia->ia_irq = isa_discoverintr(ad1848_forceintr, &sc->sc_ad1848); if (!WSS_IRQ_VALID(ia->ia_irq)) { printf("wss: couldn't auto-detect interrupt\n"); goto bad; } } else #endif if (!WSS_IRQ_VALID(ia->ia_irq)) { DPRINTF(("wss: configured interrupt %d invalid\n", ia->ia_irq)); goto bad; } sc->wss_irq = ia->ia_irq; bus_space_write_1(sc->sc_iot, sc->sc_ioh, WSS_CONFIG, (interrupt_bits[ia->ia_irq] | dma_bits[ia->ia_drq])); if (sc->mad_chip_type != MAD_NONE) madprobedone(sc); return 1; bad: bus_space_unmap(sc->sc_iot, sc->sc_ioh, WSS_CODEC); if (sc->mad_chip_type != MAD_NONE) bus_space_unmap(sc->sc_iot, sc->sc_mad_ioh, MAD_NPORT); return 0; } /* * Attach hardware to driver, attach hardware driver to audio * pseudo-device driver . */ void wssattach(parent, self, aux) struct device *parent, *self; void *aux; { register struct wss_softc *sc = (struct wss_softc *)self; struct isa_attach_args *ia = (struct isa_attach_args *)aux; int version; int err; sc->sc_ad1848.sc_recdrq = ia->ia_drq; #ifdef NEWCONFIG isa_establish(&sc->sc_id, &sc->sc_dev); #endif sc->sc_ih = isa_intr_establish(ia->ia_ic, ia->ia_irq, IST_EDGE, IPL_AUDIO, ad1848_intr, &sc->sc_ad1848); ad1848_attach(&sc->sc_ad1848); version = bus_space_read_1(sc->sc_iot, sc->sc_ioh, WSS_STATUS) & WSS_VERSMASK; printf(" (vers %d)", version); if (sc->mad_chip_type != MAD_NONE) printf(", %s", sc->mad_chip_type == MAD_82C929 ? "82C929" : sc->mad_chip_type == MAD_82C928 ? "82C928" : "OTI-601D"); printf("\n"); sc->sc_ad1848.parent = sc; if ((err = audio_hardware_attach(&wss_hw_if, &sc->sc_ad1848)) != 0) printf("wss: could not attach to audio pseudo-device driver (%d)\n", err); } static int wss_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 int wss_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); } int wssopen(dev, flags) dev_t dev; int flags; { struct wss_softc *sc; int unit = AUDIOUNIT(dev); if (unit >= wss_cd.cd_ndevs) return ENODEV; sc = wss_cd.cd_devs[unit]; if (!sc) return ENXIO; return ad1848_open(&sc->sc_ad1848, dev, flags); } int wss_getdev(addr, retp) void *addr; struct audio_device *retp; { *retp = wss_device; return 0; } int wss_setfd(addr, flag) void *addr; int flag; { /* Can't do full-duplex */ return(ENOTTY); } int wss_set_out_port(addr, port) void *addr; int port; { DPRINTF(("wss_set_out_port:\n")); return(EINVAL); } int wss_get_out_port(addr) void *addr; { DPRINTF(("wss_get_out_port:\n")); return(WSS_DAC_LVL); } int wss_set_in_port(addr, port) void *addr; int port; { register struct ad1848_softc *ac = addr; DPRINTF(("wss_set_in_port: %d\n", port)); switch(port) { case WSS_MIC_IN_LVL: port = MIC_IN_PORT; break; case WSS_LINE_IN_LVL: port = LINE_IN_PORT; break; case WSS_DAC_LVL: port = DAC_IN_PORT; break; default: return(EINVAL); /*NOTREACHED*/ } return(ad1848_set_rec_port(ac, port)); } int wss_get_in_port(addr) void *addr; { register struct ad1848_softc *ac = addr; int port = WSS_MIC_IN_LVL; switch(ad1848_get_rec_port(ac)) { case MIC_IN_PORT: port = WSS_MIC_IN_LVL; break; case LINE_IN_PORT: port = WSS_LINE_IN_LVL; break; case DAC_IN_PORT: port = WSS_DAC_LVL; break; } DPRINTF(("wss_get_in_port: %d\n", port)); return(port); } int wss_mixer_set_port(addr, cp) void *addr; mixer_ctrl_t *cp; { register struct ad1848_softc *ac = addr; register struct wss_softc *sc = ac->parent; struct ad1848_volume vol; int error = EINVAL; DPRINTF(("wss_mixer_set_port: dev=%d type=%d\n", cp->dev, cp->type)); switch (cp->dev) { case WSS_MIC_IN_LVL: /* Microphone */ if (cp->type == AUDIO_MIXER_VALUE) { if (wss_to_vol(cp, &vol)) error = ad1848_set_aux2_gain(ac, &vol); } break; case WSS_MIC_IN_MUTE: /* Microphone */ if (cp->type == AUDIO_MIXER_ENUM) { sc->mic_mute = cp->un.ord; DPRINTF(("mic mute %d\n", cp->un.ord)); error = 0; } break; case WSS_LINE_IN_LVL: /* linein/CD */ if (cp->type == AUDIO_MIXER_VALUE) { if (wss_to_vol(cp, &vol)) error = ad1848_set_aux1_gain(ac, &vol); } break; case WSS_LINE_IN_MUTE: /* linein/CD */ if (cp->type == AUDIO_MIXER_ENUM) { sc->cd_mute = cp->un.ord; DPRINTF(("CD mute %d\n", cp->un.ord)); error = 0; } break; case WSS_DAC_LVL: /* dac out */ if (cp->type == AUDIO_MIXER_VALUE) { if (wss_to_vol(cp, &vol)) error = ad1848_set_out_gain(ac, &vol); } break; case WSS_DAC_MUTE: /* dac out */ if (cp->type == AUDIO_MIXER_ENUM) { sc->dac_mute = cp->un.ord; DPRINTF(("DAC mute %d\n", cp->un.ord)); error = 0; } break; case WSS_REC_LVL: /* record level */ if (cp->type == AUDIO_MIXER_VALUE) { if (wss_to_vol(cp, &vol)) error = ad1848_set_rec_gain(ac, &vol); } break; case WSS_RECORD_SOURCE: if (cp->type == AUDIO_MIXER_ENUM) { error = ad1848_set_rec_port(ac, cp->un.ord); } break; case WSS_MON_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; default: return ENXIO; /*NOTREACHED*/ } return 0; } int wss_mixer_get_port(addr, cp) void *addr; mixer_ctrl_t *cp; { register struct ad1848_softc *ac = addr; register struct wss_softc *sc = ac->parent; struct ad1848_volume vol; int error = EINVAL; DPRINTF(("wss_mixer_get_port: port=%d\n", cp->dev)); switch (cp->dev) { case WSS_MIC_IN_LVL: /* Microphone */ if (cp->type == AUDIO_MIXER_VALUE) { error = ad1848_get_aux2_gain(ac, &vol); if (!error) wss_from_vol(cp, &vol); } break; case WSS_MIC_IN_MUTE: if (cp->type == AUDIO_MIXER_ENUM) { cp->un.ord = sc->mic_mute; error = 0; } break; case WSS_LINE_IN_LVL: /* linein/CD */ if (cp->type == AUDIO_MIXER_VALUE) { error = ad1848_get_aux1_gain(ac, &vol); if (!error) wss_from_vol(cp, &vol); } break; case WSS_LINE_IN_MUTE: if (cp->type == AUDIO_MIXER_ENUM) { cp->un.ord = sc->cd_mute; error = 0; } break; case WSS_DAC_LVL: /* dac out */ if (cp->type == AUDIO_MIXER_VALUE) { error = ad1848_get_out_gain(ac, &vol); if (!error) wss_from_vol(cp, &vol); } break; case WSS_DAC_MUTE: if (cp->type == AUDIO_MIXER_ENUM) { cp->un.ord = sc->dac_mute; error = 0; } break; case WSS_REC_LVL: /* record level */ if (cp->type == AUDIO_MIXER_VALUE) { error = ad1848_get_rec_gain(ac, &vol); if (!error) wss_from_vol(cp, &vol); } break; case WSS_RECORD_SOURCE: if (cp->type == AUDIO_MIXER_ENUM) { cp->un.ord = ad1848_get_rec_port(ac); error = 0; } break; case WSS_MON_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; default: error = ENXIO; break; } return(error); } int wss_query_devinfo(addr, dip) void *addr; register mixer_devinfo_t *dip; { DPRINTF(("wss_query_devinfo: index=%d\n", dip->index)); switch(dip->index) { case WSS_MIC_IN_LVL: /* Microphone */ dip->type = AUDIO_MIXER_VALUE; dip->mixer_class = WSS_INPUT_CLASS; dip->prev = AUDIO_MIXER_LAST; dip->next = WSS_MIC_IN_MUTE; strcpy(dip->label.name, AudioNmicrophone); dip->un.v.num_channels = 2; strcpy(dip->un.v.units.name, AudioNvolume); break; case WSS_LINE_IN_LVL: /* line/CD */ dip->type = AUDIO_MIXER_VALUE; dip->mixer_class = WSS_INPUT_CLASS; dip->prev = AUDIO_MIXER_LAST; dip->next = WSS_LINE_IN_MUTE; strcpy(dip->label.name, AudioNcd); dip->un.v.num_channels = 2; strcpy(dip->un.v.units.name, AudioNvolume); break; case WSS_DAC_LVL: /* dacout */ dip->type = AUDIO_MIXER_VALUE; dip->mixer_class = WSS_INPUT_CLASS; dip->prev = AUDIO_MIXER_LAST; dip->next = WSS_DAC_MUTE; strcpy(dip->label.name, AudioNdac); dip->un.v.num_channels = 2; strcpy(dip->un.v.units.name, AudioNvolume); break; case WSS_REC_LVL: /* record level */ dip->type = AUDIO_MIXER_VALUE; dip->mixer_class = WSS_RECORD_CLASS; dip->prev = AUDIO_MIXER_LAST; dip->next = WSS_RECORD_SOURCE; strcpy(dip->label.name, AudioNrecord); dip->un.v.num_channels = 2; strcpy(dip->un.v.units.name, AudioNvolume); break; case WSS_MON_LVL: /* monitor level */ dip->type = AUDIO_MIXER_VALUE; dip->mixer_class = WSS_MONITOR_CLASS; dip->next = 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 WSS_INPUT_CLASS: /* input class descriptor */ dip->type = AUDIO_MIXER_CLASS; dip->mixer_class = WSS_INPUT_CLASS; dip->next = dip->prev = AUDIO_MIXER_LAST; strcpy(dip->label.name, AudioCInputs); break; case WSS_MONITOR_CLASS: /* monitor class descriptor */ dip->type = AUDIO_MIXER_CLASS; dip->mixer_class = WSS_MONITOR_CLASS; dip->next = dip->prev = AUDIO_MIXER_LAST; strcpy(dip->label.name, AudioNmonitor); break; case WSS_RECORD_CLASS: /* record source class */ dip->type = AUDIO_MIXER_CLASS; dip->mixer_class = WSS_RECORD_CLASS; dip->next = dip->prev = AUDIO_MIXER_LAST; strcpy(dip->label.name, AudioNrecord); break; case WSS_MIC_IN_MUTE: dip->mixer_class = WSS_INPUT_CLASS; dip->type = AUDIO_MIXER_ENUM; dip->prev = WSS_MIC_IN_LVL; dip->next = AUDIO_MIXER_LAST; goto mute; case WSS_LINE_IN_MUTE: dip->mixer_class = WSS_INPUT_CLASS; dip->type = AUDIO_MIXER_ENUM; dip->prev = WSS_LINE_IN_LVL; dip->next = AUDIO_MIXER_LAST; goto mute; case WSS_DAC_MUTE: dip->mixer_class = WSS_INPUT_CLASS; dip->type = AUDIO_MIXER_ENUM; dip->prev = WSS_DAC_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 WSS_RECORD_SOURCE: dip->mixer_class = WSS_RECORD_CLASS; dip->type = AUDIO_MIXER_ENUM; dip->prev = WSS_REC_LVL; dip->next = AUDIO_MIXER_LAST; strcpy(dip->label.name, AudioNsource); dip->un.e.num_mem = 3; strcpy(dip->un.e.member[0].label.name, AudioNmicrophone); dip->un.e.member[0].ord = WSS_MIC_IN_LVL; strcpy(dip->un.e.member[1].label.name, AudioNcd); dip->un.e.member[1].ord = WSS_LINE_IN_LVL; strcpy(dip->un.e.member[2].label.name, AudioNdac); dip->un.e.member[2].ord = WSS_DAC_LVL; break; default: return ENXIO; /*NOTREACHED*/ } DPRINTF(("AUDIO_MIXER_DEVINFO: name=%s\n", dip->label.name)); return 0; } /* * Initialization code for OPTi MAD16 compatible audio chips. Including * * OPTi 82C928 MAD16 (replaced by C929) * OAK OTI-601D Mozart * OPTi 82C929 MAD16 Pro * */ static unsigned int mad_read __P((struct wss_softc *, int, int)); static void mad_write __P((struct wss_softc *, int, int, int)); static int detect_mad16 __P((struct wss_softc *, int)); static unsigned int mad_read(sc, chip_type, port) struct wss_softc *sc; int chip_type; int port; { unsigned int tmp; int s = splaudio(); /* don't want an interrupt between outb&inb */ switch (chip_type) { /* Output password */ case MAD_82C928: case MAD_OTI601D: bus_space_write_1(sc->sc_iot, sc->sc_mad_ioh, MC_PASSWD_REG, M_PASSWD_928); break; case MAD_82C929: bus_space_write_1(sc->sc_iot, sc->sc_mad_ioh, MC_PASSWD_REG, M_PASSWD_929); break; } tmp = bus_space_read_1(sc->sc_iot, sc->sc_mad_ioh, port); splx(s); return tmp; } static void mad_write(sc, chip_type, port, value) struct wss_softc *sc; int chip_type; int port; int value; { int s = splaudio(); /* don't want an interrupt between outb&outb */ switch (chip_type) { /* Output password */ case MAD_82C928: case MAD_OTI601D: bus_space_write_1(sc->sc_iot, sc->sc_mad_ioh, MC_PASSWD_REG, M_PASSWD_928); break; case MAD_82C929: bus_space_write_1(sc->sc_iot, sc->sc_mad_ioh, MC_PASSWD_REG, M_PASSWD_929); break; } bus_space_write_1(sc->sc_iot, sc->sc_mad_ioh, port, value & 0xff); splx(s); } static int detect_mad16(sc, chip_type) struct wss_softc *sc; int chip_type; { unsigned char tmp, tmp2; /* * Check that reading a register doesn't return bus float (0xff) * when the card is accessed using password. This may fail in case * the card is in low power mode. Normally at least the power saving mode * bit should be 0. */ if ((tmp = mad_read(sc, chip_type, MC1_PORT)) == 0xff) { DPRINTF(("MC1_PORT returned 0xff\n")); return 0; } /* * Now check that the gate is closed on first I/O after writing * the password. (This is how a MAD16 compatible card works). */ if ((tmp2 = bus_space_read_1(sc->sc_iot, sc->sc_mad_ioh, MC1_PORT)) == tmp) { /* It didn't close */ DPRINTF(("MC1_PORT didn't close after read (0x%02x)\n", tmp2)); return 0; } mad_write(sc, chip_type, MC1_PORT, tmp ^ 0x80); /* Toggle a bit */ /* Compare the bit */ if ((tmp2 = mad_read(sc, chip_type, MC1_PORT)) != (tmp ^ 0x80)) { mad_write(sc, chip_type, MC1_PORT, tmp); /* Restore */ DPRINTF(("Bit revert test failed (0x%02x, 0x%02x)\n", tmp, tmp2)); return 0; } mad_write(sc, chip_type, MC1_PORT, tmp); /* Restore */ return 1; } static int madprobe(sc, iobase) struct wss_softc *sc; int iobase; { static int valid_ports[M_WSS_NPORTS] = { M_WSS_PORT0, M_WSS_PORT1, M_WSS_PORT2, M_WSS_PORT3 }; int i; int chip_type; if (bus_space_map(sc->sc_iot, MAD_BASE, MAD_NPORT, 0, &sc->sc_mad_ioh)) return MAD_NONE; DPRINTF(("mad: Detect using password = 0xE2\n")); if (!detect_mad16(sc, MAD_82C928)) { /* No luck. Try different model */ DPRINTF(("mad: Detect using password = 0xE3\n")); if (!detect_mad16(sc, MAD_82C929)) goto bad; chip_type = MAD_82C929; DPRINTF(("mad: 82C929 detected\n")); } else { if ((mad_read(sc, MAD_82C928, MC3_PORT) & 0x03) == 0x03) { DPRINTF(("mad: Mozart detected\n")); chip_type = MAD_OTI601D; } else { DPRINTF(("mad: 82C928 detected?\n")); chip_type = MAD_82C928; } } #ifdef AUDIO_DEBUG for (i = MC1_PORT; i <= MC7_PORT; i++) printf("mad: port %03x = %02x\n", i, mad_read(sc, chip_type, i)); #endif /* Set the WSS address. */ for (i = 0; i < 4; i++) if (valid_ports[i] == iobase) break; if (i > 3) { /* Not a valid port */ printf("mad: Bad WSS base address 0x%x\n", iobase); goto bad; } /* enable WSS emulation at the I/O port, keep joystck */ mad_write(sc, chip_type, MC1_PORT, M_WSS_PORT_SELECT(i)); mad_write(sc, chip_type, MC2_PORT, 0x03); /* ? */ mad_write(sc, chip_type, MC3_PORT, 0xf0); /* Disable SB */ return chip_type; bad: bus_space_unmap(sc->sc_iot, sc->sc_mad_ioh, MAD_NPORT); return MAD_NONE; } static void madprobedone(sc) struct wss_softc *sc; { int chip_type = sc->mad_chip_type; unsigned char cs4231_mode; cs4231_mode = strncmp(sc->sc_ad1848.chip_name, "CS4248", 6) == 0 || strncmp(sc->sc_ad1848.chip_name, "CS4231", 6) == 0 ? 0x02 : 0; if (chip_type == MAD_82C929) { mad_write(sc, chip_type, MC4_PORT, 0xa2); mad_write(sc, chip_type, MC5_PORT, 0xA5 | cs4231_mode); mad_write(sc, chip_type, MC6_PORT, 0x03); /* Disable MPU401 */ } else { mad_write(sc, chip_type, MC4_PORT, 0x02); mad_write(sc, chip_type, MC5_PORT, 0x30 | cs4231_mode); } #ifdef AUDIO_DEBUG if (wssdebug) { int i; for (i = MC1_PORT; i <= MC7_PORT; i++) DPRINTF(("port %03x after init = %02x\n", i, mad_read(sc, chip_type, i))); } #endif }