[RFC] ALSA: hda - hdmi: Disallow 3/4/5-channel playback to Statement D2
diff mbox

Message ID 1399247766-23697-1-git-send-email-anssi.hannula@iki.fi
State Accepted
Delegated to: Takashi Iwai
Headers show

Commit Message

Anssi Hannula May 4, 2014, 11:56 p.m. UTC
Anthem Statement D2 receiver plays only front channels when a
3/4/5-channel HDMI CA is selected. Only 2 and 6 channel maps work
properly.

Disallow 3/4/5-channel maps based on sink name, allowing userspace to
retry with a 6-channel mode with additional silent channels.

Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi>
Reported-by: Grant Warecki <gjwaudio1@hotmail.com>
Tested-by: Grant Warecki <gjwaudio1@hotmail.com>
---

Hi Takashi,

I got a report about an issue concerning a fairly obscure high-end
Anthem Statement D2 sink, and wrote this patch for it. However, I'm now
having second thoughts about whether the kernel is the correct place for
this, since
(a) this is the only case I've encountered so far (and is obscure), and
(b) these kind of issues might get fixed with fw upgrades, and
(c) cross-platform media players will have to handle (possibly with
    manual configuration, though) these cases anyway on non-Linux...

WDYT?

 sound/pci/hda/patch_hdmi.c | 74 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 72 insertions(+), 2 deletions(-)

Comments

Takashi Iwai May 5, 2014, 2:39 p.m. UTC | #1
At Mon,  5 May 2014 02:56:06 +0300,
Anssi Hannula wrote:
> 
> Anthem Statement D2 receiver plays only front channels when a
> 3/4/5-channel HDMI CA is selected. Only 2 and 6 channel maps work
> properly.
> 
> Disallow 3/4/5-channel maps based on sink name, allowing userspace to
> retry with a 6-channel mode with additional silent channels.
> 
> Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi>
> Reported-by: Grant Warecki <gjwaudio1@hotmail.com>
> Tested-by: Grant Warecki <gjwaudio1@hotmail.com>
> ---
> 
> Hi Takashi,
> 
> I got a report about an issue concerning a fairly obscure high-end
> Anthem Statement D2 sink, and wrote this patch for it. However, I'm now
> having second thoughts about whether the kernel is the correct place for
> this, since
> (a) this is the only case I've encountered so far (and is obscure), and
> (b) these kind of issues might get fixed with fw upgrades, and
> (c) cross-platform media players will have to handle (possibly with
>     manual configuration, though) these cases anyway on non-Linux...
> 
> WDYT?

The framework for possible workarounds looks good to me, but in this
particular case, I'd rather like to skip.  We can reconsider once when
we see more similar problems.


thanks,

Takashi

