ac97.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 (C) 4Front Technologies 1996-2008.
*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/sysmacros.h>
#include "ac97_impl.h"
/*
* This is the initial value for many controls. This is
* a 75% level.
*/
#define INIT_VAL_MN 75
#define INIT_IGAIN_MN 50
/*
* In AC'97 v2.3, the registers are carved up as follows:
*
* Audio Base Registers: 0x00 - 0x26
* Audio Extended Registers: 0x28 - 0x3A
* Modem Extended Registers: 0x3C - 0x58
* Vendor Reserved Registers: 0x5A - 0x5F
* Page Registers: 0x60 - 0x6F
* Vendor Reserved Registers: 0x70 - 0x7A
* Vendor ID Registers: 0x7C - 0x7F
*
* We only need to shadow the normal audio registers by default.
* TBD: Handling of codec-specific registers in vendor reserved space.
* We cannot necessarily meaningfully shadow them.
*/
#define LAST_SHADOW_REG 0x3A
/*
* Record source selection.
*/
#define INPUT_MIC 0
#define INPUT_CD 1
#define INPUT_VIDEO 2
#define INPUT_AUXIN 3
#define INPUT_LINEIN 4
#define INPUT_STEREOMIX 5
#define INPUT_MONOMIX 6
#define INPUT_PHONE 7
static const char *ac97_insrcs[] = {
NULL,
};
/*
* Per audio device state structure
*/
struct ac97 {
audio_dev_t *d;
void *private; /* drivers devc */
void (*codec_init)(ac97_t *);
void (*codec_reset)(ac97_t *);
};
struct modlmisc ac97_modlmisc = {
"Audio Codec '97 Support"
};
struct modlinkage ac97_modlinkage = {
{ &ac97_modlmisc, NULL }
};
int
_init(void)
{
return (mod_install(&ac97_modlinkage));
}
int
_fini(void)
{
return (mod_install(&ac97_modlinkage));
}
int
{
}
#if 0
/*
* The following table, and the code to scale it, works in percentages.
* This may be convenient for humans, but it would be faster if the table
* entries were rescaled to 256. (Division by 100 is painful. Divison by
* 256 is trivial.)
*/
static const char ac97_val_cvt[101] = {
0, 0, 3, 7, 10, 13, 16, 19,
21, 23, 26, 28, 30, 32, 34, 35,
37, 39, 40, 42, 43, 45, 46, 47,
49, 50, 51, 52, 53, 55, 56, 57,
58, 59, 60, 61, 62, 63, 64, 65,
65, 66, 67, 68, 69, 70, 70, 71,
72, 73, 73, 74, 75, 75, 76, 77,
77, 78, 79, 79, 80, 81, 81, 82,
82, 83, 84, 84, 85, 85, 86, 86,
87, 87, 88, 88, 89, 89, 90, 90,
91, 91, 92, 92, 93, 93, 94, 94,
95, 95, 96, 96, 96, 97, 97, 98,
98, 98, 99, 99, 100
};
#endif
/*
* This code has three main functions. All related to converting
* a standard controls value to hardware specific values. All
* Standard passed in values are 0-100 as in percent.
*
* First it takes a value passed in as volume or gain and
* converts to attenuation or gain correspondingly. Since this is
* what the hardware needs.
*
* Second it adjusts the value passed in to compensate for the none
* linear nature of human hearing, sound loudness, sensitivity. It
* converts the linear value to a logarithmic value. This gives users
* the perception that the controls are linear.
*
* Third it converts the value to the number of bits that a hardware
* register needs to be.
*
* On input the following are supplied:
* left - The gain or volume in percent for left channel.
* right - The gain or volume in percent for right channel.
* bits - The number of bits the hardware needs. If this value
* is negetive then right and left are gain else they
* are volume.
*
* On return the following is returned:
*
* bit: 15 8 7 0
* ----------------------------------
* | left channel | right channel |
* ----------------------------------
* ( each channel is "bits" wide )
*/
{
if (bits < 0) { /* This is gain not ATTN */
}
#if 0
/*
* 4Front's code used a table to smooth the transitions
* somewhat. Without this change, the volume levels adjusted
* near the top of the table seem to have less effect. Its
* hard to notice a volume change from 100 to 95, without the
* val_cvt table, for example. However, the scaling has an
* ugly side effect, which is at the default volumes (75%), we
* wind up having the level set too high for some
*
* Legacy Sun code didn't have this table, and some
* qualitative testing shows that it isn't really necessary.
*/
#else
#endif
}
{
if (bits < 0) { /* This is gain not ATTN */
} else {
}
}
{
return (ac->d);
}
int
{
int rv;
return (rv);
}
/*
* This calls the Hardware drivers access write routine
* to write to a device register.
*/
/*
* Probe routines for optional controls
*
* These routines each probe one aspect of hardware
* for controls presents.
* If the control is present these routines should
* return none zero.
*/
/*
* Is the named register implemented? This routine saves and
* restores the original value, and relies on the fact that the
* registers (if implemented) will have at least one bit that acts
* as a mute (0x8000, 0x8080), so we can probe "silently".
*
* The probe logic is suggested by the AC'97 2.3 spec. (Unimplemented
* registers are required to return zero to facilitate this sort of
* detection.)
*/
static int
{
int rv = 0;
/* get the original value */
rv = 1;
}
/* restore the original value */
return (rv);
}
/*
*/
static int
{
return (1);
else
return (0);
}
/*
* If there is a loudness switch?
*/
static int
{
/* loudness contol present */
return (1);
else
return (0);
}
/*
* Does this device have a mono-mic input volume control?
*/
static int
{
/* mono mic present */
return (1);
else
return (0);
}
/*
* Does this device have a simulated stereo switch?
*/
static int
{
/* simulated stereocontol present */
return (1);
else
return (0);
}
/*
* Does this device have a PC beeper input volume control?
*/
static int
{
}
/*
* Does this device have AUX output port volume control?
*/
static int
{
return (1);
else
return (0);
}
/*
* Does this device have a mic?
*/
static int
{
return (1);
}
return (0);
}
/*
* If this device has an AUX output port is it used for headphones?
*/
static int
{
/* headphone control present */
return (1);
}
return (0);
}
/*
* Does this device have AUX output port volume control?
*/
static int
{
/* ALT PCM control present */
return (1);
}
return (0);
}
/*
* Does this device have an AUX input port volume control?
*/
static int
{
return (1);
}
return (0);
}
/*
* Does this device have a phone input port with a volume control?
*/
static int
{
return (1);
}
return (0);
}
/*
* Does this device have a mono output port with volume control?
*/
static int
{
return (0);
}
return (1);
}
return (0);
}
/*
* Does this device have a line input port with volume control?
*/
static int
{
return (1);
}
return (0);
}
/*
* Does this device have a cdrom input port with volume control?
*/
static int
{
return (1);
}
return (0);
}
/*
* Does this device have a video input port with volume control?
*/
static int
{
return (1);
}
return (0);
}
/*
* Does this device have a 3D sound enhancement?
*/
static int
{
/* 3D control present */
return (1);
else
return (0);
}
static int
{
int rv = 0;
return (0);
/* get the original value */
rv = 1;
}
/* restore the original value */
return (rv);
}
static int
{
}
static int
{
}
/*
* Does this device have a center output port with volume control?
*/
static int
{
/* center volume present */
return (1);
else
return (0);
}
/*
* Does this device have a LFE (Sub-woofer) output port with
* a volume control?
*/
static int
{
/* We have LFE control */
return (1);
else
return (0);
}
/*
* Are we a multichannel codec?
*/
static int
{
/* Are any of the Surround, Center, or LFE dacs present? */
return (1);
else
return (0);
}
static int
{
/* if not multichannel, then use "lineout" instead of "front" label */
return (!ac97_probe_front(ac));
}
static const char *ac97_mics[] = {
NULL,
};
static const char *ac97_monos[] = {
};
/*
* This calls the Hardware drivers access write routine
* to write to a device register.
*/
void
{
}
/*
* Don't touch hardware _unless_ if we are suspended, unless we
* are in the process of resuming.
*/
}
}
/*
* This obtains the shadowed value of a register. If the register is
* out of range, zero is returned.
*
* To read a hardware register, use the RD() macro above.
*/
{
}
}
return (0);
}
/*
* to set bits in a device register.
*/
void
{
}
/*
* to clear bits in a device register.
*/
void
{
}
/*
* Look for a control attached to this device based
* on its control number.
*
* If this control number is found the per controls state
* structure is returned.
*/
{
/* Validate that ctrlnum is real and usable */
return (ctrl);
}
}
return (NULL);
}
/*
* This will update all the codec registers from the shadow table.
*/
static void
{
/*
* If we are restoring previous settings, just reload from the
* shadowed settings.
*/
}
/*
* Then go and do the controls. This is important because some of
* the controls might use registers that aren't shadowed. Doing it
* a second time also may help guarantee that it all works.
*/
}
}
/*
* This will update all the hardware controls to the initial values at
* start of day.
*/
static void
{
}
}
/*
* Select the input source for recording. This is the set routine
* for the control AUDIO_CONTROL_INPUTS.
*/
static void
{
set_val--;
}
}
static void
{
uint16_t v;
if (onoff) {
v |= bit;
} else {
v &= ~bit;
}
}
static void
{
}
static void
{
}
static void
{
}
/*
* This will set simulated stereo control to on or off.
*/
static void
{
}
/*
* This will set mic select control to mic1=0 or mic2=1.
*/
static void
{
}
/*
* This will set mono source select control to mix=0 or mic=1.
*/
static void
{
}
static void
{
}
static void
{
v &= ~mask; /* clear all of our bits, preserve others */
/* now set the mute bit, and volume bits */
v |= mute;
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
/*
* This will set mono mic gain control.
*/
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
/*
* be scaled, because the technology in use may vary. We
* need more information about each of the options available
* to do the right thing.
*/
}
static void
{
/*
* be scaled, because the technology in use may vary. We
* need more information about each of the options available
* to do the right thing.
*/
}
static void
{
uint16_t v;
if (value) {
v |= MICVR_20dB_BOOST;
} else {
v &= ~MICVR_20dB_BOOST;
}
}
/*
* This will return the stored value for any control that has been set.
* Note this does not return the actual hardware value from a port. But
* instead returns the cached value from the last write to the hardware
* port.
*
* arg - This control structure for this control.
* value - This is a pointer to the location to put the
* controls value.
*
* On success zero is returned.
*/
static int
{
return (0);
}
static int
{
/* a bit of quick checking */
switch (ctrl->actrl_type) {
case AUDIO_CTRL_TYPE_STEREO:
(value > 0xffff)) {
return (EINVAL);
}
break;
case AUDIO_CTRL_TYPE_ENUM:
return (EINVAL);
}
break;
case AUDIO_CTRL_TYPE_MONO:
case AUDIO_CTRL_TYPE_BOOLEAN:
return (EINVAL);
}
break;
}
return (0);
}
/*
* This simply sets a flag to block calls to the underlying
* hardware driver to get or set hardware controls. This is usually
* called just before a power down of devices. Once this gets called any
* calls to set controls will not touch the real hardware. But
* since all control updates are always saved in soft registers it
* is a simple mater to update the hardware with the latest values
* on resume which also unblocks calls to the hardware controls.
*/
void
{
/* This will prevent any new operations from starting! */
/* XXX - should we powerdown codec's here?? */
}
/*
* Reset the analog codec hardware
*
* Reset all analog AC97 hardware, input ADC's, output DAC's and MIXER.
* Wait a resonable amount of time for hardware to become ready.
*/
static void
{
/* Clear stale data and resync register accesses */
/* reset the codec */
WR(AC97_RESET_REGISTER, 0);
/* power up */
while (wait--) {
/* 1 msec delay */
drv_usecwait(1000);
/* If all ready - end delay */
return;
}
}
}
/*
* This is the internal hardware reset routine.
* It has no locking and we must be locked before it is
* called!
*
* This will reset and re-initialize the device.
* It has two modes of operation that affect how it handles
* all controls.
*
* It re-initializes the device and reloads values with
* last updated versions.
*/
static void
{
/*
* Fully Power up the device
*/
/* power up - external amp powerd up */
} else {
/* power up - external amp powered down */
}
case AC97_CODEC_STAC9708:
#if 0
/* non-inverted phase */
/* ac97_rd(ac, AC97_VENDOR_REGISTER_11) & ~0x8); */
#endif
break;
case AC97_CODEC_EM28028:
~3800) | 0xE0);
break;
case AC97_CODEC_AD1886:
/* jack sense */
break;
case AC97_CODEC_AD1888:
#if 0
/* GED: This looks fishy to me, so I'm nuking it for now */
#endif
break;
case AC97_CODEC_AD1980:
#if 0
/* set jacksense to mute line if headphone is plugged */
#endif
break;
case AC97_CODEC_AD1985:
break;
case AC97_CODEC_WM9704:
/* enable I2S */
break;
case AC97_CODEC_VT1612A:
case AC97_CODEC_VT1617A:
case AC97_CODEC_VT1616:
/* Turn off Center, Surround, and LFE DACs */
break;
case AC97_CODEC_YMF753:
/* set TX8 + 3AWE */
break;
default:
break;
}
/* call codec specific reset hook */
}
/* Turn off variable sampling rate support */
}
/*
* This will reset and re-initialize the device.
* It has two modes of operation that affect how it handles
* all controls.
*
* It re-initializes the device and then can either reset
* all controls back to their initial values or it can
* re-load all controls with their last updated values.
*
* initval - If this is none zero then all controls will
* be restored to their initial values.
*/
void
{
/* If we are about to suspend so no point in going on */
return;
}
}
/*
* Given the need to resume the hardware this reloads the base hardware
* and then takes the stored values for each control and sends them
* to the hardware again.
*/
void
{
/*
* This should only be called when already suspended.
* this takes us out of suspend state after it brings the
* controls back to life.
*/
/* We simply call reset since the operation is the same */
}
/*
* Register a control -- if it fails, it will generate a message to
* syslog, but the driver muddles on. (Failure to register a control
* should never occur, and is generally benign if it happens.)
*/
void
{
for (int e = 0; e < 64; e++) {
break;
}
}
/*
* Warning for extended controls this field gets changed
* by audio_dev_add_control() to be a unique value.
*/
/* Register control with framework */
if (!ctrl->actrl_ctrl) {
return;
}
/*
* Not that it can not be referenced until in is in the
* list. So again by adding to the list last we avoid the need
* for control locks.
*/
}
/*
* De-Register and free up a control
*
* Note ctrl_lock read write must be held for writing when calling
* this function
*/
void
{
}
/*
* This is the master list of all controls known and handled by
* the AC97 framework. This is the list used to probe, allocate
* and configure controls. If a control is not in this list it
* will not be handled. If a control is in this list but does not
* have a probe routine then it will always be included. If a
* control in list has a probe routine then it must return true
* for that control to be included.
*/
/* Master PCM Volume */
/* LINE out volume */
/* Front volume */
/* 4CH out volume (has one of three possible uses, first use) */
/* ALT out volume (has one of three possible uses, second use) */
/* ALT out volume (has one of three possible uses, third use) */
/* center out volume */
/* LFE out volume (sub-woofer) */
/* MONO out volume */
/* Record in GAIN */
/* MIC in volume */
/* LINE in volume */
/* CD in volume */
/* VIDEO in volume */
/* AUX in volume */
/* PHONE in volume */
/* PC BEEPER in volume (motherboard speaker pins) */
/* BASS out level (note, zero is hardware bypass) */
/* TREBLE out level (note, zero is hardware bypass) */
/* 3D depth out level */
/* 3D center out level */
/* MIC BOOST switch */
/*
* The following selectors *must* come after the others, as they rely
* on the probe results of other controls.
*/
/* record src select (only one port at a time) */
/* Start of non-standard private controls */
/* mono MIC GAIN */
/* MIC select switch 0=mic1 1=mic2 */
/* MONO out src select 0=mix 1=mic */
{NULL}
};
/*
* Probe all possible controls and register existing
* ones and set initial values
*
* Returns zero on success
*/
static int
{
/*
* Set some ports which are always present.
*/
}
}
}
}
}
}
}
return (0);
}
/*
* Allocate an AC97 instance for use by a hardware driver.
*
* returns an allocated and initialize ac97 structure.
*/
ac97_t *
{
} else { \
}
/*
* Engage the external amplifier by default, suppress with
* a property of the form "ac97-amplifier=0".
*/
/*
* We cannot necessarily know if the headphone jack is present
* or not. There's a technique to probe the codec for
* headphone support, but many vendors seem to simply hang the
* headphone jack on the line out circuit, and have some kind
* of jack sense detection to enable or disable it by default.
* None of this is visible in the AC'97 registers.
*
* We cannot do much about it, but what we can do is offer users
* a way to suppress the option for a headphone port. Users and
* administrators can then set a flag in the driver.conf to suppress
* the option from display.
*
* It turns out that this problem exists for other signals as
* well.
*/
/*
* Most SPARC systems use the AC97 monoaural output for the
* built-in speaker. On these systems, we want to expose and
* enable the built-in speaker by default.
*
* On most x86 systems, the mono output is not connected to
* anything -- the AC'97 spec makes it pretty clear that the
* output was actually intended for use with speaker phones.
* So on those systems, we really don't want to activate the
* speaker -- we don't even want to expose it's presence
* normally.
*
* However, there could be an exception to the rule here. To
* facilitate this, we allow for the presence of the property
* to indicate that the speaker should be exposed. Whether it
* is enabled by default or not depends on the value of the
* property. (Generally on SPARC, we enable by default. On
* other systems we do not.)
*/
#ifdef __sparc
#else
}
#endif
/*
* Enable microphone boost (20dB normally) by default?
*/
return (ac);
}
/*
* Free an AC97 instance.
*/
void
{
/* Clear out any controls that are still attached */
}
}
static struct vendor {
unsigned id;
const char *name;
} vendors[] = {
{ AC97_VENDOR_ADS, "Analog Devices" },
{ AC97_VENDOR_AKM, "Asahi Kasei" },
{ AC97_VENDOR_ALC, "Realtek" },
{ AC97_VENDOR_ALG, "Avance Logic" },
{ AC97_VENDOR_CMI, "C-Media" },
{ AC97_VENDOR_CRY, "Cirrus Logic" },
{ AC97_VENDOR_CXT, "Conexant" },
{ AC97_VENDOR_ESS, "ESS Technology" },
{ AC97_VENDOR_EV, "Ectiva" },
{ AC97_VENDOR_ICE, "ICEnsemble" },
{ AC97_VENDOR_ST, "SigmaTel" },
{ AC97_VENDOR_TRA, "TriTech", },
{ AC97_VENDOR_VIA, "VIA Technologies" },
{ AC97_VENDOR_WML, "Wolfson" },
{ AC97_VENDOR_YMH, "Yamaha" },
{ 0, NULL },
};
static struct codec {
unsigned id;
const char *name;
int enh_bits;
} codecs[] = {
{ AC97_CODEC_AK4540, "AK4540" },
{ AC97_CODEC_STAC9700, "STAC9700" },
{ AC97_CODEC_STAC9701, "STAC9701" },
{ AC97_CODEC_STAC9701_2, "STAC9701" },
{ AC97_CODEC_STAC9704, "STAC9704" },
{ AC97_CODEC_STAC9705, "STAC9705" },
{ AC97_CODEC_STAC9721, "STAC9721" },
{ AC97_CODEC_STAC9744, "STAC9744" },
{ AC97_CODEC_TR28028, "TR28028" },
{ AC97_CODEC_TR28028_2, "TR28028" },
{ AC97_CODEC_TR28023, "TR28023" },
{ AC97_CODEC_TR28023_2, "TR28023" },
{ AC97_CODEC_EM28028, "EM28028" },
{ AC97_CODEC_CX20468, "CX20468" },
{ AC97_CODEC_CX20468_2, "CX20468" },
{ AC97_CODEC_CX20468_21, "CX20468-21" },
{ AC97_CODEC_CS4297, "CS4297" },
{ AC97_CODEC_CS4297A, "CS4297A" },
{ AC97_CODEC_CS4294, "CS4294" },
{ AC97_CODEC_CS4299, "CS4299" },
{ AC97_CODEC_CS4202, "CS4202" },
{ AC97_CODEC_CS4205, "CS4205" },
{ AC97_CODEC_AD1819B, "AD1819B" },
{ AC97_CODEC_AD1881, "AD1881" },
{ AC97_CODEC_AD1881A, "AD1881A" },
{ AC97_CODEC_AD1885, "AD1885" },
{ AC97_CODEC_AD1886, "AD1886" },
{ AC97_CODEC_AD1887, "AD1887" },
{ AC97_CODEC_AD1888, "AD1888" },
{ AC97_CODEC_AD1980, "AD1980" },
{ AC97_CODEC_AD1985, "AD1985" },
{ AC97_CODEC_WM9701A, "WM9701A" },
{ AC97_CODEC_WM9703, "WM9703" },
{ AC97_CODEC_WM9704, "WM9704" },
{ AC97_CODEC_ES1921, "ES1921" },
{ AC97_CODEC_ICE1232, "ICE1232/VT1611A" },
{ AC97_CODEC_VT1612A, "VT1612A" },
{ AC97_CODEC_VT1616, "VT1616" },
{ AC97_CODEC_VT1616A, "VT1616A" },
{ AC97_CODEC_VT1617A, "VT1617A" },
{ AC97_CODEC_VT1618, "VT1618" },
{ AC97_CODEC_EV1938, "EV1938" },
{ AC97_CODEC_CMI9780, "CMI9780" },
{ AC97_CODEC_YMF743, "YMF743" },
{ AC97_CODEC_YMF753, "YMF753" },
{ 0, NULL }
};
/*
* Init the actual hardware related to a previously
* allocated instance of an AC97 device.
*
* Return zero on success.
*/
int
{
int enh_bits;
int vol_bits;
char nmbuf[128];
char buf[128];
/* Save audio framework instance structure */
ac->d = d;
if (vid1 == 0xffff) {
audio_dev_warn(d, "AC'97 codec unresponsive");
return (-1);
}
/*
* Find out kind of codec we have and set any special case
* settings needed.
*/
break;
}
}
break;
}
}
}
vendor = "Unknown";
}
/*
* Populate the initial shadow table.
*/
for (int i = 0; i < LAST_SHADOW_REG; i += sizeof (uint16_t)) {
}
enh_bits = 4;
vol_bits = 6;
flags = 0;
/* detect the bit width of the master volume controls */
vol_bits = 5;
}
/*
* AC'97 2.3 spec indicates three possible uses for AUX_OUT
* (aka LNLVL_OUT aka HP_OUT). We have to figure out which one
* is in use.
*/
/* it looks like it is probably headphones */
/* it is implemented */
}
}
/*
* If not a headphone, is it 4CH_OUT (surround?)
*/
}
}
/*
* If neither, then maybe its an auxiliary line level output?
*/
}
}
audio_dev_add_info(d, buf);
/*
* Probe and register all known controls with framework
*/
audio_dev_warn(d, "AC97 controls init failed");
/* XXX - need to free all controls registered? */
return (-1);
}
return (0);
}