ASoC: SOF: Intel: cnl: Implement feature to support DSP D0i3 in S0
authorRanjani Sridharan <ranjani.sridharan@linux.intel.com>
Wed, 29 Jan 2020 22:07:25 +0000 (16:07 -0600)
committerMark Brown <broonie@kernel.org>
Tue, 11 Feb 2020 11:48:07 +0000 (11:48 +0000)
This patch implements support for DSP D0i3 when the system
is in S0. The basic idea is to schedule a delayed work after
every successful IPC TX that checks if there are only
D0I3-compatible streams active and if so transition
the DSP to D0I3.

With the introduction of DSP D0I3 in S0, we need to
ensure that the DSP is in D0I0 before sending any new
IPCs. The exception for this would be the
compact IPCs that are used to set the DSP in
D0I3/D0I0 states.

Signed-off-by: Keyon Jie <yang.jie@linux.intel.com>
Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20200129220726.31792-9-pierre-louis.bossart@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/sof/intel/cnl.c
sound/soc/sof/intel/hda-dsp.c
sound/soc/sof/intel/hda.c
sound/soc/sof/intel/hda.h
sound/soc/sof/ipc.c
sound/soc/sof/sof-priv.h

index 9e2d8afe05351028788235cdca592f94af249efa..8a59fec7291999619c77c84cb80c8350b49efe7b 100644 (file)
@@ -171,23 +171,48 @@ static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg,
 static int cnl_ipc_send_msg(struct snd_sof_dev *sdev,
                            struct snd_sof_ipc_msg *msg)
 {
+       struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+       struct sof_ipc_cmd_hdr *hdr;
        u32 dr = 0;
        u32 dd = 0;
 
+       /*
+        * Currently the only compact IPC supported is the PM_GATE
+        * IPC which is used for transitioning the DSP between the
+        * D0I0 and D0I3 states. And these are sent only during the
+        * set_power_state() op. Therefore, there will never be a case
+        * that a compact IPC results in the DSP exiting D0I3 without
+        * the host and FW being in sync.
+        */
        if (cnl_compact_ipc_compress(msg, &dr, &dd)) {
                /* send the message via IPC registers */
                snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD,
                                  dd);
                snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
                                  CNL_DSP_REG_HIPCIDR_BUSY | dr);
-       } else {
-               /* send the message via mailbox */
-               sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
-                                 msg->msg_size);
-               snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
-                                 CNL_DSP_REG_HIPCIDR_BUSY);
+               return 0;
        }
 
+       /* send the message via mailbox */
+       sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
+                         msg->msg_size);
+       snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
+                         CNL_DSP_REG_HIPCIDR_BUSY);
+
+       hdr = msg->msg_data;
+
+       /*
+        * Use mod_delayed_work() to schedule the delayed work
+        * to avoid scheduling multiple workqueue items when
+        * IPCs are sent at a high-rate. mod_delayed_work()
+        * modifies the timer if the work is pending.
+        * Also, a new delayed work should not be queued after the
+        * the CTX_SAVE IPC, which is sent before the DSP enters D3.
+        */
+       if (hdr->cmd != (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE))
+               mod_delayed_work(system_wq, &hdev->d0i3_work,
+                                msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS));
+
        return 0;
 }
 
index 7b8425330ae0b18ba616d1b1c698b8fce880ccb1..ee604be715b9e94aab14341463d4328ebcd44c37 100644 (file)
@@ -17,6 +17,7 @@
 
 #include <sound/hdaudio_ext.h>
 #include <sound/hda_register.h>
+#include "../sof-audio.h"
 #include "../ops.h"
 #include "hda.h"
 #include "hda-ipc.h"
@@ -334,8 +335,9 @@ static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags)
        pm_gate.flags = flags;
 
        /* send pm_gate ipc to dsp */
-       return sof_ipc_tx_message(sdev->ipc, pm_gate.hdr.cmd, &pm_gate,
-                                 sizeof(pm_gate), &reply, sizeof(reply));
+       return sof_ipc_tx_message_no_pm(sdev->ipc, pm_gate.hdr.cmd,
+                                       &pm_gate, sizeof(pm_gate), &reply,
+                                       sizeof(reply));
 }
 
 static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value)
@@ -706,6 +708,9 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
        };
        int ret;
 
+       /* cancel any attempt for DSP D0I3 */
+       cancel_delayed_work_sync(&hda->d0i3_work);
+
        if (target_state == SOF_DSP_PM_D0) {
                /* Set DSP power state */
                ret = hda_dsp_set_power_state(sdev, &target_dsp_state);
@@ -780,3 +785,33 @@ int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
 #endif
        return 0;
 }
