/* $OpenBSD: ad1848.c,v 1.44 2015/06/25 06:43:46 ratchov Exp $ */ /* $NetBSD: ad1848.c,v 1.45 1998/01/30 02:02:38 augustss Exp $ */ /* * Copyright (c) 1994 John Brezak * Copyright (c) 1991-1993 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the Computer Systems * Engineering Group at Lawrence Berkeley Laboratory. * 4. 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. * */ /* * Portions of this code are from the VOXware support for the ad1848 * by Hannu Savolainen * * Portions also supplied from the SoundBlaster driver for NetBSD. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef AUDIO_DEBUG #define DPRINTF(x) do { if (ad1848debug) printf x; } while (0); int ad1848debug = 0; #else #define DPRINTF(x) #endif /* * Initial values for the indirect registers of CS4248/AD1848. */ static int ad1848_init_values[] = { GAIN_12 | INPUT_MIC_GAIN_ENABLE, /* Left Input Control */ GAIN_12 | INPUT_MIC_GAIN_ENABLE, /* Right Input Control */ ATTEN_12, /* Left Aux #1 Input Control */ ATTEN_12, /* Right Aux #1 Input Control */ ATTEN_12, /* Left Aux #2 Input Control */ ATTEN_12, /* Right Aux #2 Input Control */ /* bits 5-0 are attenuation select */ ATTEN_12, /* Left DAC output Control */ ATTEN_12, /* Right DAC output Control */ CLOCK_XTAL1 | FMT_PCM8, /* Clock and Data Format */ SINGLE_DMA | AUTO_CAL_ENABLE, /* Interface Config */ INTERRUPT_ENABLE, /* Pin control */ 0x00, /* Test and Init */ MODE2, /* Misc control */ ATTEN_0 << 2, /* Digital Mix Control */ 0, /* Upper base Count */ 0, /* Lower base Count */ /* These are for CS4231 &c. only (additional registers): */ 0, /* Alt feature 1 */ 0, /* Alt feature 2 */ ATTEN_12, /* Left line in */ ATTEN_12, /* Right line in */ 0, /* Timer low */ 0, /* Timer high */ 0, /* unused */ 0, /* unused */ 0, /* IRQ status */ 0, /* unused */ /* Mono input (a.k.a speaker) (mic) Control */ MONO_INPUT_MUTE|ATTEN_6, /* mute speaker by default */ 0, /* unused */ 0, /* record format */ 0, /* Crystal Clock Select */ 0, /* upper record count */ 0 /* lower record count */ }; static struct audio_params ad1848_audio_default = {48000, AUDIO_ENCODING_SLINEAR_LE, 16, 2, 1, 2}; void ad1848_reset(struct ad1848_softc *); int ad1848_set_speed(struct ad1848_softc *, u_long *); void ad1848_mute_monitor(void *, int); /* indirect register access */ static int ad_read(struct ad1848_softc *, int); static void ad_write(struct ad1848_softc *, int, int); static void ad_set_MCE(struct ad1848_softc *, int); static void wait_for_calibration(struct ad1848_softc *); /* direct register (AD1848_{IADDR,IDATA,STATUS} only) access */ #define ADREAD(sc, addr) bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_iooffs+(addr)) #define ADWRITE(sc, addr, data) bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_iooffs+(addr), (data)) static int ad_read(struct ad1848_softc *sc, int reg) { int x; ADWRITE(sc, AD1848_IADDR, (reg & 0xff) | sc->MCE_bit); x = ADREAD(sc, AD1848_IDATA); /* printf("(%02x<-%02x) ", reg|sc->MCE_bit, x); */ return x; } static void ad_write(struct ad1848_softc *sc, int reg, int data) { ADWRITE(sc, AD1848_IADDR, (reg & 0xff) | sc->MCE_bit); ADWRITE(sc, AD1848_IDATA, data & 0xff); /* printf("(%02x->%02x) ", reg|sc->MCE_bit, data); */ } static void ad_set_MCE(struct ad1848_softc *sc, int state) { if (state) sc->MCE_bit = MODE_CHANGE_ENABLE; else sc->MCE_bit = 0; ADWRITE(sc, AD1848_IADDR, sc->MCE_bit); } static void wait_for_calibration(struct ad1848_softc *sc) { int timeout; DPRINTF(("ad1848: Auto calibration started.\n")); /* * Wait until the auto calibration process has finished. * * 1) Wait until the chip becomes ready (reads don't return SP_IN_INIT). * 2) Wait until the ACI bit of I11 goes hi and then lo. * a) With AD1848 alike, ACI goes hi within 5 sample cycles * and remains hi for ~384 sample periods. * b) With CS4231 alike, ACI goes hi immediately and remains * hi for at least 168 sample periods. */ timeout = AD1848_TIMO; while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) timeout--; if (ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) DPRINTF(("ad1848: Auto calibration timed out(1).\n")); if (!(sc->sc_flags & AD1848_FLAG_32REGS)) { timeout = AD1848_TIMO; while (timeout > 0 && !(ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG)) timeout--; if (!(ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG)) { DPRINTF(("ad1848: Auto calibration timed out(2).\n")); } } timeout = AD1848_TIMO; while (timeout > 0 && ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG) timeout--; if (ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG) DPRINTF(("ad1848: Auto calibration timed out(3).\n")); } #ifdef AUDIO_DEBUG void ad1848_dump_regs(struct ad1848_softc *); void ad1848_dump_regs(struct ad1848_softc *sc) { int i; u_char r; printf("ad1848 status=%02x", ADREAD(sc, AD1848_STATUS)); printf(" regs: "); for (i = 0; i < 16; i++) { r = ad_read(sc, i); printf("%02x ", r); } if (sc->mode == 2) { for (i = 16; i < 32; i++) { r = ad_read(sc, i); printf("%02x ", r); } } printf("\n"); } #endif /* * Map and probe for the ad1848 chip */ int ad1848_mapprobe(struct ad1848_softc *sc, int iobase) { if (!AD1848_BASE_VALID(iobase)) { #ifdef AUDIO_DEBUG printf("ad1848: configured iobase %04x invalid\n", iobase); #endif return 0; } sc->sc_iooffs = 0; /* Map the AD1848 ports */ if (bus_space_map(sc->sc_iot, iobase, AD1848_NPORT, 0, &sc->sc_ioh)) return 0; if (!ad1848_probe(sc)) { bus_space_unmap(sc->sc_iot, sc->sc_ioh, AD1848_NPORT); return 0; } else return 1; } /* * Probe for the ad1848 chip */ int ad1848_probe(struct ad1848_softc *sc) { u_char tmp, tmp1 = 0xff, tmp2 = 0xff; #if 0 int i; #endif /* Is there an ad1848 chip ? */ sc->MCE_bit = MODE_CHANGE_ENABLE; sc->mode = 1; /* MODE 1 = original ad1848/ad1846/cs4248 */ sc->sc_flags = 0; /* * Check that the I/O address is in use. * * The SP_IN_INIT bit of the base I/O port is known to be 0 after the * chip has performed its power-on initialization. Just assume * this has happened before the OS is starting. * * If the I/O address is unused, inb() typically returns 0xff. */ tmp = ADREAD(sc, AD1848_IADDR); if (tmp & SP_IN_INIT) { /* Not a AD1848 */ #if 0 DPRINTF(("ad_detect_A %x\n", tmp)); #endif goto bad; } /* * Test if it's possible to change contents of the indirect registers. * Registers 0 and 1 are ADC volume registers. The bit 0x10 is read * only so try to avoid using it. */ ad_write(sc, 0, 0xaa); ad_write(sc, 1, 0x45); /* 0x55 with bit 0x10 clear */ if ((tmp1 = ad_read(sc, 0)) != 0xaa || (tmp2 = ad_read(sc, 1)) != 0x45) { DPRINTF(("ad_detect_B (%x/%x)\n", tmp1, tmp2)); goto bad; } ad_write(sc, 0, 0x45); ad_write(sc, 1, 0xaa); if ((tmp1 = ad_read(sc, 0)) != 0x45 || (tmp2 = ad_read(sc, 1)) != 0xaa) { DPRINTF(("ad_detect_C (%x/%x)\n", tmp1, tmp2)); goto bad; } /* * The indirect register I12 has some read only bits. Lets * try to change them. */ tmp = ad_read(sc, SP_MISC_INFO); ad_write(sc, SP_MISC_INFO, (~tmp) & 0x0f); if ((tmp & 0x0f) != ((tmp1 = ad_read(sc, SP_MISC_INFO)) & 0x0f)) { DPRINTF(("ad_detect_D (%x)\n", tmp1)); goto bad; } /* * MSB and 4 LSBs of the reg I12 tell the chip revision. * * A preliminary version of the AD1846 data sheet stated that it * used an ID field of 0x0B. The current version, however, * states that the AD1846 uses ID 0x0A, just like the AD1848K. * * this switch statement will need updating as newer clones arrive.... */ switch (tmp1 & 0x8f) { case 0x09: sc->chip_name = "AD1848J"; break; case 0x0A: sc->chip_name = "AD1848K"; break; #if 0 /* See above */ case 0x0B: sc->chip_name = "AD1846"; break; #endif case 0x81: sc->chip_name = "CS4248revB"; /* or CS4231 rev B; see below */ break; case 0x89: sc->chip_name = "CS4248"; break; case 0x8A: sc->chip_name = "broken"; /* CS4231/AD1845; see below */ break; default: sc->chip_name = "unknown"; DPRINTF(("ad1848: unknown codec version %#02X\n", (tmp1 & 0x8f))); } #if 0 /* * XXX I don't know why, but this probe fails on an otherwise * well-working AW35/pro card, so I'll just take it out for now. * [niklas@openbsd.org] */ /* * The original AD1848/CS4248 has just 16 indirect registers. This * means that I0 and I16 should return the same value (etc.). * Ensure that the Mode2 enable bit of I12 is 0. Otherwise this test * fails with CS4231, AD1845, etc. */ ad_write(sc, SP_MISC_INFO, 0); /* Mode2 = disabled */ for (i = 0; i < 16; i++) { if ((tmp1 = ad_read(sc, i)) != (tmp2 = ad_read(sc, i + 16))) { if (i != SP_TEST_AND_INIT) { DPRINTF(("ad_detect_F(%d/%x/%x)\n", i, tmp1, tmp2)); goto bad; } } } #endif /* * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit * The bit 0x80 is always 1 in CS4248, CS4231, and AD1845. */ ad_write(sc, SP_MISC_INFO, MODE2); /* Set mode2, clear 0x80 */ tmp1 = ad_read(sc, SP_MISC_INFO); if ((tmp1 & 0xc0) == (0x80 | MODE2)) { /* * CS4231 or AD1845 detected - is it? * * Verify that setting I2 doesn't change I18. */ ad_write(sc, 18, 0x88); /* Set I18 to known value */ ad_write(sc, 2, 0x45); if ((tmp2 = ad_read(sc, 18)) != 0x45) { /* No change -> CS4231? */ ad_write(sc, 2, 0xaa); if ((tmp2 = ad_read(sc, 18)) == 0xaa) { /* Rotten bits? */ DPRINTF(("ad_detect_H(%x)\n", tmp2)); goto bad; } /* * It's a CS4231, or another clone with 32 registers. * Let's find out which by checking I25. */ if ((tmp1 & 0x8f) == 0x8a) { tmp1 = ad_read(sc, CS_VERSION_ID); switch (tmp1 & 0xe7) { case 0xA0: sc->chip_name = "CS4231A"; break; case 0x80: /* I25 no good, AD1845 same as CS4231 */ sc->chip_name = "CS4231 or AD1845"; break; case 0x82: sc->chip_name = "CS4232"; break; case 0xa2: sc->chip_name = "CS4232C"; break; case 0x03: sc->chip_name = "CS4236/CS4236B"; break; } } sc->mode = 2; sc->sc_flags |= AD1848_FLAG_32REGS; } } /* Wait for 1848 to init */ while(ADREAD(sc, AD1848_IADDR) & SP_IN_INIT) ; /* Wait for 1848 to autocal */ ADWRITE(sc, AD1848_IADDR, SP_TEST_AND_INIT); while(ADREAD(sc, AD1848_IDATA) & AUTO_CAL_IN_PROG) ; return 1; bad: return 0; } /* Unmap the I/O ports */ void ad1848_unmap(struct ad1848_softc *sc) { bus_space_unmap(sc->sc_iot, sc->sc_ioh, AD1848_NPORT); } /* * Attach hardware to driver, attach hardware driver to audio * pseudo-device driver . */ void ad1848_attach(struct ad1848_softc *sc) { int i; struct ad1848_volume vol_mid = {220, 220}; struct ad1848_volume vol_0 = {0, 0}; struct audio_params pparams, rparams; int timeout; sc->sc_playrun = 0; sc->sc_recrun = 0; if (sc->sc_drq != -1) { if (isa_dmamap_create(sc->sc_isa, sc->sc_drq, MAX_ISADMA, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW)) { printf("ad1848_attach: can't create map for drq %d\n", sc->sc_drq); return; } } if (sc->sc_recdrq != -1 && sc->sc_recdrq != sc->sc_drq) { if (isa_dmamap_create(sc->sc_isa, sc->sc_recdrq, MAX_ISADMA, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW)) { printf("ad1848_attach: can't create map for second drq %d\n", sc->sc_recdrq); return; } } /* Initialize the ad1848... */ for (i = 0; i < 0x10; i++) { ad_write(sc, i, ad1848_init_values[i]); timeout = AD1848_TIMO; while (timeout > 0 && ADREAD(sc, AD1848_IADDR) & SP_IN_INIT) timeout--; } /* need 2 separate drqs for mode 2 */ if ((sc->mode == 2) && ((sc->sc_recdrq == -1) || (sc->sc_recdrq == sc->sc_drq))) { ad_write(sc, SP_MISC_INFO, ad_read(sc, SP_MISC_INFO) & ~MODE2); if (!(ad_read(sc, SP_MISC_INFO) & MODE2)) sc->mode = 1; } /* ...and additional CS4231 stuff too */ if (sc->mode == 2) { ad_write(sc, SP_INTERFACE_CONFIG, 0); /* disable SINGLE_DMA */ for (i = 0x10; i < 0x20; i++) { if (ad1848_init_values[i] != 0) { ad_write(sc, i, ad1848_init_values[i]); timeout = AD1848_TIMO; while (timeout > 0 && ADREAD(sc, AD1848_IADDR) & SP_IN_INIT) timeout--; } } } ad1848_reset(sc); pparams = ad1848_audio_default; rparams = ad1848_audio_default; (void) ad1848_set_params(sc, AUMODE_RECORD|AUMODE_PLAY, 0, &pparams, &rparams); /* Set default gains */ (void) ad1848_set_rec_gain(sc, &vol_mid); (void) ad1848_set_channel_gain(sc, AD1848_DAC_CHANNEL, &vol_mid); (void) ad1848_set_channel_gain(sc, AD1848_MONITOR_CHANNEL, &vol_0); /* CD volume */ (void) ad1848_set_channel_gain(sc, AD1848_AUX1_CHANNEL, &vol_mid); if (sc->mode == 2) { /* CD volume */ (void) ad1848_set_channel_gain(sc, AD1848_AUX2_CHANNEL, &vol_mid); (void) ad1848_set_channel_gain(sc, AD1848_LINE_CHANNEL, &vol_mid); (void) ad1848_set_channel_gain(sc, AD1848_MONO_CHANNEL, &vol_0); sc->mute[AD1848_MONO_CHANNEL] = MUTE_ALL; } else (void) ad1848_set_channel_gain(sc, AD1848_AUX2_CHANNEL, &vol_0); /* Set default port */ (void) ad1848_set_rec_port(sc, MIC_IN_PORT); if (sc->chip_name) printf(": %s", sc->chip_name); } /* * Various routines to interface to higher level audio driver */ struct ad1848_mixerinfo { int left_reg; int right_reg; int atten_bits; int atten_mask; } mixer_channel_info[] = { { SP_LEFT_AUX2_CONTROL, SP_RIGHT_AUX2_CONTROL, AUX_INPUT_ATTEN_BITS, AUX_INPUT_ATTEN_MASK }, { SP_LEFT_AUX1_CONTROL, SP_RIGHT_AUX1_CONTROL, AUX_INPUT_ATTEN_BITS, AUX_INPUT_ATTEN_MASK }, { SP_LEFT_OUTPUT_CONTROL, SP_RIGHT_OUTPUT_CONTROL, OUTPUT_ATTEN_BITS, OUTPUT_ATTEN_MASK }, { CS_LEFT_LINE_CONTROL, CS_RIGHT_LINE_CONTROL, LINE_INPUT_ATTEN_BITS, LINE_INPUT_ATTEN_MASK }, { CS_MONO_IO_CONTROL, 0, MONO_INPUT_ATTEN_BITS, MONO_INPUT_ATTEN_MASK }, { SP_DIGITAL_MIX, 0, OUTPUT_ATTEN_BITS, MIX_ATTEN_MASK } }; /* * This function doesn't set the mute flags but does use them. * The mute flags reflect the mutes that have been applied by the user. * However, the driver occasionally wants to mute devices (e.g. when chaing * sampling rate). These operations should not affect the mute flags. */ void ad1848_mute_channel(struct ad1848_softc *sc, int device, int mute) { u_char reg; reg = ad_read(sc, mixer_channel_info[device].left_reg); if (mute & MUTE_LEFT) { if (device == AD1848_MONITOR_CHANNEL) { ad_write(sc, mixer_channel_info[device].left_reg, reg & 0xFE); } else { ad_write(sc, mixer_channel_info[device].left_reg, reg | 0x80); } } else if (!(sc->mute[device] & MUTE_LEFT)) { if (device == AD1848_MONITOR_CHANNEL) { ad_write(sc, mixer_channel_info[device].left_reg, reg | 0x01); } else { ad_write(sc, mixer_channel_info[device].left_reg, reg & ~0x80); } } if (!mixer_channel_info[device].right_reg) { return; } reg = ad_read(sc, mixer_channel_info[device].right_reg); if (mute & MUTE_RIGHT) { ad_write(sc, mixer_channel_info[device].right_reg, reg | 0x80); } else if (!(sc->mute[device] & MUTE_RIGHT)) { ad_write(sc, mixer_channel_info[device].right_reg, reg & ~0x80); } } int ad1848_set_channel_gain(struct ad1848_softc *sc, int device, struct ad1848_volume *gp) { struct ad1848_mixerinfo *info = &mixer_channel_info[device]; u_char reg; u_int atten; sc->gains[device] = *gp; atten = ((AUDIO_MAX_GAIN - gp->left) * info->atten_bits) / AUDIO_MAX_GAIN; reg = ad_read(sc, info->left_reg) & (info->atten_mask); if (device == AD1848_MONITOR_CHANNEL) reg |= ((atten & info->atten_bits) << 2); else reg |= ((atten & info->atten_bits)); ad_write(sc, info->left_reg, reg); if (!info->right_reg) return 0; atten = ((AUDIO_MAX_GAIN - gp->right) * info->atten_bits) / AUDIO_MAX_GAIN; reg = ad_read(sc, info->right_reg); reg &= (info->atten_mask); ad_write(sc, info->right_reg, (atten & info->atten_bits) | reg); return 0; } int ad1848_get_device_gain(struct ad1848_softc *sc, int device, struct ad1848_volume *gp) { *gp = sc->gains[device]; return 0; } int ad1848_get_rec_gain(struct ad1848_softc *sc, struct ad1848_volume *gp) { *gp = sc->rec_gain; return 0; } int ad1848_set_rec_gain(struct ad1848_softc *sc, struct ad1848_volume *gp) { u_char reg, gain; DPRINTF(("ad1848_set_rec_gain: %d:%d\n", gp->left, gp->right)); sc->rec_gain = *gp; gain = (gp->left * GAIN_22_5) / AUDIO_MAX_GAIN; reg = ad_read(sc, SP_LEFT_INPUT_CONTROL); reg &= INPUT_GAIN_MASK; ad_write(sc, SP_LEFT_INPUT_CONTROL, (gain & 0x0f) | reg); gain = (gp->right * GAIN_22_5) / AUDIO_MAX_GAIN; reg = ad_read(sc, SP_RIGHT_INPUT_CONTROL); reg &= INPUT_GAIN_MASK; ad_write(sc, SP_RIGHT_INPUT_CONTROL, (gain & 0x0f) | reg); return 0; } void ad1848_mute_monitor(void *addr, int mute) { struct ad1848_softc *sc = addr; DPRINTF(("ad1848_mute_monitor: %smuting\n", mute ? "" : "un")); if (sc->mode == 2) { ad1848_mute_channel(sc, AD1848_DAC_CHANNEL, mute ? MUTE_ALL : 0); ad1848_mute_channel(sc, AD1848_MONO_CHANNEL, mute ? MUTE_MONO : 0); ad1848_mute_channel(sc, AD1848_LINE_CHANNEL, mute ? MUTE_ALL : 0); } ad1848_mute_channel(sc, AD1848_AUX2_CHANNEL, mute ? MUTE_ALL : 0); ad1848_mute_channel(sc, AD1848_AUX1_CHANNEL, mute ? MUTE_ALL : 0); } int ad1848_set_mic_gain(struct ad1848_softc *sc, struct ad1848_volume *gp) { u_char reg; DPRINTF(("cs4231_set_mic_gain: %d\n", gp->left)); if (gp->left > AUDIO_MAX_GAIN / 2) { sc->mic_gain_on = 1; reg = ad_read(sc, SP_LEFT_INPUT_CONTROL); ad_write(sc, SP_LEFT_INPUT_CONTROL, reg | INPUT_MIC_GAIN_ENABLE); } else { sc->mic_gain_on = 0; reg = ad_read(sc, SP_LEFT_INPUT_CONTROL); ad_write(sc, SP_LEFT_INPUT_CONTROL, reg & ~INPUT_MIC_GAIN_ENABLE); } return 0; } int ad1848_get_mic_gain(struct ad1848_softc *sc, struct ad1848_volume *gp) { if (sc->mic_gain_on) gp->left = gp->right = AUDIO_MAX_GAIN; else gp->left = gp->right = AUDIO_MIN_GAIN; return 0; } static ad1848_devmap_t *ad1848_mixer_find_dev(ad1848_devmap_t *, int, mixer_ctrl_t *); static ad1848_devmap_t * ad1848_mixer_find_dev(ad1848_devmap_t *map, int cnt, mixer_ctrl_t *cp) { int idx; for (idx = 0; idx < cnt; idx++) { if (map[idx].id == cp->dev) { return &map[idx]; } } return NULL; } int ad1848_mixer_get_port(struct ad1848_softc *ac, struct ad1848_devmap *map, int cnt, mixer_ctrl_t *cp) { ad1848_devmap_t *entry; struct ad1848_volume vol; int error = EINVAL; int dev; if (!(entry = ad1848_mixer_find_dev(map, cnt, cp))) return (ENXIO); dev = entry->dev; mtx_enter(&audio_lock); switch (entry->kind) { case AD1848_KIND_LVL: if (cp->type != AUDIO_MIXER_VALUE) break; if (dev < AD1848_AUX2_CHANNEL || dev > AD1848_MONITOR_CHANNEL) break; if (cp->un.value.num_channels != 1 && mixer_channel_info[dev].right_reg == 0) break; error = ad1848_get_device_gain(ac, dev, &vol); if (!error) ad1848_from_vol(cp, &vol); break; case AD1848_KIND_MUTE: if (cp->type != AUDIO_MIXER_ENUM) break; cp->un.ord = ac->mute[dev] ? 1 : 0; error = 0; break; case AD1848_KIND_RECORDGAIN: if (cp->type != AUDIO_MIXER_VALUE) break; error = ad1848_get_rec_gain(ac, &vol); if (!error) ad1848_from_vol(cp, &vol); break; case AD1848_KIND_MICGAIN: if (cp->type != AUDIO_MIXER_VALUE) break; error = ad1848_get_mic_gain(ac, &vol); if (!error) ad1848_from_vol(cp, &vol); break; case AD1848_KIND_RECORDSOURCE: if (cp->type != AUDIO_MIXER_ENUM) break; cp->un.ord = ad1848_get_rec_port(ac); error = 0; break; default: printf("Invalid kind\n"); break; } mtx_leave(&audio_lock); return error; } int ad1848_mixer_set_port(struct ad1848_softc *ac, struct ad1848_devmap *map, int cnt, mixer_ctrl_t *cp) { ad1848_devmap_t *entry; struct ad1848_volume vol; int error = EINVAL; int dev; if (!(entry = ad1848_mixer_find_dev(map, cnt, cp))) return (ENXIO); dev = entry->dev; mtx_enter(&audio_lock); switch (entry->kind) { case AD1848_KIND_LVL: if (cp->type != AUDIO_MIXER_VALUE) break; if (dev < AD1848_AUX2_CHANNEL || dev > AD1848_MONITOR_CHANNEL) break; if (cp->un.value.num_channels != 1 && mixer_channel_info[dev].right_reg == 0) break; ad1848_to_vol(cp, &vol); error = ad1848_set_channel_gain(ac, dev, &vol); break; case AD1848_KIND_MUTE: if (cp->type != AUDIO_MIXER_ENUM) break; ac->mute[dev] = (cp->un.ord ? MUTE_ALL : 0); ad1848_mute_channel(ac, dev, ac->mute[dev]); error = 0; break; case AD1848_KIND_RECORDGAIN: if (cp->type != AUDIO_MIXER_VALUE) break; ad1848_to_vol(cp, &vol); error = ad1848_set_rec_gain(ac, &vol); break; case AD1848_KIND_MICGAIN: if (cp->type != AUDIO_MIXER_VALUE) break; ad1848_to_vol(cp, &vol); error = ad1848_set_mic_gain(ac, &vol); break; case AD1848_KIND_RECORDSOURCE: if (cp->type != AUDIO_MIXER_ENUM) break; error = ad1848_set_rec_port(ac, cp->un.ord); break; default: printf("Invalid kind\n"); break; } mtx_leave(&audio_lock); return (error); } int ad1848_query_encoding(void *addr, struct audio_encoding *fp) { switch (fp->index) { case 0: strlcpy(fp->name, AudioEmulaw, sizeof fp->name); fp->encoding = AUDIO_ENCODING_ULAW; fp->precision = 8; fp->flags = 0; break; case 1: strlcpy(fp->name, AudioEalaw, sizeof fp->name); fp->encoding = AUDIO_ENCODING_ALAW; fp->precision = 8; fp->flags = 0; break; case 2: strlcpy(fp->name, AudioEslinear_le, sizeof fp->name); fp->encoding = AUDIO_ENCODING_SLINEAR_LE; fp->precision = 16; fp->flags = 0; break; case 3: strlcpy(fp->name, AudioEulinear, sizeof fp->name); fp->encoding = AUDIO_ENCODING_ULINEAR; fp->precision = 8; fp->flags = 0; break; default: return EINVAL; /*NOTREACHED*/ } fp->bps = AUDIO_BPS(fp->precision); fp->msb = 1; return (0); } int ad1848_set_params(void *addr, int setmode, int usemode, struct audio_params *p, struct audio_params *r) { struct ad1848_softc *sc = addr; int error, bits, enc; DPRINTF(("ad1848_set_params: %d %d %d %ld\n", p->encoding, p->precision, p->channels, p->sample_rate)); enc = p->encoding; switch (enc) { case AUDIO_ENCODING_SLINEAR_LE: if (p->precision == 8) return EINVAL; break; case AUDIO_ENCODING_SLINEAR_BE: if (p->precision == 16) return EINVAL; break; case AUDIO_ENCODING_ULINEAR_LE: if (p->precision == 16) return EINVAL; break; case AUDIO_ENCODING_ULINEAR_BE: if (p->precision == 16) return EINVAL; break; } switch (enc) { case AUDIO_ENCODING_ULAW: p->precision = 8; bits = FMT_ULAW; break; case AUDIO_ENCODING_ALAW: p->precision = 8; bits = FMT_ALAW; break; case AUDIO_ENCODING_SLINEAR_LE: if (p->precision == 16) bits = FMT_TWOS_COMP; else return EINVAL; break; case AUDIO_ENCODING_SLINEAR_BE: if (p->precision == 16) bits = FMT_TWOS_COMP_BE; else return EINVAL; break; case AUDIO_ENCODING_ULINEAR_LE: if (p->precision == 8) bits = FMT_PCM8; else return EINVAL; break; default: return EINVAL; } if (p->channels < 1 || p->channels > 2) return EINVAL; error = ad1848_set_speed(sc, &p->sample_rate); if (error) return error; p->bps = AUDIO_BPS(p->precision); r->bps = AUDIO_BPS(r->precision); p->msb = 1; r->msb = 1; sc->format_bits = bits; sc->channels = p->channels; sc->precision = p->precision; sc->need_commit = 1; DPRINTF(("ad1848_set_params succeeded, bits=%x\n", bits)); return (0); } int ad1848_set_rec_port(struct ad1848_softc *sc, int port) { u_char inp, reg; DPRINTF(("ad1848_set_rec_port: 0x%x\n", port)); if (port == MIC_IN_PORT) { inp = MIC_INPUT; } else if (port == LINE_IN_PORT) { inp = LINE_INPUT; } else if (port == DAC_IN_PORT) { inp = MIXED_DAC_INPUT; } else if (sc->mode == 2 && port == AUX1_IN_PORT) { inp = AUX_INPUT; } else return EINVAL; reg = ad_read(sc, SP_LEFT_INPUT_CONTROL); reg &= INPUT_SOURCE_MASK; ad_write(sc, SP_LEFT_INPUT_CONTROL, (inp | reg)); reg = ad_read(sc, SP_RIGHT_INPUT_CONTROL); reg &= INPUT_SOURCE_MASK; ad_write(sc, SP_RIGHT_INPUT_CONTROL, (inp | reg)); sc->rec_port = port; return 0; } int ad1848_get_rec_port(struct ad1848_softc *sc) { return sc->rec_port; } int ad1848_round_blocksize(void *addr, int blk) { /* Round to a multiple of the biggest sample size. */ blk = (blk + 3) & -4; return blk; } int ad1848_open(void *addr, int flags) { struct ad1848_softc *sc = addr; DPRINTF(("ad1848_open: sc=%p\n", sc)); sc->sc_pintr = sc->sc_parg = NULL; sc->sc_rintr = sc->sc_rarg = NULL; /* Enable interrupts */ DPRINTF(("ad1848_open: enable intrs\n")); ad_write(sc, SP_PIN_CONTROL, INTERRUPT_ENABLE | ad_read(sc, SP_PIN_CONTROL)); #ifdef AUDIO_DEBUG if (ad1848debug > 2) ad1848_dump_regs(sc); #endif return 0; } /* * Close function is called at splaudio(). */ void ad1848_close(void *addr) { struct ad1848_softc *sc = addr; u_char r; ad1848_halt_output(sc); ad1848_halt_input(sc); sc->sc_pintr = NULL; sc->sc_rintr = NULL; DPRINTF(("ad1848_close: stop DMA\n")); ad_write(sc, SP_LOWER_BASE_COUNT, (u_char)0); ad_write(sc, SP_UPPER_BASE_COUNT, (u_char)0); /* Disable interrupts */ DPRINTF(("ad1848_close: disable intrs\n")); ad_write(sc, SP_PIN_CONTROL, ad_read(sc, SP_PIN_CONTROL) & ~INTERRUPT_ENABLE); DPRINTF(("ad1848_close: disable capture and playback\n")); r = ad_read(sc, SP_INTERFACE_CONFIG); r &= ~(CAPTURE_ENABLE | PLAYBACK_ENABLE); ad_write(sc, SP_INTERFACE_CONFIG, r); #ifdef AUDIO_DEBUG if (ad1848debug > 2) ad1848_dump_regs(sc); #endif } /* * Lower-level routines */ int ad1848_commit_settings(void *addr) { struct ad1848_softc *sc = addr; int timeout; u_char fs; if (!sc->need_commit) return 0; mtx_enter(&audio_lock); ad1848_mute_monitor(sc, 1); /* Enables changes to the format select reg */ ad_set_MCE(sc, 1); fs = sc->speed_bits | sc->format_bits; if (sc->channels == 2) fs |= FMT_STEREO; ad_write(sc, SP_CLOCK_DATA_FORMAT, fs); /* * If mode == 2 (CS4231), set I28 also. It's the capture format * register. */ if (sc->mode == 2) { /* Gravis Ultrasound MAX SDK sources says something about * errata sheets, with the implication that these inb()s * are necessary. */ (void)ADREAD(sc, AD1848_IDATA); (void)ADREAD(sc, AD1848_IDATA); /* * Write to I8 starts resynchronization. Wait until it * completes. */ timeout = AD1848_TIMO; while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) timeout--; ad_write(sc, CS_REC_FORMAT, fs); /* Gravis Ultrasound MAX SDK sources says something about * errata sheets, with the implication that these inb()s * are necessary. */ (void)ADREAD(sc, AD1848_IDATA); (void)ADREAD(sc, AD1848_IDATA); /* Now wait for resync for capture side of the house */ } /* * Write to I8 starts resynchronization. Wait until it completes. */ timeout = AD1848_TIMO; while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) timeout--; if (ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) printf("ad1848_commit: Auto calibration timed out\n"); /* * Starts the calibration process and enters playback mode after it. */ ad_set_MCE(sc, 0); wait_for_calibration(sc); ad1848_mute_monitor(sc, 0); mtx_leave(&audio_lock); sc->need_commit = 0; return 0; } void ad1848_reset(struct ad1848_softc *sc) { u_char r; DPRINTF(("ad1848_reset\n")); /* Clear the PEN and CEN bits */ r = ad_read(sc, SP_INTERFACE_CONFIG); r &= ~(CAPTURE_ENABLE | PLAYBACK_ENABLE); ad_write(sc, SP_INTERFACE_CONFIG, r); /* Clear interrupt status */ if (sc->mode == 2) ad_write(sc, CS_IRQ_STATUS, 0); ADWRITE(sc, AD1848_STATUS, 0); #ifdef AUDIO_DEBUG if (ad1848debug > 2) ad1848_dump_regs(sc); #endif } int ad1848_set_speed(struct ad1848_softc *sc, u_long *argp) { /* * The sampling speed is encoded in the least significant nible of I8. * The LSB selects the clock source (0=24.576 MHz, 1=16.9344 MHz) and * other three bits select the divisor (indirectly): * * The available speeds are in the following table. Keep the speeds in * the increasing order. */ typedef struct { int speed; u_char bits; } speed_struct; u_long arg = *argp; static speed_struct speed_table[] = { {5510, (0 << 1) | 1}, {5510, (0 << 1) | 1}, {6620, (7 << 1) | 1}, {8000, (0 << 1) | 0}, {9600, (7 << 1) | 0}, {11025, (1 << 1) | 1}, {16000, (1 << 1) | 0}, {18900, (2 << 1) | 1}, {22050, (3 << 1) | 1}, {27420, (2 << 1) | 0}, {32000, (3 << 1) | 0}, {33075, (6 << 1) | 1}, {37800, (4 << 1) | 1}, {44100, (5 << 1) | 1}, {48000, (6 << 1) | 0} }; int i, n, selected = -1; n = sizeof(speed_table) / sizeof(speed_struct); if (arg < speed_table[0].speed) selected = 0; if (arg > speed_table[n - 1].speed) selected = n - 1; for (i = 1 /*really*/ ; selected == -1 && i < n; i++) if (speed_table[i].speed == arg) selected = i; else if (speed_table[i].speed > arg) { int diff1, diff2; diff1 = arg - speed_table[i - 1].speed; diff2 = speed_table[i].speed - arg; if (diff1 < diff2) selected = i - 1; else selected = i; } if (selected == -1) { printf("ad1848: Can't find speed???\n"); selected = 3; } sc->speed_bits = speed_table[selected].bits; sc->need_commit = 1; *argp = speed_table[selected].speed; return 0; } /* * Halt a DMA in progress. */ int ad1848_halt_output(void *addr) { struct ad1848_softc *sc = addr; u_char reg; DPRINTF(("ad1848: ad1848_halt_output\n")); mtx_enter(&audio_lock); reg = ad_read(sc, SP_INTERFACE_CONFIG); ad_write(sc, SP_INTERFACE_CONFIG, (reg & ~PLAYBACK_ENABLE)); if (sc->sc_playrun == 1) { isa_dmaabort(sc->sc_isa, sc->sc_drq); sc->sc_playrun = 0; } mtx_leave(&audio_lock); return 0; } int ad1848_halt_input(void *addr) { struct ad1848_softc *sc = addr; u_char reg; DPRINTF(("ad1848: ad1848_halt_input\n")); mtx_enter(&audio_lock); reg = ad_read(sc, SP_INTERFACE_CONFIG); ad_write(sc, SP_INTERFACE_CONFIG, (reg & ~CAPTURE_ENABLE)); if (sc->sc_recrun == 1) { isa_dmaabort(sc->sc_isa, sc->sc_recdrq); sc->sc_recrun = 0; } mtx_leave(&audio_lock); return 0; } int ad1848_trigger_input(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) { struct ad1848_softc *sc = addr; u_char reg; if (sc->sc_recdrq == -1) { DPRINTF(("ad1848_trigger_input: invalid recording drq\n")); return ENXIO; } mtx_enter(&audio_lock); isa_dmastart(sc->sc_isa, sc->sc_recdrq, start, (char *)end - (char *)start, NULL, DMAMODE_READ | DMAMODE_LOOP, BUS_DMA_NOWAIT); sc->sc_recrun = 1; sc->sc_rintr = intr; sc->sc_rarg = arg; blksize = (blksize * NBBY) / (param->precision * param->channels) - 1; if (sc->mode == 2) { ad_write(sc, CS_LOWER_REC_CNT, (blksize & 0xff)); ad_write(sc, CS_UPPER_REC_CNT, ((blksize >> 8) & 0xff)); } else { ad_write(sc, SP_LOWER_BASE_COUNT, blksize & 0xff); ad_write(sc, SP_UPPER_BASE_COUNT, (blksize >> 8) & 0xff); } reg = ad_read(sc, SP_INTERFACE_CONFIG); ad_write(sc, SP_INTERFACE_CONFIG, (CAPTURE_ENABLE | reg)); #ifdef AUDIO_DEBUG if (ad1848debug > 1) printf("ad1848_trigger_input: started capture\n"); #endif mtx_leave(&audio_lock); return 0; } int ad1848_trigger_output(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) { struct ad1848_softc *sc = addr; u_char reg; mtx_enter(&audio_lock); isa_dmastart(sc->sc_isa, sc->sc_drq, start, (char *)end - (char *)start, NULL, DMAMODE_WRITE | DMAMODE_LOOP, BUS_DMA_NOWAIT); sc->sc_playrun = 1; sc->sc_pintr = intr; sc->sc_parg = arg; blksize = (blksize * NBBY) / (param->precision * param->channels) - 1; ad_write(sc, SP_LOWER_BASE_COUNT, blksize & 0xff); ad_write(sc, SP_UPPER_BASE_COUNT, (blksize >> 8) & 0xff); reg = ad_read(sc, SP_INTERFACE_CONFIG); ad_write(sc, SP_INTERFACE_CONFIG, (PLAYBACK_ENABLE | reg)); #ifdef AUDIO_DEBUG if (ad1848debug > 1) printf("ad1848_trigger_output: started playback\n"); #endif mtx_leave(&audio_lock); return 0; } int ad1848_intr(void *arg) { struct ad1848_softc *sc = arg; int retval = 0; u_char status; mtx_enter(&audio_lock); /* Get intr status */ status = ADREAD(sc, AD1848_STATUS); #ifdef AUDIO_DEBUG if (ad1848debug > 1) printf("ad1848_intr: mode=%d pintr=%p prun=%d rintr=%p rrun=%d status=0x%x\n", sc->mode, sc->sc_pintr, sc->sc_playrun, sc->sc_rintr, sc->sc_recrun, status); #endif /* Handle interrupt */ if ((status & INTERRUPT_STATUS) != 0) { if (sc->mode == 2) { status = ad_read(sc, CS_IRQ_STATUS); #ifdef AUDIO_DEBUG if (ad1848debug > 2) printf("ad1848_intr: cs_irq_status=0x%x (play=0x%x rec0x%x)\n", status, CS_IRQ_PI, CS_IRQ_CI); #endif if ((status & CS_IRQ_PI) && sc->sc_playrun) { (*sc->sc_pintr)(sc->sc_parg); retval = 1; } if ((status & CS_IRQ_CI) && sc->sc_recrun) { (*sc->sc_rintr)(sc->sc_rarg); retval = 1; } } else { if (sc->sc_playrun) { (*sc->sc_pintr)(sc->sc_parg); retval = 1; } else if (sc->sc_recrun) { (*sc->sc_rintr)(sc->sc_rarg); retval = 1; } } /* clear interrupt */ ADWRITE(sc, AD1848_STATUS, 0); } mtx_leave(&audio_lock); return(retval); } void * ad1848_malloc(void *addr, int direction, size_t size, int pool, int flags) { struct ad1848_softc *sc = addr; int drq; if (direction == AUMODE_PLAY) drq = sc->sc_drq; else drq = sc->sc_recdrq; return isa_malloc(sc->sc_isa, drq, size, pool, flags); } void ad1848_free(void *addr, void *ptr, int pool) { isa_free(ptr, pool); } size_t ad1848_round(void *addr, int direction, size_t size) { if (size > MAX_ISADMA) size = MAX_ISADMA; return size; } paddr_t ad1848_mappage(void *addr, void *mem, off_t off, int prot) { return isa_mappage(mem, off, prot); } int ad1848_get_props(void *addr) { struct ad1848_softc *sc = addr; return AUDIO_PROP_MMAP | (sc->mode == 2 ? AUDIO_PROP_FULLDUPLEX : 0); }