ATI/AMD HDMI/DP codecs do not include standard HDA ELD (EDID-like data) support.
In place of providing access to an ELD buffer, various vendor-specific verbs are provided to provide the relevant information. Revision ID 3 and later (0x100300 as reported by procfs codec#X) have support for providing more information than the previous revisions (but only if supported by the display driver).
Generate ELD from the information provided by the vendor-specific verbs on ATI/AMD codecs.
The specification is available at: http://www.x.org/docs/AMD/AMD_HDA_verbs_v2.pdf
v2: moved code to hda_eld.c and cleaned it up v3: adapted to hdmi_ops infrastructure
Signed-off-by: Anssi Hannula anssi.hannula@iki.fi Tested-by: Peter Frühberger fritsch@xbmc.org # v2 Tested-by: Olivier Langlois olivier@trillion01.com # v2 --- sound/pci/hda/hda_eld.c | 151 +++++++++++++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_local.h | 4 ++ sound/pci/hda/patch_hdmi.c | 9 +++ 3 files changed, 164 insertions(+)
diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c index f62356c..32d3e38 100644 --- a/sound/pci/hda/hda_eld.c +++ b/sound/pci/hda/hda_eld.c @@ -2,6 +2,7 @@ * 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 @@ -633,3 +634,153 @@ void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e, hinfo->maxbps = min(hinfo->maxbps, maxbps); hinfo->channels_max = min(hinfo->channels_max, channels_max); } + + +/* ATI/AMD specific stuff (ELD emulation) */ + +#define ATI_VERB_SET_AUDIO_DESCRIPTOR 0x776 +#define ATI_VERB_SET_SINK_INFO_INDEX 0x780 +#define ATI_VERB_GET_SPEAKER_ALLOCATION 0xf70 +#define ATI_VERB_GET_AUDIO_DESCRIPTOR 0xf76 +#define ATI_VERB_GET_AUDIO_VIDEO_DELAY 0xf7b +#define ATI_VERB_GET_SINK_INFO_INDEX 0xf80 +#define ATI_VERB_GET_SINK_INFO_DATA 0xf81 + +#define ATI_SPKALLOC_SPKALLOC 0x007f +#define ATI_SPKALLOC_TYPE_HDMI 0x0100 +#define ATI_SPKALLOC_TYPE_DISPLAYPORT 0x0200 + +/* first three bytes are just standard SAD */ +#define ATI_AUDIODESC_CHANNELS 0x00000007 +#define ATI_AUDIODESC_RATES 0x0000ff00 +#define ATI_AUDIODESC_LPCM_STEREO_RATES 0xff000000 + +/* in standard HDMI VSDB format */ +#define ATI_DELAY_VIDEO_LATENCY 0x000000ff +#define ATI_DELAY_AUDIO_LATENCY 0x0000ff00 + +enum ati_sink_info_idx { + ATI_INFO_IDX_MANUFACTURER_ID = 0, + ATI_INFO_IDX_PRODUCT_ID = 1, + ATI_INFO_IDX_SINK_DESC_LEN = 2, + ATI_INFO_IDX_PORT_ID_LOW = 3, + ATI_INFO_IDX_PORT_ID_HIGH = 4, + ATI_INFO_IDX_SINK_DESC_FIRST = 5, + ATI_INFO_IDX_SINK_DESC_LAST = 22, /* max len 18 bytes */ +}; + +int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size, bool rev3_or_later) +{ + int spkalloc, ati_sad, aud_synch; + int sink_desc_len = 0; + int pos, i; + + /* ATI/AMD does not have ELD, emulate it */ + + spkalloc = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SPEAKER_ALLOCATION, 0); + + if (!spkalloc) { + snd_printd(KERN_INFO "HDMI ATI/AMD: no speaker allocation for ELD\n"); + return -EINVAL; + } + + memset(buf, 0, ELD_FIXED_BYTES + ELD_MAX_MNL + ELD_MAX_SAD * 3); + + /* version */ + buf[0] = ELD_VER_CEA_861D << 3; + + /* speaker allocation from EDID */ + buf[7] = spkalloc & ATI_SPKALLOC_SPKALLOC; + + /* is DisplayPort? */ + if (spkalloc & ATI_SPKALLOC_TYPE_DISPLAYPORT) + buf[5] |= 0x04; + + pos = ELD_FIXED_BYTES; + + if (rev3_or_later) { + int sink_info; + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_LOW); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le32(sink_info, buf + 8); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_HIGH); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le32(sink_info, buf + 12); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_MANUFACTURER_ID); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le16(sink_info, buf + 16); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PRODUCT_ID); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le16(sink_info, buf + 18); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_LEN); + sink_desc_len = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + + if (sink_desc_len > ELD_MAX_MNL) { + snd_printd(KERN_INFO "HDMI ATI/AMD: Truncating HDMI sink description with length %d\n", + sink_desc_len); + sink_desc_len = ELD_MAX_MNL; + } + + buf[4] |= sink_desc_len; + + for (i = 0; i < sink_desc_len; i++) { + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_FIRST + i); + buf[pos++] = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + } + } + + for (i = AUDIO_CODING_TYPE_LPCM; i <= AUDIO_CODING_TYPE_WMAPRO; i++) { + if (i == AUDIO_CODING_TYPE_SACD || i == AUDIO_CODING_TYPE_DST) + continue; /* not handled by ATI/AMD */ + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_AUDIO_DESCRIPTOR, i << 3); + ati_sad = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_DESCRIPTOR, 0); + + if (ati_sad & ATI_AUDIODESC_RATES) { + /* format is supported, copy SAD as-is */ + buf[pos++] = (ati_sad & 0x0000ff) >> 0; + buf[pos++] = (ati_sad & 0x00ff00) >> 8; + buf[pos++] = (ati_sad & 0xff0000) >> 16; + } + + if (i == AUDIO_CODING_TYPE_LPCM + && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES) + && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES) >> 16 != (ati_sad & ATI_AUDIODESC_RATES)) { + /* for PCM there is a separate stereo rate mask */ + buf[pos++] = ((ati_sad & 0x000000ff) & ~ATI_AUDIODESC_CHANNELS) | 0x1; + /* rates from the extra byte */ + buf[pos++] = (ati_sad & 0xff000000) >> 24; + buf[pos++] = (ati_sad & 0x00ff0000) >> 16; + } + } + + if (pos == ELD_FIXED_BYTES + sink_desc_len) { + snd_printd(KERN_INFO "HDMI ATI/AMD: no audio descriptors for ELD\n"); + return -EINVAL; + } + + aud_synch = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_VIDEO_DELAY, 0); + if ((aud_synch & ATI_DELAY_VIDEO_LATENCY) && (aud_synch & ATI_DELAY_AUDIO_LATENCY)) { + int video_latency = (aud_synch & ATI_DELAY_VIDEO_LATENCY) - 1; + int audio_latency = ((aud_synch & ATI_DELAY_AUDIO_LATENCY) >> 8) - 1; + + if (video_latency > audio_latency) + buf[6] = min(video_latency - audio_latency, 0xfa); + } + + /* Baseline length */ + buf[2] = pos - 4; + + /* SAD count */ + buf[5] |= ((pos - ELD_FIXED_BYTES - sink_desc_len) / 3) << 4; + + *eld_size = pos; + + return 0; +} diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 46cddd4..d398b64 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -763,6 +763,10 @@ void snd_hdmi_show_eld(struct parsed_hdmi_eld *e); void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e, struct hda_pcm_stream *hinfo);
+int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size, + bool rev3_or_later); + #ifdef CONFIG_PROC_FS void snd_hdmi_print_eld_info(struct hdmi_eld *eld, struct snd_info_buffer *buffer); diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 4ef5676..75ba933 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -2822,6 +2822,14 @@ static int patch_nvhdmi_8ch_7x(struct hda_codec *codec) #define ATI_MULTICHANNEL_MODE_PAIRED 0 #define ATI_MULTICHANNEL_MODE_SINGLE 1
+static int atihdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size) +{ + /* call hda_eld.c ATI/AMD-specific function */ + return snd_hdmi_get_eld_ati(codec, nid, buf, eld_size, + is_amdhdmi_rev3_or_later(codec)); +} + static void atihdmi_pin_setup_infoframe(struct hda_codec *codec, hda_nid_t pin_nid, int ca, int active_channels, int conn_type) { @@ -3049,6 +3057,7 @@ static int patch_atihdmi(struct hda_codec *codec)
spec = codec->spec;
+ spec->ops.pin_get_eld = atihdmi_pin_get_eld; spec->ops.pin_get_slot_channel = atihdmi_pin_get_slot_channel; spec->ops.pin_set_slot_channel = atihdmi_pin_set_slot_channel; spec->ops.pin_setup_infoframe = atihdmi_pin_setup_infoframe;