ALSA: hda/hdmi - Allow audio component for AMD/ATI and Nvidia HDMI
[linux-2.6-block.git] / sound / pci / hda / patch_hdmi.c
index c380596b2e84c0079d7268e554dc7c645372278e..2096993eaf285cfbd7160a96a137accd4d5b18f8 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <linux/init.h>
 #include <linux/delay.h>
+#include <linux/pci.h>
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
@@ -119,6 +120,7 @@ struct hdmi_pcm {
 };
 
 struct hdmi_spec {
+       struct hda_codec *codec;
        int num_cvts;
        struct snd_array cvts; /* struct hdmi_spec_per_cvt */
        hda_nid_t cvt_nids[4]; /* only for haswell fix */
@@ -163,9 +165,11 @@ struct hdmi_spec {
        struct hda_multi_out multiout;
        struct hda_pcm_stream pcm_playback;
 
-       /* i915/powerwell (Haswell+/Valleyview+) specific */
-       bool use_acomp_notifier; /* use i915 eld_notify callback for hotplug */
+       bool use_jack_detect; /* jack detection enabled */
+       bool use_acomp_notifier; /* use eld_notify callback for hotplug */
+       bool acomp_registered; /* audio component registered in this driver */
        struct drm_audio_component_audio_ops drm_audio_ops;
+       int (*port2pin)(struct hda_codec *, int); /* reverse port/pin mapping */
 
        struct hdac_chmap chmap;
        hda_nid_t vendor_nid;
@@ -765,6 +769,10 @@ static void check_presence_and_report(struct hda_codec *codec, hda_nid_t nid,
 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;
+
        /* hda_jack don't support DP MST */
        check_presence_and_report(codec, jack->nid, 0);
 }
@@ -823,6 +831,9 @@ static void hdmi_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;
 
+       if (codec_has_acomp(codec))
+               return;
+
        if (!snd_hda_jack_tbl_get_from_tag(codec, tag)) {
                codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag);
                return;
@@ -1636,18 +1647,13 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
                        snd_hda_power_down_pm(codec);
                        return false;
                }
-       }
-
-       if (codec_has_acomp(codec)) {
+               ret = hdmi_present_sense_via_verbs(per_pin, repoll);
+               snd_hda_power_down_pm(codec);
+       } else {
                sync_eld_via_acomp(codec, per_pin);
                ret = false; /* don't call snd_hda_jack_report_sync() */
-       } else {
-               ret = hdmi_present_sense_via_verbs(per_pin, repoll);
        }
 
-       if (!codec_has_acomp(codec))
-               snd_hda_power_down_pm(codec);
-
        return ret;
 }
 
@@ -2252,6 +2258,8 @@ static int generic_hdmi_init(struct hda_codec *codec)
        struct hdmi_spec *spec = codec->spec;
        int pin_idx;
 
+       mutex_lock(&spec->pcm_lock);
+       spec->use_jack_detect = !codec->jackpoll_interval;
        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;
@@ -2259,11 +2267,15 @@ static int generic_hdmi_init(struct hda_codec *codec)
 
                snd_hda_set_dev_select(codec, pin_nid, dev_id);
                hdmi_init_pin(codec, pin_nid);
-               if (!codec_has_acomp(codec))
+               if (codec_has_acomp(codec))
+                       continue;
+               if (spec->use_jack_detect)
+                       snd_hda_jack_detect_enable(codec, pin_nid);
+               else
                        snd_hda_jack_detect_enable_callback(codec, pin_nid,
-                               codec->jackpoll_interval > 0 ?
-                               jack_callback : NULL);
+                                                           jack_callback);
        }
+       mutex_unlock(&spec->pcm_lock);
        return 0;
 }
 
@@ -2296,7 +2308,9 @@ static void generic_hdmi_free(struct hda_codec *codec)
        struct hdmi_spec *spec = codec->spec;
        int pin_idx, pcm_idx;
 
-       if (codec_has_acomp(codec)) {
+       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;
        }
@@ -2364,6 +2378,7 @@ static int alloc_generic_hdmi(struct hda_codec *codec)
        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);
@@ -2401,6 +2416,136 @@ static int patch_generic_hdmi(struct hda_codec *codec)
        return 0;
 }
 
