ALSA: hda: cs35l41: Support System Suspend
[platform/kernel/linux-starfive.git] / sound / pci / hda / cs35l41_hda.c
index 15e2a00..e5f0549 100644 (file)
@@ -91,20 +91,18 @@ static const struct reg_sequence cs35l41_hda_mute[] = {
        { CS35L41_AMP_DIG_VOL_CTRL,     0x0000A678 }, // AMP_VOL_PCM Mute
 };
 
-static int cs35l41_control_add(struct cs_dsp_coeff_ctl *cs_ctl)
+static void cs35l41_add_controls(struct cs35l41_hda *cs35l41)
 {
-       struct cs35l41_hda *cs35l41 = container_of(cs_ctl->dsp, struct cs35l41_hda, cs_dsp);
        struct hda_cs_dsp_ctl_info info;
 
        info.device_name = cs35l41->amp_name;
        info.fw_type = cs35l41->firmware_type;
        info.card = cs35l41->codec->card;
 
-       return hda_cs_dsp_control_add(cs_ctl, &info);
+       hda_cs_dsp_add_controls(&cs35l41->cs_dsp, &info);
 }
 
 static const struct cs_dsp_client_ops client_ops = {
-       .control_add = cs35l41_control_add,
        .control_remove = hda_cs_dsp_control_remove,
 };
 
@@ -435,6 +433,8 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41)
        if (ret)
                goto err_release;
 
+       cs35l41_add_controls(cs35l41);
+
        ret = cs35l41_save_calibration(cs35l41);
 
 err_release:
@@ -461,9 +461,12 @@ static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41)
        struct cs_dsp *dsp = &cs35l41->cs_dsp;
 
        cancel_work_sync(&cs35l41->fw_load_work);
+
+       mutex_lock(&cs35l41->fw_mutex);
        cs35l41_shutdown_dsp(cs35l41);
        cs_dsp_remove(dsp);
        cs35l41->halo_initialized = false;
+       mutex_unlock(&cs35l41->fw_mutex);
 }
 
 /* Protection release cycle to get the speaker out of Safe-Mode */
@@ -487,10 +490,10 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
        struct regmap *reg = cs35l41->regmap;
        int ret = 0;
 
-       mutex_lock(&cs35l41->fw_mutex);
-
        switch (action) {
        case HDA_GEN_PCM_ACT_OPEN:
+               pm_runtime_get_sync(dev);
+               mutex_lock(&cs35l41->fw_mutex);
                cs35l41->playback_started = true;
                if (cs35l41->firmware_running) {
                        regmap_multi_reg_write(reg, cs35l41_hda_config_dsp,
@@ -508,15 +511,21 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
                                         CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT);
                if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
                        regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001);
+               mutex_unlock(&cs35l41->fw_mutex);
                break;
        case HDA_GEN_PCM_ACT_PREPARE:
+               mutex_lock(&cs35l41->fw_mutex);
                ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1);
+               mutex_unlock(&cs35l41->fw_mutex);
                break;
        case HDA_GEN_PCM_ACT_CLEANUP:
+               mutex_lock(&cs35l41->fw_mutex);
                regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
                ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0);
+               mutex_unlock(&cs35l41->fw_mutex);
                break;
        case HDA_GEN_PCM_ACT_CLOSE:
+               mutex_lock(&cs35l41->fw_mutex);
                ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2,
                                         CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
                if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
@@ -530,14 +539,16 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action)
                }
                cs35l41_irq_release(cs35l41);
                cs35l41->playback_started = false;
+               mutex_unlock(&cs35l41->fw_mutex);
+
+               pm_runtime_mark_last_busy(dev);
+               pm_runtime_put_autosuspend(dev);
                break;
        default:
                dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action);
                break;
        }
 
-       mutex_unlock(&cs35l41->fw_mutex);
-
        if (ret)
                dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret);
 }
@@ -562,45 +573,148 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi
                                    rx_slot);
 }
 
