[alsa-devel] [PATCH] alsa: hdmi - support infoframe for DisplayPort

Wu Fengguang fengguang.wu at intel.com
Tue Sep 21 08:25:49 CEST 2010


DisplayPort works mostly in the same way as HDMI, except that it expects
a slightly different audio infoframe format.

Citations from "HDA036-A: Display Port Support and HDMI Miscellaneous
Corrections":

The HDMI specification defines a data island packet with a header of 4
bytes (3 bytes content + 1 byte ECC) and packet body of 32 bytes (28
bytes content and 4 bytes ECC). Display Port specification on the other
hand defines a data island packet (secondary data packet) with header of
4 bytes protected by 4 bytes of parity, and data of theoretically up to
1024 bytes with each 16 bytes chunk of data protected by 4 bytes of
parity. Note that the ECC or parity bytes are not present in the DIP
content populated by software and are hardware generated.

It tests DP connection based on the ELD conn_type field, which will be
set by the graphics driver and can be overriden manually by users
through the /proc/asound/card0/eld* interface.

The DP infoframe is tested OK on Intel SandyBridge/CougarPoint platform.

Signed-off-by: Wu Fengguang <fengguang.wu at intel.com>
---
 sound/pci/hda/patch_hdmi.c |  110 +++++++++++++++++++++++------------
 1 file changed, 73 insertions(+), 37 deletions(-)

Takashi: half of the patch is harmless code refactor.
The real changes happen in the first and last chunks.

--- sound-2.6.orig/sound/pci/hda/patch_hdmi.c	2010-09-21 09:41:46.000000000 +0800
+++ sound-2.6/sound/pci/hda/patch_hdmi.c	2010-09-21 11:20:59.000000000 +0800
@@ -82,17 +82,29 @@ struct hdmi_spec {
 struct hdmi_audio_infoframe {
 	u8 type; /* 0x84 */
 	u8 ver;  /* 0x01 */
 	u8 len;  /* 0x0a */
 
-	u8 checksum;	/* PB0 */
+	u8 checksum;
+
 	u8 CC02_CT47;	/* CC in bits 0:2, CT in 4:7 */
 	u8 SS01_SF24;
 	u8 CXT04;
 	u8 CA;
 	u8 LFEPBL01_LSV36_DM_INH7;
-	u8 reserved[5];	/* PB6 - PB10 */
+};
+
+struct dp_audio_infoframe {
+	u8 type; /* 0x84 */
+	u8 len;  /* 0x1b */
+	u8 ver;  /* 0x11 << 2 */
+
+	u8 CC02_CT47;	/* match with HDMI infoframe from this on */
+	u8 SS01_SF24;
+	u8 CXT04;
+	u8 CA;
+	u8 LFEPBL01_LSV36_DM_INH7;
 };
 
 /*
  * CEA speaker placement:
  *
@@ -192,11 +204,11 @@ static int hdmi_channel_mapping[0x32][8]
 
 /*
  * This is an ordered list!
  *
  * The preceding ones have better chances to be selected by
- * hdmi_setup_channel_allocation().
+ * hdmi_channel_allocation().
  */
 static struct cea_channel_speaker_allocation channel_allocations[] = {
 /*			  channel:   7     6    5    4    3     2    1    0  */
 { .ca_index = 0x00,  .speakers = {   0,    0,   0,   0,   0,    0,  FR,  FL } },
 				 /* 2.1 */
@@ -369,18 +381,18 @@ static void init_channel_allocations(voi
  *	eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask
  *	      spk_mask => (channel_allocations[])         => ai->CA
  *
  * TODO: it could select the wrong CA from multiple candidates.
 */
-static int hdmi_setup_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
-					 struct hdmi_audio_infoframe *ai)
+static int hdmi_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
+				   int channels)
 {
 	struct hdmi_spec *spec = codec->spec;
 	struct hdmi_eld *eld;
 	int i;
+	int ca = 0;
 	int spk_mask = 0;
-	int channels = 1 + (ai->CC02_CT47 & 0x7);
 	char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
 
 	/*
 	 * CA defaults to 0 for basic stereo audio
 	 */
@@ -414,20 +426,20 @@ static int hdmi_setup_channel_allocation
 	/* search for the first working match in the CA table */
 	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
 		if (channels == channel_allocations[i].channels &&
 		    (spk_mask & channel_allocations[i].spk_mask) ==
 				channel_allocations[i].spk_mask) {
-			ai->CA = channel_allocations[i].ca_index;
+			ca = channel_allocations[i].ca_index;
 			break;
 		}
 	}
 
 	snd_print_channel_allocation(eld->spk_alloc, buf, sizeof(buf));
 	snd_printdd("HDMI: select CA 0x%x for %d-channel allocation: %s\n",
-		    ai->CA, channels, buf);
+		    ca, channels, buf);
 
-	return ai->CA;
+	return ca;
 }
 
 static void hdmi_debug_channel_mapping(struct hda_codec *codec,
 				       hda_nid_t pin_nid)
 {
@@ -445,14 +457,13 @@ static void hdmi_debug_channel_mapping(s
 }
 
 
 static void hdmi_setup_channel_mapping(struct hda_codec *codec,
 				       hda_nid_t pin_nid,
-				       struct hdmi_audio_infoframe *ai)
+				       int ca)
 {
 	int i;
-	int ca = ai->CA;
 	int err;
 
 	if (hdmi_channel_mapping[ca][1] == 0) {
 		for (i = 0; i < channel_allocations[ca].channels; i++)
 			hdmi_channel_mapping[ca][i] = i | (i << 4);
@@ -545,57 +556,53 @@ static void hdmi_clear_dip_buffers(struc
 			i, size, j);
 	}
 #endif
 }
 
