Index: head/sys/dev/sound/isa/gusc.c =================================================================== --- head/sys/dev/sound/isa/gusc.c (revision 274034) +++ head/sys/dev/sound/isa/gusc.c (revision 274035) @@ -1,671 +1,665 @@ /*- * Copyright (c) 1999 Seigo Tanimura * Copyright (c) 1999 Ville-Pertti Keinonen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "bus_if.h" #include #include SND_DECLARE_FILE("$FreeBSD$"); #define LOGICALID_NOPNP 0 #define LOGICALID_PCM 0x0000561e #define LOGICALID_OPL 0x0300561e #define LOGICALID_MIDI 0x0400561e /* PnP IDs */ static struct isa_pnp_id gusc_ids[] = { {LOGICALID_PCM, "GRV0000 Gravis UltraSound PnP PCM"}, /* GRV0000 */ {LOGICALID_OPL, "GRV0003 Gravis UltraSound PnP OPL"}, /* GRV0003 */ {LOGICALID_MIDI, "GRV0004 Gravis UltraSound PnP MIDI"}, /* GRV0004 */ }; /* Interrupt handler. */ struct gusc_ihandler { void (*intr)(void *); void *arg; }; /* Here is the parameter structure per a device. */ struct gusc_softc { device_t dev; /* device */ int io_rid[3]; /* io port rids */ struct resource *io[3]; /* io port resources */ int io_alloced[3]; /* io port alloc flag */ int irq_rid; /* irq rids */ struct resource *irq; /* irq resources */ int irq_alloced; /* irq alloc flag */ int drq_rid[2]; /* drq rids */ struct resource *drq[2]; /* drq resources */ int drq_alloced[2]; /* drq alloc flag */ /* Interrupts are shared (XXX non-PnP only?) */ struct gusc_ihandler midi_intr; struct gusc_ihandler pcm_intr; }; typedef struct gusc_softc *sc_p; static int gusc_probe(device_t dev); static int gusc_attach(device_t dev); static int gusisa_probe(device_t dev); static void gusc_intr(void *); static struct resource *gusc_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags); static int gusc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); static device_t find_masterdev(sc_p scp); static int alloc_resource(sc_p scp); static int release_resource(sc_p scp); static devclass_t gusc_devclass; static int gusc_probe(device_t dev) { device_t child; u_int32_t logical_id; char *s; struct sndcard_func *func; int ret; logical_id = isa_get_logicalid(dev); s = NULL; /* Check isapnp ids */ if (logical_id != 0 && (ret = ISA_PNP_PROBE(device_get_parent(dev), dev, gusc_ids)) != 0) return (ret); else { if (logical_id == 0) return gusisa_probe(dev); } switch (logical_id) { case LOGICALID_PCM: s = "Gravis UltraSound Plug & Play PCM"; func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) return (ENOMEM); func->func = SCF_PCM; child = device_add_child(dev, "pcm", -1); device_set_ivars(child, func); break; case LOGICALID_OPL: s = "Gravis UltraSound Plug & Play OPL"; func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) return (ENOMEM); func->func = SCF_SYNTH; child = device_add_child(dev, "midi", -1); device_set_ivars(child, func); break; case LOGICALID_MIDI: s = "Gravis UltraSound Plug & Play MIDI"; func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) return (ENOMEM); func->func = SCF_MIDI; child = device_add_child(dev, "midi", -1); device_set_ivars(child, func); break; } if (s != NULL) { device_set_desc(dev, s); return (0); } return (ENXIO); } static void port_wr(struct resource *r, int i, unsigned char v) { bus_space_write_1(rman_get_bustag(r), rman_get_bushandle(r), i, v); } static int port_rd(struct resource *r, int i) { return bus_space_read_1(rman_get_bustag(r), rman_get_bushandle(r), i); } /* * Probe for an old (non-PnP) GUS card on the ISA bus. */ static int gusisa_probe(device_t dev) { device_t child; struct resource *res, *res2; int base, rid, rid2, s, flags; unsigned char val; base = isa_get_port(dev); flags = device_get_flags(dev); rid = 1; res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, base + 0x100, base + 0x107, 8, RF_ACTIVE); if (res == NULL) return ENXIO; res2 = NULL; /* * Check for the presence of some GUS card. Reset the card, * then see if we can access the memory on it. */ port_wr(res, 3, 0x4c); port_wr(res, 5, 0); DELAY(30 * 1000); port_wr(res, 3, 0x4c); port_wr(res, 5, 1); DELAY(30 * 1000); s = splhigh(); /* Write to DRAM. */ port_wr(res, 3, 0x43); /* Register select */ port_wr(res, 4, 0); /* Low addr */ port_wr(res, 5, 0); /* Med addr */ port_wr(res, 3, 0x44); /* Register select */ port_wr(res, 4, 0); /* High addr */ port_wr(res, 7, 0x55); /* DRAM */ /* Read from DRAM. */ port_wr(res, 3, 0x43); /* Register select */ port_wr(res, 4, 0); /* Low addr */ port_wr(res, 5, 0); /* Med addr */ port_wr(res, 3, 0x44); /* Register select */ port_wr(res, 4, 0); /* High addr */ val = port_rd(res, 7); /* DRAM */ splx(s); if (val != 0x55) goto fail; rid2 = 0; res2 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid2, base, base, 1, RF_ACTIVE); if (res2 == NULL) goto fail; s = splhigh(); port_wr(res2, 0x0f, 0x20); val = port_rd(res2, 0x0f); splx(s); if (val == 0xff || (val & 0x06) == 0) val = 0; else { val = port_rd(res2, 0x506); /* XXX Out of range. */ if (val == 0xff) val = 0; } bus_release_resource(dev, SYS_RES_IOPORT, rid2, res2); bus_release_resource(dev, SYS_RES_IOPORT, rid, res); if (val >= 10) { struct sndcard_func *func; /* Looks like a GUS MAX. Set the rest of the resources. */ bus_set_resource(dev, SYS_RES_IOPORT, 2, base + 0x10c, 8); if (flags & DV_F_DUAL_DMA) bus_set_resource(dev, SYS_RES_DRQ, 1, flags & DV_F_DRQ_MASK, 1); /* We can support the CS4231 and MIDI devices. */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) return ENOMEM; func->func = SCF_MIDI; child = device_add_child(dev, "midi", -1); device_set_ivars(child, func); func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) printf("xxx: gus pcm not attached, out of memory\n"); else { func->func = SCF_PCM; child = device_add_child(dev, "pcm", -1); device_set_ivars(child, func); } device_set_desc(dev, "Gravis UltraSound MAX"); return 0; } else { /* * TODO: Support even older GUS cards. MIDI should work on * all models. */ return ENXIO; } fail: bus_release_resource(dev, SYS_RES_IOPORT, rid, res); return ENXIO; } static int gusc_attach(device_t dev) { sc_p scp; void *ih; scp = device_get_softc(dev); bzero(scp, sizeof(*scp)); scp->dev = dev; if (alloc_resource(scp)) { release_resource(scp); return (ENXIO); } if (scp->irq != NULL) snd_setup_intr(dev, scp->irq, 0, gusc_intr, scp, &ih); bus_generic_attach(dev); return (0); } /* * Handle interrupts on GUS devices until there aren't any left. */ static void gusc_intr(void *arg) { sc_p scp = (sc_p)arg; int did_something; do { did_something = 0; if (scp->pcm_intr.intr != NULL && (port_rd(scp->io[2], 2) & 1)) { (*scp->pcm_intr.intr)(scp->pcm_intr.arg); did_something = 1; } if (scp->midi_intr.intr != NULL && (port_rd(scp->io[1], 0) & 0x80)) { (*scp->midi_intr.intr)(scp->midi_intr.arg); did_something = 1; } } while (did_something != 0); } static struct resource * gusc_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { sc_p scp; int *alloced, rid_max, alloced_max; struct resource **res; scp = device_get_softc(bus); switch (type) { case SYS_RES_IOPORT: alloced = scp->io_alloced; res = scp->io; rid_max = 2; alloced_max = 2; /* pcm + midi (more to include synth) */ break; case SYS_RES_IRQ: alloced = &scp->irq_alloced; res = &scp->irq; rid_max = 0; alloced_max = 2; /* pcm and midi share the single irq. */ break; case SYS_RES_DRQ: alloced = scp->drq_alloced; res = scp->drq; rid_max = 1; alloced_max = 1; break; default: return (NULL); } if (*rid > rid_max || alloced[*rid] == alloced_max) return (NULL); alloced[*rid]++; return (res[*rid]); } static int gusc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { sc_p scp; int *alloced, rid_max; scp = device_get_softc(bus); switch (type) { case SYS_RES_IOPORT: alloced = scp->io_alloced; rid_max = 2; break; case SYS_RES_IRQ: alloced = &scp->irq_alloced; rid_max = 0; break; case SYS_RES_DRQ: alloced = scp->drq_alloced; rid_max = 1; break; default: return (1); } if (rid > rid_max || alloced[rid] == 0) return (1); alloced[rid]--; return (0); } static int gusc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, -#if __FreeBSD_version >= 700031 driver_filter_t *filter, -#endif driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp = (sc_p)device_get_softc(dev); devclass_t devclass; -#if __FreeBSD_version >= 700031 if (filter != NULL) { printf("gusc.c: we cannot use a filter here\n"); return (EINVAL); } -#endif devclass = device_get_devclass(child); if (strcmp(devclass_get_name(devclass), "midi") == 0) { scp->midi_intr.intr = intr; scp->midi_intr.arg = arg; return 0; } else if (strcmp(devclass_get_name(devclass), "pcm") == 0) { scp->pcm_intr.intr = intr; scp->pcm_intr.arg = arg; return 0; } return bus_generic_setup_intr(dev, child, irq, flags, -#if __FreeBSD_version >= 700031 filter, -#endif intr, arg, cookiep); } static device_t find_masterdev(sc_p scp) { int i, units; devclass_t devclass; device_t dev; devclass = device_get_devclass(scp->dev); units = devclass_get_maxunit(devclass); dev = NULL; for (i = 0 ; i < units ; i++) { dev = devclass_get_device(devclass, i); if (isa_get_vendorid(dev) == isa_get_vendorid(scp->dev) && isa_get_logicalid(dev) == LOGICALID_PCM && isa_get_serial(dev) == isa_get_serial(scp->dev)) break; } if (i == units) return (NULL); return (dev); } static int io_range[3] = {0x10, 0x8 , 0x4 }; static int io_offset[3] = {0x0 , 0x100, 0x10c}; static int alloc_resource(sc_p scp) { int i, base, lid, flags; device_t dev; flags = 0; if (isa_get_vendorid(scp->dev)) lid = isa_get_logicalid(scp->dev); else { lid = LOGICALID_NOPNP; flags = device_get_flags(scp->dev); } switch(lid) { case LOGICALID_PCM: case LOGICALID_NOPNP: /* XXX Non-PnP */ if (lid == LOGICALID_NOPNP) base = isa_get_port(scp->dev); else base = 0; for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) { if (scp->io[i] == NULL) { scp->io_rid[i] = i; if (base == 0) scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], 0, ~0, io_range[i], RF_ACTIVE); else scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], base + io_offset[i], base + io_offset[i] + io_range[i] - 1 , io_range[i], RF_ACTIVE); if (scp->io[i] == NULL) return (1); scp->io_alloced[i] = 0; } } if (scp->irq == NULL) { scp->irq_rid = 0; scp->irq = bus_alloc_resource_any(scp->dev, SYS_RES_IRQ, &scp->irq_rid, RF_ACTIVE|RF_SHAREABLE); if (scp->irq == NULL) return (1); scp->irq_alloced = 0; } for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) { if (scp->drq[i] == NULL) { scp->drq_rid[i] = i; if (base == 0 || i == 0) scp->drq[i] = bus_alloc_resource_any( scp->dev, SYS_RES_DRQ, &scp->drq_rid[i], RF_ACTIVE); else if ((flags & DV_F_DUAL_DMA) != 0) /* XXX The secondary drq is specified in the flag. */ scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i], flags & DV_F_DRQ_MASK, flags & DV_F_DRQ_MASK, 1, RF_ACTIVE); if (scp->drq[i] == NULL) return (1); scp->drq_alloced[i] = 0; } } break; case LOGICALID_OPL: if (scp->io[0] == NULL) { scp->io_rid[0] = 0; scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0], 0, ~0, io_range[0], RF_ACTIVE); if (scp->io[0] == NULL) return (1); scp->io_alloced[0] = 0; } break; case LOGICALID_MIDI: if (scp->io[0] == NULL) { scp->io_rid[0] = 0; scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0], 0, ~0, io_range[0], RF_ACTIVE); if (scp->io[0] == NULL) return (1); scp->io_alloced[0] = 0; } if (scp->irq == NULL) { /* The irq is shared with pcm audio. */ dev = find_masterdev(scp); if (dev == NULL) return (1); scp->irq_rid = 0; scp->irq = BUS_ALLOC_RESOURCE(dev, NULL, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (scp->irq == NULL) return (1); scp->irq_alloced = 0; } break; } return (0); } static int release_resource(sc_p scp) { int i, lid; device_t dev; if (isa_get_vendorid(scp->dev)) lid = isa_get_logicalid(scp->dev); else lid = LOGICALID_NOPNP; switch(lid) { case LOGICALID_PCM: case LOGICALID_NOPNP: /* XXX Non-PnP */ for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) { if (scp->io[i] != NULL) { bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[i], scp->io[i]); scp->io[i] = NULL; } } if (scp->irq != NULL) { bus_release_resource(scp->dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); scp->irq = NULL; } for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) { if (scp->drq[i] != NULL) { bus_release_resource(scp->dev, SYS_RES_DRQ, scp->drq_rid[i], scp->drq[i]); scp->drq[i] = NULL; } } break; case LOGICALID_OPL: if (scp->io[0] != NULL) { bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); scp->io[0] = NULL; } break; case LOGICALID_MIDI: if (scp->io[0] != NULL) { bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); scp->io[0] = NULL; } if (scp->irq != NULL) { /* The irq is shared with pcm audio. */ dev = find_masterdev(scp); if (dev == NULL) return (1); BUS_RELEASE_RESOURCE(dev, NULL, SYS_RES_IOPORT, scp->irq_rid, scp->irq); scp->irq = NULL; } break; } return (0); } static device_method_t gusc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gusc_probe), DEVMETHOD(device_attach, gusc_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_alloc_resource, gusc_alloc_resource), DEVMETHOD(bus_release_resource, gusc_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, gusc_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t gusc_driver = { "gusc", gusc_methods, sizeof(struct gusc_softc), }; /* * gusc can be attached to an isa bus. */ DRIVER_MODULE(snd_gusc, isa, gusc_driver, gusc_devclass, 0, 0); DRIVER_MODULE(snd_gusc, acpi, gusc_driver, gusc_devclass, 0, 0); MODULE_DEPEND(snd_gusc, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_gusc, 1); Index: head/sys/dev/sound/isa/sb16.c =================================================================== --- head/sys/dev/sound/isa/sb16.c (revision 274034) +++ head/sys/dev/sound/isa/sb16.c (revision 274035) @@ -1,915 +1,913 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright * conditions. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define SB16_BUFFSIZE 4096 #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) static u_int32_t sb16_fmt8[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), 0 }; static struct pcmchan_caps sb16_caps8 = {5000, 45000, sb16_fmt8, 0}; static u_int32_t sb16_fmt16[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps sb16_caps16 = {5000, 45000, sb16_fmt16, 0}; static u_int32_t sb16x_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps sb16x_caps = {5000, 49000, sb16x_fmt, 0}; struct sb_info; struct sb_chinfo { struct sb_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, run, dch; u_int32_t fmt, spd, blksz; }; struct sb_info { struct resource *io_base; /* I/O address for the board */ struct resource *irq; struct resource *drq1; struct resource *drq2; void *ih; bus_dma_tag_t parent_dmat; unsigned int bufsize; int bd_id; u_long bd_flags; /* board-specific flags */ int prio, prio16; struct sb_chinfo pch, rch; device_t parent_dev; }; #if 0 static void sb_lock(struct sb_info *sb); static void sb_unlock(struct sb_info *sb); static int sb_rd(struct sb_info *sb, int reg); static void sb_wr(struct sb_info *sb, int reg, u_int8_t val); static int sb_cmd(struct sb_info *sb, u_char val); /* static int sb_cmd1(struct sb_info *sb, u_char cmd, int val); */ static int sb_cmd2(struct sb_info *sb, u_char cmd, int val); static u_int sb_get_byte(struct sb_info *sb); static void sb_setmixer(struct sb_info *sb, u_int port, u_int value); static int sb_getmixer(struct sb_info *sb, u_int port); static int sb_reset_dsp(struct sb_info *sb); static void sb_intr(void *arg); #endif /* * Common code for the midi and pcm functions * * sb_cmd write a single byte to the CMD port. * sb_cmd1 write a CMD + 1 byte arg * sb_cmd2 write a CMD + 2 byte arg * sb_get_byte returns a single byte from the DSP data port */ static void sb_lock(struct sb_info *sb) { sbc_lock(device_get_softc(sb->parent_dev)); } static void sb_lockassert(struct sb_info *sb) { sbc_lockassert(device_get_softc(sb->parent_dev)); } static void sb_unlock(struct sb_info *sb) { sbc_unlock(device_get_softc(sb->parent_dev)); } static int port_rd(struct resource *port, int off) { return bus_space_read_1(rman_get_bustag(port), rman_get_bushandle(port), off); } static void port_wr(struct resource *port, int off, u_int8_t data) { bus_space_write_1(rman_get_bustag(port), rman_get_bushandle(port), off, data); } static int sb_rd(struct sb_info *sb, int reg) { return port_rd(sb->io_base, reg); } static void sb_wr(struct sb_info *sb, int reg, u_int8_t val) { port_wr(sb->io_base, reg, val); } static int sb_dspwr(struct sb_info *sb, u_char val) { int i; for (i = 0; i < 1000; i++) { if ((sb_rd(sb, SBDSP_STATUS) & 0x80)) DELAY((i > 100)? 1000 : 10); else { sb_wr(sb, SBDSP_CMD, val); return 1; } } -#if __FreeBSD_version > 500000 if (curthread->td_intr_nesting_level == 0) printf("sb_dspwr(0x%02x) timed out.\n", val); -#endif return 0; } static int sb_cmd(struct sb_info *sb, u_char val) { #if 0 printf("sb_cmd: %x\n", val); #endif return sb_dspwr(sb, val); } /* static int sb_cmd1(struct sb_info *sb, u_char cmd, int val) { #if 0 printf("sb_cmd1: %x, %x\n", cmd, val); #endif if (sb_dspwr(sb, cmd)) { return sb_dspwr(sb, val & 0xff); } else return 0; } */ static int sb_cmd2(struct sb_info *sb, u_char cmd, int val) { int r; #if 0 printf("sb_cmd2: %x, %x\n", cmd, val); #endif sb_lockassert(sb); r = 0; if (sb_dspwr(sb, cmd)) { if (sb_dspwr(sb, val & 0xff)) { if (sb_dspwr(sb, (val >> 8) & 0xff)) { r = 1; } } } return r; } /* * in the SB, there is a set of indirect "mixer" registers with * address at offset 4, data at offset 5 */ static void sb_setmixer(struct sb_info *sb, u_int port, u_int value) { sb_lock(sb); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); sb_wr(sb, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); sb_unlock(sb); } static int sb_getmixer(struct sb_info *sb, u_int port) { int val; sb_lockassert(sb); sb_wr(sb, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = sb_rd(sb, SB_MIX_DATA); DELAY(10); return val; } static u_int sb_get_byte(struct sb_info *sb) { int i; for (i = 1000; i > 0; i--) { if (sb_rd(sb, DSP_DATA_AVAIL) & 0x80) return sb_rd(sb, DSP_READ); else DELAY(20); } return 0xffff; } static int sb_reset_dsp(struct sb_info *sb) { u_char b; sb_lockassert(sb); sb_wr(sb, SBDSP_RST, 3); DELAY(100); sb_wr(sb, SBDSP_RST, 0); b = sb_get_byte(sb); if (b != 0xAA) { DEB(printf("sb_reset_dsp 0x%lx failed\n", rman_get_start(sb->io_base))); return ENXIO; /* Sorry */ } return 0; } /************************************************************/ struct sb16_mixent { int reg; int bits; int ofs; int stereo; }; static const struct sb16_mixent sb16_mixtab[32] = { [SOUND_MIXER_VOLUME] = { 0x30, 5, 3, 1 }, [SOUND_MIXER_PCM] = { 0x32, 5, 3, 1 }, [SOUND_MIXER_SYNTH] = { 0x34, 5, 3, 1 }, [SOUND_MIXER_CD] = { 0x36, 5, 3, 1 }, [SOUND_MIXER_LINE] = { 0x38, 5, 3, 1 }, [SOUND_MIXER_MIC] = { 0x3a, 5, 3, 0 }, [SOUND_MIXER_SPEAKER] = { 0x3b, 5, 3, 0 }, [SOUND_MIXER_IGAIN] = { 0x3f, 2, 6, 1 }, [SOUND_MIXER_OGAIN] = { 0x41, 2, 6, 1 }, [SOUND_MIXER_TREBLE] = { 0x44, 4, 4, 1 }, [SOUND_MIXER_BASS] = { 0x46, 4, 4, 1 }, [SOUND_MIXER_LINE1] = { 0x52, 5, 3, 1 } }; static int sb16mix_init(struct snd_mixer *m) { struct sb_info *sb = mix_getdevinfo(m); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | SOUND_MASK_LINE1 | SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE); mix_setrecdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC | SOUND_MASK_CD); sb_setmixer(sb, 0x3c, 0x1f); /* make all output active */ sb_setmixer(sb, 0x3d, 0); /* make all inputs-l off */ sb_setmixer(sb, 0x3e, 0); /* make all inputs-r off */ return 0; } static int rel2abs_volume(int x, int max) { int temp; temp = ((x * max) + 50) / 100; if (temp > max) temp = max; else if (temp < 0) temp = 0; return (temp); } static int sb16mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sb_info *sb = mix_getdevinfo(m); const struct sb16_mixent *e; int max; e = &sb16_mixtab[dev]; max = (1 << e->bits) - 1; left = rel2abs_volume(left, max); right = rel2abs_volume(right, max); sb_setmixer(sb, e->reg, left << e->ofs); if (e->stereo) sb_setmixer(sb, e->reg + 1, right << e->ofs); else right = left; left = (left * 100) / max; right = (right * 100) / max; return left | (right << 8); } static u_int32_t sb16mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); u_char recdev_l, recdev_r; recdev_l = 0; recdev_r = 0; if (src & SOUND_MASK_MIC) { recdev_l |= 0x01; /* mono mic */ recdev_r |= 0x01; } if (src & SOUND_MASK_CD) { recdev_l |= 0x04; /* l cd */ recdev_r |= 0x02; /* r cd */ } if (src & SOUND_MASK_LINE) { recdev_l |= 0x10; /* l line */ recdev_r |= 0x08; /* r line */ } if (src & SOUND_MASK_SYNTH) { recdev_l |= 0x40; /* l midi */ recdev_r |= 0x20; /* r midi */ } sb_setmixer(sb, SB16_IMASK_L, recdev_l); sb_setmixer(sb, SB16_IMASK_R, recdev_r); /* Switch on/off FM tuner source */ if (src & SOUND_MASK_LINE1) sb_setmixer(sb, 0x4a, 0x0c); else sb_setmixer(sb, 0x4a, 0x00); /* * since the same volume controls apply to the input and * output sections, the best approach to have a consistent * behaviour among cards would be to disable the output path * on devices which are used to record. * However, since users like to have feedback, we only disable * the mic -- permanently. */ sb_setmixer(sb, SB16_OMASK, 0x1f & ~1); return src; } static kobj_method_t sb16mix_mixer_methods[] = { KOBJMETHOD(mixer_init, sb16mix_init), KOBJMETHOD(mixer_set, sb16mix_set), KOBJMETHOD(mixer_setrecsrc, sb16mix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(sb16mix_mixer); /************************************************************/ static void sb16_release_resources(struct sb_info *sb, device_t dev) { if (sb->irq) { if (sb->ih) bus_teardown_intr(dev, sb->irq, sb->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sb->irq); sb->irq = 0; } if (sb->drq2) { if (sb->drq2 != sb->drq1) { isa_dma_release(rman_get_start(sb->drq2)); bus_release_resource(dev, SYS_RES_DRQ, 1, sb->drq2); } sb->drq2 = 0; } if (sb->drq1) { isa_dma_release(rman_get_start(sb->drq1)); bus_release_resource(dev, SYS_RES_DRQ, 0, sb->drq1); sb->drq1 = 0; } if (sb->io_base) { bus_release_resource(dev, SYS_RES_IOPORT, 0, sb->io_base); sb->io_base = 0; } if (sb->parent_dmat) { bus_dma_tag_destroy(sb->parent_dmat); sb->parent_dmat = 0; } free(sb, M_DEVBUF); } static int sb16_alloc_resources(struct sb_info *sb, device_t dev) { int rid; rid = 0; if (!sb->io_base) sb->io_base = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; if (!sb->irq) sb->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); rid = 0; if (!sb->drq1) sb->drq1 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); rid = 1; if (!sb->drq2) sb->drq2 = bus_alloc_resource_any(dev, SYS_RES_DRQ, &rid, RF_ACTIVE); if (sb->io_base && sb->drq1 && sb->irq) { isa_dma_acquire(rman_get_start(sb->drq1)); isa_dmainit(rman_get_start(sb->drq1), sb->bufsize); if (sb->drq2) { isa_dma_acquire(rman_get_start(sb->drq2)); isa_dmainit(rman_get_start(sb->drq2), sb->bufsize); } else { sb->drq2 = sb->drq1; pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); } return 0; } else return ENXIO; } /* sbc does locking for us */ static void sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; int reason, c; /* * The Vibra16X has separate flags for 8 and 16 bit transfers, but * I have no idea how to tell capture from playback interrupts... */ reason = 0; sb_lock(sb); c = sb_getmixer(sb, IRQ_STAT); if (c & 1) sb_rd(sb, DSP_DATA_AVAIL); /* 8-bit int ack */ if (c & 2) sb_rd(sb, DSP_DATA_AVL16); /* 16-bit int ack */ sb_unlock(sb); /* * this tells us if the source is 8-bit or 16-bit dma. We * have to check the io channel to map it to read or write... */ if (sb->bd_flags & BD_F_SB16X) { if (c & 1) { /* 8-bit format */ if (sb->pch.fmt & AFMT_8BIT) reason |= 1; if (sb->rch.fmt & AFMT_8BIT) reason |= 2; } if (c & 2) { /* 16-bit format */ if (sb->pch.fmt & AFMT_16BIT) reason |= 1; if (sb->rch.fmt & AFMT_16BIT) reason |= 2; } } else { if (c & 1) { /* 8-bit dma */ if (sb->pch.dch == 1) reason |= 1; if (sb->rch.dch == 1) reason |= 2; } if (c & 2) { /* 16-bit dma */ if (sb->pch.dch == 2) reason |= 1; if (sb->rch.dch == 2) reason |= 2; } } #if 0 printf("sb_intr: reason=%d c=0x%x\n", reason, c); #endif if ((reason & 1) && (sb->pch.run)) chn_intr(sb->pch.channel); if ((reason & 2) && (sb->rch.run)) chn_intr(sb->rch.channel); } static int sb_setup(struct sb_info *sb) { struct sb_chinfo *ch; u_int8_t v; int l, pprio; sb_lock(sb); if (sb->bd_flags & BD_F_DMARUN) sndbuf_dma(sb->pch.buffer, PCMTRIG_STOP); if (sb->bd_flags & BD_F_DMARUN2) sndbuf_dma(sb->rch.buffer, PCMTRIG_STOP); sb->bd_flags &= ~(BD_F_DMARUN | BD_F_DMARUN2); sb_reset_dsp(sb); if (sb->bd_flags & BD_F_SB16X) { /* full-duplex doesn't work! */ pprio = sb->pch.run? 1 : 0; sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq1 : sb->drq2); sb->pch.dch = pprio? 1 : 0; sndbuf_dmasetup(sb->rch.buffer, pprio? sb->drq2 : sb->drq1); sb->rch.dch = pprio? 2 : 1; } else { if (sb->pch.run && sb->rch.run) { pprio = (sb->rch.fmt & AFMT_16BIT)? 0 : 1; sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq2 : sb->drq1); sb->pch.dch = pprio? 2 : 1; sndbuf_dmasetup(sb->rch.buffer, pprio? sb->drq1 : sb->drq2); sb->rch.dch = pprio? 1 : 2; } else { if (sb->pch.run) { sndbuf_dmasetup(sb->pch.buffer, (sb->pch.fmt & AFMT_16BIT)? sb->drq2 : sb->drq1); sb->pch.dch = (sb->pch.fmt & AFMT_16BIT)? 2 : 1; sndbuf_dmasetup(sb->rch.buffer, (sb->pch.fmt & AFMT_16BIT)? sb->drq1 : sb->drq2); sb->rch.dch = (sb->pch.fmt & AFMT_16BIT)? 1 : 2; } else if (sb->rch.run) { sndbuf_dmasetup(sb->pch.buffer, (sb->rch.fmt & AFMT_16BIT)? sb->drq1 : sb->drq2); sb->pch.dch = (sb->rch.fmt & AFMT_16BIT)? 1 : 2; sndbuf_dmasetup(sb->rch.buffer, (sb->rch.fmt & AFMT_16BIT)? sb->drq2 : sb->drq1); sb->rch.dch = (sb->rch.fmt & AFMT_16BIT)? 2 : 1; } } } sndbuf_dmasetdir(sb->pch.buffer, PCMDIR_PLAY); sndbuf_dmasetdir(sb->rch.buffer, PCMDIR_REC); /* printf("setup: [pch = %d, pfmt = %d, pgo = %d] [rch = %d, rfmt = %d, rgo = %d]\n", sb->pch.dch, sb->pch.fmt, sb->pch.run, sb->rch.dch, sb->rch.fmt, sb->rch.run); */ ch = &sb->pch; if (ch->run) { l = ch->blksz; if (ch->fmt & AFMT_16BIT) l >>= 1; l--; /* play speed */ RANGE(ch->spd, 5000, 45000); sb_cmd(sb, DSP_CMD_OUT16); sb_cmd(sb, ch->spd >> 8); sb_cmd(sb, ch->spd & 0xff); /* play format, length */ v = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_F16_DAC; v |= (ch->fmt & AFMT_16BIT)? DSP_DMA16 : DSP_DMA8; sb_cmd(sb, v); v = (AFMT_CHANNEL(ch->fmt) > 1)? DSP_F16_STEREO : 0; v |= (ch->fmt & AFMT_SIGNED)? DSP_F16_SIGNED : 0; sb_cmd2(sb, v, l); sndbuf_dma(ch->buffer, PCMTRIG_START); sb->bd_flags |= BD_F_DMARUN; } ch = &sb->rch; if (ch->run) { l = ch->blksz; if (ch->fmt & AFMT_16BIT) l >>= 1; l--; /* record speed */ RANGE(ch->spd, 5000, 45000); sb_cmd(sb, DSP_CMD_IN16); sb_cmd(sb, ch->spd >> 8); sb_cmd(sb, ch->spd & 0xff); /* record format, length */ v = DSP_F16_AUTO | DSP_F16_FIFO_ON | DSP_F16_ADC; v |= (ch->fmt & AFMT_16BIT)? DSP_DMA16 : DSP_DMA8; sb_cmd(sb, v); v = (AFMT_CHANNEL(ch->fmt) > 1)? DSP_F16_STEREO : 0; v |= (ch->fmt & AFMT_SIGNED)? DSP_F16_SIGNED : 0; sb_cmd2(sb, v, l); sndbuf_dma(ch->buffer, PCMTRIG_START); sb->bd_flags |= BD_F_DMARUN2; } sb_unlock(sb); return 0; } /* channel interface */ static void * sb16chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sb_info *sb = devinfo; struct sb_chinfo *ch = (dir == PCMDIR_PLAY)? &sb->pch : &sb->rch; ch->parent = sb; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; return ch; } static int sb16chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; ch->fmt = format; sb->prio = ch->dir; sb->prio16 = (ch->fmt & AFMT_16BIT)? 1 : 0; return 0; } static u_int32_t sb16chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sb_chinfo *ch = data; ch->spd = speed; return speed; } static u_int32_t sb16chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sb_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int sb16chan_trigger(kobj_t obj, void *data, int go) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) ch->run = 1; else ch->run = 0; sb_setup(sb); return 0; } static u_int32_t sb16chan_getptr(kobj_t obj, void *data) { struct sb_chinfo *ch = data; return sndbuf_dmaptr(ch->buffer); } static struct pcmchan_caps * sb16chan_getcaps(kobj_t obj, void *data) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; if ((sb->prio == 0) || (sb->prio == ch->dir)) return &sb16x_caps; else return sb->prio16? &sb16_caps8 : &sb16_caps16; } static int sb16chan_resetdone(kobj_t obj, void *data) { struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; sb->prio = 0; return 0; } static kobj_method_t sb16chan_methods[] = { KOBJMETHOD(channel_init, sb16chan_init), KOBJMETHOD(channel_resetdone, sb16chan_resetdone), KOBJMETHOD(channel_setformat, sb16chan_setformat), KOBJMETHOD(channel_setspeed, sb16chan_setspeed), KOBJMETHOD(channel_setblocksize, sb16chan_setblocksize), KOBJMETHOD(channel_trigger, sb16chan_trigger), KOBJMETHOD(channel_getptr, sb16chan_getptr), KOBJMETHOD(channel_getcaps, sb16chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(sb16chan); /************************************************************/ static int sb16_probe(device_t dev) { char buf[64]; uintptr_t func, ver, r, f; /* The parent device has already been probed. */ r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); if (func != SCF_PCM) return (ENXIO); r = BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); f = (ver & 0xffff0000) >> 16; ver &= 0x0000ffff; if (f & BD_F_SB16) { snprintf(buf, sizeof buf, "SB16 DSP %d.%02d%s", (int) ver >> 8, (int) ver & 0xff, (f & BD_F_SB16X)? " (ViBRA16X)" : ""); device_set_desc_copy(dev, buf); return 0; } else return (ENXIO); } static int sb16_attach(device_t dev) { struct sb_info *sb; uintptr_t ver; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; sb = malloc(sizeof(*sb), M_DEVBUF, M_WAITOK | M_ZERO); sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(sb->parent_dev, dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; sb->bd_flags = (ver & 0xffff0000) >> 16; sb->bufsize = pcm_getbuffersize(dev, 4096, SB16_BUFFSIZE, 65536); if (sb16_alloc_resources(sb, dev)) goto no; sb_lock(sb); if (sb_reset_dsp(sb)) { sb_unlock(sb); goto no; } sb_unlock(sb); if (mixer_init(dev, &sb16mix_mixer_class, sb)) goto no; if (snd_setup_intr(dev, sb->irq, 0, sb_intr, sb, &sb->ih)) goto no; if (sb->bd_flags & BD_F_SB16X) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); sb->prio = 0; if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sb->bufsize, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sb->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (!(pcm_getflags(dev) & SD_F_SIMPLEX)) snprintf(status2, SND_STATUSLEN, ":%ld", rman_get_start(sb->drq2)); else status2[0] = '\0'; snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u %s", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1), status2, sb->bufsize, PCM_KLDSTRING(snd_sb16)); if (pcm_register(dev, sb, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &sb16chan_class, sb); pcm_addchan(dev, PCMDIR_PLAY, &sb16chan_class, sb); pcm_setstatus(dev, status); return 0; no: sb16_release_resources(sb, dev); return ENXIO; } static int sb16_detach(device_t dev) { int r; struct sb_info *sb; r = pcm_unregister(dev); if (r) return r; sb = pcm_getdevinfo(dev); sb16_release_resources(sb, dev); return 0; } static device_method_t sb16_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sb16_probe), DEVMETHOD(device_attach, sb16_attach), DEVMETHOD(device_detach, sb16_detach), { 0, 0 } }; static driver_t sb16_driver = { "pcm", sb16_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_sb16, sbc, sb16_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_sb16, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_sb16, snd_sbc, 1, 1, 1); MODULE_VERSION(snd_sb16, 1); Index: head/sys/dev/sound/isa/sbc.c =================================================================== --- head/sys/dev/sound/isa/sbc.c (revision 274034) +++ head/sys/dev/sound/isa/sbc.c (revision 274035) @@ -1,813 +1,807 @@ /*- * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #define IO_MAX 3 #define IRQ_MAX 1 #define DRQ_MAX 2 #define INTR_MAX 2 struct sbc_softc; struct sbc_ihl { driver_intr_t *intr[INTR_MAX]; void *intr_arg[INTR_MAX]; struct sbc_softc *parent; }; /* Here is the parameter structure per a device. */ struct sbc_softc { device_t dev; /* device */ device_t child_pcm, child_midi1, child_midi2; int io_rid[IO_MAX]; /* io port rids */ struct resource *io[IO_MAX]; /* io port resources */ int io_alloced[IO_MAX]; /* io port alloc flag */ int irq_rid[IRQ_MAX]; /* irq rids */ struct resource *irq[IRQ_MAX]; /* irq resources */ int irq_alloced[IRQ_MAX]; /* irq alloc flag */ int drq_rid[DRQ_MAX]; /* drq rids */ struct resource *drq[DRQ_MAX]; /* drq resources */ int drq_alloced[DRQ_MAX]; /* drq alloc flag */ struct sbc_ihl ihl[IRQ_MAX]; void *ih[IRQ_MAX]; struct mtx *lock; u_int32_t bd_ver; }; static int sbc_probe(device_t dev); static int sbc_attach(device_t dev); static void sbc_intr(void *p); static struct resource *sbc_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags); static int sbc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); static int sbc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, -#if __FreeBSD_version >= 700031 driver_filter_t *filter, -#endif driver_intr_t *intr, void *arg, void **cookiep); static int sbc_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie); static int alloc_resource(struct sbc_softc *scp); static int release_resource(struct sbc_softc *scp); static devclass_t sbc_devclass; static int io_range[3] = {0x10, 0x2, 0x4}; #ifdef PC98 /* I/O address table for PC98 */ static bus_addr_t pcm_iat[] = { 0x000, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700, 0x800, 0x900, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00, 0xf00 }; static bus_addr_t midi_iat[] = { 0x000, 0x100 }; static bus_addr_t opl_iat[] = { 0x000, 0x100, 0x200, 0x300 }; static bus_addr_t *sb_iat[] = { pcm_iat, midi_iat, opl_iat }; #endif static int sb_rd(struct resource *io, int reg); static void sb_wr(struct resource *io, int reg, u_int8_t val); static int sb_dspready(struct resource *io); static int sb_cmd(struct resource *io, u_char val); static u_int sb_get_byte(struct resource *io); static void sb_setmixer(struct resource *io, u_int port, u_int value); static void sbc_lockinit(struct sbc_softc *scp) { scp->lock = snd_mtxcreate(device_get_nameunit(scp->dev), "snd_sbc softc"); } static void sbc_lockdestroy(struct sbc_softc *scp) { snd_mtxfree(scp->lock); } void sbc_lock(struct sbc_softc *scp) { snd_mtxlock(scp->lock); } void sbc_lockassert(struct sbc_softc *scp) { snd_mtxassert(scp->lock); } void sbc_unlock(struct sbc_softc *scp) { snd_mtxunlock(scp->lock); } static int sb_rd(struct resource *io, int reg) { return bus_space_read_1(rman_get_bustag(io), rman_get_bushandle(io), reg); } static void sb_wr(struct resource *io, int reg, u_int8_t val) { bus_space_write_1(rman_get_bustag(io), rman_get_bushandle(io), reg, val); } static int sb_dspready(struct resource *io) { return ((sb_rd(io, SBDSP_STATUS) & 0x80) == 0); } static int sb_dspwr(struct resource *io, u_char val) { int i; for (i = 0; i < 1000; i++) { if (sb_dspready(io)) { sb_wr(io, 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 resource *io, u_char val) { return sb_dspwr(io, val); } static void sb_setmixer(struct resource *io, u_int port, u_int value) { u_long flags; flags = spltty(); sb_wr(io, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); sb_wr(io, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); splx(flags); } static u_int sb_get_byte(struct resource *io) { int i; for (i = 1000; i > 0; i--) { if (sb_rd(io, DSP_DATA_AVAIL) & 0x80) return sb_rd(io, DSP_READ); else DELAY(20); } return 0xffff; } static int sb_reset_dsp(struct resource *io) { sb_wr(io, SBDSP_RST, 3); DELAY(100); sb_wr(io, SBDSP_RST, 0); return (sb_get_byte(io) == 0xAA)? 0 : ENXIO; } static int sb_identify_board(struct resource *io) { int ver, essver, rev; sb_cmd(io, DSP_CMD_GETVER); /* Get version */ ver = (sb_get_byte(io) << 8) | sb_get_byte(io); if (ver < 0x100 || ver > 0x4ff) return 0; if (ver == 0x0301) { /* Try to detect ESS chips. */ sb_cmd(io, DSP_CMD_GETID); /* Return ident. bytes. */ essver = (sb_get_byte(io) << 8) | sb_get_byte(io); rev = essver & 0x000f; essver &= 0xfff0; if (essver == 0x4880) ver |= 0x1000; else if (essver == 0x6880) ver = 0x0500 | rev; } return ver; } static struct isa_pnp_id sbc_ids[] = { {0x01008c0e, "Creative ViBRA16C"}, /* CTL0001 */ {0x31008c0e, "Creative SB16/SB32"}, /* CTL0031 */ {0x41008c0e, "Creative SB16/SB32"}, /* CTL0041 */ {0x42008c0e, "Creative SB AWE64"}, /* CTL0042 */ {0x43008c0e, "Creative ViBRA16X"}, /* CTL0043 */ {0x44008c0e, "Creative SB AWE64 Gold"}, /* CTL0044 */ {0x45008c0e, "Creative SB AWE64"}, /* CTL0045 */ {0x46008c0e, "Creative SB AWE64"}, /* CTL0046 */ {0x01000000, "Avance Logic ALS100+"}, /* @@@0001 - ViBRA16X clone */ {0x01100000, "Avance Asound 110"}, /* @@@1001 */ {0x01200000, "Avance Logic ALS120"}, /* @@@2001 - ViBRA16X clone */ {0x81167316, "ESS ES1681"}, /* ESS1681 */ {0x02017316, "ESS ES1688"}, /* ESS1688 */ {0x68097316, "ESS ES1688"}, /* ESS1688 */ {0x68187316, "ESS ES1868"}, /* ESS1868 */ {0x03007316, "ESS ES1869"}, /* ESS1869 */ {0x69187316, "ESS ES1869"}, /* ESS1869 */ {0xabb0110e, "ESS ES1869 (Compaq OEM)"}, /* CPQb0ab */ {0xacb0110e, "ESS ES1869 (Compaq OEM)"}, /* CPQb0ac */ {0x78187316, "ESS ES1878"}, /* ESS1878 */ {0x79187316, "ESS ES1879"}, /* ESS1879 */ {0x88187316, "ESS ES1888"}, /* ESS1888 */ {0x07017316, "ESS ES1888 (DEC OEM)"}, /* ESS0107 */ {0x06017316, "ESS ES1888 (Dell OEM)"}, /* ESS0106 */ {0} }; static int sbc_probe(device_t dev) { char *s = NULL; u_int32_t lid, vid; lid = isa_get_logicalid(dev); vid = isa_get_vendorid(dev); if (lid) { if (lid == 0x01000000 && vid != 0x01009305) /* ALS0001 */ return ENXIO; /* Check pnp ids */ return ISA_PNP_PROBE(device_get_parent(dev), dev, sbc_ids); } else { int rid = 0, ver; struct resource *io; #ifdef PC98 io = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, pcm_iat, 16, RF_ACTIVE); #else io = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 16, RF_ACTIVE); #endif if (!io) goto bad; #ifdef PC98 isa_load_resourcev(io, pcm_iat, 16); #endif if (sb_reset_dsp(io)) goto bad2; ver = sb_identify_board(io); if (ver == 0) goto bad2; switch ((ver & 0x00000f00) >> 8) { case 1: device_set_desc(dev, "SoundBlaster 1.0 (not supported)"); s = NULL; break; case 2: s = "SoundBlaster 2.0"; break; case 3: s = (ver & 0x0000f000)? "ESS 488" : "SoundBlaster Pro"; break; case 4: s = "SoundBlaster 16"; break; case 5: s = (ver & 0x00000008)? "ESS 688" : "ESS 1688"; break; } if (s) device_set_desc(dev, s); bad2: bus_release_resource(dev, SYS_RES_IOPORT, rid, io); bad: return s? 0 : ENXIO; } } static int sbc_attach(device_t dev) { char *err = NULL; struct sbc_softc *scp; struct sndcard_func *func; u_int32_t logical_id = isa_get_logicalid(dev); int flags = device_get_flags(dev); int f, dh, dl, x, irq, i; if (!logical_id && (flags & DV_F_DUAL_DMA)) { bus_set_resource(dev, SYS_RES_DRQ, 1, flags & DV_F_DRQ_MASK, 1); } scp = device_get_softc(dev); bzero(scp, sizeof(*scp)); scp->dev = dev; sbc_lockinit(scp); err = "alloc_resource"; if (alloc_resource(scp)) goto bad; err = "sb_reset_dsp"; if (sb_reset_dsp(scp->io[0])) goto bad; err = "sb_identify_board"; scp->bd_ver = sb_identify_board(scp->io[0]) & 0x00000fff; if (scp->bd_ver == 0) goto bad; f = 0; if (logical_id == 0x01200000 && scp->bd_ver < 0x0400) scp->bd_ver = 0x0499; switch ((scp->bd_ver & 0x0f00) >> 8) { case 1: /* old sound blaster has nothing... */ break; case 2: f |= BD_F_DUP_MIDI; if (scp->bd_ver > 0x200) f |= BD_F_MIX_CT1335; break; case 5: f |= BD_F_ESS; scp->bd_ver = 0x0301; case 3: f |= BD_F_DUP_MIDI | BD_F_MIX_CT1345; break; case 4: f |= BD_F_SB16 | BD_F_MIX_CT1745; if (scp->drq[0]) dl = rman_get_start(scp->drq[0]); else dl = -1; if (scp->drq[1]) dh = rman_get_start(scp->drq[1]); else dh = dl; if (!logical_id && (dh < dl)) { struct resource *r; r = scp->drq[0]; scp->drq[0] = scp->drq[1]; scp->drq[1] = r; dl = rman_get_start(scp->drq[0]); dh = rman_get_start(scp->drq[1]); } /* soft irq/dma configuration */ x = -1; irq = rman_get_start(scp->irq[0]); #ifdef PC98 /* SB16 in PC98 use different IRQ table */ if (irq == 3) x = 1; else if (irq == 5) x = 8; else if (irq == 10) x = 2; else if (irq == 12) x = 4; if (x == -1) { err = "bad irq (3/5/10/12 valid)"; goto bad; } else sb_setmixer(scp->io[0], IRQ_NR, x); /* SB16 in PC98 use different dma setting */ sb_setmixer(scp->io[0], DMA_NR, dh == 0 ? 1 : 2); #else if (irq == 5) x = 2; else if (irq == 7) x = 4; else if (irq == 9) x = 1; else if (irq == 10) x = 8; if (x == -1) { err = "bad irq (5/7/9/10 valid)"; goto bad; } else sb_setmixer(scp->io[0], IRQ_NR, x); sb_setmixer(scp->io[0], DMA_NR, (1 << dh) | (1 << dl)); #endif if (bootverbose) { device_printf(dev, "setting card to irq %d, drq %d", irq, dl); if (dl != dh) printf(", %d", dh); printf("\n"); } break; } switch (logical_id) { case 0x43008c0e: /* CTL0043 */ case 0x01200000: case 0x01000000: f |= BD_F_SB16X; break; } scp->bd_ver |= f << 16; err = "setup_intr"; for (i = 0; i < IRQ_MAX; i++) { scp->ihl[i].parent = scp; if (snd_setup_intr(dev, scp->irq[i], 0, sbc_intr, &scp->ihl[i], &scp->ih[i])) goto bad; } /* PCM Audio */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) goto bad; func->func = SCF_PCM; scp->child_pcm = device_add_child(dev, "pcm", -1); device_set_ivars(scp->child_pcm, func); /* Midi Interface */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) goto bad; func->func = SCF_MIDI; scp->child_midi1 = device_add_child(dev, "midi", -1); device_set_ivars(scp->child_midi1, func); /* OPL FM Synthesizer */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) goto bad; func->func = SCF_SYNTH; scp->child_midi2 = device_add_child(dev, "midi", -1); device_set_ivars(scp->child_midi2, func); /* probe/attach kids */ bus_generic_attach(dev); return (0); bad: if (err) device_printf(dev, "%s\n", err); release_resource(scp); return (ENXIO); } static int sbc_detach(device_t dev) { struct sbc_softc *scp = device_get_softc(dev); sbc_lock(scp); device_delete_child(dev, scp->child_midi2); device_delete_child(dev, scp->child_midi1); device_delete_child(dev, scp->child_pcm); release_resource(scp); sbc_lockdestroy(scp); return bus_generic_detach(dev); } static void sbc_intr(void *p) { struct sbc_ihl *ihl = p; int i; /* sbc_lock(ihl->parent); */ i = 0; while (i < INTR_MAX) { if (ihl->intr[i] != NULL) ihl->intr[i](ihl->intr_arg[i]); i++; } /* sbc_unlock(ihl->parent); */ } static int sbc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, -#if __FreeBSD_version >= 700031 driver_filter_t *filter, -#endif driver_intr_t *intr, void *arg, void **cookiep) { struct sbc_softc *scp = device_get_softc(dev); struct sbc_ihl *ihl = NULL; int i, ret; -#if __FreeBSD_version >= 700031 if (filter != NULL) { printf("sbc.c: we cannot use a filter here\n"); return (EINVAL); } -#endif sbc_lock(scp); i = 0; while (i < IRQ_MAX) { if (irq == scp->irq[i]) ihl = &scp->ihl[i]; i++; } ret = 0; if (ihl == NULL) ret = EINVAL; i = 0; while ((ret == 0) && (i < INTR_MAX)) { if (ihl->intr[i] == NULL) { ihl->intr[i] = intr; ihl->intr_arg[i] = arg; *cookiep = &ihl->intr[i]; ret = -1; } else i++; } sbc_unlock(scp); return (ret > 0)? EINVAL : 0; } static int sbc_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct sbc_softc *scp = device_get_softc(dev); struct sbc_ihl *ihl = NULL; int i, ret; sbc_lock(scp); i = 0; while (i < IRQ_MAX) { if (irq == scp->irq[i]) ihl = &scp->ihl[i]; i++; } ret = 0; if (ihl == NULL) ret = EINVAL; i = 0; while ((ret == 0) && (i < INTR_MAX)) { if (cookie == &ihl->intr[i]) { ihl->intr[i] = NULL; ihl->intr_arg[i] = NULL; return 0; } else i++; } sbc_unlock(scp); return (ret > 0)? EINVAL : 0; } static struct resource * sbc_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct sbc_softc *scp; int *alloced, rid_max, alloced_max; struct resource **res; #ifdef PC98 int i; #endif scp = device_get_softc(bus); switch (type) { case SYS_RES_IOPORT: alloced = scp->io_alloced; res = scp->io; #ifdef PC98 rid_max = 0; for (i = 0; i < IO_MAX; i++) rid_max += io_range[i]; #else rid_max = IO_MAX - 1; #endif alloced_max = 1; break; case SYS_RES_DRQ: alloced = scp->drq_alloced; res = scp->drq; rid_max = DRQ_MAX - 1; alloced_max = 1; break; case SYS_RES_IRQ: alloced = scp->irq_alloced; res = scp->irq; rid_max = IRQ_MAX - 1; alloced_max = INTR_MAX; /* pcm and mpu may share the irq. */ break; default: return (NULL); } if (*rid > rid_max || alloced[*rid] == alloced_max) return (NULL); alloced[*rid]++; return (res[*rid]); } static int sbc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { struct sbc_softc *scp; int *alloced, rid_max; scp = device_get_softc(bus); switch (type) { case SYS_RES_IOPORT: alloced = scp->io_alloced; rid_max = IO_MAX - 1; break; case SYS_RES_DRQ: alloced = scp->drq_alloced; rid_max = DRQ_MAX - 1; break; case SYS_RES_IRQ: alloced = scp->irq_alloced; rid_max = IRQ_MAX - 1; break; default: return (1); } if (rid > rid_max || alloced[rid] == 0) return (1); alloced[rid]--; return (0); } static int sbc_read_ivar(device_t bus, device_t dev, int index, uintptr_t * result) { struct sbc_softc *scp = device_get_softc(bus); struct sndcard_func *func = device_get_ivars(dev); switch (index) { case 0: *result = func->func; break; case 1: *result = scp->bd_ver; break; default: return ENOENT; } return 0; } static int sbc_write_ivar(device_t bus, device_t dev, int index, uintptr_t value) { switch (index) { case 0: case 1: return EINVAL; default: return (ENOENT); } } static int alloc_resource(struct sbc_softc *scp) { int i; for (i = 0 ; i < IO_MAX ; i++) { if (scp->io[i] == NULL) { #ifdef PC98 scp->io_rid[i] = i > 0 ? scp->io_rid[i - 1] + io_range[i - 1] : 0; scp->io[i] = isa_alloc_resourcev(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], sb_iat[i], io_range[i], RF_ACTIVE); if (scp->io[i] != NULL) isa_load_resourcev(scp->io[i], sb_iat[i], io_range[i]); #else scp->io_rid[i] = i; scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i], 0, ~0, io_range[i], RF_ACTIVE); #endif if (i == 0 && scp->io[i] == NULL) return (1); scp->io_alloced[i] = 0; } } for (i = 0 ; i < DRQ_MAX ; i++) { if (scp->drq[i] == NULL) { scp->drq_rid[i] = i; scp->drq[i] = bus_alloc_resource_any(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i], RF_ACTIVE); if (i == 0 && scp->drq[i] == NULL) return (1); scp->drq_alloced[i] = 0; } } for (i = 0 ; i < IRQ_MAX ; i++) { if (scp->irq[i] == NULL) { scp->irq_rid[i] = i; scp->irq[i] = bus_alloc_resource_any(scp->dev, SYS_RES_IRQ, &scp->irq_rid[i], RF_ACTIVE); if (i == 0 && scp->irq[i] == NULL) return (1); scp->irq_alloced[i] = 0; } } return (0); } static int release_resource(struct sbc_softc *scp) { int i; for (i = 0 ; i < IO_MAX ; i++) { if (scp->io[i] != NULL) { bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[i], scp->io[i]); scp->io[i] = NULL; } } for (i = 0 ; i < DRQ_MAX ; i++) { if (scp->drq[i] != NULL) { bus_release_resource(scp->dev, SYS_RES_DRQ, scp->drq_rid[i], scp->drq[i]); scp->drq[i] = NULL; } } for (i = 0 ; i < IRQ_MAX ; i++) { if (scp->irq[i] != NULL) { if (scp->ih[i] != NULL) bus_teardown_intr(scp->dev, scp->irq[i], scp->ih[i]); scp->ih[i] = NULL; bus_release_resource(scp->dev, SYS_RES_IRQ, scp->irq_rid[i], scp->irq[i]); scp->irq[i] = NULL; } } return (0); } static device_method_t sbc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sbc_probe), DEVMETHOD(device_attach, sbc_attach), DEVMETHOD(device_detach, sbc_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, sbc_read_ivar), DEVMETHOD(bus_write_ivar, sbc_write_ivar), DEVMETHOD(bus_alloc_resource, sbc_alloc_resource), DEVMETHOD(bus_release_resource, sbc_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, sbc_setup_intr), DEVMETHOD(bus_teardown_intr, sbc_teardown_intr), DEVMETHOD_END }; static driver_t sbc_driver = { "sbc", sbc_methods, sizeof(struct sbc_softc), }; /* sbc can be attached to an isa bus. */ DRIVER_MODULE(snd_sbc, isa, sbc_driver, sbc_devclass, 0, 0); DRIVER_MODULE(snd_sbc, acpi, sbc_driver, sbc_devclass, 0, 0); MODULE_DEPEND(snd_sbc, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_sbc, 1); Index: head/sys/dev/sound/midi/sequencer.c =================================================================== --- head/sys/dev/sound/midi/sequencer.c (revision 274034) +++ head/sys/dev/sound/midi/sequencer.c (revision 274035) @@ -1,2104 +1,2095 @@ /*- * Copyright (c) 2003 Mathew Kanner * Copyright (c) 1993 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The sequencer personality manager. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include /* for DELAY */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "synth_if.h" #include #define TMR_TIMERBASE 13 #define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM * synthesizer and MIDI output) */ #define SND_DEV_MUSIC 8 /* /dev/music, level 2 interface */ /* Length of a sequencer event. */ #define EV_SZ 8 #define IEV_SZ 8 /* Lookup modes */ #define LOOKUP_EXIST (0) #define LOOKUP_OPEN (1) #define LOOKUP_CLOSE (2) #define PCMMKMINOR(u, d, c) \ ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) #define MIDIMKMINOR(u, d, c) PCMMKMINOR(u, d, c) #define MIDIUNIT(y) ((dev2unit(y) >> 4) & 0x0f) #define MIDIDEV(y) (dev2unit(y) & 0x0f) /* These are the entries to the sequencer driver. */ static d_open_t seq_open; static d_close_t seq_close; static d_ioctl_t seq_ioctl; static d_read_t seq_read; static d_write_t seq_write; static d_poll_t seq_poll; static struct cdevsw seq_cdevsw = { .d_version = D_VERSION, .d_open = seq_open, .d_close = seq_close, .d_read = seq_read, .d_write = seq_write, .d_ioctl = seq_ioctl, .d_poll = seq_poll, .d_name = "sequencer", }; struct seq_softc { KOBJ_FIELDS; struct mtx seq_lock, q_lock; struct cv empty_cv, reset_cv, in_cv, out_cv, state_cv, th_cv; MIDIQ_HEAD(, u_char) in_q, out_q; u_long flags; /* Flags (protected by flag_mtx of mididev_info) */ int fflags; /* Access mode */ int music; int out_water; /* Sequence output threshould */ snd_sync_parm sync_parm; /* AIOSYNC parameter set */ struct thread *sync_thread; /* AIOSYNCing thread */ struct selinfo in_sel, out_sel; int midi_number; struct cdev *seqdev, *musicdev; int unit; int maxunits; kobj_t *midis; int *midi_flags; kobj_t mapper; void *mapper_cookie; struct timeval timerstop, timersub; int timerbase, tempo; int timerrun; int done; int playing; int recording; int busy; int pre_event_timeout; int waiting; }; /* * Module specific stuff, including how many sequecers * we currently own. */ SYSCTL_NODE(_hw_midi, OID_AUTO, seq, CTLFLAG_RD, 0, "Midi sequencer"); int seq_debug; /* XXX: should this be moved into debug.midi? */ SYSCTL_INT(_hw_midi_seq, OID_AUTO, debug, CTLFLAG_RW, &seq_debug, 0, ""); midi_cmdtab cmdtab_seqevent[] = { {SEQ_NOTEOFF, "SEQ_NOTEOFF"}, {SEQ_NOTEON, "SEQ_NOTEON"}, {SEQ_WAIT, "SEQ_WAIT"}, {SEQ_PGMCHANGE, "SEQ_PGMCHANGE"}, {SEQ_SYNCTIMER, "SEQ_SYNCTIMER"}, {SEQ_MIDIPUTC, "SEQ_MIDIPUTC"}, {SEQ_DRUMON, "SEQ_DRUMON"}, {SEQ_DRUMOFF, "SEQ_DRUMOFF"}, {SEQ_ECHO, "SEQ_ECHO"}, {SEQ_AFTERTOUCH, "SEQ_AFTERTOUCH"}, {SEQ_CONTROLLER, "SEQ_CONTROLLER"}, {SEQ_BALANCE, "SEQ_BALANCE"}, {SEQ_VOLMODE, "SEQ_VOLMODE"}, {SEQ_FULLSIZE, "SEQ_FULLSIZE"}, {SEQ_PRIVATE, "SEQ_PRIVATE"}, {SEQ_EXTENDED, "SEQ_EXTENDED"}, {EV_SEQ_LOCAL, "EV_SEQ_LOCAL"}, {EV_TIMING, "EV_TIMING"}, {EV_CHN_COMMON, "EV_CHN_COMMON"}, {EV_CHN_VOICE, "EV_CHN_VOICE"}, {EV_SYSEX, "EV_SYSEX"}, {-1, NULL}, }; midi_cmdtab cmdtab_seqioctl[] = { {SNDCTL_SEQ_RESET, "SNDCTL_SEQ_RESET"}, {SNDCTL_SEQ_SYNC, "SNDCTL_SEQ_SYNC"}, {SNDCTL_SYNTH_INFO, "SNDCTL_SYNTH_INFO"}, {SNDCTL_SEQ_CTRLRATE, "SNDCTL_SEQ_CTRLRATE"}, {SNDCTL_SEQ_GETOUTCOUNT, "SNDCTL_SEQ_GETOUTCOUNT"}, {SNDCTL_SEQ_GETINCOUNT, "SNDCTL_SEQ_GETINCOUNT"}, {SNDCTL_SEQ_PERCMODE, "SNDCTL_SEQ_PERCMODE"}, {SNDCTL_FM_LOAD_INSTR, "SNDCTL_FM_LOAD_INSTR"}, {SNDCTL_SEQ_TESTMIDI, "SNDCTL_SEQ_TESTMIDI"}, {SNDCTL_SEQ_RESETSAMPLES, "SNDCTL_SEQ_RESETSAMPLES"}, {SNDCTL_SEQ_NRSYNTHS, "SNDCTL_SEQ_NRSYNTHS"}, {SNDCTL_SEQ_NRMIDIS, "SNDCTL_SEQ_NRMIDIS"}, {SNDCTL_SEQ_GETTIME, "SNDCTL_SEQ_GETTIME"}, {SNDCTL_MIDI_INFO, "SNDCTL_MIDI_INFO"}, {SNDCTL_SEQ_THRESHOLD, "SNDCTL_SEQ_THRESHOLD"}, {SNDCTL_SYNTH_MEMAVL, "SNDCTL_SYNTH_MEMAVL"}, {SNDCTL_FM_4OP_ENABLE, "SNDCTL_FM_4OP_ENABLE"}, {SNDCTL_PMGR_ACCESS, "SNDCTL_PMGR_ACCESS"}, {SNDCTL_SEQ_PANIC, "SNDCTL_SEQ_PANIC"}, {SNDCTL_SEQ_OUTOFBAND, "SNDCTL_SEQ_OUTOFBAND"}, {SNDCTL_TMR_TIMEBASE, "SNDCTL_TMR_TIMEBASE"}, {SNDCTL_TMR_START, "SNDCTL_TMR_START"}, {SNDCTL_TMR_STOP, "SNDCTL_TMR_STOP"}, {SNDCTL_TMR_CONTINUE, "SNDCTL_TMR_CONTINUE"}, {SNDCTL_TMR_TEMPO, "SNDCTL_TMR_TEMPO"}, {SNDCTL_TMR_SOURCE, "SNDCTL_TMR_SOURCE"}, {SNDCTL_TMR_METRONOME, "SNDCTL_TMR_METRONOME"}, {SNDCTL_TMR_SELECT, "SNDCTL_TMR_SELECT"}, {SNDCTL_MIDI_PRETIME, "SNDCTL_MIDI_PRETIME"}, {AIONWRITE, "AIONWRITE"}, {AIOGSIZE, "AIOGSIZE"}, {AIOSSIZE, "AIOSSIZE"}, {AIOGFMT, "AIOGFMT"}, {AIOSFMT, "AIOSFMT"}, {AIOGMIX, "AIOGMIX"}, {AIOSMIX, "AIOSMIX"}, {AIOSTOP, "AIOSTOP"}, {AIOSYNC, "AIOSYNC"}, {AIOGCAP, "AIOGCAP"}, {-1, NULL}, }; midi_cmdtab cmdtab_timer[] = { {TMR_WAIT_REL, "TMR_WAIT_REL"}, {TMR_WAIT_ABS, "TMR_WAIT_ABS"}, {TMR_STOP, "TMR_STOP"}, {TMR_START, "TMR_START"}, {TMR_CONTINUE, "TMR_CONTINUE"}, {TMR_TEMPO, "TMR_TEMPO"}, {TMR_ECHO, "TMR_ECHO"}, {TMR_CLOCK, "TMR_CLOCK"}, {TMR_SPP, "TMR_SPP"}, {TMR_TIMESIG, "TMR_TIMESIG"}, {-1, NULL}, }; midi_cmdtab cmdtab_seqcv[] = { {MIDI_NOTEOFF, "MIDI_NOTEOFF"}, {MIDI_NOTEON, "MIDI_NOTEON"}, {MIDI_KEY_PRESSURE, "MIDI_KEY_PRESSURE"}, {-1, NULL}, }; midi_cmdtab cmdtab_seqccmn[] = { {MIDI_CTL_CHANGE, "MIDI_CTL_CHANGE"}, {MIDI_PGM_CHANGE, "MIDI_PGM_CHANGE"}, {MIDI_CHN_PRESSURE, "MIDI_CHN_PRESSURE"}, {MIDI_PITCH_BEND, "MIDI_PITCH_BEND"}, {MIDI_SYSTEM_PREFIX, "MIDI_SYSTEM_PREFIX"}, {-1, NULL}, }; #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif /* * static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m); */ static kobj_method_t seq_methods[] = { /* KOBJMETHOD(mpu_provider,mpu401_mprovider), */ KOBJMETHOD_END }; DEFINE_CLASS(sequencer, seq_methods, 0); /* The followings are the local function. */ static int seq_convertold(u_char *event, u_char *out); /* * static void seq_midiinput(struct seq_softc * scp, void *md); */ static void seq_reset(struct seq_softc *scp); static int seq_sync(struct seq_softc *scp); static int seq_processevent(struct seq_softc *scp, u_char *event); static int seq_timing(struct seq_softc *scp, u_char *event); static int seq_local(struct seq_softc *scp, u_char *event); static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event); static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event); static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event); static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md); void seq_copytoinput(struct seq_softc *scp, u_char *event, int len); int seq_modevent(module_t mod, int type, void *data); struct seq_softc *seqs[10]; static struct mtx seqinfo_mtx; static u_long nseq = 0; static void timer_start(struct seq_softc *t); static void timer_stop(struct seq_softc *t); static void timer_setvals(struct seq_softc *t, int tempo, int timerbase); static void timer_wait(struct seq_softc *t, int ticks, int wait_abs); static int timer_now(struct seq_softc *t); static void timer_start(struct seq_softc *t) { t->timerrun = 1; getmicrotime(&t->timersub); } static void timer_continue(struct seq_softc *t) { struct timeval now; if (t->timerrun == 1) return; t->timerrun = 1; getmicrotime(&now); timevalsub(&now, &t->timerstop); timevaladd(&t->timersub, &now); } static void timer_stop(struct seq_softc *t) { t->timerrun = 0; getmicrotime(&t->timerstop); } static void timer_setvals(struct seq_softc *t, int tempo, int timerbase) { t->tempo = tempo; t->timerbase = timerbase; } static void timer_wait(struct seq_softc *t, int ticks, int wait_abs) { struct timeval now, when; int ret; unsigned long long i; while (t->timerrun == 0) { SEQ_DEBUG(2, printf("Timer wait when timer isn't running\n")); /* * The old sequencer used timeouts that only increased * the timer when the timer was running. * Hence the sequencer would stick (?) if the * timer was disabled. */ cv_wait(&t->reset_cv, &t->seq_lock); if (t->playing == 0) return; } i = ticks * 60ull * 1000000ull / (t->tempo * t->timerbase); when.tv_sec = i / 1000000; when.tv_usec = i % 1000000; #if 0 printf("timer_wait tempo %d timerbase %d ticks %d abs %d u_sec %llu\n", t->tempo, t->timerbase, ticks, wait_abs, i); #endif if (wait_abs != 0) { getmicrotime(&now); timevalsub(&now, &t->timersub); timevalsub(&when, &now); } if (when.tv_sec < 0 || when.tv_usec < 0) { SEQ_DEBUG(3, printf("seq_timer error negative time %lds.%06lds\n", (long)when.tv_sec, (long)when.tv_usec)); return; } i = when.tv_sec * 1000000ull; i += when.tv_usec; i *= hz; i /= 1000000ull; #if 0 printf("seq_timer usec %llu ticks %llu\n", when.tv_sec * 1000000ull + when.tv_usec, i); #endif t->waiting = 1; ret = cv_timedwait(&t->reset_cv, &t->seq_lock, i + 1); t->waiting = 0; if (ret != EWOULDBLOCK) SEQ_DEBUG(3, printf("seq_timer didn't timeout\n")); } static int timer_now(struct seq_softc *t) { struct timeval now; unsigned long long i; int ret; if (t->timerrun == 0) now = t->timerstop; else getmicrotime(&now); timevalsub(&now, &t->timersub); i = now.tv_sec * 1000000ull; i += now.tv_usec; i *= t->timerbase; /* i /= t->tempo; */ i /= 1000000ull; ret = i; /* * printf("timer_now: %llu %d\n", i, ret); */ return ret; } static void seq_eventthread(void *arg) { struct seq_softc *scp = arg; char event[EV_SZ]; mtx_lock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_eventthread started\n")); while (scp->done == 0) { restart: while (scp->playing == 0) { cv_wait(&scp->state_cv, &scp->seq_lock); if (scp->done) goto done; } while (MIDIQ_EMPTY(scp->out_q)) { cv_broadcast(&scp->empty_cv); cv_wait(&scp->out_cv, &scp->seq_lock); if (scp->playing == 0) goto restart; if (scp->done) goto done; } MIDIQ_DEQ(scp->out_q, event, EV_SZ); if (MIDIQ_AVAIL(scp->out_q) < scp->out_water) { cv_broadcast(&scp->out_cv); selwakeup(&scp->out_sel); } seq_processevent(scp, event); } done: cv_broadcast(&scp->th_cv); mtx_unlock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_eventthread finished\n")); -#if __FreeBSD_version >= 800002 kproc_exit(0); -#else - mtx_lock(&Giant); - kthread_exit(0); -#endif } /* * seq_processevent: This maybe called by the event thread or the IOCTL * handler for queued and out of band events respectively. */ static int seq_processevent(struct seq_softc *scp, u_char *event) { int ret; kobj_t m; ret = 0; if (event[0] == EV_SEQ_LOCAL) ret = seq_local(scp, event); else if (event[0] == EV_TIMING) ret = seq_timing(scp, event); else if (event[0] != EV_CHN_VOICE && event[0] != EV_CHN_COMMON && event[0] != EV_SYSEX && event[0] != SEQ_MIDIPUTC) { ret = 1; SEQ_DEBUG(2, printf("seq_processevent not known %d\n", event[0])); } else if (seq_fetch_mid(scp, event[1], &m) != 0) { ret = 1; SEQ_DEBUG(2, printf("seq_processevent midi unit not found %d\n", event[1])); } else switch (event[0]) { case EV_CHN_VOICE: ret = seq_chnvoice(scp, m, event); break; case EV_CHN_COMMON: ret = seq_chncommon(scp, m, event); break; case EV_SYSEX: ret = seq_sysex(scp, m, event); break; case SEQ_MIDIPUTC: mtx_unlock(&scp->seq_lock); ret = SYNTH_WRITERAW(m, &event[2], 1); mtx_lock(&scp->seq_lock); break; } return ret; } static int seq_addunit(void) { struct seq_softc *scp; int ret; u_char *buf; /* Allocate the softc. */ ret = ENOMEM; scp = malloc(sizeof(*scp), M_DEVBUF, M_NOWAIT | M_ZERO); if (scp == NULL) { SEQ_DEBUG(1, printf("seq_addunit: softc allocation failed.\n")); goto err; } kobj_init((kobj_t)scp, &sequencer_class); buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); if (buf == NULL) goto err; MIDIQ_INIT(scp->in_q, buf, EV_SZ * 1024); buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); if (buf == NULL) goto err; MIDIQ_INIT(scp->out_q, buf, EV_SZ * 1024); ret = EINVAL; scp->midis = malloc(sizeof(kobj_t) * 32, M_TEMP, M_NOWAIT | M_ZERO); scp->midi_flags = malloc(sizeof(*scp->midi_flags) * 32, M_TEMP, M_NOWAIT | M_ZERO); if (scp->midis == NULL || scp->midi_flags == NULL) goto err; scp->flags = 0; mtx_init(&scp->seq_lock, "seqflq", NULL, 0); cv_init(&scp->state_cv, "seqstate"); cv_init(&scp->empty_cv, "seqempty"); cv_init(&scp->reset_cv, "seqtimer"); cv_init(&scp->out_cv, "seqqout"); cv_init(&scp->in_cv, "seqqin"); cv_init(&scp->th_cv, "seqstart"); /* * Init the damn timer */ scp->mapper = midimapper_addseq(scp, &scp->unit, &scp->mapper_cookie); if (scp->mapper == NULL) goto err; scp->seqdev = make_dev(&seq_cdevsw, MIDIMKMINOR(scp->unit, SND_DEV_SEQ, 0), UID_ROOT, GID_WHEEL, 0666, "sequencer%d", scp->unit); scp->musicdev = make_dev(&seq_cdevsw, MIDIMKMINOR(scp->unit, SND_DEV_MUSIC, 0), UID_ROOT, GID_WHEEL, 0666, "music%d", scp->unit); if (scp->seqdev == NULL || scp->musicdev == NULL) goto err; /* * TODO: Add to list of sequencers this module provides */ ret = -#if __FreeBSD_version >= 800002 kproc_create -#else - kthread_create -#endif (seq_eventthread, scp, NULL, RFHIGHPID, 0, "sequencer %02d", scp->unit); if (ret) goto err; scp->seqdev->si_drv1 = scp->musicdev->si_drv1 = scp; SEQ_DEBUG(2, printf("sequencer %d created scp %p\n", scp->unit, scp)); ret = 0; mtx_lock(&seqinfo_mtx); seqs[nseq++] = scp; mtx_unlock(&seqinfo_mtx); goto ok; err: if (scp != NULL) { if (scp->seqdev != NULL) destroy_dev(scp->seqdev); if (scp->musicdev != NULL) destroy_dev(scp->musicdev); /* * TODO: Destroy mutex and cv */ if (scp->midis != NULL) free(scp->midis, M_TEMP); if (scp->midi_flags != NULL) free(scp->midi_flags, M_TEMP); if (scp->out_q.b) free(scp->out_q.b, M_TEMP); if (scp->in_q.b) free(scp->in_q.b, M_TEMP); free(scp, M_DEVBUF); } ok: return ret; } static int seq_delunit(int unit) { struct seq_softc *scp = seqs[unit]; int i; //SEQ_DEBUG(4, printf("seq_delunit: %d\n", unit)); SEQ_DEBUG(1, printf("seq_delunit: 1 \n")); mtx_lock(&scp->seq_lock); scp->playing = 0; scp->done = 1; cv_broadcast(&scp->out_cv); cv_broadcast(&scp->state_cv); cv_broadcast(&scp->reset_cv); SEQ_DEBUG(1, printf("seq_delunit: 2 \n")); cv_wait(&scp->th_cv, &scp->seq_lock); SEQ_DEBUG(1, printf("seq_delunit: 3.0 \n")); mtx_unlock(&scp->seq_lock); SEQ_DEBUG(1, printf("seq_delunit: 3.1 \n")); cv_destroy(&scp->state_cv); SEQ_DEBUG(1, printf("seq_delunit: 4 \n")); cv_destroy(&scp->empty_cv); SEQ_DEBUG(1, printf("seq_delunit: 5 \n")); cv_destroy(&scp->reset_cv); SEQ_DEBUG(1, printf("seq_delunit: 6 \n")); cv_destroy(&scp->out_cv); SEQ_DEBUG(1, printf("seq_delunit: 7 \n")); cv_destroy(&scp->in_cv); SEQ_DEBUG(1, printf("seq_delunit: 8 \n")); cv_destroy(&scp->th_cv); SEQ_DEBUG(1, printf("seq_delunit: 10 \n")); if (scp->seqdev) destroy_dev(scp->seqdev); SEQ_DEBUG(1, printf("seq_delunit: 11 \n")); if (scp->musicdev) destroy_dev(scp->musicdev); SEQ_DEBUG(1, printf("seq_delunit: 12 \n")); scp->seqdev = scp->musicdev = NULL; if (scp->midis != NULL) free(scp->midis, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 13 \n")); if (scp->midi_flags != NULL) free(scp->midi_flags, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 14 \n")); free(scp->out_q.b, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 15 \n")); free(scp->in_q.b, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 16 \n")); mtx_destroy(&scp->seq_lock); SEQ_DEBUG(1, printf("seq_delunit: 17 \n")); free(scp, M_DEVBUF); mtx_lock(&seqinfo_mtx); for (i = unit; i < (nseq - 1); i++) seqs[i] = seqs[i + 1]; nseq--; mtx_unlock(&seqinfo_mtx); return 0; } int seq_modevent(module_t mod, int type, void *data) { int retval, r; retval = 0; switch (type) { case MOD_LOAD: mtx_init(&seqinfo_mtx, "seqmod", NULL, 0); retval = seq_addunit(); break; case MOD_UNLOAD: while (nseq) { r = seq_delunit(nseq - 1); if (r) { retval = r; break; } } if (nseq == 0) { retval = 0; mtx_destroy(&seqinfo_mtx); } break; default: break; } return retval; } static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md) { if (unit > scp->midi_number || unit < 0) return EINVAL; *md = scp->midis[unit]; return 0; } int seq_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct seq_softc *scp = i_dev->si_drv1; int i; if (scp == NULL) return ENXIO; SEQ_DEBUG(3, printf("seq_open: scp %p unit %d, flags 0x%x.\n", scp, scp->unit, flags)); /* * Mark this device busy. */ mtx_lock(&scp->seq_lock); if (scp->busy) { mtx_unlock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_open: unit %d is busy.\n", scp->unit)); return EBUSY; } scp->fflags = flags; /* if ((scp->fflags & O_NONBLOCK) != 0) scp->flags |= SEQ_F_NBIO; */ scp->music = MIDIDEV(i_dev) == SND_DEV_MUSIC; /* * Enumerate the available midi devices */ scp->midi_number = 0; scp->maxunits = midimapper_open(scp->mapper, &scp->mapper_cookie); if (scp->maxunits == 0) SEQ_DEBUG(2, printf("seq_open: no midi devices\n")); for (i = 0; i < scp->maxunits; i++) { scp->midis[scp->midi_number] = midimapper_fetch_synth(scp->mapper, scp->mapper_cookie, i); if (scp->midis[scp->midi_number]) { if (SYNTH_OPEN(scp->midis[scp->midi_number], scp, scp->fflags) != 0) scp->midis[scp->midi_number] = NULL; else { scp->midi_flags[scp->midi_number] = SYNTH_QUERY(scp->midis[scp->midi_number]); scp->midi_number++; } } } timer_setvals(scp, 60, 100); timer_start(scp); timer_stop(scp); /* * actually, if we're in rdonly mode, we should start the timer */ /* * TODO: Handle recording now */ scp->out_water = MIDIQ_SIZE(scp->out_q) / 2; scp->busy = 1; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_open: opened, mode %s.\n", scp->music ? "music" : "sequencer")); SEQ_DEBUG(2, printf("Sequencer %d %p opened maxunits %d midi_number %d:\n", scp->unit, scp, scp->maxunits, scp->midi_number)); for (i = 0; i < scp->midi_number; i++) SEQ_DEBUG(3, printf(" midi %d %p\n", i, scp->midis[i])); return 0; } /* * seq_close */ int seq_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { int i; struct seq_softc *scp = i_dev->si_drv1; int ret; if (scp == NULL) return ENXIO; SEQ_DEBUG(2, printf("seq_close: unit %d.\n", scp->unit)); mtx_lock(&scp->seq_lock); ret = ENXIO; if (scp->busy == 0) goto err; seq_reset(scp); seq_sync(scp); for (i = 0; i < scp->midi_number; i++) if (scp->midis[i]) SYNTH_CLOSE(scp->midis[i]); midimapper_close(scp->mapper, scp->mapper_cookie); timer_stop(scp); scp->busy = 0; ret = 0; err: SEQ_DEBUG(3, printf("seq_close: closed ret = %d.\n", ret)); mtx_unlock(&scp->seq_lock); return ret; } int seq_read(struct cdev *i_dev, struct uio *uio, int ioflag) { int retval, used; struct seq_softc *scp = i_dev->si_drv1; #define SEQ_RSIZE 32 u_char buf[SEQ_RSIZE]; if (scp == NULL) return ENXIO; SEQ_DEBUG(7, printf("seq_read: unit %d, resid %zd.\n", scp->unit, uio->uio_resid)); mtx_lock(&scp->seq_lock); if ((scp->fflags & FREAD) == 0) { SEQ_DEBUG(2, printf("seq_read: unit %d is not for reading.\n", scp->unit)); retval = EIO; goto err1; } /* * Begin recording. */ /* * if ((scp->flags & SEQ_F_READING) == 0) */ /* * TODO, start recording if not alread */ /* * I think the semantics are to return as soon * as possible. * Second thought, it doens't seem like midimoutain * expects that at all. * TODO: Look up in some sort of spec */ while (uio->uio_resid > 0) { while (MIDIQ_EMPTY(scp->in_q)) { retval = EWOULDBLOCK; /* * I wish I knew which one to care about */ if (scp->fflags & O_NONBLOCK) goto err1; if (ioflag & O_NONBLOCK) goto err1; retval = cv_wait_sig(&scp->in_cv, &scp->seq_lock); if (retval == EINTR) goto err1; } used = MIN(MIDIQ_LEN(scp->in_q), uio->uio_resid); used = MIN(used, SEQ_RSIZE); SEQ_DEBUG(8, printf("midiread: uiomove cc=%d\n", used)); MIDIQ_DEQ(scp->in_q, buf, used); retval = uiomove(buf, used, uio); if (retval) goto err1; } retval = 0; err1: mtx_unlock(&scp->seq_lock); SEQ_DEBUG(6, printf("seq_read: ret %d, resid %zd.\n", retval, uio->uio_resid)); return retval; } int seq_write(struct cdev *i_dev, struct uio *uio, int ioflag) { u_char event[EV_SZ], newevent[EV_SZ], ev_code; struct seq_softc *scp = i_dev->si_drv1; int retval; int used; SEQ_DEBUG(7, printf("seq_write: unit %d, resid %zd.\n", scp->unit, uio->uio_resid)); if (scp == NULL) return ENXIO; mtx_lock(&scp->seq_lock); if ((scp->fflags & FWRITE) == 0) { SEQ_DEBUG(2, printf("seq_write: unit %d is not for writing.\n", scp->unit)); retval = EIO; goto err0; } while (uio->uio_resid > 0) { while (MIDIQ_AVAIL(scp->out_q) == 0) { retval = EWOULDBLOCK; if (scp->fflags & O_NONBLOCK) goto err0; if (ioflag & O_NONBLOCK) goto err0; SEQ_DEBUG(8, printf("seq_write cvwait\n")); scp->playing = 1; cv_broadcast(&scp->out_cv); cv_broadcast(&scp->state_cv); retval = cv_wait_sig(&scp->out_cv, &scp->seq_lock); /* * We slept, maybe things have changed since last * dying check */ if (retval == EINTR) goto err0; #if 0 /* * Useless test */ if (scp != i_dev->si_drv1) retval = ENXIO; #endif } used = MIN(uio->uio_resid, 4); SEQ_DEBUG(8, printf("seqout: resid %zd len %jd avail %jd\n", uio->uio_resid, (intmax_t)MIDIQ_LEN(scp->out_q), (intmax_t)MIDIQ_AVAIL(scp->out_q))); if (used != 4) { retval = ENXIO; goto err0; } retval = uiomove(event, used, uio); if (retval) goto err0; ev_code = event[0]; SEQ_DEBUG(8, printf("seq_write: unit %d, event %s.\n", scp->unit, midi_cmdname(ev_code, cmdtab_seqevent))); /* Have a look at the event code. */ if (ev_code == SEQ_FULLSIZE) { /* * TODO: restore code for SEQ_FULLSIZE */ #if 0 /* * A long event, these are the patches/samples for a * synthesizer. */ midiunit = *(u_short *)&event[2]; mtx_lock(&sd->seq_lock); ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); mtx_unlock(&sd->seq_lock); if (ret != 0) return (ret); SEQ_DEBUG(printf("seq_write: loading a patch to the unit %d.\n", midiunit)); ret = md->synth.loadpatch(md, *(short *)&event[0], buf, p + 4, count, 0); return (ret); #else /* * For now, just flush the darn buffer */ SEQ_DEBUG(2, printf("seq_write: SEQ_FULLSIZE flusing buffer.\n")); while (uio->uio_resid > 0) { retval = uiomove(event, EV_SZ, uio); if (retval) goto err0; } retval = 0; goto err0; #endif } retval = EINVAL; if (ev_code >= 128) { /* * Some sort of an extended event. The size is eight * bytes. scoop extra info. */ if (scp->music && ev_code == SEQ_EXTENDED) { SEQ_DEBUG(2, printf("seq_write: invalid level two event %x.\n", ev_code)); goto err0; } if (uiomove((caddr_t)&event[4], 4, uio)) { SEQ_DEBUG(2, printf("seq_write: user memory mangled?\n")); goto err0; } } else { /* * Size four event. */ if (scp->music) { SEQ_DEBUG(2, printf("seq_write: four byte event in music mode.\n")); goto err0; } } if (ev_code == SEQ_MIDIPUTC) { /* * TODO: event[2] is unit number to receive char. * Range check it. */ } if (scp->music) { #ifdef not_ever_ever if (event[0] == EV_TIMING && (event[1] == TMR_START || event[1] == TMR_STOP)) { /* * For now, try to make midimoutain work by * forcing these events to be processed * immediatly. */ seq_processevent(scp, event); } else MIDIQ_ENQ(scp->out_q, event, EV_SZ); #else MIDIQ_ENQ(scp->out_q, event, EV_SZ); #endif } else { if (seq_convertold(event, newevent) > 0) MIDIQ_ENQ(scp->out_q, newevent, EV_SZ); #if 0 else goto err0; #endif } } scp->playing = 1; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); retval = 0; err0: SEQ_DEBUG(6, printf("seq_write done: leftover buffer length %zd retval %d\n", uio->uio_resid, retval)); mtx_unlock(&scp->seq_lock); return retval; } int seq_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { int midiunit, ret, tmp; struct seq_softc *scp = i_dev->si_drv1; struct synth_info *synthinfo; struct midi_info *midiinfo; u_char event[EV_SZ]; u_char newevent[EV_SZ]; kobj_t md; /* * struct snd_size *sndsize; */ if (scp == NULL) return ENXIO; SEQ_DEBUG(6, printf("seq_ioctl: unit %d, cmd %s.\n", scp->unit, midi_cmdname(cmd, cmdtab_seqioctl))); ret = 0; switch (cmd) { case SNDCTL_SEQ_GETTIME: /* * ioctl needed by libtse */ mtx_lock(&scp->seq_lock); *(int *)arg = timer_now(scp); mtx_unlock(&scp->seq_lock); SEQ_DEBUG(6, printf("seq_ioctl: gettime %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_TMR_METRONOME: /* fallthrough */ case SNDCTL_TMR_SOURCE: /* * Not implemented */ ret = 0; break; case SNDCTL_TMR_TEMPO: event[1] = TMR_TEMPO; event[4] = *(int *)arg & 0xFF; event[5] = (*(int *)arg >> 8) & 0xFF; event[6] = (*(int *)arg >> 16) & 0xFF; event[7] = (*(int *)arg >> 24) & 0xFF; goto timerevent; case SNDCTL_TMR_TIMEBASE: event[1] = TMR_TIMERBASE; event[4] = *(int *)arg & 0xFF; event[5] = (*(int *)arg >> 8) & 0xFF; event[6] = (*(int *)arg >> 16) & 0xFF; event[7] = (*(int *)arg >> 24) & 0xFF; goto timerevent; case SNDCTL_TMR_START: event[1] = TMR_START; goto timerevent; case SNDCTL_TMR_STOP: event[1] = TMR_STOP; goto timerevent; case SNDCTL_TMR_CONTINUE: event[1] = TMR_CONTINUE; timerevent: event[0] = EV_TIMING; mtx_lock(&scp->seq_lock); if (!scp->music) { ret = EINVAL; mtx_unlock(&scp->seq_lock); break; } seq_processevent(scp, event); mtx_unlock(&scp->seq_lock); break; case SNDCTL_TMR_SELECT: SEQ_DEBUG(2, printf("seq_ioctl: SNDCTL_TMR_SELECT not supported\n")); ret = EINVAL; break; case SNDCTL_SEQ_SYNC: if (mode == O_RDONLY) { ret = 0; break; } mtx_lock(&scp->seq_lock); ret = seq_sync(scp); mtx_unlock(&scp->seq_lock); break; case SNDCTL_SEQ_PANIC: /* fallthrough */ case SNDCTL_SEQ_RESET: /* * SNDCTL_SEQ_PANIC == SNDCTL_SEQ_RESET */ mtx_lock(&scp->seq_lock); seq_reset(scp); mtx_unlock(&scp->seq_lock); ret = 0; break; case SNDCTL_SEQ_TESTMIDI: mtx_lock(&scp->seq_lock); /* * TODO: SNDCTL_SEQ_TESTMIDI now means "can I write to the * device?". */ mtx_unlock(&scp->seq_lock); break; #if 0 case SNDCTL_SEQ_GETINCOUNT: if (mode == O_WRONLY) *(int *)arg = 0; else { mtx_lock(&scp->seq_lock); *(int *)arg = scp->in_q.rl; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(printf("seq_ioctl: incount %d.\n", *(int *)arg)); } ret = 0; break; case SNDCTL_SEQ_GETOUTCOUNT: if (mode == O_RDONLY) *(int *)arg = 0; else { mtx_lock(&scp->seq_lock); *(int *)arg = scp->out_q.fl; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(printf("seq_ioctl: outcount %d.\n", *(int *)arg)); } ret = 0; break; #endif case SNDCTL_SEQ_CTRLRATE: if (*(int *)arg != 0) { ret = EINVAL; break; } mtx_lock(&scp->seq_lock); *(int *)arg = scp->timerbase; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg)); ret = 0; break; /* * TODO: ioctl SNDCTL_SEQ_RESETSAMPLES */ #if 0 case SNDCTL_SEQ_RESETSAMPLES: mtx_lock(&scp->seq_lock); ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); mtx_unlock(&scp->seq_lock); if (ret != 0) break; ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); break; #endif case SNDCTL_SEQ_NRSYNTHS: mtx_lock(&scp->seq_lock); *(int *)arg = scp->midi_number; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: synths %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_SEQ_NRMIDIS: mtx_lock(&scp->seq_lock); if (scp->music) *(int *)arg = 0; else { /* * TODO: count the numbder of devices that can WRITERAW */ *(int *)arg = scp->midi_number; } mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: midis %d.\n", *(int *)arg)); ret = 0; break; /* * TODO: ioctl SNDCTL_SYNTH_MEMAVL */ #if 0 case SNDCTL_SYNTH_MEMAVL: mtx_lock(&scp->seq_lock); ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); mtx_unlock(&scp->seq_lock); if (ret != 0) break; ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); break; #endif case SNDCTL_SEQ_OUTOFBAND: for (ret = 0; ret < EV_SZ; ret++) event[ret] = (u_char)arg[0]; mtx_lock(&scp->seq_lock); if (scp->music) ret = seq_processevent(scp, event); else { if (seq_convertold(event, newevent) > 0) ret = seq_processevent(scp, newevent); else ret = EINVAL; } mtx_unlock(&scp->seq_lock); break; case SNDCTL_SYNTH_INFO: synthinfo = (struct synth_info *)arg; midiunit = synthinfo->device; mtx_lock(&scp->seq_lock); if (seq_fetch_mid(scp, midiunit, &md) == 0) { bzero(synthinfo, sizeof(*synthinfo)); synthinfo->name[0] = 'f'; synthinfo->name[1] = 'a'; synthinfo->name[2] = 'k'; synthinfo->name[3] = 'e'; synthinfo->name[4] = 's'; synthinfo->name[5] = 'y'; synthinfo->name[6] = 'n'; synthinfo->name[7] = 't'; synthinfo->name[8] = 'h'; synthinfo->device = midiunit; synthinfo->synth_type = SYNTH_TYPE_MIDI; synthinfo->capabilities = scp->midi_flags[midiunit]; ret = 0; } else ret = EINVAL; mtx_unlock(&scp->seq_lock); break; case SNDCTL_MIDI_INFO: midiinfo = (struct midi_info *)arg; midiunit = midiinfo->device; mtx_lock(&scp->seq_lock); if (seq_fetch_mid(scp, midiunit, &md) == 0) { bzero(midiinfo, sizeof(*midiinfo)); midiinfo->name[0] = 'f'; midiinfo->name[1] = 'a'; midiinfo->name[2] = 'k'; midiinfo->name[3] = 'e'; midiinfo->name[4] = 'm'; midiinfo->name[5] = 'i'; midiinfo->name[6] = 'd'; midiinfo->name[7] = 'i'; midiinfo->device = midiunit; midiinfo->capabilities = scp->midi_flags[midiunit]; /* * TODO: What devtype? */ midiinfo->dev_type = 0x01; ret = 0; } else ret = EINVAL; mtx_unlock(&scp->seq_lock); break; case SNDCTL_SEQ_THRESHOLD: mtx_lock(&scp->seq_lock); RANGE(*(int *)arg, 1, MIDIQ_SIZE(scp->out_q) - 1); scp->out_water = *(int *)arg; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: water %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_MIDI_PRETIME: tmp = *(int *)arg; if (tmp < 0) tmp = 0; mtx_lock(&scp->seq_lock); scp->pre_event_timeout = (hz * tmp) / 10; *(int *)arg = scp->pre_event_timeout; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: pretime %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_FM_4OP_ENABLE: case SNDCTL_PMGR_IFACE: case SNDCTL_PMGR_ACCESS: /* * Patch manager and fm are ded, ded, ded. */ /* fallthrough */ default: /* * TODO: Consider ioctl default case. * Old code used to * if ((scp->fflags & O_ACCMODE) == FREAD) { * ret = EIO; * break; * } * Then pass on the ioctl to device 0 */ SEQ_DEBUG(2, printf("seq_ioctl: unsupported IOCTL %ld.\n", cmd)); ret = EINVAL; break; } return ret; } int seq_poll(struct cdev *i_dev, int events, struct thread *td) { int ret, lim; struct seq_softc *scp = i_dev->si_drv1; SEQ_DEBUG(3, printf("seq_poll: unit %d.\n", scp->unit)); SEQ_DEBUG(1, printf("seq_poll: unit %d.\n", scp->unit)); mtx_lock(&scp->seq_lock); ret = 0; /* Look up the apropriate queue and select it. */ if ((events & (POLLOUT | POLLWRNORM)) != 0) { /* Start playing. */ scp->playing = 1; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); lim = scp->out_water; if (MIDIQ_AVAIL(scp->out_q) < lim) /* No enough space, record select. */ selrecord(td, &scp->out_sel); else /* We can write now. */ ret |= events & (POLLOUT | POLLWRNORM); } if ((events & (POLLIN | POLLRDNORM)) != 0) { /* TODO: Start recording. */ /* Find out the boundary. */ lim = 1; if (MIDIQ_LEN(scp->in_q) < lim) /* No data ready, record select. */ selrecord(td, &scp->in_sel); else /* We can read now. */ ret |= events & (POLLIN | POLLRDNORM); } mtx_unlock(&scp->seq_lock); return (ret); } #if 0 static void sein_qtr(void *p, void /* mididev_info */ *md) { struct seq_softc *scp; scp = (struct seq_softc *)p; mtx_lock(&scp->seq_lock); /* Restart playing if we have the data to output. */ if (scp->queueout_pending) seq_callback(scp, SEQ_CB_START | SEQ_CB_WR); /* Check the midi device if we are reading. */ if ((scp->flags & SEQ_F_READING) != 0) seq_midiinput(scp, md); mtx_unlock(&scp->seq_lock); } #endif /* * seq_convertold * Was the old playevent. Use this to convert and old * style /dev/sequencer event to a /dev/music event */ static int seq_convertold(u_char *event, u_char *out) { int used; u_char dev, chn, note, vel; out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = out[7] = 0; dev = 0; chn = event[1]; note = event[2]; vel = event[3]; used = 0; restart: /* * TODO: Debug statement */ switch (event[0]) { case EV_TIMING: case EV_CHN_VOICE: case EV_CHN_COMMON: case EV_SYSEX: case EV_SEQ_LOCAL: out[0] = event[0]; out[1] = event[1]; out[2] = event[2]; out[3] = event[3]; out[4] = event[4]; out[5] = event[5]; out[6] = event[6]; out[7] = event[7]; used += 8; break; case SEQ_NOTEOFF: out[0] = EV_CHN_VOICE; out[1] = dev; out[2] = MIDI_NOTEOFF; out[3] = chn; out[4] = note; out[5] = 255; used += 4; break; case SEQ_NOTEON: out[0] = EV_CHN_VOICE; out[1] = dev; out[2] = MIDI_NOTEON; out[3] = chn; out[4] = note; out[5] = vel; used += 4; break; /* * wait delay = (event[2] << 16) + (event[3] << 8) + event[4] */ case SEQ_PGMCHANGE: out[0] = EV_CHN_COMMON; out[1] = dev; out[2] = MIDI_PGM_CHANGE; out[3] = chn; out[4] = note; out[5] = vel; used += 4; break; /* out[0] = EV_TIMING; out[1] = dev; out[2] = MIDI_PGM_CHANGE; out[3] = chn; out[4] = note; out[5] = vel; SEQ_DEBUG(4,printf("seq_playevent: synctimer\n")); break; */ case SEQ_MIDIPUTC: SEQ_DEBUG(4, printf("seq_playevent: put data 0x%02x, unit %d.\n", event[1], event[2])); /* * Pass through to the midi device. * device = event[2] * data = event[1] */ out[0] = SEQ_MIDIPUTC; out[1] = dev; out[2] = chn; used += 4; break; #ifdef notyet case SEQ_ECHO: /* * This isn't handled here yet because I don't know if I can * just use four bytes events. There might be consequences * in the _read routing */ if (seq_copytoinput(scp, event, 4) == EAGAIN) { ret = QUEUEFULL; break; } ret = MORE; break; #endif case SEQ_EXTENDED: switch (event[1]) { case SEQ_NOTEOFF: case SEQ_NOTEON: case SEQ_PGMCHANGE: event++; used = 4; goto restart; break; case SEQ_AFTERTOUCH: /* * SYNTH_AFTERTOUCH(md, event[3], event[4]) */ case SEQ_BALANCE: /* * SYNTH_PANNING(md, event[3], (char)event[4]) */ case SEQ_CONTROLLER: /* * SYNTH_CONTROLLER(md, event[3], event[4], *(short *)&event[5]) */ case SEQ_VOLMODE: /* * SYNTH_VOLUMEMETHOD(md, event[3]) */ default: SEQ_DEBUG(2, printf("seq_convertold: SEQ_EXTENDED type %d" "not handled\n", event[1])); break; } break; case SEQ_WAIT: out[0] = EV_TIMING; out[1] = TMR_WAIT_REL; out[4] = event[2]; out[5] = event[3]; out[6] = event[4]; SEQ_DEBUG(5, printf("SEQ_WAIT %d", event[2] + (event[3] << 8) + (event[4] << 24))); used += 4; break; case SEQ_ECHO: case SEQ_SYNCTIMER: case SEQ_PRIVATE: default: SEQ_DEBUG(2, printf("seq_convertold: event type %d not handled %d %d %d\n", event[0], event[1], event[2], event[3])); break; } return used; } /* * Writting to the sequencer buffer never blocks and drops * input which cannot be queued */ void seq_copytoinput(struct seq_softc *scp, u_char *event, int len) { mtx_assert(&scp->seq_lock, MA_OWNED); if (MIDIQ_AVAIL(scp->in_q) < len) { /* * ENOROOM? EINPUTDROPPED? ETOUGHLUCK? */ SEQ_DEBUG(2, printf("seq_copytoinput: queue full\n")); } else { MIDIQ_ENQ(scp->in_q, event, len); selwakeup(&scp->in_sel); cv_broadcast(&scp->in_cv); } } static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event) { int ret, voice; u_char cmd, chn, note, parm; ret = 0; cmd = event[2]; chn = event[3]; note = event[4]; parm = event[5]; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_chnvoice: unit %d, dev %d, cmd %s," " chn %d, note %d, parm %d.\n", scp->unit, event[1], midi_cmdname(cmd, cmdtab_seqcv), chn, note, parm)); voice = SYNTH_ALLOC(md, chn, note); mtx_unlock(&scp->seq_lock); switch (cmd) { case MIDI_NOTEON: if (note < 128 || note == 255) { #if 0 if (scp->music && chn == 9) { /* * This channel is a percussion. The note * number is the patch number. */ /* mtx_unlock(&scp->seq_lock); if (SYNTH_SETINSTR(md, voice, 128 + note) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); */ note = 60; /* Middle C. */ } #endif if (scp->music) { /* mtx_unlock(&scp->seq_lock); if (SYNTH_SETUPVOICE(md, voice, chn) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); */ } SYNTH_STARTNOTE(md, voice, note, parm); } break; case MIDI_NOTEOFF: SYNTH_KILLNOTE(md, voice, note, parm); break; case MIDI_KEY_PRESSURE: SYNTH_AFTERTOUCH(md, voice, parm); break; default: ret = 1; SEQ_DEBUG(2, printf("seq_chnvoice event type %d not handled\n", event[1])); break; } mtx_lock(&scp->seq_lock); return ret; } static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event) { int ret; u_short w14; u_char cmd, chn, p1; ret = 0; cmd = event[2]; chn = event[3]; p1 = event[4]; w14 = *(u_short *)&event[6]; SEQ_DEBUG(5, printf("seq_chncommon: unit %d, dev %d, cmd %s, chn %d," " p1 %d, w14 %d.\n", scp->unit, event[1], midi_cmdname(cmd, cmdtab_seqccmn), chn, p1, w14)); mtx_unlock(&scp->seq_lock); switch (cmd) { case MIDI_PGM_CHANGE: SEQ_DEBUG(4, printf("seq_chncommon pgmchn chn %d pg %d\n", chn, p1)); SYNTH_SETINSTR(md, chn, p1); break; case MIDI_CTL_CHANGE: SEQ_DEBUG(4, printf("seq_chncommon ctlch chn %d pg %d %d\n", chn, p1, w14)); SYNTH_CONTROLLER(md, chn, p1, w14); break; case MIDI_PITCH_BEND: if (scp->music) { /* * TODO: MIDI_PITCH_BEND */ #if 0 mtx_lock(&md->synth.vc_mtx); md->synth.chn_info[chn].bender_value = w14; if (md->midiunit >= 0) { /* * Handle all of the notes playing on this * channel. */ key = ((int)chn << 8); for (i = 0; i < md->synth.alloc.max_voice; i++) if ((md->synth.alloc.map[i] & 0xff00) == key) { mtx_unlock(&md->synth.vc_mtx); mtx_unlock(&scp->seq_lock); if (md->synth.bender(md, i, w14) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); } } else { mtx_unlock(&md->synth.vc_mtx); mtx_unlock(&scp->seq_lock); if (md->synth.bender(md, chn, w14) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); } #endif } else SYNTH_BENDER(md, chn, w14); break; default: ret = 1; SEQ_DEBUG(2, printf("seq_chncommon event type %d not handled.\n", event[1])); break; } mtx_lock(&scp->seq_lock); return ret; } static int seq_timing(struct seq_softc *scp, u_char *event) { int param; int ret; ret = 0; param = event[4] + (event[5] << 8) + (event[6] << 16) + (event[7] << 24); SEQ_DEBUG(5, printf("seq_timing: unit %d, cmd %d, param %d.\n", scp->unit, event[1], param)); switch (event[1]) { case TMR_WAIT_REL: timer_wait(scp, param, 0); break; case TMR_WAIT_ABS: timer_wait(scp, param, 1); break; case TMR_START: timer_start(scp); cv_broadcast(&scp->reset_cv); break; case TMR_STOP: timer_stop(scp); /* * The following cv_broadcast isn't needed since we only * wait for 0->1 transitions. It probably won't hurt */ cv_broadcast(&scp->reset_cv); break; case TMR_CONTINUE: timer_continue(scp); cv_broadcast(&scp->reset_cv); break; case TMR_TEMPO: if (param < 8) param = 8; if (param > 360) param = 360; SEQ_DEBUG(4, printf("Timer set tempo %d\n", param)); timer_setvals(scp, param, scp->timerbase); break; case TMR_TIMERBASE: if (param < 1) param = 1; if (param > 1000) param = 1000; SEQ_DEBUG(4, printf("Timer set timerbase %d\n", param)); timer_setvals(scp, scp->tempo, param); break; case TMR_ECHO: /* * TODO: Consider making 4-byte events for /dev/sequencer * PRO: Maybe needed by legacy apps * CON: soundcard.h has been warning for a while many years * to expect 8 byte events. */ #if 0 if (scp->music) seq_copytoinput(scp, event, 8); else { param = (param << 8 | SEQ_ECHO); seq_copytoinput(scp, (u_char *)¶m, 4); } #else seq_copytoinput(scp, event, 8); #endif break; default: SEQ_DEBUG(2, printf("seq_timing event type %d not handled.\n", event[1])); ret = 1; break; } return ret; } static int seq_local(struct seq_softc *scp, u_char *event) { int ret; ret = 0; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_local: unit %d, cmd %d\n", scp->unit, event[1])); switch (event[1]) { default: SEQ_DEBUG(1, printf("seq_local event type %d not handled\n", event[1])); ret = 1; break; } return ret; } static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event) { int i, l; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_sysex: unit %d device %d\n", scp->unit, event[1])); l = 0; for (i = 0; i < 6 && event[i + 2] != 0xff; i++) l = i + 1; if (l > 0) { mtx_unlock(&scp->seq_lock); if (SYNTH_SENDSYSEX(md, &event[2], l) == EAGAIN) { mtx_lock(&scp->seq_lock); return 1; } mtx_lock(&scp->seq_lock); } return 0; } /* * Reset no longer closes the raw devices nor seq_sync's * Callers are IOCTL and seq_close */ static void seq_reset(struct seq_softc *scp) { int chn, i; kobj_t m; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_reset: unit %d.\n", scp->unit)); /* * Stop reading and writing. */ /* scp->recording = 0; */ scp->playing = 0; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); cv_broadcast(&scp->reset_cv); /* * For now, don't reset the timers. */ MIDIQ_CLEAR(scp->in_q); MIDIQ_CLEAR(scp->out_q); for (i = 0; i < scp->midi_number; i++) { m = scp->midis[i]; mtx_unlock(&scp->seq_lock); SYNTH_RESET(m); for (chn = 0; chn < 16; chn++) { SYNTH_CONTROLLER(m, chn, 123, 0); SYNTH_CONTROLLER(m, chn, 121, 0); SYNTH_BENDER(m, chn, 1 << 13); } mtx_lock(&scp->seq_lock); } } /* * seq_sync * *really* flush the output queue * flush the event queue, then flush the synthsisers. * Callers are IOCTL and close */ #define SEQ_SYNC_TIMEOUT 8 static int seq_sync(struct seq_softc *scp) { int i, rl, sync[16], done; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(4, printf("seq_sync: unit %d.\n", scp->unit)); /* * Wait until output queue is empty. Check every so often to see if * the queue is moving along. If it isn't just abort. */ while (!MIDIQ_EMPTY(scp->out_q)) { if (!scp->playing) { scp->playing = 1; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); } rl = MIDIQ_LEN(scp->out_q); i = cv_timedwait_sig(&scp->out_cv, &scp->seq_lock, SEQ_SYNC_TIMEOUT * hz); if (i == EINTR || i == ERESTART) { if (i == EINTR) { /* * XXX: I don't know why we stop playing */ scp->playing = 0; cv_broadcast(&scp->out_cv); } return i; } if (i == EWOULDBLOCK && rl == MIDIQ_LEN(scp->out_q) && scp->waiting == 0) { /* * A queue seems to be stuck up. Give up and clear * queues. */ MIDIQ_CLEAR(scp->out_q); scp->playing = 0; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); cv_broadcast(&scp->reset_cv); /* * TODO: Consider if the raw devices need to be flushed */ SEQ_DEBUG(1, printf("seq_sync queue stuck, aborting\n")); return i; } } scp->playing = 0; /* * Since syncing a midi device might block, unlock scp->seq_lock. */ mtx_unlock(&scp->seq_lock); for (i = 0; i < scp->midi_number; i++) sync[i] = 1; do { done = 1; for (i = 0; i < scp->midi_number; i++) if (sync[i]) { if (SYNTH_INSYNC(scp->midis[i]) == 0) sync[i] = 0; else done = 0; } if (!done) DELAY(5000); } while (!done); mtx_lock(&scp->seq_lock); return 0; } char * midi_cmdname(int cmd, midi_cmdtab *tab) { while (tab->name != NULL) { if (cmd == tab->cmd) return (tab->name); tab++; } return ("unknown"); } Index: head/sys/dev/sound/pci/als4000.c =================================================================== --- head/sys/dev/sound/pci/als4000.c (revision 274034) +++ head/sys/dev/sound/pci/als4000.c (revision 274035) @@ -1,951 +1,941 @@ /*- * Copyright (c) 2001 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * als4000.c - driver for the Avance Logic ALS 4000 chipset. * * The ALS4000 is effectively an SB16 with a PCI interface. * * This driver derives from ALS4000a.PDF, Bart Hartgers alsa driver, and * SB16 register descriptions. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* Debugging macro's */ #undef DEB #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ #define ALS_DEFAULT_BUFSZ 16384 /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t format, speed, phys_buf, bps; u_int32_t dma_active:1, dma_was_active:1; u_int8_t gcr_fifo_status; int dir; }; struct sc_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regid, irqid; void *ih; struct mtx *lock; unsigned int bufsz; struct sc_chinfo pch, rch; }; /* Channel caps */ static u_int32_t als_format[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; /* * I don't believe this rotten soundcard can do 48k, really, * trust me. */ static struct pcmchan_caps als_caps = { 4000, 44100, als_format, 0 }; /* ------------------------------------------------------------------------- */ /* Register Utilities */ static u_int32_t als_gcr_rd(struct sc_info *sc, int index) { bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); return bus_space_read_4(sc->st, sc->sh, ALS_GCR_DATA); } static void als_gcr_wr(struct sc_info *sc, int index, int data) { bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); bus_space_write_4(sc->st, sc->sh, ALS_GCR_DATA, data); } static u_int8_t als_intr_rd(struct sc_info *sc) { return bus_space_read_1(sc->st, sc->sh, ALS_SB_MPU_IRQ); } static void als_intr_wr(struct sc_info *sc, u_int8_t data) { bus_space_write_1(sc->st, sc->sh, ALS_SB_MPU_IRQ, data); } static u_int8_t als_mix_rd(struct sc_info *sc, u_int8_t index) { bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); return bus_space_read_1(sc->st, sc->sh, ALS_MIXER_DATA); } static void als_mix_wr(struct sc_info *sc, u_int8_t index, u_int8_t data) { bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); bus_space_write_1(sc->st, sc->sh, ALS_MIXER_DATA, data); } static void als_esp_wr(struct sc_info *sc, u_int8_t data) { u_int32_t tries, v; tries = 1000; do { v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_WR_STATUS); if (~v & 0x80) break; DELAY(20); } while (--tries != 0); if (tries == 0) device_printf(sc->dev, "als_esp_wr timeout"); bus_space_write_1(sc->st, sc->sh, ALS_ESP_WR_DATA, data); } static int als_esp_reset(struct sc_info *sc) { u_int32_t tries, u, v; bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 1); DELAY(10); bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 0); DELAY(30); tries = 1000; do { u = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_STATUS8); if (u & 0x80) { v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_DATA); if (v == 0xaa) return 0; else break; } DELAY(20); } while (--tries != 0); if (tries == 0) device_printf(sc->dev, "als_esp_reset timeout"); return 1; } static u_int8_t als_ack_read(struct sc_info *sc, u_int8_t addr) { u_int8_t r = bus_space_read_1(sc->st, sc->sh, addr); return r; } /* ------------------------------------------------------------------------- */ /* Common pcm channel implementation */ static void * alschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; snd_mtxlock(sc->lock); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->gcr_fifo_status = ALS_GCR_FIFO0_STATUS; } else { ch = &sc->rch; ch->gcr_fifo_status = ALS_GCR_FIFO1_STATUS; } ch->dir = dir; ch->parent = sc; ch->channel = c; ch->bps = 1; ch->format = SND_FORMAT(AFMT_U8, 1, 0); ch->speed = DSP_DEFAULT_SPEED; ch->buffer = b; snd_mtxunlock(sc->lock); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; return ch; } static int alschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->format = format; return 0; } static u_int32_t alschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data, *other; struct sc_info *sc = ch->parent; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; /* Deny request if other dma channel is active */ if (other->dma_active) { ch->speed = other->speed; return other->speed; } ch->speed = speed; return speed; } static u_int32_t alschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (blocksize > sc->bufsz / 2) { blocksize = sc->bufsz / 2; } sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static u_int32_t alschan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int32_t pos, sz; snd_mtxlock(sc->lock); pos = als_gcr_rd(ch->parent, ch->gcr_fifo_status) & 0xffff; snd_mtxunlock(sc->lock); sz = sndbuf_getsize(ch->buffer); return (2 * sz - pos - 1) % sz; } static struct pcmchan_caps* alschan_getcaps(kobj_t obj, void *data) { return &als_caps; } static void als_set_speed(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; struct sc_chinfo *other; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; if (other->dma_active == 0) { als_esp_wr(sc, ALS_ESP_SAMPLE_RATE); als_esp_wr(sc, ch->speed >> 8); als_esp_wr(sc, ch->speed & 0xff); } else { DEB(printf("speed locked at %d (tried %d)\n", other->speed, ch->speed)); } } /* ------------------------------------------------------------------------- */ /* Playback channel implementation */ #define ALS_8BIT_CMD(x, y) { (x), (y), DSP_DMA8, DSP_CMD_DMAPAUSE_8 } #define ALS_16BIT_CMD(x, y) { (x), (y), DSP_DMA16, DSP_CMD_DMAPAUSE_16 } struct playback_command { u_int32_t pcm_format; /* newpcm format */ u_int8_t format_val; /* sb16 format value */ u_int8_t dma_prog; /* sb16 dma program */ u_int8_t dma_stop; /* sb16 stop register */ } static const playback_cmds[] = { ALS_8BIT_CMD(SND_FORMAT(AFMT_U8, 1, 0), DSP_MODE_U8MONO), ALS_8BIT_CMD(SND_FORMAT(AFMT_U8, 2, 0), DSP_MODE_U8STEREO), ALS_16BIT_CMD(SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_MODE_S16MONO), ALS_16BIT_CMD(SND_FORMAT(AFMT_S16_LE, 2, 0), DSP_MODE_S16STEREO), }; static const struct playback_command* als_get_playback_command(u_int32_t format) { u_int32_t i, n; n = sizeof(playback_cmds) / sizeof(playback_cmds[0]); for (i = 0; i < n; i++) { if (playback_cmds[i].pcm_format == format) { return &playback_cmds[i]; } } DEB(printf("als_get_playback_command: invalid format 0x%08x\n", format)); return &playback_cmds[0]; } static void als_playback_start(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; buf = sndbuf_getbufaddr(ch->buffer); bufsz = sndbuf_getsize(ch->buffer); count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_DMA0_START, buf); als_gcr_wr(sc, ALS_GCR_DMA0_MODE, (bufsz - 1) | 0x180000); p = als_get_playback_command(ch->format); dma_prog = p->dma_prog | DSP_F16_DAC | DSP_F16_AUTO | DSP_F16_FIFO_ON; als_esp_wr(sc, dma_prog); als_esp_wr(sc, p->format_val); als_esp_wr(sc, count & 0xff); als_esp_wr(sc, count >> 8); ch->dma_active = 1; } static int als_playback_stop(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { p = als_get_playback_command(ch->format); als_esp_wr(sc, p->dma_stop); } ch->dma_active = 0; return active; } static int alspchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_playback_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: als_playback_stop(ch); break; default: break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alspchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alspchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(alspchan); /* ------------------------------------------------------------------------- */ /* Capture channel implementation */ static u_int8_t als_get_fifo_format(struct sc_info *sc, u_int32_t format) { switch (format) { case SND_FORMAT(AFMT_U8, 1, 0): return ALS_FIFO1_8BIT; case SND_FORMAT(AFMT_U8, 2, 0): return ALS_FIFO1_8BIT | ALS_FIFO1_STEREO; case SND_FORMAT(AFMT_S16_LE, 1, 0): return ALS_FIFO1_SIGNED; case SND_FORMAT(AFMT_S16_LE, 2, 0): return ALS_FIFO1_SIGNED | ALS_FIFO1_STEREO; } device_printf(sc->dev, "format not found: 0x%08x\n", format); return ALS_FIFO1_8BIT; } static void als_capture_start(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; buf = sndbuf_getbufaddr(ch->buffer); bufsz = sndbuf_getsize(ch->buffer); count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_FIFO1_START, buf); als_gcr_wr(sc, ALS_GCR_FIFO1_COUNT, (bufsz - 1)); als_mix_wr(sc, ALS_FIFO1_LENGTH_LO, count & 0xff); als_mix_wr(sc, ALS_FIFO1_LENGTH_HI, count >> 8); dma_prog = ALS_FIFO1_RUN | als_get_fifo_format(sc, ch->format); als_mix_wr(sc, ALS_FIFO1_CONTROL, dma_prog); ch->dma_active = 1; } static int als_capture_stop(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { als_mix_wr(sc, ALS_FIFO1_CONTROL, ALS_FIFO1_STOP); } ch->dma_active = 0; return active; } static int alsrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_capture_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: als_capture_stop(ch); break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alsrchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alsrchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(alsrchan); /* ------------------------------------------------------------------------- */ /* Mixer related */ /* * ALS4000 has an sb16 mixer, with some additional controls that we do * not yet a means to support. */ struct sb16props { u_int8_t lreg; u_int8_t rreg; u_int8_t bits; u_int8_t oselect; u_int8_t iselect; /* left input mask */ } static const amt[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { 0x30, 0x31, 5, 0x00, 0x00 }, [SOUND_MIXER_PCM] = { 0x32, 0x33, 5, 0x00, 0x00 }, [SOUND_MIXER_SYNTH] = { 0x34, 0x35, 5, 0x60, 0x40 }, [SOUND_MIXER_CD] = { 0x36, 0x37, 5, 0x06, 0x04 }, [SOUND_MIXER_LINE] = { 0x38, 0x39, 5, 0x18, 0x10 }, [SOUND_MIXER_MIC] = { 0x3a, 0x00, 5, 0x01, 0x01 }, [SOUND_MIXER_SPEAKER] = { 0x3b, 0x00, 2, 0x00, 0x00 }, [SOUND_MIXER_IGAIN] = { 0x3f, 0x40, 2, 0x00, 0x00 }, [SOUND_MIXER_OGAIN] = { 0x41, 0x42, 2, 0x00, 0x00 }, /* The following have register values but no h/w implementation */ [SOUND_MIXER_TREBLE] = { 0x44, 0x45, 4, 0x00, 0x00 }, [SOUND_MIXER_BASS] = { 0x46, 0x47, 4, 0x00, 0x00 } }; static int alsmix_init(struct snd_mixer *m) { u_int32_t i, v; for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (amt[i].bits) v |= 1 << i; } mix_setdevs(m, v); for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (amt[i].iselect) v |= 1 << i; } mix_setrecdevs(m, v); return 0; } static int alsmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t r, l, v, mask; /* Fill upper n bits in mask with 1's */ mask = ((1 << amt[dev].bits) - 1) << (8 - amt[dev].bits); l = (left * mask / 100) & mask; v = als_mix_rd(sc, amt[dev].lreg) & ~mask; als_mix_wr(sc, amt[dev].lreg, l | v); if (amt[dev].rreg) { r = (right * mask / 100) & mask; v = als_mix_rd(sc, amt[dev].rreg) & ~mask; als_mix_wr(sc, amt[dev].rreg, r | v); } else { r = 0; } /* Zero gain does not mute channel from output, but this does. */ v = als_mix_rd(sc, SB16_OMASK); if (l == 0 && r == 0) { v &= ~amt[dev].oselect; } else { v |= amt[dev].oselect; } als_mix_wr(sc, SB16_OMASK, v); return 0; } static u_int32_t alsmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, l, r; for (i = l = r = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (src & (1 << i)) { if (amt[i].iselect == 1) { /* microphone */ l |= amt[i].iselect; r |= amt[i].iselect; } else { l |= amt[i].iselect; r |= amt[i].iselect >> 1; } } } als_mix_wr(sc, SB16_IMASK_L, l); als_mix_wr(sc, SB16_IMASK_R, r); return src; } static kobj_method_t als_mixer_methods[] = { KOBJMETHOD(mixer_init, alsmix_init), KOBJMETHOD(mixer_set, alsmix_set), KOBJMETHOD(mixer_setrecsrc, alsmix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(als_mixer); /* ------------------------------------------------------------------------- */ /* Interrupt Handler */ static void als_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int8_t intr, sb_status; snd_mtxlock(sc->lock); intr = als_intr_rd(sc); if (intr & 0x80) { snd_mtxunlock(sc->lock); chn_intr(sc->pch.channel); snd_mtxlock(sc->lock); } if (intr & 0x40) { snd_mtxunlock(sc->lock); chn_intr(sc->rch.channel); snd_mtxlock(sc->lock); } /* ACK interrupt in PCI core */ als_intr_wr(sc, intr); /* ACK interrupt in SB core */ sb_status = als_mix_rd(sc, IRQ_STAT); if (sb_status & ALS_IRQ_STATUS8) als_ack_read(sc, ALS_ESP_RD_STATUS8); if (sb_status & ALS_IRQ_STATUS16) als_ack_read(sc, ALS_ESP_RD_STATUS16); if (sb_status & ALS_IRQ_MPUIN) als_ack_read(sc, ALS_MIDI_DATA); if (sb_status & ALS_IRQ_CR1E) als_ack_read(sc, ALS_CR1E_ACK_PORT); snd_mtxunlock(sc->lock); return; } /* ------------------------------------------------------------------------- */ /* H/W initialization */ static int als_init(struct sc_info *sc) { u_int32_t i, v; /* Reset Chip */ if (als_esp_reset(sc)) { return 1; } /* Enable write on DMA_SETUP register */ v = als_mix_rd(sc, ALS_SB16_CONFIG); als_mix_wr(sc, ALS_SB16_CONFIG, v | 0x80); /* Select DMA0 */ als_mix_wr(sc, ALS_SB16_DMA_SETUP, 0x01); /* Disable write on DMA_SETUP register */ als_mix_wr(sc, ALS_SB16_CONFIG, v & 0x7f); /* Enable interrupts */ v = als_gcr_rd(sc, ALS_GCR_MISC); als_gcr_wr(sc, ALS_GCR_MISC, v | 0x28000); /* Black out GCR DMA registers */ for (i = 0x91; i <= 0x96; i++) { als_gcr_wr(sc, i, 0); } /* Emulation mode */ v = als_gcr_rd(sc, ALS_GCR_DMA_EMULATION); als_gcr_wr(sc, ALS_GCR_DMA_EMULATION, v); DEB(printf("GCR_DMA_EMULATION 0x%08x\n", v)); return 0; } static void als_uninit(struct sc_info *sc) { /* Disable interrupts */ als_gcr_wr(sc, ALS_GCR_MISC, 0); } /* ------------------------------------------------------------------------- */ /* Probe and attach card */ static int als_pci_probe(device_t dev) { if (pci_get_devid(dev) == ALS_PCI_ID0) { device_set_desc(dev, "Avance Logic ALS4000"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void als_resource_free(device_t dev, struct sc_info *sc) { if (sc->reg) { bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); sc->reg = 0; } if (sc->ih) { bus_teardown_intr(dev, sc->irq, sc->ih); sc->ih = 0; } if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = 0; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } } static int als_resource_grab(device_t dev, struct sc_info *sc) { sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->regid, 0, ~0, ALS_CONFIG_SPACE_BYTES, RF_ACTIVE); if (sc->reg == 0) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (sc->irq == 0) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, als_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, ALS_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } return 0; bad: als_resource_free(dev, sc); return ENXIO; } static int als_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_als4000 softc"); sc->dev = dev; pci_enable_busmaster(dev); /* * By default the power to the various components on the * ALS4000 is entirely controlled by the pci powerstate. We * could attempt finer grained control by setting GCR6.31. */ -#if __FreeBSD_version > 500000 if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } -#else - data = pci_read_config(dev, ALS_PCI_POWERREG, 2); - if ((data & 0x03) != 0) { - device_printf(dev, "chip is in D%d power mode " - "-- setting to D0\n", data & 0x03); - data &= ~0x03; - pci_write_config(dev, ALS_PCI_POWERREG, data, 2); - } -#endif if (als_resource_grab(dev, sc)) { device_printf(dev, "failed to allocate resources\n"); goto bad_attach; } if (als_init(sc)) { device_printf(dev, "failed to initialize hardware\n"); goto bad_attach; } if (mixer_init(dev, &als_mixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad_attach; } if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "failed to register pcm entries\n"); goto bad_attach; } pcm_addchan(dev, PCMDIR_PLAY, &alspchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &alsrchan_class, sc); snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_als4000)); pcm_setstatus(dev, status); return 0; bad_attach: als_resource_free(dev, sc); free(sc, M_DEVBUF); return ENXIO; } static int als_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); als_uninit(sc); als_resource_free(dev, sc); free(sc, M_DEVBUF); return 0; } static int als_pci_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = als_playback_stop(&sc->pch); sc->rch.dma_was_active = als_capture_stop(&sc->rch); als_uninit(sc); snd_mtxunlock(sc->lock); return 0; } static int als_pci_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); if (als_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) != 0) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { als_playback_start(&sc->pch); } if (sc->rch.dma_was_active) { als_capture_start(&sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t als_methods[] = { /* Device interface */ DEVMETHOD(device_probe, als_pci_probe), DEVMETHOD(device_attach, als_pci_attach), DEVMETHOD(device_detach, als_pci_detach), DEVMETHOD(device_suspend, als_pci_suspend), DEVMETHOD(device_resume, als_pci_resume), { 0, 0 } }; static driver_t als_driver = { "pcm", als_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_als4000, pci, als_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_als4000, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_als4000, 1); Index: head/sys/dev/sound/pci/cs4281.c =================================================================== --- head/sys/dev/sound/pci/cs4281.c (revision 274034) +++ head/sys/dev/sound/pci/cs4281.c (revision 274035) @@ -1,984 +1,972 @@ /*- * Copyright (c) 2000 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * The order of pokes in the initiation sequence is based on Linux * driver by Thomas Sailer, gw boynton (wesb@crystal.cirrus.com), tom * woller (twoller@crystal.cirrus.com). Shingo Watanabe (nabe@nabechan.org) * contributed towards power management. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); #define CS4281_DEFAULT_BUFSZ 16384 /* Max fifo size for full duplex is 64 */ #define CS4281_FIFO_SIZE 15 /* DMA Engine Indices */ #define CS4281_DMA_PLAY 0 #define CS4281_DMA_REC 1 /* Misc */ #define inline __inline #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; /* channel registers */ struct sc_chinfo { struct sc_info *parent; struct snd_dbuf *buffer; struct pcm_channel *channel; u_int32_t spd, fmt, bps, blksz; int dma_setup, dma_active, dma_chan; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq, *mem; int regtype, regid, irqid, memid; void *ih; int power; unsigned long bufsz; struct sc_chinfo pch; struct sc_chinfo rch; }; /* -------------------------------------------------------------------- */ /* prototypes */ /* ADC/DAC control */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go); static void adcdac_prog(struct sc_chinfo *ch); /* power management and interrupt control */ static void cs4281_intr(void *); static int cs4281_power(struct sc_info *, int); static int cs4281_init(struct sc_info *); /* talk to the card */ static u_int32_t cs4281_rd(struct sc_info *, int); static void cs4281_wr(struct sc_info *, int, u_int32_t); /* misc */ static u_int8_t cs4281_rate_to_rv(u_int32_t); static u_int32_t cs4281_format_to_dmr(u_int32_t); static u_int32_t cs4281_format_to_bps(u_int32_t); /* -------------------------------------------------------------------- */ /* formats (do not add formats without editing cs_fmt_tab) */ static u_int32_t cs4281_fmts[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), SND_FORMAT(AFMT_U16_BE, 1, 0), SND_FORMAT(AFMT_U16_BE, 2, 0), 0 }; static struct pcmchan_caps cs4281_caps = {6024, 48000, cs4281_fmts, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static inline u_int32_t cs4281_rd(struct sc_info *sc, int regno) { return bus_space_read_4(sc->st, sc->sh, regno); } static inline void cs4281_wr(struct sc_info *sc, int regno, u_int32_t data) { bus_space_write_4(sc->st, sc->sh, regno, data); DELAY(100); } static inline void cs4281_clr4(struct sc_info *sc, int regno, u_int32_t mask) { u_int32_t r; r = cs4281_rd(sc, regno); cs4281_wr(sc, regno, r & ~mask); } static inline void cs4281_set4(struct sc_info *sc, int regno, u_int32_t mask) { u_int32_t v; v = cs4281_rd(sc, regno); cs4281_wr(sc, regno, v | mask); } static int cs4281_waitset(struct sc_info *sc, int regno, u_int32_t mask, int tries) { u_int32_t v; while (tries > 0) { DELAY(100); v = cs4281_rd(sc, regno); if ((v & mask) == mask) break; tries --; } return tries; } static int cs4281_waitclr(struct sc_info *sc, int regno, u_int32_t mask, int tries) { u_int32_t v; while (tries > 0) { DELAY(100); v = ~ cs4281_rd(sc, regno); if (v & mask) break; tries --; } return tries; } /* ------------------------------------------------------------------------- */ /* Register value mapping functions */ static u_int32_t cs4281_rates[] = {48000, 44100, 22050, 16000, 11025, 8000}; #define CS4281_NUM_RATES sizeof(cs4281_rates)/sizeof(cs4281_rates[0]) static u_int8_t cs4281_rate_to_rv(u_int32_t rate) { u_int32_t v; for (v = 0; v < CS4281_NUM_RATES; v++) { if (rate == cs4281_rates[v]) return v; } v = 1536000 / rate; if (v > 255 || v < 32) v = 5; /* default to 8k */ return v; } static u_int32_t cs4281_rv_to_rate(u_int8_t rv) { u_int32_t r; if (rv < CS4281_NUM_RATES) return cs4281_rates[rv]; r = 1536000 / rv; return r; } static inline u_int32_t cs4281_format_to_dmr(u_int32_t format) { u_int32_t dmr = 0; if (AFMT_8BIT & format) dmr |= CS4281PCI_DMR_SIZE8; if (AFMT_CHANNEL(format) < 2) dmr |= CS4281PCI_DMR_MONO; if (AFMT_BIGENDIAN & format) dmr |= CS4281PCI_DMR_BEND; if (!(AFMT_SIGNED & format)) dmr |= CS4281PCI_DMR_USIGN; return dmr; } static inline u_int32_t cs4281_format_to_bps(u_int32_t format) { return ((AFMT_8BIT & format) ? 1 : 2) * ((AFMT_CHANNEL(format) > 1) ? 2 : 1); } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int cs4281_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; int codecno; codecno = regno >> 8; regno &= 0xff; /* Remove old state */ cs4281_rd(sc, CS4281PCI_ACSDA); /* Fill in AC97 register value request form */ cs4281_wr(sc, CS4281PCI_ACCAD, regno); cs4281_wr(sc, CS4281PCI_ACCDA, 0); cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV | CS4281PCI_ACCTL_CRW); /* Wait for read to complete */ if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { device_printf(sc->dev, "cs4281_rdcd: DCV did not go\n"); return -1; } /* Wait for valid status */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_VSTS, 250) == 0) { device_printf(sc->dev,"cs4281_rdcd: VSTS did not come\n"); return -1; } return cs4281_rd(sc, CS4281PCI_ACSDA); } static int cs4281_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; int codecno; codecno = regno >> 8; regno &= 0xff; cs4281_wr(sc, CS4281PCI_ACCAD, regno); cs4281_wr(sc, CS4281PCI_ACCDA, data); cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV); if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { device_printf(sc->dev,"cs4281_wrcd: DCV did not go\n"); } return 0; } static kobj_method_t cs4281_ac97_methods[] = { KOBJMETHOD(ac97_read, cs4281_rdcd), KOBJMETHOD(ac97_write, cs4281_wrcd), KOBJMETHOD_END }; AC97_DECLARE(cs4281_ac97); /* ------------------------------------------------------------------------- */ /* shared rec/play channel interface */ static void * cs4281chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->buffer = b; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { return NULL; } ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->bps = 1; ch->blksz = sndbuf_getsize(ch->buffer); ch->dma_chan = (dir == PCMDIR_PLAY) ? CS4281_DMA_PLAY : CS4281_DMA_REC; ch->dma_setup = 0; adcdac_go(ch, 0); adcdac_prog(ch); return ch; } static u_int32_t cs4281chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t go; go = adcdac_go(ch, 0); /* 2 interrupts are possible and used in buffer (half-empty,empty), * hence factor of 2. */ ch->blksz = MIN(blocksize, sc->bufsz / 2); sndbuf_resize(ch->buffer, 2, ch->blksz); ch->dma_setup = 0; adcdac_prog(ch); adcdac_go(ch, go); DEB(printf("cs4281chan_setblocksize: blksz %d Setting %d\n", blocksize, ch->blksz)); return ch->blksz; } static u_int32_t cs4281chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t go, v, r; go = adcdac_go(ch, 0); /* pause */ r = (ch->dma_chan == CS4281_DMA_PLAY) ? CS4281PCI_DACSR : CS4281PCI_ADCSR; v = cs4281_rate_to_rv(speed); cs4281_wr(sc, r, v); adcdac_go(ch, go); /* unpause */ ch->spd = cs4281_rv_to_rate(v); return ch->spd; } static int cs4281chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t v, go; go = adcdac_go(ch, 0); /* pause */ if (ch->dma_chan == CS4281_DMA_PLAY) v = CS4281PCI_DMR_TR_PLAY; else v = CS4281PCI_DMR_TR_REC; v |= CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO; v |= cs4281_format_to_dmr(format); cs4281_wr(sc, CS4281PCI_DMR(ch->dma_chan), v); adcdac_go(ch, go); /* unpause */ ch->fmt = format; ch->bps = cs4281_format_to_bps(format); ch->dma_setup = 0; return 0; } static u_int32_t cs4281chan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t dba, dca, ptr; int sz; sz = sndbuf_getsize(ch->buffer); dba = cs4281_rd(sc, CS4281PCI_DBA(ch->dma_chan)); dca = cs4281_rd(sc, CS4281PCI_DCA(ch->dma_chan)); ptr = (dca - dba + sz) % sz; return ptr; } static int cs4281chan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; switch(go) { case PCMTRIG_START: adcdac_prog(ch); adcdac_go(ch, 1); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: adcdac_go(ch, 0); break; default: break; } /* return 0 if ok */ return 0; } static struct pcmchan_caps * cs4281chan_getcaps(kobj_t obj, void *data) { return &cs4281_caps; } static kobj_method_t cs4281chan_methods[] = { KOBJMETHOD(channel_init, cs4281chan_init), KOBJMETHOD(channel_setformat, cs4281chan_setformat), KOBJMETHOD(channel_setspeed, cs4281chan_setspeed), KOBJMETHOD(channel_setblocksize, cs4281chan_setblocksize), KOBJMETHOD(channel_trigger, cs4281chan_trigger), KOBJMETHOD(channel_getptr, cs4281chan_getptr), KOBJMETHOD(channel_getcaps, cs4281chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(cs4281chan); /* -------------------------------------------------------------------- */ /* ADC/DAC control */ /* adcdac_go enables/disable DMA channel, returns non-zero if DMA was * active before call */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go) { struct sc_info *sc = ch->parent; u_int32_t going; going = !(cs4281_rd(sc, CS4281PCI_DCR(ch->dma_chan)) & CS4281PCI_DCR_MSK); if (go) cs4281_clr4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); else cs4281_set4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); return going; } static void adcdac_prog(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t go; if (!ch->dma_setup) { go = adcdac_go(ch, 0); cs4281_wr(sc, CS4281PCI_DBA(ch->dma_chan), sndbuf_getbufaddr(ch->buffer)); cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan), sndbuf_getsize(ch->buffer) / ch->bps - 1); ch->dma_setup = 1; adcdac_go(ch, go); } } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void cs4281_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t hisr; hisr = cs4281_rd(sc, CS4281PCI_HISR); if (hisr == 0) return; if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_PLAY)) { chn_intr(sc->pch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_PLAY)); /* Clear interrupt */ } if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_REC)) { chn_intr(sc->rch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_REC)); /* Clear interrupt */ } /* Signal End-of-Interrupt */ cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); } /* -------------------------------------------------------------------- */ /* power management related */ static int cs4281_power(struct sc_info *sc, int state) { switch (state) { case 0: /* Permit r/w access to all BA0 registers */ cs4281_wr(sc, CS4281PCI_CWPR, CS4281PCI_CWPR_MAGIC); /* Power on */ cs4281_clr4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); break; case 3: /* Power off card and codec */ cs4281_set4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); cs4281_clr4(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); break; } DEB(printf("cs4281_power %d -> %d\n", sc->power, state)); sc->power = state; return 0; } static int cs4281_init(struct sc_info *sc) { u_int32_t i, v; /* (0) Blast clock register and serial port */ cs4281_wr(sc, CS4281PCI_CLKCR1, 0); cs4281_wr(sc, CS4281PCI_SERMC, 0); /* (1) Make ESYN 0 to turn sync pulse on AC97 link */ cs4281_wr(sc, CS4281PCI_ACCTL, 0); DELAY(50); /* (2) Effect Reset */ cs4281_wr(sc, CS4281PCI_SPMC, 0); DELAY(100); cs4281_wr(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); /* Wait 50ms for ABITCLK to become stable */ DELAY(50000); /* (3) Enable Sound System Clocks */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP); DELAY(50000); /* Wait for PLL to stabilize */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP | CS4281PCI_CLKCR1_SWCE); /* (4) Power Up - this combination is essential. */ cs4281_set4(sc, CS4281PCI_SSPM, CS4281PCI_SSPM_ACLEN | CS4281PCI_SSPM_PSRCEN | CS4281PCI_SSPM_CSRCEN | CS4281PCI_SSPM_MIXEN); /* (5) Wait for clock stabilization */ if (cs4281_waitset(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLRDY, 250) == 0) { device_printf(sc->dev, "Clock stabilization failed\n"); return -1; } /* (6) Enable ASYNC generation. */ cs4281_wr(sc, CS4281PCI_ACCTL,CS4281PCI_ACCTL_ESYN); /* Wait to allow AC97 to start generating clock bit */ DELAY(50000); /* Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (7) Wait for AC97 ready signal */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_CRDY, 250) == 0) { device_printf(sc->dev, "codec did not avail\n"); return -1; } /* (8) Assert valid frame signal to begin sending commands to * AC97 codec */ cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_ESYN); /* (9) Wait for codec calibration */ for(i = 0 ; i < 1000; i++) { DELAY(10000); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if ((v & 0x0f) == 0x0f) { break; } } if (i == 1000) { device_printf(sc->dev, "codec failed to calibrate\n"); return -1; } /* (10) Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (11) Wait for valid data to arrive */ if (cs4281_waitset(sc, CS4281PCI_ACISV, CS4281PCI_ACISV_ISV(3) | CS4281PCI_ACISV_ISV(4), 10000) == 0) { device_printf(sc->dev, "cs4281 never got valid data\n"); return -1; } /* (12) Start digital data transfer of audio data to codec */ cs4281_wr(sc, CS4281PCI_ACOSV, CS4281PCI_ACOSV_SLV(3) | CS4281PCI_ACOSV_SLV(4)); /* Set Master and headphone to max */ cs4281_wrcd(0, sc, AC97_MIX_AUXOUT, 0); cs4281_wrcd(0, sc, AC97_MIX_MASTER, 0); /* Power on the DAC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfdff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until DAC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x02) break; } /* Power on the ADC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfeff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until ADC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x01) break; } /* FIFO configuration (driver is DMA orientated, implicit FIFO) */ /* Play FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(0); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v | CS4281PCI_FCR_FEN); /* Record FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_REC_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(CS4281_FIFO_SIZE + 1); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_PSH); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_FEN); /* Match AC97 slots to FIFOs */ v = CS4281PCI_SRCSA_PLSS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_SRCSA_PRSS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_SRCSA_CLSS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_SRCSA_CRSS(CS4281PCI_RPCM_REC_SLOT); cs4281_wr(sc, CS4281PCI_SRCSA, v); /* Set Auto-Initialize and set directions */ cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_PLAY), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_PLAY); cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_REC), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_REC); /* Enable half and empty buffer interrupts keeping DMA paused */ cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_PLAY), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_REC), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); /* Enable Interrupts */ cs4281_clr4(sc, CS4281PCI_HIMR, CS4281PCI_HIMR_DMAI | CS4281PCI_HIMR_DMA(CS4281_DMA_PLAY) | CS4281PCI_HIMR_DMA(CS4281_DMA_REC)); /* Set playback volume */ cs4281_wr(sc, CS4281PCI_PPLVC, 7); cs4281_wr(sc, CS4281PCI_PPRVC, 7); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int cs4281_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case CS4281_PCI_ID: s = "Crystal Semiconductor CS4281"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } static int cs4281_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); pci_enable_busmaster(dev); -#if __FreeBSD_version > 500000 if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } -#else - data = pci_read_config(dev, CS4281PCI_PMCS_OFFSET, 4); - if (data & CS4281PCI_PMCS_PS_MASK) { - /* Reset the power state. */ - device_printf(dev, "chip is in D%d power mode " - "-- setting to D0\n", - data & CS4281PCI_PMCS_PS_MASK); - pci_write_config(dev, CS4281PCI_PMCS_OFFSET, - data & ~CS4281PCI_PMCS_PS_MASK, 4); - } -#endif sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, 0, ~0, CS4281PCI_BA0_SIZE, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, 0, ~0, CS4281PCI_BA0_SIZE, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->memid = PCIR_BAR(1); sc->mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->memid, 0, ~0, CS4281PCI_BA1_SIZE, RF_ACTIVE); if (sc->mem == NULL) { device_printf(dev, "unable to allocate fifo space\n"); goto bad; } sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, 0, cs4281_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, CS4281_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* power up */ cs4281_power(sc, 0); /* init chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, cs4281_ac97); if (codec == NULL) goto bad; mixer_init(dev, ac97_getmixerclass(), codec); if (pcm_register(dev, sc, 1, 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &cs4281chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cs4281chan_class, sc); snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", (sc->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(sc->reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_cs4281)); pcm_setstatus(dev, status); return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->mem) bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return ENXIO; } static int cs4281_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* power off */ cs4281_power(sc, 3); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return 0; } static int cs4281_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); sc->rch.dma_active = adcdac_go(&sc->rch, 0); sc->pch.dma_active = adcdac_go(&sc->pch, 0); cs4281_power(sc, 3); return 0; } static int cs4281_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* power up */ cs4281_power(sc, 0); /* initialize chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } /* restore mixer state */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restore chip state */ cs4281chan_setspeed(NULL, &sc->rch, sc->rch.spd); cs4281chan_setblocksize(NULL, &sc->rch, sc->rch.blksz); cs4281chan_setformat(NULL, &sc->rch, sc->rch.fmt); adcdac_go(&sc->rch, sc->rch.dma_active); cs4281chan_setspeed(NULL, &sc->pch, sc->pch.spd); cs4281chan_setblocksize(NULL, &sc->pch, sc->pch.blksz); cs4281chan_setformat(NULL, &sc->pch, sc->pch.fmt); adcdac_go(&sc->pch, sc->pch.dma_active); return 0; } static device_method_t cs4281_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cs4281_pci_probe), DEVMETHOD(device_attach, cs4281_pci_attach), DEVMETHOD(device_detach, cs4281_pci_detach), DEVMETHOD(device_suspend, cs4281_pci_suspend), DEVMETHOD(device_resume, cs4281_pci_resume), { 0, 0 } }; static driver_t cs4281_driver = { "pcm", cs4281_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_cs4281, pci, cs4281_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_cs4281, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_cs4281, 1); Index: head/sys/dev/sound/pci/csa.c =================================================================== --- head/sys/dev/sound/pci/csa.c (revision 274034) +++ head/sys/dev/sound/pci/csa.c (revision 274035) @@ -1,1114 +1,1108 @@ /*- * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in * cwcealdr1.zip, the sample sources by Crystal Semiconductor. * Copyright (c) 1996-1998 Crystal Semiconductor Corp. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* This is the pci device id. */ #define CS4610_PCI_ID 0x60011013 #define CS4614_PCI_ID 0x60031013 #define CS4615_PCI_ID 0x60041013 /* Here is the parameter structure per a device. */ struct csa_softc { device_t dev; /* device */ csa_res res; /* resources */ device_t pcm; /* pcm device */ driver_intr_t* pcmintr; /* pcm intr */ void *pcmintr_arg; /* pcm intr arg */ device_t midi; /* midi device */ driver_intr_t* midiintr; /* midi intr */ void *midiintr_arg; /* midi intr arg */ void *ih; /* cookie */ struct csa_card *card; struct csa_bridgeinfo binfo; /* The state of this bridge. */ }; typedef struct csa_softc *sc_p; static int csa_probe(device_t dev); static int csa_attach(device_t dev); static struct resource *csa_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags); static int csa_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); static int csa_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, -#if __FreeBSD_version >= 700031 driver_filter_t *filter, -#endif driver_intr_t *intr, void *arg, void **cookiep); static int csa_teardown_intr(device_t bus, device_t child, struct resource *irq, void *cookie); static driver_intr_t csa_intr; static int csa_initialize(sc_p scp); static int csa_downloadimage(csa_res *resp); static int csa_transferimage(csa_res *resp, u_int32_t *src, u_long dest, u_long len); static devclass_t csa_devclass; static void amp_none(void) { } static void amp_voyetra(void) { } static int clkrun_hack(int run) { #ifdef __i386__ devclass_t pci_devclass; device_t *pci_devices, *pci_children, *busp, *childp; int pci_count = 0, pci_childcount = 0; int i, j, port; u_int16_t control; bus_space_tag_t btag; if ((pci_devclass = devclass_find("pci")) == NULL) { return ENXIO; } devclass_get_devices(pci_devclass, &pci_devices, &pci_count); for (i = 0, busp = pci_devices; i < pci_count; i++, busp++) { pci_childcount = 0; if (device_get_children(*busp, &pci_children, &pci_childcount)) continue; for (j = 0, childp = pci_children; j < pci_childcount; j++, childp++) { if (pci_get_vendor(*childp) == 0x8086 && pci_get_device(*childp) == 0x7113) { port = (pci_read_config(*childp, 0x41, 1) << 8) + 0x10; /* XXX */ btag = X86_BUS_SPACE_IO; control = bus_space_read_2(btag, 0x0, port); control &= ~0x2000; control |= run? 0 : 0x2000; bus_space_write_2(btag, 0x0, port, control); free(pci_devices, M_TEMP); free(pci_children, M_TEMP); return 0; } } free(pci_children, M_TEMP); } free(pci_devices, M_TEMP); return ENXIO; #else return 0; #endif } static struct csa_card cards_4610[] = { {0, 0, "Unknown/invalid SSID (CS4610)", NULL, NULL, NULL, 0}, }; static struct csa_card cards_4614[] = { {0x1489, 0x7001, "Genius Soundmaker 128 value", amp_none, NULL, NULL, 0}, {0x5053, 0x3357, "Turtle Beach Santa Cruz", amp_voyetra, NULL, NULL, 1}, {0x1071, 0x6003, "Mitac MI6020/21", amp_voyetra, NULL, NULL, 0}, {0x14AF, 0x0050, "Hercules Game Theatre XP", NULL, NULL, NULL, 0}, {0x1681, 0x0050, "Hercules Game Theatre XP", NULL, NULL, NULL, 0}, {0x1014, 0x0132, "Thinkpad 570", amp_none, NULL, NULL, 0}, {0x1014, 0x0153, "Thinkpad 600X/A20/T20", amp_none, NULL, clkrun_hack, 0}, {0x1014, 0x1010, "Thinkpad 600E (unsupported)", NULL, NULL, NULL, 0}, {0, 0, "Unknown/invalid SSID (CS4614)", NULL, NULL, NULL, 0}, }; static struct csa_card cards_4615[] = { {0, 0, "Unknown/invalid SSID (CS4615)", NULL, NULL, NULL, 0}, }; static struct csa_card nocard = {0, 0, "unknown", NULL, NULL, NULL, 0}; struct card_type { u_int32_t devid; char *name; struct csa_card *cards; }; static struct card_type cards[] = { {CS4610_PCI_ID, "CS4610/CS4611", cards_4610}, {CS4614_PCI_ID, "CS4280/CS4614/CS4622/CS4624/CS4630", cards_4614}, {CS4615_PCI_ID, "CS4615", cards_4615}, {0, NULL, NULL}, }; static struct card_type * csa_findcard(device_t dev) { int i; i = 0; while (cards[i].devid != 0) { if (pci_get_devid(dev) == cards[i].devid) return &cards[i]; i++; } return NULL; } struct csa_card * csa_findsubcard(device_t dev) { int i; struct card_type *card; struct csa_card *subcard; card = csa_findcard(dev); if (card == NULL) return &nocard; subcard = card->cards; i = 0; while (subcard[i].subvendor != 0) { if (pci_get_subvendor(dev) == subcard[i].subvendor && pci_get_subdevice(dev) == subcard[i].subdevice) { return &subcard[i]; } i++; } return &subcard[i]; } static int csa_probe(device_t dev) { struct card_type *card; card = csa_findcard(dev); if (card) { device_set_desc(dev, card->name); return BUS_PROBE_DEFAULT; } return ENXIO; } static int csa_attach(device_t dev) { sc_p scp; csa_res *resp; struct sndcard_func *func; int error = ENXIO; scp = device_get_softc(dev); /* Fill in the softc. */ bzero(scp, sizeof(*scp)); scp->dev = dev; pci_enable_busmaster(dev); /* Allocate the resources. */ resp = &scp->res; scp->card = csa_findsubcard(dev); scp->binfo.card = scp->card; printf("csa: card is %s\n", scp->card->name); resp->io_rid = PCIR_BAR(0); resp->io = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->io_rid, RF_ACTIVE); if (resp->io == NULL) return (ENXIO); resp->mem_rid = PCIR_BAR(1); resp->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->mem_rid, RF_ACTIVE); if (resp->mem == NULL) goto err_io; resp->irq_rid = 0; resp->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &resp->irq_rid, RF_ACTIVE | RF_SHAREABLE); if (resp->irq == NULL) goto err_mem; /* Enable interrupt. */ if (snd_setup_intr(dev, resp->irq, 0, csa_intr, scp, &scp->ih)) goto err_intr; #if 0 if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); #endif /* Initialize the chip. */ if (csa_initialize(scp)) goto err_teardown; /* Reset the Processor. */ csa_resetdsp(resp); /* Download the Processor Image to the processor. */ if (csa_downloadimage(resp)) goto err_teardown; /* Attach the children. */ /* PCM Audio */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto err_teardown; } func->varinfo = &scp->binfo; func->func = SCF_PCM; scp->pcm = device_add_child(dev, "pcm", -1); device_set_ivars(scp->pcm, func); /* Midi Interface */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto err_teardown; } func->varinfo = &scp->binfo; func->func = SCF_MIDI; scp->midi = device_add_child(dev, "midi", -1); device_set_ivars(scp->midi, func); bus_generic_attach(dev); return (0); err_teardown: bus_teardown_intr(dev, resp->irq, scp->ih); err_intr: bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); err_mem: bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); err_io: bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); return (error); } static int csa_detach(device_t dev) { csa_res *resp; sc_p scp; struct sndcard_func *func; int err; scp = device_get_softc(dev); resp = &scp->res; if (scp->midi != NULL) { func = device_get_ivars(scp->midi); err = device_delete_child(dev, scp->midi); if (err != 0) return err; if (func != NULL) free(func, M_DEVBUF); scp->midi = NULL; } if (scp->pcm != NULL) { func = device_get_ivars(scp->pcm); err = device_delete_child(dev, scp->pcm); if (err != 0) return err; if (func != NULL) free(func, M_DEVBUF); scp->pcm = NULL; } bus_teardown_intr(dev, resp->irq, scp->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); return bus_generic_detach(dev); } static int csa_resume(device_t dev) { csa_res *resp; sc_p scp; scp = device_get_softc(dev); resp = &scp->res; /* Initialize the chip. */ if (csa_initialize(scp)) return (ENXIO); /* Reset the Processor. */ csa_resetdsp(resp); /* Download the Processor Image to the processor. */ if (csa_downloadimage(resp)) return (ENXIO); return (bus_generic_resume(dev)); } static struct resource * csa_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { sc_p scp; csa_res *resp; struct resource *res; scp = device_get_softc(bus); resp = &scp->res; switch (type) { case SYS_RES_IRQ: if (*rid != 0) return (NULL); res = resp->irq; break; case SYS_RES_MEMORY: switch (*rid) { case PCIR_BAR(0): res = resp->io; break; case PCIR_BAR(1): res = resp->mem; break; default: return (NULL); } break; default: return (NULL); } return res; } static int csa_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { return (0); } /* * The following three functions deal with interrupt handling. * An interrupt is primarily handled by the bridge driver. * The bridge driver then determines the child devices to pass * the interrupt. Certain information of the device can be read * only once(eg the value of HISR). The bridge driver is responsible * to pass such the information to the children. */ static int csa_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, -#if __FreeBSD_version >= 700031 driver_filter_t *filter, -#endif driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp; csa_res *resp; struct sndcard_func *func; -#if __FreeBSD_version >= 700031 if (filter != NULL) { printf("ata-csa.c: we cannot use a filter here\n"); return (EINVAL); } -#endif scp = device_get_softc(bus); resp = &scp->res; /* * Look at the function code of the child to determine * the appropriate hander for it. */ func = device_get_ivars(child); if (func == NULL || irq != resp->irq) return (EINVAL); switch (func->func) { case SCF_PCM: scp->pcmintr = intr; scp->pcmintr_arg = arg; break; case SCF_MIDI: scp->midiintr = intr; scp->midiintr_arg = arg; break; default: return (EINVAL); } *cookiep = scp; if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); return (0); } static int csa_teardown_intr(device_t bus, device_t child, struct resource *irq, void *cookie) { sc_p scp; csa_res *resp; struct sndcard_func *func; scp = device_get_softc(bus); resp = &scp->res; /* * Look at the function code of the child to determine * the appropriate hander for it. */ func = device_get_ivars(child); if (func == NULL || irq != resp->irq || cookie != scp) return (EINVAL); switch (func->func) { case SCF_PCM: scp->pcmintr = NULL; scp->pcmintr_arg = NULL; break; case SCF_MIDI: scp->midiintr = NULL; scp->midiintr_arg = NULL; break; default: return (EINVAL); } return (0); } /* The interrupt handler */ static void csa_intr(void *arg) { sc_p scp = arg; csa_res *resp; u_int32_t hisr; resp = &scp->res; /* Is this interrupt for us? */ hisr = csa_readio(resp, BA0_HISR); if ((hisr & 0x7fffffff) == 0) { /* Throw an eoi. */ csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); return; } /* * Pass the value of HISR via struct csa_bridgeinfo. * The children get access through their ivars. */ scp->binfo.hisr = hisr; /* Invoke the handlers of the children. */ if ((hisr & (HISR_VC0 | HISR_VC1)) != 0 && scp->pcmintr != NULL) { scp->pcmintr(scp->pcmintr_arg); hisr &= ~(HISR_VC0 | HISR_VC1); } if ((hisr & HISR_MIDI) != 0 && scp->midiintr != NULL) { scp->midiintr(scp->midiintr_arg); hisr &= ~HISR_MIDI; } /* Throw an eoi. */ csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); } static int csa_initialize(sc_p scp) { int i; u_int32_t acsts, acisv; csa_res *resp; resp = &scp->res; /* * First, blast the clock control register to zero so that the PLL starts * out in a known state, and blast the master serial port control register * to zero so that the serial ports also start out in a known state. */ csa_writeio(resp, BA0_CLKCR1, 0); csa_writeio(resp, BA0_SERMC1, 0); /* * If we are in AC97 mode, then we must set the part to a host controlled * AC-link. Otherwise, we won't be able to bring up the link. */ #if 1 csa_writeio(resp, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_1_03); /* 1.03 codec */ #else csa_writeio(resp, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_2_0); /* 2.0 codec */ #endif /* 1 */ /* * Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97 * spec) and then drive it high. This is done for non AC97 modes since * there might be logic external to the CS461x that uses the ARST# line * for a reset. */ csa_writeio(resp, BA0_ACCTL, 1); DELAY(50); csa_writeio(resp, BA0_ACCTL, 0); DELAY(50); csa_writeio(resp, BA0_ACCTL, ACCTL_RSTN); /* * The first thing we do here is to enable sync generation. As soon * as we start receiving bit clock, we'll start producing the SYNC * signal. */ csa_writeio(resp, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN); /* * Now wait for a short while to allow the AC97 part to start * generating bit clock (so we don't try to start the PLL without an * input clock). */ DELAY(50000); /* * Set the serial port timing configuration, so that * the clock control circuit gets its clock from the correct place. */ csa_writeio(resp, BA0_SERMC1, SERMC1_PTC_AC97); DELAY(700000); /* * Write the selected clock control setup to the hardware. Do not turn on * SWCE yet (if requested), so that the devices clocked by the output of * PLL are not clocked until the PLL is stable. */ csa_writeio(resp, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ); csa_writeio(resp, BA0_PLLM, 0x3a); csa_writeio(resp, BA0_CLKCR2, CLKCR2_PDIVS_8); /* * Power up the PLL. */ csa_writeio(resp, BA0_CLKCR1, CLKCR1_PLLP); /* * Wait until the PLL has stabilized. */ DELAY(5000); /* * Turn on clocking of the core so that we can setup the serial ports. */ csa_writeio(resp, BA0_CLKCR1, csa_readio(resp, BA0_CLKCR1) | CLKCR1_SWCE); /* * Fill the serial port FIFOs with silence. */ csa_clearserialfifos(resp); /* * Set the serial port FIFO pointer to the first sample in the FIFO. */ #ifdef notdef csa_writeio(resp, BA0_SERBSP, 0); #endif /* notdef */ /* * Write the serial port configuration to the part. The master * enable bit is not set until all other values have been written. */ csa_writeio(resp, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN); csa_writeio(resp, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN); csa_writeio(resp, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE); /* * Wait for the codec ready signal from the AC97 codec. */ acsts = 0; for (i = 0 ; i < 1000 ; 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 AC97 status register to see if we've seen a CODEC READY * signal from the AC97 codec. */ acsts = csa_readio(resp, BA0_ACSTS); if ((acsts & ACSTS_CRDY) != 0) break; } /* * Make sure we sampled CODEC READY. */ if ((acsts & ACSTS_CRDY) == 0) return (ENXIO); /* * Assert the vaid frame signal so that we can start sending commands * to the AC97 codec. */ csa_writeio(resp, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); /* * Wait until we've sampled input slots 3 and 4 as valid, meaning that * the codec is pumping ADC data across the AC-link. */ acisv = 0; for (i = 0 ; i < 1000 ; i++) { /* * First, lets wait a short while to let things settle out a bit, * and to prevent retrying the read too quickly. */ #ifdef notdef DELAY(10000000L); /* clw */ #else DELAY(1000); #endif /* notdef */ /* * Read the input slot valid register and see if input slots 3 and * 4 are valid yet. */ acisv = csa_readio(resp, BA0_ACISV); if ((acisv & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4)) break; } /* * Make sure we sampled valid input slots 3 and 4. If not, then return * an error. */ if ((acisv & (ACISV_ISV3 | ACISV_ISV4)) != (ACISV_ISV3 | ACISV_ISV4)) return (ENXIO); /* * Now, assert valid frame and the slot 3 and 4 valid bits. This will * commense the transfer of digital audio data to the AC97 codec. */ csa_writeio(resp, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4); /* * Power down the DAC and ADC. We will power them up (if) when we need * them. */ #ifdef notdef csa_writeio(resp, BA0_AC97_POWERDOWN, 0x300); #endif /* notdef */ /* * Turn off the Processor by turning off the software clock enable flag in * the clock control register. */ #ifdef notdef clkcr1 = csa_readio(resp, BA0_CLKCR1) & ~CLKCR1_SWCE; csa_writeio(resp, BA0_CLKCR1, clkcr1); #endif /* notdef */ /* * Enable interrupts on the part. */ #if 0 csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); #endif /* notdef */ return (0); } void csa_clearserialfifos(csa_res *resp) { int i, j, pwr; u_int8_t clkcr1, serbst; /* * See if the devices are powered down. If so, we must power them up first * or they will not respond. */ pwr = 1; clkcr1 = csa_readio(resp, BA0_CLKCR1); if ((clkcr1 & CLKCR1_SWCE) == 0) { csa_writeio(resp, BA0_CLKCR1, clkcr1 | CLKCR1_SWCE); pwr = 0; } /* * We want to clear out the serial port FIFOs so we don't end up playing * whatever random garbage happens to be in them. We fill the sample FIFOs * with zero (silence). */ csa_writeio(resp, BA0_SERBWP, 0); /* Fill all 256 sample FIFO locations. */ serbst = 0; for (i = 0 ; i < 256 ; i++) { /* Make sure the previous FIFO write operation has completed. */ for (j = 0 ; j < 5 ; j++) { DELAY(100); serbst = csa_readio(resp, BA0_SERBST); if ((serbst & SERBST_WBSY) == 0) break; } if ((serbst & SERBST_WBSY) != 0) { if (!pwr) csa_writeio(resp, BA0_CLKCR1, clkcr1); } /* Write the serial port FIFO index. */ csa_writeio(resp, BA0_SERBAD, i); /* Tell the serial port to load the new value into the FIFO location. */ csa_writeio(resp, BA0_SERBCM, SERBCM_WRC); } /* * Now, if we powered up the devices, then power them back down again. * This is kinda ugly, but should never happen. */ if (!pwr) csa_writeio(resp, BA0_CLKCR1, clkcr1); } void csa_resetdsp(csa_res *resp) { int i; /* * Write the reset bit of the SP control register. */ csa_writemem(resp, BA1_SPCR, SPCR_RSTSP); /* * Write the control register. */ csa_writemem(resp, BA1_SPCR, SPCR_DRQEN); /* * Clear the trap registers. */ for (i = 0 ; i < 8 ; i++) { csa_writemem(resp, BA1_DREG, DREG_REGID_TRAP_SELECT + i); csa_writemem(resp, BA1_TWPR, 0xffff); } csa_writemem(resp, BA1_DREG, 0); /* * Set the frame timer to reflect the number of cycles per frame. */ csa_writemem(resp, BA1_FRMT, 0xadf); } static int csa_downloadimage(csa_res *resp) { int ret; u_long ul, offset; for (ul = 0, offset = 0 ; ul < INKY_MEMORY_COUNT ; ul++) { /* * DMA this block from host memory to the appropriate * memory on the CSDevice. */ ret = csa_transferimage(resp, cs461x_firmware.BA1Array + offset, cs461x_firmware.MemoryStat[ul].ulDestAddr, cs461x_firmware.MemoryStat[ul].ulSourceSize); if (ret) return (ret); offset += cs461x_firmware.MemoryStat[ul].ulSourceSize >> 2; } return (0); } static int csa_transferimage(csa_res *resp, u_int32_t *src, u_long dest, u_long len) { u_long ul; /* * We do not allow DMAs from host memory to host memory (although the DMA * can do it) and we do not allow DMAs which are not a multiple of 4 bytes * in size (because that DMA can not do that). Return an error if either * of these conditions exist. */ if ((len & 0x3) != 0) return (EINVAL); /* Check the destination address that it is a multiple of 4 */ if ((dest & 0x3) != 0) return (EINVAL); /* Write the buffer out. */ for (ul = 0 ; ul < len ; ul += 4) csa_writemem(resp, dest + ul, src[ul >> 2]); return (0); } int csa_readcodec(csa_res *resp, u_long offset, u_int32_t *data) { int i; u_int32_t acctl, acsts; /* * Make sure that there is not data sitting around from a previous * uncompleted access. ACSDA = Status Data Register = 47Ch */ csa_readio(resp, BA0_ACSDA); /* * Setup the AC97 control registers on the CS461x to send the * appropriate command to the AC97 to perform the read. * ACCAD = Command Address Register = 46Ch * ACCDA = Command Data Register = 470h * ACCTL = Control Register = 460h * set DCV - will clear when process completed * set CRW - Read command * set VFRM - valid frame enabled * set ESYN - ASYNC generation enabled * set RSTN - ARST# inactive, AC97 codec not reset */ /* * Get the actual AC97 register from the offset */ csa_writeio(resp, BA0_ACCAD, offset - BA0_AC97_RESET); csa_writeio(resp, BA0_ACCDA, 0); csa_writeio(resp, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); /* * Wait for the read to occur. */ acctl = 0; for (i = 0 ; i < 10 ; i++) { /* * First, we want to wait for a short time. */ DELAY(25); /* * Now, check to see if the read has completed. * ACCTL = 460h, DCV should be reset by now and 460h = 17h */ acctl = csa_readio(resp, BA0_ACCTL); if ((acctl & ACCTL_DCV) == 0) break; } /* * Make sure the read completed. */ if ((acctl & ACCTL_DCV) != 0) return (EAGAIN); /* * Wait for the valid status bit to go active. */ acsts = 0; for (i = 0 ; i < 10 ; i++) { /* * Read the AC97 status register. * ACSTS = Status Register = 464h */ acsts = csa_readio(resp, BA0_ACSTS); /* * See if we have valid status. * VSTS - Valid Status */ if ((acsts & ACSTS_VSTS) != 0) break; /* * Wait for a short while. */ DELAY(25); } /* * Make sure we got valid status. */ if ((acsts & ACSTS_VSTS) == 0) return (EAGAIN); /* * Read the data returned from the AC97 register. * ACSDA = Status Data Register = 474h */ *data = csa_readio(resp, BA0_ACSDA); return (0); } int csa_writecodec(csa_res *resp, u_long offset, u_int32_t data) { int i; u_int32_t acctl; /* * Setup the AC97 control registers on the CS461x to send the * appropriate command to the AC97 to perform the write. * ACCAD = Command Address Register = 46Ch * ACCDA = Command Data Register = 470h * ACCTL = Control Register = 460h * set DCV - will clear when process completed * set VFRM - valid frame enabled * set ESYN - ASYNC generation enabled * set RSTN - ARST# inactive, AC97 codec not reset */ /* * Get the actual AC97 register from the offset */ csa_writeio(resp, BA0_ACCAD, offset - BA0_AC97_RESET); csa_writeio(resp, BA0_ACCDA, data); csa_writeio(resp, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); /* * Wait for the write to occur. */ acctl = 0; for (i = 0 ; i < 10 ; i++) { /* * First, we want to wait for a short time. */ DELAY(25); /* * Now, check to see if the read has completed. * ACCTL = 460h, DCV should be reset by now and 460h = 17h */ acctl = csa_readio(resp, BA0_ACCTL); if ((acctl & ACCTL_DCV) == 0) break; } /* * Make sure the write completed. */ if ((acctl & ACCTL_DCV) != 0) return (EAGAIN); return (0); } u_int32_t csa_readio(csa_res *resp, u_long offset) { u_int32_t ul; if (offset < BA0_AC97_RESET) return bus_space_read_4(rman_get_bustag(resp->io), rman_get_bushandle(resp->io), offset) & 0xffffffff; else { if (csa_readcodec(resp, offset, &ul)) ul = 0; return (ul); } } void csa_writeio(csa_res *resp, u_long offset, u_int32_t data) { if (offset < BA0_AC97_RESET) bus_space_write_4(rman_get_bustag(resp->io), rman_get_bushandle(resp->io), offset, data); else csa_writecodec(resp, offset, data); } u_int32_t csa_readmem(csa_res *resp, u_long offset) { return bus_space_read_4(rman_get_bustag(resp->mem), rman_get_bushandle(resp->mem), offset); } void csa_writemem(csa_res *resp, u_long offset, u_int32_t data) { bus_space_write_4(rman_get_bustag(resp->mem), rman_get_bushandle(resp->mem), offset, data); } static device_method_t csa_methods[] = { /* Device interface */ DEVMETHOD(device_probe, csa_probe), DEVMETHOD(device_attach, csa_attach), DEVMETHOD(device_detach, csa_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, csa_resume), /* Bus interface */ DEVMETHOD(bus_alloc_resource, csa_alloc_resource), DEVMETHOD(bus_release_resource, csa_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, csa_setup_intr), DEVMETHOD(bus_teardown_intr, csa_teardown_intr), DEVMETHOD_END }; static driver_t csa_driver = { "csa", csa_methods, sizeof(struct csa_softc), }; /* * csa can be attached to a pci bus. */ DRIVER_MODULE(snd_csa, pci, csa_driver, csa_devclass, 0, 0); MODULE_DEPEND(snd_csa, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_csa, 1); Index: head/sys/dev/sound/pci/emu10kx.c =================================================================== --- head/sys/dev/sound/pci/emu10kx.c (revision 274034) +++ head/sys/dev/sound/pci/emu10kx.c (revision 274035) @@ -1,3572 +1,3570 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright (c) 2003-2007 Yuriy Tsibizov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for DELAY */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include /* hw flags */ #define HAS_51 0x0001 #define HAS_71 0x0002 #define HAS_AC97 0x0004 #define IS_EMU10K1 0x0008 #define IS_EMU10K2 0x0010 #define IS_CA0102 0x0020 #define IS_CA0108 0x0040 #define IS_UNKNOWN 0x0080 #define BROKEN_DIGITAL 0x0100 #define DIGITAL_ONLY 0x0200 #define IS_CARDBUS 0x0400 #define MODE_ANALOG 1 #define MODE_DIGITAL 2 #define SPDIF_MODE_PCM 1 #define SPDIF_MODE_AC3 2 #define MACS 0x0 #define MACS1 0x1 #define MACW 0x2 #define MACW1 0x3 #define MACINTS 0x4 #define MACINTW 0x5 #define ACC3 0x6 #define MACMV 0x7 #define ANDXOR 0x8 #define TSTNEG 0x9 #define LIMIT 0xA #define LIMIT1 0xB #define LOG 0xC #define EXP 0xD #define INTERP 0xE #define SKIP 0xF #define GPR(i) (sc->gpr_base+(i)) #define INP(i) (sc->input_base+(i)) #define OUTP(i) (sc->output_base+(i)) #define FX(i) (i) #define FX2(i) (sc->efxc_base+(i)) #define DSP_CONST(i) (sc->dsp_zero+(i)) #define COND_NORMALIZED DSP_CONST(0x1) #define COND_BORROW DSP_CONST(0x2) #define COND_MINUS DSP_CONST(0x3) #define COND_LESS_ZERO DSP_CONST(0x4) #define COND_EQ_ZERO DSP_CONST(0x5) #define COND_SATURATION DSP_CONST(0x6) #define COND_NEQ_ZERO DSP_CONST(0x8) #define DSP_ACCUM DSP_CONST(0x16) #define DSP_CCR DSP_CONST(0x17) /* Live! Inputs */ #define IN_AC97_L 0x00 #define IN_AC97_R 0x01 #define IN_AC97 IN_AC97_L #define IN_SPDIF_CD_L 0x02 #define IN_SPDIF_CD_R 0x03 #define IN_SPDIF_CD IN_SPDIF_CD_L #define IN_ZOOM_L 0x04 #define IN_ZOOM_R 0x05 #define IN_ZOOM IN_ZOOM_L #define IN_TOSLINK_L 0x06 #define IN_TOSLINK_R 0x07 #define IN_TOSLINK IN_TOSLINK_L #define IN_LINE1_L 0x08 #define IN_LINE1_R 0x09 #define IN_LINE1 IN_LINE1_L #define IN_COAX_SPDIF_L 0x0a #define IN_COAX_SPDIF_R 0x0b #define IN_COAX_SPDIF IN_COAX_SPDIF_L #define IN_LINE2_L 0x0c #define IN_LINE2_R 0x0d #define IN_LINE2 IN_LINE2_L #define IN_0E 0x0e #define IN_0F 0x0f /* Outputs */ #define OUT_AC97_L 0x00 #define OUT_AC97_R 0x01 #define OUT_AC97 OUT_AC97_L #define OUT_A_FRONT OUT_AC97 #define OUT_TOSLINK_L 0x02 #define OUT_TOSLINK_R 0x03 #define OUT_TOSLINK OUT_TOSLINK_L #define OUT_D_CENTER 0x04 #define OUT_D_SUB 0x05 #define OUT_HEADPHONE_L 0x06 #define OUT_HEADPHONE_R 0x07 #define OUT_HEADPHONE OUT_HEADPHONE_L #define OUT_REAR_L 0x08 #define OUT_REAR_R 0x09 #define OUT_REAR OUT_REAR_L #define OUT_ADC_REC_L 0x0a #define OUT_ADC_REC_R 0x0b #define OUT_ADC_REC OUT_ADC_REC_L #define OUT_MIC_CAP 0x0c /* Live! 5.1 Digital, non-standard 5.1 (center & sub) outputs */ #define OUT_A_CENTER 0x11 #define OUT_A_SUB 0x12 /* Audigy Inputs */ #define A_IN_AC97_L 0x00 #define A_IN_AC97_R 0x01 #define A_IN_AC97 A_IN_AC97_L #define A_IN_SPDIF_CD_L 0x02 #define A_IN_SPDIF_CD_R 0x03 #define A_IN_SPDIF_CD A_IN_SPDIF_CD_L #define A_IN_O_SPDIF_L 0x04 #define A_IN_O_SPDIF_R 0x05 #define A_IN_O_SPDIF A_IN_O_SPDIF_L #define A_IN_LINE2_L 0x08 #define A_IN_LINE2_R 0x09 #define A_IN_LINE2 A_IN_LINE2_L #define A_IN_R_SPDIF_L 0x0a #define A_IN_R_SPDIF_R 0x0b #define A_IN_R_SPDIF A_IN_R_SPDIF_L #define A_IN_AUX2_L 0x0c #define A_IN_AUX2_R 0x0d #define A_IN_AUX2 A_IN_AUX2_L /* Audigy Outputs */ #define A_OUT_D_FRONT_L 0x00 #define A_OUT_D_FRONT_R 0x01 #define A_OUT_D_FRONT A_OUT_D_FRONT_L #define A_OUT_D_CENTER 0x02 #define A_OUT_D_SUB 0x03 #define A_OUT_D_SIDE_L 0x04 #define A_OUT_D_SIDE_R 0x05 #define A_OUT_D_SIDE A_OUT_D_SIDE_L #define A_OUT_D_REAR_L 0x06 #define A_OUT_D_REAR_R 0x07 #define A_OUT_D_REAR A_OUT_D_REAR_L /* on Audigy Platinum only */ #define A_OUT_HPHONE_L 0x04 #define A_OUT_HPHONE_R 0x05 #define A_OUT_HPHONE A_OUT_HPHONE_L #define A_OUT_A_FRONT_L 0x08 #define A_OUT_A_FRONT_R 0x09 #define A_OUT_A_FRONT A_OUT_A_FRONT_L #define A_OUT_A_CENTER 0x0a #define A_OUT_A_SUB 0x0b #define A_OUT_A_SIDE_L 0x0c #define A_OUT_A_SIDE_R 0x0d #define A_OUT_A_SIDE A_OUT_A_SIDE_L #define A_OUT_A_REAR_L 0x0e #define A_OUT_A_REAR_R 0x0f #define A_OUT_A_REAR A_OUT_A_REAR_L #define A_OUT_AC97_L 0x10 #define A_OUT_AC97_R 0x11 #define A_OUT_AC97 A_OUT_AC97_L #define A_OUT_ADC_REC_L 0x16 #define A_OUT_ADC_REC_R 0x17 #define A_OUT_ADC_REC A_OUT_ADC_REC_L #define EMU_DATA2 0x24 #define EMU_IPR2 0x28 #define EMU_INTE2 0x2c #define EMU_IPR3 0x38 #define EMU_INTE3 0x3c #define EMU_A2_SRCSel 0x60 #define EMU_A2_SRCMULTI_ENABLE 0x6e #define EMU_A_I2S_CAPTURE_96000 0x00000400 #define EMU_A2_MIXER_I2S_ENABLE 0x7B #define EMU_A2_MIXER_SPDIF_ENABLE 0x7A #define C_FRONT_L 0 #define C_FRONT_R 1 #define C_REC_L 2 #define C_REC_R 3 #define C_REAR_L 4 #define C_REAR_R 5 #define C_CENTER 6 #define C_SUB 7 #define C_SIDE_L 8 #define C_SIDE_R 9 #define NUM_CACHES 10 #define CDSPDIFMUTE 0 #define ANALOGMUTE 1 #define NUM_MUTE 2 #define EMU_MAX_GPR 512 #define EMU_MAX_IRQ_CONSUMERS 32 struct emu_voice { int vnum; unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start; int end; int vol; uint32_t buf; void *vbuf; struct emu_voice *slave; uint32_t sa; uint32_t ea; uint32_t routing[8]; uint32_t amounts[8]; }; struct emu_memblk { SLIST_ENTRY(emu_memblk) link; void *buf; char owner[16]; bus_addr_t buf_addr; uint32_t pte_start, pte_size; bus_dmamap_t buf_map; }; struct emu_mem { uint8_t bmap[EMU_MAXPAGES / 8]; uint32_t *ptb_pages; void *silent_page; bus_addr_t ptb_pages_addr; bus_addr_t silent_page_addr; bus_dmamap_t ptb_map; bus_dmamap_t silent_map; bus_dma_tag_t dmat; struct emu_sc_info *card; SLIST_HEAD(, emu_memblk) blocks; }; /* rm */ struct emu_rm { struct emu_sc_info *card; struct mtx gpr_lock; signed int allocmap[EMU_MAX_GPR]; int num_gprs; int last_free_gpr; int num_used; }; struct emu_intr_handler { void* softc; uint32_t intr_mask; uint32_t inte_mask; uint32_t(*irq_func) (void *softc, uint32_t irq); }; struct emu_sc_info { struct mtx lock; struct mtx rw; /* Hardware exclusive access lock */ /* Hardware and subdevices */ device_t dev; device_t pcm[RT_COUNT]; device_t midi[2]; uint32_t type; uint32_t rev; bus_space_tag_t st; bus_space_handle_t sh; struct cdev *cdev; /* /dev/emu10k character device */ struct mtx emu10kx_lock; int emu10kx_isopen; struct sbuf emu10kx_sbuf; int emu10kx_bufptr; /* Resources */ struct resource *reg; struct resource *irq; void *ih; /* IRQ handlers */ struct emu_intr_handler ihandler[EMU_MAX_IRQ_CONSUMERS]; /* Card HW configuration */ unsigned int mode; /* analog / digital */ unsigned int mchannel_fx; unsigned int dsp_zero; unsigned int code_base; unsigned int code_size; unsigned int gpr_base; unsigned int num_gprs; unsigned int input_base; unsigned int output_base; unsigned int efxc_base; unsigned int opcode_shift; unsigned int high_operand_shift; unsigned int address_mask; uint32_t is_emu10k1:1, is_emu10k2, is_ca0102, is_ca0108:1, has_ac97:1, has_51:1, has_71:1, enable_ir:1, broken_digital:1, is_cardbus:1; signed int mch_disabled, mch_rec, dbg_level; signed int num_inputs; unsigned int num_outputs; unsigned int num_fxbuses; unsigned int routing_code_start; unsigned int routing_code_end; /* HW resources */ struct emu_voice voice[NUM_G]; /* Hardware voices */ uint32_t irq_mask[EMU_MAX_IRQ_CONSUMERS]; /* IRQ manager data */ int timer[EMU_MAX_IRQ_CONSUMERS]; /* timer */ int timerinterval; struct emu_rm *rm; struct emu_mem mem; /* memory */ /* Mixer */ int mixer_gpr[NUM_MIXERS]; int mixer_volcache[NUM_MIXERS]; int cache_gpr[NUM_CACHES]; int dummy_gpr; int mute_gpr[NUM_MUTE]; struct sysctl_ctx_list *ctx; struct sysctl_oid *root; }; static void emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error); static void* emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, bus_dmamap_t *map); static void emu_free(struct emu_mem *mem, void *dmabuf, bus_dmamap_t map); static void* emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char * owner); static int emu_memfree(struct emu_mem *mem, void *membuf); static int emu_memstart(struct emu_mem *mem, void *membuf); /* /dev */ static int emu10kx_dev_init(struct emu_sc_info *sc); static int emu10kx_dev_uninit(struct emu_sc_info *sc); static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s); static void emumix_set_mode(struct emu_sc_info *sc, int mode); static void emumix_set_spdif_mode(struct emu_sc_info *sc, int mode); static void emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol); static void emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val); static int sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS); static int emu_rm_init(struct emu_sc_info *sc); static int emu_rm_uninit(struct emu_sc_info *sc); static int emu_rm_gpr_alloc(struct emu_rm *rm, int count); static unsigned int emu_getcard(device_t dev); static uint32_t emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size); static void emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size); static void emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data); static void emu_vstop(struct emu_sc_info *sc, char channel, int enable); static void emu_intr(void *p); static void emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data); static void emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc); static void emu_initefx(struct emu_sc_info *sc); static int emu_cardbus_init(struct emu_sc_info *sc); static int emu_init(struct emu_sc_info *sc); static int emu_uninit(struct emu_sc_info *sc); static int emu_read_ivar(device_t bus __unused, device_t dev, int ivar_index, uintptr_t * result); static int emu_write_ivar(device_t bus __unused, device_t dev __unused, int ivar_index, uintptr_t value __unused); static int emu_pci_probe(device_t dev); static int emu_pci_attach(device_t dev); static int emu_pci_detach(device_t dev); static int emu_modevent(module_t mod __unused, int cmd, void *data __unused); #ifdef SND_EMU10KX_DEBUG #define EMU_MTX_DEBUG() do { \ if (mtx_owned(&sc->rw)) { \ printf("RW owned in %s line %d for %s\n", __func__, \ __LINE__ , device_get_nameunit(sc->dev)); \ printf("rw lock owned: %d\n", mtx_owned(&sc->rw)); \ printf("rw lock: value %x thread %x\n", \ ((&sc->rw)->mtx_lock & ~MTX_FLAGMASK), \ (uintptr_t)curthread); \ printf("rw lock: recursed %d\n", mtx_recursed(&sc->rw));\ db_show_mtx(&sc->rw); \ } \ } while (0) #else #define EMU_MTX_DEBUG() do { \ } while (0) #endif #define EMU_RWLOCK() do { \ EMU_MTX_DEBUG(); \ mtx_lock(&(sc->rw)); \ } while (0) #define EMU_RWUNLOCK() do { \ mtx_unlock(&(sc->rw)); \ EMU_MTX_DEBUG(); \ } while (0) /* Supported cards */ struct emu_hwinfo { uint16_t vendor; uint16_t device; uint16_t subvendor; uint16_t subdevice; char SBcode[8]; char desc[32]; int flags; }; static struct emu_hwinfo emu_cards[] = { {0xffff, 0xffff, 0xffff, 0xffff, "BADCRD", "Not a compatible card", 0}, /* 0x0020..0x002f 4.0 EMU10K1 cards */ {0x1102, 0x0002, 0x1102, 0x0020, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x0021, "CT4620", "SBLive!", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x002f, "CT????", "SBLive! mainboard implementation", HAS_AC97 | IS_EMU10K1}, /* (range unknown) 5.1 EMU10K1 cards */ {0x1102, 0x0002, 0x1102, 0x100a, "CT????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, /* 0x80??..0x805? 4.0 EMU10K1 cards */ {0x1102, 0x0002, 0x1102, 0x8022, "CT4780", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8023, "CT4790", "SB PCI512", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8024, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8025, "CT????", "SBLive! Mainboard Implementation", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8026, "CT4830", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8027, "CT4832", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8028, "CT4760", "SBLive! OEM version", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8031, "CT4831", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8040, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8051, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, /* 0x8061..0x???? 5.1 EMU10K1 cards */ {0x1102, 0x0002, 0x1102, 0x8061, "SB????", "SBLive! Player 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8062, "CT4830", "SBLive! 1024", HAS_AC97 | HAS_51 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8064, "SB????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8065, "SB0220", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8066, "CT4780", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1}, {0x1102, 0x0002, 0x1102, 0x8067, "SB????", "SBLive!", HAS_AC97 | HAS_51 | IS_EMU10K1}, /* Generic SB Live! */ {0x1102, 0x0002, 0x1102, 0x0000, "SB????", "SBLive! (Unknown model)", HAS_AC97 | IS_EMU10K1}, /* 0x0041..0x0043 EMU10K2 (some kind of Audigy) cards */ /* 0x0051..0x0051 5.1 CA0100-IAF cards */ {0x1102, 0x0004, 0x1102, 0x0051, "SB0090", "Audigy", HAS_AC97 | HAS_51 | IS_EMU10K2}, /* ES is CA0100-IDF chip that don't work in digital mode */ {0x1102, 0x0004, 0x1102, 0x0052, "SB0160", "Audigy ES", HAS_AC97 | HAS_71 | IS_EMU10K2 | BROKEN_DIGITAL}, /* 0x0053..0x005C 5.1 CA0101-NAF cards */ {0x1102, 0x0004, 0x1102, 0x0053, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2}, {0x1102, 0x0004, 0x1102, 0x0058, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2}, /* 0x1002..0x1009 5.1 CA0102-IAT cards */ {0x1102, 0x0004, 0x1102, 0x1002, "SB????", "Audigy 2 Platinum", HAS_51 | IS_CA0102}, {0x1102, 0x0004, 0x1102, 0x1005, "SB????", "Audigy 2 Platinum EX", HAS_51 | IS_CA0102}, {0x1102, 0x0004, 0x1102, 0x1007, "SB0240", "Audigy 2", HAS_AC97 | HAS_51 | IS_CA0102}, /* 0x2001..0x2003 7.1 CA0102-ICT cards */ {0x1102, 0x0004, 0x1102, 0x2001, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, {0x1102, 0x0004, 0x1102, 0x2002, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, /* XXX No reports about 0x2003 & 0x2004 cards */ {0x1102, 0x0004, 0x1102, 0x2003, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, {0x1102, 0x0004, 0x1102, 0x2004, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, {0x1102, 0x0004, 0x1102, 0x2005, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, /* (range unknown) 7.1 CA0102-xxx Audigy 4 cards */ {0x1102, 0x0004, 0x1102, 0x2007, "SB0380", "Audigy 4 Pro", HAS_AC97 | HAS_71 | IS_CA0102}, /* Generic Audigy or Audigy 2 */ {0x1102, 0x0004, 0x1102, 0x0000, "SB????", "Audigy (Unknown model)", HAS_AC97 | HAS_51 | IS_EMU10K2}, /* We don't support CA0103-DAT (Audigy LS) cards */ /* There is NO CA0104-xxx cards */ /* There is NO CA0105-xxx cards */ /* We don't support CA0106-DAT (SB Live! 24 bit) cards */ /* There is NO CA0107-xxx cards */ /* 0x1000..0x1001 7.1 CA0108-IAT cards */ {0x1102, 0x0008, 0x1102, 0x1000, "SB????", "Audigy 2 LS", HAS_AC97 | HAS_51 | IS_CA0108 | DIGITAL_ONLY}, {0x1102, 0x0008, 0x1102, 0x1001, "SB0400", "Audigy 2 Value", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY}, {0x1102, 0x0008, 0x1102, 0x1021, "SB0610", "Audigy 4", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY}, {0x1102, 0x0008, 0x1102, 0x2001, "SB0530", "Audigy 2 ZS CardBus", HAS_AC97 | HAS_71 | IS_CA0108 | IS_CARDBUS}, {0x1102, 0x0008, 0x0000, 0x0000, "SB????", "Audigy 2 Value (Unknown model)", HAS_AC97 | HAS_51 | IS_CA0108}, }; /* Unsupported cards */ static struct emu_hwinfo emu_bad_cards[] = { /* APS cards should be possible to support */ {0x1102, 0x0002, 0x1102, 0x4001, "EMUAPS", "E-mu APS", 0}, {0x1102, 0x0002, 0x1102, 0x4002, "EMUAPS", "E-mu APS", 0}, {0x1102, 0x0004, 0x1102, 0x4001, "EMU???", "E-mu 1212m [4001]", 0}, /* Similar-named ("Live!" or "Audigy") cards on different chipsets */ {0x1102, 0x8064, 0x0000, 0x0000, "SB0100", "SBLive! 5.1 OEM", 0}, {0x1102, 0x0006, 0x0000, 0x0000, "SB0200", "DELL OEM SBLive! Value", 0}, {0x1102, 0x0007, 0x0000, 0x0000, "SB0310", "Audigy LS", 0}, }; /* * Get best known information about device. */ static unsigned int emu_getcard(device_t dev) { uint16_t device; uint16_t subdevice; int n_cards; unsigned int thiscard; int i; device = pci_read_config(dev, PCIR_DEVICE, /* bytes */ 2); subdevice = pci_read_config(dev, PCIR_SUBDEV_0, /* bytes */ 2); n_cards = sizeof(emu_cards) / sizeof(struct emu_hwinfo); thiscard = 0; for (i = 1; i < n_cards; i++) { if (device == emu_cards[i].device) { if (subdevice == emu_cards[i].subdevice) { thiscard = i; break; } if (0x0000 == emu_cards[i].subdevice) { thiscard = i; /* * don't break, we can get more specific card * later in the list. */ } } } n_cards = sizeof(emu_bad_cards) / sizeof(struct emu_hwinfo); for (i = 0; i < n_cards; i++) { if (device == emu_bad_cards[i].device) { if (subdevice == emu_bad_cards[i].subdevice) { thiscard = 0; break; } if (0x0000 == emu_bad_cards[i].subdevice) { thiscard = 0; break; /* we avoid all this cards */ } } } return (thiscard); } /* * Base hardware interface are 32 (Audigy) or 64 (Audigy2) registers. * Some of them are used directly, some of them provide pointer / data pairs. */ static uint32_t emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size) { KASSERT(sc != NULL, ("emu_rd: NULL sc")); 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)); } return (0xffffffff); } static void emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size) { KASSERT(sc != NULL, ("emu_rd: NULL sc")); 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; } } /* * EMU_PTR / EMU_DATA interface. Access to EMU10Kx is made * via (channel, register) pair. Some registers are channel-specific, * some not. */ uint32_t emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg) { uint32_t ptr, val, mask, size, offset; ptr = ((reg << 16) & sc->address_mask) | (chn & EMU_PTR_CHNO_MASK); EMU_RWLOCK(); emu_wr_nolock(sc, EMU_PTR, ptr, 4); val = emu_rd_nolock(sc, EMU_DATA, 4); EMU_RWUNLOCK(); /* * XXX Some register numbers has data size and offset encoded in * it to get only part of 32bit register. This use is not described * in register name, be careful! */ if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; val &= mask; val >>= offset; } return (val); } void emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data) { uint32_t ptr, mask, size, offset; ptr = ((reg << 16) & sc->address_mask) | (chn & EMU_PTR_CHNO_MASK); EMU_RWLOCK(); emu_wr_nolock(sc, EMU_PTR, ptr, 4); /* * XXX Another kind of magic encoding in register number. This can * give you side effect - it will read previous data from register * and change only required bits. */ if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; data <<= offset; data &= mask; data |= emu_rd_nolock(sc, EMU_DATA, 4) & ~mask; } emu_wr_nolock(sc, EMU_DATA, data, 4); EMU_RWUNLOCK(); } /* * EMU_A2_PTR / EMU_DATA2 interface. Access to P16v is made * via (channel, register) pair. Some registers are channel-specific, * some not. This interface is supported by CA0102 and CA0108 chips only. */ uint32_t emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg) { uint32_t val; /* XXX separate lock? */ EMU_RWLOCK(); emu_wr_nolock(sc, EMU_A2_PTR, (reg << 16) | chn, 4); val = emu_rd_nolock(sc, EMU_DATA2, 4); EMU_RWUNLOCK(); return (val); } void emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data) { EMU_RWLOCK(); emu_wr_nolock(sc, EMU_A2_PTR, (reg << 16) | chn, 4); emu_wr_nolock(sc, EMU_DATA2, data, 4); EMU_RWUNLOCK(); } /* * XXX CardBus interface. Not tested on any real hardware. */ static void emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data) { uint32_t val; /* * 0x38 is IPE3 (CD S/PDIF interrupt pending register) on CA0102. Seems * to be some reg/value accessible kind of config register on CardBus * CA0108, with value(?) in top 16 bit, address(?) in low 16 */ val = emu_rd_nolock(sc, 0x38, 4); emu_wr_nolock(sc, 0x38, data, 4); val = emu_rd_nolock(sc, 0x38, 4); } /* * Direct hardware register access * Assume that it is never used to access EMU_PTR-based registers and can run unlocked. */ void emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size) { KASSERT(regno != EMU_PTR, ("emu_wr: attempt to write to EMU_PTR")); KASSERT(regno != EMU_A2_PTR, ("emu_wr: attempt to write to EMU_A2_PTR")); emu_wr_nolock(sc, regno, data, size); } uint32_t emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size) { uint32_t rd; KASSERT(regno != EMU_DATA, ("emu_rd: attempt to read DATA")); KASSERT(regno != EMU_DATA2, ("emu_rd: attempt to read DATA2")); rd = emu_rd_nolock(sc, regno, size); return (rd); } /* * Enabling IR MIDI messages is another kind of black magic. It just * has to be made this way. It really do it. */ void emu_enable_ir(struct emu_sc_info *sc) { uint32_t iocfg; if (sc->is_emu10k2 || sc->is_ca0102) { iocfg = emu_rd_nolock(sc, EMU_A_IOCFG, 2); emu_wr_nolock(sc, EMU_A_IOCFG, iocfg | EMU_A_IOCFG_GPOUT2, 2); DELAY(500); emu_wr_nolock(sc, EMU_A_IOCFG, iocfg | EMU_A_IOCFG_GPOUT1 | EMU_A_IOCFG_GPOUT2, 2); DELAY(500); emu_wr_nolock(sc, EMU_A_IOCFG, iocfg | EMU_A_IOCFG_GPOUT1, 2); DELAY(100); emu_wr_nolock(sc, EMU_A_IOCFG, iocfg, 2); device_printf(sc->dev, "Audigy IR MIDI events enabled.\n"); sc->enable_ir = 1; } if (sc->is_emu10k1) { iocfg = emu_rd_nolock(sc, EMU_HCFG, 4); emu_wr_nolock(sc, EMU_HCFG, iocfg | EMU_HCFG_GPOUT2, 4); DELAY(500); emu_wr_nolock(sc, EMU_HCFG, iocfg | EMU_HCFG_GPOUT1 | EMU_HCFG_GPOUT2, 4); DELAY(100); emu_wr_nolock(sc, EMU_HCFG, iocfg, 4); device_printf(sc->dev, "SB Live! IR MIDI events enabled.\n"); sc->enable_ir = 1; } } /* * emu_timer_ - HW timer management */ int emu_timer_create(struct emu_sc_info *sc) { int i, timer; timer = -1; mtx_lock(&sc->lock); for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) if (sc->timer[i] == 0) { sc->timer[i] = -1; /* disable it */ timer = i; mtx_unlock(&sc->lock); return (timer); } mtx_unlock(&sc->lock); return (-1); } int emu_timer_set(struct emu_sc_info *sc, int timer, int delay) { int i; if (timer < 0) return (-1); RANGE(delay, 16, 1024); RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); mtx_lock(&sc->lock); sc->timer[timer] = delay; for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) if (sc->timerinterval > sc->timer[i]) sc->timerinterval = sc->timer[i]; /* XXX */ emu_wr(sc, EMU_TIMER, sc->timerinterval & 0x03ff, 2); mtx_unlock(&sc->lock); return (timer); } int emu_timer_enable(struct emu_sc_info *sc, int timer, int go) { uint32_t x; int ena_int; int i; if (timer < 0) return (-1); RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); mtx_lock(&sc->lock); if ((go == 1) && (sc->timer[timer] < 0)) sc->timer[timer] = -sc->timer[timer]; if ((go == 0) && (sc->timer[timer] > 0)) sc->timer[timer] = -sc->timer[timer]; ena_int = 0; for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) { if (sc->timerinterval > sc->timer[i]) sc->timerinterval = sc->timer[i]; if (sc->timer[i] > 0) ena_int = 1; } emu_wr(sc, EMU_TIMER, sc->timerinterval & 0x03ff, 2); if (ena_int == 1) { x = emu_rd(sc, EMU_INTE, 4); x |= EMU_INTE_INTERTIMERENB; emu_wr(sc, EMU_INTE, x, 4); } else { x = emu_rd(sc, EMU_INTE, 4); x &= ~EMU_INTE_INTERTIMERENB; emu_wr(sc, EMU_INTE, x, 4); } mtx_unlock(&sc->lock); return (0); } int emu_timer_clear(struct emu_sc_info *sc, int timer) { if (timer < 0) return (-1); RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); emu_timer_enable(sc, timer, 0); mtx_lock(&sc->lock); if (sc->timer[timer] != 0) sc->timer[timer] = 0; mtx_unlock(&sc->lock); return (timer); } /* * emu_intr_ - HW interrupt handler management */ int emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc) { int i; uint32_t x; mtx_lock(&sc->lock); for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) if (sc->ihandler[i].inte_mask == 0) { sc->ihandler[i].inte_mask = inte_mask; sc->ihandler[i].intr_mask = intr_mask; sc->ihandler[i].softc = isc; sc->ihandler[i].irq_func = func; x = emu_rd(sc, EMU_INTE, 4); x |= inte_mask; emu_wr(sc, EMU_INTE, x, 4); mtx_unlock(&sc->lock); if (sc->dbg_level > 1) device_printf(sc->dev, "ihandle %d registered\n", i); return (i); } mtx_unlock(&sc->lock); if (sc->dbg_level > 1) device_printf(sc->dev, "ihandle not registered\n"); return (-1); } int emu_intr_unregister(struct emu_sc_info *sc, int hnumber) { uint32_t x; int i; mtx_lock(&sc->lock); if (sc->ihandler[hnumber].inte_mask == 0) { mtx_unlock(&sc->lock); return (-1); } x = emu_rd(sc, EMU_INTE, 4); x &= ~sc->ihandler[hnumber].inte_mask; sc->ihandler[hnumber].inte_mask = 0; sc->ihandler[hnumber].intr_mask = 0; sc->ihandler[hnumber].softc = NULL; sc->ihandler[hnumber].irq_func = NULL; /* other interrupt handlers may use this EMU_INTE value */ for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) if (sc->ihandler[i].inte_mask != 0) x |= sc->ihandler[i].inte_mask; emu_wr(sc, EMU_INTE, x, 4); mtx_unlock(&sc->lock); return (hnumber); } static void emu_intr(void *p) { struct emu_sc_info *sc = (struct emu_sc_info *)p; uint32_t stat, ack; int i; for (;;) { stat = emu_rd(sc, EMU_IPR, 4); ack = 0; if (stat == 0) break; emu_wr(sc, EMU_IPR, stat, 4); for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) { if ((((sc->ihandler[i].intr_mask) & stat) != 0) && (((void *)sc->ihandler[i].irq_func) != NULL)) { ack |= sc->ihandler[i].irq_func(sc->ihandler[i].softc, (sc->ihandler[i].intr_mask) & stat); } } if (sc->dbg_level > 1) if (stat & (~ack)) device_printf(sc->dev, "Unhandled interrupt: %08x\n", stat & (~ack)); } if ((sc->is_ca0102) || (sc->is_ca0108)) for (;;) { stat = emu_rd(sc, EMU_IPR2, 4); ack = 0; if (stat == 0) break; emu_wr(sc, EMU_IPR2, stat, 4); if (sc->dbg_level > 1) device_printf(sc->dev, "EMU_IPR2: %08x\n", stat); break; /* to avoid infinite loop. should be removed * after completion of P16V interface. */ } if (sc->is_ca0102) for (;;) { stat = emu_rd(sc, EMU_IPR3, 4); ack = 0; if (stat == 0) break; emu_wr(sc, EMU_IPR3, stat, 4); if (sc->dbg_level > 1) device_printf(sc->dev, "EMU_IPR3: %08x\n", stat); break; /* to avoid infinite loop. should be removed * after completion of S/PDIF interface */ } } /* * Get data from private emu10kx structure for PCM buffer allocation. * Used by PCM code only. */ bus_dma_tag_t emu_gettag(struct emu_sc_info *sc) { return (sc->mem.dmat); } static void emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error) { bus_addr_t *phys = (bus_addr_t *) arg; *phys = error ? 0 : (bus_addr_t) segs->ds_addr; if (bootverbose) { printf("emu10kx: 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 emu_mem *mem, uint32_t sz, bus_addr_t * addr, bus_dmamap_t *map) { void *dmabuf; int error; *addr = 0; if ((error = bus_dmamem_alloc(mem->dmat, &dmabuf, BUS_DMA_NOWAIT, map))) { if (mem->card->dbg_level > 2) device_printf(mem->card->dev, "emu_malloc: failed to alloc DMA map: %d\n", error); return (NULL); } if ((error = bus_dmamap_load(mem->dmat, *map, dmabuf, sz, emu_setmap, addr, 0)) || !*addr) { if (mem->card->dbg_level > 2) device_printf(mem->card->dev, "emu_malloc: failed to load DMA memory: %d\n", error); bus_dmamem_free(mem->dmat, dmabuf, *map); return (NULL); } return (dmabuf); } static void emu_free(struct emu_mem *mem, void *dmabuf, bus_dmamap_t map) { bus_dmamap_unload(mem->dmat, map); bus_dmamem_free(mem->dmat, dmabuf, map); } static void * emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char *owner) { uint32_t blksz, start, idx, ofs, tmp, found; struct emu_memblk *blk; void *membuf; blksz = sz / EMUPAGESIZE; if (sz > (blksz * EMUPAGESIZE)) blksz++; if (blksz > EMU_MAX_BUFSZ / EMUPAGESIZE) { if (mem->card->dbg_level > 2) device_printf(mem->card->dev, "emu_memalloc: memory request tool large\n"); return (NULL); } /* find a free block in the bitmap */ found = 0; start = 1; while (!found && start + blksz < EMU_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) { if (mem->card->dbg_level > 2) device_printf(mem->card->dev, "emu_memalloc: no free space in bitmap\n"); return (NULL); } blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT); if (blk == NULL) { if (mem->card->dbg_level > 2) device_printf(mem->card->dev, "emu_memalloc: buffer allocation failed\n"); return (NULL); } bzero(blk, sizeof(*blk)); membuf = emu_malloc(mem, sz, &blk->buf_addr, &blk->buf_map); *addr = blk->buf_addr; if (membuf == NULL) { if (mem->card->dbg_level > 2) device_printf(mem->card->dev, "emu_memalloc: can't setup HW memory\n"); free(blk, M_DEVBUF); return (NULL); } blk->buf = membuf; blk->pte_start = start; blk->pte_size = blksz; strncpy(blk->owner, owner, 15); blk->owner[15] = '\0'; ofs = 0; for (idx = start; idx < start + blksz; idx++) { mem->bmap[idx >> 3] |= 1 << (idx & 7); tmp = (uint32_t) (blk->buf_addr + ofs); mem->ptb_pages[idx] = (tmp << 1) | idx; ofs += EMUPAGESIZE; } SLIST_INSERT_HEAD(&mem->blocks, blk, link); return (membuf); } static int emu_memfree(struct emu_mem *mem, void *membuf) { uint32_t idx, tmp; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == membuf) blk = i; } if (blk == NULL) return (EINVAL); SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link); emu_free(mem, membuf, blk->buf_map); tmp = (uint32_t) (mem->silent_page_addr) << 1; for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) { mem->bmap[idx >> 3] &= ~(1 << (idx & 7)); mem->ptb_pages[idx] = tmp | idx; } free(blk, M_DEVBUF); return (0); } static int emu_memstart(struct emu_mem *mem, void *membuf) { struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == membuf) blk = i; } if (blk == NULL) return (-1); return (blk->pte_start); } static uint32_t emu_rate_to_pitch(uint32_t rate) { static uint32_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); rate *= 11185; /* Scale 48000 to 0x20002380 */ for (i = 31; i > 0; i--) { if (rate & 0x80000000) { /* Detect leading "1" */ return (((uint32_t) (i - 15) << 20) + logMagTable[0x7f & (rate >> 24)] + (0x7f & (rate >> 17)) * logSlopeTable[0x7f & (rate >> 24)]); } rate <<= 1; } /* NOTREACHED */ return (0); } static uint32_t emu_rate_to_linearpitch(uint32_t rate) { rate = (rate << 8) / 375; return ((rate >> 1) + (rate & 1)); } struct emu_voice * emu_valloc(struct emu_sc_info *sc) { struct emu_voice *v; int i; v = NULL; mtx_lock(&sc->lock); for (i = 0; i < NUM_G && sc->voice[i].busy; i++); if (i < NUM_G) { v = &sc->voice[i]; v->busy = 1; } mtx_unlock(&sc->lock); return (v); } void emu_vfree(struct emu_sc_info *sc, struct emu_voice *v) { int i, r; mtx_lock(&sc->lock); for (i = 0; i < NUM_G; i++) { if (v == &sc->voice[i] && sc->voice[i].busy) { v->busy = 0; /* * XXX What we should do with mono channels? * See -pcm.c emupchan_init for other side of * this problem */ if (v->slave != NULL) r = emu_memfree(&sc->mem, v->vbuf); } } mtx_unlock(&sc->lock); } int emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s, uint32_t sz, struct snd_dbuf *b) { void *vbuf; bus_addr_t tmp_addr; vbuf = emu_memalloc(&sc->mem, sz, &tmp_addr, "vinit"); if (vbuf == NULL) { if(sc->dbg_level > 2) device_printf(sc->dev, "emu_memalloc returns NULL in enu_vinit\n"); return (ENOMEM); } if (b != NULL) sndbuf_setup(b, vbuf, sz); m->start = emu_memstart(&sc->mem, vbuf) * EMUPAGESIZE; if (m->start < 0) { if(sc->dbg_level > 2) device_printf(sc->dev, "emu_memstart returns (-1) in enu_vinit\n"); emu_memfree(&sc->mem, vbuf); return (ENOMEM); } m->end = m->start + sz; m->speed = 0; m->b16 = 0; m->stereo = 0; m->running = 0; m->ismaster = 1; m->vol = 0xff; m->buf = tmp_addr; m->vbuf = vbuf; m->slave = s; if (s != NULL) { s->start = m->start; s->end = m->end; s->speed = 0; s->b16 = 0; s->stereo = 0; s->running = 0; s->ismaster = 0; s->vol = m->vol; s->buf = m->buf; s->vbuf = NULL; s->slave = NULL; } return (0); } void emu_vsetup(struct emu_voice *v, int fmt, int spd) { if (fmt) { v->b16 = (fmt & AFMT_16BIT) ? 1 : 0; v->stereo = (AFMT_CHANNEL(fmt) > 1) ? 1 : 0; if (v->slave != NULL) { v->slave->b16 = v->b16; v->slave->stereo = v->stereo; } } if (spd) { v->speed = spd; if (v->slave != NULL) v->slave->speed = v->speed; } } void emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v) { int i; for (i = 0; i < 8; i++) { v->routing[i] = rt->routing_left[i]; v->amounts[i] = rt->amounts_left[i]; } if ((v->stereo) && (v->ismaster == 0)) for (i = 0; i < 8; i++) { v->routing[i] = rt->routing_right[i]; v->amounts[i] = rt->amounts_right[i]; } if ((v->stereo) && (v->slave != NULL)) emu_vroute(sc, rt, v->slave); } void emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v) { int s; uint32_t start, val, silent_page; s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0); v->sa = v->start >> s; v->ea = v->end >> s; if (v->stereo) { emu_wrptr(sc, v->vnum, EMU_CHAN_CPF, EMU_CHAN_CPF_STEREO_MASK); } else { emu_wrptr(sc, v->vnum, EMU_CHAN_CPF, 0); } val = v->stereo ? 28 : 30; val *= v->b16 ? 1 : 2; start = v->sa + val; if (sc->is_emu10k1) { emu_wrptr(sc, v->vnum, EMU_CHAN_FXRT, ((v->routing[3] << 12) | (v->routing[2] << 8) | (v->routing[1] << 4) | (v->routing[0] << 0)) << 16); } else { emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT1, (v->routing[3] << 24) | (v->routing[2] << 16) | (v->routing[1] << 8) | (v->routing[0] << 0)); emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT2, (v->routing[7] << 24) | (v->routing[6] << 16) | (v->routing[5] << 8) | (v->routing[4] << 0)); emu_wrptr(sc, v->vnum, EMU_A_CHAN_SENDAMOUNTS, (v->amounts[7] << 24) | (v->amounts[6] << 26) | (v->amounts[5] << 8) | (v->amounts[4] << 0)); } emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX, (v->amounts[0] << 8) | (v->amounts[1] << 0)); emu_wrptr(sc, v->vnum, EMU_CHAN_DSL, v->ea | (v->amounts[3] << 24)); emu_wrptr(sc, v->vnum, EMU_CHAN_PSST, v->sa | (v->amounts[2] << 24)); emu_wrptr(sc, v->vnum, EMU_CHAN_CCCA, start | (v->b16 ? 0 : EMU_CHAN_CCCA_8BITSELECT)); emu_wrptr(sc, v->vnum, EMU_CHAN_Z1, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_Z2, 0); silent_page = ((uint32_t) (sc->mem.silent_page_addr) << 1) | EMU_CHAN_MAP_PTI_MASK; emu_wrptr(sc, v->vnum, EMU_CHAN_MAPA, silent_page); emu_wrptr(sc, v->vnum, EMU_CHAN_MAPB, silent_page); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, EMU_CHAN_CVCF_CURRFILTER_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, EMU_CHAN_VTFT_FILTERTARGET_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDM, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSM, EMU_CHAN_DCYSUSM_DECAYTIME_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL1, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL2, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_FMMOD, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_TREMFRQ, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_FM2FRQ2, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVAL, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDV, EMU_CHAN_ATKHLDV_HOLDTIME_MASK | EMU_CHAN_ATKHLDV_ATTACKTIME_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVOL, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_FILTERAMOUNT, 0x7f); emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_PITCHAMOUNT, 0); if ((v->stereo) && (v->slave != NULL)) emu_vwrite(sc, v->slave); } static void emu_vstop(struct emu_sc_info *sc, char channel, int enable) { int reg; reg = (channel & 0x20) ? EMU_SOLEH : EMU_SOLEL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; emu_wrptr(sc, 0, reg, enable); } void emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go) { uint32_t pitch_target, initial_pitch; uint32_t cra, cs, ccis; uint32_t sample, i; if (go) { cra = 64; cs = v->stereo ? 4 : 2; ccis = v->stereo ? 28 : 30; ccis *= v->b16 ? 1 : 2; sample = v->b16 ? 0x00000000 : 0x80808080; for (i = 0; i < cs; i++) emu_wrptr(sc, v->vnum, EMU_CHAN_CD0 + i, sample); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_READADDRESS, cra); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, ccis); emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xff00); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0xffffffff); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0xffffffff); emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSV, 0x00007f7f); emu_vstop(sc, v->vnum, 0); pitch_target = emu_rate_to_linearpitch(v->speed); initial_pitch = emu_rate_to_pitch(v->speed) >> 8; emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, pitch_target); emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, pitch_target); emu_wrptr(sc, v->vnum, EMU_CHAN_IP, initial_pitch); } else { emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xffff); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0x0000ffff); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0x0000ffff); emu_wrptr(sc, v->vnum, EMU_CHAN_IP, 0); emu_vstop(sc, v->vnum, 1); } if ((v->stereo) && (v->slave != NULL)) emu_vtrigger(sc, v->slave, go); } int emu_vpos(struct emu_sc_info *sc, struct emu_voice *v) { int s, ptr; s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0); ptr = (emu_rdptr(sc, v->vnum, EMU_CHAN_CCCA_CURRADDR) - (v->start >> s)) << s; return (ptr & ~0x0000001f); } /* fx */ static void emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data) { emu_wrptr(sc, 0, sc->code_base + pc, data); } static void emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc) { if ((*pc) + 1 > sc->code_size) { device_printf(sc->dev, "DSP CODE OVERRUN: attept to write past code_size (pc=%d)\n", (*pc)); return; } emu_wrefx(sc, (*pc) * 2, (x << sc->high_operand_shift) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << sc->opcode_shift) | (z << sc->high_operand_shift) | w); (*pc)++; } static int sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS) { struct emu_sc_info *sc; int mixer_id; int new_vol; int err; sc = arg1; mixer_id = arg2; new_vol = emumix_get_volume(sc, mixer_id); err = sysctl_handle_int(oidp, &new_vol, 0, req); if (err || req->newptr == NULL) return (err); if (new_vol < 0 || new_vol > 100) return (EINVAL); emumix_set_volume(sc, mixer_id, new_vol); return (0); } static int emu_addefxmixer(struct emu_sc_info *sc, const char *mix_name, const int mix_id, uint32_t defvolume) { int volgpr; char sysctl_name[32]; volgpr = emu_rm_gpr_alloc(sc->rm, 1); emumix_set_fxvol(sc, volgpr, defvolume); /* * Mixer controls with NULL mix_name are handled * by AC97 emulation code or PCM mixer. */ if (mix_name != NULL) { /* * Temporary sysctls should start with underscore, * see freebsd-current mailing list, emu10kx driver * discussion around 2006-05-24. */ snprintf(sysctl_name, 32, "_%s", mix_name); SYSCTL_ADD_PROC(sc->ctx, SYSCTL_CHILDREN(sc->root), OID_AUTO, sysctl_name, CTLTYPE_INT | CTLFLAG_RW, sc, mix_id, sysctl_emu_mixer_control, "I", ""); } return (volgpr); } static int sysctl_emu_digitalswitch_control(SYSCTL_HANDLER_ARGS) { struct emu_sc_info *sc; int new_val; int err; sc = arg1; new_val = (sc->mode == MODE_DIGITAL) ? 1 : 0; err = sysctl_handle_int(oidp, &new_val, 0, req); if (err || req->newptr == NULL) return (err); if (new_val < 0 || new_val > 1) return (EINVAL); switch (new_val) { case 0: emumix_set_mode(sc, MODE_ANALOG); break; case 1: emumix_set_mode(sc, MODE_DIGITAL); break; } return (0); } static void emu_digitalswitch(struct emu_sc_info *sc) { /* XXX temporary? */ SYSCTL_ADD_PROC(sc->ctx, SYSCTL_CHILDREN(sc->root), OID_AUTO, "_digital", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_emu_digitalswitch_control, "I", "Enable digital output"); return; } /* * Allocate cache GPRs that will hold mixed output channels * and clear it on every DSP run. */ #define EFX_CACHE(CACHE_IDX) do { \ sc->cache_gpr[CACHE_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \ emu_addefxop(sc, ACC3, \ GPR(sc->cache_gpr[CACHE_IDX]), \ DSP_CONST(0), \ DSP_CONST(0), \ DSP_CONST(0), \ &pc); \ } while (0) /* Allocate GPR for volume control and route sound: OUT = OUT + IN * VOL */ #define EFX_ROUTE(TITLE, INP_NR, IN_GPR_IDX, OUT_CACHE_IDX, DEF) do { \ sc->mixer_gpr[IN_GPR_IDX] = emu_addefxmixer(sc, TITLE, IN_GPR_IDX, DEF); \ sc->mixer_volcache[IN_GPR_IDX] = DEF; \ emu_addefxop(sc, MACS, \ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ INP_NR, \ GPR(sc->mixer_gpr[IN_GPR_IDX]), \ &pc); \ } while (0) /* allocate GPR, OUT = IN * VOL */ #define EFX_OUTPUT(TITLE, OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR, DEF) do { \ sc->mixer_gpr[OUT_GPR_IDX] = emu_addefxmixer(sc, TITLE, OUT_GPR_IDX, DEF); \ sc->mixer_volcache[OUT_GPR_IDX] = DEF; \ emu_addefxop(sc, MACS, \ OUTP(OUTP_NR), \ DSP_CONST(0), \ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ GPR(sc->mixer_gpr[OUT_GPR_IDX]), \ &pc); \ } while (0) /* like EFX_OUTPUT, but don't allocate mixer gpr */ #define EFX_OUTPUTD(OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR) do { \ emu_addefxop(sc, MACS, \ OUTP(OUTP_NR), \ DSP_CONST(0), \ GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ GPR(sc->mixer_gpr[OUT_GPR_IDX]), \ &pc); \ } while (0) /* skip next OPCOUNT instructions if FLAG != 0 */ #define EFX_SKIP(OPCOUNT, FLAG_GPR) do { \ emu_addefxop(sc, MACS, \ DSP_CONST(0), \ GPR(sc->mute_gpr[FLAG_GPR]), \ DSP_CONST(0), \ DSP_CONST(0), \ &pc); \ emu_addefxop(sc, SKIP, \ DSP_CCR, \ DSP_CCR, \ COND_NEQ_ZERO, \ OPCOUNT, \ &pc); \ } while (0) #define EFX_COPY(TO, FROM) do { \ emu_addefxop(sc, ACC3, \ TO, \ DSP_CONST(0), \ DSP_CONST(0), \ FROM, \ &pc); \ } while (0) static void emu_initefx(struct emu_sc_info *sc) { unsigned int i; uint32_t pc; /* stop DSP */ if (sc->is_emu10k1) { emu_wrptr(sc, 0, EMU_DBG, EMU_DBG_SINGLE_STEP); } else { emu_wrptr(sc, 0, EMU_A_DBG, EMU_A_DBG_SINGLE_STEP); } /* code size is in instructions */ pc = 0; for (i = 0; i < sc->code_size; i++) { if (sc->is_emu10k1) { emu_addefxop(sc, ACC3, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), &pc); } else { emu_addefxop(sc, SKIP, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0xf), DSP_CONST(0x0), &pc); } } /* allocate GPRs for mute switches (EFX_SKIP). Mute by default */ for (i = 0; i < NUM_MUTE; i++) { sc->mute_gpr[i] = emu_rm_gpr_alloc(sc->rm, 1); emumix_set_gpr(sc, sc->mute_gpr[i], 1); } emu_digitalswitch(sc); pc = 0; /* * DSP code below is not good, because: * 1. It can be written smaller, if it can use DSP accumulator register * instead of cache_gpr[]. * 2. It can be more careful when volume is 100%, because in DSP * x*0x7fffffff may not be equal to x ! */ /* clean outputs */ for (i = 0; i < 16 ; i++) { emu_addefxop(sc, ACC3, OUTP(i), DSP_CONST(0), DSP_CONST(0), DSP_CONST(0), &pc); } if (sc->is_emu10k1) { EFX_CACHE(C_FRONT_L); EFX_CACHE(C_FRONT_R); EFX_CACHE(C_REC_L); EFX_CACHE(C_REC_R); /* fx0 to front/record, 100%/muted by default */ EFX_ROUTE("pcm_front_l", FX(0), M_FX0_FRONT_L, C_FRONT_L, 100); EFX_ROUTE("pcm_front_r", FX(1), M_FX1_FRONT_R, C_FRONT_R, 100); EFX_ROUTE(NULL, FX(0), M_FX0_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, FX(1), M_FX1_REC_R, C_REC_R, 0); /* in0, from AC97 codec output */ EFX_ROUTE("ac97_front_l", INP(IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 0); EFX_ROUTE("ac97_front_r", INP(IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 0); EFX_ROUTE("ac97_rec_l", INP(IN_AC97_L), M_IN0_REC_L, C_REC_L, 0); EFX_ROUTE("ac97_rec_r", INP(IN_AC97_R), M_IN0_REC_R, C_REC_R, 0); /* in1, from CD S/PDIF */ /* XXX EFX_SKIP 4 assumes that each EFX_ROUTE is one DSP op */ EFX_SKIP(4, CDSPDIFMUTE); EFX_ROUTE(NULL, INP(IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0); if (sc->dbg_level > 0) { /* in2, ZoomVide (???) */ EFX_ROUTE("zoom_front_l", INP(IN_ZOOM_L), M_IN2_FRONT_L, C_FRONT_L, 0); EFX_ROUTE("zoom_front_r", INP(IN_ZOOM_R), M_IN2_FRONT_R, C_FRONT_R, 0); EFX_ROUTE("zoom_rec_l", INP(IN_ZOOM_L), M_IN2_REC_L, C_REC_L, 0); EFX_ROUTE("zoom_rec_r", INP(IN_ZOOM_R), M_IN2_REC_R, C_REC_R, 0); } /* in3, TOSLink */ EFX_ROUTE(NULL, INP(IN_TOSLINK_L), M_IN3_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(IN_TOSLINK_R), M_IN3_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(IN_TOSLINK_L), M_IN3_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(IN_TOSLINK_R), M_IN3_REC_R, C_REC_R, 0); /* in4, LineIn */ EFX_ROUTE(NULL, INP(IN_LINE1_L), M_IN4_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(IN_LINE1_R), M_IN4_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(IN_LINE1_L), M_IN4_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(IN_LINE1_R), M_IN4_REC_R, C_REC_R, 0); /* in5, on-card S/PDIF */ EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_L), M_IN5_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_R), M_IN5_REC_R, C_REC_R, 0); /* in6, Line2 on Live!Drive */ EFX_ROUTE(NULL, INP(IN_LINE2_L), M_IN6_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(IN_LINE2_R), M_IN6_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(IN_LINE2_L), M_IN6_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(IN_LINE2_R), M_IN6_REC_R, C_REC_R, 0); if (sc->dbg_level > 0) { /* in7, unknown */ EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0); EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0); EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0); EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0); } /* analog and digital */ EFX_OUTPUT("master_front_l", C_FRONT_L, M_MASTER_FRONT_L, OUT_AC97_L, 100); EFX_OUTPUT("master_front_r", C_FRONT_R, M_MASTER_FRONT_R, OUT_AC97_R, 100); /* S/PDIF */ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_TOSLINK_L); EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_TOSLINK_R); /* Headphones */ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_HEADPHONE_L); EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_HEADPHONE_R); /* rec output to "ADC" */ EFX_OUTPUT("master_rec_l", C_REC_L, M_MASTER_REC_L, OUT_ADC_REC_L, 100); EFX_OUTPUT("master_rec_r", C_REC_R, M_MASTER_REC_R, OUT_ADC_REC_R, 100); if (!(sc->mch_disabled)) { /* * Additional channel volume is controlled by mixer in * emu_dspmixer_set() in -pcm.c */ /* fx2/3 (pcm1) to rear */ EFX_CACHE(C_REAR_L); EFX_CACHE(C_REAR_R); EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100); EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100); EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, OUT_REAR_L, 100); EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, OUT_REAR_R, 100); if (sc->has_51) { /* fx4 (pcm2) to center */ EFX_CACHE(C_CENTER); EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100); EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, OUT_D_CENTER, 100); /* XXX in digital mode (default) this should be muted because this output is shared with digital out */ EFX_SKIP(1, ANALOGMUTE); EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, OUT_A_CENTER); /* fx5 (pcm3) to sub */ EFX_CACHE(C_SUB); EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100); EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, OUT_D_SUB, 100); /* XXX in digital mode (default) this should be muted because this output is shared with digital out */ EFX_SKIP(1, ANALOGMUTE); EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, OUT_A_SUB); } } else { /* SND_EMU10KX_MULTICHANNEL_DISABLED */ EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_REAR_L, OUT_REAR_L, 57); /* 75%*75% */ EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_REAR_R, OUT_REAR_R, 57); /* 75%*75% */ #if 0 /* XXX 5.1 does not work */ if (sc->has_51) { /* (fx0+fx1)/2 to center */ EFX_CACHE(C_CENTER); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_CENTER]), GPR(sc->cache_gpr[C_CENTER]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_L]), &pc); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_CENTER]), GPR(sc->cache_gpr[C_CENTER]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_R]), &pc); EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, OUT_D_CENTER, 100); /* XXX in digital mode (default) this should be muted because this output is shared with digital out */ EFX_SKIP(1, ANALOGMUTE); EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, OUT_A_CENTER); /* (fx0+fx1)/2 to sub */ EFX_CACHE(C_SUB); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_CENTER]), GPR(sc->cache_gpr[C_CENTER]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_L]), &pc); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_CENTER]), GPR(sc->cache_gpr[C_CENTER]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_R]), &pc); /* XXX add lowpass filter here */ EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, OUT_D_SUB, 100); /* XXX in digital mode (default) this should be muted because this output is shared with digital out */ EFX_SKIP(1, ANALOGMUTE); EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, OUT_A_SUB); } #endif } /* !mch_disabled */ if (sc->mch_rec) { /* * MCH RECORDING , hight 16 slots. On 5.1 cards first 4 slots * are used as outputs and already filled with data */ /* * XXX On Live! cards stream does not begin at zero offset. * It can be HW, driver or sound buffering problem. * Use sync substream (offset 0x3E) to let userland find * correct data. */ /* * Substream map (in byte offsets, each substream is 2 bytes): * 0x00..0x1E - outputs * 0x20..0x3E - FX, inputs and sync stream */ /* First 2 channels (offset 0x20,0x22) are empty */ for(i = (sc->has_51 ? 2 : 0); i < 2; i++) EFX_COPY(FX2(i), DSP_CONST(0)); /* PCM Playback monitoring, offset 0x24..0x2A */ for(i = 0; i < 4; i++) EFX_COPY(FX2(i+2), FX(i)); /* Copy of some inputs, offset 0x2C..0x3C */ for(i = 0; i < 9; i++) EFX_COPY(FX2(i+8), INP(i)); /* sync data (0xc0de, offset 0x3E) */ sc->dummy_gpr = emu_rm_gpr_alloc(sc->rm, 1); emumix_set_gpr(sc, sc->dummy_gpr, 0xc0de0000); EFX_COPY(FX2(15), GPR(sc->dummy_gpr)); } /* mch_rec */ } else /* emu10k2 and later */ { EFX_CACHE(C_FRONT_L); EFX_CACHE(C_FRONT_R); EFX_CACHE(C_REC_L); EFX_CACHE(C_REC_R); /* fx0 to front/record, 100%/muted by default */ /* * FRONT_[L|R] is controlled by AC97 emulation in * emu_ac97_[read|write]_emulation in -pcm.c */ EFX_ROUTE(NULL, FX(0), M_FX0_FRONT_L, C_FRONT_L, 100); EFX_ROUTE(NULL, FX(1), M_FX1_FRONT_R, C_FRONT_R, 100); EFX_ROUTE(NULL, FX(0), M_FX0_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, FX(1), M_FX1_REC_R, C_REC_R, 0); /* in0, from AC97 codec output */ EFX_ROUTE(NULL, INP(A_IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 100); EFX_ROUTE(NULL, INP(A_IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 100); EFX_ROUTE(NULL, INP(A_IN_AC97_L), M_IN0_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(A_IN_AC97_R), M_IN0_REC_R, C_REC_R, 0); /* in1, from CD S/PDIF */ EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0); /* in2, optical & coax S/PDIF on AudigyDrive*/ /* XXX Should be muted when GPRSCS valid stream == 0 */ EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_L), M_IN2_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_R), M_IN2_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_L), M_IN2_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_R), M_IN2_REC_R, C_REC_R, 0); if (sc->dbg_level > 0) { /* in3, unknown */ EFX_ROUTE("in3_front_l", INP(0x6), M_IN3_FRONT_L, C_FRONT_L, 0); EFX_ROUTE("in3_front_r", INP(0x7), M_IN3_FRONT_R, C_FRONT_R, 0); EFX_ROUTE("in3_rec_l", INP(0x6), M_IN3_REC_L, C_REC_L, 0); EFX_ROUTE("in3_rec_r", INP(0x7), M_IN3_REC_R, C_REC_R, 0); } /* in4, LineIn 2 on AudigyDrive */ EFX_ROUTE(NULL, INP(A_IN_LINE2_L), M_IN4_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(A_IN_LINE2_R), M_IN4_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(A_IN_LINE2_L), M_IN4_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(A_IN_LINE2_R), M_IN4_REC_R, C_REC_R, 0); /* in5, on-card S/PDIF */ EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_L), M_IN5_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_R), M_IN5_REC_R, C_REC_R, 0); /* in6, AUX2 on AudigyDrive */ EFX_ROUTE(NULL, INP(A_IN_AUX2_L), M_IN6_FRONT_L, C_FRONT_L, 0); EFX_ROUTE(NULL, INP(A_IN_AUX2_R), M_IN6_FRONT_R, C_FRONT_R, 0); EFX_ROUTE(NULL, INP(A_IN_AUX2_L), M_IN6_REC_L, C_REC_L, 0); EFX_ROUTE(NULL, INP(A_IN_AUX2_R), M_IN6_REC_R, C_REC_R, 0); if (sc->dbg_level > 0) { /* in7, unknown */ EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0); EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0); EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0); EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0); } /* front output to headphones and alog and digital *front */ /* volume controlled by AC97 emulation */ EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_FRONT_L, 100); EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_FRONT_R, 100); EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_FRONT_L); EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_FRONT_R); EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_HPHONE_L); EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_HPHONE_R); /* rec output to "ADC" */ /* volume controlled by AC97 emulation */ EFX_OUTPUT(NULL, C_REC_L, M_MASTER_REC_L, A_OUT_ADC_REC_L, 100); EFX_OUTPUT(NULL, C_REC_R, M_MASTER_REC_R, A_OUT_ADC_REC_R, 100); if (!(sc->mch_disabled)) { /* * Additional channel volume is controlled by mixer in * emu_dspmixer_set() in -pcm.c */ /* fx2/3 (pcm1) to rear */ EFX_CACHE(C_REAR_L); EFX_CACHE(C_REAR_R); EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100); EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100); EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, A_OUT_A_REAR_L, 100); EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, A_OUT_A_REAR_R, 100); EFX_OUTPUTD(C_REAR_L, M_MASTER_REAR_L, A_OUT_D_REAR_L); EFX_OUTPUTD(C_REAR_R, M_MASTER_REAR_R, A_OUT_D_REAR_R); /* fx4 (pcm2) to center */ EFX_CACHE(C_CENTER); EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100); EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100); #if 0 /* * XXX in digital mode (default) this should be muted * because this output is shared with digital out */ EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER); #endif /* fx5 (pcm3) to sub */ EFX_CACHE(C_SUB); EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100); EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100); #if 0 /* * XXX in digital mode (default) this should be muted * because this output is shared with digital out */ EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB); #endif if (sc->has_71) { /* XXX this will broke headphones on AudigyDrive */ /* fx6/7 (pcm4) to side */ EFX_CACHE(C_SIDE_L); EFX_CACHE(C_SIDE_R); EFX_ROUTE(NULL, FX(6), M_FX6_SIDE_L, C_SIDE_L, 100); EFX_ROUTE(NULL, FX(7), M_FX7_SIDE_R, C_SIDE_R, 100); EFX_OUTPUT(NULL, C_SIDE_L, M_MASTER_SIDE_L, A_OUT_A_SIDE_L, 100); EFX_OUTPUT(NULL, C_SIDE_R, M_MASTER_SIDE_R, A_OUT_A_SIDE_R, 100); EFX_OUTPUTD(C_SIDE_L, M_MASTER_SIDE_L, A_OUT_D_SIDE_L); EFX_OUTPUTD(C_SIDE_R, M_MASTER_SIDE_R, A_OUT_D_SIDE_R); } } else { /* mch_disabled */ EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_REAR_L); EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_REAR_R); EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_REAR_L); EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_REAR_R); if (sc->has_51) { /* (fx0+fx1)/2 to center */ EFX_CACHE(C_CENTER); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_CENTER]), GPR(sc->cache_gpr[C_CENTER]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_L]), &pc); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_CENTER]), GPR(sc->cache_gpr[C_CENTER]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_R]), &pc); EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100); /* XXX in digital mode (default) this should be muted because this output is shared with digital out */ EFX_SKIP(1, ANALOGMUTE); EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER); /* (fx0+fx1)/2 to sub */ EFX_CACHE(C_SUB); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_SUB]), GPR(sc->cache_gpr[C_SUB]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_L]), &pc); emu_addefxop(sc, MACS, GPR(sc->cache_gpr[C_SUB]), GPR(sc->cache_gpr[C_SUB]), DSP_CONST(0xd), /* = 1/2 */ GPR(sc->cache_gpr[C_FRONT_R]), &pc); /* XXX add lowpass filter here */ EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100); /* XXX in digital mode (default) this should be muted because this output is shared with digital out */ EFX_SKIP(1, ANALOGMUTE); EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB); } } /* mch_disabled */ if (sc->mch_rec) { /* MCH RECORDING, high 32 slots */ /* * Stream map (in byte offsets): * 0x00..0x3E - outputs * 0x40..0x7E - FX, inputs * each substream is 2 bytes. */ /* * XXX Audigy 2 Value cards (and, possibly, * Audigy 4) write some unknown data in place of * some outputs (offsets 0x20..0x3F) and one * input (offset 0x7E). */ /* PCM Playback monitoring, offsets 0x40..0x5E */ for(i = 0; i < 16; i++) EFX_COPY(FX2(i), FX(i)); /* Copy of all inputs, offsets 0x60..0x7E */ for(i = 0; i < 16; i++) EFX_COPY(FX2(i+16), INP(i)); #if 0 /* XXX Audigy seems to work correct and does not need this */ /* sync data (0xc0de), offset 0x7E */ sc->dummy_gpr = emu_rm_gpr_alloc(sc->rm, 1); emumix_set_gpr(sc, sc->dummy_gpr, 0xc0de0000); EFX_COPY(FX2(31), GPR(sc->dummy_gpr)); #endif } /* mch_rec */ } sc->routing_code_end = pc; /* start DSP */ if (sc->is_emu10k1) { emu_wrptr(sc, 0, EMU_DBG, 0); } else { emu_wrptr(sc, 0, EMU_A_DBG, 0); } } /* /dev/em10kx */ static d_open_t emu10kx_open; static d_close_t emu10kx_close; static d_read_t emu10kx_read; static struct cdevsw emu10kx_cdevsw = { .d_open = emu10kx_open, .d_close = emu10kx_close, .d_read = emu10kx_read, .d_name = "emu10kx", .d_version = D_VERSION, }; static int emu10kx_open(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) { int error; struct emu_sc_info *sc; sc = i_dev->si_drv1; mtx_lock(&sc->emu10kx_lock); if (sc->emu10kx_isopen) { mtx_unlock(&sc->emu10kx_lock); return (EBUSY); } sc->emu10kx_isopen = 1; mtx_unlock(&sc->emu10kx_lock); if (sbuf_new(&sc->emu10kx_sbuf, NULL, 4096, 0) == NULL) { error = ENXIO; goto out; } sc->emu10kx_bufptr = 0; error = (emu10kx_prepare(sc, &sc->emu10kx_sbuf) > 0) ? 0 : ENOMEM; out: if (error) { mtx_lock(&sc->emu10kx_lock); sc->emu10kx_isopen = 0; mtx_unlock(&sc->emu10kx_lock); } return (error); } static int emu10kx_close(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) { struct emu_sc_info *sc; sc = i_dev->si_drv1; mtx_lock(&sc->emu10kx_lock); if (!(sc->emu10kx_isopen)) { mtx_unlock(&sc->emu10kx_lock); return (EBADF); } sbuf_delete(&sc->emu10kx_sbuf); sc->emu10kx_isopen = 0; mtx_unlock(&sc->emu10kx_lock); return (0); } static int emu10kx_read(struct cdev *i_dev, struct uio *buf, int flag __unused) { int l, err; struct emu_sc_info *sc; sc = i_dev->si_drv1; mtx_lock(&sc->emu10kx_lock); if (!(sc->emu10kx_isopen)) { mtx_unlock(&sc->emu10kx_lock); return (EBADF); } mtx_unlock(&sc->emu10kx_lock); l = min(buf->uio_resid, sbuf_len(&sc->emu10kx_sbuf) - sc->emu10kx_bufptr); err = (l > 0) ? uiomove(sbuf_data(&sc->emu10kx_sbuf) + sc->emu10kx_bufptr, l, buf) : 0; sc->emu10kx_bufptr += l; return (err); } static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s) { int i; sbuf_printf(s, "FreeBSD EMU10Kx Audio Driver\n"); sbuf_printf(s, "\nHardware resource usage:\n"); sbuf_printf(s, "DSP General Purpose Registers: %d used, %d total\n", sc->rm->num_used, sc->rm->num_gprs); sbuf_printf(s, "DSP Instruction Registers: %d used, %d total\n", sc->routing_code_end, sc->code_size); sbuf_printf(s, "Card supports"); if (sc->has_ac97) { sbuf_printf(s, " AC97 codec"); } else { sbuf_printf(s, " NO AC97 codec"); } if (sc->has_51) { if (sc->has_71) sbuf_printf(s, " and 7.1 output"); else sbuf_printf(s, " and 5.1 output"); } if (sc->is_emu10k1) sbuf_printf(s, ", SBLive! DSP code"); if (sc->is_emu10k2) sbuf_printf(s, ", Audigy DSP code"); if (sc->is_ca0102) sbuf_printf(s, ", Audigy DSP code with Audigy2 hacks"); if (sc->is_ca0108) sbuf_printf(s, ", Audigy DSP code with Audigy2Value hacks"); sbuf_printf(s, "\n"); if (sc->broken_digital) sbuf_printf(s, "Digital mode unsupported\n"); sbuf_printf(s, "\nInstalled devices:\n"); for (i = 0; i < RT_COUNT; i++) if (sc->pcm[i] != NULL) if (device_is_attached(sc->pcm[i])) { sbuf_printf(s, "%s on %s\n", device_get_desc(sc->pcm[i]), device_get_nameunit(sc->pcm[i])); } if (sc->midi[0] != NULL) if (device_is_attached(sc->midi[0])) { sbuf_printf(s, "EMU10Kx MIDI Interface\n"); sbuf_printf(s, "\tOn-card connector on %s\n", device_get_nameunit(sc->midi[0])); } if (sc->midi[1] != NULL) if (device_is_attached(sc->midi[1])) { sbuf_printf(s, "\tOn-Drive connector on %s\n", device_get_nameunit(sc->midi[1])); } if (sc->midi[0] != NULL) if (device_is_attached(sc->midi[0])) { sbuf_printf(s, "\tIR reciever MIDI events %s\n", sc->enable_ir ? "enabled" : "disabled"); } sbuf_printf(s, "Card is in %s mode\n", (sc->mode == MODE_ANALOG) ? "analog" : "digital"); sbuf_finish(s); return (sbuf_len(s)); } /* INIT & UNINIT */ static int emu10kx_dev_init(struct emu_sc_info *sc) { int unit; mtx_init(&sc->emu10kx_lock, device_get_nameunit(sc->dev), "kxdevlock", 0); unit = device_get_unit(sc->dev); sc->cdev = make_dev(&emu10kx_cdevsw, PCMMINOR(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit); if (sc->cdev != NULL) { sc->cdev->si_drv1 = sc; return (0); } return (ENXIO); } static int emu10kx_dev_uninit(struct emu_sc_info *sc) { mtx_lock(&sc->emu10kx_lock); if (sc->emu10kx_isopen) { mtx_unlock(&sc->emu10kx_lock); return (EBUSY); } if (sc->cdev) destroy_dev(sc->cdev); sc->cdev = 0; mtx_destroy(&sc->emu10kx_lock); return (0); } /* resource manager */ int emu_rm_init(struct emu_sc_info *sc) { int i; int maxcount; struct emu_rm *rm; rm = malloc(sizeof(struct emu_rm), M_DEVBUF, M_NOWAIT | M_ZERO); if (rm == NULL) { return (ENOMEM); } sc->rm = rm; rm->card = sc; maxcount = sc->num_gprs; rm->num_used = 0; mtx_init(&(rm->gpr_lock), device_get_nameunit(sc->dev), "gpr alloc", MTX_DEF); rm->num_gprs = (maxcount < EMU_MAX_GPR ? maxcount : EMU_MAX_GPR); for (i = 0; i < rm->num_gprs; i++) rm->allocmap[i] = 0; /* pre-allocate gpr[0] */ rm->allocmap[0] = 1; rm->last_free_gpr = 1; return (0); } int emu_rm_uninit(struct emu_sc_info *sc) { int i; if (sc->dbg_level > 1) { mtx_lock(&(sc->rm->gpr_lock)); for (i = 1; i < sc->rm->last_free_gpr; i++) if (sc->rm->allocmap[i] > 0) device_printf(sc->dev, "rm: gpr %d not free before uninit\n", i); mtx_unlock(&(sc->rm->gpr_lock)); } mtx_destroy(&(sc->rm->gpr_lock)); free(sc->rm, M_DEVBUF); return (0); } static int emu_rm_gpr_alloc(struct emu_rm *rm, int count) { int i, j; int allocated_gpr; allocated_gpr = rm->num_gprs; /* try fast way first */ mtx_lock(&(rm->gpr_lock)); if (rm->last_free_gpr + count <= rm->num_gprs) { allocated_gpr = rm->last_free_gpr; rm->last_free_gpr += count; rm->allocmap[allocated_gpr] = count; for (i = 1; i < count; i++) rm->allocmap[allocated_gpr + i] = -(count - i); } else { /* longer */ i = 0; allocated_gpr = rm->num_gprs; while (i < rm->last_free_gpr - count) { if (rm->allocmap[i] > 0) { i += rm->allocmap[i]; } else { allocated_gpr = i; for (j = 1; j < count; j++) { if (rm->allocmap[i + j] != 0) allocated_gpr = rm->num_gprs; } if (allocated_gpr == i) break; } } if (allocated_gpr + count < rm->last_free_gpr) { rm->allocmap[allocated_gpr] = count; for (i = 1; i < count; i++) rm->allocmap[allocated_gpr + i] = -(count - i); } } if (allocated_gpr == rm->num_gprs) allocated_gpr = (-1); if (allocated_gpr >= 0) rm->num_used += count; mtx_unlock(&(rm->gpr_lock)); return (allocated_gpr); } /* mixer */ void emumix_set_mode(struct emu_sc_info *sc, int mode) { uint32_t a_iocfg; uint32_t hcfg; uint32_t tmp; switch (mode) { case MODE_DIGITAL: /* FALLTHROUGH */ case MODE_ANALOG: break; default: return; } hcfg = EMU_HCFG_AUDIOENABLE | EMU_HCFG_AUTOMUTE; a_iocfg = 0; if (sc->rev >= 6) hcfg |= EMU_HCFG_JOYENABLE; if (sc->is_emu10k1) hcfg |= EMU_HCFG_LOCKTANKCACHE_MASK; else hcfg |= EMU_HCFG_CODECFMT_I2S | EMU_HCFG_JOYENABLE; if (mode == MODE_DIGITAL) { if (sc->broken_digital) { device_printf(sc->dev, "Digital mode is reported as broken on this card.\n"); } a_iocfg |= EMU_A_IOCFG_GPOUT1; hcfg |= EMU_HCFG_GPOUT0; } if (mode == MODE_ANALOG) emumix_set_spdif_mode(sc, SPDIF_MODE_PCM); if (sc->is_emu10k2) a_iocfg |= 0x80; /* XXX */ if ((sc->is_ca0102) || (sc->is_ca0108)) /* * Setting EMU_A_IOCFG_DISABLE_ANALOG will do opposite things * on diffrerent cards. * "don't disable analog outs" on Audigy 2 (ca0102/ca0108) * "disable analog outs" on Audigy (emu10k2) */ a_iocfg |= EMU_A_IOCFG_DISABLE_ANALOG; if (sc->is_ca0108) a_iocfg |= 0x20; /* XXX */ /* Mute analog center & subwoofer before mode change */ if (mode == MODE_DIGITAL) emumix_set_gpr(sc, sc->mute_gpr[ANALOGMUTE], 1); emu_wr(sc, EMU_HCFG, hcfg, 4); if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { tmp = emu_rd(sc, EMU_A_IOCFG, 2); tmp = a_iocfg; emu_wr(sc, EMU_A_IOCFG, tmp, 2); } /* Unmute if we have changed mode to analog. */ if (mode == MODE_ANALOG) emumix_set_gpr(sc, sc->mute_gpr[ANALOGMUTE], 0); sc->mode = mode; } void emumix_set_spdif_mode(struct emu_sc_info *sc, int mode) { uint32_t spcs; switch (mode) { case SPDIF_MODE_PCM: break; case SPDIF_MODE_AC3: device_printf(sc->dev, "AC3 mode does not work and disabled\n"); return; default: return; } spcs = EMU_SPCS_CLKACCY_1000PPM | EMU_SPCS_SAMPLERATE_48 | EMU_SPCS_CHANNELNUM_LEFT | EMU_SPCS_SOURCENUM_UNSPEC | EMU_SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 | EMU_SPCS_EMPHASIS_NONE | EMU_SPCS_COPYRIGHT; mode = SPDIF_MODE_PCM; emu_wrptr(sc, 0, EMU_SPCS0, spcs); emu_wrptr(sc, 0, EMU_SPCS1, spcs); emu_wrptr(sc, 0, EMU_SPCS2, spcs); } #define L2L_POINTS 10 static int l2l_df[L2L_POINTS] = { 0x572C5CA, /* 100..90 */ 0x3211625, /* 90..80 */ 0x1CC1A76, /* 80..70 */ 0x108428F, /* 70..60 */ 0x097C70A, /* 60..50 */ 0x0572C5C, /* 50..40 */ 0x0321162, /* 40..30 */ 0x01CC1A7, /* 30..20 */ 0x0108428, /* 20..10 */ 0x016493D /* 10..0 */ }; static int l2l_f[L2L_POINTS] = { 0x4984461A, /* 90 */ 0x2A3968A7, /* 80 */ 0x18406003, /* 70 */ 0x0DEDC66D, /* 60 */ 0x07FFFFFF, /* 50 */ 0x04984461, /* 40 */ 0x02A3968A, /* 30 */ 0x01840600, /* 20 */ 0x00DEDC66, /* 10 */ 0x00000000 /* 0 */ }; static int log2lin(int log_t) { int lin_t; int idx, lin; if (log_t <= 0) { lin_t = 0x00000000; return (lin_t); } if (log_t >= 100) { lin_t = 0x7fffffff; return (lin_t); } idx = (L2L_POINTS - 1) - log_t / (L2L_POINTS); lin = log_t % (L2L_POINTS); lin_t = l2l_df[idx] * lin + l2l_f[idx]; return (lin_t); } void emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol) { vol = log2lin(vol); emumix_set_gpr(sc, gpr, vol); } void emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val) { if (sc->dbg_level > 1) if (gpr == 0) { device_printf(sc->dev, "Zero gpr write access\n"); #ifdef KDB kdb_backtrace(); #endif return; } emu_wrptr(sc, 0, GPR(gpr), val); } void emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume) { RANGE(volume, 0, 100); if (mixer_idx < NUM_MIXERS) { sc->mixer_volcache[mixer_idx] = volume; emumix_set_fxvol(sc, sc->mixer_gpr[mixer_idx], volume); } } int emumix_get_volume(struct emu_sc_info *sc, int mixer_idx) { if ((mixer_idx < NUM_MIXERS) && (mixer_idx >= 0)) return (sc->mixer_volcache[mixer_idx]); return (-1); } /* Init CardBus part */ static int emu_cardbus_init(struct emu_sc_info *sc) { /* * XXX May not need this if we have EMU_IPR3 handler. * Is it a real init calls, or EMU_IPR3 interrupt acknowledgments? * Looks much like "(data << 16) | register". */ emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0000); emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0001); emu_wr_cbptr(sc, (0x00d0 << 16) | 0x005f); emu_wr_cbptr(sc, (0x00d0 << 16) | 0x007f); emu_wr_cbptr(sc, (0x0090 << 16) | 0x007f); return (0); } /* Probe and attach the card */ static int emu_init(struct emu_sc_info *sc) { uint32_t ch, tmp; uint32_t spdif_sr; uint32_t ac97slot; int def_mode; int i; /* disable audio and lock cache */ emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4); /* reset recording buffers */ emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_MICBA, 0); emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_FXBA, 0); emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_ADCBA, 0); /* disable channel interrupt */ emu_wr(sc, EMU_INTE, EMU_INTE_INTERTIMERENB | EMU_INTE_SAMPLERATER | EMU_INTE_PCIERRENABLE, 4); emu_wrptr(sc, 0, EMU_CLIEL, 0); emu_wrptr(sc, 0, EMU_CLIEH, 0); emu_wrptr(sc, 0, EMU_SOLEL, 0); emu_wrptr(sc, 0, EMU_SOLEH, 0); /* disable P16V and S/PDIF interrupts */ if ((sc->is_ca0102) || (sc->is_ca0108)) emu_wr(sc, EMU_INTE2, 0, 4); if (sc->is_ca0102) emu_wr(sc, EMU_INTE3, 0, 4); /* init phys inputs and outputs */ ac97slot = 0; if (sc->has_51) ac97slot = EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE; if (sc->has_71) ac97slot = EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE | EMU_AC97SLOT_REAR_LEFT | EMU_AC97SLOT_REAR_RIGHT; if (sc->is_emu10k2) ac97slot |= 0x40; emu_wrptr(sc, 0, EMU_AC97SLOT, ac97slot); if (sc->is_emu10k2) /* XXX for later cards? */ emu_wrptr(sc, 0, EMU_SPBYPASS, 0xf00); /* What will happen if * we write 1 here? */ if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(sc->dev), /* alignment */ 2, /* boundary */ 0, /* lowaddr */ (1U << 31) - 1, /* can only access 0-2gb */ /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ EMU_MAX_BUFSZ, /* nsegments */ 1, /* maxsegz */ 0x3ffff, /* flags */ 0, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &Giant, &(sc->mem.dmat)) != 0) { device_printf(sc->dev, "unable to create dma tag\n"); bus_dma_tag_destroy(sc->mem.dmat); return (ENOMEM); } sc->mem.card = sc; SLIST_INIT(&sc->mem.blocks); sc->mem.ptb_pages = emu_malloc(&sc->mem, EMU_MAXPAGES * sizeof(uint32_t), &sc->mem.ptb_pages_addr, &sc->mem.ptb_map); if (sc->mem.ptb_pages == NULL) return (ENOMEM); sc->mem.silent_page = emu_malloc(&sc->mem, EMUPAGESIZE, &sc->mem.silent_page_addr, &sc->mem.silent_map); if (sc->mem.silent_page == NULL) { emu_free(&sc->mem, sc->mem.ptb_pages, sc->mem.ptb_map); return (ENOMEM); } /* Clear page with silence & setup all pointers to this page */ bzero(sc->mem.silent_page, EMUPAGESIZE); tmp = (uint32_t) (sc->mem.silent_page_addr) << 1; for (i = 0; i < EMU_MAXPAGES; i++) sc->mem.ptb_pages[i] = tmp | i; for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_MAPA, tmp | EMU_CHAN_MAP_PTI_MASK); emu_wrptr(sc, ch, EMU_CHAN_MAPB, tmp | EMU_CHAN_MAP_PTI_MASK); } emu_wrptr(sc, 0, EMU_PTB, (sc->mem.ptb_pages_addr)); emu_wrptr(sc, 0, EMU_TCB, 0); /* taken from original driver */ emu_wrptr(sc, 0, EMU_TCBS, 0); /* taken from original driver */ /* init envelope engine */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, 0); emu_wrptr(sc, ch, EMU_CHAN_IP, 0); emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0); emu_wrptr(sc, ch, EMU_CHAN_CPF, 0); emu_wrptr(sc, ch, EMU_CHAN_CCR, 0); emu_wrptr(sc, ch, EMU_CHAN_PSST, 0); emu_wrptr(sc, ch, EMU_CHAN_DSL, 0x10); emu_wrptr(sc, ch, EMU_CHAN_CCCA, 0); emu_wrptr(sc, ch, EMU_CHAN_Z1, 0); emu_wrptr(sc, ch, EMU_CHAN_Z2, 0); emu_wrptr(sc, ch, EMU_CHAN_FXRT, 0xd01c0000); emu_wrptr(sc, ch, EMU_CHAN_ATKHLDM, 0); emu_wrptr(sc, ch, EMU_CHAN_DCYSUSM, 0); emu_wrptr(sc, ch, EMU_CHAN_IFATN, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_PEFE, 0); emu_wrptr(sc, ch, EMU_CHAN_FMMOD, 0); emu_wrptr(sc, ch, EMU_CHAN_TREMFRQ, 24); /* 1 Hz */ emu_wrptr(sc, ch, EMU_CHAN_FM2FRQ2, 24); /* 1 Hz */ emu_wrptr(sc, ch, EMU_CHAN_TEMPENV, 0); /*** these are last so OFF prevents writing ***/ emu_wrptr(sc, ch, EMU_CHAN_LFOVAL2, 0); emu_wrptr(sc, ch, EMU_CHAN_LFOVAL1, 0); emu_wrptr(sc, ch, EMU_CHAN_ATKHLDV, 0); emu_wrptr(sc, ch, EMU_CHAN_ENVVOL, 0); emu_wrptr(sc, ch, EMU_CHAN_ENVVAL, 0); if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { emu_wrptr(sc, ch, 0x4c, 0x0); emu_wrptr(sc, ch, 0x4d, 0x0); emu_wrptr(sc, ch, 0x4e, 0x0); emu_wrptr(sc, ch, 0x4f, 0x0); emu_wrptr(sc, ch, EMU_A_CHAN_FXRT1, 0x3f3f3f3f); emu_wrptr(sc, ch, EMU_A_CHAN_FXRT2, 0x3f3f3f3f); emu_wrptr(sc, ch, EMU_A_CHAN_SENDAMOUNTS, 0x0); } } emumix_set_spdif_mode(sc, SPDIF_MODE_PCM); if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) emu_wrptr(sc, 0, EMU_A_SPDIF_SAMPLERATE, EMU_A_SPDIF_48000); /* * CAxxxx cards needs additional setup: * 1. Set I2S capture sample rate to 96000 * 2. Disable P16v / P17v proceesing * 3. Allow EMU10K DSP inputs */ if ((sc->is_ca0102) || (sc->is_ca0108)) { spdif_sr = emu_rdptr(sc, 0, EMU_A_SPDIF_SAMPLERATE); spdif_sr &= 0xfffff1ff; spdif_sr |= EMU_A_I2S_CAPTURE_96000; emu_wrptr(sc, 0, EMU_A_SPDIF_SAMPLERATE, spdif_sr); /* Disable P16v processing */ emu_wr_p16vptr(sc, 0, EMU_A2_SRCSel, 0x14); /* Setup P16v/P17v sound routing */ if (sc->is_ca0102) emu_wr_p16vptr(sc, 0, EMU_A2_SRCMULTI_ENABLE, 0xFF00FF00); else { emu_wr_p16vptr(sc, 0, EMU_A2_MIXER_I2S_ENABLE, 0xFF000000); emu_wr_p16vptr(sc, 0, EMU_A2_MIXER_SPDIF_ENABLE, 0xFF000000); tmp = emu_rd(sc, EMU_A_IOCFG, 2); emu_wr(sc, EMU_A_IOCFG, tmp & ~0x8, 2); } } emu_initefx(sc); def_mode = MODE_ANALOG; if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) def_mode = MODE_DIGITAL; if (((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) && (sc->broken_digital)) { device_printf(sc->dev, "Audigy card initialized in analog mode.\n"); def_mode = MODE_ANALOG; } emumix_set_mode(sc, def_mode); if (bootverbose) { tmp = emu_rd(sc, EMU_HCFG, 4); device_printf(sc->dev, "Card Configuration ( 0x%08x )\n", tmp); device_printf(sc->dev, "Card Configuration ( & 0xff000000 ) : %s%s%s%s%s%s%s%s\n", (tmp & 0x80000000 ? "[Legacy MPIC] " : ""), (tmp & 0x40000000 ? "[0x40] " : ""), (tmp & 0x20000000 ? "[0x20] " : ""), (tmp & 0x10000000 ? "[0x10] " : ""), (tmp & 0x08000000 ? "[0x08] " : ""), (tmp & 0x04000000 ? "[0x04] " : ""), (tmp & 0x02000000 ? "[0x02] " : ""), (tmp & 0x01000000 ? "[0x01]" : " ")); device_printf(sc->dev, "Card Configuration ( & 0x00ff0000 ) : %s%s%s%s%s%s%s%s\n", (tmp & 0x00800000 ? "[0x80] " : ""), (tmp & 0x00400000 ? "[0x40] " : ""), (tmp & 0x00200000 ? "[Legacy INT] " : ""), (tmp & 0x00100000 ? "[0x10] " : ""), (tmp & 0x00080000 ? "[0x08] " : ""), (tmp & 0x00040000 ? "[Codec4] " : ""), (tmp & 0x00020000 ? "[Codec2] " : ""), (tmp & 0x00010000 ? "[I2S Codec]" : " ")); device_printf(sc->dev, "Card Configuration ( & 0x0000ff00 ) : %s%s%s%s%s%s%s%s\n", (tmp & 0x00008000 ? "[0x80] " : ""), (tmp & 0x00004000 ? "[GPINPUT0] " : ""), (tmp & 0x00002000 ? "[GPINPUT1] " : ""), (tmp & 0x00001000 ? "[GPOUT0] " : ""), (tmp & 0x00000800 ? "[GPOUT1] " : ""), (tmp & 0x00000400 ? "[GPOUT2] " : ""), (tmp & 0x00000200 ? "[Joystick] " : ""), (tmp & 0x00000100 ? "[0x01]" : " ")); device_printf(sc->dev, "Card Configuration ( & 0x000000ff ) : %s%s%s%s%s%s%s%s\n", (tmp & 0x00000080 ? "[0x80] " : ""), (tmp & 0x00000040 ? "[0x40] " : ""), (tmp & 0x00000020 ? "[0x20] " : ""), (tmp & 0x00000010 ? "[AUTOMUTE] " : ""), (tmp & 0x00000008 ? "[LOCKSOUNDCACHE] " : ""), (tmp & 0x00000004 ? "[LOCKTANKCACHE] " : ""), (tmp & 0x00000002 ? "[MUTEBUTTONENABLE] " : ""), (tmp & 0x00000001 ? "[AUDIOENABLE]" : " ")); if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { tmp = emu_rd(sc, EMU_A_IOCFG, 2); device_printf(sc->dev, "Audigy Card Configuration ( 0x%04x )\n", tmp); device_printf(sc->dev, "Audigy Card Configuration ( & 0xff00 )"); printf(" : %s%s%s%s%s%s%s%s\n", (tmp & 0x8000 ? "[Rear Speakers] " : ""), (tmp & 0x4000 ? "[Front Speakers] " : ""), (tmp & 0x2000 ? "[0x20] " : ""), (tmp & 0x1000 ? "[0x10] " : ""), (tmp & 0x0800 ? "[0x08] " : ""), (tmp & 0x0400 ? "[0x04] " : ""), (tmp & 0x0200 ? "[0x02] " : ""), (tmp & 0x0100 ? "[AudigyDrive Phones]" : " ")); device_printf(sc->dev, "Audigy Card Configuration ( & 0x00ff )"); printf(" : %s%s%s%s%s%s%s%s\n", (tmp & 0x0080 ? "[0x80] " : ""), (tmp & 0x0040 ? "[Mute AnalogOut] " : ""), (tmp & 0x0020 ? "[0x20] " : ""), (tmp & 0x0010 ? "[0x10] " : ""), (tmp & 0x0008 ? "[0x08] " : ""), (tmp & 0x0004 ? "[GPOUT0] " : ""), (tmp & 0x0002 ? "[GPOUT1] " : ""), (tmp & 0x0001 ? "[GPOUT2]" : " ")); } /* is_emu10k2 or ca* */ } /* bootverbose */ return (0); } static int emu_uninit(struct emu_sc_info *sc) { uint32_t ch; struct emu_memblk *blk; emu_wr(sc, EMU_INTE, 0, 4); for (ch = 0; ch < NUM_G; ch++) emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, 0); for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0); emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0); emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0); emu_wrptr(sc, ch, EMU_CHAN_CPF, 0); } /* disable audio and lock cache */ emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4); emu_wrptr(sc, 0, EMU_PTB, 0); /* reset recording buffers */ emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_MICBA, 0); emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_FXBA, 0); emu_wrptr(sc, 0, EMU_FXWC, 0); emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_ADCBA, 0); emu_wrptr(sc, 0, EMU_TCB, 0); emu_wrptr(sc, 0, EMU_TCBS, 0); /* disable channel interrupt */ emu_wrptr(sc, 0, EMU_CLIEL, 0); emu_wrptr(sc, 0, EMU_CLIEH, 0); emu_wrptr(sc, 0, EMU_SOLEL, 0); emu_wrptr(sc, 0, EMU_SOLEH, 0); if (!SLIST_EMPTY(&sc->mem.blocks)) device_printf(sc->dev, "warning: memblock list not empty\n"); SLIST_FOREACH(blk, &sc->mem.blocks, link) if (blk != NULL) device_printf(sc->dev, "lost %d for %s\n", blk->pte_size, blk->owner); emu_free(&sc->mem, sc->mem.ptb_pages, sc->mem.ptb_map); emu_free(&sc->mem, sc->mem.silent_page, sc->mem.silent_map); return (0); } static int emu_read_ivar(device_t bus, device_t dev, int ivar_index, uintptr_t * result) { struct sndcard_func *func = device_get_ivars(dev); struct emu_sc_info *sc = device_get_softc(bus); if (func==NULL) return (ENOMEM); if (sc == NULL) return (ENOMEM); switch (ivar_index) { case EMU_VAR_FUNC: *result = func->func; break; case EMU_VAR_ROUTE: if (func->varinfo == NULL) return (ENOMEM); *result = ((struct emu_pcminfo *)func->varinfo)->route; break; case EMU_VAR_ISEMU10K1: *result = sc->is_emu10k1; break; case EMU_VAR_MCH_DISABLED: *result = sc->mch_disabled; break; case EMU_VAR_MCH_REC: *result = sc->mch_rec; break; default: return (ENOENT); } return (0); } static int emu_write_ivar(device_t bus __unused, device_t dev __unused, int ivar_index, uintptr_t value __unused) { switch (ivar_index) { case 0: return (EINVAL); default: return (ENOENT); } } static int emu_pci_probe(device_t dev) { struct sbuf *s; unsigned int thiscard = 0; uint16_t vendor; vendor = pci_read_config(dev, PCIR_DEVVENDOR, /* bytes */ 2); if (vendor != 0x1102) return (ENXIO); /* Not Creative */ thiscard = emu_getcard(dev); if (thiscard == 0) return (ENXIO); s = sbuf_new(NULL, NULL, 4096, 0); if (s == NULL) return (ENOMEM); sbuf_printf(s, "Creative %s [%s]", emu_cards[thiscard].desc, emu_cards[thiscard].SBcode); sbuf_finish(s); device_set_desc_copy(dev, sbuf_data(s)); sbuf_delete(s); return (BUS_PROBE_DEFAULT); } static int emu_pci_attach(device_t dev) { struct sndcard_func *func; struct emu_sc_info *sc; struct emu_pcminfo *pcminfo; #if 0 struct emu_midiinfo *midiinfo; #endif int i; int device_flags; char status[255]; int error = ENXIO; int unit; sc = device_get_softc(dev); unit = device_get_unit(dev); /* Get configuration */ sc->ctx = device_get_sysctl_ctx(dev); if (sc->ctx == NULL) goto bad; sc->root = device_get_sysctl_tree(dev); if (sc->root == NULL) goto bad; if (resource_int_value("emu10kx", unit, "multichannel_disabled", &(sc->mch_disabled))) RANGE(sc->mch_disabled, 0, 1); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "multichannel_disabled", CTLFLAG_RD, &(sc->mch_disabled), 0, "Multichannel playback setting"); if (resource_int_value("emu10kx", unit, "multichannel_recording", &(sc->mch_rec))) RANGE(sc->mch_rec, 0, 1); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "multichannel_recording", CTLFLAG_RD, &(sc->mch_rec), 0, "Multichannel recording setting"); if (resource_int_value("emu10kx", unit, "debug", &(sc->dbg_level))) RANGE(sc->mch_rec, 0, 2); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug", CTLFLAG_RW, &(sc->dbg_level), 0, "Debug level"); /* Fill in the softc. */ mtx_init(&sc->lock, device_get_nameunit(dev), "bridge conf", MTX_DEF); mtx_init(&sc->rw, device_get_nameunit(dev), "exclusive io", MTX_DEF); sc->dev = dev; sc->type = pci_get_devid(dev); sc->rev = pci_get_revid(dev); sc->enable_ir = 0; sc->has_ac97 = 0; sc->has_51 = 0; sc->has_71 = 0; sc->broken_digital = 0; sc->is_emu10k1 = 0; sc->is_emu10k2 = 0; sc->is_ca0102 = 0; sc->is_ca0108 = 0; sc->is_cardbus = 0; device_flags = emu_cards[emu_getcard(dev)].flags; if (device_flags & HAS_51) sc->has_51 = 1; if (device_flags & HAS_71) { sc->has_51 = 1; sc->has_71 = 1; } if (device_flags & IS_EMU10K1) sc->is_emu10k1 = 1; if (device_flags & IS_EMU10K2) sc->is_emu10k2 = 1; if (device_flags & IS_CA0102) sc->is_ca0102 = 1; if (device_flags & IS_CA0108) sc->is_ca0108 = 1; if ((sc->is_emu10k2) && (sc->rev == 4)) { sc->is_emu10k2 = 0; sc->is_ca0102 = 1; /* for unknown Audigy 2 cards */ } if ((sc->is_ca0102 == 1) || (sc->is_ca0108 == 1)) if (device_flags & IS_CARDBUS) sc->is_cardbus = 1; if ((sc->is_emu10k1 + sc->is_emu10k2 + sc->is_ca0102 + sc->is_ca0108) != 1) { device_printf(sc->dev, "Unable to detect HW chipset\n"); goto bad; } if (device_flags & BROKEN_DIGITAL) sc->broken_digital = 1; if (device_flags & HAS_AC97) sc->has_ac97 = 1; sc->opcode_shift = 0; if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { sc->opcode_shift = 24; sc->high_operand_shift = 12; /* DSP map */ /* sc->fx_base = 0x0 */ sc->input_base = 0x40; /* sc->p16vinput_base = 0x50; */ sc->output_base = 0x60; sc->efxc_base = 0x80; /* sc->output32h_base = 0xa0; */ /* sc->output32l_base = 0xb0; */ sc->dsp_zero = 0xc0; /* 0xe0...0x100 are unknown */ /* sc->tram_base = 0x200 */ /* sc->tram_addr_base = 0x300 */ sc->gpr_base = EMU_A_FXGPREGBASE; sc->num_gprs = 0x200; sc->code_base = EMU_A_MICROCODEBASE; sc->code_size = 0x800 / 2; /* 0x600-0xdff, 2048 words, * 1024 instructions */ sc->mchannel_fx = 8; sc->num_fxbuses = 16; sc->num_inputs = 8; sc->num_outputs = 16; sc->address_mask = EMU_A_PTR_ADDR_MASK; } if (sc->is_emu10k1) { sc->has_51 = 0; /* We don't support 5.1 sound on SB Live! 5.1 */ sc->opcode_shift = 20; sc->high_operand_shift = 10; sc->code_base = EMU_MICROCODEBASE; sc->code_size = 0x400 / 2; /* 0x400-0x7ff, 1024 words, * 512 instructions */ sc->gpr_base = EMU_FXGPREGBASE; sc->num_gprs = 0x100; sc->input_base = 0x10; sc->output_base = 0x20; /* * XXX 5.1 Analog outputs are inside efxc address space! * They use output+0x11/+0x12 (=efxc+1/+2). * Don't use this efx registers for recording on SB Live! 5.1! */ sc->efxc_base = 0x30; sc->dsp_zero = 0x40; sc->mchannel_fx = 0; sc->num_fxbuses = 8; sc->num_inputs = 8; sc->num_outputs = 16; sc->address_mask = EMU_PTR_ADDR_MASK; } if (sc->opcode_shift == 0) goto bad; pci_enable_busmaster(dev); i = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE); if (sc->reg == NULL) { device_printf(dev, "unable to map register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) sc->timer[i] = 0; /* disable it */ i = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); if ((sc->irq == NULL) || bus_setup_intr(dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV, -#if __FreeBSD_version >= 700031 NULL, -#endif emu_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (emu_rm_init(sc) != 0) { device_printf(dev, "unable to create resource manager\n"); goto bad; } if (sc->is_cardbus) if (emu_cardbus_init(sc) != 0) { device_printf(dev, "unable to initialize CardBus interface\n"); goto bad; } if (emu_init(sc) != 0) { device_printf(dev, "unable to initialize the card\n"); goto bad; } if (emu10kx_dev_init(sc) != 0) { device_printf(dev, "unable to create control device\n"); goto bad; } snprintf(status, 255, "rev %d at io 0x%lx irq %ld", sc->rev, rman_get_start(sc->reg), rman_get_start(sc->irq)); /* Voices */ for (i = 0; i < NUM_G; i++) { sc->voice[i].vnum = i; sc->voice[i].slave = NULL; sc->voice[i].busy = 0; sc->voice[i].ismaster = 0; sc->voice[i].running = 0; sc->voice[i].b16 = 0; sc->voice[i].stereo = 0; sc->voice[i].speed = 0; sc->voice[i].start = 0; sc->voice[i].end = 0; } /* PCM Audio */ for (i = 0; i < RT_COUNT; i++) sc->pcm[i] = NULL; /* FRONT */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (pcminfo == NULL) { error = ENOMEM; goto bad; } pcminfo->card = sc; pcminfo->route = RT_FRONT; func->func = SCF_PCM; func->varinfo = pcminfo; sc->pcm[RT_FRONT] = device_add_child(dev, "pcm", -1); device_set_ivars(sc->pcm[RT_FRONT], func); if (!(sc->mch_disabled)) { /* REAR */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (pcminfo == NULL) { error = ENOMEM; goto bad; } pcminfo->card = sc; pcminfo->route = RT_REAR; func->func = SCF_PCM; func->varinfo = pcminfo; sc->pcm[RT_REAR] = device_add_child(dev, "pcm", -1); device_set_ivars(sc->pcm[RT_REAR], func); if (sc->has_51) { /* CENTER */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (pcminfo == NULL) { error = ENOMEM; goto bad; } pcminfo->card = sc; pcminfo->route = RT_CENTER; func->func = SCF_PCM; func->varinfo = pcminfo; sc->pcm[RT_CENTER] = device_add_child(dev, "pcm", -1); device_set_ivars(sc->pcm[RT_CENTER], func); /* SUB */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (pcminfo == NULL) { error = ENOMEM; goto bad; } pcminfo->card = sc; pcminfo->route = RT_SUB; func->func = SCF_PCM; func->varinfo = pcminfo; sc->pcm[RT_SUB] = device_add_child(dev, "pcm", -1); device_set_ivars(sc->pcm[RT_SUB], func); } if (sc->has_71) { /* SIDE */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (pcminfo == NULL) { error = ENOMEM; goto bad; } pcminfo->card = sc; pcminfo->route = RT_SIDE; func->func = SCF_PCM; func->varinfo = pcminfo; sc->pcm[RT_SIDE] = device_add_child(dev, "pcm", -1); device_set_ivars(sc->pcm[RT_SIDE], func); } } /* mch_disabled */ if (sc->mch_rec) { func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (pcminfo == NULL) { error = ENOMEM; goto bad; } pcminfo->card = sc; pcminfo->route = RT_MCHRECORD; func->func = SCF_PCM; func->varinfo = pcminfo; sc->pcm[RT_MCHRECORD] = device_add_child(dev, "pcm", -1); device_set_ivars(sc->pcm[RT_MCHRECORD], func); } /*mch_rec */ for (i = 0; i < 2; i++) sc->midi[i] = NULL; /* MIDI has some memory mangament and (possible) locking problems */ #if 0 /* Midi Interface 1: Live!, Audigy, Audigy 2 */ if ((sc->is_emu10k1) || (sc->is_emu10k2) || (sc->is_ca0102)) { func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } midiinfo = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (midiinfo == NULL) { error = ENOMEM; goto bad; } midiinfo->card = sc; if (sc->is_emu10k2 || (sc->is_ca0102)) { midiinfo->port = EMU_A_MUDATA1; midiinfo->portnr = 1; } if (sc->is_emu10k1) { midiinfo->port = MUDATA; midiinfo->portnr = 1; } func->func = SCF_MIDI; func->varinfo = midiinfo; sc->midi[0] = device_add_child(dev, "midi", -1); device_set_ivars(sc->midi[0], func); } /* Midi Interface 2: Audigy, Audigy 2 (on AudigyDrive) */ if (sc->is_emu10k2 || (sc->is_ca0102)) { func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) { error = ENOMEM; goto bad; } midiinfo = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (midiinfo == NULL) { error = ENOMEM; goto bad; } midiinfo->card = sc; midiinfo->port = EMU_A_MUDATA2; midiinfo->portnr = 2; func->func = SCF_MIDI; func->varinfo = midiinfo; sc->midi[1] = device_add_child(dev, "midi", -1); device_set_ivars(sc->midi[1], func); } #endif return (bus_generic_attach(dev)); bad: /* XXX can we just call emu_pci_detach here? */ if (sc->cdev) emu10kx_dev_uninit(sc); if (sc->rm != NULL) emu_rm_uninit(sc); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); mtx_destroy(&sc->rw); mtx_destroy(&sc->lock); return (error); } static int emu_pci_detach(device_t dev) { struct emu_sc_info *sc; struct sndcard_func *func; int devcount, i; device_t *childlist; int r = 0; sc = device_get_softc(dev); for (i = 0; i < RT_COUNT; i++) { if (sc->pcm[i] != NULL) { func = device_get_ivars(sc->pcm[i]); if (func != NULL && func->func == SCF_PCM) { device_set_ivars(sc->pcm[i], NULL); free(func->varinfo, M_DEVBUF); free(func, M_DEVBUF); } r = device_delete_child(dev, sc->pcm[i]); if (r) return (r); } } if (sc->midi[0] != NULL) { func = device_get_ivars(sc->midi[0]); if (func != NULL && func->func == SCF_MIDI) { device_set_ivars(sc->midi[0], NULL); free(func->varinfo, M_DEVBUF); free(func, M_DEVBUF); } r = device_delete_child(dev, sc->midi[0]); if (r) return (r); } if (sc->midi[1] != NULL) { func = device_get_ivars(sc->midi[1]); if (func != NULL && func->func == SCF_MIDI) { device_set_ivars(sc->midi[1], NULL); free(func->varinfo, M_DEVBUF); free(func, M_DEVBUF); } r = device_delete_child(dev, sc->midi[1]); if (r) return (r); } if (device_get_children(dev, &childlist, &devcount) == 0) for (i = 0; i < devcount - 1; i++) { device_printf(dev, "removing stale child %d (unit %d)\n", i, device_get_unit(childlist[i])); func = device_get_ivars(childlist[i]); if (func != NULL && (func->func == SCF_MIDI || func->func == SCF_PCM)) { device_set_ivars(childlist[i], NULL); free(func->varinfo, M_DEVBUF); free(func, M_DEVBUF); } device_delete_child(dev, childlist[i]); } if (childlist != NULL) free(childlist, M_TEMP); r = emu10kx_dev_uninit(sc); if (r) return (r); /* shutdown chip */ emu_uninit(sc); emu_rm_uninit(sc); if (sc->mem.dmat) bus_dma_tag_destroy(sc->mem.dmat); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); mtx_destroy(&sc->rw); mtx_destroy(&sc->lock); return (bus_generic_detach(dev)); } /* add suspend, resume */ static device_method_t emu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emu_pci_probe), DEVMETHOD(device_attach, emu_pci_attach), DEVMETHOD(device_detach, emu_pci_detach), /* Bus methods */ DEVMETHOD(bus_read_ivar, emu_read_ivar), DEVMETHOD(bus_write_ivar, emu_write_ivar), DEVMETHOD_END }; static driver_t emu_driver = { "emu10kx", emu_methods, sizeof(struct emu_sc_info), NULL, 0, NULL }; static int emu_modevent(module_t mod __unused, int cmd, void *data __unused) { int err = 0; switch (cmd) { case MOD_LOAD: break; /* Success */ case MOD_UNLOAD: case MOD_SHUTDOWN: /* XXX Should we check state of pcm & midi subdevices here? */ break; /* Success */ default: err = EINVAL; break; } return (err); } static devclass_t emu_devclass; DRIVER_MODULE(snd_emu10kx, pci, emu_driver, emu_devclass, emu_modevent, NULL); MODULE_VERSION(snd_emu10kx, SND_EMU10KX_PREFVER); Index: head/sys/dev/sound/pci/envy24.c =================================================================== --- head/sys/dev/sound/pci/envy24.c (revision 274034) +++ head/sys/dev/sound/pci/envy24.c (revision 274035) @@ -1,2703 +1,2699 @@ /*- * Copyright (c) 2001 Katsurajima Naoto * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); static MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio"); /* -------------------------------------------------------------------- */ struct sc_info; #define ENVY24_PLAY_CHNUM 10 #define ENVY24_REC_CHNUM 12 #define ENVY24_PLAY_BUFUNIT (4 /* byte/sample */ * 10 /* channel */) #define ENVY24_REC_BUFUNIT (4 /* byte/sample */ * 12 /* channel */) #define ENVY24_SAMPLE_NUM 4096 #define ENVY24_TIMEOUT 1000 #define ENVY24_DEFAULT_FORMAT SND_FORMAT(AFMT_S16_LE, 2, 0) #define ENVY24_NAMELEN 32 #define SDA_GPIO 0x10 #define SCL_GPIO 0x20 struct envy24_sample { volatile u_int32_t buffer; }; typedef struct envy24_sample sample32_t; /* channel registers */ struct sc_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; int dir; unsigned num; /* hw channel number */ /* channel information */ u_int32_t format; u_int32_t speed; u_int32_t blk; /* hw block size(dword) */ /* format conversion structure */ u_int8_t *data; unsigned int size; /* data buffer size(byte) */ int unit; /* sample size(byte) */ unsigned int offset; /* samples number offset */ void (*emldma)(struct sc_chinfo *); /* flags */ int run; }; /* codec interface entrys */ struct codec_entry { void *(*create)(device_t dev, void *devinfo, int dir, int num); void (*destroy)(void *codec); void (*init)(void *codec); void (*reinit)(void *codec); void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); void (*setrate)(void *codec, int which, int rate); }; /* system configuration information */ struct cfg_info { char *name; u_int16_t subvendor, subdevice; u_int8_t scfg, acl, i2s, spdif; u_int8_t gpiomask, gpiostate, gpiodir; u_int8_t cdti, cclk, cs, cif, type; u_int8_t free; struct codec_entry *codec; }; /* device private data */ struct sc_info { device_t dev; struct mtx *lock; /* Control/Status registor */ struct resource *cs; int csid; bus_space_tag_t cst; bus_space_handle_t csh; /* DDMA registor */ struct resource *ddma; int ddmaid; bus_space_tag_t ddmat; bus_space_handle_t ddmah; /* Consumer Section DMA Channel Registers */ struct resource *ds; int dsid; bus_space_tag_t dst; bus_space_handle_t dsh; /* MultiTrack registor */ struct resource *mt; int mtid; bus_space_tag_t mtt; bus_space_handle_t mth; /* DMA tag */ bus_dma_tag_t dmat; /* IRQ resource */ struct resource *irq; int irqid; void *ih; /* system configuration data */ struct cfg_info *cfg; /* ADC/DAC number and info */ int adcn, dacn; void *adc[4], *dac[4]; /* mixer control data */ u_int32_t src; u_int8_t left[ENVY24_CHAN_NUM]; u_int8_t right[ENVY24_CHAN_NUM]; /* Play/Record DMA fifo */ sample32_t *pbuf; sample32_t *rbuf; u_int32_t psize, rsize; /* DMA buffer size(byte) */ u_int16_t blk[2]; /* transfer check blocksize(dword) */ bus_dmamap_t pmap, rmap; bus_addr_t paddr, raddr; /* current status */ u_int32_t speed; int run[2]; u_int16_t intr[2]; struct pcmchan_caps caps[2]; /* channel info table */ unsigned chnum; struct sc_chinfo chan[11]; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* DMA emulator */ static void envy24_p8u(struct sc_chinfo *); static void envy24_p16sl(struct sc_chinfo *); static void envy24_p32sl(struct sc_chinfo *); static void envy24_r16sl(struct sc_chinfo *); static void envy24_r32sl(struct sc_chinfo *); /* channel interface */ static void *envy24chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int envy24chan_setformat(kobj_t, void *, u_int32_t); static u_int32_t envy24chan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t envy24chan_setblocksize(kobj_t, void *, u_int32_t); static int envy24chan_trigger(kobj_t, void *, int); static u_int32_t envy24chan_getptr(kobj_t, void *); static struct pcmchan_caps *envy24chan_getcaps(kobj_t, void *); /* mixer interface */ static int envy24mixer_init(struct snd_mixer *); static int envy24mixer_reinit(struct snd_mixer *); static int envy24mixer_uninit(struct snd_mixer *); static int envy24mixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *, u_int32_t); /* M-Audio Delta series AK4524 access interface */ static void *envy24_delta_ak4524_create(device_t, void *, int, int); static void envy24_delta_ak4524_destroy(void *); static void envy24_delta_ak4524_init(void *); static void envy24_delta_ak4524_reinit(void *); static void envy24_delta_ak4524_setvolume(void *, int, unsigned int, unsigned int); /* -------------------------------------------------------------------- */ /* system constant tables */ /* API -> hardware channel map */ static unsigned envy24_chanmap[ENVY24_CHAN_NUM] = { ENVY24_CHAN_PLAY_SPDIF, /* 0 */ ENVY24_CHAN_PLAY_DAC1, /* 1 */ ENVY24_CHAN_PLAY_DAC2, /* 2 */ ENVY24_CHAN_PLAY_DAC3, /* 3 */ ENVY24_CHAN_PLAY_DAC4, /* 4 */ ENVY24_CHAN_REC_MIX, /* 5 */ ENVY24_CHAN_REC_SPDIF, /* 6 */ ENVY24_CHAN_REC_ADC1, /* 7 */ ENVY24_CHAN_REC_ADC2, /* 8 */ ENVY24_CHAN_REC_ADC3, /* 9 */ ENVY24_CHAN_REC_ADC4, /* 10 */ }; /* mixer -> API channel map. see above */ static int envy24_mixmap[] = { -1, /* Master output level. It is depend on codec support */ -1, /* Treble level of all output channels */ -1, /* Bass level of all output channels */ -1, /* Volume of synthesier input */ 0, /* Output level for the audio device */ -1, /* Output level for the PC speaker */ 7, /* line in jack */ -1, /* microphone jack */ -1, /* CD audio input */ -1, /* Recording monitor */ 1, /* alternative codec */ -1, /* global recording level */ -1, /* Input gain */ -1, /* Output gain */ 8, /* Input source 1 */ 9, /* Input source 2 */ 10, /* Input source 3 */ 6, /* Digital (input) 1 */ -1, /* Digital (input) 2 */ -1, /* Digital (input) 3 */ -1, /* Phone input */ -1, /* Phone output */ -1, /* Video/TV (audio) in */ -1, /* Radio in */ -1, /* Monitor volume */ }; /* variable rate audio */ static u_int32_t envy24_speed[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000, 0 }; /* known boards configuration */ static struct codec_entry delta_codec = { envy24_delta_ak4524_create, envy24_delta_ak4524_destroy, envy24_delta_ak4524_init, envy24_delta_ak4524_reinit, envy24_delta_ak4524_setvolume, NULL, /* setrate */ }; static struct cfg_info cfg_table[] = { { "Envy24 audio (M Audio Delta Dio 2496)", 0x1412, 0xd631, 0x10, 0x80, 0xf0, 0x03, 0x02, 0xc0, 0xfd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (Terratec DMX 6fire)", 0x153b, 0x1138, 0x2f, 0x80, 0xf0, 0x03, 0xc0, 0xff, 0x7f, 0x10, 0x20, 0x01, 0x01, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Audiophile 2496)", 0x1412, 0xd634, 0x10, 0x80, 0x72, 0x03, 0x04, 0xfe, 0xfb, 0x08, 0x02, 0x20, 0x00, 0x01, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 66)", 0x1412, 0xd632, 0x15, 0x80, 0xf0, 0x03, 0x02, 0xc0, 0xfd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 44)", 0x1412, 0xd633, 0x15, 0x80, 0xf0, 0x00, 0x02, 0xc0, 0xfd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 1010)", 0x1412, 0xd630, 0x1f, 0x80, 0xf0, 0x03, 0x22, 0xd0, 0xdd, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (M Audio Delta 1010LT)", 0x1412, 0xd63b, 0x1f, 0x80, 0x72, 0x03, 0x04, 0x7e, 0xfb, 0x08, 0x02, 0x70, 0x00, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (Terratec EWX 2496)", 0x153b, 0x1130, 0x10, 0x80, 0xf0, 0x03, 0xc0, 0x3f, 0x3f, 0x10, 0x20, 0x01, 0x01, 0x00, 0x00, &delta_codec, }, { "Envy24 audio (Generic)", 0, 0, 0x0f, 0x00, 0x01, 0x03, 0xff, 0x00, 0x00, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, &delta_codec, /* default codec routines */ } }; static u_int32_t envy24_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24_reccaps = {8000, 96000, envy24_recfmt, 0}; static u_int32_t envy24_playfmt[] = { SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24_playcaps = {8000, 96000, envy24_playfmt, 0}; struct envy24_emldma { u_int32_t format; void (*emldma)(struct sc_chinfo *); int unit; }; static struct envy24_emldma envy24_pemltab[] = { {SND_FORMAT(AFMT_U8, 2, 0), envy24_p8u, 2}, {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24_p16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24_p32sl, 8}, {0, NULL, 0} }; static struct envy24_emldma envy24_remltab[] = { {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24_r16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24_r32sl, 8}, {0, NULL, 0} }; /* -------------------------------------------------------------------- */ /* common routines */ static u_int32_t envy24_rdcs(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->cst, sc->csh, regno); case 2: return bus_space_read_2(sc->cst, sc->csh, regno); case 4: return bus_space_read_4(sc->cst, sc->csh, regno); default: return 0xffffffff; } } static void envy24_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->cst, sc->csh, regno, data); break; case 2: bus_space_write_2(sc->cst, sc->csh, regno, data); break; case 4: bus_space_write_4(sc->cst, sc->csh, regno, data); break; } } static u_int32_t envy24_rdmt(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->mtt, sc->mth, regno); case 2: return bus_space_read_2(sc->mtt, sc->mth, regno); case 4: return bus_space_read_4(sc->mtt, sc->mth, regno); default: return 0xffffffff; } } static void envy24_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->mtt, sc->mth, regno, data); break; case 2: bus_space_write_2(sc->mtt, sc->mth, regno, data); break; case 4: bus_space_write_4(sc->mtt, sc->mth, regno, data); break; } } static u_int32_t envy24_rdci(struct sc_info *sc, int regno) { envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); return envy24_rdcs(sc, ENVY24_CCS_DATA, 1); } static void envy24_wrci(struct sc_info *sc, int regno, u_int32_t data) { envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); envy24_wrcs(sc, ENVY24_CCS_DATA, data, 1); } /* -------------------------------------------------------------------- */ /* I2C port/E2PROM access routines */ static int envy24_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); envy24_wrcs(sc, ENVY24_CCS_I2CDEV, (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_RD, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } data = envy24_rdcs(sc, ENVY24_CCS_I2CDATA, 1); #if(0) device_printf(sc->dev, "envy24_rdi2c(): return 0x%x\n", data); #endif return (int)data; } #if 0 static int envy24_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) { u_int32_t tmp; int i; #if(0) device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24_TIMEOUT; i++) { tmp = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((tmp & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); envy24_wrcs(sc, ENVY24_CCS_I2CDATA, data, 1); envy24_wrcs(sc, ENVY24_CCS_I2CDEV, (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_WR, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24_TIMEOUT) { return -1; } return 0; } #endif static int envy24_rdrom(struct sc_info *sc, u_int32_t addr) { u_int32_t data; #if(0) device_printf(sc->dev, "envy24_rdrom(sc, 0x%02x)\n", addr); #endif data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); if ((data & ENVY24_CCS_I2CSTAT_ROM) == 0) { #if(0) device_printf(sc->dev, "envy24_rdrom(): E2PROM not presented\n"); #endif return -1; } return envy24_rdi2c(sc, ENVY24_CCS_I2CDEV_ROM, addr); } static struct cfg_info * envy24_rom2cfg(struct sc_info *sc) { struct cfg_info *buff; int size; int i; #if(0) device_printf(sc->dev, "envy24_rom2cfg(sc)\n"); #endif size = envy24_rdrom(sc, ENVY24_E2PROM_SIZE); if (size < ENVY24_E2PROM_GPIODIR + 1) { #if(0) device_printf(sc->dev, "envy24_rom2cfg(): ENVY24_E2PROM_SIZE-->%d\n", size); #endif return NULL; } buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); if (buff == NULL) { #if(0) device_printf(sc->dev, "envy24_rom2cfg(): malloc()\n"); #endif return NULL; } buff->free = 1; buff->subvendor = envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE + 1); buff->scfg = envy24_rdrom(sc, ENVY24_E2PROM_SCFG); buff->acl = envy24_rdrom(sc, ENVY24_E2PROM_ACL); buff->i2s = envy24_rdrom(sc, ENVY24_E2PROM_I2S); buff->spdif = envy24_rdrom(sc, ENVY24_E2PROM_SPDIF); buff->gpiomask = envy24_rdrom(sc, ENVY24_E2PROM_GPIOMASK); buff->gpiostate = envy24_rdrom(sc, ENVY24_E2PROM_GPIOSTATE); buff->gpiodir = envy24_rdrom(sc, ENVY24_E2PROM_GPIODIR); for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) if (cfg_table[i].subvendor == buff->subvendor && cfg_table[i].subdevice == buff->subdevice) break; buff->name = cfg_table[i].name; buff->codec = cfg_table[i].codec; return buff; } static void envy24_cfgfree(struct cfg_info *cfg) { if (cfg == NULL) return; if (cfg->free) free(cfg, M_ENVY24); return; } /* -------------------------------------------------------------------- */ /* AC'97 codec access routines */ #if 0 static int envy24_coldcd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_coldcd()\n"); #endif envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD, 1); DELAY(10); envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if (data & ENVY24_MT_AC97CMD_RDY) { return 0; } } return -1; } #endif static int envy24_slavecd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_slavecd()\n"); #endif envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD | ENVY24_MT_AC97CMD_WRM, 1); DELAY(10); envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if (data & ENVY24_MT_AC97CMD_RDY) { return 0; } } return -1; } #if 0 static int envy24_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24_rdcd(obj, sc, 0x%02x)\n", regno); #endif envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_RD, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if ((data & ENVY24_MT_AC97CMD_RD) == 0) break; } data = envy24_rdmt(sc, ENVY24_MT_AC97DLO, 2); #if(0) device_printf(sc->dev, "envy24_rdcd(): return 0x%x\n", data); #endif return (int)data; } static int envy24_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t cmd; int i; #if(0) device_printf(sc->dev, "envy24_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); #endif envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); envy24_wrmt(sc, ENVY24_MT_AC97DLO, (u_int32_t)data, 2); envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_WR, 1); for (i = 0; i < ENVY24_TIMEOUT; i++) { cmd = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); if ((cmd & ENVY24_MT_AC97CMD_WR) == 0) break; } return 0; } static kobj_method_t envy24_ac97_methods[] = { KOBJMETHOD(ac97_read, envy24_rdcd), KOBJMETHOD(ac97_write, envy24_wrcd), KOBJMETHOD_END }; AC97_DECLARE(envy24_ac97); #endif /* -------------------------------------------------------------------- */ /* GPIO access routines */ static u_int32_t envy24_gpiord(struct sc_info *sc) { return envy24_rdci(sc, ENVY24_CCI_GPIODAT); } static void envy24_gpiowr(struct sc_info *sc, u_int32_t data) { #if(0) device_printf(sc->dev, "envy24_gpiowr(sc, 0x%02x)\n", data & 0xff); return; #endif envy24_wrci(sc, ENVY24_CCI_GPIODAT, data); return; } #if 0 static u_int32_t envy24_gpiogetmask(struct sc_info *sc) { return envy24_rdci(sc, ENVY24_CCI_GPIOMASK); } #endif static void envy24_gpiosetmask(struct sc_info *sc, u_int32_t mask) { envy24_wrci(sc, ENVY24_CCI_GPIOMASK, mask); return; } #if 0 static u_int32_t envy24_gpiogetdir(struct sc_info *sc) { return envy24_rdci(sc, ENVY24_CCI_GPIOCTL); } #endif static void envy24_gpiosetdir(struct sc_info *sc, u_int32_t dir) { envy24_wrci(sc, ENVY24_CCI_GPIOCTL, dir); return; } /* -------------------------------------------------------------------- */ /* Envy24 I2C through GPIO bit-banging */ struct envy24_delta_ak4524_codec { struct spicds_info *info; struct sc_info *parent; int dir; int num; int cs, cclk, cdti; }; static void envy24_gpio_i2c_ctl(void *codec, unsigned int scl, unsigned int sda) { u_int32_t data = 0; struct envy24_delta_ak4524_codec *ptr = codec; #if(0) device_printf(ptr->parent->dev, "--> %d, %d\n", scl, sda); #endif data = envy24_gpiord(ptr->parent); data &= ~(SDA_GPIO | SCL_GPIO); if (scl) data += SCL_GPIO; if (sda) data += SDA_GPIO; envy24_gpiowr(ptr->parent, data); return; } static void i2c_wrbit(void *codec, void (*ctrl)(void*, unsigned int, unsigned int), int bit) { struct envy24_delta_ak4524_codec *ptr = codec; unsigned int sda; if (bit) sda = 1; else sda = 0; ctrl(ptr, 0, sda); DELAY(I2C_DELAY); ctrl(ptr, 1, sda); DELAY(I2C_DELAY); ctrl(ptr, 0, sda); DELAY(I2C_DELAY); } static void i2c_start(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) { struct envy24_delta_ak4524_codec *ptr = codec; ctrl(ptr, 1, 1); DELAY(I2C_DELAY); ctrl(ptr, 1, 0); DELAY(I2C_DELAY); ctrl(ptr, 0, 0); DELAY(I2C_DELAY); } static void i2c_stop(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) { struct envy24_delta_ak4524_codec *ptr = codec; ctrl(ptr, 0, 0); DELAY(I2C_DELAY); ctrl(ptr, 1, 0); DELAY(I2C_DELAY); ctrl(ptr, 1, 1); DELAY(I2C_DELAY); } static void i2c_ack(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) { struct envy24_delta_ak4524_codec *ptr = codec; ctrl(ptr, 0, 1); DELAY(I2C_DELAY); ctrl(ptr, 1, 1); DELAY(I2C_DELAY); /* dummy, need routine to change gpio direction */ ctrl(ptr, 0, 1); DELAY(I2C_DELAY); } static void i2c_wr(void *codec, void (*ctrl)(void*, unsigned int, unsigned int), u_int32_t dev, int reg, u_int8_t val) { struct envy24_delta_ak4524_codec *ptr = codec; int mask; i2c_start(ptr, ctrl); for (mask = 0x80; mask != 0; mask >>= 1) i2c_wrbit(ptr, ctrl, dev & mask); i2c_ack(ptr, ctrl); if (reg != 0xff) { for (mask = 0x80; mask != 0; mask >>= 1) i2c_wrbit(ptr, ctrl, reg & mask); i2c_ack(ptr, ctrl); } for (mask = 0x80; mask != 0; mask >>= 1) i2c_wrbit(ptr, ctrl, val & mask); i2c_ack(ptr, ctrl); i2c_stop(ptr, ctrl); } /* -------------------------------------------------------------------- */ /* M-Audio Delta series AK4524 access interface routine */ static void envy24_delta_ak4524_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) { u_int32_t data = 0; struct envy24_delta_ak4524_codec *ptr = codec; #if(0) device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); #endif data = envy24_gpiord(ptr->parent); data &= ~(ptr->cs | ptr->cclk | ptr->cdti); if (cs) data += ptr->cs; if (cclk) data += ptr->cclk; if (cdti) data += ptr->cdti; envy24_gpiowr(ptr->parent, data); return; } static void * envy24_delta_ak4524_create(device_t dev, void *info, int dir, int num) { struct sc_info *sc = info; struct envy24_delta_ak4524_codec *buff = NULL; #if(0) device_printf(sc->dev, "envy24_delta_ak4524_create(dev, sc, %d, %d)\n", dir, num); #endif buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); if (buff == NULL) return NULL; if (dir == PCMDIR_REC && sc->adc[num] != NULL) buff->info = ((struct envy24_delta_ak4524_codec *)sc->adc[num])->info; else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) buff->info = ((struct envy24_delta_ak4524_codec *)sc->dac[num])->info; else buff->info = spicds_create(dev, buff, num, envy24_delta_ak4524_ctl); if (buff->info == NULL) { free(buff, M_ENVY24); return NULL; } buff->parent = sc; buff->dir = dir; buff->num = num; return (void *)buff; } static void envy24_delta_ak4524_destroy(void *codec) { struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_destroy()\n"); #endif if (ptr->dir == PCMDIR_PLAY) { if (ptr->parent->dac[ptr->num] != NULL) spicds_destroy(ptr->info); } else { if (ptr->parent->adc[ptr->num] != NULL) spicds_destroy(ptr->info); } free(codec, M_ENVY24); } static void envy24_delta_ak4524_init(void *codec) { #if 0 u_int32_t gpiomask, gpiodir; #endif struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_init()\n"); #endif /* gpiomask = envy24_gpiogetmask(ptr->parent); gpiomask &= ~(ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1); envy24_gpiosetmask(ptr->parent, gpiomask); gpiodir = envy24_gpiogetdir(ptr->parent); gpiodir |= ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1; envy24_gpiosetdir(ptr->parent, gpiodir); */ ptr->cs = ptr->parent->cfg->cs; #if 0 envy24_gpiosetmask(ptr->parent, ENVY24_GPIO_CS8414_STATUS); envy24_gpiosetdir(ptr->parent, ~ENVY24_GPIO_CS8414_STATUS); if (ptr->num == 0) ptr->cs = ENVY24_GPIO_AK4524_CS0; else ptr->cs = ENVY24_GPIO_AK4524_CS1; ptr->cclk = ENVY24_GPIO_AK4524_CCLK; #endif ptr->cclk = ptr->parent->cfg->cclk; ptr->cdti = ptr->parent->cfg->cdti; spicds_settype(ptr->info, ptr->parent->cfg->type); spicds_setcif(ptr->info, ptr->parent->cfg->cif); spicds_setformat(ptr->info, AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); spicds_setdvc(ptr->info, AK452X_DVC_DEMOFF); /* for the time being, init only first codec */ if (ptr->num == 0) spicds_init(ptr->info); /* 6fire rear input init test, set ptr->num to 1 for test */ if (ptr->parent->cfg->subvendor == 0x153b && \ ptr->parent->cfg->subdevice == 0x1138 && ptr->num == 100) { ptr->cs = 0x02; spicds_init(ptr->info); device_printf(ptr->parent->dev, "6fire rear input init\n"); i2c_wr(ptr, envy24_gpio_i2c_ctl, \ PCA9554_I2CDEV, PCA9554_DIR, 0x80); i2c_wr(ptr, envy24_gpio_i2c_ctl, \ PCA9554_I2CDEV, PCA9554_OUT, 0x02); } } static void envy24_delta_ak4524_reinit(void *codec) { struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_reinit()\n"); #endif spicds_reinit(ptr->info); } static void envy24_delta_ak4524_setvolume(void *codec, int dir, unsigned int left, unsigned int right) { struct envy24_delta_ak4524_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24_delta_ak4524_set()\n"); #endif spicds_set(ptr->info, dir, left, right); } /* There is no need for AK452[48] codec to set sample rate static void envy24_delta_ak4524_setrate(struct envy24_delta_ak4524_codec *codec, int which, int rate) { } */ /* -------------------------------------------------------------------- */ /* hardware access routeines */ static struct { u_int32_t speed; u_int32_t code; } envy24_speedtab[] = { {48000, ENVY24_MT_RATE_48000}, {24000, ENVY24_MT_RATE_24000}, {12000, ENVY24_MT_RATE_12000}, {9600, ENVY24_MT_RATE_9600}, {32000, ENVY24_MT_RATE_32000}, {16000, ENVY24_MT_RATE_16000}, {8000, ENVY24_MT_RATE_8000}, {96000, ENVY24_MT_RATE_96000}, {64000, ENVY24_MT_RATE_64000}, {44100, ENVY24_MT_RATE_44100}, {22050, ENVY24_MT_RATE_22050}, {11025, ENVY24_MT_RATE_11025}, {88200, ENVY24_MT_RATE_88200}, {0, 0x10} }; static u_int32_t envy24_setspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t code; int i = 0; #if(0) device_printf(sc->dev, "envy24_setspeed(sc, %d)\n", speed); #endif if (speed == 0) { code = ENVY24_MT_RATE_SPDIF; /* external master clock */ envy24_slavecd(sc); } else { for (i = 0; envy24_speedtab[i].speed != 0; i++) { if (envy24_speedtab[i].speed == speed) break; } code = envy24_speedtab[i].code; } #if(0) device_printf(sc->dev, "envy24_setspeed(): speed %d/code 0x%04x\n", envy24_speedtab[i].speed, code); #endif if (code < 0x10) { envy24_wrmt(sc, ENVY24_MT_RATE, code, 1); code = envy24_rdmt(sc, ENVY24_MT_RATE, 1); code &= ENVY24_MT_RATE_MASK; for (i = 0; envy24_speedtab[i].code < 0x10; i++) { if (envy24_speedtab[i].code == code) break; } speed = envy24_speedtab[i].speed; } else speed = 0; #if(0) device_printf(sc->dev, "envy24_setspeed(): return %d\n", speed); #endif return speed; } static void envy24_setvolume(struct sc_info *sc, unsigned ch) { #if(0) device_printf(sc->dev, "envy24_setvolume(sc, %d)\n", ch); #endif if (sc->cfg->subvendor==0x153b && sc->cfg->subdevice==0x1138 ) { envy24_wrmt(sc, ENVY24_MT_VOLIDX, 16, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2); envy24_wrmt(sc, ENVY24_MT_VOLIDX, 17, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2); } envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f00 | sc->left[ch], 2); envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); } static void envy24_mutevolume(struct sc_info *sc, unsigned ch) { u_int32_t vol; #if(0) device_printf(sc->dev, "envy24_mutevolume(sc, %d)\n", ch); #endif vol = ENVY24_VOL_MUTE << 8 | ENVY24_VOL_MUTE; envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); } static u_int32_t envy24_gethwptr(struct sc_info *sc, int dir) { int unit, regno; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24_gethwptr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { rtn = sc->psize / 4; unit = ENVY24_PLAY_BUFUNIT / 4; regno = ENVY24_MT_PCNT; } else { rtn = sc->rsize / 4; unit = ENVY24_REC_BUFUNIT / 4; regno = ENVY24_MT_RCNT; } ptr = envy24_rdmt(sc, regno, 2); rtn -= (ptr + 1); rtn /= unit; #if(0) device_printf(sc->dev, "envy24_gethwptr(): return %d\n", rtn); #endif return rtn; } static void envy24_updintr(struct sc_info *sc, int dir) { int regptr, regintr; u_int32_t mask, intr; u_int32_t ptr, size, cnt; u_int16_t blk; #if(0) device_printf(sc->dev, "envy24_updintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { blk = sc->blk[0]; size = sc->psize / 4; regptr = ENVY24_MT_PCNT; regintr = ENVY24_MT_PTERM; mask = ~ENVY24_MT_INT_PMASK; } else { blk = sc->blk[1]; size = sc->rsize / 4; regptr = ENVY24_MT_RCNT; regintr = ENVY24_MT_RTERM; mask = ~ENVY24_MT_INT_RMASK; } ptr = size - envy24_rdmt(sc, regptr, 2) - 1; /* cnt = blk - ptr % blk - 1; if (cnt == 0) cnt = blk - 1; */ cnt = blk - 1; #if(0) device_printf(sc->dev, "envy24_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt); #endif envy24_wrmt(sc, regintr, cnt, 2); intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); #if(0) device_printf(sc->dev, "envy24_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); #endif envy24_wrmt(sc, ENVY24_MT_INT, intr & mask, 1); #if(0) device_printf(sc->dev, "envy24_updintr():INT-->0x%02x\n", envy24_rdmt(sc, ENVY24_MT_INT, 1)); #endif return; } #if 0 static void envy24_maskintr(struct sc_info *sc, int dir) { u_int32_t mask, intr; #if(0) device_printf(sc->dev, "envy24_maskintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) mask = ENVY24_MT_INT_PMASK; else mask = ENVY24_MT_INT_RMASK; intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); envy24_wrmt(sc, ENVY24_MT_INT, intr | mask, 1); return; } #endif static int envy24_checkintr(struct sc_info *sc, int dir) { u_int32_t mask, stat, intr, rtn; #if(0) device_printf(sc->dev, "envy24_checkintr(sc, %d)\n", dir); #endif intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); if (dir == PCMDIR_PLAY) { if ((rtn = intr & ENVY24_MT_INT_PSTAT) != 0) { mask = ~ENVY24_MT_INT_RSTAT; stat = ENVY24_MT_INT_PSTAT | ENVY24_MT_INT_PMASK; envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); } } else { if ((rtn = intr & ENVY24_MT_INT_RSTAT) != 0) { mask = ~ENVY24_MT_INT_PSTAT; stat = ENVY24_MT_INT_RSTAT | ENVY24_MT_INT_RMASK; envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); } } return rtn; } static void envy24_start(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24_start(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ENVY24_MT_PCTL_PSTART; else sw = ENVY24_MT_PCTL_RSTART; stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); envy24_wrmt(sc, ENVY24_MT_PCTL, stat | sw, 1); #if(0) DELAY(100); device_printf(sc->dev, "PADDR:0x%08x\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); device_printf(sc->dev, "PCNT:%ld\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); #endif return; } static void envy24_stop(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24_stop(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ~ENVY24_MT_PCTL_PSTART; else sw = ~ENVY24_MT_PCTL_RSTART; stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); envy24_wrmt(sc, ENVY24_MT_PCTL, stat & sw, 1); return; } static int envy24_route(struct sc_info *sc, int dac, int class, int adc, int rev) { u_int32_t reg, mask; u_int32_t left, right; #if(0) device_printf(sc->dev, "envy24_route(sc, %d, %d, %d, %d)\n", dac, class, adc, rev); #endif /* parameter pattern check */ if (dac < 0 || ENVY24_ROUTE_DAC_SPDIF < dac) return -1; if (class == ENVY24_ROUTE_CLASS_MIX && (dac != ENVY24_ROUTE_DAC_1 && dac != ENVY24_ROUTE_DAC_SPDIF)) return -1; if (rev) { left = ENVY24_ROUTE_RIGHT; right = ENVY24_ROUTE_LEFT; } else { left = ENVY24_ROUTE_LEFT; right = ENVY24_ROUTE_RIGHT; } if (dac == ENVY24_ROUTE_DAC_SPDIF) { reg = class | class << 2 | ((adc << 1 | left) | left << 3) << 8 | ((adc << 1 | right) | right << 3) << 12; #if(0) device_printf(sc->dev, "envy24_route(): MT_SPDOUT-->0x%04x\n", reg); #endif envy24_wrmt(sc, ENVY24_MT_SPDOUT, reg, 2); } else { mask = ~(0x0303 << dac * 2); reg = envy24_rdmt(sc, ENVY24_MT_PSDOUT, 2); reg = (reg & mask) | ((class | class << 8) << dac * 2); #if(0) device_printf(sc->dev, "envy24_route(): MT_PSDOUT-->0x%04x\n", reg); #endif envy24_wrmt(sc, ENVY24_MT_PSDOUT, reg, 2); mask = ~(0xff << dac * 8); reg = envy24_rdmt(sc, ENVY24_MT_RECORD, 4); reg = (reg & mask) | (((adc << 1 | left) | left << 3) | ((adc << 1 | right) | right << 3) << 4) << dac * 8; #if(0) device_printf(sc->dev, "envy24_route(): MT_RECORD-->0x%08x\n", reg); #endif envy24_wrmt(sc, ENVY24_MT_RECORD, reg, 4); /* 6fire rear input init test */ envy24_wrmt(sc, ENVY24_MT_RECORD, 0x00, 4); } return 0; } /* -------------------------------------------------------------------- */ /* buffer copy routines */ static void envy24_p32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 8; dmabuf = ch->parent->pbuf; data = (u_int32_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 4; dst = src / 2 + ch->offset; ssize = ch->size / 4; dsize = ch->size / 8; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = data[src]; dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24_p16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; #if(0) device_printf(ch->parent->dev, "envy24_p16sl()\n"); #endif length = sndbuf_getready(ch->buffer) / 4; dmabuf = ch->parent->pbuf; data = (u_int16_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 2; dst = src / 2 + ch->offset; ssize = ch->size / 2; dsize = ch->size / 4; slot = ch->num * 2; #if(0) device_printf(ch->parent->dev, "envy24_p16sl():%lu-->%lu(%lu)\n", src, dst, length); #endif for (i = 0; i < length; i++) { dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16; dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16; #if(0) if (i < 16) { printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot]); printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1]); } #endif dst++; dst %= dsize; src += 2; src %= ssize; } #if(0) printf("\n"); #endif return; } static void envy24_p8u(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int8_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 2; dmabuf = ch->parent->pbuf; data = (u_int8_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer); dst = src / 2 + ch->offset; ssize = ch->size; dsize = ch->size / 4; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24; dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24_r32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 8; dmabuf = ch->parent->rbuf; data = (u_int32_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 4; src = dst / 2 + ch->offset; dsize = ch->size / 4; ssize = ch->size / 8; slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } static void envy24_r16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 4; dmabuf = ch->parent->rbuf; data = (u_int16_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 2; src = dst / 2 + ch->offset; dsize = ch->size / 2; ssize = ch->size / 8; slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * envy24chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = (struct sc_info *)devinfo; struct sc_chinfo *ch; unsigned num; #if(0) device_printf(sc->dev, "envy24chan_init(obj, devinfo, b, c, %d)\n", dir); #endif snd_mtxlock(sc->lock); if ((sc->chnum > ENVY24_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || (sc->chnum < ENVY24_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { snd_mtxunlock(sc->lock); return NULL; } num = sc->chnum; ch = &sc->chan[num]; ch->size = 8 * ENVY24_SAMPLE_NUM; ch->data = malloc(ch->size, M_ENVY24, M_NOWAIT); if (ch->data == NULL) { ch->size = 0; ch = NULL; } else { ch->buffer = b; ch->channel = c; ch->parent = sc; ch->dir = dir; /* set channel map */ ch->num = envy24_chanmap[num]; snd_mtxunlock(sc->lock); sndbuf_setup(ch->buffer, ch->data, ch->size); snd_mtxlock(sc->lock); /* these 2 values are dummy */ ch->unit = 4; ch->blk = 10240; } snd_mtxunlock(sc->lock); return ch; } static int envy24chan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; #if(0) device_printf(sc->dev, "envy24chan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_ENVY24); ch->data = NULL; } snd_mtxunlock(sc->lock); return 0; } static int envy24chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct envy24_emldma *emltab; /* unsigned int bcnt, bsize; */ int i; #if(0) device_printf(sc->dev, "envy24chan_setformat(obj, data, 0x%08x)\n", format); #endif snd_mtxlock(sc->lock); /* check and get format related information */ if (ch->dir == PCMDIR_PLAY) emltab = envy24_pemltab; else emltab = envy24_remltab; if (emltab == NULL) { snd_mtxunlock(sc->lock); return -1; } for (i = 0; emltab[i].format != 0; i++) if (emltab[i].format == format) break; if (emltab[i].format == 0) { snd_mtxunlock(sc->lock); return -1; } /* set format information */ ch->format = format; ch->emldma = emltab[i].emldma; if (ch->unit > emltab[i].unit) ch->blk *= ch->unit / emltab[i].unit; else ch->blk /= emltab[i].unit / ch->unit; ch->unit = emltab[i].unit; /* set channel buffer information */ ch->size = ch->unit * ENVY24_SAMPLE_NUM; #if 0 if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); #endif snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24chan_setformat(): return 0x%08x\n", 0); #endif return 0; } /* IMPLEMENT NOTICE: In this driver, setspeed function only do setting of speed information value. And real hardware speed setting is done at start triggered(see envy24chan_trigger()). So, at this function is called, any value that ENVY24 can use is able to set. But, at start triggerd, some other channel is running, and that channel's speed isn't same with, then trigger function will fail. */ static u_int32_t envy24chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; u_int32_t val, prev; int i; #if(0) device_printf(ch->parent->dev, "envy24chan_setspeed(obj, data, %d)\n", speed); #endif prev = 0x7fffffff; for (i = 0; (val = envy24_speed[i]) != 0; i++) { if (abs(val - speed) < abs(prev - speed)) prev = val; else break; } ch->speed = prev; #if(0) device_printf(ch->parent->dev, "envy24chan_setspeed(): return %d\n", ch->speed); #endif return ch->speed; } static u_int32_t envy24chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; /* struct sc_info *sc = ch->parent; */ u_int32_t size, prev; unsigned int bcnt, bsize; #if(0) device_printf(sc->dev, "envy24chan_setblocksize(obj, data, %d)\n", blocksize); #endif prev = 0x7fffffff; /* snd_mtxlock(sc->lock); */ for (size = ch->size / 2; size > 0; size /= 2) { if (abs(size - blocksize) < abs(prev - blocksize)) prev = size; else break; } ch->blk = prev / ch->unit; if (ch->dir == PCMDIR_PLAY) ch->blk *= ENVY24_PLAY_BUFUNIT / 4; else ch->blk *= ENVY24_REC_BUFUNIT / 4; /* set channel buffer information */ /* ch->size = ch->unit * ENVY24_SAMPLE_NUM; */ if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); /* snd_mtxunlock(sc->lock); */ #if(0) device_printf(sc->dev, "envy24chan_setblocksize(): return %d\n", prev); #endif return prev; } /* semantic note: must start at beginning of buffer */ static int envy24chan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr; int slot; int error = 0; #if 0 int i; device_printf(sc->dev, "envy24chan_trigger(obj, data, %d)\n", go); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) slot = 0; else slot = 1; switch (go) { case PCMTRIG_START: #if(0) device_printf(sc->dev, "envy24chan_trigger(): start\n"); #endif /* check or set channel speed */ if (sc->run[0] == 0 && sc->run[1] == 0) { sc->speed = envy24_setspeed(sc, ch->speed); sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; } else if (ch->speed != 0 && ch->speed != sc->speed) { error = -1; goto fail; } if (ch->speed == 0) ch->channel->speed = sc->speed; /* start or enable channel */ sc->run[slot]++; if (sc->run[slot] == 1) { /* first channel */ ch->offset = 0; sc->blk[slot] = ch->blk; } else { ptr = envy24_gethwptr(sc, ch->dir); ch->offset = ((ptr / ch->blk + 1) * ch->blk % (ch->size / 4)) * 4 / ch->unit; if (ch->blk < sc->blk[slot]) sc->blk[slot] = ch->blk; } if (ch->dir == PCMDIR_PLAY) { ch->emldma(ch); envy24_setvolume(sc, ch->num); } envy24_updintr(sc, ch->dir); if (sc->run[slot] == 1) envy24_start(sc, ch->dir); ch->run = 1; break; case PCMTRIG_EMLDMAWR: #if(0) device_printf(sc->dev, "envy24chan_trigger(): emldmawr\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_EMLDMARD: #if(0) device_printf(sc->dev, "envy24chan_trigger(): emldmard\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_ABORT: if (ch->run) { #if(0) device_printf(sc->dev, "envy24chan_trigger(): abort\n"); #endif ch->run = 0; sc->run[slot]--; if (ch->dir == PCMDIR_PLAY) envy24_mutevolume(sc, ch->num); if (sc->run[slot] == 0) { envy24_stop(sc, ch->dir); sc->intr[slot] = 0; } #if 0 else if (ch->blk == sc->blk[slot]) { sc->blk[slot] = ENVY24_SAMPLE_NUM / 2; for (i = 0; i < ENVY24_CHAN_NUM; i++) { if (sc->chan[i].dir == ch->dir && sc->chan[i].run == 1 && sc->chan[i].blk < sc->blk[slot]) sc->blk[slot] = sc->chan[i].blk; } if (ch->blk != sc->blk[slot]) envy24_updintr(sc, ch->dir); } #endif } break; } fail: snd_mtxunlock(sc->lock); return (error); } static u_int32_t envy24chan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24chan_getptr()\n"); #endif snd_mtxlock(sc->lock); ptr = envy24_gethwptr(sc, ch->dir); rtn = ptr * ch->unit; snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24chan_getptr(): return %d\n", rtn); #endif return rtn; } static struct pcmchan_caps * envy24chan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct pcmchan_caps *rtn; #if(0) device_printf(sc->dev, "envy24chan_getcaps()\n"); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (sc->run[0] == 0) rtn = &envy24_playcaps; else rtn = &sc->caps[0]; } else { if (sc->run[1] == 0) rtn = &envy24_reccaps; else rtn = &sc->caps[1]; } snd_mtxunlock(sc->lock); return rtn; } static kobj_method_t envy24chan_methods[] = { KOBJMETHOD(channel_init, envy24chan_init), KOBJMETHOD(channel_free, envy24chan_free), KOBJMETHOD(channel_setformat, envy24chan_setformat), KOBJMETHOD(channel_setspeed, envy24chan_setspeed), KOBJMETHOD(channel_setblocksize, envy24chan_setblocksize), KOBJMETHOD(channel_trigger, envy24chan_trigger), KOBJMETHOD(channel_getptr, envy24chan_getptr), KOBJMETHOD(channel_getcaps, envy24chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(envy24chan); /* -------------------------------------------------------------------- */ /* mixer interface */ static int envy24mixer_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); #if(0) device_printf(sc->dev, "envy24mixer_init()\n"); #endif if (sc == NULL) return -1; /* set volume control rate */ snd_mtxlock(sc->lock); envy24_wrmt(sc, ENVY24_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ mix_setdevs(m, ENVY24_MIX_MASK); mix_setrecdevs(m, ENVY24_MIX_REC_MASK); snd_mtxunlock(sc->lock); return 0; } static int envy24mixer_reinit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24mixer_reinit()\n"); #endif return 0; } static int envy24mixer_uninit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24mixer_uninit()\n"); #endif return 0; } static int envy24mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24_mixmap[dev]; int hwch; int i; if (sc == NULL) return -1; if (dev == 0 && sc->cfg->codec->setvolume == NULL) return -1; if (dev != 0 && ch == -1) return -1; hwch = envy24_chanmap[ch]; #if(0) device_printf(sc->dev, "envy24mixer_set(m, %d, %d, %d)\n", dev, left, right); #endif snd_mtxlock(sc->lock); if (dev == 0) { for (i = 0; i < sc->dacn; i++) { sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); } } else { /* set volume value for hardware */ if ((sc->left[hwch] = 100 - left) > ENVY24_VOL_MIN) sc->left[hwch] = ENVY24_VOL_MUTE; if ((sc->right[hwch] = 100 - right) > ENVY24_VOL_MIN) sc->right[hwch] = ENVY24_VOL_MUTE; /* set volume for record channel and running play channel */ if (hwch > ENVY24_CHAN_PLAY_SPDIF || sc->chan[ch].run) envy24_setvolume(sc, hwch); } snd_mtxunlock(sc->lock); return right << 8 | left; } static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24_mixmap[src]; #if(0) device_printf(sc->dev, "envy24mixer_setrecsrc(m, %d)\n", src); #endif if (ch > ENVY24_CHAN_PLAY_SPDIF) sc->src = ch; return src; } static kobj_method_t envy24mixer_methods[] = { KOBJMETHOD(mixer_init, envy24mixer_init), KOBJMETHOD(mixer_reinit, envy24mixer_reinit), KOBJMETHOD(mixer_uninit, envy24mixer_uninit), KOBJMETHOD(mixer_set, envy24mixer_set), KOBJMETHOD(mixer_setrecsrc, envy24mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(envy24mixer); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void envy24_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; u_int32_t ptr, dsize, feed; int i; #if(0) device_printf(sc->dev, "envy24_intr()\n"); #endif snd_mtxlock(sc->lock); if (envy24_checkintr(sc, PCMDIR_PLAY)) { #if(0) device_printf(sc->dev, "envy24_intr(): play\n"); #endif dsize = sc->psize / 4; ptr = dsize - envy24_rdmt(sc, ENVY24_MT_PCNT, 2) - 1; #if(0) device_printf(sc->dev, "envy24_intr(): ptr = %d-->", ptr); #endif ptr -= ptr % sc->blk[0]; feed = (ptr + dsize - sc->intr[0]) % dsize; #if(0) printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); #endif for (i = ENVY24_CHAN_PLAY_DAC1; i <= ENVY24_CHAN_PLAY_SPDIF; i++) { ch = &sc->chan[i]; #if(0) if (ch->run) device_printf(sc->dev, "envy24_intr(): chan[%d].blk = %d\n", i, ch->blk); #endif if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[0] = ptr; envy24_updintr(sc, PCMDIR_PLAY); } if (envy24_checkintr(sc, PCMDIR_REC)) { #if(0) device_printf(sc->dev, "envy24_intr(): rec\n"); #endif dsize = sc->rsize / 4; ptr = dsize - envy24_rdmt(sc, ENVY24_MT_RCNT, 2) - 1; ptr -= ptr % sc->blk[1]; feed = (ptr + dsize - sc->intr[1]) % dsize; for (i = ENVY24_CHAN_REC_ADC1; i <= ENVY24_CHAN_REC_SPDIF; i++) { ch = &sc->chan[i]; if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[1] = ptr; envy24_updintr(sc, PCMDIR_REC); } snd_mtxunlock(sc->lock); return; } /* * Probe and attach the card */ static int envy24_pci_probe(device_t dev) { u_int16_t sv, sd; int i; #if(0) printf("envy24_pci_probe()\n"); #endif if (pci_get_device(dev) == PCID_ENVY24 && pci_get_vendor(dev) == PCIV_ENVY24) { sv = pci_get_subvendor(dev); sd = pci_get_subdevice(dev); for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { if (cfg_table[i].subvendor == sv && cfg_table[i].subdevice == sd) { break; } } device_set_desc(dev, cfg_table[i].name); #if(0) printf("envy24_pci_probe(): return 0\n"); #endif return 0; } else { #if(0) printf("envy24_pci_probe(): return ENXIO\n"); #endif return ENXIO; } } static void envy24_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->paddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24_dmapsetmap()\n"); if (bootverbose) { printf("envy24(play): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); printf("%p -> %lx\n", sc->pmap, sc->paddr); } #endif } static void envy24_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->raddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24_dmarsetmap()\n"); if (bootverbose) { printf("envy24(record): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); printf("%p -> %lx\n", sc->rmap, sc->raddr); } #endif } static void envy24_dmafree(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24_dmafree():"); printf(" sc->raddr(0x%08x)", (u_int32_t)sc->raddr); printf(" sc->paddr(0x%08x)", (u_int32_t)sc->paddr); if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); else printf(" sc->rbuf(null)"); if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); else printf(" sc->pbuf(null)\n"); #endif #if(0) if (sc->raddr) bus_dmamap_unload(sc->dmat, sc->rmap); if (sc->paddr) bus_dmamap_unload(sc->dmat, sc->pmap); if (sc->rbuf) bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); if (sc->pbuf) bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #else bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #endif sc->raddr = sc->paddr = 0; sc->pbuf = NULL; sc->rbuf = NULL; return; } static int envy24_dmainit(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24_dmainit()\n"); #endif /* init values */ sc->psize = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM; sc->rsize = ENVY24_REC_BUFUNIT * ENVY24_SAMPLE_NUM; sc->pbuf = NULL; sc->rbuf = NULL; sc->paddr = sc->raddr = 0; sc->blk[0] = sc->blk[1] = 0; /* allocate DMA buffer */ #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) goto bad; #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) goto bad; #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->pmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24_dmapsetmap, sc, 0)) goto bad; #if(0) device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->rmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24_dmarsetmap, sc, 0)) goto bad; bzero(sc->pbuf, sc->psize); bzero(sc->rbuf, sc->rsize); /* set values to register */ #if(0) device_printf(sc->dev, "paddr(0x%08x)\n", sc->paddr); #endif envy24_wrmt(sc, ENVY24_MT_PADDR, sc->paddr, 4); #if(0) device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1); #endif envy24_wrmt(sc, ENVY24_MT_PCNT, sc->psize / 4 - 1, 2); #if(0) device_printf(sc->dev, "PCNT-->(%ld)\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); #endif envy24_wrmt(sc, ENVY24_MT_RADDR, sc->raddr, 4); envy24_wrmt(sc, ENVY24_MT_RCNT, sc->rsize / 4 - 1, 2); return 0; bad: envy24_dmafree(sc); return ENOSPC; } static void envy24_putcfg(struct sc_info *sc) { device_printf(sc->dev, "system configuration\n"); printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", sc->cfg->subvendor, sc->cfg->subdevice); printf(" XIN2 Clock Source: "); switch (sc->cfg->scfg & PCIM_SCFG_XIN2) { case 0x00: printf("22.5792MHz(44.1kHz*512)\n"); break; case 0x40: printf("16.9344MHz(44.1kHz*384)\n"); break; case 0x80: printf("from external clock synthesizer chip\n"); break; default: printf("illeagal system setting\n"); } printf(" MPU-401 UART(s) #: "); if (sc->cfg->scfg & PCIM_SCFG_MPU) printf("2\n"); else printf("1\n"); printf(" AC'97 codec: "); if (sc->cfg->scfg & PCIM_SCFG_AC97) printf("not exist\n"); else printf("exist\n"); printf(" ADC #: "); printf("%d\n", sc->adcn); printf(" DAC #: "); printf("%d\n", sc->dacn); printf(" Multi-track converter type: "); if ((sc->cfg->acl & PCIM_ACL_MTC) == 0) { printf("AC'97(SDATA_OUT:"); if (sc->cfg->acl & PCIM_ACL_OMODE) printf("packed"); else printf("split"); printf("|SDATA_IN:"); if (sc->cfg->acl & PCIM_ACL_IMODE) printf("packed"); else printf("split"); printf(")\n"); } else { printf("I2S("); if (sc->cfg->i2s & PCIM_I2S_VOL) printf("with volume, "); if (sc->cfg->i2s & PCIM_I2S_96KHZ) printf("96KHz support, "); switch (sc->cfg->i2s & PCIM_I2S_RES) { case PCIM_I2S_16BIT: printf("16bit resolution, "); break; case PCIM_I2S_18BIT: printf("18bit resolution, "); break; case PCIM_I2S_20BIT: printf("20bit resolution, "); break; case PCIM_I2S_24BIT: printf("24bit resolution, "); break; } printf("ID#0x%x)\n", sc->cfg->i2s & PCIM_I2S_ID); } printf(" S/PDIF(IN/OUT): "); if (sc->cfg->spdif & PCIM_SPDIF_IN) printf("1/"); else printf("0/"); if (sc->cfg->spdif & PCIM_SPDIF_OUT) printf("1 "); else printf("0 "); if (sc->cfg->spdif & (PCIM_SPDIF_IN | PCIM_SPDIF_OUT)) printf("ID# 0x%02x\n", (sc->cfg->spdif & PCIM_SPDIF_ID) >> 2); printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); } static int envy24_init(struct sc_info *sc) { u_int32_t data; #if(0) int rtn; #endif int i; u_int32_t sv, sd; #if(0) device_printf(sc->dev, "envy24_init()\n"); #endif /* reset chip */ envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_RESET | ENVY24_CCS_CTL_NATIVE, 1); DELAY(200); envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_NATIVE, 1); DELAY(200); /* legacy hardware disable */ data = pci_read_config(sc->dev, PCIR_LAC, 2); data |= PCIM_LAC_DISABLE; pci_write_config(sc->dev, PCIR_LAC, data, 2); /* check system configuration */ sc->cfg = NULL; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { /* 1st: search configuration from table */ sv = pci_get_subvendor(sc->dev); sd = pci_get_subdevice(sc->dev); if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { #if(0) device_printf(sc->dev, "Set configuration from table\n"); #endif sc->cfg = &cfg_table[i]; break; } } if (sc->cfg == NULL) { /* 2nd: read configuration from table */ sc->cfg = envy24_rom2cfg(sc); } sc->adcn = ((sc->cfg->scfg & PCIM_SCFG_ADC) >> 2) + 1; sc->dacn = (sc->cfg->scfg & PCIM_SCFG_DAC) + 1; if (1 /* bootverbose */) { envy24_putcfg(sc); } /* set system configuration */ pci_write_config(sc->dev, PCIR_SCFG, sc->cfg->scfg, 1); pci_write_config(sc->dev, PCIR_ACL, sc->cfg->acl, 1); pci_write_config(sc->dev, PCIR_I2S, sc->cfg->i2s, 1); pci_write_config(sc->dev, PCIR_SPDIF, sc->cfg->spdif, 1); envy24_gpiosetmask(sc, sc->cfg->gpiomask); envy24_gpiosetdir(sc, sc->cfg->gpiodir); envy24_gpiowr(sc, sc->cfg->gpiostate); for (i = 0; i < sc->adcn; i++) { sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); sc->cfg->codec->init(sc->adc[i]); } for (i = 0; i < sc->dacn; i++) { sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); sc->cfg->codec->init(sc->dac[i]); } /* initialize DMA buffer */ #if(0) device_printf(sc->dev, "envy24_init(): initialize DMA buffer\n"); #endif if (envy24_dmainit(sc)) return ENOSPC; /* initialize status */ sc->run[0] = sc->run[1] = 0; sc->intr[0] = sc->intr[1] = 0; sc->speed = 0; sc->caps[0].fmtlist = envy24_playfmt; sc->caps[1].fmtlist = envy24_recfmt; /* set channel router */ envy24_route(sc, ENVY24_ROUTE_DAC_1, ENVY24_ROUTE_CLASS_MIX, 0, 0); envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_DMA, 0, 0); /* envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_MIX, 0, 0); */ /* set macro interrupt mask */ data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); envy24_wrcs(sc, ENVY24_CCS_IMASK, data & ~ENVY24_CCS_IMASK_PMT, 1); data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); #if(0) device_printf(sc->dev, "envy24_init(): CCS_IMASK-->0x%02x\n", data); #endif return 0; } static int envy24_alloc_resource(struct sc_info *sc) { /* allocate I/O port resource */ sc->csid = PCIR_CCS; sc->cs = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, &sc->csid, 0, ~0, 1, RF_ACTIVE); sc->ddmaid = PCIR_DDMA; sc->ddma = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, &sc->ddmaid, 0, ~0, 1, RF_ACTIVE); sc->dsid = PCIR_DS; sc->ds = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, &sc->dsid, 0, ~0, 1, RF_ACTIVE); sc->mtid = PCIR_MT; sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, &sc->mtid, 0, ~0, 1, RF_ACTIVE); if (!sc->cs || !sc->ddma || !sc->ds || !sc->mt) { device_printf(sc->dev, "unable to map IO port space\n"); return ENXIO; } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); sc->ddmat = rman_get_bustag(sc->ddma); sc->ddmah = rman_get_bushandle(sc->ddma); sc->dst = rman_get_bustag(sc->ds); sc->dsh = rman_get_bushandle(sc->ds); sc->mtt = rman_get_bustag(sc->mt); sc->mth = rman_get_bushandle(sc->mt); #if(0) device_printf(sc->dev, "IO port register values\nCCS: 0x%lx\nDDMA: 0x%lx\nDS: 0x%lx\nMT: 0x%lx\n", pci_read_config(sc->dev, PCIR_CCS, 4), pci_read_config(sc->dev, PCIR_DDMA, 4), pci_read_config(sc->dev, PCIR_DS, 4), pci_read_config(sc->dev, PCIR_MT, 4)); #endif /* allocate interrupt resource */ sc->irqid = 0; sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24_intr, sc, &sc->ih)) { device_printf(sc->dev, "unable to map interrupt\n"); return ENXIO; } /* allocate DMA resource */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_ENVY24, /*highaddr*/BUS_SPACE_MAXADDR_ENVY24, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, /*nsegments*/1, /*maxsegsz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sc->dmat) != 0) { device_printf(sc->dev, "unable to create dma tag\n"); return ENXIO; } return 0; } static int envy24_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; int err = 0; int i; #if(0) device_printf(dev, "envy24_pci_attach()\n"); #endif /* get sc_info data area */ if ((sc = malloc(sizeof(*sc), M_ENVY24, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_envy24 softc"); sc->dev = dev; /* initialize PCI interface */ pci_enable_busmaster(dev); /* allocate resources */ err = envy24_alloc_resource(sc); if (err) { device_printf(dev, "unable to allocate system resources\n"); goto bad; } /* initialize card */ err = envy24_init(sc); if (err) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* set multi track mixer */ mixer_init(dev, &envy24mixer_class, sc); /* set channel information */ err = pcm_register(dev, sc, 5, 2 + sc->adcn); if (err) goto bad; sc->chnum = 0; for (i = 0; i < 5; i++) { pcm_addchan(dev, PCMDIR_PLAY, &envy24chan_class, sc); sc->chnum++; } for (i = 0; i < 2 + sc->adcn; i++) { pcm_addchan(dev, PCMDIR_REC, &envy24chan_class, sc); sc->chnum++; } /* set status iformation */ snprintf(status, SND_STATUSLEN, "at io 0x%lx:%ld,0x%lx:%ld,0x%lx:%ld,0x%lx:%ld irq %ld", rman_get_start(sc->cs), rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, rman_get_start(sc->ddma), rman_get_end(sc->ddma) - rman_get_start(sc->ddma) + 1, rman_get_start(sc->ds), rman_get_end(sc->ds) - rman_get_start(sc->ds) + 1, rman_get_start(sc->mt), rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, rman_get_start(sc->irq)); pcm_setstatus(dev, status); return 0; bad: if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); envy24_dmafree(sc); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24_cfgfree(sc->cfg); if (sc->cs) bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); if (sc->ddma) bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); if (sc->ds) bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); if (sc->mt) bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_ENVY24); return err; } static int envy24_pci_detach(device_t dev) { struct sc_info *sc; int r; int i; #if(0) device_printf(dev, "envy24_pci_detach()\n"); #endif sc = pcm_getdevinfo(dev); if (sc == NULL) return 0; r = pcm_unregister(dev); if (r) return r; envy24_dmafree(sc); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24_cfgfree(sc->cfg); bus_dma_tag_destroy(sc->dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); snd_mtxfree(sc->lock); free(sc, M_ENVY24); return 0; } static device_method_t envy24_methods[] = { /* Device interface */ DEVMETHOD(device_probe, envy24_pci_probe), DEVMETHOD(device_attach, envy24_pci_attach), DEVMETHOD(device_detach, envy24_pci_detach), { 0, 0 } }; static driver_t envy24_driver = { "pcm", envy24_methods, -#if __FreeBSD_version > 500000 PCM_SOFTC_SIZE, -#else - sizeof(struct snddev_info), -#endif }; DRIVER_MODULE(snd_envy24, pci, envy24_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_envy24, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_envy24, snd_spicds, 1, 1, 1); MODULE_VERSION(snd_envy24, 1); Index: head/sys/dev/sound/pci/envy24ht.c =================================================================== --- head/sys/dev/sound/pci/envy24ht.c (revision 274034) +++ head/sys/dev/sound/pci/envy24ht.c (revision 274035) @@ -1,2601 +1,2597 @@ /*- * Copyright (c) 2006 Konstantin Dimitrov * Copyright (c) 2001 Katsurajima Naoto * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* * Konstantin Dimitrov's thanks list: * * A huge thanks goes to Spas Filipov for his friendship, support and his * generous gift - an 'Audiotrak Prodigy HD2' audio card! I also want to * thank Keiichi Iwasaki and his parents, because they helped Spas to get * the card from Japan! Having hardware sample of Prodigy HD2 made adding * support for that great card very easy and real fun and pleasure. * */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); static MALLOC_DEFINE(M_ENVY24HT, "envy24ht", "envy24ht audio"); /* -------------------------------------------------------------------- */ struct sc_info; #define ENVY24HT_PLAY_CHNUM 8 #define ENVY24HT_REC_CHNUM 2 #define ENVY24HT_PLAY_BUFUNIT (4 /* byte/sample */ * 8 /* channel */) #define ENVY24HT_REC_BUFUNIT (4 /* byte/sample */ * 2 /* channel */) #define ENVY24HT_SAMPLE_NUM 4096 #define ENVY24HT_TIMEOUT 1000 #define ENVY24HT_DEFAULT_FORMAT SND_FORMAT(AFMT_S16_LE, 2, 0) #define ENVY24HT_NAMELEN 32 struct envy24ht_sample { volatile u_int32_t buffer; }; typedef struct envy24ht_sample sample32_t; /* channel registers */ struct sc_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; int dir; unsigned num; /* hw channel number */ /* channel information */ u_int32_t format; u_int32_t speed; u_int32_t blk; /* hw block size(dword) */ /* format conversion structure */ u_int8_t *data; unsigned int size; /* data buffer size(byte) */ int unit; /* sample size(byte) */ unsigned int offset; /* samples number offset */ void (*emldma)(struct sc_chinfo *); /* flags */ int run; }; /* codec interface entrys */ struct codec_entry { void *(*create)(device_t dev, void *devinfo, int dir, int num); void (*destroy)(void *codec); void (*init)(void *codec); void (*reinit)(void *codec); void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); void (*setrate)(void *codec, int which, int rate); }; /* system configuration information */ struct cfg_info { char *name; u_int16_t subvendor, subdevice; u_int8_t scfg, acl, i2s, spdif; u_int32_t gpiomask, gpiostate, gpiodir; u_int32_t cdti, cclk, cs; u_int8_t cif, type, free; struct codec_entry *codec; }; /* device private data */ struct sc_info { device_t dev; struct mtx *lock; /* Control/Status registor */ struct resource *cs; int csid; bus_space_tag_t cst; bus_space_handle_t csh; /* MultiTrack registor */ struct resource *mt; int mtid; bus_space_tag_t mtt; bus_space_handle_t mth; /* DMA tag */ bus_dma_tag_t dmat; /* IRQ resource */ struct resource *irq; int irqid; void *ih; /* system configuration data */ struct cfg_info *cfg; /* ADC/DAC number and info */ int adcn, dacn; void *adc[4], *dac[4]; /* mixer control data */ u_int32_t src; u_int8_t left[ENVY24HT_CHAN_NUM]; u_int8_t right[ENVY24HT_CHAN_NUM]; /* Play/Record DMA fifo */ sample32_t *pbuf; sample32_t *rbuf; u_int32_t psize, rsize; /* DMA buffer size(byte) */ u_int16_t blk[2]; /* transfer check blocksize(dword) */ bus_dmamap_t pmap, rmap; bus_addr_t paddr, raddr; /* current status */ u_int32_t speed; int run[2]; u_int16_t intr[2]; struct pcmchan_caps caps[2]; /* channel info table */ unsigned chnum; struct sc_chinfo chan[11]; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* DMA emulator */ static void envy24ht_p8u(struct sc_chinfo *); static void envy24ht_p16sl(struct sc_chinfo *); static void envy24ht_p32sl(struct sc_chinfo *); static void envy24ht_r16sl(struct sc_chinfo *); static void envy24ht_r32sl(struct sc_chinfo *); /* channel interface */ static void *envy24htchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int envy24htchan_setformat(kobj_t, void *, u_int32_t); static u_int32_t envy24htchan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t envy24htchan_setblocksize(kobj_t, void *, u_int32_t); static int envy24htchan_trigger(kobj_t, void *, int); static u_int32_t envy24htchan_getptr(kobj_t, void *); static struct pcmchan_caps *envy24htchan_getcaps(kobj_t, void *); /* mixer interface */ static int envy24htmixer_init(struct snd_mixer *); static int envy24htmixer_reinit(struct snd_mixer *); static int envy24htmixer_uninit(struct snd_mixer *); static int envy24htmixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); static u_int32_t envy24htmixer_setrecsrc(struct snd_mixer *, u_int32_t); /* SPI codec access interface */ static void *envy24ht_spi_create(device_t, void *, int, int); static void envy24ht_spi_destroy(void *); static void envy24ht_spi_init(void *); static void envy24ht_spi_reinit(void *); static void envy24ht_spi_setvolume(void *, int, unsigned int, unsigned int); /* -------------------------------------------------------------------- */ /* system constant tables */ /* API -> hardware channel map */ static unsigned envy24ht_chanmap[ENVY24HT_CHAN_NUM] = { ENVY24HT_CHAN_PLAY_DAC1, /* 1 */ ENVY24HT_CHAN_PLAY_DAC2, /* 2 */ ENVY24HT_CHAN_PLAY_DAC3, /* 3 */ ENVY24HT_CHAN_PLAY_DAC4, /* 4 */ ENVY24HT_CHAN_PLAY_SPDIF, /* 0 */ ENVY24HT_CHAN_REC_MIX, /* 5 */ ENVY24HT_CHAN_REC_SPDIF, /* 6 */ ENVY24HT_CHAN_REC_ADC1, /* 7 */ ENVY24HT_CHAN_REC_ADC2, /* 8 */ ENVY24HT_CHAN_REC_ADC3, /* 9 */ ENVY24HT_CHAN_REC_ADC4, /* 10 */ }; /* mixer -> API channel map. see above */ static int envy24ht_mixmap[] = { -1, /* Master output level. It is depend on codec support */ -1, /* Treble level of all output channels */ -1, /* Bass level of all output channels */ -1, /* Volume of synthesier input */ 0, /* Output level for the audio device */ -1, /* Output level for the PC speaker */ 7, /* line in jack */ -1, /* microphone jack */ -1, /* CD audio input */ -1, /* Recording monitor */ 1, /* alternative codec */ -1, /* global recording level */ -1, /* Input gain */ -1, /* Output gain */ 8, /* Input source 1 */ 9, /* Input source 2 */ 10, /* Input source 3 */ 6, /* Digital (input) 1 */ -1, /* Digital (input) 2 */ -1, /* Digital (input) 3 */ -1, /* Phone input */ -1, /* Phone output */ -1, /* Video/TV (audio) in */ -1, /* Radio in */ -1, /* Monitor volume */ }; /* variable rate audio */ static u_int32_t envy24ht_speed[] = { 192000, 176400, 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000, 0 }; /* known boards configuration */ static struct codec_entry spi_codec = { envy24ht_spi_create, envy24ht_spi_destroy, envy24ht_spi_init, envy24ht_spi_reinit, envy24ht_spi_setvolume, NULL, /* setrate */ }; static struct cfg_info cfg_table[] = { { "Envy24HT audio (Terratec Aureon 7.1 Space)", 0x153b, 0x1145, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec Aureon 5.1 Sky)", 0x153b, 0x1147, 0x0a, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec Aureon 7.1 Universe)", 0x153b, 0x1153, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1)", 0x4933, 0x4553, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec PHASE 28)", 0x153b, 0x1149, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT-S audio (Terratec PHASE 22)", 0x153b, 0x1150, 0x10, 0x80, 0xf0, 0xc3, 0x7ffbc7, 0x7fffff, 0x438, 0x10, 0x20, 0x400, 0x01, 0x00, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1 LT)", 0x3132, 0x4154, 0x4b, 0x80, 0xfc, 0xc3, 0x7ff8ff, 0x7fffff, 0x700, 0x400, 0x200, 0x100, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1 XT)", 0x3136, 0x4154, 0x4b, 0x80, 0xfc, 0xc3, 0x7ff8ff, 0x7fffff, 0x700, 0x400, 0x200, 0x100, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (M-Audio Revolution 7.1)", 0x1412, 0x3630, 0x43, 0x80, 0xf8, 0xc1, 0x3fff85, 0x400072, 0x4000fa, 0x08, 0x02, 0x20, 0x00, 0x04, 0, &spi_codec, }, { "Envy24GT audio (M-Audio Revolution 5.1)", 0x1412, 0x3631, 0x42, 0x80, 0xf8, 0xc1, 0x3fff05, 0x4000f0, 0x4000fa, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (M-Audio Audiophile 192)", 0x1412, 0x3632, 0x68, 0x80, 0xf8, 0xc3, 0x45, 0x4000b5, 0x7fffba, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy HD2)", 0x3137, 0x4154, 0x68, 0x80, 0x78, 0xc3, 0xfff8ff, 0x200700, 0xdfffff, 0x400, 0x200, 0x100, 0x00, 0x05, 0, &spi_codec, }, { "Envy24HT audio (ESI Juli@)", 0x3031, 0x4553, 0x20, 0x80, 0xf8, 0xc3, 0x7fff9f, 0x8016, 0x7fff9f, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT-S audio (Terrasoniq TS22PCI)", 0x153b, 0x117b, 0x10, 0x80, 0xf0, 0xc3, 0x7ffbc7, 0x7fffff, 0x438, 0x10, 0x20, 0x400, 0x01, 0x00, 0, &spi_codec, }, { "Envy24HT audio (Generic)", 0, 0, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, /* default codec routines */ } }; static u_int32_t envy24ht_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24ht_reccaps = {8000, 96000, envy24ht_recfmt, 0}; static u_int32_t envy24ht_playfmt[] = { SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps envy24ht_playcaps = {8000, 192000, envy24ht_playfmt, 0}; struct envy24ht_emldma { u_int32_t format; void (*emldma)(struct sc_chinfo *); int unit; }; static struct envy24ht_emldma envy24ht_pemltab[] = { {SND_FORMAT(AFMT_U8, 2, 0), envy24ht_p8u, 2}, {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24ht_p16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24ht_p32sl, 8}, {0, NULL, 0} }; static struct envy24ht_emldma envy24ht_remltab[] = { {SND_FORMAT(AFMT_S16_LE, 2, 0), envy24ht_r16sl, 4}, {SND_FORMAT(AFMT_S32_LE, 2, 0), envy24ht_r32sl, 8}, {0, NULL, 0} }; /* -------------------------------------------------------------------- */ /* common routines */ static u_int32_t envy24ht_rdcs(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->cst, sc->csh, regno); case 2: return bus_space_read_2(sc->cst, sc->csh, regno); case 4: return bus_space_read_4(sc->cst, sc->csh, regno); default: return 0xffffffff; } } static void envy24ht_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->cst, sc->csh, regno, data); break; case 2: bus_space_write_2(sc->cst, sc->csh, regno, data); break; case 4: bus_space_write_4(sc->cst, sc->csh, regno, data); break; } } static u_int32_t envy24ht_rdmt(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->mtt, sc->mth, regno); case 2: return bus_space_read_2(sc->mtt, sc->mth, regno); case 4: return bus_space_read_4(sc->mtt, sc->mth, regno); default: return 0xffffffff; } } static void envy24ht_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->mtt, sc->mth, regno, data); break; case 2: bus_space_write_2(sc->mtt, sc->mth, regno, data); break; case 4: bus_space_write_4(sc->mtt, sc->mth, regno, data); break; } } /* -------------------------------------------------------------------- */ /* I2C port/E2PROM access routines */ static int envy24ht_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_RD, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CDATA, 1); #if(0) device_printf(sc->dev, "envy24ht_rdi2c(): return 0x%x\n", data); #endif return (int)data; } static int envy24ht_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) { u_int32_t tmp; int i; #if(0) device_printf(sc->dev, "envy24ht_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); #endif for (i = 0; i < ENVY24HT_TIMEOUT; i++) { tmp = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((tmp & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDATA, data, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_WR, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return -1; } return 0; } static int envy24ht_rdrom(struct sc_info *sc, u_int32_t addr) { u_int32_t data; #if(0) device_printf(sc->dev, "envy24ht_rdrom(sc, 0x%02x)\n", addr); #endif data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_ROM) == 0) { #if(0) device_printf(sc->dev, "envy24ht_rdrom(): E2PROM not presented\n"); #endif return -1; } return envy24ht_rdi2c(sc, ENVY24HT_CCS_I2CDEV_ROM, addr); } static struct cfg_info * envy24ht_rom2cfg(struct sc_info *sc) { struct cfg_info *buff; int size; int i; #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(sc)\n"); #endif size = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SIZE); if ((size < ENVY24HT_E2PROM_GPIOSTATE + 3) || (size == 0x78)) { #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(): ENVY24HT_E2PROM_SIZE-->%d\n", size); #endif buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) { #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(): malloc()\n"); #endif return NULL; } buff->free = 1; /* no valid e2prom, using default values */ buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); buff->scfg = 0x0b; buff->acl = 0x80; buff->i2s = 0xfc; buff->spdif = 0xc3; buff->gpiomask = 0x21efff; buff->gpiostate = 0x7fffff; buff->gpiodir = 0x5e1000; buff->cdti = 0x40000; buff->cclk = 0x80000; buff->cs = 0x1000; buff->cif = 0x00; buff->type = 0x02; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) if (cfg_table[i].subvendor == buff->subvendor && cfg_table[i].subdevice == buff->subdevice) break; buff->name = cfg_table[i].name; buff->codec = cfg_table[i].codec; return buff; #if 0 return NULL; #endif } buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) { #if(0) device_printf(sc->dev, "envy24ht_rom2cfg(): malloc()\n"); #endif return NULL; } buff->free = 1; buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); buff->scfg = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SCFG); buff->acl = envy24ht_rdrom(sc, ENVY24HT_E2PROM_ACL); buff->i2s = envy24ht_rdrom(sc, ENVY24HT_E2PROM_I2S); buff->spdif = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SPDIF); buff->gpiomask = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK) | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 1) << 8 | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 2) << 16; buff->gpiostate = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE) | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 1) << 8 | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 2) << 16; buff->gpiodir = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR) | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 1) << 8 | \ envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 2) << 16; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) if (cfg_table[i].subvendor == buff->subvendor && cfg_table[i].subdevice == buff->subdevice) break; buff->name = cfg_table[i].name; buff->codec = cfg_table[i].codec; return buff; } static void envy24ht_cfgfree(struct cfg_info *cfg) { if (cfg == NULL) return; if (cfg->free) free(cfg, M_ENVY24HT); return; } /* -------------------------------------------------------------------- */ /* AC'97 codec access routines */ #if 0 static int envy24ht_coldcd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_coldcd()\n"); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD, 1); DELAY(10); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if (data & ENVY24HT_MT_AC97CMD_RDY) { return 0; } } return -1; } static int envy24ht_slavecd(struct sc_info *sc) { u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_slavecd()\n"); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD | ENVY24HT_MT_AC97CMD_WRM, 1); DELAY(10); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if (data & ENVY24HT_MT_AC97CMD_RDY) { return 0; } } return -1; } static int envy24ht_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; int i; #if(0) device_printf(sc->dev, "envy24ht_rdcd(obj, sc, 0x%02x)\n", regno); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (u_int32_t)regno, 1); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_RD, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if ((data & ENVY24HT_MT_AC97CMD_RD) == 0) break; } data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97DLO, 2); #if(0) device_printf(sc->dev, "envy24ht_rdcd(): return 0x%x\n", data); #endif return (int)data; } static int envy24ht_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t cmd; int i; #if(0) device_printf(sc->dev, "envy24ht_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); #endif envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (u_int32_t)regno, 1); envy24ht_wrmt(sc, ENVY24HT_MT_AC97DLO, (u_int32_t)data, 2); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_WR, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { cmd = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if ((cmd & ENVY24HT_MT_AC97CMD_WR) == 0) break; } return 0; } static kobj_method_t envy24ht_ac97_methods[] = { KOBJMETHOD(ac97_read, envy24ht_rdcd), KOBJMETHOD(ac97_write, envy24ht_wrcd), KOBJMETHOD_END }; AC97_DECLARE(envy24ht_ac97); #endif /* -------------------------------------------------------------------- */ /* GPIO access routines */ static u_int32_t envy24ht_gpiord(struct sc_info *sc) { if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) return envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2); else return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HDATA, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2)); } static void envy24ht_gpiowr(struct sc_info *sc, u_int32_t data) { #if(0) device_printf(sc->dev, "envy24ht_gpiowr(sc, 0x%02x)\n", data & 0x7FFFFF); return; #endif envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LDATA, data, 2); if (sc->cfg->subdevice != 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HDATA, data >> 16, 1); return; } #if 0 static u_int32_t envy24ht_gpiogetmask(struct sc_info *sc) { return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HMASK, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LMASK, 2)); } #endif static void envy24ht_gpiosetmask(struct sc_info *sc, u_int32_t mask) { envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LMASK, mask, 2); if (sc->cfg->subdevice != 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HMASK, mask >> 16, 1); return; } #if 0 static u_int32_t envy24ht_gpiogetdir(struct sc_info *sc) { return envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, 4); } #endif static void envy24ht_gpiosetdir(struct sc_info *sc, u_int32_t dir) { if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 2); else envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 4); return; } /* -------------------------------------------------------------------- */ /* SPI codec access interface routine */ struct envy24ht_spi_codec { struct spicds_info *info; struct sc_info *parent; int dir; int num; int cs, cclk, cdti; }; static void envy24ht_spi_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) { u_int32_t data = 0; struct envy24ht_spi_codec *ptr = codec; #if(0) device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); #endif data = envy24ht_gpiord(ptr->parent); data &= ~(ptr->cs | ptr->cclk | ptr->cdti); if (cs) data += ptr->cs; if (cclk) data += ptr->cclk; if (cdti) data += ptr->cdti; envy24ht_gpiowr(ptr->parent, data); return; } static void * envy24ht_spi_create(device_t dev, void *info, int dir, int num) { struct sc_info *sc = info; struct envy24ht_spi_codec *buff = NULL; #if(0) device_printf(sc->dev, "envy24ht_spi_create(dev, sc, %d, %d)\n", dir, num); #endif buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) return NULL; if (dir == PCMDIR_REC && sc->adc[num] != NULL) buff->info = ((struct envy24ht_spi_codec *)sc->adc[num])->info; else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) buff->info = ((struct envy24ht_spi_codec *)sc->dac[num])->info; else buff->info = spicds_create(dev, buff, num, envy24ht_spi_ctl); if (buff->info == NULL) { free(buff, M_ENVY24HT); return NULL; } buff->parent = sc; buff->dir = dir; buff->num = num; return (void *)buff; } static void envy24ht_spi_destroy(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spi_destroy()\n"); #endif if (ptr->dir == PCMDIR_PLAY) { if (ptr->parent->dac[ptr->num] != NULL) spicds_destroy(ptr->info); } else { if (ptr->parent->adc[ptr->num] != NULL) spicds_destroy(ptr->info); } free(codec, M_ENVY24HT); } static void envy24ht_spi_init(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spicds_init()\n"); #endif ptr->cs = ptr->parent->cfg->cs; ptr->cclk = ptr->parent->cfg->cclk; ptr->cdti = ptr->parent->cfg->cdti; spicds_settype(ptr->info, ptr->parent->cfg->type); spicds_setcif(ptr->info, ptr->parent->cfg->cif); if (ptr->parent->cfg->type == SPICDS_TYPE_AK4524 || \ ptr->parent->cfg->type == SPICDS_TYPE_AK4528) { spicds_setformat(ptr->info, AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); spicds_setdvc(ptr->info, AK452X_DVC_DEMOFF); } /* for the time being, init only first codec */ if (ptr->num == 0) spicds_init(ptr->info); } static void envy24ht_spi_reinit(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spi_reinit()\n"); #endif spicds_reinit(ptr->info); } static void envy24ht_spi_setvolume(void *codec, int dir, unsigned int left, unsigned int right) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; #if(0) device_printf(ptr->parent->dev, "envy24ht_spi_set()\n"); #endif spicds_set(ptr->info, dir, left, right); } /* -------------------------------------------------------------------- */ /* hardware access routeines */ static struct { u_int32_t speed; u_int32_t code; } envy24ht_speedtab[] = { {48000, ENVY24HT_MT_RATE_48000}, {24000, ENVY24HT_MT_RATE_24000}, {12000, ENVY24HT_MT_RATE_12000}, {9600, ENVY24HT_MT_RATE_9600}, {32000, ENVY24HT_MT_RATE_32000}, {16000, ENVY24HT_MT_RATE_16000}, {8000, ENVY24HT_MT_RATE_8000}, {96000, ENVY24HT_MT_RATE_96000}, {192000, ENVY24HT_MT_RATE_192000}, {64000, ENVY24HT_MT_RATE_64000}, {44100, ENVY24HT_MT_RATE_44100}, {22050, ENVY24HT_MT_RATE_22050}, {11025, ENVY24HT_MT_RATE_11025}, {88200, ENVY24HT_MT_RATE_88200}, {176400, ENVY24HT_MT_RATE_176400}, {0, 0x10} }; static u_int32_t envy24ht_setspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t code, i2sfmt; int i = 0; #if(0) device_printf(sc->dev, "envy24ht_setspeed(sc, %d)\n", speed); if (speed == 0) { code = ENVY24HT_MT_RATE_SPDIF; /* external master clock */ envy24ht_slavecd(sc); } else { #endif for (i = 0; envy24ht_speedtab[i].speed != 0; i++) { if (envy24ht_speedtab[i].speed == speed) break; } code = envy24ht_speedtab[i].code; #if 0 } device_printf(sc->dev, "envy24ht_setspeed(): speed %d/code 0x%04x\n", envy24ht_speedtab[i].speed, code); #endif if (code < 0x10) { envy24ht_wrmt(sc, ENVY24HT_MT_RATE, code, 1); if ((((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_XIN2) == 0x00) && (code == ENVY24HT_MT_RATE_192000)) || \ (code == ENVY24HT_MT_RATE_176400)) { i2sfmt = envy24ht_rdmt(sc, ENVY24HT_MT_I2S, 1); i2sfmt |= ENVY24HT_MT_I2S_MLR128; envy24ht_wrmt(sc, ENVY24HT_MT_I2S, i2sfmt, 1); } else { i2sfmt = envy24ht_rdmt(sc, ENVY24HT_MT_I2S, 1); i2sfmt &= ~ENVY24HT_MT_I2S_MLR128; envy24ht_wrmt(sc, ENVY24HT_MT_I2S, i2sfmt, 1); } code = envy24ht_rdmt(sc, ENVY24HT_MT_RATE, 1); code &= ENVY24HT_MT_RATE_MASK; for (i = 0; envy24ht_speedtab[i].code < 0x10; i++) { if (envy24ht_speedtab[i].code == code) break; } speed = envy24ht_speedtab[i].speed; } else speed = 0; #if(0) device_printf(sc->dev, "envy24ht_setspeed(): return %d\n", speed); #endif return speed; } static void envy24ht_setvolume(struct sc_info *sc, unsigned ch) { #if(0) device_printf(sc->dev, "envy24ht_setvolume(sc, %d)\n", ch); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, 0x7f00 | sc->left[ch], 2); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); #endif } static void envy24ht_mutevolume(struct sc_info *sc, unsigned ch) { #if 0 u_int32_t vol; device_printf(sc->dev, "envy24ht_mutevolume(sc, %d)\n", ch); vol = ENVY24HT_VOL_MUTE << 8 | ENVY24HT_VOL_MUTE; envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); #endif } static u_int32_t envy24ht_gethwptr(struct sc_info *sc, int dir) { int unit, regno; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24ht_gethwptr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { rtn = sc->psize / 4; unit = ENVY24HT_PLAY_BUFUNIT / 4; regno = ENVY24HT_MT_PCNT; } else { rtn = sc->rsize / 4; unit = ENVY24HT_REC_BUFUNIT / 4; regno = ENVY24HT_MT_RCNT; } ptr = envy24ht_rdmt(sc, regno, 2); rtn -= (ptr + 1); rtn /= unit; #if(0) device_printf(sc->dev, "envy24ht_gethwptr(): return %d\n", rtn); #endif return rtn; } static void envy24ht_updintr(struct sc_info *sc, int dir) { int regptr, regintr; u_int32_t mask, intr; u_int32_t ptr, size, cnt; u_int16_t blk; #if(0) device_printf(sc->dev, "envy24ht_updintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) { blk = sc->blk[0]; size = sc->psize / 4; regptr = ENVY24HT_MT_PCNT; regintr = ENVY24HT_MT_PTERM; mask = ~ENVY24HT_MT_INT_PMASK; } else { blk = sc->blk[1]; size = sc->rsize / 4; regptr = ENVY24HT_MT_RCNT; regintr = ENVY24HT_MT_RTERM; mask = ~ENVY24HT_MT_INT_RMASK; } ptr = size - envy24ht_rdmt(sc, regptr, 2) - 1; /* cnt = blk - ptr % blk - 1; if (cnt == 0) cnt = blk - 1; */ cnt = blk - 1; #if(0) device_printf(sc->dev, "envy24ht_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt); #endif envy24ht_wrmt(sc, regintr, cnt, 2); intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); #if(0) device_printf(sc->dev, "envy24ht_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); #endif envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, intr & mask, 1); #if(0) device_printf(sc->dev, "envy24ht_updintr():INT-->0x%02x\n", envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1)); #endif return; } #if 0 static void envy24ht_maskintr(struct sc_info *sc, int dir) { u_int32_t mask, intr; #if(0) device_printf(sc->dev, "envy24ht_maskintr(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) mask = ENVY24HT_MT_INT_PMASK; else mask = ENVY24HT_MT_INT_RMASK; intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT, intr | mask, 1); return; } #endif static int envy24ht_checkintr(struct sc_info *sc, int dir) { u_int32_t mask, stat, intr, rtn; #if(0) device_printf(sc->dev, "envy24ht_checkintr(sc, %d)\n", dir); #endif intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_STAT, 1); if (dir == PCMDIR_PLAY) { if ((rtn = intr & ENVY24HT_MT_INT_PSTAT) != 0) { mask = ~ENVY24HT_MT_INT_RSTAT; envy24ht_wrmt(sc, 0x1a, 0x01, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_PSTAT | 0x08, 1); stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_PMASK, 1); } } else { if ((rtn = intr & ENVY24HT_MT_INT_RSTAT) != 0) { mask = ~ENVY24HT_MT_INT_PSTAT; #if 0 stat = ENVY24HT_MT_INT_RSTAT | ENVY24HT_MT_INT_RMASK; #endif envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_RSTAT, 1); stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_RMASK, 1); } } return rtn; } static void envy24ht_start(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24ht_start(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ENVY24HT_MT_PCTL_PSTART; else sw = ENVY24HT_MT_PCTL_RSTART; stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat | sw, 1); #if(0) DELAY(100); device_printf(sc->dev, "PADDR:0x%08x\n", envy24ht_rdmt(sc, ENVY24HT_MT_PADDR, 4)); device_printf(sc->dev, "PCNT:%ld\n", envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2)); #endif return; } static void envy24ht_stop(struct sc_info *sc, int dir) { u_int32_t stat, sw; #if(0) device_printf(sc->dev, "envy24ht_stop(sc, %d)\n", dir); #endif if (dir == PCMDIR_PLAY) sw = ~ENVY24HT_MT_PCTL_PSTART; else sw = ~ENVY24HT_MT_PCTL_RSTART; stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat & sw, 1); return; } #if 0 static int envy24ht_route(struct sc_info *sc, int dac, int class, int adc, int rev) { return 0; } #endif /* -------------------------------------------------------------------- */ /* buffer copy routines */ static void envy24ht_p32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 8; dmabuf = ch->parent->pbuf; data = (u_int32_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 4; dst = src / 2 + ch->offset; ssize = ch->size / 4; dsize = ch->size / 8; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = data[src]; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24ht_p16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; #if(0) device_printf(ch->parent->dev, "envy24ht_p16sl()\n"); #endif length = sndbuf_getready(ch->buffer) / 4; dmabuf = ch->parent->pbuf; data = (u_int16_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer) / 2; dst = src / 2 + ch->offset; ssize = ch->size / 2; dsize = ch->size / 4; slot = ch->num * 2; #if(0) device_printf(ch->parent->dev, "envy24ht_p16sl():%lu-->%lu(%lu)\n", src, dst, length); #endif for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16; #if(0) if (i < 16) { printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot]); printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1]); } #endif dst++; dst %= dsize; src += 2; src %= ssize; } #if(0) printf("\n"); #endif return; } static void envy24ht_p8u(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int8_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 2; dmabuf = ch->parent->pbuf; data = (u_int8_t *)ch->data; src = sndbuf_getreadyptr(ch->buffer); dst = src / 2 + ch->offset; ssize = ch->size; dsize = ch->size / 4; slot = ch->num * 2; for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24; dst++; dst %= dsize; src += 2; src %= ssize; } return; } static void envy24ht_r32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 8; dmabuf = ch->parent->rbuf; data = (u_int32_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 4; src = dst / 2 + ch->offset; dsize = ch->size / 4; ssize = ch->size / 8; slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } static void envy24ht_r16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; u_int16_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 4; dmabuf = ch->parent->rbuf; data = (u_int16_t *)ch->data; dst = sndbuf_getfreeptr(ch->buffer) / 2; src = dst / 2 + ch->offset; dsize = ch->size / 2; ssize = ch->size / 8; slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } return; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * envy24htchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = (struct sc_info *)devinfo; struct sc_chinfo *ch; unsigned num; #if(0) device_printf(sc->dev, "envy24htchan_init(obj, devinfo, b, c, %d)\n", dir); #endif snd_mtxlock(sc->lock); #if 0 if ((sc->chnum > ENVY24HT_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || (sc->chnum < ENVY24HT_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { snd_mtxunlock(sc->lock); return NULL; } #endif num = sc->chnum; ch = &sc->chan[num]; ch->size = 8 * ENVY24HT_SAMPLE_NUM; ch->data = malloc(ch->size, M_ENVY24HT, M_NOWAIT); if (ch->data == NULL) { ch->size = 0; ch = NULL; } else { ch->buffer = b; ch->channel = c; ch->parent = sc; ch->dir = dir; /* set channel map */ ch->num = envy24ht_chanmap[num]; snd_mtxunlock(sc->lock); sndbuf_setup(ch->buffer, ch->data, ch->size); snd_mtxlock(sc->lock); /* these 2 values are dummy */ ch->unit = 4; ch->blk = 10240; } snd_mtxunlock(sc->lock); return ch; } static int envy24htchan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; #if(0) device_printf(sc->dev, "envy24htchan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_ENVY24HT); ch->data = NULL; } snd_mtxunlock(sc->lock); return 0; } static int envy24htchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct envy24ht_emldma *emltab; /* unsigned int bcnt, bsize; */ int i; #if(0) device_printf(sc->dev, "envy24htchan_setformat(obj, data, 0x%08x)\n", format); #endif snd_mtxlock(sc->lock); /* check and get format related information */ if (ch->dir == PCMDIR_PLAY) emltab = envy24ht_pemltab; else emltab = envy24ht_remltab; if (emltab == NULL) { snd_mtxunlock(sc->lock); return -1; } for (i = 0; emltab[i].format != 0; i++) if (emltab[i].format == format) break; if (emltab[i].format == 0) { snd_mtxunlock(sc->lock); return -1; } /* set format information */ ch->format = format; ch->emldma = emltab[i].emldma; if (ch->unit > emltab[i].unit) ch->blk *= ch->unit / emltab[i].unit; else ch->blk /= emltab[i].unit / ch->unit; ch->unit = emltab[i].unit; /* set channel buffer information */ ch->size = ch->unit * ENVY24HT_SAMPLE_NUM; #if 0 if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24HT_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); #endif snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24htchan_setformat(): return 0x%08x\n", 0); #endif return 0; } /* IMPLEMENT NOTICE: In this driver, setspeed function only do setting of speed information value. And real hardware speed setting is done at start triggered(see envy24htchan_trigger()). So, at this function is called, any value that ENVY24 can use is able to set. But, at start triggerd, some other channel is running, and that channel's speed isn't same with, then trigger function will fail. */ static u_int32_t envy24htchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; u_int32_t val, prev; int i; #if(0) device_printf(ch->parent->dev, "envy24htchan_setspeed(obj, data, %d)\n", speed); #endif prev = 0x7fffffff; for (i = 0; (val = envy24ht_speed[i]) != 0; i++) { if (abs(val - speed) < abs(prev - speed)) prev = val; else break; } ch->speed = prev; #if(0) device_printf(ch->parent->dev, "envy24htchan_setspeed(): return %d\n", ch->speed); #endif return ch->speed; } static u_int32_t envy24htchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; /* struct sc_info *sc = ch->parent; */ u_int32_t size, prev; unsigned int bcnt, bsize; #if(0) device_printf(sc->dev, "envy24htchan_setblocksize(obj, data, %d)\n", blocksize); #endif prev = 0x7fffffff; /* snd_mtxlock(sc->lock); */ for (size = ch->size / 2; size > 0; size /= 2) { if (abs(size - blocksize) < abs(prev - blocksize)) prev = size; else break; } ch->blk = prev / ch->unit; if (ch->dir == PCMDIR_PLAY) ch->blk *= ENVY24HT_PLAY_BUFUNIT / 4; else ch->blk *= ENVY24HT_REC_BUFUNIT / 4; /* set channel buffer information */ /* ch->size = ch->unit * ENVY24HT_SAMPLE_NUM; */ if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24HT_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); /* snd_mtxunlock(sc->lock); */ #if(0) device_printf(sc->dev, "envy24htchan_setblocksize(): return %d\n", prev); #endif return prev; } /* semantic note: must start at beginning of buffer */ static int envy24htchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr; int slot; int error = 0; #if 0 int i; device_printf(sc->dev, "envy24htchan_trigger(obj, data, %d)\n", go); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) slot = 0; else slot = 1; switch (go) { case PCMTRIG_START: #if(0) device_printf(sc->dev, "envy24htchan_trigger(): start\n"); #endif /* check or set channel speed */ if (sc->run[0] == 0 && sc->run[1] == 0) { sc->speed = envy24ht_setspeed(sc, ch->speed); sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; } else if (ch->speed != 0 && ch->speed != sc->speed) { error = -1; goto fail; } if (ch->speed == 0) ch->channel->speed = sc->speed; /* start or enable channel */ sc->run[slot]++; if (sc->run[slot] == 1) { /* first channel */ ch->offset = 0; sc->blk[slot] = ch->blk; } else { ptr = envy24ht_gethwptr(sc, ch->dir); ch->offset = ((ptr / ch->blk + 1) * ch->blk % (ch->size / 4)) * 4 / ch->unit; if (ch->blk < sc->blk[slot]) sc->blk[slot] = ch->blk; } if (ch->dir == PCMDIR_PLAY) { ch->emldma(ch); envy24ht_setvolume(sc, ch->num); } envy24ht_updintr(sc, ch->dir); if (sc->run[slot] == 1) envy24ht_start(sc, ch->dir); ch->run = 1; break; case PCMTRIG_EMLDMAWR: #if(0) device_printf(sc->dev, "envy24htchan_trigger(): emldmawr\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_EMLDMARD: #if(0) device_printf(sc->dev, "envy24htchan_trigger(): emldmard\n"); #endif if (ch->run != 1) { error = -1; goto fail; } ch->emldma(ch); break; case PCMTRIG_ABORT: if (ch->run) { #if(0) device_printf(sc->dev, "envy24htchan_trigger(): abort\n"); #endif ch->run = 0; sc->run[slot]--; if (ch->dir == PCMDIR_PLAY) envy24ht_mutevolume(sc, ch->num); if (sc->run[slot] == 0) { envy24ht_stop(sc, ch->dir); sc->intr[slot] = 0; } /* else if (ch->blk == sc->blk[slot]) { sc->blk[slot] = ENVY24HT_SAMPLE_NUM / 2; for (i = 0; i < ENVY24HT_CHAN_NUM; i++) { if (sc->chan[i].dir == ch->dir && sc->chan[i].run == 1 && sc->chan[i].blk < sc->blk[slot]) sc->blk[slot] = sc->chan[i].blk; } if (ch->blk != sc->blk[slot]) envy24ht_updintr(sc, ch->dir); }*/ } break; } fail: snd_mtxunlock(sc->lock); return (error); } static u_int32_t envy24htchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t ptr, rtn; #if(0) device_printf(sc->dev, "envy24htchan_getptr()\n"); #endif snd_mtxlock(sc->lock); ptr = envy24ht_gethwptr(sc, ch->dir); rtn = ptr * ch->unit; snd_mtxunlock(sc->lock); #if(0) device_printf(sc->dev, "envy24htchan_getptr(): return %d\n", rtn); #endif return rtn; } static struct pcmchan_caps * envy24htchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct pcmchan_caps *rtn; #if(0) device_printf(sc->dev, "envy24htchan_getcaps()\n"); #endif snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (sc->run[0] == 0) rtn = &envy24ht_playcaps; else rtn = &sc->caps[0]; } else { if (sc->run[1] == 0) rtn = &envy24ht_reccaps; else rtn = &sc->caps[1]; } snd_mtxunlock(sc->lock); return rtn; } static kobj_method_t envy24htchan_methods[] = { KOBJMETHOD(channel_init, envy24htchan_init), KOBJMETHOD(channel_free, envy24htchan_free), KOBJMETHOD(channel_setformat, envy24htchan_setformat), KOBJMETHOD(channel_setspeed, envy24htchan_setspeed), KOBJMETHOD(channel_setblocksize, envy24htchan_setblocksize), KOBJMETHOD(channel_trigger, envy24htchan_trigger), KOBJMETHOD(channel_getptr, envy24htchan_getptr), KOBJMETHOD(channel_getcaps, envy24htchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(envy24htchan); /* -------------------------------------------------------------------- */ /* mixer interface */ static int envy24htmixer_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); #if(0) device_printf(sc->dev, "envy24htmixer_init()\n"); #endif if (sc == NULL) return -1; /* set volume control rate */ snd_mtxlock(sc->lock); #if 0 envy24ht_wrmt(sc, ENVY24HT_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ #endif pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, ENVY24HT_MIX_MASK); mix_setrecdevs(m, ENVY24HT_MIX_REC_MASK); snd_mtxunlock(sc->lock); return 0; } static int envy24htmixer_reinit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24htmixer_reinit()\n"); #endif return 0; } static int envy24htmixer_uninit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return -1; #if(0) device_printf(sc->dev, "envy24htmixer_uninit()\n"); #endif return 0; } static int envy24htmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24ht_mixmap[dev]; int hwch; int i; if (sc == NULL) return -1; if (dev == 0 && sc->cfg->codec->setvolume == NULL) return -1; if (dev != 0 && ch == -1) return -1; hwch = envy24ht_chanmap[ch]; #if(0) device_printf(sc->dev, "envy24htmixer_set(m, %d, %d, %d)\n", dev, left, right); #endif snd_mtxlock(sc->lock); if (dev == 0) { for (i = 0; i < sc->dacn; i++) { sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); } } else { /* set volume value for hardware */ if ((sc->left[hwch] = 100 - left) > ENVY24HT_VOL_MIN) sc->left[hwch] = ENVY24HT_VOL_MUTE; if ((sc->right[hwch] = 100 - right) > ENVY24HT_VOL_MIN) sc->right[hwch] = ENVY24HT_VOL_MUTE; /* set volume for record channel and running play channel */ if (hwch > ENVY24HT_CHAN_PLAY_SPDIF || sc->chan[ch].run) envy24ht_setvolume(sc, hwch); } snd_mtxunlock(sc->lock); return right << 8 | left; } static u_int32_t envy24htmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24ht_mixmap[src]; #if(0) device_printf(sc->dev, "envy24htmixer_setrecsrc(m, %d)\n", src); #endif if (ch > ENVY24HT_CHAN_PLAY_SPDIF) sc->src = ch; return src; } static kobj_method_t envy24htmixer_methods[] = { KOBJMETHOD(mixer_init, envy24htmixer_init), KOBJMETHOD(mixer_reinit, envy24htmixer_reinit), KOBJMETHOD(mixer_uninit, envy24htmixer_uninit), KOBJMETHOD(mixer_set, envy24htmixer_set), KOBJMETHOD(mixer_setrecsrc, envy24htmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(envy24htmixer); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void envy24ht_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; u_int32_t ptr, dsize, feed; int i; #if(0) device_printf(sc->dev, "envy24ht_intr()\n"); #endif snd_mtxlock(sc->lock); if (envy24ht_checkintr(sc, PCMDIR_PLAY)) { #if(0) device_printf(sc->dev, "envy24ht_intr(): play\n"); #endif dsize = sc->psize / 4; ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2) - 1; #if(0) device_printf(sc->dev, "envy24ht_intr(): ptr = %d-->", ptr); #endif ptr -= ptr % sc->blk[0]; feed = (ptr + dsize - sc->intr[0]) % dsize; #if(0) printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); #endif for (i = ENVY24HT_CHAN_PLAY_DAC1; i <= ENVY24HT_CHAN_PLAY_SPDIF; i++) { ch = &sc->chan[i]; #if(0) if (ch->run) device_printf(sc->dev, "envy24ht_intr(): chan[%d].blk = %d\n", i, ch->blk); #endif if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[0] = ptr; envy24ht_updintr(sc, PCMDIR_PLAY); } if (envy24ht_checkintr(sc, PCMDIR_REC)) { #if(0) device_printf(sc->dev, "envy24ht_intr(): rec\n"); #endif dsize = sc->rsize / 4; ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_RCNT, 2) - 1; ptr -= ptr % sc->blk[1]; feed = (ptr + dsize - sc->intr[1]) % dsize; for (i = ENVY24HT_CHAN_REC_ADC1; i <= ENVY24HT_CHAN_REC_SPDIF; i++) { ch = &sc->chan[i]; if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[1] = ptr; envy24ht_updintr(sc, PCMDIR_REC); } snd_mtxunlock(sc->lock); return; } /* * Probe and attach the card */ static int envy24ht_pci_probe(device_t dev) { u_int16_t sv, sd; int i; #if(0) printf("envy24ht_pci_probe()\n"); #endif if (pci_get_device(dev) == PCID_ENVY24HT && pci_get_vendor(dev) == PCIV_ENVY24) { sv = pci_get_subvendor(dev); sd = pci_get_subdevice(dev); for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { if (cfg_table[i].subvendor == sv && cfg_table[i].subdevice == sd) { break; } } device_set_desc(dev, cfg_table[i].name); #if(0) printf("envy24ht_pci_probe(): return 0\n"); #endif return 0; } else { #if(0) printf("envy24ht_pci_probe(): return ENXIO\n"); #endif return ENXIO; } } static void envy24ht_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = arg; sc->paddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24ht_dmapsetmap()\n"); if (bootverbose) { printf("envy24ht(play): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); } #endif envy24ht_wrmt(sc, ENVY24HT_MT_PADDR, (uint32_t)segs->ds_addr, 4); envy24ht_wrmt(sc, ENVY24HT_MT_PCNT, (uint32_t)(segs->ds_len / 4 - 1), 2); } static void envy24ht_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = arg; sc->raddr = segs->ds_addr; #if(0) device_printf(sc->dev, "envy24ht_dmarsetmap()\n"); if (bootverbose) { printf("envy24ht(record): setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); } #endif envy24ht_wrmt(sc, ENVY24HT_MT_RADDR, (uint32_t)segs->ds_addr, 4); envy24ht_wrmt(sc, ENVY24HT_MT_RCNT, (uint32_t)(segs->ds_len / 4 - 1), 2); } static void envy24ht_dmafree(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24ht_dmafree():"); printf(" sc->raddr(0x%08x)", (u_int32_t)sc->raddr); printf(" sc->paddr(0x%08x)", (u_int32_t)sc->paddr); if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); else printf(" sc->rbuf(null)"); if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); else printf(" sc->pbuf(null)\n"); #endif #if(0) if (sc->raddr) bus_dmamap_unload(sc->dmat, sc->rmap); if (sc->paddr) bus_dmamap_unload(sc->dmat, sc->pmap); if (sc->rbuf) bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); if (sc->pbuf) bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #else bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); #endif sc->raddr = sc->paddr = 0; sc->pbuf = NULL; sc->rbuf = NULL; return; } static int envy24ht_dmainit(struct sc_info *sc) { #if(0) device_printf(sc->dev, "envy24ht_dmainit()\n"); #endif /* init values */ sc->psize = ENVY24HT_PLAY_BUFUNIT * ENVY24HT_SAMPLE_NUM; sc->rsize = ENVY24HT_REC_BUFUNIT * ENVY24HT_SAMPLE_NUM; sc->pbuf = NULL; sc->rbuf = NULL; sc->paddr = sc->raddr = 0; sc->blk[0] = sc->blk[1] = 0; /* allocate DMA buffer */ #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) goto bad; #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); #endif if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) goto bad; #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_load(): sc->pmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24ht_dmapsetmap, sc, BUS_DMA_NOWAIT)) goto bad; #if(0) device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_load(): sc->rmap\n"); #endif if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24ht_dmarsetmap, sc, BUS_DMA_NOWAIT)) goto bad; bzero(sc->pbuf, sc->psize); bzero(sc->rbuf, sc->rsize); return 0; bad: envy24ht_dmafree(sc); return ENOSPC; } static void envy24ht_putcfg(struct sc_info *sc) { device_printf(sc->dev, "system configuration\n"); printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", sc->cfg->subvendor, sc->cfg->subdevice); printf(" XIN2 Clock Source: "); switch (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_XIN2) { case 0x00: printf("24.576MHz(96kHz*256)\n"); break; case 0x40: printf("49.152MHz(192kHz*256)\n"); break; case 0x80: printf("reserved\n"); break; default: printf("illeagal system setting\n"); } printf(" MPU-401 UART(s) #: "); if (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_MPU) printf("1\n"); else printf("not implemented\n"); switch (sc->adcn) { case 0x01: case 0x02: printf(" ADC #: "); printf("%d\n", sc->adcn); break; case 0x03: printf(" ADC #: "); printf("%d", 1); printf(" and SPDIF receiver connected\n"); break; default: printf(" no physical inputs\n"); } printf(" DAC #: "); printf("%d\n", sc->dacn); printf(" Multi-track converter type: "); if ((sc->cfg->acl & ENVY24HT_CCSM_ACL_MTC) == 0) { printf("AC'97(SDATA_OUT:"); if (sc->cfg->acl & ENVY24HT_CCSM_ACL_OMODE) printf("packed"); else printf("split"); printf(")\n"); } else { printf("I2S("); if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_VOL) printf("with volume, "); if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_192KHZ) printf("192KHz support, "); else if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_96KHZ) printf("192KHz support, "); else printf("48KHz support, "); switch (sc->cfg->i2s & ENVY24HT_CCSM_I2S_RES) { case ENVY24HT_CCSM_I2S_16BIT: printf("16bit resolution, "); break; case ENVY24HT_CCSM_I2S_18BIT: printf("18bit resolution, "); break; case ENVY24HT_CCSM_I2S_20BIT: printf("20bit resolution, "); break; case ENVY24HT_CCSM_I2S_24BIT: printf("24bit resolution, "); break; } printf("ID#0x%x)\n", sc->cfg->i2s & ENVY24HT_CCSM_I2S_ID); } printf(" S/PDIF(IN/OUT): "); if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_IN) printf("1/"); else printf("0/"); if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_OUT) printf("1 "); else printf("0 "); if (sc->cfg->spdif & (ENVY24HT_CCSM_SPDIF_IN | ENVY24HT_CCSM_SPDIF_OUT)) printf("ID# 0x%02x\n", (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_ID) >> 2); printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); } static int envy24ht_init(struct sc_info *sc) { u_int32_t data; #if(0) int rtn; #endif int i; u_int32_t sv, sd; #if(0) device_printf(sc->dev, "envy24ht_init()\n"); #endif /* reset chip */ #if 0 envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_CCS_CTL_RESET, 1); DELAY(200); envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_CCS_CTL_NATIVE, 1); DELAY(200); /* legacy hardware disable */ data = pci_read_config(sc->dev, PCIR_LAC, 2); data |= PCIM_LAC_DISABLE; pci_write_config(sc->dev, PCIR_LAC, data, 2); #endif /* check system configuration */ sc->cfg = NULL; for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { /* 1st: search configuration from table */ sv = pci_get_subvendor(sc->dev); sd = pci_get_subdevice(sc->dev); if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { #if(0) device_printf(sc->dev, "Set configuration from table\n"); #endif sc->cfg = &cfg_table[i]; break; } } if (sc->cfg == NULL) { /* 2nd: read configuration from table */ sc->cfg = envy24ht_rom2cfg(sc); } sc->adcn = ((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_ADC) >> 2) + 1; /* need to be fixed */ sc->dacn = (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_DAC) + 1; if (1 /* bootverbose */) { envy24ht_putcfg(sc); } /* set system configuration */ envy24ht_wrcs(sc, ENVY24HT_CCS_SCFG, sc->cfg->scfg, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_ACL, sc->cfg->acl, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2S, sc->cfg->i2s, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_SPDIF, sc->cfg->spdif, 1); envy24ht_gpiosetmask(sc, sc->cfg->gpiomask); envy24ht_gpiosetdir(sc, sc->cfg->gpiodir); envy24ht_gpiowr(sc, sc->cfg->gpiostate); if ((sc->cfg->subvendor == 0x3031) && (sc->cfg->subdevice == 0x4553)) { envy24ht_wri2c(sc, 0x22, 0x00, 0x07); envy24ht_wri2c(sc, 0x22, 0x04, 0x5f | 0x80); envy24ht_wri2c(sc, 0x22, 0x05, 0x5f | 0x80); } for (i = 0; i < sc->adcn; i++) { sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); sc->cfg->codec->init(sc->adc[i]); } for (i = 0; i < sc->dacn; i++) { sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); sc->cfg->codec->init(sc->dac[i]); } /* initialize DMA buffer */ #if(0) device_printf(sc->dev, "envy24ht_init(): initialize DMA buffer\n"); #endif if (envy24ht_dmainit(sc)) return ENOSPC; /* initialize status */ sc->run[0] = sc->run[1] = 0; sc->intr[0] = sc->intr[1] = 0; sc->speed = 0; sc->caps[0].fmtlist = envy24ht_playfmt; sc->caps[1].fmtlist = envy24ht_recfmt; /* set channel router */ #if 0 envy24ht_route(sc, ENVY24HT_ROUTE_DAC_1, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_DMA, 0, 0); envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); #endif /* set macro interrupt mask */ data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_IMASK, data & ~ENVY24HT_CCS_IMASK_PMT, 1); data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); #if(0) device_printf(sc->dev, "envy24ht_init(): CCS_IMASK-->0x%02x\n", data); #endif return 0; } static int envy24ht_alloc_resource(struct sc_info *sc) { /* allocate I/O port resource */ sc->csid = PCIR_CCS; sc->cs = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, &sc->csid, 0, ~0, 1, RF_ACTIVE); sc->mtid = ENVY24HT_PCIR_MT; sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, &sc->mtid, 0, ~0, 1, RF_ACTIVE); if (!sc->cs || !sc->mt) { device_printf(sc->dev, "unable to map IO port space\n"); return ENXIO; } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); sc->mtt = rman_get_bustag(sc->mt); sc->mth = rman_get_bushandle(sc->mt); #if(0) device_printf(sc->dev, "IO port register values\nCCS: 0x%lx\nMT: 0x%lx\n", pci_read_config(sc->dev, PCIR_CCS, 4), pci_read_config(sc->dev, PCIR_MT, 4)); #endif /* allocate interrupt resource */ sc->irqid = 0; sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24ht_intr, sc, &sc->ih)) { device_printf(sc->dev, "unable to map interrupt\n"); return ENXIO; } /* allocate DMA resource */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, /*nsegments*/1, /*maxsegsz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->dmat) != 0) { device_printf(sc->dev, "unable to create dma tag\n"); return ENXIO; } return 0; } static int envy24ht_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; int err = 0; int i; #if(0) device_printf(dev, "envy24ht_pci_attach()\n"); #endif /* get sc_info data area */ if ((sc = malloc(sizeof(*sc), M_ENVY24HT, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(sc, sizeof(*sc)); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_envy24ht softc"); sc->dev = dev; /* initialize PCI interface */ pci_enable_busmaster(dev); /* allocate resources */ err = envy24ht_alloc_resource(sc); if (err) { device_printf(dev, "unable to allocate system resources\n"); goto bad; } /* initialize card */ err = envy24ht_init(sc); if (err) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* set multi track mixer */ mixer_init(dev, &envy24htmixer_class, sc); /* set channel information */ /* err = pcm_register(dev, sc, 5, 2 + sc->adcn); */ err = pcm_register(dev, sc, 1, 2 + sc->adcn); if (err) goto bad; sc->chnum = 0; /* for (i = 0; i < 5; i++) { */ pcm_addchan(dev, PCMDIR_PLAY, &envy24htchan_class, sc); sc->chnum++; /* } */ for (i = 0; i < 2 + sc->adcn; i++) { pcm_addchan(dev, PCMDIR_REC, &envy24htchan_class, sc); sc->chnum++; } /* set status iformation */ snprintf(status, SND_STATUSLEN, "at io 0x%lx:%ld,0x%lx:%ld irq %ld", rman_get_start(sc->cs), rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, rman_get_start(sc->mt), rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, rman_get_start(sc->irq)); pcm_setstatus(dev, status); return 0; bad: if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); envy24ht_dmafree(sc); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24ht_cfgfree(sc->cfg); if (sc->cs) bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); if (sc->mt) bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_ENVY24HT); return err; } static int envy24ht_pci_detach(device_t dev) { struct sc_info *sc; int r; int i; #if(0) device_printf(dev, "envy24ht_pci_detach()\n"); #endif sc = pcm_getdevinfo(dev); if (sc == NULL) return 0; r = pcm_unregister(dev); if (r) return r; envy24ht_dmafree(sc); if (sc->cfg->codec->destroy != NULL) { for (i = 0; i < sc->adcn; i++) sc->cfg->codec->destroy(sc->adc[i]); for (i = 0; i < sc->dacn; i++) sc->cfg->codec->destroy(sc->dac[i]); } envy24ht_cfgfree(sc->cfg); bus_dma_tag_destroy(sc->dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); snd_mtxfree(sc->lock); free(sc, M_ENVY24HT); return 0; } static device_method_t envy24ht_methods[] = { /* Device interface */ DEVMETHOD(device_probe, envy24ht_pci_probe), DEVMETHOD(device_attach, envy24ht_pci_attach), DEVMETHOD(device_detach, envy24ht_pci_detach), { 0, 0 } }; static driver_t envy24ht_driver = { "pcm", envy24ht_methods, -#if __FreeBSD_version > 500000 PCM_SOFTC_SIZE, -#else - sizeof(struct snddev_info), -#endif }; DRIVER_MODULE(snd_envy24ht, pci, envy24ht_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_envy24ht, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_envy24ht, snd_spicds, 1, 1, 1); MODULE_VERSION(snd_envy24ht, 1); Index: head/sys/dev/sound/pci/maestro.c =================================================================== --- head/sys/dev/sound/pci/maestro.c (revision 274034) +++ head/sys/dev/sound/pci/maestro.c (revision 274035) @@ -1,2072 +1,2062 @@ /*- * Copyright (c) 2000-2004 Taku YAMAMOTO * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * maestro.c,v 1.23.2.1 2003/10/03 18:21:38 taku Exp */ /* * Credits: * * Part of this code (especially in many magic numbers) was heavily inspired * by the Linux driver originally written by * Alan Cox , modified heavily by * Zach Brown . * * busdma()-ize and buffer size reduction were suggested by * Cameron Grant . * Also he showed me the way to use busdma() suite. * * Internal speaker problems on NEC VersaPro's and Dell Inspiron 7500 * were looked at by * Munehiro Matsuda , * who brought patches based on the Linux driver with some simplification. * * Hardware volume controller was implemented by * John Baldwin . */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); /* * PCI IDs of supported chips: * * MAESTRO-1 0x01001285 * MAESTRO-2 0x1968125d * MAESTRO-2E 0x1978125d */ #define MAESTRO_1_PCI_ID 0x01001285 #define MAESTRO_2_PCI_ID 0x1968125d #define MAESTRO_2E_PCI_ID 0x1978125d #define NEC_SUBID1 0x80581033 /* Taken from Linux driver */ #define NEC_SUBID2 0x803c1033 /* NEC VersaProNX VA26D */ #ifdef AGG_MAXPLAYCH # if AGG_MAXPLAYCH > 4 # undef AGG_MAXPLAYCH # define AGG_MAXPLAYCH 4 # endif #else # define AGG_MAXPLAYCH 4 #endif #define AGG_DEFAULT_BUFSZ 0x4000 /* 0x1000, but gets underflows */ -/* compatibility */ -#if __FreeBSD_version < 500000 -# define critical_enter() disable_intr() -# define critical_exit() enable_intr() -#endif - #ifndef PCIR_BAR #define PCIR_BAR(x) (PCIR_MAPS + (x) * 4) #endif /* ----------------------------- * Data structures. */ struct agg_chinfo { /* parent softc */ struct agg_info *parent; /* FreeBSD newpcm related */ struct pcm_channel *channel; struct snd_dbuf *buffer; /* OS independent */ bus_dmamap_t map; bus_addr_t phys; /* channel buffer physical address */ bus_addr_t base; /* channel buffer segment base */ u_int32_t blklen; /* DMA block length in WORDs */ u_int32_t buflen; /* channel buffer length in WORDs */ u_int32_t speed; unsigned num : 3; unsigned stereo : 1; unsigned qs16 : 1; /* quantum size is 16bit */ unsigned us : 1; /* in unsigned format */ }; struct agg_rchinfo { /* parent softc */ struct agg_info *parent; /* FreeBSD newpcm related */ struct pcm_channel *channel; struct snd_dbuf *buffer; /* OS independent */ bus_dmamap_t map; bus_addr_t phys; /* channel buffer physical address */ bus_addr_t base; /* channel buffer segment base */ u_int32_t blklen; /* DMA block length in WORDs */ u_int32_t buflen; /* channel buffer length in WORDs */ u_int32_t speed; unsigned : 3; unsigned stereo : 1; bus_addr_t srcphys; int16_t *src; /* stereo peer buffer */ int16_t *sink; /* channel buffer pointer */ volatile u_int32_t hwptr; /* ready point in 16bit sample */ }; struct agg_info { /* FreeBSD newbus related */ device_t dev; /* I wonder whether bus_space_* are in common in *BSD... */ struct resource *reg; int regid; bus_space_tag_t st; bus_space_handle_t sh; struct resource *irq; int irqid; void *ih; bus_dma_tag_t buf_dmat; bus_dma_tag_t stat_dmat; /* FreeBSD SMPng related */ struct mtx lock; /* mutual exclusion */ /* FreeBSD newpcm related */ struct ac97_info *codec; /* OS independent */ bus_dmamap_t stat_map; u_int8_t *stat; /* status buffer pointer */ bus_addr_t phys; /* status buffer physical address */ unsigned int bufsz; /* channel buffer size in bytes */ u_int playchns; volatile u_int active; struct agg_chinfo pch[AGG_MAXPLAYCH]; struct agg_rchinfo rch; volatile u_int8_t curpwr; /* current power status: D[0-3] */ }; /* ----------------------------- * Sysctls for debug. */ static unsigned int powerstate_active = PCI_POWERSTATE_D1; #ifdef MAESTRO_AGGRESSIVE_POWERSAVE static unsigned int powerstate_idle = PCI_POWERSTATE_D2; #else static unsigned int powerstate_idle = PCI_POWERSTATE_D1; #endif static unsigned int powerstate_init = PCI_POWERSTATE_D2; /* XXX: this should move to a device specific sysctl dev.pcm.X.debug.Y via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ static SYSCTL_NODE(_debug, OID_AUTO, maestro, CTLFLAG_RD, 0, ""); SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_active, CTLFLAG_RW, &powerstate_active, 0, "The Dx power state when active (0-1)"); SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_idle, CTLFLAG_RW, &powerstate_idle, 0, "The Dx power state when idle (0-2)"); SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_init, CTLFLAG_RW, &powerstate_init, 0, "The Dx power state prior to the first use (0-2)"); /* ----------------------------- * Prototypes */ static void agg_sleep(struct agg_info*, const char *wmesg, int msec); #if 0 static __inline u_int32_t agg_rd(struct agg_info*, int, int size); static __inline void agg_wr(struct agg_info*, int, u_int32_t data, int size); #endif static int agg_rdcodec(struct agg_info*, int); static int agg_wrcodec(struct agg_info*, int, u_int32_t); static void ringbus_setdest(struct agg_info*, int, int); static u_int16_t wp_rdreg(struct agg_info*, u_int16_t); static void wp_wrreg(struct agg_info*, u_int16_t, u_int16_t); static u_int16_t wp_rdapu(struct agg_info*, unsigned, u_int16_t); static void wp_wrapu(struct agg_info*, unsigned, u_int16_t, u_int16_t); static void wp_settimer(struct agg_info*, u_int); static void wp_starttimer(struct agg_info*); static void wp_stoptimer(struct agg_info*); #if 0 static u_int16_t wc_rdreg(struct agg_info*, u_int16_t); #endif static void wc_wrreg(struct agg_info*, u_int16_t, u_int16_t); #if 0 static u_int16_t wc_rdchctl(struct agg_info*, int); #endif static void wc_wrchctl(struct agg_info*, int, u_int16_t); static void agg_stopclock(struct agg_info*, int part, int st); static void agg_initcodec(struct agg_info*); static void agg_init(struct agg_info*); static void agg_power(struct agg_info*, int); static void aggch_start_dac(struct agg_chinfo*); static void aggch_stop_dac(struct agg_chinfo*); static void aggch_start_adc(struct agg_rchinfo*); static void aggch_stop_adc(struct agg_rchinfo*); static void aggch_feed_adc_stereo(struct agg_rchinfo*); static void aggch_feed_adc_mono(struct agg_rchinfo*); #ifdef AGG_JITTER_CORRECTION static void suppress_jitter(struct agg_chinfo*); static void suppress_rec_jitter(struct agg_rchinfo*); #endif static void set_timer(struct agg_info*); static void agg_intr(void *); static int agg_probe(device_t); static int agg_attach(device_t); static int agg_detach(device_t); static int agg_suspend(device_t); static int agg_resume(device_t); static int agg_shutdown(device_t); static void *dma_malloc(bus_dma_tag_t, u_int32_t, bus_addr_t*, bus_dmamap_t *); static void dma_free(bus_dma_tag_t, void *, bus_dmamap_t); /* ----------------------------- * Subsystems. */ /* locking */ #define agg_lock(sc) snd_mtxlock(&((sc)->lock)) #define agg_unlock(sc) snd_mtxunlock(&((sc)->lock)) static void agg_sleep(struct agg_info *sc, const char *wmesg, int msec) { int timo; timo = msec * hz / 1000; if (timo == 0) timo = 1; msleep(sc, &sc->lock, PWAIT, wmesg, timo); } /* I/O port */ #if 0 static __inline u_int32_t agg_rd(struct agg_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 ~(u_int32_t)0; } } #endif #define AGG_RD(sc, regno, size) \ bus_space_read_##size( \ ((struct agg_info*)(sc))->st, \ ((struct agg_info*)(sc))->sh, (regno)) #if 0 static __inline void agg_wr(struct agg_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; } } #endif #define AGG_WR(sc, regno, data, size) \ bus_space_write_##size( \ ((struct agg_info*)(sc))->st, \ ((struct agg_info*)(sc))->sh, (regno), (data)) /* -------------------------------------------------------------------- */ /* Codec/Ringbus */ static int agg_codec_wait4idle(struct agg_info *ess) { unsigned t = 26; while (AGG_RD(ess, PORT_CODEC_STAT, 1) & CODEC_STAT_MASK) { if (--t == 0) return EBUSY; DELAY(2); /* 20.8us / 13 */ } return 0; } static int agg_rdcodec(struct agg_info *ess, int regno) { int ret; /* We have to wait for a SAFE time to write addr/data */ if (agg_codec_wait4idle(ess)) { /* Timed out. No read performed. */ device_printf(ess->dev, "agg_rdcodec() PROGLESS timed out.\n"); return -1; } AGG_WR(ess, PORT_CODEC_CMD, CODEC_CMD_READ | regno, 1); /*DELAY(21); * AC97 cycle = 20.8usec */ /* Wait for data retrieve */ if (!agg_codec_wait4idle(ess)) { ret = AGG_RD(ess, PORT_CODEC_REG, 2); } else { /* Timed out. No read performed. */ device_printf(ess->dev, "agg_rdcodec() RW_DONE timed out.\n"); ret = -1; } return ret; } static int agg_wrcodec(struct agg_info *ess, int regno, u_int32_t data) { /* We have to wait for a SAFE time to write addr/data */ if (agg_codec_wait4idle(ess)) { /* Timed out. Abort writing. */ device_printf(ess->dev, "agg_wrcodec() PROGLESS timed out.\n"); return -1; } AGG_WR(ess, PORT_CODEC_REG, data, 2); AGG_WR(ess, PORT_CODEC_CMD, CODEC_CMD_WRITE | regno, 1); /* Wait for write completion */ if (agg_codec_wait4idle(ess)) { /* Timed out. */ device_printf(ess->dev, "agg_wrcodec() RW_DONE timed out.\n"); return -1; } return 0; } static void ringbus_setdest(struct agg_info *ess, int src, int dest) { u_int32_t data; data = AGG_RD(ess, PORT_RINGBUS_CTRL, 4); data &= ~(0xfU << src); data |= (0xfU & dest) << src; AGG_WR(ess, PORT_RINGBUS_CTRL, data, 4); } /* -------------------------------------------------------------------- */ /* Wave Processor */ static u_int16_t wp_rdreg(struct agg_info *ess, u_int16_t reg) { AGG_WR(ess, PORT_DSP_INDEX, reg, 2); return AGG_RD(ess, PORT_DSP_DATA, 2); } static void wp_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { AGG_WR(ess, PORT_DSP_INDEX, reg, 2); AGG_WR(ess, PORT_DSP_DATA, data, 2); } static int wp_wait_data(struct agg_info *ess, u_int16_t data) { unsigned t = 0; while (AGG_RD(ess, PORT_DSP_DATA, 2) != data) { if (++t == 1000) { return EAGAIN; } AGG_WR(ess, PORT_DSP_DATA, data, 2); } return 0; } static u_int16_t wp_rdapu(struct agg_info *ess, unsigned ch, u_int16_t reg) { wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); if (wp_wait_data(ess, reg | (ch << 4)) != 0) device_printf(ess->dev, "wp_rdapu() indexing timed out.\n"); return wp_rdreg(ess, WPREG_DATA_PORT); } static void wp_wrapu(struct agg_info *ess, unsigned ch, u_int16_t reg, u_int16_t data) { wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); if (wp_wait_data(ess, reg | (ch << 4)) == 0) { wp_wrreg(ess, WPREG_DATA_PORT, data); if (wp_wait_data(ess, data) != 0) device_printf(ess->dev, "wp_wrapu() write timed out.\n"); } else { device_printf(ess->dev, "wp_wrapu() indexing timed out.\n"); } } static void apu_setparam(struct agg_info *ess, int apuch, u_int32_t wpwa, u_int16_t size, int16_t pan, u_int dv) { wp_wrapu(ess, apuch, APUREG_WAVESPACE, (wpwa >> 8) & APU_64KPAGE_MASK); wp_wrapu(ess, apuch, APUREG_CURPTR, wpwa); wp_wrapu(ess, apuch, APUREG_ENDPTR, wpwa + size); wp_wrapu(ess, apuch, APUREG_LOOPLEN, size); wp_wrapu(ess, apuch, APUREG_ROUTING, 0); wp_wrapu(ess, apuch, APUREG_AMPLITUDE, 0xf000); wp_wrapu(ess, apuch, APUREG_POSITION, 0x8f00 | (APU_RADIUS_MASK & (RADIUS_CENTERCIRCLE << APU_RADIUS_SHIFT)) | (APU_PAN_MASK & ((pan + PAN_FRONT) << APU_PAN_SHIFT))); wp_wrapu(ess, apuch, APUREG_FREQ_LOBYTE, APU_plus6dB | ((dv & 0xff) << APU_FREQ_LOBYTE_SHIFT)); wp_wrapu(ess, apuch, APUREG_FREQ_HIWORD, dv >> 8); } static void wp_settimer(struct agg_info *ess, u_int divide) { u_int prescale = 0; RANGE(divide, 2, 32 << 7); for (; divide > 32; divide >>= 1) { prescale++; divide++; } for (; prescale < 7 && divide > 2 && !(divide & 1); divide >>= 1) prescale++; wp_wrreg(ess, WPREG_TIMER_ENABLE, 0); wp_wrreg(ess, WPREG_TIMER_FREQ, 0x9000 | (prescale << WP_TIMER_FREQ_PRESCALE_SHIFT) | (divide - 1)); wp_wrreg(ess, WPREG_TIMER_ENABLE, 1); } static void wp_starttimer(struct agg_info *ess) { AGG_WR(ess, PORT_INT_STAT, 1, 2); AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_DSOUND_INT_ENABLED | AGG_RD(ess, PORT_HOSTINT_CTRL, 2), 2); wp_wrreg(ess, WPREG_TIMER_START, 1); } static void wp_stoptimer(struct agg_info *ess) { AGG_WR(ess, PORT_HOSTINT_CTRL, ~HOSTINT_CTRL_DSOUND_INT_ENABLED & AGG_RD(ess, PORT_HOSTINT_CTRL, 2), 2); AGG_WR(ess, PORT_INT_STAT, 1, 2); wp_wrreg(ess, WPREG_TIMER_START, 0); } /* -------------------------------------------------------------------- */ /* WaveCache */ #if 0 static u_int16_t wc_rdreg(struct agg_info *ess, u_int16_t reg) { AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); return AGG_RD(ess, PORT_WAVCACHE_DATA, 2); } #endif static void wc_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); AGG_WR(ess, PORT_WAVCACHE_DATA, data, 2); } #if 0 static u_int16_t wc_rdchctl(struct agg_info *ess, int ch) { return wc_rdreg(ess, ch << 3); } #endif static void wc_wrchctl(struct agg_info *ess, int ch, u_int16_t data) { wc_wrreg(ess, ch << 3, data); } /* -------------------------------------------------------------------- */ /* Power management */ static void agg_stopclock(struct agg_info *ess, int part, int st) { u_int32_t data; data = pci_read_config(ess->dev, CONF_ACPI_STOPCLOCK, 4); if (part < 16) { if (st == PCI_POWERSTATE_D1) data &= ~(1 << part); else data |= (1 << part); if (st == PCI_POWERSTATE_D1 || st == PCI_POWERSTATE_D2) data |= (0x10000 << part); else data &= ~(0x10000 << part); pci_write_config(ess->dev, CONF_ACPI_STOPCLOCK, data, 4); } } /* ----------------------------- * Controller. */ static void agg_initcodec(struct agg_info* ess) { u_int16_t data; if (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) & RINGBUS_CTRL_ACLINK_ENABLED) { AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); DELAY(104); /* 20.8us * (4 + 1) */ } /* XXX - 2nd codec should be looked at. */ AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_AC97_SWRESET, 4); DELAY(2); AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_ACLINK_ENABLED, 4); DELAY(50); if (agg_rdcodec(ess, 0) < 0) { AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); DELAY(21); /* Try cold reset. */ device_printf(ess->dev, "will perform cold reset.\n"); data = AGG_RD(ess, PORT_GPIO_DIR, 2); if (pci_read_config(ess->dev, 0x58, 2) & 1) data |= 0x10; data |= 0x009 & ~AGG_RD(ess, PORT_GPIO_DATA, 2); AGG_WR(ess, PORT_GPIO_MASK, 0xff6, 2); AGG_WR(ess, PORT_GPIO_DIR, data | 0x009, 2); AGG_WR(ess, PORT_GPIO_DATA, 0x000, 2); DELAY(2); AGG_WR(ess, PORT_GPIO_DATA, 0x001, 2); DELAY(1); AGG_WR(ess, PORT_GPIO_DATA, 0x009, 2); agg_sleep(ess, "agginicd", 500); AGG_WR(ess, PORT_GPIO_DIR, data, 2); DELAY(84); /* 20.8us * 4 */ AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_ACLINK_ENABLED, 4); DELAY(50); } } static void agg_init(struct agg_info* ess) { u_int32_t data; /* Setup PCI config registers. */ /* Disable all legacy emulations. */ data = pci_read_config(ess->dev, CONF_LEGACY, 2); data |= LEGACY_DISABLED; pci_write_config(ess->dev, CONF_LEGACY, data, 2); /* Disconnect from CHI. (Makes Dell inspiron 7500 work?) * Enable posted write. * Prefer PCI timing rather than that of ISA. * Don't swap L/R. */ data = pci_read_config(ess->dev, CONF_MAESTRO, 4); data |= MAESTRO_PMC; data |= MAESTRO_CHIBUS | MAESTRO_POSTEDWRITE | MAESTRO_DMA_PCITIMING; data &= ~MAESTRO_SWAP_LR; pci_write_config(ess->dev, CONF_MAESTRO, data, 4); /* Turn off unused parts if necessary. */ /* consult CONF_MAESTRO. */ if (data & MAESTRO_SPDIF) agg_stopclock(ess, ACPI_PART_SPDIF, PCI_POWERSTATE_D2); else agg_stopclock(ess, ACPI_PART_SPDIF, PCI_POWERSTATE_D1); if (data & MAESTRO_HWVOL) agg_stopclock(ess, ACPI_PART_HW_VOL, PCI_POWERSTATE_D3); else agg_stopclock(ess, ACPI_PART_HW_VOL, PCI_POWERSTATE_D1); /* parts that never be used */ agg_stopclock(ess, ACPI_PART_978, PCI_POWERSTATE_D1); agg_stopclock(ess, ACPI_PART_DAA, PCI_POWERSTATE_D1); agg_stopclock(ess, ACPI_PART_GPIO, PCI_POWERSTATE_D1); agg_stopclock(ess, ACPI_PART_SB, PCI_POWERSTATE_D1); agg_stopclock(ess, ACPI_PART_FM, PCI_POWERSTATE_D1); agg_stopclock(ess, ACPI_PART_MIDI, PCI_POWERSTATE_D1); agg_stopclock(ess, ACPI_PART_GAME_PORT, PCI_POWERSTATE_D1); /* parts that will be used only when play/recording */ agg_stopclock(ess, ACPI_PART_WP, PCI_POWERSTATE_D2); /* parts that should always be turned on */ agg_stopclock(ess, ACPI_PART_CODEC_CLOCK, PCI_POWERSTATE_D3); agg_stopclock(ess, ACPI_PART_GLUE, PCI_POWERSTATE_D3); agg_stopclock(ess, ACPI_PART_PCI_IF, PCI_POWERSTATE_D3); agg_stopclock(ess, ACPI_PART_RINGBUS, PCI_POWERSTATE_D3); /* Reset direct sound. */ AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_SOFT_RESET, 2); DELAY(100); AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); DELAY(100); AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_DSOUND_RESET, 2); DELAY(100); AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); DELAY(100); /* Enable hardware volume control interruption. */ if (data & MAESTRO_HWVOL) /* XXX - why not use device flags? */ AGG_WR(ess, PORT_HOSTINT_CTRL,HOSTINT_CTRL_HWVOL_ENABLED, 2); /* Setup Wave Processor. */ /* Enable WaveCache, set DMA base address. */ wp_wrreg(ess, WPREG_WAVE_ROMRAM, WP_WAVE_VIRTUAL_ENABLED | WP_WAVE_DRAM_ENABLED); wp_wrreg(ess, WPREG_CRAM_DATA, 0); AGG_WR(ess, PORT_WAVCACHE_CTRL, WAVCACHE_ENABLED | WAVCACHE_WTSIZE_2MB | WAVCACHE_SGC_32_47, 2); for (data = WAVCACHE_PCMBAR; data < WAVCACHE_PCMBAR + 4; data++) wc_wrreg(ess, data, ess->phys >> WAVCACHE_BASEADDR_SHIFT); /* Setup Codec/Ringbus. */ agg_initcodec(ess); AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_RINGBUS_ENABLED | RINGBUS_CTRL_ACLINK_ENABLED, 4); wp_wrreg(ess, 0x08, 0xB004); wp_wrreg(ess, 0x09, 0x001B); wp_wrreg(ess, 0x0A, 0x8000); wp_wrreg(ess, 0x0B, 0x3F37); wp_wrreg(ess, WPREG_BASE, 0x8598); /* Parallel I/O */ wp_wrreg(ess, WPREG_BASE + 1, 0x7632); ringbus_setdest(ess, RINGBUS_SRC_ADC, RINGBUS_DEST_STEREO | RINGBUS_DEST_DSOUND_IN); ringbus_setdest(ess, RINGBUS_SRC_DSOUND, RINGBUS_DEST_STEREO | RINGBUS_DEST_DAC); /* Enable S/PDIF if necessary. */ if (pci_read_config(ess->dev, CONF_MAESTRO, 4) & MAESTRO_SPDIF) /* XXX - why not use device flags? */ AGG_WR(ess, PORT_RINGBUS_CTRL_B, RINGBUS_CTRL_SPDIF | AGG_RD(ess, PORT_RINGBUS_CTRL_B, 1), 1); /* Setup ASSP. Needed for Dell Inspiron 7500? */ AGG_WR(ess, PORT_ASSP_CTRL_B, 0x00, 1); AGG_WR(ess, PORT_ASSP_CTRL_A, 0x03, 1); AGG_WR(ess, PORT_ASSP_CTRL_C, 0x00, 1); /* * Setup GPIO. * There seems to be speciality with NEC systems. */ switch (pci_get_subvendor(ess->dev) | (pci_get_subdevice(ess->dev) << 16)) { case NEC_SUBID1: case NEC_SUBID2: /* Matthew Braithwaite reported that * NEC Versa LX doesn't need GPIO operation. */ AGG_WR(ess, PORT_GPIO_MASK, 0x9ff, 2); AGG_WR(ess, PORT_GPIO_DIR, AGG_RD(ess, PORT_GPIO_DIR, 2) | 0x600, 2); AGG_WR(ess, PORT_GPIO_DATA, 0x200, 2); break; } } /* Deals power state transition. Must be called with softc->lock held. */ static void agg_power(struct agg_info *ess, int status) { u_int8_t lastpwr; lastpwr = ess->curpwr; if (lastpwr == status) return; switch (status) { case PCI_POWERSTATE_D0: case PCI_POWERSTATE_D1: switch (lastpwr) { case PCI_POWERSTATE_D2: pci_set_powerstate(ess->dev, status); /* Turn on PCM-related parts. */ agg_wrcodec(ess, AC97_REG_POWER, 0); DELAY(100); #if 0 if ((agg_rdcodec(ess, AC97_REG_POWER) & 3) != 3) device_printf(ess->dev, "warning: codec not ready.\n"); #endif AGG_WR(ess, PORT_RINGBUS_CTRL, (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) & ~RINGBUS_CTRL_ACLINK_ENABLED) | RINGBUS_CTRL_RINGBUS_ENABLED, 4); DELAY(50); AGG_WR(ess, PORT_RINGBUS_CTRL, AGG_RD(ess, PORT_RINGBUS_CTRL, 4) | RINGBUS_CTRL_ACLINK_ENABLED, 4); break; case PCI_POWERSTATE_D3: /* Initialize. */ pci_set_powerstate(ess->dev, PCI_POWERSTATE_D0); DELAY(100); agg_init(ess); /* FALLTHROUGH */ case PCI_POWERSTATE_D0: case PCI_POWERSTATE_D1: pci_set_powerstate(ess->dev, status); break; } break; case PCI_POWERSTATE_D2: switch (lastpwr) { case PCI_POWERSTATE_D3: /* Initialize. */ pci_set_powerstate(ess->dev, PCI_POWERSTATE_D0); DELAY(100); agg_init(ess); /* FALLTHROUGH */ case PCI_POWERSTATE_D0: case PCI_POWERSTATE_D1: /* Turn off PCM-related parts. */ AGG_WR(ess, PORT_RINGBUS_CTRL, AGG_RD(ess, PORT_RINGBUS_CTRL, 4) & ~RINGBUS_CTRL_RINGBUS_ENABLED, 4); DELAY(100); agg_wrcodec(ess, AC97_REG_POWER, 0x300); DELAY(100); break; } pci_set_powerstate(ess->dev, status); break; case PCI_POWERSTATE_D3: /* Entirely power down. */ agg_wrcodec(ess, AC97_REG_POWER, 0xdf00); DELAY(100); AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); /*DELAY(1);*/ if (lastpwr != PCI_POWERSTATE_D2) wp_stoptimer(ess); AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); AGG_WR(ess, PORT_HOSTINT_STAT, 0xff, 1); pci_set_powerstate(ess->dev, status); break; default: /* Invalid power state; let it ignored. */ status = lastpwr; break; } ess->curpwr = status; } /* -------------------------------------------------------------------- */ /* Channel controller. */ static void aggch_start_dac(struct agg_chinfo *ch) { bus_addr_t wpwa; u_int32_t speed; u_int16_t size, apuch, wtbar, wcreg, aputype; u_int dv; int pan; speed = ch->speed; wpwa = (ch->phys - ch->base) >> 1; wtbar = 0xc & (wpwa >> WPWA_WTBAR_SHIFT(2)); wcreg = (ch->phys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; size = ch->buflen; apuch = (ch->num << 1) | 32; pan = PAN_RIGHT - PAN_FRONT; if (ch->stereo) { wcreg |= WAVCACHE_CHCTL_STEREO; if (ch->qs16) { aputype = APUTYPE_16BITSTEREO; wpwa >>= 1; size >>= 1; pan = -pan; } else aputype = APUTYPE_8BITSTEREO; } else { pan = 0; if (ch->qs16) aputype = APUTYPE_16BITLINEAR; else { aputype = APUTYPE_8BITLINEAR; speed >>= 1; } } if (ch->us) wcreg |= WAVCACHE_CHCTL_U8; if (wtbar > 8) wtbar = (wtbar >> 1) + 4; dv = (((speed % 48000) << 16) + 24000) / 48000 + ((speed / 48000) << 16); agg_lock(ch->parent); agg_power(ch->parent, powerstate_active); wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar, ch->base >> WAVCACHE_BASEADDR_SHIFT); wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 1, ch->base >> WAVCACHE_BASEADDR_SHIFT); if (wtbar < 8) { wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 2, ch->base >> WAVCACHE_BASEADDR_SHIFT); wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 3, ch->base >> WAVCACHE_BASEADDR_SHIFT); } wc_wrchctl(ch->parent, apuch, wcreg); wc_wrchctl(ch->parent, apuch + 1, wcreg); apu_setparam(ch->parent, apuch, wpwa, size, pan, dv); if (ch->stereo) { if (ch->qs16) wpwa |= (WPWA_STEREO >> 1); apu_setparam(ch->parent, apuch + 1, wpwa, size, -pan, dv); critical_enter(); wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); wp_wrapu(ch->parent, apuch + 1, APUREG_APUTYPE, (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); critical_exit(); } else { wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); } /* to mark that this channel is ready for intr. */ ch->parent->active |= (1 << ch->num); set_timer(ch->parent); wp_starttimer(ch->parent); agg_unlock(ch->parent); } static void aggch_stop_dac(struct agg_chinfo *ch) { agg_lock(ch->parent); /* to mark that this channel no longer needs further intrs. */ ch->parent->active &= ~(1 << ch->num); wp_wrapu(ch->parent, (ch->num << 1) | 32, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); wp_wrapu(ch->parent, (ch->num << 1) | 33, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); if (ch->parent->active) { set_timer(ch->parent); wp_starttimer(ch->parent); } else { wp_stoptimer(ch->parent); agg_power(ch->parent, powerstate_idle); } agg_unlock(ch->parent); } static void aggch_start_adc(struct agg_rchinfo *ch) { bus_addr_t wpwa, wpwa2; u_int16_t wcreg, wcreg2; u_int dv; int pan; /* speed > 48000 not cared */ dv = ((ch->speed << 16) + 24000) / 48000; /* RATECONV doesn't seem to like dv == 0x10000. */ if (dv == 0x10000) dv--; if (ch->stereo) { wpwa = (ch->srcphys - ch->base) >> 1; wpwa2 = (ch->srcphys + ch->parent->bufsz/2 - ch->base) >> 1; wcreg = (ch->srcphys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; wcreg2 = (ch->base - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; pan = PAN_LEFT - PAN_FRONT; } else { wpwa = (ch->phys - ch->base) >> 1; wpwa2 = (ch->srcphys - ch->base) >> 1; wcreg = (ch->phys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; wcreg2 = (ch->base - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; pan = 0; } agg_lock(ch->parent); ch->hwptr = 0; agg_power(ch->parent, powerstate_active); /* Invalidate WaveCache. */ wc_wrchctl(ch->parent, 0, wcreg | WAVCACHE_CHCTL_STEREO); wc_wrchctl(ch->parent, 1, wcreg | WAVCACHE_CHCTL_STEREO); wc_wrchctl(ch->parent, 2, wcreg2 | WAVCACHE_CHCTL_STEREO); wc_wrchctl(ch->parent, 3, wcreg2 | WAVCACHE_CHCTL_STEREO); /* Load APU registers. */ /* APU #0 : Sample rate converter for left/center. */ apu_setparam(ch->parent, 0, WPWA_USE_SYSMEM | wpwa, ch->buflen >> ch->stereo, 0, dv); wp_wrapu(ch->parent, 0, APUREG_AMPLITUDE, 0); wp_wrapu(ch->parent, 0, APUREG_ROUTING, 2 << APU_DATASRC_A_SHIFT); /* APU #1 : Sample rate converter for right. */ apu_setparam(ch->parent, 1, WPWA_USE_SYSMEM | wpwa2, ch->buflen >> ch->stereo, 0, dv); wp_wrapu(ch->parent, 1, APUREG_AMPLITUDE, 0); wp_wrapu(ch->parent, 1, APUREG_ROUTING, 3 << APU_DATASRC_A_SHIFT); /* APU #2 : Input mixer for left. */ apu_setparam(ch->parent, 2, WPWA_USE_SYSMEM | 0, ch->parent->bufsz >> 2, pan, 0x10000); wp_wrapu(ch->parent, 2, APUREG_AMPLITUDE, 0); wp_wrapu(ch->parent, 2, APUREG_EFFECT_GAIN, 0xf0); wp_wrapu(ch->parent, 2, APUREG_ROUTING, 0x15 << APU_DATASRC_A_SHIFT); /* APU #3 : Input mixer for right. */ apu_setparam(ch->parent, 3, WPWA_USE_SYSMEM | (ch->parent->bufsz >> 2), ch->parent->bufsz >> 2, -pan, 0x10000); wp_wrapu(ch->parent, 3, APUREG_AMPLITUDE, 0); wp_wrapu(ch->parent, 3, APUREG_EFFECT_GAIN, 0xf0); wp_wrapu(ch->parent, 3, APUREG_ROUTING, 0x14 << APU_DATASRC_A_SHIFT); /* to mark this channel ready for intr. */ ch->parent->active |= (1 << ch->parent->playchns); /* start adc */ critical_enter(); wp_wrapu(ch->parent, 0, APUREG_APUTYPE, (APUTYPE_RATECONV << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); wp_wrapu(ch->parent, 1, APUREG_APUTYPE, (APUTYPE_RATECONV << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); wp_wrapu(ch->parent, 2, APUREG_APUTYPE, (APUTYPE_INPUTMIXER << APU_APUTYPE_SHIFT) | 0xf); wp_wrapu(ch->parent, 3, APUREG_APUTYPE, (APUTYPE_INPUTMIXER << APU_APUTYPE_SHIFT) | 0xf); critical_exit(); set_timer(ch->parent); wp_starttimer(ch->parent); agg_unlock(ch->parent); } static void aggch_stop_adc(struct agg_rchinfo *ch) { int apuch; agg_lock(ch->parent); /* to mark that this channel no longer needs further intrs. */ ch->parent->active &= ~(1 << ch->parent->playchns); for (apuch = 0; apuch < 4; apuch++) wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); if (ch->parent->active) { set_timer(ch->parent); wp_starttimer(ch->parent); } else { wp_stoptimer(ch->parent); agg_power(ch->parent, powerstate_idle); } agg_unlock(ch->parent); } /* * Feed from L/R channel of ADC to destination with stereo interleaving. * This function expects n not overwrapping the buffer boundary. * Note that n is measured in sample unit. * * XXX - this function works in 16bit stereo format only. */ static void interleave(int16_t *l, int16_t *r, int16_t *p, unsigned n) { int16_t *end; for (end = l + n; l < end; ) { *p++ = *l++; *p++ = *r++; } } static void aggch_feed_adc_stereo(struct agg_rchinfo *ch) { unsigned cur, last; int16_t *src2; agg_lock(ch->parent); cur = wp_rdapu(ch->parent, 0, APUREG_CURPTR); agg_unlock(ch->parent); cur -= 0xffff & ((ch->srcphys - ch->base) >> 1); last = ch->hwptr; src2 = ch->src + ch->parent->bufsz/4; if (cur < last) { interleave(ch->src + last, src2 + last, ch->sink + 2*last, ch->buflen/2 - last); interleave(ch->src, src2, ch->sink, cur); } else if (cur > last) interleave(ch->src + last, src2 + last, ch->sink + 2*last, cur - last); ch->hwptr = cur; } /* * Feed from R channel of ADC and mixdown to destination L/center. * This function expects n not overwrapping the buffer boundary. * Note that n is measured in sample unit. * * XXX - this function works in 16bit monoral format only. */ static void mixdown(int16_t *src, int16_t *dest, unsigned n) { int16_t *end; for (end = dest + n; dest < end; dest++) *dest = (int16_t)(((int)*dest - (int)*src++) / 2); } static void aggch_feed_adc_mono(struct agg_rchinfo *ch) { unsigned cur, last; agg_lock(ch->parent); cur = wp_rdapu(ch->parent, 0, APUREG_CURPTR); agg_unlock(ch->parent); cur -= 0xffff & ((ch->phys - ch->base) >> 1); last = ch->hwptr; if (cur < last) { mixdown(ch->src + last, ch->sink + last, ch->buflen - last); mixdown(ch->src, ch->sink, cur); } else if (cur > last) mixdown(ch->src + last, ch->sink + last, cur - last); ch->hwptr = cur; } #ifdef AGG_JITTER_CORRECTION /* * Stereo jitter suppressor. * Sometimes playback pointers differ in stereo-paired channels. * Calling this routine within intr fixes the problem. */ static void suppress_jitter(struct agg_chinfo *ch) { if (ch->stereo) { int cp1, cp2, diff /*, halfsize*/ ; /*halfsize = (ch->qs16? ch->buflen >> 2 : ch->buflen >> 1);*/ cp1 = wp_rdapu(ch->parent, (ch->num << 1) | 32, APUREG_CURPTR); cp2 = wp_rdapu(ch->parent, (ch->num << 1) | 33, APUREG_CURPTR); if (cp1 != cp2) { diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); if (diff > 1 /* && diff < halfsize*/ ) AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); } } } static void suppress_rec_jitter(struct agg_rchinfo *ch) { int cp1, cp2, diff /*, halfsize*/ ; /*halfsize = (ch->stereo? ch->buflen >> 2 : ch->buflen >> 1);*/ cp1 = (ch->stereo? ch->parent->bufsz >> 2 : ch->parent->bufsz >> 1) + wp_rdapu(ch->parent, 0, APUREG_CURPTR); cp2 = wp_rdapu(ch->parent, 1, APUREG_CURPTR); if (cp1 != cp2) { diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); if (diff > 1 /* && diff < halfsize*/ ) AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); } } #endif static u_int calc_timer_div(struct agg_chinfo *ch) { u_int speed; speed = ch->speed; #ifdef INVARIANTS if (speed == 0) { printf("snd_maestro: pch[%d].speed == 0, which shouldn't\n", ch->num); speed = 1; } #endif return (48000 * (ch->blklen << (!ch->qs16 + !ch->stereo)) + speed - 1) / speed; } static u_int calc_timer_div_rch(struct agg_rchinfo *ch) { u_int speed; speed = ch->speed; #ifdef INVARIANTS if (speed == 0) { printf("snd_maestro: rch.speed == 0, which shouldn't\n"); speed = 1; } #endif return (48000 * (ch->blklen << (!ch->stereo)) + speed - 1) / speed; } static void set_timer(struct agg_info *ess) { int i; u_int dv = 32 << 7, newdv; for (i = 0; i < ess->playchns; i++) if ((ess->active & (1 << i)) && (dv > (newdv = calc_timer_div(ess->pch + i)))) dv = newdv; if ((ess->active & (1 << i)) && (dv > (newdv = calc_timer_div_rch(&ess->rch)))) dv = newdv; wp_settimer(ess, dv); } /* ----------------------------- * Newpcm glue. */ /* AC97 mixer interface. */ static u_int32_t agg_ac97_init(kobj_t obj, void *sc) { struct agg_info *ess = sc; return (AGG_RD(ess, PORT_CODEC_STAT, 1) & CODEC_STAT_MASK)? 0 : 1; } static int agg_ac97_read(kobj_t obj, void *sc, int regno) { struct agg_info *ess = sc; int ret; /* XXX sound locking violation: agg_lock(ess); */ ret = agg_rdcodec(ess, regno); /* agg_unlock(ess); */ return ret; } static int agg_ac97_write(kobj_t obj, void *sc, int regno, u_int32_t data) { struct agg_info *ess = sc; int ret; /* XXX sound locking violation: agg_lock(ess); */ ret = agg_wrcodec(ess, regno, data); /* agg_unlock(ess); */ return ret; } static kobj_method_t agg_ac97_methods[] = { KOBJMETHOD(ac97_init, agg_ac97_init), KOBJMETHOD(ac97_read, agg_ac97_read), KOBJMETHOD(ac97_write, agg_ac97_write), KOBJMETHOD_END }; AC97_DECLARE(agg_ac97); /* -------------------------------------------------------------------- */ /* Playback channel. */ static void * aggpch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct agg_info *ess = devinfo; struct agg_chinfo *ch; bus_addr_t physaddr; void *p; KASSERT((dir == PCMDIR_PLAY), ("aggpch_init() called for RECORDING channel!")); ch = ess->pch + ess->playchns; ch->parent = ess; ch->channel = c; ch->buffer = b; ch->num = ess->playchns; p = dma_malloc(ess->buf_dmat, ess->bufsz, &physaddr, &ch->map); if (p == NULL) return NULL; ch->phys = physaddr; ch->base = physaddr & ((~(bus_addr_t)0) << WAVCACHE_BASEADDR_SHIFT); sndbuf_setup(b, p, ess->bufsz); ch->blklen = sndbuf_getblksz(b) / 2; ch->buflen = sndbuf_getsize(b) / 2; ess->playchns++; return ch; } static void adjust_pchbase(struct agg_chinfo *chans, u_int n, u_int size) { struct agg_chinfo *pchs[AGG_MAXPLAYCH]; u_int i, j, k; bus_addr_t base; /* sort pchs by phys address */ for (i = 0; i < n; i++) { for (j = 0; j < i; j++) if (chans[i].phys < pchs[j]->phys) { for (k = i; k > j; k--) pchs[k] = pchs[k - 1]; break; } pchs[j] = chans + i; } /* use new base register if next buffer can not be addressed via current base. */ #define BASE_SHIFT (WPWA_WTBAR_SHIFT(2) + 2 + 1) base = pchs[0]->base; for (k = 1, i = 1; i < n; i++) { if (pchs[i]->phys + size - base >= 1 << BASE_SHIFT) /* not addressable: assign new base */ base = (pchs[i]->base -= k++ << BASE_SHIFT); else pchs[i]->base = base; } #undef BASE_SHIFT if (bootverbose) { printf("Total of %d bases are assigned.\n", k); for (i = 0; i < n; i++) { printf("ch.%d: phys 0x%llx, wpwa 0x%llx\n", i, (long long)chans[i].phys, (long long)(chans[i].phys - chans[i].base) >> 1); } } } static int aggpch_free(kobj_t obj, void *data) { struct agg_chinfo *ch = data; struct agg_info *ess = ch->parent; /* free up buffer - called after channel stopped */ dma_free(ess->buf_dmat, sndbuf_getbuf(ch->buffer), ch->map); /* return 0 if ok */ return 0; } static int aggpch_setformat(kobj_t obj, void *data, u_int32_t format) { struct agg_chinfo *ch = data; if (format & AFMT_BIGENDIAN || format & AFMT_U16_LE) return EINVAL; ch->stereo = ch->qs16 = ch->us = 0; if (AFMT_CHANNEL(format) > 1) ch->stereo = 1; if (format & AFMT_U8 || format & AFMT_S8) { if (format & AFMT_U8) ch->us = 1; } else ch->qs16 = 1; return 0; } static u_int32_t aggpch_setspeed(kobj_t obj, void *data, u_int32_t speed) { ((struct agg_chinfo*)data)->speed = speed; return (speed); } static u_int32_t aggpch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct agg_chinfo *ch = data; int blkcnt; /* try to keep at least 20msec DMA space */ blkcnt = (ch->speed << (ch->stereo + ch->qs16)) / (50 * blocksize); RANGE(blkcnt, 2, ch->parent->bufsz / blocksize); if (sndbuf_getsize(ch->buffer) != blkcnt * blocksize) { sndbuf_resize(ch->buffer, blkcnt, blocksize); blkcnt = sndbuf_getblkcnt(ch->buffer); blocksize = sndbuf_getblksz(ch->buffer); } else { sndbuf_setblkcnt(ch->buffer, blkcnt); sndbuf_setblksz(ch->buffer, blocksize); } ch->blklen = blocksize / 2; ch->buflen = blkcnt * blocksize / 2; return blocksize; } static int aggpch_trigger(kobj_t obj, void *data, int go) { struct agg_chinfo *ch = data; switch (go) { case PCMTRIG_EMLDMAWR: break; case PCMTRIG_START: aggch_start_dac(ch); break; case PCMTRIG_ABORT: case PCMTRIG_STOP: aggch_stop_dac(ch); break; } return 0; } static u_int32_t aggpch_getptr(kobj_t obj, void *data) { struct agg_chinfo *ch = data; u_int32_t cp; agg_lock(ch->parent); cp = wp_rdapu(ch->parent, (ch->num << 1) | 32, APUREG_CURPTR); agg_unlock(ch->parent); return ch->qs16 && ch->stereo ? (cp << 2) - ((0xffff << 2) & (ch->phys - ch->base)) : (cp << 1) - ((0xffff << 1) & (ch->phys - ch->base)); } static struct pcmchan_caps * aggpch_getcaps(kobj_t obj, void *data) { static u_int32_t playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps playcaps = {8000, 48000, playfmt, 0}; return &playcaps; } static kobj_method_t aggpch_methods[] = { KOBJMETHOD(channel_init, aggpch_init), KOBJMETHOD(channel_free, aggpch_free), KOBJMETHOD(channel_setformat, aggpch_setformat), KOBJMETHOD(channel_setspeed, aggpch_setspeed), KOBJMETHOD(channel_setblocksize, aggpch_setblocksize), KOBJMETHOD(channel_trigger, aggpch_trigger), KOBJMETHOD(channel_getptr, aggpch_getptr), KOBJMETHOD(channel_getcaps, aggpch_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(aggpch); /* -------------------------------------------------------------------- */ /* Recording channel. */ static void * aggrch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct agg_info *ess = devinfo; struct agg_rchinfo *ch; u_int8_t *p; KASSERT((dir == PCMDIR_REC), ("aggrch_init() called for PLAYBACK channel!")); ch = &ess->rch; ch->parent = ess; ch->channel = c; ch->buffer = b; /* Uses the bottom-half of the status buffer. */ p = ess->stat + ess->bufsz; ch->phys = ess->phys + ess->bufsz; ch->base = ess->phys; ch->src = (int16_t *)(p + ess->bufsz); ch->srcphys = ch->phys + ess->bufsz; ch->sink = (int16_t *)p; sndbuf_setup(b, p, ess->bufsz); ch->blklen = sndbuf_getblksz(b) / 2; ch->buflen = sndbuf_getsize(b) / 2; return ch; } static int aggrch_setformat(kobj_t obj, void *data, u_int32_t format) { struct agg_rchinfo *ch = data; if (!(format & AFMT_S16_LE)) return EINVAL; if (AFMT_CHANNEL(format) > 1) ch->stereo = 1; else ch->stereo = 0; return 0; } static u_int32_t aggrch_setspeed(kobj_t obj, void *data, u_int32_t speed) { ((struct agg_rchinfo*)data)->speed = speed; return (speed); } static u_int32_t aggrch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct agg_rchinfo *ch = data; int blkcnt; /* try to keep at least 20msec DMA space */ blkcnt = (ch->speed << ch->stereo) / (25 * blocksize); RANGE(blkcnt, 2, ch->parent->bufsz / blocksize); if (sndbuf_getsize(ch->buffer) != blkcnt * blocksize) { sndbuf_resize(ch->buffer, blkcnt, blocksize); blkcnt = sndbuf_getblkcnt(ch->buffer); blocksize = sndbuf_getblksz(ch->buffer); } else { sndbuf_setblkcnt(ch->buffer, blkcnt); sndbuf_setblksz(ch->buffer, blocksize); } ch->blklen = blocksize / 2; ch->buflen = blkcnt * blocksize / 2; return blocksize; } static int aggrch_trigger(kobj_t obj, void *sc, int go) { struct agg_rchinfo *ch = sc; switch (go) { case PCMTRIG_EMLDMARD: if (ch->stereo) aggch_feed_adc_stereo(ch); else aggch_feed_adc_mono(ch); break; case PCMTRIG_START: aggch_start_adc(ch); break; case PCMTRIG_ABORT: case PCMTRIG_STOP: aggch_stop_adc(ch); break; } return 0; } static u_int32_t aggrch_getptr(kobj_t obj, void *sc) { struct agg_rchinfo *ch = sc; return ch->stereo? ch->hwptr << 2 : ch->hwptr << 1; } static struct pcmchan_caps * aggrch_getcaps(kobj_t obj, void *sc) { static u_int32_t recfmt[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps reccaps = {8000, 48000, recfmt, 0}; return &reccaps; } static kobj_method_t aggrch_methods[] = { KOBJMETHOD(channel_init, aggrch_init), /* channel_free: no-op */ KOBJMETHOD(channel_setformat, aggrch_setformat), KOBJMETHOD(channel_setspeed, aggrch_setspeed), KOBJMETHOD(channel_setblocksize, aggrch_setblocksize), KOBJMETHOD(channel_trigger, aggrch_trigger), KOBJMETHOD(channel_getptr, aggrch_getptr), KOBJMETHOD(channel_getcaps, aggrch_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(aggrch); /* ----------------------------- * Bus space. */ static void agg_intr(void *sc) { struct agg_info* ess = sc; register u_int8_t status; int i; u_int m; status = AGG_RD(ess, PORT_HOSTINT_STAT, 1); if (!status) return; /* Acknowledge intr. */ AGG_WR(ess, PORT_HOSTINT_STAT, status, 1); if (status & HOSTINT_STAT_DSOUND) { #ifdef AGG_JITTER_CORRECTION agg_lock(ess); #endif if (ess->curpwr <= PCI_POWERSTATE_D1) { AGG_WR(ess, PORT_INT_STAT, 1, 2); #ifdef AGG_JITTER_CORRECTION for (i = 0, m = 1; i < ess->playchns; i++, m <<= 1) { if (ess->active & m) suppress_jitter(ess->pch + i); } if (ess->active & m) suppress_rec_jitter(&ess->rch); agg_unlock(ess); #endif for (i = 0, m = 1; i < ess->playchns; i++, m <<= 1) { if (ess->active & m) { if (ess->curpwr <= PCI_POWERSTATE_D1) chn_intr(ess->pch[i].channel); else { m = 0; break; } } } if ((ess->active & m) && ess->curpwr <= PCI_POWERSTATE_D1) chn_intr(ess->rch.channel); } #ifdef AGG_JITTER_CORRECTION else agg_unlock(ess); #endif } if (status & HOSTINT_STAT_HWVOL) { register u_int8_t event; agg_lock(ess); event = AGG_RD(ess, PORT_HWVOL_MASTER, 1); AGG_WR(ess, PORT_HWVOL_MASTER, HWVOL_NOP, 1); agg_unlock(ess); switch (event) { case HWVOL_UP: mixer_hwvol_step(ess->dev, 1, 1); break; case HWVOL_DOWN: mixer_hwvol_step(ess->dev, -1, -1); break; case HWVOL_NOP: break; default: if (event & HWVOL_MUTE) { mixer_hwvol_mute(ess->dev); break; } device_printf(ess->dev, "%s: unknown HWVOL event 0x%x\n", device_get_nameunit(ess->dev), event); } } } static void setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *phys = arg; *phys = error? 0 : segs->ds_addr; if (bootverbose) { printf("setmap (%lx, %lx), nseg=%d, error=%d\n", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, nseg, error); } } static void * dma_malloc(bus_dma_tag_t dmat, u_int32_t sz, bus_addr_t *phys, bus_dmamap_t *map) { void *buf; if (bus_dmamem_alloc(dmat, &buf, BUS_DMA_NOWAIT, map)) return NULL; if (bus_dmamap_load(dmat, *map, buf, sz, setmap, phys, 0) != 0 || *phys == 0) { bus_dmamem_free(dmat, buf, *map); return NULL; } return buf; } static void dma_free(bus_dma_tag_t dmat, void *buf, bus_dmamap_t map) { bus_dmamap_unload(dmat, map); bus_dmamem_free(dmat, buf, map); } static int agg_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case MAESTRO_1_PCI_ID: s = "ESS Technology Maestro-1"; break; case MAESTRO_2_PCI_ID: s = "ESS Technology Maestro-2"; break; case MAESTRO_2E_PCI_ID: s = "ESS Technology Maestro-2E"; break; } if (s != NULL && pci_get_class(dev) == PCIC_MULTIMEDIA) { device_set_desc(dev, s); return BUS_PROBE_DEFAULT; } return ENXIO; } static int agg_attach(device_t dev) { struct agg_info *ess = NULL; u_int32_t data; int regid = PCIR_BAR(0); struct resource *reg = NULL; struct ac97_info *codec = NULL; int irqid = 0; struct resource *irq = NULL; void *ih = NULL; char status[SND_STATUSLEN]; int dacn, ret = 0; ess = malloc(sizeof(*ess), M_DEVBUF, M_WAITOK | M_ZERO); ess->dev = dev; mtx_init(&ess->lock, device_get_desc(dev), "snd_maestro softc", MTX_DEF | MTX_RECURSE); if (!mtx_initialized(&ess->lock)) { device_printf(dev, "failed to create a mutex.\n"); ret = ENOMEM; goto bad; } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &dacn) == 0) { if (dacn < 1) dacn = 1; else if (dacn > AGG_MAXPLAYCH) dacn = AGG_MAXPLAYCH; } else dacn = AGG_MAXPLAYCH; ess->bufsz = pcm_getbuffersize(dev, 4096, AGG_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/ bus_get_dma_tag(dev), /*align */ 4, 1 << (16+1), /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, /*filter*/ NULL, NULL, /*size */ ess->bufsz, 1, 0x3ffff, /*flags */ 0, -#if __FreeBSD_version >= 501102 /*lock */ busdma_lock_mutex, &Giant, -#endif &ess->buf_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); ret = ENOMEM; goto bad; } if (bus_dma_tag_create(/*parent*/ bus_get_dma_tag(dev), /*align */ 1 << WAVCACHE_BASEADDR_SHIFT, 1 << (16+1), /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, /*filter*/ NULL, NULL, /*size */ 3*ess->bufsz, 1, 0x3ffff, /*flags */ 0, -#if __FreeBSD_version >= 501102 /*lock */ busdma_lock_mutex, &Giant, -#endif &ess->stat_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); ret = ENOMEM; goto bad; } /* Allocate the room for brain-damaging status buffer. */ ess->stat = dma_malloc(ess->stat_dmat, 3*ess->bufsz, &ess->phys, &ess->stat_map); if (ess->stat == NULL) { device_printf(dev, "cannot allocate status buffer\n"); ret = ENOMEM; goto bad; } if (bootverbose) device_printf(dev, "Maestro status/record buffer: %#llx\n", (long long)ess->phys); /* State D0-uninitialized. */ ess->curpwr = PCI_POWERSTATE_D3; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); /* Allocate resources. */ reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, ®id, RF_ACTIVE); if (reg != NULL) { ess->reg = reg; ess->regid = regid; ess->st = rman_get_bustag(reg); ess->sh = rman_get_bushandle(reg); } else { device_printf(dev, "unable to map register space\n"); ret = ENXIO; goto bad; } irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &irqid, RF_ACTIVE | RF_SHAREABLE); if (irq != NULL) { ess->irq = irq; ess->irqid = irqid; } else { device_printf(dev, "unable to map interrupt\n"); ret = ENXIO; goto bad; } /* Setup resources. */ if (snd_setup_intr(dev, irq, INTR_MPSAFE, agg_intr, ess, &ih)) { device_printf(dev, "unable to setup interrupt\n"); ret = ENXIO; goto bad; } else ess->ih = ih; /* Transition from D0-uninitialized to D0. */ agg_lock(ess); agg_power(ess, PCI_POWERSTATE_D0); if (agg_rdcodec(ess, 0) == 0x80) { /* XXX - TODO: PT101 */ agg_unlock(ess); device_printf(dev, "PT101 codec detected!\n"); ret = ENXIO; goto bad; } agg_unlock(ess); codec = AC97_CREATE(dev, ess, agg_ac97); if (codec == NULL) { device_printf(dev, "failed to create AC97 codec softc!\n"); ret = ENOMEM; goto bad; } if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { device_printf(dev, "mixer initialization failed!\n"); ret = ENXIO; goto bad; } ess->codec = codec; ret = pcm_register(dev, ess, dacn, 1); if (ret) goto bad; mixer_hwvol_init(dev); agg_lock(ess); agg_power(ess, powerstate_init); agg_unlock(ess); for (data = 0; data < dacn; data++) pcm_addchan(dev, PCMDIR_PLAY, &aggpch_class, ess); pcm_addchan(dev, PCMDIR_REC, &aggrch_class, ess); adjust_pchbase(ess->pch, ess->playchns, ess->bufsz); snprintf(status, SND_STATUSLEN, "port 0x%lx-0x%lx irq %ld at device %d.%d on pci%d", rman_get_start(reg), rman_get_end(reg), rman_get_start(irq), pci_get_slot(dev), pci_get_function(dev), pci_get_bus(dev)); pcm_setstatus(dev, status); return 0; bad: if (codec != NULL) ac97_destroy(codec); if (ih != NULL) bus_teardown_intr(dev, irq, ih); if (irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); if (reg != NULL) bus_release_resource(dev, SYS_RES_IOPORT, regid, reg); if (ess != NULL) { if (ess->stat != NULL) dma_free(ess->stat_dmat, ess->stat, ess->stat_map); if (ess->stat_dmat != NULL) bus_dma_tag_destroy(ess->stat_dmat); if (ess->buf_dmat != NULL) bus_dma_tag_destroy(ess->buf_dmat); if (mtx_initialized(&ess->lock)) mtx_destroy(&ess->lock); free(ess, M_DEVBUF); } return ret; } static int agg_detach(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); int r; u_int16_t icr; icr = AGG_RD(ess, PORT_HOSTINT_CTRL, 2); AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); agg_lock(ess); if (ess->active) { AGG_WR(ess, PORT_HOSTINT_CTRL, icr, 2); agg_unlock(ess); return EBUSY; } agg_unlock(ess); r = pcm_unregister(dev); if (r) { AGG_WR(ess, PORT_HOSTINT_CTRL, icr, 2); return r; } agg_lock(ess); agg_power(ess, PCI_POWERSTATE_D3); agg_unlock(ess); bus_teardown_intr(dev, ess->irq, ess->ih); bus_release_resource(dev, SYS_RES_IRQ, ess->irqid, ess->irq); bus_release_resource(dev, SYS_RES_IOPORT, ess->regid, ess->reg); dma_free(ess->stat_dmat, ess->stat, ess->stat_map); bus_dma_tag_destroy(ess->stat_dmat); bus_dma_tag_destroy(ess->buf_dmat); mtx_destroy(&ess->lock); free(ess, M_DEVBUF); return 0; } static int agg_suspend(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); agg_lock(ess); agg_power(ess, PCI_POWERSTATE_D3); agg_unlock(ess); return 0; } static int agg_resume(device_t dev) { int i; struct agg_info *ess = pcm_getdevinfo(dev); for (i = 0; i < ess->playchns; i++) if (ess->active & (1 << i)) aggch_start_dac(ess->pch + i); if (ess->active & (1 << i)) aggch_start_adc(&ess->rch); agg_lock(ess); if (!ess->active) agg_power(ess, powerstate_init); agg_unlock(ess); if (mixer_reinit(dev)) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } return 0; } static int agg_shutdown(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); agg_lock(ess); agg_power(ess, PCI_POWERSTATE_D3); agg_unlock(ess); return 0; } static device_method_t agg_methods[] = { DEVMETHOD(device_probe, agg_probe), DEVMETHOD(device_attach, agg_attach), DEVMETHOD(device_detach, agg_detach), DEVMETHOD(device_suspend, agg_suspend), DEVMETHOD(device_resume, agg_resume), DEVMETHOD(device_shutdown, agg_shutdown), { 0, 0 } }; static driver_t agg_driver = { "pcm", agg_methods, PCM_SOFTC_SIZE, }; /*static devclass_t pcm_devclass;*/ DRIVER_MODULE(snd_maestro, pci, agg_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_maestro, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_maestro, 1); Index: head/sys/dev/sound/pci/vibes.c =================================================================== --- head/sys/dev/sound/pci/vibes.c (revision 274034) +++ head/sys/dev/sound/pci/vibes.c (revision 274035) @@ -1,946 +1,944 @@ /*- * Copyright (c) 2001 Orion Hodson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This card has the annoying habit of "clicking" when attached and * detached, haven't been able to remedy this with any combination of * muting. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* ------------------------------------------------------------------------- */ /* Constants */ #define SV_PCI_ID 0xca005333 #define SV_DEFAULT_BUFSZ 16384 #define SV_MIN_BLKSZ 128 #define SV_INTR_PER_BUFFER 2 #ifndef DEB #define DEB(x) /* (x) */ #endif /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t fmt, spd; int dir; int dma_active, dma_was_active; }; struct sc_info { device_t dev; /* DMA buffer allocator */ bus_dma_tag_t parent_dmat; /* Enhanced register resources */ struct resource *enh_reg; bus_space_tag_t enh_st; bus_space_handle_t enh_sh; int enh_type; int enh_rid; /* DMA configuration */ struct resource *dmaa_reg, *dmac_reg; bus_space_tag_t dmaa_st, dmac_st; bus_space_handle_t dmaa_sh, dmac_sh; int dmaa_type, dmac_type; int dmaa_rid, dmac_rid; /* Interrupt resources */ struct resource *irq; int irqid; void *ih; /* User configurable buffer size */ unsigned int bufsz; struct sc_chinfo rch, pch; u_int8_t rev; }; static u_int32_t sc_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps sc_caps = {8000, 48000, sc_fmt, 0}; /* ------------------------------------------------------------------------- */ /* Register Manipulations */ #define sv_direct_set(x, y, z) _sv_direct_set(x, y, z, __LINE__) static u_int8_t sv_direct_get(struct sc_info *sc, u_int8_t reg) { return bus_space_read_1(sc->enh_st, sc->enh_sh, reg); } static void _sv_direct_set(struct sc_info *sc, u_int8_t reg, u_int8_t val, int line) { u_int8_t n; bus_space_write_1(sc->enh_st, sc->enh_sh, reg, val); n = sv_direct_get(sc, reg); if (n != val) { device_printf(sc->dev, "sv_direct_set register 0x%02x %d != %d from line %d\n", reg, n, val, line); } } static u_int8_t sv_indirect_get(struct sc_info *sc, u_int8_t reg) { if (reg == SV_REG_FORMAT || reg == SV_REG_ANALOG_PWR) reg |= SV_CM_INDEX_MCE; bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_INDEX, reg); return bus_space_read_1(sc->enh_st, sc->enh_sh, SV_CM_DATA); } #define sv_indirect_set(x, y, z) _sv_indirect_set(x, y, z, __LINE__) static void _sv_indirect_set(struct sc_info *sc, u_int8_t reg, u_int8_t val, int line) { if (reg == SV_REG_FORMAT || reg == SV_REG_ANALOG_PWR) reg |= SV_CM_INDEX_MCE; bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_INDEX, reg); bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_DATA, val); reg &= ~SV_CM_INDEX_MCE; if (reg != SV_REG_ADC_PLLM) { u_int8_t n; n = sv_indirect_get(sc, reg); if (n != val) { device_printf(sc->dev, "sv_indirect_set register 0x%02x %d != %d line %d\n", reg, n, val, line); } } } static void sv_dma_set_config(bus_space_tag_t st, bus_space_handle_t sh, u_int32_t base, u_int32_t count, u_int8_t mode) { bus_space_write_4(st, sh, SV_DMA_ADDR, base); bus_space_write_4(st, sh, SV_DMA_COUNT, count & 0xffffff); bus_space_write_1(st, sh, SV_DMA_MODE, mode); DEB(printf("base 0x%08x count %5d mode 0x%02x\n", base, count, mode)); } static u_int32_t sv_dma_get_count(bus_space_tag_t st, bus_space_handle_t sh) { return bus_space_read_4(st, sh, SV_DMA_COUNT) & 0xffffff; } /* ------------------------------------------------------------------------- */ /* Play / Record Common Interface */ static void * svchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->dir = dir; if (sndbuf_alloc(b, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("svchan_init failed\n")); return NULL; } ch->buffer = b; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->dma_active = ch->dma_was_active = 0; return ch; } static struct pcmchan_caps * svchan_getcaps(kobj_t obj, void *data) { return &sc_caps; } static u_int32_t svchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; /* user has requested interrupts every blocksize bytes */ RANGE(blocksize, SV_MIN_BLKSZ, sc->bufsz / SV_INTR_PER_BUFFER); sndbuf_resize(ch->buffer, SV_INTR_PER_BUFFER, blocksize); DEB(printf("svchan_setblocksize: %d\n", blocksize)); return blocksize; } static int svchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; /* NB Just note format here as setting format register * generates noise if dma channel is inactive. */ ch->fmt = (AFMT_CHANNEL(format) > 1) ? SV_AFMT_STEREO : SV_AFMT_MONO; ch->fmt |= (format & AFMT_16BIT) ? SV_AFMT_S16 : SV_AFMT_U8; return 0; } static u_int32_t svchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; RANGE(speed, 8000, 48000); ch->spd = speed; return speed; } /* ------------------------------------------------------------------------- */ /* Recording interface */ static int sv_set_recspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t f_out, f_actual; u_int32_t rs, re, r, best_r = 0, r2, t, n, best_n = 0; int32_t m, best_m = 0, ms, me, err, min_err; /* This algorithm is a variant described in sonicvibes.pdf * appendix A. This search is marginally more extensive and * results in (nominally) better sample rate matching. */ f_out = SV_F_SCALE * speed; min_err = 0x7fffffff; /* Find bounds of r to examine, rs <= r <= re */ t = 80000000 / f_out; for (rs = 1; (1 << rs) < t; rs++); t = 150000000 / f_out; for (re = 1; (2 << re) < t; re++); if (re > 7) re = 7; /* Search over r, n, m */ for (r = rs; r <= re; r++) { r2 = (1 << r); for (n = 3; n < 34; n++) { m = f_out * n / (SV_F_REF / r2); ms = (m > 3) ? (m - 1) : 3; me = (m < 129) ? (m + 1) : 129; for (m = ms; m <= me; m++) { f_actual = m * SV_F_REF / (n * r2); if (f_actual > f_out) { err = f_actual - f_out; } else { err = f_out - f_actual; } if (err < min_err) { best_r = r; best_m = m - 2; best_n = n - 2; min_err = err; if (err == 0) break; } } } } sv_indirect_set(sc, SV_REG_ADC_PLLM, best_m); sv_indirect_set(sc, SV_REG_ADC_PLLN, SV_ADC_PLLN(best_n) | SV_ADC_PLLR(best_r)); DEB(printf("svrchan_setspeed: %d -> PLLM 0x%02x PLLNR 0x%08x\n", speed, sv_indirect_get(sc, SV_REG_ADC_PLLM), sv_indirect_get(sc, SV_REG_ADC_PLLN))); return 0; } static int svrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ sv_set_recspeed(sc, ch->spd); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAC_MSK; v |= SV_AFMT_DMAC(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ count = sndbuf_getsize(ch->buffer) / 2; /* DMAC uses words */ sv_dma_set_config(sc->dmac_st, sc->dmac_sh, sndbuf_getbufaddr(ch->buffer), count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_RD); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAC_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAC_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE) | SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static u_int32_t svrchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; sz = sndbuf_getsize(ch->buffer); /* DMAC uses words */ remain = (sv_dma_get_count(sc->dmac_st, sc->dmac_sh) + 1) * 2; return sz - remain; } static kobj_method_t svrchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svrchan_trigger), KOBJMETHOD(channel_getptr, svrchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(svrchan); /* ------------------------------------------------------------------------- */ /* Playback interface */ static int svpchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable, speed; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ speed = (ch->spd * 65536) / 48000; if (speed > 65535) speed = 65535; sv_indirect_set(sc, SV_REG_PCM_SAMPLING_HI, speed >> 8); sv_indirect_set(sc, SV_REG_PCM_SAMPLING_LO, speed & 0xff); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAA_MSK; v |= SV_AFMT_DMAA(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ count = sndbuf_getsize(ch->buffer); sv_dma_set_config(sc->dmaa_st, sc->dmaa_sh, sndbuf_getbufaddr(ch->buffer), count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_WR); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAA_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAA_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE); enable = (enable | SV_PLAY_ENABLE) & ~SV_PLAYBACK_PAUSE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_PLAY_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static u_int32_t svpchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; sz = sndbuf_getsize(ch->buffer); /* DMAA uses bytes */ remain = sv_dma_get_count(sc->dmaa_st, sc->dmaa_sh) + 1; return (sz - remain); } static kobj_method_t svpchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svpchan_trigger), KOBJMETHOD(channel_getptr, svpchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(svpchan); /* ------------------------------------------------------------------------- */ /* Mixer support */ struct sv_mix_props { u_int8_t reg; /* Register */ u_int8_t stereo:1; /* Supports 2 channels */ u_int8_t mute:1; /* Supports muting */ u_int8_t neg:1; /* Negative gain */ u_int8_t max; /* Max gain */ u_int8_t iselect; /* Input selector */ } static const mt [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_LINE1] = {SV_REG_AUX1, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_AUX1}, [SOUND_MIXER_CD] = {SV_REG_CD, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_CD}, [SOUND_MIXER_LINE] = {SV_REG_LINE, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_LINE}, [SOUND_MIXER_MIC] = {SV_REG_MIC, 0, 1, 1, SV_MIC_MAX, SV_INPUT_MIC}, [SOUND_MIXER_SYNTH] = {SV_REG_SYNTH, 0, 1, 1, SV_DEFAULT_MAX, 0}, [SOUND_MIXER_LINE2] = {SV_REG_AUX2, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_AUX2}, [SOUND_MIXER_VOLUME] = {SV_REG_MIX, 1, 1, 1, SV_DEFAULT_MAX, 0}, [SOUND_MIXER_PCM] = {SV_REG_PCM, 1, 1, 1, SV_PCM_MAX, 0}, [SOUND_MIXER_RECLEV] = {SV_REG_ADC_INPUT, 1, 0, 0, SV_ADC_MAX, 0}, }; static void sv_channel_gain(struct sc_info *sc, u_int32_t dev, u_int32_t gain, u_int32_t channel) { u_int8_t v; int32_t g; g = mt[dev].max * gain / 100; if (mt[dev].neg) g = mt[dev].max - g; v = sv_indirect_get(sc, mt[dev].reg + channel) & ~mt[dev].max; v |= g; if (mt[dev].mute) { if (gain == 0) { v |= SV_MUTE; } else { v &= ~SV_MUTE; } } sv_indirect_set(sc, mt[dev].reg + channel, v); } static int sv_gain(struct sc_info *sc, u_int32_t dev, u_int32_t left, u_int32_t right) { sv_channel_gain(sc, dev, left, 0); if (mt[dev].stereo) sv_channel_gain(sc, dev, right, 1); return 0; } static void sv_mix_mute_all(struct sc_info *sc) { int32_t i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].reg) sv_gain(sc, i, 0, 0); } } static int sv_mix_init(struct snd_mixer *m) { u_int32_t i, v; for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].max) v |= (1 << i); } mix_setdevs(m, v); for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].iselect) v |= (1 << i); } mix_setrecdevs(m, v); return 0; } static int sv_mix_set(struct snd_mixer *m, u_int32_t dev, u_int32_t left, u_int32_t right) { struct sc_info *sc = mix_getdevinfo(m); return sv_gain(sc, dev, left, right); } static u_int32_t sv_mix_setrecsrc(struct snd_mixer *m, u_int32_t mask) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, v; v = sv_indirect_get(sc, SV_REG_ADC_INPUT) & SV_INPUT_GAIN_MASK; for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1 << i) & mask) { v |= mt[i].iselect; } } DEB(printf("sv_mix_setrecsrc: mask 0x%08x adc_input 0x%02x\n", mask, v)); sv_indirect_set(sc, SV_REG_ADC_INPUT, v); return mask; } static kobj_method_t sv_mixer_methods[] = { KOBJMETHOD(mixer_init, sv_mix_init), KOBJMETHOD(mixer_set, sv_mix_set), KOBJMETHOD(mixer_setrecsrc, sv_mix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(sv_mixer); /* ------------------------------------------------------------------------- */ /* Power management and reset */ static void sv_power(struct sc_info *sc, int state) { u_int8_t v; switch (state) { case 0: /* power on */ v = sv_indirect_get(sc, SV_REG_ANALOG_PWR) &~ SV_ANALOG_OFF; v |= SV_ANALOG_OFF_SRS | SV_ANALOG_OFF_SPLL; sv_indirect_set(sc, SV_REG_ANALOG_PWR, v); v = sv_indirect_get(sc, SV_REG_DIGITAL_PWR) &~ SV_DIGITAL_OFF; v |= SV_DIGITAL_OFF_SYN | SV_DIGITAL_OFF_MU | SV_DIGITAL_OFF_GP; sv_indirect_set(sc, SV_REG_DIGITAL_PWR, v); break; default: /* power off */ v = sv_indirect_get(sc, SV_REG_ANALOG_PWR) | SV_ANALOG_OFF; sv_indirect_set(sc, SV_REG_ANALOG_PWR, v); v = sv_indirect_get(sc, SV_REG_DIGITAL_PWR) | SV_DIGITAL_OFF; sv_indirect_set(sc, SV_REG_DIGITAL_PWR, SV_DIGITAL_OFF); break; } DEB(printf("Power state %d\n", state)); } static int sv_init(struct sc_info *sc) { u_int8_t v; /* Effect reset */ v = sv_direct_get(sc, SV_CM_CONTROL) & ~SV_CM_CONTROL_ENHANCED; v |= SV_CM_CONTROL_RESET; sv_direct_set(sc, SV_CM_CONTROL, v); DELAY(50); v = sv_direct_get(sc, SV_CM_CONTROL) & ~SV_CM_CONTROL_RESET; sv_direct_set(sc, SV_CM_CONTROL, v); DELAY(50); /* Set in enhanced mode */ v = sv_direct_get(sc, SV_CM_CONTROL); v |= SV_CM_CONTROL_ENHANCED; sv_direct_set(sc, SV_CM_CONTROL, v); /* Enable interrupts (UDM and MIDM are superfluous) */ v = sv_direct_get(sc, SV_CM_IMR); v &= ~(SV_CM_IMR_AMSK | SV_CM_IMR_CMSK | SV_CM_IMR_SMSK); sv_direct_set(sc, SV_CM_IMR, v); /* Select ADC PLL for ADC clock */ v = sv_indirect_get(sc, SV_REG_CLOCK_SOURCE) & ~SV_CLOCK_ALTERNATE; sv_indirect_set(sc, SV_REG_CLOCK_SOURCE, v); /* Disable loopback - binds ADC and DAC rates */ v = sv_indirect_get(sc, SV_REG_LOOPBACK) & ~SV_LOOPBACK_ENABLE; sv_indirect_set(sc, SV_REG_LOOPBACK, v); /* Disable SRS */ v = sv_indirect_get(sc, SV_REG_SRS_SPACE) | SV_SRS_DISABLED; sv_indirect_set(sc, SV_REG_SRS_SPACE, v); /* Get revision */ sc->rev = sv_indirect_get(sc, SV_REG_REVISION); return 0; } static int sv_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); sc->rch.dma_was_active = sc->rch.dma_active; svrchan_trigger(NULL, &sc->rch, PCMTRIG_ABORT); sc->pch.dma_was_active = sc->pch.dma_active; svrchan_trigger(NULL, &sc->pch, PCMTRIG_ABORT); sv_mix_mute_all(sc); sv_power(sc, 3); return 0; } static int sv_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 0); if (sv_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } if (sc->rch.dma_was_active) { svrchan_trigger(0, &sc->rch, PCMTRIG_START); } if (sc->pch.dma_was_active) { svpchan_trigger(0, &sc->pch, PCMTRIG_START); } return 0; } /* ------------------------------------------------------------------------- */ /* Resource related */ static void sv_intr(void *data) { struct sc_info *sc = data; u_int8_t status; status = sv_direct_get(sc, SV_CM_STATUS); if (status & SV_CM_STATUS_AINT) chn_intr(sc->pch.channel); if (status & SV_CM_STATUS_CINT) chn_intr(sc->rch.channel); status &= ~(SV_CM_STATUS_AINT|SV_CM_STATUS_CINT); DEB(if (status) printf("intr 0x%02x ?\n", status)); return; } static int sv_probe(device_t dev) { switch(pci_get_devid(dev)) { case SV_PCI_ID: device_set_desc(dev, "S3 Sonicvibes"); return BUS_PROBE_DEFAULT; default: return ENXIO; } } static int sv_attach(device_t dev) { struct sc_info *sc; u_int32_t data; char status[SND_STATUSLEN]; u_long midi_start, games_start, count, sdmaa, sdmac, ml, mu; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; pci_enable_busmaster(dev); -#if __FreeBSD_version > 500000 if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } -#endif sc->enh_rid = SV_PCI_ENHANCED; sc->enh_type = SYS_RES_IOPORT; sc->enh_reg = bus_alloc_resource(dev, sc->enh_type, &sc->enh_rid, 0, ~0, SV_PCI_ENHANCED_SIZE, RF_ACTIVE); if (sc->enh_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate enh\n"); return ENXIO; } sc->enh_st = rman_get_bustag(sc->enh_reg); sc->enh_sh = rman_get_bushandle(sc->enh_reg); data = pci_read_config(dev, SV_PCI_DMAA, 4); DEB(printf("sv_attach: initial dmaa 0x%08x\n", data)); data = pci_read_config(dev, SV_PCI_DMAC, 4); DEB(printf("sv_attach: initial dmac 0x%08x\n", data)); /* Initialize DMA_A and DMA_C */ pci_write_config(dev, SV_PCI_DMAA, SV_PCI_DMA_EXTENDED, 4); pci_write_config(dev, SV_PCI_DMAC, 0, 4); /* Register IRQ handler */ sc->irqid = 0; sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, sv_intr, sc, &sc->ih)) { device_printf(dev, "sv_attach: Unable to map interrupt\n"); goto fail; } sc->bufsz = pcm_getbuffersize(dev, 4096, SV_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &sc->parent_dmat) != 0) { device_printf(dev, "sv_attach: Unable to create dma tag\n"); goto fail; } /* Power up and initialize */ sv_mix_mute_all(sc); sv_power(sc, 0); sv_init(sc); if (mixer_init(dev, &sv_mixer_class, sc) != 0) { device_printf(dev, "sv_attach: Mixer failed to initialize\n"); goto fail; } /* XXX This is a hack, and it's ugly. Okay, the deal is this * card has two more io regions that available for automatic * configuration by the pci code. These need to be allocated * to used as control registers for the DMA engines. * Unfortunately FBSD has no bus_space_foo() functions so we * have to grab port space in region of existing resources. Go * for space between midi and game ports. */ bus_get_resource(dev, SYS_RES_IOPORT, SV_PCI_MIDI, &midi_start, &count); bus_get_resource(dev, SYS_RES_IOPORT, SV_PCI_GAMES, &games_start, &count); if (games_start < midi_start) { ml = games_start; mu = midi_start; } else { ml = midi_start; mu = games_start; } /* Check assumptions about space availability and alignment. How driver loaded can determine whether games_start > midi_start or vice versa */ if ((mu - ml >= 0x800) || ((mu - ml) % 0x200)) { device_printf(dev, "sv_attach: resource assumptions not met " "(midi 0x%08lx, games 0x%08lx)\n", midi_start, games_start); goto fail; } sdmaa = ml + 0x40; sdmac = sdmaa + 0x40; /* Add resources to list of pci resources for this device - from here on * they look like normal pci resources. */ bus_set_resource(dev, SYS_RES_IOPORT, SV_PCI_DMAA, sdmaa, SV_PCI_DMAA_SIZE); bus_set_resource(dev, SYS_RES_IOPORT, SV_PCI_DMAC, sdmac, SV_PCI_DMAC_SIZE); /* Cache resource short-cuts for dma_a */ sc->dmaa_rid = SV_PCI_DMAA; sc->dmaa_type = SYS_RES_IOPORT; sc->dmaa_reg = bus_alloc_resource(dev, sc->dmaa_type, &sc->dmaa_rid, 0, ~0, SV_PCI_ENHANCED_SIZE, RF_ACTIVE); if (sc->dmaa_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmaa\n"); goto fail; } sc->dmaa_st = rman_get_bustag(sc->dmaa_reg); sc->dmaa_sh = rman_get_bushandle(sc->dmaa_reg); /* Poke port into dma_a configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAA, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmaa & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAA, data, 4); DEB(printf("dmaa: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAA, 4))); /* Cache resource short-cuts for dma_c */ sc->dmac_rid = SV_PCI_DMAC; sc->dmac_type = SYS_RES_IOPORT; sc->dmac_reg = bus_alloc_resource(dev, sc->dmac_type, &sc->dmac_rid, 0, ~0, SV_PCI_ENHANCED_SIZE, RF_ACTIVE); if (sc->dmac_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmac\n"); goto fail; } sc->dmac_st = rman_get_bustag(sc->dmac_reg); sc->dmac_sh = rman_get_bushandle(sc->dmac_reg); /* Poke port into dma_c configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAC, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmac & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAC, data, 4); DEB(printf("dmac: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAC, 4))); if (bootverbose) printf("Sonicvibes: revision %d.\n", sc->rev); if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "sv_attach: pcm_register fail\n"); goto fail; } pcm_addchan(dev, PCMDIR_PLAY, &svpchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &svrchan_class, sc); snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(sc->enh_reg), rman_get_start(sc->irq),PCM_KLDSTRING(snd_vibes)); pcm_setstatus(dev, status); DEB(printf("sv_attach: succeeded\n")); return 0; fail: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->enh_reg) bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); if (sc->dmaa_reg) bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); if (sc->dmac_reg) bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); return ENXIO; } static int sv_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); free(sc, M_DEVBUF); return 0; } static device_method_t sc_methods[] = { DEVMETHOD(device_probe, sv_probe), DEVMETHOD(device_attach, sv_attach), DEVMETHOD(device_detach, sv_detach), DEVMETHOD(device_resume, sv_resume), DEVMETHOD(device_suspend, sv_suspend), { 0, 0 } }; static driver_t sonicvibes_driver = { "pcm", sc_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_vibes, pci, sonicvibes_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_vibes, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_vibes, 1); Index: head/sys/dev/sound/pcm/dsp.c =================================================================== --- head/sys/dev/sound/pcm/dsp.c (revision 274034) +++ head/sys/dev/sound/pcm/dsp.c (revision 274035) @@ -1,3363 +1,3361 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD$"); static int dsp_mmap_allow_prot_exec = 0; SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RW, &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility (-1=force disable 0=auto 1=force enable)"); struct dsp_cdevinfo { struct pcm_channel *rdch, *wrch; struct pcm_channel *volch; int busy, simplex; TAILQ_ENTRY(dsp_cdevinfo) link; }; #define PCM_RDCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->rdch) #define PCM_WRCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->wrch) #define PCM_VOLCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->volch) #define PCM_SIMPLEX(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->simplex) #define DSP_CDEVINFO_CACHESIZE 8 #define DSP_REGISTERED(x, y) (PCM_REGISTERED(x) && \ (y) != NULL && (y)->si_drv1 != NULL) #define OLDPCM_IOCTL static d_open_t dsp_open; static d_close_t dsp_close; static d_read_t dsp_read; static d_write_t dsp_write; static d_ioctl_t dsp_ioctl; static d_poll_t dsp_poll; static d_mmap_t dsp_mmap; static d_mmap_single_t dsp_mmap_single; struct cdevsw dsp_cdevsw = { .d_version = D_VERSION, .d_open = dsp_open, .d_close = dsp_close, .d_read = dsp_read, .d_write = dsp_write, .d_ioctl = dsp_ioctl, .d_poll = dsp_poll, .d_mmap = dsp_mmap, .d_mmap_single = dsp_mmap_single, .d_name = "dsp", }; static eventhandler_tag dsp_ehtag = NULL; static int dsp_umax = -1; static int dsp_cmax = -1; static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); static int dsp_oss_syncstart(int sg_id); static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy); static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled); static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask); #ifdef OSSV4_EXPERIMENT static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif static struct snddev_info * dsp_get_info(struct cdev *dev) { return (devclass_get_softc(pcm_devclass, PCMUNIT(dev))); } static uint32_t dsp_get_flags(struct cdev *dev) { device_t bdev; bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); return ((bdev != NULL) ? pcm_getflags(bdev) : 0xffffffff); } static void dsp_set_flags(struct cdev *dev, uint32_t flags) { device_t bdev; bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); if (bdev != NULL) pcm_setflags(bdev, flags); } /* * return the channels associated with an open device instance. * lock channels specified. */ static int getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, uint32_t prio) { struct snddev_info *d; struct pcm_channel *ch; uint32_t flags; if (PCM_SIMPLEX(dev) != 0) { d = dsp_get_info(dev); if (!PCM_REGISTERED(d)) return (ENXIO); PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); /* * Note: order is important - * pcm flags -> prio query flags -> wild guess */ ch = NULL; flags = dsp_get_flags(dev); if (flags & SD_F_PRIO_WR) { ch = PCM_RDCH(dev); PCM_RDCH(dev) = NULL; } else if (flags & SD_F_PRIO_RD) { ch = PCM_WRCH(dev); PCM_WRCH(dev) = NULL; } else if (prio & SD_F_PRIO_WR) { ch = PCM_RDCH(dev); PCM_RDCH(dev) = NULL; flags |= SD_F_PRIO_WR; } else if (prio & SD_F_PRIO_RD) { ch = PCM_WRCH(dev); PCM_WRCH(dev) = NULL; flags |= SD_F_PRIO_RD; } else if (PCM_WRCH(dev) != NULL) { ch = PCM_RDCH(dev); PCM_RDCH(dev) = NULL; flags |= SD_F_PRIO_WR; } else if (PCM_RDCH(dev) != NULL) { ch = PCM_WRCH(dev); PCM_WRCH(dev) = NULL; flags |= SD_F_PRIO_RD; } PCM_SIMPLEX(dev) = 0; dsp_set_flags(dev, flags); if (ch != NULL) { CHN_LOCK(ch); pcm_chnref(ch, -1); pcm_chnrelease(ch); } PCM_RELEASE(d); PCM_UNLOCK(d); } *rdch = PCM_RDCH(dev); *wrch = PCM_WRCH(dev); if (*rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_LOCK(*rdch); if (*wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_LOCK(*wrch); return (0); } /* unlock specified channels */ static void relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, uint32_t prio) { if (wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_UNLOCK(wrch); if (rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_UNLOCK(rdch); } static void dsp_cdevinfo_alloc(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, struct pcm_channel *volch) { struct snddev_info *d; struct dsp_cdevinfo *cdi; int simplex; d = dsp_get_info(dev); KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 == NULL && ((rdch == NULL && wrch == NULL) || rdch != wrch), ("bogus %s(), what are you trying to accomplish here?", __func__)); PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); simplex = (dsp_get_flags(dev) & SD_F_SIMPLEX) ? 1 : 0; /* * Scan for free instance entry and put it into the end of list. * Create new one if necessary. */ TAILQ_FOREACH(cdi, &d->dsp_cdevinfo_pool, link) { if (cdi->busy != 0) break; cdi->rdch = rdch; cdi->wrch = wrch; cdi->volch = volch; cdi->simplex = simplex; cdi->busy = 1; TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link); dev->si_drv1 = cdi; return; } PCM_UNLOCK(d); cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); PCM_LOCK(d); cdi->rdch = rdch; cdi->wrch = wrch; cdi->volch = volch; cdi->simplex = simplex; cdi->busy = 1; TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link); dev->si_drv1 = cdi; } static void dsp_cdevinfo_free(struct cdev *dev) { struct snddev_info *d; struct dsp_cdevinfo *cdi, *tmp; uint32_t flags; int i; d = dsp_get_info(dev); KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 != NULL && PCM_RDCH(dev) == NULL && PCM_WRCH(dev) == NULL && PCM_VOLCH(dev) == NULL, ("bogus %s(), what are you trying to accomplish here?", __func__)); PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); cdi = dev->si_drv1; dev->si_drv1 = NULL; cdi->rdch = NULL; cdi->wrch = NULL; cdi->volch = NULL; cdi->simplex = 0; cdi->busy = 0; /* * Once it is free, move it back to the beginning of list for * faster new entry allocation. */ TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link); /* * Scan the list, cache free entries up to DSP_CDEVINFO_CACHESIZE. * Reset simplex flags. */ flags = dsp_get_flags(dev) & ~SD_F_PRIO_SET; i = DSP_CDEVINFO_CACHESIZE; TAILQ_FOREACH_SAFE(cdi, &d->dsp_cdevinfo_pool, link, tmp) { if (cdi->busy != 0) { if (cdi->simplex == 0) { if (cdi->rdch != NULL) flags |= SD_F_PRIO_RD; if (cdi->wrch != NULL) flags |= SD_F_PRIO_WR; } } else { if (i == 0) { TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); free(cdi, M_DEVBUF); } else i--; } } dsp_set_flags(dev, flags); } void dsp_cdevinfo_init(struct snddev_info *d) { struct dsp_cdevinfo *cdi; int i; KASSERT(d != NULL, ("NULL snddev_info")); PCM_BUSYASSERT(d); PCM_UNLOCKASSERT(d); TAILQ_INIT(&d->dsp_cdevinfo_pool); for (i = 0; i < DSP_CDEVINFO_CACHESIZE; i++) { cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link); } } void dsp_cdevinfo_flush(struct snddev_info *d) { struct dsp_cdevinfo *cdi, *tmp; KASSERT(d != NULL, ("NULL snddev_info")); PCM_BUSYASSERT(d); PCM_UNLOCKASSERT(d); cdi = TAILQ_FIRST(&d->dsp_cdevinfo_pool); while (cdi != NULL) { tmp = TAILQ_NEXT(cdi, link); free(cdi, M_DEVBUF); cdi = tmp; } TAILQ_INIT(&d->dsp_cdevinfo_pool); } /* duplex / simplex cdev type */ enum { DSP_CDEV_TYPE_RDONLY, /* simplex read-only (record) */ DSP_CDEV_TYPE_WRONLY, /* simplex write-only (play) */ DSP_CDEV_TYPE_RDWR /* duplex read, write, or both */ }; enum { DSP_CDEV_VOLCTL_NONE, DSP_CDEV_VOLCTL_READ, DSP_CDEV_VOLCTL_WRITE }; #define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) #define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) #define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) #define DSP_F_READ(x) ((x) & FREAD) #define DSP_F_WRITE(x) ((x) & FWRITE) static const struct { int type; char *name; char *sep; char *alias; int use_sep; int hw; int max; int volctl; uint32_t fmt, spd; int query; } dsp_cdevs[] = { { SND_DEV_DSP, "dsp", ".", NULL, 0, 0, 0, 0, SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_AUDIO, "audio", ".", NULL, 0, 0, 0, 0, SND_FORMAT(AFMT_MU_LAW, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_DSP16, "dspW", ".", NULL, 0, 0, 0, 0, SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_DSPHW_PLAY, "dsp", ".p", NULL, 1, 1, SND_MAXHWCHAN, 1, SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY }, { SND_DEV_DSPHW_VPLAY, "dsp", ".vp", NULL, 1, 1, SND_MAXVCHANS, 1, SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY }, { SND_DEV_DSPHW_REC, "dsp", ".r", NULL, 1, 1, SND_MAXHWCHAN, 1, SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY }, { SND_DEV_DSPHW_VREC, "dsp", ".vr", NULL, 1, 1, SND_MAXVCHANS, 1, SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY }, { SND_DEV_DSPHW_CD, "dspcd", ".", NULL, 0, 0, 0, 0, SND_FORMAT(AFMT_S16_LE, 2, 0), 44100, DSP_CDEV_TYPE_RDWR }, /* Low priority, OSSv4 aliases. */ { SND_DEV_DSP, "dsp_ac3", ".", "dsp", 0, 0, 0, 0, SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_DSP, "dsp_mmap", ".", "dsp", 0, 0, 0, 0, SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_DSP, "dsp_multich", ".", "dsp", 0, 0, 0, 0, SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_DSP, "dsp_spdifout", ".", "dsp", 0, 0, 0, 0, SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, { SND_DEV_DSP, "dsp_spdifin", ".", "dsp", 0, 0, 0, 0, SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, }; #define DSP_FIXUP_ERROR() do { \ prio = dsp_get_flags(i_dev); \ if (!DSP_F_VALID(flags)) \ error = EINVAL; \ if (!DSP_F_DUPLEX(flags) && \ ((DSP_F_READ(flags) && d->reccount == 0) || \ (DSP_F_WRITE(flags) && d->playcount == 0))) \ error = ENOTSUP; \ else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) && \ ((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) || \ (DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD)))) \ error = EBUSY; \ else if (DSP_REGISTERED(d, i_dev)) \ error = EBUSY; \ } while (0) static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct pcm_channel *rdch, *wrch; struct snddev_info *d; uint32_t fmt, spd, prio, volctl; int i, error, rderror, wrerror, devtype, wdevunit, rdevunit; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return (ENODEV); d = dsp_get_info(i_dev); if (!PCM_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); /* Lock snddev so nobody else can monkey with it. */ PCM_LOCK(d); PCM_WAIT(d); /* * Try to acquire cloned device before someone else pick it. * ENODEV means this is not a cloned droids. */ error = snd_clone_acquire(i_dev); if (!(error == 0 || error == ENODEV)) { DSP_FIXUP_ERROR(); PCM_UNLOCK(d); PCM_GIANT_EXIT(d); return (error); } error = 0; DSP_FIXUP_ERROR(); if (error != 0) { (void)snd_clone_release(i_dev); PCM_UNLOCK(d); PCM_GIANT_EXIT(d); return (error); } /* * That is just enough. Acquire and unlock pcm lock so * the other will just have to wait until we finish doing * everything. */ PCM_ACQUIRE(d); PCM_UNLOCK(d); devtype = PCMDEV(i_dev); wdevunit = -1; rdevunit = -1; fmt = 0; spd = 0; volctl = DSP_CDEV_VOLCTL_NONE; for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { if (devtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) continue; /* * Volume control only valid for DSPHW devices, * and it must be opened in opposite direction be it * simplex or duplex. Anything else will be handled * as usual. */ if (dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY) { if (dsp_cdevs[i].volctl != 0 && DSP_F_READ(flags)) { volctl = DSP_CDEV_VOLCTL_WRITE; flags &= ~FREAD; flags |= FWRITE; } if (DSP_F_READ(flags)) { (void)snd_clone_release(i_dev); PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (ENOTSUP); } wdevunit = dev2unit(i_dev); } else if (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY) { if (dsp_cdevs[i].volctl != 0 && DSP_F_WRITE(flags)) { volctl = DSP_CDEV_VOLCTL_READ; flags &= ~FWRITE; flags |= FREAD; } if (DSP_F_WRITE(flags)) { (void)snd_clone_release(i_dev); PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (ENOTSUP); } rdevunit = dev2unit(i_dev); } fmt = dsp_cdevs[i].fmt; spd = dsp_cdevs[i].spd; break; } /* No matching devtype? */ if (fmt == 0 || spd == 0) panic("impossible devtype %d", devtype); rdch = NULL; wrch = NULL; rderror = 0; wrerror = 0; /* * if we get here, the open request is valid- either: * * we were previously not open * * we were open for play xor record and the opener wants * the non-open direction */ if (DSP_F_READ(flags)) { /* open for read */ rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, td->td_proc->p_comm, rdevunit); if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0) rderror = ENXIO; if (volctl == DSP_CDEV_VOLCTL_READ) rderror = 0; if (rderror != 0) { if (rdch != NULL) pcm_chnrelease(rdch); if (!DSP_F_DUPLEX(flags)) { (void)snd_clone_release(i_dev); PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (rderror); } rdch = NULL; } else if (volctl == DSP_CDEV_VOLCTL_READ) { if (rdch != NULL) { pcm_chnref(rdch, 1); pcm_chnrelease(rdch); } } else { if (flags & O_NONBLOCK) rdch->flags |= CHN_F_NBIO; if (flags & O_EXCL) rdch->flags |= CHN_F_EXCLUSIVE; pcm_chnref(rdch, 1); if (volctl == DSP_CDEV_VOLCTL_NONE) chn_vpc_reset(rdch, SND_VOL_C_PCM, 0); CHN_UNLOCK(rdch); } } if (DSP_F_WRITE(flags)) { /* open for write */ wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, td->td_proc->p_comm, wdevunit); if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0) wrerror = ENXIO; if (volctl == DSP_CDEV_VOLCTL_WRITE) wrerror = 0; if (wrerror != 0) { if (wrch != NULL) pcm_chnrelease(wrch); if (!DSP_F_DUPLEX(flags)) { if (rdch != NULL) { /* * Lock, deref and release previously * created record channel */ CHN_LOCK(rdch); pcm_chnref(rdch, -1); pcm_chnrelease(rdch); } (void)snd_clone_release(i_dev); PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (wrerror); } wrch = NULL; } else if (volctl == DSP_CDEV_VOLCTL_WRITE) { if (wrch != NULL) { pcm_chnref(wrch, 1); pcm_chnrelease(wrch); } } else { if (flags & O_NONBLOCK) wrch->flags |= CHN_F_NBIO; if (flags & O_EXCL) wrch->flags |= CHN_F_EXCLUSIVE; pcm_chnref(wrch, 1); if (volctl == DSP_CDEV_VOLCTL_NONE) chn_vpc_reset(wrch, SND_VOL_C_PCM, 0); CHN_UNLOCK(wrch); } } PCM_LOCK(d); /* * We're done. Allocate channels information for this cdev. */ switch (volctl) { case DSP_CDEV_VOLCTL_READ: KASSERT(wrch == NULL, ("wrch=%p not null!", wrch)); dsp_cdevinfo_alloc(i_dev, NULL, NULL, rdch); break; case DSP_CDEV_VOLCTL_WRITE: KASSERT(rdch == NULL, ("rdch=%p not null!", rdch)); dsp_cdevinfo_alloc(i_dev, NULL, NULL, wrch); break; case DSP_CDEV_VOLCTL_NONE: default: if (wrch == NULL && rdch == NULL) { (void)snd_clone_release(i_dev); PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_EXIT(d); if (wrerror != 0) return (wrerror); if (rderror != 0) return (rderror); return (EINVAL); } dsp_cdevinfo_alloc(i_dev, rdch, wrch, NULL); if (rdch != NULL) CHN_INSERT_HEAD(d, rdch, channels.pcm.opened); if (wrch != NULL) CHN_INSERT_HEAD(d, wrch, channels.pcm.opened); break; } /* * Increase clone refcount for its automatic garbage collector. */ (void)snd_clone_ref(i_dev); PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_LEAVE(d); return (0); } static int dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct pcm_channel *rdch, *wrch, *volch; struct snddev_info *d; int sg_ids, rdref, wdref; d = dsp_get_info(i_dev); if (!DSP_REGISTERED(d, i_dev)) return (EBADF); PCM_GIANT_ENTER(d); PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); rdch = PCM_RDCH(i_dev); wrch = PCM_WRCH(i_dev); volch = PCM_VOLCH(i_dev); PCM_RDCH(i_dev) = NULL; PCM_WRCH(i_dev) = NULL; PCM_VOLCH(i_dev) = NULL; rdref = -1; wdref = -1; if (volch != NULL) { if (volch == rdch) rdref--; else if (volch == wrch) wdref--; else { CHN_LOCK(volch); pcm_chnref(volch, -1); CHN_UNLOCK(volch); } } if (rdch != NULL) CHN_REMOVE(d, rdch, channels.pcm.opened); if (wrch != NULL) CHN_REMOVE(d, wrch, channels.pcm.opened); if (rdch != NULL || wrch != NULL) { PCM_UNLOCK(d); if (rdch != NULL) { /* * The channel itself need not be locked because: * a) Adding a channel to a syncgroup happens only * in dsp_ioctl(), which cannot run concurrently * to dsp_close(). * b) The syncmember pointer (sm) is protected by * the global syncgroup list lock. * c) A channel can't just disappear, invalidating * pointers, unless it's closed/dereferenced * first. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(rdch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); CHN_LOCK(rdch); pcm_chnref(rdch, rdref); chn_abort(rdch); /* won't sleep */ rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | CHN_F_DEAD | CHN_F_EXCLUSIVE); chn_reset(rdch, 0, 0); pcm_chnrelease(rdch); } if (wrch != NULL) { /* * Please see block above. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(wrch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); CHN_LOCK(wrch); pcm_chnref(wrch, wdref); chn_flush(wrch); /* may sleep */ wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | CHN_F_DEAD | CHN_F_EXCLUSIVE); chn_reset(wrch, 0, 0); pcm_chnrelease(wrch); } PCM_LOCK(d); } dsp_cdevinfo_free(i_dev); /* * Release clone busy state and unref it so the automatic * garbage collector will get the hint and do the remaining * cleanup process. */ (void)snd_clone_release(i_dev); /* * destroy_dev() might sleep, so release pcm lock * here and rely on pcm cv serialization. */ PCM_UNLOCK(d); (void)snd_clone_unref(i_dev); PCM_LOCK(d); PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_LEAVE(d); return (0); } static __inline int dsp_io_ops(struct cdev *i_dev, struct uio *buf) { struct snddev_info *d; struct pcm_channel **ch, *rdch, *wrch; int (*chn_io)(struct pcm_channel *, struct uio *); int prio, ret; pid_t runpid; KASSERT(i_dev != NULL && buf != NULL && (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE), ("%s(): io train wreck!", __func__)); d = dsp_get_info(i_dev); if (!DSP_REGISTERED(d, i_dev)) return (EBADF); PCM_GIANT_ENTER(d); switch (buf->uio_rw) { case UIO_READ: prio = SD_F_PRIO_RD; ch = &rdch; chn_io = chn_read; break; case UIO_WRITE: prio = SD_F_PRIO_WR; ch = &wrch; chn_io = chn_write; break; default: panic("invalid/corrupted uio direction: %d", buf->uio_rw); break; } rdch = NULL; wrch = NULL; runpid = buf->uio_td->td_proc->p_pid; getchns(i_dev, &rdch, &wrch, prio); if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { PCM_GIANT_EXIT(d); return (EBADF); } if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) || (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { relchns(i_dev, rdch, wrch, prio); PCM_GIANT_EXIT(d); return (EINVAL); } else if (!((*ch)->flags & CHN_F_RUNNING)) { (*ch)->flags |= CHN_F_RUNNING; (*ch)->pid = runpid; } /* * chn_read/write must give up channel lock in order to copy bytes * from/to userland, so up the "in progress" counter to make sure * someone else doesn't come along and muss up the buffer. */ ++(*ch)->inprog; ret = chn_io(*ch, buf); --(*ch)->inprog; CHN_BROADCAST(&(*ch)->cv); relchns(i_dev, rdch, wrch, prio); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_read(struct cdev *i_dev, struct uio *buf, int flag) { return (dsp_io_ops(i_dev, buf)); } static int dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { return (dsp_io_ops(i_dev, buf)); } static int dsp_get_volume_channel(struct cdev *dev, struct pcm_channel **volch) { struct snddev_info *d; struct pcm_channel *c; int unit; KASSERT(dev != NULL && volch != NULL, ("%s(): NULL query dev=%p volch=%p", __func__, dev, volch)); d = dsp_get_info(dev); if (!PCM_REGISTERED(d)) { *volch = NULL; return (EINVAL); } PCM_UNLOCKASSERT(d); *volch = NULL; c = PCM_VOLCH(dev); if (c != NULL) { if (!(c->feederflags & (1 << FEEDER_VOLUME))) return (-1); *volch = c; return (0); } PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); unit = dev2unit(dev); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->unit != unit) { CHN_UNLOCK(c); continue; } *volch = c; pcm_chnref(c, 1); PCM_VOLCH(dev) = c; CHN_UNLOCK(c); PCM_RELEASE(d); PCM_UNLOCK(d); return ((c->feederflags & (1 << FEEDER_VOLUME)) ? 0 : -1); } PCM_RELEASE(d); PCM_UNLOCK(d); return (EINVAL); } static int dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd, caddr_t arg) { struct snddev_info *d; struct pcm_channel *rdch, *wrch; int j, devtype, ret; d = dsp_get_info(dev); if (!PCM_REGISTERED(d) || !(dsp_get_flags(dev) & SD_F_VPC)) return (-1); PCM_UNLOCKASSERT(d); j = cmd & 0xff; rdch = PCM_RDCH(dev); wrch = PCM_WRCH(dev); /* No specific channel, look into cache */ if (volch == NULL) volch = PCM_VOLCH(dev); /* Look harder */ if (volch == NULL) { if (j == SOUND_MIXER_RECLEV && rdch != NULL) volch = rdch; else if (j == SOUND_MIXER_PCM && wrch != NULL) volch = wrch; } devtype = PCMDEV(dev); /* Look super harder */ if (volch == NULL && (devtype == SND_DEV_DSPHW_PLAY || devtype == SND_DEV_DSPHW_VPLAY || devtype == SND_DEV_DSPHW_REC || devtype == SND_DEV_DSPHW_VREC)) { ret = dsp_get_volume_channel(dev, &volch); if (ret != 0) return (ret); if (volch == NULL) return (EINVAL); } /* Final validation */ if (volch != NULL) { CHN_LOCK(volch); if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { CHN_UNLOCK(volch); return (-1); } if (volch->direction == PCMDIR_PLAY) wrch = volch; else rdch = volch; } ret = EINVAL; if (volch != NULL && ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) || (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) { if ((cmd & ~0xff) == MIXER_WRITE(0)) { int left, right, center; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, center); } else if ((cmd & ~0xff) == MIXER_READ(0)) { *(int *)arg = CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; } ret = 0; } else if (rdch != NULL || wrch != NULL) { switch (j) { case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: if ((cmd & ~0xff) == MIXER_READ(0)) { *(int *)arg = 0; if (rdch != NULL) *(int *)arg |= SOUND_MASK_RECLEV; if (wrch != NULL) *(int *)arg |= SOUND_MASK_PCM; } ret = 0; break; case SOUND_MIXER_RECMASK: case SOUND_MIXER_RECSRC: if ((cmd & ~0xff) == MIXER_READ(0)) *(int *)arg = 0; ret = 0; break; default: break; } } if (volch != NULL) CHN_UNLOCK(volch); return (ret); } static int dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; u_long xcmd; int *arg_i, ret, tmp; d = dsp_get_info(i_dev); if (!DSP_REGISTERED(d, i_dev)) return (EBADF); PCM_GIANT_ENTER(d); arg_i = (int *)arg; ret = 0; xcmd = 0; chn = NULL; if (IOCGROUP(cmd) == 'M') { if (cmd == OSS_GETVERSION) { *arg_i = SOUND_VERSION; PCM_GIANT_EXIT(d); return (0); } ret = dsp_ioctl_channel(i_dev, PCM_VOLCH(i_dev), cmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = EBADF; PCM_GIANT_EXIT(d); return (ret); } /* * Certain ioctls may be made on any type of device (audio, mixer, * and MIDI). Handle those special cases here. */ if (IOCGROUP(cmd) == 'X') { PCM_ACQUIRE_QUICK(d); switch(cmd) { case SNDCTL_SYSINFO: sound_oss_sysinfo((oss_sysinfo *)arg); break; case SNDCTL_CARDINFO: ret = sound_oss_card_info((oss_card_info *)arg); break; case SNDCTL_AUDIOINFO: case SNDCTL_AUDIOINFO_EX: case SNDCTL_ENGINEINFO: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); break; case SNDCTL_MIXERINFO: ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); break; default: ret = EINVAL; } PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (ret); } getchns(i_dev, &rdch, &wrch, 0); if (wrch != NULL && (wrch->flags & CHN_F_DEAD)) wrch = NULL; if (rdch != NULL && (rdch->flags & CHN_F_DEAD)) rdch = NULL; if (wrch == NULL && rdch == NULL) { PCM_GIANT_EXIT(d); return (EINVAL); } switch(cmd) { #ifdef OLDPCM_IOCTL /* * we start with the new ioctl interface. */ case AIONWRITE: /* how many bytes can write ? */ if (wrch) { CHN_LOCK(wrch); /* if (wrch && wrch->bufhard.dl) while (chn_wrfeed(wrch) == 0); */ *arg_i = sndbuf_getfree(wrch->bufsoft); CHN_UNLOCK(wrch); } else { *arg_i = 0; ret = EINVAL; } break; case AIOSSIZE: /* set the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; p->play_size = 0; p->rec_size = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, p->play_size); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, p->rec_size); p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); } break; case AIOGSIZE: /* get the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; if (wrch) { CHN_LOCK(wrch); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } } break; case AIOSFMT: case AIOGFMT: { snd_chan_param *p = (snd_chan_param *)arg; if (cmd == AIOSFMT && ((p->play_format != 0 && p->play_rate == 0) || (p->rec_format != 0 && p->rec_rate == 0))) { ret = EINVAL; break; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); if (cmd == AIOSFMT && p->play_format != 0) { chn_setformat(wrch, SND_FORMAT(p->play_format, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); chn_setspeed(wrch, p->play_rate); } p->play_rate = wrch->speed; p->play_format = AFMT_ENCODING(wrch->format); CHN_UNLOCK(wrch); } else { p->play_rate = 0; p->play_format = 0; } if (rdch) { CHN_LOCK(rdch); if (cmd == AIOSFMT && p->rec_format != 0) { chn_setformat(rdch, SND_FORMAT(p->rec_format, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); chn_setspeed(rdch, p->rec_rate); } p->rec_rate = rdch->speed; p->rec_format = AFMT_ENCODING(rdch->format); CHN_UNLOCK(rdch); } else { p->rec_rate = 0; p->rec_format = 0; } PCM_RELEASE_QUICK(d); } break; case AIOGCAP: /* get capabilities */ { snd_capabilities *p = (snd_capabilities *)arg; struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; PCM_LOCK(d); if (rdch) { CHN_LOCK(rdch); rcaps = chn_getcaps(rdch); } if (wrch) { CHN_LOCK(wrch); pcaps = chn_getcaps(wrch); } p->rate_min = max(rcaps? rcaps->minspeed : 0, pcaps? pcaps->minspeed : 0); p->rate_max = min(rcaps? rcaps->maxspeed : 1000000, pcaps? pcaps->maxspeed : 1000000); p->bufsize = min(rdch? sndbuf_getsize(rdch->bufsoft) : 1000000, wrch? sndbuf_getsize(wrch->bufsoft) : 1000000); /* XXX bad on sb16 */ p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) & (wrch? chn_getformats(wrch) : 0xffffffff); if (rdch && wrch) p->formats |= (dsp_get_flags(i_dev) & SD_F_SIMPLEX)? 0 : AFMT_FULLDUPLEX; pdev = d->mixer_dev; p->mixers = 1; /* default: one mixer */ p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0; p->left = p->right = 100; if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_UNLOCK(d); } break; case AIOSTOP: if (*arg_i == AIOSYNC_PLAY && wrch) { CHN_LOCK(wrch); *arg_i = chn_abort(wrch); CHN_UNLOCK(wrch); } else if (*arg_i == AIOSYNC_CAPTURE && rdch) { CHN_LOCK(rdch); *arg_i = chn_abort(rdch); CHN_UNLOCK(rdch); } else { printf("AIOSTOP: bad channel 0x%x\n", *arg_i); *arg_i = 0; } break; case AIOSYNC: printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n", ((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos); break; #endif /* * here follow the standard ioctls (filio.h etc.) */ case FIONREAD: /* get # bytes to read */ if (rdch) { CHN_LOCK(rdch); /* if (rdch && rdch->bufhard.dl) while (chn_rdfeed(rdch) == 0); */ *arg_i = sndbuf_getready(rdch->bufsoft); CHN_UNLOCK(rdch); } else { *arg_i = 0; ret = EINVAL; } break; case FIOASYNC: /*set/clear async i/o */ DEB( printf("FIOASYNC\n") ; ) break; case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */ case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) { CHN_LOCK(rdch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) rdch->flags |= CHN_F_NBIO; else rdch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) wrch->flags |= CHN_F_NBIO; else wrch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(wrch); } break; /* * Finally, here is the linux-compatible ioctl interface */ #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int) case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = sndbuf_getblksz(chn->bufsoft); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, *arg_i); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, *arg_i); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_RESET: DEB(printf("dsp reset\n")); if (wrch) { CHN_LOCK(wrch); chn_abort(wrch); chn_resetbuf(wrch); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_abort(rdch); chn_resetbuf(rdch); CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_SYNC: DEB(printf("dsp sync\n")); /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SPEED: /* chn_setspeed may sleep */ tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setspeed(wrch, *arg_i); tmp = wrch->speed; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setspeed(rdch, *arg_i); if (tmp == 0) tmp = rdch->speed; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_READ_RATE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn->speed; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_STEREO: tmp = -1; *arg_i = (*arg_i)? 2 : 1; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, 0)); tmp = (AFMT_CHANNEL(wrch->format) > 1)? 1 : 0; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, 0)); if (tmp == -1) tmp = (AFMT_CHANNEL(rdch->format) > 1)? 1 : 0; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_WRITE_CHANNELS: /* case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */ if (*arg_i < 0) { *arg_i = 0; ret = EINVAL; break; } if (*arg_i != 0) { struct pcmchan_matrix *m; uint32_t ext; tmp = 0; if (*arg_i > SND_CHN_MAX) *arg_i = SND_CHN_MAX; m = feeder_matrix_default_channel_map(*arg_i); if (m != NULL) ext = m->ext; else ext = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, ext)); tmp = AFMT_CHANNEL(wrch->format); CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, ext)); if (tmp == 0) tmp = AFMT_CHANNEL(rdch->format); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } break; case SOUND_PCM_READ_CHANNELS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn_getformats(chn); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ if (*arg_i != AFMT_QUERY) { tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(*arg_i, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); tmp = wrch->format; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(*arg_i, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); if (tmp == 0) tmp = rdch->format; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = AFMT_ENCODING(tmp); } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_ENCODING(chn->format); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_SETFRAGMENT: DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { uint32_t fragln = (*arg_i) & 0x0000ffff; uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; uint32_t fragsz; uint32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; if (maxfrags == 0) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; if (maxfrags < 2) maxfrags = 2; if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft); r_fragsz = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } else { r_maxfrags = maxfrags; r_fragsz = fragsz; } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setblocksize(wrch, maxfrags, fragsz); maxfrags = sndbuf_getblkcnt(wrch->bufsoft); fragsz = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } else { /* use whatever came from the read channel */ maxfrags = r_maxfrags; fragsz = r_fragsz; } PCM_RELEASE_QUICK(d); fragln = 0; while (fragsz > 1) { fragln++; fragsz >>= 1; } *arg_i = (maxfrags << 16) | fragln; } break; case SNDCTL_DSP_GETISPACE: /* return the size of data available in the input queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); a->bytes = sndbuf_getready(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOSPACE: /* return space available in the output queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_getfree(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETIPTR: { count_info *a = (count_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); /* XXX abusive DMA update: chn_rdupdate(rdch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getfreeptr(bs); rdch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOPTR: { count_info *a = (count_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); wrch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETCAPS: PCM_LOCK(d); *arg_i = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; if (rdch && wrch && !(dsp_get_flags(i_dev) & SD_F_SIMPLEX)) *arg_i |= PCM_CAP_DUPLEX; PCM_UNLOCK(d); break; case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); if (chn->format & AFMT_8BIT) *arg_i = 8; else if (chn->format & AFMT_16BIT) *arg_i = 16; else if (chn->format & AFMT_24BIT) *arg_i = 24; else if (chn->format & AFMT_32BIT) *arg_i = 32; else ret = EINVAL; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETTRIGGER: if (rdch) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_INPUT) chn_start(rdch, 1); else { chn_abort(rdch); chn_resetbuf(rdch); rdch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_OUTPUT) chn_start(wrch, 1); else { chn_abort(wrch); chn_resetbuf(wrch); wrch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_GETTRIGGER: *arg_i = 0; if (wrch) { CHN_LOCK(wrch); if (wrch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_OUTPUT; CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); if (rdch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_INPUT; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETODELAY: if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; break; case SNDCTL_DSP_POST: if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; chn_start(wrch, 1); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SETDUPLEX: /* * switch to full-duplex mode if card is in half-duplex * mode and is able to work in full-duplex mode */ PCM_LOCK(d); if (rdch && wrch && (dsp_get_flags(i_dev) & SD_F_SIMPLEX)) dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX); PCM_UNLOCK(d); break; /* * The following four ioctls are simple wrappers around mixer_ioctl * with no further processing. xcmd is short for "translated * command". */ case SNDCTL_DSP_GETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_GETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_PCM; chn = wrch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_PCM; chn = wrch; } ret = dsp_ioctl_channel(i_dev, chn, xcmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = ENOTSUP; break; case SNDCTL_DSP_GET_RECSRC_NAMES: case SNDCTL_DSP_GET_RECSRC: case SNDCTL_DSP_SET_RECSRC: if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = ENOTSUP; break; /* * The following 3 ioctls aren't very useful at the moment. For * now, only a single channel is associated with a cdev (/dev/dspN * instance), so there's only a single output routing to use (i.e., * the wrch bound to this cdev). */ case SNDCTL_DSP_GET_PLAYTGT_NAMES: { oss_mixer_enuminfo *ei; ei = (oss_mixer_enuminfo *)arg; ei->dev = 0; ei->ctrl = 0; ei->version = 0; /* static for now */ ei->strindex[0] = 0; if (wrch != NULL) { ei->nvalues = 1; strlcpy(ei->strings, wrch->name, sizeof(ei->strings)); } else { ei->nvalues = 0; ei->strings[0] = '\0'; } } break; case SNDCTL_DSP_GET_PLAYTGT: case SNDCTL_DSP_SET_PLAYTGT: /* yes, they are the same for now */ /* * Re: SET_PLAYTGT * OSSv4: "The value that was accepted by the device will * be returned back in the variable pointed by the * argument." */ if (wrch != NULL) *arg_i = 0; else ret = EINVAL; break; case SNDCTL_DSP_SILENCE: /* * Flush the software (pre-feed) buffer, but try to minimize playback * interruption. (I.e., record unplayed samples with intent to * restore by SNDCTL_DSP_SKIP.) Intended for application "pause" * functionality. */ if (wrch == NULL) ret = EINVAL; else { struct snd_dbuf *bs; CHN_LOCK(wrch); while (wrch->inprog != 0) cv_wait(&wrch->cv, wrch->lock); bs = wrch->bufsoft; if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) { bs->sl = sndbuf_getready(bs); sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs)); sndbuf_fillsilence(bs); chn_start(wrch, 0); } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SKIP: /* * OSSv4 docs: "This ioctl call discards all unplayed samples in the * playback buffer by moving the current write position immediately * before the point where the device is currently reading the samples." */ if (wrch == NULL) ret = EINVAL; else { struct snd_dbuf *bs; CHN_LOCK(wrch); while (wrch->inprog != 0) cv_wait(&wrch->cv, wrch->lock); bs = wrch->bufsoft; if ((bs->shadbuf != NULL) && (bs->sl > 0)) { sndbuf_softreset(bs); sndbuf_acquire(bs, bs->shadbuf, bs->sl); bs->sl = 0; chn_start(wrch, 0); } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_CURRENT_OPTR: case SNDCTL_DSP_CURRENT_IPTR: /** * @note Changing formats resets the buffer counters, which differs * from the 4Front drivers. However, I don't expect this to be * much of a problem. * * @note In a test where @c CURRENT_OPTR is called immediately after write * returns, this driver is about 32K samples behind whereas * 4Front's is about 8K samples behind. Should determine source * of discrepancy, even if only out of curiosity. * * @todo Actually test SNDCTL_DSP_CURRENT_IPTR. */ chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { struct snd_dbuf *bs; /* int tmp; */ oss_count_t *oc = (oss_count_t *)arg; CHN_LOCK(chn); bs = chn->bufsoft; #if 0 tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b); oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getalign(b); oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getalign(b); #else oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs); oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(bs); #endif CHN_UNLOCK(chn); } break; case SNDCTL_DSP_HALT_OUTPUT: case SNDCTL_DSP_HALT_INPUT: chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { CHN_LOCK(chn); chn_abort(chn); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_LOW_WATER: /* * Set the number of bytes required to attract attention by * select/poll. */ if (wrch != NULL) { CHN_LOCK(wrch); wrch->lw = (*arg_i > 1) ? *arg_i : 1; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->lw = (*arg_i > 1) ? *arg_i : 1; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETERROR: /* * OSSv4 docs: "All errors and counters will automatically be * cleared to zeroes after the call so each call will return only * the errors that occurred after the previous invocation. ... The * play_underruns and rec_overrun fields are the only useful fields * returned by OSS 4.0." */ { audio_errinfo *ei = (audio_errinfo *)arg; bzero((void *)ei, sizeof(*ei)); if (wrch != NULL) { CHN_LOCK(wrch); ei->play_underruns = wrch->xruns; wrch->xruns = 0; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); ei->rec_overruns = rdch->xruns; rdch->xruns = 0; CHN_UNLOCK(rdch); } } break; case SNDCTL_DSP_SYNCGROUP: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SYNCSTART: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncstart(*arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_POLICY: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_policy(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_COOKEDMODE: PCM_ACQUIRE_QUICK(d); if (!(dsp_get_flags(i_dev) & SD_F_BITPERFECT)) ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GETCHANNELMASK: /* XXX vlc */ PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchannelmask(wrch, rdch, (int *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_BIND_CHANNEL: /* XXX what?!? */ ret = EINVAL; break; #ifdef OSSV4_EXPERIMENT /* * XXX The following ioctls are not yet supported and just return * EINVAL. */ case SNDCTL_DSP_GETOPEAKS: case SNDCTL_DSP_GETIPEAKS: chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { oss_peaks_t *op = (oss_peaks_t *)arg; int lpeak, rpeak; CHN_LOCK(chn); ret = chn_getpeaks(chn, &lpeak, &rpeak); if (ret == -1) ret = EINVAL; else { (*op)[0] = lpeak; (*op)[1] = rpeak; } CHN_UNLOCK(chn); } break; /* * XXX Once implemented, revisit this for proper cv protection * (if necessary). */ case SNDCTL_GETLABEL: ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg); break; case SNDCTL_SETLABEL: ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg); break; case SNDCTL_GETSONG: ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg); break; case SNDCTL_SETSONG: ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg); break; case SNDCTL_SETNAME: ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); break; #if 0 /** * @note The S/PDIF interface ioctls, @c SNDCTL_DSP_READCTL and * @c SNDCTL_DSP_WRITECTL have been omitted at the suggestion of * 4Front Technologies. */ case SNDCTL_DSP_READCTL: case SNDCTL_DSP_WRITECTL: ret = EINVAL; break; #endif /* !0 (explicitly omitted ioctls) */ #endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: /* undocumented */ case SNDCTL_DSP_SUBDIVIDE: case SOUND_PCM_WRITE_FILTER: case SOUND_PCM_READ_FILTER: /* dunno what these do, don't sound important */ default: DEB(printf("default ioctl fn 0x%08lx fail\n", cmd)); ret = EINVAL; break; } PCM_GIANT_LEAVE(d); return (ret); } static int dsp_poll(struct cdev *i_dev, int events, struct thread *td) { struct snddev_info *d; struct pcm_channel *wrch, *rdch; int ret, e; d = dsp_get_info(i_dev); if (!DSP_REGISTERED(d, i_dev)) return (EBADF); PCM_GIANT_ENTER(d); wrch = NULL; rdch = NULL; ret = 0; getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) { e = (events & (POLLOUT | POLLWRNORM)); if (e) ret |= chn_poll(wrch, e, td); } if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) { e = (events & (POLLIN | POLLRDNORM)); if (e) ret |= chn_poll(rdch, e, td); } relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { /* XXX memattr is not honored */ *paddr = vtophys(offset); return (0); } static int dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, vm_size_t size, struct vm_object **object, int nprot) { struct snddev_info *d; struct pcm_channel *wrch, *rdch, *c; /* * Reject PROT_EXEC by default. It just doesn't makes sense. * Unfortunately, we have to give up this one due to linux_mmap * changes. * * http://lists.freebsd.org/pipermail/freebsd-emulation/2007-June/003698.html * */ #ifdef SV_ABI_LINUX if ((nprot & PROT_EXEC) && (dsp_mmap_allow_prot_exec < 0 || (dsp_mmap_allow_prot_exec == 0 && SV_CURPROC_ABI() != SV_ABI_LINUX))) #else if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec < 1) #endif return (EINVAL); /* * PROT_READ (alone) selects the input buffer. * PROT_WRITE (alone) selects the output buffer. * PROT_WRITE|PROT_READ together select the output buffer. */ if ((nprot & (PROT_READ | PROT_WRITE)) == 0) return (EINVAL); d = dsp_get_info(i_dev); if (!DSP_REGISTERED(d, i_dev)) return (EINVAL); PCM_GIANT_ENTER(d); getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch; if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) || (*offset + size) > sndbuf_getsize(c->bufsoft) || (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); PCM_GIANT_EXIT(d); return (EINVAL); } if (wrch != NULL) wrch->flags |= CHN_F_MMAP; if (rdch != NULL) rdch->flags |= CHN_F_MMAP; *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); *object = vm_pager_allocate(OBJT_DEVICE, i_dev, size, nprot, *offset, curthread->td_ucred); PCM_GIANT_LEAVE(d); if (*object == NULL) return (EINVAL); return (0); } /* So much for dev_stdclone() */ static int dsp_stdclone(char *name, char *namep, char *sep, int use_sep, int *u, int *c) { size_t len; len = strlen(namep); if (bcmp(name, namep, len) != 0) return (ENODEV); name += len; if (isdigit(*name) == 0) return (ENODEV); len = strlen(sep); if (*name == '0' && !(name[1] == '\0' || bcmp(name + 1, sep, len) == 0)) return (ENODEV); for (*u = 0; isdigit(*name) != 0; name++) { *u *= 10; *u += *name - '0'; if (*u > dsp_umax) return (ENODEV); } if (*name == '\0') return ((use_sep == 0) ? 0 : ENODEV); if (bcmp(name, sep, len) != 0 || isdigit(name[len]) == 0) return (ENODEV); name += len; if (*name == '0' && name[1] != '\0') return (ENODEV); for (*c = 0; isdigit(*name) != 0; name++) { *c *= 10; *c += *name - '0'; if (*c > dsp_cmax) return (ENODEV); } if (*name != '\0') return (ENODEV); return (0); } static void dsp_clone(void *arg, -#if __FreeBSD_version >= 600034 struct ucred *cred, -#endif char *name, int namelen, struct cdev **dev) { struct snddev_info *d; struct snd_clone_entry *ce; struct pcm_channel *c; int i, unit, udcmask, cunit, devtype, devhw, devcmax, tumax; char *devname, *devcmp, *devsep; KASSERT(dsp_umax >= 0 && dsp_cmax >= 0, ("Uninitialized unit!")); if (*dev != NULL) return; unit = -1; cunit = -1; devtype = -1; devhw = 0; devcmax = -1; tumax = -1; devname = NULL; devsep = NULL; for (i = 0; unit == -1 && i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { devtype = dsp_cdevs[i].type; devcmp = dsp_cdevs[i].name; devsep = dsp_cdevs[i].sep; devname = dsp_cdevs[i].alias; if (devname == NULL) devname = devcmp; devhw = dsp_cdevs[i].hw; devcmax = dsp_cdevs[i].max - 1; if (strcmp(name, devcmp) == 0) unit = snd_unit; else if (dsp_stdclone(name, devcmp, devsep, dsp_cdevs[i].use_sep, &unit, &cunit) != 0) { unit = -1; cunit = -1; } } d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d) || d->clones == NULL) return; /* XXX Need Giant magic entry ??? */ PCM_LOCK(d); if (snd_clone_disabled(d->clones)) { PCM_UNLOCK(d); return; } PCM_WAIT(d); PCM_ACQUIRE(d); PCM_UNLOCK(d); udcmask = snd_u2unit(unit) | snd_d2unit(devtype); if (devhw != 0) { KASSERT(devcmax <= dsp_cmax, ("overflow: devcmax=%d, dsp_cmax=%d", devcmax, dsp_cmax)); if (cunit > devcmax) { PCM_RELEASE_QUICK(d); return; } udcmask |= snd_c2unit(cunit); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->unit != udcmask) { CHN_UNLOCK(c); continue; } CHN_UNLOCK(c); udcmask &= ~snd_c2unit(cunit); /* * Temporarily increase clone maxunit to overcome * vchan flexibility. * * # sysctl dev.pcm.0.play.vchans=256 * dev.pcm.0.play.vchans: 1 -> 256 * # cat /dev/zero > /dev/dsp0.vp255 & * [1] 17296 * # sysctl dev.pcm.0.play.vchans=0 * dev.pcm.0.play.vchans: 256 -> 1 * # fg * [1] + running cat /dev/zero > /dev/dsp0.vp255 * ^C * # cat /dev/zero > /dev/dsp0.vp255 * zsh: operation not supported: /dev/dsp0.vp255 */ tumax = snd_clone_getmaxunit(d->clones); if (cunit > tumax) snd_clone_setmaxunit(d->clones, cunit); else tumax = -1; goto dsp_clone_alloc; } /* * Ok, so we're requesting unallocated vchan, but still * within maximum vchan limit. */ if (((devtype == SND_DEV_DSPHW_VPLAY && d->pvchancount > 0) || (devtype == SND_DEV_DSPHW_VREC && d->rvchancount > 0)) && cunit < snd_maxautovchans) { udcmask &= ~snd_c2unit(cunit); tumax = snd_clone_getmaxunit(d->clones); if (cunit > tumax) snd_clone_setmaxunit(d->clones, cunit); else tumax = -1; goto dsp_clone_alloc; } PCM_RELEASE_QUICK(d); return; } dsp_clone_alloc: ce = snd_clone_alloc(d->clones, dev, &cunit, udcmask); if (tumax != -1) snd_clone_setmaxunit(d->clones, tumax); if (ce != NULL) { udcmask |= snd_c2unit(cunit); *dev = make_dev(&dsp_cdevsw, PCMMINOR(udcmask), UID_ROOT, GID_WHEEL, 0666, "%s%d%s%d", devname, unit, devsep, cunit); snd_clone_register(ce, *dev); } PCM_RELEASE_QUICK(d); if (*dev != NULL) dev_ref(*dev); } static void dsp_sysinit(void *p) { if (dsp_ehtag != NULL) return; /* initialize unit numbering */ snd_unit_init(); dsp_umax = PCMMAXUNIT; dsp_cmax = PCMMAXCHAN; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { if (dsp_ehtag == NULL) return; EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); char * dsp_unit2name(char *buf, size_t len, int unit) { int i, dtype; KASSERT(buf != NULL && len != 0, ("bogus buf=%p len=%ju", buf, (uintmax_t)len)); dtype = snd_unit2d(unit); for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { if (dtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) continue; snprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name, snd_unit2u(unit), dsp_cdevs[i].sep, snd_unit2c(unit)); return (buf); } return (NULL); } /** * @brief Handler for SNDCTL_AUDIOINFO. * * Gathers information about the audio device specified in ai->dev. If * ai->dev == -1, then this function gathers information about the current * device. If the call comes in on a non-audio device and ai->dev == -1, * return EINVAL. * * This routine is supposed to go practically straight to the hardware, * getting capabilities directly from the sound card driver, side-stepping * the intermediate channel interface. * * Note, however, that the usefulness of this command is significantly * decreased when requesting info about any device other than the one serving * the request. While each snddev_channel refers to a specific device node, * the converse is *not* true. Currently, when a sound device node is opened, * the sound subsystem scans for an available audio channel (or channels, if * opened in read+write) and then assigns them to the si_drv[12] private * data fields. As a result, any information returned linking a channel to * a specific character device isn't necessarily accurate. * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @param dev device on which the ioctl was issued * @param ai ioctl request data container * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device * * @todo Verify correctness of Doxygen tags. ;) */ int dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, nchan, *rates, minch, maxch; char *devname, buf[CHN_NAMELEN]; /* * If probing the device that received the ioctl, make sure it's a * DSP device. (Users may use this ioctl with /dev/mixer and * /dev/midi.) */ if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) return (EINVAL); ch = NULL; devname = NULL; nchan = 0; bzero(buf, sizeof(buf)); /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. */ for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See the note in function docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); if (ai->dev == -1) { if (DSP_REGISTERED(d, i_dev) && (ch == PCM_RDCH(i_dev) || /* record ch */ ch == PCM_WRCH(i_dev))) { /* playback ch */ devname = dsp_unit2name(buf, sizeof(buf), ch->unit); } } else if (ai->dev == nchan) { devname = dsp_unit2name(buf, sizeof(buf), ch->unit); } if (devname != NULL) break; CHN_UNLOCK(ch); ++nchan; } if (devname != NULL) { /* * At this point, the following synchronization stuff * has happened: * - a specific PCM device is locked. * - a specific audio channel has been locked, so be * sure to unlock when exiting; */ caps = chn_getcaps(ch); /* * With all handles collected, zero out the user's * container and begin filling in its fields. */ bzero((void *)ai, sizeof(oss_audioinfo)); ai->dev = nchan; strlcpy(ai->name, ch->name, sizeof(ai->name)); if ((ch->flags & CHN_F_BUSY) == 0) ai->busy = 0; else ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; /** * @note * @c cmd - OSSv4 docs: "Only supported under Linux at * this moment." Cop-out, I know, but I'll save * running around in the process table for later. * Is there a risk of leaking information? */ ai->pid = ch->pid; /* * These flags stolen from SNDCTL_DSP_GETCAPS handler. * Note, however, that a single channel operates in * only one direction, so PCM_CAP_DUPLEX is out. */ /** * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep * these in pcmchan::caps? */ ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); /* * Collect formats supported @b natively by the * device. Also determine min/max channels. (I.e., * mono, stereo, or both?) * * If any channel is stereo, maxch = 2; * if all channels are stereo, minch = 2, too; * if any channel is mono, minch = 1; * and if all channels are mono, maxch = 1. */ minch = 0; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= caps->fmtlist[i]; if (AFMT_CHANNEL(caps->fmtlist[i]) > 1) { minch = (minch == 0) ? 2 : minch; maxch = 2; } else { minch = 1; maxch = (maxch == 0) ? 1 : maxch; } } if (ch->direction == PCMDIR_PLAY) ai->oformats = fmts; else ai->iformats = fmts; /** * @note * @c magic - OSSv4 docs: "Reserved for internal use * by OSS." * * @par * @c card_number - OSSv4 docs: "Number of the sound * card where this device belongs or -1 if this * information is not available. Applications * should normally not use this field for any * purpose." */ ai->card_number = -1; /** * @todo @c song_name - depends first on * SNDCTL_[GS]ETSONG @todo @c label - depends * on SNDCTL_[GS]ETLABEL * @todo @c port_number - routing information? */ ai->port_number = -1; ai->mixer_dev = (d->mixer_dev != NULL) ? PCMUNIT(d->mixer_dev) : -1; /** * @note * @c real_device - OSSv4 docs: "Obsolete." */ ai->real_device = -1; strlcpy(ai->devnode, "/dev/", sizeof(ai->devnode)); strlcat(ai->devnode, devname, sizeof(ai->devnode)); ai->enabled = device_is_attached(d->dev) ? 1 : 0; /** * @note * @c flags - OSSv4 docs: "Reserved for future use." * * @note * @c binding - OSSv4 docs: "Reserved for future use." * * @todo @c handle - haven't decided how to generate * this yet; bus, vendor, device IDs? */ ai->min_rate = caps->minspeed; ai->max_rate = caps->maxspeed; ai->min_channels = minch; ai->max_channels = maxch; ai->nrates = chn_getrates(ch, &rates); if (ai->nrates > OSS_MAX_SAMPLE_RATES) ai->nrates = OSS_MAX_SAMPLE_RATES; for (i = 0; i < ai->nrates; i++) ai->rates[i] = rates[i]; ai->next_play_engine = 0; ai->next_rec_engine = 0; CHN_UNLOCK(ch); } PCM_UNLOCK(d); if (devname != NULL) return (0); } /* Exhausted the search -- nothing is locked, so return. */ return (EINVAL); } /** * @brief Assigns a PCM channel to a sync group. * * Sync groups are used to enable audio operations on multiple devices * simultaneously. They may be used with any number of devices and may * span across applications. Devices are added to groups with * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the * SNDCTL_DSP_SYNCSTART ioctl. * * If the @c id field of the @c group parameter is set to zero, then a new * sync group is created. Otherwise, wrch and rdch (if set) are added to * the group specified. * * @todo As far as memory allocation, should we assume that things are * okay and allocate with M_WAITOK before acquiring channel locks, * freeing later if not? * * @param wrch output channel associated w/ device (if any) * @param rdch input channel associated w/ device (if any) * @param group Sync group parameters * * @retval 0 success * @retval non-zero error to be propagated upstream */ static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group) { struct pcmchan_syncmember *smrd, *smwr; struct pcmchan_syncgroup *sg; int ret, sg_ids[3]; smrd = NULL; smwr = NULL; sg = NULL; ret = 0; /* * Free_unr() may sleep, so store released syncgroup IDs until after * all locks are released. */ sg_ids[0] = sg_ids[1] = sg_ids[2] = 0; PCM_SG_LOCK(); /* * - Insert channel(s) into group's member list. * - Set CHN_F_NOTRIGGER on channel(s). * - Stop channel(s). */ /* * If device's channels are already mapped to a group, unmap them. */ if (wrch) { CHN_LOCK(wrch); sg_ids[0] = chn_syncdestroy(wrch); } if (rdch) { CHN_LOCK(rdch); sg_ids[1] = chn_syncdestroy(rdch); } /* * Verify that mode matches character device properites. * - Bail if PCM_ENABLE_OUTPUT && wrch == NULL. * - Bail if PCM_ENABLE_INPUT && rdch == NULL. */ if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) || ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) { ret = EINVAL; goto out; } /* * An id of zero indicates the user wants to create a new * syncgroup. */ if (group->id == 0) { sg = (struct pcmchan_syncgroup *)malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); if (sg != NULL) { SLIST_INIT(&sg->members); sg->id = alloc_unr(pcmsg_unrhdr); group->id = sg->id; SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link); } else ret = ENOMEM; } else { SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { if (sg->id == group->id) break; } if (sg == NULL) ret = EINVAL; } /* Couldn't create or find a syncgroup. Fail. */ if (sg == NULL) goto out; /* * Allocate a syncmember, assign it and a channel together, and * insert into syncgroup. */ if (group->mode & PCM_ENABLE_INPUT) { smrd = (struct pcmchan_syncmember *)malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); if (smrd == NULL) { ret = ENOMEM; goto out; } SLIST_INSERT_HEAD(&sg->members, smrd, link); smrd->parent = sg; smrd->ch = rdch; chn_abort(rdch); rdch->flags |= CHN_F_NOTRIGGER; rdch->sm = smrd; } if (group->mode & PCM_ENABLE_OUTPUT) { smwr = (struct pcmchan_syncmember *)malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); if (smwr == NULL) { ret = ENOMEM; goto out; } SLIST_INSERT_HEAD(&sg->members, smwr, link); smwr->parent = sg; smwr->ch = wrch; chn_abort(wrch); wrch->flags |= CHN_F_NOTRIGGER; wrch->sm = smwr; } out: if (ret != 0) { if (smrd != NULL) free(smrd, M_DEVBUF); if ((sg != NULL) && SLIST_EMPTY(&sg->members)) { sg_ids[2] = sg->id; SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); free(sg, M_DEVBUF); } if (wrch) wrch->sm = NULL; if (rdch) rdch->sm = NULL; } if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_SG_UNLOCK(); if (sg_ids[0]) free_unr(pcmsg_unrhdr, sg_ids[0]); if (sg_ids[1]) free_unr(pcmsg_unrhdr, sg_ids[1]); if (sg_ids[2]) free_unr(pcmsg_unrhdr, sg_ids[2]); return (ret); } /** * @brief Launch a sync group into action * * Sync groups are established via SNDCTL_DSP_SYNCGROUP. This function * iterates over all members, triggering them along the way. * * @note Caller must not hold any channel locks. * * @param sg_id sync group identifier * * @retval 0 success * @retval non-zero error worthy of propagating upstream to user */ static int dsp_oss_syncstart(int sg_id) { struct pcmchan_syncmember *sm, *sm_tmp; struct pcmchan_syncgroup *sg; struct pcm_channel *c; int ret, needlocks; /* Get the synclists lock */ PCM_SG_LOCK(); do { ret = 0; needlocks = 0; /* Search for syncgroup by ID */ SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { if (sg->id == sg_id) break; } /* Return EINVAL if not found */ if (sg == NULL) { ret = EINVAL; break; } /* Any removals resulting in an empty group should've handled this */ KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup")); /* * Attempt to lock all member channels - if any are already * locked, unlock those acquired, sleep for a bit, and try * again. */ SLIST_FOREACH(sm, &sg->members, link) { if (CHN_TRYLOCK(sm->ch) == 0) { int timo = hz * 5/1000; if (timo < 1) timo = 1; /* Release all locked channels so far, retry */ SLIST_FOREACH(sm_tmp, &sg->members, link) { /* sm is the member already locked */ if (sm == sm_tmp) break; CHN_UNLOCK(sm_tmp->ch); } /** @todo Is PRIBIO correct/ */ ret = msleep(sm, &snd_pcm_syncgroups_mtx, PRIBIO | PCATCH, "pcmsg", timo); if (ret == EINTR || ret == ERESTART) break; needlocks = 1; ret = 0; /* Assumes ret == EAGAIN... */ } } } while (needlocks && ret == 0); /* Proceed only if no errors encountered. */ if (ret == 0) { /* Launch channels */ while ((sm = SLIST_FIRST(&sg->members)) != NULL) { SLIST_REMOVE_HEAD(&sg->members, link); c = sm->ch; c->sm = NULL; chn_start(c, 1); c->flags &= ~CHN_F_NOTRIGGER; CHN_UNLOCK(c); free(sm, M_DEVBUF); } SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); free(sg, M_DEVBUF); } PCM_SG_UNLOCK(); /* * Free_unr() may sleep, so be sure to give up the syncgroup lock * first. */ if (ret == 0) free_unr(pcmsg_unrhdr, sg_id); return (ret); } /** * @brief Handler for SNDCTL_DSP_POLICY * * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment * size and count like with SNDCTL_DSP_SETFRAGMENT. Instead of the user * specifying those two parameters, s/he simply selects a number from 0..10 * which corresponds to a buffer size. Smaller numbers request smaller * buffers with lower latencies (at greater overhead from more frequent * interrupts), while greater numbers behave in the opposite manner. * * The 4Front spec states that a value of 5 should be the default. However, * this implementation deviates slightly by using a linear scale without * consulting drivers. I.e., even though drivers may have different default * buffer sizes, a policy argument of 5 will have the same result across * all drivers. * * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for * more information. * * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to * work with hardware drivers directly. * * @note PCM channel arguments must not be locked by caller. * * @param wrch Pointer to opened playback channel (optional; may be NULL) * @param rdch " recording channel (optional; may be NULL) * @param policy Integer from [0:10] * * @retval 0 constant (for now) */ static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) { int ret; if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) return (EIO); /* Default: success */ ret = 0; if (rdch) { CHN_LOCK(rdch); ret = chn_setlatency(rdch, policy); CHN_UNLOCK(rdch); } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setlatency(wrch, policy); CHN_UNLOCK(wrch); } if (ret) ret = EIO; return (ret); } /** * @brief Enable or disable "cooked" mode * * This is a handler for @c SNDCTL_DSP_COOKEDMODE. When in cooked mode, which * is the default, the sound system handles rate and format conversions * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only * operates with 44100Hz/16bit/signed samples). * * Disabling cooked mode is intended for applications wanting to mmap() * a sound card's buffer space directly, bypassing the FreeBSD 2-stage * feeder architecture, presumably to gain as much control over audio * hardware as possible. * * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html * for more details. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param enabled 0 = raw mode, 1 = cooked mode * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled) { /* * XXX I just don't get it. Why don't they call it * "BITPERFECT" ~ SNDCTL_DSP_BITPERFECT !?!?. * This is just plain so confusing, incoherent, * . */ if (!(enabled == 1 || enabled == 0)) return (EINVAL); /* * I won't give in. I'm inverting its logic here and now. * Brag all you want, but "BITPERFECT" should be the better * term here. */ enabled ^= 0x00000001; if (wrch != NULL) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_BITPERFECT; wrch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_BITPERFECT; rdch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(rdch); } return (0); } /** * @brief Retrieve channel interleaving order * * This is the handler for @c SNDCTL_DSP_GET_CHNORDER. * * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_DSP_GET_CHNORDER. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param map channel map (result will be stored there) * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) { struct pcm_channel *ch; int ret; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getorder(ch, map); CHN_UNLOCK(ch); } else ret = EINVAL; return (ret); } /** * @brief Specify channel interleaving order * * This is the handler for @c SNDCTL_DSP_SET_CHNORDER. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support @c SNDCTL_DSP_SET_CHNORDER. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param map channel map * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) { int ret; ret = 0; if (wrch != NULL) { CHN_LOCK(wrch); ret = chn_oss_setorder(wrch, map); CHN_UNLOCK(wrch); } if (ret == 0 && rdch != NULL) { CHN_LOCK(rdch); ret = chn_oss_setorder(rdch, map); CHN_UNLOCK(rdch); } return (ret); } static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask) { struct pcm_channel *ch; uint32_t chnmask; int ret; chnmask = 0; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getmask(ch, &chnmask); CHN_UNLOCK(ch); } else ret = EINVAL; if (ret == 0) *mask = chnmask; return (ret); } #ifdef OSSV4_EXPERIMENT /** * @brief Retrieve an audio device's label * * This is a handler for the @c SNDCTL_GETLABEL ioctl. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html * for more details. * * From Hannu@4Front: "For example ossxmix (just like some HW mixer * consoles) can show variable "labels" for certain controls. By default * the application name (say quake) is shown as the label but * applications may change the labels themselves." * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support @c SNDCTL_GETLABEL. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param label label gets copied here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) { return (EINVAL); } /** * @brief Specify an audio device's label * * This is a handler for the @c SNDCTL_SETLABEL ioctl. Please see the * comments for @c dsp_oss_getlabel immediately above. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETLABEL. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param label label gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) { return (EINVAL); } /** * @brief Retrieve name of currently played song * * This is a handler for the @c SNDCTL_GETSONG ioctl. Audio players could * tell the system the name of the currently playing song, which would be * visible in @c /dev/sndstat. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_GETSONG. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param song song name gets copied here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) { return (EINVAL); } /** * @brief Retrieve name of currently played song * * This is a handler for the @c SNDCTL_SETSONG ioctl. Audio players could * tell the system the name of the currently playing song, which would be * visible in @c /dev/sndstat. * * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETSONG. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param song song name gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) { return (EINVAL); } /** * @brief Rename a device * * This is a handler for the @c SNDCTL_SETNAME ioctl. * * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for * more details. * * From Hannu@4Front: "This call is used to change the device name * reported in /dev/sndstat and ossinfo. So instead of using some generic * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull * name depending on the current context (for example 'OSS virtual wave table * synth' or 'VoIP link to London')." * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETNAME. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param name new device name gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name) { return (EINVAL); } #endif /* !OSSV4_EXPERIMENT */ Index: head/sys/dev/sound/pcm/mixer.c =================================================================== --- head/sys/dev/sound/pcm/mixer.c (revision 274034) +++ head/sys/dev/sound/pcm/mixer.c (revision 274035) @@ -1,1550 +1,1548 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include "feeder_if.h" #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); static MALLOC_DEFINE(M_MIXER, "mixer", "mixer"); static int mixer_bypass = 1; SYSCTL_INT(_hw_snd, OID_AUTO, vpc_mixer_bypass, CTLFLAG_RWTUN, &mixer_bypass, 0, "control channel pcm/rec volume, bypassing real mixer device"); #define MIXER_NAMELEN 16 struct snd_mixer { KOBJ_FIELDS; void *devinfo; int busy; int hwvol_muted; int hwvol_mixer; int hwvol_step; int type; device_t dev; u_int32_t hwvol_mute_level; u_int32_t devs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; u_int8_t parent[32]; u_int32_t child[32]; u_int8_t realdev[32]; char name[MIXER_NAMELEN]; struct mtx *lock; oss_mixer_enuminfo enuminfo; /** * Counter is incremented when applications change any of this * mixer's controls. A change in value indicates that persistent * mixer applications should update their displays. */ int modify_counter; }; 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_IGAIN] = 0, [SOUND_MIXER_LINE1] = 75, [SOUND_MIXER_VIDEO] = 75, [SOUND_MIXER_RECLEV] = 75, [SOUND_MIXER_OGAIN] = 50, [SOUND_MIXER_MONITOR] = 75, }; static char* snd_mixernames[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; static d_open_t mixer_open; static d_close_t mixer_close; static d_ioctl_t mixer_ioctl; static struct cdevsw mixer_cdevsw = { .d_version = D_VERSION, .d_open = mixer_open, .d_close = mixer_close, .d_ioctl = mixer_ioctl, .d_name = "mixer", }; /** * Keeps a count of mixer devices; used only by OSSv4 SNDCTL_SYSINFO ioctl. */ int mixer_count = 0; static eventhandler_tag mixer_ehtag = NULL; static struct cdev * mixer_get_devt(device_t dev) { struct snddev_info *snddev; snddev = device_get_softc(dev); return snddev->mixer_dev; } static int mixer_lookup(char *devname) { int i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if (strncmp(devname, snd_mixernames[i], strlen(snd_mixernames[i])) == 0) return i; return -1; } #define MIXER_SET_UNLOCK(x, y) do { \ if ((y) != 0) \ snd_mtxunlock((x)->lock); \ } while (0) #define MIXER_SET_LOCK(x, y) do { \ if ((y) != 0) \ snd_mtxlock((x)->lock); \ } while (0) static int mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d, u_int left, u_int right) { struct pcm_channel *c; int dropmtx, acquiremtx; if (!PCM_REGISTERED(d)) return (EINVAL); if (mtx_owned(m->lock)) dropmtx = 1; else dropmtx = 0; if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0) acquiremtx = 0; else acquiremtx = 1; /* * Be careful here. If we're coming from cdev ioctl, it is OK to * not doing locking AT ALL (except on individual channel) since * we've been heavily guarded by pcm cv, or if we're still * under Giant influence. Since we also have mix_* calls, we cannot * assume such protection and just do the lock as usuall. */ MIXER_SET_UNLOCK(m, dropmtx); MIXER_SET_LOCK(d, acquiremtx); CHN_FOREACH(c, d, channels.pcm.busy) { CHN_LOCK(c); if (c->direction == PCMDIR_PLAY && (c->feederflags & (1 << FEEDER_VOLUME))) chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right, (left + right) >> 1); CHN_UNLOCK(c); } MIXER_SET_UNLOCK(d, acquiremtx); MIXER_SET_LOCK(m, dropmtx); return (0); } static int mixer_set_eq(struct snd_mixer *m, struct snddev_info *d, u_int dev, u_int level) { struct pcm_channel *c; struct pcm_feeder *f; int tone, dropmtx, acquiremtx; if (dev == SOUND_MIXER_TREBLE) tone = FEEDEQ_TREBLE; else if (dev == SOUND_MIXER_BASS) tone = FEEDEQ_BASS; else return (EINVAL); if (!PCM_REGISTERED(d)) return (EINVAL); if (mtx_owned(m->lock)) dropmtx = 1; else dropmtx = 0; if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0) acquiremtx = 0; else acquiremtx = 1; /* * Be careful here. If we're coming from cdev ioctl, it is OK to * not doing locking AT ALL (except on individual channel) since * we've been heavily guarded by pcm cv, or if we're still * under Giant influence. Since we also have mix_* calls, we cannot * assume such protection and just do the lock as usuall. */ MIXER_SET_UNLOCK(m, dropmtx); MIXER_SET_LOCK(d, acquiremtx); CHN_FOREACH(c, d, channels.pcm.busy) { CHN_LOCK(c); f = chn_findfeeder(c, FEEDER_EQ); if (f != NULL) (void)FEEDER_SET(f, tone, level); CHN_UNLOCK(c); } MIXER_SET_UNLOCK(d, acquiremtx); MIXER_SET_LOCK(m, dropmtx); return (0); } static int mixer_set(struct snd_mixer *m, u_int dev, u_int lev) { struct snddev_info *d; u_int l, r, tl, tr; u_int32_t parent = SOUND_MIXER_NONE, child = 0; u_int32_t realdev; int i, dropmtx; if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || (0 == (m->devs & (1 << dev)))) return -1; l = min((lev & 0x00ff), 100); r = min(((lev & 0xff00) >> 8), 100); realdev = m->realdev[dev]; d = device_get_softc(m->dev); if (d == NULL) return -1; /* It is safe to drop this mutex due to Giant. */ if (!(d->flags & SD_F_MPSAFE) && mtx_owned(m->lock) != 0) dropmtx = 1; else dropmtx = 0; MIXER_SET_UNLOCK(m, dropmtx); /* TODO: recursive handling */ parent = m->parent[dev]; if (parent >= SOUND_MIXER_NRDEVICES) parent = SOUND_MIXER_NONE; if (parent == SOUND_MIXER_NONE) child = m->child[dev]; if (parent != SOUND_MIXER_NONE) { tl = (l * (m->level[parent] & 0x00ff)) / 100; tr = (r * ((m->level[parent] & 0xff00) >> 8)) / 100; if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) (void)mixer_set_softpcmvol(m, d, tl, tr); else if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, tl, tr) < 0) { MIXER_SET_LOCK(m, dropmtx); return -1; } } else if (child != 0) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(child & (1 << i)) || m->parent[i] != dev) continue; realdev = m->realdev[i]; tl = (l * (m->level[i] & 0x00ff)) / 100; tr = (r * ((m->level[i] & 0xff00) >> 8)) / 100; if (i == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) (void)mixer_set_softpcmvol(m, d, tl, tr); else if (realdev != SOUND_MIXER_NONE) MIXER_SET(m, realdev, tl, tr); } realdev = m->realdev[dev]; if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, l, r) < 0) { MIXER_SET_LOCK(m, dropmtx); return -1; } } else { if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) (void)mixer_set_softpcmvol(m, d, l, r); else if ((dev == SOUND_MIXER_TREBLE || dev == SOUND_MIXER_BASS) && (d->flags & SD_F_EQ)) (void)mixer_set_eq(m, d, dev, (l + r) >> 1); else if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, l, r) < 0) { MIXER_SET_LOCK(m, dropmtx); return -1; } } MIXER_SET_LOCK(m, dropmtx); m->level[dev] = l | (r << 8); return 0; } static int mixer_get(struct snd_mixer *mixer, int dev) { if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) return mixer->level[dev]; else return -1; } static int mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src) { struct snddev_info *d; u_int32_t recsrc; int dropmtx; d = device_get_softc(mixer->dev); if (d == NULL) return -1; if (!(d->flags & SD_F_MPSAFE) && mtx_owned(mixer->lock) != 0) dropmtx = 1; else dropmtx = 0; src &= mixer->recdevs; if (src == 0) src = mixer->recdevs & SOUND_MASK_MIC; if (src == 0) src = mixer->recdevs & SOUND_MASK_MONITOR; if (src == 0) src = mixer->recdevs & SOUND_MASK_LINE; if (src == 0 && mixer->recdevs != 0) src = (1 << (ffs(mixer->recdevs) - 1)); /* It is safe to drop this mutex due to Giant. */ MIXER_SET_UNLOCK(mixer, dropmtx); recsrc = MIXER_SETRECSRC(mixer, src); MIXER_SET_LOCK(mixer, dropmtx); mixer->recsrc = recsrc; return 0; } static int mixer_getrecsrc(struct snd_mixer *mixer) { return mixer->recsrc; } /** * @brief Retrieve the route number of the current recording device * * OSSv4 assigns routing numbers to recording devices, unlike the previous * API which relied on a fixed table of device numbers and names. This * function returns the routing number of the device currently selected * for recording. * * For now, this function is kind of a goofy compatibility stub atop the * existing sound system. (For example, in theory, the old sound system * allows multiple recording devices to be specified via a bitmask.) * * @param m mixer context container thing * * @retval 0 success * @retval EIDRM no recording device found (generally not possible) * @todo Ask about error code */ static int mixer_get_recroute(struct snd_mixer *m, int *route) { int i, cnt; cnt = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { /** @todo can user set a multi-device mask? (== or &?) */ if ((1 << i) == m->recsrc) break; if ((1 << i) & m->recdevs) ++cnt; } if (i == SOUND_MIXER_NRDEVICES) return EIDRM; *route = cnt; return 0; } /** * @brief Select a device for recording * * This function sets a recording source based on a recording device's * routing number. Said number is translated to an old school recdev * mask and passed over mixer_setrecsrc. * * @param m mixer context container thing * * @retval 0 success(?) * @retval EINVAL User specified an invalid device number * @retval otherwise error from mixer_setrecsrc */ static int mixer_set_recroute(struct snd_mixer *m, int route) { int i, cnt, ret; ret = 0; cnt = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1 << i) & m->recdevs) { if (route == cnt) break; ++cnt; } } if (i == SOUND_MIXER_NRDEVICES) ret = EINVAL; else ret = mixer_setrecsrc(m, (1 << i)); return ret; } void mix_setdevs(struct snd_mixer *m, u_int32_t v) { struct snddev_info *d; int i; if (m == NULL) return; d = device_get_softc(m->dev); if (d != NULL && (d->flags & SD_F_SOFTPCMVOL)) v |= SOUND_MASK_PCM; if (d != NULL && (d->flags & SD_F_EQ)) v |= SOUND_MASK_TREBLE | SOUND_MASK_BASS; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (m->parent[i] < SOUND_MIXER_NRDEVICES) v |= 1 << m->parent[i]; v |= m->child[i]; } m->devs = v; } /** * @brief Record mask of available recording devices * * Calling functions are responsible for defining the mask of available * recording devices. This function records that value in a structure * used by the rest of the mixer code. * * This function also populates a structure used by the SNDCTL_DSP_*RECSRC* * family of ioctls that are part of OSSV4. All recording device labels * are concatenated in ascending order corresponding to their routing * numbers. (Ex: a system might have 0 => 'vol', 1 => 'cd', 2 => 'line', * etc.) For now, these labels are just the standard recording device * names (cd, line1, etc.), but will eventually be fully dynamic and user * controlled. * * @param m mixer device context container thing * @param v mask of recording devices */ void mix_setrecdevs(struct snd_mixer *m, u_int32_t v) { oss_mixer_enuminfo *ei; char *loc; int i, nvalues, nwrote, nleft, ncopied; ei = &m->enuminfo; nvalues = 0; nwrote = 0; nleft = sizeof(ei->strings); loc = ei->strings; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1 << i) & v) { ei->strindex[nvalues] = nwrote; ncopied = strlcpy(loc, snd_mixernames[i], nleft) + 1; /* strlcpy retval doesn't include terminator */ nwrote += ncopied; nleft -= ncopied; nvalues++; /* * XXX I don't think this should ever be possible. * Even with a move to dynamic device/channel names, * each label is limited to ~16 characters, so that'd * take a LOT to fill this buffer. */ if ((nleft <= 0) || (nvalues >= OSS_ENUM_MAXVALUE)) { device_printf(m->dev, "mix_setrecdevs: Not enough room to store device names--please file a bug report.\n"); device_printf(m->dev, "mix_setrecdevs: Please include details about your sound hardware, OS version, etc.\n"); break; } loc = &ei->strings[nwrote]; } } /* * NB: The SNDCTL_DSP_GET_RECSRC_NAMES ioctl ignores the dev * and ctrl fields. */ ei->nvalues = nvalues; m->recdevs = v; } void mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs) { u_int32_t mask = 0; int i; if (m == NULL || parent >= SOUND_MIXER_NRDEVICES) return; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (i == parent) continue; if (childs & (1 << i)) { mask |= 1 << i; if (m->parent[i] < SOUND_MIXER_NRDEVICES) m->child[m->parent[i]] &= ~(1 << i); m->parent[i] = parent; m->child[i] = 0; } } mask &= ~(1 << parent); m->child[parent] = mask; } void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev) { if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || !(realdev == SOUND_MIXER_NONE || realdev < SOUND_MIXER_NRDEVICES)) return; m->realdev[dev] = realdev; } u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev) { if (m == NULL || dev >= SOUND_MIXER_NRDEVICES) return SOUND_MIXER_NONE; return m->parent[dev]; } u_int32_t mix_getchild(struct snd_mixer *m, u_int32_t dev) { if (m == NULL || dev >= SOUND_MIXER_NRDEVICES) return 0; return m->child[dev]; } u_int32_t mix_getdevs(struct snd_mixer *m) { return m->devs; } u_int32_t mix_getrecdevs(struct snd_mixer *m) { return m->recdevs; } void * mix_getdevinfo(struct snd_mixer *m) { return m->devinfo; } static struct snd_mixer * mixer_obj_create(device_t dev, kobj_class_t cls, void *devinfo, int type, const char *desc) { struct snd_mixer *m; int i; KASSERT(dev != NULL && cls != NULL && devinfo != NULL, ("%s(): NULL data dev=%p cls=%p devinfo=%p", __func__, dev, cls, devinfo)); KASSERT(type == MIXER_TYPE_PRIMARY || type == MIXER_TYPE_SECONDARY, ("invalid mixer type=%d", type)); m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); snprintf(m->name, sizeof(m->name), "%s:mixer", device_get_nameunit(dev)); if (desc != NULL) { strlcat(m->name, ":", sizeof(m->name)); strlcat(m->name, desc, sizeof(m->name)); } m->lock = snd_mtxcreate(m->name, (type == MIXER_TYPE_PRIMARY) ? "primary pcm mixer" : "secondary pcm mixer"); m->type = type; m->devinfo = devinfo; m->busy = 0; m->dev = dev; for (i = 0; i < (sizeof(m->parent) / sizeof(m->parent[0])); i++) { m->parent[i] = SOUND_MIXER_NONE; m->child[i] = 0; m->realdev[i] = i; } if (MIXER_INIT(m)) { snd_mtxlock(m->lock); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); return (NULL); } return (m); } int mixer_delete(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); KASSERT(m->type == MIXER_TYPE_SECONDARY, ("%s(): illegal mixer type=%d", __func__, m->type)); /* mixer uninit can sleep --hps */ MIXER_UNINIT(m); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); --mixer_count; return (0); } struct snd_mixer * mixer_create(device_t dev, kobj_class_t cls, void *devinfo, const char *desc) { struct snd_mixer *m; m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_SECONDARY, desc); if (m != NULL) ++mixer_count; return (m); } int mixer_init(device_t dev, kobj_class_t cls, void *devinfo) { struct snddev_info *snddev; struct snd_mixer *m; u_int16_t v; struct cdev *pdev; int i, unit, devunit, val; snddev = device_get_softc(dev); if (snddev == NULL) return (-1); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "eq", &val) == 0 && val != 0) { snddev->flags |= SD_F_EQ; if ((val & SD_F_EQ_MASK) == val) snddev->flags |= val; else snddev->flags |= SD_F_EQ_DEFAULT; snddev->eqpreamp = 0; } m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_PRIMARY, NULL); if (m == NULL) return (-1); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { v = snd_mixerdefaults[i]; if (resource_int_value(device_get_name(dev), device_get_unit(dev), snd_mixernames[i], &val) == 0) { if (val >= 0 && val <= 100) { v = (u_int16_t) val; } } mixer_set(m, i, v | (v << 8)); } mixer_setrecsrc(m, 0); /* Set default input. */ unit = device_get_unit(dev); devunit = snd_mkunit(unit, SND_DEV_CTL, 0); pdev = make_dev(&mixer_cdevsw, PCMMINOR(devunit), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; snddev->mixer_dev = pdev; ++mixer_count; if (bootverbose) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(m->devs & (1 << i))) continue; if (m->realdev[i] != i) { device_printf(dev, "Mixer \"%s\" -> \"%s\":", snd_mixernames[i], (m->realdev[i] < SOUND_MIXER_NRDEVICES) ? snd_mixernames[m->realdev[i]] : "none"); } else { device_printf(dev, "Mixer \"%s\":", snd_mixernames[i]); } if (m->parent[i] < SOUND_MIXER_NRDEVICES) printf(" parent=\"%s\"", snd_mixernames[m->parent[i]]); if (m->child[i] != 0) printf(" child=0x%08x", m->child[i]); printf("\n"); } if (snddev->flags & SD_F_SOFTPCMVOL) device_printf(dev, "Soft PCM mixer ENABLED\n"); if (snddev->flags & SD_F_EQ) device_printf(dev, "EQ Treble/Bass ENABLED\n"); } return (0); } int mixer_uninit(device_t dev) { int i; struct snddev_info *d; struct snd_mixer *m; struct cdev *pdev; d = device_get_softc(dev); pdev = mixer_get_devt(dev); if (d == NULL || pdev == NULL || pdev->si_drv1 == NULL) return EBADF; m = pdev->si_drv1; KASSERT(m != NULL, ("NULL snd_mixer")); KASSERT(m->type == MIXER_TYPE_PRIMARY, ("%s(): illegal mixer type=%d", __func__, m->type)); snd_mtxlock(m->lock); if (m->busy) { snd_mtxunlock(m->lock); return EBUSY; } /* destroy dev can sleep --hps */ snd_mtxunlock(m->lock); pdev->si_drv1 = NULL; destroy_dev(pdev); snd_mtxlock(m->lock); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(m, i, 0); mixer_setrecsrc(m, SOUND_MASK_MIC); snd_mtxunlock(m->lock); /* mixer uninit can sleep --hps */ MIXER_UNINIT(m); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); d->mixer_dev = NULL; --mixer_count; return 0; } int mixer_reinit(device_t dev) { struct snd_mixer *m; struct cdev *pdev; int i; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); i = MIXER_REINIT(m); if (i) { snd_mtxunlock(m->lock); return i; } for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(m, i, m->level[i]); mixer_setrecsrc(m, m->recsrc); snd_mtxunlock(m->lock); return 0; } static int sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS) { char devname[32]; int error, dev; struct snd_mixer *m; m = oidp->oid_arg1; snd_mtxlock(m->lock); strlcpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); if (error == 0 && req->newptr != NULL) { dev = mixer_lookup(devname); if (dev == -1) { snd_mtxunlock(m->lock); return EINVAL; } else if (dev != m->hwvol_mixer) { m->hwvol_mixer = dev; m->hwvol_muted = 0; } } snd_mtxunlock(m->lock); return error; } int mixer_hwvol_init(device_t dev) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); return 0; } void mixer_hwvol_mute_locked(struct snd_mixer *m) { if (m->hwvol_muted) { m->hwvol_muted = 0; mixer_set(m, m->hwvol_mixer, m->hwvol_mute_level); } else { m->hwvol_muted++; m->hwvol_mute_level = mixer_get(m, m->hwvol_mixer); mixer_set(m, m->hwvol_mixer, 0); } } void mixer_hwvol_mute(device_t dev) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); mixer_hwvol_mute_locked(m); snd_mtxunlock(m->lock); } void mixer_hwvol_step_locked(struct snd_mixer *m, int left_step, int right_step) { int level, left, right; if (m->hwvol_muted) { m->hwvol_muted = 0; level = m->hwvol_mute_level; } else level = mixer_get(m, m->hwvol_mixer); if (level != -1) { left = level & 0xff; right = (level >> 8) & 0xff; left += left_step * m->hwvol_step; if (left < 0) left = 0; else if (left > 100) left = 100; right += right_step * m->hwvol_step; if (right < 0) right = 0; else if (right > 100) right = 100; mixer_set(m, m->hwvol_mixer, left | right << 8); } } void mixer_hwvol_step(device_t dev, int left_step, int right_step) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); mixer_hwvol_step_locked(m, left_step, right_step); snd_mtxunlock(m->lock); } int mixer_busy(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); return (m->busy); } int mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right) { int ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_set(m, dev, left | (right << 8)); snd_mtxunlock(m->lock); return ((ret != 0) ? ENXIO : 0); } int mix_get(struct snd_mixer *m, u_int dev) { int ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_get(m, dev); snd_mtxunlock(m->lock); return (ret); } int mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { int ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_setrecsrc(m, src); snd_mtxunlock(m->lock); return ((ret != 0) ? ENXIO : 0); } u_int32_t mix_getrecsrc(struct snd_mixer *m) { u_int32_t ret; KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); ret = mixer_getrecsrc(m); snd_mtxunlock(m->lock); return (ret); } int mix_get_type(struct snd_mixer *m) { KASSERT(m != NULL, ("NULL snd_mixer")); return (m->type); } /* ----------------------------------------------------------------------- */ static int mixer_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snddev_info *d; struct snd_mixer *m; if (i_dev == NULL || i_dev->si_drv1 == NULL) return (EBADF); m = i_dev->si_drv1; d = device_get_softc(m->dev); if (!PCM_REGISTERED(d)) return (EBADF); /* XXX Need Giant magic entry ??? */ snd_mtxlock(m->lock); m->busy = 1; snd_mtxunlock(m->lock); return (0); } static int mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snddev_info *d; struct snd_mixer *m; int ret; if (i_dev == NULL || i_dev->si_drv1 == NULL) return (EBADF); m = i_dev->si_drv1; d = device_get_softc(m->dev); if (!PCM_REGISTERED(d)) return (EBADF); /* XXX Need Giant magic entry ??? */ snd_mtxlock(m->lock); ret = (m->busy == 0) ? EBADF : 0; m->busy = 0; snd_mtxunlock(m->lock); return (ret); } static int mixer_ioctl_channel(struct cdev *dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from) { struct snddev_info *d; struct snd_mixer *m; struct pcm_channel *c, *rdch, *wrch; pid_t pid; int j, ret; if (td == NULL || td->td_proc == NULL) return (-1); m = dev->si_drv1; d = device_get_softc(m->dev); j = cmd & 0xff; switch (j) { case SOUND_MIXER_PCM: case SOUND_MIXER_RECLEV: case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: break; default: return (-1); break; } pid = td->td_proc->p_pid; rdch = NULL; wrch = NULL; c = NULL; ret = -1; /* * This is unfair. Imagine single proc opening multiple * instances of same direction. What we do right now * is looking for the first matching proc/pid, and just * that. Nothing more. Consider it done. * * The better approach of controlling specific channel * pcm or rec volume is by doing mixer ioctl * (SNDCTL_DSP_[SET|GET][PLAY|REC]VOL / SOUND_MIXER_[PCM|RECLEV] * on its open fd, rather than cracky mixer bypassing here. */ CHN_FOREACH(c, d, channels.pcm.opened) { CHN_LOCK(c); if (c->pid != pid || !(c->feederflags & (1 << FEEDER_VOLUME))) { CHN_UNLOCK(c); continue; } if (rdch == NULL && c->direction == PCMDIR_REC) { rdch = c; if (j == SOUND_MIXER_RECLEV) goto mixer_ioctl_channel_proc; } else if (wrch == NULL && c->direction == PCMDIR_PLAY) { wrch = c; if (j == SOUND_MIXER_PCM) goto mixer_ioctl_channel_proc; } CHN_UNLOCK(c); if (rdch != NULL && wrch != NULL) break; } if (rdch == NULL && wrch == NULL) return (-1); if ((j == SOUND_MIXER_DEVMASK || j == SOUND_MIXER_CAPS || j == SOUND_MIXER_STEREODEVS) && (cmd & ~0xff) == MIXER_READ(0)) { snd_mtxlock(m->lock); *(int *)arg = mix_getdevs(m); snd_mtxunlock(m->lock); if (rdch != NULL) *(int *)arg |= SOUND_MASK_RECLEV; if (wrch != NULL) *(int *)arg |= SOUND_MASK_PCM; ret = 0; } return (ret); mixer_ioctl_channel_proc: KASSERT(c != NULL, ("%s(): NULL channel", __func__)); CHN_LOCKASSERT(c); if ((cmd & ~0xff) == MIXER_WRITE(0)) { int left, right, center; left = *(int *)arg & 0x7f; right = (*(int *)arg >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(c, SND_VOL_C_PCM, left, right, center); } else if ((cmd & ~0xff) == MIXER_READ(0)) { *(int *)arg = CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; } CHN_UNLOCK(c); return (0); } static int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct snddev_info *d; int ret; if (i_dev == NULL || i_dev->si_drv1 == NULL) return (EBADF); d = device_get_softc(((struct snd_mixer *)i_dev->si_drv1)->dev); if (!PCM_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); PCM_ACQUIRE_QUICK(d); ret = -1; if (mixer_bypass != 0 && (d->flags & SD_F_VPC)) ret = mixer_ioctl_channel(i_dev, cmd, arg, mode, td, MIXER_CMD_CDEV); if (ret == -1) ret = mixer_ioctl_cmd(i_dev, cmd, arg, mode, td, MIXER_CMD_CDEV); PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); return (ret); } static void mixer_mixerinfo(struct snd_mixer *m, mixer_info *mi) { bzero((void *)mi, sizeof(*mi)); strlcpy(mi->id, m->name, sizeof(mi->id)); strlcpy(mi->name, device_get_desc(m->dev), sizeof(mi->name)); mi->modify_counter = m->modify_counter; } /* * XXX Make sure you can guarantee concurrency safety before calling this * function, be it through Giant, PCM_*, etc ! */ int mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from) { struct snd_mixer *m; int ret = EINVAL, *arg_i = (int *)arg; int v = -1, j = cmd & 0xff; /* * Certain ioctls may be made on any type of device (audio, mixer, * and MIDI). Handle those special cases here. */ if (IOCGROUP(cmd) == 'X') { switch (cmd) { case SNDCTL_SYSINFO: sound_oss_sysinfo((oss_sysinfo *)arg); return (0); case SNDCTL_CARDINFO: return (sound_oss_card_info((oss_card_info *)arg)); case SNDCTL_AUDIOINFO: case SNDCTL_AUDIOINFO_EX: case SNDCTL_ENGINEINFO: return (dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg)); case SNDCTL_MIXERINFO: return (mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg)); } return (EINVAL); } m = i_dev->si_drv1; if (m == NULL) return (EBADF); snd_mtxlock(m->lock); if (from == MIXER_CMD_CDEV && !m->busy) { snd_mtxunlock(m->lock); return (EBADF); } switch (cmd) { case SNDCTL_DSP_GET_RECSRC_NAMES: bcopy((void *)&m->enuminfo, arg, sizeof(oss_mixer_enuminfo)); ret = 0; goto done; case SNDCTL_DSP_GET_RECSRC: ret = mixer_get_recroute(m, arg_i); goto done; case SNDCTL_DSP_SET_RECSRC: ret = mixer_set_recroute(m, *arg_i); goto done; case OSS_GETVERSION: *arg_i = SOUND_VERSION; ret = 0; goto done; case SOUND_MIXER_INFO: mixer_mixerinfo(m, (mixer_info *)arg); ret = 0; goto done; } if ((cmd & ~0xff) == MIXER_WRITE(0)) { if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(m, *arg_i); else ret = mixer_set(m, j, *arg_i); snd_mtxunlock(m->lock); return ((ret == 0) ? 0 : ENXIO); } if ((cmd & ~0xff) == MIXER_READ(0)) { switch (j) { case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: v = mix_getdevs(m); break; case SOUND_MIXER_RECMASK: v = mix_getrecdevs(m); break; case SOUND_MIXER_RECSRC: v = mixer_getrecsrc(m); break; default: v = mixer_get(m, j); } *arg_i = v; snd_mtxunlock(m->lock); return ((v != -1) ? 0 : ENXIO); } done: snd_mtxunlock(m->lock); return (ret); } static void mixer_clone(void *arg, -#if __FreeBSD_version >= 600034 struct ucred *cred, -#endif char *name, int namelen, struct cdev **dev) { struct snddev_info *d; if (*dev != NULL) return; if (strcmp(name, "mixer") == 0) { d = devclass_get_softc(pcm_devclass, snd_unit); if (PCM_REGISTERED(d) && d->mixer_dev != NULL) { *dev = d->mixer_dev; dev_ref(*dev); } } } static void mixer_sysinit(void *p) { if (mixer_ehtag != NULL) return; mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); } static void mixer_sysuninit(void *p) { if (mixer_ehtag == NULL) return; EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); mixer_ehtag = NULL; } SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); /** * @brief Handler for SNDCTL_MIXERINFO * * This function searches for a mixer based on the numeric ID stored * in oss_miserinfo::dev. If set to -1, then information about the * current mixer handling the request is provided. Note, however, that * this ioctl may be made with any sound device (audio, mixer, midi). * * @note Caller must not hold any PCM device, channel, or mixer locks. * * See http://manuals.opensound.com/developer/SNDCTL_MIXERINFO.html for * more information. * * @param i_dev character device on which the ioctl arrived * @param arg user argument (oss_mixerinfo *) * * @retval EINVAL oss_mixerinfo::dev specified a bad value * @retval 0 success */ int mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi) { struct snddev_info *d; struct snd_mixer *m; int nmix, i; /* * If probing the device handling the ioctl, make sure it's a mixer * device. (This ioctl is valid on audio, mixer, and midi devices.) */ if (mi->dev == -1 && i_dev->si_devsw != &mixer_cdevsw) return (EINVAL); d = NULL; m = NULL; nmix = 0; /* * There's a 1:1 relationship between mixers and PCM devices, so * begin by iterating over PCM devices and search for our mixer. */ for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry */ /* See the note in function docblock. */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); if (d->mixer_dev != NULL && d->mixer_dev->si_drv1 != NULL && ((mi->dev == -1 && d->mixer_dev == i_dev) || mi->dev == nmix)) { m = d->mixer_dev->si_drv1; mtx_lock(m->lock); /* * At this point, the following synchronization stuff * has happened: * - a specific PCM device is locked. * - a specific mixer device has been locked, so be * sure to unlock when existing. */ bzero((void *)mi, sizeof(*mi)); mi->dev = nmix; snprintf(mi->id, sizeof(mi->id), "mixer%d", i); strlcpy(mi->name, m->name, sizeof(mi->name)); mi->modify_counter = m->modify_counter; mi->card_number = i; /* * Currently, FreeBSD assumes 1:1 relationship between * a pcm and mixer devices, so this is hardcoded to 0. */ mi->port_number = 0; /** * @todo Fill in @sa oss_mixerinfo::mixerhandle. * @note From 4Front: "mixerhandle is an arbitrary * string that identifies the mixer better than * the device number (mixerinfo.dev). Device * numbers may change depending on the order the * drivers are loaded. However the handle should * remain the same provided that the sound card * is not moved to another PCI slot." */ /** * @note * @sa oss_mixerinfo::magic is a reserved field. * * @par * From 4Front: "magic is usually 0. However some * devices may have dedicated setup utilities and the * magic field may contain an unique driver specific * value (managed by [4Front])." */ mi->enabled = device_is_attached(m->dev) ? 1 : 0; /** * The only flag for @sa oss_mixerinfo::caps is * currently MIXER_CAP_VIRTUAL, which I'm not sure we * really worry about. */ /** * Mixer extensions currently aren't supported, so * leave @sa oss_mixerinfo::nrext blank for now. */ /** * @todo Fill in @sa oss_mixerinfo::priority (requires * touching drivers?) * @note The priority field is for mixer applets to * determine which mixer should be the default, with 0 * being least preferred and 10 being most preferred. * From 4Front: "OSS drivers like ICH use higher * values (10) because such chips are known to be used * only on motherboards. Drivers for high end pro * devices use 0 because they will never be the * default mixer. Other devices use values 1 to 9 * depending on the estimated probability of being the * default device. * * XXX Described by Hannu@4Front, but not found in * soundcard.h. strlcpy(mi->devnode, devtoname(d->mixer_dev), sizeof(mi->devnode)); mi->legacy_device = i; */ mtx_unlock(m->lock); } else ++nmix; PCM_UNLOCK(d); if (m != NULL) return (0); } return (EINVAL); } /* * Allow the sound driver to use the mixer lock to protect its mixer * data: */ struct mtx * mixer_get_lock(struct snd_mixer *m) { if (m->lock == NULL) { return (&Giant); } return (m->lock); } int mix_get_locked(struct snd_mixer *m, u_int dev, int *pleft, int *pright) { int level; level = mixer_get(m, dev); if (level < 0) { *pright = *pleft = -1; return (-1); } *pleft = level & 0xFF; *pright = (level >> 8) & 0xFF; return (0); } int mix_set_locked(struct snd_mixer *m, u_int dev, int left, int right) { int level; level = (left & 0xFF) | ((right & 0xFF) << 8); return (mixer_set(m, dev, level)); } Index: head/sys/dev/sound/pcm/sound.c =================================================================== --- head/sys/dev/sound/pcm/sound.c (revision 274034) +++ head/sys/dev/sound/pcm/sound.c (revision 274035) @@ -1,1431 +1,1427 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997 Luigi Rizzo * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); devclass_t pcm_devclass; int pcm_veto_load = 1; int snd_unit = -1; TUNABLE_INT("hw.snd.default_unit", &snd_unit); static int snd_unit_auto = -1; SYSCTL_INT(_hw_snd, OID_AUTO, default_auto, CTLFLAG_RWTUN, &snd_unit_auto, 0, "assign default unit to a newly attached device"); int snd_maxautovchans = 16; /* XXX: a tunable implies that we may need more than one sound channel before the system can change a sysctl (/etc/sysctl.conf), do we really need this? */ TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); /* * XXX I've had enough with people not telling proper version/arch * while reporting problems, not after 387397913213th questions/requests. */ static char snd_driver_version[] = __XSTRING(SND_DRV_VERSION)"/"MACHINE_ARCH; SYSCTL_STRING(_hw_snd, OID_AUTO, version, CTLFLAG_RD, &snd_driver_version, 0, "driver version/arch"); /** * @brief Unit number allocator for syncgroup IDs */ struct unrhdr *pcmsg_unrhdr = NULL; static int sndstat_prepare_pcm(SNDSTAT_PREPARE_PCM_ARGS) { SNDSTAT_PREPARE_PCM_BEGIN(); SNDSTAT_PREPARE_PCM_END(); } void * snd_mtxcreate(const char *desc, const char *type) { struct mtx *m; m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); mtx_init(m, desc, type, MTX_DEF); return m; } void snd_mtxfree(void *m) { struct mtx *mtx = m; mtx_destroy(mtx); free(mtx, M_DEVBUF); } void snd_mtxassert(void *m) { #ifdef INVARIANTS struct mtx *mtx = m; mtx_assert(mtx, MA_OWNED); #endif } int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) { struct snddev_info *d; flags &= INTR_MPSAFE; flags |= INTR_TYPE_AV; d = device_get_softc(dev); if (d != NULL && (flags & INTR_MPSAFE)) d->flags |= SD_F_MPSAFE; - return bus_setup_intr(dev, res, flags, -#if __FreeBSD_version >= 700031 - NULL, -#endif - hand, param, cookiep); + return bus_setup_intr(dev, res, flags, NULL, hand, param, cookiep); } static void pcm_clonereset(struct snddev_info *d) { int cmax; PCM_BUSYASSERT(d); cmax = d->playcount + d->reccount - 1; if (d->pvchancount > 0) cmax += max(d->pvchancount, snd_maxautovchans) - 1; if (d->rvchancount > 0) cmax += max(d->rvchancount, snd_maxautovchans) - 1; if (cmax > PCMMAXCLONE) cmax = PCMMAXCLONE; (void)snd_clone_gc(d->clones); (void)snd_clone_setmaxunit(d->clones, cmax); } int pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num) { struct pcm_channel *c, *ch, *nch; struct pcmchan_caps *caps; int i, err, vcnt; PCM_BUSYASSERT(d); if ((direction == PCMDIR_PLAY && d->playcount < 1) || (direction == PCMDIR_REC && d->reccount < 1)) return (ENODEV); if (!(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); if (newcnt < 0 || newcnt > SND_MAXVCHANS) return (E2BIG); if (direction == PCMDIR_PLAY) vcnt = d->pvchancount; else if (direction == PCMDIR_REC) vcnt = d->rvchancount; else return (EINVAL); if (newcnt > vcnt) { KASSERT(num == -1 || (num >= 0 && num < SND_MAXVCHANS && (newcnt - 1) == vcnt), ("bogus vchan_create() request num=%d newcnt=%d vcnt=%d", num, newcnt, vcnt)); /* add new vchans - find a parent channel first */ ch = NULL; CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->direction == direction && ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 && c->refcount < 1 && !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) { /* * Reuse hw channel with vchans already * created. */ if (c->flags & CHN_F_HAS_VCHAN) { ch = c; break; } /* * No vchans ever created, look for * channels with supported formats. */ caps = chn_getcaps(c); if (caps == NULL) { CHN_UNLOCK(c); continue; } for (i = 0; caps->fmtlist[i] != 0; i++) { if (caps->fmtlist[i] & AFMT_CONVERTIBLE) break; } if (caps->fmtlist[i] != 0) { ch = c; break; } } CHN_UNLOCK(c); } if (ch == NULL) return (EBUSY); ch->flags |= CHN_F_BUSY; err = 0; while (err == 0 && newcnt > vcnt) { err = vchan_create(ch, num); if (err == 0) vcnt++; else if (err == E2BIG && newcnt > vcnt) device_printf(d->dev, "%s: err=%d Maximum channel reached.\n", __func__, err); } if (vcnt == 0) ch->flags &= ~CHN_F_BUSY; CHN_UNLOCK(ch); if (err != 0) return (err); else pcm_clonereset(d); } else if (newcnt < vcnt) { KASSERT(num == -1, ("bogus vchan_destroy() request num=%d", num)); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (c->direction != direction || CHN_EMPTY(c, children) || !(c->flags & CHN_F_HAS_VCHAN)) { CHN_UNLOCK(c); continue; } CHN_FOREACH_SAFE(ch, c, nch, children) { CHN_LOCK(ch); if (vcnt == 1 && c->refcount > 0) { CHN_UNLOCK(ch); break; } if (!(ch->flags & CHN_F_BUSY) && ch->refcount < 1) { err = vchan_destroy(ch); if (err == 0) vcnt--; } else CHN_UNLOCK(ch); if (vcnt == newcnt) break; } CHN_UNLOCK(c); break; } pcm_clonereset(d); } return (0); } /* return error status and a locked channel */ int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, char *comm, int devunit) { struct pcm_channel *c; int err, vchancount, vchan_num; KASSERT(d != NULL && ch != NULL && (devunit == -1 || !(devunit & ~(SND_U_MASK | SND_D_MASK | SND_C_MASK))) && (direction == PCMDIR_PLAY || direction == PCMDIR_REC), ("%s(): invalid d=%p ch=%p direction=%d pid=%d devunit=%d", __func__, d, ch, direction, pid, devunit)); PCM_BUSYASSERT(d); /* Double check again. */ if (devunit != -1) { switch (snd_unit2d(devunit)) { case SND_DEV_DSPHW_PLAY: case SND_DEV_DSPHW_VPLAY: if (direction != PCMDIR_PLAY) return (ENOTSUP); break; case SND_DEV_DSPHW_REC: case SND_DEV_DSPHW_VREC: if (direction != PCMDIR_REC) return (ENOTSUP); break; default: if (!(direction == PCMDIR_PLAY || direction == PCMDIR_REC)) return (ENOTSUP); break; } } *ch = NULL; vchan_num = 0; vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount : d->rvchancount; retry_chnalloc: err = ENOTSUP; /* scan for a free channel */ CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); if (devunit == -1 && c->direction == direction && (c->flags & CHN_F_VIRTUAL)) { if (vchancount < snd_maxautovchans && vchan_num < CHN_CHAN(c)) { CHN_UNLOCK(c); goto vchan_alloc; } vchan_num++; } if (c->direction == direction && !(c->flags & CHN_F_BUSY) && (devunit == -1 || devunit == -2 || c->unit == devunit)) { c->flags |= CHN_F_BUSY; c->pid = pid; strlcpy(c->comm, (comm != NULL) ? comm : CHN_COMM_UNKNOWN, sizeof(c->comm)); *ch = c; return (0); } else if (c->unit == devunit) { if (c->direction != direction) err = ENOTSUP; else if (c->flags & CHN_F_BUSY) err = EBUSY; else err = EINVAL; CHN_UNLOCK(c); return (err); } else if ((devunit == -1 || devunit == -2) && c->direction == direction && (c->flags & CHN_F_BUSY)) err = EBUSY; CHN_UNLOCK(c); } if (devunit == -2) return (err); vchan_alloc: /* no channel available */ if (devunit == -1 || snd_unit2d(devunit) == SND_DEV_DSPHW_VPLAY || snd_unit2d(devunit) == SND_DEV_DSPHW_VREC) { if (!(vchancount > 0 && vchancount < snd_maxautovchans) && (devunit == -1 || snd_unit2c(devunit) < snd_maxautovchans)) return (err); err = pcm_setvchans(d, direction, vchancount + 1, (devunit == -1) ? -1 : snd_unit2c(devunit)); if (err == 0) { if (devunit == -1) devunit = -2; goto retry_chnalloc; } } return (err); } /* release a locked channel and unlock it */ int pcm_chnrelease(struct pcm_channel *c) { PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); c->flags &= ~CHN_F_BUSY; c->pid = -1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); CHN_UNLOCK(c); return (0); } int pcm_chnref(struct pcm_channel *c, int ref) { PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); c->refcount += ref; return (c->refcount); } int pcm_inprog(struct snddev_info *d, int delta) { PCM_LOCKASSERT(d); d->inprog += delta; return (d->inprog); } static void pcm_setmaxautovchans(struct snddev_info *d, int num) { PCM_BUSYASSERT(d); if (num < 0) return; if (num >= 0 && d->pvchancount > num) (void)pcm_setvchans(d, PCMDIR_PLAY, num, -1); else if (num > 0 && d->pvchancount == 0) (void)pcm_setvchans(d, PCMDIR_PLAY, 1, -1); if (num >= 0 && d->rvchancount > num) (void)pcm_setvchans(d, PCMDIR_REC, num, -1); else if (num > 0 && d->rvchancount == 0) (void)pcm_setvchans(d, PCMDIR_REC, 1, -1); pcm_clonereset(d); } static int sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int error, unit; unit = snd_unit; error = sysctl_handle_int(oidp, &unit, 0, req); if (error == 0 && req->newptr != NULL) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d) || CHN_EMPTY(d, channels.pcm)) return EINVAL; snd_unit = unit; snd_unit_auto = 0; } return (error); } /* XXX: do we need a way to let the user change the default unit? */ SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); static int sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, v, error; v = snd_maxautovchans; error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { if (v < 0) v = 0; if (v > SND_MAXVCHANS) v = SND_MAXVCHANS; snd_maxautovchans = v; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; PCM_ACQUIRE_QUICK(d); pcm_setmaxautovchans(d, v); PCM_RELEASE_QUICK(d); } } return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); struct pcm_channel * pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo) { struct pcm_channel *ch; int direction, err, rpnum, *pnum, max; int udc, device, chan; char *dirs, *devname, buf[CHN_NAMELEN]; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); KASSERT(num >= -1, ("invalid num=%d", num)); switch (dir) { case PCMDIR_PLAY: dirs = "play"; direction = PCMDIR_PLAY; pnum = &d->playcount; device = SND_DEV_DSPHW_PLAY; max = SND_MAXHWCHAN; break; case PCMDIR_PLAY_VIRTUAL: dirs = "virtual"; direction = PCMDIR_PLAY; pnum = &d->pvchancount; device = SND_DEV_DSPHW_VPLAY; max = SND_MAXVCHANS; break; case PCMDIR_REC: dirs = "record"; direction = PCMDIR_REC; pnum = &d->reccount; device = SND_DEV_DSPHW_REC; max = SND_MAXHWCHAN; break; case PCMDIR_REC_VIRTUAL: dirs = "virtual"; direction = PCMDIR_REC; pnum = &d->rvchancount; device = SND_DEV_DSPHW_VREC; max = SND_MAXVCHANS; break; default: return (NULL); } chan = (num == -1) ? 0 : num; if (*pnum >= max || chan >= max) return (NULL); rpnum = 0; CHN_FOREACH(ch, d, channels.pcm) { if (CHN_DEV(ch) != device) continue; if (chan == CHN_CHAN(ch)) { if (num != -1) { device_printf(d->dev, "channel num=%d allocated!\n", chan); return (NULL); } chan++; if (chan >= max) { device_printf(d->dev, "chan=%d > %d\n", chan, max); return (NULL); } } rpnum++; } if (*pnum != rpnum) { device_printf(d->dev, "%s(): WARNING: pnum screwed : dirs=%s pnum=%d rpnum=%d\n", __func__, dirs, *pnum, rpnum); return (NULL); } udc = snd_mkunit(device_get_unit(d->dev), device, chan); devname = dsp_unit2name(buf, sizeof(buf), udc); if (devname == NULL) { device_printf(d->dev, "Failed to query device name udc=0x%08x\n", udc); return (NULL); } PCM_UNLOCK(d); ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); ch->unit = udc; ch->pid = -1; strlcpy(ch->comm, CHN_COMM_UNUSED, sizeof(ch->comm)); ch->parentsnddev = d; ch->parentchannel = parent; ch->dev = d->dev; ch->trigger = PCMTRIG_STOP; snprintf(ch->name, sizeof(ch->name), "%s:%s:%s", device_get_nameunit(ch->dev), dirs, devname); err = chn_init(ch, devinfo, dir, direction); PCM_LOCK(d); if (err) { device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err); kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); return (NULL); } return (ch); } int pcm_chn_destroy(struct pcm_channel *ch) { struct snddev_info *d; int err; d = ch->parentsnddev; PCM_BUSYASSERT(d); err = chn_kill(ch); if (err) { device_printf(ch->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err); return (err); } kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); return (0); } int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) { PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); KASSERT(ch != NULL && (ch->direction == PCMDIR_PLAY || ch->direction == PCMDIR_REC), ("Invalid pcm channel")); CHN_INSERT_SORT_ASCEND(d, ch, channels.pcm); switch (CHN_DEV(ch)) { case SND_DEV_DSPHW_PLAY: d->playcount++; break; case SND_DEV_DSPHW_VPLAY: d->pvchancount++; break; case SND_DEV_DSPHW_REC: d->reccount++; break; case SND_DEV_DSPHW_VREC: d->rvchancount++; break; default: break; } d->devcount++; return (0); } int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { struct pcm_channel *tmp; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); tmp = NULL; CHN_FOREACH(tmp, d, channels.pcm) { if (tmp == ch) break; } if (tmp != ch) return (EINVAL); CHN_REMOVE(d, ch, channels.pcm); switch (CHN_DEV(ch)) { case SND_DEV_DSPHW_PLAY: d->playcount--; break; case SND_DEV_DSPHW_VPLAY: d->pvchancount--; break; case SND_DEV_DSPHW_REC: d->reccount--; break; case SND_DEV_DSPHW_VREC: d->rvchancount--; break; default: break; } d->devcount--; return (0); } int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) { struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; int err; PCM_BUSYASSERT(d); PCM_LOCK(d); ch = pcm_chn_create(d, NULL, cls, dir, -1, devinfo); if (!ch) { device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo); PCM_UNLOCK(d); return (ENODEV); } err = pcm_chn_add(d, ch); PCM_UNLOCK(d); if (err) { device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err); pcm_chn_destroy(ch); } return (err); } static int pcm_killchan(device_t dev) { struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; int error; PCM_BUSYASSERT(d); ch = CHN_FIRST(d, channels.pcm); PCM_LOCK(d); error = pcm_chn_remove(d, ch); PCM_UNLOCK(d); if (error) return (error); return (pcm_chn_destroy(ch)); } static int pcm_best_unit(int old) { struct snddev_info *d; int i, best, bestprio, prio; best = -1; bestprio = -100; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; prio = 0; if (d->playcount == 0) prio -= 10; if (d->reccount == 0) prio -= 2; if (prio > bestprio || (prio == bestprio && i == old)) { best = i; bestprio = prio; } } return (best); } int pcm_setstatus(device_t dev, char *str) { struct snddev_info *d = device_get_softc(dev); PCM_BUSYASSERT(d); if (d->playcount == 0 || d->reccount == 0) d->flags |= SD_F_SIMPLEX; if ((d->playcount > 0 || d->reccount > 0) && !(d->flags & SD_F_AUTOVCHAN)) { d->flags |= SD_F_AUTOVCHAN; vchan_initsys(dev); } pcm_setmaxautovchans(d, snd_maxautovchans); strlcpy(d->status, str, SND_STATUSLEN); PCM_LOCK(d); /* Last stage, enable cloning. */ if (d->clones != NULL) (void)snd_clone_enable(d->clones); /* Done, we're ready.. */ d->flags |= SD_F_REGISTERED; PCM_RELEASE(d); PCM_UNLOCK(d); if (snd_unit_auto < 0) snd_unit_auto = (snd_unit < 0) ? 1 : 0; if (snd_unit < 0 || snd_unit_auto > 1) snd_unit = device_get_unit(dev); else if (snd_unit_auto == 1) snd_unit = pcm_best_unit(snd_unit); return (0); } uint32_t pcm_getflags(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->flags; } void pcm_setflags(device_t dev, uint32_t val) { struct snddev_info *d = device_get_softc(dev); d->flags = val; } void * pcm_getdevinfo(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->devinfo; } unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz) { struct snddev_info *d = device_get_softc(dev); int sz, x; sz = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) { x = sz; RANGE(sz, minbufsz, maxbufsz); if (x != sz) device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, minbufsz, maxbufsz, sz); x = minbufsz; while (x < sz) x <<= 1; if (x > sz) x >>= 1; if (x != sz) { device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x); sz = x; } } else { sz = deflt; } d->bufsz = sz; return sz; } static int sysctl_dev_pcm_bitperfect(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int err, val; d = oidp->oid_arg1; if (!PCM_REGISTERED(d)) return (ENODEV); PCM_LOCK(d); PCM_WAIT(d); val = (d->flags & SD_F_BITPERFECT) ? 1 : 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); err = sysctl_handle_int(oidp, &val, 0, req); if (err == 0 && req->newptr != NULL) { if (!(val == 0 || val == 1)) { PCM_RELEASE_QUICK(d); return (EINVAL); } PCM_LOCK(d); d->flags &= ~SD_F_BITPERFECT; d->flags |= (val != 0) ? SD_F_BITPERFECT : 0; PCM_RELEASE(d); PCM_UNLOCK(d); } else PCM_RELEASE_QUICK(d); return (err); } #ifdef SND_DEBUG static int sysctl_dev_pcm_clone_flags(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; uint32_t flags; int err; d = oidp->oid_arg1; if (!PCM_REGISTERED(d) || d->clones == NULL) return (ENODEV); PCM_ACQUIRE_QUICK(d); flags = snd_clone_getflags(d->clones); err = sysctl_handle_int(oidp, &flags, 0, req); if (err == 0 && req->newptr != NULL) { if (flags & ~SND_CLONE_MASK) err = EINVAL; else (void)snd_clone_setflags(d->clones, flags); } PCM_RELEASE_QUICK(d); return (err); } static int sysctl_dev_pcm_clone_deadline(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int err, deadline; d = oidp->oid_arg1; if (!PCM_REGISTERED(d) || d->clones == NULL) return (ENODEV); PCM_ACQUIRE_QUICK(d); deadline = snd_clone_getdeadline(d->clones); err = sysctl_handle_int(oidp, &deadline, 0, req); if (err == 0 && req->newptr != NULL) { if (deadline < 0) err = EINVAL; else (void)snd_clone_setdeadline(d->clones, deadline); } PCM_RELEASE_QUICK(d); return (err); } static int sysctl_dev_pcm_clone_gc(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int err, val; d = oidp->oid_arg1; if (!PCM_REGISTERED(d) || d->clones == NULL) return (ENODEV); val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err == 0 && req->newptr != NULL && val != 0) { PCM_ACQUIRE_QUICK(d); val = snd_clone_gc(d->clones); PCM_RELEASE_QUICK(d); if (bootverbose != 0 || snd_verbose > 3) device_printf(d->dev, "clone gc: pruned=%d\n", val); } return (err); } static int sysctl_hw_snd_clone_gc(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, err, val; val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err == 0 && req->newptr != NULL && val != 0) { for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d) || d->clones == NULL) continue; PCM_ACQUIRE_QUICK(d); val = snd_clone_gc(d->clones); PCM_RELEASE_QUICK(d); if (bootverbose != 0 || snd_verbose > 3) device_printf(d->dev, "clone gc: pruned=%d\n", val); } } return (err); } SYSCTL_PROC(_hw_snd, OID_AUTO, clone_gc, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_clone_gc, "I", "global clone garbage collector"); #endif int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { struct snddev_info *d; int i; if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); return EINVAL; } if (device_get_unit(dev) > PCMMAXUNIT) { device_printf(dev, "PCMMAXUNIT reached : unit=%d > %d\n", device_get_unit(dev), PCMMAXUNIT); device_printf(dev, "Use 'hw.snd.maxunit' tunable to raise the limit.\n"); return ENODEV; } d = device_get_softc(dev); d->dev = dev; d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); cv_init(&d->cv, device_get_nameunit(dev)); PCM_ACQUIRE_QUICK(d); dsp_cdevinfo_init(d); #if 0 /* * d->flags should be cleared by the allocator of the softc. * We cannot clear this field here because several devices set * this flag before calling pcm_register(). */ d->flags = 0; #endif i = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "vpc", &i) != 0 || i != 0) d->flags |= SD_F_VPC; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "bitperfect", &i) == 0 && i != 0) d->flags |= SD_F_BITPERFECT; d->devinfo = devinfo; d->devcount = 0; d->reccount = 0; d->playcount = 0; d->pvchancount = 0; d->rvchancount = 0; d->pvchanrate = 0; d->pvchanformat = 0; d->rvchanrate = 0; d->rvchanformat = 0; d->inprog = 0; /* * Create clone manager, disabled by default. Cloning will be * enabled during final stage of driver initialization through * pcm_setstatus(). */ d->clones = snd_clone_create(SND_U_MASK | SND_D_MASK, PCMMAXCLONE, SND_CLONE_DEADLINE_DEFAULT, SND_CLONE_WAITOK | SND_CLONE_GC_ENABLE | SND_CLONE_GC_UNREF | SND_CLONE_GC_LASTREF | SND_CLONE_GC_EXPIRED); CHN_INIT(d, channels.pcm); CHN_INIT(d, channels.pcm.busy); CHN_INIT(d, channels.pcm.opened); /* XXX This is incorrect, but lets play along for now. */ if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; sysctl_ctx_init(&d->play_sysctl_ctx); d->play_sysctl_tree = SYSCTL_ADD_NODE(&d->play_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "play", CTLFLAG_RD, 0, "playback channels node"); sysctl_ctx_init(&d->rec_sysctl_ctx); d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", CTLFLAG_RD, 0, "record channels node"); /* XXX: an user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "bitperfect", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), sysctl_dev_pcm_bitperfect, "I", "bit-perfect playback/recording (0=disable, 1=enable)"); #ifdef SND_DEBUG SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clone_flags", CTLTYPE_UINT | CTLFLAG_RW, d, sizeof(d), sysctl_dev_pcm_clone_flags, "IU", "clone flags"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clone_deadline", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), sysctl_dev_pcm_clone_deadline, "I", "clone expiration deadline (ms)"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clone_gc", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), sysctl_dev_pcm_clone_gc, "I", "clone garbage collector"); #endif if (numplay > 0 || numrec > 0) { d->flags |= SD_F_AUTOVCHAN; vchan_initsys(dev); } if (d->flags & SD_F_EQ) feeder_eq_initsys(dev); sndstat_register(dev, d->status, sndstat_prepare_pcm); return 0; } int pcm_unregister(device_t dev) { struct snddev_info *d; struct pcm_channel *ch; struct thread *td; td = curthread; d = device_get_softc(dev); if (!PCM_ALIVE(d)) { device_printf(dev, "unregister: device not configured\n"); return (0); } if (sndstat_acquire(td) != 0) { device_printf(dev, "unregister: sndstat busy\n"); return (EBUSY); } PCM_LOCK(d); PCM_WAIT(d); if (d->inprog != 0) { device_printf(dev, "unregister: operation in progress\n"); PCM_UNLOCK(d); sndstat_release(td); return (EBUSY); } PCM_ACQUIRE(d); PCM_UNLOCK(d); CHN_FOREACH(ch, d, channels.pcm) { CHN_LOCK(ch); if (ch->refcount > 0) { device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); CHN_UNLOCK(ch); PCM_RELEASE_QUICK(d); sndstat_release(td); return (EBUSY); } CHN_UNLOCK(ch); } if (d->clones != NULL) { if (snd_clone_busy(d->clones) != 0) { device_printf(dev, "unregister: clone busy\n"); PCM_RELEASE_QUICK(d); sndstat_release(td); return (EBUSY); } else { PCM_LOCK(d); (void)snd_clone_disable(d->clones); PCM_UNLOCK(d); } } if (mixer_uninit(dev) == EBUSY) { device_printf(dev, "unregister: mixer busy\n"); PCM_LOCK(d); if (d->clones != NULL) (void)snd_clone_enable(d->clones); PCM_RELEASE(d); PCM_UNLOCK(d); sndstat_release(td); return (EBUSY); } PCM_LOCK(d); d->flags |= SD_F_DYING; d->flags &= ~SD_F_REGISTERED; PCM_UNLOCK(d); /* * No lock being held, so this thing can be flushed without * stucking into devdrn oblivion. */ if (d->clones != NULL) { snd_clone_destroy(d->clones); d->clones = NULL; } if (d->play_sysctl_tree != NULL) { sysctl_ctx_free(&d->play_sysctl_ctx); d->play_sysctl_tree = NULL; } if (d->rec_sysctl_tree != NULL) { sysctl_ctx_free(&d->rec_sysctl_ctx); d->rec_sysctl_tree = NULL; } while (!CHN_EMPTY(d, channels.pcm)) pcm_killchan(dev); dsp_cdevinfo_flush(d); PCM_LOCK(d); PCM_RELEASE(d); cv_destroy(&d->cv); PCM_UNLOCK(d); snd_mtxfree(d->lock); sndstat_unregister(dev); sndstat_release(td); if (snd_unit == device_get_unit(dev)) { snd_unit = pcm_best_unit(-1); if (snd_unit_auto == 0) snd_unit_auto = 1; } return (0); } /************************************************************************/ /** * @brief Handle OSSv4 SNDCTL_SYSINFO ioctl. * * @param si Pointer to oss_sysinfo struct where information about the * sound subsystem will be written/copied. * * This routine returns information about the sound system, such as the * current OSS version, number of audio, MIDI, and mixer drivers, etc. * Also includes a bitmask showing which of the above types of devices * are open (busy). * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @author Ryan Beasley */ void sound_oss_sysinfo(oss_sysinfo *si) { static char si_product[] = "FreeBSD native OSS ABI"; static char si_version[] = __XSTRING(__FreeBSD_version); static char si_license[] = "BSD"; static int intnbits = sizeof(int) * 8; /* Better suited as macro? Must pester a C guru. */ struct snddev_info *d; struct pcm_channel *c; int i, j, ncards; ncards = 0; strlcpy(si->product, si_product, sizeof(si->product)); strlcpy(si->version, si_version, sizeof(si->version)); si->versionnum = SOUND_VERSION; strlcpy(si->license, si_license, sizeof(si->license)); /* * Iterate over PCM devices and their channels, gathering up data * for the numaudios, ncards, and openedaudio fields. */ si->numaudios = 0; bzero((void *)&si->openedaudio, sizeof(si->openedaudio)); j = 0; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See note in function's docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); si->numaudios += d->devcount; ++ncards; CHN_FOREACH(c, d, channels.pcm) { CHN_UNLOCKASSERT(c); CHN_LOCK(c); if (c->flags & CHN_F_BUSY) si->openedaudio[j / intnbits] |= (1 << (j % intnbits)); CHN_UNLOCK(c); j++; } PCM_UNLOCK(d); } si->numaudioengines = si->numaudios; si->numsynths = 0; /* OSSv4 docs: this field is obsolete */ /** * @todo Collect num{midis,timers}. * * Need access to sound/midi/midi.c::midistat_lock in order * to safely touch midi_devices and get a head count of, well, * MIDI devices. midistat_lock is a global static (i.e., local to * midi.c), but midi_devices is a regular global; should the mutex * be publicized, or is there another way to get this information? * * NB: MIDI/sequencer stuff is currently on hold. */ si->nummidis = 0; si->numtimers = 0; si->nummixers = mixer_count; si->numcards = ncards; /* OSSv4 docs: Intended only for test apps; API doesn't really have much of a concept of cards. Shouldn't be used by applications. */ /** * @todo Fill in "busy devices" fields. * * si->openedmidi = " MIDI devices */ bzero((void *)&si->openedmidi, sizeof(si->openedmidi)); /* * Si->filler is a reserved array, but according to docs each * element should be set to -1. */ for (i = 0; i < sizeof(si->filler)/sizeof(si->filler[0]); i++) si->filler[i] = -1; } int sound_oss_card_info(oss_card_info *si) { struct snddev_info *d; int i, ncards; ncards = 0; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; if (ncards++ != si->card) continue; PCM_UNLOCKASSERT(d); PCM_LOCK(d); strlcpy(si->shortname, device_get_nameunit(d->dev), sizeof(si->shortname)); strlcpy(si->longname, device_get_desc(d->dev), sizeof(si->longname)); strlcpy(si->hw_info, d->status, sizeof(si->hw_info)); si->intr_count = si->ack_count = 0; PCM_UNLOCK(d); return (0); } return (ENXIO); } /************************************************************************/ static int sound_modevent(module_t mod, int type, void *data) { int ret; #if 0 return (midi_modevent(mod, type, data)); #else ret = 0; switch(type) { case MOD_LOAD: pcmsg_unrhdr = new_unrhdr(1, INT_MAX, NULL); break; case MOD_UNLOAD: ret = sndstat_acquire(curthread); if (ret != 0) break; if (pcmsg_unrhdr != NULL) { delete_unrhdr(pcmsg_unrhdr); pcmsg_unrhdr = NULL; } break; case MOD_SHUTDOWN: break; default: ret = ENOTSUP; } return ret; #endif } DEV_MODULE(sound, sound_modevent, NULL); MODULE_VERSION(sound, SOUND_MODVER); Index: head/sys/dev/sound/pcm/sound.h =================================================================== --- head/sys/dev/sound/pcm/sound.h (revision 274034) +++ head/sys/dev/sound/pcm/sound.h (revision 274035) @@ -1,629 +1,622 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #include #include #include #define PCM_SOFTC_SIZE (sizeof(struct snddev_info)) #define SND_STATUSLEN 64 #define SOUND_MODVER 5 #define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER /* * We're abusing the fact that MAXMINOR still have enough room * for our bit twiddling and nobody ever need 512 unique soundcards, * 32 unique device types and 1024 unique cloneable devices for the * next 100 years... */ #define PCMMAXUNIT (snd_max_u()) #define PCMMAXDEV (snd_max_d()) #define PCMMAXCHAN (snd_max_c()) #define PCMMAXCLONE PCMMAXCHAN #define PCMUNIT(x) (snd_unit2u(dev2unit(x))) #define PCMDEV(x) (snd_unit2d(dev2unit(x))) #define PCMCHAN(x) (snd_unit2c(dev2unit(x))) /* XXX unit2minor compat */ -#if __FreeBSD_version >= 800062 #define PCMMINOR(x) (x) -#else -#define PCMMINOR(x) unit2minor(x) -#endif /* * By design, limit possible channels for each direction. */ #define SND_MAXHWCHAN 256 #define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 /* * Obsolete due to better matrixing */ #if 0 #define SD_F_PSWAPLR 0x00000008 #define SD_F_RSWAPLR 0x00000010 #endif #define SD_F_DYING 0x00000008 #define SD_F_SUICIDE 0x00000010 #define SD_F_BUSY 0x00000020 #define SD_F_MPSAFE 0x00000040 #define SD_F_REGISTERED 0x00000080 #define SD_F_BITPERFECT 0x00000100 #define SD_F_VPC 0x00000200 /* volume-per-channel */ #define SD_F_EQ 0x00000400 /* EQ */ #define SD_F_EQ_ENABLED 0x00000800 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00001000 /* EQ bypassed */ #define SD_F_EQ_PC 0x00002000 /* EQ per-channel */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ "\002AUTOVCHAN" \ "\003SOFTPCMVOL" \ "\004DYING" \ "\005SUICIDE" \ "\006BUSY" \ "\007MPSAFE" \ "\010REGISTERED" \ "\011BITPERFECT" \ "\012VPC" \ "\013EQ" \ "\014EQ_ENABLED" \ "\015EQ_BYPASSED" \ "\016EQ_PC" \ "\035PRIO_RD" \ "\036PRIO_WR" \ "\037DIR_SET" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL && \ !((x)->flags & SD_F_DYING)) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && \ ((x)->flags & SD_F_REGISTERED)) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff #define AFMT_CHANNEL_MASK 0x01f00000 #define AFMT_CHANNEL_SHIFT 20 #define AFMT_EXTCHANNEL_MASK 0x0e000000 #define AFMT_EXTCHANNEL_SHIFT 25 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 #define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) /* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this * reason the /dev/dsp is reserved for digitized audio use. There is a * device for true DSP processors but it will be called something else. * In v3.0 it's /dev/sndproc but this could be a temporary solution. */ #define SND_DEV_CTL 0 /* Control port /dev/mixer */ #define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ #define SND_DEV_MIDIN 2 /* Raw midi access */ #define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ #define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ #define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ #define SND_DEV_STATUS 6 /* /dev/sndstat */ /* #7 not in use now. */ #define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 #define SND_DEV_DSPHW_PLAY 11 /* specific playback channel */ #define SND_DEV_DSPHW_VPLAY 12 /* specific virtual playback channel */ #define SND_DEV_DSPHW_REC 13 /* specific record channel */ #define SND_DEV_DSPHW_VREC 14 /* specific virtual record channel */ #define SND_DEV_DSPHW_CD 15 /* s16le/stereo 44100Hz CD */ /* * OSSv4 compatible device. For now, it serve no purpose and * the cloning itself will forward the request to ordinary /dev/dsp * instead. */ #define SND_DEV_DSP_MMAP 16 /* /dev/dsp_mmap */ #define SND_DEV_DSP_AC3 17 /* /dev/dsp_ac3 */ #define SND_DEV_DSP_MULTICH 18 /* /dev/dsp_multich */ #define SND_DEV_DSP_SPDIFOUT 19 /* /dev/dsp_spdifout */ #define SND_DEV_DSP_SPDIFIN 20 /* /dev/dsp_spdifin */ #define SND_DEV_LAST SND_DEV_DSP_SPDIFIN #define SND_DEV_MAX PCMMAXDEV #define DSP_DEFAULT_SPEED 8000 #define ON 1 #define OFF 0 extern int pcm_veto_load; extern int snd_unit; extern int snd_maxautovchans; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; /* * some macros for debugging purposes * DDB/DEB to enable/disable debugging stuff * BVDDB to enable debugging when bootverbose */ #define BVDDB(x) if (bootverbose) x #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); int pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num); int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, char *comm, int devunit); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo); int pcm_chn_destroy(struct pcm_channel *ch); int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch); int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(m) typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); int sndstat_acquire(struct thread *td); int sndstat_release(struct thread *td); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); int sndstat_unregisterfile(char *str); #define SND_DECLARE_FILE(version) \ _SND_DECLARE_FILE(__LINE__, version) #define _SND_DECLARE_FILE(uniq, version) \ __SND_DECLARE_FILE(uniq, version) #define __SND_DECLARE_FILE(uniq, version) \ static char sndstat_vinfo[] = version; \ SYSINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_registerfile, sndstat_vinfo); \ SYSUNINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_unregisterfile, sndstat_vinfo); /* usage of flags in device config entry (config file) */ #define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ #define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ /* ought to be made obsolete but still used by mss */ #define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ #define DV_F_DEV_SHIFT 8 /* force device type/class */ /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; } pcm; } channels; TAILQ_HEAD(dsp_cdevinfo_linkhead, dsp_cdevinfo) dsp_cdevinfo_pool; struct snd_clone *clones; unsigned devcount, playcount, reccount, pvchancount, rvchancount ; unsigned flags; int inprog; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; uint32_t pvchanrate, pvchanformat; uint32_t rvchanrate, rvchanformat; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) { \ if ((x)->cv.cv_waiters > 1 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM RELEASE] " \ "cv_waiters=%d > 1!\n", \ __func__, __LINE__, \ (x)->cv.cv_waiters); \ cv_broadcast(&(x)->cv); \ } \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* !SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) #ifdef KLD_MODULE #define PCM_KLDSTRING(a) ("kld " # a) #else #define PCM_KLDSTRING(a) "" #endif #endif /* _KERNEL */ #endif /* _OS_H_ */