[alsa-devel] [PATCH 2/2] ASoC: hdmi-codec: add channel mapping control
Takashi Sakamoto
o-takashi at sakamocchi.jp
Sun Dec 11 07:09:52 CET 2016
On Dec 9 2016 01:37, Arnaud Pouliquen wrote:
> Add user interface to provide channel mapping.
> In a first step this control is read only.
>
> As TLV type, the control provides all configurations available for
> HDMI sink(ELD), and provides current channel mapping selected by codec
> based on ELD and number of channels specified by user on open.
> When control is called before the number of the channel is specified
> (i.e. hw_params is set), it returns all channels set to UNKNOWN.
>
> Notice that SNDRV_CTL_TLVT_CHMAP_FIXED is used for all mappings,
> as no information is available from HDMI driver to allow channel swapping.
>
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen at st.com>
> ---
> sound/soc/codecs/hdmi-codec.c | 346 +++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 345 insertions(+), 1 deletion(-)
>
> diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
> index f27d115..0cb83a3 100644
> --- a/sound/soc/codecs/hdmi-codec.c
> +++ b/sound/soc/codecs/hdmi-codec.c
> @@ -18,12 +18,137 @@
> #include <sound/pcm.h>
> #include <sound/pcm_params.h>
> #include <sound/soc.h>
> +#include <sound/tlv.h>
> #include <sound/pcm_drm_eld.h>
> #include <sound/hdmi-codec.h>
> #include <sound/pcm_iec958.h>
>
> #include <drm/drm_crtc.h> /* This is only to get MAX_ELD_BYTES */
>
> +#define HDMI_MAX_SPEAKERS 8
> +
> +/*
> + * CEA speaker placement for HDMI 1.4:
> + *
> + * FL FLC FC FRC FR FRW
> + *
> + * LFE
> + *
> + * RL RLC RC RRC RR
> + *
> + * Speaker placement has to be extended to support HDMI 2.0
> + */
> +enum hdmi_codec_cea_spk_placement {
> + FL = (1 << 0), /* Front Left */
> + FC = (1 << 1), /* Front Center */
> + FR = (1 << 2), /* Front Right */
> + FLC = (1 << 3), /* Front Left Center */
> + FRC = (1 << 4), /* Front Right Center */
> + RL = (1 << 5), /* Rear Left */
> + RC = (1 << 6), /* Rear Center */
> + RR = (1 << 7), /* Rear Right */
> + RLC = (1 << 8), /* Rear Left Center */
> + RRC = (1 << 9), /* Rear Right Center */
> + LFE = (1 << 10), /* Low Frequency Effect */
> +};
BIT() macro in "linux/bitops.h" is available.
> +
> +/*
> + * ELD Speaker allocation bits in the CEA Speaker Allocation data block
> + */
> +static int hdmi_codec_eld_spk_alloc_bits[] = {
> + [0] = FL | FR,
> + [1] = LFE,
> + [2] = FC,
> + [3] = RL | RR,
> + [4] = RC,
> + [5] = FLC | FRC,
> + [6] = RLC | RRC,
> +};
Please put this kind of invariant table into .rodata section with
'const' modifier.
> +
> +struct hdmi_codec_channel_map_table {
> + unsigned char map; /* ALSA API channel map position */
> + int spk_mask; /* speaker position bit mask */
> +};
> +
> +static struct hdmi_codec_channel_map_table hdmi_codec_map_table[] = {
> + { SNDRV_CHMAP_FL, FL },
> + { SNDRV_CHMAP_FR, FR },
> + { SNDRV_CHMAP_RL, RL },
> + { SNDRV_CHMAP_RR, RR },
> + { SNDRV_CHMAP_LFE, LFE },
> + { SNDRV_CHMAP_FC, FC },
> + { SNDRV_CHMAP_RLC, RLC },
> + { SNDRV_CHMAP_RRC, RRC },
> + { SNDRV_CHMAP_RC, RC },
> + { SNDRV_CHMAP_FLC, FLC },
> + { SNDRV_CHMAP_FRC, FRC },
> + {} /* terminator */
> +};
In this case, the table can be put into snd_hdac_spk_to_chmap().
> +
> +/*
> + * cea Speaker allocation structure
> + */
> +struct hdmi_codec_cea_spk_alloc {
> + int ca_index;
> + int speakers[HDMI_MAX_SPEAKERS];
> +
> + /* Derived values, computed during init */
> + int channels;
> + int spk_mask;
> + int spk_na_mask;
> +};
> +
> +/*
> + * This is an ordered list!
> + *
> + * The preceding ones have better chances to be selected by
> + * hdmi_channel_allocation().
The function is not defined and implemented. It takes developers to be
puzzled.
> + */
> +static struct hdmi_codec_cea_spk_alloc hdmi_codec_channel_alloc[] = {
> +/* channel: 7 6 5 4 3 2 1 0 */
> +{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } },
> + /* 2.1 */
> +{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } },
> + /* Dolby Surround */
> +{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } },
> + /* surround51 */
> +{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } },
> + /* surround40 */
> +{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } },
> + /* surround41 */
> +{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } },
> + /* surround50 */
> +{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } },
> + /* 6.1 */
> +{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } },
> + /* surround71 */
> +{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } },
> +
> +{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } },
> +{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } },
> +{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } },
> +{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } },
> +{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } },
> +{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } },
> +{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } },
> +{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } },
> +{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } },
> +{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } },
> +{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } },
> +{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } },
> +{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } },
> +{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } },
> +{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } },
> +{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } },
> +{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } },
> +{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } },
> +{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } },
> +{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } },
> +{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } },
> +{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } },
> +{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } },
> +};
Ditto.
> +
> struct hdmi_codec_priv {
> struct hdmi_codec_pdata hcd;
> struct snd_soc_dai_driver *daidrv;
> @@ -32,6 +157,7 @@ struct hdmi_codec_priv {
> struct snd_pcm_substream *current_stream;
> struct snd_pcm_hw_constraint_list ratec;
> uint8_t eld[MAX_ELD_BYTES];
> + unsigned int chmap[HDMI_MAX_SPEAKERS];
> };
>
> static const struct snd_soc_dapm_widget hdmi_widgets[] = {
> @@ -70,6 +196,201 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
> return 0;
> }
>
> +static int hdmi_codec_chmap_ctl_info(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_info *uinfo)
> +{
> + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
> + uinfo->count = HDMI_MAX_SPEAKERS;
> + uinfo->value.integer.min = 0;
> + uinfo->value.integer.max = SNDRV_CHMAP_LAST;
> +
> + return 0;
> +}
> +
> +static int hdmi_codec_spk_mask_from_alloc(int spk_alloc)
> +{
> + int i;
> + int spk_mask = hdmi_codec_eld_spk_alloc_bits[0];
> +
> + for (i = 0; i < ARRAY_SIZE(hdmi_codec_eld_spk_alloc_bits); i++) {
> + if (spk_alloc & (1 << i))
> + spk_mask |= hdmi_codec_eld_spk_alloc_bits[i];
> + }
> +
> + return spk_mask;
> +}
> +
> +static int hdmi_codec_chmap_ctl_get(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
> + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component);
> + int i;
> +
> + memset(ucontrol->value.integer.value, 0,
> + sizeof(ucontrol->value.integer.value));
> +
> + mutex_lock(&hcp->current_stream_lock);
> + if (hcp->current_stream)
> + for (i = 0; i < HDMI_MAX_SPEAKERS; i++)
> + ucontrol->value.integer.value[i] = hcp->chmap[i];
> +
> + mutex_unlock(&hcp->current_stream_lock);
> +
> + return 0;
> +}
> +
> +/* From speaker bit mask to ALSA API channel position */
> +static int snd_hdac_spk_to_chmap(int spk)
> +{
> + struct hdmi_codec_channel_map_table *t = hdmi_codec_map_table;
> +
> + for (; t->map; t++) {
> + if (t->spk_mask == spk)
> + return t->map;
> + }
> +
> + return 0;
> +}
Why hdac? Are there some relationship between HDA controller and table
you added?
> +/**
> + * hdmi_codec_cea_init_channel_alloc:
> + * Compute derived values in hdmi_codec_channel_alloc[].
> + * spk_na_mask is used to store unused channels in mid of the channel
> + * allocations. These particular channels are then considered as active channels
> + * For instance:
> + * CA_ID 0x02: CA = (FL, FR, 0, FC) => spk_na_mask = 0x04, channels = 4
> + * CA_ID 0x04: CA = (FL, FR, 0, 0, RC) => spk_na_mask = 0x03C, channels = 5
> + */
> +static void hdmi_codec_cea_init_channel_alloc(void)
> +{
> + int i, j, k, last;
> + struct hdmi_codec_cea_spk_alloc *p;
> +
> + for (i = 0; i < ARRAY_SIZE(hdmi_codec_channel_alloc); i++) {
> + p = hdmi_codec_channel_alloc + i;
> + p->spk_mask = 0;
> + p->spk_na_mask = 0;
> + last = HDMI_MAX_SPEAKERS;
> + for (j = 0, k = 7; j < HDMI_MAX_SPEAKERS; j++, k--) {
> + if (p->speakers[j]) {
> + p->spk_mask |= p->speakers[j];
> + if (last == HDMI_MAX_SPEAKERS)
> + last = j;
> + } else if (last != HDMI_MAX_SPEAKERS) {
> + p->spk_na_mask |= 1 << k;
> + }
> + }
> + p->channels = 8 - last;
> + }
> +}
> +
> +static int hdmi_codec_get_ch_alloc_table_idx(struct hdmi_codec_priv *hcp,
> + unsigned char channels)
> +{
> + int i, spk_alloc, spk_mask;
> + struct hdmi_codec_cea_spk_alloc *cap = hdmi_codec_channel_alloc;
> +
> + spk_alloc = drm_eld_get_spk_alloc(hcp->eld);
> + spk_mask = hdmi_codec_spk_mask_from_alloc(spk_alloc);
> +
> + for (i = 0; i < ARRAY_SIZE(hdmi_codec_channel_alloc); i++, cap++) {
> + if (cap->channels != channels)
> + continue;
> + if (!(cap->spk_mask == (spk_mask & cap->spk_mask)))
> + continue;
> + return i;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static void hdmi_cea_alloc_to_tlv_chmap(struct hdmi_codec_cea_spk_alloc *cap,
> + unsigned int *chmap)
> +{
> + int count = 0;
> + int c, spk;
> +
> + /* Detect unused channels in cea caps, tag them as N/A channel in TLV */
> + for (c = 0; c < HDMI_MAX_SPEAKERS; c++) {
> + spk = cap->speakers[7 - c];
> + if (cap->spk_na_mask & BIT(c))
> + chmap[count++] = SNDRV_CHMAP_NA;
> + else
> + chmap[count++] = snd_hdac_spk_to_chmap(spk);
> + }
> +}
> +
> +static int hdmi_codec_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
> + unsigned int size, unsigned int __user *tlv)
> +{
> + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
> + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component);
> + unsigned int __user *dst;
> + int chs, count = 0;
> + int num_ca = ARRAY_SIZE(hdmi_codec_channel_alloc);
> + unsigned long max_chs;
> + int spk_alloc, spk_mask;
> +
> + if (size < 8)
> + return -ENOMEM;
> +
> + if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
> + return -EFAULT;
> + size -= 8;
> + dst = tlv + 2;
> +
> + spk_alloc = drm_eld_get_spk_alloc(hcp->eld);
> + spk_mask = hdmi_codec_spk_mask_from_alloc(spk_alloc);
> +
> + max_chs = hweight_long(spk_mask);
> +
> + for (chs = 2; chs <= max_chs; chs++) {
> + int i;
> + struct hdmi_codec_cea_spk_alloc *cap;
> +
> + cap = hdmi_codec_channel_alloc;
> + for (i = 0; i < num_ca; i++, cap++) {
> + int chs_bytes = chs * 4;
> + unsigned int tlv_chmap[HDMI_MAX_SPEAKERS];
> +
> + if (cap->channels != chs)
> + continue;
> +
> + if (!(cap->spk_mask == (spk_mask & cap->spk_mask)))
> + continue;
> +
> + /*
> + * Channel mapping is fixed as hdmi codec capability
> + * is not know.
> + */
> + if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, 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;
> + hdmi_cea_alloc_to_tlv_chmap(cap, tlv_chmap);
> +
> + if (copy_to_user(dst, tlv_chmap, chs_bytes))
> + return -EFAULT;
> + dst += chs;
> + }
> + }
> +
> + if (put_user(count, tlv + 1))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
This function has a bug to cause buffer-over-run in user space because
applications can request with a small buffer.
> static const struct snd_kcontrol_new hdmi_controls[] = {
> {
> .access = SNDRV_CTL_ELEM_ACCESS_READ |
> @@ -79,6 +400,17 @@ static const struct snd_kcontrol_new hdmi_controls[] = {
> .info = hdmi_eld_ctl_info,
> .get = hdmi_eld_ctl_get,
> },
> + {
> + .access = SNDRV_CTL_ELEM_ACCESS_READ |
> + SNDRV_CTL_ELEM_ACCESS_TLV_READ |
> + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
> + SNDRV_CTL_ELEM_ACCESS_VOLATILE,
> + .iface = SNDRV_CTL_ELEM_IFACE_PCM,
> + .name = "Playback Channel Map",
> + .info = hdmi_codec_chmap_ctl_info,
> + .get = hdmi_codec_chmap_ctl_get,
> + .tlv.c = hdmi_codec_chmap_ctl_tlv,
> + },
> };
If you can keep the same interface for applications as
'snd_pcm_add_chmap_ctls()' have, it's better to integrate the function
to have different tables/callbacks depending on drivers.
> static int hdmi_codec_new_stream(struct snd_pcm_substream *substream,
> @@ -164,7 +496,7 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
> .dig_subframe = { 0 },
> }
> };
> - int ret;
> + int ret, idx;
>
> dev_dbg(dai->dev, "%s() width %d rate %d channels %d\n", __func__,
> params_width(params), params_rate(params),
> @@ -191,6 +523,16 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
> hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
> hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
>
> + /* Select a channel allocation that matches with ELD and pcm channels */
> + idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels);
> + if (idx < 0) {
> + dev_err(dai->dev, "Not able to map channels to speakers (%d)\n",
> + ret);
> + return idx;
> + }
> + hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_index;
> + hdmi_cea_alloc_to_tlv_chmap(&hdmi_codec_channel_alloc[idx], hcp->chmap);
> +
> hp.sample_width = params_width(params);
> hp.sample_rate = params_rate(params);
> hp.channels = params_channels(params);
> @@ -407,6 +749,8 @@ static int hdmi_codec_probe(struct platform_device *pdev)
> return ret;
> }
>
> + hdmi_codec_cea_init_channel_alloc();
> +
> dev_set_drvdata(dev, hcp);
> return 0;
> }
Regards
Takashi Sakamoto
More information about the Alsa-devel
mailing list