| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * ATI/AMD codec support |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/unaligned.h> |
| #include <sound/core.h> |
| #include <sound/tlv.h> |
| #include <sound/hdaudio.h> |
| #include <sound/hda_codec.h> |
| #include "hda_local.h" |
| #include "hdmi_local.h" |
| |
| #define is_amdhdmi_rev3_or_later(codec) \ |
| ((codec)->core.vendor_id == 0x1002aa01 && \ |
| ((codec)->core.revision_id & 0xff00) >= 0x0300) |
| #define has_amd_full_remap_support(codec) is_amdhdmi_rev3_or_later(codec) |
| |
| /* ATI/AMD specific HDA pin verbs, see the AMD HDA Verbs specification */ |
| #define ATI_VERB_SET_CHANNEL_ALLOCATION 0x771 |
| #define ATI_VERB_SET_DOWNMIX_INFO 0x772 |
| #define ATI_VERB_SET_MULTICHANNEL_01 0x777 |
| #define ATI_VERB_SET_MULTICHANNEL_23 0x778 |
| #define ATI_VERB_SET_MULTICHANNEL_45 0x779 |
| #define ATI_VERB_SET_MULTICHANNEL_67 0x77a |
| #define ATI_VERB_SET_HBR_CONTROL 0x77c |
| #define ATI_VERB_SET_MULTICHANNEL_1 0x785 |
| #define ATI_VERB_SET_MULTICHANNEL_3 0x786 |
| #define ATI_VERB_SET_MULTICHANNEL_5 0x787 |
| #define ATI_VERB_SET_MULTICHANNEL_7 0x788 |
| #define ATI_VERB_SET_MULTICHANNEL_MODE 0x789 |
| #define ATI_VERB_GET_CHANNEL_ALLOCATION 0xf71 |
| #define ATI_VERB_GET_DOWNMIX_INFO 0xf72 |
| #define ATI_VERB_GET_MULTICHANNEL_01 0xf77 |
| #define ATI_VERB_GET_MULTICHANNEL_23 0xf78 |
| #define ATI_VERB_GET_MULTICHANNEL_45 0xf79 |
| #define ATI_VERB_GET_MULTICHANNEL_67 0xf7a |
| #define ATI_VERB_GET_HBR_CONTROL 0xf7c |
| #define ATI_VERB_GET_MULTICHANNEL_1 0xf85 |
| #define ATI_VERB_GET_MULTICHANNEL_3 0xf86 |
| #define ATI_VERB_GET_MULTICHANNEL_5 0xf87 |
| #define ATI_VERB_GET_MULTICHANNEL_7 0xf88 |
| #define ATI_VERB_GET_MULTICHANNEL_MODE 0xf89 |
| |
| /* AMD specific HDA cvt verbs */ |
| #define ATI_VERB_SET_RAMP_RATE 0x770 |
| #define ATI_VERB_GET_RAMP_RATE 0xf70 |
| |
| #define ATI_OUT_ENABLE 0x1 |
| |
| #define ATI_MULTICHANNEL_MODE_PAIRED 0 |
| #define ATI_MULTICHANNEL_MODE_SINGLE 1 |
| |
| #define ATI_HBR_CAPABLE 0x01 |
| #define ATI_HBR_ENABLE 0x10 |
| |
| /* ATI/AMD specific 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 */ |
| }; |
| |
| static int 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 <= 0) { |
| codec_info(codec, "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) { |
| codec_info(codec, "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 <= 0) |
| continue; |
| |
| 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) { |
| codec_info(codec, "HDMI ATI/AMD: no audio descriptors for ELD\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * HDMI VSDB latency format: |
| * separately for both audio and video: |
| * 0 field not valid or unknown latency |
| * [1..251] msecs = (x-1)*2 (max 500ms with x = 251 = 0xfb) |
| * 255 audio/video not supported |
| * |
| * HDA latency format: |
| * single value indicating video latency relative to audio: |
| * 0 unknown or 0ms |
| * [1..250] msecs = x*2 (max 500ms with x = 250 = 0xfa) |
| * [251..255] reserved |
| */ |
| 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_hdmi = (aud_synch & ATI_DELAY_VIDEO_LATENCY); |
| int audio_latency_hdmi = (aud_synch & ATI_DELAY_AUDIO_LATENCY) >> 8; |
| |
| if (video_latency_hdmi <= 0xfb && audio_latency_hdmi <= 0xfb && |
| video_latency_hdmi > audio_latency_hdmi) |
| buf[6] = video_latency_hdmi - audio_latency_hdmi; |
| /* else unknown/invalid or 0ms or video ahead of audio, so use zero */ |
| } |
| |
| /* SAD count */ |
| buf[5] |= ((pos - ELD_FIXED_BYTES - sink_desc_len) / 3) << 4; |
| |
| /* Baseline ELD block length is 4-byte aligned */ |
| pos = round_up(pos, 4); |
| |
| /* Baseline ELD length (4-byte header is not counted in) */ |
| buf[2] = (pos - 4) / 4; |
| |
| *eld_size = pos; |
| |
| return 0; |
| } |
| |
| static int atihdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid, |
| int dev_id, unsigned char *buf, int *eld_size) |
| { |
| WARN_ON(dev_id != 0); |
| /* call hda_eld.c ATI/AMD-specific function */ |
| return 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 dev_id, int ca, |
| int active_channels, int conn_type) |
| { |
| WARN_ON(dev_id != 0); |
| snd_hda_codec_write(codec, pin_nid, 0, ATI_VERB_SET_CHANNEL_ALLOCATION, ca); |
| } |
| |
| static int atihdmi_paired_swap_fc_lfe(int pos) |
| { |
| /* |
| * ATI/AMD have automatic FC/LFE swap built-in |
| * when in pairwise mapping mode. |
| */ |
| |
| switch (pos) { |
| /* see channel_allocations[].speakers[] */ |
| case 2: return 3; |
| case 3: return 2; |
| default: return pos; |
| } |
| } |
| |
| static int atihdmi_paired_chmap_validate(struct hdac_chmap *chmap, |
| int ca, int chs, unsigned char *map) |
| { |
| struct hdac_cea_channel_speaker_allocation *cap; |
| int i, j; |
| |
| /* check that only channel pairs need to be remapped on old pre-rev3 ATI/AMD */ |
| |
| cap = snd_hdac_get_ch_alloc_from_ca(ca); |
| for (i = 0; i < chs; ++i) { |
| int mask = snd_hdac_chmap_to_spk_mask(map[i]); |
| bool ok = false; |
| bool companion_ok = false; |
| |
| if (!mask) |
| continue; |
| |
| for (j = 0 + i % 2; j < 8; j += 2) { |
| int chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j); |
| |
| if (cap->speakers[chan_idx] == mask) { |
| /* channel is in a supported position */ |
| ok = true; |
| |
| if (i % 2 == 0 && i + 1 < chs) { |
| /* even channel, check the odd companion */ |
| int comp_chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j + 1); |
| int comp_mask_req = snd_hdac_chmap_to_spk_mask(map[i+1]); |
| int comp_mask_act = cap->speakers[comp_chan_idx]; |
| |
| if (comp_mask_req == comp_mask_act) |
| companion_ok = true; |
| else |
| return -EINVAL; |
| } |
| break; |
| } |
| } |
| |
| if (!ok) |
| return -EINVAL; |
| |
| if (companion_ok) |
| i++; /* companion channel already checked */ |
| } |
| |
| return 0; |
| } |
| |
| static int atihdmi_pin_set_slot_channel(struct hdac_device *hdac, |
| hda_nid_t pin_nid, int hdmi_slot, int stream_channel) |
| { |
| struct hda_codec *codec = hdac_to_hda_codec(hdac); |
| int verb; |
| int ati_channel_setup = 0; |
| |
| if (hdmi_slot > 7) |
| return -EINVAL; |
| |
| if (!has_amd_full_remap_support(codec)) { |
| hdmi_slot = atihdmi_paired_swap_fc_lfe(hdmi_slot); |
| |
| /* In case this is an odd slot but without stream channel, do not |
| * disable the slot since the corresponding even slot could have a |
| * channel. In case neither have a channel, the slot pair will be |
| * disabled when this function is called for the even slot. |
| */ |
| if (hdmi_slot % 2 != 0 && stream_channel == 0xf) |
| return 0; |
| |
| hdmi_slot -= hdmi_slot % 2; |
| |
| if (stream_channel != 0xf) |
| stream_channel -= stream_channel % 2; |
| } |
| |
| verb = ATI_VERB_SET_MULTICHANNEL_01 + hdmi_slot/2 + (hdmi_slot % 2) * 0x00e; |
| |
| /* ati_channel_setup format: [7..4] = stream_channel_id, [1] = mute, [0] = enable */ |
| |
| if (stream_channel != 0xf) |
| ati_channel_setup = (stream_channel << 4) | ATI_OUT_ENABLE; |
| |
| return snd_hda_codec_write(codec, pin_nid, 0, verb, ati_channel_setup); |
| } |
| |
| static int atihdmi_pin_get_slot_channel(struct hdac_device *hdac, |
| hda_nid_t pin_nid, int asp_slot) |
| { |
| struct hda_codec *codec = hdac_to_hda_codec(hdac); |
| bool was_odd = false; |
| int ati_asp_slot = asp_slot; |
| int verb; |
| int ati_channel_setup; |
| |
| if (asp_slot > 7) |
| return -EINVAL; |
| |
| if (!has_amd_full_remap_support(codec)) { |
| ati_asp_slot = atihdmi_paired_swap_fc_lfe(asp_slot); |
| if (ati_asp_slot % 2 != 0) { |
| ati_asp_slot -= 1; |
| was_odd = true; |
| } |
| } |
| |
| verb = ATI_VERB_GET_MULTICHANNEL_01 + ati_asp_slot/2 + (ati_asp_slot % 2) * 0x00e; |
| |
| ati_channel_setup = snd_hda_codec_read(codec, pin_nid, 0, verb, 0); |
| |
| if (!(ati_channel_setup & ATI_OUT_ENABLE)) |
| return 0xf; |
| |
| return ((ati_channel_setup & 0xf0) >> 4) + !!was_odd; |
| } |
| |
| static int atihdmi_paired_chmap_cea_alloc_validate_get_type( |
| struct hdac_chmap *chmap, |
| struct hdac_cea_channel_speaker_allocation *cap, |
| int channels) |
| { |
| int c; |
| |
| /* |
| * Pre-rev3 ATI/AMD codecs operate in a paired channel mode, so |
| * we need to take that into account (a single channel may take 2 |
| * channel slots if we need to carry a silent channel next to it). |
| * On Rev3+ AMD codecs this function is not used. |
| */ |
| int chanpairs = 0; |
| |
| /* We only produce even-numbered channel count TLVs */ |
| if ((channels % 2) != 0) |
| return -1; |
| |
| for (c = 0; c < 7; c += 2) { |
| if (cap->speakers[c] || cap->speakers[c+1]) |
| chanpairs++; |
| } |
| |
| if (chanpairs * 2 != channels) |
| return -1; |
| |
| return SNDRV_CTL_TLVT_CHMAP_PAIRED; |
| } |
| |
| static void atihdmi_paired_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap, |
| struct hdac_cea_channel_speaker_allocation *cap, |
| unsigned int *chmap, int channels) |
| { |
| /* produce paired maps for pre-rev3 ATI/AMD codecs */ |
| int count = 0; |
| int c; |
| |
| for (c = 7; c >= 0; c--) { |
| int chan = 7 - atihdmi_paired_swap_fc_lfe(7 - c); |
| int spk = cap->speakers[chan]; |
| |
| if (!spk) { |
| /* add N/A channel if the companion channel is occupied */ |
| if (cap->speakers[chan + (chan % 2 ? -1 : 1)]) |
| chmap[count++] = SNDRV_CHMAP_NA; |
| |
| continue; |
| } |
| |
| chmap[count++] = snd_hdac_spk_to_chmap(spk); |
| } |
| |
| WARN_ON(count != channels); |
| } |
| |
| static int atihdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid, |
| int dev_id, bool hbr) |
| { |
| int hbr_ctl, hbr_ctl_new; |
| |
| WARN_ON(dev_id != 0); |
| |
| hbr_ctl = snd_hda_codec_read(codec, pin_nid, 0, ATI_VERB_GET_HBR_CONTROL, 0); |
| if (hbr_ctl >= 0 && (hbr_ctl & ATI_HBR_CAPABLE)) { |
| if (hbr) |
| hbr_ctl_new = hbr_ctl | ATI_HBR_ENABLE; |
| else |
| hbr_ctl_new = hbr_ctl & ~ATI_HBR_ENABLE; |
| |
| codec_dbg(codec, |
| "%s: NID=0x%x, %shbr-ctl=0x%x\n", |
| __func__, |
| pin_nid, |
| hbr_ctl == hbr_ctl_new ? "" : "new-", |
| hbr_ctl_new); |
| |
| if (hbr_ctl != hbr_ctl_new) |
| snd_hda_codec_write(codec, pin_nid, 0, |
| ATI_VERB_SET_HBR_CONTROL, |
| hbr_ctl_new); |
| |
| } else if (hbr) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int atihdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid, |
| hda_nid_t pin_nid, int dev_id, |
| u32 stream_tag, int format) |
| { |
| if (is_amdhdmi_rev3_or_later(codec)) { |
| int ramp_rate = 180; /* default as per AMD spec */ |
| /* disable ramp-up/down for non-pcm as per AMD spec */ |
| if (format & AC_FMT_TYPE_NON_PCM) |
| ramp_rate = 0; |
| |
| snd_hda_codec_write(codec, cvt_nid, 0, ATI_VERB_SET_RAMP_RATE, ramp_rate); |
| } |
| |
| return snd_hda_hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id, |
| stream_tag, format); |
| } |
| |
| |
| static int atihdmi_init(struct hda_codec *codec) |
| { |
| struct hdmi_spec *spec = codec->spec; |
| int pin_idx, err; |
| |
| err = snd_hda_hdmi_generic_init(codec); |
| |
| if (err) |
| return err; |
| |
| for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { |
| struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); |
| |
| /* make sure downmix information in infoframe is zero */ |
| snd_hda_codec_write(codec, per_pin->pin_nid, 0, ATI_VERB_SET_DOWNMIX_INFO, 0); |
| |
| /* enable channel-wise remap mode if supported */ |
| if (has_amd_full_remap_support(codec)) |
| snd_hda_codec_write(codec, per_pin->pin_nid, 0, |
| ATI_VERB_SET_MULTICHANNEL_MODE, |
| ATI_MULTICHANNEL_MODE_SINGLE); |
| } |
| codec->auto_runtime_pm = 1; |
| |
| return 0; |
| } |
| |
| /* map from pin NID to port; port is 0-based */ |
| /* for AMD: assume widget NID starting from 3, with step 2 (3, 5, 7, ...) */ |
| static int atihdmi_pin2port(void *audio_ptr, int pin_nid) |
| { |
| return pin_nid / 2 - 1; |
| } |
| |
| /* reverse-map from port to pin NID: see above */ |
| static int atihdmi_port2pin(struct hda_codec *codec, int port) |
| { |
| return port * 2 + 3; |
| } |
| |
| static const struct drm_audio_component_audio_ops atihdmi_audio_ops = { |
| .pin2port = atihdmi_pin2port, |
| .pin_eld_notify = snd_hda_hdmi_acomp_pin_eld_notify, |
| .master_bind = snd_hda_hdmi_acomp_master_bind, |
| .master_unbind = snd_hda_hdmi_acomp_master_unbind, |
| }; |
| |
| static int atihdmi_probe(struct hda_codec *codec, const struct hda_device_id *id) |
| { |
| struct hdmi_spec *spec; |
| struct hdmi_spec_per_cvt *per_cvt; |
| int err, cvt_idx; |
| |
| err = snd_hda_hdmi_generic_probe(codec); |
| if (err) |
| return err; |
| |
| spec = codec->spec; |
| |
| spec->static_pcm_mapping = true; |
| |
| spec->ops.pin_get_eld = atihdmi_pin_get_eld; |
| spec->ops.pin_setup_infoframe = atihdmi_pin_setup_infoframe; |
| spec->ops.pin_hbr_setup = atihdmi_pin_hbr_setup; |
| spec->ops.setup_stream = atihdmi_setup_stream; |
| |
| spec->chmap.ops.pin_get_slot_channel = atihdmi_pin_get_slot_channel; |
| spec->chmap.ops.pin_set_slot_channel = atihdmi_pin_set_slot_channel; |
| |
| if (!has_amd_full_remap_support(codec)) { |
| /* override to ATI/AMD-specific versions with pairwise mapping */ |
| spec->chmap.ops.chmap_cea_alloc_validate_get_type = |
| atihdmi_paired_chmap_cea_alloc_validate_get_type; |
| spec->chmap.ops.cea_alloc_to_tlv_chmap = |
| atihdmi_paired_cea_alloc_to_tlv_chmap; |
| spec->chmap.ops.chmap_validate = atihdmi_paired_chmap_validate; |
| } |
| |
| /* ATI/AMD converters do not advertise all of their capabilities */ |
| for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { |
| per_cvt = get_cvt(spec, cvt_idx); |
| per_cvt->channels_max = max(per_cvt->channels_max, 8u); |
| per_cvt->rates |= SUPPORTED_RATES; |
| per_cvt->formats |= SUPPORTED_FORMATS; |
| per_cvt->maxbps = max(per_cvt->maxbps, 24u); |
| } |
| |
| spec->chmap.channels_max = max(spec->chmap.channels_max, 8u); |
| |
| /* AMD GPUs have neither EPSS nor CLKSTOP bits, hence preventing |
| * the link-down as is. Tell the core to allow it. |
| */ |
| codec->link_down_at_suspend = 1; |
| |
| snd_hda_hdmi_acomp_init(codec, &atihdmi_audio_ops, atihdmi_port2pin); |
| |
| return 0; |
| } |
| |
| static const struct hda_codec_ops atihdmi_codec_ops = { |
| .probe = atihdmi_probe, |
| .remove = snd_hda_hdmi_generic_remove, |
| .init = atihdmi_init, |
| .build_pcms = snd_hda_hdmi_generic_build_pcms, |
| .build_controls = snd_hda_hdmi_generic_build_controls, |
| .unsol_event = snd_hda_hdmi_generic_unsol_event, |
| .suspend = snd_hda_hdmi_generic_suspend, |
| .resume = snd_hda_hdmi_generic_resume, |
| }; |
| |
| /* |
| * driver entries |
| */ |
| static const struct hda_device_id snd_hda_id_atihdmi[] = { |
| HDA_CODEC_ID(0x1002793c, "RS600 HDMI"), |
| HDA_CODEC_ID(0x10027919, "RS600 HDMI"), |
| HDA_CODEC_ID(0x1002791a, "RS690/780 HDMI"), |
| HDA_CODEC_ID(0x1002aa01, "R6xx HDMI"), |
| {} /* terminator */ |
| }; |
| MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_atihdmi); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("AMD/ATI HDMI HD-audio codec"); |
| MODULE_IMPORT_NS("SND_HDA_CODEC_HDMI"); |
| |
| static struct hda_codec_driver atihdmi_driver = { |
| .id = snd_hda_id_atihdmi, |
| .ops = &atihdmi_codec_ops, |
| }; |
| |
| module_hda_codec_driver(atihdmi_driver); |