/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include "audiohd.h" #define DRVNAME "audiohd" /* * Module linkage routines for the kernel */ 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 *); /* * Local routines */ 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; /* Warlock annotation */ _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, /* reserved port, don't use */ 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", 0x0}, {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; } /* set device information */ 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); } /* allocate the soft state structure */ 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); /* interrupt cookie and initialize mutex */ if (audiohd_init_state(statep, dip) != DDI_SUCCESS) { audio_dev_warn(NULL, "audiohd_init_state failed"); goto error; } /* Set PCI command register to enable bus master and memeory I/O */ 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); /* set up kernel statistics */ if ((statep->hda_ksp = kstat_create(DRVNAME, instance, DRVNAME, "controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT)) != NULL) { kstat_install(statep->hda_ksp); } /* disable interrupts and clear interrupt status */ audiohd_disable_intr(statep); /* * Register audio controls. */ 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, /* rev */ 0, /* refcnt */ NULL, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ audiohd_attach, /* attach */ audiohd_detach, /* detach */ nodev, /* reset */ NULL, /* cb_ops */ NULL, /* bus_ops */ NULL, /* power */ audiohd_quiesce, /* quiesce */ }; static struct modldrv audiohd_modldrv = { &mod_driverops, /* drv_modops */ "AudioHD", /* linkinfo */ &audiohd_dev_ops, /* 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)); } /* * Audio routines */ 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)); } /* * get the max channels the hardware supported */ 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; /* for record, support stereo so far */ 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; /* enable SPDIF output */ 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); /* * We find that on intel ICH10 chipset with codec * ALC888, audio is scratchy if we set the tag on the * SPDIF path. So we just return here without setting * the tag for the path as a workaround. */ if (codec->codec_info->flags & NO_SPDIF) return; } } wid = path->pin_wid[0]; widget = codec->widget[wid]; pin = (audiohd_pin_t *)widget->priv; /* two channels supported */ 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); /* multichannel supported */ } 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; /* * Since there is no SPDIF input device available for test, * we will use this code in the future to support SPDIF input */ #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); /* stop stream */ bTmp &= ~AUDIOHD_REG_RIRBSIZE; AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL, bTmp); /* wait 40us for stream to stop as HD spec */ drv_usecwait(40); /* reset stream */ bTmp |= AUDIOHDR_SD_CTL_SRST; AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL, bTmp); for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) { /* Empirical testing time, which works well */ 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); } /* Empirical testing time, which works well */ drv_usecwait(300); /* exit reset stream */ bTmp &= ~AUDIOHDR_SD_CTL_SRST; AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_CTL, bTmp); for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) { /* Empircal testing time */ 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); /* clear status */ AUDIOHD_REG_SET8(regbase + AUDIOHD_SDREG_OFFSET_STS, AUDIOHDR_SD_STS_BCIS | AUDIOHDR_SD_STS_FIFOE | AUDIOHDR_SD_STS_DESE); /* set stream tag */ 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); } /* Start DMA */ 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); /* Convert the position into a frame count */ 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; /* * Check unsolicited response from pins, maybe something plugged in or * out of the jack. */ status = AUDIOHD_REG_GET32(AUDIOHD_REG_INTSTS); if (status == 0) { /* No pending interrupt we should take care */ return; } if (status & AUDIOHD_CIS_MASK) { /* Clear the unsolicited response interrupt */ rirbsts = AUDIOHD_REG_GET8(AUDIOHD_REG_RIRBSTS); AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBSTS, rirbsts); /* * We have to wait and try several times to make sure the * unsolicited response is generated by our pins. * we need to make it work for audiohd spec 0.9, which is * just a draft version and requires more time to wait. */ 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)) { /* * A pin may generate more than one ur rirb, * we only need handle one of them, and clear * the other ones */ 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, /* version number */ 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 there is a real selector in this input path, * we select the right one input for the selector. */ 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: /* * Sigmatel HD codec specific operation. * There is a workaround, * Due to Sigmatel HD codec hardware problem, * which it can't mute beep when volume is 0. * So add global value audiohd_beep_vol, * Set freq to 0 when volume is 0. */ 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: /* Common operation based on audiohd spec */ 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) { /* Turn on loopback recording */ 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 { /* Turn off loopback recording */ 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; /* * We always use soft volume control to adjust PCM volume. */ audio_dev_add_soft_volume(statep->adev); /* Allocate other controls */ 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); } /* * quiesce(9E) entry point. * * This function is called when the system is single-threaded at high * PIL with preemption disabled. Therefore, this function must not be * blocked. * * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure. * DDI_FAILURE indicates an error condition and should almost never happen. */ 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: /* * Sigmatel HD codec specification: * frequency = 48000 * (257 - Divider) / 1024 */ 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); } /* * audiohd_init_state() * * Description * This routine initailizes soft state of driver instance, * also, it requests an interrupt cookie and initializes * mutex for soft state. */ /*ARGSUSED*/ 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; /* set device information */ audio_dev_set_description(adev, AUDIOHD_DEV_CONFIG); audio_dev_set_version(adev, AUDIOHD_DEV_VERSION); return (DDI_SUCCESS); } /* audiohd_init_state() */ /* * audiohd_init_pci() * * Description * enable driver to access PCI configure space and memory * I/O space. */ 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); } /* * HD audio control uses memory I/O only, enable it here. */ 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: /* * Currently, Intel (G)MCH and ICHx chipsets support PCI * Express QoS. It implemenets two VCs(virtual channels) * and allows OS software to map 8 traffic classes to the * two VCs. Some BIOSes initialize HD audio hardware to * use TC7 (traffic class 7) and to map TC7 to VC1 as Intel * recommended. However, solaris doesn't support PCI express * QoS yet. As a result, this driver can not work for those * hardware without touching PCI express control registers. * Here, we set TCSEL to 0 so as to use TC0/VC0 (VC0 is * always enabled and TC0 is always mapped to VC0) for all * Intel HD audio controllers. */ 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: /* * Refer to ATI SB450 datesheet. We set snoop for SB450 * like hardware. */ 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: /* * Refer to the datasheet, we set snoop for NVIDIA * like hardware */ 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); } /* audiohd_init_pci() */ /* * audiohd_fini_pci() * * Description * Release mapping for PCI configure space. */ 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); } } /* audiohd_fini_pci() */ /* * audiohd_stop_dma() * * Description * Stop all DMA behaviors of controllers, for command I/O * and each audio stream. */ 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); /* for input/output stream, it is the same */ bTmp &= ~AUDIOHDR_RIRBCTL_DMARUN; AUDIOHD_REG_SET8(base + AUDIOHD_SDREG_OFFSET_CTL, bTmp); base += AUDIOHD_REG_SD_LEN; } /* wait 40us for stream DMA to stop */ drv_usecwait(40); } /* audiohd_stop_dma() */ /* * audiohd_reset_controller() * * Description: * This routine is just used to reset controller and * CODEC as well by HW reset bit in global control * register of HD controller. */ static int audiohd_reset_controller(audiohd_state_t *statep) { int i; uint16_t sTmp; uint32_t gctl; /* Reset Status register but preserve the first bit */ sTmp = AUDIOHD_REG_GET16(AUDIOHD_REG_STATESTS); AUDIOHD_REG_SET16(AUDIOHD_REG_STATESTS, sTmp & 0x8000); /* reset controller */ gctl = AUDIOHD_REG_GET32(AUDIOHD_REG_GCTL); gctl &= ~AUDIOHDR_GCTL_CRST; AUDIOHD_REG_SET32(AUDIOHD_REG_GCTL, gctl); /* entering reset state */ for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) { /* Empirical testing time: 150 */ 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); } /* Empirical testing time:300 */ drv_usecwait(300); /* exit reset state */ AUDIOHD_REG_SET32(AUDIOHD_REG_GCTL, gctl | AUDIOHDR_GCTL_CRST); for (i = 0; i < AUDIOHD_RETRY_TIMES; i++) { /* Empirical testing time: 150, which works well */ 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); } /* HD spec requires to wait 250us at least. we use 500us */ drv_usecwait(500); /* enable unsolicited response */ AUDIOHD_REG_SET32(AUDIOHD_REG_GCTL, gctl | AUDIOHDR_GCTL_URESPE); return (DDI_SUCCESS); } /* audiohd_reset_controller() */ /* * audiohd_alloc_dma_mem() * * Description: * This is an utility routine. It is used to allocate DMA * memory. */ 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); } /* audiohd_alloc_dma_mem() */ /* * audiohd_release_dma_mem() * * Description: * Release DMA memory. */ 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; } } /* audiohd_release_dma_mem() */ /* * audiohd_reinit_hda() * * Description: * This routine is used to re-initialize HD controller and codec. */ static int audiohd_reinit_hda(audiohd_state_t *statep) { uint64_t addr; /* set PCI configure space in case it's not restored OK */ (void) audiohd_init_pci(statep, &hda_dev_accattr); /* reset controller */ if (audiohd_reset_controller(statep) != DDI_SUCCESS) return (DDI_FAILURE); AUDIOHD_REG_SET32(AUDIOHD_REG_SYNC, 0); /* needn't sync stream */ /* Initialize controller RIRB */ 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); /* Initialize controller CORB */ 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); } /* audiohd_reinit_hda */ /* * audiohd_init_controller() * * Description: * This routine is used to initialize HD controller. It * allocates DMA memory for CORB/RIRB, buffer descriptor * list and cylic data buffer for both play and record * stream. */ 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, /* version */ 0, /* addr_lo */ 0xffffffffffffffffULL, /* addr_hi */ 0x00000000ffffffffULL, /* count_max */ 128, /* 128-byte alignment as HD spec */ 0xfff, /* burstsize */ 1, /* minxfer */ 0xffffffff, /* maxxfer */ 0xffffffff, /* seg */ 1, /* sgllen */ 1, /* granular */ 0 /* flags */ }; gcap = AUDIOHD_REG_GET16(AUDIOHD_REG_GCAP); /* * If the device doesn't support 64-bit DMA, we should not * allocate DMA memory from 4G above */ 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; /* stop all dma before starting to reset controller */ audiohd_stop_dma(statep); if (audiohd_reset_controller(statep) != DDI_SUCCESS) return (DDI_FAILURE); /* check codec */ 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); } /* allocate DMA for CORB */ 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); } /* allocate DMA for RIRB */ 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); /* needn't sync stream */ /* Initialize RIRB */ 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); /* initialize CORB */ 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); } /* audiohd_init_controller() */ /* * audiohd_fini_controller() * * Description: * Releases DMA memory allocated in audiohd_init_controller() */ 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); } /* audiohd_fini_controller() */ /* * audiohd_get_conns_from_entry() * * Description: * Get connection list from every entry for a widget */ 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; } } } /* * audiohd_get_conns() * * Description: * Get all connection list for a widget. The connection list is used for * build output path, input path, and monitor path */ 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; /* * This should not happen since the ConnectionList bit of * widget capabilities already told us that this widget * has a connection list */ 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); } } /* * Read PinCapabilities & default configuration */ 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 the pin has no physical connection for port, * we won't link it to pin linkage list ??? */ if (((config >> AUDIOHD_PIN_CON_STEP) & AUDIOHD_PIN_CON_MASK) == 0x1) { pin->no_phys_conn = 1; } /* bit 4:3 are reserved, read-modify-write is needed */ 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; /* enable the unsolicited response of the pin */ 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); } /* accommodate all the pins in a link list sorted by assoc and seq */ 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; } } } /* audiohd_get_pin_config() */ /* * audiohd_create_widgets() * * Description: * All widgets are created and stored in an array of codec */ 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 there's connection list */ if (widcap & AUDIOHD_WIDCAP_CONNLIST) { audiohd_get_conns(codec, wid); } /* if power control, power it up to D0 state */ if (widcap & AUDIOHD_WIDCAP_PWRCTRL) { (void) audioha_codec_verb_get(statep, caddr, wid, AUDIOHDC_VERB_SET_POWER_STATE, 0); } /* * if this widget has format override, we read it. * Otherwise, it uses the format of audio function. */ 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; } /* * Input amplifier. Has the widget input amplifier ? */ if (widcap & AUDIOHD_WIDCAP_INAMP) { /* * if overrided bit is 0, use the default * amplifier of audio function as HD spec. * Otherwise, we read it. */ 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; } /* * output amplifier. Has this widget output amplifier ? */ 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: /* * Some codec(like ALC262) don't provide beep widget, * it only has input Pin to connect an external beep * (maybe in motherboard or elsewhere). So we open * all PINs here in order to enable external beep * source. */ 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: /* * Get the audiohd_beep_switch value from audiohd.conf, * which is for turning on/off widget 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); } /* audiohd_create_widgets() */ /* * audiohd_destroy_widgets() */ 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; } } } /* audiohd_destroy_widgets() */ /* * audiohd_create_codec() * * Description: * Searching for supported CODEC. If find, allocate memory * to hold codec structure. */ 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; /* * Assume that each codec has just one audio function group */ 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); /* work around for Sony VAIO laptop with specific codec */ if ((codec->codec_info->flags & NO_GPIO) == 0) { /* * GPIO controls which are laptop specific workarounds * and might be changed. Some laptops use GPIO, * so we need to enable and set the GPIO correctly. */ (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); } /* power-up audio function group */ (void) audioha_codec_verb_get(statep, i, wid, AUDIOHDC_VERB_SET_POWER_STATE, AUDIOHD_PW_D0); /* subsystem id is attached to funtion group */ 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; /* * We output the codec information to syslog */ statep->codec[i] = codec; codec->statep = statep; (void) audiohd_create_widgets(codec); } return (DDI_SUCCESS); } /* audiohd_create_codec() */ /* * audiohd_destroy_codec() * * Description: * destroy codec structure, and release its memory */ 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]); /* * free pins */ 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; } } } /* audiohd_destroy_codec() */ /* * audiohd_find_dac() * Description: * Find a dac for a output path. Then the play data can be sent to the out * put pin through the output path. * * Arguments: * hda_codec_t *codec where the dac widget exists * wid_t wid the no. of a widget * int mixer whether the path need mixer or not * int *mixernum the total of mixer in the output path * int exclusive an exclusive path or share path * int depth the depth of search * * Return: * 1) wid of the first shared widget in the path from * pin to DAC if exclusive is 0; * 2) wid of DAC widget; * 3) 0 if no path */ 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 is true, we try to find a path which doesn't * share any widget with other paths. */ 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: /* We need mixer widget, but the the mixer num is 0, failed */ 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)++; /* FALLTHRU */ 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 when found a path */ return (wdac); } } default: break; } return (wdac); } /* audiohd_find_dac() */ /* * audiohd_do_build_output_path() * * Description: * Search an output path for each pin in the codec. * Arguments: * hda_codec_t *codec where the output path exists * int mixer whether the path needs mixer widget * int *mnum total of mixer widget in the path * int exclusive an exclusive path or shared path * int depth search depth */ 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++) { /* * If a dac found, the return value is the wid of the * widget on the path, or the return value is * DDI_FAILURE */ wid = audiohd_find_dac(codec, widget->avail_conn[i], mixer, mnum, exclusive, depth); /* * A dac was not found */ 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; } } } /* audiohd_do_build_output_path() */ /* * audiohd_build_output_path() * * Description: * Build the output path in the codec for every pin. * First we try to search output path with mixer widget exclusively * Then we try to search shared output path with mixer widget. * Then we try to search output path without mixer widget exclusively. * At last we try to search shared ouput path for the remained pins */ static void audiohd_build_output_path(hda_codec_t *codec) { int mnum = 0; uint8_t mixer_allow = 1; /* * Work around for laptops which have IDT or AD audio chipset, such as * HP mini 1000 laptop, Dell Lattitude 6400, Lenovo T60, Lenove R61e. * We don't allow mixer widget on such path, which leads to speaker * loud hiss noise. */ if (codec->codec_info->flags & NO_MIXER) mixer_allow = 0; /* search an exclusive mixer widget path. This is preferred */ audiohd_do_build_output_path(codec, mixer_allow, &mnum, 1, 0); /* search a shared mixer widget path for the remained pins */ audiohd_do_build_output_path(codec, mixer_allow, &mnum, 0, 0); /* search an exclusive widget path without mixer for the remained pin */ audiohd_do_build_output_path(codec, 0, &mnum, 1, 0); /* search a shared widget path without mixer for the remained pin */ audiohd_do_build_output_path(codec, 0, &mnum, 0, 0); } /* audiohd_build_output_path */ /* * audiohd_build_output_amp * * Description: * Find the gain control and mute control widget */ 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; /* * search a node which can mute this pin while * the mute functionality doesn't effect other * pins. */ 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; } /* * We select the wid which has maxium gain range in * the output path. Meanwhile, the gain controlling * of this node doesn't effect other pins if this * output stream has multiple pins. */ 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 this stream has multiple pins, we try to find * a mute & gain-controlling nodes which can effect * all output pins of this stream to be used for the * whole stream */ 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]; } /* find mute controlling node for this stream */ 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]; } /* find volume controlling node for this stream */ 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; } } } /* audiohd_build_output_amp */ /* * audiohd_finish_output_path() * * Description: * Enable the widgets on the output path */ 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 this pin has external amplifier, enable it */ 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) { /* * Set all amplifiers in this path to * the maximum volume and unmute them. */ 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; /* * Accoding to HD spec, mixer doesn't support * "select connection" */ 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]; } } } } /* audiohd_finish_output_path() */ /* * audiohd_find_input_pins() * * Description: * Here we consider a mixer/selector with multi-input as a real sum * widget. Only the first real mixer/selector widget is permitted in * an input path(recording path). If there are more mixers/selectors * execept the first one, only the first input/connection of those * widgets will be used by our driver, that means, we ignore other * inputs of those mixers/selectors. */ 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); /* we don't share widgets */ 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); /* enable the pins' input capability */ 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 the sum widget has only one input, we don't * consider it as a real sum widget. */ 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) { /* * This is a real sum widget, we will reject * other real sum widget when we find more in * the following path-searching. */ 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; } } /* return SUCCESS if we found at least one input path */ if (path->pin_nums > 0) retval = DDI_SUCCESS; } else { /* * We had already found a real sum before this one since * allowmixer is 0. */ 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); } /* audiohd_find_input_pins */ /* * audiohd_build_input_path() * * Description: * Find input path for the codec */ 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]; /* check if it is an ADC widget */ 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; /* * Is there any ADC widget which has more than one input ?? * I don't believe. Anyway, we carefully deal with this. But * if hardware vendors embed a selector in a ADC, we just use * the first available input, which has connection to input pin * widget. Because selector cannot perform mixer functionality, * and we just permit one selector or mixer in a recording path, * if we use the selector embedded in ADC,we cannot use possible * mixer during path searching. */ 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)); } /* audiohd_build_input_path */ /* * audiohd_build_input_amp() * * Description: * Find gain and mute control widgets on the input path */ 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; /* * Search node which has mute functionality for * the whole input path */ 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; } /* * Search a node for amplifier adjusting for the whole * input path */ 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 the input path has one pin only, the mute/amp * controlling is shared by the whole path and pin */ 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; } /* * For multi-pin device, there must be a selector * or mixer along the input path, and the sum_wid * is the widget's node id. */ wid = path->sum_wid; wsum = path->codec->widget[wid]; /* sum widget */ for (j = 0; j < path->pin_nums; j++) { wid = path->pin_wid[j]; w = path->codec->widget[wid]; pin = (audiohd_pin_t *)w->priv; /* find node for mute */ 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]; } } /* find node for amp controlling */ 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; } } } /* audiohd_build_input_amp() */ /* * audiohd_finish_input_path() * * Description: * Enable the widgets on the input path */ 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]; } /* * After exiting from the above loop, the widget pointed * by w can be a pin widget or select/mixer widget. If it * is a pin widget, we already finish "select connection" * operation for the whole path. */ if (w && w->type == WTYPE_PIN) continue; /* * deal with multi-pin input devices. */ 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]; } } } /* end of istream loop */ } /* audiohd_finish_input_path */ /* * audiohd_find_inpin_for_monitor() * * Description: * Find input pin for monitor path. * * Arguments: * hda_codec_t *codec where the monitor path exists * wid_t id no. of widget being searched * int mixer share or not */ 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); } } /* the widget has been visited and can't be directed to input pin */ if (widget->path_flags & AUDIOHD_PATH_NOMON) { return (uint32_t)(DDI_FAILURE); } /* the widget has been used by the monitor path, and we can share it */ 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); } /* audiohd_find_inpin_for_monitor */ /* * audiohd_build_monitor_path() * * Description: * The functionality of mixer is to mix inputs, such as CD-IN, MIC, * Line-in, etc, with DAC outputs, so as to minitor what is being * recorded and implement "What you hear is what you get". However, * this functionality are really hardware-dependent: the inputs * must be directed to MIXER if they can be directed to ADC as * recording sources. */ 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]; } /* * No mixer in this output path, we cannot build * mixer path for this path, skip it, * and continue for next output path. */ if (widget == NULL || widget->output_path_next == AUDIOHD_NULL_CONN) { break; } mixernum++; for (k = 0; k < widget->nconns; k++) { /* * this connection must be routined * to DAC instead of an input pin * widget, we needn't waste time for * it */ 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; } } /* * we needn't check widget->output_path_next * here since this widget is a selector or * mixer, it cannot be NULL connection. */ 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; } /* audiohd_build_monitor_path */ /* * audiohd_do_finish_monitor_path * * Description: * Enable the widgets on the monitor path */ 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); } } } /* audiohd_do_finish_monitor_path */ /* * audiohd_finish_monitor_path * * Description: * Enable the monitor path for every ostream path */ 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); } } } } /* audiohd_finish_monitor_path */ /* * audiohd_do_build_monit_amp() * * Description: * Search for the gain control widget for the monitor path */ 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]); } } /* audiohd_do_build_monitor_amp() */ /* * audiohd_build_monitor_amp() * * Description: * Search gain control widget for every ostream monitor */ 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); } } } } /* * audiohd_find_beep() * Description: * Find a beep for a beep path. Then the play data can be sent to the out * put pin through the beep path. * * Arguments: * hda_codec_t *codec where the beep widget exists * wid_t wid the no. of a widget * int depth the depth of search * * Return: * 1) wid of Beep widget; * 2) 0 if no path */ 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); } /* audiohd_find_beep() */ /* * audiohd_build_beep_path() * * Description: * Search an beep path for each pin in the codec. * Arguments: * hda_codec_t *codec where the beep path exists */ 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++) { /* * If a beep found, the return value is the wid of the * widget on the path, or the return value is * DDI_FAILURE */ wid = audiohd_find_beep(codec, widget->avail_conn[i], 0); /* * A beep was not found */ 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; } } } } } /* audiohd_build_beep_path() */ /* * audiohd_build_beep_amp * * Description: * Find the gain control and mute control widget */ 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; } } } /* audiohd_build_beep_amp */ /* * audiohd_finish_beep_path() * * Description: * Enable the widgets on the beep path */ 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) { /* * Set all amplifiers in this path to * the maximum volume and unmute them. */ 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; /* * Accoding to HD spec, mixer doesn't support * "select connection" */ 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]; } } } } /* audiohd_finish_beep_path */ 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: /* Connection between line-in and output pins */ 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 the sum widget has only one input, we don't * consider it as a real sum widget. */ 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]; /* check if it is an ADC widget */ 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)); } /* audiohd_build_loopback_path() */ /* * audiohd_build_path() * * Description: * Here we build the output, input, monitor path. * And also enable the path in default. * Search for the gain and mute control for the path */ 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]); } } } /* audiohd_build_path */ /* * audiohd_allocate_port() */ 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, /* version */ 0, /* addr_lo */ 0xffffffffffffffffULL, /* addr_hi */ 0x00000000ffffffffULL, /* count_max */ 128, /* 128-byte alignment as HD spec */ 0xfff, /* burstsize */ 1, /* minxfer */ 0xffffffff, /* maxxfer */ 0xffffffff, /* seg */ 1, /* sgllen */ 1, /* granular */ 0 /* flags */ }; 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: /* 48kHz is 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: /* 16 bits is 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; /* allocate dma handle */ 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); } /* * Warning: please be noted that allocating the dma memory * with the flag IOMEM_DATA_UNCACHED is a hack due * to an incorrect cache synchronization on NVidia MCP79 * chipset which causes the audio distortion problem, * and that it should be fixed later. There should be * no reason you have to allocate UNCACHED memory. In * complex architectures with nested IO caches, * reliance on this flag might lead to 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); } } /* bind DMA buffer */ 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; /* * now, from here we allocate DMA * memory for buffer descriptor list. * we allocate adjacent DMA memory for all DMA engines. */ 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); } /* * we allocate all buffer descriptors lists in continuous * dma memory. */ 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)); } } /* * audiohd_change_widget_power_state(audiohd_state_t *statep, int state) * Description: * This routine is used to change the widget power betwen D0 and D2. * D0 is fully on; D2 allows the lowest possible power consuming state * from which it can return to the fully on state: D0. */ 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); } } } } /* * audiohd_restore_path() * Description: * This routine is used to restore the path on the codec. */ 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]); } } /* * audiohd_reset_pins_ur_cap() * Description: * Enable the unsolicited response of the pins which have the unsolicited * response capability */ 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) { /* enable the unsolicited response of the 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; /* power-up audio function group */ (void) audioha_codec_verb_get(statep, i, wid, AUDIOHDC_VERB_SET_POWER_STATE, AUDIOHD_PW_D0); /* work around for Sony VAIO laptop with specific codec */ if ((codec->codec_info->flags & NO_GPIO) == 0) { /* * GPIO controls which are laptop specific workarounds * and might be changed. Some laptops use GPIO, * so we need to enable and set the GPIO correctly. */ (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); } } } /* * audiohd_resume() */ static int audiohd_resume(audiohd_state_t *statep) { uint8_t rirbsts; mutex_enter(&statep->hda_mutex); statep->suspended = B_FALSE; /* Restore the hda state */ if (audiohd_reinit_hda(statep) == DDI_FAILURE) { audio_dev_warn(statep->adev, "hda reinit failed"); mutex_exit(&statep->hda_mutex); return (DDI_FAILURE); } /* reset to enable the capability of unsolicited response for pin */ audiohd_reset_pins_ur_cap(statep); /* clear the unsolicited response interrupt */ rirbsts = AUDIOHD_REG_GET8(AUDIOHD_REG_RIRBSTS); AUDIOHD_REG_SET8(AUDIOHD_REG_RIRBSTS, rirbsts); /* set widget power to D0 */ 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); } /* audiohd_resume */ /* * audiohd_suspend() */ static int audiohd_suspend(audiohd_state_t *statep) { audio_dev_suspend(statep->adev); mutex_enter(&statep->hda_mutex); statep->suspended = B_TRUE; /* set widget power to D2 */ audiohd_change_widget_power_state(statep, AUDIOHD_PW_D2); /* Disable h/w */ audiohd_stop_dma(statep); audiohd_fini_pci(statep); mutex_exit(&statep->hda_mutex); return (DDI_SUCCESS); } /* audiohd_suspend */ /* * audiohd_disable_pin() */ 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)); } /* * audiohd_enable_pin() */ 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); } /* * audiohd_change_speaker_state() */ 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); } } } } } /* * audiohd_select_mic() * * Description: * This function is used for the recording path which has a selector * as the sumwidget. We select the external MIC if it is plugged into the * MIC jack, otherwise the internal integrated MIC is selected. */ 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) { /* Have a selector to choose input pin */ 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 { /* * No selector widget in the path, * mute unselected input pin */ /* Open all input pin, and then mute others */ audiohd_set_pin_volume(statep, DTYPE_MIC_IN); if (select == 1) { /* Select external mic, mute internal */ 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 { /* Select internal mic, mute external */ 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 the input istream > 1, we should set the record stream tag * respectively. All the input streams sharing one tag may make the * record sound distorted. */ 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; } } } } } /* * audiohd_pin_sense() * * Description * * When the earphone is plugged into the jack associtated with the pin * complex, we disable the built in speaker. When the earphone is plugged * out of the jack, we enable the built in speaker. */ 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) { /* A MIC is plugged in, we select the MIC as input */ if ((widget->type == WTYPE_PIN) && (pin = (audiohd_pin_t *)widget->priv) && (pin->device == DTYPE_MIC_IN)) { audiohd_select_mic(statep, index, id, 1); return; } /* output pin is plugged */ audiohd_change_speaker_state(statep, AUDIOHD_SP_OFF); } else { /* * A MIC is unplugged, we select the built in MIC * as input. */ if ((widget->type == WTYPE_PIN) && (pin = (audiohd_pin_t *)widget->priv) && (pin->device == DTYPE_MIC_IN)) { audiohd_select_mic(statep, index, id, 0); return; } /* output pin is unplugged */ audiohd_change_speaker_state(statep, AUDIOHD_SP_ON); } } /* * audiohd_disable_intr() * * Description: * Disable all possible interrupts. */ 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)); } /* audiohd_disable_intr() */ /* * audiohd_12bit_verb_to_codec() * * Description: * */ 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; /* overflow */ 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); } /* audiohd_12bit_verb_to_codec() */ /* * audiohd_4bit_verb_to_codec() * * Description: * */ 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; /* overflow */ 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); } /* audiohd_4bit_verb_to_codec() */ /* * audiohd_response_from_codec() * * Description: * */ 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); } /* audiohd_response_from_codec() */ /* * audioha_codec_verb_get() */ 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); } /* * Empirical testing times. 50 times is enough for audiohd spec 1.0. * But we need to make it work for audiohd spec 0.9, which is just a * draft version and requires more time to wait. */ 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; /* Empirical testing time, which works well */ 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)); } /* audioha_codec_verb_get() */ /* * audioha_codec_4bit_verb_get() */ 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; /* Empirical testing time, which works well */ 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)); } /* audioha_codec_4bit_verb_get() */