Skip to content

Commit 8867282

Browse files
Stefan Bindingtiwai
authored andcommitted
ALSA: hda: cs35l41: Support System Suspend
Add support for system suspend into the CS35L41 HDA Driver. Since S4 suspend may power off the system, it is required that the driver ensure the part is safe to be shutdown before system suspend, as well as ensuring that the firmware is unloaded before shutdown. The part must then be restored on system resume, including re-downloading the firmware. Signed-off-by: Stefan Binding <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent 23904f7 commit 8867282

File tree

1 file changed

+136
-24
lines changed

1 file changed

+136
-24
lines changed

sound/pci/hda/cs35l41_hda.c

Lines changed: 136 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -461,9 +461,12 @@ static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41)
461461
struct cs_dsp *dsp = &cs35l41->cs_dsp;
462462

463463
cancel_work_sync(&cs35l41->fw_load_work);
464+
465+
mutex_lock(&cs35l41->fw_mutex);
464466
cs35l41_shutdown_dsp(cs35l41);
465467
cs_dsp_remove(dsp);
466468
cs35l41->halo_initialized = false;
469+
mutex_unlock(&cs35l41->fw_mutex);
467470
}
468471

469472
/* Protection release cycle to get the speaker out of Safe-Mode */
@@ -570,45 +573,148 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi
570573
rx_slot);
571574
}
572575

576+
static void cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41)
577+
{
578+
mutex_lock(&cs35l41->fw_mutex);
579+
if (cs35l41->firmware_running) {
580+
581+
regcache_cache_only(cs35l41->regmap, false);
582+
583+
cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
584+
cs35l41_shutdown_dsp(cs35l41);
585+
cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
586+
587+
regcache_cache_only(cs35l41->regmap, true);
588+
regcache_mark_dirty(cs35l41->regmap);
589+
}
590+
mutex_unlock(&cs35l41->fw_mutex);
591+
}
592+
593+
static int cs35l41_system_suspend(struct device *dev)
594+
{
595+
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
596+
int ret;
597+
598+
dev_dbg(cs35l41->dev, "System Suspend\n");
599+
600+
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
601+
dev_err(cs35l41->dev, "System Suspend not supported\n");
602+
return -EINVAL;
603+
}
604+
605+
ret = pm_runtime_force_suspend(dev);
606+
if (ret)
607+
return ret;
608+
609+
/* Shutdown DSP before system suspend */
610+
cs35l41_ready_for_reset(cs35l41);
611+
612+
/*
613+
* Reset GPIO may be shared, so cannot reset here.
614+
* However beyond this point, amps may be powered down.
615+
*/
616+
return 0;
617+
}
618+
619+
static int cs35l41_system_resume(struct device *dev)
620+
{
621+
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
622+
int ret;
623+
624+
dev_dbg(cs35l41->dev, "System Resume\n");
625+
626+
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
627+
dev_err(cs35l41->dev, "System Resume not supported\n");
628+
return -EINVAL;
629+
}
630+
631+
if (cs35l41->reset_gpio) {
632+
usleep_range(2000, 2100);
633+
gpiod_set_value_cansleep(cs35l41->reset_gpio, 1);
634+
}
635+
636+
usleep_range(2000, 2100);
637+
638+
ret = pm_runtime_force_resume(dev);
639+
640+
mutex_lock(&cs35l41->fw_mutex);
641+
if (!ret && cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) {
642+
cs35l41->fw_request_ongoing = true;
643+
schedule_work(&cs35l41->fw_load_work);
644+
}
645+
mutex_unlock(&cs35l41->fw_mutex);
646+
647+
return ret;
648+
}
649+
573650
static int cs35l41_runtime_suspend(struct device *dev)
574651
{
575652
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
653+
int ret = 0;
576654

577-
dev_dbg(cs35l41->dev, "Suspend\n");
655+
dev_dbg(cs35l41->dev, "Runtime Suspend\n");
578656

579-
if (!cs35l41->firmware_running)
657+
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
658+
dev_dbg(cs35l41->dev, "Runtime Suspend not supported\n");
580659
return 0;
660+
}
581661

582-
if (cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type) < 0)
583-
return 0;
662+
mutex_lock(&cs35l41->fw_mutex);
663+
664+
if (cs35l41->playback_started) {
665+
regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
666+
ARRAY_SIZE(cs35l41_hda_mute));
667+
cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0);
668+
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
669+
CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
670+
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
671+
regmap_write(cs35l41->regmap, CS35L41_GPIO1_CTRL1, 0x00000001);
672+
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
673+
CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK,
674+
0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT);
675+
cs35l41->playback_started = false;
676+
}
677+
678+
if (cs35l41->firmware_running) {
679+
ret = cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap,
680+
cs35l41->hw_cfg.bst_type);
681+
if (ret)
682+
goto err;
683+
} else {
684+
cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
685+
}
584686

585687
regcache_cache_only(cs35l41->regmap, true);
586688
regcache_mark_dirty(cs35l41->regmap);
587689

