Commit 3bcca378 authored by Mark Brown's avatar Mark Brown

ASoC: Intel: avs: PCM power management

Merge series from Cezary Rojewski <cezary.rojewski@intel.com>:

Goal of the series is implementation of suspend/resume operations for a
PCM stream along with all the collaterals connected to the subject.

Start with splitting avs_dai_fe_hw_free() as ideally we would like to
reuse as much of existing code as possible but snd_pcm_lib_free_pages()
is not desired part of the function when speaking of suspend operation.

The actual implementation of suspend/resume() for component drivers
follows. For most scenarios, the PM flow is similar to standard
streaming one, except for the part where the position register are being
saved and the lack of PCM pages freeing. To reduce code duplication, all
avs_dai_suspend_XXX() and avs_dai_resume_XXX() functions reuse their
non-PM equivalents.
Order of operations is affected by the fact that path binding/unbinding
happens only in FE part of the stream.

Above essentially unlocks SX+streaming scenarios i.e.: power transitions
with an ongoing stream.

As some streams are allowed to run in low power state, support is
provided for S0iX state. The handlers check ACPI capabilities and the
number of active low-power paths before deciding between SX and S0iX
flows.

The last portion of the patchset is addition of power/clock gating
overrides. There is no single set of registers that ensures AudioDSP
firmware loads 100% of time on every single configuration. By having
them exposed, user can have the loading procedure behavior adjusted for
their configuration without having to recompile the kernel.
parents 5e01ff7d 758ba92f
...@@ -597,6 +597,7 @@ int snd_hdac_stream_get_spbmaxfifo(struct hdac_bus *bus, ...@@ -597,6 +597,7 @@ int snd_hdac_stream_get_spbmaxfifo(struct hdac_bus *bus,
struct hdac_stream *azx_dev); struct hdac_stream *azx_dev);
void snd_hdac_stream_drsm_enable(struct hdac_bus *bus, void snd_hdac_stream_drsm_enable(struct hdac_bus *bus,
bool enable, int index); bool enable, int index);
int snd_hdac_stream_wait_drsm(struct hdac_stream *azx_dev);
int snd_hdac_stream_set_dpibr(struct hdac_bus *bus, int snd_hdac_stream_set_dpibr(struct hdac_bus *bus,
struct hdac_stream *azx_dev, u32 value); struct hdac_stream *azx_dev, u32 value);
int snd_hdac_stream_set_lpib(struct hdac_stream *azx_dev, u32 value); int snd_hdac_stream_set_lpib(struct hdac_stream *azx_dev, u32 value);
......
...@@ -51,6 +51,11 @@ struct hdac_ext_stream { ...@@ -51,6 +51,11 @@ struct hdac_ext_stream {
void __iomem *pphc_addr; void __iomem *pphc_addr;
void __iomem *pplc_addr; void __iomem *pplc_addr;
u32 pphcllpl;
u32 pphcllpu;
u32 pphcldpl;
u32 pphcldpu;
bool decoupled:1; bool decoupled:1;
bool link_locked:1; bool link_locked:1;
bool link_prepared; bool link_prepared;
......
...@@ -821,6 +821,28 @@ void snd_hdac_stream_drsm_enable(struct hdac_bus *bus, ...@@ -821,6 +821,28 @@ void snd_hdac_stream_drsm_enable(struct hdac_bus *bus,
} }
EXPORT_SYMBOL_GPL(snd_hdac_stream_drsm_enable); EXPORT_SYMBOL_GPL(snd_hdac_stream_drsm_enable);
/*
* snd_hdac_stream_wait_drsm - wait for HW to clear RSM for a stream
* @azx_dev: HD-audio core stream to await RSM for
*
* Returns 0 on success and -ETIMEDOUT upon a timeout.
*/
int snd_hdac_stream_wait_drsm(struct hdac_stream *azx_dev)
{
struct hdac_bus *bus = azx_dev->bus;
u32 mask, reg;
int ret;
mask = 1 << azx_dev->index;
ret = read_poll_timeout(snd_hdac_reg_readl, reg, !(reg & mask), 250, 2000, false, bus,
bus->drsmcap + AZX_REG_DRSM_CTL);
if (ret)
dev_dbg(bus->dev, "polling RSM 0x%08x failed: %d\n", mask, ret);
return ret;
}
EXPORT_SYMBOL_GPL(snd_hdac_stream_wait_drsm);
/** /**
* snd_hdac_stream_set_dpibr - sets the dpibr value of a stream * snd_hdac_stream_set_dpibr - sets the dpibr value of a stream
* @bus: HD-audio core bus * @bus: HD-audio core bus
......
...@@ -24,6 +24,13 @@ struct avs_tplg_library; ...@@ -24,6 +24,13 @@ struct avs_tplg_library;
struct avs_soc_component; struct avs_soc_component;
struct avs_ipc_msg; struct avs_ipc_msg;
#ifdef CONFIG_ACPI
#define AVS_S0IX_SUPPORTED \
(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
#else
#define AVS_S0IX_SUPPORTED false
#endif
/* /*
* struct avs_dsp_ops - Platform-specific DSP operations * struct avs_dsp_ops - Platform-specific DSP operations
* *
...@@ -127,6 +134,7 @@ struct avs_dev { ...@@ -127,6 +134,7 @@ struct avs_dev {
struct list_head fw_list; struct list_head fw_list;
int *core_refs; /* reference count per core */ int *core_refs; /* reference count per core */
char **lib_names; char **lib_names;
int num_lp_paths;
struct completion fw_ready; struct completion fw_ready;
struct work_struct probe_work; struct work_struct probe_work;
......
...@@ -27,6 +27,14 @@ ...@@ -27,6 +27,14 @@
#include "avs.h" #include "avs.h"
#include "cldma.h" #include "cldma.h"
static u32 pgctl_mask = AZX_PGCTL_LSRMD_MASK;
module_param(pgctl_mask, uint, 0444);
MODULE_PARM_DESC(pgctl_mask, "PCI PGCTL policy override");
static u32 cgctl_mask = AZX_CGCTL_MISCBDCGE_MASK;
module_param(cgctl_mask, uint, 0444);
MODULE_PARM_DESC(cgctl_mask, "PCI CGCTL policy override");
static void static void
avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask, u32 value) avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask, u32 value)
{ {
...@@ -41,19 +49,16 @@ avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask, u32 value) ...@@ -41,19 +49,16 @@ avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask, u32 value)
void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable) void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable)
{ {
u32 value; u32 value = enable ? 0 : pgctl_mask;
value = enable ? 0 : AZX_PGCTL_LSRMD_MASK; avs_hda_update_config_dword(&adev->base.core, AZX_PCIREG_PGCTL, pgctl_mask, value);
avs_hda_update_config_dword(&adev->base.core, AZX_PCIREG_PGCTL,
AZX_PGCTL_LSRMD_MASK, value);
} }
static void avs_hdac_clock_gating_enable(struct hdac_bus *bus, bool enable) static void avs_hdac_clock_gating_enable(struct hdac_bus *bus, bool enable)
{ {
u32 value; u32 value = enable ? cgctl_mask : 0;
value = enable ? AZX_CGCTL_MISCBDCGE_MASK : 0; avs_hda_update_config_dword(bus, AZX_PCIREG_CGCTL, cgctl_mask, value);
avs_hda_update_config_dword(bus, AZX_PCIREG_CGCTL, AZX_CGCTL_MISCBDCGE_MASK, value);
} }
void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable) void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable)
...@@ -63,9 +68,8 @@ void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable) ...@@ -63,9 +68,8 @@ void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable)
void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable) void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable)
{ {
u32 value; u32 value = enable ? AZX_VS_EM2_L1SEN : 0;
value = enable ? AZX_VS_EM2_L1SEN : 0;
snd_hdac_chip_updatel(&adev->base.core, VS_EM2, AZX_VS_EM2_L1SEN, value); snd_hdac_chip_updatel(&adev->base.core, VS_EM2, AZX_VS_EM2_L1SEN, value);
} }
...@@ -534,12 +538,30 @@ static void avs_pci_remove(struct pci_dev *pci) ...@@ -534,12 +538,30 @@ static void avs_pci_remove(struct pci_dev *pci)
pm_runtime_get_noresume(&pci->dev); pm_runtime_get_noresume(&pci->dev);
} }
static int __maybe_unused avs_suspend_common(struct avs_dev *adev) static int avs_suspend_standby(struct avs_dev *adev)
{
struct hdac_bus *bus = &adev->base.core;
struct pci_dev *pci = adev->base.pci;
if (bus->cmd_dma_state)
snd_hdac_bus_stop_cmd_io(bus);
snd_hdac_ext_bus_link_power_down_all(bus);
enable_irq_wake(pci->irq);
pci_save_state(pci);
return 0;
}
static int __maybe_unused avs_suspend_common(struct avs_dev *adev, bool low_power)
{ {
struct hdac_bus *bus = &adev->base.core; struct hdac_bus *bus = &adev->base.core;
int ret; int ret;
flush_work(&adev->probe_work); flush_work(&adev->probe_work);
if (low_power && adev->num_lp_paths)
return avs_suspend_standby(adev);
snd_hdac_ext_bus_link_power_down_all(bus); snd_hdac_ext_bus_link_power_down_all(bus);
...@@ -577,11 +599,30 @@ static int __maybe_unused avs_suspend_common(struct avs_dev *adev) ...@@ -577,11 +599,30 @@ static int __maybe_unused avs_suspend_common(struct avs_dev *adev)
return 0; return 0;
} }
static int __maybe_unused avs_resume_common(struct avs_dev *adev, bool purge) static int avs_resume_standby(struct avs_dev *adev)
{
struct hdac_bus *bus = &adev->base.core;
struct pci_dev *pci = adev->base.pci;
pci_restore_state(pci);
disable_irq_wake(pci->irq);
snd_hdac_ext_bus_link_power_up_all(bus);
if (bus->cmd_dma_state)
snd_hdac_bus_init_cmd_io(bus);
return 0;
}
static int __maybe_unused avs_resume_common(struct avs_dev *adev, bool low_power, bool purge)
{ {
struct hdac_bus *bus = &adev->base.core; struct hdac_bus *bus = &adev->base.core;
int ret; int ret;
if (low_power && adev->num_lp_paths)
return avs_resume_standby(adev);
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true);
avs_hdac_bus_init_chip(bus, true); avs_hdac_bus_init_chip(bus, true);
...@@ -599,26 +640,50 @@ static int __maybe_unused avs_resume_common(struct avs_dev *adev, bool purge) ...@@ -599,26 +640,50 @@ static int __maybe_unused avs_resume_common(struct avs_dev *adev, bool purge)
static int __maybe_unused avs_suspend(struct device *dev) static int __maybe_unused avs_suspend(struct device *dev)
{ {
return avs_suspend_common(to_avs_dev(dev)); return avs_suspend_common(to_avs_dev(dev), true);
} }
static int __maybe_unused avs_resume(struct device *dev) static int __maybe_unused avs_resume(struct device *dev)
{ {
return avs_resume_common(to_avs_dev(dev), true); return avs_resume_common(to_avs_dev(dev), true, true);
} }
static int __maybe_unused avs_runtime_suspend(struct device *dev) static int __maybe_unused avs_runtime_suspend(struct device *dev)
{ {
return avs_suspend_common(to_avs_dev(dev)); return avs_suspend_common(to_avs_dev(dev), true);
} }
static int __maybe_unused avs_runtime_resume(struct device *dev) static int __maybe_unused avs_runtime_resume(struct device *dev)
{ {
return avs_resume_common(to_avs_dev(dev), true); return avs_resume_common(to_avs_dev(dev), true, false);
}
static int __maybe_unused avs_freeze(struct device *dev)
{
return avs_suspend_common(to_avs_dev(dev), false);
}
static int __maybe_unused avs_thaw(struct device *dev)
{
return avs_resume_common(to_avs_dev(dev), false, true);
}
static int __maybe_unused avs_poweroff(struct device *dev)
{
return avs_suspend_common(to_avs_dev(dev), false);
}
static int __maybe_unused avs_restore(struct device *dev)
{
return avs_resume_common(to_avs_dev(dev), false, true);
} }
static const struct dev_pm_ops avs_dev_pm = { static const struct dev_pm_ops avs_dev_pm = {
SET_SYSTEM_SLEEP_PM_OPS(avs_suspend, avs_resume) .suspend = avs_suspend,
.resume = avs_resume,
.freeze = avs_freeze,
.thaw = avs_thaw,
.poweroff = avs_poweroff,
.restore = avs_restore,
SET_RUNTIME_PM_OPS(avs_runtime_suspend, avs_runtime_resume, NULL) SET_RUNTIME_PM_OPS(avs_runtime_suspend, avs_runtime_resume, NULL)
}; };
......
...@@ -224,11 +224,19 @@ static int avs_cldma_load_module(struct avs_dev *adev, struct avs_module_entry * ...@@ -224,11 +224,19 @@ static int avs_cldma_load_module(struct avs_dev *adev, struct avs_module_entry *
if (ret < 0) if (ret < 0)
return ret; return ret;
avs_hda_power_gating_enable(adev, false);
avs_hda_clock_gating_enable(adev, false);
avs_hda_l1sen_enable(adev, false);
hda_cldma_set_data(cl, (void *)mod->data, mod->size); hda_cldma_set_data(cl, (void *)mod->data, mod->size);
hda_cldma_transfer(cl, msecs_to_jiffies(AVS_CLDMA_START_DELAY_MS)); hda_cldma_transfer(cl, msecs_to_jiffies(AVS_CLDMA_START_DELAY_MS));
ret = avs_ipc_load_modules(adev, &mentry->module_id, 1); ret = avs_ipc_load_modules(adev, &mentry->module_id, 1);
hda_cldma_stop(cl); hda_cldma_stop(cl);
avs_hda_l1sen_enable(adev, true);
avs_hda_clock_gating_enable(adev, true);
avs_hda_power_gating_enable(adev, true);
if (ret) { if (ret) {
dev_err(adev->dev, "load module %d failed: %d\n", mentry->module_id, ret); dev_err(adev->dev, "load module %d failed: %d\n", mentry->module_id, ret);
avs_release_last_firmware(adev); avs_release_last_firmware(adev);
...@@ -605,6 +613,7 @@ int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge) ...@@ -605,6 +613,7 @@ int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge)
for (i = 1; i < adev->fw_cfg.max_libs_count; i++) for (i = 1; i < adev->fw_cfg.max_libs_count; i++)
memset(adev->lib_names[i], 0, AVS_LIB_NAME_SIZE); memset(adev->lib_names[i], 0, AVS_LIB_NAME_SIZE);
avs_hda_power_gating_enable(adev, false);
avs_hda_clock_gating_enable(adev, false); avs_hda_clock_gating_enable(adev, false);
avs_hda_l1sen_enable(adev, false); avs_hda_l1sen_enable(adev, false);
...@@ -625,6 +634,7 @@ int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge) ...@@ -625,6 +634,7 @@ int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge)
reenable_gating: reenable_gating:
avs_hda_l1sen_enable(adev, true); avs_hda_l1sen_enable(adev, true);
avs_hda_clock_gating_enable(adev, true); avs_hda_clock_gating_enable(adev, true);
avs_hda_power_gating_enable(adev, true);
if (ret < 0) if (ret < 0)
return ret; return ret;
......
This diff is collapsed.
...@@ -1405,6 +1405,11 @@ static int avs_widget_load(struct snd_soc_component *comp, int index, ...@@ -1405,6 +1405,11 @@ static int avs_widget_load(struct snd_soc_component *comp, int index,
if (!le32_to_cpu(dw->priv.size)) if (!le32_to_cpu(dw->priv.size))
return 0; return 0;
if (w->ignore_suspend && !AVS_S0IX_SUPPORTED) {
dev_info_once(comp->dev, "Device does not support S0IX, check BIOS settings\n");
w->ignore_suspend = false;
}
tplg = acomp->tplg; tplg = acomp->tplg;
mach = dev_get_platdata(comp->card->dev); mach = dev_get_platdata(comp->card->dev);
...@@ -1442,6 +1447,11 @@ static int avs_dai_load(struct snd_soc_component *comp, int index, ...@@ -1442,6 +1447,11 @@ static int avs_dai_load(struct snd_soc_component *comp, int index,
static int avs_link_load(struct snd_soc_component *comp, int index, struct snd_soc_dai_link *link, static int avs_link_load(struct snd_soc_component *comp, int index, struct snd_soc_dai_link *link,
struct snd_soc_tplg_link_config *cfg) struct snd_soc_tplg_link_config *cfg)
{ {
if (link->ignore_suspend && !AVS_S0IX_SUPPORTED) {
dev_info_once(comp->dev, "Device does not support S0IX, check BIOS settings\n");
link->ignore_suspend = false;
}
if (!link->no_pcm) { if (!link->no_pcm) {
/* Stream control handled by IPCs. */ /* Stream control handled by IPCs. */
link->nonatomic = true; link->nonatomic = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment