audio_shim.c revision 88447a05f537aabe9a1bc3d5313f22581ec992a7
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include "audio_shim.h"
extern void *usb_ac_statep;
extern struct cb_ops audio_cb_ops;
/*
* The code here is a "shim" connecting legacy SADA USB drivers with the
* Boomer audio framework, translating interfaces between the two.
* This is an interim measure until new Boomer-native USB drivers are in place.
*/
extern void audio_dump_bytes(const uint8_t *, int);
extern void audio_dump_words(const uint16_t *, int);
extern void audio_dump_dwords(const uint32_t *, int);
#define ASHIM_ENG_PLAY 0
#define ASHIM_ENG_REC 1
#define ASHIM_ENG_DIR(engp) \
static int ashim_eng_start(ashim_eng_t *);
static void ashim_eng_stop(ashim_eng_t *);
static void ashim_af_close(void *arg);
static int ashim_af_start(void *arg);
static void ashim_af_stop(void *arg);
static int ashim_af_format(void *arg);
static int ashim_af_channels(void *arg);
static int ashim_af_rate(void *arg);
};
#define ASHIM_BUFSCALE_MIN 1
#define ASHIM_BUFSCALE_MAX 2000
#define ASHIM_BUFSCALE_DEF 10
/* engine buffer size in terms of fragments */
int ashim_bufscale = ASHIM_BUFSCALE_DEF;
/* use driver specified buffer size instead */
int ashim_use_drvbuf = 0;
/* format override */
uint_t ashim_fmt_sr = 0;
uint_t ashim_fmt_ch = 0;
uint_t ashim_fmt_prec = 0;
/* open without starting engine */
int ashim_eng_disable = 0;
/* dump audio data */
int ashim_dump_audio_bufsel = 0; /* 0 = shim buf; 1 = drv/dma buf */
/* dump i/o related counters */
/* ignore errors when setting control values */
int ashim_ctrl_ignore_errors = 0;
/*
* *************************************************************************
* audio controls
*
* Note: we cannot determine if a SADA device truly supports play or record
* gain adjustment until we actually try the command. Messages showing
* failed control updates will be printed at the DINFO ashim_debug level.
*/
/*
* framework gain range
*/
#define AF_MAX_GAIN 100
#define AF_MIN_GAIN 0
/*
* convert between framework and driver gain values
* must multiply (n) before dividing else D2F_GAIN result will be 0
*/
typedef struct ashim_ctrl_map ashim_ctrl_map_t;
static int ashim_ctrl_rd(void *, uint64_t *);
static int ashim_ctrl_wr_st(void *, uint64_t);
static int ashim_ctrl_wr_mn(void *, uint64_t);
static int ashim_ctrl_wr_bool(void *, uint64_t);
static int ashim_ctrl_wr_ports(void *, uint64_t);
/*
* map framework port definitions to SADA ports
*/
typedef struct {
int dport;
const char *pname;
static ashim_port_map_t ashim_play_pmap[] = {
};
static int ashim_play_pmap_len =
sizeof (ashim_play_pmap) / sizeof (ashim_port_map_t);
static ashim_port_map_t ashim_rec_pmap[] = {
};
static int ashim_rec_pmap_len =
sizeof (ashim_rec_pmap) / sizeof (ashim_port_map_t);
/*
* map frameowork controls to SADA controls
*/
struct ashim_ctrl_map {
int dcmd;
int dir;
};
#define DESC_ST(n, f) { \
.acd_name = (n), \
.acd_flags = (f) | \
.acd_maxvalue = AF_MAX_GAIN, \
.acd_minvalue = AF_MIN_GAIN, \
}
#define FUNC_ST \
#define DESC_MN(n, f) { \
.acd_name = (n), \
.acd_type = AUDIO_CTRL_TYPE_MONO, \
.acd_flags = (f) | \
.acd_maxvalue = AF_MAX_GAIN, \
.acd_minvalue = AF_MIN_GAIN, \
}
#define FUNC_MN \
#define DESC_BOOL(n, f) { \
.acd_name = (n), \
.acd_flags = (f) | \
.acd_maxvalue = 1, \
.acd_minvalue = 0, \
}
#define FUNC_BOOL \
#define DESC_OUTS(n, f) { \
.acd_type = AUDIO_CTRL_TYPE_ENUM, \
.acd_flags = (f) | \
.acd_maxvalue = (n), \
.acd_minvalue = (n), \
}
#define DESC_INS(n, f) { \
.acd_name = AUDIO_CTRL_ID_INPUTS, \
.acd_type = AUDIO_CTRL_TYPE_ENUM, \
.acd_flags = (f) | \
.acd_maxvalue = (n), \
.acd_minvalue = (n), \
}
#define FUNC_PORTS \
/*
* sada <-> framework translation table
* a DESC_NULL() for audio_ctrl_desc_t is used to indicate a
* non-registered sada control that needs some initialization or be used
* internally by the shim
*
* Note: currently, usb_ac only notifies the framework of play volume changes
* from HID so the volume control the only one using AUDIO_CTRL_FLAG_POLL
*/
static ashim_ctrl_map_t ashim_ctrl_map[] = {
FUNC_ST},
FUNC_MN},
FUNC_ST},
FUNC_MN},
FUNC_MN},
DESC_OUTS(0, 0),
DESC_INS(0, 0),
};
static int ashim_ctrl_map_len =
sizeof (ashim_ctrl_map) / sizeof (ashim_ctrl_map_t);
/*
* *************************************************************************
* shim support routines
*/
int ashim_debug = DBG_WARN;
void
{
if (ashim_debug < lvl)
return;
}
void
{
}
void
{
}
void
{
}
void
{
}
/*
* get the maximum format specification the device supports
*/
static void
{
int i;
if (dir == AUDIO_RECORD) {
}
}
}
}
}
/*
* calls the driver's setup routine if present
* For USB audio, this opens a pipe to the endpoint usb_as
* returns AUDIO_SUCCESS or AUDIO_FAILURE
*/
static int
{
return (AUDIO_SUCCESS);
}
/*
* calls the driver's teardown routine if present.
* Note that the amount of teardowns must match the amount of setups used
* in each direction.
* returns AUDIO_SUCCESS or AUDIO_FAILURE
*/
static void
{
}
/*
* sets the audio format (sample rate, channels, precision, encoding)
* returns AUDIO_SUCCESS or AUDIO_FAILURE
*/
static int
{
}
static int
{
case AUDIO_ENCODING_ULAW:
return (AUDIO_FORMAT_ULAW);
case AUDIO_ENCODING_ALAW:
return (AUDIO_FORMAT_ALAW);
case AUDIO_ENCODING_DVI:
return (AUDIO_FORMAT_NONE);
case AUDIO_ENCODING_LINEAR8:
return (AUDIO_FORMAT_U8);
case AUDIO_ENCODING_LINEAR:
break;
default:
return (AUDIO_FORMAT_NONE);
}
case 8:
return (AUDIO_FORMAT_S8);
case 16:
return (AUDIO_FORMAT_S16_NE);
case 24:
return (AUDIO_FORMAT_S24_NE);
case 32:
return (AUDIO_FORMAT_S32_NE);
default:
break;
}
return (AUDIO_FORMAT_NONE);
}
static void
{
return;
}
static int
{
int rv = AUDIO_FAILURE;
int which;
if (dir == AUDIO_PLAY) {
} else {
}
goto OUT;
}
rv = AUDIO_SUCCESS;
OUT:
if (rv != AUDIO_SUCCESS)
return (rv);
}
static int
{
int (*fn)(audiohdl_t, int, int, int, int) =
ddtl("%s - ashim_set_config: cmd 0x%x, dir %d, arg1 0x%x, arg2 0x%x\n",
dinfo("%s: failed update control %s "
"with cmd 0x%x, dir %d, arg1 0x%x, arg2 0x%x",
return (EIO);
}
return (0);
}
static int
{
int rv = AUDIO_SUCCESS;
dinfo("%s: setting control %s to default value "
"0x%llx failed\n",
rv = AUDIO_FAILURE;
}
}
return (rv);
}
static int
{
int rv = AUDIO_SUCCESS;
dinfo("%s: restoring "
"control %s to value 0x%llx failed",
rv = AUDIO_FAILURE;
}
}
return (rv);
}
static inline void
{
"BUF I/O #%llu, SAMPLES %d (%d frames, %d bytes), "
"BUF ADDR 0x%p =======\n",
(unsigned long long)engp->bufio_count,
if (sz <= 0)
return;
case 8:
case 24:
break;
case 16:
break;
case 32:
break;
}
}
}
static inline void
{
if (bufcnt >= 0) {
"REQUEST #%llu, SAMPLES %d "
"(%d frames, %d bytes)\n",
} else {
"SAMPLES %d (%d frames, %d bytes)\n",
}
}
}
/*
*/
static void
{
} else {
}
/*
* Wrap. If sz is exactly the remainder of the buffer
* (bufpos + sz == bufendp) then the second cpsz should be 0 and so
* the second memcpy() should have no effect, with bufpos updated
* to the head of the buffer.
*/
if (ashim_dump_audio_len)
}
if (cpsz) {
if (ashim_dump_audio_len)
}
engp->bufio_count++;
}
static void
{
dinfo("%s - %s: driver i/o: %llu, framework i/o: %llu, "
(long long unsigned)engp->bufio_count,
}
/*
* *************************************************************************
* audio control routines
*/
static uint64_t
{
return (0);
else
return (0);
else
cval = 0;
}
return (cval);
}
static ashim_ctrl_t *
{
break;
}
return (ctrlp);
}
/*
* control callback and related routines
*/
static int
{
int gain;
gain = 0;
else if (left != 0)
else
/*
* mute when gain = 0, and on the transition when gain != 0
* but do not set unmute cmds during non-zero changes
*/
if (gain == 0) {
return (EIO);
AUDIO_PLAY, 0, 0) != AUDIO_SUCCESS)
return (EIO);
}
}
return (0);
}
/*
* control read callback
*/
static int
{
return (0);
}
/*
* stereo level control callback
*/
static int
{
return (EINVAL);
}
0) != AUDIO_SUCCESS)
goto OUT;
1) != AUDIO_SUCCESS) {
/* restore previous left gain value */
left, 0);
goto OUT;
}
OUT:
return (ashim_ctrl_ignore_errors ? 0 : rv);
}
/*
* mono level control callback
*/
static int
{
int gain;
return (EINVAL);
}
gain, 0) != AUDIO_SUCCESS)
goto OUT;
OUT:
return (ashim_ctrl_ignore_errors ? 0 : rv);
}
/*
* boolean control callback
*/
/*ARGSUSED*/
static int
{
(int)cval, 0) != AUDIO_SUCCESS)
goto OUT;
rv = 0;
OUT:
return (ashim_ctrl_ignore_errors ? 0 : rv);
}
/*
* port selection control callback
*/
static int
{
return (EINVAL);
}
0) != AUDIO_SUCCESS)
goto OUT;
rv = 0;
OUT:
return (ashim_ctrl_ignore_errors ? 0 : rv);
}
/*
* audio control registration related routines
*/
static ashim_ctrl_t *
ashim_ctrl_alloc(void)
{
}
static void
{
}
static void
{
}
/*
* returns the amount of modifiable ports
*/
static int
{
int pmaplen;
int i;
int count = 0;
} else {
}
/*
* look at all SADA supported ports then set the corresponding
* framework defined bits in the control description if the driver
* informs us that it is present (avail_ports) and if it can be
*/
for (i = 0; i < pmaplen; i++) {
dinfo("%s: available port: "
"driver 0x%x, framework 0x%s\n",
}
dinfo("%s: modifiable port: "
"(driver 0x%x, framework %s)\n",
count++;
}
}
return (count);
}
static void
{
}
/*
* returns 0 if initialization is successful; failure to initialize is
* not fatal so caller should deallocate and continue on to the next control
*/
static int
{
dinfo("%s: no more than one modifiable port detected for "
"control %s, enabling and skipping\n",
return (1);
}
return (0);
}
/*
* heuristic to determine if a control is actually present by writing the
* default value and checking if the operation succeeds
*/
/*ARGSUSED*/
static int
{
}
static void
{
}
/* required */
}
static int
{
int rv = AUDIO_FAILURE;
int i;
ashim_fmt_t playfmt = {0};
ashim_fmt_t recfmt = {0};
if (ad_feat & AUDIO_HWFEATURE_PLAY)
if (ad_feat & AUDIO_HWFEATURE_RECORD)
for (i = 0; i < ashim_ctrl_map_len; i++) {
mapp = &ashim_ctrl_map[i];
if (!(ad_feat & AUDIO_HWFEATURE_PLAY))
continue;
}
if (!(ad_feat & AUDIO_HWFEATURE_RECORD))
continue;
}
continue;
continue;
continue;
}
ctrlp = ashim_ctrl_alloc();
continue;
}
dinfo("%s: control %s tested invalid, ignoring\n",
continue;
}
goto OUT;
}
dinfo("%s: added control %s, type: %d, "
"flags: 0x%x, min: 0x%llx, max: 0x%llx, default: 0x%llx\n",
}
rv = AUDIO_SUCCESS;
OUT:
if (rv != AUDIO_SUCCESS)
return (rv);
}
/*
* **************************************************************************
* replacements for SADA framework interfaces
*/
void *
{
}
void
{
}
int
{
return (AUDIO_SUCCESS);
return (AUDIO_SUCCESS);
}
{
int inst;
const char *nm;
return (AUDIO_SHIMST2HDL(statep));
}
int
{
return (AUDIO_SUCCESS);
dwarn("%s: am_unregister: audio_dev_unregister() "
return (AUDIO_FAILURE);
}
}
return (AUDIO_SUCCESS);
}
/*ARGSUSED*/
int
{
int i;
return (AUDIO_SUCCESS);
return (AUDIO_SUCCESS);
dwarn("%s: am_detach: audio_dev_unregister() failed\n",
return (AUDIO_FAILURE);
}
return (AUDIO_SUCCESS);
}
int
{
int rv = AUDIO_FAILURE;
if (cmd != DDI_ATTACH)
return (AUDIO_FAILURE);
/*
* setup the engines
*/
/*
* If the device supports both play and record we require duplex
* functionality. However, there are no known simplex SADA devices.
* In this case we limit the device to play only.
*/
if ((ad_feat & AUDIO_HWFEATURE_PLAY) &&
(ad_feat & AUDIO_HWFEATURE_RECORD) &&
!(ad_feat & AUDIO_HWFEATURE_DUPLEX)) {
"required for record");
}
if (ad_feat & AUDIO_HWFEATURE_PLAY) {
goto OUT;
}
if (ad_feat & AUDIO_HWFEATURE_RECORD) {
goto OUT;
}
goto OUT;
goto OUT;
goto OUT;
}
rv = AUDIO_SUCCESS;
OUT:
if (rv != AUDIO_SUCCESS)
return (rv);
}
int
{
unsigned frames;
unsigned i;
int bufcnt = 0;
ddtl("%s - am_get_audio: stop in progress, ignoring\n",
return (0);
}
/* break requests from the driver into fragment sized chunks */
if (ashim_dump_counters_len) {
}
bufcnt++;
/* must move data before updating framework */
}
return (samples);
}
void
{
/*
* XXX
* used to notify framework that device's engine has stopped
*/
}
void
{
unsigned frames;
unsigned i;
int bufcnt = 0;
ddtl("%s - am_send_audio: stop in progress, ignoring\n",
return;
}
/* break requests from the driver into fragment sized chunks */
bufcnt++;
/* must move data before updating framework */
}
}
void
{
int i;
int start = 0;
(void) ashim_ctrl_restore(statep);
if (start)
(void) ashim_eng_start(engp);
}
}
/*
* **************************************************************************
* audio framework engine callbacks
*/
/*ARGSUSED*/
static int
{
return (EIO);
}
goto OUT;
}
goto OUT;
}
goto OUT;
}
/*
* In order to match the requested number of samples per interrupt
* from SADA drivers when computing the fragment size,
* we need to first truncate the floating point result from
* sample rate * channels / intr rate
* then adjust up to an even number, before multiplying it
* with the sample size
*/
if (ashim_use_drvbuf) {
/* adjust buf size to frag boundary */
} else {
if (ashim_bufscale < ASHIM_BUFSCALE_MIN ||
else
}
engp->bufio_count = 0;
goto OUT;
}
rv = 0;
dinfo("%s - %s: "
"frames per frag: %u, frags in buffer: %u, frag size: %u, "
"intr rate: %u, buffer size: %u, buffer: 0x%p - 0x%p\n",
OUT:
if (rv != 0)
return (rv);
}
static void
ashim_af_close(void *arg)
{
}
}
}
static int
{
int (*start)(audiohdl_t);
int rv = 0;
}
return (rv);
}
static void
{
void (*stop)(audiohdl_t);
}
static int
ashim_af_start(void *arg)
{
if (ashim_eng_disable)
return (rv);
return (rv);
}
static void
ashim_af_stop(void *arg)
{
}
static uint64_t
ashim_af_count(void *arg)
{
return (val);
}
static int
ashim_af_format(void *arg)
{
}
static int
ashim_af_channels(void *arg)
{
}
static int
ashim_af_rate(void *arg)
{
}
/*ARGSUSED*/
static void
{
/*
* drivers will call ddi_dma_sync() themselves after requesting data
* on playback and before sending data on record through the shim
*/
}
static size_t
ashim_af_qlen(void *arg)
{
}
/*
* **************************************************************************
* interfaces used by USB audio
*/
/*ARGSUSED*/
int
int sleep)
{
int dcmd = AM_SET_GAIN;
/* only known HWSC command used */
if (cmd != AM_HWSC_SET_GAIN_DELTA) {
"command recieved");
return (AUDIO_FAILURE);
}
return (AUDIO_FAILURE);
}
if (left > AF_MAX_GAIN)
left = AF_MAX_GAIN;
if (right > AF_MAX_GAIN)
right = AF_MAX_GAIN;
if (left < AF_MIN_GAIN)
left = AF_MIN_GAIN;
if (right < AF_MIN_GAIN)
right = AF_MIN_GAIN;
"control %s to value 0x%llx by driver failed",
return (AUDIO_FAILURE);
}
return (AUDIO_SUCCESS);
}