> 
>  sound/pci/hda/patch_hdmi.c | 74 ++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 72 insertions(+), 2 deletions(-)
> 
> diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
> index 0cb5b89cd0c8..0a9c19cdb511 100644
> --- a/sound/pci/hda/patch_hdmi.c
> +++ b/sound/pci/hda/patch_hdmi.c
> @@ -64,6 +64,9 @@ struct hdmi_spec_per_cvt {
>  /* max. connections to a widget */
>  #define HDA_MAX_CONNECTIONS	32
>  
> +/* sink supports only CAs with 2/6 channels */
> +#define SINK_QUIRK_2_6_CHANNELS	0x0001
> +
>  struct hdmi_spec_per_pin {
>  	hda_nid_t pin_nid;
>  	int num_mux_nids;
> @@ -83,6 +86,8 @@ struct hdmi_spec_per_pin {
>  	bool chmap_set;		/* channel-map override by ALSA API? */
>  	unsigned char chmap[8]; /* ALSA API channel-map */
>  	char pcm_name[8];	/* filled in build_pcm callbacks */
> +	int sink_quirks;	/* sink-specific quirks */
> +
>  #ifdef CONFIG_PROC_FS
>  	struct snd_info_entry *proc_entry;
>  #endif
> @@ -342,6 +347,13 @@ static struct cea_channel_speaker_allocation channel_allocations[] = {
>  { .ca_index = 0x31,  .speakers = { FRW,  FLW,  RR,  RL,  FC,  LFE,  FR,  FL } },
>  };
>  
> +static struct hdmi_sink_quirks {
> +	int mfg_id;
> +	char name[ELD_MAX_MNL + 1];
> +	int quirks;
> +} sink_quirks[] = {
> +	{ .mfg_id = 0xed40, .name = "Statement D2", .quirks = SINK_QUIRK_2_6_CHANNELS },
> +};
>  
>  /*
>   * HDMI routines
> @@ -640,6 +652,39 @@ static int get_channel_allocation_order(int ca)
>  	return i;
>  }
>  
> +static unsigned int channels_2_6[] = {
> +        2, 6
> +};
> +
> +static struct snd_pcm_hw_constraint_list hw_constraints_2_6_channels = {
> +        .count = ARRAY_SIZE(channels_2_6),
> +        .list = channels_2_6,
> +        .mask = 0,
> +};
> +
> +static void set_constraints_from_sink_quirks(struct snd_pcm_runtime *runtime,
> +					     int sink_quirks)
> +{
> +	if (sink_quirks & SINK_QUIRK_2_6_CHANNELS) {
> +		snd_pcm_hw_constraint_list(runtime, 0,
> +					   SNDRV_PCM_HW_PARAM_CHANNELS,
> +					   &hw_constraints_2_6_channels);
> +	}
> +}
> +
> +static bool ca_allowed_by_sink_quirks(int ca, int sink_quirks)
> +{
> +	if (sink_quirks & SINK_QUIRK_2_6_CHANNELS) {
> +		int ordered_ca = get_channel_allocation_order(ca);
> +		if (channel_allocations[ordered_ca].channels != 2 &&
> +		    channel_allocations[ordered_ca].channels != 6)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +
>  /*
>   * The transformation takes two steps:
>   *
> @@ -1482,6 +1527,8 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
>  			snd_hda_spdif_ctls_unassign(codec, pin_idx);
>  			return -ENODEV;
>  		}
> +
> +		set_constraints_from_sink_quirks(substream->runtime, per_pin->sink_quirks);
>  	}
>  
>  	/* Store the updated parameters */
> @@ -1518,6 +1565,24 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
>  	return 0;
>  }
>  
> +static void hdmi_set_sink_quirks(struct hdmi_spec_per_pin *per_pin)
> +{
> +	int i;
> +
> +	per_pin->sink_quirks = 0;
> +
> +	if (!per_pin->sink_eld.eld_valid)
> +		return;
> +
> +	for (i = 0; i < ARRAY_SIZE(sink_quirks); i++) {
> +		if (per_pin->sink_eld.info.manufacture_id == sink_quirks[i].mfg_id &&
> +		    !strcmp(per_pin->sink_eld.info.monitor_name, sink_quirks[i].name)) {
> +			per_pin->sink_quirks = sink_quirks[i].quirks;
> +			break;
> +		}
> +	}
> +}
> +
>  static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
>  {
>  	struct hda_jack_tbl *jack;
> @@ -1592,6 +1657,8 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
>  		pin_eld->eld_size = eld->eld_size;
>  		pin_eld->info = eld->info;
>  
> +		hdmi_set_sink_quirks(per_pin);
> +
>  		/*
>  		 * Re-setup pin and infoframe. This is needed e.g. when
>  		 * - sink is first plugged-in (infoframe is not set up if !monitor_present)
> @@ -1916,6 +1983,8 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
>  	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
>  	struct hda_codec *codec = info->private_data;
>  	struct hdmi_spec *spec = codec->spec;
> +	int pin_idx = kcontrol->private_value;
> +	struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
>  	unsigned int __user *dst;
>  	int chs, count = 0;
>  
> @@ -1934,7 +2003,8 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
>  			int type = spec->ops.chmap_cea_alloc_validate_get_type(cap, chs);
>  			unsigned int tlv_chmap[8];
>  
> -			if (type < 0)
> +			if (type < 0 ||
> +			    !ca_allowed_by_sink_quirks(cap->ca_index, per_pin->sink_quirks))
>  				continue;
>  			if (size < 8)
>  				return -ENOMEM;
> @@ -2007,7 +2077,7 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
>  	if (!memcmp(chmap, per_pin->chmap, sizeof(chmap)))
>  		return 0;
>  	ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
> -	if (ca < 0)
> +	if (ca < 0 || !ca_allowed_by_sink_quirks(ca, per_pin->sink_quirks))
>  		return -EINVAL;
>  	if (spec->ops.chmap_validate) {
>  		err = spec->ops.chmap_validate(ca, ARRAY_SIZE(chmap), chmap);
> -- 
> 1.8.4.5
>

Patch
diff mbox

diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 0cb5b89cd0c8..0a9c19cdb511 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -64,6 +64,9 @@  struct hdmi_spec_per_cvt {
 /* max. connections to a widget */
 #define HDA_MAX_CONNECTIONS	32
 
+/* sink supports only CAs with 2/6 channels */
+#define SINK_QUIRK_2_6_CHANNELS	0x0001
+
 struct hdmi_spec_per_pin {
 	hda_nid_t pin_nid;
 	int num_mux_nids;
@@ -83,6 +86,8 @@  struct hdmi_spec_per_pin {
 	bool chmap_set;		/* channel-map override by ALSA API? */
 	unsigned char chmap[8]; /* ALSA API channel-map */
 	char pcm_name[8];	/* filled in build_pcm callbacks */
+	int sink_quirks;	/* sink-specific quirks */
+
 #ifdef CONFIG_PROC_FS
 	struct snd_info_entry *proc_entry;
 #endif
@@ -342,6 +347,13 @@  static struct cea_channel_speaker_allocation channel_allocations[] = {
 { .ca_index = 0x31,  .speakers = { FRW,  FLW,  RR,  RL,  FC,  LFE,  FR,  FL } },
 };
 
+static struct hdmi_sink_quirks {
+	int mfg_id;
+	char name[ELD_MAX_MNL + 1];
+	int quirks;
+} sink_quirks[] = {
+	{ .mfg_id = 0xed40, .name = "Statement D2", .quirks = SINK_QUIRK_2_6_CHANNELS },
+};
 
 /*
  * HDMI routines
@@ -640,6 +652,39 @@  static int get_channel_allocation_order(int ca)
 	return i;
 }
 
+static unsigned int channels_2_6[] = {
+        2, 6
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_2_6_channels = {
+        .count = ARRAY_SIZE(channels_2_6),
+        .list = channels_2_6,
+        .mask = 0,
+};
+
+static void set_constraints_from_sink_quirks(struct snd_pcm_runtime *runtime,
+					     int sink_quirks)
+{
+	if (sink_quirks & SINK_QUIRK_2_6_CHANNELS) {
+		snd_pcm_hw_constraint_list(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_CHANNELS,
+					   &hw_constraints_2_6_channels);
+	}
+}
+
+static bool ca_allowed_by_sink_quirks(int ca, int sink_quirks)
+{
+	if (sink_quirks & SINK_QUIRK_2_6_CHANNELS) {
+		int ordered_ca = get_channel_allocation_order(ca);
+		if (channel_allocations[ordered_ca].channels != 2 &&
+		    channel_allocations[ordered_ca].channels != 6)
+			return false;
+	}
+
+	return true;
+}
+
+
 /*
  * The transformation takes two steps:
  *
@@ -1482,6 +1527,8 @@  static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
 			snd_hda_spdif_ctls_unassign(codec, pin_idx);
 			return -ENODEV;
 		}
+
+		set_constraints_from_sink_quirks(substream->runtime, per_pin->sink_quirks);
 	}
 
 	/* Store the updated parameters */
@@ -1518,6 +1565,24 @@  static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
 	return 0;
 }
 
+static void hdmi_set_sink_quirks(struct hdmi_spec_per_pin *per_pin)
+{
+	int i;
+
+	per_pin->sink_quirks = 0;
+
+	if (!per_pin->sink_eld.eld_valid)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(sink_quirks); i++) {
+		if (per_pin->sink_eld.info.manufacture_id == sink_quirks[i].mfg_id &&
+		    !strcmp(per_pin->sink_eld.info.monitor_name, sink_quirks[i].name)) {
+			per_pin->sink_quirks = sink_quirks[i].quirks;
+			break;
+		}
+	}
+}
+
 static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
 {
 	struct hda_jack_tbl *jack;
@@ -1592,6 +1657,8 @@  static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
 		pin_eld->eld_size = eld->eld_size;
 		pin_eld->info = eld->info;
 
+		hdmi_set_sink_quirks(per_pin);
+
 		/*
 		 * Re-setup pin and infoframe. This is needed e.g. when
 		 * - sink is first plugged-in (infoframe is not set up if !monitor_present)
@@ -1916,6 +1983,8 @@  static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
 	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
 	struct hda_codec *codec = info->private_data;
 	struct hdmi_spec *spec = codec->spec;
+	int pin_idx = kcontrol->private_value;
+	struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
 	unsigned int __user *dst;
 	int chs, count = 0;
 
@@ -1934,7 +2003,8 @@  static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
 			int type = spec->ops.chmap_cea_alloc_validate_get_type(cap, chs);
 			unsigned int tlv_chmap[8];
 
-			if (type < 0)
+			if (type < 0 ||
+			    !ca_allowed_by_sink_quirks(cap->ca_index, per_pin->sink_quirks))
 				continue;
 			if (size < 8)
 				return -ENOMEM;
@@ -2007,7 +2077,7 @@  static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
 	if (!memcmp(chmap, per_pin->chmap, sizeof(chmap)))
 		return 0;
 	ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
-	if (ca < 0)
+	if (ca < 0 || !ca_allowed_by_sink_quirks(ca, per_pin->sink_quirks))
 		return -EINVAL;
 	if (spec->ops.chmap_validate) {
 		err = spec->ops.chmap_validate(ca, ARRAY_SIZE(chmap), chmap);