diff --git a/sys/dev/sound/pci/als4000.c b/sys/dev/sound/pci/als4000.c index 75fbefe27633..a8c3303af74f 100644 --- a/sys/dev/sound/pci/als4000.c +++ b/sys/dev/sound/pci/als4000.c @@ -1,941 +1,941 @@ /*- * 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->speed = 8000; 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 = ch->buffer->bufsize; 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 = ch->buffer->buf_addr; bufsz = ch->buffer->bufsize; 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 = ch->buffer->buf_addr; bufsz = ch->buffer->bufsize; 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; } pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &alspchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &alsrchan_class, sc); 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))); if (pcm_register(dev, status)) { device_printf(dev, "failed to register pcm entries\n"); goto bad_attach; } 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/cmi.c b/sys/dev/sound/pci/cmi.c index a1356f58b567..28427d449c8d 100644 --- a/sys/dev/sound/pci/cmi.c +++ b/sys/dev/sound/pci/cmi.c @@ -1,1111 +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 = ch->buffer->buf_addr; cmi_wr(sc, base, ch->phys_buf, 4); sz = (u_int32_t)ch->buffer->bufsize; 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->spd = 8000; 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 = ch->buffer->bufsize; 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; pcm_init(dev, sc); cmi_initsys(sc); pcm_addchan(dev, PCMDIR_PLAY, &cmichan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cmichan_class, sc); 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))); if (pcm_register(dev, status)) goto bad; 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 af2b4da76aba..5b0b229a021b 100644 --- a/sys/dev/sound/pci/cs4281.c +++ b/sys/dev/sound/pci/cs4281.c @@ -1,968 +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->spd = 8000; ch->bps = 1; ch->blksz = ch->buffer->bufsize; 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 = ch->buffer->bufsize; 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), ch->buffer->buf_addr); cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan), ch->buffer->bufsize / 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); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &cs4281chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cs4281chan_class, sc); 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))); if (pcm_register(dev, status)) goto bad; 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/es137x.c b/sys/dev/sound/pci/es137x.c index eb546de81f65..6b2093e16246 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, ch->buffer->buf_addr, 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, ch->buffer->buf_addr, 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, ch->buffer->buf_addr, 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 > (ch->buffer->maxsize / ES_DMA_SEGS_MIN)) blksz = ch->buffer->maxsize / 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) > ch->buffer->maxsize) { if ((blkcnt >> 1) >= ES_DMA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ES_BLK_MIN) blksz >>= 1; else break; } if ((ch->buffer->blksz != blksz || ch->buffer->blkcnt != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(es->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->bufsz = ch->buffer->bufsize; ch->blksz = ch->buffer->blksz; ch->blkcnt = ch->buffer->blkcnt; 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 / ch->buffer->align) - 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; + fixed_rate = 8000; } 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, "%s 0x%jx irq %jd on %s", (es->regtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(es->reg), rman_get_start(es->irq), device_get_nameunit(device_get_parent(dev))); pcm_init(dev, es); for (i = 0; i < numplay; i++) pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_addchan(dev, PCMDIR_REC, ct, es); es_init_sysctls(dev); if (pcm_register(dev, status)) goto bad; 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/maestro3.c b/sys/dev/sound/pci/maestro3.c index ee39f5f3c90d..bad2b4eee1cd 100644 --- a/sys/dev/sound/pci/maestro3.c +++ b/sys/dev/sound/pci/maestro3.c @@ -1,1794 +1,1794 @@ /*- * 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; + ch->spd = 8000; 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 = ch->buffer->bufsize; /* host dma buffer pointers */ bus_addr = ch->buffer->buf_addr; 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 < nitems(pv); 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 (ch->buffer->blksz); } 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 = ch->buffer->buf_addr; 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; + ch->spd = 8000; 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 = ch->buffer->bufsize; /* host dma buffer pointers */ bus_addr = ch->buffer->buf_addr; 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 < nitems(rv); 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 (ch->buffer->blksz); } 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 = ch->buffer->buf_addr; 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 < pch->buffer->blksz) 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 < rch->buffer->blksz) 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); pcm_init(dev, sc); for (i=0 ; iregtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) { device_printf(dev, "pcm_register 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 = 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/vibes.c b/sys/dev/sound/pci/vibes.c index 6df76306b28f..1b7353464503 100644 --- a/sys/dev/sound/pci/vibes.c +++ b/sys/dev/sound/pci/vibes.c @@ -1,942 +1,942 @@ /*- * 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->spd = 8000; 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 = ch->buffer->bufsize / 2; /* DMAC uses words */ sv_dma_set_config(sc->dmac_st, sc->dmac_sh, ch->buffer->buf_addr, 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 = ch->buffer->bufsize; /* 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 = ch->buffer->bufsize; sv_dma_set_config(sc->dmaa_st, sc->dmaa_sh, ch->buffer->buf_addr, 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 = ch->buffer->bufsize; /* 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); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &svpchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &svrchan_class, sc); 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))); if (pcm_register(dev, status)) { device_printf(dev, "sv_attach: pcm_register fail\n"); goto fail; } 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 6bd435d0ea25..19da1a22871a 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,507 +1,505 @@ /*- * 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. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 #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 #define SD_F_SIMPLEX 0x00000001 /* unused 0x00000002 */ #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_BUSY 0x00000008 #define SD_F_MPSAFE 0x00000010 #define SD_F_REGISTERED 0x00000020 #define SD_F_BITPERFECT 0x00000040 #define SD_F_VPC 0x00000080 /* volume-per-channel */ #define SD_F_EQ 0x00000100 /* EQ */ #define SD_F_EQ_ENABLED 0x00000200 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00000400 /* EQ bypassed */ #define SD_F_EQ_PC 0x00000800 /* EQ per-channel */ #define SD_F_PVCHANS 0x00001000 /* Playback vchans enabled */ #define SD_F_RVCHANS 0x00002000 /* Recording vchans enabled */ #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_BITS "\020" \ "\001SIMPLEX" \ /* "\002 */ \ "\003SOFTPCMVOL" \ "\004BUSY" \ "\005MPSAFE" \ "\006REGISTERED" \ "\007BITPERFECT" \ "\010VPC" \ "\011EQ" \ "\012EQ_ENABLED" \ "\013EQ_BYPASSED" \ "\014EQ_PC" \ "\015PVCHANS" \ "\016RVCHANS" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && ((x)->flags & SD_F_REGISTERED)) #define PCM_MAXCHANS 10000 #define PCM_CHANCOUNT(d) \ (d->playcount + d->pvchancount + d->reccount + d->rvchancount) /* 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_DEFAULT_SPEED 8000 - extern int snd_unit; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); 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); void pcm_init(device_t dev, void *devinfo); int pcm_register(device_t dev, char *str); int pcm_unregister(device_t dev); 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) int sndstat_register(device_t dev, char *str); int sndstat_unregister(device_t dev); /* These are the function codes assigned to the children of sound cards. */ enum { SCF_PCM, SCF_MIDI, SCF_SYNTH, }; /* * This is the device information struct, used by a bridge device to pass the * device function code to the children. */ struct sndcard_func { int func; /* The function code. */ void *varinfo; /* Bridge-specific information. */ }; /* * 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; struct { SLIST_HEAD(, pcm_channel) head; } primary; } pcm; } channels; unsigned playcount, reccount, pvchancount, rvchancount; unsigned flags; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; struct cdev *dsp_dev; uint32_t pvchanrate, pvchanformat, pvchanmode; uint32_t rvchanrate, rvchanformat, rvchanmode; 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; struct unrhdr *p_unr; struct unrhdr *vp_unr; struct unrhdr *r_unr; struct unrhdr *vr_unr; }; 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; \ 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; \ 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) #endif /* _KERNEL */ /* 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 | \ AFMT_F32_LE | AFMT_F32_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_F32_LE | AFMT_F32_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_F32_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 | AFMT_F32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) #endif /* _OS_H_ */