diff --git a/sys/dev/sound/chip.h b/sys/dev/sound/chip.h deleted file mode 100644 index bb40d2809a00..000000000000 --- a/sys/dev/sound/chip.h +++ /dev/null @@ -1,47 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * 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. - */ - -/* - * These are the function codes assigned to the children of - * sound cards. - */ -enum { - SCF_PCM, - SCF_MIDI, - SCF_SYNTH, -}; - -/* - * This is the device information struct, used by - * a bridge device to pass the device function code - * to the children. - */ -struct sndcard_func { - int func; /* The function code. */ - void *varinfo; /* Bridge-specific information. */ -}; diff --git a/sys/dev/sound/pci/csa.c b/sys/dev/sound/pci/csa.c index 7bb7967b74e7..662f0de80188 100644 --- a/sys/dev/sound/pci/csa.c +++ b/sys/dev/sound/pci/csa.c @@ -1,1108 +1,1107 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in * cwcealdr1.zip, the sample sources by Crystal Semiconductor. * Copyright (c) 1996-1998 Crystal Semiconductor Corp. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #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 /* 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, rman_res_t start, rman_res_t end, rman_res_t 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, driver_filter_t *filter, 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 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}, {0x153b, 0x1136, "Terratec SiXPack 5.1+", 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, rman_res_t start, rman_res_t end, rman_res_t 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, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp; csa_res *resp; struct sndcard_func *func; if (filter != NULL) { printf("ata-csa.c: we cannot use a filter here\n"); return (EINVAL); } scp = device_get_softc(bus); resp = &scp->res; /* * Look at the function code of the child to determine * the appropriate handler 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 handler 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 < 2000 ; 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, 0, 0); MODULE_DEPEND(snd_csa, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_csa, 1); diff --git a/sys/dev/sound/pci/csamidi.c b/sys/dev/sound/pci/csamidi.c index df1699092990..29d5548b0954 100644 --- a/sys/dev/sound/pci/csamidi.c +++ b/sys/dev/sound/pci/csamidi.c @@ -1,285 +1,284 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * Copyright (c) 2015-2018 Tai-hwa Liang * 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 #include #include #include #include #include #include "mpufoi_if.h" /* pulled from mpu401.c */ #define MPU_DATAPORT 0 #define MPU_CMDPORT 1 #define MPU_STATPORT 1 #define MPU_RESET 0xff #define MPU_UART 0x3f #define MPU_ACK 0xfe #define MPU_STATMASK 0xc0 #define MPU_OUTPUTBUSY 0x40 #define MPU_INPUTBUSY 0x80 /* device private data */ struct csa_midi_softc { /* hardware resources */ int io_rid; /* io rid */ struct resource *io; /* io */ struct mtx mtx; device_t dev; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; int mflags; /* MIDI flags */ }; static struct kobj_class csamidi_mpu_class; static u_int32_t csamidi_readio(struct csa_midi_softc *scp, u_long offset) { if (offset < BA0_AC97_RESET) return bus_space_read_4(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), offset) & 0xffffffff; else return (0); } static void csamidi_writeio(struct csa_midi_softc *scp, u_long offset, u_int32_t data) { if (offset < BA0_AC97_RESET) bus_space_write_4(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), offset, data); } static void csamidi_midi_intr(void *arg) { struct csa_midi_softc *scp = (struct csa_midi_softc *)arg; if (scp->mpu_intr) (scp->mpu_intr)(scp->mpu); } static unsigned char csamidi_mread(struct mpu401 *arg __unused, void *cookie, int reg) { struct csa_midi_softc *scp = cookie; unsigned int rc; unsigned int uart_stat; rc = 0; /* hacks to convert hardware status to MPU compatible ones */ switch (reg) { case MPU_STATPORT: uart_stat = csamidi_readio(scp, BA0_MIDSR); if (uart_stat & MIDSR_TBF) rc |= MPU_OUTPUTBUSY; /* Tx buffer full */ if (uart_stat & MIDSR_RBE) rc |= MPU_INPUTBUSY; break; case MPU_DATAPORT: rc = csamidi_readio(scp, BA0_MIDRP); break; default: printf("csamidi_mread: unknown register %d\n", reg); break; } return (rc); } static void csamidi_mwrite(struct mpu401 *arg __unused, void *cookie, int reg, unsigned char b) { struct csa_midi_softc *scp = cookie; unsigned int val; switch (reg) { case MPU_CMDPORT: switch (b) { case MPU_RESET: /* preserve current operation mode */ val = csamidi_readio(scp, BA0_MIDCR); /* reset the MIDI port */ csamidi_writeio(scp, BA0_MIDCR, MIDCR_MRST); csamidi_writeio(scp, BA0_MIDCR, MIDCR_MLB); csamidi_writeio(scp, BA0_MIDCR, 0x00); /* restore previous operation mode */ csamidi_writeio(scp, BA0_MIDCR, val); break; case MPU_UART: /* switch to UART mode, no-op */ default: break; } break; case MPU_DATAPORT: /* put the MIDI databyte in the write port */ csamidi_writeio(scp, BA0_MIDWP, b); break; default: printf("csamidi_mwrite: unknown register %d\n", reg); break; } } static int csamidi_muninit(struct mpu401 *arg __unused, void *cookie) { struct csa_midi_softc *scp = cookie; mtx_lock(&scp->mtx); scp->mpu_intr = NULL; mtx_unlock(&scp->mtx); return (0); } static int midicsa_probe(device_t dev) { struct sndcard_func *func; /* The parent device has already been probed. */ func = device_get_ivars(dev); if (func == NULL || func->func != SCF_MIDI) return (ENXIO); device_set_desc(dev, "CS461x MIDI"); return (0); } static int midicsa_attach(device_t dev) { struct csa_midi_softc *scp; int rc = ENXIO; scp = device_get_softc(dev); bzero(scp, sizeof(struct csa_midi_softc)); scp->dev = dev; /* allocate the required resources */ scp->io_rid = PCIR_BAR(0); scp->io = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &scp->io_rid, RF_ACTIVE); if (scp->io == NULL) goto err0; /* init the fake MPU401 interface. */ scp->mpu = mpu401_init(&csamidi_mpu_class, scp, csamidi_midi_intr, &scp->mpu_intr); if (scp->mpu == NULL) { rc = ENOMEM; goto err1; } mtx_init(&scp->mtx, device_get_nameunit(dev), "csamidi softc", MTX_DEF); /* reset the MIDI port */ csamidi_writeio(scp, BA0_MIDCR, MIDCR_MRST); /* MIDI transmit enable, no interrupt */ csamidi_writeio(scp, BA0_MIDCR, MIDCR_TXE | MIDCR_RXE); csamidi_writeio(scp, BA0_HICR, HICR_IEV | HICR_CHGM); return (0); err1: bus_release_resource(dev, SYS_RES_MEMORY, scp->io_rid, scp->io); scp->io = NULL; err0: return (rc); } static int midicsa_detach(device_t dev) { struct csa_midi_softc *scp; int rc = 0; scp = device_get_softc(dev); rc = mpu401_uninit(scp->mpu); if (rc) return (rc); if (scp->io != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, scp->io_rid, scp->io); scp->io = NULL; } mtx_destroy(&scp->mtx); return (rc); } static kobj_method_t csamidi_mpu_methods[] = { KOBJMETHOD(mpufoi_read, csamidi_mread), KOBJMETHOD(mpufoi_write, csamidi_mwrite), KOBJMETHOD(mpufoi_uninit, csamidi_muninit), KOBJMETHOD_END }; static DEFINE_CLASS(csamidi_mpu, csamidi_mpu_methods, 0); static device_method_t midicsa_methods[] = { DEVMETHOD(device_probe, midicsa_probe), DEVMETHOD(device_attach, midicsa_attach), DEVMETHOD(device_detach, midicsa_detach), DEVMETHOD_END }; static driver_t midicsa_driver = { "midi", midicsa_methods, sizeof(struct csa_midi_softc), }; DRIVER_MODULE(snd_csa_midi, csa, midicsa_driver, 0, 0); MODULE_DEPEND(snd_csa_midi, snd_csa, 1, 1, 1); MODULE_DEPEND(snd_csa_midi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_csa_midi, 1); diff --git a/sys/dev/sound/pci/csapcm.c b/sys/dev/sound/pci/csapcm.c index 71b9a0253cdb..c8424dd3e433 100644 --- a/sys/dev/sound/pci/csapcm.c +++ b/sys/dev/sound/pci/csapcm.c @@ -1,1040 +1,1039 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in * cwcealdr1.zip, the sample sources by Crystal Semiconductor. * Copyright (c) 1996-1998 Crystal Semiconductor Corp. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include -#include #include #include #include #include /* Buffer size on dma transfer. Fixed for CS416x. */ #define CS461x_BUFFSIZE (4 * 1024) #define GOF_PER_SEC 200 /* device private data */ struct csa_info; struct csa_chinfo { struct csa_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; u_int32_t fmt, spd; int dma; }; struct csa_info { csa_res res; /* resource */ void *ih; /* Interrupt cookie */ bus_dma_tag_t parent_dmat; /* DMA tag */ struct csa_bridgeinfo *binfo; /* The state of the parent. */ struct csa_card *card; int active; /* Contents of board's registers */ u_long pfie; u_long pctl; u_long cctl; struct csa_chinfo pch, rch; u_int32_t ac97[CS461x_AC97_NUMBER_RESTORE_REGS]; u_int32_t ac97_powerdown; u_int32_t ac97_general_purpose; }; /* -------------------------------------------------------------------- */ /* prototypes */ static int csa_init(struct csa_info *); static void csa_intr(void *); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate); static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate); static void csa_startplaydma(struct csa_info *csa); static void csa_startcapturedma(struct csa_info *csa); static void csa_stopplaydma(struct csa_info *csa); static void csa_stopcapturedma(struct csa_info *csa); static int csa_startdsp(csa_res *resp); static int csa_stopdsp(csa_res *resp); static int csa_allocres(struct csa_info *scp, device_t dev); static void csa_releaseres(struct csa_info *scp, device_t dev); static void csa_ac97_suspend(struct csa_info *csa); static void csa_ac97_resume(struct csa_info *csa); static u_int32_t csa_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), 0 }; static struct pcmchan_caps csa_playcaps = {8000, 48000, csa_playfmt, 0}; static u_int32_t csa_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps csa_reccaps = {11025, 48000, csa_recfmt, 0}; /* -------------------------------------------------------------------- */ static int csa_active(struct csa_info *csa, int run) { int old; old = csa->active; csa->active += run; if ((csa->active > 1) || (csa->active < -1)) csa->active = 0; if (csa->card->active) return (csa->card->active(!(csa->active && old))); return 0; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int csa_rdcd(kobj_t obj, void *devinfo, int regno) { u_int32_t data; struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data)) data = 0; csa_active(csa, -1); return data; } static int csa_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data); csa_active(csa, -1); return 0; } static kobj_method_t csa_ac97_methods[] = { KOBJMETHOD(ac97_read, csa_rdcd), KOBJMETHOD(ac97_write, csa_wrcd), KOBJMETHOD_END }; AC97_DECLARE(csa_ac97); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate) { u_long ulTemp1, ulTemp2; u_long ulPhiIncr; u_long ulCorrectionPerGOF, ulCorrectionPerSec; u_long ulOutRate; ulOutRate = 48000; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * * i.e. * * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) */ ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; /* * Fill in the SampleRateConverter control block. */ csa_writemem(resp, BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_PPI, ulPhiIncr); } static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate) { u_long ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2; u_long ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay; u_long dwFrameGroupLength, dwCnt; u_long ulInRate; ulInRate = 48000; /* * We can only decimate by up to a factor of 1/9th the hardware rate. * Return an error if an attempt is made to stray outside that limit. */ if((ulOutRate * 9) < ulInRate) return; /* * We can not capture at at rate greater than the Input Rate (48000). * Return an error if an attempt is made to stray outside that limit. */ if(ulOutRate > ulInRate) return; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulCoeffIncr = -floor((Fs,out * 2^23) / Fs,in) * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * ulInitialDelay = ceil((24 * Fs,in) / Fs,out) * * i.e. * * ulCoeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) * ulInitialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) */ ulTemp1 = ulOutRate << 16; ulCoeffIncr = ulTemp1 / ulInRate; ulTemp1 -= ulCoeffIncr * ulInRate; ulTemp1 <<= 7; ulCoeffIncr <<= 7; ulCoeffIncr += ulTemp1 / ulInRate; ulCoeffIncr ^= 0xFFFFFFFF; ulCoeffIncr++; ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate; /* * Fill in the VariDecimate control block. */ csa_writemem(resp, BA1_CSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_CCI, ulCoeffIncr); csa_writemem(resp, BA1_CD, (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); csa_writemem(resp, BA1_CPI, ulPhiIncr); /* * Figure out the frame group length for the write back task. Basically, * this is just the factors of 24000 (2^6*3*5^3) that are not present in * the output sample rate. */ dwFrameGroupLength = 1; for(dwCnt = 2; dwCnt <= 64; dwCnt *= 2) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 2; } } if(((ulOutRate / 3) * 3) != ulOutRate) { dwFrameGroupLength *= 3; } for(dwCnt = 5; dwCnt <= 125; dwCnt *= 5) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 5; } } /* * Fill in the WriteBack control block. */ csa_writemem(resp, BA1_CFG1, dwFrameGroupLength); csa_writemem(resp, BA1_CFG2, (0x00800000 | dwFrameGroupLength)); csa_writemem(resp, BA1_CCST, 0x0000FFFF); csa_writemem(resp, BA1_CSPB, ((65536 * ulOutRate) / 24000)); csa_writemem(resp, (BA1_CSPB + 4), 0x0000FFFF); } static void csa_startplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); ul &= 0x0000ffff; csa_writemem(resp, BA1_PCTL, ul | csa->pctl); csa_writemem(resp, BA1_PVOL, 0x80008000); csa->pch.dma = 1; } } static void csa_startcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); ul &= 0xffff0000; csa_writemem(resp, BA1_CCTL, ul | csa->cctl); csa_writemem(resp, BA1_CVOL, 0x80008000); csa->rch.dma = 1; } } static void csa_stopplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); csa->pctl = ul & 0xffff0000; csa_writemem(resp, BA1_PCTL, ul & 0x0000ffff); csa_writemem(resp, BA1_PVOL, 0xffffffff); csa->pch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not capturing. */ if (!csa->rch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static void csa_stopcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); csa->cctl = ul & 0x0000ffff; csa_writemem(resp, BA1_CCTL, ul & 0xffff0000); csa_writemem(resp, BA1_CVOL, 0xffffffff); csa->rch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not playing. */ if (!csa->pch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static int csa_startdsp(csa_res *resp) { int i; u_long ul; /* * Set the frame timer to reflect the number of cycles per frame. */ csa_writemem(resp, BA1_FRMT, 0xadf); /* * Turn on the run, run at frame, and DMA enable bits in the local copy of * the SP control register. */ csa_writemem(resp, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); /* * Wait until the run at frame bit resets itself in the SP control * register. */ ul = 0; for (i = 0 ; i < 25 ; i++) { /* * Wait a little bit, so we don't issue PCI reads too frequently. */ DELAY(50); /* * Fetch the current value of the SP status register. */ ul = csa_readmem(resp, BA1_SPCR); /* * If the run at frame bit has reset, then stop waiting. */ if((ul & SPCR_RUNFR) == 0) break; } /* * If the run at frame bit never reset, then return an error. */ if((ul & SPCR_RUNFR) != 0) return (EAGAIN); return (0); } static int csa_stopdsp(csa_res *resp) { /* * Turn off the run, run at frame, and DMA enable bits in * the local copy of the SP control register. */ csa_writemem(resp, BA1_SPCR, 0); return (0); } static int csa_setupchan(struct csa_chinfo *ch) { struct csa_info *csa = ch->parent; csa_res *resp = &csa->res; u_long pdtc, tmp; if (ch->dir == PCMDIR_PLAY) { /* direction */ csa_writemem(resp, BA1_PBA, sndbuf_getbufaddr(ch->buffer)); /* format */ csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f; if (!(ch->fmt & AFMT_SIGNED)) csa->pfie |= 0x8000; if (ch->fmt & AFMT_BIGENDIAN) csa->pfie |= 0x4000; if (AFMT_CHANNEL(ch->fmt) < 2) csa->pfie |= 0x2000; if (ch->fmt & AFMT_8BIT) csa->pfie |= 0x1000; csa_writemem(resp, BA1_PFIE, csa->pfie); tmp = 4; if (ch->fmt & AFMT_16BIT) tmp <<= 1; if (AFMT_CHANNEL(ch->fmt) > 1) tmp <<= 1; tmp--; pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000001ff; pdtc |= tmp; csa_writemem(resp, BA1_PDTC, pdtc); /* rate */ csa_setplaysamplerate(resp, ch->spd); } else if (ch->dir == PCMDIR_REC) { /* direction */ csa_writemem(resp, BA1_CBA, sndbuf_getbufaddr(ch->buffer)); /* format */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* rate */ csa_setcapturesamplerate(resp, ch->spd); } return 0; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * csachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct csa_info *csa = devinfo; struct csa_chinfo *ch = (dir == PCMDIR_PLAY)? &csa->pch : &csa->rch; ch->parent = csa; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, csa->parent_dmat, 0, CS461x_BUFFSIZE) != 0) return NULL; return ch; } static int csachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct csa_chinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t csachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct csa_chinfo *ch = data; ch->spd = speed; return ch->spd; /* XXX calc real speed */ } static u_int32_t csachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return CS461x_BUFFSIZE / 2; } static int csachan_trigger(kobj_t obj, void *data, int go) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { csa_active(csa, 1); csa_setupchan(ch); if (ch->dir == PCMDIR_PLAY) csa_startplaydma(csa); else csa_startcapturedma(csa); } else { if (ch->dir == PCMDIR_PLAY) csa_stopplaydma(csa); else csa_stopcapturedma(csa); csa_active(csa, -1); } return 0; } static u_int32_t csachan_getptr(kobj_t obj, void *data) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; csa_res *resp; u_int32_t ptr; resp = &csa->res; if (ch->dir == PCMDIR_PLAY) { ptr = csa_readmem(resp, BA1_PBA) - sndbuf_getbufaddr(ch->buffer); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } else { ptr = csa_readmem(resp, BA1_CBA) - sndbuf_getbufaddr(ch->buffer); if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } return (ptr); } static struct pcmchan_caps * csachan_getcaps(kobj_t obj, void *data) { struct csa_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps; } static kobj_method_t csachan_methods[] = { KOBJMETHOD(channel_init, csachan_init), KOBJMETHOD(channel_setformat, csachan_setformat), KOBJMETHOD(channel_setspeed, csachan_setspeed), KOBJMETHOD(channel_setblocksize, csachan_setblocksize), KOBJMETHOD(channel_trigger, csachan_trigger), KOBJMETHOD(channel_getptr, csachan_getptr), KOBJMETHOD(channel_getcaps, csachan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(csachan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void csa_intr(void *p) { struct csa_info *csa = p; if ((csa->binfo->hisr & HISR_VC0) != 0) chn_intr(csa->pch.channel); if ((csa->binfo->hisr & HISR_VC1) != 0) chn_intr(csa->rch.channel); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int csa_init(struct csa_info *csa) { csa_res *resp; resp = &csa->res; csa->pfie = 0; csa_stopplaydma(csa); csa_stopcapturedma(csa); if (csa_startdsp(resp)) return (1); /* Crank up the power on the DAC and ADC. */ csa_setplaysamplerate(resp, 8000); csa_setcapturesamplerate(resp, 8000); /* Set defaults */ csa_writeio(resp, BA0_EGPIODR, EGPIODR_GPOE0); csa_writeio(resp, BA0_EGPIOPTR, EGPIOPTR_GPPT0); /* Power up amplifier */ csa_writeio(resp, BA0_EGPIODR, csa_readio(resp, BA0_EGPIODR) | EGPIODR_GPOE2); csa_writeio(resp, BA0_EGPIOPTR, csa_readio(resp, BA0_EGPIOPTR) | EGPIOPTR_GPPT2); return 0; } /* Allocates resources. */ static int csa_allocres(struct csa_info *csa, device_t dev) { csa_res *resp; resp = &csa->res; if (resp->io == NULL) { resp->io = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->io_rid, RF_ACTIVE); if (resp->io == NULL) return (1); } if (resp->mem == NULL) { resp->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->mem_rid, RF_ACTIVE); if (resp->mem == NULL) return (1); } if (resp->irq == NULL) { resp->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &resp->irq_rid, RF_ACTIVE | RF_SHAREABLE); if (resp->irq == NULL) return (1); } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/CS461x_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &csa->parent_dmat) != 0) return (1); return (0); } /* Releases resources. */ static void csa_releaseres(struct csa_info *csa, device_t dev) { csa_res *resp; KASSERT(csa != NULL, ("called with bogus resource structure")); resp = &csa->res; if (resp->irq != NULL) { if (csa->ih) bus_teardown_intr(dev, resp->irq, csa->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); resp->irq = NULL; } if (resp->io != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); resp->io = NULL; } if (resp->mem != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); resp->mem = NULL; } if (csa->parent_dmat != NULL) { bus_dma_tag_destroy(csa->parent_dmat); csa->parent_dmat = NULL; } free(csa, M_DEVBUF); } static int pcmcsa_probe(device_t dev) { char *s; struct sndcard_func *func; /* The parent device has already been probed. */ func = device_get_ivars(dev); if (func == NULL || func->func != SCF_PCM) return (ENXIO); s = "CS461x PCM Audio"; device_set_desc(dev, s); return (0); } static int pcmcsa_attach(device_t dev) { struct csa_info *csa; csa_res *resp; char status[SND_STATUSLEN]; struct ac97_info *codec; struct sndcard_func *func; csa = malloc(sizeof(*csa), M_DEVBUF, M_WAITOK | M_ZERO); func = device_get_ivars(dev); csa->binfo = func->varinfo; /* * Fake the status of DMA so that the initial value of * PCTL and CCTL can be stored into csa->pctl and csa->cctl, * respectively. */ csa->pch.dma = csa->rch.dma = 1; csa->active = 0; csa->card = csa->binfo->card; /* Allocate the resources. */ resp = &csa->res; resp->io_rid = PCIR_BAR(0); resp->mem_rid = PCIR_BAR(1); resp->irq_rid = 0; if (csa_allocres(csa, dev)) { csa_releaseres(csa, dev); return (ENXIO); } csa_active(csa, 1); if (csa_init(csa)) { csa_releaseres(csa, dev); return (ENXIO); } codec = AC97_CREATE(dev, csa, csa_ac97); if (codec == NULL) { csa_releaseres(csa, dev); return (ENXIO); } if (csa->card->inv_eapd) ac97_setflags(codec, AC97_F_EAPD_INV); if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } snprintf(status, SND_STATUSLEN, "irq %jd on %s", rman_get_start(resp->irq), device_get_nameunit(device_get_parent(dev))); /* Enable interrupt. */ if (snd_setup_intr(dev, resp->irq, 0, csa_intr, csa, &csa->ih)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); csa_active(csa, -1); if (pcm_register(dev, csa, 1, 1)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } pcm_addchan(dev, PCMDIR_REC, &csachan_class, csa); pcm_addchan(dev, PCMDIR_PLAY, &csachan_class, csa); pcm_setstatus(dev, status); return (0); } static int pcmcsa_detach(device_t dev) { int r; struct csa_info *csa; r = pcm_unregister(dev); if (r) return r; csa = pcm_getdevinfo(dev); csa_releaseres(csa, dev); return 0; } static void csa_ac97_suspend(struct csa_info *csa) { int count, i; uint32_t tmp; for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_readcodec(&csa->res, BA0_AC97_RESET + count, &csa->ac97[i]); /* mute the outputs */ csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_HEADPHONE_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME_MONO, 0x8000); csa_writecodec(&csa->res, BA0_AC97_PCM_OUT_VOLUME, 0x8000); /* save the registers that cause pops */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &csa->ac97_powerdown); csa_readcodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, &csa->ac97_general_purpose); /* * And power down everything on the AC97 codec. Well, for now, * only power down the DAC/ADC and MIXER VREFON components. * trouble with removing VREF. */ /* MIXVON */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_MIXVON); /* ADC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_ADC); /* DAC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_DAC); } static void csa_ac97_resume(struct csa_info *csa) { int count, i; /* * First, we restore the state of the general purpose register. This * contains the mic select (mic1 or mic2) and if we restore this after * we restore the mic volume/boost state and mic2 was selected at * suspend time, we will end up with a brief period of time where mic1 * is selected with the volume/boost settings for mic2, causing * acoustic feedback. So we restore the general purpose register * first, thereby getting the correct mic selected before we restore * the mic volume/boost. */ csa_writecodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, csa->ac97_general_purpose); /* * Now, while the outputs are still muted, restore the state of power * on the AC97 part. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, csa->ac97_powerdown); /* * Restore just the first set of registers, from register number * 0x02 to the register number that ulHighestRegToRestore specifies. */ for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_writecodec(&csa->res, BA0_AC97_RESET + count, csa->ac97[i]); } static int pcmcsa_suspend(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* playback interrupt disable */ csa_writemem(resp, BA1_PFIE, (csa_readmem(resp, BA1_PFIE) & ~0x0000f03f) | 0x00000010); /* capture interrupt disable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000011); csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_suspend(csa); csa_resetdsp(resp); csa_stopdsp(resp); /* * Power down the DAC and ADC. For now leave the other areas on. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, 0x300); /* * Power down the PLL. */ csa_writemem(resp, BA0_CLKCR1, 0); /* * Turn off the Processor by turning off the software clock * enable flag in the clock control register. */ csa_writemem(resp, BA0_CLKCR1, csa_readmem(resp, BA0_CLKCR1) & ~CLKCR1_SWCE); csa_active(csa, -1); return 0; } static int pcmcsa_resume(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* cs_hardware_init */ csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_resume(csa); if (csa_startdsp(resp)) return (ENXIO); /* Enable interrupts on the part. */ if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); /* playback interrupt enable */ csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); /* capture interrupt enable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* cs_restart_part */ csa_setupchan(&csa->pch); csa_startplaydma(csa); csa_setupchan(&csa->rch); csa_startcapturedma(csa); csa_active(csa, -1); return 0; } static device_method_t pcmcsa_methods[] = { /* Device interface */ DEVMETHOD(device_probe , pcmcsa_probe ), DEVMETHOD(device_attach, pcmcsa_attach), DEVMETHOD(device_detach, pcmcsa_detach), DEVMETHOD(device_suspend, pcmcsa_suspend), DEVMETHOD(device_resume, pcmcsa_resume), { 0, 0 }, }; static driver_t pcmcsa_driver = { "pcm", pcmcsa_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_csapcm, csa, pcmcsa_driver, 0, 0); MODULE_DEPEND(snd_csapcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_csapcm, snd_csa, 1, 1, 1); MODULE_VERSION(snd_csapcm, 1); diff --git a/sys/dev/sound/pci/emu10kx-midi.c b/sys/dev/sound/pci/emu10kx-midi.c index 4ed8e6c1dd9c..2a98562f8f39 100644 --- a/sys/dev/sound/pci/emu10kx-midi.c +++ b/sys/dev/sound/pci/emu10kx-midi.c @@ -1,251 +1,250 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Seigo Tanimura * Copyright (c) 2003 Mathew Kanner * Copyright (c) 2003-2006 Yuriy Tsibizov * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 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 #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif -#include #include #include #include #include "mpufoi_if.h" #include #include struct emu_midi_softc { struct mtx mtx; device_t dev; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; struct emu_sc_info *card; int port; /* I/O port or I/O ptr reg */ int is_emu10k1; int fflags; /* File flags */ int ihandle; /* interrupt manager handle */ }; static uint32_t emu_midi_card_intr(void *p, uint32_t arg); static unsigned char emu_mread(struct mpu401 *arg __unused, void *cookie, int reg) { struct emu_midi_softc *sc = cookie; unsigned int d; d = 0; if (sc->is_emu10k1) d = emu_rd(sc->card, 0x18 + reg, 1); else d = emu_rdptr(sc->card, 0, sc->port + reg); return (d); } static void emu_mwrite(struct mpu401 *arg __unused, void *cookie, int reg, unsigned char b) { struct emu_midi_softc *sc = cookie; if (sc->is_emu10k1) emu_wr(sc->card, 0x18 + reg, b, 1); else emu_wrptr(sc->card, 0, sc->port + reg, b); } static int emu_muninit(struct mpu401 *arg __unused, void *cookie) { struct emu_midi_softc *sc = cookie; mtx_lock(&sc->mtx); sc->mpu_intr = NULL; mtx_unlock(&sc->mtx); return (0); } static kobj_method_t emu_mpu_methods[] = { KOBJMETHOD(mpufoi_read, emu_mread), KOBJMETHOD(mpufoi_write, emu_mwrite), KOBJMETHOD(mpufoi_uninit, emu_muninit), KOBJMETHOD_END }; static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); static uint32_t emu_midi_card_intr(void *p, uint32_t intr_status) { struct emu_midi_softc *sc = (struct emu_midi_softc *)p; if (sc->mpu_intr) (sc->mpu_intr) (sc->mpu); if (sc->mpu_intr == NULL) { /* We should read MIDI event to unlock card after * interrupt. XXX - check, why this happens. */ if (bootverbose) device_printf(sc->dev, "midi interrupt %08x without interrupt handler, force mread!\n", intr_status); (void)emu_mread((void *)(NULL), sc, 0); } return (intr_status); /* Acknowledge everything */ } static void emu_midi_intr(void *p) { (void)emu_midi_card_intr(p, 0); } static int emu_midi_probe(device_t dev) { struct emu_midi_softc *scp; uintptr_t func, is_emu10k1; BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); if (func != SCF_MIDI) return (ENXIO); scp = device_get_softc(dev); bzero(scp, sizeof(*scp)); BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &is_emu10k1); scp->is_emu10k1 = is_emu10k1 ? 1 : 0; device_set_desc(dev, "EMU10Kx MIDI Interface"); return (0); } static int emu_midi_attach(device_t dev) { struct emu_midi_softc * scp; struct sndcard_func *func; struct emu_midiinfo *midiinfo; uint32_t inte_val, ipr_val; scp = device_get_softc(dev); func = device_get_ivars(dev); scp->dev = dev; midiinfo = (struct emu_midiinfo *)func->varinfo; scp->port = midiinfo->port; scp->card = midiinfo->card; mtx_init(&scp->mtx, device_get_nameunit(dev), "midi softc", MTX_DEF); if (scp->is_emu10k1) { /* SB Live! - only one MIDI device here */ inte_val = 0; /* inte_val |= EMU_INTE_MIDITXENABLE;*/ inte_val |= EMU_INTE_MIDIRXENABLE; ipr_val = EMU_IPR_MIDITRANSBUFE; ipr_val |= EMU_IPR_MIDIRECVBUFE; } else { if (scp->port == EMU_A_MUDATA1) { /* EXTERNAL MIDI (AudigyDrive) */ inte_val = 0; /* inte_val |= A_EMU_INTE_MIDITXENABLE1;*/ inte_val |= EMU_INTE_MIDIRXENABLE; ipr_val = EMU_IPR_MIDITRANSBUFE; ipr_val |= EMU_IPR_MIDIRECVBUFE; } else { /* MIDI hw config port 2 */ inte_val = 0; /* inte_val |= A_EMU_INTE_MIDITXENABLE2;*/ inte_val |= EMU_INTE_A_MIDIRXENABLE2; ipr_val = EMU_IPR_A_MIDITRANSBUFE2; ipr_val |= EMU_IPR_A_MIDIRECBUFE2; } } scp->ihandle = emu_intr_register(scp->card, inte_val, ipr_val, &emu_midi_card_intr, scp); /* Init the interface. */ scp->mpu = mpu401_init(&emu_mpu_class, scp, emu_midi_intr, &scp->mpu_intr); if (scp->mpu == NULL) { emu_intr_unregister(scp->card, scp->ihandle); mtx_destroy(&scp->mtx); return (ENOMEM); } /* * XXX I don't know how to check for Live!Drive / AudigyDrive * presence. Let's hope that IR enabling code will not harm if * it is not present. */ if (scp->is_emu10k1) emu_enable_ir(scp->card); else { if (scp->port == EMU_A_MUDATA1) emu_enable_ir(scp->card); } return (0); } static int emu_midi_detach(device_t dev) { struct emu_midi_softc *scp; scp = device_get_softc(dev); mpu401_uninit(scp->mpu); emu_intr_unregister(scp->card, scp->ihandle); mtx_destroy(&scp->mtx); return (0); } static device_method_t emu_midi_methods[] = { DEVMETHOD(device_probe, emu_midi_probe), DEVMETHOD(device_attach, emu_midi_attach), DEVMETHOD(device_detach, emu_midi_detach), DEVMETHOD_END }; static driver_t emu_midi_driver = { "midi", emu_midi_methods, sizeof(struct emu_midi_softc), }; DRIVER_MODULE(snd_emu10kx_midi, emu10kx, emu_midi_driver, 0, 0); MODULE_DEPEND(snd_emu10kx_midi, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); MODULE_DEPEND(snd_emu10kx_midi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10kx_midi, SND_EMU10KX_PREFVER); diff --git a/sys/dev/sound/pci/emu10kx-pcm.c b/sys/dev/sound/pci/emu10kx-pcm.c index 825a39fc4e63..bef6b596646e 100644 --- a/sys/dev/sound/pci/emu10kx-pcm.c +++ b/sys/dev/sound/pci/emu10kx-pcm.c @@ -1,1533 +1,1532 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * Copyright (c) 2003-2007 Yuriy Tsibizov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif -#include #include #include #include "mixer_if.h" #include #include struct emu_pcm_pchinfo { int spd; int fmt; unsigned int blksz; int run; struct emu_voice *master; struct emu_voice *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; struct emu_pcm_rchinfo { int spd; int fmt; unsigned int blksz; int run; uint32_t idxreg; uint32_t basereg; uint32_t sizereg; uint32_t setupreg; uint32_t irqmask; uint32_t iprmask; int ihandle; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; /* XXX Hardware playback channels */ #define MAX_CHANNELS 4 #if MAX_CHANNELS > 13 #error Too many hardware channels defined. 13 is the maximum #endif struct emu_pcm_info { struct mtx *lock; device_t dev; /* device information */ struct emu_sc_info *card; struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */ int pnum; /* next free channel number */ struct emu_pcm_rchinfo rch_adc; struct emu_pcm_rchinfo rch_efx; struct emu_route rt; struct emu_route rt_mono; int route; int ihandle; /* interrupt handler */ unsigned int bufsz; int is_emu10k1; struct ac97_info *codec; uint32_t ac97_state[0x7F]; kobj_class_t ac97_mixerclass; uint32_t ac97_recdevs; uint32_t ac97_playdevs; struct snd_mixer *sm; int mch_disabled; unsigned int emu10k1_volcache[2][2]; }; static uint32_t emu_rfmt_adc[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_reccaps_adc = { 8000, 48000, emu_rfmt_adc, 0 }; static uint32_t emu_rfmt_efx[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), 0 }; static struct pcmchan_caps emu_reccaps_efx_live = { 48000*32, 48000*32, emu_rfmt_efx, 0 }; static struct pcmchan_caps emu_reccaps_efx_audigy = { 48000*64, 48000*64, emu_rfmt_efx, 0 }; static int emu_rates_live[] = { 48000*32 }; static int emu_rates_audigy[] = { 48000*64 }; static uint32_t emu_pfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static uint32_t emu_pfmt_mono[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0}; static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000}; static uint32_t emu_pcm_intr(void *pcm, uint32_t stat); static const struct emu_dspmix_props_k1 { uint8_t present; uint8_t recdev; int8_t input; } dspmix_k1 [SOUND_MIXER_NRDEVICES] = { /* no mixer device for ac97 */ /* in0 AC97 */ [SOUND_MIXER_DIGITAL1] = {1, 1, 1}, /* in1 CD SPDIF */ /* not connected */ /* in2 (zoom) */ [SOUND_MIXER_DIGITAL2] = {1, 1, 3}, /* in3 toslink */ [SOUND_MIXER_LINE2] = {1, 1, 4}, /* in4 Line-In2 */ [SOUND_MIXER_DIGITAL3] = {1, 1, 5}, /* in5 on-card SPDIF */ [SOUND_MIXER_LINE3] = {1, 1, 6}, /* in6 AUX2 */ /* not connected */ /* in7 */ }; static const struct emu_dspmix_props_k2 { uint8_t present; uint8_t recdev; int8_t input; } dspmix_k2 [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = {1, 0, (-1)}, [SOUND_MIXER_PCM] = {1, 0, (-1)}, /* no mixer device */ /* in0 AC97 */ [SOUND_MIXER_DIGITAL1] = {1, 1, 1}, /* in1 CD SPDIF */ [SOUND_MIXER_DIGITAL2] = {1, 1, 2}, /* in2 COAX SPDIF */ /* not connected */ /* in3 */ [SOUND_MIXER_LINE2] = {1, 1, 4}, /* in4 Line-In2 */ [SOUND_MIXER_DIGITAL3] = {1, 1, 5}, /* in5 on-card SPDIF */ [SOUND_MIXER_LINE3] = {1, 1, 6}, /* in6 AUX2 */ /* not connected */ /* in7 */ }; static int emu_dspmixer_init(struct snd_mixer *m) { struct emu_pcm_info *sc; int i; int p, r; p = 0; r = 0; sc = mix_getdevinfo(m); if (sc->route == RT_FRONT) { /* create submixer for AC97 codec */ if ((sc->ac97_mixerclass != NULL) && (sc->codec != NULL)) { sc->sm = mixer_create(sc->dev, sc->ac97_mixerclass, sc->codec, "ac97"); if (sc->sm != NULL) { p = mix_getdevs(sc->sm); r = mix_getrecdevs(sc->sm); } } sc->ac97_playdevs = p; sc->ac97_recdevs = r; } /* This two are always here */ p |= (1 << SOUND_MIXER_PCM); p |= (1 << SOUND_MIXER_VOLUME); if (sc->route == RT_FRONT) { if (sc->is_emu10k1) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k1[i].present) p |= (1 << i); if (dspmix_k1[i].recdev) r |= (1 << i); } } else { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k2[i].present) p |= (1 << i); if (dspmix_k2[i].recdev) r |= (1 << i); } } } mix_setdevs(m, p); mix_setrecdevs(m, r); return (0); } static int emu_dspmixer_uninit(struct snd_mixer *m) { struct emu_pcm_info *sc; int err = 0; /* drop submixer for AC97 codec */ sc = mix_getdevinfo(m); if (sc->sm != NULL) { err = mixer_delete(sc->sm); if (err) return (err); sc->sm = NULL; } return (0); } static int emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct emu_pcm_info *sc; sc = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_VOLUME: switch (sc->route) { case RT_FRONT: if (sc->sm != NULL) mix_set(sc->sm, dev, left, right); if (sc->mch_disabled) { /* In emu10k1 case PCM volume does not affect sound routed to rear & center/sub (it is connected to AC97 codec). Calculate it manually. */ /* This really should belong to emu10kx.c */ if (sc->is_emu10k1) { sc->emu10k1_volcache[0][0] = left; left = left * sc->emu10k1_volcache[1][0] / 100; sc->emu10k1_volcache[0][1] = right; right = right * sc->emu10k1_volcache[1][1] / 100; } emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); /* XXX side */ } } /* mch disabled */ break; case RT_REAR: emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_PCM: switch (sc->route) { case RT_FRONT: if (sc->sm != NULL) mix_set(sc->sm, dev, left, right); if (sc->mch_disabled) { /* See SOUND_MIXER_VOLUME case */ if (sc->is_emu10k1) { sc->emu10k1_volcache[1][0] = left; left = left * sc->emu10k1_volcache[0][0] / 100; sc->emu10k1_volcache[1][1] = right; right = right * sc->emu10k1_volcache[0][1] / 100; } emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); /* XXX side */ } } /* mch_disabled */ break; case RT_REAR: emumix_set_volume(sc->card, M_FX2_REAR_L, left); emumix_set_volume(sc->card, M_FX3_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_DIGITAL1: /* CD SPDIF, in1 */ emumix_set_volume(sc->card, M_IN1_FRONT_L, left); emumix_set_volume(sc->card, M_IN1_FRONT_R, right); break; case SOUND_MIXER_DIGITAL2: if (sc->is_emu10k1) { /* TOSLink, in3 */ emumix_set_volume(sc->card, M_IN3_FRONT_L, left); emumix_set_volume(sc->card, M_IN3_FRONT_R, right); } else { /* COAX SPDIF, in2 */ emumix_set_volume(sc->card, M_IN2_FRONT_L, left); emumix_set_volume(sc->card, M_IN2_FRONT_R, right); } break; case SOUND_MIXER_LINE2: /* Line-In2, in4 */ emumix_set_volume(sc->card, M_IN4_FRONT_L, left); emumix_set_volume(sc->card, M_IN4_FRONT_R, right); break; case SOUND_MIXER_DIGITAL3: /* on-card SPDIF, in5 */ emumix_set_volume(sc->card, M_IN5_FRONT_L, left); emumix_set_volume(sc->card, M_IN5_FRONT_R, right); break; case SOUND_MIXER_LINE3: /* AUX2, in6 */ emumix_set_volume(sc->card, M_IN6_FRONT_L, left); emumix_set_volume(sc->card, M_IN6_FRONT_R, right); break; default: if (sc->sm != NULL) { /* XXX emumix_set_volume is not required here */ emumix_set_volume(sc->card, M_IN0_FRONT_L, 100); emumix_set_volume(sc->card, M_IN0_FRONT_R, 100); mix_set(sc->sm, dev, left, right); } else device_printf(sc->dev, "mixer error: unknown device %d\n", dev); } return (0); } static u_int32_t emu_dspmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct emu_pcm_info *sc; int i; u_int32_t recmask; int input[8]; sc = mix_getdevinfo(m); recmask = 0; for (i=0; i < 8; i++) input[i]=0; if (sc->sm != NULL) if ((src & sc->ac97_recdevs) !=0) if (mix_setrecsrc(sc->sm, src & sc->ac97_recdevs) == 0) { recmask |= (src & sc->ac97_recdevs); /* Recording from AC97 codec. Enable AC97 route to rec on DSP */ input[0] = 1; } if (sc->is_emu10k1) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k1[i].recdev) if ((src & (1 << i)) == ((uint32_t)1 << i)) { recmask |= (1 << i); /* enable device i */ input[dspmix_k1[i].input] = 1; } } } else { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k2[i].recdev) if ((src & (1 << i)) == ((uint32_t)1 << i)) { recmask |= (1 << i); /* enable device i */ input[dspmix_k2[i].input] = 1; } } } emumix_set_volume(sc->card, M_IN0_REC_L, input[0] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN0_REC_R, input[0] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN1_REC_L, input[1] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN1_REC_R, input[1] == 1 ? 100 : 0); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_IN2_REC_L, input[2] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN2_REC_R, input[2] == 1 ? 100 : 0); } if (sc->is_emu10k1) { emumix_set_volume(sc->card, M_IN3_REC_L, input[3] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN3_REC_R, input[3] == 1 ? 100 : 0); } emumix_set_volume(sc->card, M_IN4_REC_L, input[4] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN4_REC_R, input[4] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN5_REC_L, input[5] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN5_REC_R, input[5] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN6_REC_L, input[6] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN6_REC_R, input[6] == 1 ? 100 : 0); /* XXX check for K1/k2 differences? */ if ((src & (1 << SOUND_MIXER_PCM)) == (1 << SOUND_MIXER_PCM)) { emumix_set_volume(sc->card, M_FX0_REC_L, emumix_get_volume(sc->card, M_FX0_FRONT_L)); emumix_set_volume(sc->card, M_FX1_REC_R, emumix_get_volume(sc->card, M_FX1_FRONT_R)); } else { emumix_set_volume(sc->card, M_FX0_REC_L, 0); emumix_set_volume(sc->card, M_FX1_REC_R, 0); } return (recmask); } static kobj_method_t emudspmixer_methods[] = { KOBJMETHOD(mixer_init, emu_dspmixer_init), KOBJMETHOD(mixer_uninit, emu_dspmixer_uninit), KOBJMETHOD(mixer_set, emu_dspmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(emudspmixer); static int emu_efxmixer_init(struct snd_mixer *m) { mix_setdevs(m, SOUND_MASK_VOLUME); mix_setrecdevs(m, SOUND_MASK_MONITOR); return (0); } static int emu_efxmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { if (left + right == 200) return (0); return (0); } static u_int32_t emu_efxmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused) { return (SOUND_MASK_MONITOR); } static kobj_method_t emuefxmixer_methods[] = { KOBJMETHOD(mixer_init, emu_efxmixer_init), KOBJMETHOD(mixer_set, emu_efxmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_efxmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(emuefxmixer); /* * AC97 emulation code for Audigy and later cards. * Some parts of AC97 codec are not used by hardware, but can be used * to change some DSP controls via AC97 mixer interface. This includes: * - master volume controls MASTER_FRONT_[R|L] * - pcm volume controls FX[0|1]_FRONT_[R|L] * - rec volume controls MASTER_REC_[R|L] * We do it because we need to put it under user control.... * We also keep some parts of AC97 disabled to get better sound quality */ #define AC97LEFT(x) ((x & 0x7F00)>>8) #define AC97RIGHT(x) (x & 0x007F) #define AC97MUTE(x) ((x & 0x8000)>>15) #define BIT4_TO100(x) (100-(x)*100/(0x0f)) #define BIT6_TO100(x) (100-(x)*100/(0x3f)) #define BIT4_TO255(x) (255-(x)*255/(0x0f)) #define BIT6_TO255(x) (255-(x)*255/(0x3f)) #define V100_TOBIT6(x) (0x3f*(100-x)/100) #define V100_TOBIT4(x) (0x0f*(100-x)/100) #define AC97ENCODE(x_muted, x_left, x_right) (((x_muted & 1)<<15) | ((x_left & 0x3f)<<8) | (x_right & 0x3f)) static int emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno) { int use_ac97; int emulated; int tmp; use_ac97 = 1; emulated = 0; switch (regno) { case AC97_MIX_MASTER: emulated = sc->ac97_state[AC97_MIX_MASTER]; use_ac97 = 0; break; case AC97_MIX_PCM: emulated = sc->ac97_state[AC97_MIX_PCM]; use_ac97 = 0; break; case AC97_REG_RECSEL: emulated = 0x0505; use_ac97 = 0; break; case AC97_MIX_RGAIN: emulated = sc->ac97_state[AC97_MIX_RGAIN]; use_ac97 = 0; break; } emu_wr(sc->card, EMU_AC97ADDR, regno, 1); tmp = emu_rd(sc->card, EMU_AC97DATA, 2); if (use_ac97) emulated = tmp; return (emulated); } static void emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data) { int write_ac97; int left, right; uint32_t emu_left, emu_right; int is_mute; write_ac97 = 1; left = AC97LEFT(data); emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */ right = AC97RIGHT(data); emu_right = BIT6_TO100(right); is_mute = AC97MUTE(data); if (is_mute) emu_left = emu_right = 0; switch (regno) { /* TODO: reset emulator on AC97_RESET */ case AC97_MIX_MASTER: emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left); emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 main out */ break; case AC97_MIX_PCM: /* PCM OUT VOL */ emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left); emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 PCM out */ break; case AC97_REG_RECSEL: /* * PCM recording source is set to "stereo mix" (labeled "vol" * in mixer). There is no 'playback' from AC97 codec - * if you want to hear anything from AC97 you have to _record_ * it. Keep things simple and record "stereo mix". */ data = 0x0505; break; case AC97_MIX_RGAIN: /* RECORD GAIN */ emu_left = BIT4_TO100(left); /* rgain is 4-bit */ emu_right = BIT4_TO100(right); emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left); emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right); /* * Record gain on AC97 should stay zero to get AC97 sound on * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not * directly connected to any output, only to EMU10K2 chip Use * this control to set AC97 mix volume inside EMU10K2 chip */ sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f); data = 0x0000; break; } if (write_ac97) { emu_wr(sc->card, EMU_AC97ADDR, regno, 1); emu_wr(sc->card, EMU_AC97DATA, data, 2); } } static int emu_erdcd(kobj_t obj __unused, void *devinfo, int regno) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; return (emu_ac97_read_emulation(sc, regno)); } static int emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; emu_ac97_write_emulation(sc, regno, data); return (0); } static kobj_method_t emu_eac97_methods[] = { KOBJMETHOD(ac97_read, emu_erdcd), KOBJMETHOD(ac97_write, emu_ewrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_eac97); /* real ac97 codec */ static int emu_rdcd(kobj_t obj __unused, void *devinfo, int regno) { int rd; struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard")); emu_wr(sc->card, EMU_AC97ADDR, regno, 1); rd = emu_rd(sc->card, EMU_AC97DATA, 2); return (rd); } static int emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard")); emu_wr(sc->card, EMU_AC97ADDR, regno, 1); emu_wr(sc->card, EMU_AC97DATA, data, 2); return (0); } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_ac97); static int emu_k1_recval(int speed) { int val; val = 0; while ((val < 7) && (speed < emu10k1_adcspeed[val])) val++; return (val); } static int emu_k2_recval(int speed) { int val; val = 0; while ((val < 8) && (speed < emu10k2_adcspeed[val])) val++; return (val); } static void * emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); KASSERT(sc->card != NULL, ("empchan_init: no soundcard")); if (sc->pnum >= MAX_CHANNELS) return (NULL); ch = &(sc->pch[sc->pnum++]); ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->master = emu_valloc(sc->card); /* * XXX we have to allocate slave even for mono channel until we * fix emu_vfree to handle this case. */ ch->slave = emu_valloc(sc->card); ch->timer = emu_timer_create(sc->card); r = (emu_vinit(sc->card, ch->master, ch->slave, EMU_PLAY_BUFSZ, ch->buffer)) ? NULL : ch; return (r); } static int emupchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); if (ch->slave != NULL) emu_vfree(sc->card, ch->slave); emu_vfree(sc->card, ch->master); return (0); } static int emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static uint32_t emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->spd = speed; return (ch->spd); } static uint32_t emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (blocksize > ch->pcm->bufsz) blocksize = ch->pcm->bufsz; snd_mtxlock(sc->lock); ch->blksz = blocksize; emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); snd_mtxunlock(sc->lock); return (ch->blksz); } static int emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */ if (go == PCMTRIG_START) { emu_vsetup(ch->master, ch->fmt, ch->spd); if (AFMT_CHANNEL(ch->fmt) > 1) emu_vroute(sc->card, &(sc->rt), ch->master); else emu_vroute(sc->card, &(sc->rt_mono), ch->master); emu_vwrite(sc->card, ch->master); emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); emu_timer_enable(sc->card, ch->timer, 1); } /* PCM interrupt handler will handle PCMTRIG_STOP event */ ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc->card, ch->master, ch->run); snd_mtxunlock(sc->lock); return (0); } static uint32_t emupchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_vpos(sc->card, ch->master); return (r); } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; switch (sc->route) { case RT_FRONT: /* FALLTHROUGH */ case RT_REAR: /* FALLTHROUGH */ case RT_SIDE: return (&emu_playcaps); break; case RT_CENTER: /* FALLTHROUGH */ case RT_SUB: return (&emu_playcaps_mono); break; } return (NULL); } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emupchan); static void * emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch_adc; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; /* We rise interrupt for half-full buffer */ ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->idxreg = sc->is_emu10k1 ? EMU_ADCIDX : EMU_A_ADCIDX; ch->basereg = EMU_ADCBA; ch->sizereg = EMU_ADCBS; ch->setupreg = EMU_ADCCR; ch->irqmask = EMU_INTE_ADCBUFENABLE; ch->iprmask = EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { ch->timer = emu_timer_create(sc->card); emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emurchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); return (0); } static int emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static uint32_t emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; if (ch->pcm->is_emu10k1) { speed = emu10k1_adcspeed[emu_k1_recval(speed)]; } else { speed = emu10k2_adcspeed[emu_k2_recval(speed)]; } ch->spd = speed; return (ch->spd); } static uint32_t emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; ch->blksz = blocksize; /* * If blocksize is less than half of buffer size we will not get * BUFHALFFULL interrupt in time and channel will need to generate * (and use) timer interrupts. Otherwise channel will be marked dead. */ if (ch->blksz < (ch->pcm->bufsz / 2)) { emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); emu_timer_enable(sc->card, ch->timer, 1); } else { emu_timer_enable(sc->card, ch->timer, 0); } return (ch->blksz); } static int emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t val, sz; if (!PCMTRIG_COMMON(go)) return (0); switch (sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); val = sc->is_emu10k1 ? EMU_ADCCR_LCHANENABLE : EMU_A_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= sc->is_emu10k1 ? EMU_ADCCR_RCHANENABLE : EMU_A_ADCCR_RCHANENABLE; val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd); emu_wrptr(sc->card, 0, ch->setupreg, 0); emu_wrptr(sc->card, 0, ch->setupreg, val); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc->card, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc->card, 0, ch->setupreg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t emurchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { return (&emu_reccaps_adc); } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_free, emurchan_free), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emurchan); static void * emufxrchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); if (sc == NULL) return (NULL); ch = &(sc->rch_efx); ch->fmt = SND_FORMAT(AFMT_S16_LE, 1, 0); ch->spd = sc->is_emu10k1 ? 48000*32 : 48000 * 64; ch->idxreg = EMU_FXIDX; ch->basereg = EMU_FXBA; ch->sizereg = EMU_FXBS; ch->irqmask = EMU_INTE_EFXBUFENABLE; ch->iprmask = EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emufxrchan_setformat(kobj_t obj __unused, void *c_devinfo __unused, uint32_t format) { if (format == SND_FORMAT(AFMT_S16_LE, 1, 0)) return (0); return (EINVAL); } static uint32_t emufxrchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; /* FIXED RATE CHANNEL */ return (ch->spd); } static uint32_t emufxrchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->blksz = blocksize; /* * XXX If blocksize is less than half of buffer size we will not get * interrupt in time and channel will die due to interrupt timeout. * This should not happen with FX rchan, because it will fill buffer * very fast (64K buffer is 0.021seconds on Audigy). */ if (ch->blksz < (ch->pcm->bufsz / 2)) ch->blksz = ch->pcm->bufsz / 2; return (ch->blksz); } static int emufxrchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t sz; if (!PCMTRIG_COMMON(go)) return (0); switch (sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); /* * SB Live! is limited to 32 mono channels. Audigy * has 64 mono channels. Channels are enabled * by setting a bit in EMU_A_FXWC[1|2] registers. */ /* XXX there is no way to demultiplex this streams for now */ if (sc->is_emu10k1) { emu_wrptr(sc->card, 0, EMU_FXWC, 0xffffffff); } else { emu_wrptr(sc->card, 0, EMU_A_FXWC1, 0xffffffff); emu_wrptr(sc->card, 0, EMU_A_FXWC2, 0xffffffff); } break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; if (sc->is_emu10k1) { emu_wrptr(sc->card, 0, EMU_FXWC, 0x0); } else { emu_wrptr(sc->card, 0, EMU_A_FXWC1, 0x0); emu_wrptr(sc->card, 0, EMU_A_FXWC2, 0x0); } emu_wrptr(sc->card, 0, ch->sizereg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t emufxrchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emufxrchan_getcaps(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (sc->is_emu10k1) return (&emu_reccaps_efx_live); return (&emu_reccaps_efx_audigy); } static int emufxrchan_getrates(kobj_t obj __unused, void *c_devinfo, int **rates) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (sc->is_emu10k1) *rates = emu_rates_live; else *rates = emu_rates_audigy; return 1; } static kobj_method_t emufxrchan_methods[] = { KOBJMETHOD(channel_init, emufxrchan_init), KOBJMETHOD(channel_setformat, emufxrchan_setformat), KOBJMETHOD(channel_setspeed, emufxrchan_setspeed), KOBJMETHOD(channel_setblocksize, emufxrchan_setblocksize), KOBJMETHOD(channel_trigger, emufxrchan_trigger), KOBJMETHOD(channel_getptr, emufxrchan_getptr), KOBJMETHOD(channel_getcaps, emufxrchan_getcaps), KOBJMETHOD(channel_getrates, emufxrchan_getrates), KOBJMETHOD_END }; CHANNEL_DECLARE(emufxrchan); static uint32_t emu_pcm_intr(void *pcm, uint32_t stat) { struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm; uint32_t ack; int i; ack = 0; snd_mtxlock(sc->lock); if (stat & EMU_IPR_INTERVALTIMER) { ack |= EMU_IPR_INTERVALTIMER; for (i = 0; i < MAX_CHANNELS; i++) if (sc->pch[i].channel) { if (sc->pch[i].run == 1) { snd_mtxunlock(sc->lock); chn_intr(sc->pch[i].channel); snd_mtxlock(sc->lock); } else emu_timer_enable(sc->card, sc->pch[i].timer, 0); } /* ADC may install timer to get low-latency interrupts */ if ((sc->rch_adc.channel) && (sc->rch_adc.run)) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_adc.channel); snd_mtxlock(sc->lock); } /* * EFX does not use timer, because it will fill * buffer at least 32x times faster than ADC. */ } if (stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) { ack |= stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL); if (sc->rch_adc.channel) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_adc.channel); snd_mtxlock(sc->lock); } } if (stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) { ack |= stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL); if (sc->rch_efx.channel) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_efx.channel); snd_mtxlock(sc->lock); } } snd_mtxunlock(sc->lock); return (ack); } static int emu_pcm_init(struct emu_pcm_info *sc) { sc->bufsz = pcm_getbuffersize(sc->dev, EMUPAGESIZE, EMU_REC_BUFSZ, EMU_MAX_BUFSZ); return (0); } static int emu_pcm_uninit(struct emu_pcm_info *sc __unused) { return (0); } static int emu_pcm_probe(device_t dev) { uintptr_t func, route; const char *rt; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func); if (func != SCF_PCM) return (ENXIO); rt = "UNKNOWN"; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); switch (route) { case RT_FRONT: rt = "front"; break; case RT_REAR: rt = "rear"; break; case RT_CENTER: rt = "center"; break; case RT_SUB: rt = "subwoofer"; break; case RT_SIDE: rt = "side"; break; case RT_MCHRECORD: rt = "multichannel recording"; break; } device_set_descf(dev, "EMU10Kx DSP %s PCM interface", rt); return (0); } static int emu_pcm_attach(device_t dev) { struct emu_pcm_info *sc; unsigned int i; char status[SND_STATUSLEN]; uint32_t inte, ipr; uintptr_t route, ivar; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev))); if (sc->card == NULL) { device_printf(dev, "cannot get bridge conf\n"); free(sc, M_DEVBUF); return (ENXIO); } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10kx pcm softc"); sc->dev = dev; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &ivar); sc->is_emu10k1 = ivar ? 1 : 0; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_MCH_DISABLED, &ivar); sc->mch_disabled = ivar ? 1 : 0; sc->codec = NULL; for (i = 0; i < 8; i++) { sc->rt.routing_left[i] = i; sc->rt.amounts_left[i] = 0x00; sc->rt.routing_right[i] = i; sc->rt.amounts_right[i] = 0x00; } for (i = 0; i < 8; i++) { sc->rt_mono.routing_left[i] = i; sc->rt_mono.amounts_left[i] = 0x00; sc->rt_mono.routing_right[i] = i; sc->rt_mono.amounts_right[i] = 0x00; } sc->emu10k1_volcache[0][0] = 75; sc->emu10k1_volcache[1][0] = 75; sc->emu10k1_volcache[0][1] = 75; sc->emu10k1_volcache[1][1] = 75; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); sc->route = route; switch (route) { case RT_FRONT: sc->rt.amounts_left[0] = 0xff; sc->rt.amounts_right[1] = 0xff; sc->rt_mono.amounts_left[0] = 0xff; sc->rt_mono.amounts_left[1] = 0xff; if (sc->is_emu10k1) sc->codec = AC97_CREATE(dev, sc, emu_ac97); else sc->codec = AC97_CREATE(dev, sc, emu_eac97); sc->ac97_mixerclass = NULL; if (sc->codec != NULL) sc->ac97_mixerclass = ac97_getmixerclass(); if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize DSP mixer\n"); goto bad; } break; case RT_REAR: sc->rt.amounts_left[2] = 0xff; sc->rt.amounts_right[3] = 0xff; sc->rt_mono.amounts_left[2] = 0xff; sc->rt_mono.amounts_left[3] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_CENTER: sc->rt.amounts_left[4] = 0xff; sc->rt_mono.amounts_left[4] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SUB: sc->rt.amounts_left[5] = 0xff; sc->rt_mono.amounts_left[5] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SIDE: sc->rt.amounts_left[6] = 0xff; sc->rt.amounts_right[7] = 0xff; sc->rt_mono.amounts_left[6] = 0xff; sc->rt_mono.amounts_left[7] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_MCHRECORD: if (mixer_init(dev, &emuefxmixer_class, sc)) { device_printf(dev, "failed to initialize EFX mixer\n"); goto bad; } break; default: device_printf(dev, "invalid default route\n"); goto bad; } inte = EMU_INTE_INTERTIMERENB; ipr = EMU_IPR_INTERVALTIMER; /* Used by playback & ADC */ sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc); if (emu_pcm_init(sc) == -1) { device_printf(dev, "unable to initialize PCM part of the card\n"); goto bad; } /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); /* XXX we should better get number of available channels from parent */ if (pcm_register(dev, sc, (route == RT_FRONT) ? MAX_CHANNELS : 1, (route == RT_FRONT) ? 1 : 0)) { device_printf(dev, "can't register PCM channels!\n"); goto bad; } sc->pnum = 0; if (route != RT_MCHRECORD) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); if (route == RT_FRONT) { for (i = 1; i < MAX_CHANNELS; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); } if (route == RT_MCHRECORD) pcm_addchan(dev, PCMDIR_REC, &emufxrchan_class, sc); snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static int emu_pcm_detach(device_t dev) { int r; struct emu_pcm_info *sc; sc = pcm_getdevinfo(dev); r = pcm_unregister(dev); if (r) return (r); emu_pcm_uninit(sc); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (0); } static device_method_t emu_pcm_methods[] = { DEVMETHOD(device_probe, emu_pcm_probe), DEVMETHOD(device_attach, emu_pcm_attach), DEVMETHOD(device_detach, emu_pcm_detach), DEVMETHOD_END }; static driver_t emu_pcm_driver = { "pcm", emu_pcm_methods, PCM_SOFTC_SIZE, NULL, 0, NULL }; DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, 0, 0); MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER); diff --git a/sys/dev/sound/pci/emu10kx.c b/sys/dev/sound/pci/emu10kx.c index d17f5fb16d34..0f938597e06c 100644 --- a/sys/dev/sound/pci/emu10kx.c +++ b/sys/dev/sound/pci/emu10kx.c @@ -1,3533 +1,3532 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * Copyright (c) 2003-2007 Yuriy Tsibizov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #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; unsigned int thiscard; int i; device = pci_read_config(dev, PCIR_DEVICE, /* bytes */ 2); subdevice = pci_read_config(dev, PCIR_SUBDEV_0, /* bytes */ 2); thiscard = 0; for (i = 1; i < nitems(emu_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. */ } } } for (i = 0; i < nitems(emu_bad_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) { /* * 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 */ emu_rd_nolock(sc, 0x38, 4); emu_wr_nolock(sc, 0x38, data, 4); 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) { mtx_lock(&sc->lock); for (int 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) 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 | CTLFLAG_MPSAFE, 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 | CTLFLAG_MPSAFE, 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 receiver 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, 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 = NULL; 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 */NULL, /* lockarg */NULL, &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) { 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); device_set_descf(dev, "Creative %s [%s]", emu_cards[thiscard].desc, emu_cards[thiscard].SBcode); 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, NULL, 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%jx irq %jd", 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); } DRIVER_MODULE(snd_emu10kx, pci, emu_driver, emu_modevent, NULL); MODULE_VERSION(snd_emu10kx, SND_EMU10KX_PREFVER); diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c index db39b867879f..4bea6c65e087 100644 --- a/sys/dev/sound/pci/hdspe-pcm.c +++ b/sys/dev/sound/pci/hdspe-pcm.c @@ -1,1083 +1,1082 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2021 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * RME HDSPe driver for FreeBSD (pcm-part). * Supported cards: AIO, RayDAT. */ #include #include -#include #include #include #include struct hdspe_latency { uint32_t n; uint32_t period; float ms; }; static struct hdspe_latency latency_map[] = { { 7, 32, 0.7 }, { 0, 64, 1.5 }, { 1, 128, 3 }, { 2, 256, 6 }, { 3, 512, 12 }, { 4, 1024, 23 }, { 5, 2048, 46 }, { 6, 4096, 93 }, { 0, 0, 0 }, }; struct hdspe_rate { uint32_t speed; uint32_t reg; }; static struct hdspe_rate rate_map[] = { { 32000, (HDSPE_FREQ_32000) }, { 44100, (HDSPE_FREQ_44100) }, { 48000, (HDSPE_FREQ_48000) }, { 64000, (HDSPE_FREQ_32000 | HDSPE_FREQ_DOUBLE) }, { 88200, (HDSPE_FREQ_44100 | HDSPE_FREQ_DOUBLE) }, { 96000, (HDSPE_FREQ_48000 | HDSPE_FREQ_DOUBLE) }, { 128000, (HDSPE_FREQ_32000 | HDSPE_FREQ_QUAD) }, { 176400, (HDSPE_FREQ_44100 | HDSPE_FREQ_QUAD) }, { 192000, (HDSPE_FREQ_48000 | HDSPE_FREQ_QUAD) }, { 0, 0 }, }; static uint32_t hdspe_channel_play_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL | HDSPE_CHAN_RAY_ALL)); } static uint32_t hdspe_channel_rec_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL_REC | HDSPE_CHAN_RAY_ALL)); } static unsigned int hdspe_adat_width(uint32_t speed) { if (speed > 96000) return (2); if (speed > 48000) return (4); return (8); } static uint32_t hdspe_port_first(uint32_t ports) { return (ports & (~(ports - 1))); /* Extract first bit set. */ } static uint32_t hdspe_port_first_row(uint32_t ports) { uint32_t ends; /* Restrict ports to one set with contiguous slots. */ if (ports & HDSPE_CHAN_AIO_LINE) ports = HDSPE_CHAN_AIO_LINE; /* Gap in the AIO slots here. */ else if (ports & HDSPE_CHAN_AIO_ALL) ports &= HDSPE_CHAN_AIO_ALL; /* Rest of the AIO slots. */ else if (ports & HDSPE_CHAN_RAY_ALL) ports &= HDSPE_CHAN_RAY_ALL; /* All RayDAT slots. */ /* Ends of port rows are followed by a port which is not in the set. */ ends = ports & (~(ports >> 1)); /* First row of contiguous ports ends in the first row end. */ return (ports & (ends ^ (ends - 1))); } static unsigned int hdspe_channel_count(uint32_t ports, uint32_t adat_width) { unsigned int count = 0; if (ports & HDSPE_CHAN_AIO_ALL) { /* AIO ports. */ if (ports & HDSPE_CHAN_AIO_LINE) count += 2; if (ports & HDSPE_CHAN_AIO_PHONE) count += 2; if (ports & HDSPE_CHAN_AIO_AES) count += 2; if (ports & HDSPE_CHAN_AIO_SPDIF) count += 2; if (ports & HDSPE_CHAN_AIO_ADAT) count += adat_width; } else if (ports & HDSPE_CHAN_RAY_ALL) { /* RayDAT ports. */ if (ports & HDSPE_CHAN_RAY_AES) count += 2; if (ports & HDSPE_CHAN_RAY_SPDIF) count += 2; if (ports & HDSPE_CHAN_RAY_ADAT1) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT2) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT3) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT4) count += adat_width; } return (count); } static unsigned int hdspe_channel_offset(uint32_t subset, uint32_t ports, unsigned int adat_width) { uint32_t preceding; /* Make sure we have a subset of ports. */ subset &= ports; /* Include all ports preceding the first one of the subset. */ preceding = ports & (~subset & (subset - 1)); if (preceding & HDSPE_CHAN_AIO_ALL) preceding &= HDSPE_CHAN_AIO_ALL; /* Contiguous AIO slots. */ else if (preceding & HDSPE_CHAN_RAY_ALL) preceding &= HDSPE_CHAN_RAY_ALL; /* Contiguous RayDAT slots. */ return (hdspe_channel_count(preceding, adat_width)); } static unsigned int hdspe_port_slot_offset(uint32_t port, unsigned int adat_width) { /* Exctract the first port (lowest bit) if set of ports. */ switch (hdspe_port_first(port)) { /* AIO ports */ case HDSPE_CHAN_AIO_LINE: return (0); case HDSPE_CHAN_AIO_PHONE: return (6); case HDSPE_CHAN_AIO_AES: return (8); case HDSPE_CHAN_AIO_SPDIF: return (10); case HDSPE_CHAN_AIO_ADAT: return (12); /* RayDAT ports */ case HDSPE_CHAN_RAY_AES: return (0); case HDSPE_CHAN_RAY_SPDIF: return (2); case HDSPE_CHAN_RAY_ADAT1: return (4); case HDSPE_CHAN_RAY_ADAT2: return (4 + adat_width); case HDSPE_CHAN_RAY_ADAT3: return (4 + 2 * adat_width); case HDSPE_CHAN_RAY_ADAT4: return (4 + 3 * adat_width); default: return (0); } } static unsigned int hdspe_port_slot_width(uint32_t ports, unsigned int adat_width) { uint32_t row; /* Count number of contiguous slots from the first physical port. */ row = hdspe_port_first_row(ports); return (hdspe_channel_count(row, adat_width)); } static int hdspe_hw_mixer(struct sc_chinfo *ch, unsigned int dst, unsigned int src, unsigned short data) { struct sc_pcminfo *scp; struct sc_info *sc; int offs; scp = ch->parent; sc = scp->sc; offs = 0; if (ch->dir == PCMDIR_PLAY) offs = 64; hdspe_write_4(sc, HDSPE_MIXER_BASE + ((offs + src + 128 * dst) * sizeof(uint32_t)), data & 0xFFFF); return (0); }; static int hdspechan_setgain(struct sc_chinfo *ch) { struct sc_info *sc; uint32_t port, ports; unsigned int slot, end_slot; unsigned short volume; sc = ch->parent->sc; /* Iterate through all physical ports of the channel. */ ports = ch->ports; port = hdspe_port_first(ports); while (port != 0) { /* Get slot range of the physical port. */ slot = hdspe_port_slot_offset(port, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(port, hdspe_adat_width(sc->speed)); /* Treat first slot as left channel. */ volume = ch->lvol * HDSPE_MAX_GAIN / 100; for (; slot < end_slot; slot++) { hdspe_hw_mixer(ch, slot, slot, volume); /* Subsequent slots all get the right channel volume. */ volume = ch->rvol * HDSPE_MAX_GAIN / 100; } ports &= ~port; port = hdspe_port_first(ports); } return (0); } static int hdspemixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return (-1); mask = SOUND_MASK_PCM; if (hdspe_channel_play_ports(scp->hc)) mask |= SOUND_MASK_VOLUME; if (hdspe_channel_rec_ports(scp->hc)) mask |= SOUND_MASK_RECLEV; snd_mtxlock(sc->lock); pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, mask); snd_mtxunlock(sc->lock); return (0); } static int hdspemixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_pcminfo *scp; struct sc_chinfo *ch; int i; scp = mix_getdevinfo(m); #if 0 device_printf(scp->dev, "hdspemixer_set() %d %d\n", left, right); #endif for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; if ((dev == SOUND_MIXER_VOLUME && ch->dir == PCMDIR_PLAY) || (dev == SOUND_MIXER_RECLEV && ch->dir == PCMDIR_REC)) { ch->lvol = left; ch->rvol = right; if (ch->run) hdspechan_setgain(ch); } } return (0); } static kobj_method_t hdspemixer_methods[] = { KOBJMETHOD(mixer_init, hdspemixer_init), KOBJMETHOD(mixer_set, hdspemixer_set), KOBJMETHOD_END }; MIXER_DECLARE(hdspemixer); static void hdspechan_enable(struct sc_chinfo *ch, int value) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; int reg; unsigned int slot, end_slot; scp = ch->parent; sc = scp->sc; if (ch->dir == PCMDIR_PLAY) reg = HDSPE_OUT_ENABLE_BASE; else reg = HDSPE_IN_ENABLE_BASE; ch->run = value; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { slot = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); for (; slot < end_slot; slot++) { hdspe_write_1(sc, reg + (4 * slot), value); } ports &= ~row; row = hdspe_port_first_row(ports); } } static int hdspe_running(struct sc_info *sc) { struct sc_pcminfo *scp; struct sc_chinfo *ch; device_t *devlist; int devcount; int i, j; int err; if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) goto bad; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); for (j = 0; j < scp->chnum; j++) { ch = &scp->chan[j]; if (ch->run) goto bad; } } free(devlist, M_TEMP); return (0); bad: #if 0 device_printf(sc->dev, "hdspe is running\n"); #endif free(devlist, M_TEMP); return (1); } static void hdspe_start_audio(struct sc_info *sc) { sc->ctrl_register |= (HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); } static void hdspe_stop_audio(struct sc_info *sc) { if (hdspe_running(sc) == 1) return; sc->ctrl_register &= ~(HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); } static void buffer_mux_write(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { dma[slot * HDSPE_CHANBUF_SAMPLES + pos] = pcm[pos * channels + slot]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_mux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate DMA slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_mux_write(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_mux_write(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_mux_write(dma, pcm, pos, samples, 8, channels); else buffer_mux_write(dma, pcm, pos, samples, slots, channels); } static void buffer_demux_read(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { pcm[pos * channels + slot] = dma[slot * HDSPE_CHANBUF_SAMPLES + pos]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_demux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate port slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_demux_read(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_demux_read(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_demux_read(dma, pcm, pos, samples, 8, channels); else buffer_demux_read(dma, pcm, pos, samples, slots, channels); } /* Copy data between DMA and PCM buffers. */ static void buffer_copy(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; unsigned int pos; unsigned int n; unsigned int adat_width, pcm_width; scp = ch->parent; sc = scp->sc; n = AFMT_CHANNEL(ch->format); /* n channels */ /* Let pcm formats differ from current hardware ADAT width. */ adat_width = hdspe_adat_width(sc->speed); if (n == hdspe_channel_count(ch->ports, 2)) pcm_width = 2; else if (n == hdspe_channel_count(ch->ports, 4)) pcm_width = 4; else pcm_width = 8; if (ch->dir == PCMDIR_PLAY) pos = sndbuf_getreadyptr(ch->buffer); else pos = sndbuf_getfreeptr(ch->buffer); pos /= 4; /* Bytes per sample. */ pos /= n; /* Destination buffer n-times smaller. */ /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); while (row != 0) { if (ch->dir == PCMDIR_PLAY) buffer_mux_port(sc->pbuf, ch->data, row, ch->ports, pos, sc->period * 2, adat_width, pcm_width); else buffer_demux_port(sc->rbuf, ch->data, row, ch->ports, pos, sc->period * 2, adat_width, pcm_width); ports &= ~row; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); } } static int clean(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t *buf; uint32_t row, ports; unsigned int offset, slots; scp = ch->parent; sc = scp->sc; buf = sc->rbuf; if (ch->dir == PCMDIR_PLAY) buf = sc->pbuf; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { offset = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); slots = hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); bzero(buf + offset * HDSPE_CHANBUF_SAMPLES, slots * HDSPE_CHANBUF_SIZE); ports &= ~row; row = hdspe_port_first_row(ports); } return (0); } /* Channel interface. */ static void * hdspechan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int num; scp = devinfo; sc = scp->sc; snd_mtxlock(sc->lock); num = scp->chnum; ch = &scp->chan[num]; if (dir == PCMDIR_PLAY) ch->ports = hdspe_channel_play_ports(scp->hc); else ch->ports = hdspe_channel_rec_ports(scp->hc); ch->run = 0; ch->lvol = 0; ch->rvol = 0; /* Support all possible ADAT widths as channel formats. */ ch->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 2), 0); ch->cap_fmts[1] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 4), 0); ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 8), 0); ch->cap_fmts[3] = 0; ch->caps = malloc(sizeof(struct pcmchan_caps), M_HDSPE, M_NOWAIT); *(ch->caps) = (struct pcmchan_caps) {32000, 192000, ch->cap_fmts, 0}; /* Allocate maximum buffer size. */ ch->size = HDSPE_CHANBUF_SIZE * hdspe_channel_count(ch->ports, 8); ch->data = malloc(ch->size, M_HDSPE, M_NOWAIT); ch->buffer = b; ch->channel = c; ch->parent = scp; ch->dir = dir; snd_mtxunlock(sc->lock); if (sndbuf_setup(ch->buffer, ch->data, ch->size) != 0) { device_printf(scp->dev, "Can't setup sndbuf.\n"); return (NULL); } return (ch); } static int hdspechan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "hdspechan_trigger(): start\n"); #endif hdspechan_enable(ch, 1); hdspechan_setgain(ch); hdspe_start_audio(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "hdspechan_trigger(): stop or abort\n"); #endif clean(ch); hdspechan_enable(ch, 0); hdspe_stop_audio(sc); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: if(ch->run) buffer_copy(ch); break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t hdspechan_getptr(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; uint32_t ret, pos; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); ret = hdspe_read_2(sc, HDSPE_STATUS_REG); snd_mtxunlock(sc->lock); pos = ret & HDSPE_BUF_POSITION_MASK; pos *= AFMT_CHANNEL(ch->format); /* Hardbuf with multiple channels. */ return (pos); } static int hdspechan_free(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; #if 0 device_printf(scp->dev, "hdspechan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_HDSPE); ch->data = NULL; } if (ch->caps != NULL) { free(ch->caps, M_HDSPE); ch->caps = NULL; } snd_mtxunlock(sc->lock); return (0); } static int hdspechan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scp = ch->parent; device_printf(scp->dev, "hdspechan_setformat(%d)\n", format); #endif ch->format = format; return (0); } static uint32_t hdspechan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct hdspe_rate *hr; struct sc_chinfo *ch; struct sc_info *sc; long long period; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hr = NULL; #if 0 device_printf(scp->dev, "hdspechan_setspeed(%d)\n", speed); #endif if (hdspe_running(sc) == 1) goto end; /* First look for equal frequency. */ for (i = 0; rate_map[i].speed != 0; i++) { if (rate_map[i].speed == speed) hr = &rate_map[i]; } /* If no match, just find nearest. */ if (hr == NULL) { for (i = 0; rate_map[i].speed != 0; i++) { hr = &rate_map[i]; threshold = hr->speed + ((rate_map[i + 1].speed != 0) ? ((rate_map[i + 1].speed - hr->speed) >> 1) : 0); if (speed < threshold) break; } } switch (sc->type) { case HDSPE_RAYDAT: case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: /* Unsupported card. */ goto end; } /* Write frequency on the device. */ sc->ctrl_register &= ~HDSPE_FREQ_MASK; sc->ctrl_register |= hr->reg; hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); speed = hr->speed; if (speed > 96000) speed /= 4; else if (speed > 48000) speed /= 2; /* Set DDS value. */ period /= speed; hdspe_write_4(sc, HDSPE_FREQ_REG, period); sc->speed = hr->speed; end: return (sc->speed); } static uint32_t hdspechan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct hdspe_latency *hl; struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hl = NULL; #if 0 device_printf(scp->dev, "hdspechan_setblocksize(%d)\n", blocksize); #endif if (hdspe_running(sc) == 1) goto end; if (blocksize > HDSPE_LAT_BYTES_MAX) blocksize = HDSPE_LAT_BYTES_MAX; else if (blocksize < HDSPE_LAT_BYTES_MIN) blocksize = HDSPE_LAT_BYTES_MIN; blocksize /= 4 /* samples */; /* First look for equal latency. */ for (i = 0; latency_map[i].period != 0; i++) { if (latency_map[i].period == blocksize) hl = &latency_map[i]; } /* If no match, just find nearest. */ if (hl == NULL) { for (i = 0; latency_map[i].period != 0; i++) { hl = &latency_map[i]; threshold = hl->period + ((latency_map[i + 1].period != 0) ? ((latency_map[i + 1].period - hl->period) >> 1) : 0); if (blocksize < threshold) break; } } snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSPE_LAT_MASK; sc->ctrl_register |= hdspe_encode_latency(hl->n); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); sc->period = hl->period; snd_mtxunlock(sc->lock); #if 0 device_printf(scp->dev, "New period=%d\n", sc->period); #endif sndbuf_resize(ch->buffer, (HDSPE_CHANBUF_SIZE * AFMT_CHANNEL(ch->format)) / (sc->period * 4), (sc->period * 4)); end: return (sndbuf_getblksz(ch->buffer)); } static uint32_t hdspe_bkp_fmt[] = { SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps hdspe_bkp_caps = {32000, 192000, hdspe_bkp_fmt, 0}; static struct pcmchan_caps * hdspechan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scl = ch->parent; device_printf(scp->dev, "hdspechan_getcaps()\n"); #endif if (ch->caps != NULL) return (ch->caps); return (&hdspe_bkp_caps); } static kobj_method_t hdspechan_methods[] = { KOBJMETHOD(channel_init, hdspechan_init), KOBJMETHOD(channel_free, hdspechan_free), KOBJMETHOD(channel_setformat, hdspechan_setformat), KOBJMETHOD(channel_setspeed, hdspechan_setspeed), KOBJMETHOD(channel_setblocksize, hdspechan_setblocksize), KOBJMETHOD(channel_trigger, hdspechan_trigger), KOBJMETHOD(channel_getptr, hdspechan_getptr), KOBJMETHOD(channel_getcaps, hdspechan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdspechan); static int hdspe_pcm_probe(device_t dev) { #if 0 device_printf(dev,"hdspe_pcm_probe()\n"); #endif return (0); } static uint32_t hdspe_pcm_intr(struct sc_pcminfo *scp) { struct sc_chinfo *ch; struct sc_info *sc; int i; sc = scp->sc; for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } return (0); } static int hdspe_pcm_attach(device_t dev) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; const char *buf; int err; int play, rec; scp = device_get_ivars(dev); scp->ih = &hdspe_pcm_intr; if (scp->hc->ports & HDSPE_CHAN_AIO_ALL) buf = "AIO"; else if (scp->hc->ports & HDSPE_CHAN_RAY_ALL) buf = "RayDAT"; else buf = "?"; device_set_descf(dev, "HDSPe %s [%s]", buf, scp->hc->descr); /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); play = (hdspe_channel_play_ports(scp->hc)) ? 1 : 0; rec = (hdspe_channel_rec_ports(scp->hc)) ? 1 : 0; err = pcm_register(dev, scp, play, rec); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } scp->chnum = 0; if (play) { pcm_addchan(dev, PCMDIR_PLAY, &hdspechan_class, scp); scp->chnum++; } if (rec) { pcm_addchan(dev, PCMDIR_REC, &hdspechan_class, scp); scp->chnum++; } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(scp->sc->cs), rman_get_start(scp->sc->irq), device_get_nameunit(device_get_parent(dev))); pcm_setstatus(dev, status); mixer_init(dev, &hdspemixer_class, scp); return (0); } static int hdspe_pcm_detach(device_t dev) { int err; err = pcm_unregister(dev); if (err) { device_printf(dev, "Can't unregister device.\n"); return (err); } return (0); } static device_method_t hdspe_pcm_methods[] = { DEVMETHOD(device_probe, hdspe_pcm_probe), DEVMETHOD(device_attach, hdspe_pcm_attach), DEVMETHOD(device_detach, hdspe_pcm_detach), { 0, 0 } }; static driver_t hdspe_pcm_driver = { "pcm", hdspe_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdspe_pcm, hdspe, hdspe_pcm_driver, 0, 0); MODULE_DEPEND(snd_hdspe, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hdspe, 1); diff --git a/sys/dev/sound/pci/hdspe.c b/sys/dev/sound/pci/hdspe.c index e0197d1e981a..91d1f3ee1bf6 100644 --- a/sys/dev/sound/pci/hdspe.c +++ b/sys/dev/sound/pci/hdspe.c @@ -1,586 +1,585 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2016 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * RME HDSPe driver for FreeBSD. * Supported cards: AIO, RayDAT. */ #include #include #include #include -#include #include #include #include static struct hdspe_clock_source hdspe_clock_source_table_rd[] = { { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, { "adat1", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, { "adat2", 4 << 1 | 0, HDSPE_STATUS1_CLOCK( 4), 1 << 3, 1 << 11 }, { "adat3", 5 << 1 | 0, HDSPE_STATUS1_CLOCK( 5), 1 << 4, 1 << 12 }, { "adat4", 6 << 1 | 0, HDSPE_STATUS1_CLOCK( 6), 1 << 5, 1 << 13 }, { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, }; static struct hdspe_clock_source hdspe_clock_source_table_aio[] = { { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, { "adat", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, }; static struct hdspe_channel chan_map_aio[] = { { HDSPE_CHAN_AIO_LINE, "line" }, { HDSPE_CHAN_AIO_PHONE, "phone" }, { HDSPE_CHAN_AIO_AES, "aes" }, { HDSPE_CHAN_AIO_SPDIF, "s/pdif" }, { HDSPE_CHAN_AIO_ADAT, "adat" }, { 0, NULL }, }; static struct hdspe_channel chan_map_rd[] = { { HDSPE_CHAN_RAY_AES, "aes" }, { HDSPE_CHAN_RAY_SPDIF, "s/pdif" }, { HDSPE_CHAN_RAY_ADAT1, "adat1" }, { HDSPE_CHAN_RAY_ADAT2, "adat2" }, { HDSPE_CHAN_RAY_ADAT3, "adat3" }, { HDSPE_CHAN_RAY_ADAT4, "adat4" }, { 0, NULL }, }; static void hdspe_intr(void *p) { struct sc_pcminfo *scp; struct sc_info *sc; device_t *devlist; int devcount; int status; int err; int i; sc = (struct sc_info *)p; snd_mtxlock(sc->lock); status = hdspe_read_1(sc, HDSPE_STATUS_REG); if (status & HDSPE_AUDIO_IRQ_PENDING) { if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) return; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); if (scp->ih != NULL) scp->ih(scp); } hdspe_write_1(sc, HDSPE_INTERRUPT_ACK, 0); free(devlist, M_TEMP); } snd_mtxunlock(sc->lock); } static void hdspe_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { #if 0 device_printf(sc->dev, "hdspe_dmapsetmap()\n"); #endif } static int hdspe_alloc_resources(struct sc_info *sc) { /* Allocate resource. */ sc->csid = PCIR_BAR(0); sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &sc->csid, RF_ACTIVE); if (!sc->cs) { device_printf(sc->dev, "Unable to map SYS_RES_MEMORY.\n"); return (ENXIO); } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); /* Allocate interrupt resource. */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV, NULL, hdspe_intr, sc, &sc->ih)) { device_printf(sc->dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } /* Allocate DMA resources. */ 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*/2 * HDSPE_DMASEGSIZE, /*nsegments*/2, /*maxsegsz*/HDSPE_DMASEGSIZE, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, /*dmatag*/&sc->dmat) != 0) { device_printf(sc->dev, "Unable to create dma tag.\n"); return (ENXIO); } sc->bufsize = HDSPE_DMASEGSIZE; /* pbuf (play buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_WAITOK, &sc->pmap)) { device_printf(sc->dev, "Can't alloc pbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize, hdspe_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load pbuf.\n"); return (ENXIO); } /* rbuf (rec buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_WAITOK, &sc->rmap)) { device_printf(sc->dev, "Can't alloc rbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize, hdspe_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load rbuf.\n"); return (ENXIO); } bzero(sc->pbuf, sc->bufsize); bzero(sc->rbuf, sc->bufsize); return (0); } static void hdspe_map_dmabuf(struct sc_info *sc) { uint32_t paddr, raddr; int i; paddr = vtophys(sc->pbuf); raddr = vtophys(sc->rbuf); for (i = 0; i < HDSPE_MAX_SLOTS * 16; i++) { hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_OUT + 4 * i, paddr + i * 4096); hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_IN + 4 * i, raddr + i * 4096); } } static int hdspe_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[16] = "invalid"; int error; uint32_t setting; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Extract preferred clock source from settings register. */ setting = sc->settings_register & HDSPE_SETTING_CLOCK_MASK; for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->setting == setting) break; } if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find clock source matching the sysctl string. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (strncasecmp(buf, clock->name, sizeof(buf)) == 0) break; } /* Set preferred clock source in settings register. */ if (clock->name != NULL) { setting = clock->setting & HDSPE_SETTING_CLOCK_MASK; snd_mtxlock(sc->lock); sc->settings_register &= ~HDSPE_SETTING_CLOCK_MASK; sc->settings_register |= setting; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); snd_mtxunlock(sc->lock); } return (0); } static int hdspe_sysctl_clock_source(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[16] = "invalid"; uint32_t status; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Read current (autosync) clock source from status register. */ snd_mtxlock(sc->lock); status = hdspe_read_4(sc, HDSPE_STATUS1_REG); status &= HDSPE_STATUS1_CLOCK_MASK; snd_mtxunlock(sc->lock); /* Translate status register value to clock source. */ for (clock = clock_table; clock->name != NULL; ++clock) { /* In clock master mode, override with internal clock source. */ if (sc->settings_register & HDSPE_SETTING_MASTER) { if (clock->setting & HDSPE_SETTING_MASTER) break; } else if (clock->status == status) break; } /* Process sysctl string request. */ if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_sysctl_clock_list(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[256]; int n; sc = oidp->oid_arg1; n = 0; /* Select clock source table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* List available clock sources. */ buf[0] = 0; for (clock = clock_table; clock->name != NULL; ++clock) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); n += strlcpy(buf + n, clock->name, sizeof(buf) - n); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_sysctl_sync_status(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[256]; char *state; int n; uint32_t status; sc = oidp->oid_arg1; n = 0; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Read current lock and sync bits from status register. */ snd_mtxlock(sc->lock); status = hdspe_read_4(sc, HDSPE_STATUS1_REG); snd_mtxunlock(sc->lock); /* List clock sources with lock and sync state. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->sync_bit != 0) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); state = "none"; if ((clock->sync_bit & status) != 0) state = "sync"; else if ((clock->lock_bit & status) != 0) state = "lock"; n += snprintf(buf + n, sizeof(buf) - n, "%s(%s)", clock->name, state); } } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_probe(device_t dev) { uint32_t rev; if (pci_get_vendor(dev) == PCI_VENDOR_XILINX && pci_get_device(dev) == PCI_DEVICE_XILINX_HDSPE) { rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_AIO: device_set_desc(dev, "RME HDSPe AIO"); return (0); case PCI_REVISION_RAYDAT: device_set_desc(dev, "RME HDSPe RayDAT"); return (0); } } return (ENXIO); } static int hdspe_init(struct sc_info *sc) { long long period; /* Set latency. */ sc->period = 32; sc->ctrl_register = hdspe_encode_latency(7); /* Set rate. */ sc->speed = HDSPE_SPEED_DEFAULT; sc->ctrl_register &= ~HDSPE_FREQ_MASK; sc->ctrl_register |= HDSPE_FREQ_MASK_DEFAULT; hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); switch (sc->type) { case HDSPE_RAYDAT: case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: return (ENXIO); } /* Set DDS value. */ period /= sc->speed; hdspe_write_4(sc, HDSPE_FREQ_REG, period); /* Other settings. */ sc->settings_register = 0; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); return (0); } static int hdspe_attach(device_t dev) { struct hdspe_channel *chan_map; struct sc_pcminfo *scp; struct sc_info *sc; uint32_t rev; int i, err; #if 0 device_printf(dev, "hdspe_attach()\n"); #endif sc = device_get_softc(dev); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_hdspe softc"); sc->dev = dev; pci_enable_busmaster(dev); rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_AIO: sc->type = HDSPE_AIO; chan_map = chan_map_aio; break; case PCI_REVISION_RAYDAT: sc->type = HDSPE_RAYDAT; chan_map = chan_map_rd; break; default: return (ENXIO); } /* Allocate resources. */ err = hdspe_alloc_resources(sc); if (err) { device_printf(dev, "Unable to allocate system resources.\n"); return (ENXIO); } if (hdspe_init(sc) != 0) return (ENXIO); for (i = 0; i < HDSPE_MAX_CHANS && chan_map[i].descr != NULL; i++) { scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); scp->hc = &chan_map[i]; scp->sc = sc; scp->dev = device_add_child(dev, "pcm", -1); device_set_ivars(scp->dev, scp); } hdspe_map_dmabuf(sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sync_status", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_sync_status, "A", "List clock source signal lock and sync status"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_source", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_source, "A", "Currently effective clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_preference", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_preference, "A", "Set 'internal' (master) or preferred autosync clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_list", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_list, "A", "List of supported clock sources"); return (bus_generic_attach(dev)); } static void hdspe_dmafree(struct sc_info *sc) { 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); sc->rbuf = sc->pbuf = NULL; } static int hdspe_detach(device_t dev) { struct sc_info *sc; int err; sc = device_get_softc(dev); if (sc == NULL) { device_printf(dev,"Can't detach: softc is null.\n"); return (0); } err = device_delete_children(dev); if (err) return (err); hdspe_dmafree(sc); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->cs) bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->cs); if (sc->lock) snd_mtxfree(sc->lock); return (0); } static device_method_t hdspe_methods[] = { DEVMETHOD(device_probe, hdspe_probe), DEVMETHOD(device_attach, hdspe_attach), DEVMETHOD(device_detach, hdspe_detach), { 0, 0 } }; static driver_t hdspe_driver = { "hdspe", hdspe_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdspe, pci, hdspe_driver, 0, 0); diff --git a/sys/dev/sound/pci/solo.c b/sys/dev/sound/pci/solo.c index bee79e723696..82eabf3a4884 100644 --- a/sys/dev/sound/pci/solo.c +++ b/sys/dev/sound/pci/solo.c @@ -1,1076 +1,1075 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include -#include #include "mixer_if.h" #define SOLO_DEFAULT_BUFSZ 16384 #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ #define ESS18XX_DUPLEX 1 /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED static u_int32_t ess_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_playfmt, 0}; /* * Recording output is byte-swapped */ static u_int32_t ess_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), SND_FORMAT(AFMT_U16_BE, 1, 0), SND_FORMAT(AFMT_U16_BE, 2, 0), 0 }; static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_recfmt, 0}; struct ess_info; struct ess_chinfo { struct ess_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, hwch, stopping; u_int32_t fmt, spd, blksz; }; struct ess_info { struct resource *io, *sb, *vc, *mpu, *gp; /* I/O address for the board */ struct resource *irq; void *ih; bus_dma_tag_t parent_dmat; int simplex_dir, type, dmasz[2]; unsigned int duplex:1, newspeed:1; unsigned int bufsz; struct ess_chinfo pch, rch; struct mtx *lock; }; #define ess_lock(_ess) snd_mtxlock((_ess)->lock) #define ess_unlock(_ess) snd_mtxunlock((_ess)->lock) #define ess_lock_assert(_ess) snd_mtxassert((_ess)->lock) static int ess_rd(struct ess_info *sc, int reg); static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); static int ess_dspready(struct ess_info *sc); static int ess_cmd(struct ess_info *sc, u_char val); static int ess_cmd1(struct ess_info *sc, u_char cmd, int val); static int ess_get_byte(struct ess_info *sc); static void ess_setmixer(struct ess_info *sc, u_int port, u_int value); static int ess_getmixer(struct ess_info *sc, u_int port); static int ess_reset_dsp(struct ess_info *sc); static int ess_write(struct ess_info *sc, u_char reg, int val); static int ess_read(struct ess_info *sc, u_char reg); static void ess_intr(void *arg); static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len); static int ess_start(struct ess_chinfo *ch); static int ess_stop(struct ess_chinfo *ch); static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir); static int ess_dmapos(struct ess_info *sc, int ch); static int ess_dmatrigger(struct ess_info *sc, int ch, int go); /* * Common code for the midi and pcm functions * * ess_cmd write a single byte to the CMD port. * ess_cmd1 write a CMD + 1 byte arg * ess_cmd2 write a CMD + 2 byte arg * ess_get_byte returns a single byte from the DSP data port * * ess_write is actually ess_cmd1 * ess_read access ext. regs via ess_cmd(0xc0, reg) followed by ess_get_byte */ static int port_rd(struct resource *port, int regno, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void port_wr(struct resource *port, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static int ess_rd(struct ess_info *sc, int reg) { return port_rd(sc->sb, reg, 1); } static void ess_wr(struct ess_info *sc, int reg, u_int8_t val) { port_wr(sc->sb, reg, val, 1); } static int ess_dspready(struct ess_info *sc) { return ((ess_rd(sc, SBDSP_STATUS) & 0x80) == 0); } static int ess_dspwr(struct ess_info *sc, u_char val) { int i; for (i = 0; i < 1000; i++) { if (ess_dspready(sc)) { ess_wr(sc, SBDSP_CMD, val); return 1; } if (i > 10) DELAY((i > 100)? 1000 : 10); } printf("ess_dspwr(0x%02x) timed out.\n", val); return 0; } static int ess_cmd(struct ess_info *sc, u_char val) { DEB(printf("ess_cmd: %x\n", val)); return ess_dspwr(sc, val); } static int ess_cmd1(struct ess_info *sc, u_char cmd, int val) { DEB(printf("ess_cmd1: %x, %x\n", cmd, val)); if (ess_dspwr(sc, cmd)) { return ess_dspwr(sc, val & 0xff); } else return 0; } static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); return val; } static int ess_get_byte(struct ess_info *sc) { int i; for (i = 1000; i > 0; i--) { if (ess_rd(sc, 0xc) & 0x40) return ess_rd(sc, DSP_READ); else DELAY(20); } return -1; } static int ess_write(struct ess_info *sc, u_char reg, int val) { return ess_cmd1(sc, reg, val); } static int ess_read(struct ess_info *sc, u_char reg) { return (ess_cmd(sc, 0xc0) && ess_cmd(sc, reg))? ess_get_byte(sc) : -1; } static int ess_reset_dsp(struct ess_info *sc) { DEB(printf("ess_reset_dsp\n")); ess_wr(sc, SBDSP_RST, 3); DELAY(100); ess_wr(sc, SBDSP_RST, 0); if (ess_get_byte(sc) != 0xAA) { DEB(printf("ess_reset_dsp failed\n")); /* rman_get_start(d->io_base))); */ return ENXIO; /* Sorry */ } ess_cmd(sc, 0xc6); return 0; } static void ess_intr(void *arg) { struct ess_info *sc = (struct ess_info *)arg; int src, pirq = 0, rirq = 0; ess_lock(sc); src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; if (src == 0) { ess_unlock(sc); return; } if (sc->duplex) { pirq = (src & sc->pch.hwch)? 1 : 0; rirq = (src & sc->rch.hwch)? 1 : 0; } else { if (sc->simplex_dir == PCMDIR_PLAY) pirq = 1; if (sc->simplex_dir == PCMDIR_REC) rirq = 1; if (!pirq && !rirq) printf("solo: IRQ neither playback nor rec!\n"); } DEB(printf("ess_intr: pirq:%d rirq:%d\n",pirq,rirq)); if (pirq) { if (sc->pch.stopping) { ess_dmatrigger(sc, sc->pch.hwch, 0); sc->pch.stopping = 0; if (sc->pch.hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } ess_unlock(sc); chn_intr(sc->pch.channel); ess_lock(sc); } if (rirq) { if (sc->rch.stopping) { ess_dmatrigger(sc, sc->rch.hwch, 0); sc->rch.stopping = 0; /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } ess_unlock(sc); chn_intr(sc->rch.channel); ess_lock(sc); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); ess_unlock(sc); } /* utility functions for ESS */ static u_int8_t ess_calcspeed8(int *spd) { int speed = *spd; u_int32_t t; if (speed > 22000) { t = (795500 + speed / 2) / speed; speed = (795500 + t / 2) / t; t = (256 - t) | 0x80; } else { t = (397700 + speed / 2) / speed; speed = (397700 + t / 2) / t; t = 128 - t; } *spd = speed; return t & 0x000000ff; } static u_int8_t ess_calcspeed9(int *spd) { int speed, s0, s1, use0; u_int8_t t0, t1; /* rate = source / (256 - divisor) */ /* divisor = 256 - (source / rate) */ speed = *spd; t0 = 128 - (793800 / speed); s0 = 793800 / (128 - t0); t1 = 128 - (768000 / speed); s1 = 768000 / (128 - t1); t1 |= 0x80; use0 = (ABS(speed - s0) < ABS(speed - s1))? 1 : 0; *spd = use0? s0 : s1; return use0? t0 : t1; } static u_int8_t ess_calcfilter(int spd) { int cutoff; /* cutoff = 7160000 / (256 - divisor) */ /* divisor = 256 - (7160000 / cutoff) */ cutoff = (spd * 9 * 82) / 20; return (256 - (7160000 / cutoff)); } static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len) { int play = (dir == PCMDIR_PLAY)? 1 : 0; int b16 = (fmt & AFMT_16BIT)? 1 : 0; int stereo = (AFMT_CHANNEL(fmt) > 1)? 1 : 0; int unsign = (!(fmt & AFMT_SIGNED))? 1 : 0; u_int8_t spdval, fmtval; DEB(printf("ess_setupch\n")); spdval = (sc->newspeed)? ess_calcspeed9(&spd) : ess_calcspeed8(&spd); sc->simplex_dir = play ? PCMDIR_PLAY : PCMDIR_REC ; if (ch == 1) { KASSERT((dir == PCMDIR_PLAY) || (dir == PCMDIR_REC), ("ess_setupch: dir1 bad")); len = -len; /* transfer length low */ ess_write(sc, 0xa4, len & 0x00ff); /* transfer length high */ ess_write(sc, 0xa5, (len & 0xff00) >> 8); /* autoinit, dma dir */ ess_write(sc, 0xb8, 0x04 | (play? 0x00 : 0x0a)); /* mono/stereo */ ess_write(sc, 0xa8, (ess_read(sc, 0xa8) & ~0x03) | (stereo? 0x01 : 0x02)); /* demand mode, 4 bytes/xfer */ ess_write(sc, 0xb9, 0x02); /* sample rate */ ess_write(sc, 0xa1, spdval); /* filter cutoff */ ess_write(sc, 0xa2, ess_calcfilter(spd)); /* setup dac/adc */ /* if (play) ess_write(sc, 0xb6, unsign? 0x80 : 0x00); */ /* mono, b16: signed, load signal */ /* ess_write(sc, 0xb7, 0x51 | (unsign? 0x00 : 0x20)); */ /* setup fifo */ ess_write(sc, 0xb7, 0x91 | (unsign? 0x00 : 0x20) | (b16? 0x04 : 0x00) | (stereo? 0x08 : 0x40)); /* irq control */ ess_write(sc, 0xb1, (ess_read(sc, 0xb1) & 0x0f) | 0x50); /* drq control */ ess_write(sc, 0xb2, (ess_read(sc, 0xb2) & 0x0f) | 0x50); } else if (ch == 2) { KASSERT(dir == PCMDIR_PLAY, ("ess_setupch: dir2 bad")); len >>= 1; len = -len; /* transfer length low */ ess_setmixer(sc, 0x74, len & 0x00ff); /* transfer length high */ ess_setmixer(sc, 0x76, (len & 0xff00) >> 8); /* autoinit, 4 bytes/req */ ess_setmixer(sc, 0x78, 0x10); fmtval = b16 | (stereo << 1) | ((!unsign) << 2); /* enable irq, set format */ ess_setmixer(sc, 0x7a, 0x40 | fmtval); if (sc->newspeed) { /* sample rate */ ess_setmixer(sc, 0x70, spdval); /* filter cutoff */ ess_setmixer(sc, 0x72, ess_calcfilter(spd)); } } return 0; } static int ess_start(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_start\n");); ess_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->blksz); ch->stopping = 0; if (ch->hwch == 1) { ess_write(sc, 0xb8, ess_read(sc, 0xb8) | 0x01); if (ch->dir == PCMDIR_PLAY) { #if 0 DELAY(100000); /* 100 ms */ #endif ess_cmd(sc, 0xd1); } } else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) | 0x03); return 0; } static int ess_stop(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_stop\n")); ch->stopping = 1; if (ch->hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x04); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x10); DEB(printf("done with stop\n")); return 0; } /* -------------------------------------------------------------------- */ /* channel interface for ESS18xx */ static void * esschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct ess_info *sc = devinfo; struct ess_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; DEB(printf("esschan_init\n")); ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; return ch; } static int esschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t esschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; ch->spd = speed; if (sc->newspeed) ess_calcspeed9(&ch->spd); else ess_calcspeed8(&ch->spd); return ch->spd; } static u_int32_t esschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct ess_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int esschan_trigger(kobj_t obj, void *data, int go) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; DEB(printf("esschan_trigger: %d\n",go)); ess_lock(sc); switch (go) { case PCMTRIG_START: ess_dmasetup(sc, ch->hwch, sndbuf_getbufaddr(ch->buffer), sndbuf_getsize(ch->buffer), ch->dir); ess_dmatrigger(sc, ch->hwch, 1); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } ess_unlock(sc); return 0; } static u_int32_t esschan_getptr(kobj_t obj, void *data) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; u_int32_t ret; ess_lock(sc); ret = ess_dmapos(sc, ch->hwch); ess_unlock(sc); return ret; } static struct pcmchan_caps * esschan_getcaps(kobj_t obj, void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } static kobj_method_t esschan_methods[] = { KOBJMETHOD(channel_init, esschan_init), KOBJMETHOD(channel_setformat, esschan_setformat), KOBJMETHOD(channel_setspeed, esschan_setspeed), KOBJMETHOD(channel_setblocksize, esschan_setblocksize), KOBJMETHOD(channel_trigger, esschan_trigger), KOBJMETHOD(channel_getptr, esschan_getptr), KOBJMETHOD(channel_getcaps, esschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(esschan); /************************************************************/ static int essmix_init(struct snd_mixer *m) { struct ess_info *sc = mix_getdevinfo(m); mix_setrecdevs(m, SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_IMIX); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME | SOUND_MASK_LINE1); ess_setmixer(sc, 0, 0); /* reset */ return 0; } static int essmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ess_info *sc = mix_getdevinfo(m); int preg = 0, rreg = 0, l, r; l = (left * 15) / 100; r = (right * 15) / 100; switch (dev) { case SOUND_MIXER_SYNTH: preg = 0x36; rreg = 0x6b; break; case SOUND_MIXER_PCM: preg = 0x14; rreg = 0x7c; break; case SOUND_MIXER_LINE: preg = 0x3e; rreg = 0x6e; break; case SOUND_MIXER_MIC: preg = 0x1a; rreg = 0x68; break; case SOUND_MIXER_LINE1: preg = 0x3a; rreg = 0x6c; break; case SOUND_MIXER_CD: preg = 0x38; rreg = 0x6a; break; case SOUND_MIXER_VOLUME: l = left? (left * 63) / 100 : 64; r = right? (right * 63) / 100 : 64; ess_setmixer(sc, 0x60, l); ess_setmixer(sc, 0x62, r); left = (l == 64)? 0 : (l * 100) / 63; right = (r == 64)? 0 : (r * 100) / 63; return left | (right << 8); } if (preg) ess_setmixer(sc, preg, (l << 4) | r); if (rreg) ess_setmixer(sc, rreg, (l << 4) | r); left = (l * 100) / 15; right = (r * 100) / 15; return left | (right << 8); } static u_int32_t essmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct ess_info *sc = mix_getdevinfo(m); u_char recdev; switch (src) { case SOUND_MASK_CD: recdev = 0x02; break; case SOUND_MASK_LINE: recdev = 0x06; break; case SOUND_MASK_IMIX: recdev = 0x05; break; case SOUND_MASK_MIC: default: recdev = 0x00; src = SOUND_MASK_MIC; break; } ess_setmixer(sc, 0x1c, recdev); return src; } static kobj_method_t solomixer_methods[] = { KOBJMETHOD(mixer_init, essmix_init), KOBJMETHOD(mixer_set, essmix_set), KOBJMETHOD(mixer_setrecsrc, essmix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(solomixer); /************************************************************/ static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir) { KASSERT(ch == 1 || ch == 2, ("bad ch")); sc->dmasz[ch - 1] = cnt; if (ch == 1) { port_wr(sc->vc, 0x8, 0xc4, 1); /* command */ port_wr(sc->vc, 0xd, 0xff, 1); /* reset */ port_wr(sc->vc, 0xf, 0x01, 1); /* mask */ port_wr(sc->vc, 0xb, dir == PCMDIR_PLAY? 0x58 : 0x54, 1); /* mode */ port_wr(sc->vc, 0x0, base, 4); port_wr(sc->vc, 0x4, cnt - 1, 2); } else if (ch == 2) { port_wr(sc->io, 0x6, 0x08, 1); /* autoinit */ port_wr(sc->io, 0x0, base, 4); port_wr(sc->io, 0x4, cnt, 2); } return 0; } static int ess_dmapos(struct ess_info *sc, int ch) { int p = 0, i = 0, j = 0; KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) { /* * During recording, this register is known to give back * garbage if it's not quiescent while being read. That's * why we spl, stop the DMA, and try over and over until * adjacent reads are "close", in the right order and not * bigger than is otherwise possible. */ ess_dmatrigger(sc, ch, 0); DELAY(20); do { DELAY(10); if (j > 1) printf("DMA count reg bogus: %04x & %04x\n", i, p); i = port_rd(sc->vc, 0x4, 2) + 1; p = port_rd(sc->vc, 0x4, 2) + 1; } while ((p > sc->dmasz[ch - 1] || i < p || (p - i) > 0x8) && j++ < 1000); ess_dmatrigger(sc, ch, 1); } else if (ch == 2) p = port_rd(sc->io, 0x4, 2); return sc->dmasz[ch - 1] - p; } static int ess_dmatrigger(struct ess_info *sc, int ch, int go) { KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) port_wr(sc->vc, 0xf, go? 0x00 : 0x01, 1); /* mask */ else if (ch == 2) port_wr(sc->io, 0x6, 0x08 | (go? 0x02 : 0x00), 1); /* autoinit */ return 0; } static void ess_release_resources(struct ess_info *sc, device_t dev) { if (sc->irq) { if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = NULL; } if (sc->io) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->io); sc->io = NULL; } if (sc->sb) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(1), sc->sb); sc->sb = NULL; } if (sc->vc) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(2), sc->vc); sc->vc = NULL; } if (sc->mpu) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(3), sc->mpu); sc->mpu = NULL; } if (sc->gp) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(4), sc->gp); sc->gp = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } free(sc, M_DEVBUF); } static int ess_alloc_resources(struct ess_info *sc, device_t dev) { int rid; rid = PCIR_BAR(0); sc->io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(1); sc->sb = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(2); sc->vc = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(3); sc->mpu = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(4); sc->gp = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_solo softc"); return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; } static int ess_probe(device_t dev) { char *s = NULL; u_int32_t subdev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case 0x1969125d: if (subdev == 0x8888125d) s = "ESS Solo-1E"; else if (subdev == 0x1818125d) s = "ESS Solo-1"; else s = "ESS Solo-1 (unknown vendor)"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } #define ESS_PCI_LEGACYCONTROL 0x40 #define ESS_PCI_CONFIG 0x50 #define ESS_PCI_DDMACONTROL 0x60 static int ess_suspend(device_t dev) { return 0; } static int ess_resume(device_t dev) { uint16_t ddma; struct ess_info *sc = pcm_getdevinfo(dev); ess_lock(sc); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); if (ess_reset_dsp(sc)) { ess_unlock(sc); goto no; } ess_unlock(sc); if (mixer_reinit(dev)) goto no; ess_lock(sc); if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ ess_unlock(sc); return 0; no: return EIO; } static int ess_attach(device_t dev) { struct ess_info *sc; char status[SND_STATUSLEN]; u_int16_t ddma; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); pci_enable_busmaster(dev); if (ess_alloc_resources(sc, dev)) goto no; sc->bufsz = pcm_getbuffersize(dev, 4096, SOLO_DEFAULT_BUFSZ, 65536); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ #ifdef ESS18XX_DUPLEX sc->duplex = 1; #else sc->duplex = 0; #endif #ifdef ESS18XX_NEWSPEED sc->newspeed = 1; #else sc->newspeed = 0; #endif if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ess_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto no; } if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); #if 0 if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/65536, /*boundary*/0, #endif if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (ess_reset_dsp(sc)) goto no; if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); if (mixer_init(dev, &solomixer_class, sc)) goto no; snprintf(status, SND_STATUSLEN, "port 0x%jx,0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, sc, 1, 1)) goto no; pcm_addchan(dev, PCMDIR_REC, &esschan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &esschan_class, sc); pcm_setstatus(dev, status); return 0; no: ess_release_resources(sc, dev); return ENXIO; } static int ess_detach(device_t dev) { int r; struct ess_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); ess_release_resources(sc, dev); return 0; } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), DEVMETHOD(device_detach, ess_detach), DEVMETHOD(device_resume, ess_resume), DEVMETHOD(device_suspend, ess_suspend), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_solo, pci, ess_driver, 0, 0); MODULE_DEPEND(snd_solo, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_solo, 1); diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 6c9f4c8f18b2..d2ec480b45b0 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,565 +1,581 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #include #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 /* * By design, limit possible channels for each direction. */ #define SND_MAXHWCHAN 256 #define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_DYING 0x00000008 #define SD_F_DETACHING 0x00000010 #define SD_F_BUSY 0x00000020 #define SD_F_MPSAFE 0x00000040 #define SD_F_REGISTERED 0x00000080 #define SD_F_BITPERFECT 0x00000100 #define SD_F_VPC 0x00000200 /* volume-per-channel */ #define SD_F_EQ 0x00000400 /* EQ */ #define SD_F_EQ_ENABLED 0x00000800 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00001000 /* EQ bypassed */ #define SD_F_EQ_PC 0x00002000 /* EQ per-channel */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ "\002AUTOVCHAN" \ "\003SOFTPCMVOL" \ "\004DYING" \ "\005DETACHING" \ "\006BUSY" \ "\007MPSAFE" \ "\010REGISTERED" \ "\011BITPERFECT" \ "\012VPC" \ "\013EQ" \ "\014EQ_ENABLED" \ "\015EQ_BYPASSED" \ "\016EQ_PC" \ "\035PRIO_RD" \ "\036PRIO_WR" \ "\037DIR_SET" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL && \ !((x)->flags & SD_F_DYING)) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && \ ((x)->flags & SD_F_REGISTERED)) #define PCM_DETACHING(x) ((x)->flags & SD_F_DETACHING) #define PCM_CHANCOUNT(d) \ (d->playcount + d->pvchancount + d->reccount + d->rvchancount) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff #define AFMT_CHANNEL_MASK 0x07f00000 #define AFMT_CHANNEL_SHIFT 20 #define AFMT_CHANNEL_MAX 0x7f #define AFMT_EXTCHANNEL_MASK 0x08000000 #define AFMT_EXTCHANNEL_SHIFT 27 #define AFMT_EXTCHANNEL_MAX 1 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 #define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) /* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this * reason the /dev/dsp is reserved for digitized audio use. There is a * device for true DSP processors but it will be called something else. * In v3.0 it's /dev/sndproc but this could be a temporary solution. */ #define SND_DEV_CTL 0 /* Control port /dev/mixer */ #define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ #define SND_DEV_MIDIN 2 /* Raw midi access */ #define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ #define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ #define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ #define SND_DEV_STATUS 6 /* /dev/sndstat */ /* #7 not in use now. */ #define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 #define SND_DEV_DSPHW_PLAY 11 /* specific playback channel */ #define SND_DEV_DSPHW_VPLAY 12 /* specific virtual playback channel */ #define SND_DEV_DSPHW_REC 13 /* specific record channel */ #define SND_DEV_DSPHW_VREC 14 /* specific virtual record channel */ #define SND_DEV_DSPHW_CD 15 /* s16le/stereo 44100Hz CD */ /* * OSSv4 compatible device. For now, it serve no purpose and * the cloning itself will forward the request to ordinary /dev/dsp * instead. */ #define SND_DEV_DSP_MMAP 16 /* /dev/dsp_mmap */ #define SND_DEV_DSP_AC3 17 /* /dev/dsp_ac3 */ #define SND_DEV_DSP_MULTICH 18 /* /dev/dsp_multich */ #define SND_DEV_DSP_SPDIFOUT 19 /* /dev/dsp_spdifout */ #define SND_DEV_DSP_SPDIFIN 20 /* /dev/dsp_spdifin */ #define 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); void 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) int sndstat_register(device_t dev, char *str); int sndstat_unregister(device_t dev); +/* These are the function codes assigned to the children of sound cards. */ +enum { + SCF_PCM, + SCF_MIDI, + SCF_SYNTH, +}; + +/* + * This is the device information struct, used by a bridge device to pass the + * device function code to the children. + */ +struct sndcard_func { + int func; /* The function code. */ + void *varinfo; /* Bridge-specific information. */ +}; + /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; } pcm; } channels; unsigned playcount, reccount, pvchancount, rvchancount; unsigned flags; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; struct cdev *dsp_dev; uint32_t pvchanrate, pvchanformat; uint32_t rvchanrate, rvchanformat; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_MODE_MIXER 0x01 #define PCM_MODE_PLAY 0x02 #define PCM_MODE_REC 0x04 #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) { \ if ((x)->cv.cv_waiters > 1 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM RELEASE] " \ "cv_waiters=%d > 1!\n", \ __func__, __LINE__, \ (x)->cv.cv_waiters); \ cv_broadcast(&(x)->cv); \ } \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* !SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) #endif /* _KERNEL */ #endif /* _OS_H_ */ diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c index 2351c2522021..d47eb86ed271 100644 --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -1,6306 +1,6305 @@ /* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf * http://www.usb.org/developers/devclass_docs/frmts10.pdf * http://www.usb.org/developers/devclass_docs/termt10.pdf */ /* * Also merged: * $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $ * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #define USB_DEBUG_VAR uaudio_debug #include #include #include /* for bootverbose */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include -#include #include "feeder_if.h" static int uaudio_default_rate = 0; /* use rate list */ static int uaudio_default_bits = 0; /* use default sample size */ static int uaudio_default_channels = 0; /* use default */ static int uaudio_buffer_ms = 4; static bool uaudio_handle_hid = true; static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB uaudio"); SYSCTL_BOOL(_hw_usb_uaudio, OID_AUTO, handle_hid, CTLFLAG_RWTUN, &uaudio_handle_hid, 0, "uaudio handles any HID volume/mute keys, if set"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RWTUN, &uaudio_default_rate, 0, "uaudio default sample rate"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RWTUN, &uaudio_default_bits, 0, "uaudio default sample bits"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RWTUN, &uaudio_default_channels, 0, "uaudio default sample channels"); #define UAUDIO_BUFFER_MS_MIN 1 #define UAUDIO_BUFFER_MS_MAX 8 static int uaudio_buffer_ms_sysctl(SYSCTL_HANDLER_ARGS) { int err, val; val = uaudio_buffer_ms; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == uaudio_buffer_ms) return (err); if (val > UAUDIO_BUFFER_MS_MAX) val = UAUDIO_BUFFER_MS_MAX; else if (val < UAUDIO_BUFFER_MS_MIN) val = UAUDIO_BUFFER_MS_MIN; uaudio_buffer_ms = val; return (0); } SYSCTL_PROC(_hw_usb_uaudio, OID_AUTO, buffer_ms, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), uaudio_buffer_ms_sysctl, "I", "uaudio buffering delay in milliseconds, from 1 to 8"); #ifdef USB_DEBUG static int uaudio_debug; SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RWTUN, &uaudio_debug, 0, "uaudio debug level"); #else #define uaudio_debug 0 #endif #define UAUDIO_NFRAMES 64 /* must be factor of 8 due HS-USB */ #define UAUDIO_NCHANBUFS 2 /* number of outstanding request */ #define UAUDIO_RECURSE_LIMIT 255 /* rounds */ #define UAUDIO_BITS_MAX 32 /* maximum sample size in bits */ #define UAUDIO_CHANNELS_MAX MIN(64, AFMT_CHANNEL_MAX) #define UAUDIO_MATRIX_MAX 8 /* channels */ #define MAKE_WORD(h,l) (((h) << 8) | (l)) #define BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1) #define UAUDIO_MAX_CHAN(x) (x) #define MIX(sc) ((sc)->sc_mixer_node) union uaudio_asid { const struct usb_audio_streaming_interface_descriptor *v1; const struct usb_audio20_streaming_interface_descriptor *v2; }; union uaudio_asf1d { const struct usb_audio_streaming_type1_descriptor *v1; const struct usb_audio20_streaming_type1_descriptor *v2; }; union uaudio_sed { const struct usb_audio_streaming_endpoint_descriptor *v1; const struct usb_audio20_streaming_endpoint_descriptor *v2; }; struct uaudio_mixer_node { const char *name; int32_t minval; int32_t maxval; #define MIX_MAX_CHAN 16 int32_t wValue[MIX_MAX_CHAN]; /* using nchan */ uint32_t mul; uint32_t ctl; int wData[MIX_MAX_CHAN]; /* using nchan */ uint16_t wIndex; uint8_t update[(MIX_MAX_CHAN + 7) / 8]; uint8_t nchan; uint8_t type; #define MIX_ON_OFF 1 #define MIX_SIGNED_16 2 #define MIX_UNSIGNED_16 3 #define MIX_SIGNED_8 4 #define MIX_SELECTOR 5 #define MIX_UNKNOWN 6 #define MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \ ((n) == MIX_UNSIGNED_16)) ? 2 : 1) #define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) #define MAX_SELECTOR_INPUT_PIN 256 uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; uint8_t val_default; uint8_t desc[64]; struct uaudio_mixer_node *next; }; struct uaudio_configure_msg { struct usb_proc_msg hdr; struct uaudio_softc *sc; }; #define CHAN_MAX_ALT 24 struct uaudio_chan_alt { union uaudio_asf1d p_asf1d; union uaudio_sed p_sed; const usb_endpoint_descriptor_audio_t *p_ed1; const struct uaudio_format *p_fmt; const struct usb_config *usb_cfg; uint32_t sample_rate; /* in Hz */ uint16_t sample_size; uint8_t iface_index; uint8_t iface_alt_index; uint8_t channels; }; struct uaudio_chan { struct pcmchan_caps pcm_cap; /* capabilities */ struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT]; struct snd_dbuf *pcm_buf; struct mtx *pcm_mtx; /* lock protecting this structure */ struct uaudio_softc *priv_sc; struct pcm_channel *pcm_ch; struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1]; uint8_t *buf; /* pointer to buffer */ uint8_t *start; /* upper layer buffer start */ uint8_t *end; /* upper layer buffer end */ uint8_t *cur; /* current position in upper layer * buffer */ uint32_t intr_frames; /* in units */ uint32_t frames_per_second; uint32_t sample_rem; uint32_t sample_curr; uint32_t max_buf; int32_t jitter_rem; int32_t jitter_curr; int feedback_rate; uint32_t pcm_format[2]; uint16_t bytes_per_frame[2]; uint32_t intr_counter; uint32_t running; uint32_t num_alt; uint32_t cur_alt; uint32_t set_alt; uint32_t operation; #define CHAN_OP_NONE 0 #define CHAN_OP_START 1 #define CHAN_OP_STOP 2 #define CHAN_OP_DRAIN 3 uint8_t iface_index; }; #define UMIDI_EMB_JACK_MAX 16 /* units */ #define UMIDI_TX_FRAMES 256 /* units */ #define UMIDI_TX_BUFFER (UMIDI_TX_FRAMES * 4) /* bytes */ enum { UMIDI_TX_TRANSFER, UMIDI_RX_TRANSFER, UMIDI_N_TRANSFER, }; struct umidi_sub_chan { struct usb_fifo_sc fifo; uint8_t *temp_cmd; uint8_t temp_0[4]; uint8_t temp_1[4]; uint8_t state; #define UMIDI_ST_UNKNOWN 0 /* scan for command */ #define UMIDI_ST_1PARAM 1 #define UMIDI_ST_2PARAM_1 2 #define UMIDI_ST_2PARAM_2 3 #define UMIDI_ST_SYSEX_0 4 #define UMIDI_ST_SYSEX_1 5 #define UMIDI_ST_SYSEX_2 6 uint8_t read_open:1; uint8_t write_open:1; uint8_t unused:6; }; struct umidi_chan { struct umidi_sub_chan sub[UMIDI_EMB_JACK_MAX]; struct mtx mtx; struct usb_xfer *xfer[UMIDI_N_TRANSFER]; uint8_t iface_index; uint8_t iface_alt_index; uint8_t read_open_refcount; uint8_t write_open_refcount; uint8_t curr_cable; uint8_t max_emb_jack; uint8_t valid; uint8_t single_command; }; struct uaudio_search_result { uint8_t bit_input[(256 + 7) / 8]; uint8_t bit_output[(256 + 7) / 8]; uint8_t recurse_level; uint8_t id_max; uint8_t is_input; }; enum { UAUDIO_HID_RX_TRANSFER, UAUDIO_HID_N_TRANSFER, }; struct uaudio_hid { struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER]; struct hid_location volume_up_loc; struct hid_location volume_down_loc; struct hid_location mute_loc; uint32_t flags; #define UAUDIO_HID_VALID 0x0001 #define UAUDIO_HID_HAS_ID 0x0002 #define UAUDIO_HID_HAS_VOLUME_UP 0x0004 #define UAUDIO_HID_HAS_VOLUME_DOWN 0x0008 #define UAUDIO_HID_HAS_MUTE 0x0010 uint8_t iface_index; uint8_t volume_up_id; uint8_t volume_down_id; uint8_t mute_id; }; #define UAUDIO_SPDIF_OUT 0x01 /* Enable S/PDIF output */ #define UAUDIO_SPDIF_OUT_48K 0x02 /* Out sample rate = 48K */ #define UAUDIO_SPDIF_OUT_96K 0x04 /* Out sample rate = 96K */ #define UAUDIO_SPDIF_IN_MIX 0x10 /* Input mix enable */ #define UAUDIO_MAX_CHILD 2 struct uaudio_softc_child { device_t pcm_device; struct mtx *mixer_lock; struct snd_mixer *mixer_dev; uint32_t mix_info; uint32_t recsrc_info; uint8_t pcm_registered:1; uint8_t mixer_init:1; }; struct uaudio_softc { struct sbuf sc_sndstat; struct sndcard_func sc_sndcard_func; struct uaudio_chan sc_rec_chan[UAUDIO_MAX_CHILD]; struct uaudio_chan sc_play_chan[UAUDIO_MAX_CHILD]; struct umidi_chan sc_midi_chan; struct uaudio_hid sc_hid; struct uaudio_search_result sc_mixer_clocks; struct uaudio_mixer_node sc_mixer_node; struct uaudio_configure_msg sc_config_msg[2]; struct uaudio_softc_child sc_child[UAUDIO_MAX_CHILD]; struct usb_device *sc_udev; struct usb_xfer *sc_mixer_xfer[1]; struct uaudio_mixer_node *sc_mixer_root; struct uaudio_mixer_node *sc_mixer_curr; int (*sc_set_spdif_fn) (struct uaudio_softc *, int); uint16_t sc_audio_rev; uint16_t sc_mixer_count; uint8_t sc_mixer_iface_index; uint8_t sc_mixer_iface_no; uint8_t sc_mixer_chan; uint8_t sc_sndstat_valid:1; uint8_t sc_uq_audio_swap_lr:1; uint8_t sc_uq_au_inp_async:1; uint8_t sc_uq_au_no_xu:1; uint8_t sc_uq_bad_adc:1; uint8_t sc_uq_au_vendor_class:1; uint8_t sc_pcm_bitperfect:1; }; struct uaudio_terminal_node { union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it_v1; const struct usb_audio_output_terminal *ot_v1; const struct usb_audio_mixer_unit_0 *mu_v1; const struct usb_audio_selector_unit *su_v1; const struct usb_audio_feature_unit *fu_v1; const struct usb_audio_processing_unit_0 *pu_v1; const struct usb_audio_extension_unit_0 *eu_v1; const struct usb_audio20_clock_source_unit *csrc_v2; const struct usb_audio20_clock_selector_unit_0 *csel_v2; const struct usb_audio20_clock_multiplier_unit *cmul_v2; const struct usb_audio20_input_terminal *it_v2; const struct usb_audio20_output_terminal *ot_v2; const struct usb_audio20_mixer_unit_0 *mu_v2; const struct usb_audio20_selector_unit *su_v2; const struct usb_audio20_feature_unit *fu_v2; const struct usb_audio20_sample_rate_unit *ru_v2; const struct usb_audio20_processing_unit_0 *pu_v2; const struct usb_audio20_extension_unit_0 *eu_v2; const struct usb_audio20_effect_unit *ef_v2; } u; struct uaudio_search_result usr; struct uaudio_terminal_node *root; }; struct uaudio_format { uint16_t wFormat; uint8_t bPrecision; uint32_t freebsd_fmt; const char *description; }; static const struct uaudio_format uaudio10_formats[] = { {UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; static const struct uaudio_format uaudio20_formats[] = { {UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; /* prototypes */ static device_probe_t uaudio_probe; static device_attach_t uaudio_attach; static device_detach_t uaudio_detach; static usb_callback_t uaudio_chan_play_callback; static usb_callback_t uaudio_chan_play_sync_callback; static usb_callback_t uaudio_chan_record_callback; static usb_callback_t uaudio_chan_record_sync_callback; static usb_callback_t uaudio_mixer_write_cfg_callback; static usb_callback_t umidi_bulk_read_callback; static usb_callback_t umidi_bulk_write_callback; static usb_callback_t uaudio_hid_rx_callback; static usb_proc_callback_t uaudio_configure_msg; /* ==== USB mixer ==== */ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS); static void uaudio_mixer_ctl_free(struct uaudio_softc *); static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t, unsigned); static void uaudio_mixer_reload_all(struct uaudio_softc *); static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *); /* ==== USB audio v1.0 ==== */ static void uaudio_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static uint32_t uaudio_mixer_feature_get_bmaControls( const struct usb_audio_feature_unit *, uint8_t); static void uaudio_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing_updown(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_extension(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *); static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t); static int uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t, struct uaudio_mixer_node *); /* ==== USB audio v2.0 ==== */ static void uaudio20_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *); static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio20_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t, uint8_t, uint32_t); /* USB audio v1.0 and v2.0 */ static void uaudio_chan_fill_info_sub(struct uaudio_softc *, struct usb_device *, uint32_t, uint8_t, uint8_t); static void uaudio_chan_fill_info(struct uaudio_softc *, struct usb_device *); static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_fill_info(struct uaudio_softc *, struct usb_device *, void *); static int uaudio_mixer_signext(uint8_t, int); static void uaudio_mixer_init(struct uaudio_softc *, unsigned); static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t); static struct umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *); static void umidi_start_read(struct usb_fifo *); static void umidi_stop_read(struct usb_fifo *); static void umidi_start_write(struct usb_fifo *); static void umidi_stop_write(struct usb_fifo *); static int umidi_open(struct usb_fifo *, int); static int umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int); static void umidi_close(struct usb_fifo *, int); static void umidi_init(device_t dev); static int umidi_probe(device_t dev); static int umidi_detach(device_t dev); static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa); static void uaudio_hid_detach(struct uaudio_softc *sc); #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc( const usb_endpoint_descriptor_audio_t *); #endif static const struct usb_config uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_sync_callback, }, }; static const struct usb_config uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_sync_callback, }, }; static const struct usb_config uaudio_mixer_config[1] = { [0] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + 4), .callback = &uaudio_mixer_write_cfg_callback, .timeout = 1000, /* 1 second */ }, }; static const uint8_t umidi_cmd_to_len[16] = { [0x0] = 0, /* reserved */ [0x1] = 0, /* reserved */ [0x2] = 2, /* bytes */ [0x3] = 3, /* bytes */ [0x4] = 3, /* bytes */ [0x5] = 1, /* bytes */ [0x6] = 2, /* bytes */ [0x7] = 3, /* bytes */ [0x8] = 3, /* bytes */ [0x9] = 3, /* bytes */ [0xA] = 3, /* bytes */ [0xB] = 3, /* bytes */ [0xC] = 2, /* bytes */ [0xD] = 2, /* bytes */ [0xE] = 3, /* bytes */ [0xF] = 1, /* bytes */ }; static const struct usb_config umidi_config[UMIDI_N_TRANSFER] = { [UMIDI_TX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMIDI_TX_BUFFER, .flags = {.no_pipe_ok = 1}, .callback = &umidi_bulk_write_callback, }, [UMIDI_RX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 4, /* bytes */ .flags = {.short_xfer_ok = 1,.proxy_buffer = 1,.no_pipe_ok = 1}, .callback = &umidi_bulk_read_callback, }, }; static const struct usb_config uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = { [UAUDIO_HID_RX_TRANSFER] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_hid_rx_callback, }, }; static device_method_t uaudio_methods[] = { DEVMETHOD(device_probe, uaudio_probe), DEVMETHOD(device_attach, uaudio_attach), DEVMETHOD(device_detach, uaudio_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t uaudio_driver = { .name = "uaudio", .methods = uaudio_methods, .size = sizeof(struct uaudio_softc), }; /* The following table is derived from Linux's quirks-table.h */ static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = { { USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */ }; static const STRUCT_USB_HOST_ID __used uaudio_devs[] = { /* Generic USB audio class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),}, /* Generic USB MIDI class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),}, }; static unsigned uaudio_get_child_index_by_dev(struct uaudio_softc *sc, device_t dev) { unsigned i; for (i = 0; i != UAUDIO_MAX_CHILD; i++) { if (dev == sc->sc_child[i].pcm_device) return (i); } panic("uaudio_get_child_index_dev: Invalid device: %p\n", dev); return (0); } static unsigned uaudio_get_child_index_by_chan(struct uaudio_softc *sc, struct uaudio_chan *ch) { unsigned i; for (i = 0; i != UAUDIO_MAX_CHILD; i++) { if ((sc->sc_play_chan + i) == ch || (sc->sc_rec_chan + i) == ch) return (i); } panic("uaudio_get_child_index_by_chan: Invalid chan: %p\n", ch); return (0); } static int uaudio_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); /* lookup non-standard device(s) */ if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { return (BUS_PROBE_SPECIFIC); } if (uaa->info.bInterfaceClass != UICLASS_AUDIO) { if (uaa->info.bInterfaceClass != UICLASS_VENDOR || usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0) return (ENXIO); } /* check for AUDIO control interface */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) { if (usb_test_quirk(uaa, UQ_BAD_AUDIO)) return (ENXIO); else return (BUS_PROBE_GENERIC); } /* check for MIDI stream */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) { if (usb_test_quirk(uaa, UQ_BAD_MIDI)) return (ENXIO); else return (BUS_PROBE_GENERIC); } return (ENXIO); } /* * Set Cmedia CM6206 S/PDIF settings * Source: CM6206 Datasheet v2.3. */ static int uaudio_set_spdif_cm6206(struct uaudio_softc *sc, int flags) { uint8_t cmd[2][4] = { {0x20, 0x20, 0x00, 0}, {0x20, 0x30, 0x02, 1} }; int i; if (flags & UAUDIO_SPDIF_OUT) cmd[1][1] = 0x00; else cmd[1][1] = 0x02; if (flags & UAUDIO_SPDIF_OUT_96K) cmd[0][1] = 0x60; /* 96K: 3'b110 */ if (flags & UAUDIO_SPDIF_IN_MIX) cmd[1][1] = 0x03; /* SPDIFMIX */ for (i = 0; i < 2; i++) { if (usbd_req_set_report(sc->sc_udev, NULL, cmd[i], sizeof(cmd[0]), sc->sc_mixer_iface_index, UHID_OUTPUT_REPORT, 0) != 0) { return (ENXIO); } } return (0); } static int uaudio_set_spdif_dummy(struct uaudio_softc *sc, int flags) { return (0); } static usb_error_t uaudio_force_power_save(struct uaudio_softc *sc, uint8_t iface_index) { struct usb_interface *iface; usb_error_t err; iface = usbd_get_iface(sc->sc_udev, iface_index); if (iface == NULL || iface->idesc == NULL) return (USB_ERR_INVAL); /* check if correct alternate setting is already selected */ if (iface->alt_index == 0) { /* force power save mode by selecting default alternate setting */ err = usbd_req_set_alt_interface_no(sc->sc_udev, NULL, iface_index, iface->idesc->bAlternateSetting); } else { err = usbd_set_alt_interface_index(sc->sc_udev, iface_index, 0); } return (err); } static int uaudio_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uaudio_softc *sc = device_get_softc(dev); struct usb_interface_descriptor *id; usb_error_t err; unsigned i; sc->sc_udev = uaa->device; sc->sc_mixer_iface_index = uaa->info.bIfaceIndex; sc->sc_mixer_iface_no = uaa->info.bIfaceNum; sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[0].sc = sc; sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[1].sc = sc; if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR)) sc->sc_uq_audio_swap_lr = 1; if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC)) sc->sc_uq_au_inp_async = 1; if (usb_test_quirk(uaa, UQ_AU_NO_XU)) sc->sc_uq_au_no_xu = 1; if (usb_test_quirk(uaa, UQ_BAD_ADC)) sc->sc_uq_bad_adc = 1; if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS)) sc->sc_uq_au_vendor_class = 1; /* set S/PDIF function */ if (usb_test_quirk(uaa, UQ_AU_SET_SPDIF_CM6206)) sc->sc_set_spdif_fn = uaudio_set_spdif_cm6206; else sc->sc_set_spdif_fn = uaudio_set_spdif_dummy; umidi_init(dev); device_set_usb_desc(dev); id = usbd_get_interface_descriptor(uaa->iface); /* must fill mixer info before channel info */ uaudio_mixer_fill_info(sc, uaa->device, id); /* fill channel info */ uaudio_chan_fill_info(sc, uaa->device); DPRINTF("audio rev %d.%02x\n", sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); if (sc->sc_mixer_count == 0) { if (uaa->info.idVendor == USB_VENDOR_MAUDIO && (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA || uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) { DPRINTF("Generating mixer descriptors\n"); uaudio_mixer_controls_create_ftu(sc); } } DPRINTF("%d mixer controls\n", sc->sc_mixer_count); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { uint8_t x; if (sc->sc_play_chan[i].num_alt <= 0) break; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinite * re-enumeration loop: */ err = uaudio_force_power_save(sc, sc->sc_play_chan[i].usb_alt[0].iface_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_play_chan[i].num_alt; x++) { device_printf(dev, "Play[%u]: %d Hz, %d ch, %s format, " "2x%dms buffer.%s\n", i, sc->sc_play_chan[i].usb_alt[x].sample_rate, sc->sc_play_chan[i].usb_alt[x].channels, sc->sc_play_chan[i].usb_alt[x].p_fmt->description, uaudio_buffer_ms, (x == 0) ? " (selected)" : ""); } } if (i == 0) device_printf(dev, "No playback.\n"); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { uint8_t x; if (sc->sc_rec_chan[i].num_alt <= 0) break; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinite * re-enumeration loop: */ err = uaudio_force_power_save(sc, sc->sc_rec_chan[i].usb_alt[0].iface_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_rec_chan[i].num_alt; x++) { device_printf(dev, "Record[%u]: %d Hz, %d ch, %s format, " "2x%dms buffer.%s\n", i, sc->sc_rec_chan[i].usb_alt[x].sample_rate, sc->sc_rec_chan[i].usb_alt[x].channels, sc->sc_rec_chan[i].usb_alt[x].p_fmt->description, uaudio_buffer_ms, (x == 0) ? " (selected)" : ""); } } if (i == 0) device_printf(dev, "No recording.\n"); if (sc->sc_midi_chan.valid == 0) { if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { sc->sc_midi_chan.iface_index = (uint8_t)uaa->driver_info; sc->sc_midi_chan.iface_alt_index = 0; sc->sc_midi_chan.valid = 1; } } if (sc->sc_midi_chan.valid) { if (umidi_probe(dev)) { goto detach; } device_printf(dev, "MIDI sequencer.\n"); } else { device_printf(dev, "No MIDI sequencer.\n"); } DPRINTF("doing child attach\n"); /* attach the children */ sc->sc_sndcard_func.func = SCF_PCM; /* * Only attach a PCM device if we have a playback, recording * or mixer device present: */ for (i = 0; i != UAUDIO_MAX_CHILD; i++) { if (sc->sc_play_chan[i].num_alt <= 0 && sc->sc_rec_chan[i].num_alt <= 0 && sc->sc_child[i].mix_info == 0) continue; sc->sc_child[i].pcm_device = device_add_child(dev, "pcm", -1); if (sc->sc_child[i].pcm_device == NULL) { DPRINTF("out of memory\n"); goto detach; } device_set_ivars(sc->sc_child[i].pcm_device, &sc->sc_sndcard_func); } if (bus_generic_attach(dev)) { DPRINTF("child attach failed\n"); goto detach; } if (uaudio_handle_hid) { if (uaudio_hid_probe(sc, uaa) == 0) { device_printf(dev, "HID volume keys found.\n"); } else { device_printf(dev, "No HID volume keys found.\n"); } } /* reload all mixer settings */ uaudio_mixer_reload_all(sc); /* enable S/PDIF output, if any */ if (sc->sc_set_spdif_fn(sc, UAUDIO_SPDIF_OUT | UAUDIO_SPDIF_OUT_48K) != 0) { device_printf(dev, "Failed to enable S/PDIF at 48K\n"); } return (0); /* success */ detach: uaudio_detach(dev); return (ENXIO); } static void uaudio_pcm_setflags(device_t dev, uint32_t flags) { pcm_setflags(dev, pcm_getflags(dev) | flags); } int uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); unsigned i = uaudio_get_child_index_by_dev(sc, dev); char status[SND_STATUSLEN]; uaudio_mixer_init(sc, i); if (sc->sc_uq_audio_swap_lr) { DPRINTF("hardware has swapped left and right\n"); /* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */ } if (sc->sc_play_chan[i].num_alt > 0 && (sc->sc_child[i].mix_info & SOUND_MASK_PCM) == 0) { DPRINTF("software controlled main volume\n"); /* * Emulate missing pcm mixer controller * through FEEDER_VOLUME */ uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL); } if (sc->sc_pcm_bitperfect) { DPRINTF("device needs bitperfect by default\n"); uaudio_pcm_setflags(dev, SD_F_BITPERFECT); } if (mixer_init(dev, mixer_class, sc)) goto detach; sc->sc_child[i].mixer_init = 1; mixer_hwvol_init(dev); device_set_descf(dev, "%s %s", usb_get_manufacturer(sc->sc_udev), usb_get_product(sc->sc_udev)); snprintf(status, sizeof(status), "on %s", device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, sc, (sc->sc_play_chan[i].num_alt > 0) ? 1 : 0, (sc->sc_rec_chan[i].num_alt > 0) ? 1 : 0)) { goto detach; } uaudio_pcm_setflags(dev, SD_F_MPSAFE); sc->sc_child[i].pcm_registered = 1; if (sc->sc_play_chan[i].num_alt > 0) { sc->sc_play_chan[i].priv_sc = sc; pcm_addchan(dev, PCMDIR_PLAY, chan_class, &sc->sc_play_chan[i]); } if (sc->sc_rec_chan[i].num_alt > 0) { sc->sc_rec_chan[i].priv_sc = sc; pcm_addchan(dev, PCMDIR_REC, chan_class, &sc->sc_rec_chan[i]); } pcm_setstatus(dev, status); uaudio_mixer_register_sysctl(sc, dev, i); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan[i].feedback_rate, 0, "Feedback sample rate in Hz"); return (0); /* success */ detach: uaudio_detach_sub(dev); return (ENXIO); } int uaudio_detach_sub(device_t dev) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); unsigned i = uaudio_get_child_index_by_dev(sc, dev); int error = 0; if (sc->sc_child[i].pcm_registered) { error = pcm_unregister(dev); } else if (sc->sc_child[i].mixer_init) { error = mixer_uninit(dev); } return (error); } static int uaudio_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); unsigned i; /* * Stop USB transfers early so that any audio applications * will time out and close opened /dev/dspX.Y device(s), if * any. */ usb_proc_explore_lock(sc->sc_udev); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { sc->sc_play_chan[i].operation = CHAN_OP_DRAIN; sc->sc_rec_chan[i].operation = CHAN_OP_DRAIN; } usb_proc_explore_mwait(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); usb_proc_explore_unlock(sc->sc_udev); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { usbd_transfer_unsetup(sc->sc_play_chan[i].xfer, UAUDIO_NCHANBUFS + 1); usbd_transfer_unsetup(sc->sc_rec_chan[i].xfer, UAUDIO_NCHANBUFS + 1); } uaudio_hid_detach(sc); if (bus_generic_detach(dev) != 0) { DPRINTF("detach failed!\n"); } sbuf_delete(&sc->sc_sndstat); sc->sc_sndstat_valid = 0; umidi_detach(dev); /* free mixer data */ uaudio_mixer_ctl_free(sc); /* disable S/PDIF output, if any */ (void) sc->sc_set_spdif_fn(sc, 0); return (0); } static uint32_t uaudio_get_interval_frames(const usb_endpoint_descriptor_audio_t *ed) { uint32_t frames = 1; /* Isochronous transfer interval is 2^(bInterval - 1) frames. */ if (ed->bInterval >= 1 && ed->bInterval <= 16) frames = (1 << (ed->bInterval - 1)); /* Limit transfer interval to maximum number of frames. */ if (frames > UAUDIO_NFRAMES) frames = UAUDIO_NFRAMES; return (frames); } static uint32_t uaudio_get_buffer_ms(struct uaudio_softc *sc, uint32_t int_frames) { uint32_t ms = 1; uint32_t fps = usbd_get_isoc_fps(sc->sc_udev); /* Make sure a whole USB transfer interval fits into the buffer. */ if (fps >= 1000 && int_frames > 0 && int_frames <= UAUDIO_NFRAMES) { /* Convert interval frames to milliseconds. */ ms = ((int_frames * 1000) / fps); } /* Respect minimum buffer length set through buffer_ms tunable. */ if (ms < uaudio_buffer_ms) ms = uaudio_buffer_ms; /* Limit buffer length to 8 milliseconds. */ if (ms > UAUDIO_BUFFER_MS_MAX) ms = UAUDIO_BUFFER_MS_MAX; return (ms); } static uint32_t uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt) { struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt]; uint32_t int_frames, ms, buf_size; /* USB transfer interval in frames, from endpoint descriptor. */ int_frames = uaudio_get_interval_frames(chan_alt->p_ed1); /* Buffer length in milliseconds, and in bytes of audio data. */ ms = uaudio_get_buffer_ms(ch->priv_sc, int_frames); buf_size = chan_alt->sample_size * howmany(chan_alt->sample_rate * ms, 1000); return (buf_size); } static uint32_t uaudio_max_buffer_size(struct uaudio_chan *ch, uint8_t alt) { struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt]; uint32_t buf_size; /* Maximum buffer length is 8 milliseconds. */ buf_size = chan_alt->sample_size * howmany(chan_alt->sample_rate * UAUDIO_BUFFER_MS_MAX, 1000); return (buf_size); } static void uaudio_configure_msg_sub(struct uaudio_softc *sc, struct uaudio_chan *chan, int dir) { struct uaudio_chan_alt *chan_alt; uint32_t frames; uint32_t buf_size; uint16_t fps; uint8_t next_alt; uint8_t fps_shift; uint8_t operation; usb_error_t err; if (chan->num_alt <= 0) return; DPRINTF("\n"); usb_proc_explore_lock(sc->sc_udev); operation = chan->operation; switch (operation) { case CHAN_OP_START: case CHAN_OP_STOP: chan->operation = CHAN_OP_NONE; break; default: break; } usb_proc_explore_unlock(sc->sc_udev); switch (operation) { case CHAN_OP_STOP: /* Unsetup prior USB transfers, if any. */ usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); mtx_lock(chan->pcm_mtx); chan->cur_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); /* * The first alternate setting is typically used for * power saving mode. Set this alternate setting as * part of entering stop. */ err = usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, 0); if (err) { DPRINTF("setting of default alternate index failed: %s!\n", usbd_errstr(err)); } return; case CHAN_OP_START: /* Unsetup prior USB transfers, if any. */ usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); break; default: return; } mtx_lock(chan->pcm_mtx); next_alt = chan->set_alt; mtx_unlock(chan->pcm_mtx); chan_alt = chan->usb_alt + next_alt; err = usbd_set_alt_interface_index(sc->sc_udev, chan_alt->iface_index, chan_alt->iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); goto error; } /* * Only set the sample rate if the channel reports that it * supports the frequency control. */ if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { unsigned int x; for (x = 0; x != 256; x++) { if (dir == PCMDIR_PLAY) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } if (uaudio20_set_speed(sc->sc_udev, sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting * the speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } } else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) { if (uaudio_set_speed(sc->sc_udev, chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting the * speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer, chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) { DPRINTF("could not allocate USB transfers!\n"); goto error; } fps = usbd_get_isoc_fps(sc->sc_udev); if (fps < 8000) { /* FULL speed USB */ frames = uaudio_buffer_ms; } else { /* HIGH speed USB */ frames = uaudio_buffer_ms * 8; } fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]); /* down shift number of frames per second, if any */ fps >>= fps_shift; frames >>= fps_shift; /* bytes per frame should not be zero */ chan->bytes_per_frame[0] = ((chan_alt->sample_rate / fps) * chan_alt->sample_size); chan->bytes_per_frame[1] = howmany(chan_alt->sample_rate, fps) * chan_alt->sample_size; /* setup data rate dithering, if any */ chan->frames_per_second = fps; chan->sample_rem = chan_alt->sample_rate % fps; chan->sample_curr = 0; /* compute required buffer size */ buf_size = (chan->bytes_per_frame[1] * frames); if (buf_size > (chan->end - chan->start)) { DPRINTF("buffer size is too big\n"); goto error; } chan->intr_frames = frames; DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem); if (chan->intr_frames == 0) { DPRINTF("frame shift is too high!\n"); goto error; } #if (UAUDIO_NCHANBUFS != 2) #error "Please update code below!" #endif mtx_lock(chan->pcm_mtx); chan->cur_alt = next_alt; usbd_transfer_start(chan->xfer[0]); usbd_transfer_start(chan->xfer[1]); mtx_unlock(chan->pcm_mtx); return; error: usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); mtx_lock(chan->pcm_mtx); chan->cur_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); } static void uaudio_configure_msg(struct usb_proc_msg *pm) { struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc; unsigned i; usb_proc_explore_unlock(sc->sc_udev); for (i = 0; i != UAUDIO_MAX_CHILD; i++) { uaudio_configure_msg_sub(sc, &sc->sc_play_chan[i], PCMDIR_PLAY); uaudio_configure_msg_sub(sc, &sc->sc_rec_chan[i], PCMDIR_REC); } usb_proc_explore_lock(sc->sc_udev); } /*========================================================================* * AS - Audio Stream - routines *========================================================================*/ #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed) { if (ed) { DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n" "bEndpointAddress=%d bmAttributes=0x%x \n" "wMaxPacketSize=%d bInterval=%d \n" "bRefresh=%d bSynchAddress=%d\n", ed, ed->bLength, ed->bDescriptorType, ed->bEndpointAddress, ed->bmAttributes, UGETW(ed->wMaxPacketSize), ed->bInterval, UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0, UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0); } } #endif /* * The following is a workaround for broken no-name USB audio devices * sold by dealextreme called "3D sound". The problem is that the * manufacturer computed wMaxPacketSize is too small to hold the * actual data sent. In other words the device sometimes sends more * data than it actually reports it can send in a single isochronous * packet. */ static void uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep, uint32_t xps, uint32_t add) { uint32_t mps; mps = UGETW(ep->wMaxPacketSize); /* * If the device indicates it can send more data than what the * sample rate indicates, we apply the workaround. */ if (mps > xps) { /* allow additional data */ xps += add; /* check against the maximum USB 1.x length */ if (xps > 1023) xps = 1023; /* check if we should do an update */ if (mps < xps) { /* simply update the wMaxPacketSize field */ USETW(ep->wMaxPacketSize, xps); DPRINTF("Workaround: Updated wMaxPacketSize " "from %d to %d bytes.\n", (int)mps, (int)xps); } } } static usb_error_t uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t rate) { struct usb_device_request req; usb_error_t error; #define UAUDIO20_MAX_RATES 32 /* we support at maximum 32 rates */ uint8_t data[2 + UAUDIO20_MAX_RATES * 12]; uint16_t actlen; uint16_t rates; uint16_t x; DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n", iface_no, clockid, rate); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UA20_CS_RANGE; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); /* * Assume there is at least one rate to begin with, else some * devices might refuse to return the USB descriptor: */ USETW(req.wLength, (2 + 1 * 12)); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) { /* * Likely the descriptor doesn't fit into the supplied * buffer. Try using a larger buffer and see if that * helps: */ rates = MIN(UAUDIO20_MAX_RATES, (255 - 2) / 12); error = USB_ERR_INVAL; } else { rates = UGETW(data); if (rates > UAUDIO20_MAX_RATES) { DPRINTF("Too many rates truncating to %d\n", UAUDIO20_MAX_RATES); rates = UAUDIO20_MAX_RATES; error = USB_ERR_INVAL; } else if (rates > 1) { DPRINTF("Need to read full rate descriptor\n"); error = USB_ERR_INVAL; } } if (error != 0) { /* * Try to read full rate descriptor. */ actlen = (2 + rates * 12); USETW(req.wLength, actlen); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) return (USB_ERR_INVAL); rates = UGETW(data); } actlen = (actlen - 2) / 12; if (rates > actlen) { DPRINTF("Too many rates truncating to %d\n", actlen); rates = actlen; } for (x = 0; x != rates; x++) { uint32_t min = UGETDW(data + 2 + (12 * x)); uint32_t max = UGETDW(data + 6 + (12 * x)); uint32_t res = UGETDW(data + 10 + (12 * x)); if (res == 0) { DPRINTF("Zero residue\n"); res = 1; } if (min > max) { DPRINTF("Swapped max and min\n"); uint32_t temp; temp = min; min = max; max = temp; } if (rate >= min && rate <= max && (((rate - min) % res) == 0)) { return (0); } } return (USB_ERR_INVAL); } static struct uaudio_chan * uaudio_get_chan(struct uaudio_softc *sc, struct uaudio_chan *chan, uint8_t iface_index) { unsigned i; for (i = 0; i != UAUDIO_MAX_CHILD; i++, chan++) { if (chan->num_alt == 0) { chan->iface_index = iface_index; return (chan); } else if (chan->iface_index == iface_index) return (chan); } return (NULL); } static void uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev, uint32_t rate, uint8_t channels, uint8_t bit_resolution) { struct usb_descriptor *desc = NULL; union uaudio_asid asid = { NULL }; union uaudio_asf1d asf1d = { NULL }; union uaudio_sed sed = { NULL }; struct usb_midi_streaming_endpoint_descriptor *msid = NULL; usb_endpoint_descriptor_audio_t *ed1 = NULL; const struct usb_audio_control_descriptor *acdp = NULL; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); struct usb_interface_descriptor *id; const struct uaudio_format *p_fmt = NULL; struct uaudio_chan *chan; struct uaudio_chan_alt *chan_alt; uint32_t format; uint16_t curidx = 0xFFFF; uint16_t lastidx = 0xFFFF; uint16_t alt_index = 0; uint16_t audio_rev = 0; uint16_t x; uint8_t ep_dir; uint8_t bChannels; uint8_t bBitResolution; uint8_t audio_if = 0; uint8_t midi_if = 0; uint8_t uma_if_class; while ((desc = usb_desc_foreach(cd, desc))) { if ((desc->bDescriptorType == UDESC_INTERFACE) && (desc->bLength >= sizeof(*id))) { id = (void *)desc; if (id->bInterfaceNumber != lastidx) { lastidx = id->bInterfaceNumber; curidx++; alt_index = 0; } else { alt_index++; } if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) && (id->bInterfaceClass == UICLASS_HID) && (id->bInterfaceSubClass == 0) && (id->bInterfaceProtocol == 0) && (alt_index == 0) && usbd_get_iface(udev, curidx) != NULL) { DPRINTF("Found HID interface at %d\n", curidx); sc->sc_hid.flags |= UAUDIO_HID_VALID; sc->sc_hid.iface_index = curidx; } uma_if_class = ((id->bInterfaceClass == UICLASS_AUDIO) || ((id->bInterfaceClass == UICLASS_VENDOR) && (sc->sc_uq_au_vendor_class != 0))); if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) { audio_if = 1; } else { audio_if = 0; } if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) { /* * XXX could allow multiple MIDI interfaces */ midi_if = 1; if ((sc->sc_midi_chan.valid == 0) && (usbd_get_iface(udev, curidx) != NULL)) { sc->sc_midi_chan.iface_index = curidx; sc->sc_midi_chan.iface_alt_index = alt_index; sc->sc_midi_chan.valid = 1; } } else { midi_if = 0; } asid.v1 = NULL; asf1d.v1 = NULL; ed1 = NULL; sed.v1 = NULL; /* * There can only be one USB audio instance * per USB device. Grab all USB audio * interfaces on this USB device so that we * don't attach USB audio twice: */ if (alt_index == 0 && curidx != sc->sc_mixer_iface_index && (id->bInterfaceClass == UICLASS_AUDIO || audio_if != 0 || midi_if != 0)) { usbd_set_parent_iface(sc->sc_udev, curidx, sc->sc_mixer_iface_index); } } if (audio_if == 0) { if (midi_if == 0) { if ((acdp == NULL) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) && (desc->bLength >= sizeof(*acdp))) { acdp = (void *)desc; audio_rev = UGETW(acdp->bcdADC); } } else { msid = (void *)desc; /* get the maximum number of embedded jacks in use, if any */ if (msid->bLength >= sizeof(*msid) && msid->bDescriptorType == UDESC_CS_ENDPOINT && msid->bDescriptorSubtype == MS_GENERAL && msid->bNumEmbMIDIJack > sc->sc_midi_chan.max_emb_jack) { sc->sc_midi_chan.max_emb_jack = msid->bNumEmbMIDIJack; } } /* * Don't collect any USB audio descriptors if * this is not an USB audio stream interface. */ continue; } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == AS_GENERAL) && (asid.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asid.v2)) { asid.v2 = (void *)desc; } } else { if (desc->bLength >= sizeof(*asid.v1)) { asid.v1 = (void *)desc; } } } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == FORMAT_TYPE) && (asf1d.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asf1d.v2)) asf1d.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*asf1d.v1)) { asf1d.v1 = (void *)desc; if (asf1d.v1->bFormatType != FORMAT_TYPE_I) { DPRINTFN(11, "ignored bFormatType = %d\n", asf1d.v1->bFormatType); asf1d.v1 = NULL; continue; } if (desc->bLength < (sizeof(*asf1d.v1) + ((asf1d.v1->bSamFreqType == 0) ? 6 : (asf1d.v1->bSamFreqType * 3)))) { DPRINTFN(11, "invalid descriptor, " "too short\n"); asf1d.v1 = NULL; continue; } } } } if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= UEP_MINSIZE) && (ed1 == NULL)) { ed1 = (void *)desc; if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) { ed1 = NULL; continue; } } if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) && (desc->bDescriptorType == UDESC_CS_ENDPOINT) && (desc->bDescriptorSubtype == AS_GENERAL) && (sed.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*sed.v2)) sed.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*sed.v1)) sed.v1 = (void *)desc; } } if (asid.v1 == NULL || asf1d.v1 == NULL || ed1 == NULL || sed.v1 == NULL) { /* need more descriptors */ continue; } ep_dir = UE_GET_DIR(ed1->bEndpointAddress); /* We ignore sync endpoint information until further. */ if (audio_rev >= UAUDIO_VERSION_30) { goto next_ep; } else if (audio_rev >= UAUDIO_VERSION_20) { uint32_t dwFormat; dwFormat = UGETDW(asid.v2->bmFormats); bChannels = asid.v2->bNrChannels; bBitResolution = asf1d.v2->bSubslotSize * 8; if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } for (p_fmt = uaudio20_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat & dwFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } for (x = 0; x != 256; x++) { if (ep_dir == UE_DIR_OUT) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } DPRINTF("Checking clock ID=%d\n", x); if (uaudio20_check_rate(udev, sc->sc_mixer_iface_no, x, rate)) { DPRINTF("Unsupported sampling " "rate, id=%d\n", x); goto next_ep; } } } else { uint16_t wFormat; wFormat = UGETW(asid.v1->wFormatTag); bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels); bBitResolution = asf1d.v1->bSubFrameSize * 8; if (asf1d.v1->bSamFreqType == 0) { DPRINTFN(16, "Sample rate: %d-%dHz\n", UA_SAMP_LO(asf1d.v1), UA_SAMP_HI(asf1d.v1)); if ((rate >= UA_SAMP_LO(asf1d.v1)) && (rate <= UA_SAMP_HI(asf1d.v1))) goto found_rate; } else { for (x = 0; x < asf1d.v1->bSamFreqType; x++) { DPRINTFN(16, "Sample rate = %dHz\n", UA_GETSAMP(asf1d.v1, x)); if (rate == UA_GETSAMP(asf1d.v1, x)) goto found_rate; } } goto next_ep; found_rate: for (p_fmt = uaudio10_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat == wFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } } chan = uaudio_get_chan(sc, (ep_dir == UE_DIR_OUT) ? &sc->sc_play_chan[0] : &sc->sc_rec_chan[0], curidx); if (chan == NULL) { DPRINTF("More than %d sub devices. (skipped)\n", UAUDIO_MAX_CHILD); goto next_ep; } if (usbd_get_iface(udev, curidx) == NULL) { DPRINTF("Interface is not valid\n"); goto next_ep; } if (chan->num_alt == CHAN_MAX_ALT) { DPRINTF("Too many alternate settings\n"); goto next_ep; } chan->set_alt = 0; chan->cur_alt = CHAN_MAX_ALT; chan_alt = &chan->usb_alt[chan->num_alt++]; #ifdef USB_DEBUG uaudio_chan_dump_ep_desc(ed1); #endif DPRINTF("Sample rate = %dHz, channels = %d, " "bits = %d, format = %s, ep 0x%02x, chan %p\n", rate, channels, bit_resolution, p_fmt->description, ed1->bEndpointAddress, chan); chan_alt->sample_rate = rate; chan_alt->p_asf1d = asf1d; chan_alt->p_ed1 = ed1; chan_alt->p_fmt = p_fmt; chan_alt->p_sed = sed; chan_alt->iface_index = curidx; chan_alt->iface_alt_index = alt_index; if (ep_dir == UE_DIR_IN) chan_alt->usb_cfg = uaudio_cfg_record; else chan_alt->usb_cfg = uaudio_cfg_play; chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) * p_fmt->bPrecision) / 8; chan_alt->channels = channels; if (ep_dir == UE_DIR_IN && usbd_get_speed(udev) == USB_SPEED_FULL) { uaudio_record_fix_fs(ed1, chan_alt->sample_size * (rate / 1000), chan_alt->sample_size * (rate / 4000)); } /* setup play/record format */ format = chan_alt->p_fmt->freebsd_fmt; /* get default SND_FORMAT() */ format = SND_FORMAT(format, chan_alt->channels, 0); switch (chan_alt->channels) { uint32_t temp_fmt; case 1: case 2: /* mono and stereo */ break; default: /* surround and more */ temp_fmt = feeder_matrix_default_format(format); /* if multichannel, then format can be zero */ if (temp_fmt != 0) format = temp_fmt; break; } /* check if format is not supported */ if (format == 0) { DPRINTF("The selected audio format is not supported\n"); chan->num_alt--; goto next_ep; } if (chan->num_alt > 1) { /* we only accumulate one format at different sample rates */ if (chan->pcm_format[0] != format) { DPRINTF("Multiple formats is not supported\n"); chan->num_alt--; goto next_ep; } /* ignore if duplicate sample rate entry */ if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) { DPRINTF("Duplicate sample rate detected\n"); chan->num_alt--; goto next_ep; } } chan->pcm_cap.fmtlist = chan->pcm_format; chan->pcm_cap.fmtlist[0] = format; /* check if device needs bitperfect */ if (chan_alt->channels > UAUDIO_MATRIX_MAX) sc->sc_pcm_bitperfect = 1; if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0) chan->pcm_cap.minspeed = rate; if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0) chan->pcm_cap.maxspeed = rate; if (sc->sc_sndstat_valid != 0) { sbuf_printf(&sc->sc_sndstat, "\n\t" "mode %d.%d:(%s) %dch, %dbit, %s, %dHz", curidx, alt_index, (ep_dir == UE_DIR_IN) ? "input" : "output", channels, p_fmt->bPrecision, p_fmt->description, rate); } next_ep: sed.v1 = NULL; ed1 = NULL; } } /* This structure defines all the supported rates. */ static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = { 384000, 352800, 192000, 176400, 96000, 88200, 88000, 80000, 72000, 64000, 56000, 48000, 44100, 40000, 32000, 24000, 22050, 16000, 11025, 8000, 0 }; static void uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev) { uint32_t rate = uaudio_default_rate; uint8_t z; uint8_t bits = uaudio_default_bits; uint8_t y; uint8_t channels = uaudio_default_channels; uint8_t channels_max; uint8_t x; bits -= (bits % 8); if ((bits == 0) || (bits > UAUDIO_BITS_MAX)) { /* set a valid value */ bits = UAUDIO_BITS_MAX; } if (channels > UAUDIO_CHANNELS_MAX) channels = UAUDIO_CHANNELS_MAX; switch (usbd_get_speed(udev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: /* * Due to high bandwidth usage and problems * with HIGH-speed split transactions we * disable surround setups on FULL-speed USB * by default */ channels_max = 4; /* more channels on request */ if (channels > channels_max) channels_max = channels; break; default: channels_max = UAUDIO_CHANNELS_MAX; break; } if (channels == 0) channels = channels_max; if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) sc->sc_sndstat_valid = 1; /* try to search for a valid config */ for (x = channels; x; x--) { for (y = bits; y; y -= 8) { /* try user defined rate, if any */ if (rate != 0) uaudio_chan_fill_info_sub(sc, udev, rate, x, y); /* try find a matching rate, if any */ for (z = 0; uaudio_rate_list[z]; z++) { if (uaudio_rate_list[z] != rate) uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y); } /* after default value in first round, proceed with max bits */ if (y == bits) y = UAUDIO_BITS_MAX + 8; /* skip default value subsequently */ if (y == (bits + 8)) y -= 8; } /* after default value in first round, proceed with max channels */ if (x == channels) x = channels_max + 1; /* skip default value subsequently */ if (x == (channels + 1)) x--; } if (sc->sc_sndstat_valid) sbuf_finish(&sc->sc_sndstat); } static void uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint64_t sample_rate; uint8_t buf[4]; uint64_t temp; unsigned i; int len; int actlen; int nframes; usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); i = uaudio_get_child_index_by_chan(ch->priv_sc, ch); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "transferred %d bytes\n", actlen); if (nframes == 0) break; len = usbd_xfer_frame_len(xfer, 0); if (len == 0) break; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); temp = UGETDW(buf); DPRINTF("Value = 0x%08x\n", (int)temp); /* auto-detect SYNC format */ if (len == 4) temp &= 0x0fffffff; /* check for no data */ if (temp == 0) break; temp *= 125ULL; sample_rate = ch->usb_alt[ch->cur_alt].sample_rate; /* auto adjust */ while (temp < (sample_rate - (sample_rate / 4))) temp *= 2; while (temp > (sample_rate + (sample_rate / 2))) temp /= 2; DPRINTF("Comparing %d Hz :: %d Hz\n", (int)temp, (int)sample_rate); /* * Use feedback value as fallback when there is no * recording channel: */ if (ch->priv_sc->sc_rec_chan[i].num_alt == 0) { int32_t jitter_max = howmany(sample_rate, 16000); /* * Range check the jitter values to avoid * bogus sample rate adjustments. The expected * deviation should not be more than 1Hz per * second. The USB v2.0 specification also * mandates this requirement. Refer to chapter * 5.12.4.2 about feedback. */ ch->jitter_curr = temp - sample_rate; if (ch->jitter_curr > jitter_max) ch->jitter_curr = jitter_max; else if (ch->jitter_curr < -jitter_max) ch->jitter_curr = -jitter_max; } ch->feedback_rate = temp; break; case USB_ST_SETUP: /* * Check if the recording stream can be used as a * source of jitter information to save some * isochronous bandwidth: */ if (ch->priv_sc->sc_rec_chan[i].num_alt != 0 && uaudio_debug == 0) break; usbd_xfer_set_frames(xfer, 1); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ break; } } static int uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt) { uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes; return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC); } static void uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct uaudio_chan *ch_rec; struct usb_page_cache *pc; uint32_t mfl; uint32_t total; uint32_t blockcount; uint32_t n; uint32_t offset; unsigned i; int sample_size; int actlen; int sumlen; if (ch->running == 0 || ch->start == ch->end) { DPRINTF("not running or no buffer!\n"); return; } i = uaudio_get_child_index_by_chan(ch->priv_sc, ch); /* check if there is a valid record channel */ ch_rec = ch->priv_sc->sc_rec_chan + i; if (ch_rec->num_alt == 0) ch_rec = NULL; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: tr_setup: if (ch_rec != NULL) { /* * NOTE: The play and record callbacks are * executed from the same USB thread and * locking the record channel mutex here is * not needed. This avoids a LOR situation. */ /* reset receive jitter counters */ ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; } /* reset transmit jitter counters */ ch->jitter_curr = 0; ch->jitter_rem = 0; /* FALLTHROUGH */ case USB_ST_TRANSFERRED: if (actlen < sumlen) { DPRINTF("short transfer, " "%d of %d bytes\n", actlen, sumlen); } chn_intr(ch->pcm_ch); /* * Check for asynchronous playback endpoint and that * the playback endpoint is properly configured: */ if (ch_rec != NULL && uaudio_chan_is_async(ch, ch->cur_alt) != 0) { uint32_t rec_alt = ch_rec->cur_alt; if (rec_alt < ch_rec->num_alt) { int64_t tx_jitter; int64_t rx_rate; /* * NOTE: The play and record callbacks * are executed from the same USB * thread and locking the record * channel mutex here is not needed. * This avoids a LOR situation. */ /* translate receive jitter into transmit jitter */ tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate; tx_jitter = (tx_jitter * ch_rec->jitter_curr) + ch->jitter_rem; /* reset receive jitter counters */ ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; /* compute exact number of transmit jitter samples */ rx_rate = ch_rec->usb_alt[rec_alt].sample_rate; ch->jitter_curr += tx_jitter / rx_rate; ch->jitter_rem = tx_jitter % rx_rate; } } /* start the SYNC transfer one time per second, if any */ ch->intr_counter += ch->intr_frames; if (ch->intr_counter >= ch->frames_per_second) { ch->intr_counter -= ch->frames_per_second; usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]); } mfl = usbd_xfer_max_framelen(xfer); if (ch->bytes_per_frame[1] > mfl) { DPRINTF("bytes per transfer, %d, " "exceeds maximum, %d!\n", ch->bytes_per_frame[1], mfl); break; } blockcount = ch->intr_frames; /* setup number of frames */ usbd_xfer_set_frames(xfer, blockcount); /* get sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; /* reset total length */ total = 0; /* setup frame lengths */ for (n = 0; n != blockcount; n++) { uint32_t frame_len; ch->sample_curr += ch->sample_rem; if (ch->sample_curr >= ch->frames_per_second) { ch->sample_curr -= ch->frames_per_second; frame_len = ch->bytes_per_frame[1]; } else { frame_len = ch->bytes_per_frame[0]; } /* handle free running clock case */ if (ch->jitter_curr > 0 && (frame_len + sample_size) <= mfl) { DPRINTFN(6, "sending one sample more\n"); ch->jitter_curr--; frame_len += sample_size; } else if (ch->jitter_curr < 0 && frame_len >= sample_size) { DPRINTFN(6, "sending one sample less\n"); ch->jitter_curr++; frame_len -= sample_size; } usbd_xfer_set_frame_len(xfer, n, frame_len); total += frame_len; } DPRINTFN(6, "transferring %d bytes\n", total); offset = 0; pc = usbd_xfer_get_frame(xfer, 0); while (total > 0) { n = (ch->end - ch->cur); if (n > total) n = total; usbd_copy_in(pc, offset, ch->cur, n); total -= n; ch->cur += n; offset += n; if (ch->cur >= ch->end) ch->cur = ch->start; } usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } static void uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error) { /* TODO */ } static void uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t offset0; uint32_t mfl; int m; int n; int len; int actlen; int nframes; int expected_bytes; int sample_size; if (ch->start == ch->end) { DPRINTF("no buffer!\n"); return; } usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); mfl = usbd_xfer_max_framelen(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: offset0 = 0; pc = usbd_xfer_get_frame(xfer, 0); /* try to compute the number of expected bytes */ ch->sample_curr += (ch->sample_rem * ch->intr_frames); /* compute number of expected bytes */ expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) + ((ch->sample_curr / ch->frames_per_second) * (ch->bytes_per_frame[1] - ch->bytes_per_frame[0])); /* keep remainder */ ch->sample_curr %= ch->frames_per_second; /* get current sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; for (n = 0; n != nframes; n++) { uint32_t offset1 = offset0; len = usbd_xfer_frame_len(xfer, n); /* make sure we only receive complete samples */ len = len - (len % sample_size); /* subtract bytes received from expected payload */ expected_bytes -= len; /* don't receive data when not ready */ if (ch->running == 0 || ch->cur_alt != ch->set_alt) continue; /* fill ring buffer with samples, if any */ while (len > 0) { m = (ch->end - ch->cur); if (m > len) m = len; usbd_copy_out(pc, offset1, ch->cur, m); len -= m; offset1 += m; ch->cur += m; if (ch->cur >= ch->end) ch->cur = ch->start; } offset0 += mfl; } /* update current jitter */ ch->jitter_curr -= (expected_bytes / sample_size); /* don't allow a huge amount of jitter to accumulate */ nframes = 2 * ch->intr_frames; /* range check current jitter */ if (ch->jitter_curr < -nframes) ch->jitter_curr = -nframes; else if (ch->jitter_curr > nframes) ch->jitter_curr = nframes; DPRINTFN(6, "transferred %d bytes, jitter %d samples\n", actlen, ch->jitter_curr); if (ch->running != 0) chn_intr(ch->pcm_ch); case USB_ST_SETUP: tr_setup: nframes = ch->intr_frames; usbd_xfer_set_frames(xfer, nframes); for (n = 0; n != nframes; n++) usbd_xfer_set_frame_len(xfer, n, mfl); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } void * uaudio_chan_init(struct uaudio_chan *ch, struct snd_dbuf *b, struct pcm_channel *c, int dir) { uint32_t buf_size; uint8_t x; /* store mutex and PCM channel */ ch->pcm_ch = c; ch->pcm_mtx = c->lock; /* compute worst case buffer */ buf_size = 0; for (x = 0; x != ch->num_alt; x++) { uint32_t temp = uaudio_max_buffer_size(ch, x); if (temp > buf_size) buf_size = temp; } /* allow double buffering */ buf_size *= 2; DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size); ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO); if (ch->buf == NULL) goto error; if (sndbuf_setup(b, ch->buf, buf_size) != 0) goto error; ch->start = ch->buf; ch->end = ch->buf + buf_size; ch->cur = ch->buf; ch->pcm_buf = b; ch->max_buf = buf_size; if (ch->pcm_mtx == NULL) { DPRINTF("ERROR: PCM channels does not have a mutex!\n"); goto error; } return (ch); error: uaudio_chan_free(ch); return (NULL); } int uaudio_chan_free(struct uaudio_chan *ch) { if (ch->buf != NULL) { free(ch->buf, M_DEVBUF); ch->buf = NULL; } usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1); ch->num_alt = 0; return (0); } int uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize) { uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); sndbuf_setup(ch->pcm_buf, ch->buf, temp); return (temp / 2); } int uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, uint32_t blockcount) { return (1); } int uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) { struct uaudio_softc *sc; uint8_t x, y; sc = ch->priv_sc; for (x = 0, y = 1; y < ch->num_alt; y++) { /* prefer sample rate closer to and greater than requested */ if ((ch->usb_alt[x].sample_rate < speed && ch->usb_alt[x].sample_rate < ch->usb_alt[y].sample_rate) || (speed <= ch->usb_alt[y].sample_rate && ch->usb_alt[y].sample_rate < ch->usb_alt[x].sample_rate)) x = y; } usb_proc_explore_lock(sc->sc_udev); ch->set_alt = x; usb_proc_explore_unlock(sc->sc_udev); DPRINTF("Selecting alt %d\n", (int)x); return (ch->usb_alt[x].sample_rate); } int uaudio_chan_getptr(struct uaudio_chan *ch) { return (ch->cur - ch->start); } struct pcmchan_caps * uaudio_chan_getcaps(struct uaudio_chan *ch) { return (&ch->pcm_cap); } static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = { .id = SND_CHN_MATRIX_DRV, .channels = 2, .ext = 0, .map = { /* Right */ [0] = { .type = SND_CHN_T_FR, .members = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR }, /* Left */ [1] = { .type = SND_CHN_T_FL, .members = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL }, [2] = { .type = SND_CHN_T_MAX, .members = 0 } }, .mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL, .offset = { 1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } }; struct pcmchan_matrix * uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format) { struct uaudio_softc *sc; sc = ch->priv_sc; if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 && AFMT_CHANNEL(format) == 2) return (&uaudio_chan_matrix_swap_2_0); return (feeder_matrix_format_map(format)); } int uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format) { DPRINTF("Selecting format 0x%08x\n", (unsigned int)format); return (0); } static void uaudio_chan_reconfigure(struct uaudio_chan *ch, uint8_t operation) { struct uaudio_softc *sc = ch->priv_sc; /* Check for shutdown. */ if (ch->operation == CHAN_OP_DRAIN) return; /* Set next operation. */ ch->operation = operation; /* * Because changing the alternate setting modifies the USB * configuration, this part must be executed from the USB * explore process. */ (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } static int uaudio_chan_need_both(struct uaudio_chan *pchan, struct uaudio_chan *rchan) { return (pchan->num_alt > 0 && pchan->running != 0 && uaudio_chan_is_async(pchan, pchan->set_alt) != 0 && rchan->num_alt > 0 && rchan->running == 0); } static int uaudio_chan_need_none(struct uaudio_chan *pchan, struct uaudio_chan *rchan) { return (pchan->num_alt > 0 && pchan->running == 0 && rchan->num_alt > 0 && rchan->running == 0); } void uaudio_chan_start(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; unsigned i = uaudio_get_child_index_by_chan(sc, ch); /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if not running */ if (ch->running == 0) { uint32_t temp; /* get current buffer size */ temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); /* set running flag */ ch->running = 1; /* ensure the hardware buffer is reset */ ch->start = ch->buf; ch->end = ch->buf + temp; ch->cur = ch->buf; if (uaudio_chan_need_both( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { /* * Start both endpoints because of need for * jitter information: */ uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_START); uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_START); } else { uaudio_chan_reconfigure(ch, CHAN_OP_START); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } void uaudio_chan_stop(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; unsigned i = uaudio_get_child_index_by_chan(sc, ch); /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if running */ if (ch->running != 0) { /* clear running flag */ ch->running = 0; if (uaudio_chan_need_both( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { /* * Leave the endpoints running because we need * information about jitter! */ } else if (uaudio_chan_need_none( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { /* * Stop both endpoints in case the one was used for * jitter information: */ uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_STOP); uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_STOP); } else { uaudio_chan_reconfigure(ch, CHAN_OP_STOP); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } /*========================================================================* * AC - Audio Controller - routines *========================================================================*/ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct uaudio_softc *sc; struct uaudio_mixer_node *pmc; int hint; int error; int temp = 0; int chan = 0; sc = (struct uaudio_softc *)oidp->oid_arg1; hint = oidp->oid_arg2; if (sc->sc_child[0].mixer_lock == NULL) return (ENXIO); /* lookup mixer node */ mtx_lock(sc->sc_child[0].mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { for (chan = 0; chan != (int)pmc->nchan; chan++) { if (pmc->wValue[chan] != -1 && pmc->wValue[chan] == hint) { temp = pmc->wData[chan]; goto found; } } } found: mtx_unlock(sc->sc_child[0].mixer_lock); error = sysctl_handle_int(oidp, &temp, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* update mixer value */ mtx_lock(sc->sc_child[0].mixer_lock); if (pmc != NULL && temp >= pmc->minval && temp <= pmc->maxval) { pmc->wData[chan] = temp; pmc->update[(chan / 8)] |= (1 << (chan % 8)); /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } mtx_unlock(sc->sc_child[0].mixer_lock); return (0); } static void uaudio_mixer_ctl_free(struct uaudio_softc *sc) { struct uaudio_mixer_node *p_mc; while ((p_mc = sc->sc_mixer_root) != NULL) { sc->sc_mixer_root = p_mc->next; free(p_mc, M_USBDEV); } } static void uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev, unsigned index) { struct uaudio_mixer_node *pmc; struct sysctl_oid *mixer_tree; struct sysctl_oid *control_tree; char buf[32]; int chan; int n; if (index != 0) return; mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); if (mixer_tree == NULL) return; for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next, n++) { for (chan = 0; chan < pmc->nchan; chan++) { if (pmc->nchan > 1) { snprintf(buf, sizeof(buf), "%s_%d_%d", pmc->name, n, chan); } else { snprintf(buf, sizeof(buf), "%s_%d", pmc->name, n); } control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Mixer control nodes"); if (control_tree == NULL) continue; SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "val", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, pmc->wValue[chan], uaudio_mixer_sysctl_handler, "I", "Current value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval, "Minimum value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval, "Maximum value"); SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0, "Description"); } } } /* M-Audio FastTrack Ultra Mixer Description */ /* Origin: Linux USB Audio driver */ static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc) { int chx; int chy; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(8, 0); MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect"; MIX(sc).minval = 0; MIX(sc).maxval = 7; MIX(sc).mul = 7; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chx = 0; chx != 8; chx++) { for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_rec"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "AIn%d - Out%d Record Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_play"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = (chx == chy) ? 2 : 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "DIn%d - Out%d Playback Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(2, 0); MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_vol"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(3, 0); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_dur"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f00; MIX(sc).mul = 0x7f00; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(4, 0); MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_fb"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no); for (chy = 0; chy != 4; chy++) { MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_ret"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Return %d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send AIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send DIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_reload_all(struct uaudio_softc *sc) { struct uaudio_mixer_node *pmc; int chan; if (sc->sc_child[0].mixer_lock == NULL) return; mtx_lock(sc->sc_child[0].mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { /* use reset defaults for non-oss controlled settings */ if (pmc->ctl == SOUND_MIXER_NRDEVICES) continue; for (chan = 0; chan < pmc->nchan; chan++) pmc->update[chan / 8] |= (1 << (chan % 8)); } usbd_transfer_start(sc->sc_mixer_xfer[0]); /* start HID volume keys, if any */ usbd_transfer_start(sc->sc_hid.xfer[0]); mtx_unlock(sc->sc_child[0].mixer_lock); } static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { struct uaudio_mixer_node *p_mc_new = malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK); int ch; if (p_mc_new != NULL) { memcpy(p_mc_new, mc, sizeof(*p_mc_new)); p_mc_new->next = sc->sc_mixer_root; sc->sc_mixer_root = p_mc_new; sc->sc_mixer_count++; /* set default value for all channels */ for (ch = 0; ch < p_mc_new->nchan; ch++) { switch (p_mc_new->val_default) { case 1: /* 50% */ p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2; break; case 2: /* 100% */ p_mc_new->wData[ch] = p_mc_new->maxval; break; default: /* 0% */ p_mc_new->wData[ch] = p_mc_new->minval; break; } } } else { DPRINTF("out of memory\n"); } } static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { int32_t res; DPRINTF("adding %d\n", mc->ctl); if (mc->type == MIX_ON_OFF) { mc->minval = 0; mc->maxval = 1; } else if (mc->type == MIX_SELECTOR) { } else { /* determine min and max values */ mc->minval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MIN, mc); mc->maxval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MAX, mc); /* check if max and min was swapped */ if (mc->maxval < mc->minval) { res = mc->maxval; mc->maxval = mc->minval; mc->minval = res; } /* compute value range */ mc->mul = mc->maxval - mc->minval; if (mc->mul == 0) mc->mul = 1; /* compute value alignment */ res = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_RES, mc); DPRINTF("Resolution = %d\n", (int)res); } uaudio_mixer_add_ctl_sub(sc, mc); #ifdef USB_DEBUG if (uaudio_debug > 2) { uint8_t i; for (i = 0; i < mc->nchan; i++) { DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]); } DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' " "min=%d max=%d\n", mc->wIndex, mc->type, mc->ctl, mc->minval, mc->maxval); } #endif } static void uaudio_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1; const struct usb_audio_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).type = MIX_SIGNED_16; if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio20_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2; const struct usb_audio20_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).type = MIX_SIGNED_16; if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio_mixer_check_selectors(struct uaudio_softc *sc) { uint8_t reserve_feature[] = { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, }; const uint16_t reserve_max = sizeof(reserve_feature) / sizeof(reserve_feature[0]); uint16_t i; uint16_t j; uint16_t k; /* remove existing selector types from the reserve */ for (i = 0; i < MIX(sc).maxval; i++) { if (MIX(sc).slctrtype[i] == SOUND_MIXER_NRDEVICES) continue; for (j = 0; j != reserve_max; j++) { if (reserve_feature[j] == MIX(sc).slctrtype[i]) reserve_feature[j] = SOUND_MIXER_NRDEVICES; } } /* make sure selector types are not overlapping */ for (i = 0; i < MIX(sc).maxval; i++) { if (MIX(sc).slctrtype[i] == SOUND_MIXER_NRDEVICES) continue; for (j = i + 1; j < MIX(sc).maxval; j++) { if (MIX(sc).slctrtype[j] == SOUND_MIXER_NRDEVICES) continue; if (MIX(sc).slctrtype[i] != MIX(sc).slctrtype[j]) continue; for (k = 0; k != reserve_max; k++) { if (reserve_feature[k] == SOUND_MIXER_NRDEVICES) continue; MIX(sc).slctrtype[j] = reserve_feature[k]; reserve_feature[k] = SOUND_MIXER_NRDEVICES; break; } if (k == reserve_max) { DPRINTF("Selector type %d is not selectable!\n", j); MIX(sc).slctrtype[j] = SOUND_MIXER_NRDEVICES; } } } } static void uaudio_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_selector_unit *d = iot[id].u.su_v1; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; MIX(sc).mul = MIX(sc).maxval - MIX(sc).minval; for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio_mixer_determine_class(&iot[d->baSourceId[i]]); } for (; i < MAX_SELECTOR_INPUT_PIN; i++) MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; uaudio_mixer_check_selectors(sc); uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio20_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_selector_unit *d = iot[id].u.su_v2; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; MIX(sc).mul = MIX(sc).maxval - MIX(sc).minval; for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio20_mixer_determine_class(&iot[d->baSourceId[i]]); } for (; i < MAX_SELECTOR_INPUT_PIN; i++) MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; uaudio_mixer_check_selectors(sc); uaudio_mixer_add_ctl(sc, &MIX(sc)); } static uint32_t uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d, uint8_t i) { uint32_t temp = 0; uint32_t offset = (i * d->bControlSize); if (d->bControlSize > 0) { temp |= d->bmaControls[offset]; if (d->bControlSize > 1) { temp |= d->bmaControls[offset + 1] << 8; if (d->bControlSize > 2) { temp |= d->bmaControls[offset + 2] << 16; if (d->bControlSize > 3) { temp |= d->bmaControls[offset + 3] << 24; } } } } return (temp); } static void uaudio_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_feature_unit *d = iot[id].u.fu_v1; uint32_t fumask; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t ctl; uint8_t i; if (d->bControlSize == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 7) / d->bControlSize; mmask = uaudio_mixer_feature_get_bmaControls(d, 0); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) { DPRINTFN(10, "chan=%d mask=%x\n", chan, uaudio_mixer_feature_get_bmaControls(d, chan)); cmask |= uaudio_mixer_feature_get_bmaControls(d, chan); } MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[nchan * d->bControlSize]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (nchan > MIX_MAX_CHAN) nchan = MIX_MAX_CHAN; for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) { fumask = FU_MASK(ctl); DPRINTFN(5, "ctl=%d fumask=0x%04x\n", ctl, fumask); if (mmask & fumask) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(ctl, 0); } else if (cmask & fumask) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask) MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } mixernumber = uaudio_mixer_determine_class(&iot[id]); switch (ctl) { case MUTE_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_MUTE; MIX(sc).name = "mute"; break; case VOLUME_CONTROL: MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; break; case BASS_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; break; case MID_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; break; case TREBLE_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; break; case GRAPHIC_EQUALIZER_CONTROL: continue; /* XXX don't add anything */ case AGC_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; break; case DELAY_CONTROL: MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; break; case BASS_BOOST_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; break; case LOUDNESS_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; break; default: MIX(sc).type = MIX_UNKNOWN; break; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio20_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2; uint32_t ctl; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t i; uint8_t what; if (UGETDW(d->bmaControls[0]) == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 6) / 4; mmask = UGETDW(d->bmaControls[0]); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) cmask |= UGETDW(d->bmaControls[chan]); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[nchan][0]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (nchan > MIX_MAX_CHAN) nchan = MIX_MAX_CHAN; for (ctl = 3; ctl != 0; ctl <<= 2) { mixernumber = uaudio20_mixer_determine_class(&iot[id]); switch (ctl) { case (3 << 0): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_MUTE; MIX(sc).name = "mute"; what = MUTE_CONTROL; break; case (3 << 2): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; what = VOLUME_CONTROL; break; case (3 << 4): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; what = BASS_CONTROL; break; case (3 << 6): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; what = MID_CONTROL; break; case (3 << 8): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; what = TREBLE_CONTROL; break; case (3 << 12): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; what = AGC_CONTROL; break; case (3 << 14): MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; what = DELAY_CONTROL; break; case (3 << 16): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; what = BASS_BOOST_CONTROL; break; case (3 << 18): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; what = LOUDNESS_CONTROL; break; case (3 << 20): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igain"; what = INPUT_GAIN_CONTROL; break; case (3 << 22): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igainpad"; what = INPUT_GAIN_PAD_CONTROL; break; default: continue; } if ((mmask & ctl) == ctl) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(what, 0); } else if ((cmask & ctl) == ctl) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if ((UGETDW(d->bmaControls[i]) & ctl) == ctl) MIX(sc).wValue[i - 1] = MAKE_WORD(what, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_add_processing_updown(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); const struct usb_audio_processing_unit_updown *ud = (const void *)(d1->bmControls + d1->bControlSize); uint8_t i; if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) { return; } if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes)) == NULL) { return; } DPRINTFN(3, "bUnitId=%d bNrModes=%d\n", d0->bUnitId, ud->bNrModes); if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { DPRINTF("no mode select\n"); return; } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0); MIX(sc).type = MIX_ON_OFF; /* XXX */ for (i = 0; i < ud->bNrModes; i++) { DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i])); /* XXX */ } uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio_mixer_add_processing(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); uint16_t ptype; memset(&MIX(sc), 0, sizeof(MIX(sc))); ptype = UGETW(d0->wProcessType); DPRINTFN(3, "wProcessType=%d bUnitId=%d " "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins); if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } switch (ptype) { case UPDOWNMIX_PROCESS: uaudio_mixer_add_processing_updown(sc, iot, id); break; case DOLBY_PROLOGIC_PROCESS: case P3D_STEREO_EXTENDER_PROCESS: case REVERBATION_PROCESS: case CHORUS_PROCESS: case DYN_RANGE_COMP_PROCESS: default: DPRINTF("unit %d, type=%d is not implemented\n", d0->bUnitId, ptype); break; } } static void uaudio_mixer_add_extension(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1; const struct usb_audio_extension_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); if (sc->sc_uq_au_no_xu) { return; } if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static const void * uaudio_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio_mixer_unit_1 *d1; const struct usb_audio_extension_unit_1 *e1; const struct usb_audio_processing_unit_1 *u1; union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it; const struct usb_audio_output_terminal *ot; const struct usb_audio_mixer_unit_0 *mu; const struct usb_audio_selector_unit *su; const struct usb_audio_feature_unit *fu; const struct usb_audio_processing_unit_0 *pu; const struct usb_audio_extension_unit_0 *eu; } u; u.desc = arg; if (u.desc == NULL) { goto error; } if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) { goto error; } switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) { goto error; } len += u.mu->bNrInPins; if (u.desc->bLength < len) { goto error; } d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1); break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) { goto error; } len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; if (u.desc->bLength < len) goto error; len += u.fu->bControlSize; break; case UDESCSUB_AC_PROCESSING: len += sizeof(*u.pu); if (u.desc->bLength < len) { goto error; } len += u.pu->bNrInPins; if (u.desc->bLength < len) { goto error; } u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); if (u.desc->bLength < len) { goto error; } len += u1->bControlSize; break; case UDESCSUB_AC_EXTENSION: len += sizeof(*u.eu); if (u.desc->bLength < len) { goto error; } len += u.eu->bNrInPins; if (u.desc->bLength < len) { goto error; } e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); if (u.desc->bLength < len) { goto error; } len += e1->bControlSize; break; default: goto error; } if (u.desc->bLength < len) { goto error; } return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static const void * uaudio20_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio20_mixer_unit_1 *d1; const struct usb_audio20_extension_unit_1 *e1; const struct usb_audio20_processing_unit_1 *u1; const struct usb_audio20_clock_selector_unit_1 *c1; union { const struct usb_descriptor *desc; const struct usb_audio20_clock_source_unit *csrc; const struct usb_audio20_clock_selector_unit_0 *csel; const struct usb_audio20_clock_multiplier_unit *cmul; const struct usb_audio20_input_terminal *it; const struct usb_audio20_output_terminal *ot; const struct usb_audio20_mixer_unit_0 *mu; const struct usb_audio20_selector_unit *su; const struct usb_audio20_feature_unit *fu; const struct usb_audio20_sample_rate_unit *ru; const struct usb_audio20_processing_unit_0 *pu; const struct usb_audio20_extension_unit_0 *eu; const struct usb_audio20_effect_unit *ef; } u; u.desc = arg; if (u.desc == NULL) goto error; if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) goto error; switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) goto error; len += u.mu->bNrInPins; if (u.desc->bLength < len) goto error; d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1) + d1->bNrChannels; break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) goto error; len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; break; case UDESCSUB_AC_EFFECT: len += sizeof(*u.ef) + 4; break; case UDESCSUB_AC_PROCESSING_V2: len += sizeof(*u.pu); if (u.desc->bLength < len) goto error; len += u.pu->bNrInPins; if (u.desc->bLength < len) goto error; u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); break; case UDESCSUB_AC_EXTENSION_V2: len += sizeof(*u.eu); if (u.desc->bLength < len) goto error; len += u.eu->bNrInPins; if (u.desc->bLength < len) goto error; e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); break; case UDESCSUB_AC_CLOCK_SRC: len += sizeof(*u.csrc); break; case UDESCSUB_AC_CLOCK_SEL: len += sizeof(*u.csel); if (u.desc->bLength < len) goto error; len += u.csel->bNrInPins; if (u.desc->bLength < len) goto error; c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins); len += sizeof(*c1); break; case UDESCSUB_AC_CLOCK_MUL: len += sizeof(*u.cmul); break; case UDESCSUB_AC_SAMPLE_RT: len += sizeof(*u.ru); break; default: goto error; } if (u.desc->bLength < len) goto error; return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) { goto error; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v1->bNrChannels; r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0]; r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1]; r.iChannelNames = iot[id].u.it_v1->iChannelNames; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v1->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio_cluster *) &iot[id].u.mu_v1->baSourceId[ iot[id].u.mu_v1->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v1->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v1->baSourceId[0]; } break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v1->bSourceId; break; case UDESCSUB_AC_PROCESSING: r = *((const struct usb_audio_cluster *) &iot[id].u.pu_v1->baSourceId[ iot[id].u.pu_v1->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION: r = *((const struct usb_audio_cluster *) &iot[id].u.eu_v1->baSourceId[ iot[id].u.eu_v1->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("bad data\n"); memset(&r, 0, sizeof(r)); done: return (r); } static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio20_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) goto error; switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v2->bNrChannels; r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0]; r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1]; r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2]; r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3]; r.iChannelNames = iot[id].u.it_v2->iTerminal; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v2->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio20_cluster *) &iot[id].u.mu_v2->baSourceId[ iot[id].u.mu_v2->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v2->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v2->baSourceId[0]; } break; case UDESCSUB_AC_SAMPLE_RT: id = iot[id].u.ru_v2->bSourceId; break; case UDESCSUB_AC_EFFECT: id = iot[id].u.ef_v2->bSourceId; break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v2->bSourceId; break; case UDESCSUB_AC_PROCESSING_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.pu_v2->baSourceId[ iot[id].u.pu_v2->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.eu_v2->baSourceId[ iot[id].u.eu_v2->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("Bad data!\n"); memset(&r, 0, sizeof(r)); done: return (r); } static bool uaudio_mixer_foreach_input(const struct uaudio_terminal_node *iot, uint8_t *pindex) { uint8_t n; n = *pindex; while (1) { if (!n--) n = iot->usr.id_max; if (n == 0) return (false); if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) break; } *pindex = n; return (true); } static bool uaudio_mixer_foreach_output(const struct uaudio_terminal_node *iot, uint8_t *pindex) { uint8_t n; n = *pindex; while (1) { if (!n--) n = iot->usr.id_max; if (n == 0) return (false); if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) break; } *pindex = n; return (true); } struct uaudio_tt_to_feature { uint16_t terminal_type; uint16_t feature; }; static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = { {UATI_MICROPHONE, SOUND_MIXER_MIC}, {UATI_DESKMICROPHONE, SOUND_MIXER_MIC}, {UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC}, {UATI_OMNIMICROPHONE, SOUND_MIXER_MIC}, {UATI_MICROPHONEARRAY, SOUND_MIXER_MIC}, {UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC}, {UATE_ANALOGCONN, SOUND_MIXER_LINE}, {UATE_LINECONN, SOUND_MIXER_LINE}, {UATE_LEGACYCONN, SOUND_MIXER_LINE}, {UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM}, {UATE_SPDIF, SOUND_MIXER_ALTPCM}, {UATE_1394DA, SOUND_MIXER_ALTPCM}, {UATE_1394DV, SOUND_MIXER_ALTPCM}, {UATF_CDPLAYER, SOUND_MIXER_CD}, {UATF_SYNTHESIZER, SOUND_MIXER_SYNTH}, {UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO}, {UATF_DVDAUDIO, SOUND_MIXER_VIDEO}, {UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO}, {UATF_RADIORECV, SOUND_MIXER_RADIO}, {UATF_RADIOXMIT, SOUND_MIXER_RADIO}, {} /* END */ }; static uint16_t uaudio_mixer_get_feature_by_tt(uint16_t terminal_type, uint16_t default_type) { const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature; uint16_t retval; if (terminal_type == 0) { retval = default_type; } else while (1) { if (uat->terminal_type == 0) { switch (terminal_type >> 8) { case UATI_UNDEFINED >> 8: retval = SOUND_MIXER_RECLEV; goto done; case UATO_UNDEFINED >> 8: retval = SOUND_MIXER_PCM; goto done; case UATT_UNDEFINED >> 8: retval = SOUND_MIXER_PHONEIN; goto done; default: retval = default_type; goto done; } } else if (uat->terminal_type == terminal_type) { retval = uat->feature; goto done; } uat++; } done: DPRINTF("terminal_type=0x%04x RET=%d DEF=%d\n", terminal_type, retval, default_type); return (retval); } static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot) { const struct uaudio_terminal_node *ptr; uint16_t terminal_type_input = 0; uint16_t terminal_type_output = 0; uint16_t temp; uint8_t match = 0; uint8_t i; for (i = 0; uaudio_mixer_foreach_input(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.it_v1->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 1; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_input = temp; } for (i = 0; uaudio_mixer_foreach_output(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.ot_v1->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 2; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_output = temp; } DPRINTF("MATCH=%d IN=0x%04x OUT=0x%04x\n", match, terminal_type_input, terminal_type_output); switch (match) { case 0: /* not connected to USB */ if (terminal_type_output != 0) { return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_MONITOR)); } else { return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_MONITOR)); } case 3: /* connected to both USB input and USB output */ return (SOUND_MIXER_IMIX); case 2: /* connected to USB output */ return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_RECLEV)); case 1: /* connected to USB input */ return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_PCM)); default: return (SOUND_MIXER_NRDEVICES); } } static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot) { const struct uaudio_terminal_node *ptr; uint16_t terminal_type_input = 0; uint16_t terminal_type_output = 0; uint16_t temp; uint8_t match = 0; uint8_t i; for (i = 0; uaudio_mixer_foreach_input(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.it_v2->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 1; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_input = temp; } for (i = 0; uaudio_mixer_foreach_output(iot, &i); ) { ptr = iot->root + i; temp = UGETW(ptr->u.ot_v2->wTerminalType); if (temp == 0) continue; else if (temp == UAT_STREAM) match |= 2; else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00)) terminal_type_output = temp; } DPRINTF("MATCH=%d IN=0x%04x OUT=0x%04x\n", match, terminal_type_input, terminal_type_output); switch (match) { case 0: /* not connected to USB */ if (terminal_type_output != 0) { return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_MONITOR)); } else { return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_MONITOR)); } case 3: /* connected to both USB input and USB output */ return (SOUND_MIXER_IMIX); case 2: /* connected to USB output */ return (uaudio_mixer_get_feature_by_tt( terminal_type_input, SOUND_MIXER_RECLEV)); case 1: /* connected to USB input */ return (uaudio_mixer_get_feature_by_tt( terminal_type_output, SOUND_MIXER_PCM)); default: return (SOUND_MIXER_NRDEVICES); } } static void uaudio_mixer_merge_outputs(struct uaudio_search_result *dst, const struct uaudio_search_result *src) { const uint8_t max = sizeof(src->bit_output) / sizeof(src->bit_output[0]); uint8_t x; for (x = 0; x != max; x++) dst->bit_output[x] |= src->bit_output[x]; } static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: uaudio_mixer_merge_outputs(&iot->usr, info); info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, &iot->u.fu_v1->bSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: info->bit_output[i / 8] |= (1 << (i % 8)); uaudio_mixer_find_inputs_sub( root, &iot->u.ot_v1->bSourceId, 1, info); info->bit_output[i / 8] &= ~(1 << (i % 8)); break; case UDESCSUB_AC_MIXER: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.mu_v1->baSourceId, iot->u.mu_v1->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.su_v1->baSourceId, iot->u.su_v1->bNrInPins, info); break; case UDESCSUB_AC_PROCESSING: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.pu_v1->baSourceId, iot->u.pu_v1->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio_mixer_find_inputs_sub( root, iot->u.eu_v1->baSourceId, iot->u.eu_v1->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: uaudio_mixer_merge_outputs(&iot->usr, info); info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_OUTPUT: info->bit_output[i / 8] |= (1 << (i % 8)); uaudio20_mixer_find_inputs_sub( root, &iot->u.ot_v2->bSourceId, 1, info); info->bit_output[i / 8] &= ~(1 << (i % 8)); break; case UDESCSUB_AC_MIXER: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.mu_v2->baSourceId, iot->u.mu_v2->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.su_v2->baSourceId, iot->u.su_v2->bNrInPins, info); break; case UDESCSUB_AC_SAMPLE_RT: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, &iot->u.ru_v2->bSourceId, 1, info); break; case UDESCSUB_AC_EFFECT: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, &iot->u.ef_v2->bSourceId, 1, info); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, &iot->u.fu_v2->bSourceId, 1, info); break; case UDESCSUB_AC_PROCESSING_V2: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.pu_v2->baSourceId, iot->u.pu_v2->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION_V2: uaudio_mixer_merge_outputs(&iot->usr, info); uaudio20_mixer_find_inputs_sub( root, iot->u.eu_v2->baSourceId, iot->u.eu_v2->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; uint8_t id; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->is_input = 1; if (is_last) { p_id = &iot->u.it_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.it_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: info->is_input = 0; if (is_last) { p_id = &iot->u.ot_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.ot_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SEL: if (is_last) { p_id = iot->u.csel_v2->baCSourceId; n_id = iot->u.csel_v2->bNrInPins; goto top; } uaudio20_mixer_find_clocks_sub(root, iot->u.csel_v2->baCSourceId, iot->u.csel_v2->bNrInPins, info); break; case UDESCSUB_AC_CLOCK_MUL: if (is_last) { p_id = &iot->u.cmul_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.cmul_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SRC: id = iot->u.csrc_v2->bClockId; switch (info->is_input) { case 0: info->bit_output[id / 8] |= (1 << (id % 8)); break; case 1: info->bit_input[id / 8] |= (1 << (id % 8)); break; default: break; } break; default: break; } } } static void uaudio_mixer_fill_info(struct uaudio_softc *sc, struct usb_device *udev, void *desc) { const struct usb_audio_control_descriptor *acdp; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); const struct usb_descriptor *dp; const struct usb_audio_unit *au; struct uaudio_terminal_node *iot = NULL; uint16_t wTotalLen; uint8_t ID_max = 0; /* inclusive */ uint8_t i; desc = usb_desc_foreach(cd, desc); if (desc == NULL) { DPRINTF("no Audio Control header\n"); goto done; } acdp = desc; if ((acdp->bLength < sizeof(*acdp)) || (acdp->bDescriptorType != UDESC_CS_INTERFACE) || (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) { DPRINTF("invalid Audio Control header\n"); goto done; } /* "wTotalLen" is allowed to be corrupt */ wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength; /* get USB audio revision */ sc->sc_audio_rev = UGETW(acdp->bcdADC); DPRINTFN(3, "found AC header, vers=%03x, len=%d\n", sc->sc_audio_rev, wTotalLen); iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP, M_WAITOK | M_ZERO); while ((desc = usb_desc_foreach(cd, desc))) { dp = desc; if (dp->bLength > wTotalLen) { break; } else { wTotalLen -= dp->bLength; } if (sc->sc_audio_rev >= UAUDIO_VERSION_30) au = NULL; else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) au = uaudio20_mixer_verify_desc(dp, 0); else au = uaudio_mixer_verify_desc(dp, 0); if (au) { iot[au->bUnitId].u.desc = (const void *)au; if (au->bUnitId > ID_max) ID_max = au->bUnitId; } } DPRINTF("Maximum ID=%d\n", ID_max); /* * determine sourcing inputs for * all nodes in the tree: */ i = ID_max; do { if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { uaudio20_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); sc->sc_mixer_clocks.is_input = 255; sc->sc_mixer_clocks.recurse_level = 0; uaudio20_mixer_find_clocks_sub(iot, &i, 1, &sc->sc_mixer_clocks); } else { uaudio_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); } } while (i--); /* set "id_max" and "root" */ i = ID_max; do { (iot + i)->usr.id_max = ID_max; (iot + i)->root = iot; } while (i--); /* * Scan the config to create a linked list of "mixer" nodes: */ i = ID_max; do { dp = iot[i].u.desc; if (dp == NULL) continue; DPRINTFN(11, "id=%d subtype=%d\n", i, dp->bDescriptorSubtype); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { continue; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: case UDESCSUB_AC_PROCESSING_V2: case UDESCSUB_AC_EXTENSION_V2: case UDESCSUB_AC_EFFECT: case UDESCSUB_AC_CLOCK_SRC: case UDESCSUB_AC_CLOCK_SEL: case UDESCSUB_AC_CLOCK_MUL: case UDESCSUB_AC_SAMPLE_RT: break; case UDESCSUB_AC_MIXER: uaudio20_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio20_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio20_mixer_add_feature(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } continue; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: break; case UDESCSUB_AC_MIXER: uaudio_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_add_feature(sc, iot, i); break; case UDESCSUB_AC_PROCESSING: uaudio_mixer_add_processing(sc, iot, i); break; case UDESCSUB_AC_EXTENSION: uaudio_mixer_add_extension(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } } while (i--); done: free(iot, M_TEMP); } static int uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev, uint8_t what, struct uaudio_mixer_node *mc) { struct usb_device_request req; int val; uint8_t data[2 + (2 * 3)]; usb_error_t err; if (mc->wValue[0] == -1) return (0); if (audio_rev >= UAUDIO_VERSION_30) return (0); else if (audio_rev >= UAUDIO_VERSION_20) { if (what == GET_CUR) { req.bRequest = UA20_CS_CUR; USETW(req.wLength, 2); } else { req.bRequest = UA20_CS_RANGE; USETW(req.wLength, 8); } } else { uint16_t len = MIX_SIZE(mc->type); req.bRequest = what; USETW(req.wLength, len); } req.bmRequestType = UT_READ_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[0]); USETW(req.wIndex, mc->wIndex); memset(data, 0, sizeof(data)); err = usbd_do_request(udev, NULL, &req, data); if (err) { DPRINTF("err=%s\n", usbd_errstr(err)); return (0); } if (audio_rev >= UAUDIO_VERSION_30) { val = 0; } else if (audio_rev >= UAUDIO_VERSION_20) { switch (what) { case GET_CUR: val = (data[0] | (data[1] << 8)); break; case GET_MIN: val = (data[2] | (data[3] << 8)); break; case GET_MAX: val = (data[4] | (data[5] << 8)); break; case GET_RES: val = (data[6] | (data[7] << 8)); break; default: val = 0; break; } } else { val = (data[0] | (data[1] << 8)); } if (what == GET_CUR || what == GET_MIN || what == GET_MAX) val = uaudio_mixer_signext(mc->type, val); DPRINTFN(3, "val=%d\n", val); return (val); } static void uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_device_request req; struct uaudio_softc *sc = usbd_xfer_softc(xfer); struct uaudio_mixer_node *mc = sc->sc_mixer_curr; struct usb_page_cache *pc; uint16_t len; uint8_t repeat = 1; uint8_t update; uint8_t chan; uint8_t buf[2]; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: case USB_ST_SETUP: tr_setup: if (mc == NULL) { mc = sc->sc_mixer_root; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; repeat = 0; } while (mc) { while (sc->sc_mixer_chan < mc->nchan) { chan = sc->sc_mixer_chan; sc->sc_mixer_chan++; update = ((mc->update[chan / 8] & (1 << (chan % 8))) && (mc->wValue[chan] != -1)); mc->update[chan / 8] &= ~(1 << (chan % 8)); if (update) { req.bmRequestType = UT_WRITE_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[chan]); USETW(req.wIndex, mc->wIndex); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { return; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { len = 2; req.bRequest = UA20_CS_CUR; USETW(req.wLength, len); } else { len = MIX_SIZE(mc->type); req.bRequest = SET_CUR; USETW(req.wLength, len); } buf[0] = (mc->wData[chan] & 0xFF); buf[1] = (mc->wData[chan] >> 8) & 0xFF; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, buf, len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, len); usbd_xfer_set_frames(xfer, len ? 2 : 1); usbd_transfer_submit(xfer); return; } } mc = mc->next; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; } if (repeat) { goto tr_setup; } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) { /* do nothing - we are detaching */ break; } goto tr_transferred; } } static usb_error_t uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed) { struct usb_device_request req; uint8_t data[3]; DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed); req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; req.bRequest = SET_CUR; USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0); USETW(req.wIndex, endpt); USETW(req.wLength, 3); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; return (usbd_do_request(udev, NULL, &req, data)); } static usb_error_t uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t speed) { struct usb_device_request req; uint8_t data[4]; DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n", iface_no, clockid, speed); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UA20_CS_CUR; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); USETW(req.wLength, 4); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; data[3] = speed >> 24; return (usbd_do_request(udev, NULL, &req, data)); } static int uaudio_mixer_signext(uint8_t type, int val) { if (!MIX_UNSIGNED(type)) { if (MIX_SIZE(type) == 2) { val = (int16_t)val; } else { val = (int8_t)val; } } return (val); } static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int val) { if (mc->type == MIX_ON_OFF) { val = (val != 0); } else if (mc->type != MIX_SELECTOR) { /* compute actual volume */ val = (val * mc->mul) / 100; /* add lower offset */ val = val + mc->minval; } /* make sure we don't write a value out of range */ if (val > mc->maxval) val = mc->maxval; else if (val < mc->minval) val = mc->minval; DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n", mc->type, val, mc->minval, mc->maxval, val); return (val); } static void uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc, uint8_t chan, int val) { val = uaudio_mixer_bsd2value(mc, val); mc->update[chan / 8] |= (1 << (chan % 8)); mc->wData[chan] = val; /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } static void uaudio_mixer_init(struct uaudio_softc *sc, unsigned index) { struct uaudio_mixer_node *mc; int32_t i; if (index != 0) return; for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if (mc->ctl != SOUND_MIXER_NRDEVICES) { /* * Set device mask bits. See * /usr/include/machine/soundcard.h */ sc->sc_child[index].mix_info |= 1U << mc->ctl; } if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) continue; sc->sc_child[index].recsrc_info |= 1U << mc->slctrtype[i - 1]; } } } } int uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m) { unsigned i = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); DPRINTF("child=%u\n", i); sc->sc_child[i].mixer_lock = mixer_get_lock(m); sc->sc_child[i].mixer_dev = m; if (i == 0 && usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index, sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc, sc->sc_child[i].mixer_lock)) { DPRINTFN(0, "could not allocate USB transfer for mixer!\n"); return (ENOMEM); } if (sc->sc_play_chan[i].num_alt > 0 && (sc->sc_child[i].mix_info & SOUND_MASK_VOLUME) == 0) { mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); } mix_setdevs(m, sc->sc_child[i].mix_info); mix_setrecdevs(m, sc->sc_child[i].recsrc_info); return (0); } int uaudio_mixer_uninit_sub(struct uaudio_softc *sc, struct snd_mixer *m) { unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); DPRINTF("child=%u\n", index); if (index == 0) usbd_transfer_unsetup(sc->sc_mixer_xfer, 1); sc->sc_child[index].mixer_lock = NULL; return (0); } void uaudio_mixer_set(struct uaudio_softc *sc, struct snd_mixer *m, unsigned type, unsigned left, unsigned right) { unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); struct uaudio_mixer_node *mc; int chan; if (index != 0) return; for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) { if (mc->ctl == type) { for (chan = 0; chan < mc->nchan; chan++) { uaudio_mixer_ctl_set(sc, mc, chan, chan == 0 ? left : right); } } } } uint32_t uaudio_mixer_setrecsrc(struct uaudio_softc *sc, struct snd_mixer *m, uint32_t src) { unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m)); struct uaudio_mixer_node *mc; uint32_t mask; uint32_t temp; int32_t i; if (index != 0) return (0); for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { /* compute selector mask */ mask = 0; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) mask |= 1U << mc->slctrtype[i - 1]; temp = mask & src; if (temp == 0) continue; /* find the first set bit */ temp = (-temp) & temp; /* update "src" */ src &= ~mask; src |= temp; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (temp != (1U << mc->slctrtype[i - 1])) continue; uaudio_mixer_ctl_set(sc, mc, 0, i); break; } } } return (src); } /*========================================================================* * MIDI support routines *========================================================================*/ static void umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint8_t buf[4]; uint8_t cmd_len; uint8_t cn; uint16_t pos; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", actlen); pos = 0; pc = usbd_xfer_get_frame(xfer, 0); while (actlen >= 4) { /* copy out the MIDI data */ usbd_copy_out(pc, pos, buf, 4); /* command length */ cmd_len = umidi_cmd_to_len[buf[0] & 0xF]; /* cable number */ cn = buf[0] >> 4; /* * Lookup sub-channel. The index is range * checked below. */ sub = &chan->sub[cn]; if ((cmd_len != 0) && (cn < chan->max_emb_jack) && (sub->read_open != 0)) { /* Send data to the application */ usb_fifo_put_data_linear( sub->fifo.fp[USB_FIFO_RX], buf + 1, cmd_len, 1); } actlen -= 4; pos += 4; } case USB_ST_SETUP: DPRINTF("start\n"); tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } /* * The following statemachine, that converts MIDI commands to * USB MIDI packets, derives from Linux's usbmidi.c, which * was written by "Clemens Ladisch": * * Returns: * 0: No command * Else: Command is complete */ static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b) { uint8_t p0 = (cn << 4); if (b >= 0xf8) { sub->temp_0[0] = p0 | 0x0f; sub->temp_0[1] = b; sub->temp_0[2] = 0; sub->temp_0[3] = 0; sub->temp_cmd = sub->temp_0; return (1); } else if (b >= 0xf0) { switch (b) { case 0xf0: /* system exclusive begin */ sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case 0xf1: /* MIDI time code */ case 0xf3: /* song select */ sub->temp_1[1] = b; sub->state = UMIDI_ST_1PARAM; break; case 0xf2: /* song position pointer */ sub->temp_1[1] = b; sub->state = UMIDI_ST_2PARAM_1; break; case 0xf4: /* unknown */ case 0xf5: /* unknown */ sub->state = UMIDI_ST_UNKNOWN; break; case 0xf6: /* tune request */ sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf6; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case 0xf7: /* system exclusive end */ switch (sub->state) { case UMIDI_ST_SYSEX_0: sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf7; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_1: sub->temp_1[0] = p0 | 0x06; sub->temp_1[2] = 0xf7; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x07; sub->temp_1[3] = 0xf7; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); } sub->state = UMIDI_ST_UNKNOWN; break; } } else if (b >= 0x80) { sub->temp_1[1] = b; if ((b >= 0xc0) && (b <= 0xdf)) { sub->state = UMIDI_ST_1PARAM; } else { sub->state = UMIDI_ST_2PARAM_1; } } else { /* b < 0x80 */ switch (sub->state) { case UMIDI_ST_1PARAM: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; } else { p0 |= 0x02; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[2] = b; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_2PARAM_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_2PARAM_2; break; case UMIDI_ST_2PARAM_2: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; sub->state = UMIDI_ST_2PARAM_1; } else { p0 |= 0x03; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_SYSEX_0: sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case UMIDI_ST_SYSEX_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_SYSEX_2; break; case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x04; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_SYSEX_0; return (1); default: break; } } return (0); } static void umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint32_t actlen; uint16_t nframes; uint8_t buf; uint8_t start_cable; uint8_t tr_any; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); /* * NOTE: Some MIDI devices only accept 4 bytes of data per * short terminated USB transfer. */ switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", len); case USB_ST_SETUP: tr_setup: DPRINTF("start\n"); nframes = 0; /* reset */ start_cable = chan->curr_cable; tr_any = 0; pc = usbd_xfer_get_frame(xfer, 0); while (1) { /* round robin de-queueing */ sub = &chan->sub[chan->curr_cable]; if (sub->write_open) { usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX], &buf, 1, &actlen, 0); } else { actlen = 0; } if (actlen) { tr_any = 1; DPRINTF("byte=0x%02x from FIFO %u\n", buf, (unsigned int)chan->curr_cable); if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) { DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n", sub->temp_cmd[0], sub->temp_cmd[1], sub->temp_cmd[2], sub->temp_cmd[3]); usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4); nframes++; if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0)) break; } else { continue; } } chan->curr_cable++; if (chan->curr_cable >= chan->max_emb_jack) chan->curr_cable = 0; if (chan->curr_cable == start_cable) { if (tr_any == 0) break; tr_any = 0; } } if (nframes != 0) { DPRINTF("Transferring %d frames\n", (int)nframes); usbd_xfer_set_frame_len(xfer, 0, 4 * nframes); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static struct umidi_sub_chan * umidi_sub_by_fifo(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) { sub = &chan->sub[n]; if ((sub->fifo.fp[USB_FIFO_RX] == fifo) || (sub->fifo.fp[USB_FIFO_TX] == fifo)) { return (sub); } } panic("%s:%d cannot find usb_fifo!\n", __FILE__, __LINE__); return (NULL); } static void umidi_start_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); } static void umidi_stop_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->read_open = 0; if (--(chan->read_open_refcount) == 0) { /* * XXX don't stop the read transfer here, hence that causes * problems with some MIDI adapters */ DPRINTF("(stopping read transfer)\n"); } } static void umidi_start_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); if (chan->xfer[UMIDI_TX_TRANSFER] == NULL) { uint8_t buf[1]; int actlen; do { /* dump data */ usb_fifo_get_data_linear(fifo, buf, 1, &actlen, 0); } while (actlen > 0); } else { usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]); } } static void umidi_stop_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->write_open = 0; if (--(chan->write_open_refcount) == 0) { DPRINTF("(stopping write transfer)\n"); usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]); } } static int umidi_open(struct usb_fifo *fifo, int fflags) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); if (fflags & FREAD) { if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) { return (ENOMEM); } mtx_lock(&chan->mtx); chan->read_open_refcount++; sub->read_open = 1; mtx_unlock(&chan->mtx); } if (fflags & FWRITE) { if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) { return (ENOMEM); } /* clear stall first */ mtx_lock(&chan->mtx); chan->write_open_refcount++; sub->write_open = 1; /* reset */ sub->state = UMIDI_ST_UNKNOWN; mtx_unlock(&chan->mtx); } return (0); /* success */ } static void umidi_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { usb_fifo_free_buffer(fifo); } if (fflags & FWRITE) { usb_fifo_free_buffer(fifo); } } static int umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags) { return (ENODEV); } static void umidi_init(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE); } static struct usb_fifo_methods umidi_fifo_methods = { .f_start_read = &umidi_start_read, .f_start_write = &umidi_start_write, .f_stop_read = &umidi_stop_read, .f_stop_write = &umidi_stop_write, .f_open = &umidi_open, .f_close = &umidi_close, .f_ioctl = &umidi_ioctl, .basename[0] = "umidi", }; static int umidi_probe(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct umidi_chan *chan = &sc->sc_midi_chan; struct umidi_sub_chan *sub; int unit = device_get_unit(dev); int error; uint32_t n; if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI)) chan->single_command = 1; error = usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, chan->iface_alt_index); if (error) { DPRINTF("setting of alternate index failed: %s\n", usbd_errstr(error)); goto detach; } usbd_set_parent_iface(sc->sc_udev, chan->iface_index, sc->sc_mixer_iface_index); error = usbd_transfer_setup(uaa->device, &chan->iface_index, chan->xfer, umidi_config, UMIDI_N_TRANSFER, chan, &chan->mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } if (chan->xfer[UMIDI_TX_TRANSFER] == NULL && chan->xfer[UMIDI_RX_TRANSFER] == NULL) { DPRINTF("no BULK or INTERRUPT MIDI endpoint(s) found\n"); goto detach; } /* * Some USB MIDI device makers couldn't resist using * wMaxPacketSize = 4 for RX and TX BULK endpoints, although * that size is an unsupported value for FULL speed BULK * endpoints. The same applies to some HIGH speed MIDI devices * which are using a wMaxPacketSize different from 512 bytes. * * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host * Controllers are required to have support for 8-, 16-, 32-, * and 64-byte maximum packet sizes for full-speed bulk * endpoints and 512 bytes for high-speed bulk endpoints." */ if (chan->xfer[UMIDI_TX_TRANSFER] != NULL && usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER])) chan->single_command = 1; if (chan->single_command != 0) device_printf(dev, "Single command MIDI quirk enabled\n"); if ((chan->max_emb_jack == 0) || (chan->max_emb_jack > UMIDI_EMB_JACK_MAX)) { chan->max_emb_jack = UMIDI_EMB_JACK_MAX; } for (n = 0; n < chan->max_emb_jack; n++) { sub = &chan->sub[n]; error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx, &umidi_fifo_methods, &sub->fifo, unit, n, chan->iface_index, UID_ROOT, GID_OPERATOR, 0666); if (error) { goto detach; } } mtx_lock(&chan->mtx); /* * NOTE: At least one device will not work properly unless the * BULK IN pipe is open all the time. This might have to do * about that the internal queues of the device overflow if we * don't read them regularly. */ usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); return (0); /* success */ detach: return (ENXIO); /* failure */ } static int umidi_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) usb_fifo_detach(&chan->sub[n].fifo); mtx_lock(&chan->mtx); usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER); mtx_destroy(&chan->mtx); return (0); } static void uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_softc *sc = usbd_xfer_softc(xfer); const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0); struct snd_mixer *m; uint8_t id; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d\n", actlen); if (actlen != 0 && (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) { id = *buffer; buffer++; actlen--; } else { id = 0; } m = sc->sc_child[0].mixer_dev; if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) && (sc->sc_hid.mute_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.mute_loc)) { DPRINTF("Mute toggle\n"); mixer_hwvol_mute_locked(m); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) && (sc->sc_hid.volume_up_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_up_loc)) { DPRINTF("Volume Up\n"); mixer_hwvol_step_locked(m, 1, 1); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) && (sc->sc_hid.volume_down_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_down_loc)) { DPRINTF("Volume Down\n"); mixer_hwvol_step_locked(m, -1, -1); } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa) { void *d_ptr; uint32_t flags; uint16_t d_len; uint8_t id; int error; if (!(sc->sc_hid.flags & UAUDIO_HID_VALID)) return (-1); if (sc->sc_child[0].mixer_lock == NULL) return (-1); /* Get HID descriptor */ error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, sc->sc_hid.iface_index); if (error) { DPRINTF("error reading report description\n"); return (-1); } /* check if there is an ID byte */ hid_report_size_max(d_ptr, d_len, hid_input, &id); if (id != 0) sc->sc_hid.flags |= UAUDIO_HID_HAS_ID; if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */), hid_input, 0, &sc->sc_hid.volume_up_loc, &flags, &sc->sc_hid.volume_up_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP; DPRINTFN(1, "Found Volume Up key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */), hid_input, 0, &sc->sc_hid.volume_down_loc, &flags, &sc->sc_hid.volume_down_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN; DPRINTFN(1, "Found Volume Down key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */), hid_input, 0, &sc->sc_hid.mute_loc, &flags, &sc->sc_hid.mute_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE; DPRINTFN(1, "Found Mute key\n"); } free(d_ptr, M_TEMP); if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP | UAUDIO_HID_HAS_VOLUME_DOWN | UAUDIO_HID_HAS_MUTE))) { DPRINTFN(1, "Did not find any volume related keys\n"); return (-1); } /* prevent the uhid driver from attaching */ usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index, sc->sc_mixer_iface_index); /* allocate USB transfers */ error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index, sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER, sc, sc->sc_child[0].mixer_lock); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); return (-1); } return (0); } static void uaudio_hid_detach(struct uaudio_softc *sc) { usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER); } DRIVER_MODULE_ORDERED(snd_uaudio, uhub, uaudio_driver, NULL, NULL, SI_ORDER_ANY); MODULE_DEPEND(snd_uaudio, usb, 1, 1, 1); MODULE_DEPEND(snd_uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_uaudio, hid, 1, 1, 1); MODULE_VERSION(snd_uaudio, 1); USB_PNP_HOST_INFO(uaudio_devs); USB_PNP_HOST_INFO(uaudio_vendor_midi); diff --git a/sys/dev/sound/usb/uaudio_pcm.c b/sys/dev/sound/usb/uaudio_pcm.c index 9b17cb232907..0b3da9b20440 100644 --- a/sys/dev/sound/usb/uaudio_pcm.c +++ b/sys/dev/sound/usb/uaudio_pcm.c @@ -1,240 +1,239 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000-2002 Hiroyuki Aizu * Copyright (c) 2006 Hans Petter Selasky * * 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 "mixer_if.h" /************************************************************/ static void * ua_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { return (uaudio_chan_init(devinfo, b, c, dir)); } static int ua_chan_free(kobj_t obj, void *data) { return (uaudio_chan_free(data)); } static int ua_chan_setformat(kobj_t obj, void *data, uint32_t format) { /* * At this point, no need to query as we * shouldn't select an unsorted format */ return (uaudio_chan_set_param_format(data, format)); } static uint32_t ua_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { return (uaudio_chan_set_param_speed(data, speed)); } static uint32_t ua_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { return (uaudio_chan_set_param_blocksize(data, blocksize)); } static int ua_chan_setfragments(kobj_t obj, void *data, uint32_t blocksize, uint32_t blockcount) { return (uaudio_chan_set_param_fragments(data, blocksize, blockcount)); } static int ua_chan_trigger(kobj_t obj, void *data, int go) { if (PCMTRIG_COMMON(go)) { if (go == PCMTRIG_START) { uaudio_chan_start(data); } else { uaudio_chan_stop(data); } } return (0); } static uint32_t ua_chan_getptr(kobj_t obj, void *data) { return (uaudio_chan_getptr(data)); } static struct pcmchan_caps * ua_chan_getcaps(kobj_t obj, void *data) { return (uaudio_chan_getcaps(data)); } static struct pcmchan_matrix * ua_chan_getmatrix(kobj_t obj, void *data, uint32_t format) { return (uaudio_chan_getmatrix(data, format)); } static kobj_method_t ua_chan_methods[] = { KOBJMETHOD(channel_init, ua_chan_init), KOBJMETHOD(channel_free, ua_chan_free), KOBJMETHOD(channel_setformat, ua_chan_setformat), KOBJMETHOD(channel_setspeed, ua_chan_setspeed), KOBJMETHOD(channel_setblocksize, ua_chan_setblocksize), KOBJMETHOD(channel_setfragments, ua_chan_setfragments), KOBJMETHOD(channel_trigger, ua_chan_trigger), KOBJMETHOD(channel_getptr, ua_chan_getptr), KOBJMETHOD(channel_getcaps, ua_chan_getcaps), KOBJMETHOD(channel_getmatrix, ua_chan_getmatrix), KOBJMETHOD_END }; CHANNEL_DECLARE(ua_chan); /************************************************************/ static int ua_mixer_init(struct snd_mixer *m) { return (uaudio_mixer_init_sub(mix_getdevinfo(m), m)); } static int ua_mixer_set(struct snd_mixer *m, unsigned type, unsigned left, unsigned right) { struct mtx *mtx = mixer_get_lock(m); uint8_t do_unlock; if (mtx_owned(mtx)) { do_unlock = 0; } else { do_unlock = 1; mtx_lock(mtx); } uaudio_mixer_set(mix_getdevinfo(m), m, type, left, right); if (do_unlock) { mtx_unlock(mtx); } return (left | (right << 8)); } static uint32_t ua_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct mtx *mtx = mixer_get_lock(m); int retval; uint8_t do_unlock; if (mtx_owned(mtx)) { do_unlock = 0; } else { do_unlock = 1; mtx_lock(mtx); } retval = uaudio_mixer_setrecsrc(mix_getdevinfo(m), m, src); if (do_unlock) { mtx_unlock(mtx); } return (retval); } static int ua_mixer_uninit(struct snd_mixer *m) { return (uaudio_mixer_uninit_sub(mix_getdevinfo(m), m)); } static kobj_method_t ua_mixer_methods[] = { KOBJMETHOD(mixer_init, ua_mixer_init), KOBJMETHOD(mixer_uninit, ua_mixer_uninit), KOBJMETHOD(mixer_set, ua_mixer_set), KOBJMETHOD(mixer_setrecsrc, ua_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(ua_mixer); /************************************************************/ static int ua_probe(device_t dev) { struct sndcard_func *func; /* the parent device has already been probed */ func = device_get_ivars(dev); if ((func == NULL) || (func->func != SCF_PCM)) { return (ENXIO); } return (BUS_PROBE_DEFAULT); } static int ua_attach(device_t dev) { return (uaudio_attach_sub(dev, &ua_mixer_class, &ua_chan_class)); } static int ua_detach(device_t dev) { return (uaudio_detach_sub(dev)); } /************************************************************/ static device_method_t ua_pcm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ua_probe), DEVMETHOD(device_attach, ua_attach), DEVMETHOD(device_detach, ua_detach), DEVMETHOD_END }; static driver_t ua_pcm_driver = { "pcm", ua_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(ua_pcm, uaudio, ua_pcm_driver, 0, 0); MODULE_DEPEND(ua_pcm, snd_uaudio, 1, 1, 1); MODULE_DEPEND(ua_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(ua_pcm, 1);