From: Anssi Hannula Date: Thu, 24 Oct 2013 18:10:36 +0000 (+0300) Subject: ALSA: hda - hdmi: Add ELD emulation for ATI/AMD codecs X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=89250f84644676c1ed6a09acef1033e14596a402;p=linux-beck.git ALSA: hda - hdmi: Add ELD emulation for ATI/AMD codecs 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 Tested-by: Peter Frühberger # v2 Tested-by: Olivier Langlois # v2 Signed-off-by: Takashi Iwai --- diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c index f62356c2f54c..32d3e3855a6e 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 * * Authors: * Wu Fengguang @@ -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 46cddd4c7b72..d398b648bb5d 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 1114be018e5a..0307ae87bef3 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -2821,6 +2821,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) { @@ -3048,6 +3056,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;