-static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *ai)
+static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *hdmi_ai)
 {
-	u8 *bytes = (u8 *)ai;
+	u8 *bytes = (u8 *)hdmi_ai;
 	u8 sum = 0;
 	int i;
 
-	ai->checksum = 0;
+	hdmi_ai->checksum = 0;
 
-	for (i = 0; i < sizeof(*ai); i++)
+	for (i = 0; i < sizeof(*hdmi_ai); i++)
 		sum += bytes[i];
 
-	ai->checksum = -sum;
+	hdmi_ai->checksum = -sum;
 }
 
 static void hdmi_fill_audio_infoframe(struct hda_codec *codec,
 				      hda_nid_t pin_nid,
-				      struct hdmi_audio_infoframe *ai)
+				      u8 *dip, int size)
 {
-	u8 *bytes = (u8 *)ai;
 	int i;
 
 	hdmi_debug_dip_size(codec, pin_nid);
 	hdmi_clear_dip_buffers(codec, pin_nid); /* be paranoid */
 
-	hdmi_checksum_audio_infoframe(ai);
-
 	hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
-	for (i = 0; i < sizeof(*ai); i++)
-		hdmi_write_dip_byte(codec, pin_nid, bytes[i]);
+	for (i = 0; i < size; i++)
+		hdmi_write_dip_byte(codec, pin_nid, dip[i]);
 }
 
 static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
-				    struct hdmi_audio_infoframe *ai)
+				    u8 *dip, int size)
 {
-	u8 *bytes = (u8 *)ai;
 	u8 val;
 	int i;
 
 	if (snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_XMIT, 0)
 							    != AC_DIPXMIT_BEST)
 		return false;
 
 	hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
-	for (i = 0; i < sizeof(*ai); i++) {
+	for (i = 0; i < size; i++) {
 		val = snd_hda_codec_read(codec, pin_nid, 0,
 					 AC_VERB_GET_HDMI_DIP_DATA, 0);
-		if (val != bytes[i])
+		if (val != dip[i])
 			return false;
 	}
 
 	return true;
 }
@@ -603,35 +610,64 @@ static bool hdmi_infoframe_uptodate(stru
 static void hdmi_setup_audio_infoframe(struct hda_codec *codec, hda_nid_t nid,
 					struct snd_pcm_substream *substream)
 {
 	struct hdmi_spec *spec = codec->spec;
 	hda_nid_t pin_nid;
+	int channels = substream->runtime->channels;
+	int ca;
 	int i;
-	struct hdmi_audio_infoframe ai = {
-		.type		= 0x84,
-		.ver		= 0x01,
-		.len		= 0x0a,
-		.CC02_CT47	= substream->runtime->channels - 1,
-	};
+	u8 ai[max(sizeof(struct hdmi_audio_infoframe),
+		  sizeof(struct dp_audio_infoframe))];
 
-	hdmi_setup_channel_allocation(codec, nid, &ai);
+	ca = hdmi_channel_allocation(codec, nid, channels);
 
 	for (i = 0; i < spec->num_pins; i++) {
 		if (spec->pin_cvt[i] != nid)
 			continue;
 		if (!spec->sink_eld[i].monitor_present)
 			continue;
 
 		pin_nid = spec->pin[i];
-		if (!hdmi_infoframe_uptodate(codec, pin_nid, &ai)) {
+
+		memset(ai, 0, sizeof(ai));
+		if (spec->sink_eld[i].conn_type == 0) { /* HDMI */
+			struct hdmi_audio_infoframe *hdmi_ai;
+
+			hdmi_ai = (struct hdmi_audio_infoframe *)ai;
+			hdmi_ai->type		= 0x84;
+			hdmi_ai->ver		= 0x01;
+			hdmi_ai->len		= 0x0a;
+			hdmi_ai->CC02_CT47	= channels - 1;
+			hdmi_checksum_audio_infoframe(hdmi_ai);
+		} else if (spec->sink_eld[i].conn_type == 1) { /* DisplayPort */
+			struct dp_audio_infoframe *dp_ai;
+
+			dp_ai = (struct dp_audio_infoframe *)ai;
+			dp_ai->type		= 0x84;
+			dp_ai->len		= 0x1b;
+			dp_ai->ver		= 0x11 << 2;
+			dp_ai->CC02_CT47	= channels - 1;
+		} else {
+			snd_printd("HDMI: unknown connection type at pin %d\n",
+				   pin_nid);
+			continue;
+		}
+
+		/*
+		 * sizeof(ai) is used instead of sizeof(*hdmi_ai) or
+		 * sizeof(*dp_ai) to avoid partial match/update problems when
+		 * the user switches between HDMI/DP monitors.
+		 */
+		if (!hdmi_infoframe_uptodate(codec, pin_nid, ai, sizeof(ai))) {
 			snd_printdd("hdmi_setup_audio_infoframe: "
 				    "cvt=%d pin=%d channels=%d\n",
 				    nid, pin_nid,
-				    substream->runtime->channels);
-			hdmi_setup_channel_mapping(codec, pin_nid, &ai);
+				    channels);
+			hdmi_setup_channel_mapping(codec, pin_nid, ca);
 			hdmi_stop_infoframe_trans(codec, pin_nid);
-			hdmi_fill_audio_infoframe(codec, pin_nid, &ai);
+			hdmi_fill_audio_infoframe(codec, pin_nid,
+						  ai, sizeof(ai));
 			hdmi_start_infoframe_trans(codec, pin_nid);
 		}
 	}
 }
 


More information about the Alsa-devel mailing list