ASoC: SOF: Intel: cnl: Implement feature to support DSP D0i3 in S0
[platform/kernel/linux-rpi.git] / sound / soc / sof / intel / cnl.c
index 9e2d8af..8a59fec 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;
 }