]> git.dujemihanovic.xyz Git - linux.git/commitdiff
ASoC: SOF: Intel: hda: fix hotplug when only codec is suspended
authorKai Vehmanen <kai.vehmanen@linux.intel.com>
Fri, 5 Nov 2021 11:16:55 +0000 (13:16 +0200)
committerMark Brown <broonie@kernel.org>
Fri, 5 Nov 2021 12:58:11 +0000 (12:58 +0000)
If codec is in runtime suspend, but controller is not, hotplug events
are missed as the codec has no way to alert the controller. Problem does
not occur if both controller and codec are active, or when both are
suspended.

An easy way to reproduce is to play an audio stream on one codec (e.g.
to HDMI/DP display codec), wait for other HDA codec to go to runtime
suspend, and then plug in a headset to the suspended codec. The jack
event is not reported correctly in this case. Another way to reproduce
is to force controller to stay active with
"snd_sof_pci.sof_pci_debug=0x1"

Fix the issue by reconfiguring the WAKEEN register when powering up/down
individual links, and handling control events in the interrupt handler.

Fixes: 87fc20e4a0cb ("ASoC: SOF: Intel: hda: use hdac_ext fine-grained link management")
Reported-by: Hui Wang <hui.wang@canonical.com>
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com>
Link: https://lore.kernel.org/r/20211105111655.668777-1-kai.vehmanen@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/sof/intel/hda-bus.c
sound/soc/sof/intel/hda-dsp.c
sound/soc/sof/intel/hda.c

index 30025d3c16b6e925d7c0cd3790d086b2e3bc5be1..0862ff8b66273389414a8f1bf1c4ac8e73b29ed5 100644 (file)
@@ -10,6 +10,8 @@
 #include <linux/io.h>
 #include <sound/hdaudio.h>
 #include <sound/hda_i915.h>
+#include <sound/hda_codec.h>
+#include <sound/hda_register.h>
 #include "../sof-priv.h"
 #include "hda.h"
 
 #endif
 
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+static void update_codec_wake_enable(struct hdac_bus *bus, unsigned int addr, bool link_power)
+{
+       unsigned int mask = snd_hdac_chip_readw(bus, WAKEEN);
+
+       if (link_power)
+               mask &= ~BIT(addr);
+       else
+               mask |= BIT(addr);
+
+       snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, mask);
+}
+
 static void sof_hda_bus_link_power(struct hdac_device *codec, bool enable)
 {
        struct hdac_bus *bus = codec->bus;
@@ -41,6 +55,9 @@ static void sof_hda_bus_link_power(struct hdac_device *codec, bool enable)
         */
        if (codec->addr == HDA_IDISP_ADDR && !enable)
                snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
+
+       /* WAKEEN needs to be set for disabled links */
+       update_codec_wake_enable(bus, codec->addr, enable);
 }
 
 static const struct hdac_bus_ops bus_core_ops = {
index 058baca2cd0e90cb197aa95b86c6692a413e192c..287dc0eb6686f52bf815cfba934d71cdd7ecb667 100644 (file)
@@ -622,8 +622,7 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
        hda_dsp_ipc_int_disable(sdev);
 
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-       if (runtime_suspend)
-               hda_codec_jack_wake_enable(sdev, true);
+       hda_codec_jack_wake_enable(sdev, runtime_suspend);
 
        /* power down all hda link */
        snd_hdac_ext_bus_link_power_down_all(bus);
index 883d78dd01b5a4ddba16367c504a93f8dbc8c38f..568d351b7a4e97cb5f9beb3a612c8cf709e56dae 100644 (file)
@@ -810,6 +810,20 @@ skip_soundwire:
        return 0;
 }
 
+static void hda_check_for_state_change(struct snd_sof_dev *sdev)
+{
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+       struct hdac_bus *bus = sof_to_bus(sdev);
+       unsigned int codec_mask;
+
+       codec_mask = snd_hdac_chip_readw(bus, STATESTS);
+       if (codec_mask) {
+               hda_codec_jack_check(sdev);
+               snd_hdac_chip_writew(bus, STATESTS, codec_mask);
+       }
+#endif
+}
+
 static irqreturn_t hda_dsp_interrupt_handler(int irq, void *context)
 {
        struct snd_sof_dev *sdev = context;
@@ -851,6 +865,8 @@ static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context)
        if (hda_sdw_check_wakeen_irq(sdev))
                hda_sdw_process_wakeen(sdev);
 
+       hda_check_for_state_change(sdev);
+
        /* enable GIE interrupt */
        snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
                                SOF_HDA_INTCTL,