| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Generic routines and proc interface for ELD(EDID Like Data) information |
| * |
| * Copyright(c) 2008 Intel Corporation. |
| * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi> |
| * |
| * Authors: |
| * Wu Fengguang <wfg@linux.intel.com> |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <sound/core.h> |
| #include <sound/hda_chmap.h> |
| #include <sound/hda_codec.h> |
| #include "hda_local.h" |
| |
| enum cea_edid_versions { |
| CEA_EDID_VER_NONE = 0, |
| CEA_EDID_VER_CEA861 = 1, |
| CEA_EDID_VER_CEA861A = 2, |
| CEA_EDID_VER_CEA861BCD = 3, |
| CEA_EDID_VER_RESERVED = 4, |
| }; |
| |
| /* |
| * The following two lists are shared between |
| * - HDMI audio InfoFrame (source to sink) |
| * - CEA E-EDID Extension (sink to source) |
| */ |
| |
| static unsigned int hdmi_get_eld_data(struct hda_codec *codec, hda_nid_t nid, |
| int byte_index) |
| { |
| unsigned int val; |
| |
| val = snd_hda_codec_read(codec, nid, 0, |
| AC_VERB_GET_HDMI_ELDD, byte_index); |
| #ifdef BE_PARANOID |
| codec_info(codec, "HDMI: ELD data byte %d: 0x%x\n", byte_index, val); |
| #endif |
| return val; |
| } |
| |
| int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid) |
| { |
| return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_HDMI_DIP_SIZE, |
| AC_DIPSIZE_ELD_BUF); |
| } |
| |
| int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid, |
| unsigned char *buf, int *eld_size) |
| { |
| int i; |
| int ret = 0; |
| int size; |
| |
| /* |
| * ELD size is initialized to zero in caller function. If no errors and |
| * ELD is valid, actual eld_size is assigned. |
| */ |
| |
| size = snd_hdmi_get_eld_size(codec, nid); |
| if (size == 0) { |
| /* wfg: workaround for ASUS P5E-VM HDMI board */ |
| codec_info(codec, "HDMI: ELD buf size is 0, force 128\n"); |
| size = 128; |
| } |
| if (size < ELD_FIXED_BYTES || size > ELD_MAX_SIZE) { |
| codec_info(codec, "HDMI: invalid ELD buf size %d\n", size); |
| return -ERANGE; |
| } |
| |
| /* set ELD buffer */ |
| for (i = 0; i < size; i++) { |
| unsigned int val = hdmi_get_eld_data(codec, nid, i); |
| /* |
| * Graphics driver might be writing to ELD buffer right now. |
| * Just abort. The caller will repoll after a while. |
| */ |
| if (!(val & AC_ELDD_ELD_VALID)) { |
| codec_info(codec, "HDMI: invalid ELD data byte %d\n", i); |
| ret = -EINVAL; |
| goto error; |
| } |
| val &= AC_ELDD_ELD_DATA; |
| /* |
| * The first byte cannot be zero. This can happen on some DVI |
| * connections. Some Intel chips may also need some 250ms delay |
| * to return non-zero ELD data, even when the graphics driver |
| * correctly writes ELD content before setting ELD_valid bit. |
| */ |
| if (!val && !i) { |
| codec_dbg(codec, "HDMI: 0 ELD data\n"); |
| ret = -EINVAL; |
| goto error; |
| } |
| buf[i] = val; |
| } |
| |
| *eld_size = size; |
| error: |
| return ret; |
| } |
| |
| #ifdef CONFIG_SND_PROC_FS |
| void snd_hdmi_print_eld_info(struct hdmi_eld *eld, |
| struct snd_info_buffer *buffer, |
| hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid) |
| { |
| snd_iprintf(buffer, "monitor_present\t\t%d\n", eld->monitor_present); |
| snd_iprintf(buffer, "eld_valid\t\t%d\n", eld->eld_valid); |
| snd_iprintf(buffer, "codec_pin_nid\t\t0x%x\n", pin_nid); |
| snd_iprintf(buffer, "codec_dev_id\t\t0x%x\n", dev_id); |
| snd_iprintf(buffer, "codec_cvt_nid\t\t0x%x\n", cvt_nid); |
| |
| if (!eld->eld_valid) |
| return; |
| |
| snd_print_eld_info(&eld->info, buffer); |
| } |
| |
| void snd_hdmi_write_eld_info(struct hdmi_eld *eld, |
| struct snd_info_buffer *buffer) |
| { |
| struct snd_parsed_hdmi_eld *e = &eld->info; |
| char line[64]; |
| char name[64]; |
| char *sname; |
| long long val; |
| unsigned int n; |
| |
| while (!snd_info_get_line(buffer, line, sizeof(line))) { |
| if (sscanf(line, "%s %llx", name, &val) != 2) |
| continue; |
| /* |
| * We don't allow modification to these fields: |
| * monitor_name manufacture_id product_id |
| * eld_version edid_version |
| */ |
| if (!strcmp(name, "monitor_present")) |
| eld->monitor_present = val; |
| else if (!strcmp(name, "eld_valid")) |
| eld->eld_valid = val; |
| else if (!strcmp(name, "connection_type")) |
| e->conn_type = val; |
| else if (!strcmp(name, "port_id")) |
| e->port_id = val; |
| else if (!strcmp(name, "support_hdcp")) |
| e->support_hdcp = val; |
| else if (!strcmp(name, "support_ai")) |
| e->support_ai = val; |
| else if (!strcmp(name, "audio_sync_delay")) |
| e->aud_synch_delay = val; |
| else if (!strcmp(name, "speakers")) |
| e->spk_alloc = val; |
| else if (!strcmp(name, "sad_count")) |
| e->sad_count = val; |
| else if (!strncmp(name, "sad", 3)) { |
| sname = name + 4; |
| n = name[3] - '0'; |
| if (name[4] >= '0' && name[4] <= '9') { |
| sname++; |
| n = 10 * n + name[4] - '0'; |
| } |
| if (n >= ELD_MAX_SAD) |
| continue; |
| if (!strcmp(sname, "_coding_type")) |
| e->sad[n].format = val; |
| else if (!strcmp(sname, "_channels")) |
| e->sad[n].channels = val; |
| else if (!strcmp(sname, "_rates")) |
| e->sad[n].rates = val; |
| else if (!strcmp(sname, "_bits")) |
| e->sad[n].sample_bits = val; |
| else if (!strcmp(sname, "_max_bitrate")) |
| e->sad[n].max_bitrate = val; |
| else if (!strcmp(sname, "_profile")) |
| e->sad[n].profile = val; |
| if (n >= e->sad_count) |
| e->sad_count = n + 1; |
| } |
| } |
| } |
| #endif /* CONFIG_SND_PROC_FS */ |
| |
| /* update PCM info based on ELD */ |
| void snd_hdmi_eld_update_pcm_info(struct snd_parsed_hdmi_eld *e, |
| struct hda_pcm_stream *hinfo) |
| { |
| u32 rates; |
| u64 formats; |
| unsigned int maxbps; |
| unsigned int channels_max; |
| int i; |
| |
| /* assume basic audio support (the basic audio flag is not in ELD; |
| * however, all audio capable sinks are required to support basic |
| * audio) */ |
| rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | |
| SNDRV_PCM_RATE_48000; |
| formats = SNDRV_PCM_FMTBIT_S16_LE; |
| maxbps = 16; |
| channels_max = 2; |
| for (i = 0; i < e->sad_count; i++) { |
| struct snd_cea_sad *a = &e->sad[i]; |
| rates |= a->rates; |
| if (a->channels > channels_max) |
| channels_max = a->channels; |
| if (a->format == AUDIO_CODING_TYPE_LPCM) { |
| if (a->sample_bits & ELD_PCM_BITS_20) { |
| formats |= SNDRV_PCM_FMTBIT_S32_LE; |
| if (maxbps < 20) |
| maxbps = 20; |
| } |
| if (a->sample_bits & ELD_PCM_BITS_24) { |
| formats |= SNDRV_PCM_FMTBIT_S32_LE; |
| if (maxbps < 24) |
| maxbps = 24; |
| } |
| } |
| } |
| |
| /* restrict the parameters by the values the codec provides */ |
| hinfo->rates &= rates; |
| hinfo->formats &= formats; |
| hinfo->maxbps = min(hinfo->maxbps, maxbps); |
| hinfo->channels_max = min(hinfo->channels_max, channels_max); |
| } |