| From 058328afcdd3d5c9531cfac8b980d0c0db75856f Mon Sep 17 00:00:00 2001 |
| From: popcornmix <popcornmix@gmail.com> |
| Date: Mon, 20 Apr 2020 18:00:38 +0100 |
| Subject: [PATCH] vc4: Report channel mapping back to userspace |
| |
| This follows logic in hdmi-codec.c to use speaker layout |
| from ELD to choose a suitable speaker mapping based on |
| number of channels requested and signal that in audio |
| infoframe and report this back to userspace. |
| |
| This allows apps like speaker-test and kodi to get the |
| output to the right speakers. |
| |
| Signed-off-by: Dom Cobley <popcornmix@gmail.com> |
| --- |
| drivers/gpu/drm/vc4/vc4_hdmi.c | 415 +++++++++++++++++++++++++++++++++ |
| drivers/gpu/drm/vc4/vc4_hdmi.h | 3 + |
| 2 files changed, 418 insertions(+) |
| |
| --- a/drivers/gpu/drm/vc4/vc4_hdmi.c |
| +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c |
| @@ -48,6 +48,7 @@ |
| #include <sound/pcm_drm_eld.h> |
| #include <sound/pcm_params.h> |
| #include <sound/soc.h> |
| +#include <sound/tlv.h> |
| #include "media/cec.h" |
| #include "vc4_drv.h" |
| #include "vc4_hdmi.h" |
| @@ -82,6 +83,311 @@ |
| #define CEC_CLOCK_FREQ 40000 |
| #define VC4_HSM_CLOCK 163682864 |
| |
| +#define HDMI_CODEC_CHMAP_IDX_UNKNOWN -1 |
| + |
| +/* |
| + * 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 = BIT(0), /* Front Left */ |
| + FC = BIT(1), /* Front Center */ |
| + FR = BIT(2), /* Front Right */ |
| + FLC = BIT(3), /* Front Left Center */ |
| + FRC = BIT(4), /* Front Right Center */ |
| + RL = BIT(5), /* Rear Left */ |
| + RC = BIT(6), /* Rear Center */ |
| + RR = BIT(7), /* Rear Right */ |
| + RLC = BIT(8), /* Rear Left Center */ |
| + RRC = BIT(9), /* Rear Right Center */ |
| + LFE = BIT(10), /* Low Frequency Effect */ |
| +}; |
| + |
| +/* |
| + * cea Speaker allocation structure |
| + */ |
| +struct hdmi_codec_cea_spk_alloc { |
| + const int ca_id; |
| + unsigned int n_ch; |
| + unsigned long mask; |
| +}; |
| + |
| +/* Channel maps stereo HDMI */ |
| +static const struct snd_pcm_chmap_elem hdmi_codec_stereo_chmaps[] = { |
| + { .channels = 2, |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, |
| + { } |
| +}; |
| + |
| +/* Channel maps for multi-channel playbacks, up to 8 n_ch */ |
| +static const struct snd_pcm_chmap_elem hdmi_codec_8ch_chmaps[] = { |
| + { .channels = 2, /* CA_ID 0x00 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, |
| + { .channels = 4, /* CA_ID 0x01 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA } }, |
| + { .channels = 4, /* CA_ID 0x02 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC } }, |
| + { .channels = 4, /* CA_ID 0x03 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC } }, |
| + { .channels = 6, /* CA_ID 0x04 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 6, /* CA_ID 0x05 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 6, /* CA_ID 0x06 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 6, /* CA_ID 0x07 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 6, /* CA_ID 0x08 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, |
| + { .channels = 6, /* CA_ID 0x09 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, |
| + { .channels = 6, /* CA_ID 0x0A */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, |
| + { .channels = 6, /* CA_ID 0x0B */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, |
| + { .channels = 8, /* CA_ID 0x0C */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 8, /* CA_ID 0x0D */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 8, /* CA_ID 0x0E */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 8, /* CA_ID 0x0F */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, |
| + { .channels = 8, /* CA_ID 0x10 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, |
| + { .channels = 8, /* CA_ID 0x11 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, |
| + { .channels = 8, /* CA_ID 0x12 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, |
| + { .channels = 8, /* CA_ID 0x13 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, |
| + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, |
| + { .channels = 8, /* CA_ID 0x14 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x15 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x16 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x17 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x18 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x19 */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x1A */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x1B */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x1C */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x1D */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x1E */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { .channels = 8, /* CA_ID 0x1F */ |
| + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, |
| + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, |
| + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, |
| + { } |
| +}; |
| + |
| +/* |
| + * hdmi_codec_channel_alloc: speaker configuration available for CEA |
| + * |
| + * This is an ordered list that must match with hdmi_codec_8ch_chmaps struct |
| + * The preceding ones have better chances to be selected by |
| + * hdmi_codec_get_ch_alloc_table_idx(). |
| + */ |
| +static const struct hdmi_codec_cea_spk_alloc hdmi_codec_channel_alloc[] = { |
| + { .ca_id = 0x00, .n_ch = 2, |
| + .mask = FL | FR}, |
| + /* 2.1 */ |
| + { .ca_id = 0x01, .n_ch = 4, |
| + .mask = FL | FR | LFE}, |
| + /* Dolby Surround */ |
| + { .ca_id = 0x02, .n_ch = 4, |
| + .mask = FL | FR | FC }, |
| + /* surround51 */ |
| + { .ca_id = 0x0b, .n_ch = 6, |
| + .mask = FL | FR | LFE | FC | RL | RR}, |
| + /* surround40 */ |
| + { .ca_id = 0x08, .n_ch = 6, |
| + .mask = FL | FR | RL | RR }, |
| + /* surround41 */ |
| + { .ca_id = 0x09, .n_ch = 6, |
| + .mask = FL | FR | LFE | RL | RR }, |
| + /* surround50 */ |
| + { .ca_id = 0x0a, .n_ch = 6, |
| + .mask = FL | FR | FC | RL | RR }, |
| + /* 6.1 */ |
| + { .ca_id = 0x0f, .n_ch = 8, |
| + .mask = FL | FR | LFE | FC | RL | RR | RC }, |
| + /* surround71 */ |
| + { .ca_id = 0x13, .n_ch = 8, |
| + .mask = FL | FR | LFE | FC | RL | RR | RLC | RRC }, |
| + /* others */ |
| + { .ca_id = 0x03, .n_ch = 8, |
| + .mask = FL | FR | LFE | FC }, |
| + { .ca_id = 0x04, .n_ch = 8, |
| + .mask = FL | FR | RC}, |
| + { .ca_id = 0x05, .n_ch = 8, |
| + .mask = FL | FR | LFE | RC }, |
| + { .ca_id = 0x06, .n_ch = 8, |
| + .mask = FL | FR | FC | RC }, |
| + { .ca_id = 0x07, .n_ch = 8, |
| + .mask = FL | FR | LFE | FC | RC }, |
| + { .ca_id = 0x0c, .n_ch = 8, |
| + .mask = FL | FR | RC | RL | RR }, |
| + { .ca_id = 0x0d, .n_ch = 8, |
| + .mask = FL | FR | LFE | RL | RR | RC }, |
| + { .ca_id = 0x0e, .n_ch = 8, |
| + .mask = FL | FR | FC | RL | RR | RC }, |
| + { .ca_id = 0x10, .n_ch = 8, |
| + .mask = FL | FR | RL | RR | RLC | RRC }, |
| + { .ca_id = 0x11, .n_ch = 8, |
| + .mask = FL | FR | LFE | RL | RR | RLC | RRC }, |
| + { .ca_id = 0x12, .n_ch = 8, |
| + .mask = FL | FR | FC | RL | RR | RLC | RRC }, |
| + { .ca_id = 0x14, .n_ch = 8, |
| + .mask = FL | FR | FLC | FRC }, |
| + { .ca_id = 0x15, .n_ch = 8, |
| + .mask = FL | FR | LFE | FLC | FRC }, |
| + { .ca_id = 0x16, .n_ch = 8, |
| + .mask = FL | FR | FC | FLC | FRC }, |
| + { .ca_id = 0x17, .n_ch = 8, |
| + .mask = FL | FR | LFE | FC | FLC | FRC }, |
| + { .ca_id = 0x18, .n_ch = 8, |
| + .mask = FL | FR | RC | FLC | FRC }, |
| + { .ca_id = 0x19, .n_ch = 8, |
| + .mask = FL | FR | LFE | RC | FLC | FRC }, |
| + { .ca_id = 0x1a, .n_ch = 8, |
| + .mask = FL | FR | RC | FC | FLC | FRC }, |
| + { .ca_id = 0x1b, .n_ch = 8, |
| + .mask = FL | FR | LFE | RC | FC | FLC | FRC }, |
| + { .ca_id = 0x1c, .n_ch = 8, |
| + .mask = FL | FR | RL | RR | FLC | FRC }, |
| + { .ca_id = 0x1d, .n_ch = 8, |
| + .mask = FL | FR | LFE | RL | RR | FLC | FRC }, |
| + { .ca_id = 0x1e, .n_ch = 8, |
| + .mask = FL | FR | FC | RL | RR | FLC | FRC }, |
| + { .ca_id = 0x1f, .n_ch = 8, |
| + .mask = FL | FR | LFE | FC | RL | RR | FLC | FRC }, |
| +}; |
| + |
| +static unsigned long hdmi_codec_spk_mask_from_alloc(int spk_alloc) |
| +{ |
| + int i; |
| + static const unsigned long hdmi_codec_eld_spk_alloc_bits[] = { |
| + [0] = FL | FR, [1] = LFE, [2] = FC, [3] = RL | RR, |
| + [4] = RC, [5] = FLC | FRC, [6] = RLC | RRC, |
| + }; |
| + unsigned long spk_mask = 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_get_ch_alloc_table_idx(struct vc4_hdmi *vc4_hdmi, |
| + unsigned char channels) |
| +{ |
| + struct drm_connector *connector = &vc4_hdmi->connector; |
| + int i; |
| + u8 spk_alloc; |
| + unsigned long spk_mask; |
| + const struct hdmi_codec_cea_spk_alloc *cap = hdmi_codec_channel_alloc; |
| + |
| + spk_alloc = drm_eld_get_spk_alloc(connector->eld); |
| + spk_mask = hdmi_codec_spk_mask_from_alloc(spk_alloc); |
| + |
| + for (i = 0; i < ARRAY_SIZE(hdmi_codec_channel_alloc); i++, cap++) { |
| + /* If spk_alloc == 0, HDMI is unplugged return stereo config*/ |
| + if (!spk_alloc && cap->ca_id == 0) |
| + return i; |
| + if (cap->n_ch != channels) |
| + continue; |
| + if (!(cap->mask == (spk_mask & cap->mask))) |
| + continue; |
| + return i; |
| + } |
| + |
| + return -EINVAL; |
| +} |
| + |
| +static void hdmi_codec_eld_chmap(struct vc4_hdmi *vc4_hdmi) |
| +{ |
| + struct drm_connector *connector = &vc4_hdmi->connector; |
| + u8 spk_alloc; |
| + unsigned long spk_mask; |
| + |
| + spk_alloc = drm_eld_get_spk_alloc(connector->eld); |
| + spk_mask = hdmi_codec_spk_mask_from_alloc(spk_alloc); |
| + |
| + /* Detect if only stereo supported, else return 8 channels mappings */ |
| + if ((spk_mask & ~(FL | FR))) |
| + vc4_hdmi->audio.chmap = hdmi_codec_8ch_chmaps; |
| + else |
| + vc4_hdmi->audio.chmap = hdmi_codec_stereo_chmaps; |
| +} |
| + |
| static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) |
| { |
| struct drm_info_node *node = (struct drm_info_node *)m->private; |
| @@ -350,6 +656,9 @@ static void vc4_hdmi_set_audio_infoframe |
| frame.audio.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; |
| frame.audio.channels = vc4_hdmi->audio.channels; |
| |
| + /* Select a channel allocation that matches with ELD and pcm channels */ |
| + frame.audio.channel_allocation = vc4_hdmi->audio.chmap_idx; |
| + |
| vc4_hdmi_write_infoframe(encoder, &frame); |
| } |
| |
| @@ -881,6 +1190,10 @@ static int vc4_hdmi_audio_startup(struct |
| if (ret) |
| return ret; |
| |
| + /* Select chmap supported */ |
| + vc4_hdmi->audio.max_channels = 8; |
| + hdmi_codec_eld_chmap(vc4_hdmi); |
| + |
| return 0; |
| } |
| |
| @@ -967,6 +1280,7 @@ static int vc4_hdmi_audio_prepare(struct |
| u32 channel_map; |
| u32 mai_audio_format; |
| u32 mai_sample_rate; |
| + int idx; |
| |
| if (substream != vc4_hdmi->audio.substream) |
| return -EINVAL; |
| @@ -1027,6 +1341,14 @@ static int vc4_hdmi_audio_prepare(struct |
| HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config); |
| vc4_hdmi_set_n_cts(vc4_hdmi); |
| |
| + idx = hdmi_codec_get_ch_alloc_table_idx(vc4_hdmi, vc4_hdmi->audio.channels); |
| + if (idx < 0) { |
| + DRM_ERROR("Not able to map channels to speakers (%d)\n", idx); |
| + vc4_hdmi->audio.chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; |
| + } else { |
| + vc4_hdmi->audio.chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; |
| + } |
| + |
| return 0; |
| } |
| |
| @@ -1145,6 +1467,89 @@ static int vc4_spdif_mask_get(struct snd |
| return 0; |
| } |
| |
| +/* |
| + * ALSA API channel-map control callbacks |
| + */ |
| +static int vc4_chmap_ctl_info(struct snd_kcontrol *kcontrol, |
| + struct snd_ctl_elem_info *uinfo) |
| +{ |
| + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| + struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); |
| + |
| + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| + uinfo->count = vc4_hdmi->audio.max_channels; |
| + uinfo->value.integer.min = 0; |
| + uinfo->value.integer.max = SNDRV_CHMAP_LAST; |
| + |
| + return 0; |
| +} |
| + |
| +static int vc4_chmap_ctl_get(struct snd_kcontrol *kcontrol, |
| + struct snd_ctl_elem_value *ucontrol) |
| +{ |
| + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| + struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); |
| + unsigned const char *map; |
| + unsigned int i; |
| + |
| + if (!vc4_hdmi->audio.chmap) |
| + return -EINVAL; |
| + |
| + map = vc4_hdmi->audio.chmap[vc4_hdmi->audio.chmap_idx].map; |
| + |
| + for (i = 0; i < vc4_hdmi->audio.max_channels; i++) { |
| + if (vc4_hdmi->audio.chmap_idx == HDMI_CODEC_CHMAP_IDX_UNKNOWN) |
| + ucontrol->value.integer.value[i] = 0; |
| + else |
| + ucontrol->value.integer.value[i] = map[i]; |
| + } |
| + return 0; |
| +} |
| + |
| +static int vc4_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 vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); |
| + const struct snd_pcm_chmap_elem *map; |
| + unsigned int __user *dst; |
| + int c, count = 0; |
| + |
| + if (!vc4_hdmi->audio.chmap) |
| + return -EINVAL; |
| + if (size < 8) |
| + return -ENOMEM; |
| + if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) |
| + return -EFAULT; |
| + size -= 8; |
| + dst = tlv + 2; |
| + for (map = vc4_hdmi->audio.chmap; map->channels; map++) { |
| + int chs_bytes = map->channels * 4; |
| + //if (!valid_chmap_channels(info, map->channels)) |
| + // continue; |
| + if (size < 8) |
| + return -ENOMEM; |
| + 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; |
| + for (c = 0; c < map->channels; c++) { |
| + if (put_user(map->map[c], dst)) |
| + return -EFAULT; |
| + dst++; |
| + } |
| + } |
| + if (put_user(count, tlv + 1)) |
| + return -EFAULT; |
| + return 0; |
| +} |
| + |
| static const struct snd_kcontrol_new vc4_hdmi_audio_controls[] = { |
| { |
| .access = SNDRV_CTL_ELEM_ACCESS_READ | |
| @@ -1167,6 +1572,16 @@ static const struct snd_kcontrol_new vc4 |
| .info = vc4_spdif_info, |
| .get = vc4_spdif_mask_get, |
| }, |
| + { |
| + .access = SNDRV_CTL_ELEM_ACCESS_READ | |
| + SNDRV_CTL_ELEM_ACCESS_TLV_READ | |
| + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, |
| + .iface = SNDRV_CTL_ELEM_IFACE_PCM, |
| + .name = "Playback Channel Map", |
| + .info = vc4_chmap_ctl_info, |
| + .get = vc4_chmap_ctl_get, |
| + .tlv.c = vc4_chmap_ctl_tlv, |
| + }, |
| }; |
| |
| static const struct snd_soc_dapm_widget vc4_hdmi_audio_widgets[] = { |
| --- a/drivers/gpu/drm/vc4/vc4_hdmi.h |
| +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h |
| @@ -117,6 +117,9 @@ struct vc4_hdmi_audio { |
| bool streaming; |
| |
| unsigned char iec_status[4]; |
| + const struct snd_pcm_chmap_elem *chmap; |
| + unsigned int chmap_idx; |
| + unsigned int max_channels; |
| }; |
| |
| /* General HDMI hardware state. */ |