ALSA: hda: cs35l41: Support Hibernation during Suspend
[linux-2.6-microblaze.git] / sound / pci / hda / cs35l41_hda.c
index d5d2323..61441bf 100644 (file)
@@ -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"
@@ -435,6 +436,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;
@@ -492,19 +562,27 @@ 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->codec->core.subsystem_id);
        cs35l41->codec = comps->codec;
        strscpy(comps->name, dev_name(dev), sizeof(comps->name));
-       comps->playback_hook = cs35l41_hda_playback_hook;
 
        mutex_lock(&cs35l41->fw_mutex);
        if (cs35l41_smart_amp(cs35l41) < 0)
                dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
        mutex_unlock(&cs35l41->fw_mutex);
 
+       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;
 }
 
@@ -600,7 +678,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,
@@ -608,6 +686,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)
@@ -1015,13 +1094,23 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
 
        mutex_init(&cs35l41->fw_mutex);
 
+       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;
        }
 
@@ -1029,6 +1118,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);
@@ -1042,17 +1135,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->halo_initialized)
                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>");