// SPDX-License-Identifier: GPL-2.0-or-later /* * * hdmi.c - routines for HDMI/DisplayPort codecs * * Copyright(c) 2008-2010 Intel Corporation * Copyright (c) 2006 ATI Technologies Inc. * Copyright (c) 2008 NVIDIA Corp. All rights reserved. * Copyright (c) 2008 Wei Ni * Copyright (c) 2013 Anssi Hannula * * Authors: * Wu Fengguang * * Maintained by: * Wu Fengguang */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hda_local.h" #include "hda_jack.h" #include "hda_controller.h" #include "hdmi_local.h" static bool static_hdmi_pcm; module_param(static_hdmi_pcm, bool, 0644); MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info"); static bool enable_acomp = true; module_param(enable_acomp, bool, 0444); MODULE_PARM_DESC(enable_acomp, "Enable audio component binding (default=yes)"); static bool enable_all_pins; module_param(enable_all_pins, bool, 0444); MODULE_PARM_DESC(enable_all_pins, "Forcibly enable all pins"); int snd_hda_hdmi_pin_id_to_pin_index(struct hda_codec *codec, hda_nid_t pin_nid, int dev_id) { struct hdmi_spec *spec = codec->spec; int pin_idx; struct hdmi_spec_per_pin *per_pin; /* * (dev_id == -1) means it is NON-MST pin * return the first virtual pin on this port */ if (dev_id == -1) dev_id = 0; for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { per_pin = get_pin(spec, pin_idx); if ((per_pin->pin_nid == pin_nid) && (per_pin->dev_id == dev_id)) return pin_idx; } codec_warn(codec, "HDMI: pin NID 0x%x not registered\n", pin_nid); return -EINVAL; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_pin_id_to_pin_index, "SND_HDA_CODEC_HDMI"); static int hinfo_to_pcm_index(struct hda_codec *codec, struct hda_pcm_stream *hinfo) { struct hdmi_spec *spec = codec->spec; int pcm_idx; for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) if (get_pcm_rec(spec, pcm_idx)->stream == hinfo) return pcm_idx; codec_warn(codec, "HDMI: hinfo %p not tied to a PCM\n", hinfo); return -EINVAL; } static int hinfo_to_pin_index(struct hda_codec *codec, struct hda_pcm_stream *hinfo) { struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin; int pin_idx; for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { per_pin = get_pin(spec, pin_idx); if (per_pin->pcm && per_pin->pcm->pcm->stream == hinfo) return pin_idx; } codec_dbg(codec, "HDMI: hinfo %p (pcm %d) not registered\n", hinfo, hinfo_to_pcm_index(codec, hinfo)); return -EINVAL; } static struct hdmi_spec_per_pin *pcm_idx_to_pin(struct hdmi_spec *spec, int pcm_idx) { int i; struct hdmi_spec_per_pin *per_pin; for (i = 0; i < spec->num_pins; i++) { per_pin = get_pin(spec, i); if (per_pin->pcm_idx == pcm_idx) return per_pin; } return NULL; } static int cvt_nid_to_cvt_index(struct hda_codec *codec, hda_nid_t cvt_nid) { struct hdmi_spec *spec = codec->spec; int cvt_idx; for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) if (get_cvt(spec, cvt_idx)->cvt_nid == cvt_nid) return cvt_idx; codec_warn(codec, "HDMI: cvt NID 0x%x not registered\n", cvt_nid); return -EINVAL; } static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin; struct hdmi_eld *eld; int pcm_idx; uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; pcm_idx = kcontrol->private_value; mutex_lock(&spec->pcm_lock); per_pin = pcm_idx_to_pin(spec, pcm_idx); if (!per_pin) { /* no pin is bound to the pcm */ uinfo->count = 0; goto unlock; } eld = &per_pin->sink_eld; uinfo->count = eld->eld_valid ? eld->eld_size : 0; unlock: mutex_unlock(&spec->pcm_lock); return 0; } static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin; struct hdmi_eld *eld; int pcm_idx; int err = 0; pcm_idx = kcontrol->private_value; mutex_lock(&spec->pcm_lock); per_pin = pcm_idx_to_pin(spec, pcm_idx); if (!per_pin) { /* no pin is bound to the pcm */ memset(ucontrol->value.bytes.data, 0, ARRAY_SIZE(ucontrol->value.bytes.data)); goto unlock; } eld = &per_pin->sink_eld; if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data) || eld->eld_size > ELD_MAX_SIZE) { snd_BUG(); err = -EINVAL; goto unlock; } memset(ucontrol->value.bytes.data, 0, ARRAY_SIZE(ucontrol->value.bytes.data)); if (eld->eld_valid) memcpy(ucontrol->value.bytes.data, eld->eld_buffer, eld->eld_size); unlock: mutex_unlock(&spec->pcm_lock); return err; } static const struct snd_kcontrol_new eld_bytes_ctl = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "ELD", .info = hdmi_eld_ctl_info, .get = hdmi_eld_ctl_get, }; static int hdmi_create_eld_ctl(struct hda_codec *codec, int pcm_idx, int device) { struct snd_kcontrol *kctl; struct hdmi_spec *spec = codec->spec; int err; kctl = snd_ctl_new1(&eld_bytes_ctl, codec); if (!kctl) return -ENOMEM; kctl->private_value = pcm_idx; kctl->id.device = device; /* no pin nid is associated with the kctl now * tbd: associate pin nid to eld ctl later */ err = snd_hda_ctl_add(codec, 0, kctl); if (err < 0) return err; get_hdmi_pcm(spec, pcm_idx)->eld_ctl = kctl; return 0; } #ifdef BE_PARANOID static void hdmi_get_dip_index(struct hda_codec *codec, hda_nid_t pin_nid, int *packet_index, int *byte_index) { int val; val = snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_INDEX, 0); *packet_index = val >> 5; *byte_index = val & 0x1f; } #endif static void hdmi_set_dip_index(struct hda_codec *codec, hda_nid_t pin_nid, int packet_index, int byte_index) { int val; val = (packet_index << 5) | (byte_index & 0x1f); snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_INDEX, val); } static void hdmi_write_dip_byte(struct hda_codec *codec, hda_nid_t pin_nid, unsigned char val) { snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_DATA, val); } static void hdmi_init_pin(struct hda_codec *codec, hda_nid_t pin_nid) { struct hdmi_spec *spec = codec->spec; int pin_out; /* Unmute */ if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP) snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); if (spec->dyn_pin_out) /* Disable pin out until stream is active */ pin_out = 0; else /* Enable pin out: some machines with GM965 gets broken output * when the pin is disabled or changed while using with HDMI */ pin_out = PIN_OUT; snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pin_out); } /* * ELD proc files */ #ifdef CONFIG_SND_PROC_FS static void print_eld_info(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdmi_spec_per_pin *per_pin = entry->private_data; mutex_lock(&per_pin->lock); snd_hdmi_print_eld_info(&per_pin->sink_eld, buffer, per_pin->pin_nid, per_pin->dev_id, per_pin->cvt_nid); mutex_unlock(&per_pin->lock); } static void write_eld_info(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdmi_spec_per_pin *per_pin = entry->private_data; mutex_lock(&per_pin->lock); snd_hdmi_write_eld_info(&per_pin->sink_eld, buffer); mutex_unlock(&per_pin->lock); } static int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index) { char name[32]; struct hda_codec *codec = per_pin->codec; struct snd_info_entry *entry; int err; snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, index); err = snd_card_proc_new(codec->card, name, &entry); if (err < 0) return err; snd_info_set_text_ops(entry, per_pin, print_eld_info); entry->c.text.write = write_eld_info; entry->mode |= 0200; per_pin->proc_entry = entry; return 0; } static void eld_proc_free(struct hdmi_spec_per_pin *per_pin) { if (!per_pin->codec->bus->shutdown) { snd_info_free_entry(per_pin->proc_entry); per_pin->proc_entry = NULL; } } #else static inline int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index) { return 0; } static inline void eld_proc_free(struct hdmi_spec_per_pin *per_pin) { } #endif /* * Audio InfoFrame routines */ /* * Enable Audio InfoFrame Transmission */ static void hdmi_start_infoframe_trans(struct hda_codec *codec, hda_nid_t pin_nid) { hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_BEST); } /* * Disable Audio InfoFrame Transmission */ static void hdmi_stop_infoframe_trans(struct hda_codec *codec, hda_nid_t pin_nid) { hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_DISABLE); } static void hdmi_debug_dip_size(struct hda_codec *codec, hda_nid_t pin_nid) { #ifdef CONFIG_SND_DEBUG_VERBOSE int i; int size; size = snd_hdmi_get_eld_size(codec, pin_nid); codec_dbg(codec, "HDMI: ELD buf size is %d\n", size); for (i = 0; i < 8; i++) { size = snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_SIZE, i); codec_dbg(codec, "HDMI: DIP GP[%d] buf size is %d\n", i, size); } #endif } static void hdmi_clear_dip_buffers(struct hda_codec *codec, hda_nid_t pin_nid) { #ifdef BE_PARANOID int i, j; int size; int pi, bi; for (i = 0; i < 8; i++) { size = snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_SIZE, i); if (size == 0) continue; hdmi_set_dip_index(codec, pin_nid, i, 0x0); for (j = 1; j < 1000; j++) { hdmi_write_dip_byte(codec, pin_nid, 0x0); hdmi_get_dip_index(codec, pin_nid, &pi, &bi); if (pi != i) codec_dbg(codec, "dip index %d: %d != %d\n", bi, pi, i); if (bi == 0) /* byte index wrapped around */ break; } codec_dbg(codec, "HDMI: DIP GP[%d] buf reported size=%d, written=%d\n", i, size, j); } #endif } static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *hdmi_ai) { u8 *bytes = (u8 *)hdmi_ai; u8 sum = 0; int i; hdmi_ai->checksum = 0; for (i = 0; i < sizeof(*hdmi_ai); i++) sum += bytes[i]; hdmi_ai->checksum = -sum; } static void hdmi_fill_audio_infoframe(struct hda_codec *codec, hda_nid_t pin_nid, u8 *dip, int size) { int i; hdmi_debug_dip_size(codec, pin_nid); hdmi_clear_dip_buffers(codec, pin_nid); /* be paranoid */ hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); for (i = 0; i < size; i++) hdmi_write_dip_byte(codec, pin_nid, dip[i]); } static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid, u8 *dip, int size) { u8 val; int i; hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); if (snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_XMIT, 0) != AC_DIPXMIT_BEST) return false; for (i = 0; i < size; i++) { val = snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_DATA, 0); if (val != dip[i]) return false; } return true; } static int hdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid, int dev_id, unsigned char *buf, int *eld_size) { snd_hda_set_dev_select(codec, nid, dev_id); return snd_hdmi_get_eld(codec, nid, buf, eld_size); } static void hdmi_pin_setup_infoframe(struct hda_codec *codec, hda_nid_t pin_nid, int dev_id, int ca, int active_channels, int conn_type) { struct hdmi_spec *spec = codec->spec; union audio_infoframe ai; memset(&ai, 0, sizeof(ai)); if ((conn_type == 0) || /* HDMI */ /* Nvidia DisplayPort: Nvidia HW expects same layout as HDMI */ (conn_type == 1 && spec->nv_dp_workaround)) { struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi; if (conn_type == 0) { /* HDMI */ hdmi_ai->type = 0x84; hdmi_ai->ver = 0x01; hdmi_ai->len = 0x0a; } else {/* Nvidia DP */ hdmi_ai->type = 0x84; hdmi_ai->ver = 0x1b; hdmi_ai->len = 0x11 << 2; } hdmi_ai->CC02_CT47 = active_channels - 1; hdmi_ai->CA = ca; hdmi_checksum_audio_infoframe(hdmi_ai); } else if (conn_type == 1) { /* DisplayPort */ struct dp_audio_infoframe *dp_ai = &ai.dp; dp_ai->type = 0x84; dp_ai->len = 0x1b; dp_ai->ver = 0x11 << 2; dp_ai->CC02_CT47 = active_channels - 1; dp_ai->CA = ca; } else { codec_dbg(codec, "HDMI: unknown connection type at pin NID 0x%x\n", pin_nid); return; } snd_hda_set_dev_select(codec, pin_nid, dev_id); /* * sizeof(ai) is used instead of sizeof(*hdmi_ai) or * sizeof(*dp_ai) to avoid partial match/update problems when * the user switches between HDMI/DP monitors. */ if (!hdmi_infoframe_uptodate(codec, pin_nid, ai.bytes, sizeof(ai))) { codec_dbg(codec, "%s: pin NID=0x%x channels=%d ca=0x%02x\n", __func__, pin_nid, active_channels, ca); hdmi_stop_infoframe_trans(codec, pin_nid); hdmi_fill_audio_infoframe(codec, pin_nid, ai.bytes, sizeof(ai)); hdmi_start_infoframe_trans(codec, pin_nid); } } void snd_hda_hdmi_setup_audio_infoframe(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin, bool non_pcm) { struct hdmi_spec *spec = codec->spec; struct hdac_chmap *chmap = &spec->chmap; hda_nid_t pin_nid = per_pin->pin_nid; int dev_id = per_pin->dev_id; int channels = per_pin->channels; int active_channels; struct hdmi_eld *eld; int ca; if (!channels) return; snd_hda_set_dev_select(codec, pin_nid, dev_id); /* some HW (e.g. HSW+) needs reprogramming the amp at each time */ if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP) snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); eld = &per_pin->sink_eld; ca = snd_hdac_channel_allocation(&codec->core, eld->info.spk_alloc, channels, per_pin->chmap_set, non_pcm, per_pin->chmap); active_channels = snd_hdac_get_active_channels(ca); chmap->ops.set_channel_count(&codec->core, per_pin->cvt_nid, active_channels); /* * always configure channel mapping, it may have been changed by the * user in the meantime */ snd_hdac_setup_channel_mapping(&spec->chmap, pin_nid, non_pcm, ca, channels, per_pin->chmap, per_pin->chmap_set); spec->ops.pin_setup_infoframe(codec, pin_nid, dev_id, ca, active_channels, eld->info.conn_type); per_pin->non_pcm = non_pcm; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_setup_audio_infoframe, "SND_HDA_CODEC_HDMI"); /* * Unsolicited events */ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll); void snd_hda_hdmi_check_presence_and_report(struct hda_codec *codec, hda_nid_t nid, int dev_id) { struct hdmi_spec *spec = codec->spec; int pin_idx = pin_id_to_pin_index(codec, nid, dev_id); if (pin_idx < 0) return; mutex_lock(&spec->pcm_lock); hdmi_present_sense(get_pin(spec, pin_idx), 1); mutex_unlock(&spec->pcm_lock); } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_check_presence_and_report, "SND_HDA_CODEC_HDMI"); static void jack_callback(struct hda_codec *codec, struct hda_jack_callback *jack) { /* stop polling when notification is enabled */ if (codec_has_acomp(codec)) return; snd_hda_hdmi_check_presence_and_report(codec, jack->nid, jack->dev_id); } static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res, struct hda_jack_tbl *jack) { jack->jack_dirty = 1; codec_dbg(codec, "HDMI hot plug event: Codec=%d NID=0x%x Device=%d Inactive=%d Presence_Detect=%d ELD_Valid=%d\n", codec->addr, jack->nid, jack->dev_id, !!(res & AC_UNSOL_RES_IA), !!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV)); snd_hda_hdmi_check_presence_and_report(codec, jack->nid, jack->dev_id); } static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res) { int tag = res >> AC_UNSOL_RES_TAG_SHIFT; int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; int cp_state = !!(res & AC_UNSOL_RES_CP_STATE); int cp_ready = !!(res & AC_UNSOL_RES_CP_READY); codec_info(codec, "HDMI CP event: CODEC=%d TAG=%d SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n", codec->addr, tag, subtag, cp_state, cp_ready); /* TODO */ if (cp_state) { ; } if (cp_ready) { ; } } void snd_hda_hdmi_generic_unsol_event(struct hda_codec *codec, unsigned int res) { int tag = res >> AC_UNSOL_RES_TAG_SHIFT; int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; struct hda_jack_tbl *jack; if (codec_has_acomp(codec)) return; if (codec->dp_mst) { int dev_entry = (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT; jack = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry); } else { jack = snd_hda_jack_tbl_get_from_tag(codec, tag, 0); } if (!jack) { codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag); return; } if (subtag == 0) hdmi_intrinsic_event(codec, res, jack); else hdmi_non_intrinsic_event(codec, res); } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_unsol_event, "SND_HDA_CODEC_HDMI"); /* * Callbacks */ /* HBR should be Non-PCM, 8 channels */ #define is_hbr_format(format) \ ((format & AC_FMT_TYPE_NON_PCM) && (format & AC_FMT_CHAN_MASK) == 7) static int hdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid, int dev_id, bool hbr) { int pinctl, new_pinctl; if (snd_hda_query_pin_caps(codec, pin_nid) & AC_PINCAP_HBR) { snd_hda_set_dev_select(codec, pin_nid, dev_id); pinctl = snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0); if (pinctl < 0) return hbr ? -EINVAL : 0; new_pinctl = pinctl & ~AC_PINCTL_EPT; if (hbr) new_pinctl |= AC_PINCTL_EPT_HBR; else new_pinctl |= AC_PINCTL_EPT_NATIVE; codec_dbg(codec, "hdmi_pin_hbr_setup: NID=0x%x, %spinctl=0x%x\n", pin_nid, pinctl == new_pinctl ? "" : "new-", new_pinctl); if (pinctl != new_pinctl) snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, new_pinctl); } else if (hbr) return -EINVAL; return 0; } int snd_hda_hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid, hda_nid_t pin_nid, int dev_id, u32 stream_tag, int format) { struct hdmi_spec *spec = codec->spec; unsigned int param; int err; err = spec->ops.pin_hbr_setup(codec, pin_nid, dev_id, is_hbr_format(format)); if (err) { codec_dbg(codec, "hdmi_setup_stream: HBR is not supported\n"); return err; } if (spec->intel_hsw_fixup) { /* * on recent platforms IEC Coding Type is required for HBR * support, read current Digital Converter settings and set * ICT bitfield if needed. */ param = snd_hda_codec_read(codec, cvt_nid, 0, AC_VERB_GET_DIGI_CONVERT_1, 0); param = (param >> 16) & ~(AC_DIG3_ICT); /* on recent platforms ICT mode is required for HBR support */ if (is_hbr_format(format)) param |= 0x1; snd_hda_codec_write(codec, cvt_nid, 0, AC_VERB_SET_DIGI_CONVERT_3, param); } snd_hda_codec_setup_stream(codec, cvt_nid, stream_tag, 0, format); return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_setup_stream, "SND_HDA_CODEC_HDMI"); /* Try to find an available converter * If pin_idx is less then zero, just try to find an available converter. * Otherwise, try to find an available converter and get the cvt mux index * of the pin. */ static int hdmi_choose_cvt(struct hda_codec *codec, int pin_idx, int *cvt_id, bool silent) { struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin; struct hdmi_spec_per_cvt *per_cvt = NULL; int cvt_idx, mux_idx = 0; /* pin_idx < 0 means no pin will be bound to the converter */ if (pin_idx < 0) per_pin = NULL; else per_pin = get_pin(spec, pin_idx); if (per_pin && per_pin->silent_stream) { cvt_idx = cvt_nid_to_cvt_index(codec, per_pin->cvt_nid); per_cvt = get_cvt(spec, cvt_idx); if (per_cvt->assigned && !silent) return -EBUSY; if (cvt_id) *cvt_id = cvt_idx; return 0; } /* Dynamically assign converter to stream */ for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { per_cvt = get_cvt(spec, cvt_idx); /* Must not already be assigned */ if (per_cvt->assigned || per_cvt->silent_stream) continue; if (per_pin == NULL) break; /* Must be in pin's mux's list of converters */ for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++) if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid) break; /* Not in mux list */ if (mux_idx == per_pin->num_mux_nids) continue; break; } /* No free converters */ if (cvt_idx == spec->num_cvts) return -EBUSY; if (per_pin != NULL) per_pin->mux_idx = mux_idx; if (cvt_id) *cvt_id = cvt_idx; return 0; } /* skeleton caller of pin_cvt_fixup ops */ static void pin_cvt_fixup(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin, hda_nid_t cvt_nid) { struct hdmi_spec *spec = codec->spec; if (spec->ops.pin_cvt_fixup) spec->ops.pin_cvt_fixup(codec, per_pin, cvt_nid); } /* called in hdmi_pcm_open when no pin is assigned to the PCM */ static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { struct hdmi_spec *spec = codec->spec; struct snd_pcm_runtime *runtime = substream->runtime; int cvt_idx, pcm_idx; struct hdmi_spec_per_cvt *per_cvt = NULL; int err; pcm_idx = hinfo_to_pcm_index(codec, hinfo); if (pcm_idx < 0) return -EINVAL; err = hdmi_choose_cvt(codec, -1, &cvt_idx, false); if (err) return err; per_cvt = get_cvt(spec, cvt_idx); per_cvt->assigned = true; hinfo->nid = per_cvt->cvt_nid; pin_cvt_fixup(codec, NULL, per_cvt->cvt_nid); set_bit(pcm_idx, &spec->pcm_in_use); /* todo: setup spdif ctls assign */ /* Initially set the converter's capabilities */ hinfo->channels_min = per_cvt->channels_min; hinfo->channels_max = per_cvt->channels_max; hinfo->rates = per_cvt->rates; hinfo->formats = per_cvt->formats; hinfo->maxbps = per_cvt->maxbps; /* Store the updated parameters */ runtime->hw.channels_min = hinfo->channels_min; runtime->hw.channels_max = hinfo->channels_max; runtime->hw.formats = hinfo->formats; runtime->hw.rates = hinfo->rates; snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); return 0; } /* * HDA PCM callbacks */ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { struct hdmi_spec *spec = codec->spec; struct snd_pcm_runtime *runtime = substream->runtime; int pin_idx, cvt_idx, pcm_idx; struct hdmi_spec_per_pin *per_pin; struct hdmi_eld *eld; struct hdmi_spec_per_cvt *per_cvt = NULL; int err; /* Validate hinfo */ pcm_idx = hinfo_to_pcm_index(codec, hinfo); if (pcm_idx < 0) return -EINVAL; mutex_lock(&spec->pcm_lock); pin_idx = hinfo_to_pin_index(codec, hinfo); /* no pin is assigned to the PCM * PA need pcm open successfully when probe */ if (pin_idx < 0) { err = hdmi_pcm_open_no_pin(hinfo, codec, substream); goto unlock; } err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, false); if (err < 0) goto unlock; per_cvt = get_cvt(spec, cvt_idx); /* Claim converter */ per_cvt->assigned = true; set_bit(pcm_idx, &spec->pcm_in_use); per_pin = get_pin(spec, pin_idx); per_pin->cvt_nid = per_cvt->cvt_nid; hinfo->nid = per_cvt->cvt_nid; /* flip stripe flag for the assigned stream if supported */ if (get_wcaps(codec, per_cvt->cvt_nid) & AC_WCAP_STRIPE) azx_stream(get_azx_dev(substream))->stripe = 1; snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, AC_VERB_SET_CONNECT_SEL, per_pin->mux_idx); /* configure unused pins to choose other converters */ pin_cvt_fixup(codec, per_pin, 0); snd_hda_spdif_ctls_assign(codec, pcm_idx, per_cvt->cvt_nid); /* Initially set the converter's capabilities */ hinfo->channels_min = per_cvt->channels_min; hinfo->channels_max = per_cvt->channels_max; hinfo->rates = per_cvt->rates; hinfo->formats = per_cvt->formats; hinfo->maxbps = per_cvt->maxbps; eld = &per_pin->sink_eld; /* Restrict capabilities by ELD if this isn't disabled */ if (!static_hdmi_pcm && eld->eld_valid) { snd_hdmi_eld_update_pcm_info(&eld->info, hinfo); if (hinfo->channels_min > hinfo->channels_max || !hinfo->rates || !hinfo->formats) { per_cvt->assigned = false; hinfo->nid = 0; snd_hda_spdif_ctls_unassign(codec, pcm_idx); err = -ENODEV; goto unlock; } } /* Store the updated parameters */ runtime->hw.channels_min = hinfo->channels_min; runtime->hw.channels_max = hinfo->channels_max; runtime->hw.formats = hinfo->formats; runtime->hw.rates = hinfo->rates; snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); unlock: mutex_unlock(&spec->pcm_lock); return err; } /* * HDA/HDMI auto parsing */ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx) { struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); hda_nid_t pin_nid = per_pin->pin_nid; int dev_id = per_pin->dev_id; int conns; if (!(get_wcaps(codec, pin_nid) & AC_WCAP_CONN_LIST)) { codec_warn(codec, "HDMI: pin NID 0x%x wcaps %#x does not support connection list\n", pin_nid, get_wcaps(codec, pin_nid)); return -EINVAL; } snd_hda_set_dev_select(codec, pin_nid, dev_id); if (spec->intel_hsw_fixup) { conns = spec->num_cvts; memcpy(per_pin->mux_nids, spec->cvt_nids, sizeof(hda_nid_t) * conns); } else { conns = snd_hda_get_raw_connections(codec, pin_nid, per_pin->mux_nids, HDA_MAX_CONNECTIONS); } /* all the device entries on the same pin have the same conn list */ per_pin->num_mux_nids = conns; return 0; } static int hdmi_find_pcm_slot(struct hdmi_spec *spec, struct hdmi_spec_per_pin *per_pin) { int i; for (i = 0; i < spec->pcm_used; i++) { if (!test_bit(i, &spec->pcm_bitmap)) return i; } return -EBUSY; } static void hdmi_attach_hda_pcm(struct hdmi_spec *spec, struct hdmi_spec_per_pin *per_pin) { int idx; /* pcm already be attached to the pin */ if (per_pin->pcm) return; /* try the previously used slot at first */ idx = per_pin->prev_pcm_idx; if (idx >= 0) { if (!test_bit(idx, &spec->pcm_bitmap)) goto found; per_pin->prev_pcm_idx = -1; /* no longer valid, clear it */ } idx = hdmi_find_pcm_slot(spec, per_pin); if (idx == -EBUSY) return; found: per_pin->pcm_idx = idx; per_pin->pcm = get_hdmi_pcm(spec, idx); set_bit(idx, &spec->pcm_bitmap); } static void hdmi_detach_hda_pcm(struct hdmi_spec *spec, struct hdmi_spec_per_pin *per_pin) { int idx; /* pcm already be detached from the pin */ if (!per_pin->pcm) return; idx = per_pin->pcm_idx; per_pin->pcm_idx = -1; per_pin->prev_pcm_idx = idx; /* remember the previous index */ per_pin->pcm = NULL; if (idx >= 0 && idx < spec->pcm_used) clear_bit(idx, &spec->pcm_bitmap); } static int hdmi_get_pin_cvt_mux(struct hdmi_spec *spec, struct hdmi_spec_per_pin *per_pin, hda_nid_t cvt_nid) { int mux_idx; for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++) if (per_pin->mux_nids[mux_idx] == cvt_nid) break; return mux_idx; } static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid); static void hdmi_pcm_setup_pin(struct hdmi_spec *spec, struct hdmi_spec_per_pin *per_pin) { struct hda_codec *codec = per_pin->codec; struct hda_pcm *pcm; struct hda_pcm_stream *hinfo; struct snd_pcm_substream *substream; int mux_idx; bool non_pcm; if (per_pin->pcm_idx < 0 || per_pin->pcm_idx >= spec->pcm_used) return; pcm = get_pcm_rec(spec, per_pin->pcm_idx); if (!pcm->pcm) return; if (!test_bit(per_pin->pcm_idx, &spec->pcm_in_use)) return; /* hdmi audio only uses playback and one substream */ hinfo = pcm->stream; substream = pcm->pcm->streams[0].substream; per_pin->cvt_nid = hinfo->nid; mux_idx = hdmi_get_pin_cvt_mux(spec, per_pin, hinfo->nid); if (mux_idx < per_pin->num_mux_nids) { snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, AC_VERB_SET_CONNECT_SEL, mux_idx); } snd_hda_spdif_ctls_assign(codec, per_pin->pcm_idx, hinfo->nid); non_pcm = check_non_pcm_per_cvt(codec, hinfo->nid); if (substream->runtime) per_pin->channels = substream->runtime->channels; per_pin->setup = true; per_pin->mux_idx = mux_idx; snd_hda_hdmi_setup_audio_infoframe(codec, per_pin, non_pcm); } static void hdmi_pcm_reset_pin(struct hdmi_spec *spec, struct hdmi_spec_per_pin *per_pin) { if (per_pin->pcm_idx >= 0 && per_pin->pcm_idx < spec->pcm_used) snd_hda_spdif_ctls_unassign(per_pin->codec, per_pin->pcm_idx); per_pin->chmap_set = false; memset(per_pin->chmap, 0, sizeof(per_pin->chmap)); per_pin->setup = false; per_pin->channels = 0; } static struct snd_jack *pin_idx_to_pcm_jack(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin) { struct hdmi_spec *spec = codec->spec; if (per_pin->pcm_idx >= 0) return spec->pcm_rec[per_pin->pcm_idx].jack; else return NULL; } /* update per_pin ELD from the given new ELD; * setup info frame and notification accordingly * also notify ELD kctl and report jack status changes */ static void update_eld(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin, struct hdmi_eld *eld, int repoll) { struct hdmi_eld *pin_eld = &per_pin->sink_eld; struct hdmi_spec *spec = codec->spec; struct snd_jack *pcm_jack; bool old_eld_valid = pin_eld->eld_valid; bool eld_changed; int pcm_idx; if (eld->eld_valid) { if (eld->eld_size <= 0 || snd_parse_eld(hda_codec_dev(codec), &eld->info, eld->eld_buffer, eld->eld_size) < 0) { eld->eld_valid = false; if (repoll) { schedule_delayed_work(&per_pin->work, msecs_to_jiffies(300)); return; } } } if (!eld->eld_valid || eld->eld_size <= 0 || eld->info.sad_count <= 0) { eld->eld_valid = false; eld->eld_size = 0; } /* for monitor disconnection, save pcm_idx firstly */ pcm_idx = per_pin->pcm_idx; /* * pcm_idx >=0 before update_eld() means it is in monitor * disconnected event. Jack must be fetched before update_eld(). */ pcm_jack = pin_idx_to_pcm_jack(codec, per_pin); if (!spec->static_pcm_mapping) { if (eld->eld_valid) { hdmi_attach_hda_pcm(spec, per_pin); hdmi_pcm_setup_pin(spec, per_pin); } else { hdmi_pcm_reset_pin(spec, per_pin); hdmi_detach_hda_pcm(spec, per_pin); } } /* if pcm_idx == -1, it means this is in monitor connection event * we can get the correct pcm_idx now. */ if (pcm_idx == -1) pcm_idx = per_pin->pcm_idx; if (!pcm_jack) pcm_jack = pin_idx_to_pcm_jack(codec, per_pin); if (eld->eld_valid) snd_show_eld(hda_codec_dev(codec), &eld->info); eld_changed = (pin_eld->eld_valid != eld->eld_valid); eld_changed |= (pin_eld->monitor_present != eld->monitor_present); if (!eld_changed && eld->eld_valid && pin_eld->eld_valid) if (pin_eld->eld_size != eld->eld_size || memcmp(pin_eld->eld_buffer, eld->eld_buffer, eld->eld_size) != 0) eld_changed = true; if (eld_changed) { pin_eld->monitor_present = eld->monitor_present; pin_eld->eld_valid = eld->eld_valid; pin_eld->eld_size = eld->eld_size; if (eld->eld_valid) memcpy(pin_eld->eld_buffer, eld->eld_buffer, eld->eld_size); pin_eld->info = eld->info; } /* * Re-setup pin and infoframe. This is needed e.g. when * - sink is first plugged-in * - transcoder can change during stream playback on Haswell * and this can make HW reset converter selection on a pin. */ if (eld->eld_valid && !old_eld_valid && per_pin->setup) { pin_cvt_fixup(codec, per_pin, 0); snd_hda_hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm); } if (eld_changed && pcm_idx >= 0) snd_ctl_notify(codec->card, SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, &get_hdmi_pcm(spec, pcm_idx)->eld_ctl->id); if (eld_changed && pcm_jack) snd_jack_report(pcm_jack, (eld->monitor_present && eld->eld_valid) ? SND_JACK_AVOUT : 0); } /* update ELD and jack state via HD-audio verbs */ static void hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin, int repoll) { struct hda_codec *codec = per_pin->codec; struct hdmi_spec *spec = codec->spec; struct hdmi_eld *eld = &spec->temp_eld; struct device *dev = hda_codec_dev(codec); hda_nid_t pin_nid = per_pin->pin_nid; int dev_id = per_pin->dev_id; /* * Always execute a GetPinSense verb here, even when called from * hdmi_intrinsic_event; for some NVIDIA HW, the unsolicited * response's PD bit is not the real PD value, but indicates that * the real PD value changed. An older version of the HD-audio * specification worked this way. Hence, we just ignore the data in * the unsolicited response to avoid custom WARs. */ int present; int ret; #ifdef CONFIG_PM if (dev->power.runtime_status == RPM_SUSPENDING) return; #endif ret = snd_hda_power_up_pm(codec); if (ret < 0 && pm_runtime_suspended(dev)) goto out; present = snd_hda_jack_pin_sense(codec, pin_nid, dev_id); mutex_lock(&per_pin->lock); eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE); if (eld->monitor_present) eld->eld_valid = !!(present & AC_PINSENSE_ELDV); else eld->eld_valid = false; codec_dbg(codec, "HDMI status: Codec=%d NID=0x%x Presence_Detect=%d ELD_Valid=%d\n", codec->addr, pin_nid, eld->monitor_present, eld->eld_valid); if (eld->eld_valid) { if (spec->ops.pin_get_eld(codec, pin_nid, dev_id, eld->eld_buffer, &eld->eld_size) < 0) eld->eld_valid = false; } update_eld(codec, per_pin, eld, repoll); mutex_unlock(&per_pin->lock); out: snd_hda_power_down_pm(codec); } static void silent_stream_enable(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin) { struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_cvt *per_cvt; int cvt_idx, pin_idx, err; /* * Power-up will call hdmi_present_sense, so the PM calls * have to be done without mutex held. */ err = snd_hda_power_up_pm(codec); if (err < 0 && err != -EACCES) { codec_err(codec, "Failed to power up codec for silent stream enable ret=[%d]\n", err); snd_hda_power_down_pm(codec); return; } mutex_lock(&per_pin->lock); if (per_pin->setup) { codec_dbg(codec, "hdmi: PCM already open, no silent stream\n"); err = -EBUSY; goto unlock_out; } pin_idx = pin_id_to_pin_index(codec, per_pin->pin_nid, per_pin->dev_id); err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, true); if (err) { codec_err(codec, "hdmi: no free converter to enable silent mode\n"); goto unlock_out; } per_cvt = get_cvt(spec, cvt_idx); per_cvt->silent_stream = true; per_pin->cvt_nid = per_cvt->cvt_nid; per_pin->silent_stream = true; codec_dbg(codec, "hdmi: enabling silent stream pin-NID=0x%x cvt-NID=0x%x\n", per_pin->pin_nid, per_cvt->cvt_nid); snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, AC_VERB_SET_CONNECT_SEL, per_pin->mux_idx); /* configure unused pins to choose other converters */ pin_cvt_fixup(codec, per_pin, 0); spec->ops.silent_stream(codec, per_pin, true); unlock_out: mutex_unlock(&per_pin->lock); snd_hda_power_down_pm(codec); } static void silent_stream_disable(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin) { struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_cvt *per_cvt; int cvt_idx, err; err = snd_hda_power_up_pm(codec); if (err < 0 && err != -EACCES) { codec_err(codec, "Failed to power up codec for silent stream disable ret=[%d]\n", err); snd_hda_power_down_pm(codec); return; } mutex_lock(&per_pin->lock); if (!per_pin->silent_stream) goto unlock_out; codec_dbg(codec, "HDMI: disable silent stream on pin-NID=0x%x cvt-NID=0x%x\n", per_pin->pin_nid, per_pin->cvt_nid); cvt_idx = cvt_nid_to_cvt_index(codec, per_pin->cvt_nid); if (cvt_idx >= 0 && cvt_idx < spec->num_cvts) { per_cvt = get_cvt(spec, cvt_idx); per_cvt->silent_stream = false; } spec->ops.silent_stream(codec, per_pin, false); per_pin->cvt_nid = 0; per_pin->silent_stream = false; unlock_out: mutex_unlock(&per_pin->lock); snd_hda_power_down_pm(codec); } /* update ELD and jack state via audio component */ static void sync_eld_via_acomp(struct hda_codec *codec, struct hdmi_spec_per_pin *per_pin) { struct hdmi_spec *spec = codec->spec; struct hdmi_eld *eld = &spec->temp_eld; bool monitor_prev, monitor_next; mutex_lock(&per_pin->lock); eld->monitor_present = false; monitor_prev = per_pin->sink_eld.monitor_present; eld->eld_size = snd_hdac_acomp_get_eld(&codec->core, per_pin->pin_nid, per_pin->dev_id, &eld->monitor_present, eld->eld_buffer, ELD_MAX_SIZE); eld->eld_valid = (eld->eld_size > 0); update_eld(codec, per_pin, eld, 0); monitor_next = per_pin->sink_eld.monitor_present; mutex_unlock(&per_pin->lock); if (spec->silent_stream_type) { if (!monitor_prev && monitor_next) silent_stream_enable(codec, per_pin); else if (monitor_prev && !monitor_next) silent_stream_disable(codec, per_pin); } } static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll) { struct hda_codec *codec = per_pin->codec; if (!codec_has_acomp(codec)) hdmi_present_sense_via_verbs(per_pin, repoll); else sync_eld_via_acomp(codec, per_pin); } static void hdmi_repoll_eld(struct work_struct *work) { struct hdmi_spec_per_pin *per_pin = container_of(to_delayed_work(work), struct hdmi_spec_per_pin, work); struct hda_codec *codec = per_pin->codec; struct hdmi_spec *spec = codec->spec; struct hda_jack_tbl *jack; jack = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid, per_pin->dev_id); if (jack) jack->jack_dirty = 1; if (per_pin->repoll_count++ > 6) per_pin->repoll_count = 0; mutex_lock(&spec->pcm_lock); hdmi_present_sense(per_pin, per_pin->repoll_count); mutex_unlock(&spec->pcm_lock); } static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid) { struct hdmi_spec *spec = codec->spec; unsigned int caps, config; int pin_idx; struct hdmi_spec_per_pin *per_pin; int err; int dev_num, i; caps = snd_hda_query_pin_caps(codec, pin_nid); if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP))) return 0; /* * For DP MST audio, Configuration Default is the same for * all device entries on the same pin */ config = snd_hda_codec_get_pincfg(codec, pin_nid); if (get_defcfg_connect(config) == AC_JACK_PORT_NONE && !spec->force_connect) return 0; /* * To simplify the implementation, malloc all * the virtual pins in the initialization statically */ if (spec->intel_hsw_fixup) { /* * On Intel platforms, device entries count returned * by AC_PAR_DEVLIST_LEN is dynamic, and depends on * the type of receiver that is connected. Allocate pin * structures based on worst case. */ dev_num = spec->dev_num; } else if (codec->dp_mst) { dev_num = snd_hda_get_num_devices(codec, pin_nid) + 1; /* * spec->dev_num is the maxinum number of device entries * among all the pins */ spec->dev_num = (spec->dev_num > dev_num) ? spec->dev_num : dev_num; } else { /* * If the platform doesn't support DP MST, * manually set dev_num to 1. This means * the pin has only one device entry. */ dev_num = 1; spec->dev_num = 1; } for (i = 0; i < dev_num; i++) { pin_idx = spec->num_pins; per_pin = snd_array_new(&spec->pins); if (!per_pin) return -ENOMEM; per_pin->pcm = NULL; per_pin->pcm_idx = -1; per_pin->prev_pcm_idx = -1; per_pin->pin_nid = pin_nid; per_pin->pin_nid_idx = spec->num_nids; per_pin->dev_id = i; per_pin->non_pcm = false; snd_hda_set_dev_select(codec, pin_nid, i); err = hdmi_read_pin_conn(codec, pin_idx); if (err < 0) return err; if (!is_jack_detectable(codec, pin_nid)) codec_warn(codec, "HDMI: pin NID 0x%x - jack not detectable\n", pin_nid); spec->num_pins++; } spec->num_nids++; return 0; } static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid) { struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_cvt *per_cvt; unsigned int chans; int err; chans = get_wcaps(codec, cvt_nid); chans = get_wcaps_channels(chans); per_cvt = snd_array_new(&spec->cvts); if (!per_cvt) return -ENOMEM; per_cvt->cvt_nid = cvt_nid; per_cvt->channels_min = 2; if (chans <= 16) { per_cvt->channels_max = chans; if (chans > spec->chmap.channels_max) spec->chmap.channels_max = chans; } err = snd_hda_query_supported_pcm(codec, cvt_nid, &per_cvt->rates, &per_cvt->formats, NULL, &per_cvt->maxbps); if (err < 0) return err; if (spec->num_cvts < ARRAY_SIZE(spec->cvt_nids)) spec->cvt_nids[spec->num_cvts] = cvt_nid; spec->num_cvts++; return 0; } static const struct snd_pci_quirk force_connect_list[] = { SND_PCI_QUIRK(0x103c, 0x83e2, "HP EliteDesk 800 G4", 1), SND_PCI_QUIRK(0x103c, 0x83ef, "HP MP9 G4 Retail System AMS", 1), SND_PCI_QUIRK(0x103c, 0x870f, "HP", 1), SND_PCI_QUIRK(0x103c, 0x871a, "HP", 1), SND_PCI_QUIRK(0x103c, 0x8711, "HP", 1), SND_PCI_QUIRK(0x103c, 0x8715, "HP", 1), SND_PCI_QUIRK(0x1043, 0x86ae, "ASUS", 1), /* Z170 PRO */ SND_PCI_QUIRK(0x1043, 0x86c7, "ASUS", 1), /* Z170M PLUS */ SND_PCI_QUIRK(0x1462, 0xec94, "MS-7C94", 1), SND_PCI_QUIRK(0x8086, 0x2060, "Intel NUC5CPYB", 1), SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", 1), {} }; int snd_hda_hdmi_parse_codec(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; hda_nid_t start_nid; unsigned int caps; int i, nodes; const struct snd_pci_quirk *q; nodes = snd_hda_get_sub_nodes(codec, codec->core.afg, &start_nid); if (!start_nid || nodes < 0) { codec_warn(codec, "HDMI: failed to get afg sub nodes\n"); return -EINVAL; } if (enable_all_pins) spec->force_connect = true; q = snd_pci_quirk_lookup(codec->bus->pci, force_connect_list); if (q && q->value) spec->force_connect = true; /* * hdmi_add_pin() assumes total amount of converters to * be known, so first discover all converters */ for (i = 0; i < nodes; i++) { hda_nid_t nid = start_nid + i; caps = get_wcaps(codec, nid); if (!(caps & AC_WCAP_DIGITAL)) continue; if (get_wcaps_type(caps) == AC_WID_AUD_OUT) hdmi_add_cvt(codec, nid); } /* discover audio pins */ for (i = 0; i < nodes; i++) { hda_nid_t nid = start_nid + i; caps = get_wcaps(codec, nid); if (!(caps & AC_WCAP_DIGITAL)) continue; if (get_wcaps_type(caps) == AC_WID_PIN) hdmi_add_pin(codec, nid); } return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_parse_codec, "SND_HDA_CODEC_HDMI"); /* */ static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid) { struct hda_spdif_out *spdif; bool non_pcm; mutex_lock(&codec->spdif_mutex); spdif = snd_hda_spdif_out_of_nid(codec, cvt_nid); /* Add sanity check to pass klockwork check. * This should never happen. */ if (WARN_ON(spdif == NULL)) { mutex_unlock(&codec->spdif_mutex); return true; } non_pcm = !!(spdif->status & IEC958_AES0_NONAUDIO); mutex_unlock(&codec->spdif_mutex); return non_pcm; } /* * HDMI callbacks */ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo, struct hda_codec *codec, unsigned int stream_tag, unsigned int format, struct snd_pcm_substream *substream) { hda_nid_t cvt_nid = hinfo->nid; struct hdmi_spec *spec = codec->spec; int pin_idx; struct hdmi_spec_per_pin *per_pin; struct snd_pcm_runtime *runtime = substream->runtime; bool non_pcm; int pinctl, stripe; int err = 0; mutex_lock(&spec->pcm_lock); pin_idx = hinfo_to_pin_index(codec, hinfo); if (pin_idx < 0) { /* when pcm is not bound to a pin skip pin setup and return 0 * to make audio playback be ongoing */ pin_cvt_fixup(codec, NULL, cvt_nid); snd_hda_codec_setup_stream(codec, cvt_nid, stream_tag, 0, format); goto unlock; } per_pin = get_pin(spec, pin_idx); /* Verify pin:cvt selections to avoid silent audio after S3. * After S3, the audio driver restores pin:cvt selections * but this can happen before gfx is ready and such selection * is overlooked by HW. Thus multiple pins can share a same * default convertor and mute control will affect each other, * which can cause a resumed audio playback become silent * after S3. */ pin_cvt_fixup(codec, per_pin, 0); /* Call sync_audio_rate to set the N/CTS/M manually if necessary */ /* Todo: add DP1.2 MST audio support later */ if (codec_has_acomp(codec)) snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid, per_pin->dev_id, runtime->rate); non_pcm = check_non_pcm_per_cvt(codec, cvt_nid); mutex_lock(&per_pin->lock); per_pin->channels = substream->runtime->channels; per_pin->setup = true; if (get_wcaps(codec, cvt_nid) & AC_WCAP_STRIPE) { stripe = snd_hdac_get_stream_stripe_ctl(&codec->bus->core, substream); snd_hda_codec_write(codec, cvt_nid, 0, AC_VERB_SET_STRIPE_CONTROL, stripe); } snd_hda_hdmi_setup_audio_infoframe(codec, per_pin, non_pcm); mutex_unlock(&per_pin->lock); if (spec->dyn_pin_out) { snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0); snd_hda_codec_write(codec, per_pin->pin_nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl | PIN_OUT); } /* snd_hda_set_dev_select() has been called before */ err = spec->ops.setup_stream(codec, cvt_nid, per_pin->pin_nid, per_pin->dev_id, stream_tag, format); unlock: mutex_unlock(&spec->pcm_lock); return err; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_pcm_prepare, "SND_HDA_CODEC_HDMI"); int snd_hda_hdmi_generic_pcm_cleanup(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { snd_hda_codec_cleanup_stream(codec, hinfo->nid); return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_pcm_cleanup, "SND_HDA_CODEC_HDMI"); static int hdmi_pcm_close(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { struct hdmi_spec *spec = codec->spec; int cvt_idx, pin_idx, pcm_idx; struct hdmi_spec_per_cvt *per_cvt; struct hdmi_spec_per_pin *per_pin; int pinctl; int err = 0; mutex_lock(&spec->pcm_lock); if (hinfo->nid) { pcm_idx = hinfo_to_pcm_index(codec, hinfo); if (snd_BUG_ON(pcm_idx < 0)) { err = -EINVAL; goto unlock; } cvt_idx = cvt_nid_to_cvt_index(codec, hinfo->nid); if (snd_BUG_ON(cvt_idx < 0)) { err = -EINVAL; goto unlock; } per_cvt = get_cvt(spec, cvt_idx); per_cvt->assigned = false; hinfo->nid = 0; azx_stream(get_azx_dev(substream))->stripe = 0; snd_hda_spdif_ctls_unassign(codec, pcm_idx); clear_bit(pcm_idx, &spec->pcm_in_use); pin_idx = hinfo_to_pin_index(codec, hinfo); /* * In such a case, return 0 to match the behavior in * hdmi_pcm_open() */ if (pin_idx < 0) goto unlock; per_pin = get_pin(spec, pin_idx); if (spec->dyn_pin_out) { snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0); snd_hda_codec_write(codec, per_pin->pin_nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl & ~PIN_OUT); } mutex_lock(&per_pin->lock); per_pin->chmap_set = false; memset(per_pin->chmap, 0, sizeof(per_pin->chmap)); per_pin->setup = false; per_pin->channels = 0; mutex_unlock(&per_pin->lock); } unlock: mutex_unlock(&spec->pcm_lock); return err; } static const struct hda_pcm_ops generic_ops = { .open = hdmi_pcm_open, .close = hdmi_pcm_close, .prepare = snd_hda_hdmi_generic_pcm_prepare, .cleanup = snd_hda_hdmi_generic_pcm_cleanup, }; static int hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx) { struct hda_codec *codec = hdac_to_hda_codec(hdac); struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); if (!per_pin) return 0; return per_pin->sink_eld.info.spk_alloc; } static void hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx, unsigned char *chmap) { struct hda_codec *codec = hdac_to_hda_codec(hdac); struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); /* chmap is already set to 0 in caller */ if (!per_pin) return; memcpy(chmap, per_pin->chmap, ARRAY_SIZE(per_pin->chmap)); } static void hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx, unsigned char *chmap, int prepared) { struct hda_codec *codec = hdac_to_hda_codec(hdac); struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); if (!per_pin) return; mutex_lock(&per_pin->lock); per_pin->chmap_set = true; memcpy(per_pin->chmap, chmap, ARRAY_SIZE(per_pin->chmap)); if (prepared) snd_hda_hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm); mutex_unlock(&per_pin->lock); } static bool is_hdmi_pcm_attached(struct hdac_device *hdac, int pcm_idx) { struct hda_codec *codec = hdac_to_hda_codec(hdac); struct hdmi_spec *spec = codec->spec; struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); return per_pin ? true:false; } int snd_hda_hdmi_generic_build_pcms(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int idx, pcm_num; /* limit the PCM devices to the codec converters or available PINs */ pcm_num = min(spec->num_cvts, spec->num_pins); codec_dbg(codec, "hdmi: pcm_num set to %d\n", pcm_num); for (idx = 0; idx < pcm_num; idx++) { struct hdmi_spec_per_cvt *per_cvt; struct hda_pcm *info; struct hda_pcm_stream *pstr; info = snd_hda_codec_pcm_new(codec, "HDMI %d", idx); if (!info) return -ENOMEM; spec->pcm_rec[idx].pcm = info; spec->pcm_used++; info->pcm_type = HDA_PCM_TYPE_HDMI; info->own_chmap = true; pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK]; pstr->substreams = 1; pstr->ops = generic_ops; per_cvt = get_cvt(spec, 0); pstr->channels_min = per_cvt->channels_min; pstr->channels_max = per_cvt->channels_max; /* pcm number is less than pcm_rec array size */ if (spec->pcm_used >= ARRAY_SIZE(spec->pcm_rec)) break; /* other pstr fields are set in open */ } return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_build_pcms, "SND_HDA_CODEC_HDMI"); static void free_hdmi_jack_priv(struct snd_jack *jack) { struct hdmi_pcm *pcm = jack->private_data; pcm->jack = NULL; } static int generic_hdmi_build_jack(struct hda_codec *codec, int pcm_idx) { char hdmi_str[32] = "HDMI/DP"; struct hdmi_spec *spec = codec->spec; struct snd_jack *jack; int pcmdev = get_pcm_rec(spec, pcm_idx)->device; int err; if (pcmdev > 0) sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev); err = snd_jack_new(codec->card, hdmi_str, SND_JACK_AVOUT, &jack, true, false); if (err < 0) return err; spec->pcm_rec[pcm_idx].jack = jack; jack->private_data = &spec->pcm_rec[pcm_idx]; jack->private_free = free_hdmi_jack_priv; return 0; } int snd_hda_hdmi_generic_build_controls(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int dev, err; int pin_idx, pcm_idx; for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) { if (!get_pcm_rec(spec, pcm_idx)->pcm) { /* no PCM: mark this for skipping permanently */ set_bit(pcm_idx, &spec->pcm_bitmap); continue; } err = generic_hdmi_build_jack(codec, pcm_idx); if (err < 0) return err; /* create the spdif for each pcm * pin will be bound when monitor is connected */ err = snd_hda_create_dig_out_ctls(codec, 0, spec->cvt_nids[0], HDA_PCM_TYPE_HDMI); if (err < 0) return err; snd_hda_spdif_ctls_unassign(codec, pcm_idx); dev = get_pcm_rec(spec, pcm_idx)->device; if (dev != SNDRV_PCM_INVALID_DEVICE) { /* add control for ELD Bytes */ err = hdmi_create_eld_ctl(codec, pcm_idx, dev); if (err < 0) 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); struct hdmi_eld *pin_eld = &per_pin->sink_eld; if (spec->static_pcm_mapping) { hdmi_attach_hda_pcm(spec, per_pin); hdmi_pcm_setup_pin(spec, per_pin); } pin_eld->eld_valid = false; hdmi_present_sense(per_pin, 0); } /* add channel maps */ for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) { struct hda_pcm *pcm; pcm = get_pcm_rec(spec, pcm_idx); if (!pcm || !pcm->pcm) break; err = snd_hdac_add_chmap_ctls(pcm->pcm, pcm_idx, &spec->chmap); if (err < 0) return err; } return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_build_controls, "SND_HDA_CODEC_HDMI"); int snd_hda_hdmi_generic_init_per_pins(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int pin_idx; for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); per_pin->codec = codec; mutex_init(&per_pin->lock); INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld); eld_proc_new(per_pin, pin_idx); } return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_init_per_pins, "SND_HDA_CODEC_HDMI"); int snd_hda_hdmi_generic_init(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int pin_idx; mutex_lock(&spec->bind_lock); for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); hda_nid_t pin_nid = per_pin->pin_nid; int dev_id = per_pin->dev_id; snd_hda_set_dev_select(codec, pin_nid, dev_id); hdmi_init_pin(codec, pin_nid); if (codec_has_acomp(codec)) continue; snd_hda_jack_detect_enable_callback_mst(codec, pin_nid, dev_id, jack_callback); } mutex_unlock(&spec->bind_lock); return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_init, "SND_HDA_CODEC_HDMI"); static void hdmi_array_init(struct hdmi_spec *spec, int nums) { snd_array_init(&spec->pins, sizeof(struct hdmi_spec_per_pin), nums); snd_array_init(&spec->cvts, sizeof(struct hdmi_spec_per_cvt), nums); } static void hdmi_array_free(struct hdmi_spec *spec) { snd_array_free(&spec->pins); snd_array_free(&spec->cvts); } void snd_hda_hdmi_generic_spec_free(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; if (spec) { hdmi_array_free(spec); kfree(spec); codec->spec = NULL; } codec->dp_mst = false; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_spec_free, "SND_HDA_CODEC_HDMI"); void snd_hda_hdmi_generic_remove(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int pin_idx, pcm_idx; if (spec->acomp_registered) { snd_hdac_acomp_exit(&codec->bus->core); } else if (codec_has_acomp(codec)) { snd_hdac_acomp_register_notifier(&codec->bus->core, NULL); } codec->relaxed_resume = 0; for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); cancel_delayed_work_sync(&per_pin->work); eld_proc_free(per_pin); } for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) { if (spec->pcm_rec[pcm_idx].jack == NULL) continue; snd_device_free(codec->card, spec->pcm_rec[pcm_idx].jack); } snd_hda_hdmi_generic_spec_free(codec); } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_remove, "SND_HDA_CODEC_HDMI"); int snd_hda_hdmi_generic_suspend(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int pin_idx; for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); cancel_delayed_work_sync(&per_pin->work); } return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_suspend, "SND_HDA_CODEC_HDMI"); int snd_hda_hdmi_generic_resume(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int pin_idx; snd_hda_codec_init(codec); snd_hda_regmap_sync(codec); for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); hdmi_present_sense(per_pin, 1); } return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_resume, "SND_HDA_CODEC_HDMI"); static const struct hdmi_ops generic_standard_hdmi_ops = { .pin_get_eld = hdmi_pin_get_eld, .pin_setup_infoframe = hdmi_pin_setup_infoframe, .pin_hbr_setup = hdmi_pin_hbr_setup, .setup_stream = snd_hda_hdmi_setup_stream, }; /* allocate codec->spec and assign/initialize generic parser ops */ int snd_hda_hdmi_generic_alloc(struct hda_codec *codec) { struct hdmi_spec *spec; spec = kzalloc(sizeof(*spec), GFP_KERNEL); if (!spec) return -ENOMEM; spec->codec = codec; spec->ops = generic_standard_hdmi_ops; spec->dev_num = 1; /* initialize to 1 */ mutex_init(&spec->pcm_lock); mutex_init(&spec->bind_lock); snd_hdac_register_chmap_ops(&codec->core, &spec->chmap); spec->chmap.ops.get_chmap = hdmi_get_chmap; spec->chmap.ops.set_chmap = hdmi_set_chmap; spec->chmap.ops.is_pcm_attached = is_hdmi_pcm_attached; spec->chmap.ops.get_spk_alloc = hdmi_get_spk_alloc; codec->spec = spec; hdmi_array_init(spec, 4); return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_alloc, "SND_HDA_CODEC_HDMI"); /* generic HDMI parser */ int snd_hda_hdmi_generic_probe(struct hda_codec *codec) { int err; err = snd_hda_hdmi_generic_alloc(codec); if (err < 0) return err; err = snd_hda_hdmi_parse_codec(codec); if (err < 0) { snd_hda_hdmi_generic_spec_free(codec); return err; } snd_hda_hdmi_generic_init_per_pins(codec); return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_generic_probe, "SND_HDA_CODEC_HDMI"); /* * generic audio component binding */ /* turn on / off the unsol event jack detection dynamically */ static void reprogram_jack_detect(struct hda_codec *codec, hda_nid_t nid, int dev_id, bool use_acomp) { struct hda_jack_tbl *tbl; tbl = snd_hda_jack_tbl_get_mst(codec, nid, dev_id); if (tbl) { /* clear unsol even if component notifier is used, or re-enable * if notifier is cleared */ unsigned int val = use_acomp ? 0 : (AC_USRSP_EN | tbl->tag); snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_UNSOLICITED_ENABLE, val); } } /* set up / clear component notifier dynamically */ static void generic_acomp_notifier_set(struct drm_audio_component *acomp, bool use_acomp) { struct hdmi_spec *spec; int i; spec = container_of(acomp->audio_ops, struct hdmi_spec, drm_audio_ops); mutex_lock(&spec->bind_lock); spec->use_acomp_notifier = use_acomp; spec->codec->relaxed_resume = use_acomp; spec->codec->bus->keep_power = 0; /* reprogram each jack detection logic depending on the notifier */ for (i = 0; i < spec->num_pins; i++) reprogram_jack_detect(spec->codec, get_pin(spec, i)->pin_nid, get_pin(spec, i)->dev_id, use_acomp); mutex_unlock(&spec->bind_lock); } /* enable / disable the notifier via master bind / unbind */ int snd_hda_hdmi_acomp_master_bind(struct device *dev, struct drm_audio_component *acomp) { generic_acomp_notifier_set(acomp, true); return 0; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_acomp_master_bind, "SND_HDA_CODEC_HDMI"); void snd_hda_hdmi_acomp_master_unbind(struct device *dev, struct drm_audio_component *acomp) { generic_acomp_notifier_set(acomp, false); } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_acomp_master_unbind, "SND_HDA_CODEC_HDMI"); /* check whether both HD-audio and DRM PCI devices belong to the same bus */ static int match_bound_vga(struct device *dev, int subtype, void *data) { struct hdac_bus *bus = data; struct pci_dev *pci, *master; if (!dev_is_pci(dev) || !dev_is_pci(bus->dev)) return 0; master = to_pci_dev(bus->dev); pci = to_pci_dev(dev); return master->bus == pci->bus; } /* audio component notifier for AMD/Nvidia HDMI codecs */ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) { struct hda_codec *codec = audio_ptr; struct hdmi_spec *spec = codec->spec; hda_nid_t pin_nid = spec->port2pin(codec, port); if (!pin_nid) return; if (get_wcaps_type(get_wcaps(codec, pin_nid)) != AC_WID_PIN) return; /* skip notification during system suspend (but not in runtime PM); * the state will be updated at resume */ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) return; snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_acomp_pin_eld_notify, "SND_HDA_CODEC_HDMI"); /* set up the private drm_audio_ops from the template */ void snd_hda_hdmi_setup_drm_audio_ops(struct hda_codec *codec, const struct drm_audio_component_audio_ops *ops) { struct hdmi_spec *spec = codec->spec; spec->drm_audio_ops.audio_ptr = codec; /* intel_audio_codec_enable() or intel_audio_codec_disable() * will call pin_eld_notify with using audio_ptr pointer * We need make sure audio_ptr is really setup */ wmb(); spec->drm_audio_ops.pin2port = ops->pin2port; spec->drm_audio_ops.pin_eld_notify = ops->pin_eld_notify; spec->drm_audio_ops.master_bind = ops->master_bind; spec->drm_audio_ops.master_unbind = ops->master_unbind; } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_setup_drm_audio_ops, "SND_HDA_CODEC_HDMI"); /* initialize the generic HDMI audio component */ void snd_hda_hdmi_acomp_init(struct hda_codec *codec, const struct drm_audio_component_audio_ops *ops, int (*port2pin)(struct hda_codec *, int)) { struct hdmi_spec *spec = codec->spec; if (!enable_acomp) { codec_info(codec, "audio component disabled by module option\n"); return; } spec->port2pin = port2pin; snd_hda_hdmi_setup_drm_audio_ops(codec, ops); if (!snd_hdac_acomp_init(&codec->bus->core, &spec->drm_audio_ops, match_bound_vga, 0)) { spec->acomp_registered = true; } } EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_acomp_init, "SND_HDA_CODEC_HDMI"); /* */ enum { MODEL_GENERIC, MODEL_GF, }; static int generichdmi_probe(struct hda_codec *codec, const struct hda_device_id *id) { int err; err = snd_hda_hdmi_generic_probe(codec); if (err < 0) return err; /* * Glenfly GPUs have two codecs, stream switches from one codec to * another, need to do actual clean-ups in codec_cleanup_stream */ if (id->driver_data == MODEL_GF) codec->no_sticky_stream = 1; return 0; } static const struct hda_codec_ops generichdmi_codec_ops = { .probe = generichdmi_probe, .remove = snd_hda_hdmi_generic_remove, .init = snd_hda_hdmi_generic_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, }; /* */ static const struct hda_device_id snd_hda_id_generichdmi[] = { HDA_CODEC_ID_MODEL(0x00147a47, "Loongson HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x10951390, "SiI1390 HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x10951392, "SiI1392 HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x11069f84, "VX11 HDMI/DP", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x11069f85, "VX11 HDMI/DP", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x17e80047, "Chrontel HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x1d179f86, "ZX-100S HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f87, "ZX-100S HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f88, "KX-5000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f89, "KX-5000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8a, "KX-6000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8b, "KX-6000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8c, "KX-6000G HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8d, "KX-6000G HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d85, "Arise 85 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d86, "Arise 86 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d87, "Arise 87 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x80862801, "Bearlake HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x80862802, "Cantiga HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x80862803, "Eaglelake HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x80862880, "CedarTrail HDMI", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x808629fb, "Crestline HDMI", MODEL_GENERIC), /* special ID for generic HDMI */ HDA_CODEC_ID_MODEL(HDA_CODEC_ID_GENERIC_HDMI, "Generic HDMI", MODEL_GENERIC), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_generichdmi); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Generic HDMI HD-audio codec"); static struct hda_codec_driver generichdmi_driver = { .id = snd_hda_id_generichdmi, .ops = &generichdmi_codec_ops, }; module_hda_codec_driver(generichdmi_driver);