diff --git a/include/sound/hda_chmap.h b/include/sound/hda_chmap.h index e20d219a0304..babd445c7505 100644 --- a/include/sound/hda_chmap.h +++ b/include/sound/hda_chmap.h @@ -36,6 +36,8 @@ struct hdac_chmap_ops { int (*chmap_validate)(struct hdac_chmap *hchmap, int ca, int channels, unsigned char *chmap); + int (*get_spk_alloc)(struct hdac_device *hdac, int pcm_idx); + void (*get_chmap)(struct hdac_device *hdac, int pcm_idx, unsigned char *chmap); void (*set_chmap)(struct hdac_device *hdac, int pcm_idx, diff --git a/sound/hda/hdmi_chmap.c b/sound/hda/hdmi_chmap.c index d7ec86263828..c6c75e7e0981 100644 --- a/sound/hda/hdmi_chmap.c +++ b/sound/hda/hdmi_chmap.c @@ -625,13 +625,30 @@ static void hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap, WARN_ON(count != channels); } +static int spk_mask_from_spk_alloc(int spk_alloc) +{ + int i; + int spk_mask = eld_speaker_allocation_bits[0]; + + for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { + if (spk_alloc & (1 << i)) + spk_mask |= eld_speaker_allocation_bits[i]; + } + + return spk_mask; +} + static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *tlv) { struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); struct hdac_chmap *chmap = info->private_data; + int pcm_idx = kcontrol->private_value; unsigned int __user *dst; int chs, count = 0; + unsigned long max_chs; + int type; + int spk_alloc, spk_mask; if (size < 8) return -ENOMEM; @@ -639,40 +656,59 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, return -EFAULT; size -= 8; dst = tlv + 2; - for (chs = 2; chs <= chmap->channels_max; chs++) { + + spk_alloc = chmap->ops.get_spk_alloc(chmap->hdac, pcm_idx); + spk_mask = spk_mask_from_spk_alloc(spk_alloc); + + max_chs = hweight_long(spk_mask); + + for (chs = 2; chs <= max_chs; chs++) { int i; struct hdac_cea_channel_speaker_allocation *cap; cap = channel_allocations; for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) { int chs_bytes = chs * 4; - int type = chmap->ops.chmap_cea_alloc_validate_get_type( - chmap, cap, chs); unsigned int tlv_chmap[8]; - if (type < 0) + if (cap->channels != chs) continue; + + if (!(cap->spk_mask == (spk_mask & cap->spk_mask))) + continue; + + type = chmap->ops.chmap_cea_alloc_validate_get_type( + chmap, cap, chs); + if (type < 0) + return -ENODEV; if (size < 8) return -ENOMEM; + if (put_user(type, dst) || put_user(chs_bytes, dst + 1)) return -EFAULT; + dst += 2; size -= 8; count += 8; + if (size < chs_bytes) return -ENOMEM; + size -= chs_bytes; count += chs_bytes; chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap, tlv_chmap, chs); + if (copy_to_user(dst, tlv_chmap, chs_bytes)) return -EFAULT; dst += chs; } } + if (put_user(count, tlv + 1)) return -EFAULT; + return 0; } diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 4833c7bdd1e8..9452384d2000 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -1837,6 +1837,18 @@ static const struct hda_pcm_ops generic_ops = { .cleanup = generic_hdmi_playback_pcm_cleanup, }; +static int hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx) +{ + struct hda_codec *codec = container_of(hdac, struct hda_codec, core); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); + + if (!per_pin) + return 0; + + return per_pin->sink_eld.info.spk_alloc; +} + static void hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx, unsigned char *chmap) { @@ -2165,6 +2177,7 @@ static int alloc_generic_hdmi(struct hda_codec *codec) spec->chmap.ops.get_chmap = hdmi_get_chmap; spec->chmap.ops.set_chmap = hdmi_set_chmap; spec->chmap.ops.is_pcm_attached = is_hdmi_pcm_attached; + spec->chmap.ops.get_spk_alloc = hdmi_get_spk_alloc, codec->spec = spec; hdmi_array_init(spec, 4);