// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) // // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // // Copyright(c) 2018 Intel Corporation // // Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> // Ranjani Sridharan <ranjani.sridharan@linux.intel.com> // Rander Wang <rander.wang@intel.com> // Keyon Jie <yang.jie@linux.intel.com> //
staticbool hda_enable_trace_D0I3_S0; #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG)
module_param_named(enable_trace_D0I3_S0, hda_enable_trace_D0I3_S0, bool, 0444);
MODULE_PARM_DESC(enable_trace_D0I3_S0, "SOF HDA enable trace when the DSP is in D0I3 in S0"); #endif
/* restrict core_mask to host managed cores mask */
core_mask &= chip->host_managed_cores_mask; /* return if core_mask is not valid */ if (!core_mask) return0;
/* poll with timeout to check if operation successful */
cpa = HDA_DSP_ADSPCS_CPA_MASK(core_mask);
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
(adspcs & cpa) == cpa,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US); if (ret < 0) {
dev_err(sdev->dev, "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n",
__func__); return ret;
}
/* did core power up ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS); if ((adspcs & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) !=
HDA_DSP_ADSPCS_CPA_MASK(core_mask)) {
dev_err(sdev->dev, "error: power up core failed core_mask %xadspcs 0x%x\n",
core_mask, adspcs);
ret = -EIO;
}
/* return if core_mask is not valid or cores are already enabled */ if (!core_mask || hda_dsp_core_is_enabled(sdev, core_mask)) return0;
/* power up */
ret = hda_dsp_core_power_up(sdev, core_mask); if (ret < 0) {
dev_err(sdev->dev, "error: dsp core power up failed: core_mask %x\n",
core_mask); return ret;
}
/* return if core_mask is not valid */ if (!core_mask) return0;
/* place core in reset prior to power down */
ret = hda_dsp_core_stall_reset(sdev, core_mask); if (ret < 0) {
dev_err(sdev->dev, "error: dsp core reset failed: core_mask %x\n",
core_mask); return ret;
}
/* power down core */
ret = hda_dsp_core_power_down(sdev, core_mask); if (ret < 0) {
dev_err(sdev->dev, "error: dsp core power down fail mask %x: %d\n",
core_mask, ret); return ret;
}
/* make sure we are in OFF state */ if (hda_dsp_core_is_enabled(sdev, core_mask)) {
dev_err(sdev->dev, "error: dsp core disable fail mask %x: %d\n",
core_mask, ret);
ret = -EIO;
}
/* Write to D0I3C after Command-In-Progress bit is cleared */
ret = hda_dsp_wait_d0i3c_done(sdev); if (ret < 0) {
dev_err(sdev->dev, "CIP timeout before D0I3C update!\n"); return ret;
}
/* Wait for cmd in progress to be cleared before exiting the function */
ret = hda_dsp_wait_d0i3c_done(sdev); if (ret < 0) {
dev_err(sdev->dev, "CIP timeout after D0I3C update!\n"); return ret;
}
reg = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, chip->d0i3_offset); /* Confirm d0i3 state changed with paranoia check */ if ((reg ^ value) & SOF_HDA_VS_D0I3C_I3) {
dev_err(sdev->dev, "failed to update D0I3C!\n"); return -EIO;
}
/* helper to log DSP state */ staticvoid hda_dsp_state_log(struct snd_sof_dev *sdev)
{ switch (sdev->dsp_power_state.state) { case SOF_DSP_PM_D0: switch (sdev->dsp_power_state.substate) { case SOF_HDA_DSP_PM_D0I0:
dev_dbg(sdev->dev, "Current DSP power state: D0I0\n"); break; case SOF_HDA_DSP_PM_D0I3:
dev_dbg(sdev->dev, "Current DSP power state: D0I3\n"); break; default:
dev_dbg(sdev->dev, "Unknown DSP D0 substate: %d\n",
sdev->dsp_power_state.substate); break;
} break; case SOF_DSP_PM_D1:
dev_dbg(sdev->dev, "Current DSP power state: D1\n"); break; case SOF_DSP_PM_D2:
dev_dbg(sdev->dev, "Current DSP power state: D2\n"); break; case SOF_DSP_PM_D3:
dev_dbg(sdev->dev, "Current DSP power state: D3\n"); break; default:
dev_dbg(sdev->dev, "Unknown DSP power state: %d\n",
sdev->dsp_power_state.state); break;
}
}
/* *AllDSPpowerstatetransitionsareinitiatedbythedriver. *Iftherequestedstatechangefails,theerrorissimplyreturned. *Furtherstatetransitionsareattemptedonlywhentheset_power_save()op *iscalledagaineitherbecauseofanewIPCsenttotheDSPor *duringsystemsuspend/resume.
*/ staticint hda_dsp_set_power_state(struct snd_sof_dev *sdev, conststruct sof_dsp_power_state *target_state)
{ int ret = 0;
switch (target_state->state) { case SOF_DSP_PM_D0:
ret = hda_dsp_set_D0_state(sdev, target_state); break; case SOF_DSP_PM_D3: /* The only allowed transition is: D0I0 -> D3 */ if (sdev->dsp_power_state.state == SOF_DSP_PM_D0 &&
sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0) break;
dev_err(sdev->dev, "error: transition from %d to %d not allowed\n",
sdev->dsp_power_state.state, target_state->state); return -EINVAL; default:
dev_err(sdev->dev, "error: target state unsupported %d\n",
target_state->state); return -EINVAL;
} if (ret < 0) {
dev_err(sdev->dev, "failed to set requested target DSP state %d substate %d\n",
target_state->state, target_state->substate); return ret;
}
int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev, conststruct sof_dsp_power_state *target_state)
{ /* Return without doing anything if the DSP is already in the target state */ if (target_state->state == sdev->dsp_power_state.state &&
target_state->substate == sdev->dsp_power_state.substate) return0;
/* reset and start hda controller */
ret = hda_dsp_ctrl_init_chip(sdev); if (ret < 0) {
dev_err(sdev->dev, "error: failed to start controller after resume\n"); goto cleanup;
}
/* check jack status */ if (runtime_resume) {
hda_codec_jack_wake_enable(sdev, false); if (sdev->system_suspend_target == SOF_SUSPEND_NONE)
hda_codec_jack_check(sdev);
}
/* resume from D0I3 */ if (sdev->dsp_power_state.state == SOF_DSP_PM_D0) {
ret = hda_bus_ml_resume(bus); if (ret < 0) {
dev_err(sdev->dev, "error %d in %s: failed to power up links",
ret, __func__); return ret;
}
/* set up CORB/RIRB buffers if was on before suspend */
hda_codec_resume_cmd_io(sdev);
/* Set DSP power state */
ret = snd_sof_dsp_set_power_state(sdev, &target_state); if (ret < 0) {
dev_err(sdev->dev, "error: setting dsp state %d substate %d\n",
target_state.state, target_state.substate); return ret;
}
/* restore L1SEN bit */ if (hda->l1_disabled)
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, 0);
/* restore and disable the system wakeup */
pci_restore_state(pci);
disable_irq_wake(pci->irq); return0;
}
/* init hda controller. DSP cores will be powered up during fw boot */
ret = hda_resume(sdev, false); if (ret < 0) return ret;
if (!sdev->dspless_mode_selected) { /* cancel any attempt for DSP D0I3 */
cancel_delayed_work_sync(&hda->d0i3_work);
/* Cancel the microphone privacy work if mic privacy is active */ if (hda->mic_privacy.active)
cancel_work_sync(&hda->mic_privacy.work);
}
if (target_state == SOF_DSP_PM_D0) { /* Set DSP power state */
ret = snd_sof_dsp_set_power_state(sdev, &target_dsp_state); if (ret < 0) {
dev_err(sdev->dev, "error: setting dsp state %d substate %d\n",
target_dsp_state.state,
target_dsp_state.substate); return ret;
}
/* enable L1SEN to make sure the system can enter S0Ix */ if (hda->l1_disabled)
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN);
/* stop the CORB/RIRB DMA if it is On */
hda_codec_suspend_cmd_io(sdev);
/* no link can be powered in s0ix state */
ret = hda_bus_ml_suspend(bus); if (ret < 0) {
dev_err(sdev->dev, "error %d in %s: failed to power down links",
ret, __func__); return ret;
}
/* enable the system waking up via IPC IRQ */
enable_irq_wake(pci->irq);
pci_save_state(pci); return0;
}
/* stop hda controller and power dsp off */
ret = hda_suspend(sdev, false); if (ret < 0) {
dev_err(bus->dev, "error: suspending dsp\n"); return ret;
}
/* *TakecontrolleroutofresettoflushDMA *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)
{ unsignedint 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);
}
int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
{ int ret;
/* make sure all DAI resources are freed */
ret = hda_dsp_dais_suspend(sdev); if (ret < 0)
dev_warn(sdev->dev, "%s: failure in hda_dsp_dais_suspend\n", __func__);
/* DSP can enter D0I3 iff only D0I3-compatible streams are active */ if (!snd_sof_dsp_only_d0i3_compatible_stream_active(sdev)) /* remain in D0I0 */ return;
/* This can fail but error cannot be propagated */
ret = snd_sof_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);
}
EXPORT_SYMBOL_NS(hda_dsp_d0i3_work, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_core_get(struct snd_sof_dev *sdev, int core)
{ conststruct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; int ret, ret1;
/* power up core */
ret = hda_dsp_enable_core(sdev, BIT(core)); if (ret < 0) {
dev_err(sdev->dev, "failed to power up core %d with err: %d\n",
core, ret); return ret;
}
/* No need to send IPC for primary core or if FW boot is not complete */ if (sdev->fw_state != SOF_FW_BOOT_COMPLETE || core == SOF_DSP_PRIMARY_CORE) return0;
/* No need to continue the set_core_state ops is not available */ if (!pm_ops->set_core_state) return0;
/* Now notify DSP for secondary cores */
ret = pm_ops->set_core_state(sdev, core, true); if (ret < 0) {
dev_err(sdev->dev, "failed to enable secondary core '%d' failed with %d\n",
core, ret); goto power_down;
}
return ret;
power_down: /* power down core if it is host managed and return the original error if this fails too */
ret1 = hda_dsp_core_reset_power_down(sdev, BIT(core)); if (ret1 < 0)
dev_err(sdev->dev, "failed to power down core: %d with err: %d\n", core, ret1);
/* first read registers */
sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
/* note: variable AR register array is not read */
/* then get panic info */ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
xoops->arch_hdr.totalsize); return;
}
offset += xoops->arch_hdr.totalsize;
sof_block_read(sdev, sdev->mmio_bar, offset,
panic_info, sizeof(*panic_info));
/* then get the stack */
offset += sizeof(*panic_info);
sof_block_read(sdev, sdev->mmio_bar, offset, stack,
stack_words * sizeof(u32));
}
/* dump the first 8 dwords representing the extended ROM status */ void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, constchar *level,
u32 flags)
{ conststruct sof_intel_dsp_desc *chip; char msg[128]; int len = 0;
u32 value; int i;
chip = get_chip_info(sdev->pdata); for (i = 0; i < HDA_EXT_ROM_STATUS_SIZE; i++) {
value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + i * 0x4);
len += scnprintf(msg + len, sizeof(msg) - len, " 0x%x", value);
}
dev_printk(level, sdev->dev, "extended rom status: %s", msg);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.