diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index edd5ea3e505fa8..2b275fb190cc00 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -351,14 +351,6 @@ static int sof_probe(struct platform_device *pdev) dev_dbg(sdev->dev, "created machine %s\n", dev_name(&plat_data->pdev_mach->dev)); - /* autosuspend sof device */ - pm_runtime_mark_last_busy(sdev->dev); - pm_runtime_put_autosuspend(sdev->dev); - - /* autosuspend pci/acpi/spi device */ - pm_runtime_mark_last_busy(plat_data->dev); - pm_runtime_put_autosuspend(plat_data->dev); - return 0; comp_err: diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 82edc77eeac1f7..19e98726060548 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -89,6 +89,10 @@ struct snd_sof_dsp_ops sof_apl_ops = { /* firmware run */ .run = hda_dsp_cl_boot_firmware, + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + /* trace callback */ .trace_init = hda_dsp_trace_init, .trace_release = hda_dsp_trace_release, diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 0fb531b08ea3af..e6f53afb1638c7 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -233,6 +233,10 @@ struct snd_sof_dsp_ops sof_cnl_ops = { /* firmware loading */ .load_firmware = hda_dsp_cl_load_fw, + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + /* firmware run */ .run = hda_dsp_cl_boot_firmware, diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c index 867bc3c4c7d3e9..3414e50bf4fb7e 100644 --- a/sound/soc/sof/intel/hda-ctrl.c +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "../sof-priv.h" #include "../ops.h" @@ -38,49 +39,31 @@ * HDA Operations. */ -int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev) +int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset) { unsigned long timeout; u32 gctl = 0; + u32 val; - /* reset the HDA controller */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL, - SOF_HDA_GCTL_RESET, 0); - - /* wait for reset */ - timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT); - while (time_before(jiffies, timeout)) { - usleep_range(500, 1000); - gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL); - if ((gctl & SOF_HDA_GCTL_RESET) == 0) - goto clear; - } - - /* reset failed */ - dev_err(sdev->dev, "error: failed to reset HDA controller gctl 0x%x\n", - gctl); - return -EIO; + /* 0 to enter reset and 1 to exit reset */ + val = reset ? 0 : SOF_HDA_GCTL_RESET; -clear: - /* wait for codec */ - usleep_range(500, 1000); - - /* now take controller out of reset */ + /* enter/exit HDA controller reset */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL, - SOF_HDA_GCTL_RESET, SOF_HDA_GCTL_RESET); + SOF_HDA_GCTL_RESET, val); - /* wait for controller to be ready */ + /* wait to enter/exit reset */ timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT); while (time_before(jiffies, timeout)) { gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL); - if ((gctl & SOF_HDA_GCTL_RESET) == 1) + if ((gctl & SOF_HDA_GCTL_RESET) == val) return 0; usleep_range(500, 1000); } - /* reset failed */ - dev_err(sdev->dev, "error: failed to ready HDA controller gctl 0x%x\n", - gctl); + /* enter/exit reset failed */ + dev_err(sdev->dev, "error: failed to %s HDA controller gctl 0x%x\n", + reset ? "reset" : "ready", gctl); return -EIO; } @@ -148,6 +131,33 @@ void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable) snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val); } +/* + * enable/disable audio dsp clock gating and power gating bits. + * This allows the HW to opportunistically power and clock gate + * the audio dsp when it is idle + */ +int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + u32 val; + + /* enable/disable audio dsp clock gating */ + val = enable ? PCI_CGCTL_ADSPDCGE : 0; + snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* enable/disable L1 support */ + val = enable ? SOF_HDA_VS_EM2_L1SEN : 0; + snd_hdac_chip_updatel(bus, VS_EM2, SOF_HDA_VS_EM2_L1SEN, val); +#endif + + /* enable/disable audio dsp power gating */ + val = enable ? 0 : PCI_PGCTL_ADSPPGD; + snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val); + + return 0; +} + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) /* * While performing reset, controller may not come back properly and causing diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index aeaa1af787dbae..cd5ad87375c15d 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -298,8 +298,16 @@ static int hda_suspend(struct snd_sof_dev *sdev, int state) #endif /* disable LP retention mode */ - snd_sof_pci_update_bits(sdev, PCI_TCSEL, - PCI_CGCTL_LSRMD_MASK, PCI_CGCTL_LSRMD_MASK); + snd_sof_pci_update_bits(sdev, PCI_PGCTL, + PCI_PGCTL_LSRMD_MASK, PCI_PGCTL_LSRMD_MASK); + + /* reset controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to reset controller during suspend\n"); + return ret; + } return 0; } @@ -339,6 +347,22 @@ static int hda_resume(struct snd_sof_dev *sdev) snd_hdac_ext_bus_ppcap_int_enable(bus, true); #endif + /* reset controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to reset controller during resume\n"); + return ret; + } + + /* take controller out of reset */ + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to ready controller during resume\n"); + return ret; + } + /* power up the DSP */ ret = hda_dsp_core_power_up(sdev, chip->cores_mask); if (ret < 0) { diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 76ac08d8d0883e..a02fe3873b3ece 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -372,3 +372,17 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) dev_err(sdev->dev, "error: load fw failed err: %d\n", ret); return ret; } + +/* pre fw run operations */ +int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); +} + +/* post fw run operations */ +int hda_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + /* re-enable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, true); +} diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 89fbb3321b55ec..a31f9084446aac 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -617,12 +617,19 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) goto free_ipc_irq; /* reset HDA controller */ - ret = hda_dsp_ctrl_link_reset(sdev); + ret = hda_dsp_ctrl_link_reset(sdev, true); if (ret < 0) { dev_err(&pci->dev, "error: failed to reset HDA controller\n"); goto free_ipc_irq; } + /* exit HDA controller reset */ + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) { + dev_err(&pci->dev, "error: failed to exit HDA controller reset\n"); + goto free_ipc_irq; + } + /* clear stream status */ list_for_each_entry(stream, &bus->stream_list, list) { sd_offset = SOF_STREAM_SD_OFFSET(stream); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index c9c3e52508f00f..538ddbdac51f13 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -15,11 +15,16 @@ /* PCI registers */ #define PCI_TCSEL 0x44 +#define PCI_PGCTL PCI_TCSEL #define PCI_CGCTL 0x48 +/* PCI_PGCTL bits */ +#define PCI_PGCTL_ADSPPGD BIT(2) +#define PCI_PGCTL_LSRMD_MASK BIT(4) + /* PCI_CGCTL bits */ #define PCI_CGCTL_MISCBDCGE_MASK BIT(6) -#define PCI_CGCTL_LSRMD_MASK BIT(4) +#define PCI_CGCTL_ADSPDCGE BIT(1) /* Legacy HDA registers and bits used - widths are variable */ #define SOF_HDA_GCAP 0x0 @@ -32,6 +37,7 @@ #define SOF_HDA_WAKESTS 0x0E #define SOF_HDA_WAKESTS_INT_MASK ((1 << 8) - 1) #define SOF_HDA_RIRBSTS 0x5d +#define SOF_HDA_VS_EM2_L1SEN BIT(13) /* SOF_HDA_GCTL register bist */ #define SOF_HDA_GCTL_RESET BIT(0) @@ -484,12 +490,17 @@ int hda_dsp_cl_load_fw(struct snd_sof_dev *sdev, bool first_boot); int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev); int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev); +/* pre and post fw run ops */ +int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev); +int hda_dsp_post_fw_run(struct snd_sof_dev *sdev); + /* * HDA Controller Operations. */ int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev); -int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev); +int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset); void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable); +int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable); int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset); /* diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c index 43b0bc169438fe..b5cb56461e1f4c 100644 --- a/sound/soc/sof/intel/skl.c +++ b/sound/soc/sof/intel/skl.c @@ -85,6 +85,10 @@ struct snd_sof_dsp_ops sof_skl_ops = { /* firmware loading */ .load_firmware = hda_dsp_cl_load_fw, + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + /* firmware run */ .run = hda_dsp_cl_boot_firmware_skl, diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 6828d9e9b1a0d1..3bbbd3c0e0398b 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -296,6 +296,13 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) } } + /* perform pre fw run operations */ + ret = snd_sof_dsp_pre_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed pre fw run op\n"); + return ret; + } + dev_dbg(sdev->dev, "booting DSP firmware\n"); /* boot the firmware on the DSP */ @@ -317,6 +324,13 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) dev_info(sdev->dev, "firmware boot complete\n"); + /* perform post fw run operations */ + ret = snd_sof_dsp_post_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed post fw run op\n"); + return ret; + } + return 0; } EXPORT_SYMBOL(snd_sof_run_firmware); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 5bd6e3921b5510..78bca5ad419f2d 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -61,6 +61,23 @@ static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev) return 0; } +/* pre/post fw load */ +static inline int snd_sof_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + if (sdev->ops->pre_fw_run) + return sdev->ops->pre_fw_run(sdev); + else + return 0; +} + +static inline int snd_sof_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + if (sdev->ops->post_fw_run) + return sdev->ops->post_fw_run(sdev); + else + return 0; +} + /* power management */ static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev) { diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index f2e256d129bf50..f873c9bffac6b1 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -712,7 +712,9 @@ static int sof_pcm_probe(struct snd_soc_component *component) SND_SOF_SUSPEND_DELAY); pm_runtime_use_autosuspend(component->dev); pm_runtime_enable(component->dev); - err = pm_runtime_idle(component->dev); + + pm_runtime_mark_last_busy(component->dev); + err = pm_runtime_put_autosuspend(component->dev); if (err < 0) dev_err(sdev->dev, "error: failed to enter PM idle %d\n", err); diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index 98f041247b3922..37eaa06047dcb9 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -259,8 +259,16 @@ static int sof_pci_probe(struct pci_dev *pci, /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY); pm_runtime_use_autosuspend(dev); + + /* + * runtime pm for pci device is "forbidden" by default. + * so call pm_runtime_allow() to enable it. + */ pm_runtime_allow(dev); + /* follow recommendation in pci-driver.c to decrement usage counter */ + pm_runtime_put_noidle(dev); + return ret; release_regions: @@ -291,6 +299,9 @@ static void sof_pci_remove(struct pci_dev *pci) /* release firmware */ release_firmware(sof_pdata->fw); + /* follow recommendation in pci-driver.c to increment usage counter */ + pm_runtime_get_noresume(&pci->dev); + /* release pci regions and disable device */ pci_release_regions(pci); pci_disable_device(pci); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 318762d1349fea..a4e6624d317088 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -78,6 +78,10 @@ struct snd_sof_dsp_ops { int (*stall)(struct snd_sof_dev *sof_dev); int (*reset)(struct snd_sof_dev *sof_dev); + /* pre/post firmware run */ + int (*pre_fw_run)(struct snd_sof_dev *sof_dev); + int (*post_fw_run)(struct snd_sof_dev *sof_dev); + /* DSP PM */ int (*suspend)(struct snd_sof_dev *sof_dev, int state); int (*resume)(struct snd_sof_dev *sof_dev);