+static void cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41)
+{
+       mutex_lock(&cs35l41->fw_mutex);
+       if (cs35l41->firmware_running) {
+
+               regcache_cache_only(cs35l41->regmap, false);
+
+               cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
+               cs35l41_shutdown_dsp(cs35l41);
+               cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
+
+               regcache_cache_only(cs35l41->regmap, true);
+               regcache_mark_dirty(cs35l41->regmap);
+       }
+       mutex_unlock(&cs35l41->fw_mutex);
+}
+
+static int cs35l41_system_suspend(struct device *dev)
+{
+       struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+       int ret;
+
+       dev_dbg(cs35l41->dev, "System Suspend\n");
+
+       if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
+               dev_err(cs35l41->dev, "System Suspend not supported\n");
+               return -EINVAL;
+       }
+
+       ret = pm_runtime_force_suspend(dev);
+       if (ret)
+               return ret;
+
+       /* Shutdown DSP before system suspend */
+       cs35l41_ready_for_reset(cs35l41);
+
+       /*
+        * Reset GPIO may be shared, so cannot reset here.
+        * However beyond this point, amps may be powered down.
+        */
+       return 0;
+}
+
+static int cs35l41_system_resume(struct device *dev)
+{
+       struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+       int ret;
+
+       dev_dbg(cs35l41->dev, "System Resume\n");
+
+       if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
+               dev_err(cs35l41->dev, "System Resume not supported\n");
+               return -EINVAL;
+       }
+
+       if (cs35l41->reset_gpio) {
+               usleep_range(2000, 2100);
+               gpiod_set_value_cansleep(cs35l41->reset_gpio, 1);
+       }
+
+       usleep_range(2000, 2100);
+
+       ret = pm_runtime_force_resume(dev);
+
+       mutex_lock(&cs35l41->fw_mutex);
+       if (!ret && cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) {
+               cs35l41->fw_request_ongoing = true;
+               schedule_work(&cs35l41->fw_load_work);
+       }
+       mutex_unlock(&cs35l41->fw_mutex);
+
+       return ret;
+}
+
 static int cs35l41_runtime_suspend(struct device *dev)
 {
        struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+       int ret = 0;
 
-       dev_dbg(cs35l41->dev, "Suspend\n");
+       dev_dbg(cs35l41->dev, "Runtime Suspend\n");
 
-       if (!cs35l41->firmware_running)
+       if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
+               dev_dbg(cs35l41->dev, "Runtime Suspend not supported\n");
                return 0;
+       }
 
-       if (cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type) < 0)
-               return 0;
+       mutex_lock(&cs35l41->fw_mutex);
+
+       if (cs35l41->playback_started) {
+               regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
+                                      ARRAY_SIZE(cs35l41_hda_mute));
+               cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0);
+               regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+                                  CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
+               if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
+                       regmap_write(cs35l41->regmap, CS35L41_GPIO1_CTRL1, 0x00000001);
+               regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
+                                  CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
+                                  0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT);
+               cs35l41->playback_started = false;
+       }
+
+       if (cs35l41->firmware_running) {
+               ret = cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap,
+                                             cs35l41->hw_cfg.bst_type);
+               if (ret)
+                       goto err;
+       } else {
+               cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
+       }
 
        regcache_cache_only(cs35l41->regmap, true);
        regcache_mark_dirty(cs35l41->regmap);
 
-       return 0;
+err:
+       mutex_unlock(&cs35l41->fw_mutex);
+
+       return ret;
 }
 
 static int cs35l41_runtime_resume(struct device *dev)
 {
        struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
-       int ret;
+       int ret = 0;
 
-       dev_dbg(cs35l41->dev, "Resume.\n");
+       dev_dbg(cs35l41->dev, "Runtime Resume\n");
 
        if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
-               dev_dbg(cs35l41->dev, "System does not support Resume\n");
+               dev_dbg(cs35l41->dev, "Runtime Resume not supported\n");
                return 0;
        }
 
