]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
ASoC: SOF: Intel: pci-tgl: unblock S5 entry if DMA stop has failed"
authorKai Vehmanen <kai.vehmanen@linux.intel.com>
Fri, 9 Dec 2022 11:45:28 +0000 (13:45 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Jan 2023 11:02:43 +0000 (12:02 +0100)
[ Upstream commit 2aa2a5ead0ee0a358bf80a2984a641d1bf2adc2a ]

If system shutdown has not been completed cleanly, it is possible the
DMA stream shutdown has not been done, or was not clean.

If this is the case, Intel TGL/ADL HDA platforms may fail to shutdown
cleanly due to pending HDA DMA transactions. To avoid this, detect this
scenario in the shutdown callback, and perform an additional controller
reset. This has been tested to unblock S5 entry if this condition is
hit.

Co-developed-by: Archana Patni <archana.patni@intel.com>
Signed-off-by: Archana Patni <archana.patni@intel.com>
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/20221209114529.3909192-2-kai.vehmanen@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
sound/soc/sof/intel/hda-dsp.c
sound/soc/sof/intel/hda.h
sound/soc/sof/intel/tgl.c

index 3c76f843454b6fc7fb4f50fd73c515fd7e27cc5b..428aee8fd93b12ecc82118eb706bfa3df2d685a0 100644 (file)
@@ -903,6 +903,78 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
        return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
 }
 
+static unsigned int hda_dsp_check_for_dma_streams(struct snd_sof_dev *sdev)
+{
+       struct hdac_bus *bus = sof_to_bus(sdev);
+       struct hdac_stream *s;
+       unsigned int active_streams = 0;
+       int sd_offset;
+       u32 val;
+
+       list_for_each_entry(s, &bus->stream_list, list) {
+               sd_offset = SOF_STREAM_SD_OFFSET(s);
+               val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+                                      sd_offset);
+               if (val & SOF_HDA_SD_CTL_DMA_START)
+                       active_streams |= BIT(s->index);
+       }
+
+       return active_streams;
+}
+
+static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev)
+{
+       int ret;
+
+       /*
+        * Do not assume a certain timing between the prior
+        * suspend flow, and running of this quirk function.
+        * This is needed if the controller was just put
+        * to reset before calling this function.
+        */
+       usleep_range(500, 1000);
+
+       /*
+        * Take controller out of reset to flush DMA
+        * transactions.
+        */
+       ret = hda_dsp_ctrl_link_reset(sdev, false);
+       if (ret < 0)
+               return ret;
+
+       usleep_range(500, 1000);
+
+       /* Restore state for shutdown, back to reset */
+       ret = hda_dsp_ctrl_link_reset(sdev, true);
+       if (ret < 0)
+               return ret;
+
+       return ret;
+}
+
+int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev)
+{
+       unsigned int active_streams;
+       int ret, ret2;
+
+       /* check if DMA cleanup has been successful */
+       active_streams = hda_dsp_check_for_dma_streams(sdev);
+
+       sdev->system_suspend_target = SOF_SUSPEND_S3;
+       ret = snd_sof_suspend(sdev->dev);
+
+       if (active_streams) {
+               dev_warn(sdev->dev,
+                        "There were active DSP streams (%#x) at shutdown, trying to recover\n",
+                        active_streams);
+               ret2 = hda_dsp_s5_quirk(sdev);
+               if (ret2 < 0)
+                       dev_err(sdev->dev, "shutdown recovery failed (%d)\n", ret2);
+       }
+
+       return ret;
+}
+
 int hda_dsp_shutdown(struct snd_sof_dev *sdev)
 {
        sdev->system_suspend_target = SOF_SUSPEND_S3;
index 2ab3c3840b926925803d39a89b1b9a07dff76047..9acd21901e68cbee2bdf0e4f353e8e55be26f1d0 100644 (file)
@@ -581,6 +581,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev);
 int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev);
 int hda_dsp_runtime_resume(struct snd_sof_dev *sdev);
 int hda_dsp_runtime_idle(struct snd_sof_dev *sdev);
+int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev);
 int hda_dsp_shutdown(struct snd_sof_dev *sdev);
 int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev);
 void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags);
index 9ae2890e9dac491e2243cbc1e934e06eeca0d100..8637fe102c87ce04417861201220c945a222b525 100644 (file)
@@ -60,7 +60,7 @@ int sof_tgl_ops_init(struct snd_sof_dev *sdev)
        memcpy(&sof_tgl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
 
        /* probe/remove/shutdown */
-       sof_tgl_ops.shutdown    = hda_dsp_shutdown;
+       sof_tgl_ops.shutdown    = hda_dsp_shutdown_dma_flush;
 
        if (sdev->pdata->ipc_type == SOF_IPC) {
                /* doorbell */