#include <sys/audio/audio_driver.h>
#include <sys/note.h>
#include <sys/beep.h>
#include <sys/pci.h>
#include "audiohd.h"
#define DRVNAME "audiohd"
static int audiohd_attach(dev_info_t *, ddi_attach_cmd_t);
static int audiohd_detach(dev_info_t *, ddi_detach_cmd_t);
static int audiohd_quiesce(dev_info_t *);
static int audiohd_resume(audiohd_state_t *);
static int audiohd_suspend(audiohd_state_t *);
static int audiohd_init_state(audiohd_state_t *, dev_info_t *);
static int audiohd_init_pci(audiohd_state_t *, ddi_device_acc_attr_t *);
static void audiohd_fini_pci(audiohd_state_t *);
static int audiohd_reset_controller(audiohd_state_t *);
static int audiohd_init_controller(audiohd_state_t *);
static void audiohd_fini_controller(audiohd_state_t *);
static void audiohd_stop_dma(audiohd_state_t *);
static void audiohd_disable_intr(audiohd_state_t *);
static int audiohd_create_codec(audiohd_state_t *);
static void audiohd_build_path(audiohd_state_t *);
static void audiohd_destroy_codec(audiohd_state_t *);
static int audiohd_alloc_dma_mem(audiohd_state_t *, audiohd_dma_t *,
size_t, ddi_dma_attr_t *, uint_t);
static void audiohd_finish_output_path(hda_codec_t *);
static uint32_t audioha_codec_verb_get(void *, uint8_t,
uint8_t, uint16_t, uint8_t);
static uint32_t audioha_codec_4bit_verb_get(void *, uint8_t,
uint8_t, uint16_t, uint16_t);
static int audiohd_reinit_hda(audiohd_state_t *);
static int audiohd_response_from_codec(audiohd_state_t *,
uint32_t *, uint32_t *);
static void audiohd_restore_codec_gpio(audiohd_state_t *);
static void audiohd_change_speaker_state(audiohd_state_t *, int);
static int audiohd_allocate_port(audiohd_state_t *);
static void audiohd_free_port(audiohd_state_t *);
static void audiohd_restore_path(audiohd_state_t *);
static void audiohd_create_controls(audiohd_state_t *);
static void audiohd_get_channels(audiohd_state_t *);
static void audiohd_init_path(audiohd_state_t *);
static void audiohd_del_controls(audiohd_state_t *);
static void audiohd_destroy(audiohd_state_t *);
static void audiohd_beep_on(void *);
static void audiohd_beep_off(void *);
static void audiohd_beep_freq(void *, int);
static wid_t audiohd_find_beep(hda_codec_t *, wid_t, int);
static void audiohd_build_beep_path(hda_codec_t *);
static void audiohd_build_beep_amp(hda_codec_t *);
static void audiohd_finish_beep_path(hda_codec_t *);
static void audiohd_do_set_beep_volume(audiohd_state_t *,
audiohd_path_t *, uint64_t);
static void audiohd_set_beep_volume(audiohd_state_t *);
static int audiohd_set_beep(void *, uint64_t);
static void audiohd_pin_sense(audiohd_state_t *, uint32_t, uint32_t);
static int audiohd_beep;
static int audiohd_beep_divider;
static int audiohd_beep_vol = 1;
_NOTE(SCHEME_PROTECTS_DATA("unshared data", audiohd_beep))
_NOTE(SCHEME_PROTECTS_DATA("unshared data", audiohd_beep_divider))
_NOTE(SCHEME_PROTECTS_DATA("unshared data", audiohd_beep_vol))
static ddi_device_acc_attr_t hda_dev_accattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static const char *audiohd_dtypes[] = {
AUDIO_PORT_LINEOUT,
AUDIO_PORT_SPEAKER,
AUDIO_PORT_HEADPHONES,
AUDIO_PORT_CD,
AUDIO_PORT_SPDIFOUT,
AUDIO_PORT_DIGOUT,
AUDIO_PORT_MODEM,
AUDIO_PORT_HANDSET,
AUDIO_PORT_LINEIN,
AUDIO_PORT_AUX1IN,
AUDIO_PORT_MIC,
AUDIO_PORT_PHONE,
AUDIO_PORT_SPDIFIN,
AUDIO_PORT_DIGIN,
AUDIO_PORT_STEREOMIX,
AUDIO_PORT_NONE,
AUDIO_PORT_OTHER,
NULL,
};
static audiohd_codec_info_t audiohd_codecs[] = {
{0x1002aa01, "ATI R600 HDMI", 0x0},
{0x10134206, "Cirrus CS4206", 0x0},
{0x10de0002, "nVidia MCP78 HDMI", 0x0},
{0x10de0003, "nVidia MCP78 HDMI", 0x0},
{0x10de0006, "nVidia MCP78 HDMI", 0x0},
{0x10de0007, "nVidia MCP7A HDMI", 0x0},
{0x10ec0260, "Realtek ALC260", (NO_GPIO)},
{0x10ec0262, "Realtek ALC262", (NO_GPIO | EN_PIN_BEEP)},
{0x10ec0268, "Realtek ALC268", 0x0},
{0x10ec0272, "Realtek ALC272", 0x0},
{0x10ec0662, "Realtek ALC662", 0x0},
{0x10ec0663, "Realtek ALC663", 0x0},
{0x10ec0861, "Realtek ALC861", 0x0},
{0x10ec0862, "Realtek ALC862", 0x0},
{0x10ec0880, "Realtek ALC880", 0x0},
{0x10ec0882, "Realtek ALC882", 0x0},
{0x10ec0883, "Realtek ALC883", 0x0},
{0x10ec0885, "Realtek ALC885", 0x0},
{0x10ec0888, "Realtek ALC888", (NO_SPDIF)},
{0x111d7603, "Integrated Devices 92HD75B3X5", (NO_MIXER)},
{0x111d7608, "Integrated Devices 92HD75B2X5", (NO_MIXER)},
{0x111d76b2, "Integrated Devices 92HD71B7X", (NO_MIXER)},
{0x11d4194a, "Analog Devices AD1984A", 0x0},
{0x11d41981, "Analog Devices AD1981", (NO_MIXER)},
{0x11d41983, "Analog Devices AD1983", 0x0},
{0x11d41984, "Analog Devices AD1984", 0x0},
{0x11d41986, "Analog Devices AD1986A", 0x0},
{0x11d41988, "Analog Devices AD1988A", 0x0},
{0x11d4198b, "Analog Devices AD1988B", 0x0},
{0x13f69880, "CMedia CMI19880", 0x0},
{0x14f15045, "Conexant CX20549", (NO_MIXER)},
{0x14f15051, "Conexant CX20561", 0x0},
{0x434d4980, "CMedia CMI19880", 0x0},
{0x80862802, "Intel HDMI", 0x0},
{0x83847610, "Sigmatel STAC9230XN", 0x0},
{0x83847611, "Sigmatel STAC9230DN", 0x0},
{0x83847612, "Sigmatel STAC9230XT", 0x0},
{0x83847613, "Sigmatel STAC9230DT", 0x0},
{0x83847614, "Sigmatel STAC9229X", 0x0},
{0x83847615, "Sigmatel STAC9229D", 0x0},
{0x83847616, "Sigmatel STAC9228X", 0x0},
{0x83847617, "Sigmatel STAC9228D", 0x0},
{0x83847618, "Sigmatel STAC9227X", 0x0},
{0x83847619, "Sigmatel STAC9227D", 0x0},
{0x83847620, "Sigmatel STAC9274", 0x0},
{0x83847621, "Sigmatel STAC9274D", 0x0},
{0x83847622, "Sigmatel STAC9273X", 0x0},
{0x83847623, "Sigmatel STAC9273D", 0x0},
{0x83847624, "Sigmatel STAC9272X", 0x0},
{0x83847625, "Sigmatel STAC9272D", 0x0},
{0x83847626, "Sigmatel STAC9271X", 0x0},
{0x83847627, "Sigmatel STAC9271D", 0x0},
{0x83847628, "Sigmatel STAC9274X5NH", 0x0},
{0x83847629, "Sigmatel STAC9274D5NH", 0x0},
{0x83847662, "Sigmatel STAC9872AK", 0x0},
{0x83847664, "Sigmatel STAC9872K", 0x0},
{0x83847680, "Sigmatel STAC9221A1", 0x0},
{0x83847680, "Sigmatel STAC9221A1", 0x0},
{0x83847681, "Sigmatel STAC9220D", 0x0},
{0x83847682, "Sigmatel STAC9221", 0x0},
{0x83847683, "Sigmatel STAC9221D", 0x0},
{0x83847690, "Sigmatel STAC9200", 0x0},
{0x838476a0, "Sigmatel STAC9205", 0x0},
{0x838476a1, "Sigmatel STAC9205D", 0x0},
{0x838476a2, "Sigmatel STAC9204", 0x0},
{0x838476a3, "Sigmatel STAC9204D", 0x0},
{0x838476a4, "Sigmatel STAC9255", 0x0},
{0x838476a5, "Sigmatel STAC9255D", 0x0},
{0x838476a6, "Sigmatel STAC9254", 0x0},
{0x838476a7, "Sigmatel STAC9254D", 0x0},
{0x83847880, "Sigmatel STAC9220A1", 0x0},
{0x83847882, "Sigmatel STAC9220A2", 0x0},
{0x0, "Unknown 0x00000000", 0x0},
};
static void
audiohd_set_chipset_info(audiohd_state_t *statep)
{
uint32_t devid;
const char *name;
const char *vers;
devid = pci_config_get16(statep->hda_pci_handle, PCI_CONF_VENID);
devid <<= 16;
devid |= pci_config_get16(statep->hda_pci_handle, PCI_CONF_DEVID);
statep->devid = devid;
name = AUDIOHD_DEV_CONFIG;
vers = AUDIOHD_DEV_VERSION;
switch (devid) {
case 0x1002437b:
name = "ATI HD Audio";
vers = "SB450";
break;
case 0x10024383:
name = "ATI HD Audio";
vers = "SB600";
break;
case 0x10029442:
name = "ATI HD Audio";
vers = "Radeon HD 4850";
break;
case 0x1002aa30:
name = "ATI HD Audio";
vers = "HD 48x0";
break;
case 0x1002aa38:
name = "ATI HD Audio";
vers = "Radeon HD 4670";
break;
case 0x10de026c:
name = "NVIDIA HD Audio";
vers = "MCP51";
break;
case 0x10de0371:
name = "NVIDIA HD Audio";
vers = "MCP55";
break;
case 0x10de03e4:
name = "NVIDIA HD Audio";
vers = "MCP61";
break;
case 0x10de03f0:
name = "NVIDIA HD Audio";
vers = "MCP61A";
break;
case 0x10de044a:
name = "NVIDIA HD Audio";
vers = "MCP65";
break;
case 0x10de055c:
name = "NVIDIA HD Audio";
vers = "MCP67";
break;
case 0x10de0774:
name = "NVIDIA HD Audio";
vers = "MCP78S";
break;
case 0x10de0ac0:
name = "NVIDIA HD Audio";
vers = "MCP79";
break;
case 0x11063288:
name = "VIA HD Audio";
vers = "HDA";
break;
case 0x80862668:
name = "Intel HD Audio";
vers = "ICH6";
break;
case 0x808627d8:
name = "Intel HD Audio";
vers = "ICH7";
break;
case 0x8086284b:
name = "Intel HD Audio";
vers = "ICH8";
break;
case 0x8086293e:
name = "Intel HD Audio";
vers = "ICH9";
break;
case 0x80863a3e:
name = "Intel HD Audio";
vers = "ICH10";
break;
case 0x80863b56:
name = "Intel HD Audio";
vers = "PCH";
break;
}
audio_dev_set_description(statep->adev, name);
audio_dev_set_version(statep->adev, vers);
}
static int
audiohd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
audiohd_state_t *statep;
int instance;
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
statep = ddi_get_driver_private(dip);
ASSERT(statep != NULL);
return (audiohd_resume(statep));
default:
return (DDI_FAILURE);
}
statep = kmem_zalloc(sizeof (*statep), KM_SLEEP);
ddi_set_driver_private(dip, statep);
mutex_init(&statep->hda_mutex, NULL, MUTEX_DRIVER, 0);
mutex_enter(&statep->hda_mutex);
if (audiohd_init_state(statep, dip) != DDI_SUCCESS) {
audio_dev_warn(NULL, "audiohd_init_state failed");
goto error;
}
if (audiohd_init_pci(statep, &hda_dev_accattr) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"couldn't init pci regs");
goto error;
}
audiohd_set_chipset_info(statep);
if (audiohd_init_controller(statep) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"couldn't init controller");
goto error;
}
if (audiohd_create_codec(statep) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"couldn't create codec");
goto error;
}
audiohd_build_path(statep);
audiohd_get_channels(statep);
if (audiohd_allocate_port(statep) != DDI_SUCCESS) {
audio_dev_warn(statep->adev, "allocate port failure");
goto error;
}
audiohd_init_path(statep);
if ((statep->hda_ksp = kstat_create(DRVNAME, instance,
DRVNAME, "controller", KSTAT_TYPE_INTR, 1,
KSTAT_FLAG_PERSISTENT)) != NULL) {
kstat_install(statep->hda_ksp);
}
audiohd_disable_intr(statep);
audiohd_create_controls(statep);
if (audio_dev_register(statep->adev) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"unable to register with framework");
goto error;
}
ddi_report_dev(dip);
mutex_exit(&statep->hda_mutex);
return (DDI_SUCCESS);
error:
mutex_exit(&statep->hda_mutex);
audiohd_destroy(statep);
return (DDI_FAILURE);
}
static int
audiohd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
audiohd_state_t *statep;
statep = ddi_get_driver_private(dip);
ASSERT(statep != NULL);
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (audiohd_suspend(statep));
default:
return (DDI_FAILURE);
}
if (audio_dev_unregister(statep->adev) != DDI_SUCCESS)
return (DDI_FAILURE);
if (audiohd_beep)
(void) beep_fini();
audiohd_destroy(statep);
return (DDI_SUCCESS);
}
static struct dev_ops audiohd_dev_ops = {
DEVO_REV,
0,
NULL,
nulldev,
nulldev,
audiohd_attach,
audiohd_detach,
nodev,
NULL,
NULL,
NULL,
audiohd_quiesce,
};
static struct modldrv audiohd_modldrv = {
&mod_driverops,
"AudioHD",
&audiohd_dev_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
{ &audiohd_modldrv, NULL }
};
int
_init(void)
{
int rv;
audio_init_ops(&audiohd_dev_ops, DRVNAME);
if ((rv = mod_install(&modlinkage)) != 0) {
audio_fini_ops(&audiohd_dev_ops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&modlinkage)) == 0) {
audio_fini_ops(&audiohd_dev_ops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
audiohd_engine_format(void *arg)
{
audiohd_port_t *port = arg;
audiohd_state_t *statep = port->statep;
switch (statep->sample_bit_depth) {
case AUDIOHD_BIT_DEPTH24:
return (AUDIO_FORMAT_S32_LE);
case AUDIOHD_BIT_DEPTH16:
default:
return (AUDIO_FORMAT_S16_LE);
}
}
static int
audiohd_engine_channels(void *arg)
{
audiohd_port_t *port = arg;
return (port->nchan);
}
static int
audiohd_engine_rate(void *arg)
{
audiohd_port_t *port = arg;
audiohd_state_t *statep = port->statep;
return (statep->sample_rate);
}
static void
audiohd_free_path(audiohd_state_t *statep)
{
audiohd_path_t *path;
int i;
for (i = 0; i < statep->pathnum; i++) {
if (statep->path[i]) {
path = statep->path[i];
kmem_free(path, sizeof (audiohd_path_t));
}
}
}
static void
audiohd_destroy(audiohd_state_t *statep)
{
mutex_enter(&statep->hda_mutex);
audiohd_stop_dma(statep);
if (statep->hda_ksp)
kstat_delete(statep->hda_ksp);
audiohd_free_port(statep);
audiohd_free_path(statep);
audiohd_destroy_codec(statep);
audiohd_del_controls(statep);
audiohd_fini_controller(statep);
audiohd_fini_pci(statep);
mutex_exit(&statep->hda_mutex);
mutex_destroy(&statep->hda_mutex);
if (statep->adev)
audio_dev_free(statep->adev);
kmem_free(statep, sizeof (*statep));
}
static void
audiohd_get_channels(audiohd_state_t *statep)
{
int i;
uint8_t maxp, assoc;
maxp = 2;
for (i = 0; i < AUDIOHD_MAX_ASSOC; i++) {
if (maxp < statep->chann[i]) {
maxp = statep->chann[i];
assoc = i;
}
}
statep->pchan = maxp;
statep->assoc = assoc;
statep->rchan = 2;
}
static void
audiohd_init_play_path(audiohd_path_t *path)
{
int i;
uint32_t ctrl;
uint8_t ctrl8;
uint8_t nchann;
audiohd_widget_t *widget;
audiohd_pin_t *pin;
wid_t wid;
audiohd_pin_color_t color;
audiohd_state_t *statep = path->statep;
hda_codec_t *codec = path->codec;
for (i = 0; i < path->pin_nums; i++) {
wid = path->pin_wid[i];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if (pin->device == DTYPE_SPDIF_OUT) {
ctrl = audioha_codec_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_GET_SPDIF_CTL,
0);
ctrl |= AUDIOHD_SPDIF_ON;
ctrl8 = ctrl &
AUDIOHD_SPDIF_MASK;
(void) audioha_codec_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_SPDIF_LCL,
ctrl8);
if (codec->codec_info->flags & NO_SPDIF)
return;
}
}
wid = path->pin_wid[0];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if (pin->device == DTYPE_SPEAKER ||
pin->device == DTYPE_HP_OUT ||
pin->assoc != statep->assoc) {
(void) audioha_codec_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_STREAM_CHANN,
statep->port[PORT_DAC]->index <<
AUDIOHD_PLAY_TAG_OFF);
(void) audioha_codec_4bit_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_CONV_FMT,
statep->port[PORT_DAC]->format << 4 |
statep->pchan - 1);
} else {
color = (pin->config >> AUDIOHD_PIN_CLR_OFF) &
AUDIOHD_PIN_CLR_MASK;
switch (color) {
case AUDIOHD_PIN_BLACK:
nchann = statep->pchan - 2;
break;
case AUDIOHD_PIN_ORANGE:
nchann = 2;
break;
case AUDIOHD_PIN_GREY:
nchann = 4;
break;
case AUDIOHD_PIN_GREEN:
nchann = 0;
break;
default:
nchann = 0;
break;
}
(void) audioha_codec_verb_get(statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_STREAM_CHANN,
statep->port[PORT_DAC]->index <<
AUDIOHD_PLAY_TAG_OFF |
nchann);
(void) audioha_codec_4bit_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_CONV_FMT,
statep->port[PORT_DAC]->format << 4 |
statep->pchan - 1);
}
}
static void
audiohd_init_record_path(audiohd_path_t *path)
{
audiohd_state_t *statep = path->statep;
hda_codec_t *codec = path->codec;
int i;
wid_t wid;
audiohd_pin_t *pin;
audiohd_widget_t *widget;
for (i = 0; i < path->pin_nums; i++) {
wid = path->pin_wid[i];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
#if 0
if (pin->device == DTYPE_SPDIF_IN) {
ctrl = audioha_codec_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_GET_SPDIF_CTL,
0);
ctrl |= AUDIOHD_SPDIF_ON;
ctrl8 = ctrl &
AUDIOHD_SPDIF_MASK;
(void) audioha_codec_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_SPDIF_LCL,
ctrl8);
statep->inmask |= (1U << DTYPE_SPDIF_IN);
}
#endif
if (pin->device == DTYPE_MIC_IN) {
if (((pin->config >>
AUDIOHD_PIN_CONTP_OFF) &
AUDIOHD_PIN_CONTP_MASK) ==
AUDIOHD_PIN_CON_FIXED)
statep->port[PORT_ADC]->index = path->tag;
}
if ((pin->device == DTYPE_LINE_IN) ||
(pin->device == DTYPE_CD) ||
(pin->device == DTYPE_MIC_IN)) {
statep->inmask |= (1U << pin->device);
}
}
(void) audioha_codec_verb_get(statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_STREAM_CHANN,
path->tag <<
AUDIOHD_REC_TAG_OFF);
(void) audioha_codec_4bit_verb_get(statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_SET_CONV_FMT,
statep->port[PORT_ADC]->format << 4 | statep->rchan - 1);
}
static void
audiohd_init_path(audiohd_state_t *statep)
{
int i;
audiohd_path_t *path;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path)
continue;
switch (path->path_type) {
case PLAY:
audiohd_init_play_path(path);
break;
case RECORD:
audiohd_init_record_path(path);
break;
default:
break;
}
}
statep->in_port = 0;
}
static int
audiohd_reset_port(audiohd_port_t *port)
{
uint16_t regbase;
audiohd_state_t *statep;
uint8_t bTmp;
int i;
regbase = port->regoff;
statep = port->statep;
bTmp = AUDIOHD_REG_GET8(regbase + AUDIOHD_SDREG_OFFSET_CTL);
bTmp &= ~AUDIOHD_REG_RIRBSIZE;
AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL, bTmp);
drv_usecwait(40);
bTmp |= AUDIOHDR_SD_CTL_SRST;
AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL, bTmp);
for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) {
drv_usecwait(50);
bTmp = AUDIOHD_REG_GET8(regbase + AUDIOHD_SDREG_OFFSET_CTL);
bTmp &= AUDIOHDR_SD_CTL_SRST;
if (bTmp)
break;
}
if (!bTmp) {
audio_dev_warn(statep->adev, "Failed to reset stream %d",
port->index);
return (EIO);
}
drv_usecwait(300);
bTmp &= ~AUDIOHDR_SD_CTL_SRST;
AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL, bTmp);
for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) {
drv_usecwait(50);
bTmp = AUDIOHD_REG_GET8(regbase + AUDIOHD_SDREG_OFFSET_CTL);
bTmp &= AUDIOHDR_SD_CTL_SRST;
if (!bTmp)
break;
}
if (bTmp) {
audio_dev_warn(statep->adev,
"Failed to exit reset state for"
" stream %d, bTmp=0x%02x", port->index, bTmp);
return (EIO);
}
AUDIOHD_REG_SET32(regbase + AUDIOHD_SDREG_OFFSET_BDLPL,
(uint32_t)port->bdl_paddr);
AUDIOHD_REG_SET32(regbase + AUDIOHD_SDREG_OFFSET_BDLPU,
(uint32_t)(port->bdl_paddr >> 32));
AUDIOHD_REG_SET16(regbase + AUDIOHD_SDREG_OFFSET_LVI,
AUDIOHD_BDLE_NUMS - 1);
AUDIOHD_REG_SET32(regbase + AUDIOHD_SDREG_OFFSET_CBL, port->bufsize);
AUDIOHD_REG_SET16(regbase + AUDIOHD_SDREG_OFFSET_FORMAT,
port->format << 4 | port->nchan - 1);
AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_STS,
AUDIOHDR_SD_STS_BCIS | AUDIOHDR_SD_STS_FIFOE |
AUDIOHDR_SD_STS_DESE);
AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL +
AUDIOHD_PLAY_CTL_OFF,
(port->index) << AUDIOHD_PLAY_TAG_OFF);
return (0);
}
static int
audiohd_engine_open(void *arg, int flag, unsigned *nframes, caddr_t *bufp)
{
audiohd_port_t *port = arg;
audiohd_state_t *statep = port->statep;
_NOTE(ARGUNUSED(flag));
mutex_enter(&statep->hda_mutex);
port->count = 0;
port->curpos = 0;
*nframes = port->nframes;
*bufp = port->samp_kaddr;
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_engine_start(void *arg)
{
audiohd_port_t *port = arg;
audiohd_state_t *statep = port->statep;
int rv;
mutex_enter(&statep->hda_mutex);
if ((rv = audiohd_reset_port(port)) != 0) {
mutex_exit(&statep->hda_mutex);
return (rv);
}
AUDIOHD_REG_SET8(port->regoff + AUDIOHD_SDREG_OFFSET_CTL,
AUDIOHDR_SD_CTL_SRUN);
mutex_exit(&statep->hda_mutex);
return (0);
}
static void
audiohd_engine_stop(void *arg)
{
audiohd_port_t *port = arg;
audiohd_state_t *statep = port->statep;
mutex_enter(&statep->hda_mutex);
AUDIOHD_REG_SET8(port->regoff + AUDIOHD_SDREG_OFFSET_CTL, 0);
mutex_exit(&statep->hda_mutex);
}
static void
audiohd_update_port(audiohd_port_t *port)
{
uint32_t pos, len;
audiohd_state_t *statep = port->statep;
int i, ret;
uint32_t status, resp = 0, respex = 0;
uint8_t rirbsts;
pos = AUDIOHD_REG_GET32(port->regoff + AUDIOHD_SDREG_OFFSET_LPIB);
pos /= (port->nchan * statep->sample_packed_bytes);
ASSERT(pos <= port->nframes);
if (pos >= port->curpos) {
len = (pos - port->curpos);
} else {
len = pos + port->nframes - port->curpos;
}
ASSERT(len <= port->nframes);
port->curpos = pos;
port->count += len;
status = AUDIOHD_REG_GET32(AUDIOHD_REG_INTSTS);
if (status == 0) {
return;
}
if (status & AUDIOHD_CIS_MASK) {
rirbsts = AUDIOHD_REG_GET8(AUDIOHD_REG_RIRBSTS);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBSTS, rirbsts);
for (i = 0; i < AUDIOHD_TEST_TIMES; i++) {
ret = audiohd_response_from_codec(statep, &resp,
&respex);
if ((ret == DDI_SUCCESS) &&
(respex & AUDIOHD_RIRB_UR_MASK)) {
statep->hda_rirb_rp =
AUDIOHD_REG_GET16(AUDIOHD_REG_RIRBWP) &
AUDIOHD_RIRB_WPMASK;
audiohd_pin_sense(statep, resp, respex);
break;
}
}
}
}
static uint64_t
audiohd_engine_count(void *arg)
{
audiohd_port_t *port = arg;
audiohd_state_t *statep = port->statep;
uint64_t val;
mutex_enter(&statep->hda_mutex);
audiohd_update_port(port);
val = port->count;
mutex_exit(&statep->hda_mutex);
return (val);
}
static void
audiohd_engine_close(void *arg)
{
_NOTE(ARGUNUSED(arg));
}
static void
audiohd_engine_sync(void *arg, unsigned nframes)
{
audiohd_port_t *port = arg;
_NOTE(ARGUNUSED(nframes));
(void) ddi_dma_sync(port->samp_dmah, 0, 0, port->sync_dir);
}
audio_engine_ops_t audiohd_engine_ops = {
AUDIO_ENGINE_VERSION,
audiohd_engine_open,
audiohd_engine_close,
audiohd_engine_start,
audiohd_engine_stop,
audiohd_engine_count,
audiohd_engine_format,
audiohd_engine_channels,
audiohd_engine_rate,
audiohd_engine_sync,
NULL,
NULL,
NULL
};
static int
audiohd_get_control(void *arg, uint64_t *val)
{
audiohd_ctrl_t *ac = arg;
audiohd_state_t *statep = ac->statep;
mutex_enter(&statep->hda_mutex);
*val = ac->val;
mutex_exit(&statep->hda_mutex);
return (0);
}
static void
audiohd_do_set_pin_volume(audiohd_state_t *statep, audiohd_path_t *path,
uint64_t val)
{
uint8_t l, r;
uint_t tmp;
int gain;
if (path->mute_wid && val == 0) {
(void) audioha_codec_4bit_verb_get(
statep,
path->codec->index,
path->mute_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_LNR |
AUDIOHDC_AMP_SET_MUTE);
return;
}
l = (val & 0xff00) >> 8;
r = (val & 0xff);
tmp = l * path->gain_bits / 100;
(void) audioha_codec_4bit_verb_get(statep,
path->codec->index,
path->gain_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LEFT | path->gain_dir |
tmp);
tmp = r * path->gain_bits / 100;
(void) audioha_codec_4bit_verb_get(statep,
path->codec->index,
path->gain_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_RIGHT | path->gain_dir |
tmp);
if (path->mute_wid && path->mute_wid != path->gain_wid) {
gain = AUDIOHDC_GAIN_MAX;
(void) audioha_codec_4bit_verb_get(
statep,
path->codec->index,
path->mute_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_LEFT |
gain);
(void) audioha_codec_4bit_verb_get(
statep,
path->codec->index,
path->mute_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_RIGHT |
gain);
}
}
static void
audiohd_set_pin_volume(audiohd_state_t *statep, audiohda_device_type_t type)
{
int i, j;
audiohd_path_t *path;
audiohd_widget_t *widget;
wid_t wid;
audiohd_pin_t *pin;
hda_codec_t *codec;
uint64_t val;
audiohd_ctrl_t control;
switch (type) {
case DTYPE_SPEAKER:
control = statep->ctrls[CTL_SPEAKER];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case DTYPE_HP_OUT:
control = statep->ctrls[CTL_HEADPHONE];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case DTYPE_LINEOUT:
control = statep->ctrls[CTL_FRONT];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case DTYPE_CD:
control = statep->ctrls[CTL_CD];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case DTYPE_LINE_IN:
control = statep->ctrls[CTL_LINEIN];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case DTYPE_MIC_IN:
control = statep->ctrls[CTL_MIC];
if (control.ctrl == NULL)
return;
val = control.val;
break;
}
for (i = 0; i < statep->pathnum; i++) {
if ((path = statep->path[i]) == NULL)
continue;
codec = path->codec;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if ((pin->device == type) && path->gain_wid) {
audiohd_do_set_pin_volume(statep, path, val);
}
}
}
}
static void
audiohd_set_pin_volume_by_color(audiohd_state_t *statep,
audiohd_pin_color_t color)
{
int i, j;
audiohd_path_t *path;
audiohd_widget_t *widget;
wid_t wid;
audiohd_pin_t *pin;
hda_codec_t *codec;
uint8_t l, r;
uint64_t val;
audiohd_pin_color_t clr;
audiohd_ctrl_t control;
switch (color) {
case AUDIOHD_PIN_GREEN:
control = statep->ctrls[CTL_FRONT];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case AUDIOHD_PIN_BLACK:
control = statep->ctrls[CTL_REAR];
if (control.ctrl == NULL)
return;
val = control.val;
break;
case AUDIOHD_PIN_ORANGE:
control = statep->ctrls[CTL_CENTER];
if (control.ctrl == NULL)
return;
l = control.val;
control = statep->ctrls[CTL_LFE];
if (control.ctrl == NULL)
return;
r = control.val;
val = (l << 8) | r;
break;
case AUDIOHD_PIN_GREY:
control = statep->ctrls[CTL_SURROUND];
if (control.ctrl == NULL)
return;
val = control.val;
break;
}
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path)
continue;
codec = path->codec;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
clr = (pin->config >> AUDIOHD_PIN_CLR_OFF) &
AUDIOHD_PIN_CLR_MASK;
if ((clr == color) && path->gain_wid) {
audiohd_do_set_pin_volume(statep, path, val);
}
}
}
}
static int
audiohd_set_input_pin(audiohd_state_t *statep)
{
uint64_t val;
hda_codec_t *codec;
audiohd_pin_t *pin;
audiohd_path_t *path;
audiohd_widget_t *widget, *w;
int i, j;
wid_t wid, pin_wid = 0;
uint32_t set_val;
val = statep->ctrls[CTL_RECSRC].val;
set_val = ddi_ffs(val & 0xffff) - 1;
for (i = 0; i < statep->pathnum; i++) {
if ((path = statep->path[i]) == NULL ||
path->path_type != RECORD)
continue;
switch (set_val) {
case DTYPE_LINE_IN:
case DTYPE_MIC_IN:
case DTYPE_CD:
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = path->codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if ((1U << pin->device) == val) {
AUDIOHD_ENABLE_PIN_IN(statep,
path->codec->index, pin->wid);
pin_wid = pin->wid;
codec = path->codec;
statep->in_port = pin->device;
} else if (statep->in_port == pin->device) {
AUDIOHD_DISABLE_PIN_IN(statep,
path->codec->index, pin->wid);
}
}
break;
default:
break;
}
}
if (pin_wid == 0)
return (DDI_SUCCESS);
w = codec->widget[pin_wid];
pin = (audiohd_pin_t *)w->priv;
w = codec->widget[pin->adc_wid];
path = (audiohd_path_t *)w->priv;
if (path->sum_wid) {
w = codec->widget[path->sum_wid];
if (w->type == WTYPE_AUDIO_SEL) {
for (i = 0; i < path->pin_nums; i++) {
if (path->pin_wid[i] == pin->wid) {
(void) audioha_codec_verb_get(
statep, codec->index, path->sum_wid,
AUDIOHDC_VERB_SET_CONN_SEL,
path->sum_selconn[i]);
break;
}
}
}
}
return (DDI_SUCCESS);
}
static void
audiohd_set_pin_monitor_gain(hda_codec_t *codec, audiohd_state_t *statep,
uint_t caddr, audiohd_pin_t *pin, uint64_t gain)
{
int i, k;
uint_t ltmp, rtmp;
audiohd_widget_t *widget;
uint8_t l, r;
l = (gain & 0xff00) >> 8;
r = (gain & 0xff);
for (k = 0; k < pin->num; k++) {
ltmp = l * pin->mg_gain[k] / 100;
rtmp = r * pin->mg_gain[k] / 100;
widget = codec->widget[pin->mg_wid[k]];
if (pin->mg_dir[k] == AUDIOHDC_AMP_SET_OUTPUT) {
(void) audioha_codec_4bit_verb_get(
statep,
caddr,
pin->mg_wid[k],
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LEFT|
pin->mg_dir[k] | ltmp);
(void) audioha_codec_4bit_verb_get(
statep,
caddr,
pin->mg_wid[k],
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_RIGHT|
pin->mg_dir[k] | rtmp);
} else if (pin->mg_dir[k] == AUDIOHDC_AMP_SET_INPUT) {
for (i = 0; i < widget->used; i++) {
(void) audioha_codec_4bit_verb_get(
statep,
caddr,
pin->mg_wid[k],
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_RIGHT|
widget->monitor_path_next[i]<<
AUDIOHDC_AMP_SET_INDEX_OFFSET |
pin->mg_dir[k] | rtmp);
(void) audioha_codec_4bit_verb_get(
statep,
caddr,
pin->mg_wid[k],
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LEFT|
widget->monitor_path_next[i]<<
AUDIOHDC_AMP_SET_INDEX_OFFSET |
pin->mg_dir[k] | ltmp);
}
}
}
}
static void
audiohd_set_monitor_gain(audiohd_state_t *statep)
{
int i, j;
audiohd_path_t *path;
uint_t caddr;
audiohd_widget_t *w;
wid_t wid;
audiohd_pin_t *pin;
audiohd_ctrl_t ctrl;
uint64_t val;
ctrl = statep->ctrls[CTL_MONGAIN];
val = ctrl.val;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (path == NULL || path->path_type != PLAY)
continue;
caddr = path->codec->index;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
w = path->codec->widget[wid];
pin = (audiohd_pin_t *)w->priv;
audiohd_set_pin_monitor_gain(path->codec, statep,
caddr, pin, val);
}
}
}
static void
audiohd_set_beep_volume(audiohd_state_t *statep)
{
int i;
audiohd_path_t *path;
hda_codec_t *codec;
uint64_t val;
uint_t tmp;
audiohd_ctrl_t control;
uint32_t vid;
control = statep->ctrls[CTL_BEEP];
val = control.val;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path || path->path_type != BEEP)
continue;
codec = path->codec;
vid = codec->vid;
vid = vid >> 16;
switch (vid) {
case AUDIOHD_VID_SIGMATEL:
tmp = val * path->gain_bits / 100;
if (tmp == 0) {
audiohd_beep_vol = 0;
} else {
audiohd_beep_vol = tmp;
(void) audioha_codec_verb_get(
statep,
codec->index,
path->beep_wid,
AUDIOHDC_VERB_SET_BEEP_VOL,
tmp);
}
break;
default:
audiohd_do_set_beep_volume(statep, path, val);
break;
}
}
}
static void
audiohd_do_set_beep_volume(audiohd_state_t *statep, audiohd_path_t *path,
uint64_t val)
{
uint8_t l, r;
uint_t tmp;
int gain;
if (val == 0) {
(void) audioha_codec_4bit_verb_get(
statep,
path->codec->index,
path->mute_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_LNR |
AUDIOHDC_AMP_SET_MUTE);
return;
}
r = (val & 0xff);
l = r;
tmp = l * path->gain_bits / 100;
(void) audioha_codec_4bit_verb_get(statep,
path->codec->index,
path->gain_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LEFT | path->gain_dir |
tmp);
tmp = r * path->gain_bits / 100;
(void) audioha_codec_4bit_verb_get(statep,
path->codec->index,
path->gain_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_RIGHT | path->gain_dir |
tmp);
if (path->mute_wid != path->gain_wid) {
gain = AUDIOHDC_GAIN_MAX;
(void) audioha_codec_4bit_verb_get(
statep,
path->codec->index,
path->mute_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_LEFT |
gain);
(void) audioha_codec_4bit_verb_get(
statep,
path->codec->index,
path->mute_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_RIGHT |
gain);
}
}
static void
audiohd_configure_output(audiohd_state_t *statep)
{
audiohd_set_pin_volume(statep, DTYPE_LINEOUT);
audiohd_set_pin_volume(statep, DTYPE_SPEAKER);
audiohd_set_pin_volume(statep, DTYPE_HP_OUT);
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_GREEN);
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_BLACK);
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_GREY);
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_ORANGE);
}
static void
audiohd_configure_input(audiohd_state_t *statep)
{
(void) audiohd_set_input_pin(statep);
audiohd_set_monitor_gain(statep);
audiohd_set_pin_volume(statep, DTYPE_LINE_IN);
audiohd_set_pin_volume(statep, DTYPE_CD);
audiohd_set_pin_volume(statep, DTYPE_MIC_IN);
}
static int
audiohd_set_recsrc(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
if (val & ~(statep->inmask))
return (EINVAL);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_configure_input(statep);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_rear(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_BLACK);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_center(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_CHANNEL_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_ORANGE);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_surround(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_GREY);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_lfe(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_CHANNEL_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_ORANGE);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_speaker(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume(statep, DTYPE_SPEAKER);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_front(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume_by_color(statep, AUDIOHD_PIN_GREEN);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_headphone(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume(statep, DTYPE_HP_OUT);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_linein(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume(statep, DTYPE_LINE_IN);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_loopback(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
audiohd_path_t *path = NULL;
audiohd_widget_t *widget = NULL;
audiohd_pin_t *pin = NULL;
wid_t wid;
uint32_t pinctrl;
int i, j;
mutex_enter(&statep->hda_mutex);
pc->val = val;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (path == NULL || path->path_type != LOOPBACK)
continue;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = path->codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if (val == 1) {
pinctrl = audioha_codec_verb_get(statep,
path->codec->index, wid,
AUDIOHDC_VERB_GET_PIN_CTRL, 0);
(void) audioha_codec_verb_get(statep,
path->codec->index, wid,
AUDIOHDC_VERB_SET_PIN_CTRL,
pinctrl | AUDIOHD_PIN_OUT_ENABLE);
if (pin->cap & AUDIOHD_EXT_AMP_MASK) {
(void) audioha_codec_verb_get(statep,
path->codec->index,
wid, AUDIOHDC_VERB_SET_EAPD,
AUDIOHD_EXT_AMP_ENABLE);
}
} else {
if (pin->device == DTYPE_LINE_IN) {
pinctrl = audioha_codec_verb_get(statep,
path->codec->index, wid,
AUDIOHDC_VERB_GET_PIN_CTRL, 0);
(void) audioha_codec_verb_get(statep,
path->codec->index, wid,
AUDIOHDC_VERB_SET_PIN_CTRL,
pinctrl & ~AUDIOHD_PIN_OUT_ENABLE);
}
}
}
}
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_mic(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume(statep, DTYPE_MIC_IN);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_cd(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_pin_volume(statep, DTYPE_CD);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_mongain(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_2CHANNELS_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_monitor_gain(statep);
mutex_exit(&statep->hda_mutex);
return (0);
}
static int
audiohd_set_beep(void *arg, uint64_t val)
{
audiohd_ctrl_t *pc = arg;
audiohd_state_t *statep = pc->statep;
AUDIOHD_CHECK_CHANNEL_VOLUME(val);
mutex_enter(&statep->hda_mutex);
pc->val = val;
audiohd_set_beep_volume(statep);
mutex_exit(&statep->hda_mutex);
return (0);
}
#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
#define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
#define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
#define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
#define MONVOL (MONCTL | AUDIO_CTRL_FLAG_MONVOL)
#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
static void
audiohd_del_controls(audiohd_state_t *statep)
{
int i;
for (i = 0; i < CTL_MAX; i++) {
audiohd_ctrl_t *ac = &statep->ctrls[i];
if (ac->ctrl != NULL) {
audio_dev_del_control(ac->ctrl);
ac->ctrl = NULL;
}
}
}
static void
audiohd_create_mono(audiohd_state_t *statep, int ctl,
const char *id, int flags, int defval, audio_ctrl_wr_t fn)
{
audiohd_ctrl_t *ac;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ac = &statep->ctrls[ctl];
ac->statep = statep;
ac->num = ctl;
desc.acd_name = id;
desc.acd_type = AUDIO_CTRL_TYPE_MONO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = flags;
ac->val = defval;
ac->ctrl = audio_dev_add_control(statep->adev, &desc,
audiohd_get_control, fn, ac);
}
static void
audiohd_create_stereo(audiohd_state_t *statep, int ctl,
const char *id, int flags, int defval, audio_ctrl_wr_t fn)
{
audiohd_ctrl_t *ac;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ac = &statep->ctrls[ctl];
ac->statep = statep;
ac->num = ctl;
desc.acd_name = id;
desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = flags;
ac->val = (defval << 8) | defval;
ac->ctrl = audio_dev_add_control(statep->adev, &desc,
audiohd_get_control, fn, ac);
}
static void
audiohd_create_bool(audiohd_state_t *statep, int ctl,
const char *id, int defval, audio_ctrl_wr_t fn)
{
audiohd_ctrl_t *ac;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ac = &statep->ctrls[ctl];
ac->statep = statep;
ac->num = ctl;
desc.acd_name = id;
desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 1;
desc.acd_flags = RECCTL;
ac->val = defval;
ac->ctrl = audio_dev_add_control(statep->adev, &desc,
audiohd_get_control, fn, ac);
}
static void
audiohd_create_recsrc(audiohd_state_t *statep)
{
audiohd_ctrl_t *ac;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ac = &statep->ctrls[CTL_RECSRC];
ac->statep = statep;
ac->num = CTL_RECSRC;
desc.acd_name = AUDIO_CTRL_ID_RECSRC;
desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
desc.acd_flags = RECVOL;
desc.acd_minvalue = statep->inmask;
desc.acd_maxvalue = statep->inmask;
for (int i = 0; audiohd_dtypes[i]; i++) {
desc.acd_enum[i] = audiohd_dtypes[i];
}
ac->val = (1U << DTYPE_MIC_IN);
ac->ctrl = audio_dev_add_control(statep->adev, &desc,
audiohd_get_control, audiohd_set_recsrc, ac);
}
static void
audiohd_create_controls(audiohd_state_t *statep)
{
wid_t wid;
audiohd_widget_t *widget;
audiohd_path_t *path;
hda_codec_t *codec;
audiohd_pin_t *pin;
audiohd_pin_color_t color;
int i, j;
audio_dev_add_soft_volume(statep->adev);
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (path == NULL)
continue;
codec = path->codec;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
color = (pin->config >> AUDIOHD_PIN_CLR_OFF) &
AUDIOHD_PIN_CLR_MASK;
if (color == AUDIOHD_PIN_GREEN) {
audiohd_create_stereo(statep, CTL_FRONT,
AUDIO_CTRL_ID_FRONT, MAINVOL, 75,
audiohd_set_front);
} else if (color == AUDIOHD_PIN_BLACK &&
pin->device != DTYPE_HP_OUT &&
pin->device != DTYPE_MIC_IN) {
audiohd_create_stereo(statep, CTL_REAR,
AUDIO_CTRL_ID_REAR, MAINVOL, 75,
audiohd_set_rear);
} else if (color == AUDIOHD_PIN_ORANGE) {
audiohd_create_mono(statep, CTL_CENTER,
AUDIO_CTRL_ID_CENTER, MAINVOL, 75,
audiohd_set_center);
audiohd_create_mono(statep, CTL_LFE,
AUDIO_CTRL_ID_LFE, MAINVOL, 75,
audiohd_set_lfe);
} else if (color == AUDIOHD_PIN_GREY) {
audiohd_create_stereo(statep, CTL_SURROUND,
AUDIO_CTRL_ID_SURROUND, MAINVOL, 75,
audiohd_set_surround);
}
if (pin->device == DTYPE_SPEAKER) {
audiohd_create_stereo(statep, CTL_SPEAKER,
AUDIO_CTRL_ID_SPEAKER, MAINVOL, 75,
audiohd_set_speaker);
} else if (pin->device == DTYPE_HP_OUT) {
audiohd_create_stereo(statep, CTL_HEADPHONE,
AUDIO_CTRL_ID_HEADPHONE, MAINVOL, 75,
audiohd_set_headphone);
} else if (pin->device == DTYPE_LINE_IN) {
audiohd_create_stereo(statep, CTL_LINEIN,
AUDIO_CTRL_ID_LINEIN, RECVOL, 50,
audiohd_set_linein);
} else if (pin->device == DTYPE_MIC_IN) {
audiohd_create_stereo(statep, CTL_MIC,
AUDIO_CTRL_ID_MIC, RECVOL, 50,
audiohd_set_mic);
} else if (pin->device == DTYPE_CD) {
audiohd_create_stereo(statep, CTL_CD,
AUDIO_CTRL_ID_CD, RECVOL, 50,
audiohd_set_cd);
}
}
if (path->path_type == BEEP) {
widget = codec->widget[path->beep_wid];
if (widget->type == WTYPE_BEEP &&
path->gain_wid != 0) {
audiohd_create_mono(statep, CTL_BEEP,
AUDIO_CTRL_ID_BEEP, AUDIO_CTRL_FLAG_RW, 75,
audiohd_set_beep);
continue;
}
}
}
if (statep->monitor_supported) {
audiohd_create_stereo(statep, CTL_MONGAIN,
AUDIO_CTRL_ID_MONGAIN, MONVOL, 0,
audiohd_set_mongain);
}
if (statep->loopback_supported) {
audiohd_create_bool(statep, CTL_LOOP, AUDIO_CTRL_ID_LOOPBACK,
0, audiohd_set_loopback);
}
audiohd_create_recsrc(statep);
audiohd_configure_output(statep);
audiohd_configure_input(statep);
}
static int
audiohd_quiesce(dev_info_t *dip)
{
audiohd_state_t *statep;
statep = ddi_get_driver_private(dip);
mutex_enter(&statep->hda_mutex);
audiohd_stop_dma(statep);
mutex_exit(&statep->hda_mutex);
return (DDI_SUCCESS);
}
static void
audiohd_beep_on(void *arg)
{
hda_codec_t *codec = ((audiohd_widget_t *)arg)->codec;
audiohd_state_t *statep = codec->statep;
int caddr = codec->index;
wid_t wid = ((audiohd_widget_t *)arg)->wid_wid;
mutex_enter(&statep->hda_mutex);
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_BEEP_GEN, audiohd_beep_divider);
mutex_exit(&statep->hda_mutex);
}
static void
audiohd_beep_off(void *arg)
{
hda_codec_t *codec = ((audiohd_widget_t *)arg)->codec;
audiohd_state_t *statep = codec->statep;
int caddr = codec->index;
wid_t wid = ((audiohd_widget_t *)arg)->wid_wid;
mutex_enter(&statep->hda_mutex);
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_BEEP_GEN, AUDIOHDC_MUTE_BEEP_GEN);
mutex_exit(&statep->hda_mutex);
}
static void
audiohd_beep_freq(void *arg, int freq)
{
hda_codec_t *codec = ((audiohd_widget_t *)arg)->codec;
audiohd_state_t *statep = codec->statep;
uint32_t vid = codec->vid >> 16;
int divider;
_NOTE(ARGUNUSED(arg));
if (freq == 0) {
divider = 0;
} else {
if (freq > AUDIOHDC_MAX_BEEP_GEN)
freq = AUDIOHDC_MAX_BEEP_GEN;
else if (freq < AUDIOHDC_MIX_BEEP_GEN)
freq = AUDIOHDC_MIX_BEEP_GEN;
switch (vid) {
case AUDIOHD_VID_SIGMATEL:
divider = 257 - freq * 1024 / AUDIOHDC_SAMPR48000;
break;
default:
divider = AUDIOHDC_SAMPR48000 / freq;
break;
}
}
if (audiohd_beep_vol == 0)
divider = 0;
mutex_enter(&statep->hda_mutex);
audiohd_beep_divider = divider;
mutex_exit(&statep->hda_mutex);
}
static int
audiohd_init_state(audiohd_state_t *statep, dev_info_t *dip)
{
audio_dev_t *adev;
statep->hda_dip = dip;
statep->hda_rirb_rp = 0;
if ((adev = audio_dev_alloc(dip, 0)) == NULL) {
audio_dev_warn(NULL, "unable to allocate audio dev");
return (DDI_FAILURE);
}
statep->adev = adev;
audio_dev_set_description(adev, AUDIOHD_DEV_CONFIG);
audio_dev_set_version(adev, AUDIOHD_DEV_VERSION);
return (DDI_SUCCESS);
}
static int
audiohd_init_pci(audiohd_state_t *statep, ddi_device_acc_attr_t *acc_attr)
{
uint16_t cmdreg;
uint16_t vid;
uint8_t cTmp;
dev_info_t *dip = statep->hda_dip;
audio_dev_t *adev = statep->adev;
if (pci_config_setup(dip, &statep->hda_pci_handle) == DDI_FAILURE) {
audio_dev_warn(adev,
"pci config mapping failed");
return (DDI_FAILURE);
}
if (ddi_regs_map_setup(dip, 1, &statep->hda_reg_base, 0,
0, acc_attr, &statep->hda_reg_handle) != DDI_SUCCESS) {
audio_dev_warn(adev,
"memory I/O mapping failed");
return (DDI_FAILURE);
}
cmdreg = pci_config_get16(statep->hda_pci_handle, PCI_CONF_COMM);
pci_config_put16(statep->hda_pci_handle, PCI_CONF_COMM,
cmdreg | PCI_COMM_MAE | PCI_COMM_ME);
vid = pci_config_get16(statep->hda_pci_handle, PCI_CONF_VENID);
switch (vid) {
case AUDIOHD_VID_INTEL:
cTmp = pci_config_get8(statep->hda_pci_handle,
AUDIOHD_INTEL_PCI_TCSEL);
pci_config_put8(statep->hda_pci_handle,
AUDIOHD_INTEL_PCI_TCSEL, (cTmp & AUDIOHD_INTEL_TCS_MASK));
break;
case AUDIOHD_VID_ATI:
cTmp = pci_config_get8(statep->hda_pci_handle,
AUDIOHD_ATI_PCI_MISC2);
pci_config_put8(statep->hda_pci_handle, AUDIOHD_ATI_PCI_MISC2,
(cTmp & AUDIOHD_ATI_MISC2_MASK) | AUDIOHD_ATI_MISC2_SNOOP);
break;
case AUDIOHD_VID_NVIDIA:
cTmp = pci_config_get8(statep->hda_pci_handle,
AUDIOHD_CORB_SIZE_OFF);
pci_config_put8(statep->hda_pci_handle, AUDIOHD_CORB_SIZE_OFF,
cTmp | AUDIOHD_NVIDIA_SNOOP);
break;
default:
break;
}
return (DDI_SUCCESS);
}
static void
audiohd_fini_pci(audiohd_state_t *statep)
{
if (statep->hda_reg_handle != NULL) {
ddi_regs_map_free(&statep->hda_reg_handle);
}
if (statep->hda_pci_handle != NULL) {
pci_config_teardown(&statep->hda_pci_handle);
}
}
static void
audiohd_stop_dma(audiohd_state_t *statep)
{
int i;
uint_t base;
uint8_t bTmp;
AUDIOHD_REG_SET8(AUDIOHD_REG_CORBCTL, 0);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBCTL, 0);
base = AUDIOHD_REG_SD_BASE;
for (i = 0; i < statep->hda_streams_nums; i++) {
bTmp = AUDIOHD_REG_GET8(base + AUDIOHD_SDREG_OFFSET_CTL);
bTmp &= ~AUDIOHDR_RIRBCTL_DMARUN;
AUDIOHD_REG_SET8(base + AUDIOHD_SDREG_OFFSET_CTL, bTmp);
base += AUDIOHD_REG_SD_LEN;
}
drv_usecwait(40);
}
static int
audiohd_reset_controller(audiohd_state_t *statep)
{
int i;
uint16_t sTmp;
uint32_t gctl;
sTmp = AUDIOHD_REG_GET16(AUDIOHD_REG_STATESTS);
AUDIOHD_REG_SET16(AUDIOHD_REG_STATESTS, sTmp & 0x8000);
gctl = AUDIOHD_REG_GET32(AUDIOHD_REG_GCTL);
gctl &= ~AUDIOHDR_GCTL_CRST;
AUDIOHD_REG_SET32(AUDIOHD_REG_GCTL, gctl);
for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) {
drv_usecwait(150);
gctl = AUDIOHD_REG_GET32(AUDIOHD_REG_GCTL);
if ((gctl & AUDIOHDR_GCTL_CRST) == 0)
break;
}
if ((gctl & AUDIOHDR_GCTL_CRST) != 0) {
audio_dev_warn(statep->adev,
"failed to enter reset state");
return (DDI_FAILURE);
}
drv_usecwait(300);
AUDIOHD_REG_SET32(AUDIOHD_REG_GCTL, gctl | AUDIOHDR_GCTL_CRST);
for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) {
drv_usecwait(150);
gctl = AUDIOHD_REG_GET32(AUDIOHD_REG_GCTL);
if (gctl & AUDIOHDR_GCTL_CRST)
break;
}
if ((gctl & AUDIOHDR_GCTL_CRST) == 0) {
audio_dev_warn(statep->adev,
"failed to exit reset state");
return (DDI_FAILURE);
}
drv_usecwait(500);
AUDIOHD_REG_SET32(AUDIOHD_REG_GCTL,
gctl | AUDIOHDR_GCTL_URESPE);
return (DDI_SUCCESS);
}
static int
audiohd_alloc_dma_mem(audiohd_state_t *statep, audiohd_dma_t *pdma,
size_t memsize, ddi_dma_attr_t *dma_attr_p, uint_t dma_flags)
{
ddi_dma_cookie_t cookie;
uint_t count;
dev_info_t *dip = statep->hda_dip;
audio_dev_t *ahandle = statep->adev;
if (ddi_dma_alloc_handle(dip, dma_attr_p, DDI_DMA_SLEEP,
NULL, &pdma->ad_dmahdl) != DDI_SUCCESS) {
audio_dev_warn(ahandle,
"ddi_dma_alloc_handle failed");
return (DDI_FAILURE);
}
if (ddi_dma_mem_alloc(pdma->ad_dmahdl, memsize, &hda_dev_accattr,
dma_flags & (DDI_DMA_CONSISTENT | DDI_DMA_STREAMING),
DDI_DMA_SLEEP, NULL,
(caddr_t *)&pdma->ad_vaddr, &pdma->ad_real_sz,
&pdma->ad_acchdl) != DDI_SUCCESS) {
audio_dev_warn(ahandle,
"ddi_dma_mem_alloc failed");
return (DDI_FAILURE);
}
if (ddi_dma_addr_bind_handle(pdma->ad_dmahdl, NULL,
(caddr_t)pdma->ad_vaddr, pdma->ad_real_sz, dma_flags,
DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_DMA_MAPPED) {
audio_dev_warn(ahandle,
"ddi_dma_addr_bind_handle failed");
return (DDI_FAILURE);
}
pdma->ad_paddr = (uint64_t)(cookie.dmac_laddress);
pdma->ad_req_sz = memsize;
return (DDI_SUCCESS);
}
static void
audiohd_release_dma_mem(audiohd_dma_t *pdma)
{
if (pdma->ad_dmahdl != NULL) {
(void) ddi_dma_unbind_handle(pdma->ad_dmahdl);
}
if (pdma->ad_acchdl != NULL) {
ddi_dma_mem_free(&pdma->ad_acchdl);
pdma->ad_acchdl = NULL;
}
if (pdma->ad_dmahdl != NULL) {
ddi_dma_free_handle(&pdma->ad_dmahdl);
pdma->ad_dmahdl = NULL;
}
}
static int
audiohd_reinit_hda(audiohd_state_t *statep)
{
uint64_t addr;
(void) audiohd_init_pci(statep, &hda_dev_accattr);
if (audiohd_reset_controller(statep) != DDI_SUCCESS)
return (DDI_FAILURE);
AUDIOHD_REG_SET32(AUDIOHD_REG_SYNC, 0);
addr = statep->hda_dma_rirb.ad_paddr;
AUDIOHD_REG_SET32(AUDIOHD_REG_RIRBLBASE, (uint32_t)addr);
AUDIOHD_REG_SET32(AUDIOHD_REG_RIRBUBASE,
(uint32_t)(addr >> 32));
AUDIOHD_REG_SET16(AUDIOHD_REG_RIRBWP, AUDIOHDR_RIRBWP_RESET);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBSIZE, AUDIOHDR_RIRBSZ_256);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBCTL, AUDIOHDR_RIRBCTL_DMARUN |
AUDIOHDR_RIRBCTL_RINTCTL);
addr = statep->hda_dma_corb.ad_paddr;
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBRP, AUDIOHDR_CORBRP_RESET);
AUDIOHD_REG_SET32(AUDIOHD_REG_CORBLBASE, (uint32_t)addr);
AUDIOHD_REG_SET32(AUDIOHD_REG_CORBUBASE,
(uint32_t)(addr >> 32));
AUDIOHD_REG_SET8(AUDIOHD_REG_CORBSIZE, AUDIOHDR_CORBSZ_256);
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBWP, 0);
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBRP, 0);
AUDIOHD_REG_SET8(AUDIOHD_REG_CORBCTL, AUDIOHDR_CORBCTL_DMARUN);
audiohd_restore_codec_gpio(statep);
audiohd_restore_path(statep);
audiohd_init_path(statep);
return (DDI_SUCCESS);
}
static int
audiohd_init_controller(audiohd_state_t *statep)
{
uint64_t addr;
uint16_t gcap;
int retval;
ddi_dma_attr_t dma_attr = {
DMA_ATTR_V0,
0,
0xffffffffffffffffULL,
0x00000000ffffffffULL,
128,
0xfff,
1,
0xffffffff,
0xffffffff,
1,
1,
0
};
gcap = AUDIOHD_REG_GET16(AUDIOHD_REG_GCAP);
if ((gcap & AUDIOHDR_GCAP_64OK) == 0)
dma_attr.dma_attr_addr_hi = 0xffffffffUL;
statep->hda_input_streams = (gcap & AUDIOHDR_GCAP_INSTREAMS) >>
AUDIOHD_INSTR_NUM_OFF;
statep->hda_output_streams = (gcap & AUDIOHDR_GCAP_OUTSTREAMS) >>
AUDIOHD_OUTSTR_NUM_OFF;
statep->hda_streams_nums = statep->hda_input_streams +
statep->hda_output_streams;
statep->hda_record_regbase = AUDIOHD_REG_SD_BASE;
statep->hda_play_regbase = AUDIOHD_REG_SD_BASE + AUDIOHD_REG_SD_LEN *
statep->hda_input_streams;
audiohd_stop_dma(statep);
if (audiohd_reset_controller(statep) != DDI_SUCCESS)
return (DDI_FAILURE);
statep->hda_codec_mask = AUDIOHD_REG_GET16(AUDIOHD_REG_STATESTS);
if (!statep->hda_codec_mask) {
audio_dev_warn(statep->adev,
"no codec exists");
return (DDI_FAILURE);
}
retval = audiohd_alloc_dma_mem(statep, &statep->hda_dma_corb,
AUDIOHD_CDBIO_CORB_LEN, &dma_attr,
DDI_DMA_WRITE | DDI_DMA_STREAMING);
if (retval != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"failed to alloc DMA for CORB");
return (DDI_FAILURE);
}
retval = audiohd_alloc_dma_mem(statep, &statep->hda_dma_rirb,
AUDIOHD_CDBIO_RIRB_LEN, &dma_attr,
DDI_DMA_READ | DDI_DMA_STREAMING);
if (retval != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"failed to alloc DMA for RIRB");
return (DDI_FAILURE);
}
AUDIOHD_REG_SET32(AUDIOHD_REG_SYNC, 0);
addr = statep->hda_dma_rirb.ad_paddr;
AUDIOHD_REG_SET32(AUDIOHD_REG_RIRBLBASE, (uint32_t)addr);
AUDIOHD_REG_SET32(AUDIOHD_REG_RIRBUBASE, (uint32_t)(addr >> 32));
AUDIOHD_REG_SET16(AUDIOHD_REG_RIRBWP, AUDIOHDR_RIRBWP_RESET);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBSIZE, AUDIOHDR_RIRBSZ_256);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBCTL, AUDIOHDR_RIRBCTL_DMARUN |
AUDIOHDR_RIRBCTL_RINTCTL);
addr = statep->hda_dma_corb.ad_paddr;
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBRP, AUDIOHDR_CORBRP_RESET);
AUDIOHD_REG_SET32(AUDIOHD_REG_CORBLBASE, (uint32_t)addr);
AUDIOHD_REG_SET32(AUDIOHD_REG_CORBUBASE, (uint32_t)(addr >> 32));
AUDIOHD_REG_SET8(AUDIOHD_REG_CORBSIZE, AUDIOHDR_CORBSZ_256);
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBWP, 0);
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBRP, 0);
AUDIOHD_REG_SET8(AUDIOHD_REG_CORBCTL, AUDIOHDR_CORBCTL_DMARUN);
return (DDI_SUCCESS);
}
static void
audiohd_fini_controller(audiohd_state_t *statep)
{
audiohd_release_dma_mem(&statep->hda_dma_rirb);
audiohd_release_dma_mem(&statep->hda_dma_corb);
}
static void
audiohd_get_conns_from_entry(hda_codec_t *codec, audiohd_widget_t *widget,
uint32_t entry, audiohd_entry_prop_t *prop)
{
int i, k, num;
wid_t input_wid;
for (i = 0; i < prop->conns_per_entry &&
widget->nconns < prop->conn_len;
i++, entry >>= prop->bits_per_conn) {
ASSERT(widget->nconns < AUDIOHD_MAX_CONN);
input_wid = entry & prop->mask_wid;
if (entry & prop->mask_range) {
if (widget->nconns == 0) {
if (input_wid < codec->first_wid ||
(input_wid > codec->last_wid)) {
break;
}
widget->avail_conn[widget->nconns++] =
input_wid;
} else {
for (k = widget->avail_conn[widget->nconns-1] +
1; k <= input_wid; k++) {
ASSERT(widget->nconns <
AUDIOHD_MAX_CONN);
if (k < codec->first_wid ||
(k > codec->last_wid)) {
break;
} else {
num = widget->nconns;
widget->avail_conn[num] = k;
widget->nconns++;
}
}
}
} else {
if ((codec->first_wid <= input_wid) && (input_wid <=
codec->last_wid))
widget->avail_conn[widget->nconns++] =
input_wid;
}
}
}
static void
audiohd_get_conns(hda_codec_t *codec, wid_t wid)
{
audiohd_state_t *statep = codec->statep;
audiohd_widget_t *widget = codec->widget[wid];
uint8_t caddr = codec->index;
uint32_t entry;
audiohd_entry_prop_t prop;
wid_t input_wid;
int i;
prop.conn_len = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_CONNLIST_LEN);
if (prop.conn_len & AUDIOHD_FORM_MASK) {
prop.conns_per_entry = 2;
prop.bits_per_conn = 16;
prop.mask_range = 0x00008000;
prop.mask_wid = 0x00007fff;
} else {
prop.conns_per_entry = 4;
prop.bits_per_conn = 8;
prop.mask_range = 0x00000080;
prop.mask_wid = 0x0000007f;
}
prop.conn_len &= AUDIOHD_LEN_MASK;
if (prop.conn_len == 0) {
widget->nconns = 0;
audio_dev_warn(statep->adev,
"node %d has 0 connections", wid);
return;
}
if (prop.conn_len == 1) {
entry = audioha_codec_verb_get(statep, caddr,
wid, AUDIOHDC_VERB_GET_CONN_LIST_ENT, 0);
input_wid = entry & prop.mask_wid;
if ((input_wid < codec->first_wid) ||
(input_wid > codec->last_wid)) {
return;
}
widget->avail_conn[0] = input_wid;
widget->nconns = 1;
return;
}
widget->nconns = 0;
for (i = 0; i < prop.conn_len; i += prop.conns_per_entry) {
entry = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_CONN_LIST_ENT, i);
audiohd_get_conns_from_entry(codec, widget, entry, &prop);
}
}
static void
audiohd_get_pin_config(audiohd_widget_t *widget)
{
hda_codec_t *codec = widget->codec;
audiohd_state_t *statep = codec->statep;
audiohd_pin_t *pin, *prev, *p;
int caddr = codec->index;
wid_t wid = widget->wid_wid;
uint32_t cap, config, pinctrl;
uint8_t urctrl, vrefbits;
cap = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_PIN_CAP);
config = audioha_codec_verb_get(statep, caddr,
wid, AUDIOHDC_VERB_GET_DEFAULT_CONF, 0);
pinctrl = audioha_codec_verb_get(statep, caddr,
wid, AUDIOHDC_VERB_GET_PIN_CTRL, 0);
pin = (audiohd_pin_t *)kmem_zalloc(sizeof (audiohd_pin_t), KM_SLEEP);
widget->priv = pin;
if (((config >> AUDIOHD_PIN_CON_STEP) & AUDIOHD_PIN_CON_MASK) == 0x1) {
pin->no_phys_conn = 1;
}
pin->ctrl = pinctrl & AUDIOHD_PIN_IO_MASK;
pin->wid = wid;
pin->cap = cap;
pin->config = config;
pin->num = 0;
pin->finish = 0;
vrefbits = (cap >> AUDIOHD_PIN_VREF_OFF) & AUDIOHD_PIN_VREF_MASK;
if (vrefbits & AUDIOHD_PIN_VREF_L1)
pin->vrefvalue = 0x5;
else if (vrefbits & AUDIOHD_PIN_VREF_L2)
pin->vrefvalue = 0x4;
else if (vrefbits & AUDIOHD_PIN_VREF_L3)
pin->vrefvalue = 0x2;
else
pin->vrefvalue = 0x1;
pin->seq = config & AUDIOHD_PIN_SEQ_MASK;
pin->assoc = (config & AUDIOHD_PIN_ASO_MASK) >> AUDIOHD_PIN_ASO_OFF;
pin->device = (config & AUDIOHD_PIN_DEV_MASK) >> AUDIOHD_PIN_DEV_OFF;
if ((widget->widget_cap & AUDIOHD_URCAP_MASK) &&
(pin->cap & AUDIOHD_DTCCAP_MASK) &&
((pin->device == DTYPE_LINEOUT) ||
(pin->device == DTYPE_SPDIF_OUT) ||
(pin->device == DTYPE_HP_OUT) ||
(pin->device == DTYPE_MIC_IN))) {
urctrl = (uint8_t)(1 << (AUDIOHD_UR_ENABLE_OFF - 1));
urctrl |= (wid & AUDIOHD_UR_TAG_MASK);
(void) audioha_codec_verb_get(statep, caddr,
wid, AUDIOHDC_VERB_SET_UNS_ENABLE, urctrl);
}
if (codec->first_pin == NULL) {
codec->first_pin = pin;
} else {
prev = NULL;
p = codec->first_pin;
while (p) {
if (p->assoc > pin->assoc)
break;
if ((p->assoc == pin->assoc) &&
(p->seq > pin->seq))
break;
prev = p;
p = p->next;
}
if (prev) {
pin->next = prev->next;
prev->next = pin;
} else {
pin->next = codec->first_pin;
codec->first_pin = pin;
}
}
}
static int
audiohd_create_widgets(hda_codec_t *codec)
{
audiohd_widget_t *widget;
audiohd_state_t *statep = codec->statep;
wid_t wid;
uint32_t type, widcap;
int caddr = codec->index;
for (wid = codec->first_wid;
wid <= codec->last_wid; wid++) {
widget = (audiohd_widget_t *)
kmem_zalloc(sizeof (audiohd_widget_t), KM_SLEEP);
codec->widget[wid] = widget;
widget->codec = codec;
widget->output_path_next = AUDIOHD_NULL_CONN;
widget->input_path_next = AUDIOHD_NULL_CONN;
widget->beep_path_next = AUDIOHD_NULL_CONN;
widget->loopback_path_next = AUDIOHD_NULL_CONN;
widcap = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_AUDIO_WID_CAP);
type = AUDIOHD_WIDCAP_TO_WIDTYPE(widcap);
widget->wid_wid = wid;
widget->type = type;
widget->widget_cap = widcap;
widget->finish = 0;
widget->used = 0;
if (widcap & AUDIOHD_WIDCAP_CONNLIST) {
audiohd_get_conns(codec, wid);
}
if (widcap & AUDIOHD_WIDCAP_PWRCTRL) {
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_POWER_STATE, 0);
}
if (widcap & AUDIOHD_WIDCAP_FMT_OVRIDE) {
widget->pcm_format =
audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_PCM);
} else {
widget->pcm_format = codec->pcm_format;
}
if (widcap & AUDIOHD_WIDCAP_INAMP) {
if ((widcap & AUDIOHD_WIDCAP_AMP_OVRIDE) == 0)
widget->inamp_cap = codec->inamp_cap;
else
widget->inamp_cap =
audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PARAM,
AUDIOHDC_PAR_INAMP_CAP);
} else {
widget->inamp_cap = 0;
}
if (widcap & AUDIOHD_WIDCAP_OUTAMP) {
if ((widcap & AUDIOHD_WIDCAP_AMP_OVRIDE) == 0)
widget->outamp_cap = codec->outamp_cap;
else
widget->outamp_cap =
audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PARAM,
AUDIOHDC_PAR_OUTAMP_CAP);
} else {
widget->outamp_cap = 0;
}
switch (type) {
case WTYPE_AUDIO_OUT:
case WTYPE_AUDIO_IN:
case WTYPE_AUDIO_MIX:
case WTYPE_AUDIO_SEL:
case WTYPE_VENDOR:
case WTYPE_POWER:
case WTYPE_VOL_KNOB:
break;
case WTYPE_PIN:
if ((codec->codec_info->flags & EN_PIN_BEEP) == 0) {
(void) audioha_codec_4bit_verb_get(statep,
caddr, widget->wid_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
audiohd_get_pin_config(widget);
break;
case WTYPE_BEEP:
audiohd_beep = ddi_prop_get_int(DDI_DEV_T_ANY,
statep->hda_dip,
DDI_PROP_DONTPASS, "audiohd_beep", 1);
if (audiohd_beep) {
(void) beep_fini();
(void) beep_init((void *) widget,
audiohd_beep_on,
audiohd_beep_off,
audiohd_beep_freq);
}
break;
default:
break;
}
}
return (DDI_SUCCESS);
}
static void
audiohd_destroy_widgets(hda_codec_t *codec)
{
for (int i = 0; i < AUDIOHD_MAX_WIDGET; i++) {
if (codec->widget[i]) {
kmem_free(codec->widget[i], sizeof (audiohd_widget_t));
codec->widget[i] = NULL;
}
}
}
static int
audiohd_create_codec(audiohd_state_t *statep)
{
hda_codec_t *codec;
uint32_t mask, type;
uint32_t nums;
uint32_t i, j, len;
wid_t wid;
char buf[128];
int rate, bits;
dev_info_t *dip = statep->hda_dip;
mask = statep->hda_codec_mask;
ASSERT(mask != 0);
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
if ((mask & (1 << i)) == 0)
continue;
codec = (hda_codec_t *)kmem_zalloc(
sizeof (hda_codec_t), KM_SLEEP);
codec->index = i;
codec->vid = audioha_codec_verb_get(statep, i,
AUDIOHDC_NODE_ROOT, AUDIOHDC_VERB_GET_PARAM,
AUDIOHDC_PAR_VENDOR_ID);
if (codec->vid == (uint32_t)(-1)) {
kmem_free(codec, sizeof (hda_codec_t));
continue;
}
codec->revid =
audioha_codec_verb_get(statep, i,
AUDIOHDC_NODE_ROOT, AUDIOHDC_VERB_GET_PARAM,
AUDIOHDC_PAR_REV_ID);
nums = audioha_codec_verb_get(statep,
i, AUDIOHDC_NODE_ROOT,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_NODE_COUNT);
if (nums == (uint32_t)(-1)) {
kmem_free(codec, sizeof (hda_codec_t));
continue;
}
wid = (nums >> AUDIOHD_CODEC_STR_OFF) & AUDIOHD_CODEC_STR_MASK;
nums = nums & AUDIOHD_CODEC_NUM_MASK;
for (j = 0; j < nums; j++, wid++) {
type = audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_GET_PARAM,
AUDIOHDC_PAR_FUNCTION_TYPE);
if ((type & AUDIOHD_CODEC_TYPE_MASK) ==
AUDIOHDC_AUDIO_FUNC_GROUP) {
codec->wid_afg = wid;
break;
}
}
if (codec->wid_afg == 0) {
kmem_free(codec, sizeof (hda_codec_t));
continue;
}
ASSERT(codec->wid_afg == wid);
len = sizeof (audiohd_codecs) / sizeof (audiohd_codec_info_t);
for (j = 0; j < len-1; j++) {
if (audiohd_codecs[j].devid == codec->vid) {
codec->codec_info = &(audiohd_codecs[j]);
break;
}
}
if (codec->codec_info == NULL) {
codec->codec_info = &(audiohd_codecs[len-1]);
(void) snprintf(buf, sizeof (buf),
"Unknown HD codec: 0x%x", codec->vid);
} else {
(void) snprintf(buf, sizeof (buf), "HD codec: %s",
codec->codec_info->buf);
}
audio_dev_add_info(statep->adev, buf);
if ((codec->codec_info->flags & NO_GPIO) == 0) {
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_MASK, AUDIOHDC_GPIO_ENABLE);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_UNSOL_ENABLE_MASK,
AUDIOHDC_GPIO_ENABLE);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_DIREC, AUDIOHDC_GPIO_DIRECT);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_STCK,
AUDIOHDC_GPIO_DATA_CTRL);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_DATA,
AUDIOHDC_GPIO_STCK_CTRL);
}
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_POWER_STATE, AUDIOHD_PW_D0);
codec->outamp_cap = audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_OUTAMP_CAP);
codec->inamp_cap = audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_INAMP_CAP);
codec->stream_format = audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_STREAM);
codec->pcm_format = audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_GET_PARAM, AUDIOHDC_PAR_PCM);
statep->sample_rate = 48000;
rate = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "sample-rate", 48000);
if (rate == 192000 &&
(codec->pcm_format & AUDIOHD_SAMP_RATE192)) {
statep->sample_rate = 192000;
} else if (rate == 96000 &&
(codec->pcm_format & AUDIOHD_SAMP_RATE96)) {
statep->sample_rate = 96000;
} else {
statep->sample_rate = 48000;
}
statep->sample_bit_depth = AUDIOHD_BIT_DEPTH16;
bits = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "sample-bits", 16);
if (bits == 24 &&
(codec->pcm_format & AUDIOHD_BIT_DEPTH24)) {
statep->sample_bit_depth = AUDIOHD_BIT_DEPTH24;
} else {
statep->sample_bit_depth = AUDIOHD_BIT_DEPTH16;
}
nums = audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_GET_PARAM,
AUDIOHDC_PAR_NODE_COUNT);
wid = (nums >> AUDIOHD_CODEC_STR_OFF) & AUDIOHD_CODEC_STR_MASK;
nums = nums & AUDIOHD_CODEC_NUM_MASK;
codec->first_wid = wid;
codec->last_wid = wid + nums;
codec->nnodes = nums;
statep->codec[i] = codec;
codec->statep = statep;
(void) audiohd_create_widgets(codec);
}
return (DDI_SUCCESS);
}
static void
audiohd_destroy_codec(audiohd_state_t *statep)
{
int i;
audiohd_pin_t *pin, *npin;
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
if (statep->codec[i]) {
audiohd_destroy_widgets(statep->codec[i]);
pin = statep->codec[i]->first_pin;
while (pin) {
npin = pin;
pin = pin->next;
kmem_free(npin, sizeof (audiohd_pin_t));
}
kmem_free(statep->codec[i], sizeof (hda_codec_t));
statep->codec[i] = NULL;
}
}
}
static wid_t
audiohd_find_dac(hda_codec_t *codec, wid_t wid,
int mixer, int *mixernum,
int exclusive, int depth)
{
audiohd_widget_t *widget = codec->widget[wid];
wid_t wdac = (uint32_t)(DDI_FAILURE);
wid_t retval;
if (depth > AUDIOHD_MAX_DEPTH)
return (uint32_t)(DDI_FAILURE);
if (widget == NULL)
return (uint32_t)(DDI_FAILURE);
if (exclusive) {
if (widget->path_flags & AUDIOHD_PATH_DAC)
return (uint32_t)(DDI_FAILURE);
} else {
if (widget->path_flags & AUDIOHD_PATH_DAC)
return (wid);
}
switch (widget->type) {
case WTYPE_AUDIO_OUT:
if (mixer && !*mixernum)
return (uint32_t)(DDI_FAILURE);
widget->path_flags |= AUDIOHD_PATH_DAC;
widget->out_weight++;
wdac = widget->wid_wid;
break;
case WTYPE_AUDIO_MIX:
(*mixernum)++;
case WTYPE_AUDIO_SEL:
for (int i = 0; i < widget->nconns; i++) {
retval = audiohd_find_dac(codec,
widget->avail_conn[i],
mixer, mixernum,
exclusive, depth + 1);
if (retval != (uint32_t)DDI_FAILURE) {
if (widget->output_path_next ==
AUDIOHD_NULL_CONN) {
widget->output_path_next = i;
wdac = retval;
}
widget->path_flags |= AUDIOHD_PATH_DAC;
widget->out_weight++;
return (wdac);
}
}
default:
break;
}
return (wdac);
}
static void
audiohd_do_build_output_path(hda_codec_t *codec, int mixer, int *mnum,
int exclusive, int depth)
{
audiohd_pin_t *pin;
audiohd_widget_t *widget, *wdac;
audiohd_path_t *path;
wid_t wid;
audiohd_state_t *statep;
int i;
statep = codec->statep;
for (pin = codec->first_pin; pin; pin = pin->next) {
if ((pin->cap & AUDIOHD_PIN_CAP_MASK) == 0)
continue;
if ((pin->config & AUDIOHD_PIN_CONF_MASK) ==
AUDIOHD_PIN_NO_CONN)
continue;
if ((pin->device != DTYPE_LINEOUT) &&
(pin->device != DTYPE_SPEAKER) &&
(pin->device != DTYPE_SPDIF_OUT) &&
(pin->device != DTYPE_HP_OUT))
continue;
if (pin->finish)
continue;
widget = codec->widget[pin->wid];
widget->inamp_cap = 0;
for (i = 0; i < widget->nconns; i++) {
wid = audiohd_find_dac(codec,
widget->avail_conn[i], mixer, mnum, exclusive,
depth);
if (wid == (wid_t)DDI_FAILURE)
continue;
if (pin->device != DTYPE_SPEAKER &&
pin->device != DTYPE_HP_OUT)
statep->chann[pin->assoc] += 2;
path = (audiohd_path_t *)
kmem_zalloc(sizeof (audiohd_path_t),
KM_SLEEP);
path->adda_wid = wid;
path->pin_wid[0] = widget->wid_wid;
path->pin_nums = 1;
path->path_type = PLAY;
path->codec = codec;
path->statep = statep;
wdac = codec->widget[wid];
wdac->priv = path;
pin->dac_wid = wid;
pin->finish = 1;
widget->path_flags |= AUDIOHD_PATH_DAC;
widget->out_weight++;
widget->output_path_next = i;
statep->path[statep->pathnum++] = path;
break;
}
}
}
static void
audiohd_build_output_path(hda_codec_t *codec)
{
int mnum = 0;
uint8_t mixer_allow = 1;
if (codec->codec_info->flags & NO_MIXER)
mixer_allow = 0;
audiohd_do_build_output_path(codec, mixer_allow, &mnum, 1, 0);
audiohd_do_build_output_path(codec, mixer_allow, &mnum, 0, 0);
audiohd_do_build_output_path(codec, 0, &mnum, 1, 0);
audiohd_do_build_output_path(codec, 0, &mnum, 0, 0);
}
static void
audiohd_build_output_amp(hda_codec_t *codec)
{
audiohd_path_t *path;
audiohd_widget_t *w, *widget, *wpin, *wdac;
audiohd_pin_t *pin;
wid_t wid, next;
int weight;
int i, j;
uint32_t gain;
for (i = 0; i < codec->statep->pathnum; i++) {
path = codec->statep->path[i];
if (path == NULL || path->path_type != PLAY ||
path->codec != codec)
continue;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
wpin = codec->widget[wid];
pin = (audiohd_pin_t *)wpin->priv;
weight = wpin->out_weight;
widget = wpin;
while (widget) {
if (widget->outamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
pin->mute_wid = widget->wid_wid;
pin->mute_dir = AUDIOHDC_AMP_SET_OUTPUT;
break;
}
if (widget->inamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
pin->mute_wid = widget->wid_wid;
pin->mute_dir = AUDIOHDC_AMP_SET_INPUT;
break;
}
next = widget->output_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
if (widget && widget->out_weight != weight)
break;
}
gain = 0;
widget = wpin;
while (widget) {
gain = (widget->outamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS);
if (gain && gain > pin->gain_bits) {
pin->gain_dir = AUDIOHDC_AMP_SET_OUTPUT;
pin->gain_bits = gain;
pin->gain_wid = widget->wid_wid;
}
gain = widget->inamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS;
if (gain && gain > pin->gain_bits) {
pin->gain_dir = AUDIOHDC_AMP_SET_INPUT;
pin->gain_bits = gain;
pin->gain_wid = widget->wid_wid;
}
next = widget->output_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
if (widget && widget->out_weight != weight)
break;
}
pin->gain_bits >>= AUDIOHD_GAIN_OFF;
}
if (path->pin_nums == 1) {
path->mute_wid = pin->mute_wid;
path->mute_dir = pin->mute_dir;
path->gain_wid = pin->gain_wid;
path->gain_dir = pin->gain_dir;
path->gain_bits = pin->gain_bits;
} else {
wdac = codec->widget[path->adda_wid];
weight = wdac->out_weight;
wid = path->pin_wid[0];
w = codec->widget[wid];
while (w && w->out_weight != weight) {
wid = w->avail_conn[w->output_path_next];
w = codec->widget[wid];
}
widget = w;
while (widget) {
if (widget->outamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
path->mute_wid = widget->wid_wid;
path->mute_dir =
AUDIOHDC_AMP_SET_OUTPUT;
break;
}
if (widget->inamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
path->mute_wid = widget->wid_wid;
path->mute_dir =
AUDIOHDC_AMP_SET_INPUT;
break;
}
next = widget->output_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
gain = 0;
widget = w;
while (widget) {
gain = (widget->outamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS);
if (gain && gain > pin->gain_bits) {
path->gain_dir =
AUDIOHDC_AMP_SET_OUTPUT;
path->gain_bits = gain;
path->gain_wid = widget->wid_wid;
}
gain = widget->inamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS;
if (gain && (gain > pin->gain_bits) &&
(widget->type != WTYPE_AUDIO_MIX)) {
path->gain_dir =
AUDIOHDC_AMP_SET_INPUT;
path->gain_bits = gain;
path->gain_wid = widget->wid_wid;
}
next = widget->output_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
path->gain_bits >>= AUDIOHD_GAIN_OFF;
}
}
}
static void
audiohd_finish_output_path(hda_codec_t *codec)
{
audiohd_state_t *statep = codec->statep;
audiohd_path_t *path;
audiohd_widget_t *widget;
audiohd_pin_t *pin;
uint_t caddr = codec->index;
wid_t wid, next;
int i, j;
for (i = 0; i < codec->statep->pathnum; i++) {
path = codec->statep->path[i];
if (!path || path->path_type != PLAY || path->codec != codec)
continue;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
{
uint32_t lTmp;
lTmp = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PIN_CTRL, 0);
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_PIN_CTRL, (lTmp |
pin->vrefvalue |
AUDIOHDC_PIN_CONTROL_OUT_ENABLE |
AUDIOHDC_PIN_CONTROL_HP_ENABLE) &
~ AUDIOHDC_PIN_CONTROL_IN_ENABLE);
}
if (pin->cap & AUDIOHD_EXT_AMP_MASK)
(void) audioha_codec_verb_get(statep, caddr,
wid, AUDIOHDC_VERB_SET_EAPD,
AUDIOHD_EXT_AMP_ENABLE);
if (widget->outamp_cap) {
(void) audioha_codec_4bit_verb_get(statep,
caddr, wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_CONN_SEL,
widget->output_path_next);
wid = widget->avail_conn[widget->output_path_next];
widget = codec->widget[wid];
while (widget) {
if (widget->outamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep, caddr,
wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
if (widget->inamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep, caddr,
wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT |
AUDIOHDC_GAIN_MAX |
(widget->output_path_next <<
AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
next = widget->output_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
if ((widget->type == WTYPE_AUDIO_SEL) &&
(widget->nconns > 1))
(void) audioha_codec_verb_get(statep,
caddr, wid,
AUDIOHDC_VERB_SET_CONN_SEL,
widget->output_path_next);
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
}
}
}
static int
audiohd_find_input_pins(hda_codec_t *codec, wid_t wid, int allowmixer,
int depth, audiohd_path_t *path)
{
audiohd_widget_t *widget = codec->widget[wid];
audiohd_pin_t *pin;
audiohd_state_t *statep = codec->statep;
uint_t caddr = codec->index;
int retval = -1;
int num, i;
uint32_t pinctrl;
if (depth > AUDIOHD_MAX_DEPTH)
return (uint32_t)(DDI_FAILURE);
if (widget == NULL)
return (uint32_t)(DDI_FAILURE);
if (widget->path_flags & AUDIOHD_PATH_ADC ||
widget->path_flags & AUDIOHD_PATH_DAC)
return (uint32_t)(DDI_FAILURE);
switch (widget->type) {
case WTYPE_PIN:
pin = (audiohd_pin_t *)widget->priv;
if (pin->no_phys_conn)
return (uint32_t)(DDI_FAILURE);
pinctrl = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PIN_CTRL, 0);
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_PIN_CTRL,
pinctrl | AUDIOHD_PIN_IN_ENABLE);
if (pin->cap & AUDIOHD_EXT_AMP_MASK) {
(void) audioha_codec_verb_get(statep, caddr,
wid, AUDIOHDC_VERB_SET_EAPD,
AUDIOHD_EXT_AMP_ENABLE);
}
switch (pin->device) {
case DTYPE_CD:
case DTYPE_LINE_IN:
case DTYPE_MIC_IN:
case DTYPE_AUX:
widget->path_flags |= AUDIOHD_PATH_ADC;
widget->in_weight++;
path->pin_wid[path->pin_nums++] = wid;
pin->adc_wid = path->adda_wid;
return (DDI_SUCCESS);
}
break;
case WTYPE_AUDIO_MIX:
case WTYPE_AUDIO_SEL:
if (widget->nconns == 1) {
widget->input_path_next = 0;
retval = audiohd_find_input_pins(codec,
widget->avail_conn[0],
allowmixer, depth + 1, path);
if (retval == DDI_SUCCESS) {
widget->path_flags |= AUDIOHD_PATH_ADC;
widget->in_weight++;
}
break;
}
if (allowmixer) {
for (int i = 0; i < widget->nconns; i++) {
retval = audiohd_find_input_pins(codec,
widget->avail_conn[i], 0, depth + 1,
path);
if (retval == DDI_SUCCESS) {
widget->input_path_next = i;
widget->in_weight++;
num = path->pin_nums - 1;
path->sum_selconn[num] = i;
path->sum_wid = wid;
widget->path_flags |=
AUDIOHD_PATH_ADC;
}
}
if (path->pin_nums > 0)
retval = DDI_SUCCESS;
} else {
for (i = 0; i < widget->nconns; i++) {
retval = audiohd_find_input_pins(codec,
widget->avail_conn[i], 0, depth + 1,
path);
if (retval == DDI_SUCCESS) {
widget->input_path_next = i;
widget->path_flags |= AUDIOHD_PATH_ADC;
widget->in_weight++;
break;
}
}
}
break;
default:
break;
}
return (retval);
}
static void
audiohd_build_input_path(hda_codec_t *codec)
{
audiohd_widget_t *widget;
audiohd_path_t *path = NULL;
wid_t wid;
int i;
int retval;
uint8_t rtag = 0;
audiohd_state_t *statep = codec->statep;
for (wid = codec->first_wid; wid <= codec->last_wid; wid++) {
widget = codec->widget[wid];
if (widget == NULL || widget->type != WTYPE_AUDIO_IN)
continue;
if (path == NULL)
path = kmem_zalloc(sizeof (audiohd_path_t),
KM_SLEEP);
else
bzero(path, sizeof (audiohd_port_t));
path->adda_wid = wid;
for (i = 0; i < widget->nconns; i++) {
retval = audiohd_find_input_pins(codec,
widget->avail_conn[i], 1, 0, path);
if (retval == DDI_SUCCESS) {
path->codec = codec;
path->statep = statep;
path->path_type = RECORD;
path->tag = ++rtag;
codec->nistream++;
statep->path[statep->pathnum++] = path;
widget->input_path_next = i;
widget->priv = path;
path = NULL;
break;
}
}
}
if (path)
kmem_free(path, sizeof (audiohd_path_t));
}
static void
audiohd_build_input_amp(hda_codec_t *codec)
{
audiohd_path_t *path;
audiohd_widget_t *wsum, *wadc, *w;
audiohd_pin_t *pin;
uint_t gain;
wid_t wid, next;
int i, j;
int weight;
for (i = 0; i < codec->statep->pathnum; i++) {
path = codec->statep->path[i];
if (path == NULL || path->path_type != RECORD ||
path->codec != codec)
continue;
wid = path->adda_wid;
wadc = path->codec->widget[wid];
weight = wadc->in_weight;
w = wadc;
while (w) {
if (w->outamp_cap & AUDIOHDC_AMP_CAP_MUTE_CAP) {
path->mute_wid = w->wid_wid;
path->mute_dir = AUDIOHDC_AMP_SET_OUTPUT;
break;
}
if ((w->inamp_cap & AUDIOHDC_AMP_CAP_MUTE_CAP) &&
(w->wid_wid != path->sum_wid)) {
path->mute_wid = w->wid_wid;
path->mute_dir = AUDIOHDC_AMP_SET_INPUT;
break;
}
next = w->input_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = w->avail_conn[next];
w = path->codec->widget[wid];
if (w && w->in_weight != weight)
break;
}
w = wadc;
gain = 0;
while (w) {
gain = (w->outamp_cap & AUDIOHDC_AMP_CAP_STEP_NUMS);
if (gain && gain > path->gain_bits) {
path->gain_dir = AUDIOHDC_AMP_SET_OUTPUT;
path->gain_bits = gain;
path->gain_wid = w->wid_wid;
}
gain = w->inamp_cap & AUDIOHDC_AMP_CAP_STEP_NUMS;
if (gain && (gain > path->gain_bits) &&
(w->wid_wid != path->sum_wid)) {
path->gain_dir = AUDIOHDC_AMP_SET_INPUT;
path->gain_bits = gain;
path->gain_wid = w->wid_wid;
}
next = w->input_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = w->avail_conn[next];
w = path->codec->widget[wid];
}
path->gain_bits >>= AUDIOHD_GAIN_OFF;
if (path->pin_nums == 1) {
wid = path->pin_wid[0];
w = path->codec->widget[wid];
pin = (audiohd_pin_t *)w->priv;
pin->gain_dir = path->gain_dir;
pin->gain_bits = path->gain_bits;
pin->gain_wid = path->gain_wid;
pin->mute_wid = path->mute_wid;
pin->mute_dir = path->mute_dir;
continue;
}
wid = path->sum_wid;
wsum = path->codec->widget[wid];
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
w = path->codec->widget[wid];
pin = (audiohd_pin_t *)w->priv;
if (wsum->inamp_cap & AUDIOHDC_AMP_CAP_MUTE_CAP) {
pin->mute_wid = wsum->wid_wid;
pin->mute_dir = AUDIOHDC_AMP_SET_INPUT;
} else {
wid = wsum->avail_conn[path->sum_selconn[i]];
w = path->codec->widget[wid];
while (w) {
if (w->outamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
pin->mute_wid = w->wid_wid;
pin->mute_dir =
AUDIOHDC_AMP_SET_OUTPUT;
break;
}
if (w->inamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
pin->mute_wid = w->wid_wid;
pin->mute_dir =
AUDIOHDC_AMP_SET_INPUT;
break;
}
next = w->input_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = w->avail_conn[next];
w = path->codec->widget[wid];
}
}
gain = (wsum->inamp_cap & AUDIOHDC_AMP_CAP_STEP_NUMS);
wid = wsum->avail_conn[path->sum_selconn[i]];
w = path->codec->widget[wid];
while (w) {
gain = (w->outamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS);
if (gain && gain > pin->gain_bits) {
pin->gain_dir = AUDIOHDC_AMP_SET_OUTPUT;
pin->gain_bits = gain;
pin->gain_wid = w->wid_wid;
}
gain = w->inamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS;
if (gain && (gain > pin->gain_bits)) {
pin->gain_dir = AUDIOHDC_AMP_SET_INPUT;
pin->gain_bits = gain;
pin->gain_wid = w->wid_wid;
}
next = w->input_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = w->avail_conn[next];
w = path->codec->widget[wid];
}
pin->gain_bits >>= AUDIOHD_GAIN_OFF;
}
}
}
static void
audiohd_finish_input_path(hda_codec_t *codec)
{
audiohd_state_t *statep = codec->statep;
audiohd_path_t *path;
audiohd_widget_t *w, *wsum;
uint_t caddr = codec->index;
wid_t wid;
int i, j;
for (i = 0; i < codec->statep->pathnum; i++) {
path = codec->statep->path[i];
if (path == NULL || path->path_type != RECORD ||
path->codec != codec)
continue;
wid = path->adda_wid;
w = path->codec->widget[wid];
while (w && (w->wid_wid != path->sum_wid) &&
(w->type != WTYPE_PIN)) {
if ((w->type == WTYPE_AUDIO_SEL) && (w->nconns > 1))
(void) audioha_codec_verb_get(statep, caddr,
w->wid_wid, AUDIOHDC_VERB_SET_CONN_SEL,
w->input_path_next);
if (w->outamp_cap) {
(void) audioha_codec_4bit_verb_get(statep,
caddr,
w->wid_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
if (w->inamp_cap) {
(void) audioha_codec_4bit_verb_get(statep,
caddr,
w->wid_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT |
AUDIOHDC_GAIN_MAX |
(w->input_path_next <<
AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
wid = w->avail_conn[w->input_path_next];
w = path->codec->widget[wid];
}
if (w && w->type == WTYPE_PIN)
continue;
wid = path->sum_wid;
wsum = path->codec->widget[wid];
if (wsum == NULL)
continue;
if (wsum->outamp_cap) {
(void) audioha_codec_4bit_verb_get(statep,
caddr,
wsum->wid_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
for (j = 0; j < path->pin_nums; j++) {
if (wsum->inamp_cap) {
(void) audioha_codec_4bit_verb_get(statep,
caddr,
wsum->wid_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT |
AUDIOHDC_GAIN_MAX |
(path->sum_selconn[j] <<
AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
if (wsum->type == WTYPE_AUDIO_SEL) {
(void) audioha_codec_verb_get(statep, caddr,
wsum->wid_wid,
AUDIOHDC_VERB_SET_CONN_SEL,
path->sum_selconn[j]);
}
wid = wsum->avail_conn[path->sum_selconn[j]];
w = path->codec->widget[wid];
while (w && w->type != WTYPE_PIN) {
if ((w->type != WTYPE_AUDIO_MIX) &&
(w->nconns > 1))
(void) audioha_codec_verb_get(statep,
caddr, w->wid_wid,
AUDIOHDC_VERB_SET_CONN_SEL,
w->input_path_next);
if (w->outamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep,
caddr,
w->wid_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
if (w->inamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep,
caddr,
w->wid_wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT |
AUDIOHDC_GAIN_MAX |
(w->input_path_next <<
AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
wid = w->avail_conn[w->input_path_next];
w = path->codec->widget[wid];
}
}
}
}
static int
audiohd_find_inpin_for_monitor(hda_codec_t *codec, wid_t id, int mixer)
{
wid_t wid;
audiohd_widget_t *widget, *w;
audiohd_pin_t *pin;
int i, find = 0;
wid = id;
widget = codec->widget[wid];
if (widget == NULL)
return (uint32_t)(DDI_FAILURE);
if (widget->type == WTYPE_PIN) {
pin = (audiohd_pin_t *)widget->priv;
if (pin->no_phys_conn)
return (uint32_t)(DDI_FAILURE);
switch (pin->device) {
case DTYPE_SPDIF_IN:
case DTYPE_CD:
case DTYPE_LINE_IN:
case DTYPE_MIC_IN:
case DTYPE_AUX:
widget->path_flags |= AUDIOHD_PATH_MON;
return (DDI_SUCCESS);
default:
return (uint32_t)(DDI_FAILURE);
}
}
if (widget->path_flags & AUDIOHD_PATH_NOMON) {
return (uint32_t)(DDI_FAILURE);
}
if (widget->path_flags & AUDIOHD_PATH_MON) {
if (mixer)
return (DDI_SUCCESS);
else
return (uint32_t)(DDI_FAILURE);
}
switch (widget->type) {
case WTYPE_AUDIO_MIX:
for (i = 0; i < widget->nconns; i++) {
if (widget->output_path_next == i)
continue;
if (audiohd_find_inpin_for_monitor(codec,
widget->avail_conn[i], mixer) ==
DDI_SUCCESS) {
w = widget;
w->monitor_path_next[w->used++] = i;
w->path_flags |= AUDIOHD_PATH_MON;
find = 1;
}
}
break;
case WTYPE_AUDIO_SEL:
for (i = 0; i < widget->nconns; i++) {
if (widget->output_path_next == i)
continue;
if (audiohd_find_inpin_for_monitor(codec,
widget->avail_conn[i], mixer) ==
DDI_SUCCESS) {
widget->monitor_path_next[0] = i;
widget->path_flags |= AUDIOHD_PATH_MON;
find = 1;
break;
}
}
break;
default:
break;
}
if (!find) {
widget->path_flags |= AUDIOHD_PATH_NOMON;
return (uint32_t)(DDI_FAILURE);
}
else
return (DDI_SUCCESS);
}
static void
audiohd_build_monitor_path(hda_codec_t *codec)
{
audiohd_path_t *path;
audiohd_widget_t *widget, *w;
audiohd_state_t *statep = codec->statep;
wid_t wid, next;
int i, j, k, l, find;
int mixernum = 0;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (path == NULL || path->codec != codec ||
path->path_type != PLAY)
continue;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
l = 0;
while (widget) {
while (widget &&
((widget->type != WTYPE_AUDIO_MIX) ||
(widget->nconns < 2))) {
next = widget->output_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
if (widget == NULL ||
widget->output_path_next ==
AUDIOHD_NULL_CONN) {
break;
}
mixernum++;
for (k = 0; k < widget->nconns; k++) {
if (widget->output_path_next == k)
continue;
find = 0;
if (audiohd_find_inpin_for_monitor(
codec,
widget->avail_conn[k], 0) ==
DDI_SUCCESS) {
path->mon_wid[j][l] = wid;
w = widget;
w->monitor_path_next[w->used++]
= k;
w->path_flags |=
AUDIOHD_PATH_MON;
find = 1;
} else if (
audiohd_find_inpin_for_monitor(
codec,
widget->avail_conn[k], 1) ==
DDI_SUCCESS) {
path->mon_wid[j][l] = wid;
w = widget;
w->monitor_path_next[w->used++]
= k;
w->path_flags |=
AUDIOHD_PATH_MON;
find = 1;
}
}
if (!find) {
path->mon_wid[j][l] = 0;
widget->path_flags |=
AUDIOHD_PATH_NOMON;
}
next = widget->output_path_next;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
l++;
}
path->maxmixer[j] = l;
}
}
if (mixernum == 0)
statep->monitor_supported = B_FALSE;
else
statep->monitor_supported = B_TRUE;
}
static void
audiohd_do_finish_monitor_path(hda_codec_t *codec, audiohd_widget_t *wgt)
{
uint_t caddr = codec->index;
audiohd_widget_t *widget = wgt;
audiohd_widget_t *w;
audiohd_state_t *statep = codec->statep;
wid_t wid;
int i;
int share = 0;
if (!widget || widget->finish)
return;
if (widget->path_flags & AUDIOHD_PATH_ADC)
share = 1;
if ((widget->outamp_cap) && !share)
(void) audioha_codec_4bit_verb_get(statep, caddr,
widget->wid_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT | AUDIOHDC_GAIN_MAX);
if ((widget->inamp_cap) && !share) {
for (i = 0; i < widget->used; i++) {
(void) audioha_codec_4bit_verb_get(statep, caddr,
widget->wid_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT | AUDIOHDC_GAIN_MAX |
(widget->monitor_path_next[i]
<< AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
}
if ((widget->type == WTYPE_AUDIO_SEL) && (widget->nconns > 1) &&
!share) {
(void) audioha_codec_verb_get(statep, caddr, widget->wid_wid,
AUDIOHDC_VERB_SET_CONN_SEL, widget->monitor_path_next[0]);
}
widget->finish = 1;
if (widget->used == 0)
return;
if (widget->used > 0) {
for (i = 0; i < widget->used; i++) {
wid = widget->avail_conn[widget->monitor_path_next[i]];
w = codec->widget[wid];
audiohd_do_finish_monitor_path(codec, w);
}
}
}
static void
audiohd_finish_monitor_path(hda_codec_t *codec)
{
audiohd_path_t *path;
audiohd_widget_t *widget;
audiohd_state_t *statep = codec->statep;
wid_t wid;
int i, j, k;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path || path->codec != codec || path->path_type != PLAY)
continue;
for (j = 0; j < path->pin_nums; j++) {
for (k = 0; k < path->maxmixer[j]; k++) {
wid = path->mon_wid[j][k];
if (wid == 0) {
continue;
}
widget = codec->widget[wid];
audiohd_do_finish_monitor_path(codec, widget);
}
}
}
}
static void
audiohd_do_build_monitor_amp(hda_codec_t *codec, audiohd_pin_t *pin,
audiohd_widget_t *widget)
{
audiohd_widget_t *w = widget;
uint32_t gain;
int i;
wid_t wid;
if (!w ||
(w->type == WTYPE_PIN) ||
!w->used ||
(pin->num == AUDIOHD_MAX_CONN) ||
(w->path_flags & AUDIOHD_PATH_ADC))
return;
if (!(w->path_flags & AUDIOHD_PATH_DAC)) {
gain = w->outamp_cap & AUDIOHDC_AMP_CAP_STEP_NUMS;
if (gain) {
pin->mg_dir[pin->num] = AUDIOHDC_AMP_SET_OUTPUT;
pin->mg_gain[pin->num] = gain;
pin->mg_wid[pin->num] = w->wid_wid;
pin->mg_gain[pin->num] >>= AUDIOHD_GAIN_OFF;
pin->num++;
return;
}
gain = w->inamp_cap & AUDIOHDC_AMP_CAP_STEP_NUMS;
if (gain) {
pin->mg_dir[pin->num] = AUDIOHDC_AMP_SET_INPUT;
pin->mg_gain[pin->num] = gain;
pin->mg_wid[pin->num] = w->wid_wid;
pin->mg_gain[pin->num] >>= AUDIOHD_GAIN_OFF;
pin->num++;
return;
}
}
for (i = 0; i < w->used; i++) {
wid = w->avail_conn[w->monitor_path_next[i]];
audiohd_do_build_monitor_amp(codec, pin, codec->widget[wid]);
}
}
static void
audiohd_build_monitor_amp(hda_codec_t *codec)
{
audiohd_path_t *path;
audiohd_widget_t *widget, *w;
audiohd_state_t *statep = codec->statep;
audiohd_pin_t *pin;
wid_t wid, id;
int i, j, k;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path || path->codec != codec || path->path_type != PLAY)
continue;
for (j = 0; j < path->pin_nums; j++) {
id = path->pin_wid[j];
w = codec->widget[id];
pin = (audiohd_pin_t *)(w->priv);
for (k = 0; k < path->maxmixer[j]; k++) {
wid = path->mon_wid[j][k];
if (!wid)
continue;
widget = codec->widget[wid];
audiohd_do_build_monitor_amp(codec, pin,
widget);
}
}
}
}
static wid_t
audiohd_find_beep(hda_codec_t *codec, wid_t wid, int depth)
{
audiohd_widget_t *widget = codec->widget[wid];
wid_t wbeep = (uint32_t)(DDI_FAILURE);
wid_t retval;
if (depth > AUDIOHD_MAX_DEPTH)
return (uint32_t)(DDI_FAILURE);
if (widget == NULL)
return (uint32_t)(DDI_FAILURE);
switch (widget->type) {
case WTYPE_BEEP:
widget->path_flags |= AUDIOHD_PATH_BEEP;
wbeep = widget->wid_wid;
break;
case WTYPE_AUDIO_MIX:
case WTYPE_AUDIO_SEL:
for (int i = 0; i < widget->nconns; i++) {
retval = audiohd_find_beep(codec,
widget->avail_conn[i], depth + 1);
if (retval == DDI_SUCCESS) {
if (widget->output_path_next !=
AUDIOHD_NULL_CONN)
continue;
widget->beep_path_next = i;
wbeep = retval;
widget->path_flags |= AUDIOHD_PATH_BEEP;
return (wbeep);
}
}
break;
default:
break;
}
return (wbeep);
}
static void
audiohd_build_beep_path(hda_codec_t *codec)
{
audiohd_pin_t *pin;
audiohd_widget_t *widget;
audiohd_path_t *path;
wid_t wid;
audiohd_state_t *statep;
int i;
boolean_t beeppath = B_FALSE;
statep = codec->statep;
for (pin = codec->first_pin; pin; pin = pin->next) {
if ((pin->cap & AUDIOHD_PIN_CAP_MASK) == 0)
continue;
if ((pin->config & AUDIOHD_PIN_CONF_MASK) ==
AUDIOHD_PIN_NO_CONN)
continue;
if ((pin->device != DTYPE_LINEOUT) &&
(pin->device != DTYPE_SPEAKER) &&
(pin->device != DTYPE_SPDIF_OUT) &&
(pin->device != DTYPE_HP_OUT))
continue;
widget = codec->widget[pin->wid];
widget->inamp_cap = 0;
for (i = 0; i < widget->nconns; i++) {
wid = audiohd_find_beep(codec,
widget->avail_conn[i], 0);
if (wid == (wid_t)DDI_FAILURE)
continue;
if (widget->output_path_next != AUDIOHD_NULL_CONN)
continue;
path = (audiohd_path_t *)
kmem_zalloc(sizeof (audiohd_path_t),
KM_SLEEP);
path->beep_wid = wid;
path->pin_wid[0] = widget->wid_wid;
path->pin_nums = 1;
path->path_type = BEEP;
beeppath = 1;
path->codec = codec;
path->statep = statep;
widget->path_flags |= AUDIOHD_PATH_BEEP;
widget->beep_path_next = i;
statep->path[statep->pathnum++] = path;
break;
}
}
if (!beeppath) {
for (int i = 0; i < AUDIOHD_CODEC_MAX; i++) {
codec = statep->codec[i];
if (codec == NULL)
continue;
for (wid = codec->first_wid; wid <= codec->last_wid;
wid++) {
widget = codec->widget[wid];
if (widget->type == WTYPE_BEEP) {
path = (audiohd_path_t *)
kmem_zalloc(sizeof (audiohd_path_t),
KM_SLEEP);
path->beep_wid = wid;
path->pin_nums = 0;
path->path_type = BEEP;
beeppath = 1;
path->codec = codec;
path->statep = statep;
widget->path_flags |= AUDIOHD_PATH_BEEP;
statep->path[statep->pathnum++] = path;
break;
}
}
}
}
}
static void
audiohd_build_beep_amp(hda_codec_t *codec)
{
audiohd_path_t *path;
audiohd_widget_t *widget, *wpin, *wbeep;
wid_t wid, next;
int i, j;
uint32_t gain;
for (i = 0; i < codec->statep->pathnum; i++) {
path = codec->statep->path[i];
if (path == NULL || path->path_type != BEEP ||
path->codec != codec)
continue;
if (path->pin_nums == 0) {
path->mute_wid = path->beep_wid;
path->mute_dir = AUDIOHDC_AMP_SET_OUTPUT;
wbeep = codec->widget[path->beep_wid];
gain = (wbeep->outamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS);
if (gain) {
path->gain_dir = AUDIOHDC_AMP_SET_OUTPUT;
path->gain_bits = gain;
path->gain_wid = path->beep_wid;
}
path->gain_bits >>= AUDIOHD_GAIN_OFF;
break;
}
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
wpin = codec->widget[wid];
wbeep = codec->widget[path->beep_wid];
widget = wpin;
while (widget) {
if (widget->out_weight == 0 &&
widget->outamp_cap &
AUDIOHDC_AMP_CAP_MUTE_CAP) {
path->mute_wid = widget->wid_wid;
path->mute_dir =
AUDIOHDC_AMP_SET_OUTPUT;
break;
}
next = widget->beep_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
gain = 0;
widget = wpin;
while (widget) {
if (widget->out_weight == 0 &&
widget->outamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS) {
gain = (widget->outamp_cap &
AUDIOHDC_AMP_CAP_STEP_NUMS);
if (gain && gain > path->gain_bits) {
path->gain_dir =
AUDIOHDC_AMP_SET_OUTPUT;
path->gain_bits = gain;
path->gain_wid =
widget->wid_wid;
}
}
next = widget->beep_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
path->gain_bits >>= AUDIOHD_GAIN_OFF;
}
}
}
static void
audiohd_finish_beep_path(hda_codec_t *codec)
{
audiohd_state_t *statep = codec->statep;
audiohd_path_t *path;
audiohd_widget_t *widget;
uint_t caddr = codec->index;
wid_t wid, next;
int i, j;
for (i = 0; i < codec->statep->pathnum; i++) {
path = codec->statep->path[i];
if (!path || path->path_type != BEEP || path->codec != codec)
continue;
if (path->pin_nums == 0) {
widget = codec->widget[path->beep_wid];
if (widget->outamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep, caddr,
path->beep_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
if (widget->inamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep, caddr,
path->beep_wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT |
AUDIOHDC_GAIN_MAX |
(widget->beep_path_next <<
AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
continue;
}
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
(void) audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_CONN_SEL, widget->beep_path_next);
wid = widget->avail_conn[widget->beep_path_next];
widget = codec->widget[wid];
while (widget) {
if (widget->out_weight != 0)
continue;
if (widget->outamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep, caddr,
wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_OUTPUT |
AUDIOHDC_GAIN_MAX);
}
if (widget->inamp_cap) {
(void) audioha_codec_4bit_verb_get(
statep, caddr,
wid, AUDIOHDC_VERB_SET_AMP_MUTE,
AUDIOHDC_AMP_SET_LR_INPUT |
AUDIOHDC_GAIN_MAX |
(widget->beep_path_next <<
AUDIOHDC_AMP_SET_INDEX_OFFSET));
}
next = widget->beep_path_next;
if (next == AUDIOHD_NULL_CONN)
break;
if ((widget->type != WTYPE_AUDIO_MIX) &&
(widget->nconns > 1))
(void) audioha_codec_verb_get(statep,
caddr, wid,
AUDIOHDC_VERB_SET_CONN_SEL,
widget->beep_path_next);
wid = widget->avail_conn[next];
widget = codec->widget[wid];
}
}
}
}
static int
audiohd_find_output_pins(hda_codec_t *codec, wid_t wid, int depth,
audiohd_path_t *path)
{
audiohd_widget_t *widget = codec->widget[wid];
audiohd_pin_t *pin = NULL;
int num, retval = (DDI_FAILURE);
if (depth > AUDIOHD_MAX_DEPTH)
return (retval);
if (widget == NULL)
return (retval);
switch (widget->type) {
case WTYPE_PIN:
pin = (audiohd_pin_t *)widget->priv;
if (pin->no_phys_conn)
return (DDI_FAILURE);
switch (pin->device) {
case DTYPE_LINE_IN:
path->pin_wid[path->pin_nums++] = wid;
break;
case DTYPE_LINEOUT:
case DTYPE_HP_OUT:
case DTYPE_SPDIF_OUT:
widget->path_flags |= AUDIOHD_PATH_LOOPBACK;
widget->in_weight++;
pin->adc_wid = path->adda_wid;
path->pin_wid[path->pin_nums++] = wid;
retval = (DDI_SUCCESS);
break;
default:
break;
}
break;
case WTYPE_AUDIO_MIX:
case WTYPE_AUDIO_SEL:
if (widget->nconns == 1) {
widget->loopback_path_next = 0;
retval = audiohd_find_output_pins(codec,
widget->avail_conn[0], depth + 1, path);
if (retval == (DDI_SUCCESS)) {
widget->path_flags |= AUDIOHD_PATH_LOOPBACK;
widget->in_weight++;
}
break;
}
for (int i = 0; i < widget->nconns; i++) {
retval = audiohd_find_output_pins(codec,
widget->avail_conn[i], depth + 1, path);
if (retval == (DDI_SUCCESS)) {
widget->loopback_path_next = i;
widget->in_weight++;
num = path->pin_nums - 1;
path->sum_selconn[num] = i;
path->sum_wid = wid;
widget->path_flags |= AUDIOHD_PATH_LOOPBACK;
break;
}
}
break;
default:
break;
}
return (retval);
}
static void
audiohd_build_loopback_path(hda_codec_t *codec)
{
audiohd_state_t *statep = codec->statep;
audiohd_widget_t *widget;
audiohd_path_t *path = NULL;
wid_t wid;
int i, retval;
uint8_t rtag = 0;
for (wid = codec->first_wid; wid <= codec->last_wid; wid++) {
widget = codec->widget[wid];
if (widget == NULL || widget->type != WTYPE_AUDIO_IN)
continue;
if (path == NULL)
path = kmem_zalloc(sizeof (audiohd_path_t), KM_SLEEP);
else
bzero(path, sizeof (audiohd_port_t));
path->adda_wid = wid;
for (i = 0; i < widget->nconns; i++) {
retval = audiohd_find_output_pins(codec,
widget->avail_conn[i], 0, path);
if (retval == (DDI_SUCCESS)) {
path->codec = codec;
path->statep = statep;
path->path_type = LOOPBACK;
path->tag = ++rtag;
codec->nistream++;
statep->path[statep->pathnum++] = path;
widget->loopback_path_next = i;
widget->priv = path;
path = NULL;
statep->loopback_supported = B_TRUE;
break;
}
}
}
if (path)
kmem_free(path, sizeof (audiohd_path_t));
}
static void
audiohd_build_path(audiohd_state_t *statep)
{
int i;
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
if (statep->codec[i]) {
audiohd_build_output_path(statep->codec[i]);
audiohd_build_output_amp(statep->codec[i]);
audiohd_finish_output_path(statep->codec[i]);
audiohd_build_input_path(statep->codec[i]);
audiohd_build_input_amp(statep->codec[i]);
audiohd_finish_input_path(statep->codec[i]);
audiohd_build_monitor_path(statep->codec[i]);
audiohd_build_monitor_amp(statep->codec[i]);
audiohd_finish_monitor_path(statep->codec[i]);
audiohd_build_beep_path(statep->codec[i]);
audiohd_build_beep_amp(statep->codec[i]);
audiohd_finish_beep_path(statep->codec[i]);
audiohd_build_loopback_path(statep->codec[i]);
}
}
}
static int
audiohd_allocate_port(audiohd_state_t *statep)
{
int i, j;
audiohd_port_t *port;
int dir;
unsigned caps;
int rc;
audio_dev_t *adev;
dev_info_t *dip;
ddi_dma_cookie_t cookie;
uint_t count;
uint64_t buf_phys_addr;
sd_bdle_t *entry;
uint16_t gcap;
size_t real_size;
adev = statep->adev;
dip = statep->hda_dip;
ddi_dma_attr_t dma_attr = {
DMA_ATTR_V0,
0,
0xffffffffffffffffULL,
0x00000000ffffffffULL,
128,
0xfff,
1,
0xffffffff,
0xffffffff,
1,
1,
0
};
gcap = AUDIOHD_REG_GET16(AUDIOHD_REG_GCAP);
if ((gcap & AUDIOHDR_GCAP_64OK) == 0)
dma_attr.dma_attr_addr_hi = 0xffffffffUL;
for (i = 0; i < PORT_MAX; i++) {
port = kmem_zalloc(sizeof (*port), KM_SLEEP);
statep->port[i] = port;
port->statep = statep;
switch (i) {
case PORT_ADC:
dir = DDI_DMA_READ | DDI_DMA_CONSISTENT;
caps = ENGINE_INPUT_CAP;
port->sync_dir = DDI_DMA_SYNC_FORKERNEL;
port->nchan = statep->rchan;
port->index = 1;
port->regoff = AUDIOHD_REG_SD_BASE;
break;
case PORT_DAC:
dir = DDI_DMA_WRITE | DDI_DMA_CONSISTENT;
caps = ENGINE_OUTPUT_CAP;
port->sync_dir = DDI_DMA_SYNC_FORDEV;
port->nchan = statep->pchan;
port->index = statep->hda_input_streams + 1;
port->regoff = AUDIOHD_REG_SD_BASE +
AUDIOHD_REG_SD_LEN *
statep->hda_input_streams;
break;
default:
return (DDI_FAILURE);
}
switch (statep->sample_rate) {
case 192000:
port->format = 0x18 << 4;
break;
case 96000:
port->format = 0x08 << 4;
break;
case 48000:
default:
port->format = 0x00;
break;
}
switch (statep->sample_bit_depth) {
case AUDIOHD_BIT_DEPTH24:
port->format |= 0x3;
statep->sample_packed_bytes = 4;
break;
case AUDIOHD_BIT_DEPTH16:
default:
port->format |= 0x1;
statep->sample_packed_bytes = 2;
break;
}
port->nframes = 1024 * AUDIOHD_BDLE_NUMS *
statep->sample_rate / 48000;
port->fragsize = 1024 * port->nchan *
statep->sample_packed_bytes *
statep->sample_rate / 48000;
port->bufsize = port->nframes * port->nchan *
statep->sample_packed_bytes;
rc = ddi_dma_alloc_handle(dip, &dma_attr, DDI_DMA_SLEEP,
NULL, &port->samp_dmah);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev, "ddi_dma_alloc_handle failed: %d",
rc);
return (DDI_FAILURE);
}
rc = ddi_dma_mem_alloc(port->samp_dmah, port->bufsize,
&hda_dev_accattr, DDI_DMA_CONSISTENT | IOMEM_DATA_UNCACHED,
DDI_DMA_SLEEP, NULL, &port->samp_kaddr,
&real_size, &port->samp_acch);
if (rc == DDI_FAILURE) {
if (ddi_dma_mem_alloc(port->samp_dmah, port->bufsize,
&hda_dev_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, NULL,
&port->samp_kaddr, &real_size,
&port->samp_acch) != DDI_SUCCESS) {
audio_dev_warn(adev,
"ddi_dma_mem_alloc failed");
return (DDI_FAILURE);
}
}
rc = ddi_dma_addr_bind_handle(port->samp_dmah, NULL,
port->samp_kaddr, real_size, dir,
DDI_DMA_SLEEP, NULL, &cookie, &count);
if ((rc != DDI_DMA_MAPPED) || (count != 1)) {
audio_dev_warn(adev,
"ddi_dma_addr_bind_handle failed: %d", rc);
return (DDI_FAILURE);
}
port->samp_paddr = (uint64_t)cookie.dmac_laddress;
rc = ddi_dma_alloc_handle(dip, &dma_attr, DDI_DMA_SLEEP,
NULL, &port->bdl_dmah);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev,
"ddi_dma_alloc_handle(bdlist) failed");
return (DDI_FAILURE);
}
port->bdl_size = sizeof (sd_bdle_t) * AUDIOHD_BDLE_NUMS;
rc = ddi_dma_mem_alloc(port->bdl_dmah, port->bdl_size,
&hda_dev_accattr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&port->bdl_kaddr, &real_size, &port->bdl_acch);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev,
"ddi_dma_mem_alloc(bdlist) failed");
return (DDI_FAILURE);
}
rc = ddi_dma_addr_bind_handle(port->bdl_dmah, NULL,
port->bdl_kaddr,
real_size, DDI_DMA_WRITE | DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP,
NULL, &cookie, &count);
if ((rc != DDI_DMA_MAPPED) || (count != 1)) {
audio_dev_warn(adev, "addr_bind_handle failed");
return (DDI_FAILURE);
}
port->bdl_paddr = (uint64_t)cookie.dmac_laddress;
entry = (sd_bdle_t *)port->bdl_kaddr;
buf_phys_addr = port->samp_paddr;
for (j = 0; j < AUDIOHD_BDLE_NUMS; j++) {
entry->sbde_addr = buf_phys_addr;
entry->sbde_len = port->fragsize;
entry->sbde_ioc = 1;
buf_phys_addr += port->fragsize;
entry++;
}
(void) ddi_dma_sync(port->bdl_dmah, 0, sizeof (sd_bdle_t) *
AUDIOHD_BDLE_NUMS, DDI_DMA_SYNC_FORDEV);
port->curpos = 0;
port->engine = audio_engine_alloc(&audiohd_engine_ops, caps);
if (port->engine == NULL) {
return (DDI_FAILURE);
}
audio_engine_set_private(port->engine, port);
audio_dev_add_engine(adev, port->engine);
}
return (DDI_SUCCESS);
}
static void
audiohd_free_port(audiohd_state_t *statep)
{
int i;
audiohd_port_t *port;
for (i = 0; i < PORT_MAX; i++) {
port = statep->port[i];
if (port == NULL)
continue;
if (port->engine) {
audio_dev_remove_engine(statep->adev,
port->engine);
audio_engine_free(port->engine);
}
if (port->samp_dmah) {
(void) ddi_dma_unbind_handle(port->samp_dmah);
}
if (port->samp_acch) {
ddi_dma_mem_free(&port->samp_acch);
}
if (port->samp_dmah) {
ddi_dma_free_handle(&port->samp_dmah);
}
if (port->bdl_dmah) {
(void) ddi_dma_unbind_handle(port->bdl_dmah);
}
if (port->bdl_acch) {
ddi_dma_mem_free(&port->bdl_acch);
}
if (port->bdl_dmah) {
ddi_dma_free_handle(&port->bdl_dmah);
}
kmem_free(port, sizeof (audiohd_port_t));
}
}
static void
audiohd_change_widget_power_state(audiohd_state_t *statep, int state)
{
int i;
wid_t wid;
hda_codec_t *codec;
audiohd_widget_t *widget;
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
codec = statep->codec[i];
if (codec == NULL)
continue;
for (wid = codec->first_wid; wid <= codec->last_wid;
wid++) {
widget = codec->widget[wid];
if (widget->widget_cap &
AUDIOHD_WIDCAP_PWRCTRL) {
(void) audioha_codec_verb_get(statep,
codec->index, wid,
AUDIOHDC_VERB_SET_POWER_STATE,
state);
}
}
}
}
static void
audiohd_restore_path(audiohd_state_t *statep)
{
int i;
hda_codec_t *codec;
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
codec = statep->codec[i];
if (codec == NULL)
continue;
audiohd_finish_output_path(statep->codec[i]);
audiohd_finish_input_path(statep->codec[i]);
audiohd_finish_monitor_path(statep->codec[i]);
audiohd_finish_beep_path(statep->codec[i]);
}
}
static void
audiohd_reset_pins_ur_cap(audiohd_state_t *statep)
{
hda_codec_t *codec;
audiohd_pin_t *pin;
audiohd_widget_t *widget;
uint32_t urctrl;
int i;
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
codec = statep->codec[i];
if (codec == NULL)
continue;
pin = codec->first_pin;
while (pin) {
widget = codec->widget[pin->wid];
if ((widget->widget_cap &
(AUDIOHD_URCAP_MASK) &&
(pin->cap & AUDIOHD_DTCCAP_MASK)) &&
((pin->device == DTYPE_LINEOUT) ||
(pin->device == DTYPE_SPDIF_OUT) ||
(pin->device == DTYPE_HP_OUT) ||
(pin->device == DTYPE_MIC_IN))) {
urctrl = (uint8_t)(1 <<
(AUDIOHD_UR_ENABLE_OFF - 1));
urctrl |= (pin->wid & AUDIOHD_UR_TAG_MASK);
(void) audioha_codec_verb_get(statep,
codec->index,
pin->wid,
AUDIOHDC_VERB_SET_UNS_ENABLE, urctrl);
}
pin = pin->next;
}
}
}
static void
audiohd_restore_codec_gpio(audiohd_state_t *statep)
{
int i;
wid_t wid;
hda_codec_t *codec;
for (i = 0; i < AUDIOHD_CODEC_MAX; i++) {
codec = statep->codec[i];
if (codec == NULL)
continue;
wid = codec->wid_afg;
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_POWER_STATE, AUDIOHD_PW_D0);
if ((codec->codec_info->flags & NO_GPIO) == 0) {
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_MASK, AUDIOHDC_GPIO_ENABLE);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_DIREC, AUDIOHDC_GPIO_DIRECT);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_STCK,
AUDIOHDC_GPIO_DATA_CTRL);
(void) audioha_codec_verb_get(statep, i, wid,
AUDIOHDC_VERB_SET_GPIO_DATA,
AUDIOHDC_GPIO_STCK_CTRL);
}
}
}
static int
audiohd_resume(audiohd_state_t *statep)
{
uint8_t rirbsts;
mutex_enter(&statep->hda_mutex);
statep->suspended = B_FALSE;
if (audiohd_reinit_hda(statep) == DDI_FAILURE) {
audio_dev_warn(statep->adev,
"hda reinit failed");
mutex_exit(&statep->hda_mutex);
return (DDI_FAILURE);
}
audiohd_reset_pins_ur_cap(statep);
rirbsts = AUDIOHD_REG_GET8(AUDIOHD_REG_RIRBSTS);
AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBSTS, rirbsts);
audiohd_change_widget_power_state(statep, AUDIOHD_PW_D0);
audiohd_configure_output(statep);
audiohd_configure_input(statep);
mutex_exit(&statep->hda_mutex);
audio_dev_resume(statep->adev);
return (DDI_SUCCESS);
}
static int
audiohd_suspend(audiohd_state_t *statep)
{
audio_dev_suspend(statep->adev);
mutex_enter(&statep->hda_mutex);
statep->suspended = B_TRUE;
audiohd_change_widget_power_state(statep, AUDIOHD_PW_D2);
audiohd_stop_dma(statep);
audiohd_fini_pci(statep);
mutex_exit(&statep->hda_mutex);
return (DDI_SUCCESS);
}
static void
audiohd_disable_pin(audiohd_state_t *statep, int caddr, wid_t wid)
{
uint32_t tmp;
tmp = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PIN_CTRL, 0);
if (tmp == AUDIOHD_CODEC_FAILURE)
return;
tmp = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_PIN_CTRL,
(tmp & ~AUDIOHDC_PIN_CONTROL_OUT_ENABLE));
}
static void
audiohd_enable_pin(audiohd_state_t *statep, int caddr, wid_t wid)
{
uint32_t tmp;
tmp = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_GET_PIN_CTRL, 0);
if (tmp == AUDIOHD_CODEC_FAILURE)
return;
tmp = audioha_codec_verb_get(statep, caddr, wid,
AUDIOHDC_VERB_SET_PIN_CTRL,
tmp | AUDIOHDC_PIN_CONTROL_OUT_ENABLE |
AUDIOHDC_PIN_CONTROL_HP_ENABLE);
}
static void
audiohd_change_speaker_state(audiohd_state_t *statep, int on)
{
audiohd_path_t *path;
audiohd_widget_t *widget;
audiohd_pin_t *pin;
int i, j;
wid_t wid;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path || path->path_type != PLAY)
continue;
if (on) {
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = path->codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if (pin->device == DTYPE_SPEAKER) {
audiohd_enable_pin(statep,
path->codec->index,
pin->wid);
}
}
} else {
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = path->codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if (pin->device == DTYPE_SPEAKER) {
audiohd_disable_pin(statep,
path->codec->index,
pin->wid);
}
}
}
}
}
static void
audiohd_select_mic(audiohd_state_t *statep, uint8_t index,
uint8_t id, int select)
{
hda_codec_t *codec;
audiohd_path_t *path;
audiohd_widget_t *widget, *sumwgt = NULL;
audiohd_pin_t *pin;
int i, j;
wid_t wid;
codec = statep->codec[index];
if (codec == NULL)
return;
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (path->codec != codec || path->path_type != RECORD)
continue;
sumwgt = codec->widget[path->sum_wid];
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
pin = (audiohd_pin_t *)widget->priv;
if (pin->device != DTYPE_MIC_IN)
continue;
if (sumwgt != NULL &&
sumwgt->type == WTYPE_AUDIO_SEL) {
if (select && pin->wid == id &&
(((pin->config >>
AUDIOHD_PIN_CONTP_OFF) &
AUDIOHD_PIN_CONTP_MASK) ==
AUDIOHD_PIN_CON_JACK)) {
(void) audioha_codec_verb_get(
statep,
index,
path->sum_wid,
AUDIOHDC_VERB_SET_CONN_SEL,
path->sum_selconn[j]);
statep->port[PORT_ADC]->index =
path->tag;
return;
} else if (!select && pin->wid != id &&
(((pin->config >>
AUDIOHD_PIN_CONTP_OFF) &
AUDIOHD_PIN_CONTP_MASK) ==
AUDIOHD_PIN_CON_FIXED)) {
(void) audioha_codec_verb_get(
statep,
index,
path->sum_wid,
AUDIOHDC_VERB_SET_CONN_SEL,
path->sum_selconn[j]);
statep->port[PORT_ADC]->index =
path->tag;
return;
}
} else {
audiohd_set_pin_volume(statep, DTYPE_MIC_IN);
if (select == 1) {
if (wid != id) {
(void)
audioha_codec_4bit_verb_get(
statep, path->codec->index,
wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_LNR |
AUDIOHDC_AMP_SET_MUTE);
}
} else {
if (wid == id) {
(void)
audioha_codec_4bit_verb_get(
statep, path->codec->index,
wid,
AUDIOHDC_VERB_SET_AMP_MUTE,
path->mute_dir |
AUDIOHDC_AMP_SET_LNR |
AUDIOHDC_AMP_SET_MUTE);
}
}
}
}
}
if (codec->nistream > 1) {
for (i = 0; i < statep->pathnum; i++) {
path = statep->path[i];
if (!path || path->path_type != RECORD)
continue;
for (j = 0; j < path->pin_nums; j++) {
wid = path->pin_wid[j];
widget = codec->widget[wid];
if (widget == NULL)
return;
pin = (audiohd_pin_t *)widget->priv;
if (select &&
pin->device == DTYPE_MIC_IN &&
pin->wid == id &&
(((pin->config >>
AUDIOHD_PIN_CONTP_OFF) &
AUDIOHD_PIN_CONTP_MASK) ==
AUDIOHD_PIN_CON_JACK)) {
statep->port[PORT_ADC]->index =
path->tag;
return;
} else if (!select &&
pin->device == DTYPE_MIC_IN &&
(((pin->config >>
AUDIOHD_PIN_CONTP_OFF) &
AUDIOHD_PIN_CONTP_MASK) ==
AUDIOHD_PIN_CON_FIXED)) {
statep->port[PORT_ADC]->index =
path->tag;
return;
}
}
}
}
}
static void
audiohd_pin_sense(audiohd_state_t *statep, uint32_t resp, uint32_t respex)
{
uint8_t index;
uint8_t id;
uint32_t rs;
audiohd_widget_t *widget;
audiohd_pin_t *pin;
hda_codec_t *codec;
index = respex & AUDIOHD_RIRB_CODEC_MASK;
id = resp >> (AUDIOHD_RIRB_WID_OFF - 1);
codec = statep->codec[index];
if (codec == NULL)
return;
widget = codec->widget[id];
if (widget == NULL)
return;
rs = audioha_codec_verb_get(statep, index, id,
AUDIOHDC_VERB_GET_PIN_SENSE, 0);
if (rs & AUDIOHD_PIN_PRES_MASK) {
if ((widget->type == WTYPE_PIN) &&
(pin = (audiohd_pin_t *)widget->priv) &&
(pin->device == DTYPE_MIC_IN)) {
audiohd_select_mic(statep, index, id, 1);
return;
}
audiohd_change_speaker_state(statep, AUDIOHD_SP_OFF);
} else {
if ((widget->type == WTYPE_PIN) &&
(pin = (audiohd_pin_t *)widget->priv) &&
(pin->device == DTYPE_MIC_IN)) {
audiohd_select_mic(statep, index, id, 0);
return;
}
audiohd_change_speaker_state(statep, AUDIOHD_SP_ON);
}
}
static void
audiohd_disable_intr(audiohd_state_t *statep)
{
int i;
uint32_t base;
AUDIOHD_REG_SET32(AUDIOHD_REG_INTCTL, 0);
base = AUDIOHD_REG_SD_BASE;
for (i = 0; i < statep->hda_streams_nums; i++) {
AUDIOHD_REG_SET8(base + AUDIOHD_SDREG_OFFSET_STS,
AUDIOHDR_SD_STS_INTRS);
base += AUDIOHD_REG_SD_LEN;
}
AUDIOHD_REG_SET32(AUDIOHD_REG_INTSTS, (uint32_t)(-1));
}
static int
audiohd_12bit_verb_to_codec(audiohd_state_t *statep, uint8_t caddr,
uint8_t wid,
uint16_t cmd, uint8_t param)
{
uint32_t verb;
uint16_t wptr;
uint16_t rptr;
ASSERT((cmd & AUDIOHDC_12BIT_VERB_MASK) == 0);
wptr = AUDIOHD_REG_GET16(AUDIOHD_REG_CORBWP) & AUDIOHD_CMDIO_ENT_MASK;
rptr = AUDIOHD_REG_GET16(AUDIOHD_REG_CORBRP) & AUDIOHD_CMDIO_ENT_MASK;
wptr++;
wptr &= AUDIOHD_CMDIO_ENT_MASK;
if (wptr == rptr) {
return (DDI_FAILURE);
}
verb = (caddr & 0x0f) << AUDIOHD_VERB_ADDR_OFF;
verb |= wid << AUDIOHD_VERB_NID_OFF;
verb |= cmd << AUDIOHD_VERB_CMD_OFF;
verb |= param;
*((uint32_t *)(statep->hda_dma_corb.ad_vaddr) + wptr) = verb;
(void) ddi_dma_sync(statep->hda_dma_corb.ad_dmahdl, 0,
sizeof (sd_bdle_t) * AUDIOHD_BDLE_NUMS, DDI_DMA_SYNC_FORDEV);
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBWP, wptr);
return (DDI_SUCCESS);
}
static int
audiohd_4bit_verb_to_codec(audiohd_state_t *statep, uint8_t caddr,
uint8_t wid,
uint32_t cmd, uint16_t param)
{
uint32_t verb;
uint16_t wptr;
uint16_t rptr;
ASSERT((cmd & AUDIOHDC_4BIT_VERB_MASK) == 0);
wptr = AUDIOHD_REG_GET16(AUDIOHD_REG_CORBWP) & AUDIOHD_CMDIO_ENT_MASK;
rptr = AUDIOHD_REG_GET16(AUDIOHD_REG_CORBRP) & AUDIOHD_CMDIO_ENT_MASK;
wptr++;
wptr &= AUDIOHD_CMDIO_ENT_MASK;
if (wptr == rptr) {
return (DDI_FAILURE);
}
verb = (caddr & 0x0f) << AUDIOHD_VERB_ADDR_OFF;
verb |= wid << AUDIOHD_VERB_NID_OFF;
verb |= cmd << AUDIOHD_VERB_CMD16_OFF;
verb |= param;
*((uint32_t *)(statep->hda_dma_corb.ad_vaddr) + wptr) = verb;
AUDIOHD_REG_SET16(AUDIOHD_REG_CORBWP, wptr);
return (DDI_SUCCESS);
}
static int
audiohd_response_from_codec(audiohd_state_t *statep, uint32_t *resp,
uint32_t *respex)
{
uint16_t wptr;
uint16_t rptr;
uint32_t *lp;
wptr = AUDIOHD_REG_GET16(AUDIOHD_REG_RIRBWP) & 0x00ff;
rptr = statep->hda_rirb_rp;
if (rptr == wptr) {
return (DDI_FAILURE);
}
rptr++;
rptr &= AUDIOHD_RING_MAX_SIZE;
lp = (uint32_t *)(statep->hda_dma_rirb.ad_vaddr) + (rptr << 1);
*resp = *(lp);
*respex = *(lp + 1);
statep->hda_rirb_rp = rptr;
return (DDI_SUCCESS);
}
static uint32_t
audioha_codec_verb_get(void *arg, uint8_t caddr, uint8_t wid,
uint16_t verb,
uint8_t param)
{
audiohd_state_t *statep = (audiohd_state_t *)arg;
uint32_t resp;
uint32_t respex;
int ret;
int i;
ret = audiohd_12bit_verb_to_codec(statep, caddr, wid, verb, param);
if (ret != DDI_SUCCESS) {
return (uint32_t)(-1);
}
for (i = 0; i < 500; i++) {
ret = audiohd_response_from_codec(statep, &resp, &respex);
if (((respex & AUDIOHD_BDLE_RIRB_SDI) == caddr) &&
((respex & AUDIOHD_BDLE_RIRB_UNSOLICIT) == 0) &&
(ret == DDI_SUCCESS))
break;
drv_usecwait(30);
}
if (ret == DDI_SUCCESS) {
return (resp);
}
if (wid != AUDIOHDC_NODE_ROOT && param != AUDIOHDC_PAR_VENDOR_ID) {
audio_dev_warn(statep->adev, "timeout when get "
"response from codec: wid=%d, verb=0x%04x, param=0x%04x",
wid, verb, param);
}
return ((uint32_t)(-1));
}
static uint32_t
audioha_codec_4bit_verb_get(void *arg, uint8_t caddr, uint8_t wid,
uint16_t verb, uint16_t param)
{
audiohd_state_t *statep = (audiohd_state_t *)arg;
uint32_t resp;
uint32_t respex;
int ret;
int i;
ret = audiohd_4bit_verb_to_codec(statep, caddr, wid, verb, param);
if (ret != DDI_SUCCESS) {
return (uint32_t)(-1);
}
for (i = 0; i < 500; i++) {
ret = audiohd_response_from_codec(statep, &resp, &respex);
if (((respex & AUDIOHD_BDLE_RIRB_SDI) == caddr) &&
((respex & AUDIOHD_BDLE_RIRB_UNSOLICIT) == 0) &&
(ret == DDI_SUCCESS))
break;
drv_usecwait(30);
}
if (ret == DDI_SUCCESS) {
return (resp);
}
audio_dev_warn(statep->adev, "timeout when get "
"response from codec: wid=%d, verb=0x%04x, param=0x%04x",
wid, verb, param);
return ((uint32_t)(-1));
}