Index: head/sys/dev/sound/isa/ad1816.c =================================================================== --- head/sys/dev/sound/isa/ad1816.c (revision 170520) +++ head/sys/dev/sound/isa/ad1816.c (revision 170521) @@ -1,690 +1,690 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997,1998 Luigi Rizzo * Copyright (c) 1994,1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); struct ad1816_info; struct ad1816_chinfo { struct ad1816_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, blksz; }; struct ad1816_info { struct resource *io_base; /* primary I/O address for the board */ int io_rid; struct resource *irq; int irq_rid; struct resource *drq1; /* play */ int drq1_rid; struct resource *drq2; /* rec */ int drq2_rid; void *ih; bus_dma_tag_t parent_dmat; struct mtx *lock; unsigned int bufsize; struct ad1816_chinfo pch, rch; }; static u_int32_t ad1816_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_MU_LAW, AFMT_STEREO | AFMT_MU_LAW, AFMT_A_LAW, AFMT_STEREO | AFMT_A_LAW, 0 }; static struct pcmchan_caps ad1816_caps = {4000, 55200, ad1816_fmt, 0}; #define AD1816_MUTE 31 /* value for mute */ static void ad1816_lock(struct ad1816_info *ad1816) { snd_mtxlock(ad1816->lock); } static void ad1816_unlock(struct ad1816_info *ad1816) { snd_mtxunlock(ad1816->lock); } static int port_rd(struct resource *port, int off) { if (port) return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); else return -1; } static void port_wr(struct resource *port, int off, u_int8_t data) { if (port) bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); } static int io_rd(struct ad1816_info *ad1816, int reg) { return port_rd(ad1816->io_base, reg); } static void io_wr(struct ad1816_info *ad1816, int reg, u_int8_t data) { port_wr(ad1816->io_base, reg, data); } static void ad1816_intr(void *arg) { struct ad1816_info *ad1816 = (struct ad1816_info *)arg; unsigned char c, served = 0; ad1816_lock(ad1816); /* get interupt status */ c = io_rd(ad1816, AD1816_INT); /* check for stray interupts */ if (c & ~(AD1816_INTRCI | AD1816_INTRPI)) { printf("pcm: stray int (%x)\n", c); c &= AD1816_INTRCI | AD1816_INTRPI; } /* check for capture interupt */ if (sndbuf_runsz(ad1816->rch.buffer) && (c & AD1816_INTRCI)) { ad1816_unlock(ad1816); chn_intr(ad1816->rch.channel); ad1816_lock(ad1816); served |= AD1816_INTRCI; /* cp served */ } /* check for playback interupt */ if (sndbuf_runsz(ad1816->pch.buffer) && (c & AD1816_INTRPI)) { ad1816_unlock(ad1816); chn_intr(ad1816->pch.channel); ad1816_lock(ad1816); served |= AD1816_INTRPI; /* pb served */ } if (served == 0) { /* this probably means this is not a (working) ad1816 chip, */ /* or an error in dma handling */ printf("pcm: int without reason (%x)\n", c); c = 0; } else c &= ~served; io_wr(ad1816, AD1816_INT, c); c = io_rd(ad1816, AD1816_INT); if (c != 0) printf("pcm: int clear failed (%x)\n", c); ad1816_unlock(ad1816); } static int ad1816_wait_init(struct ad1816_info *ad1816, int x) { int n = 0; /* to shut up the compiler... */ for (; x--;) if ((n = (io_rd(ad1816, AD1816_ALE) & AD1816_BUSY)) == 0) DELAY(10); else return n; printf("ad1816_wait_init failed 0x%02x.\n", n); return -1; } static unsigned short ad1816_read(struct ad1816_info *ad1816, unsigned int reg) { u_short x = 0; if (ad1816_wait_init(ad1816, 100) == -1) return 0; io_wr(ad1816, AD1816_ALE, 0); io_wr(ad1816, AD1816_ALE, (reg & AD1816_ALEMASK)); if (ad1816_wait_init(ad1816, 100) == -1) return 0; x = (io_rd(ad1816, AD1816_HIGH) << 8) | io_rd(ad1816, AD1816_LOW); return x; } static void ad1816_write(struct ad1816_info *ad1816, unsigned int reg, unsigned short data) { if (ad1816_wait_init(ad1816, 100) == -1) return; io_wr(ad1816, AD1816_ALE, (reg & AD1816_ALEMASK)); io_wr(ad1816, AD1816_LOW, (data & 0x000000ff)); io_wr(ad1816, AD1816_HIGH, (data & 0x0000ff00) >> 8); } /* -------------------------------------------------------------------- */ static int ad1816mix_init(struct snd_mixer *m) { mix_setdevs(m, AD1816_MIXER_DEVICES); mix_setrecdevs(m, AD1816_REC_DEVICES); return 0; } static int ad1816mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ad1816_info *ad1816 = mix_getdevinfo(m); u_short reg = 0; /* Scale volumes */ left = AD1816_MUTE - (AD1816_MUTE * left) / 100; right = AD1816_MUTE - (AD1816_MUTE * right) / 100; reg = (left << 8) | right; /* do channel selective muting if volume is zero */ if (left == AD1816_MUTE) reg |= 0x8000; if (right == AD1816_MUTE) reg |= 0x0080; ad1816_lock(ad1816); switch (dev) { case SOUND_MIXER_VOLUME: /* Register 14 master volume */ ad1816_write(ad1816, 14, reg); break; case SOUND_MIXER_CD: /* Register 15 cd */ case SOUND_MIXER_LINE1: ad1816_write(ad1816, 15, reg); break; case SOUND_MIXER_SYNTH: /* Register 16 synth */ ad1816_write(ad1816, 16, reg); break; case SOUND_MIXER_PCM: /* Register 4 pcm */ ad1816_write(ad1816, 4, reg); break; case SOUND_MIXER_LINE: case SOUND_MIXER_LINE3: /* Register 18 line in */ ad1816_write(ad1816, 18, reg); break; case SOUND_MIXER_MIC: /* Register 19 mic volume */ ad1816_write(ad1816, 19, reg & ~0xff); /* mic is mono */ break; case SOUND_MIXER_IGAIN: /* and now to something completely different ... */ ad1816_write(ad1816, 20, ((ad1816_read(ad1816, 20) & ~0x0f0f) | (((AD1816_MUTE - left) / 2) << 8) /* four bits of adc gain */ | ((AD1816_MUTE - right) / 2))); break; default: printf("ad1816_mixer_set(): unknown device.\n"); break; } ad1816_unlock(ad1816); left = ((AD1816_MUTE - left) * 100) / AD1816_MUTE; right = ((AD1816_MUTE - right) * 100) / AD1816_MUTE; return left | (right << 8); } static int ad1816mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct ad1816_info *ad1816 = mix_getdevinfo(m); int dev; switch (src) { case SOUND_MASK_LINE: case SOUND_MASK_LINE3: dev = 0x00; break; case SOUND_MASK_CD: case SOUND_MASK_LINE1: dev = 0x20; break; case SOUND_MASK_MIC: default: dev = 0x50; src = SOUND_MASK_MIC; } dev |= dev << 8; ad1816_lock(ad1816); ad1816_write(ad1816, 20, (ad1816_read(ad1816, 20) & ~0x7070) | dev); ad1816_unlock(ad1816); return src; } static kobj_method_t ad1816mixer_methods[] = { KOBJMETHOD(mixer_init, ad1816mix_init), KOBJMETHOD(mixer_set, ad1816mix_set), KOBJMETHOD(mixer_setrecsrc, ad1816mix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(ad1816mixer); /* -------------------------------------------------------------------- */ /* channel interface */ static void * ad1816chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct ad1816_info *ad1816 = devinfo; struct ad1816_chinfo *ch = (dir == PCMDIR_PLAY)? &ad1816->pch : &ad1816->rch; ch->parent = ad1816; ch->channel = c; ch->buffer = b; if (sndbuf_alloc(ch->buffer, ad1816->parent_dmat, 0, ad1816->bufsize) != 0) return NULL; return ch; } static int ad1816chan_setdir(kobj_t obj, void *data, int dir) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; sndbuf_dmasetup(ch->buffer, (dir == PCMDIR_PLAY)? ad1816->drq1 : ad1816->drq2); ch->dir = dir; return 0; } static int ad1816chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; int fmt = AD1816_U8, reg; ad1816_lock(ad1816); if (ch->dir == PCMDIR_PLAY) { reg = AD1816_PLAY; ad1816_write(ad1816, 8, 0x0000); /* reset base and current counter */ ad1816_write(ad1816, 9, 0x0000); /* for playback and capture */ } else { reg = AD1816_CAPT; ad1816_write(ad1816, 10, 0x0000); ad1816_write(ad1816, 11, 0x0000); } switch (format & ~AFMT_STEREO) { case AFMT_A_LAW: fmt = AD1816_ALAW; break; case AFMT_MU_LAW: fmt = AD1816_MULAW; break; case AFMT_S16_LE: fmt = AD1816_S16LE; break; case AFMT_S16_BE: fmt = AD1816_S16BE; break; case AFMT_U8: fmt = AD1816_U8; break; } if (format & AFMT_STEREO) fmt |= AD1816_STEREO; io_wr(ad1816, reg, fmt); ad1816_unlock(ad1816); #if 0 return format; #else return 0; #endif } static int ad1816chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; RANGE(speed, 4000, 55200); ad1816_lock(ad1816); ad1816_write(ad1816, (ch->dir == PCMDIR_PLAY)? 2 : 3, speed); ad1816_unlock(ad1816); return speed; } static int ad1816chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct ad1816_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int ad1816chan_trigger(kobj_t obj, void *data, int go) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; int wr, reg; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); wr = (ch->dir == PCMDIR_PLAY); reg = wr? AD1816_PLAY : AD1816_CAPT; ad1816_lock(ad1816); switch (go) { case PCMTRIG_START: /* start only if not already running */ if (!(io_rd(ad1816, reg) & AD1816_ENABLE)) { int cnt = ((ch->blksz) >> 2) - 1; ad1816_write(ad1816, wr? 8 : 10, cnt); /* count */ ad1816_write(ad1816, wr? 9 : 11, 0); /* reset cur cnt */ ad1816_write(ad1816, 1, ad1816_read(ad1816, 1) | (wr? 0x8000 : 0x4000)); /* enable int */ /* enable playback */ io_wr(ad1816, reg, io_rd(ad1816, reg) | AD1816_ENABLE); if (!(io_rd(ad1816, reg) & AD1816_ENABLE)) printf("ad1816: failed to start %s DMA!\n", wr? "play" : "rec"); } break; case PCMTRIG_STOP: case PCMTRIG_ABORT: /* XXX check this... */ /* we don't test here if it is running... */ if (wr) { ad1816_write(ad1816, 1, ad1816_read(ad1816, 1) & ~(wr? 0x8000 : 0x4000)); /* disable int */ io_wr(ad1816, reg, io_rd(ad1816, reg) & ~AD1816_ENABLE); /* disable playback */ if (io_rd(ad1816, reg) & AD1816_ENABLE) printf("ad1816: failed to stop %s DMA!\n", wr? "play" : "rec"); ad1816_write(ad1816, wr? 8 : 10, 0); /* reset base cnt */ ad1816_write(ad1816, wr? 9 : 11, 0); /* reset cur cnt */ } break; } ad1816_unlock(ad1816); return 0; } static int ad1816chan_getptr(kobj_t obj, void *data) { struct ad1816_chinfo *ch = data; return sndbuf_dmaptr(ch->buffer); } static struct pcmchan_caps * ad1816chan_getcaps(kobj_t obj, void *data) { return &ad1816_caps; } static kobj_method_t ad1816chan_methods[] = { KOBJMETHOD(channel_init, ad1816chan_init), KOBJMETHOD(channel_setdir, ad1816chan_setdir), KOBJMETHOD(channel_setformat, ad1816chan_setformat), KOBJMETHOD(channel_setspeed, ad1816chan_setspeed), KOBJMETHOD(channel_setblocksize, ad1816chan_setblocksize), KOBJMETHOD(channel_trigger, ad1816chan_trigger), KOBJMETHOD(channel_getptr, ad1816chan_getptr), KOBJMETHOD(channel_getcaps, ad1816chan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(ad1816chan); /* -------------------------------------------------------------------- */ static void ad1816_release_resources(struct ad1816_info *ad1816, device_t dev) { if (ad1816->irq) { if (ad1816->ih) bus_teardown_intr(dev, ad1816->irq, ad1816->ih); bus_release_resource(dev, SYS_RES_IRQ, ad1816->irq_rid, ad1816->irq); ad1816->irq = 0; } if (ad1816->drq1) { isa_dma_release(rman_get_start(ad1816->drq1)); bus_release_resource(dev, SYS_RES_DRQ, ad1816->drq1_rid, ad1816->drq1); ad1816->drq1 = 0; } if (ad1816->drq2) { isa_dma_release(rman_get_start(ad1816->drq2)); bus_release_resource(dev, SYS_RES_DRQ, ad1816->drq2_rid, ad1816->drq2); ad1816->drq2 = 0; } if (ad1816->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, ad1816->io_rid, ad1816->io_base); ad1816->io_base = 0; } if (ad1816->parent_dmat) { bus_dma_tag_destroy(ad1816->parent_dmat); ad1816->parent_dmat = 0; } if (ad1816->lock) snd_mtxfree(ad1816->lock); free(ad1816, M_DEVBUF); } static int ad1816_alloc_resources(struct ad1816_info *ad1816, device_t dev) { int ok = 1, pdma, rdma; if (!ad1816->io_base) ad1816->io_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &ad1816->io_rid, RF_ACTIVE); if (!ad1816->irq) ad1816->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ad1816->irq_rid, RF_ACTIVE); if (!ad1816->drq1) ad1816->drq1 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &ad1816->drq1_rid, RF_ACTIVE); if (!ad1816->drq2) ad1816->drq2 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &ad1816->drq2_rid, RF_ACTIVE); if (!ad1816->io_base || !ad1816->drq1 || !ad1816->irq) ok = 0; if (ok) { pdma = rman_get_start(ad1816->drq1); isa_dma_acquire(pdma); isa_dmainit(pdma, ad1816->bufsize); if (ad1816->drq2) { rdma = rman_get_start(ad1816->drq2); isa_dma_acquire(rdma); isa_dmainit(rdma, ad1816->bufsize); } else rdma = pdma; if (pdma == rdma) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); } return ok; } static int ad1816_init(struct ad1816_info *ad1816, device_t dev) { ad1816_write(ad1816, 1, 0x2); /* disable interrupts */ ad1816_write(ad1816, 32, 0x90F0); /* SoundSys Mode, split fmt */ ad1816_write(ad1816, 5, 0x8080); /* FM volume mute */ ad1816_write(ad1816, 6, 0x8080); /* I2S1 volume mute */ ad1816_write(ad1816, 7, 0x8080); /* I2S0 volume mute */ ad1816_write(ad1816, 17, 0x8888); /* VID Volume mute */ ad1816_write(ad1816, 20, 0x5050); /* recsrc mic, agc off */ /* adc gain is set to 0 */ return 0; } static int ad1816_probe(device_t dev) { char *s = NULL; u_int32_t logical_id = isa_get_logicalid(dev); switch (logical_id) { case 0x80719304: /* ADS7180 */ s = "AD1816"; break; case 0x50719304: /* ADS7150 */ s = "AD1815"; break; } if (s) { device_set_desc(dev, s); return BUS_PROBE_DEFAULT; } return ENXIO; } static int ad1816_attach(device_t dev) { struct ad1816_info *ad1816; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; ad1816 = (struct ad1816_info *)malloc(sizeof *ad1816, M_DEVBUF, M_NOWAIT | M_ZERO); if (!ad1816) return ENXIO; ad1816->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ad1816 softc"); ad1816->io_rid = 2; ad1816->irq_rid = 0; ad1816->drq1_rid = 0; ad1816->drq2_rid = 1; ad1816->bufsize = pcm_getbuffersize(dev, 4096, DSP_BUFFSIZE, 65536); if (!ad1816_alloc_resources(ad1816, dev)) goto no; ad1816_init(ad1816, dev); if (mixer_init(dev, &ad1816mixer_class, ad1816)) goto no; snd_setup_intr(dev, ad1816->irq, 0, ad1816_intr, ad1816, &ad1816->ih); 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*/ad1816->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/ &Giant, &ad1816->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (ad1816->drq2) snprintf(status2, SND_STATUSLEN, ":%ld", rman_get_start(ad1816->drq2)); else status2[0] = '\0'; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u %s", rman_get_start(ad1816->io_base), rman_get_start(ad1816->irq), rman_get_start(ad1816->drq1), status2, ad1816->bufsize, PCM_KLDSTRING(snd_ad1816)); if (pcm_register(dev, ad1816, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &ad1816chan_class, ad1816); pcm_addchan(dev, PCMDIR_PLAY, &ad1816chan_class, ad1816); pcm_setstatus(dev, status); return 0; no: ad1816_release_resources(ad1816, dev); return ENXIO; } static int ad1816_detach(device_t dev) { int r; struct ad1816_info *ad1816; r = pcm_unregister(dev); if (r) return r; ad1816 = pcm_getdevinfo(dev); ad1816_release_resources(ad1816, dev); return 0; } static device_method_t ad1816_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ad1816_probe), DEVMETHOD(device_attach, ad1816_attach), DEVMETHOD(device_detach, ad1816_detach), { 0, 0 } }; static driver_t ad1816_driver = { "pcm", ad1816_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_ad1816, isa, ad1816_driver, pcm_devclass, 0, 0); DRIVER_MODULE(snd_ad1816, acpi, ad1816_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ad1816, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_ad1816, 1); Index: head/sys/dev/sound/isa/ess.c =================================================================== --- head/sys/dev/sound/isa/ess.c (revision 170520) +++ head/sys/dev/sound/isa/ess.c (revision 170521) @@ -1,1015 +1,1015 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright * conditions. * 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. */ #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define ESS_BUFFSIZE (4096) #define ABS(x) (((x) < 0)? -(x) : (x)) /* audio2 never generates irqs and sounds very noisy */ #undef ESS18XX_DUPLEX /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED static u_int32_t ess_pfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, 0 }; static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_pfmt, 0}; static u_int32_t ess_rfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, 0 }; static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_rfmt, 0}; struct ess_info; struct ess_chinfo { struct ess_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, hwch, stopping, run; u_int32_t fmt, spd, blksz; }; struct ess_info { device_t parent_dev; struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; void *ih; bus_dma_tag_t parent_dmat; unsigned int bufsize; int type; unsigned int duplex:1, newspeed:1; u_long bd_flags; /* board-specific flags */ struct ess_chinfo pch, rch; }; #if 0 static int ess_rd(struct ess_info *sc, int reg); static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); static int ess_dspready(struct ess_info *sc); static int ess_cmd(struct ess_info *sc, u_char val); static int ess_cmd1(struct ess_info *sc, u_char cmd, int val); static int ess_get_byte(struct ess_info *sc); static void ess_setmixer(struct ess_info *sc, u_int port, u_int value); static int ess_getmixer(struct ess_info *sc, u_int port); static int ess_reset_dsp(struct ess_info *sc); static int ess_write(struct ess_info *sc, u_char reg, int val); static int ess_read(struct ess_info *sc, u_char reg); static void ess_intr(void *arg); static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len); static int ess_start(struct ess_chinfo *ch); static int ess_stop(struct ess_chinfo *ch); #endif /* * Common code for the midi and pcm functions * * ess_cmd write a single byte to the CMD port. * ess_cmd1 write a CMD + 1 byte arg * ess_cmd2 write a CMD + 2 byte arg * ess_get_byte returns a single byte from the DSP data port * * ess_write is actually ess_cmd1 * ess_read access ext. regs via ess_cmd(0xc0, reg) followed by ess_get_byte */ static void ess_lock(struct ess_info *sc) { sbc_lock(device_get_softc(sc->parent_dev)); } static void ess_unlock(struct ess_info *sc) { sbc_unlock(device_get_softc(sc->parent_dev)); } static int port_rd(struct resource *port, int off) { return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); } static void port_wr(struct resource *port, int off, u_int8_t data) { bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); } static int ess_rd(struct ess_info *sc, int reg) { return port_rd(sc->io_base, reg); } static void ess_wr(struct ess_info *sc, int reg, u_int8_t val) { port_wr(sc->io_base, reg, val); } static int ess_dspready(struct ess_info *sc) { return ((ess_rd(sc, SBDSP_STATUS) & 0x80) == 0); } static int ess_dspwr(struct ess_info *sc, u_char val) { int i; for (i = 0; i < 1000; i++) { if (ess_dspready(sc)) { ess_wr(sc, SBDSP_CMD, val); return 1; } if (i > 10) DELAY((i > 100)? 1000 : 10); } printf("ess_dspwr(0x%02x) timed out.\n", val); return 0; } static int ess_cmd(struct ess_info *sc, u_char val) { #if 0 printf("ess_cmd: %x\n", val); #endif return ess_dspwr(sc, val); } static int ess_cmd1(struct ess_info *sc, u_char cmd, int val) { #if 0 printf("ess_cmd1: %x, %x\n", cmd, val); #endif if (ess_dspwr(sc, cmd)) { return ess_dspwr(sc, val & 0xff); } else return 0; } static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); return val; } static int ess_get_byte(struct ess_info *sc) { int i; for (i = 1000; i > 0; i--) { if (ess_rd(sc, DSP_DATA_AVAIL) & 0x80) return ess_rd(sc, DSP_READ); else DELAY(20); } return -1; } static int ess_write(struct ess_info *sc, u_char reg, int val) { return ess_cmd1(sc, reg, val); } static int ess_read(struct ess_info *sc, u_char reg) { return (ess_cmd(sc, 0xc0) && ess_cmd(sc, reg))? ess_get_byte(sc) : -1; } static int ess_reset_dsp(struct ess_info *sc) { ess_wr(sc, SBDSP_RST, 3); DELAY(100); ess_wr(sc, SBDSP_RST, 0); if (ess_get_byte(sc) != 0xAA) { DEB(printf("ess_reset_dsp 0x%lx failed\n", rman_get_start(sc->io_base))); return ENXIO; /* Sorry */ } ess_cmd(sc, 0xc6); return 0; } static void ess_release_resources(struct ess_info *sc, device_t dev) { if (sc->irq) { if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = 0; } if (sc->drq1) { isa_dma_release(rman_get_start(sc->drq1)); bus_release_resource(dev, SYS_RES_DRQ, 0, sc->drq1); sc->drq1 = 0; } if (sc->drq2) { isa_dma_release(rman_get_start(sc->drq2)); bus_release_resource(dev, SYS_RES_DRQ, 1, sc->drq2); sc->drq2 = 0; } if (sc->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->io_base); sc->io_base = 0; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } free(sc, M_DEVBUF); } static int ess_alloc_resources(struct ess_info *sc, device_t dev) { int rid; rid = 0; if (!sc->io_base) sc->io_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; if (!sc->irq) sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); rid = 0; if (!sc->drq1) sc->drq1 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); rid = 1; if (!sc->drq2) sc->drq2 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); if (sc->io_base && sc->drq1 && sc->irq) { isa_dma_acquire(rman_get_start(sc->drq1)); isa_dmainit(rman_get_start(sc->drq1), sc->bufsize); if (sc->drq2) { isa_dma_acquire(rman_get_start(sc->drq2)); isa_dmainit(rman_get_start(sc->drq2), sc->bufsize); } return 0; } else return ENXIO; } static void ess_intr(void *arg) { struct ess_info *sc = (struct ess_info *)arg; int src, pirq, rirq; ess_lock(sc); src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; pirq = (src & sc->pch.hwch)? 1 : 0; rirq = (src & sc->rch.hwch)? 1 : 0; if (pirq) { if (sc->pch.run) { ess_unlock(sc); chn_intr(sc->pch.channel); ess_lock(sc); } if (sc->pch.stopping) { sc->pch.run = 0; sndbuf_dma(sc->pch.buffer, PCMTRIG_STOP); sc->pch.stopping = 0; if (sc->pch.hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } } if (rirq) { if (sc->rch.run) { ess_unlock(sc); chn_intr(sc->rch.channel); ess_lock(sc); } if (sc->rch.stopping) { sc->rch.run = 0; sndbuf_dma(sc->rch.buffer, PCMTRIG_STOP); sc->rch.stopping = 0; /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); ess_unlock(sc); } /* utility functions for ESS */ static u_int8_t ess_calcspeed8(int *spd) { int speed = *spd; u_int32_t t; if (speed > 22000) { t = (795500 + speed / 2) / speed; speed = (795500 + t / 2) / t; t = (256 - t) | 0x80; } else { t = (397700 + speed / 2) / speed; speed = (397700 + t / 2) / t; t = 128 - t; } *spd = speed; return t & 0x000000ff; } static u_int8_t ess_calcspeed9(int *spd) { int speed, s0, s1, use0; u_int8_t t0, t1; /* rate = source / (256 - divisor) */ /* divisor = 256 - (source / rate) */ speed = *spd; t0 = 128 - (793800 / speed); s0 = 793800 / (128 - t0); t1 = 128 - (768000 / speed); s1 = 768000 / (128 - t1); t1 |= 0x80; use0 = (ABS(speed - s0) < ABS(speed - s1))? 1 : 0; *spd = use0? s0 : s1; return use0? t0 : t1; } static u_int8_t ess_calcfilter(int spd) { int cutoff; /* cutoff = 7160000 / (256 - divisor) */ /* divisor = 256 - (7160000 / cutoff) */ cutoff = (spd * 9 * 82) / 20; return (256 - (7160000 / cutoff)); } static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len) { int play = (dir == PCMDIR_PLAY)? 1 : 0; int b16 = (fmt & AFMT_16BIT)? 1 : 0; int stereo = (fmt & AFMT_STEREO)? 1 : 0; int unsign = (fmt == AFMT_U8 || fmt == AFMT_U16_LE)? 1 : 0; u_int8_t spdval, fmtval; spdval = (sc->newspeed)? ess_calcspeed9(&spd) : ess_calcspeed8(&spd); len = -len; if (ch == 1) { KASSERT((dir == PCMDIR_PLAY) || (dir == PCMDIR_REC), ("ess_setupch: dir1 bad")); /* transfer length low */ ess_write(sc, 0xa4, len & 0x00ff); /* transfer length high */ ess_write(sc, 0xa5, (len & 0xff00) >> 8); /* autoinit, dma dir */ ess_write(sc, 0xb8, 0x04 | (play? 0x00 : 0x0a)); /* mono/stereo */ ess_write(sc, 0xa8, (ess_read(sc, 0xa8) & ~0x03) | (stereo? 0x01 : 0x02)); /* demand mode, 4 bytes/xfer */ ess_write(sc, 0xb9, 0x02); /* sample rate */ ess_write(sc, 0xa1, spdval); /* filter cutoff */ ess_write(sc, 0xa2, ess_calcfilter(spd)); /* setup dac/adc */ if (play) ess_write(sc, 0xb6, unsign? 0x80 : 0x00); /* mono, b16: signed, load signal */ ess_write(sc, 0xb7, 0x51 | (unsign? 0x00 : 0x20)); /* setup fifo */ ess_write(sc, 0xb7, 0x90 | (unsign? 0x00 : 0x20) | (b16? 0x04 : 0x00) | (stereo? 0x08 : 0x40)); /* irq control */ ess_write(sc, 0xb1, (ess_read(sc, 0xb1) & 0x0f) | 0x50); /* drq control */ ess_write(sc, 0xb2, (ess_read(sc, 0xb2) & 0x0f) | 0x50); } else if (ch == 2) { KASSERT(dir == PCMDIR_PLAY, ("ess_setupch: dir2 bad")); /* transfer length low */ ess_setmixer(sc, 0x74, len & 0x00ff); /* transfer length high */ ess_setmixer(sc, 0x76, (len & 0xff00) >> 8); /* autoinit, 4 bytes/req */ ess_setmixer(sc, 0x78, 0x90); fmtval = b16 | (stereo << 1) | (unsign << 2); /* enable irq, set format */ ess_setmixer(sc, 0x7a, 0x40 | fmtval); if (sc->newspeed) { /* sample rate */ ess_setmixer(sc, 0x70, spdval); /* filter cutoff */ ess_setmixer(sc, 0x72, ess_calcfilter(spd)); } } return 0; } static int ess_start(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; ess_lock(sc); ess_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->blksz); ch->stopping = 0; if (ch->hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) | 0x01); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) | 0x03); if (play) ess_cmd(sc, DSP_CMD_SPKON); ess_unlock(sc); return 0; } static int ess_stop(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; ess_lock(sc); ch->stopping = 1; if (ch->hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x04); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x10); if (play) ess_cmd(sc, DSP_CMD_SPKOFF); ess_unlock(sc); return 0; } /* -------------------------------------------------------------------- */ /* channel interface for ESS18xx */ static void * esschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct ess_info *sc = devinfo; struct ess_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->buffer = b; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsize) != 0) return NULL; ch->dir = dir; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; sndbuf_dmasetup(ch->buffer, (ch->hwch == 1)? sc->drq1 : sc->drq2); return ch; } static int esschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static int esschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; ch->spd = speed; if (sc->newspeed) ess_calcspeed9(&ch->spd); else ess_calcspeed8(&ch->spd); return ch->spd; } static int esschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct ess_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int esschan_trigger(kobj_t obj, void *data, int go) { struct ess_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; switch (go) { case PCMTRIG_START: ch->run = 1; sndbuf_dma(ch->buffer, go); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } return 0; } static int esschan_getptr(kobj_t obj, void *data) { struct ess_chinfo *ch = data; return sndbuf_dmaptr(ch->buffer); } static struct pcmchan_caps * esschan_getcaps(kobj_t obj, void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } static kobj_method_t esschan_methods[] = { KOBJMETHOD(channel_init, esschan_init), KOBJMETHOD(channel_setformat, esschan_setformat), KOBJMETHOD(channel_setspeed, esschan_setspeed), KOBJMETHOD(channel_setblocksize, esschan_setblocksize), KOBJMETHOD(channel_trigger, esschan_trigger), KOBJMETHOD(channel_getptr, esschan_getptr), KOBJMETHOD(channel_getcaps, esschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(esschan); /************************************************************/ static int essmix_init(struct snd_mixer *m) { struct ess_info *sc = mix_getdevinfo(m); mix_setrecdevs(m, SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_IMIX); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME | SOUND_MASK_LINE1 | SOUND_MASK_SPEAKER); ess_setmixer(sc, 0, 0); /* reset */ return 0; } static int essmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ess_info *sc = mix_getdevinfo(m); int preg = 0, rreg = 0, l, r; l = (left * 15) / 100; r = (right * 15) / 100; switch (dev) { case SOUND_MIXER_SYNTH: preg = 0x36; rreg = 0x6b; break; case SOUND_MIXER_PCM: preg = 0x14; rreg = 0x7c; break; case SOUND_MIXER_LINE: preg = 0x3e; rreg = 0x6e; break; case SOUND_MIXER_MIC: preg = 0x1a; rreg = 0x68; break; case SOUND_MIXER_LINE1: preg = 0x3a; rreg = 0x6c; break; case SOUND_MIXER_CD: preg = 0x38; rreg = 0x6a; break; case SOUND_MIXER_SPEAKER: preg = 0x3c; break; case SOUND_MIXER_VOLUME: l = left? (left * 63) / 100 : 64; r = right? (right * 63) / 100 : 64; ess_setmixer(sc, 0x60, l); ess_setmixer(sc, 0x62, r); left = (l == 64)? 0 : (l * 100) / 63; right = (r == 64)? 0 : (r * 100) / 63; return left | (right << 8); } if (preg) ess_setmixer(sc, preg, (l << 4) | r); if (rreg) ess_setmixer(sc, rreg, (l << 4) | r); left = (l * 100) / 15; right = (r * 100) / 15; return left | (right << 8); } static int essmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct ess_info *sc = mix_getdevinfo(m); u_char recdev; switch (src) { case SOUND_MASK_CD: recdev = 0x02; break; case SOUND_MASK_LINE: recdev = 0x06; break; case SOUND_MASK_IMIX: recdev = 0x05; break; case SOUND_MASK_MIC: default: recdev = 0x00; src = SOUND_MASK_MIC; break; } ess_setmixer(sc, 0x1c, recdev); return src; } static kobj_method_t essmixer_methods[] = { KOBJMETHOD(mixer_init, essmix_init), KOBJMETHOD(mixer_set, essmix_set), KOBJMETHOD(mixer_setrecsrc, essmix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(essmixer); /************************************************************/ static int ess_probe(device_t dev) { uintptr_t func, ver, r, f; /* The parent device has already been probed. */ r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); if (func != SCF_PCM) return (ENXIO); r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); f = (ver & 0xffff0000) >> 16; if (!(f & BD_F_ESS)) return (ENXIO); device_set_desc(dev, "ESS 18xx DSP"); return 0; } static int ess_attach(device_t dev) { struct ess_info *sc; char status[SND_STATUSLEN], buf[64]; int ver; sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO); if (!sc) return ENXIO; sc->parent_dev = device_get_parent(dev); sc->bufsize = pcm_getbuffersize(dev, 4096, ESS_BUFFSIZE, 65536); if (ess_alloc_resources(sc, dev)) goto no; if (ess_reset_dsp(sc)) goto no; if (mixer_init(dev, &essmixer_class, sc)) goto no; sc->duplex = 0; sc->newspeed = 0; ver = (ess_getmixer(sc, 0x40) << 8) | ess_rd(sc, SB_MIX_DATA); snprintf(buf, sizeof buf, "ESS %x DSP", ver); device_set_desc_copy(dev, buf); if (bootverbose) device_printf(dev, "ESS%x detected", ver); switch (ver) { case 0x1869: case 0x1879: #ifdef ESS18XX_DUPLEX sc->duplex = sc->drq2? 1 : 0; #endif #ifdef ESS18XX_NEWSPEED sc->newspeed = 1; #endif break; } if (bootverbose) printf("%s%s\n", sc->duplex? ", duplex" : "", sc->newspeed? ", newspeed" : ""); if (sc->newspeed) ess_setmixer(sc, 0x71, 0x22); snd_setup_intr(dev, sc->irq, 0, ess_intr, sc, &sc->ih); if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); 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->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (sc->drq2) snprintf(buf, SND_STATUSLEN, ":%ld", rman_get_start(sc->drq2)); else buf[0] = '\0'; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u %s", rman_get_start(sc->io_base), rman_get_start(sc->irq), rman_get_start(sc->drq1), buf, sc->bufsize, PCM_KLDSTRING(snd_ess)); if (pcm_register(dev, sc, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &esschan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &esschan_class, sc); pcm_setstatus(dev, status); return 0; no: ess_release_resources(sc, dev); return ENXIO; } static int ess_detach(device_t dev) { int r; struct ess_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); ess_release_resources(sc, dev); return 0; } static int ess_resume(device_t dev) { struct ess_info *sc; sc = pcm_getdevinfo(dev); if (ess_reset_dsp(sc)) { device_printf(dev, "unable to reset DSP at resume\n"); return ENXIO; } if (mixer_reinit(dev)) { device_printf(dev, "unable to reinitialize mixer at resume\n"); return ENXIO; } return 0; } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), DEVMETHOD(device_detach, ess_detach), DEVMETHOD(device_resume, ess_resume), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_ess, sbc, ess_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ess, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_ess, snd_sbc, 1, 1, 1); MODULE_VERSION(snd_ess, 1); /************************************************************/ static devclass_t esscontrol_devclass; static struct isa_pnp_id essc_ids[] = { {0x06007316, "ESS Control"}, {0} }; static int esscontrol_probe(device_t dev) { int i; i = ISA_PNP_PROBE(device_get_parent(dev), dev, essc_ids); if (i == 0) device_quiet(dev); return i; } static int esscontrol_attach(device_t dev) { #ifdef notyet struct resource *io; int rid, i, x; rid = 0; io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); x = 0; for (i = 0; i < 0x100; i++) { port_wr(io, 0, i); x = port_rd(io, 1); if ((i & 0x0f) == 0) printf("%3.3x: ", i); printf("%2.2x ", x); if ((i & 0x0f) == 0x0f) printf("\n"); } bus_release_resource(dev, SYS_RES_IOPORT, 0, io); io = NULL; #endif return 0; } static int esscontrol_detach(device_t dev) { return 0; } static device_method_t esscontrol_methods[] = { /* Device interface */ DEVMETHOD(device_probe, esscontrol_probe), DEVMETHOD(device_attach, esscontrol_attach), DEVMETHOD(device_detach, esscontrol_detach), { 0, 0 } }; static driver_t esscontrol_driver = { "esscontrol", esscontrol_methods, 1, }; DRIVER_MODULE(esscontrol, isa, esscontrol_driver, esscontrol_devclass, 0, 0); DRIVER_MODULE(esscontrol, acpi, esscontrol_driver, esscontrol_devclass, 0, 0); Index: head/sys/dev/sound/isa/mss.c =================================================================== --- head/sys/dev/sound/isa/mss.c (revision 170520) +++ head/sys/dev/sound/isa/mss.c (revision 170521) @@ -1,2317 +1,2317 @@ /*- * Copyright (c) 2001 George Reid * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997,1998 Luigi Rizzo * Copyright (c) 1994,1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include SND_DECLARE_FILE("$FreeBSD$"); /* board-specific include files */ #include #include #include #include #include "mixer_if.h" #define MSS_DEFAULT_BUFSZ (4096) #define MSS_INDEXED_REGS 0x20 #define OPL_INDEXED_REGS 0x19 struct mss_info; struct mss_chinfo { struct mss_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; u_int32_t fmt, blksz; }; struct mss_info { struct resource *io_base; /* primary I/O address for the board */ int io_rid; struct resource *conf_base; /* and the opti931 also has a config space */ int conf_rid; struct resource *irq; int irq_rid; struct resource *drq1; /* play */ int drq1_rid; struct resource *drq2; /* rec */ int drq2_rid; void *ih; bus_dma_tag_t parent_dmat; struct mtx *lock; char mss_indexed_regs[MSS_INDEXED_REGS]; char opl_indexed_regs[OPL_INDEXED_REGS]; int bd_id; /* used to hold board-id info, eg. sb version, * mss codec type, etc. etc. */ int opti_offset; /* offset from config_base for opti931 */ u_long bd_flags; /* board-specific flags */ int optibase; /* base address for OPTi9xx config */ struct resource *indir; /* Indirect register index address */ int indir_rid; int password; /* password for opti9xx cards */ int passwdreg; /* password register */ unsigned int bufsize; struct mss_chinfo pch, rch; }; static int mss_probe(device_t dev); static int mss_attach(device_t dev); static driver_intr_t mss_intr; /* prototypes for local functions */ static int mss_detect(device_t dev, struct mss_info *mss); #ifndef PC98 static int opti_detect(device_t dev, struct mss_info *mss); #endif static char *ymf_test(device_t dev, struct mss_info *mss); static void ad_unmute(struct mss_info *mss); /* mixer set funcs */ static int mss_mixer_set(struct mss_info *mss, int dev, int left, int right); static int mss_set_recsrc(struct mss_info *mss, int mask); /* io funcs */ static int ad_wait_init(struct mss_info *mss, int x); static int ad_read(struct mss_info *mss, int reg); static void ad_write(struct mss_info *mss, int reg, u_char data); static void ad_write_cnt(struct mss_info *mss, int reg, u_short data); static void ad_enter_MCE(struct mss_info *mss); static void ad_leave_MCE(struct mss_info *mss); /* OPTi-specific functions */ static void opti_write(struct mss_info *mss, u_char reg, u_char data); #ifndef PC98 static u_char opti_read(struct mss_info *mss, u_char reg); #endif static int opti_init(device_t dev, struct mss_info *mss); /* io primitives */ static void conf_wr(struct mss_info *mss, u_char reg, u_char data); static u_char conf_rd(struct mss_info *mss, u_char reg); static int pnpmss_probe(device_t dev); static int pnpmss_attach(device_t dev); static driver_intr_t opti931_intr; static u_int32_t mss_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_MU_LAW, AFMT_STEREO | AFMT_MU_LAW, AFMT_A_LAW, AFMT_STEREO | AFMT_A_LAW, 0 }; static struct pcmchan_caps mss_caps = {4000, 48000, mss_fmt, 0}; static u_int32_t guspnp_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_A_LAW, AFMT_STEREO | AFMT_A_LAW, 0 }; static struct pcmchan_caps guspnp_caps = {4000, 48000, guspnp_fmt, 0}; static u_int32_t opti931_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps opti931_caps = {4000, 48000, opti931_fmt, 0}; #define MD_AD1848 0x91 #define MD_AD1845 0x92 #define MD_CS42XX 0xA1 #define MD_CS423X 0xA2 #define MD_OPTI930 0xB0 #define MD_OPTI931 0xB1 #define MD_OPTI925 0xB2 #define MD_OPTI924 0xB3 #define MD_GUSPNP 0xB8 #define MD_GUSMAX 0xB9 #define MD_YM0020 0xC1 #define MD_VIVO 0xD1 #define DV_F_TRUE_MSS 0x00010000 /* mss _with_ base regs */ #define FULL_DUPLEX(x) ((x)->bd_flags & BD_F_DUPLEX) static void mss_lock(struct mss_info *mss) { snd_mtxlock(mss->lock); } static void mss_unlock(struct mss_info *mss) { snd_mtxunlock(mss->lock); } static int port_rd(struct resource *port, int off) { if (port) return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); else return -1; } static void port_wr(struct resource *port, int off, u_int8_t data) { if (port) bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); } static int io_rd(struct mss_info *mss, int reg) { if (mss->bd_flags & BD_F_MSS_OFFSET) reg -= 4; return port_rd(mss->io_base, reg); } static void io_wr(struct mss_info *mss, int reg, u_int8_t data) { if (mss->bd_flags & BD_F_MSS_OFFSET) reg -= 4; port_wr(mss->io_base, reg, data); } static void conf_wr(struct mss_info *mss, u_char reg, u_char value) { port_wr(mss->conf_base, 0, reg); port_wr(mss->conf_base, 1, value); } static u_char conf_rd(struct mss_info *mss, u_char reg) { port_wr(mss->conf_base, 0, reg); return port_rd(mss->conf_base, 1); } static void opti_wr(struct mss_info *mss, u_char reg, u_char value) { port_wr(mss->conf_base, mss->opti_offset + 0, reg); port_wr(mss->conf_base, mss->opti_offset + 1, value); } static u_char opti_rd(struct mss_info *mss, u_char reg) { port_wr(mss->conf_base, mss->opti_offset + 0, reg); return port_rd(mss->conf_base, mss->opti_offset + 1); } static void gus_wr(struct mss_info *mss, u_char reg, u_char value) { port_wr(mss->conf_base, 3, reg); port_wr(mss->conf_base, 5, value); } static u_char gus_rd(struct mss_info *mss, u_char reg) { port_wr(mss->conf_base, 3, reg); return port_rd(mss->conf_base, 5); } static void mss_release_resources(struct mss_info *mss, device_t dev) { if (mss->irq) { if (mss->ih) bus_teardown_intr(dev, mss->irq, mss->ih); bus_release_resource(dev, SYS_RES_IRQ, mss->irq_rid, mss->irq); mss->irq = 0; } if (mss->drq2) { if (mss->drq2 != mss->drq1) { isa_dma_release(rman_get_start(mss->drq2)); bus_release_resource(dev, SYS_RES_DRQ, mss->drq2_rid, mss->drq2); } mss->drq2 = 0; } if (mss->drq1) { isa_dma_release(rman_get_start(mss->drq1)); bus_release_resource(dev, SYS_RES_DRQ, mss->drq1_rid, mss->drq1); mss->drq1 = 0; } if (mss->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, mss->io_rid, mss->io_base); mss->io_base = 0; } if (mss->conf_base) { bus_release_resource(dev, SYS_RES_IOPORT, mss->conf_rid, mss->conf_base); mss->conf_base = 0; } if (mss->indir) { bus_release_resource(dev, SYS_RES_IOPORT, mss->indir_rid, mss->indir); mss->indir = 0; } if (mss->parent_dmat) { bus_dma_tag_destroy(mss->parent_dmat); mss->parent_dmat = 0; } if (mss->lock) snd_mtxfree(mss->lock); free(mss, M_DEVBUF); } static int mss_alloc_resources(struct mss_info *mss, device_t dev) { int pdma, rdma, ok = 1; if (!mss->io_base) mss->io_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &mss->io_rid, RF_ACTIVE); if (!mss->irq) mss->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &mss->irq_rid, RF_ACTIVE); if (!mss->drq1) mss->drq1 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &mss->drq1_rid, RF_ACTIVE); if (mss->conf_rid >= 0 && !mss->conf_base) mss->conf_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &mss->conf_rid, RF_ACTIVE); if (mss->drq2_rid >= 0 && !mss->drq2) mss->drq2 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &mss->drq2_rid, RF_ACTIVE); if (!mss->io_base || !mss->drq1 || !mss->irq) ok = 0; if (mss->conf_rid >= 0 && !mss->conf_base) ok = 0; if (mss->drq2_rid >= 0 && !mss->drq2) ok = 0; if (ok) { pdma = rman_get_start(mss->drq1); isa_dma_acquire(pdma); isa_dmainit(pdma, mss->bufsize); mss->bd_flags &= ~BD_F_DUPLEX; if (mss->drq2) { rdma = rman_get_start(mss->drq2); isa_dma_acquire(rdma); isa_dmainit(rdma, mss->bufsize); mss->bd_flags |= BD_F_DUPLEX; } else mss->drq2 = mss->drq1; } return ok; } /* * The various mixers use a variety of bitmasks etc. The Voxware * driver had a very nice technique to describe a mixer and interface * to it. A table defines, for each channel, which register, bits, * offset, polarity to use. This procedure creates the new value * using the table and the old value. */ static void change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval) { u_char mask; int shift; DEB(printf("ch_bits dev %d ch %d val %d old 0x%02x " "r %d p %d bit %d off %d\n", dev, chn, newval, *regval, (*t)[dev][chn].regno, (*t)[dev][chn].polarity, (*t)[dev][chn].nbits, (*t)[dev][chn].bitoffs ) ); if ( (*t)[dev][chn].polarity == 1) /* reverse */ newval = 100 - newval ; mask = (1 << (*t)[dev][chn].nbits) - 1; newval = (int) ((newval * mask) + 50) / 100; /* Scale it */ shift = (*t)[dev][chn].bitoffs /*- (*t)[dev][LEFT_CHN].nbits + 1*/; *regval &= ~(mask << shift); /* Filter out the previous value */ *regval |= (newval & mask) << shift; /* Set the new value */ } /* -------------------------------------------------------------------- */ /* only one source can be set... */ static int mss_set_recsrc(struct mss_info *mss, int mask) { u_char recdev; switch (mask) { case SOUND_MASK_LINE: case SOUND_MASK_LINE3: recdev = 0; break; case SOUND_MASK_CD: case SOUND_MASK_LINE1: recdev = 0x40; break; case SOUND_MASK_IMIX: recdev = 0xc0; break; case SOUND_MASK_MIC: default: mask = SOUND_MASK_MIC; recdev = 0x80; } ad_write(mss, 0, (ad_read(mss, 0) & 0x3f) | recdev); ad_write(mss, 1, (ad_read(mss, 1) & 0x3f) | recdev); return mask; } /* there are differences in the mixer depending on the actual sound card. */ static int mss_mixer_set(struct mss_info *mss, int dev, int left, int right) { int regoffs; mixer_tab *mix_d; u_char old, val; switch (mss->bd_id) { case MD_OPTI931: mix_d = &opti931_devices; break; case MD_OPTI930: mix_d = &opti930_devices; break; default: mix_d = &mix_devices; } if ((*mix_d)[dev][LEFT_CHN].nbits == 0) { DEB(printf("nbits = 0 for dev %d\n", dev)); return -1; } if ((*mix_d)[dev][RIGHT_CHN].nbits == 0) right = left; /* mono */ /* Set the left channel */ regoffs = (*mix_d)[dev][LEFT_CHN].regno; old = val = ad_read(mss, regoffs); /* if volume is 0, mute chan. Otherwise, unmute. */ if (regoffs != 0) val = (left == 0)? old | 0x80 : old & 0x7f; change_bits(mix_d, &val, dev, LEFT_CHN, left); ad_write(mss, regoffs, val); DEB(printf("LEFT: dev %d reg %d old 0x%02x new 0x%02x\n", dev, regoffs, old, val)); if ((*mix_d)[dev][RIGHT_CHN].nbits != 0) { /* have stereo */ /* Set the right channel */ regoffs = (*mix_d)[dev][RIGHT_CHN].regno; old = val = ad_read(mss, regoffs); if (regoffs != 1) val = (right == 0)? old | 0x80 : old & 0x7f; change_bits(mix_d, &val, dev, RIGHT_CHN, right); ad_write(mss, regoffs, val); DEB(printf("RIGHT: dev %d reg %d old 0x%02x new 0x%02x\n", dev, regoffs, old, val)); } return 0; /* success */ } /* -------------------------------------------------------------------- */ static int mssmix_init(struct snd_mixer *m) { struct mss_info *mss = mix_getdevinfo(m); mix_setdevs(m, MODE2_MIXER_DEVICES); mix_setrecdevs(m, MSS_REC_DEVICES); switch(mss->bd_id) { case MD_OPTI930: mix_setdevs(m, OPTI930_MIXER_DEVICES); break; case MD_OPTI931: mix_setdevs(m, OPTI931_MIXER_DEVICES); mss_lock(mss); ad_write(mss, 20, 0x88); ad_write(mss, 21, 0x88); mss_unlock(mss); break; case MD_AD1848: mix_setdevs(m, MODE1_MIXER_DEVICES); break; case MD_GUSPNP: case MD_GUSMAX: /* this is only necessary in mode 3 ... */ mss_lock(mss); ad_write(mss, 22, 0x88); ad_write(mss, 23, 0x88); mss_unlock(mss); break; } return 0; } static int mssmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct mss_info *mss = mix_getdevinfo(m); mss_lock(mss); mss_mixer_set(mss, dev, left, right); mss_unlock(mss); return left | (right << 8); } static int mssmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct mss_info *mss = mix_getdevinfo(m); mss_lock(mss); src = mss_set_recsrc(mss, src); mss_unlock(mss); return src; } static kobj_method_t mssmix_mixer_methods[] = { KOBJMETHOD(mixer_init, mssmix_init), KOBJMETHOD(mixer_set, mssmix_set), KOBJMETHOD(mixer_setrecsrc, mssmix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(mssmix_mixer); /* -------------------------------------------------------------------- */ static int ymmix_init(struct snd_mixer *m) { struct mss_info *mss = mix_getdevinfo(m); mssmix_init(m); mix_setdevs(m, mix_getdevs(m) | SOUND_MASK_VOLUME | SOUND_MASK_MIC | SOUND_MASK_BASS | SOUND_MASK_TREBLE); /* Set master volume */ mss_lock(mss); conf_wr(mss, OPL3SAx_VOLUMEL, 7); conf_wr(mss, OPL3SAx_VOLUMER, 7); mss_unlock(mss); return 0; } static int ymmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct mss_info *mss = mix_getdevinfo(m); int t, l, r; mss_lock(mss); switch (dev) { case SOUND_MIXER_VOLUME: if (left) t = 15 - (left * 15) / 100; else t = 0x80; /* mute */ conf_wr(mss, OPL3SAx_VOLUMEL, t); if (right) t = 15 - (right * 15) / 100; else t = 0x80; /* mute */ conf_wr(mss, OPL3SAx_VOLUMER, t); break; case SOUND_MIXER_MIC: t = left; if (left) t = 31 - (left * 31) / 100; else t = 0x80; /* mute */ conf_wr(mss, OPL3SAx_MIC, t); break; case SOUND_MIXER_BASS: l = (left * 7) / 100; r = (right * 7) / 100; t = (r << 4) | l; conf_wr(mss, OPL3SAx_BASS, t); break; case SOUND_MIXER_TREBLE: l = (left * 7) / 100; r = (right * 7) / 100; t = (r << 4) | l; conf_wr(mss, OPL3SAx_TREBLE, t); break; default: mss_mixer_set(mss, dev, left, right); } mss_unlock(mss); return left | (right << 8); } static int ymmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct mss_info *mss = mix_getdevinfo(m); mss_lock(mss); src = mss_set_recsrc(mss, src); mss_unlock(mss); return src; } static kobj_method_t ymmix_mixer_methods[] = { KOBJMETHOD(mixer_init, ymmix_init), KOBJMETHOD(mixer_set, ymmix_set), KOBJMETHOD(mixer_setrecsrc, ymmix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(ymmix_mixer); /* -------------------------------------------------------------------- */ /* * XXX This might be better off in the gusc driver. */ static void gusmax_setup(struct mss_info *mss, device_t dev, struct resource *alt) { static const unsigned char irq_bits[16] = { 0, 0, 0, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7 }; static const unsigned char dma_bits[8] = { 0, 1, 0, 2, 0, 3, 4, 5 }; device_t parent = device_get_parent(dev); unsigned char irqctl, dmactl; int s; s = splhigh(); port_wr(alt, 0x0f, 0x05); port_wr(alt, 0x00, 0x0c); port_wr(alt, 0x0b, 0x00); port_wr(alt, 0x0f, 0x00); irqctl = irq_bits[isa_get_irq(parent)]; /* Share the IRQ with the MIDI driver. */ irqctl |= 0x40; dmactl = dma_bits[isa_get_drq(parent)]; if (device_get_flags(parent) & DV_F_DUAL_DMA) dmactl |= dma_bits[device_get_flags(parent) & DV_F_DRQ_MASK] << 3; /* * Set the DMA and IRQ control latches. */ port_wr(alt, 0x00, 0x0c); port_wr(alt, 0x0b, dmactl | 0x80); port_wr(alt, 0x00, 0x4c); port_wr(alt, 0x0b, irqctl); port_wr(alt, 0x00, 0x0c); port_wr(alt, 0x0b, dmactl); port_wr(alt, 0x00, 0x4c); port_wr(alt, 0x0b, irqctl); port_wr(mss->conf_base, 2, 0); port_wr(alt, 0x00, 0x0c); port_wr(mss->conf_base, 2, 0); splx(s); } static int mss_init(struct mss_info *mss, device_t dev) { u_char r6, r9; struct resource *alt; int rid, tmp; mss->bd_flags |= BD_F_MCE_BIT; switch(mss->bd_id) { case MD_OPTI931: /* * The MED3931 v.1.0 allocates 3 bytes for the config * space, whereas v.2.0 allocates 4 bytes. What I know * for sure is that the upper two ports must be used, * and they should end on a boundary of 4 bytes. So I * need the following trick. */ mss->opti_offset = (rman_get_start(mss->conf_base) & ~3) + 2 - rman_get_start(mss->conf_base); BVDDB(printf("mss_init: opti_offset=%d\n", mss->opti_offset)); opti_wr(mss, 4, 0xd6); /* fifo empty, OPL3, audio enable, SB3.2 */ ad_write(mss, 10, 2); /* enable interrupts */ opti_wr(mss, 6, 2); /* MCIR6: mss enable, sb disable */ opti_wr(mss, 5, 0x28); /* MCIR5: codec in exp. mode,fifo */ break; case MD_GUSPNP: case MD_GUSMAX: gus_wr(mss, 0x4c /* _URSTI */, 0);/* Pull reset */ DELAY(1000 * 30); /* release reset and enable DAC */ gus_wr(mss, 0x4c /* _URSTI */, 3); DELAY(1000 * 30); /* end of reset */ rid = 0; alt = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (alt == NULL) { printf("XXX couldn't init GUS PnP/MAX\n"); break; } port_wr(alt, 0, 0xC); /* enable int and dma */ if (mss->bd_id == MD_GUSMAX) gusmax_setup(mss, dev, alt); bus_release_resource(dev, SYS_RES_IOPORT, rid, alt); /* * unmute left & right line. Need to go in mode3, unmute, * and back to mode 2 */ tmp = ad_read(mss, 0x0c); ad_write(mss, 0x0c, 0x6c); /* special value to enter mode 3 */ ad_write(mss, 0x19, 0); /* unmute left */ ad_write(mss, 0x1b, 0); /* unmute right */ ad_write(mss, 0x0c, tmp); /* restore old mode */ /* send codec interrupts on irq1 and only use that one */ gus_wr(mss, 0x5a, 0x4f); /* enable access to hidden regs */ tmp = gus_rd(mss, 0x5b /* IVERI */); gus_wr(mss, 0x5b, tmp | 1); BVDDB(printf("GUS: silicon rev %c\n", 'A' + ((tmp & 0xf) >> 4))); break; case MD_YM0020: conf_wr(mss, OPL3SAx_DMACONF, 0xa9); /* dma-b rec, dma-a play */ r6 = conf_rd(mss, OPL3SAx_DMACONF); r9 = conf_rd(mss, OPL3SAx_MISC); /* version */ BVDDB(printf("Yamaha: ver 0x%x DMA config 0x%x\n", r6, r9);) /* yamaha - set volume to max */ conf_wr(mss, OPL3SAx_VOLUMEL, 0); conf_wr(mss, OPL3SAx_VOLUMER, 0); conf_wr(mss, OPL3SAx_DMACONF, FULL_DUPLEX(mss)? 0xa9 : 0x8b); break; } if (FULL_DUPLEX(mss) && mss->bd_id != MD_OPTI931) ad_write(mss, 12, ad_read(mss, 12) | 0x40); /* mode 2 */ ad_enter_MCE(mss); ad_write(mss, 9, FULL_DUPLEX(mss)? 0 : 4); ad_leave_MCE(mss); ad_write(mss, 10, 2); /* int enable */ io_wr(mss, MSS_STATUS, 0); /* Clear interrupt status */ /* the following seem required on the CS4232 */ ad_unmute(mss); return 0; } /* * main irq handler for the CS423x. The OPTi931 code is * a separate one. * The correct way to operate for a device with multiple internal * interrupt sources is to loop on the status register and ack * interrupts until all interrupts are served and none are reported. At * this point the IRQ line to the ISA IRQ controller should go low * and be raised at the next interrupt. * * Since the ISA IRQ controller is sent EOI _before_ passing control * to the isr, it might happen that we serve an interrupt early, in * which case the status register at the next interrupt should just * say that there are no more interrupts... */ static void mss_intr(void *arg) { struct mss_info *mss = arg; u_char c = 0, served = 0; int i; DEB(printf("mss_intr\n")); mss_lock(mss); ad_read(mss, 11); /* fake read of status bits */ /* loop until there are interrupts, but no more than 10 times. */ for (i = 10; i > 0 && io_rd(mss, MSS_STATUS) & 1; i--) { /* get exact reason for full-duplex boards */ c = FULL_DUPLEX(mss)? ad_read(mss, 24) : 0x30; c &= ~served; if (sndbuf_runsz(mss->pch.buffer) && (c & 0x10)) { served |= 0x10; mss_unlock(mss); chn_intr(mss->pch.channel); mss_lock(mss); } if (sndbuf_runsz(mss->rch.buffer) && (c & 0x20)) { served |= 0x20; mss_unlock(mss); chn_intr(mss->rch.channel); mss_lock(mss); } /* now ack the interrupt */ if (FULL_DUPLEX(mss)) ad_write(mss, 24, ~c); /* ack selectively */ else io_wr(mss, MSS_STATUS, 0); /* Clear interrupt status */ } if (i == 10) { BVDDB(printf("mss_intr: irq, but not from mss\n")); } else if (served == 0) { BVDDB(printf("mss_intr: unexpected irq with reason %x\n", c)); /* * this should not happen... I have no idea what to do now. * maybe should do a sanity check and restart dmas ? */ io_wr(mss, MSS_STATUS, 0); /* Clear interrupt status */ } mss_unlock(mss); } /* * AD_WAIT_INIT waits if we are initializing the board and * we cannot modify its settings */ static int ad_wait_init(struct mss_info *mss, int x) { int arg = x, n = 0; /* to shut up the compiler... */ for (; x > 0; x--) if ((n = io_rd(mss, MSS_INDEX)) & MSS_IDXBUSY) DELAY(10); else return n; printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n); return n; } static int ad_read(struct mss_info *mss, int reg) { int x; ad_wait_init(mss, 201000); x = io_rd(mss, MSS_INDEX) & ~MSS_IDXMASK; io_wr(mss, MSS_INDEX, (u_char)(reg & MSS_IDXMASK) | x); x = io_rd(mss, MSS_IDATA); /* printf("ad_read %d, %x\n", reg, x); */ return x; } static void ad_write(struct mss_info *mss, int reg, u_char data) { int x; /* printf("ad_write %d, %x\n", reg, data); */ ad_wait_init(mss, 1002000); x = io_rd(mss, MSS_INDEX) & ~MSS_IDXMASK; io_wr(mss, MSS_INDEX, (u_char)(reg & MSS_IDXMASK) | x); io_wr(mss, MSS_IDATA, data); } static void ad_write_cnt(struct mss_info *mss, int reg, u_short cnt) { ad_write(mss, reg+1, cnt & 0xff); ad_write(mss, reg, cnt >> 8); /* upper base must be last */ } static void wait_for_calibration(struct mss_info *mss) { int t; /* * Wait until the auto calibration process has finished. * * 1) Wait until the chip becomes ready (reads don't return 0x80). * 2) Wait until the ACI bit of I11 gets on * 3) Wait until the ACI bit of I11 gets off */ t = ad_wait_init(mss, 1000000); if (t & MSS_IDXBUSY) printf("mss: Auto calibration timed out(1).\n"); /* * The calibration mode for chips that support it is set so that * we never see ACI go on. */ if (mss->bd_id == MD_GUSMAX || mss->bd_id == MD_GUSPNP) { for (t = 100; t > 0 && (ad_read(mss, 11) & 0x20) == 0; t--); } else { /* * XXX This should only be enabled for cards that *really* * need it. Are there any? */ for (t = 100; t > 0 && (ad_read(mss, 11) & 0x20) == 0; t--) DELAY(100); } for (t = 100; t > 0 && ad_read(mss, 11) & 0x20; t--) DELAY(100); } static void ad_unmute(struct mss_info *mss) { ad_write(mss, 6, ad_read(mss, 6) & ~I6_MUTE); ad_write(mss, 7, ad_read(mss, 7) & ~I6_MUTE); } static void ad_enter_MCE(struct mss_info *mss) { int prev; mss->bd_flags |= BD_F_MCE_BIT; ad_wait_init(mss, 203000); prev = io_rd(mss, MSS_INDEX); prev &= ~MSS_TRD; io_wr(mss, MSS_INDEX, prev | MSS_MCE); } static void ad_leave_MCE(struct mss_info *mss) { u_char prev; if ((mss->bd_flags & BD_F_MCE_BIT) == 0) { DEB(printf("--- hey, leave_MCE: MCE bit was not set!\n")); return; } ad_wait_init(mss, 1000000); mss->bd_flags &= ~BD_F_MCE_BIT; prev = io_rd(mss, MSS_INDEX); prev &= ~MSS_TRD; io_wr(mss, MSS_INDEX, prev & ~MSS_MCE); /* Clear the MCE bit */ wait_for_calibration(mss); } static int mss_speed(struct mss_chinfo *ch, int speed) { struct mss_info *mss = ch->parent; /* * In the CS4231, the low 4 bits of I8 are used to hold the * sample rate. Only a fixed number of values is allowed. This * table lists them. The speed-setting routines scans the table * looking for the closest match. This is the only supported method. * * In the CS4236, there is an alternate metod (which we do not * support yet) which provides almost arbitrary frequency setting. * In the AD1845, it looks like the sample rate can be * almost arbitrary, and written directly to a register. * In the OPTi931, there is a SB command which provides for * almost arbitrary frequency setting. * */ ad_enter_MCE(mss); if (mss->bd_id == MD_AD1845) { /* Use alternate speed select regs */ ad_write(mss, 22, (speed >> 8) & 0xff); /* Speed MSB */ ad_write(mss, 23, speed & 0xff); /* Speed LSB */ /* XXX must also do something in I27 for the ad1845 */ } else { int i, sel = 0; /* assume entry 0 does not contain -1 */ static int speeds[] = {8000, 5512, 16000, 11025, 27429, 18900, 32000, 22050, -1, 37800, -1, 44100, 48000, 33075, 9600, 6615}; for (i = 1; i < 16; i++) if (speeds[i] > 0 && abs(speed-speeds[i]) < abs(speed-speeds[sel])) sel = i; speed = speeds[sel]; ad_write(mss, 8, (ad_read(mss, 8) & 0xf0) | sel); ad_wait_init(mss, 10000); } ad_leave_MCE(mss); return speed; } /* * mss_format checks that the format is supported (or defaults to AFMT_U8) * and returns the bit setting for the 1848 register corresponding to * the desired format. * * fixed lr970724 */ static int mss_format(struct mss_chinfo *ch, u_int32_t format) { struct mss_info *mss = ch->parent; int i, arg = format & ~AFMT_STEREO; /* * The data format uses 3 bits (just 2 on the 1848). For each * bit setting, the following array returns the corresponding format. * The code scans the array looking for a suitable format. In * case it is not found, default to AFMT_U8 (not such a good * choice, but let's do it for compatibility...). */ static int fmts[] = {AFMT_U8, AFMT_MU_LAW, AFMT_S16_LE, AFMT_A_LAW, -1, AFMT_IMA_ADPCM, AFMT_U16_BE, -1}; ch->fmt = format; for (i = 0; i < 8; i++) if (arg == fmts[i]) break; arg = i << 1; if (format & AFMT_STEREO) arg |= 1; arg <<= 4; ad_enter_MCE(mss); ad_write(mss, 8, (ad_read(mss, 8) & 0x0f) | arg); ad_wait_init(mss, 10000); if (ad_read(mss, 12) & 0x40) { /* mode2? */ ad_write(mss, 28, arg); /* capture mode */ ad_wait_init(mss, 10000); } ad_leave_MCE(mss); return format; } static int mss_trigger(struct mss_chinfo *ch, int go) { struct mss_info *mss = ch->parent; u_char m; int retry, wr, cnt, ss; ss = 1; ss <<= (ch->fmt & AFMT_STEREO)? 1 : 0; ss <<= (ch->fmt & AFMT_16BIT)? 1 : 0; wr = (ch->dir == PCMDIR_PLAY)? 1 : 0; m = ad_read(mss, 9); switch (go) { case PCMTRIG_START: cnt = (ch->blksz / ss) - 1; DEB(if (m & 4) printf("OUCH! reg 9 0x%02x\n", m);); m |= wr? I9_PEN : I9_CEN; /* enable DMA */ ad_write_cnt(mss, (wr || !FULL_DUPLEX(mss))? 14 : 30, cnt); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: /* XXX check this... */ m &= ~(wr? I9_PEN : I9_CEN); /* Stop DMA */ #if 0 /* * try to disable DMA by clearing count registers. Not sure it * is needed, and it might cause false interrupts when the * DMA is re-enabled later. */ ad_write_cnt(mss, (wr || !FULL_DUPLEX(mss))? 14 : 30, 0); #endif } /* on the OPTi931 the enable bit seems hard to set... */ for (retry = 10; retry > 0; retry--) { ad_write(mss, 9, m); if (ad_read(mss, 9) == m) break; } if (retry == 0) BVDDB(printf("stop dma, failed to set bit 0x%02x 0x%02x\n", \ m, ad_read(mss, 9))); return 0; } /* * the opti931 seems to miss interrupts when working in full * duplex, so we try some heuristics to catch them. */ static void opti931_intr(void *arg) { struct mss_info *mss = (struct mss_info *)arg; u_char masked = 0, i11, mc11, c = 0; u_char reason; /* b0 = playback, b1 = capture, b2 = timer */ int loops = 10; #if 0 reason = io_rd(mss, MSS_STATUS); if (!(reason & 1)) {/* no int, maybe a shared line ? */ DEB(printf("intr: flag 0, mcir11 0x%02x\n", ad_read(mss, 11))); return; } #endif mss_lock(mss); i11 = ad_read(mss, 11); /* XXX what's for ? */ again: c = mc11 = FULL_DUPLEX(mss)? opti_rd(mss, 11) : 0xc; mc11 &= 0x0c; if (c & 0x10) { DEB(printf("Warning: CD interrupt\n");) mc11 |= 0x10; } if (c & 0x20) { DEB(printf("Warning: MPU interrupt\n");) mc11 |= 0x20; } if (mc11 & masked) BVDDB(printf("irq reset failed, mc11 0x%02x, 0x%02x\n",\ mc11, masked)); masked |= mc11; /* * the nice OPTi931 sets the IRQ line before setting the bits in * mc11. So, on some occasions I have to retry (max 10 times). */ if (mc11 == 0) { /* perhaps can return ... */ reason = io_rd(mss, MSS_STATUS); if (reason & 1) { DEB(printf("one more try...\n");) if (--loops) goto again; else BVDDB(printf("intr, but mc11 not set\n");) } if (loops == 0) BVDDB(printf("intr, nothing in mcir11 0x%02x\n", mc11)); mss_unlock(mss); return; } if (sndbuf_runsz(mss->rch.buffer) && (mc11 & 8)) { mss_unlock(mss); chn_intr(mss->rch.channel); mss_lock(mss); } if (sndbuf_runsz(mss->pch.buffer) && (mc11 & 4)) { mss_unlock(mss); chn_intr(mss->pch.channel); mss_lock(mss); } opti_wr(mss, 11, ~mc11); /* ack */ if (--loops) goto again; mss_unlock(mss); DEB(printf("xxx too many loops\n");) } /* -------------------------------------------------------------------- */ /* channel interface */ static void * msschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct mss_info *mss = devinfo; struct mss_chinfo *ch = (dir == PCMDIR_PLAY)? &mss->pch : &mss->rch; ch->parent = mss; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, mss->parent_dmat, 0, mss->bufsize) != 0) return NULL; sndbuf_dmasetup(ch->buffer, (dir == PCMDIR_PLAY)? mss->drq1 : mss->drq2); return ch; } static int msschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct mss_chinfo *ch = data; struct mss_info *mss = ch->parent; mss_lock(mss); mss_format(ch, format); mss_unlock(mss); return 0; } static int msschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct mss_chinfo *ch = data; struct mss_info *mss = ch->parent; int r; mss_lock(mss); r = mss_speed(ch, speed); mss_unlock(mss); return r; } static int msschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct mss_chinfo *ch = data; ch->blksz = blocksize; sndbuf_resize(ch->buffer, 2, ch->blksz); return ch->blksz; } static int msschan_trigger(kobj_t obj, void *data, int go) { struct mss_chinfo *ch = data; struct mss_info *mss = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); mss_lock(mss); mss_trigger(ch, go); mss_unlock(mss); return 0; } static int msschan_getptr(kobj_t obj, void *data) { struct mss_chinfo *ch = data; return sndbuf_dmaptr(ch->buffer); } static struct pcmchan_caps * msschan_getcaps(kobj_t obj, void *data) { struct mss_chinfo *ch = data; switch(ch->parent->bd_id) { case MD_OPTI931: return &opti931_caps; break; case MD_GUSPNP: case MD_GUSMAX: return &guspnp_caps; break; default: return &mss_caps; break; } } static kobj_method_t msschan_methods[] = { KOBJMETHOD(channel_init, msschan_init), KOBJMETHOD(channel_setformat, msschan_setformat), KOBJMETHOD(channel_setspeed, msschan_setspeed), KOBJMETHOD(channel_setblocksize, msschan_setblocksize), KOBJMETHOD(channel_trigger, msschan_trigger), KOBJMETHOD(channel_getptr, msschan_getptr), KOBJMETHOD(channel_getcaps, msschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(msschan); /* -------------------------------------------------------------------- */ /* * mss_probe() is the probe routine. Note, it is not necessary to * go through this for PnP devices, since they are already * indentified precisely using their PnP id. * * The base address supplied in the device refers to the old MSS * specs where the four 4 registers in io space contain configuration * information. Some boards (as an example, early MSS boards) * has such a block of registers, whereas others (generally CS42xx) * do not. In order to distinguish between the two and do not have * to supply two separate probe routines, the flags entry in isa_device * has a bit to mark this. * */ static int mss_probe(device_t dev) { u_char tmp, tmpx; int flags, irq, drq, result = ENXIO, setres = 0; struct mss_info *mss; if (isa_get_logicalid(dev)) return ENXIO; /* not yet */ mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); if (!mss) return ENXIO; mss->io_rid = 0; mss->conf_rid = -1; mss->irq_rid = 0; mss->drq1_rid = 0; mss->drq2_rid = -1; mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, 0, ~0, 8, RF_ACTIVE); if (!mss->io_base) { BVDDB(printf("mss_probe: no address given, try 0x%x\n", 0x530)); mss->io_rid = 0; /* XXX verify this */ setres = 1; bus_set_resource(dev, SYS_RES_IOPORT, mss->io_rid, 0x530, 8); mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, 0, ~0, 8, RF_ACTIVE); } if (!mss->io_base) goto no; /* got irq/dma regs? */ flags = device_get_flags(dev); irq = isa_get_irq(dev); drq = isa_get_drq(dev); if (!(device_get_flags(dev) & DV_F_TRUE_MSS)) goto mss_probe_end; /* * Check if the IO port returns valid signature. The original MS * Sound system returns 0x04 while some cards * (AudioTriX Pro for example) return 0x00 or 0x0f. */ device_set_desc(dev, "MSS"); tmpx = tmp = io_rd(mss, 3); if (tmp == 0xff) { /* Bus float */ BVDDB(printf("I/O addr inactive (%x), try pseudo_mss\n", tmp)); device_set_flags(dev, flags & ~DV_F_TRUE_MSS); goto mss_probe_end; } tmp &= 0x3f; if (!(tmp == 0x04 || tmp == 0x0f || tmp == 0x00 || tmp == 0x05)) { BVDDB(printf("No MSS signature detected on port 0x%lx (0x%x)\n", rman_get_start(mss->io_base), tmpx)); goto no; } #ifdef PC98 if (irq > 12) { #else if (irq > 11) { #endif printf("MSS: Bad IRQ %d\n", irq); goto no; } if (!(drq == 0 || drq == 1 || drq == 3)) { printf("MSS: Bad DMA %d\n", drq); goto no; } if (tmpx & 0x80) { /* 8-bit board: only drq1/3 and irq7/9 */ if (drq == 0) { printf("MSS: Can't use DMA0 with a 8 bit card/slot\n"); goto no; } if (!(irq == 7 || irq == 9)) { printf("MSS: Can't use IRQ%d with a 8 bit card/slot\n", irq); goto no; } } mss_probe_end: result = mss_detect(dev, mss); no: mss_release_resources(mss, dev); #if 0 if (setres) ISA_DELETE_RESOURCE(device_get_parent(dev), dev, SYS_RES_IOPORT, mss->io_rid); /* XXX ? */ #endif return result; } static int mss_detect(device_t dev, struct mss_info *mss) { int i; u_char tmp = 0, tmp1, tmp2; char *name, *yamaha; if (mss->bd_id != 0) { device_printf(dev, "presel bd_id 0x%04x -- %s\n", mss->bd_id, device_get_desc(dev)); return 0; } name = "AD1848"; mss->bd_id = MD_AD1848; /* AD1848 or CS4248 */ #ifndef PC98 if (opti_detect(dev, mss)) { switch (mss->bd_id) { case MD_OPTI924: name = "OPTi924"; break; case MD_OPTI930: name = "OPTi930"; break; } printf("Found OPTi device %s\n", name); if (opti_init(dev, mss) == 0) goto gotit; } #endif /* * Check that the I/O address is in use. * * bit 7 of the base I/O port is known to be 0 after the chip has * performed its power on initialization. Just assume this has * happened before the OS is starting. * * If the I/O address is unused, it typically returns 0xff. */ for (i = 0; i < 10; i++) if ((tmp = io_rd(mss, MSS_INDEX)) & MSS_IDXBUSY) DELAY(10000); else break; if (i >= 10) { /* Not an AD1848 */ BVDDB(printf("mss_detect, busy still set (0x%02x)\n", tmp)); goto no; } /* * Test if it's possible to change contents of the indirect * registers. Registers 0 and 1 are ADC volume registers. The bit * 0x10 is read only so try to avoid using it. */ ad_write(mss, 0, 0xaa); ad_write(mss, 1, 0x45);/* 0x55 with bit 0x10 clear */ tmp1 = ad_read(mss, 0); tmp2 = ad_read(mss, 1); if (tmp1 != 0xaa || tmp2 != 0x45) { BVDDB(printf("mss_detect error - IREG (%x/%x)\n", tmp1, tmp2)); goto no; } ad_write(mss, 0, 0x45); ad_write(mss, 1, 0xaa); tmp1 = ad_read(mss, 0); tmp2 = ad_read(mss, 1); if (tmp1 != 0x45 || tmp2 != 0xaa) { BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2)); goto no; } /* * The indirect register I12 has some read only bits. Lets try to * change them. */ tmp = ad_read(mss, 12); ad_write(mss, 12, (~tmp) & 0x0f); tmp1 = ad_read(mss, 12); if ((tmp & 0x0f) != (tmp1 & 0x0f)) { BVDDB(printf("mss_detect - I12 (0x%02x was 0x%02x)\n", tmp1, tmp)); goto no; } /* * NOTE! Last 4 bits of the reg I12 tell the chip revision. * 0x01=RevB * 0x0A=RevC. also CS4231/CS4231A and OPTi931 */ BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);) /* * The original AD1848/CS4248 has just 16 indirect registers. This * means that I0 and I16 should return the same value (etc.). Ensure * that the Mode2 enable bit of I12 is 0. Otherwise this test fails * with new parts. */ ad_write(mss, 12, 0); /* Mode2=disabled */ #if 0 for (i = 0; i < 16; i++) { if ((tmp1 = ad_read(mss, i)) != (tmp2 = ad_read(mss, i + 16))) { BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n", i, tmp1, tmp2)); /* * note - this seems to fail on the 4232 on I11. So we just break * rather than fail. (which makes this test pointless - cg) */ break; /* return 0; */ } } #endif /* * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit * (0x40). The bit 0x80 is always 1 in CS4248 and CS4231. * * On the OPTi931, however, I12 is readonly and only contains the * chip revision ID (as in the CS4231A). The upper bits return 0. */ ad_write(mss, 12, 0x40); /* Set mode2, clear 0x80 */ tmp1 = ad_read(mss, 12); if (tmp1 & 0x80) name = "CS4248"; /* Our best knowledge just now */ if ((tmp1 & 0xf0) == 0x00) { BVDDB(printf("this should be an OPTi931\n");) } else if ((tmp1 & 0xc0) != 0xC0) goto gotit; /* * The 4231 has bit7=1 always, and bit6 we just set to 1. * We want to check that this is really a CS4231 * Verify that setting I0 doesn't change I16. */ ad_write(mss, 16, 0); /* Set I16 to known value */ ad_write(mss, 0, 0x45); if ((tmp1 = ad_read(mss, 16)) == 0x45) goto gotit; ad_write(mss, 0, 0xaa); if ((tmp1 = ad_read(mss, 16)) == 0xaa) { /* Rotten bits? */ BVDDB(printf("mss_detect error - step H(%x)\n", tmp1)); goto no; } /* Verify that some bits of I25 are read only. */ tmp1 = ad_read(mss, 25); /* Original bits */ ad_write(mss, 25, ~tmp1); /* Invert all bits */ if ((ad_read(mss, 25) & 0xe7) == (tmp1 & 0xe7)) { int id; /* It's at least CS4231 */ name = "CS4231"; mss->bd_id = MD_CS42XX; /* * It could be an AD1845 or CS4231A as well. * CS4231 and AD1845 report the same revision info in I25 * while the CS4231A reports different. */ id = ad_read(mss, 25) & 0xe7; /* * b7-b5 = version number; * 100 : all CS4231 * 101 : CS4231A * * b2-b0 = chip id; */ switch (id) { case 0xa0: name = "CS4231A"; mss->bd_id = MD_CS42XX; break; case 0xa2: name = "CS4232"; mss->bd_id = MD_CS42XX; break; case 0xb2: /* strange: the 4231 data sheet says b4-b3 are XX * so this should be the same as 0xa2 */ name = "CS4232A"; mss->bd_id = MD_CS42XX; break; case 0x80: /* * It must be a CS4231 or AD1845. The register I23 * of CS4231 is undefined and it appears to be read * only. AD1845 uses I23 for setting sample rate. * Assume the chip is AD1845 if I23 is changeable. */ tmp = ad_read(mss, 23); ad_write(mss, 23, ~tmp); if (ad_read(mss, 23) != tmp) { /* AD1845 ? */ name = "AD1845"; mss->bd_id = MD_AD1845; } ad_write(mss, 23, tmp); /* Restore */ yamaha = ymf_test(dev, mss); if (yamaha) { mss->bd_id = MD_YM0020; name = yamaha; } break; case 0x83: /* CS4236 */ case 0x03: /* CS4236 on Intel PR440FX motherboard XXX */ name = "CS4236"; mss->bd_id = MD_CS42XX; break; default: /* Assume CS4231 */ BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);) mss->bd_id = MD_CS42XX; } } ad_write(mss, 25, tmp1); /* Restore bits */ gotit: BVDDB(printf("mss_detect() - Detected %s\n", name)); device_set_desc(dev, name); device_set_flags(dev, ((device_get_flags(dev) & ~DV_F_DEV_MASK) | ((mss->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK))); return 0; no: return ENXIO; } #ifndef PC98 static int opti_detect(device_t dev, struct mss_info *mss) { int c; static const struct opticard { int boardid; int passwdreg; int password; int base; int indir_reg; } cards[] = { { MD_OPTI930, 0, 0xe4, 0xf8f, 0xe0e }, /* 930 */ { MD_OPTI924, 3, 0xe5, 0xf8c, 0, }, /* 924 */ { 0 }, }; mss->conf_rid = 3; mss->indir_rid = 4; for (c = 0; cards[c].base; c++) { mss->optibase = cards[c].base; mss->password = cards[c].password; mss->passwdreg = cards[c].passwdreg; mss->bd_id = cards[c].boardid; if (cards[c].indir_reg) mss->indir = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->indir_rid, cards[c].indir_reg, cards[c].indir_reg+1, 1, RF_ACTIVE); mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, mss->optibase, mss->optibase+9, 9, RF_ACTIVE); if (opti_read(mss, 1) != 0xff) { return 1; } else { if (mss->indir) bus_release_resource(dev, SYS_RES_IOPORT, mss->indir_rid, mss->indir); mss->indir = NULL; if (mss->conf_base) bus_release_resource(dev, SYS_RES_IOPORT, mss->conf_rid, mss->conf_base); mss->conf_base = NULL; } } return 0; } #endif static char * ymf_test(device_t dev, struct mss_info *mss) { static int ports[] = {0x370, 0x310, 0x538}; int p, i, j, version; static char *chipset[] = { NULL, /* 0 */ "OPL3-SA2 (YMF711)", /* 1 */ "OPL3-SA3 (YMF715)", /* 2 */ "OPL3-SA3 (YMF715)", /* 3 */ "OPL3-SAx (YMF719)", /* 4 */ "OPL3-SAx (YMF719)", /* 5 */ "OPL3-SAx (YMF719)", /* 6 */ "OPL3-SAx (YMF719)", /* 7 */ }; for (p = 0; p < 3; p++) { mss->conf_rid = 1; mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, ports[p], ports[p] + 1, 2, RF_ACTIVE); if (!mss->conf_base) return 0; /* Test the index port of the config registers */ i = port_rd(mss->conf_base, 0); port_wr(mss->conf_base, 0, OPL3SAx_DMACONF); j = (port_rd(mss->conf_base, 0) == OPL3SAx_DMACONF)? 1 : 0; port_wr(mss->conf_base, 0, i); if (!j) { bus_release_resource(dev, SYS_RES_IOPORT, mss->conf_rid, mss->conf_base); #ifdef PC98 /* PC98 need this. I don't know reason why. */ bus_delete_resource(dev, SYS_RES_IOPORT, mss->conf_rid); #endif mss->conf_base = 0; continue; } version = conf_rd(mss, OPL3SAx_MISC) & 0x07; return chipset[version]; } return NULL; } static int mss_doattach(device_t dev, struct mss_info *mss) { int pdma, rdma, flags = device_get_flags(dev); char status[SND_STATUSLEN], status2[SND_STATUSLEN]; mss->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_mss softc"); mss->bufsize = pcm_getbuffersize(dev, 4096, MSS_DEFAULT_BUFSZ, 65536); if (!mss_alloc_resources(mss, dev)) goto no; mss_init(mss, dev); pdma = rman_get_start(mss->drq1); rdma = rman_get_start(mss->drq2); if (flags & DV_F_TRUE_MSS) { /* has IRQ/DMA registers, set IRQ and DMA addr */ #ifdef PC98 /* CS423[12] in PC98 can use IRQ3,5,10,12 */ static char interrupt_bits[13] = {-1, -1, -1, 0x08, -1, 0x10, -1, -1, -1, -1, 0x18, -1, 0x20}; #else static char interrupt_bits[12] = {-1, -1, -1, -1, -1, 0x28, -1, 0x08, -1, 0x10, 0x18, 0x20}; #endif static char pdma_bits[4] = {1, 2, -1, 3}; static char valid_rdma[4] = {1, 0, -1, 0}; char bits; if (!mss->irq || (bits = interrupt_bits[rman_get_start(mss->irq)]) == -1) goto no; #ifndef PC98 /* CS423[12] in PC98 don't support this. */ io_wr(mss, 0, bits | 0x40); /* config port */ if ((io_rd(mss, 3) & 0x40) == 0) device_printf(dev, "IRQ Conflict?\n"); #endif /* Write IRQ+DMA setup */ if (pdma_bits[pdma] == -1) goto no; bits |= pdma_bits[pdma]; if (pdma != rdma) { if (rdma == valid_rdma[pdma]) bits |= 4; else { printf("invalid dual dma config %d:%d\n", pdma, rdma); goto no; } } io_wr(mss, 0, bits); printf("drq/irq conf %x\n", io_rd(mss, 0)); } mixer_init(dev, (mss->bd_id == MD_YM0020)? &ymmix_mixer_class : &mssmix_mixer_class, mss); switch (mss->bd_id) { case MD_OPTI931: snd_setup_intr(dev, mss->irq, 0, opti931_intr, mss, &mss->ih); break; default: snd_setup_intr(dev, mss->irq, 0, mss_intr, mss, &mss->ih); } if (pdma == rdma) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); 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*/mss->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &mss->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (pdma != rdma) snprintf(status2, SND_STATUSLEN, ":%d", rdma); else status2[0] = '\0'; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %d%s bufsz %u", rman_get_start(mss->io_base), rman_get_start(mss->irq), pdma, status2, mss->bufsize); if (pcm_register(dev, mss, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &msschan_class, mss); pcm_addchan(dev, PCMDIR_PLAY, &msschan_class, mss); pcm_setstatus(dev, status); return 0; no: mss_release_resources(mss, dev); return ENXIO; } static int mss_detach(device_t dev) { int r; struct mss_info *mss; r = pcm_unregister(dev); if (r) return r; mss = pcm_getdevinfo(dev); mss_release_resources(mss, dev); return 0; } static int mss_attach(device_t dev) { struct mss_info *mss; int flags = device_get_flags(dev); mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); if (!mss) return ENXIO; mss->io_rid = 0; mss->conf_rid = -1; mss->irq_rid = 0; mss->drq1_rid = 0; mss->drq2_rid = -1; if (flags & DV_F_DUAL_DMA) { bus_set_resource(dev, SYS_RES_DRQ, 1, flags & DV_F_DRQ_MASK, 1); mss->drq2_rid = 1; } mss->bd_id = (device_get_flags(dev) & DV_F_DEV_MASK) >> DV_F_DEV_SHIFT; if (mss->bd_id == MD_YM0020) ymf_test(dev, mss); return mss_doattach(dev, mss); } /* * mss_resume() is the code to allow a laptop to resume using the sound * card. * * This routine re-sets the state of the board to the state before going * to sleep. According to the yamaha docs this is the right thing to do, * but getting DMA restarted appears to be a bit of a trick, so the device * has to be closed and re-opened to be re-used, but there is no skipping * problem, and volume, bass/treble and most other things are restored * properly. * */ static int mss_resume(device_t dev) { /* * Restore the state taken below. */ struct mss_info *mss; int i; mss = pcm_getdevinfo(dev); if(mss->bd_id == MD_YM0020 || mss->bd_id == MD_CS423X) { /* This works on a Toshiba Libretto 100CT. */ for (i = 0; i < MSS_INDEXED_REGS; i++) ad_write(mss, i, mss->mss_indexed_regs[i]); for (i = 0; i < OPL_INDEXED_REGS; i++) conf_wr(mss, i, mss->opl_indexed_regs[i]); mss_intr(mss); } if (mss->bd_id == MD_CS423X) { /* Needed on IBM Thinkpad 600E */ mss_lock(mss); mss_format(&mss->pch, mss->pch.channel->format); mss_speed(&mss->pch, mss->pch.channel->speed); mss_unlock(mss); } return 0; } /* * mss_suspend() is the code that gets called right before a laptop * suspends. * * This code saves the state of the sound card right before shutdown * so it can be restored above. * */ static int mss_suspend(device_t dev) { int i; struct mss_info *mss; mss = pcm_getdevinfo(dev); if(mss->bd_id == MD_YM0020 || mss->bd_id == MD_CS423X) { /* this stops playback. */ conf_wr(mss, 0x12, 0x0c); for(i = 0; i < MSS_INDEXED_REGS; i++) mss->mss_indexed_regs[i] = ad_read(mss, i); for(i = 0; i < OPL_INDEXED_REGS; i++) mss->opl_indexed_regs[i] = conf_rd(mss, i); mss->opl_indexed_regs[0x12] = 0x0; } return 0; } static device_method_t mss_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mss_probe), DEVMETHOD(device_attach, mss_attach), DEVMETHOD(device_detach, mss_detach), DEVMETHOD(device_suspend, mss_suspend), DEVMETHOD(device_resume, mss_resume), { 0, 0 } }; static driver_t mss_driver = { "pcm", mss_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_mss, isa, mss_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_mss, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_mss, 1); static int azt2320_mss_mode(struct mss_info *mss, device_t dev) { struct resource *sbport; int i, ret, rid; rid = 0; ret = -1; sbport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (sbport) { for (i = 0; i < 1000; i++) { if ((port_rd(sbport, SBDSP_STATUS) & 0x80)) DELAY((i > 100) ? 1000 : 10); else { port_wr(sbport, SBDSP_CMD, 0x09); break; } } for (i = 0; i < 1000; i++) { if ((port_rd(sbport, SBDSP_STATUS) & 0x80)) DELAY((i > 100) ? 1000 : 10); else { port_wr(sbport, SBDSP_CMD, 0x00); ret = 0; break; } } DELAY(1000); bus_release_resource(dev, SYS_RES_IOPORT, rid, sbport); } return ret; } static struct isa_pnp_id pnpmss_ids[] = { {0x0000630e, "CS423x"}, /* CSC0000 */ {0x0001630e, "CS423x-PCI"}, /* CSC0100 */ {0x01000000, "CMI8330"}, /* @@@0001 */ {0x2100a865, "Yamaha OPL-SAx"}, /* YMH0021 */ {0x1110d315, "ENSONIQ SoundscapeVIVO"}, /* ENS1011 */ {0x1093143e, "OPTi931"}, /* OPT9310 */ {0x5092143e, "OPTi925"}, /* OPT9250 XXX guess */ {0x0000143e, "OPTi924"}, /* OPT0924 */ {0x1022b839, "Neomagic 256AV (non-ac97)"}, /* NMX2210 */ {0x01005407, "Aztech 2320"}, /* AZT0001 */ #if 0 {0x0000561e, "GusPnP"}, /* GRV0000 */ #endif {0}, }; static int pnpmss_probe(device_t dev) { u_int32_t lid, vid; lid = isa_get_logicalid(dev); vid = isa_get_vendorid(dev); if (lid == 0x01000000 && vid != 0x0100a90d) /* CMI0001 */ return ENXIO; return ISA_PNP_PROBE(device_get_parent(dev), dev, pnpmss_ids); } static int pnpmss_attach(device_t dev) { struct mss_info *mss; mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); if (!mss) return ENXIO; mss->io_rid = 0; mss->conf_rid = -1; mss->irq_rid = 0; mss->drq1_rid = 0; mss->drq2_rid = 1; mss->bd_id = MD_CS42XX; switch (isa_get_logicalid(dev)) { case 0x0000630e: /* CSC0000 */ case 0x0001630e: /* CSC0100 */ mss->bd_flags |= BD_F_MSS_OFFSET; mss->bd_id = MD_CS423X; break; case 0x2100a865: /* YHM0021 */ mss->io_rid = 1; mss->conf_rid = 4; mss->bd_id = MD_YM0020; break; case 0x1110d315: /* ENS1011 */ mss->io_rid = 1; mss->bd_id = MD_VIVO; break; case 0x1093143e: /* OPT9310 */ mss->bd_flags |= BD_F_MSS_OFFSET; mss->conf_rid = 3; mss->bd_id = MD_OPTI931; break; case 0x5092143e: /* OPT9250 XXX guess */ mss->io_rid = 1; mss->conf_rid = 3; mss->bd_id = MD_OPTI925; break; case 0x0000143e: /* OPT0924 */ mss->password = 0xe5; mss->passwdreg = 3; mss->optibase = 0xf0c; mss->io_rid = 2; mss->conf_rid = 3; mss->bd_id = MD_OPTI924; mss->bd_flags |= BD_F_924PNP; if(opti_init(dev, mss) != 0) { free(mss, M_DEVBUF); return ENXIO; } break; case 0x1022b839: /* NMX2210 */ mss->io_rid = 1; break; case 0x01005407: /* AZT0001 */ /* put into MSS mode first (snatched from NetBSD) */ if (azt2320_mss_mode(mss, dev) == -1) { free(mss, M_DEVBUF); return ENXIO; } mss->bd_flags |= BD_F_MSS_OFFSET; mss->io_rid = 2; break; #if 0 case 0x0000561e: /* GRV0000 */ mss->bd_flags |= BD_F_MSS_OFFSET; mss->io_rid = 2; mss->conf_rid = 1; mss->drq1_rid = 1; mss->drq2_rid = 0; mss->bd_id = MD_GUSPNP; break; #endif case 0x01000000: /* @@@0001 */ mss->drq2_rid = -1; break; /* Unknown MSS default. We could let the CSC0000 stuff match too */ default: mss->bd_flags |= BD_F_MSS_OFFSET; break; } return mss_doattach(dev, mss); } static int opti_init(device_t dev, struct mss_info *mss) { int flags = device_get_flags(dev); int basebits = 0; if (!mss->conf_base) { bus_set_resource(dev, SYS_RES_IOPORT, mss->conf_rid, mss->optibase, 0x9); mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, mss->optibase, mss->optibase+0x9, 0x9, RF_ACTIVE); } if (!mss->conf_base) return ENXIO; if (!mss->io_base) mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, 0, ~0, 8, RF_ACTIVE); if (!mss->io_base) /* No hint specified, use 0x530 */ mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, 0x530, 0x537, 8, RF_ACTIVE); if (!mss->io_base) return ENXIO; switch (rman_get_start(mss->io_base)) { case 0x530: basebits = 0x0; break; case 0xe80: basebits = 0x10; break; case 0xf40: basebits = 0x20; break; case 0x604: basebits = 0x30; break; default: printf("opti_init: invalid MSS base address!\n"); return ENXIO; } switch (mss->bd_id) { case MD_OPTI924: opti_write(mss, 1, 0x80 | basebits); /* MSS mode */ opti_write(mss, 2, 0x00); /* Disable CD */ opti_write(mss, 3, 0xf0); /* Disable SB IRQ */ opti_write(mss, 4, 0xf0); opti_write(mss, 5, 0x00); opti_write(mss, 6, 0x02); /* MPU stuff */ break; case MD_OPTI930: opti_write(mss, 1, 0x00 | basebits); opti_write(mss, 3, 0x00); /* Disable SB IRQ/DMA */ opti_write(mss, 4, 0x52); /* Empty FIFO */ opti_write(mss, 5, 0x3c); /* Mode 2 */ opti_write(mss, 6, 0x02); /* Enable MSS */ break; } if (mss->bd_flags & BD_F_924PNP) { u_int32_t irq = isa_get_irq(dev); u_int32_t drq = isa_get_drq(dev); bus_set_resource(dev, SYS_RES_IRQ, 0, irq, 1); bus_set_resource(dev, SYS_RES_DRQ, mss->drq1_rid, drq, 1); if (flags & DV_F_DUAL_DMA) { bus_set_resource(dev, SYS_RES_DRQ, 1, flags & DV_F_DRQ_MASK, 1); mss->drq2_rid = 1; } } /* OPTixxx has I/DRQ registers */ device_set_flags(dev, device_get_flags(dev) | DV_F_TRUE_MSS); return 0; } static void opti_write(struct mss_info *mss, u_char reg, u_char val) { port_wr(mss->conf_base, mss->passwdreg, mss->password); switch(mss->bd_id) { case MD_OPTI924: if (reg > 7) { /* Indirect register */ port_wr(mss->conf_base, mss->passwdreg, reg); port_wr(mss->conf_base, mss->passwdreg, mss->password); port_wr(mss->conf_base, 9, val); return; } port_wr(mss->conf_base, reg, val); break; case MD_OPTI930: port_wr(mss->indir, 0, reg); port_wr(mss->conf_base, mss->passwdreg, mss->password); port_wr(mss->indir, 1, val); break; } } #ifndef PC98 u_char opti_read(struct mss_info *mss, u_char reg) { port_wr(mss->conf_base, mss->passwdreg, mss->password); switch(mss->bd_id) { case MD_OPTI924: if (reg > 7) { /* Indirect register */ port_wr(mss->conf_base, mss->passwdreg, reg); port_wr(mss->conf_base, mss->passwdreg, mss->password); return(port_rd(mss->conf_base, 9)); } return(port_rd(mss->conf_base, reg)); break; case MD_OPTI930: port_wr(mss->indir, 0, reg); port_wr(mss->conf_base, mss->passwdreg, mss->password); return port_rd(mss->indir, 1); break; } return -1; } #endif static device_method_t pnpmss_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pnpmss_probe), DEVMETHOD(device_attach, pnpmss_attach), DEVMETHOD(device_detach, mss_detach), DEVMETHOD(device_suspend, mss_suspend), DEVMETHOD(device_resume, mss_resume), { 0, 0 } }; static driver_t pnpmss_driver = { "pcm", pnpmss_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_pnpmss, isa, pnpmss_driver, pcm_devclass, 0, 0); DRIVER_MODULE(snd_pnpmss, acpi, pnpmss_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_pnpmss, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_pnpmss, 1); static int guspcm_probe(device_t dev) { struct sndcard_func *func; func = device_get_ivars(dev); if (func == NULL || func->func != SCF_PCM) return ENXIO; device_set_desc(dev, "GUS CS4231"); return 0; } static int guspcm_attach(device_t dev) { device_t parent = device_get_parent(dev); struct mss_info *mss; int base, flags; unsigned char ctl; mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); if (mss == NULL) return ENOMEM; mss->bd_flags = BD_F_MSS_OFFSET; mss->io_rid = 2; mss->conf_rid = 1; mss->irq_rid = 0; mss->drq1_rid = 1; mss->drq2_rid = -1; if (isa_get_logicalid(parent) == 0) mss->bd_id = MD_GUSMAX; else { mss->bd_id = MD_GUSPNP; mss->drq2_rid = 0; goto skip_setup; } flags = device_get_flags(parent); if (flags & DV_F_DUAL_DMA) mss->drq2_rid = 0; mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, 0, ~0, 8, RF_ACTIVE); if (mss->conf_base == NULL) { mss_release_resources(mss, dev); return ENXIO; } base = isa_get_port(parent); ctl = 0x40; /* CS4231 enable */ if (isa_get_drq(dev) > 3) ctl |= 0x10; /* 16-bit dma channel 1 */ if ((flags & DV_F_DUAL_DMA) != 0 && (flags & DV_F_DRQ_MASK) > 3) ctl |= 0x20; /* 16-bit dma channel 2 */ ctl |= (base >> 4) & 0x0f; /* 2X0 -> 3XC */ port_wr(mss->conf_base, 6, ctl); skip_setup: return mss_doattach(dev, mss); } static device_method_t guspcm_methods[] = { DEVMETHOD(device_probe, guspcm_probe), DEVMETHOD(device_attach, guspcm_attach), DEVMETHOD(device_detach, mss_detach), { 0, 0 } }; static driver_t guspcm_driver = { "pcm", guspcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_guspcm, gusc, guspcm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_guspcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_guspcm, 1); Index: head/sys/dev/sound/isa/sb16.c =================================================================== --- head/sys/dev/sound/isa/sb16.c (revision 170520) +++ head/sys/dev/sound/isa/sb16.c (revision 170521) @@ -1,914 +1,914 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright * conditions. * 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. */ #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define SB16_BUFFSIZE 4096 #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) static u_int32_t sb16_fmt8[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static struct pcmchan_caps sb16_caps8 = {5000, 45000, sb16_fmt8, 0}; static u_int32_t sb16_fmt16[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps sb16_caps16 = {5000, 45000, sb16_fmt16, 0}; static u_int32_t sb16x_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps sb16x_caps = {5000, 49000, sb16x_fmt, 0}; struct sb_info; struct sb_chinfo { struct sb_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, run, dch; u_int32_t fmt, spd, blksz; }; struct sb_info { struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; void *ih; bus_dma_tag_t parent_dmat; unsigned int bufsize; int bd_id; u_long bd_flags; /* board-specific flags */ int prio, prio16; struct sb_chinfo pch, rch; device_t parent_dev; }; #if 0 static void sb_lock(struct sb_info *sb); static void sb_unlock(struct sb_info *sb); static int sb_rd(struct sb_info *sb, int reg); static void sb_wr(struct sb_info *sb, int reg, u_int8_t val); static int sb_cmd(struct sb_info *sb, u_char val); /* static int sb_cmd1(struct sb_info *sb, u_char cmd, int val); */ static int sb_cmd2(struct sb_info *sb, u_char cmd, int val); static u_int sb_get_byte(struct sb_info *sb); static void sb_setmixer(struct sb_info *sb, u_int port, u_int value); static int sb_getmixer(struct sb_info *sb, u_int port); static int sb_reset_dsp(struct sb_info *sb); static void sb_intr(void *arg); #endif /* * Common code for the midi and pcm functions * * sb_cmd write a single byte to the CMD port. * sb_cmd1 write a CMD + 1 byte arg * sb_cmd2 write a CMD + 2 byte arg * sb_get_byte returns a single byte from the DSP data port */ static void sb_lock(struct sb_info *sb) { sbc_lock(device_get_softc(sb->parent_dev)); } static void sb_lockassert(struct sb_info *sb) { sbc_lockassert(device_get_softc(sb->parent_dev)); } static void sb_unlock(struct sb_info *sb) { sbc_unlock(device_get_softc(sb->parent_dev)); } static int port_rd(struct resource *port, int off) { return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); } static void port_wr(struct resource *port, int off, u_int8_t data) { bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); } static int sb_rd(struct sb_info *sb, int reg) { return port_rd(sb->io_base, reg); } static void sb_wr(struct sb_info *sb, int reg, u_int8_t val) { port_wr(sb->io_base, reg, val); } static int sb_dspwr(struct sb_info *sb, u_char val) { int i; for (i = 0; i < 1000; i++) { if ((sb_rd(sb, SBDSP_STATUS) & 0x80)) DELAY((i > 100)? 1000 : 10); else { sb_wr(sb, SBDSP_CMD, val); return 1; } } #if __FreeBSD_version > 500000 if (curthread->td_intr_nesting_level == 0) printf("sb_dspwr(0x%02x) timed out.\n", val); #endif return 0; } static int sb_cmd(struct sb_info *sb, u_char val) { #if 0 printf("sb_cmd: %x\n", val); #endif return sb_dspwr(sb, val); } /* static int sb_cmd1(struct sb_info *sb, u_char cmd, int val) { #if 0 printf("sb_cmd1: %x, %x\n", cmd, val); #endif if (sb_dspwr(sb, cmd)) { return sb_dspwr(sb, val & 0xff); } else return 0; } */ static int sb_cmd2(struct sb_info *sb, u_char cmd, int val) { int r; #if 0 printf("sb_cmd2: %x, %x\n", cmd, val); #endif sb_lockassert(sb); r = 0; if (sb_dspwr(sb, cmd)) { if (sb_dspwr(sb, val & 0xff)) { if (sb_dspwr(sb, (val >> 8) & 0xff)) { r = 1; } } } return r; } /* * in the SB, there is a set of indirect "mixer" registers with * address at offset 4, data at offset 5 */ static void sb_setmixer(struct sb_info *sb, u_int port, u_int value) { sb_lock(sb); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); sb_wr(sb, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); sb_unlock(sb); } static int sb_getmixer(struct sb_info *sb, u_int port) { int val; sb_lockassert(sb); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = sb_rd(sb, SB_MIX_DATA); DELAY(10); return val; } static u_int sb_get_byte(struct sb_info *sb) { int i; for (i = 1000; i > 0; i--) { if (sb_rd(sb, DSP_DATA_AVAIL) & 0x80) return sb_rd(sb, DSP_READ); else DELAY(20); } return 0xffff; } static int sb_reset_dsp(struct sb_info *sb) { u_char b; sb_lockassert(sb); sb_wr(sb, SBDSP_RST, 3); DELAY(100); sb_wr(sb, SBDSP_RST, 0); b = sb_get_byte(sb); if (b != 0xAA) { DEB(printf("sb_reset_dsp 0x%lx failed\n", rman_get_start(sb->io_base))); return ENXIO; /* Sorry */ } return 0; } /************************************************************/ struct sb16_mixent { int reg; int bits; int ofs; int stereo; }; static const struct sb16_mixent sb16_mixtab[32] = { [SOUND_MIXER_VOLUME] = { 0x30, 5, 3, 1 }, [SOUND_MIXER_PCM] = { 0x32, 5, 3, 1 }, [SOUND_MIXER_SYNTH] = { 0x34, 5, 3, 1 }, [SOUND_MIXER_CD] = { 0x36, 5, 3, 1 }, [SOUND_MIXER_LINE] = { 0x38, 5, 3, 1 }, [SOUND_MIXER_MIC] = { 0x3a, 5, 3, 0 }, [SOUND_MIXER_SPEAKER] = { 0x3b, 5, 3, 0 }, [SOUND_MIXER_IGAIN] = { 0x3f, 2, 6, 1 }, [SOUND_MIXER_OGAIN] = { 0x41, 2, 6, 1 }, [SOUND_MIXER_TREBLE] = { 0x44, 4, 4, 1 }, [SOUND_MIXER_BASS] = { 0x46, 4, 4, 1 }, [SOUND_MIXER_LINE1] = { 0x52, 5, 3, 1 } }; static int sb16mix_init(struct snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | SOUND_MASK_LINE1 | SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE); mix_setrecdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_CD); sb_setmixer(sb, 0x3c, 0x1f); /* make all output active */ sb_setmixer(sb, 0x3d, 0); /* make all inputs-l off */ sb_setmixer(sb, 0x3e, 0); /* make all inputs-r off */ return 0; } static int rel2abs_volume(int x, int max) { int temp; temp = ((x * max) + 50) / 100; if (temp > max) temp = max; else if (temp < 0) temp = 0; return (temp); } static int sb16mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); const struct sb16_mixent *e; int max; e = &sb16_mixtab[dev]; max = (1 << e->bits) - 1; left = rel2abs_volume(left, max); right = rel2abs_volume(right, max); sb_setmixer(sb, e->reg, left << e->ofs); if (e->stereo) sb_setmixer(sb, e->reg + 1, right << e->ofs); else right = left; left = (left * 100) / max; right = (right * 100) / max; return left | (right << 8); } static int sb16mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); u_char recdev_l, recdev_r; recdev_l = 0; recdev_r = 0; if (src & SOUND_MASK_MIC) { recdev_l |= 0x01; /* mono mic */ recdev_r |= 0x01; } if (src & SOUND_MASK_CD) { recdev_l |= 0x04; /* l cd */ recdev_r |= 0x02; /* r cd */ } if (src & SOUND_MASK_LINE) { recdev_l |= 0x10; /* l line */ recdev_r |= 0x08; /* r line */ } if (src & SOUND_MASK_SYNTH) { recdev_l |= 0x40; /* l midi */ recdev_r |= 0x20; /* r midi */ } sb_setmixer(sb, SB16_IMASK_L, recdev_l); sb_setmixer(sb, SB16_IMASK_R, recdev_r); /* Switch on/off FM tuner source */ if (src & SOUND_MASK_LINE1) sb_setmixer(sb, 0x4a, 0x0c); else sb_setmixer(sb, 0x4a, 0x00); /* * since the same volume controls apply to the input and * output sections, the best approach to have a consistent * behaviour among cards would be to disable the output path * on devices which are used to record. * However, since users like to have feedback, we only disable * the mic -- permanently. */ sb_setmixer(sb, SB16_OMASK, 0x1f & ~1); return src; } static kobj_method_t sb16mix_mixer_methods[] = { KOBJMETHOD(mixer_init, sb16mix_init), KOBJMETHOD(mixer_set, sb16mix_set), KOBJMETHOD(mixer_setrecsrc, sb16mix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(sb16mix_mixer); /************************************************************/ static void sb16_release_resources(struct sb_info *sb, device_t dev) { if (sb->irq) { if (sb->ih) bus_teardown_intr(dev, sb->irq, sb->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); sb->irq = 0; } if (sb->drq2) { if (sb->drq2 != sb->drq1) { isa_dma_release(rman_get_start(sb->drq2)); bus_release_resource(dev, SYS_RES_DRQ, 1, sb->drq2); } sb->drq2 = 0; } if (sb->drq1) { isa_dma_release(rman_get_start(sb->drq1)); bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq1); sb->drq1 = 0; } if (sb->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); sb->io_base = 0; } if (sb->parent_dmat) { bus_dma_tag_destroy(sb->parent_dmat); sb->parent_dmat = 0; } free(sb, M_DEVBUF); } static int sb16_alloc_resources(struct sb_info *sb, device_t dev) { int rid; rid = 0; if (!sb->io_base) sb->io_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; if (!sb->irq) sb->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); rid = 0; if (!sb->drq1) sb->drq1 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); rid = 1; if (!sb->drq2) sb->drq2 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); if (sb->io_base && sb->drq1 && sb->irq) { isa_dma_acquire(rman_get_start(sb->drq1)); isa_dmainit(rman_get_start(sb->drq1), sb->bufsize); if (sb->drq2) { isa_dma_acquire(rman_get_start(sb->drq2)); isa_dmainit(rman_get_start(sb->drq2), sb->bufsize); } else { sb->drq2 = sb->drq1; pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); } return 0; } else return ENXIO; } /* sbc does locking for us */ static void sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; int reason, c; /* * The Vibra16X has separate flags for 8 and 16 bit transfers, but * I have no idea how to tell capture from playback interrupts... */ reason = 0; sb_lock(sb); c = sb_getmixer(sb, IRQ_STAT); if (c & 1) sb_rd(sb, DSP_DATA_AVAIL); /* 8-bit int ack */ if (c & 2) sb_rd(sb, DSP_DATA_AVL16); /* 16-bit int ack */ sb_unlock(sb); /* * this tells us if the source is 8-bit or 16-bit dma. We * have to check the io channel to map it to read or write... */ if (sb->bd_flags & BD_F_SB16X) { if (c & 1) { /* 8-bit format */ if (sb->pch.fmt & AFMT_8BIT) reason |= 1; if (sb->rch.fmt & AFMT_8BIT) reason |= 2; } if (c & 2) { /* 16-bit format */ if (sb->pch.fmt & AFMT_16BIT) reason |= 1; if (sb->rch.fmt & AFMT_16BIT) reason |= 2; } } else { if (c & 1) { /* 8-bit dma */ if (sb->pch.dch == 1) reason |= 1; if (sb->rch.dch == 1) reason |= 2; } if (c & 2) { /* 16-bit dma */ if (sb->pch.dch == 2) reason |= 1; if (sb->rch.dch == 2) reason |= 2; } } #if 0 printf("sb_intr: reason=%d c=0x%x\n", reason, c); #endif if ((reason & 1) && (sb->pch.run)) chn_intr(sb->pch.channel); if ((reason & 2) && (sb->rch.run)) chn_intr(sb->rch.channel); } static int sb_setup(struct sb_info *sb) { struct sb_chinfo *ch; u_int8_t v; int l, pprio; sb_lock(sb); if (sb->bd_flags & BD_F_DMARUN) sndbuf_dma(sb->pch.buffer, PCMTRIG_STOP); if (sb->bd_flags & BD_F_DMARUN2) sndbuf_dma(sb->rch.buffer, PCMTRIG_STOP); sb->bd_flags &= ~(BD_F_DMARUN | BD_F_DMARUN2); sb_reset_dsp(sb); if (sb->bd_flags & BD_F_SB16X) { /* full-duplex doesn't work! */ pprio = sb->pch.run? 1 : 0; sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq1 : sb->drq2); sb->pch.dch = pprio? 1 : 0; sndbuf_dmasetup(sb->rch.buffer, pprio? sb->drq2 : sb->drq1); sb->rch.dch = pprio? 2 : 1; } else { if (sb->pch.run && sb->rch.run) { pprio = (sb->rch.fmt & AFMT_16BIT)? 0 : 1; sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq2 : sb->drq1); sb->pch.dch = pprio? 2 : 1; sndbuf_dmasetup(sb->rch.buffer, pprio? sb->drq1 : sb->drq2); sb->rch.dch = pprio? 1 : 2; } else { if (sb->pch.run) { sndbuf_dmasetup(sb->pch.buffer, (sb->pch.fmt & AFMT_16BIT)? sb->drq2 : sb->drq1); sb->pch.dch = (sb->pch.fmt & AFMT_16BIT)? 2 : 1; sndbuf_dmasetup(sb->rch.buffer, (sb->pch.fmt & AFMT_16BIT)? sb->drq1 : sb->drq2); sb->rch.dch = (sb->pch.fmt & AFMT_16BIT)? 1 : 2; } else if (sb->rch.run) { sndbuf_dmasetup(sb->pch.buffer, (sb->rch.fmt & AFMT_16BIT)? sb->drq1 : sb->drq2); sb->pch.dch = (sb->rch.fmt & AFMT_16BIT)? 1 : 2; sndbuf_dmasetup(sb->rch.buffer, (sb->rch.fmt & AFMT_16BIT)? sb->drq2 : sb->drq1); sb->rch.dch = (sb->rch.fmt & AFMT_16BIT)? 2 : 1; } } } sndbuf_dmasetdir(sb->pch.buffer, PCMDIR_PLAY); sndbuf_dmasetdir(sb->rch.buffer, PCMDIR_REC); /* printf("setup: [pch = %d, pfmt = %d, pgo = %d] [rch = %d, rfmt = %d, rgo = %d]\n", sb->pch.dch, sb->pch.fmt, sb->pch.run, sb->rch.dch, sb->rch.fmt, sb->rch.run); */ ch = &sb->pch; if (ch->run) { l = ch->blksz; if (ch->fmt & AFMT_16BIT) l >>= 1; l--; /* play speed */ RANGE(ch->spd, 5000, 45000); sb_cmd(sb, DSP_CMD_OUT16); sb_cmd(sb, ch->spd >> 8); sb_cmd(sb, ch->spd & 0xff); /* play format, length */ v = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_F16_DAC; v |= (ch->fmt & AFMT_16BIT)? DSP_DMA16 : DSP_DMA8; sb_cmd(sb, v); v = (ch->fmt & AFMT_STEREO)? DSP_F16_STEREO : 0; v |= (ch->fmt & AFMT_SIGNED)? DSP_F16_SIGNED : 0; sb_cmd2(sb, v, l); sndbuf_dma(ch->buffer, PCMTRIG_START); sb->bd_flags |= BD_F_DMARUN; } ch = &sb->rch; if (ch->run) { l = ch->blksz; if (ch->fmt & AFMT_16BIT) l >>= 1; l--; /* record speed */ RANGE(ch->spd, 5000, 45000); sb_cmd(sb, DSP_CMD_IN16); sb_cmd(sb, ch->spd >> 8); sb_cmd(sb, ch->spd & 0xff); /* record format, length */ v = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_F16_ADC; v |= (ch->fmt & AFMT_16BIT)? DSP_DMA16 : DSP_DMA8; sb_cmd(sb, v); v = (ch->fmt & AFMT_STEREO)? DSP_F16_STEREO : 0; v |= (ch->fmt & AFMT_SIGNED)? DSP_F16_SIGNED : 0; sb_cmd2(sb, v, l); sndbuf_dma(ch->buffer, PCMTRIG_START); sb->bd_flags |= BD_F_DMARUN2; } sb_unlock(sb); return 0; } /* channel interface */ static void * sb16chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sb_info *sb = devinfo; struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; ch->parent = sb; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; return ch; } static int sb16chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; ch->fmt = format; sb->prio = ch->dir; sb->prio16 = (ch->fmt & AFMT_16BIT)? 1 : 0; return 0; } static int sb16chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sb_chinfo *ch = data; ch->spd = speed; return speed; } static int sb16chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sb_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int sb16chan_trigger(kobj_t obj, void *data, int go) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) ch->run = 1; else ch->run = 0; sb_setup(sb); return 0; } static int sb16chan_getptr(kobj_t obj, void *data) { struct sb_chinfo *ch = data; return sndbuf_dmaptr(ch->buffer); } static struct pcmchan_caps * sb16chan_getcaps(kobj_t obj, void *data) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; if ((sb->prio == 0) || (sb->prio == ch->dir)) return &sb16x_caps; else return sb->prio16? &sb16_caps8 : &sb16_caps16; } static int sb16chan_resetdone(kobj_t obj, void *data) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; sb->prio = 0; return 0; } static kobj_method_t sb16chan_methods[] = { KOBJMETHOD(channel_init, sb16chan_init), KOBJMETHOD(channel_resetdone, sb16chan_resetdone), KOBJMETHOD(channel_setformat, sb16chan_setformat), KOBJMETHOD(channel_setspeed, sb16chan_setspeed), KOBJMETHOD(channel_setblocksize, sb16chan_setblocksize), KOBJMETHOD(channel_trigger, sb16chan_trigger), KOBJMETHOD(channel_getptr, sb16chan_getptr), KOBJMETHOD(channel_getcaps, sb16chan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(sb16chan); /************************************************************/ static int sb16_probe(device_t dev) { char buf[64]; uintptr_t func, ver, r, f; /* The parent device has already been probed. */ r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); if (func != SCF_PCM) return (ENXIO); r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); f = (ver & 0xffff0000) >> 16; ver &= 0x0000ffff; if (f & BD_F_SB16) { snprintf(buf, sizeof buf, "SB16 DSP %d.%02d%s", (int) ver >> 8, (int) ver & 0xff, (f & BD_F_SB16X)? " (ViBRA16X)" : ""); device_set_desc_copy(dev, buf); return 0; } else return (ENXIO); } static int sb16_attach(device_t dev) { struct sb_info *sb; uintptr_t ver; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); if (!sb) return ENXIO; sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(sb->parent_dev, dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; sb->bd_flags = (ver & 0xffff0000) >> 16; sb->bufsize = pcm_getbuffersize(dev, 4096, SB16_BUFFSIZE, 65536); if (sb16_alloc_resources(sb, dev)) goto no; sb_lock(sb); if (sb_reset_dsp(sb)) { sb_unlock(sb); goto no; } sb_unlock(sb); if (mixer_init(dev, &sb16mix_mixer_class, sb)) goto no; if (snd_setup_intr(dev, sb->irq, 0, sb_intr, sb, &sb->ih)) goto no; if (sb->bd_flags & BD_F_SB16X) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); sb->prio = 0; 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*/sb->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sb->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (!(pcm_getflags(dev) & SD_F_SIMPLEX)) snprintf(status2, SND_STATUSLEN, ":%ld", rman_get_start(sb->drq2)); else status2[0] = '\0'; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u %s", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1), status2, sb->bufsize, PCM_KLDSTRING(snd_sb16)); if (pcm_register(dev, sb, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &sb16chan_class, sb); pcm_addchan(dev, PCMDIR_PLAY, &sb16chan_class, sb); pcm_setstatus(dev, status); return 0; no: sb16_release_resources(sb, dev); return ENXIO; } static int sb16_detach(device_t dev) { int r; struct sb_info *sb; r = pcm_unregister(dev); if (r) return r; sb = pcm_getdevinfo(dev); sb16_release_resources(sb, dev); return 0; } static device_method_t sb16_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sb16_probe), DEVMETHOD(device_attach, sb16_attach), DEVMETHOD(device_detach, sb16_detach), { 0, 0 } }; static driver_t sb16_driver = { "pcm", sb16_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_sb16, sbc, sb16_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_sb16, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_sb16, snd_sbc, 1, 1, 1); MODULE_VERSION(snd_sb16, 1); Index: head/sys/dev/sound/isa/sb8.c =================================================================== --- head/sys/dev/sound/isa/sb8.c (revision 170520) +++ head/sys/dev/sound/isa/sb8.c (revision 170521) @@ -1,806 +1,806 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright * conditions. * 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. */ #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define SB_DEFAULT_BUFSZ 4096 static u_int32_t sb_fmt[] = { AFMT_U8, 0 }; static struct pcmchan_caps sb200_playcaps = {4000, 23000, sb_fmt, 0}; static struct pcmchan_caps sb200_reccaps = {4000, 13000, sb_fmt, 0}; static struct pcmchan_caps sb201_playcaps = {4000, 44100, sb_fmt, 0}; static struct pcmchan_caps sb201_reccaps = {4000, 15000, sb_fmt, 0}; static u_int32_t sbpro_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static struct pcmchan_caps sbpro_playcaps = {4000, 44100, sbpro_fmt, 0}; static struct pcmchan_caps sbpro_reccaps = {4000, 44100, sbpro_fmt, 0}; struct sb_info; struct sb_chinfo { struct sb_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; u_int32_t fmt, spd, blksz; }; struct sb_info { device_t parent_dev; struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq; void *ih; bus_dma_tag_t parent_dmat; unsigned int bufsize; int bd_id; u_long bd_flags; /* board-specific flags */ struct sb_chinfo pch, rch; }; static int sb_rd(struct sb_info *sb, int reg); static void sb_wr(struct sb_info *sb, int reg, u_int8_t val); static int sb_dspready(struct sb_info *sb); static int sb_cmd(struct sb_info *sb, u_char val); static int sb_cmd1(struct sb_info *sb, u_char cmd, int val); static int sb_cmd2(struct sb_info *sb, u_char cmd, int val); static u_int sb_get_byte(struct sb_info *sb); static void sb_setmixer(struct sb_info *sb, u_int port, u_int value); static int sb_getmixer(struct sb_info *sb, u_int port); static int sb_reset_dsp(struct sb_info *sb); static void sb_intr(void *arg); static int sb_speed(struct sb_chinfo *ch); static int sb_start(struct sb_chinfo *ch); static int sb_stop(struct sb_chinfo *ch); /* * Common code for the midi and pcm functions * * sb_cmd write a single byte to the CMD port. * sb_cmd1 write a CMD + 1 byte arg * sb_cmd2 write a CMD + 2 byte arg * sb_get_byte returns a single byte from the DSP data port */ static void sb_lock(struct sb_info *sb) { sbc_lock(device_get_softc(sb->parent_dev)); } static void sb_unlock(struct sb_info *sb) { sbc_unlock(device_get_softc(sb->parent_dev)); } static int port_rd(struct resource *port, int off) { return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); } static void port_wr(struct resource *port, int off, u_int8_t data) { bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); } static int sb_rd(struct sb_info *sb, int reg) { return port_rd(sb->io_base, reg); } static void sb_wr(struct sb_info *sb, int reg, u_int8_t val) { port_wr(sb->io_base, reg, val); } static int sb_dspready(struct sb_info *sb) { return ((sb_rd(sb, SBDSP_STATUS) & 0x80) == 0); } static int sb_dspwr(struct sb_info *sb, u_char val) { int i; for (i = 0; i < 1000; i++) { if (sb_dspready(sb)) { sb_wr(sb, SBDSP_CMD, val); return 1; } if (i > 10) DELAY((i > 100)? 1000 : 10); } printf("sb_dspwr(0x%02x) timed out.\n", val); return 0; } static int sb_cmd(struct sb_info *sb, u_char val) { #if 0 printf("sb_cmd: %x\n", val); #endif return sb_dspwr(sb, val); } static int sb_cmd1(struct sb_info *sb, u_char cmd, int val) { #if 0 printf("sb_cmd1: %x, %x\n", cmd, val); #endif if (sb_dspwr(sb, cmd)) { return sb_dspwr(sb, val & 0xff); } else return 0; } static int sb_cmd2(struct sb_info *sb, u_char cmd, int val) { #if 0 printf("sb_cmd2: %x, %x\n", cmd, val); #endif if (sb_dspwr(sb, cmd)) { return sb_dspwr(sb, val & 0xff) && sb_dspwr(sb, (val >> 8) & 0xff); } else return 0; } /* * in the SB, there is a set of indirect "mixer" registers with * address at offset 4, data at offset 5 * * we don't need to interlock these, the mixer lock will suffice. */ static void sb_setmixer(struct sb_info *sb, u_int port, u_int value) { sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); sb_wr(sb, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); } static int sb_getmixer(struct sb_info *sb, u_int port) { int val; sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = sb_rd(sb, SB_MIX_DATA); DELAY(10); return val; } static u_int sb_get_byte(struct sb_info *sb) { int i; for (i = 1000; i > 0; i--) { if (sb_rd(sb, DSP_DATA_AVAIL) & 0x80) return sb_rd(sb, DSP_READ); else DELAY(20); } return 0xffff; } static int sb_reset_dsp(struct sb_info *sb) { sb_wr(sb, SBDSP_RST, 3); DELAY(100); sb_wr(sb, SBDSP_RST, 0); if (sb_get_byte(sb) != 0xAA) { DEB(printf("sb_reset_dsp 0x%lx failed\n", rman_get_start(sb->io_base))); return ENXIO; /* Sorry */ } return 0; } static void sb_release_resources(struct sb_info *sb, device_t dev) { if (sb->irq) { if (sb->ih) bus_teardown_intr(dev, sb->irq, sb->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); sb->irq = 0; } if (sb->drq) { isa_dma_release(rman_get_start(sb->drq)); bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq); sb->drq = 0; } if (sb->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); sb->io_base = 0; } if (sb->parent_dmat) { bus_dma_tag_destroy(sb->parent_dmat); sb->parent_dmat = 0; } free(sb, M_DEVBUF); } static int sb_alloc_resources(struct sb_info *sb, device_t dev) { int rid; rid = 0; if (!sb->io_base) sb->io_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; if (!sb->irq) sb->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); rid = 0; if (!sb->drq) sb->drq = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); if (sb->io_base && sb->drq && sb->irq) { isa_dma_acquire(rman_get_start(sb->drq)); isa_dmainit(rman_get_start(sb->drq), sb->bufsize); return 0; } else return ENXIO; } /************************************************************/ static int sbpromix_init(struct snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME); mix_setrecdevs(m, SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD); sb_setmixer(sb, 0, 1); /* reset mixer */ return 0; } static int sbpromix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); int reg, max; u_char val; max = 7; switch (dev) { case SOUND_MIXER_PCM: reg = 0x04; break; case SOUND_MIXER_MIC: reg = 0x0a; max = 3; break; case SOUND_MIXER_VOLUME: reg = 0x22; break; case SOUND_MIXER_SYNTH: reg = 0x26; break; case SOUND_MIXER_CD: reg = 0x28; break; case SOUND_MIXER_LINE: reg = 0x2e; break; default: return -1; } left = (left * max) / 100; right = (dev == SOUND_MIXER_MIC)? left : ((right * max) / 100); val = (dev == SOUND_MIXER_MIC)? (left << 1) : (left << 5 | right << 1); sb_setmixer(sb, reg, val); left = (left * 100) / max; right = (right * 100) / max; return left | (right << 8); } static int sbpromix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); u_char recdev; if (src == SOUND_MASK_LINE) recdev = 0x06; else if (src == SOUND_MASK_CD) recdev = 0x02; else { /* default: mic */ src = SOUND_MASK_MIC; recdev = 0; } sb_setmixer(sb, RECORD_SRC, recdev | (sb_getmixer(sb, RECORD_SRC) & ~0x07)); return src; } static kobj_method_t sbpromix_mixer_methods[] = { KOBJMETHOD(mixer_init, sbpromix_init), KOBJMETHOD(mixer_set, sbpromix_set), KOBJMETHOD(mixer_setrecsrc, sbpromix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(sbpromix_mixer); /************************************************************/ static int sbmix_init(struct snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_CD | SOUND_MASK_VOLUME); mix_setrecdevs(m, 0); sb_setmixer(sb, 0, 1); /* reset mixer */ return 0; } static int sbmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); int reg, max; max = 7; switch (dev) { case SOUND_MIXER_VOLUME: reg = 0x2; break; case SOUND_MIXER_SYNTH: reg = 0x6; break; case SOUND_MIXER_CD: reg = 0x8; break; case SOUND_MIXER_PCM: reg = 0x0a; max = 3; break; default: return -1; } left = (left * max) / 100; sb_setmixer(sb, reg, left << 1); left = (left * 100) / max; return left | (left << 8); } static int sbmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { return 0; } static kobj_method_t sbmix_mixer_methods[] = { KOBJMETHOD(mixer_init, sbmix_init), KOBJMETHOD(mixer_set, sbmix_set), KOBJMETHOD(mixer_setrecsrc, sbmix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(sbmix_mixer); /************************************************************/ static void sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; sb_lock(sb); if (sndbuf_runsz(sb->pch.buffer) > 0) { sb_unlock(sb); chn_intr(sb->pch.channel); sb_lock(sb); } if (sndbuf_runsz(sb->rch.buffer) > 0) { sb_unlock(sb); chn_intr(sb->rch.channel); sb_lock(sb); } sb_rd(sb, DSP_DATA_AVAIL); /* int ack */ sb_unlock(sb); } static int sb_speed(struct sb_chinfo *ch) { struct sb_info *sb = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; int speed, tmp, thresh, max; u_char tconst; if (sb->bd_id >= 0x300) { thresh = stereo? 11025 : 23000; max = stereo? 22050 : 44100; } else if (sb->bd_id > 0x200) { thresh = play? 23000 : 13000; max = play? 44100 : 15000; } else { thresh = 999999; max = play? 23000 : 13000; } speed = ch->spd; if (speed > max) speed = max; sb_lock(sb); sb->bd_flags &= ~BD_F_HISPEED; if (speed > thresh) sb->bd_flags |= BD_F_HISPEED; tmp = 65536 - (256000000 / (speed << stereo)); tconst = tmp >> 8; sb_cmd1(sb, 0x40, tconst); /* set time constant */ speed = (256000000 / (65536 - tmp)) >> stereo; ch->spd = speed; sb_unlock(sb); return speed; } static int sb_start(struct sb_chinfo *ch) { struct sb_info *sb = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; int l = ch->blksz; u_char i; l--; sb_lock(sb); if (play) sb_cmd(sb, DSP_CMD_SPKON); if (sb->bd_flags & BD_F_HISPEED) i = play? 0x90 : 0x98; else i = play? 0x1c : 0x2c; sb_setmixer(sb, 0x0e, stereo? 2 : 0); sb_cmd2(sb, 0x48, l); sb_cmd(sb, i); sb->bd_flags |= BD_F_DMARUN; sb_unlock(sb); return 0; } static int sb_stop(struct sb_chinfo *ch) { struct sb_info *sb = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; sb_lock(sb); if (sb->bd_flags & BD_F_HISPEED) sb_reset_dsp(sb); else { #if 0 /* * NOTE: DSP_CMD_DMAEXIT_8 does not work with old * soundblaster. */ sb_cmd(sb, DSP_CMD_DMAEXIT_8); #endif sb_reset_dsp(sb); } if (play) sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ sb_unlock(sb); sb->bd_flags &= ~BD_F_DMARUN; return 0; } /* channel interface */ static void * sbchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sb_info *sb = devinfo; struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; ch->parent = sb; ch->channel = c; ch->dir = dir; ch->buffer = b; if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; sndbuf_dmasetup(ch->buffer, sb->drq); return ch; } static int sbchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sb_chinfo *ch = data; ch->fmt = format; return 0; } static int sbchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sb_chinfo *ch = data; ch->spd = speed; return sb_speed(ch); } static int sbchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sb_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int sbchan_trigger(kobj_t obj, void *data, int go) { struct sb_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); if (go == PCMTRIG_START) sb_start(ch); else sb_stop(ch); return 0; } static int sbchan_getptr(kobj_t obj, void *data) { struct sb_chinfo *ch = data; return sndbuf_dmaptr(ch->buffer); } static struct pcmchan_caps * sbchan_getcaps(kobj_t obj, void *data) { struct sb_chinfo *ch = data; int p = (ch->dir == PCMDIR_PLAY)? 1 : 0; if (ch->parent->bd_id == 0x200) return p? &sb200_playcaps : &sb200_reccaps; if (ch->parent->bd_id < 0x300) return p? &sb201_playcaps : &sb201_reccaps; return p? &sbpro_playcaps : &sbpro_reccaps; } static kobj_method_t sbchan_methods[] = { KOBJMETHOD(channel_init, sbchan_init), KOBJMETHOD(channel_setformat, sbchan_setformat), KOBJMETHOD(channel_setspeed, sbchan_setspeed), KOBJMETHOD(channel_setblocksize, sbchan_setblocksize), KOBJMETHOD(channel_trigger, sbchan_trigger), KOBJMETHOD(channel_getptr, sbchan_getptr), KOBJMETHOD(channel_getcaps, sbchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(sbchan); /************************************************************/ static int sb_probe(device_t dev) { char buf[64]; uintptr_t func, ver, r, f; /* The parent device has already been probed. */ r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); if (func != SCF_PCM) return (ENXIO); r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); f = (ver & 0xffff0000) >> 16; ver &= 0x0000ffff; if ((f & BD_F_ESS) || (ver >= 0x400)) return (ENXIO); snprintf(buf, sizeof buf, "SB DSP %d.%02d", (int) ver >> 8, (int) ver & 0xff); device_set_desc_copy(dev, buf); return 0; } static int sb_attach(device_t dev) { struct sb_info *sb; char status[SND_STATUSLEN]; uintptr_t ver; sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); if (!sb) return ENXIO; sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; sb->bd_flags = (ver & 0xffff0000) >> 16; sb->bufsize = pcm_getbuffersize(dev, 4096, SB_DEFAULT_BUFSZ, 65536); if (sb_alloc_resources(sb, dev)) goto no; if (sb_reset_dsp(sb)) goto no; if (mixer_init(dev, (sb->bd_id < 0x300)? &sbmix_mixer_class : &sbpromix_mixer_class, sb)) goto no; if (snd_setup_intr(dev, sb->irq, 0, sb_intr, sb, &sb->ih)) goto no; pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); 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*/sb->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sb->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld bufsz %u %s", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq), sb->bufsize, PCM_KLDSTRING(snd_sb8)); if (pcm_register(dev, sb, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &sbchan_class, sb); pcm_addchan(dev, PCMDIR_PLAY, &sbchan_class, sb); pcm_setstatus(dev, status); return 0; no: sb_release_resources(sb, dev); return ENXIO; } static int sb_detach(device_t dev) { int r; struct sb_info *sb; r = pcm_unregister(dev); if (r) return r; sb = pcm_getdevinfo(dev); sb_release_resources(sb, dev); return 0; } static device_method_t sb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sb_probe), DEVMETHOD(device_attach, sb_attach), DEVMETHOD(device_detach, sb_detach), { 0, 0 } }; static driver_t sb_driver = { "pcm", sb_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_sb8, sbc, sb_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_sb8, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_sb8, snd_sbc, 1, 1, 1); MODULE_VERSION(snd_sb8, 1); Index: head/sys/dev/sound/pci/als4000.c =================================================================== --- head/sys/dev/sound/pci/als4000.c (revision 170520) +++ head/sys/dev/sound/pci/als4000.c (revision 170521) @@ -1,947 +1,954 @@ /*- * 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. */ #include #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* 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[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 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 = AFMT_U8; ch->speed = DSP_DEFAULT_SPEED; ch->buffer = b; snd_mtxunlock(sc->lock); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; return ch; } static int alschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->format = format; return 0; } static int 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 int 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 int alschan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int32_t pos, sz; snd_mtxlock(sc->lock); pos = als_gcr_rd(ch->parent, ch->gcr_fifo_status) & 0xffff; snd_mtxunlock(sc->lock); sz = sndbuf_getsize(ch->buffer); return (2 * sz - pos - 1) % sz; } static struct pcmchan_caps* alschan_getcaps(kobj_t obj, void *data) { return &als_caps; } static void als_set_speed(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; struct sc_chinfo *other; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; if (other->dma_active == 0) { als_esp_wr(sc, ALS_ESP_SAMPLE_RATE); als_esp_wr(sc, ch->speed >> 8); als_esp_wr(sc, ch->speed & 0xff); } else { DEB(printf("speed locked at %d (tried %d)\n", other->speed, ch->speed)); } } /* ------------------------------------------------------------------------- */ /* Playback channel implementation */ #define ALS_8BIT_CMD(x, y) { (x), (y), DSP_DMA8, DSP_CMD_DMAPAUSE_8 } #define ALS_16BIT_CMD(x, y) { (x), (y), DSP_DMA16, DSP_CMD_DMAPAUSE_16 } struct playback_command { u_int32_t pcm_format; /* newpcm format */ u_int8_t format_val; /* sb16 format value */ u_int8_t dma_prog; /* sb16 dma program */ u_int8_t dma_stop; /* sb16 stop register */ } static const playback_cmds[] = { ALS_8BIT_CMD(AFMT_U8, DSP_MODE_U8MONO), ALS_8BIT_CMD(AFMT_U8 | AFMT_STEREO, DSP_MODE_U8STEREO), ALS_16BIT_CMD(AFMT_S16_LE, DSP_MODE_S16MONO), ALS_16BIT_CMD(AFMT_S16_LE | AFMT_STEREO, DSP_MODE_S16STEREO), }; static const struct playback_command* als_get_playback_command(u_int32_t format) { u_int32_t i, n; n = sizeof(playback_cmds) / sizeof(playback_cmds[0]); for (i = 0; i < n; i++) { if (playback_cmds[i].pcm_format == format) { return &playback_cmds[i]; } } DEB(printf("als_get_playback_command: invalid format 0x%08x\n", format)); return &playback_cmds[0]; } static void als_playback_start(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; buf = sndbuf_getbufaddr(ch->buffer); bufsz = sndbuf_getsize(ch->buffer); count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_DMA0_START, buf); als_gcr_wr(sc, ALS_GCR_DMA0_MODE, (bufsz - 1) | 0x180000); p = als_get_playback_command(ch->format); dma_prog = p->dma_prog | DSP_F16_DAC | DSP_F16_AUTO | DSP_F16_FIFO_ON; als_esp_wr(sc, dma_prog); als_esp_wr(sc, p->format_val); als_esp_wr(sc, count & 0xff); als_esp_wr(sc, count >> 8); ch->dma_active = 1; } static int als_playback_stop(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { p = als_get_playback_command(ch->format); als_esp_wr(sc, p->dma_stop); } ch->dma_active = 0; return active; } static int alspchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + if (!PCMTRIG_COMMON(go)) + return 0; + snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_playback_start(ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: als_playback_stop(ch); break; + default: + break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alspchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alspchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), { 0, 0 } }; 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 AFMT_U8: return ALS_FIFO1_8BIT; case AFMT_U8 | AFMT_STEREO: return ALS_FIFO1_8BIT | ALS_FIFO1_STEREO; case AFMT_S16_LE: return ALS_FIFO1_SIGNED; case AFMT_S16_LE | AFMT_STEREO: return ALS_FIFO1_SIGNED | ALS_FIFO1_STEREO; } device_printf(sc->dev, "format not found: 0x%08x\n", format); return ALS_FIFO1_8BIT; } static void als_capture_start(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; buf = sndbuf_getbufaddr(ch->buffer); bufsz = sndbuf_getsize(ch->buffer); count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_FIFO1_START, buf); als_gcr_wr(sc, ALS_GCR_FIFO1_COUNT, (bufsz - 1)); als_mix_wr(sc, ALS_FIFO1_LENGTH_LO, count & 0xff); als_mix_wr(sc, ALS_FIFO1_LENGTH_HI, count >> 8); dma_prog = ALS_FIFO1_RUN | als_get_fifo_format(sc, ch->format); als_mix_wr(sc, ALS_FIFO1_CONTROL, dma_prog); ch->dma_active = 1; } static int als_capture_stop(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { als_mix_wr(sc, ALS_FIFO1_CONTROL, ALS_FIFO1_STOP); } ch->dma_active = 0; return active; } static int alsrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_capture_start(ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: als_capture_stop(ch); break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alsrchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alsrchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), { 0, 0 } }; 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 int 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), { 0, 0 } }; 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 = 0; } if (sc->ih) { bus_teardown_intr(dev, sc->irq, sc->ih); sc->ih = 0; } if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = 0; } 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(dev, SYS_RES_IOPORT, &sc->regid, 0, ~0, ALS_CONFIG_SPACE_BYTES, RF_ACTIVE); if (sc->reg == 0) { 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 == 0) { 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; u_int32_t data; char status[SND_STATUSLEN]; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_als4000 softc"); sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); /* * 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 __FreeBSD_version > 500000 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); } #else data = pci_read_config(dev, ALS_PCI_POWERREG, 2); if ((data & 0x03) != 0) { device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", data & 0x03); data &= ~0x03; pci_write_config(dev, ALS_PCI_POWERREG, data, 2); } #endif if (als_resource_grab(dev, sc)) { device_printf(dev, "failed to allocate resources\n"); goto bad_attach; } if (als_init(sc)) { device_printf(dev, "failed to initialize hardware\n"); goto bad_attach; } if (mixer_init(dev, &als_mixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad_attach; } if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "failed to register pcm entries\n"); goto bad_attach; } pcm_addchan(dev, PCMDIR_PLAY, &alspchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &alsrchan_class, sc); snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_als4000)); pcm_setstatus(dev, status); return 0; bad_attach: als_resource_free(dev, sc); free(sc, M_DEVBUF); return ENXIO; } static int als_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); als_uninit(sc); als_resource_free(dev, sc); free(sc, M_DEVBUF); return 0; } static int als_pci_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = als_playback_stop(&sc->pch); sc->rch.dma_was_active = als_capture_stop(&sc->rch); als_uninit(sc); snd_mtxunlock(sc->lock); return 0; } static int als_pci_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); if (als_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) != 0) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { als_playback_start(&sc->pch); } if (sc->rch.dma_was_active) { als_capture_start(&sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t als_methods[] = { /* Device interface */ DEVMETHOD(device_probe, als_pci_probe), DEVMETHOD(device_attach, als_pci_attach), DEVMETHOD(device_detach, als_pci_detach), DEVMETHOD(device_suspend, als_pci_suspend), DEVMETHOD(device_resume, als_pci_resume), { 0, 0 } }; static driver_t als_driver = { "pcm", als_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_als4000, pci, als_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_als4000, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_als4000, 1); Index: head/sys/dev/sound/pci/atiixp.c =================================================================== --- head/sys/dev/sound/pci/atiixp.c (revision 170520) +++ head/sys/dev/sound/pci/atiixp.c (revision 170521) @@ -1,1404 +1,1407 @@ /*- * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * FreeBSD pcm driver for ATI IXP 150/200/250/300 AC97 controllers * * Features * * 16bit playback / recording * * 32bit native playback - yay! * * 32bit native recording (seems broken on few hardwares) * * Issues / TODO: * * SPDIF * * Support for more than 2 channels. * * VRA ? VRM ? DRA ? * * 32bit native recording seems broken on few hardwares, most * probably because of incomplete VRA/DRA cleanup. * * * Thanks goes to: * * Shaharil @ SCAN Associates whom relentlessly providing me the * mind blowing Acer Ferrari 4002 WLMi with this ATI IXP hardware. * * Reinoud Zandijk (auixp), which this driver is * largely based upon although large part of it has been reworked. His * driver is the primary reference and pretty much well documented. * * Takashi Iwai (ALSA snd-atiixp), for register definitions and some * random ninja hackery. */ #include #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #define ATI_IXP_DMA_RETRY_MAX 100 #define ATI_IXP_BUFSZ_MIN 4096 #define ATI_IXP_BUFSZ_MAX 65536 #define ATI_IXP_BUFSZ_DEFAULT 16384 #define ATI_IXP_BLK_MIN 32 #define ATI_IXP_BLK_ALIGN (~(ATI_IXP_BLK_MIN - 1)) struct atiixp_dma_op { volatile uint32_t addr; volatile uint16_t status; volatile uint16_t size; volatile uint32_t next; }; struct atiixp_info; struct atiixp_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct atiixp_info *parent; struct atiixp_dma_op *sgd_table; bus_addr_t sgd_addr; uint32_t enable_bit, flush_bit, linkptr_bit, dt_cur_bit; uint32_t blksz, blkcnt; uint32_t ptr, prevptr; uint32_t fmt; int caps_32bit, dir, active; }; struct atiixp_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; struct ac97_info *codec; struct atiixp_chinfo pch; struct atiixp_chinfo rch; struct atiixp_dma_op *sgd_table; struct intr_config_hook delayed_attach; uint32_t bufsz; uint32_t codec_not_ready_bits, codec_idx, codec_found; uint32_t blkcnt; int registered_channels; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; #define atiixp_rd(_sc, _reg) \ bus_space_read_4((_sc)->st, (_sc)->sh, _reg) #define atiixp_wr(_sc, _reg, _val) \ bus_space_write_4((_sc)->st, (_sc)->sh, _reg, _val) #define atiixp_lock(_sc) snd_mtxlock((_sc)->lock) #define atiixp_unlock(_sc) snd_mtxunlock((_sc)->lock) #define atiixp_assert(_sc) snd_mtxassert((_sc)->lock) static uint32_t atiixp_fmt_32bit[] = { AFMT_STEREO | AFMT_S16_LE, AFMT_STEREO | AFMT_S32_LE, 0 }; static uint32_t atiixp_fmt[] = { AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps atiixp_caps_32bit = { ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt_32bit, 0 }; static struct pcmchan_caps atiixp_caps = { ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt, 0 }; static const struct { uint16_t vendor; uint16_t devid; char *desc; } atiixp_hw[] = { { ATI_VENDOR_ID, ATI_IXP_200_ID, "ATI IXP 200" }, { ATI_VENDOR_ID, ATI_IXP_300_ID, "ATI IXP 300" }, { ATI_VENDOR_ID, ATI_IXP_400_ID, "ATI IXP 400" }, }; static void atiixp_enable_interrupts(struct atiixp_info *); static void atiixp_disable_interrupts(struct atiixp_info *); static void atiixp_reset_aclink(struct atiixp_info *); static void atiixp_flush_dma(struct atiixp_chinfo *); static void atiixp_enable_dma(struct atiixp_chinfo *); static void atiixp_disable_dma(struct atiixp_chinfo *); static int atiixp_waitready_codec(struct atiixp_info *); static int atiixp_rdcd(kobj_t, void *, int); static int atiixp_wrcd(kobj_t, void *, int, uint32_t); static void *atiixp_chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int atiixp_chan_setformat(kobj_t, void *, uint32_t); static int atiixp_chan_setspeed(kobj_t, void *, uint32_t); static int atiixp_chan_setfragments(kobj_t, void *, uint32_t, uint32_t); static int atiixp_chan_setblocksize(kobj_t, void *, uint32_t); static void atiixp_buildsgdt(struct atiixp_chinfo *); static int atiixp_chan_trigger(kobj_t, void *, int); static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *); static int atiixp_chan_getptr(kobj_t, void *); static struct pcmchan_caps *atiixp_chan_getcaps(kobj_t, void *); static void atiixp_intr(void *); static void atiixp_dma_cb(void *, bus_dma_segment_t *, int, int); static void atiixp_chip_pre_init(struct atiixp_info *); static void atiixp_chip_post_init(void *); static void atiixp_release_resource(struct atiixp_info *); static int atiixp_pci_probe(device_t); static int atiixp_pci_attach(device_t); static int atiixp_pci_detach(device_t); static int atiixp_pci_suspend(device_t); static int atiixp_pci_resume(device_t); /* * ATI IXP helper functions */ static void atiixp_enable_interrupts(struct atiixp_info *sc) { uint32_t value; /* clear all pending */ atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); /* enable all relevant interrupt sources we can handle */ value = atiixp_rd(sc, ATI_REG_IER); value |= ATI_REG_IER_IO_STATUS_EN; /* * Disable / ignore internal xrun/spdf interrupt flags * since it doesn't interest us (for now). */ #if 1 value &= ~(ATI_REG_IER_IN_XRUN_EN | ATI_REG_IER_OUT_XRUN_EN | ATI_REG_IER_SPDF_XRUN_EN | ATI_REG_IER_SPDF_STATUS_EN); #else value |= ATI_REG_IER_IN_XRUN_EN; value |= ATI_REG_IER_OUT_XRUN_EN; value |= ATI_REG_IER_SPDF_XRUN_EN; value |= ATI_REG_IER_SPDF_STATUS_EN; #endif atiixp_wr(sc, ATI_REG_IER, value); } static void atiixp_disable_interrupts(struct atiixp_info *sc) { /* disable all interrupt sources */ atiixp_wr(sc, ATI_REG_IER, 0); /* clear all pending */ atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); } static void atiixp_reset_aclink(struct atiixp_info *sc) { uint32_t value, timeout; /* if power is down, power it up */ value = atiixp_rd(sc, ATI_REG_CMD); if (value & ATI_REG_CMD_POWERDOWN) { /* explicitly enable power */ value &= ~ATI_REG_CMD_POWERDOWN; atiixp_wr(sc, ATI_REG_CMD, value); /* have to wait at least 10 usec for it to initialise */ DELAY(20); } /* perform a soft reset */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_SOFT_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* need to read the CMD reg and wait aprox. 10 usec to init */ value = atiixp_rd(sc, ATI_REG_CMD); DELAY(20); /* clear soft reset flag again */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_AC_SOFT_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* check if the ac-link is working; reset device otherwise */ timeout = 10; value = atiixp_rd(sc, ATI_REG_CMD); while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) && --timeout) { #if 0 device_printf(sc->dev, "not up; resetting aclink hardware\n"); #endif /* dip aclink reset but keep the acsync */ value &= ~ATI_REG_CMD_AC_RESET; value |= ATI_REG_CMD_AC_SYNC; atiixp_wr(sc, ATI_REG_CMD, value); /* need to read CMD again and wait again (clocking in issue?) */ value = atiixp_rd(sc, ATI_REG_CMD); DELAY(20); /* assert aclink reset again */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* check if its active now */ value = atiixp_rd(sc, ATI_REG_CMD); } if (timeout == 0) device_printf(sc->dev, "giving up aclink reset\n"); #if 0 if (timeout != 10) device_printf(sc->dev, "aclink hardware reset successful\n"); #endif /* assert reset and sync for safety */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_SYNC | ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, value); } static void atiixp_flush_dma(struct atiixp_chinfo *ch) { atiixp_wr(ch->parent, ATI_REG_FIFO_FLUSH, ch->flush_bit); } static void atiixp_enable_dma(struct atiixp_chinfo *ch) { uint32_t value; value = atiixp_rd(ch->parent, ATI_REG_CMD); if (!(value & ch->enable_bit)) { value |= ch->enable_bit; atiixp_wr(ch->parent, ATI_REG_CMD, value); } } static void atiixp_disable_dma(struct atiixp_chinfo *ch) { uint32_t value; value = atiixp_rd(ch->parent, ATI_REG_CMD); if (value & ch->enable_bit) { value &= ~ch->enable_bit; atiixp_wr(ch->parent, ATI_REG_CMD, value); } } /* * AC97 interface */ static int atiixp_waitready_codec(struct atiixp_info *sc) { int timeout = 500; do { if ((atiixp_rd(sc, ATI_REG_PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) == 0) return (0); DELAY(1); } while (--timeout); return (-1); } static int atiixp_rdcd(kobj_t obj, void *devinfo, int reg) { struct atiixp_info *sc = devinfo; uint32_t data; int timeout; if (atiixp_waitready_codec(sc)) return (-1); data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); if (atiixp_waitready_codec(sc)) return (-1); timeout = 500; do { data = atiixp_rd(sc, ATI_REG_PHYS_IN_ADDR); if (data & ATI_REG_PHYS_IN_READ_FLAG) return (data >> ATI_REG_PHYS_IN_DATA_SHIFT); DELAY(1); } while (--timeout); if (reg < 0x7c) device_printf(sc->dev, "codec read timeout! (reg 0x%x)\n", reg); return (-1); } static int atiixp_wrcd(kobj_t obj, void *devinfo, int reg, uint32_t data) { struct atiixp_info *sc = devinfo; if (atiixp_waitready_codec(sc)) return (-1); data = (data << ATI_REG_PHYS_OUT_DATA_SHIFT) | (((uint32_t)reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); return (0); } static kobj_method_t atiixp_ac97_methods[] = { KOBJMETHOD(ac97_read, atiixp_rdcd), KOBJMETHOD(ac97_write, atiixp_wrcd), { 0, 0 } }; AC97_DECLARE(atiixp_ac97); /* * Playback / Record channel interface */ static void * atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct atiixp_info *sc = devinfo; struct atiixp_chinfo *ch; int num; atiixp_lock(sc); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->linkptr_bit = ATI_REG_OUT_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SEND_EN; ch->flush_bit = ATI_REG_FIFO_OUT_FLUSH; ch->dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; /* Native 32bit playback working properly */ ch->caps_32bit = 1; } else { ch = &sc->rch; ch->linkptr_bit = ATI_REG_IN_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; ch->flush_bit = ATI_REG_FIFO_IN_FLUSH; ch->dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; /* XXX Native 32bit recording appear to be broken */ ch->caps_32bit = 1; } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; atiixp_unlock(sc); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) == -1) return (NULL); atiixp_lock(sc); num = sc->registered_channels++; ch->sgd_table = &sc->sgd_table[num * ATI_IXP_DMA_CHSEGS_MAX]; ch->sgd_addr = sc->sgd_addr + (num * ATI_IXP_DMA_CHSEGS_MAX * sizeof(struct atiixp_dma_op)); atiixp_disable_dma(ch); atiixp_unlock(sc); return (ch); } static int atiixp_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; atiixp_lock(sc); if (ch->dir == PCMDIR_REC) { value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_INTERLEAVE_IN; if ((format & AFMT_32BIT) == 0) value |= ATI_REG_CMD_INTERLEAVE_IN; atiixp_wr(sc, ATI_REG_CMD, value); } else { value = atiixp_rd(sc, ATI_REG_OUT_DMA_SLOT); value &= ~ATI_REG_OUT_DMA_SLOT_MASK; /* We do not have support for more than 2 channels, _yet_. */ value |= ATI_REG_OUT_DMA_SLOT_BIT(3) | ATI_REG_OUT_DMA_SLOT_BIT(4); value |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; atiixp_wr(sc, ATI_REG_OUT_DMA_SLOT, value); value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_INTERLEAVE_OUT; if ((format & AFMT_32BIT) == 0) value |= ATI_REG_CMD_INTERLEAVE_OUT; atiixp_wr(sc, ATI_REG_CMD, value); value = atiixp_rd(sc, ATI_REG_6CH_REORDER); value &= ~ATI_REG_6CH_REORDER_EN; atiixp_wr(sc, ATI_REG_6CH_REORDER, value); } ch->fmt = format; atiixp_unlock(sc); return (0); } static int atiixp_chan_setspeed(kobj_t obj, void *data, uint32_t spd) { /* XXX We're supposed to do VRA/DRA processing right here */ return (ATI_IXP_BASE_RATE); } static int atiixp_chan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; blksz &= ATI_IXP_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN)) blksz = sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN; if (blksz < ATI_IXP_BLK_MIN) blksz = ATI_IXP_BLK_MIN; if (blkcnt > ATI_IXP_DMA_CHSEGS_MAX) blkcnt = ATI_IXP_DMA_CHSEGS_MAX; if (blkcnt < ATI_IXP_DMA_CHSEGS_MIN) blkcnt = ATI_IXP_DMA_CHSEGS_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= ATI_IXP_DMA_CHSEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ATI_IXP_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); ch->blkcnt = sndbuf_getblkcnt(ch->buffer); return (1); } static int atiixp_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; atiixp_chan_setfragments(obj, data, blksz, sc->blkcnt); return (ch->blksz); } static void atiixp_buildsgdt(struct atiixp_chinfo *ch) { struct atiixp_info *sc = ch->parent; uint32_t addr, blksz, blkcnt; int i; addr = sndbuf_getbufaddr(ch->buffer); if (sc->polling != 0) { blksz = ch->blksz * ch->blkcnt; blkcnt = 1; } else { blksz = ch->blksz; blkcnt = ch->blkcnt; } for (i = 0; i < blkcnt; i++) { ch->sgd_table[i].addr = htole32(addr + (i * blksz)); ch->sgd_table[i].status = htole16(0); ch->sgd_table[i].size = htole16(blksz >> 2); ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + (((i + 1) % blkcnt) * sizeof(struct atiixp_dma_op))); } } static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *ch) { struct atiixp_info *sc = ch->parent; uint32_t reg, addr, sz, retry; volatile uint32_t ptr; reg = ch->dt_cur_bit; addr = sndbuf_getbufaddr(ch->buffer); sz = ch->blkcnt * ch->blksz; retry = ATI_IXP_DMA_RETRY_MAX; do { ptr = atiixp_rd(sc, reg); if (ptr < addr) continue; ptr -= addr; if (ptr < sz) { #if 0 #ifdef ATI_IXP_DEBUG if ((ptr & ~(ch->blksz - 1)) != ch->ptr) { uint32_t delta; delta = (sz + ptr - ch->prevptr) % sz; #ifndef ATI_IXP_DEBUG_VERBOSE if (delta < ch->blksz) #endif device_printf(sc->dev, "PCMDIR_%s: incoherent DMA " "prevptr=%u ptr=%u " "ptr=%u blkcnt=%u " "[delta=%u != blksz=%u] " "(%s)\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->prevptr, ptr, ch->ptr, ch->blkcnt, delta, ch->blksz, (delta < ch->blksz) ? "OVERLAPPED!" : "Ok"); ch->ptr = ptr & ~(ch->blksz - 1); } ch->prevptr = ptr; #endif #endif return (ptr); } } while (--retry); device_printf(sc->dev, "PCMDIR_%s: invalid DMA pointer ptr=%u\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); return (0); } static __inline int atiixp_poll_channel(struct atiixp_chinfo *ch) { uint32_t sz, delta; volatile uint32_t ptr; if (ch->active == 0) return (0); sz = ch->blksz * ch->blkcnt; ptr = atiixp_dmapos(ch); ch->ptr = ptr; ptr %= sz; ptr &= ~(ch->blksz - 1); delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } #define atiixp_chan_active(sc) ((sc)->pch.active + (sc)->rch.active) static void atiixp_poll_callback(void *arg) { struct atiixp_info *sc = arg; uint32_t trigger = 0; if (sc == NULL) return; atiixp_lock(sc); if (sc->polling == 0 || atiixp_chan_active(sc) == 0) { atiixp_unlock(sc); return; } trigger |= (atiixp_poll_channel(&sc->pch) != 0) ? 1 : 0; trigger |= (atiixp_poll_channel(&sc->rch) != 0) ? 2 : 0; /* XXX */ callout_reset(&sc->poll_timer, 1/*sc->poll_ticks*/, atiixp_poll_callback, sc); atiixp_unlock(sc); if (trigger & 1) chn_intr(sc->pch.channel); if (trigger & 2) chn_intr(sc->rch.channel); } static int atiixp_chan_trigger(kobj_t obj, void *data, int go) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; int pollticks; + if (!PCMTRIG_COMMON(go)) + return (0); + atiixp_lock(sc); switch (go) { case PCMTRIG_START: atiixp_flush_dma(ch); atiixp_buildsgdt(ch); atiixp_wr(sc, ch->linkptr_bit, 0); atiixp_enable_dma(ch); atiixp_wr(sc, ch->linkptr_bit, (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); if (sc->polling != 0) { ch->ptr = 0; ch->prevptr = 0; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (atiixp_chan_active(sc) == 0 || pollticks < sc->poll_ticks) { if (bootverbose) { if (atiixp_chan_active(sc) == 0) device_printf(sc->dev, "%s: pollticks=%d\n", __func__, pollticks); else device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); } sc->poll_ticks = pollticks; callout_reset(&sc->poll_timer, 1, atiixp_poll_callback, sc); } } ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: atiixp_disable_dma(ch); atiixp_flush_dma(ch); ch->active = 0; if (sc->polling != 0) { if (atiixp_chan_active(sc) == 0) { callout_stop(&sc->poll_timer); sc->poll_ticks = 1; } else { if (sc->pch.active != 0) ch = &sc->pch; else ch = &sc->rch; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks > sc->poll_ticks) { if (bootverbose) device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); sc->poll_ticks = pollticks; callout_reset(&sc->poll_timer, 1, atiixp_poll_callback, sc); } } } break; default: atiixp_unlock(sc); return (0); break; } /* Update bus busy status */ value = atiixp_rd(sc, ATI_REG_IER); if (atiixp_rd(sc, ATI_REG_CMD) & (ATI_REG_CMD_SEND_EN | ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) value |= ATI_REG_IER_SET_BUS_BUSY; else value &= ~ATI_REG_IER_SET_BUS_BUSY; atiixp_wr(sc, ATI_REG_IER, value); atiixp_unlock(sc); return (0); } static int atiixp_chan_getptr(kobj_t obj, void *data) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t ptr; atiixp_lock(sc); if (sc->polling != 0) ptr = ch->ptr; else ptr = atiixp_dmapos(ch); atiixp_unlock(sc); return (ptr); } static struct pcmchan_caps * atiixp_chan_getcaps(kobj_t obj, void *data) { struct atiixp_chinfo *ch = data; if (ch->caps_32bit) return (&atiixp_caps_32bit); return (&atiixp_caps); } static kobj_method_t atiixp_chan_methods[] = { KOBJMETHOD(channel_init, atiixp_chan_init), KOBJMETHOD(channel_setformat, atiixp_chan_setformat), KOBJMETHOD(channel_setspeed, atiixp_chan_setspeed), KOBJMETHOD(channel_setblocksize, atiixp_chan_setblocksize), KOBJMETHOD(channel_setfragments, atiixp_chan_setfragments), KOBJMETHOD(channel_trigger, atiixp_chan_trigger), KOBJMETHOD(channel_getptr, atiixp_chan_getptr), KOBJMETHOD(channel_getcaps, atiixp_chan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(atiixp_chan); /* * PCI driver interface */ static void atiixp_intr(void *p) { struct atiixp_info *sc = p; uint32_t status, enable, detected_codecs; uint32_t trigger = 0; atiixp_lock(sc); if (sc->polling != 0) { atiixp_unlock(sc); return; } status = atiixp_rd(sc, ATI_REG_ISR); if (status == 0) { atiixp_unlock(sc); return; } if ((status & ATI_REG_ISR_OUT_STATUS) && sc->pch.active != 0) trigger |= 1; if ((status & ATI_REG_ISR_IN_STATUS) && sc->rch.active != 0) trigger |= 2; #if 0 if (status & ATI_REG_ISR_IN_XRUN) { device_printf(sc->dev, "Recieve IN XRUN interrupt\n"); } if (status & ATI_REG_ISR_OUT_XRUN) { device_printf(sc->dev, "Recieve OUT XRUN interrupt\n"); } #endif if (status & CODEC_CHECK_BITS) { /* mark missing codecs as not ready */ detected_codecs = status & CODEC_CHECK_BITS; sc->codec_not_ready_bits |= detected_codecs; /* disable detected interupt sources */ enable = atiixp_rd(sc, ATI_REG_IER); enable &= ~detected_codecs; atiixp_wr(sc, ATI_REG_IER, enable); } /* acknowledge */ atiixp_wr(sc, ATI_REG_ISR, status); atiixp_unlock(sc); if (trigger & 1) chn_intr(sc->pch.channel); if (trigger & 2) chn_intr(sc->rch.channel); } static void atiixp_dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct atiixp_info *sc = (struct atiixp_info *)p; sc->sgd_addr = bds->ds_addr; } static void atiixp_chip_pre_init(struct atiixp_info *sc) { uint32_t value; atiixp_lock(sc); /* disable interrupts */ atiixp_disable_interrupts(sc); /* clear all DMA enables (preserving rest of settings) */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~(ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SPDF_OUT_EN ); atiixp_wr(sc, ATI_REG_CMD, value); /* reset aclink */ atiixp_reset_aclink(sc); sc->codec_not_ready_bits = 0; /* enable all codecs to interrupt as well as the new frame interrupt */ atiixp_wr(sc, ATI_REG_IER, CODEC_CHECK_BITS); atiixp_unlock(sc); } #ifdef SND_DYNSYSCTL static int sysctl_atiixp_polling(SYSCTL_HANDLER_ARGS) { struct atiixp_info *sc; device_t dev; int err, val; dev = oidp->oid_arg1; sc = pcm_getdevinfo(dev); if (sc == NULL) return (EINVAL); atiixp_lock(sc); val = sc->polling; atiixp_unlock(sc); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); atiixp_lock(sc); if (val != sc->polling) { if (atiixp_chan_active(sc) != 0) err = EBUSY; else if (val == 0) { atiixp_enable_interrupts(sc); sc->polling = 0; DELAY(1000); } else { atiixp_disable_interrupts(sc); sc->polling = 1; DELAY(1000); } } atiixp_unlock(sc); return (err); } #endif static void atiixp_chip_post_init(void *arg) { struct atiixp_info *sc = (struct atiixp_info *)arg; uint32_t subdev; int i, timeout, found, polling; char status[SND_STATUSLEN]; atiixp_lock(sc); if (sc->delayed_attach.ich_func) { config_intrhook_disestablish(&sc->delayed_attach); sc->delayed_attach.ich_func = NULL; } polling = sc->polling; sc->polling = 0; /* wait for the interrupts to happen */ timeout = 100; do { msleep(sc, sc->lock, PWAIT, "ixpslp", 1); if (sc->codec_not_ready_bits) break; } while (--timeout); sc->polling = polling; atiixp_disable_interrupts(sc); if (timeout == 0) { device_printf(sc->dev, "WARNING: timeout during codec detection; " "codecs might be present but haven't interrupted\n"); atiixp_unlock(sc); goto postinitbad; } found = 0; /* * ATI IXP can have upto 3 codecs, but single codec should be * suffice for now. */ if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC0_NOT_READY)) { /* codec 0 present */ sc->codec_found++; sc->codec_idx = 0; found++; } if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC1_NOT_READY)) { /* codec 1 present */ sc->codec_found++; } if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) { /* codec 2 present */ sc->codec_found++; } atiixp_unlock(sc); if (found == 0) goto postinitbad; /* create/init mixer */ sc->codec = AC97_CREATE(sc->dev, sc, atiixp_ac97); if (sc->codec == NULL) goto postinitbad; subdev = (pci_get_subdevice(sc->dev) << 16) | pci_get_subvendor(sc->dev); switch (subdev) { case 0x11831043: /* ASUS A6R */ case 0x2043161f: /* Maxselect x710s - http://maxselect.ru/ */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(sc->dev, ac97_getmixerclass(), sc->codec); if (pcm_register(sc->dev, sc, ATI_IXP_NPCHAN, ATI_IXP_NRCHAN)) goto postinitbad; for (i = 0; i < ATI_IXP_NPCHAN; i++) pcm_addchan(sc->dev, PCMDIR_PLAY, &atiixp_chan_class, sc); for (i = 0; i < ATI_IXP_NRCHAN; i++) pcm_addchan(sc->dev, PCMDIR_REC, &atiixp_chan_class, sc); #ifdef SND_DYNSYSCTL SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), sysctl_atiixp_polling, "I", "Enable polling mode"); #endif snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s", rman_get_start(sc->reg), rman_get_start(sc->irq), PCM_KLDSTRING(snd_atiixp)); pcm_setstatus(sc->dev, status); atiixp_lock(sc); if (sc->polling == 0) atiixp_enable_interrupts(sc); atiixp_unlock(sc); return; postinitbad: atiixp_release_resource(sc); } static void atiixp_release_resource(struct atiixp_info *sc) { if (sc == NULL) return; if (sc->codec) { ac97_destroy(sc->codec); sc->codec = NULL; } if (sc->ih) { bus_teardown_intr(sc->dev, sc->irq, sc->ih); sc->ih = NULL; } if (sc->reg) { bus_release_resource(sc->dev, sc->regtype, sc->regid, sc->reg); sc->reg = NULL; } if (sc->irq) { bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = NULL; } if (sc->sgd_dmamap) bus_dmamap_unload(sc->sgd_dmat, sc->sgd_dmamap); if (sc->sgd_table) { bus_dmamem_free(sc->sgd_dmat, sc->sgd_table, sc->sgd_dmamap); sc->sgd_table = NULL; } sc->sgd_dmamap = NULL; if (sc->sgd_dmat) { bus_dma_tag_destroy(sc->sgd_dmat); sc->sgd_dmat = NULL; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } } static int atiixp_pci_probe(device_t dev) { int i; uint16_t devid, vendor; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < sizeof(atiixp_hw) / sizeof(atiixp_hw[0]); i++) { if (vendor == atiixp_hw[i].vendor && devid == atiixp_hw[i].devid) { device_set_desc(dev, atiixp_hw[i].desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int atiixp_pci_attach(device_t dev) { struct atiixp_info *sc; int i; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENXIO); } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_atiixp softc"); sc->dev = dev; callout_init(&sc->poll_timer, CALLOUT_MPSAFE); sc->poll_ticks = 1; if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) sc->polling = 1; else sc->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, ATI_IXP_BUFSZ_MIN, ATI_IXP_BUFSZ_DEFAULT, ATI_IXP_BUFSZ_MAX); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, atiixp_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } /* * Let the user choose the best DMA segments. */ if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= ATI_IXP_BLK_ALIGN; if (i < ATI_IXP_BLK_MIN) i = ATI_IXP_BLK_MIN; sc->blkcnt = sc->bufsz / i; i = 0; while (sc->blkcnt >> i) i++; sc->blkcnt = 1 << (i - 1); if (sc->blkcnt < ATI_IXP_DMA_CHSEGS_MIN) sc->blkcnt = ATI_IXP_DMA_CHSEGS_MIN; else if (sc->blkcnt > ATI_IXP_DMA_CHSEGS_MAX) sc->blkcnt = ATI_IXP_DMA_CHSEGS_MAX; } else sc->blkcnt = ATI_IXP_DMA_CHSEGS; /* * DMA tag for scatter-gather buffers and link pointers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) goto bad; if (bus_dmamap_load(sc->sgd_dmat, sc->sgd_dmamap, sc->sgd_table, ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), atiixp_dma_cb, sc, 0)) goto bad; atiixp_chip_pre_init(sc); sc->delayed_attach.ich_func = atiixp_chip_post_init; sc->delayed_attach.ich_arg = sc; if (cold == 0 || config_intrhook_establish(&sc->delayed_attach) != 0) { sc->delayed_attach.ich_func = NULL; atiixp_chip_post_init(sc); } return (0); bad: atiixp_release_resource(sc); return (ENXIO); } static int atiixp_pci_detach(device_t dev) { int r; struct atiixp_info *sc; sc = pcm_getdevinfo(dev); if (sc != NULL) { if (sc->codec != NULL) { r = pcm_unregister(dev); if (r) return (r); } sc->codec = NULL; if (sc->st != 0 && sc->sh != 0) atiixp_disable_interrupts(sc); atiixp_release_resource(sc); free(sc, M_DEVBUF); } return (0); } static int atiixp_pci_suspend(device_t dev) { struct atiixp_info *sc = pcm_getdevinfo(dev); uint32_t value; /* quickly disable interrupts and save channels active state */ atiixp_lock(sc); atiixp_disable_interrupts(sc); atiixp_unlock(sc); /* stop everything */ if (sc->pch.active != 0) atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); if (sc->rch.active != 0) atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); /* power down aclink and pci bus */ atiixp_lock(sc); value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, ATI_REG_CMD_POWERDOWN); pci_set_powerstate(dev, PCI_POWERSTATE_D3); atiixp_unlock(sc); return (0); } static int atiixp_pci_resume(device_t dev) { struct atiixp_info *sc = pcm_getdevinfo(dev); atiixp_lock(sc); /* power up pci bus */ pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_io(dev, SYS_RES_MEMORY); pci_enable_busmaster(dev); /* reset / power up aclink */ atiixp_reset_aclink(sc); atiixp_unlock(sc); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } /* * Resume channel activities. Reset channel format regardless * of its previous state. */ if (sc->pch.channel != NULL) { if (sc->pch.fmt != 0) atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); if (sc->pch.active != 0) atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); } if (sc->rch.channel != NULL) { if (sc->rch.fmt != 0) atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); if (sc->rch.active != 0) atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); } /* enable interrupts */ atiixp_lock(sc); if (sc->polling == 0) atiixp_enable_interrupts(sc); atiixp_unlock(sc); return (0); } static device_method_t atiixp_methods[] = { DEVMETHOD(device_probe, atiixp_pci_probe), DEVMETHOD(device_attach, atiixp_pci_attach), DEVMETHOD(device_detach, atiixp_pci_detach), DEVMETHOD(device_suspend, atiixp_pci_suspend), DEVMETHOD(device_resume, atiixp_pci_resume), { 0, 0 } }; static driver_t atiixp_driver = { "pcm", atiixp_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_atiixp, pci, atiixp_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_atiixp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_atiixp, 1); Index: head/sys/dev/sound/pci/aureal.c =================================================================== --- head/sys/dev/sound/pci/aureal.c (revision 170520) +++ head/sys/dev/sound/pci/aureal.c (revision 170521) @@ -1,688 +1,688 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* PCI IDs of supported chips */ #define AU8820_PCI_ID 0x000112eb /* channel interface */ static u_int32_t au_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps au_playcaps = {4000, 48000, au_playfmt, 0}; static u_int32_t au_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps au_reccaps = {4000, 48000, au_recfmt, 0}; /* -------------------------------------------------------------------- */ struct au_info; struct au_chinfo { struct au_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; }; struct au_info { int unit; bus_space_tag_t st[3]; bus_space_handle_t sh[3]; bus_dma_tag_t parent_dmat; struct mtx *lock; u_int32_t x[32], y[128]; char z[128]; u_int32_t routes[4], interrupts; struct au_chinfo pch; }; static int au_init(device_t dev, struct au_info *au); static void au_intr(void *); /* -------------------------------------------------------------------- */ static u_int32_t au_rd(struct au_info *au, int mapno, int regno, int size) { switch(size) { case 1: return bus_space_read_1(au->st[mapno], au->sh[mapno], regno); case 2: return bus_space_read_2(au->st[mapno], au->sh[mapno], regno); case 4: return bus_space_read_4(au->st[mapno], au->sh[mapno], regno); default: return 0xffffffff; } } static void au_wr(struct au_info *au, int mapno, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(au->st[mapno], au->sh[mapno], regno, data); break; case 2: bus_space_write_2(au->st[mapno], au->sh[mapno], regno, data); break; case 4: bus_space_write_4(au->st[mapno], au->sh[mapno], regno, data); break; } } /* -------------------------------------------------------------------- */ static int au_rdcd(kobj_t obj, void *arg, int regno) { struct au_info *au = (struct au_info *)arg; int i=0, j=0; regno<<=16; au_wr(au, 0, AU_REG_CODECIO, regno, 4); while (j<50) { i=au_rd(au, 0, AU_REG_CODECIO, 4); if ((i & 0x00ff0000) == (regno | 0x00800000)) break; DELAY(j * 200 + 2000); j++; } if (j==50) printf("pcm%d: codec timeout reading register %x (%x)\n", au->unit, (regno & AU_CDC_REGMASK)>>16, i); return i & AU_CDC_DATAMASK; } static int au_wrcd(kobj_t obj, void *arg, int regno, u_int32_t data) { struct au_info *au = (struct au_info *)arg; int i, j, tries; i=j=tries=0; do { while (j<50 && (i & AU_CDC_WROK) == 0) { i=au_rd(au, 0, AU_REG_CODECST, 4); DELAY(2000); j++; } if (j==50) printf("codec timeout during write of register %x, data %x\n", regno, data); au_wr(au, 0, AU_REG_CODECIO, (regno<<16) | AU_CDC_REGSET | data, 4); /* DELAY(20000); i=au_rdcd(au, regno); */ tries++; } while (0); /* (i != data && tries < 3); */ /* if (tries == 3) printf("giving up writing 0x%4x to codec reg %2x\n", data, regno); */ return 0; } static kobj_method_t au_ac97_methods[] = { KOBJMETHOD(ac97_read, au_rdcd), KOBJMETHOD(ac97_write, au_wrcd), { 0, 0 } }; AC97_DECLARE(au_ac97); /* -------------------------------------------------------------------- */ static void au_setbit(u_int32_t *p, char bit, u_int32_t value) { p += bit >> 5; bit &= 0x1f; *p &= ~ (1 << bit); *p |= (value << bit); } static void au_addroute(struct au_info *au, int a, int b, int route) { int j = 0x1099c+(a<<2); if (au->x[a] != a+0x67) j = AU_REG_RTBASE+(au->x[a]<<2); au_wr(au, 0, AU_REG_RTBASE+(route<<2), 0xffffffff, 4); au_wr(au, 0, j, route | (b<<7), 4); au->y[route]=au->x[a]; au->x[a]=route; au->z[route]=a & 0x000000ff; au_setbit(au->routes, route, 1); } static void au_delroute(struct au_info *au, int route) { int i; int j=au->z[route]; au_setbit(au->routes, route, 0); au->z[route]=0x1f; i=au_rd(au, 0, AU_REG_RTBASE+(route<<2), 4); au_wr(au, 0, AU_REG_RTBASE+(au->y[route]<<2), i, 4); au->y[i & 0x7f]=au->y[route]; au_wr(au, 0, AU_REG_RTBASE+(route<<2), 0xfffffffe, 4); if (au->x[j] == route) au->x[j]=au->y[route]; au->y[route]=0x7f; } static void au_encodec(struct au_info *au, char channel) { au_wr(au, 0, AU_REG_CODECEN, au_rd(au, 0, AU_REG_CODECEN, 4) | (1 << (channel + 8)), 4); } static void au_clrfifo(struct au_info *au, u_int32_t c) { u_int32_t i; for (i=0; i<32; i++) au_wr(au, 0, AU_REG_FIFOBASE+(c<<7)+(i<<2), 0, 4); } static void au_setadb(struct au_info *au, u_int32_t c, u_int32_t enable) { int x; x = au_rd(au, 0, AU_REG_ADB, 4); x &= ~(1 << c); x |= (enable << c); au_wr(au, 0, AU_REG_ADB, x, 4); } static void au_prepareoutput(struct au_chinfo *ch, u_int32_t format) { struct au_info *au = ch->parent; int i, stereo = (format & AFMT_STEREO)? 1 : 0; u_int32_t baseaddr = sndbuf_getbufaddr(ch->buffer); au_wr(au, 0, 0x1061c, 0, 4); au_wr(au, 0, 0x10620, 0, 4); au_wr(au, 0, 0x10624, 0, 4); switch(format & ~AFMT_STEREO) { case 1: i=0xb000; break; case 2: i=0xf000; break; case 8: i=0x7000; break; case 16: i=0x23000; break; default: i=0x3000; } au_wr(au, 0, 0x10200, baseaddr, 4); au_wr(au, 0, 0x10204, baseaddr+0x1000, 4); au_wr(au, 0, 0x10208, baseaddr+0x2000, 4); au_wr(au, 0, 0x1020c, baseaddr+0x3000, 4); au_wr(au, 0, 0x10400, 0xdeffffff, 4); au_wr(au, 0, 0x10404, 0xfcffffff, 4); au_wr(au, 0, 0x10580, i, 4); au_wr(au, 0, 0x10210, baseaddr, 4); au_wr(au, 0, 0x10214, baseaddr+0x1000, 4); au_wr(au, 0, 0x10218, baseaddr+0x2000, 4); au_wr(au, 0, 0x1021c, baseaddr+0x3000, 4); au_wr(au, 0, 0x10408, 0x00fff000 | 0x56000000 | 0x00000fff, 4); au_wr(au, 0, 0x1040c, 0x00fff000 | 0x74000000 | 0x00000fff, 4); au_wr(au, 0, 0x10584, i, 4); au_wr(au, 0, 0x0f800, stereo? 0x00030032 : 0x00030030, 4); au_wr(au, 0, 0x0f804, stereo? 0x00030032 : 0x00030030, 4); au_addroute(au, 0x11, 0, 0x58); au_addroute(au, 0x11, stereo? 0 : 1, 0x59); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * auchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct au_info *au = devinfo; struct au_chinfo *ch = (dir == PCMDIR_PLAY)? &au->pch : NULL; ch->parent = au; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, au->parent_dmat, 0, AU_BUFFSIZE) != 0) return NULL; return ch; } static int auchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct au_chinfo *ch = data; if (ch->dir == PCMDIR_PLAY) au_prepareoutput(ch, format); return 0; } static int auchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct au_chinfo *ch = data; if (ch->dir == PCMDIR_PLAY) { } else { } return speed; } static int auchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return blocksize; } static int auchan_trigger(kobj_t obj, void *data, int go) { struct au_chinfo *ch = data; struct au_info *au = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (ch->dir == PCMDIR_PLAY) { au_setadb(au, 0x11, (go)? 1 : 0); - if (!go) { + if (go != PCMTRIG_START) { au_wr(au, 0, 0xf800, 0, 4); au_wr(au, 0, 0xf804, 0, 4); au_delroute(au, 0x58); au_delroute(au, 0x59); } } else { } return 0; } static int auchan_getptr(kobj_t obj, void *data) { struct au_chinfo *ch = data; struct au_info *au = ch->parent; if (ch->dir == PCMDIR_PLAY) { return au_rd(au, 0, AU_REG_UNK2, 4) & (AU_BUFFSIZE-1); } else { return 0; } } static struct pcmchan_caps * auchan_getcaps(kobj_t obj, void *data) { struct au_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &au_playcaps : &au_reccaps; } static kobj_method_t auchan_methods[] = { KOBJMETHOD(channel_init, auchan_init), KOBJMETHOD(channel_setformat, auchan_setformat), KOBJMETHOD(channel_setspeed, auchan_setspeed), KOBJMETHOD(channel_setblocksize, auchan_setblocksize), KOBJMETHOD(channel_trigger, auchan_trigger), KOBJMETHOD(channel_getptr, auchan_getptr), KOBJMETHOD(channel_getcaps, auchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(auchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void au_intr (void *p) { struct au_info *au = p; u_int32_t intsrc, i; au->interrupts++; intsrc=au_rd(au, 0, AU_REG_IRQSRC, 4); printf("pcm%d: interrupt with src %x\n", au->unit, intsrc); if (intsrc & AU_IRQ_FATAL) printf("pcm%d: fatal error irq\n", au->unit); if (intsrc & AU_IRQ_PARITY) printf("pcm%d: parity error irq\n", au->unit); if (intsrc & AU_IRQ_UNKNOWN) { (void)au_rd(au, 0, AU_REG_UNK1, 4); au_wr(au, 0, AU_REG_UNK1, 0, 4); au_wr(au, 0, AU_REG_UNK1, 0x10000, 4); } if (intsrc & AU_IRQ_PCMOUT) { i=au_rd(au, 0, AU_REG_UNK2, 4) & (AU_BUFFSIZE-1); chn_intr(au->pch.channel); (void)au_rd(au, 0, AU_REG_UNK3, 4); (void)au_rd(au, 0, AU_REG_UNK4, 4); (void)au_rd(au, 0, AU_REG_UNK5, 4); } /* don't support midi if (intsrc & AU_IRQ_MIDI) { i=au_rd(au, 0, 0x11004, 4); j=10; while (i & 0xff) { if (j-- <= 0) break; i=au_rd(au, 0, 0x11000, 4); if ((au->midi_stat & 1) && (au->midi_out)) au->midi_out(au->midi_devno, i); i=au_rd(au, 0, 0x11004); } } */ au_wr(au, 0, AU_REG_IRQSRC, intsrc & 0x7ff, 4); au_rd(au, 0, AU_REG_IRQSRC, 4); } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int au_init(device_t dev, struct au_info *au) { u_int32_t i, j; au_wr(au, 0, AU_REG_IRQGLOB, 0xffffffff, 4); DELAY(100000); /* init codec */ /* cold reset */ for (i=0; i<32; i++) { au_wr(au, 0, AU_REG_CODECCHN+(i<<2), 0, 4); DELAY(10000); } if (1) { au_wr(au, 0, AU_REG_CODECST, 0x8068, 4); DELAY(10000); au_wr(au, 0, AU_REG_CODECST, 0x00e8, 4); DELAY(10000); } else { au_wr(au, 0, AU_REG_CODECST, 0x00a8, 4); DELAY(100000); au_wr(au, 0, AU_REG_CODECST, 0x80a8, 4); DELAY(100000); au_wr(au, 0, AU_REG_CODECST, 0x80e8, 4); DELAY(100000); au_wr(au, 0, AU_REG_CODECST, 0x80a8, 4); DELAY(100000); au_wr(au, 0, AU_REG_CODECST, 0x00a8, 4); DELAY(100000); au_wr(au, 0, AU_REG_CODECST, 0x00e8, 4); DELAY(100000); } /* init */ for (i=0; i<32; i++) { au_wr(au, 0, AU_REG_CODECCHN+(i<<2), 0, 4); DELAY(10000); } au_wr(au, 0, AU_REG_CODECST, 0xe8, 4); DELAY(10000); au_wr(au, 0, AU_REG_CODECEN, 0, 4); /* setup codec */ i=j=0; while (j<100 && (i & AU_CDC_READY)==0) { i=au_rd(au, 0, AU_REG_CODECST, 4); DELAY(1000); j++; } if (j==100) device_printf(dev, "codec not ready, status 0x%x\n", i); /* init adb */ /*au->x5c=0;*/ for (i=0; i<32; i++) au->x[i]=i+0x67; for (i=0; i<128; i++) au->y[i]=0x7f; for (i=0; i<128; i++) au->z[i]=0x1f; au_wr(au, 0, AU_REG_ADB, 0, 4); for (i=0; i<124; i++) au_wr(au, 0, AU_REG_RTBASE+(i<<2), 0xffffffff, 4); /* test */ i=au_rd(au, 0, 0x107c0, 4); if (i!=0xdeadbeef) device_printf(dev, "dma check failed: 0x%x\n", i); /* install mixer */ au_wr(au, 0, AU_REG_IRQGLOB, au_rd(au, 0, AU_REG_IRQGLOB, 4) | AU_IRQ_ENABLE, 4); /* braindead but it's what the oss/linux driver does * for (i=0; i<0x80000000; i++) au_wr(au, 0, i<<2, 0, 4); */ au->routes[0]=au->routes[1]=au->routes[2]=au->routes[3]=0; /*au->x1e4=0;*/ /* attach channel */ au_addroute(au, 0x11, 0x48, 0x02); au_addroute(au, 0x11, 0x49, 0x03); au_encodec(au, 0); au_encodec(au, 1); for (i=0; i<48; i++) au_wr(au, 0, 0xf800+(i<<2), 0x20, 4); for (i=2; i<6; i++) au_wr(au, 0, 0xf800+(i<<2), 0, 4); au_wr(au, 0, 0xf8c0, 0x0843, 4); for (i=0; i<4; i++) au_clrfifo(au, i); return (0); } static int au_testirq(struct au_info *au) { au_wr(au, 0, AU_REG_UNK1, 0x80001000, 4); au_wr(au, 0, AU_REG_IRQEN, 0x00001030, 4); au_wr(au, 0, AU_REG_IRQSRC, 0x000007ff, 4); DELAY(1000000); if (au->interrupts==0) printf("pcm%d: irq test failed\n", au->unit); /* this apparently generates an irq */ return 0; } static int au_pci_probe(device_t dev) { if (pci_get_devid(dev) == AU8820_PCI_ID) { device_set_desc(dev, "Aureal Vortex 8820"); return BUS_PROBE_DEFAULT; } return ENXIO; } static int au_pci_attach(device_t dev) { u_int32_t data; struct au_info *au; int type[10]; int regid[10]; struct resource *reg[10]; int i, j, mapped = 0; int irqid; struct resource *irq = 0; void *ih = 0; struct ac97_info *codec; char status[SND_STATUSLEN]; if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } au->unit = device_get_unit(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); j=0; /* XXX dfr: is this strictly necessary? */ for (i=0; imap[i].ln2size); printf("%s space ", (config_id->map[i].type & PCI_MAPPORT)? "io" : "memory"); printf("at 0x%x...", config_id->map[i].base); } #endif regid[j] = PCIR_BAR(i); type[j] = SYS_RES_MEMORY; reg[j] = bus_alloc_resource_any(dev, type[j], ®id[j], RF_ACTIVE); if (!reg[j]) { type[j] = SYS_RES_IOPORT; reg[j] = bus_alloc_resource_any(dev, type[j], ®id[j], RF_ACTIVE); } if (reg[j]) { au->st[i] = rman_get_bustag(reg[j]); au->sh[i] = rman_get_bushandle(reg[j]); mapped++; } #if 0 if (bootverbose) printf("%s\n", mapped? "ok" : "failed"); #endif if (mapped) j++; if (j == 10) { /* XXX */ device_printf(dev, "too many resources"); goto bad; } } #if 0 if (j < config_id->nummaps) { printf("pcm%d: unable to map a required resource\n", unit); free(au, M_DEVBUF); return; } #endif au_wr(au, 0, AU_REG_IRQEN, 0, 4); irqid = 0; irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &irqid, RF_ACTIVE | RF_SHAREABLE); if (!irq || snd_setup_intr(dev, irq, 0, au_intr, au, &ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (au_testirq(au)) device_printf(dev, "irq test failed\n"); if (au_init(dev, au) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, au, au_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) 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*/AU_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &au->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", (type[0] == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(reg[0]), rman_get_start(irq),PCM_KLDSTRING(snd_aureal)); if (pcm_register(dev, au, 1, 1)) goto bad; /* pcm_addchan(dev, PCMDIR_REC, &au_chantemplate, au); */ pcm_addchan(dev, PCMDIR_PLAY, &auchan_class, au); pcm_setstatus(dev, status); return 0; bad: if (au) free(au, M_DEVBUF); for (i = 0; i < j; i++) bus_release_resource(dev, type[i], regid[i], reg[i]); if (ih) bus_teardown_intr(dev, irq, ih); if (irq) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); return ENXIO; } static device_method_t au_methods[] = { /* Device interface */ DEVMETHOD(device_probe, au_pci_probe), DEVMETHOD(device_attach, au_pci_attach), { 0, 0 } }; static driver_t au_driver = { "pcm", au_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_aureal, pci, au_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_aureal, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_aureal, 1); Index: head/sys/dev/sound/pci/cmi.c =================================================================== --- head/sys/dev/sound/pci/cmi.c (revision 170520) +++ head/sys/dev/sound/pci/cmi.c (revision 170521) @@ -1,1109 +1,1114 @@ /*- * 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. */ #include #include #include #include #include #include #include #include "mixer_if.h" #include "mpufoi_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* Supported chip ID's */ #define CMI8338A_PCI_ID 0x010013f6 #define CMI8338B_PCI_ID 0x010113f6 #define CMI8738_PCI_ID 0x011113f6 #define CMI8738B_PCI_ID 0x011213f6 /* 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[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps cmi_caps = {5512, 48000, cmi_fmt, 0}; /* ------------------------------------------------------------------------- */ /* Register Utilities */ static u_int32_t cmi_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: DEB(printf("cmi_rd: failed 0x%04x %d\n", regno, size)); return 0xFFFFFFFF; } } static void cmi_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->st, sc->sh, regno, data); break; case 2: bus_space_write_2(sc->st, sc->sh, regno, data); break; case 4: bus_space_write_4(sc->st, sc->sh, regno, data); break; } } static void cmi_partial_wr4(struct sc_info *sc, int reg, int shift, u_int32_t mask, u_int32_t val) { u_int32_t r; r = cmi_rd(sc, reg, 4); r &= ~(mask << shift); r |= val << shift; cmi_wr(sc, reg, r, 4); } static void cmi_clr4(struct sc_info *sc, int reg, u_int32_t mask) { u_int32_t r; r = cmi_rd(sc, reg, 4); r &= ~mask; cmi_wr(sc, reg, r, 4); } static void cmi_set4(struct sc_info *sc, int reg, u_int32_t mask) { u_int32_t r; r = cmi_rd(sc, reg, 4); r |= mask; cmi_wr(sc, reg, r, 4); } /* ------------------------------------------------------------------------- */ /* Rate Mapping */ static int cmi_rates[] = {5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000}; #define NUM_CMI_RATES (sizeof(cmi_rates)/sizeof(cmi_rates[0])) /* cmpci_rate_to_regvalue returns sampling freq selector for FCR1 * register - reg order is 5k,11k,22k,44k,8k,16k,32k,48k */ static u_int32_t cmpci_rate_to_regvalue(int rate) { int i, r; for(i = 0; i < NUM_CMI_RATES - 1; i++) { if (rate < ((cmi_rates[i] + cmi_rates[i + 1]) / 2)) { break; } } DEB(printf("cmpci_rate_to_regvalue: %d -> %d\n", rate, cmi_rates[i])); r = ((i >> 1) | (i << 2)) & 0x07; return r; } static int cmpci_regvalue_to_rate(u_int32_t r) { int i; i = ((r << 1) | (r >> 2)) & 0x07; DEB(printf("cmpci_regvalue_to_rate: %d -> %d\n", r, i)); return cmi_rates[i]; } /* ------------------------------------------------------------------------- */ /* ADC/DAC control - there are 2 dma channels on 8738, either can be * playback or capture. We use ch0 for playback and ch1 for capture. */ static void cmi_dma_prog(struct sc_info *sc, struct sc_chinfo *ch, u_int32_t base) { u_int32_t s, i, sz; ch->phys_buf = sndbuf_getbufaddr(ch->buffer); cmi_wr(sc, base, ch->phys_buf, 4); sz = (u_int32_t)sndbuf_getsize(ch->buffer); s = sz / ch->bps - 1; cmi_wr(sc, base + 4, s, 2); i = sz / (ch->bps * CMI_INTR_PER_BUFFER) - 1; cmi_wr(sc, base + 6, i, 2); } static void cmi_ch0_start(struct sc_info *sc, struct sc_chinfo *ch) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA0_BASE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE); cmi_set4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); ch->dma_active = 1; } static u_int32_t cmi_ch0_stop(struct sc_info *sc, struct sc_chinfo *ch) { u_int32_t r = ch->dma_active; cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_RESET); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_RESET); ch->dma_active = 0; return r; } static void cmi_ch1_start(struct sc_info *sc, struct sc_chinfo *ch) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA1_BASE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_ENABLE); /* Enable Interrupts */ cmi_set4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); DEB(printf("cmi_ch1_start: dma prog\n")); ch->dma_active = 1; } static u_int32_t cmi_ch1_stop(struct sc_info *sc, struct sc_chinfo *ch) { u_int32_t r = ch->dma_active; cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_ENABLE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_RESET); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_RESET); ch->dma_active = 0; return r; } static void cmi_spdif_speed(struct sc_info *sc, int speed) { u_int32_t fcr1, lcr, mcr; if (speed >= 44100) { fcr1 = CMPCI_REG_SPDIF0_ENABLE; lcr = CMPCI_REG_XSPDIF_ENABLE; mcr = (speed == 48000) ? CMPCI_REG_W_SPDIF_48L | CMPCI_REG_SPDIF_48K : 0; } else { fcr1 = mcr = lcr = 0; } cmi_partial_wr4(sc, CMPCI_REG_MISC, 0, CMPCI_REG_W_SPDIF_48L | CMPCI_REG_SPDIF_48K, mcr); cmi_partial_wr4(sc, CMPCI_REG_FUNC_1, 0, CMPCI_REG_SPDIF0_ENABLE, fcr1); cmi_partial_wr4(sc, CMPCI_REG_LEGACY_CTRL, 0, CMPCI_REG_XSPDIF_ENABLE, lcr); } /* ------------------------------------------------------------------------- */ /* Channel Interface implementation */ static void * cmichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->bps = 1; ch->fmt = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; ch->buffer = b; ch->dma_active = 0; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("cmichan_init failed\n")); return NULL; } ch->dir = dir; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA0_BASE); } else { cmi_dma_prog(sc, ch, CMPCI_REG_DMA1_BASE); } snd_mtxunlock(sc->lock); return ch; } static int cmichan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t f; if (format & AFMT_S16_LE) { f = CMPCI_REG_FORMAT_16BIT; ch->bps = 2; } else { f = CMPCI_REG_FORMAT_8BIT; ch->bps = 1; } if (format & AFMT_STEREO) { 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 int 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; 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 int 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 int cmichan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t physptr, bufptr, sz; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { physptr = cmi_rd(sc, CMPCI_REG_DMA0_BASE, 4); } else { physptr = cmi_rd(sc, CMPCI_REG_DMA1_BASE, 4); } snd_mtxunlock(sc->lock); sz = sndbuf_getsize(ch->buffer); bufptr = (physptr - ch->phys_buf + sz - ch->bps) % sz; return bufptr; } static void cmi_intr(void *data) { struct sc_info *sc = data; u_int32_t intrstat; u_int32_t toclear; snd_mtxlock(sc->lock); intrstat = cmi_rd(sc, CMPCI_REG_INTR_STATUS, 4); if ((intrstat & CMPCI_REG_ANY_INTR) != 0) { toclear = 0; if (intrstat & CMPCI_REG_CH0_INTR) { toclear |= CMPCI_REG_CH0_INTR_ENABLE; //cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); } if (intrstat & CMPCI_REG_CH1_INTR) { toclear |= CMPCI_REG_CH1_INTR_ENABLE; //cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); } if (toclear) { cmi_clr4(sc, CMPCI_REG_INTR_CTRL, toclear); snd_mtxunlock(sc->lock); /* Signal interrupts to channel */ if (intrstat & CMPCI_REG_CH0_INTR) { chn_intr(sc->pch.channel); } if (intrstat & CMPCI_REG_CH1_INTR) { chn_intr(sc->rch.channel); } snd_mtxlock(sc->lock); cmi_set4(sc, CMPCI_REG_INTR_CTRL, toclear); } } if(sc->mpu_intr) { (sc->mpu_intr)(sc->mpu); } snd_mtxunlock(sc->lock); return; } static struct pcmchan_caps * cmichan_getcaps(kobj_t obj, void *data) { return &cmi_caps; } static kobj_method_t cmichan_methods[] = { KOBJMETHOD(channel_init, cmichan_init), KOBJMETHOD(channel_setformat, cmichan_setformat), KOBJMETHOD(channel_setspeed, cmichan_setspeed), KOBJMETHOD(channel_setblocksize, cmichan_setblocksize), KOBJMETHOD(channel_trigger, cmichan_trigger), KOBJMETHOD(channel_getptr, cmichan_getptr), KOBJMETHOD(channel_getcaps, cmichan_getcaps), { 0, 0 } }; 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 int 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"); #endif /* SND_DYNSYSCTL */ return 0; } /* ------------------------------------------------------------------------- */ static kobj_method_t cmi_mixer_methods[] = { KOBJMETHOD(mixer_init, cmimix_init), KOBJMETHOD(mixer_set, cmimix_set), KOBJMETHOD(mixer_setrecsrc, cmimix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(cmi_mixer); /* * mpu401 functions */ static unsigned char cmi_mread(void *arg, struct sc_info *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(void *arg, struct sc_info *sc, int reg, unsigned char b) { bus_space_write_1(0,0,0x330 + reg , b); } static int cmi_muninit(void *arg, struct sc_info *sc) { snd_mtxlock(sc->lock); sc->mpu_intr = 0; sc->mpu = 0; 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), { 0, 0 } }; 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 = 0; } /* ------------------------------------------------------------------------- */ /* 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; default: return ENXIO; } } static int cmi_attach(device_t dev) { struct sc_info *sc; u_int32_t data; char status[SND_STATUSLEN]; sc = malloc(sizeof(struct sc_info), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_cmi softc"); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); sc->dev = dev; sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "cmi_attach: Cannot allocate bus resource\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); if (0) cmi_midiattach(sc); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, cmi_intr, sc, &sc->ih)) { device_printf(dev, "cmi_attach: Unable to map interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, CMI_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockfunc*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "cmi_attach: Unable to create dma tag\n"); goto bad; } cmi_power(sc, 0); if (cmi_init(sc)) goto bad; if (mixer_init(dev, &cmi_mixer_class, sc)) goto bad; if (pcm_register(dev, sc, 1, 1)) goto bad; cmi_initsys(sc); pcm_addchan(dev, PCMDIR_PLAY, &cmichan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cmichan_class, sc); snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_cmi)); pcm_setstatus(dev, status); DEB(printf("cmi_attach: succeeded\n")); return 0; bad: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); if (sc->lock) snd_mtxfree(sc->lock); if (sc) free(sc, M_DEVBUF); return ENXIO; } static int cmi_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); cmi_uninit(sc); cmi_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if(sc->mpu) mpu401_uninit(sc->mpu); bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); if (sc->mpu_reg) bus_release_resource(dev, SYS_RES_IOPORT, sc->mpu_regid, sc->mpu_reg); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } static int cmi_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = cmi_ch0_stop(sc, &sc->pch); sc->rch.dma_was_active = cmi_ch1_stop(sc, &sc->rch); cmi_power(sc, 3); snd_mtxunlock(sc->lock); return 0; } static int cmi_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); cmi_power(sc, 0); if (cmi_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { cmichan_setspeed(NULL, &sc->pch, sc->pch.spd); cmichan_setformat(NULL, &sc->pch, sc->pch.fmt); cmi_ch0_start(sc, &sc->pch); } if (sc->rch.dma_was_active) { cmichan_setspeed(NULL, &sc->rch, sc->rch.spd); cmichan_setformat(NULL, &sc->rch, sc->rch.fmt); cmi_ch1_start(sc, &sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t cmi_methods[] = { DEVMETHOD(device_probe, cmi_probe), DEVMETHOD(device_attach, cmi_attach), DEVMETHOD(device_detach, cmi_detach), DEVMETHOD(device_resume, cmi_resume), DEVMETHOD(device_suspend, cmi_suspend), { 0, 0 } }; static driver_t cmi_driver = { "pcm", cmi_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_cmi, pci, cmi_driver, pcm_devclass, 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); Index: head/sys/dev/sound/pci/cs4281.c =================================================================== --- head/sys/dev/sound/pci/cs4281.c (revision 170520) +++ head/sys/dev/sound/pci/cs4281.c (revision 170521) @@ -1,981 +1,982 @@ /*- * 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. */ #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #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[] = { AFMT_U8, AFMT_U8 | AFMT_STEREO, AFMT_S8, AFMT_S8 | AFMT_STEREO, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, AFMT_U16_LE, AFMT_U16_LE | AFMT_STEREO, AFMT_S16_BE, AFMT_S16_BE | AFMT_STEREO, AFMT_U16_BE, AFMT_U16_BE | AFMT_STEREO, 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_STEREO & format)) 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_STEREO & format) ? 2 : 1); } /* -------------------------------------------------------------------- */ /* ac97 codec */ static u_int32_t cs4281_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; int codecno; codecno = regno >> 8; 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 0xffffffff; } /* 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 0xffffffff; } return cs4281_rd(sc, CS4281PCI_ACSDA); } static void cs4281_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; int codecno; codecno = regno >> 8; 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"); } } static kobj_method_t cs4281_ac97_methods[] = { KOBJMETHOD(ac97_read, cs4281_rdcd), KOBJMETHOD(ac97_write, cs4281_wrcd), { 0, 0 } }; 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 = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; ch->bps = 1; ch->blksz = sndbuf_getsize(ch->buffer); ch->dma_chan = (dir == PCMDIR_PLAY) ? CS4281_DMA_PLAY : CS4281_DMA_REC; ch->dma_setup = 0; adcdac_go(ch, 0); adcdac_prog(ch); return ch; } static int 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 int 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 int cs4281chan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t dba, dca, ptr; int sz; sz = sndbuf_getsize(ch->buffer); dba = cs4281_rd(sc, CS4281PCI_DBA(ch->dma_chan)); dca = cs4281_rd(sc, CS4281PCI_DCA(ch->dma_chan)); ptr = (dca - dba + sz) % sz; return ptr; } static int cs4281chan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; switch(go) { case PCMTRIG_START: adcdac_prog(ch); adcdac_go(ch, 1); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: adcdac_go(ch, 0); break; default: break; } /* return 0 if ok */ return 0; } static struct pcmchan_caps * cs4281chan_getcaps(kobj_t obj, void *data) { return &cs4281_caps; } static kobj_method_t cs4281chan_methods[] = { KOBJMETHOD(channel_init, cs4281chan_init), KOBJMETHOD(channel_setformat, cs4281chan_setformat), KOBJMETHOD(channel_setspeed, cs4281chan_setspeed), KOBJMETHOD(channel_setblocksize, cs4281chan_setblocksize), KOBJMETHOD(channel_trigger, cs4281chan_trigger), KOBJMETHOD(channel_getptr, cs4281chan_getptr), KOBJMETHOD(channel_getcaps, cs4281chan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(cs4281chan); /* -------------------------------------------------------------------- */ /* ADC/DAC control */ /* adcdac_go enables/disable DMA channel, returns non-zero if DMA was * active before call */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go) { struct sc_info *sc = ch->parent; u_int32_t going; going = !(cs4281_rd(sc, CS4281PCI_DCR(ch->dma_chan)) & CS4281PCI_DCR_MSK); if (go) cs4281_clr4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); else cs4281_set4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); return going; } static void adcdac_prog(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t go; if (!ch->dma_setup) { go = adcdac_go(ch, 0); cs4281_wr(sc, CS4281PCI_DBA(ch->dma_chan), sndbuf_getbufaddr(ch->buffer)); cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan), sndbuf_getsize(ch->buffer) / ch->bps - 1); ch->dma_setup = 1; adcdac_go(ch, go); } } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void cs4281_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t hisr; hisr = cs4281_rd(sc, CS4281PCI_HISR); if (hisr == 0) return; if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_PLAY)) { chn_intr(sc->pch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_PLAY)); /* Clear interrupt */ } if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_REC)) { chn_intr(sc->rch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_REC)); /* Clear interrupt */ } /* Signal End-of-Interrupt */ cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); } /* -------------------------------------------------------------------- */ /* power management related */ static int cs4281_power(struct sc_info *sc, int state) { switch (state) { case 0: /* Permit r/w access to all BA0 registers */ cs4281_wr(sc, CS4281PCI_CWPR, CS4281PCI_CWPR_MAGIC); /* Power on */ cs4281_clr4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); break; case 3: /* Power off card and codec */ cs4281_set4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); cs4281_clr4(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); break; } DEB(printf("cs4281_power %d -> %d\n", sc->power, state)); sc->power = state; return 0; } static int cs4281_init(struct sc_info *sc) { u_int32_t i, v; /* (0) Blast clock register and serial port */ cs4281_wr(sc, CS4281PCI_CLKCR1, 0); cs4281_wr(sc, CS4281PCI_SERMC, 0); /* (1) Make ESYN 0 to turn sync pulse on AC97 link */ cs4281_wr(sc, CS4281PCI_ACCTL, 0); DELAY(50); /* (2) Effect Reset */ cs4281_wr(sc, CS4281PCI_SPMC, 0); DELAY(100); cs4281_wr(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); /* Wait 50ms for ABITCLK to become stable */ DELAY(50000); /* (3) Enable Sound System Clocks */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP); DELAY(50000); /* Wait for PLL to stabilize */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP | CS4281PCI_CLKCR1_SWCE); /* (4) Power Up - this combination is essential. */ cs4281_set4(sc, CS4281PCI_SSPM, CS4281PCI_SSPM_ACLEN | CS4281PCI_SSPM_PSRCEN | CS4281PCI_SSPM_CSRCEN | CS4281PCI_SSPM_MIXEN); /* (5) Wait for clock stabilization */ if (cs4281_waitset(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLRDY, 250) == 0) { device_printf(sc->dev, "Clock stabilization failed\n"); return -1; } /* (6) Enable ASYNC generation. */ cs4281_wr(sc, CS4281PCI_ACCTL,CS4281PCI_ACCTL_ESYN); /* Wait to allow AC97 to start generating clock bit */ DELAY(50000); /* Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (7) Wait for AC97 ready signal */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_CRDY, 250) == 0) { device_printf(sc->dev, "codec did not avail\n"); return -1; } /* (8) Assert valid frame signal to begin sending commands to * AC97 codec */ cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_ESYN); /* (9) Wait for codec calibration */ for(i = 0 ; i < 1000; i++) { DELAY(10000); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if ((v & 0x0f) == 0x0f) { break; } } if (i == 1000) { device_printf(sc->dev, "codec failed to calibrate\n"); return -1; } /* (10) Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (11) Wait for valid data to arrive */ if (cs4281_waitset(sc, CS4281PCI_ACISV, CS4281PCI_ACISV_ISV(3) | CS4281PCI_ACISV_ISV(4), 10000) == 0) { device_printf(sc->dev, "cs4281 never got valid data\n"); return -1; } /* (12) Start digital data transfer of audio data to codec */ cs4281_wr(sc, CS4281PCI_ACOSV, CS4281PCI_ACOSV_SLV(3) | CS4281PCI_ACOSV_SLV(4)); /* Set Master and headphone to max */ cs4281_wrcd(0, sc, AC97_MIX_AUXOUT, 0); cs4281_wrcd(0, sc, AC97_MIX_MASTER, 0); /* Power on the DAC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfdff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until DAC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x02) break; } /* Power on the ADC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfeff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until ADC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x01) break; } /* FIFO configuration (driver is DMA orientated, implicit FIFO) */ /* Play FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(0); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v | CS4281PCI_FCR_FEN); /* Record FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_REC_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(CS4281_FIFO_SIZE + 1); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_PSH); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_FEN); /* Match AC97 slots to FIFOs */ v = CS4281PCI_SRCSA_PLSS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_SRCSA_PRSS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_SRCSA_CLSS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_SRCSA_CRSS(CS4281PCI_RPCM_REC_SLOT); cs4281_wr(sc, CS4281PCI_SRCSA, v); /* Set Auto-Initialize and set directions */ cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_PLAY), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_PLAY); cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_REC), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_REC); /* Enable half and empty buffer interrupts keeping DMA paused */ cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_PLAY), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_REC), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); /* Enable Interrupts */ cs4281_clr4(sc, CS4281PCI_HIMR, CS4281PCI_HIMR_DMAI | CS4281PCI_HIMR_DMA(CS4281_DMA_PLAY) | CS4281PCI_HIMR_DMA(CS4281_DMA_REC)); /* Set playback volume */ cs4281_wr(sc, CS4281PCI_PPLVC, 7); cs4281_wr(sc, CS4281PCI_PPRVC, 7); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int cs4281_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case CS4281_PCI_ID: s = "Crystal Semiconductor CS4281"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } static int cs4281_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; u_int32_t data; char status[SND_STATUSLEN]; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->dev = dev; sc->type = pci_get_devid(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); #if __FreeBSD_version > 500000 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); } #else data = pci_read_config(dev, CS4281PCI_PMCS_OFFSET, 4); if (data & CS4281PCI_PMCS_PS_MASK) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", data & CS4281PCI_PMCS_PS_MASK); pci_write_config(dev, CS4281PCI_PMCS_OFFSET, data & ~CS4281PCI_PMCS_PS_MASK, 4); } #endif sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, 0, ~0, CS4281PCI_BA0_SIZE, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, 0, ~0, CS4281PCI_BA0_SIZE, 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(dev, SYS_RES_MEMORY, &sc->memid, 0, ~0, CS4281PCI_BA1_SIZE, 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*/busdma_lock_mutex, /*lockarg*/&Giant, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* power up */ cs4281_power(sc, 0); /* init chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, cs4281_ac97); if (codec == NULL) goto bad; mixer_init(dev, ac97_getmixerclass(), codec); if (pcm_register(dev, sc, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &cs4281chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cs4281chan_class, sc); snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", (sc->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_cs4281)); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->mem) bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return ENXIO; } static int cs4281_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* power off */ cs4281_power(sc, 3); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return 0; } static int cs4281_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); sc->rch.dma_active = adcdac_go(&sc->rch, 0); sc->pch.dma_active = adcdac_go(&sc->pch, 0); cs4281_power(sc, 3); return 0; } static int cs4281_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* power up */ cs4281_power(sc, 0); /* initialize chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } /* restore mixer state */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restore chip state */ cs4281chan_setspeed(NULL, &sc->rch, sc->rch.spd); cs4281chan_setblocksize(NULL, &sc->rch, sc->rch.blksz); cs4281chan_setformat(NULL, &sc->rch, sc->rch.fmt); adcdac_go(&sc->rch, sc->rch.dma_active); cs4281chan_setspeed(NULL, &sc->pch, sc->pch.spd); cs4281chan_setblocksize(NULL, &sc->pch, sc->pch.blksz); cs4281chan_setformat(NULL, &sc->pch, sc->pch.fmt); adcdac_go(&sc->pch, sc->pch.dma_active); return 0; } static device_method_t cs4281_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cs4281_pci_probe), DEVMETHOD(device_attach, cs4281_pci_attach), DEVMETHOD(device_detach, cs4281_pci_detach), DEVMETHOD(device_suspend, cs4281_pci_suspend), DEVMETHOD(device_resume, cs4281_pci_resume), { 0, 0 } }; static driver_t cs4281_driver = { "pcm", cs4281_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_cs4281, pci, cs4281_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_cs4281, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_cs4281, 1); Index: head/sys/dev/sound/pci/csapcm.c =================================================================== --- head/sys/dev/sound/pci/csapcm.c (revision 170520) +++ head/sys/dev/sound/pci/csapcm.c (revision 170521) @@ -1,1040 +1,1040 @@ /*- * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in * cwcealdr1.zip, the sample sources by Crystal Semiconductor. * Copyright (c) 1996-1998 Crystal Semiconductor Corp. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* Buffer size on dma transfer. Fixed for CS416x. */ #define CS461x_BUFFSIZE (4 * 1024) #define GOF_PER_SEC 200 /* device private data */ struct csa_info; struct csa_chinfo { struct csa_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; u_int32_t fmt, spd; int dma; }; struct csa_info { csa_res res; /* resource */ void *ih; /* Interrupt cookie */ bus_dma_tag_t parent_dmat; /* DMA tag */ struct csa_bridgeinfo *binfo; /* The state of the parent. */ struct csa_card *card; int active; /* Contents of board's registers */ u_long pfie; u_long pctl; u_long cctl; struct csa_chinfo pch, rch; u_int32_t ac97[CS461x_AC97_NUMBER_RESTORE_REGS]; u_int32_t ac97_powerdown; u_int32_t ac97_general_purpose; }; /* -------------------------------------------------------------------- */ /* prototypes */ static int csa_init(struct csa_info *); static void csa_intr(void *); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate); static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate); static void csa_startplaydma(struct csa_info *csa); static void csa_startcapturedma(struct csa_info *csa); static void csa_stopplaydma(struct csa_info *csa); static void csa_stopcapturedma(struct csa_info *csa); static int csa_startdsp(csa_res *resp); static int csa_stopdsp(csa_res *resp); static int csa_allocres(struct csa_info *scp, device_t dev); static void csa_releaseres(struct csa_info *scp, device_t dev); static void csa_ac97_suspend(struct csa_info *csa); static void csa_ac97_resume(struct csa_info *csa); static u_int32_t csa_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_S16_BE, AFMT_STEREO | AFMT_S16_BE, 0 }; static struct pcmchan_caps csa_playcaps = {8000, 48000, csa_playfmt, 0}; static u_int32_t csa_recfmt[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps csa_reccaps = {11025, 48000, csa_recfmt, 0}; /* -------------------------------------------------------------------- */ static int csa_active(struct csa_info *csa, int run) { int old; old = csa->active; csa->active += run; if ((csa->active > 1) || (csa->active < -1)) csa->active = 0; if (csa->card->active) return (csa->card->active(!(csa->active && old))); return 0; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int csa_rdcd(kobj_t obj, void *devinfo, int regno) { u_int32_t data; struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data)) data = 0; csa_active(csa, -1); return data; } static int csa_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data); csa_active(csa, -1); return 0; } static kobj_method_t csa_ac97_methods[] = { KOBJMETHOD(ac97_read, csa_rdcd), KOBJMETHOD(ac97_write, csa_wrcd), { 0, 0 } }; AC97_DECLARE(csa_ac97); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate) { u_long ulTemp1, ulTemp2; u_long ulPhiIncr; u_long ulCorrectionPerGOF, ulCorrectionPerSec; u_long ulOutRate; ulOutRate = 48000; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * * i.e. * * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) */ ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; /* * Fill in the SampleRateConverter control block. */ csa_writemem(resp, BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_PPI, ulPhiIncr); } static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate) { u_long ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2; u_long ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay; u_long dwFrameGroupLength, dwCnt; u_long ulInRate; ulInRate = 48000; /* * We can only decimate by up to a factor of 1/9th the hardware rate. * Return an error if an attempt is made to stray outside that limit. */ if((ulOutRate * 9) < ulInRate) return; /* * We can not capture at at rate greater than the Input Rate (48000). * Return an error if an attempt is made to stray outside that limit. */ if(ulOutRate > ulInRate) return; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulCoeffIncr = -floor((Fs,out * 2^23) / Fs,in) * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * ulInitialDelay = ceil((24 * Fs,in) / Fs,out) * * i.e. * * ulCoeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) * ulInitialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) */ ulTemp1 = ulOutRate << 16; ulCoeffIncr = ulTemp1 / ulInRate; ulTemp1 -= ulCoeffIncr * ulInRate; ulTemp1 <<= 7; ulCoeffIncr <<= 7; ulCoeffIncr += ulTemp1 / ulInRate; ulCoeffIncr ^= 0xFFFFFFFF; ulCoeffIncr++; ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate; /* * Fill in the VariDecimate control block. */ csa_writemem(resp, BA1_CSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_CCI, ulCoeffIncr); csa_writemem(resp, BA1_CD, (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); csa_writemem(resp, BA1_CPI, ulPhiIncr); /* * Figure out the frame group length for the write back task. Basically, * this is just the factors of 24000 (2^6*3*5^3) that are not present in * the output sample rate. */ dwFrameGroupLength = 1; for(dwCnt = 2; dwCnt <= 64; dwCnt *= 2) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 2; } } if(((ulOutRate / 3) * 3) != ulOutRate) { dwFrameGroupLength *= 3; } for(dwCnt = 5; dwCnt <= 125; dwCnt *= 5) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 5; } } /* * Fill in the WriteBack control block. */ csa_writemem(resp, BA1_CFG1, dwFrameGroupLength); csa_writemem(resp, BA1_CFG2, (0x00800000 | dwFrameGroupLength)); csa_writemem(resp, BA1_CCST, 0x0000FFFF); csa_writemem(resp, BA1_CSPB, ((65536 * ulOutRate) / 24000)); csa_writemem(resp, (BA1_CSPB + 4), 0x0000FFFF); } static void csa_startplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); ul &= 0x0000ffff; csa_writemem(resp, BA1_PCTL, ul | csa->pctl); csa_writemem(resp, BA1_PVOL, 0x80008000); csa->pch.dma = 1; } } static void csa_startcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); ul &= 0xffff0000; csa_writemem(resp, BA1_CCTL, ul | csa->cctl); csa_writemem(resp, BA1_CVOL, 0x80008000); csa->rch.dma = 1; } } static void csa_stopplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); csa->pctl = ul & 0xffff0000; csa_writemem(resp, BA1_PCTL, ul & 0x0000ffff); csa_writemem(resp, BA1_PVOL, 0xffffffff); csa->pch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not capturing. */ if (!csa->rch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static void csa_stopcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); csa->cctl = ul & 0x0000ffff; csa_writemem(resp, BA1_CCTL, ul & 0xffff0000); csa_writemem(resp, BA1_CVOL, 0xffffffff); csa->rch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not playing. */ if (!csa->pch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static int csa_startdsp(csa_res *resp) { int i; u_long ul; /* * Set the frame timer to reflect the number of cycles per frame. */ csa_writemem(resp, BA1_FRMT, 0xadf); /* * Turn on the run, run at frame, and DMA enable bits in the local copy of * the SP control register. */ csa_writemem(resp, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); /* * Wait until the run at frame bit resets itself in the SP control * register. */ ul = 0; for (i = 0 ; i < 25 ; i++) { /* * Wait a little bit, so we don't issue PCI reads too frequently. */ DELAY(50); /* * Fetch the current value of the SP status register. */ ul = csa_readmem(resp, BA1_SPCR); /* * If the run at frame bit has reset, then stop waiting. */ if((ul & SPCR_RUNFR) == 0) break; } /* * If the run at frame bit never reset, then return an error. */ if((ul & SPCR_RUNFR) != 0) return (EAGAIN); return (0); } static int csa_stopdsp(csa_res *resp) { /* * Turn off the run, run at frame, and DMA enable bits in * the local copy of the SP control register. */ csa_writemem(resp, BA1_SPCR, 0); return (0); } static int csa_setupchan(struct csa_chinfo *ch) { struct csa_info *csa = ch->parent; csa_res *resp = &csa->res; u_long pdtc, tmp; if (ch->dir == PCMDIR_PLAY) { /* direction */ csa_writemem(resp, BA1_PBA, sndbuf_getbufaddr(ch->buffer)); /* format */ csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f; if (!(ch->fmt & AFMT_SIGNED)) csa->pfie |= 0x8000; if (ch->fmt & AFMT_BIGENDIAN) csa->pfie |= 0x4000; if (!(ch->fmt & AFMT_STEREO)) csa->pfie |= 0x2000; if (ch->fmt & AFMT_8BIT) csa->pfie |= 0x1000; csa_writemem(resp, BA1_PFIE, csa->pfie); tmp = 4; if (ch->fmt & AFMT_16BIT) tmp <<= 1; if (ch->fmt & AFMT_STEREO) tmp <<= 1; tmp--; pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000001ff; pdtc |= tmp; csa_writemem(resp, BA1_PDTC, pdtc); /* rate */ csa_setplaysamplerate(resp, ch->spd); } else if (ch->dir == PCMDIR_REC) { /* direction */ csa_writemem(resp, BA1_CBA, sndbuf_getbufaddr(ch->buffer)); /* format */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* rate */ csa_setcapturesamplerate(resp, ch->spd); } return 0; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * csachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct csa_info *csa = devinfo; struct csa_chinfo *ch = (dir == PCMDIR_PLAY)? &csa->pch : &csa->rch; ch->parent = csa; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, csa->parent_dmat, 0, CS461x_BUFFSIZE) != 0) return NULL; return ch; } static int csachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct csa_chinfo *ch = data; ch->fmt = format; return 0; } static int csachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct csa_chinfo *ch = data; ch->spd = speed; return ch->spd; /* XXX calc real speed */ } static int csachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return CS461x_BUFFSIZE / 2; } static int csachan_trigger(kobj_t obj, void *data, int go) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { csa_active(csa, 1); csa_setupchan(ch); if (ch->dir == PCMDIR_PLAY) csa_startplaydma(csa); else csa_startcapturedma(csa); } else { if (ch->dir == PCMDIR_PLAY) csa_stopplaydma(csa); else csa_stopcapturedma(csa); csa_active(csa, -1); } return 0; } static int csachan_getptr(kobj_t obj, void *data) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; csa_res *resp; int ptr; resp = &csa->res; if (ch->dir == PCMDIR_PLAY) { ptr = csa_readmem(resp, BA1_PBA) - sndbuf_getbufaddr(ch->buffer); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } else { ptr = csa_readmem(resp, BA1_CBA) - sndbuf_getbufaddr(ch->buffer); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } return (ptr); } static struct pcmchan_caps * csachan_getcaps(kobj_t obj, void *data) { struct csa_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps; } static kobj_method_t csachan_methods[] = { KOBJMETHOD(channel_init, csachan_init), KOBJMETHOD(channel_setformat, csachan_setformat), KOBJMETHOD(channel_setspeed, csachan_setspeed), KOBJMETHOD(channel_setblocksize, csachan_setblocksize), KOBJMETHOD(channel_trigger, csachan_trigger), KOBJMETHOD(channel_getptr, csachan_getptr), KOBJMETHOD(channel_getcaps, csachan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(csachan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void csa_intr(void *p) { struct csa_info *csa = p; if ((csa->binfo->hisr & HISR_VC0) != 0) chn_intr(csa->pch.channel); if ((csa->binfo->hisr & HISR_VC1) != 0) chn_intr(csa->rch.channel); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int csa_init(struct csa_info *csa) { csa_res *resp; resp = &csa->res; csa->pfie = 0; csa_stopplaydma(csa); csa_stopcapturedma(csa); if (csa_startdsp(resp)) return (1); /* Crank up the power on the DAC and ADC. */ csa_setplaysamplerate(resp, 8000); csa_setcapturesamplerate(resp, 8000); /* Set defaults */ csa_writeio(resp, BA0_EGPIODR, EGPIODR_GPOE0); csa_writeio(resp, BA0_EGPIOPTR, EGPIOPTR_GPPT0); /* Power up amplifier */ csa_writeio(resp, BA0_EGPIODR, csa_readio(resp, BA0_EGPIODR) | EGPIODR_GPOE2); csa_writeio(resp, BA0_EGPIOPTR, csa_readio(resp, BA0_EGPIOPTR) | EGPIOPTR_GPPT2); return 0; } /* Allocates resources. */ static int csa_allocres(struct csa_info *csa, device_t dev) { csa_res *resp; resp = &csa->res; if (resp->io == NULL) { resp->io = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->io_rid, RF_ACTIVE); if (resp->io == NULL) return (1); } if (resp->mem == NULL) { resp->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->mem_rid, RF_ACTIVE); if (resp->mem == NULL) return (1); } if (resp->irq == NULL) { resp->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &resp->irq_rid, RF_ACTIVE | RF_SHAREABLE); if (resp->irq == NULL) return (1); } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/CS461x_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &csa->parent_dmat) != 0) return (1); return (0); } /* Releases resources. */ static void csa_releaseres(struct csa_info *csa, device_t dev) { csa_res *resp; KASSERT(csa != NULL, ("called with bogus resource structure")); resp = &csa->res; if (resp->irq != NULL) { if (csa->ih) bus_teardown_intr(dev, resp->irq, csa->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); resp->irq = NULL; } if (resp->io != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); resp->io = NULL; } if (resp->mem != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); resp->mem = NULL; } if (csa->parent_dmat != NULL) { bus_dma_tag_destroy(csa->parent_dmat); csa->parent_dmat = NULL; } free(csa, M_DEVBUF); } static int pcmcsa_probe(device_t dev) { char *s; struct sndcard_func *func; /* The parent device has already been probed. */ func = device_get_ivars(dev); if (func == NULL || func->func != SCF_PCM) return (ENXIO); s = "CS461x PCM Audio"; device_set_desc(dev, s); return (0); } static int pcmcsa_attach(device_t dev) { struct csa_info *csa; csa_res *resp; int unit; char status[SND_STATUSLEN]; struct ac97_info *codec; struct sndcard_func *func; csa = malloc(sizeof(*csa), M_DEVBUF, M_NOWAIT | M_ZERO); if (csa == NULL) return (ENOMEM); unit = device_get_unit(dev); func = device_get_ivars(dev); csa->binfo = func->varinfo; /* * Fake the status of DMA so that the initial value of * PCTL and CCTL can be stored into csa->pctl and csa->cctl, * respectively. */ csa->pch.dma = csa->rch.dma = 1; csa->active = 0; csa->card = csa->binfo->card; /* Allocate the resources. */ resp = &csa->res; resp->io_rid = PCIR_BAR(0); resp->mem_rid = PCIR_BAR(1); resp->irq_rid = 0; if (csa_allocres(csa, dev)) { csa_releaseres(csa, dev); return (ENXIO); } csa_active(csa, 1); if (csa_init(csa)) { csa_releaseres(csa, dev); return (ENXIO); } codec = AC97_CREATE(dev, csa, csa_ac97); if (codec == NULL) { csa_releaseres(csa, dev); return (ENXIO); } if (csa->card->inv_eapd) ac97_setflags(codec, AC97_F_EAPD_INV); if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } snprintf(status, SND_STATUSLEN, "at irq %ld %s", rman_get_start(resp->irq),PCM_KLDSTRING(snd_csa)); /* Enable interrupt. */ if (snd_setup_intr(dev, resp->irq, 0, csa_intr, csa, &csa->ih)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); csa_active(csa, -1); if (pcm_register(dev, csa, 1, 1)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } pcm_addchan(dev, PCMDIR_REC, &csachan_class, csa); pcm_addchan(dev, PCMDIR_PLAY, &csachan_class, csa); pcm_setstatus(dev, status); return (0); } static int pcmcsa_detach(device_t dev) { int r; struct csa_info *csa; r = pcm_unregister(dev); if (r) return r; csa = pcm_getdevinfo(dev); csa_releaseres(csa, dev); return 0; } static void csa_ac97_suspend(struct csa_info *csa) { int count, i; uint32_t tmp; for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_readcodec(&csa->res, BA0_AC97_RESET + count, &csa->ac97[i]); /* mute the outputs */ csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_HEADPHONE_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME_MONO, 0x8000); csa_writecodec(&csa->res, BA0_AC97_PCM_OUT_VOLUME, 0x8000); /* save the registers that cause pops */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &csa->ac97_powerdown); csa_readcodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, &csa->ac97_general_purpose); /* * And power down everything on the AC97 codec. Well, for now, * only power down the DAC/ADC and MIXER VREFON components. * trouble with removing VREF. */ /* MIXVON */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_MIXVON); /* ADC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_ADC); /* DAC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_DAC); } static void csa_ac97_resume(struct csa_info *csa) { int count, i; /* * First, we restore the state of the general purpose register. This * contains the mic select (mic1 or mic2) and if we restore this after * we restore the mic volume/boost state and mic2 was selected at * suspend time, we will end up with a brief period of time where mic1 * is selected with the volume/boost settings for mic2, causing * acoustic feedback. So we restore the general purpose register * first, thereby getting the correct mic selected before we restore * the mic volume/boost. */ csa_writecodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, csa->ac97_general_purpose); /* * Now, while the outputs are still muted, restore the state of power * on the AC97 part. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, csa->ac97_powerdown); /* * Restore just the first set of registers, from register number * 0x02 to the register number that ulHighestRegToRestore specifies. */ for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_writecodec(&csa->res, BA0_AC97_RESET + count, csa->ac97[i]); } static int pcmcsa_suspend(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* playback interrupt disable */ csa_writemem(resp, BA1_PFIE, (csa_readmem(resp, BA1_PFIE) & ~0x0000f03f) | 0x00000010); /* capture interrupt disable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000011); csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_suspend(csa); csa_resetdsp(resp); csa_stopdsp(resp); /* * Power down the DAC and ADC. For now leave the other areas on. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, 0x300); /* * Power down the PLL. */ csa_writemem(resp, BA0_CLKCR1, 0); /* * Turn off the Processor by turning off the software clock * enable flag in the clock control register. */ csa_writemem(resp, BA0_CLKCR1, csa_readmem(resp, BA0_CLKCR1) & ~CLKCR1_SWCE); csa_active(csa, -1); return 0; } static int pcmcsa_resume(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* cs_hardware_init */ csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_resume(csa); if (csa_startdsp(resp)) return (ENXIO); /* Enable interrupts on the part. */ if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); /* playback interrupt enable */ csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); /* capture interrupt enable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* cs_restart_part */ csa_setupchan(&csa->pch); csa_startplaydma(csa); csa_setupchan(&csa->rch); csa_startcapturedma(csa); csa_active(csa, -1); return 0; } static device_method_t pcmcsa_methods[] = { /* Device interface */ DEVMETHOD(device_probe , pcmcsa_probe ), DEVMETHOD(device_attach, pcmcsa_attach), DEVMETHOD(device_detach, pcmcsa_detach), DEVMETHOD(device_suspend, pcmcsa_suspend), DEVMETHOD(device_resume, pcmcsa_resume), { 0, 0 }, }; static driver_t pcmcsa_driver = { "pcm", pcmcsa_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_csapcm, csa, pcmcsa_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_csapcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_csapcm, snd_csa, 1, 1, 1); MODULE_VERSION(snd_csapcm, 1); Index: head/sys/dev/sound/pci/ds1.c =================================================================== --- head/sys/dev/sound/pci/ds1.c (revision 170520) +++ head/sys/dev/sound/pci/ds1.c (revision 170521) @@ -1,1106 +1,1106 @@ /*- * Copyright (c) 2000 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ #define DS1_CHANS 4 #define DS1_RECPRIMARY 0 #define DS1_IRQHZ ((48000 << 8) / 256) #define DS1_BUFFSIZE 4096 struct pbank { volatile u_int32_t Format; volatile u_int32_t LoopDefault; volatile u_int32_t PgBase; volatile u_int32_t PgLoop; volatile u_int32_t PgLoopEnd; volatile u_int32_t PgLoopFrac; volatile u_int32_t PgDeltaEnd; volatile u_int32_t LpfKEnd; volatile u_int32_t EgGainEnd; volatile u_int32_t LchGainEnd; volatile u_int32_t RchGainEnd; volatile u_int32_t Effect1GainEnd; volatile u_int32_t Effect2GainEnd; volatile u_int32_t Effect3GainEnd; volatile u_int32_t LpfQ; volatile u_int32_t Status; volatile u_int32_t NumOfFrames; volatile u_int32_t LoopCount; volatile u_int32_t PgStart; volatile u_int32_t PgStartFrac; volatile u_int32_t PgDelta; volatile u_int32_t LpfK; volatile u_int32_t EgGain; volatile u_int32_t LchGain; volatile u_int32_t RchGain; volatile u_int32_t Effect1Gain; volatile u_int32_t Effect2Gain; volatile u_int32_t Effect3Gain; volatile u_int32_t LpfD1; volatile u_int32_t LpfD2; }; struct rbank { volatile u_int32_t PgBase; volatile u_int32_t PgLoopEnd; volatile u_int32_t PgStart; volatile u_int32_t NumOfLoops; }; struct sc_info; /* channel registers */ struct sc_pchinfo { int run, spd, dir, fmt; struct snd_dbuf *buffer; struct pcm_channel *channel; volatile struct pbank *lslot, *rslot; int lsnum, rsnum; struct sc_info *parent; }; struct sc_rchinfo { int run, spd, dir, fmt, num; struct snd_dbuf *buffer; struct pcm_channel *channel; volatile struct rbank *slot; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type, rev; u_int32_t cd2id, ctrlbase; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t buffer_dmat, control_dmat; bus_dmamap_t map; struct resource *reg, *irq; int regid, irqid; void *ih; struct mtx *lock; void *regbase; u_int32_t *pbase, pbankbase, pbanksize; volatile struct pbank *pbank[2 * 64]; volatile struct rbank *rbank; int pslotfree, currbank, pchn, rchn; unsigned int bufsz; struct sc_pchinfo pch[DS1_CHANS]; struct sc_rchinfo rch[2]; }; struct { u_int32_t dev, subdev; char *name; u_int32_t *mcode; } ds_devs[] = { {0x00041073, 0, "Yamaha DS-1 (YMF724)", CntrlInst}, {0x000d1073, 0, "Yamaha DS-1E (YMF724F)", CntrlInst1E}, {0x00051073, 0, "Yamaha DS-1? (YMF734)", CntrlInst}, {0x00081073, 0, "Yamaha DS-1? (YMF737)", CntrlInst}, {0x00201073, 0, "Yamaha DS-1? (YMF738)", CntrlInst}, {0x00061073, 0, "Yamaha DS-1? (YMF738_TEG)", CntrlInst}, {0x000a1073, 0x00041073, "Yamaha DS-1 (YMF740)", CntrlInst}, {0x000a1073, 0x000a1073, "Yamaha DS-1 (YMF740B)", CntrlInst}, {0x000a1073, 0x53328086, "Yamaha DS-1 (YMF740I)", CntrlInst}, {0x000a1073, 0, "Yamaha DS-1 (YMF740?)", CntrlInst}, {0x000c1073, 0, "Yamaha DS-1E (YMF740C)", CntrlInst1E}, {0x00101073, 0, "Yamaha DS-1E (YMF744)", CntrlInst1E}, {0x00121073, 0, "Yamaha DS-1E (YMF754)", CntrlInst1E}, {0, 0, NULL, NULL} }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int ds_init(struct sc_info *); static void ds_intr(void *); /* talk to the card */ static u_int32_t ds_rd(struct sc_info *, int, int); static void ds_wr(struct sc_info *, int, u_int32_t, int); /* -------------------------------------------------------------------- */ static u_int32_t ds_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, 0 }; static struct pcmchan_caps ds_reccaps = {4000, 48000, ds_recfmt, 0}; static u_int32_t ds_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, /* AFMT_S16_LE, */ AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps ds_playcaps = {4000, 96000, ds_playfmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t ds_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: return 0xffffffff; } } static void ds_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 wrl(struct sc_info *sc, u_int32_t *ptr, u_int32_t val) { *(volatile u_int32_t *)ptr = val; bus_space_barrier(sc->st, sc->sh, 0, 0, BUS_SPACE_BARRIER_WRITE); } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int ds_cdbusy(struct sc_info *sc, int sec) { int i, reg; reg = sec? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR; i = YDSXG_AC97TIMEOUT; while (i > 0) { if (!(ds_rd(sc, reg, 2) & 0x8000)) return 0; i--; } return ETIMEDOUT; } static u_int32_t ds_initcd(kobj_t obj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t x; x = pci_read_config(sc->dev, PCIR_DSXGCTRL, 1); if (x & 0x03) { pci_write_config(sc->dev, PCIR_DSXGCTRL, x & ~0x03, 1); pci_write_config(sc->dev, PCIR_DSXGCTRL, x | 0x03, 1); pci_write_config(sc->dev, PCIR_DSXGCTRL, x & ~0x03, 1); /* * The YMF740 on some Intel motherboards requires a pretty * hefty delay after this reset for some reason... Otherwise: * "pcm0: ac97 codec init failed" * Maybe this is needed for all YMF740's? * 400ms and 500ms here seem to work, 300ms does not. * * do it for all chips -cg */ DELAY(500000); } return ds_cdbusy(sc, 0)? 0 : 1; } static int ds_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; int sec, cid, i; u_int32_t cmd, reg; sec = regno & 0x100; regno &= 0xff; cid = sec? (sc->cd2id << 8) : 0; reg = sec? YDSXGR_SECSTATUSDATA : YDSXGR_PRISTATUSDATA; if (sec && cid == 0) return 0xffffffff; cmd = YDSXG_AC97READCMD | cid | regno; ds_wr(sc, YDSXGR_AC97CMDADR, cmd, 2); if (ds_cdbusy(sc, sec)) return 0xffffffff; if (sc->type == 11 && sc->rev < 2) for (i = 0; i < 600; i++) ds_rd(sc, reg, 2); return ds_rd(sc, reg, 2); } static int ds_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; int sec, cid; u_int32_t cmd; sec = regno & 0x100; regno &= 0xff; cid = sec? (sc->cd2id << 8) : 0; if (sec && cid == 0) return ENXIO; cmd = YDSXG_AC97WRITECMD | cid | regno; cmd <<= 16; cmd |= data; ds_wr(sc, YDSXGR_AC97CMDDATA, cmd, 4); return ds_cdbusy(sc, sec); } static kobj_method_t ds_ac97_methods[] = { KOBJMETHOD(ac97_init, ds_initcd), KOBJMETHOD(ac97_read, ds_rdcd), KOBJMETHOD(ac97_write, ds_wrcd), { 0, 0 } }; AC97_DECLARE(ds_ac97); /* -------------------------------------------------------------------- */ static void ds_enadsp(struct sc_info *sc, int on) { u_int32_t v, i; v = on? 1 : 0; if (on) { ds_wr(sc, YDSXGR_CONFIG, 0x00000001, 4); } else { if (ds_rd(sc, YDSXGR_CONFIG, 4)) ds_wr(sc, YDSXGR_CONFIG, 0x00000000, 4); i = YDSXG_WORKBITTIMEOUT; while (i > 0) { if (!(ds_rd(sc, YDSXGR_CONFIG, 4) & 0x00000002)) break; i--; } } } static volatile struct pbank * ds_allocpslot(struct sc_info *sc) { int slot; if (sc->pslotfree > 63) return NULL; slot = sc->pslotfree++; return sc->pbank[slot * 2]; } static int ds_initpbank(volatile struct pbank *pb, int ch, int b16, int stereo, u_int32_t rate, bus_addr_t base, u_int32_t len) { u_int32_t lv[] = {1, 1, 0, 0, 0}; u_int32_t rv[] = {1, 0, 1, 0, 0}; u_int32_t e1[] = {0, 0, 0, 0, 0}; u_int32_t e2[] = {1, 0, 0, 1, 0}; u_int32_t e3[] = {1, 0, 0, 0, 1}; int ss, i; u_int32_t delta; struct { int rate, fK, fQ; } speedinfo[] = { { 100, 0x00570000, 0x35280000}, { 2000, 0x06aa0000, 0x34a70000}, { 8000, 0x18b20000, 0x32020000}, {11025, 0x20930000, 0x31770000}, {16000, 0x2b9a0000, 0x31390000}, {22050, 0x35a10000, 0x31c90000}, {32000, 0x3eaa0000, 0x33d00000}, /* {44100, 0x04646000, 0x370a0000}, */ {48000, 0x40000000, 0x40000000}, }; ss = b16? 1 : 0; ss += stereo? 1 : 0; delta = (65536 * rate) / 48000; i = 0; while (i < 7 && speedinfo[i].rate < rate) i++; pb->Format = stereo? 0x00010000 : 0; pb->Format |= b16? 0 : 0x80000000; pb->Format |= (stereo && (ch == 2 || ch == 4))? 0x00000001 : 0; pb->LoopDefault = 0; pb->PgBase = base? base : 0; pb->PgLoop = 0; pb->PgLoopEnd = len >> ss; pb->PgLoopFrac = 0; pb->Status = 0; pb->NumOfFrames = 0; pb->LoopCount = 0; pb->PgStart = 0; pb->PgStartFrac = 0; pb->PgDelta = pb->PgDeltaEnd = delta << 12; pb->LpfQ = speedinfo[i].fQ; pb->LpfK = pb->LpfKEnd = speedinfo[i].fK; pb->LpfD1 = pb->LpfD2 = 0; pb->EgGain = pb->EgGainEnd = 0x40000000; pb->LchGain = pb->LchGainEnd = lv[ch] * 0x40000000; pb->RchGain = pb->RchGainEnd = rv[ch] * 0x40000000; pb->Effect1Gain = pb->Effect1GainEnd = e1[ch] * 0x40000000; pb->Effect2Gain = pb->Effect2GainEnd = e2[ch] * 0x40000000; pb->Effect3Gain = pb->Effect3GainEnd = e3[ch] * 0x40000000; return 0; } static void ds_enapslot(struct sc_info *sc, int slot, int go) { wrl(sc, &sc->pbase[slot + 1], go? (sc->pbankbase + 2 * slot * sc->pbanksize) : 0); /* printf("pbase[%d] = 0x%x\n", slot + 1, go? (sc->pbankbase + 2 * slot * sc->pbanksize) : 0); */ } static void ds_setuppch(struct sc_pchinfo *ch) { int stereo, b16, c, sz; bus_addr_t addr; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; b16 = (ch->fmt & AFMT_16BIT)? 1 : 0; c = stereo? 1 : 0; addr = sndbuf_getbufaddr(ch->buffer); sz = sndbuf_getsize(ch->buffer); ds_initpbank(ch->lslot, c, stereo, b16, ch->spd, addr, sz); ds_initpbank(ch->lslot + 1, c, stereo, b16, ch->spd, addr, sz); ds_initpbank(ch->rslot, 2, stereo, b16, ch->spd, addr, sz); ds_initpbank(ch->rslot + 1, 2, stereo, b16, ch->spd, addr, sz); } static void ds_setuprch(struct sc_rchinfo *ch) { struct sc_info *sc = ch->parent; int stereo, b16, i, sz, pri; u_int32_t x, y; bus_addr_t addr; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; b16 = (ch->fmt & AFMT_16BIT)? 1 : 0; addr = sndbuf_getbufaddr(ch->buffer); sz = sndbuf_getsize(ch->buffer); pri = (ch->num == DS1_RECPRIMARY)? 1 : 0; for (i = 0; i < 2; i++) { ch->slot[i].PgBase = addr; ch->slot[i].PgLoopEnd = sz; ch->slot[i].PgStart = 0; ch->slot[i].NumOfLoops = 0; } x = (b16? 0x00 : 0x01) | (stereo? 0x02 : 0x00); y = (48000 * 4096) / ch->spd; y--; /* printf("pri = %d, x = %d, y = %d\n", pri, x, y); */ ds_wr(sc, pri? YDSXGR_ADCFORMAT : YDSXGR_RECFORMAT, x, 4); ds_wr(sc, pri? YDSXGR_ADCSLOTSR : YDSXGR_RECSLOTSR, y, 4); } /* -------------------------------------------------------------------- */ /* play channel interface */ static void * ds1pchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; KASSERT(dir == PCMDIR_PLAY, ("ds1pchan_init: bad direction")); ch = &sc->pch[sc->pchn++]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->fmt = AFMT_U8; ch->spd = 8000; ch->run = 0; if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, 0, sc->bufsz) != 0) return NULL; else { ch->lsnum = sc->pslotfree; ch->lslot = ds_allocpslot(sc); ch->rsnum = sc->pslotfree; ch->rslot = ds_allocpslot(sc); ds_setuppch(ch); return ch; } } static int ds1pchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_pchinfo *ch = data; ch->fmt = format; return 0; } static int ds1pchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_pchinfo *ch = data; ch->spd = speed; return speed; } static int ds1pchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int drate; /* irq rate is fixed at 187.5hz */ drate = ch->spd * sndbuf_getbps(ch->buffer); blocksize = roundup2((drate << 8) / DS1_IRQHZ, 4); sndbuf_resize(ch->buffer, sc->bufsz / blocksize, blocksize); return blocksize; } /* semantic note: must start at beginning of buffer */ static int ds1pchan_trigger(kobj_t obj, void *data, int go) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int stereo; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; if (go == PCMTRIG_START) { ch->run = 1; ds_setuppch(ch); ds_enapslot(sc, ch->lsnum, 1); ds_enapslot(sc, ch->rsnum, stereo); snd_mtxlock(sc->lock); ds_wr(sc, YDSXGR_MODE, 0x00000003, 4); snd_mtxunlock(sc->lock); } else { ch->run = 0; /* ds_setuppch(ch); */ ds_enapslot(sc, ch->lsnum, 0); ds_enapslot(sc, ch->rsnum, 0); } return 0; } static int ds1pchan_getptr(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; volatile struct pbank *bank; int ss; u_int32_t ptr; ss = (ch->fmt & AFMT_STEREO)? 1 : 0; ss += (ch->fmt & AFMT_16BIT)? 1 : 0; bank = ch->lslot + sc->currbank; /* printf("getptr: %d\n", bank->PgStart << ss); */ ptr = bank->PgStart; ptr <<= ss; return ptr; } static struct pcmchan_caps * ds1pchan_getcaps(kobj_t obj, void *data) { return &ds_playcaps; } static kobj_method_t ds1pchan_methods[] = { KOBJMETHOD(channel_init, ds1pchan_init), KOBJMETHOD(channel_setformat, ds1pchan_setformat), KOBJMETHOD(channel_setspeed, ds1pchan_setspeed), KOBJMETHOD(channel_setblocksize, ds1pchan_setblocksize), KOBJMETHOD(channel_trigger, ds1pchan_trigger), KOBJMETHOD(channel_getptr, ds1pchan_getptr), KOBJMETHOD(channel_getcaps, ds1pchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(ds1pchan); /* -------------------------------------------------------------------- */ /* record channel interface */ static void * ds1rchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("ds1rchan_init: bad direction")); ch = &sc->rch[sc->rchn]; ch->num = sc->rchn++; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->fmt = AFMT_U8; ch->spd = 8000; if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, 0, sc->bufsz) != 0) return NULL; else { ch->slot = (ch->num == DS1_RECPRIMARY)? sc->rbank + 2: sc->rbank; ds_setuprch(ch); return ch; } } static int ds1rchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_rchinfo *ch = data; ch->fmt = format; return 0; } static int ds1rchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_rchinfo *ch = data; ch->spd = speed; return speed; } static int ds1rchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; int drate; /* irq rate is fixed at 187.5hz */ drate = ch->spd * sndbuf_getbps(ch->buffer); blocksize = roundup2((drate << 8) / DS1_IRQHZ, 4); sndbuf_resize(ch->buffer, sc->bufsz / blocksize, blocksize); return blocksize; } /* semantic note: must start at beginning of buffer */ static int ds1rchan_trigger(kobj_t obj, void *data, int go) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t x; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { ch->run = 1; ds_setuprch(ch); snd_mtxlock(sc->lock); x = ds_rd(sc, YDSXGR_MAPOFREC, 4); x |= (ch->num == DS1_RECPRIMARY)? 0x02 : 0x01; ds_wr(sc, YDSXGR_MAPOFREC, x, 4); ds_wr(sc, YDSXGR_MODE, 0x00000003, 4); snd_mtxunlock(sc->lock); } else { ch->run = 0; snd_mtxlock(sc->lock); x = ds_rd(sc, YDSXGR_MAPOFREC, 4); x &= ~((ch->num == DS1_RECPRIMARY)? 0x02 : 0x01); ds_wr(sc, YDSXGR_MAPOFREC, x, 4); snd_mtxunlock(sc->lock); } return 0; } static int ds1rchan_getptr(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; return ch->slot[sc->currbank].PgStart; } static struct pcmchan_caps * ds1rchan_getcaps(kobj_t obj, void *data) { return &ds_reccaps; } static kobj_method_t ds1rchan_methods[] = { KOBJMETHOD(channel_init, ds1rchan_init), KOBJMETHOD(channel_setformat, ds1rchan_setformat), KOBJMETHOD(channel_setspeed, ds1rchan_setspeed), KOBJMETHOD(channel_setblocksize, ds1rchan_setblocksize), KOBJMETHOD(channel_trigger, ds1rchan_trigger), KOBJMETHOD(channel_getptr, ds1rchan_getptr), KOBJMETHOD(channel_getcaps, ds1rchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(ds1rchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void ds_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t i, x; snd_mtxlock(sc->lock); i = ds_rd(sc, YDSXGR_STATUS, 4); if (i & 0x00008000) device_printf(sc->dev, "timeout irq\n"); if (i & 0x80008000) { ds_wr(sc, YDSXGR_STATUS, i & 0x80008000, 4); sc->currbank = ds_rd(sc, YDSXGR_CTRLSELECT, 4) & 0x00000001; x = 0; for (i = 0; i < DS1_CHANS; i++) { if (sc->pch[i].run) { x = 1; snd_mtxunlock(sc->lock); chn_intr(sc->pch[i].channel); snd_mtxlock(sc->lock); } } for (i = 0; i < 2; i++) { if (sc->rch[i].run) { x = 1; snd_mtxunlock(sc->lock); chn_intr(sc->rch[i].channel); snd_mtxlock(sc->lock); } } i = ds_rd(sc, YDSXGR_MODE, 4); if (x) ds_wr(sc, YDSXGR_MODE, i | 0x00000002, 4); } snd_mtxunlock(sc->lock); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static void ds_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = arg; sc->ctrlbase = error? 0 : (u_int32_t)segs->ds_addr; if (bootverbose) { printf("ds1: setmap (%lx, %lx), nseg=%d, error=%d\n", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, nseg, error); } } static int ds_init(struct sc_info *sc) { int i; u_int32_t *ci, r, pcs, rcs, ecs, ws, memsz, cb; u_int8_t *t; void *buf; ci = ds_devs[sc->type].mcode; ds_wr(sc, YDSXGR_NATIVEDACOUTVOL, 0x00000000, 4); ds_enadsp(sc, 0); ds_wr(sc, YDSXGR_MODE, 0x00010000, 4); ds_wr(sc, YDSXGR_MODE, 0x00000000, 4); ds_wr(sc, YDSXGR_MAPOFREC, 0x00000000, 4); ds_wr(sc, YDSXGR_MAPOFEFFECT, 0x00000000, 4); ds_wr(sc, YDSXGR_PLAYCTRLBASE, 0x00000000, 4); ds_wr(sc, YDSXGR_RECCTRLBASE, 0x00000000, 4); ds_wr(sc, YDSXGR_EFFCTRLBASE, 0x00000000, 4); r = ds_rd(sc, YDSXGR_GLOBALCTRL, 2); ds_wr(sc, YDSXGR_GLOBALCTRL, r & ~0x0007, 2); for (i = 0; i < YDSXG_DSPLENGTH; i += 4) ds_wr(sc, YDSXGR_DSPINSTRAM + i, DspInst[i >> 2], 4); for (i = 0; i < YDSXG_CTRLLENGTH; i += 4) ds_wr(sc, YDSXGR_CTRLINSTRAM + i, ci[i >> 2], 4); ds_enadsp(sc, 1); pcs = 0; for (i = 100; i > 0; i--) { pcs = ds_rd(sc, YDSXGR_PLAYCTRLSIZE, 4) << 2; if (pcs == sizeof(struct pbank)) break; DELAY(1000); } if (pcs != sizeof(struct pbank)) { device_printf(sc->dev, "preposterous playctrlsize (%d)\n", pcs); return -1; } rcs = ds_rd(sc, YDSXGR_RECCTRLSIZE, 4) << 2; ecs = ds_rd(sc, YDSXGR_EFFCTRLSIZE, 4) << 2; ws = ds_rd(sc, YDSXGR_WORKSIZE, 4) << 2; memsz = 64 * 2 * pcs + 2 * 2 * rcs + 5 * 2 * ecs + ws; memsz += (64 + 1) * 4; if (sc->regbase == NULL) { if (bus_dma_tag_create(bus_get_dma_tag(sc->dev), 2, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, memsz, 1, memsz, 0, NULL, NULL, &sc->control_dmat)) return -1; if (bus_dmamem_alloc(sc->control_dmat, &buf, BUS_DMA_NOWAIT, &sc->map)) return -1; if (bus_dmamap_load(sc->control_dmat, sc->map, buf, memsz, ds_setmap, sc, 0) || !sc->ctrlbase) { device_printf(sc->dev, "pcs=%d, rcs=%d, ecs=%d, ws=%d, memsz=%d\n", pcs, rcs, ecs, ws, memsz); return -1; } sc->regbase = buf; } else buf = sc->regbase; cb = 0; t = buf; ds_wr(sc, YDSXGR_WORKBASE, sc->ctrlbase + cb, 4); cb += ws; sc->pbase = (u_int32_t *)(t + cb); /* printf("pbase = %p -> 0x%x\n", sc->pbase, sc->ctrlbase + cb); */ ds_wr(sc, YDSXGR_PLAYCTRLBASE, sc->ctrlbase + cb, 4); cb += (64 + 1) * 4; sc->rbank = (struct rbank *)(t + cb); ds_wr(sc, YDSXGR_RECCTRLBASE, sc->ctrlbase + cb, 4); cb += 2 * 2 * rcs; ds_wr(sc, YDSXGR_EFFCTRLBASE, sc->ctrlbase + cb, 4); cb += 5 * 2 * ecs; sc->pbankbase = sc->ctrlbase + cb; sc->pbanksize = pcs; for (i = 0; i < 64; i++) { wrl(sc, &sc->pbase[i + 1], 0); sc->pbank[i * 2] = (struct pbank *)(t + cb); /* printf("pbank[%d] = %p -> 0x%x; ", i * 2, (struct pbank *)(t + cb), sc->ctrlbase + cb - vtophys(t + cb)); */ cb += pcs; sc->pbank[i * 2 + 1] = (struct pbank *)(t + cb); /* printf("pbank[%d] = %p -> 0x%x\n", i * 2 + 1, (struct pbank *)(t + cb), sc->ctrlbase + cb - vtophys(t + cb)); */ cb += pcs; } wrl(sc, &sc->pbase[0], DS1_CHANS * 2); sc->pchn = sc->rchn = 0; ds_wr(sc, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff, 4); ds_wr(sc, YDSXGR_NATIVEADCINVOL, 0x3fff3fff, 4); ds_wr(sc, YDSXGR_NATIVEDACINVOL, 0x3fff3fff, 4); return 0; } static int ds_uninit(struct sc_info *sc) { ds_wr(sc, YDSXGR_NATIVEDACOUTVOL, 0x00000000, 4); ds_wr(sc, YDSXGR_NATIVEADCINVOL, 0, 4); ds_wr(sc, YDSXGR_NATIVEDACINVOL, 0, 4); ds_enadsp(sc, 0); ds_wr(sc, YDSXGR_MODE, 0x00010000, 4); ds_wr(sc, YDSXGR_MAPOFREC, 0x00000000, 4); ds_wr(sc, YDSXGR_MAPOFEFFECT, 0x00000000, 4); ds_wr(sc, YDSXGR_PLAYCTRLBASE, 0x00000000, 4); ds_wr(sc, YDSXGR_RECCTRLBASE, 0x00000000, 4); ds_wr(sc, YDSXGR_EFFCTRLBASE, 0x00000000, 4); ds_wr(sc, YDSXGR_GLOBALCTRL, 0, 2); bus_dmamap_unload(sc->control_dmat, sc->map); bus_dmamem_free(sc->control_dmat, sc->regbase, sc->map); return 0; } static int ds_finddev(u_int32_t dev, u_int32_t subdev) { int i; for (i = 0; ds_devs[i].dev; i++) { if (ds_devs[i].dev == dev && (ds_devs[i].subdev == subdev || ds_devs[i].subdev == 0)) return i; } return -1; } static int ds_pci_probe(device_t dev) { int i; u_int32_t subdev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); i = ds_finddev(pci_get_devid(dev), subdev); if (i >= 0) { device_set_desc(dev, ds_devs[i].name); return BUS_PROBE_DEFAULT; } else return ENXIO; } static int ds_pci_attach(device_t dev) { u_int32_t data; u_int32_t subdev, i; struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ds1 softc"); sc->dev = dev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); sc->type = ds_finddev(pci_get_devid(dev), subdev); sc->rev = pci_get_revid(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to map register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, 4096, DS1_BUFFSIZE, 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->buffer_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } sc->regbase = NULL; if (ds_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, ds_ac97); if (codec == NULL) goto bad; /* * Turn on inverted external amplifier sense flags for few * 'special' boards. */ switch (subdev) { case 0x81171033: /* NEC ValueStar (VT550/0) */ ac97_setflags(codec, ac97_getflags(codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(dev, ac97_getmixerclass(), codec); 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, ds_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s", rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_ds1)); if (pcm_register(dev, sc, DS1_CHANS, 2)) goto bad; for (i = 0; i < DS1_CHANS; i++) pcm_addchan(dev, PCMDIR_PLAY, &ds1pchan_class, sc); for (i = 0; i < 2; i++) pcm_addchan(dev, PCMDIR_REC, &ds1rchan_class, sc); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->buffer_dmat) bus_dma_tag_destroy(sc->buffer_dmat); if (sc->control_dmat) bus_dma_tag_destroy(sc->control_dmat); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return ENXIO; } static int ds_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); if (ds_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; } return 0; } static int ds_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); ds_uninit(sc); bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_dma_tag_destroy(sc->buffer_dmat); bus_dma_tag_destroy(sc->control_dmat); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } static device_method_t ds1_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ds_pci_probe), DEVMETHOD(device_attach, ds_pci_attach), DEVMETHOD(device_detach, ds_pci_detach), DEVMETHOD(device_resume, ds_pci_resume), { 0, 0 } }; static driver_t ds1_driver = { "pcm", ds1_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_ds1, pci, ds1_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ds1, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_ds1, 1); Index: head/sys/dev/sound/pci/emu10k1.c =================================================================== --- head/sys/dev/sound/pci/emu10k1.c (revision 170520) +++ head/sys/dev/sound/pci/emu10k1.c (revision 170521) @@ -1,2168 +1,2171 @@ /*- * Copyright (c) 2004 David O'Brien * Copyright (c) 2003 Orlando Bassotto * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include "emu10k1-alsa%diked.h" #include #include #include #include #include "mpufoi_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ #define NUM_G 64 /* use all channels */ #define WAVEOUT_MAXBUFSIZE 32768 #define EMUPAGESIZE 4096 /* don't change */ #define EMUMAXPAGES (WAVEOUT_MAXBUFSIZE * NUM_G / EMUPAGESIZE) #define EMU10K1_PCI_ID 0x00021102 /* 1102 => Creative Labs Vendor ID */ #define EMU10K2_PCI_ID 0x00041102 #define EMU10K3_PCI_ID 0x00081102 #define EMU_DEFAULT_BUFSZ 4096 #define EMU_MAX_CHANS 8 #define EMU_CHANS 4 #define MAXREQVOICES 8 #define RESERVED 0 #define NUM_MIDI 16 #define NUM_FXSENDS 4 #define TMEMSIZE 256*1024 #define TMEMSIZEREG 4 #define ENABLE 0xffffffff #define DISABLE 0x00000000 #define ENV_ON DCYSUSV_CHANNELENABLE_MASK #define ENV_OFF 0x00 /* XXX: should this be 1? */ #define A_IOCFG_GPOUT_A 0x40 /* Analog Output */ #define A_IOCFG_GPOUT_D 0x04 /* Digital Output */ #define A_IOCFG_GPOUT_AD (A_IOCFG_GPOUT_A|A_IOCFG_GPOUT_D) /* A_IOCFG_GPOUT0 */ struct emu_memblk { SLIST_ENTRY(emu_memblk) link; void *buf; bus_addr_t buf_addr; u_int32_t pte_start, pte_size; }; struct emu_mem { u_int8_t bmap[EMUMAXPAGES / 8]; u_int32_t *ptb_pages; void *silent_page; bus_addr_t silent_page_addr; bus_addr_t ptb_pages_addr; SLIST_HEAD(, emu_memblk) blocks; }; struct emu_voice { int vnum; unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start, end, vol; int fxrt1; /* FX routing */ int fxrt2; /* FX routing (only for audigy) */ u_int32_t buf; struct emu_voice *slave; struct pcm_channel *channel; }; struct sc_info; /* channel registers */ struct sc_pchinfo { int spd, fmt, blksz, run; struct emu_voice *master, *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; struct sc_rchinfo { int spd, fmt, run, blksz, num; u_int32_t idxreg, basereg, sizereg, setupreg, irqmask; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type, rev; u_int32_t tos_link:1, APS:1, audigy:1, audigy2:1; u_int32_t addrmask; /* wider if audigy */ bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; void *ih; struct mtx *lock; unsigned int bufsz; int timer, timerinterval; int pnum, rnum; int nchans; struct emu_mem mem; struct emu_voice voice[64]; struct sc_pchinfo pch[EMU_MAX_CHANS]; struct sc_rchinfo rch[3]; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; int mputx; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int emu_init(struct sc_info *); static void emu_intr(void *); static void *emu_malloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr); static void *emu_memalloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr); static int emu_memfree(struct sc_info *sc, void *buf); static int emu_memstart(struct sc_info *sc, void *buf); #ifdef EMUDEBUG static void emu_vdump(struct sc_info *sc, struct emu_voice *v); #endif /* talk to the card */ static u_int32_t emu_rd(struct sc_info *, int, int); static void emu_wr(struct sc_info *, int, u_int32_t, int); /* -------------------------------------------------------------------- */ static u_int32_t emu_rfmt_ac97[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static u_int32_t emu_rfmt_mic[] = { AFMT_U8, 0 }; static u_int32_t emu_rfmt_efx[] = { AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps emu_reccaps[3] = { {8000, 48000, emu_rfmt_ac97, 0}, {8000, 8000, emu_rfmt_mic, 0}, {48000, 48000, emu_rfmt_efx, 0}, }; static u_int32_t emu_pfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static int adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int audigy_adcspeed[9] = { 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000 }; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t emu_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: return 0xffffffff; } } static void emu_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->st, sc->sh, regno, data); break; case 2: bus_space_write_2(sc->st, sc->sh, regno, data); break; case 4: bus_space_write_4(sc->st, sc->sh, regno, data); break; } } static u_int32_t emu_rdptr(struct sc_info *sc, int chn, int reg) { u_int32_t ptr, val, mask, size, offset; ptr = ((reg << 16) & sc->addrmask) | (chn & PTR_CHANNELNUM_MASK); emu_wr(sc, PTR, ptr, 4); val = emu_rd(sc, DATA, 4); if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; val &= mask; val >>= offset; } return val; } static void emu_wrptr(struct sc_info *sc, int chn, int reg, u_int32_t data) { u_int32_t ptr, mask, size, offset; ptr = ((reg << 16) & sc->addrmask) | (chn & PTR_CHANNELNUM_MASK); emu_wr(sc, PTR, ptr, 4); if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; data <<= offset; data &= mask; data |= emu_rd(sc, DATA, 4) & ~mask; } emu_wr(sc, DATA, data, 4); } static void emu_wrefx(struct sc_info *sc, unsigned int pc, unsigned int data) { pc += sc->audigy ? A_MICROCODEBASE : MICROCODEBASE; emu_wrptr(sc, 0, pc, data); } /* -------------------------------------------------------------------- */ /* ac97 codec */ /* no locking needed */ static int emu_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; emu_wr(sc, AC97ADDRESS, regno, 1); return emu_rd(sc, AC97DATA, 2); } static int emu_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; emu_wr(sc, AC97ADDRESS, regno, 1); emu_wr(sc, AC97DATA, data, 2); return 0; } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), { 0, 0 } }; AC97_DECLARE(emu_ac97); /* -------------------------------------------------------------------- */ /* stuff */ static int emu_settimer(struct sc_info *sc) { struct sc_pchinfo *pch; struct sc_rchinfo *rch; int i, tmp, rate; rate = 0; for (i = 0; i < sc->nchans; i++) { pch = &sc->pch[i]; if (pch->buffer) { tmp = (pch->spd * sndbuf_getbps(pch->buffer)) / pch->blksz; if (tmp > rate) rate = tmp; } } for (i = 0; i < 3; i++) { rch = &sc->rch[i]; if (rch->buffer) { tmp = (rch->spd * sndbuf_getbps(rch->buffer)) / rch->blksz; if (tmp > rate) rate = tmp; } } RANGE(rate, 48, 9600); sc->timerinterval = 48000 / rate; emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2); return sc->timerinterval; } static int emu_enatimer(struct sc_info *sc, int go) { u_int32_t x; if (go) { if (sc->timer++ == 0) { x = emu_rd(sc, INTE, 4); x |= INTE_INTERVALTIMERENB; emu_wr(sc, INTE, x, 4); } } else { sc->timer = 0; x = emu_rd(sc, INTE, 4); x &= ~INTE_INTERVALTIMERENB; emu_wr(sc, INTE, x, 4); } return 0; } static void emu_enastop(struct sc_info *sc, char channel, int enable) { int reg = (channel & 0x20) ? SOLEH : SOLEL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; emu_wrptr(sc, 0, reg, enable); } static int emu_recval(int speed) { int val; val = 0; while (val < 7 && speed < adcspeed[val]) val++; return val; } static int audigy_recval(int speed) { int val; val = 0; while (val < 8 && speed < audigy_adcspeed[val]) val++; return val; } static u_int32_t emu_rate_to_pitch(u_int32_t rate) { static u_int32_t logMagTable[128] = { 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df }; static char logSlopeTable[128] = { 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f }; int i; if (rate == 0) return 0; /* Bail out if no leading "1" */ rate *= 11185; /* Scale 48000 to 0x20002380 */ for (i = 31; i > 0; i--) { if (rate & 0x80000000) { /* Detect leading "1" */ return (((u_int32_t) (i - 15) << 20) + logMagTable[0x7f & (rate >> 24)] + (0x7f & (rate >> 17)) * logSlopeTable[0x7f & (rate >> 24)]); } rate <<= 1; } return 0; /* Should never reach this point */ } static u_int32_t emu_rate_to_linearpitch(u_int32_t rate) { rate = (rate << 8) / 375; return (rate >> 1) + (rate & 1); } static struct emu_voice * emu_valloc(struct sc_info *sc) { struct emu_voice *v; int i; v = NULL; for (i = 0; i < 64 && sc->voice[i].busy; i++); if (i < 64) { v = &sc->voice[i]; v->busy = 1; } return v; } static int emu_vinit(struct sc_info *sc, struct emu_voice *m, struct emu_voice *s, u_int32_t sz, struct snd_dbuf *b) { void *buf; bus_addr_t tmp_addr; buf = emu_memalloc(sc, sz, &tmp_addr); if (buf == NULL) return -1; if (b != NULL) sndbuf_setup(b, buf, sz); m->start = emu_memstart(sc, buf) * EMUPAGESIZE; m->end = m->start + sz; m->channel = NULL; m->speed = 0; m->b16 = 0; m->stereo = 0; m->running = 0; m->ismaster = 1; m->vol = 0xff; m->buf = tmp_addr; m->slave = s; if (sc->audigy) { m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 8 | FXBUS_PCM_LEFT << 16 | FXBUS_MIDI_REVERB << 24; m->fxrt2 = 0x3f3f3f3f; /* No effects on second route */ } else { m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 4 | FXBUS_PCM_LEFT << 8 | FXBUS_MIDI_REVERB << 12; m->fxrt2 = 0; } if (s != NULL) { s->start = m->start; s->end = m->end; s->channel = NULL; s->speed = 0; s->b16 = 0; s->stereo = 0; s->running = 0; s->ismaster = 0; s->vol = m->vol; s->buf = m->buf; s->fxrt1 = m->fxrt1; s->fxrt2 = m->fxrt2; s->slave = NULL; } return 0; } static void emu_vsetup(struct sc_pchinfo *ch) { struct emu_voice *v = ch->master; if (ch->fmt) { v->b16 = (ch->fmt & AFMT_16BIT) ? 1 : 0; v->stereo = (ch->fmt & AFMT_STEREO) ? 1 : 0; if (v->slave != NULL) { v->slave->b16 = v->b16; v->slave->stereo = v->stereo; } } if (ch->spd) { v->speed = ch->spd; if (v->slave != NULL) v->slave->speed = v->speed; } } static void emu_vwrite(struct sc_info *sc, struct emu_voice *v) { int s; int l, r, x, y; u_int32_t sa, ea, start, val, silent_page; s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0); sa = v->start >> s; ea = v->end >> s; l = r = x = y = v->vol; if (v->stereo) { l = v->ismaster ? l : 0; r = v->ismaster ? 0 : r; } emu_wrptr(sc, v->vnum, CPF, v->stereo ? CPF_STEREO_MASK : 0); val = v->stereo ? 28 : 30; val *= v->b16 ? 1 : 2; start = sa + val; if (sc->audigy) { emu_wrptr(sc, v->vnum, A_FXRT1, v->fxrt1); emu_wrptr(sc, v->vnum, A_FXRT2, v->fxrt2); emu_wrptr(sc, v->vnum, A_SENDAMOUNTS, 0); } else emu_wrptr(sc, v->vnum, FXRT, v->fxrt1 << 16); emu_wrptr(sc, v->vnum, PTRX, (x << 8) | r); emu_wrptr(sc, v->vnum, DSL, ea | (y << 24)); emu_wrptr(sc, v->vnum, PSST, sa | (l << 24)); emu_wrptr(sc, v->vnum, CCCA, start | (v->b16 ? 0 : CCCA_8BITSELECT)); emu_wrptr(sc, v->vnum, Z1, 0); emu_wrptr(sc, v->vnum, Z2, 0); silent_page = ((u_int32_t)(sc->mem.silent_page_addr) << 1) | MAP_PTI_MASK; emu_wrptr(sc, v->vnum, MAPA, silent_page); emu_wrptr(sc, v->vnum, MAPB, silent_page); emu_wrptr(sc, v->vnum, CVCF, CVCF_CURRENTFILTER_MASK); emu_wrptr(sc, v->vnum, VTFT, VTFT_FILTERTARGET_MASK); emu_wrptr(sc, v->vnum, ATKHLDM, 0); emu_wrptr(sc, v->vnum, DCYSUSM, DCYSUSM_DECAYTIME_MASK); emu_wrptr(sc, v->vnum, LFOVAL1, 0x8000); emu_wrptr(sc, v->vnum, LFOVAL2, 0x8000); emu_wrptr(sc, v->vnum, FMMOD, 0); emu_wrptr(sc, v->vnum, TREMFRQ, 0); emu_wrptr(sc, v->vnum, FM2FRQ2, 0); emu_wrptr(sc, v->vnum, ENVVAL, 0x8000); emu_wrptr(sc, v->vnum, ATKHLDV, ATKHLDV_HOLDTIME_MASK | ATKHLDV_ATTACKTIME_MASK); emu_wrptr(sc, v->vnum, ENVVOL, 0x8000); emu_wrptr(sc, v->vnum, PEFE_FILTERAMOUNT, 0x7f); emu_wrptr(sc, v->vnum, PEFE_PITCHAMOUNT, 0); if (v->slave != NULL) emu_vwrite(sc, v->slave); } static void emu_vtrigger(struct sc_info *sc, struct emu_voice *v, int go) { u_int32_t pitch_target, initial_pitch; u_int32_t cra, cs, ccis; u_int32_t sample, i; if (go) { cra = 64; cs = v->stereo ? 4 : 2; ccis = v->stereo ? 28 : 30; ccis *= v->b16 ? 1 : 2; sample = v->b16 ? 0x00000000 : 0x80808080; for (i = 0; i < cs; i++) emu_wrptr(sc, v->vnum, CD0 + i, sample); emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, 0); emu_wrptr(sc, v->vnum, CCR_READADDRESS, cra); emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, ccis); emu_wrptr(sc, v->vnum, IFATN, 0xff00); emu_wrptr(sc, v->vnum, VTFT, 0xffffffff); emu_wrptr(sc, v->vnum, CVCF, 0xffffffff); emu_wrptr(sc, v->vnum, DCYSUSV, 0x00007f7f); emu_enastop(sc, v->vnum, 0); pitch_target = emu_rate_to_linearpitch(v->speed); initial_pitch = emu_rate_to_pitch(v->speed) >> 8; emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, pitch_target); emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, pitch_target); emu_wrptr(sc, v->vnum, IP, initial_pitch); } else { emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, 0); emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, 0); emu_wrptr(sc, v->vnum, IFATN, 0xffff); emu_wrptr(sc, v->vnum, VTFT, 0x0000ffff); emu_wrptr(sc, v->vnum, CVCF, 0x0000ffff); emu_wrptr(sc, v->vnum, IP, 0); emu_enastop(sc, v->vnum, 1); } if (v->slave != NULL) emu_vtrigger(sc, v->slave, go); } static int emu_vpos(struct sc_info *sc, struct emu_voice *v) { int s, ptr; s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0); ptr = (emu_rdptr(sc, v->vnum, CCCA_CURRADDR) - (v->start >> s)) << s; return ptr & ~0x0000001f; } #ifdef EMUDEBUG static void emu_vdump(struct sc_info *sc, struct emu_voice *v) { char *regname[] = { "cpf", "ptrx", "cvcf", "vtft", "z2", "z1", "psst", "dsl", "ccca", "ccr", "clp", "fxrt", "mapa", "mapb", NULL, NULL, "envvol", "atkhldv", "dcysusv", "lfoval1", "envval", "atkhldm", "dcysusm", "lfoval2", "ip", "ifatn", "pefe", "fmmod", "tremfrq", "fmfrq2", "tempenv" }; char *regname2[] = { "mudata1", "mustat1", "mudata2", "mustat2", "fxwc1", "fxwc2", "spdrate", NULL, NULL, NULL, NULL, NULL, "fxrt2", "sndamnt", "fxrt1", NULL, NULL }; int i, x; printf("voice number %d\n", v->vnum); for (i = 0, x = 0; i <= 0x1e; i++) { if (regname[i] == NULL) continue; printf("%s\t[%08x]", regname[i], emu_rdptr(sc, v->vnum, i)); printf("%s", (x == 2) ? "\n" : "\t"); x++; if (x > 2) x = 0; } /* Print out audigy extra registers */ if (sc->audigy) { for (i = 0; i <= 0xe; i++) { if (regname2[i] == NULL) continue; printf("%s\t[%08x]", regname2[i], emu_rdptr(sc, v->vnum, i + 0x70)); printf("%s", (x == 2)? "\n" : "\t"); x++; if (x > 2) x = 0; } } printf("\n\n"); } #endif /* channel interface */ static void * emupchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); ch = &sc->pch[sc->pnum++]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; ch->fmt = AFMT_U8; ch->spd = 8000; snd_mtxlock(sc->lock); ch->master = emu_valloc(sc); ch->slave = emu_valloc(sc); snd_mtxunlock(sc->lock); r = (emu_vinit(sc, ch->master, ch->slave, sc->bufsz, ch->buffer)) ? NULL : ch; return r; } static int emupchan_free(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_memfree(sc, sndbuf_getbuf(ch->buffer)); snd_mtxunlock(sc->lock); return r; } static int emupchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_pchinfo *ch = data; ch->fmt = format; return 0; } static int emupchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_pchinfo *ch = data; ch->spd = speed; return ch->spd; } static int emupchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int irqrate, blksz; ch->blksz = blocksize; snd_mtxlock(sc->lock); emu_settimer(sc); irqrate = 48000 / sc->timerinterval; snd_mtxunlock(sc->lock); blksz = (ch->spd * sndbuf_getbps(ch->buffer)) / irqrate; return blocksize; } static int emupchan_trigger(kobj_t obj, void *data, int go) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); if (go == PCMTRIG_START) { emu_vsetup(ch); emu_vwrite(sc, ch->master); emu_settimer(sc); emu_enatimer(sc, 1); #ifdef EMUDEBUG printf("start [%d bit, %s, %d hz]\n", ch->master->b16 ? 16 : 8, ch->master->stereo ? "stereo" : "mono", ch->master->speed); emu_vdump(sc, ch->master); emu_vdump(sc, ch->slave); #endif } ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc, ch->master, ch->run); snd_mtxunlock(sc->lock); return 0; } static int emupchan_getptr(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_vpos(sc, ch->master); snd_mtxunlock(sc->lock); return r; } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj, void *data) { return &emu_playcaps; } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(emupchan); /* channel interface */ static void * emurchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch[sc->rnum]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; ch->fmt = AFMT_U8; ch->spd = 8000; ch->num = sc->rnum; switch(sc->rnum) { case 0: ch->idxreg = sc->audigy ? A_ADCIDX : ADCIDX; ch->basereg = ADCBA; ch->sizereg = ADCBS; ch->setupreg = ADCCR; ch->irqmask = INTE_ADCBUFENABLE; break; case 1: ch->idxreg = FXIDX; ch->basereg = FXBA; ch->sizereg = FXBS; ch->setupreg = FXWC; ch->irqmask = INTE_EFXBUFENABLE; break; case 2: ch->idxreg = MICIDX; ch->basereg = MICBA; ch->sizereg = MICBS; ch->setupreg = 0; ch->irqmask = INTE_MICBUFENABLE; break; } sc->rnum++; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; else { snd_mtxlock(sc->lock); emu_wrptr(sc, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc, 0, ch->sizereg, 0); /* off */ snd_mtxunlock(sc->lock); return ch; } } static int emurchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_rchinfo *ch = data; ch->fmt = format; return 0; } static int emurchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_rchinfo *ch = data; if (ch->num == 0) { if (ch->parent->audigy) speed = audigy_adcspeed[audigy_recval(speed)]; else speed = adcspeed[emu_recval(speed)]; } if (ch->num == 1) speed = 48000; if (ch->num == 2) speed = 8000; ch->spd = speed; return ch->spd; } static int emurchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; int irqrate, blksz; ch->blksz = blocksize; snd_mtxlock(sc->lock); emu_settimer(sc); irqrate = 48000 / sc->timerinterval; snd_mtxunlock(sc->lock); blksz = (ch->spd * sndbuf_getbps(ch->buffer)) / irqrate; return blocksize; } /* semantic note: must start at beginning of buffer */ static int emurchan_trigger(kobj_t obj, void *data, int go) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t val, sz; + + if (!PCMTRIG_COMMON(go)) + return 0; switch(sc->bufsz) { case 4096: sz = ADCBS_BUFSIZE_4096; break; case 8192: sz = ADCBS_BUFSIZE_8192; break; case 16384: sz = ADCBS_BUFSIZE_16384; break; case 32768: sz = ADCBS_BUFSIZE_32768; break; case 65536: sz = ADCBS_BUFSIZE_65536; break; default: sz = ADCBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc, 0, ch->sizereg, sz); if (ch->num == 0) { if (sc->audigy) { val = A_ADCCR_LCHANENABLE; if (ch->fmt & AFMT_STEREO) val |= A_ADCCR_RCHANENABLE; val |= audigy_recval(ch->spd); } else { val = ADCCR_LCHANENABLE; if (ch->fmt & AFMT_STEREO) val |= ADCCR_RCHANENABLE; val |= emu_recval(ch->spd); } emu_wrptr(sc, 0, ch->setupreg, 0); emu_wrptr(sc, 0, ch->setupreg, val); } val = emu_rd(sc, INTE, 4); val |= ch->irqmask; emu_wr(sc, INTE, val, 4); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc, 0, ch->setupreg, 0); val = emu_rd(sc, INTE, 4); val &= ~ch->irqmask; emu_wr(sc, INTE, val, 4); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: default: break; } snd_mtxunlock(sc->lock); return 0; } static int emurchan_getptr(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_rdptr(sc, 0, ch->idxreg) & 0x0000ffff; snd_mtxunlock(sc->lock); return r; } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; return &emu_reccaps[ch->num]; } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(emurchan); static unsigned char emu_mread(void *arg, struct sc_info *sc, int reg) { unsigned int d; d = emu_rd(sc, 0x18 + reg, 1); return d; } static void emu_mwrite(void *arg, struct sc_info *sc, int reg, unsigned char b) { emu_wr(sc, 0x18 + reg, b, 1); } static int emu_muninit(void *arg, struct sc_info *sc) { snd_mtxlock(sc->lock); sc->mpu_intr = 0; snd_mtxunlock(sc->lock); return 0; } static kobj_method_t emu_mpu_methods[] = { KOBJMETHOD(mpufoi_read, emu_mread), KOBJMETHOD(mpufoi_write, emu_mwrite), KOBJMETHOD(mpufoi_uninit, emu_muninit), { 0, 0 } }; static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); static void emu_intr2(void *p) { struct sc_info *sc = (struct sc_info *)p; if (sc->mpu_intr) (sc->mpu_intr)(sc->mpu); } static void emu_midiattach(struct sc_info *sc) { int i; i = emu_rd(sc, INTE, 4); i |= INTE_MIDIRXENABLE; emu_wr(sc, INTE, i, 4); sc->mpu = mpu401_init(&emu_mpu_class, sc, emu_intr2, &sc->mpu_intr); } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void emu_intr(void *data) { struct sc_info *sc = data; u_int32_t stat, ack, i, x; snd_mtxlock(sc->lock); while (1) { stat = emu_rd(sc, IPR, 4); if (stat == 0) break; ack = 0; /* process irq */ if (stat & IPR_INTERVALTIMER) ack |= IPR_INTERVALTIMER; if (stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) ack |= stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL); if (stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL)) ack |= stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL); if (stat & (IPR_MICBUFFULL | IPR_MICBUFHALFFULL)) ack |= stat & (IPR_MICBUFFULL | IPR_MICBUFHALFFULL); if (stat & IPR_PCIERROR) { ack |= IPR_PCIERROR; device_printf(sc->dev, "pci error\n"); /* we still get an nmi with ecc ram even if we ack this */ } if (stat & IPR_SAMPLERATETRACKER) { ack |= IPR_SAMPLERATETRACKER; #ifdef EMUDEBUG device_printf(sc->dev, "sample rate tracker lock status change\n"); #endif } if (stat & IPR_MIDIRECVBUFEMPTY) if (sc->mpu_intr) { (sc->mpu_intr)(sc->mpu); ack |= IPR_MIDIRECVBUFEMPTY | IPR_MIDITRANSBUFEMPTY; } if (stat & ~ack) device_printf(sc->dev, "dodgy irq: %x (harmless)\n", stat & ~ack); emu_wr(sc, IPR, stat, 4); if (ack) { snd_mtxunlock(sc->lock); if (ack & IPR_INTERVALTIMER) { x = 0; for (i = 0; i < sc->nchans; i++) { if (sc->pch[i].run) { x = 1; chn_intr(sc->pch[i].channel); } } if (x == 0) emu_enatimer(sc, 0); } if (ack & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) { if (sc->rch[0].channel) chn_intr(sc->rch[0].channel); } if (ack & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL)) { if (sc->rch[1].channel) chn_intr(sc->rch[1].channel); } if (ack & (IPR_MICBUFFULL | IPR_MICBUFHALFFULL)) { if (sc->rch[2].channel) chn_intr(sc->rch[2].channel); } snd_mtxlock(sc->lock); } } snd_mtxunlock(sc->lock); } /* -------------------------------------------------------------------- */ static void emu_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *phys = arg; *phys = error ? 0 : (bus_addr_t)segs->ds_addr; if (bootverbose) { printf("emu: setmap (%lx, %lx), nseg=%d, error=%d\n", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, nseg, error); } } static void * emu_malloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr) { void *buf; bus_dmamap_t map; *addr = 0; if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, &map)) return NULL; if (bus_dmamap_load(sc->parent_dmat, map, buf, sz, emu_setmap, addr, 0) || !*addr) return NULL; return buf; } static void emu_free(struct sc_info *sc, void *buf) { bus_dmamem_free(sc->parent_dmat, buf, NULL); } static void * emu_memalloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr) { u_int32_t blksz, start, idx, ofs, tmp, found; struct emu_mem *mem = &sc->mem; struct emu_memblk *blk; void *buf; blksz = sz / EMUPAGESIZE; if (sz > (blksz * EMUPAGESIZE)) blksz++; /* find a free block in the bitmap */ found = 0; start = 1; while (!found && start + blksz < EMUMAXPAGES) { found = 1; for (idx = start; idx < start + blksz; idx++) if (mem->bmap[idx >> 3] & (1 << (idx & 7))) found = 0; if (!found) start++; } if (!found) return NULL; blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT); if (blk == NULL) return NULL; buf = emu_malloc(sc, sz, &blk->buf_addr); *addr = blk->buf_addr; if (buf == NULL) { free(blk, M_DEVBUF); return NULL; } blk->buf = buf; blk->pte_start = start; blk->pte_size = blksz; #ifdef EMUDEBUG printf("buf %p, pte_start %d, pte_size %d\n", blk->buf, blk->pte_start, blk->pte_size); #endif ofs = 0; for (idx = start; idx < start + blksz; idx++) { mem->bmap[idx >> 3] |= 1 << (idx & 7); tmp = (u_int32_t)(u_long)((u_int8_t *)blk->buf_addr + ofs); #ifdef EMUDEBUG printf("pte[%d] -> %x phys, %x virt\n", idx, tmp, ((u_int32_t)buf) + ofs); #endif mem->ptb_pages[idx] = (tmp << 1) | idx; ofs += EMUPAGESIZE; } SLIST_INSERT_HEAD(&mem->blocks, blk, link); return buf; } static int emu_memfree(struct sc_info *sc, void *buf) { u_int32_t idx, tmp; struct emu_mem *mem = &sc->mem; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == buf) blk = i; } if (blk == NULL) return EINVAL; SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link); emu_free(sc, buf); tmp = (u_int32_t)(sc->mem.silent_page_addr) << 1; for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) { mem->bmap[idx >> 3] &= ~(1 << (idx & 7)); mem->ptb_pages[idx] = tmp | idx; } free(blk, M_DEVBUF); return 0; } static int emu_memstart(struct sc_info *sc, void *buf) { struct emu_mem *mem = &sc->mem; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == buf) blk = i; } if (blk == NULL) return -EINVAL; return blk->pte_start; } static void emu_addefxop(struct sc_info *sc, int op, int z, int w, int x, int y, u_int32_t *pc) { emu_wrefx(sc, (*pc) * 2, (x << 10) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << 20) | (z << 10) | w); (*pc)++; } static void audigy_addefxop(struct sc_info *sc, int op, int z, int w, int x, int y, u_int32_t *pc) { emu_wrefx(sc, (*pc) * 2, (x << 12) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << 24) | (z << 12) | w); (*pc)++; } static void audigy_initefx(struct sc_info *sc) { int i; u_int32_t pc = 0; /* skip 0, 0, -1, 0 - NOPs */ for (i = 0; i < 512; i++) audigy_addefxop(sc, 0x0f, 0x0c0, 0x0c0, 0x0cf, 0x0c0, &pc); for (i = 0; i < 512; i++) emu_wrptr(sc, 0, A_FXGPREGBASE + i, 0x0); pc = 16; /* stop fx processor */ emu_wrptr(sc, 0, A_DBG, A_DBG_SINGLE_STEP); /* Audigy 2 (EMU10K2) DSP Registers: FX Bus 0x000-0x00f : 16 registers (?) Input 0x040/0x041 : AC97 Codec (l/r) 0x042/0x043 : ADC, S/PDIF (l/r) 0x044/0x045 : Optical S/PDIF in (l/r) 0x046/0x047 : ? 0x048/0x049 : Line/Mic 2 (l/r) 0x04a/0x04b : RCA S/PDIF (l/r) 0x04c/0x04d : Aux 2 (l/r) Output 0x060/0x061 : Digital Front (l/r) 0x062/0x063 : Digital Center/LFE 0x064/0x065 : AudigyDrive Heaphone (l/r) 0x066/0x067 : Digital Rear (l/r) 0x068/0x069 : Analog Front (l/r) 0x06a/0x06b : Analog Center/LFE 0x06c/0x06d : ? 0x06e/0x06f : Analog Rear (l/r) 0x070/0x071 : AC97 Output (l/r) 0x072/0x073 : ? 0x074/0x075 : ? 0x076/0x077 : ADC Recording Buffer (l/r) Constants 0x0c0 - 0x0c4 = 0 - 4 0x0c5 = 0x8, 0x0c6 = 0x10, 0x0c7 = 0x20 0x0c8 = 0x100, 0x0c9 = 0x10000, 0x0ca = 0x80000 0x0cb = 0x10000000, 0x0cc = 0x20000000, 0x0cd = 0x40000000 0x0ce = 0x80000000, 0x0cf = 0x7fffffff, 0x0d0 = 0xffffffff 0x0d1 = 0xfffffffe, 0x0d2 = 0xc0000000, 0x0d3 = 0x41fbbcdc 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (?) Temporary Values 0x0d6 : Accumulator (?) 0x0d7 : Condition Register 0x0d8 : Noise source 0x0d9 : Noise source Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff General Purpose Registers 0x400 - 0x5ff */ /* AC97Output[l/r] = FXBus PCM[l/r] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AC97_L), A_C_00000000, A_C_00000000, A_FXBUS(FXBUS_PCM_LEFT), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AC97_R), A_C_00000000, A_C_00000000, A_FXBUS(FXBUS_PCM_RIGHT), &pc); /* GPR[0/1] = RCA S/PDIF[l/r] -- Master volume */ audigy_addefxop(sc, iACC3, A_GPR(0), A_C_00000000, A_C_00000000, A_EXTIN(EXTIN_COAX_SPDIF_L), &pc); audigy_addefxop(sc, iACC3, A_GPR(1), A_C_00000000, A_C_00000000, A_EXTIN(EXTIN_COAX_SPDIF_R), &pc); /* GPR[2] = GPR[0] (Left) / 2 + GPR[1] (Right) / 2 -- Central volume */ audigy_addefxop(sc, iINTERP, A_GPR(2), A_GPR(1), A_C_40000000, A_GPR(0), &pc); /* Headphones[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_HEADPHONE_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_HEADPHONE_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Analog Front[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AFRONT_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AFRONT_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Digital Front[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_FRONT_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Center and Subwoofer configuration */ /* Analog Center = GPR[0] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ACENTER), A_C_00000000, A_GPR(0), A_GPR(2), &pc); /* Analog Sub = GPR[1] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ALFE), A_C_00000000, A_GPR(1), A_GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_CENTER), A_C_00000000, A_GPR(0), A_GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_LFE), A_C_00000000, A_GPR(1), A_GPR(2), &pc); #if 0 /* Analog Rear[l/r] = (GPR[0/1] * RearVolume[l/r]) >> 31 */ /* RearVolume = GPR[0x10/0x11] (Will this ever be implemented?) */ audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_AREAR_L), A_C_00000000, A_GPR(16), A_GPR(0), &pc); audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_AREAR_R), A_C_00000000, A_GPR(17), A_GPR(1), &pc); /* Digital Rear[l/r] = (GPR[0/1] * RearVolume[l/r]) >> 31 */ /* RearVolume = GPR[0x10/0x11] (Will this ever be implemented?) */ audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_REAR_L), A_C_00000000, A_GPR(16), A_GPR(0), &pc); audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_REAR_R), A_C_00000000, A_GPR(17), A_GPR(1), &pc); #else /* XXX This is just a copy to the channel, since we do not have * a patch manager, it is useful for have another output enabled. */ /* Analog Rear[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AREAR_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AREAR_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Digital Rear[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_REAR_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_REAR_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); #endif /* ADC Recording buffer[l/r] = AC97Input[l/r] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ADC_CAP_L), A_C_00000000, A_C_00000000, A_EXTIN(A_EXTIN_AC97_L), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ADC_CAP_R), A_C_00000000, A_C_00000000, A_EXTIN(A_EXTIN_AC97_R), &pc); /* resume normal operations */ emu_wrptr(sc, 0, A_DBG, 0); } static void emu_initefx(struct sc_info *sc) { int i; u_int32_t pc = 16; /* acc3 0,0,0,0 - NOPs */ for (i = 0; i < 512; i++) { emu_wrefx(sc, i * 2, 0x10040); emu_wrefx(sc, i * 2 + 1, 0x610040); } for (i = 0; i < 256; i++) emu_wrptr(sc, 0, FXGPREGBASE + i, 0); /* FX-8010 DSP Registers: FX Bus 0x000-0x00f : 16 registers Input 0x010/0x011 : AC97 Codec (l/r) 0x012/0x013 : ADC, S/PDIF (l/r) 0x014/0x015 : Mic(left), Zoom (l/r) 0x016/0x017 : TOS link in (l/r) 0x018/0x019 : Line/Mic 1 (l/r) 0x01a/0x01b : COAX S/PDIF (l/r) 0x01c/0x01d : Line/Mic 2 (l/r) Output 0x020/0x021 : AC97 Output (l/r) 0x022/0x023 : TOS link out (l/r) 0x024/0x025 : Center/LFE 0x026/0x027 : LiveDrive Headphone (l/r) 0x028/0x029 : Rear Channel (l/r) 0x02a/0x02b : ADC Recording Buffer (l/r) 0x02c : Mic Recording Buffer 0x031/0x032 : Analog Center/LFE Constants 0x040 - 0x044 = 0 - 4 0x045 = 0x8, 0x046 = 0x10, 0x047 = 0x20 0x048 = 0x100, 0x049 = 0x10000, 0x04a = 0x80000 0x04b = 0x10000000, 0x04c = 0x20000000, 0x04d = 0x40000000 0x04e = 0x80000000, 0x04f = 0x7fffffff, 0x050 = 0xffffffff 0x051 = 0xfffffffe, 0x052 = 0xc0000000, 0x053 = 0x41fbbcdc 0x054 = 0x5a7ef9db, 0x055 = 0x00100000 Temporary Values 0x056 : Accumulator 0x057 : Condition Register 0x058 : Noise source 0x059 : Noise source 0x05a : IRQ Register 0x05b : TRAM Delay Base Address Count General Purpose Registers 0x100 - 0x1ff Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff */ /* Routing - this will be configurable in later version */ /* GPR[0/1] = FX * 4 + SPDIF-in */ emu_addefxop(sc, iMACINT0, GPR(0), EXTIN(EXTIN_SPDIF_CD_L), FXBUS(FXBUS_PCM_LEFT), C_00000004, &pc); emu_addefxop(sc, iMACINT0, GPR(1), EXTIN(EXTIN_SPDIF_CD_R), FXBUS(FXBUS_PCM_RIGHT), C_00000004, &pc); /* GPR[0/1] += APS-input */ emu_addefxop(sc, iACC3, GPR(0), GPR(0), C_00000000, sc->APS ? EXTIN(EXTIN_TOSLINK_L) : C_00000000, &pc); emu_addefxop(sc, iACC3, GPR(1), GPR(1), C_00000000, sc->APS ? EXTIN(EXTIN_TOSLINK_R) : C_00000000, &pc); /* FrontOut (AC97) = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_R), C_00000000, C_00000001, GPR(1), &pc); /* GPR[2] = GPR[0] (Left) / 2 + GPR[1] (Right) / 2 -- Central volume */ emu_addefxop(sc, iINTERP, GPR(2), GPR(1), C_40000000, GPR(0), &pc); #if 0 /* RearOut = (GPR[0/1] * RearVolume) >> 31 */ /* RearVolume = GPR[0x10/0x11] */ emu_addefxop(sc, iMAC0, EXTOUT(EXTOUT_REAR_L), C_00000000, GPR(16), GPR(0), &pc); emu_addefxop(sc, iMAC0, EXTOUT(EXTOUT_REAR_R), C_00000000, GPR(17), GPR(1), &pc); #else /* XXX This is just a copy to the channel, since we do not have * a patch manager, it is useful for have another output enabled. */ /* Rear[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_REAR_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_REAR_R), C_00000000, C_00000000, GPR(1), &pc); #endif /* TOS out[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_TOSLINK_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_TOSLINK_R), C_00000000, C_00000000, GPR(1), &pc); /* Center and Subwoofer configuration */ /* Analog Center = GPR[0] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ACENTER), C_00000000, GPR(0), GPR(2), &pc); /* Analog Sub = GPR[1] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ALFE), C_00000000, GPR(1), GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_CENTER), C_00000000, GPR(0), GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_LFE), C_00000000, GPR(1), GPR(2), &pc); /* Headphones[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_HEADPHONE_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_HEADPHONE_R), C_00000000, C_00000000, GPR(1), &pc); /* ADC Recording buffer[l/r] = AC97Input[l/r] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ADC_CAP_L), C_00000000, C_00000000, EXTIN(EXTIN_AC97_L), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ADC_CAP_R), C_00000000, C_00000000, EXTIN(EXTIN_AC97_R), &pc); /* resume normal operations */ emu_wrptr(sc, 0, DBG, 0); } /* Probe and attach the card */ static int emu_init(struct sc_info *sc) { u_int32_t spcs, ch, tmp, i; if (sc->audigy) { /* enable additional AC97 slots */ emu_wrptr(sc, 0, AC97SLOT, AC97SLOT_CNTR | AC97SLOT_LFE); } /* disable audio and lock cache */ emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4); /* reset recording buffers */ emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE); emu_wrptr(sc, 0, MICBA, 0); emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE); emu_wrptr(sc, 0, FXBA, 0); emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE); emu_wrptr(sc, 0, ADCBA, 0); /* disable channel interrupt */ emu_wr(sc, INTE, INTE_INTERVALTIMERENB | INTE_SAMPLERATETRACKER | INTE_PCIERRORENABLE, 4); emu_wrptr(sc, 0, CLIEL, 0); emu_wrptr(sc, 0, CLIEH, 0); emu_wrptr(sc, 0, SOLEL, 0); emu_wrptr(sc, 0, SOLEH, 0); /* wonder what these do... */ if (sc->audigy) { emu_wrptr(sc, 0, SPBYPASS, 0xf00); emu_wrptr(sc, 0, AC97SLOT, 0x3); } /* init envelope engine */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, DCYSUSV, ENV_OFF); emu_wrptr(sc, ch, IP, 0); emu_wrptr(sc, ch, VTFT, 0xffff); emu_wrptr(sc, ch, CVCF, 0xffff); emu_wrptr(sc, ch, PTRX, 0); emu_wrptr(sc, ch, CPF, 0); emu_wrptr(sc, ch, CCR, 0); emu_wrptr(sc, ch, PSST, 0); emu_wrptr(sc, ch, DSL, 0x10); emu_wrptr(sc, ch, CCCA, 0); emu_wrptr(sc, ch, Z1, 0); emu_wrptr(sc, ch, Z2, 0); emu_wrptr(sc, ch, FXRT, 0xd01c0000); emu_wrptr(sc, ch, ATKHLDM, 0); emu_wrptr(sc, ch, DCYSUSM, 0); emu_wrptr(sc, ch, IFATN, 0xffff); emu_wrptr(sc, ch, PEFE, 0); emu_wrptr(sc, ch, FMMOD, 0); emu_wrptr(sc, ch, TREMFRQ, 24); /* 1 Hz */ emu_wrptr(sc, ch, FM2FRQ2, 24); /* 1 Hz */ emu_wrptr(sc, ch, TEMPENV, 0); /*** these are last so OFF prevents writing ***/ emu_wrptr(sc, ch, LFOVAL2, 0); emu_wrptr(sc, ch, LFOVAL1, 0); emu_wrptr(sc, ch, ATKHLDV, 0); emu_wrptr(sc, ch, ENVVOL, 0); emu_wrptr(sc, ch, ENVVAL, 0); if (sc->audigy) { /* audigy cards need this to initialize correctly */ emu_wrptr(sc, ch, 0x4c, 0); emu_wrptr(sc, ch, 0x4d, 0); emu_wrptr(sc, ch, 0x4e, 0); emu_wrptr(sc, ch, 0x4f, 0); /* set default routing */ emu_wrptr(sc, ch, A_FXRT1, 0x03020100); emu_wrptr(sc, ch, A_FXRT2, 0x3f3f3f3f); emu_wrptr(sc, ch, A_SENDAMOUNTS, 0); } sc->voice[ch].vnum = ch; sc->voice[ch].slave = NULL; sc->voice[ch].busy = 0; sc->voice[ch].ismaster = 0; sc->voice[ch].running = 0; sc->voice[ch].b16 = 0; sc->voice[ch].stereo = 0; sc->voice[ch].speed = 0; sc->voice[ch].start = 0; sc->voice[ch].end = 0; sc->voice[ch].channel = NULL; } sc->pnum = sc->rnum = 0; /* * Init to 0x02109204 : * Clock accuracy = 0 (1000ppm) * Sample Rate = 2 (48kHz) * Audio Channel = 1 (Left of 2) * Source Number = 0 (Unspecified) * Generation Status = 1 (Original for Cat Code 12) * Cat Code = 12 (Digital Signal Mixer) * Mode = 0 (Mode 0) * Emphasis = 0 (None) * CP = 1 (Copyright unasserted) * AN = 0 (Audio data) * P = 0 (Consumer) */ spcs = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT; emu_wrptr(sc, 0, SPCS0, spcs); emu_wrptr(sc, 0, SPCS1, spcs); emu_wrptr(sc, 0, SPCS2, spcs); if (!sc->audigy) emu_initefx(sc); else if (sc->audigy2) { /* Audigy 2 */ /* from ALSA initialization code: */ /* Hack for Alice3 to work independent of haP16V driver */ u_int32_t tmp; /* Setup SRCMulti_I2S SamplingRate */ tmp = emu_rdptr(sc, 0, A_SPDIF_SAMPLERATE) & 0xfffff1ff; emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, tmp | 0x400); /* Setup SRCSel (Enable SPDIF, I2S SRCMulti) */ emu_wr(sc, 0x20, 0x00600000, 4); emu_wr(sc, 0x24, 0x00000014, 4); /* Setup SRCMulti Input Audio Enable */ emu_wr(sc, 0x20, 0x006e0000, 4); emu_wr(sc, 0x24, 0xff00ff00, 4); } SLIST_INIT(&sc->mem.blocks); sc->mem.ptb_pages = emu_malloc(sc, EMUMAXPAGES * sizeof(u_int32_t), &sc->mem.ptb_pages_addr); if (sc->mem.ptb_pages == NULL) return -1; sc->mem.silent_page = emu_malloc(sc, EMUPAGESIZE, &sc->mem.silent_page_addr); if (sc->mem.silent_page == NULL) { emu_free(sc, sc->mem.ptb_pages); return -1; } /* Clear page with silence & setup all pointers to this page */ bzero(sc->mem.silent_page, EMUPAGESIZE); tmp = (u_int32_t)(sc->mem.silent_page_addr) << 1; for (i = 0; i < EMUMAXPAGES; i++) sc->mem.ptb_pages[i] = tmp | i; emu_wrptr(sc, 0, PTB, (sc->mem.ptb_pages_addr)); emu_wrptr(sc, 0, TCB, 0); /* taken from original driver */ emu_wrptr(sc, 0, TCBS, 0); /* taken from original driver */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, MAPA, tmp | MAP_PTI_MASK); emu_wrptr(sc, ch, MAPB, tmp | MAP_PTI_MASK); } /* emu_memalloc(sc, EMUPAGESIZE); */ /* * Hokay, now enable the AUD bit * * Audigy * Enable Audio = 0 (enabled after fx processor initialization) * Mute Disable Audio = 0 * Joystick = 1 * * Audigy 2 * Enable Audio = 1 * Mute Disable Audio = 0 * Joystick = 1 * GP S/PDIF AC3 Enable = 1 * CD S/PDIF AC3 Enable = 1 * * EMU10K1 * Enable Audio = 1 * Mute Disable Audio = 0 * Lock Tank Memory = 1 * Lock Sound Memory = 0 * Auto Mute = 1 */ if (sc->audigy) { tmp = HCFG_AUTOMUTE | HCFG_JOYENABLE; if (sc->audigy2) /* Audigy 2 */ tmp = HCFG_AUDIOENABLE | HCFG_AC3ENABLE_CDSPDIF | HCFG_AC3ENABLE_GPSPDIF; emu_wr(sc, HCFG, tmp, 4); audigy_initefx(sc); /* from ALSA initialization code: */ /* enable audio and disable both audio/digital outputs */ emu_wr(sc, HCFG, emu_rd(sc, HCFG, 4) | HCFG_AUDIOENABLE, 4); emu_wr(sc, A_IOCFG, emu_rd(sc, A_IOCFG, 4) & ~A_IOCFG_GPOUT_AD, 4); if (sc->audigy2) { /* Audigy 2 */ /* Unmute Analog. * Set GPO6 to 1 for Apollo. This has to be done after * init Alice3 I2SOut beyond 48kHz. * So, sequence is important. */ emu_wr(sc, A_IOCFG, emu_rd(sc, A_IOCFG, 4) | A_IOCFG_GPOUT_A, 4); } } else { /* EMU10K1 initialization code */ tmp = HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE; if (sc->rev >= 6) tmp |= HCFG_JOYENABLE; emu_wr(sc, HCFG, tmp, 4); /* TOSLink detection */ sc->tos_link = 0; tmp = emu_rd(sc, HCFG, 4); if (tmp & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) { emu_wr(sc, HCFG, tmp | HCFG_GPOUT1, 4); DELAY(50); if (tmp != (emu_rd(sc, HCFG, 4) & ~HCFG_GPOUT1)) { sc->tos_link = 1; emu_wr(sc, HCFG, tmp, 4); } } } return 0; } static int emu_uninit(struct sc_info *sc) { u_int32_t ch; emu_wr(sc, INTE, 0, 4); for (ch = 0; ch < NUM_G; ch++) emu_wrptr(sc, ch, DCYSUSV, ENV_OFF); for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, VTFT, 0); emu_wrptr(sc, ch, CVCF, 0); emu_wrptr(sc, ch, PTRX, 0); emu_wrptr(sc, ch, CPF, 0); } if (sc->audigy) { /* stop fx processor */ emu_wrptr(sc, 0, A_DBG, A_DBG_SINGLE_STEP); } /* disable audio and lock cache */ emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4); emu_wrptr(sc, 0, PTB, 0); /* reset recording buffers */ emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE); emu_wrptr(sc, 0, MICBA, 0); emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE); emu_wrptr(sc, 0, FXBA, 0); emu_wrptr(sc, 0, FXWC, 0); emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE); emu_wrptr(sc, 0, ADCBA, 0); emu_wrptr(sc, 0, TCB, 0); emu_wrptr(sc, 0, TCBS, 0); /* disable channel interrupt */ emu_wrptr(sc, 0, CLIEL, 0); emu_wrptr(sc, 0, CLIEH, 0); emu_wrptr(sc, 0, SOLEL, 0); emu_wrptr(sc, 0, SOLEH, 0); /* init envelope engine */ if (!SLIST_EMPTY(&sc->mem.blocks)) device_printf(sc->dev, "warning: memblock list not empty\n"); emu_free(sc, sc->mem.ptb_pages); emu_free(sc, sc->mem.silent_page); if(sc->mpu) mpu401_uninit(sc->mpu); return 0; } static int emu_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case EMU10K1_PCI_ID: s = "Creative EMU10K1"; break; case EMU10K2_PCI_ID: if (pci_get_revid(dev) == 0x04) s = "Creative Audigy 2 (EMU10K2)"; else s = "Creative Audigy (EMU10K2)"; break; case EMU10K3_PCI_ID: s = "Creative Audigy 2 (EMU10K3)"; break; default: return ENXIO; } device_set_desc(dev, s); return BUS_PROBE_LOW_PRIORITY; } static int emu_pci_attach(device_t dev) { struct ac97_info *codec = NULL; struct sc_info *sc; u_int32_t data; int i, gotmic; char status[SND_STATUSLEN]; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10k1 softc"); sc->dev = dev; sc->type = pci_get_devid(dev); sc->rev = pci_get_revid(dev); sc->audigy = sc->type == EMU10K2_PCI_ID || sc->type == EMU10K3_PCI_ID; sc->audigy2 = (sc->audigy && sc->rev == 0x04); sc->nchans = sc->audigy ? 8 : 4; sc->addrmask = sc->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK; data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); i = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE); if (sc->reg == NULL) { device_printf(dev, "unable to map register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, 4096, EMU_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/1 << 31, /* can only access 0-2gb */ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (emu_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, emu_ac97); if (codec == NULL) goto bad; gotmic = (ac97_getcaps(codec) & AC97_CAP_MICCHANNEL) ? 1 : 0; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; emu_midiattach(sc); i = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, emu_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(sc->reg), rman_get_start(sc->irq), PCM_KLDSTRING(snd_emu10k1)); if (pcm_register(dev, sc, sc->nchans, gotmic ? 3 : 2)) goto bad; for (i = 0; i < sc->nchans; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); for (i = 0; i < (gotmic ? 3 : 2); i++) pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return ENXIO; } static int emu_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* shutdown chip */ emu_uninit(sc); bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } /* add suspend, resume */ static device_method_t emu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emu_pci_probe), DEVMETHOD(device_attach, emu_pci_attach), DEVMETHOD(device_detach, emu_pci_detach), { 0, 0 } }; static driver_t emu_driver = { "pcm", emu_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_emu10k1, pci, emu_driver, pcm_devclass, 0, 0); DRIVER_MODULE(snd_emu10k1, cardbus, emu_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_emu10k1, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10k1, 1); MODULE_DEPEND(snd_emu10k1, midi, 1, 1, 1); /* dummy driver to silence the joystick device */ static int emujoy_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case 0x70021102: s = "Creative EMU10K1 Joystick"; device_quiet(dev); break; case 0x70031102: s = "Creative EMU10K2 Joystick"; device_quiet(dev); break; } if (s) device_set_desc(dev, s); return s ? -1000 : ENXIO; } static int emujoy_pci_attach(device_t dev) { return 0; } static int emujoy_pci_detach(device_t dev) { return 0; } static device_method_t emujoy_methods[] = { DEVMETHOD(device_probe, emujoy_pci_probe), DEVMETHOD(device_attach, emujoy_pci_attach), DEVMETHOD(device_detach, emujoy_pci_detach), { 0, 0 } }; static driver_t emujoy_driver = { "emujoy", emujoy_methods, 8, }; static devclass_t emujoy_devclass; DRIVER_MODULE(emujoy, pci, emujoy_driver, emujoy_devclass, 0, 0); Index: head/sys/dev/sound/pci/emu10kx-pcm.c =================================================================== --- head/sys/dev/sound/pci/emu10kx-pcm.c (revision 170520) +++ head/sys/dev/sound/pci/emu10kx-pcm.c (revision 170521) @@ -1,1187 +1,1194 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 2003-2006 Yuriy Tsibizov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mixer_if.h" #include "opt_emu10kx.h" #include #include "emu10k1-alsa%diked.h" struct emu_pcm_pchinfo { int spd; int fmt; int blksz; int run; struct emu_voice *master; struct emu_voice *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; struct emu_pcm_rchinfo { int spd; int fmt; int blksz; int run; uint32_t idxreg; uint32_t basereg; uint32_t sizereg; uint32_t setupreg; uint32_t irqmask; uint32_t iprmask; int ihandle; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; }; /* XXX Hardware playback channels */ #define MAX_CHANNELS 4 #if MAX_CHANNELS > 13 #error Too many hardware channels defined. 13 is the maximum #endif struct emu_pcm_info { struct mtx *lock; device_t dev; /* device information */ struct snddev_info *devinfo; /* pcm device information */ struct emu_sc_info *card; struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */ int pnum; /* next free channel number */ struct emu_pcm_rchinfo rch_adc; struct emu_pcm_rchinfo rch_efx; struct emu_route rt; struct emu_route rt_mono; int route; int ihandle; /* interrupt handler */ unsigned int bufsz; int is_emu10k1; struct ac97_info *codec; uint32_t ac97_state[0x7F]; }; static uint32_t emu_rfmt_adc[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps emu_reccaps_adc = { 8000, 48000, emu_rfmt_adc, 0 }; static uint32_t emu_rfmt_efx[] = { AFMT_S16_LE, 0 }; static struct pcmchan_caps emu_reccaps_efx_live = { 48000*32, 48000*32, emu_rfmt_efx, 0 }; static struct pcmchan_caps emu_reccaps_efx_audigy = { 48000*64, 48000*64, emu_rfmt_efx, 0 }; static uint32_t emu_pfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static uint32_t emu_pfmt_mono[] = { AFMT_U8, AFMT_S16_LE, 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0}; static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000}; static uint32_t emu_pcm_intr(void *pcm, uint32_t stat); static const struct emu_dspmix_props { u_int8_t present; } dspmix [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = {1}, [SOUND_MIXER_PCM] = {1}, }; static int emu_dspmixer_init(struct snd_mixer *m) { int i; int v; v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix[i].present) v |= 1 << i; } mix_setdevs(m, v); mix_setrecdevs(m, 0); return (0); } static int emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct emu_pcm_info *sc; sc = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_VOLUME: switch (sc->route) { case RT_REAR: emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_PCM: switch (sc->route) { case RT_REAR: emumix_set_volume(sc->card, M_FX2_REAR_L, left); emumix_set_volume(sc->card, M_FX3_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2); break; } break; default: device_printf(sc->dev, "mixer error: unknown device %d\n", dev); } return (0); } static int emu_dspmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused) { return (0); } static kobj_method_t emudspmixer_methods[] = { KOBJMETHOD(mixer_init, emu_dspmixer_init), KOBJMETHOD(mixer_set, emu_dspmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc), { 0, 0 } }; MIXER_DECLARE(emudspmixer); /* * AC97 emulation code for Audigy and later cards. * Some parts of AC97 codec are not used by hardware, but can be used * to change some DSP controls via AC97 mixer interface. This includes: * - master volume controls MASTER_FRONT_[R|L] * - pcm volume controls FX[0|1]_FRONT_[R|L] * - rec volume controls MASTER_REC_[R|L] * We do it because we need to put it under user control.... * We also keep some parts of AC97 disabled to get better sound quality */ #define AC97LEFT(x) ((x & 0x7F00)>>8) #define AC97RIGHT(x) (x & 0x007F) #define AC97MUTE(x) ((x & 0x8000)>>15) #define BIT4_TO100(x) (100-(x)*100/(0x0f)) #define BIT6_TO100(x) (100-(x)*100/(0x3f)) #define BIT4_TO255(x) (255-(x)*255/(0x0f)) #define BIT6_TO255(x) (255-(x)*255/(0x3f)) #define V100_TOBIT6(x) (0x3f*(100-x)/100) #define V100_TOBIT4(x) (0x0f*(100-x)/100) #define AC97ENCODE(x_muted,x_left,x_right) (((x_muted&1)<<15) | ((x_left&0x3f)<<8) | (x_right&0x3f)) static int emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno) { int use_ac97; int emulated; int tmp; use_ac97 = 1; emulated = 0; switch (regno) { case AC97_MIX_MASTER: emulated = sc->ac97_state[AC97_MIX_MASTER]; use_ac97 = 0; break; case AC97_MIX_PCM: emulated = sc->ac97_state[AC97_MIX_PCM]; use_ac97 = 0; break; case AC97_REG_RECSEL: emulated = 0x0505; use_ac97 = 0; break; case AC97_MIX_RGAIN: emulated = sc->ac97_state[AC97_MIX_RGAIN]; use_ac97 = 0; break; } emu_wr(sc->card, AC97ADDRESS, regno, 1); tmp = emu_rd(sc->card, AC97DATA, 2); if (use_ac97) emulated = tmp; return (emulated); } static void emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data) { int write_ac97; int left, right; uint32_t emu_left, emu_right; int is_mute; write_ac97 = 1; left = AC97LEFT(data); emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */ right = AC97RIGHT(data); emu_right = BIT6_TO100(right); is_mute = AC97MUTE(data); if (is_mute) emu_left = emu_right = 0; switch (regno) { /* TODO: reset emulator on AC97_RESET */ case AC97_MIX_MASTER: emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left); emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 main out */ break; case AC97_MIX_PCM: /* PCM OUT VOL */ emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left); emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 PCM out */ break; case AC97_REG_RECSEL: /* * PCM recording source is set to "stereo mix" (labeled "vol" * in mixer) XXX !I can't remember why! */ data = 0x0505; break; case AC97_MIX_RGAIN: /* RECORD GAIN */ emu_left = BIT4_TO100(left); /* rgain is 4-bit */ emu_right = BIT4_TO100(right); emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left); emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right); /* * Record gain on AC97 should stay zero to get AC97 sound on * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not * directly connected to any output, only to EMU10K2 chip Use * this control to set AC97 mix volume inside EMU10K2 chip */ sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f); data = 0x0000; break; } if (write_ac97) { emu_wr(sc->card, AC97ADDRESS, regno, 1); emu_wr(sc->card, AC97DATA, data, 2); } } static int emu_erdcd(kobj_t obj __unused, void *devinfo, int regno) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; return (emu_ac97_read_emulation(sc, regno)); } static int emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; emu_ac97_write_emulation(sc, regno, data); return (0); } static kobj_method_t emu_eac97_methods[] = { KOBJMETHOD(ac97_read, emu_erdcd), KOBJMETHOD(ac97_write, emu_ewrcd), {0, 0} }; AC97_DECLARE(emu_eac97); /* real ac97 codec */ static int emu_rdcd(kobj_t obj __unused, void *devinfo, int regno) { int rd; struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard")); emu_wr(sc->card, AC97ADDRESS, regno, 1); rd = emu_rd(sc->card, AC97DATA, 2); return (rd); } static int emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard")); emu_wr(sc->card, AC97ADDRESS, regno, 1); emu_wr(sc->card, AC97DATA, data, 2); return (0); } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), {0, 0} }; AC97_DECLARE(emu_ac97); static int emu_k1_recval(int speed) { int val; val = 0; while ((val < 7) && (speed < emu10k1_adcspeed[val])) val++; if (val == 6) val=5; /* XXX 8kHz does not work */ return (val); } static int emu_k2_recval(int speed) { int val; val = 0; while ((val < 8) && (speed < emu10k2_adcspeed[val])) val++; if (val == 7) val=6; /* XXX 8kHz does not work */ return (val); } static void * emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); KASSERT(sc->card != NULL, ("empchan_init: no soundcard")); if (sc->pnum >= MAX_CHANNELS) return (NULL); ch = &(sc->pch[sc->pnum++]); ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz; ch->fmt = AFMT_U8; ch->spd = 8000; ch->master = emu_valloc(sc->card); /* * XXX we have to allocate slave even for mono channel until we * fix emu_vfree to handle this case. */ ch->slave = emu_valloc(sc->card); ch->timer = emu_timer_create(sc->card); r = (emu_vinit(sc->card, ch->master, ch->slave, EMU_PLAY_BUFSZ, ch->buffer)) ? NULL : ch; return (r); } static int emupchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); if (ch->slave != NULL) emu_vfree(sc->card, ch->slave); emu_vfree(sc->card, ch->master); return (0); } static int emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static int emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->spd = speed; return (ch->spd); } static int emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (blocksize > ch->pcm->bufsz) blocksize = ch->pcm->bufsz; snd_mtxlock(sc->lock); ch->blksz = blocksize; emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer)); snd_mtxunlock(sc->lock); return (blocksize); } static int emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return (0); + snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */ if (go == PCMTRIG_START) { emu_vsetup(ch->master, ch->fmt, ch->spd); if ((ch->fmt & AFMT_STEREO) == AFMT_STEREO) emu_vroute(sc->card, &(sc->rt), ch->master); else emu_vroute(sc->card, &(sc->rt_mono), ch->master); emu_vwrite(sc->card, ch->master); emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer)); emu_timer_enable(sc->card, ch->timer, 1); } /* PCM interrupt handler will handle PCMTRIG_STOP event */ ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc->card, ch->master, ch->run); snd_mtxunlock(sc->lock); return (0); } static int emupchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_vpos(sc->card, ch->master); return (r); } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; switch (sc->route) { case RT_FRONT: /* FALLTHROUGH */ case RT_REAR: /* FALLTHROUGH */ case RT_SIDE: return (&emu_playcaps); break; case RT_CENTER: /* FALLTHROUGH */ case RT_SUB: return (&emu_playcaps_mono); break; } return (NULL); } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), {0, 0} }; CHANNEL_DECLARE(emupchan); static void * emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch_adc; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; /* We rise interrupt for half-full buffer */ ch->fmt = AFMT_U8; ch->spd = 8000; ch->idxreg = sc->is_emu10k1 ? ADCIDX : A_ADCIDX; ch->basereg = ADCBA; ch->sizereg = ADCBS; ch->setupreg = ADCCR; ch->irqmask = INTE_ADCBUFENABLE; ch->iprmask = IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static int emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; if (ch->pcm->is_emu10k1) { speed = emu10k1_adcspeed[emu_k1_recval(speed)]; } else { speed = emu10k2_adcspeed[emu_k2_recval(speed)]; } ch->spd = speed; return (ch->spd); } static int emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->blksz = blocksize; /* If blocksize is less than half of buffer size we will not get interrupt in time and channel will die due to interrupt timeout */ if(ch->blksz < (ch->pcm->bufsz / 2)) ch->blksz = ch->pcm->bufsz / 2; return (ch->blksz); } static int emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t val, sz; + if (!PCMTRIG_COMMON(go)) + return (0); + switch (sc->bufsz) { case 4096: sz = ADCBS_BUFSIZE_4096; break; case 8192: sz = ADCBS_BUFSIZE_8192; break; case 16384: sz = ADCBS_BUFSIZE_16384; break; case 32768: sz = ADCBS_BUFSIZE_32768; break; case 65536: sz = ADCBS_BUFSIZE_65536; break; default: sz = ADCBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); val = sc->is_emu10k1 ? ADCCR_LCHANENABLE : A_ADCCR_LCHANENABLE; if (ch->fmt & AFMT_STEREO) val |= sc->is_emu10k1 ? ADCCR_RCHANENABLE : A_ADCCR_RCHANENABLE; val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd); emu_wrptr(sc->card, 0, ch->setupreg, 0); emu_wrptr(sc->card, 0, ch->setupreg, val); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc->card, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc->card, 0, ch->setupreg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static int emurchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { return (&emu_reccaps_adc); } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), {0, 0} }; CHANNEL_DECLARE(emurchan); static void * emufxrchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); if (sc == NULL) return (NULL); ch = &(sc->rch_efx); ch->fmt = AFMT_S16_LE; ch->spd = sc->is_emu10k1 ? 48000*32 : 48000 * 64; ch->idxreg = FXIDX; ch->basereg = FXBA; ch->sizereg = FXBS; ch->irqmask = INTE_EFXBUFENABLE; ch->iprmask = IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emufxrchan_setformat(kobj_t obj __unused, void *c_devinfo __unused, uint32_t format) { if (format == AFMT_S16_LE) return (0); return (-1); } static int emufxrchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; /* FIXED RATE CHANNEL */ return (ch->spd); } static int emufxrchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->blksz = blocksize; /* If blocksize is less than half of buffer size we will not get interrupt in time and channel will die due to interrupt timeout */ if(ch->blksz < (ch->pcm->bufsz / 2)) ch->blksz = ch->pcm->bufsz / 2; return (ch->blksz); } static int emufxrchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t sz; + + if (!PCMTRIG_COMMON(go)) + return (0); switch (sc->bufsz) { case 4096: sz = ADCBS_BUFSIZE_4096; break; case 8192: sz = ADCBS_BUFSIZE_8192; break; case 16384: sz = ADCBS_BUFSIZE_16384; break; case 32768: sz = ADCBS_BUFSIZE_32768; break; case 65536: sz = ADCBS_BUFSIZE_65536; break; default: sz = ADCBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); /* SB Live! is limited to 32 mono channels. Audigy has 64 mono channels, each of them is selected from one of two A_FXWC[1|2] registers. */ /* XXX there is no way to demultiplex this streams for now */ if(sc->is_emu10k1) { emu_wrptr(sc->card, 0, FXWC, 0xffffffff); } else { emu_wrptr(sc->card, 0, A_FXWC1, 0xffffffff); emu_wrptr(sc->card, 0, A_FXWC2, 0xffffffff); } break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; if(sc->is_emu10k1) { emu_wrptr(sc->card, 0, FXWC, 0x0); } else { emu_wrptr(sc->card, 0, A_FXWC1, 0x0); emu_wrptr(sc->card, 0, A_FXWC2, 0x0); } emu_wrptr(sc->card, 0, ch->sizereg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static int emufxrchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emufxrchan_getcaps(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if(sc->is_emu10k1) return (&emu_reccaps_efx_live); return (&emu_reccaps_efx_audigy); } static kobj_method_t emufxrchan_methods[] = { KOBJMETHOD(channel_init, emufxrchan_init), KOBJMETHOD(channel_setformat, emufxrchan_setformat), KOBJMETHOD(channel_setspeed, emufxrchan_setspeed), KOBJMETHOD(channel_setblocksize, emufxrchan_setblocksize), KOBJMETHOD(channel_trigger, emufxrchan_trigger), KOBJMETHOD(channel_getptr, emufxrchan_getptr), KOBJMETHOD(channel_getcaps, emufxrchan_getcaps), {0, 0} }; CHANNEL_DECLARE(emufxrchan); static uint32_t emu_pcm_intr(void *pcm, uint32_t stat) { struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm; uint32_t ack; int i; ack = 0; if (stat & IPR_INTERVALTIMER) { ack |= IPR_INTERVALTIMER; for (i = 0; i < MAX_CHANNELS; i++) if (sc->pch[i].channel) { if (sc->pch[i].run == 1) chn_intr(sc->pch[i].channel); else emu_timer_enable(sc->card, sc->pch[i].timer, 0); } } if (stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) { ack |= stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL); if (sc->rch_adc.channel) chn_intr(sc->rch_adc.channel); } if (stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL)) { ack |= stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL); if (sc->rch_efx.channel) chn_intr(sc->rch_efx.channel); } return (ack); } static int emu_pcm_init(struct emu_pcm_info *sc) { sc->bufsz = pcm_getbuffersize(sc->dev, EMUPAGESIZE, EMU_REC_BUFSZ, EMU_MAX_BUFSZ); return (0); } static int emu_pcm_uninit(struct emu_pcm_info *sc __unused) { return (0); } static int emu_pcm_probe(device_t dev) { uintptr_t func, route, r; const char *rt; char buffer[255]; r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func); if (func != SCF_PCM) return (ENXIO); rt = "UNKNOWN"; r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); switch (route) { case RT_FRONT: rt = "front"; break; case RT_REAR: rt = "rear"; break; case RT_CENTER: rt = "center"; break; case RT_SUB: rt = "subwoofer"; break; case RT_SIDE: rt = "side"; break; case RT_MCHRECORD: rt = "multichannel recording"; break; } snprintf(buffer, 255, "EMU10Kx DSP %s PCM interface", rt); device_set_desc_copy(dev, buffer); return (0); } static int emu_pcm_attach(device_t dev) { struct emu_pcm_info *sc; unsigned int i; char status[SND_STATUSLEN]; uint32_t inte, ipr; uintptr_t route, r, is_emu10k1; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENXIO); } bzero(sc, sizeof(*sc)); sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev))); if (sc->card == NULL) { device_printf(dev, "cannot get bridge conf\n"); return (ENXIO); } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10kx softc"); sc->dev = dev; r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &is_emu10k1); sc->is_emu10k1 = is_emu10k1 ? 1 : 0; sc->codec = NULL; for (i = 0; i < 8; i++) { sc->rt.routing_left[i] = i; sc->rt.amounts_left[i] = 0x00; sc->rt.routing_right[i] = i; sc->rt.amounts_right[i] = 0x00; } for (i = 0; i < 8; i++) { sc->rt_mono.routing_left[i] = i; sc->rt_mono.amounts_left[i] = 0x00; sc->rt_mono.routing_right[i] = i; sc->rt_mono.amounts_right[i] = 0x00; } r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); sc->route = route; switch (route) { case RT_FRONT: sc->rt.amounts_left[0] = 0xff; sc->rt.amounts_right[1] = 0xff; sc->rt_mono.amounts_left[0] = 0xff; sc->rt_mono.amounts_left[1] = 0xff; if (sc->is_emu10k1) sc->codec = AC97_CREATE(dev, sc, emu_ac97); else sc->codec = AC97_CREATE(dev, sc, emu_eac97); if (sc->codec == NULL) { if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize DSP mixer\n"); goto bad; } } else if (mixer_init(dev, ac97_getmixerclass(), sc->codec) == -1) { device_printf(dev, "can't initialize AC97 mixer!\n"); goto bad; } break; case RT_REAR: sc->rt.amounts_left[2] = 0xff; sc->rt.amounts_right[3] = 0xff; sc->rt_mono.amounts_left[2] = 0xff; sc->rt_mono.amounts_left[3] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_CENTER: sc->rt.amounts_left[4] = 0xff; sc->rt_mono.amounts_left[4] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SUB: sc->rt.amounts_left[5] = 0xff; sc->rt_mono.amounts_left[5] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SIDE: sc->rt.amounts_left[6] = 0xff; sc->rt.amounts_right[7] = 0xff; sc->rt_mono.amounts_left[6] = 0xff; sc->rt_mono.amounts_left[7] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_MCHRECORD: /* XXX add mixer here */ break; default: device_printf(dev, "invalid default route\n"); goto bad; } inte = INTE_INTERVALTIMERENB; ipr = IPR_INTERVALTIMER; /* Used by playback */ sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc); if (emu_pcm_init(sc) == -1) { device_printf(dev, "unable to initialize PCM part of the card\n"); goto bad; } /* XXX we should better get number of available channels from parent */ if (pcm_register(dev, sc, (route == RT_FRONT) ? MAX_CHANNELS : 1, (route == RT_FRONT) ? 1 : 0)) { device_printf(dev, "can't register PCM channels!\n"); goto bad; } sc->pnum = 0; if (route != RT_MCHRECORD) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); if (route == RT_FRONT) { for (i = 1; i < MAX_CHANNELS; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); } if (route == RT_MCHRECORD) pcm_addchan(dev, PCMDIR_REC, &emufxrchan_class, sc); snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static int emu_pcm_detach(device_t dev) { int r; struct emu_pcm_info *sc; sc = pcm_getdevinfo(dev); r = pcm_unregister(dev); if (r) return (r); emu_pcm_uninit(sc); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (0); } static device_method_t emu_pcm_methods[] = { DEVMETHOD(device_probe, emu_pcm_probe), DEVMETHOD(device_attach, emu_pcm_attach), DEVMETHOD(device_detach, emu_pcm_detach), {0, 0} }; static driver_t emu_pcm_driver = { "pcm", emu_pcm_methods, PCM_SOFTC_SIZE, NULL, 0, NULL }; DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER); Index: head/sys/dev/sound/pci/es137x.c =================================================================== --- head/sys/dev/sound/pci/es137x.c (revision 170520) +++ head/sys/dev/sound/pci/es137x.c (revision 170521) @@ -1,1915 +1,1915 @@ /*- * Support the ENSONIQ AudioPCI board and Creative Labs SoundBlaster PCI * boards based on the ES1370, ES1371 and ES1373 chips. * * Copyright (c) 1999 Russell Cattelan * Copyright (c) 1999 Cameron Grant * Copyright (c) 1998 by 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. * * 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. */ /* * 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. * */ #include #include #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); #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[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 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 < 10) ? 0x80 : 15 - (l - 10) / 6; 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 < 10) ? 0x80 : 15 - (r - 10) / 6; 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 int 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), { 0, 0 } }; MIXER_DECLARE(es1370_mixer); /* -------------------------------------------------------------------- */ static int es1370_wrcodec(struct es_info *es, unsigned char i, unsigned char data) { unsigned int t; ES_LOCK_ASSERT(es); for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1370_REG_STATUS, 4) & STAT_CSTAT) == 0) { es_wr(es, ES1370_REG_CODEC, ((unsigned short)i << CODEC_INDEX_SHIFT) | data, 2); return (0); } DELAY(1); } device_printf(es->dev, "%s: timed out\n", __func__); return (-1); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct es_info *es = devinfo; struct es_chinfo *ch; uint32_t index; ES_LOCK(es); if (dir == PCMDIR_PLAY) { index = ES_GP(es->escfg); es->escfg = ES_SET_GP(es->escfg, index + 1); if (index == 0) index = ES_DAC_FIRST(es->escfg); else if (index == 1) index = ES_DAC_SECOND(es->escfg); else { device_printf(es->dev, "Invalid ES_GP index: %d\n", index); ES_UNLOCK(es); return (NULL); } if (!(index == ES_DAC1 || index == ES_DAC2)) { device_printf(es->dev, "Unknown DAC: %d\n", index + 1); ES_UNLOCK(es); return (NULL); } if (es->ch[index].channel != NULL) { device_printf(es->dev, "DAC%d already initialized!\n", index + 1); ES_UNLOCK(es); return (NULL); } } else index = ES_ADC; ch = &es->ch[index]; ch->index = index; ch->num = es->num++; ch->caps = es_caps; if (ES_IS_ES1370(es->escfg)) { if (ch->index == ES_DAC1) { ch->caps.maxspeed = ES1370_DAC1_MAXSPEED; ch->caps.minspeed = ES1370_DAC1_MINSPEED; } else { uint32_t fixed_rate = ES_FIXED_RATE(es->escfg); if (!(fixed_rate < es_caps.minspeed || fixed_rate > es_caps.maxspeed)) { ch->caps.maxspeed = fixed_rate; ch->caps.minspeed = fixed_rate; } } } ch->parent = es; ch->channel = c; ch->buffer = b; ch->bufsz = es->bufsz; ch->blkcnt = es->blkcnt; ch->blksz = ch->bufsz / ch->blkcnt; ch->dir = dir; ES_UNLOCK(es); if (sndbuf_alloc(ch->buffer, es->parent_dmat, 0, ch->bufsz) != 0) return (NULL); ES_LOCK(es); if (dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_DAC2_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_ADC_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } ES_UNLOCK(es); return (ch); } static int eschan_setformat(kobj_t obj, void *data, uint32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es->sctrl &= ~SCTRL_P1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P1SEB; if (format & AFMT_STEREO) es->sctrl |= SCTRL_P1SMB; } else { es->sctrl &= ~SCTRL_P2FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; } } else { es->sctrl &= ~SCTRL_R1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); ch->fmt = format; return (0); } static int 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) 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 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 int eschan1371_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t i; int delta; ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) i = es1371_dac_rate(es, speed, ch->index); /* play */ else i = es1371_adc_rate(es, speed, ch->index); /* record */ ES_UNLOCK(es); delta = (speed > i) ? (speed - i) : (i - speed); if (delta < 2) return (speed); return (i); } static int eschan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; blksz &= ES_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN)) blksz = sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN; if (blksz < ES_BLK_MIN) blksz = ES_BLK_MIN; if (blkcnt > ES_DMA_SEGS_MAX) blkcnt = ES_DMA_SEGS_MAX; if (blkcnt < ES_DMA_SEGS_MIN) blkcnt = ES_DMA_SEGS_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= ES_DMA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ES_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(es->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->bufsz = sndbuf_getsize(ch->buffer); ch->blksz = sndbuf_getblksz(ch->buffer); ch->blkcnt = sndbuf_getblkcnt(ch->buffer); return (1); } static int 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 (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) - return (0); + if (!PCMTRIG_COMMON(go)) + return 0; ES_LOCK(es); cnt = (ch->blksz / sndbuf_getbps(ch->buffer)) - 1; if (ch->fmt & AFMT_16BIT) b |= 0x02; if (ch->fmt & AFMT_STEREO) 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 int 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), { 0, 0 } }; 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), { 0, 0 } }; CHANNEL_DECLARE(eschan1371); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void es_intr(void *p) { struct es_info *es = p; uint32_t intsrc, sctrl; ES_LOCK(es); if (es->polling != 0) { ES_UNLOCK(es); return; } intsrc = es_rd(es, ES1370_REG_STATUS, 4); if ((intsrc & STAT_INTR) == 0) { ES_UNLOCK(es); return; } sctrl = es->sctrl; if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; es_wr(es, ES1370_REG_SERIAL_CONTROL, sctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); if (intsrc & STAT_ADC) chn_intr(es->ch[ES_ADC].channel); if (intsrc & STAT_DAC1) chn_intr(es->ch[ES_DAC1].channel); if (intsrc & STAT_DAC2) chn_intr(es->ch[ES_DAC2].channel); } /* ES1370 specific */ static int es1370_init(struct es_info *es) { uint32_t fixed_rate; int r, single_pcm; /* ES1370 default to fixed rate operation */ if (resource_int_value(device_get_name(es->dev), device_get_unit(es->dev), "fixed_rate", &r) == 0) { fixed_rate = r; if (fixed_rate) { if (fixed_rate < es_caps.minspeed) fixed_rate = es_caps.minspeed; if (fixed_rate > es_caps.maxspeed) fixed_rate = es_caps.maxspeed; } } else fixed_rate = es_caps.maxspeed; if (resource_int_value(device_get_name(es->dev), device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) single_pcm = (r != 0) ? 1 : 0; else single_pcm = 1; ES_LOCK(es); if (ES_NUMPLAY(es->escfg) == 1) single_pcm = 1; /* This is ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 1); if (fixed_rate) es->escfg = ES_SET_FIXED_RATE(es->escfg, fixed_rate); else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); fixed_rate = DSP_DEFAULT_SPEED; } if (single_pcm) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); es->ctrl = CTRL_CDC_EN | CTRL_JYSTK_EN | CTRL_SERR_DIS | (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); es->ctrl |= 3 << CTRL_SH_WTSRSEL; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es->sctrl = 0; es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); /* No RST, PD */ es1370_wrcodec(es, CODEC_RES_PD, 3); /* * CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; * program DAC_SYNC=0! */ es1370_wrcodec(es, CODEC_CSEL, 0); /* Recording source is mixer */ es1370_wrcodec(es, CODEC_ADSEL, 0); /* MIC amp is 0db */ es1370_wrcodec(es, CODEC_MGAIN, 0); ES_UNLOCK(es); return (0); } /* ES1371 specific */ int es1371_init(struct es_info *es) { uint32_t cssr, devid, revid, subdev; int idx; ES_LOCK(es); /* This is NOT ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 0); es->num = 0; es->sctrl = 0; cssr = 0; devid = pci_get_devid(es->dev); revid = pci_get_revid(es->dev); subdev = (pci_get_subdevice(es->dev) << 16) | pci_get_subvendor(es->dev); /* * Joyport blacklist. Either we're facing with broken hardware * or because this hardware need special (unknown) initialization * procedures. */ switch (subdev) { case 0x20001274: /* old Ensoniq */ es->ctrl = 0; break; default: es->ctrl = CTRL_JYSTK_EN; break; } if (devid == CT4730_PCI_ID) { /* XXX amplifier hack? */ es->ctrl |= (1 << 16); } /* initialize the chips */ es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1371_REG_LEGACY, 0, 4); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { cssr = 1 << 29; es_wr(es, ES1370_REG_STATUS, cssr, 4); DELAY(20000); } /* AC'97 warm reset to start the bitclk */ es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1371_REG_LEGACY, ES1371_SYNC_RES, 4); DELAY(2000); es_wr(es, ES1370_REG_CONTROL, es->sctrl, 4); es1371_wait_src_ready(es); /* Init the sample rate converter */ es_wr(es, ES1371_REG_SMPRATE, ES1371_DIS_SRC, 4); for (idx = 0; idx < 0x80; idx++) es1371_src_write(es, idx, 0); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); es1371_adc_rate(es, 22050, ES_ADC); es1371_dac_rate(es, 22050, ES_DAC1); es1371_dac_rate(es, 22050, ES_DAC2); /* * WARNING: * enabling the sample rate converter without properly programming * its parameters causes the chip to lock up (the SRC busy bit will * be stuck high, and I've found no way to rectify this other than * power cycle) */ es1371_wait_src_ready(es); es_wr(es, ES1371_REG_SMPRATE, 0, 4); /* try to reset codec directly */ es_wr(es, ES1371_REG_CODEC, 0, 4); es_wr(es, ES1370_REG_STATUS, cssr, 4); ES_UNLOCK(es); return (0); } /* -------------------------------------------------------------------- */ static int es1371_wrcd(kobj_t obj, void *s, int addr, uint32_t data) { uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; for (t = 0; t < 0x1000; t++) { if (!es_rd(es, ES1371_REG_CODEC & CODEC_WIP, 4)) break; } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) break; } /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; } es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); return (0); } static int es1371_rdcd(kobj_t obj, void *s, int addr) { uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; for (t = 0; t < 0x1000; t++) { if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) break; } /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; } es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; } return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static kobj_method_t es1371_ac97_methods[] = { KOBJMETHOD(ac97_read, es1371_rdcd), KOBJMETHOD(ac97_write, es1371_wrcd), { 0, 0 } }; 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); } } #ifdef SND_DYNSYSCTL 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; struct cdev *i_dev; device_t dev; uint32_t val, set; int recsrc, level, err; dev = oidp->oid_arg1; d = device_get_softc(dev); if (d == NULL || 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); i_dev = d->mixer_dev; if (mixer_ioctl(i_dev, 0, (caddr_t)&recsrc, 0, NULL) != EBADF) return (EBUSY); err = mixer_ioctl(i_dev, MIXER_READ(SOUND_MIXER_PCM), (caddr_t)&level, -1, NULL); if (!err) err = mixer_ioctl(i_dev, MIXER_READ(SOUND_MIXER_RECSRC), (caddr_t)&recsrc, -1, NULL); if (err) return (err); if (level < 0) return (EINVAL); ES_LOCK(es); if (es->ctrl & (CTRL_ADC_EN | CTRL_DAC1_EN | CTRL_DAC2_EN)) { ES_UNLOCK(es); 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); m = i_dev->si_drv1; if (!val) { mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) | (1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) | (1 << SOUND_MIXER_SYNTH)); err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), (caddr_t)&level, -1, NULL); } else { err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), (caddr_t)&level, -1, NULL); mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) & ~(1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) & ~(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 = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_RECSRC), (caddr_t)&recsrc, -1, NULL); } 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); } #endif /* SND_DYNSYSCTL */ static void es_init_sysctls(device_t dev) { #ifdef SND_DYNSYSCTL 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, 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, 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, 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, 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, dev, sizeof(dev), sysctl_es_polling, "I", "Enable polling mode"); #endif /* SND_DYNSYSCTL */ } static int es_pci_attach(device_t dev) { uint32_t data; 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; if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENXIO); } es->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_es137x softc"); es->dev = dev; es->escfg = 0; mapped = 0; pci_enable_busmaster(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { 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 && (data & PCIM_CMD_PORTEN)) { 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, CALLOUT_MPSAFE); es->poll_ticks = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) es->polling = 1; else es->polling = 0; es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= ES_BLK_ALIGN; if (i < ES_BLK_MIN) i = ES_BLK_MIN; es->blkcnt = es->bufsz / i; i = 0; while (es->blkcnt >> i) i++; es->blkcnt = 1 << (i - 1); if (es->blkcnt < ES_DMA_SEGS_MIN) es->blkcnt = ES_DMA_SEGS_MIN; else if (es->blkcnt > ES_DMA_SEGS_MAX) es->blkcnt = ES_DMA_SEGS_MAX; } else es->blkcnt = 2; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &dac_cfg) == 0) { if (dac_cfg < 0 || dac_cfg > 3) dac_cfg = ES_DEFAULT_DAC_CFG; } else dac_cfg = ES_DEFAULT_DAC_CFG; switch (dac_cfg) { case 0: /* Enable all DAC: DAC1, DAC2 */ numplay = 2; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); break; case 1: /* Only DAC1 */ numplay = 1; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); break; case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ numplay = 2; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); break; case 2: /* Only DAC2 */ default: numplay = 1; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); break; } es->escfg = ES_SET_NUMPLAY(es->escfg, numplay); es->escfg = ES_SET_NUMREC(es->escfg, 1); devid = pci_get_devid(dev); switch (devid) { case ES1371_PCI_ID: case ES1371_PCI_ID2: case CT5880_PCI_ID: case CT4730_PCI_ID: es1371_init(es); codec = AC97_CREATE(dev, es, es1371_ac97); if (codec == NULL) goto bad; /* our init routine does everything for us */ /* set to NULL; flag mixer_init not to run the ac97_init */ /* ac97_mixer.init = NULL; */ if (mixer_init(dev, ac97_getmixerclass(), codec)) goto bad; ct = &eschan1371_class; break; case ES1370_PCI_ID: es1370_init(es); /* * Disable fixed rate operation if DAC2 disabled. * This is a special case for es1370 only, where the * speed of both ADC and DAC2 locked together. */ if (!ES_DAC2_ENABLED(es->escfg)) es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; break; default: goto bad; /* NOTREACHED */ } es->irqid = 0; es->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &es->irqid, RF_ACTIVE | RF_SHAREABLE); if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, es, &es->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/es->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &es->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", (es->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(es->reg), rman_get_start(es->irq), PCM_KLDSTRING(snd_es137x)); if (pcm_register(dev, es, numplay, 1)) goto bad; for (i = 0; i < numplay; i++) pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_addchan(dev, PCMDIR_REC, ct, es); es_init_sysctls(dev); pcm_setstatus(dev, status); es->escfg = ES_SET_GP(es->escfg, 0); if (numplay == 1) device_printf(dev, "\n", ES_DAC_FIRST(es->escfg) + 1); else if (numplay == 2) device_printf(dev, "\n", ES_DAC_FIRST(es->escfg) + 1, ES_DAC_SECOND(es->escfg) + 1); return (0); bad: if (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); if (es->ih) bus_teardown_intr(dev, es->irq, es->ih); if (es->irq) bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); if (codec) ac97_destroy(codec); if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); if (es->lock) snd_mtxfree(es->lock); if (es) free(es, M_DEVBUF); return (ENXIO); } static int es_pci_detach(device_t dev) { int r; struct es_info *es; r = pcm_unregister(dev); if (r) return (r); es = pcm_getdevinfo(dev); 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, pcm_devclass, 0, 0); MODULE_DEPEND(snd_es137x, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_es137x, 1); Index: head/sys/dev/sound/pci/fm801.c =================================================================== --- head/sys/dev/sound/pci/fm801.c (revision 170520) +++ head/sys/dev/sound/pci/fm801.c (revision 170521) @@ -1,768 +1,767 @@ /*- * Copyright (c) 2000 Dmitry Dicky diwil@dataart.com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #define PCI_VENDOR_FORTEMEDIA 0x1319 #define PCI_DEVICE_FORTEMEDIA1 0x08011319 /* Audio controller */ #define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* Joystick controller */ #define FM_PCM_VOLUME 0x00 #define FM_FM_VOLUME 0x02 #define FM_I2S_VOLUME 0x04 #define FM_RECORD_SOURCE 0x06 #define FM_PLAY_CTL 0x08 #define FM_PLAY_RATE_MASK 0x0f00 #define FM_PLAY_BUF1_LAST 0x0001 #define FM_PLAY_BUF2_LAST 0x0002 #define FM_PLAY_START 0x0020 #define FM_PLAY_PAUSE 0x0040 #define FM_PLAY_STOPNOW 0x0080 #define FM_PLAY_16BIT 0x4000 #define FM_PLAY_STEREO 0x8000 #define FM_PLAY_DMALEN 0x0a #define FM_PLAY_DMABUF1 0x0c #define FM_PLAY_DMABUF2 0x10 #define FM_REC_CTL 0x14 #define FM_REC_RATE_MASK 0x0f00 #define FM_REC_BUF1_LAST 0x0001 #define FM_REC_BUF2_LAST 0x0002 #define FM_REC_START 0x0020 #define FM_REC_PAUSE 0x0040 #define FM_REC_STOPNOW 0x0080 #define FM_REC_16BIT 0x4000 #define FM_REC_STEREO 0x8000 #define FM_REC_DMALEN 0x16 #define FM_REC_DMABUF1 0x18 #define FM_REC_DMABUF2 0x1c #define FM_CODEC_CTL 0x22 #define FM_VOLUME 0x26 #define FM_VOLUME_MUTE 0x8000 #define FM_CODEC_CMD 0x2a #define FM_CODEC_CMD_READ 0x0080 #define FM_CODEC_CMD_VALID 0x0100 #define FM_CODEC_CMD_BUSY 0x0200 #define FM_CODEC_DATA 0x2c #define FM_IO_CTL 0x52 #define FM_CARD_CTL 0x54 #define FM_INTMASK 0x56 #define FM_INTMASK_PLAY 0x0001 #define FM_INTMASK_REC 0x0002 #define FM_INTMASK_VOL 0x0040 #define FM_INTMASK_MPU 0x0080 #define FM_INTSTATUS 0x5a #define FM_INTSTATUS_PLAY 0x0100 #define FM_INTSTATUS_REC 0x0200 #define FM_INTSTATUS_VOL 0x4000 #define FM_INTSTATUS_MPU 0x8000 #define FM801_DEFAULT_BUFSZ 4096 /* Other values do not work!!! */ /* debug purposes */ #define DPRINT if(0) printf /* static int fm801ch_setup(struct pcm_channel *c); */ static u_int32_t fmts[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps fm801ch_caps = { 5500, 48000, fmts, 0 }; struct fm801_info; struct fm801_chinfo { struct fm801_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t spd, dir, fmt; /* speed, direction, format */ u_int32_t shift; }; struct fm801_info { int type; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; device_t dev; int num; u_int32_t unit; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; u_int32_t play_flip, play_nextblk, play_start, play_blksize, play_fmt, play_shift, play_size; u_int32_t rec_flip, rec_nextblk, rec_start, rec_blksize, rec_fmt, rec_shift, rec_size; unsigned int bufsz; struct fm801_chinfo pch, rch; device_t radio; }; /* Bus Read / Write routines */ static u_int32_t fm801_rd(struct fm801_info *fm801, int regno, int size) { switch(size) { case 1: return (bus_space_read_1(fm801->st, fm801->sh, regno)); case 2: return (bus_space_read_2(fm801->st, fm801->sh, regno)); case 4: return (bus_space_read_4(fm801->st, fm801->sh, regno)); default: return 0xffffffff; } } static void fm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(fm801->st, fm801->sh, regno, data); break; case 2: bus_space_write_2(fm801->st, fm801->sh, regno, data); break; case 4: bus_space_write_4(fm801->st, fm801->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* * ac97 codec routines */ #define TIMO 50 static int fm801_rdcd(kobj_t obj, void *devinfo, int regno) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; int i; for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 rdcd: 1 - DELAY\n"); } if (i >= TIMO) { printf("fm801 rdcd: codec busy\n"); return 0; } fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2); for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID); i++) { DELAY(10000); DPRINT("fm801 rdcd: 2 - DELAY\n"); } if (i >= TIMO) { printf("fm801 rdcd: write codec invalid\n"); return 0; } return fm801_rd(fm801,FM_CODEC_DATA,2); } static int fm801_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; int i; DPRINT("fm801_wrcd reg 0x%x val 0x%x\n",regno, data); /* if(regno == AC97_REG_RECSEL) return; */ /* Poll until codec is ready */ for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 rdcd: 1 - DELAY\n"); } if (i >= TIMO) { printf("fm801 wrcd: read codec busy\n"); return -1; } fm801_wr(fm801,FM_CODEC_DATA,data, 2); fm801_wr(fm801,FM_CODEC_CMD, regno,2); /* wait until codec is ready */ for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 wrcd: 2 - DELAY\n"); } if (i >= TIMO) { printf("fm801 wrcd: read codec busy\n"); return -1; } DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data); return 0; } static kobj_method_t fm801_ac97_methods[] = { KOBJMETHOD(ac97_read, fm801_rdcd), KOBJMETHOD(ac97_write, fm801_wrcd), { 0, 0 } }; AC97_DECLARE(fm801_ac97); /* -------------------------------------------------------------------- */ /* * The interrupt handler */ static void fm801_intr(void *p) { struct fm801_info *fm801 = (struct fm801_info *)p; u_int32_t intsrc = fm801_rd(fm801, FM_INTSTATUS, 2); DPRINT("\nfm801_intr intsrc 0x%x ", intsrc); if(intsrc & FM_INTSTATUS_PLAY) { fm801->play_flip++; if(fm801->play_flip & 1) { fm801_wr(fm801, FM_PLAY_DMABUF1, fm801->play_start,4); } else fm801_wr(fm801, FM_PLAY_DMABUF2, fm801->play_nextblk,4); chn_intr(fm801->pch.channel); } if(intsrc & FM_INTSTATUS_REC) { fm801->rec_flip++; if(fm801->rec_flip & 1) { fm801_wr(fm801, FM_REC_DMABUF1, fm801->rec_start,4); } else fm801_wr(fm801, FM_REC_DMABUF2, fm801->rec_nextblk,4); chn_intr(fm801->rch.channel); } if ( intsrc & FM_INTSTATUS_MPU ) { /* This is a TODOish thing... */ fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_MPU,2); } if ( intsrc & FM_INTSTATUS_VOL ) { /* This is a TODOish thing... */ fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_VOL,2); } DPRINT("fm801_intr clear\n\n"); fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 2); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * fm801ch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch; DPRINT("fm801ch_init, direction = %d\n", dir); ch->parent = fm801; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, 0, fm801->bufsz) != 0) return NULL; return (void *)ch; } static int fm801ch_setformat(kobj_t obj, void *data, u_int32_t format) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; DPRINT("fm801ch_setformat 0x%x : %s, %s, %s, %s\n", format, (format & AFMT_STEREO)?"stereo":"mono", (format & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)) ? "16bit":"8bit", (format & AFMT_SIGNED)? "signed":"unsigned", (format & AFMT_BIGENDIAN)?"bigendiah":"littleendian" ); if(ch->dir == PCMDIR_PLAY) { fm801->play_fmt = (format & AFMT_STEREO)? FM_PLAY_STEREO : 0; fm801->play_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0; return 0; } if(ch->dir == PCMDIR_REC ) { fm801->rec_fmt = (format & AFMT_STEREO)? FM_REC_STEREO:0; fm801->rec_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0; return 0; } return 0; } struct { int limit; int rate; } fm801_rates[11] = { { 6600, 5500 }, { 8750, 8000 }, { 10250, 9600 }, { 13200, 11025 }, { 17500, 16000 }, { 20500, 19200 }, { 26500, 22050 }, { 35000, 32000 }, { 41000, 38400 }, { 46000, 44100 }, { 48000, 48000 }, /* anything above -> 48000 */ }; static int fm801ch_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; register int i; for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ; if(ch->dir == PCMDIR_PLAY) { fm801->pch.spd = fm801_rates[i].rate; fm801->play_shift = (i<<8); fm801->play_shift &= FM_PLAY_RATE_MASK; } if(ch->dir == PCMDIR_REC ) { fm801->rch.spd = fm801_rates[i].rate; fm801->rec_shift = (i<<8); fm801->rec_shift &= FM_REC_RATE_MASK; } ch->spd = fm801_rates[i].rate; return fm801_rates[i].rate; } static int fm801ch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; /* * Don't mind for play_flip, set the blocksize to the * desired values in any case - otherwise sound playback * breaks here. */ if(ch->dir == PCMDIR_PLAY) fm801->play_blksize = blocksize; if(ch->dir == PCMDIR_REC) fm801->rec_blksize = blocksize; DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir); return blocksize; } static int fm801ch_trigger(kobj_t obj, void *data, int go) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; u_int32_t baseaddr = sndbuf_getbufaddr(ch->buffer); u_int32_t k1; DPRINT("fm801ch_trigger go %d , ", go); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) { + if (!PCMTRIG_COMMON(go)) return 0; - } if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { fm801->play_start = baseaddr; fm801->play_nextblk = fm801->play_start + fm801->play_blksize; fm801->play_flip = 0; fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2); fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4); fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4); fm801_wr(fm801, FM_PLAY_CTL, FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt | fm801->play_shift, 2 ); } else { fm801->play_flip = 0; k1 = fm801_rd(fm801, FM_PLAY_CTL,2); fm801_wr(fm801, FM_PLAY_CTL, (k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) | FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 ); } } else if(ch->dir == PCMDIR_REC) { if (go == PCMTRIG_START) { fm801->rec_start = baseaddr; fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize; fm801->rec_flip = 0; fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2); fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4); fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4); fm801_wr(fm801, FM_REC_CTL, FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt | fm801->rec_shift, 2 ); } else { fm801->rec_flip = 0; k1 = fm801_rd(fm801, FM_REC_CTL,2); fm801_wr(fm801, FM_REC_CTL, (k1 & ~(FM_REC_STOPNOW | FM_REC_START)) | FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2); } } return 0; } /* Almost ALSA copy */ static int fm801ch_getptr(kobj_t obj, void *data) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; int result = 0; if (ch->dir == PCMDIR_PLAY) { result = fm801_rd(fm801, (fm801->play_flip&1) ? FM_PLAY_DMABUF2:FM_PLAY_DMABUF1, 4) - fm801->play_start; } if (ch->dir == PCMDIR_REC) { result = fm801_rd(fm801, (fm801->rec_flip&1) ? FM_REC_DMABUF2:FM_REC_DMABUF1, 4) - fm801->rec_start; } return result; } static struct pcmchan_caps * fm801ch_getcaps(kobj_t obj, void *data) { return &fm801ch_caps; } static kobj_method_t fm801ch_methods[] = { KOBJMETHOD(channel_init, fm801ch_init), KOBJMETHOD(channel_setformat, fm801ch_setformat), KOBJMETHOD(channel_setspeed, fm801ch_setspeed), KOBJMETHOD(channel_setblocksize, fm801ch_setblocksize), KOBJMETHOD(channel_trigger, fm801ch_trigger), KOBJMETHOD(channel_getptr, fm801ch_getptr), KOBJMETHOD(channel_getcaps, fm801ch_getcaps), { 0, 0 } }; CHANNEL_DECLARE(fm801ch); /* -------------------------------------------------------------------- */ /* * Init routine is taken from an original NetBSD driver */ static int fm801_init(struct fm801_info *fm801) { u_int32_t k1; /* reset codec */ fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2); DELAY(100000); fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2); DELAY(100000); fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2); fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2); fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2); fm801_wr(fm801, 0x40,0x107f,2); /* enable legacy audio */ fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2); /* Unmask playback, record and mpu interrupts, mask the rest */ k1 = fm801_rd((void *)fm801, FM_INTMASK,2); fm801_wr(fm801, FM_INTMASK, (k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) | FM_INTMASK_VOL,2); fm801_wr(fm801, FM_INTSTATUS, FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU | FM_INTSTATUS_VOL,2); DPRINT("FM801 init Ok\n"); return 0; } static int fm801_pci_attach(device_t dev) { u_int32_t data; struct ac97_info *codec = 0; struct fm801_info *fm801; int i; int mapped = 0; char status[SND_STATUSLEN]; if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } fm801->type = pci_get_devid(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { fm801->regid = PCIR_BAR(i); fm801->regtype = SYS_RES_MEMORY; fm801->reg = bus_alloc_resource_any(dev, fm801->regtype, &fm801->regid, RF_ACTIVE); if(!fm801->reg) { fm801->regtype = SYS_RES_IOPORT; fm801->reg = bus_alloc_resource_any(dev, fm801->regtype, &fm801->regid, RF_ACTIVE); } if(fm801->reg) { fm801->st = rman_get_bustag(fm801->reg); fm801->sh = rman_get_bushandle(fm801->reg); mapped++; } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto oops; } fm801->bufsz = pcm_getbuffersize(dev, 4096, FM801_DEFAULT_BUFSZ, 65536); fm801_init(fm801); codec = AC97_CREATE(dev, fm801, fm801_ac97); if (codec == NULL) goto oops; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto oops; fm801->irqid = 0; fm801->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &fm801->irqid, RF_ACTIVE | RF_SHAREABLE); if (!fm801->irq || snd_setup_intr(dev, fm801->irq, 0, fm801_intr, fm801, &fm801->ih)) { device_printf(dev, "unable to map interrupt\n"); goto oops; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/fm801->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &fm801->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto oops; } snprintf(status, 64, "at %s 0x%lx irq %ld %s", (fm801->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(fm801->reg), rman_get_start(fm801->irq),PCM_KLDSTRING(snd_fm801)); #define FM801_MAXPLAYCH 1 if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops; pcm_addchan(dev, PCMDIR_PLAY, &fm801ch_class, fm801); pcm_addchan(dev, PCMDIR_REC, &fm801ch_class, fm801); pcm_setstatus(dev, status); fm801->radio = device_add_child(dev, "radio", -1); bus_generic_attach(dev); return 0; oops: if (codec) ac97_destroy(codec); if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg); if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih); if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq); if (fm801->parent_dmat) bus_dma_tag_destroy(fm801->parent_dmat); free(fm801, M_DEVBUF); return ENXIO; } static int fm801_pci_detach(device_t dev) { int r; struct fm801_info *fm801; DPRINT("Forte Media FM801 detach\n"); fm801 = pcm_getdevinfo(dev); r = bus_generic_detach(dev); if (r) return r; if (fm801->radio != NULL) { r = device_delete_child(dev, fm801->radio); if (r) return r; fm801->radio = NULL; } r = pcm_unregister(dev); if (r) return r; bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg); bus_teardown_intr(dev, fm801->irq, fm801->ih); bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq); bus_dma_tag_destroy(fm801->parent_dmat); free(fm801, M_DEVBUF); return 0; } static int fm801_pci_probe( device_t dev ) { int id; if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) { device_set_desc(dev, "Forte Media FM801 Audio Controller"); return BUS_PROBE_DEFAULT; } /* if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) { device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)"); return ENXIO; } */ return ENXIO; } static struct resource * fm801_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct fm801_info *fm801; fm801 = pcm_getdevinfo(bus); if (type == SYS_RES_IOPORT && *rid == PCIR_BAR(0)) return (fm801->reg); return (NULL); } static int fm801_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { return (0); } static device_method_t fm801_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fm801_pci_probe), DEVMETHOD(device_attach, fm801_pci_attach), DEVMETHOD(device_detach, fm801_pci_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_alloc_resource, fm801_alloc_resource), DEVMETHOD(bus_release_resource, fm801_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), { 0, 0} }; static driver_t fm801_driver = { "pcm", fm801_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_fm801, pci, fm801_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_fm801, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_fm801, 1); Index: head/sys/dev/sound/pci/hda/hdac.c =================================================================== --- head/sys/dev/sound/pci/hda/hdac.c (revision 170520) +++ head/sys/dev/sound/pci/hda/hdac.c (revision 170521) @@ -1,6247 +1,6250 @@ /*- * Copyright (c) 2006 Stephane E. Potvin * Copyright (c) 2006 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Intel High Definition Audio (Controller) driver for FreeBSD. Be advised * that this driver still in its early stage, and possible of rewrite are * pretty much guaranteed. There are supposedly several distinct parent/child * busses to make this "perfect", but as for now and for the sake of * simplicity, everything is gobble up within single source. * * List of subsys: * 1) HDA Controller support * 2) HDA Codecs support, which may include * - HDA * - Modem * - HDMI * 3) Widget parser - the real magic of why this driver works on so * many hardwares with minimal vendor specific quirk. The original * parser was written using Ruby and can be found at * http://people.freebsd.org/~ariff/HDA/parser.rb . This crude * ruby parser take the verbose dmesg dump as its input. Refer to * http://www.microsoft.com/whdc/device/audio/default.mspx for various * interesting documents, especially UAA (Universal Audio Architecture). * 4) Possible vendor specific support. * (snd_hda_intel, snd_hda_ati, etc..) * * Thanks to Ahmad Ubaidah Omar @ Defenxis Sdn. Bhd. for the * Compaq V3000 with Conexant HDA. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This driver is a collaborative effort made by: * * * * * * Stephane E. Potvin * * * Andrea Bittau * * * Wesley Morgan * * * Daniel Eischen * * * Maxime Guillaud * * * Ariff Abdullah * * * * * * ....and various people from freebsd-multimedia@FreeBSD.org * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include #include #include #include #include #include #include #include "mixer_if.h" #define HDA_DRV_TEST_REV "20070611_0045" #define HDA_WIDGET_PARSER_REV 1 SND_DECLARE_FILE("$FreeBSD$"); #define HDA_BOOTVERBOSE(stmt) do { \ if (bootverbose != 0 || snd_verbose > 3) { \ stmt \ } \ } while(0) #if 1 #undef HDAC_INTR_EXTRA #define HDAC_INTR_EXTRA 1 #endif #define hdac_lock(sc) snd_mtxlock((sc)->lock) #define hdac_unlock(sc) snd_mtxunlock((sc)->lock) #define hdac_lockassert(sc) snd_mtxassert((sc)->lock) #define hdac_lockowned(sc) mtx_owned((sc)->lock) #define HDA_FLAG_MATCH(fl, v) (((fl) & (v)) == (v)) #define HDA_DEV_MATCH(fl, v) ((fl) == (v) || \ (fl) == 0xffffffff || \ (((fl) & 0xffff0000) == 0xffff0000 && \ ((fl) & 0x0000ffff) == ((v) & 0x0000ffff)) || \ (((fl) & 0x0000ffff) == 0x0000ffff && \ ((fl) & 0xffff0000) == ((v) & 0xffff0000))) #define HDA_MATCH_ALL 0xffffffff #define HDAC_INVALID 0xffffffff /* Default controller / jack sense poll: 250ms */ #define HDAC_POLL_INTERVAL max(hz >> 2, 1) #define HDA_MODEL_CONSTRUCT(vendor, model) \ (((uint32_t)(model) << 16) | ((vendor##_VENDORID) & 0xffff)) /* Controller models */ /* Intel */ #define INTEL_VENDORID 0x8086 #define HDA_INTEL_82801F HDA_MODEL_CONSTRUCT(INTEL, 0x2668) #define HDA_INTEL_82801G HDA_MODEL_CONSTRUCT(INTEL, 0x27d8) #define HDA_INTEL_82801H HDA_MODEL_CONSTRUCT(INTEL, 0x284b) #define HDA_INTEL_63XXESB HDA_MODEL_CONSTRUCT(INTEL, 0x269a) #define HDA_INTEL_ALL HDA_MODEL_CONSTRUCT(INTEL, 0xffff) /* Nvidia */ #define NVIDIA_VENDORID 0x10de #define HDA_NVIDIA_MCP51 HDA_MODEL_CONSTRUCT(NVIDIA, 0x026c) #define HDA_NVIDIA_MCP55 HDA_MODEL_CONSTRUCT(NVIDIA, 0x0371) #define HDA_NVIDIA_MCP61A HDA_MODEL_CONSTRUCT(NVIDIA, 0x03e4) #define HDA_NVIDIA_MCP61B HDA_MODEL_CONSTRUCT(NVIDIA, 0x03f0) #define HDA_NVIDIA_MCP65A HDA_MODEL_CONSTRUCT(NVIDIA, 0x044a) #define HDA_NVIDIA_MCP65B HDA_MODEL_CONSTRUCT(NVIDIA, 0x044b) #define HDA_NVIDIA_ALL HDA_MODEL_CONSTRUCT(NVIDIA, 0xffff) /* ATI */ #define ATI_VENDORID 0x1002 #define HDA_ATI_SB450 HDA_MODEL_CONSTRUCT(ATI, 0x437b) #define HDA_ATI_SB600 HDA_MODEL_CONSTRUCT(ATI, 0x4383) #define HDA_ATI_ALL HDA_MODEL_CONSTRUCT(ATI, 0xffff) /* VIA */ #define VIA_VENDORID 0x1106 #define HDA_VIA_VT82XX HDA_MODEL_CONSTRUCT(VIA, 0x3288) #define HDA_VIA_ALL HDA_MODEL_CONSTRUCT(VIA, 0xffff) /* SiS */ #define SIS_VENDORID 0x1039 #define HDA_SIS_966 HDA_MODEL_CONSTRUCT(SIS, 0x7502) #define HDA_SIS_ALL HDA_MODEL_CONSTRUCT(SIS, 0xffff) /* OEM/subvendors */ /* Intel */ #define INTEL_D101GGC_SUBVENDOR HDA_MODEL_CONSTRUCT(INTEL, 0xd600) /* HP/Compaq */ #define HP_VENDORID 0x103c #define HP_V3000_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30b5) #define HP_NX7400_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30a2) #define HP_NX6310_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30aa) #define HP_NX6325_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30b0) #define HP_XW4300_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x3013) #define HP_3010_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x3010) #define HP_DV5000_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30a5) #define HP_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0xffff) /* What is wrong with XN 2563 anyway? (Got the picture ?) */ #define HP_NX6325_SUBVENDORX 0x103c30b0 /* Dell */ #define DELL_VENDORID 0x1028 #define DELL_D820_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01cc) #define DELL_I1300_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01c9) #define DELL_XPSM1210_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01d7) #define DELL_OPLX745_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01da) #define DELL_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0xffff) /* Clevo */ #define CLEVO_VENDORID 0x1558 #define CLEVO_D900T_SUBVENDOR HDA_MODEL_CONSTRUCT(CLEVO, 0x0900) #define CLEVO_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(CLEVO, 0xffff) /* Acer */ #define ACER_VENDORID 0x1025 #define ACER_A5050_SUBVENDOR HDA_MODEL_CONSTRUCT(ACER, 0x010f) #define ACER_3681WXM_SUBVENDOR HDA_MODEL_CONSTRUCT(ACER, 0x0110) #define ACER_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(ACER, 0xffff) /* Asus */ #define ASUS_VENDORID 0x1043 #define ASUS_M5200_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1993) #define ASUS_U5F_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1263) #define ASUS_A8JC_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1153) #define ASUS_P1AH2_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81cb) #define ASUS_A7M_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1323) #define ASUS_A7T_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x13c2) #define ASUS_W6F_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1263) #define ASUS_W2J_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1971) #define ASUS_F3JC_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1338) #define ASUS_M2V_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81e7) #define ASUS_M2N_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x8234) #define ASUS_M2NPVMX_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81cb) #define ASUS_P5BWD_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81ec) #define ASUS_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0xffff) /* IBM / Lenovo */ #define IBM_VENDORID 0x1014 #define IBM_M52_SUBVENDOR HDA_MODEL_CONSTRUCT(IBM, 0x02f6) #define IBM_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(IBM, 0xffff) /* Lenovo */ #define LENOVO_VENDORID 0x17aa #define LENOVO_3KN100_SUBVENDOR HDA_MODEL_CONSTRUCT(LENOVO, 0x2066) #define LENOVO_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(LENOVO, 0xffff) /* Samsung */ #define SAMSUNG_VENDORID 0x144d #define SAMSUNG_Q1_SUBVENDOR HDA_MODEL_CONSTRUCT(SAMSUNG, 0xc027) #define SAMSUNG_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(SAMSUNG, 0xffff) /* Medion ? */ #define MEDION_VENDORID 0x161f #define MEDION_MD95257_SUBVENDOR HDA_MODEL_CONSTRUCT(MEDION, 0x203d) #define MEDION_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(MEDION, 0xffff) /* * Apple Intel MacXXXX seems using Sigmatel codec/vendor id * instead of their own, which is beyond my comprehension * (see HDA_CODEC_STAC9221 below). */ #define APPLE_INTEL_MAC 0x76808384 /* LG Electronics */ #define LG_VENDORID 0x1854 #define LG_LW20_SUBVENDOR HDA_MODEL_CONSTRUCT(LG, 0x0018) #define LG_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(LG, 0xffff) /* Fujitsu Siemens */ #define FS_VENDORID 0x1734 #define FS_PA1510_SUBVENDOR HDA_MODEL_CONSTRUCT(FS, 0x10b8) #define FS_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(FS, 0xffff) /* Toshiba */ #define TOSHIBA_VENDORID 0x1179 #define TOSHIBA_U200_SUBVENDOR HDA_MODEL_CONSTRUCT(TOSHIBA, 0x0001) #define TOSHIBA_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(TOSHIBA, 0xffff) /* Micro-Star International (MSI) */ #define MSI_VENDORID 0x1462 #define MSI_MS1034_SUBVENDOR HDA_MODEL_CONSTRUCT(MSI, 0x0349) #define MSI_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(MSI, 0xffff) /* Uniwill ? */ #define UNIWILL_VENDORID 0x1584 #define UNIWILL_9075_SUBVENDOR HDA_MODEL_CONSTRUCT(UNIWILL, 0x9075) /* Misc constants.. */ #define HDA_AMP_MUTE_DEFAULT (0xffffffff) #define HDA_AMP_MUTE_NONE (0) #define HDA_AMP_MUTE_LEFT (1 << 0) #define HDA_AMP_MUTE_RIGHT (1 << 1) #define HDA_AMP_MUTE_ALL (HDA_AMP_MUTE_LEFT | HDA_AMP_MUTE_RIGHT) #define HDA_AMP_LEFT_MUTED(v) ((v) & (HDA_AMP_MUTE_LEFT)) #define HDA_AMP_RIGHT_MUTED(v) (((v) & HDA_AMP_MUTE_RIGHT) >> 1) #define HDA_DAC_PATH (1 << 0) #define HDA_ADC_PATH (1 << 1) #define HDA_ADC_RECSEL (1 << 2) #define HDA_DAC_LOCKED (1 << 3) #define HDA_ADC_LOCKED (1 << 4) #define HDA_CTL_OUT (1 << 0) #define HDA_CTL_IN (1 << 1) #define HDA_CTL_BOTH (HDA_CTL_IN | HDA_CTL_OUT) #define HDA_GPIO_MAX 8 /* 0 - 7 = GPIO , 8 = Flush */ #define HDA_QUIRK_GPIO0 (1 << 0) #define HDA_QUIRK_GPIO1 (1 << 1) #define HDA_QUIRK_GPIO2 (1 << 2) #define HDA_QUIRK_GPIO3 (1 << 3) #define HDA_QUIRK_GPIO4 (1 << 4) #define HDA_QUIRK_GPIO5 (1 << 5) #define HDA_QUIRK_GPIO6 (1 << 6) #define HDA_QUIRK_GPIO7 (1 << 7) #define HDA_QUIRK_GPIOFLUSH (1 << 8) /* 9 - 25 = anything else */ #define HDA_QUIRK_SOFTPCMVOL (1 << 9) #define HDA_QUIRK_FIXEDRATE (1 << 10) #define HDA_QUIRK_FORCESTEREO (1 << 11) #define HDA_QUIRK_EAPDINV (1 << 12) #define HDA_QUIRK_DMAPOS (1 << 13) /* 26 - 31 = vrefs */ #define HDA_QUIRK_IVREF50 (1 << 26) #define HDA_QUIRK_IVREF80 (1 << 27) #define HDA_QUIRK_IVREF100 (1 << 28) #define HDA_QUIRK_OVREF50 (1 << 29) #define HDA_QUIRK_OVREF80 (1 << 30) #define HDA_QUIRK_OVREF100 (1 << 31) #define HDA_QUIRK_IVREF (HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF80 | \ HDA_QUIRK_IVREF100) #define HDA_QUIRK_OVREF (HDA_QUIRK_OVREF50 | HDA_QUIRK_OVREF80 | \ HDA_QUIRK_OVREF100) #define HDA_QUIRK_VREF (HDA_QUIRK_IVREF | HDA_QUIRK_OVREF) #define SOUND_MASK_SKIP (1 << 30) #define SOUND_MASK_DISABLE (1 << 31) static const struct { char *key; uint32_t value; } hdac_quirks_tab[] = { { "gpio0", HDA_QUIRK_GPIO0 }, { "gpio1", HDA_QUIRK_GPIO1 }, { "gpio2", HDA_QUIRK_GPIO2 }, { "gpio3", HDA_QUIRK_GPIO3 }, { "gpio4", HDA_QUIRK_GPIO4 }, { "gpio5", HDA_QUIRK_GPIO5 }, { "gpio6", HDA_QUIRK_GPIO6 }, { "gpio7", HDA_QUIRK_GPIO7 }, { "gpioflush", HDA_QUIRK_GPIOFLUSH }, { "softpcmvol", HDA_QUIRK_SOFTPCMVOL }, { "fixedrate", HDA_QUIRK_FIXEDRATE }, { "forcestereo", HDA_QUIRK_FORCESTEREO }, { "eapdinv", HDA_QUIRK_EAPDINV }, { "dmapos", HDA_QUIRK_DMAPOS }, { "ivref50", HDA_QUIRK_IVREF50 }, { "ivref80", HDA_QUIRK_IVREF80 }, { "ivref100", HDA_QUIRK_IVREF100 }, { "ovref50", HDA_QUIRK_OVREF50 }, { "ovref80", HDA_QUIRK_OVREF80 }, { "ovref100", HDA_QUIRK_OVREF100 }, { "ivref", HDA_QUIRK_IVREF }, { "ovref", HDA_QUIRK_OVREF }, { "vref", HDA_QUIRK_VREF }, }; #define HDAC_QUIRKS_TAB_LEN \ (sizeof(hdac_quirks_tab) / sizeof(hdac_quirks_tab[0])) #define HDA_BDL_MIN 2 #define HDA_BDL_MAX 256 #define HDA_BDL_DEFAULT HDA_BDL_MIN #define HDA_BLK_MIN HDAC_DMA_ALIGNMENT #define HDA_BLK_ALIGN (~(HDA_BLK_MIN - 1)) #define HDA_BUFSZ_MIN 4096 #define HDA_BUFSZ_MAX 65536 #define HDA_BUFSZ_DEFAULT 16384 #define HDA_PARSE_MAXDEPTH 10 #define HDAC_UNSOLTAG_EVENT_HP 0x00 #define HDAC_UNSOLTAG_EVENT_TEST 0x01 MALLOC_DEFINE(M_HDAC, "hdac", "High Definition Audio Controller"); enum { HDA_PARSE_MIXER, HDA_PARSE_DIRECT }; /* Default */ static uint32_t hdac_fmt[] = { AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps hdac_caps = {48000, 48000, hdac_fmt, 0}; static const struct { uint32_t model; char *desc; } hdac_devices[] = { { HDA_INTEL_82801F, "Intel 82801F" }, { HDA_INTEL_82801G, "Intel 82801G" }, { HDA_INTEL_82801H, "Intel 82801H" }, { HDA_INTEL_63XXESB, "Intel 631x/632xESB" }, { HDA_NVIDIA_MCP51, "NVidia MCP51" }, { HDA_NVIDIA_MCP55, "NVidia MCP55" }, { HDA_NVIDIA_MCP61A, "NVidia MCP61A" }, { HDA_NVIDIA_MCP61B, "NVidia MCP61B" }, { HDA_NVIDIA_MCP65A, "NVidia MCP65A" }, { HDA_NVIDIA_MCP65B, "NVidia MCP65B" }, { HDA_ATI_SB450, "ATI SB450" }, { HDA_ATI_SB600, "ATI SB600" }, { HDA_VIA_VT82XX, "VIA VT8251/8237A" }, { HDA_SIS_966, "SiS 966" }, /* Unknown */ { HDA_INTEL_ALL, "Intel (Unknown)" }, { HDA_NVIDIA_ALL, "NVidia (Unknown)" }, { HDA_ATI_ALL, "ATI (Unknown)" }, { HDA_VIA_ALL, "VIA (Unknown)" }, { HDA_SIS_ALL, "SiS (Unknown)" }, }; #define HDAC_DEVICES_LEN (sizeof(hdac_devices) / sizeof(hdac_devices[0])) static const struct { uint16_t vendor; uint8_t reg; uint8_t mask; uint8_t enable; } hdac_pcie_snoop[] = { { INTEL_VENDORID, 0x00, 0x00, 0x00 }, { ATI_VENDORID, 0x42, 0xf8, 0x02 }, { NVIDIA_VENDORID, 0x4e, 0xf0, 0x0f }, }; #define HDAC_PCIESNOOP_LEN \ (sizeof(hdac_pcie_snoop) / sizeof(hdac_pcie_snoop[0])) static const struct { uint32_t rate; int valid; uint16_t base; uint16_t mul; uint16_t div; } hda_rate_tab[] = { { 8000, 1, 0x0000, 0x0000, 0x0500 }, /* (48000 * 1) / 6 */ { 9600, 0, 0x0000, 0x0000, 0x0400 }, /* (48000 * 1) / 5 */ { 12000, 0, 0x0000, 0x0000, 0x0300 }, /* (48000 * 1) / 4 */ { 16000, 1, 0x0000, 0x0000, 0x0200 }, /* (48000 * 1) / 3 */ { 18000, 0, 0x0000, 0x1000, 0x0700 }, /* (48000 * 3) / 8 */ { 19200, 0, 0x0000, 0x0800, 0x0400 }, /* (48000 * 2) / 5 */ { 24000, 0, 0x0000, 0x0000, 0x0100 }, /* (48000 * 1) / 2 */ { 28800, 0, 0x0000, 0x1000, 0x0400 }, /* (48000 * 3) / 5 */ { 32000, 1, 0x0000, 0x0800, 0x0200 }, /* (48000 * 2) / 3 */ { 36000, 0, 0x0000, 0x1000, 0x0300 }, /* (48000 * 3) / 4 */ { 38400, 0, 0x0000, 0x1800, 0x0400 }, /* (48000 * 4) / 5 */ { 48000, 1, 0x0000, 0x0000, 0x0000 }, /* (48000 * 1) / 1 */ { 64000, 0, 0x0000, 0x1800, 0x0200 }, /* (48000 * 4) / 3 */ { 72000, 0, 0x0000, 0x1000, 0x0100 }, /* (48000 * 3) / 2 */ { 96000, 1, 0x0000, 0x0800, 0x0000 }, /* (48000 * 2) / 1 */ { 144000, 0, 0x0000, 0x1000, 0x0000 }, /* (48000 * 3) / 1 */ { 192000, 1, 0x0000, 0x1800, 0x0000 }, /* (48000 * 4) / 1 */ { 8820, 0, 0x4000, 0x0000, 0x0400 }, /* (44100 * 1) / 5 */ { 11025, 1, 0x4000, 0x0000, 0x0300 }, /* (44100 * 1) / 4 */ { 12600, 0, 0x4000, 0x0800, 0x0600 }, /* (44100 * 2) / 7 */ { 14700, 0, 0x4000, 0x0000, 0x0200 }, /* (44100 * 1) / 3 */ { 17640, 0, 0x4000, 0x0800, 0x0400 }, /* (44100 * 2) / 5 */ { 18900, 0, 0x4000, 0x1000, 0x0600 }, /* (44100 * 3) / 7 */ { 22050, 1, 0x4000, 0x0000, 0x0100 }, /* (44100 * 1) / 2 */ { 25200, 0, 0x4000, 0x1800, 0x0600 }, /* (44100 * 4) / 7 */ { 26460, 0, 0x4000, 0x1000, 0x0400 }, /* (44100 * 3) / 5 */ { 29400, 0, 0x4000, 0x0800, 0x0200 }, /* (44100 * 2) / 3 */ { 33075, 0, 0x4000, 0x1000, 0x0300 }, /* (44100 * 3) / 4 */ { 35280, 0, 0x4000, 0x1800, 0x0400 }, /* (44100 * 4) / 5 */ { 44100, 1, 0x4000, 0x0000, 0x0000 }, /* (44100 * 1) / 1 */ { 58800, 0, 0x4000, 0x1800, 0x0200 }, /* (44100 * 4) / 3 */ { 66150, 0, 0x4000, 0x1000, 0x0100 }, /* (44100 * 3) / 2 */ { 88200, 1, 0x4000, 0x0800, 0x0000 }, /* (44100 * 2) / 1 */ { 132300, 0, 0x4000, 0x1000, 0x0000 }, /* (44100 * 3) / 1 */ { 176400, 1, 0x4000, 0x1800, 0x0000 }, /* (44100 * 4) / 1 */ }; #define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0])) /* All codecs you can eat... */ #define HDA_CODEC_CONSTRUCT(vendor, id) \ (((uint32_t)(vendor##_VENDORID) << 16) | ((id) & 0xffff)) /* Realtek */ #define REALTEK_VENDORID 0x10ec #define HDA_CODEC_ALC260 HDA_CODEC_CONSTRUCT(REALTEK, 0x0260) #define HDA_CODEC_ALC262 HDA_CODEC_CONSTRUCT(REALTEK, 0x0262) #define HDA_CODEC_ALC660 HDA_CODEC_CONSTRUCT(REALTEK, 0x0660) #define HDA_CODEC_ALC861 HDA_CODEC_CONSTRUCT(REALTEK, 0x0861) #define HDA_CODEC_ALC861VD HDA_CODEC_CONSTRUCT(REALTEK, 0x0862) #define HDA_CODEC_ALC880 HDA_CODEC_CONSTRUCT(REALTEK, 0x0880) #define HDA_CODEC_ALC882 HDA_CODEC_CONSTRUCT(REALTEK, 0x0882) #define HDA_CODEC_ALC883 HDA_CODEC_CONSTRUCT(REALTEK, 0x0883) #define HDA_CODEC_ALC885 HDA_CODEC_CONSTRUCT(REALTEK, 0x0885) #define HDA_CODEC_ALC888 HDA_CODEC_CONSTRUCT(REALTEK, 0x0888) #define HDA_CODEC_ALCXXXX HDA_CODEC_CONSTRUCT(REALTEK, 0xffff) /* Analog Devices */ #define ANALOGDEVICES_VENDORID 0x11d4 #define HDA_CODEC_AD1981HD HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1981) #define HDA_CODEC_AD1983 HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1983) #define HDA_CODEC_AD1986A HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1986) #define HDA_CODEC_AD1988 HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1988) #define HDA_CODEC_AD1988B HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x198b) #define HDA_CODEC_ADXXXX HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0xffff) /* CMedia */ #define CMEDIA_VENDORID 0x434d #define HDA_CODEC_CMI9880 HDA_CODEC_CONSTRUCT(CMEDIA, 0x4980) #define HDA_CODEC_CMIXXXX HDA_CODEC_CONSTRUCT(CMEDIA, 0xffff) /* Sigmatel */ #define SIGMATEL_VENDORID 0x8384 #define HDA_CODEC_STAC9221 HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7680) #define HDA_CODEC_STAC9221D HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7683) #define HDA_CODEC_STAC9220 HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7690) #define HDA_CODEC_STAC922XD HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7681) #define HDA_CODEC_STAC9227 HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7618) #define HDA_CODEC_STAC9271D HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7627) #define HDA_CODEC_STACXXXX HDA_CODEC_CONSTRUCT(SIGMATEL, 0xffff) /* * Conexant * * Ok, the truth is, I don't have any idea at all whether * it is "Venice" or "Waikiki" or other unnamed CXyadayada. The only * place that tell me it is "Venice" is from its Windows driver INF. * * Venice - CX????? * Waikiki - CX20551-22 */ #define CONEXANT_VENDORID 0x14f1 #define HDA_CODEC_CXVENICE HDA_CODEC_CONSTRUCT(CONEXANT, 0x5045) #define HDA_CODEC_CXWAIKIKI HDA_CODEC_CONSTRUCT(CONEXANT, 0x5047) #define HDA_CODEC_CXXXXX HDA_CODEC_CONSTRUCT(CONEXANT, 0xffff) /* VIA */ #define HDA_CODEC_VT1708_8 HDA_CODEC_CONSTRUCT(VIA, 0x1708) #define HDA_CODEC_VT1708_9 HDA_CODEC_CONSTRUCT(VIA, 0x1709) #define HDA_CODEC_VT1708_A HDA_CODEC_CONSTRUCT(VIA, 0x170a) #define HDA_CODEC_VT1708_B HDA_CODEC_CONSTRUCT(VIA, 0x170b) #define HDA_CODEC_VT1709_0 HDA_CODEC_CONSTRUCT(VIA, 0xe710) #define HDA_CODEC_VT1709_1 HDA_CODEC_CONSTRUCT(VIA, 0xe711) #define HDA_CODEC_VT1709_2 HDA_CODEC_CONSTRUCT(VIA, 0xe712) #define HDA_CODEC_VT1709_3 HDA_CODEC_CONSTRUCT(VIA, 0xe713) #define HDA_CODEC_VT1709_4 HDA_CODEC_CONSTRUCT(VIA, 0xe714) #define HDA_CODEC_VT1709_5 HDA_CODEC_CONSTRUCT(VIA, 0xe715) #define HDA_CODEC_VT1709_6 HDA_CODEC_CONSTRUCT(VIA, 0xe716) #define HDA_CODEC_VT1709_7 HDA_CODEC_CONSTRUCT(VIA, 0xe717) #define HDA_CODEC_VTXXXX HDA_CODEC_CONSTRUCT(VIA, 0xffff) /* Codecs */ static const struct { uint32_t id; char *name; } hdac_codecs[] = { { HDA_CODEC_ALC260, "Realtek ALC260" }, { HDA_CODEC_ALC262, "Realtek ALC262" }, { HDA_CODEC_ALC660, "Realtek ALC660" }, { HDA_CODEC_ALC861, "Realtek ALC861" }, { HDA_CODEC_ALC861VD, "Realtek ALC861-VD" }, { HDA_CODEC_ALC880, "Realtek ALC880" }, { HDA_CODEC_ALC882, "Realtek ALC882" }, { HDA_CODEC_ALC883, "Realtek ALC883" }, { HDA_CODEC_ALC885, "Realtek ALC885" }, { HDA_CODEC_ALC888, "Realtek ALC888" }, { HDA_CODEC_AD1981HD, "Analog Devices AD1981HD" }, { HDA_CODEC_AD1983, "Analog Devices AD1983" }, { HDA_CODEC_AD1986A, "Analog Devices AD1986A" }, { HDA_CODEC_AD1988, "Analog Devices AD1988" }, { HDA_CODEC_AD1988B, "Analog Devices AD1988B" }, { HDA_CODEC_CMI9880, "CMedia CMI9880" }, { HDA_CODEC_STAC9221, "Sigmatel STAC9221" }, { HDA_CODEC_STAC9221D, "Sigmatel STAC9221D" }, { HDA_CODEC_STAC9220, "Sigmatel STAC9220" }, { HDA_CODEC_STAC922XD, "Sigmatel STAC9220D/9223D" }, { HDA_CODEC_STAC9227, "Sigmatel STAC9227" }, { HDA_CODEC_STAC9271D, "Sigmatel STAC9271D" }, { HDA_CODEC_CXVENICE, "Conexant Venice" }, { HDA_CODEC_CXWAIKIKI, "Conexant Waikiki" }, { HDA_CODEC_VT1708_8, "VIA VT1708_8" }, { HDA_CODEC_VT1708_9, "VIA VT1708_9" }, { HDA_CODEC_VT1708_A, "VIA VT1708_A" }, { HDA_CODEC_VT1708_B, "VIA VT1708_B" }, { HDA_CODEC_VT1709_0, "VIA VT1709_0" }, { HDA_CODEC_VT1709_1, "VIA VT1709_1" }, { HDA_CODEC_VT1709_2, "VIA VT1709_2" }, { HDA_CODEC_VT1709_3, "VIA VT1709_3" }, { HDA_CODEC_VT1709_4, "VIA VT1709_4" }, { HDA_CODEC_VT1709_5, "VIA VT1709_5" }, { HDA_CODEC_VT1709_6, "VIA VT1709_6" }, { HDA_CODEC_VT1709_7, "VIA VT1709_7" }, /* Unknown codec */ { HDA_CODEC_ALCXXXX, "Realtek (Unknown)" }, { HDA_CODEC_ADXXXX, "Analog Devices (Unknown)" }, { HDA_CODEC_CMIXXXX, "CMedia (Unknown)" }, { HDA_CODEC_STACXXXX, "Sigmatel (Unknown)" }, { HDA_CODEC_CXXXXX, "Conexant (Unknown)" }, { HDA_CODEC_VTXXXX, "VIA (Unknown)" }, }; #define HDAC_CODECS_LEN (sizeof(hdac_codecs) / sizeof(hdac_codecs[0])) enum { HDAC_HP_SWITCH_CTL, HDAC_HP_SWITCH_CTRL, HDAC_HP_SWITCH_DEBUG }; static const struct { uint32_t model; uint32_t id; int type; int inverted; int polling; int execsense; nid_t hpnid; nid_t spkrnid[8]; nid_t eapdnid; } hdac_hp_switch[] = { /* Specific OEM models */ { HP_V3000_SUBVENDOR, HDA_CODEC_CXVENICE, HDAC_HP_SWITCH_CTL, 0, 0, -1, 17, { 16, -1 }, 16 }, /* { HP_XW4300_SUBVENDOR, HDA_CODEC_ALC260, HDAC_HP_SWITCH_CTL, 0, 0, -1, 21, { 16, 17, -1 }, -1 } */ /*{ HP_3010_SUBVENDOR, HDA_CODEC_ALC260, HDAC_HP_SWITCH_DEBUG, 0, 1, 0, 16, { 15, 18, 19, 20, 21, -1 }, -1 },*/ { HP_NX7400_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, -1 }, 5 }, { HP_NX6310_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, -1 }, 5 }, { HP_NX6325_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, -1 }, 5 }, { TOSHIBA_U200_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, -1 }, -1 }, { DELL_D820_SUBVENDOR, HDA_CODEC_STAC9220, HDAC_HP_SWITCH_CTRL, 0, 0, -1, 13, { 14, -1 }, -1 }, { DELL_I1300_SUBVENDOR, HDA_CODEC_STAC9220, HDAC_HP_SWITCH_CTRL, 0, 0, -1, 13, { 14, -1 }, -1 }, { DELL_OPLX745_SUBVENDOR, HDA_CODEC_AD1983, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, 7, -1 }, -1 }, { APPLE_INTEL_MAC, HDA_CODEC_STAC9221, HDAC_HP_SWITCH_CTRL, 0, 0, -1, 10, { 13, -1 }, -1 }, { LENOVO_3KN100_SUBVENDOR, HDA_CODEC_AD1986A, HDAC_HP_SWITCH_CTL, 1, 0, -1, 26, { 27, -1 }, -1 }, { LG_LW20_SUBVENDOR, HDA_CODEC_ALC880, HDAC_HP_SWITCH_CTL, 0, 0, -1, 27, { 20, -1 }, -1 }, { ACER_A5050_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, 0, 0, -1, 20, { 21, -1 }, -1 }, { ACER_3681WXM_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, 0, 0, -1, 20, { 21, -1 }, -1 }, { MSI_MS1034_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, 0, 0, -1, 20, { 27, -1 }, -1 }, /* * All models that at least come from the same vendor with * simmilar codec. */ { HP_ALL_SUBVENDOR, HDA_CODEC_CXVENICE, HDAC_HP_SWITCH_CTL, 0, 0, -1, 17, { 16, -1 }, 16 }, { HP_ALL_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, -1 }, 5 }, { TOSHIBA_ALL_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, 0, 0, -1, 6, { 5, -1 }, -1 }, { DELL_ALL_SUBVENDOR, HDA_CODEC_STAC9220, HDAC_HP_SWITCH_CTRL, 0, 0, -1, 13, { 14, -1 }, -1 }, { LENOVO_ALL_SUBVENDOR, HDA_CODEC_AD1986A, HDAC_HP_SWITCH_CTL, 1, 0, -1, 26, { 27, -1 }, -1 }, #if 0 { ACER_ALL_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, 0, 0, -1, 20, { 21, -1 }, -1 }, #endif }; #define HDAC_HP_SWITCH_LEN \ (sizeof(hdac_hp_switch) / sizeof(hdac_hp_switch[0])) static const struct { uint32_t model; uint32_t id; nid_t eapdnid; int hp_switch; } hdac_eapd_switch[] = { { HP_V3000_SUBVENDOR, HDA_CODEC_CXVENICE, 16, 1 }, { HP_NX7400_SUBVENDOR, HDA_CODEC_AD1981HD, 5, 1 }, { HP_NX6310_SUBVENDOR, HDA_CODEC_AD1981HD, 5, 1 }, }; #define HDAC_EAPD_SWITCH_LEN \ (sizeof(hdac_eapd_switch) / sizeof(hdac_eapd_switch[0])) /**************************************************************************** * Function prototypes ****************************************************************************/ static void hdac_intr_handler(void *); static int hdac_reset(struct hdac_softc *); static int hdac_get_capabilities(struct hdac_softc *); static void hdac_dma_cb(void *, bus_dma_segment_t *, int, int); static int hdac_dma_alloc(struct hdac_softc *, struct hdac_dma *, bus_size_t); static void hdac_dma_free(struct hdac_softc *, struct hdac_dma *); static int hdac_mem_alloc(struct hdac_softc *); static void hdac_mem_free(struct hdac_softc *); static int hdac_irq_alloc(struct hdac_softc *); static void hdac_irq_free(struct hdac_softc *); static void hdac_corb_init(struct hdac_softc *); static void hdac_rirb_init(struct hdac_softc *); static void hdac_corb_start(struct hdac_softc *); static void hdac_rirb_start(struct hdac_softc *); static void hdac_scan_codecs(struct hdac_softc *); static int hdac_probe_codec(struct hdac_codec *); static struct hdac_devinfo *hdac_probe_function(struct hdac_codec *, nid_t); static void hdac_add_child(struct hdac_softc *, struct hdac_devinfo *); static void hdac_attach2(void *); static uint32_t hdac_command_sendone_internal(struct hdac_softc *, uint32_t, int); static void hdac_command_send_internal(struct hdac_softc *, struct hdac_command_list *, int); static int hdac_probe(device_t); static int hdac_attach(device_t); static int hdac_detach(device_t); static void hdac_widget_connection_select(struct hdac_widget *, uint8_t); static void hdac_audio_ctl_amp_set(struct hdac_audio_ctl *, uint32_t, int, int); static struct hdac_audio_ctl *hdac_audio_ctl_amp_get(struct hdac_devinfo *, nid_t, int, int); static void hdac_audio_ctl_amp_set_internal(struct hdac_softc *, nid_t, nid_t, int, int, int, int, int, int); static int hdac_audio_ctl_ossmixer_getnextdev(struct hdac_devinfo *); static struct hdac_widget *hdac_widget_get(struct hdac_devinfo *, nid_t); static int hdac_rirb_flush(struct hdac_softc *sc); static int hdac_unsolq_flush(struct hdac_softc *sc); #define hdac_command(a1, a2, a3) \ hdac_command_sendone_internal(a1, a2, a3) #define hdac_codec_id(d) \ ((uint32_t)((d == NULL) ? 0x00000000 : \ ((((uint32_t)(d)->vendor_id & 0x0000ffff) << 16) | \ ((uint32_t)(d)->device_id & 0x0000ffff)))) static char * hdac_codec_name(struct hdac_devinfo *devinfo) { uint32_t id; int i; id = hdac_codec_id(devinfo); for (i = 0; i < HDAC_CODECS_LEN; i++) { if (HDA_DEV_MATCH(hdac_codecs[i].id, id)) return (hdac_codecs[i].name); } return ((id == 0x00000000) ? "NULL Codec" : "Unknown Codec"); } static char * hdac_audio_ctl_ossmixer_mask2name(uint32_t devmask) { static char *ossname[] = SOUND_DEVICE_NAMES; static char *unknown = "???"; int i; for (i = SOUND_MIXER_NRDEVICES - 1; i >= 0; i--) { if (devmask & (1 << i)) return (ossname[i]); } return (unknown); } static void hdac_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len) { static char *ossname[] = SOUND_DEVICE_NAMES; int i, first = 1; bzero(buf, len); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mask & (1 << i)) { if (first == 0) strlcat(buf, ", ", len); strlcat(buf, ossname[i], len); first = 0; } } } static struct hdac_audio_ctl * hdac_audio_ctl_each(struct hdac_devinfo *devinfo, int *index) { if (devinfo == NULL || devinfo->node_type != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO || index == NULL || devinfo->function.audio.ctl == NULL || devinfo->function.audio.ctlcnt < 1 || *index < 0 || *index >= devinfo->function.audio.ctlcnt) return (NULL); return (&devinfo->function.audio.ctl[(*index)++]); } static struct hdac_audio_ctl * hdac_audio_ctl_amp_get(struct hdac_devinfo *devinfo, nid_t nid, int index, int cnt) { struct hdac_audio_ctl *ctl, *retctl = NULL; int i, at, atindex, found = 0; if (devinfo == NULL || devinfo->function.audio.ctl == NULL) return (NULL); at = cnt; if (at == 0) at = 1; else if (at < 0) at = -1; atindex = index; if (atindex < 0) atindex = -1; i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) continue; if (!(ctl->widget->nid == nid && (atindex == -1 || ctl->index == atindex))) continue; found++; if (found == cnt) return (ctl); retctl = ctl; } return ((at == -1) ? retctl : NULL); } static void hdac_hp_switch_handler(struct hdac_devinfo *devinfo) { struct hdac_softc *sc; struct hdac_widget *w; struct hdac_audio_ctl *ctl; uint32_t val, id, res; int i = 0, j, forcemute; nid_t cad; if (devinfo == NULL || devinfo->codec == NULL || devinfo->codec->sc == NULL) return; sc = devinfo->codec->sc; cad = devinfo->codec->cad; id = hdac_codec_id(devinfo); for (i = 0; i < HDAC_HP_SWITCH_LEN; i++) { if (HDA_DEV_MATCH(hdac_hp_switch[i].model, sc->pci_subvendor) && hdac_hp_switch[i].id == id) break; } if (i >= HDAC_HP_SWITCH_LEN) return; forcemute = 0; if (hdac_hp_switch[i].eapdnid != -1) { w = hdac_widget_get(devinfo, hdac_hp_switch[i].eapdnid); if (w != NULL && w->param.eapdbtl != HDAC_INVALID) forcemute = (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD) ? 0 : 1; } if (hdac_hp_switch[i].execsense != -1) hdac_command(sc, HDA_CMD_SET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid, hdac_hp_switch[i].execsense), cad); res = hdac_command(sc, HDA_CMD_GET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid), cad); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Pin sense: nid=%d res=0x%08x\n", hdac_hp_switch[i].hpnid, res); ); res = HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT(res); res ^= hdac_hp_switch[i].inverted; switch (hdac_hp_switch[i].type) { case HDAC_HP_SWITCH_CTL: ctl = hdac_audio_ctl_amp_get(devinfo, hdac_hp_switch[i].hpnid, 0, 1); if (ctl != NULL) { val = (res != 0 && forcemute == 0) ? HDA_AMP_MUTE_NONE : HDA_AMP_MUTE_ALL; if (val != ctl->muted) { ctl->muted = val; hdac_audio_ctl_amp_set(ctl, HDA_AMP_MUTE_DEFAULT, ctl->left, ctl->right); } } for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { ctl = hdac_audio_ctl_amp_get(devinfo, hdac_hp_switch[i].spkrnid[j], 0, 1); if (ctl == NULL) continue; val = (res != 0 || forcemute == 1) ? HDA_AMP_MUTE_ALL : HDA_AMP_MUTE_NONE; if (val == ctl->muted) continue; ctl->muted = val; hdac_audio_ctl_amp_set(ctl, HDA_AMP_MUTE_DEFAULT, ctl->left, ctl->right); } break; case HDAC_HP_SWITCH_CTRL: if (res != 0) { /* HP in */ w = hdac_widget_get(devinfo, hdac_hp_switch[i].hpnid); if (w != NULL && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { if (forcemute == 0) val = w->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w->wclass.pin.ctrl) { w->wclass.pin.ctrl = val; hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL(cad, w->nid, w->wclass.pin.ctrl), cad); } } for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { w = hdac_widget_get(devinfo, hdac_hp_switch[i].spkrnid[j]); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val == w->wclass.pin.ctrl) continue; w->wclass.pin.ctrl = val; hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL( cad, w->nid, w->wclass.pin.ctrl), cad); } } else { /* HP out */ w = hdac_widget_get(devinfo, hdac_hp_switch[i].hpnid); if (w != NULL && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w->wclass.pin.ctrl) { w->wclass.pin.ctrl = val; hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL(cad, w->nid, w->wclass.pin.ctrl), cad); } } for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { w = hdac_widget_get(devinfo, hdac_hp_switch[i].spkrnid[j]); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (forcemute == 0) val = w->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val == w->wclass.pin.ctrl) continue; w->wclass.pin.ctrl = val; hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL( cad, w->nid, w->wclass.pin.ctrl), cad); } } break; case HDAC_HP_SWITCH_DEBUG: if (hdac_hp_switch[i].execsense != -1) hdac_command(sc, HDA_CMD_SET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid, hdac_hp_switch[i].execsense), cad); res = hdac_command(sc, HDA_CMD_GET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid), cad); device_printf(sc->dev, "[ 0] HDA_DEBUG: Pin sense: nid=%d res=0x%08x\n", hdac_hp_switch[i].hpnid, res); for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { w = hdac_widget_get(devinfo, hdac_hp_switch[i].spkrnid[j]); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (hdac_hp_switch[i].execsense != -1) hdac_command(sc, HDA_CMD_SET_PIN_SENSE(cad, w->nid, hdac_hp_switch[i].execsense), cad); res = hdac_command(sc, HDA_CMD_GET_PIN_SENSE(cad, w->nid), cad); device_printf(sc->dev, "[%2d] HDA_DEBUG: Pin sense: nid=%d res=0x%08x\n", j + 1, w->nid, res); } break; default: break; } } static void hdac_unsolicited_handler(struct hdac_codec *codec, uint32_t tag) { struct hdac_softc *sc; struct hdac_devinfo *devinfo = NULL; device_t *devlist = NULL; int devcount, i; if (codec == NULL || codec->sc == NULL) return; sc = codec->sc; HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Unsol Tag: 0x%08x\n", tag); ); device_get_children(sc->dev, &devlist, &devcount); for (i = 0; devlist != NULL && i < devcount; i++) { devinfo = (struct hdac_devinfo *)device_get_ivars(devlist[i]); if (devinfo != NULL && devinfo->node_type == HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO && devinfo->codec != NULL && devinfo->codec->cad == codec->cad) { break; } else devinfo = NULL; } if (devlist != NULL) free(devlist, M_TEMP); if (devinfo == NULL) return; switch (tag) { case HDAC_UNSOLTAG_EVENT_HP: hdac_hp_switch_handler(devinfo); break; case HDAC_UNSOLTAG_EVENT_TEST: device_printf(sc->dev, "Unsol Test!\n"); break; default: break; } } static int hdac_stream_intr(struct hdac_softc *sc, struct hdac_chan *ch) { /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA uint32_t res; #endif if (ch->blkcnt == 0) return (0); /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA res = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDSTS); #endif /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA HDA_BOOTVERBOSE( if (res & (HDAC_SDSTS_DESE | HDAC_SDSTS_FIFOE)) device_printf(sc->dev, "PCMDIR_%s intr triggered beyond stream boundary:" "%08x\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", res); ); #endif HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDSTS, HDAC_SDSTS_DESE | HDAC_SDSTS_FIFOE | HDAC_SDSTS_BCIS ); /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA if (res & HDAC_SDSTS_BCIS) { #endif return (1); /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA } #endif return (0); } /**************************************************************************** * void hdac_intr_handler(void *) * * Interrupt handler. Processes interrupts received from the hdac. ****************************************************************************/ static void hdac_intr_handler(void *context) { struct hdac_softc *sc; uint32_t intsts; uint8_t rirbsts; struct hdac_rirb *rirb_base; uint32_t trigger = 0; sc = (struct hdac_softc *)context; hdac_lock(sc); if (sc->polling != 0) { hdac_unlock(sc); return; } /* Do we have anything to do? */ intsts = HDAC_READ_4(&sc->mem, HDAC_INTSTS); if (!HDA_FLAG_MATCH(intsts, HDAC_INTSTS_GIS)) { hdac_unlock(sc); return; } /* Was this a controller interrupt? */ if (HDA_FLAG_MATCH(intsts, HDAC_INTSTS_CIS)) { rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; rirbsts = HDAC_READ_1(&sc->mem, HDAC_RIRBSTS); /* Get as many responses that we can */ while (HDA_FLAG_MATCH(rirbsts, HDAC_RIRBSTS_RINTFL)) { HDAC_WRITE_1(&sc->mem, HDAC_RIRBSTS, HDAC_RIRBSTS_RINTFL); hdac_rirb_flush(sc); rirbsts = HDAC_READ_1(&sc->mem, HDAC_RIRBSTS); } /* XXX to be removed */ /* Clear interrupt and exit */ #ifdef HDAC_INTR_EXTRA HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, HDAC_INTSTS_CIS); #endif } hdac_unsolq_flush(sc); if (intsts & HDAC_INTSTS_SIS_MASK) { if ((intsts & (1 << sc->num_iss)) && hdac_stream_intr(sc, &sc->play) != 0) trigger |= 1; if ((intsts & (1 << 0)) && hdac_stream_intr(sc, &sc->rec) != 0) trigger |= 2; /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, intsts & HDAC_INTSTS_SIS_MASK); #endif } hdac_unlock(sc); if (trigger & 1) chn_intr(sc->play.c); if (trigger & 2) chn_intr(sc->rec.c); } /**************************************************************************** * int hdac_reset(hdac_softc *) * * Reset the hdac to a quiescent and known state. ****************************************************************************/ static int hdac_reset(struct hdac_softc *sc) { uint32_t gctl; int count, i; /* * Stop all Streams DMA engine */ for (i = 0; i < sc->num_iss; i++) HDAC_WRITE_4(&sc->mem, HDAC_ISDCTL(sc, i), 0x0); for (i = 0; i < sc->num_oss; i++) HDAC_WRITE_4(&sc->mem, HDAC_OSDCTL(sc, i), 0x0); for (i = 0; i < sc->num_bss; i++) HDAC_WRITE_4(&sc->mem, HDAC_BSDCTL(sc, i), 0x0); /* * Stop Control DMA engines. */ HDAC_WRITE_1(&sc->mem, HDAC_CORBCTL, 0x0); HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, 0x0); /* * Reset DMA position buffer. */ HDAC_WRITE_4(&sc->mem, HDAC_DPIBLBASE, 0x0); HDAC_WRITE_4(&sc->mem, HDAC_DPIBUBASE, 0x0); /* * Reset the controller. The reset must remain asserted for * a minimum of 100us. */ gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); HDAC_WRITE_4(&sc->mem, HDAC_GCTL, gctl & ~HDAC_GCTL_CRST); count = 10000; do { gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); if (!(gctl & HDAC_GCTL_CRST)) break; DELAY(10); } while (--count); if (gctl & HDAC_GCTL_CRST) { device_printf(sc->dev, "Unable to put hdac in reset\n"); return (ENXIO); } DELAY(100); gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); HDAC_WRITE_4(&sc->mem, HDAC_GCTL, gctl | HDAC_GCTL_CRST); count = 10000; do { gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); if (gctl & HDAC_GCTL_CRST) break; DELAY(10); } while (--count); if (!(gctl & HDAC_GCTL_CRST)) { device_printf(sc->dev, "Device stuck in reset\n"); return (ENXIO); } /* * Wait for codecs to finish their own reset sequence. The delay here * should be of 250us but for some reasons, on it's not enough on my * computer. Let's use twice as much as necessary to make sure that * it's reset properly. */ DELAY(1000); return (0); } /**************************************************************************** * int hdac_get_capabilities(struct hdac_softc *); * * Retreive the general capabilities of the hdac; * Number of Input Streams * Number of Output Streams * Number of bidirectional Streams * 64bit ready * CORB and RIRB sizes ****************************************************************************/ static int hdac_get_capabilities(struct hdac_softc *sc) { uint16_t gcap; uint8_t corbsize, rirbsize; gcap = HDAC_READ_2(&sc->mem, HDAC_GCAP); sc->num_iss = HDAC_GCAP_ISS(gcap); sc->num_oss = HDAC_GCAP_OSS(gcap); sc->num_bss = HDAC_GCAP_BSS(gcap); sc->support_64bit = HDA_FLAG_MATCH(gcap, HDAC_GCAP_64OK); corbsize = HDAC_READ_1(&sc->mem, HDAC_CORBSIZE); if ((corbsize & HDAC_CORBSIZE_CORBSZCAP_256) == HDAC_CORBSIZE_CORBSZCAP_256) sc->corb_size = 256; else if ((corbsize & HDAC_CORBSIZE_CORBSZCAP_16) == HDAC_CORBSIZE_CORBSZCAP_16) sc->corb_size = 16; else if ((corbsize & HDAC_CORBSIZE_CORBSZCAP_2) == HDAC_CORBSIZE_CORBSZCAP_2) sc->corb_size = 2; else { device_printf(sc->dev, "%s: Invalid corb size (%x)\n", __func__, corbsize); return (ENXIO); } rirbsize = HDAC_READ_1(&sc->mem, HDAC_RIRBSIZE); if ((rirbsize & HDAC_RIRBSIZE_RIRBSZCAP_256) == HDAC_RIRBSIZE_RIRBSZCAP_256) sc->rirb_size = 256; else if ((rirbsize & HDAC_RIRBSIZE_RIRBSZCAP_16) == HDAC_RIRBSIZE_RIRBSZCAP_16) sc->rirb_size = 16; else if ((rirbsize & HDAC_RIRBSIZE_RIRBSZCAP_2) == HDAC_RIRBSIZE_RIRBSZCAP_2) sc->rirb_size = 2; else { device_printf(sc->dev, "%s: Invalid rirb size (%x)\n", __func__, rirbsize); return (ENXIO); } return (0); } /**************************************************************************** * void hdac_dma_cb * * This function is called by bus_dmamap_load when the mapping has been * established. We just record the physical address of the mapping into * the struct hdac_dma passed in. ****************************************************************************/ static void hdac_dma_cb(void *callback_arg, bus_dma_segment_t *segs, int nseg, int error) { struct hdac_dma *dma; if (error == 0) { dma = (struct hdac_dma *)callback_arg; dma->dma_paddr = segs[0].ds_addr; } } /**************************************************************************** * int hdac_dma_alloc * * This function allocate and setup a dma region (struct hdac_dma). * It must be freed by a corresponding hdac_dma_free. ****************************************************************************/ static int hdac_dma_alloc(struct hdac_softc *sc, struct hdac_dma *dma, bus_size_t size) { bus_size_t roundsz; int result; int lowaddr; roundsz = roundup2(size, HDAC_DMA_ALIGNMENT); lowaddr = (sc->support_64bit) ? BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT; bzero(dma, sizeof(*dma)); /* * Create a DMA tag */ result = bus_dma_tag_create(NULL, /* parent */ HDAC_DMA_ALIGNMENT, /* alignment */ 0, /* boundary */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filtfunc */ NULL, /* fistfuncarg */ roundsz, /* maxsize */ 1, /* nsegments */ roundsz, /* maxsegsz */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &dma->dma_tag); /* dmat */ if (result != 0) { device_printf(sc->dev, "%s: bus_dma_tag_create failed (%x)\n", __func__, result); goto hdac_dma_alloc_fail; } /* * Allocate DMA memory */ result = bus_dmamem_alloc(dma->dma_tag, (void **)&dma->dma_vaddr, BUS_DMA_NOWAIT | BUS_DMA_ZERO | ((sc->nocache != 0) ? BUS_DMA_NOCACHE : 0), &dma->dma_map); if (result != 0) { device_printf(sc->dev, "%s: bus_dmamem_alloc failed (%x)\n", __func__, result); goto hdac_dma_alloc_fail; } dma->dma_size = roundsz; /* * Map the memory */ result = bus_dmamap_load(dma->dma_tag, dma->dma_map, (void *)dma->dma_vaddr, roundsz, hdac_dma_cb, (void *)dma, 0); if (result != 0 || dma->dma_paddr == 0) { if (result == 0) result = ENOMEM; device_printf(sc->dev, "%s: bus_dmamem_load failed (%x)\n", __func__, result); goto hdac_dma_alloc_fail; } HDA_BOOTVERBOSE( device_printf(sc->dev, "%s: size=%ju -> roundsz=%ju\n", __func__, (uintmax_t)size, (uintmax_t)roundsz); ); return (0); hdac_dma_alloc_fail: hdac_dma_free(sc, dma); return (result); } /**************************************************************************** * void hdac_dma_free(struct hdac_softc *, struct hdac_dma *) * * Free a struct dhac_dma that has been previously allocated via the * hdac_dma_alloc function. ****************************************************************************/ static void hdac_dma_free(struct hdac_softc *sc, struct hdac_dma *dma) { if (dma->dma_map != NULL) { #if 0 /* Flush caches */ bus_dmamap_sync(dma->dma_tag, dma->dma_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); #endif bus_dmamap_unload(dma->dma_tag, dma->dma_map); } if (dma->dma_vaddr != NULL) { bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); dma->dma_vaddr = NULL; } dma->dma_map = NULL; if (dma->dma_tag != NULL) { bus_dma_tag_destroy(dma->dma_tag); dma->dma_tag = NULL; } dma->dma_size = 0; } /**************************************************************************** * int hdac_mem_alloc(struct hdac_softc *) * * Allocate all the bus resources necessary to speak with the physical * controller. ****************************************************************************/ static int hdac_mem_alloc(struct hdac_softc *sc) { struct hdac_mem *mem; mem = &sc->mem; mem->mem_rid = PCIR_BAR(0); mem->mem_res = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &mem->mem_rid, RF_ACTIVE); if (mem->mem_res == NULL) { device_printf(sc->dev, "%s: Unable to allocate memory resource\n", __func__); return (ENOMEM); } mem->mem_tag = rman_get_bustag(mem->mem_res); mem->mem_handle = rman_get_bushandle(mem->mem_res); return (0); } /**************************************************************************** * void hdac_mem_free(struct hdac_softc *) * * Free up resources previously allocated by hdac_mem_alloc. ****************************************************************************/ static void hdac_mem_free(struct hdac_softc *sc) { struct hdac_mem *mem; mem = &sc->mem; if (mem->mem_res != NULL) bus_release_resource(sc->dev, SYS_RES_MEMORY, mem->mem_rid, mem->mem_res); mem->mem_res = NULL; } /**************************************************************************** * int hdac_irq_alloc(struct hdac_softc *) * * Allocate and setup the resources necessary for interrupt handling. ****************************************************************************/ static int hdac_irq_alloc(struct hdac_softc *sc) { struct hdac_irq *irq; int result; irq = &sc->irq; irq->irq_rid = 0x0; irq->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &irq->irq_rid, RF_SHAREABLE | RF_ACTIVE); if (irq->irq_res == NULL) { device_printf(sc->dev, "%s: Unable to allocate irq\n", __func__); goto hdac_irq_alloc_fail; } result = snd_setup_intr(sc->dev, irq->irq_res, INTR_MPSAFE, hdac_intr_handler, sc, &irq->irq_handle); if (result != 0) { device_printf(sc->dev, "%s: Unable to setup interrupt handler (%x)\n", __func__, result); goto hdac_irq_alloc_fail; } return (0); hdac_irq_alloc_fail: hdac_irq_free(sc); return (ENXIO); } /**************************************************************************** * void hdac_irq_free(struct hdac_softc *) * * Free up resources previously allocated by hdac_irq_alloc. ****************************************************************************/ static void hdac_irq_free(struct hdac_softc *sc) { struct hdac_irq *irq; irq = &sc->irq; if (irq->irq_res != NULL && irq->irq_handle != NULL) bus_teardown_intr(sc->dev, irq->irq_res, irq->irq_handle); if (irq->irq_res != NULL) bus_release_resource(sc->dev, SYS_RES_IRQ, irq->irq_rid, irq->irq_res); irq->irq_handle = NULL; irq->irq_res = NULL; } /**************************************************************************** * void hdac_corb_init(struct hdac_softc *) * * Initialize the corb registers for operations but do not start it up yet. * The CORB engine must not be running when this function is called. ****************************************************************************/ static void hdac_corb_init(struct hdac_softc *sc) { uint8_t corbsize; uint64_t corbpaddr; /* Setup the CORB size. */ switch (sc->corb_size) { case 256: corbsize = HDAC_CORBSIZE_CORBSIZE(HDAC_CORBSIZE_CORBSIZE_256); break; case 16: corbsize = HDAC_CORBSIZE_CORBSIZE(HDAC_CORBSIZE_CORBSIZE_16); break; case 2: corbsize = HDAC_CORBSIZE_CORBSIZE(HDAC_CORBSIZE_CORBSIZE_2); break; default: panic("%s: Invalid CORB size (%x)\n", __func__, sc->corb_size); } HDAC_WRITE_1(&sc->mem, HDAC_CORBSIZE, corbsize); /* Setup the CORB Address in the hdac */ corbpaddr = (uint64_t)sc->corb_dma.dma_paddr; HDAC_WRITE_4(&sc->mem, HDAC_CORBLBASE, (uint32_t)corbpaddr); HDAC_WRITE_4(&sc->mem, HDAC_CORBUBASE, (uint32_t)(corbpaddr >> 32)); /* Set the WP and RP */ sc->corb_wp = 0; HDAC_WRITE_2(&sc->mem, HDAC_CORBWP, sc->corb_wp); HDAC_WRITE_2(&sc->mem, HDAC_CORBRP, HDAC_CORBRP_CORBRPRST); /* * The HDA specification indicates that the CORBRPRST bit will always * read as zero. Unfortunately, it seems that at least the 82801G * doesn't reset the bit to zero, which stalls the corb engine. * manually reset the bit to zero before continuing. */ HDAC_WRITE_2(&sc->mem, HDAC_CORBRP, 0x0); /* Enable CORB error reporting */ #if 0 HDAC_WRITE_1(&sc->mem, HDAC_CORBCTL, HDAC_CORBCTL_CMEIE); #endif } /**************************************************************************** * void hdac_rirb_init(struct hdac_softc *) * * Initialize the rirb registers for operations but do not start it up yet. * The RIRB engine must not be running when this function is called. ****************************************************************************/ static void hdac_rirb_init(struct hdac_softc *sc) { uint8_t rirbsize; uint64_t rirbpaddr; /* Setup the RIRB size. */ switch (sc->rirb_size) { case 256: rirbsize = HDAC_RIRBSIZE_RIRBSIZE(HDAC_RIRBSIZE_RIRBSIZE_256); break; case 16: rirbsize = HDAC_RIRBSIZE_RIRBSIZE(HDAC_RIRBSIZE_RIRBSIZE_16); break; case 2: rirbsize = HDAC_RIRBSIZE_RIRBSIZE(HDAC_RIRBSIZE_RIRBSIZE_2); break; default: panic("%s: Invalid RIRB size (%x)\n", __func__, sc->rirb_size); } HDAC_WRITE_1(&sc->mem, HDAC_RIRBSIZE, rirbsize); /* Setup the RIRB Address in the hdac */ rirbpaddr = (uint64_t)sc->rirb_dma.dma_paddr; HDAC_WRITE_4(&sc->mem, HDAC_RIRBLBASE, (uint32_t)rirbpaddr); HDAC_WRITE_4(&sc->mem, HDAC_RIRBUBASE, (uint32_t)(rirbpaddr >> 32)); /* Setup the WP and RP */ sc->rirb_rp = 0; HDAC_WRITE_2(&sc->mem, HDAC_RIRBWP, HDAC_RIRBWP_RIRBWPRST); if (sc->polling == 0) { /* Setup the interrupt threshold */ HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, sc->rirb_size / 2); /* Enable Overrun and response received reporting */ #if 0 HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, HDAC_RIRBCTL_RIRBOIC | HDAC_RIRBCTL_RINTCTL); #else HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, HDAC_RIRBCTL_RINTCTL); #endif } #if 0 /* * Make sure that the Host CPU cache doesn't contain any dirty * cache lines that falls in the rirb. If I understood correctly, it * should be sufficient to do this only once as the rirb is purely * read-only from now on. */ bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, BUS_DMASYNC_PREREAD); #endif } /**************************************************************************** * void hdac_corb_start(hdac_softc *) * * Startup the corb DMA engine ****************************************************************************/ static void hdac_corb_start(struct hdac_softc *sc) { uint32_t corbctl; corbctl = HDAC_READ_1(&sc->mem, HDAC_CORBCTL); corbctl |= HDAC_CORBCTL_CORBRUN; HDAC_WRITE_1(&sc->mem, HDAC_CORBCTL, corbctl); } /**************************************************************************** * void hdac_rirb_start(hdac_softc *) * * Startup the rirb DMA engine ****************************************************************************/ static void hdac_rirb_start(struct hdac_softc *sc) { uint32_t rirbctl; rirbctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); rirbctl |= HDAC_RIRBCTL_RIRBDMAEN; HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, rirbctl); } /**************************************************************************** * void hdac_scan_codecs(struct hdac_softc *) * * Scan the bus for available codecs. ****************************************************************************/ static void hdac_scan_codecs(struct hdac_softc *sc) { struct hdac_codec *codec; int i; uint16_t statests; statests = HDAC_READ_2(&sc->mem, HDAC_STATESTS); for (i = 0; i < HDAC_CODEC_MAX; i++) { if (HDAC_STATESTS_SDIWAKE(statests, i)) { /* We have found a codec. */ codec = (struct hdac_codec *)malloc(sizeof(*codec), M_HDAC, M_ZERO | M_NOWAIT); if (codec == NULL) { device_printf(sc->dev, "Unable to allocate memory for codec\n"); continue; } codec->commands = NULL; codec->responses_received = 0; codec->verbs_sent = 0; codec->sc = sc; codec->cad = i; sc->codecs[i] = codec; if (hdac_probe_codec(codec) != 0) break; } } /* All codecs have been probed, now try to attach drivers to them */ /* bus_generic_attach(sc->dev); */ } /**************************************************************************** * void hdac_probe_codec(struct hdac_softc *, int) * * Probe a the given codec_id for available function groups. ****************************************************************************/ static int hdac_probe_codec(struct hdac_codec *codec) { struct hdac_softc *sc = codec->sc; struct hdac_devinfo *devinfo; uint32_t vendorid, revisionid, subnode; int startnode; int endnode; int i; nid_t cad = codec->cad; HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Probing codec: %d\n", cad); ); vendorid = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, 0x0, HDA_PARAM_VENDOR_ID), cad); revisionid = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, 0x0, HDA_PARAM_REVISION_ID), cad); subnode = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, 0x0, HDA_PARAM_SUB_NODE_COUNT), cad); startnode = HDA_PARAM_SUB_NODE_COUNT_START(subnode); endnode = startnode + HDA_PARAM_SUB_NODE_COUNT_TOTAL(subnode); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: \tstartnode=%d endnode=%d\n", startnode, endnode); ); for (i = startnode; i < endnode; i++) { devinfo = hdac_probe_function(codec, i); if (devinfo != NULL) { /* XXX Ignore other FG. */ devinfo->vendor_id = HDA_PARAM_VENDOR_ID_VENDOR_ID(vendorid); devinfo->device_id = HDA_PARAM_VENDOR_ID_DEVICE_ID(vendorid); devinfo->revision_id = HDA_PARAM_REVISION_ID_REVISION_ID(revisionid); devinfo->stepping_id = HDA_PARAM_REVISION_ID_STEPPING_ID(revisionid); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: \tFound AFG nid=%d " "[startnode=%d endnode=%d]\n", devinfo->nid, startnode, endnode); ); return (1); } } HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: \tAFG not found\n"); ); return (0); } static struct hdac_devinfo * hdac_probe_function(struct hdac_codec *codec, nid_t nid) { struct hdac_softc *sc = codec->sc; struct hdac_devinfo *devinfo; uint32_t fctgrptype; nid_t cad = codec->cad; fctgrptype = HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE(hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_FCT_GRP_TYPE), cad)); /* XXX For now, ignore other FG. */ if (fctgrptype != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) return (NULL); devinfo = (struct hdac_devinfo *)malloc(sizeof(*devinfo), M_HDAC, M_NOWAIT | M_ZERO); if (devinfo == NULL) { device_printf(sc->dev, "%s: Unable to allocate ivar\n", __func__); return (NULL); } devinfo->nid = nid; devinfo->node_type = fctgrptype; devinfo->codec = codec; hdac_add_child(sc, devinfo); return (devinfo); } static void hdac_add_child(struct hdac_softc *sc, struct hdac_devinfo *devinfo) { devinfo->dev = device_add_child(sc->dev, NULL, -1); device_set_ivars(devinfo->dev, (void *)devinfo); /* XXX - Print more information when booting verbose??? */ } static void hdac_widget_connection_parse(struct hdac_widget *w) { struct hdac_softc *sc = w->devinfo->codec->sc; uint32_t res; int i, j, max, ents, entnum; nid_t cad = w->devinfo->codec->cad; nid_t nid = w->nid; nid_t cnid, addcnid, prevcnid; w->nconns = 0; res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_CONN_LIST_LENGTH), cad); ents = HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(res); if (ents < 1) return; entnum = HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(res) ? 2 : 4; max = (sizeof(w->conns) / sizeof(w->conns[0])) - 1; prevcnid = 0; #define CONN_RMASK(e) (1 << ((32 / (e)) - 1)) #define CONN_NMASK(e) (CONN_RMASK(e) - 1) #define CONN_RESVAL(r, e, n) ((r) >> ((32 / (e)) * (n))) #define CONN_RANGE(r, e, n) (CONN_RESVAL(r, e, n) & CONN_RMASK(e)) #define CONN_CNID(r, e, n) (CONN_RESVAL(r, e, n) & CONN_NMASK(e)) for (i = 0; i < ents; i += entnum) { res = hdac_command(sc, HDA_CMD_GET_CONN_LIST_ENTRY(cad, nid, i), cad); for (j = 0; j < entnum; j++) { cnid = CONN_CNID(res, entnum, j); if (cnid == 0) { if (w->nconns < ents) device_printf(sc->dev, "%s: nid=%d WARNING: zero cnid " "entnum=%d j=%d index=%d " "entries=%d found=%d res=0x%08x\n", __func__, nid, entnum, j, i, ents, w->nconns, res); else goto getconns_out; } if (cnid < w->devinfo->startnode || cnid >= w->devinfo->endnode) { HDA_BOOTVERBOSE( device_printf(sc->dev, "%s: GHOST: nid=%d j=%d " "entnum=%d index=%d res=0x%08x\n", __func__, nid, j, entnum, i, res); ); } if (CONN_RANGE(res, entnum, j) == 0) addcnid = cnid; else if (prevcnid == 0 || prevcnid >= cnid) { device_printf(sc->dev, "%s: WARNING: Invalid child range " "nid=%d index=%d j=%d entnum=%d " "prevcnid=%d cnid=%d res=0x%08x\n", __func__, nid, i, j, entnum, prevcnid, cnid, res); addcnid = cnid; } else addcnid = prevcnid + 1; while (addcnid <= cnid) { if (w->nconns > max) { device_printf(sc->dev, "%s: nid=%d: Adding %d: " "Max connection reached! max=%d\n", __func__, nid, addcnid, max + 1); goto getconns_out; } w->conns[w->nconns++] = addcnid++; } prevcnid = cnid; } } getconns_out: HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: %s: nid=%d entries=%d found=%d\n", __func__, nid, ents, w->nconns); ); return; } static uint32_t hdac_widget_pin_getconfig(struct hdac_widget *w) { struct hdac_softc *sc; uint32_t config, orig, id; nid_t cad, nid; sc = w->devinfo->codec->sc; cad = w->devinfo->codec->cad; nid = w->nid; id = hdac_codec_id(w->devinfo); config = hdac_command(sc, HDA_CMD_GET_CONFIGURATION_DEFAULT(cad, nid), cad); orig = config; /* * XXX REWRITE!!!! Don't argue! */ if (id == HDA_CODEC_ALC880 && sc->pci_subvendor == LG_LW20_SUBVENDOR) { switch (nid) { case 26: config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; break; case 27: config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT; break; default: break; } } else if (id == HDA_CODEC_ALC880 && (sc->pci_subvendor == CLEVO_D900T_SUBVENDOR || sc->pci_subvendor == ASUS_M5200_SUBVENDOR)) { /* * Super broken BIOS */ switch (nid) { case 20: break; case 21: break; case 22: break; case 23: break; case 24: /* MIC1 */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN; break; case 25: /* XXX MIC2 */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN; break; case 26: /* LINE1 */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; break; case 27: /* XXX LINE2 */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; break; case 28: /* CD */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_CD; break; case 30: break; case 31: break; default: break; } } else if (id == HDA_CODEC_ALC883 && HDA_DEV_MATCH(ACER_ALL_SUBVENDOR, sc->pci_subvendor)) { switch (nid) { case 25: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); break; case 28: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_CD | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); break; default: break; } } else if (id == HDA_CODEC_CXVENICE && sc->pci_subvendor == HP_V3000_SUBVENDOR) { switch (nid) { case 18: config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; config |= HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE; break; case 20: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); break; case 21: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_CD | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); break; default: break; } } else if (id == HDA_CODEC_CXWAIKIKI && sc->pci_subvendor == HP_DV5000_SUBVENDOR) { switch (nid) { case 20: case 21: config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; config |= HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE; break; default: break; } } else if (id == HDA_CODEC_ALC861 && sc->pci_subvendor == ASUS_W6F_SUBVENDOR) { switch (nid) { case 11: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); break; case 15: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK); break; default: break; } } else if (id == HDA_CODEC_ALC861 && sc->pci_subvendor == UNIWILL_9075_SUBVENDOR) { switch (nid) { case 15: config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT | HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK); break; default: break; } } else if (id == HDA_CODEC_AD1986A && sc->pci_subvendor == ASUS_M2NPVMX_SUBVENDOR) { switch (nid) { case 28: /* LINE */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; break; case 29: /* MIC */ config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; config |= HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN; break; default: break; } } HDA_BOOTVERBOSE( if (config != orig) device_printf(sc->dev, "HDA_DEBUG: Pin config nid=%u 0x%08x -> 0x%08x\n", nid, orig, config); ); return (config); } static uint32_t hdac_widget_pin_getcaps(struct hdac_widget *w) { struct hdac_softc *sc; uint32_t caps, orig, id; nid_t cad, nid; sc = w->devinfo->codec->sc; cad = w->devinfo->codec->cad; nid = w->nid; id = hdac_codec_id(w->devinfo); caps = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_PIN_CAP), cad); orig = caps; HDA_BOOTVERBOSE( if (caps != orig) device_printf(sc->dev, "HDA_DEBUG: Pin caps nid=%u 0x%08x -> 0x%08x\n", nid, orig, caps); ); return (caps); } static void hdac_widget_pin_parse(struct hdac_widget *w) { struct hdac_softc *sc = w->devinfo->codec->sc; uint32_t config, pincap; char *devstr, *connstr; nid_t cad = w->devinfo->codec->cad; nid_t nid = w->nid; config = hdac_widget_pin_getconfig(w); w->wclass.pin.config = config; pincap = hdac_widget_pin_getcaps(w); w->wclass.pin.cap = pincap; w->wclass.pin.ctrl = hdac_command(sc, HDA_CMD_GET_PIN_WIDGET_CTRL(cad, nid), cad) & ~(HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) { w->param.eapdbtl = hdac_command(sc, HDA_CMD_GET_EAPD_BTL_ENABLE(cad, nid), cad); w->param.eapdbtl &= 0x7; w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; } else w->param.eapdbtl = HDAC_INVALID; switch (config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT: devstr = "line out"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: devstr = "speaker"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT: devstr = "headphones out"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: devstr = "CD"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT: devstr = "SPDIF out"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT: devstr = "digital (other) out"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MODEM_LINE: devstr = "modem, line side"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MODEM_HANDSET: devstr = "modem, handset side"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: devstr = "line in"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_AUX: devstr = "AUX"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: devstr = "Mic in"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_TELEPHONY: devstr = "telephony"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN: devstr = "SPDIF in"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_IN: devstr = "digital (other) in"; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_OTHER: devstr = "other"; break; default: devstr = "unknown"; break; } switch (config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) { case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK: connstr = "jack"; break; case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE: connstr = "none"; break; case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED: connstr = "fixed"; break; case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_BOTH: connstr = "jack / fixed"; break; default: connstr = "unknown"; break; } strlcat(w->name, ": ", sizeof(w->name)); strlcat(w->name, devstr, sizeof(w->name)); strlcat(w->name, " (", sizeof(w->name)); strlcat(w->name, connstr, sizeof(w->name)); strlcat(w->name, ")", sizeof(w->name)); } static void hdac_widget_parse(struct hdac_widget *w) { struct hdac_softc *sc = w->devinfo->codec->sc; uint32_t wcap, cap; char *typestr; nid_t cad = w->devinfo->codec->cad; nid_t nid = w->nid; wcap = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_AUDIO_WIDGET_CAP), cad); w->param.widget_cap = wcap; w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(wcap); switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: typestr = "audio output"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: typestr = "audio input"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: typestr = "audio mixer"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: typestr = "audio selector"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: typestr = "pin"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET: typestr = "power widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET: typestr = "volume widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: typestr = "beep widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET: typestr = "vendor widget"; break; default: typestr = "unknown type"; break; } strlcpy(w->name, typestr, sizeof(w->name)); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(wcap)) { hdac_command(sc, HDA_CMD_SET_POWER_STATE(cad, nid, HDA_CMD_POWER_STATE_D0), cad); DELAY(1000); } hdac_widget_connection_parse(w); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.outamp_cap = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_OUTPUT_AMP_CAP), cad); else w->param.outamp_cap = w->devinfo->function.audio.outamp_cap; } else w->param.outamp_cap = 0; if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.inamp_cap = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_INPUT_AMP_CAP), cad); else w->param.inamp_cap = w->devinfo->function.audio.inamp_cap; } else w->param.inamp_cap = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { if (HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(wcap)) { cap = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_SUPP_STREAM_FORMATS), cad); w->param.supp_stream_formats = (cap != 0) ? cap : w->devinfo->function.audio.supp_stream_formats; cap = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE), cad); w->param.supp_pcm_size_rate = (cap != 0) ? cap : w->devinfo->function.audio.supp_pcm_size_rate; } else { w->param.supp_stream_formats = w->devinfo->function.audio.supp_stream_formats; w->param.supp_pcm_size_rate = w->devinfo->function.audio.supp_pcm_size_rate; } } else { w->param.supp_stream_formats = 0; w->param.supp_pcm_size_rate = 0; } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) hdac_widget_pin_parse(w); } static struct hdac_widget * hdac_widget_get(struct hdac_devinfo *devinfo, nid_t nid) { if (devinfo == NULL || devinfo->widget == NULL || nid < devinfo->startnode || nid >= devinfo->endnode) return (NULL); return (&devinfo->widget[nid - devinfo->startnode]); } static __inline int hda_poll_channel(struct hdac_chan *ch) { uint32_t sz, delta; volatile uint32_t ptr; if (ch->active == 0) return (0); sz = ch->blksz * ch->blkcnt; if (ch->dmapos != NULL) ptr = *(ch->dmapos); else ptr = HDAC_READ_4(&ch->devinfo->codec->sc->mem, ch->off + HDAC_SDLPIB); ch->ptr = ptr; ptr %= sz; ptr &= ~(ch->blksz - 1); delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } #define hda_chan_active(sc) ((sc)->play.active + (sc)->rec.active) static void hda_poll_callback(void *arg) { struct hdac_softc *sc = arg; uint32_t trigger = 0; if (sc == NULL) return; hdac_lock(sc); if (sc->polling == 0 || hda_chan_active(sc) == 0) { hdac_unlock(sc); return; } trigger |= (hda_poll_channel(&sc->play) != 0) ? 1 : 0; trigger |= (hda_poll_channel(&sc->rec) != 0) ? 2 : 0; /* XXX */ callout_reset(&sc->poll_hda, 1/*sc->poll_ticks*/, hda_poll_callback, sc); hdac_unlock(sc); if (trigger & 1) chn_intr(sc->play.c); if (trigger & 2) chn_intr(sc->rec.c); } static int hdac_rirb_flush(struct hdac_softc *sc) { struct hdac_rirb *rirb_base, *rirb; struct hdac_codec *codec; struct hdac_command_list *commands; nid_t cad; uint32_t resp; uint8_t rirbwp; int ret = 0; rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; rirbwp = HDAC_READ_1(&sc->mem, HDAC_RIRBWP); #if 0 bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, BUS_DMASYNC_POSTREAD); #endif while (sc->rirb_rp != rirbwp) { sc->rirb_rp++; sc->rirb_rp %= sc->rirb_size; rirb = &rirb_base[sc->rirb_rp]; cad = HDAC_RIRB_RESPONSE_EX_SDATA_IN(rirb->response_ex); if (cad < 0 || cad >= HDAC_CODEC_MAX || sc->codecs[cad] == NULL) continue; resp = rirb->response; codec = sc->codecs[cad]; commands = codec->commands; if (rirb->response_ex & HDAC_RIRB_RESPONSE_EX_UNSOLICITED) { sc->unsolq[sc->unsolq_wp++] = (cad << 16) | ((resp >> 26) & 0xffff); sc->unsolq_wp %= HDAC_UNSOLQ_MAX; } else if (commands != NULL && commands->num_commands > 0 && codec->responses_received < commands->num_commands) commands->responses[codec->responses_received++] = resp; ret++; } return (ret); } static int hdac_unsolq_flush(struct hdac_softc *sc) { nid_t cad; uint32_t tag; int ret = 0; if (sc->unsolq_st == HDAC_UNSOLQ_READY) { sc->unsolq_st = HDAC_UNSOLQ_BUSY; while (sc->unsolq_rp != sc->unsolq_wp) { cad = sc->unsolq[sc->unsolq_rp] >> 16; tag = sc->unsolq[sc->unsolq_rp++] & 0xffff; sc->unsolq_rp %= HDAC_UNSOLQ_MAX; hdac_unsolicited_handler(sc->codecs[cad], tag); ret++; } sc->unsolq_st = HDAC_UNSOLQ_READY; } return (ret); } static void hdac_poll_callback(void *arg) { struct hdac_softc *sc = arg; if (sc == NULL) return; hdac_lock(sc); if (sc->polling == 0 || sc->poll_ival == 0) { hdac_unlock(sc); return; } hdac_rirb_flush(sc); hdac_unsolq_flush(sc); callout_reset(&sc->poll_hdac, sc->poll_ival, hdac_poll_callback, sc); hdac_unlock(sc); } static void hdac_stream_stop(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; uint32_t ctl; ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); ctl &= ~(HDAC_SDCTL_IOCE | HDAC_SDCTL_FEIE | HDAC_SDCTL_DEIE | HDAC_SDCTL_RUN); HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); ch->active = 0; if (sc->polling != 0) { int pollticks; if (hda_chan_active(sc) == 0) { callout_stop(&sc->poll_hda); sc->poll_ticks = 1; } else { if (sc->play.active != 0) ch = &sc->play; else ch = &sc->rec; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->b) * sndbuf_getspd(ch->b)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) { HDA_BOOTVERBOSE( device_printf(sc->dev, "%s: pollticks=%d < 1 !\n", __func__, pollticks); ); pollticks = 1; } if (pollticks > sc->poll_ticks) { HDA_BOOTVERBOSE( device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); ); sc->poll_ticks = pollticks; callout_reset(&sc->poll_hda, 1, hda_poll_callback, sc); } } } else { ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); ctl &= ~(1 << (ch->off >> 5)); HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); } } static void hdac_stream_start(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; uint32_t ctl; if (sc->polling != 0) { int pollticks; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->b) * sndbuf_getspd(ch->b)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) { HDA_BOOTVERBOSE( device_printf(sc->dev, "%s: pollticks=%d < 1 !\n", __func__, pollticks); ); pollticks = 1; } if (hda_chan_active(sc) == 0 || pollticks < sc->poll_ticks) { HDA_BOOTVERBOSE( if (hda_chan_active(sc) == 0) { device_printf(sc->dev, "%s: pollticks=%d\n", __func__, pollticks); } else { device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); } ); sc->poll_ticks = pollticks; callout_reset(&sc->poll_hda, 1, hda_poll_callback, sc); } ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); ctl |= HDAC_SDCTL_RUN; } else { ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); ctl |= 1 << (ch->off >> 5); HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); ctl |= HDAC_SDCTL_IOCE | HDAC_SDCTL_FEIE | HDAC_SDCTL_DEIE | HDAC_SDCTL_RUN; } HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); ch->active = 1; } static void hdac_stream_reset(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; int timeout = 1000; int to = timeout; uint32_t ctl; ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); ctl |= HDAC_SDCTL_SRST; HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); do { ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); if (ctl & HDAC_SDCTL_SRST) break; DELAY(10); } while (--to); if (!(ctl & HDAC_SDCTL_SRST)) { device_printf(sc->dev, "timeout in reset\n"); } ctl &= ~HDAC_SDCTL_SRST; HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); to = timeout; do { ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); if (!(ctl & HDAC_SDCTL_SRST)) break; DELAY(10); } while (--to); if (ctl & HDAC_SDCTL_SRST) device_printf(sc->dev, "can't reset!\n"); } static void hdac_stream_setid(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; uint32_t ctl; ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL2); ctl &= ~HDAC_SDCTL2_STRM_MASK; ctl |= ch->sid << HDAC_SDCTL2_STRM_SHIFT; HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL2, ctl); } static void hdac_bdl_setup(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; struct hdac_bdle *bdle; uint64_t addr; uint32_t blksz, blkcnt; int i; addr = (uint64_t)sndbuf_getbufaddr(ch->b); bdle = (struct hdac_bdle *)ch->bdl_dma.dma_vaddr; if (sc->polling != 0) { blksz = ch->blksz * ch->blkcnt; blkcnt = 1; } else { blksz = ch->blksz; blkcnt = ch->blkcnt; } for (i = 0; i < blkcnt; i++, bdle++) { bdle->addrl = (uint32_t)addr; bdle->addrh = (uint32_t)(addr >> 32); bdle->len = blksz; bdle->ioc = 1 ^ sc->polling; addr += blksz; } HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDCBL, blksz * blkcnt); HDAC_WRITE_2(&sc->mem, ch->off + HDAC_SDLVI, blkcnt - 1); addr = ch->bdl_dma.dma_paddr; HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDBDPL, (uint32_t)addr); HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDBDPU, (uint32_t)(addr >> 32)); if (ch->dmapos != NULL && !(HDAC_READ_4(&sc->mem, HDAC_DPIBLBASE) & 0x00000001)) { addr = sc->pos_dma.dma_paddr; HDAC_WRITE_4(&sc->mem, HDAC_DPIBLBASE, ((uint32_t)addr & HDAC_DPLBASE_DPLBASE_MASK) | 0x00000001); HDAC_WRITE_4(&sc->mem, HDAC_DPIBUBASE, (uint32_t)(addr >> 32)); } } static int hdac_bdl_alloc(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; int rc; rc = hdac_dma_alloc(sc, &ch->bdl_dma, sizeof(struct hdac_bdle) * HDA_BDL_MAX); if (rc) { device_printf(sc->dev, "can't alloc bdl\n"); return (rc); } return (0); } static void hdac_audio_ctl_amp_set_internal(struct hdac_softc *sc, nid_t cad, nid_t nid, int index, int lmute, int rmute, int left, int right, int dir) { uint16_t v = 0; if (sc == NULL) return; if (left != right || lmute != rmute) { v = (1 << (15 - dir)) | (1 << 13) | (index << 8) | (lmute << 7) | left; hdac_command(sc, HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, v), cad); v = (1 << (15 - dir)) | (1 << 12) | (index << 8) | (rmute << 7) | right; } else v = (1 << (15 - dir)) | (3 << 12) | (index << 8) | (lmute << 7) | left; hdac_command(sc, HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, v), cad); } static void hdac_audio_ctl_amp_set(struct hdac_audio_ctl *ctl, uint32_t mute, int left, int right) { struct hdac_softc *sc; nid_t nid, cad; int lmute, rmute; if (ctl == NULL || ctl->widget == NULL || ctl->widget->devinfo == NULL || ctl->widget->devinfo->codec == NULL || ctl->widget->devinfo->codec->sc == NULL) return; sc = ctl->widget->devinfo->codec->sc; cad = ctl->widget->devinfo->codec->cad; nid = ctl->widget->nid; if (mute == HDA_AMP_MUTE_DEFAULT) { lmute = HDA_AMP_LEFT_MUTED(ctl->muted); rmute = HDA_AMP_RIGHT_MUTED(ctl->muted); } else { lmute = HDA_AMP_LEFT_MUTED(mute); rmute = HDA_AMP_RIGHT_MUTED(mute); } if (ctl->dir & HDA_CTL_OUT) hdac_audio_ctl_amp_set_internal(sc, cad, nid, ctl->index, lmute, rmute, left, right, 0); if (ctl->dir & HDA_CTL_IN) hdac_audio_ctl_amp_set_internal(sc, cad, nid, ctl->index, lmute, rmute, left, right, 1); ctl->left = left; ctl->right = right; } static void hdac_widget_connection_select(struct hdac_widget *w, uint8_t index) { if (w == NULL || w->nconns < 1 || index > (w->nconns - 1)) return; hdac_command(w->devinfo->codec->sc, HDA_CMD_SET_CONNECTION_SELECT_CONTROL(w->devinfo->codec->cad, w->nid, index), w->devinfo->codec->cad); w->selconn = index; } /**************************************************************************** * uint32_t hdac_command_sendone_internal * * Wrapper function that sends only one command to a given codec ****************************************************************************/ static uint32_t hdac_command_sendone_internal(struct hdac_softc *sc, uint32_t verb, nid_t cad) { struct hdac_command_list cl; uint32_t response = HDAC_INVALID; if (!hdac_lockowned(sc)) device_printf(sc->dev, "WARNING!!!! mtx not owned!!!!\n"); cl.num_commands = 1; cl.verbs = &verb; cl.responses = &response; hdac_command_send_internal(sc, &cl, cad); return (response); } /**************************************************************************** * hdac_command_send_internal * * Send a command list to the codec via the corb. We queue as much verbs as * we can and msleep on the codec. When the interrupt get the responses * back from the rirb, it will wake us up so we can queue the remaining verbs * if any. ****************************************************************************/ static void hdac_command_send_internal(struct hdac_softc *sc, struct hdac_command_list *commands, nid_t cad) { struct hdac_codec *codec; int corbrp; uint32_t *corb; int timeout; int retry = 10; struct hdac_rirb *rirb_base; if (sc == NULL || sc->codecs[cad] == NULL || commands == NULL || commands->num_commands < 1) return; codec = sc->codecs[cad]; codec->commands = commands; codec->responses_received = 0; codec->verbs_sent = 0; corb = (uint32_t *)sc->corb_dma.dma_vaddr; rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; do { if (codec->verbs_sent != commands->num_commands) { /* Queue as many verbs as possible */ corbrp = HDAC_READ_2(&sc->mem, HDAC_CORBRP); #if 0 bus_dmamap_sync(sc->corb_dma.dma_tag, sc->corb_dma.dma_map, BUS_DMASYNC_PREWRITE); #endif while (codec->verbs_sent != commands->num_commands && ((sc->corb_wp + 1) % sc->corb_size) != corbrp) { sc->corb_wp++; sc->corb_wp %= sc->corb_size; corb[sc->corb_wp] = commands->verbs[codec->verbs_sent++]; } /* Send the verbs to the codecs */ #if 0 bus_dmamap_sync(sc->corb_dma.dma_tag, sc->corb_dma.dma_map, BUS_DMASYNC_POSTWRITE); #endif HDAC_WRITE_2(&sc->mem, HDAC_CORBWP, sc->corb_wp); } timeout = 1000; while (hdac_rirb_flush(sc) == 0 && --timeout) DELAY(10); } while ((codec->verbs_sent != commands->num_commands || codec->responses_received != commands->num_commands) && --retry); if (retry == 0) device_printf(sc->dev, "%s: TIMEOUT numcmd=%d, sent=%d, received=%d\n", __func__, commands->num_commands, codec->verbs_sent, codec->responses_received); codec->commands = NULL; codec->responses_received = 0; codec->verbs_sent = 0; hdac_unsolq_flush(sc); } /**************************************************************************** * Device Methods ****************************************************************************/ /**************************************************************************** * int hdac_probe(device_t) * * Probe for the presence of an hdac. If none is found, check for a generic * match using the subclass of the device. ****************************************************************************/ static int hdac_probe(device_t dev) { int i, result; uint32_t model; uint16_t class, subclass; char desc[64]; model = (uint32_t)pci_get_device(dev) << 16; model |= (uint32_t)pci_get_vendor(dev) & 0x0000ffff; class = pci_get_class(dev); subclass = pci_get_subclass(dev); bzero(desc, sizeof(desc)); result = ENXIO; for (i = 0; i < HDAC_DEVICES_LEN; i++) { if (hdac_devices[i].model == model) { strlcpy(desc, hdac_devices[i].desc, sizeof(desc)); result = BUS_PROBE_DEFAULT; break; } if (HDA_DEV_MATCH(hdac_devices[i].model, model) && class == PCIC_MULTIMEDIA && subclass == PCIS_MULTIMEDIA_HDA) { strlcpy(desc, hdac_devices[i].desc, sizeof(desc)); result = BUS_PROBE_GENERIC; break; } } if (result == ENXIO && class == PCIC_MULTIMEDIA && subclass == PCIS_MULTIMEDIA_HDA) { strlcpy(desc, "Generic", sizeof(desc)); result = BUS_PROBE_GENERIC; } if (result != ENXIO) { strlcat(desc, " High Definition Audio Controller", sizeof(desc)); device_set_desc_copy(dev, desc); } return (result); } static void * hdac_channel_init(kobj_t obj, void *data, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct hdac_devinfo *devinfo = data; struct hdac_softc *sc = devinfo->codec->sc; struct hdac_chan *ch; hdac_lock(sc); if (dir == PCMDIR_PLAY) { ch = &sc->play; ch->off = (sc->num_iss + devinfo->function.audio.playcnt) << 5; devinfo->function.audio.playcnt++; } else { ch = &sc->rec; ch->off = devinfo->function.audio.reccnt << 5; devinfo->function.audio.reccnt++; } if (devinfo->function.audio.quirks & HDA_QUIRK_FIXEDRATE) { ch->caps.minspeed = ch->caps.maxspeed = 48000; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; } if (sc->pos_dma.dma_vaddr != NULL) ch->dmapos = (uint32_t *)(sc->pos_dma.dma_vaddr + (sc->streamcnt * 8)); else ch->dmapos = NULL; ch->sid = ++sc->streamcnt; ch->dir = dir; ch->b = b; ch->c = c; ch->devinfo = devinfo; ch->blksz = sc->chan_size / sc->chan_blkcnt; ch->blkcnt = sc->chan_blkcnt; hdac_unlock(sc); if (hdac_bdl_alloc(ch) != 0) { ch->blkcnt = 0; return (NULL); } if (sndbuf_alloc(ch->b, sc->chan_dmat, (sc->nocache != 0) ? BUS_DMA_NOCACHE : 0, sc->chan_size) != 0) return (NULL); return (ch); } static int hdac_channel_setformat(kobj_t obj, void *data, uint32_t format) { struct hdac_chan *ch = data; int i; for (i = 0; ch->caps.fmtlist[i] != 0; i++) { if (format == ch->caps.fmtlist[i]) { ch->fmt = format; return (0); } } return (EINVAL); } static int hdac_channel_setspeed(kobj_t obj, void *data, uint32_t speed) { struct hdac_chan *ch = data; uint32_t spd = 0, threshold; int i; for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; threshold = spd + ((ch->pcmrates[i + 1] != 0) ? ((ch->pcmrates[i + 1] - spd) >> 1) : 0); if (speed < threshold) break; } if (spd == 0) /* impossible */ ch->spd = 48000; else ch->spd = spd; return (ch->spd); } static void hdac_stream_setup(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; int i; nid_t cad = ch->devinfo->codec->cad; uint16_t fmt; fmt = 0; if (ch->fmt & AFMT_S16_LE) fmt |= ch->bit16 << 4; else if (ch->fmt & AFMT_S32_LE) fmt |= ch->bit32 << 4; else fmt |= 1 << 4; for (i = 0; i < HDA_RATE_TAB_LEN; i++) { if (hda_rate_tab[i].valid && ch->spd == hda_rate_tab[i].rate) { fmt |= hda_rate_tab[i].base; fmt |= hda_rate_tab[i].mul; fmt |= hda_rate_tab[i].div; break; } } if (ch->fmt & AFMT_STEREO) fmt |= 1; HDAC_WRITE_2(&sc->mem, ch->off + HDAC_SDFMT, fmt); for (i = 0; ch->io[i] != -1; i++) { HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: PCMDIR_%s: Stream setup nid=%d " "fmt=0x%08x\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->io[i], fmt); ); hdac_command(sc, HDA_CMD_SET_CONV_FMT(cad, ch->io[i], fmt), cad); hdac_command(sc, HDA_CMD_SET_CONV_STREAM_CHAN(cad, ch->io[i], ch->sid << 4), cad); } } static int hdac_channel_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct hdac_chan *ch = data; struct hdac_softc *sc = ch->devinfo->codec->sc; blksz &= HDA_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN)) blksz = sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN; if (blksz < HDA_BLK_MIN) blksz = HDA_BLK_MIN; if (blkcnt > HDA_BDL_MAX) blkcnt = HDA_BDL_MAX; if (blkcnt < HDA_BDL_MIN) blkcnt = HDA_BDL_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->b)) { if ((blkcnt >> 1) >= HDA_BDL_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= HDA_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->b) != blksz || sndbuf_getblkcnt(ch->b) != blkcnt) && sndbuf_resize(ch->b, blkcnt, blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->b); ch->blkcnt = sndbuf_getblkcnt(ch->b); return (1); } static int hdac_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct hdac_chan *ch = data; struct hdac_softc *sc = ch->devinfo->codec->sc; hdac_channel_setfragments(obj, data, blksz, sc->chan_blkcnt); return (ch->blksz); } static void hdac_channel_stop(struct hdac_softc *sc, struct hdac_chan *ch) { struct hdac_devinfo *devinfo = ch->devinfo; nid_t cad = devinfo->codec->cad; int i; hdac_stream_stop(ch); for (i = 0; ch->io[i] != -1; i++) { hdac_command(sc, HDA_CMD_SET_CONV_STREAM_CHAN(cad, ch->io[i], 0), cad); } } static void hdac_channel_start(struct hdac_softc *sc, struct hdac_chan *ch) { ch->ptr = 0; ch->prevptr = 0; hdac_stream_stop(ch); hdac_stream_reset(ch); hdac_bdl_setup(ch); hdac_stream_setid(ch); hdac_stream_setup(ch); hdac_stream_start(ch); } static int hdac_channel_trigger(kobj_t obj, void *data, int go) { struct hdac_chan *ch = data; struct hdac_softc *sc = ch->devinfo->codec->sc; + if (!PCMTRIG_COMMON(go)) + return (0); + hdac_lock(sc); switch (go) { case PCMTRIG_START: hdac_channel_start(sc, ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: hdac_channel_stop(sc, ch); break; default: break; } hdac_unlock(sc); return (0); } static int hdac_channel_getptr(kobj_t obj, void *data) { struct hdac_chan *ch = data; struct hdac_softc *sc = ch->devinfo->codec->sc; uint32_t ptr; hdac_lock(sc); if (sc->polling != 0) ptr = ch->ptr; else if (ch->dmapos != NULL) ptr = *(ch->dmapos); else ptr = HDAC_READ_4(&sc->mem, ch->off + HDAC_SDLPIB); hdac_unlock(sc); /* * Round to available space and force 128 bytes aligment. */ ptr %= ch->blksz * ch->blkcnt; ptr &= HDA_BLK_ALIGN; return (ptr); } static struct pcmchan_caps * hdac_channel_getcaps(kobj_t obj, void *data) { return (&((struct hdac_chan *)data)->caps); } static kobj_method_t hdac_channel_methods[] = { KOBJMETHOD(channel_init, hdac_channel_init), KOBJMETHOD(channel_setformat, hdac_channel_setformat), KOBJMETHOD(channel_setspeed, hdac_channel_setspeed), KOBJMETHOD(channel_setblocksize, hdac_channel_setblocksize), KOBJMETHOD(channel_setfragments, hdac_channel_setfragments), KOBJMETHOD(channel_trigger, hdac_channel_trigger), KOBJMETHOD(channel_getptr, hdac_channel_getptr), KOBJMETHOD(channel_getcaps, hdac_channel_getcaps), { 0, 0 } }; CHANNEL_DECLARE(hdac_channel); static void hdac_jack_poll_callback(void *arg) { struct hdac_devinfo *devinfo = arg; struct hdac_softc *sc; if (devinfo == NULL || devinfo->codec == NULL || devinfo->codec->sc == NULL) return; sc = devinfo->codec->sc; hdac_lock(sc); if (sc->poll_ival == 0) { hdac_unlock(sc); return; } hdac_hp_switch_handler(devinfo); callout_reset(&sc->poll_jack, sc->poll_ival, hdac_jack_poll_callback, devinfo); hdac_unlock(sc); } static int hdac_audio_ctl_ossmixer_init(struct snd_mixer *m) { struct hdac_devinfo *devinfo = mix_getdevinfo(m); struct hdac_softc *sc = devinfo->codec->sc; struct hdac_widget *w, *cw; struct hdac_audio_ctl *ctl; uint32_t mask, recmask, id; int i, j, softpcmvol; nid_t cad; hdac_lock(sc); mask = 0; recmask = 0; id = hdac_codec_id(devinfo); cad = devinfo->codec->cad; for (i = 0; i < HDAC_HP_SWITCH_LEN; i++) { if (!(HDA_DEV_MATCH(hdac_hp_switch[i].model, sc->pci_subvendor) && hdac_hp_switch[i].id == id)) continue; w = hdac_widget_get(devinfo, hdac_hp_switch[i].hpnid); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (hdac_hp_switch[i].polling != 0) callout_reset(&sc->poll_jack, 1, hdac_jack_poll_callback, devinfo); else if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) hdac_command(sc, HDA_CMD_SET_UNSOLICITED_RESPONSE(cad, w->nid, HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE | HDAC_UNSOLTAG_EVENT_HP), cad); else continue; hdac_hp_switch_handler(devinfo); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Enabling headphone/speaker " "audio routing switching:\n"); device_printf(sc->dev, "HDA_DEBUG: \tindex=%d nid=%d " "pci_subvendor=0x%08x " "codec=0x%08x [%s]\n", i, w->nid, sc->pci_subvendor, id, (hdac_hp_switch[i].polling != 0) ? "POLL" : "UNSOL"); ); break; } for (i = 0; i < HDAC_EAPD_SWITCH_LEN; i++) { if (!(HDA_DEV_MATCH(hdac_eapd_switch[i].model, sc->pci_subvendor) && hdac_eapd_switch[i].id == id)) continue; w = hdac_widget_get(devinfo, hdac_eapd_switch[i].eapdnid); if (w == NULL || w->enable == 0) break; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDAC_INVALID) break; mask |= SOUND_MASK_OGAIN; break; } for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; mask |= w->ctlflags; if (!(w->pflags & HDA_ADC_RECSEL)) continue; for (j = 0; j < w->nconns; j++) { cw = hdac_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; recmask |= cw->ctlflags; } } if (!(mask & SOUND_MASK_PCM)) { softpcmvol = 1; mask |= SOUND_MASK_PCM; } else softpcmvol = (devinfo->function.audio.quirks & HDA_QUIRK_SOFTPCMVOL) ? 1 : 0; i = 0; ctl = NULL; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->widget == NULL || ctl->enable == 0) continue; if (!(ctl->ossmask & SOUND_MASK_PCM)) continue; if (ctl->step > 0) break; } if (softpcmvol == 1 || ctl == NULL) { pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: %s Soft PCM volume\n", (softpcmvol == 1) ? "Forcing" : "Enabling"); ); i = 0; /* * XXX Temporary quirk for STAC9220, until the parser * become smarter. */ if (id == HDA_CODEC_STAC9220) { mask |= SOUND_MASK_VOLUME; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->widget == NULL || ctl->enable == 0) continue; if (ctl->widget->nid == 11 && ctl->index == 0) { ctl->ossmask = SOUND_MASK_VOLUME; ctl->ossval = 100 | (100 << 8); } else ctl->ossmask &= ~SOUND_MASK_VOLUME; } } else if (id == HDA_CODEC_STAC9221) { mask |= SOUND_MASK_VOLUME; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->widget == NULL) continue; if (ctl->widget->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT && ctl->index == 0 && (ctl->widget->nid == 2 || ctl->widget->enable != 0)) { ctl->enable = 1; ctl->ossmask = SOUND_MASK_VOLUME; ctl->ossval = 100 | (100 << 8); } else if (ctl->enable == 0) continue; else ctl->ossmask &= ~SOUND_MASK_VOLUME; } } else { mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); if (!(mask & SOUND_MASK_VOLUME)) mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->widget == NULL || ctl->enable == 0) continue; if (!HDA_FLAG_MATCH(ctl->ossmask, SOUND_MASK_VOLUME | SOUND_MASK_PCM)) continue; if (!(ctl->mute == 1 && ctl->step == 0)) ctl->enable = 0; } } } recmask &= ~(SOUND_MASK_PCM | SOUND_MASK_RECLEV | SOUND_MASK_SPEAKER | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN); recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1; mask &= (1 << SOUND_MIXER_NRDEVICES) - 1; mix_setrecdevs(m, recmask); mix_setdevs(m, mask); hdac_unlock(sc); return (0); } static int hdac_audio_ctl_ossmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct hdac_devinfo *devinfo = mix_getdevinfo(m); struct hdac_softc *sc = devinfo->codec->sc; struct hdac_widget *w; struct hdac_audio_ctl *ctl; uint32_t id, mute; int lvol, rvol, mlvol, mrvol; int i = 0; hdac_lock(sc); if (dev == SOUND_MIXER_OGAIN) { uint32_t orig; /*if (left != right || !(left == 0 || left == 1)) { hdac_unlock(sc); return (-1); }*/ id = hdac_codec_id(devinfo); for (i = 0; i < HDAC_EAPD_SWITCH_LEN; i++) { if (HDA_DEV_MATCH(hdac_eapd_switch[i].model, sc->pci_subvendor) && hdac_eapd_switch[i].id == id) break; } if (i >= HDAC_EAPD_SWITCH_LEN) { hdac_unlock(sc); return (-1); } w = hdac_widget_get(devinfo, hdac_eapd_switch[i].eapdnid); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDAC_INVALID) { hdac_unlock(sc); return (-1); } orig = w->param.eapdbtl; if (left == 0) w->param.eapdbtl &= ~HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; else w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; if (orig != w->param.eapdbtl) { uint32_t val; if (hdac_eapd_switch[i].hp_switch != 0) hdac_hp_switch_handler(devinfo); val = w->param.eapdbtl; if (devinfo->function.audio.quirks & HDA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hdac_command(sc, HDA_CMD_SET_EAPD_BTL_ENABLE(devinfo->codec->cad, w->nid, val), devinfo->codec->cad); } hdac_unlock(sc); return (left | (left << 8)); } if (dev == SOUND_MIXER_VOLUME) devinfo->function.audio.mvol = left | (right << 8); mlvol = devinfo->function.audio.mvol & 0x7f; mrvol = (devinfo->function.audio.mvol >> 8) & 0x7f; lvol = 0; rvol = 0; i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->widget == NULL || ctl->enable == 0 || !(ctl->ossmask & (1 << dev))) continue; switch (dev) { case SOUND_MIXER_VOLUME: lvol = ((ctl->ossval & 0x7f) * left) / 100; lvol = (lvol * ctl->step) / 100; rvol = (((ctl->ossval >> 8) & 0x7f) * right) / 100; rvol = (rvol * ctl->step) / 100; break; default: if (ctl->ossmask & SOUND_MASK_VOLUME) { lvol = (left * mlvol) / 100; lvol = (lvol * ctl->step) / 100; rvol = (right * mrvol) / 100; rvol = (rvol * ctl->step) / 100; } else { lvol = (left * ctl->step) / 100; rvol = (right * ctl->step) / 100; } ctl->ossval = left | (right << 8); break; } mute = 0; if (ctl->step < 1) { mute |= (left == 0) ? HDA_AMP_MUTE_LEFT : (ctl->muted & HDA_AMP_MUTE_LEFT); mute |= (right == 0) ? HDA_AMP_MUTE_RIGHT : (ctl->muted & HDA_AMP_MUTE_RIGHT); } else { mute |= (lvol == 0) ? HDA_AMP_MUTE_LEFT : (ctl->muted & HDA_AMP_MUTE_LEFT); mute |= (rvol == 0) ? HDA_AMP_MUTE_RIGHT : (ctl->muted & HDA_AMP_MUTE_RIGHT); } hdac_audio_ctl_amp_set(ctl, mute, lvol, rvol); } hdac_unlock(sc); return (left | (right << 8)); } static int hdac_audio_ctl_ossmixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct hdac_devinfo *devinfo = mix_getdevinfo(m); struct hdac_widget *w, *cw; struct hdac_softc *sc = devinfo->codec->sc; uint32_t ret = src, target; int i, j; target = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (src & (1 << i)) { target = 1 << i; break; } } hdac_lock(sc); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (!(w->pflags & HDA_ADC_RECSEL)) continue; for (j = 0; j < w->nconns; j++) { cw = hdac_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if ((target == SOUND_MASK_VOLUME && cw->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || (target != SOUND_MASK_VOLUME && cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)) continue; if (cw->ctlflags & target) { if (!(w->pflags & HDA_ADC_LOCKED)) hdac_widget_connection_select(w, j); ret = target; j += w->nconns; } } } hdac_unlock(sc); return (ret); } static kobj_method_t hdac_audio_ctl_ossmixer_methods[] = { KOBJMETHOD(mixer_init, hdac_audio_ctl_ossmixer_init), KOBJMETHOD(mixer_set, hdac_audio_ctl_ossmixer_set), KOBJMETHOD(mixer_setrecsrc, hdac_audio_ctl_ossmixer_setrecsrc), { 0, 0 } }; MIXER_DECLARE(hdac_audio_ctl_ossmixer); /**************************************************************************** * int hdac_attach(device_t) * * Attach the device into the kernel. Interrupts usually won't be enabled * when this function is called. Setup everything that doesn't require * interrupts and defer probing of codecs until interrupts are enabled. ****************************************************************************/ static int hdac_attach(device_t dev) { struct hdac_softc *sc; int result; int i; uint16_t vendor; uint8_t v; sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENOMEM); } sc->lock = snd_mtxcreate(device_get_nameunit(dev), HDAC_MTX_NAME); sc->dev = dev; sc->pci_subvendor = (uint32_t)pci_get_subdevice(sc->dev) << 16; sc->pci_subvendor |= (uint32_t)pci_get_subvendor(sc->dev) & 0x0000ffff; vendor = pci_get_vendor(dev); if (sc->pci_subvendor == HP_NX6325_SUBVENDORX) { /* Screw nx6325 - subdevice/subvendor swapped */ sc->pci_subvendor = HP_NX6325_SUBVENDOR; } callout_init(&sc->poll_hda, CALLOUT_MPSAFE); callout_init(&sc->poll_hdac, CALLOUT_MPSAFE); callout_init(&sc->poll_jack, CALLOUT_MPSAFE); sc->poll_ticks = 1; sc->poll_ival = HDAC_POLL_INTERVAL; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) sc->polling = 1; else sc->polling = 0; sc->chan_size = pcm_getbuffersize(dev, HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= HDA_BLK_ALIGN; if (i < HDA_BLK_MIN) i = HDA_BLK_MIN; sc->chan_blkcnt = sc->chan_size / i; i = 0; while (sc->chan_blkcnt >> i) i++; sc->chan_blkcnt = 1 << (i - 1); if (sc->chan_blkcnt < HDA_BDL_MIN) sc->chan_blkcnt = HDA_BDL_MIN; else if (sc->chan_blkcnt > HDA_BDL_MAX) sc->chan_blkcnt = HDA_BDL_MAX; } else sc->chan_blkcnt = HDA_BDL_DEFAULT; result = bus_dma_tag_create(NULL, /* parent */ HDAC_DMA_ALIGNMENT, /* alignment */ 0, /* boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filtfunc */ NULL, /* fistfuncarg */ sc->chan_size, /* maxsize */ 1, /* nsegments */ sc->chan_size, /* maxsegsz */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &sc->chan_dmat); /* dmat */ if (result != 0) { device_printf(dev, "%s: bus_dma_tag_create failed (%x)\n", __func__, result); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } sc->hdabus = NULL; for (i = 0; i < HDAC_CODEC_MAX; i++) sc->codecs[i] = NULL; pci_enable_busmaster(dev); if (vendor == INTEL_VENDORID) { /* TCSEL -> TC0 */ v = pci_read_config(dev, 0x44, 1); pci_write_config(dev, 0x44, v & 0xf8, 1); HDA_BOOTVERBOSE( device_printf(dev, "TCSEL: 0x%02d -> 0x%02d\n", v, pci_read_config(dev, 0x44, 1)); ); } #if defined(__i386__) || defined(__amd64__) sc->nocache = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "snoop", &i) == 0 && i != 0) { #else sc->nocache = 0; #endif /* * Try to enable PCIe snoop to avoid messing around with * uncacheable DMA attribute. Since PCIe snoop register * config is pretty much vendor specific, there are no * general solutions on how to enable it, forcing us (even * Microsoft) to enable uncacheable or write combined DMA * by default. * * http://msdn2.microsoft.com/en-us/library/ms790324.aspx */ for (i = 0; i < HDAC_PCIESNOOP_LEN; i++) { if (hdac_pcie_snoop[i].vendor != vendor) continue; sc->nocache = 0; if (hdac_pcie_snoop[i].reg == 0x00) break; v = pci_read_config(dev, hdac_pcie_snoop[i].reg, 1); if ((v & hdac_pcie_snoop[i].enable) == hdac_pcie_snoop[i].enable) break; v &= hdac_pcie_snoop[i].mask; v |= hdac_pcie_snoop[i].enable; pci_write_config(dev, hdac_pcie_snoop[i].reg, v, 1); v = pci_read_config(dev, hdac_pcie_snoop[i].reg, 1); if ((v & hdac_pcie_snoop[i].enable) != hdac_pcie_snoop[i].enable) { HDA_BOOTVERBOSE( device_printf(dev, "WARNING: Failed to enable PCIe " "snoop!\n"); ); #if defined(__i386__) || defined(__amd64__) sc->nocache = 1; #endif } break; } #if defined(__i386__) || defined(__amd64__) } #endif HDA_BOOTVERBOSE( device_printf(dev, "DMA Coherency: %s / vendor=0x%04x\n", (sc->nocache == 0) ? "PCIe snoop" : "Uncacheable", vendor); ); /* Allocate resources */ result = hdac_mem_alloc(sc); if (result != 0) goto hdac_attach_fail; result = hdac_irq_alloc(sc); if (result != 0) goto hdac_attach_fail; /* Get Capabilities */ result = hdac_get_capabilities(sc); if (result != 0) goto hdac_attach_fail; /* Allocate CORB and RIRB dma memory */ result = hdac_dma_alloc(sc, &sc->corb_dma, sc->corb_size * sizeof(uint32_t)); if (result != 0) goto hdac_attach_fail; result = hdac_dma_alloc(sc, &sc->rirb_dma, sc->rirb_size * sizeof(struct hdac_rirb)); if (result != 0) goto hdac_attach_fail; /* Quiesce everything */ hdac_reset(sc); /* Initialize the CORB and RIRB */ hdac_corb_init(sc); hdac_rirb_init(sc); /* Defer remaining of initialization until interrupts are enabled */ sc->intrhook.ich_func = hdac_attach2; sc->intrhook.ich_arg = (void *)sc; if (cold == 0 || config_intrhook_establish(&sc->intrhook) != 0) { sc->intrhook.ich_func = NULL; hdac_attach2((void *)sc); } return (0); hdac_attach_fail: hdac_irq_free(sc); hdac_dma_free(sc, &sc->rirb_dma); hdac_dma_free(sc, &sc->corb_dma); hdac_mem_free(sc); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static void hdac_audio_parse(struct hdac_devinfo *devinfo) { struct hdac_softc *sc = devinfo->codec->sc; struct hdac_widget *w; uint32_t res; int i; nid_t cad, nid; cad = devinfo->codec->cad; nid = devinfo->nid; hdac_command(sc, HDA_CMD_SET_POWER_STATE(cad, nid, HDA_CMD_POWER_STATE_D0), cad); DELAY(100); res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad , nid, HDA_PARAM_SUB_NODE_COUNT), cad); devinfo->nodecnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(res); devinfo->startnode = HDA_PARAM_SUB_NODE_COUNT_START(res); devinfo->endnode = devinfo->startnode + devinfo->nodecnt; res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad , nid, HDA_PARAM_GPIO_COUNT), cad); devinfo->function.audio.gpio = res; HDA_BOOTVERBOSE( device_printf(sc->dev, " Vendor: 0x%08x\n", devinfo->vendor_id); device_printf(sc->dev, " Device: 0x%08x\n", devinfo->device_id); device_printf(sc->dev, " Revision: 0x%08x\n", devinfo->revision_id); device_printf(sc->dev, " Stepping: 0x%08x\n", devinfo->stepping_id); device_printf(sc->dev, "PCI Subvendor: 0x%08x\n", sc->pci_subvendor); device_printf(sc->dev, " Nodes: start=%d " "endnode=%d total=%d\n", devinfo->startnode, devinfo->endnode, devinfo->nodecnt); device_printf(sc->dev, " CORB size: %d\n", sc->corb_size); device_printf(sc->dev, " RIRB size: %d\n", sc->rirb_size); device_printf(sc->dev, " Streams: ISS=%d OSS=%d BSS=%d\n", sc->num_iss, sc->num_oss, sc->num_bss); device_printf(sc->dev, " GPIO: 0x%08x\n", devinfo->function.audio.gpio); device_printf(sc->dev, " NumGPIO=%d NumGPO=%d " "NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->function.audio.gpio)); ); res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_SUPP_STREAM_FORMATS), cad); devinfo->function.audio.supp_stream_formats = res; res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE), cad); devinfo->function.audio.supp_pcm_size_rate = res; res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_OUTPUT_AMP_CAP), cad); devinfo->function.audio.outamp_cap = res; res = hdac_command(sc, HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_INPUT_AMP_CAP), cad); devinfo->function.audio.inamp_cap = res; if (devinfo->nodecnt > 0) devinfo->widget = (struct hdac_widget *)malloc( sizeof(*(devinfo->widget)) * devinfo->nodecnt, M_HDAC, M_NOWAIT | M_ZERO); else devinfo->widget = NULL; if (devinfo->widget == NULL) { device_printf(sc->dev, "unable to allocate widgets!\n"); devinfo->endnode = devinfo->startnode; devinfo->nodecnt = 0; return; } for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL) device_printf(sc->dev, "Ghost widget! nid=%d!\n", i); else { w->devinfo = devinfo; w->nid = i; w->enable = 1; w->selconn = -1; w->pflags = 0; w->ctlflags = 0; w->param.eapdbtl = HDAC_INVALID; hdac_widget_parse(w); } } } static void hdac_audio_ctl_parse(struct hdac_devinfo *devinfo) { struct hdac_softc *sc = devinfo->codec->sc; struct hdac_audio_ctl *ctls; struct hdac_widget *w, *cw; int i, j, cnt, max, ocap, icap; int mute, offset, step, size; /* XXX This is redundant */ max = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->param.outamp_cap != 0) max++; if (w->param.inamp_cap != 0) { switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { cw = hdac_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; max++; } break; default: max++; break; } } } devinfo->function.audio.ctlcnt = max; if (max < 1) return; ctls = (struct hdac_audio_ctl *)malloc( sizeof(*ctls) * max, M_HDAC, M_ZERO | M_NOWAIT); if (ctls == NULL) { /* Blekh! */ device_printf(sc->dev, "unable to allocate ctls!\n"); devinfo->function.audio.ctlcnt = 0; return; } cnt = 0; for (i = devinfo->startnode; cnt < max && i < devinfo->endnode; i++) { if (cnt >= max) { device_printf(sc->dev, "%s: Ctl overflow!\n", __func__); break; } w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; ocap = w->param.outamp_cap; icap = w->param.inamp_cap; if (ocap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(ocap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(ocap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(ocap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(ocap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: BUGGY outamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; ctls[cnt++].dir = HDA_CTL_OUT; } if (icap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(icap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(icap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(icap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(icap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: BUGGY inamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { if (cnt >= max) { device_printf(sc->dev, "%s: Ctl overflow!\n", __func__); break; } cw = hdac_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].childwidget = cw; ctls[cnt].index = j; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; ctls[cnt++].dir = HDA_CTL_IN; } break; default: if (cnt >= max) { device_printf(sc->dev, "%s: Ctl overflow!\n", __func__); break; } ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; ctls[cnt++].dir = HDA_CTL_IN; break; } } } devinfo->function.audio.ctl = ctls; } static const struct { uint32_t model; uint32_t id; uint32_t set, unset; } hdac_quirks[] = { /* * XXX Force stereo quirk. Monoural recording / playback * on few codecs (especially ALC880) seems broken or * perhaps unsupported. */ { HDA_MATCH_ALL, HDA_MATCH_ALL, HDA_QUIRK_FORCESTEREO | HDA_QUIRK_IVREF, 0 }, { ACER_ALL_SUBVENDOR, HDA_MATCH_ALL, HDA_QUIRK_GPIO0, 0 }, { ASUS_M5200_SUBVENDOR, HDA_CODEC_ALC880, HDA_QUIRK_GPIO0, 0 }, { ASUS_A7M_SUBVENDOR, HDA_CODEC_ALC880, HDA_QUIRK_GPIO0, 0 }, { ASUS_A7T_SUBVENDOR, HDA_CODEC_ALC882, HDA_QUIRK_GPIO0, 0 }, { ASUS_W2J_SUBVENDOR, HDA_CODEC_ALC882, HDA_QUIRK_GPIO0, 0 }, { ASUS_U5F_SUBVENDOR, HDA_CODEC_AD1986A, HDA_QUIRK_EAPDINV, 0 }, { ASUS_A8JC_SUBVENDOR, HDA_CODEC_AD1986A, HDA_QUIRK_EAPDINV, 0 }, { ASUS_F3JC_SUBVENDOR, HDA_CODEC_ALC861, HDA_QUIRK_OVREF, 0 }, { ASUS_W6F_SUBVENDOR, HDA_CODEC_ALC861, HDA_QUIRK_OVREF, 0 }, { UNIWILL_9075_SUBVENDOR, HDA_CODEC_ALC861, HDA_QUIRK_OVREF, 0 }, /*{ ASUS_M2N_SUBVENDOR, HDA_CODEC_AD1988, HDA_QUIRK_IVREF80, HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF100 },*/ { MEDION_MD95257_SUBVENDOR, HDA_CODEC_ALC880, HDA_QUIRK_GPIO1, 0 }, { LENOVO_3KN100_SUBVENDOR, HDA_CODEC_AD1986A, HDA_QUIRK_EAPDINV, 0 }, { SAMSUNG_Q1_SUBVENDOR, HDA_CODEC_AD1986A, HDA_QUIRK_EAPDINV, 0 }, { APPLE_INTEL_MAC, HDA_CODEC_STAC9221, HDA_QUIRK_GPIO0 | HDA_QUIRK_GPIO1, 0 }, { HDA_MATCH_ALL, HDA_CODEC_AD1988, HDA_QUIRK_IVREF80, HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF100 }, { HDA_MATCH_ALL, HDA_CODEC_AD1988B, HDA_QUIRK_IVREF80, HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF100 }, { HDA_MATCH_ALL, HDA_CODEC_CXVENICE, 0, HDA_QUIRK_FORCESTEREO }, { HDA_MATCH_ALL, HDA_CODEC_STACXXXX, HDA_QUIRK_SOFTPCMVOL, 0 } }; #define HDAC_QUIRKS_LEN (sizeof(hdac_quirks) / sizeof(hdac_quirks[0])) static void hdac_vendor_patch_parse(struct hdac_devinfo *devinfo) { struct hdac_widget *w; struct hdac_audio_ctl *ctl; uint32_t id, subvendor; int i; id = hdac_codec_id(devinfo); subvendor = devinfo->codec->sc->pci_subvendor; /* * Quirks */ for (i = 0; i < HDAC_QUIRKS_LEN; i++) { if (!(HDA_DEV_MATCH(hdac_quirks[i].model, subvendor) && HDA_DEV_MATCH(hdac_quirks[i].id, id))) continue; if (hdac_quirks[i].set != 0) devinfo->function.audio.quirks |= hdac_quirks[i].set; if (hdac_quirks[i].unset != 0) devinfo->function.audio.quirks &= ~(hdac_quirks[i].unset); } switch (id) { case HDA_CODEC_ALC260: for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->nid != 5) w->enable = 0; } if (subvendor == HP_XW4300_SUBVENDOR) { ctl = hdac_audio_ctl_amp_get(devinfo, 16, 0, 1); if (ctl != NULL && ctl->widget != NULL) { ctl->ossmask = SOUND_MASK_SPEAKER; ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; } ctl = hdac_audio_ctl_amp_get(devinfo, 17, 0, 1); if (ctl != NULL && ctl->widget != NULL) { ctl->ossmask = SOUND_MASK_SPEAKER; ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; } } else if (subvendor == HP_3010_SUBVENDOR) { ctl = hdac_audio_ctl_amp_get(devinfo, 17, 0, 1); if (ctl != NULL && ctl->widget != NULL) { ctl->ossmask = SOUND_MASK_SPEAKER; ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; } ctl = hdac_audio_ctl_amp_get(devinfo, 21, 0, 1); if (ctl != NULL && ctl->widget != NULL) { ctl->ossmask = SOUND_MASK_SPEAKER; ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; } } break; case HDA_CODEC_ALC861: ctl = hdac_audio_ctl_amp_get(devinfo, 21, 2, 1); if (ctl != NULL) ctl->muted = HDA_AMP_MUTE_ALL; break; case HDA_CODEC_ALC880: for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT && w->nid != 9 && w->nid != 29) { w->enable = 0; } else if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET && w->nid == 29) { w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET; w->param.widget_cap &= ~HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_MASK; w->param.widget_cap |= HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET << HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT; strlcpy(w->name, "beep widget", sizeof(w->name)); } } break; case HDA_CODEC_ALC883: /* * nid: 24/25 = External (jack) or Internal (fixed) Mic. * Clear vref cap for jack connectivity. */ w = hdac_widget_get(devinfo, 24); if (w != NULL && w->enable != 0 && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) w->wclass.pin.cap &= ~( HDA_PARAM_PIN_CAP_VREF_CTRL_100_MASK | HDA_PARAM_PIN_CAP_VREF_CTRL_80_MASK | HDA_PARAM_PIN_CAP_VREF_CTRL_50_MASK); w = hdac_widget_get(devinfo, 25); if (w != NULL && w->enable != 0 && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) w->wclass.pin.cap &= ~( HDA_PARAM_PIN_CAP_VREF_CTRL_100_MASK | HDA_PARAM_PIN_CAP_VREF_CTRL_80_MASK | HDA_PARAM_PIN_CAP_VREF_CTRL_50_MASK); /* * nid: 26 = Line-in, leave it alone. */ break; case HDA_CODEC_AD1981HD: w = hdac_widget_get(devinfo, 11); if (w != NULL && w->enable != 0 && w->nconns > 3) w->selconn = 3; if (subvendor == IBM_M52_SUBVENDOR) { ctl = hdac_audio_ctl_amp_get(devinfo, 7, 0, 1); if (ctl != NULL) ctl->ossmask = SOUND_MASK_SPEAKER; } break; case HDA_CODEC_AD1986A: for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) continue; if (w->nid != 3) w->enable = 0; } if (subvendor == ASUS_M2NPVMX_SUBVENDOR) { /* nid 28 is mic, nid 29 is line-in */ w = hdac_widget_get(devinfo, 15); if (w != NULL) w->selconn = 2; w = hdac_widget_get(devinfo, 16); if (w != NULL) w->selconn = 1; } break; case HDA_CODEC_AD1988: case HDA_CODEC_AD1988B: /*w = hdac_widget_get(devinfo, 12); if (w != NULL) { w->selconn = 1; w->pflags |= HDA_ADC_LOCKED; } w = hdac_widget_get(devinfo, 13); if (w != NULL) { w->selconn = 4; w->pflags |= HDA_ADC_LOCKED; } w = hdac_widget_get(devinfo, 14); if (w != NULL) { w->selconn = 2; w->pflags |= HDA_ADC_LOCKED; }*/ ctl = hdac_audio_ctl_amp_get(devinfo, 57, 0, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_IGAIN; ctl->widget->ctlflags |= SOUND_MASK_IGAIN; } ctl = hdac_audio_ctl_amp_get(devinfo, 58, 0, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_IGAIN; ctl->widget->ctlflags |= SOUND_MASK_IGAIN; } ctl = hdac_audio_ctl_amp_get(devinfo, 60, 0, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_IGAIN; ctl->widget->ctlflags |= SOUND_MASK_IGAIN; } ctl = hdac_audio_ctl_amp_get(devinfo, 32, 0, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_MIC | SOUND_MASK_VOLUME; ctl->widget->ctlflags |= SOUND_MASK_MIC; } ctl = hdac_audio_ctl_amp_get(devinfo, 32, 4, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_MIC | SOUND_MASK_VOLUME; ctl->widget->ctlflags |= SOUND_MASK_MIC; } ctl = hdac_audio_ctl_amp_get(devinfo, 32, 1, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_LINE | SOUND_MASK_VOLUME; ctl->widget->ctlflags |= SOUND_MASK_LINE; } ctl = hdac_audio_ctl_amp_get(devinfo, 32, 7, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_SPEAKER | SOUND_MASK_VOLUME; ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; } break; case HDA_CODEC_STAC9221: /* * Dell XPS M1210 need all DACs for each output jacks */ if (subvendor == DELL_XPSM1210_SUBVENDOR) break; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) continue; if (w->nid != 2) w->enable = 0; } break; case HDA_CODEC_STAC9221D: for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT && w->nid != 6) w->enable = 0; } break; case HDA_CODEC_STAC9227: w = hdac_widget_get(devinfo, 8); if (w != NULL) w->enable = 0; w = hdac_widget_get(devinfo, 9); if (w != NULL) w->enable = 0; break; case HDA_CODEC_CXWAIKIKI: if (subvendor == HP_DV5000_SUBVENDOR) { w = hdac_widget_get(devinfo, 27); if (w != NULL) w->enable = 0; } ctl = hdac_audio_ctl_amp_get(devinfo, 16, 0, 1); if (ctl != NULL) ctl->ossmask = SOUND_MASK_SKIP; ctl = hdac_audio_ctl_amp_get(devinfo, 25, 0, 1); if (ctl != NULL && ctl->childwidget != NULL && ctl->childwidget->enable != 0) { ctl->ossmask = SOUND_MASK_PCM | SOUND_MASK_VOLUME; ctl->childwidget->ctlflags |= SOUND_MASK_PCM; } ctl = hdac_audio_ctl_amp_get(devinfo, 25, 1, 1); if (ctl != NULL && ctl->childwidget != NULL && ctl->childwidget->enable != 0) { ctl->ossmask = SOUND_MASK_LINE | SOUND_MASK_VOLUME; ctl->childwidget->ctlflags |= SOUND_MASK_LINE; } ctl = hdac_audio_ctl_amp_get(devinfo, 25, 2, 1); if (ctl != NULL && ctl->childwidget != NULL && ctl->childwidget->enable != 0) { ctl->ossmask = SOUND_MASK_MIC | SOUND_MASK_VOLUME; ctl->childwidget->ctlflags |= SOUND_MASK_MIC; } ctl = hdac_audio_ctl_amp_get(devinfo, 26, 0, 1); if (ctl != NULL) { ctl->ossmask = SOUND_MASK_SKIP; /* XXX mixer \=rec mic broken.. why?!? */ /* ctl->widget->ctlflags |= SOUND_MASK_MIC; */ } break; default: break; } } static int hdac_audio_ctl_ossmixer_getnextdev(struct hdac_devinfo *devinfo) { int *dev = &devinfo->function.audio.ossidx; while (*dev < SOUND_MIXER_NRDEVICES) { switch (*dev) { case SOUND_MIXER_VOLUME: case SOUND_MIXER_BASS: case SOUND_MIXER_TREBLE: case SOUND_MIXER_PCM: case SOUND_MIXER_SPEAKER: case SOUND_MIXER_LINE: case SOUND_MIXER_MIC: case SOUND_MIXER_CD: case SOUND_MIXER_RECLEV: case SOUND_MIXER_IGAIN: case SOUND_MIXER_OGAIN: /* reserved for EAPD switch */ (*dev)++; break; default: return (*dev)++; break; } } return (-1); } static int hdac_widget_find_dac_path(struct hdac_devinfo *devinfo, nid_t nid, int depth) { struct hdac_widget *w; int i, ret = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdac_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: w->pflags |= HDA_DAC_PATH; ret = 1; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: for (i = 0; i < w->nconns; i++) { if (hdac_widget_find_dac_path(devinfo, w->conns[i], depth + 1) != 0) { if (w->selconn == -1) w->selconn = i; ret = 1; w->pflags |= HDA_DAC_PATH; } } break; default: break; } return (ret); } static int hdac_widget_find_adc_path(struct hdac_devinfo *devinfo, nid_t nid, int depth) { struct hdac_widget *w; int i, conndev, ret = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdac_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (i = 0; i < w->nconns; i++) { if (hdac_widget_find_adc_path(devinfo, w->conns[i], depth + 1) != 0) { if (w->selconn == -1) w->selconn = i; w->pflags |= HDA_ADC_PATH; ret = 1; } } break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: conndev = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; if (HDA_PARAM_PIN_CAP_INPUT_CAP(w->wclass.pin.cap) && (conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_CD || conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN || conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN)) { w->pflags |= HDA_ADC_PATH; ret = 1; } break; /*case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: if (w->pflags & HDA_DAC_PATH) { w->pflags |= HDA_ADC_PATH; ret = 1; } break;*/ default: break; } return (ret); } static uint32_t hdac_audio_ctl_outamp_build(struct hdac_devinfo *devinfo, nid_t nid, nid_t pnid, int index, int depth) { struct hdac_widget *w, *pw; struct hdac_audio_ctl *ctl; uint32_t fl = 0; int i, ossdev, conndev, strategy; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdac_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); pw = hdac_widget_get(devinfo, pnid); strategy = devinfo->function.audio.parsing_strategy; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR) { for (i = 0; i < w->nconns; i++) { fl |= hdac_audio_ctl_outamp_build(devinfo, w->conns[i], w->nid, i, depth + 1); } w->ctlflags |= fl; return (fl); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT && (w->pflags & HDA_DAC_PATH)) { i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) continue; /* XXX This should be compressed! */ if (((ctl->widget->nid == w->nid) || (ctl->widget->nid == pnid && ctl->index == index && (ctl->dir & HDA_CTL_IN)) || (ctl->widget->nid == pnid && pw != NULL && pw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && (pw->nconns < 2 || pw->selconn == index || pw->selconn == -1) && (ctl->dir & HDA_CTL_OUT)) || (strategy == HDA_PARSE_DIRECT && ctl->widget->nid == w->nid)) && !(ctl->ossmask & ~SOUND_MASK_VOLUME)) { /*if (pw != NULL && pw->selconn == -1) pw->selconn = index; fl |= SOUND_MASK_VOLUME; fl |= SOUND_MASK_PCM; ctl->ossmask |= SOUND_MASK_VOLUME; ctl->ossmask |= SOUND_MASK_PCM; ctl->ossdev = SOUND_MIXER_PCM;*/ if (!(w->ctlflags & SOUND_MASK_PCM) || (pw != NULL && !(pw->ctlflags & SOUND_MASK_PCM))) { fl |= SOUND_MASK_VOLUME; fl |= SOUND_MASK_PCM; ctl->ossmask |= SOUND_MASK_VOLUME; ctl->ossmask |= SOUND_MASK_PCM; ctl->ossdev = SOUND_MIXER_PCM; w->ctlflags |= SOUND_MASK_VOLUME; w->ctlflags |= SOUND_MASK_PCM; if (pw != NULL) { if (pw->selconn == -1) pw->selconn = index; pw->ctlflags |= SOUND_MASK_VOLUME; pw->ctlflags |= SOUND_MASK_PCM; } } } } w->ctlflags |= fl; return (fl); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && HDA_PARAM_PIN_CAP_INPUT_CAP(w->wclass.pin.cap) && (w->pflags & HDA_ADC_PATH)) { conndev = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) continue; /* XXX This should be compressed! */ if (((ctl->widget->nid == pnid && ctl->index == index && (ctl->dir & HDA_CTL_IN)) || (ctl->widget->nid == pnid && pw != NULL && pw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && (pw->nconns < 2 || pw->selconn == index || pw->selconn == -1) && (ctl->dir & HDA_CTL_OUT)) || (strategy == HDA_PARSE_DIRECT && ctl->widget->nid == w->nid)) && !(ctl->ossmask & ~SOUND_MASK_VOLUME)) { if (pw != NULL && pw->selconn == -1) pw->selconn = index; ossdev = 0; switch (conndev) { case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: ossdev = SOUND_MIXER_MIC; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: ossdev = SOUND_MIXER_LINE; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: ossdev = SOUND_MIXER_CD; break; default: ossdev = hdac_audio_ctl_ossmixer_getnextdev( devinfo); if (ossdev < 0) ossdev = 0; break; } if (strategy == HDA_PARSE_MIXER) { fl |= SOUND_MASK_VOLUME; ctl->ossmask |= SOUND_MASK_VOLUME; } fl |= 1 << ossdev; ctl->ossmask |= 1 << ossdev; ctl->ossdev = ossdev; } } w->ctlflags |= fl; return (fl); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) { i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) continue; /* XXX This should be compressed! */ if (((ctl->widget->nid == pnid && ctl->index == index && (ctl->dir & HDA_CTL_IN)) || (ctl->widget->nid == pnid && pw != NULL && pw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && (pw->nconns < 2 || pw->selconn == index || pw->selconn == -1) && (ctl->dir & HDA_CTL_OUT)) || (strategy == HDA_PARSE_DIRECT && ctl->widget->nid == w->nid)) && !(ctl->ossmask & ~SOUND_MASK_VOLUME)) { if (pw != NULL && pw->selconn == -1) pw->selconn = index; fl |= SOUND_MASK_VOLUME; fl |= SOUND_MASK_SPEAKER; ctl->ossmask |= SOUND_MASK_VOLUME; ctl->ossmask |= SOUND_MASK_SPEAKER; ctl->ossdev = SOUND_MIXER_SPEAKER; } } w->ctlflags |= fl; return (fl); } return (0); } static uint32_t hdac_audio_ctl_inamp_build(struct hdac_devinfo *devinfo, nid_t nid, int depth) { struct hdac_widget *w, *cw; struct hdac_audio_ctl *ctl; uint32_t fl; int i; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdac_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); /*if (!(w->pflags & HDA_ADC_PATH)) return (0); if (!(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) return (0);*/ i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) continue; if (ctl->widget->nid == nid) { ctl->ossmask |= SOUND_MASK_RECLEV; w->ctlflags |= SOUND_MASK_RECLEV; return (SOUND_MASK_RECLEV); } } for (i = 0; i < w->nconns; i++) { cw = hdac_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0) continue; if (cw->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR) continue; fl = hdac_audio_ctl_inamp_build(devinfo, cw->nid, depth + 1); if (fl != 0) { cw->ctlflags |= fl; w->ctlflags |= fl; return (fl); } } return (0); } static int hdac_audio_ctl_recsel_build(struct hdac_devinfo *devinfo, nid_t nid, int depth) { struct hdac_widget *w, *cw; int i, child = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdac_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); /*if (!(w->pflags & HDA_ADC_PATH)) return (0); if (!(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) return (0);*/ /* XXX weak! */ for (i = 0; i < w->nconns; i++) { cw = hdac_widget_get(devinfo, w->conns[i]); if (cw == NULL) continue; if (++child > 1) { w->pflags |= HDA_ADC_RECSEL; return (1); } } for (i = 0; i < w->nconns; i++) { if (hdac_audio_ctl_recsel_build(devinfo, w->conns[i], depth + 1) != 0) return (1); } return (0); } static int hdac_audio_build_tree_strategy(struct hdac_devinfo *devinfo) { struct hdac_widget *w, *cw; int i, j, conndev, found_dac = 0; int strategy; strategy = devinfo->function.audio.parsing_strategy; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (!HDA_PARAM_PIN_CAP_OUTPUT_CAP(w->wclass.pin.cap)) continue; conndev = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; if (!(conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT || conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER || conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT)) continue; for (j = 0; j < w->nconns; j++) { cw = hdac_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (strategy == HDA_PARSE_MIXER && !(cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) continue; if (hdac_widget_find_dac_path(devinfo, cw->nid, 0) != 0) { if (w->selconn == -1) w->selconn = j; w->pflags |= HDA_DAC_PATH; found_dac++; } } } return (found_dac); } static void hdac_audio_build_tree(struct hdac_devinfo *devinfo) { struct hdac_widget *w; struct hdac_audio_ctl *ctl; int i, j, dacs, strategy; /* Construct DAC path */ strategy = HDA_PARSE_MIXER; devinfo->function.audio.parsing_strategy = strategy; HDA_BOOTVERBOSE( device_printf(devinfo->codec->sc->dev, "HDA_DEBUG: HWiP: HDA Widget Parser - Revision %d\n", HDA_WIDGET_PARSER_REV); ); dacs = hdac_audio_build_tree_strategy(devinfo); if (dacs == 0) { HDA_BOOTVERBOSE( device_printf(devinfo->codec->sc->dev, "HDA_DEBUG: HWiP: 0 DAC path found! " "Retrying parser " "using HDA_PARSE_DIRECT strategy.\n"); ); strategy = HDA_PARSE_DIRECT; devinfo->function.audio.parsing_strategy = strategy; dacs = hdac_audio_build_tree_strategy(devinfo); } HDA_BOOTVERBOSE( device_printf(devinfo->codec->sc->dev, "HDA_DEBUG: HWiP: Found %d DAC path using HDA_PARSE_%s " "strategy.\n", dacs, (strategy == HDA_PARSE_MIXER) ? "MIXER" : "DIRECT"); ); /* Construct ADC path */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; (void)hdac_widget_find_adc_path(devinfo, w->nid, 0); } /* Output mixers */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if ((strategy == HDA_PARSE_MIXER && (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR) && (w->pflags & HDA_DAC_PATH)) || (strategy == HDA_PARSE_DIRECT && (w->pflags & (HDA_DAC_PATH | HDA_ADC_PATH)))) { w->ctlflags |= hdac_audio_ctl_outamp_build(devinfo, w->nid, devinfo->startnode - 1, 0, 0); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) { j = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &j)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) continue; if (ctl->widget->nid != w->nid) continue; ctl->ossmask |= SOUND_MASK_VOLUME; ctl->ossmask |= SOUND_MASK_SPEAKER; ctl->ossdev = SOUND_MIXER_SPEAKER; w->ctlflags |= SOUND_MASK_VOLUME; w->ctlflags |= SOUND_MASK_SPEAKER; } } } /* Input mixers (rec) */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (!(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT && w->pflags & HDA_ADC_PATH)) continue; hdac_audio_ctl_inamp_build(devinfo, w->nid, 0); hdac_audio_ctl_recsel_build(devinfo, w->nid, 0); } } #define HDA_COMMIT_CONN (1 << 0) #define HDA_COMMIT_CTRL (1 << 1) #define HDA_COMMIT_EAPD (1 << 2) #define HDA_COMMIT_GPIO (1 << 3) #define HDA_COMMIT_MISC (1 << 4) #define HDA_COMMIT_ALL (HDA_COMMIT_CONN | HDA_COMMIT_CTRL | \ HDA_COMMIT_EAPD | HDA_COMMIT_GPIO | HDA_COMMIT_MISC) static void hdac_audio_commit(struct hdac_devinfo *devinfo, uint32_t cfl) { struct hdac_softc *sc = devinfo->codec->sc; struct hdac_widget *w; nid_t cad; int i; if (!(cfl & HDA_COMMIT_ALL)) return; cad = devinfo->codec->cad; if ((cfl & HDA_COMMIT_MISC)) { if (sc->pci_subvendor == APPLE_INTEL_MAC) hdac_command(sc, HDA_CMD_12BIT(cad, devinfo->nid, 0x7e7, 0), cad); } if (cfl & HDA_COMMIT_GPIO) { uint32_t gdata, gmask, gdir; int commitgpio, numgpio; gdata = 0; gmask = 0; gdir = 0; commitgpio = 0; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO( devinfo->function.audio.gpio); if (devinfo->function.audio.quirks & HDA_QUIRK_GPIOFLUSH) commitgpio = (numgpio > 0) ? 1 : 0; else { for (i = 0; i < numgpio && i < HDA_GPIO_MAX; i++) { if (!(devinfo->function.audio.quirks & (1 << i))) continue; if (commitgpio == 0) { commitgpio = 1; HDA_BOOTVERBOSE( gdata = hdac_command(sc, HDA_CMD_GET_GPIO_DATA(cad, devinfo->nid), cad); gmask = hdac_command(sc, HDA_CMD_GET_GPIO_ENABLE_MASK(cad, devinfo->nid), cad); gdir = hdac_command(sc, HDA_CMD_GET_GPIO_DIRECTION(cad, devinfo->nid), cad); device_printf(sc->dev, "GPIO init: data=0x%08x " "mask=0x%08x dir=0x%08x\n", gdata, gmask, gdir); gdata = 0; gmask = 0; gdir = 0; ); } gdata |= 1 << i; gmask |= 1 << i; gdir |= 1 << i; } } if (commitgpio != 0) { HDA_BOOTVERBOSE( device_printf(sc->dev, "GPIO commit: data=0x%08x mask=0x%08x " "dir=0x%08x\n", gdata, gmask, gdir); ); hdac_command(sc, HDA_CMD_SET_GPIO_ENABLE_MASK(cad, devinfo->nid, gmask), cad); hdac_command(sc, HDA_CMD_SET_GPIO_DIRECTION(cad, devinfo->nid, gdir), cad); hdac_command(sc, HDA_CMD_SET_GPIO_DATA(cad, devinfo->nid, gdata), cad); } } for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL || w->enable == 0) continue; if (cfl & HDA_COMMIT_CONN) { if (w->selconn == -1) w->selconn = 0; if (w->nconns > 0) hdac_widget_connection_select(w, w->selconn); } if ((cfl & HDA_COMMIT_CTRL) && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { uint32_t pincap; pincap = w->wclass.pin.cap; if ((w->pflags & (HDA_DAC_PATH | HDA_ADC_PATH)) == (HDA_DAC_PATH | HDA_ADC_PATH)) device_printf(sc->dev, "WARNING: node %d " "participate both for DAC/ADC!\n", w->nid); if (w->pflags & HDA_DAC_PATH) { w->wclass.pin.ctrl &= ~HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) != HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT) w->wclass.pin.ctrl &= ~HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; if ((devinfo->function.audio.quirks & HDA_QUIRK_OVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->function.audio.quirks & HDA_QUIRK_OVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->function.audio.quirks & HDA_QUIRK_OVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } else if (w->pflags & HDA_ADC_PATH) { w->wclass.pin.ctrl &= ~(HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE); if ((devinfo->function.audio.quirks & HDA_QUIRK_IVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->function.audio.quirks & HDA_QUIRK_IVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->function.audio.quirks & HDA_QUIRK_IVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } else w->wclass.pin.ctrl &= ~( HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL(cad, w->nid, w->wclass.pin.ctrl), cad); } if ((cfl & HDA_COMMIT_EAPD) && w->param.eapdbtl != HDAC_INVALID) { uint32_t val; val = w->param.eapdbtl; if (devinfo->function.audio.quirks & HDA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hdac_command(sc, HDA_CMD_SET_EAPD_BTL_ENABLE(cad, w->nid, val), cad); } DELAY(1000); } } static void hdac_audio_ctl_commit(struct hdac_devinfo *devinfo) { struct hdac_softc *sc = devinfo->codec->sc; struct hdac_audio_ctl *ctl; int i; devinfo->function.audio.mvol = 100 | (100 << 8); i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL) { HDA_BOOTVERBOSE( device_printf(sc->dev, "[%2d] Ctl nid=%d", i, (ctl->widget != NULL) ? ctl->widget->nid : -1); if (ctl->childwidget != NULL) printf(" childnid=%d", ctl->childwidget->nid); if (ctl->widget == NULL) printf(" NULL WIDGET!"); printf(" DISABLED\n"); ); continue; } HDA_BOOTVERBOSE( if (ctl->ossmask == 0) { device_printf(sc->dev, "[%2d] Ctl nid=%d", i, ctl->widget->nid); if (ctl->childwidget != NULL) printf(" childnid=%d", ctl->childwidget->nid); printf(" Bind to NONE\n"); } ); if (ctl->step > 0) { ctl->ossval = (ctl->left * 100) / ctl->step; ctl->ossval |= ((ctl->right * 100) / ctl->step) << 8; } else ctl->ossval = 0; hdac_audio_ctl_amp_set(ctl, HDA_AMP_MUTE_DEFAULT, ctl->left, ctl->right); } } static int hdac_pcmchannel_setup(struct hdac_devinfo *devinfo, int dir) { struct hdac_chan *ch; struct hdac_widget *w; uint32_t cap, fmtcap, pcmcap, path; int i, type, ret, max; if (dir == PCMDIR_PLAY) { type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT; ch = &devinfo->codec->sc->play; path = HDA_DAC_PATH; } else { type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT; ch = &devinfo->codec->sc->rec; path = HDA_ADC_PATH; } ch->caps = hdac_caps; ch->caps.fmtlist = ch->fmtlist; ch->bit16 = 1; ch->bit32 = 0; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; ret = 0; fmtcap = devinfo->function.audio.supp_stream_formats; pcmcap = devinfo->function.audio.supp_pcm_size_rate; max = (sizeof(ch->io) / sizeof(ch->io[0])) - 1; for (i = devinfo->startnode; i < devinfo->endnode && ret < max; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != type || !(w->pflags & path)) continue; cap = w->param.widget_cap; /*if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(cap)) continue;*/ if (!HDA_PARAM_AUDIO_WIDGET_CAP_STEREO(cap)) continue; cap = w->param.supp_stream_formats; /*if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) { } if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) { }*/ if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) continue; if (ret == 0) { fmtcap = w->param.supp_stream_formats; pcmcap = w->param.supp_pcm_size_rate; } else { fmtcap &= w->param.supp_stream_formats; pcmcap &= w->param.supp_pcm_size_rate; } ch->io[ret++] = i; } ch->io[ret] = -1; ch->supp_stream_formats = fmtcap; ch->supp_pcm_size_rate = pcmcap; /* * 8bit = 0 * 16bit = 1 * 20bit = 2 * 24bit = 3 * 32bit = 4 */ if (ret > 0) { cap = pcmcap; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) ch->bit16 = 1; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) ch->bit16 = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) ch->bit32 = 4; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) ch->bit32 = 3; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) ch->bit32 = 2; i = 0; if (!(devinfo->function.audio.quirks & HDA_QUIRK_FORCESTEREO)) ch->fmtlist[i++] = AFMT_S16_LE; ch->fmtlist[i++] = AFMT_S16_LE | AFMT_STEREO; if (ch->bit32 > 0) { if (!(devinfo->function.audio.quirks & HDA_QUIRK_FORCESTEREO)) ch->fmtlist[i++] = AFMT_S32_LE; ch->fmtlist[i++] = AFMT_S32_LE | AFMT_STEREO; } ch->fmtlist[i] = 0; i = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) ch->pcmrates[i++] = 8000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) ch->pcmrates[i++] = 11025; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) ch->pcmrates[i++] = 16000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) ch->pcmrates[i++] = 22050; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) ch->pcmrates[i++] = 32000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) ch->pcmrates[i++] = 44100; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(cap)) */ ch->pcmrates[i++] = 48000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) ch->pcmrates[i++] = 88200; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) ch->pcmrates[i++] = 96000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) ch->pcmrates[i++] = 176400; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) ch->pcmrates[i++] = 192000; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(cap)) */ ch->pcmrates[i] = 0; if (i > 0) { ch->caps.minspeed = ch->pcmrates[0]; ch->caps.maxspeed = ch->pcmrates[i - 1]; } } return (ret); } static void hdac_dump_ctls(struct hdac_devinfo *devinfo, const char *banner, uint32_t flag) { struct hdac_audio_ctl *ctl; struct hdac_softc *sc = devinfo->codec->sc; int i; uint32_t fl = 0; if (flag == 0) { fl = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_CD | SOUND_MASK_LINE | SOUND_MASK_RECLEV | SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_OGAIN; } i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL || ctl->widget->enable == 0 || (ctl->ossmask & (SOUND_MASK_SKIP | SOUND_MASK_DISABLE))) continue; if ((flag == 0 && (ctl->ossmask & ~fl)) || (flag != 0 && (ctl->ossmask & flag))) { if (banner != NULL) { device_printf(sc->dev, "\n"); device_printf(sc->dev, "%s\n", banner); } goto hdac_ctl_dump_it_all; } } return; hdac_ctl_dump_it_all: i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget == NULL || ctl->widget->enable == 0) continue; if (!((flag == 0 && (ctl->ossmask & ~fl)) || (flag != 0 && (ctl->ossmask & flag)))) continue; if (flag == 0) { device_printf(sc->dev, "\n"); device_printf(sc->dev, "Unknown Ctl (OSS: %s)\n", hdac_audio_ctl_ossmixer_mask2name(ctl->ossmask)); } device_printf(sc->dev, " |\n"); device_printf(sc->dev, " +- nid: %2d index: %2d ", ctl->widget->nid, ctl->index); if (ctl->childwidget != NULL) printf("(nid: %2d) ", ctl->childwidget->nid); else printf(" "); printf("mute: %d step: %3d size: %3d off: %3d dir=0x%x ossmask=0x%08x\n", ctl->mute, ctl->step, ctl->size, ctl->offset, ctl->dir, ctl->ossmask); } } static void hdac_dump_audio_formats(struct hdac_softc *sc, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { device_printf(sc->dev, " Stream cap: 0x%08x\n", cap); device_printf(sc->dev, " Format:"); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) printf(" AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) printf(" FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) printf(" PCM"); printf("\n"); } cap = pcmcap; if (cap != 0) { device_printf(sc->dev, " PCM cap: 0x%08x\n", cap); device_printf(sc->dev, " PCM size:"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) printf(" 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) printf(" 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) printf(" 32"); printf("\n"); device_printf(sc->dev, " PCM rate:"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) printf(" 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) printf(" 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) printf(" 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) printf(" 44"); printf(" 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) printf(" 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) printf(" 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) printf(" 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) printf(" 192"); printf("\n"); } } static void hdac_dump_pin(struct hdac_softc *sc, struct hdac_widget *w) { uint32_t pincap, wcap; pincap = w->wclass.pin.cap; wcap = w->param.widget_cap; device_printf(sc->dev, " Pin cap: 0x%08x\n", pincap); device_printf(sc->dev, " "); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) printf(" ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) printf(" TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) printf(" PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) printf(" HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) printf(" OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) printf(" IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) printf(" BAL"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { printf(" VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) printf(" 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) printf(" 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) printf(" 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) printf(" GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) printf(" HIZ"); printf(" ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) printf(" EAPD"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(wcap)) printf(" : UNSOL"); printf("\n"); device_printf(sc->dev, " Pin config: 0x%08x\n", w->wclass.pin.config); device_printf(sc->dev, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) printf(" HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) printf(" IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) printf(" OUT"); printf("\n"); } static void hdac_dump_amp(struct hdac_softc *sc, uint32_t cap, char *banner) { device_printf(sc->dev, " %s amp: 0x%08x\n", banner, cap); device_printf(sc->dev, " " "mute=%d step=%d size=%d offset=%d\n", HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap), HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap), HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap)); } static void hdac_dump_nodes(struct hdac_devinfo *devinfo) { struct hdac_softc *sc = devinfo->codec->sc; struct hdac_widget *w, *cw; int i, j; device_printf(sc->dev, "\n"); device_printf(sc->dev, "Default Parameter\n"); device_printf(sc->dev, "-----------------\n"); hdac_dump_audio_formats(sc, devinfo->function.audio.supp_stream_formats, devinfo->function.audio.supp_pcm_size_rate); device_printf(sc->dev, " IN amp: 0x%08x\n", devinfo->function.audio.inamp_cap); device_printf(sc->dev, " OUT amp: 0x%08x\n", devinfo->function.audio.outamp_cap); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL) { device_printf(sc->dev, "Ghost widget nid=%d\n", i); continue; } device_printf(sc->dev, "\n"); device_printf(sc->dev, " nid: %d [%s]%s\n", w->nid, HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap) ? "DIGITAL" : "ANALOG", (w->enable == 0) ? " [DISABLED]" : ""); device_printf(sc->dev, " name: %s\n", w->name); device_printf(sc->dev, " widget_cap: 0x%08x\n", w->param.widget_cap); device_printf(sc->dev, " Parse flags: 0x%08x\n", w->pflags); device_printf(sc->dev, " Ctl flags: 0x%08x\n", w->ctlflags); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdac_dump_audio_formats(sc, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) hdac_dump_pin(sc, w); if (w->param.eapdbtl != HDAC_INVALID) device_printf(sc->dev, " EAPD: 0x%08x\n", w->param.eapdbtl); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdac_dump_amp(sc, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdac_dump_amp(sc, w->param.inamp_cap, " Input"); device_printf(sc->dev, " connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdac_widget_get(devinfo, w->conns[j]); device_printf(sc->dev, " |\n"); device_printf(sc->dev, " + <- nid=%d [%s]", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) printf(" [UNKNOWN]"); else if (cw->enable == 0) printf(" [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) printf(" (selected)"); printf("\n"); } } } static int hdac_dump_dac_internal(struct hdac_devinfo *devinfo, nid_t nid, int depth) { struct hdac_widget *w, *cw; struct hdac_softc *sc = devinfo->codec->sc; int i; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdac_widget_get(devinfo, nid); if (w == NULL || w->enable == 0 || !(w->pflags & HDA_DAC_PATH)) return (0); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { device_printf(sc->dev, "\n"); device_printf(sc->dev, " nid=%d [%s]\n", w->nid, w->name); device_printf(sc->dev, " ^\n"); device_printf(sc->dev, " |\n"); device_printf(sc->dev, " +-----<------+\n"); } else { device_printf(sc->dev, " ^\n"); device_printf(sc->dev, " |\n"); device_printf(sc->dev, " "); printf(" nid=%d [%s]\n", w->nid, w->name); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) { return (1); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) { for (i = 0; i < w->nconns; i++) { cw = hdac_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (hdac_dump_dac_internal(devinfo, cw->nid, depth + 1) != 0) return (1); } } else if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && w->selconn > -1 && w->selconn < w->nconns) { if (hdac_dump_dac_internal(devinfo, w->conns[w->selconn], depth + 1) != 0) return (1); } return (0); } static void hdac_dump_dac(struct hdac_devinfo *devinfo) { struct hdac_widget *w; struct hdac_softc *sc = devinfo->codec->sc; int i, printed = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || !(w->pflags & HDA_DAC_PATH)) continue; if (printed == 0) { printed = 1; device_printf(sc->dev, "\n"); device_printf(sc->dev, "Playback path:\n"); } hdac_dump_dac_internal(devinfo, w->nid, 0); } } static void hdac_dump_adc(struct hdac_devinfo *devinfo) { struct hdac_widget *w, *cw; struct hdac_softc *sc = devinfo->codec->sc; int i, j; int printed = 0; char ossdevs[256]; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (!(w->pflags & HDA_ADC_RECSEL)) continue; if (printed == 0) { printed = 1; device_printf(sc->dev, "\n"); device_printf(sc->dev, "Recording sources:\n"); } device_printf(sc->dev, "\n"); device_printf(sc->dev, " nid=%d [%s]\n", w->nid, w->name); for (j = 0; j < w->nconns; j++) { cw = hdac_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; hdac_audio_ctl_ossmixer_mask2allname(cw->ctlflags, ossdevs, sizeof(ossdevs)); device_printf(sc->dev, " |\n"); device_printf(sc->dev, " + <- nid=%d [%s]", cw->nid, cw->name); if (strlen(ossdevs) > 0) { printf(" [recsrc: %s]", ossdevs); } printf("\n"); } } } static void hdac_dump_pcmchannels(struct hdac_softc *sc, int pcnt, int rcnt) { nid_t *nids; if (pcnt > 0) { device_printf(sc->dev, "\n"); device_printf(sc->dev, " PCM Playback: %d\n", pcnt); hdac_dump_audio_formats(sc, sc->play.supp_stream_formats, sc->play.supp_pcm_size_rate); device_printf(sc->dev, " DAC:"); for (nids = sc->play.io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } if (rcnt > 0) { device_printf(sc->dev, "\n"); device_printf(sc->dev, " PCM Record: %d\n", rcnt); hdac_dump_audio_formats(sc, sc->play.supp_stream_formats, sc->rec.supp_pcm_size_rate); device_printf(sc->dev, " ADC:"); for (nids = sc->rec.io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } } static void hdac_release_resources(struct hdac_softc *sc) { struct hdac_devinfo *devinfo = NULL; device_t *devlist = NULL; int i, devcount; if (sc == NULL) return; hdac_lock(sc); sc->polling = 0; sc->poll_ival = 0; callout_stop(&sc->poll_hdac); callout_stop(&sc->poll_jack); hdac_reset(sc); hdac_unlock(sc); callout_drain(&sc->poll_hdac); callout_drain(&sc->poll_jack); hdac_irq_free(sc); device_get_children(sc->dev, &devlist, &devcount); for (i = 0; devlist != NULL && i < devcount; i++) { devinfo = (struct hdac_devinfo *)device_get_ivars(devlist[i]); if (devinfo == NULL) continue; if (devinfo->widget != NULL) free(devinfo->widget, M_HDAC); if (devinfo->node_type == HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO && devinfo->function.audio.ctl != NULL) free(devinfo->function.audio.ctl, M_HDAC); free(devinfo, M_HDAC); device_delete_child(sc->dev, devlist[i]); } if (devlist != NULL) free(devlist, M_TEMP); for (i = 0; i < HDAC_CODEC_MAX; i++) { if (sc->codecs[i] != NULL) free(sc->codecs[i], M_HDAC); sc->codecs[i] = NULL; } hdac_dma_free(sc, &sc->pos_dma); hdac_dma_free(sc, &sc->rirb_dma); hdac_dma_free(sc, &sc->corb_dma); if (sc->play.blkcnt > 0) hdac_dma_free(sc, &sc->play.bdl_dma); if (sc->rec.blkcnt > 0) hdac_dma_free(sc, &sc->rec.bdl_dma); if (sc->chan_dmat != NULL) { bus_dma_tag_destroy(sc->chan_dmat); sc->chan_dmat = NULL; } hdac_mem_free(sc); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); } /* This function surely going to make its way into upper level someday. */ static void hdac_config_fetch(struct hdac_softc *sc, uint32_t *on, uint32_t *off) { const char *res = NULL; int i = 0, j, k, len, inv; if (on != NULL) *on = 0; if (off != NULL) *off = 0; if (sc == NULL) return; if (resource_string_value(device_get_name(sc->dev), device_get_unit(sc->dev), "config", &res) != 0) return; if (!(res != NULL && strlen(res) > 0)) return; HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: HDA Config:"); ); for (;;) { while (res[i] != '\0' && (res[i] == ',' || isspace(res[i]) != 0)) i++; if (res[i] == '\0') { HDA_BOOTVERBOSE( printf("\n"); ); return; } j = i; while (res[j] != '\0' && !(res[j] == ',' || isspace(res[j]) != 0)) j++; len = j - i; if (len > 2 && strncmp(res + i, "no", 2) == 0) inv = 2; else inv = 0; for (k = 0; len > inv && k < HDAC_QUIRKS_TAB_LEN; k++) { if (strncmp(res + i + inv, hdac_quirks_tab[k].key, len - inv) != 0) continue; if (len - inv != strlen(hdac_quirks_tab[k].key)) break; HDA_BOOTVERBOSE( printf(" %s%s", (inv != 0) ? "no" : "", hdac_quirks_tab[k].key); ); if (inv == 0 && on != NULL) *on |= hdac_quirks_tab[k].value; else if (inv != 0 && off != NULL) *off |= hdac_quirks_tab[k].value; break; } i = j; } } #ifdef SND_DYNSYSCTL static int sysctl_hdac_polling(SYSCTL_HANDLER_ARGS) { struct hdac_softc *sc; struct hdac_devinfo *devinfo; device_t dev; uint32_t ctl; int err, val; dev = oidp->oid_arg1; devinfo = pcm_getdevinfo(dev); if (devinfo == NULL || devinfo->codec == NULL || devinfo->codec->sc == NULL) return (EINVAL); sc = devinfo->codec->sc; hdac_lock(sc); val = sc->polling; hdac_unlock(sc); err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); hdac_lock(sc); if (val != sc->polling) { if (hda_chan_active(sc) != 0) err = EBUSY; else if (val == 0) { callout_stop(&sc->poll_hdac); hdac_unlock(sc); callout_drain(&sc->poll_hdac); hdac_lock(sc); HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, sc->rirb_size / 2); ctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); ctl |= HDAC_RIRBCTL_RINTCTL; HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, ctl); HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); sc->polling = 0; DELAY(1000); } else { HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, 0); HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, 0); ctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); ctl &= ~HDAC_RIRBCTL_RINTCTL; HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, ctl); callout_reset(&sc->poll_hdac, 1, hdac_poll_callback, sc); sc->polling = 1; DELAY(1000); } } hdac_unlock(sc); return (err); } static int sysctl_hdac_polling_interval(SYSCTL_HANDLER_ARGS) { struct hdac_softc *sc; struct hdac_devinfo *devinfo; device_t dev; int err, val; dev = oidp->oid_arg1; devinfo = pcm_getdevinfo(dev); if (devinfo == NULL || devinfo->codec == NULL || devinfo->codec->sc == NULL) return (EINVAL); sc = devinfo->codec->sc; hdac_lock(sc); val = ((uint64_t)sc->poll_ival * 1000) / hz; hdac_unlock(sc); err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (val < 1) val = 1; if (val > 5000) val = 5000; val = ((uint64_t)val * hz) / 1000; if (val < 1) val = 1; if (val > (hz * 5)) val = hz * 5; hdac_lock(sc); sc->poll_ival = val; hdac_unlock(sc); return (err); } #ifdef SND_DEBUG static int sysctl_hdac_dump(SYSCTL_HANDLER_ARGS) { struct hdac_softc *sc; struct hdac_devinfo *devinfo; struct hdac_widget *w; device_t dev; uint32_t res, execres; int i, err, val; nid_t cad; dev = oidp->oid_arg1; devinfo = pcm_getdevinfo(dev); if (devinfo == NULL || devinfo->codec == NULL || devinfo->codec->sc == NULL) return (EINVAL); val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == 0) return (err); sc = devinfo->codec->sc; cad = devinfo->codec->cad; hdac_lock(sc); device_printf(dev, "HDAC Dump AFG [nid=%d]:\n", devinfo->nid); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; execres = hdac_command(sc, HDA_CMD_SET_PIN_SENSE(cad, w->nid, 0), cad); res = hdac_command(sc, HDA_CMD_GET_PIN_SENSE(cad, w->nid), cad); device_printf(dev, "nid=%-3d exec=0x%08x sense=0x%08x [%s]\n", w->nid, execres, res, (w->enable == 0) ? "DISABLED" : "ENABLED"); } device_printf(dev, "NumGPIO=%d NumGPO=%d NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->function.audio.gpio), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->function.audio.gpio)); if (1 || HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->function.audio.gpio) > 0) { device_printf(dev, " GPI:"); res = hdac_command(sc, HDA_CMD_GET_GPI_DATA(cad, devinfo->nid), cad); printf(" data=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(cad, devinfo->nid), cad); printf(" wake=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(cad, devinfo->nid), cad); printf(" unsol=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPI_STICKY_MASK(cad, devinfo->nid), cad); printf(" sticky=0x%08x\n", res); } if (1 || HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->function.audio.gpio) > 0) { device_printf(dev, " GPO:"); res = hdac_command(sc, HDA_CMD_GET_GPO_DATA(cad, devinfo->nid), cad); printf(" data=0x%08x\n", res); } if (1 || HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->function.audio.gpio) > 0) { device_printf(dev, "GPI0:"); res = hdac_command(sc, HDA_CMD_GET_GPIO_DATA(cad, devinfo->nid), cad); printf(" data=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPIO_ENABLE_MASK(cad, devinfo->nid), cad); printf(" enable=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPIO_DIRECTION(cad, devinfo->nid), cad); printf(" direction=0x%08x\n", res); res = hdac_command(sc, HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(cad, devinfo->nid), cad); device_printf(dev, " wake=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(cad, devinfo->nid), cad); printf(" unsol=0x%08x", res); res = hdac_command(sc, HDA_CMD_GET_GPIO_STICKY_MASK(cad, devinfo->nid), cad); printf(" sticky=0x%08x\n", res); } hdac_unlock(sc); return (0); } #endif #endif static void hdac_attach2(void *arg) { struct hdac_softc *sc; struct hdac_widget *w; struct hdac_audio_ctl *ctl; uint32_t quirks_on, quirks_off; int pcnt, rcnt; int i; char status[SND_STATUSLEN]; device_t *devlist = NULL; int devcount; struct hdac_devinfo *devinfo = NULL; sc = (struct hdac_softc *)arg; hdac_config_fetch(sc, &quirks_on, &quirks_off); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: HDA Config: on=0x%08x off=0x%08x\n", quirks_on, quirks_off); ); hdac_lock(sc); /* Remove ourselves from the config hooks */ if (sc->intrhook.ich_func != NULL) { config_intrhook_disestablish(&sc->intrhook); sc->intrhook.ich_func = NULL; } /* Start the corb and rirb engines */ HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Starting CORB Engine...\n"); ); hdac_corb_start(sc); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Starting RIRB Engine...\n"); ); hdac_rirb_start(sc); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Enabling controller interrupt...\n"); ); if (sc->polling == 0) HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); HDAC_WRITE_4(&sc->mem, HDAC_GCTL, HDAC_READ_4(&sc->mem, HDAC_GCTL) | HDAC_GCTL_UNSOL); DELAY(1000); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Scanning HDA codecs...\n"); ); hdac_scan_codecs(sc); device_get_children(sc->dev, &devlist, &devcount); for (i = 0; devlist != NULL && i < devcount; i++) { devinfo = (struct hdac_devinfo *)device_get_ivars(devlist[i]); if (devinfo != NULL && devinfo->node_type == HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) { break; } else devinfo = NULL; } if (devlist != NULL) free(devlist, M_TEMP); if (devinfo == NULL) { hdac_unlock(sc); device_printf(sc->dev, "Audio Function Group not found!\n"); hdac_release_resources(sc); return; } HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Parsing AFG nid=%d cad=%d\n", devinfo->nid, devinfo->codec->cad); ); hdac_audio_parse(devinfo); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Parsing Ctls...\n"); ); hdac_audio_ctl_parse(devinfo); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Parsing vendor patch...\n"); ); hdac_vendor_patch_parse(devinfo); if (quirks_on != 0) devinfo->function.audio.quirks |= quirks_on; if (quirks_off != 0) devinfo->function.audio.quirks &= ~quirks_off; /* XXX Disable all DIGITAL path. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdac_widget_get(devinfo, i); if (w == NULL) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { w->enable = 0; continue; } /* XXX Disable useless pin ? */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE) w->enable = 0; } i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->widget == NULL) continue; if (ctl->ossmask & SOUND_MASK_DISABLE) ctl->enable = 0; w = ctl->widget; if (w->enable == 0 || HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) ctl->enable = 0; w = ctl->childwidget; if (w == NULL) continue; if (w->enable == 0 || HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) ctl->enable = 0; } HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Building AFG tree...\n"); ); hdac_audio_build_tree(devinfo); i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->ossmask & (SOUND_MASK_SKIP | SOUND_MASK_DISABLE)) ctl->ossmask = 0; } HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: AFG commit...\n"); ); hdac_audio_commit(devinfo, HDA_COMMIT_ALL); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Ctls commit...\n"); ); hdac_audio_ctl_commit(devinfo); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: PCMDIR_PLAY setup...\n"); ); pcnt = hdac_pcmchannel_setup(devinfo, PCMDIR_PLAY); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: PCMDIR_REC setup...\n"); ); rcnt = hdac_pcmchannel_setup(devinfo, PCMDIR_REC); hdac_unlock(sc); HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: OSS mixer initialization...\n"); ); /* * There is no point of return after this. If the driver failed, * so be it. Let the detach procedure do all the cleanup. */ if (mixer_init(sc->dev, &hdac_audio_ctl_ossmixer_class, devinfo) != 0) device_printf(sc->dev, "Can't register mixer\n"); if (pcnt > 0) pcnt = 1; if (rcnt > 0) rcnt = 1; HDA_BOOTVERBOSE( device_printf(sc->dev, "HDA_DEBUG: Registering PCM channels...\n"); ); if (pcm_register(sc->dev, devinfo, pcnt, rcnt) != 0) device_printf(sc->dev, "Can't register PCM\n"); sc->registered++; if ((devinfo->function.audio.quirks & HDA_QUIRK_DMAPOS) && hdac_dma_alloc(sc, &sc->pos_dma, (sc->num_iss + sc->num_oss + sc->num_bss) * 8) != 0) { HDA_BOOTVERBOSE( device_printf(sc->dev, "Failed to allocate DMA pos buffer (non-fatal)\n"); ); } for (i = 0; i < pcnt; i++) pcm_addchan(sc->dev, PCMDIR_PLAY, &hdac_channel_class, devinfo); for (i = 0; i < rcnt; i++) pcm_addchan(sc->dev, PCMDIR_REC, &hdac_channel_class, devinfo); #ifdef SND_DYNSYSCTL SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), sysctl_hdac_polling, "I", "Enable polling mode"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "polling_interval", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), sysctl_hdac_polling_interval, "I", "Controller/Jack Sense polling interval (1-1000 ms)"); #ifdef SND_DEBUG SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "dump", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), sysctl_hdac_dump, "I", "Dump states"); #endif #endif snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s [%s]", rman_get_start(sc->mem.mem_res), rman_get_start(sc->irq.irq_res), PCM_KLDSTRING(snd_hda), HDA_DRV_TEST_REV); pcm_setstatus(sc->dev, status); device_printf(sc->dev, "\n", hdac_codec_name(devinfo)); HDA_BOOTVERBOSE( device_printf(sc->dev, "\n", hdac_codec_id(devinfo)); ); device_printf(sc->dev, "\n", HDA_DRV_TEST_REV); HDA_BOOTVERBOSE( if (devinfo->function.audio.quirks != 0) { device_printf(sc->dev, "\n"); device_printf(sc->dev, "HDA config/quirks:"); for (i = 0; i < HDAC_QUIRKS_TAB_LEN; i++) { if ((devinfo->function.audio.quirks & hdac_quirks_tab[i].value) == hdac_quirks_tab[i].value) printf(" %s", hdac_quirks_tab[i].key); } printf("\n"); } device_printf(sc->dev, "\n"); device_printf(sc->dev, "+-------------------+\n"); device_printf(sc->dev, "| DUMPING HDA NODES |\n"); device_printf(sc->dev, "+-------------------+\n"); hdac_dump_nodes(devinfo); device_printf(sc->dev, "\n"); device_printf(sc->dev, "+------------------------+\n"); device_printf(sc->dev, "| DUMPING HDA AMPLIFIERS |\n"); device_printf(sc->dev, "+------------------------+\n"); device_printf(sc->dev, "\n"); i = 0; while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { device_printf(sc->dev, "%3d: nid=%d", i, (ctl->widget != NULL) ? ctl->widget->nid : -1); if (ctl->childwidget != NULL) printf(" cnid=%d", ctl->childwidget->nid); printf(" dir=0x%x index=%d " "ossmask=0x%08x ossdev=%d%s\n", ctl->dir, ctl->index, ctl->ossmask, ctl->ossdev, (ctl->enable == 0) ? " [DISABLED]" : ""); } device_printf(sc->dev, "\n"); device_printf(sc->dev, "+-----------------------------------+\n"); device_printf(sc->dev, "| DUMPING HDA AUDIO/VOLUME CONTROLS |\n"); device_printf(sc->dev, "+-----------------------------------+\n"); hdac_dump_ctls(devinfo, "Master Volume (OSS: vol)", SOUND_MASK_VOLUME); hdac_dump_ctls(devinfo, "PCM Volume (OSS: pcm)", SOUND_MASK_PCM); hdac_dump_ctls(devinfo, "CD Volume (OSS: cd)", SOUND_MASK_CD); hdac_dump_ctls(devinfo, "Microphone Volume (OSS: mic)", SOUND_MASK_MIC); hdac_dump_ctls(devinfo, "Line-in Volume (OSS: line)", SOUND_MASK_LINE); hdac_dump_ctls(devinfo, "Recording Level (OSS: rec)", SOUND_MASK_RECLEV); hdac_dump_ctls(devinfo, "Speaker/Beep (OSS: speaker)", SOUND_MASK_SPEAKER); hdac_dump_ctls(devinfo, NULL, 0); hdac_dump_dac(devinfo); hdac_dump_adc(devinfo); device_printf(sc->dev, "\n"); device_printf(sc->dev, "+--------------------------------------+\n"); device_printf(sc->dev, "| DUMPING PCM Playback/Record Channels |\n"); device_printf(sc->dev, "+--------------------------------------+\n"); hdac_dump_pcmchannels(sc, pcnt, rcnt); ); if (sc->polling != 0) { hdac_lock(sc); callout_reset(&sc->poll_hdac, 1, hdac_poll_callback, sc); hdac_unlock(sc); } } /**************************************************************************** * int hdac_detach(device_t) * * Detach and free up resources utilized by the hdac device. ****************************************************************************/ static int hdac_detach(device_t dev) { struct hdac_softc *sc = NULL; struct hdac_devinfo *devinfo = NULL; int err; devinfo = (struct hdac_devinfo *)pcm_getdevinfo(dev); if (devinfo != NULL && devinfo->codec != NULL) sc = devinfo->codec->sc; if (sc == NULL) return (0); if (sc->registered > 0) { err = pcm_unregister(dev); if (err != 0) return (err); } hdac_release_resources(sc); return (0); } static device_method_t hdac_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdac_probe), DEVMETHOD(device_attach, hdac_attach), DEVMETHOD(device_detach, hdac_detach), { 0, 0 } }; static driver_t hdac_driver = { "pcm", hdac_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hda, pci, hdac_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_hda, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hda, 1); Index: head/sys/dev/sound/pci/ich.c =================================================================== --- head/sys/dev/sound/pci/ich.c (revision 170520) +++ head/sys/dev/sound/pci/ich.c (revision 170521) @@ -1,1235 +1,1237 @@ /*- * Copyright (c) 2000 Katsurajima Naoto * Copyright (c) 2001 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ #define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ #define ICH_DTBL_LENGTH 32 #define ICH_DEFAULT_BUFSZ 16384 #define ICH_MAX_BUFSZ 65536 #define ICH_MIN_BUFSZ 4096 #define ICH_DEFAULT_BLKCNT 2 #define ICH_MAX_BLKCNT 32 #define ICH_MIN_BLKCNT 2 #define ICH_MIN_BLKSZ 64 #define INTEL_VENDORID 0x8086 #define SIS_VENDORID 0x1039 #define NVIDIA_VENDORID 0x10de #define AMD_VENDORID 0x1022 #define INTEL_82440MX 0x7195 #define INTEL_82801AA 0x2415 #define INTEL_82801AB 0x2425 #define INTEL_82801BA 0x2445 #define INTEL_82801CA 0x2485 #define INTEL_82801DB 0x24c5 /* ICH4 needs special handling */ #define INTEL_82801EB 0x24d5 /* ICH5 needs to be treated as ICH4 */ #define INTEL_6300ESB 0x25a6 /* 6300ESB needs to be treated as ICH4 */ #define INTEL_82801FB 0x266e /* ICH6 needs to be treated as ICH4 */ #define INTEL_82801GB 0x27de /* ICH7 needs to be treated as ICH4 */ #define SIS_7012 0x7012 /* SiS 7012 needs special handling */ #define NVIDIA_NFORCE 0x01b1 #define NVIDIA_NFORCE2 0x006a #define NVIDIA_NFORCE2_400 0x008a #define NVIDIA_NFORCE3 0x00da #define NVIDIA_NFORCE3_250 0x00ea #define NVIDIA_NFORCE4 0x0059 #define NVIDIA_NFORCE_410_MCP 0x026b #define NVIDIA_NFORCE4_MCP 0x003a #define AMD_768 0x7445 #define AMD_8111 0x746d #define ICH_LOCK(sc) snd_mtxlock((sc)->ich_lock) #define ICH_UNLOCK(sc) snd_mtxunlock((sc)->ich_lock) #define ICH_LOCK_ASSERT(sc) snd_mtxassert((sc)->ich_lock) #if 0 #define ICH_DEBUG(stmt) do { \ stmt \ } while(0) #else #define ICH_DEBUG(...) #endif #define ICH_CALIBRATE_DONE (1 << 0) #define ICH_IGNORE_PCR (1 << 1) #define ICH_IGNORE_RESET (1 << 2) #define ICH_FIXED_RATE (1 << 3) #define ICH_DMA_NOCACHE (1 << 4) #define ICH_HIGH_LATENCY (1 << 5) static const struct ich_type { uint16_t vendor; uint16_t devid; uint32_t options; #define PROBE_LOW 0x01 char *name; } ich_devs[] = { { INTEL_VENDORID, INTEL_82440MX, 0, "Intel 440MX" }, { INTEL_VENDORID, INTEL_82801AA, 0, "Intel ICH (82801AA)" }, { INTEL_VENDORID, INTEL_82801AB, 0, "Intel ICH (82801AB)" }, { INTEL_VENDORID, INTEL_82801BA, 0, "Intel ICH2 (82801BA)" }, { INTEL_VENDORID, INTEL_82801CA, 0, "Intel ICH3 (82801CA)" }, { INTEL_VENDORID, INTEL_82801DB, PROBE_LOW, "Intel ICH4 (82801DB)" }, { INTEL_VENDORID, INTEL_82801EB, PROBE_LOW, "Intel ICH5 (82801EB)" }, { INTEL_VENDORID, INTEL_6300ESB, PROBE_LOW, "Intel 6300ESB" }, { INTEL_VENDORID, INTEL_82801FB, PROBE_LOW, "Intel ICH6 (82801FB)" }, { INTEL_VENDORID, INTEL_82801GB, PROBE_LOW, "Intel ICH7 (82801GB)" }, { SIS_VENDORID, SIS_7012, 0, "SiS 7012" }, { NVIDIA_VENDORID, NVIDIA_NFORCE, 0, "nVidia nForce" }, { NVIDIA_VENDORID, NVIDIA_NFORCE2, 0, "nVidia nForce2" }, { NVIDIA_VENDORID, NVIDIA_NFORCE2_400, 0, "nVidia nForce2 400" }, { NVIDIA_VENDORID, NVIDIA_NFORCE3, 0, "nVidia nForce3" }, { NVIDIA_VENDORID, NVIDIA_NFORCE3_250, 0, "nVidia nForce3 250" }, { NVIDIA_VENDORID, NVIDIA_NFORCE4, 0, "nVidia nForce4" }, { NVIDIA_VENDORID, NVIDIA_NFORCE_410_MCP, 0, "nVidia nForce 410 MCP" }, { NVIDIA_VENDORID, NVIDIA_NFORCE4_MCP, 0, "nVidia nForce 4 MCP" }, { AMD_VENDORID, AMD_768, 0, "AMD-768" }, { AMD_VENDORID, AMD_8111, 0, "AMD-8111" } }; /* buffer descriptor */ struct ich_desc { volatile uint32_t buffer; volatile uint32_t length; }; struct sc_info; /* channel registers */ struct sc_chinfo { uint32_t num:8, run:1, run_save:1; uint32_t blksz, blkcnt, spd; uint32_t regbase, spdreg; uint32_t imask; uint32_t civ; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; struct ich_desc *dtbl; bus_addr_t desc_addr; }; /* device private data */ struct sc_info { device_t dev; int hasvra, hasvrm, hasmic; unsigned int chnum, bufsz, blkcnt; int sample_size, swap_reg; struct resource *nambar, *nabmbar, *irq; int regtype, nambarid, nabmbarid, irqid; bus_space_tag_t nambart, nabmbart; bus_space_handle_t nambarh, nabmbarh; bus_dma_tag_t dmat, chan_dmat; bus_dmamap_t dtmap; void *ih; struct ac97_info *codec; struct sc_chinfo ch[3]; int ac97rate; struct ich_desc *dtbl; unsigned int dtbl_size; bus_addr_t desc_addr; struct intr_config_hook intrhook; uint16_t vendor; uint16_t devid; uint32_t flags; struct mtx *ich_lock; }; /* -------------------------------------------------------------------- */ static uint32_t ich_fmt[] = { AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps ich_vrcaps = {8000, 48000, ich_fmt, 0}; static struct pcmchan_caps ich_caps = {48000, 48000, ich_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static __inline uint32_t ich_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno)); case 2: return (bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno)); case 4: return (bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno)); default: return (0xffffffff); } } static __inline void ich_wr(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->nabmbart, sc->nabmbarh, regno, data); break; case 2: bus_space_write_2(sc->nabmbart, sc->nabmbarh, regno, data); break; case 4: bus_space_write_4(sc->nabmbart, sc->nabmbarh, regno, data); break; } } /* ac97 codec */ static int ich_waitcd(void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; uint32_t data; int i; for (i = 0; i < ICH_TIMEOUT; i++) { data = ich_rd(sc, ICH_REG_ACC_SEMA, 1); if ((data & 0x01) == 0) return (0); DELAY(1); } if ((sc->flags & ICH_IGNORE_PCR) != 0) return (0); device_printf(sc->dev, "CODEC semaphore timeout\n"); return (ETIMEDOUT); } static int ich_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; ich_waitcd(sc); return (bus_space_read_2(sc->nambart, sc->nambarh, regno)); } static int ich_wrcd(kobj_t obj, void *devinfo, int regno, uint16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; ich_waitcd(sc); bus_space_write_2(sc->nambart, sc->nambarh, regno, data); return (0); } static kobj_method_t ich_ac97_methods[] = { KOBJMETHOD(ac97_read, ich_rdcd), KOBJMETHOD(ac97_write, ich_wrcd), { 0, 0 } }; AC97_DECLARE(ich_ac97); /* -------------------------------------------------------------------- */ /* common routines */ static void ich_filldtbl(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; uint32_t base; int i; base = sndbuf_getbufaddr(ch->buffer); if ((ch->blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) ch->blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; if ((sndbuf_getblksz(ch->buffer) != ch->blksz || sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, ch->blksz, ch->blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); for (i = 0; i < ICH_DTBL_LENGTH; i++) { ch->dtbl[i].buffer = base + (ch->blksz * (i % ch->blkcnt)); ch->dtbl[i].length = ICH_BDC_IOC | (ch->blksz / ch->parent->sample_size); } } static int ich_resetchan(struct sc_info *sc, int num) { int i, cr, regbase; if (num == 0) regbase = ICH_REG_PO_BASE; else if (num == 1) regbase = ICH_REG_PI_BASE; else if (num == 2) regbase = ICH_REG_MC_BASE; else return (ENXIO); ich_wr(sc, regbase + ICH_REG_X_CR, 0, 1); #if 1 /* This may result in no sound output on NForce 2 MBs, see PR 73987 */ DELAY(100); #else (void)ich_rd(sc, regbase + ICH_REG_X_CR, 1); #endif ich_wr(sc, regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); for (i = 0; i < ICH_TIMEOUT; i++) { cr = ich_rd(sc, regbase + ICH_REG_X_CR, 1); if (cr == 0) return (0); DELAY(1); } if (sc->flags & ICH_IGNORE_RESET) return (0); #if 0 else if (sc->vendor == NVIDIA_VENDORID) { sc->flags |= ICH_IGNORE_RESET; device_printf(sc->dev, "ignoring reset failure!\n"); return (0); } #endif device_printf(sc->dev, "cannot reset channel %d\n", num); return (ENXIO); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * ichchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; unsigned int num; ICH_LOCK(sc); num = sc->chnum++; ch = &sc->ch[num]; ch->num = num; ch->buffer = b; ch->channel = c; ch->parent = sc; ch->run = 0; ch->dtbl = sc->dtbl + (ch->num * ICH_DTBL_LENGTH); ch->desc_addr = sc->desc_addr + (ch->num * ICH_DTBL_LENGTH * sizeof(struct ich_desc)); ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; switch(ch->num) { case 0: /* play */ KASSERT(dir == PCMDIR_PLAY, ("wrong direction")); ch->regbase = ICH_REG_PO_BASE; ch->spdreg = (sc->hasvra) ? AC97_REGEXT_FDACRATE : 0; ch->imask = ICH_GLOB_STA_POINT; break; case 1: /* record */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_PI_BASE; ch->spdreg = (sc->hasvra) ? AC97_REGEXT_LADCRATE : 0; ch->imask = ICH_GLOB_STA_PIINT; break; case 2: /* mic */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_MC_BASE; ch->spdreg = (sc->hasvrm) ? AC97_REGEXT_MADCRATE : 0; ch->imask = ICH_GLOB_STA_MINT; break; default: return (NULL); } if (sc->flags & ICH_FIXED_RATE) ch->spdreg = 0; ICH_UNLOCK(sc); if (sndbuf_alloc(ch->buffer, sc->chan_dmat, ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), sc->bufsz) != 0) return (NULL); ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ICH_UNLOCK(sc); return (ch); } static int ichchan_setformat(kobj_t obj, void *data, uint32_t format) { ICH_DEBUG( struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); return (0); } static int ichchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); if (ch->spdreg) { int r, ac97rate; ICH_LOCK(sc); if (sc->ac97rate <= 32000 || sc->ac97rate >= 64000) sc->ac97rate = 48000; ac97rate = sc->ac97rate; ICH_UNLOCK(sc); r = (speed * 48000) / ac97rate; /* * Cast the return value of ac97_setrate() to uint64 so that * the math don't overflow into the negative range. */ ch->spd = ((uint64_t)ac97_setrate(sc->codec, ch->spdreg, r) * ac97rate) / 48000; } else { ch->spd = 48000; } return (ch->spd); } static int ichchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); if (sc->flags & ICH_HIGH_LATENCY) blocksize = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; if (blocksize < ICH_MIN_BLKSZ) blocksize = ICH_MIN_BLKSZ; blocksize &= ~(ICH_MIN_BLKSZ - 1); ch->blksz = blocksize; ich_filldtbl(ch); ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_LVI, ch->blkcnt - 1, 1); ICH_UNLOCK(sc); return (ch->blksz); } static int ichchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); switch (go) { case PCMTRIG_START: ch->run = 1; ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE, 1); ICH_UNLOCK(sc); break; - + case PCMTRIG_STOP: case PCMTRIG_ABORT: ICH_LOCK(sc); ich_resetchan(sc, ch->num); ICH_UNLOCK(sc); ch->run = 0; + break; + default: break; } return (0); } static int ichchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; uint32_t pos; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); ICH_LOCK(sc); ch->civ = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1) % ch->blkcnt; ICH_UNLOCK(sc); pos = ch->civ * ch->blksz; return (pos); } static struct pcmchan_caps * ichchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; ICH_DEBUG( struct sc_info *sc = ch->parent; if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(ch->parent->dev, "WARNING: %s() called before calibration!\n", __func__); ); return ((ch->spdreg) ? &ich_vrcaps : &ich_caps); } static kobj_method_t ichchan_methods[] = { KOBJMETHOD(channel_init, ichchan_init), KOBJMETHOD(channel_setformat, ichchan_setformat), KOBJMETHOD(channel_setspeed, ichchan_setspeed), KOBJMETHOD(channel_setblocksize, ichchan_setblocksize), KOBJMETHOD(channel_trigger, ichchan_trigger), KOBJMETHOD(channel_getptr, ichchan_getptr), KOBJMETHOD(channel_getcaps, ichchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(ichchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void ich_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; uint32_t cbi, lbi, lvi, st, gs; int i; ICH_LOCK(sc); ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); gs = ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_IMASK; if (gs & (ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES)) { /* Clear resume interrupt(s) - nothing doing with them */ ich_wr(sc, ICH_REG_GLOB_STA, gs, 4); } gs &= ~(ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES); for (i = 0; i < 3; i++) { ch = &sc->ch[i]; if ((ch->imask & gs) == 0) continue; gs &= ~ch->imask; st = ich_rd(sc, ch->regbase + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), 2); st &= ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI; if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) { /* block complete - update buffer */ if (ch->run) { ICH_UNLOCK(sc); chn_intr(ch->channel); ICH_LOCK(sc); } lvi = ich_rd(sc, ch->regbase + ICH_REG_X_LVI, 1); cbi = ch->civ % ch->blkcnt; if (cbi == 0) cbi = ch->blkcnt - 1; else cbi--; lbi = lvi % ch->blkcnt; if (cbi >= lbi) lvi += cbi - lbi; else lvi += cbi + ch->blkcnt - lbi; lvi %= ICH_DTBL_LENGTH; ich_wr(sc, ch->regbase + ICH_REG_X_LVI, lvi, 1); } /* clear status bit */ ich_wr(sc, ch->regbase + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), st, 2); } ICH_UNLOCK(sc); if (gs != 0) { device_printf(sc->dev, "Unhandled interrupt, gs_intr = %x\n", gs); } } /* ------------------------------------------------------------------------- */ /* Sysctl to control ac97 speed (some boards appear to end up using * XTAL_IN rather than BIT_CLK for link timing). */ static int ich_initsys(struct sc_info* sc) { #ifdef SND_DYNSYSCTL /* XXX: this should move to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "ac97rate", CTLFLAG_RW, &sc->ac97rate, 48000, "AC97 link rate (default = 48000)"); #endif /* SND_DYNSYSCTL */ return (0); } static void ich_setstatus(struct sc_info *sc) { char status[SND_STATUSLEN]; snprintf(status, SND_STATUSLEN, "at io 0x%lx, 0x%lx irq %ld bufsz %u %s", rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich)); if (bootverbose && (sc->flags & ICH_DMA_NOCACHE)) device_printf(sc->dev, "PCI Master abort workaround enabled\n"); pcm_setstatus(sc->dev, status); } /* -------------------------------------------------------------------- */ /* Calibrate card to determine the clock source. The source maybe a * function of the ac97 codec initialization code (to be investigated). */ static void ich_calibrate(void *arg) { struct sc_info *sc; struct sc_chinfo *ch; struct timeval t1, t2; uint8_t ociv, nciv; uint32_t wait_us, actual_48k_rate, oblkcnt; sc = (struct sc_info *)arg; ICH_LOCK(sc); ch = &sc->ch[1]; if (sc->intrhook.ich_func != NULL) { config_intrhook_disestablish(&sc->intrhook); sc->intrhook.ich_func = NULL; } /* * Grab audio from input for fixed interval and compare how * much we actually get with what we expect. Interval needs * to be sufficiently short that no interrupts are * generated. */ KASSERT(ch->regbase == ICH_REG_PI_BASE, ("wrong direction")); oblkcnt = ch->blkcnt; ch->blkcnt = 2; sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ichchan_setblocksize(0, ch, sndbuf_getmaxsize(ch->buffer) >> 1); ICH_LOCK(sc); sc->flags &= ~ICH_CALIBRATE_DONE; /* * our data format is stereo, 16 bit so each sample is 4 bytes. * assuming we get 48000 samples per second, we get 192000 bytes/sec. * we're going to start recording with interrupts disabled and measure * the time taken for one block to complete. we know the block size, * we know the time in microseconds, we calculate the sample rate: * * actual_rate [bps] = bytes / (time [s] * 4) * actual_rate [bps] = (bytes * 1000000) / (time [us] * 4) * actual_rate [Hz] = (bytes * 250000) / time [us] */ /* prepare */ ociv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); nciv = ociv; ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); /* start */ microtime(&t1); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM, 1); /* wait */ do { microtime(&t2); if (t2.tv_sec - t1.tv_sec > 1) break; nciv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); } while (nciv == ociv); /* stop */ ich_wr(sc, ch->regbase + ICH_REG_X_CR, 0, 1); /* reset */ DELAY(100); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); ch->blkcnt = oblkcnt; /* turn time delta into us */ wait_us = ((t2.tv_sec - t1.tv_sec) * 1000000) + t2.tv_usec - t1.tv_usec; if (nciv == ociv) { device_printf(sc->dev, "ac97 link rate calibration timed out after %d us\n", wait_us); sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ich_setstatus(sc); return; } actual_48k_rate = ((uint64_t)ch->blksz * 250000) / wait_us; if (actual_48k_rate < 47500 || actual_48k_rate > 48500) { sc->ac97rate = actual_48k_rate; } else { sc->ac97rate = 48000; } if (bootverbose || sc->ac97rate != 48000) { device_printf(sc->dev, "measured ac97 link rate at %d Hz", actual_48k_rate); if (sc->ac97rate != actual_48k_rate) printf(", will use %d Hz", sc->ac97rate); printf("\n"); } sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ich_setstatus(sc); return; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static void ich_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->desc_addr = segs->ds_addr; return; } static int ich_init(struct sc_info *sc) { uint32_t stat; ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); DELAY(600000); stat = ich_rd(sc, ICH_REG_GLOB_STA, 4); if ((stat & ICH_GLOB_STA_PCR) == 0) { /* ICH4/ICH5 may fail when busmastering is enabled. Continue */ if (sc->vendor == INTEL_VENDORID && ( sc->devid == INTEL_82801DB || sc->devid == INTEL_82801EB || sc->devid == INTEL_6300ESB || sc->devid == INTEL_82801FB || sc->devid == INTEL_82801GB)) { sc->flags |= ICH_IGNORE_PCR; device_printf(sc->dev, "primary codec not ready!\n"); } } #if 0 ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD | ICH_GLOB_CTL_PRES, 4); #else ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); #endif if (ich_resetchan(sc, 0) || ich_resetchan(sc, 1)) return (ENXIO); if (sc->hasmic && ich_resetchan(sc, 2)) return (ENXIO); return (0); } static int ich_pci_probe(device_t dev) { int i; uint16_t devid, vendor; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < sizeof(ich_devs)/sizeof(ich_devs[0]); i++) { if (vendor == ich_devs[i].vendor && devid == ich_devs[i].devid) { device_set_desc(dev, ich_devs[i].name); /* allow a better driver to override us */ if ((ich_devs[i].options & PROBE_LOW) != 0) return (BUS_PROBE_LOW_PRIORITY); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int ich_pci_attach(device_t dev) { uint32_t subdev; uint16_t extcaps; uint16_t devid, vendor; struct sc_info *sc; int i; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENXIO); } sc->ich_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ich softc"); sc->dev = dev; vendor = sc->vendor = pci_get_vendor(dev); devid = sc->devid = pci_get_device(dev); subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); /* * The SiS 7012 register set isn't quite like the standard ich. * There really should be a general "quirks" mechanism. */ if (vendor == SIS_VENDORID && devid == SIS_7012) { sc->swap_reg = 1; sc->sample_size = 1; } else { sc->swap_reg = 0; sc->sample_size = 2; } /* * Intel 440MX Errata #36 * - AC97 Soft Audio and Soft Modem Master Abort Errata * * http://www.intel.com/design/chipsets/specupdt/245051.htm */ if (vendor == INTEL_VENDORID && devid == INTEL_82440MX) sc->flags |= ICH_DMA_NOCACHE; /* * Enable bus master. On ich4/5 this may prevent the detection of * the primary codec becoming ready in ich_init(). */ pci_enable_busmaster(dev); /* * By default, ich4 has NAMBAR and NABMBAR i/o spaces as * read-only. Need to enable "legacy support", by poking into * pci config space. The driver should use MMBAR and MBBAR, * but doing so will mess things up here. ich4 has enough new * features it warrants it's own driver. */ if (vendor == INTEL_VENDORID && (devid == INTEL_82801DB || devid == INTEL_82801EB || devid == INTEL_6300ESB || devid == INTEL_82801FB || devid == INTEL_82801GB)) { sc->nambarid = PCIR_MMBAR; sc->nabmbarid = PCIR_MBBAR; sc->regtype = SYS_RES_MEMORY; pci_write_config(dev, PCIR_ICH_LEGACY, ICH_LEGACY_ENABLE, 1); } else { sc->nambarid = PCIR_NAMBAR; sc->nabmbarid = PCIR_NABMBAR; sc->regtype = SYS_RES_IOPORT; } sc->nambar = bus_alloc_resource_any(dev, sc->regtype, &sc->nambarid, RF_ACTIVE); sc->nabmbar = bus_alloc_resource_any(dev, sc->regtype, &sc->nabmbarid, RF_ACTIVE); if (!sc->nambar || !sc->nabmbar) { device_printf(dev, "unable to map IO port space\n"); goto bad; } sc->nambart = rman_get_bustag(sc->nambar); sc->nambarh = rman_get_bushandle(sc->nambar); sc->nabmbart = rman_get_bustag(sc->nabmbar); sc->nabmbarh = rman_get_bushandle(sc->nabmbar); sc->bufsz = pcm_getbuffersize(dev, ICH_MIN_BUFSZ, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { sc->blkcnt = sc->bufsz / i; i = 0; while (sc->blkcnt >> i) i++; sc->blkcnt = 1 << (i - 1); if (sc->blkcnt < ICH_MIN_BLKCNT) sc->blkcnt = ICH_MIN_BLKCNT; else if (sc->blkcnt > ICH_MAX_BLKCNT) sc->blkcnt = ICH_MAX_BLKCNT; } else sc->blkcnt = ICH_DEFAULT_BLKCNT; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "highlatency", &i) == 0 && i != 0) { sc->flags |= ICH_HIGH_LATENCY; sc->blkcnt = ICH_MIN_BLKCNT; } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "fixedrate", &i) == 0 && i != 0) sc->flags |= ICH_FIXED_RATE; sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ich_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (ich_init(sc)) { device_printf(dev, "unable to initialize the card\n"); goto bad; } sc->codec = AC97_CREATE(dev, sc, ich_ac97); if (sc->codec == NULL) goto bad; /* * Turn on inverted external amplifier sense flags for few * 'special' boards. */ switch (subdev) { case 0x202f161f: /* Gateway 7326GZ */ case 0x203a161f: /* Gateway 4028GZ */ case 0x204c161f: /* Kvazar-Micro Senator 3592XT */ case 0x8144104d: /* Sony VAIO PCG-TR* */ case 0x8197104d: /* Sony S1XP */ case 0x81c0104d: /* Sony VAIO type T */ case 0x81c5104d: /* Sony VAIO VGN B1VP/B1XP */ case 0x3089103c: /* Compaq Presario B3800 */ case 0x309a103c: /* HP Compaq nx4300 */ case 0x82131033: /* NEC VersaPro VJ10F/BH */ case 0x82be1033: /* NEC VersaPro VJ12F/CH */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(dev, ac97_getmixerclass(), sc->codec); /* check and set VRA function */ extcaps = ac97_getextcaps(sc->codec); sc->hasvra = extcaps & AC97_EXTCAP_VRA; sc->hasvrm = extcaps & AC97_EXTCAP_VRM; sc->hasmic = ac97_getcaps(sc->codec) & AC97_CAP_MICCHANNEL; ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); sc->dtbl_size = sizeof(struct ich_desc) * ICH_DTBL_LENGTH * ((sc->hasmic) ? 3 : 2); /* BDL tag */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->dtbl_size, 1, 0x3ffff, 0, NULL, NULL, &sc->dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* PCM channel tag */ if (bus_dma_tag_create(bus_get_dma_tag(dev), ICH_MIN_BLKSZ, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->bufsz, 1, 0x3ffff, 0, NULL, NULL, &sc->chan_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT | ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), &sc->dtmap)) goto bad; if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sc->dtbl_size, ich_setmap, sc, 0)) goto bad; if (pcm_register(dev, sc, 1, (sc->hasmic) ? 2 : 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &ichchan_class, sc); /* play */ pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record */ if (sc->hasmic) pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record mic */ if (sc->flags & ICH_FIXED_RATE) { sc->flags |= ICH_CALIBRATE_DONE; ich_setstatus(sc); } else { ich_initsys(sc); sc->intrhook.ich_func = ich_calibrate; sc->intrhook.ich_arg = sc; if (cold == 0 || config_intrhook_establish(&sc->intrhook) != 0) { sc->intrhook.ich_func = NULL; ich_calibrate(sc); } } return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->nambar) bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); if (sc->nabmbar) bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); if (sc->dtmap) bus_dmamap_unload(sc->dmat, sc->dtmap); if (sc->dtbl) bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); if (sc->chan_dmat) bus_dma_tag_destroy(sc->chan_dmat); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->ich_lock) snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return (ENXIO); } static int ich_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return (r); sc = pcm_getdevinfo(dev); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); bus_dmamap_unload(sc->dmat, sc->dtmap); bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); bus_dma_tag_destroy(sc->chan_dmat); bus_dma_tag_destroy(sc->dmat); snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return (0); } static void ich_pci_codec_reset(struct sc_info *sc) { int i; uint32_t control; control = ich_rd(sc, ICH_REG_GLOB_CNT, 4); control &= ~(ICH_GLOB_CTL_SHUT); control |= (control & ICH_GLOB_CTL_COLD) ? ICH_GLOB_CTL_WARM : ICH_GLOB_CTL_COLD; ich_wr(sc, ICH_REG_GLOB_CNT, control, 4); for (i = 500000; i; i--) { if (ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_PCR) break; /* or ICH_SCR? */ DELAY(1); } if (i <= 0) printf("%s: time out\n", __func__); } static int ich_pci_suspend(device_t dev) { struct sc_info *sc; int i; sc = pcm_getdevinfo(dev); ICH_LOCK(sc); for (i = 0 ; i < 3; i++) { sc->ch[i].run_save = sc->ch[i].run; if (sc->ch[i].run) { ICH_UNLOCK(sc); ichchan_trigger(0, &sc->ch[i], PCMTRIG_ABORT); ICH_LOCK(sc); } } ICH_UNLOCK(sc); return (0); } static int ich_pci_resume(device_t dev) { struct sc_info *sc; int i; sc = pcm_getdevinfo(dev); if (sc->regtype == SYS_RES_IOPORT) pci_enable_io(dev, SYS_RES_IOPORT); else pci_enable_io(dev, SYS_RES_MEMORY); pci_enable_busmaster(dev); ICH_LOCK(sc); /* Reinit audio device */ if (ich_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); ICH_UNLOCK(sc); return (ENXIO); } /* Reinit mixer */ ich_pci_codec_reset(sc); ICH_UNLOCK(sc); ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } /* Re-start DMA engines */ for (i = 0 ; i < 3; i++) { struct sc_chinfo *ch = &sc->ch[i]; if (sc->ch[i].run_save) { ichchan_setblocksize(0, ch, ch->blksz); ichchan_setspeed(0, ch, ch->spd); ichchan_trigger(0, ch, PCMTRIG_START); } } return (0); } static device_method_t ich_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ich_pci_probe), DEVMETHOD(device_attach, ich_pci_attach), DEVMETHOD(device_detach, ich_pci_detach), DEVMETHOD(device_suspend, ich_pci_suspend), DEVMETHOD(device_resume, ich_pci_resume), { 0, 0 } }; static driver_t ich_driver = { "pcm", ich_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_ich, pci, ich_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ich, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_ich, 1); Index: head/sys/dev/sound/pci/maestro3.c =================================================================== --- head/sys/dev/sound/pci/maestro3.c (revision 170520) +++ head/sys/dev/sound/pci/maestro3.c (revision 170521) @@ -1,1744 +1,1750 @@ /*- * 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. * , http://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 */ #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ 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) 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 int m3_pchan_setspeed(kobj_t, void *, u_int32_t); static int 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 int m3_rchan_setspeed(kobj_t, void *, u_int32_t); static int 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 int 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), { 0, 0 } }; AC97_DECLARE(m3_codec); /* -------------------------------------------------------------------- */ /* channel descriptors */ static u_int32_t m3_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 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), { 0, 0 } }; CHANNEL_DECLARE(m3_pch); static u_int32_t m3_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 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), { 0, 0 } }; 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 int 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; 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 = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_pchan_init chn_allocbuf failed\n"); return (NULL); } M3_LOCK(sc); ch->bufsize = sndbuf_getsize(ch->buffer); /* host dma buffer pointers */ bus_addr = sndbuf_getbufaddr(ch->buffer); if (bus_addr & 3) { device_printf(sc->dev, "m3_pchan_init unaligned bus_addr\n"); bus_addr = (bus_addr + 4) & ~3; } m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_ADDRL, LO(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_ADDRH, HI(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_END_PLUS_1L, LO(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_END_PLUS_1H, HI(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL, LO(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH, HI(bus_addr)); /* dsp buffers */ m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_BEGIN, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_END_PLUS_1, dsp_in_buf + dsp_in_size/2); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_HEAD, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_TAIL, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_BEGIN, dsp_out_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_END_PLUS_1, dsp_out_buf + dsp_out_size/2); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_HEAD, dsp_out_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_TAIL, dsp_out_buf); /* some per client initializers */ m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 12, ch->dac_data + 40 + 8); m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 19, 0x400 + MINISRC_COEF_LOC); /* enable or disable low pass filter? (0xff if rate> 45000) */ m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 22, 0); /* tell it which way dma is going? */ m3_wr_assp_data(sc, ch->dac_data + CDATA_DMA_CONTROL, DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); /* set an armload of static initializers */ for(i = 0 ; i < (sizeof(pv) / sizeof(pv[0])) ; i++) { m3_wr_assp_data(sc, ch->dac_data + pv[i].addr, pv[i].val); } /* put us in the packed task lists */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt + sc->rch_cnt), ch->dac_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt + sc->rch_cnt), ch->dac_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_MIXER_XFER0 + sc->pch_cnt, ch->dac_data >> DP_SHIFT_COUNT); /* gotta start before stop */ m3_pchan_trigger_locked(NULL, ch, PCMTRIG_START); /* silence noise on load */ m3_pchan_trigger_locked(NULL, ch, PCMTRIG_STOP); sc->pch_cnt++; M3_UNLOCK(sc); return (ch); } static int m3_pchan_free(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_free(dac=%d)\n", ch->dac_idx)); /* * should remove this exact instance from the packed lists, but all * are released at once (and in a stopped state) so this is ok. */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt - 1) + sc->rch_cnt, 0); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt - 1) + sc->rch_cnt, 0); m3_wr_assp_data(sc, KDATA_MIXER_XFER0 + (sc->pch_cnt-1), 0); sc->pch_cnt--; M3_UNLOCK(sc); return (0); } static int m3_pchan_setformat(kobj_t kobj, void *chdata, u_int32_t format) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_setformat(dac=%d, format=0x%x{%s-%s})\n", ch->dac_idx, format, format & (AFMT_U8|AFMT_S8) ? "8bit":"16bit", format & AFMT_STEREO ? "STEREO":"MONO")); /* mono word */ data = (format & AFMT_STEREO) ? 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 int 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 int m3_pchan_setblocksize(kobj_t kobj, void *chdata, u_int32_t blocksize) { struct sc_pchinfo *ch = chdata; M3_DEBUG(CHANGE, ("m3_pchan_setblocksize(dac=%d, blocksize=%d)\n", ch->dac_idx, blocksize)); return (sndbuf_getblksz(ch->buffer)); } static int m3_pchan_trigger(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; int ret; + if (!PCMTRIG_COMMON(go)) + return (0); + M3_LOCK(sc); ret = m3_pchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); return (ret); } static int m3_chan_active(struct sc_info *sc) { int i, ret; ret = 0; for (i = 0; i < sc->pch_cnt; i++) ret += sc->pch[i].active; for (i = 0; i < sc->rch_cnt; i++) ret += sc->rch[i].active; return (ret); } static int m3_pchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK_ASSERT(sc); M3_DEBUG(go == PCMTRIG_START ? CHANGE : go == PCMTRIG_STOP ? CHANGE : go == PCMTRIG_ABORT ? CHANGE : CALL, ("m3_pchan_trigger(dac=%d, go=0x%x{%s})\n", ch->dac_idx, go, go == PCMTRIG_START ? "PCMTRIG_START" : go == PCMTRIG_STOP ? "PCMTRIG_STOP" : go == PCMTRIG_ABORT ? "PCMTRIG_ABORT" : "ignore")); switch(go) { case PCMTRIG_START: if (ch->active) { return 0; } ch->active = 1; ch->ptr = 0; ch->prevptr = 0; sc->pch_active_cnt++; /*[[inc_timer_users]]*/ if (m3_chan_active(sc) == 1) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 1); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, sc->pch_active_cnt); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (ch->active == 0) { return 0; } ch->active = 0; sc->pch_active_cnt--; /* XXX should the channel be drained? */ /*[[dec_timer_users]]*/ if (m3_chan_active(sc) == 0) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, sc->pch_active_cnt); break; case PCMTRIG_EMLDMAWR: /* got play irq, transfer next buffer - ignore if using dma */ case PCMTRIG_EMLDMARD: /* got rec irq, transfer next buffer - ignore if using dma */ default: break; } return 0; } static u_int32_t m3_pchan_getptr_internal(struct sc_pchinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; bus_base = sndbuf_getbufaddr(ch->buffer); hi = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL); bus_crnt = lo | (hi << 16); M3_DEBUG(CALL, ("m3_pchan_getptr(dac=%d) result=%d\n", ch->dac_idx, bus_crnt - bus_base)); return (bus_crnt - bus_base); /* current byte offset of channel */ } static u_int32_t m3_pchan_getptr(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t ptr; M3_LOCK(sc); ptr = ch->ptr; M3_UNLOCK(sc); return (ptr); } static struct pcmchan_caps * m3_pchan_getcaps(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; M3_DEBUG(CALL, ("m3_pchan_getcaps(dac=%d)\n", ch->dac_idx)); return &m3_playcaps; } /* -------------------------------------------------------------------- */ /* rec channel interface */ static void * m3_rchan_init(kobj_t kobj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; u_int32_t bus_addr, i; int idx, data_bytes, adc_data; int dsp_in_size, dsp_out_size, dsp_in_buf, dsp_out_buf; 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 = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_rchan_init chn_allocbuf failed\n"); return (NULL); } M3_LOCK(sc); ch->bufsize = sndbuf_getsize(ch->buffer); /* host dma buffer pointers */ bus_addr = sndbuf_getbufaddr(ch->buffer); if (bus_addr & 3) { device_printf(sc->dev, "m3_rchan_init unaligned bus_addr\n"); bus_addr = (bus_addr + 4) & ~3; } m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_ADDRL, LO(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_ADDRH, HI(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_END_PLUS_1L, LO(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_END_PLUS_1H, HI(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL, LO(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH, HI(bus_addr)); /* dsp buffers */ m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_BEGIN, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_END_PLUS_1, dsp_in_buf + dsp_in_size/2); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_HEAD, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_TAIL, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_BEGIN, dsp_out_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_END_PLUS_1, dsp_out_buf + dsp_out_size/2); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_HEAD, dsp_out_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_TAIL, dsp_out_buf); /* some per client initializers */ m3_wr_assp_data(sc, ch->adc_data + SRC3_DIRECTION_OFFSET + 12, ch->adc_data + 40 + 8); m3_wr_assp_data(sc, ch->adc_data + CDATA_DMA_CONTROL, DMACONTROL_DIRECTION + DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); /* set an armload of static initializers */ for(i = 0 ; i < (sizeof(rv) / sizeof(rv[0])) ; i++) { m3_wr_assp_data(sc, ch->adc_data + rv[i].addr, rv[i].val); } /* put us in the packed task lists */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt + sc->rch_cnt), ch->adc_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt + sc->rch_cnt), ch->adc_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_ADC1_XFER0 + sc->rch_cnt, ch->adc_data >> DP_SHIFT_COUNT); /* gotta start before stop */ m3_rchan_trigger_locked(NULL, ch, PCMTRIG_START); /* stop on init */ m3_rchan_trigger_locked(NULL, ch, PCMTRIG_STOP); sc->rch_cnt++; M3_UNLOCK(sc); return (ch); } static int m3_rchan_free(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_free(adc=%d)\n", ch->adc_idx)); /* * should remove this exact instance from the packed lists, but all * are released at once (and in a stopped state) so this is ok. */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->rch_cnt - 1) + sc->pch_cnt, 0); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->rch_cnt - 1) + sc->pch_cnt, 0); m3_wr_assp_data(sc, KDATA_ADC1_XFER0 + (sc->rch_cnt - 1), 0); sc->rch_cnt--; M3_UNLOCK(sc); return (0); } static int m3_rchan_setformat(kobj_t kobj, void *chdata, u_int32_t format) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_setformat(dac=%d, format=0x%x{%s-%s})\n", ch->adc_idx, format, format & (AFMT_U8|AFMT_S8) ? "8bit":"16bit", format & AFMT_STEREO ? "STEREO":"MONO")); /* mono word */ data = (format & AFMT_STEREO) ? 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 int 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 int m3_rchan_setblocksize(kobj_t kobj, void *chdata, u_int32_t blocksize) { struct sc_rchinfo *ch = chdata; M3_DEBUG(CHANGE, ("m3_rchan_setblocksize(adc=%d, blocksize=%d)\n", ch->adc_idx, blocksize)); return (sndbuf_getblksz(ch->buffer)); } static int m3_rchan_trigger(kobj_t kobj, void *chdata, int go) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; int ret; + + if (!PCMTRIG_COMMON(go)) + return (0); M3_LOCK(sc); ret = m3_rchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); return (ret); } static int m3_rchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK_ASSERT(sc); M3_DEBUG(go == PCMTRIG_START ? CHANGE : go == PCMTRIG_STOP ? CHANGE : go == PCMTRIG_ABORT ? CHANGE : CALL, ("m3_rchan_trigger(adc=%d, go=0x%x{%s})\n", ch->adc_idx, go, go == PCMTRIG_START ? "PCMTRIG_START" : go == PCMTRIG_STOP ? "PCMTRIG_STOP" : go == PCMTRIG_ABORT ? "PCMTRIG_ABORT" : "ignore")); switch(go) { case PCMTRIG_START: if (ch->active) { return 0; } ch->active = 1; ch->ptr = 0; ch->prevptr = 0; /*[[inc_timer_users]]*/ if (m3_chan_active(sc) == 1) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 1); m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 1); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (ch->active == 0) { return 0; } ch->active = 0; /*[[dec_timer_users]]*/ if (m3_chan_active(sc) == 0) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 0); break; case PCMTRIG_EMLDMAWR: /* got play irq, transfer next buffer - ignore if using dma */ case PCMTRIG_EMLDMARD: /* got rec irq, transfer next buffer - ignore if using dma */ default: break; } return 0; } static u_int32_t m3_rchan_getptr_internal(struct sc_rchinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; bus_base = sndbuf_getbufaddr(ch->buffer); hi = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL); bus_crnt = lo | (hi << 16); M3_DEBUG(CALL, ("m3_rchan_getptr(adc=%d) result=%d\n", ch->adc_idx, bus_crnt - bus_base)); return (bus_crnt - bus_base); /* current byte offset of channel */ } static u_int32_t m3_rchan_getptr(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t ptr; M3_LOCK(sc); ptr = ch->ptr; M3_UNLOCK(sc); return (ptr); } static struct pcmchan_caps * m3_rchan_getcaps(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; M3_DEBUG(CALL, ("m3_rchan_getcaps(adc=%d)\n", ch->adc_idx)); return &m3_reccaps; } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void m3_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_pchinfo *pch; struct sc_rchinfo *rch; u_int32_t status, ctl, i, delta; M3_DEBUG(INTR, ("m3_intr\n")); M3_LOCK(sc); status = m3_rd_1(sc, HOST_INT_STATUS); if (!status) { M3_UNLOCK(sc); return; } m3_wr_1(sc, HOST_INT_STATUS, 0xff); /* ack the int? */ if (status & HV_INT_PENDING) { u_int8_t event; event = m3_rd_1(sc, HW_VOL_COUNTER_MASTER); switch (event) { case 0x99: mixer_hwvol_mute(sc->dev); break; case 0xaa: mixer_hwvol_step(sc->dev, 1, 1); break; case 0x66: mixer_hwvol_step(sc->dev, -1, -1); break; case 0x88: break; default: device_printf(sc->dev, "Unknown HWVOL event\n"); } m3_wr_1(sc, HW_VOL_COUNTER_MASTER, 0x88); } if (status & ASSP_INT_PENDING) { ctl = m3_rd_1(sc, ASSP_CONTROL_B); if (!(ctl & STOP_ASSP_CLOCK)) { ctl = m3_rd_1(sc, ASSP_HOST_INT_STATUS); if (ctl & DSP2HOST_REQ_TIMER) { m3_wr_1(sc, ASSP_HOST_INT_STATUS, DSP2HOST_REQ_TIMER); /*[[ess_update_ptr]]*/ goto m3_handle_channel_intr; } } } goto m3_handle_channel_intr_out; m3_handle_channel_intr: for (i=0 ; ipch_cnt ; i++) { pch = &sc->pch[i]; if (pch->active) { pch->ptr = m3_pchan_getptr_internal(pch); delta = pch->bufsize + pch->ptr - pch->prevptr; delta %= pch->bufsize; if (delta < sndbuf_getblksz(pch->buffer)) continue; pch->prevptr = pch->ptr; M3_UNLOCK(sc); chn_intr(pch->channel); M3_LOCK(sc); } } for (i=0 ; irch_cnt ; i++) { rch = &sc->rch[i]; if (rch->active) { rch->ptr = m3_rchan_getptr_internal(rch); delta = rch->bufsize + rch->ptr - rch->prevptr; delta %= rch->bufsize; if (delta < sndbuf_getblksz(rch->buffer)) continue; rch->prevptr = rch->ptr; M3_UNLOCK(sc); chn_intr(rch->channel); M3_LOCK(sc); } } m3_handle_channel_intr_out: M3_UNLOCK(sc); } /* -------------------------------------------------------------------- */ /* stuff */ static int m3_power(struct sc_info *sc, int state) { u_int32_t data; M3_DEBUG(CHANGE, ("m3_power(%d)\n", state)); M3_LOCK_ASSERT(sc); data = pci_read_config(sc->dev, 0x34, 1); if (pci_read_config(sc->dev, data, 1) == 1) { pci_write_config(sc->dev, data + 4, state, 1); } return 0; } static int m3_init(struct sc_info *sc) { u_int32_t data, i, size; u_int8_t reset_state; M3_LOCK_ASSERT(sc); M3_DEBUG(CHANGE, ("m3_init\n")); /* diable legacy emulations. */ data = pci_read_config(sc->dev, PCI_LEGACY_AUDIO_CTRL, 2); data |= DISABLE_LEGACY; pci_write_config(sc->dev, PCI_LEGACY_AUDIO_CTRL, data, 2); m3_config(sc); reset_state = m3_assp_halt(sc); m3_codec_reset(sc); /* [m3_assp_init] */ /* zero kernel data */ size = REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA; for(i = 0 ; i < size / 2 ; i++) { m3_wr_assp_data(sc, KDATA_BASE_ADDR + i, 0); } /* zero mixer data? */ size = REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA; for(i = 0 ; i < size / 2 ; i++) { m3_wr_assp_data(sc, KDATA_BASE_ADDR2 + i, 0); } /* init dma pointer */ m3_wr_assp_data(sc, KDATA_CURRENT_DMA, KDATA_DMA_XFER0); /* write kernel into code memory */ size = sizeof(assp_kernel_image); for(i = 0 ; i < size / 2; i++) { m3_wr_assp_code(sc, REV_B_CODE_MEMORY_BEGIN + i, assp_kernel_image[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(assp_minisrc_image); for(i = 0 ; i < size / 2; i++) { m3_wr_assp_code(sc, 0x400 + i, assp_minisrc_image[i]); } /* write the coefficients for the low pass filter? */ size = sizeof(minisrc_lpf_image); for(i = 0; i < size / 2 ; i++) { m3_wr_assp_code(sc,0x400 + MINISRC_COEF_LOC + i, minisrc_lpf_image[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, ARB_VOLUME); m3_wr_assp_data(sc, KDATA_DAC_RIGHT_VOLUME, ARB_VOLUME); 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; u_int32_t data; char status[SND_STATUSLEN]; struct m3_card_type *card; int i, len, dacn, adcn; M3_DEBUG(CALL, ("m3_pci_attach\n")); if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } 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; data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); } if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, m3_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, M3_BUFSIZE_MIN, M3_BUFSIZE_DEFAULT, M3_BUFSIZE_MAX); if (bus_dma_tag_create( bus_get_dma_tag(dev), /* parent */ 2, 0, /* alignment, boundary */ M3_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ sc->bufsz, /* maxsize */ 1, /* nsegments */ 0x3ffff, /* maxsegz */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } M3_LOCK(sc); m3_power(sc, 0); /* power up */ /* init chip */ i = m3_init(sc); M3_UNLOCK(sc); if (i == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, m3_codec); if (codec == NULL) { device_printf(dev, "ac97_create error\n"); goto bad; } if (mixer_init(dev, ac97_getmixerclass(), codec)) { device_printf(dev, "mixer_init error\n"); goto bad; } m3_enable_ints(sc); if (pcm_register(dev, sc, dacn, adcn)) { device_printf(dev, "pcm_register error\n"); goto bad; } for (i=0 ; iregtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(sc->reg), rman_get_start(sc->irq), PCM_KLDSTRING(snd_maestro3)); if (pcm_setstatus(dev, status)) { device_printf(dev, "attach: pcm_setstatus error\n"); goto bad; } mixer_hwvol_init(dev); /* Create the buffer for saving the card state during suspend */ len = sizeof(u_int16_t) * (REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH); sc->savemem = (u_int16_t*)malloc(len, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->savemem == NULL) { device_printf(dev, "Failed to create suspend buffer\n"); goto bad; } 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, pcm_devclass, 0, 0); MODULE_DEPEND(snd_maestro3, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_maestro3, 1); Index: head/sys/dev/sound/pci/neomagic.c =================================================================== --- head/sys/dev/sound/pci/neomagic.c (revision 170520) +++ head/sys/dev/sound/pci/neomagic.c (revision 170521) @@ -1,831 +1,831 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Derived from the public domain Linux driver * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ #define NM_BUFFSIZE 16384 #define NM256AV_PCI_ID 0x800510c8 #define NM256ZX_PCI_ID 0x800610c8 struct sc_info; /* channel registers */ struct sc_chinfo { int active, spd, dir, fmt; u_int32_t blksize, wmark; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type; struct resource *reg, *irq, *buf; int regid, irqid, bufid; void *ih; u_int32_t ac97_base, ac97_status, ac97_busy; u_int32_t buftop, pbuf, rbuf, cbuf, acbuf; u_int32_t playint, recint, misc1int, misc2int; u_int32_t irsz, badintr; struct sc_chinfo pch, rch; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int nm_loadcoeff(struct sc_info *sc, int dir, int num); static int nm_setch(struct sc_chinfo *ch); static int nm_init(struct sc_info *); static void nm_intr(void *); /* talk to the card */ static u_int32_t nm_rd(struct sc_info *, int, int); static void nm_wr(struct sc_info *, int, u_int32_t, int); static u_int32_t nm_rdbuf(struct sc_info *, int, int); static void nm_wrbuf(struct sc_info *, int, u_int32_t, int); static u_int32_t badcards[] = { 0x0007103c, 0x008f1028, 0x00dd1014, 0x8005110a, }; #define NUM_BADCARDS (sizeof(badcards) / sizeof(u_int32_t)) /* The actual rates supported by the card. */ static int samplerates[9] = { 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 99999999 }; /* -------------------------------------------------------------------- */ static u_int32_t nm_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps nm_caps = {4000, 48000, nm_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t nm_rd(struct sc_info *sc, int regno, int size) { bus_space_tag_t st = rman_get_bustag(sc->reg); bus_space_handle_t sh = rman_get_bushandle(sc->reg); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void nm_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(sc->reg); bus_space_handle_t sh = rman_get_bushandle(sc->reg); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static u_int32_t nm_rdbuf(struct sc_info *sc, int regno, int size) { bus_space_tag_t st = rman_get_bustag(sc->buf); bus_space_handle_t sh = rman_get_bushandle(sc->buf); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void nm_wrbuf(struct sc_info *sc, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(sc->buf); bus_space_handle_t sh = rman_get_bushandle(sc->buf); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int nm_waitcd(struct sc_info *sc) { int cnt = 10; int fail = 1; while (cnt-- > 0) { if (nm_rd(sc, sc->ac97_status, 2) & sc->ac97_busy) { DELAY(100); } else { fail = 0; break; } } return (fail); } static u_int32_t nm_initcd(kobj_t obj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; nm_wr(sc, 0x6c0, 0x01, 1); #if 0 /* * The following code-line may cause a hang for some chipsets, see * PR 56617. * In case of a bugreport without this line have a look at the PR and * conditionize the code-line based upon the specific version of * the chip. */ nm_wr(sc, 0x6cc, 0x87, 1); #endif nm_wr(sc, 0x6cc, 0x80, 1); nm_wr(sc, 0x6cc, 0x00, 1); return 1; } static int nm_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t x; if (!nm_waitcd(sc)) { x = nm_rd(sc, sc->ac97_base + regno, 2); DELAY(1000); return x; } else { device_printf(sc->dev, "ac97 codec not ready\n"); return -1; } } static int nm_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; int cnt = 3; if (!nm_waitcd(sc)) { while (cnt-- > 0) { nm_wr(sc, sc->ac97_base + regno, data, 2); if (!nm_waitcd(sc)) { DELAY(1000); return 0; } } } device_printf(sc->dev, "ac97 codec not ready\n"); return -1; } static kobj_method_t nm_ac97_methods[] = { KOBJMETHOD(ac97_init, nm_initcd), KOBJMETHOD(ac97_read, nm_rdcd), KOBJMETHOD(ac97_write, nm_wrcd), { 0, 0 } }; AC97_DECLARE(nm_ac97); /* -------------------------------------------------------------------- */ static void nm_ackint(struct sc_info *sc, u_int32_t num) { if (sc->type == NM256AV_PCI_ID) { nm_wr(sc, NM_INT_REG, num << 1, 2); } else if (sc->type == NM256ZX_PCI_ID) { nm_wr(sc, NM_INT_REG, num, 4); } } static int nm_loadcoeff(struct sc_info *sc, int dir, int num) { int ofs, sz, i; u_int32_t addr; addr = (dir == PCMDIR_PLAY)? 0x01c : 0x21c; if (dir == PCMDIR_REC) num += 8; sz = coefficientSizes[num]; ofs = 0; while (num-- > 0) ofs+= coefficientSizes[num]; for (i = 0; i < sz; i++) nm_wrbuf(sc, sc->cbuf + i, coefficients[ofs + i], 1); nm_wr(sc, addr, sc->cbuf, 4); if (dir == PCMDIR_PLAY) sz--; nm_wr(sc, addr + 4, sc->cbuf + sz, 4); return 0; } static int nm_setch(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t base; u_int8_t x; for (x = 0; x < 8; x++) if (ch->spd < (samplerates[x] + samplerates[x + 1]) / 2) break; if (x == 8) return 1; ch->spd = samplerates[x]; nm_loadcoeff(sc, ch->dir, x); x <<= 4; x &= NM_RATE_MASK; if (ch->fmt & AFMT_16BIT) x |= NM_RATE_BITS_16; if (ch->fmt & AFMT_STEREO) x |= NM_RATE_STEREO; base = (ch->dir == PCMDIR_PLAY)? NM_PLAYBACK_REG_OFFSET : NM_RECORD_REG_OFFSET; nm_wr(sc, base + NM_RATE_REG_OFFSET, x, 1); return 0; } /* channel interface */ static void * nmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; u_int32_t chnbuf; chnbuf = (dir == PCMDIR_PLAY)? sc->pbuf : sc->rbuf; ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; ch->active = 0; ch->blksize = 0; ch->wmark = 0; ch->buffer = b; sndbuf_setup(ch->buffer, (u_int8_t *)rman_get_virtual(sc->buf) + chnbuf, NM_BUFFSIZE); if (bootverbose) device_printf(sc->dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? "play" : "rec", sndbuf_getbuf(ch->buffer)); ch->parent = sc; ch->channel = c; ch->dir = dir; return ch; } static int nmchan_free(kobj_t obj, void *data) { return 0; } static int nmchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->fmt = format; return nm_setch(ch); } static int nmchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; ch->spd = speed; return nm_setch(ch)? 0 : ch->spd; } static int nmchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; ch->blksize = blocksize; return blocksize; } static int nmchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int ssz; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ssz = (ch->fmt & AFMT_16BIT)? 2 : 1; if (ch->fmt & AFMT_STEREO) ssz <<= 1; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { ch->active = 1; ch->wmark = ch->blksize; nm_wr(sc, NM_PBUFFER_START, sc->pbuf, 4); nm_wr(sc, NM_PBUFFER_END, sc->pbuf + NM_BUFFSIZE - ssz, 4); nm_wr(sc, NM_PBUFFER_CURRP, sc->pbuf, 4); nm_wr(sc, NM_PBUFFER_WMARK, sc->pbuf + ch->wmark, 4); nm_wr(sc, NM_PLAYBACK_ENABLE_REG, NM_PLAYBACK_FREERUN | NM_PLAYBACK_ENABLE_FLAG, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, 0, 2); } else { ch->active = 0; nm_wr(sc, NM_PLAYBACK_ENABLE_REG, 0, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, NM_AUDIO_MUTE_BOTH, 2); } } else { if (go == PCMTRIG_START) { ch->active = 1; ch->wmark = ch->blksize; nm_wr(sc, NM_RECORD_ENABLE_REG, NM_RECORD_FREERUN | NM_RECORD_ENABLE_FLAG, 1); nm_wr(sc, NM_RBUFFER_START, sc->rbuf, 4); nm_wr(sc, NM_RBUFFER_END, sc->rbuf + NM_BUFFSIZE, 4); nm_wr(sc, NM_RBUFFER_CURRP, sc->rbuf, 4); nm_wr(sc, NM_RBUFFER_WMARK, sc->rbuf + ch->wmark, 4); } else { ch->active = 0; nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } } return 0; } static int nmchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (ch->dir == PCMDIR_PLAY) return nm_rd(sc, NM_PBUFFER_CURRP, 4) - sc->pbuf; else return nm_rd(sc, NM_RBUFFER_CURRP, 4) - sc->rbuf; } static struct pcmchan_caps * nmchan_getcaps(kobj_t obj, void *data) { return &nm_caps; } static kobj_method_t nmchan_methods[] = { KOBJMETHOD(channel_init, nmchan_init), KOBJMETHOD(channel_free, nmchan_free), KOBJMETHOD(channel_setformat, nmchan_setformat), KOBJMETHOD(channel_setspeed, nmchan_setspeed), KOBJMETHOD(channel_setblocksize, nmchan_setblocksize), KOBJMETHOD(channel_trigger, nmchan_trigger), KOBJMETHOD(channel_getptr, nmchan_getptr), KOBJMETHOD(channel_getcaps, nmchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(nmchan); /* The interrupt handler */ static void nm_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; int status, x; status = nm_rd(sc, NM_INT_REG, sc->irsz); if (status == 0) return; if (status & sc->playint) { status &= ~sc->playint; sc->pch.wmark += sc->pch.blksize; sc->pch.wmark %= NM_BUFFSIZE; nm_wr(sc, NM_PBUFFER_WMARK, sc->pbuf + sc->pch.wmark, 4); nm_ackint(sc, sc->playint); chn_intr(sc->pch.channel); } if (status & sc->recint) { status &= ~sc->recint; sc->rch.wmark += sc->rch.blksize; sc->rch.wmark %= NM_BUFFSIZE; nm_wr(sc, NM_RBUFFER_WMARK, sc->rbuf + sc->rch.wmark, 4); nm_ackint(sc, sc->recint); chn_intr(sc->rch.channel); } if (status & sc->misc1int) { status &= ~sc->misc1int; nm_ackint(sc, sc->misc1int); x = nm_rd(sc, 0x400, 1); nm_wr(sc, 0x400, x | 2, 1); device_printf(sc->dev, "misc int 1\n"); } if (status & sc->misc2int) { status &= ~sc->misc2int; nm_ackint(sc, sc->misc2int); x = nm_rd(sc, 0x400, 1); nm_wr(sc, 0x400, x & ~2, 1); device_printf(sc->dev, "misc int 2\n"); } if (status) { nm_ackint(sc, status); device_printf(sc->dev, "unknown int\n"); } } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int nm_init(struct sc_info *sc) { u_int32_t ofs, i; if (sc->type == NM256AV_PCI_ID) { sc->ac97_base = NM_MIXER_OFFSET; sc->ac97_status = NM_MIXER_STATUS_OFFSET; sc->ac97_busy = NM_MIXER_READY_MASK; sc->buftop = 2560 * 1024; sc->irsz = 2; sc->playint = NM_PLAYBACK_INT; sc->recint = NM_RECORD_INT; sc->misc1int = NM_MISC_INT_1; sc->misc2int = NM_MISC_INT_2; } else if (sc->type == NM256ZX_PCI_ID) { sc->ac97_base = NM_MIXER_OFFSET; sc->ac97_status = NM2_MIXER_STATUS_OFFSET; sc->ac97_busy = NM2_MIXER_READY_MASK; sc->buftop = (nm_rd(sc, 0xa0b, 2)? 6144 : 4096) * 1024; sc->irsz = 4; sc->playint = NM2_PLAYBACK_INT; sc->recint = NM2_RECORD_INT; sc->misc1int = NM2_MISC_INT_1; sc->misc2int = NM2_MISC_INT_2; } else return -1; sc->badintr = 0; ofs = sc->buftop - 0x0400; sc->buftop -= 0x1400; if (bootverbose) device_printf(sc->dev, "buftop is 0x%08x\n", sc->buftop); if ((nm_rdbuf(sc, ofs, 4) & NM_SIG_MASK) == NM_SIGNATURE) { i = nm_rdbuf(sc, ofs + 4, 4); if (i != 0 && i != 0xffffffff) { if (bootverbose) device_printf(sc->dev, "buftop is changed to 0x%08x\n", i); sc->buftop = i; } } sc->cbuf = sc->buftop - NM_MAX_COEFFICIENT; sc->rbuf = sc->cbuf - NM_BUFFSIZE; sc->pbuf = sc->rbuf - NM_BUFFSIZE; sc->acbuf = sc->pbuf - (NM_TOTAL_COEFF_COUNT * 4); nm_wr(sc, 0, 0x11, 1); nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); nm_wr(sc, 0x214, 0, 2); return 0; } static int nm_pci_probe(device_t dev) { struct sc_info *sc = NULL; char *s = NULL; u_int32_t subdev, i, data; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case NM256AV_PCI_ID: i = 0; while ((i < NUM_BADCARDS) && (badcards[i] != subdev)) i++; /* Try to catch other non-ac97 cards */ if (i == NUM_BADCARDS) { if (!(sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO))) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } data = pci_read_config(dev, PCIR_COMMAND, 2); pci_write_config(dev, PCIR_COMMAND, data | PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN, 2); sc->regid = PCIR_BAR(1); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to map register space\n"); pci_write_config(dev, PCIR_COMMAND, data, 2); free(sc, M_DEVBUF); return ENXIO; } /* * My Panasonic CF-M2EV needs resetting device * before checking mixer is present or not. * t.ichinoseki@nifty.com. */ nm_wr(sc, 0, 0x11, 1); /* reset device */ if ((nm_rd(sc, NM_MIXER_PRESENCE, 2) & NM_PRESENCE_MASK) != NM_PRESENCE_VALUE) { i = 0; /* non-ac97 card, but not listed */ DEB(device_printf(dev, "subdev = 0x%x - badcard?\n", subdev)); } pci_write_config(dev, PCIR_COMMAND, data, 2); bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); free(sc, M_DEVBUF); } if (i == NUM_BADCARDS) s = "NeoMagic 256AV"; DEB(else) DEB(device_printf(dev, "this is a non-ac97 NM256AV, not attaching\n")); break; case NM256ZX_PCI_ID: s = "NeoMagic 256ZX"; break; } if (s) device_set_desc(dev, s); return s? 0 : ENXIO; } static int nm_pci_attach(device_t dev) { u_int32_t data; struct sc_info *sc; struct ac97_info *codec = 0; char status[SND_STATUSLEN]; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->dev = dev; sc->type = pci_get_devid(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); sc->bufid = PCIR_BAR(0); sc->buf = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->bufid, RF_ACTIVE); sc->regid = PCIR_BAR(1); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->buf || !sc->reg) { device_printf(dev, "unable to map register space\n"); goto bad; } if (nm_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, nm_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, nm_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at memory 0x%lx, 0x%lx irq %ld %s", rman_get_start(sc->buf), rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_neomagic)); if (pcm_register(dev, sc, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &nmchan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &nmchan_class, sc); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->buf) bus_release_resource(dev, SYS_RES_MEMORY, sc->bufid, sc->buf); if (sc->reg) bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); free(sc, M_DEVBUF); return ENXIO; } static int nm_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_MEMORY, sc->bufid, sc->buf); bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); free(sc, M_DEVBUF); return 0; } static int nm_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* stop playing */ if (sc->pch.active) { nm_wr(sc, NM_PLAYBACK_ENABLE_REG, 0, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, NM_AUDIO_MUTE_BOTH, 2); } /* stop recording */ if (sc->rch.active) { nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } return 0; } static int nm_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* * Reinit audio device. * Don't call nm_init(). It would change buftop if X ran or * is running. This makes playing and recording buffer address * shift but these buffers of channel layer are not changed. * As a result of this inconsistency, periodic noise will be * generated while playing. */ nm_wr(sc, 0, 0x11, 1); nm_wr(sc, 0x214, 0, 2); /* Reinit mixer */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restart playing */ if (sc->pch.active) { nm_wr(sc, NM_PLAYBACK_ENABLE_REG, NM_PLAYBACK_FREERUN | NM_PLAYBACK_ENABLE_FLAG, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, 0, 2); } /* restart recording */ if (sc->rch.active) { nm_wr(sc, NM_RECORD_ENABLE_REG, NM_RECORD_FREERUN | NM_RECORD_ENABLE_FLAG, 1); } return 0; } static device_method_t nm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nm_pci_probe), DEVMETHOD(device_attach, nm_pci_attach), DEVMETHOD(device_detach, nm_pci_detach), DEVMETHOD(device_suspend, nm_pci_suspend), DEVMETHOD(device_resume, nm_pci_resume), { 0, 0 } }; static driver_t nm_driver = { "pcm", nm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_neomagic, pci, nm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_neomagic, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_neomagic, 1); Index: head/sys/dev/sound/pci/solo.c =================================================================== --- head/sys/dev/sound/pci/solo.c (revision 170520) +++ head/sys/dev/sound/pci/solo.c (revision 170521) @@ -1,1116 +1,1117 @@ /*- * Copyright (c) 1999 Cameron Grant * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define SOLO_DEFAULT_BUFSZ 16384 #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ #define ESS18XX_DUPLEX 1 /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED /* 1 = INTR_MPSAFE, 0 = GIANT */ #define ESS18XX_MPSAFE 1 static u_int32_t ess_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, 0 }; static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_playfmt, 0}; /* * Recording output is byte-swapped */ static u_int32_t ess_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_BE, AFMT_STEREO | AFMT_S16_BE, AFMT_U16_BE, AFMT_STEREO | AFMT_U16_BE, 0 }; static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_recfmt, 0}; struct ess_info; struct ess_chinfo { struct ess_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, hwch, stopping; u_int32_t fmt, spd, blksz; }; struct ess_info { struct resource *io, *sb, *vc, *mpu, *gp; /* I/O address for the board */ struct resource *irq; void *ih; bus_dma_tag_t parent_dmat; int simplex_dir, type, dmasz[2]; unsigned int duplex:1, newspeed:1; unsigned int bufsz; struct ess_chinfo pch, rch; #if ESS18XX_MPSAFE == 1 struct mtx *lock; #endif }; #if ESS18XX_MPSAFE == 1 #define ess_lock(_ess) snd_mtxlock((_ess)->lock) #define ess_unlock(_ess) snd_mtxunlock((_ess)->lock) #define ess_lock_assert(_ess) snd_mtxassert((_ess)->lock) #else #define ess_lock(_ess) #define ess_unlock(_ess) #define ess_lock_assert(_ess) #endif static int ess_rd(struct ess_info *sc, int reg); static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); static int ess_dspready(struct ess_info *sc); static int ess_cmd(struct ess_info *sc, u_char val); static int ess_cmd1(struct ess_info *sc, u_char cmd, int val); static int ess_get_byte(struct ess_info *sc); static void ess_setmixer(struct ess_info *sc, u_int port, u_int value); static int ess_getmixer(struct ess_info *sc, u_int port); static int ess_reset_dsp(struct ess_info *sc); static int ess_write(struct ess_info *sc, u_char reg, int val); static int ess_read(struct ess_info *sc, u_char reg); static void ess_intr(void *arg); static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len); static int ess_start(struct ess_chinfo *ch); static int ess_stop(struct ess_chinfo *ch); static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir); static int ess_dmapos(struct ess_info *sc, int ch); static int ess_dmatrigger(struct ess_info *sc, int ch, int go); /* * Common code for the midi and pcm functions * * ess_cmd write a single byte to the CMD port. * ess_cmd1 write a CMD + 1 byte arg * ess_cmd2 write a CMD + 2 byte arg * ess_get_byte returns a single byte from the DSP data port * * ess_write is actually ess_cmd1 * ess_read access ext. regs via ess_cmd(0xc0, reg) followed by ess_get_byte */ static int port_rd(struct resource *port, int regno, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void port_wr(struct resource *port, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static int ess_rd(struct ess_info *sc, int reg) { return port_rd(sc->sb, reg, 1); } static void ess_wr(struct ess_info *sc, int reg, u_int8_t val) { port_wr(sc->sb, reg, val, 1); } static int ess_dspready(struct ess_info *sc) { return ((ess_rd(sc, SBDSP_STATUS) & 0x80) == 0); } static int ess_dspwr(struct ess_info *sc, u_char val) { int i; for (i = 0; i < 1000; i++) { if (ess_dspready(sc)) { ess_wr(sc, SBDSP_CMD, val); return 1; } if (i > 10) DELAY((i > 100)? 1000 : 10); } printf("ess_dspwr(0x%02x) timed out.\n", val); return 0; } static int ess_cmd(struct ess_info *sc, u_char val) { DEB(printf("ess_cmd: %x\n", val)); return ess_dspwr(sc, val); } static int ess_cmd1(struct ess_info *sc, u_char cmd, int val) { DEB(printf("ess_cmd1: %x, %x\n", cmd, val)); if (ess_dspwr(sc, cmd)) { return ess_dspwr(sc, val & 0xff); } else return 0; } static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); return val; } static int ess_get_byte(struct ess_info *sc) { int i; for (i = 1000; i > 0; i--) { if (ess_rd(sc, 0xc) & 0x40) return ess_rd(sc, DSP_READ); else DELAY(20); } return -1; } static int ess_write(struct ess_info *sc, u_char reg, int val) { return ess_cmd1(sc, reg, val); } static int ess_read(struct ess_info *sc, u_char reg) { return (ess_cmd(sc, 0xc0) && ess_cmd(sc, reg))? ess_get_byte(sc) : -1; } static int ess_reset_dsp(struct ess_info *sc) { DEB(printf("ess_reset_dsp\n")); ess_wr(sc, SBDSP_RST, 3); DELAY(100); ess_wr(sc, SBDSP_RST, 0); if (ess_get_byte(sc) != 0xAA) { DEB(printf("ess_reset_dsp failed\n")); /* rman_get_start(d->io_base))); */ return ENXIO; /* Sorry */ } ess_cmd(sc, 0xc6); return 0; } static void ess_intr(void *arg) { struct ess_info *sc = (struct ess_info *)arg; int src, pirq = 0, rirq = 0; ess_lock(sc); src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; if (src == 0) { ess_unlock(sc); return; } if (sc->duplex) { pirq = (src & sc->pch.hwch)? 1 : 0; rirq = (src & sc->rch.hwch)? 1 : 0; } else { if (sc->simplex_dir == PCMDIR_PLAY) pirq = 1; if (sc->simplex_dir == PCMDIR_REC) rirq = 1; if (!pirq && !rirq) printf("solo: IRQ neither playback nor rec!\n"); } DEB(printf("ess_intr: pirq:%d rirq:%d\n",pirq,rirq)); if (pirq) { if (sc->pch.stopping) { ess_dmatrigger(sc, sc->pch.hwch, 0); sc->pch.stopping = 0; if (sc->pch.hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } ess_unlock(sc); chn_intr(sc->pch.channel); ess_lock(sc); } if (rirq) { if (sc->rch.stopping) { ess_dmatrigger(sc, sc->rch.hwch, 0); sc->rch.stopping = 0; /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } ess_unlock(sc); chn_intr(sc->rch.channel); ess_lock(sc); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); ess_unlock(sc); } /* utility functions for ESS */ static u_int8_t ess_calcspeed8(int *spd) { int speed = *spd; u_int32_t t; if (speed > 22000) { t = (795500 + speed / 2) / speed; speed = (795500 + t / 2) / t; t = (256 - t) | 0x80; } else { t = (397700 + speed / 2) / speed; speed = (397700 + t / 2) / t; t = 128 - t; } *spd = speed; return t & 0x000000ff; } static u_int8_t ess_calcspeed9(int *spd) { int speed, s0, s1, use0; u_int8_t t0, t1; /* rate = source / (256 - divisor) */ /* divisor = 256 - (source / rate) */ speed = *spd; t0 = 128 - (793800 / speed); s0 = 793800 / (128 - t0); t1 = 128 - (768000 / speed); s1 = 768000 / (128 - t1); t1 |= 0x80; use0 = (ABS(speed - s0) < ABS(speed - s1))? 1 : 0; *spd = use0? s0 : s1; return use0? t0 : t1; } static u_int8_t ess_calcfilter(int spd) { int cutoff; /* cutoff = 7160000 / (256 - divisor) */ /* divisor = 256 - (7160000 / cutoff) */ cutoff = (spd * 9 * 82) / 20; return (256 - (7160000 / cutoff)); } static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len) { int play = (dir == PCMDIR_PLAY)? 1 : 0; int b16 = (fmt & AFMT_16BIT)? 1 : 0; int stereo = (fmt & AFMT_STEREO)? 1 : 0; int unsign = (fmt == AFMT_U8 || fmt == AFMT_U16_LE || fmt == AFMT_U16_BE)? 1 : 0; u_int8_t spdval, fmtval; DEB(printf("ess_setupch\n")); spdval = (sc->newspeed)? ess_calcspeed9(&spd) : ess_calcspeed8(&spd); sc->simplex_dir = play ? PCMDIR_PLAY : PCMDIR_REC ; if (ch == 1) { KASSERT((dir == PCMDIR_PLAY) || (dir == PCMDIR_REC), ("ess_setupch: dir1 bad")); len = -len; /* transfer length low */ ess_write(sc, 0xa4, len & 0x00ff); /* transfer length high */ ess_write(sc, 0xa5, (len & 0xff00) >> 8); /* autoinit, dma dir */ ess_write(sc, 0xb8, 0x04 | (play? 0x00 : 0x0a)); /* mono/stereo */ ess_write(sc, 0xa8, (ess_read(sc, 0xa8) & ~0x03) | (stereo? 0x01 : 0x02)); /* demand mode, 4 bytes/xfer */ ess_write(sc, 0xb9, 0x02); /* sample rate */ ess_write(sc, 0xa1, spdval); /* filter cutoff */ ess_write(sc, 0xa2, ess_calcfilter(spd)); /* setup dac/adc */ /* if (play) ess_write(sc, 0xb6, unsign? 0x80 : 0x00); */ /* mono, b16: signed, load signal */ /* ess_write(sc, 0xb7, 0x51 | (unsign? 0x00 : 0x20)); */ /* setup fifo */ ess_write(sc, 0xb7, 0x91 | (unsign? 0x00 : 0x20) | (b16? 0x04 : 0x00) | (stereo? 0x08 : 0x40)); /* irq control */ ess_write(sc, 0xb1, (ess_read(sc, 0xb1) & 0x0f) | 0x50); /* drq control */ ess_write(sc, 0xb2, (ess_read(sc, 0xb2) & 0x0f) | 0x50); } else if (ch == 2) { KASSERT(dir == PCMDIR_PLAY, ("ess_setupch: dir2 bad")); len >>= 1; len = -len; /* transfer length low */ ess_setmixer(sc, 0x74, len & 0x00ff); /* transfer length high */ ess_setmixer(sc, 0x76, (len & 0xff00) >> 8); /* autoinit, 4 bytes/req */ ess_setmixer(sc, 0x78, 0x10); fmtval = b16 | (stereo << 1) | ((!unsign) << 2); /* enable irq, set format */ ess_setmixer(sc, 0x7a, 0x40 | fmtval); if (sc->newspeed) { /* sample rate */ ess_setmixer(sc, 0x70, spdval); /* filter cutoff */ ess_setmixer(sc, 0x72, ess_calcfilter(spd)); } } return 0; } static int ess_start(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_start\n");); ess_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->blksz); ch->stopping = 0; if (ch->hwch == 1) { ess_write(sc, 0xb8, ess_read(sc, 0xb8) | 0x01); if (ch->dir == PCMDIR_PLAY) { #if 0 DELAY(100000); /* 100 ms */ #endif ess_cmd(sc, 0xd1); } } else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) | 0x03); return 0; } static int ess_stop(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_stop\n")); ch->stopping = 1; if (ch->hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x04); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x10); DEB(printf("done with stop\n")); return 0; } /* -------------------------------------------------------------------- */ /* channel interface for ESS18xx */ static void * esschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct ess_info *sc = devinfo; struct ess_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; DEB(printf("esschan_init\n")); ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; return ch; } static int esschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static int esschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; ch->spd = speed; if (sc->newspeed) ess_calcspeed9(&ch->spd); else ess_calcspeed8(&ch->spd); return ch->spd; } static int esschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct ess_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int esschan_trigger(kobj_t obj, void *data, int go) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; - DEB(printf("esschan_trigger: %d\n",go)); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; + + DEB(printf("esschan_trigger: %d\n",go)); ess_lock(sc); switch (go) { case PCMTRIG_START: ess_dmasetup(sc, ch->hwch, sndbuf_getbufaddr(ch->buffer), sndbuf_getsize(ch->buffer), ch->dir); ess_dmatrigger(sc, ch->hwch, 1); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } ess_unlock(sc); return 0; } static int esschan_getptr(kobj_t obj, void *data) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; int ret; ess_lock(sc); ret = ess_dmapos(sc, ch->hwch); ess_unlock(sc); return ret; } static struct pcmchan_caps * esschan_getcaps(kobj_t obj, void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } static kobj_method_t esschan_methods[] = { KOBJMETHOD(channel_init, esschan_init), KOBJMETHOD(channel_setformat, esschan_setformat), KOBJMETHOD(channel_setspeed, esschan_setspeed), KOBJMETHOD(channel_setblocksize, esschan_setblocksize), KOBJMETHOD(channel_trigger, esschan_trigger), KOBJMETHOD(channel_getptr, esschan_getptr), KOBJMETHOD(channel_getcaps, esschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(esschan); /************************************************************/ static int essmix_init(struct snd_mixer *m) { struct ess_info *sc = mix_getdevinfo(m); mix_setrecdevs(m, SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_IMIX); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME | SOUND_MASK_LINE1); ess_setmixer(sc, 0, 0); /* reset */ return 0; } static int essmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ess_info *sc = mix_getdevinfo(m); int preg = 0, rreg = 0, l, r; l = (left * 15) / 100; r = (right * 15) / 100; switch (dev) { case SOUND_MIXER_SYNTH: preg = 0x36; rreg = 0x6b; break; case SOUND_MIXER_PCM: preg = 0x14; rreg = 0x7c; break; case SOUND_MIXER_LINE: preg = 0x3e; rreg = 0x6e; break; case SOUND_MIXER_MIC: preg = 0x1a; rreg = 0x68; break; case SOUND_MIXER_LINE1: preg = 0x3a; rreg = 0x6c; break; case SOUND_MIXER_CD: preg = 0x38; rreg = 0x6a; break; case SOUND_MIXER_VOLUME: l = left? (left * 63) / 100 : 64; r = right? (right * 63) / 100 : 64; ess_setmixer(sc, 0x60, l); ess_setmixer(sc, 0x62, r); left = (l == 64)? 0 : (l * 100) / 63; right = (r == 64)? 0 : (r * 100) / 63; return left | (right << 8); } if (preg) ess_setmixer(sc, preg, (l << 4) | r); if (rreg) ess_setmixer(sc, rreg, (l << 4) | r); left = (l * 100) / 15; right = (r * 100) / 15; return left | (right << 8); } static int essmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct ess_info *sc = mix_getdevinfo(m); u_char recdev; switch (src) { case SOUND_MASK_CD: recdev = 0x02; break; case SOUND_MASK_LINE: recdev = 0x06; break; case SOUND_MASK_IMIX: recdev = 0x05; break; case SOUND_MASK_MIC: default: recdev = 0x00; src = SOUND_MASK_MIC; break; } ess_setmixer(sc, 0x1c, recdev); return src; } static kobj_method_t solomixer_methods[] = { KOBJMETHOD(mixer_init, essmix_init), KOBJMETHOD(mixer_set, essmix_set), KOBJMETHOD(mixer_setrecsrc, essmix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(solomixer); /************************************************************/ static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir) { KASSERT(ch == 1 || ch == 2, ("bad ch")); sc->dmasz[ch - 1] = cnt; if (ch == 1) { port_wr(sc->vc, 0x8, 0xc4, 1); /* command */ port_wr(sc->vc, 0xd, 0xff, 1); /* reset */ port_wr(sc->vc, 0xf, 0x01, 1); /* mask */ port_wr(sc->vc, 0xb, dir == PCMDIR_PLAY? 0x58 : 0x54, 1); /* mode */ port_wr(sc->vc, 0x0, base, 4); port_wr(sc->vc, 0x4, cnt - 1, 2); } else if (ch == 2) { port_wr(sc->io, 0x6, 0x08, 1); /* autoinit */ port_wr(sc->io, 0x0, base, 4); port_wr(sc->io, 0x4, cnt, 2); } return 0; } static int ess_dmapos(struct ess_info *sc, int ch) { int p = 0, i = 0, j = 0; KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) { /* * During recording, this register is known to give back * garbage if it's not quiescent while being read. That's * why we spl, stop the DMA, and try over and over until * adjacent reads are "close", in the right order and not * bigger than is otherwise possible. */ ess_dmatrigger(sc, ch, 0); DELAY(20); do { DELAY(10); if (j > 1) printf("DMA count reg bogus: %04x & %04x\n", i, p); i = port_rd(sc->vc, 0x4, 2) + 1; p = port_rd(sc->vc, 0x4, 2) + 1; } while ((p > sc->dmasz[ch - 1] || i < p || (p - i) > 0x8) && j++ < 1000); ess_dmatrigger(sc, ch, 1); } else if (ch == 2) p = port_rd(sc->io, 0x4, 2); return sc->dmasz[ch - 1] - p; } static int ess_dmatrigger(struct ess_info *sc, int ch, int go) { KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) port_wr(sc->vc, 0xf, go? 0x00 : 0x01, 1); /* mask */ else if (ch == 2) port_wr(sc->io, 0x6, 0x08 | (go? 0x02 : 0x00), 1); /* autoinit */ return 0; } static void ess_release_resources(struct ess_info *sc, device_t dev) { if (sc->irq) { if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = 0; } if (sc->io) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->io); sc->io = 0; } if (sc->sb) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(1), sc->sb); sc->sb = 0; } if (sc->vc) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(2), sc->vc); sc->vc = 0; } if (sc->mpu) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(3), sc->mpu); sc->mpu = 0; } if (sc->gp) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(4), sc->gp); sc->gp = 0; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } #if ESS18XX_MPSAFE == 1 if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } #endif free(sc, M_DEVBUF); } static int ess_alloc_resources(struct ess_info *sc, device_t dev) { int rid; rid = PCIR_BAR(0); sc->io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(1); sc->sb = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(2); sc->vc = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(3); sc->mpu = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(4); sc->gp = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); #if ESS18XX_MPSAFE == 1 sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_solo softc"); return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; #else return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp)? 0 : ENXIO; #endif } static int ess_probe(device_t dev) { char *s = NULL; u_int32_t subdev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case 0x1969125d: if (subdev == 0x8888125d) s = "ESS Solo-1E"; else if (subdev == 0x1818125d) s = "ESS Solo-1"; else s = "ESS Solo-1 (unknown vendor)"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } #define ESS_PCI_LEGACYCONTROL 0x40 #define ESS_PCI_CONFIG 0x50 #define ESS_PCI_DDMACONTROL 0x60 static int ess_suspend(device_t dev) { return 0; } static int ess_resume(device_t dev) { uint16_t ddma; uint32_t data; struct ess_info *sc = pcm_getdevinfo(dev); ess_lock(sc); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); if (ess_reset_dsp(sc)) { ess_unlock(sc); goto no; } ess_unlock(sc); if (mixer_reinit(dev)) goto no; ess_lock(sc); if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ ess_unlock(sc); return 0; no: return EIO; } static int ess_attach(device_t dev) { struct ess_info *sc; char status[SND_STATUSLEN]; u_int16_t ddma; u_int32_t data; sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO); if (!sc) return ENXIO; data = pci_read_config(dev, PCIR_COMMAND, 2); data |= PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); if (ess_alloc_resources(sc, dev)) goto no; sc->bufsz = pcm_getbuffersize(dev, 4096, SOLO_DEFAULT_BUFSZ, 65536); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ #ifdef ESS18XX_DUPLEX sc->duplex = 1; #else sc->duplex = 0; #endif #ifdef ESS18XX_NEWSPEED sc->newspeed = 1; #else sc->newspeed = 0; #endif if (snd_setup_intr(dev, sc->irq, #if ESS18XX_MPSAFE == 1 INTR_MPSAFE #else 0 #endif , ess_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto no; } if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); #if 0 if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/65536, /*boundary*/0, #endif if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, #if ESS18XX_MPSAFE == 1 /*lockfunc*/NULL, /*lockarg*/NULL, #else /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, #endif &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (ess_reset_dsp(sc)) goto no; if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); if (mixer_init(dev, &solomixer_class, sc)) goto no; snprintf(status, SND_STATUSLEN, "at io 0x%lx,0x%lx,0x%lx irq %ld %s", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), rman_get_start(sc->irq),PCM_KLDSTRING(snd_solo)); if (pcm_register(dev, sc, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &esschan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &esschan_class, sc); pcm_setstatus(dev, status); return 0; no: ess_release_resources(sc, dev); return ENXIO; } static int ess_detach(device_t dev) { int r; struct ess_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); ess_release_resources(sc, dev); return 0; } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), DEVMETHOD(device_detach, ess_detach), DEVMETHOD(device_resume, ess_resume), DEVMETHOD(device_suspend, ess_suspend), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_solo, pci, ess_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_solo, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_solo, 1); Index: head/sys/dev/sound/pci/t4dwave.c =================================================================== --- head/sys/dev/sound/pci/t4dwave.c (revision 170520) +++ head/sys/dev/sound/pci/t4dwave.c (revision 170521) @@ -1,990 +1,990 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ #define TDX_PCI_ID 0x20001023 #define TNX_PCI_ID 0x20011023 #define ALI_PCI_ID 0x545110b9 #define SPA_PCI_ID 0x70181039 #define TR_DEFAULT_BUFSZ 0x1000 #define TR_TIMEOUT_CDC 0xffff #define TR_MAXPLAYCH 4 /* * Though, it's not clearly documented in trident datasheet, trident * audio cards can't handle DMA addresses located above 1GB. The LBA * (loop begin address) register which holds DMA base address is 32bits * register. * But the MSB 2bits are used for other purposes(I guess it is really * bad idea). This effectivly limits the DMA address space up to 1GB. */ #define TR_MAXADDR ((1 << 30) - 1) struct tr_info; /* channel registers */ struct tr_chinfo { u_int32_t cso, alpha, fms, fmc, ec; u_int32_t lba; u_int32_t eso, delta; u_int32_t rvol, cvol; u_int32_t gvsel, pan, vol, ctrl; u_int32_t active:1, was_active:1; int index, bufhalf; struct snd_dbuf *buffer; struct pcm_channel *channel; struct tr_info *parent; }; struct tr_rchinfo { u_int32_t delta; u_int32_t active:1, was_active:1; struct snd_dbuf *buffer; struct pcm_channel *channel; struct tr_info *parent; }; /* device private data */ struct tr_info { u_int32_t type; u_int32_t rev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; struct mtx *lock; u_int32_t playchns; unsigned int bufsz; struct tr_chinfo chinfo[TR_MAXPLAYCH]; struct tr_rchinfo recchinfo; }; /* -------------------------------------------------------------------- */ static u_int32_t tr_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, 0 }; static struct pcmchan_caps tr_reccaps = {4000, 48000, tr_recfmt, 0}; static u_int32_t tr_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, 0 }; static struct pcmchan_caps tr_playcaps = {4000, 48000, tr_playfmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t tr_rd(struct tr_info *tr, int regno, int size) { switch(size) { case 1: return bus_space_read_1(tr->st, tr->sh, regno); case 2: return bus_space_read_2(tr->st, tr->sh, regno); case 4: return bus_space_read_4(tr->st, tr->sh, regno); default: return 0xffffffff; } } static void tr_wr(struct tr_info *tr, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(tr->st, tr->sh, regno, data); break; case 2: bus_space_write_2(tr->st, tr->sh, regno, data); break; case 4: bus_space_write_4(tr->st, tr->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int tr_rdcd(kobj_t obj, void *devinfo, int regno) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { case SPA_PCI_ID: treg=SPA_REG_CODECRD; trw=SPA_CDC_RWSTAT; break; case ALI_PCI_ID: if (tr->rev > 0x01) treg=TDX_REG_CODECWR; else treg=TDX_REG_CODECRD; trw=TDX_CDC_RWSTAT; break; case TDX_PCI_ID: treg=TDX_REG_CODECRD; trw=TDX_CDC_RWSTAT; break; case TNX_PCI_ID: treg=(regno & 0x100)? TNX_REG_CODEC2RD : TNX_REG_CODEC1RD; trw=TNX_CDC_RWSTAT; break; default: printf("!!! tr_rdcd defaulted !!!\n"); return -1; } i = j = 0; regno &= 0x7f; snd_mtxlock(tr->lock); if (tr->type == ALI_PCI_ID) { u_int32_t chk1, chk2; j = trw; for (i = TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j = tr_rd(tr, treg, 4); if (i > 0) { chk1 = tr_rd(tr, 0xc8, 4); chk2 = tr_rd(tr, 0xc8, 4); for (i = TR_TIMEOUT_CDC; (i > 0) && (chk1 == chk2); i--) chk2 = tr_rd(tr, 0xc8, 4); } } if (tr->type != ALI_PCI_ID || i > 0) { tr_wr(tr, treg, regno | trw, 4); j=trw; for (i=TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j=tr_rd(tr, treg, 4); } snd_mtxunlock(tr->lock); if (i == 0) printf("codec timeout during read of register %x\n", regno); return (j >> TR_CDC_DATA) & 0xffff; } static int tr_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { case SPA_PCI_ID: treg=SPA_REG_CODECWR; trw=SPA_CDC_RWSTAT; break; case ALI_PCI_ID: case TDX_PCI_ID: treg=TDX_REG_CODECWR; trw=TDX_CDC_RWSTAT; break; case TNX_PCI_ID: treg=TNX_REG_CODECWR; trw=TNX_CDC_RWSTAT | ((regno & 0x100)? TNX_CDC_SEC : 0); break; default: printf("!!! tr_wrcd defaulted !!!"); return -1; } i = 0; regno &= 0x7f; #if 0 printf("tr_wrcd: reg %x was %x", regno, tr_rdcd(devinfo, regno)); #endif j=trw; snd_mtxlock(tr->lock); if (tr->type == ALI_PCI_ID) { j = trw; for (i = TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j = tr_rd(tr, treg, 4); if (i > 0) { u_int32_t chk1, chk2; chk1 = tr_rd(tr, 0xc8, 4); chk2 = tr_rd(tr, 0xc8, 4); for (i = TR_TIMEOUT_CDC; (i > 0) && (chk1 == chk2); i--) chk2 = tr_rd(tr, 0xc8, 4); } } if (tr->type != ALI_PCI_ID || i > 0) { for (i=TR_TIMEOUT_CDC; (i>0) && (j & trw); i--) j=tr_rd(tr, treg, 4); if (tr->type == ALI_PCI_ID && tr->rev > 0x01) trw |= 0x0100; tr_wr(tr, treg, (data << TR_CDC_DATA) | regno | trw, 4); } #if 0 printf(" - wrote %x, now %x\n", data, tr_rdcd(devinfo, regno)); #endif snd_mtxunlock(tr->lock); if (i==0) printf("codec timeout writing %x, data %x\n", regno, data); return (i > 0)? 0 : -1; } static kobj_method_t tr_ac97_methods[] = { KOBJMETHOD(ac97_read, tr_rdcd), KOBJMETHOD(ac97_write, tr_wrcd), { 0, 0 } }; AC97_DECLARE(tr_ac97); /* -------------------------------------------------------------------- */ /* playback channel interrupts */ #if 0 static u_int32_t tr_testint(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; return tr_rd(tr, bank? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 4) & (1 << chan); } #endif static void tr_clrint(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 1 << chan, 4); } static void tr_enaint(struct tr_chinfo *ch, int enable) { struct tr_info *tr = ch->parent; u_int32_t i, reg; int bank, chan; snd_mtxlock(tr->lock); bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; reg = bank? TR_REG_INTENB : TR_REG_INTENA; i = tr_rd(tr, reg, 4); i &= ~(1 << chan); i |= (enable? 1 : 0) << chan; tr_clrint(ch); tr_wr(tr, reg, i, 4); snd_mtxunlock(tr->lock); } /* playback channels */ static void tr_selch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int i; i = tr_rd(tr, TR_REG_CIR, 4); i &= ~TR_CIR_MASK; i |= ch->index & 0x3f; tr_wr(tr, TR_REG_CIR, i, 4); } static void tr_startch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_STARTB : TR_REG_STARTA, 1 << chan, 4); } static void tr_stopch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_STOPB : TR_REG_STOPA, 1 << chan, 4); } static void tr_wrch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; u_int32_t cr[TR_CHN_REGS], i; ch->gvsel &= 0x00000001; ch->fmc &= 0x00000003; ch->fms &= 0x0000000f; ch->ctrl &= 0x0000000f; ch->pan &= 0x0000007f; ch->rvol &= 0x0000007f; ch->cvol &= 0x0000007f; ch->vol &= 0x000000ff; ch->ec &= 0x00000fff; ch->alpha &= 0x00000fff; ch->delta &= 0x0000ffff; ch->lba &= 0x3fffffff; cr[1]=ch->lba; cr[3]=(ch->fmc<<14) | (ch->rvol<<7) | (ch->cvol); cr[4]=(ch->gvsel<<31) | (ch->pan<<24) | (ch->vol<<16) | (ch->ctrl<<12) | (ch->ec); switch (tr->type) { case SPA_PCI_ID: case ALI_PCI_ID: case TDX_PCI_ID: ch->cso &= 0x0000ffff; ch->eso &= 0x0000ffff; cr[0]=(ch->cso<<16) | (ch->alpha<<4) | (ch->fms); cr[2]=(ch->eso<<16) | (ch->delta); break; case TNX_PCI_ID: ch->cso &= 0x00ffffff; ch->eso &= 0x00ffffff; cr[0]=((ch->delta & 0xff)<<24) | (ch->cso); cr[2]=((ch->delta>>8)<<24) | (ch->eso); cr[3]|=(ch->alpha<<20) | (ch->fms<<16) | (ch->fmc<<14); break; } snd_mtxlock(tr->lock); tr_selch(ch); for (i=0; ilock); } static void tr_rdch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; u_int32_t cr[5], i; snd_mtxlock(tr->lock); tr_selch(ch); for (i=0; i<5; i++) cr[i]=tr_rd(tr, TR_REG_CHNBASE+(i<<2), 4); snd_mtxunlock(tr->lock); ch->lba= (cr[1] & 0x3fffffff); ch->fmc= (cr[3] & 0x0000c000) >> 14; ch->rvol= (cr[3] & 0x00003f80) >> 7; ch->cvol= (cr[3] & 0x0000007f); ch->gvsel= (cr[4] & 0x80000000) >> 31; ch->pan= (cr[4] & 0x7f000000) >> 24; ch->vol= (cr[4] & 0x00ff0000) >> 16; ch->ctrl= (cr[4] & 0x0000f000) >> 12; ch->ec= (cr[4] & 0x00000fff); switch(tr->type) { case SPA_PCI_ID: case ALI_PCI_ID: case TDX_PCI_ID: ch->cso= (cr[0] & 0xffff0000) >> 16; ch->alpha= (cr[0] & 0x0000fff0) >> 4; ch->fms= (cr[0] & 0x0000000f); ch->eso= (cr[2] & 0xffff0000) >> 16; ch->delta= (cr[2] & 0x0000ffff); break; case TNX_PCI_ID: ch->cso= (cr[0] & 0x00ffffff); ch->eso= (cr[2] & 0x00ffffff); ch->delta= ((cr[2] & 0xff000000) >> 16) | ((cr[0] & 0xff000000) >> 24); ch->alpha= (cr[3] & 0xfff00000) >> 20; ch->fms= (cr[3] & 0x000f0000) >> 16; break; } } static u_int32_t tr_fmttobits(u_int32_t fmt) { u_int32_t bits; bits = 0; bits |= (fmt & AFMT_SIGNED)? 0x2 : 0; bits |= (fmt & AFMT_STEREO)? 0x4 : 0; bits |= (fmt & AFMT_16BIT)? 0x8 : 0; return bits; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * trpchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_chinfo *ch; KASSERT(dir == PCMDIR_PLAY, ("trpchan_init: bad direction")); ch = &tr->chinfo[tr->playchns]; ch->index = tr->playchns++; ch->buffer = b; ch->parent = tr; ch->channel = c; if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; } static int trpchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct tr_chinfo *ch = data; ch->ctrl = tr_fmttobits(format) | 0x01; return 0; } static int trpchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct tr_chinfo *ch = data; ch->delta = (speed << 12) / 48000; return (ch->delta * 48000) >> 12; } static int trpchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct tr_chinfo *ch = data; sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static int trpchan_trigger(kobj_t obj, void *data, int go) { struct tr_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { ch->fmc = 3; ch->fms = 0; ch->ec = 0; ch->alpha = 0; ch->lba = sndbuf_getbufaddr(ch->buffer); ch->cso = 0; ch->eso = (sndbuf_getsize(ch->buffer) / sndbuf_getbps(ch->buffer)) - 1; ch->rvol = ch->cvol = 0x7f; ch->gvsel = 0; ch->pan = 0; ch->vol = 0; ch->bufhalf = 0; tr_wrch(ch); tr_enaint(ch, 1); tr_startch(ch); ch->active = 1; } else { tr_stopch(ch); ch->active = 0; } return 0; } static int trpchan_getptr(kobj_t obj, void *data) { struct tr_chinfo *ch = data; tr_rdch(ch); return ch->cso * sndbuf_getbps(ch->buffer); } static struct pcmchan_caps * trpchan_getcaps(kobj_t obj, void *data) { return &tr_playcaps; } static kobj_method_t trpchan_methods[] = { KOBJMETHOD(channel_init, trpchan_init), KOBJMETHOD(channel_setformat, trpchan_setformat), KOBJMETHOD(channel_setspeed, trpchan_setspeed), KOBJMETHOD(channel_setblocksize, trpchan_setblocksize), KOBJMETHOD(channel_trigger, trpchan_trigger), KOBJMETHOD(channel_getptr, trpchan_getptr), KOBJMETHOD(channel_getcaps, trpchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(trpchan); /* -------------------------------------------------------------------- */ /* rec channel interface */ static void * trrchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("trrchan_init: bad direction")); ch = &tr->recchinfo; ch->buffer = b; ch->parent = tr; ch->channel = c; if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; } static int trrchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t i, bits; bits = tr_fmttobits(format); /* set # of samples between interrupts */ i = (sndbuf_runsz(ch->buffer) >> ((bits & 0x08)? 1 : 0)) - 1; tr_wr(tr, TR_REG_SBBL, i | (i << 16), 4); /* set sample format */ i = 0x18 | (bits << 4); tr_wr(tr, TR_REG_SBCTRL, i, 1); return 0; } static int trrchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; /* setup speed */ ch->delta = (48000 << 12) / speed; tr_wr(tr, TR_REG_SBDELTA, ch->delta, 2); /* return closest possible speed */ return (48000 << 12) / ch->delta; } static int trrchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct tr_rchinfo *ch = data; sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static int trrchan_trigger(kobj_t obj, void *data, int go) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t i; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { /* set up dma mode regs */ tr_wr(tr, TR_REG_DMAR15, 0, 1); i = tr_rd(tr, TR_REG_DMAR11, 1) & 0x03; tr_wr(tr, TR_REG_DMAR11, i | 0x54, 1); /* set up base address */ tr_wr(tr, TR_REG_DMAR0, sndbuf_getbufaddr(ch->buffer), 4); /* set up buffer size */ i = tr_rd(tr, TR_REG_DMAR4, 4) & ~0x00ffffff; tr_wr(tr, TR_REG_DMAR4, i | (sndbuf_runsz(ch->buffer) - 1), 4); /* start */ tr_wr(tr, TR_REG_SBCTRL, tr_rd(tr, TR_REG_SBCTRL, 1) | 1, 1); ch->active = 1; } else { tr_wr(tr, TR_REG_SBCTRL, tr_rd(tr, TR_REG_SBCTRL, 1) & ~7, 1); ch->active = 0; } /* return 0 if ok */ return 0; } static int trrchan_getptr(kobj_t obj, void *data) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; /* return current byte offset of channel */ return tr_rd(tr, TR_REG_DMAR0, 4) - sndbuf_getbufaddr(ch->buffer); } static struct pcmchan_caps * trrchan_getcaps(kobj_t obj, void *data) { return &tr_reccaps; } static kobj_method_t trrchan_methods[] = { KOBJMETHOD(channel_init, trrchan_init), KOBJMETHOD(channel_setformat, trrchan_setformat), KOBJMETHOD(channel_setspeed, trrchan_setspeed), KOBJMETHOD(channel_setblocksize, trrchan_setblocksize), KOBJMETHOD(channel_trigger, trrchan_trigger), KOBJMETHOD(channel_getptr, trrchan_getptr), KOBJMETHOD(channel_getcaps, trrchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(trrchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void tr_intr(void *p) { struct tr_info *tr = (struct tr_info *)p; struct tr_chinfo *ch; u_int32_t active, mask, bufhalf, chnum, intsrc; int tmp; intsrc = tr_rd(tr, TR_REG_MISCINT, 4); if (intsrc & TR_INT_ADDR) { chnum = 0; while (chnum < 64) { mask = 0x00000001; active = tr_rd(tr, (chnum < 32)? TR_REG_ADDRINTA : TR_REG_ADDRINTB, 4); bufhalf = tr_rd(tr, (chnum < 32)? TR_REG_CSPF_A : TR_REG_CSPF_B, 4); if (active) { do { if (active & mask) { tmp = (bufhalf & mask)? 1 : 0; if (chnum < tr->playchns) { ch = &tr->chinfo[chnum]; /* printf("%d @ %d, ", chnum, trpchan_getptr(NULL, ch)); */ if (ch->bufhalf != tmp) { chn_intr(ch->channel); ch->bufhalf = tmp; } } } chnum++; mask <<= 1; } while (chnum & 31); } else chnum += 32; tr_wr(tr, (chnum <= 32)? TR_REG_ADDRINTA : TR_REG_ADDRINTB, active, 4); } } if (intsrc & TR_INT_SB) { chn_intr(tr->recchinfo.channel); tr_rd(tr, TR_REG_SBR9, 1); tr_rd(tr, TR_REG_SBR10, 1); } } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int tr_init(struct tr_info *tr) { switch (tr->type) { case SPA_PCI_ID: tr_wr(tr, SPA_REG_GPIO, 0, 4); tr_wr(tr, SPA_REG_CODECST, SPA_RST_OFF, 4); break; case TDX_PCI_ID: tr_wr(tr, TDX_REG_CODECST, TDX_CDC_ON, 4); break; case TNX_PCI_ID: tr_wr(tr, TNX_REG_CODECST, TNX_CDC_ON, 4); break; } tr_wr(tr, TR_REG_CIR, TR_CIR_MIDENA | TR_CIR_ADDRENA, 4); return 0; } static int tr_pci_probe(device_t dev) { switch (pci_get_devid(dev)) { case SPA_PCI_ID: device_set_desc(dev, "SiS 7018"); return BUS_PROBE_DEFAULT; case ALI_PCI_ID: device_set_desc(dev, "Acer Labs M5451"); return BUS_PROBE_DEFAULT; case TDX_PCI_ID: device_set_desc(dev, "Trident 4DWave DX"); return BUS_PROBE_DEFAULT; case TNX_PCI_ID: device_set_desc(dev, "Trident 4DWave NX"); return BUS_PROBE_DEFAULT; } return ENXIO; } static int tr_pci_attach(device_t dev) { u_int32_t data; struct tr_info *tr; struct ac97_info *codec = 0; int i; char status[SND_STATUSLEN]; if ((tr = malloc(sizeof(*tr), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } tr->type = pci_get_devid(dev); tr->rev = pci_get_revid(dev); tr->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_t4dwave softc"); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); tr->regid = PCIR_BAR(0); tr->regtype = SYS_RES_IOPORT; tr->reg = bus_alloc_resource_any(dev, tr->regtype, &tr->regid, RF_ACTIVE); if (tr->reg) { tr->st = rman_get_bustag(tr->reg); tr->sh = rman_get_bushandle(tr->reg); } else { device_printf(dev, "unable to map register space\n"); goto bad; } tr->bufsz = pcm_getbuffersize(dev, 4096, TR_DEFAULT_BUFSZ, 65536); if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } tr->playchns = 0; codec = AC97_CREATE(dev, tr, tr_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; tr->irqid = 0; tr->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &tr->irqid, RF_ACTIVE | RF_SHAREABLE); if (!tr->irq || snd_setup_intr(dev, tr->irq, 0, tr_intr, tr, &tr->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/TR_MAXADDR, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/tr->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &tr->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, 64, "at io 0x%lx irq %ld %s", rman_get_start(tr->reg), rman_get_start(tr->irq),PCM_KLDSTRING(snd_t4dwave)); if (pcm_register(dev, tr, TR_MAXPLAYCH, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &trrchan_class, tr); for (i = 0; i < TR_MAXPLAYCH; i++) pcm_addchan(dev, PCMDIR_PLAY, &trpchan_class, tr); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (tr->reg) bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); if (tr->ih) bus_teardown_intr(dev, tr->irq, tr->ih); if (tr->irq) bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); if (tr->parent_dmat) bus_dma_tag_destroy(tr->parent_dmat); if (tr->lock) snd_mtxfree(tr->lock); free(tr, M_DEVBUF); return ENXIO; } static int tr_pci_detach(device_t dev) { int r; struct tr_info *tr; r = pcm_unregister(dev); if (r) return r; tr = pcm_getdevinfo(dev); bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); bus_teardown_intr(dev, tr->irq, tr->ih); bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); bus_dma_tag_destroy(tr->parent_dmat); snd_mtxfree(tr->lock); free(tr, M_DEVBUF); return 0; } static int tr_pci_suspend(device_t dev) { int i; struct tr_info *tr; tr = pcm_getdevinfo(dev); for (i = 0; i < tr->playchns; i++) { tr->chinfo[i].was_active = tr->chinfo[i].active; if (tr->chinfo[i].active) { trpchan_trigger(NULL, &tr->chinfo[i], PCMTRIG_STOP); } } tr->recchinfo.was_active = tr->recchinfo.active; if (tr->recchinfo.active) { trrchan_trigger(NULL, &tr->recchinfo, PCMTRIG_STOP); } return 0; } static int tr_pci_resume(device_t dev) { int i; struct tr_info *tr; tr = pcm_getdevinfo(dev); if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to initialize the mixer\n"); return ENXIO; } for (i = 0; i < tr->playchns; i++) { if (tr->chinfo[i].was_active) { trpchan_trigger(NULL, &tr->chinfo[i], PCMTRIG_START); } } if (tr->recchinfo.was_active) { trrchan_trigger(NULL, &tr->recchinfo, PCMTRIG_START); } return 0; } static device_method_t tr_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tr_pci_probe), DEVMETHOD(device_attach, tr_pci_attach), DEVMETHOD(device_detach, tr_pci_detach), DEVMETHOD(device_suspend, tr_pci_suspend), DEVMETHOD(device_resume, tr_pci_resume), { 0, 0 } }; static driver_t tr_driver = { "pcm", tr_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_t4dwave, pci, tr_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_t4dwave, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_t4dwave, 1); Index: head/sys/dev/sound/pci/via8233.c =================================================================== --- head/sys/dev/sound/pci/via8233.c (revision 170520) +++ head/sys/dev/sound/pci/via8233.c (revision 170521) @@ -1,1437 +1,1440 @@ /*- * Copyright (c) 2002 Orion Hodson * Portions of this code derived from via82c686.c: * Copyright (c) 2000 David Jones * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Credits due to: * * Grzybowski Rafal, Russell Davies, Mark Handley, Daniel O'Connor for * comments, machine time, testing patches, and patience. VIA for * providing specs. ALSA for helpful comments and some register poke * ordering. */ #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #define VIA8233_PCI_ID 0x30591106 #define VIA8233_REV_ID_8233PRE 0x10 #define VIA8233_REV_ID_8233C 0x20 #define VIA8233_REV_ID_8233 0x30 #define VIA8233_REV_ID_8233A 0x40 #define VIA8233_REV_ID_8235 0x50 #define VIA8233_REV_ID_8237 0x60 #define VIA8233_REV_ID_8251 0x70 #define SEGS_PER_CHAN 2 /* Segments per channel */ #define NDXSCHANS 4 /* No of DXS channels */ #define NMSGDCHANS 1 /* No of multichannel SGD */ #define NWRCHANS 1 /* No of write channels */ #define NCHANS (NWRCHANS + NDXSCHANS + NMSGDCHANS) #define NSEGS NCHANS * SEGS_PER_CHAN /* Segments in SGD table */ #define VIA_SEGS_MIN 2 #define VIA_SEGS_MAX 64 #define VIA_SEGS_DEFAULT 2 #define VIA_BLK_MIN 32 #define VIA_BLK_ALIGN (~(VIA_BLK_MIN - 1)) #define VIA_DEFAULT_BUFSZ 0x1000 /* we rely on this struct being packed to 64 bits */ struct via_dma_op { volatile uint32_t ptr; volatile uint32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 #define VIA_DMAOP_COUNT(x) ((x)&0x00FFFFFF) }; struct via_info; struct via_chinfo { struct via_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; int dir, rbase, active; unsigned int blksz, blkcnt; unsigned int ptr, prevptr; }; struct via_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regid, irqid; void *ih; struct ac97_info *codec; unsigned int bufsz, blkcnt; int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; uint16_t codec_caps; uint16_t n_dxs_registered; int play_num, rec_num; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; static uint32_t via_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; static __inline int via_chan_active(struct via_info *via) { int i, ret = 0; if (via == NULL) return (0); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) ret += via->pch[i].active; for (i = 0; i < NWRCHANS; i++) ret += via->rch[i].active; return (ret); } #ifdef SND_DYNSYSCTL static int sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; uint32_t r; int err, new_en; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); snd_mtxlock(via->lock); r = pci_read_config(dev, VIA_PCI_SPDIF, 1); snd_mtxunlock(via->lock); new_en = (r & VIA_SPDIF_EN) ? 1 : 0; err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) return (err); if (new_en < 0 || new_en > 1) return (EINVAL); if (new_en) r |= VIA_SPDIF_EN; else r &= ~VIA_SPDIF_EN; snd_mtxlock(via->lock); pci_write_config(dev, VIA_PCI_SPDIF, r, 1); snd_mtxunlock(via->lock); return (0); } static int sysctl_via8233_dxs_src(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; int err, val; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); snd_mtxlock(via->lock); val = via->dxs_src; snd_mtxunlock(via->lock); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); snd_mtxlock(via->lock); via->dxs_src = val; snd_mtxunlock(via->lock); return (0); } static int sysctl_via_polling(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; int err, val; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); if (via == NULL) return (EINVAL); snd_mtxlock(via->lock); val = via->polling; snd_mtxunlock(via->lock); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); snd_mtxlock(via->lock); if (val != via->polling) { if (via_chan_active(via) != 0) err = EBUSY; else if (val == 0) via->polling = 0; else via->polling = 1; } snd_mtxunlock(via->lock); return (err); } #endif /* SND_DYNSYSCTL */ static void via_init_sysctls(device_t dev) { #ifdef SND_DYNSYSCTL /* 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, dev, sizeof(dev), sysctl_via8233_spdif_enable, "I", "Enable S/PDIF output on primary playback channel"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "dxs_src", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), sysctl_via8233_dxs_src, "I", "Enable VIA DXS Sample Rate Converter"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), sysctl_via_polling, "I", "Enable polling mode"); #endif } static __inline uint32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(via->st, via->sh, regno)); case 2: return (bus_space_read_2(via->st, via->sh, regno)); case 4: return (bus_space_read_4(via->st, via->sh, regno)); default: return (0xFFFFFFFF); } } static __inline void via_wr(struct via_info *via, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(via->st, via->sh, regno, data); break; case 2: bus_space_write_2(via->st, via->sh, regno, data); break; case 4: bus_space_write_4(via->st, via->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* Codec interface */ static int via_waitready_codec(struct via_info *via) { int i; /* poll until codec not busy */ for (i = 0; i < 1000; i++) { if ((via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_BUSY) == 0) return (0); DELAY(1); } device_printf(via->dev, "%s: codec busy\n", __func__); return (1); } static int via_waitvalid_codec(struct via_info *via) { int i; /* poll until codec valid */ for (i = 0; i < 1000; i++) { if (via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_CODEC00_VALID) return (0); DELAY(1); } device_printf(via->dev, "%s: codec invalid\n", __func__); return (1); } static int via_write_codec(kobj_t obj, void *addr, int reg, uint32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return (-1); via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_INDEX(reg) | VIA_AC97_DATA(val), 4); return (0); } static int via_read_codec(kobj_t obj, void *addr, int reg) { struct via_info *via = addr; if (via_waitready_codec(via)) return (-1); via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); if (via_waitready_codec(via)) return (-1); if (via_waitvalid_codec(via)) return (-1); return (via_rd(via, VIA_AC97_CONTROL, 2)); } static kobj_method_t via_ac97_methods[] = { KOBJMETHOD(ac97_read, via_read_codec), KOBJMETHOD(ac97_write, via_write_codec), { 0, 0 } }; AC97_DECLARE(via_ac97); /* -------------------------------------------------------------------- */ static int via_buildsgdt(struct via_chinfo *ch) { uint32_t phys_addr, flag; int i; phys_addr = sndbuf_getbufaddr(ch->buffer); for (i = 0; i < ch->blkcnt; i++) { flag = (i == ch->blkcnt - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ch->sgd_table[i].ptr = phys_addr + (i * ch->blksz); ch->sgd_table[i].flags = flag | ch->blksz; } return (0); } /* -------------------------------------------------------------------- */ /* Format setting functions */ static int via8233wr_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t f = WR_FORMAT_STOP_INDEX; if (format & AFMT_STEREO) f |= WR_FORMAT_STEREO; if (format & AFMT_S16_LE) f |= WR_FORMAT_16BIT; snd_mtxlock(via->lock); via_wr(via, VIA_WR0_FORMAT, f, 4); snd_mtxunlock(via->lock); return (0); } static int via8233dxs_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); v = via_rd(via, r, 4); v &= ~(VIA8233_DXS_RATEFMT_STEREO | VIA8233_DXS_RATEFMT_16BIT); if (format & AFMT_STEREO) v |= VIA8233_DXS_RATEFMT_STEREO; if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); snd_mtxunlock(via->lock); return (0); } static int via8233msgd_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t s = 0xff000000; uint8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; if (format & AFMT_STEREO) { v |= MC_SGD_CHANNELS(2); s |= SLOT3(1) | SLOT4(2); } else { v |= MC_SGD_CHANNELS(1); s |= SLOT3(1) | SLOT4(1); } snd_mtxlock(via->lock); via_wr(via, VIA_MC_SLOT_SELECT, s, 4); via_wr(via, VIA_MC_SGD_FORMAT, v, 1); snd_mtxunlock(via->lock); return (0); } /* -------------------------------------------------------------------- */ /* Speed setting functions */ static int via8233wr_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) return (ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed)); return (48000); } static int via8233dxs_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); v = via_rd(via, r, 4) & ~VIA8233_DXS_RATEFMT_48K; /* Careful to avoid overflow (divide by 48 per vt8233c docs) */ v |= VIA8233_DXS_RATEFMT_48K * (speed / 48) / (48000 / 48); via_wr(via, r, v, 4); snd_mtxunlock(via->lock); return (speed); } static int via8233msgd_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) return (ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed)); return (48000); } /* -------------------------------------------------------------------- */ /* Format probing functions */ static struct pcmchan_caps * via8233wr_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) return (&via_vracaps); return (&via_caps); } static struct pcmchan_caps * via8233dxs_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* * Controlled by onboard registers * * Apparently, few boards can do DXS sample rate * conversion. */ if (via->dxs_src) return (&via_vracaps); return (&via_caps); } static struct pcmchan_caps * via8233msgd_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) return (&via_vracaps); return (&via_caps); } /* -------------------------------------------------------------------- */ /* Common functions */ static int via8233chan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; blksz &= VIA_BLK_ALIGN; if (blksz > (sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN)) blksz = sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN; if (blksz < VIA_BLK_MIN) blksz = VIA_BLK_MIN; if (blkcnt > VIA_SEGS_MAX) blkcnt = VIA_SEGS_MAX; if (blkcnt < VIA_SEGS_MIN) blkcnt = VIA_SEGS_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= VIA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= VIA_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(via->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); ch->blkcnt = sndbuf_getblkcnt(ch->buffer); return (1); } static int via8233chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; via8233chan_setfragments(obj, data, blksz, via->blkcnt); return (ch->blksz); } static int via8233chan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t v, index, count; int ptr; snd_mtxlock(via->lock); if (via->polling != 0) { ptr = ch->ptr; snd_mtxunlock(via->lock); } else { v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); snd_mtxunlock(via->lock); index = v >> 24; /* Last completed buffer */ count = v & 0x00ffffff; /* Bytes remaining */ ptr = (index + 1) * ch->blksz - count; ptr %= ch->blkcnt * ch->blksz; /* Wrap to available space */ } return (ptr); } static void via8233chan_reset(struct via_info *via, struct via_chinfo *ch) { via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via_wr(via, ch->rbase + VIA_RP_CONTROL, 0x00, 1); via_wr(via, ch->rbase + VIA_RP_STATUS, SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); } /* -------------------------------------------------------------------- */ /* Channel initialization functions */ static void via8233chan_sgdinit(struct via_info *via, struct via_chinfo *ch, int chnum) { ch->sgd_table = &via->sgd_table[chnum * VIA_SEGS_MAX]; ch->sgd_addr = via->sgd_addr + chnum * VIA_SEGS_MAX * sizeof(struct via_dma_op); } static void* via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->rec_num++; ch = &via->rch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->blkcnt = via->blkcnt; ch->rbase = VIA_WR_BASE(num); via_wr(via, ch->rbase + VIA_WR_RP_SGD_FORMAT, WR_FIFO_ENABLE, 1); snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void* via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->play_num++; ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->blkcnt = via->blkcnt; /* * All cards apparently support DXS3, but not other DXS * channels. We therefore want to align first DXS channel to * DXS3. */ ch->rbase = VIA_DXS_BASE(NDXSCHANS - 1 - via->n_dxs_registered); via->n_dxs_registered++; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void* via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->play_num++; ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->rbase = VIA_MC_SGD_STATUS; ch->blkcnt = via->blkcnt; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void via8233chan_mute(struct via_info *via, struct via_chinfo *ch, int muted) { if (BASE_IS_VIA_DXS_REG(ch->rbase)) { int r; muted = (muted) ? VIA8233_DXS_MUTE : 0; via_wr(via, ch->rbase + VIA8233_RP_DXS_LVOL, muted, 1); via_wr(via, ch->rbase + VIA8233_RP_DXS_RVOL, muted, 1); r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & VIA8233_DXS_MUTE; if (r != muted) device_printf(via->dev, "%s: failed to set dxs volume " "(dxs base 0x%02x).\n", __func__, ch->rbase); } } static __inline int via_poll_channel(struct via_chinfo *ch) { struct via_info *via; uint32_t sz, delta; uint32_t v, index, count; int ptr; if (ch == NULL || ch->channel == NULL || ch->active == 0) return (0); via = ch->parent; sz = ch->blksz * ch->blkcnt; v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); index = v >> 24; count = v & 0x00ffffff; ptr = ((index + 1) * ch->blksz) - count; ptr %= sz; ptr &= ~(ch->blksz - 1); ch->ptr = ptr; delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } static void via_poll_callback(void *arg) { struct via_info *via = arg; uint32_t ptrigger = 0, rtrigger = 0; int i; if (via == NULL) return; snd_mtxlock(via->lock); if (via->polling == 0 || via_chan_active(via) == 0) { snd_mtxunlock(via->lock); return; } for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) ptrigger |= (via_poll_channel(&via->pch[i]) != 0) ? (1 << i) : 0; for (i = 0; i < NWRCHANS; i++) rtrigger |= (via_poll_channel(&via->rch[i]) != 0) ? (1 << i) : 0; /* XXX */ callout_reset(&via->poll_timer, 1/*via->poll_ticks*/, via_poll_callback, via); snd_mtxunlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (ptrigger & (1 << i)) chn_intr(via->pch[i].channel); } for (i = 0; i < NWRCHANS; i++) { if (rtrigger & (1 << i)) chn_intr(via->rch[i].channel); } } static int via_poll_ticks(struct via_info *via) { struct via_chinfo *ch; int i; int ret = hz; int pollticks; for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { ch = &via->pch[i]; if (ch->channel == NULL || ch->active == 0) continue; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks < ret) ret = pollticks; } for (i = 0; i < NWRCHANS; i++) { ch = &via->rch[i]; if (ch->channel == NULL || ch->active == 0) continue; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks < ret) ret = pollticks; } return (ret); } static int via8233chan_trigger(kobj_t obj, void* data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int pollticks; + if (!PCMTRIG_COMMON(go)) + return (0); + snd_mtxlock(via->lock); switch(go) { case PCMTRIG_START: via_buildsgdt(ch); via8233chan_mute(via, ch, 0); via_wr(via, ch->rbase + VIA_RP_TABLE_PTR, ch->sgd_addr, 4); if (via->polling != 0) { ch->ptr = 0; ch->prevptr = 0; pollticks = ((uint64_t)hz * ch->blksz) / ((uint64_t)sndbuf_getbps(ch->buffer) * sndbuf_getspd(ch->buffer)); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (via_chan_active(via) == 0 || pollticks < via->poll_ticks) { if (bootverbose) { if (via_chan_active(via) == 0) printf("%s: pollticks=%d\n", __func__, pollticks); else printf("%s: " "pollticks %d -> %d\n", __func__, via->poll_ticks, pollticks); } via->poll_ticks = pollticks; callout_reset(&via->poll_timer, 1, via_poll_callback, via); } } via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | ((via->polling == 0) ? (SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG) : 0), 1); ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via8233chan_mute(via, ch, 1); via8233chan_reset(via, ch); ch->active = 0; if (via->polling != 0) { if (via_chan_active(via) == 0) { callout_stop(&via->poll_timer); via->poll_ticks = 1; } else { pollticks = via_poll_ticks(via); if (pollticks > via->poll_ticks) { if (bootverbose) printf("%s: pollticks " "%d -> %d\n", __func__, via->poll_ticks, pollticks); via->poll_ticks = pollticks; callout_reset(&via->poll_timer, 1, via_poll_callback, via); } } } break; default: break; } snd_mtxunlock(via->lock); return (0); } static kobj_method_t via8233wr_methods[] = { KOBJMETHOD(channel_init, via8233wr_init), KOBJMETHOD(channel_setformat, via8233wr_setformat), KOBJMETHOD(channel_setspeed, via8233wr_setspeed), KOBJMETHOD(channel_getcaps, via8233wr_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233wr); static kobj_method_t via8233dxs_methods[] = { KOBJMETHOD(channel_init, via8233dxs_init), KOBJMETHOD(channel_setformat, via8233dxs_setformat), KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233dxs); static kobj_method_t via8233msgd_methods[] = { KOBJMETHOD(channel_init, via8233msgd_init), KOBJMETHOD(channel_setformat, via8233msgd_setformat), KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233msgd); /* -------------------------------------------------------------------- */ static void via_intr(void *p) { struct via_info *via = p; uint32_t ptrigger = 0, rtrigger = 0; int i, reg, stat; snd_mtxlock(via->lock); if (via->polling != 0) { snd_mtxunlock(via->lock); return; } /* Poll playback channels */ for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (via->pch[i].channel == NULL || via->pch[i].active == 0) continue; reg = via->pch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || !(stat & SGD_STATUS_ACTIVE))) via_wr(via, via->pch[i].rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); ptrigger |= 1 << i; } } /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { if (via->rch[i].channel == NULL || via->rch[i].active == 0) continue; reg = via->rch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || !(stat & SGD_STATUS_ACTIVE))) via_wr(via, via->rch[i].rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); rtrigger |= 1 << i; } } snd_mtxunlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (ptrigger & (1 << i)) chn_intr(via->pch[i].channel); } for (i = 0; i < NWRCHANS; i++) { if (rtrigger & (1 << i)) chn_intr(via->rch[i].channel); } } /* * Probe and attach the card */ static int via_probe(device_t dev) { switch(pci_get_devid(dev)) { case VIA8233_PCI_ID: switch(pci_get_revid(dev)) { case VIA8233_REV_ID_8233PRE: device_set_desc(dev, "VIA VT8233 (pre)"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233C: device_set_desc(dev, "VIA VT8233C"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233: device_set_desc(dev, "VIA VT8233"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233A: device_set_desc(dev, "VIA VT8233A"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8235: device_set_desc(dev, "VIA VT8235"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8251: device_set_desc(dev, "VIA VT8251"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct via_info *via = (struct via_info *)p; via->sgd_addr = bds->ds_addr; } static int via_chip_init(device_t dev) { uint32_t data, cnt; /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); if ((data & VIA_PCI_ACLINK_C00_READY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ /* Assert low */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); /* Wait T_rst_low */ DELAY(100); /* Assert high */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); /* Wait T_rst2clk */ DELAY(5); /* Assert low */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); } else { /* Warm reset */ /* Force no sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); DELAY(100); /* Sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); /* Wait T_sync_high */ DELAY(5); /* Force no sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); /* Wait T_sync2clk */ DELAY(5); } /* Power everything up */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_DESIRED, 1); /* Wait for codec to become ready (largest reported delay 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); if (data & VIA_PCI_ACLINK_C00_READY) return (0); DELAY(5000); } device_printf(dev, "primary codec not ready (cnt = 0x%02x)\n", cnt); return (ENXIO); } static int via_attach(device_t dev) { struct via_info *via = 0; char status[SND_STATUSLEN]; int i, via_dxs_disabled, via_dxs_src, via_dxs_chnum, via_sgd_chnum; int nsegs; uint32_t revid; if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENXIO); } via->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_via8233 softc"); via->dev = dev; callout_init(&via->poll_timer, CALLOUT_MPSAFE); via->poll_ticks = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) via->polling = 1; else via->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); if (!via->reg) { device_printf(dev, "cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= VIA_BLK_ALIGN; if (i < VIA_BLK_MIN) i = VIA_BLK_MIN; via->blkcnt = via->bufsz / i; i = 0; while (via->blkcnt >> i) i++; via->blkcnt = 1 << (i - 1); if (via->blkcnt < VIA_SEGS_MIN) via->blkcnt = VIA_SEGS_MIN; else if (via->blkcnt > VIA_SEGS_MAX) via->blkcnt = VIA_SEGS_MAX; } else via->blkcnt = VIA_SEGS_DEFAULT; revid = pci_get_revid(dev); /* * VIA8251 lost its interrupt after DMA EOL, and need * a gentle spank on its face within interrupt handler. */ if (revid == VIA8233_REV_ID_8251) via->dma_eol_wake = 1; else via->dma_eol_wake = 0; /* * Decide whether DXS had to be disabled or not */ if (revid == VIA8233_REV_ID_8233A) { /* * DXS channel is disabled. Reports from multiple users * that it plays at half-speed. Do not see this behaviour * on available 8233C or when emulating 8233A register set * on 8233C (either with or without ac97 VRA). */ via_dxs_disabled = 1; } else if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_disabled", &via_dxs_disabled) == 0) via_dxs_disabled = (via_dxs_disabled > 0) ? 1 : 0; else via_dxs_disabled = 0; if (via_dxs_disabled) { via_dxs_chnum = 0; via_sgd_chnum = 1; } else { if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_channels", &via_dxs_chnum) != 0) via_dxs_chnum = NDXSCHANS; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_sgd_channels", &via_sgd_chnum) != 0) via_sgd_chnum = NMSGDCHANS; } if (via_dxs_chnum > NDXSCHANS) via_dxs_chnum = NDXSCHANS; else if (via_dxs_chnum < 0) via_dxs_chnum = 0; if (via_sgd_chnum > NMSGDCHANS) via_sgd_chnum = NMSGDCHANS; else if (via_sgd_chnum < 0) via_sgd_chnum = 0; if (via_dxs_chnum + via_sgd_chnum < 1) { /* Minimalist ? */ via_dxs_chnum = 1; via_sgd_chnum = 0; } if (via_dxs_chnum > 0 && resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_src", &via_dxs_src) == 0) via->dxs_src = (via_dxs_src > 0) ? 1 : 0; else via->dxs_src = 0; nsegs = (via_dxs_chnum + via_sgd_chnum + NWRCHANS) * VIA_SEGS_MAX; /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* * DMA tag for SGD table. The 686 uses scatter/gather DMA and * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/nsegs * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) goto bad; if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, nsegs * sizeof(struct via_dma_op), dma_cb, via, 0)) goto bad; if (via_chip_init(dev)) goto bad; via->codec = AC97_CREATE(dev, via, via_ac97); if (!via->codec) goto bad; mixer_init(dev, ac97_getmixerclass(), via->codec); via->codec_caps = ac97_getextcaps(via->codec); /* Try to set VRA without generating an error, VRM not reqrd yet */ if (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { uint16_t ext = ac97_getextmode(via->codec); ext |= (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); ext &= ~AC97_EXTCAP_DRA; ac97_setextmode(via->codec, ext); } snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(via->reg), rman_get_start(via->irq), PCM_KLDSTRING(snd_via8233)); /* Register */ if (pcm_register(dev, via, via_dxs_chnum + via_sgd_chnum, NWRCHANS)) goto bad; for (i = 0; i < via_dxs_chnum; i++) pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); for (i = 0; i < via_sgd_chnum; i++) pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); for (i = 0; i < NWRCHANS; i++) pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); if (via_dxs_chnum > 0) via_init_sysctls(dev); device_printf(dev, "\n", (via_dxs_chnum > 0) ? "En" : "Dis", (via->dxs_src) ? "(SRC)" : "", via_dxs_chnum, via_sgd_chnum, NWRCHANS); pcm_setstatus(dev, status); return (0); bad: if (via->codec) ac97_destroy(via->codec); if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return (ENXIO); } static int via_detach(device_t dev) { int r; struct via_info *via = 0; r = pcm_unregister(dev); if (r) return (r); via = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); return (0); } static device_method_t via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), DEVMETHOD(device_detach, via_detach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_via8233, pci, via_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_via8233, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_via8233, 1); Index: head/sys/dev/sound/pci/via82c686.c =================================================================== --- head/sys/dev/sound/pci/via82c686.c (revision 170520) +++ head/sys/dev/sound/pci/via82c686.c (revision 170521) @@ -1,659 +1,659 @@ /*- * Copyright (c) 2000 David Jones * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #define VIA_PCI_ID 0x30581106 #define NSEGS 4 /* Number of segments in SGD table */ #define SEGS_PER_CHAN (NSEGS/2) #define TIMEOUT 50 #define VIA_DEFAULT_BUFSZ 0x1000 #undef DEB #define DEB(x) /* we rely on this struct being packed to 64 bits */ struct via_dma_op { u_int32_t ptr; u_int32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 #define VIA_DMAOP_COUNT(x) ((x)&0x00FFFFFF) }; struct via_info; struct via_chinfo { struct via_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; int dir, blksz; int base, count, mode, ctrl; }; struct via_info { bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regid, irqid; void *ih; struct ac97_info *codec; unsigned int bufsz; struct via_chinfo pch, rch; struct via_dma_op *sgd_table; u_int16_t codec_caps; struct mtx *lock; }; static u_int32_t via_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps via_vracaps = {4000, 48000, via_fmt, 0}; static struct pcmchan_caps via_caps = {48000, 48000, via_fmt, 0}; static __inline u_int32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: return bus_space_read_1(via->st, via->sh, regno); case 2: return bus_space_read_2(via->st, via->sh, regno); case 4: return bus_space_read_4(via->st, via->sh, regno); default: return 0xFFFFFFFF; } } static __inline void via_wr(struct via_info *via, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(via->st, via->sh, regno, data); break; case 2: bus_space_write_2(via->st, via->sh, regno, data); break; case 4: bus_space_write_4(via->st, via->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* Codec interface */ static int via_waitready_codec(struct via_info *via) { int i; /* poll until codec not busy */ for (i = 0; (i < TIMEOUT) && (via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_BUSY); i++) DELAY(1); if (i >= TIMEOUT) { printf("via: codec busy\n"); return 1; } return 0; } static int via_waitvalid_codec(struct via_info *via) { int i; /* poll until codec valid */ for (i = 0; (i < TIMEOUT) && !(via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_PRIVALID); i++) DELAY(1); if (i >= TIMEOUT) { printf("via: codec invalid\n"); return 1; } return 0; } static int via_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return -1; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_INDEX(reg) | val, 4); return 0; } static int via_read_codec(kobj_t obj, void *addr, int reg) { struct via_info *via = addr; if (via_waitready_codec(via)) return -1; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_READ | VIA_CODEC_INDEX(reg),4); if (via_waitready_codec(via)) return -1; if (via_waitvalid_codec(via)) return -1; return via_rd(via, VIA_CODEC_CTL, 2); } static kobj_method_t via_ac97_methods[] = { KOBJMETHOD(ac97_read, via_read_codec), KOBJMETHOD(ac97_write, via_write_codec), { 0, 0 } }; AC97_DECLARE(via_ac97); /* -------------------------------------------------------------------- */ static int via_buildsgdt(struct via_chinfo *ch) { u_int32_t phys_addr, flag; int i, segs, seg_size; /* * Build the scatter/gather DMA (SGD) table. * There are four slots in the table: two for play, two for record. * This creates two half-buffers, one of which is playing; the other * is feeding. */ seg_size = ch->blksz; segs = sndbuf_getsize(ch->buffer) / seg_size; phys_addr = sndbuf_getbufaddr(ch->buffer); for (i = 0; i < segs; i++) { flag = (i == segs - 1)? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ch->sgd_table[i].ptr = phys_addr + (i * seg_size); ch->sgd_table[i].flags = flag | seg_size; } return 0; } /* channel interface */ static void * viachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; snd_mtxlock(via->lock); if (dir == PCMDIR_PLAY) { ch = &via->pch; ch->base = VIA_PLAY_DMAOPS_BASE; ch->count = VIA_PLAY_DMAOPS_COUNT; ch->ctrl = VIA_PLAY_CONTROL; ch->mode = VIA_PLAY_MODE; ch->sgd_addr = via->sgd_addr; ch->sgd_table = &via->sgd_table[0]; } else { ch = &via->rch; ch->base = VIA_RECORD_DMAOPS_BASE; ch->count = VIA_RECORD_DMAOPS_COUNT; ch->ctrl = VIA_RECORD_CONTROL; ch->mode = VIA_RECORD_MODE; ch->sgd_addr = via->sgd_addr + sizeof(struct via_dma_op) * SEGS_PER_CHAN; ch->sgd_table = &via->sgd_table[SEGS_PER_CHAN]; } ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return NULL; return ch; } static int viachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int mode, mode_set; mode_set = 0; if (format & AFMT_STEREO) mode_set |= VIA_RPMODE_STEREO; if (format & AFMT_S16_LE) mode_set |= VIA_RPMODE_16BIT; DEB(printf("set format: dir = %d, format=%x\n", ch->dir, format)); snd_mtxlock(via->lock); mode = via_rd(via, ch->mode, 1); mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO); mode |= mode_set; via_wr(via, ch->mode, mode, 1); snd_mtxunlock(via->lock); return 0; } static int viachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int reg; /* * Basic AC'97 defines a 48 kHz sample rate only. For other rates, * upsampling is required. * * The VT82C686A does not perform upsampling, and neither do we. * If the codec supports variable-rate audio (i.e. does the upsampling * itself), then negotiate the rate with the codec. Otherwise, * return 48 kHz cuz that's all you got. */ if (via->codec_caps & AC97_EXTCAP_VRA) { reg = (ch->dir == PCMDIR_PLAY)? AC97_REGEXT_FDACRATE : AC97_REGEXT_LADCRATE; return ac97_setrate(via->codec, reg, speed); } else return 48000; } static int viachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct via_chinfo *ch = data; ch->blksz = blocksize; sndbuf_resize(ch->buffer, SEGS_PER_CHAN, ch->blksz); return ch->blksz; } static int viachan_trigger(kobj_t obj, void *data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; struct via_dma_op *ado; bus_addr_t sgd_addr = ch->sgd_addr; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ado = ch->sgd_table; DEB(printf("ado located at va=%p pa=%x\n", ado, sgd_addr)); snd_mtxlock(via->lock); if (go == PCMTRIG_START) { via_buildsgdt(ch); via_wr(via, ch->base, sgd_addr, 4); via_wr(via, ch->ctrl, VIA_RPCTRL_START, 1); } else via_wr(via, ch->ctrl, VIA_RPCTRL_TERMINATE, 1); snd_mtxunlock(via->lock); DEB(printf("viachan_trigger: go=%d\n", go)); return 0; } static int viachan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; struct via_dma_op *ado; bus_addr_t sgd_addr = ch->sgd_addr; int ptr, base, base1, len, seg; ado = ch->sgd_table; snd_mtxlock(via->lock); base1 = via_rd(via, ch->base, 4); len = via_rd(via, ch->count, 4); base = via_rd(via, ch->base, 4); if (base != base1) /* Avoid race hazard */ len = via_rd(via, ch->count, 4); snd_mtxunlock(via->lock); DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base)); /* Base points to SGD segment to do, one past current */ /* Determine how many segments have been done */ seg = (base - sgd_addr) / sizeof(struct via_dma_op); if (seg == 0) seg = SEGS_PER_CHAN; /* Now work out offset: seg less count */ ptr = (seg * sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN) - len; if (ch->dir == PCMDIR_REC) { /* DMA appears to operate on memory 'lines' of 32 bytes */ /* so don't return any part line - it isn't in RAM yet */ ptr = ptr & ~0x1f; } DEB(printf("return ptr=%d\n", ptr)); return ptr; } static struct pcmchan_caps * viachan_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; return (via->codec_caps & AC97_EXTCAP_VRA)? &via_vracaps : &via_caps; } static kobj_method_t viachan_methods[] = { KOBJMETHOD(channel_init, viachan_init), KOBJMETHOD(channel_setformat, viachan_setformat), KOBJMETHOD(channel_setspeed, viachan_setspeed), KOBJMETHOD(channel_setblocksize, viachan_setblocksize), KOBJMETHOD(channel_trigger, viachan_trigger), KOBJMETHOD(channel_getptr, viachan_getptr), KOBJMETHOD(channel_getcaps, viachan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(viachan); /* -------------------------------------------------------------------- */ static void via_intr(void *p) { struct via_info *via = p; /* DEB(printf("viachan_intr\n")); */ /* Read channel */ snd_mtxlock(via->lock); if (via_rd(via, VIA_PLAY_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_PLAY_STAT, VIA_RPSTAT_INTR, 1); snd_mtxunlock(via->lock); chn_intr(via->pch.channel); snd_mtxlock(via->lock); } /* Write channel */ if (via_rd(via, VIA_RECORD_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_RECORD_STAT, VIA_RPSTAT_INTR, 1); snd_mtxunlock(via->lock); chn_intr(via->rch.channel); return; } snd_mtxunlock(via->lock); } /* * Probe and attach the card */ static int via_probe(device_t dev) { if (pci_get_devid(dev) == VIA_PCI_ID) { device_set_desc(dev, "VIA VT82C686A"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct via_info *via = (struct via_info *)p; via->sgd_addr = bds->ds_addr; } static int via_attach(device_t dev) { struct via_info *via = 0; char status[SND_STATUSLEN]; u_int32_t data, cnt; if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } via->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_via82c686 softc"); /* Get resources */ data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_AC97STATUS, 1); if ((data & VIA_AC97STATUS_RDY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Assert low */ DELAY(100); /* Wait T_rst_low */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN | VIA_ACLINK_NRST, 1); /* Assert high */ DELAY(5); /* Wait T_rst2clk */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Assert low */ } else { /* Warm reset */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Force no sync */ DELAY(100); pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN | VIA_ACLINK_SYNC, 1); /* Sync */ DELAY(5); /* Wait T_sync_high */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Force no sync */ DELAY(5); /* Wait T_sync2clk */ } /* Power everything up */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_DESIRED, 1); /* Wait for codec to become ready (largest reported delay here 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_AC97STATUS, 1); if (data & VIA_AC97STATUS_RDY) break; DELAY(5000); } via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); if (!via->reg) { device_printf(dev, "cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } via_wr(via, VIA_PLAY_MODE, VIA_RPMODE_AUTOSTART | VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1); via_wr(via, VIA_RECORD_MODE, VIA_RPMODE_AUTOSTART | VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1); via->codec = AC97_CREATE(dev, via, via_ac97); if (!via->codec) goto bad; if (mixer_init(dev, ac97_getmixerclass(), via->codec)) goto bad; via->codec_caps = ac97_getextcaps(via->codec); ac97_setextmode(via->codec, via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* * DMA tag for SGD table. The 686 uses scatter/gather DMA and * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/NSEGS * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, BUS_DMA_NOWAIT, &via->sgd_dmamap) != 0) goto bad; if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, NSEGS * sizeof(struct via_dma_op), dma_cb, via, 0) != 0) goto bad; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(via->reg), rman_get_start(via->irq), PCM_KLDSTRING(snd_via82c686)); /* Register */ if (pcm_register(dev, via, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &viachan_class, via); pcm_addchan(dev, PCMDIR_REC, &viachan_class, via); pcm_setstatus(dev, status); return 0; bad: if (via->codec) ac97_destroy(via->codec); if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return ENXIO; } static int via_detach(device_t dev) { int r; struct via_info *via = 0; r = pcm_unregister(dev); if (r) return r; via = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); return 0; } static device_method_t via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), DEVMETHOD(device_detach, via_detach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_via82c686, pci, via_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_via82c686, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_via82c686, 1); Index: head/sys/dev/sound/pci/vibes.c =================================================================== --- head/sys/dev/sound/pci/vibes.c (revision 170520) +++ head/sys/dev/sound/pci/vibes.c (revision 170521) @@ -1,945 +1,947 @@ /*- * 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. */ #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* ------------------------------------------------------------------------- */ /* 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[] = { AFMT_U8, AFMT_U8 | AFMT_STEREO, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, 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 = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; ch->dma_active = ch->dma_was_active = 0; return ch; } static struct pcmchan_caps * svchan_getcaps(kobj_t obj, void *data) { return &sc_caps; } static int 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 = (format & AFMT_STEREO) ? SV_AFMT_STEREO : SV_AFMT_MONO; ch->fmt |= (format & AFMT_16BIT) ? SV_AFMT_S16 : SV_AFMT_U8; return 0; } static int svchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; RANGE(speed, 8000, 48000); ch->spd = speed; return speed; } /* ------------------------------------------------------------------------- */ /* Recording interface */ static int sv_set_recspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t f_out, f_actual; u_int32_t rs, re, r, best_r = 0, r2, t, n, best_n = 0; int32_t m, best_m = 0, ms, me, err, min_err; /* This algorithm is a variant described in sonicvibes.pdf * appendix A. This search is marginally more extensive and * results in (nominally) better sample rate matching. */ f_out = SV_F_SCALE * speed; min_err = 0x7fffffff; /* Find bounds of r to examine, rs <= r <= re */ t = 80000000 / f_out; for (rs = 1; (1 << rs) < t; rs++); t = 150000000 / f_out; for (re = 1; (2 << re) < t; re++); if (re > 7) re = 7; /* Search over r, n, m */ for (r = rs; r <= re; r++) { r2 = (1 << r); for (n = 3; n < 34; n++) { m = f_out * n / (SV_F_REF / r2); ms = (m > 3) ? (m - 1) : 3; me = (m < 129) ? (m + 1) : 129; for (m = ms; m <= me; m++) { f_actual = m * SV_F_REF / (n * r2); if (f_actual > f_out) { err = f_actual - f_out; } else { err = f_out - f_actual; } if (err < min_err) { best_r = r; best_m = m - 2; best_n = n - 2; min_err = err; if (err == 0) break; } } } } sv_indirect_set(sc, SV_REG_ADC_PLLM, best_m); sv_indirect_set(sc, SV_REG_ADC_PLLN, SV_ADC_PLLN(best_n) | SV_ADC_PLLR(best_r)); DEB(printf("svrchan_setspeed: %d -> PLLM 0x%02x PLLNR 0x%08x\n", speed, sv_indirect_get(sc, SV_REG_ADC_PLLM), sv_indirect_get(sc, SV_REG_ADC_PLLN))); return 0; } static int svrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ sv_set_recspeed(sc, ch->spd); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAC_MSK; v |= SV_AFMT_DMAC(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ count = sndbuf_getsize(ch->buffer) / 2; /* DMAC uses words */ sv_dma_set_config(sc->dmac_st, sc->dmac_sh, sndbuf_getbufaddr(ch->buffer), count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_RD); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAC_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAC_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE) | SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static int svrchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; sz = sndbuf_getsize(ch->buffer); /* DMAC uses words */ remain = (sv_dma_get_count(sc->dmac_st, sc->dmac_sh) + 1) * 2; return sz - remain; } static kobj_method_t svrchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svrchan_trigger), KOBJMETHOD(channel_getptr, svrchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(svrchan); /* ------------------------------------------------------------------------- */ /* Playback interface */ static int svpchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable, speed; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ speed = (ch->spd * 65536) / 48000; if (speed > 65535) speed = 65535; sv_indirect_set(sc, SV_REG_PCM_SAMPLING_HI, speed >> 8); sv_indirect_set(sc, SV_REG_PCM_SAMPLING_LO, speed & 0xff); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAA_MSK; v |= SV_AFMT_DMAA(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ count = sndbuf_getsize(ch->buffer); sv_dma_set_config(sc->dmaa_st, sc->dmaa_sh, sndbuf_getbufaddr(ch->buffer), count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_WR); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAA_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAA_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE); enable = (enable | SV_PLAY_ENABLE) & ~SV_PLAYBACK_PAUSE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_PLAY_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static int svpchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; sz = sndbuf_getsize(ch->buffer); /* DMAA uses bytes */ remain = sv_dma_get_count(sc->dmaa_st, sc->dmaa_sh) + 1; return (sz - remain); } static kobj_method_t svpchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svpchan_trigger), KOBJMETHOD(channel_getptr, svpchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), { 0, 0 } }; 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 int 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), { 0, 0 } }; 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; u_int32_t data; char status[SND_STATUSLEN]; u_long midi_start, games_start, count, sdmaa, sdmac, ml, mu; sc = malloc(sizeof(struct sc_info), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc == NULL) { device_printf(dev, "cannot allocate softc"); return ENXIO; } sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); #if __FreeBSD_version > 500000 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); } #endif sc->enh_rid = SV_PCI_ENHANCED; sc->enh_type = SYS_RES_IOPORT; sc->enh_reg = bus_alloc_resource(dev, sc->enh_type, &sc->enh_rid, 0, ~0, SV_PCI_ENHANCED_SIZE, 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(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, 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*/busdma_lock_mutex, /*lockarg*/&Giant, &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", midi_start, 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(dev, sc->dmaa_type, &sc->dmaa_rid, 0, ~0, SV_PCI_ENHANCED_SIZE, 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(dev, sc->dmac_type, &sc->dmac_rid, 0, ~0, SV_PCI_ENHANCED_SIZE, RF_ACTIVE); if (sc->dmac_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmac\n"); goto fail; } sc->dmac_st = rman_get_bustag(sc->dmac_reg); sc->dmac_sh = rman_get_bushandle(sc->dmac_reg); /* Poke port into dma_c configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAC, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmac & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAC, data, 4); DEB(printf("dmac: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAC, 4))); if (bootverbose) printf("Sonicvibes: revision %d.\n", sc->rev); if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "sv_attach: pcm_register fail\n"); goto fail; } pcm_addchan(dev, PCMDIR_PLAY, &svpchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &svrchan_class, sc); snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(sc->enh_reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_vibes)); pcm_setstatus(dev, status); DEB(printf("sv_attach: succeeded\n")); return 0; fail: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->enh_reg) bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); if (sc->dmaa_reg) bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); if (sc->dmac_reg) bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); return ENXIO; } static int sv_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); free(sc, M_DEVBUF); return 0; } static device_method_t sc_methods[] = { DEVMETHOD(device_probe, sv_probe), DEVMETHOD(device_attach, sv_attach), DEVMETHOD(device_detach, sv_detach), DEVMETHOD(device_resume, sv_resume), DEVMETHOD(device_suspend, sv_suspend), { 0, 0 } }; static driver_t sonicvibes_driver = { "pcm", sc_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_vibes, pci, sonicvibes_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_vibes, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_vibes, 1); Index: head/sys/dev/sound/pcm/channel.c =================================================================== --- head/sys/dev/sound/pcm/channel.c (revision 170520) +++ head/sys/dev/sound/pcm/channel.c (revision 170521) @@ -1,2253 +1,2251 @@ /*- * Copyright (c) 1999 Cameron Grant * Portions Copyright by Luigi Rizzo - 1997-99 * 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. */ #include "opt_isa.h" #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ #if 0 #define DMA_ALIGN_THRESHOLD 4 #define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) #endif #define CHN_STARTED(c) ((c)->flags & CHN_F_TRIGGERED) #define CHN_STOPPED(c) (!CHN_STARTED(c)) #define CHN_DIRSTR(c) (((c)->direction == PCMDIR_PLAY) ? \ "PCMDIR_PLAY" : "PCMDIR_REC") #define BUF_PARENT(c, b) \ (((c) != NULL && (c)->parentchannel != NULL && \ (c)->parentchannel->bufhard != NULL) ? \ (c)->parentchannel->bufhard : (b)) #define CHN_TIMEOUT 5 #define CHN_TIMEOUT_MIN 1 #define CHN_TIMEOUT_MAX 10 /* #define DEB(x) x */ int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 1, "report software-emulated formats"); int chn_latency = CHN_LATENCY_DEFAULT; TUNABLE_INT("hw.snd.latency", &chn_latency); static int sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) err = EINVAL; else chn_latency = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_latency, "I", "buffering latency (0=low ... 10=high)"); int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; TUNABLE_INT("hw.snd.latency_profile", &chn_latency_profile); static int sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency_profile; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) err = EINVAL; else chn_latency_profile = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", "buffering latency profile (0=aggresive 1=safe)"); static int chn_timeout = CHN_TIMEOUT; TUNABLE_INT("hw.snd.timeout", &chn_timeout); #ifdef SND_DEBUG static int sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_timeout; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX) err = EINVAL; else chn_timeout = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_timeout, "I", "interrupt timeout (1 - 10) seconds"); #endif static int chn_usefrags = 0; TUNABLE_INT("hw.snd.usefrags", &chn_usefrags); static int chn_syncdelay = -1; TUNABLE_INT("hw.snd.syncdelay", &chn_syncdelay); #ifdef SND_DEBUG SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RW, &chn_usefrags, 1, "prefer setfragments() over setblocksize()"); SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RW, &chn_syncdelay, 1, "append (0-1000) millisecond trailing buffer delay on each sync"); #endif /** * @brief Channel sync group lock * * Clients should acquire this lock @b without holding any channel locks * before touching syncgroups or the main syncgroup list. */ struct mtx snd_pcm_syncgroups_mtx; MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); /** * @brief syncgroups' master list * * Each time a channel syncgroup is created, it's added to this list. This * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. * * See SNDCTL_DSP_SYNCGROUP for more information. */ struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(head); static int chn_buildfeeder(struct pcm_channel *c); static void chn_lockinit(struct pcm_channel *c, int dir) { switch(dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); break; case PCMDIR_PLAY_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); break; case PCMDIR_REC_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); break; case 0: c->lock = snd_mtxcreate(c->name, "pcm fake channel"); break; } cv_init(&c->cv, c->name); } static void chn_lockdestroy(struct pcm_channel *c) { snd_mtxfree(c->lock); cv_destroy(&c->cv); } /** * @brief Determine channel is ready for I/O * * @retval 1 = ready for I/O * @retval 0 = not ready for I/O */ static int chn_polltrigger(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; unsigned amt, lim; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MAPPED) { if (sndbuf_getprevblocks(bs) == 0) return 1; else return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0; } else { amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); #if 0 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; #endif lim = c->lw; return (amt >= lim) ? 1 : 0; } return 0; } static int chn_pollreset(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); sndbuf_updateprevtotal(bs); return 1; } static void chn_wakeup(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; struct pcm_channel *ch; CHN_LOCKASSERT(c); if (CHN_EMPTY(c, children)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); } else if (CHN_EMPTY(c, children.busy)) { CHN_FOREACH(ch, c, children) { CHN_LOCK(ch); chn_wakeup(ch); CHN_UNLOCK(ch); } } else { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); chn_wakeup(ch); CHN_UNLOCK(ch); } } if (c->flags & CHN_F_SLEEPING) wakeup_one(bs); } static int chn_sleep(struct pcm_channel *c, char *str, int timeout) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); c->flags |= CHN_F_SLEEPING; #ifdef USING_MUTEX ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); #else ret = tsleep(bs, PRIBIO | PCATCH, str, timeout); #endif c->flags &= ~CHN_F_SLEEPING; return ret; } /* * chn_dmaupdate() tracks the status of a dma transfer, * updating pointers. */ static unsigned int chn_dmaupdate(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; unsigned int delta, old, hwptr, amt; KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0")); CHN_LOCKASSERT(c); old = sndbuf_gethwptr(b); hwptr = chn_getptr(c); delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); sndbuf_sethwptr(b, hwptr); DEB( if (delta >= ((sndbuf_getsize(b) * 15) / 16)) { if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr); } ); if (c->direction == PCMDIR_PLAY) { amt = min(delta, sndbuf_getready(b)); amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = min(delta, sndbuf_getfree(b)); amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) { device_printf(c->dev, "WARNING: %s DMA completion " "too fast/slow ! hwptr=%u, old=%u " "delta=%u amt=%u ready=%u free=%u\n", CHN_DIRSTR(c), hwptr, old, delta, amt, sndbuf_getready(b), sndbuf_getfree(b)); } return delta; } void chn_wrupdate(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_dmaupdate(c); ret = chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); DEB(if (ret) printf("chn_wrupdate: chn_wrfeed returned %d\n", ret);) } int chn_wrfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int ret, amt; CHN_LOCKASSERT(c); #if 0 DEB( if (c->flags & CHN_F_CLOSING) { sndbuf_dump(b, "b", 0x02); sndbuf_dump(bs, "bs", 0x02); }) #endif if ((c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_CLOSING)) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); amt = sndbuf_getfree(b); DEB(if (amt > sndbuf_getsize(bs) && sndbuf_getbps(bs) >= sndbuf_getbps(b)) { printf("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, amt, sndbuf_getsize(bs), c->flags); }); ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; /* * Possible xruns. There should be no empty space left in buffer. */ if (sndbuf_getfree(b) > 0) c->xruns++; if (sndbuf_getfree(b) < amt) chn_wakeup(c); return ret; } static void chn_wrintr(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from secondary to primary */ ret = chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); DEB(if (ret) printf("chn_wrintr: chn_wrfeed returned %d\n", ret);) } /* * user write routine - uiomove data into secondary buffer, trigger if necessary * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_write(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getfree(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getfreeptr(bs); t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_acquire(bs, NULL, t); } ret = 0; if (CHN_STOPPED(c)) chn_start(c, 0); } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) { /** * @todo Evaluate whether EAGAIN is truly desirable. * 4Front drivers behave like this, but I'm * not sure if it at all violates the "write * should be allowed to block" model. * * The idea is that, while set with CHN_F_NOTRIGGER, * a channel isn't playing, *but* without this we * end up with "interrupt timeout / channel dead". */ ret = EAGAIN; } else { ret = chn_sleep(c, "pcmwr", timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; printf("%s: play interrupt timeout, " "channel dead\n", c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return ret; } #if 0 static int chn_rddump(struct pcm_channel *c, unsigned int cnt) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); #if 0 static u_int32_t kk = 0; printf("%u: dumping %d bytes\n", ++kk, cnt); #endif c->xruns++; sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); return sndbuf_dispose(b, NULL, cnt); } #endif /* * Feed new data from the read buffer. Can be called in the bottom half. */ int chn_rdfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int ret, amt; CHN_LOCKASSERT(c); DEB( if (c->flags & CHN_F_CLOSING) { sndbuf_dump(b, "b", 0x02); sndbuf_dump(bs, "bs", 0x02); }) #if 0 amt = sndbuf_getready(b); if (sndbuf_getfree(bs) < amt) { c->xruns++; amt = sndbuf_getfree(bs); } #endif amt = sndbuf_getfree(bs); ret = (amt > 0) ? sndbuf_feed(b, bs, c, c->feeder, amt) : ENOSPC; amt = sndbuf_getready(b); if (amt > 0) { c->xruns++; sndbuf_dispose(b, NULL, amt); } if (sndbuf_getready(bs) > 0) chn_wakeup(c); return ret; } void chn_rdupdate(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_trigger(c, PCMTRIG_EMLDMARD); chn_dmaupdate(c); ret = chn_rdfeed(c); DEB(if (ret) printf("chn_rdfeed: %d\n", ret);) } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); /* tell the driver to update the primary buffer if non-dma */ chn_trigger(c, PCMTRIG_EMLDMARD); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from primary to secondary */ ret = chn_rdfeed(c); } /* * user read routine - trigger if necessary, uiomove data from secondary buffer * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_read(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); if (CHN_STOPPED(c)) chn_start(c, 0); ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getreadyptr(bs); t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_dispose(bs, NULL, t); } ret = 0; } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) ret = EAGAIN; else { ret = chn_sleep(c, "pcmrd", timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; printf("%s: record interrupt timeout, " "channel dead\n", c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return ret; } void chn_intr(struct pcm_channel *c) { CHN_LOCK(c); c->interrupts++; if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); CHN_UNLOCK(c); } u_int32_t chn_start(struct pcm_channel *c, int force) { u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return EINVAL; if (force) { i = 1; j = 0; } else { if (c->direction == PCMDIR_REC) { i = sndbuf_getfree(bs); j = (i > 0) ? 1 : sndbuf_getready(b); } else { if (sndbuf_getfree(bs) == 0) { i = 1; j = 0; } else { struct snd_dbuf *pb; pb = BUF_PARENT(c, b); i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb); j = sndbuf_getbps(pb); } } if (snd_verbose > 3 && CHN_EMPTY(c, children)) printf("%s: %s (%s) threshold i=%d j=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", i, j); } if (i >= j) { c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); c->feedcount = (c->flags & CHN_F_CLOSING) ? 2 : 0; c->interrupts = 0; c->xruns = 0; if (c->direction == PCMDIR_PLAY && c->parentchannel == NULL) { sndbuf_fillsilence(b); if (snd_verbose > 3) printf("%s: %s starting! (%s) (ready=%d " "force=%d i=%d j=%d intrtimeout=%u " "latency=%dms)\n", __func__, (c->flags & CHN_F_HAS_VCHAN) ? "VCHAN" : "HW", (c->flags & CHN_F_CLOSING) ? "closing" : "running", sndbuf_getready(b), force, i, j, c->timeout, (sndbuf_getsize(b) * 1000) / (sndbuf_getbps(b) * sndbuf_getspd(b))); } chn_trigger(c, PCMTRIG_START); - return 0; } return 0; } void chn_resetbuf(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; c->blocks = 0; sndbuf_reset(b); sndbuf_reset(bs); } /* * chn_sync waits until the space in the given channel goes above * a threshold. The threshold is checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */ int chn_sync(struct pcm_channel *c, int threshold) { struct snd_dbuf *b, *bs; int ret, count, hcount, minflush, resid, residp, syncdelay, blksz; u_int32_t cflag; CHN_LOCKASSERT(c); bs = c->bufsoft; if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) || (threshold < 1 && sndbuf_getready(bs) < 1)) return 0; if (c->direction != PCMDIR_PLAY) return EINVAL; /* if we haven't yet started and nothing is buffered, else start*/ if (CHN_STOPPED(c)) { if (threshold > 0 || sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret) return ret; } else return 0; } b = BUF_PARENT(c, c->bufhard); minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs); syncdelay = chn_syncdelay; if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0)) minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs); /* * Append (0-1000) millisecond trailing buffer (if needed) * for slower / high latency hardwares (notably USB audio) * to avoid audible truncation. */ if (syncdelay > 0) minflush += (sndbuf_getbps(bs) * sndbuf_getspd(bs) * ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000; minflush -= minflush % sndbuf_getbps(bs); if (minflush > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); minflush -= threshold; } resid = sndbuf_getready(bs); residp = resid; blksz = sndbuf_getblksz(b); if (blksz < 1) { printf("%s: WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n", __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b)); if (sndbuf_getblkcnt(b) > 0) blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b); if (blksz < 1) blksz = 1; } count = sndbuf_xbytes(minflush + resid, bs, b) / blksz; hcount = count; ret = 0; if (snd_verbose > 3) printf("%s: [begin] timeout=%d count=%d " "minflush=%d resid=%d\n", __func__, c->timeout, count, minflush, resid); cflag = c->flags & CHN_F_CLOSING; c->flags |= CHN_F_CLOSING; while (count > 0 && (resid > 0 || minflush > 0)) { ret = chn_sleep(c, "pcmsyn", c->timeout); if (ret == ERESTART || ret == EINTR) { c->flags |= CHN_F_ABORTING; break; } if (ret == 0 || ret == EAGAIN) { resid = sndbuf_getready(bs); if (resid == residp) { --count; if (snd_verbose > 3) printf("%s: [stalled] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } else if (resid < residp && count < hcount) { ++count; if (snd_verbose > 3) printf("%s: [resume] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } if (minflush > 0 && sndbuf_getfree(bs) > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); resid = sndbuf_getready(bs); minflush -= threshold; } residp = resid; } } c->flags &= ~CHN_F_CLOSING; c->flags |= cflag; if (snd_verbose > 3) printf("%s: timeout=%d count=%d hcount=%d resid=%d residp=%d " "minflush=%d ret=%d\n", __func__, c->timeout, count, hcount, resid, residp, minflush, ret); return 0; } /* called externally, handle locking */ int chn_poll(struct pcm_channel *c, int ev, struct thread *td) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); if (!(c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED))) chn_start(c, 1); ret = 0; if (chn_polltrigger(c) && chn_pollreset(c)) ret = ev; else selrecord(td, sndbuf_getsel(bs)); return ret; } /* * chn_abort terminates a running dma transfer. it may sleep up to 200ms. * it returns the number of bytes that have not been transferred. * * called from: dsp_close, dsp_ioctl, with channel locked */ int chn_abort(struct pcm_channel *c) { int missing = 0; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); if (CHN_STOPPED(c)) return 0; c->flags |= CHN_F_ABORTING; c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); missing = sndbuf_getready(bs); c->flags &= ~CHN_F_ABORTING; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close of a playback channel. * first, if there is data in the buffer, but the dma has not yet * begun, we need to start it. * next, we wait for the play buffer to drain * finally, we stop the dma. * * called from: dsp_close, not valid for record channels. */ int chn_flush(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); c->flags |= CHN_F_CLOSING; chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); c->flags &= ~CHN_F_CLOSING; return 0; } int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist) { int i; for (i = 0; fmtlist[i]; i++) if (fmt == fmtlist[i]) return 1; return 0; } static struct afmtstr_table default_afmtstr_table[] = { { "alaw", AFMT_A_LAW }, { "mulaw", AFMT_MU_LAW }, { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, { NULL, 0 }, }; int afmtstr_swap_sign(char *s) { if (s == NULL || strlen(s) < 2) /* full length of "s8" */ return 0; if (*s == 's') *s = 'u'; else if (*s == 'u') *s = 's'; else return 0; return 1; } int afmtstr_swap_endian(char *s) { if (s == NULL || strlen(s) < 5) /* full length of "s16le" */ return 0; if (s[3] == 'l') s[3] = 'b'; else if (s[3] == 'b') s[3] = 'l'; else return 0; return 1; } u_int32_t afmtstr2afmt(struct afmtstr_table *tbl, const char *s, int stereo) { size_t fsz, sz; sz = (s == NULL) ? 0 : strlen(s); if (sz > 1) { if (tbl == NULL) tbl = default_afmtstr_table; for (; tbl->fmtstr != NULL; tbl++) { fsz = strlen(tbl->fmtstr); if (sz < fsz) continue; if (strncmp(s, tbl->fmtstr, fsz) != 0) continue; if (fsz == sz) return tbl->format | ((stereo) ? AFMT_STEREO : 0); if ((sz - fsz) < 2 || s[fsz] != ':') break; /* * For now, just handle mono/stereo. */ if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 'm' || s[fsz + 1] == '1')) || strcmp(s + fsz + 1, "mono") == 0) return tbl->format; if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 's' || s[fsz + 1] == '2')) || strcmp(s + fsz + 1, "stereo") == 0) return tbl->format | AFMT_STEREO; break; } } return 0; } u_int32_t afmt2afmtstr(struct afmtstr_table *tbl, u_int32_t afmt, char *dst, size_t len, int type, int stereo) { u_int32_t fmt = 0; char *fmtstr = NULL, *tag = ""; if (tbl == NULL) tbl = default_afmtstr_table; for (; tbl->format != 0; tbl++) { if (tbl->format == 0) break; if ((afmt & ~AFMT_STEREO) != tbl->format) continue; fmt = afmt; fmtstr = tbl->fmtstr; break; } if (fmt != 0 && fmtstr != NULL && dst != NULL && len > 0) { strlcpy(dst, fmtstr, len); switch (type) { case AFMTSTR_SIMPLE: tag = (fmt & AFMT_STEREO) ? ":s" : ":m"; break; case AFMTSTR_NUM: tag = (fmt & AFMT_STEREO) ? ":2" : ":1"; break; case AFMTSTR_FULL: tag = (fmt & AFMT_STEREO) ? ":stereo" : ":mono"; break; case AFMTSTR_NONE: default: break; } if (strlen(tag) > 0 && ((stereo && !(fmt & AFMT_STEREO)) || \ (!stereo && (fmt & AFMT_STEREO)))) strlcat(dst, tag, len); } return fmt; } int chn_reset(struct pcm_channel *c, u_int32_t fmt) { int hwspd, r; CHN_LOCKASSERT(c); c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; c->timeout = 1; c->xruns = 0; r = CHANNEL_RESET(c->methods, c->devinfo); if (fmt != 0) { #if 0 hwspd = DSP_DEFAULT_SPEED; /* only do this on a record channel until feederbuilder works */ if (c->direction == PCMDIR_REC) RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); c->speed = hwspd; #endif hwspd = chn_getcaps(c)->minspeed; c->speed = hwspd; if (r == 0) r = chn_setformat(c, fmt); if (r == 0) r = chn_setspeed(c, hwspd); #if 0 if (r == 0) r = chn_setvolume(c, 100, 100); #endif } if (r == 0) r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); } return r; } int chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) { struct feeder_class *fc; struct snd_dbuf *b, *bs; int ret; if (chn_timeout < CHN_TIMEOUT_MIN || chn_timeout > CHN_TIMEOUT_MAX) chn_timeout = CHN_TIMEOUT; chn_lockinit(c, dir); b = NULL; bs = NULL; CHN_INIT(c, children); CHN_INIT(c, children.busy); c->devinfo = NULL; c->feeder = NULL; c->latency = -1; c->timeout = 1; ret = ENOMEM; b = sndbuf_create(c->dev, c->name, "primary", c); if (b == NULL) goto out; bs = sndbuf_create(c->dev, c->name, "secondary", c); if (bs == NULL) goto out; CHN_LOCK(c); ret = EINVAL; fc = feeder_getclass(NULL); if (fc == NULL) goto out; if (chn_addfeeder(c, fc, NULL)) goto out; /* * XXX - sndbuf_setup() & sndbuf_resize() expect to be called * with the channel unlocked because they are also called * from driver methods that don't know about locking */ CHN_UNLOCK(c); sndbuf_setup(bs, NULL, 0); CHN_LOCK(c); c->bufhard = b; c->bufsoft = bs; c->flags = 0; c->feederflags = 0; c->sm = NULL; ret = ENODEV; CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */ c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, direction); CHN_LOCK(c); if (c->devinfo == NULL) goto out; ret = ENOMEM; if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) goto out; ret = chn_setdir(c, direction); if (ret) goto out; ret = sndbuf_setfmt(b, AFMT_U8); if (ret) goto out; ret = sndbuf_setfmt(bs, AFMT_U8); if (ret) goto out; /** * @todo Should this be moved somewhere else? The primary buffer * is allocated by the driver or via DMA map setup, and tmpbuf * seems to only come into existence in sndbuf_resize(). */ if (c->direction == PCMDIR_PLAY) { bs->sl = sndbuf_getmaxsize(bs); bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_NOWAIT); if (bs->shadbuf == NULL) { ret = ENOMEM; goto out; } } out: CHN_UNLOCK(c); if (ret) { if (c->devinfo) { if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); } if (bs) sndbuf_destroy(bs); if (b) sndbuf_destroy(b); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); return ret; } return 0; } int chn_kill(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; if (CHN_STARTED(c)) { CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); CHN_UNLOCK(c); } while (chn_removefeeder(c) == 0) ; if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); c->flags |= CHN_F_DEAD; sndbuf_destroy(bs); sndbuf_destroy(b); chn_lockdestroy(c); return 0; } int chn_setdir(struct pcm_channel *c, int dir) { #ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; #endif int r; CHN_LOCKASSERT(c); c->direction = dir; r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction); #ifdef DEV_ISA if (!r && SND_DMA(b)) sndbuf_dmasetdir(b, c->direction); #endif return r; } int chn_setvolume(struct pcm_channel *c, int left, int right) { CHN_LOCKASSERT(c); /* should add a feeder for volume changing if channel returns -1 */ if (left > 100) left = 100; if (left < 0) left = 0; if (right > 100) right = 100; if (right < 0) right = 0; c->volume = left | (right << 8); return 0; } static u_int32_t round_pow2(u_int32_t v) { u_int32_t ret; if (v < 2) v = 2; ret = 0; while (v >> ret) ret++; ret = 1 << (ret - 1); while (ret < v) ret <<= 1; return ret; } static u_int32_t round_blksz(u_int32_t v, int round) { u_int32_t ret, tmp; if (round < 1) round = 1; ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) ret >>= 1; tmp = ret - (ret % round); while (tmp < 16 || tmp < round) { ret <<= 1; tmp = ret - (ret % round); } return ret; } /* * 4Front call it DSP Policy, while we call it "Latency Profile". The idea * is to keep 2nd buffer short so that it doesn't cause long queue during * buffer transfer. * * Latency reference table for 48khz stereo 16bit: (PLAY) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 2 | 64 | 128 | * +---------+------------+-----------+------------+ * | 1 | 4 | 128 | 512 | * +---------+------------+-----------+------------+ * | 2 | 8 | 512 | 4096 | * +---------+------------+-----------+------------+ * | 3 | 16 | 512 | 8192 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Recording need a different reference table. All we care is * gobbling up everything within reasonable buffering threshold. * * Latency reference table for 48khz stereo 16bit: (REC) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 512 | 32 | 16384 | * +---------+------------+-----------+------------+ * | 1 | 256 | 64 | 16384 | * +---------+------------+-----------+------------+ * | 2 | 128 | 128 | 16384 | * +---------+------------+-----------+------------+ * | 3 | 64 | 256 | 16384 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Calculations for other data rate are entirely based on these reference * tables. For normal operation, Latency 5 seems give the best, well * balanced performance for typical workload. Anything below 5 will * eat up CPU to keep up with increasing context switches because of * shorter buffer space and usually require the application to handle it * aggresively through possibly real time programming technique. * */ #define CHN_LATENCY_PBLKCNT_REF \ {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_PBUFSZ_REF \ {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_RBLKCNT_REF \ {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_RBUFSZ_REF \ {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ static int chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, u_int32_t max, int *rblksz, int *rblkcnt) { static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBLKCNT_REF; static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBUFSZ_REF; static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBLKCNT_REF; static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBUFSZ_REF; u_int32_t bufsz; int lprofile, blksz, blkcnt; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || bps < 1 || datarate < 1 || !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { if (rblksz != NULL) *rblksz = CHN_2NDBUFMAXSIZE >> 1; if (rblkcnt != NULL) *rblkcnt = 2; printf("%s: FAILED dir=%d latency=%d bps=%d " "datarate=%u max=%u\n", __func__, dir, latency, bps, datarate, max); return CHN_2NDBUFMAXSIZE; } lprofile = chn_latency_profile; if (dir == PCMDIR_PLAY) { blkcnt = pblkcnts[lprofile][latency]; bufsz = pbufszs[lprofile][latency]; } else { blkcnt = rblkcnts[lprofile][latency]; bufsz = rbufszs[lprofile][latency]; } bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate)); if (bufsz > max) bufsz = max; blksz = round_blksz(bufsz >> blkcnt, bps); if (rblksz != NULL) *rblksz = blksz; if (rblkcnt != NULL) *rblkcnt = 1 << blkcnt; return blksz << blkcnt; } static int chn_resizebuf(struct pcm_channel *c, int latency, int blkcnt, int blksz) { struct snd_dbuf *b, *bs, *pb; int sblksz, sblkcnt, hblksz, hblkcnt, limit = 1; int ret; CHN_LOCKASSERT(c); if ((c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED)) || !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) return EINVAL; if (latency == -1) { c->latency = -1; latency = chn_latency; } else if (latency == -2) { latency = c->latency; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) latency = chn_latency; } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) return EINVAL; else { c->latency = latency; limit = 0; } bs = c->bufsoft; b = c->bufhard; if (!(blksz == 0 || blkcnt == -1) && (blksz < 16 || blksz < sndbuf_getbps(bs) || blkcnt < 2 || (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) return EINVAL; chn_calclatency(c->direction, latency, sndbuf_getbps(bs), sndbuf_getbps(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, &sblksz, &sblkcnt); if (blksz == 0 || blkcnt == -1) { if (blkcnt == -1) c->flags &= ~CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { blksz = sndbuf_getblksz(bs); blkcnt = sndbuf_getblkcnt(bs); } } else c->flags |= CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { /* * The application has requested their own blksz/blkcnt. * Just obey with it, and let them toast alone. We can * clamp it to the nearest latency profile, but that would * defeat the purpose of having custom control. The least * we can do is round it to the nearest ^2 and align it. */ sblksz = round_blksz(blksz, sndbuf_getbps(bs)); sblkcnt = round_pow2(blkcnt); limit = 0; } if (c->parentchannel != NULL) { pb = BUF_PARENT(c, NULL); CHN_UNLOCK(c); chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); CHN_LOCK(c); limit = (limit != 0 && pb != NULL) ? sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; c->timeout = c->parentchannel->timeout; } else { hblkcnt = 2; if (c->flags & CHN_F_HAS_SIZE) { hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), sndbuf_getbps(b)); hblkcnt = round_pow2(sndbuf_getblkcnt(bs)); } else chn_calclatency(c->direction, latency, sndbuf_getbps(b), sndbuf_getbps(b) * sndbuf_getspd(b), CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); if ((hblksz << 1) > sndbuf_getmaxsize(b)) hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1, sndbuf_getbps(b)); while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) { if (hblkcnt < 4) hblksz >>= 1; else hblkcnt >>= 1; } hblksz -= hblksz % sndbuf_getbps(b); #if 0 hblksz = sndbuf_getmaxsize(b) >> 1; hblksz -= hblksz % sndbuf_getbps(b); hblkcnt = 2; #endif CHN_UNLOCK(c); if (chn_usefrags == 0 || CHANNEL_SETFRAGMENTS(c->methods, c->devinfo, hblksz, hblkcnt) < 1) sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, hblksz)); CHN_LOCK(c); if (!CHN_EMPTY(c, children)) { sblksz = round_blksz( sndbuf_xbytes(sndbuf_getsize(b) >> 1, b, bs), sndbuf_getbps(bs)); sblkcnt = 2; limit = 0; } else if (limit != 0) limit = sndbuf_xbytes(sndbuf_getsize(b), b, bs); /* * Interrupt timeout */ c->timeout = ((u_int64_t)hz * sndbuf_getsize(b)) / ((u_int64_t)sndbuf_getspd(b) * sndbuf_getbps(b)); if (c->timeout < 1) c->timeout = 1; } if (limit > CHN_2NDBUFMAXSIZE) limit = CHN_2NDBUFMAXSIZE; #if 0 while (limit > 0 && (sblksz * sblkcnt) > limit) { if (sblkcnt < 4) break; sblkcnt >>= 1; } #endif while ((sblksz * sblkcnt) < limit) sblkcnt <<= 1; while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { if (sblkcnt < 4) sblksz >>= 1; else sblkcnt >>= 1; } sblksz -= sblksz % sndbuf_getbps(bs); if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || sndbuf_getsize(bs) != (sblkcnt * sblksz)) { ret = sndbuf_remalloc(bs, sblkcnt, sblksz); if (ret != 0) { printf("%s: Failed: %d %d\n", __func__, sblkcnt, sblksz); return ret; } } /* * OSSv4 docs: "By default OSS will set the low water level equal * to the fragment size which is optimal in most cases." */ c->lw = sndbuf_getblksz(bs); chn_resetbuf(c); if (snd_verbose > 3) printf("%s: %s (%s) timeout=%u " "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", c->timeout, sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), limit); return 0; } int chn_setlatency(struct pcm_channel *c, int latency) { CHN_LOCKASSERT(c); /* Destroy blksz/blkcnt, enforce latency profile. */ return chn_resizebuf(c, latency, -1, 0); } int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) { CHN_LOCKASSERT(c); /* Destroy latency profile, enforce blksz/blkcnt */ return chn_resizebuf(c, -1, blkcnt, blksz); } static int chn_tryspeed(struct pcm_channel *c, int speed) { struct pcm_feeder *f; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; struct snd_dbuf *x; int r, delta; CHN_LOCKASSERT(c); DEB(printf("setspeed, channel %s\n", c->name)); DEB(printf("want speed %d, ", speed)); if (speed <= 0) return EINVAL; if (CHN_STOPPED(c)) { r = 0; c->speed = speed; sndbuf_setspd(bs, speed); RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); DEB(printf("try speed %d, ", speed)); sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed)); DEB(printf("got speed %d\n", sndbuf_getspd(b))); delta = sndbuf_getspd(b) - sndbuf_getspd(bs); if (delta < 0) delta = -delta; c->feederflags &= ~(1 << FEEDER_RATE); /* * Used to be 500. It was too big! */ if (delta > feeder_rate_round) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); r = chn_buildfeeder(c); DEB(printf("r = %d\n", r)); if (r) goto out; if (!(c->feederflags & (1 << FEEDER_RATE))) goto out; r = EINVAL; f = chn_findfeeder(c, FEEDER_RATE); DEB(printf("feedrate = %p\n", f)); if (f == NULL) goto out; x = (c->direction == PCMDIR_REC)? b : bs; r = FEEDER_SET(f, FEEDRATE_SRC, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_SRC, %d) = %d\n", sndbuf_getspd(x), r)); if (r) goto out; x = (c->direction == PCMDIR_REC)? bs : b; r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r)); out: if (!r) r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b)); if (!r) sndbuf_setfmt(bs, c->format); if (!r) r = chn_resizebuf(c, -2, 0, 0); DEB(printf("setspeed done, r = %d\n", r)); return r; } else return EINVAL; } int chn_setspeed(struct pcm_channel *c, int speed) { int r, oldspeed = c->speed; r = chn_tryspeed(c, speed); if (r) { if (snd_verbose > 3) printf("Failed to set speed %d falling back to %d\n", speed, oldspeed); r = chn_tryspeed(c, oldspeed); } return r; } static int chn_tryformat(struct pcm_channel *c, u_int32_t fmt) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; int r; CHN_LOCKASSERT(c); if (CHN_STOPPED(c)) { DEB(printf("want format %d\n", fmt)); c->format = fmt; r = chn_buildfeeder(c); if (r == 0) { sndbuf_setfmt(bs, c->format); chn_resetbuf(c); r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b)); if (r == 0) r = chn_tryspeed(c, c->speed); } return r; } else return EINVAL; } int chn_setformat(struct pcm_channel *c, u_int32_t fmt) { u_int32_t oldfmt = c->format; int r; r = chn_tryformat(c, fmt); if (r) { if (snd_verbose > 3) printf("Format change 0x%08x failed, reverting to 0x%08x\n", fmt, oldfmt); chn_tryformat(c, oldfmt); } return r; } int chn_trigger(struct pcm_channel *c, int go) { #ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; #endif struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); #ifdef DEV_ISA if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) sndbuf_dmabounce(b); #endif - if ((go == PCMTRIG_START || go == PCMTRIG_STOP || - go == PCMTRIG_ABORT) && go == c->trigger) - return 0; + if (PCMTRIG_COMMON(go) && go == c->trigger) + return (0); ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); if (ret == 0) { switch (go) { case PCMTRIG_START: if (snd_verbose > 3) device_printf(c->dev, "%s() %s: calling go=0x%08x , " "prev=0x%08x\n", __func__, c->name, go, c->trigger); if (c->trigger != PCMTRIG_START) { c->trigger = go; CHN_UNLOCK(c); pcm_lock(d); CHN_INSERT_HEAD(d, c, channels.pcm.busy); pcm_unlock(d); CHN_LOCK(c); } break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (snd_verbose > 3) device_printf(c->dev, "%s() %s: calling go=0x%08x , " "prev=0x%08x\n", __func__, c->name, go, c->trigger); if (c->trigger == PCMTRIG_START) { c->trigger = go; CHN_UNLOCK(c); pcm_lock(d); CHN_REMOVE(d, c, channels.pcm.busy); pcm_unlock(d); CHN_LOCK(c); } break; default: break; } } - return ret; + return (ret); } /** * @brief Queries sound driver for sample-aligned hardware buffer pointer index * * This function obtains the hardware pointer location, then aligns it to * the current bytes-per-sample value before returning. (E.g., a channel * running in 16 bit stereo mode would require 4 bytes per sample, so a * hwptr value ranging from 32-35 would be returned as 32.) * * @param c PCM channel context * @returns sample-aligned hardware buffer pointer index */ int chn_getptr(struct pcm_channel *c) { #if 0 int hwptr; int a = (1 << c->align) - 1; CHN_LOCKASSERT(c); hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; /* don't allow unaligned values in the hwa ptr */ #if 1 hwptr &= ~a ; /* Apply channel align mask */ #endif hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ return hwptr; #endif int hwptr; CHN_LOCKASSERT(c); hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; return (hwptr - (hwptr % sndbuf_getbps(c->bufhard))); } struct pcmchan_caps * chn_getcaps(struct pcm_channel *c) { CHN_LOCKASSERT(c); return CHANNEL_GETCAPS(c->methods, c->devinfo); } u_int32_t chn_getformats(struct pcm_channel *c) { u_int32_t *fmtlist, fmts; int i; fmtlist = chn_getcaps(c)->fmtlist; fmts = 0; for (i = 0; fmtlist[i]; i++) fmts |= fmtlist[i]; /* report software-supported formats */ if (report_soft_formats) fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U32_LE|AFMT_U32_BE| AFMT_S32_LE|AFMT_S32_BE|AFMT_U24_LE|AFMT_U24_BE| AFMT_S24_LE|AFMT_S24_BE|AFMT_U16_LE|AFMT_U16_BE| AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8; return fmts; } static int chn_buildfeeder(struct pcm_channel *c) { struct feeder_class *fc; struct pcm_feederdesc desc; u_int32_t tmp[2], type, flags, hwfmt, *fmtlist; int err; char fmtstr[AFMTSTR_MAXSZ]; CHN_LOCKASSERT(c); while (chn_removefeeder(c) == 0) ; KASSERT((c->feeder == NULL), ("feeder chain not empty")); c->align = sndbuf_getalign(c->bufsoft); if (CHN_EMPTY(c, children) || c->direction == PCMDIR_REC) { /* * Virtual rec need this. */ fc = feeder_getclass(NULL); KASSERT(fc != NULL, ("can't find root feeder")); err = chn_addfeeder(c, fc, NULL); if (err) { DEB(printf("can't add root feeder, err %d\n", err)); return err; } c->feeder->desc->out = c->format; } else if (c->direction == PCMDIR_PLAY) { if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; desc.in = c->format; } else { DEB(printf("can't decide which feeder type to use!\n")); return EOPNOTSUPP; } desc.out = c->format; desc.flags = 0; fc = feeder_getclass(&desc); if (fc == NULL) { DEB(printf("can't find vchan feeder\n")); return EOPNOTSUPP; } err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add vchan feeder, err %d\n", err)); return err; } } else return EOPNOTSUPP; c->feederflags &= ~(1 << FEEDER_VOLUME); if (c->direction == PCMDIR_PLAY && !(c->flags & CHN_F_VIRTUAL) && c->parentsnddev && (c->parentsnddev->flags & SD_F_SOFTPCMVOL) && c->parentsnddev->mixer_dev) c->feederflags |= 1 << FEEDER_VOLUME; if (!(c->flags & CHN_F_VIRTUAL) && c->parentsnddev && ((c->direction == PCMDIR_PLAY && (c->parentsnddev->flags & SD_F_PSWAPLR)) || (c->direction == PCMDIR_REC && (c->parentsnddev->flags & SD_F_RSWAPLR)))) c->feederflags |= 1 << FEEDER_SWAPLR; flags = c->feederflags; fmtlist = chn_getcaps(c)->fmtlist; DEB(printf("feederflags %x\n", flags)); for (type = FEEDER_RATE; type < FEEDER_LAST; type++) { if (flags & (1 << type)) { desc.type = type; desc.in = 0; desc.out = 0; desc.flags = 0; DEB(printf("find feeder type %d, ", type)); if (type == FEEDER_VOLUME || type == FEEDER_RATE) { if (c->feeder->desc->out & AFMT_32BIT) strlcpy(fmtstr,"s32le", sizeof(fmtstr)); else if (c->feeder->desc->out & AFMT_24BIT) strlcpy(fmtstr, "s24le", sizeof(fmtstr)); else { /* * 8bit doesn't provide enough headroom * for proper processing without * creating too much noises. Force to * 16bit instead. */ strlcpy(fmtstr, "s16le", sizeof(fmtstr)); } if (!(c->feeder->desc->out & AFMT_8BIT) && c->feeder->desc->out & AFMT_BIGENDIAN) afmtstr_swap_endian(fmtstr); if (!(c->feeder->desc->out & (AFMT_A_LAW | AFMT_MU_LAW)) && !(c->feeder->desc->out & AFMT_SIGNED)) afmtstr_swap_sign(fmtstr); desc.in = afmtstr2afmt(NULL, fmtstr, AFMTSTR_MONO_RETURN); if (desc.in == 0) desc.in = AFMT_S16_LE; /* feeder_volume need stereo processing */ if (type == FEEDER_VOLUME || c->feeder->desc->out & AFMT_STEREO) desc.in |= AFMT_STEREO; desc.out = desc.in; } else if (type == FEEDER_SWAPLR) { desc.in = c->feeder->desc->out; desc.in |= AFMT_STEREO; desc.out = desc.in; } fc = feeder_getclass(&desc); DEB(printf("got %p\n", fc)); if (fc == NULL) { DEB(printf("can't find required feeder type %d\n", type)); return EOPNOTSUPP; } if (desc.in == 0 || desc.out == 0) desc = *fc->desc; DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in)); tmp[0] = desc.in; tmp[1] = 0; if (chn_fmtchain(c, tmp) == 0) { DEB(printf("failed\n")); return ENODEV; } DEB(printf("ok\n")); err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add feeder %p, output 0x%x, err %d\n", fc, fc->desc->out, err)); return err; } DEB(printf("added feeder %p, output 0x%x\n", fc, c->feeder->desc->out)); } } if (c->direction == PCMDIR_REC) { tmp[0] = c->format; tmp[1] = 0; hwfmt = chn_fmtchain(c, tmp); } else hwfmt = chn_fmtchain(c, fmtlist); if (hwfmt == 0 || !fmtvalid(hwfmt, fmtlist)) { DEB(printf("Invalid hardware format: 0x%08x\n", hwfmt)); return ENODEV; } else if (c->direction == PCMDIR_REC && !CHN_EMPTY(c, children)) { /* * Kind of awkward. This whole "MIXER" concept need a * rethinking, I guess :) . Recording is the inverse * of Playback, which is why we push mixer vchan down here. */ if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; desc.in = c->format; } else return EOPNOTSUPP; desc.out = c->format; desc.flags = 0; fc = feeder_getclass(&desc); if (fc == NULL) return EOPNOTSUPP; err = chn_addfeeder(c, fc, &desc); if (err != 0) return err; } sndbuf_setfmt(c->bufhard, hwfmt); if ((flags & (1 << FEEDER_VOLUME))) { u_int32_t parent = SOUND_MIXER_NONE; int vol, left, right; vol = 100 | (100 << 8); CHN_UNLOCK(c); /* * XXX This is ugly! The way mixer subs being so secretive * about its own internals force us to use this silly * monkey trick. */ if (mixer_ioctl(c->parentsnddev->mixer_dev, MIXER_READ(SOUND_MIXER_PCM), (caddr_t)&vol, -1, NULL) != 0) device_printf(c->dev, "Soft PCM Volume: Failed to read default value\n"); left = vol & 0x7f; right = (vol >> 8) & 0x7f; if (c->parentsnddev != NULL && c->parentsnddev->mixer_dev != NULL && c->parentsnddev->mixer_dev->si_drv1 != NULL) parent = mix_getparent( c->parentsnddev->mixer_dev->si_drv1, SOUND_MIXER_PCM); if (parent != SOUND_MIXER_NONE) { vol = 100 | (100 << 8); if (mixer_ioctl(c->parentsnddev->mixer_dev, MIXER_READ(parent), (caddr_t)&vol, -1, NULL) != 0) device_printf(c->dev, "Soft Volume: Failed to read parent default value\n"); left = (left * (vol & 0x7f)) / 100; right = (right * ((vol >> 8) & 0x7f)) / 100; } CHN_LOCK(c); chn_setvolume(c, left, right); } return 0; } int chn_notify(struct pcm_channel *c, u_int32_t flags) { int run; CHN_LOCK(c); if (CHN_EMPTY(c, children)) { CHN_UNLOCK(c); return ENODEV; } run = (CHN_STARTED(c)) ? 1 : 0; /* * if the hwchan is running, we can't change its rate, format or * blocksize */ if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { /* * we could do something here, like scan children and decide on * the most appropriate rate to mix at, but we don't for now */ } if (flags & CHN_N_FORMAT) { /* * we could do something here, like scan children and decide on * the most appropriate mixer feeder to use, but we don't for now */ } if (flags & CHN_N_VOLUME) { /* * we could do something here but we don't for now */ } if (flags & CHN_N_BLOCKSIZE) { /* * Set to default latency profile */ chn_setlatency(c, chn_latency); } if (flags & CHN_N_TRIGGER) { int nrun; nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) chn_start(c, 1); if (!nrun && run) chn_abort(c); } CHN_UNLOCK(c); return 0; } /** * @brief Fetch array of supported discrete sample rates * * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for * detailed information. * * @note If the operation isn't supported, this function will just return 0 * (no rates in the array), and *rates will be set to NULL. Callers * should examine rates @b only if this function returns non-zero. * * @param c pcm channel to examine * @param rates pointer to array of integers; rate table will be recorded here * * @return number of rates in the array pointed to be @c rates */ int chn_getrates(struct pcm_channel *c, int **rates) { KASSERT(rates != NULL, ("rates is null")); CHN_LOCKASSERT(c); return CHANNEL_GETRATES(c->methods, c->devinfo, rates); } /** * @brief Remove channel from a sync group, if there is one. * * This function is initially intended for the following conditions: * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) * - Closing a device. (A channel can't be destroyed if it's still in use.) * * @note Before calling this function, the syncgroup list mutex must be * held. (Consider pcm_channel::sm protected by the SG list mutex * whether @c c is locked or not.) * * @param c channel device to be started or closed * @returns If this channel was the only member of a group, the group ID * is returned to the caller so that the caller can release it * via free_unr() after giving up the syncgroup lock. Else it * returns 0. */ int chn_syncdestroy(struct pcm_channel *c) { struct pcmchan_syncmember *sm; struct pcmchan_syncgroup *sg; int sg_id; sg_id = 0; PCM_SG_LOCKASSERT(MA_OWNED); if (c->sm != NULL) { sm = c->sm; sg = sm->parent; c->sm = NULL; KASSERT(sg != NULL, ("syncmember has null parent")); SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); free(sm, M_DEVBUF); if (SLIST_EMPTY(&sg->members)) { SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); sg_id = sg->id; free(sg, M_DEVBUF); } } return sg_id; } void chn_lock(struct pcm_channel *c) { CHN_LOCK(c); } void chn_unlock(struct pcm_channel *c) { CHN_UNLOCK(c); } #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) { CHN_LOCKASSERT(c); return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); } #endif Index: head/sys/dev/sound/pcm/channel.h =================================================================== --- head/sys/dev/sound/pcm/channel.h (revision 170520) +++ head/sys/dev/sound/pcm/channel.h (revision 170521) @@ -1,345 +1,350 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 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. * * $FreeBSD$ */ struct pcmchan_caps { u_int32_t minspeed, maxspeed; u_int32_t *fmtlist; u_int32_t caps; }; /* Forward declarations */ struct pcm_channel; struct pcmchan_syncgroup; struct pcmchan_syncmember; extern struct mtx snd_pcm_syncgroups_mtx; extern SLIST_HEAD(pcm_synclist, pcmchan_syncgroup) snd_pcm_syncgroups; #define PCM_SG_LOCK() mtx_lock(&snd_pcm_syncgroups_mtx) #define PCM_SG_TRYLOCK() mtx_trylock(&snd_pcm_syncgroups_mtx) #define PCM_SG_UNLOCK() mtx_unlock(&snd_pcm_syncgroups_mtx) #define PCM_SG_LOCKASSERT(arg) mtx_assert(&snd_pcm_syncgroups_mtx, arg) /** * @brief Specifies an audio device sync group */ struct pcmchan_syncgroup { SLIST_ENTRY(pcmchan_syncgroup) link; SLIST_HEAD(, pcmchan_syncmember) members; int id; /**< Group identifier; set to address of group. */ }; /** * @brief Specifies a container for members of a sync group */ struct pcmchan_syncmember { SLIST_ENTRY(pcmchan_syncmember) link; struct pcmchan_syncgroup *parent; /**< group head */ struct pcm_channel *ch; }; #define CHN_NAMELEN 32 struct pcm_channel { kobj_t methods; pid_t pid; int refcount; struct pcm_feeder *feeder; u_int32_t align; int volume; int latency; u_int32_t speed; u_int32_t format; u_int32_t flags; u_int32_t feederflags; u_int32_t blocks; int direction; unsigned int interrupts, xruns, feedcount; unsigned int timeout; struct snd_dbuf *bufhard, *bufsoft; struct snddev_info *parentsnddev; struct pcm_channel *parentchannel; void *devinfo; device_t dev; int unit; char name[CHN_NAMELEN]; struct mtx *lock; int trigger; /** * Increment,decrement this around operations that temporarily yield * lock. */ unsigned int inprog; /** * Special channel operations should examine @c inprog after acquiring * lock. If zero, operations may continue. Else, thread should * wait on this cv for previous operation to finish. */ struct cv cv; /** * Low water mark for select()/poll(). * * This is initialized to the channel's fragment size, and will be * overwritten if a new fragment size is set. Users may alter this * value directly with the @c SNDCTL_DSP_LOW_WATER ioctl. */ unsigned int lw; /** * If part of a sync group, this will point to the syncmember * container. */ struct pcmchan_syncmember *sm; #ifdef OSSV4_EXPERIMENT u_int16_t lpeak, rpeak; /**< Peak value from 0-32767. */ #endif struct { SLIST_HEAD(, pcm_channel) head; SLIST_ENTRY(pcm_channel) link; struct { SLIST_HEAD(, pcm_channel) head; SLIST_ENTRY(pcm_channel) link; } busy; } children; struct { struct { SLIST_ENTRY(pcm_channel) link; struct { SLIST_ENTRY(pcm_channel) link; } busy; } pcm; } channels; void *data1, *data2; }; #define CHN_HEAD(x, y) &(x)->y.head #define CHN_INIT(x, y) SLIST_INIT(CHN_HEAD(x, y)) #define CHN_LINK(y) y.link #define CHN_EMPTY(x, y) SLIST_EMPTY(CHN_HEAD(x, y)) #define CHN_FIRST(x, y) SLIST_FIRST(CHN_HEAD(x, y)) #define CHN_FOREACH(x, y, z) \ SLIST_FOREACH(x, CHN_HEAD(y, z), CHN_LINK(z)) #define CHN_FOREACH_SAFE(w, x, y, z) \ SLIST_FOREACH_SAFE(w, CHN_HEAD(x, z), CHN_LINK(z), y) #define CHN_INSERT_HEAD(x, y, z) \ SLIST_INSERT_HEAD(CHN_HEAD(x, z), y, CHN_LINK(z)) #define CHN_INSERT_AFTER(x, y, z) \ SLIST_INSERT_AFTER(x, y, CHN_LINK(z)) #define CHN_REMOVE(x, y, z) \ SLIST_REMOVE(CHN_HEAD(x, z), y, pcm_channel, CHN_LINK(z)) #define CHN_INSERT_HEAD_SAFE(x, y, z) do { \ struct pcm_channel *t = NULL; \ CHN_FOREACH(t, x, z) { \ if (t == y) \ break; \ } \ if (t != y) { \ CHN_INSERT_HEAD(x, y, z); \ } \ } while(0) #define CHN_INSERT_AFTER_SAFE(w, x, y, z) do { \ struct pcm_channel *t = NULL; \ CHN_FOREACH(t, w, z) { \ if (t == y) \ break; \ } \ if (t != y) { \ CHN_INSERT_AFTER(x, y, z); \ } \ } while(0) #define CHN_REMOVE_SAFE(x, y, z) do { \ struct pcm_channel *t = NULL; \ CHN_FOREACH(t, x, z) { \ if (t == y) \ break; \ } \ if (t == y) { \ CHN_REMOVE(x, y, z); \ } \ } while(0) #define CHN_UNIT(x) (snd_unit2u((x)->unit)) #define CHN_DEV(x) (snd_unit2d((x)->unit)) #define CHN_CHAN(x) (snd_unit2c((x)->unit)) #include "channel_if.h" int chn_reinit(struct pcm_channel *c); int chn_write(struct pcm_channel *c, struct uio *buf); int chn_read(struct pcm_channel *c, struct uio *buf); u_int32_t chn_start(struct pcm_channel *c, int force); int chn_sync(struct pcm_channel *c, int threshold); int chn_flush(struct pcm_channel *c); int chn_poll(struct pcm_channel *c, int ev, struct thread *td); int chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction); int chn_kill(struct pcm_channel *c); int chn_setdir(struct pcm_channel *c, int dir); int chn_reset(struct pcm_channel *c, u_int32_t fmt); int chn_setvolume(struct pcm_channel *c, int left, int right); int chn_setspeed(struct pcm_channel *c, int speed); int chn_setformat(struct pcm_channel *c, u_int32_t fmt); int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); int chn_setlatency(struct pcm_channel *c, int latency); int chn_trigger(struct pcm_channel *c, int go); int chn_getptr(struct pcm_channel *c); struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); u_int32_t chn_getformats(struct pcm_channel *c); void chn_resetbuf(struct pcm_channel *c); void chn_intr(struct pcm_channel *c); int chn_wrfeed(struct pcm_channel *c); int chn_rdfeed(struct pcm_channel *c); int chn_abort(struct pcm_channel *c); void chn_wrupdate(struct pcm_channel *c); void chn_rdupdate(struct pcm_channel *c); int chn_notify(struct pcm_channel *c, u_int32_t flags); void chn_lock(struct pcm_channel *c); void chn_unlock(struct pcm_channel *c); int chn_getrates(struct pcm_channel *c, int **rates); int chn_syncdestroy(struct pcm_channel *c); #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); #endif #ifdef USING_MUTEX #define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock)) #define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock)) #define CHN_TRYLOCK(c) mtx_trylock((struct mtx *)((c)->lock)) #define CHN_LOCKASSERT(c) mtx_assert((struct mtx *)((c)->lock), MA_OWNED) #else #define CHN_LOCK(c) #define CHN_UNLOCK(c) #define CHN_TRYLOCK(c) #define CHN_LOCKASSERT(c) #endif int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); #define AFMTSTR_NONE 0 /* "s16le" */ #define AFMTSTR_SIMPLE 1 /* "s16le:s" */ #define AFMTSTR_NUM 2 /* "s16le:2" */ #define AFMTSTR_FULL 3 /* "s16le:stereo" */ #define AFMTSTR_MAXSZ 13 /* include null terminator */ #define AFMTSTR_MONO_RETURN 0 #define AFMTSTR_STEREO_RETURN 1 struct afmtstr_table { char *fmtstr; u_int32_t format; }; int afmtstr_swap_sign(char *); int afmtstr_swap_endian(char *); u_int32_t afmtstr2afmt(struct afmtstr_table *, const char *, int); u_int32_t afmt2afmtstr(struct afmtstr_table *, u_int32_t, char *, size_t, int, int); extern int chn_latency; extern int chn_latency_profile; extern int report_soft_formats; +#define PCMDIR_FAKE 0 #define PCMDIR_PLAY 1 #define PCMDIR_PLAY_VIRTUAL 2 #define PCMDIR_REC -1 #define PCMDIR_REC_VIRTUAL -2 #define PCMTRIG_START 1 #define PCMTRIG_EMLDMAWR 2 #define PCMTRIG_EMLDMARD 3 #define PCMTRIG_STOP 0 #define PCMTRIG_ABORT -1 + +#define PCMTRIG_COMMON(x) ((x) == PCMTRIG_START || \ + (x) == PCMTRIG_STOP || \ + (x) == PCMTRIG_ABORT) #define CHN_F_CLOSING 0x00000004 /* a pending close */ #define CHN_F_ABORTING 0x00000008 /* a pending abort */ #define CHN_F_RUNNING 0x00000010 /* dma is running */ #define CHN_F_TRIGGERED 0x00000020 #define CHN_F_NOTRIGGER 0x00000040 #define CHN_F_SLEEPING 0x00000080 #define CHN_F_BUSY 0x00001000 /* has been opened */ #define CHN_F_HAS_SIZE 0x00002000 /* user set block size */ #define CHN_F_NBIO 0x00004000 /* do non-blocking i/o */ #define CHN_F_MAPPED 0x00010000 /* has been mmap()ed */ #define CHN_F_DEAD 0x00020000 #define CHN_F_BADSETTING 0x00040000 #define CHN_F_SETBLOCKSIZE 0x00080000 #define CHN_F_HAS_VCHAN 0x00100000 #define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */ #define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | \ CHN_F_HAS_VCHAN | CHN_F_VIRTUAL) #define CHN_N_RATE 0x00000001 #define CHN_N_FORMAT 0x00000002 #define CHN_N_VOLUME 0x00000004 #define CHN_N_BLOCKSIZE 0x00000008 #define CHN_N_TRIGGER 0x00000010 #define CHN_LATENCY_MIN 0 #define CHN_LATENCY_MAX 10 #define CHN_LATENCY_DEFAULT 5 #define CHN_POLICY_MIN CHN_LATENCY_MIN #define CHN_POLICY_MAX CHN_LATENCY_MAX #define CHN_POLICY_DEFAULT CHN_LATENCY_DEFAULT #define CHN_LATENCY_PROFILE_MIN 0 #define CHN_LATENCY_PROFILE_MAX 1 #define CHN_LATENCY_PROFILE_DEFAULT CHN_LATENCY_PROFILE_MAX /* * This should be large enough to hold all pcm data between * tsleeps in chn_{read,write} at the highest sample rate. * (which is usually 48kHz * 16bit * stereo = 192000 bytes/sec) */ #define CHN_2NDBUFBLKSIZE (2 * 1024) /* The total number of blocks per secondary bufhard. */ #define CHN_2NDBUFBLKNUM (32) /* The size of a whole secondary bufhard. */ #define CHN_2NDBUFMAXSIZE (131072) #define CHANNEL_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) Index: head/sys/dev/sound/pcm/vchan.c =================================================================== --- head/sys/dev/sound/pcm/vchan.c (revision 170520) +++ head/sys/dev/sound/pcm/vchan.c (revision 170521) @@ -1,1026 +1,1025 @@ /*- * Copyright (c) 2001 Cameron Grant * Copyright (c) 2006 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 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. */ /* Almost entirely rewritten to add multi-format/channels mixing support. */ #include #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); typedef uint32_t (*feed_vchan_mixer)(uint8_t *, uint8_t *, uint32_t); struct vchinfo { struct pcm_channel *channel; struct pcmchan_caps caps; uint32_t fmtlist[2]; int trigger; }; /* support everything (mono / stereo), except a-law / mu-law */ static struct afmtstr_table vchan_supported_fmts[] = { { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, { NULL, 0 }, }; /* alias table, shorter. */ static const struct { char *alias, *fmtstr; } vchan_fmtstralias[] = { { "8", "u8" }, { "16", "s16le" }, { "24", "s24le" }, { "32", "s32le" }, { NULL, NULL }, }; #define vchan_valid_format(fmt) \ afmt2afmtstr(vchan_supported_fmts, fmt, NULL, 0, 0, \ AFMTSTR_STEREO_RETURN) #define vchan_valid_strformat(strfmt) \ afmtstr2afmt(vchan_supported_fmts, strfmt, AFMTSTR_STEREO_RETURN); /* * Need specialized WRITE macros since 32bit might involved saturation * if calculation is done within 32bit arithmetic. */ #define VCHAN_PCM_WRITE_S8_NE(b8, val) PCM_WRITE_S8(b8, val) #define VCHAN_PCM_WRITE_S16_LE(b8, val) PCM_WRITE_S16_LE(b8, val) #define VCHAN_PCM_WRITE_S24_LE(b8, val) PCM_WRITE_S24_LE(b8, val) #define VCHAN_PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) #define VCHAN_PCM_WRITE_S16_BE(b8, val) PCM_WRITE_S16_BE(b8, val) #define VCHAN_PCM_WRITE_S24_BE(b8, val) PCM_WRITE_S24_BE(b8, val) #define VCHAN_PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) #define VCHAN_PCM_WRITE_U8_NE(b8, val) PCM_WRITE_U8(b8, val) #define VCHAN_PCM_WRITE_U16_LE(b8, val) PCM_WRITE_U16_LE(b8, val) #define VCHAN_PCM_WRITE_U24_LE(b8, val) PCM_WRITE_U24_LE(b8, val) #define VCHAN_PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) #define VCHAN_PCM_WRITE_U16_BE(b8, val) PCM_WRITE_U16_BE(b8, val) #define VCHAN_PCM_WRITE_U24_BE(b8, val) PCM_WRITE_U24_BE(b8, val) #define VCHAN_PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) #define FEEDER_VCHAN_MIX(FMTBIT, VCHAN_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ static uint32_t \ feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS(uint8_t *to, uint8_t *tmp, \ uint32_t count) \ { \ int32_t x, y; \ VCHAN_INTCAST z; \ int i; \ \ i = count; \ tmp += i; \ to += i; \ \ do { \ tmp -= PCM_##FMTBIT##_BPS; \ to -= PCM_##FMTBIT##_BPS; \ i -= PCM_##FMTBIT##_BPS; \ x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(tmp); \ y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(to); \ z = (VCHAN_INTCAST)x + y; \ x = PCM_CLAMP_##SIGN##FMTBIT(z); \ VCHAN_PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(to, x); \ } while (i != 0); \ \ return (count); \ } FEEDER_VCHAN_MIX(8, int32_t, S, s, NE, ne) FEEDER_VCHAN_MIX(16, int32_t, S, s, LE, le) FEEDER_VCHAN_MIX(24, int32_t, S, s, LE, le) FEEDER_VCHAN_MIX(32, intpcm_t, S, s, LE, le) FEEDER_VCHAN_MIX(16, int32_t, S, s, BE, be) FEEDER_VCHAN_MIX(24, int32_t, S, s, BE, be) FEEDER_VCHAN_MIX(32, intpcm_t, S, s, BE, be) FEEDER_VCHAN_MIX(8, int32_t, U, u, NE, ne) FEEDER_VCHAN_MIX(16, int32_t, U, u, LE, le) FEEDER_VCHAN_MIX(24, int32_t, U, u, LE, le) FEEDER_VCHAN_MIX(32, intpcm_t, U, u, LE, le) FEEDER_VCHAN_MIX(16, int32_t, U, u, BE, be) FEEDER_VCHAN_MIX(24, int32_t, U, u, BE, be) FEEDER_VCHAN_MIX(32, intpcm_t, U, u, BE, be) struct feed_vchan_info { uint32_t format; int bps; feed_vchan_mixer mix; }; static struct feed_vchan_info feed_vchan_info_tbl[] = { { AFMT_S8, PCM_8_BPS, feed_vchan_mix_s8ne }, { AFMT_S16_LE, PCM_16_BPS, feed_vchan_mix_s16le }, { AFMT_S24_LE, PCM_24_BPS, feed_vchan_mix_s24le }, { AFMT_S32_LE, PCM_32_BPS, feed_vchan_mix_s32le }, { AFMT_S16_BE, PCM_16_BPS, feed_vchan_mix_s16be }, { AFMT_S24_BE, PCM_24_BPS, feed_vchan_mix_s24be }, { AFMT_S32_BE, PCM_32_BPS, feed_vchan_mix_s32be }, { AFMT_U8, PCM_8_BPS, feed_vchan_mix_u8ne }, { AFMT_U16_LE, PCM_16_BPS, feed_vchan_mix_u16le }, { AFMT_U24_LE, PCM_24_BPS, feed_vchan_mix_u24le }, { AFMT_U32_LE, PCM_32_BPS, feed_vchan_mix_u32le }, { AFMT_U16_BE, PCM_16_BPS, feed_vchan_mix_u16be }, { AFMT_U24_BE, PCM_24_BPS, feed_vchan_mix_u24be }, { AFMT_U32_BE, PCM_32_BPS, feed_vchan_mix_u32be }, }; #define FVCHAN_DATA(i, c) ((intptr_t)((((i) & 0x1f) << 4) | ((c) & 0xf))) #define FVCHAN_INFOIDX(m) (((m) >> 4) & 0x1f) #define FVCHAN_CHANNELS(m) ((m) & 0xf) static int feed_vchan_init(struct pcm_feeder *f) { int i, channels; if (f->desc->out != f->desc->in) return (EINVAL); channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; for (i = 0; i < sizeof(feed_vchan_info_tbl) / sizeof(feed_vchan_info_tbl[0]); i++) { if ((f->desc->out & ~AFMT_STEREO) == feed_vchan_info_tbl[i].format) { f->data = (void *)FVCHAN_DATA(i, channels); return (0); } } return (-1); } static __inline int feed_vchan_rec(struct pcm_channel *c) { struct pcm_channel *ch; struct snd_dbuf *b, *bs; int cnt, rdy; /* * Reset ready and moving pointer. We're not using bufsoft * anywhere since its sole purpose is to become the primary * distributor for the recorded buffer and also as an interrupt * threshold progress indicator. */ b = c->bufsoft; b->rp = 0; b->rl = 0; cnt = sndbuf_getsize(b); do { cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, cnt, c->bufhard); if (cnt != 0) { sndbuf_acquire(b, b->tmpbuf, cnt); cnt = sndbuf_getfree(b); } } while (cnt != 0); /* Not enough data */ if (b->rl < sndbuf_getbps(b)) { b->rl = 0; return (0); } /* * Keep track of ready and moving pointer since we will use * bufsoft over and over again, pretending nothing has happened. */ rdy = b->rl; CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); bs = ch->bufsoft; cnt = sndbuf_getfree(bs); if (!(ch->flags & CHN_F_TRIGGERED) || cnt < sndbuf_getbps(bs)) { CHN_UNLOCK(ch); continue; } do { cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, cnt, b); if (cnt != 0) { sndbuf_acquire(bs, bs->tmpbuf, cnt); cnt = sndbuf_getfree(bs); } } while (cnt != 0); /* * Not entirely flushed out... */ if (b->rl != 0) ch->xruns++; CHN_UNLOCK(ch); /* * Rewind buffer position for next virtual channel. */ b->rp = 0; b->rl = rdy; } /* * Set ready pointer to indicate that our children are ready * to be woken up, also as an interrupt threshold progress * indicator. */ b->rl = 1; /* * Return 0 to bail out early from sndbuf_feed() loop. * No need to increase feedcount counter since part of this * feeder chains already include feed_root(). */ return (0); } static int feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { struct feed_vchan_info *info; struct snd_dbuf *src = source; struct pcm_channel *ch; uint32_t cnt, mcnt, rcnt, sz; uint8_t *tmp; if (c->direction == PCMDIR_REC) return (feed_vchan_rec(c)); sz = sndbuf_getsize(src); if (sz < count) count = sz; info = &feed_vchan_info_tbl[FVCHAN_INFOIDX((intptr_t)f->data)]; sz = info->bps * FVCHAN_CHANNELS((intptr_t)f->data); count -= count % sz; if (count < sz) return (0); /* * we are going to use our source as a temporary buffer since it's * got no other purpose. we obtain our data by traversing the channel * list of children and calling vchan_mix_* to mix count bytes from * each into our destination buffer, b */ tmp = sndbuf_getbuf(src); rcnt = 0; mcnt = 0; CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (!(ch->flags & CHN_F_TRIGGERED)) { CHN_UNLOCK(ch); continue; } if ((ch->flags & CHN_F_MAPPED) && !(ch->flags & CHN_F_CLOSING)) sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); if (rcnt == 0) { rcnt = FEEDER_FEED(ch->feeder, ch, b, count, ch->bufsoft); rcnt -= rcnt % sz; mcnt = count - rcnt; } else { cnt = FEEDER_FEED(ch->feeder, ch, tmp, count, ch->bufsoft); cnt -= cnt % sz; if (cnt != 0) { if (mcnt != 0) { memset(b + rcnt, sndbuf_zerodata(f->desc->out), mcnt); mcnt = 0; } cnt = info->mix(b, tmp, cnt); if (cnt > rcnt) rcnt = cnt; } } CHN_UNLOCK(ch); } if (++c->feedcount == 0) c->feedcount = 2; return (rcnt); } static struct pcm_feederdesc feeder_vchan_desc[] = { {FEEDER_MIXER, AFMT_S8, AFMT_S8, 0}, {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, {FEEDER_MIXER, AFMT_S24_LE, AFMT_S24_LE, 0}, {FEEDER_MIXER, AFMT_S32_LE, AFMT_S32_LE, 0}, {FEEDER_MIXER, AFMT_S16_BE, AFMT_S16_BE, 0}, {FEEDER_MIXER, AFMT_S24_BE, AFMT_S24_BE, 0}, {FEEDER_MIXER, AFMT_S32_BE, AFMT_S32_BE, 0}, {FEEDER_MIXER, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U8, AFMT_U8, 0}, {FEEDER_MIXER, AFMT_U16_LE, AFMT_U16_LE, 0}, {FEEDER_MIXER, AFMT_U24_LE, AFMT_U24_LE, 0}, {FEEDER_MIXER, AFMT_U32_LE, AFMT_U32_LE, 0}, {FEEDER_MIXER, AFMT_U16_BE, AFMT_U16_BE, 0}, {FEEDER_MIXER, AFMT_U24_BE, AFMT_U24_BE, 0}, {FEEDER_MIXER, AFMT_U32_BE, AFMT_U32_BE, 0}, {FEEDER_MIXER, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_vchan_methods[] = { KOBJMETHOD(feeder_init, feed_vchan_init), KOBJMETHOD(feeder_feed, feed_vchan), {0, 0} }; FEEDER_DECLARE(feeder_vchan, 2, NULL); /************************************************************/ static void * vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct vchinfo *ch; KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, ("vchan_init: bad direction")); KASSERT(c != NULL && c->parentchannel != NULL, ("vchan_init: bad channels")); ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); ch->channel = c; ch->trigger = PCMTRIG_STOP; c->flags |= CHN_F_VIRTUAL; return (ch); } static int vchan_free(kobj_t obj, void *data) { free(data, M_DEVBUF); return (0); } static int vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchinfo *ch = data; if (fmtvalid(format, ch->fmtlist) == 0) return (-1); return (0); } static int vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchinfo *ch = data; struct pcm_channel *p = ch->channel->parentchannel; return (sndbuf_getspd(p->bufsoft)); } static int vchan_trigger(kobj_t obj, void *data, int go) { struct vchinfo *ch = data; struct pcm_channel *c, *p; int otrigger; - if (!(go == PCMTRIG_START || go == PCMTRIG_STOP || - go == PCMTRIG_ABORT) || go == ch->trigger) + if (!PCMTRIG_COMMON(go) || go == ch->trigger) return (0); c = ch->channel; p = c->parentchannel; otrigger = ch->trigger; ch->trigger = go; CHN_UNLOCK(c); CHN_LOCK(p); switch (go) { case PCMTRIG_START: if (otrigger != PCMTRIG_START) { CHN_INSERT_HEAD(p, c, children.busy); } break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (otrigger == PCMTRIG_START) { CHN_REMOVE(p, c, children.busy); } break; default: break; } CHN_UNLOCK(p); chn_notify(p, CHN_N_TRIGGER); CHN_LOCK(c); return (0); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; struct pcm_channel *c, *p; uint32_t fmt; c = ch->channel; p = c->parentchannel; ch->caps.minspeed = sndbuf_getspd(p->bufsoft); ch->caps.maxspeed = ch->caps.minspeed; ch->caps.caps = 0; ch->fmtlist[1] = 0; fmt = sndbuf_getfmt(p->bufsoft); if (fmt != vchan_valid_format(fmt)) { device_printf(c->dev, "%s: WARNING: invalid vchan format! (0x%08x)\n", __func__, fmt); fmt = VCHAN_DEFAULT_AFMT; } ch->fmtlist[0] = fmt; ch->caps.fmtlist = ch->fmtlist; return (&ch->caps); } static kobj_method_t vchan_methods[] = { KOBJMETHOD(channel_init, vchan_init), KOBJMETHOD(channel_free, vchan_free), KOBJMETHOD(channel_setformat, vchan_setformat), KOBJMETHOD(channel_setspeed, vchan_setspeed), KOBJMETHOD(channel_trigger, vchan_trigger), KOBJMETHOD(channel_getcaps, vchan_getcaps), {0, 0} }; CHANNEL_DECLARE(vchan); /* * On the fly vchan rate settings */ #ifdef SND_DYNSYSCTL static int sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch = NULL; struct pcmchan_caps *caps; int vchancount, *vchanrate; int direction; int err = 0; int newspd = 0; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (d == NULL || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: direction = PCMDIR_PLAY; vchancount = d->pvchancount; vchanrate = &d->pvchanrate; break; case VCHAN_REC: direction = PCMDIR_REC; vchancount = d->rvchancount; vchanrate = &d->rvchanrate; break; default: return (EINVAL); break; } if (vchancount < 1) return (EINVAL); if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { pcm_inprog(d, -1); return (EINPROGRESS); } CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->direction == direction) { if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (ch != NULL && ch != c->parentchannel) { CHN_UNLOCK(c); pcm_inprog(d, -1); return (EINVAL); } if (req->newptr != NULL && (c->flags & CHN_F_BUSY)) { CHN_UNLOCK(c); pcm_inprog(d, -1); return (EBUSY); } } else if (c->flags & CHN_F_HAS_VCHAN) { /* No way!! */ if (ch != NULL) { CHN_UNLOCK(c); pcm_inprog(d, -1); return (EINVAL); } ch = c; newspd = ch->speed; } } CHN_UNLOCK(c); } if (ch == NULL) { pcm_inprog(d, -1); return (EINVAL); } err = sysctl_handle_int(oidp, &newspd, 0, req); if (err == 0 && req->newptr != NULL) { if (newspd < 1 || newspd < feeder_rate_min || newspd > feeder_rate_max) { pcm_inprog(d, -1); return (EINVAL); } CHN_LOCK(ch); if (feeder_rate_round) { caps = chn_getcaps(ch); if (caps == NULL || newspd < caps->minspeed || newspd > caps->maxspeed) { CHN_UNLOCK(ch); pcm_inprog(d, -1); return (EINVAL); } } if (newspd != ch->speed) { err = chn_setspeed(ch, newspd); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ if (!err && feeder_rate_round && (ch->feederflags & (1 << FEEDER_RATE))) { newspd = sndbuf_getspd(ch->bufhard); err = chn_setspeed(ch, newspd); } CHN_UNLOCK(ch); if (err == 0) { pcm_lock(d); *vchanrate = newspd; pcm_unlock(d); } } else CHN_UNLOCK(ch); } pcm_inprog(d, -1); return (err); } static int sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch = NULL; uint32_t newfmt, spd; int vchancount, *vchanformat; int direction; int err = 0, i; char fmtstr[AFMTSTR_MAXSZ]; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (d == NULL || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: direction = PCMDIR_PLAY; vchancount = d->pvchancount; vchanformat = &d->pvchanformat; break; case VCHAN_REC: direction = PCMDIR_REC; vchancount = d->rvchancount; vchanformat = &d->rvchanformat; break; default: return (EINVAL); break; } if (vchancount < 1) return (EINVAL); if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { pcm_inprog(d, -1); return (EINPROGRESS); } CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->direction == direction) { if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (ch != NULL && ch != c->parentchannel) { CHN_UNLOCK(c); pcm_inprog(d, -1); return (EINVAL); } if (req->newptr != NULL && (c->flags & CHN_F_BUSY)) { CHN_UNLOCK(c); pcm_inprog(d, -1); return (EBUSY); } } else if (c->flags & CHN_F_HAS_VCHAN) { /* No way!! */ if (ch != NULL) { CHN_UNLOCK(c); pcm_inprog(d, -1); return (EINVAL); } ch = c; if (ch->format != afmt2afmtstr(vchan_supported_fmts, ch->format, fmtstr, sizeof(fmtstr), AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, sizeof(fmtstr)); } } } CHN_UNLOCK(c); } if (ch == NULL) { pcm_inprog(d, -1); return (EINVAL); } err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); if (err == 0 && req->newptr != NULL) { for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, sizeof(fmtstr)); break; } } newfmt = vchan_valid_strformat(fmtstr); if (newfmt == 0) { pcm_inprog(d, -1); return (EINVAL); } CHN_LOCK(ch); if (newfmt != ch->format) { /* Get channel speed, before chn_reset() screw it. */ spd = ch->speed; err = chn_reset(ch, newfmt); if (err == 0) err = chn_setspeed(ch, spd); CHN_UNLOCK(ch); if (err == 0) { pcm_lock(d); *vchanformat = newfmt; pcm_unlock(d); } } else CHN_UNLOCK(ch); } pcm_inprog(d, -1); return (err); } #endif /* virtual channel interface */ #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanformat" : "rec.vchanformat" #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanrate" : "rec.vchanrate" int vchan_create(struct pcm_channel *parent, int num) { struct snddev_info *d = parent->parentsnddev; struct pcm_channel *ch, *tmp, *after; struct pcmchan_caps *parent_caps; uint32_t vchanfmt; int err, first, speed, r; int direction; if (!(parent->flags & CHN_F_BUSY)) return (EBUSY); if (parent->direction == PCMDIR_PLAY) { direction = PCMDIR_PLAY_VIRTUAL; vchanfmt = d->pvchanformat; speed = d->pvchanrate; } else if (parent->direction == PCMDIR_REC) { direction = PCMDIR_REC_VIRTUAL; vchanfmt = d->rvchanformat; speed = d->rvchanrate; } else return (EINVAL); CHN_UNLOCK(parent); /* create a new playback channel */ ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent); if (ch == NULL) { CHN_LOCK(parent); return (ENODEV); } /* add us to our grandparent's channel list */ err = pcm_chn_add(d, ch); if (err) { pcm_chn_destroy(ch); CHN_LOCK(parent); return (err); } CHN_LOCK(parent); /* add us to our parent channel's children */ first = CHN_EMPTY(parent, children); after = NULL; CHN_FOREACH(tmp, parent, children) { if (CHN_CHAN(tmp) > CHN_CHAN(ch)) after = tmp; else if (CHN_CHAN(tmp) < CHN_CHAN(ch)) break; } if (after != NULL) { CHN_INSERT_AFTER(after, ch, children); } else { CHN_INSERT_HEAD(parent, ch, children); } parent->flags |= CHN_F_HAS_VCHAN; if (first) { parent_caps = chn_getcaps(parent); if (parent_caps == NULL) err = EINVAL; if (!err) { if (vchanfmt == 0) { const char *vfmt; CHN_UNLOCK(parent); r = resource_string_value( device_get_name(parent->dev), device_get_unit(parent->dev), VCHAN_FMT_HINT(direction), &vfmt); CHN_LOCK(parent); if (r != 0) vfmt = NULL; if (vfmt != NULL) { vchanfmt = vchan_valid_strformat(vfmt); for (r = 0; vchanfmt == 0 && vchan_fmtstralias[r].alias != NULL; r++) { if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); break; } } } if (vchanfmt == 0) vchanfmt = VCHAN_DEFAULT_AFMT; } err = chn_reset(parent, vchanfmt); } if (!err) { /* * This is very sad. Few soundcards advertised as being * able to do (insanely) higher/lower speed, but in * reality, they simply can't. At least, we give user chance * to set sane value via kernel hints or sysctl. */ if (speed < 1) { CHN_UNLOCK(parent); r = resource_int_value( device_get_name(parent->dev), device_get_unit(parent->dev), VCHAN_SPD_HINT(direction), &speed); CHN_LOCK(parent); if (r != 0) { /* * No saved value, no hint, NOTHING. * * Workaround for sb16 running * poorly at 45k / 49k. */ switch (parent_caps->maxspeed) { case 45000: case 49000: speed = 44100; break; default: speed = VCHAN_DEFAULT_SPEED; if (speed > parent_caps->maxspeed) speed = parent_caps->maxspeed; break; } if (speed < parent_caps->minspeed) speed = parent_caps->minspeed; } } if (feeder_rate_round) { /* * Limit speed based on driver caps. * This is supposed to help fixed rate, non-VRA * AC97 cards, but.. (see below) */ if (speed < parent_caps->minspeed) speed = parent_caps->minspeed; if (speed > parent_caps->maxspeed) speed = parent_caps->maxspeed; } /* * We still need to limit the speed between * feeder_rate_min <-> feeder_rate_max. This is * just an escape goat if all of the above failed * miserably. */ if (speed < feeder_rate_min) speed = feeder_rate_min; if (speed > feeder_rate_max) speed = feeder_rate_max; err = chn_setspeed(parent, speed); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ if (!err && feeder_rate_round && (parent->feederflags & (1 << FEEDER_RATE))) { speed = sndbuf_getspd(parent->bufhard); err = chn_setspeed(parent, speed); } if (!err) { /* * Save new value. */ CHN_UNLOCK(parent); pcm_lock(d); if (direction == PCMDIR_PLAY_VIRTUAL) { d->pvchanformat = vchanfmt; d->pvchanrate = speed; } else { d->rvchanformat = vchanfmt; d->rvchanrate = speed; } pcm_unlock(d); CHN_LOCK(parent); } } if (err) { CHN_REMOVE(parent, ch, children); parent->flags &= ~CHN_F_HAS_VCHAN; CHN_UNLOCK(parent); pcm_lock(d); if (pcm_chn_remove(d, ch) == 0) { pcm_unlock(d); pcm_chn_destroy(ch); } else pcm_unlock(d); CHN_LOCK(parent); return (err); } } return (0); } int vchan_destroy(struct pcm_channel *c) { struct pcm_channel *parent = c->parentchannel; struct snddev_info *d = parent->parentsnddev; uint32_t spd; int err; CHN_LOCK(parent); if (!(parent->flags & CHN_F_BUSY)) { CHN_UNLOCK(parent); return (EBUSY); } if (CHN_EMPTY(parent, children)) { CHN_UNLOCK(parent); return (EINVAL); } /* remove us from our parent's children list */ CHN_REMOVE(parent, c, children); if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); spd = parent->speed; if (chn_reset(parent, parent->format) == 0) chn_setspeed(parent, spd); } CHN_UNLOCK(parent); /* remove us from our grandparent's channel list */ pcm_lock(d); err = pcm_chn_remove(d, c); pcm_unlock(d); /* destroy ourselves */ if (!err) err = pcm_chn_destroy(c); return (err); } int vchan_initsys(device_t dev) { #ifdef SND_DYNSYSCTL struct snddev_info *d; int unit; unit = device_get_unit(dev); d = device_get_softc(dev); /* Play */ SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchanformat, "A", "virtual channel format"); /* Rec */ SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchanrate, "I", "virtual channel base speed/rate"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchanformat, "A", "virtual channel format"); #endif return (0); } Index: head/sys/dev/sound/usb/uaudio_pcm.c =================================================================== --- head/sys/dev/sound/usb/uaudio_pcm.c (revision 170520) +++ head/sys/dev/sound/usb/uaudio_pcm.c (revision 170521) @@ -1,484 +1,484 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2000-2002 Hiroyuki Aizu * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include "mixer_if.h" struct ua_info; struct ua_chinfo { struct ua_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_char *buf; int dir, hwch; u_int32_t fmt, spd, blksz; /* XXXXX */ }; struct ua_info { device_t sc_dev; u_int32_t bufsz; struct ua_chinfo pch, rch; #define FORMAT_NUM 32 u_int32_t ua_playfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ u_int32_t ua_recfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ struct pcmchan_caps ua_playcaps; struct pcmchan_caps ua_reccaps; int vendor, product, release; }; #define UAUDIO_DEFAULT_BUFSZ 16*1024 static const struct { int vendor; int product; int release; uint32_t dflags; } ua_quirks[] = { { 0x1130, 0xf211, 0x0101, SD_F_PSWAPLR }, }; /************************************************************/ static void * ua_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { device_t pa_dev; struct ua_info *sc = devinfo; struct ua_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; pa_dev = device_get_parent(sc->sc_dev); ch->buf = malloc(sc->bufsz, M_DEVBUF, M_NOWAIT); if (ch->buf == NULL) return NULL; if (sndbuf_setup(b, ch->buf, sc->bufsz) != 0) { free(ch->buf, M_DEVBUF); return NULL; } uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, ch->buf+sc->bufsz, ch->channel, dir); if (bootverbose) device_printf(pa_dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? "play" : "rec", sndbuf_getbuf(ch->buffer)); ch->dir = dir; #ifndef NO_RECORDING ch->hwch = 1; if (dir == PCMDIR_PLAY) ch->hwch = 2; #else ch->hwch = 2; #endif return ch; } static int ua_chan_free(kobj_t obj, void *data) { struct ua_chinfo *ua = data; if (ua->buf != NULL) free(ua->buf, M_DEVBUF); return 0; } static int ua_chan_setformat(kobj_t obj, void *data, u_int32_t format) { device_t pa_dev; struct ua_info *ua; struct ua_chinfo *ch = data; /* * At this point, no need to query as we shouldn't select an unsorted format */ ua = ch->parent; pa_dev = device_get_parent(ua->sc_dev); uaudio_chan_set_param_format(pa_dev, format, ch->dir); ch->fmt = format; return 0; } static int ua_chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ua_chinfo *ch; device_t pa_dev; int bestspeed; ch = data; pa_dev = device_get_parent(ch->parent->sc_dev); if ((bestspeed = uaudio_chan_set_param_speed(pa_dev, speed, ch->dir))) ch->spd = bestspeed; return ch->spd; } static int ua_chan_setfragments(kobj_t obj, void *data, u_int32_t blksz, u_int32_t blkcnt) { device_t pa_dev; struct ua_chinfo *ch = data; struct ua_info *ua = ch->parent; RANGE(blksz, 128, sndbuf_getmaxsize(ch->buffer) / 2); RANGE(blkcnt, 2, 512); while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { if ((blkcnt >> 1) >= 2) blkcnt >>= 1; else if ((blksz >> 1) >= 128) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->buffer) != blksz || sndbuf_getblkcnt(ch->buffer) != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(ua->sc_dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); pa_dev = device_get_parent(ua->sc_dev); uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, ch->buf + sndbuf_getsize(ch->buffer), ch->channel, ch->dir); uaudio_chan_set_param_blocksize(pa_dev, ch->blksz, ch->dir); return 1; } static int ua_chan_setblocksize(kobj_t obj, void *data, u_int32_t blksz) { struct ua_chinfo *ch = data; ua_chan_setfragments(obj, data, blksz, sndbuf_getmaxsize(ch->buffer) / blksz); return ch->blksz; } static int ua_chan_trigger(kobj_t obj, void *data, int go) { device_t pa_dev; struct ua_info *ua; struct ua_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ua = ch->parent; pa_dev = device_get_parent(ua->sc_dev); /* XXXXX */ if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { uaudio_trigger_output(pa_dev); } else { uaudio_halt_out_dma(pa_dev); } } else { #ifndef NO_RECORDING if (go == PCMTRIG_START) uaudio_trigger_input(pa_dev); else uaudio_halt_in_dma(pa_dev); #endif } return 0; } static int ua_chan_getptr(kobj_t obj, void *data) { device_t pa_dev; struct ua_info *ua; struct ua_chinfo *ch = data; ua = ch->parent; pa_dev = device_get_parent(ua->sc_dev); return uaudio_chan_getptr(pa_dev, ch->dir); } static struct pcmchan_caps * ua_chan_getcaps(kobj_t obj, void *data) { struct ua_chinfo *ch; ch = data; return (ch->dir == PCMDIR_PLAY) ? &(ch->parent->ua_playcaps) : &(ch->parent->ua_reccaps); } static kobj_method_t ua_chan_methods[] = { KOBJMETHOD(channel_init, ua_chan_init), KOBJMETHOD(channel_free, ua_chan_free), KOBJMETHOD(channel_setformat, ua_chan_setformat), KOBJMETHOD(channel_setspeed, ua_chan_setspeed), KOBJMETHOD(channel_setblocksize, ua_chan_setblocksize), KOBJMETHOD(channel_setfragments, ua_chan_setfragments), KOBJMETHOD(channel_trigger, ua_chan_trigger), KOBJMETHOD(channel_getptr, ua_chan_getptr), KOBJMETHOD(channel_getcaps, ua_chan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(ua_chan); /************************************************************/ static int ua_mixer_init(struct snd_mixer *m) { u_int32_t mask; device_t pa_dev; struct ua_info *ua = mix_getdevinfo(m); pa_dev = device_get_parent(ua->sc_dev); mask = uaudio_query_mix_info(pa_dev); if (!(mask & SOUND_MASK_PCM)) { /* * Emulate missing pcm mixer controller * through FEEDER_VOLUME */ pcm_setflags(ua->sc_dev, pcm_getflags(ua->sc_dev) | SD_F_SOFTPCMVOL); } if (!(mask & SOUND_MASK_VOLUME)) { mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); } mix_setdevs(m, mask); mask = uaudio_query_recsrc_info(pa_dev); mix_setrecdevs(m, mask); return 0; } static int ua_mixer_set(struct snd_mixer *m, unsigned type, unsigned left, unsigned right) { device_t pa_dev; struct ua_info *ua = mix_getdevinfo(m); pa_dev = device_get_parent(ua->sc_dev); uaudio_mixer_set(pa_dev, type, left, right); return left | (right << 8); } static int ua_mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { device_t pa_dev; struct ua_info *ua = mix_getdevinfo(m); pa_dev = device_get_parent(ua->sc_dev); return uaudio_mixer_setrecsrc(pa_dev, src); } static kobj_method_t ua_mixer_methods[] = { KOBJMETHOD(mixer_init, ua_mixer_init), KOBJMETHOD(mixer_set, ua_mixer_set), KOBJMETHOD(mixer_setrecsrc, ua_mixer_setrecsrc), { 0, 0 } }; MIXER_DECLARE(ua_mixer); /************************************************************/ static int ua_probe(device_t dev) { char *s; struct sndcard_func *func; /* The parent device has already been probed. */ func = device_get_ivars(dev); if (func == NULL || func->func != SCF_PCM) return (ENXIO); s = "USB Audio"; device_set_desc(dev, s); return BUS_PROBE_DEFAULT; } static int ua_attach(device_t dev) { struct ua_info *ua; struct sndcard_func *func; char status[SND_STATUSLEN]; device_t pa_dev; u_int32_t nplay, nrec, flags; int i; ua = (struct ua_info *)malloc(sizeof *ua, M_DEVBUF, M_ZERO | M_NOWAIT); if (ua == NULL) return ENXIO; ua->sc_dev = dev; /* Mark for existence */ func = device_get_ivars(dev); if (func != NULL) func->varinfo = (void *)ua; pa_dev = device_get_parent(dev); ua->vendor = uaudio_get_vendor(pa_dev); ua->product = uaudio_get_product(pa_dev); ua->release = uaudio_get_release(pa_dev); if (bootverbose) device_printf(dev, "USB Audio: " "vendor=0x%04x, product=0x%04x, release=0x%04x\n", ua->vendor, ua->product, ua->release); ua->bufsz = pcm_getbuffersize(dev, 4096, UAUDIO_DEFAULT_BUFSZ, 65536); if (bootverbose) device_printf(dev, "using a default buffer size of %jd\n", (intmax_t)ua->bufsz); if (mixer_init(dev, &ua_mixer_class, ua)) { goto bad; } snprintf(status, SND_STATUSLEN, "at ? %s", PCM_KLDSTRING(snd_uaudio)); ua->ua_playcaps.fmtlist = ua->ua_playfmt; ua->ua_reccaps.fmtlist = ua->ua_recfmt; nplay = uaudio_query_formats(pa_dev, PCMDIR_PLAY, FORMAT_NUM * 2, &ua->ua_playcaps); nrec = uaudio_query_formats(pa_dev, PCMDIR_REC, FORMAT_NUM * 2, &ua->ua_reccaps); if (nplay > 1) nplay = 1; if (nrec > 1) nrec = 1; flags = pcm_getflags(dev); for (i = 0; i < (sizeof(ua_quirks) / sizeof(ua_quirks[0])); i++) { if (ua->vendor == ua_quirks[i].vendor && ua->product == ua_quirks[i].product && ua->release == ua_quirks[i].release) flags |= ua_quirks[i].dflags; } pcm_setflags(dev, flags); #ifndef NO_RECORDING if (pcm_register(dev, ua, nplay, nrec)) { #else if (pcm_register(dev, ua, nplay, 0)) { #endif goto bad; } sndstat_unregister(dev); uaudio_sndstat_register(dev); for (i = 0; i < nplay; i++) { pcm_addchan(dev, PCMDIR_PLAY, &ua_chan_class, ua); } #ifndef NO_RECORDING for (i = 0; i < nrec; i++) { pcm_addchan(dev, PCMDIR_REC, &ua_chan_class, ua); } #endif pcm_setstatus(dev, status); return 0; bad: free(ua, M_DEVBUF); return ENXIO; } static int ua_detach(device_t dev) { struct ua_info *sc; struct sndcard_func *func; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); free(sc, M_DEVBUF); /* Mark for deletion */ func = device_get_ivars(dev); if (func != NULL) func->varinfo = NULL; return 0; } /************************************************************/ static device_method_t ua_pcm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ua_probe), DEVMETHOD(device_attach, ua_attach), DEVMETHOD(device_detach, ua_detach), { 0, 0 } }; static driver_t ua_pcm_driver = { "pcm", ua_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(ua_pcm, uaudio, ua_pcm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(ua_pcm, uaudio, 1, 1, 1); MODULE_DEPEND(ua_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(ua_pcm, 1);