Index: head/sys/dev/sound/isa/ad1816.c =================================================================== --- head/sys/dev/sound/isa/ad1816.c (revision 65339) +++ head/sys/dev/sound/isa/ad1816.c (revision 65340) @@ -1,634 +1,642 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright Luigi Rizzo, 1997,1998 * Copyright by Hannu Savolainen 1994, 1995 * 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$ */ #include #include struct ad1816_info; struct ad1816_chinfo { struct ad1816_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; }; 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; bus_dma_tag_t parent_dmat; struct ad1816_chinfo pch, rch; }; static driver_intr_t ad1816_intr; static int ad1816_probe(device_t dev); static int ad1816_attach(device_t dev); /* IO primitives */ static int ad1816_wait_init(struct ad1816_info *ad1816, int x); static u_short ad1816_read(struct ad1816_info *ad1816, u_int reg); static void ad1816_write(struct ad1816_info *ad1816, u_int reg, u_short data); static int ad1816mix_init(snd_mixer *m); static int ad1816mix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int ad1816mix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer ad1816_mixer = { - "ad1816 mixer", - ad1816mix_init, - ad1816mix_set, - ad1816mix_setrecsrc, + "ad1816 mixer", + ad1816mix_init, + NULL, + ad1816mix_set, + ad1816mix_setrecsrc, }; static devclass_t pcm_devclass; /* channel interface */ static void *ad1816chan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int ad1816chan_setdir(void *data, int dir); static int ad1816chan_setformat(void *data, u_int32_t format); static int ad1816chan_setspeed(void *data, u_int32_t speed); static int ad1816chan_setblocksize(void *data, u_int32_t blocksize); static int ad1816chan_trigger(void *data, int go); static int ad1816chan_getptr(void *data); static pcmchan_caps *ad1816chan_getcaps(void *data); 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 pcmchan_caps ad1816_caps = {4000, 55200, ad1816_fmt, 0}; static pcm_channel ad1816_chantemplate = { ad1816chan_init, ad1816chan_setdir, ad1816chan_setformat, ad1816chan_setspeed, ad1816chan_setblocksize, ad1816chan_trigger, ad1816chan_getptr, ad1816chan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; #define AD1816_MUTE 31 /* value for mute */ 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) return 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) { return 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; /* 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 (ad1816->rch.buffer->dl && (c & AD1816_INTRCI)) { chn_intr(ad1816->rch.channel); served |= AD1816_INTRCI; /* cp served */ } /* check for playback interupt */ if (ad1816->pch.buffer->dl && (c & AD1816_INTRPI)) { chn_intr(ad1816->pch.channel); 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); } 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) { int flags; u_short x = 0; /* we don't want to be blocked here */ flags = spltty(); 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); splx(flags); return x; } static void ad1816_write(struct ad1816_info *ad1816, unsigned int reg, unsigned short data) { int flags; flags = spltty(); 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); splx(flags); } static int ad1816mix_init(snd_mixer *m) { mix_setdevs(m, AD1816_MIXER_DEVICES); mix_setrecdevs(m, AD1816_REC_DEVICES); return 0; } static int ad1816mix_set(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; 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; } left = ((AD1816_MUTE - left) * 100) / AD1816_MUTE; right = ((AD1816_MUTE - right) * 100) / AD1816_MUTE; return left | (right << 8); } static int ad1816mix_setrecsrc(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_write(ad1816, 20, (ad1816_read(ad1816, 20) & ~0x7070) | dev); return src; } /* channel interface */ static void * ad1816chan_init(void *devinfo, snd_dbuf *b, 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; ch->buffer->bufsize = DSP_BUFFSIZE; if (chn_allocbuf(ch->buffer, ad1816->parent_dmat) == -1) return NULL; return ch; } static int ad1816chan_setdir(void *data, int dir) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; ch->buffer->chan = rman_get_start((dir == PCMDIR_PLAY)? ad1816->drq1 : ad1816->drq2); ch->dir = dir; return 0; } static int ad1816chan_setformat(void *data, u_int32_t format) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; int fmt = AD1816_U8, reg; 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); return format; } static int ad1816chan_setspeed(void *data, u_int32_t speed) { struct ad1816_chinfo *ch = data; struct ad1816_info *ad1816 = ch->parent; RANGE(speed, 4000, 55200); ad1816_write(ad1816, (ch->dir == PCMDIR_PLAY)? 2 : 3, speed); return speed; } static int ad1816chan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int ad1816chan_trigger(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) return 0; buf_isadma(ch->buffer, go); wr = (ch->dir == PCMDIR_PLAY); reg = wr? AD1816_PLAY : AD1816_CAPT; switch (go) { case PCMTRIG_START: /* start only if not already running */ if (!(io_rd(ad1816, reg) & AD1816_ENABLE)) { int cnt = ((ch->buffer->dl) >> 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; } return 0; } static int ad1816chan_getptr(void *data) { struct ad1816_chinfo *ch = data; return buf_isadmaptr(ch->buffer); } static pcmchan_caps * ad1816chan_getcaps(void *data) { return &ad1816_caps; } static void ad1816_release_resources(struct ad1816_info *ad1816, device_t dev) { if (ad1816->irq) { bus_release_resource(dev, SYS_RES_IRQ, ad1816->irq_rid, ad1816->irq); ad1816->irq = 0; } if (ad1816->drq1) { bus_release_resource(dev, SYS_RES_DRQ, ad1816->drq1_rid, ad1816->drq1); ad1816->drq1 = 0; } if (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; } 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(dev, SYS_RES_IOPORT, &ad1816->io_rid, 0, ~0, 1, RF_ACTIVE); if (!ad1816->irq) ad1816->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &ad1816->irq_rid, 0, ~0, 1, RF_ACTIVE); if (!ad1816->drq1) ad1816->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &ad1816->drq1_rid, 0, ~0, 1, RF_ACTIVE); if (!ad1816->drq2) ad1816->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &ad1816->drq2_rid, 0, ~0, 1, 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, DSP_BUFFSIZE); if (ad1816->drq2) { rdma = rman_get_start(ad1816->drq2); isa_dma_acquire(rdma); isa_dmainit(rdma, DSP_BUFFSIZE); } 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; } if (s) { device_set_desc(dev, s); return 0; } return ENXIO; } static int ad1816_attach(device_t dev) { struct ad1816_info *ad1816; - snddev_info *d = device_get_softc(dev); void *ih; char status[SND_STATUSLEN]; ad1816 = (struct ad1816_info *)malloc(sizeof *ad1816, M_DEVBUF, M_NOWAIT); if (!ad1816) return ENXIO; bzero(ad1816, sizeof *ad1816); ad1816->io_rid = 2; ad1816->irq_rid = 0; ad1816->drq1_rid = 0; ad1816->drq2_rid = 1; if (!ad1816_alloc_resources(ad1816, dev)) goto no; ad1816_init(ad1816, dev); - mixer_init(d, &ad1816_mixer, ad1816); + mixer_init(dev, &ad1816_mixer, ad1816); bus_setup_intr(dev, ad1816->irq, INTR_TYPE_TTY, ad1816_intr, ad1816, &ih); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/DSP_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &ad1816->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", rman_get_start(ad1816->io_base), rman_get_start(ad1816->irq), rman_get_start(ad1816->drq1)); if (ad1816->drq2) snprintf(status + strlen(status), SND_STATUSLEN - strlen(status), ":%ld", rman_get_start(ad1816->drq2)); if (pcm_register(dev, ad1816, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &ad1816_chantemplate, ad1816); pcm_addchan(dev, PCMDIR_PLAY, &ad1816_chantemplate, ad1816); pcm_setstatus(dev, status); return 0; no: ad1816_release_resources(ad1816, dev); return ENXIO; } static device_method_t ad1816_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ad1816_probe), DEVMETHOD(device_attach, ad1816_attach), { 0, 0 } }; static driver_t ad1816_driver = { "pcm", ad1816_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_ad1816, isa, ad1816_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ad1816, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_ad1816, 1); Index: head/sys/dev/sound/isa/ess.c =================================================================== --- head/sys/dev/sound/isa/ess.c (revision 65339) +++ head/sys/dev/sound/isa/ess.c (revision 65340) @@ -1,945 +1,953 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright 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. * * $FreeBSD$ */ #include #include #include #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 /* channel interface for ESS */ static void *esschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int esschan_setdir(void *data, int dir); static int esschan_setformat(void *data, u_int32_t format); static int esschan_setspeed(void *data, u_int32_t speed); static int esschan_setblocksize(void *data, u_int32_t blocksize); static int esschan_trigger(void *data, int go); static int esschan_getptr(void *data); static pcmchan_caps *esschan_getcaps(void *data); 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 pcmchan_caps ess_playcaps = {5000, 49000, 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 pcmchan_caps ess_reccaps = {5000, 49000, ess_rfmt, 0}; static pcm_channel ess_chantemplate = { esschan_init, esschan_setdir, esschan_setformat, esschan_setspeed, esschan_setblocksize, esschan_trigger, esschan_getptr, esschan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; struct ess_info; struct ess_chinfo { struct ess_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir, hwch, stopping; u_int32_t fmt, spd; }; struct ess_info { struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; bus_dma_tag_t parent_dmat; int type, duplex:1, newspeed:1; u_long bd_flags; /* board-specific flags */ struct ess_chinfo pch, rch; }; 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 essmix_init(snd_mixer *m); static int essmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int essmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer ess_mixer = { - "ESS mixer", - essmix_init, - essmix_set, - essmix_setrecsrc, + "ESS mixer", + essmix_init, + NULL, + essmix_set, + essmix_setrecsrc, }; static devclass_t pcm_devclass; /* * 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 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) { return 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) { u_long flags; DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) flags = spltty(); 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); splx(flags); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; u_long flags; flags = spltty(); ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); splx(flags); 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(d->io_base))); return ENXIO; /* Sorry */ } ess_cmd(sc, 0xc6); return 0; } static void ess_release_resources(struct ess_info *sc, device_t dev) { /* should we bus_teardown_intr here? */ if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = 0; } if (sc->drq1) { bus_release_resource(dev, SYS_RES_DRQ, 0, sc->drq1); sc->drq1 = 0; } if (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; } 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(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sc->irq) sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sc->drq1) sc->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 1; if (!sc->drq2) sc->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, 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), ESS_BUFFSIZE); if (sc->drq2) { isa_dma_acquire(rman_get_start(sc->drq2)); isa_dmainit(rman_get_start(sc->drq2), ESS_BUFFSIZE); } return 0; } else return ENXIO; } static int ess_doattach(device_t dev, struct ess_info *sc) { - snddev_info *d = device_get_softc(dev); void *ih; char status[SND_STATUSLEN], buf[64]; int ver; if (ess_alloc_resources(sc, dev)) goto no; if (ess_reset_dsp(sc)) goto no; - mixer_init(d, &ess_mixer, sc); + mixer_init(dev, &ess_mixer, sc); 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); bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, ess_intr, sc, &ih); if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/ESS_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &sc->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", rman_get_start(sc->io_base), rman_get_start(sc->irq), rman_get_start(sc->drq1)); if (sc->drq2) snprintf(status + strlen(status), SND_STATUSLEN - strlen(status), ":%ld", rman_get_start(sc->drq2)); if (pcm_register(dev, sc, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &ess_chantemplate, sc); pcm_addchan(dev, PCMDIR_PLAY, &ess_chantemplate, sc); pcm_setstatus(dev, status); return 0; no: ess_release_resources(sc, dev); return ENXIO; } static void ess_intr(void *arg) { struct ess_info *sc = (struct ess_info *)arg; int src, pirq, rirq; 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.stopping) { buf_isadma(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); } chn_intr(sc->pch.channel); } if (rirq) { if (sc->rch.stopping) { buf_isadma(sc->rch.buffer, PCMTRIG_STOP); sc->rch.stopping = 0; /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } chn_intr(sc->rch.channel); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); } /* 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_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->buffer->dl); 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); return 0; } static int ess_stop(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; 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); return 0; } /* channel interface for ESS18xx */ static void * esschan_init(void *devinfo, snd_dbuf *b, 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; ch->buffer->bufsize = ESS_BUFFSIZE; if (chn_allocbuf(ch->buffer, sc->parent_dmat) == -1) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; ch->buffer->chan = rman_get_start((ch->hwch == 1)? sc->drq1 : sc->drq2); return ch; } static int esschan_setdir(void *data, int dir) { struct ess_chinfo *ch = data; ch->dir = dir; return 0; } static int esschan_setformat(void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static int esschan_setspeed(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(void *data, u_int32_t blocksize) { return blocksize; } static int esschan_trigger(void *data, int go) { struct ess_chinfo *ch = data; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; switch (go) { case PCMTRIG_START: buf_isadma(ch->buffer, go); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } return 0; } static int esschan_getptr(void *data) { struct ess_chinfo *ch = data; return buf_isadmaptr(ch->buffer); } static pcmchan_caps * esschan_getcaps(void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } /************************************************************/ static int essmix_init(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(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(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 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; sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); if (!sc) return ENXIO; bzero(sc, sizeof *sc); return ess_doattach(dev, sc); } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_ess, sbc, ess_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ess, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); 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) { return ISA_PNP_PROBE(device_get_parent(dev), dev, essc_ids); } static int esscontrol_attach(device_t dev) { #ifdef notyet struct resource *io; int rid, i, x; rid = 0; io = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, 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 device_method_t esscontrol_methods[] = { /* Device interface */ DEVMETHOD(device_probe, esscontrol_probe), DEVMETHOD(device_attach, esscontrol_attach), { 0, 0 } }; static driver_t esscontrol_driver = { "esscontrol", esscontrol_methods, sizeof(snddev_info), }; DRIVER_MODULE(esscontrol, isa, esscontrol_driver, esscontrol_devclass, 0, 0); Index: head/sys/dev/sound/isa/mss.c =================================================================== --- head/sys/dev/sound/isa/mss.c (revision 65339) +++ head/sys/dev/sound/isa/mss.c (revision 65340) @@ -1,1894 +1,1903 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright Luigi Rizzo, 1997,1998 * Copyright by Hannu Savolainen 1994, 1995 * 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$ */ #include /* board-specific include files */ #include #include #define MSS_BUFFSIZE (65536 - 256) #define abs(x) (((x) < 0) ? -(x) : (x)) #define MSS_INDEXED_REGS 0x20 #define OPL_INDEXED_REGS 0x19 struct mss_info; struct mss_chinfo { struct mss_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; u_int32_t fmt; }; 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; bus_dma_tag_t parent_dmat; char mss_indexed_regs[MSS_INDEXED_REGS]; char opl_indexed_regs[OPL_INDEXED_REGS]; int pdma, rdma; 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 */ 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); 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); /* 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 int mssmix_init(snd_mixer *m); static int mssmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int mssmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer mss_mixer = { - "MSS mixer", - mssmix_init, - mssmix_set, - mssmix_setrecsrc, + "MSS mixer", + mssmix_init, + NULL, + mssmix_set, + mssmix_setrecsrc, }; static int ymmix_init(snd_mixer *m); static int ymmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int ymmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer yamaha_mixer = { - "OPL3-SAx mixer", - ymmix_init, - ymmix_set, - ymmix_setrecsrc, + "OPL3-SAx mixer", + ymmix_init, + NULL, + ymmix_set, + ymmix_setrecsrc, }; static devclass_t pcm_devclass; /* channel interface */ static void *msschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int msschan_setdir(void *data, int dir); static int msschan_setformat(void *data, u_int32_t format); static int msschan_setspeed(void *data, u_int32_t speed); static int msschan_setblocksize(void *data, u_int32_t blocksize); static int msschan_trigger(void *data, int go); static int msschan_getptr(void *data); static pcmchan_caps *msschan_getcaps(void *data); 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 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 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 pcmchan_caps opti931_caps = {4000, 48000, opti931_fmt, 0}; static pcm_channel mss_chantemplate = { msschan_init, msschan_setdir, msschan_setformat, msschan_setspeed, msschan_setblocksize, msschan_trigger, msschan_getptr, msschan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; #define MD_AD1848 0x91 #define MD_AD1845 0x92 #define MD_CS42XX 0xA1 #define MD_OPTI931 0xB1 #define MD_OPTI925 0xB2 #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 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) return 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; return 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) { bus_release_resource(dev, SYS_RES_IRQ, mss->irq_rid, mss->irq); mss->irq = 0; } if (mss->drq1) { bus_release_resource(dev, SYS_RES_DRQ, mss->drq1_rid, mss->drq1); mss->drq1 = 0; mss->pdma = -1; } if (mss->drq2) { bus_release_resource(dev, SYS_RES_DRQ, mss->drq2_rid, mss->drq2); mss->drq2 = 0; mss->rdma = -1; } 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; } free(mss, M_DEVBUF); } static int mss_alloc_resources(struct mss_info *mss, device_t dev) { int ok = 1; if (!mss->io_base) mss->io_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->io_rid, 0, ~0, 1, RF_ACTIVE); if (!mss->irq) mss->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &mss->irq_rid, 0, ~0, 1, RF_ACTIVE); if (!mss->drq1) mss->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &mss->drq1_rid, 0, ~0, 1, RF_ACTIVE); if (mss->conf_rid >= 0 && !mss->conf_base) mss->conf_base = bus_alloc_resource(dev, SYS_RES_IOPORT, &mss->conf_rid, 0, ~0, 1, RF_ACTIVE); if (mss->drq2_rid >= 0 && !mss->drq2) mss->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &mss->drq2_rid, 0, ~0, 1, 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) { mss->pdma = rman_get_start(mss->drq1); isa_dma_acquire(mss->pdma); isa_dmainit(mss->pdma, MSS_BUFFSIZE); mss->bd_flags &= ~BD_F_DUPLEX; if (mss->drq2) { mss->rdma = rman_get_start(mss->drq2); isa_dma_acquire(mss->rdma); isa_dmainit(mss->rdma, MSS_BUFFSIZE); mss->bd_flags |= BD_F_DUPLEX; } else mss->rdma = mss->pdma; } return ok; } /* * 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(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, 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; } /* * 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); if (!mss) return ENXIO; bzero(mss, sizeof *mss); 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)) { 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 */ /* * 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 a 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; } 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) { - snddev_info *d = device_get_softc(dev); void *ih; int flags = device_get_flags(dev); char status[SND_STATUSLEN]; if (!mss_alloc_resources(mss, dev)) goto no; mss_init(mss, dev); 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[mss->pdma] == -1) goto no; bits |= pdma_bits[mss->pdma]; if (mss->pdma != mss->rdma) { if (mss->rdma == valid_rdma[mss->pdma]) bits |= 4; else { printf("invalid dual dma config %d:%d\n", mss->pdma, mss->rdma); goto no; } } io_wr(mss, 0, bits); printf("drq/irq conf %x\n", io_rd(mss, 0)); } - mixer_init(d, (mss->bd_id == MD_YM0020)? &yamaha_mixer : &mss_mixer, mss); + mixer_init(dev, (mss->bd_id == MD_YM0020)? &yamaha_mixer : &mss_mixer, mss); switch (mss->bd_id) { case MD_OPTI931: bus_setup_intr(dev, mss->irq, INTR_TYPE_TTY, opti931_intr, mss, &ih); break; default: bus_setup_intr(dev, mss->irq, INTR_TYPE_TTY, mss_intr, mss, &ih); } if (mss->pdma == mss->rdma) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/MSS_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &mss->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 %d", rman_get_start(mss->io_base), rman_get_start(mss->irq), mss->pdma); if (mss->pdma != mss->rdma) snprintf(status + strlen(status), SND_STATUSLEN - strlen(status), ":%d", mss->rdma); if (pcm_register(dev, mss, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &mss_chantemplate, mss); pcm_addchan(dev, PCMDIR_PLAY, &mss_chantemplate, mss); pcm_setstatus(dev, status); return 0; no: mss_release_resources(mss, dev); return ENXIO; } 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); if (!mss) return ENXIO; bzero(mss, sizeof *mss); 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) { /* 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); } 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) { /* 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_suspend, mss_suspend), DEVMETHOD(device_resume, mss_resume), { 0, 0 } }; static driver_t mss_driver = { "pcm", mss_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_mss, isa, mss_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_mss, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_mss, 1); /* * 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")); 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 (mss->pch.buffer->dl && (c & 0x10)) { served |= 0x10; chn_intr(mss->pch.channel); } if (mss->rch.buffer->dl && (c & 0x20)) { served |= 0x20; chn_intr(mss->rch.channel); } /* 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 */ } } /* * 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) { u_long flags; int x; flags = spltty(); ad_wait_init(mss, 201); 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); splx(flags); /* printf("ad_read %d, %x\n", reg, x); */ return x; } static void ad_write(struct mss_info *mss, int reg, u_char data) { u_long flags; int x; /* printf("ad_write %d, %x\n", reg, data); */ flags = spltty(); ad_wait_init(mss, 1002); 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); splx(flags); } 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, 1000); 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, 203); 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_long flags; 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, 1000); flags = spltty(); 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); splx(flags); } /* * 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 = (mss->bd_id == MD_OPTI931)? &opti931_devices : &mix_devices; u_char old, val; 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 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_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); if (FULL_DUPLEX(mss)) ad_write(mss, 28, arg); /* capture mode */ 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->buffer->dl / 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; } 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 */ {0x1022b839, "Neomagic 256AV (non-ac97)"}, /* NMX2210 */ #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); if (!mss) return ENXIO; bzero(mss, sizeof *mss); 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; 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 0x1022b839: /* NMX2210 */ mss->io_rid = 1; 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 device_method_t pnpmss_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pnpmss_probe), DEVMETHOD(device_attach, pnpmss_attach), DEVMETHOD(device_suspend, mss_suspend), DEVMETHOD(device_resume, mss_resume), { 0, 0 } }; static driver_t pnpmss_driver = { "pcm", pnpmss_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_pnpmss, isa, pnpmss_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_pnpmss, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_pnpmss, 1); /* * 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 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 DDB(printf("intr, but mc11 not set\n");) } if (loops == 0) BVDDB(printf("intr, nothing in mcir11 0x%02x\n", mc11)); return; } if (mss->rch.buffer->dl && (mc11 & 8)) chn_intr(mss->rch.channel); if (mss->pch.buffer->dl && (mc11 & 4)) chn_intr(mss->pch.channel); opti_wr(mss, 11, ~mc11); /* ack */ if (--loops) goto again; DEB(printf("xxx too many loops\n");) } 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); if (mss == NULL) return ENOMEM; bzero(mss, sizeof *mss); 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), { 0, 0 } }; static driver_t guspcm_driver = { "pcm", guspcm_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_guspcm, gusc, guspcm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_guspcm, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_guspcm, 1); static int mssmix_init(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_OPTI931: mix_setdevs(m, OPTI931_MIXER_DEVICES); ad_write(mss, 20, 0x88); ad_write(mss, 21, 0x88); break; case MD_AD1848: mix_setdevs(m, MODE1_MIXER_DEVICES); break; case MD_GUSPNP: case MD_GUSMAX: /* this is only necessary in mode 3 ... */ ad_write(mss, 22, 0x88); ad_write(mss, 23, 0x88); break; } return 0; } static int mssmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct mss_info *mss = mix_getdevinfo(m); mss_mixer_set(mss, dev, left, right); return left | (right << 8); } static int mssmix_setrecsrc(snd_mixer *m, u_int32_t src) { struct mss_info *mss = mix_getdevinfo(m); src = mss_set_recsrc(mss, src); return src; } static int ymmix_init(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 */ conf_wr(mss, OPL3SAx_VOLUMEL, 7); conf_wr(mss, OPL3SAx_VOLUMER, 7); return 0; } static int ymmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct mss_info *mss = mix_getdevinfo(m); int t, l, r; 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); } return left | (right << 8); } static int ymmix_setrecsrc(snd_mixer *m, u_int32_t src) { struct mss_info *mss = mix_getdevinfo(m); src = mss_set_recsrc(mss, src); return src; } /* channel interface */ static void * msschan_init(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = MSS_BUFFSIZE; if (chn_allocbuf(ch->buffer, mss->parent_dmat) == -1) return NULL; return ch; } static int msschan_setdir(void *data, int dir) { struct mss_chinfo *ch = data; ch->buffer->chan = (dir == PCMDIR_PLAY)? ch->parent->pdma : ch->parent->rdma; ch->dir = dir; return 0; } static int msschan_setformat(void *data, u_int32_t format) { struct mss_chinfo *ch = data; mss_format(ch, format); return 0; } static int msschan_setspeed(void *data, u_int32_t speed) { struct mss_chinfo *ch = data; return mss_speed(ch, speed); } static int msschan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int msschan_trigger(void *data, int go) { struct mss_chinfo *ch = data; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; buf_isadma(ch->buffer, go); mss_trigger(ch, go); return 0; } static int msschan_getptr(void *data) { struct mss_chinfo *ch = data; return buf_isadmaptr(ch->buffer); } static pcmchan_caps * msschan_getcaps(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; } } Index: head/sys/dev/sound/isa/sb.c =================================================================== --- head/sys/dev/sound/isa/sb.c (revision 65339) +++ head/sys/dev/sound/isa/sb.c (revision 65340) @@ -1,908 +1,916 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright 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. * * $FreeBSD$ */ #include #define __SB_MIXER_C__ /* XXX warning... */ #include #include #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) /* channel interface */ static void *sbchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int sbchan_setdir(void *data, int dir); static int sbchan_setformat(void *data, u_int32_t format); static int sbchan_setspeed(void *data, u_int32_t speed); static int sbchan_setblocksize(void *data, u_int32_t blocksize); static int sbchan_trigger(void *data, int go); static int sbchan_getptr(void *data); static pcmchan_caps *sbchan_getcaps(void *data); static u_int32_t sb_playfmt[] = { AFMT_U8, 0 }; static pcmchan_caps sb_playcaps = {4000, 22050, sb_playfmt, 0}; static u_int32_t sb_recfmt[] = { AFMT_U8, 0 }; static pcmchan_caps sb_reccaps = {4000, 13000, sb_recfmt, 0}; static u_int32_t sbpro_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sbpro_playcaps = {4000, 45000, sbpro_playfmt, 0}; static u_int32_t sbpro_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sbpro_reccaps = {4000, 15000, sbpro_recfmt, 0}; static u_int32_t sb16_hfmt[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps sb16_hcaps = {5000, 45000, sb16_hfmt, 0}; static u_int32_t sb16_lfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sb16_lcaps = {5000, 45000, sb16_lfmt, 0}; static u_int32_t sb16x_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps sb16x_caps = {5000, 49000, sb16x_fmt, 0}; static pcm_channel sb_chantemplate = { sbchan_init, sbchan_setdir, sbchan_setformat, sbchan_setspeed, sbchan_setblocksize, sbchan_trigger, sbchan_getptr, sbchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; struct sb_info; struct sb_chinfo { struct sb_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; u_int32_t fmt, spd; }; struct sb_info { struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; bus_dma_tag_t parent_dmat; 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); static int sbmix_init(snd_mixer *m); static int sbmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int sbmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer sb_mixer = { - "SoundBlaster mixer", - sbmix_init, - sbmix_set, - sbmix_setrecsrc, + "SoundBlaster mixer", + sbmix_init, + NULL, + sbmix_set, + sbmix_setrecsrc, }; static devclass_t pcm_devclass; /* * 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 * * ess_write is actually sb_cmd1 * ess_read access ext. regs via sb_cmd(0xc0, reg) followed by sb_get_byte */ 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) { return 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 */ static void sb_setmixer(struct sb_info *sb, u_int port, u_int value) { u_long flags; flags = spltty(); 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); splx(flags); } static int sb_getmixer(struct sb_info *sb, u_int port) { int val; u_long flags; flags = spltty(); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = sb_rd(sb, SB_MIX_DATA); DELAY(10); splx(flags); 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(d->io_base))); return ENXIO; /* Sorry */ } if (sb->bd_flags & BD_F_ESS) sb_cmd(sb, 0xc6); return 0; } static void sb_release_resources(struct sb_info *sb, device_t dev) { /* should we bus_teardown_intr here? */ if (sb->irq) { bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); sb->irq = 0; } if (sb->drq1) { bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq1); sb->drq1 = 0; } if (sb->drq2) { bus_release_resource(dev, SYS_RES_DRQ, 1, sb->drq2); sb->drq2 = 0; } if (sb->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); sb->io_base = 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(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sb->irq) sb->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sb->drq1) sb->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 1; if (!sb->drq2) sb->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); if (sb->io_base && sb->drq1 && sb->irq) { int bs = DSP_BUFFSIZE; isa_dma_acquire(rman_get_start(sb->drq1)); isa_dmainit(rman_get_start(sb->drq1), bs); if (sb->drq2) { isa_dma_acquire(rman_get_start(sb->drq2)); isa_dmainit(rman_get_start(sb->drq2), bs); } return 0; } else return ENXIO; } static void sb16_swap(void *v, int dir) { struct sb_info *sb = v; int pb = sb->pch.buffer->dl; int rb = sb->rch.buffer->dl; int pc = sb->pch.buffer->chan; int rc = sb->rch.buffer->chan; int swp = 0; if (!pb && !rb) { if (dir == PCMDIR_PLAY && pc < 4) swp = 1; else if (dir == PCMDIR_REC && rc < 4) swp = 1; if (swp) { int t; t = sb->pch.buffer->chan; sb->pch.buffer->chan = sb->rch.buffer->chan; sb->rch.buffer->chan = t; sb->pch.buffer->dir = ISADMA_WRITE; sb->rch.buffer->dir = ISADMA_READ; } } } static int sb_doattach(device_t dev, struct sb_info *sb) { - snddev_info *d = device_get_softc(dev); void *ih; char status[SND_STATUSLEN]; int bs = DSP_BUFFSIZE; if (sb_alloc_resources(sb, dev)) goto no; if (sb_reset_dsp(sb)) goto no; - mixer_init(d, &sb_mixer, sb); + mixer_init(dev, &sb_mixer, sb); bus_setup_intr(dev, sb->irq, INTR_TYPE_TTY, sb_intr, sb, &ih); if ((sb->bd_flags & BD_F_SB16) && !(sb->bd_flags & BD_F_SB16X)) pcm_setswap(dev, sb16_swap); if (!sb->drq2) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/bs, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &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", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1)); if (sb->drq2) snprintf(status + strlen(status), SND_STATUSLEN - strlen(status), ":%ld", rman_get_start(sb->drq2)); if (pcm_register(dev, sb, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &sb_chantemplate, sb); pcm_addchan(dev, PCMDIR_PLAY, &sb_chantemplate, sb); pcm_setstatus(dev, status); return 0; no: sb_release_resources(sb, dev); return ENXIO; } static void sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; int reason = 3, c; /* * SB < 4.0 is half duplex and has only 1 bit for int source, * so we fake it. SB 4.x (SB16) has the int source in a separate * register. * The Vibra16X has separate flags for 8 and 16 bit transfers, but * I have no idea how to tell capture from playback interrupts... */ if (sb->bd_flags & BD_F_SB16) { c = sb_getmixer(sb, IRQ_STAT); /* 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... */ reason = 0; if (c & 1) { /* 8-bit dma */ if (sb->pch.fmt & AFMT_U8) reason |= 1; if (sb->rch.fmt & AFMT_U8) reason |= 2; } if (c & 2) { /* 16-bit dma */ if (sb->pch.fmt & AFMT_S16_LE) reason |= 1; if (sb->rch.fmt & AFMT_S16_LE) reason |= 2; } } else c = 1; #if 0 printf("sb_intr: reason=%d c=0x%x\n", reason, c); #endif if ((reason & 1) && (sb->pch.buffer->dl > 0)) chn_intr(sb->pch.channel); if ((reason & 2) && (sb->rch.buffer->dl > 0)) chn_intr(sb->rch.channel); 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 */ } 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 = ch->spd; if (sb->bd_flags & BD_F_SB16) { RANGE(speed, 5000, 45000); sb_cmd(sb, 0x42 - play); sb_cmd(sb, speed >> 8); sb_cmd(sb, speed & 0xff); } else { u_char tconst; int max_speed = 45000, tmp; u_long flags; /* here enforce speed limitations - max 22050 on sb 1.x*/ if (sb->bd_id <= 0x200) max_speed = 22050; /* * SB models earlier than SB Pro have low limit for the * input rate. Note that this is only for input, but since * we do not support separate values for rec & play.... */ if (!play) { if (sb->bd_id <= 0x200) max_speed = 13000; else if (sb->bd_id < 0x300) max_speed = 15000; } RANGE(speed, 4000, max_speed); if (stereo) speed <<= 1; /* * Now the speed should be valid. Compute the value to be * programmed into the board. */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ tconst = (u_char) ((65536 - ((256000000 + speed / 2) / speed)) >> 8); sb->bd_flags |= BD_F_HISPEED; tmp = 65536 - (tconst << 8); speed = (256000000 + tmp / 2) / tmp; } else { sb->bd_flags &= ~BD_F_HISPEED; tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff; tmp = 256 - tconst; speed = (1000000 + tmp / 2) / tmp; } flags = spltty(); sb_cmd1(sb, 0x40, tconst); /* set time constant */ splx(flags); if (stereo) speed >>= 1; } ch->spd = speed; 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 b16 = (ch->fmt & AFMT_S16_LE)? 1 : 0; int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; int l = ch->buffer->dl; int dh = ch->buffer->chan > 3; u_char i1, i2; if (b16 || dh) l >>= 1; l--; if (play) sb_cmd(sb, DSP_CMD_SPKON); if (sb->bd_flags & BD_F_SB16) { i1 = DSP_F16_AUTO | DSP_F16_FIFO_ON; i1 |= play? DSP_F16_DAC : DSP_F16_ADC; i1 |= (b16 || dh)? DSP_DMA16 : DSP_DMA8; i2 = (stereo? DSP_F16_STEREO : 0) | (b16? DSP_F16_SIGNED : 0); sb_cmd(sb, i1); sb_cmd2(sb, i2, l); } else { if (sb->bd_flags & BD_F_HISPEED) i1 = play? 0x90 : 0x98; else i1 = play? 0x1c : 0x2c; sb_setmixer(sb, 0x0e, stereo? 2 : 0); sb_cmd2(sb, 0x48, l); sb_cmd(sb, i1); } sb->bd_flags |= BD_F_DMARUN << b16; return 0; } static int sb_stop(struct sb_chinfo *ch) { struct sb_info *sb = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; int b16 = (ch->fmt & AFMT_S16_LE)? 1 : 0; if (sb->bd_flags & BD_F_HISPEED) sb_reset_dsp(sb); else { sb_cmd(sb, b16? DSP_CMD_DMAPAUSE_16 : DSP_CMD_DMAPAUSE_8); /* * The above seems to have the undocumented side effect of * blocking the other side as well. If the other * channel was active (SB16) I have to re-enable it :( */ if (sb->bd_flags & (BD_F_DMARUN << (1 - b16))) sb_cmd(sb, b16? 0xd4 : 0xd6 ); } if (play) sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ sb->bd_flags &= ~(BD_F_DMARUN << b16); return 0; } /* channel interface */ static void * sbchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct sb_info *sb = devinfo; struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; int dch, dl, dh; ch->parent = sb; ch->channel = c; ch->buffer = b; ch->buffer->bufsize = DSP_BUFFSIZE; if (chn_allocbuf(ch->buffer, sb->parent_dmat) == -1) return NULL; dch = (dir == PCMDIR_PLAY)? 1 : 0; if (sb->bd_flags & BD_F_SB16X) dch = !dch; dl = rman_get_start(sb->drq1); dh = sb->drq2? rman_get_start(sb->drq2) : dl; ch->buffer->chan = dch? dh : dl; return ch; } static int sbchan_setdir(void *data, int dir) { struct sb_chinfo *ch = data; ch->dir = dir; return 0; } static int sbchan_setformat(void *data, u_int32_t format) { struct sb_chinfo *ch = data; ch->fmt = format; return 0; } static int sbchan_setspeed(void *data, u_int32_t speed) { struct sb_chinfo *ch = data; ch->spd = speed; return sb_speed(ch); } static int sbchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int sbchan_trigger(void *data, int go) { struct sb_chinfo *ch = data; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; buf_isadma(ch->buffer, go); if (go == PCMTRIG_START) sb_start(ch); else sb_stop(ch); return 0; } static int sbchan_getptr(void *data) { struct sb_chinfo *ch = data; return buf_isadmaptr(ch->buffer); } static pcmchan_caps * sbchan_getcaps(void *data) { struct sb_chinfo *ch = data; int p = (ch->dir == PCMDIR_PLAY)? 1 : 0; if (ch->parent->bd_id < 0x300) return p? &sb_playcaps : &sb_reccaps; else if (ch->parent->bd_id < 0x400) return p? &sbpro_playcaps : &sbpro_reccaps; else if (ch->parent->bd_flags & BD_F_SB16X) return &sb16x_caps; else return (ch->buffer->chan >= 4)? &sb16_hcaps : &sb16_lcaps; } /************************************************************/ static int sbmix_init(snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: /* SB 3.0 has 1345 mixer */ mix_setdevs(m, SBPRO_MIXER_DEVICES); mix_setrecdevs(m, SBPRO_RECORDING_DEVICES); sb_setmixer(sb, 0, 1); /* reset mixer */ sb_setmixer(sb, MIC_VOL, 0x6); /* mic volume max */ sb_setmixer(sb, RECORD_SRC, 0x0); /* mic source */ sb_setmixer(sb, FM_VOL, 0x0); /* no midi */ break; case BD_F_MIX_CT1745: /* SB16 mixer ... */ mix_setdevs(m, SB16_MIXER_DEVICES); mix_setrecdevs(m, SB16_RECORDING_DEVICES); 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 sbmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); int regoffs; u_char val; mixer_tab *iomap; switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: iomap = &sbpro_mix; break; case BD_F_MIX_CT1745: iomap = &sb16_mix; break; default: return -1; } /* Change left channel */ regoffs = (*iomap)[dev][LEFT_CHN].regno; if (regoffs != 0) { val = sb_getmixer(sb, regoffs); change_bits(iomap, &val, dev, LEFT_CHN, left); sb_setmixer(sb, regoffs, val); } /* Change right channel */ regoffs = (*iomap)[dev][RIGHT_CHN].regno; if (regoffs != 0) { val = sb_getmixer(sb, regoffs); /* Read the new one */ change_bits(iomap, &val, dev, RIGHT_CHN, right); sb_setmixer(sb, regoffs, val); } else right = left; return left | (right << 8); } static int sbmix_setrecsrc(snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); u_char recdev; switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: 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)); break; case BD_F_MIX_CT1745: /* sb16 */ recdev = 0; if (src & SOUND_MASK_MIC) recdev |= 0x01; /* mono mic */ if (src & SOUND_MASK_CD) recdev |= 0x06; /* l+r cd */ if (src & SOUND_MASK_LINE) recdev |= 0x18; /* l+r line */ if (src & SOUND_MASK_SYNTH) recdev |= 0x60; /* l+r midi */ sb_setmixer(sb, SB16_IMASK_L, recdev); sb_setmixer(sb, SB16_IMASK_R, recdev); /* * 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); break; } return src; } static int sbsbc_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) return (ENXIO); snprintf(buf, sizeof buf, "SB DSP %d.%02d%s", (int) ver >> 8, (int) ver & 0xff, (f & BD_F_SB16X)? " (ViBRA16X)" : ""); device_set_desc_copy(dev, buf); return 0; } static int sbsbc_attach(device_t dev) { struct sb_info *sb; uintptr_t ver; sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT); if (!sb) return ENXIO; bzero(sb, sizeof *sb); BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; sb->bd_flags = (ver & 0xffff0000) >> 16; return sb_doattach(dev, sb); } static device_method_t sbsbc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sbsbc_probe), DEVMETHOD(device_attach, sbsbc_attach), { 0, 0 } }; static driver_t sbsbc_driver = { "pcm", sbsbc_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_sb, sbc, sbsbc_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_sb, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_sb, 1); Index: head/sys/dev/sound/isa/sb16.c =================================================================== --- head/sys/dev/sound/isa/sb16.c (revision 65339) +++ head/sys/dev/sound/isa/sb16.c (revision 65340) @@ -1,908 +1,916 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright 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. * * $FreeBSD$ */ #include #define __SB_MIXER_C__ /* XXX warning... */ #include #include #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) /* channel interface */ static void *sbchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int sbchan_setdir(void *data, int dir); static int sbchan_setformat(void *data, u_int32_t format); static int sbchan_setspeed(void *data, u_int32_t speed); static int sbchan_setblocksize(void *data, u_int32_t blocksize); static int sbchan_trigger(void *data, int go); static int sbchan_getptr(void *data); static pcmchan_caps *sbchan_getcaps(void *data); static u_int32_t sb_playfmt[] = { AFMT_U8, 0 }; static pcmchan_caps sb_playcaps = {4000, 22050, sb_playfmt, 0}; static u_int32_t sb_recfmt[] = { AFMT_U8, 0 }; static pcmchan_caps sb_reccaps = {4000, 13000, sb_recfmt, 0}; static u_int32_t sbpro_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sbpro_playcaps = {4000, 45000, sbpro_playfmt, 0}; static u_int32_t sbpro_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sbpro_reccaps = {4000, 15000, sbpro_recfmt, 0}; static u_int32_t sb16_hfmt[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps sb16_hcaps = {5000, 45000, sb16_hfmt, 0}; static u_int32_t sb16_lfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sb16_lcaps = {5000, 45000, sb16_lfmt, 0}; static u_int32_t sb16x_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps sb16x_caps = {5000, 49000, sb16x_fmt, 0}; static pcm_channel sb_chantemplate = { sbchan_init, sbchan_setdir, sbchan_setformat, sbchan_setspeed, sbchan_setblocksize, sbchan_trigger, sbchan_getptr, sbchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; struct sb_info; struct sb_chinfo { struct sb_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; u_int32_t fmt, spd; }; struct sb_info { struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; bus_dma_tag_t parent_dmat; 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); static int sbmix_init(snd_mixer *m); static int sbmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int sbmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer sb_mixer = { - "SoundBlaster mixer", - sbmix_init, - sbmix_set, - sbmix_setrecsrc, + "SoundBlaster mixer", + sbmix_init, + NULL, + sbmix_set, + sbmix_setrecsrc, }; static devclass_t pcm_devclass; /* * 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 * * ess_write is actually sb_cmd1 * ess_read access ext. regs via sb_cmd(0xc0, reg) followed by sb_get_byte */ 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) { return 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 */ static void sb_setmixer(struct sb_info *sb, u_int port, u_int value) { u_long flags; flags = spltty(); 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); splx(flags); } static int sb_getmixer(struct sb_info *sb, u_int port) { int val; u_long flags; flags = spltty(); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = sb_rd(sb, SB_MIX_DATA); DELAY(10); splx(flags); 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(d->io_base))); return ENXIO; /* Sorry */ } if (sb->bd_flags & BD_F_ESS) sb_cmd(sb, 0xc6); return 0; } static void sb_release_resources(struct sb_info *sb, device_t dev) { /* should we bus_teardown_intr here? */ if (sb->irq) { bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); sb->irq = 0; } if (sb->drq1) { bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq1); sb->drq1 = 0; } if (sb->drq2) { bus_release_resource(dev, SYS_RES_DRQ, 1, sb->drq2); sb->drq2 = 0; } if (sb->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); sb->io_base = 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(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sb->irq) sb->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sb->drq1) sb->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 1; if (!sb->drq2) sb->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); if (sb->io_base && sb->drq1 && sb->irq) { int bs = DSP_BUFFSIZE; isa_dma_acquire(rman_get_start(sb->drq1)); isa_dmainit(rman_get_start(sb->drq1), bs); if (sb->drq2) { isa_dma_acquire(rman_get_start(sb->drq2)); isa_dmainit(rman_get_start(sb->drq2), bs); } return 0; } else return ENXIO; } static void sb16_swap(void *v, int dir) { struct sb_info *sb = v; int pb = sb->pch.buffer->dl; int rb = sb->rch.buffer->dl; int pc = sb->pch.buffer->chan; int rc = sb->rch.buffer->chan; int swp = 0; if (!pb && !rb) { if (dir == PCMDIR_PLAY && pc < 4) swp = 1; else if (dir == PCMDIR_REC && rc < 4) swp = 1; if (swp) { int t; t = sb->pch.buffer->chan; sb->pch.buffer->chan = sb->rch.buffer->chan; sb->rch.buffer->chan = t; sb->pch.buffer->dir = ISADMA_WRITE; sb->rch.buffer->dir = ISADMA_READ; } } } static int sb_doattach(device_t dev, struct sb_info *sb) { - snddev_info *d = device_get_softc(dev); void *ih; char status[SND_STATUSLEN]; int bs = DSP_BUFFSIZE; if (sb_alloc_resources(sb, dev)) goto no; if (sb_reset_dsp(sb)) goto no; - mixer_init(d, &sb_mixer, sb); + mixer_init(dev, &sb_mixer, sb); bus_setup_intr(dev, sb->irq, INTR_TYPE_TTY, sb_intr, sb, &ih); if ((sb->bd_flags & BD_F_SB16) && !(sb->bd_flags & BD_F_SB16X)) pcm_setswap(dev, sb16_swap); if (!sb->drq2) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/bs, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &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", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1)); if (sb->drq2) snprintf(status + strlen(status), SND_STATUSLEN - strlen(status), ":%ld", rman_get_start(sb->drq2)); if (pcm_register(dev, sb, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &sb_chantemplate, sb); pcm_addchan(dev, PCMDIR_PLAY, &sb_chantemplate, sb); pcm_setstatus(dev, status); return 0; no: sb_release_resources(sb, dev); return ENXIO; } static void sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; int reason = 3, c; /* * SB < 4.0 is half duplex and has only 1 bit for int source, * so we fake it. SB 4.x (SB16) has the int source in a separate * register. * The Vibra16X has separate flags for 8 and 16 bit transfers, but * I have no idea how to tell capture from playback interrupts... */ if (sb->bd_flags & BD_F_SB16) { c = sb_getmixer(sb, IRQ_STAT); /* 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... */ reason = 0; if (c & 1) { /* 8-bit dma */ if (sb->pch.fmt & AFMT_U8) reason |= 1; if (sb->rch.fmt & AFMT_U8) reason |= 2; } if (c & 2) { /* 16-bit dma */ if (sb->pch.fmt & AFMT_S16_LE) reason |= 1; if (sb->rch.fmt & AFMT_S16_LE) reason |= 2; } } else c = 1; #if 0 printf("sb_intr: reason=%d c=0x%x\n", reason, c); #endif if ((reason & 1) && (sb->pch.buffer->dl > 0)) chn_intr(sb->pch.channel); if ((reason & 2) && (sb->rch.buffer->dl > 0)) chn_intr(sb->rch.channel); 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 */ } 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 = ch->spd; if (sb->bd_flags & BD_F_SB16) { RANGE(speed, 5000, 45000); sb_cmd(sb, 0x42 - play); sb_cmd(sb, speed >> 8); sb_cmd(sb, speed & 0xff); } else { u_char tconst; int max_speed = 45000, tmp; u_long flags; /* here enforce speed limitations - max 22050 on sb 1.x*/ if (sb->bd_id <= 0x200) max_speed = 22050; /* * SB models earlier than SB Pro have low limit for the * input rate. Note that this is only for input, but since * we do not support separate values for rec & play.... */ if (!play) { if (sb->bd_id <= 0x200) max_speed = 13000; else if (sb->bd_id < 0x300) max_speed = 15000; } RANGE(speed, 4000, max_speed); if (stereo) speed <<= 1; /* * Now the speed should be valid. Compute the value to be * programmed into the board. */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ tconst = (u_char) ((65536 - ((256000000 + speed / 2) / speed)) >> 8); sb->bd_flags |= BD_F_HISPEED; tmp = 65536 - (tconst << 8); speed = (256000000 + tmp / 2) / tmp; } else { sb->bd_flags &= ~BD_F_HISPEED; tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff; tmp = 256 - tconst; speed = (1000000 + tmp / 2) / tmp; } flags = spltty(); sb_cmd1(sb, 0x40, tconst); /* set time constant */ splx(flags); if (stereo) speed >>= 1; } ch->spd = speed; 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 b16 = (ch->fmt & AFMT_S16_LE)? 1 : 0; int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; int l = ch->buffer->dl; int dh = ch->buffer->chan > 3; u_char i1, i2; if (b16 || dh) l >>= 1; l--; if (play) sb_cmd(sb, DSP_CMD_SPKON); if (sb->bd_flags & BD_F_SB16) { i1 = DSP_F16_AUTO | DSP_F16_FIFO_ON; i1 |= play? DSP_F16_DAC : DSP_F16_ADC; i1 |= (b16 || dh)? DSP_DMA16 : DSP_DMA8; i2 = (stereo? DSP_F16_STEREO : 0) | (b16? DSP_F16_SIGNED : 0); sb_cmd(sb, i1); sb_cmd2(sb, i2, l); } else { if (sb->bd_flags & BD_F_HISPEED) i1 = play? 0x90 : 0x98; else i1 = play? 0x1c : 0x2c; sb_setmixer(sb, 0x0e, stereo? 2 : 0); sb_cmd2(sb, 0x48, l); sb_cmd(sb, i1); } sb->bd_flags |= BD_F_DMARUN << b16; return 0; } static int sb_stop(struct sb_chinfo *ch) { struct sb_info *sb = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; int b16 = (ch->fmt & AFMT_S16_LE)? 1 : 0; if (sb->bd_flags & BD_F_HISPEED) sb_reset_dsp(sb); else { sb_cmd(sb, b16? DSP_CMD_DMAPAUSE_16 : DSP_CMD_DMAPAUSE_8); /* * The above seems to have the undocumented side effect of * blocking the other side as well. If the other * channel was active (SB16) I have to re-enable it :( */ if (sb->bd_flags & (BD_F_DMARUN << (1 - b16))) sb_cmd(sb, b16? 0xd4 : 0xd6 ); } if (play) sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ sb->bd_flags &= ~(BD_F_DMARUN << b16); return 0; } /* channel interface */ static void * sbchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct sb_info *sb = devinfo; struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; int dch, dl, dh; ch->parent = sb; ch->channel = c; ch->buffer = b; ch->buffer->bufsize = DSP_BUFFSIZE; if (chn_allocbuf(ch->buffer, sb->parent_dmat) == -1) return NULL; dch = (dir == PCMDIR_PLAY)? 1 : 0; if (sb->bd_flags & BD_F_SB16X) dch = !dch; dl = rman_get_start(sb->drq1); dh = sb->drq2? rman_get_start(sb->drq2) : dl; ch->buffer->chan = dch? dh : dl; return ch; } static int sbchan_setdir(void *data, int dir) { struct sb_chinfo *ch = data; ch->dir = dir; return 0; } static int sbchan_setformat(void *data, u_int32_t format) { struct sb_chinfo *ch = data; ch->fmt = format; return 0; } static int sbchan_setspeed(void *data, u_int32_t speed) { struct sb_chinfo *ch = data; ch->spd = speed; return sb_speed(ch); } static int sbchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int sbchan_trigger(void *data, int go) { struct sb_chinfo *ch = data; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; buf_isadma(ch->buffer, go); if (go == PCMTRIG_START) sb_start(ch); else sb_stop(ch); return 0; } static int sbchan_getptr(void *data) { struct sb_chinfo *ch = data; return buf_isadmaptr(ch->buffer); } static pcmchan_caps * sbchan_getcaps(void *data) { struct sb_chinfo *ch = data; int p = (ch->dir == PCMDIR_PLAY)? 1 : 0; if (ch->parent->bd_id < 0x300) return p? &sb_playcaps : &sb_reccaps; else if (ch->parent->bd_id < 0x400) return p? &sbpro_playcaps : &sbpro_reccaps; else if (ch->parent->bd_flags & BD_F_SB16X) return &sb16x_caps; else return (ch->buffer->chan >= 4)? &sb16_hcaps : &sb16_lcaps; } /************************************************************/ static int sbmix_init(snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: /* SB 3.0 has 1345 mixer */ mix_setdevs(m, SBPRO_MIXER_DEVICES); mix_setrecdevs(m, SBPRO_RECORDING_DEVICES); sb_setmixer(sb, 0, 1); /* reset mixer */ sb_setmixer(sb, MIC_VOL, 0x6); /* mic volume max */ sb_setmixer(sb, RECORD_SRC, 0x0); /* mic source */ sb_setmixer(sb, FM_VOL, 0x0); /* no midi */ break; case BD_F_MIX_CT1745: /* SB16 mixer ... */ mix_setdevs(m, SB16_MIXER_DEVICES); mix_setrecdevs(m, SB16_RECORDING_DEVICES); 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 sbmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); int regoffs; u_char val; mixer_tab *iomap; switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: iomap = &sbpro_mix; break; case BD_F_MIX_CT1745: iomap = &sb16_mix; break; default: return -1; } /* Change left channel */ regoffs = (*iomap)[dev][LEFT_CHN].regno; if (regoffs != 0) { val = sb_getmixer(sb, regoffs); change_bits(iomap, &val, dev, LEFT_CHN, left); sb_setmixer(sb, regoffs, val); } /* Change right channel */ regoffs = (*iomap)[dev][RIGHT_CHN].regno; if (regoffs != 0) { val = sb_getmixer(sb, regoffs); /* Read the new one */ change_bits(iomap, &val, dev, RIGHT_CHN, right); sb_setmixer(sb, regoffs, val); } else right = left; return left | (right << 8); } static int sbmix_setrecsrc(snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); u_char recdev; switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: 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)); break; case BD_F_MIX_CT1745: /* sb16 */ recdev = 0; if (src & SOUND_MASK_MIC) recdev |= 0x01; /* mono mic */ if (src & SOUND_MASK_CD) recdev |= 0x06; /* l+r cd */ if (src & SOUND_MASK_LINE) recdev |= 0x18; /* l+r line */ if (src & SOUND_MASK_SYNTH) recdev |= 0x60; /* l+r midi */ sb_setmixer(sb, SB16_IMASK_L, recdev); sb_setmixer(sb, SB16_IMASK_R, recdev); /* * 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); break; } return src; } static int sbsbc_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) return (ENXIO); snprintf(buf, sizeof buf, "SB DSP %d.%02d%s", (int) ver >> 8, (int) ver & 0xff, (f & BD_F_SB16X)? " (ViBRA16X)" : ""); device_set_desc_copy(dev, buf); return 0; } static int sbsbc_attach(device_t dev) { struct sb_info *sb; uintptr_t ver; sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT); if (!sb) return ENXIO; bzero(sb, sizeof *sb); BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; sb->bd_flags = (ver & 0xffff0000) >> 16; return sb_doattach(dev, sb); } static device_method_t sbsbc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sbsbc_probe), DEVMETHOD(device_attach, sbsbc_attach), { 0, 0 } }; static driver_t sbsbc_driver = { "pcm", sbsbc_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_sb, sbc, sbsbc_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_sb, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_sb, 1); Index: head/sys/dev/sound/isa/sb8.c =================================================================== --- head/sys/dev/sound/isa/sb8.c (revision 65339) +++ head/sys/dev/sound/isa/sb8.c (revision 65340) @@ -1,908 +1,916 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright 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. * * $FreeBSD$ */ #include #define __SB_MIXER_C__ /* XXX warning... */ #include #include #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) /* channel interface */ static void *sbchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int sbchan_setdir(void *data, int dir); static int sbchan_setformat(void *data, u_int32_t format); static int sbchan_setspeed(void *data, u_int32_t speed); static int sbchan_setblocksize(void *data, u_int32_t blocksize); static int sbchan_trigger(void *data, int go); static int sbchan_getptr(void *data); static pcmchan_caps *sbchan_getcaps(void *data); static u_int32_t sb_playfmt[] = { AFMT_U8, 0 }; static pcmchan_caps sb_playcaps = {4000, 22050, sb_playfmt, 0}; static u_int32_t sb_recfmt[] = { AFMT_U8, 0 }; static pcmchan_caps sb_reccaps = {4000, 13000, sb_recfmt, 0}; static u_int32_t sbpro_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sbpro_playcaps = {4000, 45000, sbpro_playfmt, 0}; static u_int32_t sbpro_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sbpro_reccaps = {4000, 15000, sbpro_recfmt, 0}; static u_int32_t sb16_hfmt[] = { AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps sb16_hcaps = {5000, 45000, sb16_hfmt, 0}; static u_int32_t sb16_lfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, 0 }; static pcmchan_caps sb16_lcaps = {5000, 45000, sb16_lfmt, 0}; static u_int32_t sb16x_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps sb16x_caps = {5000, 49000, sb16x_fmt, 0}; static pcm_channel sb_chantemplate = { sbchan_init, sbchan_setdir, sbchan_setformat, sbchan_setspeed, sbchan_setblocksize, sbchan_trigger, sbchan_getptr, sbchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; struct sb_info; struct sb_chinfo { struct sb_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; u_int32_t fmt, spd; }; struct sb_info { struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; bus_dma_tag_t parent_dmat; 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); static int sbmix_init(snd_mixer *m); static int sbmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int sbmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer sb_mixer = { - "SoundBlaster mixer", - sbmix_init, - sbmix_set, - sbmix_setrecsrc, + "SoundBlaster mixer", + sbmix_init, + NULL, + sbmix_set, + sbmix_setrecsrc, }; static devclass_t pcm_devclass; /* * 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 * * ess_write is actually sb_cmd1 * ess_read access ext. regs via sb_cmd(0xc0, reg) followed by sb_get_byte */ 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) { return 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 */ static void sb_setmixer(struct sb_info *sb, u_int port, u_int value) { u_long flags; flags = spltty(); 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); splx(flags); } static int sb_getmixer(struct sb_info *sb, u_int port) { int val; u_long flags; flags = spltty(); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = sb_rd(sb, SB_MIX_DATA); DELAY(10); splx(flags); 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(d->io_base))); return ENXIO; /* Sorry */ } if (sb->bd_flags & BD_F_ESS) sb_cmd(sb, 0xc6); return 0; } static void sb_release_resources(struct sb_info *sb, device_t dev) { /* should we bus_teardown_intr here? */ if (sb->irq) { bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); sb->irq = 0; } if (sb->drq1) { bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq1); sb->drq1 = 0; } if (sb->drq2) { bus_release_resource(dev, SYS_RES_DRQ, 1, sb->drq2); sb->drq2 = 0; } if (sb->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); sb->io_base = 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(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sb->irq) sb->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; if (!sb->drq1) sb->drq1 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); rid = 1; if (!sb->drq2) sb->drq2 = bus_alloc_resource(dev, SYS_RES_DRQ, &rid, 0, ~0, 1, RF_ACTIVE); if (sb->io_base && sb->drq1 && sb->irq) { int bs = DSP_BUFFSIZE; isa_dma_acquire(rman_get_start(sb->drq1)); isa_dmainit(rman_get_start(sb->drq1), bs); if (sb->drq2) { isa_dma_acquire(rman_get_start(sb->drq2)); isa_dmainit(rman_get_start(sb->drq2), bs); } return 0; } else return ENXIO; } static void sb16_swap(void *v, int dir) { struct sb_info *sb = v; int pb = sb->pch.buffer->dl; int rb = sb->rch.buffer->dl; int pc = sb->pch.buffer->chan; int rc = sb->rch.buffer->chan; int swp = 0; if (!pb && !rb) { if (dir == PCMDIR_PLAY && pc < 4) swp = 1; else if (dir == PCMDIR_REC && rc < 4) swp = 1; if (swp) { int t; t = sb->pch.buffer->chan; sb->pch.buffer->chan = sb->rch.buffer->chan; sb->rch.buffer->chan = t; sb->pch.buffer->dir = ISADMA_WRITE; sb->rch.buffer->dir = ISADMA_READ; } } } static int sb_doattach(device_t dev, struct sb_info *sb) { - snddev_info *d = device_get_softc(dev); void *ih; char status[SND_STATUSLEN]; int bs = DSP_BUFFSIZE; if (sb_alloc_resources(sb, dev)) goto no; if (sb_reset_dsp(sb)) goto no; - mixer_init(d, &sb_mixer, sb); + mixer_init(dev, &sb_mixer, sb); bus_setup_intr(dev, sb->irq, INTR_TYPE_TTY, sb_intr, sb, &ih); if ((sb->bd_flags & BD_F_SB16) && !(sb->bd_flags & BD_F_SB16X)) pcm_setswap(dev, sb16_swap); if (!sb->drq2) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/bs, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &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", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1)); if (sb->drq2) snprintf(status + strlen(status), SND_STATUSLEN - strlen(status), ":%ld", rman_get_start(sb->drq2)); if (pcm_register(dev, sb, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &sb_chantemplate, sb); pcm_addchan(dev, PCMDIR_PLAY, &sb_chantemplate, sb); pcm_setstatus(dev, status); return 0; no: sb_release_resources(sb, dev); return ENXIO; } static void sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; int reason = 3, c; /* * SB < 4.0 is half duplex and has only 1 bit for int source, * so we fake it. SB 4.x (SB16) has the int source in a separate * register. * The Vibra16X has separate flags for 8 and 16 bit transfers, but * I have no idea how to tell capture from playback interrupts... */ if (sb->bd_flags & BD_F_SB16) { c = sb_getmixer(sb, IRQ_STAT); /* 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... */ reason = 0; if (c & 1) { /* 8-bit dma */ if (sb->pch.fmt & AFMT_U8) reason |= 1; if (sb->rch.fmt & AFMT_U8) reason |= 2; } if (c & 2) { /* 16-bit dma */ if (sb->pch.fmt & AFMT_S16_LE) reason |= 1; if (sb->rch.fmt & AFMT_S16_LE) reason |= 2; } } else c = 1; #if 0 printf("sb_intr: reason=%d c=0x%x\n", reason, c); #endif if ((reason & 1) && (sb->pch.buffer->dl > 0)) chn_intr(sb->pch.channel); if ((reason & 2) && (sb->rch.buffer->dl > 0)) chn_intr(sb->rch.channel); 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 */ } 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 = ch->spd; if (sb->bd_flags & BD_F_SB16) { RANGE(speed, 5000, 45000); sb_cmd(sb, 0x42 - play); sb_cmd(sb, speed >> 8); sb_cmd(sb, speed & 0xff); } else { u_char tconst; int max_speed = 45000, tmp; u_long flags; /* here enforce speed limitations - max 22050 on sb 1.x*/ if (sb->bd_id <= 0x200) max_speed = 22050; /* * SB models earlier than SB Pro have low limit for the * input rate. Note that this is only for input, but since * we do not support separate values for rec & play.... */ if (!play) { if (sb->bd_id <= 0x200) max_speed = 13000; else if (sb->bd_id < 0x300) max_speed = 15000; } RANGE(speed, 4000, max_speed); if (stereo) speed <<= 1; /* * Now the speed should be valid. Compute the value to be * programmed into the board. */ if (speed > 22050) { /* High speed mode on 2.01/3.xx */ tconst = (u_char) ((65536 - ((256000000 + speed / 2) / speed)) >> 8); sb->bd_flags |= BD_F_HISPEED; tmp = 65536 - (tconst << 8); speed = (256000000 + tmp / 2) / tmp; } else { sb->bd_flags &= ~BD_F_HISPEED; tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff; tmp = 256 - tconst; speed = (1000000 + tmp / 2) / tmp; } flags = spltty(); sb_cmd1(sb, 0x40, tconst); /* set time constant */ splx(flags); if (stereo) speed >>= 1; } ch->spd = speed; 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 b16 = (ch->fmt & AFMT_S16_LE)? 1 : 0; int stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; int l = ch->buffer->dl; int dh = ch->buffer->chan > 3; u_char i1, i2; if (b16 || dh) l >>= 1; l--; if (play) sb_cmd(sb, DSP_CMD_SPKON); if (sb->bd_flags & BD_F_SB16) { i1 = DSP_F16_AUTO | DSP_F16_FIFO_ON; i1 |= play? DSP_F16_DAC : DSP_F16_ADC; i1 |= (b16 || dh)? DSP_DMA16 : DSP_DMA8; i2 = (stereo? DSP_F16_STEREO : 0) | (b16? DSP_F16_SIGNED : 0); sb_cmd(sb, i1); sb_cmd2(sb, i2, l); } else { if (sb->bd_flags & BD_F_HISPEED) i1 = play? 0x90 : 0x98; else i1 = play? 0x1c : 0x2c; sb_setmixer(sb, 0x0e, stereo? 2 : 0); sb_cmd2(sb, 0x48, l); sb_cmd(sb, i1); } sb->bd_flags |= BD_F_DMARUN << b16; return 0; } static int sb_stop(struct sb_chinfo *ch) { struct sb_info *sb = ch->parent; int play = (ch->dir == PCMDIR_PLAY)? 1 : 0; int b16 = (ch->fmt & AFMT_S16_LE)? 1 : 0; if (sb->bd_flags & BD_F_HISPEED) sb_reset_dsp(sb); else { sb_cmd(sb, b16? DSP_CMD_DMAPAUSE_16 : DSP_CMD_DMAPAUSE_8); /* * The above seems to have the undocumented side effect of * blocking the other side as well. If the other * channel was active (SB16) I have to re-enable it :( */ if (sb->bd_flags & (BD_F_DMARUN << (1 - b16))) sb_cmd(sb, b16? 0xd4 : 0xd6 ); } if (play) sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ sb->bd_flags &= ~(BD_F_DMARUN << b16); return 0; } /* channel interface */ static void * sbchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct sb_info *sb = devinfo; struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; int dch, dl, dh; ch->parent = sb; ch->channel = c; ch->buffer = b; ch->buffer->bufsize = DSP_BUFFSIZE; if (chn_allocbuf(ch->buffer, sb->parent_dmat) == -1) return NULL; dch = (dir == PCMDIR_PLAY)? 1 : 0; if (sb->bd_flags & BD_F_SB16X) dch = !dch; dl = rman_get_start(sb->drq1); dh = sb->drq2? rman_get_start(sb->drq2) : dl; ch->buffer->chan = dch? dh : dl; return ch; } static int sbchan_setdir(void *data, int dir) { struct sb_chinfo *ch = data; ch->dir = dir; return 0; } static int sbchan_setformat(void *data, u_int32_t format) { struct sb_chinfo *ch = data; ch->fmt = format; return 0; } static int sbchan_setspeed(void *data, u_int32_t speed) { struct sb_chinfo *ch = data; ch->spd = speed; return sb_speed(ch); } static int sbchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int sbchan_trigger(void *data, int go) { struct sb_chinfo *ch = data; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; buf_isadma(ch->buffer, go); if (go == PCMTRIG_START) sb_start(ch); else sb_stop(ch); return 0; } static int sbchan_getptr(void *data) { struct sb_chinfo *ch = data; return buf_isadmaptr(ch->buffer); } static pcmchan_caps * sbchan_getcaps(void *data) { struct sb_chinfo *ch = data; int p = (ch->dir == PCMDIR_PLAY)? 1 : 0; if (ch->parent->bd_id < 0x300) return p? &sb_playcaps : &sb_reccaps; else if (ch->parent->bd_id < 0x400) return p? &sbpro_playcaps : &sbpro_reccaps; else if (ch->parent->bd_flags & BD_F_SB16X) return &sb16x_caps; else return (ch->buffer->chan >= 4)? &sb16_hcaps : &sb16_lcaps; } /************************************************************/ static int sbmix_init(snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: /* SB 3.0 has 1345 mixer */ mix_setdevs(m, SBPRO_MIXER_DEVICES); mix_setrecdevs(m, SBPRO_RECORDING_DEVICES); sb_setmixer(sb, 0, 1); /* reset mixer */ sb_setmixer(sb, MIC_VOL, 0x6); /* mic volume max */ sb_setmixer(sb, RECORD_SRC, 0x0); /* mic source */ sb_setmixer(sb, FM_VOL, 0x0); /* no midi */ break; case BD_F_MIX_CT1745: /* SB16 mixer ... */ mix_setdevs(m, SB16_MIXER_DEVICES); mix_setrecdevs(m, SB16_RECORDING_DEVICES); 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 sbmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); int regoffs; u_char val; mixer_tab *iomap; switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: iomap = &sbpro_mix; break; case BD_F_MIX_CT1745: iomap = &sb16_mix; break; default: return -1; } /* Change left channel */ regoffs = (*iomap)[dev][LEFT_CHN].regno; if (regoffs != 0) { val = sb_getmixer(sb, regoffs); change_bits(iomap, &val, dev, LEFT_CHN, left); sb_setmixer(sb, regoffs, val); } /* Change right channel */ regoffs = (*iomap)[dev][RIGHT_CHN].regno; if (regoffs != 0) { val = sb_getmixer(sb, regoffs); /* Read the new one */ change_bits(iomap, &val, dev, RIGHT_CHN, right); sb_setmixer(sb, regoffs, val); } else right = left; return left | (right << 8); } static int sbmix_setrecsrc(snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); u_char recdev; switch (sb->bd_flags & BD_F_MIX_MASK) { case BD_F_MIX_CT1345: 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)); break; case BD_F_MIX_CT1745: /* sb16 */ recdev = 0; if (src & SOUND_MASK_MIC) recdev |= 0x01; /* mono mic */ if (src & SOUND_MASK_CD) recdev |= 0x06; /* l+r cd */ if (src & SOUND_MASK_LINE) recdev |= 0x18; /* l+r line */ if (src & SOUND_MASK_SYNTH) recdev |= 0x60; /* l+r midi */ sb_setmixer(sb, SB16_IMASK_L, recdev); sb_setmixer(sb, SB16_IMASK_R, recdev); /* * 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); break; } return src; } static int sbsbc_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) return (ENXIO); snprintf(buf, sizeof buf, "SB DSP %d.%02d%s", (int) ver >> 8, (int) ver & 0xff, (f & BD_F_SB16X)? " (ViBRA16X)" : ""); device_set_desc_copy(dev, buf); return 0; } static int sbsbc_attach(device_t dev) { struct sb_info *sb; uintptr_t ver; sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT); if (!sb) return ENXIO; bzero(sb, sizeof *sb); BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; sb->bd_flags = (ver & 0xffff0000) >> 16; return sb_doattach(dev, sb); } static device_method_t sbsbc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sbsbc_probe), DEVMETHOD(device_attach, sbsbc_attach), { 0, 0 } }; static driver_t sbsbc_driver = { "pcm", sbsbc_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_sb, sbc, sbsbc_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_sb, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_sb, 1); Index: head/sys/dev/sound/pci/aureal.c =================================================================== --- head/sys/dev/sound/pci/aureal.c (revision 65339) +++ head/sys/dev/sound/pci/aureal.c (revision 65340) @@ -1,697 +1,704 @@ /* * 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$ */ #include #include #include #include #include /* PCI IDs of supported chips */ #define AU8820_PCI_ID 0x000112eb /* channel interface */ static void *auchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int auchan_setdir(void *data, int dir); static int auchan_setformat(void *data, u_int32_t format); static int auchan_setspeed(void *data, u_int32_t speed); static int auchan_setblocksize(void *data, u_int32_t blocksize); static int auchan_trigger(void *data, int go); static int auchan_getptr(void *data); static pcmchan_caps *auchan_getcaps(void *data); static u_int32_t au_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static 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 pcmchan_caps au_reccaps = {4000, 48000, au_recfmt, 0}; static pcm_channel au_chantemplate = { auchan_init, auchan_setdir, auchan_setformat, auchan_setspeed, auchan_setblocksize, auchan_trigger, auchan_getptr, auchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* -------------------------------------------------------------------- */ static u_int32_t au_rdcd(void *arg, int regno); static void au_wrcd(void *arg, int regno, u_int32_t data); struct au_info; struct au_chinfo { struct au_info *parent; pcm_channel *channel; 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; 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 u_int32_t au_rdcd(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 void au_wrcd(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); */ } 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 = vtophys(ch->buffer->buf); 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(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = AU_BUFFSIZE; if (chn_allocbuf(ch->buffer, au->parent_dmat) == -1) return NULL; return ch; } static int auchan_setdir(void *data, int dir) { struct au_chinfo *ch = data; if (dir == PCMDIR_PLAY) { } else { } ch->dir = dir; return 0; } static int auchan_setformat(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(void *data, u_int32_t speed) { struct au_chinfo *ch = data; if (ch->dir == PCMDIR_PLAY) { } else { } return speed; } static int auchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int auchan_trigger(void *data, int go) { struct au_chinfo *ch = data; struct au_info *au = ch->parent; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; if (ch->dir == PCMDIR_PLAY) { au_setadb(au, 0x11, (go)? 1 : 0); if (!go) { 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(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 pcmchan_caps * auchan_getcaps(void *data) { struct au_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &au_playcaps : &au_reccaps; } /* 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 0; } return ENXIO; } static int au_pci_attach(device_t dev) { - snddev_info *d; 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]; d = device_get_softc(dev); if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(au, sizeof(*au)); 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_MAPS + i*4; type[j] = SYS_RES_MEMORY; reg[j] = bus_alloc_resource(dev, type[j], ®id[j], 0, ~0, 1, RF_ACTIVE); if (!reg[j]) { type[j] = SYS_RES_IOPORT; reg[j] = bus_alloc_resource(dev, type[j], ®id[j], 0, ~0, 1, 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(dev, SYS_RES_IRQ, &irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!irq || bus_setup_intr(dev, irq, INTR_TYPE_TTY, 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, NULL, au_rdcd, au_wrcd); if (codec == NULL) goto bad; - if (mixer_init(d, &ac97_mixer, codec) == -1) goto bad; + if (mixer_init(dev, &ac97_mixer, codec) == -1) goto bad; if (bus_dma_tag_create(/*parent*/NULL, /*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, &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", (type[0] == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(reg[0]), rman_get_start(irq)); if (pcm_register(dev, au, 1, 1)) goto bad; /* pcm_addchan(dev, PCMDIR_REC, &au_chantemplate, au); */ pcm_addchan(dev, PCMDIR_PLAY, &au_chantemplate, 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, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_aureal, pci, au_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_aureal, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_aureal, 1); Index: head/sys/dev/sound/pci/csapcm.c =================================================================== --- head/sys/dev/sound/pci/csapcm.c (revision 65339) +++ head/sys/dev/sound/pci/csapcm.c (revision 65340) @@ -1,888 +1,894 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include /* device private data */ struct csa_info; struct csa_chinfo { struct csa_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; u_int32_t fmt; 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. */ /* Contents of board's registers */ u_long pfie; u_long pctl; u_long cctl; struct csa_chinfo pch, rch; }; /* -------------------------------------------------------------------- */ /* 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 void csa_powerupadc(csa_res *resp); static void csa_powerupdac(csa_res *resp); static int csa_startdsp(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); /* talk to the codec - called from ac97.c */ static u_int32_t csa_rdcd(void *, int); static void csa_wrcd(void *, int, u_int32_t); /* channel interface */ static void *csachan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int csachan_setdir(void *data, int dir); static int csachan_setformat(void *data, u_int32_t format); static int csachan_setspeed(void *data, u_int32_t speed); static int csachan_setblocksize(void *data, u_int32_t blocksize); static int csachan_trigger(void *data, int go); static int csachan_getptr(void *data); static pcmchan_caps *csachan_getcaps(void *data); 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 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 pcmchan_caps csa_reccaps = {11025, 48000, csa_recfmt, 0}; static pcm_channel csa_chantemplate = { csachan_init, csachan_setdir, csachan_setformat, csachan_setspeed, csachan_setblocksize, csachan_trigger, csachan_getptr, csachan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* -------------------------------------------------------------------- */ /* channel interface */ static void * csachan_init(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = CS461x_BUFFSIZE; if (chn_allocbuf(ch->buffer, csa->parent_dmat) == -1) return NULL; return ch; } static int csachan_setdir(void *data, int dir) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; csa_res *resp; resp = &csa->res; if (dir == PCMDIR_PLAY) csa_writemem(resp, BA1_PBA, vtophys(ch->buffer->buf)); else csa_writemem(resp, BA1_CBA, vtophys(ch->buffer->buf)); ch->dir = dir; return 0; } static int csachan_setformat(void *data, u_int32_t format) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; u_long pdtc; csa_res *resp; resp = &csa->res; if (ch->dir == PCMDIR_REC) csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); else { csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f; if (format & AFMT_U8 || format & AFMT_U16_LE || format & AFMT_U16_BE) csa->pfie |= 0x8000; if (format & AFMT_S16_BE || format & AFMT_U16_BE) csa->pfie |= 0x4000; if (!(format & AFMT_STEREO)) csa->pfie |= 0x2000; if (format & AFMT_U8 || format & AFMT_S8) csa->pfie |= 0x1000; csa_writemem(resp, BA1_PFIE, csa->pfie); pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000003ff; if ((format & AFMT_S16_BE || format & AFMT_U16_BE || format & AFMT_S16_LE || format & AFMT_U16_LE) && (format & AFMT_STEREO)) pdtc |= 0x00f; else if ((format & AFMT_S16_BE || format & AFMT_U16_BE || format & AFMT_S16_LE || format & AFMT_U16_LE) || (format & AFMT_STEREO)) pdtc |= 0x007; else pdtc |= 0x003; csa_writemem(resp, BA1_PDTC, pdtc); } ch->fmt = format; return 0; } static int csachan_setspeed(void *data, u_int32_t speed) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; csa_res *resp; resp = &csa->res; if (ch->dir == PCMDIR_PLAY) csa_setplaysamplerate(resp, speed); else if (ch->dir == PCMDIR_REC) csa_setcapturesamplerate(resp, speed); /* rec/play speeds locked together - should indicate in flags */ #if 0 if (ch->direction == PCMDIR_PLAY) d->rec[0].speed = speed; else d->play[0].speed = speed; #endif return speed; /* XXX calc real speed */ } 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 int csachan_setblocksize(void *data, u_int32_t blocksize) { #if notdef return blocksize; #else struct csa_chinfo *ch = data; return ch->buffer->bufsize / 2; #endif /* notdef */ } static int csachan_trigger(void *data, int go) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) csa_startplaydma(csa); else csa_stopplaydma(csa); } else { if (go == PCMTRIG_START) csa_startcapturedma(csa); else csa_stopcapturedma(csa); } return 0; } 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 void csa_powerupdac(csa_res *resp) { int i; u_long ul; /* * Power on the DACs on the AC97 codec. We turn off the DAC * powerdown bit and write the new value of the power control * register. */ ul = csa_readio(resp, BA0_AC97_POWERDOWN); ul &= 0xfdff; csa_writeio(resp, BA0_AC97_POWERDOWN, ul); /* * Now, we wait until we sample a DAC ready state. */ for (i = 0 ; i < 32 ; i++) { /* * First, lets wait a short while to let things settle out a * bit, and to prevent retrying the read too quickly. */ DELAY(125); /* * Read the current state of the power control register. */ ul = csa_readio(resp, BA0_AC97_POWERDOWN); /* * If the DAC ready state bit is set, then stop waiting. */ if ((ul & 0x2) != 0) break; } /* * The DACs are now calibrated, so we can unmute the DAC output. */ csa_writeio(resp, BA0_AC97_PCM_OUT_VOLUME, 0x0808); } static void csa_powerupadc(csa_res *resp) { int i; u_long ul; /* * Power on the ADCs on the AC97 codec. We turn off the ADC * powerdown bit and write the new value of the power control * register. */ ul = csa_readio(resp, BA0_AC97_POWERDOWN); ul &= 0xfeff; csa_writeio(resp, BA0_AC97_POWERDOWN, ul); /* * Now, we wait until we sample a ADC ready state. */ for (i = 0 ; i < 32 ; i++) { /* * First, lets wait a short while to let things settle out a * bit, and to prevent retrying the read too quickly. */ DELAY(125); /* * Read the current state of the power control register. */ ul = csa_readio(resp, BA0_AC97_POWERDOWN); /* * If the ADC ready state bit is set, then stop waiting. */ if ((ul & 0x1) != 0) break; } } 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. */ #if notdef DELAY(1000); #else DELAY(125); #endif /* notdef */ /* * 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 csachan_getptr(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) - vtophys(ch->buffer->buf); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } else { ptr = csa_readmem(resp, BA1_CBA) - vtophys(ch->buffer->buf); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } return (ptr); } static pcmchan_caps * csachan_getcaps(void *data) { struct csa_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps; } /* 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); /* Crank up the power on the DAC and ADC. */ csa_powerupadc(resp); csa_powerupdac(resp); csa_setplaysamplerate(resp, 8000); csa_setcapturesamplerate(resp, 8000); if (csa_startdsp(resp)) return (1); 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(dev, SYS_RES_MEMORY, &resp->io_rid, 0, ~0, CS461x_IO_SIZE, RF_ACTIVE); if (resp->io == NULL) return (1); } if (resp->mem == NULL) { resp->mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &resp->mem_rid, 0, ~0, CS461x_MEM_SIZE, RF_ACTIVE); if (resp->mem == NULL) return (1); } if (resp->irq == NULL) { resp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &resp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (resp->irq == NULL) return (1); } if (bus_dma_tag_create(/*parent*/NULL, /*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, &csa->parent_dmat) != 0) return (1); return (0); } /* Releases resources. */ static void csa_releaseres(struct csa_info *csa, device_t dev) { csa_res *resp; resp = &csa->res; if (resp->irq != NULL) { 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; } } static int pcmcsa_probe(device_t dev); static int pcmcsa_attach(device_t dev); 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) { - snddev_info *devinfo; struct csa_info *csa; csa_res *resp; int unit; char status[SND_STATUSLEN]; struct ac97_info *codec; struct sndcard_func *func; - devinfo = device_get_softc(dev); csa = malloc(sizeof(*csa), M_DEVBUF, M_NOWAIT); if (csa == NULL) return (ENOMEM); bzero(csa, sizeof(*csa)); 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; /* Allocate the resources. */ resp = &csa->res; resp->io_rid = CS461x_IO_OFFSET; resp->mem_rid = CS461x_MEM_OFFSET; resp->irq_rid = 0; if (csa_allocres(csa, dev)) { csa_releaseres(csa, dev); return (ENXIO); } if (csa_init(csa)) { csa_releaseres(csa, dev); return (ENXIO); } codec = ac97_create(dev, csa, NULL, csa_rdcd, csa_wrcd); if (codec == NULL) return (ENXIO); - if (mixer_init(devinfo, &ac97_mixer, codec) == -1) + if (mixer_init(dev, &ac97_mixer, codec) == -1) return (ENXIO); snprintf(status, SND_STATUSLEN, "at irq %ld", rman_get_start(resp->irq)); /* Enable interrupt. */ if (bus_setup_intr(dev, resp->irq, INTR_TYPE_TTY, csa_intr, csa, &csa->ih)) { 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); if (pcm_register(dev, csa, 1, 1)) { csa_releaseres(csa, dev); return (ENXIO); } pcm_addchan(dev, PCMDIR_REC, &csa_chantemplate, csa); pcm_addchan(dev, PCMDIR_PLAY, &csa_chantemplate, csa); pcm_setstatus(dev, status); return (0); } /* ac97 codec */ static u_int32_t csa_rdcd(void *devinfo, int regno) { u_int32_t data; struct csa_info *csa = (struct csa_info *)devinfo; if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data)) data = 0; return data; } static void csa_wrcd(void *devinfo, int regno, u_int32_t data) { struct csa_info *csa = (struct csa_info *)devinfo; csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data); } static device_method_t pcmcsa_methods[] = { /* Device interface */ DEVMETHOD(device_probe , pcmcsa_probe ), DEVMETHOD(device_attach, pcmcsa_attach), { 0, 0 }, }; static driver_t pcmcsa_driver = { "pcm", pcmcsa_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_csapcm, csa, pcmcsa_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_csapcm, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_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 65339) +++ head/sys/dev/sound/pci/ds1.c (revision 65340) @@ -1,1028 +1,1084 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include #include /* -------------------------------------------------------------------- */ #define DS1_CHANS 4 #define DS1_RECPRIMARY 0 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; snd_dbuf *buffer; pcm_channel *channel; volatile struct pbank *lslot, *rslot; int lsnum, rsnum; struct sc_info *parent; }; struct sc_rchinfo { int run, spd, dir, fmt, num; snd_dbuf *buffer; 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 parent_dmat; + bus_dmamap_t map; struct resource *reg, *irq; int regid, irqid; void *ih; void *regbase; u_int32_t *pbase, pbankbase, pbanksize; volatile struct pbank *pbank[2 * 64]; volatile struct rbank *rbank; int pslotfree, currbank, pchn, rchn; 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[] = { /* Beware, things know the indexes here */ {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}, /*8*/ {0x000a1073, 0x53328086, "Yamaha DS-1 (YMF740I)", CntrlInst}, {0x000a1073, 0, "Yamaha DS-1 (YMF740?)", CntrlInst}, {0x000c1073, 0, "Yamaha DS-1E (YMF740C)", CntrlInst1E}, /*11*/ {0x00101073, 0, "Yamaha DS-1E (YMF744)", CntrlInst1E}, {0x00121073, 0, "Yamaha DS-1E (YMF754)", CntrlInst1E}, {0, 0, NULL, NULL} }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* channel interface */ static void *ds1pchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int ds1pchan_setdir(void *data, int dir); static int ds1pchan_setformat(void *data, u_int32_t format); static int ds1pchan_setspeed(void *data, u_int32_t speed); static int ds1pchan_setblocksize(void *data, u_int32_t blocksize); static int ds1pchan_trigger(void *data, int go); static int ds1pchan_getptr(void *data); static pcmchan_caps *ds1pchan_getcaps(void *data); static void *ds1rchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int ds1rchan_setdir(void *data, int dir); static int ds1rchan_setformat(void *data, u_int32_t format); static int ds1rchan_setspeed(void *data, u_int32_t speed); static int ds1rchan_setblocksize(void *data, u_int32_t blocksize); static int ds1rchan_trigger(void *data, int go); static int ds1rchan_getptr(void *data); static pcmchan_caps *ds1rchan_getcaps(void *data); /* talk to the codec - called from ac97.c */ static u_int32_t ds_rdcd(void *, int); static void ds_wrcd(void *, int, u_int32_t); /* 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 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 pcmchan_caps ds_playcaps = {4000, 96000, ds_playfmt, 0}; static pcm_channel ds_pchantemplate = { ds1pchan_init, ds1pchan_setdir, ds1pchan_setformat, ds1pchan_setspeed, ds1pchan_setblocksize, ds1pchan_trigger, ds1pchan_getptr, ds1pchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; static pcm_channel ds_rchantemplate = { ds1rchan_init, ds1rchan_setdir, ds1rchan_setformat, ds1rchan_setspeed, ds1rchan_setblocksize, ds1rchan_trigger, ds1rchan_getptr, ds1rchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* -------------------------------------------------------------------- */ /* 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(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); } static u_int32_t ds_rdcd(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 void ds_wrcd(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; cmd = YDSXG_AC97WRITECMD | cid | regno; cmd <<= 16; cmd |= data; ds_wr(sc, YDSXGR_AC97CMDDATA, cmd, 4); ds_cdbusy(sc, sec); } 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, void *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? vtophys(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; void *buf; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; b16 = (ch->fmt & AFMT_16BIT)? 1 : 0; c = stereo? 1 : 0; buf = ch->buffer->buf; sz = ch->buffer->bufsize; ds_initpbank(ch->lslot, c, stereo, b16, ch->spd, buf, sz); ds_initpbank(ch->lslot + 1, c, stereo, b16, ch->spd, buf, sz); ds_initpbank(ch->rslot, 2, stereo, b16, ch->spd, buf, sz); ds_initpbank(ch->rslot + 1, 2, stereo, b16, ch->spd, buf, 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; void *buf; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; b16 = (ch->fmt & AFMT_16BIT)? 1 : 0; buf = ch->buffer->buf; sz = ch->buffer->bufsize; pri = (ch->num == DS1_RECPRIMARY)? 1 : 0; for (i = 0; i < 2; i++) { ch->slot[i].PgBase = vtophys(buf); 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(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = 4096; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->fmt = AFMT_U8; ch->spd = 8000; ch->run = 0; if (chn_allocbuf(ch->buffer, sc->parent_dmat) == -1) 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_setdir(void *data, int dir) { return 0; } static int ds1pchan_setformat(void *data, u_int32_t format) { struct sc_pchinfo *ch = data; ch->fmt = format; return 0; } static int ds1pchan_setspeed(void *data, u_int32_t speed) { struct sc_pchinfo *ch = data; ch->spd = speed; return speed; } static int ds1pchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } /* semantic note: must start at beginning of buffer */ static int ds1pchan_trigger(void *data, int go) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int stereo; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) 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); ds_wr(sc, YDSXGR_MODE, 0x00000003, 4); } 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(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 pcmchan_caps * ds1pchan_getcaps(void *data) { return &ds_playcaps; } /* record channel interface */ static void * ds1rchan_init(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = 4096; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->fmt = AFMT_U8; ch->spd = 8000; if (chn_allocbuf(ch->buffer, sc->parent_dmat) == -1) return NULL; else { ch->slot = (ch->num == DS1_RECPRIMARY)? sc->rbank + 2: sc->rbank; ds_setuprch(ch); return ch; } } static int ds1rchan_setdir(void *data, int dir) { return 0; } static int ds1rchan_setformat(void *data, u_int32_t format) { struct sc_rchinfo *ch = data; ch->fmt = format; return 0; } static int ds1rchan_setspeed(void *data, u_int32_t speed) { struct sc_rchinfo *ch = data; ch->spd = speed; return speed; } static int ds1rchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } /* semantic note: must start at beginning of buffer */ static int ds1rchan_trigger(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) return 0; if (go == PCMTRIG_START) { ch->run = 1; ds_setuprch(ch); 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); } else { ch->run = 0; x = ds_rd(sc, YDSXGR_MAPOFREC, 4); x &= ~((ch->num == DS1_RECPRIMARY)? 0x02 : 0x01); ds_wr(sc, YDSXGR_MAPOFREC, x, 4); } return 0; } static int ds1rchan_getptr(void *data) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; return ch->slot[sc->currbank].PgStart; } static pcmchan_caps * ds1rchan_getcaps(void *data) { return &ds_reccaps; } /* The interrupt handler */ static void ds_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t i, x; 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; chn_intr(sc->pch[i].channel); } } for (i = 0; i < 2; i++) { if (sc->rch[i].run) { x = 1; chn_intr(sc->rch[i].channel); } } i = ds_rd(sc, YDSXGR_MODE, 4); if (x) ds_wr(sc, YDSXGR_MODE, i | 0x00000002, 4); } } /* -------------------------------------------------------------------- */ /* * 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; - bus_dmamap_t map; 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_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, &map)) + if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, &sc->map)) return -1; - if (bus_dmamap_load(sc->parent_dmat, map, buf, memsz, ds_setmap, sc, 0) + if (bus_dmamap_load(sc->parent_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->parent_dmat, sc->map); + bus_dmamem_free(sc->parent_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 0; } else return ENXIO; } static int ds_pci_attach(device_t dev) { - snddev_info *d; u_int32_t data; u_int32_t subdev, i; struct sc_info *sc; struct ac97_info *codec; char status[SND_STATUSLEN]; - d = device_get_softc(dev); if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); 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_MAPS; sc->reg = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->regid, 0, ~0, 1, 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); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/65536, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &sc->parent_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_initcd, ds_rdcd, ds_wrcd); if (codec == NULL) goto bad; - mixer_init(d, &ac97_mixer, codec); + mixer_init(dev, &ac97_mixer, codec); sc->irqid = 0; sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, 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", rman_get_start(sc->reg), rman_get_start(sc->irq)); if (pcm_register(dev, sc, DS1_CHANS, 2)) goto bad; for (i = 0; i < DS1_CHANS; i++) pcm_addchan(dev, PCMDIR_PLAY, &ds_pchantemplate, sc); for (i = 0; i < 2; i++) pcm_addchan(dev, PCMDIR_REC, &ds_rchantemplate, sc); pcm_setstatus(dev, status); return 0; bad: 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 ds_pci_resume(device_t dev) { - snddev_info *d; struct sc_info *sc; - d = device_get_softc(dev); sc = pcm_getdevinfo(dev); if (ds_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } - if (mixer_reinit(d) == -1) { + 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); + 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 0; +} + static device_method_t ds1_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ds_pci_probe), DEVMETHOD(device_attach, ds_pci_attach), - DEVMETHOD(device_resume, ds_pci_resume), + DEVMETHOD(device_detach, ds_pci_detach), + DEVMETHOD(device_resume, ds_pci_resume), { 0, 0 } }; static driver_t ds1_driver = { "pcm", ds1_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_ds1, pci, ds1_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_ds1, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_ds1, 1); Index: head/sys/dev/sound/pci/emu10k1.c =================================================================== --- head/sys/dev/sound/pci/emu10k1.c (revision 65339) +++ head/sys/dev/sound/pci/emu10k1.c (revision 65340) @@ -1,1501 +1,1521 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include #include /* -------------------------------------------------------------------- */ #define EMU10K1_PCI_ID 0x00021102 #define EMU_BUFFSIZE 4096 #define EMU_CHANS 4 #undef EMUDEBUG struct emu_memblk { SLIST_ENTRY(emu_memblk) link; void *buf; u_int32_t pte_start, pte_size; }; struct emu_mem { u_int8_t bmap[MAXPAGES / 8]; u_int32_t *ptb_pages; void *silent_page; SLIST_HEAD(, emu_memblk) blocks; }; struct emu_voice { int vnum; int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start, end, vol; u_int32_t buf; struct emu_voice *slave; pcm_channel *channel; }; struct sc_info; /* channel registers */ struct sc_pchinfo { int spd, fmt, run; struct emu_voice *master, *slave; snd_dbuf *buffer; pcm_channel *channel; struct sc_info *parent; }; struct sc_rchinfo { int spd, fmt, run, num; u_int32_t idxreg, basereg, sizereg, setupreg, irqmask; snd_dbuf *buffer; 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; 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; int timer; int pnum, rnum; struct emu_mem mem; struct emu_voice voice[64]; struct sc_pchinfo pch[EMU_CHANS]; struct sc_rchinfo rch[3]; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* channel interface */ static void *emupchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int emupchan_free(void *data); static int emupchan_setdir(void *data, int dir); static int emupchan_setformat(void *data, u_int32_t format); static int emupchan_setspeed(void *data, u_int32_t speed); static int emupchan_setblocksize(void *data, u_int32_t blocksize); static int emupchan_trigger(void *data, int go); static int emupchan_getptr(void *data); static pcmchan_caps *emupchan_getcaps(void *data); /* channel interface */ static void *emurchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int emurchan_setdir(void *data, int dir); static int emurchan_setformat(void *data, u_int32_t format); static int emurchan_setspeed(void *data, u_int32_t speed); static int emurchan_setblocksize(void *data, u_int32_t blocksize); static int emurchan_trigger(void *data, int go); static int emurchan_getptr(void *data); static pcmchan_caps *emurchan_getcaps(void *data); /* talk to the codec - called from ac97.c */ static u_int32_t emu_rdcd(void *, int); static void emu_wrcd(void *, int, u_int32_t); /* 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); static void *emu_memalloc(struct sc_info *sc, u_int32_t sz); -#ifdef notyet static int emu_memfree(struct sc_info *sc, void *buf); -#endif 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 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 pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static pcm_channel emu_chantemplate = { emupchan_init, emupchan_setdir, emupchan_setformat, emupchan_setspeed, emupchan_setblocksize, emupchan_trigger, emupchan_getptr, emupchan_getcaps, + emupchan_free, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; static pcm_channel emur_chantemplate = { emurchan_init, emurchan_setdir, emurchan_setformat, emurchan_setspeed, emurchan_setblocksize, emurchan_trigger, emurchan_getptr, emurchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; static int adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 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) & PTR_ADDRESS_MASK) | (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) & PTR_ADDRESS_MASK) | (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) { emu_wrptr(sc, 0, MICROCODEBASE + pc, data); } /* ac97 codec */ static u_int32_t emu_rdcd(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 void emu_wrcd(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); } #if 0 /* playback channel interrupts */ static u_int32_t emu_testint(struct sc_info *sc, char channel) { int reg = (channel & 0x20)? CLIPH : CLIPL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; return emu_rdptr(sc, 0, reg); } static void emu_clrint(struct sc_info *sc, char channel) { int reg = (channel & 0x20)? CLIPH : CLIPL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; emu_wrptr(sc, 0, reg, 1); } static void emu_enaint(struct sc_info *sc, char channel, int enable) { int reg = (channel & 0x20)? CLIEH : CLIEL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; emu_wrptr(sc, 0, reg, enable); } #endif /* stuff */ static int emu_enatimer(struct sc_info *sc, int go) { u_int32_t x; if (go) { if (sc->timer++ == 0) { emu_wr(sc, TIMER, 256, 2); 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 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, pcm_channel *c) { void *buf; buf = emu_memalloc(sc, sz); if (buf == NULL) return -1; if (c != NULL) { c->buffer.buf = buf; c->buffer.bufsize = 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 = vtophys(buf); m->slave = s; 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->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; emu_wrptr(sc, v->vnum, FXRT, 0xd01c0000); 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)vtophys(sc->mem.silent_page) << 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" }; 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; } printf("\n\n"); } #endif /* channel interface */ void * emupchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); ch = &sc->pch[sc->pnum++]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->master = emu_valloc(sc); ch->slave = emu_valloc(sc); if (emu_vinit(sc, ch->master, ch->slave, EMU_BUFFSIZE, ch->channel)) return NULL; else return ch; } static int +emupchan_free(void *data) +{ + struct sc_pchinfo *ch = data; + struct sc_info *sc = ch->parent; + + return emu_memfree(sc, ch->buffer->buf); +} + +static int emupchan_setdir(void *data, int dir) { return 0; } static int emupchan_setformat(void *data, u_int32_t format) { struct sc_pchinfo *ch = data; ch->fmt = format; return 0; } static int emupchan_setspeed(void *data, u_int32_t speed) { struct sc_pchinfo *ch = data; ch->spd = speed; return ch->spd; } static int emupchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int emupchan_trigger(void *data, int go) { struct sc_pchinfo *ch = data; - struct sc_info *sc = ch->parent; + struct sc_info *sc = ch->parent; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; if (go == PCMTRIG_START) { emu_vsetup(ch); emu_vwrite(sc, ch->master); 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); return 0; } static int emupchan_getptr(void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; return emu_vpos(sc, ch->master); } static pcmchan_caps * emupchan_getcaps(void *data) { return &emu_playcaps; } /* channel interface */ static void * emurchan_init(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = EMU_BUFFSIZE; ch->parent = sc; ch->channel = c; ch->fmt = AFMT_U8; ch->spd = 8000; ch->num = sc->rnum; switch(sc->rnum) { case 0: ch->idxreg = ADCIDX; ch->basereg = ADCBA; ch->sizereg = ADCBS; ch->setupreg = ADCCR; ch->irqmask = INTE_ADCBUFENABLE; break; case 1: ch->idxreg = MICIDX; ch->basereg = MICBA; ch->sizereg = MICBS; ch->setupreg = 0; ch->irqmask = INTE_MICBUFENABLE; break; case 2: ch->idxreg = FXIDX; ch->basereg = FXBA; ch->sizereg = FXBS; ch->setupreg = FXWC; ch->irqmask = INTE_EFXBUFENABLE; break; } sc->rnum++; if (chn_allocbuf(ch->buffer, sc->parent_dmat) == -1) return NULL; else { emu_wrptr(sc, 0, ch->basereg, vtophys(ch->buffer->buf)); emu_wrptr(sc, 0, ch->sizereg, 0); /* off */ return ch; } } static int emurchan_setdir(void *data, int dir) { return 0; } static int emurchan_setformat(void *data, u_int32_t format) { struct sc_rchinfo *ch = data; ch->fmt = format; return 0; } static int emurchan_setspeed(void *data, u_int32_t speed) { struct sc_rchinfo *ch = data; if (ch->num == 0) speed = adcspeed[emu_recval(speed)]; if (ch->num == 1) speed = 8000; if (ch->num == 2) speed = 48000; ch->spd = speed; return ch->spd; } static int emurchan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } /* semantic note: must start at beginning of buffer */ static int emurchan_trigger(void *data, int go) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t val; switch(go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc, 0, ch->sizereg, ADCBS_BUFSIZE_4096); if (ch->num == 0) { val = ADCCR_LCHANENABLE; if (ch->fmt & AFMT_STEREO) val |= ADCCR_RCHANENABLE; val |= emu_recval(ch->spd); 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; } return 0; } static int emurchan_getptr(void *data) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; return emu_rdptr(sc, 0, ch->idxreg) & 0x0000ffff; } static pcmchan_caps * emurchan_getcaps(void *data) { struct sc_rchinfo *ch = data; return &emu_reccaps[ch->num]; } /* The interrupt handler */ static void emu_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t stat, ack, i, x; while (1) { stat = emu_rd(sc, IPR, 4); if (stat == 0) break; ack = 0; /* process irq */ if (stat & IPR_INTERVALTIMER) { ack |= IPR_INTERVALTIMER; x = 0; for (i = 0; i < EMU_CHANS; i++) { if (sc->pch[i].run) { x = 1; chn_intr(sc->pch[i].channel); } } if (x == 0) emu_enatimer(sc, 0); } if (stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) { ack |= stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL); if (sc->rch[0].channel) chn_intr(sc->rch[0].channel); } if (stat & (IPR_MICBUFFULL | IPR_MICBUFHALFFULL)) { ack |= stat & (IPR_MICBUFFULL | IPR_MICBUFHALFFULL); if (sc->rch[1].channel) chn_intr(sc->rch[1].channel); } if (stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL)) { ack |= stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL); if (sc->rch[2].channel) chn_intr(sc->rch[2].channel); } 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; device_printf(sc->dev, "sample rate tracker lock status change\n"); } if (stat & ~ack) device_printf(sc->dev, "dodgy irq: %x (harmless)\n", stat & ~ack); emu_wr(sc, IPR, stat, 4); } } /* -------------------------------------------------------------------- */ static void emu_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { void **phys = arg; *phys = error? 0 : (void *)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) { void *buf, *phys = 0; bus_dmamap_t map; 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, &phys, 0) || !phys) 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) { 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 < MAXPAGES) { 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); if (buf == NULL) { free(blk, M_DEVBUF); return NULL; } blk->buf = buf; blk->pte_start = start; blk->pte_size = blksz; /* printf("buf %p, pte_start %d, pte_size %d\n", blk->buf, blk->pte_start, blk->pte_size); */ ofs = 0; for (idx = start; idx < start + blksz; idx++) { mem->bmap[idx >> 3] |= 1 << (idx & 7); tmp = (u_int32_t)vtophys((u_int8_t *)buf + ofs); /* printf("pte[%d] -> %x phys, %x virt\n", idx, tmp, ((u_int32_t)buf) + ofs); */ mem->ptb_pages[idx] = (tmp << 1) | idx; ofs += EMUPAGESIZE; } SLIST_INSERT_HEAD(&mem->blocks, blk, link); return buf; } -#ifdef notyet 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)vtophys(sc->mem.silent_page) << 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; } -#endif 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 emu_initefx(struct sc_info *sc) { int i; u_int32_t pc = 16; 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 : APS S/PDIF?? (l/r) Output 0x020/0x021 : AC97 Output (l/r) 0x022/0x023 : TOS link out (l/r) 0x024/0x025 : ??? (l/r) 0x026/0x027 : LiveDrive Headphone (l/r) 0x028/0x029 : Rear Channel (l/r) 0x02a/0x02b : ADC Recording Buffer (l/r) 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 Temporary Values 0x056 : Accumulator 0x058 : Noise source? 0x059 : Noise source? General Purpose Registers 0x100 - 0x1ff Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff */ /* Operators: 0 : z := w + (x * y >> 31) 4 : z := w + x * y 6 : z := w + x + y */ /* Routing - this will be configurable in later version */ /* GPR[0/1] = FX * 4 + SPDIF-in */ emu_addefxop(sc, 4, 0x100, 0x12, 0, 0x44, &pc); emu_addefxop(sc, 4, 0x101, 0x13, 1, 0x44, &pc); /* GPR[0/1] += APS-input */ emu_addefxop(sc, 6, 0x100, 0x100, 0x40, sc->APS ? 0x16 : 0x40, &pc); emu_addefxop(sc, 6, 0x101, 0x101, 0x40, sc->APS ? 0x17 : 0x40, &pc); /* FrontOut (AC97) = GPR[0/1] */ emu_addefxop(sc, 6, 0x20, 0x40, 0x40, 0x100, &pc); emu_addefxop(sc, 6, 0x21, 0x40, 0x41, 0x101, &pc); /* RearOut = (GPR[0/1] * RearVolume) >> 31 */ /* RearVolume = GRP[0x10/0x11] */ emu_addefxop(sc, 0, 0x28, 0x40, 0x110, 0x100, &pc); emu_addefxop(sc, 0, 0x29, 0x40, 0x111, 0x101, &pc); /* TOS out = GPR[0/1] */ emu_addefxop(sc, 6, 0x22, 0x40, 0x40, 0x100, &pc); emu_addefxop(sc, 6, 0x23, 0x40, 0x40, 0x101, &pc); /* Mute Out2 */ emu_addefxop(sc, 6, 0x24, 0x40, 0x40, 0x40, &pc); emu_addefxop(sc, 6, 0x25, 0x40, 0x40, 0x40, &pc); /* Mute Out3 */ emu_addefxop(sc, 6, 0x26, 0x40, 0x40, 0x40, &pc); emu_addefxop(sc, 6, 0x27, 0x40, 0x40, 0x40, &pc); /* Input0 (AC97) -> Record */ emu_addefxop(sc, 6, 0x2a, 0x40, 0x40, 0x10, &pc); emu_addefxop(sc, 6, 0x2b, 0x40, 0x40, 0x11, &pc); 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; /* disable audio and lock cache */ emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE | 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); /* 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); 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); emu_initefx(sc); SLIST_INIT(&sc->mem.blocks); sc->mem.ptb_pages = emu_malloc(sc, MAXPAGES * sizeof(u_int32_t)); if (sc->mem.ptb_pages == NULL) return -1; sc->mem.silent_page = emu_malloc(sc, EMUPAGESIZE); 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)vtophys(sc->mem.silent_page) << 1; for (i = 0; i < MAXPAGES; i++) sc->mem.ptb_pages[i] = tmp | i; emu_wrptr(sc, 0, PTB, vtophys(sc->mem.ptb_pages)); 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 * Enable Audio = 1 * Mute Disable Audio = 0 * Lock Tank Memory = 1 * Lock Sound Memory = 0 * Auto Mute = 1 */ tmp = HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE | 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 | 0x800, 4); DELAY(50); if (tmp != (emu_rd(sc, HCFG, 4) & ~0x800)) { sc->tos_link = 1; emu_wr(sc, HCFG, tmp, 4); } } 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; } if (s) device_set_desc(dev, s); return s? 0 : ENXIO; } static int emu_pci_attach(device_t dev) { - snddev_info *d; u_int32_t data; struct sc_info *sc; struct ac97_info *codec; int i, mapped; char status[SND_STATUSLEN]; - d = device_get_softc(dev); if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); sc->dev = dev; sc->type = pci_get_devid(dev); 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); mapped = 0; for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { sc->regid = PCIR_MAPS + i*4; sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, 0, ~0, 1, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, 0, ~0, 1, RF_ACTIVE); } if (sc->reg) { sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); mapped++; } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto bad; } if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/1 << 31, /* can only access 0-2gb */ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/262144, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &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, NULL, emu_rdcd, emu_wrcd); if (codec == NULL) goto bad; - if (mixer_init(d, &ac97_mixer, codec) == -1) goto bad; + if (mixer_init(dev, &ac97_mixer, codec) == -1) goto bad; sc->irqid = 0; sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, emu_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", (sc->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(sc->reg), rman_get_start(sc->irq)); if (pcm_register(dev, sc, EMU_CHANS, 3)) goto bad; for (i = 0; i < EMU_CHANS; i++) pcm_addchan(dev, PCMDIR_PLAY, &emu_chantemplate, sc); for (i = 0; i < 3; i++) pcm_addchan(dev, PCMDIR_REC, &emur_chantemplate, sc); pcm_setstatus(dev, status); return 0; bad: if (sc->reg) bus_release_resource(dev, sc->regtype, 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; } /* add suspend, resume, unload */ static device_method_t emu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emu_pci_probe), DEVMETHOD(device_attach, emu_pci_attach), { 0, 0 } }; static driver_t emu_driver = { "pcm", emu_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_emu10k1, pci, emu_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_emu10k1, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_emu10k1, 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; } if (s) device_set_desc(dev, s); return s? 0 : ENXIO; } static int emujoy_pci_attach(device_t dev) { return 0; } static device_method_t emujoy_methods[] = { DEVMETHOD(device_probe, emujoy_pci_probe), DEVMETHOD(device_attach, emujoy_pci_attach), { 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/es137x.c =================================================================== --- head/sys/dev/sound/pci/es137x.c (revision 65339) +++ head/sys/dev/sound/pci/es137x.c (revision 65340) @@ -1,878 +1,893 @@ /* * 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. * * $FreeBSD$ */ /* * 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 static int debug = 0; SYSCTL_INT(_debug, OID_AUTO, es_debug, CTLFLAG_RW, &debug, 0, ""); #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 ES1371_PCI_ID3 0x58801274 #define ES_BUFFSIZE 4096 /* device private data */ struct es_info; struct es_chinfo { struct es_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir, num; u_int32_t fmt; }; struct es_info { bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; device_t dev; int num; /* Contents of board's registers */ u_long ctrl; u_long sctrl; struct es_chinfo pch, rch; }; /* -------------------------------------------------------------------- */ /* prototypes */ static void es_intr(void *); static void es1371_wrcodec(void *, int, u_int32_t); static u_int32_t es1371_rdcodec(void *, int); static u_int es1371_wait_src_ready(struct es_info *); static void es1371_src_write(struct es_info *, u_short, unsigned short); static u_int es1371_adc_rate(struct es_info *, u_int, int); static u_int es1371_dac_rate(struct es_info *, u_int, int); static int es1371_init(struct es_info *es, int); static int es1370_init(struct es_info *); static int es1370_wrcodec(struct es_info *, u_char, u_char); /* channel interface */ static void *eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int eschan_setdir(void *data, int dir); static int eschan_setformat(void *data, u_int32_t format); static int eschan1370_setspeed(void *data, u_int32_t speed); static int eschan1371_setspeed(void *data, u_int32_t speed); static int eschan_setblocksize(void *data, u_int32_t blocksize); static int eschan_trigger(void *data, int go); static int eschan_getptr(void *data); static pcmchan_caps *eschan_getcaps(void *data); static u_int32_t es_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps es_playcaps = {4000, 48000, es_playfmt, 0}; static u_int32_t es_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps es_reccaps = {4000, 48000, es_recfmt, 0}; static pcm_channel es1370_chantemplate = { eschan_init, eschan_setdir, eschan_setformat, eschan1370_setspeed, eschan_setblocksize, eschan_trigger, eschan_getptr, eschan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; static pcm_channel es1371_chantemplate = { eschan_init, eschan_setdir, eschan_setformat, eschan1371_setspeed, eschan_setblocksize, eschan_trigger, eschan_getptr, eschan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* -------------------------------------------------------------------- */ /* The es1370 mixer interface */ static int es1370_mixinit(snd_mixer *m); static int es1370_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int es1370_mixsetrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer es1370_mixer = { "AudioPCI 1370 mixer", es1370_mixinit, + NULL, es1370_mixset, es1370_mixsetrecsrc, }; 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, 0x0000, 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 int es1370_mixinit(snd_mixer *m) { int i; u_int32_t v; v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if (mixtable[i].avail) v |= (1 << i); mix_setdevs(m, v); v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if (mixtable[i].recmask) v |= (1 << i); mix_setrecdevs(m, v); return 0; } static int es1370_mixset(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { int l, r, rl, rr; 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; } if (mixtable[dev].stereo) { rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; es1370_wrcodec(mix_getdevinfo(m), mixtable[dev].right, rr); } es1370_wrcodec(mix_getdevinfo(m), mixtable[dev].left, rl); return l | (r << 8); } static int es1370_mixsetrecsrc(snd_mixer *m, u_int32_t src) { int i, j = 0; 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; es1370_wrcodec(mix_getdevinfo(m), CODEC_LIMIX1, j & 0x55); es1370_wrcodec(mix_getdevinfo(m), CODEC_RIMIX1, j & 0xaa); es1370_wrcodec(mix_getdevinfo(m), CODEC_LIMIX2, (j >> 8) & 0x17); es1370_wrcodec(mix_getdevinfo(m), CODEC_RIMIX2, (j >> 8) & 0x0f); es1370_wrcodec(mix_getdevinfo(m), CODEC_OMIX1, 0x7f); es1370_wrcodec(mix_getdevinfo(m), CODEC_OMIX2, 0x3f); return src; } static int es1370_wrcodec(struct es_info *es, u_char i, u_char data) { int wait = 100; /* 100 msec timeout */ do { if ((bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS) & STAT_CSTAT) == 0) { bus_space_write_2(es->st, es->sh, ES1370_REG_CODEC, ((u_short)i << CODEC_INDEX_SHIFT) | data); return 0; } DELAY(1000); } while (--wait); printf("pcm: es1370_wrcodec timed out\n"); return -1; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * eschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct es_info *es = devinfo; struct es_chinfo *ch = (dir == PCMDIR_PLAY)? &es->pch : &es->rch; ch->parent = es; ch->channel = c; ch->buffer = b; ch->buffer->bufsize = ES_BUFFSIZE; ch->num = ch->parent->num++; if (chn_allocbuf(ch->buffer, es->parent_dmat) == -1) return NULL; return ch; } static int eschan_setdir(void *data, int dir) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; if (dir == PCMDIR_PLAY) { bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8); bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMEADR & 0xff, vtophys(ch->buffer->buf)); bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->buffer->bufsize >> 2) - 1); } else { bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8); bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMEADR & 0xff, vtophys(ch->buffer->buf)); bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->buffer->bufsize >> 2) - 1); } ch->dir = dir; return 0; } static int eschan_setformat(void *data, u_int32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; if (ch->dir == PCMDIR_PLAY) { 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; } bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); ch->fmt = format; return 0; } static int eschan1370_setspeed(void *data, u_int32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; es->ctrl &= ~CTRL_PCLKDIV; es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); /* rec/play speeds locked together - should indicate in flags */ return speed; /* XXX calc real speed */ } int eschan1371_setspeed(void *data, u_int32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; if (ch->dir == PCMDIR_PLAY) { return es1371_dac_rate(es, speed, 3 - ch->num); /* play */ } else { return es1371_adc_rate(es, speed, 1); /* record */ } } static int eschan_setblocksize(void *data, u_int32_t blocksize) { return blocksize; } static int eschan_trigger(void *data, int go) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; unsigned ss, cnt; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; ss = 1; ss <<= (ch->fmt & AFMT_STEREO)? 1 : 0; ss <<= (ch->fmt & AFMT_16BIT)? 1 : 0; cnt = ch->buffer->dl / ss - 1; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { int b = (ch->fmt & AFMT_S16_LE)? 2 : 1; es->ctrl |= CTRL_DAC2_EN; es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN); es->sctrl |= SCTRL_P2INTEN | (b << SCTRL_SH_P2ENDINC); bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_SCOUNT, cnt); /* start at beginning of buffer */ bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8); bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->buffer->bufsize >> 2) - 1); } else es->ctrl &= ~CTRL_DAC2_EN; } else { if (go == PCMTRIG_START) { es->ctrl |= CTRL_ADC_EN; es->sctrl &= ~SCTRL_R1LOOPSEL; es->sctrl |= SCTRL_R1INTEN; bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_SCOUNT, cnt); /* start at beginning of buffer */ bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8); bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->buffer->bufsize >> 2) - 1); } else es->ctrl &= ~CTRL_ADC_EN; } bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); return 0; } static int eschan_getptr(void *data) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; u_int32_t reg, cnt; if (ch->dir == PCMDIR_PLAY) reg = ES1370_REG_DAC2_FRAMECNT; else reg = ES1370_REG_ADC_FRAMECNT; bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, reg >> 8); cnt = bus_space_read_4(es->st, es->sh, reg & 0x000000ff) >> 16; /* cnt is longwords */ return cnt << 2; } static pcmchan_caps * eschan_getcaps(void *data) { struct es_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &es_playcaps : &es_reccaps; } /* The interrupt handler */ static void es_intr(void *p) { struct es_info *es = p; unsigned intsrc, sctrl; intsrc = bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS); if ((intsrc & STAT_INTR) == 0) 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; bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, sctrl); bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); if (intsrc & STAT_ADC) chn_intr(es->rch.channel); if (intsrc & STAT_DAC1); if (intsrc & STAT_DAC2) chn_intr(es->pch.channel); } /* ES1370 specific */ static int es1370_init(struct es_info *es) { es->ctrl = CTRL_CDC_EN | CTRL_SERR_DIS | (DAC2_SRTODIV(DSP_DEFAULT_SPEED) << CTRL_SH_PCLKDIV); bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); es->sctrl = 0; bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); es1370_wrcodec(es, CODEC_RES_PD, 3);/* No RST, PD */ es1370_wrcodec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use * {LR,B}CLK2 and run off the LRCLK2 * PLL; program DAC_SYNC=0! */ es1370_wrcodec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ es1370_wrcodec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ return 0; } /* ES1371 specific */ int es1371_init(struct es_info *es, int rev) { int idx; if (debug > 0) printf("es_init\n"); es->num = 0; es->ctrl = 0; es->sctrl = 0; /* initialize the chips */ if (rev == 7 || rev >= 9 || rev == 2) { #define ES1371_BINTSUMM_OFF 0x07 bus_space_write_4(es->st, es->sh, ES1371_BINTSUMM_OFF, 0x20); if (debug > 0) printf("es_init rev == 7 || rev >= 9\n"); } else { /* pre ac97 2.1 card */ bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); if (debug > 0) printf("es_init pre ac97 2.1\n"); } bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); bus_space_write_4(es->st, es->sh, ES1371_REG_LEGACY, 0); /* AC'97 warm reset to start the bitclk */ bus_space_write_4(es->st, es->sh, ES1371_REG_LEGACY, es->ctrl | ES1371_SYNC_RES); DELAY(2000); bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->ctrl); /* Init the sample rate converter */ bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, ES1371_DIS_SRC); 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, 1); es1371_dac_rate (es, 22050, 1); es1371_dac_rate (es, 22050, 2); /* 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) */ bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, 0); return (0); } static void es1371_wrcodec(void *s, int addr, u_int32_t data) { int sl; unsigned t, x; struct es_info *es = (struct es_info*)s; if (debug > 0) printf("wrcodec addr 0x%x data 0x%x\n", addr, data); for (t = 0; t < 0x1000; t++) if (!(bus_space_read_4(es->st, es->sh,(ES1371_REG_CODEC & CODEC_WIP)))) break; sl = spltty(); /* save the current state for later */ x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE); /* enable SRC state data in SRC mux */ bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, (es1371_wait_src_ready(s) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1))); /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) if ((bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE) & 0x00070000) == 0x00010000) break; if (debug > 2) printf("one b_s_w: 0x%x 0x%x 0x%x\n", es->sh, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK)); bus_space_write_4(es->st, es->sh,ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK)); /* restore SRC reg */ es1371_wait_src_ready(s); if (debug > 2) printf("two b_s_w: 0x%x 0x%x 0x%x\n", es->sh, ES1371_REG_SMPRATE, x); bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, x); splx(sl); } static u_int32_t es1371_rdcodec(void *s, int addr) { int sl; unsigned t, x; struct es_info *es = (struct es_info *)s; if (debug > 0) printf("rdcodec addr 0x%x ... ", addr); for (t = 0; t < 0x1000; t++) if (!(x = bus_space_read_4(es->st, es->sh, ES1371_REG_CODEC) & CODEC_WIP)) break; if (debug > 0) printf("loop 1 t 0x%x x 0x%x ", t, x); sl = spltty(); /* save the current state for later */ x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE); /* enable SRC state data in SRC mux */ bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, (es1371_wait_src_ready(s) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1))); /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x5000; t++) if ((x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE) & 0x00070000) == 0x00010000) break; if (debug > 0) printf("loop 2 t 0x%x x 0x%x ", t, x); bus_space_write_4(es->st, es->sh, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD); /* restore SRC reg */ es1371_wait_src_ready(s); bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, x); splx(sl); /* now wait for the stinkin' data (RDY) */ for (t = 0; t < 0x1000; t++) if ((x = bus_space_read_4(es->st, es->sh, ES1371_REG_CODEC)) & CODEC_RDY) break; if (debug > 0) printf("loop 3 t 0x%x 0x%x ret 0x%x\n", t, x, ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT)); return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static u_int es1371_src_read(struct es_info *es, u_short reg) { unsigned int 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); bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE,r); return ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es)); } static void es1371_src_write(struct es_info *es, u_short reg, u_short data){ u_int 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); /* printf("es1371_src_write 0x%x 0x%x\n",ES1371_REG_SMPRATE,r | ES1371_SRC_RAM_WE); */ bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE); } static u_int es1371_adc_rate(struct es_info *es, u_int rate, int set) { u_int n, truncm, freq, result; 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 u_int es1371_dac_rate(struct es_info *es, u_int rate, int set) { u_int freq, r, result, dac, dis; if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; freq = (rate << 15) / 3000; result = (freq * 3000) >> 15; if (set) { dac = (set == 1)? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; dis = (set == 1)? ES1371_DIS_P2 : ES1371_DIS_P1; r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r); 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)); bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r); } return result; } static u_int es1371_wait_src_ready(struct es_info *es) { u_int t, r; for (t = 0; t < 500; t++) { if (!((r = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE)) & ES1371_SRC_RAM_BUSY)) return r; DELAY(1000); } printf("es1371: wait src ready timeout 0x%x [0x%x]\n", ES1371_REG_SMPRATE, r); return 0; } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int es_pci_probe(device_t dev) { if (pci_get_devid(dev) == ES1370_PCI_ID) { device_set_desc(dev, "AudioPCI ES1370"); return 0; } else if (pci_get_devid(dev) == ES1371_PCI_ID || pci_get_devid(dev) == ES1371_PCI_ID2 || pci_get_devid(dev) == ES1371_PCI_ID3) { device_set_desc(dev, "AudioPCI ES1371"); return 0; } return ENXIO; } static int es_pci_attach(device_t dev) { - snddev_info *d; u_int32_t data; struct es_info *es = 0; int type = 0; int regid; struct resource *reg = 0; int mapped; int irqid; struct resource *irq = 0; void *ih = 0; char status[SND_STATUSLEN]; struct ac97_info *codec; pcm_channel *ct = NULL; - d = device_get_softc(dev); if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(es, sizeof *es); es->dev = dev; mapped = 0; 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); if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { regid = MEM_MAP_REG; type = SYS_RES_MEMORY; reg = bus_alloc_resource(dev, type, ®id, 0, ~0, 1, RF_ACTIVE); if (reg) { es->st = rman_get_bustag(reg); es->sh = rman_get_bushandle(reg); mapped++; } } if (mapped == 0 && (data & PCIM_CMD_PORTEN)) { regid = PCIR_MAPS; type = SYS_RES_IOPORT; reg = bus_alloc_resource(dev, type, ®id, 0, ~0, 1, RF_ACTIVE); if (reg) { es->st = rman_get_bustag(reg); es->sh = rman_get_bushandle(reg); mapped++; } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto bad; } if (pci_get_devid(dev) == ES1371_PCI_ID || - pci_get_devid(dev) == ES1371_PCI_ID2 || + pci_get_devid(dev) == ES1371_PCI_ID2 || pci_get_devid(dev) == ES1371_PCI_ID3) { if(-1 == es1371_init(es, pci_get_revid(dev))) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = ac97_create(dev, es, NULL, es1371_rdcodec, es1371_wrcodec); 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(d, &ac97_mixer, codec) == -1) goto bad; + if (mixer_init(dev, &ac97_mixer, codec) == -1) goto bad; ct = &es1371_chantemplate; } else if (pci_get_devid(dev) == ES1370_PCI_ID) { if (-1 == es1370_init(es)) { device_printf(dev, "unable to initialize the card\n"); goto bad; } - mixer_init(d, &es1370_mixer, es); + mixer_init(dev, &es1370_mixer, es); ct = &es1370_chantemplate; } else goto bad; irqid = 0; irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!irq || bus_setup_intr(dev, irq, INTR_TYPE_TTY, es_intr, es, &ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/ES_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &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", (type == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(reg), rman_get_start(irq)); if (pcm_register(dev, es, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, ct, es); pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_setstatus(dev, status); return 0; bad: if (es) free(es, M_DEVBUF); if (reg) bus_release_resource(dev, type, regid, reg); 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 es_methods[] = { /* Device interface */ DEVMETHOD(device_probe, es_pci_probe), DEVMETHOD(device_attach, es_pci_attach), { 0, 0 } }; static driver_t es_driver = { "pcm", es_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_es137x, pci, es_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_es137x, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_es137x, 1); Index: head/sys/dev/sound/pci/fm801.c =================================================================== --- head/sys/dev/sound/pci/fm801.c (revision 65339) +++ head/sys/dev/sound/pci/fm801.c (revision 65340) @@ -1,743 +1,749 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #define PCI_VENDOR_FORTEMEDIA 0x1319 #define PCI_DEVICE_FORTEMEDIA1 0x08011319 #define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* ??? have no idea what's this... */ #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_BUFFSIZE 1024*4 /* Other values do not work!!! */ /* debug purposes */ #define DPRINT if(0) printf /* channel interface */ static void *fm801ch_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int fm801ch_setdir(void *data, int dir); static int fm801ch_setformat(void *data, u_int32_t format); static int fm801ch_setspeed(void *data, u_int32_t speed); static int fm801ch_setblocksize(void *data, u_int32_t blocksize); static int fm801ch_trigger(void *data, int go); static int fm801ch_getptr(void *data); static pcmchan_caps *fm801ch_getcaps(void *data); /* static int fm801ch_setup(pcm_channel *c); */ static u_int32_t fmts[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, /* AFMT_STEREO | (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE), (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE), */ 0 }; static pcmchan_caps fm801ch_caps = { 4000, 48000, fmts, 0 }; static pcm_channel fm801_chantemplate = { fm801ch_init, fm801ch_setdir, fm801ch_setformat, fm801ch_setspeed, fm801ch_setblocksize, fm801ch_trigger, fm801ch_getptr, fm801ch_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; struct fm801_info; struct fm801_chinfo { struct fm801_info *parent; pcm_channel *channel; 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; struct fm801_chinfo pch, rch; }; /* several procedures to release the thing properly if compiled as module */ static struct fm801_info *save801; struct fm801_info *fm801_get __P((void )); static void fm801_save(struct fm801_info *fm801) { save801 = fm801; } struct fm801_info * fm801_get(void ) { return save801; } /* 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: return bus_space_write_1(fm801->st, fm801->sh, regno, data); case 2: return bus_space_write_2(fm801->st, fm801->sh, regno, data); case 4: return bus_space_write_4(fm801->st, fm801->sh, regno, data); default: return; } } /* * ac97 codec routines */ #define TIMO 50 static u_int32_t fm801_rdcd(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 void fm801_wrcd(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; } - + 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; } DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data); return; } -/* - * The interrupt handler +/* + * 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); struct fm801_chinfo *ch = &(fm801->pch); snd_dbuf *b = ch->buffer; - + DPRINT("\nfm801_intr intsrc 0x%x ", intsrc); DPRINT("rp %d, rl %d, fp %d fl %d, size=%d\n", b->rp,b->rl, b->fp,b->fl, b->blksz); - + 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); } /* * 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) { - snddev_info *d; u_int32_t data; struct ac97_info *codec; struct fm801_info *fm801; int i; int mapped = 0; char status[SND_STATUSLEN]; - - d = device_get_softc(dev); + if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801),M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } - + bzero(fm801, sizeof(*fm801)); 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_MAPS + i*4; fm801->regtype = SYS_RES_MEMORY; fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid, 0, ~0, 1, RF_ACTIVE); if(!fm801->reg) { fm801->regtype = SYS_RES_IOPORT; fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid, 0, ~0, 1, 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_init(fm801); - + codec = ac97_create(dev, (void *)fm801, NULL, fm801_rdcd, fm801_wrcd); if (codec == NULL) goto oops; - if (mixer_init(d, &ac97_mixer, codec) == -1) goto oops; + if (mixer_init(dev, &ac97_mixer, codec) == -1) goto oops; fm801->irqid = 0; fm801->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &fm801->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!fm801->irq || bus_setup_intr(dev, fm801->irq, INTR_TYPE_TTY, fm801_intr, fm801, &fm801->ih)) { device_printf(dev, "unable to map interrupt\n"); goto oops; } - + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/FM801_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &fm801->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto oops; } - + snprintf(status, 64, "at %s 0x%lx irq %ld", (fm801->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(fm801->reg), rman_get_start(fm801->irq)); #define FM801_MAXPLAYCH 1 if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops; pcm_addchan(dev, PCMDIR_PLAY, &fm801_chantemplate, fm801); pcm_addchan(dev, PCMDIR_REC, &fm801_chantemplate, fm801); pcm_setstatus(dev, status); fm801_save(fm801); return 0; - -oops: + +oops: printf("Forte Media FM801 initialization failed\n"); 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); free(fm801, M_DEVBUF); return ENXIO; } 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 0; } /* if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) { device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)"); return ENXIO; } */ return ENXIO; } /* channel interface */ static void * fm801ch_init(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = FM801_BUFFSIZE; ch->dir = dir; if( chn_allocbuf(ch->buffer, fm801->parent_dmat) == -1) return NULL; return (void *)ch; } static int fm801ch_setdir(void *data, int dir) { struct fm801_chinfo *ch = data; ch->dir = dir; return 0; } static int fm801ch_setformat(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 }, + { 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(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(void *data, u_int32_t blocksize) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; - + if(ch->dir == PCMDIR_PLAY) { if(fm801->play_flip) return fm801->play_blksize; fm801->play_blksize = blocksize; } - + if(ch->dir == PCMDIR_REC) { if(fm801->rec_flip) return fm801->rec_blksize; fm801->rec_blksize = blocksize; } - + DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir); return blocksize; } static int fm801ch_trigger(void *data, int go) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; u_int32_t baseaddr = vtophys(ch->buffer->buf); snd_dbuf *b = ch->buffer; u_int32_t k1; - + DPRINT("fm801ch_trigger go %d , ", go); DPRINT("rp %d, rl %d, fp %d fl %d, dl %d, blksize=%d\n", b->rp,b->rl, b->fp,b->fl, b->dl, b->blksz); - + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) { 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, + 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, + 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(void *data) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; int result = 0; snd_dbuf *b = ch->buffer; - + if (ch->dir == PCMDIR_PLAY) { - result = fm801_rd(fm801, - (fm801->play_flip&1) ? + 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; - } - + } + DPRINT("fm801ch_getptr:%d, rp %d, rl %d, fp %d fl %d\n", - result, b->rp,b->rl, b->fp,b->fl); + result, b->rp,b->rl, b->fp,b->fl); return result; } static pcmchan_caps * fm801ch_getcaps(void *data) { return &fm801ch_caps; } static int fm801_pci_detach(device_t dev) { struct fm801_info *fm801 = fm801_get(); DPRINT("Forte Media FM801 detach\n"); 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); free(fm801, M_DEVBUF); 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), { 0, 0} }; static driver_t fm801_driver = { "pcm", fm801_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(fm801, pci, fm801_driver, pcm_devclass,0, 0); Index: head/sys/dev/sound/pci/neomagic.c =================================================================== --- head/sys/dev/sound/pci/neomagic.c (revision 65339) +++ head/sys/dev/sound/pci/neomagic.c (revision 65340) @@ -1,696 +1,707 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include #include /* -------------------------------------------------------------------- */ #define NM_BUFFSIZE 16384 #define NM256AV_PCI_ID 0x800510c8 #define NM256ZX_PCI_ID 0x800610c8 struct sc_info; /* channel registers */ struct sc_chinfo { int spd, dir, fmt; snd_dbuf *buffer; 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 */ /* channel interface */ static void *nmchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int nmchan_free(void *data); static int nmchan_setdir(void *data, int dir); static int nmchan_setformat(void *data, u_int32_t format); static int nmchan_setspeed(void *data, u_int32_t speed); static int nmchan_setblocksize(void *data, u_int32_t blocksize); static int nmchan_trigger(void *data, int go); static int nmchan_getptr(void *data); static pcmchan_caps *nmchan_getcaps(void *data); static int nm_waitcd(struct sc_info *sc); /* talk to the codec - called from ac97.c */ static u_int32_t nm_rdcd(void *, int); static void nm_wrcd(void *, int, u_int32_t); /* 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, }; #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 pcmchan_caps nm_caps = {4000, 48000, nm_fmt, 0}; static pcm_channel nm_chantemplate = { nmchan_init, nmchan_setdir, nmchan_setformat, nmchan_setspeed, nmchan_setblocksize, nmchan_trigger, nmchan_getptr, nmchan_getcaps, + nmchan_free, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* -------------------------------------------------------------------- */ /* 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; while (cnt-- > 0) { if (nm_rd(sc, sc->ac97_status, 2) & sc->ac97_busy) DELAY(100); else break; } return (nm_rd(sc, sc->ac97_status, 2) & sc->ac97_busy); } static u_int32_t nm_initcd(void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; nm_wr(sc, 0x6c0, 0x01, 1); nm_wr(sc, 0x6cc, 0x87, 1); nm_wr(sc, 0x6cc, 0x80, 1); nm_wr(sc, 0x6cc, 0x00, 1); return 0; } static u_int32_t nm_rdcd(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 0xffffffff; } } static void nm_wrcd(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; } } } device_printf(sc->dev, "ac97 codec not ready\n"); } 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(void *devinfo, snd_dbuf *b, 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->buffer = b; ch->buffer->bufsize = NM_BUFFSIZE; ch->buffer->buf = (u_int8_t *)rman_get_virtual(sc->buf) + chnbuf; if (bootverbose) device_printf(sc->dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? "play" : "rec", ch->buffer->buf); ch->parent = sc; ch->channel = c; ch->dir = dir; return ch; } static int +nmchan_free(void *data) +{ + return 0; +} + +static int nmchan_setdir(void *data, int dir) { return 0; } static int nmchan_setformat(void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->fmt = format; return nm_setch(ch); } static int nmchan_setspeed(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(void *data, u_int32_t blocksize) { return blocksize; } static int nmchan_trigger(void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int ssz; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) 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) { 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 + NM_BUFFSIZE / 2, 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 { 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) { 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 + NM_BUFFSIZE / 2, 4); } else { nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } } return 0; } static int nmchan_getptr(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 pcmchan_caps * nmchan_getcaps(void *data) { return &nm_caps; } /* 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; nm_ackint(sc, sc->playint); chn_intr(sc->pch.channel); } if (status & sc->recint) { status &= ~sc->recint; 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 ((nm_rdbuf(sc, ofs, 4) & NM_SIG_MASK) == NM_SIGNATURE) { i = nm_rdbuf(sc, ofs + 4, 4); if (i != 0 && i != 0xffffffff) 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) { char *s = NULL; u_int32_t subdev, i; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case NM256AV_PCI_ID: i = 0; while ((i < NUM_BADCARDS) && (badcards[i] != subdev)) i++; 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) { - snddev_info *d; u_int32_t data; struct sc_info *sc; struct ac97_info *codec; char status[SND_STATUSLEN]; - d = device_get_softc(dev); if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); 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_MAPS; sc->buf = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->bufid, 0, ~0, 1, RF_ACTIVE); sc->regid = PCIR_MAPS + 4; sc->reg = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->regid, 0, ~0, 1, 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_initcd, nm_rdcd, nm_wrcd); if (codec == NULL) goto bad; - if (mixer_init(d, &ac97_mixer, codec) == -1) goto bad; + if (mixer_init(dev, &ac97_mixer, codec) == -1) goto bad; sc->irqid = 0; sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, 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", rman_get_start(sc->buf), rman_get_start(sc->reg), rman_get_start(sc->irq)); if (pcm_register(dev, sc, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &nm_chantemplate, sc); pcm_addchan(dev, PCMDIR_PLAY, &nm_chantemplate, sc); pcm_setstatus(dev, status); return 0; bad: 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_resume(device_t dev) { - snddev_info *d; struct sc_info *sc; - d = device_get_softc(dev); sc = pcm_getdevinfo(dev); /* Reinit audio device */ if (nm_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } /* Reinit mixer */ - if (mixer_reinit(d) == -1) { + if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } return 0; } static device_method_t nm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nm_pci_probe), DEVMETHOD(device_attach, nm_pci_attach), DEVMETHOD(device_resume, nm_pci_resume), { 0, 0 } }; static driver_t nm_driver = { "pcm", nm_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_neomagic, pci, nm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_neomagic, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_neomagic, 1); Index: head/sys/dev/sound/pci/solo.c =================================================================== --- head/sys/dev/sound/pci/solo.c (revision 65339) +++ head/sys/dev/sound/pci/solo.c (revision 65340) @@ -1,1006 +1,1014 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include #define ESS_BUFFSIZE (16384) #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ #undef ESS18XX_DUPLEX /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED /* channel interface for ESS */ static void *esschan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int esschan_setdir(void *data, int dir); static int esschan_setformat(void *data, u_int32_t format); static int esschan_setspeed(void *data, u_int32_t speed); static int esschan_setblocksize(void *data, u_int32_t blocksize); static int esschan_trigger(void *data, int go); static int esschan_getptr(void *data); static pcmchan_caps *esschan_getcaps(void *data); 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 pcmchan_caps ess_playcaps = {5000, 49000, 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 pcmchan_caps ess_reccaps = {5000, 49000, ess_recfmt, 0}; static pcm_channel ess_chantemplate = { esschan_init, esschan_setdir, esschan_setformat, esschan_setspeed, esschan_setblocksize, esschan_trigger, esschan_getptr, esschan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; struct ess_info; struct ess_chinfo { struct ess_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir, hwch, stopping; u_int32_t fmt, spd; }; struct ess_info { struct resource *io, *sb, *vc, *mpu, *gp; /* I/O address for the board */ struct resource *irq; bus_dma_tag_t parent_dmat; int simplex_dir, type, duplex:1, newspeed:1, dmasz[2]; struct ess_chinfo pch, rch; }; 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); static int essmix_init(snd_mixer *m); static int essmix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right); static int essmix_setrecsrc(snd_mixer *m, u_int32_t src); static snd_mixer ess_mixer = { - "ESS mixer", - essmix_init, - essmix_set, - essmix_setrecsrc, + "ESS mixer", + essmix_init, + NULL, + essmix_set, + essmix_setrecsrc, }; static devclass_t pcm_devclass; /* * 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) { u_long flags; DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) flags = spltty(); 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); splx(flags); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; u_long flags; flags = spltty(); ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); splx(flags); 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; src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; if (src == 0) 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); } chn_intr(sc->pch.channel); } 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); } chn_intr(sc->rch.channel); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); } /* 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->buffer->dl); 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(void *devinfo, snd_dbuf *b, 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->buffer->bufsize = ESS_BUFFSIZE; if (chn_allocbuf(ch->buffer, sc->parent_dmat) == -1) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; return ch; } static int esschan_setdir(void *data, int dir) { struct ess_chinfo *ch = data; ch->dir = dir; return 0; } static int esschan_setformat(void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static int esschan_setspeed(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(void *data, u_int32_t blocksize) { return blocksize; } static int esschan_trigger(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) return 0; switch (go) { case PCMTRIG_START: ess_dmasetup(sc, ch->hwch, vtophys(ch->buffer->buf), ch->buffer->bufsize, ch->dir); ess_dmatrigger(sc, ch->hwch, 1); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } return 0; } static int esschan_getptr(void *data) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; return ess_dmapos(sc, ch->hwch); } static pcmchan_caps * esschan_getcaps(void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } /************************************************************/ static int essmix_init(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(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(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 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; u_long flags; KASSERT(ch == 1 || ch == 2, ("bad ch")); flags = spltty(); 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); splx(flags); 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) { /* should we bus_teardown_intr here? */ if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = 0; } if (sc->io) { bus_release_resource(dev, SYS_RES_IOPORT, 0 * 4 + PCIR_MAPS, sc->io); sc->io = 0; } if (sc->sb) { bus_release_resource(dev, SYS_RES_IOPORT, 1 * 4 + PCIR_MAPS, sc->sb); sc->sb = 0; } if (sc->vc) { bus_release_resource(dev, SYS_RES_IOPORT, 2 * 4 + PCIR_MAPS, sc->vc); sc->vc = 0; } if (sc->mpu) { bus_release_resource(dev, SYS_RES_IOPORT, 3 * 4 + PCIR_MAPS, sc->mpu); sc->mpu = 0; } if (sc->gp) { bus_release_resource(dev, SYS_RES_IOPORT, 4 * 4 + PCIR_MAPS, sc->gp); sc->gp = 0; } free(sc, M_DEVBUF); } static int ess_alloc_resources(struct ess_info *sc, device_t dev) { int rid; rid = 0 * 4 + PCIR_MAPS; sc->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 1 * 4 + PCIR_MAPS; sc->sb = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 2 * 4 + PCIR_MAPS; sc->vc = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 3 * 4 + PCIR_MAPS; sc->mpu = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 4 * 4 + PCIR_MAPS; sc->gp = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); rid = 0; sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp)? 0 : ENXIO; } static int ess_probe(device_t dev) { char *s = NULL; u_int32_t subdev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case 0x1969125d: if (subdev == 0x8888125d) s = "ESS Solo-1E"; else if (subdev == 0x1818125d) s = "ESS Solo-1"; else s = "ESS Solo-1 (unknown vendor)"; break; } if (s) device_set_desc(dev, s); return s? 0 : ENXIO; } #define PCI_LEGACYCONTROL 0x40 #define PCI_CONFIG 0x50 #define PCI_DDMACONTROL 0x60 static int ess_attach(device_t dev) { - snddev_info *d = device_get_softc(dev); struct ess_info *sc; void *ih; char status[SND_STATUSLEN]; u_int16_t ddma; u_int32_t data; sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); if (!sc) return ENXIO; bzero(sc, sizeof *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); if (ess_alloc_resources(sc, dev)) goto no; ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, PCI_CONFIG, 0, 2); if (ess_reset_dsp(sc)) goto no; - mixer_init(d, &ess_mixer, sc); + mixer_init(dev, &ess_mixer, sc); 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 (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, ess_intr, sc, &ih); if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/65536, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/ESS_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } snprintf(status, SND_STATUSLEN, "at io 0x%lx,0x%lx,0x%lx irq %ld", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), rman_get_start(sc->irq)); if (pcm_register(dev, sc, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &ess_chantemplate, sc); pcm_addchan(dev, PCMDIR_PLAY, &ess_chantemplate, sc); pcm_setstatus(dev, status); return 0; no: ess_release_resources(sc, dev); return ENXIO; } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, sizeof(snddev_info), }; DRIVER_MODULE(snd_solo, pci, ess_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_solo, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_solo, 1); Index: head/sys/dev/sound/pci/t4dwave.c =================================================================== --- head/sys/dev/sound/pci/t4dwave.c (revision 65339) +++ head/sys/dev/sound/pci/t4dwave.c (revision 65340) @@ -1,717 +1,723 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include /* -------------------------------------------------------------------- */ #define TDX_PCI_ID 0x20001023 #define TNX_PCI_ID 0x20011023 #define TR_BUFFSIZE 0xf000 #define TR_TIMEOUT_CDC 0xffff #define TR_INTSAMPLES 0x2000 #define TR_MAXPLAYCH 4 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; int index, ss; snd_dbuf *buffer; pcm_channel *channel; struct tr_info *parent; }; /* device private data */ struct tr_info { u_int32_t type; 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; u_int32_t playchns; struct tr_chinfo chinfo[TR_MAXPLAYCH]; struct tr_chinfo recchinfo; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* channel interface */ static void *trchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int trchan_setdir(void *data, int dir); static int trchan_setformat(void *data, u_int32_t format); static int trchan_setspeed(void *data, u_int32_t speed); static int trchan_setblocksize(void *data, u_int32_t blocksize); static int trchan_trigger(void *data, int go); static int trchan_getptr(void *data); static pcmchan_caps *trchan_getcaps(void *data); /* talk to the codec - called from ac97.c */ static u_int32_t tr_rdcd(void *, int); static void tr_wrcd(void *, int, u_int32_t); /* stuff */ static int tr_init(struct tr_info *); static void tr_intr(void *); /* talk to the card */ static u_int32_t tr_rd(struct tr_info *, int, int); static void tr_wr(struct tr_info *, int, u_int32_t, int); /* manipulate playback channels */ static void tr_clrint(struct tr_info *, char); static void tr_enaint(struct tr_info *, char, int); static u_int32_t tr_testint(struct tr_info *, char); static void tr_rdch(struct tr_info *, char, struct tr_chinfo *); static void tr_wrch(struct tr_info *, char, struct tr_chinfo *); static void tr_selch(struct tr_info *, char); static void tr_startch(struct tr_info *, char); static void tr_stopch(struct tr_info *, char); /* -------------------------------------------------------------------- */ 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 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 pcmchan_caps tr_playcaps = {4000, 48000, tr_playfmt, 0}; static pcm_channel tr_chantemplate = { trchan_init, trchan_setdir, trchan_setformat, trchan_setspeed, trchan_setblocksize, trchan_trigger, trchan_getptr, trchan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* -------------------------------------------------------------------- */ static u_int32_t tr_fmttobits(u_int32_t fmt) { u_int32_t bits = 0; bits |= (fmt & AFMT_STEREO)? 0x4 : 0; bits |= (fmt & (AFMT_S8 | AFMT_S16_LE))? 0x2 : 0; bits |= (fmt & (AFMT_S16_LE | AFMT_U16_LE))? 0x8 : 0; return bits; } /* 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 u_int32_t tr_rdcd(void *devinfo, int regno) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { 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 0xffffffff; } regno &= 0x7f; 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); if (i == 0) printf("codec timeout during read of register %x\n", regno); return (j >> TR_CDC_DATA) & 0xffff; } static void tr_wrcd(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 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; } regno &= 0x7f; #if 0 printf("tr_wrcd: reg %x was %x", regno, tr_rdcd(devinfo, regno)); #endif j=trw; for (i=TR_TIMEOUT_CDC; (i>0) && (j & trw); i--) j=tr_rd(tr, treg, 4); 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 if (i==0) printf("codec timeout writing %x, data %x\n", regno, data); } /* playback channel interrupts */ static u_int32_t tr_testint(struct tr_info *tr, char channel) { return tr_rd(tr, (channel & 0x20)? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 4) & (1<<(channel & 0x1f)); } static void tr_clrint(struct tr_info *tr, char channel) { tr_wr(tr, (channel & 0x20)? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 1<<(channel & 0x1f), 4); } static void tr_enaint(struct tr_info *tr, char channel, int enable) { u_int32_t reg = (channel & 0x20)? TR_REG_INTENB : TR_REG_INTENA; u_int32_t i = tr_rd(tr, reg, 4); channel &= 0x1f; i &= ~(1 << channel); i |= (enable? 1 : 0) << channel; tr_clrint(tr, channel); tr_wr(tr, reg, i, 4); } /* playback channels */ static void tr_selch(struct tr_info *tr, char channel) { int i=tr_rd(tr, TR_REG_CIR, 4); i &= ~TR_CIR_MASK; i |= channel & 0x3f; tr_wr(tr, TR_REG_CIR, i, 4); } static void tr_startch(struct tr_info *tr, char channel) { tr_wr(tr, (channel & 0x20)? TR_REG_STARTB : TR_REG_STARTA, 1<<(channel & 0x1f), 4); } static void tr_stopch(struct tr_info *tr, char channel) { tr_wr(tr, (channel & 0x20)? TR_REG_STOPB : TR_REG_STOPA, 1<<(channel & 0x1f), 4); } static void tr_wrch(struct tr_info *tr, char channel, struct tr_chinfo *ch) { 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->rvol<<7) | (ch->cvol); cr[4]=(ch->gvsel<<31)|(ch->pan<<24)|(ch->vol<<16)|(ch->ctrl<<12)|(ch->ec); switch (tr->type) { 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); cr[3]|=0x0000c000; break; case TNX_PCI_ID: ch->cso &= 0x00ffffff; ch->eso &= 0x00ffffff; cr[0]=((ch->delta & 0xff)<<24) | (ch->cso); cr[2]=((ch->delta>>16)<<24) | (ch->eso); cr[3]|=(ch->alpha<<20) | (ch->fms<<16) | (ch->fmc<<14); break; } tr_selch(tr, channel); for (i=0; ilba= (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 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; } } /* channel interface */ void * trchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_chinfo *ch; if (dir == PCMDIR_PLAY) { ch = &tr->chinfo[tr->playchns]; ch->index = tr->playchns++; } else { ch = &tr->recchinfo; ch->index = -1; } ch->buffer = b; ch->buffer->bufsize = TR_BUFFSIZE; ch->parent = tr; ch->channel = c; if (chn_allocbuf(ch->buffer, tr->parent_dmat) == -1) return NULL; else return ch; } static int trchan_setdir(void *data, int dir) { struct tr_chinfo *ch = data; struct tr_info *tr = ch->parent; if (dir == PCMDIR_PLAY && ch->index >= 0) { ch->fmc = ch->fms = ch->ec = ch->alpha = 0; ch->lba = vtophys(ch->buffer->buf); ch->cso = 0; ch->eso = ch->buffer->bufsize - 1; ch->rvol = ch->cvol = 0; ch->gvsel = 0; ch->pan = 0; ch->vol = 0; ch->ctrl = 0x01; ch->delta = 0; tr_wrch(tr, ch->index, ch); tr_enaint(tr, ch->index, 1); } else if (dir == PCMDIR_REC && ch->index == -1) { /* set up dma mode regs */ u_int32_t i; 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, vtophys(ch->buffer->buf), 4); /* set up buffer size */ i = tr_rd(tr, TR_REG_DMAR4, 4) & ~0x00ffffff; tr_wr(tr, TR_REG_DMAR4, i | (ch->buffer->bufsize - 1), 4); } else return -1; return 0; } static int trchan_setformat(void *data, u_int32_t format) { struct tr_chinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t bits = tr_fmttobits(format); ch->ss = 1; ch->ss <<= (format & AFMT_STEREO)? 1 : 0; ch->ss <<= (format & AFMT_16BIT)? 1 : 0; if (ch->index >= 0) { tr_rdch(tr, ch->index, ch); ch->eso = (ch->buffer->bufsize / ch->ss) - 1; ch->ctrl = bits | 0x01; tr_wrch(tr, ch->index, ch); } else { u_int32_t i; /* set # of samples between interrupts */ i = (TR_INTSAMPLES >> ((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 trchan_setspeed(void *data, u_int32_t speed) { struct tr_chinfo *ch = data; struct tr_info *tr = ch->parent; if (ch->index >= 0) { tr_rdch(tr, ch->index, ch); ch->delta = (speed << 12) / 48000; tr_wrch(tr, ch->index, ch); return (ch->delta * 48000) >> 12; } else { /* setup speed */ ch->delta = (48000 << 12) / speed; tr_wr(tr, TR_REG_SBDELTA, ch->delta, 2); return (48000 << 12) / ch->delta; } return 0; } static int trchan_setblocksize(void *data, u_int32_t blocksize) { struct tr_chinfo *ch = data; return ch->buffer->bufsize / 2; } static int trchan_trigger(void *data, int go) { struct tr_chinfo *ch = data; struct tr_info *tr = ch->parent; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; if (ch->index >= 0) { if (go == PCMTRIG_START) { tr_rdch(tr, ch->index, ch); ch->cso = 0; tr_wrch(tr, ch->index, ch); tr_startch(tr, ch->index); } else tr_stopch(tr, ch->index); } else { u_int32_t i = tr_rd(tr, TR_REG_SBCTRL, 1) & ~7; tr_wr(tr, TR_REG_SBCTRL, i | (go == PCMTRIG_START)? 1 : 0, 1); } return 0; } static int trchan_getptr(void *data) { struct tr_chinfo *ch = data; struct tr_info *tr = ch->parent; if (ch->index >= 0) { tr_rdch(tr, ch->index, ch); return ch->cso * ch->ss; } else return tr_rd(tr, TR_REG_DMAR0, 4) - vtophys(ch->buffer->buf); } static pcmchan_caps * trchan_getcaps(void *data) { struct tr_chinfo *ch = data; return (ch->index >= 0)? &tr_playcaps : &tr_reccaps; } /* The interrupt handler */ static void tr_intr(void *p) { struct tr_info *tr = (struct tr_info *)p; u_int32_t intsrc = tr_rd(tr, TR_REG_MISCINT, 4); if (intsrc & TR_INT_ADDR) { int i; for (i = 0; i < tr->playchns; i++) { if (tr_testint(tr, i)) { chn_intr(tr->chinfo[i].channel); tr_clrint(tr, i); } } } 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) { if (tr->type == TDX_PCI_ID) { tr_wr(tr, TDX_REG_CODECST, TDX_CDC_ON, 4); } else tr_wr(tr, TNX_REG_CODECST, TNX_CDC_ON, 4); tr_wr(tr, TR_REG_CIR, TR_CIR_MIDENA | TR_CIR_ADDRENA, 4); tr->playchns = 0; return 0; } static int tr_pci_probe(device_t dev) { if (pci_get_devid(dev) == TDX_PCI_ID) { device_set_desc(dev, "Trident 4DWave DX"); return 0; } if (pci_get_devid(dev) == TNX_PCI_ID) { device_set_desc(dev, "Trident 4DWave NX"); return 0; } return ENXIO; } static int tr_pci_attach(device_t dev) { - snddev_info *d; u_int32_t data; struct tr_info *tr; struct ac97_info *codec; int i; int mapped; char status[SND_STATUSLEN]; - d = device_get_softc(dev); if ((tr = malloc(sizeof(*tr), M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(tr, sizeof(*tr)); tr->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); mapped = 0; /* XXX dfr: is this strictly necessary? */ for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { tr->regid = PCIR_MAPS + i*4; tr->regtype = SYS_RES_MEMORY; tr->reg = bus_alloc_resource(dev, tr->regtype, &tr->regid, 0, ~0, 1, RF_ACTIVE); if (!tr->reg) { tr->regtype = SYS_RES_IOPORT; tr->reg = bus_alloc_resource(dev, tr->regtype, &tr->regid, 0, ~0, 1, RF_ACTIVE); } if (tr->reg) { tr->st = rman_get_bustag(tr->reg); tr->sh = rman_get_bushandle(tr->reg); mapped++; } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto bad; } if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = ac97_create(dev, tr, NULL, tr_rdcd, tr_wrcd); if (codec == NULL) goto bad; - if (mixer_init(d, &ac97_mixer, codec) == -1) goto bad; + if (mixer_init(dev, &ac97_mixer, codec) == -1) goto bad; tr->irqid = 0; tr->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &tr->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!tr->irq || bus_setup_intr(dev, tr->irq, INTR_TYPE_TTY, tr_intr, tr, &tr->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/TR_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &tr->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, 64, "at %s 0x%lx irq %ld", (tr->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(tr->reg), rman_get_start(tr->irq)); if (pcm_register(dev, tr, TR_MAXPLAYCH, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &tr_chantemplate, tr); for (i = 0; i < TR_MAXPLAYCH; i++) pcm_addchan(dev, PCMDIR_PLAY, &tr_chantemplate, tr); pcm_setstatus(dev, status); return 0; bad: 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); free(tr, M_DEVBUF); return ENXIO; } static device_method_t tr_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tr_pci_probe), DEVMETHOD(device_attach, tr_pci_attach), { 0, 0 } }; static driver_t tr_driver = { "pcm", tr_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(snd_t4dwave, pci, tr_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_t4dwave, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(snd_t4dwave, 1); Index: head/sys/dev/sound/pci/via82c686.c =================================================================== --- head/sys/dev/sound/pci/via82c686.c (revision 65339) +++ head/sys/dev/sound/pci/via82c686.c (revision 65340) @@ -1,660 +1,666 @@ /* * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #define VIA_PCI_ID 0x30581106 #define NSEGS 16 /* Number of segments in SGD table */ #define SEGS_PER_CHAN (NSEGS/2) #undef DEB #define DEB(x) struct via_info; struct via_chinfo { struct via_info *parent; pcm_channel *channel; snd_dbuf *buffer; int dir; }; 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; struct via_chinfo pch, rch; struct via_dma_op *sgd_table; u_int16_t codec_caps; }; static u_int32_t via_rd(struct via_info *via, int regno, int size); static void via_wr(struct via_info *, int regno, u_int32_t data, int size); int via_waitready_codec(struct via_info *via); int via_waitvalid_codec(struct via_info *via); u_int32_t via_read_codec(void *addr, int reg); void via_write_codec(void *addr, int reg, u_int32_t val); static void via_intr(void *); bus_dmamap_callback_t dma_cb; /* channel interface */ static void *viachan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); static int viachan_setdir(void *data, int dir); static int viachan_setformat(void *data, u_int32_t format); static int viachan_setspeed(void *data, u_int32_t speed); static int viachan_setblocksize(void *data, u_int32_t blocksize); static int viachan_trigger(void *data, int go); static int viachan_getptr(void *data); static pcmchan_caps *viachan_getcaps(void *data); static u_int32_t via_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps via_playcaps = {4000, 48000, via_playfmt, 0}; static u_int32_t via_recfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static pcmchan_caps via_reccaps = {4000, 48000, via_recfmt, 0}; static pcm_channel via_chantemplate = { viachan_init, viachan_setdir, viachan_setformat, viachan_setspeed, viachan_setblocksize, viachan_trigger, viachan_getptr, viachan_getcaps, + NULL, /* free */ + NULL, /* nop1 */ + NULL, /* nop2 */ + NULL, /* nop3 */ + NULL, /* nop4 */ + NULL, /* nop5 */ + NULL, /* nop6 */ + NULL, /* nop7 */ }; /* * 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 AC'97 Audio"); return 0; } return ENXIO; } void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { } static int via_attach(device_t dev) { - snddev_info *d; struct via_info *via = 0; struct ac97_info *codec; char status[SND_STATUSLEN]; u_int32_t data; struct resource *reg = 0; int regid; struct resource *irq = 0; void *ih = 0; int irqid; u_int16_t v; bus_dmamap_t sgd_dma_map; - d = device_get_softc(dev); if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(via, sizeof *via); /* 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); pci_write_config(dev, VIA_PCICONF_MISC, VIA_PCICONF_ACLINKENAB | VIA_PCICONF_ACSGD | VIA_PCICONF_ACNOTRST | VIA_PCICONF_ACVSR, 1); regid = PCIR_MAPS; reg = bus_alloc_resource(dev, SYS_RES_IOPORT, ®id, 0, ~0, 1, RF_ACTIVE); if (!reg) { device_printf(dev, "via: Cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(reg); via->sh = rman_get_bushandle(reg); irqid = 0; irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!irq || bus_setup_intr(dev, irq, INTR_TYPE_TTY, via_intr, 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); codec = ac97_create(dev, via, NULL, via_read_codec, via_write_codec); if (!codec) goto bad; - mixer_init(d, &ac97_mixer, codec); + mixer_init(dev, &ac97_mixer, codec); /* * The mixer init resets the codec. So enabling VRA must be done * afterwards. */ v = via_read_codec(via, AC97_REG_EXT_AUDIO_ID); v &= (AC97_ENAB_VRA | AC97_ENAB_MICVRA); via_write_codec(via, AC97_REG_EXT_AUDIO_STAT, v); via->codec_caps = v; { v = via_read_codec(via, AC97_REG_EXT_AUDIO_STAT); DEB(printf("init: codec stat: %d\n", v)); } if (!(v & AC97_CODEC_DOES_VRA)) { /* no VRA => can do only 48 kbps */ via_playcaps.minspeed = 48000; via_reccaps.minspeed = 48000; } /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/VIA_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, &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*/NULL, /*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, &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, &sgd_dma_map) == -1) goto bad; if (bus_dmamap_load(via->sgd_dmat, sgd_dma_map, via->sgd_table, NSEGS * sizeof(struct via_dma_op), dma_cb, 0, 0)) goto bad; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld", rman_get_start(reg), rman_get_start(irq)); /* Register */ if (pcm_register(dev, via, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &via_chantemplate, via); pcm_addchan(dev, PCMDIR_REC, &via_chantemplate, via); pcm_setstatus(dev, status); return 0; bad: if (via) free(via, M_DEVBUF); bus_release_resource(dev, SYS_RES_IOPORT, regid, reg); 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 via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, sizeof(snddev_info), }; static devclass_t pcm_devclass; DRIVER_MODULE(via, pci, via_driver, pcm_devclass, 0, 0); MODULE_DEPEND(via, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); MODULE_VERSION(via, 1); static 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 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 */ 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; } 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; } void via_write_codec(void *addr, int reg, u_int32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_INDEX(reg) | val, 4); } u_int32_t via_read_codec(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); } /* channel interface */ static void * viachan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch = (dir == PCMDIR_PLAY) ? &via->pch : &via->rch; ch->parent = via; ch->channel = c; ch->buffer = b; b->bufsize = VIA_BUFFSIZE; if (chn_allocbuf(ch->buffer, via->parent_dmat) == -1) return NULL; return ch; } static int viachan_setdir(void *data, int dir) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; struct via_dma_op *ado; int i, chunk_size; int phys_addr, flag; ch->dir = dir; /* * 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. */ ado = via->sgd_table; chunk_size = ch->buffer->bufsize / SEGS_PER_CHAN; if (dir == PCMDIR_REC) { ado += SEGS_PER_CHAN; } DEB(printf("SGD table located at va %p\n", ado)); phys_addr = vtophys(ch->buffer->buf); for (i = 0; i < SEGS_PER_CHAN; i++) { ado->ptr = phys_addr; flag = (i == SEGS_PER_CHAN-1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ado->flags = flag | chunk_size; DEB(printf("ado->ptr/flags = %x/%x\n", phys_addr, flag)); phys_addr += chunk_size; ado++; } return 0; } static int viachan_setformat(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; /* Set up for output format */ if (ch->dir == PCMDIR_PLAY) { DEB(printf("set play format: %x\n", format)); mode = via_rd(via, VIA_PLAY_MODE, 1); mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO); mode |= mode_set; via_wr(via, VIA_PLAY_MODE, mode, 1); } else { DEB(printf("set record format: %x\n", format)); mode = via_rd(via, VIA_RECORD_MODE, 1); mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO); mode |= mode_set; via_wr(via, VIA_RECORD_MODE, mode, 1); } return 0; } static int viachan_setspeed(void *data, u_int32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* * 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 (ch->dir == PCMDIR_PLAY) { DEB(printf("requested play speed: %d\n", speed)); if (via->codec_caps & AC97_CODEC_DOES_VRA) { via_write_codec(via, AC97_REG_EXT_DAC_RATE, speed); speed = via_read_codec(via, AC97_REG_EXT_DAC_RATE); } else { DEB(printf("VRA not supported!\n")); speed = 48000; } DEB(printf("obtained play speed: %d\n", speed)); } else { DEB(printf("requested record speed: %d\n", speed)); if (via->codec_caps & AC97_CODEC_DOES_VRA) { via_write_codec(via, AC97_REG_EXT_ADC_RATE, speed); speed = via_read_codec(via, AC97_REG_EXT_ADC_RATE); } else { DEB(printf("VRA not supported!\n")); speed = 48000; } DEB(printf("obtained record speed: %d\n", speed)); } return speed; } static int viachan_setblocksize(void *data, u_int32_t blocksize) { struct via_chinfo *ch = data; return ch->buffer->bufsize / 2; } static int viachan_trigger(void *data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; struct via_dma_op *ado; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { ado = &via->sgd_table[0]; DEB(printf("ado located at va=%p pa=%x\n", ado, vtophys(ado))); via_wr(via, VIA_PLAY_DMAOPS_BASE, vtophys(ado),4); via_wr(via, VIA_PLAY_CONTROL, VIA_RPCTRL_START, 1); } else { /* Stop DMA */ via_wr(via, VIA_PLAY_CONTROL, VIA_RPCTRL_TERMINATE, 1); } } else { if (go == PCMTRIG_START) { ado = &via->sgd_table[SEGS_PER_CHAN]; DEB(printf("ado located at va=%p pa=%x\n", ado, vtophys(ado))); via_wr(via, VIA_RECORD_DMAOPS_BASE, vtophys(ado),4); via_wr(via, VIA_RECORD_CONTROL, VIA_RPCTRL_START, 1); } else { /* Stop DMA */ via_wr(via, VIA_RECORD_CONTROL, VIA_RPCTRL_TERMINATE, 1); } } DEB(printf("viachan_trigger: go=%d\n", go)); return 0; } static int viachan_getptr(void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; struct via_dma_op *ado; int ptr, base, len, seg; int base1; if (ch->dir == PCMDIR_PLAY) { ado = &via->sgd_table[0]; base1 = via_rd(via, VIA_PLAY_DMAOPS_BASE, 4); len = via_rd(via, VIA_PLAY_DMAOPS_COUNT, 4); base = via_rd(via, VIA_PLAY_DMAOPS_BASE, 4); if (base != base1) { /* Avoid race hazzard */ len = via_rd(via, VIA_PLAY_DMAOPS_COUNT, 4); } 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 - vtophys(ado)) / sizeof(struct via_dma_op); if (seg == 0) seg = SEGS_PER_CHAN; /* Now work out offset: seg less count */ ptr = seg * ch->buffer->bufsize / SEGS_PER_CHAN - len; DEB(printf("return ptr=%d\n", ptr)); return ptr; } else { base1 = via_rd(via, VIA_RECORD_DMAOPS_BASE, 4); ado = &via->sgd_table[SEGS_PER_CHAN]; len = via_rd(via, VIA_RECORD_DMAOPS_COUNT, 4); base = via_rd(via, VIA_RECORD_DMAOPS_BASE, 4); if (base != base1) { /* Avoid race hazzard */ len = via_rd(via, VIA_RECORD_DMAOPS_COUNT, 4); } DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base)); /* Base points to next block to do, one past current */ /* Determine how many segments have been done */ seg = (base - vtophys(ado)) / sizeof(struct via_dma_op); if (seg == 0) seg = SEGS_PER_CHAN; /* Now work out offset: seg less count */ ptr = seg * ch->buffer->bufsize / SEGS_PER_CHAN - len; /* 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; } return 0; } static pcmchan_caps * viachan_getcaps(void *data) { struct via_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY) ? &via_playcaps : &via_reccaps; } static void via_intr(void *p) { struct via_info *via = p; int st; DEB(printf("viachan_intr\n")); /* Read channel */ st = via_rd(via, VIA_PLAY_STAT, 1); if (st & VIA_RPSTAT_INTR) { via_wr(via, VIA_PLAY_STAT, VIA_RPSTAT_INTR, 1); chn_intr(via->pch.channel); } /* Write channel */ st = via_rd(via, VIA_RECORD_STAT, 1); if (st & VIA_RPSTAT_INTR) { via_wr(via, VIA_RECORD_STAT, VIA_RPSTAT_INTR, 1); chn_intr(via->rch.channel); } } Index: head/sys/dev/sound/pcm/ac97.c =================================================================== --- head/sys/dev/sound/pcm/ac97.c (revision 65339) +++ head/sys/dev/sound/pcm/ac97.c (revision 65340) @@ -1,430 +1,451 @@ /* * 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$ */ #include #include struct ac97mixtable_entry { int reg:8; unsigned bits:4; unsigned ofs:4; unsigned stereo:1; unsigned mute:1; unsigned recidx:4; unsigned mask:1; }; struct ac97_info { device_t dev; ac97_init *init; ac97_read *read; ac97_write *write; void *devinfo; char id[4]; char rev; unsigned caps, se, extcaps, extid, extstat, noext:1; struct ac97mixtable_entry mix[32]; }; struct ac97_codecid { u_int32_t id, noext:1; char *name; }; static const struct ac97mixtable_entry ac97mixtable_default[32] = { [SOUND_MIXER_VOLUME] = { AC97_MIX_MASTER, 5, 0, 1, 1, 6, 0 }, [SOUND_MIXER_BASS] = { AC97_MIX_TONE, 4, 8, 0, 0, 0, 1 }, [SOUND_MIXER_TREBLE] = { AC97_MIX_TONE, 4, 0, 0, 0, 0, 1 }, [SOUND_MIXER_PCM] = { AC97_MIX_PCM, 5, 0, 1, 1, 0, 0 }, [SOUND_MIXER_SPEAKER] = { AC97_MIX_BEEP, 4, 1, 0, 1, 0, 0 }, [SOUND_MIXER_LINE] = { AC97_MIX_LINE, 5, 0, 1, 1, 5, 0 }, [SOUND_MIXER_MIC] = { AC97_MIX_MIC, 5, 0, 0, 1, 1, 0 }, [SOUND_MIXER_CD] = { AC97_MIX_CD, 5, 0, 1, 1, 2, 0 }, [SOUND_MIXER_LINE1] = { AC97_MIX_AUX, 5, 0, 1, 1, 4, 0 }, [SOUND_MIXER_VIDEO] = { AC97_MIX_VIDEO, 5, 0, 1, 1, 3, 0 }, [SOUND_MIXER_RECLEV] = { -AC97_MIX_RGAIN, 4, 0, 1, 1, 0, 0 } }; static const unsigned ac97mixdevs = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_LINE1 | SOUND_MASK_VIDEO | SOUND_MASK_RECLEV; static const unsigned ac97recdevs = SOUND_MASK_VOLUME | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_LINE1 | SOUND_MASK_VIDEO; static struct ac97_codecid ac97codecid[] = { { 0x414b4d00, 1, "Asahi Kasei AK4540 rev 0" }, { 0x43525900, 0, "Cirrus Logic CS4297" }, { 0x83847600, 0, "SigmaTel STAC????" }, { 0x83847604, 0, "SigmaTel STAC9701/3/4/5" }, { 0x83847605, 0, "SigmaTel STAC9704" }, { 0x83847608, 0, "SigmaTel STAC9708" }, { 0x83847609, 0, "SigmaTel STAC9721" }, { 0x414b4d01, 1, "Asahi Kasei AK4540 rev 1" }, { 0, 0, NULL } }; static char *ac97enhancement[] = { "no 3D Stereo Enhancement", "Analog Devices Phat Stereo", "Creative Stereo Enhancement", "National Semi 3D Stereo Enhancement", "Yamaha Ymersion", "BBE 3D Stereo Enhancement", "Crystal Semi 3D Stereo Enhancement", "Qsound QXpander", "Spatializer 3D Stereo Enhancement", "SRS 3D Stereo Enhancement", "Platform Tech 3D Stereo Enhancement", "AKM 3D Audio", "Aureal Stereo Enhancement", "Aztech 3D Enhancement", "Binaura 3D Audio Enhancement", "ESS Technology Stereo Enhancement", "Harman International VMAx", "Nvidea 3D Stereo Enhancement", "Philips Incredible Sound", "Texas Instruments 3D Stereo Enhancement", "VLSI Technology 3D Stereo Enhancement", "TriTech 3D Stereo Enhancement", "Realtek 3D Stereo Enhancement", "Samsung 3D Stereo Enhancement", "Wolfson Microelectronics 3D Enhancement", "Delta Integration 3D Enhancement", "SigmaTel 3D Enhancement", "Reserved 27", "Rockwell 3D Stereo Enhancement", "Reserved 29", "Reserved 30", "Reserved 31" }; static char *ac97feature[] = { "mic channel", "reserved", "tone", "simulated stereo", "headphone", "bass boost", "18 bit DAC", "20 bit DAC", "18 bit ADC", "20 bit ADC" }; static char *ac97extfeature[] = { "variable rate PCM", "double rate PCM", "reserved 1", "variable rate mic", "reserved 2", "reserved 3", "center DAC", "surround DAC", "LFE DAC", "AMAP", "reserved 4", "reserved 5", "reserved 6", "reserved 7", }; static u_int16_t rdcd(struct ac97_info *codec, int reg) { return codec->read(codec->devinfo, reg); } static void wrcd(struct ac97_info *codec, int reg, u_int16_t val) { codec->write(codec->devinfo, reg, val); } int ac97_setrate(struct ac97_info *codec, int which, int rate) { u_int16_t v; switch(which) { case AC97_REGEXT_FDACRATE: case AC97_REGEXT_SDACRATE: case AC97_REGEXT_LDACRATE: case AC97_REGEXT_LADCRATE: case AC97_REGEXT_MADCRATE: break; default: return -1; } if (rate != 0) { v = rate; if (codec->extstat & AC97_EXTCAP_DRA) v >>= 1; wrcd(codec, which, v); } v = rdcd(codec, which); if (codec->extstat & AC97_EXTCAP_DRA) v <<= 1; return v; } int ac97_setextmode(struct ac97_info *codec, u_int16_t mode) { mode &= AC97_EXTCAPS; if ((mode & ~codec->extcaps) != 0) return -1; wrcd(codec, AC97_REGEXT_STAT, mode); codec->extstat = rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS; return (mode == codec->extstat)? 0 : -1; } static int ac97_setrecsrc(struct ac97_info *codec, int channel) { struct ac97mixtable_entry *e = &codec->mix[channel]; if (e->recidx > 0) { int val = e->recidx - 1; val |= val << 8; wrcd(codec, AC97_REG_RECSEL, val); return 0; } else return -1; } static int ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right) { struct ac97mixtable_entry *e = &codec->mix[channel]; if (e->reg != 0) { int max, val, reg = (e->reg >= 0)? e->reg : -e->reg; if (!e->stereo) right = left; if (e->reg > 0) { left = 100 - left; right = 100 - right; } max = (1 << e->bits) - 1; left = (left * max) / 100; right = (right * max) / 100; val = (left << 8) | right; left = (left * 100) / max; right = (right * 100) / max; if (e->reg > 0) { left = 100 - left; right = 100 - right; } if (!e->stereo) { val &= max; val <<= e->ofs; if (e->mask) { int cur = rdcd(codec, e->reg); val |= cur & ~(max << e->ofs); } } if (left == 0 && right == 0 && e->mute == 1) val = AC97_MUTE; wrcd(codec, reg, val); return left | (right << 8); } else return -1; } #if 0 static int ac97_getmixer(struct ac97_info *codec, int channel) { struct ac97mixtable_entry *e = &codec->mix[channel]; if (channel < SOUND_MIXER_NRDEVICES && e->reg != 0) { int max, val, volume; max = (1 << e->bits) - 1; val = rdcd(code, e->reg); if (val == AC97_MUTE && e->mute == 1) volume = 0; else { if (e->stereo == 0) val >>= e->ofs; val &= max; volume = (val * 100) / max; if (e->reg > 0) volume = 100 - volume; } return volume; } else return -1; } #endif static unsigned ac97_initmixer(struct ac97_info *codec) { unsigned i, j; u_int32_t id; for (i = 0; i < 32; i++) codec->mix[i] = ac97mixtable_default[i]; if (codec->init) { if (codec->init(codec->devinfo)) { device_printf(codec->dev, "ac97 codec init failed\n"); return ENODEV; } } wrcd(codec, AC97_REG_POWER, 0); wrcd(codec, AC97_REG_RESET, 0); DELAY(100000); i = rdcd(codec, AC97_REG_RESET); codec->caps = i & 0x03ff; codec->se = (i & 0x7c00) >> 10; id = (rdcd(codec, AC97_REG_ID1) << 16) | rdcd(codec, AC97_REG_ID2); codec->rev = id & 0x000000ff; if (id == 0 || id == 0xffffffff) { device_printf(codec->dev, "ac97 codec invalid or not present (id == %x)\n", id); return ENODEV; } for (i = 0; ac97codecid[i].id; i++) if (ac97codecid[i].id == id) codec->noext = 1; if (!codec->noext) { i = rdcd(codec, AC97_REGEXT_ID); codec->extcaps = i & 0x3fff; codec->extid = (i & 0xc000) >> 14; codec->extstat = rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS; } else { codec->extcaps = 0; codec->extid = 0; codec->extstat = 0; } wrcd(codec, AC97_MIX_MASTER, 0x20); if ((rdcd(codec, AC97_MIX_MASTER) & 0x20) == 0x20) codec->mix[SOUND_MIXER_VOLUME].bits++; wrcd(codec, AC97_MIX_MASTER, 0x00); if (bootverbose) { device_printf(codec->dev, "ac97 codec id 0x%08x", id); for (i = 0; ac97codecid[i].id; i++) if (ac97codecid[i].id == id) printf(" (%s)", ac97codecid[i].name); printf("\n"); device_printf(codec->dev, "ac97 codec features "); for (i = j = 0; i < 10; i++) if (codec->caps & (1 << i)) printf("%s%s", j++? ", " : "", ac97feature[i]); printf("%s%d bit master volume", j++? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits); printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]); if (codec->extcaps != 0 || codec->extid) { device_printf(codec->dev, "ac97 %s codec", codec->extid? "secondary" : "primary"); if (codec->extcaps) printf(" extended features "); for (i = j = 0; i < 14; i++) if (codec->extcaps & (1 << i)) printf("%s%s", j++? ", " : "", ac97extfeature[i]); printf("\n"); } } if ((rdcd(codec, AC97_REG_POWER) & 2) == 0) device_printf(codec->dev, "ac97 codec reports dac not ready\n"); return 0; } struct ac97_info * ac97_create(device_t dev, void *devinfo, ac97_init *init, ac97_read *rd, ac97_write *wr) { struct ac97_info *codec; codec = (struct ac97_info *)malloc(sizeof *codec, M_DEVBUF, M_NOWAIT); if (codec != NULL) { codec->dev = dev; codec->init = init; codec->read = rd; codec->write = wr; codec->devinfo = devinfo; } return codec; } +void +ac97_destroy(struct ac97_info *codec) +{ + free(codec, M_DEVBUF); +} + static int ac97mix_init(snd_mixer *m) { struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; if (ac97_initmixer(codec)) return -1; mix_setdevs(m, ac97mixdevs | ((codec->caps & 4)? SOUND_MASK_BASS | SOUND_MASK_TREBLE : 0)); mix_setrecdevs(m, ac97recdevs); return 0; } static int +ac97mix_uninit(snd_mixer *m) +{ + struct ac97_info *codec = mix_getdevinfo(m); + if (codec == NULL) + return -1; + /* + if (ac97_uninitmixer(codec)) + return -1; + */ + ac97_destroy(codec); + return 0; +} + +static int ac97mix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; return ac97_setmixer(codec, dev, left, right); } static int ac97mix_setrecsrc(snd_mixer *m, u_int32_t src) { int i; struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if ((src & (1 << i)) != 0) break; return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1; } snd_mixer ac97_mixer = { "AC97 mixer", ac97mix_init, + ac97mix_uninit, ac97mix_set, ac97mix_setrecsrc, }; Index: head/sys/dev/sound/pcm/ac97.h =================================================================== --- head/sys/dev/sound/pcm/ac97.h (revision 65339) +++ head/sys/dev/sound/pcm/ac97.h (revision 65340) @@ -1,77 +1,78 @@ /* * 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$ */ #define AC97_MUTE 0x8000 #define AC97_REG_RESET 0x00 #define AC97_MIX_MASTER 0x02 #define AC97_MIX_PHONES 0x04 #define AC97_MIX_MONO 0x06 #define AC97_MIX_TONE 0x08 #define AC97_MIX_BEEP 0x0a #define AC97_MIX_PHONE 0x0c #define AC97_MIX_MIC 0x0e #define AC97_MIX_LINE 0x10 #define AC97_MIX_CD 0x12 #define AC97_MIX_VIDEO 0x14 #define AC97_MIX_AUX 0x16 #define AC97_MIX_PCM 0x18 #define AC97_REG_RECSEL 0x1a #define AC97_MIX_RGAIN 0x1c #define AC97_MIX_MGAIN 0x1e #define AC97_REG_GEN 0x20 #define AC97_REG_3D 0x22 #define AC97_REG_POWER 0x26 #define AC97_REGEXT_ID 0x28 #define AC97_EXTCAP_VRA (1 << 0) #define AC97_EXTCAP_DRA (1 << 1) #define AC97_EXTCAP_VRM (1 << 3) #define AC97_EXTCAPS (AC97_EXTCAP_VRA | AC97_EXTCAP_DRA | AC97_EXTCAP_VRM) #define AC97_REGEXT_STAT 0x2a #define AC97_REGEXT_FDACRATE 0x2c #define AC97_REGEXT_SDACRATE 0x2e #define AC97_REGEXT_LDACRATE 0x30 #define AC97_REGEXT_LADCRATE 0x32 #define AC97_REGEXT_MADCRATE 0x34 #define AC97_MIXEXT_CLFE 0x36 #define AC97_MIXEXT_SURROUND 0x38 #define AC97_REG_ID1 0x7c #define AC97_REG_ID2 0x7e typedef u_int32_t (ac97_init)(void *devinfo); typedef u_int32_t (ac97_read)(void *devinfo, int regno); typedef void (ac97_write)(void *devinfo, int regno, u_int32_t data); extern snd_mixer ac97_mixer; struct ac97_info; struct ac97_info *ac97_create(device_t dev, void *devinfo, ac97_init *init, ac97_read *rd, ac97_write *wr); +void ac97_destroy(struct ac97_info *codec); int ac97_setrate(struct ac97_info *codec, int which, int rate); int ac97_setextmode(struct ac97_info *codec, u_int16_t mode); Index: head/sys/dev/sound/pcm/channel.c =================================================================== --- head/sys/dev/sound/pcm/channel.c (revision 65339) +++ head/sys/dev/sound/pcm/channel.c (revision 65340) @@ -1,1334 +1,1356 @@ /* * 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. * * $FreeBSD$ */ #include #define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ #define DMA_ALIGN_THRESHOLD 4 #define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) #define ISA_DMA(b) (((b)->chan >= 0 && (b)->chan != 4 && (b)->chan < 8)) #define CANCHANGE(c) (!(c)->buffer.dl) #define ROUND(x) ((x) & DMA_ALIGN_MASK) /* #define DEB(x) x */ static void buf_clear(snd_dbuf *b, u_int32_t fmt, int length); static void chn_dmaupdate(pcm_channel *c); static void chn_wrintr(pcm_channel *c); static void chn_rdintr(pcm_channel *c); static u_int32_t chn_start(pcm_channel *c); /* * SOUND OUTPUT We use a circular buffer to store samples directed to the DAC. The buffer is split into two variable-size regions, each identified by an offset in the buffer (rp,fp) and a length (rl,fl): 0 rp,rl fp,fl bufsize |__________>____________>________| FREE d READY w FREE READY: data written from the process and ready to be sent to the DAC; FREE: free part of the buffer. Both regions can wrap around the end of the buffer. At initialization, READY is empty, FREE takes all the available space, and dma is idle. dl contains the length of the current DMA transfer, dl=0 means that the dma is idle. The two boundaries (rp,fp) in the buffers are advanced by DMA [d] and write() [w] operations. The first portion of the READY region is used for DMA transfers. The transfer is started at rp and with chunks of length dl. During DMA operations, dsp_wr_dmaupdate() updates rp, rl and fl tracking the ISA DMA engine as the transfer makes progress. When a new block is written, fp advances and rl,fl are updated accordingly. The code works as follows: the user write routine dsp_write_body() fills up the READY region with new data (reclaiming space from the FREE region) and starts the write DMA engine if inactive. When a DMA transfer is complete, an interrupt causes dsp_wrintr() to be called which extends the FREE region and possibly starts the next transfer. In some cases, the code tries to track the current status of DMA operations by calling dsp_wr_dmaupdate() which changes rp, rl and fl. The system tries to make all DMA transfers use the same size, play_blocksize or rec_blocksize. The size is either selected by the user, or computed by the system to correspond to about .25s of audio. The blocksize must be within a range which is currently: min(5ms, 40 bytes) ... 1/2 buffer size. When there aren't enough data (write) or space (read), a transfer is started with a reduced size. To reduce problems in case of overruns, the routine which fills up the buffer should initialize (e.g. by repeating the last value) a reasonably long area after the last block so that no noise is produced on overruns. * */ /* XXX this is broken: in the event a bounce buffer is used, data never * gets copied in or out of the real buffer. fix requires mods to isa_dma.c * and possibly fixes to other autodma mode clients */ static void chn_isadmabounce(pcm_channel *c) { if (ISA_DMA(&c->buffer)) { /* tell isa_dma to bounce data in/out */ } else KASSERT(1, ("chn_isadmabounce called on invalid channel")); } static int chn_polltrigger(pcm_channel *c) { snd_dbuf *bs = &c->buffer2nd; unsigned lim = (c->flags & CHN_F_HAS_SIZE)? bs->blksz : 0; int trig = 0; if (c->flags & CHN_F_MAPPED) trig = ((bs->int_count > bs->prev_int_count) || bs->prev_int_count == 0); else trig = (((c->direction == PCMDIR_PLAY)? bs->fl : bs->rl) > lim); return trig; } static int chn_pollreset(pcm_channel *c) { snd_dbuf *bs = &c->buffer2nd; if (c->flags & CHN_F_MAPPED) bs->prev_int_count = bs->int_count; return 1; } /* * chn_dmadone() updates pointers and wakes up any process waiting * on a select(). Must be called at spltty(). */ static void chn_dmadone(pcm_channel *c) { snd_dbuf *b = &c->buffer; if (c->direction == PCMDIR_PLAY) chn_checkunderflow(c); else chn_dmaupdate(c); if (ISA_DMA(b)) chn_isadmabounce(c); /* sync bounce buffer */ b->int_count++; } /* * chn_dmawakeup() wakes up any process sleeping. Separated from * chn_dmadone() so that wakeup occurs only when feed from a * secondary buffer to a DMA buffer takes place. Must be called * at spltty(). */ static void chn_dmawakeup(pcm_channel *c) { snd_dbuf *b = &c->buffer; wakeup(b); } /* * chn_dmaupdate() tracks the status of a dma transfer, * updating pointers. It must be called at spltty(). * * NOTE: when we are using auto dma in the device, rl might become * negative. */ DEB (static int chn_updatecount=0); static void chn_dmaupdate(pcm_channel *c) { snd_dbuf *b = &c->buffer; int delta, hwptr; DEB (int b_rl=b->rl; int b_fl=b->fl; int b_rp=b->rp; int b_fp=b->fp); hwptr = chn_getptr(c); delta = (b->bufsize + hwptr - b->hp) % b->bufsize; if (delta >= ((b->bufsize * 15) / 16)) { if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) device_printf(c->parent->dev, "hwptr went backwards %d -> %d\n", b->hp, hwptr); } if (c->direction == PCMDIR_PLAY) { delta = (b->bufsize + hwptr - b->rp) % b->bufsize; b->rp = hwptr; b->rl -= delta; b->fl += delta; if (b->rl < 0) { DEB(printf("OUCH!(%d) rl %d(%d) delta %d bufsize %d hwptr %d rp %d(%d)\n", chn_updatecount++, b->rl, b_rl, delta, b->bufsize, hwptr, b->rp, b_rp)); } } else { delta = (b->bufsize + hwptr - b->fp) % b->bufsize; b->fp = hwptr; b->rl += delta; b->fl -= delta; if (b->fl < 0) { DEB(printf("OUCH!(%d) fl %d(%d) delta %d bufsize %d hwptr %d fp %d(%d)\n", chn_updatecount++, b->fl, b_fl, delta, b->bufsize, hwptr, b->fp, b_fp)); } } b->hp = hwptr; b->total += delta; } /* * Check channel for underflow occured. Reset DMA buffer in case of * underflow, so that new data can go into the buffer. It must be * called at spltty(). */ void chn_checkunderflow(pcm_channel *c) { snd_dbuf *b = &c->buffer; if (b->underflow) { DEB(printf("Clear underflow condition\n")); /* * The DMA keeps running even after underflow occurs. * Hence the value returned by chn_getptr() here soon * gets a lag when we get back to chn_write(). Although * there are no easy and precise methods to figure out * the lag, a quarter of b->bufsize would be a fair * choice, provided that a DMA interrupt generates upon * each transfer of a half b->bufsize. */ b->rp = chn_getptr(c); b->fp = (b->rp + b->bufsize / 4) % b->bufsize; b->rl = b->bufsize / 4; b->fl = b->bufsize - b->rl; b->underflow = 0; } else { chn_dmaupdate(c); } } /* * Feeds new data to the write dma buffer. Can be called in the bottom half. * Hence must be called at spltty. */ int chn_wrfeed(pcm_channel *c) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; int a, l, lacc; /* ensure we always have a whole number of samples */ a = (1 << c->align) - 1; lacc = 0; if (c->flags & CHN_F_MAPPED) { bs->rl = min(b->blksz, b->fl); bs->fl = 0; a = 0; } /* printf("b: [rl: %d, rp %d, fl %d, fp %d]; bs: [rl: %d, rp %d, fl %d, fp %d]\n", b->rl, b->rp, b->fl, b->fp, bs->rl, bs->rp, bs->fl, bs->fp); */ /* Don't allow write unaligned data */ while (bs->rl > a && b->fl > a) { /* ensure we always have a whole number of samples */ l = min(min(bs->rl, bs->bufsize - bs->rp), min(b->fl, b->bufsize - b->fp)) & ~a; if (l == 0) return lacc; /* Move the samples, update the markers and pointers. */ bcopy(bs->buf + bs->rp, b->buf + b->fp, l); bs->fl += l; bs->rl -= l; bs->rp = (bs->rp + l) % bs->bufsize; b->rl += l; b->fl -= l; b->fp = (b->fp + l) % b->bufsize; /* Clear the new space in the secondary buffer. */ buf_clear(bs, bs->fmt, l); /* Accumulate the total bytes of the moved samples. */ lacc += l; /* A feed to the DMA buffer is equivalent to an interrupt. */ bs->total += l; if (c->flags & CHN_F_MAPPED) { if (bs->total - bs->prev_total >= bs->blksz) { bs->prev_total = bs->total; bs->int_count++; c->blocks++; } } else bs->int_count++; if (bs->sel.si_pid && chn_polltrigger(c)) selwakeup(&bs->sel); } return lacc; } /* Feeds new data to the secondary write buffer. */ static int chn_wrfeed2nd(pcm_channel *c, struct uio *buf) { snd_dbuf *bs = &c->buffer2nd; int l, w, wacc; /* The DMA buffer may have some space. */ while (chn_wrfeed(c) > 0); /* ensure we always have a whole number of samples */ wacc = 0; while (buf->uio_resid > 0 && bs->fl > 0) { /* * The size of the data to move here does not have to be * aligned. We take care of it upon moving the data to a * DMA buffer. */ l = min(bs->fl, bs->bufsize - bs->fp); /* Move the samples, update the markers and pointers. */ w = c->feeder->feed(c->feeder, c, bs->buf + bs->fp, l, buf); if (w == 0) panic("no feed"); bs->rl += w; bs->fl -= w; bs->fp = (bs->fp + w) % bs->bufsize; /* Accumulate the total bytes of the moved samples. */ wacc += w; /* If any pcm data gets moved, push it to the DMA buffer. */ if (w > 0) while (chn_wrfeed(c) > 0); } return wacc; } /* * Write interrupt routine. Can be called from other places (e.g. * to start a paused transfer), but with interrupts disabled. */ static void chn_wrintr(pcm_channel *c) { snd_dbuf *b = &c->buffer; if (b->underflow && !(c->flags & CHN_F_MAPPED)) { /* printf("underflow return\n"); */ return; /* nothing new happened */ } if (b->dl) chn_dmadone(c); /* * start another dma operation only if have ready data in the buffer, * there is no pending abort, have a full-duplex device, or have a * half duplex device and there is no pending op on the other side. * * Force transfers to be aligned to a boundary of 4, which is * needed when doing stereo and 16-bit. */ /* Check underflow and update the pointers. */ chn_checkunderflow(c); /* * Fill up the DMA buffer, followed by waking up the top half. * If some of the pcm data in uio are still left, the top half * goes to sleep by itself. */ if (c->flags & CHN_F_MAPPED) chn_wrfeed(c); else { while (chn_wrfeed(c) > 0); buf_clear(b, b->fmt, b->fl); } chn_dmawakeup(c); if (c->flags & CHN_F_TRIGGERED) { chn_dmaupdate(c); /* * check if we need to reprogram the DMA on the sound card. * This happens if the size has changed from zero */ if (b->dl == 0) { /* Start DMA operation */ b->dl = b->blksz; /* record new transfer size */ chn_trigger(c, PCMTRIG_START); } /* * Emulate writing by DMA, i.e. transfer the pcm data from * the emulated-DMA buffer to the device itself. */ chn_trigger(c, PCMTRIG_EMLDMAWR); if (b->rl < b->dl) { DEB(printf("near underflow (%d < %d), %d\n", b->rl, b->dl, b->fl)); /* * we are near to underflow condition, so to prevent * audio 'clicks' clear next b->fl bytes */ buf_clear(b, b->fmt, b->fl); if (b->rl < DMA_ALIGN_THRESHOLD) b->underflow = 1; } } else { /* cannot start a new dma transfer */ DEB(printf("underflow, flags 0x%08x rp %d rl %d\n", c->flags, b->rp, b->rl)); if (b->dl) { /* DMA was active */ b->underflow = 1; /* set underflow flag */ buf_clear(b, b->fmt, b->bufsize); } } } /* * user write routine * * advance the boundary between READY and FREE, fill the space with * uiomove(), and possibly start DMA. Do the above until the transfer * is complete. * * To minimize latency in case a pending DMA transfer is about to end, * we do the transfer in pieces of increasing sizes, extending the * READY area at every checkpoint. In the (necessary) assumption that * memory bandwidth is larger than the rate at which the dma consumes * data, we reduce the latency to something proportional to the length * of the first piece, while keeping the overhead low and being able * to feed the DMA with large blocks. */ int chn_write(pcm_channel *c, struct uio *buf) { int ret = 0, timeout, res, newsize, count; long s; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; if (c->flags & CHN_F_WRITING) { /* This shouldn't happen and is actually silly * - will never wake up, just timeout; why not sleep on b? */ tsleep(&s, PZERO, "pcmwrW", hz); return EBUSY; } c->flags |= CHN_F_WRITING; c->flags &= ~CHN_F_ABORTING; s = spltty(); /* * XXX Certain applications attempt to write larger size * of pcm data than c->blocksize2nd without blocking, * resulting partial write. Expand the block size so that * the write operation avoids blocking. */ if ((c->flags & CHN_F_NBIO) && buf->uio_resid > bs->blksz) { DEB(printf("pcm warning: broken app, nbio and tried to write %d bytes with fragsz %d\n", buf->uio_resid, bs->blksz)); newsize = 16; while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) newsize <<= 1; chn_setblocksize(c, bs->blkcnt, newsize); DEB(printf("pcm warning: frags reset to %d x %d\n", bs->blkcnt, bs->blksz)); } /* * Fill up the secondary and DMA buffer. * chn_wrfeed*() takes care of the alignment. */ /* Check for underflow before writing into the buffers. */ chn_checkunderflow(c); while (chn_wrfeed2nd(c, buf) > 0); if ((c->flags & CHN_F_NBIO) && (buf->uio_resid > 0)) ret = EAGAIN; /* Start playing if not yet. */ if (!b->dl) chn_start(c); if (ret == 0) { count = hz; /* Wait until all samples are played in blocking mode. */ while ((buf->uio_resid > 0) && (count > 0)) { /* Check for underflow before writing into the buffers. */ chn_checkunderflow(c); /* Fill up the buffers with new pcm data. */ res = buf->uio_resid; while (chn_wrfeed2nd(c, buf) > 0); if (buf->uio_resid < res) count = hz; else count--; /* Have we finished to feed the secondary buffer? */ if (buf->uio_resid == 0) break; /* Wait for new free space to write new pcm samples. */ /* splx(s); */ timeout = 1; /*(buf->uio_resid >= b->dl)? hz / 20 : 1; */ ret = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout); /* s = spltty(); */ /* if (ret == EINTR) chn_abort(c); */ if (ret == EINTR || ret == ERESTART) break; } if (count == 0) { c->flags |= CHN_F_DEAD; device_printf(c->parent->dev, "play interrupt timeout, channel dead\n"); } } else ret = 0; c->flags &= ~CHN_F_WRITING; splx(s); return ret; } /* * SOUND INPUT * The input part is similar to the output one, with a circular buffer split in two regions, and boundaries advancing because of read() calls [r] or dma operation [d]. At initialization, as for the write routine, READY is empty, and FREE takes all the space. 0 rp,rl fp,fl bufsize |__________>____________>________| FREE r READY d FREE Operation is as follows: upon user read (dsp_read_body()) a DMA read is started if not already active (marked by b->dl > 0), then as soon as data are available in the READY region they are transferred to the user buffer, thus advancing the boundary between FREE and READY. Upon interrupts, caused by a completion of a DMA transfer, the READY region is extended and possibly a new transfer is started. When necessary, dsp_rd_dmaupdate() is called to advance fp (and update rl,fl accordingly). Upon user reads, rp is advanced and rl,fl are updated accordingly. The rules to choose the size of the new DMA area are similar to the other case, with a preferred constant transfer size equal to rec_blocksize, and fallback to smaller sizes if no space is available. */ static int chn_rddump(pcm_channel *c, int cnt) { snd_dbuf *b = &c->buffer; int maxover, ss; ss = 1; ss <<= (b->fmt & AFMT_STEREO)? 1 : 0; ss <<= (b->fmt & AFMT_16BIT)? 1 : 0; maxover = c->speed * ss; b->overrun += cnt; if (b->overrun > maxover) { device_printf(c->parent->dev, "record overrun, dumping %d bytes\n", b->overrun); b->overrun = 0; } b->rl -= cnt; b->fl += cnt; b->rp = (b->rp + cnt) % b->bufsize; return cnt; } /* * Feed new data from the read buffer. Can be called in the bottom half. * Hence must be called at spltty. */ int chn_rdfeed(pcm_channel *c) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; int l, lacc; /* printf("b: [rl: %d, rp %d, fl %d, fp %d]; bs: [rl: %d, rp %d, fl %d, fp %d]\n", b->rl, b->rp, b->fl, b->fp, bs->rl, bs->rp, bs->fl, bs->fp); */ /* ensure we always have a whole number of samples */ lacc = 0; while (bs->fl >= DMA_ALIGN_THRESHOLD && b->rl >= DMA_ALIGN_THRESHOLD) { l = min(min(bs->fl, bs->bufsize - bs->fp), min(b->rl, b->bufsize - b->rp)) & DMA_ALIGN_MASK; /* Move the samples, update the markers and pointers. */ bcopy(b->buf + b->rp, bs->buf + bs->fp, l); bs->fl -= l; bs->rl += l; bs->fp = (bs->fp + l) % bs->bufsize; b->rl -= l; b->fl += l; b->rp = (b->rp + l) % b->bufsize; /* Accumulate the total bytes of the moved samples. */ lacc += l; /* A feed from the DMA buffer is equivalent to an interrupt. */ bs->int_count++; if (bs->sel.si_pid && chn_polltrigger(c)) selwakeup(&bs->sel); } return lacc; } /* Feeds new data from the secondary read buffer. */ static int chn_rdfeed2nd(pcm_channel *c, struct uio *buf) { snd_dbuf *bs = &c->buffer2nd; int l, w, wacc; /* ensure we always have a whole number of samples */ wacc = 0; while ((buf->uio_resid > 0) && (bs->rl > 0)) { /* The DMA buffer may have pcm data. */ /* while (chn_rdfeed(c) > 0); */ /* * The size of the data to move here does not have to be * aligned. We take care of it upon moving the data to a * DMA buffer. */ l = min(bs->rl, bs->bufsize - bs->rp); /* Move the samples, update the markers and pointers. */ w = c->feeder->feed(c->feeder, c, bs->buf + bs->rp, l, buf); if (w == 0) panic("no feed"); bs->fl += w; bs->rl -= w; bs->rp = (bs->rp + w) % bs->bufsize; /* Clear the new space in the secondary buffer. */ buf_clear(bs, bs->fmt, l); /* Accumulate the total bytes of the moved samples. */ bs->total += w; wacc += w; } return wacc; } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(pcm_channel *c) { snd_dbuf *b = &c->buffer; if (b->dl) chn_dmadone(c); DEB(printf("rdintr: start dl %d, rp:rl %d:%d, fp:fl %d:%d\n", b->dl, b->rp, b->rl, b->fp, b->fl)); /* Update the pointers. */ chn_dmaupdate(c); /* * Suck up the DMA buffer, followed by waking up the top half. * If some of the pcm data in the secondary buffer are still left, * the top half goes to sleep by itself. */ while(chn_rdfeed(c) > 0); chn_dmawakeup(c); if (b->fl < b->dl) { DEB(printf("near overflow (%d < %d), %d\n", b->fl, b->dl, b->rl)); chn_rddump(c, b->blksz - b->fl); } if (c->flags & CHN_F_TRIGGERED) { /* * check if we need to reprogram the DMA on the sound card. * This happens if the size has changed from zero */ if (b->dl == 0) { /* Start DMA operation */ b->dl = b->blksz; /* record new transfer size */ chn_trigger(c, PCMTRIG_START); } /* * Emulate writing by DMA, i.e. transfer the pcm data from * the emulated-DMA buffer to the device itself. */ chn_trigger(c, PCMTRIG_EMLDMARD); } else { if (b->dl) { /* was active */ b->dl = 0; chn_trigger(c, PCMTRIG_STOP); chn_dmaupdate(c); } } } /* * body of user-read routine * * Start DMA if not active; wait for READY not empty. * Transfer data from READY region using uiomove(), advance boundary * between FREE and READY. Repeat until transfer is complete. * * To avoid excessive latency in freeing up space for the DMA * engine, transfers are done in blocks of increasing size, so that * the latency is proportional to the size of the smallest block, but * we have a low overhead and are able to feed the dma engine with * large blocks. * * NOTE: in the current version, read will not return more than * blocksize bytes at once (unless more are already available), to * avoid that requests using very large buffers block for too long. */ int chn_read(pcm_channel *c, struct uio *buf) { int ret = 0, timeout, limit, res, count; long s; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; if (c->flags & CHN_F_READING) { /* This shouldn't happen and is actually silly */ tsleep(&s, PZERO, "pcmrdR", hz); return (EBUSY); } s = spltty(); /* Store the initial size in the uio. */ res = buf->uio_resid; c->flags |= CHN_F_READING; c->flags &= ~CHN_F_ABORTING; /* suck up the DMA and secondary buffers. */ while (chn_rdfeed2nd(c, buf) > 0); if (buf->uio_resid == 0) goto skip; limit = res - b->blksz; if (limit < 0) limit = 0; /* Start capturing if not yet. */ if ((!bs->rl || !b->rl) && !b->dl) chn_start(c); if (!(c->flags & CHN_F_NBIO)) { count = hz; /* Wait until all samples are captured. */ while ((buf->uio_resid > 0) && (count > 0)) { /* Suck up the DMA and secondary buffers. */ chn_dmaupdate(c); res = buf->uio_resid; while (chn_rdfeed(c) > 0); while (chn_rdfeed2nd(c, buf) > 0); if (buf->uio_resid < res) count = hz; else count--; /* Have we finished to feed the uio? */ if (buf->uio_resid == 0) break; /* Wait for new pcm samples. */ /* splx(s); */ timeout = (buf->uio_resid - limit >= b->dl)? hz / 20 : 1; ret = tsleep(b, PRIBIO | PCATCH, "pcmrd", 1); /* s = spltty(); */ /* if (ret == EINTR) chn_abort(c); */ if (ret == EINTR || ret == ERESTART) break; } if (count == 0) { c->flags |= CHN_F_DEAD; device_printf(c->parent->dev, "record interrupt timeout, channel dead\n"); } } else { /* If no pcm data was read on nonblocking, return EAGAIN. */ if (buf->uio_resid == res) ret = EAGAIN; } skip: c->flags &= ~CHN_F_READING; splx(s); return ret; } void chn_intr(pcm_channel *c) { if (c->flags & CHN_F_INIT) chn_reinit(c); if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); } u_int32_t chn_start(pcm_channel *c) { u_int32_t r, s; snd_dbuf *b = &c->buffer; r = 0; s = spltty(); if (b->dl == 0 && !(c->flags & (CHN_F_MAPPED | CHN_F_NOTRIGGER))) { if (c->direction == PCMDIR_PLAY) { /* Fill up the DMA buffer. */ while (chn_wrfeed(c) > 0); if (b->rl >= b->blksz) r = CHN_F_TRIGGERED; } else { /* Suck up the DMA buffer. */ while (chn_rdfeed(c) > 0); if (b->fl >= b->blksz) r = CHN_F_TRIGGERED; } c->flags |= r; chn_intr(c); } splx(s); return r; } static void chn_dma_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { snd_dbuf *b = (snd_dbuf *)arg; if (bootverbose) { printf("pcm: setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); printf("%p -> %lx\n", b->buf, (unsigned long)vtophys(b->buf)); } } /* * Allocate memory for DMA buffer. If the device do not perform DMA transfer, * the drvier can call malloc(9) by its own. */ int chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat) { - if (bus_dmamem_alloc(parent_dmat, (void **)&b->buf, + b->parent_dmat = parent_dmat; + if (bus_dmamem_alloc(b->parent_dmat, (void **)&b->buf, BUS_DMA_NOWAIT, &b->dmamap)) return -1; - if (bus_dmamap_load(parent_dmat, b->dmamap, b->buf, + if (bus_dmamap_load(b->parent_dmat, b->dmamap, b->buf, b->bufsize, chn_dma_setmap, b, 0)) return -1; return 0; } +void +chn_freebuf(snd_dbuf *b) +{ + bus_dmamem_free(b->parent_dmat, b->buf, b->dmamap); +} + static void buf_clear(snd_dbuf *b, u_int32_t fmt, int length) { int i; u_int16_t data, *p; if (length == 0) return; if (fmt & AFMT_SIGNED) data = 0x00; else data = 0x80; if (fmt & AFMT_16BIT) data <<= 8; else data |= data << 8; if (fmt & AFMT_BIGENDIAN) data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00); i = b->fp; p = (u_int16_t *)(b->buf + b->fp); while (length > 1) { *p++ = data; length -= 2; i += 2; if (i >= b->bufsize) { p = (u_int16_t *)b->buf; i = 0; } } if (length == 1) *(b->buf + i) = data & 0xff; } void chn_resetbuf(pcm_channel *c) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; c->blocks = 0; b->rp = b->fp = 0; b->dl = b->rl = 0; b->fl = b->bufsize; b->prev_total = b->total = 0; b->prev_int_count = b->int_count = 0; b->underflow = 0; if (b->buf && b->bufsize > 0) buf_clear(b, b->fmt, b->bufsize); bs->rp = bs->fp = 0; bs->dl = bs->rl = 0; bs->fl = bs->bufsize; bs->prev_total = bs->total = 0; bs->prev_int_count = bs->int_count = 0; bs->underflow = 0; if (bs->buf && bs->bufsize > 0) buf_clear(bs, bs->fmt, bs->bufsize); } void buf_isadma(snd_dbuf *b, int go) { if (ISA_DMA(b)) { switch (go) { case PCMTRIG_START: DEB(printf("buf 0x%p ISA DMA started\n", b)); isa_dmastart(b->dir | ISADMA_RAW, b->buf, b->bufsize, b->chan); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: DEB(printf("buf 0x%p ISA DMA stopped\n", b)); isa_dmastop(b->chan); isa_dmadone(b->dir | ISADMA_RAW, b->buf, b->bufsize, b->chan); break; } } else KASSERT(1, ("buf_isadma called on invalid channel")); } int buf_isadmaptr(snd_dbuf *b) { if (ISA_DMA(b)) { int i = b->dl? isa_dmastatus(b->chan) : b->bufsize; if (i < 0) i = 0; return b->bufsize - i; } else KASSERT(1, ("buf_isadmaptr called on invalid channel")); return -1; } /* * 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(pcm_channel *c, int threshold) { u_long s, rdy; int ret; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; for (;;) { s = spltty(); chn_checkunderflow(c); while (chn_wrfeed(c) > 0); rdy = (c->direction == PCMDIR_PLAY)? bs->fl : bs->rl; if (rdy <= threshold) { ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmsyn", 1); splx(s); if (ret == ERESTART || ret == EINTR) { DEB(printf("chn_sync: tsleep returns %d\n", ret)); return -1; } } else break; } splx(s); return 0; } int chn_poll(pcm_channel *c, int ev, struct proc *p) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; u_long s; int ret; s = spltty(); if (!(c->flags & CHN_F_MAPPED)) { if (c->direction == PCMDIR_PLAY) { /* Fill up the DMA buffer. */ chn_checkunderflow(c); while (chn_wrfeed(c) > 0); } else { /* Suck up the DMA buffer. */ chn_dmaupdate(c); while (chn_rdfeed(c) > 0); } if (!b->dl) chn_start(c); } ret = 0; if (chn_polltrigger(c) && chn_pollreset(c)) ret = ev; else selrecord(p, &bs->sel); splx(s); return ret; } /* * chn_abort is a non-blocking function which aborts a pending * DMA transfer and flushes the buffers. * It returns the number of bytes that have not been transferred. */ int chn_abort(pcm_channel *c) { int missing = 0, cnt = 0; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; if (!b->dl) return 0; c->flags |= CHN_F_ABORTING; c->flags &= ~CHN_F_TRIGGERED; cnt = 10; while (!b->underflow && (cnt-- > 0)) tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmabr", hz / 50); chn_trigger(c, PCMTRIG_ABORT); b->dl = 0; chn_dmaupdate(c); missing = bs->rl + b->rl; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close. We immediately abort any read DMA * operation, and then wait for the play buffer to drain. */ int chn_flush(pcm_channel *c) { int ret, count, s, resid, resid_p; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; DEB(printf("chn_flush c->flags 0x%08x\n", c->flags)); c->flags |= CHN_F_CLOSING; if (c->direction == PCMDIR_REC) chn_abort(c); else if (b->dl) { resid_p = resid = b->rl + bs->rl; count = 10; while ((count > 0) && (resid > 0) && !b->underflow) { /* still pending output data. */ ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmflu", hz / 10); if (ret == EINTR || ret == ERESTART) { DEB(printf("chn_flush: tsleep returns %d\n", ret)); return ret; } s = spltty(); chn_dmaupdate(c); splx(s); DEB(printf("chn_flush: now rl = %d, fl = %d\n", b->rl, b->fl)); resid = b->rl + bs->rl; if (resid >= resid_p) count--; resid_p = resid; } if (count == 0) DEB(printf("chn_flush: timeout flushing dbuf_out, cnt 0x%x flags 0x%x\n", b->rl, c->flags)); if (c->direction == PCMDIR_PLAY && b->dl) chn_abort(c); } c->flags &= ~CHN_F_CLOSING; return 0; } int chn_reset(pcm_channel *c, u_int32_t fmt) { int r = 0; chn_abort(c); c->flags &= CHN_F_RESET; r = chn_setblocksize(c, CHN_2NDBUFBLKNUM, CHN_2NDBUFBLKSIZE); if (r) return r; if (fmt) { r = chn_setformat(c, fmt); if (r == 0) r = chn_setspeed(c, DSP_DEFAULT_SPEED); if (r == 0) r = chn_setvolume(c, 100, 100); } chn_resetbuf(c); /* c->flags |= CHN_F_INIT; */ return 0; } int chn_reinit(pcm_channel *c) { if ((c->flags & CHN_F_INIT) && CANCHANGE(c)) { chn_setformat(c, c->format); chn_setspeed(c, c->speed); chn_setvolume(c, (c->volume >> 8) & 0xff, c->volume & 0xff); c->flags &= ~CHN_F_INIT; return 1; } return 0; } int chn_init(pcm_channel *c, void *devinfo, int dir) { snd_dbuf *bs = &c->buffer2nd; /* Initialize the hardware and DMA buffer first. */ c->feeder = malloc(sizeof(*(c->feeder)), M_DEVBUF, M_NOWAIT); *(c->feeder) = *feeder_getroot(); c->feederdesc = malloc(sizeof(*(c->feeder)), M_DEVBUF, M_NOWAIT); c->feederdesc->type = FEEDER_ROOT; c->feederdesc->in = 0; c->feederdesc->out = 0; c->feederdesc->flags = 0; c->feederdesc->idx = 0; c->feeder->desc = c->feederdesc; c->feeder->source = NULL; c->flags = 0; c->buffer.chan = -1; c->devinfo = c->init(devinfo, &c->buffer, c, dir); if (c->devinfo == NULL || c->buffer.bufsize == 0) return 1; chn_setdir(c, dir); /* And the secondary buffer. */ bs->buf = NULL; bs->bufsize = 0; + return 0; +} + +int +chn_kill(pcm_channel *c) +{ + chn_trigger(c, PCMTRIG_ABORT); + while (chn_removefeeder(c) == 0); + free(c->feeder->desc, M_DEVBUF); + free(c->feeder, M_DEVBUF); + if (c->free) + c->free(c->devinfo); + else + chn_freebuf(&c->buffer); + c->flags |= CHN_F_DEAD; return 0; } int chn_setdir(pcm_channel *c, int dir) { int r; c->direction = dir; r = c->setdir(c->devinfo, c->direction); if (!r && ISA_DMA(&c->buffer)) c->buffer.dir = (dir == PCMDIR_PLAY)? ISADMA_WRITE : ISADMA_READ; return r; } int chn_setvolume(pcm_channel *c, int left, int right) { /* could add a feeder for volume changing if channel returns -1 */ if (CANCHANGE(c)) { c->volume = (left << 8) | right; return 0; } c->volume = (left << 8) | right; c->flags |= CHN_F_INIT; return 0; } int chn_setspeed(pcm_channel *c, int speed) { if (speed <= 0) return EINVAL; /* could add a feeder for rate conversion */ if (CANCHANGE(c)) { c->speed = c->setspeed(c->devinfo, speed); return 0; } c->speed = speed; c->flags |= CHN_F_INIT; 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; } int chn_setformat(pcm_channel *c, u_int32_t fmt) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; u_int32_t hwfmt; if (CANCHANGE(c)) { while (chn_removefeeder(c) == 0); c->format = fmt; c->feederdesc->out = c->format; hwfmt = c->format; if (!fmtvalid(hwfmt, chn_getcaps(c)->fmtlist)) { if (c->flags & CHN_F_MAPPED) return EINVAL; hwfmt = chn_feedchain(c, chn_getcaps(c)->fmtlist); if (hwfmt == 0) return EINVAL; } b->fmt = hwfmt; bs->fmt = hwfmt; chn_resetbuf(c); c->setformat(c->devinfo, hwfmt); return 0; } c->format = fmt; c->flags |= CHN_F_INIT; return 0; } int chn_setblocksize(pcm_channel *c, int blkcnt, int blksz) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; int s, ss, bufsz; if (bs->blkcnt == blkcnt && bs->blksz == blksz) return 0; if (c->flags & CHN_F_MAPPED) { DEB(printf("chn_setblocksize: can't work on mapped channel")); return EINVAL; } c->flags &= ~CHN_F_HAS_SIZE; ss = 1; ss <<= (bs->fmt & AFMT_STEREO)? 1 : 0; ss <<= (bs->fmt & AFMT_16BIT)? 1 : 0; if (blksz >= 2) c->flags |= CHN_F_HAS_SIZE; /* let us specify blksz without setting CHN_F_HAS_SIZE */ if (blksz < 0) blksz = -blksz; /* default to blksz = ~0.25s */ if (blksz < 16) blksz = (ss * c->speed) >> 2; if (blksz > CHN_2NDBUFMAXSIZE / 2) blksz = CHN_2NDBUFMAXSIZE / 2; if (blkcnt < 2) blkcnt = 2; if (blkcnt * blksz > CHN_2NDBUFMAXSIZE) blkcnt = CHN_2NDBUFMAXSIZE / blksz; bufsz = blkcnt * blksz; s = spltty(); if (bs->buf != NULL) free(bs->buf, M_DEVBUF); bs->buf = malloc(bufsz, M_DEVBUF, M_WAITOK); if (bs->buf == NULL) { splx(s); DEB(printf("chn_setblocksize: out of memory.")); return ENOSPC; } bs->bufsize = bufsz; bs->rl = bs->rp = bs->fp = 0; bs->fl = bs->bufsize; buf_clear(bs, bs->fmt, bs->bufsize); bs->blkcnt = blkcnt; bs->blksz = blksz; RANGE(blksz, 16, b->bufsize / 2); b->blksz = c->setblocksize(c->devinfo, blksz); splx(s); return 0; } int chn_trigger(pcm_channel *c, int go) { return c->trigger(c->devinfo, go); } int chn_getptr(pcm_channel *c) { int hwptr; int a = (1 << c->align) - 1; snd_dbuf *b = &c->buffer; hwptr = b->dl? c->getptr(c->devinfo) : 0; /* don't allow unaligned values in the hwa ptr */ hwptr &= ~a ; /* Apply channel align mask */ hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ return hwptr; } pcmchan_caps * chn_getcaps(pcm_channel *c) { return c->getcaps(c->devinfo); } u_int32_t chn_getformats(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]; return fmts; } Index: head/sys/dev/sound/pcm/channel.h =================================================================== --- head/sys/dev/sound/pcm/channel.h (revision 65339) +++ head/sys/dev/sound/pcm/channel.h (revision 65340) @@ -1,98 +1,100 @@ /* * 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$ */ int chn_reinit(pcm_channel *c); int chn_write(pcm_channel *c, struct uio *buf); int chn_read(pcm_channel *c, struct uio *buf); int chn_sync(pcm_channel *c, int threshold); int chn_flush(pcm_channel *c); int chn_poll(pcm_channel *c, int ev, struct proc *p); int chn_init(pcm_channel *c, void *devinfo, int dir); +int chn_kill(pcm_channel *c); int chn_setdir(pcm_channel *c, int dir); int chn_reset(pcm_channel *c, u_int32_t fmt); int chn_setvolume(pcm_channel *c, int left, int right); int chn_setspeed(pcm_channel *c, int speed); int chn_setformat(pcm_channel *c, u_int32_t fmt); int chn_setblocksize(pcm_channel *c, int blkcnt, int blksz); int chn_trigger(pcm_channel *c, int go); int chn_getptr(pcm_channel *c); pcmchan_caps *chn_getcaps(pcm_channel *c); u_int32_t chn_getformats(pcm_channel *c); int chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat); +void chn_freebuf(snd_dbuf *b); void chn_resetbuf(pcm_channel *c); void chn_intr(pcm_channel *c); void chn_checkunderflow(pcm_channel *c); int chn_wrfeed(pcm_channel *c); int chn_rdfeed(pcm_channel *c); int chn_abort(pcm_channel *c); int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); void buf_isadma(snd_dbuf *b, int go); int buf_isadmaptr(snd_dbuf *b); #define PCMDIR_PLAY 1 #define PCMDIR_REC -1 #define PCMTRIG_START 1 #define PCMTRIG_EMLDMAWR 2 #define PCMTRIG_EMLDMARD 3 #define PCMTRIG_STOP 0 #define PCMTRIG_ABORT -1 #define CHN_F_READING 0x00000001 /* have a pending read */ #define CHN_F_WRITING 0x00000002 /* have a pending write */ #define CHN_F_CLOSING 0x00000004 /* a pending close */ #define CHN_F_ABORTING 0x00000008 /* a pending abort */ #define CHN_F_PENDING_IO (CHN_F_READING | CHN_F_WRITING) #define CHN_F_RUNNING 0x00000010 /* dma is running */ #define CHN_F_TRIGGERED 0x00000020 #define CHN_F_NOTRIGGER 0x00000040 #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_INIT 0x00008000 /* changed parameters. need init */ #define CHN_F_MAPPED 0x00010000 /* has been mmap()ed */ #define CHN_F_DEAD 0x00020000 #define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD) /* * 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 buffer. */ #define CHN_2NDBUFBLKNUM (32) /* The size of a whole secondary buffer. */ #define CHN_2NDBUFMAXSIZE (131072) Index: head/sys/dev/sound/pcm/datatypes.h =================================================================== --- head/sys/dev/sound/pcm/datatypes.h (revision 65339) +++ head/sys/dev/sound/pcm/datatypes.h (revision 65340) @@ -1,181 +1,195 @@ /* * 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$ */ typedef struct _snd_mixer snd_mixer; typedef struct _snd_dbuf snd_dbuf; typedef struct _snddev_info snddev_info; typedef struct _pcmchan_caps pcmchan_caps; typedef struct _pcm_feeder pcm_feeder; typedef struct _pcm_channel pcm_channel; typedef int (mix_set_t)(snd_mixer *m, unsigned dev, unsigned left, unsigned right); typedef int (mix_recsrc_t)(snd_mixer *m, u_int32_t src); typedef int (mix_init_t)(snd_mixer *m); +typedef int (mix_uninit_t)(snd_mixer *m); struct _snd_mixer { char name[64]; mix_init_t *init; + mix_uninit_t *uninit; mix_set_t *set; mix_recsrc_t *setrecsrc; void *devinfo; + int busy; u_int32_t devs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; }; /* * descriptor of a dma buffer. See dmabuf.c for documentation. * (rp,rl) and (fp,fl) identify the READY and FREE regions of the * buffer. dl contains the length used for dma transfer, dl>0 also * means that the channel is busy and there is a DMA transfer in progress. */ struct _snd_dbuf { u_int8_t *buf; int bufsize; volatile int dl; /* transfer size */ volatile int rp, fp; /* pointers to the ready and free area */ volatile int rl, fl; /* lenght of ready and free areas. */ volatile int hp; volatile u_int32_t int_count, prev_int_count; volatile u_int32_t total, prev_total; int chan, dir; /* dma channel */ int fmt, blksz, blkcnt; int underflow, overrun; bus_dmamap_t dmamap; + bus_dma_tag_t parent_dmat; struct selinfo sel; }; typedef int (pcmfeed_init_t)(pcm_feeder *feeder); typedef int (pcmfeed_free_t)(pcm_feeder *feeder); typedef int (pcmfeed_feed_t)(pcm_feeder *feeder, pcm_channel *c, u_int8_t *buffer, u_int32_t count, struct uio *stream); #define FEEDER_ROOT 1 #define FEEDER_FMT 2 #define FEEDER_RATE 3 #define FEEDER_FILTER 4 struct pcm_feederdesc { u_int32_t type; u_int32_t in, out; u_int32_t flags; int idx; }; #define MAXFEEDERS 256 struct _pcm_feeder { char name[16]; int align; struct pcm_feederdesc *desc; pcmfeed_init_t *init; pcmfeed_free_t *free; pcmfeed_feed_t *feed; void *data; pcm_feeder *source; }; struct _pcmchan_caps { u_int32_t minspeed, maxspeed; u_int32_t *fmtlist; u_int32_t caps; }; typedef void *(pcmchan_init_t)(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); typedef int (pcmchan_setdir_t)(void *data, int dir); typedef int (pcmchan_setformat_t)(void *data, u_int32_t format); typedef int (pcmchan_setspeed_t)(void *data, u_int32_t speed); typedef int (pcmchan_setblocksize_t)(void *data, u_int32_t blocksize); typedef int (pcmchan_trigger_t)(void *data, int go); typedef int (pcmchan_getptr_t)(void *data); +typedef int (pcmchan_free_t)(void *data); typedef pcmchan_caps *(pcmchan_getcaps_t)(void *data); struct _pcm_channel { pcmchan_init_t *init; pcmchan_setdir_t *setdir; pcmchan_setformat_t *setformat; pcmchan_setspeed_t *setspeed; pcmchan_setblocksize_t *setblocksize; pcmchan_trigger_t *trigger; pcmchan_getptr_t *getptr; pcmchan_getcaps_t *getcaps; + pcmchan_free_t *free; + void *nop1; + void *nop2; + void *nop3; + void *nop4; + void *nop5; + void *nop6; + void *nop7; + pcm_feeder *feeder; struct pcm_feederdesc *feederdesc; u_int32_t align; int volume; u_int32_t speed; u_int32_t flags; u_int32_t format; u_int32_t blocks; int direction; snd_dbuf buffer, buffer2nd; snddev_info *parent; void *devinfo; }; typedef void (pcm_swap_t)(void *data, int dir); #define SND_STATUSLEN 64 /* descriptor of audio device */ struct _snddev_info { pcm_channel *play, *rec, **aplay, **arec, fakechan; int *ref; - unsigned playcount, reccount, chancount; + unsigned playcount, reccount, chancount, maxchans; snd_mixer mixer; u_long magic; unsigned flags; void *devinfo; pcm_swap_t *swap; device_t dev; char status[SND_STATUSLEN]; }; /* mixer description structure and macros - these should go away, * only sb.[ch] and mss.[ch] use them */ struct mixer_def { u_int regno:7; u_int polarity:1; /* 1 means reversed */ u_int bitoffs:4; u_int nbits:4; }; typedef struct mixer_def mixer_ent; typedef struct mixer_def mixer_tab[32][2]; #define MIX_ENT(name, reg_l, pol_l, pos_l, len_l, reg_r, pol_r, pos_r, len_r) \ {{reg_l, pol_l, pos_l, len_l}, {reg_r, pol_r, pos_r, len_r}} #define PMIX_ENT(name, reg_l, pos_l, len_l, reg_r, pos_r, len_r) \ {{reg_l, 0, pos_l, len_l}, {reg_r, 0, pos_r, len_r}} #define MIX_NONE(name) MIX_ENT(name, 0,0,0,0, 0,0,0,0) Index: head/sys/dev/sound/pcm/mixer.c =================================================================== --- head/sys/dev/sound/pcm/mixer.c (revision 65339) +++ head/sys/dev/sound/pcm/mixer.c (revision 65340) @@ -1,217 +1,246 @@ /* * 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$ */ #include static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = 75, [SOUND_MIXER_BASS] = 50, [SOUND_MIXER_TREBLE] = 50, [SOUND_MIXER_SYNTH] = 75, [SOUND_MIXER_PCM] = 75, [SOUND_MIXER_SPEAKER] = 75, [SOUND_MIXER_LINE] = 75, [SOUND_MIXER_MIC] = 0, [SOUND_MIXER_CD] = 75, [SOUND_MIXER_LINE1] = 75, [SOUND_MIXER_VIDEO] = 75, [SOUND_MIXER_RECLEV] = 0, [SOUND_MIXER_OGAIN] = 50, }; int -mixer_init(snddev_info *d, snd_mixer *m, void *devinfo) +mixer_init(device_t dev, snd_mixer *m, void *devinfo) { + snddev_info *d = device_get_softc(dev); if (d == NULL) return -1; d->mixer = *m; d->mixer.devinfo = devinfo; bzero(&d->mixer.level, sizeof d->mixer.level); if (d->mixer.init != NULL && d->mixer.init(&d->mixer) == 0) { int i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { u_int16_t v = snd_mixerdefaults[i]; mixer_set(d, i, v | (v << 8)); } mixer_setrecsrc(d, SOUND_MASK_MIC); return 0; } else return -1; } int -mixer_reinit(snddev_info *d) +mixer_uninit(device_t dev) { int i; + snddev_info *d = device_get_softc(dev); if (d == NULL) return -1; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + mixer_set(d, i, 0); + mixer_setrecsrc(d, SOUND_MASK_MIC); + if (d->mixer.uninit != NULL) d->mixer.uninit(&d->mixer); + return 0; +} + +int +mixer_reinit(device_t dev) +{ + int i; + snddev_info *d = device_get_softc(dev); + if (d == NULL) return -1; if (d->mixer.init != NULL && d->mixer.init(&d->mixer) == 0) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(d, i, d->mixer.level[i]); mixer_setrecsrc(d, d->mixer.recsrc); return 0; } else return -1; } -int +static int mixer_set(snddev_info *d, unsigned dev, unsigned lev) { if (d == NULL || d->mixer.set == NULL) return -1; if ((dev < SOUND_MIXER_NRDEVICES) && (d->mixer.devs & (1 << dev))) { unsigned l = min((lev & 0x00ff), 100); unsigned r = min(((lev & 0xff00) >> 8), 100); int v = d->mixer.set(&d->mixer, dev, l, r); if (v >= 0) d->mixer.level[dev] = l | (r << 8); return 0; } else return -1; } -int +static int mixer_get(snddev_info *d, int dev) { if (d == NULL) return -1; if (dev < SOUND_MIXER_NRDEVICES && (d->mixer.devs & (1 << dev))) return d->mixer.level[dev]; else return -1; } -int +static int mixer_setrecsrc(snddev_info *d, u_int32_t src) { if (d == NULL || d->mixer.setrecsrc == NULL) return -1; src &= d->mixer.recdevs; if (src == 0) src = SOUND_MASK_MIC; d->mixer.recsrc = d->mixer.setrecsrc(&d->mixer, src); return 0; } -int +static int mixer_getrecsrc(snddev_info *d) { if (d == NULL) return -1; return d->mixer.recsrc; } int mixer_ioctl(snddev_info *d, u_long cmd, caddr_t arg) { int ret, *arg_i = (int *)arg; if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { int j = cmd & 0xff; if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(d, *arg_i); else ret = mixer_set(d, j, *arg_i); return (ret == 0)? 0 : ENXIO; } if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { int v = -1, j = cmd & 0xff; switch (j) { case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: v = d->mixer.devs; break; case SOUND_MIXER_RECMASK: v = d->mixer.recdevs; break; case SOUND_MIXER_RECSRC: v = mixer_getrecsrc(d); break; default: v = mixer_get(d, j); } *arg_i = v; return (v != -1)? 0 : ENXIO; } return ENXIO; +} + +int +mixer_busy(snddev_info *d, int busy) +{ + if (d == NULL) return -1; + d->mixer.busy = busy; +} + +int +mixer_isbusy(snddev_info *d) +{ + if (d == NULL) return -1; + return d->mixer.busy; } void mix_setdevs(snd_mixer *m, u_int32_t v) { m->devs = v; } void mix_setrecdevs(snd_mixer *m, u_int32_t v) { m->recdevs = v; } u_int32_t mix_getdevs(snd_mixer *m) { return m->devs; } u_int32_t mix_getrecdevs(snd_mixer *m) { return m->recdevs; } void * mix_getdevinfo(snd_mixer *m) { return m->devinfo; } /* * 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. */ 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 */ } Index: head/sys/dev/sound/pcm/mixer.h =================================================================== --- head/sys/dev/sound/pcm/mixer.h (revision 65339) +++ head/sys/dev/sound/pcm/mixer.h (revision 65340) @@ -1,43 +1,42 @@ /* * 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$ */ -extern int mixer_init(snddev_info *d, snd_mixer *m, void *devinfo); -extern int mixer_reinit(snddev_info *d); -extern int mixer_set(snddev_info *d, unsigned dev, unsigned lev); -extern int mixer_get(snddev_info *d, int dev); -extern int mixer_setrecsrc(snddev_info *d, u_int32_t src); -extern int mixer_getrecsrc(snddev_info *d); +extern int mixer_init(device_t dev, snd_mixer *m, void *devinfo); +extern int mixer_uninit(device_t dev); +extern int mixer_reinit(device_t dev); extern int mixer_ioctl(snddev_info *d, u_long cmd, caddr_t arg); +extern int mixer_busy(snddev_info *d, int busy); +extern int mixer_isbusy(snddev_info *d); extern void change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval); void mix_setdevs(snd_mixer *m, u_int32_t v); void mix_setrecdevs(snd_mixer *m, u_int32_t v); u_int32_t mix_getdevs(snd_mixer *m); u_int32_t mix_getrecdevs(snd_mixer *m); void *mix_getdevinfo(snd_mixer *m); Index: head/sys/dev/sound/pcm/sound.c =================================================================== --- head/sys/dev/sound/pcm/sound.c (revision 65339) +++ head/sys/dev/sound/pcm/sound.c (revision 65340) @@ -1,560 +1,659 @@ /* * Copyright (c) 1999 Cameron Grant * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) * 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$ */ #include #include #include "opt_devfs.h" +static dev_t status_dev = 0; static int status_isopen = 0; static int status_init(char *buf, int size); static int status_read(struct uio *buf); -MODULE_VERSION(snd_pcm, PCM_MODVER); - static d_open_t sndopen; static d_close_t sndclose; static d_ioctl_t sndioctl; static d_read_t sndread; static d_write_t sndwrite; static d_mmap_t sndmmap; static d_poll_t sndpoll; #define CDEV_MAJOR 30 static struct cdevsw snd_cdevsw = { /* open */ sndopen, /* close */ sndclose, /* read */ sndread, /* write */ sndwrite, /* ioctl */ sndioctl, /* poll */ sndpoll, /* mmap */ sndmmap, /* strategy */ nostrategy, /* name */ "snd", /* maj */ CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; /* PROPOSAL: each unit needs: status, mixer, dsp, dspW, audio, sequencer, midi-in, seq2, sndproc = 9 devices dspW and audio are deprecated. dsp needs min 64 channels, will give it 256 minor = (unit << 20) + (dev << 16) + channel currently minor = (channel << 16) + (unit << 4) + dev nomenclature: /dev/pcmX/dsp.(0..255) /dev/pcmX/dspW /dev/pcmX/audio /dev/pcmX/status /dev/pcmX/mixer [etc.] */ #define PCMMINOR(x) (minor(x)) #define PCMCHAN(x) ((PCMMINOR(x) & 0x00ff0000) >> 16) #define PCMUNIT(x) ((PCMMINOR(x) & 0x000000f0) >> 4) #define PCMDEV(x) (PCMMINOR(x) & 0x0000000f) #define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) static devclass_t pcm_devclass; #ifdef DEVFS int snd_unit; TUNABLE_INT_DECL("hw.sndunit", 0, snd_unit); #endif -static snddev_info * -gsd(int unit) +#ifdef DEVFS +static void +pcm_makelinks(void *dummy) { - return devclass_get_softc(pcm_devclass, unit); + int unit; + dev_t pdev; + static dev_t dsp = 0, dspW = 0, audio = 0, mixer = 0; + + if (pcm_devclass == NULL) + return; + if (dsp) { + destroy_dev(dsp); + dsp = 0; + } + if (dspW) { + destroy_dev(dspW); + dspW = 0; + } + if (audio) { + destroy_dev(audio); + audio = 0; + } + if (mixer) { + destroy_dev(mixer); + mixer = 0; + } + + unit = snd_unit; + if (unit < 0 || unit > devclass_get_maxunit(pcm_devclass)) + return; + if (devclass_get_softc(pcm_devclass, unit) == NULL) + return; + + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, 0)); + dsp = make_dev_alias(pdev, "dsp"); + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP16, 0)); + dspW = make_dev_alias(pdev, "dspW"); + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_AUDIO, 0)); + audio = make_dev_alias(pdev, "audio"); + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_CTL, 0)); + mixer = make_dev_alias(pdev, "mixer"); } +static int +sysctl_hw_sndunit(SYSCTL_HANDLER_ARGS) +{ + int error, unit; + + unit = snd_unit; + error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); + if (error == 0 && req->newptr != NULL) { + snd_unit = unit; + pcm_makelinks(NULL); + } + return (error); +} +SYSCTL_PROC(_hw, OID_AUTO, sndunit, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_sndunit, "I", ""); +#endif + int pcm_addchan(device_t dev, int dir, pcm_channel *templ, void *devinfo) { - int unit = device_get_unit(dev); + int unit = device_get_unit(dev), idx; snddev_info *d = device_get_softc(dev); - pcm_channel *ch; + pcm_channel *chns, *ch; + char *dirs; - if (((dir == PCMDIR_PLAY)? d->play : d->rec) == NULL) { - device_printf(dev, "bad channel add (%s)\n", - (dir == PCMDIR_PLAY)? "play" : "record"); + dirs = ((dir == PCMDIR_PLAY)? "play" : "record"); + chns = ((dir == PCMDIR_PLAY)? d->play : d->rec); + idx = ((dir == PCMDIR_PLAY)? d->playcount++ : d->reccount++); + + if (chns == NULL) { + device_printf(dev, "bad channel add (%s:%d)\n", dirs, idx); return 1; } - ch = (dir == PCMDIR_PLAY)? &d->play[d->playcount] : &d->rec[d->reccount]; + ch = &chns[idx]; *ch = *templ; ch->parent = d; if (chn_init(ch, devinfo, dir)) { - device_printf(dev, "chn_init() for %s:%d failed\n", - (dir == PCMDIR_PLAY)? "play" : "record", - (dir == PCMDIR_PLAY)? d->playcount : d->reccount); + device_printf(dev, "chn_init() for (%s:%d) failed\n", dirs, idx); return 1; } - if (dir == PCMDIR_PLAY) d->playcount++; else d->reccount++; make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_DSP, d->chancount), UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", unit, d->chancount); - make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_AUDIO, d->chancount), - UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", unit, d->chancount); make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_DSP16, d->chancount), UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", unit, d->chancount); + make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_AUDIO, d->chancount), + UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", unit, d->chancount); /* XXX SND_DEV_NORESET? */ d->chancount++; +#ifdef DEVFS + if (d->chancount == d->maxchans) + pcm_makelinks(NULL); +#endif return 0; } +static int +pcm_killchan(device_t dev, int dir) +{ + int unit = device_get_unit(dev), idx; + snddev_info *d = device_get_softc(dev); + pcm_channel *chns, *ch; + char *dirs; + dev_t pdev; + + dirs = ((dir == PCMDIR_PLAY)? "play" : "record"); + chns = ((dir == PCMDIR_PLAY)? d->play : d->rec); + idx = ((dir == PCMDIR_PLAY)? --d->playcount : --d->reccount); + + if (chns == NULL || idx < 0) { + device_printf(dev, "bad channel kill (%s:%d)\n", dirs, idx); + return 1; + } + ch = &chns[idx]; + if (chn_kill(ch)) { + device_printf(dev, "chn_kill() for (%s:%d) failed\n", dirs, idx); + return 1; + } + d->chancount--; + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, d->chancount)); + destroy_dev(pdev); + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP16, d->chancount)); + destroy_dev(pdev); + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_AUDIO, d->chancount)); + destroy_dev(pdev); + return 0; +} + int pcm_setstatus(device_t dev, char *str) { snddev_info *d = device_get_softc(dev); strncpy(d->status, str, SND_STATUSLEN); return 0; } u_int32_t pcm_getflags(device_t dev) { snddev_info *d = device_get_softc(dev); return d->flags; } void pcm_setflags(device_t dev, u_int32_t val) { snddev_info *d = device_get_softc(dev); d->flags = val; } void * pcm_getdevinfo(device_t dev) { snddev_info *d = device_get_softc(dev); return d->devinfo; } void pcm_setswap(device_t dev, pcm_swap_t *swap) { snddev_info *d = device_get_softc(dev); d->swap = swap; } + /* This is the generic init routine */ int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { int sz, unit = device_get_unit(dev); snddev_info *d = device_get_softc(dev); if (!pcm_devclass) { pcm_devclass = device_get_devclass(dev); - make_dev(&snd_cdevsw, PCMMKMINOR(0, SND_DEV_STATUS, 0), + status_dev = make_dev(&snd_cdevsw, PCMMKMINOR(0, SND_DEV_STATUS, 0), UID_ROOT, GID_WHEEL, 0444, "sndstat"); } make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); d->dev = dev; d->devinfo = devinfo; d->chancount = d->playcount = d->reccount = 0; + d->maxchans = numplay + numrec; sz = (numplay + numrec) * sizeof(pcm_channel *); if (sz > 0) { d->aplay = (pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT); if (!d->aplay) goto no; bzero(d->aplay, sz); d->arec = (pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT); if (!d->arec) goto no; bzero(d->arec, sz); sz = (numplay + numrec) * sizeof(int); d->ref = (int *)malloc(sz, M_DEVBUF, M_NOWAIT); if (!d->ref) goto no; bzero(d->ref, sz); } if (numplay > 0) { d->play = (pcm_channel *)malloc(numplay * sizeof(pcm_channel), M_DEVBUF, M_NOWAIT); if (!d->play) goto no; bzero(d->play, numplay * sizeof(pcm_channel)); } else d->play = NULL; if (numrec > 0) { d->rec = (pcm_channel *)malloc(numrec * sizeof(pcm_channel), M_DEVBUF, M_NOWAIT); if (!d->rec) goto no; bzero(d->rec, numrec * sizeof(pcm_channel)); } else d->rec = NULL; if (numplay == 0 || numrec == 0) d->flags |= SD_F_SIMPLEX; fkchan_setup(&d->fakechan); chn_init(&d->fakechan, NULL, 0); d->magic = MAGIC(unit); /* debugging... */ d->swap = NULL; return 0; no: if (d->aplay) free(d->aplay, M_DEVBUF); if (d->play) free(d->play, M_DEVBUF); if (d->arec) free(d->arec, M_DEVBUF); if (d->rec) free(d->rec, M_DEVBUF); + if (d->ref) free(d->ref, M_DEVBUF); return ENXIO; } -#ifdef DEVFS -static void -pcm_makelinks(void *dummy) +int +pcm_unregister(device_t dev) { - int unit; + int r, i, unit = device_get_unit(dev); + snddev_info *d = device_get_softc(dev); dev_t pdev; - static dev_t dsp = 0, dspW = 0, audio = 0, mixer = 0; - unit = snd_unit; - if (unit > devclass_get_maxunit(pcm_devclass)) - unit = -1; + r = 0; + for (i = 0; i < d->chancount; i++) + if (d->ref[i]) r = EBUSY; + if (r) return r; + if (mixer_isbusy(d) || status_isopen) return EBUSY; - if (dsp) { - destroy_dev(dsp); - dsp = 0; - } - if (dspW) { - destroy_dev(dspW); - dspW = 0; - } - if (audio) { - destroy_dev(audio); - audio = 0; - } - if (mixer) { - destroy_dev(mixer); - mixer = 0; - } + pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_CTL, 0)); + destroy_dev(pdev); + mixer_uninit(dev); - if (unit >= 0) { - pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, 0)); - dsp = make_dev_alias(pdev, "dsp"); - pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP16, 0)); - dspW = make_dev_alias(pdev, "dspW"); - pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_AUDIO, 0)); - audio = make_dev_alias(pdev, "audio"); - pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_CTL, 0)); - mixer = make_dev_alias(pdev, "mixer"); - } -} -SYSINIT(pcm_makelinks, SI_SUB_MOUNT_ROOT, SI_ORDER_ANY, pcm_makelinks, NULL); + while (d->playcount > 0) + pcm_killchan(dev, PCMDIR_PLAY); + while (d->reccount > 0) + pcm_killchan(dev, PCMDIR_REC); + d->magic = 0; -static int -sysctl_hw_sndunit(SYSCTL_HANDLER_ARGS) -{ - int error, unit; + if (d->aplay) free(d->aplay, M_DEVBUF); + if (d->play) free(d->play, M_DEVBUF); + if (d->arec) free(d->arec, M_DEVBUF); + if (d->rec) free(d->rec, M_DEVBUF); + if (d->ref) free(d->ref, M_DEVBUF); - unit = snd_unit; - error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); - if (error == 0 && req->newptr != NULL) { - snd_unit = unit; - pcm_makelinks(NULL); - } - return (error); -} -SYSCTL_PROC(_hw, OID_AUTO, sndunit, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_sndunit, "I", ""); +#ifdef DEVFS + pcm_makelinks(NULL); #endif + return 0; +} /* * a small utility function which, given a device number, returns * a pointer to the associated snddev_info struct, and sets the unit * number. */ static snddev_info * get_snddev_info(dev_t i_dev, int *unit, int *dev, int *chan) { + snddev_info *sc; int u, d, c; u = PCMUNIT(i_dev); d = PCMDEV(i_dev); c = PCMCHAN(i_dev); if (u > devclass_get_maxunit(pcm_devclass)) u = -1; if (unit) *unit = u; if (dev) *dev = d; if (chan) *chan = c; if (u < 0) return NULL; - switch(d) { + sc = devclass_get_softc(pcm_devclass, u); + if (sc == NULL || sc->magic == 0) return NULL; + + switch(d) { case SND_DEV_CTL: /* /dev/mixer handled by pcm */ case SND_DEV_STATUS: /* /dev/sndstat handled by pcm */ case SND_DEV_DSP: case SND_DEV_DSP16: case SND_DEV_AUDIO: - return gsd(u); + return sc; case SND_DEV_SEQ: /* XXX when enabled... */ case SND_DEV_SEQ2: case SND_DEV_MIDIN: case SND_DEV_SNDPROC: /* /dev/sndproc handled by pcm */ default: printf("unsupported subdevice %d\n", d); return NULL; } } static int sndopen(dev_t i_dev, int flags, int mode, struct proc *p) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("open snd%d subdev %d flags 0x%08x mode 0x%08x\n", unit, dev, flags, mode)); switch(dev) { case SND_DEV_STATUS: if (status_isopen) return EBUSY; status_isopen = 1; return 0; case SND_DEV_CTL: - return d? 0 : ENXIO; + return d? mixer_busy(d, 1) : ENXIO; case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: case SND_DEV_NORESET: return d? dsp_open(d, chan, flags, dev) : ENXIO; default: return ENXIO; } } static int sndclose(dev_t i_dev, int flags, int mode, struct proc *p) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("close snd%d subdev %d\n", unit, dev)); switch(dev) { /* only those for which close makes sense */ case SND_DEV_STATUS: if (!status_isopen) return EBADF; status_isopen = 0; return 0; case SND_DEV_CTL: - return d? 0 : ENXIO; + return d? mixer_busy(d, 0) : ENXIO; case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return d? dsp_close(d, chan, dev) : ENXIO; default: return ENXIO; } } static int sndread(dev_t i_dev, struct uio *buf, int flag) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("read snd%d subdev %d flag 0x%08x\n", unit, dev, flag)); switch(dev) { case SND_DEV_STATUS: return status_isopen? status_read(buf) : EBADF; case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return d? dsp_read(d, chan, buf, flag) : EBADF; default: return ENXIO; } } static int sndwrite(dev_t i_dev, struct uio *buf, int flag) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("write snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); switch(dev) { /* only writeable devices */ case SND_DEV_DSP: case SND_DEV_DSP16: case SND_DEV_AUDIO: return d? dsp_write(d, chan, buf, flag) : EBADF; default: return EPERM; /* for non-writeable devices ; */ } } static int sndioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p) { int dev, chan; snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan); if (d == NULL) return ENXIO; switch(dev) { case SND_DEV_CTL: return mixer_ioctl(d, cmd, arg); case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: if (IOCGROUP(cmd) == 'M') return mixer_ioctl(d, cmd, arg); else return dsp_ioctl(d, chan, cmd, arg); default: return ENXIO; } } static int sndpoll(dev_t i_dev, int events, struct proc *p) { int dev, chan; snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan); DEB(printf("sndpoll d 0x%p dev 0x%04x events 0x%08x\n", d, dev, events)); if (d == NULL) return ENXIO; switch(dev) { case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return dsp_poll(d, chan, events, p); default: return (events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)) | POLLHUP; } } /* * The mmap interface allows access to the play and read buffer, * plus the device descriptor. * The various blocks are accessible at the following offsets: * * 0x00000000 ( 0 ) : write buffer ; * 0x01000000 (16 MB) : read buffer ; * 0x02000000 (32 MB) : device descriptor (dangerous!) * * WARNING: the mmap routines assume memory areas are aligned. This * is true (probably) for the dma buffers, but likely false for the * device descriptor. As a consequence, we do not know where it is * located in the requested area. */ static int sndmmap(dev_t i_dev, vm_offset_t offset, int nprot) { int unit, dev, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("sndmmap d 0x%p dev 0x%04x ofs 0x%08x nprot 0x%08x\n", d, dev, offset, nprot)); if (d == NULL || nprot & PROT_EXEC) return -1; /* forbidden */ switch(dev) { case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return dsp_mmap(d, chan, offset, nprot); default: return -1; } } static int status_init(char *buf, int size) { int i; device_t dev; snddev_info *d; snprintf(buf, size, "FreeBSD Audio Driver (newpcm) %s %s\n" "Installed devices:\n", __DATE__, __TIME__); for (i = 0; i <= devclass_get_maxunit(pcm_devclass); i++) { - d = gsd(i); + d = devclass_get_softc(pcm_devclass, i); if (!d) continue; dev = devclass_get_device(pcm_devclass, i); if (1) { snprintf(buf + strlen(buf), size - strlen(buf), "pcm%d: <%s> %s", i, device_get_desc(dev), d->status); if (d->chancount > 0) snprintf(buf + strlen(buf), size - strlen(buf), " (%dp/%dr channels%s)\n", d->playcount, d->reccount, (!(d->flags & SD_F_SIMPLEX))? " duplex" : ""); else snprintf(buf + strlen(buf), size - strlen(buf), " (mixer only)\n"); } } return strlen(buf); } static int status_read(struct uio *buf) { static char status_buf[4096]; static int bufptr = 0, buflen = 0; int l; if (status_isopen == 1) { status_isopen++; bufptr = 0; buflen = status_init(status_buf, sizeof status_buf); } l = min(buf->uio_resid, buflen - bufptr); bufptr += l; return (l > 0)? uiomove(status_buf + bufptr - l, l, buf) : 0; } + +static int +sndpcm_modevent(module_t mod, int type, void *data) +{ + + switch (type) { + case MOD_LOAD: + break; + case MOD_UNLOAD: + if (status_dev) + destroy_dev(status_dev); + status_dev = 0; + break; + default: + break; + } + return 0; +} + +static moduledata_t sndpcm_mod = { + "snd_pcm", + sndpcm_modevent, + NULL +}; +DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); +MODULE_VERSION(snd_pcm, PCM_MODVER); Index: head/sys/dev/sound/pcm/sound.h =================================================================== --- head/sys/dev/sound/pcm/sound.h (revision 65339) +++ head/sys/dev/sound/pcm/sound.h (revision 65340) @@ -1,182 +1,183 @@ /* * Copyright (c) 1999 Cameron Grant * Copyright by Hannu Savolainen 1995 * 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$ */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #if __FreeBSD_version > 500000 #include #endif #include #include /* for DELAY */ #include #include #include #include #include #include #include #include #include #else struct isa_device { int dummy; }; #define d_open_t void #define d_close_t void #define d_read_t void #define d_write_t void #define d_ioctl_t void #define d_select_t void #endif /* _KERNEL */ #endif /* _OS_H_ */ #include #include #include #include #include #ifndef ISADMA_WRITE #define ISADMA_WRITE B_WRITE #define ISADMA_READ B_READ #define ISADMA_RAW B_RAW #endif #define PCM_MODVER 1 #define PCM_MINVER 1 #define PCM_PREFVER PCM_MODVER #define PCM_MAXVER 1 #define MAGIC(unit) (0xa4d10de0 + unit) #define SD_F_SIMPLEX 0x00000001 #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) /* #define DSP_BUFFSIZE (65536 - 256) */ #define DSP_BUFFSIZE (8192) /* the last 256 bytes are room for buggy soundcard to overflow. */ /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_SIGNED (AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S16_BE | AFMT_U16_BE) int fkchan_setup(pcm_channel *c); /* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this * reason the /dev/dsp is reserved for digitized audio use. There is a * device for true DSP processors but it will be called something else. * In v3.0 it's /dev/sndproc but this could be a temporary solution. */ #define SND_DEV_CTL 0 /* Control port /dev/mixer */ #define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ #define SND_DEV_MIDIN 2 /* Raw midi access */ #define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ #define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ #define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ #define SND_DEV_STATUS 6 /* /dev/sndstat */ /* #7 not in use now. */ #define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 #define DSP_DEFAULT_SPEED 8000 #define ON 1 #define OFF 0 #ifdef _KERNEL /* * some macros for debugging purposes * DDB/DEB to enable/disable debugging stuff * BVDDB to enable debugging when bootverbose */ #define DDB(x) x /* XXX */ #define BVDDB(x) if (bootverbose) x #ifndef DEB #define DEB(x) #endif int pcm_addchan(device_t dev, int dir, pcm_channel *templ, void *devinfo); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); +int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); void pcm_setswap(device_t dev, pcm_swap_t *swap); #endif /* _KERNEL */ /* usage of flags in device config entry (config file) */ #define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ #define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ /* ought to be made obsolete */ #define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ #define DV_F_DEV_SHIFT 8 /* force device type/class */