@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <sound/hda_codec.h>
#include <sound/soc.h>
+#include <linux/pm_runtime.h>
#include "hda_local.h"
#include "hda_auto_parser.h"
#include "hda_jack.h"
@@ -425,6 +426,75 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi
rx_slot);
}
+static int cs35l41_runtime_suspend(struct device *dev)
+{
+ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+
+ dev_dbg(cs35l41->dev, "Suspend\n");
+
+ if (!cs35l41->firmware_running)
+ return 0;
+
+ if (cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type) < 0)
+ return 0;
+
+ regcache_cache_only(cs35l41->regmap, true);
+ regcache_mark_dirty(cs35l41->regmap);
+
+ return 0;
+}
+
+static int cs35l41_runtime_resume(struct device *dev)
+{
+ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(cs35l41->dev, "Resume.\n");
+
+ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
+ dev_dbg(cs35l41->dev, "System does not support Resume\n");
+ return 0;
+ }
+
+ if (!cs35l41->firmware_running)
+ return 0;
+
+ regcache_cache_only(cs35l41->regmap, false);
+
+ ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
+ if (ret) {
+ regcache_cache_only(cs35l41->regmap, true);
+ return ret;
+ }
+
+ /* Test key needs to be unlocked to allow the OTP settings to re-apply */
+ cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap);
+ ret = regcache_sync(cs35l41->regmap);
+ cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap);
+ if (ret) {
+ dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret);
+ return ret;
+ }
+
+ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+ cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg);
+
+ return 0;
+}
+
+static int cs35l41_hda_suspend_hook(struct device *dev)
+{
+ dev_dbg(dev, "Request Suspend\n");
+ pm_runtime_mark_last_busy(dev);
+ return pm_runtime_put_autosuspend(dev);
+}
+
+static int cs35l41_hda_resume_hook(struct device *dev)
+{
+ dev_dbg(dev, "Request Resume\n");
+ return pm_runtime_get_sync(dev);
+}
+
static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
{
int halo_sts;
@@ -482,17 +552,25 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
if (comps->dev)
return -EBUSY;
+ pm_runtime_get_sync(dev);
+
comps->dev = dev;
if (!cs35l41->acpi_subsystem_id)
cs35l41->acpi_subsystem_id = devm_kasprintf(dev, GFP_KERNEL,
"%.8x", comps->subsystem_id);
cs35l41->codec = comps->codec;
strscpy(comps->name, dev_name(dev), sizeof(comps->name));
- comps->playback_hook = cs35l41_hda_playback_hook;
if (cs35l41_smart_amp(cs35l41) < 0)
dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
+ comps->playback_hook = cs35l41_hda_playback_hook;
+ comps->suspend_hook = cs35l41_hda_suspend_hook;
+ comps->resume_hook = cs35l41_hda_resume_hook;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
return 0;
}
@@ -588,7 +666,7 @@ static const struct regmap_irq cs35l41_reg_irqs[] = {
CS35L41_REG_IRQ(IRQ1_STATUS1, AMP_SHORT_ERR),
};
-static const struct regmap_irq_chip cs35l41_regmap_irq_chip = {
+static struct regmap_irq_chip cs35l41_regmap_irq_chip = {
.name = "cs35l41 IRQ1 Controller",
.status_base = CS35L41_IRQ1_STATUS1,
.mask_base = CS35L41_IRQ1_MASK1,
@@ -596,6 +674,7 @@ static const struct regmap_irq_chip cs35l41_regmap_irq_chip = {
.num_regs = 4,
.irqs = cs35l41_reg_irqs,
.num_irqs = ARRAY_SIZE(cs35l41_reg_irqs),
+ .runtime_pm = true,
};
static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
@@ -1003,13 +1082,23 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
if (ret)
goto err;
+ pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
+ pm_runtime_use_autosuspend(cs35l41->dev);
+ pm_runtime_mark_last_busy(cs35l41->dev);
+ pm_runtime_set_active(cs35l41->dev);
+ pm_runtime_get_noresume(cs35l41->dev);
+ pm_runtime_enable(cs35l41->dev);
+
ret = cs35l41_hda_apply_properties(cs35l41);
if (ret)
- goto err;
+ goto err_pm;
+
+ pm_runtime_put_autosuspend(cs35l41->dev);
ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops);
if (ret) {
dev_err(cs35l41->dev, "Register component failed: %d\n", ret);
+ pm_runtime_disable(cs35l41->dev);
goto err;
}
@@ -1017,6 +1106,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
return 0;
+err_pm:
+ pm_runtime_disable(cs35l41->dev);
+ pm_runtime_put_noidle(cs35l41->dev);
+
err:
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
@@ -1030,17 +1123,27 @@ void cs35l41_hda_remove(struct device *dev)
{
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+ pm_runtime_get_sync(cs35l41->dev);
+ pm_runtime_disable(cs35l41->dev);
+
if (cs35l41->firmware_running)
cs35l41_remove_dsp(cs35l41);
component_del(cs35l41->dev, &cs35l41_hda_comp_ops);
+ pm_runtime_put_noidle(cs35l41->dev);
+
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
gpiod_put(cs35l41->reset_gpio);
}
EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);
+const struct dev_pm_ops cs35l41_hda_pm_ops = {
+ SET_RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
+};
+EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41);
+
MODULE_DESCRIPTION("CS35L41 HDA Driver");
MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, <tanureal@opensource.cirrus.com>");
@@ -56,6 +56,8 @@ enum halo_state {
HALO_STATE_CODE_RUN
};
+extern const struct dev_pm_ops cs35l41_hda_pm_ops;
+
int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,
struct regmap *regmap);
void cs35l41_hda_remove(struct device *dev);
@@ -55,6 +55,7 @@ static struct i2c_driver cs35l41_i2c_driver = {
.driver = {
.name = "cs35l41-hda",
.acpi_match_table = ACPI_PTR(cs35l41_acpi_hda_match),
+ .pm = &cs35l41_hda_pm_ops,
},
.id_table = cs35l41_hda_i2c_id,
.probe = cs35l41_hda_i2c_probe,
@@ -50,6 +50,7 @@ static struct spi_driver cs35l41_spi_driver = {
.driver = {
.name = "cs35l41-hda",
.acpi_match_table = ACPI_PTR(cs35l41_acpi_hda_match),
+ .pm = &cs35l41_hda_pm_ops,
},
.id_table = cs35l41_hda_spi_id,
.probe = cs35l41_hda_spi_probe,
@@ -17,4 +17,6 @@ struct hda_component {
int subsystem_id;
struct hda_codec *codec;
void (*playback_hook)(struct device *dev, int action);
+ int (*suspend_hook)(struct device *dev);
+ int (*resume_hook)(struct device *dev);
};
@@ -4003,15 +4003,22 @@ static void alc5505_dsp_init(struct hda_codec *codec)
static int alc269_suspend(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
+ int i;
if (spec->has_alc5505_dsp)
alc5505_dsp_suspend(codec);
+
+ for (i = 0; i < HDA_MAX_COMPONENTS; i++)
+ if (spec->comps[i].suspend_hook)
+ spec->comps[i].suspend_hook(spec->comps[i].dev);
+
return alc_suspend(codec);
}
static int alc269_resume(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
+ int i;
if (spec->codec_variant == ALC269_TYPE_ALC269VB)
alc269vb_toggle_power_output(codec, 0);
@@ -4042,6 +4049,10 @@ static int alc269_resume(struct hda_codec *codec)
if (spec->has_alc5505_dsp)
alc5505_dsp_resume(codec);
+ for (i = 0; i < HDA_MAX_COMPONENTS; i++)
+ if (spec->comps[i].resume_hook)
+ spec->comps[i].resume_hook(spec->comps[i].dev);
+
return 0;
}
#endif /* CONFIG_PM */
@@ -6584,8 +6595,20 @@ static int comp_bind(struct device *dev)
{
struct hda_codec *cdc = dev_to_hda_codec(dev);
struct alc_spec *spec = cdc->spec;
+ int ret, i;
+
+ ret = component_bind_all(dev, spec->comps);
+ if (ret)
+ return ret;
- return component_bind_all(dev, spec->comps);
+ if (snd_hdac_is_power_on(&cdc->core)) {
+ codec_dbg(cdc, "Resuming after bind.\n");
+ for (i = 0; i < HDA_MAX_COMPONENTS; i++)
+ if (spec->comps[i].resume_hook)
+ spec->comps[i].resume_hook(spec->comps[i].dev);
+ }
+
+ return 0;
}
static void comp_unbind(struct device *dev)