diff options
author | thompsa <thompsa@FreeBSD.org> | 2009-02-23 21:07:20 +0000 |
---|---|---|
committer | thompsa <thompsa@FreeBSD.org> | 2009-02-23 21:07:20 +0000 |
commit | de29f650e65ee47a3b0430555b28f248cbceb4da (patch) | |
tree | 950fd630ee5efc2483a3c62d06f19a6246f5a15c /sys/legacy | |
parent | 40c2eac6aa3328ce690020e0325867fd0ee36d1c (diff) | |
download | FreeBSD-src-de29f650e65ee47a3b0430555b28f248cbceb4da.zip FreeBSD-src-de29f650e65ee47a3b0430555b28f248cbceb4da.tar.gz |
Move two missed usb drivers out to the graveyard location under sys/legacy/dev.
Diffstat (limited to 'sys/legacy')
-rw-r--r-- | sys/legacy/dev/ata/ata-usb.c | 970 | ||||
-rw-r--r-- | sys/legacy/dev/sound/usb/uaudio.c | 4662 | ||||
-rw-r--r-- | sys/legacy/dev/sound/usb/uaudio.h | 56 | ||||
-rw-r--r-- | sys/legacy/dev/sound/usb/uaudio_pcm.c | 481 | ||||
-rw-r--r-- | sys/legacy/dev/sound/usb/uaudioreg.h | 404 |
5 files changed, 6573 insertions, 0 deletions
diff --git a/sys/legacy/dev/ata/ata-usb.c b/sys/legacy/dev/ata/ata-usb.c new file mode 100644 index 0000000..5c868b2 --- /dev/null +++ b/sys/legacy/dev/ata/ata-usb.c @@ -0,0 +1,970 @@ +/*- + * Copyright (c) 2006 - 2008 Søren Schmidt <sos@FreeBSD.org> + * 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, + * without modification, immediately at the beginning of the file. + * 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 ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_ata.h" +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/mutex.h> +#include <sys/ata.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/sema.h> +#include <sys/taskqueue.h> +#include <vm/uma.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <dev/usb/usb_port.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include <dev/ata/ata-all.h> +#include <ata_if.h> + +/* Command Block Wrapper */ +struct bbb_cbw { + u_int8_t signature[4]; +#define CBWSIGNATURE 0x43425355 + + u_int8_t tag[4]; + u_int8_t transfer_length[4]; + u_int8_t flags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + + u_int8_t lun; + u_int8_t length; +#define CBWCDBLENGTH 16 + + u_int8_t cdb[CBWCDBLENGTH]; +}; + +/* Command Status Wrapper */ +struct bbb_csw { + u_int8_t signature[4]; +#define CSWSIGNATURE 0x53425355 + + u_int8_t tag[4]; + u_int8_t residue[4]; + u_int8_t status; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +}; + +/* USB-ATA 'controller' softc */ +struct atausb_softc { + device_t dev; /* base device */ + usbd_interface_handle iface; /* interface */ + int ifaceno; /* interface number */ + u_int8_t bulkin; /* endpoint address's */ + u_int8_t bulkout; + u_int8_t bulkirq; + usbd_pipe_handle bulkin_pipe; /* pipe handle's */ + usbd_pipe_handle bulkout_pipe; + usbd_pipe_handle bulkirq_pipe; + int maxlun; + int timeout; + struct ata_request *ata_request; + usb_device_request_t usb_request; + struct bbb_cbw cbw; + struct bbb_csw csw; + +#define ATAUSB_T_BBB_CBW 0 +#define ATAUSB_T_BBB_DATA 1 +#define ATAUSB_T_BBB_DCLEAR 2 +#define ATAUSB_T_BBB_CSW1 3 +#define ATAUSB_T_BBB_CSW2 4 +#define ATAUSB_T_BBB_SCLEAR 5 +#define ATAUSB_T_BBB_RESET1 6 +#define ATAUSB_T_BBB_RESET2 7 +#define ATAUSB_T_BBB_RESET3 8 +#define ATAUSB_T_MAX 9 + usbd_xfer_handle transfer[ATAUSB_T_MAX]; + + int state; +#define ATAUSB_S_ATTACH 0 +#define ATAUSB_S_IDLE 1 +#define ATAUSB_S_BBB_COMMAND 2 +#define ATAUSB_S_BBB_DATA 3 +#define ATAUSB_S_BBB_DCLEAR 4 +#define ATAUSB_S_BBB_STATUS1 5 +#define ATAUSB_S_BBB_SCLEAR 6 +#define ATAUSB_S_BBB_STATUS2 7 +#define ATAUSB_S_BBB_RESET1 8 +#define ATAUSB_S_BBB_RESET2 9 +#define ATAUSB_S_BBB_RESET3 10 +#define ATAUSB_S_DETACH 11 + + struct mtx locked_mtx; + struct ata_channel *locked_ch; + struct ata_channel *restart_ch; +}; + +static int atausbdebug = 0; + +/* prototypes*/ +static usbd_status atausb_start(struct atausb_softc *sc, usbd_pipe_handle pipe, void *buffer, int buflen, int flags, usbd_xfer_handle xfer); +static usbd_status atausb_ctl_start(struct atausb_softc *sc, usbd_device_handle udev, usb_device_request_t *req, void *buffer, int buflen, int flags, usbd_xfer_handle xfer); +static void atausb_clear_stall(struct atausb_softc *sc, u_int8_t endpt, usbd_pipe_handle pipe, int state, usbd_xfer_handle xfer); +static void atausb_bbb_reset(struct atausb_softc *sc); +static int atausb_bbb_start(struct ata_request *request); +static void atausb_bbb_finish(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status err); +int ata_usbchannel_begin_transaction(struct ata_request *request); +int ata_usbchannel_end_transaction(struct ata_request *request); + + +/* + * USB frontend part + */ +USB_DECLARE_DRIVER(atausb); +DRIVER_MODULE(atausb, uhub, atausb_driver, atausb_devclass, 0, 0); +MODULE_VERSION(atausb, 1); + +static int +atausb_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return UMATCH_NONE; + + id = usbd_get_interface_descriptor(uaa->iface); + if (!id || id->bInterfaceClass != UICLASS_MASS) + return UMATCH_NONE; + + switch (id->bInterfaceSubClass) { + case UISUBCLASS_QIC157: + case UISUBCLASS_RBC: + case UISUBCLASS_SCSI: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + case UISUBCLASS_UFI: + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + case UIPROTO_MASS_CBI_I: + case UIPROTO_MASS_BBB: + case UIPROTO_MASS_BBB_OLD: + return UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO; + default: + return UMATCH_IFACECLASS_IFACESUBCLASS; + } + break; + default: + return UMATCH_IFACECLASS; + } +} + +static int +atausb_attach(device_t dev) +{ + struct atausb_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_device_handle udev; + usb_device_request_t request; + device_t child; + char devinfo[1024], *proto, *subclass; + u_int8_t maxlun; + int err, i; + + sc->dev = dev; + usbd_devinfo(uaa->device, 0, devinfo); + device_set_desc_copy(dev, devinfo); + sc->bulkin = sc->bulkout = sc->bulkirq = -1; + sc->bulkin_pipe = sc->bulkout_pipe= sc->bulkirq_pipe = NULL; + sc->iface = uaa->iface; + sc->ifaceno = uaa->ifaceno; + sc->maxlun = 0; + sc->timeout = 5000; + sc->locked_ch = NULL; + sc->restart_ch = NULL; + mtx_init(&sc->locked_mtx, "ATAUSB lock", NULL, MTX_DEF); + + id = usbd_get_interface_descriptor(sc->iface); + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_BBB: + case UIPROTO_MASS_BBB_OLD: + proto = "Bulk-Only"; + break; + case UIPROTO_MASS_CBI: + proto = "CBI"; + break; + case UIPROTO_MASS_CBI_I: + proto = "CBI with CCI"; + break; + default: + proto = "Unknown"; + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_RBC: + subclass = "RBC"; + break; + case UISUBCLASS_QIC157: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + subclass = "ATAPI"; + break; + case UISUBCLASS_SCSI: + subclass = "SCSI"; + break; + case UISUBCLASS_UFI: + subclass = "UFI"; + break; + default: + subclass = "Unknown"; + } + device_printf(dev, "using %s over %s\n", subclass, proto); + if (strcmp(proto, "Bulk-Only") || + (strcmp(subclass, "ATAPI") && strcmp(subclass, "SCSI"))) + return ENXIO; + + for (i = 0 ; i < id->bNumEndpoints ; i++) { + if (!(ed = usbd_interface2endpoint_descriptor(sc->iface, i))) { + device_printf(sc->dev, "could not read endpoint descriptor\n"); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + sc->bulkin = ed->bEndpointAddress; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + sc->bulkout = ed->bEndpointAddress; + } + if (id->bInterfaceProtocol == UIPROTO_MASS_CBI_I && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { + sc->bulkirq = ed->bEndpointAddress; + } + } + + /* check whether we found at least the endpoints we need */ + if (!sc->bulkin || !sc->bulkout) { + device_printf(sc->dev, "needed endpoints not found (%d,%d)\n", + sc->bulkin, sc->bulkout); + atausb_detach(dev); + return ENXIO; + } + + /* open the pipes */ + if (usbd_open_pipe(sc->iface, sc->bulkout, + USBD_EXCLUSIVE_USE, &sc->bulkout_pipe)) { + device_printf(sc->dev, "cannot open bulkout pipe (%d)\n", sc->bulkout); + atausb_detach(dev); + return ENXIO; + } + if (usbd_open_pipe(sc->iface, sc->bulkin, + USBD_EXCLUSIVE_USE, &sc->bulkin_pipe)) { + device_printf(sc->dev, "cannot open bulkin pipe (%d)\n", sc->bulkin); + atausb_detach(dev); + return ENXIO; + } + if (id->bInterfaceProtocol == UIPROTO_MASS_CBI_I) { + if (usbd_open_pipe(sc->iface, sc->bulkirq, + USBD_EXCLUSIVE_USE, &sc->bulkirq_pipe)) { + device_printf(sc->dev, "cannot open bulkirq pipe (%d)\n", + sc->bulkirq); + atausb_detach(dev); + return ENXIO; + } + } + sc->state = ATAUSB_S_ATTACH; + + /* alloc needed number of transfer handles */ + for (i = 0; i < ATAUSB_T_MAX; i++) { + sc->transfer[i] = usbd_alloc_xfer(uaa->device); + if (!sc->transfer[i]) { + device_printf(sc->dev, "out of memory\n"); + atausb_detach(dev); + return ENXIO; + } + } + + /* driver is ready to process requests here */ + sc->state = ATAUSB_S_IDLE; + + /* get number of devices so we can add matching channels */ + usbd_interface2device_handle(sc->iface, &udev); + request.bmRequestType = UT_READ_CLASS_INTERFACE; + request.bRequest = 0xfe; //GET_MAX_LUN; + USETW(request.wValue, 0); + USETW(request.wIndex, sc->ifaceno); + USETW(request.wLength, sizeof(maxlun)); + switch ((err = usbd_do_request(udev, &request, &maxlun))) { + case USBD_NORMAL_COMPLETION: + if (bootverbose) + device_printf(sc->dev, "maxlun=%d\n", maxlun); + sc->maxlun = maxlun; + break; + default: + if (bootverbose) + device_printf(sc->dev, "get maxlun not supported %s\n", + usbd_errstr(err)); + } + + /* ata channels are children to this USB control device */ + for (i = 0; i <= sc->maxlun; i++) { + if ((child = device_add_child(sc->dev, "ata", + devclass_find_free_unit(ata_devclass, 2))) == NULL) { + device_printf(sc->dev, "failed to add ata child device\n"); + } else + device_set_ivars(child, (void *)(intptr_t)i); + } + bus_generic_attach(sc->dev); + return 0; +} + +static int +atausb_detach(device_t dev) +{ + struct atausb_softc *sc = device_get_softc(dev); + usbd_device_handle udev; + device_t *children; + int nchildren, i; + + /* signal that device is going away */ + sc->state = ATAUSB_S_DETACH; + + /* abort all the pipes in case there are active transfers */ + usbd_interface2device_handle(sc->iface, &udev); + usbd_abort_default_pipe(udev); + if (sc->bulkout_pipe) + usbd_abort_pipe(sc->bulkout_pipe); + if (sc->bulkin_pipe) + usbd_abort_pipe(sc->bulkin_pipe); + if (sc->bulkirq_pipe) + usbd_abort_pipe(sc->bulkirq_pipe); + + /* detach & delete all children */ + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + device_delete_child(dev, children[i]); + free(children, M_TEMP); + } + + /* free the transfers */ + for (i = 0; i < ATAUSB_T_MAX; i++) + if (sc->transfer[i]) + usbd_free_xfer(sc->transfer[i]); + + /* remove all the pipes */ + if (sc->bulkout_pipe) + usbd_close_pipe(sc->bulkout_pipe); + if (sc->bulkin_pipe) + usbd_close_pipe(sc->bulkin_pipe); + if (sc->bulkirq_pipe) + usbd_close_pipe(sc->bulkirq_pipe); + + mtx_destroy(&sc->locked_mtx); + return 0; +} + + +/* + * Generic USB transfer routines + */ +static usbd_status +atausb_start(struct atausb_softc *sc, usbd_pipe_handle pipe, + void *buffer, int buflen, int flags, usbd_xfer_handle xfer) +{ + usbd_status err; + + if (sc->state == ATAUSB_S_DETACH) + return USBD_NOT_STARTED; + + usbd_setup_xfer(xfer, pipe, (void *)sc, buffer, buflen, flags, + sc->timeout, atausb_bbb_finish); + err = usbd_transfer(xfer); + if (err && (err != USBD_IN_PROGRESS)) { + if (atausbdebug) + device_printf(sc->dev, "failed to setup transfer, %s\n", + usbd_errstr(err)); + return err; + } + return USBD_NORMAL_COMPLETION; +} + +static usbd_status +atausb_ctl_start(struct atausb_softc *sc, usbd_device_handle udev, + usb_device_request_t *req, void *buffer, int buflen, int flags, + usbd_xfer_handle xfer) +{ + usbd_status err; + + if (sc->state == ATAUSB_S_DETACH) + return USBD_NOT_STARTED; + + usbd_setup_default_xfer(xfer, udev, (void *)sc, sc->timeout, req, + buffer, buflen, flags, atausb_bbb_finish); + err = usbd_transfer(xfer); + if (err && (err != USBD_IN_PROGRESS)) { + if (atausbdebug) + device_printf(sc->dev, "failed to setup ctl transfer, %s\n", + usbd_errstr(err)); + return err; + } + return USBD_NORMAL_COMPLETION; +} + +static void +atausb_clear_stall(struct atausb_softc *sc, u_int8_t endpt, + usbd_pipe_handle pipe, int state, usbd_xfer_handle xfer) +{ + usbd_device_handle udev; + + if (atausbdebug) + device_printf(sc->dev, "clear endpoint 0x%02x stall\n", endpt); + usbd_interface2device_handle(sc->iface, &udev); + sc->state = state; + usbd_clear_endpoint_toggle(pipe); + sc->usb_request.bmRequestType = UT_WRITE_ENDPOINT; + sc->usb_request.bRequest = UR_CLEAR_FEATURE; + USETW(sc->usb_request.wValue, UF_ENDPOINT_HALT); + USETW(sc->usb_request.wIndex, endpt); + USETW(sc->usb_request.wLength, 0); + atausb_ctl_start(sc, udev, &sc->usb_request, NULL, 0, 0, xfer); +} + + +/* + * Bulk-Only transport part + */ +static void +atausb_bbb_reset(struct atausb_softc *sc) +{ + usbd_device_handle udev; + + if (atausbdebug) + device_printf(sc->dev, "Bulk Reset\n"); + sc->timeout = 5000; + sc->state = ATAUSB_S_BBB_RESET1; + usbd_interface2device_handle(sc->iface, &udev); + sc->usb_request.bmRequestType = UT_WRITE_CLASS_INTERFACE; + sc->usb_request.bRequest = 0xff; /* bulk-only reset */ + USETW(sc->usb_request.wValue, 0); + USETW(sc->usb_request.wIndex, sc->ifaceno); + USETW(sc->usb_request.wLength, 0); + atausb_ctl_start(sc, udev, &sc->usb_request, NULL, + 0, 0, sc->transfer[ATAUSB_T_BBB_RESET1]); +} + +static int +atausb_bbb_start(struct ata_request *request) +{ + struct atausb_softc *sc = + device_get_softc(device_get_parent(request->parent)); + struct ata_channel *ch = device_get_softc(request->parent); + + sc->timeout = (request->timeout * 1000) + 5000; + USETDW(sc->cbw.signature, CBWSIGNATURE); + USETDW(sc->cbw.tag, UGETDW(sc->cbw.tag) + 1); + USETDW(sc->cbw.transfer_length, request->bytecount); + sc->cbw.flags = (request->flags & ATA_R_READ) ? CBWFLAGS_IN : CBWFLAGS_OUT; + sc->cbw.lun = ch->unit; + sc->cbw.length = 16; + bzero(sc->cbw.cdb, 16); + bcopy(request->u.atapi.ccb, sc->cbw.cdb, 12); /* XXX SOS */ + sc->state = ATAUSB_S_BBB_COMMAND; + if (atausb_start(sc, sc->bulkout_pipe, &sc->cbw, sizeof(struct bbb_cbw), + 0, sc->transfer[ATAUSB_T_BBB_CBW])) { + request->result = EIO; + if (atausbdebug) + device_printf(request->dev, "cannot setup USB transfer\n"); + atausb_bbb_reset(sc); + return ATA_OP_FINISHED; + } + return ATA_OP_CONTINUES; +} + +static void +atausb_bbb_finish(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status err) +{ + struct atausb_softc *sc = (struct atausb_softc *)priv; + struct ata_request *request = sc->ata_request; + usbd_xfer_handle next_xfer; + + //device_printf(sc->dev, "BBB state %d: %s\n", sc->state, usbd_errstr(err)); + + if (sc->state == ATAUSB_S_DETACH) { + device_printf(sc->dev, "WARNING - device has been removed\n"); + return; + } + + switch (sc->state) { + case ATAUSB_S_BBB_COMMAND: /* command transport phase */ + if (err) { + if (atausbdebug) + device_printf(sc->dev, "failed to send CBW\n"); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + + /* next is data transport phase, setup transfer */ + sc->state = ATAUSB_S_BBB_DATA; + if (request->flags & ATA_R_READ) { + if (atausb_start(sc, sc->bulkin_pipe, + request->data, request->bytecount, + USBD_SHORT_XFER_OK, + sc->transfer[ATAUSB_T_BBB_DATA])) { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + if (request->flags & ATA_R_WRITE) { + if (atausb_start(sc, sc->bulkout_pipe, + request->data, request->bytecount, + 0, sc->transfer[ATAUSB_T_BBB_DATA])) { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + /* FALLTHROUGH */ + + case ATAUSB_S_BBB_DATA: /* data transport phase */ + if (request->flags & (ATA_R_READ | ATA_R_WRITE)) { + usbd_get_xfer_status(xfer, NULL, NULL, &request->donecount, NULL); + if (err) { + if (atausbdebug) + device_printf(sc->dev, "data %s count %d failed: %s\n", + (request->flags & ATA_R_READ?"read":"write"), + request->bytecount, usbd_errstr(err)); + if (err == USBD_STALLED) { + atausb_clear_stall(sc, + (request->flags & ATA_R_READ ? + sc->bulkin : sc->bulkout), + (request->flags & ATA_R_READ ? + sc->bulkin_pipe : sc->bulkout_pipe), + ATAUSB_S_BBB_DCLEAR, + sc->transfer[ATAUSB_T_BBB_DCLEAR]); + } + else { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + } + /* FALLTHROUGH */ + + case ATAUSB_S_BBB_DCLEAR: /* stall clear after data phase */ + case ATAUSB_S_BBB_SCLEAR: /* stall clear after status phase */ + if (err) { + if (atausbdebug) + device_printf(sc->dev, "bulk%s stall clear failed %s\n", + (request->flags & ATA_R_READ ? "in" : "out"), + usbd_errstr(err)); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + + if (sc->state == ATAUSB_S_BBB_COMMAND || + sc->state == ATAUSB_S_BBB_DATA || + sc->state == ATAUSB_S_BBB_DCLEAR) { + /* first attempt on status transport phase setup transfer */ + sc->state = ATAUSB_S_BBB_STATUS1; + next_xfer = sc->transfer[ATAUSB_T_BBB_CSW1]; + } + else { + /* second attempt of fetching status */ + sc->state = ATAUSB_S_BBB_STATUS2; + next_xfer = sc->transfer[ATAUSB_T_BBB_CSW2]; + } + if (atausb_start(sc, sc->bulkin_pipe, &sc->csw, sizeof(struct bbb_csw), + USBD_SHORT_XFER_OK, next_xfer)) { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + + case ATAUSB_S_BBB_STATUS1: /* status transfer first attempt */ + case ATAUSB_S_BBB_STATUS2: /* status transfer second attempt */ + if (err) { + if (atausbdebug) + device_printf(sc->dev, "cannot get CSW, %s%s\n", + usbd_errstr(err), + sc->state == ATAUSB_S_BBB_STATUS1 ? ", retry":""); + if (sc->state == ATAUSB_S_BBB_STATUS1) { + atausb_clear_stall(sc, sc->bulkin, sc->bulkin_pipe, + ATAUSB_S_BBB_SCLEAR, + sc->transfer[ATAUSB_T_BBB_SCLEAR]); + } + else { + request->result = EIO; + atausb_bbb_reset(sc); + } + return; + } + + int residue = UGETDW(sc->csw.residue); + + if (!residue && + (request->bytecount - request->donecount)) + residue = request->bytecount - request->donecount; + + /* check CSW and handle eventual error */ + if (UGETDW(sc->csw.signature) != CSWSIGNATURE) { + if (atausbdebug) + device_printf(sc->dev, "bad CSW signature 0x%08x != 0x%08x\n", + UGETDW(sc->csw.signature), CSWSIGNATURE); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (UGETDW(sc->csw.tag) != UGETDW(sc->cbw.tag)) { + if (atausbdebug) + device_printf(sc->dev, "bad CSW tag %d != %d\n", + UGETDW(sc->csw.tag), UGETDW(sc->cbw.tag)); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (sc->csw.status > CSWSTATUS_PHASE) { + if (atausbdebug) + device_printf(sc->dev, "bad CSW status %d > %d\n", + sc->csw.status, CSWSTATUS_PHASE); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (sc->csw.status == CSWSTATUS_PHASE) { + if (atausbdebug) + device_printf(sc->dev, "phase error residue = %d\n", residue); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (request->donecount > request->bytecount) { + if (atausbdebug) + device_printf(sc->dev, "buffer overrun %d > %d", + request->donecount, request->bytecount); + request->result = EIO; + atausb_bbb_reset(sc); + return; + } + else if (sc->csw.status == CSWSTATUS_FAILED) { + if (atausbdebug) + device_printf(sc->dev, "CSWSTATUS_FAILED\n"); + request->error = ATA_E_ATAPI_SENSE_MASK ; + sc->state = ATAUSB_S_IDLE; + ata_interrupt(device_get_softc(request->parent)); + return; + } + else { + sc->state = ATAUSB_S_IDLE; + ata_interrupt(device_get_softc(request->parent)); + return; + } + /* NOT REACHED */ + + case ATAUSB_S_BBB_RESET1: + if (err) + if (atausbdebug) + device_printf(sc->dev, + "BBB reset failure: %s\n", usbd_errstr(err)); + atausb_clear_stall(sc, sc->bulkin, sc->bulkin_pipe, + ATAUSB_S_BBB_RESET2, + sc->transfer[ATAUSB_T_BBB_RESET2]); + return; + + case ATAUSB_S_BBB_RESET2: + if (err) + if (atausbdebug) + device_printf(sc->dev, "BBB bulkin clear stall failure: %s\n", + usbd_errstr(err)); + atausb_clear_stall(sc, sc->bulkout, sc->bulkout_pipe, + ATAUSB_S_BBB_RESET3, + sc->transfer[ATAUSB_T_BBB_RESET3]); + return; + + case ATAUSB_S_BBB_RESET3: + if (err) + if (atausbdebug) + device_printf(sc->dev, "BBB bulk-out clear stall failure: %s\n", + usbd_errstr(err)); + sc->state = ATAUSB_S_IDLE; + if (request) { + if (err) + request->result = ENXIO; + else + request->result = EIO; + ata_interrupt(device_get_softc(request->parent)); + } + return; + + default: + if (atausbdebug) + device_printf(sc->dev, "unknown state %d", sc->state); + } +} + + +/* + * ATA backend part + */ +struct atapi_inquiry { + u_int8_t device_type; + u_int8_t device_modifier; + u_int8_t version; + u_int8_t response_format; + u_int8_t length; + u_int8_t reserved[2]; + u_int8_t flags; + u_int8_t vendor[8]; + u_int8_t product[16]; + u_int8_t revision[4]; + //u_int8_t crap[60]; +}; + +int +ata_usbchannel_begin_transaction(struct ata_request *request) +{ + struct atausb_softc *sc = + device_get_softc(device_get_parent(request->parent)); + + if (atausbdebug > 1) + device_printf(request->dev, "begin_transaction %s\n", + ata_cmd2str(request)); + + /* sanity just in case */ + if (sc->state != ATAUSB_S_IDLE) { + printf("begin is busy (%d)\n", sc->state); + request->result = EBUSY; + return ATA_OP_FINISHED; + } + + /* XXX SOS convert the request into the format used, only BBB for now*/ + sc->ata_request = request; + + /* ATA/ATAPI IDENTIFY needs special treatment */ + if (!(request->flags & ATA_R_ATAPI)) { + if (request->u.ata.command != ATA_ATAPI_IDENTIFY) { + device_printf(request->dev,"%s unsupported\n",ata_cmd2str(request)); + request->result = EIO; + return ATA_OP_FINISHED; + } + request->flags |= ATA_R_ATAPI; + bzero(request->u.atapi.ccb, 16); + request->u.atapi.ccb[0] = ATAPI_INQUIRY; + request->u.atapi.ccb[4] = 255; //sizeof(struct atapi_inquiry); + request->data += 256; /* arbitrary offset into ata_param */ + request->bytecount = 255; //sizeof(struct atapi_inquiry); + } + return atausb_bbb_start(request); +} + +int +ata_usbchannel_end_transaction(struct ata_request *request) +{ + if (atausbdebug > 1) + device_printf(request->dev, "end_transaction %s\n", + ata_cmd2str(request)); + + /* XXX SOS convert the request from the format used, only BBB for now*/ + + /* ATA/ATAPI IDENTIFY needs special treatment */ + if ((request->flags & ATA_R_ATAPI) && + (request->u.atapi.ccb[0] == ATAPI_INQUIRY)) { + struct ata_device *atadev = device_get_softc(request->dev); + struct atapi_inquiry *inquiry = (struct atapi_inquiry *)request->data; + u_int16_t *ptr; + + /* convert inquiry data into simple ata_param like format */ + atadev->param.config = ATA_PROTO_ATAPI | ATA_PROTO_ATAPI_12; + atadev->param.config |= (inquiry->device_type & 0x1f) << 8; + bzero(atadev->param.model, sizeof(atadev->param.model)); + strncpy(atadev->param.model, inquiry->vendor, 8); + strcpy(atadev->param.model, " "); + strncpy(atadev->param.model, inquiry->product, 16); + ptr = (u_int16_t*)(atadev->param.model + sizeof(atadev->param.model)); + while (--ptr >= (u_int16_t*)atadev->param.model) + *ptr = ntohs(*ptr); + strncpy(atadev->param.revision, inquiry->revision, 4); + ptr=(u_int16_t*)(atadev->param.revision+sizeof(atadev->param.revision)); + while (--ptr >= (u_int16_t*)atadev->param.revision) + *ptr = ntohs(*ptr); + request->result = 0; + } + return ATA_OP_FINISHED; +} + +static int +ata_usbchannel_probe(device_t dev) +{ + char buffer[32]; + + sprintf(buffer, "USB lun %d", (int)(intptr_t)device_get_ivars(dev)); + device_set_desc_copy(dev, buffer); + + return 0; +} + +static int +ata_usbchannel_attach(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + + if (ch->attached) + return (0); + ch->attached = 1; + + /* initialize the softc basics */ + ch->dev = dev; + ch->unit = (intptr_t)device_get_ivars(dev); + ch->state = ATA_IDLE; + ch->hw.begin_transaction = ata_usbchannel_begin_transaction; + ch->hw.end_transaction = ata_usbchannel_end_transaction; + ch->hw.status = NULL; + ch->hw.command = NULL; + bzero(&ch->state_mtx, sizeof(struct mtx)); + mtx_init(&ch->state_mtx, "ATA state lock", NULL, MTX_DEF); + bzero(&ch->queue_mtx, sizeof(struct mtx)); + mtx_init(&ch->queue_mtx, "ATA queue lock", NULL, MTX_DEF); + TAILQ_INIT(&ch->ata_queue); + + /* XXX SOS reset the controller HW, the channel and device(s) */ + //ATA_RESET(dev); + + /* probe and attach device on this channel */ + ch->devices = ATA_ATAPI_MASTER; + if (!ata_delayed_attach) + ata_identify(dev); + return 0; +} + +static int +ata_usbchannel_detach(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + device_t *children; + int nchildren, i; + + if (!ch->attached) + return (0); + ch->attached = 0; + + /* detach & delete all children */ + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + if (children[i]) + device_delete_child(dev, children[i]); + free(children, M_TEMP); + } + mtx_destroy(&ch->state_mtx); + mtx_destroy(&ch->queue_mtx); + return 0; +} + +static void +ata_usbchannel_setmode(device_t parent, device_t dev) +{ + struct atausb_softc *sc = device_get_softc(GRANDPARENT(dev)); + struct ata_device *atadev = device_get_softc(dev); + usbd_device_handle udev; + + usbd_interface2device_handle(sc->iface, &udev); + if (usbd_get_speed(udev) == USB_SPEED_HIGH) + atadev->mode = ATA_USB2; + else + atadev->mode = ATA_USB1; +} + +static int +ata_usbchannel_locking(device_t dev, int flags) +{ + struct atausb_softc *sc = device_get_softc(device_get_parent(dev)); + struct ata_channel *ch = device_get_softc(dev); + int res = -1; + + + mtx_lock(&sc->locked_mtx); + switch (flags) { + case ATA_LF_LOCK: + if (sc->locked_ch == NULL) + sc->locked_ch = ch; + if (sc->locked_ch != ch) + sc->restart_ch = ch; + break; + + case ATA_LF_UNLOCK: + if (sc->locked_ch == ch) { + sc->locked_ch = NULL; + if (sc->restart_ch) { + ch = sc->restart_ch; + sc->restart_ch = NULL; + mtx_unlock(&sc->locked_mtx); + ata_start(ch->dev); + return res; + } + } + break; + + case ATA_LF_WHICH: + break; + } + if (sc->locked_ch) + res = sc->locked_ch->unit; + mtx_unlock(&sc->locked_mtx); + return res; +} + +static device_method_t ata_usbchannel_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, ata_usbchannel_probe), + DEVMETHOD(device_attach, ata_usbchannel_attach), + DEVMETHOD(device_detach, ata_usbchannel_detach), + + /* ATA methods */ + DEVMETHOD(ata_setmode, ata_usbchannel_setmode), + DEVMETHOD(ata_locking, ata_usbchannel_locking), + //DEVMETHOD(ata_reset, ata_usbchannel_reset), + + { 0, 0 } +}; + +static driver_t ata_usbchannel_driver = { + "ata", + ata_usbchannel_methods, + sizeof(struct ata_channel), +}; + +DRIVER_MODULE(ata, atausb, ata_usbchannel_driver, ata_devclass, 0, 0); +MODULE_DEPEND(atausb, ata, 1, 1, 1); diff --git a/sys/legacy/dev/sound/usb/uaudio.c b/sys/legacy/dev/sound/usb/uaudio.c new file mode 100644 index 0000000..758d6cd --- /dev/null +++ b/sys/legacy/dev/sound/usb/uaudio.c @@ -0,0 +1,4662 @@ +/* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ +/* $FreeBSD$ */ + +/*- + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +/* + * 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 + */ + +#include <sys/cdefs.h> +#if defined(__NetBSD__) || defined(__OpenBSD__) +__KERNEL_RCSID(0, "$NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $"); +#endif + +/* + * 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 $ + * $NetBSD: uaudio.c,v 1.102 2006/04/14 17:00:55 christos Exp $ + * $NetBSD: uaudio.c,v 1.103 2006/05/11 19:09:25 mrg Exp $ + * $NetBSD: uaudio.c,v 1.105 2006/10/04 16:00:15 christos Exp $ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/device.h> +#include <sys/ioctl.h> +#endif +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/reboot.h> /* for bootverbose */ +#include <sys/select.h> +#include <sys/proc.h> +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/device.h> +#elif defined(__FreeBSD__) +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#endif +#include <sys/poll.h> +#if defined(__FreeBSD__) +#include <sys/sysctl.h> +#include <sys/sbuf.h> +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/audiovar.h> +#include <dev/mulaw.h> +#include <dev/auconv.h> +#elif defined(__FreeBSD__) +#include <dev/sound/pcm/sound.h> /* XXXXX */ +#include <dev/sound/chip.h> +#include "feeder_if.h" +#endif + +#include <dev/usb/usb_port.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_quirks.h> + +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include <dev/usb/uaudioreg.h> +#elif defined(__FreeBSD__) +#include <dev/sound/usb/uaudioreg.h> +#include <dev/sound/usb/uaudio.h> +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +/* #define UAUDIO_DEBUG */ +#else +/* #define USB_DEBUG */ +#endif +/* #define UAUDIO_MULTIPLE_ENDPOINTS */ +#ifdef USB_DEBUG +#define DPRINTF(x) do { if (uaudiodebug) printf x; } while (0) +#define DPRINTFN(n,x) do { if (uaudiodebug>(n)) printf x; } while (0) +int uaudiodebug = 0; +#if defined(__FreeBSD__) +SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio"); +SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RW, + &uaudiodebug, 0, "uaudio debug level"); +#endif +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UAUDIO_NCHANBUFS 6 /* number of outstanding request */ +#if defined(__NetBSD__) || defined(__OpenBSD__) +#define UAUDIO_NFRAMES 10 /* ms of sound in each request */ +#elif defined(__FreeBSD__) +#define UAUDIO_NFRAMES 20 /* ms of sound in each request */ +#endif + + +#define MIX_MAX_CHAN 8 +struct mixerctl { + uint16_t wValue[MIX_MAX_CHAN]; /* using nchan */ + uint16_t wIndex; + 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_SIZE(n) ((n) == MIX_SIGNED_16 || (n) == MIX_UNSIGNED_16 ? 2 : 1) +#define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) + int minval, maxval; + u_int delta; + u_int mul; +#if defined(__FreeBSD__) /* XXXXX */ + unsigned ctl; +#define MAX_SELECTOR_INPUT_PIN 256 + uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; +#endif + uint8_t class; +#if !defined(__FreeBSD__) + char ctlname[MAX_AUDIO_DEV_LEN]; + char *ctlunit; +#endif +}; +#define MAKE(h,l) (((h) << 8) | (l)) + +struct as_info { + uint8_t alt; + uint8_t encoding; + uint8_t attributes; /* Copy of bmAttributes of + * usb_audio_streaming_endpoint_descriptor + */ + usbd_interface_handle ifaceh; + const usb_interface_descriptor_t *idesc; + const usb_endpoint_descriptor_audio_t *edesc; + const usb_endpoint_descriptor_audio_t *edesc1; + const struct usb_audio_streaming_type1_descriptor *asf1desc; + int sc_busy; /* currently used */ +}; + +struct chan { +#if defined(__NetBSD__) || defined(__OpenBSD__) + void (*intr)(void *); /* DMA completion intr handler */ + void *arg; /* arg for intr() */ +#else + struct pcm_channel *pcm_ch; +#endif + usbd_pipe_handle pipe; + usbd_pipe_handle sync_pipe; + + u_int sample_size; + u_int sample_rate; + u_int bytes_per_frame; + u_int fraction; /* fraction/1000 is the extra samples/frame */ + u_int residue; /* accumulates the fractional samples */ + + u_char *start; /* upper layer buffer start */ + u_char *end; /* upper layer buffer end */ + u_char *cur; /* current position in upper layer buffer */ + int blksize; /* chunk size to report up */ + int transferred; /* transferred bytes not reported up */ + + int altidx; /* currently used altidx */ + + int curchanbuf; + struct chanbuf { + struct chan *chan; + usbd_xfer_handle xfer; + u_char *buffer; + u_int16_t sizes[UAUDIO_NFRAMES]; + u_int16_t offsets[UAUDIO_NFRAMES]; + u_int16_t size; + } chanbufs[UAUDIO_NCHANBUFS]; + + struct uaudio_softc *sc; /* our softc */ +#if defined(__FreeBSD__) + u_int32_t format; + int precision; + int channels; +#endif +}; + +struct uaudio_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; /* USB device */ + int sc_ac_iface; /* Audio Control interface */ + usbd_interface_handle sc_ac_ifaceh; + struct chan sc_playchan; /* play channel */ + struct chan sc_recchan; /* record channel */ + int sc_nullalt; + int sc_audio_rev; + struct as_info *sc_alts; /* alternate settings */ + int sc_nalts; /* # of alternate settings */ + int sc_altflags; +#define HAS_8 0x01 +#define HAS_16 0x02 +#define HAS_8U 0x04 +#define HAS_ALAW 0x08 +#define HAS_MULAW 0x10 +#define UA_NOFRAC 0x20 /* don't do sample rate adjustment */ +#define HAS_24 0x40 +#define HAS_32 0x80 + int sc_mode; /* play/record capability */ + struct mixerctl *sc_ctls; /* mixer controls */ + int sc_nctls; /* # of mixer controls */ + device_t sc_audiodev; + char sc_dying; +#if defined(__FreeBSD__) + struct sbuf uaudio_sndstat; + int uaudio_sndstat_flag; + int async; +#endif + int sc_vendor; + int sc_product; + int sc_release; +}; + +struct terminal_list { + int size; + uint16_t terminals[1]; +}; +#define TERMINAL_LIST_SIZE(N) (offsetof(struct terminal_list, terminals) \ + + sizeof(uint16_t) * (N)) + +struct io_terminal { + union { + const usb_descriptor_t *desc; + const struct usb_audio_input_terminal *it; + const struct usb_audio_output_terminal *ot; + const struct usb_audio_mixer_unit *mu; + const struct usb_audio_selector_unit *su; + const struct usb_audio_feature_unit *fu; + const struct usb_audio_processing_unit *pu; + const struct usb_audio_extension_unit *eu; + } d; + int inputs_size; + struct terminal_list **inputs; /* list of source input terminals */ + struct terminal_list *output; /* list of destination output terminals */ + int direct; /* directly connected to an output terminal */ +}; + +#define UAC_OUTPUT 0 +#define UAC_INPUT 1 +#define UAC_EQUAL 2 +#define UAC_RECORD 3 +#define UAC_NCLASSES 4 +#ifdef USB_DEBUG +#if defined(__FreeBSD__) +#define AudioCinputs "inputs" +#define AudioCoutputs "outputs" +#define AudioCrecord "record" +#define AudioCequalization "equalization" +#endif +static const char *uac_names[] = { + AudioCoutputs, AudioCinputs, AudioCequalization, AudioCrecord, +}; +#endif + +static usbd_status uaudio_identify_ac + (struct uaudio_softc *, const usb_config_descriptor_t *); +static usbd_status uaudio_identify_as + (struct uaudio_softc *, const usb_config_descriptor_t *); +static usbd_status uaudio_process_as + (struct uaudio_softc *, const char *, int *, int, + const usb_interface_descriptor_t *); + +static void uaudio_add_alt(struct uaudio_softc *, const struct as_info *); + +static const usb_interface_descriptor_t *uaudio_find_iface + (const char *, int, int *, int); + +static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct mixerctl *); + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static char *uaudio_id_name + (struct uaudio_softc *, const struct io_terminal *, int); +#endif + +#ifdef USB_DEBUG +static void uaudio_dump_cluster(const struct usb_audio_cluster *); +#endif +static struct usb_audio_cluster uaudio_get_cluster + (int, const struct io_terminal *); +static void uaudio_add_input + (struct uaudio_softc *, const struct io_terminal *, int); +static void uaudio_add_output + (struct uaudio_softc *, const struct io_terminal *, int); +static void uaudio_add_mixer + (struct uaudio_softc *, const struct io_terminal *, int); +static void uaudio_add_selector + (struct uaudio_softc *, const struct io_terminal *, int); +#ifdef USB_DEBUG +static const char *uaudio_get_terminal_name(int); +#endif +static int uaudio_determine_class + (const struct io_terminal *, struct mixerctl *); +#if defined(__FreeBSD__) +static int uaudio_feature_name(const struct io_terminal *, + struct mixerctl *); +#else +static const char *uaudio_feature_name + (const struct io_terminal *, struct mixerctl *); +#endif +static void uaudio_add_feature + (struct uaudio_softc *, const struct io_terminal *, int); +static void uaudio_add_processing_updown + (struct uaudio_softc *, const struct io_terminal *, int); +static void uaudio_add_processing + (struct uaudio_softc *, const struct io_terminal *, int); +static void uaudio_add_extension + (struct uaudio_softc *, const struct io_terminal *, int); +static struct terminal_list *uaudio_merge_terminal_list + (const struct io_terminal *); +static struct terminal_list *uaudio_io_terminaltype + (int, struct io_terminal *, int); +static usbd_status uaudio_identify + (struct uaudio_softc *, const usb_config_descriptor_t *); + +static int uaudio_signext(int, int); +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int uaudio_value2bsd(struct mixerctl *, int); +#endif +static int uaudio_bsd2value(struct mixerctl *, int); +static int uaudio_get(struct uaudio_softc *, int, int, int, int, int); +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int uaudio_ctl_get + (struct uaudio_softc *, int, struct mixerctl *, int); +#endif +static void uaudio_set + (struct uaudio_softc *, int, int, int, int, int, int); +static void uaudio_ctl_set + (struct uaudio_softc *, int, struct mixerctl *, int, int); + +static usbd_status uaudio_set_speed(struct uaudio_softc *, int, u_int); + +static usbd_status uaudio_chan_open(struct uaudio_softc *, struct chan *); +static void uaudio_chan_close(struct uaudio_softc *, struct chan *); +static usbd_status uaudio_chan_alloc_buffers + (struct uaudio_softc *, struct chan *); +static void uaudio_chan_free_buffers(struct uaudio_softc *, struct chan *); + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static void uaudio_chan_init + (struct chan *, int, const struct audio_params *, int); +static void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); +#endif + +static void uaudio_chan_ptransfer(struct chan *); +static void uaudio_chan_pintr + (usbd_xfer_handle, usbd_private_handle, usbd_status); + +static void uaudio_chan_rtransfer(struct chan *); +static void uaudio_chan_rintr + (usbd_xfer_handle, usbd_private_handle, usbd_status); + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int uaudio_open(void *, int); +static void uaudio_close(void *); +static int uaudio_drain(void *); +static int uaudio_query_encoding(void *, struct audio_encoding *); +static void uaudio_get_minmax_rates + (int, const struct as_info *, const struct audio_params *, + int, u_long *, u_long *); +static int uaudio_match_alt_sub + (int, const struct as_info *, const struct audio_params *, int, u_long); +static int uaudio_match_alt_chan + (int, const struct as_info *, struct audio_params *, int); +static int uaudio_match_alt + (int, const struct as_info *, struct audio_params *, int); +static int uaudio_set_params + (void *, int, int, struct audio_params *, struct audio_params *); +static int uaudio_round_blocksize(void *, int); +static int uaudio_trigger_output + (void *, void *, void *, int, void (*)(void *), void *, + struct audio_params *); +static int uaudio_trigger_input + (void *, void *, void *, int, void (*)(void *), void *, + struct audio_params *); +static int uaudio_halt_in_dma(void *); +static int uaudio_halt_out_dma(void *); +static int uaudio_getdev(void *, struct audio_device *); +static int uaudio_mixer_set_port(void *, mixer_ctrl_t *); +static int uaudio_mixer_get_port(void *, mixer_ctrl_t *); +static int uaudio_query_devinfo(void *, mixer_devinfo_t *); +static int uaudio_get_props(void *); + +static const struct audio_hw_if uaudio_hw_if = { + uaudio_open, + uaudio_close, + uaudio_drain, + uaudio_query_encoding, + uaudio_set_params, + uaudio_round_blocksize, + NULL, + NULL, + NULL, + NULL, + NULL, + uaudio_halt_out_dma, + uaudio_halt_in_dma, + NULL, + uaudio_getdev, + NULL, + uaudio_mixer_set_port, + uaudio_mixer_get_port, + uaudio_query_devinfo, + NULL, + NULL, + NULL, + NULL, + uaudio_get_props, + uaudio_trigger_output, + uaudio_trigger_input, + NULL, +}; + +static struct audio_device uaudio_device = { + "USB audio", + "", + "uaudio" +}; + +#elif defined(__FreeBSD__) +static int audio_attach_mi(device_t); +static int uaudio_init_params(struct uaudio_softc * sc, struct chan *ch, int mode); +static int uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); + +/* for NetBSD compatibirity */ +#define AUMODE_PLAY 0x01 +#define AUMODE_RECORD 0x02 + +#define AUDIO_PROP_FULLDUPLEX 0x01 + +#define AUDIO_ENCODING_ULAW 1 +#define AUDIO_ENCODING_ALAW 2 +#define AUDIO_ENCODING_SLINEAR_LE 6 +#define AUDIO_ENCODING_SLINEAR_BE 7 +#define AUDIO_ENCODING_ULINEAR_LE 8 +#define AUDIO_ENCODING_ULINEAR_BE 9 + +#endif /* FreeBSD */ + + +#if defined(__NetBSD__) || defined(__OpenBSD__) + +USB_DECLARE_DRIVER(uaudio); + +#elif defined(__FreeBSD__) + +USB_DECLARE_DRIVER_INIT(uaudio, + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(bus_print_child, bus_generic_print_child) + ); +#endif + + +USB_MATCH(uaudio) +{ + USB_MATCH_START(uaudio, uaa); + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return UMATCH_NONE; + + id = usbd_get_interface_descriptor(uaa->iface); + /* Trigger on the control interface. */ + if (id == NULL || + id->bInterfaceClass != UICLASS_AUDIO || + id->bInterfaceSubClass != UISUBCLASS_AUDIOCONTROL || + (usbd_get_quirks(uaa->device)->uq_flags & UQ_BAD_AUDIO)) + return UMATCH_NONE; + + return UMATCH_IFACECLASS_IFACESUBCLASS; +} + +USB_ATTACH(uaudio) +{ + USB_ATTACH_START(uaudio, sc, uaa); + usb_interface_descriptor_t *id; + usb_config_descriptor_t *cdesc; +#if !defined(__FreeBSD__) + char devinfo[1024]; +#endif + usbd_status err; + int i, j, found; + +#if defined(__FreeBSD__) + sc->sc_dev = self; +#else + usbd_devinfo(uaa->device, 0, devinfo, sizeof(devinfo)); + printf(": %s\n", devinfo); +#endif + + sc->sc_udev = uaa->device; + sc->sc_vendor = uaa->vendor; + sc->sc_product = uaa->product; + sc->sc_release = uaa->release; +#if defined(__FreeBSD__) + if (resource_int_value(device_get_name(sc->sc_dev), + device_get_unit(sc->sc_dev), "async", &i) == 0 && i != 0) + sc->async = 1; + else + sc->async = 0; +#endif + + cdesc = usbd_get_config_descriptor(sc->sc_udev); + if (cdesc == NULL) { + printf("%s: failed to get configuration descriptor\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + + err = uaudio_identify(sc, cdesc); + if (err) { + printf("%s: audio descriptors make no sense, error=%d\n", + device_get_nameunit(sc->sc_dev), err); + return ENXIO; + } + + sc->sc_ac_ifaceh = uaa->iface; + /* Pick up the AS interface. */ + for (i = 0; i < uaa->nifaces; i++) { + if (uaa->ifaces[i] == NULL) + continue; + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if (id == NULL) + continue; + found = 0; + for (j = 0; j < sc->sc_nalts; j++) { + if (id->bInterfaceNumber == + sc->sc_alts[j].idesc->bInterfaceNumber) { + sc->sc_alts[j].ifaceh = uaa->ifaces[i]; + found = 1; + } + } + if (found) + uaa->ifaces[i] = NULL; + } + + for (j = 0; j < sc->sc_nalts; j++) { + if (sc->sc_alts[j].ifaceh == NULL) { + printf("%s: alt %d missing AS interface(s)\n", + device_get_nameunit(sc->sc_dev), j); + return ENXIO; + } + } + + printf("%s: audio rev %d.%02x\n", device_get_nameunit(sc->sc_dev), + sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); + + sc->sc_playchan.sc = sc->sc_recchan.sc = sc; + sc->sc_playchan.altidx = -1; + sc->sc_recchan.altidx = -1; + + if (usbd_get_quirks(sc->sc_udev)->uq_flags & UQ_AU_NO_FRAC) + sc->sc_altflags |= UA_NOFRAC; + +#ifndef USB_DEBUG + if (bootverbose) +#endif + printf("%s: %d mixer controls\n", device_get_nameunit(sc->sc_dev), + sc->sc_nctls); + +#if !defined(__FreeBSD__) + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, + USBDEV(sc->sc_dev)); +#endif + + DPRINTF(("uaudio_attach: doing audio_attach_mi\n")); +#if defined(__OpenBSD__) + audio_attach_mi(&uaudio_hw_if, sc, &sc->sc_dev); +#elif defined(__NetBSD__) + sc->sc_audiodev = audio_attach_mi(&uaudio_hw_if, sc, &sc->sc_dev); +#elif defined(__FreeBSD__) + sc->sc_dying = 0; + if (audio_attach_mi(sc->sc_dev)) { + printf("audio_attach_mi failed\n"); + return ENXIO; + } +#endif + +#if defined(__FreeBSD__) + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->sc_dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), + OID_AUTO, "async", CTLFLAG_RW, &sc->async, 0, + "Asynchronous USB request"); +#endif + return 0; +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +int +uaudio_activate(device_t self, enum devact act) +{ + struct uaudio_softc *sc; + int rv; + + sc = (struct uaudio_softc *)self; + rv = 0; + switch (act) { + case DVACT_ACTIVATE: + return EOPNOTSUPP; + + case DVACT_DEACTIVATE: + if (sc->sc_audiodev != NULL) + rv = config_deactivate(sc->sc_audiodev); + sc->sc_dying = 1; + break; + } + return rv; +} +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +int +uaudio_detach(device_t self, int flags) +{ + struct uaudio_softc *sc; + int rv; + + sc = (struct uaudio_softc *)self; + rv = 0; + /* Wait for outstanding requests to complete. */ + usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); + + if (sc->sc_audiodev != NULL) + rv = config_detach(sc->sc_audiodev, flags); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, + USBDEV(sc->sc_dev)); + + return rv; +} +#elif defined(__FreeBSD__) + +USB_DETACH(uaudio) +{ + struct sndcard_func *func; + device_t *devlist = NULL; + int err, i, devcount; + + USB_DETACH_START(uaudio, sc); + + sbuf_delete(&(sc->uaudio_sndstat)); + sc->uaudio_sndstat_flag = 0; + + sc->sc_dying = 1; + +#if 0 /* XXX */ + /* Wait for outstanding requests to complete. */ + usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); +#endif + + err = bus_generic_detach(sc->sc_dev); + + if (err == 0 && + device_get_children(sc->sc_dev, &devlist, &devcount) == 0) { + for (i = 0; devlist != NULL && i < devcount; i++) { + func = device_get_ivars(devlist[i]); + if (func != NULL && func->func == SCF_PCM && + func->varinfo == NULL) { + device_set_ivars(devlist[i], NULL); + free(func, M_DEVBUF); + device_delete_child(sc->sc_dev, devlist[i]); + } + } + free(devlist, M_TEMP); + } + + return err; +} +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int +uaudio_query_encoding(void *addr, struct audio_encoding *fp) +{ + struct uaudio_softc *sc; + int flags; + int idx; + + sc = addr; + flags = sc->sc_altflags; + if (sc->sc_dying) + return EIO; + + if (sc->sc_nalts == 0 || flags == 0) + return ENXIO; + + idx = fp->index; + switch (idx) { + case 0: + strlcpy(fp->name, AudioEulinear, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_ULINEAR; + fp->precision = 8; + fp->flags = flags&HAS_8U ? 0 : AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 1: + strlcpy(fp->name, AudioEmulaw, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_ULAW; + fp->precision = 8; + fp->flags = flags&HAS_MULAW ? 0 : AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 2: + strlcpy(fp->name, AudioEalaw, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_ALAW; + fp->precision = 8; + fp->flags = flags&HAS_ALAW ? 0 : AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 3: + strlcpy(fp->name, AudioEslinear, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_SLINEAR; + fp->precision = 8; + fp->flags = flags&HAS_8 ? 0 : AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 4: + strlcpy(fp->name, AudioEslinear_le, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_SLINEAR_LE; + fp->precision = 16; + fp->flags = 0; + return (0); + case 5: + strlcpy(fp->name, AudioEulinear_le, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_ULINEAR_LE; + fp->precision = 16; + fp->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 6: + strlcpy(fp->name, AudioEslinear_be, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_SLINEAR_BE; + fp->precision = 16; + fp->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 7: + strlcpy(fp->name, AudioEulinear_be, sizeof(fp->name)); + fp->encoding = AUDIO_ENCODING_ULINEAR_BE; + fp->precision = 16; + fp->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + default: + return (EINVAL); + } +} +#endif + +static const usb_interface_descriptor_t * +uaudio_find_iface(const char *buf, int size, int *offsp, int subtype) +{ + const usb_interface_descriptor_t *d; + + while (*offsp < size) { + d = (const void *)(buf + *offsp); + *offsp += d->bLength; + if (d->bDescriptorType == UDESC_INTERFACE && + d->bInterfaceClass == UICLASS_AUDIO && + d->bInterfaceSubClass == subtype) + return d; + } + return NULL; +} + +static void +uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct mixerctl *mc) +{ + int res; + size_t len; + struct mixerctl *nmc; + +#if defined(__FreeBSD__) + if (mc->class < UAC_NCLASSES) { + DPRINTF(("%s: adding %s.%d\n", + __func__, uac_names[mc->class], mc->ctl)); + } else { + DPRINTF(("%s: adding %d\n", __func__, mc->ctl)); + } +#else + if (mc->class < UAC_NCLASSES) { + DPRINTF(("%s: adding %s.%s\n", + __func__, uac_names[mc->class], mc->ctlname)); + } else { + DPRINTF(("%s: adding %s\n", __func__, mc->ctlname)); + } +#endif + len = sizeof(*mc) * (sc->sc_nctls + 1); + nmc = malloc(len, M_USBDEV, M_NOWAIT); + if (nmc == NULL) { + printf("uaudio_mixer_add_ctl: no memory\n"); + return; + } + /* Copy old data, if there was any */ + if (sc->sc_nctls != 0) { + memcpy(nmc, sc->sc_ctls, sizeof(*mc) * (sc->sc_nctls)); + free(sc->sc_ctls, M_USBDEV); + } + sc->sc_ctls = nmc; + + mc->delta = 0; + 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_signext(mc->type, + uaudio_get(sc, GET_MIN, UT_READ_CLASS_INTERFACE, + mc->wValue[0], mc->wIndex, + MIX_SIZE(mc->type))); + mc->maxval = 1 + uaudio_signext(mc->type, + uaudio_get(sc, GET_MAX, UT_READ_CLASS_INTERFACE, + mc->wValue[0], mc->wIndex, + MIX_SIZE(mc->type))); + mc->mul = mc->maxval - mc->minval; + if (mc->mul == 0) + mc->mul = 1; + res = uaudio_get(sc, GET_RES, UT_READ_CLASS_INTERFACE, + mc->wValue[0], mc->wIndex, + MIX_SIZE(mc->type)); + if (res > 0) + mc->delta = (res * 255 + mc->mul/2) / mc->mul; + } + + sc->sc_ctls[sc->sc_nctls++] = *mc; + +#ifdef USB_DEBUG + if (uaudiodebug > 2) { + int i; + DPRINTF(("uaudio_mixer_add_ctl: wValue=%04x",mc->wValue[0])); + for (i = 1; i < mc->nchan; i++) + DPRINTF((",%04x", mc->wValue[i])); +#if defined(__FreeBSD__) + DPRINTF((" wIndex=%04x type=%d ctl='%d' " + "min=%d max=%d\n", + mc->wIndex, mc->type, mc->ctl, + mc->minval, mc->maxval)); +#else + DPRINTF((" wIndex=%04x type=%d name='%s' unit='%s' " + "min=%d max=%d\n", + mc->wIndex, mc->type, mc->ctlname, mc->ctlunit, + mc->minval, mc->maxval)); +#endif + } +#endif +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static char * +uaudio_id_name(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ + static char buf[32]; + + snprintf(buf, sizeof(buf), "i%d", id); + return buf; +} +#endif + +#ifdef USB_DEBUG +static void +uaudio_dump_cluster(const struct usb_audio_cluster *cl) +{ + static const char *channel_names[16] = { + "LEFT", "RIGHT", "CENTER", "LFE", + "LEFT_SURROUND", "RIGHT_SURROUND", "LEFT_CENTER", "RIGHT_CENTER", + "SURROUND", "LEFT_SIDE", "RIGHT_SIDE", "TOP", + "RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15", + }; + int cc, i, first; + + cc = UGETW(cl->wChannelConfig); + printf("cluster: bNrChannels=%u wChannelConfig=0x%.4x", + cl->bNrChannels, cc); + first = TRUE; + for (i = 0; cc != 0; i++) { + if (cc & 1) { + printf("%c%s", first ? '<' : ',', channel_names[i]); + first = FALSE; + } + cc = cc >> 1; + } + printf("> iChannelNames=%u", cl->iChannelNames); +} +#endif + +static struct usb_audio_cluster +uaudio_get_cluster(int id, const struct io_terminal *iot) +{ + struct usb_audio_cluster r; + const usb_descriptor_t *dp; + int i; + + for (i = 0; i < 25; i++) { /* avoid infinite loops */ + dp = iot[id].d.desc; + if (dp == 0) + goto bad; + switch (dp->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + r.bNrChannels = iot[id].d.it->bNrChannels; + USETW(r.wChannelConfig, UGETW(iot[id].d.it->wChannelConfig)); + r.iChannelNames = iot[id].d.it->iChannelNames; + return r; + case UDESCSUB_AC_OUTPUT: + id = iot[id].d.ot->bSourceId; + break; + case UDESCSUB_AC_MIXER: + r = *(const struct usb_audio_cluster *) + &iot[id].d.mu->baSourceId[iot[id].d.mu->bNrInPins]; + return r; + case UDESCSUB_AC_SELECTOR: + /* XXX This is not really right */ + id = iot[id].d.su->baSourceId[0]; + break; + case UDESCSUB_AC_FEATURE: + id = iot[id].d.fu->bSourceId; + break; + case UDESCSUB_AC_PROCESSING: + r = *(const struct usb_audio_cluster *) + &iot[id].d.pu->baSourceId[iot[id].d.pu->bNrInPins]; + return r; + case UDESCSUB_AC_EXTENSION: + r = *(const struct usb_audio_cluster *) + &iot[id].d.eu->baSourceId[iot[id].d.eu->bNrInPins]; + return r; + default: + goto bad; + } + } + bad: + printf("uaudio_get_cluster: bad data\n"); + memset(&r, 0, sizeof r); + return r; + +} + +static void +uaudio_add_input(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ +#ifdef USB_DEBUG + const struct usb_audio_input_terminal *d = iot[id].d.it; + + DPRINTFN(2,("uaudio_add_input: bTerminalId=%d wTerminalType=0x%04x " + "bAssocTerminal=%d bNrChannels=%d wChannelConfig=%d " + "iChannelNames=%d iTerminal=%d\n", + d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, + d->bNrChannels, UGETW(d->wChannelConfig), + d->iChannelNames, d->iTerminal)); +#endif +} + +static void +uaudio_add_output(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ +#ifdef USB_DEBUG + const struct usb_audio_output_terminal *d; + + d = iot[id].d.ot; + DPRINTFN(2,("uaudio_add_output: bTerminalId=%d wTerminalType=0x%04x " + "bAssocTerminal=%d bSourceId=%d iTerminal=%d\n", + d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, + d->bSourceId, d->iTerminal)); +#endif +} + +static void +uaudio_add_mixer(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ + const struct usb_audio_mixer_unit *d = iot[id].d.mu; + const struct usb_audio_mixer_unit_1 *d1; + int c, chs, ichs, ochs, i, o, bno, p, mo, mc, k; + const uByte *bm; + struct mixerctl mix; + + DPRINTFN(2,("uaudio_add_mixer: bUnitId=%d bNrInPins=%d\n", + d->bUnitId, d->bNrInPins)); + + /* Compute the number of input channels */ + ichs = 0; + for (i = 0; i < d->bNrInPins; i++) + ichs += uaudio_get_cluster(d->baSourceId[i], iot).bNrChannels; + + /* and the number of output channels */ + d1 = (const struct usb_audio_mixer_unit_1 *)&d->baSourceId[d->bNrInPins]; + ochs = d1->bNrChannels; + DPRINTFN(2,("uaudio_add_mixer: ichs=%d ochs=%d\n", ichs, ochs)); + + bm = d1->bmControls; + mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); + uaudio_determine_class(&iot[id], &mix); + mix.type = MIX_SIGNED_16; +#if !defined(__FreeBSD__) /* XXXXX */ + mix.ctlunit = AudioNvolume; +#endif + +#define BIT(bno) ((bm[bno / 8] >> (7 - bno % 8)) & 1) + for (p = i = 0; i < d->bNrInPins; i++) { + chs = uaudio_get_cluster(d->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(bno)) + mo++; + } + if (mo == 1) + mc++; + } + if (mc == chs && chs <= MIX_MAX_CHAN) { + k = 0; + for (c = 0; c < chs; c++) + for (o = 0; o < ochs; o++) { + bno = (p + c) * ochs + o; + if (BIT(bno)) + mix.wValue[k++] = + MAKE(p+c+1, o+1); + } +#if !defined(__FreeBSD__) + snprintf(mix.ctlname, sizeof(mix.ctlname), "mix%d-%s", + d->bUnitId, uaudio_id_name(sc, iot, + d->baSourceId[i])); +#endif + mix.nchan = chs; + uaudio_mixer_add_ctl(sc, &mix); + } else { + /* XXX */ + } +#undef BIT + p += chs; + } + +} + +static void +uaudio_add_selector(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ + const struct usb_audio_selector_unit *d; + struct mixerctl mix; +#if !defined(__FreeBSD__) + int i, wp; +#else + int i; + struct mixerctl dummy; +#endif + + d = iot[id].d.su; + DPRINTFN(2,("uaudio_add_selector: bUnitId=%d bNrInPins=%d\n", + d->bUnitId, d->bNrInPins)); + mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); + mix.wValue[0] = MAKE(0, 0); + uaudio_determine_class(&iot[id], &mix); + mix.nchan = 1; + mix.type = MIX_SELECTOR; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ + mix.minval = 1; + mix.maxval = d->bNrInPins; + mix.mul = mix.maxval - mix.minval; + for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) { + mix.slctrtype[i] = SOUND_MIXER_NRDEVICES; + } + for (i = mix.minval; i <= mix.maxval; i++) { + mix.slctrtype[i - 1] = uaudio_feature_name(&iot[d->baSourceId[i - 1]], &dummy); + } +#else + mix.ctlunit = ""; + mix.minval = 1; + mix.maxval = d->bNrInPins; + mix.mul = mix.maxval - mix.minval; + wp = snprintf(mix.ctlname, MAX_AUDIO_DEV_LEN, "sel%d-", d->bUnitId); + for (i = 1; i <= d->bNrInPins; i++) { + wp += snprintf(mix.ctlname + wp, MAX_AUDIO_DEV_LEN - wp, + "i%d", d->baSourceId[i - 1]); + if (wp > MAX_AUDIO_DEV_LEN - 1) + break; + } +#endif + uaudio_mixer_add_ctl(sc, &mix); +} + +#ifdef USB_DEBUG +static const char * +uaudio_get_terminal_name(int terminal_type) +{ + static char buf[100]; + + switch (terminal_type) { + /* USB terminal types */ + case UAT_UNDEFINED: return "UAT_UNDEFINED"; + case UAT_STREAM: return "UAT_STREAM"; + case UAT_VENDOR: return "UAT_VENDOR"; + /* input terminal types */ + case UATI_UNDEFINED: return "UATI_UNDEFINED"; + case UATI_MICROPHONE: return "UATI_MICROPHONE"; + case UATI_DESKMICROPHONE: return "UATI_DESKMICROPHONE"; + case UATI_PERSONALMICROPHONE: return "UATI_PERSONALMICROPHONE"; + case UATI_OMNIMICROPHONE: return "UATI_OMNIMICROPHONE"; + case UATI_MICROPHONEARRAY: return "UATI_MICROPHONEARRAY"; + case UATI_PROCMICROPHONEARR: return "UATI_PROCMICROPHONEARR"; + /* output terminal types */ + case UATO_UNDEFINED: return "UATO_UNDEFINED"; + case UATO_SPEAKER: return "UATO_SPEAKER"; + case UATO_HEADPHONES: return "UATO_HEADPHONES"; + case UATO_DISPLAYAUDIO: return "UATO_DISPLAYAUDIO"; + case UATO_DESKTOPSPEAKER: return "UATO_DESKTOPSPEAKER"; + case UATO_ROOMSPEAKER: return "UATO_ROOMSPEAKER"; + case UATO_COMMSPEAKER: return "UATO_COMMSPEAKER"; + case UATO_SUBWOOFER: return "UATO_SUBWOOFER"; + /* bidir terminal types */ + case UATB_UNDEFINED: return "UATB_UNDEFINED"; + case UATB_HANDSET: return "UATB_HANDSET"; + case UATB_HEADSET: return "UATB_HEADSET"; + case UATB_SPEAKERPHONE: return "UATB_SPEAKERPHONE"; + case UATB_SPEAKERPHONEESUP: return "UATB_SPEAKERPHONEESUP"; + case UATB_SPEAKERPHONEECANC: return "UATB_SPEAKERPHONEECANC"; + /* telephony terminal types */ + case UATT_UNDEFINED: return "UATT_UNDEFINED"; + case UATT_PHONELINE: return "UATT_PHONELINE"; + case UATT_TELEPHONE: return "UATT_TELEPHONE"; + case UATT_DOWNLINEPHONE: return "UATT_DOWNLINEPHONE"; + /* external terminal types */ + case UATE_UNDEFINED: return "UATE_UNDEFINED"; + case UATE_ANALOGCONN: return "UATE_ANALOGCONN"; + case UATE_LINECONN: return "UATE_LINECONN"; + case UATE_LEGACYCONN: return "UATE_LEGACYCONN"; + case UATE_DIGITALAUIFC: return "UATE_DIGITALAUIFC"; + case UATE_SPDIF: return "UATE_SPDIF"; + case UATE_1394DA: return "UATE_1394DA"; + case UATE_1394DV: return "UATE_1394DV"; + /* embedded function terminal types */ + case UATF_UNDEFINED: return "UATF_UNDEFINED"; + case UATF_CALIBNOISE: return "UATF_CALIBNOISE"; + case UATF_EQUNOISE: return "UATF_EQUNOISE"; + case UATF_CDPLAYER: return "UATF_CDPLAYER"; + case UATF_DAT: return "UATF_DAT"; + case UATF_DCC: return "UATF_DCC"; + case UATF_MINIDISK: return "UATF_MINIDISK"; + case UATF_ANALOGTAPE: return "UATF_ANALOGTAPE"; + case UATF_PHONOGRAPH: return "UATF_PHONOGRAPH"; + case UATF_VCRAUDIO: return "UATF_VCRAUDIO"; + case UATF_VIDEODISCAUDIO: return "UATF_VIDEODISCAUDIO"; + case UATF_DVDAUDIO: return "UATF_DVDAUDIO"; + case UATF_TVTUNERAUDIO: return "UATF_TVTUNERAUDIO"; + case UATF_SATELLITE: return "UATF_SATELLITE"; + case UATF_CABLETUNER: return "UATF_CABLETUNER"; + case UATF_DSS: return "UATF_DSS"; + case UATF_RADIORECV: return "UATF_RADIORECV"; + case UATF_RADIOXMIT: return "UATF_RADIOXMIT"; + case UATF_MULTITRACK: return "UATF_MULTITRACK"; + case UATF_SYNTHESIZER: return "UATF_SYNTHESIZER"; + default: + snprintf(buf, sizeof(buf), "unknown type (0x%.4x)", terminal_type); + return buf; + } +} +#endif + +static int +uaudio_determine_class(const struct io_terminal *iot, struct mixerctl *mix) +{ + int terminal_type; + + if (iot == NULL || iot->output == NULL) { + mix->class = UAC_OUTPUT; + return 0; + } + terminal_type = 0; + if (iot->output->size == 1) + terminal_type = iot->output->terminals[0]; + /* + * If the only output terminal is USB, + * the class is UAC_RECORD. + */ + if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { + mix->class = UAC_RECORD; + if (iot->inputs_size == 1 + && iot->inputs[0] != NULL + && iot->inputs[0]->size == 1) + return iot->inputs[0]->terminals[0]; + else + return 0; + } + /* + * If the ultimate destination of the unit is just one output + * terminal and the unit is connected to the output terminal + * directly, the class is UAC_OUTPUT. + */ + if (terminal_type != 0 && iot->direct) { + mix->class = UAC_OUTPUT; + return terminal_type; + } + /* + * If the unit is connected to just one input terminal, + * the class is UAC_INPUT. + */ + if (iot->inputs_size == 1 && iot->inputs[0] != NULL + && iot->inputs[0]->size == 1) { + mix->class = UAC_INPUT; + return iot->inputs[0]->terminals[0]; + } + /* + * Otherwise, the class is UAC_OUTPUT. + */ + mix->class = UAC_OUTPUT; + return terminal_type; +} + +#if defined(__FreeBSD__) +static int +uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) +{ + int terminal_type; + + terminal_type = uaudio_determine_class(iot, mix); + if (mix->class == UAC_RECORD && terminal_type == 0) + return SOUND_MIXER_IMIX; + DPRINTF(("%s: terminal_type=%s\n", __func__, + uaudio_get_terminal_name(terminal_type))); + switch (terminal_type) { + case UAT_STREAM: + return SOUND_MIXER_PCM; + + case UATI_MICROPHONE: + case UATI_DESKMICROPHONE: + case UATI_PERSONALMICROPHONE: + case UATI_OMNIMICROPHONE: + case UATI_MICROPHONEARRAY: + case UATI_PROCMICROPHONEARR: + return SOUND_MIXER_MIC; + + case UATO_SPEAKER: + case UATO_DESKTOPSPEAKER: + case UATO_ROOMSPEAKER: + case UATO_COMMSPEAKER: + return SOUND_MIXER_SPEAKER; + + case UATE_ANALOGCONN: + case UATE_LINECONN: + case UATE_LEGACYCONN: + return SOUND_MIXER_LINE; + + case UATE_DIGITALAUIFC: + case UATE_SPDIF: + case UATE_1394DA: + case UATE_1394DV: + return SOUND_MIXER_ALTPCM; + + case UATF_CDPLAYER: + return SOUND_MIXER_CD; + + case UATF_SYNTHESIZER: + return SOUND_MIXER_SYNTH; + + case UATF_VIDEODISCAUDIO: + case UATF_DVDAUDIO: + case UATF_TVTUNERAUDIO: + return SOUND_MIXER_VIDEO; + +/* telephony terminal types */ + case UATT_UNDEFINED: + case UATT_PHONELINE: + case UATT_TELEPHONE: + case UATT_DOWNLINEPHONE: + return SOUND_MIXER_PHONEIN; +/* return SOUND_MIXER_PHONEOUT;*/ + + case UATF_RADIORECV: + case UATF_RADIOXMIT: + return SOUND_MIXER_RADIO; + + case UAT_UNDEFINED: + case UAT_VENDOR: + case UATI_UNDEFINED: +/* output terminal types */ + case UATO_UNDEFINED: + case UATO_DISPLAYAUDIO: + case UATO_SUBWOOFER: + case UATO_HEADPHONES: +/* bidir terminal types */ + case UATB_UNDEFINED: + case UATB_HANDSET: + case UATB_HEADSET: + case UATB_SPEAKERPHONE: + case UATB_SPEAKERPHONEESUP: + case UATB_SPEAKERPHONEECANC: +/* external terminal types */ + case UATE_UNDEFINED: +/* embedded function terminal types */ + case UATF_UNDEFINED: + case UATF_CALIBNOISE: + case UATF_EQUNOISE: + case UATF_DAT: + case UATF_DCC: + case UATF_MINIDISK: + case UATF_ANALOGTAPE: + case UATF_PHONOGRAPH: + case UATF_VCRAUDIO: + case UATF_SATELLITE: + case UATF_CABLETUNER: + case UATF_DSS: + case UATF_MULTITRACK: + case 0xffff: + default: + DPRINTF(("%s: 'master' for 0x%.4x\n", __func__, terminal_type)); + return SOUND_MIXER_VOLUME; + } + return SOUND_MIXER_VOLUME; +} +#else +static const char * +uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) +{ + int terminal_type; + + terminal_type = uaudio_determine_class(iot, mix); + if (mix->class == UAC_RECORD && terminal_type == 0) + return AudioNmixerout; + DPRINTF(("%s: terminal_type=%s\n", __func__, + uaudio_get_terminal_name(terminal_type))); + switch (terminal_type) { + case UAT_STREAM: + return AudioNdac; + + case UATI_MICROPHONE: + case UATI_DESKMICROPHONE: + case UATI_PERSONALMICROPHONE: + case UATI_OMNIMICROPHONE: + case UATI_MICROPHONEARRAY: + case UATI_PROCMICROPHONEARR: + return AudioNmicrophone; + + case UATO_SPEAKER: + case UATO_DESKTOPSPEAKER: + case UATO_ROOMSPEAKER: + case UATO_COMMSPEAKER: + return AudioNspeaker; + + case UATO_HEADPHONES: + return AudioNheadphone; + + case UATO_SUBWOOFER: + return AudioNlfe; + + /* telephony terminal types */ + case UATT_UNDEFINED: + case UATT_PHONELINE: + case UATT_TELEPHONE: + case UATT_DOWNLINEPHONE: + return "phone"; + + case UATE_ANALOGCONN: + case UATE_LINECONN: + case UATE_LEGACYCONN: + return AudioNline; + + case UATE_DIGITALAUIFC: + case UATE_SPDIF: + case UATE_1394DA: + case UATE_1394DV: + return AudioNaux; + + case UATF_CDPLAYER: + return AudioNcd; + + case UATF_SYNTHESIZER: + return AudioNfmsynth; + + case UATF_VIDEODISCAUDIO: + case UATF_DVDAUDIO: + case UATF_TVTUNERAUDIO: + return AudioNvideo; + + case UAT_UNDEFINED: + case UAT_VENDOR: + case UATI_UNDEFINED: +/* output terminal types */ + case UATO_UNDEFINED: + case UATO_DISPLAYAUDIO: +/* bidir terminal types */ + case UATB_UNDEFINED: + case UATB_HANDSET: + case UATB_HEADSET: + case UATB_SPEAKERPHONE: + case UATB_SPEAKERPHONEESUP: + case UATB_SPEAKERPHONEECANC: +/* external terminal types */ + case UATE_UNDEFINED: +/* embedded function terminal types */ + case UATF_UNDEFINED: + case UATF_CALIBNOISE: + case UATF_EQUNOISE: + case UATF_DAT: + case UATF_DCC: + case UATF_MINIDISK: + case UATF_ANALOGTAPE: + case UATF_PHONOGRAPH: + case UATF_VCRAUDIO: + case UATF_SATELLITE: + case UATF_CABLETUNER: + case UATF_DSS: + case UATF_RADIORECV: + case UATF_RADIOXMIT: + case UATF_MULTITRACK: + case 0xffff: + default: + DPRINTF(("%s: 'master' for 0x%.4x\n", __func__, terminal_type)); + return AudioNmaster; + } + return AudioNmaster; +} +#endif + +static void +uaudio_add_feature(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ + const struct usb_audio_feature_unit *d; + const uByte *ctls; + int ctlsize; + int nchan; + u_int fumask, mmask, cmask; + struct mixerctl mix; + int chan, ctl, i, unit; +#if defined(__FreeBSD__) + int mixernumber; +#else + const char *mixername; +#endif + +#define GET(i) (ctls[(i)*ctlsize] | \ + (ctlsize > 1 ? ctls[(i)*ctlsize+1] << 8 : 0)) + d = iot[id].d.fu; + ctls = d->bmaControls; + ctlsize = d->bControlSize; + nchan = (d->bLength - 7) / ctlsize; + mmask = GET(0); + /* Figure out what we can control */ + for (cmask = 0, chan = 1; chan < nchan; chan++) { + DPRINTFN(9,("uaudio_add_feature: chan=%d mask=%x\n", + chan, GET(chan))); + cmask |= GET(chan); + } + +#if !defined(__FreeBSD__) + DPRINTFN(1,("uaudio_add_feature: bUnitId=%d, " + "%d channels, mmask=0x%04x, cmask=0x%04x\n", + d->bUnitId, nchan, mmask, cmask)); +#endif + + if (nchan > MIX_MAX_CHAN) + nchan = MIX_MAX_CHAN; + unit = d->bUnitId; + mix.wIndex = MAKE(unit, sc->sc_ac_iface); + for (ctl = MUTE_CONTROL; ctl < LOUDNESS_CONTROL; ctl++) { + fumask = FU_MASK(ctl); + DPRINTFN(4,("uaudio_add_feature: ctl=%d fumask=0x%04x\n", + ctl, fumask)); + if (mmask & fumask) { + mix.nchan = 1; + mix.wValue[0] = MAKE(ctl, 0); + } else if (cmask & fumask) { + mix.nchan = nchan - 1; + for (i = 1; i < nchan; i++) { + if (GET(i) & fumask) + mix.wValue[i-1] = MAKE(ctl, i); + else + mix.wValue[i-1] = -1; + } + } else { + continue; + } +#undef GET + +#if defined(__FreeBSD__) + mixernumber = uaudio_feature_name(&iot[id], &mix); +#else + mixername = uaudio_feature_name(&iot[id], &mix); +#endif + switch (ctl) { + case MUTE_CONTROL: + mix.type = MIX_ON_OFF; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_NRDEVICES; +#else + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNmute); +#endif + break; + case VOLUME_CONTROL: + mix.type = MIX_SIGNED_16; +#if defined(__FreeBSD__) + mix.ctl = mixernumber; +#else + mix.ctlunit = AudioNvolume; + strlcpy(mix.ctlname, mixername, sizeof(mix.ctlname)); +#endif + break; + case BASS_CONTROL: + mix.type = MIX_SIGNED_8; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_BASS; +#else + mix.ctlunit = AudioNbass; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNbass); +#endif + break; + case MID_CONTROL: + mix.type = MIX_SIGNED_8; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ +#else + mix.ctlunit = AudioNmid; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNmid); +#endif + break; + case TREBLE_CONTROL: + mix.type = MIX_SIGNED_8; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_TREBLE; +#else + mix.ctlunit = AudioNtreble; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNtreble); +#endif + break; + case GRAPHIC_EQUALIZER_CONTROL: + continue; /* XXX don't add anything */ + break; + case AGC_CONTROL: + mix.type = MIX_ON_OFF; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ +#else + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), "%s.%s", + mixername, AudioNagc); +#endif + break; + case DELAY_CONTROL: + mix.type = MIX_UNSIGNED_16; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ +#else + mix.ctlunit = "4 ms"; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNdelay); +#endif + break; + case BASS_BOOST_CONTROL: + mix.type = MIX_ON_OFF; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ +#else + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNbassboost); +#endif + break; + case LOUDNESS_CONTROL: + mix.type = MIX_ON_OFF; +#if defined(__FreeBSD__) + mix.ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ +#else + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), + "%s.%s", mixername, AudioNloudness); +#endif + break; + } + uaudio_mixer_add_ctl(sc, &mix); + } +} + +static void +uaudio_add_processing_updown(struct uaudio_softc *sc, + const struct io_terminal *iot, int id) +{ + const struct usb_audio_processing_unit *d; + const struct usb_audio_processing_unit_1 *d1; + const struct usb_audio_processing_unit_updown *ud; + struct mixerctl mix; + int i; + + d = iot[id].d.pu; + d1 = (const struct usb_audio_processing_unit_1 *) + &d->baSourceId[d->bNrInPins]; + ud = (const struct usb_audio_processing_unit_updown *) + &d1->bmControls[d1->bControlSize]; + DPRINTFN(2,("uaudio_add_processing_updown: bUnitId=%d bNrModes=%d\n", + d->bUnitId, ud->bNrModes)); + + if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { + DPRINTF(("uaudio_add_processing_updown: no mode select\n")); + return; + } + + mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); + mix.nchan = 1; + mix.wValue[0] = MAKE(UD_MODE_SELECT_CONTROL, 0); + uaudio_determine_class(&iot[id], &mix); + mix.type = MIX_ON_OFF; /* XXX */ +#if !defined(__FreeBSD__) + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), "pro%d-mode", d->bUnitId); +#endif + + for (i = 0; i < ud->bNrModes; i++) { + DPRINTFN(2,("uaudio_add_processing_updown: i=%d bm=0x%x\n", + i, UGETW(ud->waModes[i]))); + /* XXX */ + } + uaudio_mixer_add_ctl(sc, &mix); +} + +static void +uaudio_add_processing(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ + const struct usb_audio_processing_unit *d; + const struct usb_audio_processing_unit_1 *d1; + int ptype; + struct mixerctl mix; + + d = iot[id].d.pu; + d1 = (const struct usb_audio_processing_unit_1 *) + &d->baSourceId[d->bNrInPins]; + ptype = UGETW(d->wProcessType); + DPRINTFN(2,("uaudio_add_processing: wProcessType=%d bUnitId=%d " + "bNrInPins=%d\n", ptype, d->bUnitId, d->bNrInPins)); + + if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { + mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); + mix.nchan = 1; + mix.wValue[0] = MAKE(XX_ENABLE_CONTROL, 0); + uaudio_determine_class(&iot[id], &mix); + mix.type = MIX_ON_OFF; +#if !defined(__FreeBSD__) + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), "pro%d.%d-enable", + d->bUnitId, ptype); +#endif + uaudio_mixer_add_ctl(sc, &mix); + } + + switch(ptype) { + case UPDOWNMIX_PROCESS: + uaudio_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: +#ifdef USB_DEBUG + printf("uaudio_add_processing: unit %d, type=%d not impl.\n", + d->bUnitId, ptype); +#endif + break; + } +} + +static void +uaudio_add_extension(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +{ + const struct usb_audio_extension_unit *d; + const struct usb_audio_extension_unit_1 *d1; + struct mixerctl mix; + + d = iot[id].d.eu; + d1 = (const struct usb_audio_extension_unit_1 *) + &d->baSourceId[d->bNrInPins]; + DPRINTFN(2,("uaudio_add_extension: bUnitId=%d bNrInPins=%d\n", + d->bUnitId, d->bNrInPins)); + + if (usbd_get_quirks(sc->sc_udev)->uq_flags & UQ_AU_NO_XU) + return; + + if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { + mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); + mix.nchan = 1; + mix.wValue[0] = MAKE(UA_EXT_ENABLE, 0); + uaudio_determine_class(&iot[id], &mix); + mix.type = MIX_ON_OFF; +#if !defined(__FreeBSD__) + mix.ctlunit = ""; + snprintf(mix.ctlname, sizeof(mix.ctlname), "ext%d-enable", + d->bUnitId); +#endif + uaudio_mixer_add_ctl(sc, &mix); + } +} + +static struct terminal_list* +uaudio_merge_terminal_list(const struct io_terminal *iot) +{ + struct terminal_list *tml; + uint16_t *ptm; + int i, len; + + len = 0; + if (iot->inputs == NULL) + return NULL; + for (i = 0; i < iot->inputs_size; i++) { + if (iot->inputs[i] != NULL) + len += iot->inputs[i]->size; + } + tml = malloc(TERMINAL_LIST_SIZE(len), M_TEMP, M_NOWAIT); + if (tml == NULL) { + printf("uaudio_merge_terminal_list: no memory\n"); + return NULL; + } + tml->size = 0; + ptm = tml->terminals; + for (i = 0; i < iot->inputs_size; i++) { + if (iot->inputs[i] == NULL) + continue; + if (iot->inputs[i]->size > len) + break; + memcpy(ptm, iot->inputs[i]->terminals, + iot->inputs[i]->size * sizeof(uint16_t)); + tml->size += iot->inputs[i]->size; + ptm += iot->inputs[i]->size; + len -= iot->inputs[i]->size; + } + return tml; +} + +static struct terminal_list * +uaudio_io_terminaltype(int outtype, struct io_terminal *iot, int id) +{ + struct terminal_list *tml; + struct io_terminal *it; + int src_id, i; + + it = &iot[id]; + if (it->output != NULL) { + /* already has outtype? */ + for (i = 0; i < it->output->size; i++) + if (it->output->terminals[i] == outtype) + return uaudio_merge_terminal_list(it); + tml = malloc(TERMINAL_LIST_SIZE(it->output->size + 1), + M_TEMP, M_NOWAIT); + if (tml == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return uaudio_merge_terminal_list(it); + } + memcpy(tml, it->output, TERMINAL_LIST_SIZE(it->output->size)); + tml->terminals[it->output->size] = outtype; + tml->size++; + free(it->output, M_TEMP); + it->output = tml; + if (it->inputs != NULL) { + for (i = 0; i < it->inputs_size; i++) + if (it->inputs[i] != NULL) + free(it->inputs[i], M_TEMP); + free(it->inputs, M_TEMP); + } + it->inputs_size = 0; + it->inputs = NULL; + } else { /* end `iot[id] != NULL' */ + it->inputs_size = 0; + it->inputs = NULL; + it->output = malloc(TERMINAL_LIST_SIZE(1), M_TEMP, M_NOWAIT); + if (it->output == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + it->output->terminals[0] = outtype; + it->output->size = 1; + it->direct = FALSE; + } + + switch (it->d.desc->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + it->inputs = malloc(sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + tml = malloc(TERMINAL_LIST_SIZE(1), M_TEMP, M_NOWAIT); + if (tml == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + free(it->inputs, M_TEMP); + it->inputs = NULL; + return NULL; + } + it->inputs[0] = tml; + tml->terminals[0] = UGETW(it->d.it->wTerminalType); + tml->size = 1; + it->inputs_size = 1; + return uaudio_merge_terminal_list(it); + case UDESCSUB_AC_FEATURE: + src_id = it->d.fu->bSourceId; + it->inputs = malloc(sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return uaudio_io_terminaltype(outtype, iot, src_id); + } + it->inputs[0] = uaudio_io_terminaltype(outtype, iot, src_id); + it->inputs_size = 1; + return uaudio_merge_terminal_list(it); + case UDESCSUB_AC_OUTPUT: + it->inputs = malloc(sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + src_id = it->d.ot->bSourceId; + it->inputs[0] = uaudio_io_terminaltype(outtype, iot, src_id); + it->inputs_size = 1; + iot[src_id].direct = TRUE; + return NULL; + case UDESCSUB_AC_MIXER: + it->inputs_size = 0; + it->inputs = malloc(sizeof(struct terminal_list *) + * it->d.mu->bNrInPins, M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + for (i = 0; i < it->d.mu->bNrInPins; i++) { + src_id = it->d.mu->baSourceId[i]; + it->inputs[i] = uaudio_io_terminaltype(outtype, iot, + src_id); + it->inputs_size++; + } + return uaudio_merge_terminal_list(it); + case UDESCSUB_AC_SELECTOR: + it->inputs_size = 0; + it->inputs = malloc(sizeof(struct terminal_list *) + * it->d.su->bNrInPins, M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + for (i = 0; i < it->d.su->bNrInPins; i++) { + src_id = it->d.su->baSourceId[i]; + it->inputs[i] = uaudio_io_terminaltype(outtype, iot, + src_id); + it->inputs_size++; + } + return uaudio_merge_terminal_list(it); + case UDESCSUB_AC_PROCESSING: + it->inputs_size = 0; + it->inputs = malloc(sizeof(struct terminal_list *) + * it->d.pu->bNrInPins, M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + for (i = 0; i < it->d.pu->bNrInPins; i++) { + src_id = it->d.pu->baSourceId[i]; + it->inputs[i] = uaudio_io_terminaltype(outtype, iot, + src_id); + it->inputs_size++; + } + return uaudio_merge_terminal_list(it); + case UDESCSUB_AC_EXTENSION: + it->inputs_size = 0; + it->inputs = malloc(sizeof(struct terminal_list *) + * it->d.eu->bNrInPins, M_TEMP, M_NOWAIT); + if (it->inputs == NULL) { + printf("uaudio_io_terminaltype: no memory\n"); + return NULL; + } + for (i = 0; i < it->d.eu->bNrInPins; i++) { + src_id = it->d.eu->baSourceId[i]; + it->inputs[i] = uaudio_io_terminaltype(outtype, iot, + src_id); + it->inputs_size++; + } + return uaudio_merge_terminal_list(it); + case UDESCSUB_AC_HEADER: + default: + return NULL; + } +} + +static usbd_status +uaudio_identify(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) +{ + usbd_status err; + + err = uaudio_identify_ac(sc, cdesc); + if (err) + return err; + return uaudio_identify_as(sc, cdesc); +} + +static void +uaudio_add_alt(struct uaudio_softc *sc, const struct as_info *ai) +{ + size_t len; + struct as_info *nai; + + len = sizeof(*ai) * (sc->sc_nalts + 1); + nai = malloc(len, M_USBDEV, M_NOWAIT); + if (nai == NULL) { + printf("uaudio_add_alt: no memory\n"); + return; + } + /* Copy old data, if there was any */ + if (sc->sc_nalts != 0) { + memcpy(nai, sc->sc_alts, sizeof(*ai) * (sc->sc_nalts)); + free(sc->sc_alts, M_USBDEV); + } + sc->sc_alts = nai; + DPRINTFN(2,("uaudio_add_alt: adding alt=%d, enc=%d\n", + ai->alt, ai->encoding)); + sc->sc_alts[sc->sc_nalts++] = *ai; +} + +static usbd_status +uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, + int size, const usb_interface_descriptor_t *id) +#define offs (*offsp) +{ + const struct usb_audio_streaming_interface_descriptor *asid; + const struct usb_audio_streaming_type1_descriptor *asf1d; + const usb_endpoint_descriptor_audio_t *ed; + const usb_endpoint_descriptor_audio_t *epdesc1; + const struct usb_audio_streaming_endpoint_descriptor *sed; + int format, chan, prec, enc; + int dir, type, sync; + struct as_info ai; + const char *format_str; + + asid = (const void *)(buf + offs); + + if (asid->bDescriptorType != UDESC_CS_INTERFACE || + asid->bDescriptorSubtype != AS_GENERAL) + return USBD_INVAL; + DPRINTF(("uaudio_process_as: asid: bTerminakLink=%d wFormatTag=%d\n", + asid->bTerminalLink, UGETW(asid->wFormatTag))); + offs += asid->bLength; + if (offs > size) + return USBD_INVAL; + + asf1d = (const void *)(buf + offs); + if (asf1d->bDescriptorType != UDESC_CS_INTERFACE || + asf1d->bDescriptorSubtype != FORMAT_TYPE) + return USBD_INVAL; + offs += asf1d->bLength; + if (offs > size) + return USBD_INVAL; + + if (asf1d->bFormatType != FORMAT_TYPE_I) { + printf("%s: ignored setting with type %d format\n", + device_get_nameunit(sc->sc_dev), UGETW(asid->wFormatTag)); + return USBD_NORMAL_COMPLETION; + } + + ed = (const void *)(buf + offs); + if (ed->bDescriptorType != UDESC_ENDPOINT) + return USBD_INVAL; + DPRINTF(("uaudio_process_as: endpoint[0] bLength=%d bDescriptorType=%d " + "bEndpointAddress=%d bmAttributes=0x%x wMaxPacketSize=%d " + "bInterval=%d bRefresh=%d bSynchAddress=%d\n", + ed->bLength, ed->bDescriptorType, ed->bEndpointAddress, + ed->bmAttributes, UGETW(ed->wMaxPacketSize), + ed->bInterval, ed->bRefresh, ed->bSynchAddress)); + offs += ed->bLength; + if (offs > size) + return USBD_INVAL; + if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) + return USBD_INVAL; + + dir = UE_GET_DIR(ed->bEndpointAddress); + type = UE_GET_ISO_TYPE(ed->bmAttributes); + if ((usbd_get_quirks(sc->sc_udev)->uq_flags & UQ_AU_INP_ASYNC) && + dir == UE_DIR_IN && type == UE_ISO_ADAPT) + type = UE_ISO_ASYNC; + + /* We can't handle endpoints that need a sync pipe yet. */ + sync = FALSE; + if (dir == UE_DIR_IN && type == UE_ISO_ADAPT) { + sync = TRUE; +#ifndef UAUDIO_MULTIPLE_ENDPOINTS + printf("%s: ignored input endpoint of type adaptive\n", + device_get_nameunit(sc->sc_dev)); + return USBD_NORMAL_COMPLETION; +#endif + } + if (dir != UE_DIR_IN && type == UE_ISO_ASYNC) { + sync = TRUE; +#ifndef UAUDIO_MULTIPLE_ENDPOINTS + printf("%s: ignored output endpoint of type async\n", + device_get_nameunit(sc->sc_dev)); + return USBD_NORMAL_COMPLETION; +#endif + } + + sed = (const void *)(buf + offs); + if (sed->bDescriptorType != UDESC_CS_ENDPOINT || + sed->bDescriptorSubtype != AS_GENERAL) + return USBD_INVAL; + DPRINTF((" streadming_endpoint: offset=%d bLength=%d\n", offs, sed->bLength)); + offs += sed->bLength; + if (offs > size) + return USBD_INVAL; + +#ifdef UAUDIO_MULTIPLE_ENDPOINTS + if (sync && id->bNumEndpoints <= 1) { + printf("%s: a sync-pipe endpoint but no other endpoint\n", + device_get_nameunit(sc->sc_dev)); + return USBD_INVAL; + } +#endif + if (!sync && id->bNumEndpoints > 1) { + printf("%s: non sync-pipe endpoint but multiple endpoints\n", + device_get_nameunit(sc->sc_dev)); + return USBD_INVAL; + } + epdesc1 = NULL; + if (id->bNumEndpoints > 1) { + epdesc1 = (const void*)(buf + offs); + if (epdesc1->bDescriptorType != UDESC_ENDPOINT) + return USBD_INVAL; + DPRINTF(("uaudio_process_as: endpoint[1] bLength=%d " + "bDescriptorType=%d bEndpointAddress=%d " + "bmAttributes=0x%x wMaxPacketSize=%d bInterval=%d " + "bRefresh=%d bSynchAddress=%d\n", + epdesc1->bLength, epdesc1->bDescriptorType, + epdesc1->bEndpointAddress, epdesc1->bmAttributes, + UGETW(epdesc1->wMaxPacketSize), epdesc1->bInterval, + epdesc1->bRefresh, epdesc1->bSynchAddress)); + offs += epdesc1->bLength; + if (offs > size) + return USBD_INVAL; + if (epdesc1->bSynchAddress != 0) { + printf("%s: invalid endpoint: bSynchAddress=0\n", + device_get_nameunit(sc->sc_dev)); + return USBD_INVAL; + } + if (UE_GET_XFERTYPE(epdesc1->bmAttributes) != UE_ISOCHRONOUS) { + printf("%s: invalid endpoint: bmAttributes=0x%x\n", + device_get_nameunit(sc->sc_dev), epdesc1->bmAttributes); + return USBD_INVAL; + } + if (epdesc1->bEndpointAddress != ed->bSynchAddress) { + printf("%s: invalid endpoint addresses: " + "ep[0]->bSynchAddress=0x%x " + "ep[1]->bEndpointAddress=0x%x\n", + device_get_nameunit(sc->sc_dev), ed->bSynchAddress, + epdesc1->bEndpointAddress); + return USBD_INVAL; + } + /* UE_GET_ADDR(epdesc1->bEndpointAddress), and epdesc1->bRefresh */ + } + + format = UGETW(asid->wFormatTag); + chan = asf1d->bNrChannels; + prec = asf1d->bBitResolution; + if (prec != 8 && prec != 16 && prec != 24 && prec != 32) { + printf("%s: ignored setting with precision %d\n", + device_get_nameunit(sc->sc_dev), prec); + return USBD_NORMAL_COMPLETION; + } + switch (format) { + case UA_FMT_PCM: + if (prec == 8) { + sc->sc_altflags |= HAS_8; + } else if (prec == 16) { + sc->sc_altflags |= HAS_16; + } else if (prec == 24) { + sc->sc_altflags |= HAS_24; + } else if (prec == 32) { + sc->sc_altflags |= HAS_32; + } + enc = AUDIO_ENCODING_SLINEAR_LE; + format_str = "pcm"; + break; + case UA_FMT_PCM8: + enc = AUDIO_ENCODING_ULINEAR_LE; + sc->sc_altflags |= HAS_8U; + format_str = "pcm8"; + break; + case UA_FMT_ALAW: + enc = AUDIO_ENCODING_ALAW; + sc->sc_altflags |= HAS_ALAW; + format_str = "alaw"; + break; + case UA_FMT_MULAW: + enc = AUDIO_ENCODING_ULAW; + sc->sc_altflags |= HAS_MULAW; + format_str = "mulaw"; + break; + case UA_FMT_IEEE_FLOAT: + default: + printf("%s: ignored setting with format %d\n", + device_get_nameunit(sc->sc_dev), format); + return USBD_NORMAL_COMPLETION; + } +#ifdef USB_DEBUG + printf("%s: %s: %dch, %d/%dbit, %s,", device_get_nameunit(sc->sc_dev), + dir == UE_DIR_IN ? "recording" : "playback", + chan, prec, asf1d->bSubFrameSize * 8, format_str); + if (asf1d->bSamFreqType == UA_SAMP_CONTNUOUS) { + printf(" %d-%dHz\n", UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + } else { + int r; + printf(" %d", UA_GETSAMP(asf1d, 0)); + for (r = 1; r < asf1d->bSamFreqType; r++) + printf(",%d", UA_GETSAMP(asf1d, r)); + printf("Hz\n"); + } +#endif +#if defined(__FreeBSD__) + if (sc->uaudio_sndstat_flag != 0) { + sbuf_printf(&(sc->uaudio_sndstat), "\n\t"); + sbuf_printf(&(sc->uaudio_sndstat), + "mode %d:(%s) %dch, %d/%dbit, %s,", + id->bAlternateSetting, + dir == UE_DIR_IN ? "input" : "output", + chan, prec, asf1d->bSubFrameSize * 8, format_str); + if (asf1d->bSamFreqType == UA_SAMP_CONTNUOUS) { + sbuf_printf(&(sc->uaudio_sndstat), " %d-%dHz", + UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + } else { + int r; + sbuf_printf(&(sc->uaudio_sndstat), + " %d", UA_GETSAMP(asf1d, 0)); + for (r = 1; r < asf1d->bSamFreqType; r++) + sbuf_printf(&(sc->uaudio_sndstat), + ",%d", UA_GETSAMP(asf1d, r)); + sbuf_printf(&(sc->uaudio_sndstat), "Hz"); + } + } +#endif + ai.alt = id->bAlternateSetting; + ai.encoding = enc; + ai.attributes = sed->bmAttributes; + ai.idesc = id; + ai.edesc = ed; + ai.edesc1 = epdesc1; + ai.asf1desc = asf1d; + ai.sc_busy = 0; + ai.ifaceh = NULL; + uaudio_add_alt(sc, &ai); +#ifdef USB_DEBUG + if (ai.attributes & UA_SED_FREQ_CONTROL) + DPRINTFN(1, ("uaudio_process_as: FREQ_CONTROL\n")); + if (ai.attributes & UA_SED_PITCH_CONTROL) + DPRINTFN(1, ("uaudio_process_as: PITCH_CONTROL\n")); +#endif + sc->sc_mode |= (dir == UE_DIR_OUT) ? AUMODE_PLAY : AUMODE_RECORD; + + return USBD_NORMAL_COMPLETION; +} +#undef offs + +static usbd_status +uaudio_identify_as(struct uaudio_softc *sc, + const usb_config_descriptor_t *cdesc) +{ + const usb_interface_descriptor_t *id; + const char *buf; + int size, offs; + + size = UGETW(cdesc->wTotalLength); + buf = (const char *)cdesc; + + /* Locate the AudioStreaming interface descriptor. */ + offs = 0; + id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOSTREAM); + if (id == NULL) + return USBD_INVAL; + +#if defined(__FreeBSD__) + sc->uaudio_sndstat_flag = 0; + if (sbuf_new(&(sc->uaudio_sndstat), NULL, 4096, SBUF_AUTOEXTEND) != NULL) + sc->uaudio_sndstat_flag = 1; +#endif + /* Loop through all the alternate settings. */ + while (offs <= size) { + DPRINTFN(2, ("uaudio_identify: interface=%d offset=%d\n", + id->bInterfaceNumber, offs)); + switch (id->bNumEndpoints) { + case 0: + DPRINTFN(2, ("uaudio_identify: AS null alt=%d\n", + id->bAlternateSetting)); + sc->sc_nullalt = id->bAlternateSetting; + break; + case 1: +#ifdef UAUDIO_MULTIPLE_ENDPOINTS + case 2: +#endif + uaudio_process_as(sc, buf, &offs, size, id); + break; + default: + printf("%s: ignored audio interface with %d " + "endpoints\n", + device_get_nameunit(sc->sc_dev), id->bNumEndpoints); + break; + } + id = uaudio_find_iface(buf, size, &offs,UISUBCLASS_AUDIOSTREAM); + if (id == NULL) + break; + } +#if defined(__FreeBSD__) + sbuf_finish(&(sc->uaudio_sndstat)); +#endif + if (offs > size) + return USBD_INVAL; + DPRINTF(("uaudio_identify_as: %d alts available\n", sc->sc_nalts)); + + if (sc->sc_mode == 0) { + printf("%s: no usable endpoint found\n", + device_get_nameunit(sc->sc_dev)); + return USBD_INVAL; + } + + return USBD_NORMAL_COMPLETION; +} + +static usbd_status +uaudio_identify_ac(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) +{ + struct io_terminal* iot; + const usb_interface_descriptor_t *id; + const struct usb_audio_control_descriptor *acdp; + const usb_descriptor_t *dp; + const struct usb_audio_output_terminal *pot; + struct terminal_list *tml; + const char *buf, *ibuf, *ibufend; + int size, offs, aclen, ndps, i, j; + + size = UGETW(cdesc->wTotalLength); + buf = (const char *)cdesc; + + /* Locate the AudioControl interface descriptor. */ + offs = 0; + id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOCONTROL); + if (id == NULL) + return USBD_INVAL; + if (offs + sizeof *acdp > size) + return USBD_INVAL; + sc->sc_ac_iface = id->bInterfaceNumber; + DPRINTFN(2,("uaudio_identify_ac: AC interface is %d\n", sc->sc_ac_iface)); + + /* A class-specific AC interface header should follow. */ + ibuf = buf + offs; + acdp = (const struct usb_audio_control_descriptor *)ibuf; + if (acdp->bDescriptorType != UDESC_CS_INTERFACE || + acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER) + return USBD_INVAL; + aclen = UGETW(acdp->wTotalLength); + if (offs + aclen > size) + return USBD_INVAL; + + if (!(usbd_get_quirks(sc->sc_udev)->uq_flags & UQ_BAD_ADC) && + UGETW(acdp->bcdADC) != UAUDIO_VERSION) + return USBD_INVAL; + + sc->sc_audio_rev = UGETW(acdp->bcdADC); + DPRINTFN(2,("uaudio_identify_ac: found AC header, vers=%03x, len=%d\n", + sc->sc_audio_rev, aclen)); + + sc->sc_nullalt = -1; + + /* Scan through all the AC specific descriptors */ + ibufend = ibuf + aclen; + dp = (const usb_descriptor_t *)ibuf; + ndps = 0; + iot = malloc(sizeof(struct io_terminal) * 256, M_TEMP, M_NOWAIT | M_ZERO); + if (iot == NULL) { + printf("%s: no memory\n", __func__); + return USBD_NOMEM; + } + for (;;) { + ibuf += dp->bLength; + if (ibuf >= ibufend) + break; + dp = (const usb_descriptor_t *)ibuf; + if (ibuf + dp->bLength > ibufend) { + free(iot, M_TEMP); + return USBD_INVAL; + } + if (dp->bDescriptorType != UDESC_CS_INTERFACE) { + printf("uaudio_identify_ac: skip desc type=0x%02x\n", + dp->bDescriptorType); + continue; + } + i = ((const struct usb_audio_input_terminal *)dp)->bTerminalId; + iot[i].d.desc = dp; + if (i > ndps) + ndps = i; + } + ndps++; + + /* construct io_terminal */ + for (i = 0; i < ndps; i++) { + dp = iot[i].d.desc; + if (dp == NULL) + continue; + if (dp->bDescriptorSubtype != UDESCSUB_AC_OUTPUT) + continue; + pot = iot[i].d.ot; + tml = uaudio_io_terminaltype(UGETW(pot->wTerminalType), iot, i); + if (tml != NULL) + free(tml, M_TEMP); + } + +#ifdef USB_DEBUG + for (i = 0; i < 256; i++) { + struct usb_audio_cluster cluster; + + if (iot[i].d.desc == NULL) + continue; + printf("id %d:\t", i); + switch (iot[i].d.desc->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + printf("AC_INPUT type=%s\n", uaudio_get_terminal_name + (UGETW(iot[i].d.it->wTerminalType))); + printf("\t"); + cluster = uaudio_get_cluster(i, iot); + uaudio_dump_cluster(&cluster); + printf("\n"); + break; + case UDESCSUB_AC_OUTPUT: + printf("AC_OUTPUT type=%s ", uaudio_get_terminal_name + (UGETW(iot[i].d.ot->wTerminalType))); + printf("src=%d\n", iot[i].d.ot->bSourceId); + break; + case UDESCSUB_AC_MIXER: + printf("AC_MIXER src="); + for (j = 0; j < iot[i].d.mu->bNrInPins; j++) + printf("%d ", iot[i].d.mu->baSourceId[j]); + printf("\n\t"); + cluster = uaudio_get_cluster(i, iot); + uaudio_dump_cluster(&cluster); + printf("\n"); + break; + case UDESCSUB_AC_SELECTOR: + printf("AC_SELECTOR src="); + for (j = 0; j < iot[i].d.su->bNrInPins; j++) + printf("%d ", iot[i].d.su->baSourceId[j]); + printf("\n"); + break; + case UDESCSUB_AC_FEATURE: + printf("AC_FEATURE src=%d\n", iot[i].d.fu->bSourceId); + break; + case UDESCSUB_AC_PROCESSING: + printf("AC_PROCESSING src="); + for (j = 0; j < iot[i].d.pu->bNrInPins; j++) + printf("%d ", iot[i].d.pu->baSourceId[j]); + printf("\n\t"); + cluster = uaudio_get_cluster(i, iot); + uaudio_dump_cluster(&cluster); + printf("\n"); + break; + case UDESCSUB_AC_EXTENSION: + printf("AC_EXTENSION src="); + for (j = 0; j < iot[i].d.eu->bNrInPins; j++) + printf("%d ", iot[i].d.eu->baSourceId[j]); + printf("\n\t"); + cluster = uaudio_get_cluster(i, iot); + uaudio_dump_cluster(&cluster); + printf("\n"); + break; + default: + printf("unknown audio control (subtype=%d)\n", + iot[i].d.desc->bDescriptorSubtype); + } + for (j = 0; j < iot[i].inputs_size; j++) { + int k; + printf("\tinput%d: ", j); + tml = iot[i].inputs[j]; + if (tml == NULL) { + printf("NULL\n"); + continue; + } + for (k = 0; k < tml->size; k++) + printf("%s ", uaudio_get_terminal_name + (tml->terminals[k])); + printf("\n"); + } + printf("\toutput: "); + tml = iot[i].output; + for (j = 0; j < tml->size; j++) + printf("%s ", uaudio_get_terminal_name(tml->terminals[j])); + printf("\n"); + } +#endif + + for (i = 0; i < ndps; i++) { + dp = iot[i].d.desc; + if (dp == NULL) + continue; + DPRINTF(("uaudio_identify_ac: id=%d subtype=%d\n", + i, dp->bDescriptorSubtype)); + switch (dp->bDescriptorSubtype) { + case UDESCSUB_AC_HEADER: + printf("uaudio_identify_ac: unexpected AC header\n"); + break; + case UDESCSUB_AC_INPUT: + uaudio_add_input(sc, iot, i); + break; + case UDESCSUB_AC_OUTPUT: + uaudio_add_output(sc, iot, i); + break; + case UDESCSUB_AC_MIXER: + uaudio_add_mixer(sc, iot, i); + break; + case UDESCSUB_AC_SELECTOR: + uaudio_add_selector(sc, iot, i); + break; + case UDESCSUB_AC_FEATURE: + uaudio_add_feature(sc, iot, i); + break; + case UDESCSUB_AC_PROCESSING: + uaudio_add_processing(sc, iot, i); + break; + case UDESCSUB_AC_EXTENSION: + uaudio_add_extension(sc, iot, i); + break; + default: + printf("uaudio_identify_ac: bad AC desc subtype=0x%02x\n", + dp->bDescriptorSubtype); + break; + } + } + + /* delete io_terminal */ + for (i = 0; i < 256; i++) { + if (iot[i].d.desc == NULL) + continue; + if (iot[i].inputs != NULL) { + for (j = 0; j < iot[i].inputs_size; j++) { + if (iot[i].inputs[j] != NULL) + free(iot[i].inputs[j], M_TEMP); + } + free(iot[i].inputs, M_TEMP); + } + if (iot[i].output != NULL) + free(iot[i].output, M_TEMP); + iot[i].d.desc = NULL; + } + free(iot, M_TEMP); + + return USBD_NORMAL_COMPLETION; +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int +uaudio_query_devinfo(void *addr, mixer_devinfo_t *mi) +{ + struct uaudio_softc *sc; + struct mixerctl *mc; + int n, nctls, i; + + sc = addr; + DPRINTFN(2,("uaudio_query_devinfo: index=%d\n", mi->index)); + if (sc->sc_dying) + return EIO; + + n = mi->index; + nctls = sc->sc_nctls; + + switch (n) { + case UAC_OUTPUT: + mi->type = AUDIO_MIXER_CLASS; + mi->mixer_class = UAC_OUTPUT; + mi->next = mi->prev = AUDIO_MIXER_LAST; + strlcpy(mi->label.name, AudioCoutputs, sizeof(mi->label.name)); + return 0; + case UAC_INPUT: + mi->type = AUDIO_MIXER_CLASS; + mi->mixer_class = UAC_INPUT; + mi->next = mi->prev = AUDIO_MIXER_LAST; + strlcpy(mi->label.name, AudioCinputs, sizeof(mi->label.name)); + return 0; + case UAC_EQUAL: + mi->type = AUDIO_MIXER_CLASS; + mi->mixer_class = UAC_EQUAL; + mi->next = mi->prev = AUDIO_MIXER_LAST; + strlcpy(mi->label.name, AudioCequalization, + sizeof(mi->label.name)); + return 0; + case UAC_RECORD: + mi->type = AUDIO_MIXER_CLASS; + mi->mixer_class = UAC_RECORD; + mi->next = mi->prev = AUDIO_MIXER_LAST; + strlcpy(mi->label.name, AudioCrecord, sizeof(mi->label.name)); + return 0; + default: + break; + } + + n -= UAC_NCLASSES; + if (n < 0 || n >= nctls) + return ENXIO; + + mc = &sc->sc_ctls[n]; + strlcpy(mi->label.name, mc->ctlname, sizeof(mi->label.name)); + mi->mixer_class = mc->class; + mi->next = mi->prev = AUDIO_MIXER_LAST; /* XXX */ + switch (mc->type) { + case MIX_ON_OFF: + mi->type = AUDIO_MIXER_ENUM; + mi->un.e.num_mem = 2; + strlcpy(mi->un.e.member[0].label.name, AudioNoff, + sizeof(mi->un.e.member[0].label.name)); + mi->un.e.member[0].ord = 0; + strlcpy(mi->un.e.member[1].label.name, AudioNon, + sizeof(mi->un.e.member[1].label.name)); + mi->un.e.member[1].ord = 1; + break; + case MIX_SELECTOR: + mi->type = AUDIO_MIXER_ENUM; + mi->un.e.num_mem = mc->maxval - mc->minval + 1; + for (i = 0; i <= mc->maxval - mc->minval; i++) { + snprintf(mi->un.e.member[i].label.name, + sizeof(mi->un.e.member[i].label.name), + "%d", i + mc->minval); + mi->un.e.member[i].ord = i + mc->minval; + } + break; + default: + mi->type = AUDIO_MIXER_VALUE; + strlcpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); + mi->un.v.num_channels = mc->nchan; + mi->un.v.delta = mc->delta; + break; + } + return 0; +} + +static int +uaudio_open(void *addr, int flags) +{ + struct uaudio_softc *sc; + + sc = addr; + DPRINTF(("uaudio_open: sc=%p\n", sc)); + if (sc->sc_dying) + return EIO; + + if ((flags & FWRITE) && !(sc->sc_mode & AUMODE_PLAY)) + return EACCES; + if ((flags & FREAD) && !(sc->sc_mode & AUMODE_RECORD)) + return EACCES; + + return 0; +} + +/* + * Close function is called at splaudio(). + */ +static void +uaudio_close(void *addr) +{ +} + +static int +uaudio_drain(void *addr) +{ + struct uaudio_softc *sc; + + sc = addr; + usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); + + return 0; +} + +static int +uaudio_halt_out_dma(void *addr) +{ + struct uaudio_softc *sc; + + sc = addr; + if (sc->sc_dying) + return EIO; + + DPRINTF(("uaudio_halt_out_dma: enter\n")); + if (sc->sc_playchan.pipe != NULL) { + uaudio_chan_close(sc, &sc->sc_playchan); + sc->sc_playchan.pipe = NULL; + uaudio_chan_free_buffers(sc, &sc->sc_playchan); + sc->sc_playchan.intr = NULL; + } + return 0; +} + +static int +uaudio_halt_in_dma(void *addr) +{ + struct uaudio_softc *sc; + + DPRINTF(("uaudio_halt_in_dma: enter\n")); + sc = addr; + if (sc->sc_recchan.pipe != NULL) { + uaudio_chan_close(sc, &sc->sc_recchan); + sc->sc_recchan.pipe = NULL; + uaudio_chan_free_buffers(sc, &sc->sc_recchan); + sc->sc_recchan.intr = NULL; + } + return 0; +} + +static int +uaudio_getdev(void *addr, struct audio_device *retp) +{ + struct uaudio_softc *sc; + + DPRINTF(("uaudio_mixer_getdev:\n")); + sc = addr; + if (sc->sc_dying) + return EIO; + + *retp = uaudio_device; + return 0; +} + +/* + * Make sure the block size is large enough to hold all outstanding transfers. + */ +static int +uaudio_round_blocksize(void *addr, int blk) +{ + struct uaudio_softc *sc; + int b; + + sc = addr; + DPRINTF(("uaudio_round_blocksize: blk=%d mode=%s\n", blk, + mode == AUMODE_PLAY ? "AUMODE_PLAY" : "AUMODE_RECORD")); + + /* chan.bytes_per_frame can be 0. */ + if (mode == AUMODE_PLAY || sc->sc_recchan.bytes_per_frame <= 0) { + b = param->sample_rate * UAUDIO_NFRAMES * UAUDIO_NCHANBUFS; + + /* + * This does not make accurate value in the case + * of b % USB_FRAMES_PER_SECOND != 0 + */ + b /= USB_FRAMES_PER_SECOND; + + b *= param->precision / 8 * param->channels; + } else { + /* + * use wMaxPacketSize in bytes_per_frame. + * See uaudio_set_params() and uaudio_chan_init() + */ + b = sc->sc_recchan.bytes_per_frame + * UAUDIO_NFRAMES * UAUDIO_NCHANBUFS; + } + + if (b <= 0) + b = 1; + blk = blk <= b ? b : blk / b * b; + +#ifdef DIAGNOSTIC + if (blk <= 0) { + printf("uaudio_round_blocksize: blk=%d\n", blk); + blk = 512; + } +#endif + + DPRINTF(("uaudio_round_blocksize: resultant blk=%d\n", blk)); + return blk; +} + +static int +uaudio_get_props(void *addr) +{ + return AUDIO_PROP_FULLDUPLEX | AUDIO_PROP_INDEPENDENT; + +} +#endif /* NetBSD or OpenBSD */ + +static int +uaudio_get(struct uaudio_softc *sc, int which, int type, int wValue, + int wIndex, int len) +{ + usb_device_request_t req; + uint8_t data[4]; + usbd_status err; + int val; + +#if defined(__FreeBSD__) + if (sc->sc_dying) + return EIO; +#endif + + if (wValue == -1) + return 0; + + req.bmRequestType = type; + req.bRequest = which; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, len); + DPRINTFN(2,("uaudio_get: type=0x%02x req=0x%02x wValue=0x%04x " + "wIndex=0x%04x len=%d\n", + type, which, wValue, wIndex, len)); +#if defined(__FreeBSD__) + if (sc->async != 0) + err = usbd_do_request_async(sc->sc_udev, &req, data); + else +#endif + err = usbd_do_request(sc->sc_udev, &req, data); + if (err) { + DPRINTF(("uaudio_get: err=%s\n", usbd_errstr(err))); + return -1; + } + switch (len) { + case 1: + val = data[0]; + break; + case 2: + val = data[0] | (data[1] << 8); + break; + default: + DPRINTF(("uaudio_get: bad length=%d\n", len)); + return -1; + } + DPRINTFN(2,("uaudio_get: val=%d\n", val)); + return val; +} + +static void +uaudio_set(struct uaudio_softc *sc, int which, int type, int wValue, + int wIndex, int len, int val) +{ + usb_device_request_t req; + uint8_t data[4]; + usbd_status err; + +#if defined(__FreeBSD__) + if (sc->sc_dying) + return; +#endif + + if (wValue == -1) + return; + + req.bmRequestType = type; + req.bRequest = which; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, len); + switch (len) { + case 1: + data[0] = val; + break; + case 2: + data[0] = val; + data[1] = val >> 8; + break; + default: + return; + } + DPRINTFN(2,("uaudio_set: type=0x%02x req=0x%02x wValue=0x%04x " + "wIndex=0x%04x len=%d, val=%d\n", + type, which, wValue, wIndex, len, val & 0xffff)); +#if defined(__FreeBSD__) + if (sc->async != 0) + err = usbd_do_request_async(sc->sc_udev, &req, data); + else +#endif + err = usbd_do_request(sc->sc_udev, &req, data); +#ifdef USB_DEBUG + if (err) + DPRINTF(("uaudio_set: err=%d\n", err)); +#endif +} + +static int +uaudio_signext(int type, int val) +{ + if (!MIX_UNSIGNED(type)) { + if (MIX_SIZE(type) == 2) + val = (int16_t)val; + else + val = (int8_t)val; + } + return val; +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int +uaudio_value2bsd(struct mixerctl *mc, int val) +{ + DPRINTFN(5, ("uaudio_value2bsd: type=%03x val=%d min=%d max=%d ", + mc->type, val, mc->minval, mc->maxval)); + if (mc->type == MIX_ON_OFF) { + val = (val != 0); + } else if (mc->type == MIX_SELECTOR) { + if (val < mc->minval || val > mc->maxval) + val = mc->minval; + } else + val = ((uaudio_signext(mc->type, val) - mc->minval) * 255 + + mc->mul/2) / mc->mul; + DPRINTFN(5, ("val'=%d\n", val)); + return val; +} +#endif + +int +uaudio_bsd2value(struct mixerctl *mc, int val) +{ + DPRINTFN(5,("uaudio_bsd2value: type=%03x val=%d min=%d max=%d ", + mc->type, val, mc->minval, mc->maxval)); + if (mc->type == MIX_ON_OFF) { + val = (val != 0); + } else if (mc->type == MIX_SELECTOR) { + if (val < mc->minval || val > mc->maxval) + val = mc->minval; + } else + val = (val + mc->delta/2) * mc->mul / 255 + mc->minval; + DPRINTFN(5, ("val'=%d\n", val)); + return val; +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int +uaudio_ctl_get(struct uaudio_softc *sc, int which, struct mixerctl *mc, + int chan) +{ + int val; + + DPRINTFN(5,("uaudio_ctl_get: which=%d chan=%d\n", which, chan)); + val = uaudio_get(sc, which, UT_READ_CLASS_INTERFACE, mc->wValue[chan], + mc->wIndex, MIX_SIZE(mc->type)); + return uaudio_value2bsd(mc, val); +} +#endif + +static void +uaudio_ctl_set(struct uaudio_softc *sc, int which, struct mixerctl *mc, + int chan, int val) +{ + val = uaudio_bsd2value(mc, val); + uaudio_set(sc, which, UT_WRITE_CLASS_INTERFACE, mc->wValue[chan], + mc->wIndex, MIX_SIZE(mc->type), val); +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static int +uaudio_mixer_get_port(void *addr, mixer_ctrl_t *cp) +{ + struct uaudio_softc *sc; + struct mixerctl *mc; + int i, n, vals[MIX_MAX_CHAN], val; + + DPRINTFN(2,("uaudio_mixer_get_port: index=%d\n", cp->dev)); + sc = addr; + if (sc->sc_dying) + return EIO; + + n = cp->dev - UAC_NCLASSES; + if (n < 0 || n >= sc->sc_nctls) + return ENXIO; + mc = &sc->sc_ctls[n]; + + if (mc->type == MIX_ON_OFF) { + if (cp->type != AUDIO_MIXER_ENUM) + return EINVAL; + cp->un.ord = uaudio_ctl_get(sc, GET_CUR, mc, 0); + } else if (mc->type == MIX_SELECTOR) { + if (cp->type != AUDIO_MIXER_ENUM) + return EINVAL; + cp->un.ord = uaudio_ctl_get(sc, GET_CUR, mc, 0); + } else { + if (cp->type != AUDIO_MIXER_VALUE) + return (EINVAL); + if (cp->un.value.num_channels != 1 && + cp->un.value.num_channels != mc->nchan) + return EINVAL; + for (i = 0; i < mc->nchan; i++) + vals[i] = uaudio_ctl_get(sc, GET_CUR, mc, i); + if (cp->un.value.num_channels == 1 && mc->nchan != 1) { + for (val = 0, i = 0; i < mc->nchan; i++) + val += vals[i]; + vals[0] = val / mc->nchan; + } + for (i = 0; i < cp->un.value.num_channels; i++) + cp->un.value.level[i] = vals[i]; + } + + return 0; +} + +static int +uaudio_mixer_set_port(void *addr, mixer_ctrl_t *cp) +{ + struct uaudio_softc *sc; + struct mixerctl *mc; + int i, n, vals[MIX_MAX_CHAN]; + + DPRINTFN(2,("uaudio_mixer_set_port: index = %d\n", cp->dev)); + sc = addr; + if (sc->sc_dying) + return EIO; + + n = cp->dev - UAC_NCLASSES; + if (n < 0 || n >= sc->sc_nctls) + return ENXIO; + mc = &sc->sc_ctls[n]; + + if (mc->type == MIX_ON_OFF) { + if (cp->type != AUDIO_MIXER_ENUM) + return EINVAL; + uaudio_ctl_set(sc, SET_CUR, mc, 0, cp->un.ord); + } else if (mc->type == MIX_SELECTOR) { + if (cp->type != AUDIO_MIXER_ENUM) + return EINVAL; + uaudio_ctl_set(sc, SET_CUR, mc, 0, cp->un.ord); + } else { + if (cp->type != AUDIO_MIXER_VALUE) + return EINVAL; + if (cp->un.value.num_channels == 1) + for (i = 0; i < mc->nchan; i++) + vals[i] = cp->un.value.level[0]; + else if (cp->un.value.num_channels == mc->nchan) + for (i = 0; i < mc->nchan; i++) + vals[i] = cp->un.value.level[i]; + else + return EINVAL; + for (i = 0; i < mc->nchan; i++) + uaudio_ctl_set(sc, SET_CUR, mc, i, vals[i]); + } + return 0; +} + +static int +uaudio_trigger_input(void *addr, void *start, void *end, int blksize, + void (*intr)(void *), void *arg, + struct audio_params *param) +{ + struct uaudio_softc *sc; + struct chan *ch; + usbd_status err; + int i, s; + + sc = addr; + if (sc->sc_dying) + return EIO; + + DPRINTFN(3,("uaudio_trigger_input: sc=%p start=%p end=%p " + "blksize=%d\n", sc, start, end, blksize)); + ch = &sc->sc_recchan; + uaudio_chan_set_param(ch, start, end, blksize); + DPRINTFN(3,("uaudio_trigger_input: sample_size=%d bytes/frame=%d " + "fraction=0.%03d\n", ch->sample_size, ch->bytes_per_frame, + ch->fraction)); + + err = uaudio_chan_alloc_buffers(sc, ch); + if (err) + return EIO; + + err = uaudio_chan_open(sc, ch); + if (err) { + uaudio_chan_free_buffers(sc, ch); + return EIO; + } + + ch->intr = intr; + ch->arg = arg; + + s = splusb(); + for (i = 0; i < UAUDIO_NCHANBUFS-1; i++) /* XXX -1 shouldn't be needed */ + uaudio_chan_rtransfer(ch); + splx(s); + + return 0; +} + +static int +uaudio_trigger_output(void *addr, void *start, void *end, int blksize, + void (*intr)(void *), void *arg, + struct audio_params *param) +{ + struct uaudio_softc *sc; + struct chan *ch; + usbd_status err; + int i, s; + + sc = addr; + if (sc->sc_dying) + return EIO; + + DPRINTFN(3,("uaudio_trigger_output: sc=%p start=%p end=%p " + "blksize=%d\n", sc, start, end, blksize)); + ch = &sc->sc_playchan; + uaudio_chan_set_param(ch, start, end, blksize); + DPRINTFN(3,("uaudio_trigger_output: sample_size=%d bytes/frame=%d " + "fraction=0.%03d\n", ch->sample_size, ch->bytes_per_frame, + ch->fraction)); + + err = uaudio_chan_alloc_buffers(sc, ch); + if (err) + return EIO; + + err = uaudio_chan_open(sc, ch); + if (err) { + uaudio_chan_free_buffers(sc, ch); + return EIO; + } + + ch->intr = intr; + ch->arg = arg; + + s = splusb(); + for (i = 0; i < UAUDIO_NCHANBUFS-1; i++) /* XXX */ + uaudio_chan_ptransfer(ch); + splx(s); + + return 0; +} +#endif /* NetBSD or OpenBSD */ + +/* Set up a pipe for a channel. */ +static usbd_status +uaudio_chan_open(struct uaudio_softc *sc, struct chan *ch) +{ + struct as_info *as; + int endpt; +#if defined(__FreeBSD__) + int locked; +#endif + usbd_status err; + +#if defined(__FreeBSD__) + if (sc->sc_dying) + return EIO; +#endif + + as = &sc->sc_alts[ch->altidx]; + endpt = as->edesc->bEndpointAddress; + DPRINTF(("uaudio_chan_open: endpt=0x%02x, speed=%d, alt=%d\n", + endpt, ch->sample_rate, as->alt)); + +#if defined(__FreeBSD__) + locked = (ch->pcm_ch != NULL && mtx_owned(ch->pcm_ch->lock)) ? 1 : 0; + if (locked) + CHN_UNLOCK(ch->pcm_ch); +#endif + /* Set alternate interface corresponding to the mode. */ + err = usbd_set_interface(as->ifaceh, as->alt); +#if defined(__FreeBSD__) + if (locked) + CHN_LOCK(ch->pcm_ch); +#endif + if (err) + return err; + + /* + * If just one sampling rate is supported, + * no need to call uaudio_set_speed(). + * Roland SD-90 freezes by a SAMPLING_FREQ_CONTROL request. + */ + if (as->asf1desc->bSamFreqType != 1) { + err = uaudio_set_speed(sc, endpt, ch->sample_rate); + if (err) { + DPRINTF(("uaudio_chan_open: set_speed failed err=%s\n", + usbd_errstr(err))); + } + } + + ch->pipe = 0; + ch->sync_pipe = 0; + DPRINTF(("uaudio_chan_open: create pipe to 0x%02x\n", endpt)); + err = usbd_open_pipe(as->ifaceh, endpt, 0, &ch->pipe); + if (err) + return err; + if (as->edesc1 != NULL) { + endpt = as->edesc1->bEndpointAddress; + DPRINTF(("uaudio_chan_open: create sync-pipe to 0x%02x\n", endpt)); + err = usbd_open_pipe(as->ifaceh, endpt, 0, &ch->sync_pipe); + } + return err; +} + +static void +uaudio_chan_close(struct uaudio_softc *sc, struct chan *ch) +{ + struct as_info *as; +#if defined(__FreeBSD__) + int locked; + + if (sc->sc_dying) + return ; +#endif + + as = &sc->sc_alts[ch->altidx]; + as->sc_busy = 0; +#if defined(__FreeBSD__) + locked = (ch->pcm_ch != NULL && mtx_owned(ch->pcm_ch->lock)) ? 1 : 0; + if (locked) + CHN_UNLOCK(ch->pcm_ch); +#endif + if (sc->sc_nullalt >= 0) { + DPRINTF(("uaudio_chan_close: set null alt=%d\n", + sc->sc_nullalt)); + /* + * The interface will be initialized later again, so an + * error does not hurt. + */ + (void)usbd_set_interface(as->ifaceh, sc->sc_nullalt); + } + if (ch->pipe) { + usbd_abort_pipe(ch->pipe); + usbd_close_pipe(ch->pipe); + } + if (ch->sync_pipe) { + usbd_abort_pipe(ch->sync_pipe); + usbd_close_pipe(ch->sync_pipe); + } +#if defined(__FreeBSD__) + if (locked) + CHN_LOCK(ch->pcm_ch); +#endif +} + +static usbd_status +uaudio_chan_alloc_buffers(struct uaudio_softc *sc, struct chan *ch) +{ + usbd_xfer_handle xfer; + void *buf; + int i, size; + + size = (ch->bytes_per_frame + ch->sample_size) * UAUDIO_NFRAMES; + for (i = 0; i < UAUDIO_NCHANBUFS; i++) { + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == 0) + goto bad; + ch->chanbufs[i].xfer = xfer; + buf = usbd_alloc_buffer(xfer, size); + if (buf == 0) { + i++; + goto bad; + } + ch->chanbufs[i].buffer = buf; + ch->chanbufs[i].chan = ch; + } + + return USBD_NORMAL_COMPLETION; + +bad: + while (--i >= 0) + /* implicit buffer free */ + usbd_free_xfer(ch->chanbufs[i].xfer); + return USBD_NOMEM; +} + +static void +uaudio_chan_free_buffers(struct uaudio_softc *sc, struct chan *ch) +{ + int i; + + for (i = 0; i < UAUDIO_NCHANBUFS; i++) + usbd_free_xfer(ch->chanbufs[i].xfer); +} + +/* Called at splusb() */ +static void +uaudio_chan_ptransfer(struct chan *ch) +{ + struct chanbuf *cb; + int i, n, size, residue, total; + + if (ch->sc->sc_dying) + return; + + /* Pick the next channel buffer. */ + cb = &ch->chanbufs[ch->curchanbuf]; + if (++ch->curchanbuf >= UAUDIO_NCHANBUFS) + ch->curchanbuf = 0; + + /* Compute the size of each frame in the next transfer. */ + residue = ch->residue; + total = 0; + for (i = 0; i < UAUDIO_NFRAMES; i++) { + size = ch->bytes_per_frame; + residue += ch->fraction; + if (residue >= USB_FRAMES_PER_SECOND) { + if ((ch->sc->sc_altflags & UA_NOFRAC) == 0) + size += ch->sample_size; + residue -= USB_FRAMES_PER_SECOND; + } + cb->sizes[i] = size; + total += size; + } + ch->residue = residue; + cb->size = total; + + /* + * Transfer data from upper layer buffer to channel buffer, taking + * care of wrapping the upper layer buffer. + */ + n = min(total, ch->end - ch->cur); + memcpy(cb->buffer, ch->cur, n); + ch->cur += n; + if (ch->cur >= ch->end) + ch->cur = ch->start; + if (total > n) { + total -= n; + memcpy(cb->buffer + n, ch->cur, total); + ch->cur += total; + } + +#ifdef USB_DEBUG + if (uaudiodebug > 8) { + DPRINTF(("uaudio_chan_ptransfer: buffer=%p, residue=0.%03d\n", + cb->buffer, ch->residue)); + for (i = 0; i < UAUDIO_NFRAMES; i++) { + DPRINTF((" [%d] length %d\n", i, cb->sizes[i])); + } + } +#endif + + DPRINTFN(5,("uaudio_chan_transfer: ptransfer xfer=%p\n", cb->xfer)); + /* Fill the request */ + usbd_setup_isoc_xfer(cb->xfer, ch->pipe, cb, cb->sizes, + UAUDIO_NFRAMES, USBD_NO_COPY, + uaudio_chan_pintr); + + (void)usbd_transfer(cb->xfer); +} + +static void +uaudio_chan_pintr(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct chanbuf *cb; + struct chan *ch; + u_int32_t count; + int s; + + cb = priv; + ch = cb->chan; + /* Return if we are aborting. */ + if (status == USBD_CANCELLED) + return; + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + DPRINTFN(5,("uaudio_chan_pintr: count=%d, transferred=%d\n", + count, ch->transferred)); +#ifdef DIAGNOSTIC + if (count != cb->size) { + printf("uaudio_chan_pintr: count(%d) != size(%d)\n", + count, cb->size); + } +#endif + + ch->transferred += cb->size; +#if defined(__FreeBSD__) + /* s = spltty(); */ + s = splhigh(); + chn_intr(ch->pcm_ch); + splx(s); +#else + s = splaudio(); + /* Call back to upper layer */ + while (ch->transferred >= ch->blksize) { + ch->transferred -= ch->blksize; + DPRINTFN(5,("uaudio_chan_pintr: call %p(%p)\n", + ch->intr, ch->arg)); + ch->intr(ch->arg); + } + splx(s); +#endif + + /* start next transfer */ + uaudio_chan_ptransfer(ch); +} + +/* Called at splusb() */ +static void +uaudio_chan_rtransfer(struct chan *ch) +{ + struct chanbuf *cb; + int i, size, residue, total; + + if (ch->sc->sc_dying) + return; + + /* Pick the next channel buffer. */ + cb = &ch->chanbufs[ch->curchanbuf]; + if (++ch->curchanbuf >= UAUDIO_NCHANBUFS) + ch->curchanbuf = 0; + + /* Compute the size of each frame in the next transfer. */ + residue = ch->residue; + total = 0; + for (i = 0; i < UAUDIO_NFRAMES; i++) { + size = ch->bytes_per_frame; + cb->sizes[i] = size; + cb->offsets[i] = total; + total += size; + } + ch->residue = residue; + cb->size = total; + +#ifdef USB_DEBUG + if (uaudiodebug > 8) { + DPRINTF(("uaudio_chan_rtransfer: buffer=%p, residue=0.%03d\n", + cb->buffer, ch->residue)); + for (i = 0; i < UAUDIO_NFRAMES; i++) { + DPRINTF((" [%d] length %d\n", i, cb->sizes[i])); + } + } +#endif + + DPRINTFN(5,("uaudio_chan_rtransfer: transfer xfer=%p\n", cb->xfer)); + /* Fill the request */ + usbd_setup_isoc_xfer(cb->xfer, ch->pipe, cb, cb->sizes, + UAUDIO_NFRAMES, USBD_NO_COPY, + uaudio_chan_rintr); + + (void)usbd_transfer(cb->xfer); +} + +static void +uaudio_chan_rintr(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct chanbuf *cb = priv; + struct chan *ch = cb->chan; + u_int32_t count; + int s, i, n, frsize; + + /* Return if we are aborting. */ + if (status == USBD_CANCELLED) + return; + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + DPRINTFN(5,("uaudio_chan_rintr: count=%d, transferred=%d\n", + count, ch->transferred)); + + /* count < cb->size is normal for asynchronous source */ +#ifdef DIAGNOSTIC + if (count > cb->size) { + printf("uaudio_chan_rintr: count(%d) > size(%d)\n", + count, cb->size); + } +#endif + + /* + * Transfer data from channel buffer to upper layer buffer, taking + * care of wrapping the upper layer buffer. + */ + for(i = 0; i < UAUDIO_NFRAMES; i++) { + frsize = cb->sizes[i]; + n = min(frsize, ch->end - ch->cur); + memcpy(ch->cur, cb->buffer + cb->offsets[i], n); + ch->cur += n; + if (ch->cur >= ch->end) + ch->cur = ch->start; + if (frsize > n) { + memcpy(ch->cur, cb->buffer + cb->offsets[i] + n, + frsize - n); + ch->cur += frsize - n; + } + } + + /* Call back to upper layer */ + ch->transferred += count; +#if defined(__FreeBSD__) + s = spltty(); + chn_intr(ch->pcm_ch); + splx(s); +#else + s = splaudio(); + while (ch->transferred >= ch->blksize) { + ch->transferred -= ch->blksize; + DPRINTFN(5,("uaudio_chan_rintr: call %p(%p)\n", + ch->intr, ch->arg)); + ch->intr(ch->arg); + } + splx(s); +#endif + + /* start next transfer */ + uaudio_chan_rtransfer(ch); +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static void +uaudio_chan_init(struct chan *ch, int altidx, const struct audio_params *param, + int maxpktsize) +{ + int samples_per_frame, sample_size; + + ch->altidx = altidx; + sample_size = param->precision * param->factor * param->hw_channels / 8; + samples_per_frame = param->hw_sample_rate / USB_FRAMES_PER_SECOND; + ch->sample_size = sample_size; + ch->sample_rate = param->hw_sample_rate; + if (maxpktsize == 0) { + ch->fraction = param->hw_sample_rate % USB_FRAMES_PER_SECOND; + ch->bytes_per_frame = samples_per_frame * sample_size; + } else { + ch->fraction = 0; + ch->bytes_per_frame = maxpktsize; + } + ch->residue = 0; +} + +static void +uaudio_chan_set_param(struct chan *ch, u_char *start, u_char *end, int blksize) +{ + ch->start = start; + ch->end = end; + ch->cur = start; + ch->blksize = blksize; + ch->transferred = 0; + ch->curchanbuf = 0; +} + +static void +uaudio_get_minmax_rates(int nalts, const struct as_info *alts, + const struct audio_params *p, int mode, + u_long *min, u_long *max) +{ + const struct usb_audio_streaming_type1_descriptor *a1d; + int i, j; + + *min = ULONG_MAX; + *max = 0; + for (i = 0; i < nalts; i++) { + a1d = alts[i].asf1desc; + if (alts[i].sc_busy) + continue; + if (p->hw_channels != a1d->bNrChannels) + continue; + if (p->hw_precision != a1d->bBitResolution) + continue; + if (p->hw_encoding != alts[i].encoding) + continue; + if (mode != UE_GET_DIR(alts[i].edesc->bEndpointAddress)) + continue; + if (a1d->bSamFreqType == UA_SAMP_CONTNUOUS) { + DPRINTFN(2,("uaudio_get_minmax_rates: cont %d-%d\n", + UA_SAMP_LO(a1d), UA_SAMP_HI(a1d))); + if (UA_SAMP_LO(a1d) < *min) + *min = UA_SAMP_LO(a1d); + if (UA_SAMP_HI(a1d) > *max) + *max = UA_SAMP_HI(a1d); + } else { + for (j = 0; j < a1d->bSamFreqType; j++) { + DPRINTFN(2,("uaudio_get_minmax_rates: disc #%d: %d\n", + j, UA_GETSAMP(a1d, j))); + if (UA_GETSAMP(a1d, j) < *min) + *min = UA_GETSAMP(a1d, j); + if (UA_GETSAMP(a1d, j) > *max) + *max = UA_GETSAMP(a1d, j); + } + } + } +} + +static int +uaudio_match_alt_sub(int nalts, const struct as_info *alts, + const struct audio_params *p, int mode, u_long rate) +{ + const struct usb_audio_streaming_type1_descriptor *a1d; + int i, j; + + DPRINTF(("uaudio_match_alt_sub: search for %luHz %dch\n", + rate, p->hw_channels)); + for (i = 0; i < nalts; i++) { + a1d = alts[i].asf1desc; + if (alts[i].sc_busy) + continue; + if (p->hw_channels != a1d->bNrChannels) + continue; + if (p->hw_precision != a1d->bBitResolution) + continue; + if (p->hw_encoding != alts[i].encoding) + continue; + if (mode != UE_GET_DIR(alts[i].edesc->bEndpointAddress)) + continue; + if (a1d->bSamFreqType == UA_SAMP_CONTNUOUS) { + DPRINTFN(3,("uaudio_match_alt_sub: cont %d-%d\n", + UA_SAMP_LO(a1d), UA_SAMP_HI(a1d))); + if (UA_SAMP_LO(a1d) <= rate && rate <= UA_SAMP_HI(a1d)) + return i; + } else { + for (j = 0; j < a1d->bSamFreqType; j++) { + DPRINTFN(3,("uaudio_match_alt_sub: disc #%d: %d\n", + j, UA_GETSAMP(a1d, j))); + /* XXX allow for some slack */ + if (UA_GETSAMP(a1d, j) == rate) + return i; + } + } + } + return -1; +} + +static int +uaudio_match_alt_chan(int nalts, const struct as_info *alts, + struct audio_params *p, int mode) +{ + int i, n; + u_long min, max; + u_long rate; + + /* Exact match */ + DPRINTF(("uaudio_match_alt_chan: examine %ldHz %dch %dbit.\n", + p->sample_rate, p->hw_channels, p->hw_precision)); + i = uaudio_match_alt_sub(nalts, alts, p, mode, p->sample_rate); + if (i >= 0) + return i; + + uaudio_get_minmax_rates(nalts, alts, p, mode, &min, &max); + DPRINTF(("uaudio_match_alt_chan: min=%lu max=%lu\n", min, max)); + if (max <= 0) + return -1; + /* Search for biggers */ + n = 2; + while ((rate = p->sample_rate * n++) <= max) { + i = uaudio_match_alt_sub(nalts, alts, p, mode, rate); + if (i >= 0) { + p->hw_sample_rate = rate; + return i; + } + } + if (p->sample_rate >= min) { + i = uaudio_match_alt_sub(nalts, alts, p, mode, max); + if (i >= 0) { + p->hw_sample_rate = max; + return i; + } + } else { + i = uaudio_match_alt_sub(nalts, alts, p, mode, min); + if (i >= 0) { + p->hw_sample_rate = min; + return i; + } + } + return -1; +} + +static int +uaudio_match_alt(int nalts, const struct as_info *alts, + struct audio_params *p, int mode) +{ + int i, n; + + mode = mode == AUMODE_PLAY ? UE_DIR_OUT : UE_DIR_IN; + i = uaudio_match_alt_chan(nalts, alts, p, mode); + if (i >= 0) + return i; + + for (n = p->channels + 1; n <= AUDIO_MAX_CHANNELS; n++) { + p->hw_channels = n; + i = uaudio_match_alt_chan(nalts, alts, p, mode); + if (i >= 0) + return i; + } + + if (p->channels != 2) + return -1; + p->hw_channels = 1; + return uaudio_match_alt_chan(nalts, alts, p, mode); +} + +static int +uaudio_set_params(void *addr, int setmode, int usemode, + struct audio_params *play, struct audio_params *rec) +{ + struct uaudio_softc *sc; + int flags; + int factor; + int enc, i; + int paltidx, raltidx; + void (*swcode)(void *, u_char *buf, int cnt); + struct audio_params *p; + int mode; + + sc = addr; + flags = sc->sc_altflags; + paltidx = -1; + raltidx = -1; + if (sc->sc_dying) + return EIO; + + if (((usemode & AUMODE_PLAY) && sc->sc_playchan.pipe != NULL) || + ((usemode & AUMODE_RECORD) && sc->sc_recchan.pipe != NULL)) + return EBUSY; + + if ((usemode & AUMODE_PLAY) && sc->sc_playchan.altidx != -1) + sc->sc_alts[sc->sc_playchan.altidx].sc_busy = 0; + if ((usemode & AUMODE_RECORD) && sc->sc_recchan.altidx != -1) + sc->sc_alts[sc->sc_recchan.altidx].sc_busy = 0; + + /* Some uaudio devices are unidirectional. Don't try to find a + matching mode for the unsupported direction. */ + setmode &= sc->sc_mode; + + for (mode = AUMODE_RECORD; mode != -1; + mode = mode == AUMODE_RECORD ? AUMODE_PLAY : -1) { + if ((setmode & mode) == 0) + continue; + + p = (mode == AUMODE_PLAY) ? play : rec; + + factor = 1; + swcode = 0; + enc = p->encoding; + switch (enc) { + case AUDIO_ENCODING_SLINEAR_BE: + /* FALLTHROUGH */ + case AUDIO_ENCODING_SLINEAR_LE: + if (enc == AUDIO_ENCODING_SLINEAR_BE + && p->precision == 16 && (flags & HAS_16)) { + swcode = swap_bytes; + enc = AUDIO_ENCODING_SLINEAR_LE; + } else if (p->precision == 8) { + if (flags & HAS_8) { + /* No conversion */ + } else if (flags & HAS_8U) { + swcode = change_sign8; + enc = AUDIO_ENCODING_ULINEAR_LE; + } else if (flags & HAS_16) { + factor = 2; + p->hw_precision = 16; + if (mode == AUMODE_PLAY) + swcode = linear8_to_linear16_le; + else + swcode = linear16_to_linear8_le; + } + } + break; + case AUDIO_ENCODING_ULINEAR_BE: + /* FALLTHROUGH */ + case AUDIO_ENCODING_ULINEAR_LE: + if (p->precision == 16) { + if (enc == AUDIO_ENCODING_ULINEAR_LE) + swcode = change_sign16_le; + else if (mode == AUMODE_PLAY) + swcode = swap_bytes_change_sign16_le; + else + swcode = change_sign16_swap_bytes_le; + enc = AUDIO_ENCODING_SLINEAR_LE; + } else if (p->precision == 8) { + if (flags & HAS_8U) { + /* No conversion */ + } else if (flags & HAS_8) { + swcode = change_sign8; + enc = AUDIO_ENCODING_SLINEAR_LE; + } else if (flags & HAS_16) { + factor = 2; + p->hw_precision = 16; + enc = AUDIO_ENCODING_SLINEAR_LE; + if (mode == AUMODE_PLAY) + swcode = ulinear8_to_slinear16_le; + else + swcode = slinear16_to_ulinear8_le; + } + } + break; + case AUDIO_ENCODING_ULAW: + if (flags & HAS_MULAW) + break; + if (flags & HAS_16) { + if (mode == AUMODE_PLAY) + swcode = mulaw_to_slinear16_le; + else + swcode = slinear16_to_mulaw_le; + factor = 2; + enc = AUDIO_ENCODING_SLINEAR_LE; + p->hw_precision = 16; + } else if (flags & HAS_8U) { + if (mode == AUMODE_PLAY) + swcode = mulaw_to_ulinear8; + else + swcode = ulinear8_to_mulaw; + enc = AUDIO_ENCODING_ULINEAR_LE; + } else if (flags & HAS_8) { + if (mode == AUMODE_PLAY) + swcode = mulaw_to_slinear8; + else + swcode = slinear8_to_mulaw; + enc = AUDIO_ENCODING_SLINEAR_LE; + } else + return (EINVAL); + break; + case AUDIO_ENCODING_ALAW: + if (flags & HAS_ALAW) + break; + if (mode == AUMODE_PLAY && (flags & HAS_16)) { + swcode = alaw_to_slinear16_le; + factor = 2; + enc = AUDIO_ENCODING_SLINEAR_LE; + p->hw_precision = 16; + } else if (flags & HAS_8U) { + if (mode == AUMODE_PLAY) + swcode = alaw_to_ulinear8; + else + swcode = ulinear8_to_alaw; + enc = AUDIO_ENCODING_ULINEAR_LE; + } else if (flags & HAS_8) { + if (mode == AUMODE_PLAY) + swcode = alaw_to_slinear8; + else + swcode = slinear8_to_alaw; + enc = AUDIO_ENCODING_SLINEAR_LE; + } else + return (EINVAL); + break; + default: + return (EINVAL); + } + /* XXX do some other conversions... */ + + DPRINTF(("uaudio_set_params: chan=%d prec=%d enc=%d rate=%ld\n", + p->channels, p->hw_precision, enc, p->sample_rate)); + + p->hw_encoding = enc; + i = uaudio_match_alt(sc->sc_nalts, sc->sc_alts, p, mode); + if (i < 0) + return (EINVAL); + + p->sw_code = swcode; + p->factor = factor; + + if (mode == AUMODE_PLAY) + paltidx = i; + else + raltidx = i; + } + + if ((setmode & AUMODE_PLAY)) { + /* XXX abort transfer if currently happening? */ + uaudio_chan_init(&sc->sc_playchan, paltidx, play, 0); + } + if ((setmode & AUMODE_RECORD)) { + /* XXX abort transfer if currently happening? */ + uaudio_chan_init(&sc->sc_recchan, raltidx, rec, + UGETW(sc->sc_alts[raltidx].edesc->wMaxPacketSize)); + } + + if ((usemode & AUMODE_PLAY) && sc->sc_playchan.altidx != -1) + sc->sc_alts[sc->sc_playchan.altidx].sc_busy = 1; + if ((usemode & AUMODE_RECORD) && sc->sc_recchan.altidx != -1) + sc->sc_alts[sc->sc_recchan.altidx].sc_busy = 1; + + DPRINTF(("uaudio_set_params: use altidx=p%d/r%d, altno=p%d/r%d\n", + sc->sc_playchan.altidx, sc->sc_recchan.altidx, + (sc->sc_playchan.altidx >= 0) + ?sc->sc_alts[sc->sc_playchan.altidx].idesc->bAlternateSetting + : -1, + (sc->sc_recchan.altidx >= 0) + ? sc->sc_alts[sc->sc_recchan.altidx].idesc->bAlternateSetting + : -1)); + + return 0; +} +#endif /* NetBSD or OpenBSD */ + +static usbd_status +uaudio_set_speed(struct uaudio_softc *sc, int endpt, u_int speed) +{ + usb_device_request_t req; + uint8_t data[3]; + + DPRINTFN(5,("uaudio_set_speed: 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; + +#if defined(__FreeBSD__) + if (sc->async != 0) + return usbd_do_request_async(sc->sc_udev, &req, data); +#endif + return usbd_do_request(sc->sc_udev, &req, data); +} + + +#if defined(__FreeBSD__) +/************************************************************/ +int +uaudio_init_params(struct uaudio_softc *sc, struct chan *ch, int mode) +{ + int i, j, enc; + int samples_per_frame, sample_size; + + if ((sc->sc_playchan.pipe != NULL) || (sc->sc_recchan.pipe != NULL)) + return (-1); + + switch(ch->format & 0x000FFFFF) { + case AFMT_U8: + enc = AUDIO_ENCODING_ULINEAR_LE; + ch->precision = 8; + break; + case AFMT_S8: + enc = AUDIO_ENCODING_SLINEAR_LE; + ch->precision = 8; + break; + case AFMT_A_LAW: /* ? */ + enc = AUDIO_ENCODING_ALAW; + ch->precision = 8; + break; + case AFMT_MU_LAW: /* ? */ + enc = AUDIO_ENCODING_ULAW; + ch->precision = 8; + break; + case AFMT_S16_LE: + enc = AUDIO_ENCODING_SLINEAR_LE; + ch->precision = 16; + break; + case AFMT_S16_BE: + enc = AUDIO_ENCODING_SLINEAR_BE; + ch->precision = 16; + break; + case AFMT_U16_LE: + enc = AUDIO_ENCODING_ULINEAR_LE; + ch->precision = 16; + break; + case AFMT_U16_BE: + enc = AUDIO_ENCODING_ULINEAR_BE; + ch->precision = 16; + break; + case AFMT_S24_LE: + enc = AUDIO_ENCODING_SLINEAR_LE; + ch->precision = 24; + break; + case AFMT_S24_BE: + enc = AUDIO_ENCODING_SLINEAR_BE; + ch->precision = 24; + break; + case AFMT_U24_LE: + enc = AUDIO_ENCODING_ULINEAR_LE; + ch->precision = 24; + break; + case AFMT_U24_BE: + enc = AUDIO_ENCODING_ULINEAR_BE; + ch->precision = 24; + break; + case AFMT_S32_LE: + enc = AUDIO_ENCODING_SLINEAR_LE; + ch->precision = 32; + break; + case AFMT_S32_BE: + enc = AUDIO_ENCODING_SLINEAR_BE; + ch->precision = 32; + break; + case AFMT_U32_LE: + enc = AUDIO_ENCODING_ULINEAR_LE; + ch->precision = 32; + break; + case AFMT_U32_BE: + enc = AUDIO_ENCODING_ULINEAR_BE; + ch->precision = 32; + break; + default: + enc = 0; + ch->precision = 16; + printf("Unknown format %x\n", ch->format); + } + + if (ch->format & AFMT_STEREO) { + ch->channels = 2; + } else { + ch->channels = 1; + } + +/* for (mode = ...... */ + for (i = 0; i < sc->sc_nalts; i++) { + const struct usb_audio_streaming_type1_descriptor *a1d = + sc->sc_alts[i].asf1desc; + if (ch->channels == a1d->bNrChannels && + ch->precision == a1d->bBitResolution && +#if 0 + enc == sc->sc_alts[i].encoding) { +#else + enc == sc->sc_alts[i].encoding && + (mode == AUMODE_PLAY ? UE_DIR_OUT : UE_DIR_IN) == + UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress)) { +#endif + if (a1d->bSamFreqType == UA_SAMP_CONTNUOUS) { + DPRINTFN(2,("uaudio_set_params: cont %d-%d\n", + UA_SAMP_LO(a1d), UA_SAMP_HI(a1d))); + if (UA_SAMP_LO(a1d) <= ch->sample_rate && + ch->sample_rate <= UA_SAMP_HI(a1d)) { + if (mode == AUMODE_PLAY) + sc->sc_playchan.altidx = i; + else + sc->sc_recchan.altidx = i; + goto found; + } + } else { + for (j = 0; j < a1d->bSamFreqType; j++) { + DPRINTFN(2,("uaudio_set_params: disc #" + "%d: %d\n", j, UA_GETSAMP(a1d, j))); + /* XXX allow for some slack */ + if (UA_GETSAMP(a1d, j) == + ch->sample_rate) { + if (mode == AUMODE_PLAY) + sc->sc_playchan.altidx = i; + else + sc->sc_recchan.altidx = i; + goto found; + } + } + } + } + } + /* return (EINVAL); */ + if (mode == AUMODE_PLAY) + printf("uaudio: This device can't play in rate=%d.\n", ch->sample_rate); + else + printf("uaudio: This device can't record in rate=%d.\n", ch->sample_rate); + return (-1); + + found: +#if 0 /* XXX */ + p->sw_code = swcode; + p->factor = factor; + if (usemode == mode) + sc->sc_curaltidx = i; +#endif +/* } */ + + sample_size = ch->precision * ch->channels / 8; + samples_per_frame = ch->sample_rate / USB_FRAMES_PER_SECOND; + ch->fraction = ch->sample_rate % USB_FRAMES_PER_SECOND; + ch->sample_size = sample_size; + ch->bytes_per_frame = samples_per_frame * sample_size; + ch->residue = 0; + + ch->cur = ch->start; + ch->transferred = 0; + ch->curchanbuf = 0; + return (0); +} + +struct uaudio_conversion { + uint8_t uaudio_fmt; + uint8_t uaudio_prec; + uint32_t freebsd_fmt; +}; + +const struct uaudio_conversion const accepted_conversion[] = { + {AUDIO_ENCODING_ULINEAR_LE, 8, AFMT_U8}, + {AUDIO_ENCODING_ULINEAR_LE, 16, AFMT_U16_LE}, + {AUDIO_ENCODING_ULINEAR_LE, 24, AFMT_U24_LE}, + {AUDIO_ENCODING_ULINEAR_LE, 32, AFMT_U32_LE}, + {AUDIO_ENCODING_ULINEAR_BE, 16, AFMT_U16_BE}, + {AUDIO_ENCODING_ULINEAR_BE, 24, AFMT_U24_BE}, + {AUDIO_ENCODING_ULINEAR_BE, 32, AFMT_U32_BE}, + {AUDIO_ENCODING_SLINEAR_LE, 8, AFMT_S8}, + {AUDIO_ENCODING_SLINEAR_LE, 16, AFMT_S16_LE}, + {AUDIO_ENCODING_SLINEAR_LE, 24, AFMT_S24_LE}, + {AUDIO_ENCODING_SLINEAR_LE, 32, AFMT_S32_LE}, + {AUDIO_ENCODING_SLINEAR_BE, 16, AFMT_S16_BE}, + {AUDIO_ENCODING_SLINEAR_BE, 24, AFMT_S24_BE}, + {AUDIO_ENCODING_SLINEAR_BE, 32, AFMT_S32_BE}, + {AUDIO_ENCODING_ALAW, 8, AFMT_A_LAW}, + {AUDIO_ENCODING_ULAW, 8, AFMT_MU_LAW}, + {0,0,0} +}; + +unsigned +uaudio_query_formats(device_t dev, int reqdir, unsigned maxfmt, struct pcmchan_caps *cap) +{ + struct uaudio_softc *sc; + const struct usb_audio_streaming_type1_descriptor *asf1d; + const struct uaudio_conversion *iterator; + unsigned fmtcount, foundcount; + u_int32_t fmt; + uint8_t format, numchan, subframesize, prec, dir, iscontinuous; + int freq, freq_min, freq_max; + char *numchannel_descr; + char freq_descr[64]; + int i,r; + + sc = device_get_softc(dev); + if (sc == NULL) + return 0; + + cap->minspeed = cap->maxspeed = 0; + foundcount = fmtcount = 0; + + for (i = 0; i < sc->sc_nalts; i++) { + dir = UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress); + + if ((dir == UE_DIR_OUT) != (reqdir == PCMDIR_PLAY)) + continue; + + asf1d = sc->sc_alts[i].asf1desc; + format = sc->sc_alts[i].encoding; + + numchan = asf1d->bNrChannels; + subframesize = asf1d->bSubFrameSize; + prec = asf1d->bBitResolution; /* precision */ + iscontinuous = asf1d->bSamFreqType == UA_SAMP_CONTNUOUS; + + if (iscontinuous) + snprintf(freq_descr, sizeof(freq_descr), "continous min %d max %d", UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + else + snprintf(freq_descr, sizeof(freq_descr), "fixed frequency (%d listed formats)", asf1d->bSamFreqType); + + if (numchan == 1) + numchannel_descr = " (mono)"; + else if (numchan == 2) + numchannel_descr = " (stereo)"; + else + numchannel_descr = ""; + + if (bootverbose) { + device_printf(dev, "uaudio_query_formats: found a native %s channel%s %s %dbit %dbytes/subframe X %d channels = %d bytes per sample\n", + (dir==UE_DIR_OUT)?"playback":"record", + numchannel_descr, freq_descr, + prec, subframesize, numchan, subframesize*numchan); + } + /* + * Now start rejecting the ones that don't map to FreeBSD + */ + + if (numchan != 1 && numchan != 2) + continue; + + for (iterator = accepted_conversion ; iterator->uaudio_fmt != 0 ; iterator++) + if (iterator->uaudio_fmt == format && iterator->uaudio_prec == prec) + break; + + if (iterator->uaudio_fmt == 0) + continue; + + fmt = iterator->freebsd_fmt; + + if (numchan == 2) + fmt |= AFMT_STEREO; + + foundcount++; + + if (fmtcount >= maxfmt) + continue; + + cap->fmtlist[fmtcount++] = fmt; + + if (iscontinuous) { + freq_min = UA_SAMP_LO(asf1d); + freq_max = UA_SAMP_HI(asf1d); + + if (cap->minspeed == 0 || freq_min < cap->minspeed) + cap->minspeed = freq_min; + if (cap->maxspeed == 0) + cap->maxspeed = cap->minspeed; + if (freq_max > cap->maxspeed) + cap->maxspeed = freq_max; + } else { + for (r = 0; r < asf1d->bSamFreqType; r++) { + freq = UA_GETSAMP(asf1d, r); + if (cap->minspeed == 0 || freq < cap->minspeed) + cap->minspeed = freq; + if (cap->maxspeed == 0) + cap->maxspeed = cap->minspeed; + if (freq > cap->maxspeed) + cap->maxspeed = freq; + } + } + } + cap->fmtlist[fmtcount] = 0; + return foundcount; +} + +void +uaudio_chan_set_param_pcm_dma_buff(device_t dev, u_char *start, u_char *end, + struct pcm_channel *pc, int dir) +{ + struct uaudio_softc *sc; + struct chan *ch; + + sc = device_get_softc(dev); +#ifndef NO_RECORDING + if (dir == PCMDIR_PLAY) + ch = &sc->sc_playchan; + else + ch = &sc->sc_recchan; +#else + ch = &sc->sc_playchan; +#endif + + ch->start = start; + ch->end = end; + + ch->pcm_ch = pc; + + return; +} + +void +uaudio_chan_set_param_blocksize(device_t dev, u_int32_t blocksize, int dir) +{ + struct uaudio_softc *sc; + struct chan *ch; + + sc = device_get_softc(dev); +#ifndef NO_RECORDING + if (dir == PCMDIR_PLAY) + ch = &sc->sc_playchan; + else + ch = &sc->sc_recchan; +#else + ch = &sc->sc_playchan; +#endif + + ch->blksize = blocksize; + + return; +} + +int +uaudio_chan_set_param_speed(device_t dev, u_int32_t speed, int reqdir) +{ + const struct uaudio_conversion *iterator; + struct uaudio_softc *sc; + struct chan *ch; + int i, r, score, hiscore, bestspeed; + + sc = device_get_softc(dev); +#ifndef NO_RECORDING + if (reqdir == PCMDIR_PLAY) + ch = &sc->sc_playchan; + else + ch = &sc->sc_recchan; +#else + ch = &sc->sc_playchan; +#endif + /* + * We are successful if we find an endpoint that matches our selected format and it + * supports the requested speed. + */ + hiscore = 0; + bestspeed = 1; + for (i = 0; i < sc->sc_nalts; i++) { + int dir = UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress); + int format = sc->sc_alts[i].encoding; + const struct usb_audio_streaming_type1_descriptor *asf1d = sc->sc_alts[i].asf1desc; + int iscontinuous = asf1d->bSamFreqType == UA_SAMP_CONTNUOUS; + + if ((dir == UE_DIR_OUT) != (reqdir == PCMDIR_PLAY)) + continue; + + for (iterator = accepted_conversion ; iterator->uaudio_fmt != 0 ; iterator++) + if (iterator->uaudio_fmt != format || iterator->freebsd_fmt != (ch->format&0xfffffff)) + continue; + if (iscontinuous) { + if (speed >= UA_SAMP_LO(asf1d) && speed <= UA_SAMP_HI(asf1d)) { + ch->sample_rate = speed; + return speed; + } else if (speed < UA_SAMP_LO(asf1d)) { + score = 0xfff * speed / UA_SAMP_LO(asf1d); + if (score > hiscore) { + bestspeed = UA_SAMP_LO(asf1d); + hiscore = score; + } + } else if (speed > UA_SAMP_HI(asf1d)) { + score = 0xfff * UA_SAMP_HI(asf1d) / speed; + if (score > hiscore) { + bestspeed = UA_SAMP_HI(asf1d); + hiscore = score; + } + } + continue; + } + for (r = 0; r < asf1d->bSamFreqType; r++) { + if (speed == UA_GETSAMP(asf1d, r)) { + ch->sample_rate = speed; + return speed; + } + if (speed > UA_GETSAMP(asf1d, r)) + score = 0xfff * UA_GETSAMP(asf1d, r) / speed; + else + score = 0xfff * speed / UA_GETSAMP(asf1d, r); + if (score > hiscore) { + bestspeed = UA_GETSAMP(asf1d, r); + hiscore = score; + } + } + } + if (bestspeed != 1) { + ch->sample_rate = bestspeed; + return bestspeed; + } + + return 0; +} + +int +uaudio_chan_getptr(device_t dev, int dir) +{ + struct uaudio_softc *sc; + struct chan *ch; + int ptr; + + sc = device_get_softc(dev); +#ifndef NO_RECORDING + if (dir == PCMDIR_PLAY) + ch = &sc->sc_playchan; + else + ch = &sc->sc_recchan; +#else + ch = &sc->sc_playchan; +#endif + + ptr = ch->cur - ch->start; + + return ptr; +} + +void +uaudio_chan_set_param_format(device_t dev, u_int32_t format, int dir) +{ + struct uaudio_softc *sc; + struct chan *ch; + + sc = device_get_softc(dev); +#ifndef NO_RECORDING + if (dir == PCMDIR_PLAY) + ch = &sc->sc_playchan; + else + ch = &sc->sc_recchan; +#else + ch = &sc->sc_playchan; +#endif + + ch->format = format; + + return; +} + +int +uaudio_halt_out_dma(device_t dev) +{ + struct uaudio_softc *sc; + + sc = device_get_softc(dev); + + DPRINTF(("uaudio_halt_out_dma: enter\n")); + if (sc->sc_playchan.pipe != NULL) { + uaudio_chan_close(sc, &sc->sc_playchan); + sc->sc_playchan.pipe = 0; + uaudio_chan_free_buffers(sc, &sc->sc_playchan); + } + return (0); +} + +int +uaudio_halt_in_dma(device_t dev) +{ + struct uaudio_softc *sc; + + sc = device_get_softc(dev); + + if (sc->sc_dying) + return (EIO); + + DPRINTF(("uaudio_halt_in_dma: enter\n")); + if (sc->sc_recchan.pipe != NULL) { + uaudio_chan_close(sc, &sc->sc_recchan); + sc->sc_recchan.pipe = NULL; + uaudio_chan_free_buffers(sc, &sc->sc_recchan); +/* sc->sc_recchan.intr = NULL; */ + } + return (0); +} + +int +uaudio_trigger_input(device_t dev) +{ + struct uaudio_softc *sc; + struct chan *ch; + usbd_status err; + int i, s; + + sc = device_get_softc(dev); + ch = &sc->sc_recchan; + + if (sc->sc_dying) + return (EIO); + +/* uaudio_chan_set_param(ch, start, end, blksize) */ + if (uaudio_init_params(sc, ch, AUMODE_RECORD)) + return (EIO); + + err = uaudio_chan_alloc_buffers(sc, ch); + if (err) + return (EIO); + + err = uaudio_chan_open(sc, ch); + if (err) { + uaudio_chan_free_buffers(sc, ch); + return (EIO); + } + +/* ch->intr = intr; + ch->arg = arg; */ + + s = splusb(); + for (i = 0; i < UAUDIO_NCHANBUFS-1; i++) /* XXX -1 shouldn't be needed */ + uaudio_chan_rtransfer(ch); + splx(s); + + return (0); +} + +int +uaudio_trigger_output(device_t dev) +{ + struct uaudio_softc *sc; + struct chan *ch; + usbd_status err; + int i, s; + + sc = device_get_softc(dev); + ch = &sc->sc_playchan; + + if (sc->sc_dying) + return (EIO); + + if (uaudio_init_params(sc, ch, AUMODE_PLAY)) + return (EIO); + + err = uaudio_chan_alloc_buffers(sc, ch); + if (err) + return (EIO); + + err = uaudio_chan_open(sc, ch); + if (err) { + uaudio_chan_free_buffers(sc, ch); + return (EIO); + } + + s = splusb(); + for (i = 0; i < UAUDIO_NCHANBUFS-1; i++) /* XXX */ + uaudio_chan_ptransfer(ch); + splx(s); + + return (0); +} + +u_int32_t +uaudio_query_mix_info(device_t dev) +{ + int i; + u_int32_t mask = 0; + struct uaudio_softc *sc; + struct mixerctl *mc; + + sc = device_get_softc(dev); + for (i=0; i < sc->sc_nctls; i++) { + mc = &sc->sc_ctls[i]; + if (mc->ctl != SOUND_MIXER_NRDEVICES) { + /* Set device mask bits. + See /usr/include/machine/soundcard.h */ + mask |= (1 << mc->ctl); + } + } + return mask; +} + +u_int32_t +uaudio_query_recsrc_info(device_t dev) +{ + int i, rec_selector_id; + u_int32_t mask = 0; + struct uaudio_softc *sc; + struct mixerctl *mc; + + sc = device_get_softc(dev); + rec_selector_id = -1; + for (i=0; i < sc->sc_nctls; i++) { + mc = &sc->sc_ctls[i]; + if (mc->ctl == SOUND_MIXER_NRDEVICES && + mc->type == MIX_SELECTOR && mc->class == UAC_RECORD) { + if (rec_selector_id == -1) { + rec_selector_id = i; + } else { + printf("There are many selectors. Can't recognize which selector is a record source selector.\n"); + return mask; + } + } + } + if (rec_selector_id == -1) + return mask; + mc = &sc->sc_ctls[rec_selector_id]; + for (i = mc->minval; i <= mc->maxval; i++) { + if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) + continue; + mask |= 1 << mc->slctrtype[i - 1]; + } + return mask; +} + +void +uaudio_mixer_set(device_t dev, unsigned type, unsigned left, unsigned right) +{ + int i; + struct uaudio_softc *sc; + struct mixerctl *mc; + + sc = device_get_softc(dev); + for (i=0; i < sc->sc_nctls; i++) { + mc = &sc->sc_ctls[i]; + if (mc->ctl == type) { + if (mc->nchan == 2) { + /* set Right */ + uaudio_ctl_set(sc, SET_CUR, mc, 1, (int)(right*255)/100); + } + /* set Left or Mono */ + uaudio_ctl_set(sc, SET_CUR, mc, 0, (int)(left*255)/100); + } + } + return; +} + +u_int32_t +uaudio_mixer_setrecsrc(device_t dev, u_int32_t src) +{ + int i, rec_selector_id; + struct uaudio_softc *sc; + struct mixerctl *mc; + + sc = device_get_softc(dev); + rec_selector_id = -1; + for (i=0; i < sc->sc_nctls; i++) { + mc = &sc->sc_ctls[i]; + if (mc->ctl == SOUND_MIXER_NRDEVICES && + mc->type == MIX_SELECTOR && mc->class == UAC_RECORD) { + if (rec_selector_id == -1) { + rec_selector_id = i; + } else { + return src; /* Can't recognize which selector is record source selector */ + } + } + } + if (rec_selector_id == -1) + return src; + mc = &sc->sc_ctls[rec_selector_id]; + for (i = mc->minval; i <= mc->maxval; i++) { + if (src != (1 << mc->slctrtype[i - 1])) + continue; + uaudio_ctl_set(sc, SET_CUR, mc, 0, i); + return (1 << mc->slctrtype[i - 1]); + } + uaudio_ctl_set(sc, SET_CUR, mc, 0, mc->minval); + return (1 << mc->slctrtype[mc->minval - 1]); +} + +static int +uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) +{ + struct snddev_info *d; + struct pcm_channel *c; + struct pcm_feeder *f; + device_t pa_dev = device_get_parent(dev); + struct uaudio_softc *sc = device_get_softc(pa_dev); + + if (verbose < 1) + return 0; + + d = device_get_softc(dev); + if (!d) + return ENXIO; + + PCM_BUSYASSERT(d); + + if (CHN_EMPTY(d, channels.pcm)) { + sbuf_printf(s, " (mixer only)"); + return 0; + } + + sbuf_printf(s, " (%dp:%dv/%dr:%dv channels%s%s)", + d->playcount, d->pvchancount, + d->reccount, d->rvchancount, + (d->flags & SD_F_SIMPLEX)? "" : " duplex", +#ifdef USING_DEVFS + (device_get_unit(dev) == snd_unit)? " default" : "" +#else + "" +#endif + ); + + if (sc->uaudio_sndstat_flag != 0) { + sbuf_cat(s, sbuf_data(&(sc->uaudio_sndstat))); + } + + if (verbose <= 1) + return 0; + + CHN_FOREACH(c, d, channels.pcm) { + + KASSERT(c->bufhard != NULL && c->bufsoft != NULL, + ("hosed pcm channel setup")); + + sbuf_printf(s, "\n\t"); + + /* it would be better to indent child channels */ + sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name); + sbuf_printf(s, "spd %d", c->speed); + if (c->speed != sndbuf_getspd(c->bufhard)) + sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard)); + sbuf_printf(s, ", fmt 0x%08x", c->format); + if (c->format != sndbuf_getfmt(c->bufhard)) + sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard)); + sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags); + if (c->pid != -1) + sbuf_printf(s, ", pid %d", c->pid); + sbuf_printf(s, "\n\t"); + + sbuf_printf(s, "interrupts %d, ", c->interrupts); + if (c->direction == PCMDIR_REC) + sbuf_printf(s, "overruns %d, feed %u, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, c->feedcount, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), + sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), + sndbuf_getblkcnt(c->bufhard), + sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), + sndbuf_getblkcnt(c->bufsoft)); + else + sbuf_printf(s, "underruns %d, feed %u, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, c->feedcount, sndbuf_getready(c->bufsoft), + sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), + sndbuf_getblkcnt(c->bufhard), + sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), + sndbuf_getblkcnt(c->bufsoft)); + sbuf_printf(s, "\n\t"); + + sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); + sbuf_printf(s, " -> "); + f = c->feeder; + while (f->source != NULL) + f = f->source; + while (f != NULL) { + sbuf_printf(s, "%s", f->class->name); + if (f->desc->type == FEEDER_FMT) + sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); + if (f->desc->type == FEEDER_RATE) + sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); + if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER || + f->desc->type == FEEDER_VOLUME) + sbuf_printf(s, "(0x%08x)", f->desc->out); + sbuf_printf(s, " -> "); + f = f->parent; + } + sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware"); + } + + return 0; +} + +void +uaudio_sndstat_register(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + sndstat_register(dev, d->status, uaudio_sndstat_prepare_pcm); +} + +int +uaudio_get_vendor(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_vendor; +} + +int +uaudio_get_product(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_product; +} + +int +uaudio_get_release(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_release; +} + +static int +audio_attach_mi(device_t dev) +{ + device_t child; + struct sndcard_func *func; + + /* Attach the children. */ + /* PCM Audio */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) + return (ENOMEM); + func->func = SCF_PCM; + child = device_add_child(dev, "pcm", -1); + device_set_ivars(child, func); + + bus_generic_attach(dev); + + return 0; /* XXXXX */ +} + +DRIVER_MODULE(uaudio, uhub, uaudio_driver, uaudio_devclass, usbd_driver_load, 0); +MODULE_VERSION(uaudio, 1); + +#endif diff --git a/sys/legacy/dev/sound/usb/uaudio.h b/sys/legacy/dev/sound/usb/uaudio.h new file mode 100644 index 0000000..b73f7a5 --- /dev/null +++ b/sys/legacy/dev/sound/usb/uaudio.h @@ -0,0 +1,56 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2000-2002 Hiroyuki Aizu <aizu@navi.org> + * + * 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. + */ + +#if 0 +#define NO_RECORDING /* XXX: some routines missing from uaudio.c */ +#endif + +/* Defined in uaudio.c, used in uaudio_pcm,c */ + +void uaudio_chan_set_param_pcm_dma_buff(device_t dev, u_char *start, + u_char *end, struct pcm_channel *pc, int dir); +int uaudio_trigger_output(device_t dev); +int uaudio_halt_out_dma(device_t dev); +#ifndef NO_RECORDING +int uaudio_trigger_input(device_t dev); +int uaudio_halt_in_dma(device_t dev); +#endif +void uaudio_chan_set_param(device_t, u_char *, u_char *); +void uaudio_chan_set_param_blocksize(device_t dev, u_int32_t blocksize, int dir); +int uaudio_chan_set_param_speed(device_t dev, u_int32_t speed, int reqdir); +void uaudio_chan_set_param_format(device_t dev, u_int32_t format,int dir); +int uaudio_chan_getptr(device_t dev, int); +void uaudio_mixer_set(device_t dev, unsigned type, unsigned left, + unsigned right); +u_int32_t uaudio_mixer_setrecsrc(device_t dev, u_int32_t src); +u_int32_t uaudio_query_mix_info(device_t dev); +u_int32_t uaudio_query_recsrc_info(device_t dev); +unsigned uaudio_query_formats(device_t dev, int dir, unsigned maxfmt, struct pcmchan_caps *fmt); +void uaudio_sndstat_register(device_t dev); +int uaudio_get_vendor(device_t dev); +int uaudio_get_product(device_t dev); +int uaudio_get_release(device_t dev); diff --git a/sys/legacy/dev/sound/usb/uaudio_pcm.c b/sys/legacy/dev/sound/usb/uaudio_pcm.c new file mode 100644 index 0000000..618a0ed --- /dev/null +++ b/sys/legacy/dev/sound/usb/uaudio_pcm.c @@ -0,0 +1,481 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2000-2002 Hiroyuki Aizu <aizu@navi.org> + * + * 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 <sys/soundcard.h> +#include <dev/sound/pcm/sound.h> +#include <dev/sound/chip.h> + +#include <dev/sound/usb/uaudio.h> + +#include "mixer_if.h" + +struct ua_info; + +struct ua_chinfo { + struct ua_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + u_char *buf; + int dir, hwch; + u_int32_t fmt, spd, blksz; /* XXXXX */ +}; + +struct ua_info { + device_t sc_dev; + u_int32_t bufsz; + struct ua_chinfo pch, rch; +#define FORMAT_NUM 32 + u_int32_t ua_playfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ + u_int32_t ua_recfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ + struct pcmchan_caps ua_playcaps; + struct pcmchan_caps ua_reccaps; + int vendor, product, release; +}; + +#define UAUDIO_DEFAULT_BUFSZ 16*1024 + +static const struct { + int vendor; + int product; + int release; + uint32_t dflags; +} ua_quirks[] = { + { 0x1130, 0xf211, 0x0101, SD_F_PSWAPLR }, +}; + +/************************************************************/ +static void * +ua_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + device_t pa_dev; + + struct ua_info *sc = devinfo; + struct ua_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; + + ch->parent = sc; + ch->channel = c; + ch->buffer = b; + ch->dir = dir; + + pa_dev = device_get_parent(sc->sc_dev); + + ch->buf = malloc(sc->bufsz, M_DEVBUF, M_NOWAIT); + if (ch->buf == NULL) + return NULL; + if (sndbuf_setup(b, ch->buf, sc->bufsz) != 0) { + free(ch->buf, M_DEVBUF); + return NULL; + } + uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, ch->buf+sc->bufsz, ch->channel, dir); + if (bootverbose) + device_printf(pa_dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? + "play" : "rec", sndbuf_getbuf(ch->buffer)); + + ch->dir = dir; +#ifndef NO_RECORDING + ch->hwch = 1; + if (dir == PCMDIR_PLAY) + ch->hwch = 2; +#else + ch->hwch = 2; +#endif + + return ch; +} + +static int +ua_chan_free(kobj_t obj, void *data) +{ + struct ua_chinfo *ua = data; + + if (ua->buf != NULL) + free(ua->buf, M_DEVBUF); + return 0; +} + +static int +ua_chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + device_t pa_dev; + struct ua_info *ua; + + struct ua_chinfo *ch = data; + + /* + * At this point, no need to query as we shouldn't select an unsorted format + */ + ua = ch->parent; + pa_dev = device_get_parent(ua->sc_dev); + uaudio_chan_set_param_format(pa_dev, format, ch->dir); + + ch->fmt = format; + return 0; +} + +static int +ua_chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct ua_chinfo *ch; + device_t pa_dev; + int bestspeed; + + ch = data; + pa_dev = device_get_parent(ch->parent->sc_dev); + + if ((bestspeed = uaudio_chan_set_param_speed(pa_dev, speed, ch->dir))) + ch->spd = bestspeed; + + return ch->spd; +} + +static int +ua_chan_setfragments(kobj_t obj, void *data, u_int32_t blksz, u_int32_t blkcnt) +{ + device_t pa_dev; + struct ua_chinfo *ch = data; + struct ua_info *ua = ch->parent; + + RANGE(blksz, 128, sndbuf_getmaxsize(ch->buffer) / 2); + RANGE(blkcnt, 2, 512); + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= 2) + blkcnt >>= 1; + else if ((blksz >> 1) >= 128) + blksz >>= 1; + else + break; + } + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(ua->sc_dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->blksz = sndbuf_getblksz(ch->buffer); + + pa_dev = device_get_parent(ua->sc_dev); + uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, + ch->buf + sndbuf_getsize(ch->buffer), ch->channel, ch->dir); + uaudio_chan_set_param_blocksize(pa_dev, ch->blksz, ch->dir); + + return 1; +} + +static int +ua_chan_setblocksize(kobj_t obj, void *data, u_int32_t blksz) +{ + struct ua_chinfo *ch = data; + + ua_chan_setfragments(obj, data, blksz, + sndbuf_getmaxsize(ch->buffer) / blksz); + + return ch->blksz; +} + +static int +ua_chan_trigger(kobj_t obj, void *data, int go) +{ + device_t pa_dev; + struct ua_info *ua; + struct ua_chinfo *ch = data; + + if (!PCMTRIG_COMMON(go)) + return 0; + + ua = ch->parent; + pa_dev = device_get_parent(ua->sc_dev); + + /* XXXXX */ + if (ch->dir == PCMDIR_PLAY) { + if (go == PCMTRIG_START) { + uaudio_trigger_output(pa_dev); + } else { + uaudio_halt_out_dma(pa_dev); + } + } else { +#ifndef NO_RECORDING + if (go == PCMTRIG_START) + uaudio_trigger_input(pa_dev); + else + uaudio_halt_in_dma(pa_dev); +#endif + } + + return 0; +} + +static int +ua_chan_getptr(kobj_t obj, void *data) +{ + device_t pa_dev; + struct ua_info *ua; + struct ua_chinfo *ch = data; + + ua = ch->parent; + pa_dev = device_get_parent(ua->sc_dev); + + return uaudio_chan_getptr(pa_dev, ch->dir); +} + +static struct pcmchan_caps * +ua_chan_getcaps(kobj_t obj, void *data) +{ + struct ua_chinfo *ch; + + ch = data; + return (ch->dir == PCMDIR_PLAY) ? &(ch->parent->ua_playcaps) : &(ch->parent->ua_reccaps); +} + +static kobj_method_t ua_chan_methods[] = { + KOBJMETHOD(channel_init, ua_chan_init), + KOBJMETHOD(channel_free, ua_chan_free), + KOBJMETHOD(channel_setformat, ua_chan_setformat), + KOBJMETHOD(channel_setspeed, ua_chan_setspeed), + KOBJMETHOD(channel_setblocksize, ua_chan_setblocksize), + KOBJMETHOD(channel_setfragments, ua_chan_setfragments), + KOBJMETHOD(channel_trigger, ua_chan_trigger), + KOBJMETHOD(channel_getptr, ua_chan_getptr), + KOBJMETHOD(channel_getcaps, ua_chan_getcaps), + { 0, 0 } +}; + +CHANNEL_DECLARE(ua_chan); + +/************************************************************/ +static int +ua_mixer_init(struct snd_mixer *m) +{ + u_int32_t mask; + device_t pa_dev; + struct ua_info *ua = mix_getdevinfo(m); + + pa_dev = device_get_parent(ua->sc_dev); + + mask = uaudio_query_mix_info(pa_dev); + if (!(mask & SOUND_MASK_PCM)) { + /* + * Emulate missing pcm mixer controller + * through FEEDER_VOLUME + */ + pcm_setflags(ua->sc_dev, pcm_getflags(ua->sc_dev) | + SD_F_SOFTPCMVOL); + } + if (!(mask & SOUND_MASK_VOLUME)) { + mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); + mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + } + mix_setdevs(m, mask); + + mask = uaudio_query_recsrc_info(pa_dev); + mix_setrecdevs(m, mask); + + return 0; +} + +static int +ua_mixer_set(struct snd_mixer *m, unsigned type, unsigned left, unsigned right) +{ + device_t pa_dev; + struct ua_info *ua = mix_getdevinfo(m); + + pa_dev = device_get_parent(ua->sc_dev); + uaudio_mixer_set(pa_dev, type, left, right); + + return left | (right << 8); +} + +static int +ua_mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + device_t pa_dev; + struct ua_info *ua = mix_getdevinfo(m); + + pa_dev = device_get_parent(ua->sc_dev); + return uaudio_mixer_setrecsrc(pa_dev, src); +} + +static kobj_method_t ua_mixer_methods[] = { + KOBJMETHOD(mixer_init, ua_mixer_init), + KOBJMETHOD(mixer_set, ua_mixer_set), + KOBJMETHOD(mixer_setrecsrc, ua_mixer_setrecsrc), + + { 0, 0 } +}; +MIXER_DECLARE(ua_mixer); +/************************************************************/ + + +static int +ua_probe(device_t dev) +{ + char *s; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_PCM) + return (ENXIO); + + s = "USB Audio"; + + device_set_desc(dev, s); + return BUS_PROBE_DEFAULT; +} + +static int +ua_attach(device_t dev) +{ + struct ua_info *ua; + struct sndcard_func *func; + char status[SND_STATUSLEN]; + device_t pa_dev; + u_int32_t nplay, nrec, flags; + int i; + + ua = malloc(sizeof(*ua), M_DEVBUF, M_WAITOK | M_ZERO); + ua->sc_dev = dev; + + /* Mark for existence */ + func = device_get_ivars(dev); + if (func != NULL) + func->varinfo = (void *)ua; + + pa_dev = device_get_parent(dev); + ua->vendor = uaudio_get_vendor(pa_dev); + ua->product = uaudio_get_product(pa_dev); + ua->release = uaudio_get_release(pa_dev); + + if (bootverbose) + device_printf(dev, + "USB Audio: " + "vendor=0x%04x, product=0x%04x, release=0x%04x\n", + ua->vendor, ua->product, ua->release); + + ua->bufsz = pcm_getbuffersize(dev, 4096, UAUDIO_DEFAULT_BUFSZ, 65536); + if (bootverbose) + device_printf(dev, "using a default buffer size of %jd\n", (intmax_t)ua->bufsz); + + if (mixer_init(dev, &ua_mixer_class, ua)) { + goto bad; + } + + snprintf(status, SND_STATUSLEN, "at ? %s", PCM_KLDSTRING(snd_uaudio)); + + ua->ua_playcaps.fmtlist = ua->ua_playfmt; + ua->ua_reccaps.fmtlist = ua->ua_recfmt; + nplay = uaudio_query_formats(pa_dev, PCMDIR_PLAY, FORMAT_NUM * 2, &ua->ua_playcaps); + nrec = uaudio_query_formats(pa_dev, PCMDIR_REC, FORMAT_NUM * 2, &ua->ua_reccaps); + + if (nplay > 1) + nplay = 1; + if (nrec > 1) + nrec = 1; + + flags = pcm_getflags(dev); + for (i = 0; i < (sizeof(ua_quirks) / sizeof(ua_quirks[0])); i++) { + if (ua->vendor == ua_quirks[i].vendor && + ua->product == ua_quirks[i].product && + ua->release == ua_quirks[i].release) + flags |= ua_quirks[i].dflags; + } + pcm_setflags(dev, flags); + +#ifndef NO_RECORDING + if (pcm_register(dev, ua, nplay, nrec)) { +#else + if (pcm_register(dev, ua, nplay, 0)) { +#endif + goto bad; + } + + sndstat_unregister(dev); + uaudio_sndstat_register(dev); + + for (i = 0; i < nplay; i++) { + pcm_addchan(dev, PCMDIR_PLAY, &ua_chan_class, ua); + } +#ifndef NO_RECORDING + for (i = 0; i < nrec; i++) { + pcm_addchan(dev, PCMDIR_REC, &ua_chan_class, ua); + } +#endif + pcm_setstatus(dev, status); + + return 0; + +bad: free(ua, M_DEVBUF); + return ENXIO; +} + +static int +ua_detach(device_t dev) +{ + struct ua_info *sc; + struct sndcard_func *func; + int r; + + r = pcm_unregister(dev); + if (r) + return r; + + sc = pcm_getdevinfo(dev); + free(sc, M_DEVBUF); + + /* Mark for deletion */ + func = device_get_ivars(dev); + if (func != NULL) + func->varinfo = NULL; + + return 0; +} + +/************************************************************/ + +static device_method_t ua_pcm_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ua_probe), + DEVMETHOD(device_attach, ua_attach), + DEVMETHOD(device_detach, ua_detach), + + { 0, 0 } +}; + +static driver_t ua_pcm_driver = { + "pcm", + ua_pcm_methods, + PCM_SOFTC_SIZE, +}; + + +DRIVER_MODULE(ua_pcm, uaudio, ua_pcm_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(ua_pcm, uaudio, 1, 1, 1); +MODULE_DEPEND(ua_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(ua_pcm, 1); diff --git a/sys/legacy/dev/sound/usb/uaudioreg.h b/sys/legacy/dev/sound/usb/uaudioreg.h new file mode 100644 index 0000000..3cc09ba --- /dev/null +++ b/sys/legacy/dev/sound/usb/uaudioreg.h @@ -0,0 +1,404 @@ +/* $NetBSD: uaudioreg.h,v 1.12 2004/11/05 19:08:29 kent Exp $ */ +/* $FreeBSD$ */ + +/*- + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#define UAUDIO_VERSION 0x100 + +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 + +#define UDESCSUB_AC_HEADER 1 +#define UDESCSUB_AC_INPUT 2 +#define UDESCSUB_AC_OUTPUT 3 +#define UDESCSUB_AC_MIXER 4 +#define UDESCSUB_AC_SELECTOR 5 +#define UDESCSUB_AC_FEATURE 6 +#define UDESCSUB_AC_PROCESSING 7 +#define UDESCSUB_AC_EXTENSION 8 + +/* The first fields are identical to usb_endpoint_descriptor_t */ +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; + uByte bmAttributes; + uWord wMaxPacketSize; + uByte bInterval; + /* + * The following two entries are only used by the Audio Class. + * And according to the specs the Audio Class is the only one + * allowed to extend the endpoint descriptor. + * Who knows what goes on in the minds of the people in the USB + * standardization? :-( + */ + uByte bRefresh; + uByte bSynchAddress; +} UPACKED usb_endpoint_descriptor_audio_t; + +struct usb_audio_control_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdADC; + uWord wTotalLength; + uByte bInCollection; + uByte baInterfaceNr[1]; +} UPACKED; + +struct usb_audio_streaming_interface_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bTerminalLink; + uByte bDelay; + uWord wFormatTag; +} UPACKED; + +struct usb_audio_streaming_endpoint_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmAttributes; +#define UA_SED_FREQ_CONTROL 0x01 +#define UA_SED_PITCH_CONTROL 0x02 +#define UA_SED_MAXPACKETSONLY 0x80 + uByte bLockDelayUnits; + uWord wLockDelay; +} UPACKED; + +struct usb_audio_streaming_type1_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bFormatType; + uByte bNrChannels; + uByte bSubFrameSize; + uByte bBitResolution; + uByte bSamFreqType; +#define UA_SAMP_CONTNUOUS 0 + uByte tSamFreq[3*2]; /* room for low and high */ +#define UA_GETSAMP(p, n) ((p)->tSamFreq[(n)*3+0] | ((p)->tSamFreq[(n)*3+1] << 8) | ((p)->tSamFreq[(n)*3+2] << 16)) +#define UA_SAMP_LO(p) UA_GETSAMP(p, 0) +#define UA_SAMP_HI(p) UA_GETSAMP(p, 1) +} UPACKED; + +struct usb_audio_cluster { + uByte bNrChannels; + uWord wChannelConfig; +#define UA_CHANNEL_LEFT 0x0001 +#define UA_CHANNEL_RIGHT 0x0002 +#define UA_CHANNEL_CENTER 0x0004 +#define UA_CHANNEL_LFE 0x0008 +#define UA_CHANNEL_L_SURROUND 0x0010 +#define UA_CHANNEL_R_SURROUND 0x0020 +#define UA_CHANNEL_L_CENTER 0x0040 +#define UA_CHANNEL_R_CENTER 0x0080 +#define UA_CHANNEL_SURROUND 0x0100 +#define UA_CHANNEL_L_SIDE 0x0200 +#define UA_CHANNEL_R_SIDE 0x0400 +#define UA_CHANNEL_TOP 0x0800 + uByte iChannelNames; +} UPACKED; + +/* Shared by all units and terminals */ +struct usb_audio_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; +}; + +/* UDESCSUB_AC_INPUT */ +struct usb_audio_input_terminal { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bTerminalId; + uWord wTerminalType; + uByte bAssocTerminal; + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte iTerminal; +} UPACKED; + +/* UDESCSUB_AC_OUTPUT */ +struct usb_audio_output_terminal { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bTerminalId; + uWord wTerminalType; + uByte bAssocTerminal; + uByte bSourceId; + uByte iTerminal; +} UPACKED; + +/* UDESCSUB_AC_MIXER */ +struct usb_audio_mixer_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uByte bNrInPins; + uByte baSourceId[255]; /* [bNrInPins] */ + /* struct usb_audio_mixer_unit_1 */ +} UPACKED; +struct usb_audio_mixer_unit_1 { + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte bmControls[255]; /* [bNrChannels] */ + /*uByte iMixer;*/ +} UPACKED; + +/* UDESCSUB_AC_SELECTOR */ +struct usb_audio_selector_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uByte bNrInPins; + uByte baSourceId[255]; /* [bNrInPins] */ + /* uByte iSelector; */ +} UPACKED; + +/* UDESCSUB_AC_FEATURE */ +struct usb_audio_feature_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uByte bSourceId; + uByte bControlSize; + uByte bmaControls[255]; /* size for more than enough */ + /* uByte iFeature; */ +} UPACKED; + +/* UDESCSUB_AC_PROCESSING */ +struct usb_audio_processing_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uWord wProcessType; + uByte bNrInPins; + uByte baSourceId[255]; /* [bNrInPins] */ + /* struct usb_audio_processing_unit_1 */ +} UPACKED; +struct usb_audio_processing_unit_1{ + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte bControlSize; + uByte bmControls[255]; /* [bControlSize] */ +#define UA_PROC_ENABLE_MASK 1 +} UPACKED; + +struct usb_audio_processing_unit_updown { + uByte iProcessing; + uByte bNrModes; + uWord waModes[255]; /* [bNrModes] */ +} UPACKED; + +/* UDESCSUB_AC_EXTENSION */ +struct usb_audio_extension_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uWord wExtensionCode; + uByte bNrInPins; + uByte baSourceId[255]; /* [bNrInPins] */ + /* struct usb_audio_extension_unit_1 */ +} UPACKED; +struct usb_audio_extension_unit_1 { + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte bControlSize; + uByte bmControls[255]; /* [bControlSize] */ +#define UA_EXT_ENABLE_MASK 1 +#define UA_EXT_ENABLE 1 + /*uByte iExtension;*/ +} UPACKED; + +/* USB terminal types */ +#define UAT_UNDEFINED 0x0100 +#define UAT_STREAM 0x0101 +#define UAT_VENDOR 0x01ff +/* input terminal types */ +#define UATI_UNDEFINED 0x0200 +#define UATI_MICROPHONE 0x0201 +#define UATI_DESKMICROPHONE 0x0202 +#define UATI_PERSONALMICROPHONE 0x0203 +#define UATI_OMNIMICROPHONE 0x0204 +#define UATI_MICROPHONEARRAY 0x0205 +#define UATI_PROCMICROPHONEARR 0x0206 +/* output terminal types */ +#define UATO_UNDEFINED 0x0300 +#define UATO_SPEAKER 0x0301 +#define UATO_HEADPHONES 0x0302 +#define UATO_DISPLAYAUDIO 0x0303 +#define UATO_DESKTOPSPEAKER 0x0304 +#define UATO_ROOMSPEAKER 0x0305 +#define UATO_COMMSPEAKER 0x0306 +#define UATO_SUBWOOFER 0x0307 +/* bidir terminal types */ +#define UATB_UNDEFINED 0x0400 +#define UATB_HANDSET 0x0401 +#define UATB_HEADSET 0x0402 +#define UATB_SPEAKERPHONE 0x0403 +#define UATB_SPEAKERPHONEESUP 0x0404 +#define UATB_SPEAKERPHONEECANC 0x0405 +/* telephony terminal types */ +#define UATT_UNDEFINED 0x0500 +#define UATT_PHONELINE 0x0501 +#define UATT_TELEPHONE 0x0502 +#define UATT_DOWNLINEPHONE 0x0503 +/* external terminal types */ +#define UATE_UNDEFINED 0x0600 +#define UATE_ANALOGCONN 0x0601 +#define UATE_DIGITALAUIFC 0x0602 +#define UATE_LINECONN 0x0603 +#define UATE_LEGACYCONN 0x0604 +#define UATE_SPDIF 0x0605 +#define UATE_1394DA 0x0606 +#define UATE_1394DV 0x0607 +/* embedded function terminal types */ +#define UATF_UNDEFINED 0x0700 +#define UATF_CALIBNOISE 0x0701 +#define UATF_EQUNOISE 0x0702 +#define UATF_CDPLAYER 0x0703 +#define UATF_DAT 0x0704 +#define UATF_DCC 0x0705 +#define UATF_MINIDISK 0x0706 +#define UATF_ANALOGTAPE 0x0707 +#define UATF_PHONOGRAPH 0x0708 +#define UATF_VCRAUDIO 0x0709 +#define UATF_VIDEODISCAUDIO 0x070a +#define UATF_DVDAUDIO 0x070b +#define UATF_TVTUNERAUDIO 0x070c +#define UATF_SATELLITE 0x070d +#define UATF_CABLETUNER 0x070e +#define UATF_DSS 0x070f +#define UATF_RADIORECV 0x0710 +#define UATF_RADIOXMIT 0x0711 +#define UATF_MULTITRACK 0x0712 +#define UATF_SYNTHESIZER 0x0713 + + +#define SET_CUR 0x01 +#define GET_CUR 0x81 +#define SET_MIN 0x02 +#define GET_MIN 0x82 +#define SET_MAX 0x03 +#define GET_MAX 0x83 +#define SET_RES 0x04 +#define GET_RES 0x84 +#define SET_MEM 0x05 +#define GET_MEM 0x85 +#define GET_STAT 0xff + +#define MUTE_CONTROL 0x01 +#define VOLUME_CONTROL 0x02 +#define BASS_CONTROL 0x03 +#define MID_CONTROL 0x04 +#define TREBLE_CONTROL 0x05 +#define GRAPHIC_EQUALIZER_CONTROL 0x06 +#define AGC_CONTROL 0x07 +#define DELAY_CONTROL 0x08 +#define BASS_BOOST_CONTROL 0x09 +#define LOUDNESS_CONTROL 0x0a + +#define FU_MASK(u) (1 << ((u)-1)) + +#define MASTER_CHAN 0 + +#define AS_GENERAL 1 +#define FORMAT_TYPE 2 +#define FORMAT_SPECIFIC 3 + +#define UA_FMT_PCM 1 +#define UA_FMT_PCM8 2 +#define UA_FMT_IEEE_FLOAT 3 +#define UA_FMT_ALAW 4 +#define UA_FMT_MULAW 5 +#define UA_FMT_MPEG 0x1001 +#define UA_FMT_AC3 0x1002 + +#define SAMPLING_FREQ_CONTROL 0x01 +#define PITCH_CONTROL 0x02 + +#define FORMAT_TYPE_UNDEFINED 0 +#define FORMAT_TYPE_I 1 +#define FORMAT_TYPE_II 2 +#define FORMAT_TYPE_III 3 + +#define UA_PROC_MASK(n) (1<< ((n)-1)) +#define PROCESS_UNDEFINED 0 +#define XX_ENABLE_CONTROL 1 +#define UPDOWNMIX_PROCESS 1 +#define UD_ENABLE_CONTROL 1 +#define UD_MODE_SELECT_CONTROL 2 +#define DOLBY_PROLOGIC_PROCESS 2 +#define DP_ENABLE_CONTROL 1 +#define DP_MODE_SELECT_CONTROL 2 +#define P3D_STEREO_EXTENDER_PROCESS 3 +#define P3D_ENABLE_CONTROL 1 +#define P3D_SPACIOUSNESS_CONTROL 2 +#define REVERBATION_PROCESS 4 +#define RV_ENABLE_CONTROL 1 +#define RV_LEVEL_CONTROL 2 +#define RV_TIME_CONTROL 3 +#define RV_FEEDBACK_CONTROL 4 +#define CHORUS_PROCESS 5 +#define CH_ENABLE_CONTROL 1 +#define CH_LEVEL_CONTROL 2 +#define CH_RATE_CONTROL 3 +#define CH_DEPTH_CONTROL 4 +#define DYN_RANGE_COMP_PROCESS 6 +#define DR_ENABLE_CONTROL 1 +#define DR_COMPRESSION_RATE_CONTROL 2 +#define DR_MAXAMPL_CONTROL 3 +#define DR_THRESHOLD_CONTROL 4 +#define DR_ATTACK_TIME_CONTROL 5 +#define DR_RELEASE_TIME_CONTROL 6 |