diff options
author | Dmitry Baryshkov <dmitry.baryshkov@linaro.org> | 2025-01-24 23:14:30 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2025-02-05 13:04:00 +0100 |
commit | 1b0e9d7f76c9bb6bd1a345ef033ae7fe5e77649c (patch) | |
tree | 57102aa939d0d29759078faa0728c1276a171b47 /sound/core | |
parent | e8d04a92484e6504bb0c98426db54bfc82a6934f (diff) |
ALSA: hda/hdmi: extract common interface for ELD handling
Other HDMI-related cards (e.g. hdmi-codec) are also using the ELD.
Exrtact common set of interfaces for handling the ELD.
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20250124-alsa-hdmi-codec-eld-v1-1-bad045cfaeac@linaro.org
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/pcm_drm_eld.c | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c index 1cdca4d4fc9c..688eefce82fa 100644 --- a/sound/core/pcm_drm_eld.c +++ b/sound/core/pcm_drm_eld.c @@ -5,8 +5,10 @@ #include <linux/bitfield.h> #include <linux/export.h> #include <linux/hdmi.h> +#include <linux/unaligned.h> #include <drm/drm_edid.h> #include <drm/drm_eld.h> +#include <sound/info.h> #include <sound/pcm.h> #include <sound/pcm_drm_eld.h> @@ -162,3 +164,388 @@ int snd_pcm_hw_constraint_eld(struct snd_pcm_runtime *runtime, void *eld) return ret; } EXPORT_SYMBOL_GPL(snd_pcm_hw_constraint_eld); + +#define SND_PRINT_RATES_ADVISED_BUFSIZE 80 +#define SND_PRINT_BITS_ADVISED_BUFSIZE 16 +#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80 + +static const char * const eld_connection_type_names[4] = { + "HDMI", + "DisplayPort", + "2-reserved", + "3-reserved" +}; + +static const char * const cea_audio_coding_type_names[] = { + /* 0 */ "undefined", + /* 1 */ "LPCM", + /* 2 */ "AC-3", + /* 3 */ "MPEG1", + /* 4 */ "MP3", + /* 5 */ "MPEG2", + /* 6 */ "AAC-LC", + /* 7 */ "DTS", + /* 8 */ "ATRAC", + /* 9 */ "DSD (One Bit Audio)", + /* 10 */ "E-AC-3/DD+ (Dolby Digital Plus)", + /* 11 */ "DTS-HD", + /* 12 */ "MLP (Dolby TrueHD)", + /* 13 */ "DST", + /* 14 */ "WMAPro", + /* 15 */ "HE-AAC", + /* 16 */ "HE-AACv2", + /* 17 */ "MPEG Surround", +}; + +static const char * const cea_speaker_allocation_names[] = { + /* 0 */ "FL/FR", + /* 1 */ "LFE", + /* 2 */ "FC", + /* 3 */ "RL/RR", + /* 4 */ "RC", + /* 5 */ "FLC/FRC", + /* 6 */ "RLC/RRC", + /* 7 */ "FLW/FRW", + /* 8 */ "FLH/FRH", + /* 9 */ "TC", + /* 10 */ "FCH", +}; + +/* + * SS1:SS0 index => sample size + */ +static const int cea_sample_sizes[4] = { + 0, /* 0: Refer to Stream Header */ + ELD_PCM_BITS_16, /* 1: 16 bits */ + ELD_PCM_BITS_20, /* 2: 20 bits */ + ELD_PCM_BITS_24, /* 3: 24 bits */ +}; + +/* + * SF2:SF1:SF0 index => sampling frequency + */ +static const int cea_sampling_frequencies[8] = { + 0, /* 0: Refer to Stream Header */ + SNDRV_PCM_RATE_32000, /* 1: 32000Hz */ + SNDRV_PCM_RATE_44100, /* 2: 44100Hz */ + SNDRV_PCM_RATE_48000, /* 3: 48000Hz */ + SNDRV_PCM_RATE_88200, /* 4: 88200Hz */ + SNDRV_PCM_RATE_96000, /* 5: 96000Hz */ + SNDRV_PCM_RATE_176400, /* 6: 176400Hz */ + SNDRV_PCM_RATE_192000, /* 7: 192000Hz */ +}; + +#define GRAB_BITS(buf, byte, lowbit, bits) \ +({ \ + BUILD_BUG_ON(lowbit > 7); \ + BUILD_BUG_ON(bits > 8); \ + BUILD_BUG_ON(bits <= 0); \ + \ + (buf[byte] >> (lowbit)) & ((1 << (bits)) - 1); \ +}) + +static void hdmi_update_short_audio_desc(struct device *dev, + struct snd_cea_sad *a, + const unsigned char *buf) +{ + int i; + int val; + + val = GRAB_BITS(buf, 1, 0, 7); + a->rates = 0; + for (i = 0; i < 7; i++) + if (val & (1 << i)) + a->rates |= cea_sampling_frequencies[i + 1]; + + a->channels = GRAB_BITS(buf, 0, 0, 3); + a->channels++; + + a->sample_bits = 0; + a->max_bitrate = 0; + + a->format = GRAB_BITS(buf, 0, 3, 4); + switch (a->format) { + case AUDIO_CODING_TYPE_REF_STREAM_HEADER: + dev_info(dev, "HDMI: audio coding type 0 not expected\n"); + break; + + case AUDIO_CODING_TYPE_LPCM: + val = GRAB_BITS(buf, 2, 0, 3); + for (i = 0; i < 3; i++) + if (val & (1 << i)) + a->sample_bits |= cea_sample_sizes[i + 1]; + break; + + case AUDIO_CODING_TYPE_AC3: + case AUDIO_CODING_TYPE_MPEG1: + case AUDIO_CODING_TYPE_MP3: + case AUDIO_CODING_TYPE_MPEG2: + case AUDIO_CODING_TYPE_AACLC: + case AUDIO_CODING_TYPE_DTS: + case AUDIO_CODING_TYPE_ATRAC: + a->max_bitrate = GRAB_BITS(buf, 2, 0, 8); + a->max_bitrate *= 8000; + break; + + case AUDIO_CODING_TYPE_SACD: + break; + + case AUDIO_CODING_TYPE_EAC3: + break; + + case AUDIO_CODING_TYPE_DTS_HD: + break; + + case AUDIO_CODING_TYPE_MLP: + break; + + case AUDIO_CODING_TYPE_DST: + break; + + case AUDIO_CODING_TYPE_WMAPRO: + a->profile = GRAB_BITS(buf, 2, 0, 3); + break; + + case AUDIO_CODING_TYPE_REF_CXT: + a->format = GRAB_BITS(buf, 2, 3, 5); + if (a->format == AUDIO_CODING_XTYPE_HE_REF_CT || + a->format >= AUDIO_CODING_XTYPE_FIRST_RESERVED) { + dev_info(dev, + "HDMI: audio coding xtype %d not expected\n", + a->format); + a->format = 0; + } else + a->format += AUDIO_CODING_TYPE_HE_AAC - + AUDIO_CODING_XTYPE_HE_AAC; + break; + } +} + +/* + * Be careful, ELD buf could be totally rubbish! + */ +int snd_parse_eld(struct device *dev, struct snd_parsed_hdmi_eld *e, + const unsigned char *buf, int size) +{ + int mnl; + int i; + + memset(e, 0, sizeof(*e)); + e->eld_ver = GRAB_BITS(buf, 0, 3, 5); + if (e->eld_ver != ELD_VER_CEA_861D && + e->eld_ver != ELD_VER_PARTIAL) { + dev_info(dev, "HDMI: Unknown ELD version %d\n", e->eld_ver); + goto out_fail; + } + + e->baseline_len = GRAB_BITS(buf, 2, 0, 8); + mnl = GRAB_BITS(buf, 4, 0, 5); + e->cea_edid_ver = GRAB_BITS(buf, 4, 5, 3); + + e->support_hdcp = GRAB_BITS(buf, 5, 0, 1); + e->support_ai = GRAB_BITS(buf, 5, 1, 1); + e->conn_type = GRAB_BITS(buf, 5, 2, 2); + e->sad_count = GRAB_BITS(buf, 5, 4, 4); + + e->aud_synch_delay = GRAB_BITS(buf, 6, 0, 8) * 2; + e->spk_alloc = GRAB_BITS(buf, 7, 0, 7); + + e->port_id = get_unaligned_le64(buf + 8); + + /* not specified, but the spec's tendency is little endian */ + e->manufacture_id = get_unaligned_le16(buf + 16); + e->product_id = get_unaligned_le16(buf + 18); + + if (mnl > ELD_MAX_MNL) { + dev_info(dev, "HDMI: MNL is reserved value %d\n", mnl); + goto out_fail; + } else if (ELD_FIXED_BYTES + mnl > size) { + dev_info(dev, "HDMI: out of range MNL %d\n", mnl); + goto out_fail; + } else + strscpy(e->monitor_name, buf + ELD_FIXED_BYTES, mnl + 1); + + for (i = 0; i < e->sad_count; i++) { + if (ELD_FIXED_BYTES + mnl + 3 * (i + 1) > size) { + dev_info(dev, "HDMI: out of range SAD %d\n", i); + goto out_fail; + } + hdmi_update_short_audio_desc(dev, e->sad + i, + buf + ELD_FIXED_BYTES + mnl + 3 * i); + } + + /* + * HDMI sink's ELD info cannot always be retrieved for now, e.g. + * in console or for audio devices. Assume the highest speakers + * configuration, to _not_ prohibit multi-channel audio playback. + */ + if (!e->spk_alloc) + e->spk_alloc = 0xffff; + + return 0; + +out_fail: + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_parse_eld); + +/* + * SNDRV_PCM_RATE_* and AC_PAR_PCM values don't match, print correct rates with + * hdmi-specific routine. + */ +static void hdmi_print_pcm_rates(int pcm, char *buf, int buflen) +{ + static const unsigned int alsa_rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, + 88200, 96000, 176400, 192000, 384000 + }; + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(alsa_rates); i++) + if (pcm & (1 << i)) + j += scnprintf(buf + j, buflen - j, " %d", + alsa_rates[i]); + + buf[j] = '\0'; /* necessary when j == 0 */ +} + +static void eld_print_pcm_bits(int pcm, char *buf, int buflen) +{ + static const unsigned int bits[] = { 8, 16, 20, 24, 32 }; + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++) + if (pcm & (ELD_PCM_BITS_8 << i)) + j += scnprintf(buf + j, buflen - j, " %d", bits[i]); + + buf[j] = '\0'; /* necessary when j == 0 */ +} + +static void hdmi_show_short_audio_desc(struct device *dev, + struct snd_cea_sad *a) +{ + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; + char buf2[8 + SND_PRINT_BITS_ADVISED_BUFSIZE] = ", bits ="; + + if (!a->format) + return; + + hdmi_print_pcm_rates(a->rates, buf, sizeof(buf)); + + if (a->format == AUDIO_CODING_TYPE_LPCM) + eld_print_pcm_bits(a->sample_bits, buf2 + 8, sizeof(buf2) - 8); + else if (a->max_bitrate) + snprintf(buf2, sizeof(buf2), + ", max bitrate = %d", a->max_bitrate); + else + buf2[0] = '\0'; + + dev_dbg(dev, + "HDMI: supports coding type %s: channels = %d, rates =%s%s\n", + cea_audio_coding_type_names[a->format], + a->channels, buf, buf2); +} + +static void snd_eld_print_channel_allocation(int spk_alloc, char *buf, int buflen) +{ + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) { + if (spk_alloc & (1 << i)) + j += scnprintf(buf + j, buflen - j, " %s", + cea_speaker_allocation_names[i]); + } + buf[j] = '\0'; /* necessary when j == 0 */ +} + +void snd_show_eld(struct device *dev, struct snd_parsed_hdmi_eld *e) +{ + int i; + + dev_dbg(dev, "HDMI: detected monitor %s at connection type %s\n", + e->monitor_name, + eld_connection_type_names[e->conn_type]); + + if (e->spk_alloc) { + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + + snd_eld_print_channel_allocation(e->spk_alloc, buf, sizeof(buf)); + dev_dbg(dev, "HDMI: available speakers:%s\n", buf); + } + + for (i = 0; i < e->sad_count; i++) + hdmi_show_short_audio_desc(dev, e->sad + i); +} +EXPORT_SYMBOL_GPL(snd_show_eld); + +#ifdef CONFIG_SND_PROC_FS +static void hdmi_print_sad_info(int i, struct snd_cea_sad *a, + struct snd_info_buffer *buffer) +{ + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; + + snd_iprintf(buffer, "sad%d_coding_type\t[0x%x] %s\n", + i, a->format, cea_audio_coding_type_names[a->format]); + snd_iprintf(buffer, "sad%d_channels\t\t%d\n", i, a->channels); + + hdmi_print_pcm_rates(a->rates, buf, sizeof(buf)); + snd_iprintf(buffer, "sad%d_rates\t\t[0x%x]%s\n", i, a->rates, buf); + + if (a->format == AUDIO_CODING_TYPE_LPCM) { + eld_print_pcm_bits(a->sample_bits, buf, sizeof(buf)); + snd_iprintf(buffer, "sad%d_bits\t\t[0x%x]%s\n", + i, a->sample_bits, buf); + } + + if (a->max_bitrate) + snd_iprintf(buffer, "sad%d_max_bitrate\t%d\n", + i, a->max_bitrate); + + if (a->profile) + snd_iprintf(buffer, "sad%d_profile\t\t%d\n", i, a->profile); +} + +void snd_print_eld_info(struct snd_parsed_hdmi_eld *e, + struct snd_info_buffer *buffer) +{ + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + int i; + static const char * const eld_version_names[32] = { + "reserved", + "reserved", + "CEA-861D or below", + [3 ... 30] = "reserved", + [31] = "partial" + }; + static const char * const cea_edid_version_names[8] = { + "no CEA EDID Timing Extension block present", + "CEA-861", + "CEA-861-A", + "CEA-861-B, C or D", + [4 ... 7] = "reserved" + }; + + snd_iprintf(buffer, "monitor_name\t\t%s\n", e->monitor_name); + snd_iprintf(buffer, "connection_type\t\t%s\n", + eld_connection_type_names[e->conn_type]); + snd_iprintf(buffer, "eld_version\t\t[0x%x] %s\n", e->eld_ver, + eld_version_names[e->eld_ver]); + snd_iprintf(buffer, "edid_version\t\t[0x%x] %s\n", e->cea_edid_ver, + cea_edid_version_names[e->cea_edid_ver]); + snd_iprintf(buffer, "manufacture_id\t\t0x%x\n", e->manufacture_id); + snd_iprintf(buffer, "product_id\t\t0x%x\n", e->product_id); + snd_iprintf(buffer, "port_id\t\t\t0x%llx\n", (long long)e->port_id); + snd_iprintf(buffer, "support_hdcp\t\t%d\n", e->support_hdcp); + snd_iprintf(buffer, "support_ai\t\t%d\n", e->support_ai); + snd_iprintf(buffer, "audio_sync_delay\t%d\n", e->aud_synch_delay); + + snd_eld_print_channel_allocation(e->spk_alloc, buf, sizeof(buf)); + snd_iprintf(buffer, "speakers\t\t[0x%x]%s\n", e->spk_alloc, buf); + + snd_iprintf(buffer, "sad_count\t\t%d\n", e->sad_count); + + for (i = 0; i < e->sad_count; i++) + hdmi_print_sad_info(i, e->sad + i, buffer); +} +EXPORT_SYMBOL_GPL(snd_print_eld_info); +#endif /* CONFIG_SND_PROC_FS */ |