summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sound/soc/sof/core.c4
-rw-r--r--sound/soc/sof/intel/hda-dsp.c223
-rw-r--r--sound/soc/sof/intel/hda.h10
-rw-r--r--sound/soc/sof/ops.h16
-rw-r--r--sound/soc/sof/pm.c92
-rw-r--r--sound/soc/sof/sof-priv.h18
6 files changed, 242 insertions, 121 deletions
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
index 34cefbaf2d2a..1d07450aff77 100644
--- a/sound/soc/sof/core.c
+++ b/sound/soc/sof/core.c
@@ -286,8 +286,8 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
/* initialize sof device */
sdev->dev = dev;
- /* initialize default D0 sub-state */
- sdev->d0_substate = SOF_DSP_D0I0;
+ /* initialize default DSP power state */
+ sdev->dsp_power_state.state = SOF_DSP_PM_D0;
sdev->pdata = plat_data;
sdev->first_boot = true;
diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c
index fddf2c48904f..8c00e128a7b0 100644
--- a/sound/soc/sof/intel/hda-dsp.c
+++ b/sound/soc/sof/intel/hda-dsp.c
@@ -338,13 +338,10 @@ static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags)
sizeof(pm_gate), &reply, sizeof(reply));
}
-int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
- enum sof_d0_substate d0_substate)
+static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value)
{
struct hdac_bus *bus = sof_to_bus(sdev);
- u32 flags;
int ret;
- u8 value;
/* Write to D0I3C after Command-In-Progress bit is cleared */
ret = hda_dsp_wait_d0i3c_done(sdev);
@@ -354,7 +351,6 @@ int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
}
/* Update D0I3C register */
- value = d0_substate == SOF_DSP_D0I3 ? SOF_HDA_VS_D0I3C_I3 : 0;
snd_hdac_chip_updateb(bus, VS_D0I3C, SOF_HDA_VS_D0I3C_I3, value);
/* Wait for cmd in progress to be cleared before exiting the function */
@@ -367,20 +363,160 @@ int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
dev_vdbg(bus->dev, "D0I3C updated, register = 0x%x\n",
snd_hdac_chip_readb(bus, VS_D0I3C));
- if (d0_substate == SOF_DSP_D0I0)
- flags = HDA_PM_PPG;/* prevent power gating in D0 */
- else
- flags = HDA_PM_NO_DMA_TRACE;/* disable DMA trace in D0I3*/
+ return 0;
+}
- /* sending pm_gate IPC */
- ret = hda_dsp_send_pm_gate_ipc(sdev, flags);
+static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ u32 flags = 0;
+ int ret;
+ u8 value = 0;
+
+ /*
+ * Sanity check for illegal state transitions
+ * The only allowed transitions are:
+ * 1. D3 -> D0I0
+ * 2. D0I0 -> D0I3
+ * 3. D0I3 -> D0I0
+ */
+ switch (sdev->dsp_power_state.state) {
+ case SOF_DSP_PM_D0:
+ /* Follow the sequence below for D0 substate transitions */
+ break;
+ case SOF_DSP_PM_D3:
+ /* Follow regular flow for D3 -> D0 transition */
+ return 0;
+ default:
+ dev_err(sdev->dev, "error: transition from %d to %d not allowed\n",
+ sdev->dsp_power_state.state, target_state->state);
+ return -EINVAL;
+ }
+
+ /* Set flags and register value for D0 target substate */
+ if (target_state->substate == SOF_HDA_DSP_PM_D0I3) {
+ value = SOF_HDA_VS_D0I3C_I3;
+
+ /* disable DMA trace in D0I3 */
+ flags = HDA_PM_NO_DMA_TRACE;
+ } else {
+ /* prevent power gating in D0I0 */
+ flags = HDA_PM_PPG;
+ }
+
+ /* update D0I3C register */
+ ret = hda_dsp_update_d0i3c_register(sdev, value);
if (ret < 0)
+ return ret;
+
+ /*
+ * Notify the DSP of the state change.
+ * If this IPC fails, revert the D0I3C register update in order
+ * to prevent partial state change.
+ */
+ ret = hda_dsp_send_pm_gate_ipc(sdev, flags);
+ if (ret < 0) {
dev_err(sdev->dev,
"error: PM_GATE ipc error %d\n", ret);
+ goto revert;
+ }
+
+ return ret;
+
+revert:
+ /* fallback to the previous register value */
+ value = value ? 0 : SOF_HDA_VS_D0I3C_I3;
+
+ /*
+ * This can fail but return the IPC error to signal that
+ * the state change failed.
+ */
+ hda_dsp_update_d0i3c_register(sdev, value);
return ret;
}
+/*
+ * All DSP power state transitions are initiated by the driver.
+ * If the requested state change fails, the error is simply returned.
+ * Further state transitions are attempted only when the set_power_save() op
+ * is called again either because of a new IPC sent to the DSP or
+ * during system suspend/resume.
+ */
+int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ int ret = 0;
+
+ /* Nothing to do if the DSP is already in the requested state */
+ if (target_state->state == sdev->dsp_power_state.state &&
+ target_state->substate == sdev->dsp_power_state.substate)
+ return 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;
+ }
+
+ sdev->dsp_power_state = *target_state;
+ dev_dbg(sdev->dev, "New DSP state %d substate %d\n",
+ target_state->state, target_state->substate);
+ return ret;
+}
+
+/*
+ * Audio DSP states may transform as below:-
+ *
+ * D0I3 compatible stream
+ * Runtime +---------------------+ opened only, timeout
+ * suspend | +--------------------+
+ * +------------+ D0(active) | |
+ * | | <---------------+ |
+ * | +--------> | | |
+ * | |Runtime +--^--+---------^--+--+ The last | |
+ * | |resume | | | | opened D0I3 | |
+ * | | | | | | compatible | |
+ * | | resume| | | | stream closed | |
+ * | | from | | D3 | | | |
+ * | | D3 | |suspend | | d0i3 | |
+ * | | | | | |suspend | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * +-v---+-----------+--v-------+ | | +------+----v----+
+ * | | | +-----------> |
+ * | D3 (suspended) | | | D0I3 +-----+
+ * | | +--------------+ | |
+ * | | resume from | | |
+ * +-------------------^--------+ d0i3 suspend +----------------+ |
+ * | |
+ * | D3 suspend |
+ * +------------------------------------------------+
+ *
+ * d0i3_suspend = s0_suspend && D0I3 stream opened,
+ * D3 suspend = !d0i3_suspend,
+ */
+
static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
@@ -480,8 +616,22 @@ int hda_dsp_resume(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct pci_dev *pci = to_pci_dev(sdev->dev);
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ .substate = SOF_HDA_DSP_PM_D0I0,
+ };
+ int ret;
+
+ /* resume from D0I3 */
+ if (sdev->dsp_power_state.state == SOF_DSP_PM_D0) {
+ /* Set DSP power state */
+ ret = hda_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;
+ }
- if (sdev->system_suspend_target == SOF_SUSPEND_S0IX) {
/* restore L1SEN bit */
if (hda->l1_support_changed)
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
@@ -495,13 +645,27 @@ int hda_dsp_resume(struct snd_sof_dev *sdev)
}
/* init hda controller. DSP cores will be powered up during fw boot */
- return hda_resume(sdev, false);
+ ret = hda_resume(sdev, false);
+ if (ret < 0)
+ return ret;
+
+ hda_dsp_set_power_state(sdev, &target_state);
+ return ret;
}
int hda_dsp_runtime_resume(struct snd_sof_dev *sdev)
{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+ int ret;
+
/* init hda controller. DSP cores will be powered up during fw boot */
- return hda_resume(sdev, true);
+ ret = hda_resume(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return hda_dsp_set_power_state(sdev, &target_state);
}
int hda_dsp_runtime_idle(struct snd_sof_dev *sdev)
@@ -519,18 +683,41 @@ int hda_dsp_runtime_idle(struct snd_sof_dev *sdev)
int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev)
{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D3,
+ };
+ int ret;
+
/* stop hda controller and power dsp off */
- return hda_suspend(sdev, true);
+ ret = hda_suspend(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return hda_dsp_set_power_state(sdev, &target_state);
}
-int hda_dsp_suspend(struct snd_sof_dev *sdev)
+int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_bus *bus = sof_to_bus(sdev);
struct pci_dev *pci = to_pci_dev(sdev->dev);
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = target_state,
+ .substate = target_state == SOF_DSP_PM_D0 ?
+ SOF_HDA_DSP_PM_D0I3 : 0,
+ };
int ret;
- if (sdev->system_suspend_target == SOF_SUSPEND_S0IX) {
+ if (target_state == SOF_DSP_PM_D0) {
+ /* Set DSP power state */
+ ret = hda_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 */
hda->l1_support_changed =
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
@@ -551,7 +738,7 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev)
return ret;
}
- return 0;
+ return hda_dsp_set_power_state(sdev, &target_dsp_state);
}
int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index 6191d9192fae..02c2a7eadb1b 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -392,6 +392,12 @@ struct sof_intel_dsp_bdl {
#define SOF_HDA_PLAYBACK 0
#define SOF_HDA_CAPTURE 1
+/* HDA DSP D0 substate */
+enum sof_hda_D0_substate {
+ SOF_HDA_DSP_PM_D0I0, /* default D0 substate */
+ SOF_HDA_DSP_PM_D0I3, /* low power D0 substate */
+};
+
/* represents DSP HDA controller frontend - i.e. host facing control */
struct sof_intel_hda_dev {
@@ -469,9 +475,9 @@ void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev);
void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev);
int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
- enum sof_d0_substate d0_substate);
+ const struct sof_dsp_power_state *target_state);
-int hda_dsp_suspend(struct snd_sof_dev *sdev);
+int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state);
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);
diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h
index e929a6e0058f..7f532bcc8e9d 100644
--- a/sound/soc/sof/ops.h
+++ b/sound/soc/sof/ops.h
@@ -146,10 +146,11 @@ static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev)
return 0;
}
-static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev)
+static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev,
+ u32 target_state)
{
if (sof_ops(sdev)->suspend)
- return sof_ops(sdev)->suspend(sdev);
+ return sof_ops(sdev)->suspend(sdev, target_state);
return 0;
}
@@ -193,14 +194,15 @@ static inline int snd_sof_dsp_set_clk(struct snd_sof_dev *sdev, u32 freq)
return 0;
}
-static inline int snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev,
- enum sof_d0_substate substate)
+static inline int
+snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
{
if (sof_ops(sdev)->set_power_state)
- return sof_ops(sdev)->set_power_state(sdev, substate);
+ return sof_ops(sdev)->set_power_state(sdev, target_state);
- /* D0 substate is not supported */
- return -ENOTSUPP;
+ /* D0 substate is not supported, do nothing here. */
+ return 0;
}
/* debug */
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c
index bec25cb6beec..c410822d9920 100644
--- a/sound/soc/sof/pm.c
+++ b/sound/soc/sof/pm.c
@@ -86,7 +86,7 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev)
static int sof_resume(struct device *dev, bool runtime_resume)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- enum sof_d0_substate old_d0_substate = sdev->d0_substate;
+ u32 old_state = sdev->dsp_power_state.state;
int ret;
/* do nothing if dsp resume callbacks are not set */
@@ -97,17 +97,6 @@ static int sof_resume(struct device *dev, bool runtime_resume)
if (sdev->first_boot)
return 0;
- /* resume from D0I3 */
- if (!runtime_resume && old_d0_substate == SOF_DSP_D0I3) {
- ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I0);
- if (ret < 0 && ret != -ENOTSUPP) {
- dev_err(sdev->dev,
- "error: failed to resume from D0I3 %d\n",
- ret);
- return ret;
- }
- }
-
/*
* if the runtime_resume flag is set, call the runtime_resume routine
* or else call the system resume routine
@@ -122,8 +111,8 @@ static int sof_resume(struct device *dev, bool runtime_resume)
return ret;
}
- /* Nothing further to do if resuming from D0I3 */
- if (!runtime_resume && old_d0_substate == SOF_DSP_D0I3)
+ /* Nothing further to do if resuming from a low-power D0 substate */
+ if (!runtime_resume && old_state == SOF_DSP_PM_D0)
return 0;
sdev->fw_state = SOF_FW_BOOT_PREPARE;
@@ -176,15 +165,13 @@ static int sof_resume(struct device *dev, bool runtime_resume)
"error: ctx_restore ipc error during resume %d\n",
ret);
- /* initialize default D0 sub-state */
- sdev->d0_substate = SOF_DSP_D0I0;
-
return ret;
}
static int sof_suspend(struct device *dev, bool runtime_suspend)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ u32 target_state = 0;
int ret;
/* do nothing if dsp suspend callback is not set */
@@ -205,18 +192,11 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
}
}
- if (snd_sof_dsp_power_target(sdev) == SOF_DSP_PM_D0) {
- /* suspend to D0i3 */
- ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I3);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to enter D0I3, %d\n",
- ret);
- return ret;
- }
+ target_state = snd_sof_dsp_power_target(sdev);
- /* Skip to platform-specific suspend if DSP is entering D0I3 */
+ /* Skip to platform-specific suspend if DSP is entering D0 */
+ if (target_state == SOF_DSP_PM_D0)
goto suspend;
- }
/* release trace */
snd_sof_release_trace(sdev);
@@ -254,14 +234,14 @@ suspend:
if (runtime_suspend)
ret = snd_sof_dsp_runtime_suspend(sdev);
else
- ret = snd_sof_dsp_suspend(sdev);
+ ret = snd_sof_dsp_suspend(sdev, target_state);
if (ret < 0)
dev_err(sdev->dev,
"error: failed to power down DSP during suspend %d\n",
ret);
- /* Do not reset FW state if DSP is in D0I3 */
- if (sdev->d0_substate == SOF_DSP_D0I3)
+ /* Do not reset FW state if DSP is in D0 */
+ if (target_state == SOF_DSP_PM_D0)
return ret;
/* reset FW state */
@@ -290,58 +270,6 @@ int snd_sof_runtime_resume(struct device *dev)
}
EXPORT_SYMBOL(snd_sof_runtime_resume);
-int snd_sof_set_d0_substate(struct snd_sof_dev *sdev,
- enum sof_d0_substate d0_substate)
-{
- int ret;
-
- if (sdev->d0_substate == d0_substate)
- return 0;
-
- /* do platform specific set_state */
- ret = snd_sof_dsp_set_power_state(sdev, d0_substate);
- if (ret < 0)
- return ret;
-
- /* update dsp D0 sub-state */
- sdev->d0_substate = d0_substate;
-
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_set_d0_substate);
-
-/*
- * Audio DSP states may transform as below:-
- *
- * D0I3 compatible stream
- * Runtime +---------------------+ opened only, timeout
- * suspend | +--------------------+
- * +------------+ D0(active) | |
- * | | <---------------+ |
- * | +--------> | | |
- * | |Runtime +--^--+---------^--+--+ The last | |
- * | |resume | | | | opened D0I3 | |
- * | | | | | | compatible | |
- * | | resume| | | | stream closed | |
- * | | from | | D3 | | | |
- * | | D3 | |suspend | | d0i3 | |
- * | | | | | |suspend | |
- * | | | | | | | |
- * | | | | | | | |
- * +-v---+-----------+--v-------+ | | +------+----v----+
- * | | | +-----------> |
- * | D3 (suspended) | | | D0I3 +-----+
- * | | +--------------+ | |
- * | | resume from | | |
- * +-------------------^--------+ d0i3 suspend +----------------+ |
- * | |
- * | D3 suspend |
- * +------------------------------------------------+
- *
- * d0i3_suspend = s0_suspend && D0I3 stream opened,
- * D3 suspend = !d0i3_suspend,
- */
-
int snd_sof_resume(struct device *dev)
{
return sof_resume(dev, false);
diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h
index a7c6109acd98..ef33aaadbc31 100644
--- a/sound/soc/sof/sof-priv.h
+++ b/sound/soc/sof/sof-priv.h
@@ -64,10 +64,9 @@ enum sof_dsp_power_states {
SOF_DSP_PM_D3_COLD,
};
-/* DSP D0ix sub-state */
-enum sof_d0_substate {
- SOF_DSP_D0I0 = 0, /* DSP default D0 substate */
- SOF_DSP_D0I3, /* DSP D0i3(low power) substate*/
+struct sof_dsp_power_state {
+ u32 state;
+ u32 substate; /* platform-specific */
};
/* System suspend target state */
@@ -186,14 +185,15 @@ struct snd_sof_dsp_ops {
int (*post_fw_run)(struct snd_sof_dev *sof_dev); /* optional */
/* DSP PM */
- int (*suspend)(struct snd_sof_dev *sof_dev); /* optional */
+ int (*suspend)(struct snd_sof_dev *sof_dev,
+ u32 target_state); /* optional */
int (*resume)(struct snd_sof_dev *sof_dev); /* optional */
int (*runtime_suspend)(struct snd_sof_dev *sof_dev); /* optional */
int (*runtime_resume)(struct snd_sof_dev *sof_dev); /* optional */
int (*runtime_idle)(struct snd_sof_dev *sof_dev); /* optional */
int (*set_hw_params_upon_resume)(struct snd_sof_dev *sdev); /* optional */
int (*set_power_state)(struct snd_sof_dev *sdev,
- enum sof_d0_substate d0_substate); /* optional */
+ const struct sof_dsp_power_state *target_state); /* optional */
/* DSP clocking */
int (*set_clk)(struct snd_sof_dev *sof_dev, u32 freq); /* optional */
@@ -340,8 +340,8 @@ struct snd_sof_dev {
*/
struct snd_soc_component_driver plat_drv;
- /* power states related */
- enum sof_d0_substate d0_substate;
+ /* current DSP power state */
+ struct sof_dsp_power_state dsp_power_state;
/* Intended power target of system suspend */
enum sof_system_suspend_state system_suspend_target;
@@ -435,8 +435,6 @@ int snd_sof_resume(struct device *dev);
int snd_sof_suspend(struct device *dev);
int snd_sof_prepare(struct device *dev);
void snd_sof_complete(struct device *dev);
-int snd_sof_set_d0_substate(struct snd_sof_dev *sdev,
- enum sof_d0_substate d0_substate);
void snd_sof_new_platform_drv(struct snd_sof_dev *sdev);