+/*
+ * 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,
+                                 bool use_acomp)
+{
+       struct hda_jack_tbl *tbl;
+
+       tbl = snd_hda_jack_tbl_get(codec, nid);
+       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);
+       } else {
+               /* if no jack entry was defined beforehand, create a new one
+                * at need (i.e. only when notifier is cleared)
+                */
+               if (!use_acomp)
+                       snd_hda_jack_detect_enable(codec, nid);
+       }
+}
+
+/* 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->pcm_lock);
+       spec->use_acomp_notifier = use_acomp;
+       spec->codec->relaxed_resume = use_acomp;
+       /* reprogram each jack detection logic depending on the notifier */
+       if (spec->use_jack_detect) {
+               for (i = 0; i < spec->num_pins; i++)
+                       reprogram_jack_detect(spec->codec,
+                                             get_pin(spec, i)->pin_nid,
+                                             use_acomp);
+       }
+       mutex_unlock(&spec->pcm_lock);
+}
+
+/* enable / disable the notifier via master bind / unbind */
+static int generic_acomp_master_bind(struct device *dev,
+                                    struct drm_audio_component *acomp)
+{
+       generic_acomp_notifier_set(acomp, true);
+       return 0;
+}
+
+static void generic_acomp_master_unbind(struct device *dev,
+                                       struct drm_audio_component *acomp)
+{
+       generic_acomp_notifier_set(acomp, false);
+}
+
+/* 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 */
+static void generic_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 (snd_power_get_state(codec->card) != SNDRV_CTL_POWER_D0)
+               return;
+       /* ditto during suspend/resume process itself */
+       if (snd_hdac_is_in_pm(&codec->core))
+               return;
+
+       check_presence_and_report(codec, pin_nid, dev_id);
+}
+
+/* set up the private drm_audio_ops from the template */
+static void 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;
+}
+
+/* initialize the generic HDMI audio component */
+static void generic_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;
+
+       spec->port2pin = port2pin;
+       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;
+}
+
 /*
  * Intel codec parsers and helpers
  */
@@ -2569,20 +2714,19 @@ static void intel_pin_eld_notify(void *audio_ptr, int port, int pipe)
        check_presence_and_report(codec, pin_nid, dev_id);
 }
 
+static const struct drm_audio_component_audio_ops intel_audio_ops = {
+       .pin2port = intel_pin2port,
+       .pin_eld_notify = intel_pin_eld_notify,
+};
+
 /* register i915 component pin_eld_notify callback */
 static void register_i915_notifier(struct hda_codec *codec)
 {
        struct hdmi_spec *spec = codec->spec;
 
        spec->use_acomp_notifier = true;
-       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 = intel_pin2port;
-       spec->drm_audio_ops.pin_eld_notify = intel_pin_eld_notify;
+       spec->port2pin = intel_port2pin;
+       setup_drm_audio_ops(codec, &intel_audio_ops);
        snd_hdac_acomp_register_notifier(&codec->bus->core,
                                        &spec->drm_audio_ops);
        /* no need for forcible resume for jack check thanks to notifier */
@@ -2980,6 +3124,7 @@ static int patch_simple_hdmi(struct hda_codec *codec,
        if (!spec)
                return -ENOMEM;
 
+       spec->codec = codec;
        codec->spec = spec;
        hdmi_array_init(spec, 1);
 
@@ -3284,6 +3429,26 @@ static int nvhdmi_chmap_validate(struct hdac_chmap *chmap,
        return 0;
 }
 
+/* map from pin NID to port; port is 0-based */
+/* for Nvidia: assume widget NID starting from 4, with step 1 (4, 5, 6, ...) */
+static int nvhdmi_pin2port(void *audio_ptr, int pin_nid)
+{
+       return pin_nid - 4;
+}
+
+/* reverse-map from port to pin NID: see above */
+static int nvhdmi_port2pin(struct hda_codec *codec, int port)
+{
+       return port + 4;
+}
+
+static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = {
+       .pin2port = nvhdmi_pin2port,
+       .pin_eld_notify = generic_acomp_pin_eld_notify,
+       .master_bind = generic_acomp_master_bind,
+       .master_unbind = generic_acomp_master_unbind,
+};
+
 static int patch_nvhdmi(struct hda_codec *codec)
 {
        struct hdmi_spec *spec;
@@ -3300,6 +3465,8 @@ static int patch_nvhdmi(struct hda_codec *codec)
                nvhdmi_chmap_cea_alloc_validate_get_type;
        spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate;
 
+       generic_acomp_init(codec, &nvhdmi_audio_ops, nvhdmi_port2pin);
+
        return 0;
 }
 
@@ -3787,6 +3954,26 @@ static int atihdmi_init(struct hda_codec *codec)
        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 = generic_acomp_pin_eld_notify,
+       .master_bind = generic_acomp_master_bind,
+       .master_unbind = generic_acomp_master_unbind,
+};
+
 static int patch_atihdmi(struct hda_codec *codec)
 {
        struct hdmi_spec *spec;
@@ -3835,6 +4022,8 @@ static int patch_atihdmi(struct hda_codec *codec)
         */
        codec->link_down_at_suspend = 1;
 
+       generic_acomp_init(codec, &atihdmi_audio_ops, atihdmi_port2pin);
+
        return 0;
 }