]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ASoC: cs35l56: Support clock stop mode 1 if enabled in ACPI
authorRichard Fitzgerald <rf@opensource.cirrus.com>
Wed, 11 Mar 2026 14:21:53 +0000 (14:21 +0000)
committerMark Brown <broonie@kernel.org>
Wed, 11 Mar 2026 16:17:10 +0000 (16:17 +0000)
Report the ability to support SoundWire clock-stop mode 1 if this is
enabled in ACPI. Mode 1 allows the device to lose state, so it can
reduce power consumption in clock-stop. Also add the necessary
handling to wait for re-enumeration on resume.

This does not use sdw_slave_read_prop(), because that also fills in
other properties from ACPI that were not previously set by the driver
and this has been observed to break some systems. Instead, the
"mipi-sdw-clock-stop-mode1-supported" property is checked directly.

When a SoundWire peripheral has been put into clock-stop mode 1 it
must be re-enumerated after the clock is restarted. A new flag
sdw_in_clock_stop_1 is set to true in cs35l56_sdw_clk_stop() if the
SoundWire core notifies that it is entering clock-stop 1.
cs35l56_sdw_handle_unattach() will wait for re-enumeration if
sdw_in_clock_stop_1 is true.

sdw_in_clock_stop_1 will be reset to false when an ATTACH notification
is received in cs35l56_sdw_update_status().

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Link: https://patch.msgid.link/20260311142153.2201761-1-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/cs35l56-sdw.c
sound/soc/codecs/cs35l56.h

index 30b3192d6ce9b9bd3d12e2c9dd9284e5ccbe081b..9dc47fec1ea048caa0f4158d26206474cc132138 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/soundwire/sdw.h>
 #include <linux/soundwire/sdw_registers.h>
 #include <linux/soundwire/sdw_type.h>
+#include <linux/string_choices.h>
 #include <linux/swab.h>
 #include <linux/types.h>
 #include <linux/workqueue.h>
@@ -340,6 +341,14 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
        struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
        struct sdw_slave_prop *prop = &peripheral->prop;
        struct sdw_dpn_prop *ports;
+       u8 clock_stop_1 = false;
+       int ret;
+
+       ret = fwnode_property_read_u8(dev_fwnode(cs35l56->base.dev),
+                                     "mipi-sdw-clock-stop-mode1-supported",
+                                     &clock_stop_1);
+       if (ret == 0)
+               prop->clk_stop_mode1 = !!clock_stop_1;
 
        ports = devm_kcalloc(cs35l56->base.dev, 2, sizeof(*ports), GFP_KERNEL);
        if (!ports)
@@ -363,6 +372,9 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
        ports[1].ch_prep_timeout = 10;
        prop->src_dpn_prop = &ports[1];
 
+       dev_dbg(&peripheral->dev, "clock stop mode 1 supported: %s\n",
+               str_yes_no(prop->clk_stop_mode1));
+
        return 0;
 }
 
@@ -374,6 +386,7 @@ static int cs35l56_sdw_update_status(struct sdw_slave *peripheral,
        switch (status) {
        case SDW_SLAVE_ATTACHED:
                dev_dbg(cs35l56->base.dev, "%s: ATTACHED\n", __func__);
+               cs35l56->sdw_in_clock_stop_1 = false;
                if (cs35l56->sdw_attached)
                        break;
 
@@ -399,25 +412,35 @@ static int __maybe_unused cs35l56_sdw_clk_stop(struct sdw_slave *peripheral,
 {
        struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
 
-       dev_dbg(cs35l56->base.dev, "%s: mode:%d type:%d\n", __func__, mode, type);
+       dev_dbg(cs35l56->base.dev, "%s: clock_stop_mode%d stage:%d\n", __func__, mode, type);
 
-       return 0;
+       switch (type) {
+       case SDW_CLK_POST_PREPARE:
+               if (mode == SDW_CLK_STOP_MODE1)
+                       cs35l56->sdw_in_clock_stop_1 = true;
+               else
+                       cs35l56->sdw_in_clock_stop_1 = false;
+               return 0;
+       default:
+               return 0;
+       }
 }
 
 static const struct sdw_slave_ops cs35l56_sdw_ops = {
        .read_prop = cs35l56_sdw_read_prop,
        .interrupt_callback = cs35l56_sdw_interrupt,
        .update_status = cs35l56_sdw_update_status,
-#ifdef DEBUG
        .clk_stop = cs35l56_sdw_clk_stop,
-#endif
 };
 
 static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs35l56)
 {
        struct sdw_slave *peripheral = cs35l56->sdw_peripheral;
 
-       if (peripheral->unattach_request) {
+       dev_dbg(cs35l56->base.dev, "attached:%u unattach_request:%u in_clock_stop_1:%u\n",
+               cs35l56->sdw_attached, peripheral->unattach_request, cs35l56->sdw_in_clock_stop_1);
+
+       if (cs35l56->sdw_in_clock_stop_1 || peripheral->unattach_request) {
                /* Cannot access registers until bus is re-initialized. */
                dev_dbg(cs35l56->base.dev, "Wait for initialization_complete\n");
                if (!wait_for_completion_timeout(&peripheral->initialization_complete,
@@ -427,6 +450,7 @@ static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs
                }
 
                peripheral->unattach_request = 0;
+               cs35l56->sdw_in_clock_stop_1 = false;
 
                /*
                 * Don't call regcache_mark_dirty(), we can't be sure that the
index 747529be3e2e033c2a49583c5fd75fb0627e90b0..36d239d571cd86d35e412b834c9bf863792033af 100644 (file)
@@ -42,6 +42,7 @@ struct cs35l56_private {
        bool sdw_irq_no_unmask;
        bool soft_resetting;
        bool sdw_attached;
+       bool sdw_in_clock_stop_1;
        struct completion init_completion;
 
        int speaker_id;