diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 6b075bbe5bfb..a08a77fa946b 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -280,6 +280,8 @@ int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, msg_data->primary | CNL_DSP_REG_HIPCIDR_BUSY); + hda_dsp_ipc4_schedule_d0i3_work(hdev, msg); + return 0; } diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index b4eacae8564c..68eb06f13a1f 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -364,19 +364,12 @@ static int hda_dsp_wait_d0i3c_done(struct snd_sof_dev *sdev) static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags) { - struct sof_ipc_pm_gate pm_gate; - struct sof_ipc_reply reply; + const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); - memset(&pm_gate, 0, sizeof(pm_gate)); + if (pm_ops && pm_ops->set_pm_gate) + return pm_ops->set_pm_gate(sdev, flags); - /* configure pm_gate ipc message */ - pm_gate.hdr.size = sizeof(pm_gate); - pm_gate.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE; - pm_gate.flags = flags; - - /* send pm_gate ipc to dsp */ - return sof_ipc_tx_message_no_pm(sdev->ipc, &pm_gate, sizeof(pm_gate), - &reply, sizeof(reply)); + return 0; } static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value) @@ -412,6 +405,34 @@ static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value) return 0; } +/* + * d0i3 streaming is enabled if all the active streams can + * work in d0i3 state and playback is enabled + */ +static bool hda_dsp_d0i3_streaming_applicable(struct snd_sof_dev *sdev) +{ + struct snd_pcm_substream *substream; + struct snd_sof_pcm *spcm; + bool playback_active = false; + int dir; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for_each_pcm_streams(dir) { + substream = spcm->stream[dir].substream; + if (!substream || !substream->runtime) + continue; + + if (!spcm->stream[dir].d0i3_compatible) + return false; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + playback_active = true; + } + } + + return playback_active; +} + static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev, const struct sof_dsp_power_state *target_state) { @@ -453,6 +474,9 @@ static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev, !hda_enable_trace_D0I3_S0 || sdev->system_suspend_target != SOF_SUSPEND_NONE) flags = HDA_PM_NO_DMA_TRACE; + + if (hda_dsp_d0i3_streaming_applicable(sdev)) + flags |= HDA_PM_PG_STREAMING; } else { /* prevent power gating in D0I0 */ flags = HDA_PM_PPG; diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index d7e16e6b6f52..df541b22b2d2 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -67,6 +67,32 @@ int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) return 0; } +static inline bool hda_dsp_ipc4_pm_msg(u32 primary) +{ + /* pm setting is only supported by module msg */ + if (SOF_IPC4_MSG_IS_MODULE_MSG(primary) != SOF_IPC4_MODULE_MSG) + return false; + + if (SOF_IPC4_MSG_TYPE_GET(primary) == SOF_IPC4_MOD_SET_DX || + SOF_IPC4_MSG_TYPE_GET(primary) == SOF_IPC4_MOD_SET_D0IX) + return true; + + return false; +} + +void hda_dsp_ipc4_schedule_d0i3_work(struct sof_intel_hda_dev *hdev, + struct snd_sof_ipc_msg *msg) +{ + struct sof_ipc4_msg *msg_data = msg->msg_data; + + /* Schedule a delayed work for d0i3 entry after sending non-pm ipc msg */ + if (hda_dsp_ipc4_pm_msg(msg_data->primary)) + return; + + mod_delayed_work(system_wq, &hdev->d0i3_work, + msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS)); +} + int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; @@ -88,6 +114,8 @@ int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, msg_data->primary | HDA_DSP_REG_HIPCI_BUSY); + hda_dsp_ipc4_schedule_d0i3_work(hdev, msg); + return 0; } diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index b3080b82ca25..45f9d4248f14 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -919,6 +919,8 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context); int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context); bool hda_ipc4_tx_is_busy(struct snd_sof_dev *sdev); +void hda_dsp_ipc4_schedule_d0i3_work(struct sof_intel_hda_dev *hdev, + struct snd_sof_ipc_msg *msg); int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); void hda_ipc4_dump(struct snd_sof_dev *sdev); extern struct sdw_intel_ops sdw_callback; diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 67b8ccbc2e94..307faad2ecf4 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -110,6 +110,8 @@ static int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *ms snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR, msg_data->primary | MTL_DSP_REG_HFIPCXIDR_BUSY); + hda_dsp_ipc4_schedule_d0i3_work(hdev, msg); + return 0; } diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index 8e936353c1c0..3de64ea2dc9a 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -1077,10 +1077,28 @@ static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev) return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); } +static int sof_ipc3_set_pm_gate(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_pm_gate pm_gate; + struct sof_ipc_reply reply; + + memset(&pm_gate, 0, sizeof(pm_gate)); + + /* configure pm_gate ipc message */ + pm_gate.hdr.size = sizeof(pm_gate); + pm_gate.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE; + pm_gate.flags = flags; + + /* send pm_gate ipc to dsp */ + return sof_ipc_tx_message_no_pm(sdev->ipc, &pm_gate, sizeof(pm_gate), + &reply, sizeof(reply)); +} + static const struct sof_ipc_pm_ops ipc3_pm_ops = { .ctx_save = sof_ipc3_ctx_save, .ctx_restore = sof_ipc3_ctx_restore, .set_core_state = sof_ipc3_set_core_state, + .set_pm_gate = sof_ipc3_set_pm_gate, }; const struct sof_ipc_ops ipc3_ops = { diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index f3c0c839d177..8ede4b952997 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -370,6 +370,17 @@ static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_ if (!msg_data) return -EINVAL; + if (!no_pm) { + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + }; + + /* ensure the DSP is in D0i0 before sending a new IPC */ + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) + return ret; + } + /* Serialise IPC TX */ mutex_lock(&ipc->tx_mutex); @@ -656,9 +667,22 @@ static int sof_ipc4_ctx_save(struct snd_sof_dev *sdev) return sof_ipc4_set_core_state(sdev, SOF_DSP_PRIMARY_CORE, false); } +static int sof_ipc4_set_pm_gate(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc4_msg msg = {{0}}; + + msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_SET_D0IX); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.extension = flags; + + return sof_ipc4_tx_msg(sdev, &msg, 0, NULL, 0, true); +} + static const struct sof_ipc_pm_ops ipc4_pm_ops = { .ctx_save = sof_ipc4_ctx_save, .set_core_state = sof_ipc4_set_core_state, + .set_pm_gate = sof_ipc4_set_pm_gate, }; static int sof_ipc4_init(struct snd_sof_dev *sdev) diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 14f7adb2dc12..5f919162a555 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -425,11 +425,13 @@ struct sof_ipc_fw_tracing_ops { * @ctx_save: Optional function pointer for context save * @ctx_restore: Optional function pointer for context restore * @set_core_state: Optional function pointer for turning on/off a DSP core + * @set_pm_gate: Optional function pointer for pm gate settings */ struct sof_ipc_pm_ops { int (*ctx_save)(struct snd_sof_dev *sdev); int (*ctx_restore)(struct snd_sof_dev *sdev); int (*set_core_state)(struct snd_sof_dev *sdev, int core_idx, bool on); + int (*set_pm_gate)(struct snd_sof_dev *sdev, u32 flags); }; /**