usb_ac.c revision 77e515715b61e28fcf0c3f30936492888cecfd8b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* AUDIO CONTROL Driver: usb_ac is a streams multiplexor that sits
* on top of usb_as and hid and is responsible for
* (1) providing the entry points to audio mixer framework, (2) passing
* streams messages to and from usb_as and hid and (3) processing
* control messages that it can handle.
*
* 1. Mixer entry points are: usb_ac_setup(), usb_ac_teardown(),
* usb_ac_set_config(), usb_ac_set_format(), usb_ac_start_play(),
* usb_ac_pause_play(), usb_ac_stop_play, usb_ac_start_record(),
* usb_ac_stop_record().
* 2. usb_ac is a streams driver that passes streams messages down to
* usb_as that selects the correct alternate with passed format
* 3. usb_ac handles the set_config command through the default pipe
* of sound control interface of the audio device in a synchronous
* manner.
*
* Serialization: usb_ac being a streams driver and having the requirement
* of making non-blockings calls (USBA or streams or mixer) needs to drop
* mutexes over such calls. But at the same time, a competing thread
* can't be allowed to interfere with (1) pipe, (2) streams state.
* So we need some kind of serialization among the asynchronous
* threads that can run in the driver. The serialization is mostly
* etc. Once a routine takes control, it checks if the resource (pipe or
* stream or dev state) is still accessible. If so, it proceeds with
* its job and until it completes, no other thread requiring the same
* resource can run.
*
* PM model in usb_ac: Raise power during attach. If a device is not at full
* power, raise power in the entry points. After the command is over,
* pm_idle_component() is called. The power is lowered in detach().
*
* locking: Warlock is not aware of the automatic locking mechanisms for
* streams drivers.
*/
#include <sys/ndi_impldefs.h>
#include <sys/audiovar.h>
/* debug support */
#ifdef DEBUG
/*
* tunable timeout for usb_as response, allow at least 10 secs for control
* cmd to timeout
*/
int usb_ac_wait_timeout = 10000000;
#endif
/*
* table for converting term types of input and output terminals
* to SADA port types (pretty rough mapping)
*/
static struct {
} usb_ac_term_type_map[] = {
{ 0, 0 }
};
/*
* Module linkage routines for the kernel
*/
static int usb_ac_power(dev_info_t *, int, int);
/*
* STREAMS module entry points
*/
/* plumbing */
uchar_t);
queue_t *);
uint_t);
/* registration */
static int usb_ac_get_curr_n_channels(usb_ac_state_t *, int);
/* descriptor handling */
static int usb_ac_handle_descriptors(usb_ac_state_t *);
static void usb_ac_free_all_units(usb_ac_state_t *);
static void usb_ac_setup_connections(usb_ac_state_t *);
/* power management */
static int usb_ac_pwrlvl0(usb_ac_state_t *);
static int usb_ac_pwrlvl1(usb_ac_state_t *);
static int usb_ac_pwrlvl2(usb_ac_state_t *);
static int usb_ac_pwrlvl3(usb_ac_state_t *);
static void usb_ac_pm_busy_component(usb_ac_state_t *);
static void usb_ac_pm_idle_component(usb_ac_state_t *);
/* event handling */
static int usb_ac_disconnect_event_cb(dev_info_t *);
static int usb_ac_reconnect_event_cb(dev_info_t *);
static int usb_ac_cpr_suspend(dev_info_t *);
static void usb_ac_cpr_resume(dev_info_t *);
static usb_event_t usb_ac_events = {
};
/* misc. support */
static void usb_ac_serialize_access(usb_ac_state_t *);
static void usb_ac_release_access(usb_ac_state_t *);
static void usb_ac_show_traverse_path(usb_ac_state_t *);
int);
int);
static void usb_ac_free_mblk(mblk_t *);
void *, uint_t);
int, void *);
static int usb_ac_send_format_cmd(audiohdl_t, int, int, int,
int, int, int);
static int usb_ac_do_setup(audiohdl_t, int, int);
static void usb_ac_do_teardown(audiohdl_t, int, int);
static void usb_ac_do_pause_play(audiohdl_t, int);
static void usb_ac_do_stop_record(audiohdl_t, int);
/* Mixer entry points */
static int usb_ac_setup(audiohdl_t, int, int);
static void usb_ac_teardown(audiohdl_t, int, int);
static int usb_ac_set_config(audiohdl_t, int, int, int, int, int);
static int usb_ac_set_format(audiohdl_t, int, int, int, int, int, int);
static int usb_ac_start_play(audiohdl_t, int);
static void usb_ac_pause_play(audiohdl_t, int);
static void usb_ac_stop_play(audiohdl_t, int);
static int usb_ac_start_record(audiohdl_t, int);
static void usb_ac_stop_record(audiohdl_t, int);
static int usb_ac_restore_audio_state(usb_ac_state_t *, int);
/*
* External functions
*/
extern void space_free(char *);
/*
* mixer registration data
*/
static am_ad_entry_t usb_ac_entry = {
usb_ac_setup, /* ad_setup() */
usb_ac_teardown, /* ad_teardown() */
usb_ac_set_config, /* ad_set_config() */
usb_ac_set_format, /* ad_set_format() */
usb_ac_start_play, /* ad_start_play() */
usb_ac_pause_play, /* ad_pause_play() */
usb_ac_stop_play, /* ad_stop_play() */
usb_ac_start_record, /* ad_start_record() */
usb_ac_stop_record, /* ad_stop_record() */
NULL, /* ad_ioctl() */
NULL /* ad_iocdata() */
};
/* anchor for soft state structures */
static void *usb_ac_statep;
/* for passing soft state etc. to usb_ac_dacf module */
static usb_ac_state_space_t ssp;
/* STREAMS driver id and limit value structure */
static struct module_info usb_ac_modinfo = {
0xffff, /* module ID number */
"usb_ac", /* module name */
USB_AUDIO_MIN_PKTSZ, /* minimum packet size */
USB_AUDIO_MAX_PKTSZ, /* maximum packet size */
USB_AC_HIWATER, /* high water mark */
USB_AC_LOWATER /* low water mark */
};
/* STREAMS queue processing procedures structures */
/* upper read queue */
static struct qinit usb_ac_urqueue = {
NULL, /* put procedure */
NULL, /* service procedure */
usb_ac_open, /* open procedure */
usb_ac_close, /* close procedure */
NULL, /* unused */
&usb_ac_modinfo, /* module parameters */
NULL /* module statistics */
};
/* upper write queue */
static struct qinit usb_ac_uwqueue = {
usb_ac_uwput, /* put procedure */
audio_sup_wsvc, /* service procedure */
NULL, /* open procedure */
NULL, /* close procedure */
NULL, /* unused */
&usb_ac_modinfo, /* module parameters */
NULL /* module statistics */
};
/* lower read queue */
static struct qinit usb_ac_lrqueue = {
NULL,
NULL,
NULL,
NULL,
&usb_ac_modinfo, /* module parameters */
};
/* lower write queue */
static struct qinit usb_ac_lwqueue = {
NULL,
NULL,
NULL,
NULL,
NULL,
&usb_ac_modinfo, /* module parameters */
};
/* STREAMS entity declaration structure */
static struct streamtab usb_ac_str_info = {
&usb_ac_urqueue, /* upper read queue */
&usb_ac_uwqueue, /* upper write queue */
&usb_ac_lrqueue, /* lower read queue */
&usb_ac_lwqueue, /* lower write queue */
};
/*
* DDI Structures
*
* Entry points structure
*/
static struct cb_ops usb_ac_cb_ops = {
nulldev, /* cb_open */
nulldev, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
nodev, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
&usb_ac_str_info, /* cb_str */
CB_REV, /* cb_rev */
nodev, /* cb_aread */
nodev, /* cb_arwite */
};
/* Device operations structure */
static struct dev_ops usb_ac_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
usb_ac_getinfo, /* devo_getinfo */
nulldev, /* devo_identify - obsolete */
nulldev, /* devo_probe - not needed */
usb_ac_attach, /* devo_attach */
usb_ac_detach, /* devo_detach */
nodev, /* devo_reset */
&usb_ac_cb_ops, /* devi_cb_ops */
NULL, /* devo_busb_ac_ops */
usb_ac_power /* devo_power */
};
/* Linkage structure for loadable drivers */
static struct modldrv usb_ac_modldrv = {
&mod_driverops, /* drv_modops */
"USB Audio Control Driver", /* drv_linkinfo */
&usb_ac_dev_ops /* drv_dev_ops */
};
/* Module linkage structure */
static struct modlinkage usb_ac_modlinkage = {
MODREV_1, /* ml_rev */
(void *)&usb_ac_modldrv, /* ml_linkage */
NULL /* NULL terminates the list */
};
/* warlock directives */
/* standard entry points */
int
_init(void)
{
int rval;
/* initialize the soft state */
return (rval);
}
}
if (!rval) {
}
return (rval);
}
int
_fini(void)
{
int rval;
/* Free the soft state internal structures */
space_free("usb_ac");
}
return (rval);
}
int
{
}
/*ARGSUSED*/
static int
{
int error = DDI_FAILURE;
int instance;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
#define INIT_PROCESS_CNT 3
static int
{
int minor;
char *key;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* wait until all processes are started from main.
* USB enumerates early in boot (ie. consconfig time).
* If the plumbing takes place early, the file descriptors
* are owned by the init process and can never be closed anymore
* Consequently, hot removal is not possible and the dips
* never go away. By waiting some time, e.g. INIT_PROCESS_CNT,
* the problem is avoided.
*/
if (nproc < INIT_PROCESS_CNT) {
"usb_ac%d attach too early", instance);
return (DDI_FAILURE);
}
/*
* Allocate soft state information.
*/
goto fail;
}
/*
* get soft state space and initialize
*/
goto fail;
}
/* get log handle */
0);
"usb_client_attach failed");
return (DDI_FAILURE);
}
USB_PARSE_LVL_IF, 0) != USB_SUCCESS) {
"usb_get_dev_data failed");
return (DDI_FAILURE);
}
/* initialize mutex & cv */
/* register with audiosup */
/*
* we register with pathname, the mgf, product, and serial number
* strings, vid.pid, and driver name which should be pretty unique
*/
}
}
}
"registering with key: %s", key);
"audio_sup_register failed");
goto fail;
}
/* save softstate pointer in audio handle */
/* parse all class specific descriptors */
goto fail;
}
/* we no longer need the descr tree */
/* read .conf file properties */
/* create minor node */
"minor=%d", minor);
"usb_ac_attach: couldn't create minor node mux");
goto fail;
}
/* we are online */
/*
* safe guard the postattach to be executed
* only two states arepossible: plumbed / unplumbed
*/
/* create components to power manage this device */
/* Register for events */
"usb_ac_attach: couldn't register for events");
goto fail;
}
/* report device */
"usb_ac_attach: End");
return (DDI_SUCCESS);
fail:
if (uacp) {
"attach failed");
}
return (DDI_FAILURE);
}
static int
{
int rval;
"usb_ac_detach:");
switch (cmd) {
case DDI_DETACH:
case DDI_SUSPEND:
default:
return (DDI_FAILURE);
}
}
/*
* usb_ac_cleanup:
* cleanup on attach failure and detach
*/
static int
{
int rval = USB_FAILURE;
"usb_ac_cleanup: uacpm=0x%p", (void *)uacpm);
/*
* deregister with audio framework, if it fails we are hosed
* and we probably don't want to plumb again
*/
if (uacp->usb_ac_audiohdl) {
if (uacp->usb_ac_registered_with_mixer) {
return (rval);
}
} else {
}
return (rval);
}
} else {
}
/*
* Disable the event callbacks, after this point, event
* callbacks will never get called. Note we shouldn't hold
* the mutex while unregistering events because there may be a
* competing event callback thread. Event callbacks are done
* with ndi mutex held and this can cause a potential deadlock.
*/
if (uacpm->acpm_wakeup_enabled) {
if (rval != USB_SUCCESS) {
"usb_ac_cleanup: disable remote "
"wakeup failed, rval=%d", rval);
}
} else {
}
}
if (uacpm) {
}
/* free descriptors */
"usb_ac_cleanup: Ending");
return (USB_SUCCESS);
}
/*
* usb_ac_open:
* Open entry point. Called on the plumbing minor node or
* audio or audioctl minor nodes which we pass to audio_sup_open()
* We do not raise power here and wait for the setup callback
*/
/*ARGSUSED*/
static int
{
int instance;
int rval;
return (ENXIO);
}
"usb_ac_open: Begin q=0x%p, minor=0x%x instance=%d "
if (sflag) {
"usb_ac_open: clone open not supported");
return (ENXIO);
}
"usb_ac_open: opening mux");
/*
* This is the plumbing open, initiated during attach/
* connect_event_callback/cpr_resume/first user open.
*/
/* Save the dev_t value of pluming q to use for lower q's */
/* Initialize the queue pointers */
/* release mutex while making streams framework call */
qprocson(q);
return (EIO);
} else {
/* pass the open to audio_sup_open so SADA can do its work */
"usb_ac_open: calling audio_sup_open, q=0x%p, open_cnt=%d",
(void *)q, uacp->usb_ac_busy_count);
/*
* go to full power
*/
if (rval != 0) {
"audio_sup_open rval=%d", rval);
return (rval);
}
}
"usb_ac_open: End q=0x%p, open cnt=%d",
(void *)q, uacp->usb_ac_busy_count);
return (0);
}
/*
* usb_ac_close :
* Close entry point
*/
/*ARGSUSED*/
static int
{
int instance = audio_sup_get_qptr_instance(q);
int rval;
"usb_ac_close: Begin q=0x%p, opencount=%d",
(void *)q, uacp->usb_ac_busy_count);
/* closing the mux? */
"usb_ac_close: closing mux plumbing stream");
/* Wait till all activity in the default pipe has drained */
qprocsoff(q);
return (0);
}
if (rval != 0) {
"audio_sup_close fails %d", rval);
return (rval);
}
/* normal streams closing */
uacp->usb_ac_busy_count --;
"usb_ac_close: End rval=%d q=0x%p, opencount=%d",
return (0);
}
/*
* usb_ac_uwput:
* are processed here. All other ioctls are passed to audio_sup routines
* for further processing.
*/
static int
{
int instance = audio_sup_get_qptr_instance(q);
int error = DDI_SUCCESS;
"usb_ac_uwput: q=0x%p, mp=0x%p", (void *)q, (void *)mp);
/* ioctl from plumbing thread (namely P_LINK) */
usb_ac_plumb_ioctl(q, mp);
return (error);
}
/* Pass to audio_sup routine */
(void) audio_sup_wput(q, mp);
return (error);
}
/*
* usb_ac_lrput:
* read put entry point for the lower mux. Get the response from the
* lower module, signal usb_ac_send_as_cmd(), the thread that is waiting
* for a response to a message sent earlier anbd pass the response
* message block.
*/
static int
{
int instance = audio_sup_get_qptr_instance(q);
int error = DDI_SUCCESS;
int val;
char val1;
"usb_ac_lrput: q=0x%p, mp=0x%p, instance=%d",
case M_CTL:
case M_ERROR:
switch (plumb_infop->acp_driver) {
case USB_AS_PLUMBED:
"reply from usb_as, lrq=0x%p", (void *)q);
break;
case USB_AH_PLUMBED:
"M_CTL from hid, lrq=0x%p", (void *)q);
if (uacp->usb_ac_registered_with_mixer) {
/* Handle relative volume change */
case USB_AUDIO_VOL_CHANGE:
/* prevent unplumbing */
if (uacp->usb_ac_plumbing_state ==
(void) am_hw_state_change(
mutex_enter(&uacp->
}
/* FALLTHRU */
case USB_AUDIO_MUTE:
default:
break;
}
} else {
}
break;
default:
"M_CTL from unknown module(%s)",
}
break;
default:
}
/*
* Nobody is waiting; nothing to send up.
*/
"usb_ac_lrput: done");
return (error);
}
/*
* Power Management
* usb_ac_power:
* power entry point
*/
static int
{
int rval = DDI_FAILURE;
"usb_ac_power: illegal level=%d pwr_states=%d",
goto done;
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
break;
case USB_DEV_OS_PWR_1:
break;
case USB_DEV_OS_PWR_2:
break;
case USB_DEV_OS_FULL_PWR:
break;
}
done:
}
/*
* functions to handle power transition for various levels
* These functions act as place holders to issue USB commands
* to the devices to change their power levels
* Level 0 = Device is powered off
* Level 3 = Device if full powered
* Level 1,2 = Intermediate power level of the device as implemented
* by the hardware.
* Note that Level 0 is OS power-off and Level 3 is OS full-power.
*/
static int
{
int rval;
switch (uacp->usb_ac_dev_state) {
case USB_DEV_ONLINE:
/* Deny the powerdown request if the device is busy */
if (uacpm->acpm_pm_busy != 0) {
return (USB_FAILURE);
}
/* Issue USB D3 command to the device here */
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
case USB_DEV_PWRED_DOWN:
default:
return (USB_SUCCESS);
}
}
/* ARGSUSED */
static int
{
int rval;
/* Issue USB D2 command to the device here */
return (USB_FAILURE);
}
/* ARGSUSED */
static int
{
int rval;
return (USB_FAILURE);
}
static int
{
int rval;
switch (uacp->usb_ac_dev_state) {
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
/* FALLTHRU */
case USB_DEV_ONLINE:
/* we are already in full power */
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
return (USB_SUCCESS);
default:
"usb_ac_pwerlvl3: Illegal dev_state");
return (USB_FAILURE);
}
}
static void
{
"usb_ac_create_pm_components: begin");
/* Allocate the state structure */
uacpm->acpm_capabilities = 0;
USB_SUCCESS) {
"remote Wakeup enabled");
}
} else {
if (uacpm) {
}
"pm not enabled");
}
"usb_ac_create_pm_components: end");
}
/*
* usb_ac_plumb_ioctl:
* caused by ldi_ioctl). Maybe we will need to use this function
* to issue other IOCTLS to children in future from plumbing thread
*/
static void
{
int instance = audio_sup_get_qptr_instance(q);
int n;
"usb_ac_plumb_ioctl, q=0x%p mp=0x%p instance=%d",
case I_PLINK:
"LINK ioctl, index=%d linkblk ptr=0x%p", n, (void *)linkp);
/*
* We keep track of the module that is being
* currently plumbed through usb_ac_current_plumbed_index
* to the plumb structure array. We set the lwq field
* of the plumb structure here.
*/
break;
case I_UNLINK:
case I_PUNLINK:
"UNLINK ioctl, linkblk ptr=0x%p", (void *)linkp);
uacp->usb_ac_dev = 0;
/*
* we bzero the streams info and plumbed structure
* since there is no guarantee that the next plumbing
* will be identical
*/
/* bzero the relevant plumbing structure */
}
break;
default:
goto iocnak;
}
/*
* Common exit path for calls that return a positive
* acknowledgment with a return value of 0.
*/
"usb_ac_plumb_ioctl: End (ACK)");
return;
"usb_ac_plumb_ioctl: End: (NAK)");
}
/*
* usb_ac_get_plumb_info:
* Get plumb_info pointer that matches module "name"
* If name = "usb_as", match the direction also (record or play)
*/
static usb_ac_plumbed_t *
{
int n;
for (n = 0; n < USB_AC_MAX_PLUMBED; n++) {
continue;
}
continue;
}
/* Match direction */
break;
}
break;
}
}
if (n < USB_AC_MAX_PLUMBED) {
}
return (plumb_infop);
}
/*
* usb_ac_get_pinfo_from_lrq:
* Get plumb_info pointer that matches the lrq passed
*/
static usb_ac_plumbed_t *
{
int n;
for (n = 0; n < USB_AC_MAX_PLUMBED; n++) {
return (&uacp->usb_ac_plumbed[n]);
}
}
return (NULL);
}
/*
* usb_ac_get_featureID:
* find out if there is at least one feature unit that supports
* the request controls.
* Return featureID or USB_AC_ID_NONE.
*/
static uint_t
{
}
/*
* usb_ac_feature_unit_check:
* check if a feature unit can support the required channel
* and control combination. Return USB_SUCCESS or USB_FAILURE.
* Called for each matching unit from usb_ac_traverse_connections.
*/
/*ARGSUSED*/
static int
{
int n_channel_controls;
"usb_ac_feature_unit_check: ID=%d ch=%d cntrl=%d",
/*
* check if this control is supported on this channel
*/
if (feature_descrp->bControlSize == 0) {
} else {
"#controls: %d index=%d", n_channel_controls,
if (channel > n_channel_controls) {
} else {
/*
* we only support MUTE and VOLUME
* which are in the first byte
*/
"control: 0x%x",
control) == 0) {
}
}
}
"usb_ac_feature_unit_check: dir=%d featureID=0x%x",
return ((featureID != USB_AC_ID_NONE) ?
}
/*
* Descriptor Management
*
* usb_ac_handle_descriptors:
* extract interesting descriptors from the config cloud
*/
static int
{
int rval = USB_FAILURE;
"config=%ld, interface=%d",
/* find USB_AUDIO_CS_INTERFACE type descriptor */
continue;
}
break;
}
}
"cannot find descriptor type %d", USB_AUDIO_CS_INTERFACE);
return (rval);
}
(void *)&descr, sizeof (usb_audio_cs_if_descr_t));
/* is this a sane header descriptor */
if (!((len >= CS_AC_IF_HEADER_SIZE) &&
"invalid header");
return (rval);
}
"header: type=0x%x subtype=0x%x bcdADC=0x%x\n\t"
"total=0x%x InCol=0x%x",
/*
* we read descriptors by index and store them in ID array.
* the actual parsing is done in usb_ac_add_unit_descriptor()
*/
continue;
}
/* add to ID array */
cvs->cvs_buf_len);
}
rval = USB_SUCCESS;
/* determine port types */
"input port types=0x%x output port types =0x%x",
return (rval);
}
/*
* usb_ac_setup_connections:
* build a matrix reflecting all connections
*/
static void
{
/* allocate array for unit types for quick reference */
KM_SLEEP);
/* allocate array for traversal path */
KM_SLEEP);
/* allocate the connection matrix and set it up */
/* trick to create a 2 dimensional array */
for (i = 0; i < uacp->usb_ac_max_unit; i++) {
p[i] = a + i * uacp->usb_ac_max_unit;
}
uacp->usb_ac_connections = p;
uacp->usb_ac_connections_a = a;
/* traverse all units and set connections */
"traversing unit=0x%x type=0x%x",
/* store type in the first unused column */
/* save the Unit ID in the unit it points to */
case USB_AUDIO_FEATURE_UNIT:
{
"sourceID=0x%x type=0x%x", d->bSourceID,
if (d->bSourceID != 0) {
}
break;
}
{
"sourceID=0x%x type=0x%x", d->bSourceID,
if (d->bSourceID != 0) {
}
break;
}
case USB_AUDIO_MIXER_UNIT:
{
int n_sourceID = d->bNrInPins;
int id;
"sourceID=0x%x type=0x%x c=%d",
d->baSourceID[id],
if (d->baSourceID[id] != 0) {
B_FALSE);
}
}
break;
}
case USB_AUDIO_SELECTOR_UNIT:
{
int n_sourceID = d->bNrInPins;
int id;
"sourceID=0x%x type=0x%x",
d->baSourceID[id],
if (d->baSourceID[id] != 0) {
B_FALSE);
}
}
break;
}
{
int n_sourceID = d->bNrInPins;
int id;
"sourceID=0x%x type=0x%x",
d->baSourceID[id],
if (d->baSourceID[id] != 0) {
B_FALSE);
}
}
break;
}
case USB_AUDIO_EXTENSION_UNIT:
{
int n_sourceID = d->bNrInPins;
int id;
"sourceID=0x%x type=0x%x",
d->baSourceID[id],
if (d->baSourceID[id] != 0) {
B_TRUE);
}
}
break;
}
case USB_AUDIO_INPUT_TERMINAL:
break;
default:
/*
* Ignore the rest because they are not support yet
*/
break;
}
}
#ifdef DEBUG
/* display topology in log buffer */
{
uint_t i, j, l;
char *buf;
"unit types:");
/* two strings so they won't be replaced accidentily by tab */
}
}
uacp->usb_ac_unit_type[i]);
}
"adjacency matrix:");
}
}
}
}
}
#endif
}
/*
* usb_ac_add_unit_descriptor:
* take the parsed descriptor in the buffer and store it in the ID unit
* array. we grow the unit array if the ID exceeds the current max
*/
static void
{
void *descr;
int len;
char *format;
"usb_ac_add_unit_descriptor: 0x%x 0x%x 0x%x",
/* doubling the length should allow for padding */
switch (buffer[2]) {
case USB_AUDIO_INPUT_TERMINAL:
break;
break;
case USB_AUDIO_MIXER_UNIT:
break;
case USB_AUDIO_SELECTOR_UNIT:
break;
case USB_AUDIO_FEATURE_UNIT:
break;
break;
case USB_AUDIO_EXTENSION_UNIT:
break;
default:
/* ignore this descriptor */
return;
}
/* ignore this descriptor */
return;
}
switch (buffer[2]) {
case USB_AUDIO_INPUT_TERMINAL:
{
"input term: type=0x%x sub=0x%x termid=0x%x\n\t"
"termtype=0x%x assoc=0x%x #ch=%d "
"chconf=0x%x ich=0x%x iterm=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
d->bTerminalID, d->wTerminalType,
d->bAssocTerminal, d->bNrChannels,
d->wChannelConfig, d->iChannelNames,
d->iTerminal);
break;
}
{
"output term: type=0x%x sub=0x%x termid=0x%x\n\t"
"termtype=0x%x assoc=0x%x sourceID=0x%x iterm=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
d->bTerminalID, d->wTerminalType,
d->bAssocTerminal, d->bSourceID,
d->iTerminal);
break;
}
case USB_AUDIO_MIXER_UNIT:
{
"mixer unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"#pins=0x%x sourceid[0]=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
break;
}
case USB_AUDIO_SELECTOR_UNIT:
{
"selector unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"#pins=0x%x sourceid[0]=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
break;
}
case USB_AUDIO_FEATURE_UNIT:
{
"feature unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"sourceid=0x%x size=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
break;
}
{
"processing unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"#pins=0x%x sourceid[0]=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
break;
}
case USB_AUDIO_EXTENSION_UNIT:
{
"mixer unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"#pins=0x%x sourceid[0]=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
break;
}
default:
break;
}
}
/*
* usb_ac_alloc_unit:
* check if the unit ID is less than max_unit in which case no
* extra entries are needed. If more entries are needed, copy over
* the existing array into a new larger array
*/
static void
{
"usb_ac_alloc_unit: unit=%d", unit);
if (uacp->usb_ac_units) {
/* existing array is big enough */
return;
}
}
/* allocate two extra ones */
unit += 2;
sizeof (usb_ac_unit_list_t), KM_SLEEP);
if (old) {
}
}
/*
* usb_ac_free_all_units:
* free the entire unit list
*/
static void
{
return;
}
if (unitp) {
if (unitp->acu_descriptor) {
}
}
}
sizeof (usb_ac_unit_list_t));
}
/*
* usb_ac_lookup_port_type:
* map term type to port type
* default just return LINE_IN + LINE_OUT
*/
static int
{
uint_t i;
for (i = 0; ; i++) {
if (usb_ac_term_type_map[i].term_type == 0) {
break;
}
return (usb_ac_term_type_map[i].port_type);
}
}
return (AUDIO_LINE_IN|AUDIO_LINE_OUT);
}
/*
* usb_ac_update_port:
* called for each terminal
*/
/*ARGSUSED*/
static int
{
if (dir & AUDIO_PLAY) {
"usb_ac_update_port: dir=%d type=0x%x port type=%d",
} else {
"usb_ac_update_port: dir=%d type=0x%x port type=%d",
}
return (USB_SUCCESS);
}
/*
* usb_ac_map_termtype_to_port:
* starting from a streaming termtype find all
* input or output terminals and OR into uacp->usb_ac_input_ports
* or uacp->usb_ac_output_ports;
*/
static void
{
"usb_ac_map_term_to_port: dir=%d", dir);
}
/*
* usb_ac_set_port:
* find a selector port (record side only) and set the
* input to the matching pin
*/
static uint_t
{
/* we only support the selector for the record side */
if (dir & AUDIO_RECORD) {
"usb_ac_set_port: id=%d count=%d port=%d",
}
return (USB_SUCCESS);
}
/*
* usb_ac_match_port:
* given the requested port type, find a correspondig term type
* Called from usb_ac_traverse_all_units()
*/
/*ARGSUSED*/
static int
{
"usb_ac_match_port: id=%d dir=%d port=%d",
if (dir & AUDIO_PLAY) {
"usb_ac_match_port: "
"dir=%d type=0x%x port_type=%d port=%d",
} else {
"usb_ac_match_port: "
"dir=%d type=0x%x port_type=%d port=%d",
}
}
/*
* usb_ac_set_selector:
* Called from usb_ac_traverse_all_units()
* Find the correct pin and set selector to this pin
*/
/*ARGSUSED*/
static int
{
int n_sourceID = d->bNrInPins;
int rval = USB_FAILURE;
"usb_ac_set_selector: id=%d dir=%d port=%d",
/*
* for each pin, find a term type that matches the
* requested port type
*/
if (d->baSourceID[pin] == 0) {
break;
}
break;
} else {
continue;
}
}
/* find units connected to this unit */
if (unit != USB_AC_ID_NONE) {
break;
}
}
if (unit != USB_AC_ID_NONE) {
/* pins are 1-based */
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
USB_AUDIO_SET_CUR, /* bRequest */
0, /* wValue */
/* feature unit and id */
1, /* wLength */
&data,
USB_FLAGS_SLEEP) == USB_SUCCESS) {
rval = USB_SUCCESS;
} else {
"set current pin selection failed");
}
} else {
"usb_ac_set_selector: nothing found");
}
return (rval);
}
/*
* usb_ac_set_control:
* apply func to all units of search_target type for both the
* requested channel and master channel
*/
static uint_t
{
"usb_ac_set_control: dir=%d type=%d ch=%d cntl=%d",
if ((channel != 0) &&
(all_or_one == USB_AC_FIND_ALL))) {
/* try master channel */
channel = 0;
}
return (id);
}
/*
* usb_ac_traverse_all_units:
* traverse all units starting with all IT or OT depending on direction.
* If no unit is found for the particular channel, try master channel
* If a matching unit is found, apply the function passed by
* the caller
*/
static uint_t
{
"usb_ac_traverse_all_units: "
"dir=%d type=%d ch=%d cntl=%d all=%d depth=%d",
/* keep track of recursion */
if ((*depth)++ > USB_AC_MAX_DEPTH) {
"Unit topology too complex, giving up");
return (USB_AC_ID_NONE);
}
/* is this an IT or OT? */
continue;
}
/* start at streaming term types */
if (dir & AUDIO_PLAY) {
if (d->wTerminalType !=
continue;
}
} else {
if (d->wTerminalType !=
continue;
}
}
/* find units connected to this unit */
if ((all_or_one == USB_AC_FIND_ONE) &&
(id != USB_AC_ID_NONE)) {
break;
}
}
(*depth)--;
}
/*
* usb_ac_set_monitor_gain_control:
* search for a feature unit between output terminal (OT) and
* input terminal. We are looking for a path between
* for example a microphone and a speaker through a feature unit
* and mixer
*/
static uint_t
{
"usb_ac_set_monitor_gain_control: dir=%d type=%d ch=%d cntl=%d",
/* is this an OT and not stream type? */
(d->wTerminalType != USB_AUDIO_TERM_TYPE_STREAMING)) {
/* find units connected to this unit */
if ((all_or_one == USB_AC_FIND_ONE) &&
(id != USB_AC_ID_NONE)) {
break;
}
}
}
return (id);
}
/*
*/
static void
{
"usb_ac_push_unit_id: pushing %d at %d", unit,
}
static void
{
"usb_ac_push_unit_id: popping %d at %d", unit,
}
/*
* usb_ac_show_traverse_path:
* display entire path, just for debugging
*/
static void
{
int i;
for (i = 0; i < uacp->usb_ac_traverse_path_index; i++) {
"traverse path %d: unit=%d type=%d",
i, uacp->usb_ac_traverse_path[i],
}
}
/*
* usb_ac_check_path:
* check for a specified type in the traverse path
*/
static int
{
int i;
for (i = 0; i < uacp->usb_ac_traverse_path_index; i++) {
return (USB_SUCCESS);
}
}
return (USB_FAILURE);
}
/*
* usb_ac_traverse_connections:
* traverse all units and for each unit with the right type, call
* func. If the func returns a success and search == USB_AC_FIND_ONE,
* we are done. If all is set then we continue until we terminate
* and input or output terminal.
* For audio play, we traverse columns starting from an input terminal
* to an output terminal while for record we traverse rows from output
* terminal to input terminal.
*/
static uint_t
{
"usb_ac_traverse_connections: "
"start=%d dir=%d type=%d ch=%d cntl=%d all=%d depth=%d",
all_or_one, *depth);
/* keep track of recursion depth */
if ((*depth)++ > USB_AC_MAX_DEPTH) {
"Unit topology too complex, giving up");
return (USB_AC_ID_NONE);
}
if (entry) {
"start=%d unit=%d entry=%d type=%d "
"done=%d found=%d",
/* did we find a matching type? */
"match: dir=%d unit=%d type=%d",
/* yes, no apply function to this unit */
(*count)++;
"func returned success, "
"unit=%d all=%d", unit,
/* are we done? */
if (all_or_one == USB_AC_FIND_ONE) {
break;
}
}
}
/* did we find the terminating unit */
continue;
}
if ((id != USB_AC_ID_NONE) &&
(all_or_one == USB_AC_FIND_ONE)) {
break;
}
}
}
(*depth)--;
}
/*
* Event Management
*
* usb_ac_disconnect_event_cb:
* The device has been disconnected. we either wait for
* detach or a reconnect event.
*/
static int
{
"usb_ac_disconnect_event_cb: dip=0x%p", (void *)dip);
/* setting to disconnect state will prevent replumbing */
if (uacp->usb_ac_busy_count) {
"device was disconnected while busy. "
"Data may have been lost");
}
"usb_ac_disconnect_event_cb: done");
return (USB_SUCCESS);
}
/*
* usb_ac_cpr_suspend:
*/
static int
{
"usb_ac_cpr_suspend: Begin");
"usb_ac_cpr_suspend: End");
return (USB_SUCCESS);
}
/*
* usb_ac_reconnect_event_cb:
* The device was disconnected but this instance not detached, probably
* because the device was busy.
* if the same device, continue with restoring state
* We should either be in the unplumbed state or the plumbed open
* state.
*/
static int
{
"usb_ac_reconnect_event_cb: dip=0x%p", (void *)dip);
/* check the plumbing state */
if (uacp->usb_ac_plumbing_state ==
}
if (uacp->usb_ac_busy_count) {
"busy device has been reconnected");
}
return (USB_SUCCESS);
}
/*
* usb_ac_cpr_resume:
* Restore device state
*/
static void
{
"usb_ac_cpr_resume");
}
/*
* usb_ac_restore_device_state:
* Set original configuration of the device
* enable wrq - this starts new transactions on the control pipe
*/
static void
{
int rval;
"usb_ac_restore_device_state:");
/* Check if we are talking to the same device */
/* change the device state from suspended to disconnected */
return;
}
if (uacpm) {
if (uacpm->acpm_wakeup_enabled) {
USB_REMOTE_WAKEUP_ENABLE)) != USB_SUCCESS) {
"usb_ac_restore_device_state: "
"remote wakeup "
"enable failed, rval=%d", rval);
}
}
}
/* prevent unplumbing */
(void) usb_ac_restore_audio_state(uacp, 0);
}
}
/*
* usb_ac_am_restore_state
*/
static void
usb_ac_am_restore_state(void *arg)
{
"usb_ac_am_restore_state: Begin");
if (uacp->usb_ac_plumbing_state ==
/*
* allow hid and usb_as to restore themselves
* (some handshake would have been preferable though)
*/
}
/* allow unplumbing */
"usb_ac_am_restore_state: End");
}
/*
* usb_ac_restore_audio_state:
*/
static int
{
"usb_ac_restore_audio_state: flag=%d", flag);
switch (uacp->usb_ac_plumbing_state) {
case USB_AC_STATE_PLUMBED:
break;
case USB_AC_STATE_UNPLUMBED:
return (USB_SUCCESS);
default:
return (USB_FAILURE);
}
/*
* increment busy_count again, it will be decremented
* in usb_ac_am_restore_state
*/
if (flag & USB_FLAGS_SLEEP) {
usb_ac_am_restore_state((void *)uacp);
} else {
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
}
/*
* Mixer Callback Management
* NOTE: all mixer callbacks are serialized. we cannot be closed while
* we are in the middle of a callback. There needs to be a
* teardown first. We cannot be unplumbed as long as we are
* still open.
*
* usb_ac_setup:
* Send setup to usb_as if the first setup
* Check power is done in usb_ac_send_as_cmd()
*/
static int
{
int rval = AUDIO_SUCCESS;
"usb_ac_setup: Begin ahdl=0x%p, stream=%d, flag=%d",
return (AUDIO_FAILURE);
}
if (flag & AUDIO_PLAY) {
}
}
"usb_ac_setup: rval=%d", rval);
}
/*
* usb_ac_do_setup:
* Wrapper function for usb_ac_setup which can be called
* either from audio framework for usb_ac_set_format
*/
static int
{
int dir;
"usb_ac_do_setup: Begin ahdl=0x%p, stream=%d, flag=%d",
/*
* Handle multiple setup calls. Pass the setup call to usb_as only
* the first time so isoc pipe will be opened only once
*/
if (streams_infop->acs_setup_teardown_count++) {
"usb_ac_do_setup: more than one setup, cnt=%d",
return (USB_SUCCESS);
}
/* Send setup command to usb_as */
USB_SUCCESS) {
"usb_ac_do_setup: failure");
return (USB_FAILURE);
}
"usb_ac_do_setup: End");
return (USB_SUCCESS);
}
/*
* usb_ac_teardown:
* Send teardown to usb_as if the last teardown
* Check power is done in usb_ac_send_as_cmd()
* NOTE: allow teardown when disconnected
*/
static void
{
"usb_ac_teardown: Begin ahdl=0x%p, stream=%d",
if (flag & AUDIO_PLAY) {
}
if (flag & AUDIO_RECORD) {
}
"usb_ac_teardown: End");
}
/*
* usb_ac_do_teardown()
* Check power is done in usb_ac_send_as_cmd()
*/
static void
{
int dir;
"usb_ac_do_teardown: Begin ahdl=0x%p, stream=%d",
/* There should be at least one matching setup call */
/*
* only this is the last teardown so that isoc pipe is closed
* only once
*/
if (--(streams_infop->acs_setup_teardown_count)) {
"cnt=%d",
return;
}
/* Send teardown command to usb_as */
(void *)NULL) != USB_SUCCESS) {
"usb_ac_do_teardown: failure");
return;
}
"usb_ac_do_teardown: End");
}
/*
* usb_ac_set_config:
* This routine will send control commands to get the max
* and min gain balance, calculate the gain to be set from the
* arguments and send another control command to set it.
* Check power is done here since we will access the default pipe
*/
static int
{
char *what;
int rval = AUDIO_FAILURE;
uchar_t n_channels = 0;
short muteval;
"usb_ac_set_config: Begin ahdl=0x%p\n\t"
"stream=%d, cmd=%d, flag=%d, arg1=%d, arg2=%d",
return (AUDIO_FAILURE);
}
return (AUDIO_FAILURE);
}
switch (command) {
case AM_SET_GAIN:
/*
* Set the gain for a channel. The audio mixer calculates the
* impact, if any, on the channel's gain.
*
* 0 <= gain <= AUDIO_MAX_GAIN
*
* arg1 --> gain
* arg2 --> channel #, 0 == left, 1 == right
*/
what = "gain";
/*
* We service the set_config command when the device is
* plumbed and opened.
*/
if (channel > n_channels) {
"usb_ac_set_config: channel(%d) passed is "
goto done;
}
count = 0;
/*
* If feature unit id could not be found, it probably means
* and we just return success if we haven't completed
* the registration with the mixer yet
*/
if (count == 0) {
"mixer=%d, no featureID, arg1=%d",
} else {
}
break;
case AM_SET_PORT:
what = "port";
break;
case AM_SET_MONITOR_GAIN:
what = "monitor gain";
dir = AUDIO_RECORD;
/*
* We service the set_config command when the device is
* plumbed and opened.
*/
if (channel > n_channels) {
"usb_ac_set_config: channel(%d) passed is "
goto done;
}
count = 0;
/*
* always return success since we told the mixer
* we always support this and sdtaudiocontrol displays
* monitor gain regardless.
*/
break;
case AM_OUTPUT_MUTE:
what = "mute";
/*
* arg1 != 0 --> mute
* arg1 == 0 --> unmute
* arg2 --> not used
*/
count = 0;
break;
case AM_MIC_BOOST:
what = "mic boost";
break;
case AM_SET_GAIN_BAL:
what = "set gain bal";
break;
default:
what = "unknown";
}
done:
/* Now it's safe to release access to other routines */
return (rval);
}
/*
* usb_ac_set_monitor_gain:
* called for each output terminal which supports
* from usb_ac_traverse_connections
*/
static int
{
"usb_ac_set_monitor_gain: ");
"id=%d dir=%d ch=%d cntl=%d gain=%d type=%d term type=0x%x",
/* log how we got here */
/* we only care about the ITs connected to real hw inputs */
switch (d->wTerminalType) {
return (USB_FAILURE);
default:
break;
}
/*
* we can only do this if the microphone is mixed into the
* audio output so look for a mixer first
*/
USB_SUCCESS) {
int i, id;
/* now look for a feature unit */
i--) {
case USB_AUDIO_MIXER_UNIT:
/* the FU should be before the mixer */
return (USB_FAILURE);
case USB_AUDIO_FEATURE_UNIT:
/*
* now set the volume
*/
/* try master channel */
USB_SUCCESS) {
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
default:
continue;
}
}
}
return (USB_FAILURE);
}
/*
* usb_ac_set_gain is called for each feature unit which supports
* the requested controls from usb_ac_traverse_connections
* we still need to check whether this unit supports the requested
* control.
*/
static int
{
"usb_ac_set_gain: id=%d dir=%d ch=%d cntl=%d gain=%d",
return (USB_FAILURE);
}
"usb_ac_set_gain: getting max gain failed");
return (USB_FAILURE);
}
"usb_ac_set_gain: getting min gain failed");
return (USB_FAILURE);
}
"usb_ac_set_gain: getting cur gain failed");
return (USB_FAILURE);
}
/*
* Set the gain for a channel. The audio mixer calculates the
* impact, if any, on the channel's gain.
*
* 0 <= gain <= AUDIO_MAX_GAIN
*
* channel #, 0 == left, 1 == right
*/
if (gain == 0) {
} else {
}
"usb_ac_set_gain: ch=%d dir=%d max=%d min=%d gain=%d",
featureID) != USB_SUCCESS) {
"usb_ac_set_gain: setting volume failed");
return (USB_FAILURE);
}
"usb_ac_set_gain: getting cur gain failed");
}
"usb_ac_set_gain done: "
return (USB_SUCCESS);
}
/*
* usb_ac_set_format
* This mixer callback initiates a command to be sent to
* usb_as to select an alternate with the passed characteristics
* and also to set the sample frequency.
* Note that this may be called when a playing is going on in
* the streaming interface. To handle that, first stop
* command, send the set_format command down and then reopen
* after a set_format command. (2) Check power is done in
* usb_ac_send_as_cmd().
*/
static int
{
int dir;
int rval;
"usb_ac_set_format: Begin ahdl=0x%p, stream=%d, flag=%d, "
return (AUDIO_FAILURE);
}
if (plumb_infop == NULL) {
"usb_ac_set_format: no plumb info");
return (AUDIO_FAILURE);
}
/* isoc pipe not open and playing is not in progress */
if (streams_infop->acs_setup_teardown_count == 0) {
return ((rval == USB_SUCCESS) ?
}
/* isoc pipe is open and playing might be in progress */
/* Keep a copy of the old format */
sizeof (usb_audio_formats_t));
if (dir == AUDIO_PLAY) {
} else if (dir == AUDIO_RECORD) {
}
/* This blocks until the current isoc xfer is over */
/*
* Setting new alternate has failed, try restoring
* old one.
* If there is a bandwidth failure, hang around
* till bandwidth is available. Also we know that
* there is a matching alternate, so that can't fail.
*/
USB_FAILURE) {
/* We closed the pipe; reopen it */
return (AUDIO_FAILURE);
}
}
/* This should block until successful */
"usb_ac_set_format: End");
return (AUDIO_SUCCESS);
}
/*
* usb_ac_get_curr_n_channels:
* Return no. of channels from the current format table
*/
static int
{
}
/*
* usb_ac_get_cur_format:
* Get format for the current alternate
*/
static usb_audio_formats_t *
{
if (plumb_infop == NULL) {
"usb_ac_get_curr_format: no plumb info");
return (NULL);
}
return (&streams_infop->acs_cur_fmt);
}
/*
* usb_ac_send_format_cmd
* Sets format and get alternate setting that matches with
* the format from the usb_as playing or recording interface
* Send the set sample freq command down to usb_as.
*/
static int
{
"usb_ac_send_format_cmd: Begin ahdl=0x%p, stream=%d, dir=%d, "
return (USB_FAILURE);
}
"usb_ac_send_format_cmd: plumb_infop=0x%p, streams_infop=0x%p",
(void *)plumb_infop, (void *)streams_infop);
/* save format info */
/*
* Set format for the streaming interface with lower write queue
* This boils down to set_alternate interface command in
* usb_as and the reply mp contains the currently active
* alternate number that is stored in the as_req structure
*/
"usb_ac_send_format_cmd: failed");
return (USB_FAILURE);
} else {
/* alternate number stored and reply mp freed */
}
/* Set the sample rate */
&sample) != USB_SUCCESS) {
"usb_ac_send_format_cmd: setting format failed");
return (USB_FAILURE);
}
"usb_ac_send_format_cmd: End");
return (USB_SUCCESS);
}
/*
* usb_ac_start_play
* Send a start_play command down to usb_as
* Check power is done in usb_ac_send_as_cmd()
*/
static int
{
"usb_ac_start_play: Begin ahdl=0x%p, stream=%d",
return (AUDIO_FAILURE);
}
"usb_ac_start_play: plumb_infop=0x%p, streams_infop=0x%p",
(void *)plumb_infop, (void *)streams_infop);
/* Check for continuous sample rate done in usb_as */
samples++;
}
/* Send setup command to usb_as */
(void *)&play_req) != USB_SUCCESS) {
"usb_ac_start_play: failure");
return (AUDIO_FAILURE);
}
"usb_ac_start_play: End");
return (AUDIO_SUCCESS);
}
/*
* usb_ac_pause_play:
* Wrapper function for usb_ac_do_pause_play and gets
* called from mixer framework.
*/
static void
{
"usb_ac_pause_play: Begin ahdl=0x%p, stream=%d",
return;
}
"usb_ac_pause_play: End");
}
/*
* usb_ac_do_pause_play:
* Send a pause_play command to usb_as.
* Check power is done in usb_ac_send_as_cmd()
*/
static void
{
"usb_ac_do_pause_play: Begin ahdl=0x%p, stream=%d",
/* Send setup command to usb_as */
(void *)NULL) != USB_SUCCESS) {
"usb_ac_do_pause_play: failure");
}
"usb_ac_do_pause_play: End");
}
/*
* usb_ac_stop_play:
* Wrapper function for usb_ac_pause_play and gets
* called from mixer framework.
*/
static void
{
"usb_ac_stop_play: Begin ahdl=0x%p, stream=%d",
"usb_ac_stop_play: End");
}
/*
* usb_ac_start_record:
* Sends a start record command down to usb_as.
* Check power is done in usb_ac_send_as_cmd()
*/
static int
{
"usb_ac_start_record: Begin ahdl=0x%p, stream=%d",
return (AUDIO_FAILURE);
}
/* Send setup command to usb_as */
(void *)&ahdl) != USB_SUCCESS) {
"usb_ac_start_record: failure");
return (AUDIO_FAILURE);
}
"usb_ac_start_record: End");
return (AUDIO_SUCCESS);
}
/*
* usb_ac_stop_record:
* Wrapper function for usb_ac_do_stop_record and is
* called form mixer framework.
*/
static void
{
"usb_ac_stop_record: Begin ahdl=0x%p, stream=%d",
"usb_ac_stop_record: End");
}
/*
* usb_ac_do_stop_record:
* Sends a stop_record command down.
* Check power is done in usb_ac_send_as_cmd()
*/
static void
{
"usb_ac_do_stop_record: Begin ahdl=0x%p, stream=%d",
/* Send setup command to usb_as */
NULL) != USB_SUCCESS) {
"usb_ac_do_stop_record: failure");
}
"usb_ac_do_stop_record: End");
}
/*
* Helper Functions for Mixer callbacks
*
* usb_ac_get_maxmin_volume:
* Send USBA command down to get the maximum or minimum gain balance
* Calculate min or max gain balance and return that. Return
* USB_FAILURE for failure cases
*/
static int
int dir, int feature_unitID)
{
short max_or_min;
"usb_ac_get_maxmin_volume: channel=%d, cmd=%d dir=%d",
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
cmd, /* bRequest */
/* feature unit and id */
2, /* wLength */
&data,
USB_FLAGS_SLEEP) != USB_SUCCESS) {
"usb_ac_get_maxmin_volume: failed, "
"cr=%d, cb=0x%x cmd=%d, data=0x%p",
return (USB_FAILURE);
}
"usb_ac_get_maxmin_volume: max_or_min=0x%x", max_or_min);
return (max_or_min);
}
/*
* usb_ac_set_volume:
* Send USBA command down to set the gain balance
*/
static int
int feature_unitID)
{
int rval = USB_FAILURE;
"usb_ac_set_volume: channel=%d gain=%d dir=%d FU=%d",
/* Construct the mblk_t from gain for sending to USBA */
if ((rval = usb_pipe_sync_ctrl_xfer(
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
USB_AUDIO_SET_CUR, /* bRequest */
/* feature unit and id */
2, /* wLength */
&data, 0,
"usb_ac_set_volume: failed, cr=%d cb=0x%x",
}
return (rval);
}
/*
* usb_ac_set_mute is called for each unit that supports the
* requested control from usb_ac_traverse_connections
*/
static int
{
int rval = USB_FAILURE;
return (USB_FAILURE);
}
/* Construct the mblk_t for sending to USBA */
if ((rval = usb_pipe_sync_ctrl_xfer(
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
USB_AUDIO_SET_CUR, /* bRequest */
/* feature unit and id */
1, /* wLength */
&data,
0, /* attributes */
}
return (rval);
}
/*
* usb_ac_send_as_cmd:
* Allocate message blk, send a command down to usb_as,
* wait for the reply and free the message
*
* although not really needed to raise power if sending to as
* it seems better to ensure that both interfaces are at full power
*/
static int
{
int error = USB_FAILURE;
"usb_ac_send_as_cmd: Begin lwq=0x%p, cmd=0x%x, arg=0x%p",
if (!canputnext(lwq)) {
"usb_ac_send_as_cmd: canputnext failed");
return (error);
}
/*
* Allocate mblk for a particular command
*/
switch (cmd) {
case USB_AUDIO_SET_FORMAT:
sizeof (usb_audio_formats_t));
break;
case USB_AUDIO_TEARDOWN:
case USB_AUDIO_STOP_RECORD:
case USB_AUDIO_PAUSE_PLAY:
case USB_AUDIO_SETUP:
break;
case USB_AUDIO_START_RECORD:
sizeof (audiohdl_t *));
break;
sizeof (int));
break;
case USB_AUDIO_START_PLAY:
sizeof (usb_audio_play_req_t));
break;
default:
"usb_ac_send_as_cmd: unknown cmd=%d", cmd);
return (error);
}
"usb_ac_send_as_cmd: can't get mblk to send cmd down");
return (error);
}
/*
* Set wait flag and send message down; we have made sure
* before that canputnext succeeds. Note mp will be freed down
*/
/*
* Wait for the response; reply will arrive through rput()
* M_CTL and the cv_wait will be signaled there and wait flag
* will be reset
*/
#ifndef DEBUG
&uacp->usb_ac_mutex);
#else
int rval;
if (rval == -1) {
"usb_ac_send_as_cmd:"
" timeout happen before cmd complete.");
} else {
"usb_ac_send_as_cmd:"
" not signaled by USB_AS_PLUMBED.");
}
}
#endif
}
/* Wait is over, get the reply data */
"usb_ac_send_as_cmd: db_type=0x%x cmd=0x%x",
case M_CTL:
case USB_AUDIO_SET_FORMAT:
/*
* This command sets mixer format data
* and returns alternate setting that matches
*/
sizeof (int));
"alternate returned %d",
/*FALLTHROUGH*/
case USB_AUDIO_SETUP:
case USB_AUDIO_START_PLAY:
case USB_AUDIO_PAUSE_PLAY:
case USB_AUDIO_START_RECORD:
case USB_AUDIO_STOP_RECORD:
case USB_AUDIO_TEARDOWN:
error = USB_SUCCESS;
break;
default:
break;
}
break;
case M_ERROR:
default:
error = USB_FAILURE;
}
if (mp) {
}
return (error);
}
/*
* usb_ac_allocate_req_mblk:
* Allocate a message block with the specified M_CTL cmd,
* The 2nd mblk contains the data for the command with a length len
*/
static mblk_t *
{
"usb_ac_allocate_req_mblk: cmd=0x%x, buf=0x%p, len=%d",
return (mp);
}
"usb_ac_allocate_req_mblk: mp=0x%p", (void *)mp);
return (mp);
}
/*
* usb_ac_free_mblk:
* Free the message block
*/
static void
{
}
}
/*
*/
static void
{
}
static void
{
}
static void
{
"usb_ac_pm_busy_component: %d",
DDI_SUCCESS) {
"usb_ac_pm_busy_component failed: %d",
}
}
}
static void
{
DDI_SUCCESS) {
"usb_ac_pm_idle_component: %d",
}
}
}