audio_4231.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* audiocs Audio Driver
*
* This Audio Driver controls the Crystal CS4231 Codec used on many SPARC
* platforms. It does not support the CS4231 on Power PCs or x86 PCs. It
* does support two different DMA engines, the APC and EB2. The code for
* those DMA engines is split out and a well defined, but private, interface
* is used to control those DMA engines.
*
* For some reason setting the CS4231's registers doesn't always succeed.
* Therefore every time we set a register we always read it back to make
* sure it was set. If not we wait a little while and then try again. This
* is all taken care of in the routines cs4231_put8() and cs4231_reg_select()
* and the macros OR_SET_BYTE() and AND_SET_BYTE(). We don't worry about
* the status register because it is cleared by writing anything to it.
* So it doesn't matter what the value written is.
*
* This driver uses the mixer Audio Personality Module to implement audio(7I)
* and mixer(7I) semantics. Unfortunately this is a single stream Codec,
* forcing the mixer to do sample rate conversion.
*
* This driver supports suspending and resuming. A suspend just stops playing
* and recording. The play DMA buffers end up getting thrown away, but when
* you shut down the machine there is a break in the audio anyway, so they
* won't be missed and it isn't worth the effort to save them. When we resume
* we always start playing and recording. If they aren't needed they get
* shut off by the mixer.
*
* System power management is supported by this driver. To facilitate
* this feature the routines audiocs_set_busy() and audiocs_set_idle()
* are provided.
* audiocs_set_busy() is called at the beginning of all audiocs_ad_*()
* entry point routines. It blocks if the driver is being suspended.
* Once it unblocks it increments a busy count and raises power.
* Once this busy count is incremented any calls to suspend the driver
* will block until the count goes back to zero.
*
* audiocs_set_idle() is called at the end of all audiocs_ad_*() entry
* points. It decrements the busy count. Once that count reaches zero
* it wakes up a sleeping suspend.
*
* Component power management is also supported by this driver. As long as
* the busy count raised by audiocs_set_busy() is non-zero or audio is
* actively playing or recording power can not be lowered.
*
* The ad_start_play()/record() routines call pm_busy_component() so that
* The ad_stop_play()/record() routines call pm_idle_component() so that when
* the busy count goes to 0 the device will be powered down.
*
* being loaded first.
*/
#include <sys/audiovar.h>
/*
* Global routines.
*/
int cs4231_poll_ready(CS_state_t *);
/*
* Module linkage routines for the kernel
*/
static int cs4231_power(dev_info_t *, int, int);
/*
* Entry point routine prototypes
*/
static int cs4231_ad_set_config(audiohdl_t, int, int, int, int, int);
static int cs4231_ad_set_format(audiohdl_t, int, int, int, int, int, int);
static int cs4231_ad_start_play(audiohdl_t, int);
static void cs4231_ad_pause_play(audiohdl_t, int);
static void cs4231_ad_stop_play(audiohdl_t, int);
static int cs4231_ad_start_record(audiohdl_t, int);
static void cs4231_ad_stop_record(audiohdl_t, int);
/* Local Routines */
static int cs4231_chip_init(CS_state_t *);
static int cs4231_set_port(CS_state_t *, int, int);
static int cs4231_set_gain(CS_state_t *, int, int, int, int);
static int cs4231_set_monitor_gain(CS_state_t *, int);
static int cs4231_set_busy(CS_state_t *);
static void cs4231_set_idle(CS_state_t *);
static void cs4231_power_up(CS_state_t *);
static void cs4231_power_down(CS_state_t *);
/*
* Global variables, but viewable only by this file.
*/
/* anchor for soft state structures */
static void *cs_statep;
/* driver name, so we don't have to call ddi_driver_name() or hard code strs */
extern char *audiocs_name = CS4231_NAME;
/* File name for the cs4231_put8() and cs4231_reg_select() routines */
static uint_t cs_mixer_srs[] = {
};
static uint_t cs_compat_srs[] = {
};
static am_ad_sample_rates_t cs_mixer_sample_rates = {
};
static am_ad_sample_rates_t cs_compat_sample_rates = {
};
static uint_t cs_channels[] = {
};
static am_ad_cap_comb_t cs_combinations[] = {
{ 0 }
};
static am_ad_entry_t cs_entry = {
NULL, /* ad_setup() */
NULL, /* ad_teardown() */
cs4231_ad_set_config, /* ad_set_config() */
cs4231_ad_set_format, /* ad_set_format() */
cs4231_ad_start_play, /* ad_start_play() */
cs4231_ad_pause_play, /* ad_pause_play() */
cs4231_ad_stop_play, /* ad_stop_play() */
cs4231_ad_start_record, /* ad_start_record() */
cs4231_ad_stop_record, /* ad_stop_record() */
NULL, /* ad_ioctl() */
NULL /* ad_iocdata() */
};
/* play gain array, converts linear gain to 64 steps of log10 gain */
static uint8_t cs4231_atten[] = {
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, /* [000] -> [004] */
0x3a, 0x39, 0x38, 0x37, 0x36, /* [005] -> [009] */
0x35, 0x34, 0x33, 0x32, 0x31, /* [010] -> [014] */
0x30, 0x2f, 0x2e, 0x2d, 0x2c, /* [015] -> [019] */
0x2b, 0x2a, 0x29, 0x29, 0x28, /* [020] -> [024] */
0x28, 0x27, 0x27, 0x26, 0x26, /* [025] -> [029] */
0x25, 0x25, 0x24, 0x24, 0x23, /* [030] -> [034] */
0x23, 0x22, 0x22, 0x21, 0x21, /* [035] -> [039] */
0x20, 0x20, 0x1f, 0x1f, 0x1f, /* [040] -> [044] */
0x1e, 0x1e, 0x1e, 0x1d, 0x1d, /* [045] -> [049] */
0x1d, 0x1c, 0x1c, 0x1c, 0x1b, /* [050] -> [054] */
0x1b, 0x1b, 0x1a, 0x1a, 0x1a, /* [055] -> [059] */
0x1a, 0x19, 0x19, 0x19, 0x19, /* [060] -> [064] */
0x18, 0x18, 0x18, 0x18, 0x17, /* [065] -> [069] */
0x17, 0x17, 0x17, 0x16, 0x16, /* [070] -> [074] */
0x16, 0x16, 0x16, 0x15, 0x15, /* [075] -> [079] */
0x15, 0x15, 0x15, 0x14, 0x14, /* [080] -> [084] */
0x14, 0x14, 0x14, 0x13, 0x13, /* [085] -> [089] */
0x13, 0x13, 0x13, 0x12, 0x12, /* [090] -> [094] */
0x12, 0x12, 0x12, 0x12, 0x11, /* [095] -> [099] */
0x11, 0x11, 0x11, 0x11, 0x11, /* [100] -> [104] */
0x10, 0x10, 0x10, 0x10, 0x10, /* [105] -> [109] */
0x10, 0x0f, 0x0f, 0x0f, 0x0f, /* [110] -> [114] */
0x0f, 0x0f, 0x0e, 0x0e, 0x0e, /* [114] -> [119] */
0x0e, 0x0e, 0x0e, 0x0e, 0x0d, /* [120] -> [124] */
0x0d, 0x0d, 0x0d, 0x0d, 0x0d, /* [125] -> [129] */
0x0d, 0x0c, 0x0c, 0x0c, 0x0c, /* [130] -> [134] */
0x0c, 0x0c, 0x0c, 0x0b, 0x0b, /* [135] -> [139] */
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, /* [140] -> [144] */
0x0b, 0x0a, 0x0a, 0x0a, 0x0a, /* [145] -> [149] */
0x0a, 0x0a, 0x0a, 0x0a, 0x09, /* [150] -> [154] */
0x09, 0x09, 0x09, 0x09, 0x09, /* [155] -> [159] */
0x09, 0x09, 0x08, 0x08, 0x08, /* [160] -> [164] */
0x08, 0x08, 0x08, 0x08, 0x08, /* [165] -> [169] */
0x08, 0x07, 0x07, 0x07, 0x07, /* [170] -> [174] */
0x07, 0x07, 0x07, 0x07, 0x07, /* [175] -> [179] */
0x06, 0x06, 0x06, 0x06, 0x06, /* [180] -> [184] */
0x06, 0x06, 0x06, 0x06, 0x05, /* [185] -> [189] */
0x05, 0x05, 0x05, 0x05, 0x05, /* [190] -> [194] */
0x05, 0x05, 0x05, 0x05, 0x04, /* [195] -> [199] */
0x04, 0x04, 0x04, 0x04, 0x04, /* [200] -> [204] */
0x04, 0x04, 0x04, 0x04, 0x03, /* [205] -> [209] */
0x03, 0x03, 0x03, 0x03, 0x03, /* [210] -> [214] */
0x03, 0x03, 0x03, 0x03, 0x03, /* [215] -> [219] */
0x02, 0x02, 0x02, 0x02, 0x02, /* [220] -> [224] */
0x02, 0x02, 0x02, 0x02, 0x02, /* [225] -> [229] */
0x02, 0x01, 0x01, 0x01, 0x01, /* [230] -> [234] */
0x01, 0x01, 0x01, 0x01, 0x01, /* [235] -> [239] */
0x01, 0x01, 0x01, 0x00, 0x00, /* [240] -> [244] */
0x00, 0x00, 0x00, 0x00, 0x00, /* [245] -> [249] */
0x00, 0x00, 0x00, 0x00, 0x00, /* [250] -> [254] */
0x00 /* [255] */
};
/*
* STREAMS Structures
*/
/* STREAMS driver id and limit value structure */
static struct module_info cs4231_modinfo = {
CS4231_IDNUM, /* module ID number */
CS4231_NAME, /* module name */
CS4231_MINPACKET, /* minimum packet size */
CS4231_MAXPACKET, /* maximum packet size */
CS4231_HIWATER, /* high water mark */
CS4231_LOWATER /* low water mark */
};
/* STREAMS queue processing procedures structures */
/* read queue */
static struct qinit cs4231_rqueue = {
audio_sup_rput, /* put procedure */
audio_sup_rsvc, /* service procedure */
audio_sup_open, /* open procedure */
audio_sup_close, /* close procedure */
NULL, /* unused */
&cs4231_modinfo, /* module parameters */
NULL /* module statistics */
};
/* write queue */
static struct qinit cs4231_wqueue = {
audio_sup_wput, /* put procedure */
audio_sup_wsvc, /* service procedure */
NULL, /* open procedure */
NULL, /* close procedure */
NULL, /* unused */
&cs4231_modinfo, /* module parameters */
NULL /* module statistics */
};
/* STREAMS entity declaration structure */
static struct streamtab cs4231_str_info = {
&cs4231_rqueue, /* read queue */
&cs4231_wqueue, /* write queue */
NULL, /* mux lower read queue */
NULL, /* mux lower write queue */
};
/*
* DDI Structures
*/
/* Entry points structure */
static struct cb_ops cs4231_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 */
&cs4231_str_info, /* cb_str */
CB_REV, /* cb_rev */
nodev, /* cb_aread */
nodev, /* cb_arwite */
};
/* Device operations structure */
static struct dev_ops cs4231_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
cs4231_getinfo, /* devo_getinfo */
nulldev, /* devo_identify - obsolete */
nulldev, /* devo_probe - not needed */
cs4231_attach, /* devo_attach */
cs4231_detach, /* devo_detach */
nodev, /* devo_reset */
&cs4231_cb_ops, /* devi_cb_ops */
NULL, /* devo_bus_ops */
cs4231_power /* devo_power */
};
/* Linkage structure for loadable drivers */
static struct modldrv cs4231_modldrv = {
&mod_driverops, /* drv_modops */
&cs4231_dev_ops /* drv_dev_ops */
};
/* Module linkage structure */
static struct modlinkage cs4231_modlinkage = {
MODREV_1, /* ml_rev */
(void *)&cs4231_modldrv, /* ml_linkage */
NULL /* NULL terminates the list */
};
/* ******* Loadable Module Configuration Entry Points ********************* */
/*
* _init()
*
* Description:
* Driver initialization, called when driver is first loaded.
* This is how access is initially given to all the static structures.
*
* Arguments:
* None
*
* Returns:
* ddi_soft_state_init() status, see ddi_soft_state_init(9f), or
* mod_install() status, see mod_install(9f)
*/
int
_init(void)
{
int error;
ATRACE("in audiocs _init()", 0);
/* initialize the soft state */
0) {
return (error);
}
}
return (error);
}
/*
* _fini()
*
* Description:
* Module de-initialization, called when the driver is to be unloaded.
*
* Arguments:
* None
*
* Returns:
* mod_remove() status, see mod_remove(9f)
*/
int
_fini(void)
{
int error;
return (error);
}
/* free the soft state internal structures */
return (0);
}
/*
* _info()
*
* Description:
* Module information, returns infomation about the driver.
*
* Arguments:
* modinfo *modinfop Pointer to the opaque modinfo structure
*
* Returns:
* mod_info() status, see mod_info(9f)
*/
int
{
int error;
ATRACE("in audiocs _info()", 0);
return (error);
}
/* ******* Driver Entry Points ******************************************** */
/*
* cs4231_getinfo()
*/
/*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);
}
/*
* cs4231_attach()
*
* Description:
* Attach an instance of the CS4231 driver. This routine does the device
* dependent attach tasks. When it is complete it calls
* audio_sup_register() and am_attach() so they may do their work.
*
* NOTE: mutex_init() no longer needs a name string, so set
* to NULL to save kernel space.
*
* Arguments:
* dev_info_t *dip Pointer to the device's dev_info struct
* ddi_attach_cmd_t cmd Attach command
*
* Returns:
* DDI_SUCCESS The driver was initialized properly
* DDI_FAILURE The driver couldn't be initialized properly
*/
static int
{
int instance;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
/* we've already allocated the state structure so get ptr */
"!%s%d: attach() RESUME get soft state failed",
return (DDI_FAILURE);
}
/* power up the Codec */
== DDI_FAILURE) {
/* match the busy call above */
"!attach() DDI_RESUME failed");
return (DDI_FAILURE);
}
/* now restart playing and recording */
AUDIO_BOTH) == AUDIO_FAILURE) {
"!attach() audio restart failed");
}
/* we're no longer busy */
return (DDI_SUCCESS);
default:
"!%s%d: attach() unknown command 0x%x", audiocs_name,
return (DDI_FAILURE);
}
/* allocate the state structure */
"!%s%d: attach() soft state allocate failed", audiocs_name,
instance);
return (DDI_FAILURE);
}
/*
* WARNING: From here on all errors require that we free memory,
* including the state structure.
*/
/* get the state structure */
"!%s%d: attach() get soft state failed", audiocs_name,
instance);
goto error_mem;
}
/* call audiosup module registration routine */
"!%s%d: cs4231_attach() audio_sup_register() failed",
goto error_mem;
}
/* initialize the audio state structures */
"!attach() init_state() failed");
goto error_audiosup;
}
/* initialize the audio chip */
goto error_destroy;
}
/* save private state */
/* call the mixer attach() routine */
goto error_destroy;
}
/* set up kernel statistics */
}
/* we're ready, set up the interrupt handler */
goto error_kstat;
}
/* everything worked out, so report the device */
return (DDI_SUCCESS);
}
(void) audio_sup_unregister(ahandle);
return (DDI_FAILURE);
} /* cs4231_attach() */
/*
* cs4231_detach()
*
* Description:
* Detach an instance of the CS4231 driver. After the Codec is detached
* we call am_detach() and audio_sup_unregister() so they may do their
* work.
*
* Power management is pretty simple. If active we fail, otherwise
* we save the Codec state.
*
* Arguments:
* dev_info_t *dip Pointer to the device's dev_info struct
* ddi_detach_cmd_t cmd Detach command
*
* Returns:
* DDI_SUCCESS The driver was detached
* DDI_FAILURE The driver couldn't be detached
*/
static int
{
int instance;
/* get the state structure */
"!%s%d: detach() get soft state failed", audiocs_name,
instance);
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
/* wait for current operations to complete */
while (state->cs_busy_cnt != 0) {
}
/* stop playing and recording */
/* now we can power down the Codec */
AUDIO_BOTH) == AUDIO_FAILURE) {
"!detach() audio save failed");
}
}
return (DDI_SUCCESS);
default:
"!detach() unknown command 0x%x", cmd);
return (DDI_FAILURE);
}
/*
* Make sure the Codec and DMA engine are off.
*/
/* make sure the DMA engine isn't going to do anything */
/*
* power down the device, no reason to waste power without
* a driver
*/
}
/*
* unregister the interrupt. That way we can't get called by and
* interrupt after the audio framework is removed.
*/
/*
* Call the mixer detach routine to tear down the mixer before
* we lose the hardware.
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/* unmap the registers */
/* free the kernel statistics structure */
}
/* destroy the state mutex */
/* free the memory for the state pointer */
return (DDI_SUCCESS);
} /* cs4231_detach() */
/*
* cs4231_power()
*
* Description:
* This routine is used to turn the power to the Codec on and off.
* power to the Codec. Therefore we call the DMA engine specific code
* to do the work, if we need to make a change.
*
* If the level is CS4231_PWR_OFF then we call cs4231_power_down(). If the
* level is CS4231_PWR_ON then we call cs4231_power_up().
*
* This routine doesn't stop or restart play and record. Other routines
* are responsible for that.
*
* Arguments:
* def_info_t *dip Ptr to the device's dev_info structure
* int level The power level for the component
*
* Returns:
* DDI_SUCCESS Power level changed, we always succeed
*/
static int
{
int instance;
int rc = DDI_FAILURE;
/* get the state structure */
"!%s%d: power() get soft state failed", audiocs_name,
instance);
return (DDI_FAILURE);
}
/* make sure we have some work to do */
/* check the level change to see what we need to do */
/* don't power off if we're busy */
if (state->cs_busy_cnt) {
/* device is busy, so don't power off */
/* reset the timer */
ATRACE("cs_power() power off failed, busy",
state->cs_busy_cnt);
goto done;
}
/* power down and save the state */
} else if (level == CS4231_PWR_ON &&
/* power up */
#ifdef DEBUG
} else {
#endif
}
rc = DDI_SUCCESS;
done:
return (rc);
} /* cs4231_power() */
/* ******* Audio Driver Entry Point Routines ******************************* */
/*
* cs4231_ad_pause_play()
*
* Description:
* This routine pauses the play DMA engine.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
*
* NOTE: This routine must be called with the state unlocked.
*
* Returns:
* void
*/
/*ARGSUSED*/
static void
{
/* get the state structure */
/* power up and mark as busy */
"!pause_play() set_busy() failed");
return;
}
/* we need to protect the state structure */
/* need an idle for the busy above */
} /* cs4231_ad_pause_play() */
/*
* cs4231_ad_set_config()
*
* Description:
* This routine is used to set new Codec parameters, except the data
* format which has it's own routine. If the Codec doesn't support a
* particular parameter and it is asked to set it then we return
* AUDIO_FAILURE.
*
* The stream argument is ignored because this isn't a multi-stream Codec.
*
* NOTE: This routine must be called with the state unlocked.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
* int command The configuration to set
* int dir AUDIO_PLAY or AUDIO_RECORD, if
* direction is important
* int arg1 Argument #1
* int arg2 Argument #2, not always needed
*
* Returns:
* AUDIO_SUCCESS The Codec parameter has been set
* AUDIO_FAILURE The Codec parameter has not been set, or the
* parameter couldn't be set
*/
/*ARGSUSED*/
static int
{
int rc = AUDIO_FAILURE;
/* get the state structure */
/* wait on suspend, power up and mark as busy */
"!set_config() set_busy() failed");
return (AUDIO_FAILURE);
}
/* CAUTION: From here on we must goto done to exit. */
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
*/
break;
case AM_SET_PORT:
/*
* enforces exclusiveness of ports, as well as which ports
* are modifyable. We just turn on the ports that match the
* bits.
*
* arg1 --> port bit pattern
* arg2 --> not used
*/
break;
case AM_SET_MONITOR_GAIN:
/*
* Set the loopback monitor gain.
*
* 0 <= gain <= AUDIO_MAX_GAIN
*
* dir ---> N/A
* arg1 --> gain
* arg2 --> not used
*/
break;
case AM_OUTPUT_MUTE:
/*
* Mute or enable the output.
*
* dir ---> N/A
* arg1 --> ~0 = mute, 0 = unmute
* arg2 --> not used
*/
if (arg1) {
} else { /* Unmute */
}
rc = AUDIO_SUCCESS;
goto done;
case AM_MIC_BOOST:
/*
* Enable or disable the mic's 20 dB boost preamplifier.
*
* dir ---> N/A
* arg1 --> ~0 == enable, 0 == disabled
* arg2 --> not used
*/
if (arg1) {
} else {
}
rc = AUDIO_SUCCESS;
goto done;
default:
/*
* We let default catch commands we don't support, as well
* as bad commands.
*/
goto done;
}
done:
/* need an idle for the busy above */
return (rc);
} /* cs4231_ad_set_config() */
/*
* cs4231_ad_set_format()
*
* Description:
* This routine is used to set a new Codec data format.
*
* The stream argument is ignored because this isn't a multi-stream Codec.
*
* NOTE: This routine must be called with the state unlocked.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
* int dir AUDIO_PLAY or AUDIO_RECORD, if
* direction is important
* int sample_rate Data sample rate
* int channels Number of channels, 1 or 2
* int precision Bits per sample, 8 or 16
* int encoding Encoding method, u-law, A-law and linear
*
* Returns:
* AUDIO_SUCCESS The Codec data format has been set
* AUDIO_FAILURE The Codec data format has not been set, or the
* data format couldn't be set
*/
/*ARGSUSED*/
static int
{
int rc = AUDIO_FAILURE;
/* get the state structure */
/* wait on suspend, power up and mark as busy */
"!set_format() set_busy() failed");
return (AUDIO_FAILURE);
}
/*
* CAUTION: From here on we must goto done to exit.
*/
switch (sample_rate) {
default:
ATRACE_32("cs_ad_set_format() bad sample rate",
goto done;
}
} else {
value = 0;
}
/* if not mono then must be stereo, i.e., the default */
if (channels == AUDIO_CHANNELS_STEREO) {
value |= PDF_STEREO;
} else if (channels != AUDIO_CHANNELS_MONO) {
goto done;
#ifdef DEBUG
} else {
#endif
}
if (precision == AUDIO_PRECISION_8) {
switch (encoding) {
case AUDIO_ENCODING_ULAW:
break;
case AUDIO_ENCODING_ALAW:
break;
case AUDIO_ENCODING_LINEAR:
value |= PDF_LINEAR8;
break;
default:
goto done;
}
} else { /* 16 bit, default, and there is only one choice */
if (encoding != AUDIO_ENCODING_LINEAR) {
goto done;
}
value |= PDF_LINEAR16BE;
}
} else { /* capture side */
}
(void) cs4231_poll_ready(state);
/* clear the mode change bit */
thisfile);
rc = AUDIO_SUCCESS;
done:
/* we're no longer busy */
return (rc);
} /* cs4231_ad_set_format() */
/*
* cs4231_ad_start_play()
*
* Description:
* This routine starts the play DMA engine. It checks to make sure the
* DMA engine is off before it does anything, otherwise it may mess
* things up.
*
* The stream argument is ignored because this isn't a multi-stream Codec.
*
* NOTE: This routine must be called with the state unlocked.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
*
* Returns:
* AUDIO_FAILURE Audio not restarted, no audio to play
*/
/*ARGSUSED*/
static int
{
int rc;
/* get the state structure */
/* power up and mark as busy */
"!start_play() set_busy() failed");
return (AUDIO_FAILURE);
}
/* we need to protect the state structure */
/* see if we are already playing */
thisfile);
rc = AUDIO_SUCCESS;
goto done;
}
ATRACE_32("cs_ad_start_play() DMA_RESTART_PLAY() returned", 0);
rc = AUDIO_SUCCESS;
} else {
/*
* The newer versions of the EB2 DMA engine reset on a non-even
* sample boundary. Then when it restarts it'll be in mid sample
* which results in loud static. When we start again we reload
* the format register, which resets the Codec, starting on an
* even boundary, and thus no static. We end up doing this for
* the APC DMA engine as well, but it's harmless.
*
* CAUTION: Don't do this for record. It causes SunVTS to
* fail. Also, do not reset the DMA engine if record is
* active. This occasionally upsets everything.
*/
ATRACE("cs_ad_start_play() play DMA engine reset",
state);
}
if (rc == AUDIO_SUCCESS) {
ATRACE("cs_ad_start_play() programming Codec to play",
state);
ATRACE_8("cs_ad_start_play() Codec INTC_REG",
#ifdef DEBUG
} else {
#endif
}
}
done:
/* need an idle for the busy above */
return (rc);
} /* cs4231_ad_start_play() */
/*
* cs4231_ad_stop_play()
*
* Description:
* This routine stops the play DMA engine.
*
* The stream argument is ignored because this isn't a multi-stream Codec.
*
* NOTE: This routine must be called with the state unlocked.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
*
* Returns:
* void
*/
/*ARGSUSED*/
static void
{
/* get the state structure */
/* power up and mark as busy */
"!stop_play() set_busy() failed");
return;
}
/* we need to protect the state structure */
ATRACE_8("cs_ad_stop_play() Codec INTC_REG",
/* stop the play DMA engine */
/* DMA_STOP() returns with the PEN cleared */
}
/* need an idle for the busy above */
} /* cs4231_ad_stop_play() */
/*
* cs4231_ad_start_record()
*
* Description:
* This routine starts the record DMA engine. It checks to make sure the
* DMA engine is off before it does anything, otherwise it may mess
* things up.
*
* The stream argument is ignored because this isn't a multi-stream Codec.
*
* NOTE: This routine must be called with the state unlocked.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
*
* Returns:
* AUDIO_SUCCESS Recording successfully started
* AUDIO_FAILURE Recording not successfully started
*/
/*ARGSUSED*/
static int
{
int rc = AUDIO_FAILURE;
/* get the state structure */
/* power up and mark as busy */
"!start_record() set_busy() failed");
return (AUDIO_FAILURE);
}
/* we need to protect the state structure */
/* see if we are already recording */
thisfile);
rc = AUDIO_SUCCESS;
goto done;
}
/*
* Enable record DMA on the Codec, do NOT reprogram the Codec as
* done for play. This will cause SunVTS to fail.
*/
if (rc == AUDIO_SUCCESS) {
ATRACE_8("cs_ad_start_record() Codec INTC_REG",
#ifdef DEBUG
} else {
#endif
}
done:
/* need an idle for the busy above */
return (rc);
} /* cs4231_ad_start_record() */
/*
* cs4231_ad_stop_record()
*
* Description:
* This routine stops the record DMA engine.
*
* The stream argument is ignored because this isn't a multi-stream Codec.
*
* NOTE: This routine must be called with the state unlocked.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
*
* Returns:
* void
*/
/*ARGSUSED*/
static void
{
/* get the state structure */
/* power up and mark as busy */
"!stop_record() set_busy() failed");
return;
}
/* we need to protect the state structure */
/* stop the record DMA engine and clear the active flag */
ATRACE_8("cs_ad_stop_record() Codec INTC_REG",
/* need an idle for the busy above */
} /* cs4231_ad_stop_record() */
/* ******* Local Routines *************************************************** */
/*
* cs4231_chip_init()
*
* Description:
* Power up the audio core, initialize the audio Codec, prepare the chip
* for use.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* AUDIO_SUCCESS Chip initialized and ready to use
* AUDIO_FAILURE Chip not initialized and not ready
*/
static int
{
/* make sure we are powered up */
/* no autocalibrate */
/* initialize the Codec */
/* activate registers 16 -> 31 */
/* now figure out what version we have */
} else {
}
/* get rid of annoying popping by muting the output channels */
thisfile);
thisfile);
/* initialize aux input channels to known gain values & muted */
thisfile);
thisfile);
thisfile);
thisfile);
/* initialize aux input channels to known gain values & muted */
/* program the sample rate, play and capture must be the same */
/*
* Set up the Codec for playback and capture disabled, dual DMA, and
* playback and capture DMA. Also, set autocal if we are supposed to.
*/
} else {
}
/* turn off the MCE bit */
/* wait for the Codec before we continue XXX - do we need this? */
return (AUDIO_FAILURE);
}
/*
* Turn on the output level bit to be 2.8 Vpp. Also, don't go to 0 on
* underflow.
*/
/* turn on the high pass filter if Rev A */
} else {
}
/* clear the play and capture interrupt flags */
/* the play and record gains will be set by the audio mixer */
/* unmute the output */
/* unmute the mono speaker and mute mono in */
/* clear the mode change bit */
/* wait for the Codec before we continue XXX - do we need this? */
return (AUDIO_FAILURE);
}
return (AUDIO_SUCCESS);
} /* cs4231_chip_init() */
/*
* audiocs_init_state()
*
* Description:
* This routine initializes the audio driver's state structure and
* maps in the registers. This also includes reading the properties.
*
* CAUTION: This routine maps the registers and initializes a mutex.
* Failure cleanup is handled by cs4231_attach(). It is not
* handled locally by this routine.
*
* Arguments:
* CS_state_t *state The device's state structure
* dev_info_t *dip Pointer to the device's dev_info struct
*
* Returns:
* AUDIO_SUCCESS State structure initialized
* AUDIO_FAILURE State structure not initialized
*/
static int
{
char *prop_str;
char *pm_comp[] = {
"NAME=audiocs audio device",
"0=off",
"1=on" };
int cs4231_pints;
int cs4231_rints;
int instance;
/*
* get the play and record interrupts per second,
* look for either cs4231_XXXX or XXXX-interrupts.
*/
}
}
if (cs4231_pints < CS4231_MIN_INTS) {
"attach() play interrupt rate set too low: %d, resetting"
} else if (cs4231_pints > CS4231_MAX_INTS) {
"attach() play interrupt rate set too high: %d, resetting"
}
if (cs4231_rints < CS4231_MIN_INTS) {
"attach() record interrupt rate set too low: %d, resetting"
} else if (cs4231_rints > CS4231_MAX_INTS) {
"attach() record interrupt rate set too high: %d, resetting"
}
/*
* Figure out the largest transfer size for the DMA engine. Then
* map in the CS4231 and the DMA registers and reset the DMA engine.
*/
/* get the mode from the .conf file */
"mixer-mode", AM_MIXER_MODE)) {
} else {
}
}
/* set up the pm-components */
"!init_state() couldn't create component");
return (AUDIO_FAILURE);
}
/* save the device info pointer */
/* get the iblock cookie needed for interrupt context */
DDI_SUCCESS) {
"!init_state() cannot get iblock cookie");
return (AUDIO_FAILURE);
}
/* now fill it in, initialize the state mutexs first */
/* fill in the device default state */
/* fill in the ad_info structure */
/* play capabilities */
/* record capabilities */
/* figure out which DMA engine hardware we have */
} else {
}
} else {
}
/* cs_regs, cs_eb2_regs and cs_handles filled in later */
/* always set to onboard1, not really correct, but very high runner */
/* version filled in below */
/* most of what's left is filled in when the registers are mapped */
/* Map in the registers */
goto error_regs;
}
state->cs_busy_cnt = 0;
return (AUDIO_SUCCESS);
return (AUDIO_FAILURE);
} /* cs4231_init_state */
/*
* cs4231_get_ports()
*
* Description:
* Get which audiocs h/w version we have and use this to
* determine the input and output ports as well whether or not
* the hardware has internal loopbacks or not. We also have three
* different ways for the properties to be specified, which we
* also need to worry about.
*
* Vers Platform(s) DMA eng. audio-module** loopback
* a SS-4+/SS-5+ apcdma no no
* b Ultra-1&2 apcdma no yes
* c positron apcdma no yes
* d PPC - retired
* e x86 - retired
* f tazmo eb2dma Perigee no
* g tazmo eb2dma Quark yes
* h darwin+ eb2dma no N/A
*
* Vers model~ aux1* aux2*
* a N/A N/A N/A
* b N/A N/A N/A
* c N/A N/A N/A
* d retired
* e retired
* f SUNW,CS4231f N/A N/A
* g SUNW,CS4231g N/A N/A
* h SUNW,CS4231h cdrom none
*
* * = Replaces internal-loopback for latest property type, can be
* set to "cdrom", "loopback", or "none".
*
* ** = For plugin audio modules only. Starting with darwin, this
* property is replaces by the model property.
*
* ~ = Replaces audio-module.
*
* + = Has the capability of having a cable run from the internal
* CD-ROM to the audio device.
*
* N/A = Not applicable, the property wasn't created for early
* platforms, or the property has been retired.
*
* NOTE: Older tazmo and quark machines don't have the model property.
*
* Arguments:
* CS_state_t *state The device's state structure
* dev_info_t *dip Pointer to the device's dev_info struct
*
* Returns:
* void
*/
static void
{
char *prop_str;
/* First we set the common ports, etc. */
/* now we try the new "model" property */
/* darwin */
/* quark audio module */
/* tazmo */
} else {
"!attach() unknown audio model: %s, some parts of "
"audio may not work correctly",
}
} else { /* now try the older "audio-module" property */
switch (*prop_str) {
case 'Q': /* quark audio module */
break;
case 'P': /* tazmo */
break;
default:
"?");
"!attach() unknown audio module: %s, some "
"parts of audio may not work correctly",
break;
}
} else { /* now try heuristics, ;-( */
(void) strcpy(
} else {
(void) strcpy(
}
} else {
}
}
}
} /* cs4231_get_ports() */
/*
* cs4231_power_up()
*
* Description:
* Power up the Codec and restore the codec's registers.
*
* NOTE: Like the audiots driver, we don't worry about locking since
* the only routines that may call us are attach() and power()
* Both of which should be the only threads in the driver.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
int i;
/* turn on the Codec */
/* reset the DMA engine(s) */
/*
* Reload the Codec's registers, the DMA engines will be
* taken care of when play and record start up again. But
* first enable registers 16 -> 31.
*/
for (i = 0; i < CS4231_REGS; i++) {
/* restore Codec registers */
}
/* clear MCE bit */
thisfile);
} /* cs4231_power_up() */
/*
* cs4231_power_down()
*
* Description:
* Power down the Codec and save the codec's registers.
*
* NOTE: See the note in cs4231_power_up() about locking.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
int i;
/*
* We are powering down, so we don't need to do a thing with
* the DMA engines. However, we do need to save the Codec
* registers.
*/
for (i = 0; i < CS4231_REGS; i++) {
/* save Codec regs */
}
/* turn off the Codec */
} /* cs4231_power_down() */
/*
* cs4231_set_gain()
*
* Description:
* Set the play or record gain.
*
* Arguments:
* CS_state_t *state The device's state structure
* int stream Stream number for multi-stream Codecs,
* which this isn't, so just ignore
* int dir AUDIO_PLAY or AUDIO_RECORD, if
* direction is important
* int gain The gain to set
* int channels Number of channels, 1 or 2
*
* Returns:
* AUDIO_SUCCESS The Codec parameter has been set
* AUDIO_FAILURE The gain has not been set
*/
/*ARGSUSED*/
static int
int channel)
{
int rc = AUDIO_FAILURE;
}
if (channel == 0) { /* left channel */
} else { /* right channel */
}
/* NOTE: LDAC0_VALID_MASK == RDAC0_VALID_MASK, so either ok */
/* we use cs4231_atten[] to linearize attenuation */
/* mute the output */
/* NOTE: LDACO_LDM == RDACO_LDM, so either ok */
} else {
ATRACE("cs_set_gain() play gain set",
cs4231_atten[gain]);
}
rc = AUDIO_SUCCESS;
} else {
if (channel == 0) { /* left channel */
} else { /* right channel */
}
/* NOTE: LADCI_VALID_MASK == RADCI_VALID_MASK, so either ok */
/* we shift right by 4 to go from 8-bit to 4-bit gain */
ATRACE("cs_set_gain() record gain set",
rc = AUDIO_SUCCESS;
}
return (rc);
} /* cs4231_set_gain() */
/*
* cs4231_set_port()
*
* Description:
*
* Arguments:
* CS_state_t *state The device's state structure
* int dir AUDIO_PLAY or AUDIO_RECORD, if
* direction is important
* int port The port to set
*
* Returns:
* AUDIO_SUCCESS The Codec parameter has been set
* AUDIO_FAILURE The port could not been set
*/
static int
{
int rc = AUDIO_SUCCESS;
/* figure out which output port(s) to turn on */
tmp_value = 0;
if (port & AUDIO_SPEAKER) {
} else {
}
if (port & AUDIO_HEADPHONE) {
} else {
}
if (port & AUDIO_LINE_OUT) {
} else {
}
rc = AUDIO_FAILURE;
goto done;
}
} else {
/*
* Figure out which input port to set. Fortunately
* the left and right port bit patterns are the same.
*/
switch (port) {
case AUDIO_NONE:
tmp_value = 0;
break;
case AUDIO_MICROPHONE:
break;
case AUDIO_LINE_IN:
break;
case AUDIO_CD:
break;
case AUDIO_CODEC_LOOPB_IN:
break;
case AUDIO_SUNVTS:
break;
default:
/* unknown or inclusive input ports */
rc = AUDIO_FAILURE;
goto done;
}
}
done:
return (rc);
} /* cs4231_set_port() */
/*
* cs4231_set_monitor_gain()
*
* Description:
* Set the monitor gain.
*
* Arguments:
* CS_state_t *state The device's state structure
* int gain The gain to set
*
* Returns:
* AUDIO_SUCCESS The Codec parameter has been set
* AUDIO_FAILURE The gain has not been set
*/
static int
{
int rc = AUDIO_SUCCESS;
}
if (gain == 0) {
/* disable loopbacks when gain == 0 */
} else {
/* we use cs4231_atten[] to linearize attenuation */
}
return (rc);
} /* cs4231_set_monitor_gain() */
/*
* cs4231_set_busy()
*
* Description:
* This routine is called whenever a routine needs to guarantee
* that it will not be suspended or the power removed by the power
* manager. It will also block any routine while a suspend is
* going on.
*
* CAUTION: This routine cannot be called by routines that will
* block. Otherwise DDI_SUSPEND will be blocked for a
* long time. And that is the wrong thing to do.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* AUDIO_SUCCESS Set busy and powered up
* AUDIO_FAILURE Couldn't power up, so not busy
*/
static int
{
/* get the lock so we are safe */
/* block if we are going to be suspended */
}
/*
* Okay, we aren't going to be suspended yet, so mark as busy.
* This will keep us from being suspended when we release the lock.
*/
state->cs_busy_cnt++;
/* now can release the lock before we raise the power */
/*
* Mark as busy before we ask for power to be raised. This removes
* the race condtion between the call to cs4231_power() and our call
* to raise power. After we raise power we immediately mark as idle
* so the count is still good.
*/
DDI_FAILURE) {
/* match the busy call above */
"!set_busy() power up failed",
if (state->cs_busy_cnt == 0) {
/* let DDI_SUSPEND continue */
}
return (AUDIO_FAILURE);
}
/* power is up and we are marked as busy, so we are done */
return (AUDIO_SUCCESS);
} /* cs4231_set_busy() */
/*
* cs4231_set_idle()
*
* Description:
* This routine reduces the busy count. It then does a cv_broadcast()
* if the count is 0 so a waiting DDI_SUSPEND will continue forward.
* It ends by resetting the power management timer.
*
* We don't do anything with power because the routine that is no longer
* busy either doesn't need the hardware, or we are playing or recording
* so the power won't come down anyway.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
/* get the lock so we are safe */
/* decrement the busy count */
state->cs_busy_cnt--;
/* if no longer busy, then we wake up a waiting SUSPEND */
if (state->cs_busy_cnt == 0) {
}
/* we're done, so unlock */
/* reset the timer */
} /* cs4231_set_idle() */
/* ******* Global Local Routines ******************************************* */
/*
* cs4231_poll_ready()
*
* Description:
* This routine waits for the Codec to complete its initialization
* sequence and is done with its autocalibration.
*
* Early versions of the Codec have a bug that can take as long as
* 15 seconds to complete its initialization. For these cases we
* use a timeout mechanism so we don't keep the machine locked up.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* AUDIO_SUCCESS The Codec is ready to continue
* AUDIO_FAILURE The Codec isn't ready to continue
*/
int
{
int x = 0;
/* wait for the chip to initialize itself */
drv_usecwait(50);
}
if (x >= CS4231_TIMEOUT) {
return (AUDIO_FAILURE);
}
x = 0;
/*
* Now wait for the chip to complete its autocalibration.
* Set the test register.
*/
drv_usecwait(50);
}
if (x >= CS4231_TIMEOUT) {
return (AUDIO_FAILURE);
}
return (AUDIO_SUCCESS);
} /* cs4231_poll_ready() */
/*
* cs4231_reg_select()
*
* Description:
* Select a cs4231 register. The cs4231 has a hardware bug where a
* register is not always selected the first time. We try and try
* again until the proper register is selected or we time out and
* print an error message.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* ddi_acc_handle_t handle A handle to the device's registers
* uint8_t addr The register address to program
* int reg The register to select
* int line The line number where this function was
* called
* char * *thefile The name of the c file that called this
* function
*
* Returns:
* void
*/
void
{
int x;
uint8_t T;
for (x = 0; x < CS4231_RETRIES; x++) {
if (T == reg) {
break;
}
drv_usecwait(1000);
}
if (x == CS4231_RETRIES) {
"!Couldn't select register (%s, Line #%d 0x%02x 0x%02x)",
"!audio may not work correctly until it is stopped and "
"restarted\n");
}
} /* cs4231_reg_select() */
/*
* cs4231_put8()
*
* Description:
* Program a cs4231 register. The cs4231 has a hardware bug where a
* register is not programmed properly the first time. We program a value,
* then immediately read back the value and reprogram if nescessary.
* We do this until the register is properly programmed or we time out and
* print an error message.
*
* Arguments:
* audiohdl_t ahandle Handle to this device
* ddi_acc_handle_t handle A handle to the device's registers
* uint8_t addr The register address to program
* uint8_t mask Mask to not set reserved register bits
* int val The value to program
* int line The line number where this function was
* called
* char * *thefile The name of the c file that called this
* function
*
* Returns:
* void
*/
void
{
int x;
uint8_t T;
for (x = 0; x < CS4231_RETRIES; x++) {
if (T == val) {
break;
}
drv_usecwait(1000);
}
if (x == CS4231_RETRIES) {
"!Couldn't set value (%s, Line #%d 0x%02x 0x%02x)",
"!audio may not work correctly until it is stopped and "
"restarted\n");
}
} /* cs4231_put8() */