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