Print this page
10619 audiohd ignores digital output pins such as HDMI/DP
@@ -24,10 +24,12 @@
#include <sys/audio/audio_driver.h>
#include <sys/note.h>
#include <sys/beep.h>
#include <sys/pci.h>
+#include <sys/stddef.h>
+#include <sys/sysmacros.h>
#include "audiohd.h"
#define DRVNAME "audiohd"
/*
@@ -82,10 +84,13 @@
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_setup_hdmi_dp(audiohd_state_t *, uint8_t, uint8_t,
+ boolean_t);
+static audiohd_eld_t *audiohd_get_eld(audiohd_state_t *, uint8_t, uint8_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;
@@ -331,11 +336,11 @@
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 */
+ /* Set PCI command register to enable bus master and memory I/O */
if (audiohd_init_pci(statep, &hda_dev_accattr) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"couldn't init pci regs");
goto error;
}
@@ -580,11 +585,12 @@
/* 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) {
+ if (pin->device == DTYPE_SPDIF_OUT ||
+ pin->device == DTYPE_DIGIT_OUT) {
ctrl = audioha_codec_verb_get(
statep,
codec->index,
path->adda_wid,
AUDIOHDC_VERB_GET_SPDIF_CTL,
@@ -664,11 +670,21 @@
path->adda_wid,
AUDIOHDC_VERB_SET_CONV_FMT,
statep->port[PORT_DAC]->format << 4 |
statep->pchan - 1);
}
+
+ /* Setup HDMI/DP output pin */
+ if (pin->device == DTYPE_DIGIT_OUT) {
+ pin->eld = audiohd_get_eld(statep, codec->index, pin->wid);
+ if (pin->eld != NULL &&
+ (pin->eld->eld_conn_type & 0x3) != 0)
+ audiohd_setup_hdmi_dp(statep, codec->index, pin->wid,
+ pin->eld->eld_conn_type == AUDIOHD_ELD_CONN_HDMI);
+ }
}
+
static void
audiohd_init_record_path(audiohd_path_t *path)
{
audiohd_state_t *statep = path->statep;
hda_codec_t *codec = path->codec;
@@ -2140,11 +2156,11 @@
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)
+ * Express QoS. It implements 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
@@ -2722,10 +2738,11 @@
/* 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_DIGIT_OUT) ||
(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);
@@ -3238,10 +3255,11 @@
if ((pin->config & AUDIOHD_PIN_CONF_MASK) ==
AUDIOHD_PIN_NO_CONN)
continue;
if ((pin->device != DTYPE_LINEOUT) &&
(pin->device != DTYPE_SPEAKER) &&
+ (pin->device != DTYPE_DIGIT_OUT) &&
(pin->device != DTYPE_SPDIF_OUT) &&
(pin->device != DTYPE_HP_OUT))
continue;
if (pin->finish)
continue;
@@ -3752,11 +3770,11 @@
if (path == NULL)
path = kmem_zalloc(sizeof (audiohd_path_t),
KM_SLEEP);
else
- bzero(path, sizeof (audiohd_port_t));
+ bzero(path, sizeof (audiohd_path_t));
path->adda_wid = wid;
/*
* Is there any ADC widget which has more than one input ??
@@ -4551,10 +4569,11 @@
if ((pin->config & AUDIOHD_PIN_CONF_MASK) ==
AUDIOHD_PIN_NO_CONN)
continue;
if ((pin->device != DTYPE_LINEOUT) &&
(pin->device != DTYPE_SPEAKER) &&
+ (pin->device != DTYPE_DIGIT_OUT) &&
(pin->device != DTYPE_SPDIF_OUT) &&
(pin->device != DTYPE_HP_OUT))
continue;
widget = codec->widget[pin->wid];
@@ -4823,10 +4842,11 @@
path->pin_wid[path->pin_nums++] = wid;
break;
case DTYPE_LINEOUT:
case DTYPE_HP_OUT:
case DTYPE_SPDIF_OUT:
+ case DTYPE_DIGIT_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);
@@ -4891,11 +4911,11 @@
continue;
if (path == NULL)
path = kmem_zalloc(sizeof (audiohd_path_t), KM_SLEEP);
else
- bzero(path, sizeof (audiohd_port_t));
+ bzero(path, sizeof (audiohd_path_t));
path->adda_wid = wid;
for (i = 0; i < widget->nconns; i++) {
retval = audiohd_find_output_pins(codec,
widget->avail_conn[i], 0, path);
@@ -5285,10 +5305,11 @@
widget = codec->widget[pin->wid];
if ((widget->widget_cap &
(AUDIOHD_URCAP_MASK) &&
(pin->cap & AUDIOHD_DTCCAP_MASK)) &&
((pin->device == DTYPE_LINEOUT) ||
+ (pin->device == DTYPE_DIGIT_OUT) ||
(pin->device == DTYPE_SPDIF_OUT) ||
(pin->device == DTYPE_HP_OUT) ||
(pin->device == DTYPE_MIC_IN))) {
urctrl = (uint8_t)(1 <<
(AUDIOHD_UR_ENABLE_OFF - 1));
@@ -5617,27 +5638,154 @@
}
}
}
}
}
+
/*
+ * audiohd_setup_hdmi_dp()
+ *
+ * Description
+ *
+ * Set up a basic channel mapping and a audio infoframe for a
+ * HDMI/DisplayPort pin widget. Due to lack of testing hardware
+ * this is limited to 2 channels for now.
+ */
+static void
+audiohd_setup_hdmi_dp(audiohd_state_t *statep, uint8_t index, uint8_t id,
+ boolean_t hdmi)
+{
+ uint8_t chanmap[] = { 0, 1, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf };
+ uint8_t dp_infoframe[] = { 0x84, 0x1b, 0x44, 0x01, 0x00, 0x00, 0x02 };
+ uint8_t hdmi_infoframe[] = { 0x84, 0x01, 0x0a,
+ (-(0x84 + 0x01 + 0x0a + 0x01 + 0x02)) & 0xff,
+ 0x01, 0x00, 0x00, 0x02 };
+ uint8_t *infoframe;
+ size_t infolen;
+ int i;
+
+ if (hdmi) {
+ infoframe = hdmi_infoframe;
+ infolen = sizeof (hdmi_infoframe);
+ } else {
+ infoframe = dp_infoframe;
+ infolen = sizeof (dp_infoframe);
+ }
+
+ /*
+ * Setup channel mapping. Only 2 channels supported for now.
+ */
+ for (i = 0; i != sizeof (chanmap); i++)
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_ASP_CHMAP, (chanmap[i] << 4) | i);
+
+ /*
+ * Send audio infoframe.
+ */
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_INDEX, 0);
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_XMIT, AUDIOHD_DIP_XMIT_STOP);
+
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_INDEX, 0);
+ for (i = 0; i != AUDIOHD_DIP_DATA_LEN; i++)
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_DATA, 0);
+
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_INDEX, 0);
+ for (i = 0; i != infolen; i++)
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_DATA, infoframe[i]);
+
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_INDEX, 0);
+ (void) audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_SET_DIP_XMIT, AUDIOHD_DIP_XMIT_BEST_EFFORT);
+
+}
+
+/*
+ * audiohd_get_eld()
+ *
+ * Description
+ *
+ * Fetch the EDID-like data (ELD) from a pin that is a HDMI/DP
+ * display sink device. The data is coming tightly packed from
+ * hardware, unpack it into a audiohd_eld_t structure for easy
+ * access. We get the complete ELD, but we completely ignore the
+ * vendor specific part.
+ */
+static audiohd_eld_t *
+audiohd_get_eld(audiohd_state_t *statep, uint8_t index, uint8_t id)
+{
+ uint8_t *buf;
+ audiohd_eld_t *eld;
+ off_t sad_off;
+ uint32_t rs;
+ size_t eldsz;
+ size_t i;
+
+ rs = audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_GET_DIP_SIZE, AUDIOHD_DIP_SIZE_ELD);
+
+ eldsz = (rs & AUDIOHD_DIP_SIZE_ELD_MASK) + 1;
+ if (eldsz < AUDIOHD_ELD_HDR_LEN)
+ return (NULL);
+
+ eldsz = MAX(eldsz, sizeof (audiohd_eld_t));
+
+ buf = kmem_zalloc(eldsz, KM_SLEEP);
+ eld = (audiohd_eld_t *)buf;
+
+ /*
+ * The device will return 0 for any offset that is beyond the length of
+ * ELD, so we don't have to check whether the size returned by DIP SIZE
+ * is less than sizeof (audiohd_eld_t).
+ */
+ for (i = 0; i != eldsz; i++)
+ buf[i] = audioha_codec_verb_get(statep, index, id,
+ AUDIOHDC_VERB_GET_ELD, i);
+
+ if (eld->eld_ver != AUDIOHD_ELD_VER_2 &&
+ eld->eld_ver != AUDIOHD_ELD_VER_NODRV) {
+ kmem_free(buf, eldsz);
+ return (NULL);
+ }
+
+ eld->eld_size = eldsz;
+
+ /* move the CEA SADs to eld_cea_sad */
+ sad_off = offsetof(audiohd_eld_t, eld_monitor_name) + eld->eld_mnl;
+ bcopy(buf + sad_off, (void *)eld->eld_cea_sad,
+ sizeof (audiohd_sad_t) * eld->eld_sad_count);
+ bzero(buf + sad_off, offsetof(audiohd_eld_t, eld_cea_sad) - sad_off);
+
+ return (eld);
+}
+
+/*
* 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.
+ *
+ * When a digital pin complex reports valid EDID-like data (ELD), update
+ * it.
*/
static void
audiohd_pin_sense(audiohd_state_t *statep, uint32_t resp, uint32_t respex)
{
+ audiohd_pin_t *pin = NULL;
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);
@@ -5646,17 +5794,27 @@
return;
widget = codec->widget[id];
if (widget == NULL)
return;
+ if (widget->type == WTYPE_PIN)
+ pin = (audiohd_pin_t *)widget->priv;
+
rs = audioha_codec_verb_get(statep, index, id,
AUDIOHDC_VERB_GET_PIN_SENSE, 0);
- if (rs & AUDIOHD_PIN_PRES_MASK) {
+
+ if ((widget->widget_cap & AUDIOHD_WIDCAP_DIGIT) != 0 &&
+ (rs & AUDIOHD_PIN_ELDV_MASK) != 0) {
+ /* updated EDID-like data */
+ if (pin != NULL) {
+ if (pin->eld != NULL)
+ kmem_free(pin->eld, pin->eld->eld_size);
+ pin->eld = audiohd_get_eld(statep, index, id);
+ }
+ } else 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)) {
+ if (pin != NULL && 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);