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);