-       if (!cs35l41->firmware_running)
-               return 0;
+       mutex_lock(&cs35l41->fw_mutex);
 
        regcache_cache_only(cs35l41->regmap, false);
 
-       ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
-       if (ret) {
-               regcache_cache_only(cs35l41->regmap, true);
-               return ret;
+       if (cs35l41->firmware_running)  {
+               ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
+               if (ret) {
+                       dev_warn(cs35l41->dev, "Unable to exit Hibernate.");
+                       goto err;
+               }
        }
 
        /* Test key needs to be unlocked to allow the OTP settings to re-apply */
@@ -609,26 +723,16 @@ static int cs35l41_runtime_resume(struct device *dev)
        cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap);
        if (ret) {
                dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret);
-               return ret;
+               goto err;
        }
 
        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);
-}
+err:
+       mutex_unlock(&cs35l41->fw_mutex);
 
-static int cs35l41_hda_resume_hook(struct device *dev)
-{
-       dev_dbg(dev, "Request Resume\n");
-       return pm_runtime_get_sync(dev);
+       return ret;
 }
 
 static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
@@ -678,8 +782,6 @@ clean_dsp:
 
 static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load)
 {
-       pm_runtime_get_sync(cs35l41->dev);
-
        if (cs35l41->firmware_running && !load) {
                dev_dbg(cs35l41->dev, "Unloading Firmware\n");
                cs35l41_shutdown_dsp(cs35l41);
@@ -689,9 +791,6 @@ static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load)
        } else {
                dev_dbg(cs35l41->dev, "Unable to Load firmware.\n");
        }
-
-       pm_runtime_mark_last_busy(cs35l41->dev);
-       pm_runtime_put_autosuspend(cs35l41->dev);
 }
 
 static int cs35l41_fw_load_ctl_get(struct snd_kcontrol *kcontrol,
@@ -707,16 +806,21 @@ static void cs35l41_fw_load_work(struct work_struct *work)
 {
        struct cs35l41_hda *cs35l41 = container_of(work, struct cs35l41_hda, fw_load_work);
 
+       pm_runtime_get_sync(cs35l41->dev);
+
        mutex_lock(&cs35l41->fw_mutex);
 
        /* Recheck if playback is ongoing, mutex will block playback during firmware loading */
        if (cs35l41->playback_started)
-               dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n");
+               dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback. Retrying...\n");
        else
                cs35l41_load_firmware(cs35l41, cs35l41->request_fw_load);
 
        cs35l41->fw_request_ongoing = false;
        mutex_unlock(&cs35l41->fw_mutex);
+
+       pm_runtime_mark_last_busy(cs35l41->dev);
+       pm_runtime_put_autosuspend(cs35l41->dev);
 }
 
 static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol,
@@ -840,10 +944,12 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
 
        pm_runtime_get_sync(dev);
 
+       mutex_lock(&cs35l41->fw_mutex);
+
        comps->dev = dev;
        if (!cs35l41->acpi_subsystem_id)
-               cs35l41->acpi_subsystem_id = devm_kasprintf(dev, GFP_KERNEL, "%.8x",
-                                                           comps->codec->core.subsystem_id);
+               cs35l41->acpi_subsystem_id = kasprintf(GFP_KERNEL, "%.8x",
+                                                      comps->codec->core.subsystem_id);
        cs35l41->codec = comps->codec;
        strscpy(comps->name, dev_name(dev), sizeof(comps->name));
 
@@ -852,10 +958,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
        if (firmware_autostart) {
                dev_dbg(cs35l41->dev, "Firmware Autostart.\n");
                cs35l41->request_fw_load = true;
-               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);
        } else {
                dev_dbg(cs35l41->dev, "Firmware Autostart is disabled.\n");
        }
@@ -863,8 +967,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
        ret = cs35l41_create_controls(cs35l41);
 
        comps->playback_hook = cs35l41_hda_playback_hook;