+
+void hda_dsp_d0i3_work(struct work_struct *work)
+{
+       struct sof_intel_hda_dev *hdev = container_of(work,
+                                                     struct sof_intel_hda_dev,
+                                                     d0i3_work.work);
+       struct hdac_bus *bus = &hdev->hbus.core;
+       struct snd_sof_dev *sdev = dev_get_drvdata(bus->dev);
+       struct sof_dsp_power_state target_state;
+       int ret;
+
+       target_state.state = SOF_DSP_PM_D0;
+
+       /* DSP can enter D0I3 iff only D0I3-compatible streams are active */
+       if (snd_sof_dsp_only_d0i3_compatible_stream_active(sdev))
+               target_state.substate = SOF_HDA_DSP_PM_D0I3;
+       else
+               target_state.substate = SOF_HDA_DSP_PM_D0I0;
+
+       /* remain in D0I0 */
+       if (target_state.substate == SOF_HDA_DSP_PM_D0I0)
+               return;
+
+       /* This can fail but error cannot be propagated */
+       ret = hda_dsp_set_power_state(sdev, &target_state);
+       if (ret < 0)
+               dev_err_ratelimited(sdev->dev,
+                                   "error: failed to set DSP state %d substate %d\n",
+                                   target_state.state, target_state.substate);
+}
index 65b86dd044f101b0255c9be51d01961469e37407..2b8754a7658433fe131f62315f158cbf7190f6af 100644 (file)
@@ -598,6 +598,8 @@ int hda_dsp_probe(struct snd_sof_dev *sdev)
        /* set default mailbox offset for FW ready message */
        sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET;
 
+       INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work);
+
        return 0;
 
 free_ipc_irq:
@@ -622,6 +624,9 @@ int hda_dsp_remove(struct snd_sof_dev *sdev)
        struct pci_dev *pci = to_pci_dev(sdev->dev);
        const struct sof_intel_dsp_desc *chip = hda->desc;
 
+       /* cancel any attempt for DSP D0I3 */
+       cancel_delayed_work_sync(&hda->d0i3_work);
+
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
        /* codec removal, invoke bus_device_remove */
        snd_hdac_ext_bus_device_remove(bus);
index 02c2a7eadb1bc66936ebfe84036cbc961785769c..a46b66437a3d2081f82f8c16203a264d4db77d1a 100644 (file)
@@ -392,6 +392,13 @@ struct sof_intel_dsp_bdl {
 #define SOF_HDA_PLAYBACK               0
 #define SOF_HDA_CAPTURE                        1
 
+/*
+ * Time in ms for opportunistic D0I3 entry delay.
+ * This has been deliberately chosen to be long to avoid race conditions.
+ * Could be optimized in future.
+ */
+#define SOF_HDA_D0I3_WORK_DELAY_MS     5000
+
 /* HDA DSP D0 substate */
 enum sof_hda_D0_substate {
        SOF_HDA_DSP_PM_D0I0,    /* default D0 substate */
@@ -420,6 +427,9 @@ struct sof_intel_hda_dev {
 
        /* DMIC device */
        struct platform_device *dmic_dev;
+
+       /* delayed work to enter D0I3 opportunistically */
+       struct delayed_work d0i3_work;
 };
 
 static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s)
@@ -487,6 +497,7 @@ void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags);
 void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags);
 void hda_ipc_dump(struct snd_sof_dev *sdev);
 void hda_ipc_irq_dump(struct snd_sof_dev *sdev);
+void hda_dsp_d0i3_work(struct work_struct *work);
 
 /*
  * DSP PCM Operations.
index b63fc529b4568e476cf6d22b4176189c74d97313..22d296f957610fa60a0ac6f2bf9f6922814ccb2a 100644 (file)
@@ -268,7 +268,6 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,
        spin_unlock_irq(&sdev->ipc_lock);
 
        if (ret < 0) {
-               /* So far IPC TX never fails, consider making the above void */
                dev_err_ratelimited(sdev->dev,
                                    "error: ipc tx failed with error %d\n",
                                    ret);
@@ -288,6 +287,32 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,
 int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
                       void *msg_data, size_t msg_bytes, void *reply_data,
                       size_t reply_bytes)
+{
+       const struct sof_dsp_power_state target_state = {
+               .state = SOF_DSP_PM_D0,
+       };
+       int ret;
+
+       /* ensure the DSP is in D0 before sending a new IPC */
+       ret = snd_sof_dsp_set_power_state(ipc->sdev, &target_state);
+       if (ret < 0) {
+               dev_err(ipc->sdev->dev, "error: resuming DSP %d\n", ret);
+               return ret;
+       }
+
+       return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes,
+                                       reply_data, reply_bytes);
+}
+EXPORT_SYMBOL(sof_ipc_tx_message);
+
+/*
+ * send IPC message from host to DSP without modifying the DSP state.
+ * This will be used for IPC's that can be handled by the DSP
+ * even in a low-power D0 substate.
+ */
+int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header,
+                            void *msg_data, size_t msg_bytes,
+                            void *reply_data, size_t reply_bytes)
 {
        int ret;
 
@@ -305,7 +330,7 @@ int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
 
        return ret;
 }
-EXPORT_SYMBOL(sof_ipc_tx_message);
+EXPORT_SYMBOL(sof_ipc_tx_message_no_pm);
 
 /* handle reply message from DSP */
 int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id)
index ef33aaadbc310d46f15a6ea390860281f4108d99..00084471d0de193452e05a8d5aeb9d318910f97f 100644 (file)
@@ -470,6 +470,9 @@ int snd_sof_ipc_valid(struct snd_sof_dev *sdev);
 int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
                       void *msg_data, size_t msg_bytes, void *reply_data,
                       size_t reply_bytes);
+int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header,
+                            void *msg_data, size_t msg_bytes,
+                            void *reply_data, size_t reply_bytes);
 
 /*
  * Trace/debug