diff --git a/sys/dev/sound/pci/als4000.c b/sys/dev/sound/pci/als4000.c index 5b7247517c13..b1376d2b6e5a 100644 --- a/sys/dev/sound/pci/als4000.c +++ b/sys/dev/sound/pci/als4000.c @@ -1,939 +1,940 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * als4000.c - driver for the Avance Logic ALS 4000 chipset. * * The ALS4000 is effectively an SB16 with a PCI interface. * * This driver derives from ALS4000a.PDF, Bart Hartgers alsa driver, and * SB16 register descriptions. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include "mixer_if.h" /* Debugging macro's */ #undef DEB #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ #define ALS_DEFAULT_BUFSZ 16384 /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t format, speed, phys_buf, bps; u_int32_t dma_active:1, dma_was_active:1; u_int8_t gcr_fifo_status; int dir; }; struct sc_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regid, irqid; void *ih; struct mtx *lock; unsigned int bufsz; struct sc_chinfo pch, rch; }; /* Channel caps */ static u_int32_t als_format[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; /* * I don't believe this rotten soundcard can do 48k, really, * trust me. */ static struct pcmchan_caps als_caps = { 4000, 44100, als_format, 0 }; /* ------------------------------------------------------------------------- */ /* Register Utilities */ static u_int32_t als_gcr_rd(struct sc_info *sc, int index) { bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); return bus_space_read_4(sc->st, sc->sh, ALS_GCR_DATA); } static void als_gcr_wr(struct sc_info *sc, int index, int data) { bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); bus_space_write_4(sc->st, sc->sh, ALS_GCR_DATA, data); } static u_int8_t als_intr_rd(struct sc_info *sc) { return bus_space_read_1(sc->st, sc->sh, ALS_SB_MPU_IRQ); } static void als_intr_wr(struct sc_info *sc, u_int8_t data) { bus_space_write_1(sc->st, sc->sh, ALS_SB_MPU_IRQ, data); } static u_int8_t als_mix_rd(struct sc_info *sc, u_int8_t index) { bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); return bus_space_read_1(sc->st, sc->sh, ALS_MIXER_DATA); } static void als_mix_wr(struct sc_info *sc, u_int8_t index, u_int8_t data) { bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); bus_space_write_1(sc->st, sc->sh, ALS_MIXER_DATA, data); } static void als_esp_wr(struct sc_info *sc, u_int8_t data) { u_int32_t tries, v; tries = 1000; do { v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_WR_STATUS); if (~v & 0x80) break; DELAY(20); } while (--tries != 0); if (tries == 0) device_printf(sc->dev, "als_esp_wr timeout"); bus_space_write_1(sc->st, sc->sh, ALS_ESP_WR_DATA, data); } static int als_esp_reset(struct sc_info *sc) { u_int32_t tries, u, v; bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 1); DELAY(10); bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 0); DELAY(30); tries = 1000; do { u = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_STATUS8); if (u & 0x80) { v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_DATA); if (v == 0xaa) return 0; else break; } DELAY(20); } while (--tries != 0); if (tries == 0) device_printf(sc->dev, "als_esp_reset timeout"); return 1; } static u_int8_t als_ack_read(struct sc_info *sc, u_int8_t addr) { u_int8_t r = bus_space_read_1(sc->st, sc->sh, addr); return r; } /* ------------------------------------------------------------------------- */ /* Common pcm channel implementation */ static void * alschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; snd_mtxlock(sc->lock); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->gcr_fifo_status = ALS_GCR_FIFO0_STATUS; } else { ch = &sc->rch; ch->gcr_fifo_status = ALS_GCR_FIFO1_STATUS; } ch->dir = dir; ch->parent = sc; ch->channel = c; ch->bps = 1; ch->format = SND_FORMAT(AFMT_U8, 1, 0); ch->speed = DSP_DEFAULT_SPEED; ch->buffer = b; snd_mtxunlock(sc->lock); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; return ch; } static int alschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->format = format; return 0; } static u_int32_t alschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data, *other; struct sc_info *sc = ch->parent; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; /* Deny request if other dma channel is active */ if (other->dma_active) { ch->speed = other->speed; return other->speed; } ch->speed = speed; return speed; } static u_int32_t alschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (blocksize > sc->bufsz / 2) { blocksize = sc->bufsz / 2; } sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static u_int32_t alschan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int32_t pos, sz; snd_mtxlock(sc->lock); pos = als_gcr_rd(ch->parent, ch->gcr_fifo_status) & 0xffff; snd_mtxunlock(sc->lock); sz = sndbuf_getsize(ch->buffer); return (2 * sz - pos - 1) % sz; } static struct pcmchan_caps* alschan_getcaps(kobj_t obj, void *data) { return &als_caps; } static void als_set_speed(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; struct sc_chinfo *other; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; if (other->dma_active == 0) { als_esp_wr(sc, ALS_ESP_SAMPLE_RATE); als_esp_wr(sc, ch->speed >> 8); als_esp_wr(sc, ch->speed & 0xff); } else { DEB(printf("speed locked at %d (tried %d)\n", other->speed, ch->speed)); } } /* ------------------------------------------------------------------------- */ /* Playback channel implementation */ #define ALS_8BIT_CMD(x, y) { (x), (y), DSP_DMA8, DSP_CMD_DMAPAUSE_8 } #define ALS_16BIT_CMD(x, y) { (x), (y), DSP_DMA16, DSP_CMD_DMAPAUSE_16 } struct playback_command { u_int32_t pcm_format; /* newpcm format */ u_int8_t format_val; /* sb16 format value */ u_int8_t dma_prog; /* sb16 dma program */ u_int8_t dma_stop; /* sb16 stop register */ } static const playback_cmds[] = { ALS_8BIT_CMD(SND_FORMAT(AFMT_U8, 1, 0), DSP_MODE_U8MONO), ALS_8BIT_CMD(SND_FORMAT(AFMT_U8, 2, 0), DSP_MODE_U8STEREO), ALS_16BIT_CMD(SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_MODE_S16MONO), ALS_16BIT_CMD(SND_FORMAT(AFMT_S16_LE, 2, 0), DSP_MODE_S16STEREO), }; static const struct playback_command* als_get_playback_command(u_int32_t format) { u_int32_t i, n; n = sizeof(playback_cmds) / sizeof(playback_cmds[0]); for (i = 0; i < n; i++) { if (playback_cmds[i].pcm_format == format) { return &playback_cmds[i]; } } DEB(printf("als_get_playback_command: invalid format 0x%08x\n", format)); return &playback_cmds[0]; } static void als_playback_start(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; buf = sndbuf_getbufaddr(ch->buffer); bufsz = sndbuf_getsize(ch->buffer); count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_DMA0_START, buf); als_gcr_wr(sc, ALS_GCR_DMA0_MODE, (bufsz - 1) | 0x180000); p = als_get_playback_command(ch->format); dma_prog = p->dma_prog | DSP_F16_DAC | DSP_F16_AUTO | DSP_F16_FIFO_ON; als_esp_wr(sc, dma_prog); als_esp_wr(sc, p->format_val); als_esp_wr(sc, count & 0xff); als_esp_wr(sc, count >> 8); ch->dma_active = 1; } static int als_playback_stop(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { p = als_get_playback_command(ch->format); als_esp_wr(sc, p->dma_stop); } ch->dma_active = 0; return active; } static int alspchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_playback_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: als_playback_stop(ch); break; default: break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alspchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alspchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(alspchan); /* ------------------------------------------------------------------------- */ /* Capture channel implementation */ static u_int8_t als_get_fifo_format(struct sc_info *sc, u_int32_t format) { switch (format) { case SND_FORMAT(AFMT_U8, 1, 0): return ALS_FIFO1_8BIT; case SND_FORMAT(AFMT_U8, 2, 0): return ALS_FIFO1_8BIT | ALS_FIFO1_STEREO; case SND_FORMAT(AFMT_S16_LE, 1, 0): return ALS_FIFO1_SIGNED; case SND_FORMAT(AFMT_S16_LE, 2, 0): return ALS_FIFO1_SIGNED | ALS_FIFO1_STEREO; } device_printf(sc->dev, "format not found: 0x%08x\n", format); return ALS_FIFO1_8BIT; } static void als_capture_start(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; buf = sndbuf_getbufaddr(ch->buffer); bufsz = sndbuf_getsize(ch->buffer); count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_FIFO1_START, buf); als_gcr_wr(sc, ALS_GCR_FIFO1_COUNT, (bufsz - 1)); als_mix_wr(sc, ALS_FIFO1_LENGTH_LO, count & 0xff); als_mix_wr(sc, ALS_FIFO1_LENGTH_HI, count >> 8); dma_prog = ALS_FIFO1_RUN | als_get_fifo_format(sc, ch->format); als_mix_wr(sc, ALS_FIFO1_CONTROL, dma_prog); ch->dma_active = 1; } static int als_capture_stop(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { als_mix_wr(sc, ALS_FIFO1_CONTROL, ALS_FIFO1_STOP); } ch->dma_active = 0; return active; } static int alsrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_capture_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: als_capture_stop(ch); break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alsrchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alsrchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(alsrchan); /* ------------------------------------------------------------------------- */ /* Mixer related */ /* * ALS4000 has an sb16 mixer, with some additional controls that we do * not yet a means to support. */ struct sb16props { u_int8_t lreg; u_int8_t rreg; u_int8_t bits; u_int8_t oselect; u_int8_t iselect; /* left input mask */ } static const amt[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { 0x30, 0x31, 5, 0x00, 0x00 }, [SOUND_MIXER_PCM] = { 0x32, 0x33, 5, 0x00, 0x00 }, [SOUND_MIXER_SYNTH] = { 0x34, 0x35, 5, 0x60, 0x40 }, [SOUND_MIXER_CD] = { 0x36, 0x37, 5, 0x06, 0x04 }, [SOUND_MIXER_LINE] = { 0x38, 0x39, 5, 0x18, 0x10 }, [SOUND_MIXER_MIC] = { 0x3a, 0x00, 5, 0x01, 0x01 }, [SOUND_MIXER_SPEAKER] = { 0x3b, 0x00, 2, 0x00, 0x00 }, [SOUND_MIXER_IGAIN] = { 0x3f, 0x40, 2, 0x00, 0x00 }, [SOUND_MIXER_OGAIN] = { 0x41, 0x42, 2, 0x00, 0x00 }, /* The following have register values but no h/w implementation */ [SOUND_MIXER_TREBLE] = { 0x44, 0x45, 4, 0x00, 0x00 }, [SOUND_MIXER_BASS] = { 0x46, 0x47, 4, 0x00, 0x00 } }; static int alsmix_init(struct snd_mixer *m) { u_int32_t i, v; for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (amt[i].bits) v |= 1 << i; } mix_setdevs(m, v); for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (amt[i].iselect) v |= 1 << i; } mix_setrecdevs(m, v); return 0; } static int alsmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t r, l, v, mask; /* Fill upper n bits in mask with 1's */ mask = ((1 << amt[dev].bits) - 1) << (8 - amt[dev].bits); l = (left * mask / 100) & mask; v = als_mix_rd(sc, amt[dev].lreg) & ~mask; als_mix_wr(sc, amt[dev].lreg, l | v); if (amt[dev].rreg) { r = (right * mask / 100) & mask; v = als_mix_rd(sc, amt[dev].rreg) & ~mask; als_mix_wr(sc, amt[dev].rreg, r | v); } else { r = 0; } /* Zero gain does not mute channel from output, but this does. */ v = als_mix_rd(sc, SB16_OMASK); if (l == 0 && r == 0) { v &= ~amt[dev].oselect; } else { v |= amt[dev].oselect; } als_mix_wr(sc, SB16_OMASK, v); return 0; } static u_int32_t alsmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, l, r; for (i = l = r = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (src & (1 << i)) { if (amt[i].iselect == 1) { /* microphone */ l |= amt[i].iselect; r |= amt[i].iselect; } else { l |= amt[i].iselect; r |= amt[i].iselect >> 1; } } } als_mix_wr(sc, SB16_IMASK_L, l); als_mix_wr(sc, SB16_IMASK_R, r); return src; } static kobj_method_t als_mixer_methods[] = { KOBJMETHOD(mixer_init, alsmix_init), KOBJMETHOD(mixer_set, alsmix_set), KOBJMETHOD(mixer_setrecsrc, alsmix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(als_mixer); /* ------------------------------------------------------------------------- */ /* Interrupt Handler */ static void als_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int8_t intr, sb_status; snd_mtxlock(sc->lock); intr = als_intr_rd(sc); if (intr & 0x80) { snd_mtxunlock(sc->lock); chn_intr(sc->pch.channel); snd_mtxlock(sc->lock); } if (intr & 0x40) { snd_mtxunlock(sc->lock); chn_intr(sc->rch.channel); snd_mtxlock(sc->lock); } /* ACK interrupt in PCI core */ als_intr_wr(sc, intr); /* ACK interrupt in SB core */ sb_status = als_mix_rd(sc, IRQ_STAT); if (sb_status & ALS_IRQ_STATUS8) als_ack_read(sc, ALS_ESP_RD_STATUS8); if (sb_status & ALS_IRQ_STATUS16) als_ack_read(sc, ALS_ESP_RD_STATUS16); if (sb_status & ALS_IRQ_MPUIN) als_ack_read(sc, ALS_MIDI_DATA); if (sb_status & ALS_IRQ_CR1E) als_ack_read(sc, ALS_CR1E_ACK_PORT); snd_mtxunlock(sc->lock); return; } /* ------------------------------------------------------------------------- */ /* H/W initialization */ static int als_init(struct sc_info *sc) { u_int32_t i, v; /* Reset Chip */ if (als_esp_reset(sc)) { return 1; } /* Enable write on DMA_SETUP register */ v = als_mix_rd(sc, ALS_SB16_CONFIG); als_mix_wr(sc, ALS_SB16_CONFIG, v | 0x80); /* Select DMA0 */ als_mix_wr(sc, ALS_SB16_DMA_SETUP, 0x01); /* Disable write on DMA_SETUP register */ als_mix_wr(sc, ALS_SB16_CONFIG, v & 0x7f); /* Enable interrupts */ v = als_gcr_rd(sc, ALS_GCR_MISC); als_gcr_wr(sc, ALS_GCR_MISC, v | 0x28000); /* Black out GCR DMA registers */ for (i = 0x91; i <= 0x96; i++) { als_gcr_wr(sc, i, 0); } /* Emulation mode */ v = als_gcr_rd(sc, ALS_GCR_DMA_EMULATION); als_gcr_wr(sc, ALS_GCR_DMA_EMULATION, v); DEB(printf("GCR_DMA_EMULATION 0x%08x\n", v)); return 0; } static void als_uninit(struct sc_info *sc) { /* Disable interrupts */ als_gcr_wr(sc, ALS_GCR_MISC, 0); } /* ------------------------------------------------------------------------- */ /* Probe and attach card */ static int als_pci_probe(device_t dev) { if (pci_get_devid(dev) == ALS_PCI_ID0) { device_set_desc(dev, "Avance Logic ALS4000"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void als_resource_free(device_t dev, struct sc_info *sc) { if (sc->reg) { bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); sc->reg = NULL; } if (sc->ih) { bus_teardown_intr(dev, sc->irq, sc->ih); sc->ih = NULL; } if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } } static int als_resource_grab(device_t dev, struct sc_info *sc) { sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->regid, RF_ACTIVE); if (sc->reg == NULL) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (sc->irq == NULL) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, als_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, ALS_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } return 0; bad: als_resource_free(dev, sc); return ENXIO; } static int als_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_als4000 softc"); sc->dev = dev; pci_enable_busmaster(dev); /* * By default the power to the various components on the * ALS4000 is entirely controlled by the pci powerstate. We * could attempt finer grained control by setting GCR6.31. */ if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } if (als_resource_grab(dev, sc)) { device_printf(dev, "failed to allocate resources\n"); goto bad_attach; } if (als_init(sc)) { device_printf(dev, "failed to initialize hardware\n"); goto bad_attach; } if (mixer_init(dev, &als_mixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad_attach; } if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "failed to register pcm entries\n"); goto bad_attach; } pcm_addchan(dev, PCMDIR_PLAY, &alspchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &alsrchan_class, sc); - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", - rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_als4000)); + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", + rman_get_start(sc->reg), rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return 0; bad_attach: als_resource_free(dev, sc); free(sc, M_DEVBUF); return ENXIO; } static int als_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); als_uninit(sc); als_resource_free(dev, sc); free(sc, M_DEVBUF); return 0; } static int als_pci_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = als_playback_stop(&sc->pch); sc->rch.dma_was_active = als_capture_stop(&sc->rch); als_uninit(sc); snd_mtxunlock(sc->lock); return 0; } static int als_pci_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); if (als_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) != 0) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { als_playback_start(&sc->pch); } if (sc->rch.dma_was_active) { als_capture_start(&sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t als_methods[] = { /* Device interface */ DEVMETHOD(device_probe, als_pci_probe), DEVMETHOD(device_attach, als_pci_attach), DEVMETHOD(device_detach, als_pci_detach), DEVMETHOD(device_suspend, als_pci_suspend), DEVMETHOD(device_resume, als_pci_resume), { 0, 0 } }; static driver_t als_driver = { "pcm", als_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_als4000, pci, als_driver, 0, 0); MODULE_DEPEND(snd_als4000, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_als4000, 1); diff --git a/sys/dev/sound/pci/atiixp.c b/sys/dev/sound/pci/atiixp.c index 78be9475acd3..dcbf041f9605 100644 --- a/sys/dev/sound/pci/atiixp.c +++ b/sys/dev/sound/pci/atiixp.c @@ -1,1420 +1,1420 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * FreeBSD pcm driver for ATI IXP 150/200/250/300 AC97 controllers * * Features * * 16bit playback / recording * * 32bit native playback - yay! * * 32bit native recording (seems broken on few hardwares) * * Issues / TODO: * * SPDIF * * Support for more than 2 channels. * * VRA ? VRM ? DRA ? * * 32bit native recording seems broken on few hardwares, most * probably because of incomplete VRA/DRA cleanup. * * * Thanks goes to: * * Shaharil @ SCAN Associates whom relentlessly providing me the * mind blowing Acer Ferrari 4002 WLMi with this ATI IXP hardware. * * Reinoud Zandijk (auixp), which this driver is * largely based upon although large part of it has been reworked. His * driver is the primary reference and pretty much well documented. * * Takashi Iwai (ALSA snd-atiixp), for register definitions and some * random ninja hackery. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #define ATI_IXP_DMA_RETRY_MAX 100 #define ATI_IXP_BUFSZ_MIN 4096 #define ATI_IXP_BUFSZ_MAX 65536 #define ATI_IXP_BUFSZ_DEFAULT 16384 #define ATI_IXP_BLK_MIN 32 #define ATI_IXP_BLK_ALIGN (~(ATI_IXP_BLK_MIN - 1)) #define ATI_IXP_CHN_RUNNING 0x00000001 #define ATI_IXP_CHN_SUSPEND 0x00000002 struct atiixp_dma_op { volatile uint32_t addr; volatile uint16_t status; volatile uint16_t size; volatile uint32_t next; }; struct atiixp_info; struct atiixp_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct atiixp_info *parent; struct atiixp_dma_op *sgd_table; bus_addr_t sgd_addr; uint32_t enable_bit, flush_bit, linkptr_bit, dt_cur_bit; uint32_t blksz, blkcnt; uint32_t ptr, prevptr; uint32_t fmt; uint32_t flags; int caps_32bit, dir; }; struct atiixp_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; struct ac97_info *codec; struct atiixp_chinfo pch; struct atiixp_chinfo rch; struct atiixp_dma_op *sgd_table; struct intr_config_hook delayed_attach; uint32_t bufsz; uint32_t codec_not_ready_bits, codec_idx, codec_found; uint32_t blkcnt; int registered_channels; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; #define atiixp_rd(_sc, _reg) \ bus_space_read_4((_sc)->st, (_sc)->sh, _reg) #define atiixp_wr(_sc, _reg, _val) \ bus_space_write_4((_sc)->st, (_sc)->sh, _reg, _val) #define atiixp_lock(_sc) snd_mtxlock((_sc)->lock) #define atiixp_unlock(_sc) snd_mtxunlock((_sc)->lock) #define atiixp_assert(_sc) snd_mtxassert((_sc)->lock) static uint32_t atiixp_fmt_32bit[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static uint32_t atiixp_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps atiixp_caps_32bit = { ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt_32bit, 0 }; static struct pcmchan_caps atiixp_caps = { ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt, 0 }; static const struct { uint16_t vendor; uint16_t devid; char *desc; } atiixp_hw[] = { { ATI_VENDOR_ID, ATI_IXP_200_ID, "ATI IXP 200" }, { ATI_VENDOR_ID, ATI_IXP_300_ID, "ATI IXP 300" }, { ATI_VENDOR_ID, ATI_IXP_400_ID, "ATI IXP 400" }, { ATI_VENDOR_ID, ATI_IXP_SB600_ID, "ATI IXP SB600" }, }; static void atiixp_enable_interrupts(struct atiixp_info *); static void atiixp_disable_interrupts(struct atiixp_info *); static void atiixp_reset_aclink(struct atiixp_info *); static void atiixp_flush_dma(struct atiixp_chinfo *); static void atiixp_enable_dma(struct atiixp_chinfo *); static void atiixp_disable_dma(struct atiixp_chinfo *); static int atiixp_waitready_codec(struct atiixp_info *); static int atiixp_rdcd(kobj_t, void *, int); static int atiixp_wrcd(kobj_t, void *, int, uint32_t); static void *atiixp_chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int atiixp_chan_setformat(kobj_t, void *, uint32_t); static uint32_t atiixp_chan_setspeed(kobj_t, void *, uint32_t); static int atiixp_chan_setfragments(kobj_t, void *, uint32_t, uint32_t); static uint32_t atiixp_chan_setblocksize(kobj_t, void *, uint32_t); static void atiixp_buildsgdt(struct atiixp_chinfo *); static int atiixp_chan_trigger(kobj_t, void *, int); static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *); static uint32_t atiixp_chan_getptr(kobj_t, void *); static struct pcmchan_caps *atiixp_chan_getcaps(kobj_t, void *); static void atiixp_intr(void *); static void atiixp_dma_cb(void *, bus_dma_segment_t *, int, int); static void atiixp_chip_pre_init(struct atiixp_info *); static void atiixp_chip_post_init(void *); static void atiixp_release_resource(struct atiixp_info *); static int atiixp_pci_probe(device_t); static int atiixp_pci_attach(device_t); static int atiixp_pci_detach(device_t); static int atiixp_pci_suspend(device_t); static int atiixp_pci_resume(device_t); /* * ATI IXP helper functions */ static void atiixp_enable_interrupts(struct atiixp_info *sc) { uint32_t value; /* clear all pending */ atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); /* enable all relevant interrupt sources we can handle */ value = atiixp_rd(sc, ATI_REG_IER); value |= ATI_REG_IER_IO_STATUS_EN; /* * Disable / ignore internal xrun/spdf interrupt flags * since it doesn't interest us (for now). */ #if 1 value &= ~(ATI_REG_IER_IN_XRUN_EN | ATI_REG_IER_OUT_XRUN_EN | ATI_REG_IER_SPDF_XRUN_EN | ATI_REG_IER_SPDF_STATUS_EN); #else value |= ATI_REG_IER_IN_XRUN_EN; value |= ATI_REG_IER_OUT_XRUN_EN; value |= ATI_REG_IER_SPDF_XRUN_EN; value |= ATI_REG_IER_SPDF_STATUS_EN; #endif atiixp_wr(sc, ATI_REG_IER, value); } static void atiixp_disable_interrupts(struct atiixp_info *sc) { /* disable all interrupt sources */ atiixp_wr(sc, ATI_REG_IER, 0); /* clear all pending */ atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); } static void atiixp_reset_aclink(struct atiixp_info *sc) { uint32_t value, timeout; /* if power is down, power it up */ value = atiixp_rd(sc, ATI_REG_CMD); if (value & ATI_REG_CMD_POWERDOWN) { /* explicitly enable power */ value &= ~ATI_REG_CMD_POWERDOWN; atiixp_wr(sc, ATI_REG_CMD, value); /* have to wait at least 10 usec for it to initialise */ DELAY(20); } /* perform a soft reset */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_SOFT_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* need to read the CMD reg and wait aprox. 10 usec to init */ value = atiixp_rd(sc, ATI_REG_CMD); DELAY(20); /* clear soft reset flag again */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_AC_SOFT_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* check if the ac-link is working; reset device otherwise */ timeout = 10; value = atiixp_rd(sc, ATI_REG_CMD); while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) && --timeout) { #if 0 device_printf(sc->dev, "not up; resetting aclink hardware\n"); #endif /* dip aclink reset but keep the acsync */ value &= ~ATI_REG_CMD_AC_RESET; value |= ATI_REG_CMD_AC_SYNC; atiixp_wr(sc, ATI_REG_CMD, value); /* need to read CMD again and wait again (clocking in issue?) */ value = atiixp_rd(sc, ATI_REG_CMD); DELAY(20); /* assert aclink reset again */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* check if its active now */ value = atiixp_rd(sc, ATI_REG_CMD); } if (timeout == 0) device_printf(sc->dev, "giving up aclink reset\n"); #if 0 if (timeout != 10) device_printf(sc->dev, "aclink hardware reset successful\n"); #endif /* assert reset and sync for safety */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_SYNC | ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, value); } static void atiixp_flush_dma(struct atiixp_chinfo *ch) { atiixp_wr(ch->parent, ATI_REG_FIFO_FLUSH, ch->flush_bit); } static void atiixp_enable_dma(struct atiixp_chinfo *ch) { uint32_t value; value = atiixp_rd(ch->parent, ATI_REG_CMD); if (!(value & ch->enable_bit)) { value |= ch->enable_bit; atiixp_wr(ch->parent, ATI_REG_CMD, value); } } static void atiixp_disable_dma(struct atiixp_chinfo *ch) { uint32_t value; value = atiixp_rd(ch->parent, ATI_REG_CMD); if (value & ch->enable_bit) { value &= ~ch->enable_bit; atiixp_wr(ch->parent, ATI_REG_CMD, value); } } /* * AC97 interface */ static int atiixp_waitready_codec(struct atiixp_info *sc) { int timeout = 500; do { if ((atiixp_rd(sc, ATI_REG_PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) == 0) return (0); DELAY(1); } while (--timeout); return (-1); } static int atiixp_rdcd(kobj_t obj, void *devinfo, int reg) { struct atiixp_info *sc = devinfo; uint32_t data; int timeout; if (atiixp_waitready_codec(sc)) return (-1); data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); if (atiixp_waitready_codec(sc)) return (-1); timeout = 500; do { data = atiixp_rd(sc, ATI_REG_PHYS_IN_ADDR); if (data & ATI_REG_PHYS_IN_READ_FLAG) return (data >> ATI_REG_PHYS_IN_DATA_SHIFT); DELAY(1); } while (--timeout); if (reg < 0x7c) device_printf(sc->dev, "codec read timeout! (reg 0x%x)\n", reg); return (-1); } static int atiixp_wrcd(kobj_t obj, void *devinfo, int reg, uint32_t data) { struct atiixp_info *sc = devinfo; if (atiixp_waitready_codec(sc)) return (-1); data = (data << ATI_REG_PHYS_OUT_DATA_SHIFT) | (((uint32_t)reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); return (0); } static kobj_method_t atiixp_ac97_methods[] = { KOBJMETHOD(ac97_read, atiixp_rdcd), KOBJMETHOD(ac97_write, atiixp_wrcd), KOBJMETHOD_END }; AC97_DECLARE(atiixp_ac97); /* * Playback / Record channel interface */ static void * atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct atiixp_info *sc = devinfo; struct atiixp_chinfo *ch; int num; atiixp_lock(sc); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->linkptr_bit = ATI_REG_OUT_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SEND_EN; ch->flush_bit = ATI_REG_FIFO_OUT_FLUSH; ch->dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; /* Native 32bit playback working properly */ ch->caps_32bit = 1; } else { ch = &sc->rch; ch->linkptr_bit = ATI_REG_IN_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; ch->flush_bit = ATI_REG_FIFO_IN_FLUSH; ch->dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; /* XXX Native 32bit recording appear to be broken */ ch->caps_32bit = 1; } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; atiixp_unlock(sc); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) == -1) return (NULL); atiixp_lock(sc); num = sc->registered_channels++; ch->sgd_table = &sc->sgd_table[num * ATI_IXP_DMA_CHSEGS_MAX]; ch->sgd_addr = sc->sgd_addr + (num * ATI_IXP_DMA_CHSEGS_MAX * sizeof(struct atiixp_dma_op)); atiixp_disable_dma(ch); atiixp_unlock(sc); return (ch); } static int atiixp_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; atiixp_lock(sc); if (ch->dir == PCMDIR_REC) { value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_INTERLEAVE_IN; if ((format & AFMT_32BIT) == 0) value |= ATI_REG_CMD_INTERLEAVE_IN; atiixp_wr(sc, ATI_REG_CMD, value); } else { value = atiixp_rd(sc, ATI_REG_OUT_DMA_SLOT); value &= ~ATI_REG_OUT_DMA_SLOT_MASK; /* We do not have support for more than 2 channels, _yet_. */ value |= ATI_REG_OUT_DMA_SLOT_BIT(3) | ATI_REG_OUT_DMA_SLOT_BIT(4); value |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; atiixp_wr(sc, ATI_REG_OUT_DMA_SLOT, value); value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_INTERLEAVE_OUT; if ((format & AFMT_32BIT) == 0) value |= ATI_REG_CMD_INTERLEAVE_OUT; atiixp_wr(sc, ATI_REG_CMD, value); value = atiixp_rd(sc, ATI_REG_6CH_REORDER); value &= ~ATI_REG_6CH_REORDER_EN; atiixp_wr(sc, ATI_REG_6CH_REORDER, value); } ch->fmt = format; atiixp_unlock(sc); return (0); } static uint32_t atiixp_chan_setspeed(kobj_t obj, void *data, uint32_t spd) { /* XXX We're supposed to do VRA/DRA processing right here */ return (ATI_IXP_BASE_RATE); } static int atiixp_chan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; blksz &= ATI_IXP_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN)) blksz = sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN; if (blksz < ATI_IXP_BLK_MIN) blksz = ATI_IXP_BLK_MIN; if (blkcnt > ATI_IXP_DMA_CHSEGS_MAX) blkcnt = ATI_IXP_DMA_CHSEGS_MAX; if (blkcnt < ATI_IXP_DMA_CHSEGS_MIN) blkcnt = ATI_IXP_DMA_CHSEGS_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= ATI_IXP_DMA_CHSEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ATI_IXP_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); ch->blkcnt = sndbuf_getblkcnt(ch->buffer); return (0); } static uint32_t atiixp_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; atiixp_chan_setfragments(obj, data, blksz, sc->blkcnt); return (ch->blksz); } static void atiixp_buildsgdt(struct atiixp_chinfo *ch) { struct atiixp_info *sc = ch->parent; uint32_t addr, blksz, blkcnt; int i; addr = sndbuf_getbufaddr(ch->buffer); if (sc->polling != 0) { blksz = ch->blksz * ch->blkcnt; blkcnt = 1; } else { blksz = ch->blksz; blkcnt = ch->blkcnt; } for (i = 0; i < blkcnt; i++) { ch->sgd_table[i].addr = htole32(addr + (i * blksz)); ch->sgd_table[i].status = htole16(0); ch->sgd_table[i].size = htole16(blksz >> 2); ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + (((i + 1) % blkcnt) * sizeof(struct atiixp_dma_op))); } } static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *ch) { struct atiixp_info *sc = ch->parent; uint32_t reg, addr, sz, retry; volatile uint32_t ptr; reg = ch->dt_cur_bit; addr = sndbuf_getbufaddr(ch->buffer); sz = ch->blkcnt * ch->blksz; retry = ATI_IXP_DMA_RETRY_MAX; do { ptr = atiixp_rd(sc, reg); if (ptr < addr) continue; ptr -= addr; if (ptr < sz) { #if 0 #ifdef ATI_IXP_DEBUG if ((ptr & ~(ch->blksz - 1)) != ch->ptr) { uint32_t delta; delta = (sz + ptr - ch->prevptr) % sz; #ifndef ATI_IXP_DEBUG_VERBOSE if (delta < ch->blksz) #endif device_printf(sc->dev, "PCMDIR_%s: incoherent DMA " "prevptr=%u ptr=%u " "ptr=%u blkcnt=%u " "[delta=%u != blksz=%u] " "(%s)\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->prevptr, ptr, ch->ptr, ch->blkcnt, delta, ch->blksz, (delta < ch->blksz) ? "OVERLAPPED!" : "Ok"); ch->ptr = ptr & ~(ch->blksz - 1); } ch->prevptr = ptr; #endif #endif return (ptr); } } while (--retry); device_printf(sc->dev, "PCMDIR_%s: invalid DMA pointer ptr=%u\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); return (0); } static __inline int atiixp_poll_channel(struct atiixp_chinfo *ch) { uint32_t sz, delta; volatile uint32_t ptr; if (!(ch->flags & ATI_IXP_CHN_RUNNING)) return (0); sz = ch->blksz * ch->blkcnt; ptr = atiixp_dmapos(ch); ch->ptr = ptr; ptr %= sz; ptr &= ~(ch->blksz - 1); delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } #define atiixp_chan_active(sc) (((sc)->pch.flags | (sc)->rch.flags) & \ ATI_IXP_CHN_RUNNING) static void atiixp_poll_callback(void *arg) { struct atiixp_info *sc = arg; uint32_t trigger = 0; if (sc == NULL) return; atiixp_lock(sc); if (sc->polling == 0 || atiixp_chan_active(sc) == 0) { atiixp_unlock(sc); return; } trigger |= (atiixp_poll_channel(&sc->pch) != 0) ? 1 : 0; trigger |= (atiixp_poll_channel(&sc->rch) != 0) ? 2 : 0; /* XXX */ callout_reset(&sc->poll_timer, 1/*sc->poll_ticks*/, atiixp_poll_callback, sc); atiixp_unlock(sc); if (trigger & 1) chn_intr(sc->pch.channel); if (trigger & 2) chn_intr(sc->rch.channel); } static int atiixp_chan_trigger(kobj_t obj, void *data, int go) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; int pollticks; if (!PCMTRIG_COMMON(go)) return (0); atiixp_lock(sc); switch (go) { case PCMTRIG_START: atiixp_flush_dma(ch); atiixp_buildsgdt(ch); atiixp_wr(sc, ch->linkptr_bit, 0); atiixp_enable_dma(ch); atiixp_wr(sc, ch->linkptr_bit, (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); if (sc->polling != 0) { ch->ptr = 0; ch->prevptr = 0; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getalign(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (atiixp_chan_active(sc) == 0 || pollticks < sc->poll_ticks) { if (bootverbose) { if (atiixp_chan_active(sc) == 0) device_printf(sc->dev, "%s: pollticks=%d\n", __func__, pollticks); else device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); } sc->poll_ticks = pollticks; callout_reset(&sc->poll_timer, 1, atiixp_poll_callback, sc); } } ch->flags |= ATI_IXP_CHN_RUNNING; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: atiixp_disable_dma(ch); atiixp_flush_dma(ch); ch->flags &= ~ATI_IXP_CHN_RUNNING; if (sc->polling != 0) { if (atiixp_chan_active(sc) == 0) { callout_stop(&sc->poll_timer); sc->poll_ticks = 1; } else { if (sc->pch.flags & ATI_IXP_CHN_RUNNING) ch = &sc->pch; else ch = &sc->rch; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getalign(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks > sc->poll_ticks) { if (bootverbose) device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); sc->poll_ticks = pollticks; callout_reset(&sc->poll_timer, 1, atiixp_poll_callback, sc); } } } break; default: atiixp_unlock(sc); return (0); break; } /* Update bus busy status */ value = atiixp_rd(sc, ATI_REG_IER); if (atiixp_rd(sc, ATI_REG_CMD) & (ATI_REG_CMD_SEND_EN | ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) value |= ATI_REG_IER_SET_BUS_BUSY; else value &= ~ATI_REG_IER_SET_BUS_BUSY; atiixp_wr(sc, ATI_REG_IER, value); atiixp_unlock(sc); return (0); } static uint32_t atiixp_chan_getptr(kobj_t obj, void *data) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t ptr; atiixp_lock(sc); if (sc->polling != 0) ptr = ch->ptr; else ptr = atiixp_dmapos(ch); atiixp_unlock(sc); return (ptr); } static struct pcmchan_caps * atiixp_chan_getcaps(kobj_t obj, void *data) { struct atiixp_chinfo *ch = data; if (ch->caps_32bit) return (&atiixp_caps_32bit); return (&atiixp_caps); } static kobj_method_t atiixp_chan_methods[] = { KOBJMETHOD(channel_init, atiixp_chan_init), KOBJMETHOD(channel_setformat, atiixp_chan_setformat), KOBJMETHOD(channel_setspeed, atiixp_chan_setspeed), KOBJMETHOD(channel_setblocksize, atiixp_chan_setblocksize), KOBJMETHOD(channel_setfragments, atiixp_chan_setfragments), KOBJMETHOD(channel_trigger, atiixp_chan_trigger), KOBJMETHOD(channel_getptr, atiixp_chan_getptr), KOBJMETHOD(channel_getcaps, atiixp_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(atiixp_chan); /* * PCI driver interface */ static void atiixp_intr(void *p) { struct atiixp_info *sc = p; uint32_t status, enable, detected_codecs; uint32_t trigger = 0; atiixp_lock(sc); if (sc->polling != 0) { atiixp_unlock(sc); return; } status = atiixp_rd(sc, ATI_REG_ISR); if (status == 0) { atiixp_unlock(sc); return; } if ((status & ATI_REG_ISR_OUT_STATUS) && (sc->pch.flags & ATI_IXP_CHN_RUNNING)) trigger |= 1; if ((status & ATI_REG_ISR_IN_STATUS) && (sc->rch.flags & ATI_IXP_CHN_RUNNING)) trigger |= 2; #if 0 if (status & ATI_REG_ISR_IN_XRUN) { device_printf(sc->dev, "Recieve IN XRUN interrupt\n"); } if (status & ATI_REG_ISR_OUT_XRUN) { device_printf(sc->dev, "Recieve OUT XRUN interrupt\n"); } #endif if (status & CODEC_CHECK_BITS) { /* mark missing codecs as not ready */ detected_codecs = status & CODEC_CHECK_BITS; sc->codec_not_ready_bits |= detected_codecs; /* disable detected interrupt sources */ enable = atiixp_rd(sc, ATI_REG_IER); enable &= ~detected_codecs; atiixp_wr(sc, ATI_REG_IER, enable); wakeup(sc); } /* acknowledge */ atiixp_wr(sc, ATI_REG_ISR, status); atiixp_unlock(sc); if (trigger & 1) chn_intr(sc->pch.channel); if (trigger & 2) chn_intr(sc->rch.channel); } static void atiixp_dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct atiixp_info *sc = (struct atiixp_info *)p; sc->sgd_addr = bds->ds_addr; } static void atiixp_chip_pre_init(struct atiixp_info *sc) { uint32_t value; atiixp_lock(sc); /* disable interrupts */ atiixp_disable_interrupts(sc); /* clear all DMA enables (preserving rest of settings) */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~(ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SPDF_OUT_EN ); atiixp_wr(sc, ATI_REG_CMD, value); /* reset aclink */ atiixp_reset_aclink(sc); sc->codec_not_ready_bits = 0; /* enable all codecs to interrupt as well as the new frame interrupt */ atiixp_wr(sc, ATI_REG_IER, CODEC_CHECK_BITS); atiixp_unlock(sc); } static int sysctl_atiixp_polling(SYSCTL_HANDLER_ARGS) { struct atiixp_info *sc; device_t dev; int err, val; dev = oidp->oid_arg1; sc = pcm_getdevinfo(dev); if (sc == NULL) return (EINVAL); atiixp_lock(sc); val = sc->polling; atiixp_unlock(sc); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); atiixp_lock(sc); if (val != sc->polling) { if (atiixp_chan_active(sc) != 0) err = EBUSY; else if (val == 0) { atiixp_enable_interrupts(sc); sc->polling = 0; DELAY(1000); } else { atiixp_disable_interrupts(sc); sc->polling = 1; DELAY(1000); } } atiixp_unlock(sc); return (err); } static void atiixp_chip_post_init(void *arg) { struct atiixp_info *sc = (struct atiixp_info *)arg; uint32_t subdev; int i, timeout, found, polling; char status[SND_STATUSLEN]; atiixp_lock(sc); if (sc->delayed_attach.ich_func) { config_intrhook_disestablish(&sc->delayed_attach); sc->delayed_attach.ich_func = NULL; } polling = sc->polling; sc->polling = 0; timeout = 10; if (sc->codec_not_ready_bits == 0) { /* wait for the interrupts to happen */ do { msleep(sc, sc->lock, PWAIT, "ixpslp", max(hz / 10, 1)); if (sc->codec_not_ready_bits != 0) break; } while (--timeout); } sc->polling = polling; atiixp_disable_interrupts(sc); if (sc->codec_not_ready_bits == 0 && timeout == 0) { device_printf(sc->dev, "WARNING: timeout during codec detection; " "codecs might be present but haven't interrupted\n"); atiixp_unlock(sc); goto postinitbad; } found = 0; /* * ATI IXP can have upto 3 codecs, but single codec should be * suffice for now. */ if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC0_NOT_READY)) { /* codec 0 present */ sc->codec_found++; sc->codec_idx = 0; found++; } if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC1_NOT_READY)) { /* codec 1 present */ sc->codec_found++; } if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) { /* codec 2 present */ sc->codec_found++; } atiixp_unlock(sc); if (found == 0) goto postinitbad; /* create/init mixer */ sc->codec = AC97_CREATE(sc->dev, sc, atiixp_ac97); if (sc->codec == NULL) goto postinitbad; subdev = (pci_get_subdevice(sc->dev) << 16) | pci_get_subvendor(sc->dev); switch (subdev) { case 0x11831043: /* ASUS A6R */ case 0x2043161f: /* Maxselect x710s - http://maxselect.ru/ */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(sc->dev, ac97_getmixerclass(), sc->codec); if (pcm_register(sc->dev, sc, ATI_IXP_NPCHAN, ATI_IXP_NRCHAN)) goto postinitbad; for (i = 0; i < ATI_IXP_NPCHAN; i++) pcm_addchan(sc->dev, PCMDIR_PLAY, &atiixp_chan_class, sc); for (i = 0; i < ATI_IXP_NRCHAN; i++) pcm_addchan(sc->dev, PCMDIR_REC, &atiixp_chan_class, sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc->dev, sizeof(sc->dev), sysctl_atiixp_polling, "I", "Enable polling mode"); - snprintf(status, SND_STATUSLEN, "at memory 0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "mem 0x%jx irq %jd on %s", rman_get_start(sc->reg), rman_get_start(sc->irq), - PCM_KLDSTRING(snd_atiixp)); + device_get_nameunit(device_get_parent(sc->dev))); pcm_setstatus(sc->dev, status); atiixp_lock(sc); if (sc->polling == 0) atiixp_enable_interrupts(sc); atiixp_unlock(sc); return; postinitbad: atiixp_release_resource(sc); } static void atiixp_release_resource(struct atiixp_info *sc) { if (sc == NULL) return; if (sc->registered_channels != 0) { atiixp_lock(sc); sc->polling = 0; callout_stop(&sc->poll_timer); atiixp_unlock(sc); callout_drain(&sc->poll_timer); } if (sc->codec) { ac97_destroy(sc->codec); sc->codec = NULL; } if (sc->ih) { bus_teardown_intr(sc->dev, sc->irq, sc->ih); sc->ih = NULL; } if (sc->reg) { bus_release_resource(sc->dev, sc->regtype, sc->regid, sc->reg); sc->reg = NULL; } if (sc->irq) { bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = NULL; } if (sc->sgd_addr) { bus_dmamap_unload(sc->sgd_dmat, sc->sgd_dmamap); sc->sgd_addr = 0; } if (sc->sgd_table) { bus_dmamem_free(sc->sgd_dmat, sc->sgd_table, sc->sgd_dmamap); sc->sgd_table = NULL; } if (sc->sgd_dmat) { bus_dma_tag_destroy(sc->sgd_dmat); sc->sgd_dmat = NULL; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } free(sc, M_DEVBUF); } static int atiixp_pci_probe(device_t dev) { int i; uint16_t devid, vendor; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < sizeof(atiixp_hw) / sizeof(atiixp_hw[0]); i++) { if (vendor == atiixp_hw[i].vendor && devid == atiixp_hw[i].devid) { device_set_desc(dev, atiixp_hw[i].desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int atiixp_pci_attach(device_t dev) { struct atiixp_info *sc; int i; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_atiixp softc"); sc->dev = dev; callout_init(&sc->poll_timer, 1); sc->poll_ticks = 1; if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) sc->polling = 1; else sc->polling = 0; pci_enable_busmaster(dev); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, ATI_IXP_BUFSZ_MIN, ATI_IXP_BUFSZ_DEFAULT, ATI_IXP_BUFSZ_MAX); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, atiixp_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } /* * Let the user choose the best DMA segments. */ if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= ATI_IXP_BLK_ALIGN; if (i < ATI_IXP_BLK_MIN) i = ATI_IXP_BLK_MIN; sc->blkcnt = sc->bufsz / i; i = 0; while (sc->blkcnt >> i) i++; sc->blkcnt = 1 << (i - 1); if (sc->blkcnt < ATI_IXP_DMA_CHSEGS_MIN) sc->blkcnt = ATI_IXP_DMA_CHSEGS_MIN; else if (sc->blkcnt > ATI_IXP_DMA_CHSEGS_MAX) sc->blkcnt = ATI_IXP_DMA_CHSEGS_MAX; } else sc->blkcnt = ATI_IXP_DMA_CHSEGS; /* * DMA tag for scatter-gather buffers and link pointers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) goto bad; if (bus_dmamap_load(sc->sgd_dmat, sc->sgd_dmamap, sc->sgd_table, ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), atiixp_dma_cb, sc, 0)) goto bad; atiixp_chip_pre_init(sc); sc->delayed_attach.ich_func = atiixp_chip_post_init; sc->delayed_attach.ich_arg = sc; if (cold == 0 || config_intrhook_establish(&sc->delayed_attach) != 0) { sc->delayed_attach.ich_func = NULL; atiixp_chip_post_init(sc); } return (0); bad: atiixp_release_resource(sc); return (ENXIO); } static int atiixp_pci_detach(device_t dev) { int r; struct atiixp_info *sc; sc = pcm_getdevinfo(dev); if (sc != NULL) { if (sc->codec != NULL) { r = pcm_unregister(dev); if (r) return (r); } sc->codec = NULL; if (sc->st != 0 && sc->sh != 0) atiixp_disable_interrupts(sc); atiixp_release_resource(sc); } return (0); } static int atiixp_pci_suspend(device_t dev) { struct atiixp_info *sc = pcm_getdevinfo(dev); /* quickly disable interrupts and save channels active state */ atiixp_lock(sc); atiixp_disable_interrupts(sc); atiixp_unlock(sc); /* stop everything */ if (sc->pch.flags & ATI_IXP_CHN_RUNNING) { atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); sc->pch.flags |= ATI_IXP_CHN_SUSPEND; } if (sc->rch.flags & ATI_IXP_CHN_RUNNING) { atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); sc->rch.flags |= ATI_IXP_CHN_SUSPEND; } /* power down aclink and pci bus */ atiixp_lock(sc); atiixp_wr(sc, ATI_REG_CMD, ATI_REG_CMD_POWERDOWN); atiixp_unlock(sc); return (0); } static int atiixp_pci_resume(device_t dev) { struct atiixp_info *sc = pcm_getdevinfo(dev); atiixp_lock(sc); /* reset / power up aclink */ atiixp_reset_aclink(sc); atiixp_unlock(sc); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } /* * Resume channel activities. Reset channel format regardless * of its previous state. */ if (sc->pch.channel != NULL) { if (sc->pch.fmt != 0) atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); if (sc->pch.flags & ATI_IXP_CHN_SUSPEND) { sc->pch.flags &= ~ATI_IXP_CHN_SUSPEND; atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); } } if (sc->rch.channel != NULL) { if (sc->rch.fmt != 0) atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); if (sc->rch.flags & ATI_IXP_CHN_SUSPEND) { sc->rch.flags &= ~ATI_IXP_CHN_SUSPEND; atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); } } /* enable interrupts */ atiixp_lock(sc); if (sc->polling == 0) atiixp_enable_interrupts(sc); atiixp_unlock(sc); return (0); } static device_method_t atiixp_methods[] = { DEVMETHOD(device_probe, atiixp_pci_probe), DEVMETHOD(device_attach, atiixp_pci_attach), DEVMETHOD(device_detach, atiixp_pci_detach), DEVMETHOD(device_suspend, atiixp_pci_suspend), DEVMETHOD(device_resume, atiixp_pci_resume), { 0, 0 } }; static driver_t atiixp_driver = { "pcm", atiixp_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_atiixp, pci, atiixp_driver, 0, 0); MODULE_DEPEND(snd_atiixp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_atiixp, 1); diff --git a/sys/dev/sound/pci/cmi.c b/sys/dev/sound/pci/cmi.c index 4fd6ac4c0a75..9a92066c51a4 100644 --- a/sys/dev/sound/pci/cmi.c +++ b/sys/dev/sound/pci/cmi.c @@ -1,1110 +1,1111 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * This driver exists largely as a result of other people's efforts. * Much of register handling is based on NetBSD CMI8x38 audio driver * by Takuya Shiozaki . Chen-Li Tien * clarified points regarding the DMA related * registers and the 8738 mixer devices. His Linux driver was also a * useful reference point. * * TODO: MIDI * * SPDIF contributed by Gerhard Gonter . * * This card/code does not always manage to sample at 44100 - actual * rate drifts slightly between recordings (usually 0-3%). No * differences visible in register dumps between times that work and * those that don't. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include "mixer_if.h" #include "mpufoi_if.h" /* Supported chip ID's */ #define CMI8338A_PCI_ID 0x010013f6 #define CMI8338B_PCI_ID 0x010113f6 #define CMI8738_PCI_ID 0x011113f6 #define CMI8738B_PCI_ID 0x011213f6 #define CMI120_USB_ID 0x01030d8c /* Buffer size max is 64k for permitted DMA boundaries */ #define CMI_DEFAULT_BUFSZ 16384 /* Interrupts per length of buffer */ #define CMI_INTR_PER_BUFFER 2 /* Clarify meaning of named defines in cmireg.h */ #define CMPCI_REG_DMA0_MAX_SAMPLES CMPCI_REG_DMA0_BYTES #define CMPCI_REG_DMA0_INTR_SAMPLES CMPCI_REG_DMA0_SAMPLES #define CMPCI_REG_DMA1_MAX_SAMPLES CMPCI_REG_DMA1_BYTES #define CMPCI_REG_DMA1_INTR_SAMPLES CMPCI_REG_DMA1_SAMPLES /* Our indication of custom mixer control */ #define CMPCI_NON_SB16_CONTROL 0xff /* Debugging macro's */ #undef DEB #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ #ifndef DEBMIX #define DEBMIX(x) /* x */ #endif /* DEBMIX */ /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t fmt, spd, phys_buf, bps; u_int32_t dma_active:1, dma_was_active:1; int dir; }; struct sc_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regid, irqid; void *ih; struct mtx *lock; int spdif_enabled; unsigned int bufsz; struct sc_chinfo pch, rch; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; struct resource *mpu_reg; int mpu_regid; bus_space_tag_t mpu_bt; bus_space_handle_t mpu_bh; }; /* Channel caps */ static u_int32_t cmi_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps cmi_caps = {5512, 48000, cmi_fmt, 0}; /* ------------------------------------------------------------------------- */ /* Register Utilities */ static u_int32_t cmi_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: DEB(printf("cmi_rd: failed 0x%04x %d\n", regno, size)); return 0xFFFFFFFF; } } static void cmi_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->st, sc->sh, regno, data); break; case 2: bus_space_write_2(sc->st, sc->sh, regno, data); break; case 4: bus_space_write_4(sc->st, sc->sh, regno, data); break; } } static void cmi_partial_wr4(struct sc_info *sc, int reg, int shift, u_int32_t mask, u_int32_t val) { u_int32_t r; r = cmi_rd(sc, reg, 4); r &= ~(mask << shift); r |= val << shift; cmi_wr(sc, reg, r, 4); } static void cmi_clr4(struct sc_info *sc, int reg, u_int32_t mask) { u_int32_t r; r = cmi_rd(sc, reg, 4); r &= ~mask; cmi_wr(sc, reg, r, 4); } static void cmi_set4(struct sc_info *sc, int reg, u_int32_t mask) { u_int32_t r; r = cmi_rd(sc, reg, 4); r |= mask; cmi_wr(sc, reg, r, 4); } /* ------------------------------------------------------------------------- */ /* Rate Mapping */ static int cmi_rates[] = {5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000}; #define NUM_CMI_RATES (sizeof(cmi_rates)/sizeof(cmi_rates[0])) /* cmpci_rate_to_regvalue returns sampling freq selector for FCR1 * register - reg order is 5k,11k,22k,44k,8k,16k,32k,48k */ static u_int32_t cmpci_rate_to_regvalue(int rate) { int i, r; for(i = 0; i < NUM_CMI_RATES - 1; i++) { if (rate < ((cmi_rates[i] + cmi_rates[i + 1]) / 2)) { break; } } DEB(printf("cmpci_rate_to_regvalue: %d -> %d\n", rate, cmi_rates[i])); r = ((i >> 1) | (i << 2)) & 0x07; return r; } static int cmpci_regvalue_to_rate(u_int32_t r) { int i; i = ((r << 1) | (r >> 2)) & 0x07; DEB(printf("cmpci_regvalue_to_rate: %d -> %d\n", r, i)); return cmi_rates[i]; } /* ------------------------------------------------------------------------- */ /* ADC/DAC control - there are 2 dma channels on 8738, either can be * playback or capture. We use ch0 for playback and ch1 for capture. */ static void cmi_dma_prog(struct sc_info *sc, struct sc_chinfo *ch, u_int32_t base) { u_int32_t s, i, sz; ch->phys_buf = sndbuf_getbufaddr(ch->buffer); cmi_wr(sc, base, ch->phys_buf, 4); sz = (u_int32_t)sndbuf_getsize(ch->buffer); s = sz / ch->bps - 1; cmi_wr(sc, base + 4, s, 2); i = sz / (ch->bps * CMI_INTR_PER_BUFFER) - 1; cmi_wr(sc, base + 6, i, 2); } static void cmi_ch0_start(struct sc_info *sc, struct sc_chinfo *ch) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA0_BASE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE); cmi_set4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); ch->dma_active = 1; } static u_int32_t cmi_ch0_stop(struct sc_info *sc, struct sc_chinfo *ch) { u_int32_t r = ch->dma_active; cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_RESET); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_RESET); ch->dma_active = 0; return r; } static void cmi_ch1_start(struct sc_info *sc, struct sc_chinfo *ch) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA1_BASE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_ENABLE); /* Enable Interrupts */ cmi_set4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); DEB(printf("cmi_ch1_start: dma prog\n")); ch->dma_active = 1; } static u_int32_t cmi_ch1_stop(struct sc_info *sc, struct sc_chinfo *ch) { u_int32_t r = ch->dma_active; cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_ENABLE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_RESET); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_RESET); ch->dma_active = 0; return r; } static void cmi_spdif_speed(struct sc_info *sc, int speed) { u_int32_t fcr1, lcr, mcr; if (speed >= 44100) { fcr1 = CMPCI_REG_SPDIF0_ENABLE; lcr = CMPCI_REG_XSPDIF_ENABLE; mcr = (speed == 48000) ? CMPCI_REG_W_SPDIF_48L | CMPCI_REG_SPDIF_48K : 0; } else { fcr1 = mcr = lcr = 0; } cmi_partial_wr4(sc, CMPCI_REG_MISC, 0, CMPCI_REG_W_SPDIF_48L | CMPCI_REG_SPDIF_48K, mcr); cmi_partial_wr4(sc, CMPCI_REG_FUNC_1, 0, CMPCI_REG_SPDIF0_ENABLE, fcr1); cmi_partial_wr4(sc, CMPCI_REG_LEGACY_CTRL, 0, CMPCI_REG_XSPDIF_ENABLE, lcr); } /* ------------------------------------------------------------------------- */ /* Channel Interface implementation */ static void * cmichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->bps = 1; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->buffer = b; ch->dma_active = 0; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("cmichan_init failed\n")); return NULL; } ch->dir = dir; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA0_BASE); } else { cmi_dma_prog(sc, ch, CMPCI_REG_DMA1_BASE); } snd_mtxunlock(sc->lock); return ch; } static int cmichan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t f; if (format & AFMT_S16_LE) { f = CMPCI_REG_FORMAT_16BIT; ch->bps = 2; } else { f = CMPCI_REG_FORMAT_8BIT; ch->bps = 1; } if (AFMT_CHANNEL(format) > 1) { f |= CMPCI_REG_FORMAT_STEREO; ch->bps *= 2; } else { f |= CMPCI_REG_FORMAT_MONO; } snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { cmi_partial_wr4(ch->parent, CMPCI_REG_CHANNEL_FORMAT, CMPCI_REG_CH0_FORMAT_SHIFT, CMPCI_REG_CH0_FORMAT_MASK, f); } else { cmi_partial_wr4(ch->parent, CMPCI_REG_CHANNEL_FORMAT, CMPCI_REG_CH1_FORMAT_SHIFT, CMPCI_REG_CH1_FORMAT_MASK, f); } snd_mtxunlock(sc->lock); ch->fmt = format; return 0; } static u_int32_t cmichan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t r, rsp __unused; r = cmpci_rate_to_regvalue(speed); snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (speed < 44100) { /* disable if req before rate change */ cmi_spdif_speed(ch->parent, speed); } cmi_partial_wr4(ch->parent, CMPCI_REG_FUNC_1, CMPCI_REG_DAC_FS_SHIFT, CMPCI_REG_DAC_FS_MASK, r); if (speed >= 44100 && ch->parent->spdif_enabled) { /* enable if req after rate change */ cmi_spdif_speed(ch->parent, speed); } rsp = cmi_rd(ch->parent, CMPCI_REG_FUNC_1, 4); rsp >>= CMPCI_REG_DAC_FS_SHIFT; rsp &= CMPCI_REG_DAC_FS_MASK; } else { cmi_partial_wr4(ch->parent, CMPCI_REG_FUNC_1, CMPCI_REG_ADC_FS_SHIFT, CMPCI_REG_ADC_FS_MASK, r); rsp = cmi_rd(ch->parent, CMPCI_REG_FUNC_1, 4); rsp >>= CMPCI_REG_ADC_FS_SHIFT; rsp &= CMPCI_REG_ADC_FS_MASK; } snd_mtxunlock(sc->lock); ch->spd = cmpci_regvalue_to_rate(r); DEB(printf("cmichan_setspeed (%s) %d -> %d (%d)\n", (ch->dir == PCMDIR_PLAY) ? "play" : "rec", speed, ch->spd, cmpci_regvalue_to_rate(rsp))); return ch->spd; } static u_int32_t cmichan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; /* user has requested interrupts every blocksize bytes */ if (blocksize > sc->bufsz / CMI_INTR_PER_BUFFER) { blocksize = sc->bufsz / CMI_INTR_PER_BUFFER; } sndbuf_resize(ch->buffer, CMI_INTR_PER_BUFFER, blocksize); return blocksize; } static int cmichan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { switch(go) { case PCMTRIG_START: cmi_ch0_start(sc, ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch0_stop(sc, ch); break; } } else { switch(go) { case PCMTRIG_START: cmi_ch1_start(sc, ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch1_stop(sc, ch); break; } } snd_mtxunlock(sc->lock); return 0; } static u_int32_t cmichan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t physptr, bufptr, sz; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { physptr = cmi_rd(sc, CMPCI_REG_DMA0_BASE, 4); } else { physptr = cmi_rd(sc, CMPCI_REG_DMA1_BASE, 4); } snd_mtxunlock(sc->lock); sz = sndbuf_getsize(ch->buffer); bufptr = (physptr - ch->phys_buf + sz - ch->bps) % sz; return bufptr; } static void cmi_intr(void *data) { struct sc_info *sc = data; u_int32_t intrstat; u_int32_t toclear; snd_mtxlock(sc->lock); intrstat = cmi_rd(sc, CMPCI_REG_INTR_STATUS, 4); if ((intrstat & CMPCI_REG_ANY_INTR) != 0) { toclear = 0; if (intrstat & CMPCI_REG_CH0_INTR) { toclear |= CMPCI_REG_CH0_INTR_ENABLE; //cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); } if (intrstat & CMPCI_REG_CH1_INTR) { toclear |= CMPCI_REG_CH1_INTR_ENABLE; //cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); } if (toclear) { cmi_clr4(sc, CMPCI_REG_INTR_CTRL, toclear); snd_mtxunlock(sc->lock); /* Signal interrupts to channel */ if (intrstat & CMPCI_REG_CH0_INTR) { chn_intr(sc->pch.channel); } if (intrstat & CMPCI_REG_CH1_INTR) { chn_intr(sc->rch.channel); } snd_mtxlock(sc->lock); cmi_set4(sc, CMPCI_REG_INTR_CTRL, toclear); } } if(sc->mpu_intr) { (sc->mpu_intr)(sc->mpu); } snd_mtxunlock(sc->lock); return; } static struct pcmchan_caps * cmichan_getcaps(kobj_t obj, void *data) { return &cmi_caps; } static kobj_method_t cmichan_methods[] = { KOBJMETHOD(channel_init, cmichan_init), KOBJMETHOD(channel_setformat, cmichan_setformat), KOBJMETHOD(channel_setspeed, cmichan_setspeed), KOBJMETHOD(channel_setblocksize, cmichan_setblocksize), KOBJMETHOD(channel_trigger, cmichan_trigger), KOBJMETHOD(channel_getptr, cmichan_getptr), KOBJMETHOD(channel_getcaps, cmichan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(cmichan); /* ------------------------------------------------------------------------- */ /* Mixer - sb16 with kinks */ static void cmimix_wr(struct sc_info *sc, u_int8_t port, u_int8_t val) { cmi_wr(sc, CMPCI_REG_SBADDR, port, 1); cmi_wr(sc, CMPCI_REG_SBDATA, val, 1); } static u_int8_t cmimix_rd(struct sc_info *sc, u_int8_t port) { cmi_wr(sc, CMPCI_REG_SBADDR, port, 1); return (u_int8_t)cmi_rd(sc, CMPCI_REG_SBDATA, 1); } struct sb16props { u_int8_t rreg; /* right reg chan register */ u_int8_t stereo:1; /* (no explanation needed, honest) */ u_int8_t rec:1; /* recording source */ u_int8_t bits:3; /* num bits to represent maximum gain rep */ u_int8_t oselect; /* output select mask */ u_int8_t iselect; /* right input select mask */ } static const cmt[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_SYNTH] = {CMPCI_SB16_MIXER_FM_R, 1, 1, 5, CMPCI_SB16_SW_FM, CMPCI_SB16_MIXER_FM_SRC_R}, [SOUND_MIXER_CD] = {CMPCI_SB16_MIXER_CDDA_R, 1, 1, 5, CMPCI_SB16_SW_CD, CMPCI_SB16_MIXER_CD_SRC_R}, [SOUND_MIXER_LINE] = {CMPCI_SB16_MIXER_LINE_R, 1, 1, 5, CMPCI_SB16_SW_LINE, CMPCI_SB16_MIXER_LINE_SRC_R}, [SOUND_MIXER_MIC] = {CMPCI_SB16_MIXER_MIC, 0, 1, 5, CMPCI_SB16_SW_MIC, CMPCI_SB16_MIXER_MIC_SRC}, [SOUND_MIXER_SPEAKER] = {CMPCI_SB16_MIXER_SPEAKER, 0, 0, 2, 0, 0}, [SOUND_MIXER_PCM] = {CMPCI_SB16_MIXER_VOICE_R, 1, 0, 5, 0, 0}, [SOUND_MIXER_VOLUME] = {CMPCI_SB16_MIXER_MASTER_R, 1, 0, 5, 0, 0}, /* These controls are not implemented in CMI8738, but maybe at a future date. They are not documented in C-Media documentation, though appear in other drivers for future h/w (ALSA, Linux, NetBSD). */ [SOUND_MIXER_IGAIN] = {CMPCI_SB16_MIXER_INGAIN_R, 1, 0, 2, 0, 0}, [SOUND_MIXER_OGAIN] = {CMPCI_SB16_MIXER_OUTGAIN_R, 1, 0, 2, 0, 0}, [SOUND_MIXER_BASS] = {CMPCI_SB16_MIXER_BASS_R, 1, 0, 4, 0, 0}, [SOUND_MIXER_TREBLE] = {CMPCI_SB16_MIXER_TREBLE_R, 1, 0, 4, 0, 0}, /* The mic pre-amp is implemented with non-SB16 compatible registers. */ [SOUND_MIXER_MONITOR] = {CMPCI_NON_SB16_CONTROL, 0, 1, 4, 0}, }; #define MIXER_GAIN_REG_RTOL(r) (r - 1) static int cmimix_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i,v; for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (cmt[i].bits) v |= 1 << i; } mix_setdevs(m, v); for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (cmt[i].rec) v |= 1 << i; } mix_setrecdevs(m, v); cmimix_wr(sc, CMPCI_SB16_MIXER_RESET, 0); cmimix_wr(sc, CMPCI_SB16_MIXER_ADCMIX_L, 0); cmimix_wr(sc, CMPCI_SB16_MIXER_ADCMIX_R, 0); cmimix_wr(sc, CMPCI_SB16_MIXER_OUTMIX, CMPCI_SB16_SW_CD | CMPCI_SB16_SW_MIC | CMPCI_SB16_SW_LINE); return 0; } static int cmimix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t r, l, max; u_int8_t v; max = (1 << cmt[dev].bits) - 1; if (cmt[dev].rreg == CMPCI_NON_SB16_CONTROL) { /* For time being this can only be one thing (mic in * mic/aux reg) */ v = cmi_rd(sc, CMPCI_REG_AUX_MIC, 1) & 0xf0; l = left * max / 100; /* 3 bit gain with LSB MICGAIN off(1),on(1) -> 4 bit value */ v |= ((l << 1) | (~l >> 3)) & 0x0f; cmi_wr(sc, CMPCI_REG_AUX_MIC, v, 1); return 0; } l = (left * max / 100) << (8 - cmt[dev].bits); if (cmt[dev].stereo) { r = (right * max / 100) << (8 - cmt[dev].bits); cmimix_wr(sc, MIXER_GAIN_REG_RTOL(cmt[dev].rreg), l); cmimix_wr(sc, cmt[dev].rreg, r); DEBMIX(printf("Mixer stereo write dev %d reg 0x%02x "\ "value 0x%02x:0x%02x\n", dev, MIXER_GAIN_REG_RTOL(cmt[dev].rreg), l, r)); } else { r = l; cmimix_wr(sc, cmt[dev].rreg, l); DEBMIX(printf("Mixer mono write dev %d reg 0x%02x " \ "value 0x%02x:0x%02x\n", dev, cmt[dev].rreg, l, l)); } /* Zero gain does not mute channel from output, but this does... */ v = cmimix_rd(sc, CMPCI_SB16_MIXER_OUTMIX); if (l == 0 && r == 0) { v &= ~cmt[dev].oselect; } else { v |= cmt[dev].oselect; } cmimix_wr(sc, CMPCI_SB16_MIXER_OUTMIX, v); return 0; } static u_int32_t cmimix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, ml, sl; ml = sl = 0; for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1< */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "spdif_enabled", CTLFLAG_RW, &sc->spdif_enabled, 0, "enable SPDIF output at 44.1 kHz and above"); return 0; } /* ------------------------------------------------------------------------- */ static kobj_method_t cmi_mixer_methods[] = { KOBJMETHOD(mixer_init, cmimix_init), KOBJMETHOD(mixer_set, cmimix_set), KOBJMETHOD(mixer_setrecsrc, cmimix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(cmi_mixer); /* * mpu401 functions */ static unsigned char cmi_mread(struct mpu401 *arg, void *sc, int reg) { unsigned int d; d = bus_space_read_1(0,0, 0x330 + reg); /* printf("cmi_mread: reg %x %x\n",reg, d); */ return d; } static void cmi_mwrite(struct mpu401 *arg, void *sc, int reg, unsigned char b) { bus_space_write_1(0,0,0x330 + reg , b); } static int cmi_muninit(struct mpu401 *arg, void *cookie) { struct sc_info *sc = cookie; snd_mtxlock(sc->lock); sc->mpu_intr = NULL; sc->mpu = NULL; snd_mtxunlock(sc->lock); return 0; } static kobj_method_t cmi_mpu_methods[] = { KOBJMETHOD(mpufoi_read, cmi_mread), KOBJMETHOD(mpufoi_write, cmi_mwrite), KOBJMETHOD(mpufoi_uninit, cmi_muninit), KOBJMETHOD_END }; static DEFINE_CLASS(cmi_mpu, cmi_mpu_methods, 0); static void cmi_midiattach(struct sc_info *sc) { /* const struct { int port,bits; } *p, ports[] = { {0x330,0}, {0x320,1}, {0x310,2}, {0x300,3}, {0,0} } ; Notes, CMPCI_REG_VMPUSEL sets the io port for the mpu. Does anyone know how to bus_space tag? */ cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); cmi_clr4(sc, CMPCI_REG_LEGACY_CTRL, CMPCI_REG_VMPUSEL_MASK << CMPCI_REG_VMPUSEL_SHIFT); cmi_set4(sc, CMPCI_REG_LEGACY_CTRL, 0 << CMPCI_REG_VMPUSEL_SHIFT ); cmi_set4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); sc->mpu = mpu401_init(&cmi_mpu_class, sc, cmi_intr, &sc->mpu_intr); } /* ------------------------------------------------------------------------- */ /* Power and reset */ static void cmi_power(struct sc_info *sc, int state) { switch (state) { case 0: /* full power */ cmi_clr4(sc, CMPCI_REG_MISC, CMPCI_REG_POWER_DOWN); break; default: /* power off */ cmi_set4(sc, CMPCI_REG_MISC, CMPCI_REG_POWER_DOWN); break; } } static int cmi_init(struct sc_info *sc) { /* Effect reset */ cmi_set4(sc, CMPCI_REG_MISC, CMPCI_REG_BUS_AND_DSP_RESET); DELAY(100); cmi_clr4(sc, CMPCI_REG_MISC, CMPCI_REG_BUS_AND_DSP_RESET); /* Disable interrupts and channels */ cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE | CMPCI_REG_CH1_ENABLE); cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE | CMPCI_REG_CH1_INTR_ENABLE); /* Configure DMA channels, ch0 = play, ch1 = capture */ cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_DIR); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_DIR); /* Attempt to enable 4 Channel output */ cmi_set4(sc, CMPCI_REG_MISC, CMPCI_REG_N4SPK3D); /* Disable SPDIF1 - not compatible with config */ cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_SPDIF1_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_SPDIF_LOOP); return 0; } static void cmi_uninit(struct sc_info *sc) { /* Disable interrupts and channels */ cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE | CMPCI_REG_CH1_INTR_ENABLE | CMPCI_REG_TDMA_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE | CMPCI_REG_CH1_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); if( sc->mpu ) sc->mpu_intr = NULL; } /* ------------------------------------------------------------------------- */ /* Bus and device registration */ static int cmi_probe(device_t dev) { switch(pci_get_devid(dev)) { case CMI8338A_PCI_ID: device_set_desc(dev, "CMedia CMI8338A"); return BUS_PROBE_DEFAULT; case CMI8338B_PCI_ID: device_set_desc(dev, "CMedia CMI8338B"); return BUS_PROBE_DEFAULT; case CMI8738_PCI_ID: device_set_desc(dev, "CMedia CMI8738"); return BUS_PROBE_DEFAULT; case CMI8738B_PCI_ID: device_set_desc(dev, "CMedia CMI8738B"); return BUS_PROBE_DEFAULT; case CMI120_USB_ID: device_set_desc(dev, "CMedia CMI120"); return BUS_PROBE_DEFAULT; default: return ENXIO; } } static int cmi_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_cmi softc"); pci_enable_busmaster(dev); sc->dev = dev; sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "cmi_attach: Cannot allocate bus resource\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); if (0) cmi_midiattach(sc); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, cmi_intr, sc, &sc->ih)) { device_printf(dev, "cmi_attach: Unable to map interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, CMI_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockfunc*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "cmi_attach: Unable to create dma tag\n"); goto bad; } cmi_power(sc, 0); if (cmi_init(sc)) goto bad; if (mixer_init(dev, &cmi_mixer_class, sc)) goto bad; if (pcm_register(dev, sc, 1, 1)) goto bad; cmi_initsys(sc); pcm_addchan(dev, PCMDIR_PLAY, &cmichan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cmichan_class, sc); - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", - rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_cmi)); + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", + rman_get_start(sc->reg), rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); DEB(printf("cmi_attach: succeeded\n")); return 0; bad: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); if (sc->lock) snd_mtxfree(sc->lock); if (sc) free(sc, M_DEVBUF); return ENXIO; } static int cmi_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); cmi_uninit(sc); cmi_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if(sc->mpu) mpu401_uninit(sc->mpu); bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); if (sc->mpu_reg) bus_release_resource(dev, SYS_RES_IOPORT, sc->mpu_regid, sc->mpu_reg); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } static int cmi_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = cmi_ch0_stop(sc, &sc->pch); sc->rch.dma_was_active = cmi_ch1_stop(sc, &sc->rch); cmi_power(sc, 3); snd_mtxunlock(sc->lock); return 0; } static int cmi_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); cmi_power(sc, 0); if (cmi_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { cmichan_setspeed(NULL, &sc->pch, sc->pch.spd); cmichan_setformat(NULL, &sc->pch, sc->pch.fmt); cmi_ch0_start(sc, &sc->pch); } if (sc->rch.dma_was_active) { cmichan_setspeed(NULL, &sc->rch, sc->rch.spd); cmichan_setformat(NULL, &sc->rch, sc->rch.fmt); cmi_ch1_start(sc, &sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t cmi_methods[] = { DEVMETHOD(device_probe, cmi_probe), DEVMETHOD(device_attach, cmi_attach), DEVMETHOD(device_detach, cmi_detach), DEVMETHOD(device_resume, cmi_resume), DEVMETHOD(device_suspend, cmi_suspend), { 0, 0 } }; static driver_t cmi_driver = { "pcm", cmi_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_cmi, pci, cmi_driver, 0, 0); MODULE_DEPEND(snd_cmi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_cmi, midi, 1,1,1); MODULE_VERSION(snd_cmi, 1); diff --git a/sys/dev/sound/pci/cs4281.c b/sys/dev/sound/pci/cs4281.c index 25beb3f7fd0d..972b83efff8f 100644 --- a/sys/dev/sound/pci/cs4281.c +++ b/sys/dev/sound/pci/cs4281.c @@ -1,967 +1,968 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * The order of pokes in the initiation sequence is based on Linux * driver by Thomas Sailer, gw boynton (wesb@crystal.cirrus.com), tom * woller (twoller@crystal.cirrus.com). Shingo Watanabe (nabe@nabechan.org) * contributed towards power management. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #define CS4281_DEFAULT_BUFSZ 16384 /* Max fifo size for full duplex is 64 */ #define CS4281_FIFO_SIZE 15 /* DMA Engine Indices */ #define CS4281_DMA_PLAY 0 #define CS4281_DMA_REC 1 /* Misc */ #define inline __inline #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; /* channel registers */ struct sc_chinfo { struct sc_info *parent; struct snd_dbuf *buffer; struct pcm_channel *channel; u_int32_t spd, fmt, bps, blksz; int dma_setup, dma_active, dma_chan; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq, *mem; int regtype, regid, irqid, memid; void *ih; int power; unsigned long bufsz; struct sc_chinfo pch; struct sc_chinfo rch; }; /* -------------------------------------------------------------------- */ /* prototypes */ /* ADC/DAC control */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go); static void adcdac_prog(struct sc_chinfo *ch); /* power management and interrupt control */ static void cs4281_intr(void *); static int cs4281_power(struct sc_info *, int); static int cs4281_init(struct sc_info *); /* talk to the card */ static u_int32_t cs4281_rd(struct sc_info *, int); static void cs4281_wr(struct sc_info *, int, u_int32_t); /* misc */ static u_int8_t cs4281_rate_to_rv(u_int32_t); static u_int32_t cs4281_format_to_dmr(u_int32_t); static u_int32_t cs4281_format_to_bps(u_int32_t); /* -------------------------------------------------------------------- */ /* formats (do not add formats without editing cs_fmt_tab) */ static u_int32_t cs4281_fmts[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), SND_FORMAT(AFMT_U16_BE, 1, 0), SND_FORMAT(AFMT_U16_BE, 2, 0), 0 }; static struct pcmchan_caps cs4281_caps = {6024, 48000, cs4281_fmts, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static inline u_int32_t cs4281_rd(struct sc_info *sc, int regno) { return bus_space_read_4(sc->st, sc->sh, regno); } static inline void cs4281_wr(struct sc_info *sc, int regno, u_int32_t data) { bus_space_write_4(sc->st, sc->sh, regno, data); DELAY(100); } static inline void cs4281_clr4(struct sc_info *sc, int regno, u_int32_t mask) { u_int32_t r; r = cs4281_rd(sc, regno); cs4281_wr(sc, regno, r & ~mask); } static inline void cs4281_set4(struct sc_info *sc, int regno, u_int32_t mask) { u_int32_t v; v = cs4281_rd(sc, regno); cs4281_wr(sc, regno, v | mask); } static int cs4281_waitset(struct sc_info *sc, int regno, u_int32_t mask, int tries) { u_int32_t v; while (tries > 0) { DELAY(100); v = cs4281_rd(sc, regno); if ((v & mask) == mask) break; tries --; } return tries; } static int cs4281_waitclr(struct sc_info *sc, int regno, u_int32_t mask, int tries) { u_int32_t v; while (tries > 0) { DELAY(100); v = ~ cs4281_rd(sc, regno); if (v & mask) break; tries --; } return tries; } /* ------------------------------------------------------------------------- */ /* Register value mapping functions */ static u_int32_t cs4281_rates[] = {48000, 44100, 22050, 16000, 11025, 8000}; #define CS4281_NUM_RATES sizeof(cs4281_rates)/sizeof(cs4281_rates[0]) static u_int8_t cs4281_rate_to_rv(u_int32_t rate) { u_int32_t v; for (v = 0; v < CS4281_NUM_RATES; v++) { if (rate == cs4281_rates[v]) return v; } v = 1536000 / rate; if (v > 255 || v < 32) v = 5; /* default to 8k */ return v; } static u_int32_t cs4281_rv_to_rate(u_int8_t rv) { u_int32_t r; if (rv < CS4281_NUM_RATES) return cs4281_rates[rv]; r = 1536000 / rv; return r; } static inline u_int32_t cs4281_format_to_dmr(u_int32_t format) { u_int32_t dmr = 0; if (AFMT_8BIT & format) dmr |= CS4281PCI_DMR_SIZE8; if (AFMT_CHANNEL(format) < 2) dmr |= CS4281PCI_DMR_MONO; if (AFMT_BIGENDIAN & format) dmr |= CS4281PCI_DMR_BEND; if (!(AFMT_SIGNED & format)) dmr |= CS4281PCI_DMR_USIGN; return dmr; } static inline u_int32_t cs4281_format_to_bps(u_int32_t format) { return ((AFMT_8BIT & format) ? 1 : 2) * ((AFMT_CHANNEL(format) > 1) ? 2 : 1); } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int cs4281_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; /* Remove old state */ cs4281_rd(sc, CS4281PCI_ACSDA); /* Fill in AC97 register value request form */ cs4281_wr(sc, CS4281PCI_ACCAD, regno); cs4281_wr(sc, CS4281PCI_ACCDA, 0); cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV | CS4281PCI_ACCTL_CRW); /* Wait for read to complete */ if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { device_printf(sc->dev, "cs4281_rdcd: DCV did not go\n"); return -1; } /* Wait for valid status */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_VSTS, 250) == 0) { device_printf(sc->dev,"cs4281_rdcd: VSTS did not come\n"); return -1; } return cs4281_rd(sc, CS4281PCI_ACSDA); } static int cs4281_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; cs4281_wr(sc, CS4281PCI_ACCAD, regno); cs4281_wr(sc, CS4281PCI_ACCDA, data); cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV); if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { device_printf(sc->dev,"cs4281_wrcd: DCV did not go\n"); } return 0; } static kobj_method_t cs4281_ac97_methods[] = { KOBJMETHOD(ac97_read, cs4281_rdcd), KOBJMETHOD(ac97_write, cs4281_wrcd), KOBJMETHOD_END }; AC97_DECLARE(cs4281_ac97); /* ------------------------------------------------------------------------- */ /* shared rec/play channel interface */ static void * cs4281chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->buffer = b; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { return NULL; } ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->bps = 1; ch->blksz = sndbuf_getsize(ch->buffer); ch->dma_chan = (dir == PCMDIR_PLAY) ? CS4281_DMA_PLAY : CS4281_DMA_REC; ch->dma_setup = 0; adcdac_go(ch, 0); adcdac_prog(ch); return ch; } static u_int32_t cs4281chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t go; go = adcdac_go(ch, 0); /* 2 interrupts are possible and used in buffer (half-empty,empty), * hence factor of 2. */ ch->blksz = MIN(blocksize, sc->bufsz / 2); sndbuf_resize(ch->buffer, 2, ch->blksz); ch->dma_setup = 0; adcdac_prog(ch); adcdac_go(ch, go); DEB(printf("cs4281chan_setblocksize: blksz %d Setting %d\n", blocksize, ch->blksz)); return ch->blksz; } static u_int32_t cs4281chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t go, v, r; go = adcdac_go(ch, 0); /* pause */ r = (ch->dma_chan == CS4281_DMA_PLAY) ? CS4281PCI_DACSR : CS4281PCI_ADCSR; v = cs4281_rate_to_rv(speed); cs4281_wr(sc, r, v); adcdac_go(ch, go); /* unpause */ ch->spd = cs4281_rv_to_rate(v); return ch->spd; } static int cs4281chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t v, go; go = adcdac_go(ch, 0); /* pause */ if (ch->dma_chan == CS4281_DMA_PLAY) v = CS4281PCI_DMR_TR_PLAY; else v = CS4281PCI_DMR_TR_REC; v |= CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO; v |= cs4281_format_to_dmr(format); cs4281_wr(sc, CS4281PCI_DMR(ch->dma_chan), v); adcdac_go(ch, go); /* unpause */ ch->fmt = format; ch->bps = cs4281_format_to_bps(format); ch->dma_setup = 0; return 0; } static u_int32_t cs4281chan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t dba, dca, ptr; int sz; sz = sndbuf_getsize(ch->buffer); dba = cs4281_rd(sc, CS4281PCI_DBA(ch->dma_chan)); dca = cs4281_rd(sc, CS4281PCI_DCA(ch->dma_chan)); ptr = (dca - dba + sz) % sz; return ptr; } static int cs4281chan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; switch(go) { case PCMTRIG_START: adcdac_prog(ch); adcdac_go(ch, 1); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: adcdac_go(ch, 0); break; default: break; } /* return 0 if ok */ return 0; } static struct pcmchan_caps * cs4281chan_getcaps(kobj_t obj, void *data) { return &cs4281_caps; } static kobj_method_t cs4281chan_methods[] = { KOBJMETHOD(channel_init, cs4281chan_init), KOBJMETHOD(channel_setformat, cs4281chan_setformat), KOBJMETHOD(channel_setspeed, cs4281chan_setspeed), KOBJMETHOD(channel_setblocksize, cs4281chan_setblocksize), KOBJMETHOD(channel_trigger, cs4281chan_trigger), KOBJMETHOD(channel_getptr, cs4281chan_getptr), KOBJMETHOD(channel_getcaps, cs4281chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(cs4281chan); /* -------------------------------------------------------------------- */ /* ADC/DAC control */ /* adcdac_go enables/disable DMA channel, returns non-zero if DMA was * active before call */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go) { struct sc_info *sc = ch->parent; u_int32_t going; going = !(cs4281_rd(sc, CS4281PCI_DCR(ch->dma_chan)) & CS4281PCI_DCR_MSK); if (go) cs4281_clr4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); else cs4281_set4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); return going; } static void adcdac_prog(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t go; if (!ch->dma_setup) { go = adcdac_go(ch, 0); cs4281_wr(sc, CS4281PCI_DBA(ch->dma_chan), sndbuf_getbufaddr(ch->buffer)); cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan), sndbuf_getsize(ch->buffer) / ch->bps - 1); ch->dma_setup = 1; adcdac_go(ch, go); } } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void cs4281_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t hisr; hisr = cs4281_rd(sc, CS4281PCI_HISR); if (hisr == 0) return; if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_PLAY)) { chn_intr(sc->pch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_PLAY)); /* Clear interrupt */ } if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_REC)) { chn_intr(sc->rch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_REC)); /* Clear interrupt */ } /* Signal End-of-Interrupt */ cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); } /* -------------------------------------------------------------------- */ /* power management related */ static int cs4281_power(struct sc_info *sc, int state) { switch (state) { case 0: /* Permit r/w access to all BA0 registers */ cs4281_wr(sc, CS4281PCI_CWPR, CS4281PCI_CWPR_MAGIC); /* Power on */ cs4281_clr4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); break; case 3: /* Power off card and codec */ cs4281_set4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); cs4281_clr4(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); break; } DEB(printf("cs4281_power %d -> %d\n", sc->power, state)); sc->power = state; return 0; } static int cs4281_init(struct sc_info *sc) { u_int32_t i, v; /* (0) Blast clock register and serial port */ cs4281_wr(sc, CS4281PCI_CLKCR1, 0); cs4281_wr(sc, CS4281PCI_SERMC, 0); /* (1) Make ESYN 0 to turn sync pulse on AC97 link */ cs4281_wr(sc, CS4281PCI_ACCTL, 0); DELAY(50); /* (2) Effect Reset */ cs4281_wr(sc, CS4281PCI_SPMC, 0); DELAY(100); cs4281_wr(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); /* Wait 50ms for ABITCLK to become stable */ DELAY(50000); /* (3) Enable Sound System Clocks */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP); DELAY(50000); /* Wait for PLL to stabilize */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP | CS4281PCI_CLKCR1_SWCE); /* (4) Power Up - this combination is essential. */ cs4281_set4(sc, CS4281PCI_SSPM, CS4281PCI_SSPM_ACLEN | CS4281PCI_SSPM_PSRCEN | CS4281PCI_SSPM_CSRCEN | CS4281PCI_SSPM_MIXEN); /* (5) Wait for clock stabilization */ if (cs4281_waitset(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLRDY, 250) == 0) { device_printf(sc->dev, "Clock stabilization failed\n"); return -1; } /* (6) Enable ASYNC generation. */ cs4281_wr(sc, CS4281PCI_ACCTL,CS4281PCI_ACCTL_ESYN); /* Wait to allow AC97 to start generating clock bit */ DELAY(50000); /* Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (7) Wait for AC97 ready signal */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_CRDY, 250) == 0) { device_printf(sc->dev, "codec did not avail\n"); return -1; } /* (8) Assert valid frame signal to begin sending commands to * AC97 codec */ cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_ESYN); /* (9) Wait for codec calibration */ for(i = 0 ; i < 1000; i++) { DELAY(10000); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if ((v & 0x0f) == 0x0f) { break; } } if (i == 1000) { device_printf(sc->dev, "codec failed to calibrate\n"); return -1; } /* (10) Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (11) Wait for valid data to arrive */ if (cs4281_waitset(sc, CS4281PCI_ACISV, CS4281PCI_ACISV_ISV(3) | CS4281PCI_ACISV_ISV(4), 10000) == 0) { device_printf(sc->dev, "cs4281 never got valid data\n"); return -1; } /* (12) Start digital data transfer of audio data to codec */ cs4281_wr(sc, CS4281PCI_ACOSV, CS4281PCI_ACOSV_SLV(3) | CS4281PCI_ACOSV_SLV(4)); /* Set Master and headphone to max */ cs4281_wrcd(0, sc, AC97_MIX_AUXOUT, 0); cs4281_wrcd(0, sc, AC97_MIX_MASTER, 0); /* Power on the DAC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfdff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until DAC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x02) break; } /* Power on the ADC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfeff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until ADC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x01) break; } /* FIFO configuration (driver is DMA orientated, implicit FIFO) */ /* Play FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(0); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v | CS4281PCI_FCR_FEN); /* Record FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_REC_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(CS4281_FIFO_SIZE + 1); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_PSH); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_FEN); /* Match AC97 slots to FIFOs */ v = CS4281PCI_SRCSA_PLSS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_SRCSA_PRSS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_SRCSA_CLSS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_SRCSA_CRSS(CS4281PCI_RPCM_REC_SLOT); cs4281_wr(sc, CS4281PCI_SRCSA, v); /* Set Auto-Initialize and set directions */ cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_PLAY), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_PLAY); cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_REC), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_REC); /* Enable half and empty buffer interrupts keeping DMA paused */ cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_PLAY), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_REC), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); /* Enable Interrupts */ cs4281_clr4(sc, CS4281PCI_HIMR, CS4281PCI_HIMR_DMAI | CS4281PCI_HIMR_DMA(CS4281_DMA_PLAY) | CS4281PCI_HIMR_DMA(CS4281_DMA_REC)); /* Set playback volume */ cs4281_wr(sc, CS4281PCI_PPLVC, 7); cs4281_wr(sc, CS4281PCI_PPRVC, 7); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int cs4281_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case CS4281_PCI_ID: s = "Crystal Semiconductor CS4281"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } static int cs4281_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); pci_enable_busmaster(dev); if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->memid = PCIR_BAR(1); sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->memid, RF_ACTIVE); if (sc->mem == NULL) { device_printf(dev, "unable to allocate fifo space\n"); goto bad; } sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, 0, cs4281_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, CS4281_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* power up */ cs4281_power(sc, 0); /* init chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, cs4281_ac97); if (codec == NULL) goto bad; mixer_init(dev, ac97_getmixerclass(), codec); if (pcm_register(dev, sc, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &cs4281chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cs4281chan_class, sc); - snprintf(status, SND_STATUSLEN, "at %s 0x%jx irq %jd %s", - (sc->regtype == SYS_RES_IOPORT)? "io" : "memory", - rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_cs4281)); + snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", + (sc->regtype == SYS_RES_IOPORT)? "port" : "mem", + rman_get_start(sc->reg), rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->mem) bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return ENXIO; } static int cs4281_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* power off */ cs4281_power(sc, 3); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return 0; } static int cs4281_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); sc->rch.dma_active = adcdac_go(&sc->rch, 0); sc->pch.dma_active = adcdac_go(&sc->pch, 0); cs4281_power(sc, 3); return 0; } static int cs4281_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* power up */ cs4281_power(sc, 0); /* initialize chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } /* restore mixer state */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restore chip state */ cs4281chan_setspeed(NULL, &sc->rch, sc->rch.spd); cs4281chan_setblocksize(NULL, &sc->rch, sc->rch.blksz); cs4281chan_setformat(NULL, &sc->rch, sc->rch.fmt); adcdac_go(&sc->rch, sc->rch.dma_active); cs4281chan_setspeed(NULL, &sc->pch, sc->pch.spd); cs4281chan_setblocksize(NULL, &sc->pch, sc->pch.blksz); cs4281chan_setformat(NULL, &sc->pch, sc->pch.fmt); adcdac_go(&sc->pch, sc->pch.dma_active); return 0; } static device_method_t cs4281_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cs4281_pci_probe), DEVMETHOD(device_attach, cs4281_pci_attach), DEVMETHOD(device_detach, cs4281_pci_detach), DEVMETHOD(device_suspend, cs4281_pci_suspend), DEVMETHOD(device_resume, cs4281_pci_resume), { 0, 0 } }; static driver_t cs4281_driver = { "pcm", cs4281_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_cs4281, pci, cs4281_driver, 0, 0); MODULE_DEPEND(snd_cs4281, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_cs4281, 1); diff --git a/sys/dev/sound/pci/csapcm.c b/sys/dev/sound/pci/csapcm.c index 8715072ec0e5..71b9a0253cdb 100644 --- a/sys/dev/sound/pci/csapcm.c +++ b/sys/dev/sound/pci/csapcm.c @@ -1,1039 +1,1040 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in * cwcealdr1.zip, the sample sources by Crystal Semiconductor. * Copyright (c) 1996-1998 Crystal Semiconductor Corp. * * 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include /* Buffer size on dma transfer. Fixed for CS416x. */ #define CS461x_BUFFSIZE (4 * 1024) #define GOF_PER_SEC 200 /* device private data */ struct csa_info; struct csa_chinfo { struct csa_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; u_int32_t fmt, spd; int dma; }; struct csa_info { csa_res res; /* resource */ void *ih; /* Interrupt cookie */ bus_dma_tag_t parent_dmat; /* DMA tag */ struct csa_bridgeinfo *binfo; /* The state of the parent. */ struct csa_card *card; int active; /* Contents of board's registers */ u_long pfie; u_long pctl; u_long cctl; struct csa_chinfo pch, rch; u_int32_t ac97[CS461x_AC97_NUMBER_RESTORE_REGS]; u_int32_t ac97_powerdown; u_int32_t ac97_general_purpose; }; /* -------------------------------------------------------------------- */ /* prototypes */ static int csa_init(struct csa_info *); static void csa_intr(void *); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate); static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate); static void csa_startplaydma(struct csa_info *csa); static void csa_startcapturedma(struct csa_info *csa); static void csa_stopplaydma(struct csa_info *csa); static void csa_stopcapturedma(struct csa_info *csa); static int csa_startdsp(csa_res *resp); static int csa_stopdsp(csa_res *resp); static int csa_allocres(struct csa_info *scp, device_t dev); static void csa_releaseres(struct csa_info *scp, device_t dev); static void csa_ac97_suspend(struct csa_info *csa); static void csa_ac97_resume(struct csa_info *csa); static u_int32_t csa_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), 0 }; static struct pcmchan_caps csa_playcaps = {8000, 48000, csa_playfmt, 0}; static u_int32_t csa_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps csa_reccaps = {11025, 48000, csa_recfmt, 0}; /* -------------------------------------------------------------------- */ static int csa_active(struct csa_info *csa, int run) { int old; old = csa->active; csa->active += run; if ((csa->active > 1) || (csa->active < -1)) csa->active = 0; if (csa->card->active) return (csa->card->active(!(csa->active && old))); return 0; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int csa_rdcd(kobj_t obj, void *devinfo, int regno) { u_int32_t data; struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data)) data = 0; csa_active(csa, -1); return data; } static int csa_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data); csa_active(csa, -1); return 0; } static kobj_method_t csa_ac97_methods[] = { KOBJMETHOD(ac97_read, csa_rdcd), KOBJMETHOD(ac97_write, csa_wrcd), KOBJMETHOD_END }; AC97_DECLARE(csa_ac97); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate) { u_long ulTemp1, ulTemp2; u_long ulPhiIncr; u_long ulCorrectionPerGOF, ulCorrectionPerSec; u_long ulOutRate; ulOutRate = 48000; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * * i.e. * * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) */ ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; /* * Fill in the SampleRateConverter control block. */ csa_writemem(resp, BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_PPI, ulPhiIncr); } static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate) { u_long ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2; u_long ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay; u_long dwFrameGroupLength, dwCnt; u_long ulInRate; ulInRate = 48000; /* * We can only decimate by up to a factor of 1/9th the hardware rate. * Return an error if an attempt is made to stray outside that limit. */ if((ulOutRate * 9) < ulInRate) return; /* * We can not capture at at rate greater than the Input Rate (48000). * Return an error if an attempt is made to stray outside that limit. */ if(ulOutRate > ulInRate) return; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulCoeffIncr = -floor((Fs,out * 2^23) / Fs,in) * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * ulInitialDelay = ceil((24 * Fs,in) / Fs,out) * * i.e. * * ulCoeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) * ulInitialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) */ ulTemp1 = ulOutRate << 16; ulCoeffIncr = ulTemp1 / ulInRate; ulTemp1 -= ulCoeffIncr * ulInRate; ulTemp1 <<= 7; ulCoeffIncr <<= 7; ulCoeffIncr += ulTemp1 / ulInRate; ulCoeffIncr ^= 0xFFFFFFFF; ulCoeffIncr++; ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate; /* * Fill in the VariDecimate control block. */ csa_writemem(resp, BA1_CSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_CCI, ulCoeffIncr); csa_writemem(resp, BA1_CD, (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); csa_writemem(resp, BA1_CPI, ulPhiIncr); /* * Figure out the frame group length for the write back task. Basically, * this is just the factors of 24000 (2^6*3*5^3) that are not present in * the output sample rate. */ dwFrameGroupLength = 1; for(dwCnt = 2; dwCnt <= 64; dwCnt *= 2) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 2; } } if(((ulOutRate / 3) * 3) != ulOutRate) { dwFrameGroupLength *= 3; } for(dwCnt = 5; dwCnt <= 125; dwCnt *= 5) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 5; } } /* * Fill in the WriteBack control block. */ csa_writemem(resp, BA1_CFG1, dwFrameGroupLength); csa_writemem(resp, BA1_CFG2, (0x00800000 | dwFrameGroupLength)); csa_writemem(resp, BA1_CCST, 0x0000FFFF); csa_writemem(resp, BA1_CSPB, ((65536 * ulOutRate) / 24000)); csa_writemem(resp, (BA1_CSPB + 4), 0x0000FFFF); } static void csa_startplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); ul &= 0x0000ffff; csa_writemem(resp, BA1_PCTL, ul | csa->pctl); csa_writemem(resp, BA1_PVOL, 0x80008000); csa->pch.dma = 1; } } static void csa_startcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); ul &= 0xffff0000; csa_writemem(resp, BA1_CCTL, ul | csa->cctl); csa_writemem(resp, BA1_CVOL, 0x80008000); csa->rch.dma = 1; } } static void csa_stopplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); csa->pctl = ul & 0xffff0000; csa_writemem(resp, BA1_PCTL, ul & 0x0000ffff); csa_writemem(resp, BA1_PVOL, 0xffffffff); csa->pch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not capturing. */ if (!csa->rch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static void csa_stopcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); csa->cctl = ul & 0x0000ffff; csa_writemem(resp, BA1_CCTL, ul & 0xffff0000); csa_writemem(resp, BA1_CVOL, 0xffffffff); csa->rch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not playing. */ if (!csa->pch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static int csa_startdsp(csa_res *resp) { int i; u_long ul; /* * Set the frame timer to reflect the number of cycles per frame. */ csa_writemem(resp, BA1_FRMT, 0xadf); /* * Turn on the run, run at frame, and DMA enable bits in the local copy of * the SP control register. */ csa_writemem(resp, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); /* * Wait until the run at frame bit resets itself in the SP control * register. */ ul = 0; for (i = 0 ; i < 25 ; i++) { /* * Wait a little bit, so we don't issue PCI reads too frequently. */ DELAY(50); /* * Fetch the current value of the SP status register. */ ul = csa_readmem(resp, BA1_SPCR); /* * If the run at frame bit has reset, then stop waiting. */ if((ul & SPCR_RUNFR) == 0) break; } /* * If the run at frame bit never reset, then return an error. */ if((ul & SPCR_RUNFR) != 0) return (EAGAIN); return (0); } static int csa_stopdsp(csa_res *resp) { /* * Turn off the run, run at frame, and DMA enable bits in * the local copy of the SP control register. */ csa_writemem(resp, BA1_SPCR, 0); return (0); } static int csa_setupchan(struct csa_chinfo *ch) { struct csa_info *csa = ch->parent; csa_res *resp = &csa->res; u_long pdtc, tmp; if (ch->dir == PCMDIR_PLAY) { /* direction */ csa_writemem(resp, BA1_PBA, sndbuf_getbufaddr(ch->buffer)); /* format */ csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f; if (!(ch->fmt & AFMT_SIGNED)) csa->pfie |= 0x8000; if (ch->fmt & AFMT_BIGENDIAN) csa->pfie |= 0x4000; if (AFMT_CHANNEL(ch->fmt) < 2) csa->pfie |= 0x2000; if (ch->fmt & AFMT_8BIT) csa->pfie |= 0x1000; csa_writemem(resp, BA1_PFIE, csa->pfie); tmp = 4; if (ch->fmt & AFMT_16BIT) tmp <<= 1; if (AFMT_CHANNEL(ch->fmt) > 1) tmp <<= 1; tmp--; pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000001ff; pdtc |= tmp; csa_writemem(resp, BA1_PDTC, pdtc); /* rate */ csa_setplaysamplerate(resp, ch->spd); } else if (ch->dir == PCMDIR_REC) { /* direction */ csa_writemem(resp, BA1_CBA, sndbuf_getbufaddr(ch->buffer)); /* format */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* rate */ csa_setcapturesamplerate(resp, ch->spd); } return 0; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * csachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct csa_info *csa = devinfo; struct csa_chinfo *ch = (dir == PCMDIR_PLAY)? &csa->pch : &csa->rch; ch->parent = csa; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, csa->parent_dmat, 0, CS461x_BUFFSIZE) != 0) return NULL; return ch; } static int csachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct csa_chinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t csachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct csa_chinfo *ch = data; ch->spd = speed; return ch->spd; /* XXX calc real speed */ } static u_int32_t csachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return CS461x_BUFFSIZE / 2; } static int csachan_trigger(kobj_t obj, void *data, int go) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { csa_active(csa, 1); csa_setupchan(ch); if (ch->dir == PCMDIR_PLAY) csa_startplaydma(csa); else csa_startcapturedma(csa); } else { if (ch->dir == PCMDIR_PLAY) csa_stopplaydma(csa); else csa_stopcapturedma(csa); csa_active(csa, -1); } return 0; } static u_int32_t csachan_getptr(kobj_t obj, void *data) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; csa_res *resp; u_int32_t ptr; resp = &csa->res; if (ch->dir == PCMDIR_PLAY) { ptr = csa_readmem(resp, BA1_PBA) - sndbuf_getbufaddr(ch->buffer); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } else { ptr = csa_readmem(resp, BA1_CBA) - sndbuf_getbufaddr(ch->buffer); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } return (ptr); } static struct pcmchan_caps * csachan_getcaps(kobj_t obj, void *data) { struct csa_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps; } static kobj_method_t csachan_methods[] = { KOBJMETHOD(channel_init, csachan_init), KOBJMETHOD(channel_setformat, csachan_setformat), KOBJMETHOD(channel_setspeed, csachan_setspeed), KOBJMETHOD(channel_setblocksize, csachan_setblocksize), KOBJMETHOD(channel_trigger, csachan_trigger), KOBJMETHOD(channel_getptr, csachan_getptr), KOBJMETHOD(channel_getcaps, csachan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(csachan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void csa_intr(void *p) { struct csa_info *csa = p; if ((csa->binfo->hisr & HISR_VC0) != 0) chn_intr(csa->pch.channel); if ((csa->binfo->hisr & HISR_VC1) != 0) chn_intr(csa->rch.channel); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int csa_init(struct csa_info *csa) { csa_res *resp; resp = &csa->res; csa->pfie = 0; csa_stopplaydma(csa); csa_stopcapturedma(csa); if (csa_startdsp(resp)) return (1); /* Crank up the power on the DAC and ADC. */ csa_setplaysamplerate(resp, 8000); csa_setcapturesamplerate(resp, 8000); /* Set defaults */ csa_writeio(resp, BA0_EGPIODR, EGPIODR_GPOE0); csa_writeio(resp, BA0_EGPIOPTR, EGPIOPTR_GPPT0); /* Power up amplifier */ csa_writeio(resp, BA0_EGPIODR, csa_readio(resp, BA0_EGPIODR) | EGPIODR_GPOE2); csa_writeio(resp, BA0_EGPIOPTR, csa_readio(resp, BA0_EGPIOPTR) | EGPIOPTR_GPPT2); return 0; } /* Allocates resources. */ static int csa_allocres(struct csa_info *csa, device_t dev) { csa_res *resp; resp = &csa->res; if (resp->io == NULL) { resp->io = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->io_rid, RF_ACTIVE); if (resp->io == NULL) return (1); } if (resp->mem == NULL) { resp->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->mem_rid, RF_ACTIVE); if (resp->mem == NULL) return (1); } if (resp->irq == NULL) { resp->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &resp->irq_rid, RF_ACTIVE | RF_SHAREABLE); if (resp->irq == NULL) return (1); } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/CS461x_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &csa->parent_dmat) != 0) return (1); return (0); } /* Releases resources. */ static void csa_releaseres(struct csa_info *csa, device_t dev) { csa_res *resp; KASSERT(csa != NULL, ("called with bogus resource structure")); resp = &csa->res; if (resp->irq != NULL) { if (csa->ih) bus_teardown_intr(dev, resp->irq, csa->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); resp->irq = NULL; } if (resp->io != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); resp->io = NULL; } if (resp->mem != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); resp->mem = NULL; } if (csa->parent_dmat != NULL) { bus_dma_tag_destroy(csa->parent_dmat); csa->parent_dmat = NULL; } free(csa, M_DEVBUF); } static int pcmcsa_probe(device_t dev) { char *s; struct sndcard_func *func; /* The parent device has already been probed. */ func = device_get_ivars(dev); if (func == NULL || func->func != SCF_PCM) return (ENXIO); s = "CS461x PCM Audio"; device_set_desc(dev, s); return (0); } static int pcmcsa_attach(device_t dev) { struct csa_info *csa; csa_res *resp; char status[SND_STATUSLEN]; struct ac97_info *codec; struct sndcard_func *func; csa = malloc(sizeof(*csa), M_DEVBUF, M_WAITOK | M_ZERO); func = device_get_ivars(dev); csa->binfo = func->varinfo; /* * Fake the status of DMA so that the initial value of * PCTL and CCTL can be stored into csa->pctl and csa->cctl, * respectively. */ csa->pch.dma = csa->rch.dma = 1; csa->active = 0; csa->card = csa->binfo->card; /* Allocate the resources. */ resp = &csa->res; resp->io_rid = PCIR_BAR(0); resp->mem_rid = PCIR_BAR(1); resp->irq_rid = 0; if (csa_allocres(csa, dev)) { csa_releaseres(csa, dev); return (ENXIO); } csa_active(csa, 1); if (csa_init(csa)) { csa_releaseres(csa, dev); return (ENXIO); } codec = AC97_CREATE(dev, csa, csa_ac97); if (codec == NULL) { csa_releaseres(csa, dev); return (ENXIO); } if (csa->card->inv_eapd) ac97_setflags(codec, AC97_F_EAPD_INV); if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } - snprintf(status, SND_STATUSLEN, "at irq %jd %s", - rman_get_start(resp->irq),PCM_KLDSTRING(snd_csa)); + snprintf(status, SND_STATUSLEN, "irq %jd on %s", + rman_get_start(resp->irq), + device_get_nameunit(device_get_parent(dev))); /* Enable interrupt. */ if (snd_setup_intr(dev, resp->irq, 0, csa_intr, csa, &csa->ih)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); csa_active(csa, -1); if (pcm_register(dev, csa, 1, 1)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } pcm_addchan(dev, PCMDIR_REC, &csachan_class, csa); pcm_addchan(dev, PCMDIR_PLAY, &csachan_class, csa); pcm_setstatus(dev, status); return (0); } static int pcmcsa_detach(device_t dev) { int r; struct csa_info *csa; r = pcm_unregister(dev); if (r) return r; csa = pcm_getdevinfo(dev); csa_releaseres(csa, dev); return 0; } static void csa_ac97_suspend(struct csa_info *csa) { int count, i; uint32_t tmp; for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_readcodec(&csa->res, BA0_AC97_RESET + count, &csa->ac97[i]); /* mute the outputs */ csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_HEADPHONE_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME_MONO, 0x8000); csa_writecodec(&csa->res, BA0_AC97_PCM_OUT_VOLUME, 0x8000); /* save the registers that cause pops */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &csa->ac97_powerdown); csa_readcodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, &csa->ac97_general_purpose); /* * And power down everything on the AC97 codec. Well, for now, * only power down the DAC/ADC and MIXER VREFON components. * trouble with removing VREF. */ /* MIXVON */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_MIXVON); /* ADC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_ADC); /* DAC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_DAC); } static void csa_ac97_resume(struct csa_info *csa) { int count, i; /* * First, we restore the state of the general purpose register. This * contains the mic select (mic1 or mic2) and if we restore this after * we restore the mic volume/boost state and mic2 was selected at * suspend time, we will end up with a brief period of time where mic1 * is selected with the volume/boost settings for mic2, causing * acoustic feedback. So we restore the general purpose register * first, thereby getting the correct mic selected before we restore * the mic volume/boost. */ csa_writecodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, csa->ac97_general_purpose); /* * Now, while the outputs are still muted, restore the state of power * on the AC97 part. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, csa->ac97_powerdown); /* * Restore just the first set of registers, from register number * 0x02 to the register number that ulHighestRegToRestore specifies. */ for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_writecodec(&csa->res, BA0_AC97_RESET + count, csa->ac97[i]); } static int pcmcsa_suspend(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* playback interrupt disable */ csa_writemem(resp, BA1_PFIE, (csa_readmem(resp, BA1_PFIE) & ~0x0000f03f) | 0x00000010); /* capture interrupt disable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000011); csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_suspend(csa); csa_resetdsp(resp); csa_stopdsp(resp); /* * Power down the DAC and ADC. For now leave the other areas on. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, 0x300); /* * Power down the PLL. */ csa_writemem(resp, BA0_CLKCR1, 0); /* * Turn off the Processor by turning off the software clock * enable flag in the clock control register. */ csa_writemem(resp, BA0_CLKCR1, csa_readmem(resp, BA0_CLKCR1) & ~CLKCR1_SWCE); csa_active(csa, -1); return 0; } static int pcmcsa_resume(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* cs_hardware_init */ csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_resume(csa); if (csa_startdsp(resp)) return (ENXIO); /* Enable interrupts on the part. */ if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); /* playback interrupt enable */ csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); /* capture interrupt enable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* cs_restart_part */ csa_setupchan(&csa->pch); csa_startplaydma(csa); csa_setupchan(&csa->rch); csa_startcapturedma(csa); csa_active(csa, -1); return 0; } static device_method_t pcmcsa_methods[] = { /* Device interface */ DEVMETHOD(device_probe , pcmcsa_probe ), DEVMETHOD(device_attach, pcmcsa_attach), DEVMETHOD(device_detach, pcmcsa_detach), DEVMETHOD(device_suspend, pcmcsa_suspend), DEVMETHOD(device_resume, pcmcsa_resume), { 0, 0 }, }; static driver_t pcmcsa_driver = { "pcm", pcmcsa_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_csapcm, csa, pcmcsa_driver, 0, 0); MODULE_DEPEND(snd_csapcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_csapcm, snd_csa, 1, 1, 1); MODULE_VERSION(snd_csapcm, 1); diff --git a/sys/dev/sound/pci/emu10k1.c b/sys/dev/sound/pci/emu10k1.c index 92937c2d0c42..0813f89c87b8 100644 --- a/sys/dev/sound/pci/emu10k1.c +++ b/sys/dev/sound/pci/emu10k1.c @@ -1,2248 +1,2248 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004 David O'Brien * Copyright (c) 2003 Orlando Bassotto * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include "mpufoi_if.h" /* -------------------------------------------------------------------- */ #define NUM_G 64 /* use all channels */ #define WAVEOUT_MAXBUFSIZE 32768 #define EMUPAGESIZE 4096 /* don't change */ #define EMUMAXPAGES (WAVEOUT_MAXBUFSIZE * NUM_G / EMUPAGESIZE) #define EMU10K1_PCI_ID 0x00021102 /* 1102 => Creative Labs Vendor ID */ #define EMU10K2_PCI_ID 0x00041102 #define EMU10K3_PCI_ID 0x00081102 #define EMU_DEFAULT_BUFSZ 4096 #define EMU_MAX_CHANS 8 #define EMU_CHANS 4 #define MAXREQVOICES 8 #define RESERVED 0 #define NUM_MIDI 16 #define NUM_FXSENDS 4 #define TMEMSIZE 256*1024 #define TMEMSIZEREG 4 #define ENABLE 0xffffffff #define DISABLE 0x00000000 #define ENV_ON EMU_CHAN_DCYSUSV_CHANNELENABLE_MASK #define ENV_OFF 0x00 /* XXX: should this be 1? */ #define EMU_A_IOCFG_GPOUT_A 0x40 #define EMU_A_IOCFG_GPOUT_D 0x04 #define EMU_A_IOCFG_GPOUT_AD (EMU_A_IOCFG_GPOUT_A|EMU_A_IOCFG_GPOUT_D) /* EMU_A_IOCFG_GPOUT0 */ #define EMU_HCFG_GPOUT1 0x00000800 /* instruction set */ #define iACC3 0x06 #define iMACINT0 0x04 #define iINTERP 0x0e #define C_00000000 0x40 #define C_00000001 0x41 #define C_00000004 0x44 #define C_40000000 0x4d /* Audigy constants */ #define A_C_00000000 0xc0 #define A_C_40000000 0xcd /* GPRs */ #define FXBUS(x) (0x00 + (x)) #define EXTIN(x) (0x10 + (x)) #define EXTOUT(x) (0x20 + (x)) #define GPR(x) (EMU_FXGPREGBASE + (x)) #define A_EXTIN(x) (0x40 + (x)) #define A_FXBUS(x) (0x00 + (x)) #define A_EXTOUT(x) (0x60 + (x)) #define A_GPR(x) (EMU_A_FXGPREGBASE + (x)) /* FX buses */ #define FXBUS_PCM_LEFT 0x00 #define FXBUS_PCM_RIGHT 0x01 #define FXBUS_MIDI_LEFT 0x04 #define FXBUS_MIDI_RIGHT 0x05 #define FXBUS_MIDI_REVERB 0x0c #define FXBUS_MIDI_CHORUS 0x0d /* Inputs */ #define EXTIN_AC97_L 0x00 #define EXTIN_AC97_R 0x01 #define EXTIN_SPDIF_CD_L 0x02 #define EXTIN_SPDIF_CD_R 0x03 #define EXTIN_TOSLINK_L 0x06 #define EXTIN_TOSLINK_R 0x07 #define EXTIN_COAX_SPDIF_L 0x0a #define EXTIN_COAX_SPDIF_R 0x0b /* Audigy Inputs */ #define A_EXTIN_AC97_L 0x00 #define A_EXTIN_AC97_R 0x01 /* Outputs */ #define EXTOUT_AC97_L 0x00 #define EXTOUT_AC97_R 0x01 #define EXTOUT_TOSLINK_L 0x02 #define EXTOUT_TOSLINK_R 0x03 #define EXTOUT_AC97_CENTER 0x04 #define EXTOUT_AC97_LFE 0x05 #define EXTOUT_HEADPHONE_L 0x06 #define EXTOUT_HEADPHONE_R 0x07 #define EXTOUT_REAR_L 0x08 #define EXTOUT_REAR_R 0x09 #define EXTOUT_ADC_CAP_L 0x0a #define EXTOUT_ADC_CAP_R 0x0b #define EXTOUT_ACENTER 0x11 #define EXTOUT_ALFE 0x12 /* Audigy Outputs */ #define A_EXTOUT_FRONT_L 0x00 #define A_EXTOUT_FRONT_R 0x01 #define A_EXTOUT_CENTER 0x02 #define A_EXTOUT_LFE 0x03 #define A_EXTOUT_HEADPHONE_L 0x04 #define A_EXTOUT_HEADPHONE_R 0x05 #define A_EXTOUT_REAR_L 0x06 #define A_EXTOUT_REAR_R 0x07 #define A_EXTOUT_AFRONT_L 0x08 #define A_EXTOUT_AFRONT_R 0x09 #define A_EXTOUT_ACENTER 0x0a #define A_EXTOUT_ALFE 0x0b #define A_EXTOUT_AREAR_L 0x0e #define A_EXTOUT_AREAR_R 0x0f #define A_EXTOUT_AC97_L 0x10 #define A_EXTOUT_AC97_R 0x11 #define A_EXTOUT_ADC_CAP_L 0x16 #define A_EXTOUT_ADC_CAP_R 0x17 struct emu_memblk { SLIST_ENTRY(emu_memblk) link; void *buf; bus_addr_t buf_addr; u_int32_t pte_start, pte_size; bus_dmamap_t buf_map; }; struct emu_mem { u_int8_t bmap[EMUMAXPAGES / 8]; u_int32_t *ptb_pages; void *silent_page; bus_addr_t silent_page_addr; bus_addr_t ptb_pages_addr; bus_dmamap_t ptb_map; bus_dmamap_t silent_map; SLIST_HEAD(, emu_memblk) blocks; }; struct emu_voice { int vnum; unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start, end, vol; int fxrt1; /* FX routing */ int fxrt2; /* FX routing (only for audigy) */ u_int32_t buf; struct emu_voice *slave; struct pcm_channel *channel; }; struct sc_info; /* channel registers */ struct sc_pchinfo { int spd, fmt, blksz, run; struct emu_voice *master, *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; struct sc_rchinfo { int spd, fmt, run, blksz, num; u_int32_t idxreg, basereg, sizereg, setupreg, irqmask; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type, rev; u_int32_t tos_link:1, APS:1, audigy:1, audigy2:1; u_int32_t addrmask; /* wider if audigy */ bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; void *ih; struct mtx *lock; unsigned int bufsz; int timer, timerinterval; int pnum, rnum; int nchans; struct emu_mem mem; struct emu_voice voice[64]; struct sc_pchinfo pch[EMU_MAX_CHANS]; struct sc_rchinfo rch[3]; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; int mputx; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int emu_init(struct sc_info *); static void emu_intr(void *); static void *emu_malloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr, bus_dmamap_t *map); static void *emu_memalloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr); static int emu_memfree(struct sc_info *sc, void *buf); static int emu_memstart(struct sc_info *sc, void *buf); #ifdef EMUDEBUG static void emu_vdump(struct sc_info *sc, struct emu_voice *v); #endif /* talk to the card */ static u_int32_t emu_rd(struct sc_info *, int, int); static void emu_wr(struct sc_info *, int, u_int32_t, int); /* -------------------------------------------------------------------- */ static u_int32_t emu_rfmt_ac97[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static u_int32_t emu_rfmt_mic[] = { SND_FORMAT(AFMT_U8, 1, 0), 0 }; static u_int32_t emu_rfmt_efx[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_reccaps[3] = { {8000, 48000, emu_rfmt_ac97, 0}, {8000, 8000, emu_rfmt_mic, 0}, {48000, 48000, emu_rfmt_efx, 0}, }; static u_int32_t emu_pfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static int adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int audigy_adcspeed[9] = { 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000 }; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t emu_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: return 0xffffffff; } } static void emu_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->st, sc->sh, regno, data); break; case 2: bus_space_write_2(sc->st, sc->sh, regno, data); break; case 4: bus_space_write_4(sc->st, sc->sh, regno, data); break; } } static u_int32_t emu_rdptr(struct sc_info *sc, int chn, int reg) { u_int32_t ptr, val, mask, size, offset; ptr = ((reg << 16) & sc->addrmask) | (chn & EMU_PTR_CHNO_MASK); emu_wr(sc, EMU_PTR, ptr, 4); val = emu_rd(sc, EMU_DATA, 4); if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; val &= mask; val >>= offset; } return val; } static void emu_wrptr(struct sc_info *sc, int chn, int reg, u_int32_t data) { u_int32_t ptr, mask, size, offset; ptr = ((reg << 16) & sc->addrmask) | (chn & EMU_PTR_CHNO_MASK); emu_wr(sc, EMU_PTR, ptr, 4); if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; data <<= offset; data &= mask; data |= emu_rd(sc, EMU_DATA, 4) & ~mask; } emu_wr(sc, EMU_DATA, data, 4); } static void emu_wrefx(struct sc_info *sc, unsigned int pc, unsigned int data) { pc += sc->audigy ? EMU_A_MICROCODEBASE : EMU_MICROCODEBASE; emu_wrptr(sc, 0, pc, data); } /* -------------------------------------------------------------------- */ /* ac97 codec */ /* no locking needed */ static int emu_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; emu_wr(sc, EMU_AC97ADDR, regno, 1); return emu_rd(sc, EMU_AC97DATA, 2); } static int emu_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; emu_wr(sc, EMU_AC97ADDR, regno, 1); emu_wr(sc, EMU_AC97DATA, data, 2); return 0; } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_ac97); /* -------------------------------------------------------------------- */ /* stuff */ static int emu_settimer(struct sc_info *sc) { struct sc_pchinfo *pch; struct sc_rchinfo *rch; int i, tmp, rate; rate = 0; for (i = 0; i < sc->nchans; i++) { pch = &sc->pch[i]; if (pch->buffer) { tmp = (pch->spd * sndbuf_getalign(pch->buffer)) / pch->blksz; if (tmp > rate) rate = tmp; } } for (i = 0; i < 3; i++) { rch = &sc->rch[i]; if (rch->buffer) { tmp = (rch->spd * sndbuf_getalign(rch->buffer)) / rch->blksz; if (tmp > rate) rate = tmp; } } RANGE(rate, 48, 9600); sc->timerinterval = 48000 / rate; emu_wr(sc, EMU_TIMER, sc->timerinterval & 0x03ff, 2); return sc->timerinterval; } static int emu_enatimer(struct sc_info *sc, int go) { u_int32_t x; if (go) { if (sc->timer++ == 0) { x = emu_rd(sc, EMU_INTE, 4); x |= EMU_INTE_INTERTIMERENB; emu_wr(sc, EMU_INTE, x, 4); } } else { sc->timer = 0; x = emu_rd(sc, EMU_INTE, 4); x &= ~EMU_INTE_INTERTIMERENB; emu_wr(sc, EMU_INTE, x, 4); } return 0; } static void emu_enastop(struct sc_info *sc, char channel, int enable) { int reg = (channel & 0x20) ? EMU_SOLEH : EMU_SOLEL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; emu_wrptr(sc, 0, reg, enable); } static int emu_recval(int speed) { int val; val = 0; while (val < 7 && speed < adcspeed[val]) val++; return val; } static int audigy_recval(int speed) { int val; val = 0; while (val < 8 && speed < audigy_adcspeed[val]) val++; return val; } static u_int32_t emu_rate_to_pitch(u_int32_t rate) { static u_int32_t logMagTable[128] = { 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df }; static char logSlopeTable[128] = { 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f }; int i; if (rate == 0) return 0; /* Bail out if no leading "1" */ rate *= 11185; /* Scale 48000 to 0x20002380 */ for (i = 31; i > 0; i--) { if (rate & 0x80000000) { /* Detect leading "1" */ return (((u_int32_t) (i - 15) << 20) + logMagTable[0x7f & (rate >> 24)] + (0x7f & (rate >> 17)) * logSlopeTable[0x7f & (rate >> 24)]); } rate <<= 1; } return 0; /* Should never reach this point */ } static u_int32_t emu_rate_to_linearpitch(u_int32_t rate) { rate = (rate << 8) / 375; return (rate >> 1) + (rate & 1); } static struct emu_voice * emu_valloc(struct sc_info *sc) { struct emu_voice *v; int i; v = NULL; for (i = 0; i < 64 && sc->voice[i].busy; i++); if (i < 64) { v = &sc->voice[i]; v->busy = 1; } return v; } static int emu_vinit(struct sc_info *sc, struct emu_voice *m, struct emu_voice *s, u_int32_t sz, struct snd_dbuf *b) { void *buf; bus_addr_t tmp_addr; buf = emu_memalloc(sc, sz, &tmp_addr); if (buf == NULL) return -1; if (b != NULL) sndbuf_setup(b, buf, sz); m->start = emu_memstart(sc, buf) * EMUPAGESIZE; m->end = m->start + sz; m->channel = NULL; m->speed = 0; m->b16 = 0; m->stereo = 0; m->running = 0; m->ismaster = 1; m->vol = 0xff; m->buf = tmp_addr; m->slave = s; if (sc->audigy) { m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 8 | FXBUS_PCM_LEFT << 16 | FXBUS_MIDI_REVERB << 24; m->fxrt2 = 0x3f3f3f3f; /* No effects on second route */ } else { m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 4 | FXBUS_PCM_LEFT << 8 | FXBUS_MIDI_REVERB << 12; m->fxrt2 = 0; } if (s != NULL) { s->start = m->start; s->end = m->end; s->channel = NULL; s->speed = 0; s->b16 = 0; s->stereo = 0; s->running = 0; s->ismaster = 0; s->vol = m->vol; s->buf = m->buf; s->fxrt1 = m->fxrt1; s->fxrt2 = m->fxrt2; s->slave = NULL; } return 0; } static void emu_vsetup(struct sc_pchinfo *ch) { struct emu_voice *v = ch->master; if (ch->fmt) { v->b16 = (ch->fmt & AFMT_16BIT) ? 1 : 0; v->stereo = (AFMT_CHANNEL(ch->fmt) > 1) ? 1 : 0; if (v->slave != NULL) { v->slave->b16 = v->b16; v->slave->stereo = v->stereo; } } if (ch->spd) { v->speed = ch->spd; if (v->slave != NULL) v->slave->speed = v->speed; } } static void emu_vwrite(struct sc_info *sc, struct emu_voice *v) { int s; int l, r, x, y; u_int32_t sa, ea, start, val, silent_page; s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0); sa = v->start >> s; ea = v->end >> s; l = r = x = y = v->vol; if (v->stereo) { l = v->ismaster ? l : 0; r = v->ismaster ? 0 : r; } emu_wrptr(sc, v->vnum, EMU_CHAN_CPF, v->stereo ? EMU_CHAN_CPF_STEREO_MASK : 0); val = v->stereo ? 28 : 30; val *= v->b16 ? 1 : 2; start = sa + val; if (sc->audigy) { emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT1, v->fxrt1); emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT2, v->fxrt2); emu_wrptr(sc, v->vnum, EMU_A_CHAN_SENDAMOUNTS, 0); } else emu_wrptr(sc, v->vnum, EMU_CHAN_FXRT, v->fxrt1 << 16); emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX, (x << 8) | r); emu_wrptr(sc, v->vnum, EMU_CHAN_DSL, ea | (y << 24)); emu_wrptr(sc, v->vnum, EMU_CHAN_PSST, sa | (l << 24)); emu_wrptr(sc, v->vnum, EMU_CHAN_CCCA, start | (v->b16 ? 0 : EMU_CHAN_CCCA_8BITSELECT)); emu_wrptr(sc, v->vnum, EMU_CHAN_Z1, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_Z2, 0); silent_page = ((u_int32_t)(sc->mem.silent_page_addr) << 1) | EMU_CHAN_MAP_PTI_MASK; emu_wrptr(sc, v->vnum, EMU_CHAN_MAPA, silent_page); emu_wrptr(sc, v->vnum, EMU_CHAN_MAPB, silent_page); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, EMU_CHAN_CVCF_CURRFILTER_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, EMU_CHAN_VTFT_FILTERTARGET_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDM, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSM, EMU_CHAN_DCYSUSM_DECAYTIME_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL1, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL2, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_FMMOD, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_TREMFRQ, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_FM2FRQ2, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVAL, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDV, EMU_CHAN_ATKHLDV_HOLDTIME_MASK | EMU_CHAN_ATKHLDV_ATTACKTIME_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVOL, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_FILTERAMOUNT, 0x7f); emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_PITCHAMOUNT, 0); if (v->slave != NULL) emu_vwrite(sc, v->slave); } static void emu_vtrigger(struct sc_info *sc, struct emu_voice *v, int go) { u_int32_t pitch_target, initial_pitch; u_int32_t cra, cs, ccis; u_int32_t sample, i; if (go) { cra = 64; cs = v->stereo ? 4 : 2; ccis = v->stereo ? 28 : 30; ccis *= v->b16 ? 1 : 2; sample = v->b16 ? 0x00000000 : 0x80808080; for (i = 0; i < cs; i++) emu_wrptr(sc, v->vnum, EMU_CHAN_CD0 + i, sample); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_READADDRESS, cra); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, ccis); emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xff00); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0xffffffff); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0xffffffff); emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSV, 0x00007f7f); emu_enastop(sc, v->vnum, 0); pitch_target = emu_rate_to_linearpitch(v->speed); initial_pitch = emu_rate_to_pitch(v->speed) >> 8; emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, pitch_target); emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, pitch_target); emu_wrptr(sc, v->vnum, EMU_CHAN_IP, initial_pitch); } else { emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xffff); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0x0000ffff); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0x0000ffff); emu_wrptr(sc, v->vnum, EMU_CHAN_IP, 0); emu_enastop(sc, v->vnum, 1); } if (v->slave != NULL) emu_vtrigger(sc, v->slave, go); } static int emu_vpos(struct sc_info *sc, struct emu_voice *v) { int s, ptr; s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0); ptr = (emu_rdptr(sc, v->vnum, EMU_CHAN_CCCA_CURRADDR) - (v->start >> s)) << s; return ptr & ~0x0000001f; } #ifdef EMUDEBUG static void emu_vdump(struct sc_info *sc, struct emu_voice *v) { char *regname[] = { "cpf", "ptrx", "cvcf", "vtft", "z2", "z1", "psst", "dsl", "ccca", "ccr", "clp", "fxrt", "mapa", "mapb", NULL, NULL, "envvol", "atkhldv", "dcysusv", "lfoval1", "envval", "atkhldm", "dcysusm", "lfoval2", "ip", "ifatn", "pefe", "fmmod", "tremfrq", "fmfrq2", "tempenv" }; char *regname2[] = { "mudata1", "mustat1", "mudata2", "mustat2", "fxwc1", "fxwc2", "spdrate", NULL, NULL, NULL, NULL, NULL, "fxrt2", "sndamnt", "fxrt1", NULL, NULL }; int i, x; printf("voice number %d\n", v->vnum); for (i = 0, x = 0; i <= 0x1e; i++) { if (regname[i] == NULL) continue; printf("%s\t[%08x]", regname[i], emu_rdptr(sc, v->vnum, i)); printf("%s", (x == 2) ? "\n" : "\t"); x++; if (x > 2) x = 0; } /* Print out audigy extra registers */ if (sc->audigy) { for (i = 0; i <= 0xe; i++) { if (regname2[i] == NULL) continue; printf("%s\t[%08x]", regname2[i], emu_rdptr(sc, v->vnum, i + 0x70)); printf("%s", (x == 2)? "\n" : "\t"); x++; if (x > 2) x = 0; } } printf("\n\n"); } #endif /* channel interface */ static void * emupchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); ch = &sc->pch[sc->pnum++]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; snd_mtxlock(sc->lock); ch->master = emu_valloc(sc); ch->slave = emu_valloc(sc); snd_mtxunlock(sc->lock); r = (emu_vinit(sc, ch->master, ch->slave, sc->bufsz, ch->buffer)) ? NULL : ch; return r; } static int emupchan_free(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_memfree(sc, sndbuf_getbuf(ch->buffer)); snd_mtxunlock(sc->lock); return r; } static int emupchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_pchinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t emupchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_pchinfo *ch = data; ch->spd = speed; return ch->spd; } static u_int32_t emupchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; ch->blksz = blocksize; snd_mtxlock(sc->lock); emu_settimer(sc); snd_mtxunlock(sc->lock); return blocksize; } static int emupchan_trigger(kobj_t obj, void *data, int go) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); if (go == PCMTRIG_START) { emu_vsetup(ch); emu_vwrite(sc, ch->master); emu_settimer(sc); emu_enatimer(sc, 1); #ifdef EMUDEBUG printf("start [%d bit, %s, %d hz]\n", ch->master->b16 ? 16 : 8, ch->master->stereo ? "stereo" : "mono", ch->master->speed); emu_vdump(sc, ch->master); emu_vdump(sc, ch->slave); #endif } ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc, ch->master, ch->run); snd_mtxunlock(sc->lock); return 0; } static u_int32_t emupchan_getptr(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_vpos(sc, ch->master); snd_mtxunlock(sc->lock); return r; } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj, void *data) { return &emu_playcaps; } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emupchan); /* channel interface */ static void * emurchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch[sc->rnum]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->num = sc->rnum; switch(sc->rnum) { case 0: ch->idxreg = sc->audigy ? EMU_A_ADCIDX : EMU_ADCIDX; ch->basereg = EMU_ADCBA; ch->sizereg = EMU_ADCBS; ch->setupreg = EMU_ADCCR; ch->irqmask = EMU_INTE_ADCBUFENABLE; break; case 1: ch->idxreg = EMU_FXIDX; ch->basereg = EMU_FXBA; ch->sizereg = EMU_FXBS; ch->setupreg = EMU_FXWC; ch->irqmask = EMU_INTE_EFXBUFENABLE; break; case 2: ch->idxreg = EMU_MICIDX; ch->basereg = EMU_MICBA; ch->sizereg = EMU_MICBS; ch->setupreg = 0; ch->irqmask = EMU_INTE_MICBUFENABLE; break; } sc->rnum++; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; else { snd_mtxlock(sc->lock); emu_wrptr(sc, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc, 0, ch->sizereg, 0); /* off */ snd_mtxunlock(sc->lock); return ch; } } static int emurchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_rchinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t emurchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_rchinfo *ch = data; if (ch->num == 0) { if (ch->parent->audigy) speed = audigy_adcspeed[audigy_recval(speed)]; else speed = adcspeed[emu_recval(speed)]; } if (ch->num == 1) speed = 48000; if (ch->num == 2) speed = 8000; ch->spd = speed; return ch->spd; } static u_int32_t emurchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; ch->blksz = blocksize; snd_mtxlock(sc->lock); emu_settimer(sc); snd_mtxunlock(sc->lock); return blocksize; } /* semantic note: must start at beginning of buffer */ static int emurchan_trigger(kobj_t obj, void *data, int go) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t val, sz; if (!PCMTRIG_COMMON(go)) return 0; switch(sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc, 0, ch->sizereg, sz); if (ch->num == 0) { if (sc->audigy) { val = EMU_A_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= EMU_A_ADCCR_RCHANENABLE; val |= audigy_recval(ch->spd); } else { val = EMU_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= EMU_ADCCR_RCHANENABLE; val |= emu_recval(ch->spd); } emu_wrptr(sc, 0, ch->setupreg, 0); emu_wrptr(sc, 0, ch->setupreg, val); } val = emu_rd(sc, EMU_INTE, 4); val |= ch->irqmask; emu_wr(sc, EMU_INTE, val, 4); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc, 0, ch->setupreg, 0); val = emu_rd(sc, EMU_INTE, 4); val &= ~ch->irqmask; emu_wr(sc, EMU_INTE, val, 4); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: default: break; } snd_mtxunlock(sc->lock); return 0; } static u_int32_t emurchan_getptr(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_rdptr(sc, 0, ch->idxreg) & 0x0000ffff; snd_mtxunlock(sc->lock); return r; } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; return &emu_reccaps[ch->num]; } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emurchan); static unsigned char emu_mread(struct mpu401 *arg, void *sc, int reg) { unsigned int d; d = emu_rd((struct sc_info *)sc, 0x18 + reg, 1); return d; } static void emu_mwrite(struct mpu401 *arg, void *sc, int reg, unsigned char b) { emu_wr((struct sc_info *)sc, 0x18 + reg, b, 1); } static int emu_muninit(struct mpu401 *arg, void *cookie) { struct sc_info *sc = cookie; snd_mtxlock(sc->lock); sc->mpu_intr = NULL; snd_mtxunlock(sc->lock); return 0; } static kobj_method_t emu_mpu_methods[] = { KOBJMETHOD(mpufoi_read, emu_mread), KOBJMETHOD(mpufoi_write, emu_mwrite), KOBJMETHOD(mpufoi_uninit, emu_muninit), KOBJMETHOD_END }; static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); static void emu_intr2(void *p) { struct sc_info *sc = (struct sc_info *)p; if (sc->mpu_intr) (sc->mpu_intr)(sc->mpu); } static void emu_midiattach(struct sc_info *sc) { int i; i = emu_rd(sc, EMU_INTE, 4); i |= EMU_INTE_MIDIRXENABLE; emu_wr(sc, EMU_INTE, i, 4); sc->mpu = mpu401_init(&emu_mpu_class, sc, emu_intr2, &sc->mpu_intr); } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void emu_intr(void *data) { struct sc_info *sc = data; u_int32_t stat, ack, i, x; snd_mtxlock(sc->lock); while (1) { stat = emu_rd(sc, EMU_IPR, 4); if (stat == 0) break; ack = 0; /* process irq */ if (stat & EMU_IPR_INTERVALTIMER) ack |= EMU_IPR_INTERVALTIMER; if (stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) ack |= stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL); if (stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) ack |= stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL); if (stat & (EMU_IPR_MICBUFFULL | EMU_IPR_MICBUFHALFFULL)) ack |= stat & (EMU_IPR_MICBUFFULL | EMU_IPR_MICBUFHALFFULL); if (stat & EMU_PCIERROR) { ack |= EMU_PCIERROR; device_printf(sc->dev, "pci error\n"); /* we still get an nmi with ecc ram even if we ack this */ } if (stat & EMU_IPR_RATETRCHANGE) { ack |= EMU_IPR_RATETRCHANGE; #ifdef EMUDEBUG device_printf(sc->dev, "sample rate tracker lock status change\n"); #endif } if (stat & EMU_IPR_MIDIRECVBUFE) { if (sc->mpu_intr) { (sc->mpu_intr)(sc->mpu); ack |= EMU_IPR_MIDIRECVBUFE | EMU_IPR_MIDITRANSBUFE; } } if (stat & ~ack) device_printf(sc->dev, "dodgy irq: %x (harmless)\n", stat & ~ack); emu_wr(sc, EMU_IPR, stat, 4); if (ack) { snd_mtxunlock(sc->lock); if (ack & EMU_IPR_INTERVALTIMER) { x = 0; for (i = 0; i < sc->nchans; i++) { if (sc->pch[i].run) { x = 1; chn_intr(sc->pch[i].channel); } } if (x == 0) emu_enatimer(sc, 0); } if (ack & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) { if (sc->rch[0].channel) chn_intr(sc->rch[0].channel); } if (ack & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) { if (sc->rch[1].channel) chn_intr(sc->rch[1].channel); } if (ack & (EMU_IPR_MICBUFFULL | EMU_IPR_MICBUFHALFFULL)) { if (sc->rch[2].channel) chn_intr(sc->rch[2].channel); } snd_mtxlock(sc->lock); } } snd_mtxunlock(sc->lock); } /* -------------------------------------------------------------------- */ static void emu_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *phys = arg; *phys = error ? 0 : (bus_addr_t)segs->ds_addr; if (bootverbose) { printf("emu: setmap (%lx, %lx), nseg=%d, error=%d\n", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, nseg, error); } } static void * emu_malloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr, bus_dmamap_t *map) { void *buf; *addr = 0; if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, map)) return NULL; if (bus_dmamap_load(sc->parent_dmat, *map, buf, sz, emu_setmap, addr, BUS_DMA_NOWAIT) || !*addr) { bus_dmamem_free(sc->parent_dmat, buf, *map); return NULL; } return buf; } static void emu_free(struct sc_info *sc, void *buf, bus_dmamap_t map) { bus_dmamap_unload(sc->parent_dmat, map); bus_dmamem_free(sc->parent_dmat, buf, map); } static void * emu_memalloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr) { u_int32_t blksz, start, idx, ofs, tmp, found; struct emu_mem *mem = &sc->mem; struct emu_memblk *blk; void *buf; blksz = sz / EMUPAGESIZE; if (sz > (blksz * EMUPAGESIZE)) blksz++; /* find a free block in the bitmap */ found = 0; start = 1; while (!found && start + blksz < EMUMAXPAGES) { found = 1; for (idx = start; idx < start + blksz; idx++) if (mem->bmap[idx >> 3] & (1 << (idx & 7))) found = 0; if (!found) start++; } if (!found) return NULL; blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT); if (blk == NULL) return NULL; buf = emu_malloc(sc, sz, &blk->buf_addr, &blk->buf_map); *addr = blk->buf_addr; if (buf == NULL) { free(blk, M_DEVBUF); return NULL; } blk->buf = buf; blk->pte_start = start; blk->pte_size = blksz; #ifdef EMUDEBUG printf("buf %p, pte_start %d, pte_size %d\n", blk->buf, blk->pte_start, blk->pte_size); #endif ofs = 0; for (idx = start; idx < start + blksz; idx++) { mem->bmap[idx >> 3] |= 1 << (idx & 7); tmp = (uint32_t)(blk->buf_addr + ofs); #ifdef EMUDEBUG printf("pte[%d] -> %x phys, %x virt\n", idx, tmp, ((u_int32_t)buf) + ofs); #endif mem->ptb_pages[idx] = (tmp << 1) | idx; ofs += EMUPAGESIZE; } SLIST_INSERT_HEAD(&mem->blocks, blk, link); return buf; } static int emu_memfree(struct sc_info *sc, void *buf) { u_int32_t idx, tmp; struct emu_mem *mem = &sc->mem; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == buf) blk = i; } if (blk == NULL) return EINVAL; SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link); emu_free(sc, buf, blk->buf_map); tmp = (u_int32_t)(sc->mem.silent_page_addr) << 1; for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) { mem->bmap[idx >> 3] &= ~(1 << (idx & 7)); mem->ptb_pages[idx] = tmp | idx; } free(blk, M_DEVBUF); return 0; } static int emu_memstart(struct sc_info *sc, void *buf) { struct emu_mem *mem = &sc->mem; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == buf) blk = i; } if (blk == NULL) return -EINVAL; return blk->pte_start; } static void emu_addefxop(struct sc_info *sc, int op, int z, int w, int x, int y, u_int32_t *pc) { emu_wrefx(sc, (*pc) * 2, (x << 10) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << 20) | (z << 10) | w); (*pc)++; } static void audigy_addefxop(struct sc_info *sc, int op, int z, int w, int x, int y, u_int32_t *pc) { emu_wrefx(sc, (*pc) * 2, (x << 12) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << 24) | (z << 12) | w); (*pc)++; } static void audigy_initefx(struct sc_info *sc) { int i; u_int32_t pc = 0; /* skip 0, 0, -1, 0 - NOPs */ for (i = 0; i < 512; i++) audigy_addefxop(sc, 0x0f, 0x0c0, 0x0c0, 0x0cf, 0x0c0, &pc); for (i = 0; i < 512; i++) emu_wrptr(sc, 0, EMU_A_FXGPREGBASE + i, 0x0); pc = 16; /* stop fx processor */ emu_wrptr(sc, 0, EMU_A_DBG, EMU_A_DBG_SINGLE_STEP); /* Audigy 2 (EMU10K2) DSP Registers: FX Bus 0x000-0x00f : 16 registers (?) Input 0x040/0x041 : AC97 Codec (l/r) 0x042/0x043 : ADC, S/PDIF (l/r) 0x044/0x045 : Optical S/PDIF in (l/r) 0x046/0x047 : ? 0x048/0x049 : Line/Mic 2 (l/r) 0x04a/0x04b : RCA S/PDIF (l/r) 0x04c/0x04d : Aux 2 (l/r) Output 0x060/0x061 : Digital Front (l/r) 0x062/0x063 : Digital Center/LFE 0x064/0x065 : AudigyDrive Heaphone (l/r) 0x066/0x067 : Digital Rear (l/r) 0x068/0x069 : Analog Front (l/r) 0x06a/0x06b : Analog Center/LFE 0x06c/0x06d : ? 0x06e/0x06f : Analog Rear (l/r) 0x070/0x071 : AC97 Output (l/r) 0x072/0x073 : ? 0x074/0x075 : ? 0x076/0x077 : ADC Recording Buffer (l/r) Constants 0x0c0 - 0x0c4 = 0 - 4 0x0c5 = 0x8, 0x0c6 = 0x10, 0x0c7 = 0x20 0x0c8 = 0x100, 0x0c9 = 0x10000, 0x0ca = 0x80000 0x0cb = 0x10000000, 0x0cc = 0x20000000, 0x0cd = 0x40000000 0x0ce = 0x80000000, 0x0cf = 0x7fffffff, 0x0d0 = 0xffffffff 0x0d1 = 0xfffffffe, 0x0d2 = 0xc0000000, 0x0d3 = 0x41fbbcdc 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (?) Temporary Values 0x0d6 : Accumulator (?) 0x0d7 : Condition Register 0x0d8 : Noise source 0x0d9 : Noise source Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff General Purpose Registers 0x400 - 0x5ff */ /* AC97Output[l/r] = FXBus PCM[l/r] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AC97_L), A_C_00000000, A_C_00000000, A_FXBUS(FXBUS_PCM_LEFT), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AC97_R), A_C_00000000, A_C_00000000, A_FXBUS(FXBUS_PCM_RIGHT), &pc); /* GPR[0/1] = RCA S/PDIF[l/r] -- Master volume */ audigy_addefxop(sc, iACC3, A_GPR(0), A_C_00000000, A_C_00000000, A_EXTIN(EXTIN_COAX_SPDIF_L), &pc); audigy_addefxop(sc, iACC3, A_GPR(1), A_C_00000000, A_C_00000000, A_EXTIN(EXTIN_COAX_SPDIF_R), &pc); /* GPR[2] = GPR[0] (Left) / 2 + GPR[1] (Right) / 2 -- Central volume */ audigy_addefxop(sc, iINTERP, A_GPR(2), A_GPR(1), A_C_40000000, A_GPR(0), &pc); /* Headphones[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_HEADPHONE_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_HEADPHONE_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Analog Front[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AFRONT_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AFRONT_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Digital Front[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_FRONT_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Center and Subwoofer configuration */ /* Analog Center = GPR[0] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ACENTER), A_C_00000000, A_GPR(0), A_GPR(2), &pc); /* Analog Sub = GPR[1] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ALFE), A_C_00000000, A_GPR(1), A_GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_CENTER), A_C_00000000, A_GPR(0), A_GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_LFE), A_C_00000000, A_GPR(1), A_GPR(2), &pc); #if 0 /* Analog Rear[l/r] = (GPR[0/1] * RearVolume[l/r]) >> 31 */ /* RearVolume = GPR[0x10/0x11] (Will this ever be implemented?) */ audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_AREAR_L), A_C_00000000, A_GPR(16), A_GPR(0), &pc); audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_AREAR_R), A_C_00000000, A_GPR(17), A_GPR(1), &pc); /* Digital Rear[l/r] = (GPR[0/1] * RearVolume[l/r]) >> 31 */ /* RearVolume = GPR[0x10/0x11] (Will this ever be implemented?) */ audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_REAR_L), A_C_00000000, A_GPR(16), A_GPR(0), &pc); audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_REAR_R), A_C_00000000, A_GPR(17), A_GPR(1), &pc); #else /* XXX This is just a copy to the channel, since we do not have * a patch manager, it is useful for have another output enabled. */ /* Analog Rear[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AREAR_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AREAR_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Digital Rear[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_REAR_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_REAR_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); #endif /* ADC Recording buffer[l/r] = AC97Input[l/r] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ADC_CAP_L), A_C_00000000, A_C_00000000, A_EXTIN(A_EXTIN_AC97_L), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ADC_CAP_R), A_C_00000000, A_C_00000000, A_EXTIN(A_EXTIN_AC97_R), &pc); /* resume normal operations */ emu_wrptr(sc, 0, EMU_A_DBG, 0); } static void emu_initefx(struct sc_info *sc) { int i; u_int32_t pc = 16; /* acc3 0,0,0,0 - NOPs */ for (i = 0; i < 512; i++) { emu_wrefx(sc, i * 2, 0x10040); emu_wrefx(sc, i * 2 + 1, 0x610040); } for (i = 0; i < 256; i++) emu_wrptr(sc, 0, EMU_FXGPREGBASE + i, 0); /* FX-8010 DSP Registers: FX Bus 0x000-0x00f : 16 registers Input 0x010/0x011 : AC97 Codec (l/r) 0x012/0x013 : ADC, S/PDIF (l/r) 0x014/0x015 : Mic(left), Zoom (l/r) 0x016/0x017 : TOS link in (l/r) 0x018/0x019 : Line/Mic 1 (l/r) 0x01a/0x01b : COAX S/PDIF (l/r) 0x01c/0x01d : Line/Mic 2 (l/r) Output 0x020/0x021 : AC97 Output (l/r) 0x022/0x023 : TOS link out (l/r) 0x024/0x025 : Center/LFE 0x026/0x027 : LiveDrive Headphone (l/r) 0x028/0x029 : Rear Channel (l/r) 0x02a/0x02b : ADC Recording Buffer (l/r) 0x02c : Mic Recording Buffer 0x031/0x032 : Analog Center/LFE Constants 0x040 - 0x044 = 0 - 4 0x045 = 0x8, 0x046 = 0x10, 0x047 = 0x20 0x048 = 0x100, 0x049 = 0x10000, 0x04a = 0x80000 0x04b = 0x10000000, 0x04c = 0x20000000, 0x04d = 0x40000000 0x04e = 0x80000000, 0x04f = 0x7fffffff, 0x050 = 0xffffffff 0x051 = 0xfffffffe, 0x052 = 0xc0000000, 0x053 = 0x41fbbcdc 0x054 = 0x5a7ef9db, 0x055 = 0x00100000 Temporary Values 0x056 : Accumulator 0x057 : Condition Register 0x058 : Noise source 0x059 : Noise source 0x05a : IRQ Register 0x05b : TRAM Delay Base Address Count General Purpose Registers 0x100 - 0x1ff Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff */ /* Routing - this will be configurable in later version */ /* GPR[0/1] = FX * 4 + SPDIF-in */ emu_addefxop(sc, iMACINT0, GPR(0), EXTIN(EXTIN_SPDIF_CD_L), FXBUS(FXBUS_PCM_LEFT), C_00000004, &pc); emu_addefxop(sc, iMACINT0, GPR(1), EXTIN(EXTIN_SPDIF_CD_R), FXBUS(FXBUS_PCM_RIGHT), C_00000004, &pc); /* GPR[0/1] += APS-input */ emu_addefxop(sc, iACC3, GPR(0), GPR(0), C_00000000, sc->APS ? EXTIN(EXTIN_TOSLINK_L) : C_00000000, &pc); emu_addefxop(sc, iACC3, GPR(1), GPR(1), C_00000000, sc->APS ? EXTIN(EXTIN_TOSLINK_R) : C_00000000, &pc); /* FrontOut (AC97) = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_R), C_00000000, C_00000001, GPR(1), &pc); /* GPR[2] = GPR[0] (Left) / 2 + GPR[1] (Right) / 2 -- Central volume */ emu_addefxop(sc, iINTERP, GPR(2), GPR(1), C_40000000, GPR(0), &pc); #if 0 /* RearOut = (GPR[0/1] * RearVolume) >> 31 */ /* RearVolume = GPR[0x10/0x11] */ emu_addefxop(sc, iMAC0, EXTOUT(EXTOUT_REAR_L), C_00000000, GPR(16), GPR(0), &pc); emu_addefxop(sc, iMAC0, EXTOUT(EXTOUT_REAR_R), C_00000000, GPR(17), GPR(1), &pc); #else /* XXX This is just a copy to the channel, since we do not have * a patch manager, it is useful for have another output enabled. */ /* Rear[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_REAR_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_REAR_R), C_00000000, C_00000000, GPR(1), &pc); #endif /* TOS out[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_TOSLINK_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_TOSLINK_R), C_00000000, C_00000000, GPR(1), &pc); /* Center and Subwoofer configuration */ /* Analog Center = GPR[0] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ACENTER), C_00000000, GPR(0), GPR(2), &pc); /* Analog Sub = GPR[1] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ALFE), C_00000000, GPR(1), GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_CENTER), C_00000000, GPR(0), GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_LFE), C_00000000, GPR(1), GPR(2), &pc); /* Headphones[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_HEADPHONE_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_HEADPHONE_R), C_00000000, C_00000000, GPR(1), &pc); /* ADC Recording buffer[l/r] = AC97Input[l/r] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ADC_CAP_L), C_00000000, C_00000000, EXTIN(EXTIN_AC97_L), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ADC_CAP_R), C_00000000, C_00000000, EXTIN(EXTIN_AC97_R), &pc); /* resume normal operations */ emu_wrptr(sc, 0, EMU_DBG, 0); } /* Probe and attach the card */ static int emu_init(struct sc_info *sc) { u_int32_t spcs, ch, tmp, i; if (sc->audigy) { /* enable additional AC97 slots */ emu_wrptr(sc, 0, EMU_AC97SLOT, EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE); } /* disable audio and lock cache */ emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4); /* reset recording buffers */ emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_MICBA, 0); emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_FXBA, 0); emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_ADCBA, 0); /* disable channel interrupt */ emu_wr(sc, EMU_INTE, EMU_INTE_INTERTIMERENB | EMU_INTE_SAMPLERATER | EMU_INTE_PCIERRENABLE, 4); emu_wrptr(sc, 0, EMU_CLIEL, 0); emu_wrptr(sc, 0, EMU_CLIEH, 0); emu_wrptr(sc, 0, EMU_SOLEL, 0); emu_wrptr(sc, 0, EMU_SOLEH, 0); /* wonder what these do... */ if (sc->audigy) { emu_wrptr(sc, 0, EMU_SPBYPASS, 0xf00); emu_wrptr(sc, 0, EMU_AC97SLOT, 0x3); } /* init envelope engine */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, ENV_OFF); emu_wrptr(sc, ch, EMU_CHAN_IP, 0); emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0); emu_wrptr(sc, ch, EMU_CHAN_CPF, 0); emu_wrptr(sc, ch, EMU_CHAN_CCR, 0); emu_wrptr(sc, ch, EMU_CHAN_PSST, 0); emu_wrptr(sc, ch, EMU_CHAN_DSL, 0x10); emu_wrptr(sc, ch, EMU_CHAN_CCCA, 0); emu_wrptr(sc, ch, EMU_CHAN_Z1, 0); emu_wrptr(sc, ch, EMU_CHAN_Z2, 0); emu_wrptr(sc, ch, EMU_CHAN_FXRT, 0xd01c0000); emu_wrptr(sc, ch, EMU_CHAN_ATKHLDM, 0); emu_wrptr(sc, ch, EMU_CHAN_DCYSUSM, 0); emu_wrptr(sc, ch, EMU_CHAN_IFATN, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_PEFE, 0); emu_wrptr(sc, ch, EMU_CHAN_FMMOD, 0); emu_wrptr(sc, ch, EMU_CHAN_TREMFRQ, 24); /* 1 Hz */ emu_wrptr(sc, ch, EMU_CHAN_FM2FRQ2, 24); /* 1 Hz */ emu_wrptr(sc, ch, EMU_CHAN_TEMPENV, 0); /*** these are last so OFF prevents writing ***/ emu_wrptr(sc, ch, EMU_CHAN_LFOVAL2, 0); emu_wrptr(sc, ch, EMU_CHAN_LFOVAL1, 0); emu_wrptr(sc, ch, EMU_CHAN_ATKHLDV, 0); emu_wrptr(sc, ch, EMU_CHAN_ENVVOL, 0); emu_wrptr(sc, ch, EMU_CHAN_ENVVAL, 0); if (sc->audigy) { /* audigy cards need this to initialize correctly */ emu_wrptr(sc, ch, 0x4c, 0); emu_wrptr(sc, ch, 0x4d, 0); emu_wrptr(sc, ch, 0x4e, 0); emu_wrptr(sc, ch, 0x4f, 0); /* set default routing */ emu_wrptr(sc, ch, EMU_A_CHAN_FXRT1, 0x03020100); emu_wrptr(sc, ch, EMU_A_CHAN_FXRT2, 0x3f3f3f3f); emu_wrptr(sc, ch, EMU_A_CHAN_SENDAMOUNTS, 0); } sc->voice[ch].vnum = ch; sc->voice[ch].slave = NULL; sc->voice[ch].busy = 0; sc->voice[ch].ismaster = 0; sc->voice[ch].running = 0; sc->voice[ch].b16 = 0; sc->voice[ch].stereo = 0; sc->voice[ch].speed = 0; sc->voice[ch].start = 0; sc->voice[ch].end = 0; sc->voice[ch].channel = NULL; } sc->pnum = sc->rnum = 0; /* * Init to 0x02109204 : * Clock accuracy = 0 (1000ppm) * Sample Rate = 2 (48kHz) * Audio Channel = 1 (Left of 2) * Source Number = 0 (Unspecified) * Generation Status = 1 (Original for Cat Code 12) * Cat Code = 12 (Digital Signal Mixer) * Mode = 0 (Mode 0) * Emphasis = 0 (None) * CP = 1 (Copyright unasserted) * AN = 0 (Audio data) * P = 0 (Consumer) */ spcs = EMU_SPCS_CLKACCY_1000PPM | EMU_SPCS_SAMPLERATE_48 | EMU_SPCS_CHANNELNUM_LEFT | EMU_SPCS_SOURCENUM_UNSPEC | EMU_SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 | EMU_SPCS_EMPHASIS_NONE | EMU_SPCS_COPYRIGHT; emu_wrptr(sc, 0, EMU_SPCS0, spcs); emu_wrptr(sc, 0, EMU_SPCS1, spcs); emu_wrptr(sc, 0, EMU_SPCS2, spcs); if (!sc->audigy) emu_initefx(sc); else if (sc->audigy2) { /* Audigy 2 */ /* from ALSA initialization code: */ /* Hack for Alice3 to work independent of haP16V driver */ u_int32_t tmp; /* Setup SRCMulti_I2S SamplingRate */ tmp = emu_rdptr(sc, 0, EMU_A_SPDIF_SAMPLERATE) & 0xfffff1ff; emu_wrptr(sc, 0, EMU_A_SPDIF_SAMPLERATE, tmp | 0x400); /* Setup SRCSel (Enable SPDIF, I2S SRCMulti) */ emu_wr(sc, 0x20, 0x00600000, 4); emu_wr(sc, 0x24, 0x00000014, 4); /* Setup SRCMulti Input Audio Enable */ emu_wr(sc, 0x20, 0x006e0000, 4); emu_wr(sc, 0x24, 0xff00ff00, 4); } SLIST_INIT(&sc->mem.blocks); sc->mem.ptb_pages = emu_malloc(sc, EMUMAXPAGES * sizeof(u_int32_t), &sc->mem.ptb_pages_addr, &sc->mem.ptb_map); if (sc->mem.ptb_pages == NULL) return -1; sc->mem.silent_page = emu_malloc(sc, EMUPAGESIZE, &sc->mem.silent_page_addr, &sc->mem.silent_map); if (sc->mem.silent_page == NULL) { emu_free(sc, sc->mem.ptb_pages, sc->mem.ptb_map); return -1; } /* Clear page with silence & setup all pointers to this page */ bzero(sc->mem.silent_page, EMUPAGESIZE); tmp = (u_int32_t)(sc->mem.silent_page_addr) << 1; for (i = 0; i < EMUMAXPAGES; i++) sc->mem.ptb_pages[i] = tmp | i; emu_wrptr(sc, 0, EMU_PTB, (sc->mem.ptb_pages_addr)); emu_wrptr(sc, 0, EMU_TCB, 0); /* taken from original driver */ emu_wrptr(sc, 0, EMU_TCBS, 0); /* taken from original driver */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_MAPA, tmp | EMU_CHAN_MAP_PTI_MASK); emu_wrptr(sc, ch, EMU_CHAN_MAPB, tmp | EMU_CHAN_MAP_PTI_MASK); } /* emu_memalloc(sc, EMUPAGESIZE); */ /* * Hokay, now enable the AUD bit * * Audigy * Enable Audio = 0 (enabled after fx processor initialization) * Mute Disable Audio = 0 * Joystick = 1 * * Audigy 2 * Enable Audio = 1 * Mute Disable Audio = 0 * Joystick = 1 * GP S/PDIF AC3 Enable = 1 * CD S/PDIF AC3 Enable = 1 * * EMU10K1 * Enable Audio = 1 * Mute Disable Audio = 0 * Lock Tank Memory = 1 * Lock Sound Memory = 0 * Auto Mute = 1 */ if (sc->audigy) { tmp = EMU_HCFG_AUTOMUTE | EMU_HCFG_JOYENABLE; if (sc->audigy2) /* Audigy 2 */ tmp = EMU_HCFG_AUDIOENABLE | EMU_HCFG_AC3ENABLE_CDSPDIF | EMU_HCFG_AC3ENABLE_GPSPDIF; emu_wr(sc, EMU_HCFG, tmp, 4); audigy_initefx(sc); /* from ALSA initialization code: */ /* enable audio and disable both audio/digital outputs */ emu_wr(sc, EMU_HCFG, emu_rd(sc, EMU_HCFG, 4) | EMU_HCFG_AUDIOENABLE, 4); emu_wr(sc, EMU_A_IOCFG, emu_rd(sc, EMU_A_IOCFG, 4) & ~EMU_A_IOCFG_GPOUT_AD, 4); if (sc->audigy2) { /* Audigy 2 */ /* Unmute Analog. * Set GPO6 to 1 for Apollo. This has to be done after * init Alice3 I2SOut beyond 48kHz. * So, sequence is important. */ emu_wr(sc, EMU_A_IOCFG, emu_rd(sc, EMU_A_IOCFG, 4) | EMU_A_IOCFG_GPOUT_A, 4); } } else { /* EMU10K1 initialization code */ tmp = EMU_HCFG_AUDIOENABLE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_AUTOMUTE; if (sc->rev >= 6) tmp |= EMU_HCFG_JOYENABLE; emu_wr(sc, EMU_HCFG, tmp, 4); /* TOSLink detection */ sc->tos_link = 0; tmp = emu_rd(sc, EMU_HCFG, 4); if (tmp & (EMU_HCFG_GPINPUT0 | EMU_HCFG_GPINPUT1)) { emu_wr(sc, EMU_HCFG, tmp | EMU_HCFG_GPOUT1, 4); DELAY(50); if (tmp != (emu_rd(sc, EMU_HCFG, 4) & ~EMU_HCFG_GPOUT1)) { sc->tos_link = 1; emu_wr(sc, EMU_HCFG, tmp, 4); } } } return 0; } static int emu_uninit(struct sc_info *sc) { u_int32_t ch; emu_wr(sc, EMU_INTE, 0, 4); for (ch = 0; ch < NUM_G; ch++) emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, ENV_OFF); for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0); emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0); emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0); emu_wrptr(sc, ch, EMU_CHAN_CPF, 0); } if (sc->audigy) { /* stop fx processor */ emu_wrptr(sc, 0, EMU_A_DBG, EMU_A_DBG_SINGLE_STEP); } /* disable audio and lock cache */ emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4); emu_wrptr(sc, 0, EMU_PTB, 0); /* reset recording buffers */ emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_MICBA, 0); emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_FXBA, 0); emu_wrptr(sc, 0, EMU_FXWC, 0); emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_ADCBA, 0); emu_wrptr(sc, 0, EMU_TCB, 0); emu_wrptr(sc, 0, EMU_TCBS, 0); /* disable channel interrupt */ emu_wrptr(sc, 0, EMU_CLIEL, 0); emu_wrptr(sc, 0, EMU_CLIEH, 0); emu_wrptr(sc, 0, EMU_SOLEL, 0); emu_wrptr(sc, 0, EMU_SOLEH, 0); /* init envelope engine */ if (!SLIST_EMPTY(&sc->mem.blocks)) device_printf(sc->dev, "warning: memblock list not empty\n"); emu_free(sc, sc->mem.ptb_pages, sc->mem.ptb_map); emu_free(sc, sc->mem.silent_page, sc->mem.silent_map); if(sc->mpu) mpu401_uninit(sc->mpu); return 0; } static int emu_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case EMU10K1_PCI_ID: s = "Creative EMU10K1"; break; case EMU10K2_PCI_ID: if (pci_get_revid(dev) == 0x04) s = "Creative Audigy 2 (EMU10K2)"; else s = "Creative Audigy (EMU10K2)"; break; case EMU10K3_PCI_ID: s = "Creative Audigy 2 (EMU10K3)"; break; default: return ENXIO; } device_set_desc(dev, s); return BUS_PROBE_LOW_PRIORITY; } static int emu_pci_attach(device_t dev) { struct ac97_info *codec = NULL; struct sc_info *sc; int i, gotmic; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10k1 softc"); sc->dev = dev; sc->type = pci_get_devid(dev); sc->rev = pci_get_revid(dev); sc->audigy = sc->type == EMU10K2_PCI_ID || sc->type == EMU10K3_PCI_ID; sc->audigy2 = (sc->audigy && sc->rev == 0x04); sc->nchans = sc->audigy ? 8 : 4; sc->addrmask = sc->audigy ? EMU_A_PTR_ADDR_MASK : EMU_PTR_ADDR_MASK; pci_enable_busmaster(dev); i = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE); if (sc->reg == NULL) { device_printf(dev, "unable to map register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, 4096, EMU_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/(1U << 31) - 1, /* can only access 0-2gb */ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (emu_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, emu_ac97); if (codec == NULL) goto bad; gotmic = (ac97_getcaps(codec) & AC97_CAP_MICCHANNEL) ? 1 : 0; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; emu_midiattach(sc); i = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, emu_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(sc->reg), rman_get_start(sc->irq), - PCM_KLDSTRING(snd_emu10k1)); + device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, sc, sc->nchans, gotmic ? 3 : 2)) goto bad; for (i = 0; i < sc->nchans; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); for (i = 0; i < (gotmic ? 3 : 2); i++) pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return ENXIO; } static int emu_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* shutdown chip */ emu_uninit(sc); bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } /* add suspend, resume */ static device_method_t emu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emu_pci_probe), DEVMETHOD(device_attach, emu_pci_attach), DEVMETHOD(device_detach, emu_pci_detach), DEVMETHOD_END }; static driver_t emu_driver = { "pcm", emu_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_emu10k1, pci, emu_driver, NULL, NULL); MODULE_DEPEND(snd_emu10k1, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10k1, 1); MODULE_DEPEND(snd_emu10k1, midi, 1, 1, 1); /* dummy driver to silence the joystick device */ static int emujoy_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case 0x70021102: s = "Creative EMU10K1 Joystick"; device_quiet(dev); break; case 0x70031102: s = "Creative EMU10K2 Joystick"; device_quiet(dev); break; } if (s) device_set_desc(dev, s); return s ? -1000 : ENXIO; } static int emujoy_pci_attach(device_t dev) { return 0; } static int emujoy_pci_detach(device_t dev) { return 0; } static device_method_t emujoy_methods[] = { DEVMETHOD(device_probe, emujoy_pci_probe), DEVMETHOD(device_attach, emujoy_pci_attach), DEVMETHOD(device_detach, emujoy_pci_detach), DEVMETHOD_END }; static driver_t emujoy_driver = { "emujoy", emujoy_methods, 1 /* no softc */ }; DRIVER_MODULE(emujoy, pci, emujoy_driver, NULL, NULL); diff --git a/sys/dev/sound/pci/emu10kx-pcm.c b/sys/dev/sound/pci/emu10kx-pcm.c index 661d65165190..b4633efdddc7 100644 --- a/sys/dev/sound/pci/emu10kx-pcm.c +++ b/sys/dev/sound/pci/emu10kx-pcm.c @@ -1,1534 +1,1535 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * Copyright (c) 2003-2007 Yuriy Tsibizov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN 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 #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include "mixer_if.h" #include #include struct emu_pcm_pchinfo { int spd; int fmt; unsigned int blksz; int run; struct emu_voice *master; struct emu_voice *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; struct emu_pcm_rchinfo { int spd; int fmt; unsigned int blksz; int run; uint32_t idxreg; uint32_t basereg; uint32_t sizereg; uint32_t setupreg; uint32_t irqmask; uint32_t iprmask; int ihandle; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; /* XXX Hardware playback channels */ #define MAX_CHANNELS 4 #if MAX_CHANNELS > 13 #error Too many hardware channels defined. 13 is the maximum #endif struct emu_pcm_info { struct mtx *lock; device_t dev; /* device information */ struct emu_sc_info *card; struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */ int pnum; /* next free channel number */ struct emu_pcm_rchinfo rch_adc; struct emu_pcm_rchinfo rch_efx; struct emu_route rt; struct emu_route rt_mono; int route; int ihandle; /* interrupt handler */ unsigned int bufsz; int is_emu10k1; struct ac97_info *codec; uint32_t ac97_state[0x7F]; kobj_class_t ac97_mixerclass; uint32_t ac97_recdevs; uint32_t ac97_playdevs; struct snd_mixer *sm; int mch_disabled; unsigned int emu10k1_volcache[2][2]; }; static uint32_t emu_rfmt_adc[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_reccaps_adc = { 8000, 48000, emu_rfmt_adc, 0 }; static uint32_t emu_rfmt_efx[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), 0 }; static struct pcmchan_caps emu_reccaps_efx_live = { 48000*32, 48000*32, emu_rfmt_efx, 0 }; static struct pcmchan_caps emu_reccaps_efx_audigy = { 48000*64, 48000*64, emu_rfmt_efx, 0 }; static int emu_rates_live[] = { 48000*32 }; static int emu_rates_audigy[] = { 48000*64 }; static uint32_t emu_pfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static uint32_t emu_pfmt_mono[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0}; static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000}; static uint32_t emu_pcm_intr(void *pcm, uint32_t stat); static const struct emu_dspmix_props_k1 { uint8_t present; uint8_t recdev; int8_t input; } dspmix_k1 [SOUND_MIXER_NRDEVICES] = { /* no mixer device for ac97 */ /* in0 AC97 */ [SOUND_MIXER_DIGITAL1] = {1, 1, 1}, /* in1 CD SPDIF */ /* not connected */ /* in2 (zoom) */ [SOUND_MIXER_DIGITAL2] = {1, 1, 3}, /* in3 toslink */ [SOUND_MIXER_LINE2] = {1, 1, 4}, /* in4 Line-In2 */ [SOUND_MIXER_DIGITAL3] = {1, 1, 5}, /* in5 on-card SPDIF */ [SOUND_MIXER_LINE3] = {1, 1, 6}, /* in6 AUX2 */ /* not connected */ /* in7 */ }; static const struct emu_dspmix_props_k2 { uint8_t present; uint8_t recdev; int8_t input; } dspmix_k2 [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = {1, 0, (-1)}, [SOUND_MIXER_PCM] = {1, 0, (-1)}, /* no mixer device */ /* in0 AC97 */ [SOUND_MIXER_DIGITAL1] = {1, 1, 1}, /* in1 CD SPDIF */ [SOUND_MIXER_DIGITAL2] = {1, 1, 2}, /* in2 COAX SPDIF */ /* not connected */ /* in3 */ [SOUND_MIXER_LINE2] = {1, 1, 4}, /* in4 Line-In2 */ [SOUND_MIXER_DIGITAL3] = {1, 1, 5}, /* in5 on-card SPDIF */ [SOUND_MIXER_LINE3] = {1, 1, 6}, /* in6 AUX2 */ /* not connected */ /* in7 */ }; static int emu_dspmixer_init(struct snd_mixer *m) { struct emu_pcm_info *sc; int i; int p, r; p = 0; r = 0; sc = mix_getdevinfo(m); if (sc->route == RT_FRONT) { /* create submixer for AC97 codec */ if ((sc->ac97_mixerclass != NULL) && (sc->codec != NULL)) { sc->sm = mixer_create(sc->dev, sc->ac97_mixerclass, sc->codec, "ac97"); if (sc->sm != NULL) { p = mix_getdevs(sc->sm); r = mix_getrecdevs(sc->sm); } } sc->ac97_playdevs = p; sc->ac97_recdevs = r; } /* This two are always here */ p |= (1 << SOUND_MIXER_PCM); p |= (1 << SOUND_MIXER_VOLUME); if (sc->route == RT_FRONT) { if (sc->is_emu10k1) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k1[i].present) p |= (1 << i); if (dspmix_k1[i].recdev) r |= (1 << i); } } else { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k2[i].present) p |= (1 << i); if (dspmix_k2[i].recdev) r |= (1 << i); } } } mix_setdevs(m, p); mix_setrecdevs(m, r); return (0); } static int emu_dspmixer_uninit(struct snd_mixer *m) { struct emu_pcm_info *sc; int err = 0; /* drop submixer for AC97 codec */ sc = mix_getdevinfo(m); if (sc->sm != NULL) { err = mixer_delete(sc->sm); if (err) return (err); sc->sm = NULL; } return (0); } static int emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct emu_pcm_info *sc; sc = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_VOLUME: switch (sc->route) { case RT_FRONT: if (sc->sm != NULL) mix_set(sc->sm, dev, left, right); if (sc->mch_disabled) { /* In emu10k1 case PCM volume does not affect sound routed to rear & center/sub (it is connected to AC97 codec). Calculate it manually. */ /* This really should belong to emu10kx.c */ if (sc->is_emu10k1) { sc->emu10k1_volcache[0][0] = left; left = left * sc->emu10k1_volcache[1][0] / 100; sc->emu10k1_volcache[0][1] = right; right = right * sc->emu10k1_volcache[1][1] / 100; } emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); /* XXX side */ } } /* mch disabled */ break; case RT_REAR: emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_PCM: switch (sc->route) { case RT_FRONT: if (sc->sm != NULL) mix_set(sc->sm, dev, left, right); if (sc->mch_disabled) { /* See SOUND_MIXER_VOLUME case */ if (sc->is_emu10k1) { sc->emu10k1_volcache[1][0] = left; left = left * sc->emu10k1_volcache[0][0] / 100; sc->emu10k1_volcache[1][1] = right; right = right * sc->emu10k1_volcache[0][1] / 100; } emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); /* XXX side */ } } /* mch_disabled */ break; case RT_REAR: emumix_set_volume(sc->card, M_FX2_REAR_L, left); emumix_set_volume(sc->card, M_FX3_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_DIGITAL1: /* CD SPDIF, in1 */ emumix_set_volume(sc->card, M_IN1_FRONT_L, left); emumix_set_volume(sc->card, M_IN1_FRONT_R, right); break; case SOUND_MIXER_DIGITAL2: if (sc->is_emu10k1) { /* TOSLink, in3 */ emumix_set_volume(sc->card, M_IN3_FRONT_L, left); emumix_set_volume(sc->card, M_IN3_FRONT_R, right); } else { /* COAX SPDIF, in2 */ emumix_set_volume(sc->card, M_IN2_FRONT_L, left); emumix_set_volume(sc->card, M_IN2_FRONT_R, right); } break; case SOUND_MIXER_LINE2: /* Line-In2, in4 */ emumix_set_volume(sc->card, M_IN4_FRONT_L, left); emumix_set_volume(sc->card, M_IN4_FRONT_R, right); break; case SOUND_MIXER_DIGITAL3: /* on-card SPDIF, in5 */ emumix_set_volume(sc->card, M_IN5_FRONT_L, left); emumix_set_volume(sc->card, M_IN5_FRONT_R, right); break; case SOUND_MIXER_LINE3: /* AUX2, in6 */ emumix_set_volume(sc->card, M_IN6_FRONT_L, left); emumix_set_volume(sc->card, M_IN6_FRONT_R, right); break; default: if (sc->sm != NULL) { /* XXX emumix_set_volume is not required here */ emumix_set_volume(sc->card, M_IN0_FRONT_L, 100); emumix_set_volume(sc->card, M_IN0_FRONT_R, 100); mix_set(sc->sm, dev, left, right); } else device_printf(sc->dev, "mixer error: unknown device %d\n", dev); } return (0); } static u_int32_t emu_dspmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct emu_pcm_info *sc; int i; u_int32_t recmask; int input[8]; sc = mix_getdevinfo(m); recmask = 0; for (i=0; i < 8; i++) input[i]=0; if (sc->sm != NULL) if ((src & sc->ac97_recdevs) !=0) if (mix_setrecsrc(sc->sm, src & sc->ac97_recdevs) == 0) { recmask |= (src & sc->ac97_recdevs); /* Recording from AC97 codec. Enable AC97 route to rec on DSP */ input[0] = 1; } if (sc->is_emu10k1) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k1[i].recdev) if ((src & (1 << i)) == ((uint32_t)1 << i)) { recmask |= (1 << i); /* enable device i */ input[dspmix_k1[i].input] = 1; } } } else { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k2[i].recdev) if ((src & (1 << i)) == ((uint32_t)1 << i)) { recmask |= (1 << i); /* enable device i */ input[dspmix_k2[i].input] = 1; } } } emumix_set_volume(sc->card, M_IN0_REC_L, input[0] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN0_REC_R, input[0] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN1_REC_L, input[1] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN1_REC_R, input[1] == 1 ? 100 : 0); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_IN2_REC_L, input[2] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN2_REC_R, input[2] == 1 ? 100 : 0); } if (sc->is_emu10k1) { emumix_set_volume(sc->card, M_IN3_REC_L, input[3] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN3_REC_R, input[3] == 1 ? 100 : 0); } emumix_set_volume(sc->card, M_IN4_REC_L, input[4] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN4_REC_R, input[4] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN5_REC_L, input[5] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN5_REC_R, input[5] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN6_REC_L, input[6] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN6_REC_R, input[6] == 1 ? 100 : 0); /* XXX check for K1/k2 differences? */ if ((src & (1 << SOUND_MIXER_PCM)) == (1 << SOUND_MIXER_PCM)) { emumix_set_volume(sc->card, M_FX0_REC_L, emumix_get_volume(sc->card, M_FX0_FRONT_L)); emumix_set_volume(sc->card, M_FX1_REC_R, emumix_get_volume(sc->card, M_FX1_FRONT_R)); } else { emumix_set_volume(sc->card, M_FX0_REC_L, 0); emumix_set_volume(sc->card, M_FX1_REC_R, 0); } return (recmask); } static kobj_method_t emudspmixer_methods[] = { KOBJMETHOD(mixer_init, emu_dspmixer_init), KOBJMETHOD(mixer_uninit, emu_dspmixer_uninit), KOBJMETHOD(mixer_set, emu_dspmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(emudspmixer); static int emu_efxmixer_init(struct snd_mixer *m) { mix_setdevs(m, SOUND_MASK_VOLUME); mix_setrecdevs(m, SOUND_MASK_MONITOR); return (0); } static int emu_efxmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { if (left + right == 200) return (0); return (0); } static u_int32_t emu_efxmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused) { return (SOUND_MASK_MONITOR); } static kobj_method_t emuefxmixer_methods[] = { KOBJMETHOD(mixer_init, emu_efxmixer_init), KOBJMETHOD(mixer_set, emu_efxmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_efxmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(emuefxmixer); /* * AC97 emulation code for Audigy and later cards. * Some parts of AC97 codec are not used by hardware, but can be used * to change some DSP controls via AC97 mixer interface. This includes: * - master volume controls MASTER_FRONT_[R|L] * - pcm volume controls FX[0|1]_FRONT_[R|L] * - rec volume controls MASTER_REC_[R|L] * We do it because we need to put it under user control.... * We also keep some parts of AC97 disabled to get better sound quality */ #define AC97LEFT(x) ((x & 0x7F00)>>8) #define AC97RIGHT(x) (x & 0x007F) #define AC97MUTE(x) ((x & 0x8000)>>15) #define BIT4_TO100(x) (100-(x)*100/(0x0f)) #define BIT6_TO100(x) (100-(x)*100/(0x3f)) #define BIT4_TO255(x) (255-(x)*255/(0x0f)) #define BIT6_TO255(x) (255-(x)*255/(0x3f)) #define V100_TOBIT6(x) (0x3f*(100-x)/100) #define V100_TOBIT4(x) (0x0f*(100-x)/100) #define AC97ENCODE(x_muted, x_left, x_right) (((x_muted & 1)<<15) | ((x_left & 0x3f)<<8) | (x_right & 0x3f)) static int emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno) { int use_ac97; int emulated; int tmp; use_ac97 = 1; emulated = 0; switch (regno) { case AC97_MIX_MASTER: emulated = sc->ac97_state[AC97_MIX_MASTER]; use_ac97 = 0; break; case AC97_MIX_PCM: emulated = sc->ac97_state[AC97_MIX_PCM]; use_ac97 = 0; break; case AC97_REG_RECSEL: emulated = 0x0505; use_ac97 = 0; break; case AC97_MIX_RGAIN: emulated = sc->ac97_state[AC97_MIX_RGAIN]; use_ac97 = 0; break; } emu_wr(sc->card, EMU_AC97ADDR, regno, 1); tmp = emu_rd(sc->card, EMU_AC97DATA, 2); if (use_ac97) emulated = tmp; return (emulated); } static void emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data) { int write_ac97; int left, right; uint32_t emu_left, emu_right; int is_mute; write_ac97 = 1; left = AC97LEFT(data); emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */ right = AC97RIGHT(data); emu_right = BIT6_TO100(right); is_mute = AC97MUTE(data); if (is_mute) emu_left = emu_right = 0; switch (regno) { /* TODO: reset emulator on AC97_RESET */ case AC97_MIX_MASTER: emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left); emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 main out */ break; case AC97_MIX_PCM: /* PCM OUT VOL */ emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left); emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 PCM out */ break; case AC97_REG_RECSEL: /* * PCM recording source is set to "stereo mix" (labeled "vol" * in mixer). There is no 'playback' from AC97 codec - * if you want to hear anything from AC97 you have to _record_ * it. Keep things simple and record "stereo mix". */ data = 0x0505; break; case AC97_MIX_RGAIN: /* RECORD GAIN */ emu_left = BIT4_TO100(left); /* rgain is 4-bit */ emu_right = BIT4_TO100(right); emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left); emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right); /* * Record gain on AC97 should stay zero to get AC97 sound on * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not * directly connected to any output, only to EMU10K2 chip Use * this control to set AC97 mix volume inside EMU10K2 chip */ sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f); data = 0x0000; break; } if (write_ac97) { emu_wr(sc->card, EMU_AC97ADDR, regno, 1); emu_wr(sc->card, EMU_AC97DATA, data, 2); } } static int emu_erdcd(kobj_t obj __unused, void *devinfo, int regno) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; return (emu_ac97_read_emulation(sc, regno)); } static int emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; emu_ac97_write_emulation(sc, regno, data); return (0); } static kobj_method_t emu_eac97_methods[] = { KOBJMETHOD(ac97_read, emu_erdcd), KOBJMETHOD(ac97_write, emu_ewrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_eac97); /* real ac97 codec */ static int emu_rdcd(kobj_t obj __unused, void *devinfo, int regno) { int rd; struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard")); emu_wr(sc->card, EMU_AC97ADDR, regno, 1); rd = emu_rd(sc->card, EMU_AC97DATA, 2); return (rd); } static int emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard")); emu_wr(sc->card, EMU_AC97ADDR, regno, 1); emu_wr(sc->card, EMU_AC97DATA, data, 2); return (0); } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_ac97); static int emu_k1_recval(int speed) { int val; val = 0; while ((val < 7) && (speed < emu10k1_adcspeed[val])) val++; return (val); } static int emu_k2_recval(int speed) { int val; val = 0; while ((val < 8) && (speed < emu10k2_adcspeed[val])) val++; return (val); } static void * emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); KASSERT(sc->card != NULL, ("empchan_init: no soundcard")); if (sc->pnum >= MAX_CHANNELS) return (NULL); ch = &(sc->pch[sc->pnum++]); ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->master = emu_valloc(sc->card); /* * XXX we have to allocate slave even for mono channel until we * fix emu_vfree to handle this case. */ ch->slave = emu_valloc(sc->card); ch->timer = emu_timer_create(sc->card); r = (emu_vinit(sc->card, ch->master, ch->slave, EMU_PLAY_BUFSZ, ch->buffer)) ? NULL : ch; return (r); } static int emupchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); if (ch->slave != NULL) emu_vfree(sc->card, ch->slave); emu_vfree(sc->card, ch->master); return (0); } static int emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static uint32_t emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->spd = speed; return (ch->spd); } static uint32_t emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (blocksize > ch->pcm->bufsz) blocksize = ch->pcm->bufsz; snd_mtxlock(sc->lock); ch->blksz = blocksize; emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); snd_mtxunlock(sc->lock); return (ch->blksz); } static int emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */ if (go == PCMTRIG_START) { emu_vsetup(ch->master, ch->fmt, ch->spd); if (AFMT_CHANNEL(ch->fmt) > 1) emu_vroute(sc->card, &(sc->rt), ch->master); else emu_vroute(sc->card, &(sc->rt_mono), ch->master); emu_vwrite(sc->card, ch->master); emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); emu_timer_enable(sc->card, ch->timer, 1); } /* PCM interrupt handler will handle PCMTRIG_STOP event */ ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc->card, ch->master, ch->run); snd_mtxunlock(sc->lock); return (0); } static uint32_t emupchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_vpos(sc->card, ch->master); return (r); } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; switch (sc->route) { case RT_FRONT: /* FALLTHROUGH */ case RT_REAR: /* FALLTHROUGH */ case RT_SIDE: return (&emu_playcaps); break; case RT_CENTER: /* FALLTHROUGH */ case RT_SUB: return (&emu_playcaps_mono); break; } return (NULL); } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emupchan); static void * emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch_adc; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; /* We rise interrupt for half-full buffer */ ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->idxreg = sc->is_emu10k1 ? EMU_ADCIDX : EMU_A_ADCIDX; ch->basereg = EMU_ADCBA; ch->sizereg = EMU_ADCBS; ch->setupreg = EMU_ADCCR; ch->irqmask = EMU_INTE_ADCBUFENABLE; ch->iprmask = EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { ch->timer = emu_timer_create(sc->card); emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emurchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); return (0); } static int emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static uint32_t emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; if (ch->pcm->is_emu10k1) { speed = emu10k1_adcspeed[emu_k1_recval(speed)]; } else { speed = emu10k2_adcspeed[emu_k2_recval(speed)]; } ch->spd = speed; return (ch->spd); } static uint32_t emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; ch->blksz = blocksize; /* * If blocksize is less than half of buffer size we will not get * BUFHALFFULL interrupt in time and channel will need to generate * (and use) timer interrupts. Otherwise channel will be marked dead. */ if (ch->blksz < (ch->pcm->bufsz / 2)) { emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); emu_timer_enable(sc->card, ch->timer, 1); } else { emu_timer_enable(sc->card, ch->timer, 0); } return (ch->blksz); } static int emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t val, sz; if (!PCMTRIG_COMMON(go)) return (0); switch (sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); val = sc->is_emu10k1 ? EMU_ADCCR_LCHANENABLE : EMU_A_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= sc->is_emu10k1 ? EMU_ADCCR_RCHANENABLE : EMU_A_ADCCR_RCHANENABLE; val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd); emu_wrptr(sc->card, 0, ch->setupreg, 0); emu_wrptr(sc->card, 0, ch->setupreg, val); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc->card, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc->card, 0, ch->setupreg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t emurchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { return (&emu_reccaps_adc); } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_free, emurchan_free), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emurchan); static void * emufxrchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); if (sc == NULL) return (NULL); ch = &(sc->rch_efx); ch->fmt = SND_FORMAT(AFMT_S16_LE, 1, 0); ch->spd = sc->is_emu10k1 ? 48000*32 : 48000 * 64; ch->idxreg = EMU_FXIDX; ch->basereg = EMU_FXBA; ch->sizereg = EMU_FXBS; ch->irqmask = EMU_INTE_EFXBUFENABLE; ch->iprmask = EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emufxrchan_setformat(kobj_t obj __unused, void *c_devinfo __unused, uint32_t format) { if (format == SND_FORMAT(AFMT_S16_LE, 1, 0)) return (0); return (EINVAL); } static uint32_t emufxrchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; /* FIXED RATE CHANNEL */ return (ch->spd); } static uint32_t emufxrchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->blksz = blocksize; /* * XXX If blocksize is less than half of buffer size we will not get * interrupt in time and channel will die due to interrupt timeout. * This should not happen with FX rchan, because it will fill buffer * very fast (64K buffer is 0.021seconds on Audigy). */ if (ch->blksz < (ch->pcm->bufsz / 2)) ch->blksz = ch->pcm->bufsz / 2; return (ch->blksz); } static int emufxrchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t sz; if (!PCMTRIG_COMMON(go)) return (0); switch (sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); /* * SB Live! is limited to 32 mono channels. Audigy * has 64 mono channels. Channels are enabled * by setting a bit in EMU_A_FXWC[1|2] registers. */ /* XXX there is no way to demultiplex this streams for now */ if (sc->is_emu10k1) { emu_wrptr(sc->card, 0, EMU_FXWC, 0xffffffff); } else { emu_wrptr(sc->card, 0, EMU_A_FXWC1, 0xffffffff); emu_wrptr(sc->card, 0, EMU_A_FXWC2, 0xffffffff); } break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; if (sc->is_emu10k1) { emu_wrptr(sc->card, 0, EMU_FXWC, 0x0); } else { emu_wrptr(sc->card, 0, EMU_A_FXWC1, 0x0); emu_wrptr(sc->card, 0, EMU_A_FXWC2, 0x0); } emu_wrptr(sc->card, 0, ch->sizereg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t emufxrchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emufxrchan_getcaps(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (sc->is_emu10k1) return (&emu_reccaps_efx_live); return (&emu_reccaps_efx_audigy); } static int emufxrchan_getrates(kobj_t obj __unused, void *c_devinfo, int **rates) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (sc->is_emu10k1) *rates = emu_rates_live; else *rates = emu_rates_audigy; return 1; } static kobj_method_t emufxrchan_methods[] = { KOBJMETHOD(channel_init, emufxrchan_init), KOBJMETHOD(channel_setformat, emufxrchan_setformat), KOBJMETHOD(channel_setspeed, emufxrchan_setspeed), KOBJMETHOD(channel_setblocksize, emufxrchan_setblocksize), KOBJMETHOD(channel_trigger, emufxrchan_trigger), KOBJMETHOD(channel_getptr, emufxrchan_getptr), KOBJMETHOD(channel_getcaps, emufxrchan_getcaps), KOBJMETHOD(channel_getrates, emufxrchan_getrates), KOBJMETHOD_END }; CHANNEL_DECLARE(emufxrchan); static uint32_t emu_pcm_intr(void *pcm, uint32_t stat) { struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm; uint32_t ack; int i; ack = 0; snd_mtxlock(sc->lock); if (stat & EMU_IPR_INTERVALTIMER) { ack |= EMU_IPR_INTERVALTIMER; for (i = 0; i < MAX_CHANNELS; i++) if (sc->pch[i].channel) { if (sc->pch[i].run == 1) { snd_mtxunlock(sc->lock); chn_intr(sc->pch[i].channel); snd_mtxlock(sc->lock); } else emu_timer_enable(sc->card, sc->pch[i].timer, 0); } /* ADC may install timer to get low-latency interrupts */ if ((sc->rch_adc.channel) && (sc->rch_adc.run)) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_adc.channel); snd_mtxlock(sc->lock); } /* * EFX does not use timer, because it will fill * buffer at least 32x times faster than ADC. */ } if (stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) { ack |= stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL); if (sc->rch_adc.channel) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_adc.channel); snd_mtxlock(sc->lock); } } if (stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) { ack |= stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL); if (sc->rch_efx.channel) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_efx.channel); snd_mtxlock(sc->lock); } } snd_mtxunlock(sc->lock); return (ack); } static int emu_pcm_init(struct emu_pcm_info *sc) { sc->bufsz = pcm_getbuffersize(sc->dev, EMUPAGESIZE, EMU_REC_BUFSZ, EMU_MAX_BUFSZ); return (0); } static int emu_pcm_uninit(struct emu_pcm_info *sc __unused) { return (0); } static int emu_pcm_probe(device_t dev) { uintptr_t func, route; const char *rt; char buffer[255]; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func); if (func != SCF_PCM) return (ENXIO); rt = "UNKNOWN"; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); switch (route) { case RT_FRONT: rt = "front"; break; case RT_REAR: rt = "rear"; break; case RT_CENTER: rt = "center"; break; case RT_SUB: rt = "subwoofer"; break; case RT_SIDE: rt = "side"; break; case RT_MCHRECORD: rt = "multichannel recording"; break; } snprintf(buffer, 255, "EMU10Kx DSP %s PCM interface", rt); device_set_desc_copy(dev, buffer); return (0); } static int emu_pcm_attach(device_t dev) { struct emu_pcm_info *sc; unsigned int i; char status[SND_STATUSLEN]; uint32_t inte, ipr; uintptr_t route, ivar; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev))); if (sc->card == NULL) { device_printf(dev, "cannot get bridge conf\n"); free(sc, M_DEVBUF); return (ENXIO); } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10kx pcm softc"); sc->dev = dev; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &ivar); sc->is_emu10k1 = ivar ? 1 : 0; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_MCH_DISABLED, &ivar); sc->mch_disabled = ivar ? 1 : 0; sc->codec = NULL; for (i = 0; i < 8; i++) { sc->rt.routing_left[i] = i; sc->rt.amounts_left[i] = 0x00; sc->rt.routing_right[i] = i; sc->rt.amounts_right[i] = 0x00; } for (i = 0; i < 8; i++) { sc->rt_mono.routing_left[i] = i; sc->rt_mono.amounts_left[i] = 0x00; sc->rt_mono.routing_right[i] = i; sc->rt_mono.amounts_right[i] = 0x00; } sc->emu10k1_volcache[0][0] = 75; sc->emu10k1_volcache[1][0] = 75; sc->emu10k1_volcache[0][1] = 75; sc->emu10k1_volcache[1][1] = 75; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); sc->route = route; switch (route) { case RT_FRONT: sc->rt.amounts_left[0] = 0xff; sc->rt.amounts_right[1] = 0xff; sc->rt_mono.amounts_left[0] = 0xff; sc->rt_mono.amounts_left[1] = 0xff; if (sc->is_emu10k1) sc->codec = AC97_CREATE(dev, sc, emu_ac97); else sc->codec = AC97_CREATE(dev, sc, emu_eac97); sc->ac97_mixerclass = NULL; if (sc->codec != NULL) sc->ac97_mixerclass = ac97_getmixerclass(); if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize DSP mixer\n"); goto bad; } break; case RT_REAR: sc->rt.amounts_left[2] = 0xff; sc->rt.amounts_right[3] = 0xff; sc->rt_mono.amounts_left[2] = 0xff; sc->rt_mono.amounts_left[3] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_CENTER: sc->rt.amounts_left[4] = 0xff; sc->rt_mono.amounts_left[4] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SUB: sc->rt.amounts_left[5] = 0xff; sc->rt_mono.amounts_left[5] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SIDE: sc->rt.amounts_left[6] = 0xff; sc->rt.amounts_right[7] = 0xff; sc->rt_mono.amounts_left[6] = 0xff; sc->rt_mono.amounts_left[7] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_MCHRECORD: if (mixer_init(dev, &emuefxmixer_class, sc)) { device_printf(dev, "failed to initialize EFX mixer\n"); goto bad; } break; default: device_printf(dev, "invalid default route\n"); goto bad; } inte = EMU_INTE_INTERTIMERENB; ipr = EMU_IPR_INTERVALTIMER; /* Used by playback & ADC */ sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc); if (emu_pcm_init(sc) == -1) { device_printf(dev, "unable to initialize PCM part of the card\n"); goto bad; } /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); /* XXX we should better get number of available channels from parent */ if (pcm_register(dev, sc, (route == RT_FRONT) ? MAX_CHANNELS : 1, (route == RT_FRONT) ? 1 : 0)) { device_printf(dev, "can't register PCM channels!\n"); goto bad; } sc->pnum = 0; if (route != RT_MCHRECORD) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); if (route == RT_FRONT) { for (i = 1; i < MAX_CHANNELS; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); } if (route == RT_MCHRECORD) pcm_addchan(dev, PCMDIR_REC, &emufxrchan_class, sc); - snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); + snprintf(status, SND_STATUSLEN, "on %s", + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static int emu_pcm_detach(device_t dev) { int r; struct emu_pcm_info *sc; sc = pcm_getdevinfo(dev); r = pcm_unregister(dev); if (r) return (r); emu_pcm_uninit(sc); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (0); } static device_method_t emu_pcm_methods[] = { DEVMETHOD(device_probe, emu_pcm_probe), DEVMETHOD(device_attach, emu_pcm_attach), DEVMETHOD(device_detach, emu_pcm_detach), DEVMETHOD_END }; static driver_t emu_pcm_driver = { "pcm", emu_pcm_methods, PCM_SOFTC_SIZE, NULL, 0, NULL }; DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, 0, 0); MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER); diff --git a/sys/dev/sound/pci/envy24.c b/sys/dev/sound/pci/envy24.c index 24b5abc0eeb9..f7cc7ff5724d 100644 --- a/sys/dev/sound/pci/envy24.c +++ b/sys/dev/sound/pci/envy24.c @@ -1,2690 +1,2691 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Katsurajima Naoto * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN 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. * */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" static MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio"); /* -------------------------------------------------------------------- */ struct sc_info; #define ENVY24_PLAY_CHNUM 10 #define ENVY24_REC_CHNUM 12 #define ENVY24_PLAY_BUFUNIT (4 /* byte/sample */ * 10 /* channel */) #define ENVY24_REC_BUFUNIT (4 /* byte/sample */ * 12 /* channel */) #define ENVY24_SAMPLE_NUM 4096 #define ENVY24_TIMEOUT 1000 #define ENVY24_DEFAULT_FORMAT SND_FORMAT(AFMT_S16_LE, 2, 0) #define ENVY24_NAMELEN 32 #define SDA_GPIO 0x10 #define SCL_GPIO 0x20 struct envy24_sample { volatile u_int32_t buffer; }; typedef struct envy24_sample sample32_t; /* channel registers */ struct sc_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; int dir; unsigned num; /* hw channel number */ /* channel information */ u_int32_t format; u_int32_t speed; u_int32_t blk; /* hw block size(dword) */ /* format conversion structure */ u_int8_t *data; unsigned int size; /* data buffer size(byte) */ int unit; /* sample size(byte) */ unsigned int offset; /* samples number offset */ void (*emldma)(struct sc_chinfo *); /* flags */ int run; }; /* codec interface entrys */ struct codec_entry { void *(*create)(device_t dev, void *devinfo, int dir, int num); void (*destroy)(void *codec); void (*init)(void *codec); void (*reinit)(void *codec); void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); void (*setrate)(void *codec, int which, int rate); }; /* system configuration information */ struct cfg_info { char *name; u_int16_t subvendor, subdevice; u_int8_t scfg, acl, i2s, spdif; u_int8_t gpiomask, gpiostate, gpiodir; u_int8_t cdti, cclk, cs, cif, type; u_int8_t free; struct codec_entry *codec; }; /* device private data */ struct sc_info { device_t dev; struct mtx *lock; /* Control/Status registor */ struct resource *cs; int csid; bus_space_tag_t cst; bus_space_handle_t csh; /* DDMA registor */ struct resource *ddma; int ddmaid; bus_space_tag_t ddmat; bus_space_handle_t ddmah; /* Consumer Section DMA Channel Registers */ struct resource *ds; int dsid; bus_space_tag_t dst; bus_space_handle_t dsh; /* MultiTrack registor */ struct resource *mt; int mtid; bus_space_tag_t mtt; bus_space_handle_t mth; /* DMA tag */ bus_dma_tag_t dmat; /* IRQ resource */ struct resource *irq; int irqid; void *ih; /* system configuration data */ struct cfg_info *cfg; /* ADC/DAC number and info */ int adcn, dacn; void *adc[4], *dac[4]; /* mixer control data */ u_int32_t src; u_int8_t left[ENVY24_CHAN_NUM]; u_int8_t right[ENVY24_CHAN_NUM]; /* Play/Record DMA fifo */ sample32_t *pbuf; sample32_t *rbuf; u_int32_t psize, rsize; /* DMA buffer size(byte) */ u_int16_t blk[2]; /* transfer check blocksize(dword) */ bus_dmamap_t pmap, rmap; bus_addr_t paddr, raddr; /* current status */ u_int32_t speed; int run[2]; u_int16_t intr[2]; struct pcmchan_caps caps[2]; /* channel info table */ unsigned chnum; struct sc_chinfo chan[11]; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* DMA emulator */ static void envy24_p8u(struct sc_chinfo *); static void envy24_p16sl(struct sc_chinfo *); static void envy24_p32sl(struct sc_chinfo *); static void envy24_r16sl(struct sc_chinfo *); static void envy24_r32sl(struct sc_chinfo *); /* channel interface */ static void *envy24chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int envy24chan_setformat(kobj_t, void *, u_int32_t); static u_int32_t envy24chan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t envy24chan_setblocksize(kobj_t, void *, u_int32_t); static int envy24chan_trigger(kobj_t, void *, int); static u_int32_t envy24chan_getptr(kobj_t, void *); static struct pcmchan_caps *envy24chan_getcaps(kobj_t, void *); /* mixer interface */ static int envy24mixer_init(struct snd_mixer *); static int envy24mixer_reinit(struct snd_mixer *); static int envy24mixer_uninit(struct snd_mixer *); static int envy24mixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *, u_int32_t); /* M-Audio Delta series AK4524 access interface */ static void *envy24_delta_ak4524_create(device_t, void *, int, int); static void envy24_delta_ak4524_destroy(void *); static void envy24_delta_ak4524_init(void *); static void envy24_delta_ak4524_reinit(void *); static void envy24_delta_ak4524_setvolume(void *, int, unsigned int, unsigned int); /* -------------------------------------------------------------------- */ /* system constant tables */ /* API -> hardware channel map */ static unsigned envy24_chanmap[ENVY24_CHAN_NUM] = { ENVY24_CHAN_PLAY_SPDIF, /* 0 */ ENVY24_CHAN_PLAY_DAC1, /* 1 */ ENVY24_CHAN_PLAY_DAC2, /* 2 */ ENVY24_CHAN_PLAY_DAC3, /* 3 */ ENVY24_CHAN_PLAY_DAC4, /* 4 */ ENVY24_CHAN_REC_MIX, /* 5 */ ENVY24_CHAN_REC_SPDIF, /* 6 */ ENVY24_CHAN_REC_ADC1, /* 7 */ ENVY24_CHAN_REC_ADC2, /* 8 */ ENVY24_CHAN_REC_ADC3, /* 9 */ ENVY24_CHAN_REC_ADC4, /* 10 */ }; /* mixer -> API channel map. see above */ static int envy24_mixmap[] = { -1, /* Master output level. It is depend on codec support */ -1, /* Treble level of all output channels */ -1, /* Bass level of all output channels */ -1, /* Volume of synthesier input */ 0, /* Output level for the audio device */ -1, /* Output level for the PC speaker */ 7, /* line in jack */ -1, /* microphone jack */ -1, /* CD audio input */ -1, /* Recording monitor */ 1, /* alternative codec */ -1, /* global recording level */ -1, /* Input gain */ -1, /* Output gain */ 8, /* Input source 1 */ 9, /* Input source 2 */ 10, /* Input source 3 */ 6, /* Digital (input) 1 */ -1, /* Digital (input) 2 */ -1, /* Digital (input) 3 */ -1, /* Phone input */ -1, /* Phone output */ -1, /* Video/TV (audio) in */ -1, /* Radio in */ -1, /* Monitor volume */ }; /* variable rate audio */ static u_int32_t envy24_speed[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000, 0 }; /* known boards configuration */ static struct codec_entry delta_codec = { envy24_delta_ak4524_create, envy24_delta_ak4524_destroy, envy24_delta_ak4524_init, envy24_delta_ak4524_reinit, envy24_delta_ak4524_setvolume, NULL, /* setrate */ }; static struct cfg_info cfg_table[] = { { "Envy24 audio (M Audio Delta Dio 2496)", 0x1412, 0xd631, 0x10, 0x80, 0xf0, 0x03, 0x02, 0xc0, 0xfd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (Terratec DMX 6fire)", 0x153b, 0x1138, 0x2f, 0x80, 0xf0, 0x03, 0xc0, 0xff, 0x7f, 0x10, 0x20, 0x01, 0x01, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Audiophile 2496)", 0x1412, 0xd634, 0x10, 0x80, 0x72, 0x03, 0x04, 0xfe, 0xfb, 0x08, 0x02, 0x20, 0x00, 0x01, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 66)", 0x1412, 0xd632, 0x15, 0x80, 0xf0, 0x03, 0x02, 0xc0, 0xfd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 44)", 0x1412, 0xd633, 0x15, 0x80, 0xf0, 0x00, 0x02, 0xc0, 0xfd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 1010)", 0x1412, 0xd630, 0x1f, 0x80, 0xf0, 0x03, 0x22, 0xd0, 0xdd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 1010LT)", 0x1412, 0xd63b, 0x1f, 0x80, 0x72, 0x03, 0x04, 0x7e, 0xfb, 0x08, 0x02, 0x70, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (Terratec EWX 2496)", 0x153b, 0x1130, 0x10, 0x80, 0xf0, 0x03, 0xc0, 0x3f, 0x3f, 0x10, 0x20, 0x01, 0x01, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (Generic)", 0, 0, 0x0f, 0x00, 0x01, 0x03, 0xff, 0x00, 0x00, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, /* default codec routines */ } }; static u_int32_t envy24_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24_reccaps = {8000, 96000, envy24_recfmt, 0}; static u_int32_t envy24_playfmt[] = { SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24_playcaps = {8000, 96000, envy24_playfmt, 0}; struct envy24_emldma { u_int32_t format; void (*emldma)(struct sc_chinfo *); int unit; }; static struct envy24_emldma envy24_pemltab[] = { {SND_FORMAT(AFMT_U8, 2, 0), envy24_p8u, 2}, {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24_p16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24_p32sl, 8}, {0, NULL, 0} }; static struct envy24_emldma envy24_remltab[] = { {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24_r16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24_r32sl, 8}, {0, NULL, 0} }; /* -------------------------------------------------------------------- */ /* common routines */ static u_int32_t envy24_rdcs(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->cst, sc->csh, regno); case 2: return bus_space_read_2(sc->cst, sc->csh, regno); case 4: return bus_space_read_4(sc->cst, sc->csh, regno); default: return 0xffffffff; } } static void envy24_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->cst, sc->csh, regno, data); break; case 2: bus_space_write_2(sc->cst, sc->csh, regno, data); break; case 4: bus_space_write_4(sc->cst, sc->csh, regno, data); break; } } static u_int32_t envy24_rdmt(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->mtt, sc->mth, regno); case 2: return bus_space_read_2(sc->mtt, sc->mth, regno); case 4: return bus_space_read_4(sc->mtt, sc->mth, regno); default: return 0xffffffff; } } static void envy24_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->mtt, sc->mth, regno, data); break; case 2: bus_space_write_2(sc->mtt, sc->mth, regno, data); break; case 4: bus_space_write_4(sc->mtt, sc->mth, regno, data); break; } } static u_int32_t envy24_rdci(struct sc_info *sc, int regno) { envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); return envy24_rdcs(sc, ENVY24_CCS_DATA, 1); } static void envy24_wrci(struct sc_info *sc, int regno, u_int32_t data) { envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); envy24_wrcs(sc, ENVY24_CCS_DATA, data, 1); } /* -------------------------------------------------------------------- */ /* I2C port/E2PROM access routines */ static int envy24_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); envy24_wrcs(sc, ENVY24_CCS_I2CDEV, (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_RD, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } data = envy24_rdcs(sc, ENVY24_CCS_I2CDATA, 1); #if(0) device_printf(sc->dev, "envy24_rdi2c(): return 0x%x\n", data); #endif return (int)data; } #if 0 static int envy24_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) { u_int32_t tmp; int i; #if(0) device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24_TIMEOUT; i++) { tmp = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((tmp & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); envy24_wrcs(sc, ENVY24_CCS_I2CDATA, data, 1); envy24_wrcs(sc, ENVY24_CCS_I2CDEV, (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_WR, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } return 0; } #endif static int envy24_rdrom(struct sc_info *sc, u_int32_t addr) { u_int32_t data; #if(0) device_printf(sc->dev, "envy24_rdrom(sc, 0x%02x)\n", addr); #endif data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_ROM) == 0) { #if(0) device_printf(sc->dev, "envy24_rdrom(): E2PROM not presented\n"); #endif return -1; } return envy24_rdi2c(sc, ENVY24_CCS_I2CDEV_ROM, addr); } static struct cfg_info * envy24_rom2cfg(struct sc_info *sc) { struct cfg_info *buff; int size; int i; #if(0) device_printf(sc->dev, "envy24_rom2cfg(sc)\n"); #endif size = envy24_rdrom(sc, ENVY24_E2PROM_SIZE); if (size < ENVY24_E2PROM_GPIODIR + 1) { #if(0) device_printf(sc->dev, "envy24_rom2cfg(): ENVY24_E2PROM_SIZE-->%d\n", size); #endif return NULL; } buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); if (buff == NULL) { #if(0) device_printf(sc->dev, "envy24_rom2cfg(): malloc()\n"); #endif return NULL; } buff->free = 1; buff->subvendor = envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE + 1); buff->scfg = envy24_rdrom(sc, ENVY24_E2PROM_SCFG); buff->acl = envy24_rdrom(sc, ENVY24_E2PROM_ACL); buff->i2s = envy24_rdrom(sc, ENVY24_E2PROM_I2S); buff->spdif = envy24_rdrom(sc, ENVY24_E2PROM_SPDIF); buff->gpiomask = envy24_rdrom(sc, ENVY24_E2PROM_GPIOMASK); buff->gpiostate = envy24_rdrom(sc, ENVY24_E2PROM_GPIOSTATE); buff->gpiodir = envy24_rdrom(sc, ENVY24_E2PROM_GPIODIR); for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) if (cfg_table[i].subvendor == buff->subvendor && cfg_table[i].subdevice == buff->subdevice) break; buff->name = cfg_table[i].name; buff->codec = cfg_table[i].codec; return buff; } static void envy24_cfgfree(struct cfg_info *cfg) { if (cfg == NULL) return; if (cfg->free) free(cfg, M_ENVY24); return; } /* -------------------------------------------------------------------- */ /* AC'97 codec access routines */ #if 0 static int envy24_coldcd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_coldcd()\n"); #endif envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD, 1); DELAY(10); envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if (data & ENVY24_MT_AC97CMD_RDY) { return 0; } } return -1; } #endif static int envy24_slavecd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_slavecd()\n"); #endif envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD | ENVY24_MT_AC97CMD_WRM, 1); DELAY(10); envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if (data & ENVY24_MT_AC97CMD_RDY) { return 0; } } return -1; } #if 0 static int envy24_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_rdcd(obj, sc, 0x%02x)\n", regno); #endif envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_RD, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if ((data & ENVY24_MT_AC97CMD_RD) == 0) break; } data = envy24_rdmt(sc, ENVY24_MT_AC97DLO, 2); #if(0) device_printf(sc->dev, "envy24_rdcd(): return 0x%x\n", data); #endif return (int)data; } static int envy24_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t cmd; int i; #if(0) device_printf(sc->dev, "envy24_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); #endif envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); envy24_wrmt(sc, ENVY24_MT_AC97DLO, (u_int32_t)data, 2); envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_WR, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { cmd = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if ((cmd & ENVY24_MT_AC97CMD_WR) == 0) break; } return 0; } static kobj_method_t envy24_ac97_methods[] = { KOBJMETHOD(ac97_read, envy24_rdcd), KOBJMETHOD(ac97_write, envy24_wrcd), KOBJMETHOD_END }; AC97_DECLARE(envy24_ac97); #endif /* -------------------------------------------------------------------- */ /* GPIO access routines */ static u_int32_t envy24_gpiord(struct sc_info *sc) { return envy24_rdci(sc, ENVY24_CCI_GPIODAT); } static void envy24_gpiowr(struct sc_info *sc, u_int32_t data) { #if(0) device_printf(sc->dev, "envy24_gpiowr(sc, 0x%02x)\n", data & 0xff); return; #endif envy24_wrci(sc, ENVY24_CCI_GPIODAT, data); return; } #if 0 static u_int32_t envy24_gpiogetmask(struct sc_info *sc) { return envy24_rdci(sc, ENVY24_CCI_GPIOMASK); } #endif static void envy24_gpiosetmask(struct sc_info *sc, u_int32_t mask) { envy24_wrci(sc, ENVY24_CCI_GPIOMASK, mask); return; } #if 0 static u_int32_t envy24_gpiogetdir(struct sc_info *sc) { return envy24_rdci(sc, ENVY24_CCI_GPIOCTL); } #endif static void envy24_gpiosetdir(struct sc_info *sc, u_int32_t dir) { envy24_wrci(sc, ENVY24_CCI_GPIOCTL, dir); return; } /* -------------------------------------------------------------------- */ /* Envy24 I2C through GPIO bit-banging */ struct envy24_delta_ak4524_codec { struct spicds_info *info; struct sc_info *parent; int dir; int num; int cs, cclk, cdti; }; static void envy24_gpio_i2c_ctl(void *codec, unsigned int scl, unsigned int sda) { u_int32_t data = 0; struct envy24_delta_ak4524_codec *ptr = codec; #if(0) device_printf(ptr->parent->dev, "--> %d, %d\n", scl, sda); #endif data = envy24_gpiord(ptr->parent); data &= ~(SDA_GPIO | SCL_GPIO); if (scl) data += SCL_GPIO; if (sda) data += SDA_GPIO; envy24_gpiowr(ptr->parent, data); return; } static void i2c_wrbit(void *codec, void (*ctrl)(void*, unsigned int, unsigned int), int bit) { struct envy24_delta_ak4524_codec *ptr = codec; unsigned int sda; if (bit) sda = 1; else sda = 0; ctrl(ptr, 0, sda); DELAY(I2C_DELAY); ctrl(ptr, 1, sda); DELAY(I2C_DELAY); ctrl(ptr, 0, sda); DELAY(I2C_DELAY); } static void i2c_start(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) { struct envy24_delta_ak4524_codec *ptr = codec; ctrl(ptr, 1, 1); DELAY(I2C_DELAY); ctrl(ptr, 1, 0); DELAY(I2C_DELAY); ctrl(ptr, 0, 0); DELAY(I2C_DELAY); } static void i2c_stop(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) { struct envy24_delta_ak4524_codec *ptr = codec; ctrl(ptr, 0, 0); DELAY(I2C_DELAY); ctrl(ptr, 1, 0); DELAY(I2C_DELAY); ctrl(ptr, 1, 1); DELAY(I2C_DELAY); } static void i2c_ack(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) { struct envy24_delta_ak4524_codec *ptr = codec; ctrl(ptr, 0, 1); DELAY(I2C_DELAY); ctrl(ptr, 1, 1); DELAY(I2C_DELAY); /* dummy, need routine to change gpio direction */ ctrl(ptr, 0, 1); DELAY(I2C_DELAY); } static void i2c_wr(void *codec, void (*ctrl)(void*, unsigned int, unsigned int), u_int32_t dev, int reg, u_int8_t val) { struct envy24_delta_ak4524_codec *ptr = codec; int mask; i2c_start(ptr, ctrl); for (mask = 0x80; mask != 0; mask >>= 1) i2c_wrbit(ptr, ctrl, dev & mask); i2c_ack(ptr, ctrl); if (reg != 0xff) { for (mask = 0x80; mask != 0; mask >>= 1) i2c_wrbit(ptr, ctrl, reg & mask); i2c_ack(ptr, ctrl); } for (mask = 0x80; mask != 0; mask >>= 1) i2c_wrbit(ptr, ctrl, val & mask); i2c_ack(ptr, ctrl); i2c_stop(ptr, ctrl); } /* -------------------------------------------------------------------- */ /* M-Audio Delta series AK4524 access interface routine */ static void envy24_delta_ak4524_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) { u_int32_t data = 0; struct envy24_delta_ak4524_codec *ptr = codec; #if(0) device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); #endif data = envy24_gpiord(ptr->parent); data &= ~(ptr->cs | ptr->cclk | ptr->cdti); if (cs) data += ptr->cs; if (cclk) data += ptr->cclk; if (cdti) data += ptr->cdti; envy24_gpiowr(ptr->parent, data); return; } static void * envy24_delta_ak4524_create(device_t dev, void *info, int dir, int num) { struct sc_info *sc = info; struct envy24_delta_ak4524_codec *buff = NULL; #if(0) device_printf(sc->dev, "envy24_delta_ak4524_create(dev, sc, %d, %d)\n", dir, num); #endif buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); if (buff == NULL) return NULL; if (dir == PCMDIR_REC && sc->adc[num] != NULL) buff->info = ((struct envy24_delta_ak4524_codec *)sc->adc[num])->info; else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) buff->info = ((struct envy24_delta_ak4524_codec *)sc->dac[num])->info; else buff->info = spicds_create(dev, buff, num, envy24_delta_ak4524_ctl); if (buff->info == NULL) { free(buff, M_ENVY24); return NULL; } buff->parent = sc; buff->dir = dir; buff->num = num; return (void *)buff; } static void envy24_delta_ak4524_destroy(void *codec) { struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_destroy()\n"); #endif if (ptr->dir == PCMDIR_PLAY) { if (ptr->parent->dac[ptr->num] != NULL) spicds_destroy(ptr->info); } else { if (ptr->parent->adc[ptr->num] != NULL) spicds_destroy(ptr->info); } free(codec, M_ENVY24); } static void envy24_delta_ak4524_init(void *codec) { #if 0 u_int32_t gpiomask, gpiodir; #endif struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_init()\n"); #endif /* gpiomask = envy24_gpiogetmask(ptr->parent); gpiomask &= ~(ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1); envy24_gpiosetmask(ptr->parent, gpiomask); gpiodir = envy24_gpiogetdir(ptr->parent); gpiodir |= ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1; envy24_gpiosetdir(ptr->parent, gpiodir); */ ptr->cs = ptr->parent->cfg->cs; #if 0 envy24_gpiosetmask(ptr->parent, ENVY24_GPIO_CS8414_STATUS); envy24_gpiosetdir(ptr->parent, ~ENVY24_GPIO_CS8414_STATUS); if (ptr->num == 0) ptr->cs = ENVY24_GPIO_AK4524_CS0; else ptr->cs = ENVY24_GPIO_AK4524_CS1; ptr->cclk = ENVY24_GPIO_AK4524_CCLK; #endif ptr->cclk = ptr->parent->cfg->cclk; ptr->cdti = ptr->parent->cfg->cdti; spicds_settype(ptr->info, ptr->parent->cfg->type); spicds_setcif(ptr->info, ptr->parent->cfg->cif); spicds_setformat(ptr->info, AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); spicds_setdvc(ptr->info, AK452X_DVC_DEMOFF); /* for the time being, init only first codec */ if (ptr->num == 0) spicds_init(ptr->info); /* 6fire rear input init test, set ptr->num to 1 for test */ if (ptr->parent->cfg->subvendor == 0x153b && \ ptr->parent->cfg->subdevice == 0x1138 && ptr->num == 100) { ptr->cs = 0x02; spicds_init(ptr->info); device_printf(ptr->parent->dev, "6fire rear input init\n"); i2c_wr(ptr, envy24_gpio_i2c_ctl, \ PCA9554_I2CDEV, PCA9554_DIR, 0x80); i2c_wr(ptr, envy24_gpio_i2c_ctl, \ PCA9554_I2CDEV, PCA9554_OUT, 0x02); } } static void envy24_delta_ak4524_reinit(void *codec) { struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_reinit()\n"); #endif spicds_reinit(ptr->info); } static void envy24_delta_ak4524_setvolume(void *codec, int dir, unsigned int left, unsigned int right) { struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_set()\n"); #endif spicds_set(ptr->info, dir, left, right); } /* There is no need for AK452[48] codec to set sample rate static void envy24_delta_ak4524_setrate(struct envy24_delta_ak4524_codec *codec, int which, int rate) { } */ /* -------------------------------------------------------------------- */ /* hardware access routeines */ static struct { u_int32_t speed; u_int32_t code; } envy24_speedtab[] = { {48000, ENVY24_MT_RATE_48000}, {24000, ENVY24_MT_RATE_24000}, {12000, ENVY24_MT_RATE_12000}, {9600, ENVY24_MT_RATE_9600}, {32000, ENVY24_MT_RATE_32000}, {16000, ENVY24_MT_RATE_16000}, {8000, ENVY24_MT_RATE_8000}, {96000, ENVY24_MT_RATE_96000}, {64000, ENVY24_MT_RATE_64000}, {44100, ENVY24_MT_RATE_44100}, {22050, ENVY24_MT_RATE_22050}, {11025, ENVY24_MT_RATE_11025}, {88200, ENVY24_MT_RATE_88200}, {0, 0x10} }; static u_int32_t envy24_setspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t code; int i = 0; #if(0) device_printf(sc->dev, "envy24_setspeed(sc, %d)\n", speed); #endif if (speed == 0) { code = ENVY24_MT_RATE_SPDIF; /* external master clock */ envy24_slavecd(sc); } else { for (i = 0; envy24_speedtab[i].speed != 0; i++) { if (envy24_speedtab[i].speed == speed) break; } code = envy24_speedtab[i].code; } #if(0) device_printf(sc->dev, "envy24_setspeed(): speed %d/code 0x%04x\n", envy24_speedtab[i].speed, code); #endif if (code < 0x10) { envy24_wrmt(sc, ENVY24_MT_RATE, code, 1); code = envy24_rdmt(sc, ENVY24_MT_RATE, 1); code &= ENVY24_MT_RATE_MASK; for (i = 0; envy24_speedtab[i].code < 0x10; i++) { if (envy24_speedtab[i].code == code) break; } speed = envy24_speedtab[i].speed; } else speed = 0; #if(0) device_printf(sc->dev, "envy24_setspeed(): return %d\n", speed); #endif return speed; } static void envy24_setvolume(struct sc_info *sc, unsigned ch) { #if(0) device_printf(sc->dev, "envy24_setvolume(sc, %d)\n", ch); #endif if (sc->cfg->subvendor==0x153b && sc->cfg->subdevice==0x1138 ) { envy24_wrmt(sc, ENVY24_MT_VOLIDX, 16, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2); envy24_wrmt(sc, ENVY24_MT_VOLIDX, 17, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2); } envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f00 | sc->left[ch], 2); envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); } static void envy24_mutevolume(struct sc_info *sc, unsigned ch) { u_int32_t vol; #if(0) device_printf(sc->dev, "envy24_mutevolume(sc, %d)\n", ch); #endif vol = ENVY24_VOL_MUTE << 8 | ENVY24_VOL_MUTE; envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); } static u_int32_t envy24_gethwptr(struct sc_info *sc, int dir) { int unit, regno; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24_gethwptr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { rtn = sc->psize / 4; unit = ENVY24_PLAY_BUFUNIT / 4; regno = ENVY24_MT_PCNT; } else { rtn = sc->rsize / 4; unit = ENVY24_REC_BUFUNIT / 4; regno = ENVY24_MT_RCNT; } ptr = envy24_rdmt(sc, regno, 2); rtn -= (ptr + 1); rtn /= unit; #if(0) device_printf(sc->dev, "envy24_gethwptr(): return %d\n", rtn); #endif return rtn; } static void envy24_updintr(struct sc_info *sc, int dir) { int regintr; u_int32_t mask, intr; u_int32_t cnt; u_int16_t blk; #if(0) device_printf(sc->dev, "envy24_updintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { blk = sc->blk[0]; regintr = ENVY24_MT_PTERM; mask = ~ENVY24_MT_INT_PMASK; } else { blk = sc->blk[1]; regintr = ENVY24_MT_RTERM; mask = ~ENVY24_MT_INT_RMASK; } cnt = blk - 1; #if(0) device_printf(sc->dev, "envy24_updintr():blk = %d, cnt = %d\n", blk, cnt); #endif envy24_wrmt(sc, regintr, cnt, 2); intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); #if(0) device_printf(sc->dev, "envy24_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); #endif envy24_wrmt(sc, ENVY24_MT_INT, intr & mask, 1); #if(0) device_printf(sc->dev, "envy24_updintr():INT-->0x%02x\n", envy24_rdmt(sc, ENVY24_MT_INT, 1)); #endif return; } #if 0 static void envy24_maskintr(struct sc_info *sc, int dir) { u_int32_t mask, intr; #if(0) device_printf(sc->dev, "envy24_maskintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) mask = ENVY24_MT_INT_PMASK; else mask = ENVY24_MT_INT_RMASK; intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); envy24_wrmt(sc, ENVY24_MT_INT, intr | mask, 1); return; } #endif static int envy24_checkintr(struct sc_info *sc, int dir) { u_int32_t mask, stat, intr, rtn; #if(0) device_printf(sc->dev, "envy24_checkintr(sc, %d)\n", dir); #endif intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); if (dir == PCMDIR_PLAY) { if ((rtn = intr & ENVY24_MT_INT_PSTAT) != 0) { mask = ~ENVY24_MT_INT_RSTAT; stat = ENVY24_MT_INT_PSTAT | ENVY24_MT_INT_PMASK; envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); } } else { if ((rtn = intr & ENVY24_MT_INT_RSTAT) != 0) { mask = ~ENVY24_MT_INT_PSTAT; stat = ENVY24_MT_INT_RSTAT | ENVY24_MT_INT_RMASK; envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); } } return rtn; } static void envy24_start(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24_start(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ENVY24_MT_PCTL_PSTART; else sw = ENVY24_MT_PCTL_RSTART; stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); envy24_wrmt(sc, ENVY24_MT_PCTL, stat | sw, 1); #if(0) DELAY(100); device_printf(sc->dev, "PADDR:0x%08x\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); device_printf(sc->dev, "PCNT:%ld\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); #endif return; } static void envy24_stop(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24_stop(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ~ENVY24_MT_PCTL_PSTART; else sw = ~ENVY24_MT_PCTL_RSTART; stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); envy24_wrmt(sc, ENVY24_MT_PCTL, stat & sw, 1); return; } static int envy24_route(struct sc_info *sc, int dac, int class, int adc, int rev) { u_int32_t reg, mask; u_int32_t left, right; #if(0) device_printf(sc->dev, "envy24_route(sc, %d, %d, %d, %d)\n", dac, class, adc, rev); #endif /* parameter pattern check */ if (dac < 0 || ENVY24_ROUTE_DAC_SPDIF < dac) return -1; if (class == ENVY24_ROUTE_CLASS_MIX && (dac != ENVY24_ROUTE_DAC_1 && dac != ENVY24_ROUTE_DAC_SPDIF)) return -1; if (rev) { left = ENVY24_ROUTE_RIGHT; right = ENVY24_ROUTE_LEFT; } else { left = ENVY24_ROUTE_LEFT; right = ENVY24_ROUTE_RIGHT; } if (dac == ENVY24_ROUTE_DAC_SPDIF) { reg = class | class << 2 | ((adc << 1 | left) | left << 3) << 8 | ((adc << 1 | right) | right << 3) << 12; #if(0) device_printf(sc->dev, "envy24_route(): MT_SPDOUT-->0x%04x\n", reg); #endif envy24_wrmt(sc, ENVY24_MT_SPDOUT, reg, 2); } else { mask = ~(0x0303 << dac * 2); reg = envy24_rdmt(sc, ENVY24_MT_PSDOUT, 2); reg = (reg & mask) | ((class | class << 8) << dac * 2); #if(0) device_printf(sc->dev, "envy24_route(): MT_PSDOUT-->0x%04x\n", reg); #endif envy24_wrmt(sc, ENVY24_MT_PSDOUT, reg, 2); mask = ~(0xff << dac * 8); reg = envy24_rdmt(sc, ENVY24_MT_RECORD, 4); reg = (reg & mask) | (((adc << 1 | left) | left << 3) | ((adc << 1 | right) | right << 3) << 4) << dac * 8; #if(0) device_printf(sc->dev, "envy24_route(): MT_RECORD-->0x%08x\n", reg); #endif envy24_wrmt(sc, ENVY24_MT_RECORD, reg, 4); /* 6fire rear input init test */ envy24_wrmt(sc, ENVY24_MT_RECORD, 0x00, 4); } return 0; } /* -------------------------------------------------------------------- */ /* buffer copy routines */ static void envy24_p32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 8; dmabuf = ch->parent->pbuf; data = (u_int32_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 4; dst = src / 2 + ch->offset; ssize = ch->size / 4; dsize = ch->size / 8; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = data[src]; dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24_p16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; #if(0) device_printf(ch->parent->dev, "envy24_p16sl()\n"); #endif length = sndbuf_getready(ch->buffer) / 4; dmabuf = ch->parent->pbuf; data = (u_int16_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 2; dst = src / 2 + ch->offset; ssize = ch->size / 2; dsize = ch->size / 4; slot = ch->num * 2; #if(0) device_printf(ch->parent->dev, "envy24_p16sl():%lu-->%lu(%lu)\n", src, dst, length); #endif for (i = 0; i < length; i++) { dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16; dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16; #if(0) if (i < 16) { printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot]); printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1]); } #endif dst++; dst %= dsize; src += 2; src %= ssize; } #if(0) printf("\n"); #endif return; } static void envy24_p8u(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int8_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 2; dmabuf = ch->parent->pbuf; data = (u_int8_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer); dst = src / 2 + ch->offset; ssize = ch->size; dsize = ch->size / 4; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24; dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24_r32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 8; dmabuf = ch->parent->rbuf; data = (u_int32_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 4; src = dst / 2 + ch->offset; dsize = ch->size / 4; ssize = ch->size / 8; slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } static void envy24_r16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 4; dmabuf = ch->parent->rbuf; data = (u_int16_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 2; src = dst / 2 + ch->offset; dsize = ch->size / 2; ssize = ch->size / 8; slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * envy24chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = (struct sc_info *)devinfo; struct sc_chinfo *ch; unsigned num; #if(0) device_printf(sc->dev, "envy24chan_init(obj, devinfo, b, c, %d)\n", dir); #endif snd_mtxlock(sc->lock); if ((sc->chnum > ENVY24_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || (sc->chnum < ENVY24_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { snd_mtxunlock(sc->lock); return NULL; } num = sc->chnum; ch = &sc->chan[num]; ch->size = 8 * ENVY24_SAMPLE_NUM; ch->data = malloc(ch->size, M_ENVY24, M_NOWAIT); if (ch->data == NULL) { ch->size = 0; ch = NULL; } else { ch->buffer = b; ch->channel = c; ch->parent = sc; ch->dir = dir; /* set channel map */ ch->num = envy24_chanmap[num]; snd_mtxunlock(sc->lock); sndbuf_setup(ch->buffer, ch->data, ch->size); snd_mtxlock(sc->lock); /* these 2 values are dummy */ ch->unit = 4; ch->blk = 10240; } snd_mtxunlock(sc->lock); return ch; } static int envy24chan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; #if(0) device_printf(sc->dev, "envy24chan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_ENVY24); ch->data = NULL; } snd_mtxunlock(sc->lock); return 0; } static int envy24chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct envy24_emldma *emltab; /* unsigned int bcnt, bsize; */ int i; #if(0) device_printf(sc->dev, "envy24chan_setformat(obj, data, 0x%08x)\n", format); #endif snd_mtxlock(sc->lock); /* check and get format related information */ if (ch->dir == PCMDIR_PLAY) emltab = envy24_pemltab; else emltab = envy24_remltab; if (emltab == NULL) { snd_mtxunlock(sc->lock); return -1; } for (i = 0; emltab[i].format != 0; i++) if (emltab[i].format == format) break; if (emltab[i].format == 0) { snd_mtxunlock(sc->lock); return -1; } /* set format information */ ch->format = format; ch->emldma = emltab[i].emldma; if (ch->unit > emltab[i].unit) ch->blk *= ch->unit / emltab[i].unit; else ch->blk /= emltab[i].unit / ch->unit; ch->unit = emltab[i].unit; /* set channel buffer information */ ch->size = ch->unit * ENVY24_SAMPLE_NUM; #if 0 if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); #endif snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24chan_setformat(): return 0x%08x\n", 0); #endif return 0; } /* IMPLEMENT NOTICE: In this driver, setspeed function only do setting of speed information value. And real hardware speed setting is done at start triggered(see envy24chan_trigger()). So, at this function is called, any value that ENVY24 can use is able to set. But, at start triggerd, some other channel is running, and that channel's speed isn't same with, then trigger function will fail. */ static u_int32_t envy24chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; u_int32_t val, prev; int i; #if(0) device_printf(ch->parent->dev, "envy24chan_setspeed(obj, data, %d)\n", speed); #endif prev = 0x7fffffff; for (i = 0; (val = envy24_speed[i]) != 0; i++) { if (abs(val - speed) < abs(prev - speed)) prev = val; else break; } ch->speed = prev; #if(0) device_printf(ch->parent->dev, "envy24chan_setspeed(): return %d\n", ch->speed); #endif return ch->speed; } static u_int32_t envy24chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; /* struct sc_info *sc = ch->parent; */ u_int32_t size, prev; unsigned int bcnt, bsize; #if(0) device_printf(sc->dev, "envy24chan_setblocksize(obj, data, %d)\n", blocksize); #endif prev = 0x7fffffff; /* snd_mtxlock(sc->lock); */ for (size = ch->size / 2; size > 0; size /= 2) { if (abs(size - blocksize) < abs(prev - blocksize)) prev = size; else break; } ch->blk = prev / ch->unit; if (ch->dir == PCMDIR_PLAY) ch->blk *= ENVY24_PLAY_BUFUNIT / 4; else ch->blk *= ENVY24_REC_BUFUNIT / 4; /* set channel buffer information */ /* ch->size = ch->unit * ENVY24_SAMPLE_NUM; */ if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); /* snd_mtxunlock(sc->lock); */ #if(0) device_printf(sc->dev, "envy24chan_setblocksize(): return %d\n", prev); #endif return prev; } /* semantic note: must start at beginning of buffer */ static int envy24chan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr; int slot; int error = 0; #if 0 int i; device_printf(sc->dev, "envy24chan_trigger(obj, data, %d)\n", go); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) slot = 0; else slot = 1; switch (go) { case PCMTRIG_START: #if(0) device_printf(sc->dev, "envy24chan_trigger(): start\n"); #endif /* check or set channel speed */ if (sc->run[0] == 0 && sc->run[1] == 0) { sc->speed = envy24_setspeed(sc, ch->speed); sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; } else if (ch->speed != 0 && ch->speed != sc->speed) { error = -1; goto fail; } if (ch->speed == 0) ch->channel->speed = sc->speed; /* start or enable channel */ sc->run[slot]++; if (sc->run[slot] == 1) { /* first channel */ ch->offset = 0; sc->blk[slot] = ch->blk; } else { ptr = envy24_gethwptr(sc, ch->dir); ch->offset = ((ptr / ch->blk + 1) * ch->blk % (ch->size / 4)) * 4 / ch->unit; if (ch->blk < sc->blk[slot]) sc->blk[slot] = ch->blk; } if (ch->dir == PCMDIR_PLAY) { ch->emldma(ch); envy24_setvolume(sc, ch->num); } envy24_updintr(sc, ch->dir); if (sc->run[slot] == 1) envy24_start(sc, ch->dir); ch->run = 1; break; case PCMTRIG_EMLDMAWR: #if(0) device_printf(sc->dev, "envy24chan_trigger(): emldmawr\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_EMLDMARD: #if(0) device_printf(sc->dev, "envy24chan_trigger(): emldmard\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_ABORT: if (ch->run) { #if(0) device_printf(sc->dev, "envy24chan_trigger(): abort\n"); #endif ch->run = 0; sc->run[slot]--; if (ch->dir == PCMDIR_PLAY) envy24_mutevolume(sc, ch->num); if (sc->run[slot] == 0) { envy24_stop(sc, ch->dir); sc->intr[slot] = 0; } #if 0 else if (ch->blk == sc->blk[slot]) { sc->blk[slot] = ENVY24_SAMPLE_NUM / 2; for (i = 0; i < ENVY24_CHAN_NUM; i++) { if (sc->chan[i].dir == ch->dir && sc->chan[i].run == 1 && sc->chan[i].blk < sc->blk[slot]) sc->blk[slot] = sc->chan[i].blk; } if (ch->blk != sc->blk[slot]) envy24_updintr(sc, ch->dir); } #endif } break; } fail: snd_mtxunlock(sc->lock); return (error); } static u_int32_t envy24chan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24chan_getptr()\n"); #endif snd_mtxlock(sc->lock); ptr = envy24_gethwptr(sc, ch->dir); rtn = ptr * ch->unit; snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24chan_getptr(): return %d\n", rtn); #endif return rtn; } static struct pcmchan_caps * envy24chan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct pcmchan_caps *rtn; #if(0) device_printf(sc->dev, "envy24chan_getcaps()\n"); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (sc->run[0] == 0) rtn = &envy24_playcaps; else rtn = &sc->caps[0]; } else { if (sc->run[1] == 0) rtn = &envy24_reccaps; else rtn = &sc->caps[1]; } snd_mtxunlock(sc->lock); return rtn; } static kobj_method_t envy24chan_methods[] = { KOBJMETHOD(channel_init, envy24chan_init), KOBJMETHOD(channel_free, envy24chan_free), KOBJMETHOD(channel_setformat, envy24chan_setformat), KOBJMETHOD(channel_setspeed, envy24chan_setspeed), KOBJMETHOD(channel_setblocksize, envy24chan_setblocksize), KOBJMETHOD(channel_trigger, envy24chan_trigger), KOBJMETHOD(channel_getptr, envy24chan_getptr), KOBJMETHOD(channel_getcaps, envy24chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(envy24chan); /* -------------------------------------------------------------------- */ /* mixer interface */ static int envy24mixer_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); #if(0) device_printf(sc->dev, "envy24mixer_init()\n"); #endif if (sc == NULL) return -1; /* set volume control rate */ snd_mtxlock(sc->lock); envy24_wrmt(sc, ENVY24_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ mix_setdevs(m, ENVY24_MIX_MASK); mix_setrecdevs(m, ENVY24_MIX_REC_MASK); snd_mtxunlock(sc->lock); return 0; } static int envy24mixer_reinit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24mixer_reinit()\n"); #endif return 0; } static int envy24mixer_uninit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24mixer_uninit()\n"); #endif return 0; } static int envy24mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24_mixmap[dev]; int hwch; int i; if (sc == NULL) return -1; if (dev == 0 && sc->cfg->codec->setvolume == NULL) return -1; if (dev != 0 && ch == -1) return -1; hwch = envy24_chanmap[ch]; #if(0) device_printf(sc->dev, "envy24mixer_set(m, %d, %d, %d)\n", dev, left, right); #endif snd_mtxlock(sc->lock); if (dev == 0) { for (i = 0; i < sc->dacn; i++) { sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); } } else { /* set volume value for hardware */ if ((sc->left[hwch] = 100 - left) > ENVY24_VOL_MIN) sc->left[hwch] = ENVY24_VOL_MUTE; if ((sc->right[hwch] = 100 - right) > ENVY24_VOL_MIN) sc->right[hwch] = ENVY24_VOL_MUTE; /* set volume for record channel and running play channel */ if (hwch > ENVY24_CHAN_PLAY_SPDIF || sc->chan[ch].run) envy24_setvolume(sc, hwch); } snd_mtxunlock(sc->lock); return right << 8 | left; } static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24_mixmap[src]; #if(0) device_printf(sc->dev, "envy24mixer_setrecsrc(m, %d)\n", src); #endif if (ch > ENVY24_CHAN_PLAY_SPDIF) sc->src = ch; return src; } static kobj_method_t envy24mixer_methods[] = { KOBJMETHOD(mixer_init, envy24mixer_init), KOBJMETHOD(mixer_reinit, envy24mixer_reinit), KOBJMETHOD(mixer_uninit, envy24mixer_uninit), KOBJMETHOD(mixer_set, envy24mixer_set), KOBJMETHOD(mixer_setrecsrc, envy24mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(envy24mixer); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void envy24_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; u_int32_t ptr, dsize, feed; int i; #if(0) device_printf(sc->dev, "envy24_intr()\n"); #endif snd_mtxlock(sc->lock); if (envy24_checkintr(sc, PCMDIR_PLAY)) { #if(0) device_printf(sc->dev, "envy24_intr(): play\n"); #endif dsize = sc->psize / 4; ptr = dsize - envy24_rdmt(sc, ENVY24_MT_PCNT, 2) - 1; #if(0) device_printf(sc->dev, "envy24_intr(): ptr = %d-->", ptr); #endif ptr -= ptr % sc->blk[0]; feed = (ptr + dsize - sc->intr[0]) % dsize; #if(0) printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); #endif for (i = ENVY24_CHAN_PLAY_DAC1; i <= ENVY24_CHAN_PLAY_SPDIF; i++) { ch = &sc->chan[i]; #if(0) if (ch->run) device_printf(sc->dev, "envy24_intr(): chan[%d].blk = %d\n", i, ch->blk); #endif if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[0] = ptr; envy24_updintr(sc, PCMDIR_PLAY); } if (envy24_checkintr(sc, PCMDIR_REC)) { #if(0) device_printf(sc->dev, "envy24_intr(): rec\n"); #endif dsize = sc->rsize / 4; ptr = dsize - envy24_rdmt(sc, ENVY24_MT_RCNT, 2) - 1; ptr -= ptr % sc->blk[1]; feed = (ptr + dsize - sc->intr[1]) % dsize; for (i = ENVY24_CHAN_REC_ADC1; i <= ENVY24_CHAN_REC_SPDIF; i++) { ch = &sc->chan[i]; if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[1] = ptr; envy24_updintr(sc, PCMDIR_REC); } snd_mtxunlock(sc->lock); return; } /* * Probe and attach the card */ static int envy24_pci_probe(device_t dev) { u_int16_t sv, sd; int i; #if(0) printf("envy24_pci_probe()\n"); #endif if (pci_get_device(dev) == PCID_ENVY24 && pci_get_vendor(dev) == PCIV_ENVY24) { sv = pci_get_subvendor(dev); sd = pci_get_subdevice(dev); for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { if (cfg_table[i].subvendor == sv && cfg_table[i].subdevice == sd) { break; } } device_set_desc(dev, cfg_table[i].name); #if(0) printf("envy24_pci_probe(): return 0\n"); #endif return 0; } else { #if(0) printf("envy24_pci_probe(): return ENXIO\n"); #endif return ENXIO; } } static void envy24_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->paddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24_dmapsetmap()\n"); if (bootverbose) { printf("envy24(play): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); printf("%p -> %lx\n", sc->pmap, sc->paddr); } #endif } static void envy24_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->raddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24_dmarsetmap()\n"); if (bootverbose) { printf("envy24(record): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); printf("%p -> %lx\n", sc->rmap, sc->raddr); } #endif } static void envy24_dmafree(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24_dmafree():"); printf(" sc->raddr(0x%08x)", (u_int32_t)sc->raddr); printf(" sc->paddr(0x%08x)", (u_int32_t)sc->paddr); if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); else printf(" sc->rbuf(null)"); if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); else printf(" sc->pbuf(null)\n"); #endif #if(0) if (sc->raddr) bus_dmamap_unload(sc->dmat, sc->rmap); if (sc->paddr) bus_dmamap_unload(sc->dmat, sc->pmap); if (sc->rbuf) bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); if (sc->pbuf) bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #else bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #endif sc->raddr = sc->paddr = 0; sc->pbuf = NULL; sc->rbuf = NULL; return; } static int envy24_dmainit(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24_dmainit()\n"); #endif /* init values */ sc->psize = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM; sc->rsize = ENVY24_REC_BUFUNIT * ENVY24_SAMPLE_NUM; sc->pbuf = NULL; sc->rbuf = NULL; sc->paddr = sc->raddr = 0; sc->blk[0] = sc->blk[1] = 0; /* allocate DMA buffer */ #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) goto bad; #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) goto bad; #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->pmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24_dmapsetmap, sc, BUS_DMA_NOWAIT)) goto bad; #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->rmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24_dmarsetmap, sc, BUS_DMA_NOWAIT)) goto bad; bzero(sc->pbuf, sc->psize); bzero(sc->rbuf, sc->rsize); /* set values to register */ #if(0) device_printf(sc->dev, "paddr(0x%08x)\n", sc->paddr); #endif envy24_wrmt(sc, ENVY24_MT_PADDR, sc->paddr, 4); #if(0) device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1); #endif envy24_wrmt(sc, ENVY24_MT_PCNT, sc->psize / 4 - 1, 2); #if(0) device_printf(sc->dev, "PCNT-->(%ld)\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); #endif envy24_wrmt(sc, ENVY24_MT_RADDR, sc->raddr, 4); envy24_wrmt(sc, ENVY24_MT_RCNT, sc->rsize / 4 - 1, 2); return 0; bad: envy24_dmafree(sc); return ENOSPC; } static void envy24_putcfg(struct sc_info *sc) { device_printf(sc->dev, "system configuration\n"); printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", sc->cfg->subvendor, sc->cfg->subdevice); printf(" XIN2 Clock Source: "); switch (sc->cfg->scfg & PCIM_SCFG_XIN2) { case 0x00: printf("22.5792MHz(44.1kHz*512)\n"); break; case 0x40: printf("16.9344MHz(44.1kHz*384)\n"); break; case 0x80: printf("from external clock synthesizer chip\n"); break; default: printf("illegal system setting\n"); } printf(" MPU-401 UART(s) #: "); if (sc->cfg->scfg & PCIM_SCFG_MPU) printf("2\n"); else printf("1\n"); printf(" AC'97 codec: "); if (sc->cfg->scfg & PCIM_SCFG_AC97) printf("not exist\n"); else printf("exist\n"); printf(" ADC #: "); printf("%d\n", sc->adcn); printf(" DAC #: "); printf("%d\n", sc->dacn); printf(" Multi-track converter type: "); if ((sc->cfg->acl & PCIM_ACL_MTC) == 0) { printf("AC'97(SDATA_OUT:"); if (sc->cfg->acl & PCIM_ACL_OMODE) printf("packed"); else printf("split"); printf("|SDATA_IN:"); if (sc->cfg->acl & PCIM_ACL_IMODE) printf("packed"); else printf("split"); printf(")\n"); } else { printf("I2S("); if (sc->cfg->i2s & PCIM_I2S_VOL) printf("with volume, "); if (sc->cfg->i2s & PCIM_I2S_96KHZ) printf("96KHz support, "); switch (sc->cfg->i2s & PCIM_I2S_RES) { case PCIM_I2S_16BIT: printf("16bit resolution, "); break; case PCIM_I2S_18BIT: printf("18bit resolution, "); break; case PCIM_I2S_20BIT: printf("20bit resolution, "); break; case PCIM_I2S_24BIT: printf("24bit resolution, "); break; } printf("ID#0x%x)\n", sc->cfg->i2s & PCIM_I2S_ID); } printf(" S/PDIF(IN/OUT): "); if (sc->cfg->spdif & PCIM_SPDIF_IN) printf("1/"); else printf("0/"); if (sc->cfg->spdif & PCIM_SPDIF_OUT) printf("1 "); else printf("0 "); if (sc->cfg->spdif & (PCIM_SPDIF_IN | PCIM_SPDIF_OUT)) printf("ID# 0x%02x\n", (sc->cfg->spdif & PCIM_SPDIF_ID) >> 2); printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); } static int envy24_init(struct sc_info *sc) { u_int32_t data; #if(0) int rtn; #endif int i; u_int32_t sv, sd; #if(0) device_printf(sc->dev, "envy24_init()\n"); #endif /* reset chip */ envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_RESET | ENVY24_CCS_CTL_NATIVE, 1); DELAY(200); envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_NATIVE, 1); DELAY(200); /* legacy hardware disable */ data = pci_read_config(sc->dev, PCIR_LAC, 2); data |= PCIM_LAC_DISABLE; pci_write_config(sc->dev, PCIR_LAC, data, 2); /* check system configuration */ sc->cfg = NULL; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { /* 1st: search configuration from table */ sv = pci_get_subvendor(sc->dev); sd = pci_get_subdevice(sc->dev); if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { #if(0) device_printf(sc->dev, "Set configuration from table\n"); #endif sc->cfg = &cfg_table[i]; break; } } if (sc->cfg == NULL) { /* 2nd: read configuration from table */ sc->cfg = envy24_rom2cfg(sc); } sc->adcn = ((sc->cfg->scfg & PCIM_SCFG_ADC) >> 2) + 1; sc->dacn = (sc->cfg->scfg & PCIM_SCFG_DAC) + 1; if (1 /* bootverbose */) { envy24_putcfg(sc); } /* set system configuration */ pci_write_config(sc->dev, PCIR_SCFG, sc->cfg->scfg, 1); pci_write_config(sc->dev, PCIR_ACL, sc->cfg->acl, 1); pci_write_config(sc->dev, PCIR_I2S, sc->cfg->i2s, 1); pci_write_config(sc->dev, PCIR_SPDIF, sc->cfg->spdif, 1); envy24_gpiosetmask(sc, sc->cfg->gpiomask); envy24_gpiosetdir(sc, sc->cfg->gpiodir); envy24_gpiowr(sc, sc->cfg->gpiostate); for (i = 0; i < sc->adcn; i++) { sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); sc->cfg->codec->init(sc->adc[i]); } for (i = 0; i < sc->dacn; i++) { sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); sc->cfg->codec->init(sc->dac[i]); } /* initialize DMA buffer */ #if(0) device_printf(sc->dev, "envy24_init(): initialize DMA buffer\n"); #endif if (envy24_dmainit(sc)) return ENOSPC; /* initialize status */ sc->run[0] = sc->run[1] = 0; sc->intr[0] = sc->intr[1] = 0; sc->speed = 0; sc->caps[0].fmtlist = envy24_playfmt; sc->caps[1].fmtlist = envy24_recfmt; /* set channel router */ envy24_route(sc, ENVY24_ROUTE_DAC_1, ENVY24_ROUTE_CLASS_MIX, 0, 0); envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_DMA, 0, 0); /* envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_MIX, 0, 0); */ /* set macro interrupt mask */ data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); envy24_wrcs(sc, ENVY24_CCS_IMASK, data & ~ENVY24_CCS_IMASK_PMT, 1); data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); #if(0) device_printf(sc->dev, "envy24_init(): CCS_IMASK-->0x%02x\n", data); #endif return 0; } static int envy24_alloc_resource(struct sc_info *sc) { /* allocate I/O port resource */ sc->csid = PCIR_CCS; sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->csid, RF_ACTIVE); sc->ddmaid = PCIR_DDMA; sc->ddma = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->ddmaid, RF_ACTIVE); sc->dsid = PCIR_DS; sc->ds = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->dsid, RF_ACTIVE); sc->mtid = PCIR_MT; sc->mt = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->mtid, RF_ACTIVE); if (!sc->cs || !sc->ddma || !sc->ds || !sc->mt) { device_printf(sc->dev, "unable to map IO port space\n"); return ENXIO; } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); sc->ddmat = rman_get_bustag(sc->ddma); sc->ddmah = rman_get_bushandle(sc->ddma); sc->dst = rman_get_bustag(sc->ds); sc->dsh = rman_get_bushandle(sc->ds); sc->mtt = rman_get_bustag(sc->mt); sc->mth = rman_get_bushandle(sc->mt); #if(0) device_printf(sc->dev, "IO port register values\nCCS: 0x%lx\nDDMA: 0x%lx\nDS: 0x%lx\nMT: 0x%lx\n", pci_read_config(sc->dev, PCIR_CCS, 4), pci_read_config(sc->dev, PCIR_DDMA, 4), pci_read_config(sc->dev, PCIR_DS, 4), pci_read_config(sc->dev, PCIR_MT, 4)); #endif /* allocate interrupt resource */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24_intr, sc, &sc->ih)) { device_printf(sc->dev, "unable to map interrupt\n"); return ENXIO; } /* allocate DMA resource */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_ENVY24, /*highaddr*/BUS_SPACE_MAXADDR_ENVY24, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, /*nsegments*/1, /*maxsegsz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->dmat) != 0) { device_printf(sc->dev, "unable to create dma tag\n"); return ENXIO; } return 0; } static int envy24_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; int err = 0; int i; #if(0) device_printf(dev, "envy24_pci_attach()\n"); #endif /* get sc_info data area */ if ((sc = malloc(sizeof(*sc), M_ENVY24, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_envy24 softc"); sc->dev = dev; /* initialize PCI interface */ pci_enable_busmaster(dev); /* allocate resources */ err = envy24_alloc_resource(sc); if (err) { device_printf(dev, "unable to allocate system resources\n"); goto bad; } /* initialize card */ err = envy24_init(sc); if (err) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* set multi track mixer */ mixer_init(dev, &envy24mixer_class, sc); /* set channel information */ err = pcm_register(dev, sc, 5, 2 + sc->adcn); if (err) goto bad; sc->chnum = 0; for (i = 0; i < 5; i++) { pcm_addchan(dev, PCMDIR_PLAY, &envy24chan_class, sc); sc->chnum++; } for (i = 0; i < 2 + sc->adcn; i++) { pcm_addchan(dev, PCMDIR_REC, &envy24chan_class, sc); sc->chnum++; } /* set status iformation */ snprintf(status, SND_STATUSLEN, - "at io 0x%jx:%jd,0x%jx:%jd,0x%jx:%jd,0x%jx:%jd irq %jd", + "port 0x%jx:%jd,0x%jx:%jd,0x%jx:%jd,0x%jx:%jd irq %jd on %s", rman_get_start(sc->cs), rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, rman_get_start(sc->ddma), rman_get_end(sc->ddma) - rman_get_start(sc->ddma) + 1, rman_get_start(sc->ds), rman_get_end(sc->ds) - rman_get_start(sc->ds) + 1, rman_get_start(sc->mt), rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, - rman_get_start(sc->irq)); + rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return 0; bad: if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); envy24_dmafree(sc); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24_cfgfree(sc->cfg); if (sc->cs) bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); if (sc->ddma) bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); if (sc->ds) bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); if (sc->mt) bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_ENVY24); return err; } static int envy24_pci_detach(device_t dev) { struct sc_info *sc; int r; int i; #if(0) device_printf(dev, "envy24_pci_detach()\n"); #endif sc = pcm_getdevinfo(dev); if (sc == NULL) return 0; r = pcm_unregister(dev); if (r) return r; envy24_dmafree(sc); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24_cfgfree(sc->cfg); bus_dma_tag_destroy(sc->dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); snd_mtxfree(sc->lock); free(sc, M_ENVY24); return 0; } static device_method_t envy24_methods[] = { /* Device interface */ DEVMETHOD(device_probe, envy24_pci_probe), DEVMETHOD(device_attach, envy24_pci_attach), DEVMETHOD(device_detach, envy24_pci_detach), { 0, 0 } }; static driver_t envy24_driver = { "pcm", envy24_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_envy24, pci, envy24_driver, 0, 0); MODULE_DEPEND(snd_envy24, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_envy24, snd_spicds, 1, 1, 1); MODULE_VERSION(snd_envy24, 1); diff --git a/sys/dev/sound/pci/envy24ht.c b/sys/dev/sound/pci/envy24ht.c index 6a3ef5afcf59..65c67b84ce53 100644 --- a/sys/dev/sound/pci/envy24ht.c +++ b/sys/dev/sound/pci/envy24ht.c @@ -1,2586 +1,2587 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 Konstantin Dimitrov * Copyright (c) 2001 Katsurajima Naoto * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN 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. * */ /* * Konstantin Dimitrov's thanks list: * * A huge thanks goes to Spas Filipov for his friendship, support and his * generous gift - an 'Audiotrak Prodigy HD2' audio card! I also want to * thank Keiichi Iwasaki and his parents, because they helped Spas to get * the card from Japan! Having hardware sample of Prodigy HD2 made adding * support for that great card very easy and real fun and pleasure. * */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" static MALLOC_DEFINE(M_ENVY24HT, "envy24ht", "envy24ht audio"); /* -------------------------------------------------------------------- */ struct sc_info; #define ENVY24HT_PLAY_CHNUM 8 #define ENVY24HT_REC_CHNUM 2 #define ENVY24HT_PLAY_BUFUNIT (4 /* byte/sample */ * 8 /* channel */) #define ENVY24HT_REC_BUFUNIT (4 /* byte/sample */ * 2 /* channel */) #define ENVY24HT_SAMPLE_NUM 4096 #define ENVY24HT_TIMEOUT 1000 #define ENVY24HT_DEFAULT_FORMAT SND_FORMAT(AFMT_S16_LE, 2, 0) #define ENVY24HT_NAMELEN 32 struct envy24ht_sample { volatile u_int32_t buffer; }; typedef struct envy24ht_sample sample32_t; /* channel registers */ struct sc_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; int dir; unsigned num; /* hw channel number */ /* channel information */ u_int32_t format; u_int32_t speed; u_int32_t blk; /* hw block size(dword) */ /* format conversion structure */ u_int8_t *data; unsigned int size; /* data buffer size(byte) */ int unit; /* sample size(byte) */ unsigned int offset; /* samples number offset */ void (*emldma)(struct sc_chinfo *); /* flags */ int run; }; /* codec interface entrys */ struct codec_entry { void *(*create)(device_t dev, void *devinfo, int dir, int num); void (*destroy)(void *codec); void (*init)(void *codec); void (*reinit)(void *codec); void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); void (*setrate)(void *codec, int which, int rate); }; /* system configuration information */ struct cfg_info { char *name; u_int16_t subvendor, subdevice; u_int8_t scfg, acl, i2s, spdif; u_int32_t gpiomask, gpiostate, gpiodir; u_int32_t cdti, cclk, cs; u_int8_t cif, type, free; struct codec_entry *codec; }; /* device private data */ struct sc_info { device_t dev; struct mtx *lock; /* Control/Status registor */ struct resource *cs; int csid; bus_space_tag_t cst; bus_space_handle_t csh; /* MultiTrack registor */ struct resource *mt; int mtid; bus_space_tag_t mtt; bus_space_handle_t mth; /* DMA tag */ bus_dma_tag_t dmat; /* IRQ resource */ struct resource *irq; int irqid; void *ih; /* system configuration data */ struct cfg_info *cfg; /* ADC/DAC number and info */ int adcn, dacn; void *adc[4], *dac[4]; /* mixer control data */ u_int32_t src; u_int8_t left[ENVY24HT_CHAN_NUM]; u_int8_t right[ENVY24HT_CHAN_NUM]; /* Play/Record DMA fifo */ sample32_t *pbuf; sample32_t *rbuf; u_int32_t psize, rsize; /* DMA buffer size(byte) */ u_int16_t blk[2]; /* transfer check blocksize(dword) */ bus_dmamap_t pmap, rmap; bus_addr_t paddr, raddr; /* current status */ u_int32_t speed; int run[2]; u_int16_t intr[2]; struct pcmchan_caps caps[2]; /* channel info table */ unsigned chnum; struct sc_chinfo chan[11]; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* DMA emulator */ static void envy24ht_p8u(struct sc_chinfo *); static void envy24ht_p16sl(struct sc_chinfo *); static void envy24ht_p32sl(struct sc_chinfo *); static void envy24ht_r16sl(struct sc_chinfo *); static void envy24ht_r32sl(struct sc_chinfo *); /* channel interface */ static void *envy24htchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int envy24htchan_setformat(kobj_t, void *, u_int32_t); static u_int32_t envy24htchan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t envy24htchan_setblocksize(kobj_t, void *, u_int32_t); static int envy24htchan_trigger(kobj_t, void *, int); static u_int32_t envy24htchan_getptr(kobj_t, void *); static struct pcmchan_caps *envy24htchan_getcaps(kobj_t, void *); /* mixer interface */ static int envy24htmixer_init(struct snd_mixer *); static int envy24htmixer_reinit(struct snd_mixer *); static int envy24htmixer_uninit(struct snd_mixer *); static int envy24htmixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); static u_int32_t envy24htmixer_setrecsrc(struct snd_mixer *, u_int32_t); /* SPI codec access interface */ static void *envy24ht_spi_create(device_t, void *, int, int); static void envy24ht_spi_destroy(void *); static void envy24ht_spi_init(void *); static void envy24ht_spi_reinit(void *); static void envy24ht_spi_setvolume(void *, int, unsigned int, unsigned int); /* -------------------------------------------------------------------- */ /* system constant tables */ /* API -> hardware channel map */ static unsigned envy24ht_chanmap[ENVY24HT_CHAN_NUM] = { ENVY24HT_CHAN_PLAY_DAC1, /* 1 */ ENVY24HT_CHAN_PLAY_DAC2, /* 2 */ ENVY24HT_CHAN_PLAY_DAC3, /* 3 */ ENVY24HT_CHAN_PLAY_DAC4, /* 4 */ ENVY24HT_CHAN_PLAY_SPDIF, /* 0 */ ENVY24HT_CHAN_REC_MIX, /* 5 */ ENVY24HT_CHAN_REC_SPDIF, /* 6 */ ENVY24HT_CHAN_REC_ADC1, /* 7 */ ENVY24HT_CHAN_REC_ADC2, /* 8 */ ENVY24HT_CHAN_REC_ADC3, /* 9 */ ENVY24HT_CHAN_REC_ADC4, /* 10 */ }; /* mixer -> API channel map. see above */ static int envy24ht_mixmap[] = { -1, /* Master output level. It is depend on codec support */ -1, /* Treble level of all output channels */ -1, /* Bass level of all output channels */ -1, /* Volume of synthesier input */ 0, /* Output level for the audio device */ -1, /* Output level for the PC speaker */ 7, /* line in jack */ -1, /* microphone jack */ -1, /* CD audio input */ -1, /* Recording monitor */ 1, /* alternative codec */ -1, /* global recording level */ -1, /* Input gain */ -1, /* Output gain */ 8, /* Input source 1 */ 9, /* Input source 2 */ 10, /* Input source 3 */ 6, /* Digital (input) 1 */ -1, /* Digital (input) 2 */ -1, /* Digital (input) 3 */ -1, /* Phone input */ -1, /* Phone output */ -1, /* Video/TV (audio) in */ -1, /* Radio in */ -1, /* Monitor volume */ }; /* variable rate audio */ static u_int32_t envy24ht_speed[] = { 192000, 176400, 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000, 0 }; /* known boards configuration */ static struct codec_entry spi_codec = { envy24ht_spi_create, envy24ht_spi_destroy, envy24ht_spi_init, envy24ht_spi_reinit, envy24ht_spi_setvolume, NULL, /* setrate */ }; static struct cfg_info cfg_table[] = { { "Envy24HT audio (Terratec Aureon 7.1 Space)", 0x153b, 0x1145, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec Aureon 5.1 Sky)", 0x153b, 0x1147, 0x0a, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec Aureon 7.1 Universe)", 0x153b, 0x1153, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1)", 0x4933, 0x4553, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec PHASE 28)", 0x153b, 0x1149, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT-S audio (Terratec PHASE 22)", 0x153b, 0x1150, 0x10, 0x80, 0xf0, 0xc3, 0x7ffbc7, 0x7fffff, 0x438, 0x10, 0x20, 0x400, 0x01, 0x00, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1 LT)", 0x3132, 0x4154, 0x4b, 0x80, 0xfc, 0xc3, 0x7ff8ff, 0x7fffff, 0x700, 0x400, 0x200, 0x100, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1 XT)", 0x3136, 0x4154, 0x4b, 0x80, 0xfc, 0xc3, 0x7ff8ff, 0x7fffff, 0x700, 0x400, 0x200, 0x100, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (M-Audio Revolution 7.1)", 0x1412, 0x3630, 0x43, 0x80, 0xf8, 0xc1, 0x3fff85, 0x400072, 0x4000fa, 0x08, 0x02, 0x20, 0x00, 0x04, 0, &spi_codec, }, { "Envy24GT audio (M-Audio Revolution 5.1)", 0x1412, 0x3631, 0x42, 0x80, 0xf8, 0xc1, 0x3fff05, 0x4000f0, 0x4000fa, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (M-Audio Audiophile 192)", 0x1412, 0x3632, 0x68, 0x80, 0xf8, 0xc3, 0x45, 0x4000b5, 0x7fffba, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy HD2)", 0x3137, 0x4154, 0x68, 0x80, 0x78, 0xc3, 0xfff8ff, 0x200700, 0xdfffff, 0x400, 0x200, 0x100, 0x00, 0x05, 0, &spi_codec, }, { "Envy24HT audio (ESI Juli@)", 0x3031, 0x4553, 0x20, 0x80, 0xf8, 0xc3, 0x7fff9f, 0x8016, 0x7fff9f, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT-S audio (Terrasoniq TS22PCI)", 0x153b, 0x117b, 0x10, 0x80, 0xf0, 0xc3, 0x7ffbc7, 0x7fffff, 0x438, 0x10, 0x20, 0x400, 0x01, 0x00, 0, &spi_codec, }, { "Envy24HT audio (Generic)", 0, 0, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, /* default codec routines */ } }; static u_int32_t envy24ht_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24ht_reccaps = {8000, 96000, envy24ht_recfmt, 0}; static u_int32_t envy24ht_playfmt[] = { SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24ht_playcaps = {8000, 192000, envy24ht_playfmt, 0}; struct envy24ht_emldma { u_int32_t format; void (*emldma)(struct sc_chinfo *); int unit; }; static struct envy24ht_emldma envy24ht_pemltab[] = { {SND_FORMAT(AFMT_U8, 2, 0), envy24ht_p8u, 2}, {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24ht_p16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24ht_p32sl, 8}, {0, NULL, 0} }; static struct envy24ht_emldma envy24ht_remltab[] = { {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24ht_r16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24ht_r32sl, 8}, {0, NULL, 0} }; /* -------------------------------------------------------------------- */ /* common routines */ static u_int32_t envy24ht_rdcs(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->cst, sc->csh, regno); case 2: return bus_space_read_2(sc->cst, sc->csh, regno); case 4: return bus_space_read_4(sc->cst, sc->csh, regno); default: return 0xffffffff; } } static void envy24ht_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->cst, sc->csh, regno, data); break; case 2: bus_space_write_2(sc->cst, sc->csh, regno, data); break; case 4: bus_space_write_4(sc->cst, sc->csh, regno, data); break; } } static u_int32_t envy24ht_rdmt(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->mtt, sc->mth, regno); case 2: return bus_space_read_2(sc->mtt, sc->mth, regno); case 4: return bus_space_read_4(sc->mtt, sc->mth, regno); default: return 0xffffffff; } } static void envy24ht_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->mtt, sc->mth, regno, data); break; case 2: bus_space_write_2(sc->mtt, sc->mth, regno, data); break; case 4: bus_space_write_4(sc->mtt, sc->mth, regno, data); break; } } /* -------------------------------------------------------------------- */ /* I2C port/E2PROM access routines */ static int envy24ht_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_RD, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CDATA, 1); #if(0) device_printf(sc->dev, "envy24ht_rdi2c(): return 0x%x\n", data); #endif return (int)data; } static int envy24ht_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) { u_int32_t tmp; int i; #if(0) device_printf(sc->dev, "envy24ht_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24HT_TIMEOUT; i++) { tmp = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((tmp & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDATA, data, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_WR, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } return 0; } static int envy24ht_rdrom(struct sc_info *sc, u_int32_t addr) { u_int32_t data; #if(0) device_printf(sc->dev, "envy24ht_rdrom(sc, 0x%02x)\n", addr); #endif data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_ROM) == 0) { #if(0) device_printf(sc->dev, "envy24ht_rdrom(): E2PROM not presented\n"); #endif return -1; } return envy24ht_rdi2c(sc, ENVY24HT_CCS_I2CDEV_ROM, addr); } static struct cfg_info * envy24ht_rom2cfg(struct sc_info *sc) { struct cfg_info *buff; int size; int i; #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(sc)\n"); #endif size = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SIZE); if ((size < ENVY24HT_E2PROM_GPIOSTATE + 3) || (size == 0x78)) { #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(): ENVY24HT_E2PROM_SIZE-->%d\n", size); #endif buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) { #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(): malloc()\n"); #endif return NULL; } buff->free = 1; /* no valid e2prom, using default values */ buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); buff->scfg = 0x0b; buff->acl = 0x80; buff->i2s = 0xfc; buff->spdif = 0xc3; buff->gpiomask = 0x21efff; buff->gpiostate = 0x7fffff; buff->gpiodir = 0x5e1000; buff->cdti = 0x40000; buff->cclk = 0x80000; buff->cs = 0x1000; buff->cif = 0x00; buff->type = 0x02; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) if (cfg_table[i].subvendor == buff->subvendor && cfg_table[i].subdevice == buff->subdevice) break; buff->name = cfg_table[i].name; buff->codec = cfg_table[i].codec; return buff; #if 0 return NULL; #endif } buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) { #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(): malloc()\n"); #endif return NULL; } buff->free = 1; buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); buff->scfg = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SCFG); buff->acl = envy24ht_rdrom(sc, ENVY24HT_E2PROM_ACL); buff->i2s = envy24ht_rdrom(sc, ENVY24HT_E2PROM_I2S); buff->spdif = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SPDIF); buff->gpiomask = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK) | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 1) << 8 | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 2) << 16; buff->gpiostate = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE) | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 1) << 8 | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 2) << 16; buff->gpiodir = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR) | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 1) << 8 | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 2) << 16; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) if (cfg_table[i].subvendor == buff->subvendor && cfg_table[i].subdevice == buff->subdevice) break; buff->name = cfg_table[i].name; buff->codec = cfg_table[i].codec; return buff; } static void envy24ht_cfgfree(struct cfg_info *cfg) { if (cfg == NULL) return; if (cfg->free) free(cfg, M_ENVY24HT); return; } /* -------------------------------------------------------------------- */ /* AC'97 codec access routines */ #if 0 static int envy24ht_coldcd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_coldcd()\n"); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD, 1); DELAY(10); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if (data & ENVY24HT_MT_AC97CMD_RDY) { return 0; } } return -1; } static int envy24ht_slavecd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_slavecd()\n"); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD | ENVY24HT_MT_AC97CMD_WRM, 1); DELAY(10); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if (data & ENVY24HT_MT_AC97CMD_RDY) { return 0; } } return -1; } static int envy24ht_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_rdcd(obj, sc, 0x%02x)\n", regno); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (u_int32_t)regno, 1); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_RD, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if ((data & ENVY24HT_MT_AC97CMD_RD) == 0) break; } data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97DLO, 2); #if(0) device_printf(sc->dev, "envy24ht_rdcd(): return 0x%x\n", data); #endif return (int)data; } static int envy24ht_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t cmd; int i; #if(0) device_printf(sc->dev, "envy24ht_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (u_int32_t)regno, 1); envy24ht_wrmt(sc, ENVY24HT_MT_AC97DLO, (u_int32_t)data, 2); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_WR, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { cmd = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if ((cmd & ENVY24HT_MT_AC97CMD_WR) == 0) break; } return 0; } static kobj_method_t envy24ht_ac97_methods[] = { KOBJMETHOD(ac97_read, envy24ht_rdcd), KOBJMETHOD(ac97_write, envy24ht_wrcd), KOBJMETHOD_END }; AC97_DECLARE(envy24ht_ac97); #endif /* -------------------------------------------------------------------- */ /* GPIO access routines */ static u_int32_t envy24ht_gpiord(struct sc_info *sc) { if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) return envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2); else return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HDATA, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2)); } static void envy24ht_gpiowr(struct sc_info *sc, u_int32_t data) { #if(0) device_printf(sc->dev, "envy24ht_gpiowr(sc, 0x%02x)\n", data & 0x7FFFFF); return; #endif envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LDATA, data, 2); if (sc->cfg->subdevice != 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HDATA, data >> 16, 1); return; } #if 0 static u_int32_t envy24ht_gpiogetmask(struct sc_info *sc) { return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HMASK, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LMASK, 2)); } #endif static void envy24ht_gpiosetmask(struct sc_info *sc, u_int32_t mask) { envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LMASK, mask, 2); if (sc->cfg->subdevice != 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HMASK, mask >> 16, 1); return; } #if 0 static u_int32_t envy24ht_gpiogetdir(struct sc_info *sc) { return envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, 4); } #endif static void envy24ht_gpiosetdir(struct sc_info *sc, u_int32_t dir) { if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 2); else envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 4); return; } /* -------------------------------------------------------------------- */ /* SPI codec access interface routine */ struct envy24ht_spi_codec { struct spicds_info *info; struct sc_info *parent; int dir; int num; int cs, cclk, cdti; }; static void envy24ht_spi_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) { u_int32_t data = 0; struct envy24ht_spi_codec *ptr = codec; #if(0) device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); #endif data = envy24ht_gpiord(ptr->parent); data &= ~(ptr->cs | ptr->cclk | ptr->cdti); if (cs) data += ptr->cs; if (cclk) data += ptr->cclk; if (cdti) data += ptr->cdti; envy24ht_gpiowr(ptr->parent, data); return; } static void * envy24ht_spi_create(device_t dev, void *info, int dir, int num) { struct sc_info *sc = info; struct envy24ht_spi_codec *buff = NULL; #if(0) device_printf(sc->dev, "envy24ht_spi_create(dev, sc, %d, %d)\n", dir, num); #endif buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) return NULL; if (dir == PCMDIR_REC && sc->adc[num] != NULL) buff->info = ((struct envy24ht_spi_codec *)sc->adc[num])->info; else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) buff->info = ((struct envy24ht_spi_codec *)sc->dac[num])->info; else buff->info = spicds_create(dev, buff, num, envy24ht_spi_ctl); if (buff->info == NULL) { free(buff, M_ENVY24HT); return NULL; } buff->parent = sc; buff->dir = dir; buff->num = num; return (void *)buff; } static void envy24ht_spi_destroy(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spi_destroy()\n"); #endif if (ptr->dir == PCMDIR_PLAY) { if (ptr->parent->dac[ptr->num] != NULL) spicds_destroy(ptr->info); } else { if (ptr->parent->adc[ptr->num] != NULL) spicds_destroy(ptr->info); } free(codec, M_ENVY24HT); } static void envy24ht_spi_init(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spicds_init()\n"); #endif ptr->cs = ptr->parent->cfg->cs; ptr->cclk = ptr->parent->cfg->cclk; ptr->cdti = ptr->parent->cfg->cdti; spicds_settype(ptr->info, ptr->parent->cfg->type); spicds_setcif(ptr->info, ptr->parent->cfg->cif); if (ptr->parent->cfg->type == SPICDS_TYPE_AK4524 || \ ptr->parent->cfg->type == SPICDS_TYPE_AK4528) { spicds_setformat(ptr->info, AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); spicds_setdvc(ptr->info, AK452X_DVC_DEMOFF); } /* for the time being, init only first codec */ if (ptr->num == 0) spicds_init(ptr->info); } static void envy24ht_spi_reinit(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spi_reinit()\n"); #endif spicds_reinit(ptr->info); } static void envy24ht_spi_setvolume(void *codec, int dir, unsigned int left, unsigned int right) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spi_set()\n"); #endif spicds_set(ptr->info, dir, left, right); } /* -------------------------------------------------------------------- */ /* hardware access routeines */ static struct { u_int32_t speed; u_int32_t code; } envy24ht_speedtab[] = { {48000, ENVY24HT_MT_RATE_48000}, {24000, ENVY24HT_MT_RATE_24000}, {12000, ENVY24HT_MT_RATE_12000}, {9600, ENVY24HT_MT_RATE_9600}, {32000, ENVY24HT_MT_RATE_32000}, {16000, ENVY24HT_MT_RATE_16000}, {8000, ENVY24HT_MT_RATE_8000}, {96000, ENVY24HT_MT_RATE_96000}, {192000, ENVY24HT_MT_RATE_192000}, {64000, ENVY24HT_MT_RATE_64000}, {44100, ENVY24HT_MT_RATE_44100}, {22050, ENVY24HT_MT_RATE_22050}, {11025, ENVY24HT_MT_RATE_11025}, {88200, ENVY24HT_MT_RATE_88200}, {176400, ENVY24HT_MT_RATE_176400}, {0, 0x10} }; static u_int32_t envy24ht_setspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t code, i2sfmt; int i = 0; #if(0) device_printf(sc->dev, "envy24ht_setspeed(sc, %d)\n", speed); if (speed == 0) { code = ENVY24HT_MT_RATE_SPDIF; /* external master clock */ envy24ht_slavecd(sc); } else { #endif for (i = 0; envy24ht_speedtab[i].speed != 0; i++) { if (envy24ht_speedtab[i].speed == speed) break; } code = envy24ht_speedtab[i].code; #if 0 } device_printf(sc->dev, "envy24ht_setspeed(): speed %d/code 0x%04x\n", envy24ht_speedtab[i].speed, code); #endif if (code < 0x10) { envy24ht_wrmt(sc, ENVY24HT_MT_RATE, code, 1); if ((((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_XIN2) == 0x00) && (code == ENVY24HT_MT_RATE_192000)) || \ (code == ENVY24HT_MT_RATE_176400)) { i2sfmt = envy24ht_rdmt(sc, ENVY24HT_MT_I2S, 1); i2sfmt |= ENVY24HT_MT_I2S_MLR128; envy24ht_wrmt(sc, ENVY24HT_MT_I2S, i2sfmt, 1); } else { i2sfmt = envy24ht_rdmt(sc, ENVY24HT_MT_I2S, 1); i2sfmt &= ~ENVY24HT_MT_I2S_MLR128; envy24ht_wrmt(sc, ENVY24HT_MT_I2S, i2sfmt, 1); } code = envy24ht_rdmt(sc, ENVY24HT_MT_RATE, 1); code &= ENVY24HT_MT_RATE_MASK; for (i = 0; envy24ht_speedtab[i].code < 0x10; i++) { if (envy24ht_speedtab[i].code == code) break; } speed = envy24ht_speedtab[i].speed; } else speed = 0; #if(0) device_printf(sc->dev, "envy24ht_setspeed(): return %d\n", speed); #endif return speed; } static void envy24ht_setvolume(struct sc_info *sc, unsigned ch) { #if(0) device_printf(sc->dev, "envy24ht_setvolume(sc, %d)\n", ch); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, 0x7f00 | sc->left[ch], 2); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); #endif } static void envy24ht_mutevolume(struct sc_info *sc, unsigned ch) { #if 0 u_int32_t vol; device_printf(sc->dev, "envy24ht_mutevolume(sc, %d)\n", ch); vol = ENVY24HT_VOL_MUTE << 8 | ENVY24HT_VOL_MUTE; envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); #endif } static u_int32_t envy24ht_gethwptr(struct sc_info *sc, int dir) { int unit, regno; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24ht_gethwptr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { rtn = sc->psize / 4; unit = ENVY24HT_PLAY_BUFUNIT / 4; regno = ENVY24HT_MT_PCNT; } else { rtn = sc->rsize / 4; unit = ENVY24HT_REC_BUFUNIT / 4; regno = ENVY24HT_MT_RCNT; } ptr = envy24ht_rdmt(sc, regno, 2); rtn -= (ptr + 1); rtn /= unit; #if(0) device_printf(sc->dev, "envy24ht_gethwptr(): return %d\n", rtn); #endif return rtn; } static void envy24ht_updintr(struct sc_info *sc, int dir) { int regintr; u_int32_t mask, intr; u_int32_t cnt; u_int16_t blk; #if(0) device_printf(sc->dev, "envy24ht_updintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { blk = sc->blk[0]; regintr = ENVY24HT_MT_PTERM; mask = ~ENVY24HT_MT_INT_PMASK; } else { blk = sc->blk[1]; regintr = ENVY24HT_MT_RTERM; mask = ~ENVY24HT_MT_INT_RMASK; } cnt = blk - 1; #if(0) device_printf(sc->dev, "envy24ht_updintr():blk = %d, cnt = %d\n", blk, cnt); #endif envy24ht_wrmt(sc, regintr, cnt, 2); intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); #if(0) device_printf(sc->dev, "envy24ht_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); #endif envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, intr & mask, 1); #if(0) device_printf(sc->dev, "envy24ht_updintr():INT-->0x%02x\n", envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1)); #endif return; } #if 0 static void envy24ht_maskintr(struct sc_info *sc, int dir) { u_int32_t mask, intr; #if(0) device_printf(sc->dev, "envy24ht_maskintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) mask = ENVY24HT_MT_INT_PMASK; else mask = ENVY24HT_MT_INT_RMASK; intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT, intr | mask, 1); return; } #endif static int envy24ht_checkintr(struct sc_info *sc, int dir) { u_int32_t mask, stat, intr, rtn; #if(0) device_printf(sc->dev, "envy24ht_checkintr(sc, %d)\n", dir); #endif intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_STAT, 1); if (dir == PCMDIR_PLAY) { if ((rtn = intr & ENVY24HT_MT_INT_PSTAT) != 0) { mask = ~ENVY24HT_MT_INT_RSTAT; envy24ht_wrmt(sc, 0x1a, 0x01, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_PSTAT | 0x08, 1); stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_PMASK, 1); } } else { if ((rtn = intr & ENVY24HT_MT_INT_RSTAT) != 0) { mask = ~ENVY24HT_MT_INT_PSTAT; #if 0 stat = ENVY24HT_MT_INT_RSTAT | ENVY24HT_MT_INT_RMASK; #endif envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_RSTAT, 1); stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_RMASK, 1); } } return rtn; } static void envy24ht_start(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24ht_start(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ENVY24HT_MT_PCTL_PSTART; else sw = ENVY24HT_MT_PCTL_RSTART; stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat | sw, 1); #if(0) DELAY(100); device_printf(sc->dev, "PADDR:0x%08x\n", envy24ht_rdmt(sc, ENVY24HT_MT_PADDR, 4)); device_printf(sc->dev, "PCNT:%ld\n", envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2)); #endif return; } static void envy24ht_stop(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24ht_stop(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ~ENVY24HT_MT_PCTL_PSTART; else sw = ~ENVY24HT_MT_PCTL_RSTART; stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat & sw, 1); return; } #if 0 static int envy24ht_route(struct sc_info *sc, int dac, int class, int adc, int rev) { return 0; } #endif /* -------------------------------------------------------------------- */ /* buffer copy routines */ static void envy24ht_p32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 8; dmabuf = ch->parent->pbuf; data = (u_int32_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 4; dst = src / 2 + ch->offset; ssize = ch->size / 4; dsize = ch->size / 8; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = data[src]; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24ht_p16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; #if(0) device_printf(ch->parent->dev, "envy24ht_p16sl()\n"); #endif length = sndbuf_getready(ch->buffer) / 4; dmabuf = ch->parent->pbuf; data = (u_int16_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 2; dst = src / 2 + ch->offset; ssize = ch->size / 2; dsize = ch->size / 4; slot = ch->num * 2; #if(0) device_printf(ch->parent->dev, "envy24ht_p16sl():%lu-->%lu(%lu)\n", src, dst, length); #endif for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16; #if(0) if (i < 16) { printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot]); printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1]); } #endif dst++; dst %= dsize; src += 2; src %= ssize; } #if(0) printf("\n"); #endif return; } static void envy24ht_p8u(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int8_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 2; dmabuf = ch->parent->pbuf; data = (u_int8_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer); dst = src / 2 + ch->offset; ssize = ch->size; dsize = ch->size / 4; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24ht_r32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 8; dmabuf = ch->parent->rbuf; data = (u_int32_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 4; src = dst / 2 + ch->offset; dsize = ch->size / 4; ssize = ch->size / 8; slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } static void envy24ht_r16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 4; dmabuf = ch->parent->rbuf; data = (u_int16_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 2; src = dst / 2 + ch->offset; dsize = ch->size / 2; ssize = ch->size / 8; slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * envy24htchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = (struct sc_info *)devinfo; struct sc_chinfo *ch; unsigned num; #if(0) device_printf(sc->dev, "envy24htchan_init(obj, devinfo, b, c, %d)\n", dir); #endif snd_mtxlock(sc->lock); #if 0 if ((sc->chnum > ENVY24HT_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || (sc->chnum < ENVY24HT_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { snd_mtxunlock(sc->lock); return NULL; } #endif num = sc->chnum; ch = &sc->chan[num]; ch->size = 8 * ENVY24HT_SAMPLE_NUM; ch->data = malloc(ch->size, M_ENVY24HT, M_NOWAIT); if (ch->data == NULL) { ch->size = 0; ch = NULL; } else { ch->buffer = b; ch->channel = c; ch->parent = sc; ch->dir = dir; /* set channel map */ ch->num = envy24ht_chanmap[num]; snd_mtxunlock(sc->lock); sndbuf_setup(ch->buffer, ch->data, ch->size); snd_mtxlock(sc->lock); /* these 2 values are dummy */ ch->unit = 4; ch->blk = 10240; } snd_mtxunlock(sc->lock); return ch; } static int envy24htchan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; #if(0) device_printf(sc->dev, "envy24htchan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_ENVY24HT); ch->data = NULL; } snd_mtxunlock(sc->lock); return 0; } static int envy24htchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct envy24ht_emldma *emltab; /* unsigned int bcnt, bsize; */ int i; #if(0) device_printf(sc->dev, "envy24htchan_setformat(obj, data, 0x%08x)\n", format); #endif snd_mtxlock(sc->lock); /* check and get format related information */ if (ch->dir == PCMDIR_PLAY) emltab = envy24ht_pemltab; else emltab = envy24ht_remltab; if (emltab == NULL) { snd_mtxunlock(sc->lock); return -1; } for (i = 0; emltab[i].format != 0; i++) if (emltab[i].format == format) break; if (emltab[i].format == 0) { snd_mtxunlock(sc->lock); return -1; } /* set format information */ ch->format = format; ch->emldma = emltab[i].emldma; if (ch->unit > emltab[i].unit) ch->blk *= ch->unit / emltab[i].unit; else ch->blk /= emltab[i].unit / ch->unit; ch->unit = emltab[i].unit; /* set channel buffer information */ ch->size = ch->unit * ENVY24HT_SAMPLE_NUM; #if 0 if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24HT_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); #endif snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24htchan_setformat(): return 0x%08x\n", 0); #endif return 0; } /* IMPLEMENT NOTICE: In this driver, setspeed function only do setting of speed information value. And real hardware speed setting is done at start triggered(see envy24htchan_trigger()). So, at this function is called, any value that ENVY24 can use is able to set. But, at start triggerd, some other channel is running, and that channel's speed isn't same with, then trigger function will fail. */ static u_int32_t envy24htchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; u_int32_t val, prev; int i; #if(0) device_printf(ch->parent->dev, "envy24htchan_setspeed(obj, data, %d)\n", speed); #endif prev = 0x7fffffff; for (i = 0; (val = envy24ht_speed[i]) != 0; i++) { if (abs(val - speed) < abs(prev - speed)) prev = val; else break; } ch->speed = prev; #if(0) device_printf(ch->parent->dev, "envy24htchan_setspeed(): return %d\n", ch->speed); #endif return ch->speed; } static u_int32_t envy24htchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; /* struct sc_info *sc = ch->parent; */ u_int32_t size, prev; unsigned int bcnt, bsize; #if(0) device_printf(sc->dev, "envy24htchan_setblocksize(obj, data, %d)\n", blocksize); #endif prev = 0x7fffffff; /* snd_mtxlock(sc->lock); */ for (size = ch->size / 2; size > 0; size /= 2) { if (abs(size - blocksize) < abs(prev - blocksize)) prev = size; else break; } ch->blk = prev / ch->unit; if (ch->dir == PCMDIR_PLAY) ch->blk *= ENVY24HT_PLAY_BUFUNIT / 4; else ch->blk *= ENVY24HT_REC_BUFUNIT / 4; /* set channel buffer information */ /* ch->size = ch->unit * ENVY24HT_SAMPLE_NUM; */ if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24HT_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); /* snd_mtxunlock(sc->lock); */ #if(0) device_printf(sc->dev, "envy24htchan_setblocksize(): return %d\n", prev); #endif return prev; } /* semantic note: must start at beginning of buffer */ static int envy24htchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr; int slot; int error = 0; #if 0 int i; device_printf(sc->dev, "envy24htchan_trigger(obj, data, %d)\n", go); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) slot = 0; else slot = 1; switch (go) { case PCMTRIG_START: #if(0) device_printf(sc->dev, "envy24htchan_trigger(): start\n"); #endif /* check or set channel speed */ if (sc->run[0] == 0 && sc->run[1] == 0) { sc->speed = envy24ht_setspeed(sc, ch->speed); sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; } else if (ch->speed != 0 && ch->speed != sc->speed) { error = -1; goto fail; } if (ch->speed == 0) ch->channel->speed = sc->speed; /* start or enable channel */ sc->run[slot]++; if (sc->run[slot] == 1) { /* first channel */ ch->offset = 0; sc->blk[slot] = ch->blk; } else { ptr = envy24ht_gethwptr(sc, ch->dir); ch->offset = ((ptr / ch->blk + 1) * ch->blk % (ch->size / 4)) * 4 / ch->unit; if (ch->blk < sc->blk[slot]) sc->blk[slot] = ch->blk; } if (ch->dir == PCMDIR_PLAY) { ch->emldma(ch); envy24ht_setvolume(sc, ch->num); } envy24ht_updintr(sc, ch->dir); if (sc->run[slot] == 1) envy24ht_start(sc, ch->dir); ch->run = 1; break; case PCMTRIG_EMLDMAWR: #if(0) device_printf(sc->dev, "envy24htchan_trigger(): emldmawr\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_EMLDMARD: #if(0) device_printf(sc->dev, "envy24htchan_trigger(): emldmard\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_ABORT: if (ch->run) { #if(0) device_printf(sc->dev, "envy24htchan_trigger(): abort\n"); #endif ch->run = 0; sc->run[slot]--; if (ch->dir == PCMDIR_PLAY) envy24ht_mutevolume(sc, ch->num); if (sc->run[slot] == 0) { envy24ht_stop(sc, ch->dir); sc->intr[slot] = 0; } /* else if (ch->blk == sc->blk[slot]) { sc->blk[slot] = ENVY24HT_SAMPLE_NUM / 2; for (i = 0; i < ENVY24HT_CHAN_NUM; i++) { if (sc->chan[i].dir == ch->dir && sc->chan[i].run == 1 && sc->chan[i].blk < sc->blk[slot]) sc->blk[slot] = sc->chan[i].blk; } if (ch->blk != sc->blk[slot]) envy24ht_updintr(sc, ch->dir); }*/ } break; } fail: snd_mtxunlock(sc->lock); return (error); } static u_int32_t envy24htchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24htchan_getptr()\n"); #endif snd_mtxlock(sc->lock); ptr = envy24ht_gethwptr(sc, ch->dir); rtn = ptr * ch->unit; snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24htchan_getptr(): return %d\n", rtn); #endif return rtn; } static struct pcmchan_caps * envy24htchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct pcmchan_caps *rtn; #if(0) device_printf(sc->dev, "envy24htchan_getcaps()\n"); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (sc->run[0] == 0) rtn = &envy24ht_playcaps; else rtn = &sc->caps[0]; } else { if (sc->run[1] == 0) rtn = &envy24ht_reccaps; else rtn = &sc->caps[1]; } snd_mtxunlock(sc->lock); return rtn; } static kobj_method_t envy24htchan_methods[] = { KOBJMETHOD(channel_init, envy24htchan_init), KOBJMETHOD(channel_free, envy24htchan_free), KOBJMETHOD(channel_setformat, envy24htchan_setformat), KOBJMETHOD(channel_setspeed, envy24htchan_setspeed), KOBJMETHOD(channel_setblocksize, envy24htchan_setblocksize), KOBJMETHOD(channel_trigger, envy24htchan_trigger), KOBJMETHOD(channel_getptr, envy24htchan_getptr), KOBJMETHOD(channel_getcaps, envy24htchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(envy24htchan); /* -------------------------------------------------------------------- */ /* mixer interface */ static int envy24htmixer_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); #if(0) device_printf(sc->dev, "envy24htmixer_init()\n"); #endif if (sc == NULL) return -1; /* set volume control rate */ snd_mtxlock(sc->lock); #if 0 envy24ht_wrmt(sc, ENVY24HT_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ #endif pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, ENVY24HT_MIX_MASK); mix_setrecdevs(m, ENVY24HT_MIX_REC_MASK); snd_mtxunlock(sc->lock); return 0; } static int envy24htmixer_reinit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24htmixer_reinit()\n"); #endif return 0; } static int envy24htmixer_uninit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24htmixer_uninit()\n"); #endif return 0; } static int envy24htmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24ht_mixmap[dev]; int hwch; int i; if (sc == NULL) return -1; if (dev == 0 && sc->cfg->codec->setvolume == NULL) return -1; if (dev != 0 && ch == -1) return -1; hwch = envy24ht_chanmap[ch]; #if(0) device_printf(sc->dev, "envy24htmixer_set(m, %d, %d, %d)\n", dev, left, right); #endif snd_mtxlock(sc->lock); if (dev == 0) { for (i = 0; i < sc->dacn; i++) { sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); } } else { /* set volume value for hardware */ if ((sc->left[hwch] = 100 - left) > ENVY24HT_VOL_MIN) sc->left[hwch] = ENVY24HT_VOL_MUTE; if ((sc->right[hwch] = 100 - right) > ENVY24HT_VOL_MIN) sc->right[hwch] = ENVY24HT_VOL_MUTE; /* set volume for record channel and running play channel */ if (hwch > ENVY24HT_CHAN_PLAY_SPDIF || sc->chan[ch].run) envy24ht_setvolume(sc, hwch); } snd_mtxunlock(sc->lock); return right << 8 | left; } static u_int32_t envy24htmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24ht_mixmap[src]; #if(0) device_printf(sc->dev, "envy24htmixer_setrecsrc(m, %d)\n", src); #endif if (ch > ENVY24HT_CHAN_PLAY_SPDIF) sc->src = ch; return src; } static kobj_method_t envy24htmixer_methods[] = { KOBJMETHOD(mixer_init, envy24htmixer_init), KOBJMETHOD(mixer_reinit, envy24htmixer_reinit), KOBJMETHOD(mixer_uninit, envy24htmixer_uninit), KOBJMETHOD(mixer_set, envy24htmixer_set), KOBJMETHOD(mixer_setrecsrc, envy24htmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(envy24htmixer); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void envy24ht_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; u_int32_t ptr, dsize, feed; int i; #if(0) device_printf(sc->dev, "envy24ht_intr()\n"); #endif snd_mtxlock(sc->lock); if (envy24ht_checkintr(sc, PCMDIR_PLAY)) { #if(0) device_printf(sc->dev, "envy24ht_intr(): play\n"); #endif dsize = sc->psize / 4; ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2) - 1; #if(0) device_printf(sc->dev, "envy24ht_intr(): ptr = %d-->", ptr); #endif ptr -= ptr % sc->blk[0]; feed = (ptr + dsize - sc->intr[0]) % dsize; #if(0) printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); #endif for (i = ENVY24HT_CHAN_PLAY_DAC1; i <= ENVY24HT_CHAN_PLAY_SPDIF; i++) { ch = &sc->chan[i]; #if(0) if (ch->run) device_printf(sc->dev, "envy24ht_intr(): chan[%d].blk = %d\n", i, ch->blk); #endif if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[0] = ptr; envy24ht_updintr(sc, PCMDIR_PLAY); } if (envy24ht_checkintr(sc, PCMDIR_REC)) { #if(0) device_printf(sc->dev, "envy24ht_intr(): rec\n"); #endif dsize = sc->rsize / 4; ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_RCNT, 2) - 1; ptr -= ptr % sc->blk[1]; feed = (ptr + dsize - sc->intr[1]) % dsize; for (i = ENVY24HT_CHAN_REC_ADC1; i <= ENVY24HT_CHAN_REC_SPDIF; i++) { ch = &sc->chan[i]; if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[1] = ptr; envy24ht_updintr(sc, PCMDIR_REC); } snd_mtxunlock(sc->lock); return; } /* * Probe and attach the card */ static int envy24ht_pci_probe(device_t dev) { u_int16_t sv, sd; int i; #if(0) printf("envy24ht_pci_probe()\n"); #endif if (pci_get_device(dev) == PCID_ENVY24HT && pci_get_vendor(dev) == PCIV_ENVY24) { sv = pci_get_subvendor(dev); sd = pci_get_subdevice(dev); for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { if (cfg_table[i].subvendor == sv && cfg_table[i].subdevice == sd) { break; } } device_set_desc(dev, cfg_table[i].name); #if(0) printf("envy24ht_pci_probe(): return 0\n"); #endif return 0; } else { #if(0) printf("envy24ht_pci_probe(): return ENXIO\n"); #endif return ENXIO; } } static void envy24ht_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = arg; sc->paddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24ht_dmapsetmap()\n"); if (bootverbose) { printf("envy24ht(play): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); } #endif envy24ht_wrmt(sc, ENVY24HT_MT_PADDR, (uint32_t)segs->ds_addr, 4); envy24ht_wrmt(sc, ENVY24HT_MT_PCNT, (uint32_t)(segs->ds_len / 4 - 1), 2); } static void envy24ht_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = arg; sc->raddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24ht_dmarsetmap()\n"); if (bootverbose) { printf("envy24ht(record): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); } #endif envy24ht_wrmt(sc, ENVY24HT_MT_RADDR, (uint32_t)segs->ds_addr, 4); envy24ht_wrmt(sc, ENVY24HT_MT_RCNT, (uint32_t)(segs->ds_len / 4 - 1), 2); } static void envy24ht_dmafree(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24ht_dmafree():"); printf(" sc->raddr(0x%08x)", (u_int32_t)sc->raddr); printf(" sc->paddr(0x%08x)", (u_int32_t)sc->paddr); if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); else printf(" sc->rbuf(null)"); if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); else printf(" sc->pbuf(null)\n"); #endif #if(0) if (sc->raddr) bus_dmamap_unload(sc->dmat, sc->rmap); if (sc->paddr) bus_dmamap_unload(sc->dmat, sc->pmap); if (sc->rbuf) bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); if (sc->pbuf) bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #else bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #endif sc->raddr = sc->paddr = 0; sc->pbuf = NULL; sc->rbuf = NULL; return; } static int envy24ht_dmainit(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24ht_dmainit()\n"); #endif /* init values */ sc->psize = ENVY24HT_PLAY_BUFUNIT * ENVY24HT_SAMPLE_NUM; sc->rsize = ENVY24HT_REC_BUFUNIT * ENVY24HT_SAMPLE_NUM; sc->pbuf = NULL; sc->rbuf = NULL; sc->paddr = sc->raddr = 0; sc->blk[0] = sc->blk[1] = 0; /* allocate DMA buffer */ #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) goto bad; #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) goto bad; #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_load(): sc->pmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24ht_dmapsetmap, sc, BUS_DMA_NOWAIT)) goto bad; #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_load(): sc->rmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24ht_dmarsetmap, sc, BUS_DMA_NOWAIT)) goto bad; bzero(sc->pbuf, sc->psize); bzero(sc->rbuf, sc->rsize); return 0; bad: envy24ht_dmafree(sc); return ENOSPC; } static void envy24ht_putcfg(struct sc_info *sc) { device_printf(sc->dev, "system configuration\n"); printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", sc->cfg->subvendor, sc->cfg->subdevice); printf(" XIN2 Clock Source: "); switch (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_XIN2) { case 0x00: printf("24.576MHz(96kHz*256)\n"); break; case 0x40: printf("49.152MHz(192kHz*256)\n"); break; case 0x80: printf("reserved\n"); break; default: printf("illegal system setting\n"); } printf(" MPU-401 UART(s) #: "); if (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_MPU) printf("1\n"); else printf("not implemented\n"); switch (sc->adcn) { case 0x01: case 0x02: printf(" ADC #: "); printf("%d\n", sc->adcn); break; case 0x03: printf(" ADC #: "); printf("%d", 1); printf(" and SPDIF receiver connected\n"); break; default: printf(" no physical inputs\n"); } printf(" DAC #: "); printf("%d\n", sc->dacn); printf(" Multi-track converter type: "); if ((sc->cfg->acl & ENVY24HT_CCSM_ACL_MTC) == 0) { printf("AC'97(SDATA_OUT:"); if (sc->cfg->acl & ENVY24HT_CCSM_ACL_OMODE) printf("packed"); else printf("split"); printf(")\n"); } else { printf("I2S("); if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_VOL) printf("with volume, "); if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_192KHZ) printf("192KHz support, "); else if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_96KHZ) printf("192KHz support, "); else printf("48KHz support, "); switch (sc->cfg->i2s & ENVY24HT_CCSM_I2S_RES) { case ENVY24HT_CCSM_I2S_16BIT: printf("16bit resolution, "); break; case ENVY24HT_CCSM_I2S_18BIT: printf("18bit resolution, "); break; case ENVY24HT_CCSM_I2S_20BIT: printf("20bit resolution, "); break; case ENVY24HT_CCSM_I2S_24BIT: printf("24bit resolution, "); break; } printf("ID#0x%x)\n", sc->cfg->i2s & ENVY24HT_CCSM_I2S_ID); } printf(" S/PDIF(IN/OUT): "); if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_IN) printf("1/"); else printf("0/"); if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_OUT) printf("1 "); else printf("0 "); if (sc->cfg->spdif & (ENVY24HT_CCSM_SPDIF_IN | ENVY24HT_CCSM_SPDIF_OUT)) printf("ID# 0x%02x\n", (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_ID) >> 2); printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); } static int envy24ht_init(struct sc_info *sc) { u_int32_t data; #if(0) int rtn; #endif int i; u_int32_t sv, sd; #if(0) device_printf(sc->dev, "envy24ht_init()\n"); #endif /* reset chip */ #if 0 envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_CCS_CTL_RESET, 1); DELAY(200); envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_CCS_CTL_NATIVE, 1); DELAY(200); /* legacy hardware disable */ data = pci_read_config(sc->dev, PCIR_LAC, 2); data |= PCIM_LAC_DISABLE; pci_write_config(sc->dev, PCIR_LAC, data, 2); #endif /* check system configuration */ sc->cfg = NULL; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { /* 1st: search configuration from table */ sv = pci_get_subvendor(sc->dev); sd = pci_get_subdevice(sc->dev); if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { #if(0) device_printf(sc->dev, "Set configuration from table\n"); #endif sc->cfg = &cfg_table[i]; break; } } if (sc->cfg == NULL) { /* 2nd: read configuration from table */ sc->cfg = envy24ht_rom2cfg(sc); } sc->adcn = ((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_ADC) >> 2) + 1; /* need to be fixed */ sc->dacn = (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_DAC) + 1; if (1 /* bootverbose */) { envy24ht_putcfg(sc); } /* set system configuration */ envy24ht_wrcs(sc, ENVY24HT_CCS_SCFG, sc->cfg->scfg, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_ACL, sc->cfg->acl, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2S, sc->cfg->i2s, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_SPDIF, sc->cfg->spdif, 1); envy24ht_gpiosetmask(sc, sc->cfg->gpiomask); envy24ht_gpiosetdir(sc, sc->cfg->gpiodir); envy24ht_gpiowr(sc, sc->cfg->gpiostate); if ((sc->cfg->subvendor == 0x3031) && (sc->cfg->subdevice == 0x4553)) { envy24ht_wri2c(sc, 0x22, 0x00, 0x07); envy24ht_wri2c(sc, 0x22, 0x04, 0x5f | 0x80); envy24ht_wri2c(sc, 0x22, 0x05, 0x5f | 0x80); } for (i = 0; i < sc->adcn; i++) { sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); sc->cfg->codec->init(sc->adc[i]); } for (i = 0; i < sc->dacn; i++) { sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); sc->cfg->codec->init(sc->dac[i]); } /* initialize DMA buffer */ #if(0) device_printf(sc->dev, "envy24ht_init(): initialize DMA buffer\n"); #endif if (envy24ht_dmainit(sc)) return ENOSPC; /* initialize status */ sc->run[0] = sc->run[1] = 0; sc->intr[0] = sc->intr[1] = 0; sc->speed = 0; sc->caps[0].fmtlist = envy24ht_playfmt; sc->caps[1].fmtlist = envy24ht_recfmt; /* set channel router */ #if 0 envy24ht_route(sc, ENVY24HT_ROUTE_DAC_1, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_DMA, 0, 0); envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); #endif /* set macro interrupt mask */ data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_IMASK, data & ~ENVY24HT_CCS_IMASK_PMT, 1); data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); #if(0) device_printf(sc->dev, "envy24ht_init(): CCS_IMASK-->0x%02x\n", data); #endif return 0; } static int envy24ht_alloc_resource(struct sc_info *sc) { /* allocate I/O port resource */ sc->csid = PCIR_CCS; sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->csid, RF_ACTIVE); sc->mtid = ENVY24HT_PCIR_MT; sc->mt = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->mtid, RF_ACTIVE); if (!sc->cs || !sc->mt) { device_printf(sc->dev, "unable to map IO port space\n"); return ENXIO; } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); sc->mtt = rman_get_bustag(sc->mt); sc->mth = rman_get_bushandle(sc->mt); #if(0) device_printf(sc->dev, "IO port register values\nCCS: 0x%lx\nMT: 0x%lx\n", pci_read_config(sc->dev, PCIR_CCS, 4), pci_read_config(sc->dev, PCIR_MT, 4)); #endif /* allocate interrupt resource */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24ht_intr, sc, &sc->ih)) { device_printf(sc->dev, "unable to map interrupt\n"); return ENXIO; } /* allocate DMA resource */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, /*nsegments*/1, /*maxsegsz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->dmat) != 0) { device_printf(sc->dev, "unable to create dma tag\n"); return ENXIO; } return 0; } static int envy24ht_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; int err = 0; int i; #if(0) device_printf(dev, "envy24ht_pci_attach()\n"); #endif /* get sc_info data area */ if ((sc = malloc(sizeof(*sc), M_ENVY24HT, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_envy24ht softc"); sc->dev = dev; /* initialize PCI interface */ pci_enable_busmaster(dev); /* allocate resources */ err = envy24ht_alloc_resource(sc); if (err) { device_printf(dev, "unable to allocate system resources\n"); goto bad; } /* initialize card */ err = envy24ht_init(sc); if (err) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* set multi track mixer */ mixer_init(dev, &envy24htmixer_class, sc); /* set channel information */ /* err = pcm_register(dev, sc, 5, 2 + sc->adcn); */ err = pcm_register(dev, sc, 1, 2 + sc->adcn); if (err) goto bad; sc->chnum = 0; /* for (i = 0; i < 5; i++) { */ pcm_addchan(dev, PCMDIR_PLAY, &envy24htchan_class, sc); sc->chnum++; /* } */ for (i = 0; i < 2 + sc->adcn; i++) { pcm_addchan(dev, PCMDIR_REC, &envy24htchan_class, sc); sc->chnum++; } /* set status iformation */ snprintf(status, SND_STATUSLEN, - "at io 0x%jx:%jd,0x%jx:%jd irq %jd", + "port 0x%jx:%jd,0x%jx:%jd irq %jd on %s", rman_get_start(sc->cs), rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, rman_get_start(sc->mt), rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, - rman_get_start(sc->irq)); + rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return 0; bad: if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); envy24ht_dmafree(sc); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24ht_cfgfree(sc->cfg); if (sc->cs) bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); if (sc->mt) bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_ENVY24HT); return err; } static int envy24ht_pci_detach(device_t dev) { struct sc_info *sc; int r; int i; #if(0) device_printf(dev, "envy24ht_pci_detach()\n"); #endif sc = pcm_getdevinfo(dev); if (sc == NULL) return 0; r = pcm_unregister(dev); if (r) return r; envy24ht_dmafree(sc); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24ht_cfgfree(sc->cfg); bus_dma_tag_destroy(sc->dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); snd_mtxfree(sc->lock); free(sc, M_ENVY24HT); return 0; } static device_method_t envy24ht_methods[] = { /* Device interface */ DEVMETHOD(device_probe, envy24ht_pci_probe), DEVMETHOD(device_attach, envy24ht_pci_attach), DEVMETHOD(device_detach, envy24ht_pci_detach), { 0, 0 } }; static driver_t envy24ht_driver = { "pcm", envy24ht_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_envy24ht, pci, envy24ht_driver, 0, 0); MODULE_DEPEND(snd_envy24ht, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_envy24ht, snd_spicds, 1, 1, 1); MODULE_VERSION(snd_envy24ht, 1); diff --git a/sys/dev/sound/pci/es137x.c b/sys/dev/sound/pci/es137x.c index 8c1c2b34e1cf..8f832d899dd3 100644 --- a/sys/dev/sound/pci/es137x.c +++ b/sys/dev/sound/pci/es137x.c @@ -1,1944 +1,1944 @@ /*- * SPDX-License-Identifier: BSD-2-Clause AND BSD-4-Clause * * Copyright (c) 1999 Russell Cattelan * Copyright (c) 1998 Joachim Kuebart * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Copyright (c) 1999 Cameron Grant * 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 Joachim Kuebart. * * 4. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED ``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 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. */ /* * Support the ENSONIQ AudioPCI board and Creative Labs SoundBlaster PCI * boards based on the ES1370, ES1371 and ES1373 chips. * * Part of this code was heavily inspired by the linux driver from * Thomas Sailer (sailer@ife.ee.ethz.ch) * Just about everything has been touched and reworked in some way but * the all the underlying sequences/timing/register values are from * Thomas' code. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" #define MEM_MAP_REG 0x14 /* PCI IDs of supported chips */ #define ES1370_PCI_ID 0x50001274 #define ES1371_PCI_ID 0x13711274 #define ES1371_PCI_ID2 0x13713274 #define CT5880_PCI_ID 0x58801274 #define CT4730_PCI_ID 0x89381102 #define ES1371REV_ES1371_A 0x02 #define ES1371REV_ES1371_B 0x09 #define ES1371REV_ES1373_8 0x08 #define ES1371REV_ES1373_A 0x04 #define ES1371REV_ES1373_B 0x06 #define ES1371REV_CT5880_A 0x07 #define CT5880REV_CT5880_C 0x02 #define CT5880REV_CT5880_D 0x03 #define CT5880REV_CT5880_E 0x04 #define CT4730REV_CT4730_A 0x00 #define ES_DEFAULT_BUFSZ 4096 /* 2 DAC for playback, 1 ADC for record */ #define ES_DAC1 0 #define ES_DAC2 1 #define ES_ADC 2 #define ES_NCHANS 3 #define ES_DMA_SEGS_MIN 2 #define ES_DMA_SEGS_MAX 256 #define ES_BLK_MIN 64 #define ES_BLK_ALIGN (~(ES_BLK_MIN - 1)) #define ES1370_DAC1_MINSPEED 5512 #define ES1370_DAC1_MAXSPEED 44100 /* device private data */ struct es_info; struct es_chinfo { struct es_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct pcmchan_caps caps; int dir, num, index; uint32_t fmt, blksz, blkcnt, bufsz; uint32_t ptr, prevptr; int active; }; /* * 32bit Ensoniq Configuration (es->escfg). * ---------------------------------------- * * +-------+--------+------+------+---------+--------+---------+---------+ * len | 16 | 1 | 1 | 1 | 2 | 2 | 1 | 8 | * +-------+--------+------+------+---------+--------+---------+---------+ * | fixed | single | | | | | is | general | * | rate | pcm | DACx | DACy | numplay | numrec | es1370? | purpose | * | | mixer | | | | | | | * +-------+--------+------+------+---------+--------+---------+---------+ */ #define ES_FIXED_RATE(cfgv) \ (((cfgv) & 0xffff0000) >> 16) #define ES_SET_FIXED_RATE(cfgv, nv) \ (((cfgv) & ~0xffff0000) | (((nv) & 0xffff) << 16)) #define ES_SINGLE_PCM_MIX(cfgv) \ (((cfgv) & 0x8000) >> 15) #define ES_SET_SINGLE_PCM_MIX(cfgv, nv) \ (((cfgv) & ~0x8000) | (((nv) ? 1 : 0) << 15)) #define ES_DAC_FIRST(cfgv) \ (((cfgv) & 0x4000) >> 14) #define ES_SET_DAC_FIRST(cfgv, nv) \ (((cfgv) & ~0x4000) | (((nv) & 0x1) << 14)) #define ES_DAC_SECOND(cfgv) \ (((cfgv) & 0x2000) >> 13) #define ES_SET_DAC_SECOND(cfgv, nv) \ (((cfgv) & ~0x2000) | (((nv) & 0x1) << 13)) #define ES_NUMPLAY(cfgv) \ (((cfgv) & 0x1800) >> 11) #define ES_SET_NUMPLAY(cfgv, nv) \ (((cfgv) & ~0x1800) | (((nv) & 0x3) << 11)) #define ES_NUMREC(cfgv) \ (((cfgv) & 0x600) >> 9) #define ES_SET_NUMREC(cfgv, nv) \ (((cfgv) & ~0x600) | (((nv) & 0x3) << 9)) #define ES_IS_ES1370(cfgv) \ (((cfgv) & 0x100) >> 8) #define ES_SET_IS_ES1370(cfgv, nv) \ (((cfgv) & ~0x100) | (((nv) ? 1 : 0) << 8)) #define ES_GP(cfgv) \ ((cfgv) & 0xff) #define ES_SET_GP(cfgv, nv) \ (((cfgv) & ~0xff) | ((nv) & 0xff)) #define ES_DAC1_ENABLED(cfgv) \ (ES_NUMPLAY(cfgv) > 1 || \ (ES_NUMPLAY(cfgv) == 1 && ES_DAC_FIRST(cfgv) == ES_DAC1)) #define ES_DAC2_ENABLED(cfgv) \ (ES_NUMPLAY(cfgv) > 1 || \ (ES_NUMPLAY(cfgv) == 1 && ES_DAC_FIRST(cfgv) == ES_DAC2)) /* * DAC 1/2 configuration through kernel hint - hint.pcm..dac="val" * * 0 = Enable both DACs - Default * 1 = Enable single DAC (DAC1) * 2 = Enable single DAC (DAC2) * 3 = Enable both DACs, swap position (DAC2 comes first instead of DAC1) */ #define ES_DEFAULT_DAC_CFG 0 struct es_info { bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; device_t dev; int num; unsigned int bufsz, blkcnt; /* Contents of board's registers */ uint32_t ctrl; uint32_t sctrl; uint32_t escfg; struct es_chinfo ch[ES_NCHANS]; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; #define ES_LOCK(sc) snd_mtxlock((sc)->lock) #define ES_UNLOCK(sc) snd_mtxunlock((sc)->lock) #define ES_LOCK_ASSERT(sc) snd_mtxassert((sc)->lock) /* prototypes */ static void es_intr(void *); static uint32_t es1371_wait_src_ready(struct es_info *); static void es1371_src_write(struct es_info *, unsigned short, unsigned short); static unsigned int es1371_adc_rate(struct es_info *, unsigned int, int); static unsigned int es1371_dac_rate(struct es_info *, unsigned int, int); static int es1371_init(struct es_info *); static int es1370_init(struct es_info *); static int es1370_wrcodec(struct es_info *, unsigned char, unsigned char); static uint32_t es_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps es_caps = {4000, 48000, es_fmt, 0}; static const struct { unsigned volidx:4; unsigned left:4; unsigned right:4; unsigned stereo:1; unsigned recmask:13; unsigned avail:1; } mixtable[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x1f7f, 1 }, [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, [SOUND_MIXER_LINE] = { 4, 0x8, 0x9, 1, 0x0018, 1 }, [SOUND_MIXER_LINE1] = { 5, 0xa, 0xb, 1, 0x1800, 1 }, [SOUND_MIXER_LINE2] = { 6, 0xc, 0x0, 0, 0x0100, 1 }, [SOUND_MIXER_LINE3] = { 7, 0xd, 0x0, 0, 0x0200, 1 }, [SOUND_MIXER_MIC] = { 8, 0xe, 0x0, 0, 0x0001, 1 }, [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; static __inline uint32_t es_rd(struct es_info *es, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(es->st, es->sh, regno)); case 2: return (bus_space_read_2(es->st, es->sh, regno)); case 4: return (bus_space_read_4(es->st, es->sh, regno)); default: return (0xFFFFFFFF); } } static __inline void es_wr(struct es_info *es, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(es->st, es->sh, regno, data); break; case 2: bus_space_write_2(es->st, es->sh, regno, data); break; case 4: bus_space_write_4(es->st, es->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* The es1370 mixer interface */ static int es1370_mixinit(struct snd_mixer *m) { struct es_info *es; int i; uint32_t v; es = mix_getdevinfo(m); v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mixtable[i].avail) v |= (1 << i); } /* * Each DAC1/2 for ES1370 can be controlled independently * DAC1 = controlled by synth * DAC2 = controlled by pcm * This is indeed can confuse user if DAC1 become primary playback * channel. Try to be smart and combine both if necessary. */ if (ES_SINGLE_PCM_MIX(es->escfg)) v &= ~(1 << SOUND_MIXER_SYNTH); mix_setdevs(m, v); v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mixtable[i].recmask) v |= (1 << i); } if (ES_SINGLE_PCM_MIX(es->escfg)) /* ditto */ v &= ~(1 << SOUND_MIXER_SYNTH); mix_setrecdevs(m, v); return (0); } static int es1370_mixset(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct es_info *es; int l, r, rl, rr, set_dac1; if (!mixtable[dev].avail) return (-1); l = left; r = (mixtable[dev].stereo) ? right : l; if (mixtable[dev].left == 0xf) rl = (l < 2) ? 0x80 : 7 - (l - 2) / 14; else rl = (l < 7) ? 0x80 : 31 - (l - 7) / 3; es = mix_getdevinfo(m); ES_LOCK(es); if (dev == SOUND_MIXER_PCM && (ES_SINGLE_PCM_MIX(es->escfg)) && ES_DAC1_ENABLED(es->escfg)) set_dac1 = 1; else set_dac1 = 0; if (mixtable[dev].stereo) { rr = (r < 7) ? 0x80 : 31 - (r - 7) / 3; es1370_wrcodec(es, mixtable[dev].right, rr); if (set_dac1 && mixtable[SOUND_MIXER_SYNTH].stereo) es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].right, rr); } es1370_wrcodec(es, mixtable[dev].left, rl); if (set_dac1) es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].left, rl); ES_UNLOCK(es); return (l | (r << 8)); } static uint32_t es1370_mixsetrecsrc(struct snd_mixer *m, uint32_t src) { struct es_info *es; int i, j = 0; es = mix_getdevinfo(m); if (src == 0) src = 1 << SOUND_MIXER_MIC; src &= mix_getrecdevs(m); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if ((src & (1 << i)) != 0) j |= mixtable[i].recmask; ES_LOCK(es); if ((src & (1 << SOUND_MIXER_PCM)) && ES_SINGLE_PCM_MIX(es->escfg) && ES_DAC1_ENABLED(es->escfg)) j |= mixtable[SOUND_MIXER_SYNTH].recmask; es1370_wrcodec(es, CODEC_LIMIX1, j & 0x55); es1370_wrcodec(es, CODEC_RIMIX1, j & 0xaa); es1370_wrcodec(es, CODEC_LIMIX2, (j >> 8) & 0x17); es1370_wrcodec(es, CODEC_RIMIX2, (j >> 8) & 0x0f); es1370_wrcodec(es, CODEC_OMIX1, 0x7f); es1370_wrcodec(es, CODEC_OMIX2, 0x3f); ES_UNLOCK(es); return (src); } static kobj_method_t es1370_mixer_methods[] = { KOBJMETHOD(mixer_init, es1370_mixinit), KOBJMETHOD(mixer_set, es1370_mixset), KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(es1370_mixer); /* -------------------------------------------------------------------- */ static int es1370_wrcodec(struct es_info *es, unsigned char i, unsigned char data) { unsigned int t; ES_LOCK_ASSERT(es); for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1370_REG_STATUS, 4) & STAT_CSTAT) == 0) { es_wr(es, ES1370_REG_CODEC, ((unsigned short)i << CODEC_INDEX_SHIFT) | data, 2); return (0); } DELAY(1); } device_printf(es->dev, "%s: timed out\n", __func__); return (-1); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct es_info *es = devinfo; struct es_chinfo *ch; uint32_t index; ES_LOCK(es); if (dir == PCMDIR_PLAY) { index = ES_GP(es->escfg); es->escfg = ES_SET_GP(es->escfg, index + 1); if (index == 0) index = ES_DAC_FIRST(es->escfg); else if (index == 1) index = ES_DAC_SECOND(es->escfg); else { device_printf(es->dev, "Invalid ES_GP index: %d\n", index); ES_UNLOCK(es); return (NULL); } if (!(index == ES_DAC1 || index == ES_DAC2)) { device_printf(es->dev, "Unknown DAC: %d\n", index + 1); ES_UNLOCK(es); return (NULL); } if (es->ch[index].channel != NULL) { device_printf(es->dev, "DAC%d already initialized!\n", index + 1); ES_UNLOCK(es); return (NULL); } } else index = ES_ADC; ch = &es->ch[index]; ch->index = index; ch->num = es->num++; ch->caps = es_caps; if (ES_IS_ES1370(es->escfg)) { if (ch->index == ES_DAC1) { ch->caps.maxspeed = ES1370_DAC1_MAXSPEED; ch->caps.minspeed = ES1370_DAC1_MINSPEED; } else { uint32_t fixed_rate = ES_FIXED_RATE(es->escfg); if (!(fixed_rate < es_caps.minspeed || fixed_rate > es_caps.maxspeed)) { ch->caps.maxspeed = fixed_rate; ch->caps.minspeed = fixed_rate; } } } ch->parent = es; ch->channel = c; ch->buffer = b; ch->bufsz = es->bufsz; ch->blkcnt = es->blkcnt; ch->blksz = ch->bufsz / ch->blkcnt; ch->dir = dir; ES_UNLOCK(es); if (sndbuf_alloc(ch->buffer, es->parent_dmat, 0, ch->bufsz) != 0) return (NULL); ES_LOCK(es); if (dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_DAC2_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_ADC_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } ES_UNLOCK(es); return (ch); } static int eschan_setformat(kobj_t obj, void *data, uint32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es->sctrl &= ~SCTRL_P1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P1SEB; if (AFMT_CHANNEL(format) > 1) es->sctrl |= SCTRL_P1SMB; } else { es->sctrl &= ~SCTRL_P2FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; if (AFMT_CHANNEL(format) > 1) es->sctrl |= SCTRL_P2SMB; } } else { es->sctrl &= ~SCTRL_R1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; if (AFMT_CHANNEL(format) > 1) es->sctrl |= SCTRL_R1SMB; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); ch->fmt = format; return (0); } static uint32_t eschan1370_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; ES_LOCK(es); /* Fixed rate , do nothing. */ if (ch->caps.minspeed == ch->caps.maxspeed) { ES_UNLOCK(es); return (ch->caps.maxspeed); } if (speed < ch->caps.minspeed) speed = ch->caps.minspeed; if (speed > ch->caps.maxspeed) speed = ch->caps.maxspeed; if (ch->index == ES_DAC1) { /* * DAC1 does not support continuous rate settings. * Pick the nearest and use it since FEEDER_RATE will * do the proper conversion for us. */ es->ctrl &= ~CTRL_WTSRSEL; if (speed < 8268) { speed = 5512; es->ctrl |= 0 << CTRL_SH_WTSRSEL; } else if (speed < 16537) { speed = 11025; es->ctrl |= 1 << CTRL_SH_WTSRSEL; } else if (speed < 33075) { speed = 22050; es->ctrl |= 2 << CTRL_SH_WTSRSEL; } else { speed = 44100; es->ctrl |= 3 << CTRL_SH_WTSRSEL; } } else { es->ctrl &= ~CTRL_PCLKDIV; es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; } es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); ES_UNLOCK(es); return (speed); } static uint32_t eschan1371_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t i; int delta; ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) i = es1371_dac_rate(es, speed, ch->index); /* play */ else i = es1371_adc_rate(es, speed, ch->index); /* record */ ES_UNLOCK(es); delta = (speed > i) ? (speed - i) : (i - speed); if (delta < 2) return (speed); return (i); } static int eschan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; blksz &= ES_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN)) blksz = sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN; if (blksz < ES_BLK_MIN) blksz = ES_BLK_MIN; if (blkcnt > ES_DMA_SEGS_MAX) blkcnt = ES_DMA_SEGS_MAX; if (blkcnt < ES_DMA_SEGS_MIN) blkcnt = ES_DMA_SEGS_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= ES_DMA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ES_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(es->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->bufsz = sndbuf_getsize(ch->buffer); ch->blksz = sndbuf_getblksz(ch->buffer); ch->blkcnt = sndbuf_getblkcnt(ch->buffer); return (0); } static uint32_t eschan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; eschan_setfragments(obj, data, blksz, es->blkcnt); return (ch->blksz); } #define es_chan_active(es) ((es)->ch[ES_DAC1].active + \ (es)->ch[ES_DAC2].active + \ (es)->ch[ES_ADC].active) static __inline int es_poll_channel(struct es_chinfo *ch) { struct es_info *es; uint32_t sz, delta; uint32_t reg, ptr; if (ch == NULL || ch->channel == NULL || ch->active == 0) return (0); es = ch->parent; if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) reg = ES1370_REG_DAC1_FRAMECNT; else reg = ES1370_REG_DAC2_FRAMECNT; } else reg = ES1370_REG_ADC_FRAMECNT; sz = ch->blksz * ch->blkcnt; es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); ptr = es_rd(es, reg & 0x000000ff, 4) >> 16; ptr <<= 2; ch->ptr = ptr; ptr %= sz; ptr &= ~(ch->blksz - 1); delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } static void es_poll_callback(void *arg) { struct es_info *es = arg; uint32_t trigger = 0; int i; if (es == NULL) return; ES_LOCK(es); if (es->polling == 0 || es_chan_active(es) == 0) { ES_UNLOCK(es); return; } for (i = 0; i < ES_NCHANS; i++) { if (es_poll_channel(&es->ch[i]) != 0) trigger |= 1 << i; } /* XXX */ callout_reset(&es->poll_timer, 1/*es->poll_ticks*/, es_poll_callback, es); ES_UNLOCK(es); for (i = 0; i < ES_NCHANS; i++) { if (trigger & (1 << i)) chn_intr(es->ch[i].channel); } } static int eschan_trigger(kobj_t obj, void *data, int go) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t cnt, b = 0; if (!PCMTRIG_COMMON(go)) return 0; ES_LOCK(es); cnt = (ch->blksz / sndbuf_getalign(ch->buffer)) - 1; if (ch->fmt & AFMT_16BIT) b |= 0x02; if (AFMT_CHANNEL(ch->fmt) > 1) b |= 0x01; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { if (ch->index == ES_DAC1) { es->ctrl |= CTRL_DAC1_EN; es->sctrl &= ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD); if (es->polling == 0) es->sctrl |= SCTRL_P1INTEN; else es->sctrl &= ~SCTRL_P1INTEN; es->sctrl |= b; es_wr(es, ES1370_REG_DAC1_SCOUNT, cnt, 4); /* start at beginning of buffer */ es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMECNT >> 8, 4); es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else { es->ctrl |= CTRL_DAC2_EN; es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN); if (es->polling == 0) es->sctrl |= SCTRL_P2INTEN; else es->sctrl &= ~SCTRL_P2INTEN; es->sctrl |= (b << 2) | ((((b >> 1) & 1) + 1) << SCTRL_SH_P2ENDINC); es_wr(es, ES1370_REG_DAC2_SCOUNT, cnt, 4); /* start at beginning of buffer */ es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8, 4); es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } } else es->ctrl &= ~((ch->index == ES_DAC1) ? CTRL_DAC1_EN : CTRL_DAC2_EN); } else { if (go == PCMTRIG_START) { es->ctrl |= CTRL_ADC_EN; es->sctrl &= ~SCTRL_R1LOOPSEL; if (es->polling == 0) es->sctrl |= SCTRL_R1INTEN; else es->sctrl &= ~SCTRL_R1INTEN; es->sctrl |= b << 4; es_wr(es, ES1370_REG_ADC_SCOUNT, cnt, 4); /* start at beginning of buffer */ es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8, 4); es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else es->ctrl &= ~CTRL_ADC_EN; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); if (go == PCMTRIG_START) { if (es->polling != 0) { ch->ptr = 0; ch->prevptr = 0; if (es_chan_active(es) == 0) { es->poll_ticks = 1; callout_reset(&es->poll_timer, 1, es_poll_callback, es); } } ch->active = 1; } else { ch->active = 0; if (es->polling != 0) { if (es_chan_active(es) == 0) { callout_stop(&es->poll_timer); es->poll_ticks = 1; } } } ES_UNLOCK(es); return (0); } static uint32_t eschan_getptr(kobj_t obj, void *data) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t reg, cnt; ES_LOCK(es); if (es->polling != 0) cnt = ch->ptr; else { if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) reg = ES1370_REG_DAC1_FRAMECNT; else reg = ES1370_REG_DAC2_FRAMECNT; } else reg = ES1370_REG_ADC_FRAMECNT; es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; /* cnt is longwords */ cnt <<= 2; } ES_UNLOCK(es); cnt &= ES_BLK_ALIGN; return (cnt); } static struct pcmchan_caps * eschan_getcaps(kobj_t obj, void *data) { struct es_chinfo *ch = data; return (&ch->caps); } static kobj_method_t eschan1370_methods[] = { KOBJMETHOD(channel_init, eschan_init), KOBJMETHOD(channel_setformat, eschan_setformat), KOBJMETHOD(channel_setspeed, eschan1370_setspeed), KOBJMETHOD(channel_setblocksize, eschan_setblocksize), KOBJMETHOD(channel_setfragments, eschan_setfragments), KOBJMETHOD(channel_trigger, eschan_trigger), KOBJMETHOD(channel_getptr, eschan_getptr), KOBJMETHOD(channel_getcaps, eschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(eschan1370); static kobj_method_t eschan1371_methods[] = { KOBJMETHOD(channel_init, eschan_init), KOBJMETHOD(channel_setformat, eschan_setformat), KOBJMETHOD(channel_setspeed, eschan1371_setspeed), KOBJMETHOD(channel_setblocksize, eschan_setblocksize), KOBJMETHOD(channel_setfragments, eschan_setfragments), KOBJMETHOD(channel_trigger, eschan_trigger), KOBJMETHOD(channel_getptr, eschan_getptr), KOBJMETHOD(channel_getcaps, eschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(eschan1371); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void es_intr(void *p) { struct es_info *es = p; uint32_t intsrc, sctrl; ES_LOCK(es); if (es->polling != 0) { ES_UNLOCK(es); return; } intsrc = es_rd(es, ES1370_REG_STATUS, 4); if ((intsrc & STAT_INTR) == 0) { ES_UNLOCK(es); return; } sctrl = es->sctrl; if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; es_wr(es, ES1370_REG_SERIAL_CONTROL, sctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); if (intsrc & STAT_ADC) chn_intr(es->ch[ES_ADC].channel); if (intsrc & STAT_DAC1) chn_intr(es->ch[ES_DAC1].channel); if (intsrc & STAT_DAC2) chn_intr(es->ch[ES_DAC2].channel); } /* ES1370 specific */ static int es1370_init(struct es_info *es) { uint32_t fixed_rate; int r, single_pcm; /* ES1370 default to fixed rate operation */ if (resource_int_value(device_get_name(es->dev), device_get_unit(es->dev), "fixed_rate", &r) == 0) { fixed_rate = r; if (fixed_rate) { if (fixed_rate < es_caps.minspeed) fixed_rate = es_caps.minspeed; if (fixed_rate > es_caps.maxspeed) fixed_rate = es_caps.maxspeed; } } else fixed_rate = es_caps.maxspeed; if (resource_int_value(device_get_name(es->dev), device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) single_pcm = (r != 0) ? 1 : 0; else single_pcm = 1; ES_LOCK(es); if (ES_NUMPLAY(es->escfg) == 1) single_pcm = 1; /* This is ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 1); if (fixed_rate) es->escfg = ES_SET_FIXED_RATE(es->escfg, fixed_rate); else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); fixed_rate = DSP_DEFAULT_SPEED; } if (single_pcm) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); es->ctrl = CTRL_CDC_EN | CTRL_JYSTK_EN | CTRL_SERR_DIS | (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); es->ctrl |= 3 << CTRL_SH_WTSRSEL; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es->sctrl = 0; es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); /* No RST, PD */ es1370_wrcodec(es, CODEC_RES_PD, 3); /* * CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; * program DAC_SYNC=0! */ es1370_wrcodec(es, CODEC_CSEL, 0); /* Recording source is mixer */ es1370_wrcodec(es, CODEC_ADSEL, 0); /* MIC amp is 0db */ es1370_wrcodec(es, CODEC_MGAIN, 0); ES_UNLOCK(es); return (0); } /* ES1371 specific */ int es1371_init(struct es_info *es) { uint32_t cssr, devid, revid, subdev; int idx; ES_LOCK(es); /* This is NOT ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 0); es->num = 0; es->sctrl = 0; cssr = 0; devid = pci_get_devid(es->dev); revid = pci_get_revid(es->dev); subdev = (pci_get_subdevice(es->dev) << 16) | pci_get_subvendor(es->dev); /* * Joyport blacklist. Either we're facing with broken hardware * or because this hardware need special (unknown) initialization * procedures. */ switch (subdev) { case 0x20001274: /* old Ensoniq */ es->ctrl = 0; break; default: es->ctrl = CTRL_JYSTK_EN; break; } if (devid == CT4730_PCI_ID) { /* XXX amplifier hack? */ es->ctrl |= (1 << 16); } /* initialize the chips */ es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1371_REG_LEGACY, 0, 4); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { cssr = 1 << 29; es_wr(es, ES1370_REG_STATUS, cssr, 4); DELAY(20000); } /* AC'97 warm reset to start the bitclk */ es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1371_REG_LEGACY, ES1371_SYNC_RES, 4); DELAY(2000); es_wr(es, ES1370_REG_CONTROL, es->sctrl, 4); es1371_wait_src_ready(es); /* Init the sample rate converter */ es_wr(es, ES1371_REG_SMPRATE, ES1371_DIS_SRC, 4); for (idx = 0; idx < 0x80; idx++) es1371_src_write(es, idx, 0); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); es1371_adc_rate(es, 22050, ES_ADC); es1371_dac_rate(es, 22050, ES_DAC1); es1371_dac_rate(es, 22050, ES_DAC2); /* * WARNING: * enabling the sample rate converter without properly programming * its parameters causes the chip to lock up (the SRC busy bit will * be stuck high, and I've found no way to rectify this other than * power cycle) */ es1371_wait_src_ready(es); es_wr(es, ES1371_REG_SMPRATE, 0, 4); /* try to reset codec directly */ es_wr(es, ES1371_REG_CODEC, 0, 4); es_wr(es, ES1370_REG_STATUS, cssr, 4); ES_UNLOCK(es); return (0); } /* -------------------------------------------------------------------- */ static int es1371_wrcd(kobj_t obj, void *s, int addr, uint32_t data) { uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; for (t = 0; t < 0x1000; t++) { if (!es_rd(es, ES1371_REG_CODEC & CODEC_WIP, 4)) break; } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) break; } /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; } es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); return (0); } static int es1371_rdcd(kobj_t obj, void *s, int addr) { uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; for (t = 0; t < 0x1000; t++) { if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) break; } /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; } es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; } return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static kobj_method_t es1371_ac97_methods[] = { KOBJMETHOD(ac97_read, es1371_rdcd), KOBJMETHOD(ac97_write, es1371_wrcd), KOBJMETHOD_END }; AC97_DECLARE(es1371_ac97); /* -------------------------------------------------------------------- */ static unsigned int es1371_src_read(struct es_info *es, unsigned short reg) { uint32_t r; r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg); es_wr(es, ES1371_REG_SMPRATE, r, 4); return (ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es))); } static void es1371_src_write(struct es_info *es, unsigned short reg, unsigned short data) { uint32_t r; r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg) | ES1371_SRC_RAM_DATAO(data); es_wr(es, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE, 4); } static unsigned int es1371_adc_rate(struct es_info *es, unsigned int rate, int set) { unsigned int n, truncm, freq, result; ES_LOCK_ASSERT(es); if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; n = rate / 3000; if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) n--; truncm = (21 * n - 1) | 1; freq = ((48000UL << 15) / rate) * n; result = (48000UL << 15) / (freq / n); if (set) { if (rate >= 24000) { if (truncm > 239) truncm = 239; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, (((239 - truncm) >> 1) << 9) | (n << 4)); } else { if (truncm > 119) truncm = 119; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); } es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS, (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & 0x00ff) | ((freq >> 5) & 0xfc00)); es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); es1371_src_write(es, ES_SMPREG_VOL_ADC, n << 8); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, n << 8); } return (result); } static unsigned int es1371_dac_rate(struct es_info *es, unsigned int rate, int set) { unsigned int freq, r, result, dac, dis; ES_LOCK_ASSERT(es); if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; freq = ((rate << 15) + 1500) / 3000; result = (freq * 3000) >> 15; dac = (set == ES_DAC1) ? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; dis = (set == ES_DAC1) ? ES1371_DIS_P2 : ES1371_DIS_P1; r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); es1371_src_write(es, dac + ES_SMPREG_INT_REGS, (es1371_src_read(es, dac + ES_SMPREG_INT_REGS) & 0x00ff) | ((freq >> 5) & 0xfc00)); es1371_src_write(es, dac + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); return (result); } static uint32_t es1371_wait_src_ready(struct es_info *es) { uint32_t t, r; for (t = 0; t < 0x1000; t++) { if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & ES1371_SRC_RAM_BUSY)) return (r); DELAY(1); } device_printf(es->dev, "%s: timed out 0x%x [0x%x]\n", __func__, ES1371_REG_SMPRATE, r); return (0); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int es_pci_probe(device_t dev) { switch(pci_get_devid(dev)) { case ES1370_PCI_ID: device_set_desc(dev, "AudioPCI ES1370"); return (BUS_PROBE_DEFAULT); case ES1371_PCI_ID: switch(pci_get_revid(dev)) { case ES1371REV_ES1371_A: device_set_desc(dev, "AudioPCI ES1371-A"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1371_B: device_set_desc(dev, "AudioPCI ES1371-B"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_A: device_set_desc(dev, "AudioPCI ES1373-A"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_B: device_set_desc(dev, "AudioPCI ES1373-B"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_8: device_set_desc(dev, "AudioPCI ES1373-8"); return (BUS_PROBE_DEFAULT); case ES1371REV_CT5880_A: device_set_desc(dev, "Creative CT5880-A"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "AudioPCI ES1371-?"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); } case ES1371_PCI_ID2: device_set_desc(dev, "Strange AudioPCI ES1371-? (vid=3274)"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); case CT4730_PCI_ID: switch(pci_get_revid(dev)) { case CT4730REV_CT4730_A: device_set_desc(dev, "Creative SB AudioPCI CT4730/EV1938"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative SB AudioPCI CT4730-?"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); } case CT5880_PCI_ID: switch(pci_get_revid(dev)) { case CT5880REV_CT5880_C: device_set_desc(dev, "Creative CT5880-C"); return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_D: device_set_desc(dev, "Creative CT5880-D"); return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_E: device_set_desc(dev, "Creative CT5880-E"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative CT5880-?"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); } default: return (ENXIO); } } static int sysctl_es137x_spdif_enable(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; uint32_t r; int err, new_en; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); ES_LOCK(es); r = es_rd(es, ES1370_REG_STATUS, 4); ES_UNLOCK(es); new_en = (r & ENABLE_SPDIF) ? 1 : 0; err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) return (err); if (new_en < 0 || new_en > 1) return (EINVAL); ES_LOCK(es); if (new_en) { r |= ENABLE_SPDIF; es->ctrl |= SPDIFEN_B; es->ctrl |= RECEN_B; } else { r &= ~ENABLE_SPDIF; es->ctrl &= ~SPDIFEN_B; es->ctrl &= ~RECEN_B; } es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1370_REG_STATUS, r, 4); ES_UNLOCK(es); return (0); } static int sysctl_es137x_latency_timer(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; uint32_t val; int err; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); ES_LOCK(es); val = pci_read_config(dev, PCIR_LATTIMER, 1); ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val > 255) return (EINVAL); ES_LOCK(es); pci_write_config(dev, PCIR_LATTIMER, val, 1); ES_UNLOCK(es); return (0); } static int sysctl_es137x_fixed_rate(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; uint32_t val; int err; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); ES_LOCK(es); val = ES_FIXED_RATE(es->escfg); if (val < es_caps.minspeed) val = 0; ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val != 0 && (val < es_caps.minspeed || val > es_caps.maxspeed)) return (EINVAL); ES_LOCK(es); if (es->ctrl & (CTRL_DAC2_EN|CTRL_ADC_EN)) { ES_UNLOCK(es); return (EBUSY); } if (val) { if (val != ES_FIXED_RATE(es->escfg)) { es->escfg = ES_SET_FIXED_RATE(es->escfg, val); es->ch[ES_DAC2].caps.maxspeed = val; es->ch[ES_DAC2].caps.minspeed = val; es->ch[ES_ADC].caps.maxspeed = val; es->ch[ES_ADC].caps.minspeed = val; es->ctrl &= ~CTRL_PCLKDIV; es->ctrl |= DAC2_SRTODIV(val) << CTRL_SH_PCLKDIV; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); } } else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); es->ch[ES_DAC2].caps = es_caps; es->ch[ES_ADC].caps = es_caps; } ES_UNLOCK(es); return (0); } static int sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) { struct es_info *es; struct snddev_info *d; struct snd_mixer *m; device_t dev; uint32_t val, set; int recsrc, level, err; dev = oidp->oid_arg1; d = device_get_softc(dev); if (!PCM_REGISTERED(d) || d->mixer_dev == NULL || d->mixer_dev->si_drv1 == NULL) return (EINVAL); es = d->devinfo; if (es == NULL) return (EINVAL); ES_LOCK(es); set = ES_SINGLE_PCM_MIX(es->escfg); val = set; ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (!(val == 0 || val == 1)) return (EINVAL); if (val == set) return (0); PCM_ACQUIRE_QUICK(d); m = (d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; if (m == NULL) { PCM_RELEASE_QUICK(d); return (ENODEV); } if (mixer_busy(m) != 0) { PCM_RELEASE_QUICK(d); return (EBUSY); } level = mix_get(m, SOUND_MIXER_PCM); recsrc = mix_getrecsrc(m); if (level < 0 || recsrc < 0) { PCM_RELEASE_QUICK(d); return (ENXIO); } ES_LOCK(es); if (es->ctrl & (CTRL_ADC_EN | CTRL_DAC1_EN | CTRL_DAC2_EN)) { ES_UNLOCK(es); PCM_RELEASE_QUICK(d); return (EBUSY); } if (val) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); ES_UNLOCK(es); if (!val) { mix_setdevs(m, mix_getdevs(m) | (1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(m) | (1 << SOUND_MIXER_SYNTH)); err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, (level >> 8) & 0x7f); } else { err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, (level >> 8) & 0x7f); mix_setdevs(m, mix_getdevs(m) & ~(1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(m) & ~(1 << SOUND_MIXER_SYNTH)); } if (!err) { level = recsrc; if (recsrc & (1 << SOUND_MIXER_PCM)) recsrc |= 1 << SOUND_MIXER_SYNTH; else if (recsrc & (1 << SOUND_MIXER_SYNTH)) recsrc |= 1 << SOUND_MIXER_PCM; if (level != recsrc) err = mix_setrecsrc(m, recsrc); } PCM_RELEASE_QUICK(d); return (err); } static int sysctl_es_polling(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; int err, val; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); if (es == NULL) return (EINVAL); ES_LOCK(es); val = es->polling; ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); ES_LOCK(es); if (val != es->polling) { if (es_chan_active(es) != 0) err = EBUSY; else if (val == 0) es->polling = 0; else es->polling = 1; } ES_UNLOCK(es); return (err); } static void es_init_sysctls(device_t dev) { struct es_info *es; int r, devid, revid; devid = pci_get_devid(dev); revid = pci_get_revid(dev); es = pcm_getdevinfo(dev); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_spdif_enable, "I", "Enable S/PDIF output on primary playback channel"); } else if (devid == ES1370_PCI_ID) { /* * Enable fixed rate sysctl if both DAC2 / ADC enabled. */ if (es->ch[ES_DAC2].channel != NULL && es->ch[ES_ADC].channel != NULL) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fixed_rate", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_fixed_rate, "I", "Enable fixed rate playback/recording"); } /* * Enable single pcm mixer sysctl if both DAC1/2 enabled. */ if (es->ch[ES_DAC1].channel != NULL && es->ch[ES_DAC2].channel != NULL) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "single_pcm_mixer", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_single_pcm_mixer, "I", "Single PCM mixer controller for both DAC1/DAC2"); } } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "latency_timer", &r) == 0 && !(r < 0 || r > 255)) pci_write_config(dev, PCIR_LATTIMER, r, 1); /* XXX: this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "latency_timer", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_latency_timer, "I", "PCI Latency Timer configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es_polling, "I", "Enable polling mode"); } static int es_pci_attach(device_t dev) { struct es_info *es = NULL; int mapped, i, numplay, dac_cfg; char status[SND_STATUSLEN]; struct ac97_info *codec = NULL; kobj_class_t ct = NULL; uint32_t devid; es = malloc(sizeof *es, M_DEVBUF, M_WAITOK | M_ZERO); es->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_es137x softc"); es->dev = dev; es->escfg = 0; mapped = 0; pci_enable_busmaster(dev); if (mapped == 0) { es->regid = MEM_MAP_REG; es->regtype = SYS_RES_MEMORY; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, RF_ACTIVE); if (es->reg) mapped++; } if (mapped == 0) { es->regid = PCIR_BAR(0); es->regtype = SYS_RES_IOPORT; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, RF_ACTIVE); if (es->reg) mapped++; } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto bad; } es->st = rman_get_bustag(es->reg); es->sh = rman_get_bushandle(es->reg); callout_init(&es->poll_timer, 1); es->poll_ticks = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) es->polling = 1; else es->polling = 0; es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= ES_BLK_ALIGN; if (i < ES_BLK_MIN) i = ES_BLK_MIN; es->blkcnt = es->bufsz / i; i = 0; while (es->blkcnt >> i) i++; es->blkcnt = 1 << (i - 1); if (es->blkcnt < ES_DMA_SEGS_MIN) es->blkcnt = ES_DMA_SEGS_MIN; else if (es->blkcnt > ES_DMA_SEGS_MAX) es->blkcnt = ES_DMA_SEGS_MAX; } else es->blkcnt = 2; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &dac_cfg) == 0) { if (dac_cfg < 0 || dac_cfg > 3) dac_cfg = ES_DEFAULT_DAC_CFG; } else dac_cfg = ES_DEFAULT_DAC_CFG; switch (dac_cfg) { case 0: /* Enable all DAC: DAC1, DAC2 */ numplay = 2; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); break; case 1: /* Only DAC1 */ numplay = 1; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); break; case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ numplay = 2; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); break; case 2: /* Only DAC2 */ default: numplay = 1; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); break; } es->escfg = ES_SET_NUMPLAY(es->escfg, numplay); es->escfg = ES_SET_NUMREC(es->escfg, 1); devid = pci_get_devid(dev); switch (devid) { case ES1371_PCI_ID: case ES1371_PCI_ID2: case CT5880_PCI_ID: case CT4730_PCI_ID: es1371_init(es); codec = AC97_CREATE(dev, es, es1371_ac97); if (codec == NULL) goto bad; /* our init routine does everything for us */ /* set to NULL; flag mixer_init not to run the ac97_init */ /* ac97_mixer.init = NULL; */ if (mixer_init(dev, ac97_getmixerclass(), codec)) goto bad; ct = &eschan1371_class; break; case ES1370_PCI_ID: es1370_init(es); /* * Disable fixed rate operation if DAC2 disabled. * This is a special case for es1370 only, where the * speed of both ADC and DAC2 locked together. */ if (!ES_DAC2_ENABLED(es->escfg)) es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; break; default: goto bad; /* NOTREACHED */ } es->irqid = 0; es->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &es->irqid, RF_ACTIVE | RF_SHAREABLE); if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, es, &es->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/es->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &es->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } - snprintf(status, SND_STATUSLEN, "at %s 0x%jx irq %jd %s", - (es->regtype == SYS_RES_IOPORT)? "io" : "memory", + snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", + (es->regtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(es->reg), rman_get_start(es->irq), - PCM_KLDSTRING(snd_es137x)); + device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, es, numplay, 1)) goto bad; for (i = 0; i < numplay; i++) pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_addchan(dev, PCMDIR_REC, ct, es); es_init_sysctls(dev); pcm_setstatus(dev, status); es->escfg = ES_SET_GP(es->escfg, 0); if (numplay == 1) device_printf(dev, "\n", ES_DAC_FIRST(es->escfg) + 1); else if (numplay == 2) device_printf(dev, "\n", ES_DAC_FIRST(es->escfg) + 1, ES_DAC_SECOND(es->escfg) + 1); return (0); bad: if (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); if (es->ih) bus_teardown_intr(dev, es->irq, es->ih); if (es->irq) bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); if (codec) ac97_destroy(codec); if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); if (es->lock) snd_mtxfree(es->lock); if (es) free(es, M_DEVBUF); return (ENXIO); } static int es_pci_detach(device_t dev) { int r; struct es_info *es; r = pcm_unregister(dev); if (r) return (r); es = pcm_getdevinfo(dev); if (es != NULL && es->num != 0) { ES_LOCK(es); es->polling = 0; callout_stop(&es->poll_timer); ES_UNLOCK(es); callout_drain(&es->poll_timer); } bus_teardown_intr(dev, es->irq, es->ih); bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); bus_release_resource(dev, es->regtype, es->regid, es->reg); bus_dma_tag_destroy(es->parent_dmat); snd_mtxfree(es->lock); free(es, M_DEVBUF); return (0); } static device_method_t es_methods[] = { /* Device interface */ DEVMETHOD(device_probe, es_pci_probe), DEVMETHOD(device_attach, es_pci_attach), DEVMETHOD(device_detach, es_pci_detach), { 0, 0 } }; static driver_t es_driver = { "pcm", es_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_es137x, pci, es_driver, 0, 0); MODULE_DEPEND(snd_es137x, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_es137x, 1); diff --git a/sys/dev/sound/pci/fm801.c b/sys/dev/sound/pci/fm801.c index d7aa731175bb..fbde14f37afe 100644 --- a/sys/dev/sound/pci/fm801.c +++ b/sys/dev/sound/pci/fm801.c @@ -1,762 +1,763 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Dmitry Dicky diwil@dataart.com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #define PCI_VENDOR_FORTEMEDIA 0x1319 #define PCI_DEVICE_FORTEMEDIA1 0x08011319 /* Audio controller */ #define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* Joystick controller */ #define FM_PCM_VOLUME 0x00 #define FM_FM_VOLUME 0x02 #define FM_I2S_VOLUME 0x04 #define FM_RECORD_SOURCE 0x06 #define FM_PLAY_CTL 0x08 #define FM_PLAY_RATE_MASK 0x0f00 #define FM_PLAY_BUF1_LAST 0x0001 #define FM_PLAY_BUF2_LAST 0x0002 #define FM_PLAY_START 0x0020 #define FM_PLAY_PAUSE 0x0040 #define FM_PLAY_STOPNOW 0x0080 #define FM_PLAY_16BIT 0x4000 #define FM_PLAY_STEREO 0x8000 #define FM_PLAY_DMALEN 0x0a #define FM_PLAY_DMABUF1 0x0c #define FM_PLAY_DMABUF2 0x10 #define FM_REC_CTL 0x14 #define FM_REC_RATE_MASK 0x0f00 #define FM_REC_BUF1_LAST 0x0001 #define FM_REC_BUF2_LAST 0x0002 #define FM_REC_START 0x0020 #define FM_REC_PAUSE 0x0040 #define FM_REC_STOPNOW 0x0080 #define FM_REC_16BIT 0x4000 #define FM_REC_STEREO 0x8000 #define FM_REC_DMALEN 0x16 #define FM_REC_DMABUF1 0x18 #define FM_REC_DMABUF2 0x1c #define FM_CODEC_CTL 0x22 #define FM_VOLUME 0x26 #define FM_VOLUME_MUTE 0x8000 #define FM_CODEC_CMD 0x2a #define FM_CODEC_CMD_READ 0x0080 #define FM_CODEC_CMD_VALID 0x0100 #define FM_CODEC_CMD_BUSY 0x0200 #define FM_CODEC_DATA 0x2c #define FM_IO_CTL 0x52 #define FM_CARD_CTL 0x54 #define FM_INTMASK 0x56 #define FM_INTMASK_PLAY 0x0001 #define FM_INTMASK_REC 0x0002 #define FM_INTMASK_VOL 0x0040 #define FM_INTMASK_MPU 0x0080 #define FM_INTSTATUS 0x5a #define FM_INTSTATUS_PLAY 0x0100 #define FM_INTSTATUS_REC 0x0200 #define FM_INTSTATUS_VOL 0x4000 #define FM_INTSTATUS_MPU 0x8000 #define FM801_DEFAULT_BUFSZ 4096 /* Other values do not work!!! */ /* debug purposes */ #define DPRINT if(0) printf /* static int fm801ch_setup(struct pcm_channel *c); */ static u_int32_t fmts[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps fm801ch_caps = { 5500, 48000, fmts, 0 }; struct fm801_info; struct fm801_chinfo { struct fm801_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t spd, dir, fmt; /* speed, direction, format */ u_int32_t shift; }; struct fm801_info { int type; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; device_t dev; int num; u_int32_t unit; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; u_int32_t play_flip, play_nextblk, play_start, play_blksize, play_fmt, play_shift, play_size; u_int32_t rec_flip, rec_nextblk, rec_start, rec_blksize, rec_fmt, rec_shift, rec_size; unsigned int bufsz; struct fm801_chinfo pch, rch; device_t radio; }; /* Bus Read / Write routines */ static u_int32_t fm801_rd(struct fm801_info *fm801, int regno, int size) { switch(size) { case 1: return (bus_space_read_1(fm801->st, fm801->sh, regno)); case 2: return (bus_space_read_2(fm801->st, fm801->sh, regno)); case 4: return (bus_space_read_4(fm801->st, fm801->sh, regno)); default: return 0xffffffff; } } static void fm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(fm801->st, fm801->sh, regno, data); break; case 2: bus_space_write_2(fm801->st, fm801->sh, regno, data); break; case 4: bus_space_write_4(fm801->st, fm801->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* * ac97 codec routines */ #define TIMO 50 static int fm801_rdcd(kobj_t obj, void *devinfo, int regno) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; int i; for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 rdcd: 1 - DELAY\n"); } if (i >= TIMO) { printf("fm801 rdcd: codec busy\n"); return 0; } fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2); for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID); i++) { DELAY(10000); DPRINT("fm801 rdcd: 2 - DELAY\n"); } if (i >= TIMO) { printf("fm801 rdcd: write codec invalid\n"); return 0; } return fm801_rd(fm801,FM_CODEC_DATA,2); } static int fm801_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; int i; DPRINT("fm801_wrcd reg 0x%x val 0x%x\n",regno, data); /* if(regno == AC97_REG_RECSEL) return; */ /* Poll until codec is ready */ for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 rdcd: 1 - DELAY\n"); } if (i >= TIMO) { printf("fm801 wrcd: read codec busy\n"); return -1; } fm801_wr(fm801,FM_CODEC_DATA,data, 2); fm801_wr(fm801,FM_CODEC_CMD, regno,2); /* wait until codec is ready */ for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 wrcd: 2 - DELAY\n"); } if (i >= TIMO) { printf("fm801 wrcd: read codec busy\n"); return -1; } DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data); return 0; } static kobj_method_t fm801_ac97_methods[] = { KOBJMETHOD(ac97_read, fm801_rdcd), KOBJMETHOD(ac97_write, fm801_wrcd), DEVMETHOD_END }; AC97_DECLARE(fm801_ac97); /* -------------------------------------------------------------------- */ /* * The interrupt handler */ static void fm801_intr(void *p) { struct fm801_info *fm801 = (struct fm801_info *)p; u_int32_t intsrc = fm801_rd(fm801, FM_INTSTATUS, 2); DPRINT("\nfm801_intr intsrc 0x%x ", intsrc); if(intsrc & FM_INTSTATUS_PLAY) { fm801->play_flip++; if(fm801->play_flip & 1) { fm801_wr(fm801, FM_PLAY_DMABUF1, fm801->play_start,4); } else fm801_wr(fm801, FM_PLAY_DMABUF2, fm801->play_nextblk,4); chn_intr(fm801->pch.channel); } if(intsrc & FM_INTSTATUS_REC) { fm801->rec_flip++; if(fm801->rec_flip & 1) { fm801_wr(fm801, FM_REC_DMABUF1, fm801->rec_start,4); } else fm801_wr(fm801, FM_REC_DMABUF2, fm801->rec_nextblk,4); chn_intr(fm801->rch.channel); } if ( intsrc & FM_INTSTATUS_MPU ) { /* This is a TODOish thing... */ fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_MPU,2); } if ( intsrc & FM_INTSTATUS_VOL ) { /* This is a TODOish thing... */ fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_VOL,2); } DPRINT("fm801_intr clear\n\n"); fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 2); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * fm801ch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch; DPRINT("fm801ch_init, direction = %d\n", dir); ch->parent = fm801; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, 0, fm801->bufsz) != 0) return NULL; return (void *)ch; } static int fm801ch_setformat(kobj_t obj, void *data, u_int32_t format) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; DPRINT("fm801ch_setformat 0x%x : %s, %s, %s, %s\n", format, (AFMT_CHANNEL(format) > 1)?"stereo":"mono", (format & AFMT_16BIT) ? "16bit":"8bit", (format & AFMT_SIGNED)? "signed":"unsigned", (format & AFMT_BIGENDIAN)?"bigendiah":"littleendian" ); if(ch->dir == PCMDIR_PLAY) { fm801->play_fmt = (AFMT_CHANNEL(format) > 1)? FM_PLAY_STEREO : 0; fm801->play_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0; return 0; } if(ch->dir == PCMDIR_REC ) { fm801->rec_fmt = (AFMT_CHANNEL(format) > 1)? FM_REC_STEREO:0; fm801->rec_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0; return 0; } return 0; } struct { u_int32_t limit; u_int32_t rate; } fm801_rates[11] = { { 6600, 5500 }, { 8750, 8000 }, { 10250, 9600 }, { 13200, 11025 }, { 17500, 16000 }, { 20500, 19200 }, { 26500, 22050 }, { 35000, 32000 }, { 41000, 38400 }, { 46000, 44100 }, { 48000, 48000 }, /* anything above -> 48000 */ }; static u_int32_t fm801ch_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; int i; for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ; if(ch->dir == PCMDIR_PLAY) { fm801->pch.spd = fm801_rates[i].rate; fm801->play_shift = (i<<8); fm801->play_shift &= FM_PLAY_RATE_MASK; } if(ch->dir == PCMDIR_REC ) { fm801->rch.spd = fm801_rates[i].rate; fm801->rec_shift = (i<<8); fm801->rec_shift &= FM_REC_RATE_MASK; } ch->spd = fm801_rates[i].rate; return fm801_rates[i].rate; } static u_int32_t fm801ch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; /* * Don't mind for play_flip, set the blocksize to the * desired values in any case - otherwise sound playback * breaks here. */ if(ch->dir == PCMDIR_PLAY) fm801->play_blksize = blocksize; if(ch->dir == PCMDIR_REC) fm801->rec_blksize = blocksize; DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir); return blocksize; } static int fm801ch_trigger(kobj_t obj, void *data, int go) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; u_int32_t baseaddr = sndbuf_getbufaddr(ch->buffer); u_int32_t k1; DPRINT("fm801ch_trigger go %d , ", go); if (!PCMTRIG_COMMON(go)) return 0; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { fm801->play_start = baseaddr; fm801->play_nextblk = fm801->play_start + fm801->play_blksize; fm801->play_flip = 0; fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2); fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4); fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4); fm801_wr(fm801, FM_PLAY_CTL, FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt | fm801->play_shift, 2 ); } else { fm801->play_flip = 0; k1 = fm801_rd(fm801, FM_PLAY_CTL,2); fm801_wr(fm801, FM_PLAY_CTL, (k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) | FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 ); } } else if(ch->dir == PCMDIR_REC) { if (go == PCMTRIG_START) { fm801->rec_start = baseaddr; fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize; fm801->rec_flip = 0; fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2); fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4); fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4); fm801_wr(fm801, FM_REC_CTL, FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt | fm801->rec_shift, 2 ); } else { fm801->rec_flip = 0; k1 = fm801_rd(fm801, FM_REC_CTL,2); fm801_wr(fm801, FM_REC_CTL, (k1 & ~(FM_REC_STOPNOW | FM_REC_START)) | FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2); } } return 0; } /* Almost ALSA copy */ static u_int32_t fm801ch_getptr(kobj_t obj, void *data) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; u_int32_t result = 0; if (ch->dir == PCMDIR_PLAY) { result = fm801_rd(fm801, (fm801->play_flip&1) ? FM_PLAY_DMABUF2:FM_PLAY_DMABUF1, 4) - fm801->play_start; } if (ch->dir == PCMDIR_REC) { result = fm801_rd(fm801, (fm801->rec_flip&1) ? FM_REC_DMABUF2:FM_REC_DMABUF1, 4) - fm801->rec_start; } return result; } static struct pcmchan_caps * fm801ch_getcaps(kobj_t obj, void *data) { return &fm801ch_caps; } static kobj_method_t fm801ch_methods[] = { KOBJMETHOD(channel_init, fm801ch_init), KOBJMETHOD(channel_setformat, fm801ch_setformat), KOBJMETHOD(channel_setspeed, fm801ch_setspeed), KOBJMETHOD(channel_setblocksize, fm801ch_setblocksize), KOBJMETHOD(channel_trigger, fm801ch_trigger), KOBJMETHOD(channel_getptr, fm801ch_getptr), KOBJMETHOD(channel_getcaps, fm801ch_getcaps), DEVMETHOD_END }; CHANNEL_DECLARE(fm801ch); /* -------------------------------------------------------------------- */ /* * Init routine is taken from an original NetBSD driver */ static int fm801_init(struct fm801_info *fm801) { u_int32_t k1; /* reset codec */ fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2); DELAY(100000); fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2); DELAY(100000); fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2); fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2); fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2); fm801_wr(fm801, 0x40,0x107f,2); /* enable legacy audio */ fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2); /* Unmask playback, record and mpu interrupts, mask the rest */ k1 = fm801_rd((void *)fm801, FM_INTMASK,2); fm801_wr(fm801, FM_INTMASK, (k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) | FM_INTMASK_VOL,2); fm801_wr(fm801, FM_INTSTATUS, FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU | FM_INTSTATUS_VOL,2); DPRINT("FM801 init Ok\n"); return 0; } static int fm801_pci_attach(device_t dev) { struct ac97_info *codec = NULL; struct fm801_info *fm801; int i; int mapped = 0; char status[SND_STATUSLEN]; fm801 = malloc(sizeof(*fm801), M_DEVBUF, M_WAITOK | M_ZERO); fm801->type = pci_get_devid(dev); pci_enable_busmaster(dev); for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { fm801->regid = PCIR_BAR(i); fm801->regtype = SYS_RES_MEMORY; fm801->reg = bus_alloc_resource_any(dev, fm801->regtype, &fm801->regid, RF_ACTIVE); if(!fm801->reg) { fm801->regtype = SYS_RES_IOPORT; fm801->reg = bus_alloc_resource_any(dev, fm801->regtype, &fm801->regid, RF_ACTIVE); } if(fm801->reg) { fm801->st = rman_get_bustag(fm801->reg); fm801->sh = rman_get_bushandle(fm801->reg); mapped++; } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto oops; } fm801->bufsz = pcm_getbuffersize(dev, 4096, FM801_DEFAULT_BUFSZ, 65536); fm801_init(fm801); codec = AC97_CREATE(dev, fm801, fm801_ac97); if (codec == NULL) goto oops; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto oops; fm801->irqid = 0; fm801->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &fm801->irqid, RF_ACTIVE | RF_SHAREABLE); if (!fm801->irq || snd_setup_intr(dev, fm801->irq, 0, fm801_intr, fm801, &fm801->ih)) { device_printf(dev, "unable to map interrupt\n"); goto oops; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/fm801->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &fm801->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto oops; } - snprintf(status, 64, "at %s 0x%jx irq %jd %s", - (fm801->regtype == SYS_RES_IOPORT)? "io" : "memory", - rman_get_start(fm801->reg), rman_get_start(fm801->irq),PCM_KLDSTRING(snd_fm801)); + snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", + (fm801->regtype == SYS_RES_IOPORT)? "port" : "mem", + rman_get_start(fm801->reg), rman_get_start(fm801->irq), + device_get_nameunit(device_get_parent(dev))); #define FM801_MAXPLAYCH 1 if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops; pcm_addchan(dev, PCMDIR_PLAY, &fm801ch_class, fm801); pcm_addchan(dev, PCMDIR_REC, &fm801ch_class, fm801); pcm_setstatus(dev, status); fm801->radio = device_add_child(dev, "radio", -1); bus_generic_attach(dev); return 0; oops: if (codec) ac97_destroy(codec); if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg); if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih); if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq); if (fm801->parent_dmat) bus_dma_tag_destroy(fm801->parent_dmat); free(fm801, M_DEVBUF); return ENXIO; } static int fm801_pci_detach(device_t dev) { int r; struct fm801_info *fm801; DPRINT("Forte Media FM801 detach\n"); fm801 = pcm_getdevinfo(dev); r = bus_generic_detach(dev); if (r) return r; if (fm801->radio != NULL) { r = device_delete_child(dev, fm801->radio); if (r) return r; fm801->radio = NULL; } r = pcm_unregister(dev); if (r) return r; bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg); bus_teardown_intr(dev, fm801->irq, fm801->ih); bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq); bus_dma_tag_destroy(fm801->parent_dmat); free(fm801, M_DEVBUF); return 0; } static int fm801_pci_probe( device_t dev ) { int id; if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) { device_set_desc(dev, "Forte Media FM801 Audio Controller"); return BUS_PROBE_DEFAULT; } /* if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) { device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)"); return ENXIO; } */ return ENXIO; } static struct resource * fm801_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct fm801_info *fm801; fm801 = pcm_getdevinfo(bus); if (type == SYS_RES_IOPORT && *rid == PCIR_BAR(0)) return (fm801->reg); return (NULL); } static int fm801_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { return (0); } static device_method_t fm801_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fm801_pci_probe), DEVMETHOD(device_attach, fm801_pci_attach), DEVMETHOD(device_detach, fm801_pci_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_alloc_resource, fm801_alloc_resource), DEVMETHOD(bus_release_resource, fm801_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD_END }; static driver_t fm801_driver = { "pcm", fm801_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_fm801, pci, fm801_driver, 0, 0); MODULE_DEPEND(snd_fm801, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_fm801, 1); diff --git a/sys/dev/sound/pci/hda/hdaa.c b/sys/dev/sound/pci/hda/hdaa.c index 1831ae0c1f19..e64eac6114e4 100644 --- a/sys/dev/sound/pci/hda/hdaa.c +++ b/sys/dev/sound/pci/hda/hdaa.c @@ -1,7157 +1,7156 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 Stephane E. Potvin * Copyright (c) 2006 Ariff Abdullah * Copyright (c) 2008-2012 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Intel High Definition Audio (Audio function) driver for FreeBSD. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" #define hdaa_lock(devinfo) snd_mtxlock((devinfo)->lock) #define hdaa_unlock(devinfo) snd_mtxunlock((devinfo)->lock) #define hdaa_lockassert(devinfo) snd_mtxassert((devinfo)->lock) static const struct { const char *key; uint32_t value; } hdaa_quirks_tab[] = { { "softpcmvol", HDAA_QUIRK_SOFTPCMVOL }, { "fixedrate", HDAA_QUIRK_FIXEDRATE }, { "forcestereo", HDAA_QUIRK_FORCESTEREO }, { "eapdinv", HDAA_QUIRK_EAPDINV }, { "senseinv", HDAA_QUIRK_SENSEINV }, { "ivref50", HDAA_QUIRK_IVREF50 }, { "ivref80", HDAA_QUIRK_IVREF80 }, { "ivref100", HDAA_QUIRK_IVREF100 }, { "ovref50", HDAA_QUIRK_OVREF50 }, { "ovref80", HDAA_QUIRK_OVREF80 }, { "ovref100", HDAA_QUIRK_OVREF100 }, { "ivref", HDAA_QUIRK_IVREF }, { "ovref", HDAA_QUIRK_OVREF }, { "vref", HDAA_QUIRK_VREF }, }; #define HDA_PARSE_MAXDEPTH 10 MALLOC_DEFINE(M_HDAA, "hdaa", "HDA Audio"); static const char *HDA_COLORS[16] = {"Unknown", "Black", "Grey", "Blue", "Green", "Red", "Orange", "Yellow", "Purple", "Pink", "Res.A", "Res.B", "Res.C", "Res.D", "White", "Other"}; static const char *HDA_DEVS[16] = {"Line-out", "Speaker", "Headphones", "CD", "SPDIF-out", "Digital-out", "Modem-line", "Modem-handset", "Line-in", "AUX", "Mic", "Telephony", "SPDIF-in", "Digital-in", "Res.E", "Other"}; static const char *HDA_CONNS[4] = {"Jack", "None", "Fixed", "Both"}; static const char *HDA_CONNECTORS[16] = { "Unknown", "1/8", "1/4", "ATAPI", "RCA", "Optical", "Digital", "Analog", "DIN", "XLR", "RJ-11", "Combo", "0xc", "0xd", "0xe", "Other" }; static const char *HDA_LOCS[64] = { "0x00", "Rear", "Front", "Left", "Right", "Top", "Bottom", "Rear-panel", "Drive-bay", "0x09", "0x0a", "0x0b", "0x0c", "0x0d", "0x0e", "0x0f", "Internal", "0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "Riser", "0x18", "Onboard", "0x1a", "0x1b", "0x1c", "0x1d", "0x1e", "0x1f", "External", "Ext-Rear", "Ext-Front", "Ext-Left", "Ext-Right", "Ext-Top", "Ext-Bottom", "0x07", "0x28", "0x29", "0x2a", "0x2b", "0x2c", "0x2d", "0x2e", "0x2f", "Other", "0x31", "0x32", "0x33", "0x34", "0x35", "Other-Bott", "Lid-In", "Lid-Out", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "0x3f" }; static const char *HDA_GPIO_ACTIONS[8] = { "keep", "set", "clear", "disable", "input", "0x05", "0x06", "0x07"}; static const char *HDA_HDMI_CODING_TYPES[18] = { "undefined", "LPCM", "AC-3", "MPEG1", "MP3", "MPEG2", "AAC-LC", "DTS", "ATRAC", "DSD", "E-AC-3", "DTS-HD", "MLP", "DST", "WMAPro", "HE-AAC", "HE-AACv2", "MPEG-Surround" }; /* Default */ static uint32_t hdaa_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps hdaa_caps = {48000, 48000, hdaa_fmt, 0}; static const struct { uint32_t rate; int valid; uint16_t base; uint16_t mul; uint16_t div; } hda_rate_tab[] = { { 8000, 1, 0x0000, 0x0000, 0x0500 }, /* (48000 * 1) / 6 */ { 9600, 0, 0x0000, 0x0000, 0x0400 }, /* (48000 * 1) / 5 */ { 12000, 0, 0x0000, 0x0000, 0x0300 }, /* (48000 * 1) / 4 */ { 16000, 1, 0x0000, 0x0000, 0x0200 }, /* (48000 * 1) / 3 */ { 18000, 0, 0x0000, 0x1000, 0x0700 }, /* (48000 * 3) / 8 */ { 19200, 0, 0x0000, 0x0800, 0x0400 }, /* (48000 * 2) / 5 */ { 24000, 0, 0x0000, 0x0000, 0x0100 }, /* (48000 * 1) / 2 */ { 28800, 0, 0x0000, 0x1000, 0x0400 }, /* (48000 * 3) / 5 */ { 32000, 1, 0x0000, 0x0800, 0x0200 }, /* (48000 * 2) / 3 */ { 36000, 0, 0x0000, 0x1000, 0x0300 }, /* (48000 * 3) / 4 */ { 38400, 0, 0x0000, 0x1800, 0x0400 }, /* (48000 * 4) / 5 */ { 48000, 1, 0x0000, 0x0000, 0x0000 }, /* (48000 * 1) / 1 */ { 64000, 0, 0x0000, 0x1800, 0x0200 }, /* (48000 * 4) / 3 */ { 72000, 0, 0x0000, 0x1000, 0x0100 }, /* (48000 * 3) / 2 */ { 96000, 1, 0x0000, 0x0800, 0x0000 }, /* (48000 * 2) / 1 */ { 144000, 0, 0x0000, 0x1000, 0x0000 }, /* (48000 * 3) / 1 */ { 192000, 1, 0x0000, 0x1800, 0x0000 }, /* (48000 * 4) / 1 */ { 8820, 0, 0x4000, 0x0000, 0x0400 }, /* (44100 * 1) / 5 */ { 11025, 1, 0x4000, 0x0000, 0x0300 }, /* (44100 * 1) / 4 */ { 12600, 0, 0x4000, 0x0800, 0x0600 }, /* (44100 * 2) / 7 */ { 14700, 0, 0x4000, 0x0000, 0x0200 }, /* (44100 * 1) / 3 */ { 17640, 0, 0x4000, 0x0800, 0x0400 }, /* (44100 * 2) / 5 */ { 18900, 0, 0x4000, 0x1000, 0x0600 }, /* (44100 * 3) / 7 */ { 22050, 1, 0x4000, 0x0000, 0x0100 }, /* (44100 * 1) / 2 */ { 25200, 0, 0x4000, 0x1800, 0x0600 }, /* (44100 * 4) / 7 */ { 26460, 0, 0x4000, 0x1000, 0x0400 }, /* (44100 * 3) / 5 */ { 29400, 0, 0x4000, 0x0800, 0x0200 }, /* (44100 * 2) / 3 */ { 33075, 0, 0x4000, 0x1000, 0x0300 }, /* (44100 * 3) / 4 */ { 35280, 0, 0x4000, 0x1800, 0x0400 }, /* (44100 * 4) / 5 */ { 44100, 1, 0x4000, 0x0000, 0x0000 }, /* (44100 * 1) / 1 */ { 58800, 0, 0x4000, 0x1800, 0x0200 }, /* (44100 * 4) / 3 */ { 66150, 0, 0x4000, 0x1000, 0x0100 }, /* (44100 * 3) / 2 */ { 88200, 1, 0x4000, 0x0800, 0x0000 }, /* (44100 * 2) / 1 */ { 132300, 0, 0x4000, 0x1000, 0x0000 }, /* (44100 * 3) / 1 */ { 176400, 1, 0x4000, 0x1800, 0x0000 }, /* (44100 * 4) / 1 */ }; #define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0])) const static char *ossnames[] = SOUND_DEVICE_NAMES; /**************************************************************************** * Function prototypes ****************************************************************************/ static int hdaa_pcmchannel_setup(struct hdaa_chan *); static void hdaa_widget_connection_select(struct hdaa_widget *, uint8_t); static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *, uint32_t, int, int); static struct hdaa_audio_ctl *hdaa_audio_ctl_amp_get(struct hdaa_devinfo *, nid_t, int, int, int); static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *, nid_t, int, int, int, int, int, int); static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf); static char * hdaa_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len) { int i, first = 1; bzero(buf, len); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mask & (1 << i)) { if (first == 0) strlcat(buf, ", ", len); strlcat(buf, ossnames[i], len); first = 0; } } return (buf); } static struct hdaa_audio_ctl * hdaa_audio_ctl_each(struct hdaa_devinfo *devinfo, int *index) { if (devinfo == NULL || index == NULL || devinfo->ctl == NULL || devinfo->ctlcnt < 1 || *index < 0 || *index >= devinfo->ctlcnt) return (NULL); return (&devinfo->ctl[(*index)++]); } static struct hdaa_audio_ctl * hdaa_audio_ctl_amp_get(struct hdaa_devinfo *devinfo, nid_t nid, int dir, int index, int cnt) { struct hdaa_audio_ctl *ctl; int i, found = 0; if (devinfo == NULL || devinfo->ctl == NULL) return (NULL); i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0) continue; if (ctl->widget->nid != nid) continue; if (dir && ctl->ndir != dir) continue; if (index >= 0 && ctl->ndir == HDAA_CTL_IN && ctl->dir == ctl->ndir && ctl->index != index) continue; found++; if (found == cnt || cnt <= 0) return (ctl); } return (NULL); } static const struct matrix { struct pcmchan_matrix m; int analog; } matrixes[] = { { SND_CHN_MATRIX_MAP_1_0, 1 }, { SND_CHN_MATRIX_MAP_2_0, 1 }, { SND_CHN_MATRIX_MAP_2_1, 0 }, { SND_CHN_MATRIX_MAP_3_0, 0 }, { SND_CHN_MATRIX_MAP_3_1, 0 }, { SND_CHN_MATRIX_MAP_4_0, 1 }, { SND_CHN_MATRIX_MAP_4_1, 0 }, { SND_CHN_MATRIX_MAP_5_0, 0 }, { SND_CHN_MATRIX_MAP_5_1, 1 }, { SND_CHN_MATRIX_MAP_6_0, 0 }, { SND_CHN_MATRIX_MAP_6_1, 0 }, { SND_CHN_MATRIX_MAP_7_0, 0 }, { SND_CHN_MATRIX_MAP_7_1, 1 }, }; static const char *channel_names[] = SND_CHN_T_NAMES; /* * Connected channels change handler. */ static void hdaa_channels_handler(struct hdaa_audio_as *as) { struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_chan *ch = &devinfo->chans[as->chans[0]]; struct hdaa_widget *w; uint8_t *eld; int i, total, sub, assume, channels; uint16_t cpins, upins, tpins; cpins = upins = 0; eld = NULL; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL) continue; if (w->wclass.pin.connected == 1) cpins |= (1 << i); else if (w->wclass.pin.connected != 0) upins |= (1 << i); if (w->eld != NULL && w->eld_len >= 8) eld = w->eld; } tpins = cpins | upins; if (as->hpredir >= 0) tpins &= 0x7fff; if (tpins == 0) tpins = as->pinset; total = sub = assume = channels = 0; if (eld) { /* Map CEA speakers to sound(4) channels. */ if (eld[7] & 0x01) /* Front Left/Right */ channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; if (eld[7] & 0x02) /* Low Frequency Effect */ channels |= SND_CHN_T_MASK_LF; if (eld[7] & 0x04) /* Front Center */ channels |= SND_CHN_T_MASK_FC; if (eld[7] & 0x08) { /* Rear Left/Right */ /* If we have both RLR and RLRC, report RLR as side. */ if (eld[7] & 0x40) /* Rear Left/Right Center */ channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR; else channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; } if (eld[7] & 0x10) /* Rear center */ channels |= SND_CHN_T_MASK_BC; if (eld[7] & 0x20) /* Front Left/Right Center */ channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC; if (eld[7] & 0x40) /* Rear Left/Right Center */ channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; } else if (as->pinset != 0 && (tpins & 0xffe0) == 0) { /* Map UAA speakers to sound(4) channels. */ if (tpins & 0x0001) channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; if (tpins & 0x0002) channels |= SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF; if (tpins & 0x0004) channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; if (tpins & 0x0008) channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC; if (tpins & 0x0010) { /* If there is no back pin, report side as back. */ if ((as->pinset & 0x0004) == 0) channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; else channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR; } } else if (as->mixed) { /* Mixed assoc can be only stereo or theoretically mono. */ if (ch->channels == 1) channels |= SND_CHN_T_MASK_FC; else channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; } if (channels) { /* We have some usable channels info. */ HDA_BOOTVERBOSE( device_printf(pdevinfo->dev, "%s channel set is: ", as->dir == HDAA_CTL_OUT ? "Playback" : "Recording"); for (i = 0; i < SND_CHN_T_MAX; i++) if (channels & (1 << i)) printf("%s, ", channel_names[i]); printf("\n"); ); /* Look for maximal fitting matrix. */ for (i = 0; i < sizeof(matrixes) / sizeof(struct matrix); i++) { if (as->pinset != 0 && matrixes[i].analog == 0) continue; if ((matrixes[i].m.mask & ~channels) == 0) { total = matrixes[i].m.channels; sub = matrixes[i].m.ext; } } } if (total == 0) { assume = 1; total = ch->channels; sub = (total == 6 || total == 8) ? 1 : 0; } HDA_BOOTVERBOSE( device_printf(pdevinfo->dev, "%s channel matrix is: %s%d.%d (%s)\n", as->dir == HDAA_CTL_OUT ? "Playback" : "Recording", assume ? "unknown, assuming " : "", total - sub, sub, cpins != 0 ? "connected" : (upins != 0 ? "unknown" : "disconnected")); ); } /* * Headphones redirection change handler. */ static void hdaa_hpredir_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_audio_as *as = &devinfo->as[w->bindas]; struct hdaa_widget *w1; struct hdaa_audio_ctl *ctl; uint32_t val; int j, connected = w->wclass.pin.connected; HDA_BOOTVERBOSE( device_printf((as->pdevinfo && as->pdevinfo->dev) ? as->pdevinfo->dev : devinfo->dev, "Redirect output to: %s\n", connected ? "headphones": "main"); ); /* (Un)Mute headphone pin. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, -1, 1); if (ctl != NULL && ctl->mute) { /* If pin has muter - use it. */ val = connected ? 0 : 1; if (val != ctl->forcemute) { ctl->forcemute = val; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); } } else { /* If there is no muter - disable pin output. */ if (connected) val = w->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w->wclass.pin.ctrl) { w->wclass.pin.ctrl = val; hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid, w->wclass.pin.ctrl)); } } /* (Un)Mute other pins. */ for (j = 0; j < 15; j++) { if (as->pins[j] <= 0) continue; ctl = hdaa_audio_ctl_amp_get(devinfo, as->pins[j], HDAA_CTL_IN, -1, 1); if (ctl != NULL && ctl->mute) { /* If pin has muter - use it. */ val = connected ? 1 : 0; if (val == ctl->forcemute) continue; ctl->forcemute = val; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); continue; } /* If there is no muter - disable pin output. */ w1 = hdaa_widget_get(devinfo, as->pins[j]); if (w1 != NULL) { if (connected) val = w1->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w1->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w1->wclass.pin.ctrl) { w1->wclass.pin.ctrl = val; hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w1->nid, w1->wclass.pin.ctrl)); } } } } /* * Recording source change handler. */ static void hdaa_autorecsrc_handler(struct hdaa_audio_as *as, struct hdaa_widget *w) { struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo; struct hdaa_widget *w1; int i, mask, fullmask, prio, bestprio; char buf[128]; if (!as->mixed || pdevinfo == NULL || pdevinfo->mixer == NULL) return; /* Don't touch anything if we asked not to. */ if (pdevinfo->autorecsrc == 0 || (pdevinfo->autorecsrc == 1 && w != NULL)) return; /* Don't touch anything if "mix" or "speaker" selected. */ if (pdevinfo->recsrc & (SOUND_MASK_IMIX | SOUND_MASK_SPEAKER)) return; /* Don't touch anything if several selected. */ if (ffs(pdevinfo->recsrc) != fls(pdevinfo->recsrc)) return; devinfo = pdevinfo->devinfo; mask = fullmask = 0; bestprio = 0; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w1 = hdaa_widget_get(devinfo, as->pins[i]); if (w1 == NULL || w1->enable == 0) continue; if (w1->wclass.pin.connected == 0) continue; prio = (w1->wclass.pin.connected == 1) ? 2 : 1; if (prio < bestprio) continue; if (prio > bestprio) { mask = 0; bestprio = prio; } mask |= (1 << w1->ossdev); fullmask |= (1 << w1->ossdev); } if (mask == 0) return; /* Prefer newly connected input. */ if (w != NULL && (mask & (1 << w->ossdev))) mask = (1 << w->ossdev); /* Prefer previously selected input */ if (mask & pdevinfo->recsrc) mask &= pdevinfo->recsrc; /* Prefer mic. */ if (mask & SOUND_MASK_MIC) mask = SOUND_MASK_MIC; /* Prefer monitor (2nd mic). */ if (mask & SOUND_MASK_MONITOR) mask = SOUND_MASK_MONITOR; /* Just take first one. */ mask = (1 << (ffs(mask) - 1)); HDA_BOOTVERBOSE( hdaa_audio_ctl_ossmixer_mask2allname(mask, buf, sizeof(buf)); device_printf(pdevinfo->dev, "Automatically set rec source to: %s\n", buf); ); hdaa_unlock(devinfo); mix_setrecsrc(pdevinfo->mixer, mask); hdaa_lock(devinfo); } /* * Jack presence detection event handler. */ static void hdaa_presence_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_audio_as *as; uint32_t res; int connected, old; if (w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) return; if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); connected = (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) != 0; if (devinfo->quirks & HDAA_QUIRK_SENSEINV) connected = !connected; old = w->wclass.pin.connected; if (connected == old) return; w->wclass.pin.connected = connected; HDA_BOOTVERBOSE( if (connected || old != 2) { device_printf(devinfo->dev, "Pin sense: nid=%d sense=0x%08x (%sconnected)\n", w->nid, res, !connected ? "dis" : ""); } ); as = &devinfo->as[w->bindas]; if (as->hpredir >= 0 && as->pins[15] == w->nid) hdaa_hpredir_handler(w); if (as->dir == HDAA_CTL_IN && old != 2) hdaa_autorecsrc_handler(as, w); if (old != 2) hdaa_channels_handler(as); } /* * Callback for poll based presence detection. */ static void hdaa_jack_poll_callback(void *arg) { struct hdaa_devinfo *devinfo = arg; struct hdaa_widget *w; int i; hdaa_lock(devinfo); if (devinfo->poll_ival == 0) { hdaa_unlock(devinfo); return; } for (i = 0; i < devinfo->ascnt; i++) { if (devinfo->as[i].hpredir < 0) continue; w = hdaa_widget_get(devinfo, devinfo->as[i].pins[15]); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_presence_handler(w); } callout_reset(&devinfo->poll_jack, devinfo->poll_ival, hdaa_jack_poll_callback, devinfo); hdaa_unlock(devinfo); } static void hdaa_eld_dump(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; device_t dev = devinfo->dev; uint8_t *sad; int mnl, i, sadc, fmt; if (w->eld == NULL || w->eld_len < 4) return; device_printf(dev, "ELD nid=%d: ELD_Ver=%u Baseline_ELD_Len=%u\n", w->nid, w->eld[0] >> 3, w->eld[2]); if ((w->eld[0] >> 3) != 0x02) return; mnl = w->eld[4] & 0x1f; device_printf(dev, "ELD nid=%d: CEA_EDID_Ver=%u MNL=%u\n", w->nid, w->eld[4] >> 5, mnl); sadc = w->eld[5] >> 4; device_printf(dev, "ELD nid=%d: SAD_Count=%u Conn_Type=%u S_AI=%u HDCP=%u\n", w->nid, sadc, (w->eld[5] >> 2) & 0x3, (w->eld[5] >> 1) & 0x1, w->eld[5] & 0x1); device_printf(dev, "ELD nid=%d: Aud_Synch_Delay=%ums\n", w->nid, w->eld[6] * 2); device_printf(dev, "ELD nid=%d: Channels=0x%b\n", w->nid, w->eld[7], "\020\07RLRC\06FLRC\05RC\04RLR\03FC\02LFE\01FLR"); device_printf(dev, "ELD nid=%d: Port_ID=0x%02x%02x%02x%02x%02x%02x%02x%02x\n", w->nid, w->eld[8], w->eld[9], w->eld[10], w->eld[11], w->eld[12], w->eld[13], w->eld[14], w->eld[15]); device_printf(dev, "ELD nid=%d: Manufacturer_Name=0x%02x%02x\n", w->nid, w->eld[16], w->eld[17]); device_printf(dev, "ELD nid=%d: Product_Code=0x%02x%02x\n", w->nid, w->eld[18], w->eld[19]); device_printf(dev, "ELD nid=%d: Monitor_Name_String='%.*s'\n", w->nid, mnl, &w->eld[20]); for (i = 0; i < sadc; i++) { sad = &w->eld[20 + mnl + i * 3]; fmt = (sad[0] >> 3) & 0x0f; if (fmt == HDA_HDMI_CODING_TYPE_REF_CTX) { fmt = (sad[2] >> 3) & 0x1f; if (fmt < 1 || fmt > 3) fmt = 0; else fmt += 14; } device_printf(dev, "ELD nid=%d: %s %dch freqs=0x%b", w->nid, HDA_HDMI_CODING_TYPES[fmt], (sad[0] & 0x07) + 1, sad[1], "\020\007192\006176\00596\00488\00348\00244\00132"); switch (fmt) { case HDA_HDMI_CODING_TYPE_LPCM: printf(" sizes=0x%b", sad[2] & 0x07, "\020\00324\00220\00116"); break; case HDA_HDMI_CODING_TYPE_AC3: case HDA_HDMI_CODING_TYPE_MPEG1: case HDA_HDMI_CODING_TYPE_MP3: case HDA_HDMI_CODING_TYPE_MPEG2: case HDA_HDMI_CODING_TYPE_AACLC: case HDA_HDMI_CODING_TYPE_DTS: case HDA_HDMI_CODING_TYPE_ATRAC: printf(" max_bitrate=%d", sad[2] * 8000); break; case HDA_HDMI_CODING_TYPE_WMAPRO: printf(" profile=%d", sad[2] & 0x07); break; } printf("\n"); } } static void hdaa_eld_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; uint32_t res; int i; if (w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) return; if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); if ((w->eld != 0) == ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) != 0)) return; if (w->eld != NULL) { w->eld_len = 0; free(w->eld, M_HDAA); w->eld = NULL; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Pin sense: nid=%d sense=0x%08x " "(%sconnected, ELD %svalid)\n", w->nid, res, (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis", (res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) ? "" : "in"); ); if ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) == 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_HDMI_DIP_SIZE(0, w->nid, 0x08)); if (res == HDA_INVALID) return; w->eld_len = res & 0xff; if (w->eld_len != 0) w->eld = malloc(w->eld_len, M_HDAA, M_ZERO | M_NOWAIT); if (w->eld == NULL) { w->eld_len = 0; return; } for (i = 0; i < w->eld_len; i++) { res = hda_command(devinfo->dev, HDA_CMD_GET_HDMI_ELDD(0, w->nid, i)); if (res & 0x80000000) w->eld[i] = res & 0xff; } HDA_BOOTVERBOSE( hdaa_eld_dump(w); ); hdaa_channels_handler(&devinfo->as[w->bindas]); } /* * Pin sense initializer. */ static void hdaa_sense_init(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, poll = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) { if (w->unsol < 0) w->unsol = HDAC_UNSOL_ALLOC( device_get_parent(devinfo->dev), devinfo->dev, w->nid); hda_command(devinfo->dev, HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE | w->unsol)); } as = &devinfo->as[w->bindas]; if (as->hpredir >= 0 && as->pins[15] == w->nid) { if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) { device_printf(devinfo->dev, "No presence detection support at nid %d\n", w->nid); } else { if (w->unsol < 0) poll = 1; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Headphones redirection for " "association %d nid=%d using %s.\n", w->bindas, w->nid, (w->unsol < 0) ? "polling" : "unsolicited responses"); ); } } hdaa_presence_handler(w); if (!HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) && !HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) continue; hdaa_eld_handler(w); } if (poll) { callout_reset(&devinfo->poll_jack, 1, hdaa_jack_poll_callback, devinfo); } } static void hdaa_sense_deinit(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; callout_stop(&devinfo->poll_jack); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->unsol < 0) continue; hda_command(devinfo->dev, HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, 0)); HDAC_UNSOL_FREE( device_get_parent(devinfo->dev), devinfo->dev, w->unsol); w->unsol = -1; } } uint32_t hdaa_widget_pin_patch(uint32_t config, const char *str) { char buf[256]; char *key, *value, *rest, *bad; int ival, i; strlcpy(buf, str, sizeof(buf)); rest = buf; while ((key = strsep(&rest, "=")) != NULL) { value = strsep(&rest, " \t"); if (value == NULL) break; ival = strtol(value, &bad, 10); if (strcmp(key, "seq") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_SEQUENCE_SHIFT) & HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK); } else if (strcmp(key, "as") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_ASSOCIATION_SHIFT) & HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK); } else if (strcmp(key, "misc") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_MISC_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_MISC_SHIFT) & HDA_CONFIG_DEFAULTCONF_MISC_MASK); } else if (strcmp(key, "color") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_COLOR_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT) & HDA_CONFIG_DEFAULTCONF_COLOR_MASK); } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_COLORS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT); break; } } } else if (strcmp(key, "ctype") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT) & HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK); } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_CONNECTORS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT); break; } } } else if (strcmp(key, "device") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT) & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK); continue; } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_DEVS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT); break; } } } else if (strcmp(key, "loc") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_LOCATION_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT) & HDA_CONFIG_DEFAULTCONF_LOCATION_MASK); continue; } for (i = 0; i < 64; i++) { if (strcasecmp(HDA_LOCS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT); break; } } } else if (strcmp(key, "conn") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT) & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); continue; } for (i = 0; i < 4; i++) { if (strcasecmp(HDA_CONNS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT); break; } } } } return (config); } uint32_t hdaa_gpio_patch(uint32_t gpio, const char *str) { char buf[256]; char *key, *value, *rest; int ikey, i; strlcpy(buf, str, sizeof(buf)); rest = buf; while ((key = strsep(&rest, "=")) != NULL) { value = strsep(&rest, " \t"); if (value == NULL) break; ikey = strtol(key, NULL, 10); if (ikey < 0 || ikey > 7) continue; for (i = 0; i < 7; i++) { if (strcasecmp(HDA_GPIO_ACTIONS[i], value) == 0) { gpio &= ~HDAA_GPIO_MASK(ikey); gpio |= i << HDAA_GPIO_SHIFT(ikey); break; } } } return (gpio); } static void hdaa_local_patch_pin(struct hdaa_widget *w) { device_t dev = w->devinfo->dev; const char *res = NULL; uint32_t config, orig; char buf[32]; config = orig = w->wclass.pin.config; snprintf(buf, sizeof(buf), "cad%u.nid%u.config", hda_get_codec_id(dev), w->nid); if (resource_string_value(device_get_name( device_get_parent(device_get_parent(dev))), device_get_unit(device_get_parent(device_get_parent(dev))), buf, &res) == 0) { if (strncmp(res, "0x", 2) == 0) { config = strtol(res + 2, NULL, 16); } else { config = hdaa_widget_pin_patch(config, res); } } snprintf(buf, sizeof(buf), "nid%u.config", w->nid); if (resource_string_value(device_get_name(dev), device_get_unit(dev), buf, &res) == 0) { if (strncmp(res, "0x", 2) == 0) { config = strtol(res + 2, NULL, 16); } else { config = hdaa_widget_pin_patch(config, res); } } HDA_BOOTVERBOSE( if (config != orig) device_printf(w->devinfo->dev, "Patching pin config nid=%u 0x%08x -> 0x%08x\n", w->nid, orig, config); ); w->wclass.pin.newconf = w->wclass.pin.config = config; } static void hdaa_dump_audio_formats_sb(struct sbuf *sb, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { sbuf_printf(sb, " Stream cap: 0x%08x", cap); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) sbuf_printf(sb, " AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) sbuf_printf(sb, " FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) sbuf_printf(sb, " PCM"); sbuf_printf(sb, "\n"); } cap = pcmcap; if (cap != 0) { sbuf_printf(sb, " PCM cap: 0x%08x", cap); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) sbuf_printf(sb, " 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) sbuf_printf(sb, " 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) sbuf_printf(sb, " 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) sbuf_printf(sb, " 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) sbuf_printf(sb, " 32"); sbuf_printf(sb, " bits,"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) sbuf_printf(sb, " 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) sbuf_printf(sb, " 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) sbuf_printf(sb, " 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) sbuf_printf(sb, " 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) sbuf_printf(sb, " 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) sbuf_printf(sb, " 44"); sbuf_printf(sb, " 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) sbuf_printf(sb, " 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) sbuf_printf(sb, " 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) sbuf_printf(sb, " 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) sbuf_printf(sb, " 192"); sbuf_printf(sb, " KHz\n"); } } static void hdaa_dump_pin_sb(struct sbuf *sb, struct hdaa_widget *w) { uint32_t pincap, conf; pincap = w->wclass.pin.cap; sbuf_printf(sb, " Pin cap: 0x%08x", pincap); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) sbuf_printf(sb, " ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) sbuf_printf(sb, " TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) sbuf_printf(sb, " PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) sbuf_printf(sb, " HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) sbuf_printf(sb, " OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) sbuf_printf(sb, " IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) sbuf_printf(sb, " BAL"); if (HDA_PARAM_PIN_CAP_HDMI(pincap)) sbuf_printf(sb, " HDMI"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { sbuf_printf(sb, " VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) sbuf_printf(sb, " 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) sbuf_printf(sb, " 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) sbuf_printf(sb, " 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) sbuf_printf(sb, " GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) sbuf_printf(sb, " HIZ"); sbuf_printf(sb, " ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) sbuf_printf(sb, " EAPD"); if (HDA_PARAM_PIN_CAP_DP(pincap)) sbuf_printf(sb, " DP"); if (HDA_PARAM_PIN_CAP_HBR(pincap)) sbuf_printf(sb, " HBR"); sbuf_printf(sb, "\n"); conf = w->wclass.pin.config; sbuf_printf(sb, " Pin config: 0x%08x", conf); sbuf_printf(sb, " as=%d seq=%d " "device=%s conn=%s ctype=%s loc=%s color=%s misc=%d\n", HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf)); sbuf_printf(sb, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) sbuf_printf(sb, " HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) sbuf_printf(sb, " IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) sbuf_printf(sb, " OUT"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03) sbuf_printf(sb, " HBR"); else if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) sbuf_printf(sb, " EPTs"); } else { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) sbuf_printf(sb, " VREFs"); } sbuf_printf(sb, "\n"); } static void hdaa_dump_amp_sb(struct sbuf *sb, uint32_t cap, const char *banner) { int offset, size, step; offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap); sbuf_printf(sb, " %s amp: 0x%08x " "mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n", banner, cap, HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), step, size, offset, ((0 - offset) * (size + 1)) / 4, ((step - offset) * (size + 1)) / 4); } static int hdaa_sysctl_caps(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo; struct hdaa_widget *w, *cw; struct sbuf sb; char buf[64]; int error, j; w = (struct hdaa_widget *)oidp->oid_arg1; devinfo = w->devinfo; sbuf_new_for_sysctl(&sb, NULL, 256, req); sbuf_printf(&sb, "%s%s\n", w->name, (w->enable == 0) ? " [DISABLED]" : ""); sbuf_printf(&sb, " Widget cap: 0x%08x", w->param.widget_cap); if (w->param.widget_cap & 0x0ee1) { if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap)) sbuf_printf(&sb, " LRSWAP"); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap)) sbuf_printf(&sb, " PWR"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) sbuf_printf(&sb, " DIGITAL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) sbuf_printf(&sb, " UNSOL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) sbuf_printf(&sb, " PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) sbuf_printf(&sb, " STRIPE(x%d)", 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) sbuf_printf(&sb, " STEREO"); else if (j > 1) sbuf_printf(&sb, " %dCH", j + 1); } sbuf_printf(&sb, "\n"); if (w->bindas != -1) { sbuf_printf(&sb, " Association: %d (0x%04x)\n", w->bindas, w->bindseqmask); } if (w->ossmask != 0 || w->ossdev >= 0) { sbuf_printf(&sb, " OSS: %s", hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) sbuf_printf(&sb, " (%s)", ossnames[w->ossdev]); sbuf_printf(&sb, "\n"); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_dump_audio_formats_sb(&sb, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) hdaa_dump_pin_sb(&sb, w); if (w->param.eapdbtl != HDA_INVALID) { sbuf_printf(&sb, " EAPD: 0x%08x%s%s%s\n", w->param.eapdbtl, (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_LR_SWAP) ? " LRSWAP" : "", (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD) ? " EAPD" : "", (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_BTL) ? " BTL" : ""); } if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdaa_dump_amp_sb(&sb, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdaa_dump_amp_sb(&sb, w->param.inamp_cap, " Input"); if (w->nconns > 0) sbuf_printf(&sb, " Connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); sbuf_printf(&sb, " + %s<- nid=%d [%s]", (w->connsenable[j] == 0)?"[DISABLED] ":"", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) sbuf_printf(&sb, " [UNKNOWN]"); else if (cw->enable == 0) sbuf_printf(&sb, " [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) sbuf_printf(&sb, " (selected)"); sbuf_printf(&sb, "\n"); } error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } static int hdaa_sysctl_config(SYSCTL_HANDLER_ARGS) { char buf[256]; int error; uint32_t conf; conf = *(uint32_t *)oidp->oid_arg1; snprintf(buf, sizeof(buf), "0x%08x as=%d seq=%d " "device=%s conn=%s ctype=%s loc=%s color=%s misc=%d", conf, HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) conf = strtol(buf + 2, NULL, 16); else conf = hdaa_widget_pin_patch(conf, buf); *(uint32_t *)oidp->oid_arg1 = conf; return (0); } static void hdaa_config_fetch(const char *str, uint32_t *on, uint32_t *off) { int i = 0, j, k, len, inv; for (;;) { while (str[i] != '\0' && (str[i] == ',' || isspace(str[i]) != 0)) i++; if (str[i] == '\0') return; j = i; while (str[j] != '\0' && !(str[j] == ',' || isspace(str[j]) != 0)) j++; len = j - i; if (len > 2 && strncmp(str + i, "no", 2) == 0) inv = 2; else inv = 0; for (k = 0; len > inv && k < nitems(hdaa_quirks_tab); k++) { if (strncmp(str + i + inv, hdaa_quirks_tab[k].key, len - inv) != 0) continue; if (len - inv != strlen(hdaa_quirks_tab[k].key)) continue; if (inv == 0) { *on |= hdaa_quirks_tab[k].value; *off &= ~hdaa_quirks_tab[k].value; } else { *off |= hdaa_quirks_tab[k].value; *on &= ~hdaa_quirks_tab[k].value; } break; } i = j; } } static int hdaa_sysctl_quirks(SYSCTL_HANDLER_ARGS) { char buf[256]; int error, n = 0, i; uint32_t quirks, quirks_off; quirks = *(uint32_t *)oidp->oid_arg1; buf[0] = 0; for (i = 0; i < nitems(hdaa_quirks_tab); i++) { if ((quirks & hdaa_quirks_tab[i].value) != 0) n += snprintf(buf + n, sizeof(buf) - n, "%s%s", n != 0 ? "," : "", hdaa_quirks_tab[i].key); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) quirks = strtol(buf + 2, NULL, 16); else { quirks = 0; hdaa_config_fetch(buf, &quirks, &quirks_off); } *(uint32_t *)oidp->oid_arg1 = quirks; return (0); } static void hdaa_local_patch(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; const char *res = NULL; uint32_t quirks_on = 0, quirks_off = 0, x; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) hdaa_local_patch_pin(w); } if (resource_string_value(device_get_name(devinfo->dev), device_get_unit(devinfo->dev), "config", &res) == 0) { if (res != NULL && strlen(res) > 0) hdaa_config_fetch(res, &quirks_on, &quirks_off); devinfo->quirks |= quirks_on; devinfo->quirks &= ~quirks_off; } if (devinfo->newquirks == -1) devinfo->newquirks = devinfo->quirks; else devinfo->quirks = devinfo->newquirks; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "Config options: 0x%08x\n", devinfo->quirks); ); if (resource_string_value(device_get_name(devinfo->dev), device_get_unit(devinfo->dev), "gpio_config", &res) == 0) { if (strncmp(res, "0x", 2) == 0) { devinfo->gpio = strtol(res + 2, NULL, 16); } else { devinfo->gpio = hdaa_gpio_patch(devinfo->gpio, res); } } if (devinfo->newgpio == -1) devinfo->newgpio = devinfo->gpio; else devinfo->gpio = devinfo->newgpio; if (devinfo->newgpo == -1) devinfo->newgpo = devinfo->gpo; else devinfo->gpo = devinfo->newgpo; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "GPIO config options:"); for (i = 0; i < 7; i++) { x = (devinfo->gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); if (x != 0) printf(" %d=%s", i, HDA_GPIO_ACTIONS[x]); } printf("\n"); ); } static void hdaa_widget_connection_parse(struct hdaa_widget *w) { uint32_t res; int i, j, max, ents, entnum; nid_t nid = w->nid; nid_t cnid, addcnid, prevcnid; w->nconns = 0; res = hda_command(w->devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_CONN_LIST_LENGTH)); ents = HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(res); if (ents < 1) return; entnum = HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(res) ? 2 : 4; max = (sizeof(w->conns) / sizeof(w->conns[0])) - 1; prevcnid = 0; #define CONN_RMASK(e) (1 << ((32 / (e)) - 1)) #define CONN_NMASK(e) (CONN_RMASK(e) - 1) #define CONN_RESVAL(r, e, n) ((r) >> ((32 / (e)) * (n))) #define CONN_RANGE(r, e, n) (CONN_RESVAL(r, e, n) & CONN_RMASK(e)) #define CONN_CNID(r, e, n) (CONN_RESVAL(r, e, n) & CONN_NMASK(e)) for (i = 0; i < ents; i += entnum) { res = hda_command(w->devinfo->dev, HDA_CMD_GET_CONN_LIST_ENTRY(0, nid, i)); for (j = 0; j < entnum; j++) { cnid = CONN_CNID(res, entnum, j); if (cnid == 0) { if (w->nconns < ents) device_printf(w->devinfo->dev, "WARNING: nid=%d has zero cnid " "entnum=%d j=%d index=%d " "entries=%d found=%d res=0x%08x\n", nid, entnum, j, i, ents, w->nconns, res); else goto getconns_out; } if (cnid < w->devinfo->startnode || cnid >= w->devinfo->endnode) { HDA_BOOTVERBOSE( device_printf(w->devinfo->dev, "WARNING: nid=%d has cnid outside " "of the AFG range j=%d " "entnum=%d index=%d res=0x%08x\n", nid, j, entnum, i, res); ); } if (CONN_RANGE(res, entnum, j) == 0) addcnid = cnid; else if (prevcnid == 0 || prevcnid >= cnid) { device_printf(w->devinfo->dev, "WARNING: Invalid child range " "nid=%d index=%d j=%d entnum=%d " "prevcnid=%d cnid=%d res=0x%08x\n", nid, i, j, entnum, prevcnid, cnid, res); addcnid = cnid; } else addcnid = prevcnid + 1; while (addcnid <= cnid) { if (w->nconns > max) { device_printf(w->devinfo->dev, "Adding %d (nid=%d): " "Max connection reached! max=%d\n", addcnid, nid, max + 1); goto getconns_out; } w->connsenable[w->nconns] = 1; w->conns[w->nconns++] = addcnid++; } prevcnid = cnid; } } getconns_out: return; } static void hdaa_widget_parse(struct hdaa_widget *w) { device_t dev = w->devinfo->dev; uint32_t wcap, cap; nid_t nid = w->nid; char buf[64]; w->param.widget_cap = wcap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_AUDIO_WIDGET_CAP)); w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(wcap); hdaa_widget_connection_parse(w); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.outamp_cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP)); else w->param.outamp_cap = w->devinfo->outamp_cap; } else w->param.outamp_cap = 0; if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.inamp_cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP)); else w->param.inamp_cap = w->devinfo->inamp_cap; } else w->param.inamp_cap = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { if (HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(wcap)) { cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS)); w->param.supp_stream_formats = (cap != 0) ? cap : w->devinfo->supp_stream_formats; cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE)); w->param.supp_pcm_size_rate = (cap != 0) ? cap : w->devinfo->supp_pcm_size_rate; } else { w->param.supp_stream_formats = w->devinfo->supp_stream_formats; w->param.supp_pcm_size_rate = w->devinfo->supp_pcm_size_rate; } if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { w->wclass.conv.stripecap = hda_command(dev, HDA_CMD_GET_STRIPE_CONTROL(0, w->nid)) >> 20; } else w->wclass.conv.stripecap = 1; } else { w->param.supp_stream_formats = 0; w->param.supp_pcm_size_rate = 0; } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { w->wclass.pin.original = w->wclass.pin.newconf = w->wclass.pin.config = hda_command(dev, HDA_CMD_GET_CONFIGURATION_DEFAULT(0, w->nid)); w->wclass.pin.cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, w->nid, HDA_PARAM_PIN_CAP)); w->wclass.pin.ctrl = hda_command(dev, HDA_CMD_GET_PIN_WIDGET_CTRL(0, nid)); w->wclass.pin.connected = 2; if (HDA_PARAM_PIN_CAP_EAPD_CAP(w->wclass.pin.cap)) { w->param.eapdbtl = hda_command(dev, HDA_CMD_GET_EAPD_BTL_ENABLE(0, nid)); w->param.eapdbtl &= 0x7; w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; } else w->param.eapdbtl = HDA_INVALID; } w->unsol = -1; hdaa_unlock(w->devinfo); snprintf(buf, sizeof(buf), "nid%d", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, w, 0, hdaa_sysctl_caps, "A", "Node capabilities"); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { snprintf(buf, sizeof(buf), "nid%d_config", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, &w->wclass.pin.newconf, 0, hdaa_sysctl_config, "A", "Current pin configuration"); snprintf(buf, sizeof(buf), "nid%d_original", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, &w->wclass.pin.original, 0, hdaa_sysctl_config, "A", "Original pin configuration"); } hdaa_lock(w->devinfo); } static void hdaa_widget_postprocess(struct hdaa_widget *w) { const char *typestr; w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(w->param.widget_cap); switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: typestr = "audio output"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: typestr = "audio input"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: typestr = "audio mixer"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: typestr = "audio selector"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: typestr = "pin"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET: typestr = "power widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET: typestr = "volume widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: typestr = "beep widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET: typestr = "vendor widget"; break; default: typestr = "unknown type"; break; } strlcpy(w->name, typestr, sizeof(w->name)); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { uint32_t config; const char *devstr; int conn, color; config = w->wclass.pin.config; devstr = HDA_DEVS[(config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) >> HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT]; conn = (config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) >> HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT; color = (config & HDA_CONFIG_DEFAULTCONF_COLOR_MASK) >> HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT; strlcat(w->name, ": ", sizeof(w->name)); strlcat(w->name, devstr, sizeof(w->name)); strlcat(w->name, " (", sizeof(w->name)); if (conn == 0 && color != 0 && color != 15) { strlcat(w->name, HDA_COLORS[color], sizeof(w->name)); strlcat(w->name, " ", sizeof(w->name)); } strlcat(w->name, HDA_CONNS[conn], sizeof(w->name)); strlcat(w->name, ")", sizeof(w->name)); } } struct hdaa_widget * hdaa_widget_get(struct hdaa_devinfo *devinfo, nid_t nid) { if (devinfo == NULL || devinfo->widget == NULL || nid < devinfo->startnode || nid >= devinfo->endnode) return (NULL); return (&devinfo->widget[nid - devinfo->startnode]); } static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *devinfo, nid_t nid, int index, int lmute, int rmute, int left, int right, int dir) { uint16_t v = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "Setting amplifier nid=%d index=%d %s mute=%d/%d vol=%d/%d\n", nid,index,dir ? "in" : "out",lmute,rmute,left,right); ); if (left != right || lmute != rmute) { v = (1 << (15 - dir)) | (1 << 13) | (index << 8) | (lmute << 7) | left; hda_command(devinfo->dev, HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v)); v = (1 << (15 - dir)) | (1 << 12) | (index << 8) | (rmute << 7) | right; } else v = (1 << (15 - dir)) | (3 << 12) | (index << 8) | (lmute << 7) | left; hda_command(devinfo->dev, HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v)); } static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *ctl, uint32_t mute, int left, int right) { nid_t nid; int lmute, rmute; nid = ctl->widget->nid; /* Save new values if valid. */ if (mute != HDAA_AMP_MUTE_DEFAULT) ctl->muted = mute; if (left != HDAA_AMP_VOL_DEFAULT) ctl->left = left; if (right != HDAA_AMP_VOL_DEFAULT) ctl->right = right; /* Prepare effective values */ if (ctl->forcemute) { lmute = 1; rmute = 1; left = 0; right = 0; } else { lmute = HDAA_AMP_LEFT_MUTED(ctl->muted); rmute = HDAA_AMP_RIGHT_MUTED(ctl->muted); left = ctl->left; right = ctl->right; } /* Apply effective values */ if (ctl->dir & HDAA_CTL_OUT) hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index, lmute, rmute, left, right, 0); if (ctl->dir & HDAA_CTL_IN) hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index, lmute, rmute, left, right, 1); } static void hdaa_widget_connection_select(struct hdaa_widget *w, uint8_t index) { if (w == NULL || w->nconns < 1 || index > (w->nconns - 1)) return; HDA_BOOTHVERBOSE( device_printf(w->devinfo->dev, "Setting selector nid=%d index=%d\n", w->nid, index); ); hda_command(w->devinfo->dev, HDA_CMD_SET_CONNECTION_SELECT_CONTROL(0, w->nid, index)); w->selconn = index; } /**************************************************************************** * Device Methods ****************************************************************************/ static void * hdaa_channel_init(kobj_t obj, void *data, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct hdaa_chan *ch = data; struct hdaa_pcm_devinfo *pdevinfo = ch->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; hdaa_lock(devinfo); if (devinfo->quirks & HDAA_QUIRK_FIXEDRATE) { ch->caps.minspeed = ch->caps.maxspeed = 48000; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; } ch->dir = dir; ch->b = b; ch->c = c; ch->blksz = pdevinfo->chan_size / pdevinfo->chan_blkcnt; ch->blkcnt = pdevinfo->chan_blkcnt; hdaa_unlock(devinfo); if (sndbuf_alloc(ch->b, bus_get_dma_tag(devinfo->dev), hda_get_dma_nocache(devinfo->dev) ? BUS_DMA_NOCACHE : BUS_DMA_COHERENT, pdevinfo->chan_size) != 0) return (NULL); return (ch); } static int hdaa_channel_setformat(kobj_t obj, void *data, uint32_t format) { struct hdaa_chan *ch = data; int i; for (i = 0; ch->caps.fmtlist[i] != 0; i++) { if (format == ch->caps.fmtlist[i]) { ch->fmt = format; return (0); } } return (EINVAL); } static uint32_t hdaa_channel_setspeed(kobj_t obj, void *data, uint32_t speed) { struct hdaa_chan *ch = data; uint32_t spd = 0, threshold; int i; /* First look for equal or multiple frequency. */ for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; if (speed != 0 && spd / speed * speed == spd) { ch->spd = spd; return (spd); } } /* If no match, just find nearest. */ for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; threshold = spd + ((ch->pcmrates[i + 1] != 0) ? ((ch->pcmrates[i + 1] - spd) >> 1) : 0); if (speed < threshold) break; } ch->spd = spd; return (spd); } static uint16_t hdaa_stream_format(struct hdaa_chan *ch) { int i; uint16_t fmt; fmt = 0; if (ch->fmt & AFMT_S16_LE) fmt |= ch->bit16 << 4; else if (ch->fmt & AFMT_S32_LE) fmt |= ch->bit32 << 4; else fmt |= 1 << 4; for (i = 0; i < HDA_RATE_TAB_LEN; i++) { if (hda_rate_tab[i].valid && ch->spd == hda_rate_tab[i].rate) { fmt |= hda_rate_tab[i].base; fmt |= hda_rate_tab[i].mul; fmt |= hda_rate_tab[i].div; break; } } fmt |= (AFMT_CHANNEL(ch->fmt) - 1); return (fmt); } static int hdaa_allowed_stripes(uint16_t fmt) { static const int bits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 }; int size; size = bits[(fmt >> 4) & 0x03]; size *= (fmt & 0x0f) + 1; size *= ((fmt >> 11) & 0x07) + 1; return (0xffffffffU >> (32 - fls(size / 8))); } static void hdaa_audio_setup(struct hdaa_chan *ch) { struct hdaa_audio_as *as = &ch->devinfo->as[ch->as]; struct hdaa_widget *w, *wp; int i, j, k, chn, cchn, totalchn, totalextchn, c; uint16_t fmt, dfmt; /* Mapping channel pairs to codec pins/converters. */ const static uint16_t convmap[2][5] = /* 1.0 2.0 4.0 5.1 7.1 */ {{ 0x0010, 0x0001, 0x0201, 0x0231, 0x4231 }, /* no dup. */ { 0x0010, 0x0001, 0x2201, 0x2231, 0x4231 }}; /* side dup. */ /* Mapping formats to HDMI channel allocations. */ const static uint8_t hdmica[2][8] = /* 1 2 3 4 5 6 7 8 */ {{ 0x02, 0x00, 0x04, 0x08, 0x0a, 0x0e, 0x12, 0x12 }, /* x.0 */ { 0x01, 0x03, 0x01, 0x03, 0x09, 0x0b, 0x0f, 0x13 }}; /* x.1 */ /* Mapping formats to HDMI channels order. */ const static uint32_t hdmich[2][8] = /* 1 / 5 2 / 6 3 / 7 4 / 8 */ {{ 0xFFFF0F00, 0xFFFFFF10, 0xFFF2FF10, 0xFF32FF10, 0xFF324F10, 0xF5324F10, 0x54326F10, 0x54326F10 }, /* x.0 */ { 0xFFFFF000, 0xFFFF0100, 0xFFFFF210, 0xFFFF2310, 0xFF32F410, 0xFF324510, 0xF6324510, 0x76325410 }}; /* x.1 */ int convmapid = -1; nid_t nid; uint8_t csum; totalchn = AFMT_CHANNEL(ch->fmt); totalextchn = AFMT_EXTCHANNEL(ch->fmt); HDA_BOOTHVERBOSE( device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup fmt=%08x (%d.%d) speed=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->fmt, totalchn - totalextchn, totalextchn, ch->spd); ); fmt = hdaa_stream_format(ch); /* Set channels to I/O converters mapping for known speaker setups. */ if ((as->pinset == 0x0007 || as->pinset == 0x0013) || /* Standard 5.1 */ (as->pinset == 0x0017)) /* Standard 7.1 */ convmapid = (ch->dir == PCMDIR_PLAY); dfmt = HDA_CMD_SET_DIGITAL_CONV_FMT1_DIGEN; if (ch->fmt & AFMT_AC3) dfmt |= HDA_CMD_SET_DIGITAL_CONV_FMT1_NAUDIO; chn = 0; for (i = 0; ch->io[i] != -1; i++) { w = hdaa_widget_get(ch->devinfo, ch->io[i]); if (w == NULL) continue; /* If HP redirection is enabled, but failed to use same DAC, make last DAC to duplicate first one. */ if (as->fakeredir && i == (as->pincnt - 1)) { c = (ch->sid << 4); } else { /* Map channels to I/O converters, if set. */ if (convmapid >= 0) chn = (((convmap[convmapid][totalchn / 2] >> i * 4) & 0xf) - 1) * 2; if (chn < 0 || chn >= totalchn) { c = 0; } else { c = (ch->sid << 4) | chn; } } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_FMT(0, ch->io[i], fmt)); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { hda_command(ch->devinfo->dev, HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], dfmt)); } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], c)); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { hda_command(ch->devinfo->dev, HDA_CMD_SET_STRIPE_CONTROL(0, w->nid, ch->stripectl)); } cchn = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (cchn > 1 && chn < totalchn) { cchn = min(cchn, totalchn - chn - 1); hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_CHAN_COUNT(0, ch->io[i], cchn)); } HDA_BOOTHVERBOSE( device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup nid=%d: " "fmt=0x%04x, dfmt=0x%04x, chan=0x%04x, " "chan_count=0x%02x, stripe=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->io[i], fmt, dfmt, c, cchn, ch->stripectl); ); for (j = 0; j < 16; j++) { if (as->dacs[ch->asindex][j] != ch->io[i]) continue; nid = as->pins[j]; wp = hdaa_widget_get(ch->devinfo, nid); if (wp == NULL) continue; if (!HDA_PARAM_PIN_CAP_DP(wp->wclass.pin.cap) && !HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap)) continue; /* Set channel mapping. */ for (k = 0; k < 8; k++) { hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_CHAN_SLOT(0, nid, (((hdmich[totalextchn == 0 ? 0 : 1][totalchn - 1] >> (k * 4)) & 0xf) << 4) | k)); } /* * Enable High Bit Rate (HBR) Encoded Packet Type * (EPT), if supported and needed (8ch data). */ if (HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap) && HDA_PARAM_PIN_CAP_HBR(wp->wclass.pin.cap)) { wp->wclass.pin.ctrl &= ~HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK; if ((ch->fmt & AFMT_AC3) && (cchn == 7)) wp->wclass.pin.ctrl |= 0x03; hda_command(ch->devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, nid, wp->wclass.pin.ctrl)); } /* Stop audio infoframe transmission. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0x00)); /* Clear audio infoframe buffer. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); for (k = 0; k < 32; k++) hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); /* Write HDMI/DisplayPort audio infoframe. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); if (w->eld != NULL && w->eld_len >= 6 && ((w->eld[5] >> 2) & 0x3) == 1) { /* DisplayPort */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x1b)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x44)); } else { /* HDMI */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x01)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x0a)); csum = 0; csum -= 0x84 + 0x01 + 0x0a + (totalchn - 1) + hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1]; hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, csum)); } hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, totalchn - 1)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1])); /* Start audio infoframe transmission. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0xc0)); } chn += cchn + 1; } } /* * Greatest Common Divisor. */ static unsigned gcd(unsigned a, unsigned b) { u_int c; while (b != 0) { c = a; a = b; b = (c % b); } return (a); } /* * Least Common Multiple. */ static unsigned lcm(unsigned a, unsigned b) { return ((a * b) / gcd(a, b)); } static int hdaa_channel_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct hdaa_chan *ch = data; blksz -= blksz % lcm(HDA_DMA_ALIGNMENT, sndbuf_getalign(ch->b)); if (blksz > (sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN)) blksz = sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN; if (blksz < HDA_BLK_MIN) blksz = HDA_BLK_MIN; if (blkcnt > HDA_BDL_MAX) blkcnt = HDA_BDL_MAX; if (blkcnt < HDA_BDL_MIN) blkcnt = HDA_BDL_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->b)) { if ((blkcnt >> 1) >= HDA_BDL_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= HDA_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->b) != blksz || sndbuf_getblkcnt(ch->b) != blkcnt) && sndbuf_resize(ch->b, blkcnt, blksz) != 0) device_printf(ch->devinfo->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->b); ch->blkcnt = sndbuf_getblkcnt(ch->b); return (0); } static uint32_t hdaa_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct hdaa_chan *ch = data; hdaa_channel_setfragments(obj, data, blksz, ch->pdevinfo->chan_blkcnt); return (ch->blksz); } static void hdaa_channel_stop(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; struct hdaa_widget *w; int i; if ((ch->flags & HDAA_CHN_RUNNING) == 0) return; ch->flags &= ~HDAA_CHN_RUNNING; HDAC_STREAM_STOP(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); for (i = 0; ch->io[i] != -1; i++) { w = hdaa_widget_get(ch->devinfo, ch->io[i]); if (w == NULL) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { hda_command(devinfo->dev, HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], 0)); } hda_command(devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], 0)); } HDAC_STREAM_FREE(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); } static int hdaa_channel_start(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; uint32_t fmt; fmt = hdaa_stream_format(ch); ch->stripectl = fls(ch->stripecap & hdaa_allowed_stripes(fmt) & hda_get_stripes_mask(devinfo->dev)) - 1; ch->sid = HDAC_STREAM_ALLOC(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, fmt, ch->stripectl, &ch->dmapos); if (ch->sid <= 0) return (EBUSY); hdaa_audio_setup(ch); HDAC_STREAM_RESET(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); HDAC_STREAM_START(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid, sndbuf_getbufaddr(ch->b), ch->blksz, ch->blkcnt); ch->flags |= HDAA_CHN_RUNNING; return (0); } static int hdaa_channel_trigger(kobj_t obj, void *data, int go) { struct hdaa_chan *ch = data; int error = 0; if (!PCMTRIG_COMMON(go)) return (0); hdaa_lock(ch->devinfo); switch (go) { case PCMTRIG_START: error = hdaa_channel_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: hdaa_channel_stop(ch); break; default: break; } hdaa_unlock(ch->devinfo); return (error); } static uint32_t hdaa_channel_getptr(kobj_t obj, void *data) { struct hdaa_chan *ch = data; struct hdaa_devinfo *devinfo = ch->devinfo; uint32_t ptr; hdaa_lock(devinfo); if (ch->dmapos != NULL) { ptr = *(ch->dmapos); } else { ptr = HDAC_STREAM_GETPTR( device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); } hdaa_unlock(devinfo); /* * Round to available space and force 128 bytes alignment. */ ptr %= ch->blksz * ch->blkcnt; ptr &= HDA_BLK_ALIGN; return (ptr); } static struct pcmchan_caps * hdaa_channel_getcaps(kobj_t obj, void *data) { return (&((struct hdaa_chan *)data)->caps); } static kobj_method_t hdaa_channel_methods[] = { KOBJMETHOD(channel_init, hdaa_channel_init), KOBJMETHOD(channel_setformat, hdaa_channel_setformat), KOBJMETHOD(channel_setspeed, hdaa_channel_setspeed), KOBJMETHOD(channel_setblocksize, hdaa_channel_setblocksize), KOBJMETHOD(channel_setfragments, hdaa_channel_setfragments), KOBJMETHOD(channel_trigger, hdaa_channel_trigger), KOBJMETHOD(channel_getptr, hdaa_channel_getptr), KOBJMETHOD(channel_getcaps, hdaa_channel_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdaa_channel); static int hdaa_audio_ctl_ossmixer_init(struct snd_mixer *m) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; uint32_t mask, recmask; int i, j; hdaa_lock(devinfo); pdevinfo->mixer = m; /* Make sure that in case of soft volume it won't stay muted. */ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { pdevinfo->left[i] = 100; pdevinfo->right[i] = 100; } /* Declare volume controls assigned to this association. */ mask = pdevinfo->ossmask; if (pdevinfo->playas >= 0) { /* Declate EAPD as ogain control. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDA_INVALID || w->bindas != pdevinfo->playas) continue; mask |= SOUND_MASK_OGAIN; break; } /* Declare soft PCM volume if needed. */ if ((mask & SOUND_MASK_PCM) == 0 || (devinfo->quirks & HDAA_QUIRK_SOFTPCMVOL) || pdevinfo->minamp[SOUND_MIXER_PCM] == pdevinfo->maxamp[SOUND_MIXER_PCM]) { mask |= SOUND_MASK_PCM; pcm_setflags(pdevinfo->dev, pcm_getflags(pdevinfo->dev) | SD_F_SOFTPCMVOL); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Forcing Soft PCM volume\n"); ); } /* Declare master volume if needed. */ if ((mask & SOUND_MASK_VOLUME) == 0) { mask |= SOUND_MASK_VOLUME; mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Forcing master volume with PCM\n"); ); } } /* Declare record sources available to this association. */ recmask = 0; if (pdevinfo->recas >= 0) { for (i = 0; i < 16; i++) { if (devinfo->as[pdevinfo->recas].dacs[0][i] < 0) continue; w = hdaa_widget_get(devinfo, devinfo->as[pdevinfo->recas].dacs[0][i]); if (w == NULL || w->enable == 0) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas != pdevinfo->recas && cw->bindas != -2) continue; recmask |= cw->ossmask; } } } recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1; mask &= (1 << SOUND_MIXER_NRDEVICES) - 1; pdevinfo->ossmask = mask; mix_setrecdevs(m, recmask); mix_setdevs(m, mask); hdaa_unlock(devinfo); return (0); } /* * Update amplification per pdevinfo per ossdev, calculate summary coefficient * and write it to codec, update *left and *right to reflect remaining error. */ static void hdaa_audio_ctl_dev_set(struct hdaa_audio_ctl *ctl, int ossdev, int mute, int *left, int *right) { int i, zleft, zright, sleft, sright, smute, lval, rval; ctl->devleft[ossdev] = *left; ctl->devright[ossdev] = *right; ctl->devmute[ossdev] = mute; smute = sleft = sright = zleft = zright = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { sleft += ctl->devleft[i]; sright += ctl->devright[i]; smute |= ctl->devmute[i]; if (i == ossdev) continue; zleft += ctl->devleft[i]; zright += ctl->devright[i]; } lval = QDB2VAL(ctl, sleft); rval = QDB2VAL(ctl, sright); hdaa_audio_ctl_amp_set(ctl, smute, lval, rval); *left -= VAL2QDB(ctl, lval) - VAL2QDB(ctl, QDB2VAL(ctl, zleft)); *right -= VAL2QDB(ctl, rval) - VAL2QDB(ctl, QDB2VAL(ctl, zright)); } /* * Trace signal from source, setting volumes on the way. */ static void hdaa_audio_ctl_source_volume(struct hdaa_pcm_devinfo *pdevinfo, int ossdev, nid_t nid, int index, int mute, int left, int right, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, conns = 0; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; conns++; } } /* If this is not a first step - use input mixer. Pins have common input ctl so care must be taken. */ if (depth > 0 && (conns == 1 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); } /* If widget has own ossdev - not traverse it. It will be traversed on its own. */ if (w->ossdev >= 0 && depth > 0) return; /* We must not traverse pin */ if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && depth > 0) return; /* * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. */ if (conns > 1 && (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || w->selconn != index)) return; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) { hdaa_audio_ctl_source_volume(pdevinfo, ossdev, wc->nid, j, mute, left, right, depth + 1); } } } return; } /* * Trace signal from destination, setting volumes on the way. */ static void hdaa_audio_ctl_dest_volume(struct hdaa_pcm_devinfo *pdevinfo, int ossdev, nid_t nid, int index, int mute, int left, int right, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, consumers, cleft, cright; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; if (depth > 0) { /* If this node produce output for several consumers, we can't touch it. */ consumers = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) consumers++; } } /* The only exception is if real HP redirection is configured and this is a duplication point. XXX: Actually exception is not completely correct. XXX: Duplication point check is not perfect. */ if ((consumers == 2 && (w->bindas < 0 || as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) return; /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); } /* We must not traverse pin */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && depth > 0) return; for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (index >= 0 && i != index) continue; cleft = left; cright = right; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &cleft, &cright); hdaa_audio_ctl_dest_volume(pdevinfo, ossdev, w->conns[i], -1, mute, cleft, cright, depth + 1); } } /* * Set volumes for the specified pdevinfo and ossdev. */ static void hdaa_audio_ctl_dev_volume(struct hdaa_pcm_devinfo *pdevinfo, unsigned dev) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; uint32_t mute; int lvol, rvol; int i, j; mute = 0; if (pdevinfo->left[dev] == 0) { mute |= HDAA_AMP_MUTE_LEFT; lvol = -4000; } else lvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) * pdevinfo->left[dev] + 50) / 100 + pdevinfo->minamp[dev]; if (pdevinfo->right[dev] == 0) { mute |= HDAA_AMP_MUTE_RIGHT; rvol = -4000; } else rvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) * pdevinfo->right[dev] + 50) / 100 + pdevinfo->minamp[dev]; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas < 0) { if (pdevinfo->index != 0) continue; } else { if (w->bindas != pdevinfo->playas && w->bindas != pdevinfo->recas) continue; } if (dev == SOUND_MIXER_RECLEV && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); continue; } if (dev == SOUND_MIXER_VOLUME && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && devinfo->as[w->bindas].dir == HDAA_CTL_OUT) { hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); continue; } if (dev == SOUND_MIXER_IGAIN && w->pflags & HDAA_ADC_MONITOR) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas == -1) continue; if (cw->bindas >= 0 && devinfo->as[cw->bindas].dir != HDAA_CTL_IN) continue; hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, j, mute, lvol, rvol, 0); } continue; } if (w->ossdev != dev) continue; hdaa_audio_ctl_source_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); if (dev == SOUND_MIXER_IMIX && (w->pflags & HDAA_IMIX_AS_DST)) hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); } } /* * OSS Mixer set method. */ static int hdaa_audio_ctl_ossmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; int i; hdaa_lock(devinfo); /* Save new values. */ pdevinfo->left[dev] = left; pdevinfo->right[dev] = right; /* 'ogain' is the special case implemented with EAPD. */ if (dev == SOUND_MIXER_OGAIN) { uint32_t orig; w = NULL; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDA_INVALID) continue; break; } if (i >= devinfo->endnode) { hdaa_unlock(devinfo); return (-1); } orig = w->param.eapdbtl; if (left == 0) w->param.eapdbtl &= ~HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; else w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; if (orig != w->param.eapdbtl) { uint32_t val; val = w->param.eapdbtl; if (devinfo->quirks & HDAA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hda_command(devinfo->dev, HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val)); } hdaa_unlock(devinfo); return (left | (left << 8)); } /* Recalculate all controls related to this OSS device. */ hdaa_audio_ctl_dev_volume(pdevinfo, dev); hdaa_unlock(devinfo); return (left | (right << 8)); } /* * Set mixer settings to our own default values: * +20dB for mics, -10dB for analog vol, mute for igain, 0dB for others. */ static void hdaa_audio_ctl_set_defaults(struct hdaa_pcm_devinfo *pdevinfo) { int amp, vol, dev; for (dev = 0; dev < SOUND_MIXER_NRDEVICES; dev++) { if ((pdevinfo->ossmask & (1 << dev)) == 0) continue; /* If the value was overridden, leave it as is. */ if (resource_int_value(device_get_name(pdevinfo->dev), device_get_unit(pdevinfo->dev), ossnames[dev], &vol) == 0) continue; vol = -1; if (dev == SOUND_MIXER_OGAIN) vol = 100; else if (dev == SOUND_MIXER_IGAIN) vol = 0; else if (dev == SOUND_MIXER_MIC || dev == SOUND_MIXER_MONITOR) amp = 20 * 4; /* +20dB */ else if (dev == SOUND_MIXER_VOLUME && !pdevinfo->digital) amp = -10 * 4; /* -10dB */ else amp = 0; if (vol < 0 && (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) <= 0) { vol = 100; } else if (vol < 0) { vol = ((amp - pdevinfo->minamp[dev]) * 100 + (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) / 2) / (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]); vol = imin(imax(vol, 1), 100); } mix_set(pdevinfo->mixer, dev, vol, vol); } } /* * Recursively commutate specified record source. */ static uint32_t hdaa_audio_ctl_recsel_comm(struct hdaa_pcm_devinfo *pdevinfo, uint32_t src, nid_t nid, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; char buf[64]; int i, muted; uint32_t res = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->bindas == -1) continue; /* Call recursively to trace signal to it's source if needed. */ if ((src & cw->ossmask) != 0) { if (cw->ossdev < 0) { res |= hdaa_audio_ctl_recsel_comm(pdevinfo, src, w->conns[i], depth + 1); } else { res |= cw->ossmask; } } /* We have two special cases: mixers and others (selectors). */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl == NULL) continue; /* If we have input control on this node mute them * according to requested sources. */ muted = (src & cw->ossmask) ? 0 : 1; if (muted != ctl->forcemute) { ctl->forcemute = muted; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); } HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Recsel (%s): nid %d source %d %s\n", hdaa_audio_ctl_ossmixer_mask2allname( src, buf, sizeof(buf)), nid, i, muted?"mute":"unmute"); ); } else { if (w->nconns == 1) break; if ((src & cw->ossmask) == 0) continue; /* If we found requested source - select it and exit. */ hdaa_widget_connection_select(w, i); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Recsel (%s): nid %d source %d select\n", hdaa_audio_ctl_ossmixer_mask2allname( src, buf, sizeof(buf)), nid, i); ); break; } } return (res); } static uint32_t hdaa_audio_ctl_ossmixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; struct hdaa_audio_as *as; struct hdaa_audio_ctl *ctl; struct hdaa_chan *ch; int i, j; uint32_t ret = 0xffffffff; hdaa_lock(devinfo); if (pdevinfo->recas < 0) { hdaa_unlock(devinfo); return (0); } as = &devinfo->as[pdevinfo->recas]; /* For non-mixed associations we always recording everything. */ if (!as->mixed) { hdaa_unlock(devinfo); return (mix_getrecdevs(m)); } /* Commutate requested recsrc for each ADC. */ for (j = 0; j < as->num_chans; j++) { ch = &devinfo->chans[as->chans[j]]; for (i = 0; ch->io[i] >= 0; i++) { w = hdaa_widget_get(devinfo, ch->io[i]); if (w == NULL || w->enable == 0) continue; ret &= hdaa_audio_ctl_recsel_comm(pdevinfo, src, ch->io[i], 0); } } if (ret == 0xffffffff) ret = 0; /* * Some controls could be shared. Reset volumes for controls * related to previously chosen devices, as they may no longer * affect the signal. */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || !(ctl->ossmask & pdevinfo->recsrc)) continue; if (!((pdevinfo->playas >= 0 && ctl->widget->bindas == pdevinfo->playas) || (pdevinfo->recas >= 0 && ctl->widget->bindas == pdevinfo->recas) || (pdevinfo->index == 0 && ctl->widget->bindas == -2))) continue; for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if (pdevinfo->recsrc & (1 << j)) { ctl->devleft[j] = 0; ctl->devright[j] = 0; ctl->devmute[j] = 0; } } } /* * Some controls could be shared. Set volumes for controls * related to devices selected both previously and now. */ for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if ((ret | pdevinfo->recsrc) & (1 << j)) hdaa_audio_ctl_dev_volume(pdevinfo, j); } pdevinfo->recsrc = ret; hdaa_unlock(devinfo); return (ret); } static kobj_method_t hdaa_audio_ctl_ossmixer_methods[] = { KOBJMETHOD(mixer_init, hdaa_audio_ctl_ossmixer_init), KOBJMETHOD(mixer_set, hdaa_audio_ctl_ossmixer_set), KOBJMETHOD(mixer_setrecsrc, hdaa_audio_ctl_ossmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(hdaa_audio_ctl_ossmixer); static void hdaa_dump_gpi(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data, wake, unsol, sticky; if (HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPI_DATA(0, devinfo->nid)); wake = hda_command(dev, HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(0, devinfo->nid)); unsol = hda_command(dev, HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(0, devinfo->nid)); sticky = hda_command(dev, HDA_CMD_GET_GPI_STICKY_MASK(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); i++) { device_printf(dev, " GPI%d:%s%s%s state=%d", i, (sticky & (1 << i)) ? " sticky" : "", (unsol & (1 << i)) ? " unsol" : "", (wake & (1 << i)) ? " wake" : "", (data >> i) & 1); } } } static void hdaa_dump_gpio(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data, dir, enable, wake, unsol, sticky; if (HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); enable = hda_command(dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); dir = hda_command(dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); wake = hda_command(dev, HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(0, devinfo->nid)); unsol = hda_command(dev, HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(0, devinfo->nid)); sticky = hda_command(dev, HDA_CMD_GET_GPIO_STICKY_MASK(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); i++) { device_printf(dev, " GPIO%d: ", i); if ((enable & (1 << i)) == 0) { printf("disabled\n"); continue; } if ((dir & (1 << i)) == 0) { printf("input%s%s%s", (sticky & (1 << i)) ? " sticky" : "", (unsol & (1 << i)) ? " unsol" : "", (wake & (1 << i)) ? " wake" : ""); } else printf("output"); printf(" state=%d\n", (data >> i) & 1); } } } static void hdaa_dump_gpo(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data; if (HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); i++) { device_printf(dev, " GPO%d: state=%d", i, (data >> i) & 1); } } } static void hdaa_audio_parse(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; uint32_t res; int i; nid_t nid; nid = devinfo->nid; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_GPIO_COUNT)); devinfo->gpio_cap = res; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "NumGPIO=%d NumGPO=%d " "NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap)); hdaa_dump_gpi(devinfo); hdaa_dump_gpio(devinfo); hdaa_dump_gpo(devinfo); ); res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS)); devinfo->supp_stream_formats = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE)); devinfo->supp_pcm_size_rate = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP)); devinfo->outamp_cap = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP)); devinfo->inamp_cap = res; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) device_printf(devinfo->dev, "Ghost widget! nid=%d!\n", i); else { w->devinfo = devinfo; w->nid = i; w->enable = 1; w->selconn = -1; w->pflags = 0; w->ossdev = -1; w->bindas = -1; w->param.eapdbtl = HDA_INVALID; hdaa_widget_parse(w); } } } static void hdaa_audio_postprocess(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; hdaa_widget_postprocess(w); } } static void hdaa_audio_ctl_parse(struct hdaa_devinfo *devinfo) { struct hdaa_audio_ctl *ctls; struct hdaa_widget *w, *cw; int i, j, cnt, max, ocap, icap; int mute, offset, step, size; /* XXX This is redundant */ max = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->param.outamp_cap != 0) max++; if (w->param.inamp_cap != 0) { switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; max++; } break; default: max++; break; } } } devinfo->ctlcnt = max; if (max < 1) return; ctls = (struct hdaa_audio_ctl *)malloc( sizeof(*ctls) * max, M_HDAA, M_ZERO | M_NOWAIT); if (ctls == NULL) { /* Blekh! */ device_printf(devinfo->dev, "unable to allocate ctls!\n"); devinfo->ctlcnt = 0; return; } cnt = 0; for (i = devinfo->startnode; cnt < max && i < devinfo->endnode; i++) { if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; ocap = w->param.outamp_cap; icap = w->param.inamp_cap; if (ocap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(ocap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(ocap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(ocap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(ocap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "BUGGY outamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) ctls[cnt].ndir = HDAA_CTL_IN; else ctls[cnt].ndir = HDAA_CTL_OUT; ctls[cnt++].dir = HDAA_CTL_OUT; } if (icap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(icap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(icap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(icap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(icap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "BUGGY inamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].childwidget = cw; ctls[cnt].index = j; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; ctls[cnt].ndir = HDAA_CTL_IN; ctls[cnt++].dir = HDAA_CTL_IN; } break; default: if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) ctls[cnt].ndir = HDAA_CTL_OUT; else ctls[cnt].ndir = HDAA_CTL_IN; ctls[cnt++].dir = HDAA_CTL_IN; break; } } } devinfo->ctl = ctls; } static void hdaa_audio_as_parse(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, j, cnt, max, type, dir, assoc, seq, first, hpredir; /* Count present associations */ max = 0; for (j = 1; j < 16; j++) { for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config) != j) continue; max++; if (j != 15) /* There could be many 1-pin assocs #15 */ break; } } devinfo->ascnt = max; if (max < 1) return; as = (struct hdaa_audio_as *)malloc( sizeof(*as) * max, M_HDAA, M_ZERO | M_NOWAIT); if (as == NULL) { /* Blekh! */ device_printf(devinfo->dev, "unable to allocate assocs!\n"); devinfo->ascnt = 0; return; } for (i = 0; i < max; i++) { as[i].hpredir = -1; as[i].digital = 0; as[i].num_chans = 1; as[i].location = -1; } /* Scan associations skipping as=0. */ cnt = 0; for (j = 1; j < 16 && cnt < max; j++) { first = 16; hpredir = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; assoc = HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config); seq = HDA_CONFIG_DEFAULTCONF_SEQUENCE(w->wclass.pin.config); if (assoc != j) { continue; } KASSERT(cnt < max, ("%s: Associations owerflow (%d of %d)", __func__, cnt, max)); type = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; /* Get pin direction. */ if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER || type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT) dir = HDAA_CTL_OUT; else dir = HDAA_CTL_IN; /* If this is a first pin - create new association. */ if (as[cnt].pincnt == 0) { as[cnt].enable = 1; as[cnt].index = j; as[cnt].dir = dir; } if (seq < first) first = seq; /* Check association correctness. */ if (as[cnt].pins[seq] != 0) { device_printf(devinfo->dev, "%s: Duplicate pin %d (%d) " "in association %d! Disabling association.\n", __func__, seq, w->nid, j); as[cnt].enable = 0; } if (dir != as[cnt].dir) { device_printf(devinfo->dev, "%s: Pin %d has wrong " "direction for association %d! Disabling " "association.\n", __func__, w->nid, j); as[cnt].enable = 0; } if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { as[cnt].digital |= 0x1; if (HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) as[cnt].digital |= 0x2; if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap)) as[cnt].digital |= 0x4; } if (as[cnt].location == -1) { as[cnt].location = HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config); } else if (as[cnt].location != HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config)) { as[cnt].location = -2; } /* Headphones with seq=15 may mean redirection. */ if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT && seq == 15) hpredir = 1; as[cnt].pins[seq] = w->nid; as[cnt].pincnt++; /* Association 15 is a multiple unassociated pins. */ if (j == 15) cnt++; } if (j != 15 && as[cnt].pincnt > 0) { if (hpredir && as[cnt].pincnt > 1) as[cnt].hpredir = first; cnt++; } } for (i = 0; i < max; i++) { if (as[i].dir == HDAA_CTL_IN && (as[i].pincnt == 1 || as[i].pins[14] > 0 || as[i].pins[15] > 0)) as[i].mixed = 1; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "%d associations found:\n", max); for (i = 0; i < max; i++) { device_printf(devinfo->dev, "Association %d (%d) %s%s:\n", i, as[i].index, (as[i].dir == HDAA_CTL_IN)?"in":"out", as[i].enable?"":" (disabled)"); for (j = 0; j < 16; j++) { if (as[i].pins[j] == 0) continue; device_printf(devinfo->dev, " Pin nid=%d seq=%d\n", as[i].pins[j], j); } } ); devinfo->as = as; } /* * Trace path from DAC to pin. */ static nid_t hdaa_audio_trace_dac(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid, int dupseq, int min, int only, int depth) { struct hdaa_widget *w; int i, im = -1; nid_t m = 0, ret; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); } ); /* Use only unused widgets */ if (w->bindas >= 0 && w->bindas != as) { HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d busy by association %d\n", depth + 1, "", w->nid, w->bindas); } ); return (0); } if (dupseq < 0) { if (w->bindseqmask != 0) { HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); } ); return (0); } } else { /* If this is headphones - allow duplicate first pin. */ if (w->bindseqmask != 0 && (w->bindseqmask & (1 << dupseq)) == 0) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); ); return (0); } } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: /* Do not traverse input. AD1988 has digital monitor for which we are not ready. */ break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: /* If we are tracing HP take only dac of first pin. */ if ((only == 0 || only == w->nid) && (w->nid >= min) && (dupseq < 0 || w->nid == devinfo->as[as].dacs[0][dupseq])) m = w->nid; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Find reachable DACs with smallest nid respecting constraints. */ for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (w->selconn != -1 && w->selconn != i) continue; if ((ret = hdaa_audio_trace_dac(devinfo, as, seq, w->conns[i], dupseq, min, only, depth + 1)) != 0) { if (m == 0 || ret < m) { m = ret; im = i; } if (only || dupseq >= 0) break; } } if (im >= 0 && only && ((w->nconns > 1 && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) w->selconn = im; break; } if (m && only) { w->bindas = as; w->bindseqmask |= (1 << seq); } HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, m); } ); return (m); } /* * Trace path from widget to ADC. */ static nid_t hdaa_audio_trace_adc(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid, int mixed, int min, int only, int depth, int *length, int onlylength) { struct hdaa_widget *w, *wc; int i, j, im, lm = HDA_PARSE_MAXDEPTH; nid_t m = 0, ret; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); ); /* Use only unused widgets */ if (w->bindas >= 0 && w->bindas != as) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by association %d\n", depth + 1, "", w->nid, w->bindas); ); return (0); } if (!mixed && w->bindseqmask != 0) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); ); return (0); } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: if ((only == 0 || only == w->nid) && (w->nid >= min) && (onlylength == 0 || onlylength == depth)) { m = w->nid; *length = depth; } break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Try to find reachable ADCs with specified nid. */ for (j = devinfo->startnode; j < devinfo->endnode; j++) { wc = hdaa_widget_get(devinfo, j); if (wc == NULL || wc->enable == 0) continue; im = -1; for (i = 0; i < wc->nconns; i++) { if (wc->connsenable[i] == 0) continue; if (wc->conns[i] != nid) continue; if ((ret = hdaa_audio_trace_adc(devinfo, as, seq, j, mixed, min, only, depth + 1, length, onlylength)) != 0) { if (m == 0 || ret < m || (ret == m && *length < lm)) { m = ret; im = i; lm = *length; } else *length = lm; if (only) break; } } if (im >= 0 && only && ((wc->nconns > 1 && wc->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) wc->selconn = im; } break; } if (m && only) { w->bindas = as; w->bindseqmask |= (1 << seq); } HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, m); ); return (m); } /* * Erase trace path of the specified association. */ static void hdaa_audio_undo_trace(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_widget *w; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == as) { if (seq >= 0) { w->bindseqmask &= ~(1 << seq); if (w->bindseqmask == 0) { w->bindas = -1; w->selconn = -1; } } else { w->bindas = -1; w->bindseqmask = 0; w->selconn = -1; } } } } /* * Trace association path from DAC to output */ static int hdaa_audio_trace_as_out(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_audio_as *ases = devinfo->as; int i, hpredir; nid_t min, res; /* Find next pin */ for (i = seq; i < 16 && ases[as].pins[i] == 0; i++) ; /* Check if there is no any left. If so - we succeeded. */ if (i == 16) return (1); hpredir = (i == 15 && ases[as].fakeredir == 0)?ases[as].hpredir:-1; min = 0; do { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d with min nid %d", ases[as].pins[i], min); if (hpredir >= 0) printf(" and hpredir %d", hpredir); printf("\n"); ); /* Trace this pin taking min nid into account. */ res = hdaa_audio_trace_dac(devinfo, as, i, ases[as].pins[i], hpredir, min, 0, 0); if (res == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d seq %d with min " "nid %d", ases[as].pins[i], i, min); if (hpredir >= 0) printf(" and hpredir %d", hpredir); printf("\n"); ); return (0); } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to DAC %d", ases[as].pins[i], res); if (hpredir >= 0) printf(" and hpredir %d", hpredir); if (ases[as].fakeredir) printf(" with fake redirection"); printf("\n"); ); /* Trace again to mark the path */ hdaa_audio_trace_dac(devinfo, as, i, ases[as].pins[i], hpredir, min, res, 0); ases[as].dacs[0][i] = res; /* We succeeded, so call next. */ if (hdaa_audio_trace_as_out(devinfo, as, i + 1)) return (1); /* If next failed, we should retry with next min */ hdaa_audio_undo_trace(devinfo, as, i); ases[as].dacs[0][i] = 0; min = res + 1; } while (1); } /* * Check equivalency of two DACs. */ static int hdaa_audio_dacs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2) { struct hdaa_devinfo *devinfo = w1->devinfo; struct hdaa_widget *w3; int i, j, c1, c2; if (memcmp(&w1->param, &w2->param, sizeof(w1->param))) return (0); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w3 = hdaa_widget_get(devinfo, i); if (w3 == NULL || w3->enable == 0) continue; if (w3->bindas != w1->bindas) continue; if (w3->nconns == 0) continue; c1 = c2 = -1; for (j = 0; j < w3->nconns; j++) { if (w3->connsenable[j] == 0) continue; if (w3->conns[j] == w1->nid) c1 = j; if (w3->conns[j] == w2->nid) c2 = j; } if (c1 < 0) continue; if (c2 < 0) return (0); if (w3->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) return (0); } return (1); } /* * Check equivalency of two ADCs. */ static int hdaa_audio_adcs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2) { struct hdaa_devinfo *devinfo = w1->devinfo; struct hdaa_widget *w3, *w4; int i; if (memcmp(&w1->param, &w2->param, sizeof(w1->param))) return (0); if (w1->nconns != 1 || w2->nconns != 1) return (0); if (w1->conns[0] == w2->conns[0]) return (1); w3 = hdaa_widget_get(devinfo, w1->conns[0]); if (w3 == NULL || w3->enable == 0) return (0); w4 = hdaa_widget_get(devinfo, w2->conns[0]); if (w4 == NULL || w4->enable == 0) return (0); if (w3->bindas == w4->bindas && w3->bindseqmask == w4->bindseqmask) return (1); if (w4->bindas >= 0) return (0); if (w3->type != w4->type) return (0); if (memcmp(&w3->param, &w4->param, sizeof(w3->param))) return (0); if (w3->nconns != w4->nconns) return (0); for (i = 0; i < w3->nconns; i++) { if (w3->conns[i] != w4->conns[i]) return (0); } return (1); } /* * Look for equivalent DAC/ADC to implement second channel. */ static void hdaa_audio_adddac(struct hdaa_devinfo *devinfo, int asid) { struct hdaa_audio_as *as = &devinfo->as[asid]; struct hdaa_widget *w1, *w2; int i, pos; nid_t nid1, nid2; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Looking for additional %sC " "for association %d (%d)\n", (as->dir == HDAA_CTL_OUT) ? "DA" : "AD", asid, as->index); ); /* Find the existing DAC position and return if found more the one. */ pos = -1; for (i = 0; i < 16; i++) { if (as->dacs[0][i] <= 0) continue; if (pos >= 0 && as->dacs[0][i] != as->dacs[0][pos]) return; pos = i; } nid1 = as->dacs[0][pos]; w1 = hdaa_widget_get(devinfo, nid1); w2 = NULL; for (nid2 = devinfo->startnode; nid2 < devinfo->endnode; nid2++) { w2 = hdaa_widget_get(devinfo, nid2); if (w2 == NULL || w2->enable == 0) continue; if (w2->bindas >= 0) continue; if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) { if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) continue; if (hdaa_audio_dacs_equal(w1, w2)) break; } else { if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (hdaa_audio_adcs_equal(w1, w2)) break; } } if (nid2 >= devinfo->endnode) return; w2->bindas = w1->bindas; w2->bindseqmask = w1->bindseqmask; if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " ADC %d considered equal to ADC %d\n", nid2, nid1); ); w1 = hdaa_widget_get(devinfo, w1->conns[0]); w2 = hdaa_widget_get(devinfo, w2->conns[0]); w2->bindas = w1->bindas; w2->bindseqmask = w1->bindseqmask; } else { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " DAC %d considered equal to DAC %d\n", nid2, nid1); ); } for (i = 0; i < 16; i++) { if (as->dacs[0][i] <= 0) continue; as->dacs[as->num_chans][i] = nid2; } as->num_chans++; } /* * Trace association path from input to ADC */ static int hdaa_audio_trace_as_in(struct hdaa_devinfo *devinfo, int as) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w; int i, j, k, length; for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->bindas >= 0 && w->bindas != as) continue; /* Find next pin */ for (i = 0; i < 16; i++) { if (ases[as].pins[i] == 0) continue; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d to ADC %d\n", ases[as].pins[i], j); ); /* Trace this pin taking goal into account. */ if (hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 1, 0, j, 0, &length, 0) == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d to ADC %d, undo traces\n", ases[as].pins[i], j); ); hdaa_audio_undo_trace(devinfo, as, -1); for (k = 0; k < 16; k++) ases[as].dacs[0][k] = 0; break; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to ADC %d\n", ases[as].pins[i], j); ); ases[as].dacs[0][i] = j; } if (i == 16) return (1); } return (0); } /* * Trace association path from input to multiple ADCs */ static int hdaa_audio_trace_as_in_mch(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_audio_as *ases = devinfo->as; int i, length; nid_t min, res; /* Find next pin */ for (i = seq; i < 16 && ases[as].pins[i] == 0; i++) ; /* Check if there is no any left. If so - we succeeded. */ if (i == 16) return (1); min = 0; do { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d with min nid %d", ases[as].pins[i], min); printf("\n"); ); /* Trace this pin taking min nid into account. */ res = hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 0, min, 0, 0, &length, 0); if (res == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d seq %d with min " "nid %d", ases[as].pins[i], i, min); printf("\n"); ); return (0); } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to ADC %d\n", ases[as].pins[i], res); ); /* Trace again to mark the path */ hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 0, min, res, 0, &length, length); ases[as].dacs[0][i] = res; /* We succeeded, so call next. */ if (hdaa_audio_trace_as_in_mch(devinfo, as, i + 1)) return (1); /* If next failed, we should retry with next min */ hdaa_audio_undo_trace(devinfo, as, i); ases[as].dacs[0][i] = 0; min = res + 1; } while (1); } /* * Trace input monitor path from mixer to output association. */ static int hdaa_audio_trace_to_out(struct hdaa_devinfo *devinfo, nid_t nid, int depth) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w, *wc; int i, j; nid_t res = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); ); /* Use only unused widgets */ if (depth > 0 && w->bindas != -1) { if (w->bindas < 0 || ases[w->bindas].dir == HDAA_CTL_OUT) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d found output association %d\n", depth + 1, "", w->nid, w->bindas); ); if (w->bindas >= 0) w->pflags |= HDAA_ADC_MONITOR; return (1); } else { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by input association %d\n", depth + 1, "", w->nid, w->bindas); ); return (0); } } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: /* Do not traverse input. AD1988 has digital monitor for which we are not ready. */ break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Try to find reachable ADCs with specified nid. */ for (j = devinfo->startnode; j < devinfo->endnode; j++) { wc = hdaa_widget_get(devinfo, j); if (wc == NULL || wc->enable == 0) continue; for (i = 0; i < wc->nconns; i++) { if (wc->connsenable[i] == 0) continue; if (wc->conns[i] != nid) continue; if (hdaa_audio_trace_to_out(devinfo, j, depth + 1) != 0) { res = 1; if (wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && wc->selconn == -1) wc->selconn = i; } } } break; } if (res && w->bindas == -1) w->bindas = -2; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, res); ); return (res); } /* * Trace extra associations (beeper, monitor) */ static void hdaa_audio_trace_as_extra(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int j; /* Input monitor */ /* Find mixer associated with input, but supplying signal for output associations. Hope it will be input monitor. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing input monitor\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d is input monitor\n", w->nid); ); w->ossdev = SOUND_MIXER_IMIX; } } /* Other inputs monitor */ /* Find input pins supplying signal for output associations. Hope it will be input monitoring. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing other input monitors\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d is input monitor\n", w->nid); ); } } /* Beeper */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing beeper\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) continue; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d traced to out\n", j); ); } w->bindas = -2; } } /* * Bind assotiations to PCM channels */ static void hdaa_audio_bind_as(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int i, j, cnt = 0, free; for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable) cnt += as[j].num_chans; } if (devinfo->num_chans == 0) { devinfo->chans = (struct hdaa_chan *)malloc( sizeof(struct hdaa_chan) * cnt, M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->chans == NULL) { device_printf(devinfo->dev, "Channels memory allocation failed!\n"); return; } } else { devinfo->chans = (struct hdaa_chan *)realloc(devinfo->chans, sizeof(struct hdaa_chan) * (devinfo->num_chans + cnt), M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->chans == NULL) { devinfo->num_chans = 0; device_printf(devinfo->dev, "Channels memory allocation failed!\n"); return; } /* Fixup relative pointers after realloc */ for (j = 0; j < devinfo->num_chans; j++) devinfo->chans[j].caps.fmtlist = devinfo->chans[j].fmtlist; } free = devinfo->num_chans; devinfo->num_chans += cnt; for (j = free; j < free + cnt; j++) { devinfo->chans[j].devinfo = devinfo; devinfo->chans[j].as = -1; } /* Assign associations in order of their numbers, */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; for (i = 0; i < as[j].num_chans; i++) { devinfo->chans[free].as = j; devinfo->chans[free].asindex = i; devinfo->chans[free].dir = (as[j].dir == HDAA_CTL_IN) ? PCMDIR_REC : PCMDIR_PLAY; hdaa_pcmchannel_setup(&devinfo->chans[free]); as[j].chans[i] = free; free++; } } } static void hdaa_audio_disable_nonaudio(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; /* Disable power and volume widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to it's" " non-audio type.\n", w->nid); ); } } } static void hdaa_audio_disable_useless(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int done, found, i, j, k; /* Disable useless pins. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling pin nid %d due" " to None connectivity.\n", w->nid); ); } else if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK) == 0) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unassociated" " pin nid %d.\n", w->nid); ); } } } do { done = 1; /* Disable and mute controls for disabled widgets. */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0) continue; if (ctl->widget->enable == 0 || (ctl->childwidget != NULL && ctl->childwidget->enable == 0)) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; if (ctl->ndir == HDAA_CTL_IN) ctl->widget->connsenable[ctl->index] = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling ctl %d nid %d cnid %d due" " to disabled widget.\n", i, ctl->widget->nid, (ctl->childwidget != NULL)? ctl->childwidget->nid:-1); ); } } /* Disable useless widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; /* Disable inputs with disabled child widgets. */ for (j = 0; j < w->nconns; j++) { if (w->connsenable[j]) { cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) { w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d connection %d due" " to disabled child widget.\n", i, j); ); } } } if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; /* Disable mixers and selectors without inputs. */ found = 0; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j]) { found = 1; break; } } if (found == 0) { w->enable = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to all it's" " inputs disabled.\n", w->nid); ); } /* Disable nodes without consumers. */ if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; found = 0; for (k = devinfo->startnode; k < devinfo->endnode; k++) { cw = hdaa_widget_get(devinfo, k); if (cw == NULL || cw->enable == 0) continue; for (j = 0; j < cw->nconns; j++) { if (cw->connsenable[j] && cw->conns[j] == i) { found = 1; break; } } } if (found == 0) { w->enable = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to all it's" " consumers disabled.\n", w->nid); ); } } } while (done == 0); } static void hdaa_audio_disable_unas(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int i, j, k; /* Disable unassosiated widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == -1) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unassociated nid %d.\n", w->nid); ); } } /* Disable input connections on input pin and * output on output. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->bindas < 0) continue; if (as[w->bindas].dir == HDAA_CTL_IN) { for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling connection to input pin " "nid %d conn %d.\n", i, j); ); } ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, -1, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } } else { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } for (k = devinfo->startnode; k < devinfo->endnode; k++) { cw = hdaa_widget_get(devinfo, k); if (cw == NULL || cw->enable == 0) continue; for (j = 0; j < cw->nconns; j++) { if (cw->connsenable[j] && cw->conns[j] == i) { cw->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling connection from output pin " "nid %d conn %d cnid %d.\n", k, j, i); ); if (cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && cw->nconns > 1) continue; ctl = hdaa_audio_ctl_amp_get(devinfo, k, HDAA_CTL_IN, j, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } } } } } } } static void hdaa_audio_disable_notselected(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int i, j; /* On playback path we can safely disable all unseleted inputs. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->nconns <= 1) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; if (w->bindas < 0 || as[w->bindas].dir == HDAA_CTL_IN) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; if (w->selconn < 0 || w->selconn == j) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unselected connection " "nid %d conn %d.\n", i, j); ); } } } static void hdaa_audio_disable_crossas(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int i, j; /* Disable crossassociatement and unwanted crosschannel connections. */ /* ... using selectors */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->nconns <= 1) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; /* Allow any -> mix */ if (w->bindas == -2) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || w->enable == 0) continue; /* Allow mix -> out. */ if (cw->bindas == -2 && w->bindas >= 0 && ases[w->bindas].dir == HDAA_CTL_OUT) continue; /* Allow mix -> mixed-in. */ if (cw->bindas == -2 && w->bindas >= 0 && ases[w->bindas].mixed) continue; /* Allow in -> mix. */ if ((w->pflags & HDAA_ADC_MONITOR) && cw->bindas >= 0 && ases[cw->bindas].dir == HDAA_CTL_IN) continue; /* Allow if have common as/seqs. */ if (w->bindas == cw->bindas && (w->bindseqmask & cw->bindseqmask) != 0) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling crossassociatement connection " "nid %d conn %d cnid %d.\n", i, j, cw->nid); ); } } /* ... using controls */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->childwidget == NULL) continue; /* Allow any -> mix */ if (ctl->widget->bindas == -2) continue; /* Allow mix -> out. */ if (ctl->childwidget->bindas == -2 && ctl->widget->bindas >= 0 && ases[ctl->widget->bindas].dir == HDAA_CTL_OUT) continue; /* Allow mix -> mixed-in. */ if (ctl->childwidget->bindas == -2 && ctl->widget->bindas >= 0 && ases[ctl->widget->bindas].mixed) continue; /* Allow in -> mix. */ if ((ctl->widget->pflags & HDAA_ADC_MONITOR) && ctl->childwidget->bindas >= 0 && ases[ctl->childwidget->bindas].dir == HDAA_CTL_IN) continue; /* Allow if have common as/seqs. */ if (ctl->widget->bindas == ctl->childwidget->bindas && (ctl->widget->bindseqmask & ctl->childwidget->bindseqmask) != 0) continue; ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; if (ctl->ndir == HDAA_CTL_IN) ctl->widget->connsenable[ctl->index] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling crossassociatement connection " "ctl %d nid %d cnid %d.\n", i, ctl->widget->nid, ctl->childwidget->nid); ); } } /* * Find controls to control amplification for source and calculate possible * amplification range. */ static int hdaa_audio_ctl_source_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, int ossdev, int ctlable, int depth, int *minamp, int *maxamp) { struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, conns = 0, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (found); /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; conns++; } } /* If this is not a first step - use input mixer. Pins have common input ctl so care must be taken. */ if (depth > 0 && ctlable && (conns == 1 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } /* If widget has own ossdev - not traverse it. It will be traversed on its own. */ if (w->ossdev >= 0 && depth > 0) return (found); /* We must not traverse pin */ if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && depth > 0) return (found); /* record that this widget exports such signal, */ w->ossmask |= (1 << ossdev); /* * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. */ if (conns > 1 && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) ctlable = 0; if (ctlable) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } cminamp = cmaxamp = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) { tminamp = tmaxamp = 0; found += hdaa_audio_ctl_source_amp(devinfo, wc->nid, j, ossdev, ctlable, depth + 1, &tminamp, &tmaxamp); if (cminamp == 0 && cmaxamp == 0) { cminamp = tminamp; cmaxamp = tmaxamp; } else if (tminamp != tmaxamp) { cminamp = imax(cminamp, tminamp); cmaxamp = imin(cmaxamp, tmaxamp); } } } } if (*minamp == *maxamp && cminamp < cmaxamp) { *minamp += cminamp; *maxamp += cmaxamp; } return (found); } /* * Find controls to control amplification for destination and calculate * possible amplification range. */ static int hdaa_audio_ctl_dest_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, int ossdev, int depth, int *minamp, int *maxamp) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, consumers, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (found); if (depth > 0) { /* If this node produce output for several consumers, we can't touch it. */ consumers = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) consumers++; } } /* The only exception is if real HP redirection is configured and this is a duplication point. XXX: Actually exception is not completely correct. XXX: Duplication point check is not perfect. */ if ((consumers == 2 && (w->bindas < 0 || as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) return (found); /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } /* We must not traverse pin */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && depth > 0) return (found); cminamp = cmaxamp = 0; for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (index >= 0 && i != index) continue; tminamp = tmaxamp = 0; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { tminamp += MINQDB(ctl); tmaxamp += MAXQDB(ctl); } } found += hdaa_audio_ctl_dest_amp(devinfo, w->conns[i], -1, ossdev, depth + 1, &tminamp, &tmaxamp); if (cminamp == 0 && cmaxamp == 0) { cminamp = tminamp; cmaxamp = tmaxamp; } else if (tminamp != tmaxamp) { cminamp = imax(cminamp, tminamp); cmaxamp = imin(cmaxamp, tmaxamp); } } if (*minamp == *maxamp && cminamp < cmaxamp) { *minamp += cminamp; *maxamp += cmaxamp; } return (found); } /* * Assign OSS names to sound sources */ static void hdaa_audio_assign_names(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int i, j; int type = -1, use, used = 0; static const int types[7][13] = { { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, -1 }, /* line */ { SOUND_MIXER_MONITOR, SOUND_MIXER_MIC, -1 }, /* int mic */ { SOUND_MIXER_MIC, SOUND_MIXER_MONITOR, -1 }, /* ext mic */ { SOUND_MIXER_CD, -1 }, /* cd */ { SOUND_MIXER_SPEAKER, -1 }, /* speaker */ { SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, -1 }, /* digital */ { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, SOUND_MIXER_PHONEIN, SOUND_MIXER_PHONEOUT, SOUND_MIXER_VIDEO, SOUND_MIXER_RADIO, SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, SOUND_MIXER_MONITOR, -1 } /* others */ }; /* Surely known names */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == -1) continue; use = -1; switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (as[w->bindas].dir == HDAA_CTL_OUT) break; type = -1; switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: type = 0; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) break; type = 1; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: type = 3; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: type = 4; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN: case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_IN: type = 5; break; } if (type == -1) break; j = 0; while (types[type][j] >= 0 && (used & (1 << types[type][j])) != 0) { j++; } if (types[type][j] >= 0) use = types[type][j]; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: use = SOUND_MIXER_PCM; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: use = SOUND_MIXER_SPEAKER; break; default: break; } if (use >= 0) { w->ossdev = use; used |= (1 << use); } } /* Semi-known names */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev >= 0) continue; if (w->bindas == -1) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (as[w->bindas].dir == HDAA_CTL_OUT) continue; type = -1; switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: case HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_AUX: type = 0; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: type = 2; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT: type = 5; break; } if (type == -1) break; j = 0; while (types[type][j] >= 0 && (used & (1 << types[type][j])) != 0) { j++; } if (types[type][j] >= 0) { w->ossdev = types[type][j]; used |= (1 << types[type][j]); } } /* Others */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev >= 0) continue; if (w->bindas == -1) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (as[w->bindas].dir == HDAA_CTL_OUT) continue; j = 0; while (types[6][j] >= 0 && (used & (1 << types[6][j])) != 0) { j++; } if (types[6][j] >= 0) { w->ossdev = types[6][j]; used |= (1 << types[6][j]); } } } static void hdaa_audio_build_tree(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int j, res; /* Trace all associations in order of their numbers. */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing association %d (%d)\n", j, as[j].index); ); if (as[j].dir == HDAA_CTL_OUT) { retry: res = hdaa_audio_trace_as_out(devinfo, j, 0); if (res == 0 && as[j].hpredir >= 0 && as[j].fakeredir == 0) { /* If CODEC can't do analog HP redirection try to make it using one more DAC. */ as[j].fakeredir = 1; goto retry; } } else if (as[j].mixed) res = hdaa_audio_trace_as_in(devinfo, j); else res = hdaa_audio_trace_as_in_mch(devinfo, j, 0); if (res) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Association %d (%d) trace succeeded\n", j, as[j].index); ); } else { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Association %d (%d) trace failed\n", j, as[j].index); ); as[j].enable = 0; } } /* Look for additional DACs/ADCs. */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; hdaa_audio_adddac(devinfo, j); } /* Trace mixer and beeper pseudo associations. */ hdaa_audio_trace_as_extra(devinfo); } /* * Store in pdevinfo new data about whether and how we can control signal * for OSS device to/from specified widget. */ static void hdaa_adjust_amp(struct hdaa_widget *w, int ossdev, int found, int minamp, int maxamp) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_pcm_devinfo *pdevinfo; if (w->bindas >= 0) pdevinfo = devinfo->as[w->bindas].pdevinfo; else pdevinfo = &devinfo->devs[0]; if (found) pdevinfo->ossmask |= (1 << ossdev); if (minamp == 0 && maxamp == 0) return; if (pdevinfo->minamp[ossdev] == 0 && pdevinfo->maxamp[ossdev] == 0) { pdevinfo->minamp[ossdev] = minamp; pdevinfo->maxamp[ossdev] = maxamp; } else { pdevinfo->minamp[ossdev] = imax(pdevinfo->minamp[ossdev], minamp); pdevinfo->maxamp[ossdev] = imin(pdevinfo->maxamp[ossdev], maxamp); } } /* * Trace signals from/to all possible sources/destionstions to find possible * recording sources, OSS device control ranges and to assign controls. */ static void hdaa_audio_assign_mixers(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *cw; int i, j, minamp, maxamp, found; /* Assign mixers to the tree. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; minamp = maxamp = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET || (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && as[w->bindas].dir == HDAA_CTL_IN)) { if (w->ossdev < 0) continue; found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, w->ossdev, 1, 0, &minamp, &maxamp); hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, SOUND_MIXER_RECLEV, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_RECLEV, found, minamp, maxamp); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && as[w->bindas].dir == HDAA_CTL_OUT) { found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, SOUND_MIXER_VOLUME, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_VOLUME, found, minamp, maxamp); } if (w->ossdev == SOUND_MIXER_IMIX) { minamp = maxamp = 0; found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, w->ossdev, 1, 0, &minamp, &maxamp); if (minamp == maxamp) { /* If we are unable to control input monitor as source - try to control it as destination. */ found += hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, w->ossdev, 0, &minamp, &maxamp); w->pflags |= HDAA_IMIX_AS_DST; } hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp); } if (w->pflags & HDAA_ADC_MONITOR) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas == -1) continue; if (cw->bindas >= 0 && as[cw->bindas].dir != HDAA_CTL_IN) continue; minamp = maxamp = 0; found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, j, SOUND_MIXER_IGAIN, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_IGAIN, found, minamp, maxamp); } } } } static void hdaa_audio_prepare_pin_ctrl(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; uint32_t pincap; int i; for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && w->waspin == 0) continue; pincap = w->wclass.pin.cap; /* Disable everything. */ if (devinfo->init_clear) { w->wclass.pin.ctrl &= ~( HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); } if (w->enable == 0) { /* Pin is unused so left it disabled. */ continue; } else if (w->waspin) { /* Enable input for beeper input. */ w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; } else if (w->bindas < 0 || as[w->bindas].enable == 0) { /* Pin is unused so left it disabled. */ continue; } else if (as[w->bindas].dir == HDAA_CTL_IN) { /* Input pin, configure for input. */ if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; if ((devinfo->quirks & HDAA_QUIRK_IVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->quirks & HDAA_QUIRK_IVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->quirks & HDAA_QUIRK_IVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } else { /* Output pin, configure for output. */ if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap) && (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; if ((devinfo->quirks & HDAA_QUIRK_OVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->quirks & HDAA_QUIRK_OVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->quirks & HDAA_QUIRK_OVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } } } static void hdaa_audio_ctl_commit(struct hdaa_devinfo *devinfo) { struct hdaa_audio_ctl *ctl; int i, z; i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->ossmask != 0) { /* Mute disabled and mixer controllable controls. * Last will be initialized by mixer_init(). * This expected to reduce click on startup. */ hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_ALL, 0, 0); continue; } /* Init fixed controls to 0dB amplification. */ z = ctl->offset; if (z > ctl->step) z = ctl->step; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_NONE, z, z); } } static void hdaa_gpio_commit(struct hdaa_devinfo *devinfo) { uint32_t gdata, gmask, gdir; int i, numgpio; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); if (devinfo->gpio != 0 && numgpio != 0) { gdata = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); gmask = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); gdir = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); for (i = 0; i < numgpio; i++) { if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_SET(i)) { gdata |= (1 << i); gmask |= (1 << i); gdir |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_CLEAR(i)) { gdata &= ~(1 << i); gmask |= (1 << i); gdir |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_DISABLE(i)) { gmask &= ~(1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_INPUT(i)) { gmask |= (1 << i); gdir &= ~(1 << i); } } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "GPIO commit\n"); ); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_ENABLE_MASK(0, devinfo->nid, gmask)); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_DIRECTION(0, devinfo->nid, gdir)); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_DATA(0, devinfo->nid, gdata)); HDA_BOOTVERBOSE( hdaa_dump_gpio(devinfo); ); } } static void hdaa_gpo_commit(struct hdaa_devinfo *devinfo) { uint32_t gdata; int i, numgpo; numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); if (devinfo->gpo != 0 && numgpo != 0) { gdata = hda_command(devinfo->dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); for (i = 0; i < numgpo; i++) { if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_SET(i)) { gdata |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_CLEAR(i)) { gdata &= ~(1 << i); } } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "GPO commit\n"); ); hda_command(devinfo->dev, HDA_CMD_SET_GPO_DATA(0, devinfo->nid, gdata)); HDA_BOOTVERBOSE( hdaa_dump_gpo(devinfo); ); } } static void hdaa_audio_commit(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; /* Commit controls. */ hdaa_audio_ctl_commit(devinfo); /* Commit selectors, pins and EAPD. */ for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL) continue; if (w->selconn == -1) w->selconn = 0; if (w->nconns > 0) hdaa_widget_connection_select(w, w->selconn); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) { hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid, w->wclass.pin.ctrl)); } if (w->param.eapdbtl != HDA_INVALID) { uint32_t val; val = w->param.eapdbtl; if (devinfo->quirks & HDAA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hda_command(devinfo->dev, HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val)); } } hdaa_gpio_commit(devinfo); hdaa_gpo_commit(devinfo); } static void hdaa_powerup(struct hdaa_devinfo *devinfo) { int i; hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, devinfo->nid, HDA_CMD_POWER_STATE_D0)); DELAY(100); for (i = devinfo->startnode; i < devinfo->endnode; i++) { hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, i, HDA_CMD_POWER_STATE_D0)); } DELAY(1000); } static int hdaa_pcmchannel_setup(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; uint32_t cap, fmtcap, pcmcap; int i, j, ret, channels, onlystereo; uint16_t pinset; ch->caps = hdaa_caps; ch->caps.fmtlist = ch->fmtlist; ch->bit16 = 1; ch->bit32 = 0; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; ch->stripecap = 0xff; ret = 0; channels = 0; onlystereo = 1; pinset = 0; fmtcap = devinfo->supp_stream_formats; pcmcap = devinfo->supp_pcm_size_rate; for (i = 0; i < 16; i++) { /* Check as is correct */ if (ch->as < 0) break; /* Cound only present DACs */ if (as[ch->as].dacs[ch->asindex][i] <= 0) continue; /* Ignore duplicates */ for (j = 0; j < ret; j++) { if (ch->io[j] == as[ch->as].dacs[ch->asindex][i]) break; } if (j < ret) continue; w = hdaa_widget_get(devinfo, as[ch->as].dacs[ch->asindex][i]); if (w == NULL || w->enable == 0) continue; cap = w->param.supp_stream_formats; if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap) && !HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) continue; /* Many CODECs does not declare AC3 support on SPDIF. I don't beleave that they doesn't support it! */ if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) cap |= HDA_PARAM_SUPP_STREAM_FORMATS_AC3_MASK; if (ret == 0) { fmtcap = cap; pcmcap = w->param.supp_pcm_size_rate; } else { fmtcap &= cap; pcmcap &= w->param.supp_pcm_size_rate; } ch->io[ret++] = as[ch->as].dacs[ch->asindex][i]; ch->stripecap &= w->wclass.conv.stripecap; /* Do not count redirection pin/dac channels. */ if (i == 15 && as[ch->as].hpredir >= 0) continue; channels += HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) + 1; if (HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) != 1) onlystereo = 0; pinset |= (1 << i); } ch->io[ret] = -1; ch->channels = channels; if (as[ch->as].fakeredir) ret--; /* Standard speaks only about stereo pins and playback, ... */ if ((!onlystereo) || as[ch->as].mixed) pinset = 0; /* ..., but there it gives us info about speakers layout. */ as[ch->as].pinset = pinset; ch->supp_stream_formats = fmtcap; ch->supp_pcm_size_rate = pcmcap; /* * 8bit = 0 * 16bit = 1 * 20bit = 2 * 24bit = 3 * 32bit = 4 */ if (ret > 0) { i = 0; if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(fmtcap)) { if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(pcmcap)) ch->bit16 = 1; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(pcmcap)) ch->bit16 = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap)) ch->bit32 = 3; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap)) ch->bit32 = 2; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap)) ch->bit32 = 4; if (!(devinfo->quirks & HDAA_QUIRK_FORCESTEREO)) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 1, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 1, 0); } if (channels >= 2) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 2, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 2, 0); } if (channels >= 3 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 1); } if (channels >= 4) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 0); if (!onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 1); } } if (channels >= 5 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 1); } if (channels >= 6) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 1); if (!onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 0); } } if (channels >= 7 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 1); } if (channels >= 8) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 8, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 8, 1); } } if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(fmtcap)) { ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 2, 0); if (channels >= 8) { ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 8, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 8, 1); } } ch->fmtlist[i] = 0; i = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(pcmcap)) ch->pcmrates[i++] = 8000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(pcmcap)) ch->pcmrates[i++] = 11025; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(pcmcap)) ch->pcmrates[i++] = 16000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(pcmcap)) ch->pcmrates[i++] = 22050; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(pcmcap)) ch->pcmrates[i++] = 32000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(pcmcap)) ch->pcmrates[i++] = 44100; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(pcmcap)) */ ch->pcmrates[i++] = 48000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(pcmcap)) ch->pcmrates[i++] = 88200; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(pcmcap)) ch->pcmrates[i++] = 96000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(pcmcap)) ch->pcmrates[i++] = 176400; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(pcmcap)) ch->pcmrates[i++] = 192000; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(pcmcap)) */ ch->pcmrates[i] = 0; if (i > 0) { ch->caps.minspeed = ch->pcmrates[0]; ch->caps.maxspeed = ch->pcmrates[i - 1]; } } return (ret); } static void hdaa_prepare_pcms(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int i, j, k, apdev = 0, ardev = 0, dpdev = 0, drdev = 0; for (i = 0; i < devinfo->ascnt; i++) { if (as[i].enable == 0) continue; if (as[i].dir == HDAA_CTL_IN) { if (as[i].digital) drdev++; else ardev++; } else { if (as[i].digital) dpdev++; else apdev++; } } devinfo->num_devs = max(ardev, apdev) + max(drdev, dpdev); devinfo->devs = (struct hdaa_pcm_devinfo *)malloc( devinfo->num_devs * sizeof(struct hdaa_pcm_devinfo), M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->devs == NULL) { device_printf(devinfo->dev, "Unable to allocate memory for devices\n"); return; } for (i = 0; i < devinfo->num_devs; i++) { devinfo->devs[i].index = i; devinfo->devs[i].devinfo = devinfo; devinfo->devs[i].playas = -1; devinfo->devs[i].recas = -1; devinfo->devs[i].digital = 255; } for (i = 0; i < devinfo->ascnt; i++) { if (as[i].enable == 0) continue; for (j = 0; j < devinfo->num_devs; j++) { if (devinfo->devs[j].digital != 255 && (!devinfo->devs[j].digital) != (!as[i].digital)) continue; if (as[i].dir == HDAA_CTL_IN) { if (devinfo->devs[j].recas >= 0) continue; devinfo->devs[j].recas = i; } else { if (devinfo->devs[j].playas >= 0) continue; devinfo->devs[j].playas = i; } as[i].pdevinfo = &devinfo->devs[j]; for (k = 0; k < as[i].num_chans; k++) { devinfo->chans[as[i].chans[k]].pdevinfo = &devinfo->devs[j]; } devinfo->devs[j].digital = as[i].digital; break; } } } static void hdaa_create_pcms(struct hdaa_devinfo *devinfo) { int i; for (i = 0; i < devinfo->num_devs; i++) { struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i]; pdevinfo->dev = device_add_child(devinfo->dev, "pcm", -1); device_set_ivars(pdevinfo->dev, (void *)pdevinfo); } } static void hdaa_dump_ctls(struct hdaa_pcm_devinfo *pdevinfo, const char *banner, uint32_t flag) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_ctl *ctl; char buf[64]; int i, j, printed = 0; if (flag == 0) { flag = ~(SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_CD | SOUND_MASK_LINE | SOUND_MASK_RECLEV | SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | SOUND_MASK_IMIX | SOUND_MASK_MONITOR); } for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if ((flag & (1 << j)) == 0) continue; i = 0; printed = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget->enable == 0) continue; if (!((pdevinfo->playas >= 0 && ctl->widget->bindas == pdevinfo->playas) || (pdevinfo->recas >= 0 && ctl->widget->bindas == pdevinfo->recas) || (ctl->widget->bindas == -2 && pdevinfo->index == 0))) continue; if ((ctl->ossmask & (1 << j)) == 0) continue; if (printed == 0) { if (banner != NULL) { device_printf(pdevinfo->dev, "%s", banner); } else { device_printf(pdevinfo->dev, "Unknown Ctl"); } printf(" (OSS: %s)", hdaa_audio_ctl_ossmixer_mask2allname(1 << j, buf, sizeof(buf))); if (pdevinfo->ossmask & (1 << j)) { printf(": %+d/%+ddB\n", pdevinfo->minamp[j] / 4, pdevinfo->maxamp[j] / 4); } else printf("\n"); printed = 1; } device_printf(pdevinfo->dev, " +- ctl %2d (nid %3d %s", i, ctl->widget->nid, (ctl->ndir == HDAA_CTL_IN)?"in ":"out"); if (ctl->ndir == HDAA_CTL_IN && ctl->ndir == ctl->dir) printf(" %2d): ", ctl->index); else printf("): "); if (ctl->step > 0) { printf("%+d/%+ddB (%d steps)%s\n", MINQDB(ctl) / 4, MAXQDB(ctl) / 4, ctl->step + 1, ctl->mute?" + mute":""); } else printf("%s\n", ctl->mute?"mute":""); } } if (printed) device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_audio_formats(device_t dev, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { device_printf(dev, " Stream cap: 0x%08x", cap); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) printf(" AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) printf(" FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) printf(" PCM"); printf("\n"); } cap = pcmcap; if (cap != 0) { device_printf(dev, " PCM cap: 0x%08x", cap); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) printf(" 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) printf(" 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) printf(" 32"); printf(" bits,"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) printf(" 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) printf(" 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) printf(" 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) printf(" 44"); printf(" 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) printf(" 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) printf(" 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) printf(" 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) printf(" 192"); printf(" KHz\n"); } } static void hdaa_dump_pin(struct hdaa_widget *w) { uint32_t pincap; pincap = w->wclass.pin.cap; device_printf(w->devinfo->dev, " Pin cap: 0x%08x", pincap); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) printf(" ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) printf(" TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) printf(" PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) printf(" HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) printf(" OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) printf(" IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) printf(" BAL"); if (HDA_PARAM_PIN_CAP_HDMI(pincap)) printf(" HDMI"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { printf(" VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) printf(" 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) printf(" 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) printf(" 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) printf(" GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) printf(" HIZ"); printf(" ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) printf(" EAPD"); if (HDA_PARAM_PIN_CAP_DP(pincap)) printf(" DP"); if (HDA_PARAM_PIN_CAP_HBR(pincap)) printf(" HBR"); printf("\n"); device_printf(w->devinfo->dev, " Pin config: 0x%08x\n", w->wclass.pin.config); device_printf(w->devinfo->dev, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) printf(" HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) printf(" IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) printf(" OUT"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03) printf(" HBR"); else if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) printf(" EPTs"); } else { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) printf(" VREFs"); } printf("\n"); } static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf) { device_printf(w->devinfo->dev, "%2d %08x %-2d %-2d " "%-13s %-5s %-7s %-10s %-7s %d%s\n", w->nid, conf, HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf), (w->enable == 0)?" DISA":""); } static void hdaa_dump_pin_configs(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; device_printf(devinfo->dev, "nid 0x as seq " "device conn jack loc color misc\n"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_dump_pin_config(w, w->wclass.pin.config); } } static void hdaa_dump_amp(device_t dev, uint32_t cap, const char *banner) { int offset, size, step; offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap); device_printf(dev, " %s amp: 0x%08x " "mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n", banner, cap, HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), step, size, offset, ((0 - offset) * (size + 1)) / 4, ((step - offset) * (size + 1)) / 4); } static void hdaa_dump_nodes(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w, *cw; char buf[64]; int i, j; device_printf(devinfo->dev, "\n"); device_printf(devinfo->dev, "Default parameters:\n"); hdaa_dump_audio_formats(devinfo->dev, devinfo->supp_stream_formats, devinfo->supp_pcm_size_rate); hdaa_dump_amp(devinfo->dev, devinfo->inamp_cap, " Input"); hdaa_dump_amp(devinfo->dev, devinfo->outamp_cap, "Output"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) { device_printf(devinfo->dev, "Ghost widget nid=%d\n", i); continue; } device_printf(devinfo->dev, "\n"); device_printf(devinfo->dev, " nid: %d%s\n", w->nid, (w->enable == 0) ? " [DISABLED]" : ""); device_printf(devinfo->dev, " Name: %s\n", w->name); device_printf(devinfo->dev, " Widget cap: 0x%08x", w->param.widget_cap); if (w->param.widget_cap & 0x0ee1) { if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap)) printf(" LRSWAP"); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap)) printf(" PWR"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) printf(" DIGITAL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) printf(" UNSOL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) printf(" PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) printf(" STRIPE(x%d)", 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) printf(" STEREO"); else if (j > 1) printf(" %dCH", j + 1); } printf("\n"); if (w->bindas != -1) { device_printf(devinfo->dev, " Association: %d (0x%04x)\n", w->bindas, w->bindseqmask); } if (w->ossmask != 0 || w->ossdev >= 0) { device_printf(devinfo->dev, " OSS: %s", hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) printf(" (%s)", ossnames[w->ossdev]); printf("\n"); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_dump_audio_formats(devinfo->dev, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) hdaa_dump_pin(w); if (w->param.eapdbtl != HDA_INVALID) device_printf(devinfo->dev, " EAPD: 0x%08x\n", w->param.eapdbtl); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdaa_dump_amp(devinfo->dev, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdaa_dump_amp(devinfo->dev, w->param.inamp_cap, " Input"); if (w->nconns > 0) device_printf(devinfo->dev, " Connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); device_printf(devinfo->dev, " + %s<- nid=%d [%s]", (w->connsenable[j] == 0)?"[DISABLED] ":"", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) printf(" [UNKNOWN]"); else if (cw->enable == 0) printf(" [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) printf(" (selected)"); printf("\n"); } } } static void hdaa_dump_dst_nid(struct hdaa_pcm_devinfo *pdevinfo, nid_t nid, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; char buf[64]; int i; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; if (depth == 0) device_printf(pdevinfo->dev, "%*s", 4, ""); else device_printf(pdevinfo->dev, "%*s + <- ", 4 + (depth - 1) * 7, ""); printf("nid=%d [%s]", w->nid, w->name); if (depth > 0) { if (w->ossmask == 0) { printf("\n"); return; } printf(" [src: %s]", hdaa_audio_ctl_ossmixer_mask2allname( w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) { printf("\n"); return; } } printf("\n"); for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->bindas == -1) continue; hdaa_dump_dst_nid(pdevinfo, w->conns[i], depth + 1); } } static void hdaa_dump_dac(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as; struct hdaa_widget *w; nid_t *nids; int chid, i; if (pdevinfo->playas < 0) return; device_printf(pdevinfo->dev, "Playback:\n"); chid = devinfo->as[pdevinfo->playas].chans[0]; hdaa_dump_audio_formats(pdevinfo->dev, devinfo->chans[chid].supp_stream_formats, devinfo->chans[chid].supp_pcm_size_rate); for (i = 0; i < devinfo->as[pdevinfo->playas].num_chans; i++) { chid = devinfo->as[pdevinfo->playas].chans[i]; device_printf(pdevinfo->dev, " DAC:"); for (nids = devinfo->chans[chid].io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL || w->enable == 0) continue; device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, as->pins[i], 0); } device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_adc(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; nid_t *nids; int chid, i; if (pdevinfo->recas < 0) return; device_printf(pdevinfo->dev, "Record:\n"); chid = devinfo->as[pdevinfo->recas].chans[0]; hdaa_dump_audio_formats(pdevinfo->dev, devinfo->chans[chid].supp_stream_formats, devinfo->chans[chid].supp_pcm_size_rate); for (i = 0; i < devinfo->as[pdevinfo->recas].num_chans; i++) { chid = devinfo->as[pdevinfo->recas].chans[i]; device_printf(pdevinfo->dev, " ADC:"); for (nids = devinfo->chans[chid].io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->bindas != pdevinfo->recas) continue; device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, i, 0); } device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_mix(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; int i; int printed = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev != SOUND_MIXER_IMIX) continue; if (w->bindas != pdevinfo->recas) continue; if (printed == 0) { printed = 1; device_printf(pdevinfo->dev, "Input Mix:\n"); } device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, i, 0); } if (printed) device_printf(pdevinfo->dev, "\n"); } static void hdaa_pindump(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; uint32_t res, pincap, delay; int i; device_printf(dev, "Dumping AFG pins:\n"); device_printf(dev, "nid 0x as seq " "device conn jack loc color misc\n"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_dump_pin_config(w, w->wclass.pin.config); pincap = w->wclass.pin.cap; device_printf(dev, " Caps: %2s %3s %2s %4s %4s", HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)?"IN":"", HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)?"OUT":"", HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)?"HP":"", HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)?"EAPD":"", HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)?"VREF":""); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap) || HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) { if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) { delay = 0; hda_command(dev, HDA_CMD_SET_PIN_SENSE(0, w->nid, 0)); do { res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); if (res != 0x7fffffff && res != 0xffffffff) break; DELAY(10); } while (++delay < 10000); } else { delay = 0; res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); } printf(" Sense: 0x%08x (%sconnected%s)", res, (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis", (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap) && (res & HDA_CMD_GET_PIN_SENSE_ELD_VALID)) ? ", ELD valid" : ""); if (delay > 0) printf(" delay %dus", delay * 10); } printf("\n"); } device_printf(dev, "NumGPIO=%d NumGPO=%d NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap)); hdaa_dump_gpi(devinfo); hdaa_dump_gpio(devinfo); hdaa_dump_gpo(devinfo); } static void hdaa_configure(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_audio_ctl *ctl; int i; HDA_BOOTHVERBOSE( device_printf(dev, "Applying built-in patches...\n"); ); hdaa_patch(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying local patches...\n"); ); hdaa_local_patch(devinfo); hdaa_audio_postprocess(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing Ctls...\n"); ); hdaa_audio_ctl_parse(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling nonaudio...\n"); ); hdaa_audio_disable_nonaudio(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTVERBOSE( device_printf(dev, "Patched pins configuration:\n"); hdaa_dump_pin_configs(devinfo); ); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing pin associations...\n"); ); hdaa_audio_as_parse(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Building AFG tree...\n"); ); hdaa_audio_build_tree(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling unassociated " "widgets...\n"); ); hdaa_audio_disable_unas(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling nonselected " "inputs...\n"); ); hdaa_audio_disable_notselected(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling " "crossassociatement connections...\n"); ); hdaa_audio_disable_crossas(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Binding associations to channels...\n"); ); hdaa_audio_bind_as(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Assigning names to signal sources...\n"); ); hdaa_audio_assign_names(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Preparing PCM devices...\n"); ); hdaa_prepare_pcms(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Assigning mixers to the tree...\n"); ); hdaa_audio_assign_mixers(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Preparing pin controls...\n"); ); hdaa_audio_prepare_pin_ctrl(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "AFG commit...\n"); ); hdaa_audio_commit(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying direct built-in patches...\n"); ); hdaa_patch_direct(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense init...\n"); ); hdaa_sense_init(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Creating PCM devices...\n"); ); hdaa_create_pcms(devinfo); HDA_BOOTVERBOSE( if (devinfo->quirks != 0) { device_printf(dev, "FG config/quirks:"); for (i = 0; i < nitems(hdaa_quirks_tab); i++) { if ((devinfo->quirks & hdaa_quirks_tab[i].value) == hdaa_quirks_tab[i].value) printf(" %s", hdaa_quirks_tab[i].key); } printf("\n"); } ); HDA_BOOTHVERBOSE( device_printf(dev, "\n"); device_printf(dev, "+-----------+\n"); device_printf(dev, "| HDA NODES |\n"); device_printf(dev, "+-----------+\n"); hdaa_dump_nodes(devinfo); device_printf(dev, "\n"); device_printf(dev, "+----------------+\n"); device_printf(dev, "| HDA AMPLIFIERS |\n"); device_printf(dev, "+----------------+\n"); device_printf(dev, "\n"); i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { device_printf(dev, "%3d: nid %3d %s (%s) index %d", i, (ctl->widget != NULL) ? ctl->widget->nid : -1, (ctl->ndir == HDAA_CTL_IN)?"in ":"out", (ctl->dir == HDAA_CTL_IN)?"in ":"out", ctl->index); if (ctl->childwidget != NULL) printf(" cnid %3d", ctl->childwidget->nid); else printf(" "); printf(" ossmask=0x%08x\n", ctl->ossmask); device_printf(dev, " mute: %d step: %3d size: %3d off: %3d%s\n", ctl->mute, ctl->step, ctl->size, ctl->offset, (ctl->enable == 0) ? " [DISABLED]" : ((ctl->ossmask == 0) ? " [UNUSED]" : "")); } device_printf(dev, "\n"); ); } static void hdaa_unconfigure(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; int i, j; HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense deinit...\n"); ); hdaa_sense_deinit(devinfo); free(devinfo->ctl, M_HDAA); devinfo->ctl = NULL; devinfo->ctlcnt = 0; free(devinfo->as, M_HDAA); devinfo->as = NULL; devinfo->ascnt = 0; free(devinfo->devs, M_HDAA); devinfo->devs = NULL; devinfo->num_devs = 0; free(devinfo->chans, M_HDAA); devinfo->chans = NULL; devinfo->num_chans = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; w->enable = 1; w->selconn = -1; w->pflags = 0; w->bindas = -1; w->bindseqmask = 0; w->ossdev = -1; w->ossmask = 0; for (j = 0; j < w->nconns; j++) w->connsenable[j] = 1; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) w->wclass.pin.config = w->wclass.pin.newconf; if (w->eld != NULL) { w->eld_len = 0; free(w->eld, M_HDAA); w->eld = NULL; } } } static int hdaa_sysctl_gpi_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpi; uint32_t data = 0; buf[0] = 0; hdaa_lock(devinfo); numgpi = HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); if (numgpi > 0) { data = hda_command(dev, HDA_CMD_GET_GPI_DATA(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpi; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d", n != 0 ? " " : "", i, ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpio_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpio; uint32_t data = 0, enable = 0, dir = 0; buf[0] = 0; hdaa_lock(devinfo); numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); if (numgpio > 0) { data = hda_command(dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); enable = hda_command(dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); dir = hda_command(dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpio; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=", n != 0 ? " " : "", i); if ((enable & (1 << i)) == 0) { n += snprintf(buf + n, sizeof(buf) - n, "disabled"); continue; } n += snprintf(buf + n, sizeof(buf) - n, "%sput(%d)", ((dir >> i) & 1) ? "out" : "in", ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpio_config(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; char buf[256]; int error, n = 0, i, numgpio; uint32_t gpio, x; gpio = devinfo->newgpio; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); buf[0] = 0; for (i = 0; i < numgpio; i++) { x = (gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s", n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) gpio = strtol(buf + 2, NULL, 16); else gpio = hdaa_gpio_patch(gpio, buf); hdaa_lock(devinfo); devinfo->newgpio = devinfo->gpio = gpio; hdaa_gpio_commit(devinfo); hdaa_unlock(devinfo); return (0); } static int hdaa_sysctl_gpo_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpo; uint32_t data = 0; buf[0] = 0; hdaa_lock(devinfo); numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); if (numgpo > 0) { data = hda_command(dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpo; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d", n != 0 ? " " : "", i, ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpo_config(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; char buf[256]; int error, n = 0, i, numgpo; uint32_t gpo, x; gpo = devinfo->newgpo; numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); buf[0] = 0; for (i = 0; i < numgpo; i++) { x = (gpo & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s", n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) gpo = strtol(buf + 2, NULL, 16); else gpo = hdaa_gpio_patch(gpo, buf); hdaa_lock(devinfo); devinfo->newgpo = devinfo->gpo = gpo; hdaa_gpo_commit(devinfo); hdaa_unlock(devinfo); return (0); } static int hdaa_sysctl_reconfig(SYSCTL_HANDLER_ARGS) { device_t dev; struct hdaa_devinfo *devinfo; int error, val; dev = oidp->oid_arg1; devinfo = device_get_softc(dev); if (devinfo == NULL) return (EINVAL); val = 0; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL || val == 0) return (error); HDA_BOOTHVERBOSE( device_printf(dev, "Reconfiguration...\n"); ); bus_topo_lock(); if ((error = device_delete_children(dev)) != 0) { bus_topo_unlock(); return (error); } hdaa_lock(devinfo); hdaa_unconfigure(dev); hdaa_configure(dev); hdaa_unlock(devinfo); bus_generic_attach(dev); HDA_BOOTHVERBOSE( device_printf(dev, "Reconfiguration done\n"); ); bus_topo_unlock(); return (0); } static int hdaa_suspend(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int i; HDA_BOOTHVERBOSE( device_printf(dev, "Suspend...\n"); ); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Stop streams...\n"); ); for (i = 0; i < devinfo->num_chans; i++) { if (devinfo->chans[i].flags & HDAA_CHN_RUNNING) { devinfo->chans[i].flags |= HDAA_CHN_SUSPEND; hdaa_channel_stop(&devinfo->chans[i]); } } HDA_BOOTHVERBOSE( device_printf(dev, "Power down FG" " nid=%d to the D3 state...\n", devinfo->nid); ); hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, devinfo->nid, HDA_CMD_POWER_STATE_D3)); callout_stop(&devinfo->poll_jack); hdaa_unlock(devinfo); callout_drain(&devinfo->poll_jack); HDA_BOOTHVERBOSE( device_printf(dev, "Suspend done\n"); ); return (0); } static int hdaa_resume(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int i; HDA_BOOTHVERBOSE( device_printf(dev, "Resume...\n"); ); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Power up audio FG nid=%d...\n", devinfo->nid); ); hdaa_powerup(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "AFG commit...\n"); ); hdaa_audio_commit(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying direct built-in patches...\n"); ); hdaa_patch_direct(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense init...\n"); ); hdaa_sense_init(devinfo); hdaa_unlock(devinfo); for (i = 0; i < devinfo->num_devs; i++) { struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i]; HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "OSS mixer reinitialization...\n"); ); if (mixer_reinit(pdevinfo->dev) == -1) device_printf(pdevinfo->dev, "unable to reinitialize the mixer\n"); } hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Start streams...\n"); ); for (i = 0; i < devinfo->num_chans; i++) { if (devinfo->chans[i].flags & HDAA_CHN_SUSPEND) { devinfo->chans[i].flags &= ~HDAA_CHN_SUSPEND; hdaa_channel_start(&devinfo->chans[i]); } } hdaa_unlock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Resume done\n"); ); return (0); } static int hdaa_probe(device_t dev) { const char *pdesc; char buf[128]; if (hda_get_node_type(dev) != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) return (ENXIO); pdesc = device_get_desc(device_get_parent(dev)); snprintf(buf, sizeof(buf), "%.*s Audio Function Group", (int)(strlen(pdesc) - 10), pdesc); device_set_desc_copy(dev, buf); return (BUS_PROBE_DEFAULT); } static int hdaa_attach(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); uint32_t res; nid_t nid = hda_get_node_id(dev); devinfo->dev = dev; devinfo->lock = HDAC_GET_MTX(device_get_parent(dev), dev); devinfo->nid = nid; devinfo->newquirks = -1; devinfo->newgpio = -1; devinfo->newgpo = -1; callout_init(&devinfo->poll_jack, 1); devinfo->poll_ival = hz; hdaa_lock(devinfo); res = hda_command(dev, HDA_CMD_GET_PARAMETER(0 , nid, HDA_PARAM_SUB_NODE_COUNT)); hdaa_unlock(devinfo); devinfo->nodecnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(res); devinfo->startnode = HDA_PARAM_SUB_NODE_COUNT_START(res); devinfo->endnode = devinfo->startnode + devinfo->nodecnt; HDA_BOOTVERBOSE( device_printf(dev, "Subsystem ID: 0x%08x\n", hda_get_subsystem_id(dev)); ); HDA_BOOTHVERBOSE( device_printf(dev, "Audio Function Group at nid=%d: %d subnodes %d-%d\n", nid, devinfo->nodecnt, devinfo->startnode, devinfo->endnode - 1); ); if (devinfo->nodecnt > 0) devinfo->widget = (struct hdaa_widget *)malloc( sizeof(*(devinfo->widget)) * devinfo->nodecnt, M_HDAA, M_WAITOK | M_ZERO); else devinfo->widget = NULL; hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Powering up...\n"); ); hdaa_powerup(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing audio FG...\n"); ); hdaa_audio_parse(devinfo); HDA_BOOTVERBOSE( device_printf(dev, "Original pins configuration:\n"); hdaa_dump_pin_configs(devinfo); ); hdaa_configure(dev); hdaa_unlock(devinfo); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, &devinfo->newquirks, 0, hdaa_sysctl_quirks, "A", "Configuration options"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpi_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpi_state, "A", "GPI state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpio_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpio_state, "A", "GPIO state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpio_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpio_config, "A", "GPIO configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpo_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpo_state, "A", "GPO state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpo_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpo_config, "A", "GPO configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "reconfig", CTLTYPE_INT | CTLFLAG_RW, dev, 0, hdaa_sysctl_reconfig, "I", "Reprocess configuration"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "init_clear", CTLFLAG_RW, &devinfo->init_clear, 1,"Clear initial pin widget configuration"); bus_generic_attach(dev); return (0); } static int hdaa_detach(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int error; if ((error = device_delete_children(dev)) != 0) return (error); hdaa_lock(devinfo); hdaa_unconfigure(dev); devinfo->poll_ival = 0; callout_stop(&devinfo->poll_jack); hdaa_unlock(devinfo); callout_drain(&devinfo->poll_jack); free(devinfo->widget, M_HDAA); return (0); } static int hdaa_print_child(device_t dev, device_t child) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(child); struct hdaa_audio_as *as; int retval, first = 1, i; retval = bus_print_child_header(dev, child); retval += printf(" at nid "); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; retval += printf("%s%d", first ? "" : ",", as->pins[i]); first = 0; } } if (pdevinfo->recas >= 0) { if (pdevinfo->playas >= 0) { retval += printf(" and "); first = 1; } as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; retval += printf("%s%d", first ? "" : ",", as->pins[i]); first = 0; } } retval += bus_print_child_footer(dev, child); return (retval); } static int hdaa_child_location(device_t dev, device_t child, struct sbuf *sb) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(child); struct hdaa_audio_as *as; int first = 1, i; sbuf_printf(sb, "nid="); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; sbuf_printf(sb, "%s%d", first ? "" : ",", as->pins[i]); first = 0; } } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; sbuf_printf(sb, "%s%d", first ? "" : ",", as->pins[i]); first = 0; } } return (0); } static void hdaa_stream_intr(device_t dev, int dir, int stream) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_chan *ch; int i; for (i = 0; i < devinfo->num_chans; i++) { ch = &devinfo->chans[i]; if (!(ch->flags & HDAA_CHN_RUNNING)) continue; if (ch->dir == ((dir == 1) ? PCMDIR_PLAY : PCMDIR_REC) && ch->sid == stream) { hdaa_unlock(devinfo); chn_intr(ch->c); hdaa_lock(devinfo); } } } static void hdaa_unsol_intr(device_t dev, uint32_t resp) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; int i, tag, flags; HDA_BOOTHVERBOSE( device_printf(dev, "Unsolicited response %08x\n", resp); ); tag = resp >> 26; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->unsol != tag) continue; if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) || HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) flags = resp & 0x03; else flags = 0x01; if (flags & 0x01) hdaa_presence_handler(w); if (flags & 0x02) hdaa_eld_handler(w); } } static device_method_t hdaa_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdaa_probe), DEVMETHOD(device_attach, hdaa_attach), DEVMETHOD(device_detach, hdaa_detach), DEVMETHOD(device_suspend, hdaa_suspend), DEVMETHOD(device_resume, hdaa_resume), /* Bus interface */ DEVMETHOD(bus_print_child, hdaa_print_child), DEVMETHOD(bus_child_location, hdaa_child_location), DEVMETHOD(hdac_stream_intr, hdaa_stream_intr), DEVMETHOD(hdac_unsol_intr, hdaa_unsol_intr), DEVMETHOD(hdac_pindump, hdaa_pindump), DEVMETHOD_END }; static driver_t hdaa_driver = { "hdaa", hdaa_methods, sizeof(struct hdaa_devinfo), }; DRIVER_MODULE(snd_hda, hdacc, hdaa_driver, NULL, NULL); static void hdaa_chan_formula(struct hdaa_devinfo *devinfo, int asid, char *buf, int buflen) { struct hdaa_audio_as *as; int c; as = &devinfo->as[asid]; c = devinfo->chans[as->chans[0]].channels; if (c == 1) snprintf(buf, buflen, "mono"); else if (c == 2) { if (as->hpredir < 0) buf[0] = 0; else snprintf(buf, buflen, "2.0"); } else if (as->pinset == 0x0003) snprintf(buf, buflen, "3.1"); else if (as->pinset == 0x0005 || as->pinset == 0x0011) snprintf(buf, buflen, "4.0"); else if (as->pinset == 0x0007 || as->pinset == 0x0013) snprintf(buf, buflen, "5.1"); else if (as->pinset == 0x0017) snprintf(buf, buflen, "7.1"); else snprintf(buf, buflen, "%dch", c); if (as->hpredir >= 0) strlcat(buf, "+HP", buflen); } static int hdaa_chan_type(struct hdaa_devinfo *devinfo, int asid) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, t = -1, t1; as = &devinfo->as[asid]; for (i = 0; i < 16; i++) { w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; t1 = HDA_CONFIG_DEFAULTCONF_DEVICE(w->wclass.pin.config); if (t == -1) t = t1; else if (t != t1) { t = -2; break; } } return (t); } static int hdaa_sysctl_32bit(SYSCTL_HANDLER_ARGS) { struct hdaa_audio_as *as = (struct hdaa_audio_as *)oidp->oid_arg1; struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_chan *ch; int error, val, i; uint32_t pcmcap; ch = &devinfo->chans[as->chans[0]]; val = (ch->bit32 == 4) ? 32 : ((ch->bit32 == 3) ? 24 : ((ch->bit32 == 2) ? 20 : 0)); error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); pcmcap = ch->supp_pcm_size_rate; if (val == 32 && HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap)) ch->bit32 = 4; else if (val == 24 && HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap)) ch->bit32 = 3; else if (val == 20 && HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap)) ch->bit32 = 2; else return (EINVAL); for (i = 1; i < as->num_chans; i++) devinfo->chans[as->chans[i]].bit32 = ch->bit32; return (0); } static int hdaa_pcm_probe(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; const char *pdesc; char chans1[8], chans2[8]; char buf[128]; int loc1, loc2, t1, t2; if (pdevinfo->playas >= 0) loc1 = devinfo->as[pdevinfo->playas].location; else loc1 = devinfo->as[pdevinfo->recas].location; if (pdevinfo->recas >= 0) loc2 = devinfo->as[pdevinfo->recas].location; else loc2 = loc1; if (loc1 != loc2) loc1 = -2; if (loc1 >= 0 && HDA_LOCS[loc1][0] == '0') loc1 = -2; chans1[0] = 0; chans2[0] = 0; t1 = t2 = -1; if (pdevinfo->playas >= 0) { hdaa_chan_formula(devinfo, pdevinfo->playas, chans1, sizeof(chans1)); t1 = hdaa_chan_type(devinfo, pdevinfo->playas); } if (pdevinfo->recas >= 0) { hdaa_chan_formula(devinfo, pdevinfo->recas, chans2, sizeof(chans2)); t2 = hdaa_chan_type(devinfo, pdevinfo->recas); } if (chans1[0] != 0 || chans2[0] != 0) { if (chans1[0] == 0 && pdevinfo->playas >= 0) snprintf(chans1, sizeof(chans1), "2.0"); else if (chans2[0] == 0 && pdevinfo->recas >= 0) snprintf(chans2, sizeof(chans2), "2.0"); if (strcmp(chans1, chans2) == 0) chans2[0] = 0; } if (t1 == -1) t1 = t2; else if (t2 == -1) t2 = t1; if (t1 != t2) t1 = -2; if (pdevinfo->digital) t1 = -2; pdesc = device_get_desc(device_get_parent(dev)); snprintf(buf, sizeof(buf), "%.*s (%s%s%s%s%s%s%s%s%s)", (int)(strlen(pdesc) - 21), pdesc, loc1 >= 0 ? HDA_LOCS[loc1] : "", loc1 >= 0 ? " " : "", (pdevinfo->digital == 0x7)?"HDMI/DP": ((pdevinfo->digital == 0x5)?"DisplayPort": ((pdevinfo->digital == 0x3)?"HDMI": ((pdevinfo->digital)?"Digital":"Analog"))), chans1[0] ? " " : "", chans1, chans2[0] ? "/" : "", chans2, t1 >= 0 ? " " : "", t1 >= 0 ? HDA_DEVS[t1] : ""); device_set_desc_copy(dev, buf); return (BUS_PROBE_SPECIFIC); } static int hdaa_pcm_attach(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as; struct snddev_info *d; char status[SND_STATUSLEN]; int i; pdevinfo->chan_size = pcm_getbuffersize(dev, HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX); HDA_BOOTVERBOSE( hdaa_dump_dac(pdevinfo); hdaa_dump_adc(pdevinfo); hdaa_dump_mix(pdevinfo); hdaa_dump_ctls(pdevinfo, "Master Volume", SOUND_MASK_VOLUME); hdaa_dump_ctls(pdevinfo, "PCM Volume", SOUND_MASK_PCM); hdaa_dump_ctls(pdevinfo, "CD Volume", SOUND_MASK_CD); hdaa_dump_ctls(pdevinfo, "Microphone Volume", SOUND_MASK_MIC); hdaa_dump_ctls(pdevinfo, "Microphone2 Volume", SOUND_MASK_MONITOR); hdaa_dump_ctls(pdevinfo, "Line-in Volume", SOUND_MASK_LINE); hdaa_dump_ctls(pdevinfo, "Speaker/Beep Volume", SOUND_MASK_SPEAKER); hdaa_dump_ctls(pdevinfo, "Recording Level", SOUND_MASK_RECLEV); hdaa_dump_ctls(pdevinfo, "Input Mix Level", SOUND_MASK_IMIX); hdaa_dump_ctls(pdevinfo, "Input Monitoring Level", SOUND_MASK_IGAIN); hdaa_dump_ctls(pdevinfo, NULL, 0); ); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= HDA_BLK_ALIGN; if (i < HDA_BLK_MIN) i = HDA_BLK_MIN; pdevinfo->chan_blkcnt = pdevinfo->chan_size / i; i = 0; while (pdevinfo->chan_blkcnt >> i) i++; pdevinfo->chan_blkcnt = 1 << (i - 1); if (pdevinfo->chan_blkcnt < HDA_BDL_MIN) pdevinfo->chan_blkcnt = HDA_BDL_MIN; else if (pdevinfo->chan_blkcnt > HDA_BDL_MAX) pdevinfo->chan_blkcnt = HDA_BDL_MAX; } else pdevinfo->chan_blkcnt = HDA_BDL_DEFAULT; /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); HDA_BOOTHVERBOSE( device_printf(dev, "OSS mixer initialization...\n"); ); if (mixer_init(dev, &hdaa_audio_ctl_ossmixer_class, pdevinfo) != 0) device_printf(dev, "Can't register mixer\n"); HDA_BOOTHVERBOSE( device_printf(dev, "Registering PCM channels...\n"); ); if (pcm_register(dev, pdevinfo, (pdevinfo->playas >= 0)?1:0, (pdevinfo->recas >= 0)?1:0) != 0) device_printf(dev, "Can't register PCM\n"); pdevinfo->registered++; d = device_get_softc(dev); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < as->num_chans; i++) pcm_addchan(dev, PCMDIR_PLAY, &hdaa_channel_class, &devinfo->chans[as->chans[i]]); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, as, sizeof(as), hdaa_sysctl_32bit, "I", "Resolution of 32bit samples (20/24/32bit)"); } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < as->num_chans; i++) pcm_addchan(dev, PCMDIR_REC, &hdaa_channel_class, &devinfo->chans[as->chans[i]]); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, as, sizeof(as), hdaa_sysctl_32bit, "I", "Resolution of 32bit samples (20/24/32bit)"); pdevinfo->autorecsrc = 2; resource_int_value(device_get_name(dev), device_get_unit(dev), "rec.autosrc", &pdevinfo->autorecsrc); SYSCTL_ADD_INT(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "autosrc", CTLFLAG_RW, &pdevinfo->autorecsrc, 0, "Automatic recording source selection"); } if (pdevinfo->mixer != NULL) { hdaa_audio_ctl_set_defaults(pdevinfo); hdaa_lock(devinfo); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; hdaa_channels_handler(as); } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; hdaa_autorecsrc_handler(as, NULL); hdaa_channels_handler(as); } hdaa_unlock(devinfo); } - snprintf(status, SND_STATUSLEN, "on %s %s", - device_get_nameunit(device_get_parent(dev)), - PCM_KLDSTRING(snd_hda)); + snprintf(status, SND_STATUSLEN, "on %s", + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return (0); } static int hdaa_pcm_detach(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); int err; if (pdevinfo->registered > 0) { err = pcm_unregister(dev); if (err != 0) return (err); } return (0); } static device_method_t hdaa_pcm_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdaa_pcm_probe), DEVMETHOD(device_attach, hdaa_pcm_attach), DEVMETHOD(device_detach, hdaa_pcm_detach), DEVMETHOD_END }; static driver_t hdaa_pcm_driver = { "pcm", hdaa_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hda_pcm, hdaa, hdaa_pcm_driver, NULL, NULL); MODULE_DEPEND(snd_hda, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hda, 1); diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c index d9d40c9877ad..0360e84fbe91 100644 --- a/sys/dev/sound/pci/hdspe-pcm.c +++ b/sys/dev/sound/pci/hdspe-pcm.c @@ -1,1086 +1,1086 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2021 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * RME HDSPe driver for FreeBSD (pcm-part). * Supported cards: AIO, RayDAT. */ #include #include #include #include #include #include struct hdspe_latency { uint32_t n; uint32_t period; float ms; }; static struct hdspe_latency latency_map[] = { { 7, 32, 0.7 }, { 0, 64, 1.5 }, { 1, 128, 3 }, { 2, 256, 6 }, { 3, 512, 12 }, { 4, 1024, 23 }, { 5, 2048, 46 }, { 6, 4096, 93 }, { 0, 0, 0 }, }; struct hdspe_rate { uint32_t speed; uint32_t reg; }; static struct hdspe_rate rate_map[] = { { 32000, (HDSPE_FREQ_32000) }, { 44100, (HDSPE_FREQ_44100) }, { 48000, (HDSPE_FREQ_48000) }, { 64000, (HDSPE_FREQ_32000 | HDSPE_FREQ_DOUBLE) }, { 88200, (HDSPE_FREQ_44100 | HDSPE_FREQ_DOUBLE) }, { 96000, (HDSPE_FREQ_48000 | HDSPE_FREQ_DOUBLE) }, { 128000, (HDSPE_FREQ_32000 | HDSPE_FREQ_QUAD) }, { 176400, (HDSPE_FREQ_44100 | HDSPE_FREQ_QUAD) }, { 192000, (HDSPE_FREQ_48000 | HDSPE_FREQ_QUAD) }, { 0, 0 }, }; static uint32_t hdspe_channel_play_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL | HDSPE_CHAN_RAY_ALL)); } static uint32_t hdspe_channel_rec_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL_REC | HDSPE_CHAN_RAY_ALL)); } static unsigned int hdspe_adat_width(uint32_t speed) { if (speed > 96000) return (2); if (speed > 48000) return (4); return (8); } static uint32_t hdspe_port_first(uint32_t ports) { return (ports & (~(ports - 1))); /* Extract first bit set. */ } static uint32_t hdspe_port_first_row(uint32_t ports) { uint32_t ends; /* Restrict ports to one set with contiguous slots. */ if (ports & HDSPE_CHAN_AIO_LINE) ports = HDSPE_CHAN_AIO_LINE; /* Gap in the AIO slots here. */ else if (ports & HDSPE_CHAN_AIO_ALL) ports &= HDSPE_CHAN_AIO_ALL; /* Rest of the AIO slots. */ else if (ports & HDSPE_CHAN_RAY_ALL) ports &= HDSPE_CHAN_RAY_ALL; /* All RayDAT slots. */ /* Ends of port rows are followed by a port which is not in the set. */ ends = ports & (~(ports >> 1)); /* First row of contiguous ports ends in the first row end. */ return (ports & (ends ^ (ends - 1))); } static unsigned int hdspe_channel_count(uint32_t ports, uint32_t adat_width) { unsigned int count = 0; if (ports & HDSPE_CHAN_AIO_ALL) { /* AIO ports. */ if (ports & HDSPE_CHAN_AIO_LINE) count += 2; if (ports & HDSPE_CHAN_AIO_PHONE) count += 2; if (ports & HDSPE_CHAN_AIO_AES) count += 2; if (ports & HDSPE_CHAN_AIO_SPDIF) count += 2; if (ports & HDSPE_CHAN_AIO_ADAT) count += adat_width; } else if (ports & HDSPE_CHAN_RAY_ALL) { /* RayDAT ports. */ if (ports & HDSPE_CHAN_RAY_AES) count += 2; if (ports & HDSPE_CHAN_RAY_SPDIF) count += 2; if (ports & HDSPE_CHAN_RAY_ADAT1) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT2) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT3) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT4) count += adat_width; } return (count); } static unsigned int hdspe_channel_offset(uint32_t subset, uint32_t ports, unsigned int adat_width) { uint32_t preceding; /* Make sure we have a subset of ports. */ subset &= ports; /* Include all ports preceding the first one of the subset. */ preceding = ports & (~subset & (subset - 1)); if (preceding & HDSPE_CHAN_AIO_ALL) preceding &= HDSPE_CHAN_AIO_ALL; /* Contiguous AIO slots. */ else if (preceding & HDSPE_CHAN_RAY_ALL) preceding &= HDSPE_CHAN_RAY_ALL; /* Contiguous RayDAT slots. */ return (hdspe_channel_count(preceding, adat_width)); } static unsigned int hdspe_port_slot_offset(uint32_t port, unsigned int adat_width) { /* Exctract the first port (lowest bit) if set of ports. */ switch (hdspe_port_first(port)) { /* AIO ports */ case HDSPE_CHAN_AIO_LINE: return (0); case HDSPE_CHAN_AIO_PHONE: return (6); case HDSPE_CHAN_AIO_AES: return (8); case HDSPE_CHAN_AIO_SPDIF: return (10); case HDSPE_CHAN_AIO_ADAT: return (12); /* RayDAT ports */ case HDSPE_CHAN_RAY_AES: return (0); case HDSPE_CHAN_RAY_SPDIF: return (2); case HDSPE_CHAN_RAY_ADAT1: return (4); case HDSPE_CHAN_RAY_ADAT2: return (4 + adat_width); case HDSPE_CHAN_RAY_ADAT3: return (4 + 2 * adat_width); case HDSPE_CHAN_RAY_ADAT4: return (4 + 3 * adat_width); default: return (0); } } static unsigned int hdspe_port_slot_width(uint32_t ports, unsigned int adat_width) { uint32_t row; /* Count number of contiguous slots from the first physical port. */ row = hdspe_port_first_row(ports); return (hdspe_channel_count(row, adat_width)); } static int hdspe_hw_mixer(struct sc_chinfo *ch, unsigned int dst, unsigned int src, unsigned short data) { struct sc_pcminfo *scp; struct sc_info *sc; int offs; scp = ch->parent; sc = scp->sc; offs = 0; if (ch->dir == PCMDIR_PLAY) offs = 64; hdspe_write_4(sc, HDSPE_MIXER_BASE + ((offs + src + 128 * dst) * sizeof(uint32_t)), data & 0xFFFF); return (0); }; static int hdspechan_setgain(struct sc_chinfo *ch) { struct sc_info *sc; uint32_t port, ports; unsigned int slot, end_slot; unsigned short volume; sc = ch->parent->sc; /* Iterate through all physical ports of the channel. */ ports = ch->ports; port = hdspe_port_first(ports); while (port != 0) { /* Get slot range of the physical port. */ slot = hdspe_port_slot_offset(port, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(port, hdspe_adat_width(sc->speed)); /* Treat first slot as left channel. */ volume = ch->lvol * HDSPE_MAX_GAIN / 100; for (; slot < end_slot; slot++) { hdspe_hw_mixer(ch, slot, slot, volume); /* Subsequent slots all get the right channel volume. */ volume = ch->rvol * HDSPE_MAX_GAIN / 100; } ports &= ~port; port = hdspe_port_first(ports); } return (0); } static int hdspemixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return (-1); mask = SOUND_MASK_PCM; if (hdspe_channel_play_ports(scp->hc)) mask |= SOUND_MASK_VOLUME; if (hdspe_channel_rec_ports(scp->hc)) mask |= SOUND_MASK_RECLEV; snd_mtxlock(sc->lock); pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, mask); snd_mtxunlock(sc->lock); return (0); } static int hdspemixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_pcminfo *scp; struct sc_chinfo *ch; int i; scp = mix_getdevinfo(m); #if 0 device_printf(scp->dev, "hdspemixer_set() %d %d\n", left, right); #endif for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; if ((dev == SOUND_MIXER_VOLUME && ch->dir == PCMDIR_PLAY) || (dev == SOUND_MIXER_RECLEV && ch->dir == PCMDIR_REC)) { ch->lvol = left; ch->rvol = right; if (ch->run) hdspechan_setgain(ch); } } return (0); } static kobj_method_t hdspemixer_methods[] = { KOBJMETHOD(mixer_init, hdspemixer_init), KOBJMETHOD(mixer_set, hdspemixer_set), KOBJMETHOD_END }; MIXER_DECLARE(hdspemixer); static void hdspechan_enable(struct sc_chinfo *ch, int value) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; int reg; unsigned int slot, end_slot; scp = ch->parent; sc = scp->sc; if (ch->dir == PCMDIR_PLAY) reg = HDSPE_OUT_ENABLE_BASE; else reg = HDSPE_IN_ENABLE_BASE; ch->run = value; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { slot = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); for (; slot < end_slot; slot++) { hdspe_write_1(sc, reg + (4 * slot), value); } ports &= ~row; row = hdspe_port_first_row(ports); } } static int hdspe_running(struct sc_info *sc) { struct sc_pcminfo *scp; struct sc_chinfo *ch; device_t *devlist; int devcount; int i, j; int err; if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) goto bad; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); for (j = 0; j < scp->chnum; j++) { ch = &scp->chan[j]; if (ch->run) goto bad; } } free(devlist, M_TEMP); return (0); bad: #if 0 device_printf(sc->dev, "hdspe is running\n"); #endif free(devlist, M_TEMP); return (1); } static void hdspe_start_audio(struct sc_info *sc) { sc->ctrl_register |= (HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); } static void hdspe_stop_audio(struct sc_info *sc) { if (hdspe_running(sc) == 1) return; sc->ctrl_register &= ~(HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); } static void buffer_mux_write(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { dma[slot * HDSPE_CHANBUF_SAMPLES + pos] = pcm[pos * channels + slot]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_mux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate DMA slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_mux_write(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_mux_write(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_mux_write(dma, pcm, pos, samples, 8, channels); else buffer_mux_write(dma, pcm, pos, samples, slots, channels); } static void buffer_demux_read(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { pcm[pos * channels + slot] = dma[slot * HDSPE_CHANBUF_SAMPLES + pos]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_demux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate port slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_demux_read(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_demux_read(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_demux_read(dma, pcm, pos, samples, 8, channels); else buffer_demux_read(dma, pcm, pos, samples, slots, channels); } /* Copy data between DMA and PCM buffers. */ static void buffer_copy(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; unsigned int pos; unsigned int n; unsigned int adat_width, pcm_width; scp = ch->parent; sc = scp->sc; n = AFMT_CHANNEL(ch->format); /* n channels */ /* Let pcm formats differ from current hardware ADAT width. */ adat_width = hdspe_adat_width(sc->speed); if (n == hdspe_channel_count(ch->ports, 2)) pcm_width = 2; else if (n == hdspe_channel_count(ch->ports, 4)) pcm_width = 4; else pcm_width = 8; if (ch->dir == PCMDIR_PLAY) pos = sndbuf_getreadyptr(ch->buffer); else pos = sndbuf_getfreeptr(ch->buffer); pos /= 4; /* Bytes per sample. */ pos /= n; /* Destination buffer n-times smaller. */ /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); while (row != 0) { if (ch->dir == PCMDIR_PLAY) buffer_mux_port(sc->pbuf, ch->data, row, ch->ports, pos, sc->period * 2, adat_width, pcm_width); else buffer_demux_port(sc->rbuf, ch->data, row, ch->ports, pos, sc->period * 2, adat_width, pcm_width); ports &= ~row; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); } } static int clean(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t *buf; uint32_t row, ports; unsigned int offset, slots; scp = ch->parent; sc = scp->sc; buf = sc->rbuf; if (ch->dir == PCMDIR_PLAY) buf = sc->pbuf; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { offset = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); slots = hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); bzero(buf + offset * HDSPE_CHANBUF_SAMPLES, slots * HDSPE_CHANBUF_SIZE); ports &= ~row; row = hdspe_port_first_row(ports); } return (0); } /* Channel interface. */ static void * hdspechan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int num; scp = devinfo; sc = scp->sc; snd_mtxlock(sc->lock); num = scp->chnum; ch = &scp->chan[num]; if (dir == PCMDIR_PLAY) ch->ports = hdspe_channel_play_ports(scp->hc); else ch->ports = hdspe_channel_rec_ports(scp->hc); ch->run = 0; ch->lvol = 0; ch->rvol = 0; /* Support all possible ADAT widths as channel formats. */ ch->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 2), 0); ch->cap_fmts[1] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 4), 0); ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 8), 0); ch->cap_fmts[3] = 0; ch->caps = malloc(sizeof(struct pcmchan_caps), M_HDSPE, M_NOWAIT); *(ch->caps) = (struct pcmchan_caps) {32000, 192000, ch->cap_fmts, 0}; /* Allocate maximum buffer size. */ ch->size = HDSPE_CHANBUF_SIZE * hdspe_channel_count(ch->ports, 8); ch->data = malloc(ch->size, M_HDSPE, M_NOWAIT); ch->buffer = b; ch->channel = c; ch->parent = scp; ch->dir = dir; snd_mtxunlock(sc->lock); if (sndbuf_setup(ch->buffer, ch->data, ch->size) != 0) { device_printf(scp->dev, "Can't setup sndbuf.\n"); return (NULL); } return (ch); } static int hdspechan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "hdspechan_trigger(): start\n"); #endif hdspechan_enable(ch, 1); hdspechan_setgain(ch); hdspe_start_audio(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "hdspechan_trigger(): stop or abort\n"); #endif clean(ch); hdspechan_enable(ch, 0); hdspe_stop_audio(sc); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: if(ch->run) buffer_copy(ch); break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t hdspechan_getptr(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; uint32_t ret, pos; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); ret = hdspe_read_2(sc, HDSPE_STATUS_REG); snd_mtxunlock(sc->lock); pos = ret & HDSPE_BUF_POSITION_MASK; pos *= AFMT_CHANNEL(ch->format); /* Hardbuf with multiple channels. */ return (pos); } static int hdspechan_free(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; #if 0 device_printf(scp->dev, "hdspechan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_HDSPE); ch->data = NULL; } if (ch->caps != NULL) { free(ch->caps, M_HDSPE); ch->caps = NULL; } snd_mtxunlock(sc->lock); return (0); } static int hdspechan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scp = ch->parent; device_printf(scp->dev, "hdspechan_setformat(%d)\n", format); #endif ch->format = format; return (0); } static uint32_t hdspechan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct hdspe_rate *hr; struct sc_chinfo *ch; struct sc_info *sc; long long period; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hr = NULL; #if 0 device_printf(scp->dev, "hdspechan_setspeed(%d)\n", speed); #endif if (hdspe_running(sc) == 1) goto end; /* First look for equal frequency. */ for (i = 0; rate_map[i].speed != 0; i++) { if (rate_map[i].speed == speed) hr = &rate_map[i]; } /* If no match, just find nearest. */ if (hr == NULL) { for (i = 0; rate_map[i].speed != 0; i++) { hr = &rate_map[i]; threshold = hr->speed + ((rate_map[i + 1].speed != 0) ? ((rate_map[i + 1].speed - hr->speed) >> 1) : 0); if (speed < threshold) break; } } switch (sc->type) { case HDSPE_RAYDAT: case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: /* Unsupported card. */ goto end; } /* Write frequency on the device. */ sc->ctrl_register &= ~HDSPE_FREQ_MASK; sc->ctrl_register |= hr->reg; hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); speed = hr->speed; if (speed > 96000) speed /= 4; else if (speed > 48000) speed /= 2; /* Set DDS value. */ period /= speed; hdspe_write_4(sc, HDSPE_FREQ_REG, period); sc->speed = hr->speed; end: return (sc->speed); } static uint32_t hdspechan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct hdspe_latency *hl; struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hl = NULL; #if 0 device_printf(scp->dev, "hdspechan_setblocksize(%d)\n", blocksize); #endif if (hdspe_running(sc) == 1) goto end; if (blocksize > HDSPE_LAT_BYTES_MAX) blocksize = HDSPE_LAT_BYTES_MAX; else if (blocksize < HDSPE_LAT_BYTES_MIN) blocksize = HDSPE_LAT_BYTES_MIN; blocksize /= 4 /* samples */; /* First look for equal latency. */ for (i = 0; latency_map[i].period != 0; i++) { if (latency_map[i].period == blocksize) hl = &latency_map[i]; } /* If no match, just find nearest. */ if (hl == NULL) { for (i = 0; latency_map[i].period != 0; i++) { hl = &latency_map[i]; threshold = hl->period + ((latency_map[i + 1].period != 0) ? ((latency_map[i + 1].period - hl->period) >> 1) : 0); if (blocksize < threshold) break; } } snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSPE_LAT_MASK; sc->ctrl_register |= hdspe_encode_latency(hl->n); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); sc->period = hl->period; snd_mtxunlock(sc->lock); #if 0 device_printf(scp->dev, "New period=%d\n", sc->period); #endif sndbuf_resize(ch->buffer, (HDSPE_CHANBUF_SIZE * AFMT_CHANNEL(ch->format)) / (sc->period * 4), (sc->period * 4)); end: return (sndbuf_getblksz(ch->buffer)); } static uint32_t hdspe_bkp_fmt[] = { SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps hdspe_bkp_caps = {32000, 192000, hdspe_bkp_fmt, 0}; static struct pcmchan_caps * hdspechan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scl = ch->parent; device_printf(scp->dev, "hdspechan_getcaps()\n"); #endif if (ch->caps != NULL) return (ch->caps); return (&hdspe_bkp_caps); } static kobj_method_t hdspechan_methods[] = { KOBJMETHOD(channel_init, hdspechan_init), KOBJMETHOD(channel_free, hdspechan_free), KOBJMETHOD(channel_setformat, hdspechan_setformat), KOBJMETHOD(channel_setspeed, hdspechan_setspeed), KOBJMETHOD(channel_setblocksize, hdspechan_setblocksize), KOBJMETHOD(channel_trigger, hdspechan_trigger), KOBJMETHOD(channel_getptr, hdspechan_getptr), KOBJMETHOD(channel_getcaps, hdspechan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdspechan); static int hdspe_pcm_probe(device_t dev) { #if 0 device_printf(dev,"hdspe_pcm_probe()\n"); #endif return (0); } static uint32_t hdspe_pcm_intr(struct sc_pcminfo *scp) { struct sc_chinfo *ch; struct sc_info *sc; int i; sc = scp->sc; for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } return (0); } static int hdspe_pcm_attach(device_t dev) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; char desc[64]; int err; int play, rec; scp = device_get_ivars(dev); scp->ih = &hdspe_pcm_intr; bzero(desc, sizeof(desc)); if (scp->hc->ports & HDSPE_CHAN_AIO_ALL) snprintf(desc, sizeof(desc), "HDSPe AIO [%s]", scp->hc->descr); else if (scp->hc->ports & HDSPE_CHAN_RAY_ALL) snprintf(desc, sizeof(desc), "HDSPe RayDAT [%s]", scp->hc->descr); else snprintf(desc, sizeof(desc), "HDSPe ? [%s]", scp->hc->descr); device_set_desc_copy(dev, desc); /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); play = (hdspe_channel_play_ports(scp->hc)) ? 1 : 0; rec = (hdspe_channel_rec_ports(scp->hc)) ? 1 : 0; err = pcm_register(dev, scp, play, rec); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } scp->chnum = 0; if (play) { pcm_addchan(dev, PCMDIR_PLAY, &hdspechan_class, scp); scp->chnum++; } if (rec) { pcm_addchan(dev, PCMDIR_REC, &hdspechan_class, scp); scp->chnum++; } - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(scp->sc->cs), rman_get_start(scp->sc->irq), - PCM_KLDSTRING(snd_hdspe)); + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); mixer_init(dev, &hdspemixer_class, scp); return (0); } static int hdspe_pcm_detach(device_t dev) { int err; err = pcm_unregister(dev); if (err) { device_printf(dev, "Can't unregister device.\n"); return (err); } return (0); } static device_method_t hdspe_pcm_methods[] = { DEVMETHOD(device_probe, hdspe_pcm_probe), DEVMETHOD(device_attach, hdspe_pcm_attach), DEVMETHOD(device_detach, hdspe_pcm_detach), { 0, 0 } }; static driver_t hdspe_pcm_driver = { "pcm", hdspe_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdspe_pcm, hdspe, hdspe_pcm_driver, 0, 0); MODULE_DEPEND(snd_hdspe, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hdspe, 1); diff --git a/sys/dev/sound/pci/ich.c b/sys/dev/sound/pci/ich.c index 70a9440d9b23..fbde0accfd28 100644 --- a/sys/dev/sound/pci/ich.c +++ b/sys/dev/sound/pci/ich.c @@ -1,1240 +1,1241 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Katsurajima Naoto * Copyright (c) 2001 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include /* -------------------------------------------------------------------- */ #define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ #define ICH_DTBL_LENGTH 32 #define ICH_DEFAULT_BUFSZ 16384 #define ICH_MAX_BUFSZ 65536 #define ICH_MIN_BUFSZ 4096 #define ICH_DEFAULT_BLKCNT 2 #define ICH_MAX_BLKCNT 32 #define ICH_MIN_BLKCNT 2 #define ICH_MIN_BLKSZ 64 #define INTEL_VENDORID 0x8086 #define SIS_VENDORID 0x1039 #define NVIDIA_VENDORID 0x10de #define AMD_VENDORID 0x1022 #define INTEL_82440MX 0x7195 #define INTEL_82801AA 0x2415 #define INTEL_82801AB 0x2425 #define INTEL_82801BA 0x2445 #define INTEL_82801CA 0x2485 #define INTEL_82801DB 0x24c5 /* ICH4 needs special handling */ #define INTEL_82801EB 0x24d5 /* ICH5 needs to be treated as ICH4 */ #define INTEL_6300ESB 0x25a6 /* 6300ESB needs to be treated as ICH4 */ #define INTEL_82801FB 0x266e /* ICH6 needs to be treated as ICH4 */ #define INTEL_82801GB 0x27de /* ICH7 needs to be treated as ICH4 */ #define SIS_7012 0x7012 /* SiS 7012 needs special handling */ #define NVIDIA_NFORCE 0x01b1 #define NVIDIA_NFORCE2 0x006a #define NVIDIA_NFORCE2_400 0x008a #define NVIDIA_NFORCE3 0x00da #define NVIDIA_NFORCE3_250 0x00ea #define NVIDIA_NFORCE4 0x0059 #define NVIDIA_NFORCE_410_MCP 0x026b #define NVIDIA_NFORCE4_MCP 0x003a #define AMD_768 0x7445 #define AMD_8111 0x746d #define ICH_LOCK(sc) snd_mtxlock((sc)->ich_lock) #define ICH_UNLOCK(sc) snd_mtxunlock((sc)->ich_lock) #define ICH_LOCK_ASSERT(sc) snd_mtxassert((sc)->ich_lock) #if 0 #define ICH_DEBUG(stmt) do { \ stmt \ } while (0) #else #define ICH_DEBUG(...) #endif #define ICH_CALIBRATE_DONE (1 << 0) #define ICH_IGNORE_PCR (1 << 1) #define ICH_IGNORE_RESET (1 << 2) #define ICH_FIXED_RATE (1 << 3) #define ICH_DMA_NOCACHE (1 << 4) #define ICH_HIGH_LATENCY (1 << 5) static const struct ich_type { uint16_t vendor; uint16_t devid; uint32_t options; #define PROBE_LOW 0x01 char *name; } ich_devs[] = { { INTEL_VENDORID, INTEL_82440MX, 0, "Intel 440MX" }, { INTEL_VENDORID, INTEL_82801AA, 0, "Intel ICH (82801AA)" }, { INTEL_VENDORID, INTEL_82801AB, 0, "Intel ICH (82801AB)" }, { INTEL_VENDORID, INTEL_82801BA, 0, "Intel ICH2 (82801BA)" }, { INTEL_VENDORID, INTEL_82801CA, 0, "Intel ICH3 (82801CA)" }, { INTEL_VENDORID, INTEL_82801DB, PROBE_LOW, "Intel ICH4 (82801DB)" }, { INTEL_VENDORID, INTEL_82801EB, PROBE_LOW, "Intel ICH5 (82801EB)" }, { INTEL_VENDORID, INTEL_6300ESB, PROBE_LOW, "Intel 6300ESB" }, { INTEL_VENDORID, INTEL_82801FB, PROBE_LOW, "Intel ICH6 (82801FB)" }, { INTEL_VENDORID, INTEL_82801GB, PROBE_LOW, "Intel ICH7 (82801GB)" }, { SIS_VENDORID, SIS_7012, 0, "SiS 7012" }, { NVIDIA_VENDORID, NVIDIA_NFORCE, 0, "nVidia nForce" }, { NVIDIA_VENDORID, NVIDIA_NFORCE2, 0, "nVidia nForce2" }, { NVIDIA_VENDORID, NVIDIA_NFORCE2_400, 0, "nVidia nForce2 400" }, { NVIDIA_VENDORID, NVIDIA_NFORCE3, 0, "nVidia nForce3" }, { NVIDIA_VENDORID, NVIDIA_NFORCE3_250, 0, "nVidia nForce3 250" }, { NVIDIA_VENDORID, NVIDIA_NFORCE4, 0, "nVidia nForce4" }, { NVIDIA_VENDORID, NVIDIA_NFORCE_410_MCP, 0, "nVidia nForce 410 MCP" }, { NVIDIA_VENDORID, NVIDIA_NFORCE4_MCP, 0, "nVidia nForce 4 MCP" }, { AMD_VENDORID, AMD_768, 0, "AMD-768" }, { AMD_VENDORID, AMD_8111, 0, "AMD-8111" } }; /* buffer descriptor */ struct ich_desc { volatile uint32_t buffer; volatile uint32_t length; }; struct sc_info; /* channel registers */ struct sc_chinfo { uint32_t num:8, run:1, run_save:1; uint32_t blksz, blkcnt, spd; uint32_t regbase, spdreg; uint32_t imask; uint32_t civ; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; struct ich_desc *dtbl; bus_addr_t desc_addr; }; /* device private data */ struct sc_info { device_t dev; int hasvra, hasvrm, hasmic; unsigned int chnum, bufsz, blkcnt; int sample_size, swap_reg; struct resource *nambar, *nabmbar, *irq; int regtype, nambarid, nabmbarid, irqid; bus_space_tag_t nambart, nabmbart; bus_space_handle_t nambarh, nabmbarh; bus_dma_tag_t dmat, chan_dmat; bus_dmamap_t dtmap; void *ih; struct ac97_info *codec; struct sc_chinfo ch[3]; int ac97rate; struct ich_desc *dtbl; unsigned int dtbl_size; bus_addr_t desc_addr; struct intr_config_hook intrhook; uint16_t vendor; uint16_t devid; uint32_t flags; struct mtx *ich_lock; }; /* -------------------------------------------------------------------- */ static uint32_t ich_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps ich_vrcaps = {8000, 48000, ich_fmt, 0}; static struct pcmchan_caps ich_caps = {48000, 48000, ich_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static __inline uint32_t ich_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno)); case 2: return (bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno)); case 4: return (bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno)); default: return (0xffffffff); } } static __inline void ich_wr(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->nabmbart, sc->nabmbarh, regno, data); break; case 2: bus_space_write_2(sc->nabmbart, sc->nabmbarh, regno, data); break; case 4: bus_space_write_4(sc->nabmbart, sc->nabmbarh, regno, data); break; } } /* ac97 codec */ static int ich_waitcd(void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; uint32_t data; int i; for (i = 0; i < ICH_TIMEOUT; i++) { data = ich_rd(sc, ICH_REG_ACC_SEMA, 1); if ((data & 0x01) == 0) return (0); DELAY(1); } if ((sc->flags & ICH_IGNORE_PCR) != 0) return (0); device_printf(sc->dev, "CODEC semaphore timeout\n"); return (ETIMEDOUT); } static int ich_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; ich_waitcd(sc); return (bus_space_read_2(sc->nambart, sc->nambarh, regno)); } static int ich_wrcd(kobj_t obj, void *devinfo, int regno, uint32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; ich_waitcd(sc); bus_space_write_2(sc->nambart, sc->nambarh, regno, data); return (0); } static kobj_method_t ich_ac97_methods[] = { KOBJMETHOD(ac97_read, ich_rdcd), KOBJMETHOD(ac97_write, ich_wrcd), KOBJMETHOD_END }; AC97_DECLARE(ich_ac97); /* -------------------------------------------------------------------- */ /* common routines */ static void ich_filldtbl(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; uint32_t base; int i; base = sndbuf_getbufaddr(ch->buffer); if ((ch->blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) ch->blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; if ((sndbuf_getblksz(ch->buffer) != ch->blksz || sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, ch->blksz, ch->blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); for (i = 0; i < ICH_DTBL_LENGTH; i++) { ch->dtbl[i].buffer = base + (ch->blksz * (i % ch->blkcnt)); ch->dtbl[i].length = ICH_BDC_IOC | (ch->blksz / ch->parent->sample_size); } } static int ich_resetchan(struct sc_info *sc, int num) { int i, cr, regbase; if (num == 0) regbase = ICH_REG_PO_BASE; else if (num == 1) regbase = ICH_REG_PI_BASE; else if (num == 2) regbase = ICH_REG_MC_BASE; else return (ENXIO); ich_wr(sc, regbase + ICH_REG_X_CR, 0, 1); #if 1 /* This may result in no sound output on NForce 2 MBs, see PR 73987 */ DELAY(100); #else (void)ich_rd(sc, regbase + ICH_REG_X_CR, 1); #endif ich_wr(sc, regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); for (i = 0; i < ICH_TIMEOUT; i++) { cr = ich_rd(sc, regbase + ICH_REG_X_CR, 1); if (cr == 0) return (0); DELAY(1); } if (sc->flags & ICH_IGNORE_RESET) return (0); #if 0 else if (sc->vendor == NVIDIA_VENDORID) { sc->flags |= ICH_IGNORE_RESET; device_printf(sc->dev, "ignoring reset failure!\n"); return (0); } #endif device_printf(sc->dev, "cannot reset channel %d\n", num); return (ENXIO); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * ichchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; unsigned int num; ICH_LOCK(sc); num = sc->chnum++; ch = &sc->ch[num]; ch->num = num; ch->buffer = b; ch->channel = c; ch->parent = sc; ch->run = 0; ch->dtbl = sc->dtbl + (ch->num * ICH_DTBL_LENGTH); ch->desc_addr = sc->desc_addr + (ch->num * ICH_DTBL_LENGTH * sizeof(struct ich_desc)); ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; switch(ch->num) { case 0: /* play */ KASSERT(dir == PCMDIR_PLAY, ("wrong direction")); ch->regbase = ICH_REG_PO_BASE; ch->spdreg = (sc->hasvra) ? AC97_REGEXT_FDACRATE : 0; ch->imask = ICH_GLOB_STA_POINT; break; case 1: /* record */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_PI_BASE; ch->spdreg = (sc->hasvra) ? AC97_REGEXT_LADCRATE : 0; ch->imask = ICH_GLOB_STA_PIINT; break; case 2: /* mic */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_MC_BASE; ch->spdreg = (sc->hasvrm) ? AC97_REGEXT_MADCRATE : 0; ch->imask = ICH_GLOB_STA_MINT; break; default: return (NULL); } if (sc->flags & ICH_FIXED_RATE) ch->spdreg = 0; ICH_UNLOCK(sc); if (sndbuf_alloc(ch->buffer, sc->chan_dmat, ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), sc->bufsz) != 0) return (NULL); ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ICH_UNLOCK(sc); return (ch); } static int ichchan_setformat(kobj_t obj, void *data, uint32_t format) { ICH_DEBUG( struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); return (0); } static uint32_t ichchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); if (ch->spdreg) { int r, ac97rate; ICH_LOCK(sc); if (sc->ac97rate <= 32000 || sc->ac97rate >= 64000) sc->ac97rate = 48000; ac97rate = sc->ac97rate; ICH_UNLOCK(sc); r = (speed * 48000) / ac97rate; /* * Cast the return value of ac97_setrate() to uint64 so that * the math don't overflow into the negative range. */ ch->spd = ((uint64_t)ac97_setrate(sc->codec, ch->spdreg, r) * ac97rate) / 48000; } else { ch->spd = 48000; } return (ch->spd); } static uint32_t ichchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); if (sc->flags & ICH_HIGH_LATENCY) blocksize = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; if (blocksize < ICH_MIN_BLKSZ) blocksize = ICH_MIN_BLKSZ; blocksize &= ~(ICH_MIN_BLKSZ - 1); ch->blksz = blocksize; ich_filldtbl(ch); ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_LVI, ch->blkcnt - 1, 1); ICH_UNLOCK(sc); return (ch->blksz); } static int ichchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); switch (go) { case PCMTRIG_START: ch->run = 1; ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE, 1); ICH_UNLOCK(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ICH_LOCK(sc); ich_resetchan(sc, ch->num); ICH_UNLOCK(sc); ch->run = 0; break; default: break; } return (0); } static uint32_t ichchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; uint32_t pos; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); ICH_LOCK(sc); ch->civ = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1) % ch->blkcnt; ICH_UNLOCK(sc); pos = ch->civ * ch->blksz; return (pos); } static struct pcmchan_caps * ichchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; ICH_DEBUG( struct sc_info *sc = ch->parent; if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(ch->parent->dev, "WARNING: %s() called before calibration!\n", __func__); ); return ((ch->spdreg) ? &ich_vrcaps : &ich_caps); } static kobj_method_t ichchan_methods[] = { KOBJMETHOD(channel_init, ichchan_init), KOBJMETHOD(channel_setformat, ichchan_setformat), KOBJMETHOD(channel_setspeed, ichchan_setspeed), KOBJMETHOD(channel_setblocksize, ichchan_setblocksize), KOBJMETHOD(channel_trigger, ichchan_trigger), KOBJMETHOD(channel_getptr, ichchan_getptr), KOBJMETHOD(channel_getcaps, ichchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(ichchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void ich_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; uint32_t cbi, lbi, lvi, st, gs; int i; ICH_LOCK(sc); ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); gs = ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_IMASK; if (gs & (ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES)) { /* Clear resume interrupt(s) - nothing doing with them */ ich_wr(sc, ICH_REG_GLOB_STA, gs, 4); } gs &= ~(ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES); for (i = 0; i < 3; i++) { ch = &sc->ch[i]; if ((ch->imask & gs) == 0) continue; gs &= ~ch->imask; st = ich_rd(sc, ch->regbase + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), 2); st &= ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI; if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) { /* block complete - update buffer */ if (ch->run) { ICH_UNLOCK(sc); chn_intr(ch->channel); ICH_LOCK(sc); } lvi = ich_rd(sc, ch->regbase + ICH_REG_X_LVI, 1); cbi = ch->civ % ch->blkcnt; if (cbi == 0) cbi = ch->blkcnt - 1; else cbi--; lbi = lvi % ch->blkcnt; if (cbi >= lbi) lvi += cbi - lbi; else lvi += cbi + ch->blkcnt - lbi; lvi %= ICH_DTBL_LENGTH; ich_wr(sc, ch->regbase + ICH_REG_X_LVI, lvi, 1); } /* clear status bit */ ich_wr(sc, ch->regbase + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), st, 2); } ICH_UNLOCK(sc); if (gs != 0) { device_printf(sc->dev, "Unhandled interrupt, gs_intr = %x\n", gs); } } /* ------------------------------------------------------------------------- */ /* Sysctl to control ac97 speed (some boards appear to end up using * XTAL_IN rather than BIT_CLK for link timing). */ static int ich_initsys(struct sc_info* sc) { /* XXX: this should move to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "ac97rate", CTLFLAG_RW, &sc->ac97rate, 48000, "AC97 link rate (default = 48000)"); return (0); } static void ich_setstatus(struct sc_info *sc) { char status[SND_STATUSLEN]; snprintf(status, SND_STATUSLEN, - "at io 0x%jx, 0x%jx irq %jd bufsz %u %s", + "port 0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), - rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich)); + rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(sc->dev))); if (bootverbose && (sc->flags & ICH_DMA_NOCACHE)) device_printf(sc->dev, "PCI Master abort workaround enabled\n"); pcm_setstatus(sc->dev, status); } /* -------------------------------------------------------------------- */ /* Calibrate card to determine the clock source. The source maybe a * function of the ac97 codec initialization code (to be investigated). */ static void ich_calibrate(void *arg) { struct sc_info *sc; struct sc_chinfo *ch; struct timeval t1, t2; uint8_t ociv, nciv; uint32_t wait_us, actual_48k_rate, oblkcnt; sc = (struct sc_info *)arg; ICH_LOCK(sc); ch = &sc->ch[1]; if (sc->intrhook.ich_func != NULL) { config_intrhook_disestablish(&sc->intrhook); sc->intrhook.ich_func = NULL; } /* * Grab audio from input for fixed interval and compare how * much we actually get with what we expect. Interval needs * to be sufficiently short that no interrupts are * generated. */ KASSERT(ch->regbase == ICH_REG_PI_BASE, ("wrong direction")); oblkcnt = ch->blkcnt; ch->blkcnt = 2; sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ichchan_setblocksize(0, ch, sndbuf_getmaxsize(ch->buffer) >> 1); ICH_LOCK(sc); sc->flags &= ~ICH_CALIBRATE_DONE; /* * our data format is stereo, 16 bit so each sample is 4 bytes. * assuming we get 48000 samples per second, we get 192000 bytes/sec. * we're going to start recording with interrupts disabled and measure * the time taken for one block to complete. we know the block size, * we know the time in microseconds, we calculate the sample rate: * * actual_rate [bps] = bytes / (time [s] * 4) * actual_rate [bps] = (bytes * 1000000) / (time [us] * 4) * actual_rate [Hz] = (bytes * 250000) / time [us] */ /* prepare */ ociv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); nciv = ociv; ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); /* start */ microtime(&t1); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM, 1); /* wait */ do { microtime(&t2); if (t2.tv_sec - t1.tv_sec > 1) break; nciv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); } while (nciv == ociv); /* stop */ ich_wr(sc, ch->regbase + ICH_REG_X_CR, 0, 1); /* reset */ DELAY(100); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); ch->blkcnt = oblkcnt; /* turn time delta into us */ wait_us = ((t2.tv_sec - t1.tv_sec) * 1000000) + t2.tv_usec - t1.tv_usec; if (nciv == ociv) { device_printf(sc->dev, "ac97 link rate calibration timed out after %d us\n", wait_us); sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ich_setstatus(sc); return; } /* Just in case the timecounter screwed. It is possible, really. */ if (wait_us > 0) actual_48k_rate = ((uint64_t)ch->blksz * 250000) / wait_us; else actual_48k_rate = 48000; if (actual_48k_rate < 47500 || actual_48k_rate > 48500) { sc->ac97rate = actual_48k_rate; } else { sc->ac97rate = 48000; } if (bootverbose || sc->ac97rate != 48000) { device_printf(sc->dev, "measured ac97 link rate at %d Hz", actual_48k_rate); if (sc->ac97rate != actual_48k_rate) printf(", will use %d Hz", sc->ac97rate); printf("\n"); } sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ich_setstatus(sc); return; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static void ich_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->desc_addr = segs->ds_addr; return; } static int ich_init(struct sc_info *sc) { uint32_t stat; ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); DELAY(600000); stat = ich_rd(sc, ICH_REG_GLOB_STA, 4); if ((stat & ICH_GLOB_STA_PCR) == 0) { /* ICH4/ICH5 may fail when busmastering is enabled. Continue */ if (sc->vendor == INTEL_VENDORID && ( sc->devid == INTEL_82801DB || sc->devid == INTEL_82801EB || sc->devid == INTEL_6300ESB || sc->devid == INTEL_82801FB || sc->devid == INTEL_82801GB)) { sc->flags |= ICH_IGNORE_PCR; device_printf(sc->dev, "primary codec not ready!\n"); } } #if 0 ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD | ICH_GLOB_CTL_PRES, 4); #else ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); #endif if (ich_resetchan(sc, 0) || ich_resetchan(sc, 1)) return (ENXIO); if (sc->hasmic && ich_resetchan(sc, 2)) return (ENXIO); return (0); } static int ich_pci_probe(device_t dev) { int i; uint16_t devid, vendor; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < sizeof(ich_devs)/sizeof(ich_devs[0]); i++) { if (vendor == ich_devs[i].vendor && devid == ich_devs[i].devid) { device_set_desc(dev, ich_devs[i].name); /* allow a better driver to override us */ if ((ich_devs[i].options & PROBE_LOW) != 0) return (BUS_PROBE_LOW_PRIORITY); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int ich_pci_attach(device_t dev) { uint32_t subdev; uint16_t extcaps; uint16_t devid, vendor; struct sc_info *sc; int i; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->ich_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ich softc"); sc->dev = dev; vendor = sc->vendor = pci_get_vendor(dev); devid = sc->devid = pci_get_device(dev); subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); /* * The SiS 7012 register set isn't quite like the standard ich. * There really should be a general "quirks" mechanism. */ if (vendor == SIS_VENDORID && devid == SIS_7012) { sc->swap_reg = 1; sc->sample_size = 1; } else { sc->swap_reg = 0; sc->sample_size = 2; } /* * Intel 440MX Errata #36 * - AC97 Soft Audio and Soft Modem Master Abort Errata * * http://www.intel.com/design/chipsets/specupdt/245051.htm */ if (vendor == INTEL_VENDORID && devid == INTEL_82440MX) sc->flags |= ICH_DMA_NOCACHE; /* * Enable bus master. On ich4/5 this may prevent the detection of * the primary codec becoming ready in ich_init(). */ pci_enable_busmaster(dev); /* * By default, ich4 has NAMBAR and NABMBAR i/o spaces as * read-only. Need to enable "legacy support", by poking into * pci config space. The driver should use MMBAR and MBBAR, * but doing so will mess things up here. ich4 has enough new * features it warrants it's own driver. */ if (vendor == INTEL_VENDORID && (devid == INTEL_82801DB || devid == INTEL_82801EB || devid == INTEL_6300ESB || devid == INTEL_82801FB || devid == INTEL_82801GB)) { sc->nambarid = PCIR_MMBAR; sc->nabmbarid = PCIR_MBBAR; sc->regtype = SYS_RES_MEMORY; pci_write_config(dev, PCIR_ICH_LEGACY, ICH_LEGACY_ENABLE, 1); } else { sc->nambarid = PCIR_NAMBAR; sc->nabmbarid = PCIR_NABMBAR; sc->regtype = SYS_RES_IOPORT; } sc->nambar = bus_alloc_resource_any(dev, sc->regtype, &sc->nambarid, RF_ACTIVE); sc->nabmbar = bus_alloc_resource_any(dev, sc->regtype, &sc->nabmbarid, RF_ACTIVE); if (!sc->nambar || !sc->nabmbar) { device_printf(dev, "unable to map IO port space\n"); goto bad; } sc->nambart = rman_get_bustag(sc->nambar); sc->nambarh = rman_get_bushandle(sc->nambar); sc->nabmbart = rman_get_bustag(sc->nabmbar); sc->nabmbarh = rman_get_bushandle(sc->nabmbar); sc->bufsz = pcm_getbuffersize(dev, ICH_MIN_BUFSZ, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { sc->blkcnt = sc->bufsz / i; i = 0; while (sc->blkcnt >> i) i++; sc->blkcnt = 1 << (i - 1); if (sc->blkcnt < ICH_MIN_BLKCNT) sc->blkcnt = ICH_MIN_BLKCNT; else if (sc->blkcnt > ICH_MAX_BLKCNT) sc->blkcnt = ICH_MAX_BLKCNT; } else sc->blkcnt = ICH_DEFAULT_BLKCNT; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "highlatency", &i) == 0 && i != 0) { sc->flags |= ICH_HIGH_LATENCY; sc->blkcnt = ICH_MIN_BLKCNT; } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "fixedrate", &i) == 0 && i != 0) sc->flags |= ICH_FIXED_RATE; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "micchannel_enabled", &i) == 0 && i != 0) sc->hasmic = 1; sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ich_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (ich_init(sc)) { device_printf(dev, "unable to initialize the card\n"); goto bad; } sc->codec = AC97_CREATE(dev, sc, ich_ac97); if (sc->codec == NULL) goto bad; /* * Turn on inverted external amplifier sense flags for few * 'special' boards. */ switch (subdev) { case 0x202f161f: /* Gateway 7326GZ */ case 0x203a161f: /* Gateway 4028GZ */ case 0x203e161f: /* Gateway 3520GZ/M210 */ case 0x204c161f: /* Kvazar-Micro Senator 3592XT */ case 0x8144104d: /* Sony VAIO PCG-TR* */ case 0x8197104d: /* Sony S1XP */ case 0x81c0104d: /* Sony VAIO type T */ case 0x81c5104d: /* Sony VAIO VGN B1VP/B1XP */ case 0x3089103c: /* Compaq Presario B3800 */ case 0x309a103c: /* HP Compaq nx4300 */ case 0x82131033: /* NEC VersaPro VJ10F/BH */ case 0x82be1033: /* NEC VersaPro VJ12F/CH */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(dev, ac97_getmixerclass(), sc->codec); /* check and set VRA function */ extcaps = ac97_getextcaps(sc->codec); sc->hasvra = extcaps & AC97_EXTCAP_VRA; sc->hasvrm = extcaps & AC97_EXTCAP_VRM; sc->hasmic = (sc->hasmic != 0 && (ac97_getcaps(sc->codec) & AC97_CAP_MICCHANNEL)) ? 1 : 0; ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); sc->dtbl_size = sizeof(struct ich_desc) * ICH_DTBL_LENGTH * ((sc->hasmic) ? 3 : 2); /* BDL tag */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->dtbl_size, 1, 0x3ffff, 0, NULL, NULL, &sc->dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* PCM channel tag */ if (bus_dma_tag_create(bus_get_dma_tag(dev), ICH_MIN_BLKSZ, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->bufsz, 1, 0x3ffff, 0, NULL, NULL, &sc->chan_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT | ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), &sc->dtmap)) goto bad; if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sc->dtbl_size, ich_setmap, sc, 0)) goto bad; if (pcm_register(dev, sc, 1, (sc->hasmic) ? 2 : 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &ichchan_class, sc); /* play */ pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record */ if (sc->hasmic) pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record mic */ if (sc->flags & ICH_FIXED_RATE) { sc->flags |= ICH_CALIBRATE_DONE; ich_setstatus(sc); } else { ich_initsys(sc); sc->intrhook.ich_func = ich_calibrate; sc->intrhook.ich_arg = sc; if (cold == 0 || config_intrhook_establish(&sc->intrhook) != 0) { sc->intrhook.ich_func = NULL; ich_calibrate(sc); } } return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->nambar) bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); if (sc->nabmbar) bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); if (sc->dtmap) bus_dmamap_unload(sc->dmat, sc->dtmap); if (sc->dtbl) bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); if (sc->chan_dmat) bus_dma_tag_destroy(sc->chan_dmat); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->ich_lock) snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return (ENXIO); } static int ich_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return (r); sc = pcm_getdevinfo(dev); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); bus_dmamap_unload(sc->dmat, sc->dtmap); bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); bus_dma_tag_destroy(sc->chan_dmat); bus_dma_tag_destroy(sc->dmat); snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return (0); } static void ich_pci_codec_reset(struct sc_info *sc) { int i; uint32_t control; control = ich_rd(sc, ICH_REG_GLOB_CNT, 4); control &= ~(ICH_GLOB_CTL_SHUT); control |= (control & ICH_GLOB_CTL_COLD) ? ICH_GLOB_CTL_WARM : ICH_GLOB_CTL_COLD; ich_wr(sc, ICH_REG_GLOB_CNT, control, 4); for (i = 500000; i; i--) { if (ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_PCR) break; /* or ICH_SCR? */ DELAY(1); } if (i <= 0) printf("%s: time out\n", __func__); } static int ich_pci_suspend(device_t dev) { struct sc_info *sc; int i; sc = pcm_getdevinfo(dev); ICH_LOCK(sc); for (i = 0 ; i < 3; i++) { sc->ch[i].run_save = sc->ch[i].run; if (sc->ch[i].run) { ICH_UNLOCK(sc); ichchan_trigger(0, &sc->ch[i], PCMTRIG_ABORT); ICH_LOCK(sc); } } ICH_UNLOCK(sc); return (0); } static int ich_pci_resume(device_t dev) { struct sc_info *sc; int err, i; sc = pcm_getdevinfo(dev); ICH_LOCK(sc); /* Reinit audio device */ err = ich_init(sc); if (err != 0) { device_printf(dev, "unable to reinitialize the card\n"); ICH_UNLOCK(sc); return (err); } /* Reinit mixer */ ich_pci_codec_reset(sc); ICH_UNLOCK(sc); ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } /* Re-start DMA engines */ for (i = 0 ; i < 3; i++) { struct sc_chinfo *ch = &sc->ch[i]; if (sc->ch[i].run_save) { ichchan_setblocksize(0, ch, ch->blksz); ichchan_setspeed(0, ch, ch->spd); ichchan_trigger(0, ch, PCMTRIG_START); } } return (0); } static device_method_t ich_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ich_pci_probe), DEVMETHOD(device_attach, ich_pci_attach), DEVMETHOD(device_detach, ich_pci_detach), DEVMETHOD(device_suspend, ich_pci_suspend), DEVMETHOD(device_resume, ich_pci_resume), { 0, 0 } }; static driver_t ich_driver = { "pcm", ich_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_ich, pci, ich_driver, 0, 0); MODULE_DEPEND(snd_ich, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_ich, 1); diff --git a/sys/dev/sound/pci/maestro3.c b/sys/dev/sound/pci/maestro3.c index 9ba83fe4de11..6dd54a66f683 100644 --- a/sys/dev/sound/pci/maestro3.c +++ b/sys/dev/sound/pci/maestro3.c @@ -1,1797 +1,1797 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Scott Long * Copyright (c) 2001 Darrell Anderson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Maestro-3/Allegro FreeBSD pcm sound driver * * executive status summary: * (+) /dev/dsp multiple concurrent play channels. * (+) /dev/dsp config (speed, mono/stereo, 8/16 bit). * (+) /dev/mixer sets left/right volumes. * (+) /dev/dsp recording works. Tested successfully with the cdrom channel * (+) apm suspend/resume works, and works properly!. * (-) hardware volme controls don't work =-( * (-) setblocksize() does nothing. * * The real credit goes to: * * Zach Brown for his Linux driver core and helpful technical comments. * , http://www.zabbo.net/maestro3 * * Cameron Grant created the pcm framework used here nearly verbatim. * , https://people.freebsd.org/~cg/template.c * * Taku YAMAMOTO for his Maestro-1/2 FreeBSD driver and sanity reference. * * * ESS docs explained a few magic registers and numbers. * http://virgo.caltech.edu/~dmoore/maestro3.pdf.gz */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #define M3_MODEL 1 #include #include /* -------------------------------------------------------------------- */ enum {CHANGE=0, CALL=1, INTR=2, BORING=3, NONE=-1}; #ifndef M3_DEBUG_LEVEL #define M3_DEBUG_LEVEL NONE #endif #define M3_DEBUG(level, _msg) {if ((level) <= M3_DEBUG_LEVEL) {printf _msg;}} /* -------------------------------------------------------------------- */ enum { ESS_ALLEGRO_1, ESS_MAESTRO3 }; static struct m3_card_type { u_int32_t pci_id; int which; int delay1; int delay2; char *name; } m3_card_types[] = { { 0x1988125d, ESS_ALLEGRO_1, 50, 800, "ESS Technology Allegro-1" }, { 0x1998125d, ESS_MAESTRO3, 20, 500, "ESS Technology Maestro3" }, { 0x199a125d, ESS_MAESTRO3, 20, 500, "ESS Technology Maestro3" }, { 0, 0, 0, 0, NULL } }; #define M3_BUFSIZE_MIN 4096 #define M3_BUFSIZE_MAX 65536 #define M3_BUFSIZE_DEFAULT 4096 #define M3_PCHANS 4 /* create /dev/dsp0.[0-N] to use more than one */ #define M3_RCHANS 1 #define M3_MAXADDR ((1 << 27) - 1) #define M3_DEFAULT_VOL 0x6800 struct sc_info; struct sc_pchinfo { u_int32_t spd; u_int32_t fmt; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; u_int32_t bufsize; u_int32_t dac_data; u_int32_t dac_idx; u_int32_t active; u_int32_t ptr; u_int32_t prevptr; }; struct sc_rchinfo { u_int32_t spd; u_int32_t fmt; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; u_int32_t bufsize; u_int32_t adc_data; u_int32_t adc_idx; u_int32_t active; u_int32_t ptr; u_int32_t prevptr; }; struct sc_info { device_t dev; u_int32_t type; int which; int delay1; int delay2; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg; struct resource *irq; int regtype; int regid; int irqid; void *ih; struct sc_pchinfo pch[M3_PCHANS]; struct sc_rchinfo rch[M3_RCHANS]; int pch_cnt; int rch_cnt; int pch_active_cnt; unsigned int bufsz; u_int16_t *savemem; struct mtx *sc_lock; }; #define M3_LOCK(_sc) snd_mtxlock((_sc)->sc_lock) #define M3_UNLOCK(_sc) snd_mtxunlock((_sc)->sc_lock) #define M3_LOCK_ASSERT(_sc) snd_mtxassert((_sc)->sc_lock) /* -------------------------------------------------------------------- */ /* play channel interface */ static void *m3_pchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int m3_pchan_free(kobj_t, void *); static int m3_pchan_setformat(kobj_t, void *, u_int32_t); static u_int32_t m3_pchan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t m3_pchan_setblocksize(kobj_t, void *, u_int32_t); static int m3_pchan_trigger(kobj_t, void *, int); static int m3_pchan_trigger_locked(kobj_t, void *, int); static u_int32_t m3_pchan_getptr_internal(struct sc_pchinfo *); static u_int32_t m3_pchan_getptr(kobj_t, void *); static struct pcmchan_caps *m3_pchan_getcaps(kobj_t, void *); /* record channel interface */ static void *m3_rchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int m3_rchan_free(kobj_t, void *); static int m3_rchan_setformat(kobj_t, void *, u_int32_t); static u_int32_t m3_rchan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t m3_rchan_setblocksize(kobj_t, void *, u_int32_t); static int m3_rchan_trigger(kobj_t, void *, int); static int m3_rchan_trigger_locked(kobj_t, void *, int); static u_int32_t m3_rchan_getptr_internal(struct sc_rchinfo *); static u_int32_t m3_rchan_getptr(kobj_t, void *); static struct pcmchan_caps *m3_rchan_getcaps(kobj_t, void *); static int m3_chan_active(struct sc_info *); /* talk to the codec - called from ac97.c */ static u_int32_t m3_initcd(kobj_t, void *); static int m3_rdcd(kobj_t, void *, int); static int m3_wrcd(kobj_t, void *, int, u_int32_t); /* stuff */ static void m3_intr(void *); static int m3_power(struct sc_info *, int); static int m3_init(struct sc_info *); static int m3_uninit(struct sc_info *); static u_int8_t m3_assp_halt(struct sc_info *); static void m3_config(struct sc_info *); static void m3_amp_enable(struct sc_info *); static void m3_enable_ints(struct sc_info *); static void m3_codec_reset(struct sc_info *); /* -------------------------------------------------------------------- */ /* Codec descriptor */ static kobj_method_t m3_codec_methods[] = { KOBJMETHOD(ac97_init, m3_initcd), KOBJMETHOD(ac97_read, m3_rdcd), KOBJMETHOD(ac97_write, m3_wrcd), KOBJMETHOD_END }; AC97_DECLARE(m3_codec); /* -------------------------------------------------------------------- */ /* channel descriptors */ static u_int32_t m3_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps m3_playcaps = {8000, 48000, m3_playfmt, 0}; static kobj_method_t m3_pch_methods[] = { KOBJMETHOD(channel_init, m3_pchan_init), KOBJMETHOD(channel_setformat, m3_pchan_setformat), KOBJMETHOD(channel_setspeed, m3_pchan_setspeed), KOBJMETHOD(channel_setblocksize, m3_pchan_setblocksize), KOBJMETHOD(channel_trigger, m3_pchan_trigger), KOBJMETHOD(channel_getptr, m3_pchan_getptr), KOBJMETHOD(channel_getcaps, m3_pchan_getcaps), KOBJMETHOD(channel_free, m3_pchan_free), KOBJMETHOD_END }; CHANNEL_DECLARE(m3_pch); static u_int32_t m3_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps m3_reccaps = {8000, 48000, m3_recfmt, 0}; static kobj_method_t m3_rch_methods[] = { KOBJMETHOD(channel_init, m3_rchan_init), KOBJMETHOD(channel_setformat, m3_rchan_setformat), KOBJMETHOD(channel_setspeed, m3_rchan_setspeed), KOBJMETHOD(channel_setblocksize, m3_rchan_setblocksize), KOBJMETHOD(channel_trigger, m3_rchan_trigger), KOBJMETHOD(channel_getptr, m3_rchan_getptr), KOBJMETHOD(channel_getcaps, m3_rchan_getcaps), KOBJMETHOD(channel_free, m3_rchan_free), KOBJMETHOD_END }; CHANNEL_DECLARE(m3_rch); /* -------------------------------------------------------------------- */ /* some i/o convenience functions */ #define m3_rd_1(sc, regno) bus_space_read_1(sc->st, sc->sh, regno) #define m3_rd_2(sc, regno) bus_space_read_2(sc->st, sc->sh, regno) #define m3_rd_4(sc, regno) bus_space_read_4(sc->st, sc->sh, regno) #define m3_wr_1(sc, regno, data) bus_space_write_1(sc->st, sc->sh, regno, data) #define m3_wr_2(sc, regno, data) bus_space_write_2(sc->st, sc->sh, regno, data) #define m3_wr_4(sc, regno, data) bus_space_write_4(sc->st, sc->sh, regno, data) #define m3_rd_assp_code(sc, index) \ m3_rd_assp(sc, MEMTYPE_INTERNAL_CODE, index) #define m3_wr_assp_code(sc, index, data) \ m3_wr_assp(sc, MEMTYPE_INTERNAL_CODE, index, data) #define m3_rd_assp_data(sc, index) \ m3_rd_assp(sc, MEMTYPE_INTERNAL_DATA, index) #define m3_wr_assp_data(sc, index, data) \ m3_wr_assp(sc, MEMTYPE_INTERNAL_DATA, index, data) static __inline u_int16_t m3_rd_assp(struct sc_info *sc, u_int16_t region, u_int16_t index) { m3_wr_2(sc, DSP_PORT_MEMORY_TYPE, region & MEMTYPE_MASK); m3_wr_2(sc, DSP_PORT_MEMORY_INDEX, index); return m3_rd_2(sc, DSP_PORT_MEMORY_DATA); } static __inline void m3_wr_assp(struct sc_info *sc, u_int16_t region, u_int16_t index, u_int16_t data) { m3_wr_2(sc, DSP_PORT_MEMORY_TYPE, region & MEMTYPE_MASK); m3_wr_2(sc, DSP_PORT_MEMORY_INDEX, index); m3_wr_2(sc, DSP_PORT_MEMORY_DATA, data); } static __inline int m3_wait(struct sc_info *sc) { int i; for (i=0 ; i<20 ; i++) { if ((m3_rd_1(sc, CODEC_STATUS) & 1) == 0) { return 0; } DELAY(2); } return -1; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static u_int32_t m3_initcd(kobj_t kobj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; M3_DEBUG(CALL, ("m3_initcd\n")); /* init ac-link */ data = m3_rd_1(sc, CODEC_COMMAND); return ((data & 0x1) ? 0 : 1); } static int m3_rdcd(kobj_t kobj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; if (m3_wait(sc)) { device_printf(sc->dev, "m3_rdcd timed out.\n"); return -1; } m3_wr_1(sc, CODEC_COMMAND, (regno & 0x7f) | 0x80); DELAY(50); /* ac97 cycle = 20.8 usec */ if (m3_wait(sc)) { device_printf(sc->dev, "m3_rdcd timed out.\n"); return -1; } data = m3_rd_2(sc, CODEC_DATA); return data; } static int m3_wrcd(kobj_t kobj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; if (m3_wait(sc)) { device_printf(sc->dev, "m3_wrcd timed out.\n"); return -1; } m3_wr_2(sc, CODEC_DATA, data); m3_wr_1(sc, CODEC_COMMAND, regno & 0x7f); DELAY(50); /* ac97 cycle = 20.8 usec */ return 0; } /* -------------------------------------------------------------------- */ /* play channel interface */ #define LO(x) (((x) & 0x0000ffff) ) #define HI(x) (((x) & 0xffff0000) >> 16) static void * m3_pchan_init(kobj_t kobj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; u_int32_t bus_addr, i; int idx, data_bytes, dac_data; int dsp_in_size, dsp_out_size, dsp_in_buf, dsp_out_buf; struct data_word { u_int16_t addr, val; } pv[] = { {CDATA_LEFT_VOLUME, M3_DEFAULT_VOL}, {CDATA_RIGHT_VOLUME, M3_DEFAULT_VOL}, {SRC3_DIRECTION_OFFSET, 0} , {SRC3_DIRECTION_OFFSET + 3, 0x0000}, {SRC3_DIRECTION_OFFSET + 4, 0}, {SRC3_DIRECTION_OFFSET + 5, 0}, {SRC3_DIRECTION_OFFSET + 6, 0}, {SRC3_DIRECTION_OFFSET + 7, 0}, {SRC3_DIRECTION_OFFSET + 8, 0}, {SRC3_DIRECTION_OFFSET + 9, 0}, {SRC3_DIRECTION_OFFSET + 10, 0x8000}, {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, {SRC3_DIRECTION_OFFSET + 13, 0}, {SRC3_DIRECTION_OFFSET + 14, 0}, {SRC3_DIRECTION_OFFSET + 15, 0}, {SRC3_DIRECTION_OFFSET + 16, 8}, {SRC3_DIRECTION_OFFSET + 17, 50*2}, {SRC3_DIRECTION_OFFSET + 18, MINISRC_BIQUAD_STAGE - 1}, {SRC3_DIRECTION_OFFSET + 20, 0}, {SRC3_DIRECTION_OFFSET + 21, 0} }; M3_LOCK(sc); idx = sc->pch_cnt; /* dac instance number, no active reuse! */ M3_DEBUG(CHANGE, ("m3_pchan_init(dac=%d)\n", idx)); if (dir != PCMDIR_PLAY) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_pchan_init not PCMDIR_PLAY\n"); return (NULL); } data_bytes = (((MINISRC_TMP_BUFFER_SIZE & ~1) + (MINISRC_IN_BUFFER_SIZE & ~1) + (MINISRC_OUT_BUFFER_SIZE & ~1) + 4) + 255) &~ 255; dac_data = 0x1100 + (data_bytes * idx); dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x20 * 2); dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x20 * 2); dsp_in_buf = dac_data + (MINISRC_TMP_BUFFER_SIZE/2); dsp_out_buf = dsp_in_buf + (dsp_in_size/2) + 1; ch = &sc->pch[idx]; ch->dac_idx = idx; ch->dac_data = dac_data; if (ch->dac_data + data_bytes/2 >= 0x1c00) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_pchan_init: revb mem exhausted\n"); return (NULL); } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_pchan_init chn_allocbuf failed\n"); return (NULL); } M3_LOCK(sc); ch->bufsize = sndbuf_getsize(ch->buffer); /* host dma buffer pointers */ bus_addr = sndbuf_getbufaddr(ch->buffer); if (bus_addr & 3) { device_printf(sc->dev, "m3_pchan_init unaligned bus_addr\n"); bus_addr = (bus_addr + 4) & ~3; } m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_ADDRL, LO(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_ADDRH, HI(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_END_PLUS_1L, LO(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_END_PLUS_1H, HI(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL, LO(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH, HI(bus_addr)); /* dsp buffers */ m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_BEGIN, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_END_PLUS_1, dsp_in_buf + dsp_in_size/2); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_HEAD, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_TAIL, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_BEGIN, dsp_out_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_END_PLUS_1, dsp_out_buf + dsp_out_size/2); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_HEAD, dsp_out_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_TAIL, dsp_out_buf); /* some per client initializers */ m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 12, ch->dac_data + 40 + 8); m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 19, 0x400 + MINISRC_COEF_LOC); /* enable or disable low pass filter? (0xff if rate> 45000) */ m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 22, 0); /* tell it which way dma is going? */ m3_wr_assp_data(sc, ch->dac_data + CDATA_DMA_CONTROL, DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); /* set an armload of static initializers */ for(i = 0 ; i < (sizeof(pv) / sizeof(pv[0])) ; i++) { m3_wr_assp_data(sc, ch->dac_data + pv[i].addr, pv[i].val); } /* put us in the packed task lists */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt + sc->rch_cnt), ch->dac_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt + sc->rch_cnt), ch->dac_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_MIXER_XFER0 + sc->pch_cnt, ch->dac_data >> DP_SHIFT_COUNT); /* gotta start before stop */ m3_pchan_trigger_locked(NULL, ch, PCMTRIG_START); /* silence noise on load */ m3_pchan_trigger_locked(NULL, ch, PCMTRIG_STOP); sc->pch_cnt++; M3_UNLOCK(sc); return (ch); } static int m3_pchan_free(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_free(dac=%d)\n", ch->dac_idx)); /* * should remove this exact instance from the packed lists, but all * are released at once (and in a stopped state) so this is ok. */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt - 1) + sc->rch_cnt, 0); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt - 1) + sc->rch_cnt, 0); m3_wr_assp_data(sc, KDATA_MIXER_XFER0 + (sc->pch_cnt-1), 0); sc->pch_cnt--; M3_UNLOCK(sc); return (0); } static int m3_pchan_setformat(kobj_t kobj, void *chdata, u_int32_t format) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_setformat(dac=%d, format=0x%x{%s-%s})\n", ch->dac_idx, format, format & (AFMT_U8|AFMT_S8) ? "8bit":"16bit", (AFMT_CHANNEL(format) > 1) ? "STEREO":"MONO")); /* mono word */ data = (AFMT_CHANNEL(format) > 1)? 0 : 1; m3_wr_assp_data(sc, ch->dac_data + SRC3_MODE_OFFSET, data); /* 8bit word */ data = ((format & AFMT_U8) || (format & AFMT_S8)) ? 1 : 0; m3_wr_assp_data(sc, ch->dac_data + SRC3_WORD_LENGTH_OFFSET, data); ch->fmt = format; M3_UNLOCK(sc); return (0); } static u_int32_t m3_pchan_setspeed(kobj_t kobj, void *chdata, u_int32_t speed) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t freq; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_setspeed(dac=%d, speed=%d)\n", ch->dac_idx, speed)); if ((freq = ((speed << 15) + 24000) / 48000) != 0) { freq--; } m3_wr_assp_data(sc, ch->dac_data + CDATA_FREQUENCY, freq); ch->spd = speed; M3_UNLOCK(sc); /* return closest possible speed */ return (speed); } static u_int32_t m3_pchan_setblocksize(kobj_t kobj, void *chdata, u_int32_t blocksize) { struct sc_pchinfo *ch = chdata; M3_DEBUG(CHANGE, ("m3_pchan_setblocksize(dac=%d, blocksize=%d)\n", ch->dac_idx, blocksize)); return (sndbuf_getblksz(ch->buffer)); } static int m3_pchan_trigger(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; int ret; if (!PCMTRIG_COMMON(go)) return (0); M3_LOCK(sc); ret = m3_pchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); return (ret); } static int m3_chan_active(struct sc_info *sc) { int i, ret; ret = 0; for (i = 0; i < sc->pch_cnt; i++) ret += sc->pch[i].active; for (i = 0; i < sc->rch_cnt; i++) ret += sc->rch[i].active; return (ret); } static int m3_pchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK_ASSERT(sc); M3_DEBUG(go == PCMTRIG_START ? CHANGE : go == PCMTRIG_STOP ? CHANGE : go == PCMTRIG_ABORT ? CHANGE : CALL, ("m3_pchan_trigger(dac=%d, go=0x%x{%s})\n", ch->dac_idx, go, go == PCMTRIG_START ? "PCMTRIG_START" : go == PCMTRIG_STOP ? "PCMTRIG_STOP" : go == PCMTRIG_ABORT ? "PCMTRIG_ABORT" : "ignore")); switch(go) { case PCMTRIG_START: if (ch->active) { return 0; } ch->active = 1; ch->ptr = 0; ch->prevptr = 0; sc->pch_active_cnt++; /*[[inc_timer_users]]*/ if (m3_chan_active(sc) == 1) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 1); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, sc->pch_active_cnt); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (ch->active == 0) { return 0; } ch->active = 0; sc->pch_active_cnt--; /* XXX should the channel be drained? */ /*[[dec_timer_users]]*/ if (m3_chan_active(sc) == 0) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, sc->pch_active_cnt); break; case PCMTRIG_EMLDMAWR: /* got play irq, transfer next buffer - ignore if using dma */ case PCMTRIG_EMLDMARD: /* got rec irq, transfer next buffer - ignore if using dma */ default: break; } return 0; } static u_int32_t m3_pchan_getptr_internal(struct sc_pchinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; bus_base = sndbuf_getbufaddr(ch->buffer); hi = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL); bus_crnt = lo | (hi << 16); M3_DEBUG(CALL, ("m3_pchan_getptr(dac=%d) result=%d\n", ch->dac_idx, bus_crnt - bus_base)); return (bus_crnt - bus_base); /* current byte offset of channel */ } static u_int32_t m3_pchan_getptr(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t ptr; M3_LOCK(sc); ptr = ch->ptr; M3_UNLOCK(sc); return (ptr); } static struct pcmchan_caps * m3_pchan_getcaps(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; M3_DEBUG(CALL, ("m3_pchan_getcaps(dac=%d)\n", ch->dac_idx)); return &m3_playcaps; } /* -------------------------------------------------------------------- */ /* rec channel interface */ static void * m3_rchan_init(kobj_t kobj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; u_int32_t bus_addr, i; int idx, data_bytes, adc_data; int dsp_in_size, dsp_out_size, dsp_in_buf, dsp_out_buf; struct data_word { u_int16_t addr, val; } rv[] = { {CDATA_LEFT_VOLUME, M3_DEFAULT_VOL}, {CDATA_RIGHT_VOLUME, M3_DEFAULT_VOL}, {SRC3_DIRECTION_OFFSET, 1}, {SRC3_DIRECTION_OFFSET + 3, 0x0000}, {SRC3_DIRECTION_OFFSET + 4, 0}, {SRC3_DIRECTION_OFFSET + 5, 0}, {SRC3_DIRECTION_OFFSET + 6, 0}, {SRC3_DIRECTION_OFFSET + 7, 0}, {SRC3_DIRECTION_OFFSET + 8, 0}, {SRC3_DIRECTION_OFFSET + 9, 0}, {SRC3_DIRECTION_OFFSET + 10, 0x8000}, {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, {SRC3_DIRECTION_OFFSET + 13, 0}, {SRC3_DIRECTION_OFFSET + 14, 0}, {SRC3_DIRECTION_OFFSET + 15, 0}, {SRC3_DIRECTION_OFFSET + 16, 50}, {SRC3_DIRECTION_OFFSET + 17, 8}, {SRC3_DIRECTION_OFFSET + 18, 0}, {SRC3_DIRECTION_OFFSET + 19, 0}, {SRC3_DIRECTION_OFFSET + 20, 0}, {SRC3_DIRECTION_OFFSET + 21, 0}, {SRC3_DIRECTION_OFFSET + 22, 0xff} }; M3_LOCK(sc); idx = sc->rch_cnt; /* adc instance number, no active reuse! */ M3_DEBUG(CHANGE, ("m3_rchan_init(adc=%d)\n", idx)); if (dir != PCMDIR_REC) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_pchan_init not PCMDIR_REC\n"); return (NULL); } data_bytes = (((MINISRC_TMP_BUFFER_SIZE & ~1) + (MINISRC_IN_BUFFER_SIZE & ~1) + (MINISRC_OUT_BUFFER_SIZE & ~1) + 4) + 255) &~ 255; adc_data = 0x1100 + (data_bytes * idx) + data_bytes/2; dsp_in_size = MINISRC_IN_BUFFER_SIZE + (0x10 * 2); dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x10 * 2); dsp_in_buf = adc_data + (MINISRC_TMP_BUFFER_SIZE / 2); dsp_out_buf = dsp_in_buf + (dsp_in_size / 2) + 1; ch = &sc->rch[idx]; ch->adc_idx = idx; ch->adc_data = adc_data; if (ch->adc_data + data_bytes/2 >= 0x1c00) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_rchan_init: revb mem exhausted\n"); return (NULL); } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_rchan_init chn_allocbuf failed\n"); return (NULL); } M3_LOCK(sc); ch->bufsize = sndbuf_getsize(ch->buffer); /* host dma buffer pointers */ bus_addr = sndbuf_getbufaddr(ch->buffer); if (bus_addr & 3) { device_printf(sc->dev, "m3_rchan_init unaligned bus_addr\n"); bus_addr = (bus_addr + 4) & ~3; } m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_ADDRL, LO(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_ADDRH, HI(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_END_PLUS_1L, LO(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_END_PLUS_1H, HI(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL, LO(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH, HI(bus_addr)); /* dsp buffers */ m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_BEGIN, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_END_PLUS_1, dsp_in_buf + dsp_in_size/2); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_HEAD, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_TAIL, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_BEGIN, dsp_out_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_END_PLUS_1, dsp_out_buf + dsp_out_size/2); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_HEAD, dsp_out_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_TAIL, dsp_out_buf); /* some per client initializers */ m3_wr_assp_data(sc, ch->adc_data + SRC3_DIRECTION_OFFSET + 12, ch->adc_data + 40 + 8); m3_wr_assp_data(sc, ch->adc_data + CDATA_DMA_CONTROL, DMACONTROL_DIRECTION + DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); /* set an armload of static initializers */ for(i = 0 ; i < (sizeof(rv) / sizeof(rv[0])) ; i++) { m3_wr_assp_data(sc, ch->adc_data + rv[i].addr, rv[i].val); } /* put us in the packed task lists */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt + sc->rch_cnt), ch->adc_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt + sc->rch_cnt), ch->adc_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_ADC1_XFER0 + sc->rch_cnt, ch->adc_data >> DP_SHIFT_COUNT); /* gotta start before stop */ m3_rchan_trigger_locked(NULL, ch, PCMTRIG_START); /* stop on init */ m3_rchan_trigger_locked(NULL, ch, PCMTRIG_STOP); sc->rch_cnt++; M3_UNLOCK(sc); return (ch); } static int m3_rchan_free(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_free(adc=%d)\n", ch->adc_idx)); /* * should remove this exact instance from the packed lists, but all * are released at once (and in a stopped state) so this is ok. */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->rch_cnt - 1) + sc->pch_cnt, 0); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->rch_cnt - 1) + sc->pch_cnt, 0); m3_wr_assp_data(sc, KDATA_ADC1_XFER0 + (sc->rch_cnt - 1), 0); sc->rch_cnt--; M3_UNLOCK(sc); return (0); } static int m3_rchan_setformat(kobj_t kobj, void *chdata, u_int32_t format) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_setformat(dac=%d, format=0x%x{%s-%s})\n", ch->adc_idx, format, format & (AFMT_U8|AFMT_S8) ? "8bit":"16bit", (AFMT_CHANNEL(format) > 1) ? "STEREO":"MONO")); /* mono word */ data = (AFMT_CHANNEL(format) > 1) ? 0 : 1; m3_wr_assp_data(sc, ch->adc_data + SRC3_MODE_OFFSET, data); /* 8bit word */ data = ((format & AFMT_U8) || (format & AFMT_S8)) ? 1 : 0; m3_wr_assp_data(sc, ch->adc_data + SRC3_WORD_LENGTH_OFFSET, data); ch->fmt = format; M3_UNLOCK(sc); return (0); } static u_int32_t m3_rchan_setspeed(kobj_t kobj, void *chdata, u_int32_t speed) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t freq; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_setspeed(adc=%d, speed=%d)\n", ch->adc_idx, speed)); if ((freq = ((speed << 15) + 24000) / 48000) != 0) { freq--; } m3_wr_assp_data(sc, ch->adc_data + CDATA_FREQUENCY, freq); ch->spd = speed; M3_UNLOCK(sc); /* return closest possible speed */ return (speed); } static u_int32_t m3_rchan_setblocksize(kobj_t kobj, void *chdata, u_int32_t blocksize) { struct sc_rchinfo *ch = chdata; M3_DEBUG(CHANGE, ("m3_rchan_setblocksize(adc=%d, blocksize=%d)\n", ch->adc_idx, blocksize)); return (sndbuf_getblksz(ch->buffer)); } static int m3_rchan_trigger(kobj_t kobj, void *chdata, int go) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; int ret; if (!PCMTRIG_COMMON(go)) return (0); M3_LOCK(sc); ret = m3_rchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); return (ret); } static int m3_rchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK_ASSERT(sc); M3_DEBUG(go == PCMTRIG_START ? CHANGE : go == PCMTRIG_STOP ? CHANGE : go == PCMTRIG_ABORT ? CHANGE : CALL, ("m3_rchan_trigger(adc=%d, go=0x%x{%s})\n", ch->adc_idx, go, go == PCMTRIG_START ? "PCMTRIG_START" : go == PCMTRIG_STOP ? "PCMTRIG_STOP" : go == PCMTRIG_ABORT ? "PCMTRIG_ABORT" : "ignore")); switch(go) { case PCMTRIG_START: if (ch->active) { return 0; } ch->active = 1; ch->ptr = 0; ch->prevptr = 0; /*[[inc_timer_users]]*/ if (m3_chan_active(sc) == 1) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 1); m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 1); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (ch->active == 0) { return 0; } ch->active = 0; /*[[dec_timer_users]]*/ if (m3_chan_active(sc) == 0) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 0); break; case PCMTRIG_EMLDMAWR: /* got play irq, transfer next buffer - ignore if using dma */ case PCMTRIG_EMLDMARD: /* got rec irq, transfer next buffer - ignore if using dma */ default: break; } return 0; } static u_int32_t m3_rchan_getptr_internal(struct sc_rchinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; bus_base = sndbuf_getbufaddr(ch->buffer); hi = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL); bus_crnt = lo | (hi << 16); M3_DEBUG(CALL, ("m3_rchan_getptr(adc=%d) result=%d\n", ch->adc_idx, bus_crnt - bus_base)); return (bus_crnt - bus_base); /* current byte offset of channel */ } static u_int32_t m3_rchan_getptr(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t ptr; M3_LOCK(sc); ptr = ch->ptr; M3_UNLOCK(sc); return (ptr); } static struct pcmchan_caps * m3_rchan_getcaps(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; M3_DEBUG(CALL, ("m3_rchan_getcaps(adc=%d)\n", ch->adc_idx)); return &m3_reccaps; } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void m3_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_pchinfo *pch; struct sc_rchinfo *rch; u_int32_t status, ctl, i, delta; M3_DEBUG(INTR, ("m3_intr\n")); M3_LOCK(sc); status = m3_rd_1(sc, HOST_INT_STATUS); if (!status) { M3_UNLOCK(sc); return; } m3_wr_1(sc, HOST_INT_STATUS, 0xff); /* ack the int? */ if (status & HV_INT_PENDING) { u_int8_t event; event = m3_rd_1(sc, HW_VOL_COUNTER_MASTER); switch (event) { case 0x99: mixer_hwvol_mute(sc->dev); break; case 0xaa: mixer_hwvol_step(sc->dev, 1, 1); break; case 0x66: mixer_hwvol_step(sc->dev, -1, -1); break; case 0x88: break; default: device_printf(sc->dev, "Unknown HWVOL event\n"); } m3_wr_1(sc, HW_VOL_COUNTER_MASTER, 0x88); } if (status & ASSP_INT_PENDING) { ctl = m3_rd_1(sc, ASSP_CONTROL_B); if (!(ctl & STOP_ASSP_CLOCK)) { ctl = m3_rd_1(sc, ASSP_HOST_INT_STATUS); if (ctl & DSP2HOST_REQ_TIMER) { m3_wr_1(sc, ASSP_HOST_INT_STATUS, DSP2HOST_REQ_TIMER); /*[[ess_update_ptr]]*/ goto m3_handle_channel_intr; } } } goto m3_handle_channel_intr_out; m3_handle_channel_intr: for (i=0 ; ipch_cnt ; i++) { pch = &sc->pch[i]; if (pch->active) { pch->ptr = m3_pchan_getptr_internal(pch); delta = pch->bufsize + pch->ptr - pch->prevptr; delta %= pch->bufsize; if (delta < sndbuf_getblksz(pch->buffer)) continue; pch->prevptr = pch->ptr; M3_UNLOCK(sc); chn_intr(pch->channel); M3_LOCK(sc); } } for (i=0 ; irch_cnt ; i++) { rch = &sc->rch[i]; if (rch->active) { rch->ptr = m3_rchan_getptr_internal(rch); delta = rch->bufsize + rch->ptr - rch->prevptr; delta %= rch->bufsize; if (delta < sndbuf_getblksz(rch->buffer)) continue; rch->prevptr = rch->ptr; M3_UNLOCK(sc); chn_intr(rch->channel); M3_LOCK(sc); } } m3_handle_channel_intr_out: M3_UNLOCK(sc); } /* -------------------------------------------------------------------- */ /* stuff */ static int m3_power(struct sc_info *sc, int state) { u_int32_t data; M3_DEBUG(CHANGE, ("m3_power(%d)\n", state)); M3_LOCK_ASSERT(sc); data = pci_read_config(sc->dev, 0x34, 1); if (pci_read_config(sc->dev, data, 1) == 1) { pci_write_config(sc->dev, data + 4, state, 1); } return 0; } static int m3_init(struct sc_info *sc) { u_int32_t data, i, size; u_int8_t reset_state; M3_LOCK_ASSERT(sc); M3_DEBUG(CHANGE, ("m3_init\n")); /* diable legacy emulations. */ data = pci_read_config(sc->dev, PCI_LEGACY_AUDIO_CTRL, 2); data |= DISABLE_LEGACY; pci_write_config(sc->dev, PCI_LEGACY_AUDIO_CTRL, data, 2); m3_config(sc); reset_state = m3_assp_halt(sc); m3_codec_reset(sc); /* [m3_assp_init] */ /* zero kernel data */ size = REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA; for(i = 0 ; i < size / 2 ; i++) { m3_wr_assp_data(sc, KDATA_BASE_ADDR + i, 0); } /* zero mixer data? */ size = REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA; for(i = 0 ; i < size / 2 ; i++) { m3_wr_assp_data(sc, KDATA_BASE_ADDR2 + i, 0); } /* init dma pointer */ m3_wr_assp_data(sc, KDATA_CURRENT_DMA, KDATA_DMA_XFER0); /* write kernel into code memory */ size = sizeof(gaw_kernel_vect_code); for(i = 0 ; i < size / 2; i++) { m3_wr_assp_code(sc, REV_B_CODE_MEMORY_BEGIN + i, gaw_kernel_vect_code[i]); } /* * We only have this one client and we know that 0x400 is free in * our kernel's mem map, so lets just drop it there. It seems that * the minisrc doesn't need vectors, so we won't bother with them.. */ size = sizeof(gaw_minisrc_code_0400); for(i = 0 ; i < size / 2; i++) { m3_wr_assp_code(sc, 0x400 + i, gaw_minisrc_code_0400[i]); } /* write the coefficients for the low pass filter? */ size = sizeof(minisrc_lpf); for(i = 0; i < size / 2 ; i++) { m3_wr_assp_code(sc,0x400 + MINISRC_COEF_LOC + i, minisrc_lpf[i]); } m3_wr_assp_code(sc, 0x400 + MINISRC_COEF_LOC + size, 0x8000); /* the minisrc is the only thing on our task list */ m3_wr_assp_data(sc, KDATA_TASK0, 0x400); /* init the mixer number */ m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, 0); /* extreme kernel master volume */ m3_wr_assp_data(sc, KDATA_DAC_LEFT_VOLUME, M3_DEFAULT_VOL); m3_wr_assp_data(sc, KDATA_DAC_RIGHT_VOLUME, M3_DEFAULT_VOL); m3_amp_enable(sc); /* [m3_assp_client_init] (only one client at index 0) */ for (i=0x1100 ; i<0x1c00 ; i++) { m3_wr_assp_data(sc, i, 0); /* zero entire dac/adc area */ } /* [m3_assp_continue] */ m3_wr_1(sc, DSP_PORT_CONTROL_REG_B, reset_state | REGB_ENABLE_RESET); return 0; } static int m3_uninit(struct sc_info *sc) { M3_DEBUG(CHANGE, ("m3_uninit\n")); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int m3_pci_probe(device_t dev) { struct m3_card_type *card; M3_DEBUG(CALL, ("m3_pci_probe(0x%x)\n", pci_get_devid(dev))); for (card = m3_card_types ; card->pci_id ; card++) { if (pci_get_devid(dev) == card->pci_id) { device_set_desc(dev, card->name); return BUS_PROBE_DEFAULT; } } return ENXIO; } static int m3_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; struct m3_card_type *card; int i, len, dacn, adcn; M3_DEBUG(CALL, ("m3_pci_attach\n")); sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); sc->sc_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_maestro3 softc"); for (card = m3_card_types ; card->pci_id ; card++) { if (sc->type == card->pci_id) { sc->which = card->which; sc->delay1 = card->delay1; sc->delay2 = card->delay2; break; } } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &i) == 0) { if (i < 1) dacn = 1; else if (i > M3_PCHANS) dacn = M3_PCHANS; else dacn = i; } else dacn = M3_PCHANS; adcn = M3_RCHANS; pci_enable_busmaster(dev); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); } if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, m3_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, M3_BUFSIZE_MIN, M3_BUFSIZE_DEFAULT, M3_BUFSIZE_MAX); if (bus_dma_tag_create( bus_get_dma_tag(dev), /* parent */ 2, 0, /* alignment, boundary */ M3_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ sc->bufsz, /* maxsize */ 1, /* nsegments */ 0x3ffff, /* maxsegz */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } M3_LOCK(sc); m3_power(sc, 0); /* power up */ /* init chip */ i = m3_init(sc); M3_UNLOCK(sc); if (i == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, m3_codec); if (codec == NULL) { device_printf(dev, "ac97_create error\n"); goto bad; } if (mixer_init(dev, ac97_getmixerclass(), codec)) { device_printf(dev, "mixer_init error\n"); goto bad; } m3_enable_ints(sc); if (pcm_register(dev, sc, dacn, adcn)) { device_printf(dev, "pcm_register error\n"); goto bad; } for (i=0 ; iregtype == SYS_RES_IOPORT)? "io" : "memory", + snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", + (sc->regtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(sc->reg), rman_get_start(sc->irq), - PCM_KLDSTRING(snd_maestro3)); + device_get_nameunit(device_get_parent(dev))); if (pcm_setstatus(dev, status)) { device_printf(dev, "attach: pcm_setstatus error\n"); goto bad; } mixer_hwvol_init(dev); /* Create the buffer for saving the card state during suspend */ len = sizeof(u_int16_t) * (REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH); sc->savemem = (u_int16_t*)malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); return 0; bad: if (codec) ac97_destroy(codec); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->sc_lock) snd_mtxfree(sc->sc_lock); free(sc, M_DEVBUF); return ENXIO; } static int m3_pci_detach(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int r; M3_DEBUG(CALL, ("m3_pci_detach\n")); if ((r = pcm_unregister(dev)) != 0) { return r; } M3_LOCK(sc); m3_uninit(sc); /* shutdown chip */ m3_power(sc, 3); /* power off */ M3_UNLOCK(sc); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_dma_tag_destroy(sc->parent_dmat); free(sc->savemem, M_DEVBUF); snd_mtxfree(sc->sc_lock); free(sc, M_DEVBUF); return 0; } static int m3_pci_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int i, index = 0; M3_DEBUG(CHANGE, ("m3_pci_suspend\n")); M3_LOCK(sc); for (i=0 ; ipch_cnt ; i++) { if (sc->pch[i].active) { m3_pchan_trigger_locked(NULL, &sc->pch[i], PCMTRIG_STOP); } } for (i=0 ; irch_cnt ; i++) { if (sc->rch[i].active) { m3_rchan_trigger_locked(NULL, &sc->rch[i], PCMTRIG_STOP); } } DELAY(10 * 1000); /* give things a chance to stop */ /* Disable interrupts */ m3_wr_2(sc, HOST_INT_CTRL, 0); m3_wr_1(sc, ASSP_CONTROL_C, 0); m3_assp_halt(sc); /* Save the state of the ASSP */ for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++) sc->savemem[index++] = m3_rd_assp_code(sc, i); for (i = REV_B_DATA_MEMORY_BEGIN; i <= REV_B_DATA_MEMORY_END; i++) sc->savemem[index++] = m3_rd_assp_data(sc, i); /* Power down the card to D3 state */ m3_power(sc, 3); M3_UNLOCK(sc); return 0; } static int m3_pci_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int i, index = 0; u_int8_t reset_state; M3_DEBUG(CHANGE, ("m3_pci_resume\n")); M3_LOCK(sc); /* Power the card back to D0 */ m3_power(sc, 0); m3_config(sc); reset_state = m3_assp_halt(sc); m3_codec_reset(sc); /* Restore the ASSP state */ for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++) m3_wr_assp_code(sc, i, sc->savemem[index++]); for (i = REV_B_DATA_MEMORY_BEGIN; i <= REV_B_DATA_MEMORY_END; i++) m3_wr_assp_data(sc, i, sc->savemem[index++]); /* Restart the DMA engine */ m3_wr_assp_data(sc, KDATA_DMA_ACTIVE, 0); /* [m3_assp_continue] */ m3_wr_1(sc, DSP_PORT_CONTROL_REG_B, reset_state | REGB_ENABLE_RESET); m3_amp_enable(sc); m3_enable_ints(sc); M3_UNLOCK(sc); /* XXX */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } M3_LOCK(sc); /* Turn the channels back on */ for (i=0 ; ipch_cnt ; i++) { if (sc->pch[i].active) { m3_pchan_trigger_locked(NULL, &sc->pch[i], PCMTRIG_START); } } for (i=0 ; irch_cnt ; i++) { if (sc->rch[i].active) { m3_rchan_trigger_locked(NULL, &sc->rch[i], PCMTRIG_START); } } M3_UNLOCK(sc); return 0; } static int m3_pci_shutdown(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); M3_DEBUG(CALL, ("m3_pci_shutdown\n")); M3_LOCK(sc); m3_power(sc, 3); /* power off */ M3_UNLOCK(sc); return 0; } static u_int8_t m3_assp_halt(struct sc_info *sc) { u_int8_t data, reset_state; M3_LOCK_ASSERT(sc); data = m3_rd_1(sc, DSP_PORT_CONTROL_REG_B); reset_state = data & ~REGB_STOP_CLOCK; /* remember for continue */ DELAY(10 * 1000); m3_wr_1(sc, DSP_PORT_CONTROL_REG_B, reset_state & ~REGB_ENABLE_RESET); DELAY(10 * 1000); /* necessary? */ return reset_state; } static void m3_config(struct sc_info *sc) { u_int32_t data, hv_cfg; int hint; M3_LOCK_ASSERT(sc); M3_UNLOCK(sc); /* * The volume buttons can be wired up via two different sets of pins. * This presents a problem since we can't tell which way it's * configured. Allow the user to set a hint in order to twiddle * the proper bits. */ if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "hwvol_config", &hint) == 0) hv_cfg = (hint > 0) ? HV_BUTTON_FROM_GD : 0; else hv_cfg = HV_BUTTON_FROM_GD; M3_LOCK(sc); data = pci_read_config(sc->dev, PCI_ALLEGRO_CONFIG, 4); data &= ~HV_BUTTON_FROM_GD; data |= REDUCED_DEBOUNCE | HV_CTRL_ENABLE | hv_cfg; data |= PM_CTRL_ENABLE | CLK_DIV_BY_49 | USE_PCI_TIMING; pci_write_config(sc->dev, PCI_ALLEGRO_CONFIG, data, 4); m3_wr_1(sc, ASSP_CONTROL_B, RESET_ASSP); data = pci_read_config(sc->dev, PCI_ALLEGRO_CONFIG, 4); data &= ~INT_CLK_SELECT; if (sc->which == ESS_MAESTRO3) { data &= ~INT_CLK_MULT_ENABLE; data |= INT_CLK_SRC_NOT_PCI; } data &= ~(CLK_MULT_MODE_SELECT | CLK_MULT_MODE_SELECT_2); pci_write_config(sc->dev, PCI_ALLEGRO_CONFIG, data, 4); if (sc->which == ESS_ALLEGRO_1) { data = pci_read_config(sc->dev, PCI_USER_CONFIG, 4); data |= IN_CLK_12MHZ_SELECT; pci_write_config(sc->dev, PCI_USER_CONFIG, data, 4); } data = m3_rd_1(sc, ASSP_CONTROL_A); data &= ~(DSP_CLK_36MHZ_SELECT | ASSP_CLK_49MHZ_SELECT); data |= ASSP_CLK_49MHZ_SELECT; /*XXX assumes 49MHZ dsp XXX*/ data |= ASSP_0_WS_ENABLE; m3_wr_1(sc, ASSP_CONTROL_A, data); m3_wr_1(sc, ASSP_CONTROL_B, RUN_ASSP); } static void m3_enable_ints(struct sc_info *sc) { u_int8_t data; m3_wr_2(sc, HOST_INT_CTRL, ASSP_INT_ENABLE | HV_INT_ENABLE); data = m3_rd_1(sc, ASSP_CONTROL_C); m3_wr_1(sc, ASSP_CONTROL_C, data | ASSP_HOST_INT_ENABLE); } static void m3_amp_enable(struct sc_info *sc) { u_int32_t gpo, polarity_port, polarity; u_int16_t data; M3_LOCK_ASSERT(sc); switch (sc->which) { case ESS_ALLEGRO_1: polarity_port = 0x1800; break; case ESS_MAESTRO3: polarity_port = 0x1100; break; default: panic("bad sc->which"); } gpo = (polarity_port >> 8) & 0x0f; polarity = polarity_port >> 12; polarity = !polarity; /* enable */ polarity = polarity << gpo; gpo = 1 << gpo; m3_wr_2(sc, GPIO_MASK, ~gpo); data = m3_rd_2(sc, GPIO_DIRECTION); m3_wr_2(sc, GPIO_DIRECTION, data | gpo); data = GPO_SECONDARY_AC97 | GPO_PRIMARY_AC97 | polarity; m3_wr_2(sc, GPIO_DATA, data); m3_wr_2(sc, GPIO_MASK, ~0); } static void m3_codec_reset(struct sc_info *sc) { u_int16_t data, dir; int retry = 0; M3_LOCK_ASSERT(sc); do { data = m3_rd_2(sc, GPIO_DIRECTION); dir = data | 0x10; /* assuming pci bus master? */ /* [[remote_codec_config]] */ data = m3_rd_2(sc, RING_BUS_CTRL_B); m3_wr_2(sc, RING_BUS_CTRL_B, data & ~SECOND_CODEC_ID_MASK); data = m3_rd_2(sc, SDO_OUT_DEST_CTRL); m3_wr_2(sc, SDO_OUT_DEST_CTRL, data & ~COMMAND_ADDR_OUT); data = m3_rd_2(sc, SDO_IN_DEST_CTRL); m3_wr_2(sc, SDO_IN_DEST_CTRL, data & ~STATUS_ADDR_IN); m3_wr_2(sc, RING_BUS_CTRL_A, IO_SRAM_ENABLE); DELAY(20); m3_wr_2(sc, GPIO_DIRECTION, dir & ~GPO_PRIMARY_AC97); m3_wr_2(sc, GPIO_MASK, ~GPO_PRIMARY_AC97); m3_wr_2(sc, GPIO_DATA, 0); m3_wr_2(sc, GPIO_DIRECTION, dir | GPO_PRIMARY_AC97); DELAY(sc->delay1 * 1000); /*delay1 (ALLEGRO:50, MAESTRO3:20)*/ m3_wr_2(sc, GPIO_DATA, GPO_PRIMARY_AC97); DELAY(5); m3_wr_2(sc, RING_BUS_CTRL_A, IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE); m3_wr_2(sc, GPIO_MASK, ~0); DELAY(sc->delay2 * 1000); /*delay2 (ALLEGRO:800, MAESTRO3:500)*/ /* [[try read vendor]] */ data = m3_rdcd(NULL, sc, 0x7c); if ((data == 0) || (data == 0xffff)) { retry++; if (retry > 3) { device_printf(sc->dev, "Codec reset failed\n"); break; } device_printf(sc->dev, "Codec reset retry\n"); } else retry = 0; } while (retry); } static device_method_t m3_methods[] = { DEVMETHOD(device_probe, m3_pci_probe), DEVMETHOD(device_attach, m3_pci_attach), DEVMETHOD(device_detach, m3_pci_detach), DEVMETHOD(device_suspend, m3_pci_suspend), DEVMETHOD(device_resume, m3_pci_resume), DEVMETHOD(device_shutdown, m3_pci_shutdown), { 0, 0 } }; static driver_t m3_driver = { "pcm", m3_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_maestro3, pci, m3_driver, 0, 0); MODULE_DEPEND(snd_maestro3, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_maestro3, 1); diff --git a/sys/dev/sound/pci/neomagic.c b/sys/dev/sound/pci/neomagic.c index 1b325205da5f..25273633ff18 100644 --- a/sys/dev/sound/pci/neomagic.c +++ b/sys/dev/sound/pci/neomagic.c @@ -1,820 +1,821 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Derived from the public domain Linux driver * * 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include /* -------------------------------------------------------------------- */ #define NM_BUFFSIZE 16384 #define NM256AV_PCI_ID 0x800510c8 #define NM256ZX_PCI_ID 0x800610c8 struct sc_info; /* channel registers */ struct sc_chinfo { int active, spd, dir, fmt; u_int32_t blksize, wmark; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type; struct resource *reg, *irq, *buf; int regid, irqid, bufid; void *ih; u_int32_t ac97_base, ac97_status, ac97_busy; u_int32_t buftop, pbuf, rbuf, cbuf, acbuf; u_int32_t playint, recint, misc1int, misc2int; u_int32_t irsz, badintr; struct sc_chinfo pch, rch; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int nm_loadcoeff(struct sc_info *sc, int dir, int num); static int nm_setch(struct sc_chinfo *ch); static int nm_init(struct sc_info *); static void nm_intr(void *); /* talk to the card */ static u_int32_t nm_rd(struct sc_info *, int, int); static void nm_wr(struct sc_info *, int, u_int32_t, int); static u_int32_t nm_rdbuf(struct sc_info *, int, int); static void nm_wrbuf(struct sc_info *, int, u_int32_t, int); static u_int32_t badcards[] = { 0x0007103c, 0x008f1028, 0x00dd1014, 0x8005110a, }; #define NUM_BADCARDS (sizeof(badcards) / sizeof(u_int32_t)) /* The actual rates supported by the card. */ static int samplerates[9] = { 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 99999999 }; /* -------------------------------------------------------------------- */ static u_int32_t nm_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps nm_caps = {4000, 48000, nm_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t nm_rd(struct sc_info *sc, int regno, int size) { bus_space_tag_t st = rman_get_bustag(sc->reg); bus_space_handle_t sh = rman_get_bushandle(sc->reg); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void nm_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(sc->reg); bus_space_handle_t sh = rman_get_bushandle(sc->reg); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static u_int32_t nm_rdbuf(struct sc_info *sc, int regno, int size) { bus_space_tag_t st = rman_get_bustag(sc->buf); bus_space_handle_t sh = rman_get_bushandle(sc->buf); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void nm_wrbuf(struct sc_info *sc, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(sc->buf); bus_space_handle_t sh = rman_get_bushandle(sc->buf); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int nm_waitcd(struct sc_info *sc) { int cnt = 10; int fail = 1; while (cnt-- > 0) { if (nm_rd(sc, sc->ac97_status, 2) & sc->ac97_busy) { DELAY(100); } else { fail = 0; break; } } return (fail); } static u_int32_t nm_initcd(kobj_t obj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; nm_wr(sc, 0x6c0, 0x01, 1); #if 0 /* * The following code-line may cause a hang for some chipsets, see * PR 56617. * In case of a bugreport without this line have a look at the PR and * conditionize the code-line based upon the specific version of * the chip. */ nm_wr(sc, 0x6cc, 0x87, 1); #endif nm_wr(sc, 0x6cc, 0x80, 1); nm_wr(sc, 0x6cc, 0x00, 1); return 1; } static int nm_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t x; if (!nm_waitcd(sc)) { x = nm_rd(sc, sc->ac97_base + regno, 2); DELAY(1000); return x; } else { device_printf(sc->dev, "ac97 codec not ready\n"); return -1; } } static int nm_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; int cnt = 3; if (!nm_waitcd(sc)) { while (cnt-- > 0) { nm_wr(sc, sc->ac97_base + regno, data, 2); if (!nm_waitcd(sc)) { DELAY(1000); return 0; } } } device_printf(sc->dev, "ac97 codec not ready\n"); return -1; } static kobj_method_t nm_ac97_methods[] = { KOBJMETHOD(ac97_init, nm_initcd), KOBJMETHOD(ac97_read, nm_rdcd), KOBJMETHOD(ac97_write, nm_wrcd), KOBJMETHOD_END }; AC97_DECLARE(nm_ac97); /* -------------------------------------------------------------------- */ static void nm_ackint(struct sc_info *sc, u_int32_t num) { if (sc->type == NM256AV_PCI_ID) { nm_wr(sc, NM_INT_REG, num << 1, 2); } else if (sc->type == NM256ZX_PCI_ID) { nm_wr(sc, NM_INT_REG, num, 4); } } static int nm_loadcoeff(struct sc_info *sc, int dir, int num) { int ofs, sz, i; u_int32_t addr; addr = (dir == PCMDIR_PLAY)? 0x01c : 0x21c; if (dir == PCMDIR_REC) num += 8; sz = coefficientSizes[num]; ofs = 0; while (num-- > 0) ofs+= coefficientSizes[num]; for (i = 0; i < sz; i++) nm_wrbuf(sc, sc->cbuf + i, coefficients[ofs + i], 1); nm_wr(sc, addr, sc->cbuf, 4); if (dir == PCMDIR_PLAY) sz--; nm_wr(sc, addr + 4, sc->cbuf + sz, 4); return 0; } static int nm_setch(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t base; u_int8_t x; for (x = 0; x < 8; x++) if (ch->spd < (samplerates[x] + samplerates[x + 1]) / 2) break; if (x == 8) return 1; ch->spd = samplerates[x]; nm_loadcoeff(sc, ch->dir, x); x <<= 4; x &= NM_RATE_MASK; if (ch->fmt & AFMT_16BIT) x |= NM_RATE_BITS_16; if (AFMT_CHANNEL(ch->fmt) > 1) x |= NM_RATE_STEREO; base = (ch->dir == PCMDIR_PLAY)? NM_PLAYBACK_REG_OFFSET : NM_RECORD_REG_OFFSET; nm_wr(sc, base + NM_RATE_REG_OFFSET, x, 1); return 0; } /* channel interface */ static void * nmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; u_int32_t chnbuf; chnbuf = (dir == PCMDIR_PLAY)? sc->pbuf : sc->rbuf; ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; ch->active = 0; ch->blksize = 0; ch->wmark = 0; ch->buffer = b; sndbuf_setup(ch->buffer, (u_int8_t *)rman_get_virtual(sc->buf) + chnbuf, NM_BUFFSIZE); if (bootverbose) device_printf(sc->dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? "play" : "rec", sndbuf_getbuf(ch->buffer)); ch->parent = sc; ch->channel = c; ch->dir = dir; return ch; } static int nmchan_free(kobj_t obj, void *data) { return 0; } static int nmchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->fmt = format; return nm_setch(ch); } static u_int32_t nmchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; ch->spd = speed; return nm_setch(ch)? 0 : ch->spd; } static u_int32_t nmchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; ch->blksize = blocksize; return blocksize; } static int nmchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int ssz; if (!PCMTRIG_COMMON(go)) return 0; ssz = (ch->fmt & AFMT_16BIT)? 2 : 1; if (AFMT_CHANNEL(ch->fmt) > 1) ssz <<= 1; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { ch->active = 1; ch->wmark = ch->blksize; nm_wr(sc, NM_PBUFFER_START, sc->pbuf, 4); nm_wr(sc, NM_PBUFFER_END, sc->pbuf + NM_BUFFSIZE - ssz, 4); nm_wr(sc, NM_PBUFFER_CURRP, sc->pbuf, 4); nm_wr(sc, NM_PBUFFER_WMARK, sc->pbuf + ch->wmark, 4); nm_wr(sc, NM_PLAYBACK_ENABLE_REG, NM_PLAYBACK_FREERUN | NM_PLAYBACK_ENABLE_FLAG, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, 0, 2); } else { ch->active = 0; nm_wr(sc, NM_PLAYBACK_ENABLE_REG, 0, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, NM_AUDIO_MUTE_BOTH, 2); } } else { if (go == PCMTRIG_START) { ch->active = 1; ch->wmark = ch->blksize; nm_wr(sc, NM_RECORD_ENABLE_REG, NM_RECORD_FREERUN | NM_RECORD_ENABLE_FLAG, 1); nm_wr(sc, NM_RBUFFER_START, sc->rbuf, 4); nm_wr(sc, NM_RBUFFER_END, sc->rbuf + NM_BUFFSIZE, 4); nm_wr(sc, NM_RBUFFER_CURRP, sc->rbuf, 4); nm_wr(sc, NM_RBUFFER_WMARK, sc->rbuf + ch->wmark, 4); } else { ch->active = 0; nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } } return 0; } static u_int32_t nmchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (ch->dir == PCMDIR_PLAY) return nm_rd(sc, NM_PBUFFER_CURRP, 4) - sc->pbuf; else return nm_rd(sc, NM_RBUFFER_CURRP, 4) - sc->rbuf; } static struct pcmchan_caps * nmchan_getcaps(kobj_t obj, void *data) { return &nm_caps; } static kobj_method_t nmchan_methods[] = { KOBJMETHOD(channel_init, nmchan_init), KOBJMETHOD(channel_free, nmchan_free), KOBJMETHOD(channel_setformat, nmchan_setformat), KOBJMETHOD(channel_setspeed, nmchan_setspeed), KOBJMETHOD(channel_setblocksize, nmchan_setblocksize), KOBJMETHOD(channel_trigger, nmchan_trigger), KOBJMETHOD(channel_getptr, nmchan_getptr), KOBJMETHOD(channel_getcaps, nmchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(nmchan); /* The interrupt handler */ static void nm_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; int status, x; status = nm_rd(sc, NM_INT_REG, sc->irsz); if (status == 0) return; if (status & sc->playint) { status &= ~sc->playint; sc->pch.wmark += sc->pch.blksize; sc->pch.wmark %= NM_BUFFSIZE; nm_wr(sc, NM_PBUFFER_WMARK, sc->pbuf + sc->pch.wmark, 4); nm_ackint(sc, sc->playint); chn_intr(sc->pch.channel); } if (status & sc->recint) { status &= ~sc->recint; sc->rch.wmark += sc->rch.blksize; sc->rch.wmark %= NM_BUFFSIZE; nm_wr(sc, NM_RBUFFER_WMARK, sc->rbuf + sc->rch.wmark, 4); nm_ackint(sc, sc->recint); chn_intr(sc->rch.channel); } if (status & sc->misc1int) { status &= ~sc->misc1int; nm_ackint(sc, sc->misc1int); x = nm_rd(sc, 0x400, 1); nm_wr(sc, 0x400, x | 2, 1); device_printf(sc->dev, "misc int 1\n"); } if (status & sc->misc2int) { status &= ~sc->misc2int; nm_ackint(sc, sc->misc2int); x = nm_rd(sc, 0x400, 1); nm_wr(sc, 0x400, x & ~2, 1); device_printf(sc->dev, "misc int 2\n"); } if (status) { nm_ackint(sc, status); device_printf(sc->dev, "unknown int\n"); } } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int nm_init(struct sc_info *sc) { u_int32_t ofs, i; if (sc->type == NM256AV_PCI_ID) { sc->ac97_base = NM_MIXER_OFFSET; sc->ac97_status = NM_MIXER_STATUS_OFFSET; sc->ac97_busy = NM_MIXER_READY_MASK; sc->buftop = 2560 * 1024; sc->irsz = 2; sc->playint = NM_PLAYBACK_INT; sc->recint = NM_RECORD_INT; sc->misc1int = NM_MISC_INT_1; sc->misc2int = NM_MISC_INT_2; } else if (sc->type == NM256ZX_PCI_ID) { sc->ac97_base = NM_MIXER_OFFSET; sc->ac97_status = NM2_MIXER_STATUS_OFFSET; sc->ac97_busy = NM2_MIXER_READY_MASK; sc->buftop = (nm_rd(sc, 0xa0b, 2)? 6144 : 4096) * 1024; sc->irsz = 4; sc->playint = NM2_PLAYBACK_INT; sc->recint = NM2_RECORD_INT; sc->misc1int = NM2_MISC_INT_1; sc->misc2int = NM2_MISC_INT_2; } else return -1; sc->badintr = 0; ofs = sc->buftop - 0x0400; sc->buftop -= 0x1400; if (bootverbose) device_printf(sc->dev, "buftop is 0x%08x\n", sc->buftop); if ((nm_rdbuf(sc, ofs, 4) & NM_SIG_MASK) == NM_SIGNATURE) { i = nm_rdbuf(sc, ofs + 4, 4); if (i != 0 && i != 0xffffffff) { if (bootverbose) device_printf(sc->dev, "buftop is changed to 0x%08x\n", i); sc->buftop = i; } } sc->cbuf = sc->buftop - NM_MAX_COEFFICIENT; sc->rbuf = sc->cbuf - NM_BUFFSIZE; sc->pbuf = sc->rbuf - NM_BUFFSIZE; sc->acbuf = sc->pbuf - (NM_TOTAL_COEFF_COUNT * 4); nm_wr(sc, 0, 0x11, 1); nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); nm_wr(sc, 0x214, 0, 2); return 0; } static int nm_pci_probe(device_t dev) { struct sc_info *sc = NULL; char *s = NULL; u_int32_t subdev, i; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case NM256AV_PCI_ID: i = 0; while ((i < NUM_BADCARDS) && (badcards[i] != subdev)) i++; /* Try to catch other non-ac97 cards */ if (i == NUM_BADCARDS) { if (!(sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO))) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->regid = PCIR_BAR(1); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to map register space\n"); free(sc, M_DEVBUF); return ENXIO; } /* * My Panasonic CF-M2EV needs resetting device * before checking mixer is present or not. * t.ichinoseki@nifty.com. */ nm_wr(sc, 0, 0x11, 1); /* reset device */ if ((nm_rd(sc, NM_MIXER_PRESENCE, 2) & NM_PRESENCE_MASK) != NM_PRESENCE_VALUE) { i = 0; /* non-ac97 card, but not listed */ DEB(device_printf(dev, "subdev = 0x%x - badcard?\n", subdev)); } bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); free(sc, M_DEVBUF); } if (i == NUM_BADCARDS) s = "NeoMagic 256AV"; DEB(else) DEB(device_printf(dev, "this is a non-ac97 NM256AV, not attaching\n")); break; case NM256ZX_PCI_ID: s = "NeoMagic 256ZX"; break; } if (s) device_set_desc(dev, s); return s? 0 : ENXIO; } static int nm_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); pci_enable_busmaster(dev); sc->bufid = PCIR_BAR(0); sc->buf = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->bufid, RF_ACTIVE); sc->regid = PCIR_BAR(1); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->buf || !sc->reg) { device_printf(dev, "unable to map register space\n"); goto bad; } if (nm_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, nm_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, nm_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } - snprintf(status, SND_STATUSLEN, "at memory 0x%jx, 0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "mem 0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->buf), rman_get_start(sc->reg), - rman_get_start(sc->irq),PCM_KLDSTRING(snd_neomagic)); + rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, sc, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &nmchan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &nmchan_class, sc); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->buf) bus_release_resource(dev, SYS_RES_MEMORY, sc->bufid, sc->buf); if (sc->reg) bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); free(sc, M_DEVBUF); return ENXIO; } static int nm_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_MEMORY, sc->bufid, sc->buf); bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); free(sc, M_DEVBUF); return 0; } static int nm_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* stop playing */ if (sc->pch.active) { nm_wr(sc, NM_PLAYBACK_ENABLE_REG, 0, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, NM_AUDIO_MUTE_BOTH, 2); } /* stop recording */ if (sc->rch.active) { nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } return 0; } static int nm_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* * Reinit audio device. * Don't call nm_init(). It would change buftop if X ran or * is running. This makes playing and recording buffer address * shift but these buffers of channel layer are not changed. * As a result of this inconsistency, periodic noise will be * generated while playing. */ nm_wr(sc, 0, 0x11, 1); nm_wr(sc, 0x214, 0, 2); /* Reinit mixer */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restart playing */ if (sc->pch.active) { nm_wr(sc, NM_PLAYBACK_ENABLE_REG, NM_PLAYBACK_FREERUN | NM_PLAYBACK_ENABLE_FLAG, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, 0, 2); } /* restart recording */ if (sc->rch.active) { nm_wr(sc, NM_RECORD_ENABLE_REG, NM_RECORD_FREERUN | NM_RECORD_ENABLE_FLAG, 1); } return 0; } static device_method_t nm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nm_pci_probe), DEVMETHOD(device_attach, nm_pci_attach), DEVMETHOD(device_detach, nm_pci_detach), DEVMETHOD(device_suspend, nm_pci_suspend), DEVMETHOD(device_resume, nm_pci_resume), { 0, 0 } }; static driver_t nm_driver = { "pcm", nm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_neomagic, pci, nm_driver, 0, 0); MODULE_DEPEND(snd_neomagic, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_neomagic, 1); diff --git a/sys/dev/sound/pci/solo.c b/sys/dev/sound/pci/solo.c index d626da4fbb68..bee79e723696 100644 --- a/sys/dev/sound/pci/solo.c +++ b/sys/dev/sound/pci/solo.c @@ -1,1075 +1,1076 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * * 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include "mixer_if.h" #define SOLO_DEFAULT_BUFSZ 16384 #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ #define ESS18XX_DUPLEX 1 /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED static u_int32_t ess_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_playfmt, 0}; /* * Recording output is byte-swapped */ static u_int32_t ess_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), SND_FORMAT(AFMT_U16_BE, 1, 0), SND_FORMAT(AFMT_U16_BE, 2, 0), 0 }; static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_recfmt, 0}; struct ess_info; struct ess_chinfo { struct ess_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, hwch, stopping; u_int32_t fmt, spd, blksz; }; struct ess_info { struct resource *io, *sb, *vc, *mpu, *gp; /* I/O address for the board */ struct resource *irq; void *ih; bus_dma_tag_t parent_dmat; int simplex_dir, type, dmasz[2]; unsigned int duplex:1, newspeed:1; unsigned int bufsz; struct ess_chinfo pch, rch; struct mtx *lock; }; #define ess_lock(_ess) snd_mtxlock((_ess)->lock) #define ess_unlock(_ess) snd_mtxunlock((_ess)->lock) #define ess_lock_assert(_ess) snd_mtxassert((_ess)->lock) static int ess_rd(struct ess_info *sc, int reg); static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); static int ess_dspready(struct ess_info *sc); static int ess_cmd(struct ess_info *sc, u_char val); static int ess_cmd1(struct ess_info *sc, u_char cmd, int val); static int ess_get_byte(struct ess_info *sc); static void ess_setmixer(struct ess_info *sc, u_int port, u_int value); static int ess_getmixer(struct ess_info *sc, u_int port); static int ess_reset_dsp(struct ess_info *sc); static int ess_write(struct ess_info *sc, u_char reg, int val); static int ess_read(struct ess_info *sc, u_char reg); static void ess_intr(void *arg); static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len); static int ess_start(struct ess_chinfo *ch); static int ess_stop(struct ess_chinfo *ch); static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir); static int ess_dmapos(struct ess_info *sc, int ch); static int ess_dmatrigger(struct ess_info *sc, int ch, int go); /* * Common code for the midi and pcm functions * * ess_cmd write a single byte to the CMD port. * ess_cmd1 write a CMD + 1 byte arg * ess_cmd2 write a CMD + 2 byte arg * ess_get_byte returns a single byte from the DSP data port * * ess_write is actually ess_cmd1 * ess_read access ext. regs via ess_cmd(0xc0, reg) followed by ess_get_byte */ static int port_rd(struct resource *port, int regno, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void port_wr(struct resource *port, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static int ess_rd(struct ess_info *sc, int reg) { return port_rd(sc->sb, reg, 1); } static void ess_wr(struct ess_info *sc, int reg, u_int8_t val) { port_wr(sc->sb, reg, val, 1); } static int ess_dspready(struct ess_info *sc) { return ((ess_rd(sc, SBDSP_STATUS) & 0x80) == 0); } static int ess_dspwr(struct ess_info *sc, u_char val) { int i; for (i = 0; i < 1000; i++) { if (ess_dspready(sc)) { ess_wr(sc, SBDSP_CMD, val); return 1; } if (i > 10) DELAY((i > 100)? 1000 : 10); } printf("ess_dspwr(0x%02x) timed out.\n", val); return 0; } static int ess_cmd(struct ess_info *sc, u_char val) { DEB(printf("ess_cmd: %x\n", val)); return ess_dspwr(sc, val); } static int ess_cmd1(struct ess_info *sc, u_char cmd, int val) { DEB(printf("ess_cmd1: %x, %x\n", cmd, val)); if (ess_dspwr(sc, cmd)) { return ess_dspwr(sc, val & 0xff); } else return 0; } static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); return val; } static int ess_get_byte(struct ess_info *sc) { int i; for (i = 1000; i > 0; i--) { if (ess_rd(sc, 0xc) & 0x40) return ess_rd(sc, DSP_READ); else DELAY(20); } return -1; } static int ess_write(struct ess_info *sc, u_char reg, int val) { return ess_cmd1(sc, reg, val); } static int ess_read(struct ess_info *sc, u_char reg) { return (ess_cmd(sc, 0xc0) && ess_cmd(sc, reg))? ess_get_byte(sc) : -1; } static int ess_reset_dsp(struct ess_info *sc) { DEB(printf("ess_reset_dsp\n")); ess_wr(sc, SBDSP_RST, 3); DELAY(100); ess_wr(sc, SBDSP_RST, 0); if (ess_get_byte(sc) != 0xAA) { DEB(printf("ess_reset_dsp failed\n")); /* rman_get_start(d->io_base))); */ return ENXIO; /* Sorry */ } ess_cmd(sc, 0xc6); return 0; } static void ess_intr(void *arg) { struct ess_info *sc = (struct ess_info *)arg; int src, pirq = 0, rirq = 0; ess_lock(sc); src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; if (src == 0) { ess_unlock(sc); return; } if (sc->duplex) { pirq = (src & sc->pch.hwch)? 1 : 0; rirq = (src & sc->rch.hwch)? 1 : 0; } else { if (sc->simplex_dir == PCMDIR_PLAY) pirq = 1; if (sc->simplex_dir == PCMDIR_REC) rirq = 1; if (!pirq && !rirq) printf("solo: IRQ neither playback nor rec!\n"); } DEB(printf("ess_intr: pirq:%d rirq:%d\n",pirq,rirq)); if (pirq) { if (sc->pch.stopping) { ess_dmatrigger(sc, sc->pch.hwch, 0); sc->pch.stopping = 0; if (sc->pch.hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } ess_unlock(sc); chn_intr(sc->pch.channel); ess_lock(sc); } if (rirq) { if (sc->rch.stopping) { ess_dmatrigger(sc, sc->rch.hwch, 0); sc->rch.stopping = 0; /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } ess_unlock(sc); chn_intr(sc->rch.channel); ess_lock(sc); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); ess_unlock(sc); } /* utility functions for ESS */ static u_int8_t ess_calcspeed8(int *spd) { int speed = *spd; u_int32_t t; if (speed > 22000) { t = (795500 + speed / 2) / speed; speed = (795500 + t / 2) / t; t = (256 - t) | 0x80; } else { t = (397700 + speed / 2) / speed; speed = (397700 + t / 2) / t; t = 128 - t; } *spd = speed; return t & 0x000000ff; } static u_int8_t ess_calcspeed9(int *spd) { int speed, s0, s1, use0; u_int8_t t0, t1; /* rate = source / (256 - divisor) */ /* divisor = 256 - (source / rate) */ speed = *spd; t0 = 128 - (793800 / speed); s0 = 793800 / (128 - t0); t1 = 128 - (768000 / speed); s1 = 768000 / (128 - t1); t1 |= 0x80; use0 = (ABS(speed - s0) < ABS(speed - s1))? 1 : 0; *spd = use0? s0 : s1; return use0? t0 : t1; } static u_int8_t ess_calcfilter(int spd) { int cutoff; /* cutoff = 7160000 / (256 - divisor) */ /* divisor = 256 - (7160000 / cutoff) */ cutoff = (spd * 9 * 82) / 20; return (256 - (7160000 / cutoff)); } static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len) { int play = (dir == PCMDIR_PLAY)? 1 : 0; int b16 = (fmt & AFMT_16BIT)? 1 : 0; int stereo = (AFMT_CHANNEL(fmt) > 1)? 1 : 0; int unsign = (!(fmt & AFMT_SIGNED))? 1 : 0; u_int8_t spdval, fmtval; DEB(printf("ess_setupch\n")); spdval = (sc->newspeed)? ess_calcspeed9(&spd) : ess_calcspeed8(&spd); sc->simplex_dir = play ? PCMDIR_PLAY : PCMDIR_REC ; if (ch == 1) { KASSERT((dir == PCMDIR_PLAY) || (dir == PCMDIR_REC), ("ess_setupch: dir1 bad")); len = -len; /* transfer length low */ ess_write(sc, 0xa4, len & 0x00ff); /* transfer length high */ ess_write(sc, 0xa5, (len & 0xff00) >> 8); /* autoinit, dma dir */ ess_write(sc, 0xb8, 0x04 | (play? 0x00 : 0x0a)); /* mono/stereo */ ess_write(sc, 0xa8, (ess_read(sc, 0xa8) & ~0x03) | (stereo? 0x01 : 0x02)); /* demand mode, 4 bytes/xfer */ ess_write(sc, 0xb9, 0x02); /* sample rate */ ess_write(sc, 0xa1, spdval); /* filter cutoff */ ess_write(sc, 0xa2, ess_calcfilter(spd)); /* setup dac/adc */ /* if (play) ess_write(sc, 0xb6, unsign? 0x80 : 0x00); */ /* mono, b16: signed, load signal */ /* ess_write(sc, 0xb7, 0x51 | (unsign? 0x00 : 0x20)); */ /* setup fifo */ ess_write(sc, 0xb7, 0x91 | (unsign? 0x00 : 0x20) | (b16? 0x04 : 0x00) | (stereo? 0x08 : 0x40)); /* irq control */ ess_write(sc, 0xb1, (ess_read(sc, 0xb1) & 0x0f) | 0x50); /* drq control */ ess_write(sc, 0xb2, (ess_read(sc, 0xb2) & 0x0f) | 0x50); } else if (ch == 2) { KASSERT(dir == PCMDIR_PLAY, ("ess_setupch: dir2 bad")); len >>= 1; len = -len; /* transfer length low */ ess_setmixer(sc, 0x74, len & 0x00ff); /* transfer length high */ ess_setmixer(sc, 0x76, (len & 0xff00) >> 8); /* autoinit, 4 bytes/req */ ess_setmixer(sc, 0x78, 0x10); fmtval = b16 | (stereo << 1) | ((!unsign) << 2); /* enable irq, set format */ ess_setmixer(sc, 0x7a, 0x40 | fmtval); if (sc->newspeed) { /* sample rate */ ess_setmixer(sc, 0x70, spdval); /* filter cutoff */ ess_setmixer(sc, 0x72, ess_calcfilter(spd)); } } return 0; } static int ess_start(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_start\n");); ess_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->blksz); ch->stopping = 0; if (ch->hwch == 1) { ess_write(sc, 0xb8, ess_read(sc, 0xb8) | 0x01); if (ch->dir == PCMDIR_PLAY) { #if 0 DELAY(100000); /* 100 ms */ #endif ess_cmd(sc, 0xd1); } } else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) | 0x03); return 0; } static int ess_stop(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_stop\n")); ch->stopping = 1; if (ch->hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x04); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x10); DEB(printf("done with stop\n")); return 0; } /* -------------------------------------------------------------------- */ /* channel interface for ESS18xx */ static void * esschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct ess_info *sc = devinfo; struct ess_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; DEB(printf("esschan_init\n")); ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; return ch; } static int esschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t esschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; ch->spd = speed; if (sc->newspeed) ess_calcspeed9(&ch->spd); else ess_calcspeed8(&ch->spd); return ch->spd; } static u_int32_t esschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct ess_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int esschan_trigger(kobj_t obj, void *data, int go) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; DEB(printf("esschan_trigger: %d\n",go)); ess_lock(sc); switch (go) { case PCMTRIG_START: ess_dmasetup(sc, ch->hwch, sndbuf_getbufaddr(ch->buffer), sndbuf_getsize(ch->buffer), ch->dir); ess_dmatrigger(sc, ch->hwch, 1); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } ess_unlock(sc); return 0; } static u_int32_t esschan_getptr(kobj_t obj, void *data) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; u_int32_t ret; ess_lock(sc); ret = ess_dmapos(sc, ch->hwch); ess_unlock(sc); return ret; } static struct pcmchan_caps * esschan_getcaps(kobj_t obj, void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } static kobj_method_t esschan_methods[] = { KOBJMETHOD(channel_init, esschan_init), KOBJMETHOD(channel_setformat, esschan_setformat), KOBJMETHOD(channel_setspeed, esschan_setspeed), KOBJMETHOD(channel_setblocksize, esschan_setblocksize), KOBJMETHOD(channel_trigger, esschan_trigger), KOBJMETHOD(channel_getptr, esschan_getptr), KOBJMETHOD(channel_getcaps, esschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(esschan); /************************************************************/ static int essmix_init(struct snd_mixer *m) { struct ess_info *sc = mix_getdevinfo(m); mix_setrecdevs(m, SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_IMIX); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME | SOUND_MASK_LINE1); ess_setmixer(sc, 0, 0); /* reset */ return 0; } static int essmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ess_info *sc = mix_getdevinfo(m); int preg = 0, rreg = 0, l, r; l = (left * 15) / 100; r = (right * 15) / 100; switch (dev) { case SOUND_MIXER_SYNTH: preg = 0x36; rreg = 0x6b; break; case SOUND_MIXER_PCM: preg = 0x14; rreg = 0x7c; break; case SOUND_MIXER_LINE: preg = 0x3e; rreg = 0x6e; break; case SOUND_MIXER_MIC: preg = 0x1a; rreg = 0x68; break; case SOUND_MIXER_LINE1: preg = 0x3a; rreg = 0x6c; break; case SOUND_MIXER_CD: preg = 0x38; rreg = 0x6a; break; case SOUND_MIXER_VOLUME: l = left? (left * 63) / 100 : 64; r = right? (right * 63) / 100 : 64; ess_setmixer(sc, 0x60, l); ess_setmixer(sc, 0x62, r); left = (l == 64)? 0 : (l * 100) / 63; right = (r == 64)? 0 : (r * 100) / 63; return left | (right << 8); } if (preg) ess_setmixer(sc, preg, (l << 4) | r); if (rreg) ess_setmixer(sc, rreg, (l << 4) | r); left = (l * 100) / 15; right = (r * 100) / 15; return left | (right << 8); } static u_int32_t essmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct ess_info *sc = mix_getdevinfo(m); u_char recdev; switch (src) { case SOUND_MASK_CD: recdev = 0x02; break; case SOUND_MASK_LINE: recdev = 0x06; break; case SOUND_MASK_IMIX: recdev = 0x05; break; case SOUND_MASK_MIC: default: recdev = 0x00; src = SOUND_MASK_MIC; break; } ess_setmixer(sc, 0x1c, recdev); return src; } static kobj_method_t solomixer_methods[] = { KOBJMETHOD(mixer_init, essmix_init), KOBJMETHOD(mixer_set, essmix_set), KOBJMETHOD(mixer_setrecsrc, essmix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(solomixer); /************************************************************/ static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir) { KASSERT(ch == 1 || ch == 2, ("bad ch")); sc->dmasz[ch - 1] = cnt; if (ch == 1) { port_wr(sc->vc, 0x8, 0xc4, 1); /* command */ port_wr(sc->vc, 0xd, 0xff, 1); /* reset */ port_wr(sc->vc, 0xf, 0x01, 1); /* mask */ port_wr(sc->vc, 0xb, dir == PCMDIR_PLAY? 0x58 : 0x54, 1); /* mode */ port_wr(sc->vc, 0x0, base, 4); port_wr(sc->vc, 0x4, cnt - 1, 2); } else if (ch == 2) { port_wr(sc->io, 0x6, 0x08, 1); /* autoinit */ port_wr(sc->io, 0x0, base, 4); port_wr(sc->io, 0x4, cnt, 2); } return 0; } static int ess_dmapos(struct ess_info *sc, int ch) { int p = 0, i = 0, j = 0; KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) { /* * During recording, this register is known to give back * garbage if it's not quiescent while being read. That's * why we spl, stop the DMA, and try over and over until * adjacent reads are "close", in the right order and not * bigger than is otherwise possible. */ ess_dmatrigger(sc, ch, 0); DELAY(20); do { DELAY(10); if (j > 1) printf("DMA count reg bogus: %04x & %04x\n", i, p); i = port_rd(sc->vc, 0x4, 2) + 1; p = port_rd(sc->vc, 0x4, 2) + 1; } while ((p > sc->dmasz[ch - 1] || i < p || (p - i) > 0x8) && j++ < 1000); ess_dmatrigger(sc, ch, 1); } else if (ch == 2) p = port_rd(sc->io, 0x4, 2); return sc->dmasz[ch - 1] - p; } static int ess_dmatrigger(struct ess_info *sc, int ch, int go) { KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) port_wr(sc->vc, 0xf, go? 0x00 : 0x01, 1); /* mask */ else if (ch == 2) port_wr(sc->io, 0x6, 0x08 | (go? 0x02 : 0x00), 1); /* autoinit */ return 0; } static void ess_release_resources(struct ess_info *sc, device_t dev) { if (sc->irq) { if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = NULL; } if (sc->io) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->io); sc->io = NULL; } if (sc->sb) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(1), sc->sb); sc->sb = NULL; } if (sc->vc) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(2), sc->vc); sc->vc = NULL; } if (sc->mpu) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(3), sc->mpu); sc->mpu = NULL; } if (sc->gp) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(4), sc->gp); sc->gp = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } free(sc, M_DEVBUF); } static int ess_alloc_resources(struct ess_info *sc, device_t dev) { int rid; rid = PCIR_BAR(0); sc->io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(1); sc->sb = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(2); sc->vc = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(3); sc->mpu = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(4); sc->gp = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_solo softc"); return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; } static int ess_probe(device_t dev) { char *s = NULL; u_int32_t subdev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case 0x1969125d: if (subdev == 0x8888125d) s = "ESS Solo-1E"; else if (subdev == 0x1818125d) s = "ESS Solo-1"; else s = "ESS Solo-1 (unknown vendor)"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } #define ESS_PCI_LEGACYCONTROL 0x40 #define ESS_PCI_CONFIG 0x50 #define ESS_PCI_DDMACONTROL 0x60 static int ess_suspend(device_t dev) { return 0; } static int ess_resume(device_t dev) { uint16_t ddma; struct ess_info *sc = pcm_getdevinfo(dev); ess_lock(sc); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); if (ess_reset_dsp(sc)) { ess_unlock(sc); goto no; } ess_unlock(sc); if (mixer_reinit(dev)) goto no; ess_lock(sc); if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ ess_unlock(sc); return 0; no: return EIO; } static int ess_attach(device_t dev) { struct ess_info *sc; char status[SND_STATUSLEN]; u_int16_t ddma; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); pci_enable_busmaster(dev); if (ess_alloc_resources(sc, dev)) goto no; sc->bufsz = pcm_getbuffersize(dev, 4096, SOLO_DEFAULT_BUFSZ, 65536); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ #ifdef ESS18XX_DUPLEX sc->duplex = 1; #else sc->duplex = 0; #endif #ifdef ESS18XX_NEWSPEED sc->newspeed = 1; #else sc->newspeed = 0; #endif if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ess_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto no; } if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); #if 0 if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/65536, /*boundary*/0, #endif if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (ess_reset_dsp(sc)) goto no; if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); if (mixer_init(dev, &solomixer_class, sc)) goto no; - snprintf(status, SND_STATUSLEN, "at io 0x%jx,0x%jx,0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "port 0x%jx,0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), - rman_get_start(sc->irq),PCM_KLDSTRING(snd_solo)); + rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, sc, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &esschan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &esschan_class, sc); pcm_setstatus(dev, status); return 0; no: ess_release_resources(sc, dev); return ENXIO; } static int ess_detach(device_t dev) { int r; struct ess_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); ess_release_resources(sc, dev); return 0; } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), DEVMETHOD(device_detach, ess_detach), DEVMETHOD(device_resume, ess_resume), DEVMETHOD(device_suspend, ess_suspend), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_solo, pci, ess_driver, 0, 0); MODULE_DEPEND(snd_solo, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_solo, 1); diff --git a/sys/dev/sound/pci/t4dwave.c b/sys/dev/sound/pci/t4dwave.c index 250b04426482..653e610febbe 100644 --- a/sys/dev/sound/pci/t4dwave.c +++ b/sys/dev/sound/pci/t4dwave.c @@ -1,1036 +1,1037 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include /* -------------------------------------------------------------------- */ #define TDX_PCI_ID 0x20001023 #define TNX_PCI_ID 0x20011023 #define ALI_PCI_ID 0x545110b9 #define SPA_PCI_ID 0x70181039 #define TR_DEFAULT_BUFSZ 0x1000 /* For ALi M5451 the DMA transfer size appears to be fixed to 64k. */ #define ALI_BUFSZ 0x10000 #define TR_BUFALGN 0x8 #define TR_TIMEOUT_CDC 0xffff #define TR_MAXHWCH 64 #define ALI_MAXHWCH 32 #define TR_MAXPLAYCH 4 #define ALI_MAXPLAYCH 1 /* * Though, it's not clearly documented in the 4DWAVE datasheet, the * DX and NX chips can't handle DMA addresses located above 1GB as the * LBA (loop begin address) register which holds the DMA base address * is 32-bit, but the two MSBs are used for other purposes. */ #define TR_MAXADDR ((1U << 30) - 1) #define ALI_MAXADDR ((1U << 31) - 1) struct tr_info; /* channel registers */ struct tr_chinfo { u_int32_t cso, alpha, fms, fmc, ec; u_int32_t lba; u_int32_t eso, delta; u_int32_t rvol, cvol; u_int32_t gvsel, pan, vol, ctrl; u_int32_t active:1, was_active:1; int index, bufhalf; struct snd_dbuf *buffer; struct pcm_channel *channel; struct tr_info *parent; }; struct tr_rchinfo { u_int32_t delta; u_int32_t active:1, was_active:1; struct snd_dbuf *buffer; struct pcm_channel *channel; struct tr_info *parent; }; /* device private data */ struct tr_info { u_int32_t type; u_int32_t rev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; struct mtx *lock; u_int32_t hwchns; u_int32_t playchns; unsigned int bufsz; struct tr_chinfo chinfo[TR_MAXPLAYCH]; struct tr_rchinfo recchinfo; }; /* -------------------------------------------------------------------- */ static u_int32_t tr_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps tr_reccaps = {4000, 48000, tr_recfmt, 0}; static u_int32_t tr_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps tr_playcaps = {4000, 48000, tr_playfmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t tr_rd(struct tr_info *tr, int regno, int size) { switch(size) { case 1: return bus_space_read_1(tr->st, tr->sh, regno); case 2: return bus_space_read_2(tr->st, tr->sh, regno); case 4: return bus_space_read_4(tr->st, tr->sh, regno); default: return 0xffffffff; } } static void tr_wr(struct tr_info *tr, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(tr->st, tr->sh, regno, data); break; case 2: bus_space_write_2(tr->st, tr->sh, regno, data); break; case 4: bus_space_write_4(tr->st, tr->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int tr_rdcd(kobj_t obj, void *devinfo, int regno) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { case SPA_PCI_ID: treg=SPA_REG_CODECRD; trw=SPA_CDC_RWSTAT; break; case ALI_PCI_ID: if (tr->rev > 0x01) treg=TDX_REG_CODECWR; else treg=TDX_REG_CODECRD; trw=TDX_CDC_RWSTAT; break; case TDX_PCI_ID: treg=TDX_REG_CODECRD; trw=TDX_CDC_RWSTAT; break; case TNX_PCI_ID: treg=(regno & 0x100)? TNX_REG_CODEC2RD : TNX_REG_CODEC1RD; trw=TNX_CDC_RWSTAT; break; default: printf("!!! tr_rdcd defaulted !!!\n"); return -1; } i = j = 0; regno &= 0x7f; snd_mtxlock(tr->lock); if (tr->type == ALI_PCI_ID) { u_int32_t chk1, chk2; j = trw; for (i = TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j = tr_rd(tr, treg, 4); if (i > 0) { chk1 = tr_rd(tr, 0xc8, 4); chk2 = tr_rd(tr, 0xc8, 4); for (i = TR_TIMEOUT_CDC; (i > 0) && (chk1 == chk2); i--) chk2 = tr_rd(tr, 0xc8, 4); } } if (tr->type != ALI_PCI_ID || i > 0) { tr_wr(tr, treg, regno | trw, 4); j=trw; for (i=TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j=tr_rd(tr, treg, 4); } snd_mtxunlock(tr->lock); if (i == 0) printf("codec timeout during read of register %x\n", regno); return (j >> TR_CDC_DATA) & 0xffff; } static int tr_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { case SPA_PCI_ID: treg=SPA_REG_CODECWR; trw=SPA_CDC_RWSTAT; break; case ALI_PCI_ID: case TDX_PCI_ID: treg=TDX_REG_CODECWR; trw=TDX_CDC_RWSTAT; break; case TNX_PCI_ID: treg=TNX_REG_CODECWR; trw=TNX_CDC_RWSTAT | ((regno & 0x100)? TNX_CDC_SEC : 0); break; default: printf("!!! tr_wrcd defaulted !!!"); return -1; } i = 0; regno &= 0x7f; #if 0 printf("tr_wrcd: reg %x was %x", regno, tr_rdcd(devinfo, regno)); #endif j=trw; snd_mtxlock(tr->lock); if (tr->type == ALI_PCI_ID) { j = trw; for (i = TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j = tr_rd(tr, treg, 4); if (i > 0) { u_int32_t chk1, chk2; chk1 = tr_rd(tr, 0xc8, 4); chk2 = tr_rd(tr, 0xc8, 4); for (i = TR_TIMEOUT_CDC; (i > 0) && (chk1 == chk2); i--) chk2 = tr_rd(tr, 0xc8, 4); } } if (tr->type != ALI_PCI_ID || i > 0) { for (i=TR_TIMEOUT_CDC; (i>0) && (j & trw); i--) j=tr_rd(tr, treg, 4); if (tr->type == ALI_PCI_ID && tr->rev > 0x01) trw |= 0x0100; tr_wr(tr, treg, (data << TR_CDC_DATA) | regno | trw, 4); } #if 0 printf(" - wrote %x, now %x\n", data, tr_rdcd(devinfo, regno)); #endif snd_mtxunlock(tr->lock); if (i==0) printf("codec timeout writing %x, data %x\n", regno, data); return (i > 0)? 0 : -1; } static kobj_method_t tr_ac97_methods[] = { KOBJMETHOD(ac97_read, tr_rdcd), KOBJMETHOD(ac97_write, tr_wrcd), KOBJMETHOD_END }; AC97_DECLARE(tr_ac97); /* -------------------------------------------------------------------- */ /* playback channel interrupts */ #if 0 static u_int32_t tr_testint(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; return tr_rd(tr, bank? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 4) & (1 << chan); } #endif static void tr_clrint(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 1 << chan, 4); } static void tr_enaint(struct tr_chinfo *ch, int enable) { struct tr_info *tr = ch->parent; u_int32_t i, reg; int bank, chan; snd_mtxlock(tr->lock); bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; reg = bank? TR_REG_INTENB : TR_REG_INTENA; i = tr_rd(tr, reg, 4); i &= ~(1 << chan); i |= (enable? 1 : 0) << chan; tr_clrint(ch); tr_wr(tr, reg, i, 4); snd_mtxunlock(tr->lock); } /* playback channels */ static void tr_selch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int i; i = tr_rd(tr, TR_REG_CIR, 4); i &= ~TR_CIR_MASK; i |= ch->index & 0x3f; tr_wr(tr, TR_REG_CIR, i, 4); } static void tr_startch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_STARTB : TR_REG_STARTA, 1 << chan, 4); } static void tr_stopch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_STOPB : TR_REG_STOPA, 1 << chan, 4); } static void tr_wrch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; u_int32_t cr[TR_CHN_REGS], i; ch->gvsel &= 0x00000001; ch->fmc &= 0x00000003; ch->fms &= 0x0000000f; ch->ctrl &= 0x0000000f; ch->pan &= 0x0000007f; ch->rvol &= 0x0000007f; ch->cvol &= 0x0000007f; ch->vol &= 0x000000ff; ch->ec &= 0x00000fff; ch->alpha &= 0x00000fff; ch->delta &= 0x0000ffff; if (tr->type == ALI_PCI_ID) ch->lba &= ALI_MAXADDR; else ch->lba &= TR_MAXADDR; cr[1]=ch->lba; cr[3]=(ch->fmc<<14) | (ch->rvol<<7) | (ch->cvol); cr[4]=(ch->gvsel<<31) | (ch->pan<<24) | (ch->vol<<16) | (ch->ctrl<<12) | (ch->ec); switch (tr->type) { case SPA_PCI_ID: case ALI_PCI_ID: case TDX_PCI_ID: ch->cso &= 0x0000ffff; ch->eso &= 0x0000ffff; cr[0]=(ch->cso<<16) | (ch->alpha<<4) | (ch->fms); cr[2]=(ch->eso<<16) | (ch->delta); break; case TNX_PCI_ID: ch->cso &= 0x00ffffff; ch->eso &= 0x00ffffff; cr[0]=((ch->delta & 0xff)<<24) | (ch->cso); cr[2]=((ch->delta>>8)<<24) | (ch->eso); cr[3]|=(ch->alpha<<20) | (ch->fms<<16) | (ch->fmc<<14); break; } snd_mtxlock(tr->lock); tr_selch(ch); for (i=0; ilock); } static void tr_rdch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; u_int32_t cr[5], i; snd_mtxlock(tr->lock); tr_selch(ch); for (i=0; i<5; i++) cr[i]=tr_rd(tr, TR_REG_CHNBASE+(i<<2), 4); snd_mtxunlock(tr->lock); if (tr->type == ALI_PCI_ID) ch->lba=(cr[1] & ALI_MAXADDR); else ch->lba=(cr[1] & TR_MAXADDR); ch->fmc= (cr[3] & 0x0000c000) >> 14; ch->rvol= (cr[3] & 0x00003f80) >> 7; ch->cvol= (cr[3] & 0x0000007f); ch->gvsel= (cr[4] & 0x80000000) >> 31; ch->pan= (cr[4] & 0x7f000000) >> 24; ch->vol= (cr[4] & 0x00ff0000) >> 16; ch->ctrl= (cr[4] & 0x0000f000) >> 12; ch->ec= (cr[4] & 0x00000fff); switch(tr->type) { case SPA_PCI_ID: case ALI_PCI_ID: case TDX_PCI_ID: ch->cso= (cr[0] & 0xffff0000) >> 16; ch->alpha= (cr[0] & 0x0000fff0) >> 4; ch->fms= (cr[0] & 0x0000000f); ch->eso= (cr[2] & 0xffff0000) >> 16; ch->delta= (cr[2] & 0x0000ffff); break; case TNX_PCI_ID: ch->cso= (cr[0] & 0x00ffffff); ch->eso= (cr[2] & 0x00ffffff); ch->delta= ((cr[2] & 0xff000000) >> 16) | ((cr[0] & 0xff000000) >> 24); ch->alpha= (cr[3] & 0xfff00000) >> 20; ch->fms= (cr[3] & 0x000f0000) >> 16; break; } } static u_int32_t tr_fmttobits(u_int32_t fmt) { u_int32_t bits; bits = 0; bits |= (fmt & AFMT_SIGNED)? 0x2 : 0; bits |= (AFMT_CHANNEL(fmt) > 1)? 0x4 : 0; bits |= (fmt & AFMT_16BIT)? 0x8 : 0; return bits; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * trpchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_chinfo *ch; KASSERT(dir == PCMDIR_PLAY, ("trpchan_init: bad direction")); ch = &tr->chinfo[tr->playchns]; ch->index = tr->playchns++; ch->buffer = b; ch->parent = tr; ch->channel = c; if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; } static int trpchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct tr_chinfo *ch = data; ch->ctrl = tr_fmttobits(format) | 0x01; return 0; } static u_int32_t trpchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct tr_chinfo *ch = data; ch->delta = (speed << 12) / 48000; return (ch->delta * 48000) >> 12; } static u_int32_t trpchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct tr_chinfo *ch = data; sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static int trpchan_trigger(kobj_t obj, void *data, int go) { struct tr_chinfo *ch = data; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { ch->fmc = 3; ch->fms = 0; ch->ec = 0; ch->alpha = 0; ch->lba = sndbuf_getbufaddr(ch->buffer); ch->cso = 0; ch->eso = (sndbuf_getsize(ch->buffer) / sndbuf_getalign(ch->buffer)) - 1; ch->rvol = ch->cvol = 0x7f; ch->gvsel = 0; ch->pan = 0; ch->vol = 0; ch->bufhalf = 0; tr_wrch(ch); tr_enaint(ch, 1); tr_startch(ch); ch->active = 1; } else { tr_stopch(ch); ch->active = 0; } return 0; } static u_int32_t trpchan_getptr(kobj_t obj, void *data) { struct tr_chinfo *ch = data; tr_rdch(ch); return ch->cso * sndbuf_getalign(ch->buffer); } static struct pcmchan_caps * trpchan_getcaps(kobj_t obj, void *data) { return &tr_playcaps; } static kobj_method_t trpchan_methods[] = { KOBJMETHOD(channel_init, trpchan_init), KOBJMETHOD(channel_setformat, trpchan_setformat), KOBJMETHOD(channel_setspeed, trpchan_setspeed), KOBJMETHOD(channel_setblocksize, trpchan_setblocksize), KOBJMETHOD(channel_trigger, trpchan_trigger), KOBJMETHOD(channel_getptr, trpchan_getptr), KOBJMETHOD(channel_getcaps, trpchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(trpchan); /* -------------------------------------------------------------------- */ /* rec channel interface */ static void * trrchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("trrchan_init: bad direction")); ch = &tr->recchinfo; ch->buffer = b; ch->parent = tr; ch->channel = c; if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; } static int trrchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t i, bits; bits = tr_fmttobits(format); /* set # of samples between interrupts */ i = (sndbuf_runsz(ch->buffer) >> ((bits & 0x08)? 1 : 0)) - 1; tr_wr(tr, TR_REG_SBBL, i | (i << 16), 4); /* set sample format */ i = 0x18 | (bits << 4); tr_wr(tr, TR_REG_SBCTRL, i, 1); return 0; } static u_int32_t trrchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; /* setup speed */ ch->delta = (48000 << 12) / speed; tr_wr(tr, TR_REG_SBDELTA, ch->delta, 2); /* return closest possible speed */ return (48000 << 12) / ch->delta; } static u_int32_t trrchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct tr_rchinfo *ch = data; sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static int trrchan_trigger(kobj_t obj, void *data, int go) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t i; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { /* set up dma mode regs */ tr_wr(tr, TR_REG_DMAR15, 0, 1); i = tr_rd(tr, TR_REG_DMAR11, 1) & 0x03; tr_wr(tr, TR_REG_DMAR11, i | 0x54, 1); /* set up base address */ tr_wr(tr, TR_REG_DMAR0, sndbuf_getbufaddr(ch->buffer), 4); /* set up buffer size */ i = tr_rd(tr, TR_REG_DMAR4, 4) & ~0x00ffffff; tr_wr(tr, TR_REG_DMAR4, i | (sndbuf_runsz(ch->buffer) - 1), 4); /* start */ tr_wr(tr, TR_REG_SBCTRL, tr_rd(tr, TR_REG_SBCTRL, 1) | 1, 1); ch->active = 1; } else { tr_wr(tr, TR_REG_SBCTRL, tr_rd(tr, TR_REG_SBCTRL, 1) & ~7, 1); ch->active = 0; } /* return 0 if ok */ return 0; } static u_int32_t trrchan_getptr(kobj_t obj, void *data) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; /* return current byte offset of channel */ return tr_rd(tr, TR_REG_DMAR0, 4) - sndbuf_getbufaddr(ch->buffer); } static struct pcmchan_caps * trrchan_getcaps(kobj_t obj, void *data) { return &tr_reccaps; } static kobj_method_t trrchan_methods[] = { KOBJMETHOD(channel_init, trrchan_init), KOBJMETHOD(channel_setformat, trrchan_setformat), KOBJMETHOD(channel_setspeed, trrchan_setspeed), KOBJMETHOD(channel_setblocksize, trrchan_setblocksize), KOBJMETHOD(channel_trigger, trrchan_trigger), KOBJMETHOD(channel_getptr, trrchan_getptr), KOBJMETHOD(channel_getcaps, trrchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(trrchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void tr_intr(void *p) { struct tr_info *tr = (struct tr_info *)p; struct tr_chinfo *ch; u_int32_t active, mask, bufhalf, chnum, intsrc; int tmp; intsrc = tr_rd(tr, TR_REG_MISCINT, 4); if (intsrc & TR_INT_ADDR) { chnum = 0; while (chnum < tr->hwchns) { mask = 0x00000001; active = tr_rd(tr, (chnum < 32)? TR_REG_ADDRINTA : TR_REG_ADDRINTB, 4); bufhalf = tr_rd(tr, (chnum < 32)? TR_REG_CSPF_A : TR_REG_CSPF_B, 4); if (active) { do { if (active & mask) { tmp = (bufhalf & mask)? 1 : 0; if (chnum < tr->playchns) { ch = &tr->chinfo[chnum]; /* printf("%d @ %d, ", chnum, trpchan_getptr(NULL, ch)); */ if (ch->bufhalf != tmp) { chn_intr(ch->channel); ch->bufhalf = tmp; } } } chnum++; mask <<= 1; } while (chnum & 31); } else chnum += 32; tr_wr(tr, (chnum <= 32)? TR_REG_ADDRINTA : TR_REG_ADDRINTB, active, 4); } } if (intsrc & TR_INT_SB) { chn_intr(tr->recchinfo.channel); tr_rd(tr, TR_REG_SBR9, 1); tr_rd(tr, TR_REG_SBR10, 1); } } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int tr_init(struct tr_info *tr) { switch (tr->type) { case SPA_PCI_ID: tr_wr(tr, SPA_REG_GPIO, 0, 4); tr_wr(tr, SPA_REG_CODECST, SPA_RST_OFF, 4); break; case TDX_PCI_ID: tr_wr(tr, TDX_REG_CODECST, TDX_CDC_ON, 4); break; case TNX_PCI_ID: tr_wr(tr, TNX_REG_CODECST, TNX_CDC_ON, 4); break; } tr_wr(tr, TR_REG_CIR, TR_CIR_MIDENA | TR_CIR_ADDRENA, 4); return 0; } static int tr_pci_probe(device_t dev) { switch (pci_get_devid(dev)) { case SPA_PCI_ID: device_set_desc(dev, "SiS 7018"); return BUS_PROBE_DEFAULT; case ALI_PCI_ID: device_set_desc(dev, "Acer Labs M5451"); return BUS_PROBE_DEFAULT; case TDX_PCI_ID: device_set_desc(dev, "Trident 4DWave DX"); return BUS_PROBE_DEFAULT; case TNX_PCI_ID: device_set_desc(dev, "Trident 4DWave NX"); return BUS_PROBE_DEFAULT; } return ENXIO; } static int tr_pci_attach(device_t dev) { struct tr_info *tr; struct ac97_info *codec = NULL; bus_addr_t lowaddr; int i, dacn; char status[SND_STATUSLEN]; tr = malloc(sizeof(*tr), M_DEVBUF, M_WAITOK | M_ZERO); tr->type = pci_get_devid(dev); tr->rev = pci_get_revid(dev); tr->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_t4dwave softc"); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &i) == 0) { if (i < 1) dacn = 1; else if (i > TR_MAXPLAYCH) dacn = TR_MAXPLAYCH; else dacn = i; } else { switch (tr->type) { case ALI_PCI_ID: dacn = ALI_MAXPLAYCH; break; default: dacn = TR_MAXPLAYCH; break; } } pci_enable_busmaster(dev); tr->regid = PCIR_BAR(0); tr->regtype = SYS_RES_IOPORT; tr->reg = bus_alloc_resource_any(dev, tr->regtype, &tr->regid, RF_ACTIVE); if (tr->reg) { tr->st = rman_get_bustag(tr->reg); tr->sh = rman_get_bushandle(tr->reg); } else { device_printf(dev, "unable to map register space\n"); goto bad; } if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } tr->playchns = 0; codec = AC97_CREATE(dev, tr, tr_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; tr->irqid = 0; tr->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &tr->irqid, RF_ACTIVE | RF_SHAREABLE); if (!tr->irq || snd_setup_intr(dev, tr->irq, 0, tr_intr, tr, &tr->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (tr->type == ALI_PCI_ID) { /* * The M5451 generates 31 bit of DMA and in order to do * 32-bit DMA, the 31st bit can be set via its accompanying * ISA bridge. Note that we can't predict whether bus_dma(9) * will actually supply us with a 32-bit buffer and even when * using a low address of BUS_SPACE_MAXADDR_32BIT for both * we might end up with the play buffer being in the 32-bit * range while the record buffer isn't or vice versa. So we * don't enabling the 31st bit. */ lowaddr = ALI_MAXADDR; tr->hwchns = ALI_MAXHWCH; tr->bufsz = ALI_BUFSZ; } else { lowaddr = TR_MAXADDR; tr->hwchns = TR_MAXHWCH; tr->bufsz = pcm_getbuffersize(dev, 4096, TR_DEFAULT_BUFSZ, 65536); } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/TR_BUFALGN, /*boundary*/0, /*lowaddr*/lowaddr, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/tr->bufsz, /*nsegments*/1, /*maxsegz*/tr->bufsz, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &tr->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } - snprintf(status, 64, "at io 0x%jx irq %jd %s", - rman_get_start(tr->reg), rman_get_start(tr->irq),PCM_KLDSTRING(snd_t4dwave)); + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", + rman_get_start(tr->reg), rman_get_start(tr->irq), + device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, tr, dacn, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &trrchan_class, tr); for (i = 0; i < dacn; i++) pcm_addchan(dev, PCMDIR_PLAY, &trpchan_class, tr); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (tr->reg) bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); if (tr->ih) bus_teardown_intr(dev, tr->irq, tr->ih); if (tr->irq) bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); if (tr->parent_dmat) bus_dma_tag_destroy(tr->parent_dmat); if (tr->lock) snd_mtxfree(tr->lock); free(tr, M_DEVBUF); return ENXIO; } static int tr_pci_detach(device_t dev) { int r; struct tr_info *tr; r = pcm_unregister(dev); if (r) return r; tr = pcm_getdevinfo(dev); bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); bus_teardown_intr(dev, tr->irq, tr->ih); bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); bus_dma_tag_destroy(tr->parent_dmat); snd_mtxfree(tr->lock); free(tr, M_DEVBUF); return 0; } static int tr_pci_suspend(device_t dev) { int i; struct tr_info *tr; tr = pcm_getdevinfo(dev); for (i = 0; i < tr->playchns; i++) { tr->chinfo[i].was_active = tr->chinfo[i].active; if (tr->chinfo[i].active) { trpchan_trigger(NULL, &tr->chinfo[i], PCMTRIG_STOP); } } tr->recchinfo.was_active = tr->recchinfo.active; if (tr->recchinfo.active) { trrchan_trigger(NULL, &tr->recchinfo, PCMTRIG_STOP); } return 0; } static int tr_pci_resume(device_t dev) { int i; struct tr_info *tr; tr = pcm_getdevinfo(dev); if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to initialize the mixer\n"); return ENXIO; } for (i = 0; i < tr->playchns; i++) { if (tr->chinfo[i].was_active) { trpchan_trigger(NULL, &tr->chinfo[i], PCMTRIG_START); } } if (tr->recchinfo.was_active) { trrchan_trigger(NULL, &tr->recchinfo, PCMTRIG_START); } return 0; } static device_method_t tr_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tr_pci_probe), DEVMETHOD(device_attach, tr_pci_attach), DEVMETHOD(device_detach, tr_pci_detach), DEVMETHOD(device_suspend, tr_pci_suspend), DEVMETHOD(device_resume, tr_pci_resume), { 0, 0 } }; static driver_t tr_driver = { "pcm", tr_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_t4dwave, pci, tr_driver, 0, 0); MODULE_DEPEND(snd_t4dwave, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_t4dwave, 1); diff --git a/sys/dev/sound/pci/via8233.c b/sys/dev/sound/pci/via8233.c index 517e80fadb4f..9f3b312e4365 100644 --- a/sys/dev/sound/pci/via8233.c +++ b/sys/dev/sound/pci/via8233.c @@ -1,1443 +1,1443 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002 Orion Hodson * Portions of this code derived from via82c686.c: * Copyright (c) 2000 David Jones * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Credits due to: * * Grzybowski Rafal, Russell Davies, Mark Handley, Daniel O'Connor for * comments, machine time, testing patches, and patience. VIA for * providing specs. ALSA for helpful comments and some register poke * ordering. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #define VIA8233_PCI_ID 0x30591106 #define VIA8233_REV_ID_8233PRE 0x10 #define VIA8233_REV_ID_8233C 0x20 #define VIA8233_REV_ID_8233 0x30 #define VIA8233_REV_ID_8233A 0x40 #define VIA8233_REV_ID_8235 0x50 #define VIA8233_REV_ID_8237 0x60 #define VIA8233_REV_ID_8251 0x70 #define SEGS_PER_CHAN 2 /* Segments per channel */ #define NDXSCHANS 4 /* No of DXS channels */ #define NMSGDCHANS 1 /* No of multichannel SGD */ #define NWRCHANS 1 /* No of write channels */ #define NCHANS (NWRCHANS + NDXSCHANS + NMSGDCHANS) #define NSEGS NCHANS * SEGS_PER_CHAN /* Segments in SGD table */ #define VIA_SEGS_MIN 2 #define VIA_SEGS_MAX 64 #define VIA_SEGS_DEFAULT 2 #define VIA_BLK_MIN 32 #define VIA_BLK_ALIGN (~(VIA_BLK_MIN - 1)) #define VIA_DEFAULT_BUFSZ 0x1000 /* we rely on this struct being packed to 64 bits */ struct via_dma_op { volatile uint32_t ptr; volatile uint32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 #define VIA_DMAOP_COUNT(x) ((x)&0x00FFFFFF) }; struct via_info; struct via_chinfo { struct via_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; int dir, rbase, active; unsigned int blksz, blkcnt; unsigned int ptr, prevptr; }; struct via_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regid, irqid; void *ih; struct ac97_info *codec; unsigned int bufsz, blkcnt; int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; uint16_t codec_caps; uint16_t n_dxs_registered; int play_num, rec_num; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; static uint32_t via_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; static __inline int via_chan_active(struct via_info *via) { int i, ret = 0; if (via == NULL) return (0); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) ret += via->pch[i].active; for (i = 0; i < NWRCHANS; i++) ret += via->rch[i].active; return (ret); } static int sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; uint32_t r; int err, new_en; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); snd_mtxlock(via->lock); r = pci_read_config(dev, VIA_PCI_SPDIF, 1); snd_mtxunlock(via->lock); new_en = (r & VIA_SPDIF_EN) ? 1 : 0; err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) return (err); if (new_en < 0 || new_en > 1) return (EINVAL); if (new_en) r |= VIA_SPDIF_EN; else r &= ~VIA_SPDIF_EN; snd_mtxlock(via->lock); pci_write_config(dev, VIA_PCI_SPDIF, r, 1); snd_mtxunlock(via->lock); return (0); } static int sysctl_via8233_dxs_src(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; int err, val; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); snd_mtxlock(via->lock); val = via->dxs_src; snd_mtxunlock(via->lock); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); snd_mtxlock(via->lock); via->dxs_src = val; snd_mtxunlock(via->lock); return (0); } static int sysctl_via_polling(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; int err, val; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); if (via == NULL) return (EINVAL); snd_mtxlock(via->lock); val = via->polling; snd_mtxunlock(via->lock); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); snd_mtxlock(via->lock); if (val != via->polling) { if (via_chan_active(via) != 0) err = EBUSY; else if (val == 0) via->polling = 0; else via->polling = 1; } snd_mtxunlock(via->lock); return (err); } static void via_init_sysctls(device_t dev) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_via8233_spdif_enable, "I", "Enable S/PDIF output on primary playback channel"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "dxs_src", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_via8233_dxs_src, "I", "Enable VIA DXS Sample Rate Converter"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_via_polling, "I", "Enable polling mode"); } static __inline uint32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(via->st, via->sh, regno)); case 2: return (bus_space_read_2(via->st, via->sh, regno)); case 4: return (bus_space_read_4(via->st, via->sh, regno)); default: return (0xFFFFFFFF); } } static __inline void via_wr(struct via_info *via, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(via->st, via->sh, regno, data); break; case 2: bus_space_write_2(via->st, via->sh, regno, data); break; case 4: bus_space_write_4(via->st, via->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* Codec interface */ static int via_waitready_codec(struct via_info *via) { int i; /* poll until codec not busy */ for (i = 0; i < 1000; i++) { if ((via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_BUSY) == 0) return (0); DELAY(1); } device_printf(via->dev, "%s: codec busy\n", __func__); return (1); } static int via_waitvalid_codec(struct via_info *via) { int i; /* poll until codec valid */ for (i = 0; i < 1000; i++) { if (via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_CODEC00_VALID) return (0); DELAY(1); } device_printf(via->dev, "%s: codec invalid\n", __func__); return (1); } static int via_write_codec(kobj_t obj, void *addr, int reg, uint32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return (-1); via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_INDEX(reg) | VIA_AC97_DATA(val), 4); return (0); } static int via_read_codec(kobj_t obj, void *addr, int reg) { struct via_info *via = addr; if (via_waitready_codec(via)) return (-1); via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); if (via_waitready_codec(via)) return (-1); if (via_waitvalid_codec(via)) return (-1); return (via_rd(via, VIA_AC97_CONTROL, 2)); } static kobj_method_t via_ac97_methods[] = { KOBJMETHOD(ac97_read, via_read_codec), KOBJMETHOD(ac97_write, via_write_codec), KOBJMETHOD_END }; AC97_DECLARE(via_ac97); /* -------------------------------------------------------------------- */ static int via_buildsgdt(struct via_chinfo *ch) { uint32_t phys_addr, flag; int i; phys_addr = sndbuf_getbufaddr(ch->buffer); for (i = 0; i < ch->blkcnt; i++) { flag = (i == ch->blkcnt - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ch->sgd_table[i].ptr = phys_addr + (i * ch->blksz); ch->sgd_table[i].flags = flag | ch->blksz; } return (0); } /* -------------------------------------------------------------------- */ /* Format setting functions */ static int via8233wr_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t f = WR_FORMAT_STOP_INDEX; if (AFMT_CHANNEL(format) > 1) f |= WR_FORMAT_STEREO; if (format & AFMT_S16_LE) f |= WR_FORMAT_16BIT; snd_mtxlock(via->lock); via_wr(via, VIA_WR0_FORMAT, f, 4); snd_mtxunlock(via->lock); return (0); } static int via8233dxs_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); v = via_rd(via, r, 4); v &= ~(VIA8233_DXS_RATEFMT_STEREO | VIA8233_DXS_RATEFMT_16BIT); if (AFMT_CHANNEL(format) > 1) v |= VIA8233_DXS_RATEFMT_STEREO; if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); snd_mtxunlock(via->lock); return (0); } static int via8233msgd_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t s = 0xff000000; uint8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; if (AFMT_CHANNEL(format) > 1) { v |= MC_SGD_CHANNELS(2); s |= SLOT3(1) | SLOT4(2); } else { v |= MC_SGD_CHANNELS(1); s |= SLOT3(1) | SLOT4(1); } snd_mtxlock(via->lock); via_wr(via, VIA_MC_SLOT_SELECT, s, 4); via_wr(via, VIA_MC_SGD_FORMAT, v, 1); snd_mtxunlock(via->lock); return (0); } /* -------------------------------------------------------------------- */ /* Speed setting functions */ static uint32_t via8233wr_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) return (ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed)); return (48000); } static uint32_t via8233dxs_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); v = via_rd(via, r, 4) & ~VIA8233_DXS_RATEFMT_48K; /* Careful to avoid overflow (divide by 48 per vt8233c docs) */ v |= VIA8233_DXS_RATEFMT_48K * (speed / 48) / (48000 / 48); via_wr(via, r, v, 4); snd_mtxunlock(via->lock); return (speed); } static uint32_t via8233msgd_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) return (ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed)); return (48000); } /* -------------------------------------------------------------------- */ /* Format probing functions */ static struct pcmchan_caps * via8233wr_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) return (&via_vracaps); return (&via_caps); } static struct pcmchan_caps * via8233dxs_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* * Controlled by onboard registers * * Apparently, few boards can do DXS sample rate * conversion. */ if (via->dxs_src) return (&via_vracaps); return (&via_caps); } static struct pcmchan_caps * via8233msgd_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) return (&via_vracaps); return (&via_caps); } /* -------------------------------------------------------------------- */ /* Common functions */ static int via8233chan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; blksz &= VIA_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN)) blksz = sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN; if (blksz < VIA_BLK_MIN) blksz = VIA_BLK_MIN; if (blkcnt > VIA_SEGS_MAX) blkcnt = VIA_SEGS_MAX; if (blkcnt < VIA_SEGS_MIN) blkcnt = VIA_SEGS_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= VIA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= VIA_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(via->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); ch->blkcnt = sndbuf_getblkcnt(ch->buffer); return (0); } static uint32_t via8233chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; via8233chan_setfragments(obj, data, blksz, via->blkcnt); return (ch->blksz); } static uint32_t via8233chan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t v, index, count, ptr; snd_mtxlock(via->lock); if (via->polling != 0) { ptr = ch->ptr; snd_mtxunlock(via->lock); } else { v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); snd_mtxunlock(via->lock); index = v >> 24; /* Last completed buffer */ count = v & 0x00ffffff; /* Bytes remaining */ ptr = (index + 1) * ch->blksz - count; ptr %= ch->blkcnt * ch->blksz; /* Wrap to available space */ } return (ptr); } static void via8233chan_reset(struct via_info *via, struct via_chinfo *ch) { via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via_wr(via, ch->rbase + VIA_RP_CONTROL, 0x00, 1); via_wr(via, ch->rbase + VIA_RP_STATUS, SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); } /* -------------------------------------------------------------------- */ /* Channel initialization functions */ static void via8233chan_sgdinit(struct via_info *via, struct via_chinfo *ch, int chnum) { ch->sgd_table = &via->sgd_table[chnum * VIA_SEGS_MAX]; ch->sgd_addr = via->sgd_addr + chnum * VIA_SEGS_MAX * sizeof(struct via_dma_op); } static void* via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->rec_num++; ch = &via->rch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->blkcnt = via->blkcnt; ch->rbase = VIA_WR_BASE(num); via_wr(via, ch->rbase + VIA_WR_RP_SGD_FORMAT, WR_FIFO_ENABLE, 1); snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void* via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->play_num++; ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->blkcnt = via->blkcnt; /* * All cards apparently support DXS3, but not other DXS * channels. We therefore want to align first DXS channel to * DXS3. */ ch->rbase = VIA_DXS_BASE(NDXSCHANS - 1 - via->n_dxs_registered); via->n_dxs_registered++; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void* via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->play_num++; ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->rbase = VIA_MC_SGD_STATUS; ch->blkcnt = via->blkcnt; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void via8233chan_mute(struct via_info *via, struct via_chinfo *ch, int muted) { if (BASE_IS_VIA_DXS_REG(ch->rbase)) { int r; muted = (muted) ? VIA8233_DXS_MUTE : 0; via_wr(via, ch->rbase + VIA8233_RP_DXS_LVOL, muted, 1); via_wr(via, ch->rbase + VIA8233_RP_DXS_RVOL, muted, 1); r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & VIA8233_DXS_MUTE; if (r != muted) device_printf(via->dev, "%s: failed to set dxs volume " "(dxs base 0x%02x).\n", __func__, ch->rbase); } } static __inline int via_poll_channel(struct via_chinfo *ch) { struct via_info *via; uint32_t sz, delta; uint32_t v, index, count; int ptr; if (ch == NULL || ch->channel == NULL || ch->active == 0) return (0); via = ch->parent; sz = ch->blksz * ch->blkcnt; v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); index = v >> 24; count = v & 0x00ffffff; ptr = ((index + 1) * ch->blksz) - count; ptr %= sz; ptr &= ~(ch->blksz - 1); ch->ptr = ptr; delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } static void via_poll_callback(void *arg) { struct via_info *via = arg; uint32_t ptrigger = 0, rtrigger = 0; int i; if (via == NULL) return; snd_mtxlock(via->lock); if (via->polling == 0 || via_chan_active(via) == 0) { snd_mtxunlock(via->lock); return; } for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) ptrigger |= (via_poll_channel(&via->pch[i]) != 0) ? (1 << i) : 0; for (i = 0; i < NWRCHANS; i++) rtrigger |= (via_poll_channel(&via->rch[i]) != 0) ? (1 << i) : 0; /* XXX */ callout_reset(&via->poll_timer, 1/*via->poll_ticks*/, via_poll_callback, via); snd_mtxunlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (ptrigger & (1 << i)) chn_intr(via->pch[i].channel); } for (i = 0; i < NWRCHANS; i++) { if (rtrigger & (1 << i)) chn_intr(via->rch[i].channel); } } static int via_poll_ticks(struct via_info *via) { struct via_chinfo *ch; int i; int ret = hz; int pollticks; for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { ch = &via->pch[i]; if (ch->channel == NULL || ch->active == 0) continue; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getalign(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks < ret) ret = pollticks; } for (i = 0; i < NWRCHANS; i++) { ch = &via->rch[i]; if (ch->channel == NULL || ch->active == 0) continue; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getalign(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks < ret) ret = pollticks; } return (ret); } static int via8233chan_trigger(kobj_t obj, void* data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int pollticks; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(via->lock); switch(go) { case PCMTRIG_START: via_buildsgdt(ch); via8233chan_mute(via, ch, 0); via_wr(via, ch->rbase + VIA_RP_TABLE_PTR, ch->sgd_addr, 4); if (via->polling != 0) { ch->ptr = 0; ch->prevptr = 0; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getalign(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (via_chan_active(via) == 0 || pollticks < via->poll_ticks) { if (bootverbose) { if (via_chan_active(via) == 0) printf("%s: pollticks=%d\n", __func__, pollticks); else printf("%s: " "pollticks %d -> %d\n", __func__, via->poll_ticks, pollticks); } via->poll_ticks = pollticks; callout_reset(&via->poll_timer, 1, via_poll_callback, via); } } via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | ((via->polling == 0) ? (SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG) : 0), 1); ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via8233chan_mute(via, ch, 1); via8233chan_reset(via, ch); ch->active = 0; if (via->polling != 0) { if (via_chan_active(via) == 0) { callout_stop(&via->poll_timer); via->poll_ticks = 1; } else { pollticks = via_poll_ticks(via); if (pollticks > via->poll_ticks) { if (bootverbose) printf("%s: pollticks " "%d -> %d\n", __func__, via->poll_ticks, pollticks); via->poll_ticks = pollticks; callout_reset(&via->poll_timer, 1, via_poll_callback, via); } } } break; default: break; } snd_mtxunlock(via->lock); return (0); } static kobj_method_t via8233wr_methods[] = { KOBJMETHOD(channel_init, via8233wr_init), KOBJMETHOD(channel_setformat, via8233wr_setformat), KOBJMETHOD(channel_setspeed, via8233wr_setspeed), KOBJMETHOD(channel_getcaps, via8233wr_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), KOBJMETHOD_END }; CHANNEL_DECLARE(via8233wr); static kobj_method_t via8233dxs_methods[] = { KOBJMETHOD(channel_init, via8233dxs_init), KOBJMETHOD(channel_setformat, via8233dxs_setformat), KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), KOBJMETHOD_END }; CHANNEL_DECLARE(via8233dxs); static kobj_method_t via8233msgd_methods[] = { KOBJMETHOD(channel_init, via8233msgd_init), KOBJMETHOD(channel_setformat, via8233msgd_setformat), KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), KOBJMETHOD_END }; CHANNEL_DECLARE(via8233msgd); /* -------------------------------------------------------------------- */ static void via_intr(void *p) { struct via_info *via = p; uint32_t ptrigger = 0, rtrigger = 0; int i, reg, stat; snd_mtxlock(via->lock); if (via->polling != 0) { snd_mtxunlock(via->lock); return; } /* Poll playback channels */ for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (via->pch[i].channel == NULL || via->pch[i].active == 0) continue; reg = via->pch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || !(stat & SGD_STATUS_ACTIVE))) via_wr(via, via->pch[i].rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); ptrigger |= 1 << i; } } /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { if (via->rch[i].channel == NULL || via->rch[i].active == 0) continue; reg = via->rch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || !(stat & SGD_STATUS_ACTIVE))) via_wr(via, via->rch[i].rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); rtrigger |= 1 << i; } } snd_mtxunlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (ptrigger & (1 << i)) chn_intr(via->pch[i].channel); } for (i = 0; i < NWRCHANS; i++) { if (rtrigger & (1 << i)) chn_intr(via->rch[i].channel); } } /* * Probe and attach the card */ static int via_probe(device_t dev) { switch(pci_get_devid(dev)) { case VIA8233_PCI_ID: switch(pci_get_revid(dev)) { case VIA8233_REV_ID_8233PRE: device_set_desc(dev, "VIA VT8233 (pre)"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233C: device_set_desc(dev, "VIA VT8233C"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233: device_set_desc(dev, "VIA VT8233"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233A: device_set_desc(dev, "VIA VT8233A"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8235: device_set_desc(dev, "VIA VT8235"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8251: device_set_desc(dev, "VIA VT8251"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct via_info *via = (struct via_info *)p; via->sgd_addr = bds->ds_addr; } static int via_chip_init(device_t dev) { uint32_t data, cnt; /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); if ((data & VIA_PCI_ACLINK_C00_READY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ /* Assert low */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); /* Wait T_rst_low */ DELAY(100); /* Assert high */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); /* Wait T_rst2clk */ DELAY(5); /* Assert low */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); } else { /* Warm reset */ /* Force no sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); DELAY(100); /* Sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); /* Wait T_sync_high */ DELAY(5); /* Force no sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); /* Wait T_sync2clk */ DELAY(5); } /* Power everything up */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_DESIRED, 1); /* Wait for codec to become ready (largest reported delay 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); if (data & VIA_PCI_ACLINK_C00_READY) return (0); DELAY(5000); } device_printf(dev, "primary codec not ready (cnt = 0x%02x)\n", cnt); return (ENXIO); } static int via_attach(device_t dev) { struct via_info *via = NULL; char status[SND_STATUSLEN]; int i, via_dxs_disabled, via_dxs_src, via_dxs_chnum, via_sgd_chnum; int nsegs; uint32_t revid; via = malloc(sizeof *via, M_DEVBUF, M_WAITOK | M_ZERO); via->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_via8233 softc"); via->dev = dev; callout_init(&via->poll_timer, 1); via->poll_ticks = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) via->polling = 1; else via->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); if (!via->reg) { device_printf(dev, "cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= VIA_BLK_ALIGN; if (i < VIA_BLK_MIN) i = VIA_BLK_MIN; via->blkcnt = via->bufsz / i; i = 0; while (via->blkcnt >> i) i++; via->blkcnt = 1 << (i - 1); if (via->blkcnt < VIA_SEGS_MIN) via->blkcnt = VIA_SEGS_MIN; else if (via->blkcnt > VIA_SEGS_MAX) via->blkcnt = VIA_SEGS_MAX; } else via->blkcnt = VIA_SEGS_DEFAULT; revid = pci_get_revid(dev); /* * VIA8251 lost its interrupt after DMA EOL, and need * a gentle spank on its face within interrupt handler. */ if (revid == VIA8233_REV_ID_8251) via->dma_eol_wake = 1; else via->dma_eol_wake = 0; /* * Decide whether DXS had to be disabled or not */ if (revid == VIA8233_REV_ID_8233A) { /* * DXS channel is disabled. Reports from multiple users * that it plays at half-speed. Do not see this behaviour * on available 8233C or when emulating 8233A register set * on 8233C (either with or without ac97 VRA). */ via_dxs_disabled = 1; } else if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_disabled", &via_dxs_disabled) == 0) via_dxs_disabled = (via_dxs_disabled > 0) ? 1 : 0; else via_dxs_disabled = 0; if (via_dxs_disabled) { via_dxs_chnum = 0; via_sgd_chnum = 1; } else { if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_channels", &via_dxs_chnum) != 0) via_dxs_chnum = NDXSCHANS; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_sgd_channels", &via_sgd_chnum) != 0) via_sgd_chnum = NMSGDCHANS; } if (via_dxs_chnum > NDXSCHANS) via_dxs_chnum = NDXSCHANS; else if (via_dxs_chnum < 0) via_dxs_chnum = 0; if (via_sgd_chnum > NMSGDCHANS) via_sgd_chnum = NMSGDCHANS; else if (via_sgd_chnum < 0) via_sgd_chnum = 0; if (via_dxs_chnum + via_sgd_chnum < 1) { /* Minimalist ? */ via_dxs_chnum = 1; via_sgd_chnum = 0; } if (via_dxs_chnum > 0 && resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_src", &via_dxs_src) == 0) via->dxs_src = (via_dxs_src > 0) ? 1 : 0; else via->dxs_src = 0; nsegs = (via_dxs_chnum + via_sgd_chnum + NWRCHANS) * VIA_SEGS_MAX; /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* * DMA tag for SGD table. The 686 uses scatter/gather DMA and * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/nsegs * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) goto bad; if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, nsegs * sizeof(struct via_dma_op), dma_cb, via, 0)) goto bad; if (via_chip_init(dev)) goto bad; via->codec = AC97_CREATE(dev, via, via_ac97); if (!via->codec) goto bad; mixer_init(dev, ac97_getmixerclass(), via->codec); via->codec_caps = ac97_getextcaps(via->codec); /* Try to set VRA without generating an error, VRM not reqrd yet */ if (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { uint16_t ext = ac97_getextmode(via->codec); ext |= (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); ext &= ~AC97_EXTCAP_DRA; ac97_setextmode(via->codec, ext); } - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(via->reg), rman_get_start(via->irq), - PCM_KLDSTRING(snd_via8233)); + device_get_nameunit(device_get_parent(dev))); /* Register */ if (pcm_register(dev, via, via_dxs_chnum + via_sgd_chnum, NWRCHANS)) goto bad; for (i = 0; i < via_dxs_chnum; i++) pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); for (i = 0; i < via_sgd_chnum; i++) pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); for (i = 0; i < NWRCHANS; i++) pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); if (via_dxs_chnum > 0) via_init_sysctls(dev); device_printf(dev, "\n", (via_dxs_chnum > 0) ? "En" : "Dis", (via->dxs_src) ? "(SRC)" : "", via_dxs_chnum, via_sgd_chnum, NWRCHANS); pcm_setstatus(dev, status); return (0); bad: if (via->codec) ac97_destroy(via->codec); if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_addr) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return (ENXIO); } static int via_detach(device_t dev) { int r; struct via_info *via; r = pcm_unregister(dev); if (r) return (r); via = pcm_getdevinfo(dev); if (via != NULL && (via->play_num != 0 || via->rec_num != 0)) { snd_mtxlock(via->lock); via->polling = 0; callout_stop(&via->poll_timer); snd_mtxunlock(via->lock); callout_drain(&via->poll_timer); } bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); return (0); } static device_method_t via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), DEVMETHOD(device_detach, via_detach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_via8233, pci, via_driver, 0, 0); MODULE_DEPEND(snd_via8233, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_via8233, 1); diff --git a/sys/dev/sound/pci/via82c686.c b/sys/dev/sound/pci/via82c686.c index 266441a938c6..44f846b684d7 100644 --- a/sys/dev/sound/pci/via82c686.c +++ b/sys/dev/sound/pci/via82c686.c @@ -1,645 +1,645 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 David Jones * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #define VIA_PCI_ID 0x30581106 #define NSEGS 4 /* Number of segments in SGD table */ #define SEGS_PER_CHAN (NSEGS/2) #define TIMEOUT 50 #define VIA_DEFAULT_BUFSZ 0x1000 #undef DEB #define DEB(x) /* we rely on this struct being packed to 64 bits */ struct via_dma_op { u_int32_t ptr; u_int32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 #define VIA_DMAOP_COUNT(x) ((x)&0x00FFFFFF) }; struct via_info; struct via_chinfo { struct via_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; int dir, blksz; int base, count, mode, ctrl; }; struct via_info { bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regid, irqid; void *ih; struct ac97_info *codec; unsigned int bufsz; struct via_chinfo pch, rch; struct via_dma_op *sgd_table; u_int16_t codec_caps; struct mtx *lock; }; static u_int32_t via_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps via_vracaps = {4000, 48000, via_fmt, 0}; static struct pcmchan_caps via_caps = {48000, 48000, via_fmt, 0}; static __inline u_int32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: return bus_space_read_1(via->st, via->sh, regno); case 2: return bus_space_read_2(via->st, via->sh, regno); case 4: return bus_space_read_4(via->st, via->sh, regno); default: return 0xFFFFFFFF; } } static __inline void via_wr(struct via_info *via, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(via->st, via->sh, regno, data); break; case 2: bus_space_write_2(via->st, via->sh, regno, data); break; case 4: bus_space_write_4(via->st, via->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* Codec interface */ static int via_waitready_codec(struct via_info *via) { int i; /* poll until codec not busy */ for (i = 0; (i < TIMEOUT) && (via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_BUSY); i++) DELAY(1); if (i >= TIMEOUT) { printf("via: codec busy\n"); return 1; } return 0; } static int via_waitvalid_codec(struct via_info *via) { int i; /* poll until codec valid */ for (i = 0; (i < TIMEOUT) && !(via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_PRIVALID); i++) DELAY(1); if (i >= TIMEOUT) { printf("via: codec invalid\n"); return 1; } return 0; } static int via_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return -1; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_INDEX(reg) | val, 4); return 0; } static int via_read_codec(kobj_t obj, void *addr, int reg) { struct via_info *via = addr; if (via_waitready_codec(via)) return -1; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_READ | VIA_CODEC_INDEX(reg),4); if (via_waitready_codec(via)) return -1; if (via_waitvalid_codec(via)) return -1; return via_rd(via, VIA_CODEC_CTL, 2); } static kobj_method_t via_ac97_methods[] = { KOBJMETHOD(ac97_read, via_read_codec), KOBJMETHOD(ac97_write, via_write_codec), KOBJMETHOD_END }; AC97_DECLARE(via_ac97); /* -------------------------------------------------------------------- */ static int via_buildsgdt(struct via_chinfo *ch) { u_int32_t phys_addr, flag; int i, segs, seg_size; /* * Build the scatter/gather DMA (SGD) table. * There are four slots in the table: two for play, two for record. * This creates two half-buffers, one of which is playing; the other * is feeding. */ seg_size = ch->blksz; segs = sndbuf_getsize(ch->buffer) / seg_size; phys_addr = sndbuf_getbufaddr(ch->buffer); for (i = 0; i < segs; i++) { flag = (i == segs - 1)? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ch->sgd_table[i].ptr = phys_addr + (i * seg_size); ch->sgd_table[i].flags = flag | seg_size; } return 0; } /* channel interface */ static void * viachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; snd_mtxlock(via->lock); if (dir == PCMDIR_PLAY) { ch = &via->pch; ch->base = VIA_PLAY_DMAOPS_BASE; ch->count = VIA_PLAY_DMAOPS_COUNT; ch->ctrl = VIA_PLAY_CONTROL; ch->mode = VIA_PLAY_MODE; ch->sgd_addr = via->sgd_addr; ch->sgd_table = &via->sgd_table[0]; } else { ch = &via->rch; ch->base = VIA_RECORD_DMAOPS_BASE; ch->count = VIA_RECORD_DMAOPS_COUNT; ch->ctrl = VIA_RECORD_CONTROL; ch->mode = VIA_RECORD_MODE; ch->sgd_addr = via->sgd_addr + sizeof(struct via_dma_op) * SEGS_PER_CHAN; ch->sgd_table = &via->sgd_table[SEGS_PER_CHAN]; } ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return NULL; return ch; } static int viachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int mode, mode_set; mode_set = 0; if (AFMT_CHANNEL(format) > 1) mode_set |= VIA_RPMODE_STEREO; if (format & AFMT_S16_LE) mode_set |= VIA_RPMODE_16BIT; DEB(printf("set format: dir = %d, format=%x\n", ch->dir, format)); snd_mtxlock(via->lock); mode = via_rd(via, ch->mode, 1); mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO); mode |= mode_set; via_wr(via, ch->mode, mode, 1); snd_mtxunlock(via->lock); return 0; } static u_int32_t viachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int reg; /* * Basic AC'97 defines a 48 kHz sample rate only. For other rates, * upsampling is required. * * The VT82C686A does not perform upsampling, and neither do we. * If the codec supports variable-rate audio (i.e. does the upsampling * itself), then negotiate the rate with the codec. Otherwise, * return 48 kHz cuz that's all you got. */ if (via->codec_caps & AC97_EXTCAP_VRA) { reg = (ch->dir == PCMDIR_PLAY)? AC97_REGEXT_FDACRATE : AC97_REGEXT_LADCRATE; return ac97_setrate(via->codec, reg, speed); } else return 48000; } static u_int32_t viachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct via_chinfo *ch = data; ch->blksz = blocksize; sndbuf_resize(ch->buffer, SEGS_PER_CHAN, ch->blksz); return ch->blksz; } static int viachan_trigger(kobj_t obj, void *data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; bus_addr_t sgd_addr = ch->sgd_addr; if (!PCMTRIG_COMMON(go)) return 0; DEB(printf("ado located at va=%p pa=%x\n", ch->sgd_table, sgd_addr)); snd_mtxlock(via->lock); if (go == PCMTRIG_START) { via_buildsgdt(ch); via_wr(via, ch->base, sgd_addr, 4); via_wr(via, ch->ctrl, VIA_RPCTRL_START, 1); } else via_wr(via, ch->ctrl, VIA_RPCTRL_TERMINATE, 1); snd_mtxunlock(via->lock); DEB(printf("viachan_trigger: go=%d\n", go)); return 0; } static u_int32_t viachan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; bus_addr_t sgd_addr = ch->sgd_addr; u_int32_t ptr, base, base1, len, seg; snd_mtxlock(via->lock); base1 = via_rd(via, ch->base, 4); len = via_rd(via, ch->count, 4); base = via_rd(via, ch->base, 4); if (base != base1) /* Avoid race hazard */ len = via_rd(via, ch->count, 4); snd_mtxunlock(via->lock); DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base)); /* Base points to SGD segment to do, one past current */ /* Determine how many segments have been done */ seg = (base - sgd_addr) / sizeof(struct via_dma_op); if (seg == 0) seg = SEGS_PER_CHAN; /* Now work out offset: seg less count */ ptr = (seg * sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN) - len; if (ch->dir == PCMDIR_REC) { /* DMA appears to operate on memory 'lines' of 32 bytes */ /* so don't return any part line - it isn't in RAM yet */ ptr = ptr & ~0x1f; } DEB(printf("return ptr=%u\n", ptr)); return ptr; } static struct pcmchan_caps * viachan_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; return (via->codec_caps & AC97_EXTCAP_VRA)? &via_vracaps : &via_caps; } static kobj_method_t viachan_methods[] = { KOBJMETHOD(channel_init, viachan_init), KOBJMETHOD(channel_setformat, viachan_setformat), KOBJMETHOD(channel_setspeed, viachan_setspeed), KOBJMETHOD(channel_setblocksize, viachan_setblocksize), KOBJMETHOD(channel_trigger, viachan_trigger), KOBJMETHOD(channel_getptr, viachan_getptr), KOBJMETHOD(channel_getcaps, viachan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(viachan); /* -------------------------------------------------------------------- */ static void via_intr(void *p) { struct via_info *via = p; /* DEB(printf("viachan_intr\n")); */ /* Read channel */ snd_mtxlock(via->lock); if (via_rd(via, VIA_PLAY_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_PLAY_STAT, VIA_RPSTAT_INTR, 1); snd_mtxunlock(via->lock); chn_intr(via->pch.channel); snd_mtxlock(via->lock); } /* Write channel */ if (via_rd(via, VIA_RECORD_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_RECORD_STAT, VIA_RPSTAT_INTR, 1); snd_mtxunlock(via->lock); chn_intr(via->rch.channel); return; } snd_mtxunlock(via->lock); } /* * Probe and attach the card */ static int via_probe(device_t dev) { if (pci_get_devid(dev) == VIA_PCI_ID) { device_set_desc(dev, "VIA VT82C686A"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct via_info *via = (struct via_info *)p; via->sgd_addr = bds->ds_addr; } static int via_attach(device_t dev) { struct via_info *via = NULL; char status[SND_STATUSLEN]; u_int32_t data, cnt; via = malloc(sizeof(*via), M_DEVBUF, M_WAITOK | M_ZERO); via->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_via82c686 softc"); pci_enable_busmaster(dev); /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_AC97STATUS, 1); if ((data & VIA_AC97STATUS_RDY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Assert low */ DELAY(100); /* Wait T_rst_low */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN | VIA_ACLINK_NRST, 1); /* Assert high */ DELAY(5); /* Wait T_rst2clk */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Assert low */ } else { /* Warm reset */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Force no sync */ DELAY(100); pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN | VIA_ACLINK_SYNC, 1); /* Sync */ DELAY(5); /* Wait T_sync_high */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Force no sync */ DELAY(5); /* Wait T_sync2clk */ } /* Power everything up */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_DESIRED, 1); /* Wait for codec to become ready (largest reported delay here 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_AC97STATUS, 1); if (data & VIA_AC97STATUS_RDY) break; DELAY(5000); } via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); if (!via->reg) { device_printf(dev, "cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } via_wr(via, VIA_PLAY_MODE, VIA_RPMODE_AUTOSTART | VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1); via_wr(via, VIA_RECORD_MODE, VIA_RPMODE_AUTOSTART | VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1); via->codec = AC97_CREATE(dev, via, via_ac97); if (!via->codec) goto bad; if (mixer_init(dev, ac97_getmixerclass(), via->codec)) goto bad; via->codec_caps = ac97_getextcaps(via->codec); ac97_setextmode(via->codec, via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* * DMA tag for SGD table. The 686 uses scatter/gather DMA and * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/NSEGS * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, BUS_DMA_NOWAIT, &via->sgd_dmamap) != 0) goto bad; if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, NSEGS * sizeof(struct via_dma_op), dma_cb, via, 0) != 0) goto bad; - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(via->reg), rman_get_start(via->irq), - PCM_KLDSTRING(snd_via82c686)); + device_get_nameunit(device_get_parent(dev))); /* Register */ if (pcm_register(dev, via, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &viachan_class, via); pcm_addchan(dev, PCMDIR_REC, &viachan_class, via); pcm_setstatus(dev, status); return 0; bad: if (via->codec) ac97_destroy(via->codec); if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_addr) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return ENXIO; } static int via_detach(device_t dev) { int r; struct via_info *via = NULL; r = pcm_unregister(dev); if (r) return r; via = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); return 0; } static device_method_t via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), DEVMETHOD(device_detach, via_detach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_via82c686, pci, via_driver, 0, 0); MODULE_DEPEND(snd_via82c686, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_via82c686, 1); diff --git a/sys/dev/sound/pci/vibes.c b/sys/dev/sound/pci/vibes.c index 538aa961d987..e587f0113b5d 100644 --- a/sys/dev/sound/pci/vibes.c +++ b/sys/dev/sound/pci/vibes.c @@ -1,942 +1,943 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This card has the annoying habit of "clicking" when attached and * detached, haven't been able to remedy this with any combination of * muting. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "mixer_if.h" /* ------------------------------------------------------------------------- */ /* Constants */ #define SV_PCI_ID 0xca005333 #define SV_DEFAULT_BUFSZ 16384 #define SV_MIN_BLKSZ 128 #define SV_INTR_PER_BUFFER 2 #ifndef DEB #define DEB(x) /* (x) */ #endif /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t fmt, spd; int dir; int dma_active, dma_was_active; }; struct sc_info { device_t dev; /* DMA buffer allocator */ bus_dma_tag_t parent_dmat; /* Enhanced register resources */ struct resource *enh_reg; bus_space_tag_t enh_st; bus_space_handle_t enh_sh; int enh_type; int enh_rid; /* DMA configuration */ struct resource *dmaa_reg, *dmac_reg; bus_space_tag_t dmaa_st, dmac_st; bus_space_handle_t dmaa_sh, dmac_sh; int dmaa_type, dmac_type; int dmaa_rid, dmac_rid; /* Interrupt resources */ struct resource *irq; int irqid; void *ih; /* User configurable buffer size */ unsigned int bufsz; struct sc_chinfo rch, pch; u_int8_t rev; }; static u_int32_t sc_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps sc_caps = {8000, 48000, sc_fmt, 0}; /* ------------------------------------------------------------------------- */ /* Register Manipulations */ #define sv_direct_set(x, y, z) _sv_direct_set(x, y, z, __LINE__) static u_int8_t sv_direct_get(struct sc_info *sc, u_int8_t reg) { return bus_space_read_1(sc->enh_st, sc->enh_sh, reg); } static void _sv_direct_set(struct sc_info *sc, u_int8_t reg, u_int8_t val, int line) { u_int8_t n; bus_space_write_1(sc->enh_st, sc->enh_sh, reg, val); n = sv_direct_get(sc, reg); if (n != val) { device_printf(sc->dev, "sv_direct_set register 0x%02x %d != %d from line %d\n", reg, n, val, line); } } static u_int8_t sv_indirect_get(struct sc_info *sc, u_int8_t reg) { if (reg == SV_REG_FORMAT || reg == SV_REG_ANALOG_PWR) reg |= SV_CM_INDEX_MCE; bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_INDEX, reg); return bus_space_read_1(sc->enh_st, sc->enh_sh, SV_CM_DATA); } #define sv_indirect_set(x, y, z) _sv_indirect_set(x, y, z, __LINE__) static void _sv_indirect_set(struct sc_info *sc, u_int8_t reg, u_int8_t val, int line) { if (reg == SV_REG_FORMAT || reg == SV_REG_ANALOG_PWR) reg |= SV_CM_INDEX_MCE; bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_INDEX, reg); bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_DATA, val); reg &= ~SV_CM_INDEX_MCE; if (reg != SV_REG_ADC_PLLM) { u_int8_t n; n = sv_indirect_get(sc, reg); if (n != val) { device_printf(sc->dev, "sv_indirect_set register 0x%02x %d != %d line %d\n", reg, n, val, line); } } } static void sv_dma_set_config(bus_space_tag_t st, bus_space_handle_t sh, u_int32_t base, u_int32_t count, u_int8_t mode) { bus_space_write_4(st, sh, SV_DMA_ADDR, base); bus_space_write_4(st, sh, SV_DMA_COUNT, count & 0xffffff); bus_space_write_1(st, sh, SV_DMA_MODE, mode); DEB(printf("base 0x%08x count %5d mode 0x%02x\n", base, count, mode)); } static u_int32_t sv_dma_get_count(bus_space_tag_t st, bus_space_handle_t sh) { return bus_space_read_4(st, sh, SV_DMA_COUNT) & 0xffffff; } /* ------------------------------------------------------------------------- */ /* Play / Record Common Interface */ static void * svchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->dir = dir; if (sndbuf_alloc(b, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("svchan_init failed\n")); return NULL; } ch->buffer = b; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->dma_active = ch->dma_was_active = 0; return ch; } static struct pcmchan_caps * svchan_getcaps(kobj_t obj, void *data) { return &sc_caps; } static u_int32_t svchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; /* user has requested interrupts every blocksize bytes */ RANGE(blocksize, SV_MIN_BLKSZ, sc->bufsz / SV_INTR_PER_BUFFER); sndbuf_resize(ch->buffer, SV_INTR_PER_BUFFER, blocksize); DEB(printf("svchan_setblocksize: %d\n", blocksize)); return blocksize; } static int svchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; /* NB Just note format here as setting format register * generates noise if dma channel is inactive. */ ch->fmt = (AFMT_CHANNEL(format) > 1) ? SV_AFMT_STEREO : SV_AFMT_MONO; ch->fmt |= (format & AFMT_16BIT) ? SV_AFMT_S16 : SV_AFMT_U8; return 0; } static u_int32_t svchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; RANGE(speed, 8000, 48000); ch->spd = speed; return speed; } /* ------------------------------------------------------------------------- */ /* Recording interface */ static int sv_set_recspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t f_out, f_actual; u_int32_t rs, re, r, best_r = 0, r2, t, n, best_n = 0; int32_t m, best_m = 0, ms, me, err, min_err; /* This algorithm is a variant described in sonicvibes.pdf * appendix A. This search is marginally more extensive and * results in (nominally) better sample rate matching. */ f_out = SV_F_SCALE * speed; min_err = 0x7fffffff; /* Find bounds of r to examine, rs <= r <= re */ t = 80000000 / f_out; for (rs = 1; (1 << rs) < t; rs++); t = 150000000 / f_out; for (re = 1; (2 << re) < t; re++); if (re > 7) re = 7; /* Search over r, n, m */ for (r = rs; r <= re; r++) { r2 = (1 << r); for (n = 3; n < 34; n++) { m = f_out * n / (SV_F_REF / r2); ms = (m > 3) ? (m - 1) : 3; me = (m < 129) ? (m + 1) : 129; for (m = ms; m <= me; m++) { f_actual = m * SV_F_REF / (n * r2); if (f_actual > f_out) { err = f_actual - f_out; } else { err = f_out - f_actual; } if (err < min_err) { best_r = r; best_m = m - 2; best_n = n - 2; min_err = err; if (err == 0) break; } } } } sv_indirect_set(sc, SV_REG_ADC_PLLM, best_m); sv_indirect_set(sc, SV_REG_ADC_PLLN, SV_ADC_PLLN(best_n) | SV_ADC_PLLR(best_r)); DEB(printf("svrchan_setspeed: %d -> PLLM 0x%02x PLLNR 0x%08x\n", speed, sv_indirect_get(sc, SV_REG_ADC_PLLM), sv_indirect_get(sc, SV_REG_ADC_PLLN))); return 0; } static int svrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ sv_set_recspeed(sc, ch->spd); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAC_MSK; v |= SV_AFMT_DMAC(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ count = sndbuf_getsize(ch->buffer) / 2; /* DMAC uses words */ sv_dma_set_config(sc->dmac_st, sc->dmac_sh, sndbuf_getbufaddr(ch->buffer), count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_RD); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAC_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAC_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE) | SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static u_int32_t svrchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; sz = sndbuf_getsize(ch->buffer); /* DMAC uses words */ remain = (sv_dma_get_count(sc->dmac_st, sc->dmac_sh) + 1) * 2; return sz - remain; } static kobj_method_t svrchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svrchan_trigger), KOBJMETHOD(channel_getptr, svrchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(svrchan); /* ------------------------------------------------------------------------- */ /* Playback interface */ static int svpchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable, speed; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ speed = (ch->spd * 65536) / 48000; if (speed > 65535) speed = 65535; sv_indirect_set(sc, SV_REG_PCM_SAMPLING_HI, speed >> 8); sv_indirect_set(sc, SV_REG_PCM_SAMPLING_LO, speed & 0xff); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAA_MSK; v |= SV_AFMT_DMAA(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ count = sndbuf_getsize(ch->buffer); sv_dma_set_config(sc->dmaa_st, sc->dmaa_sh, sndbuf_getbufaddr(ch->buffer), count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_WR); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAA_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAA_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE); enable = (enable | SV_PLAY_ENABLE) & ~SV_PLAYBACK_PAUSE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_PLAY_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static u_int32_t svpchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; sz = sndbuf_getsize(ch->buffer); /* DMAA uses bytes */ remain = sv_dma_get_count(sc->dmaa_st, sc->dmaa_sh) + 1; return (sz - remain); } static kobj_method_t svpchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svpchan_trigger), KOBJMETHOD(channel_getptr, svpchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(svpchan); /* ------------------------------------------------------------------------- */ /* Mixer support */ struct sv_mix_props { u_int8_t reg; /* Register */ u_int8_t stereo:1; /* Supports 2 channels */ u_int8_t mute:1; /* Supports muting */ u_int8_t neg:1; /* Negative gain */ u_int8_t max; /* Max gain */ u_int8_t iselect; /* Input selector */ } static const mt [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_LINE1] = {SV_REG_AUX1, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_AUX1}, [SOUND_MIXER_CD] = {SV_REG_CD, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_CD}, [SOUND_MIXER_LINE] = {SV_REG_LINE, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_LINE}, [SOUND_MIXER_MIC] = {SV_REG_MIC, 0, 1, 1, SV_MIC_MAX, SV_INPUT_MIC}, [SOUND_MIXER_SYNTH] = {SV_REG_SYNTH, 0, 1, 1, SV_DEFAULT_MAX, 0}, [SOUND_MIXER_LINE2] = {SV_REG_AUX2, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_AUX2}, [SOUND_MIXER_VOLUME] = {SV_REG_MIX, 1, 1, 1, SV_DEFAULT_MAX, 0}, [SOUND_MIXER_PCM] = {SV_REG_PCM, 1, 1, 1, SV_PCM_MAX, 0}, [SOUND_MIXER_RECLEV] = {SV_REG_ADC_INPUT, 1, 0, 0, SV_ADC_MAX, 0}, }; static void sv_channel_gain(struct sc_info *sc, u_int32_t dev, u_int32_t gain, u_int32_t channel) { u_int8_t v; int32_t g; g = mt[dev].max * gain / 100; if (mt[dev].neg) g = mt[dev].max - g; v = sv_indirect_get(sc, mt[dev].reg + channel) & ~mt[dev].max; v |= g; if (mt[dev].mute) { if (gain == 0) { v |= SV_MUTE; } else { v &= ~SV_MUTE; } } sv_indirect_set(sc, mt[dev].reg + channel, v); } static int sv_gain(struct sc_info *sc, u_int32_t dev, u_int32_t left, u_int32_t right) { sv_channel_gain(sc, dev, left, 0); if (mt[dev].stereo) sv_channel_gain(sc, dev, right, 1); return 0; } static void sv_mix_mute_all(struct sc_info *sc) { int32_t i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].reg) sv_gain(sc, i, 0, 0); } } static int sv_mix_init(struct snd_mixer *m) { u_int32_t i, v; for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].max) v |= (1 << i); } mix_setdevs(m, v); for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].iselect) v |= (1 << i); } mix_setrecdevs(m, v); return 0; } static int sv_mix_set(struct snd_mixer *m, u_int32_t dev, u_int32_t left, u_int32_t right) { struct sc_info *sc = mix_getdevinfo(m); return sv_gain(sc, dev, left, right); } static u_int32_t sv_mix_setrecsrc(struct snd_mixer *m, u_int32_t mask) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, v; v = sv_indirect_get(sc, SV_REG_ADC_INPUT) & SV_INPUT_GAIN_MASK; for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1 << i) & mask) { v |= mt[i].iselect; } } DEB(printf("sv_mix_setrecsrc: mask 0x%08x adc_input 0x%02x\n", mask, v)); sv_indirect_set(sc, SV_REG_ADC_INPUT, v); return mask; } static kobj_method_t sv_mixer_methods[] = { KOBJMETHOD(mixer_init, sv_mix_init), KOBJMETHOD(mixer_set, sv_mix_set), KOBJMETHOD(mixer_setrecsrc, sv_mix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(sv_mixer); /* ------------------------------------------------------------------------- */ /* Power management and reset */ static void sv_power(struct sc_info *sc, int state) { u_int8_t v; switch (state) { case 0: /* power on */ v = sv_indirect_get(sc, SV_REG_ANALOG_PWR) &~ SV_ANALOG_OFF; v |= SV_ANALOG_OFF_SRS | SV_ANALOG_OFF_SPLL; sv_indirect_set(sc, SV_REG_ANALOG_PWR, v); v = sv_indirect_get(sc, SV_REG_DIGITAL_PWR) &~ SV_DIGITAL_OFF; v |= SV_DIGITAL_OFF_SYN | SV_DIGITAL_OFF_MU | SV_DIGITAL_OFF_GP; sv_indirect_set(sc, SV_REG_DIGITAL_PWR, v); break; default: /* power off */ v = sv_indirect_get(sc, SV_REG_ANALOG_PWR) | SV_ANALOG_OFF; sv_indirect_set(sc, SV_REG_ANALOG_PWR, v); v = sv_indirect_get(sc, SV_REG_DIGITAL_PWR) | SV_DIGITAL_OFF; sv_indirect_set(sc, SV_REG_DIGITAL_PWR, SV_DIGITAL_OFF); break; } DEB(printf("Power state %d\n", state)); } static int sv_init(struct sc_info *sc) { u_int8_t v; /* Effect reset */ v = sv_direct_get(sc, SV_CM_CONTROL) & ~SV_CM_CONTROL_ENHANCED; v |= SV_CM_CONTROL_RESET; sv_direct_set(sc, SV_CM_CONTROL, v); DELAY(50); v = sv_direct_get(sc, SV_CM_CONTROL) & ~SV_CM_CONTROL_RESET; sv_direct_set(sc, SV_CM_CONTROL, v); DELAY(50); /* Set in enhanced mode */ v = sv_direct_get(sc, SV_CM_CONTROL); v |= SV_CM_CONTROL_ENHANCED; sv_direct_set(sc, SV_CM_CONTROL, v); /* Enable interrupts (UDM and MIDM are superfluous) */ v = sv_direct_get(sc, SV_CM_IMR); v &= ~(SV_CM_IMR_AMSK | SV_CM_IMR_CMSK | SV_CM_IMR_SMSK); sv_direct_set(sc, SV_CM_IMR, v); /* Select ADC PLL for ADC clock */ v = sv_indirect_get(sc, SV_REG_CLOCK_SOURCE) & ~SV_CLOCK_ALTERNATE; sv_indirect_set(sc, SV_REG_CLOCK_SOURCE, v); /* Disable loopback - binds ADC and DAC rates */ v = sv_indirect_get(sc, SV_REG_LOOPBACK) & ~SV_LOOPBACK_ENABLE; sv_indirect_set(sc, SV_REG_LOOPBACK, v); /* Disable SRS */ v = sv_indirect_get(sc, SV_REG_SRS_SPACE) | SV_SRS_DISABLED; sv_indirect_set(sc, SV_REG_SRS_SPACE, v); /* Get revision */ sc->rev = sv_indirect_get(sc, SV_REG_REVISION); return 0; } static int sv_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); sc->rch.dma_was_active = sc->rch.dma_active; svrchan_trigger(NULL, &sc->rch, PCMTRIG_ABORT); sc->pch.dma_was_active = sc->pch.dma_active; svrchan_trigger(NULL, &sc->pch, PCMTRIG_ABORT); sv_mix_mute_all(sc); sv_power(sc, 3); return 0; } static int sv_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 0); if (sv_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } if (sc->rch.dma_was_active) { svrchan_trigger(0, &sc->rch, PCMTRIG_START); } if (sc->pch.dma_was_active) { svpchan_trigger(0, &sc->pch, PCMTRIG_START); } return 0; } /* ------------------------------------------------------------------------- */ /* Resource related */ static void sv_intr(void *data) { struct sc_info *sc = data; u_int8_t status; status = sv_direct_get(sc, SV_CM_STATUS); if (status & SV_CM_STATUS_AINT) chn_intr(sc->pch.channel); if (status & SV_CM_STATUS_CINT) chn_intr(sc->rch.channel); status &= ~(SV_CM_STATUS_AINT|SV_CM_STATUS_CINT); DEB(if (status) printf("intr 0x%02x ?\n", status)); return; } static int sv_probe(device_t dev) { switch(pci_get_devid(dev)) { case SV_PCI_ID: device_set_desc(dev, "S3 Sonicvibes"); return BUS_PROBE_DEFAULT; default: return ENXIO; } } static int sv_attach(device_t dev) { struct sc_info *sc; rman_res_t count, midi_start, games_start; u_int32_t data; char status[SND_STATUSLEN]; u_long sdmaa, sdmac, ml, mu; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; pci_enable_busmaster(dev); if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } sc->enh_rid = SV_PCI_ENHANCED; sc->enh_type = SYS_RES_IOPORT; sc->enh_reg = bus_alloc_resource_any(dev, sc->enh_type, &sc->enh_rid, RF_ACTIVE); if (sc->enh_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate enh\n"); return ENXIO; } sc->enh_st = rman_get_bustag(sc->enh_reg); sc->enh_sh = rman_get_bushandle(sc->enh_reg); data = pci_read_config(dev, SV_PCI_DMAA, 4); DEB(printf("sv_attach: initial dmaa 0x%08x\n", data)); data = pci_read_config(dev, SV_PCI_DMAC, 4); DEB(printf("sv_attach: initial dmac 0x%08x\n", data)); /* Initialize DMA_A and DMA_C */ pci_write_config(dev, SV_PCI_DMAA, SV_PCI_DMA_EXTENDED, 4); pci_write_config(dev, SV_PCI_DMAC, 0, 4); /* Register IRQ handler */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, sv_intr, sc, &sc->ih)) { device_printf(dev, "sv_attach: Unable to map interrupt\n"); goto fail; } sc->bufsz = pcm_getbuffersize(dev, 4096, SV_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "sv_attach: Unable to create dma tag\n"); goto fail; } /* Power up and initialize */ sv_mix_mute_all(sc); sv_power(sc, 0); sv_init(sc); if (mixer_init(dev, &sv_mixer_class, sc) != 0) { device_printf(dev, "sv_attach: Mixer failed to initialize\n"); goto fail; } /* XXX This is a hack, and it's ugly. Okay, the deal is this * card has two more io regions that available for automatic * configuration by the pci code. These need to be allocated * to used as control registers for the DMA engines. * Unfortunately FBSD has no bus_space_foo() functions so we * have to grab port space in region of existing resources. Go * for space between midi and game ports. */ bus_get_resource(dev, SYS_RES_IOPORT, SV_PCI_MIDI, &midi_start, &count); bus_get_resource(dev, SYS_RES_IOPORT, SV_PCI_GAMES, &games_start, &count); if (games_start < midi_start) { ml = games_start; mu = midi_start; } else { ml = midi_start; mu = games_start; } /* Check assumptions about space availability and alignment. How driver loaded can determine whether games_start > midi_start or vice versa */ if ((mu - ml >= 0x800) || ((mu - ml) % 0x200)) { device_printf(dev, "sv_attach: resource assumptions not met " "(midi 0x%08lx, games 0x%08lx)\n", (u_long)midi_start, (u_long)games_start); goto fail; } sdmaa = ml + 0x40; sdmac = sdmaa + 0x40; /* Add resources to list of pci resources for this device - from here on * they look like normal pci resources. */ bus_set_resource(dev, SYS_RES_IOPORT, SV_PCI_DMAA, sdmaa, SV_PCI_DMAA_SIZE); bus_set_resource(dev, SYS_RES_IOPORT, SV_PCI_DMAC, sdmac, SV_PCI_DMAC_SIZE); /* Cache resource short-cuts for dma_a */ sc->dmaa_rid = SV_PCI_DMAA; sc->dmaa_type = SYS_RES_IOPORT; sc->dmaa_reg = bus_alloc_resource_any(dev, sc->dmaa_type, &sc->dmaa_rid, RF_ACTIVE); if (sc->dmaa_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmaa\n"); goto fail; } sc->dmaa_st = rman_get_bustag(sc->dmaa_reg); sc->dmaa_sh = rman_get_bushandle(sc->dmaa_reg); /* Poke port into dma_a configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAA, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmaa & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAA, data, 4); DEB(printf("dmaa: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAA, 4))); /* Cache resource short-cuts for dma_c */ sc->dmac_rid = SV_PCI_DMAC; sc->dmac_type = SYS_RES_IOPORT; sc->dmac_reg = bus_alloc_resource_any(dev, sc->dmac_type, &sc->dmac_rid, RF_ACTIVE); if (sc->dmac_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmac\n"); goto fail; } sc->dmac_st = rman_get_bustag(sc->dmac_reg); sc->dmac_sh = rman_get_bushandle(sc->dmac_reg); /* Poke port into dma_c configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAC, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmac & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAC, data, 4); DEB(printf("dmac: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAC, 4))); if (bootverbose) printf("Sonicvibes: revision %d.\n", sc->rev); if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "sv_attach: pcm_register fail\n"); goto fail; } pcm_addchan(dev, PCMDIR_PLAY, &svpchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &svrchan_class, sc); - snprintf(status, SND_STATUSLEN, "at io 0x%jx irq %jd %s", - rman_get_start(sc->enh_reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_vibes)); + snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", + rman_get_start(sc->enh_reg), rman_get_start(sc->irq), + device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); DEB(printf("sv_attach: succeeded\n")); return 0; fail: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->enh_reg) bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); if (sc->dmaa_reg) bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); if (sc->dmac_reg) bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); return ENXIO; } static int sv_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); free(sc, M_DEVBUF); return 0; } static device_method_t sc_methods[] = { DEVMETHOD(device_probe, sv_probe), DEVMETHOD(device_attach, sv_attach), DEVMETHOD(device_detach, sv_detach), DEVMETHOD(device_resume, sv_resume), DEVMETHOD(device_suspend, sv_suspend), { 0, 0 } }; static driver_t sonicvibes_driver = { "pcm", sc_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_vibes, pci, sonicvibes_driver, 0, 0); MODULE_DEPEND(snd_vibes, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_vibes, 1); diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 5981266116fb..5b366a62630c 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,609 +1,603 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #include #include #include #define PCM_SOFTC_SIZE (sizeof(struct snddev_info)) #define SND_STATUSLEN 64 #define SOUND_MODVER 5 #define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER /* * We're abusing the fact that MAXMINOR still have enough room * for our bit twiddling and nobody ever need 512 unique soundcards, * 32 unique device types and 1024 unique cloneable devices for the * next 100 years... */ #define PCMMAXUNIT (snd_max_u()) #define PCMMAXDEV (snd_max_d()) #define PCMMAXCHAN (snd_max_c()) #define PCMMAXCLONE PCMMAXCHAN #define PCMUNIT(x) (snd_unit2u(dev2unit(x))) #define PCMDEV(x) (snd_unit2d(dev2unit(x))) #define PCMCHAN(x) (snd_unit2c(dev2unit(x))) /* XXX unit2minor compat */ #define PCMMINOR(x) (x) /* * By design, limit possible channels for each direction. */ #define SND_MAXHWCHAN 256 #define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_DYING 0x00000008 #define SD_F_DETACHING 0x00000010 #define SD_F_BUSY 0x00000020 #define SD_F_MPSAFE 0x00000040 #define SD_F_REGISTERED 0x00000080 #define SD_F_BITPERFECT 0x00000100 #define SD_F_VPC 0x00000200 /* volume-per-channel */ #define SD_F_EQ 0x00000400 /* EQ */ #define SD_F_EQ_ENABLED 0x00000800 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00001000 /* EQ bypassed */ #define SD_F_EQ_PC 0x00002000 /* EQ per-channel */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ "\002AUTOVCHAN" \ "\003SOFTPCMVOL" \ "\004DYING" \ "\005DETACHING" \ "\006BUSY" \ "\007MPSAFE" \ "\010REGISTERED" \ "\011BITPERFECT" \ "\012VPC" \ "\013EQ" \ "\014EQ_ENABLED" \ "\015EQ_BYPASSED" \ "\016EQ_PC" \ "\035PRIO_RD" \ "\036PRIO_WR" \ "\037DIR_SET" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL && \ !((x)->flags & SD_F_DYING)) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && \ ((x)->flags & SD_F_REGISTERED)) #define PCM_DETACHING(x) ((x)->flags & SD_F_DETACHING) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff #define AFMT_CHANNEL_MASK 0x07f00000 #define AFMT_CHANNEL_SHIFT 20 #define AFMT_CHANNEL_MAX 0x7f #define AFMT_EXTCHANNEL_MASK 0x08000000 #define AFMT_EXTCHANNEL_SHIFT 27 #define AFMT_EXTCHANNEL_MAX 1 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 #define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) /* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this * reason the /dev/dsp is reserved for digitized audio use. There is a * device for true DSP processors but it will be called something else. * In v3.0 it's /dev/sndproc but this could be a temporary solution. */ #define SND_DEV_CTL 0 /* Control port /dev/mixer */ #define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ #define SND_DEV_MIDIN 2 /* Raw midi access */ #define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ #define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ #define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ #define SND_DEV_STATUS 6 /* /dev/sndstat */ /* #7 not in use now. */ #define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 #define SND_DEV_DSPHW_PLAY 11 /* specific playback channel */ #define SND_DEV_DSPHW_VPLAY 12 /* specific virtual playback channel */ #define SND_DEV_DSPHW_REC 13 /* specific record channel */ #define SND_DEV_DSPHW_VREC 14 /* specific virtual record channel */ #define SND_DEV_DSPHW_CD 15 /* s16le/stereo 44100Hz CD */ /* * OSSv4 compatible device. For now, it serve no purpose and * the cloning itself will forward the request to ordinary /dev/dsp * instead. */ #define SND_DEV_DSP_MMAP 16 /* /dev/dsp_mmap */ #define SND_DEV_DSP_AC3 17 /* /dev/dsp_ac3 */ #define SND_DEV_DSP_MULTICH 18 /* /dev/dsp_multich */ #define SND_DEV_DSP_SPDIFOUT 19 /* /dev/dsp_spdifout */ #define SND_DEV_DSP_SPDIFIN 20 /* /dev/dsp_spdifin */ #define SND_DEV_LAST SND_DEV_DSP_SPDIFIN #define SND_DEV_MAX PCMMAXDEV #define DSP_DEFAULT_SPEED 8000 #define ON 1 #define OFF 0 extern int pcm_veto_load; extern int snd_unit; extern int snd_maxautovchans; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; /* * some macros for debugging purposes * DDB/DEB to enable/disable debugging stuff * BVDDB to enable debugging when bootverbose */ #define BVDDB(x) if (bootverbose) x #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); int pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num); int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, char *comm, int devunit); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo); int pcm_chn_destroy(struct pcm_channel *ch); int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch); int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(m) typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_unregister(device_t dev); /* usage of flags in device config entry (config file) */ #define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ #define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ /* ought to be made obsolete but still used by mss */ #define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ #define DV_F_DEV_SHIFT 8 /* force device type/class */ /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; } pcm; } channels; TAILQ_HEAD(dsp_cdevinfo_linkhead, dsp_cdevinfo) dsp_cdevinfo_pool; struct snd_clone *clones; unsigned devcount, playcount, reccount, pvchancount, rvchancount ; unsigned flags; int inprog; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; uint32_t pvchanrate, pvchanformat; uint32_t rvchanrate, rvchanformat; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_MODE_MIXER 0x01 #define PCM_MODE_PLAY 0x02 #define PCM_MODE_REC 0x04 #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) { \ if ((x)->cv.cv_waiters > 1 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM RELEASE] " \ "cv_waiters=%d > 1!\n", \ __func__, __LINE__, \ (x)->cv.cv_waiters); \ cv_broadcast(&(x)->cv); \ } \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* !SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) -#ifdef KLD_MODULE -#define PCM_KLDSTRING(a) ("kld " # a) -#else -#define PCM_KLDSTRING(a) "" -#endif - #endif /* _KERNEL */ #endif /* _OS_H_ */ diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c index 3237093bcbfa..36985d4666a4 100644 --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -1,6240 +1,6239 @@ /* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 /* * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf * http://www.usb.org/developers/devclass_docs/frmts10.pdf * http://www.usb.org/developers/devclass_docs/termt10.pdf */ /* * Also merged: * $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $ * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #define USB_DEBUG_VAR uaudio_debug #include #include #include /* for bootverbose */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "feeder_if.h" static int uaudio_default_rate = 0; /* use rate list */ static int uaudio_default_bits = 32; static int uaudio_default_channels = 0; /* use default */ static int uaudio_buffer_ms = 2; static bool uaudio_handle_hid = true; static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB uaudio"); SYSCTL_BOOL(_hw_usb_uaudio, OID_AUTO, handle_hid, CTLFLAG_RWTUN, &uaudio_handle_hid, 0, "uaudio handles any HID volume/mute keys, if set"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RWTUN, &uaudio_default_rate, 0, "uaudio default sample rate"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RWTUN, &uaudio_default_bits, 0, "uaudio default sample bits"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RWTUN, &uaudio_default_channels, 0, "uaudio default sample channels"); static int uaudio_buffer_ms_sysctl(SYSCTL_HANDLER_ARGS) { int err, val; val = uaudio_buffer_ms; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == uaudio_buffer_ms) return (err); if (val > 8) val = 8; else if (val < 2) val = 2; uaudio_buffer_ms = val; return (0); } SYSCTL_PROC(_hw_usb_uaudio, OID_AUTO, buffer_ms, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), uaudio_buffer_ms_sysctl, "I", "uaudio buffering delay from 2ms to 8ms"); #ifdef USB_DEBUG static int uaudio_debug; SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RWTUN, &uaudio_debug, 0, "uaudio debug level"); #else #define uaudio_debug 0 #endif #define UAUDIO_NFRAMES 64 /* must be factor of 8 due HS-USB */ #define UAUDIO_NCHANBUFS 2 /* number of outstanding request */ #define UAUDIO_RECURSE_LIMIT 255 /* rounds */ #define UAUDIO_CHANNELS_MAX MIN(64, AFMT_CHANNEL_MAX) #define UAUDIO_MATRIX_MAX 8 /* channels */ #define MAKE_WORD(h,l) (((h) << 8) | (l)) #define BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1) #define UAUDIO_MAX_CHAN(x) (x) #define MIX(sc) ((sc)->sc_mixer_node) union uaudio_asid { const struct usb_audio_streaming_interface_descriptor *v1; const struct usb_audio20_streaming_interface_descriptor *v2; }; union uaudio_asf1d { const struct usb_audio_streaming_type1_descriptor *v1; const struct usb_audio20_streaming_type1_descriptor *v2; }; union uaudio_sed { const struct usb_audio_streaming_endpoint_descriptor *v1; const struct usb_audio20_streaming_endpoint_descriptor *v2; }; struct uaudio_mixer_node { const char *name; int32_t minval; int32_t maxval; #define MIX_MAX_CHAN 16 int32_t wValue[MIX_MAX_CHAN]; /* using nchan */ uint32_t mul; uint32_t ctl; int wData[MIX_MAX_CHAN]; /* using nchan */ uint16_t wIndex; uint8_t update[(MIX_MAX_CHAN + 7) / 8]; uint8_t nchan; uint8_t type; #define MIX_ON_OFF 1 #define MIX_SIGNED_16 2 #define MIX_UNSIGNED_16 3 #define MIX_SIGNED_8 4 #define MIX_SELECTOR 5 #define MIX_UNKNOWN 6 #define MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \ ((n) == MIX_UNSIGNED_16)) ? 2 : 1) #define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) #define MAX_SELECTOR_INPUT_PIN 256 uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; uint8_t val_default; uint8_t desc[64]; struct uaudio_mixer_node *next; }; struct uaudio_configure_msg { struct usb_proc_msg hdr; struct uaudio_softc *sc; }; #define CHAN_MAX_ALT 24 struct uaudio_chan_alt { union uaudio_asf1d p_asf1d; union uaudio_sed p_sed; const usb_endpoint_descriptor_audio_t *p_ed1; const struct uaudio_format *p_fmt; const struct usb_config *usb_cfg; uint32_t sample_rate; /* in Hz */ uint16_t sample_size; uint8_t iface_index; uint8_t iface_alt_index; uint8_t channels; }; struct uaudio_chan { struct pcmchan_caps pcm_cap; /* capabilities */ struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT]; struct snd_dbuf *pcm_buf; struct mtx *pcm_mtx; /* lock protecting this structure */ struct uaudio_softc *priv_sc; struct pcm_channel *pcm_ch; struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1]; uint8_t *buf; /* pointer to buffer */ uint8_t *start; /* upper layer buffer start */ uint8_t *end; /* upper layer buffer end */ uint8_t *cur; /* current position in upper layer * buffer */ uint32_t intr_frames; /* in units */ uint32_t frames_per_second; uint32_t sample_rem; uint32_t sample_curr; uint32_t max_buf; int32_t jitter_rem; int32_t jitter_curr; int feedback_rate; uint32_t pcm_format[2]; uint16_t bytes_per_frame[2]; uint32_t intr_counter; uint32_t running; uint32_t num_alt; uint32_t cur_alt; uint32_t set_alt; uint32_t operation; #define CHAN_OP_NONE 0 #define CHAN_OP_START 1 #define CHAN_OP_STOP 2 #define CHAN_OP_DRAIN 3 uint8_t iface_index; }; #define UMIDI_EMB_JACK_MAX 16 /* units */ #define UMIDI_TX_FRAMES 256 /* units */ #define UMIDI_TX_BUFFER (UMIDI_TX_FRAMES * 4) /* bytes */ enum { UMIDI_TX_TRANSFER, UMIDI_RX_TRANSFER, UMIDI_N_TRANSFER, }; struct umidi_sub_chan { struct usb_fifo_sc fifo; uint8_t *temp_cmd; uint8_t temp_0[4]; uint8_t temp_1[4]; uint8_t state; #define UMIDI_ST_UNKNOWN 0 /* scan for command */ #define UMIDI_ST_1PARAM 1 #define UMIDI_ST_2PARAM_1 2 #define UMIDI_ST_2PARAM_2 3 #define UMIDI_ST_SYSEX_0 4 #define UMIDI_ST_SYSEX_1 5 #define UMIDI_ST_SYSEX_2 6 uint8_t read_open:1; uint8_t write_open:1; uint8_t unused:6; }; struct umidi_chan { struct umidi_sub_chan sub[UMIDI_EMB_JACK_MAX]; struct mtx mtx; struct usb_xfer *xfer[UMIDI_N_TRANSFER]; uint8_t iface_index; uint8_t iface_alt_index; uint8_t read_open_refcount; uint8_t write_open_refcount; uint8_t curr_cable; uint8_t max_emb_jack; uint8_t valid; uint8_t single_command; }; struct uaudio_search_result { uint8_t bit_input[(256 + 7) / 8]; uint8_t bit_output[(256 + 7) / 8]; uint8_t recurse_level; uint8_t id_max; uint8_t is_input; }; enum { UAUDIO_HID_RX_TRANSFER, UAUDIO_HID_N_TRANSFER, }; struct uaudio_hid { struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER]; struct hid_location volume_up_loc; struct hid_location volume_down_loc; struct hid_location mute_loc; uint32_t flags; #define UAUDIO_HID_VALID 0x0001 #define UAUDIO_HID_HAS_ID 0x0002 #define UAUDIO_HID_HAS_VOLUME_UP 0x0004 #define UAUDIO_HID_HAS_VOLUME_DOWN 0x0008 #define UAUDIO_HID_HAS_MUTE 0x0010 uint8_t iface_index; uint8_t volume_up_id; uint8_t volume_down_id; uint8_t mute_id; }; #define UAUDIO_SPDIF_OUT 0x01 /* Enable S/PDIF output */ #define UAUDIO_SPDIF_OUT_48K 0x02 /* Out sample rate = 48K */ #define UAUDIO_SPDIF_OUT_96K 0x04 /* Out sample rate = 96K */ #define UAUDIO_SPDIF_IN_MIX 0x10 /* Input mix enable */ #define UAUDIO_MAX_CHILD 2 struct uaudio_softc_child { device_t pcm_device; struct mtx *mixer_lock; struct snd_mixer *mixer_dev; uint32_t mix_info; uint32_t recsrc_info; uint8_t pcm_registered:1; uint8_t mixer_init:1; }; struct uaudio_softc { struct sbuf sc_sndstat; struct sndcard_func sc_sndcard_func; struct uaudio_chan sc_rec_chan[UAUDIO_MAX_CHILD]; struct uaudio_chan sc_play_chan[UAUDIO_MAX_CHILD]; struct umidi_chan sc_midi_chan; struct uaudio_hid sc_hid; struct uaudio_search_result sc_mixer_clocks; struct uaudio_mixer_node sc_mixer_node; struct uaudio_configure_msg sc_config_msg[2]; struct uaudio_softc_child sc_child[UAUDIO_MAX_CHILD]; struct usb_device *sc_udev; struct usb_xfer *sc_mixer_xfer[1]; struct uaudio_mixer_node *sc_mixer_root; struct uaudio_mixer_node *sc_mixer_curr; int (*sc_set_spdif_fn) (struct uaudio_softc *, int); uint16_t sc_audio_rev; uint16_t sc_mixer_count; uint8_t sc_mixer_iface_index; uint8_t sc_mixer_iface_no; uint8_t sc_mixer_chan; uint8_t sc_sndstat_valid:1; uint8_t sc_uq_audio_swap_lr:1; uint8_t sc_uq_au_inp_async:1; uint8_t sc_uq_au_no_xu:1; uint8_t sc_uq_bad_adc:1; uint8_t sc_uq_au_vendor_class:1; uint8_t sc_pcm_bitperfect:1; }; struct uaudio_terminal_node { union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it_v1; const struct usb_audio_output_terminal *ot_v1; const struct usb_audio_mixer_unit_0 *mu_v1; const struct usb_audio_selector_unit *su_v1; const struct usb_audio_feature_unit *fu_v1; const struct usb_audio_processing_unit_0 *pu_v1; const struct usb_audio_extension_unit_0 *eu_v1; const struct usb_audio20_clock_source_unit *csrc_v2; const struct usb_audio20_clock_selector_unit_0 *csel_v2; const struct usb_audio20_clock_multiplier_unit *cmul_v2; const struct usb_audio20_input_terminal *it_v2; const struct usb_audio20_output_terminal *ot_v2; const struct usb_audio20_mixer_unit_0 *mu_v2; const struct usb_audio20_selector_unit *su_v2; const struct usb_audio20_feature_unit *fu_v2; const struct usb_audio20_sample_rate_unit *ru_v2; const struct usb_audio20_processing_unit_0 *pu_v2; const struct usb_audio20_extension_unit_0 *eu_v2; const struct usb_audio20_effect_unit *ef_v2; } u; struct uaudio_search_result usr; struct uaudio_terminal_node *root; }; struct uaudio_format { uint16_t wFormat; uint8_t bPrecision; uint32_t freebsd_fmt; const char *description; }; static const struct uaudio_format uaudio10_formats[] = { {UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; static const struct uaudio_format uaudio20_formats[] = { {UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; /* prototypes */ static device_probe_t uaudio_probe; static device_attach_t uaudio_attach; static device_detach_t uaudio_detach; static usb_callback_t uaudio_chan_play_callback; static usb_callback_t uaudio_chan_play_sync_callback; static usb_callback_t uaudio_chan_record_callback; static usb_callback_t uaudio_chan_record_sync_callback; static usb_callback_t uaudio_mixer_write_cfg_callback; static usb_callback_t umidi_bulk_read_callback; static usb_callback_t umidi_bulk_write_callback; static usb_callback_t uaudio_hid_rx_callback; static usb_proc_callback_t uaudio_configure_msg; /* ==== USB mixer ==== */ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS); static void uaudio_mixer_ctl_free(struct uaudio_softc *); static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t, unsigned); static void uaudio_mixer_reload_all(struct uaudio_softc *); static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *); /* ==== USB audio v1.0 ==== */ static void uaudio_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static uint32_t uaudio_mixer_feature_get_bmaControls( const struct usb_audio_feature_unit *, uint8_t); static void uaudio_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing_updown(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_extension(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *); static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t); static int uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t, struct uaudio_mixer_node *); /* ==== USB audio v2.0 ==== */ static void uaudio20_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *); static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio20_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t, uint8_t, uint32_t); /* USB audio v1.0 and v2.0 */ static void uaudio_chan_fill_info_sub(struct uaudio_softc *, struct usb_device *, uint32_t, uint8_t, uint8_t); static void uaudio_chan_fill_info(struct uaudio_softc *, struct usb_device *); static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_fill_info(struct uaudio_softc *, struct usb_device *, void *); static int uaudio_mixer_signext(uint8_t, int); static void uaudio_mixer_init(struct uaudio_softc *, unsigned); static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t); static struct umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *); static void umidi_start_read(struct usb_fifo *); static void umidi_stop_read(struct usb_fifo *); static void umidi_start_write(struct usb_fifo *); static void umidi_stop_write(struct usb_fifo *); static int umidi_open(struct usb_fifo *, int); static int umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int); static void umidi_close(struct usb_fifo *, int); static void umidi_init(device_t dev); static int umidi_probe(device_t dev); static int umidi_detach(device_t dev); static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa); static void uaudio_hid_detach(struct uaudio_softc *sc); #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc( const usb_endpoint_descriptor_audio_t *); #endif static const struct usb_config uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_sync_callback, }, }; static const struct usb_config uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_sync_callback, }, }; static const struct usb_config uaudio_mixer_config[1] = { [0] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + 4), .callback = &uaudio_mixer_write_cfg_callback, .timeout = 1000, /* 1 second */ }, }; static const uint8_t umidi_cmd_to_len[16] = { [0x0] = 0, /* reserved */ [0x1] = 0, /* reserved */ [0x2] = 2, /* bytes */ [0x3] = 3, /* bytes */ [0x4] = 3, /* bytes */ [0x5] = 1, /* bytes */ [0x6] = 2, /* bytes */ [0x7] = 3, /* bytes */ [0x8] = 3, /* bytes */ [0x9] = 3, /* bytes */ [0xA] = 3, /* bytes */ [0xB] = 3, /* bytes */ [0xC] = 2, /* bytes */ [0xD] = 2, /* bytes */ [0xE] = 3, /* bytes */ [0xF] = 1, /* bytes */ }; static const struct usb_config umidi_config[UMIDI_N_TRANSFER] = { [UMIDI_TX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMIDI_TX_BUFFER, .flags = {.no_pipe_ok = 1}, .callback = &umidi_bulk_write_callback, }, [UMIDI_RX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 4, /* bytes */ .flags = {.short_xfer_ok = 1,.proxy_buffer = 1,.no_pipe_ok = 1}, .callback = &umidi_bulk_read_callback, }, }; static const struct usb_config uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = { [UAUDIO_HID_RX_TRANSFER] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_hid_rx_callback, }, }; static device_method_t uaudio_methods[] = { DEVMETHOD(device_probe, uaudio_probe), DEVMETHOD(device_attach, uaudio_attach), DEVMETHOD(device_detach, uaudio_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t uaudio_driver = { .name = "uaudio", .methods = uaudio_methods, .size = sizeof(struct uaudio_softc), }; /* The following table is derived from Linux's quirks-table.h */ static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = { { USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */ }; static const STRUCT_USB_HOST_ID __used uaudio_devs[] = { /* Generic USB audio class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),}, /* Generic USB MIDI class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),}, }; static unsigned uaudio_get_child_index_by_dev(struct uaudio_softc *sc, device_t dev) { unsigned i; for (i = 0; i != UAUDIO_MAX_CHILD; i++) { if (dev == sc->sc_child[i].pcm_device) return (i); } panic("uaudio_get_child_index_dev: Invalid device: %p\n", dev); return (0); } static unsigned uaudio_get_child_index_by_chan(struct uaudio_softc *sc, struct uaudio_chan *ch) { unsigned i; for (i = 0; i != UAUDIO_MAX_CHILD; i++) { if ((sc->sc_play_chan + i) == ch || (sc->sc_rec_chan + i) == ch) return (i); } panic("uaudio_get_child_index_by_chan: Invalid chan: %p\n", ch); return (0); } static int uaudio_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); /* lookup non-standard device(s) */ if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { return (BUS_PROBE_SPECIFIC); } if (uaa->info.bInterfaceClass != UICLASS_AUDIO) { if (uaa->info.bInterfaceClass != UICLASS_VENDOR || usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0) return (ENXIO); } /* check for AUDIO control interface */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) { if (usb_test_quirk(uaa, UQ_BAD_AUDIO)) return (ENXIO); else return (BUS_PROBE_GENERIC); } /* check for MIDI stream */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) { if (usb_test_quirk(uaa, UQ_BAD_MIDI)) return (ENXIO); else return (BUS_PROBE_GENERIC); } return (ENXIO); } /* * Set Cmedia CM6206 S/PDIF settings * Source: CM6206 Datasheet v2.3. */ static int uaudio_set_spdif_cm6206(struct uaudio_softc *sc, int flags) { uint8_t cmd[2][4] = { {0x20, 0x20, 0x00, 0}, {0x20, 0x30, 0x02, 1} }; int i; if (flags & UAUDIO_SPDIF_OUT) cmd[1][1] = 0x00; else cmd[1][1] = 0x02; if (flags & UAUDIO_SPDIF_OUT_96K) cmd[0][1] = 0x60; /* 96K: 3'b110 */ if (flags & UAUDIO_SPDIF_IN_MIX) cmd[1][1] = 0x03; /* SPDIFMIX */ for (i = 0; i < 2; i++) { if (usbd_req_set_report(sc->sc_udev, NULL, cmd[i], sizeof(cmd[0]), sc->sc_mixer_iface_index, UHID_OUTPUT_REPORT, 0) != 0) { return (ENXIO); } } return (0); } static int uaudio_set_spdif_dummy(struct uaudio_softc *sc, int flags) { return (0); } static usb_error_t uaudio_force_power_save(struct uaudio_softc *sc, uint8_t iface_index) { struct usb_interface *iface; usb_error_t err; iface = usbd_get_iface(sc->sc_udev, iface_index); if (iface == NULL || iface->idesc == NULL) return (USB_ERR_INVAL); /* check if correct alternate setting is already selected */ if (iface->alt_index == 0) { /* force power save mode by selecting default alternate setting */ err = usbd_req_set_alt_interface_no(sc->sc_udev, NULL, iface_index, iface->idesc->bAlternateSetting); } else { err = usbd_set_alt_interface_index(sc->sc_udev, iface_index, 0); } return (err); } static int uaudio_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uaudio_softc *sc = device_get_softc(dev); struct usb_interface_descriptor *id; usb_error_t err; unsigned i; sc->sc_udev = uaa->device; sc->sc_mixer_iface_index = uaa->info.bIfaceIndex; sc->sc_mixer_iface_no = uaa->info.bIfaceNum; sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[0].sc = sc; sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[1].sc = sc; if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR)) sc->sc_uq_audio_swap_lr = 1; if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC)) sc->sc_uq_au_inp_async = 1; if (usb_test_quirk(uaa, UQ_AU_NO_XU)) sc->sc_uq_au_no_xu = 1; if (usb_test_quirk(uaa, UQ_BAD_ADC)) sc->sc_uq_bad_adc = 1; if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS)) sc->sc_uq_au_vendor_class = 1; /* set S/PDIF function */ if (usb_test_quirk(uaa, UQ_AU_SET_SPDIF_CM6206)) sc->sc_set_spdif_fn = uaudio_set_spdif_cm6206; else sc->sc_set_spdif_fn = uaudio_set_spdif_dummy; umidi_init(dev); device_set_usb_desc(dev); id = usbd_get_interface_descriptor(uaa->iface); /* must fill mixer info before channel info */ uaudio_mixer_fill_info(sc, uaa->device, id); /* fill channel info */ uaudio_chan_fill_info(sc, uaa->device); DPRINTF("audio rev %d.%02x\n", sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); if (sc->sc_mixer_count == 0) { if (uaa->info.idVendor == USB_VENDOR_MAUDIO && (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA || uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) { DPRINTF("Generating mixer descriptors\n"); uaudio_mixer_controls_create_ftu(sc); } } DPRINTF("%d mixer controls\n", sc->sc_mixer_count); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { uint8_t x; if (sc->sc_play_chan[i].num_alt <= 0) break; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinite * re-enumeration loop: */ err = uaudio_force_power_save(sc, sc->sc_play_chan[i].usb_alt[0].iface_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_play_chan[i].num_alt; x++) { device_printf(dev, "Play[%u]: %d Hz, %d ch, %s format, " "2x%dms buffer.\n", i, sc->sc_play_chan[i].usb_alt[x].sample_rate, sc->sc_play_chan[i].usb_alt[x].channels, sc->sc_play_chan[i].usb_alt[x].p_fmt->description, uaudio_buffer_ms); } } if (i == 0) device_printf(dev, "No playback.\n"); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { uint8_t x; if (sc->sc_rec_chan[i].num_alt <= 0) break; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinite * re-enumeration loop: */ err = uaudio_force_power_save(sc, sc->sc_rec_chan[i].usb_alt[0].iface_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_rec_chan[i].num_alt; x++) { device_printf(dev, "Record[%u]: %d Hz, %d ch, %s format, " "2x%dms buffer.\n", i, sc->sc_rec_chan[i].usb_alt[x].sample_rate, sc->sc_rec_chan[i].usb_alt[x].channels, sc->sc_rec_chan[i].usb_alt[x].p_fmt->description, uaudio_buffer_ms); } } if (i == 0) device_printf(dev, "No recording.\n"); if (sc->sc_midi_chan.valid == 0) { if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { sc->sc_midi_chan.iface_index = (uint8_t)uaa->driver_info; sc->sc_midi_chan.iface_alt_index = 0; sc->sc_midi_chan.valid = 1; } } if (sc->sc_midi_chan.valid) { if (umidi_probe(dev)) { goto detach; } device_printf(dev, "MIDI sequencer.\n"); } else { device_printf(dev, "No MIDI sequencer.\n"); } DPRINTF("doing child attach\n"); /* attach the children */ sc->sc_sndcard_func.func = SCF_PCM; /* * Only attach a PCM device if we have a playback, recording * or mixer device present: */ for (i = 0; i != UAUDIO_MAX_CHILD; i++) { if (sc->sc_play_chan[i].num_alt <= 0 && sc->sc_rec_chan[i].num_alt <= 0 && sc->sc_child[i].mix_info == 0) continue; sc->sc_child[i].pcm_device = device_add_child(dev, "pcm", -1); if (sc->sc_child[i].pcm_device == NULL) { DPRINTF("out of memory\n"); goto detach; } device_set_ivars(sc->sc_child[i].pcm_device, &sc->sc_sndcard_func); } if (bus_generic_attach(dev)) { DPRINTF("child attach failed\n"); goto detach; } if (uaudio_handle_hid) { if (uaudio_hid_probe(sc, uaa) == 0) { device_printf(dev, "HID volume keys found.\n"); } else { device_printf(dev, "No HID volume keys found.\n"); } } /* reload all mixer settings */ uaudio_mixer_reload_all(sc); /* enable S/PDIF output, if any */ if (sc->sc_set_spdif_fn(sc, UAUDIO_SPDIF_OUT | UAUDIO_SPDIF_OUT_48K) != 0) { device_printf(dev, "Failed to enable S/PDIF at 48K\n"); } return (0); /* success */ detach: uaudio_detach(dev); return (ENXIO); } static void uaudio_pcm_setflags(device_t dev, uint32_t flags) { pcm_setflags(dev, pcm_getflags(dev) | flags); } int uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); unsigned i = uaudio_get_child_index_by_dev(sc, dev); char status[SND_STATUSLEN], desc[128]; uaudio_mixer_init(sc, i); if (sc->sc_uq_audio_swap_lr) { DPRINTF("hardware has swapped left and right\n"); /* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */ } if (sc->sc_play_chan[i].num_alt > 0 && (sc->sc_child[i].mix_info & SOUND_MASK_PCM) == 0) { DPRINTF("software controlled main volume\n"); /* * Emulate missing pcm mixer controller * through FEEDER_VOLUME */ uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL); } if (sc->sc_pcm_bitperfect) { DPRINTF("device needs bitperfect by default\n"); uaudio_pcm_setflags(dev, SD_F_BITPERFECT); } if (mixer_init(dev, mixer_class, sc)) goto detach; sc->sc_child[i].mixer_init = 1; mixer_hwvol_init(dev); snprintf(desc, sizeof(desc), "%s %s", usb_get_manufacturer(sc->sc_udev), usb_get_product(sc->sc_udev)); device_set_desc_copy(dev, desc); - snprintf(status, sizeof(status), "at %s %s", - device_get_nameunit(device_get_parent(dev)), - PCM_KLDSTRING(snd_uaudio)); + snprintf(status, sizeof(status), "on %s", + device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, sc, (sc->sc_play_chan[i].num_alt > 0) ? 1 : 0, (sc->sc_rec_chan[i].num_alt > 0) ? 1 : 0)) { goto detach; } uaudio_pcm_setflags(dev, SD_F_MPSAFE); sc->sc_child[i].pcm_registered = 1; if (sc->sc_play_chan[i].num_alt > 0) { sc->sc_play_chan[i].priv_sc = sc; pcm_addchan(dev, PCMDIR_PLAY, chan_class, &sc->sc_play_chan[i]); } if (sc->sc_rec_chan[i].num_alt > 0) { sc->sc_rec_chan[i].priv_sc = sc; pcm_addchan(dev, PCMDIR_REC, chan_class, &sc->sc_rec_chan[i]); } pcm_setstatus(dev, status); uaudio_mixer_register_sysctl(sc, dev, i); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan[i].feedback_rate, 0, "Feedback sample rate in Hz"); return (0); /* success */ detach: uaudio_detach_sub(dev); return (ENXIO); } int uaudio_detach_sub(device_t dev) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); unsigned i = uaudio_get_child_index_by_dev(sc, dev); int error = 0; repeat: if (sc->sc_child[i].pcm_registered) { error = pcm_unregister(dev); } else { if (sc->sc_child[i].mixer_init) error = mixer_uninit(dev); } if (error) { device_printf(dev, "Waiting for sound application to exit!\n"); usb_pause_mtx(NULL, 2 * hz); goto repeat; /* try again */ } return (0); /* success */ } static int uaudio_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); unsigned i; /* * Stop USB transfers early so that any audio applications * will time out and close opened /dev/dspX.Y device(s), if * any. */ usb_proc_explore_lock(sc->sc_udev); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { sc->sc_play_chan[i].operation = CHAN_OP_DRAIN; sc->sc_rec_chan[i].operation = CHAN_OP_DRAIN; } usb_proc_explore_mwait(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); usb_proc_explore_unlock(sc->sc_udev); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { usbd_transfer_unsetup(sc->sc_play_chan[i].xfer, UAUDIO_NCHANBUFS + 1); usbd_transfer_unsetup(sc->sc_rec_chan[i].xfer, UAUDIO_NCHANBUFS + 1); } uaudio_hid_detach(sc); if (bus_generic_detach(dev) != 0) { DPRINTF("detach failed!\n"); } sbuf_delete(&sc->sc_sndstat); sc->sc_sndstat_valid = 0; umidi_detach(dev); /* free mixer data */ uaudio_mixer_ctl_free(sc); /* disable S/PDIF output, if any */ (void) sc->sc_set_spdif_fn(sc, 0); return (0); } static uint32_t uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt) { struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt]; /* We use 2 times 8ms of buffer */ uint32_t buf_size = chan_alt->sample_size * howmany(chan_alt->sample_rate * (UAUDIO_NFRAMES / 8), 1000); return (buf_size); } static void uaudio_configure_msg_sub(struct uaudio_softc *sc, struct uaudio_chan *chan, int dir) { struct uaudio_chan_alt *chan_alt; uint32_t frames; uint32_t buf_size; uint16_t fps; uint8_t next_alt; uint8_t fps_shift; uint8_t operation; usb_error_t err; if (chan->num_alt <= 0) return; DPRINTF("\n"); usb_proc_explore_lock(sc->sc_udev); operation = chan->operation; switch (operation) { case CHAN_OP_START: case CHAN_OP_STOP: chan->operation = CHAN_OP_NONE; break; default: break; } usb_proc_explore_unlock(sc->sc_udev); switch (operation) { case CHAN_OP_STOP: /* Unsetup prior USB transfers, if any. */ usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); mtx_lock(chan->pcm_mtx); chan->cur_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); /* * The first alternate setting is typically used for * power saving mode. Set this alternate setting as * part of entering stop. */ err = usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, 0); if (err) { DPRINTF("setting of default alternate index failed: %s!\n", usbd_errstr(err)); } return; case CHAN_OP_START: /* Unsetup prior USB transfers, if any. */ usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); break; default: return; } mtx_lock(chan->pcm_mtx); next_alt = chan->set_alt; mtx_unlock(chan->pcm_mtx); chan_alt = chan->usb_alt + next_alt; err = usbd_set_alt_interface_index(sc->sc_udev, chan_alt->iface_index, chan_alt->iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); goto error; } /* * Only set the sample rate if the channel reports that it * supports the frequency control. */ if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { unsigned int x; for (x = 0; x != 256; x++) { if (dir == PCMDIR_PLAY) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } if (uaudio20_set_speed(sc->sc_udev, sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting * the speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } } else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) { if (uaudio_set_speed(sc->sc_udev, chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting the * speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer, chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) { DPRINTF("could not allocate USB transfers!\n"); goto error; } fps = usbd_get_isoc_fps(sc->sc_udev); if (fps < 8000) { /* FULL speed USB */ frames = uaudio_buffer_ms; } else { /* HIGH speed USB */ frames = uaudio_buffer_ms * 8; } fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]); /* down shift number of frames per second, if any */ fps >>= fps_shift; frames >>= fps_shift; /* bytes per frame should not be zero */ chan->bytes_per_frame[0] = ((chan_alt->sample_rate / fps) * chan_alt->sample_size); chan->bytes_per_frame[1] = howmany(chan_alt->sample_rate, fps) * chan_alt->sample_size; /* setup data rate dithering, if any */ chan->frames_per_second = fps; chan->sample_rem = chan_alt->sample_rate % fps; chan->sample_curr = 0; /* compute required buffer size */ buf_size = (chan->bytes_per_frame[1] * frames); if (buf_size > (chan->end - chan->start)) { DPRINTF("buffer size is too big\n"); goto error; } chan->intr_frames = frames; DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem); if (chan->intr_frames == 0) { DPRINTF("frame shift is too high!\n"); goto error; } #if (UAUDIO_NCHANBUFS != 2) #error "Please update code below!" #endif mtx_lock(chan->pcm_mtx); chan->cur_alt = next_alt; usbd_transfer_start(chan->xfer[0]); usbd_transfer_start(chan->xfer[1]); mtx_unlock(chan->pcm_mtx); return; error: usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); mtx_lock(chan->pcm_mtx); chan->cur_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); } static void uaudio_configure_msg(struct usb_proc_msg *pm) { struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc; unsigned i; usb_proc_explore_unlock(sc->sc_udev); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { uaudio_configure_msg_sub(sc, &sc->sc_play_chan[i], PCMDIR_PLAY); uaudio_configure_msg_sub(sc, &sc->sc_rec_chan[i], PCMDIR_REC); } usb_proc_explore_lock(sc->sc_udev); } /*========================================================================* * AS - Audio Stream - routines *========================================================================*/ #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed) { if (ed) { DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n" "bEndpointAddress=%d bmAttributes=0x%x \n" "wMaxPacketSize=%d bInterval=%d \n" "bRefresh=%d bSynchAddress=%d\n", ed, ed->bLength, ed->bDescriptorType, ed->bEndpointAddress, ed->bmAttributes, UGETW(ed->wMaxPacketSize), ed->bInterval, UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0, UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0); } } #endif /* * The following is a workaround for broken no-name USB audio devices * sold by dealextreme called "3D sound". The problem is that the * manufacturer computed wMaxPacketSize is too small to hold the * actual data sent. In other words the device sometimes sends more * data than it actually reports it can send in a single isochronous * packet. */ static void uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep, uint32_t xps, uint32_t add) { uint32_t mps; mps = UGETW(ep->wMaxPacketSize); /* * If the device indicates it can send more data than what the * sample rate indicates, we apply the workaround. */ if (mps > xps) { /* allow additional data */ xps += add; /* check against the maximum USB 1.x length */ if (xps > 1023) xps = 1023; /* check if we should do an update */ if (mps < xps) { /* simply update the wMaxPacketSize field */ USETW(ep->wMaxPacketSize, xps); DPRINTF("Workaround: Updated wMaxPacketSize " "from %d to %d bytes.\n", (int)mps, (int)xps); } } } static usb_error_t uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t rate) { struct usb_device_request req; usb_error_t error; #define UAUDIO20_MAX_RATES 32 /* we support at maximum 32 rates */ uint8_t data[2 + UAUDIO20_MAX_RATES * 12]; uint16_t actlen; uint16_t rates; uint16_t x; DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n", iface_no, clockid, rate); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UA20_CS_RANGE; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); /* * Assume there is at least one rate to begin with, else some * devices might refuse to return the USB descriptor: */ USETW(req.wLength, (2 + 1 * 12)); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) { /* * Likely the descriptor doesn't fit into the supplied * buffer. Try using a larger buffer and see if that * helps: */ rates = MIN(UAUDIO20_MAX_RATES, (255 - 2) / 12); error = USB_ERR_INVAL; } else { rates = UGETW(data); if (rates > UAUDIO20_MAX_RATES) { DPRINTF("Too many rates truncating to %d\n", UAUDIO20_MAX_RATES); rates = UAUDIO20_MAX_RATES; error = USB_ERR_INVAL; } else if (rates > 1) { DPRINTF("Need to read full rate descriptor\n"); error = USB_ERR_INVAL; } } if (error != 0) { /* * Try to read full rate descriptor. */ actlen = (2 + rates * 12); USETW(req.wLength, actlen); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) return (USB_ERR_INVAL); rates = UGETW(data); } actlen = (actlen - 2) / 12; if (rates > actlen) { DPRINTF("Too many rates truncating to %d\n", actlen); rates = actlen; } for (x = 0; x != rates; x++) { uint32_t min = UGETDW(data + 2 + (12 * x)); uint32_t max = UGETDW(data + 6 + (12 * x)); uint32_t res = UGETDW(data + 10 + (12 * x)); if (res == 0) { DPRINTF("Zero residue\n"); res = 1; } if (min > max) { DPRINTF("Swapped max and min\n"); uint32_t temp; temp = min; min = max; max = temp; } if (rate >= min && rate <= max && (((rate - min) % res) == 0)) { return (0); } } return (USB_ERR_INVAL); } static struct uaudio_chan * uaudio_get_chan(struct uaudio_softc *sc, struct uaudio_chan *chan, uint8_t iface_index) { unsigned i; for (i = 0; i != UAUDIO_MAX_CHILD; i++, chan++) { if (chan->num_alt == 0) { chan->iface_index = iface_index; return (chan); } else if (chan->iface_index == iface_index) return (chan); } return (NULL); } static void uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev, uint32_t rate, uint8_t channels, uint8_t bit_resolution) { struct usb_descriptor *desc = NULL; union uaudio_asid asid = { NULL }; union uaudio_asf1d asf1d = { NULL }; union uaudio_sed sed = { NULL }; struct usb_midi_streaming_endpoint_descriptor *msid = NULL; usb_endpoint_descriptor_audio_t *ed1 = NULL; const struct usb_audio_control_descriptor *acdp = NULL; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); struct usb_interface_descriptor *id; const struct uaudio_format *p_fmt = NULL; struct uaudio_chan *chan; struct uaudio_chan_alt *chan_alt; uint32_t format; uint16_t curidx = 0xFFFF; uint16_t lastidx = 0xFFFF; uint16_t alt_index = 0; uint16_t audio_rev = 0; uint16_t x; uint8_t ep_dir; uint8_t bChannels; uint8_t bBitResolution; uint8_t audio_if = 0; uint8_t midi_if = 0; uint8_t uma_if_class; while ((desc = usb_desc_foreach(cd, desc))) { if ((desc->bDescriptorType == UDESC_INTERFACE) && (desc->bLength >= sizeof(*id))) { id = (void *)desc; if (id->bInterfaceNumber != lastidx) { lastidx = id->bInterfaceNumber; curidx++; alt_index = 0; } else { alt_index++; } if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) && (id->bInterfaceClass == UICLASS_HID) && (id->bInterfaceSubClass == 0) && (id->bInterfaceProtocol == 0) && (alt_index == 0) && usbd_get_iface(udev, curidx) != NULL) { DPRINTF("Found HID interface at %d\n", curidx); sc->sc_hid.flags |= UAUDIO_HID_VALID; sc->sc_hid.iface_index = curidx; } uma_if_class = ((id->bInterfaceClass == UICLASS_AUDIO) || ((id->bInterfaceClass == UICLASS_VENDOR) && (sc->sc_uq_au_vendor_class != 0))); if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) { audio_if = 1; } else { audio_if = 0; } if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) { /* * XXX could allow multiple MIDI interfaces */ midi_if = 1; if ((sc->sc_midi_chan.valid == 0) && (usbd_get_iface(udev, curidx) != NULL)) { sc->sc_midi_chan.iface_index = curidx; sc->sc_midi_chan.iface_alt_index = alt_index; sc->sc_midi_chan.valid = 1; } } else { midi_if = 0; } asid.v1 = NULL; asf1d.v1 = NULL; ed1 = NULL; sed.v1 = NULL; /* * There can only be one USB audio instance * per USB device. Grab all USB audio * interfaces on this USB device so that we * don't attach USB audio twice: */ if (alt_index == 0 && curidx != sc->sc_mixer_iface_index && (id->bInterfaceClass == UICLASS_AUDIO || audio_if != 0 || midi_if != 0)) { usbd_set_parent_iface(sc->sc_udev, curidx, sc->sc_mixer_iface_index); } } if (audio_if == 0) { if (midi_if == 0) { if ((acdp == NULL) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) && (desc->bLength >= sizeof(*acdp))) { acdp = (void *)desc; audio_rev = UGETW(acdp->bcdADC); } } else { msid = (void *)desc; /* get the maximum number of embedded jacks in use, if any */ if (msid->bLength >= sizeof(*msid) && msid->bDescriptorType == UDESC_CS_ENDPOINT && msid->bDescriptorSubtype == MS_GENERAL && msid->bNumEmbMIDIJack > sc->sc_midi_chan.max_emb_jack) { sc->sc_midi_chan.max_emb_jack = msid->bNumEmbMIDIJack; } } /* * Don't collect any USB audio descriptors if * this is not an USB audio stream interface. */ continue; } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == AS_GENERAL) && (asid.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asid.v2)) { asid.v2 = (void *)desc; } } else { if (desc->bLength >= sizeof(*asid.v1)) { asid.v1 = (void *)desc; } } } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == FORMAT_TYPE) && (asf1d.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asf1d.v2)) asf1d.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*asf1d.v1)) { asf1d.v1 = (void *)desc; if (asf1d.v1->bFormatType != FORMAT_TYPE_I) { DPRINTFN(11, "ignored bFormatType = %d\n", asf1d.v1->bFormatType); asf1d.v1 = NULL; continue; } if (desc->bLength < (sizeof(*asf1d.v1) + ((asf1d.v1->bSamFreqType == 0) ? 6 : (asf1d.v1->bSamFreqType * 3)))) { DPRINTFN(11, "invalid descriptor, " "too short\n"); asf1d.v1 = NULL; continue; } } } } if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= UEP_MINSIZE) && (ed1 == NULL)) { ed1 = (void *)desc; if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) { ed1 = NULL; continue; } } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_ENDPOINT) && (desc->bDescriptorSubtype == AS_GENERAL) && (sed.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*sed.v2)) sed.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*sed.v1)) sed.v1 = (void *)desc; } } if (asid.v1 == NULL || asf1d.v1 == NULL || ed1 == NULL || sed.v1 == NULL) { /* need more descriptors */ continue; } ep_dir = UE_GET_DIR(ed1->bEndpointAddress); /* We ignore sync endpoint information until further. */ if (audio_rev >= UAUDIO_VERSION_30) { goto next_ep; } else if (audio_rev >= UAUDIO_VERSION_20) { uint32_t dwFormat; dwFormat = UGETDW(asid.v2->bmFormats); bChannels = asid.v2->bNrChannels; bBitResolution = asf1d.v2->bSubslotSize * 8; if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } for (p_fmt = uaudio20_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat & dwFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } for (x = 0; x != 256; x++) { if (ep_dir == UE_DIR_OUT) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } DPRINTF("Checking clock ID=%d\n", x); if (uaudio20_check_rate(udev, sc->sc_mixer_iface_no, x, rate)) { DPRINTF("Unsupported sampling " "rate, id=%d\n", x); goto next_ep; } } } else { uint16_t wFormat; wFormat = UGETW(asid.v1->wFormatTag); bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels); bBitResolution = asf1d.v1->bSubFrameSize * 8; if (asf1d.v1->bSamFreqType == 0) { DPRINTFN(16, "Sample rate: %d-%dHz\n", UA_SAMP_LO(asf1d.v1), UA_SAMP_HI(asf1d.v1)); if ((rate >= UA_SAMP_LO(asf1d.v1)) && (rate <= UA_SAMP_HI(asf1d.v1))) goto found_rate; } else { for (x = 0; x < asf1d.v1->bSamFreqType; x++) { DPRINTFN(16, "Sample rate = %dHz\n", UA_GETSAMP(asf1d.v1, x)); if (rate == UA_GETSAMP(asf1d.v1, x)) goto found_rate; } } goto next_ep; found_rate: for (p_fmt = uaudio10_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat == wFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } } chan = uaudio_get_chan(sc, (ep_dir == UE_DIR_OUT) ? &sc->sc_play_chan[0] : &sc->sc_rec_chan[0], curidx); if (chan == NULL) { DPRINTF("More than %d sub devices. (skipped)\n", UAUDIO_MAX_CHILD); goto next_ep; } if (usbd_get_iface(udev, curidx) == NULL) { DPRINTF("Interface is not valid\n"); goto next_ep; } if (chan->num_alt == CHAN_MAX_ALT) { DPRINTF("Too many alternate settings\n"); goto next_ep; } chan->set_alt = 0; chan->cur_alt = CHAN_MAX_ALT; chan_alt = &chan->usb_alt[chan->num_alt++]; #ifdef USB_DEBUG uaudio_chan_dump_ep_desc(ed1); #endif DPRINTF("Sample rate = %dHz, channels = %d, " "bits = %d, format = %s, ep 0x%02x, chan %p\n", rate, channels, bit_resolution, p_fmt->description, ed1->bEndpointAddress, chan); chan_alt->sample_rate = rate; chan_alt->p_asf1d = asf1d; chan_alt->p_ed1 = ed1; chan_alt->p_fmt = p_fmt; chan_alt->p_sed = sed; chan_alt->iface_index = curidx; chan_alt->iface_alt_index = alt_index; if (ep_dir == UE_DIR_IN) chan_alt->usb_cfg = uaudio_cfg_record; else chan_alt->usb_cfg = uaudio_cfg_play; chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) * p_fmt->bPrecision) / 8; chan_alt->channels = channels; if (ep_dir == UE_DIR_IN && usbd_get_speed(udev) == USB_SPEED_FULL) { uaudio_record_fix_fs(ed1, chan_alt->sample_size * (rate / 1000), chan_alt->sample_size * (rate / 4000)); } /* setup play/record format */ format = chan_alt->p_fmt->freebsd_fmt; /* get default SND_FORMAT() */ format = SND_FORMAT(format, chan_alt->channels, 0); switch (chan_alt->channels) { uint32_t temp_fmt; case 1: case 2: /* mono and stereo */ break; default: /* surround and more */ temp_fmt = feeder_matrix_default_format(format); /* if multichannel, then format can be zero */ if (temp_fmt != 0) format = temp_fmt; break; } /* check if format is not supported */ if (format == 0) { DPRINTF("The selected audio format is not supported\n"); chan->num_alt--; goto next_ep; } if (chan->num_alt > 1) { /* we only accumulate one format at different sample rates */ if (chan->pcm_format[0] != format) { DPRINTF("Multiple formats is not supported\n"); chan->num_alt--; goto next_ep; } /* ignore if duplicate sample rate entry */ if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) { DPRINTF("Duplicate sample rate detected\n"); chan->num_alt--; goto next_ep; } } chan->pcm_cap.fmtlist = chan->pcm_format; chan->pcm_cap.fmtlist[0] = format; /* check if device needs bitperfect */ if (chan_alt->channels > UAUDIO_MATRIX_MAX) sc->sc_pcm_bitperfect = 1; if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0) chan->pcm_cap.minspeed = rate; if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0) chan->pcm_cap.maxspeed = rate; if (sc->sc_sndstat_valid != 0) { sbuf_printf(&sc->sc_sndstat, "\n\t" "mode %d.%d:(%s) %dch, %dbit, %s, %dHz", curidx, alt_index, (ep_dir == UE_DIR_IN) ? "input" : "output", channels, p_fmt->bPrecision, p_fmt->description, rate); } next_ep: sed.v1 = NULL; ed1 = NULL; } } /* This structure defines all the supported rates. */ static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = { 384000, 352800, 192000, 176400, 96000, 88200, 88000, 80000, 72000, 64000, 56000, 48000, 44100, 40000, 32000, 24000, 22050, 16000, 11025, 8000, 0 }; static void uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev) { uint32_t rate = uaudio_default_rate; uint8_t z; uint8_t bits = uaudio_default_bits; uint8_t y; uint8_t channels = uaudio_default_channels; uint8_t x; bits -= (bits % 8); if ((bits == 0) || (bits > 32)) { /* set a valid value */ bits = 32; } if (channels == 0) { switch (usbd_get_speed(udev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: /* * Due to high bandwidth usage and problems * with HIGH-speed split transactions we * disable surround setups on FULL-speed USB * by default */ channels = 4; break; default: channels = UAUDIO_CHANNELS_MAX; break; } } else if (channels > UAUDIO_CHANNELS_MAX) channels = UAUDIO_CHANNELS_MAX; if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) sc->sc_sndstat_valid = 1; /* try to search for a valid config */ for (x = channels; x; x--) { for (y = bits; y; y -= 8) { /* try user defined rate, if any */ if (rate != 0) uaudio_chan_fill_info_sub(sc, udev, rate, x, y); /* try find a matching rate, if any */ for (z = 0; uaudio_rate_list[z]; z++) uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y); } } if (sc->sc_sndstat_valid) sbuf_finish(&sc->sc_sndstat); } static void uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint64_t sample_rate; uint8_t buf[4]; uint64_t temp; unsigned i; int len; int actlen; int nframes; usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); i = uaudio_get_child_index_by_chan(ch->priv_sc, ch); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "transferred %d bytes\n", actlen); if (nframes == 0) break; len = usbd_xfer_frame_len(xfer, 0); if (len == 0) break; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); temp = UGETDW(buf); DPRINTF("Value = 0x%08x\n", (int)temp); /* auto-detect SYNC format */ if (len == 4) temp &= 0x0fffffff; /* check for no data */ if (temp == 0) break; temp *= 125ULL; sample_rate = ch->usb_alt[ch->cur_alt].sample_rate; /* auto adjust */ while (temp < (sample_rate - (sample_rate / 4))) temp *= 2; while (temp > (sample_rate + (sample_rate / 2))) temp /= 2; DPRINTF("Comparing %d Hz :: %d Hz\n", (int)temp, (int)sample_rate); /* * Use feedback value as fallback when there is no * recording channel: */ if (ch->priv_sc->sc_rec_chan[i].num_alt == 0) { int32_t jitter_max = howmany(sample_rate, 16000); /* * Range check the jitter values to avoid * bogus sample rate adjustments. The expected * deviation should not be more than 1Hz per * second. The USB v2.0 specification also * mandates this requirement. Refer to chapter * 5.12.4.2 about feedback. */ ch->jitter_curr = temp - sample_rate; if (ch->jitter_curr > jitter_max) ch->jitter_curr = jitter_max; else if (ch->jitter_curr < -jitter_max) ch->jitter_curr = -jitter_max; } ch->feedback_rate = temp; break; case USB_ST_SETUP: /* * Check if the recording stream can be used as a * source of jitter information to save some * isochronous bandwidth: */ if (ch->priv_sc->sc_rec_chan[i].num_alt != 0 && uaudio_debug == 0) break; usbd_xfer_set_frames(xfer, 1); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ break; } } static int uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt) { uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes; return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC); } static void uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct uaudio_chan *ch_rec; struct usb_page_cache *pc; uint32_t mfl; uint32_t total; uint32_t blockcount; uint32_t n; uint32_t offset; unsigned i; int sample_size; int actlen; int sumlen; if (ch->running == 0 || ch->start == ch->end) { DPRINTF("not running or no buffer!\n"); return; } i = uaudio_get_child_index_by_chan(ch->priv_sc, ch); /* check if there is a valid record channel */ ch_rec = ch->priv_sc->sc_rec_chan + i; if (ch_rec->num_alt == 0) ch_rec = NULL; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: tr_setup: if (ch_rec != NULL) { /* * NOTE: The play and record callbacks are * executed from the same USB thread and * locking the record channel mutex here is * not needed. This avoids a LOR situation. */ /* reset receive jitter counters */ ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; } /* reset transmit jitter counters */ ch->jitter_curr = 0; ch->jitter_rem = 0; /* FALLTHROUGH */ case USB_ST_TRANSFERRED: if (actlen < sumlen) { DPRINTF("short transfer, " "%d of %d bytes\n", actlen, sumlen); } chn_intr(ch->pcm_ch); /* * Check for asynchronous playback endpoint and that * the playback endpoint is properly configured: */ if (ch_rec != NULL && uaudio_chan_is_async(ch, ch->cur_alt) != 0) { uint32_t rec_alt = ch_rec->cur_alt; if (rec_alt < ch_rec->num_alt) { int64_t tx_jitter; int64_t rx_rate; /* * NOTE: The play and record callbacks * are executed from the same USB * thread and locking the record * channel mutex here is not needed. * This avoids a LOR situation. */ /* translate receive jitter into transmit jitter */ tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate; tx_jitter = (tx_jitter * ch_rec->jitter_curr) + ch->jitter_rem; /* reset receive jitter counters */ ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; /* compute exact number of transmit jitter samples */ rx_rate = ch_rec->usb_alt[rec_alt].sample_rate; ch->jitter_curr += tx_jitter / rx_rate; ch->jitter_rem = tx_jitter % rx_rate; } } /* start the SYNC transfer one time per second, if any */ ch->intr_counter += ch->intr_frames; if (ch->intr_counter >= ch->frames_per_second) { ch->intr_counter -= ch->frames_per_second; usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]); } mfl = usbd_xfer_max_framelen(xfer); if (ch->bytes_per_frame[1] > mfl) { DPRINTF("bytes per transfer, %d, " "exceeds maximum, %d!\n", ch->bytes_per_frame[1], mfl); break; } blockcount = ch->intr_frames; /* setup number of frames */ usbd_xfer_set_frames(xfer, blockcount); /* get sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; /* reset total length */ total = 0; /* setup frame lengths */ for (n = 0; n != blockcount; n++) { uint32_t frame_len; ch->sample_curr += ch->sample_rem; if (ch->sample_curr >= ch->frames_per_second) { ch->sample_curr -= ch->frames_per_second; frame_len = ch->bytes_per_frame[1]; } else { frame_len = ch->bytes_per_frame[0]; } /* handle free running clock case */ if (ch->jitter_curr > 0 && (frame_len + sample_size) <= mfl) { DPRINTFN(6, "sending one sample more\n"); ch->jitter_curr--; frame_len += sample_size; } else if (ch->jitter_curr < 0 && frame_len >= sample_size) { DPRINTFN(6, "sending one sample less\n"); ch->jitter_curr++; frame_len -= sample_size; } usbd_xfer_set_frame_len(xfer, n, frame_len); total += frame_len; } DPRINTFN(6, "transferring %d bytes\n", total); offset = 0; pc = usbd_xfer_get_frame(xfer, 0); while (total > 0) { n = (ch->end - ch->cur); if (n > total) n = total; usbd_copy_in(pc, offset, ch->cur, n); total -= n; ch->cur += n; offset += n; if (ch->cur >= ch->end) ch->cur = ch->start; } usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } static void uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error) { /* TODO */ } static void uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t offset0; uint32_t mfl; int m; int n; int len; int actlen; int nframes; int expected_bytes; int sample_size; if (ch->start == ch->end) { DPRINTF("no buffer!\n"); return; } usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); mfl = usbd_xfer_max_framelen(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: offset0 = 0; pc = usbd_xfer_get_frame(xfer, 0); /* try to compute the number of expected bytes */ ch->sample_curr += (ch->sample_rem * ch->intr_frames); /* compute number of expected bytes */ expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) + ((ch->sample_curr / ch->frames_per_second) * (ch->bytes_per_frame[1] - ch->bytes_per_frame[0])); /* keep remainder */ ch->sample_curr %= ch->frames_per_second; /* get current sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; for (n = 0; n != nframes; n++) { uint32_t offset1 = offset0; len = usbd_xfer_frame_len(xfer, n); /* make sure we only receive complete samples */ len = len - (len % sample_size); /* subtract bytes received from expected payload */ expected_bytes -= len; /* don't receive data when not ready */ if (ch->running == 0 || ch->cur_alt != ch->set_alt) continue; /* fill ring buffer with samples, if any */ while (len > 0) { m = (ch->end - ch->cur); if (m > len) m = len; usbd_copy_out(pc, offset1, ch->cur, m); len -= m; offset1 += m; ch->cur += m; if (ch->cur >= ch->end) ch->cur = ch->start; } offset0 += mfl; } /* update current jitter */ ch->jitter_curr -= (expected_bytes / sample_size); /* don't allow a huge amount of jitter to accumulate */ nframes = 2 * ch->intr_frames; /* range check current jitter */ if (ch->jitter_curr < -nframes) ch->jitter_curr = -nframes; else if (ch->jitter_curr > nframes) ch->jitter_curr = nframes; DPRINTFN(6, "transferred %d bytes, jitter %d samples\n", actlen, ch->jitter_curr); if (ch->running != 0) chn_intr(ch->pcm_ch); case USB_ST_SETUP: tr_setup: nframes = ch->intr_frames; usbd_xfer_set_frames(xfer, nframes); for (n = 0; n != nframes; n++) usbd_xfer_set_frame_len(xfer, n, mfl); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } void * uaudio_chan_init(struct uaudio_chan *ch, struct snd_dbuf *b, struct pcm_channel *c, int dir) { uint32_t buf_size; uint8_t x; /* store mutex and PCM channel */ ch->pcm_ch = c; ch->pcm_mtx = c->lock; /* compute worst case buffer */ buf_size = 0; for (x = 0; x != ch->num_alt; x++) { uint32_t temp = uaudio_get_buffer_size(ch, x); if (temp > buf_size) buf_size = temp; } /* allow double buffering */ buf_size *= 2; DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size); ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO); if (ch->buf == NULL) goto error; if (sndbuf_setup(b, ch->buf, buf_size) != 0) goto error; ch->start = ch->buf; ch->end = ch->buf + buf_size; ch->cur = ch->buf; ch->pcm_buf = b; ch->max_buf = buf_size; if (ch->pcm_mtx == NULL) { DPRINTF("ERROR: PCM channels does not have a mutex!\n"); goto error; } return (ch); error: uaudio_chan_free(ch); return (NULL); } int uaudio_chan_free(struct uaudio_chan *ch) { if (ch->buf != NULL) { free(ch->buf, M_DEVBUF); ch->buf = NULL; } usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1); ch->num_alt = 0; return (0); } int uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize) { uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); sndbuf_setup(ch->pcm_buf, ch->buf, temp); return (temp / 2); } int uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, uint32_t blockcount) { return (1); } int uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) { struct uaudio_softc *sc; uint8_t x; sc = ch->priv_sc; for (x = 0; x < ch->num_alt; x++) { if (ch->usb_alt[x].sample_rate < speed) { /* sample rate is too low */ break; } } if (x != 0) x--; usb_proc_explore_lock(sc->sc_udev); ch->set_alt = x; usb_proc_explore_unlock(sc->sc_udev); DPRINTF("Selecting alt %d\n", (int)x); return (ch->usb_alt[x].sample_rate); } int uaudio_chan_getptr(struct uaudio_chan *ch) { return (ch->cur - ch->start); } struct pcmchan_caps * uaudio_chan_getcaps(struct uaudio_chan *ch) { return (&ch->pcm_cap); } static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = { .id = SND_CHN_MATRIX_DRV, .channels = 2, .ext = 0, .map = { /* Right */ [0] = { .type = SND_CHN_T_FR, .members = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR }, /* Left */ [1] = { .type = SND_CHN_T_FL, .members = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL }, [2] = { .type = SND_CHN_T_MAX, .members = 0 } }, .mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL, .offset = { 1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } }; struct pcmchan_matrix * uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format) { struct uaudio_softc *sc; sc = ch->priv_sc; if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 && AFMT_CHANNEL(format) == 2) return (&uaudio_chan_matrix_swap_2_0); return (feeder_matrix_format_map(format)); } int uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format) { DPRINTF("Selecting format 0x%08x\n", (unsigned int)format); return (0); } static void uaudio_chan_reconfigure(struct uaudio_chan *ch, uint8_t operation) { struct uaudio_softc *sc = ch->priv_sc; /* Check for shutdown. */ if (ch->operation == CHAN_OP_DRAIN) return; /* Set next operation. */ ch->operation = operation; /* * Because changing the alternate setting modifies the USB * configuration, this part must be executed from the USB * explore process. */ (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } static int uaudio_chan_need_both(struct uaudio_chan *pchan, struct uaudio_chan *rchan) { return (pchan->num_alt > 0 && pchan->running != 0 && uaudio_chan_is_async(pchan, pchan->set_alt) != 0 && rchan->num_alt > 0 && rchan->running == 0); } static int uaudio_chan_need_none(struct uaudio_chan *pchan, struct uaudio_chan *rchan) { return (pchan->num_alt > 0 && pchan->running == 0 && rchan->num_alt > 0 && rchan->running == 0); } void uaudio_chan_start(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; unsigned i = uaudio_get_child_index_by_chan(sc, ch); /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if not running */ if (ch->running == 0) { uint32_t temp; /* get current buffer size */ temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); /* set running flag */ ch->running = 1; /* ensure the hardware buffer is reset */ ch->start = ch->buf; ch->end = ch->buf + temp; ch->cur = ch->buf; if (uaudio_chan_need_both( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { /* * Start both endpoints because of need for * jitter information: */ uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_START); uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_START); } else { uaudio_chan_reconfigure(ch, CHAN_OP_START); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } void uaudio_chan_stop(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; unsigned i = uaudio_get_child_index_by_chan(sc, ch); /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if running */ if (ch->running != 0) { /* clear running flag */ ch->running = 0; if (uaudio_chan_need_both( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { /* * Leave the endpoints running because we need * information about jitter! */ } else if (uaudio_chan_need_none( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { /* * Stop both endpoints in case the one was used for * jitter information: */ uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_STOP); uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_STOP); } else { uaudio_chan_reconfigure(ch, CHAN_OP_STOP); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } /*========================================================================* * AC - Audio Controller - routines *========================================================================*/ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct uaudio_softc *sc; struct uaudio_mixer_node *pmc; int hint; int error; int temp = 0; int chan = 0; sc = (struct uaudio_softc *)oidp->oid_arg1; hint = oidp->oid_arg2; if (sc->sc_child[0].mixer_lock == NULL) return (ENXIO); /* lookup mixer node */ mtx_lock(sc->sc_child[0].mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { for (chan = 0; chan != (int)pmc->nchan; chan++) { if (pmc->wValue[chan] != -1 && pmc->wValue[chan] == hint) { temp = pmc->wData[chan]; goto found; } } } found: mtx_unlock(sc->sc_child[0].mixer_lock); error = sysctl_handle_int(oidp, &temp, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* update mixer value */ mtx_lock(sc->sc_child[0].mixer_lock); if (pmc != NULL && temp >= pmc->minval && temp <= pmc->maxval) { pmc->wData[chan] = temp; pmc->update[(chan / 8)] |= (1 << (chan % 8)); /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } mtx_unlock(sc->sc_child[0].mixer_lock); return (0); } static void uaudio_mixer_ctl_free(struct uaudio_softc *sc) { struct uaudio_mixer_node *p_mc; while ((p_mc = sc->sc_mixer_root) != NULL) { sc->sc_mixer_root = p_mc->next; free(p_mc, M_USBDEV); } } static void uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev, unsigned index) { struct uaudio_mixer_node *pmc; struct sysctl_oid *mixer_tree; struct sysctl_oid *control_tree; char buf[32]; int chan; int n; if (index != 0) return; mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); if (mixer_tree == NULL) return; for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next, n++) { for (chan = 0; chan < pmc->nchan; chan++) { if (pmc->nchan > 1) { snprintf(buf, sizeof(buf), "%s_%d_%d", pmc->name, n, chan); } else { snprintf(buf, sizeof(buf), "%s_%d", pmc->name, n); } control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Mixer control nodes"); if (control_tree == NULL) continue; SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "val", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, pmc->wValue[chan], uaudio_mixer_sysctl_handler, "I", "Current value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval, "Minimum value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval, "Maximum value"); SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0, "Description"); } } } /* M-Audio FastTrack Ultra Mixer Description */ /* Origin: Linux USB Audio driver */ static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc) { int chx; int chy; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(8, 0); MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect"; MIX(sc).minval = 0; MIX(sc).maxval = 7; MIX(sc).mul = 7; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chx = 0; chx != 8; chx++) { for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_rec"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "AIn%d - Out%d Record Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_play"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = (chx == chy) ? 2 : 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "DIn%d - Out%d Playback Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(2, 0); MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_vol"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(3, 0); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_dur"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f00; MIX(sc).mul = 0x7f00; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(4, 0); MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_fb"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no); for (chy = 0; chy != 4; chy++) { MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_ret"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Return %d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send AIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send DIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_reload_all(struct uaudio_softc *sc) { struct uaudio_mixer_node *pmc; int chan; if (sc->sc_child[0].mixer_lock == NULL) return; mtx_lock(sc->sc_child[0].mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { /* use reset defaults for non-oss controlled settings */ if (pmc->ctl == SOUND_MIXER_NRDEVICES) continue; for (chan = 0; chan < pmc->nchan; chan++) pmc->update[chan / 8] |= (1 << (chan % 8)); } usbd_transfer_start(sc->sc_mixer_xfer[0]); /* start HID volume keys, if any */ usbd_transfer_start(sc->sc_hid.xfer[0]); mtx_unlock(sc->sc_child[0].mixer_lock); } static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { struct uaudio_mixer_node *p_mc_new = malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK); int ch; if (p_mc_new != NULL) { memcpy(p_mc_new, mc, sizeof(*p_mc_new)); p_mc_new->next = sc->sc_mixer_root; sc->sc_mixer_root = p_mc_new; sc->sc_mixer_count++; /* set default value for all channels */ for (ch = 0; ch < p_mc_new->nchan; ch++) { switch (p_mc_new->val_default) { case 1: /* 50% */ p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2; break; case 2: /* 100% */ p_mc_new->wData[ch] = p_mc_new->maxval; break; default: /* 0% */ p_mc_new->wData[ch] = p_mc_new->minval; break; } } } else { DPRINTF("out of memory\n"); } } static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { int32_t res; DPRINTF("adding %d\n", mc->ctl); if (mc->type == MIX_ON_OFF) { mc->minval = 0; mc->maxval = 1; } else if (mc->type == MIX_SELECTOR) { } else { /* determine min and max values */ mc->minval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MIN, mc); mc->maxval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MAX, mc); /* check if max and min was swapped */ if (mc->maxval < mc->minval) { res = mc->maxval; mc->maxval = mc->minval; mc->minval = res; } /* compute value range */ mc->mul = mc->maxval - mc->minval; if (mc->mul == 0) mc->mul = 1; /* compute value alignment */ res = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_RES, mc); DPRINTF("Resolution = %d\n", (int)res); } uaudio_mixer_add_ctl_sub(sc, mc); #ifdef USB_DEBUG if (uaudio_debug > 2) { uint8_t i; for (i = 0; i < mc->nchan; i++) { DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]); } DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' " "min=%d max=%d\n", mc->wIndex, mc->type, mc->ctl, mc->minval, mc->maxval); } #endif } static void uaudio_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1; const struct usb_audio_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).type = MIX_SIGNED_16; if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio20_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2; const struct usb_audio20_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).type = MIX_SIGNED_16; if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio_mixer_check_selectors(struct uaudio_softc *sc) { uint8_t reserve_feature[] = { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, }; const uint16_t reserve_max = sizeof(reserve_feature) / sizeof(reserve_feature[0]); uint16_t i; uint16_t j; uint16_t k; /* remove existing selector types from the reserve */ for (i = 0; i < MIX(sc).maxval; i++) { if (MIX(sc).slctrtype[i] == SOUND_MIXER_NRDEVICES) continue; for (j = 0; j != reserve_max; j++) { if (reserve_feature[j] == MIX(sc).slctrtype[i]) reserve_feature[j] = SOUND_MIXER_NRDEVICES; } } /* make sure selector types are not overlapping */ for (i = 0; i < MIX(sc).maxval; i++) { if (MIX(sc).slctrtype[i] == SOUND_MIXER_NRDEVICES) continue; for (j = i + 1; j < MIX(sc).maxval; j++) { if (MIX(sc).slctrtype[j] == SOUND_MIXER_NRDEVICES) continue; if (MIX(sc).slctrtype[i] != MIX(sc).slctrtype[j]) continue; for (k = 0; k != reserve_max; k++) { if (reserve_feature[k] == SOUND_MIXER_NRDEVICES) continue; MIX(sc).slctrtype[j] = reserve_feature[k]; reserve_feature[k] = SOUND_MIXER_NRDEVICES; break; } if (k == reserve_max) { DPRINTF("Selector type %d is not selectable!\n", j); MIX(sc).slctrtype[j] = SOUND_MIXER_NRDEVICES; } } } } static void uaudio_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_selector_unit *d = iot[id].u.su_v1; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; MIX(sc).mul = MIX(sc).maxval - MIX(sc).minval; for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio_mixer_determine_class(&iot[d->baSourceId[i]]); } for (; i < MAX_SELECTOR_INPUT_PIN; i++) MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; uaudio_mixer_check_selectors(sc); uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio20_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_selector_unit *d = iot[id].u.su_v2; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; MIX(sc).mul = MIX(sc).maxval - MIX(sc).minval; for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio20_mixer_determine_class(&iot[d->baSourceId[i]]); } for (; i < MAX_SELECTOR_INPUT_PIN; i++) MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; uaudio_mixer_check_selectors(sc); uaudio_mixer_add_ctl(sc, &MIX(sc)); } static uint32_t uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d, uint8_t i) { uint32_t temp = 0; uint32_t offset = (i * d->bControlSize); if (d->bControlSize > 0) { temp |= d->bmaControls[offset]; if (d->bControlSize > 1) { temp |= d->bmaControls[offset + 1] << 8; if (d->bControlSize > 2) { temp |= d->bmaControls[offset + 2] << 16; if (d->bControlSize > 3) { temp |= d->bmaControls[offset + 3] << 24; } } } } return (temp); } static void uaudio_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_feature_unit *d = iot[id].u.fu_v1; uint32_t fumask; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t ctl; uint8_t i; if (d->bControlSize == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 7) / d->bControlSize; mmask = uaudio_mixer_feature_get_bmaControls(d, 0); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) { DPRINTFN(10, "chan=%d mask=%x\n", chan, uaudio_mixer_feature_get_bmaControls(d, chan)); cmask |= uaudio_mixer_feature_get_bmaControls(d, chan); } MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[nchan * d->bControlSize]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (nchan > MIX_MAX_CHAN) nchan = MIX_MAX_CHAN; for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) { fumask = FU_MASK(ctl); DPRINTFN(5, "ctl=%d fumask=0x%04x\n", ctl, fumask); if (mmask & fumask) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(ctl, 0); } else if (cmask & fumask) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask) MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } mixernumber = uaudio_mixer_determine_class(&iot[id]); switch (ctl) { case MUTE_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_MUTE; MIX(sc).name = "mute"; break; case VOLUME_CONTROL: MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; break; case BASS_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; break; case MID_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; break; case TREBLE_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; break; case GRAPHIC_EQUALIZER_CONTROL: continue; /* XXX don't add anything */ case AGC_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; break; case DELAY_CONTROL: MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; break; case BASS_BOOST_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; break; case LOUDNESS_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; break; default: MIX(sc).type = MIX_UNKNOWN; break; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio20_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2; uint32_t ctl; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t i; uint8_t what; if (UGETDW(d->bmaControls[0]) == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 6) / 4; mmask = UGETDW(d->bmaControls[0]); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) cmask |= UGETDW(d->bmaControls[chan]); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[nchan][0]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (nchan > MIX_MAX_CHAN) nchan = MIX_MAX_CHAN; for (ctl = 3; ctl != 0; ctl <<= 2) { mixernumber = uaudio20_mixer_determine_class(&iot[id]); switch (ctl) { case (3 << 0): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_MUTE; MIX(sc).name = "mute"; what = MUTE_CONTROL; break; case (3 << 2): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; what = VOLUME_CONTROL; break; case (3 << 4): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; what = BASS_CONTROL; break; case (3 << 6): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; what = MID_CONTROL; break; case (3 << 8): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; what = TREBLE_CONTROL; break; case (3 << 12): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; what = AGC_CONTROL; break; case (3 << 14): MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; what = DELAY_CONTROL; break; case (3 << 16): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; what = BASS_BOOST_CONTROL; break; case (3 << 18): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; what = LOUDNESS_CONTROL; break; case (3 << 20): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igain"; what = INPUT_GAIN_CONTROL; break; case (3 << 22): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igainpad"; what = INPUT_GAIN_PAD_CONTROL; break; default: continue; } if ((mmask & ctl) == ctl) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(what, 0); } else if ((cmask & ctl) == ctl) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if ((UGETDW(d->bmaControls[i]) & ctl) == ctl) MIX(sc).wValue[i - 1] = MAKE_WORD(what, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_add_processing_updown(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); const struct usb_audio_processing_unit_updown *ud = (const void *)(d1->bmControls + d1->bControlSize); uint8_t i; if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) { return; } if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes)) == NULL) { return; } DPRINTFN(3, "bUnitId=%d bNrModes=%d\n", d0->bUnitId, ud->bNrModes); if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { DPRINTF("no mode select\n"); return; } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0); MIX(sc).type = MIX_ON_OFF; /* XXX */ for (i = 0; i < ud->bNrModes; i++) { DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i])); /* XXX */ } uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio_mixer_add_processing(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); uint16_t ptype; memset(&MIX(sc), 0, sizeof(MIX(sc))); ptype = UGETW(d0->wProcessType); DPRINTFN(3, "wProcessType=%d bUnitId=%d " "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins); if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } switch (ptype) { case UPDOWNMIX_PROCESS: uaudio_mixer_add_processing_updown(sc, iot, id); break; case DOLBY_PROLOGIC_PROCESS: case P3D_STEREO_EXTENDER_PROCESS: case REVERBATION_PROCESS: case CHORUS_PROCESS: case DYN_RANGE_COMP_PROCESS: default: DPRINTF("unit %d, type=%d is not implemented\n", d0->bUnitId, ptype); break; } } static void uaudio_mixer_add_extension(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1; const struct usb_audio_extension_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); if (sc->sc_uq_au_no_xu) { return; } if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static const void * uaudio_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio_mixer_unit_1 *d1; const struct usb_audio_extension_unit_1 *e1; const struct usb_audio_processing_unit_1 *u1; union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it; const struct usb_audio_output_terminal *ot; const struct usb_audio_mixer_unit_0 *mu; const struct usb_audio_selector_unit *su; const struct usb_audio_feature_unit *fu; const struct usb_audio_processing_unit_0 *pu; const struct usb_audio_extension_unit_0 *eu; } u; u.desc = arg; if (u.desc == NULL) { goto error; } if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) { goto error; } switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) { goto error; } len += u.mu->bNrInPins; if (u.desc->bLength < len) { goto error; } d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1); break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) { goto error; } len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; if (u.desc->bLength < len) goto error; len += u.fu->bControlSize; break; case UDESCSUB_AC_PROCESSING: len += sizeof(*u.pu); if (u.desc->bLength < len) { goto error; } len += u.pu->bNrInPins; if (u.desc->bLength < len) { goto error; } u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); if (u.desc->bLength < len) { goto error; } len += u1->bControlSize; break; case UDESCSUB_AC_EXTENSION: len += sizeof(*u.eu); if (u.desc->bLength < len) { goto error; } len += u.eu->bNrInPins; if (u.desc->bLength < len) { goto error; } e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); if (u.desc->bLength < len) { goto error; } len += e1->bControlSize; break; default: goto error; } if (u.desc->bLength < len) { goto error; } return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static const void * uaudio20_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio20_mixer_unit_1 *d1; const struct usb_audio20_extension_unit_1 *e1; const struct usb_audio20_processing_unit_1 *u1; const struct usb_audio20_clock_selector_unit_1 *c1; union { const struct usb_descriptor *desc; const struct usb_audio20_clock_source_unit *csrc; const struct usb_audio20_clock_selector_unit_0 *csel; const struct usb_audio20_clock_multiplier_unit *cmul; const struct usb_audio20_input_terminal *it; const struct usb_audio20_output_terminal *ot; const struct usb_audio20_mixer_unit_0 *mu; const struct usb_audio20_selector_unit *su; const struct usb_audio20_feature_unit *fu; const struct usb_audio20_sample_rate_unit *ru; const struct usb_audio20_processing_unit_0 *pu; const struct usb_audio20_extension_unit_0 *eu; const struct usb_audio20_effect_unit *ef; } u; u.desc = arg; if (u.desc == NULL) goto error; if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) goto error; switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) goto error; len += u.mu->bNrInPins; if (u.desc->bLength < len) goto error; d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1) + d1->bNrChannels; break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) goto error; len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; break; case UDESCSUB_AC_EFFECT: len += sizeof(*u.ef) + 4; break; case UDESCSUB_AC_PROCESSING_V2: len += sizeof(*u.pu); if (u.desc->bLength < len) goto error; len += u.pu->bNrInPins; if (u.desc->bLength < len) goto error; u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); break; case UDESCSUB_AC_EXTENSION_V2: len += sizeof(*u.eu); if (u.desc->bLength < len) goto error; len += u.eu->bNrInPins; if (u.desc->bLength < len) goto error; e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); break; case UDESCSUB_AC_CLOCK_SRC: len += sizeof(*u.csrc); break; case UDESCSUB_AC_CLOCK_SEL: len += sizeof(*u.csel); if (u.desc->bLength < len) goto error; len += u.csel->bNrInPins; if (u.desc->bLength < len) goto error; c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins); len += sizeof(*c1); break; case UDESCSUB_AC_CLOCK_MUL: len += sizeof(*u.cmul); break; case UDESCSUB_AC_SAMPLE_RT: len += sizeof(*u.ru); break; default: goto error; } if (u.desc->bLength < len) goto error; return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) { goto error; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v1->bNrChannels; r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0]; r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1]; r.iChannelNames = iot[id].u.it_v1->iChannelNames; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v1->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio_cluster *) &iot[id].u.mu_v1->baSourceId[ iot[id].u.mu_v1->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v1->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v1->baSourceId[0]; } break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v1->bSourceId; break; case UDESCSUB_AC_PROCESSING: r = *((const struct usb_audio_cluster *) &iot[id].u.pu_v1->baSourceId[ iot[id].u.pu_v1->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION: r = *((const struct usb_audio_cluster *) &iot[id].u.eu_v1->baSourceId[ iot[id].u.eu_v1->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("bad data\n"); memset(&r, 0, sizeof(r)); done: return (r); } static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio20_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) goto error; switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v2->bNrChannels; r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0]; r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1]; r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2]; r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3]; r.iChannelNames = iot[id].u.it_v2->iTerminal; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v2->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio20_cluster *) &iot[id].u.mu_v2->baSourceId[ iot[id].u.mu_v2->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v2->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v2->baSourceId[0]; } break; case UDESCSUB_AC_SAMPLE_RT: id = iot[id].u.ru_v2->bSourceId; break; case UDESCSUB_AC_EFFECT: id = iot[id].u.ef_v2->bSourceId; break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v2->bSourceId; break; case UDESCSUB_AC_PROCESSING_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.pu_v2->baSourceId[ iot[id].u.pu_v2->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.eu_v2->baSourceId[ iot[id].u.eu_v2->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("Bad data!\n"); memset(&r, 0, sizeof(r)); done: return (r); } static bool uaudio_mixer_foreach_input(const struct uaudio_terminal_node *iot, uint8_t *pindex) { uint8_t n; n = *pindex; while (1) { if (!n--) n = iot->usr.id_max; if (n == 0) return (false); if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) break; } *pindex = n; return (true); } static bool uaudio_mixer_foreach_output(const struct uaudio_terminal_node *iot, uint8_t *pindex) { uint8_t n; n = *pindex; while (1) { if (!n--) n = iot->usr.id_max; if (n == 0) return (false); if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) break; } *pindex = n; return (true); } struct uaudio_tt_to_feature { uint16_t terminal_type; uint16_t feature; }; static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = { {UATI_MICROPHONE, SOUND_MIXER_MIC}, {UATI_DESKMICROPHONE, SOUND_MIXER_MIC}, {UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC}, {UATI_OMNIMICROPHONE, SOUND_MIXER_MIC}, {UATI_MICROPHONEARRAY, SOUND_MIXER_MIC}, {UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC}, {UATE_ANALOGCONN, SOUND_MIXER_LINE}, {UATE_LINECONN, SOUND_MIXER_LINE}, {UATE_LEGACYCONN, SOUND_MIXER_LINE}, {UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM}, {UATE_SPDIF, SOUND_MIXER_ALTPCM}, {UATE_1394DA, SOUND_MIXER_ALTPCM}, {UATE_1394DV, SOUND_MIXER_ALTPCM}, {UATF_CDPLAYER, SOUND_MIXER_CD}, {UATF_SYNTHESIZER, SOUND_MIXER_SYNTH}, {UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO}, {UATF_DVDAUDIO, SOUND_MIXER_VIDEO}, {UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO}, {UATF_RADIORECV, SOUND_MIXER_RADIO}, {UATF_RADIOXMIT, SOUND_MIXER_RADIO}, {} /* END */ }; static uint16_t uaudio_mixer_get_feature_by_tt(uint16_t terminal_type, uint16_t default_type) { const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature; uint16_t retval; if (terminal_type == 0) { retval = default_type; } else while (1) { if (uat->terminal_type == 0) { switch (terminal_type >> 8) { case UATI_UNDEFINED >> 8: retval = SOUND_MIXER_RECLEV; goto done; case UATO_UNDEFINED >> 8: retval = SOUND_MIXER_PCM; goto done; case UATT_UNDEFINED >> 8: retval = SOUND_MIXER_PHONEIN; goto done; default: retval = default_type; goto done; } } else if (uat->terminal_type == terminal_type) { retval = uat->feature; goto done; } uat++; } done: DPRINTF("terminal_type=0x%04x RET=%d DEF=%d\n", terminal_type, retval, default_type); return (retval); } static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot) { const struct uaudio_terminal_node *ptr; uint16_t terminal_type_input = 0; uint16_t terminal_type_output = 0; uint16_t temp; uint8_t match = 0; uint8_t i; for (i = 0; uaudio_mixer_foreach_input(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.it_v1->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 1; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_input = temp; } for (i = 0; uaudio_mixer_foreach_output(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.ot_v1->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 2; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_output = temp; } DPRINTF("MATCH=%d IN=0x%04x OUT=0x%04x\n", match, terminal_type_input, terminal_type_output); switch (match) { case 0: /* not connected to USB */ if (terminal_type_output != 0) { return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_MONITOR)); } else { return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_MONITOR)); } case 3: /* connected to both USB input and USB output */ return (SOUND_MIXER_IMIX); case 2: /* connected to USB output */ return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_RECLEV)); case 1: /* connected to USB input */ return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_PCM)); default: return (SOUND_MIXER_NRDEVICES); } } static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot) { const struct uaudio_terminal_node *ptr; uint16_t terminal_type_input = 0; uint16_t terminal_type_output = 0; uint16_t temp; uint8_t match = 0; uint8_t i; for (i = 0; uaudio_mixer_foreach_input(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.it_v2->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 1; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_input = temp; } for (i = 0; uaudio_mixer_foreach_output(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.ot_v2->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 2; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_output = temp; } DPRINTF("MATCH=%d IN=0x%04x OUT=0x%04x\n", match, terminal_type_input, terminal_type_output); switch (match) { case 0: /* not connected to USB */ if (terminal_type_output != 0) { return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_MONITOR)); } else { return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_MONITOR)); } case 3: /* connected to both USB input and USB output */ return (SOUND_MIXER_IMIX); case 2: /* connected to USB output */ return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_RECLEV)); case 1: /* connected to USB input */ return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_PCM)); default: return (SOUND_MIXER_NRDEVICES); } } static void uaudio_mixer_merge_outputs(struct uaudio_search_result *dst, const struct uaudio_search_result *src) { const uint8_t max = sizeof(src->bit_output) / sizeof(src->bit_output[0]); uint8_t x; for (x = 0; x != max; x++) dst->bit_output[x] |= src->bit_output[x]; } static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: uaudio_mixer_merge_outputs(&iot->usr, info); info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, &iot->u.fu_v1->bSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: info->bit_output[i / 8] |= (1 << (i % 8)); uaudio_mixer_find_inputs_sub( root, &iot->u.ot_v1->bSourceId, 1, info); info->bit_output[i / 8] &= ~(1 << (i % 8)); break; case UDESCSUB_AC_MIXER: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.mu_v1->baSourceId, iot->u.mu_v1->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.su_v1->baSourceId, iot->u.su_v1->bNrInPins, info); break; case UDESCSUB_AC_PROCESSING: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.pu_v1->baSourceId, iot->u.pu_v1->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.eu_v1->baSourceId, iot->u.eu_v1->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: uaudio_mixer_merge_outputs(&iot->usr, info); info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_OUTPUT: info->bit_output[i / 8] |= (1 << (i % 8)); uaudio20_mixer_find_inputs_sub( root, &iot->u.ot_v2->bSourceId, 1, info); info->bit_output[i / 8] &= ~(1 << (i % 8)); break; case UDESCSUB_AC_MIXER: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.mu_v2->baSourceId, iot->u.mu_v2->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.su_v2->baSourceId, iot->u.su_v2->bNrInPins, info); break; case UDESCSUB_AC_SAMPLE_RT: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, &iot->u.ru_v2->bSourceId, 1, info); break; case UDESCSUB_AC_EFFECT: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, &iot->u.ef_v2->bSourceId, 1, info); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, &iot->u.fu_v2->bSourceId, 1, info); break; case UDESCSUB_AC_PROCESSING_V2: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.pu_v2->baSourceId, iot->u.pu_v2->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION_V2: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.eu_v2->baSourceId, iot->u.eu_v2->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; uint8_t id; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->is_input = 1; if (is_last) { p_id = &iot->u.it_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.it_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: info->is_input = 0; if (is_last) { p_id = &iot->u.ot_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.ot_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SEL: if (is_last) { p_id = iot->u.csel_v2->baCSourceId; n_id = iot->u.csel_v2->bNrInPins; goto top; } uaudio20_mixer_find_clocks_sub(root, iot->u.csel_v2->baCSourceId, iot->u.csel_v2->bNrInPins, info); break; case UDESCSUB_AC_CLOCK_MUL: if (is_last) { p_id = &iot->u.cmul_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.cmul_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SRC: id = iot->u.csrc_v2->bClockId; switch (info->is_input) { case 0: info->bit_output[id / 8] |= (1 << (id % 8)); break; case 1: info->bit_input[id / 8] |= (1 << (id % 8)); break; default: break; } break; default: break; } } } static void uaudio_mixer_fill_info(struct uaudio_softc *sc, struct usb_device *udev, void *desc) { const struct usb_audio_control_descriptor *acdp; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); const struct usb_descriptor *dp; const struct usb_audio_unit *au; struct uaudio_terminal_node *iot = NULL; uint16_t wTotalLen; uint8_t ID_max = 0; /* inclusive */ uint8_t i; desc = usb_desc_foreach(cd, desc); if (desc == NULL) { DPRINTF("no Audio Control header\n"); goto done; } acdp = desc; if ((acdp->bLength < sizeof(*acdp)) || (acdp->bDescriptorType != UDESC_CS_INTERFACE) || (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) { DPRINTF("invalid Audio Control header\n"); goto done; } /* "wTotalLen" is allowed to be corrupt */ wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength; /* get USB audio revision */ sc->sc_audio_rev = UGETW(acdp->bcdADC); DPRINTFN(3, "found AC header, vers=%03x, len=%d\n", sc->sc_audio_rev, wTotalLen); iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP, M_WAITOK | M_ZERO); while ((desc = usb_desc_foreach(cd, desc))) { dp = desc; if (dp->bLength > wTotalLen) { break; } else { wTotalLen -= dp->bLength; } if (sc->sc_audio_rev >= UAUDIO_VERSION_30) au = NULL; else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) au = uaudio20_mixer_verify_desc(dp, 0); else au = uaudio_mixer_verify_desc(dp, 0); if (au) { iot[au->bUnitId].u.desc = (const void *)au; if (au->bUnitId > ID_max) ID_max = au->bUnitId; } } DPRINTF("Maximum ID=%d\n", ID_max); /* * determine sourcing inputs for * all nodes in the tree: */ i = ID_max; do { if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { uaudio20_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); sc->sc_mixer_clocks.is_input = 255; sc->sc_mixer_clocks.recurse_level = 0; uaudio20_mixer_find_clocks_sub(iot, &i, 1, &sc->sc_mixer_clocks); } else { uaudio_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); } } while (i--); /* set "id_max" and "root" */ i = ID_max; do { (iot + i)->usr.id_max = ID_max; (iot + i)->root = iot; } while (i--); /* * Scan the config to create a linked list of "mixer" nodes: */ i = ID_max; do { dp = iot[i].u.desc; if (dp == NULL) continue; DPRINTFN(11, "id=%d subtype=%d\n", i, dp->bDescriptorSubtype); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { continue; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: case UDESCSUB_AC_PROCESSING_V2: case UDESCSUB_AC_EXTENSION_V2: case UDESCSUB_AC_EFFECT: case UDESCSUB_AC_CLOCK_SRC: case UDESCSUB_AC_CLOCK_SEL: case UDESCSUB_AC_CLOCK_MUL: case UDESCSUB_AC_SAMPLE_RT: break; case UDESCSUB_AC_MIXER: uaudio20_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio20_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio20_mixer_add_feature(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } continue; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: break; case UDESCSUB_AC_MIXER: uaudio_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_add_feature(sc, iot, i); break; case UDESCSUB_AC_PROCESSING: uaudio_mixer_add_processing(sc, iot, i); break; case UDESCSUB_AC_EXTENSION: uaudio_mixer_add_extension(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } } while (i--); done: free(iot, M_TEMP); } static int uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev, uint8_t what, struct uaudio_mixer_node *mc) { struct usb_device_request req; int val; uint8_t data[2 + (2 * 3)]; usb_error_t err; if (mc->wValue[0] == -1) return (0); if (audio_rev >= UAUDIO_VERSION_30) return (0); else if (audio_rev >= UAUDIO_VERSION_20) { if (what == GET_CUR) { req.bRequest = UA20_CS_CUR; USETW(req.wLength, 2); } else { req.bRequest = UA20_CS_RANGE; USETW(req.wLength, 8); } } else { uint16_t len = MIX_SIZE(mc->type); req.bRequest = what; USETW(req.wLength, len); } req.bmRequestType = UT_READ_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[0]); USETW(req.wIndex, mc->wIndex); memset(data, 0, sizeof(data)); err = usbd_do_request(udev, NULL, &req, data); if (err) { DPRINTF("err=%s\n", usbd_errstr(err)); return (0); } if (audio_rev >= UAUDIO_VERSION_30) { val = 0; } else if (audio_rev >= UAUDIO_VERSION_20) { switch (what) { case GET_CUR: val = (data[0] | (data[1] << 8)); break; case GET_MIN: val = (data[2] | (data[3] << 8)); break; case GET_MAX: val = (data[4] | (data[5] << 8)); break; case GET_RES: val = (data[6] | (data[7] << 8)); break; default: val = 0; break; } } else { val = (data[0] | (data[1] << 8)); } if (what == GET_CUR || what == GET_MIN || what == GET_MAX) val = uaudio_mixer_signext(mc->type, val); DPRINTFN(3, "val=%d\n", val); return (val); } static void uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_device_request req; struct uaudio_softc *sc = usbd_xfer_softc(xfer); struct uaudio_mixer_node *mc = sc->sc_mixer_curr; struct usb_page_cache *pc; uint16_t len; uint8_t repeat = 1; uint8_t update; uint8_t chan; uint8_t buf[2]; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: case USB_ST_SETUP: tr_setup: if (mc == NULL) { mc = sc->sc_mixer_root; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; repeat = 0; } while (mc) { while (sc->sc_mixer_chan < mc->nchan) { chan = sc->sc_mixer_chan; sc->sc_mixer_chan++; update = ((mc->update[chan / 8] & (1 << (chan % 8))) && (mc->wValue[chan] != -1)); mc->update[chan / 8] &= ~(1 << (chan % 8)); if (update) { req.bmRequestType = UT_WRITE_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[chan]); USETW(req.wIndex, mc->wIndex); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { return; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { len = 2; req.bRequest = UA20_CS_CUR; USETW(req.wLength, len); } else { len = MIX_SIZE(mc->type); req.bRequest = SET_CUR; USETW(req.wLength, len); } buf[0] = (mc->wData[chan] & 0xFF); buf[1] = (mc->wData[chan] >> 8) & 0xFF; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, buf, len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, len); usbd_xfer_set_frames(xfer, len ? 2 : 1); usbd_transfer_submit(xfer); return; } } mc = mc->next; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; } if (repeat) { goto tr_setup; } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) { /* do nothing - we are detaching */ break; } goto tr_transferred; } } static usb_error_t uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed) { struct usb_device_request req; uint8_t data[3]; DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed); req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; req.bRequest = SET_CUR; USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0); USETW(req.wIndex, endpt); USETW(req.wLength, 3); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; return (usbd_do_request(udev, NULL, &req, data)); } static usb_error_t uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t speed) { struct usb_device_request req; uint8_t data[4]; DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n", iface_no, clockid, speed); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UA20_CS_CUR; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); USETW(req.wLength, 4); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; data[3] = speed >> 24; return (usbd_do_request(udev, NULL, &req, data)); } static int uaudio_mixer_signext(uint8_t type, int val) { if (!MIX_UNSIGNED(type)) { if (MIX_SIZE(type) == 2) { val = (int16_t)val; } else { val = (int8_t)val; } } return (val); } static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int val) { if (mc->type == MIX_ON_OFF) { val = (val != 0); } else if (mc->type != MIX_SELECTOR) { /* compute actual volume */ val = (val * mc->mul) / 100; /* add lower offset */ val = val + mc->minval; } /* make sure we don't write a value out of range */ if (val > mc->maxval) val = mc->maxval; else if (val < mc->minval) val = mc->minval; DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n", mc->type, val, mc->minval, mc->maxval, val); return (val); } static void uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc, uint8_t chan, int val) { val = uaudio_mixer_bsd2value(mc, val); mc->update[chan / 8] |= (1 << (chan % 8)); mc->wData[chan] = val; /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } static void uaudio_mixer_init(struct uaudio_softc *sc, unsigned index) { struct uaudio_mixer_node *mc; int32_t i; if (index != 0) return; for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if (mc->ctl != SOUND_MIXER_NRDEVICES) { /* * Set device mask bits. See * /usr/include/machine/soundcard.h */ sc->sc_child[index].mix_info |= 1U << mc->ctl; } if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) continue; sc->sc_child[index].recsrc_info |= 1U << mc->slctrtype[i - 1]; } } } } int uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m) { unsigned i = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); DPRINTF("child=%u\n", i); sc->sc_child[i].mixer_lock = mixer_get_lock(m); sc->sc_child[i].mixer_dev = m; if (i == 0 && usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index, sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc, sc->sc_child[i].mixer_lock)) { DPRINTFN(0, "could not allocate USB transfer for mixer!\n"); return (ENOMEM); } if (sc->sc_play_chan[i].num_alt > 0 && (sc->sc_child[i].mix_info & SOUND_MASK_VOLUME) == 0) { mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); } mix_setdevs(m, sc->sc_child[i].mix_info); mix_setrecdevs(m, sc->sc_child[i].recsrc_info); return (0); } int uaudio_mixer_uninit_sub(struct uaudio_softc *sc, struct snd_mixer *m) { unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); DPRINTF("child=%u\n", index); if (index == 0) usbd_transfer_unsetup(sc->sc_mixer_xfer, 1); sc->sc_child[index].mixer_lock = NULL; return (0); } void uaudio_mixer_set(struct uaudio_softc *sc, struct snd_mixer *m, unsigned type, unsigned left, unsigned right) { unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); struct uaudio_mixer_node *mc; int chan; if (index != 0) return; for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) { if (mc->ctl == type) { for (chan = 0; chan < mc->nchan; chan++) { uaudio_mixer_ctl_set(sc, mc, chan, chan == 0 ? left : right); } } } } uint32_t uaudio_mixer_setrecsrc(struct uaudio_softc *sc, struct snd_mixer *m, uint32_t src) { unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); struct uaudio_mixer_node *mc; uint32_t mask; uint32_t temp; int32_t i; if (index != 0) return (0); for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { /* compute selector mask */ mask = 0; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) mask |= 1U << mc->slctrtype[i - 1]; temp = mask & src; if (temp == 0) continue; /* find the first set bit */ temp = (-temp) & temp; /* update "src" */ src &= ~mask; src |= temp; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (temp != (1U << mc->slctrtype[i - 1])) continue; uaudio_mixer_ctl_set(sc, mc, 0, i); break; } } } return (src); } /*========================================================================* * MIDI support routines *========================================================================*/ static void umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint8_t buf[4]; uint8_t cmd_len; uint8_t cn; uint16_t pos; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", actlen); pos = 0; pc = usbd_xfer_get_frame(xfer, 0); while (actlen >= 4) { /* copy out the MIDI data */ usbd_copy_out(pc, pos, buf, 4); /* command length */ cmd_len = umidi_cmd_to_len[buf[0] & 0xF]; /* cable number */ cn = buf[0] >> 4; /* * Lookup sub-channel. The index is range * checked below. */ sub = &chan->sub[cn]; if ((cmd_len != 0) && (cn < chan->max_emb_jack) && (sub->read_open != 0)) { /* Send data to the application */ usb_fifo_put_data_linear( sub->fifo.fp[USB_FIFO_RX], buf + 1, cmd_len, 1); } actlen -= 4; pos += 4; } case USB_ST_SETUP: DPRINTF("start\n"); tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } /* * The following statemachine, that converts MIDI commands to * USB MIDI packets, derives from Linux's usbmidi.c, which * was written by "Clemens Ladisch": * * Returns: * 0: No command * Else: Command is complete */ static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b) { uint8_t p0 = (cn << 4); if (b >= 0xf8) { sub->temp_0[0] = p0 | 0x0f; sub->temp_0[1] = b; sub->temp_0[2] = 0; sub->temp_0[3] = 0; sub->temp_cmd = sub->temp_0; return (1); } else if (b >= 0xf0) { switch (b) { case 0xf0: /* system exclusive begin */ sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case 0xf1: /* MIDI time code */ case 0xf3: /* song select */ sub->temp_1[1] = b; sub->state = UMIDI_ST_1PARAM; break; case 0xf2: /* song position pointer */ sub->temp_1[1] = b; sub->state = UMIDI_ST_2PARAM_1; break; case 0xf4: /* unknown */ case 0xf5: /* unknown */ sub->state = UMIDI_ST_UNKNOWN; break; case 0xf6: /* tune request */ sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf6; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case 0xf7: /* system exclusive end */ switch (sub->state) { case UMIDI_ST_SYSEX_0: sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf7; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_1: sub->temp_1[0] = p0 | 0x06; sub->temp_1[2] = 0xf7; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x07; sub->temp_1[3] = 0xf7; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); } sub->state = UMIDI_ST_UNKNOWN; break; } } else if (b >= 0x80) { sub->temp_1[1] = b; if ((b >= 0xc0) && (b <= 0xdf)) { sub->state = UMIDI_ST_1PARAM; } else { sub->state = UMIDI_ST_2PARAM_1; } } else { /* b < 0x80 */ switch (sub->state) { case UMIDI_ST_1PARAM: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; } else { p0 |= 0x02; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[2] = b; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_2PARAM_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_2PARAM_2; break; case UMIDI_ST_2PARAM_2: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; sub->state = UMIDI_ST_2PARAM_1; } else { p0 |= 0x03; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_SYSEX_0: sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case UMIDI_ST_SYSEX_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_SYSEX_2; break; case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x04; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_SYSEX_0; return (1); default: break; } } return (0); } static void umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint32_t actlen; uint16_t nframes; uint8_t buf; uint8_t start_cable; uint8_t tr_any; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); /* * NOTE: Some MIDI devices only accept 4 bytes of data per * short terminated USB transfer. */ switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", len); case USB_ST_SETUP: tr_setup: DPRINTF("start\n"); nframes = 0; /* reset */ start_cable = chan->curr_cable; tr_any = 0; pc = usbd_xfer_get_frame(xfer, 0); while (1) { /* round robin de-queueing */ sub = &chan->sub[chan->curr_cable]; if (sub->write_open) { usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX], &buf, 1, &actlen, 0); } else { actlen = 0; } if (actlen) { tr_any = 1; DPRINTF("byte=0x%02x from FIFO %u\n", buf, (unsigned int)chan->curr_cable); if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) { DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n", sub->temp_cmd[0], sub->temp_cmd[1], sub->temp_cmd[2], sub->temp_cmd[3]); usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4); nframes++; if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0)) break; } else { continue; } } chan->curr_cable++; if (chan->curr_cable >= chan->max_emb_jack) chan->curr_cable = 0; if (chan->curr_cable == start_cable) { if (tr_any == 0) break; tr_any = 0; } } if (nframes != 0) { DPRINTF("Transferring %d frames\n", (int)nframes); usbd_xfer_set_frame_len(xfer, 0, 4 * nframes); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static struct umidi_sub_chan * umidi_sub_by_fifo(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) { sub = &chan->sub[n]; if ((sub->fifo.fp[USB_FIFO_RX] == fifo) || (sub->fifo.fp[USB_FIFO_TX] == fifo)) { return (sub); } } panic("%s:%d cannot find usb_fifo!\n", __FILE__, __LINE__); return (NULL); } static void umidi_start_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); } static void umidi_stop_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->read_open = 0; if (--(chan->read_open_refcount) == 0) { /* * XXX don't stop the read transfer here, hence that causes * problems with some MIDI adapters */ DPRINTF("(stopping read transfer)\n"); } } static void umidi_start_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); if (chan->xfer[UMIDI_TX_TRANSFER] == NULL) { uint8_t buf[1]; int actlen; do { /* dump data */ usb_fifo_get_data_linear(fifo, buf, 1, &actlen, 0); } while (actlen > 0); } else { usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]); } } static void umidi_stop_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->write_open = 0; if (--(chan->write_open_refcount) == 0) { DPRINTF("(stopping write transfer)\n"); usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]); } } static int umidi_open(struct usb_fifo *fifo, int fflags) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); if (fflags & FREAD) { if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) { return (ENOMEM); } mtx_lock(&chan->mtx); chan->read_open_refcount++; sub->read_open = 1; mtx_unlock(&chan->mtx); } if (fflags & FWRITE) { if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) { return (ENOMEM); } /* clear stall first */ mtx_lock(&chan->mtx); chan->write_open_refcount++; sub->write_open = 1; /* reset */ sub->state = UMIDI_ST_UNKNOWN; mtx_unlock(&chan->mtx); } return (0); /* success */ } static void umidi_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { usb_fifo_free_buffer(fifo); } if (fflags & FWRITE) { usb_fifo_free_buffer(fifo); } } static int umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags) { return (ENODEV); } static void umidi_init(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE); } static struct usb_fifo_methods umidi_fifo_methods = { .f_start_read = &umidi_start_read, .f_start_write = &umidi_start_write, .f_stop_read = &umidi_stop_read, .f_stop_write = &umidi_stop_write, .f_open = &umidi_open, .f_close = &umidi_close, .f_ioctl = &umidi_ioctl, .basename[0] = "umidi", }; static int umidi_probe(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct umidi_chan *chan = &sc->sc_midi_chan; struct umidi_sub_chan *sub; int unit = device_get_unit(dev); int error; uint32_t n; if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI)) chan->single_command = 1; error = usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, chan->iface_alt_index); if (error) { DPRINTF("setting of alternate index failed: %s\n", usbd_errstr(error)); goto detach; } usbd_set_parent_iface(sc->sc_udev, chan->iface_index, sc->sc_mixer_iface_index); error = usbd_transfer_setup(uaa->device, &chan->iface_index, chan->xfer, umidi_config, UMIDI_N_TRANSFER, chan, &chan->mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } if (chan->xfer[UMIDI_TX_TRANSFER] == NULL && chan->xfer[UMIDI_RX_TRANSFER] == NULL) { DPRINTF("no BULK or INTERRUPT MIDI endpoint(s) found\n"); goto detach; } /* * Some USB MIDI device makers couldn't resist using * wMaxPacketSize = 4 for RX and TX BULK endpoints, although * that size is an unsupported value for FULL speed BULK * endpoints. The same applies to some HIGH speed MIDI devices * which are using a wMaxPacketSize different from 512 bytes. * * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host * Controllers are required to have support for 8-, 16-, 32-, * and 64-byte maximum packet sizes for full-speed bulk * endpoints and 512 bytes for high-speed bulk endpoints." */ if (chan->xfer[UMIDI_TX_TRANSFER] != NULL && usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER])) chan->single_command = 1; if (chan->single_command != 0) device_printf(dev, "Single command MIDI quirk enabled\n"); if ((chan->max_emb_jack == 0) || (chan->max_emb_jack > UMIDI_EMB_JACK_MAX)) { chan->max_emb_jack = UMIDI_EMB_JACK_MAX; } for (n = 0; n < chan->max_emb_jack; n++) { sub = &chan->sub[n]; error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx, &umidi_fifo_methods, &sub->fifo, unit, n, chan->iface_index, UID_ROOT, GID_OPERATOR, 0666); if (error) { goto detach; } } mtx_lock(&chan->mtx); /* * NOTE: At least one device will not work properly unless the * BULK IN pipe is open all the time. This might have to do * about that the internal queues of the device overflow if we * don't read them regularly. */ usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); return (0); /* success */ detach: return (ENXIO); /* failure */ } static int umidi_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) usb_fifo_detach(&chan->sub[n].fifo); mtx_lock(&chan->mtx); usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER); mtx_destroy(&chan->mtx); return (0); } static void uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_softc *sc = usbd_xfer_softc(xfer); const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0); struct snd_mixer *m; uint8_t id; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d\n", actlen); if (actlen != 0 && (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) { id = *buffer; buffer++; actlen--; } else { id = 0; } m = sc->sc_child[0].mixer_dev; if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) && (sc->sc_hid.mute_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.mute_loc)) { DPRINTF("Mute toggle\n"); mixer_hwvol_mute_locked(m); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) && (sc->sc_hid.volume_up_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_up_loc)) { DPRINTF("Volume Up\n"); mixer_hwvol_step_locked(m, 1, 1); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) && (sc->sc_hid.volume_down_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_down_loc)) { DPRINTF("Volume Down\n"); mixer_hwvol_step_locked(m, -1, -1); } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa) { void *d_ptr; uint32_t flags; uint16_t d_len; uint8_t id; int error; if (!(sc->sc_hid.flags & UAUDIO_HID_VALID)) return (-1); if (sc->sc_child[0].mixer_lock == NULL) return (-1); /* Get HID descriptor */ error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, sc->sc_hid.iface_index); if (error) { DPRINTF("error reading report description\n"); return (-1); } /* check if there is an ID byte */ hid_report_size_max(d_ptr, d_len, hid_input, &id); if (id != 0) sc->sc_hid.flags |= UAUDIO_HID_HAS_ID; if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */), hid_input, 0, &sc->sc_hid.volume_up_loc, &flags, &sc->sc_hid.volume_up_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP; DPRINTFN(1, "Found Volume Up key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */), hid_input, 0, &sc->sc_hid.volume_down_loc, &flags, &sc->sc_hid.volume_down_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN; DPRINTFN(1, "Found Volume Down key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */), hid_input, 0, &sc->sc_hid.mute_loc, &flags, &sc->sc_hid.mute_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE; DPRINTFN(1, "Found Mute key\n"); } free(d_ptr, M_TEMP); if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP | UAUDIO_HID_HAS_VOLUME_DOWN | UAUDIO_HID_HAS_MUTE))) { DPRINTFN(1, "Did not find any volume related keys\n"); return (-1); } /* prevent the uhid driver from attaching */ usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index, sc->sc_mixer_iface_index); /* allocate USB transfers */ error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index, sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER, sc, sc->sc_child[0].mixer_lock); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); return (-1); } return (0); } static void uaudio_hid_detach(struct uaudio_softc *sc) { usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER); } DRIVER_MODULE_ORDERED(uaudio, uhub, uaudio_driver, NULL, NULL, SI_ORDER_ANY); MODULE_DEPEND(uaudio, usb, 1, 1, 1); MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(uaudio, hid, 1, 1, 1); MODULE_VERSION(uaudio, 1); USB_PNP_HOST_INFO(uaudio_devs); USB_PNP_HOST_INFO(uaudio_vendor_midi);