-       comps->suspend_hook = cs35l41_hda_suspend_hook;
-       comps->resume_hook = cs35l41_hda_resume_hook;
+
+       mutex_unlock(&cs35l41->fw_mutex);
 
        pm_runtime_mark_last_busy(dev);
        pm_runtime_put_autosuspend(dev);
@@ -1048,36 +1152,6 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
        return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos);
 }
 
-static int cs35l41_get_acpi_sub_string(struct device *dev, struct acpi_device *adev,
-                                      const char **subsysid)
-{
-       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
-       union acpi_object *obj;
-       acpi_status status;
-       int ret = 0;
-
-       status = acpi_evaluate_object(adev->handle, "_SUB", NULL, &buffer);
-       if (ACPI_SUCCESS(status)) {
-               obj = buffer.pointer;
-               if (obj->type == ACPI_TYPE_STRING) {
-                       *subsysid = devm_kstrdup(dev, obj->string.pointer, GFP_KERNEL);
-                       if (*subsysid == NULL) {
-                               dev_err(dev, "Cannot allocate Subsystem ID");
-                               ret = -ENOMEM;
-                       }
-               } else {
-                       dev_warn(dev, "Warning ACPI _SUB did not return a string\n");
-                       ret = -ENODEV;
-               }
-               acpi_os_free(buffer.pointer);
-       } else {
-               dev_dbg(dev, "Warning ACPI _SUB failed: %#x\n", status);
-               ret = -ENODEV;
-       }
-
-       return ret;
-}
-
 static int cs35l41_get_speaker_id(struct device *dev, int amp_index,
                                  int num_amps, int fixed_gpio_id)
 {
@@ -1154,7 +1228,6 @@ static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physd
        hw_cfg->gpio2.func = CS35L41_INTERRUPT;
        hw_cfg->gpio2.valid = true;
        hw_cfg->valid = true;
-       put_device(physdev);
 
        if (strncmp(hid, "CLSA0100", 8) == 0) {
                hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH;
@@ -1183,6 +1256,7 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
        u32 values[HDA_MAX_COMPONENTS];
        struct acpi_device *adev;
        struct device *physdev;
+       const char *sub;
        char *property;
        size_t nval;
        int i, ret;
@@ -1196,17 +1270,17 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
        physdev = get_device(acpi_get_first_physical_node(adev));
        acpi_dev_put(adev);
 
-       ret = cs35l41_get_acpi_sub_string(cs35l41->dev, adev, &cs35l41->acpi_subsystem_id);
-       if (ret)
-               dev_info(cs35l41->dev, "No Subsystem ID found in ACPI: %d", ret);
-       else
-               dev_dbg(cs35l41->dev, "Subsystem ID %s found", cs35l41->acpi_subsystem_id);
+       sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev));
+       if (IS_ERR(sub))
+               sub = NULL;
+       cs35l41->acpi_subsystem_id = sub;
 
        property = "cirrus,dev-index";
        ret = device_property_count_u32(physdev, property);
-       if (ret <= 0)
-               return cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid);
-
+       if (ret <= 0) {
+               ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid);
+               goto err_put_physdev;
+       }
        if (ret > ARRAY_SIZE(values)) {
                ret = -EINVAL;
                goto err;
@@ -1295,8 +1369,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
        return 0;
 
 err:
-       put_device(physdev);
        dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret);
+err_put_physdev:
+       put_device(physdev);
 
        return ret;
 }
@@ -1433,6 +1508,7 @@ err:
        if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
                gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
        gpiod_put(cs35l41->reset_gpio);
+       kfree(cs35l41->acpi_subsystem_id);
 
        return ret;
 }
@@ -1455,11 +1531,13 @@ void cs35l41_hda_remove(struct device *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);
+       kfree(cs35l41->acpi_subsystem_id);
 }
 EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);
 
 const struct dev_pm_ops cs35l41_hda_pm_ops = {
        RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
+       SYSTEM_SLEEP_PM_OPS(cs35l41_system_suspend, cs35l41_system_resume)
 };
 EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41);