[PATCH v2 2/5] ASoC: codec: pcm1681: Add optional SCK clock and runtime PM support

From: Mohammad Rafi Shaik

Date: Thu Oct 08 2026 - 07:07:43 EST


The PCM1681 requires its SCK system clock to be running before register
access. On platforms where SCK is provided by a gateable clock, register
access may fail when the clock is disabled.
 
Add support for an optional "sck" clock and enable it before accessing the
device. After enabling SCK, wait for the required 65536 system clock cycles
to allow the device to complete its internal reset sequence.
 
Use runtime PM to manage SCK instead of keeping it enabled for the lifetime
of the device. Disable the clock during runtime suspend and restore it on
runtime resume. Use autosuspend to avoid unnecessary clock toggling between
closely spaced accesses.
 
Since the device register state may be lost when SCK is disabled, enable
the regmap cache. Switch regmap to cache-only mode and mark the cache
dirty on runtime suspend, then synchronize the cached register state after
SCK is restored on runtime resume. Mark the zero-detect status register
volatile since it is updated by hardware.
 
Drop idle_bias_on so that the component can reach SND_SOC_BIAS_OFF and
runtime suspend can gate SCK.

Assisted-by: LLM
Signed-off-by: Mohammad Rafi Shaik <mohammad.rafi.shaik@xxxxxxxxxxxxxxxx>
---
sound/soc/codecs/pcm1681.c | 137 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 131 insertions(+), 6 deletions(-)

diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c
index 60fdbe5c4..a1ccf1edf 100644
--- a/sound/soc/codecs/pcm1681.c
+++ b/sound/soc/codecs/pcm1681.c
@@ -12,6 +12,8 @@
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/pm_runtime.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -36,6 +38,12 @@
#define PCM1681_DEEMPH_CONTROL 0x0a /* De-emphasis control */
#define PCM1681_ZERO_DETECT_STATUS 0x0e /* Zero detect status reg */

+/*
+ * The DAC requires 65536 system clock cycles after the clock is supplied to
+ * complete its internal reset sequence before it is ready to be used.
+ */
+#define PCM1681_SCK_SETTLE_CYCLES 65536
+
static const struct reg_default pcm1681_reg_defaults[] = {
{ 0x01, 0xff },
{ 0x02, 0xff },
@@ -67,6 +75,12 @@ static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg)
(reg != PCM1681_ZERO_DETECT_STATUS);
}