588-
return 0;
690+
err:
691+
mutex_unlock(&cs35l41->fw_mutex);
692+
693+
return ret;
589694
}
590695

591696
static int cs35l41_runtime_resume(struct device *dev)
592697
{
593698
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
594-
int ret;
699+
int ret = 0;
595700

596-
dev_dbg(cs35l41->dev, "Resume.\n");
701+
dev_dbg(cs35l41->dev, "Runtime Resume\n");
597702

598703
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) {
599-
dev_dbg(cs35l41->dev, "System does not support Resume\n");
704+
dev_dbg(cs35l41->dev, "Runtime Resume not supported\n");
600705
return 0;
601706
}
602707

603-
if (!cs35l41->firmware_running)
604-
return 0;
708+
mutex_lock(&cs35l41->fw_mutex);
605709

606710
regcache_cache_only(cs35l41->regmap, false);
607711

608-
ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
609-
if (ret) {
610-
regcache_cache_only(cs35l41->regmap, true);
611-
return ret;
712+
if (cs35l41->firmware_running) {
713+
ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap);
714+
if (ret) {
715+
dev_warn(cs35l41->dev, "Unable to exit Hibernate.");
716+
goto err;
717+
}
612718
}
613719

614720
/* Test key needs to be unlocked to allow the OTP settings to re-apply */
@@ -617,13 +723,16 @@ static int cs35l41_runtime_resume(struct device *dev)
617723
cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap);
618724
if (ret) {
619725
dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret);
620-
return ret;
726+
goto err;
621727
}
622728

623729
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
624730
cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg);
625731

626-
return 0;
732+
err:
733+
mutex_unlock(&cs35l41->fw_mutex);
734+
735+
return ret;
627736
}
628737

629738
static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
@@ -673,8 +782,6 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41)
673782

674783
static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load)
675784
{
676-
pm_runtime_get_sync(cs35l41->dev);
677-
678785
if (cs35l41->firmware_running && !load) {
679786
dev_dbg(cs35l41->dev, "Unloading Firmware\n");
680787
cs35l41_shutdown_dsp(cs35l41);
@@ -684,9 +791,6 @@ static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load)
684791
} else {
685792
dev_dbg(cs35l41->dev, "Unable to Load firmware.\n");
686793
}
687-
688-
pm_runtime_mark_last_busy(cs35l41->dev);
689-
pm_runtime_put_autosuspend(cs35l41->dev);
690794
}
691795

692796
static int cs35l41_fw_load_ctl_get(struct snd_kcontrol *kcontrol,
@@ -702,16 +806,21 @@ static void cs35l41_fw_load_work(struct work_struct *work)
702806
{
703807
struct cs35l41_hda *cs35l41 = container_of(work, struct cs35l41_hda, fw_load_work);
704808

809+
pm_runtime_get_sync(cs35l41->dev);
810+
705811
mutex_lock(&cs35l41->fw_mutex);
706812

707813
/* Recheck if playback is ongoing, mutex will block playback during firmware loading */
708814
if (cs35l41->playback_started)
709-
dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n");
815+
dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback. Retrying...\n");
710816
else
711817
cs35l41_load_firmware(cs35l41, cs35l41->request_fw_load);
712818

713819
cs35l41->fw_request_ongoing = false;
714820
mutex_unlock(&cs35l41->fw_mutex);
821+
822+
pm_runtime_mark_last_busy(cs35l41->dev);
823+
pm_runtime_put_autosuspend(cs35l41->dev);
715824
}
716825

717826
static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol,
@@ -835,6 +944,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
835944

836945
pm_runtime_get_sync(dev);
837946

947+
mutex_lock(&cs35l41->fw_mutex);
948+
838949
comps->dev = dev;
839950
if (!cs35l41->acpi_subsystem_id)
840951
cs35l41->acpi_subsystem_id = kasprintf(GFP_KERNEL, "%.8x",
@@ -847,10 +958,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
847958
if (firmware_autostart) {
848959
dev_dbg(cs35l41->dev, "Firmware Autostart.\n");
849960
cs35l41->request_fw_load = true;
850-
mutex_lock(&cs35l41->fw_mutex);
851961
if (cs35l41_smart_amp(cs35l41) < 0)
852962
dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n");
853-
mutex_unlock(&cs35l41->fw_mutex);
854963
} else {
855964
dev_dbg(cs35l41->dev, "Firmware Autostart is disabled.\n");
856965
}
@@ -859,6 +968,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas
859968

860969
comps->playback_hook = cs35l41_hda_playback_hook;
861970

971+
mutex_unlock(&cs35l41->fw_mutex);
972+
862973
pm_runtime_mark_last_busy(dev);
863974
pm_runtime_put_autosuspend(dev);
864975

@@ -1426,6 +1537,7 @@ EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);
14261537

14271538
const struct dev_pm_ops cs35l41_hda_pm_ops = {
14281539
RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
1540+
SYSTEM_SLEEP_PM_OPS(cs35l41_system_suspend, cs35l41_system_resume)
14291541
};
14301542
EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41);
14311543

0 commit comments

Comments
 (0)