+static bool pcm1681_volatile_reg(struct device *dev, unsigned int reg)
+{
+ /* Status is updated by the hardware and must never be cached */
+ return reg == PCM1681_ZERO_DETECT_STATUS;
+}
+
struct pcm1681_private {
struct regmap *regmap;
unsigned int format;
@@ -74,8 +88,24 @@ struct pcm1681_private {
unsigned int deemph;
/* Current rate for deemphasis control */
unsigned int rate;
+ struct clk *sck;
};

+/* Wait for the DAC to come out of its internal reset after sck is enabled */
+static void pcm1681_sck_settle(struct pcm1681_private *priv)
+{
+ unsigned long rate = clk_get_rate(priv->sck);
+
+ /*
+ * sck is optional, so there may be no clock to wait on, and its rate
+ * is not always discoverable. Only wait when the cycle count can be
+ * converted into a delay.
+ */
+ if (rate)
+ fsleep(DIV_ROUND_UP_ULL(PCM1681_SCK_SETTLE_CYCLES *
+ (u64)USEC_PER_SEC, rate));
+}
+
static const int pcm1681_deemph[] = { 44100, 48000, 32000 };

static int pcm1681_set_deemph(struct snd_soc_component *component)
@@ -282,6 +312,8 @@ static const struct regmap_config pcm1681_regmap = {
.num_reg_defaults = ARRAY_SIZE(pcm1681_reg_defaults),
.writeable_reg = pcm1681_writeable_reg,
.readable_reg = pcm1681_accessible_reg,
+ .volatile_reg = pcm1681_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
};

static const struct snd_soc_component_driver soc_component_dev_pcm1681 = {
@@ -291,7 +323,6 @@ static const struct snd_soc_component_driver soc_component_dev_pcm1681 = {
.num_dapm_widgets = ARRAY_SIZE(pcm1681_dapm_widgets),
.dapm_routes = pcm1681_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(pcm1681_dapm_routes),
- .idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
};
@@ -304,34 +335,128 @@ MODULE_DEVICE_TABLE(i2c, pcm1681_i2c_id);

static int pcm1681_i2c_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
int ret;
struct pcm1681_private *priv;

- priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;

+ priv->sck = devm_clk_get_optional(dev, "sck");
+ if (IS_ERR(priv->sck))
+ return dev_err_probe(dev, PTR_ERR(priv->sck),
+ "Failed to get sck\n");
+
priv->regmap = devm_regmap_init_i2c(client, &pcm1681_regmap);
if (IS_ERR(priv->regmap)) {
ret = PTR_ERR(priv->regmap);
- dev_err(&client->dev, "Failed to create regmap: %d\n", ret);
+ dev_err(dev, "Failed to create regmap: %d\n", ret);
return ret;
}

+ /* Must be set before runtime PM is enabled, the callbacks use it */
i2c_set_clientdata(client, priv);

- return devm_snd_soc_register_component(&client->dev,
- &soc_component_dev_pcm1681,
- &pcm1681_dai, 1);
+ ret = clk_prepare_enable(priv->sck);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable sck\n");
+
+ pcm1681_sck_settle(priv);
+
+ /* The clock is on, so hand the now-active device over to runtime PM */
+ pm_runtime_set_autosuspend_delay(dev, 100);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_idle(dev);
+
+ ret = devm_snd_soc_register_component(dev,
+ &soc_component_dev_pcm1681,
+ &pcm1681_dai, 1);
+ if (ret) {
+ dev_err(dev, "Failed to register component: %d\n", ret);
+ goto err_pm;
+ }
+
+ return 0;
+
+err_pm:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ if (!pm_runtime_status_suspended(dev))
+ clk_disable_unprepare(priv->sck);
+ pm_runtime_set_suspended(dev);
+
+ return ret;
}

+static void pcm1681_i2c_remove(struct i2c_client *client)
+{
+ struct pcm1681_private *priv = i2c_get_clientdata(client);
+ struct device *dev = &client->dev;
+
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ /* Runtime PM may already have gated the clock */
+ if (!pm_runtime_status_suspended(dev))
+ clk_disable_unprepare(priv->sck);
+ pm_runtime_set_suspended(dev);
+}
+
+static int pcm1681_runtime_suspend(struct device *dev)
+{
+ struct pcm1681_private *priv = dev_get_drvdata(dev);
+
+ /*
+ * Registers lose their contents once the clock is gated, so serve
+ * further access from the cache and replay it on resume.
+ */
+ regcache_cache_only(priv->regmap, true);
+ regcache_mark_dirty(priv->regmap);
+
+ clk_disable_unprepare(priv->sck);
+
+ return 0;
+}
+
+static int pcm1681_runtime_resume(struct device *dev)
+{
+ struct pcm1681_private *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->sck);
+ if (ret) {
+ dev_err(dev, "Failed to enable sck: %d\n", ret);
+ return ret;
+ }
+
+ pcm1681_sck_settle(priv);
+
+ regcache_cache_only(priv->regmap, false);
+ ret = regcache_sync(priv->regmap);
+ if (ret) {
+ dev_err(dev, "Failed to sync regcache: %d\n", ret);
+ regcache_cache_only(priv->regmap, true);
+ clk_disable_unprepare(priv->sck);
+ return ret;
+ }
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(pcm1681_pm_ops, pcm1681_runtime_suspend,
+ pcm1681_runtime_resume, NULL);
+
static struct i2c_driver pcm1681_i2c_driver = {
.driver = {
.name = "pcm1681",
.of_match_table = of_match_ptr(pcm1681_dt_ids),
+ .pm = pm_ptr(&pcm1681_pm_ops),
},
.id_table = pcm1681_i2c_id,
.probe = pcm1681_i2c_probe,
+ .remove = pcm1681_i2c_remove,
};

module_i2c_driver(pcm1681_i2c_driver);

--
2.34.1