mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching
@ 2026-09-21  8:57 Sheetal
  2026-09-21  8:57 ` [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control Sheetal
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Sheetal @ 2026-09-21  8:57 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Thierry Reding, Jonathan Hunter,
	Sameer Pujar, Kuninori Morimoto, Mohan Kumar, linux-sound,
	linux-tegra, linux-kernel, Sheetal

This RFC series updates the Tegra186 ASRC driver control handling.

The main RFC point is the userspace-visible ALSA mixer control ABI
change in patch 2. The existing per-stream controls expose the software
ratio as separate integer and fractional mixer controls. This series
replaces each pair with one two-value mixer control so userspace updates
the integer and fractional parts together. That avoids programming a
transient mixed ratio to hardware, but it does require userspace that
sets ASRC software ratios to switch to the new paired controls.

Patch 1 fixes the Stream6 input-threshold control, which was using the
same stream index as Stream5 and therefore addressed the wrong lane.

Patch 2 replaces the separate integer and fractional software ratio
ALSA mixer controls with a paired two-value control per stream. This
keeps the integer/fractional ratio update atomic from userspace,
validates both values before programming hardware, serializes
ratio-source and ratio updates, refreshes the hardware lock for large
ratio jumps, and rolls back partial hardware updates on failure.

ALSA exposes one min and max range for all values in an integer-array
control, so the paired ratio controls advertise the fractional field's
full 32-bit range. The put callback validates the integer part separately
against the hardware field width.

Patch 3 caches software ratio updates while the target stream is not
active or the ASRC device is runtime suspended. Cached ratios are applied
when streams are configured and restored after runtime resume, avoiding
unsafe volatile register accesses while suspended.

Sheetal (3):
  ASoC: tegra: Fix ASRC Stream6 input threshold control
  ASoC: tegra: Update ASRC ratio controls
  ASoC: tegra: Cache ASRC ratios until streams are active

 sound/soc/tegra/tegra186_asrc.c | 513 +++++++++++++++++++++-----------
 sound/soc/tegra/tegra186_asrc.h |   5 +
 2 files changed, 347 insertions(+), 171 deletions(-)

-- 
2.43.0

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control
  2026-09-21  8:57 [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Sheetal
@ 2026-09-21  8:57 ` Sheetal
  2026-09-21 11:38   ` Thierry Reding
  2026-09-21  8:57 ` [RFC PATCH 2/3] ASoC: tegra: Update ASRC ratio controls Sheetal
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Sheetal @ 2026-09-21  8:57 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Thierry Reding, Jonathan Hunter,
	Sameer Pujar, Kuninori Morimoto, Mohan Kumar, linux-sound,
	linux-tegra, linux-kernel, Sheetal

The Stream6 Input Threshold control points at stream index 4, which is
already used by Stream5. The threshold get and put callbacks derive the
lane ID from the register offset, so the duplicate index makes Stream6
operate on lane 4 and leaves lane 5 inaccessible from userspace.

Use stream index 5 for Stream6.

Fixes: a2df8c2d5b36 ("ASoC: tegra: Add Tegra186 based ASRC driver")
Signed-off-by: Sheetal <sheetal@nvidia.com>
---
 sound/soc/tegra/tegra186_asrc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c
index 7f360dfaf8b1..d8ae5d997615 100644
--- a/sound/soc/tegra/tegra186_asrc.c
+++ b/sound/soc/tegra/tegra186_asrc.c
@@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = {
 		       tegra186_asrc_put_input_threshold),
 
 	SOC_SINGLE_EXT("Stream6 Input Threshold",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0,
+		       ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0,
 		       tegra186_asrc_get_input_threshold,
 		       tegra186_asrc_put_input_threshold),
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC PATCH 2/3] ASoC: tegra: Update ASRC ratio controls
  2026-09-21  8:57 [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Sheetal
  2026-09-21  8:57 ` [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control Sheetal
@ 2026-09-21  8:57 ` Sheetal
  2026-09-21 11:50   ` Thierry Reding
  2026-09-21  8:57 ` [RFC PATCH 3/3] ASoC: tegra: Cache ASRC ratios until streams are active Sheetal
  2026-09-21 12:07 ` [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Thierry Reding
  3 siblings, 1 reply; 8+ messages in thread
From: Sheetal @ 2026-09-21  8:57 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Thierry Reding, Jonathan Hunter,
	Sameer Pujar, Kuninori Morimoto, Mohan Kumar, linux-sound,
	linux-tegra, linux-kernel, Sheetal

Replace the separate integer and fractional ASRC ratio controls with a
single two-value control for each stream. This is a userspace-visible
control ABI change, but:

1. Keeping writable compatibility controls would preserve the non-atomic
   update path that can expose a transient mixed ratio to hardware.
2. This is limited to only the Tegra ASRC driver.

Validate both values before programming hardware. The fractional field
keeps the existing 32-bit maximum, while the integer field is rejected
when it exceeds the hardware field width.

ALSA exposes one min and max range for all values in an integer-array
control, so the paired ratio controls advertise the fractional field's
full 32-bit range. The put callback validates the integer part separately
against the hardware field width.

Serialize ratio-source changes, stream setup, and ratio updates with a
driver mutex so cached ratio state and paired hardware programming remain
consistent.

Unlock and re-lock the ASRC stream before programming a software ratio
when the new ratio differs from the cached ratio by more than 20%.
Large ratio jumps require the hardware lock to be refreshed around the
paired integer/fractional update.

Roll back the previous hardware pair and lock state if a paired ratio
update fails after partially programming the hardware.

Signed-off-by: Sheetal <sheetal@nvidia.com>
---
 sound/soc/tegra/tegra186_asrc.c | 443 ++++++++++++++++++++------------
 sound/soc/tegra/tegra186_asrc.h |   4 +
 2 files changed, 276 insertions(+), 171 deletions(-)

diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c
index d8ae5d997615..2b67d23f5fa7 100644
--- a/sound/soc/tegra/tegra186_asrc.c
+++ b/sound/soc/tegra/tegra186_asrc.c
@@ -19,6 +19,9 @@
 #include "tegra186_asrc.h"
 #include "tegra_cif.h"
 
+#define TEGRA186_ASRC_RATIO_PERCENT_SCALE		100
+#define TEGRA186_ASRC_RATIO_UNLOCK_THRESHOLD_PERCENT	20
+
 #define ASRC_STREAM_SOURCE_SELECT(id)					       \
 	(TEGRA186_ASRC_CFG + ((id) * TEGRA186_ASRC_STREAM_STRIDE))
 
@@ -65,13 +68,125 @@ static const struct reg_default tegra186_asrc_reg_defaults[] = {
 	{ TEGRA186_ASRC_CYA, 0x0},
 };
 
-static void tegra186_asrc_lock_stream(struct tegra186_asrc *asrc,
-				      unsigned int id)
+static int tegra186_asrc_set_stream_lock(struct tegra186_asrc *asrc,
+					 unsigned int id, bool lock)
 {
-	regmap_write(asrc->regmap,
-		     ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS,
-				     id),
-		     1);
+	return regmap_write(asrc->regmap,
+			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS,
+					    id),
+			    lock);
+}
+
+static int tegra186_asrc_write_ratio_pair(struct tegra186_asrc *asrc,
+					  unsigned int id,
+					  unsigned int int_part,
+					  unsigned int frac_part)
+{
+	int ret;
+
+	ret = regmap_write(asrc->regmap,
+			   ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id),
+			   int_part);
+	if (ret)
+		return ret;
+
+	return regmap_write(asrc->regmap,
+			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id),
+			    frac_part);
+}
+
+static int tegra186_asrc_read_ratio_pair(struct tegra186_asrc *asrc,
+					 unsigned int id, unsigned int *int_part,
+					 unsigned int *frac_part)
+{
+	int ret;
+
+	ret = regmap_read(asrc->regmap,
+			  ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id),
+			  int_part);
+	if (ret)
+		return ret;
+
+	return regmap_read(asrc->regmap,
+			   ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id),
+			   frac_part);
+}
+
+static void tegra186_asrc_cache_ratio(struct tegra186_asrc *asrc,
+				      unsigned int id, unsigned int int_part,
+				      unsigned int frac_part)
+{
+	asrc->lane[id].int_part = int_part;
+	asrc->lane[id].frac_part = frac_part;
+}
+
+static int tegra186_asrc_apply_ratio(struct tegra186_asrc *asrc,
+				     unsigned int id, unsigned int int_part,
+				     unsigned int frac_part, bool unlock)
+{
+	unsigned int old_int_part, old_frac_part, old_lock;
+	int ret, restore_ret;
+
+	ret = tegra186_asrc_read_ratio_pair(asrc, id, &old_int_part,
+					    &old_frac_part);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(asrc->regmap,
+			  ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS, id),
+			  &old_lock);
+	if (ret)
+		return ret;
+
+	if (unlock) {
+		ret = tegra186_asrc_set_stream_lock(asrc, id, false);
+		if (ret)
+			return ret;
+	}
+
+	ret = tegra186_asrc_write_ratio_pair(asrc, id, int_part, frac_part);
+	if (ret)
+		goto restore_ratio;
+
+	ret = tegra186_asrc_set_stream_lock(asrc, id, true);
+	if (!ret)
+		return 0;
+
+restore_ratio:
+	restore_ret = tegra186_asrc_write_ratio_pair(asrc, id,
+						     old_int_part,
+						     old_frac_part);
+	if (!restore_ret) {
+		if (old_lock)
+			restore_ret = tegra186_asrc_set_stream_lock(asrc, id,
+								    true);
+		else
+			restore_ret = tegra186_asrc_set_stream_lock(asrc, id,
+								    false);
+	}
+
+	return ret ?: restore_ret;
+}
+
+static int tegra186_asrc_apply_cached_ratio(struct tegra186_asrc *asrc,
+					    unsigned int id)
+{
+	return tegra186_asrc_apply_ratio(asrc, id, asrc->lane[id].int_part,
+					asrc->lane[id].frac_part, false);
+}
+
+static bool tegra186_asrc_need_unlock(u64 old_ratio, u64 new_ratio)
+{
+	u64 ratio_diff;
+
+	if (!old_ratio || !new_ratio)
+		return false;
+
+	ratio_diff = (old_ratio > new_ratio) ?
+		     (old_ratio - new_ratio) : (new_ratio - old_ratio);
+
+	return ((ratio_diff * TEGRA186_ASRC_RATIO_PERCENT_SCALE) >
+		(old_ratio * TEGRA186_ASRC_RATIO_UNLOCK_THRESHOLD_PERCENT));
 }
 
 static int tegra186_asrc_runtime_suspend(struct device *dev)
@@ -87,7 +202,7 @@ static int tegra186_asrc_runtime_suspend(struct device *dev)
 static int tegra186_asrc_runtime_resume(struct device *dev)
 {
 	struct tegra186_asrc *asrc = dev_get_drvdata(dev);
-	int id;
+	int id, ret, first_err = 0;
 
 	regcache_cache_only(asrc->regmap, false);
 
@@ -103,25 +218,23 @@ static int tegra186_asrc_runtime_resume(struct device *dev)
 
 	regcache_sync(asrc->regmap);
 
+	mutex_lock(&asrc->ratio_lock);
 	for (id = 0; id < TEGRA186_ASRC_STREAM_MAX; id++) {
 		if (asrc->lane[id].ratio_source !=
 		    TEGRA186_ASRC_RATIO_SOURCE_SW)
 			continue;
 
-		regmap_write(asrc->regmap,
-			ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART,
-					id),
-			asrc->lane[id].int_part);
-
-		regmap_write(asrc->regmap,
-			ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART,
-					id),
-			asrc->lane[id].frac_part);
-
-		tegra186_asrc_lock_stream(asrc, id);
+		ret = tegra186_asrc_apply_cached_ratio(asrc, id);
+		if (ret) {
+			dev_err(dev, "Failed to restore ratio for lane %d: %d\n",
+				id, ret);
+			if (!first_err)
+				first_err = ret;
+		}
 	}
+	mutex_unlock(&asrc->ratio_lock);
 
-	return 0;
+	return first_err;
 }
 
 static int tegra186_asrc_set_audio_cif(struct tegra186_asrc *asrc,
@@ -186,7 +299,7 @@ static int tegra186_asrc_out_hw_params(struct snd_pcm_substream *substream,
 {
 	struct device *dev = dai->dev;
 	struct tegra186_asrc *asrc = snd_soc_dai_get_drvdata(dai);
-	int ret, id = dai->id - 7;
+	int ret = 0, id = dai->id - 7;
 
 	 /* Set output threshold */
 	regmap_write(asrc->regmap,
@@ -217,20 +330,22 @@ static int tegra186_asrc_out_hw_params(struct snd_pcm_substream *substream,
 			TEGRA186_ASRC_STREAM_DEFAULT_HW_COMP_BIAS_VALUE);
 	}
 
+	mutex_lock(&asrc->ratio_lock);
+
 	/* Set lock */
 	regmap_update_bits(asrc->regmap,
 			   ASRC_STREAM_REG(TEGRA186_ASRC_CFG, id),
 			   1, asrc->lane[id].ratio_source);
 
 	if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_SW) {
-		regmap_write(asrc->regmap,
-			ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id),
-			asrc->lane[id].int_part);
-		regmap_write(asrc->regmap,
-			ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id),
-			asrc->lane[id].frac_part);
-		tegra186_asrc_lock_stream(asrc, id);
+		ret = tegra186_asrc_apply_cached_ratio(asrc, id);
+		if (ret)
+			dev_err(dev, "Can't apply cached ratio for ASRC stream %d: %d\n",
+				id, ret);
 	}
+	mutex_unlock(&asrc->ratio_lock);
+	if (ret)
+		return ret;
 
 	return ret;
 }
@@ -257,112 +372,132 @@ static int tegra186_asrc_put_ratio_source(struct snd_kcontrol *kcontrol,
 	struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
 	struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt);
 	unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE;
+	unsigned int new_source = ucontrol->value.enumerated.item[0];
+	unsigned int old_source, int_part = 0, frac_part = 0;
 	bool change = false;
+	int ret;
 
-	asrc->lane[id].ratio_source = ucontrol->value.enumerated.item[0];
+	mutex_lock(&asrc->ratio_lock);
 
-	regmap_update_bits_check(asrc->regmap, asrc_private->reg,
-				 TEGRA186_ASRC_STREAM_RATIO_TYPE_MASK,
-				 asrc->lane[id].ratio_source,
-				 &change);
+	old_source = asrc->lane[id].ratio_source;
 
-	return change ? 1 : 0;
-}
+	if (old_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD &&
+	    new_source == TEGRA186_ASRC_RATIO_SOURCE_SW) {
+		ret = tegra186_asrc_read_ratio_pair(asrc, id, &int_part,
+						    &frac_part);
+		if (ret) {
+			mutex_unlock(&asrc->ratio_lock);
+			return ret;
+		}
+	}
 
-static int tegra186_asrc_get_ratio_int(struct snd_kcontrol *kcontrol,
-				       struct snd_ctl_elem_value *ucontrol)
-{
-	struct soc_mixer_control *asrc_private =
-		(struct soc_mixer_control *)kcontrol->private_value;
-	struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
-	struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt);
-	unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE;
+	ret = regmap_update_bits_check(asrc->regmap, asrc_private->reg,
+				       TEGRA186_ASRC_STREAM_RATIO_TYPE_MASK,
+				       new_source, &change);
+	if (ret) {
+		mutex_unlock(&asrc->ratio_lock);
+		return ret;
+	}
 
-	regmap_read(asrc->regmap,
-		    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id),
-		    &asrc->lane[id].int_part);
+	asrc->lane[id].ratio_source = new_source;
+	if (old_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD &&
+	    new_source == TEGRA186_ASRC_RATIO_SOURCE_SW)
+		tegra186_asrc_cache_ratio(asrc, id, int_part, frac_part);
 
-	ucontrol->value.integer.value[0] = asrc->lane[id].int_part;
+	mutex_unlock(&asrc->ratio_lock);
 
-	return 0;
+	return change ? 1 : 0;
 }
 
-static int tegra186_asrc_put_ratio_int(struct snd_kcontrol *kcontrol,
-				       struct snd_ctl_elem_value *ucontrol)
+static int tegra186_asrc_get_ratio(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
 {
-	struct soc_mixer_control *asrc_private =
-		(struct soc_mixer_control *)kcontrol->private_value;
+	struct soc_mreg_control *asrc_private =
+		(struct soc_mreg_control *)kcontrol->private_value;
 	struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
 	struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt);
-	unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE;
-	bool change = false;
+	unsigned int id = asrc_private->regbase;
+	unsigned int int_part, frac_part;
+	int ret;
+
+	mutex_lock(&asrc->ratio_lock);
 
 	if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD) {
-		dev_err(cmpnt->dev,
-			"Lane %d ratio source is ARAD, invalid SW update\n",
-			id);
-		return -EINVAL;
+		ret = tegra186_asrc_read_ratio_pair(asrc, id, &int_part,
+						    &frac_part);
+	} else {
+		int_part = asrc->lane[id].int_part;
+		frac_part = asrc->lane[id].frac_part;
+		ret = 0;
 	}
 
-	asrc->lane[id].int_part = ucontrol->value.integer.value[0];
-
-	regmap_update_bits_check(asrc->regmap,
-				 ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART,
-						 id),
-				 TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK,
-				 asrc->lane[id].int_part, &change);
+	mutex_unlock(&asrc->ratio_lock);
+	if (ret)
+		return ret;
 
-	tegra186_asrc_lock_stream(asrc, id);
+	ucontrol->value.integer.value[0] = int_part;
+	ucontrol->value.integer.value[1] = frac_part;
 
-	return change ? 1 : 0;
+	return 0;
 }
 
-static int tegra186_asrc_get_ratio_frac(struct snd_kcontrol *kcontrol,
-					struct snd_ctl_elem_value *ucontrol)
+static int tegra186_asrc_put_ratio(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
 {
 	struct soc_mreg_control *asrc_private =
 		(struct soc_mreg_control *)kcontrol->private_value;
 	struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
 	struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt);
-	unsigned int id = asrc_private->regbase / TEGRA186_ASRC_STREAM_STRIDE;
-
-	regmap_read(asrc->regmap,
-		    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id),
-		    &asrc->lane[id].frac_part);
+	unsigned int id = asrc_private->regbase;
+	unsigned int int_part, frac_part;
+	u64 old_ratio, new_ratio;
+	long int_val = ucontrol->value.integer.value[0];
+	long frac_val = ucontrol->value.integer.value[1];
+	bool change = false, unlock = false;
+	int ret = 0;
+
+	if (int_val < 0 || int_val > TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK)
+		return -EINVAL;
 
-	ucontrol->value.integer.value[0] = asrc->lane[id].frac_part;
+	if (frac_val < 0 || frac_val > TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK)
+		return -EINVAL;
 
-	return 0;
-}
+	int_part = int_val;
+	frac_part = frac_val;
 
-static int tegra186_asrc_put_ratio_frac(struct snd_kcontrol *kcontrol,
-					struct snd_ctl_elem_value *ucontrol)
-{
-	struct soc_mreg_control *asrc_private =
-		(struct soc_mreg_control *)kcontrol->private_value;
-	struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
-	struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt);
-	unsigned int id = asrc_private->regbase / TEGRA186_ASRC_STREAM_STRIDE;
-	bool change = false;
+	mutex_lock(&asrc->ratio_lock);
 
 	if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD) {
 		dev_err(cmpnt->dev,
 			"Lane %d ratio source is ARAD, invalid SW update\n",
 			id);
-		return -EINVAL;
+		ret = -EINVAL;
+		goto out_unlock;
 	}
 
-	asrc->lane[id].frac_part = ucontrol->value.integer.value[0];
+	old_ratio = ((u64)asrc->lane[id].int_part << 32) |
+		    asrc->lane[id].frac_part;
+	new_ratio = ((u64)int_part << 32) | frac_part;
+
+	if (!new_ratio) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
 
-	regmap_update_bits_check(asrc->regmap,
-				 ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART,
-						 id),
-				 TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-				 asrc->lane[id].frac_part, &change);
+	change = old_ratio != new_ratio;
+	if (!change)
+		goto out_unlock;
 
-	tegra186_asrc_lock_stream(asrc, id);
+	unlock = tegra186_asrc_need_unlock(old_ratio, new_ratio);
 
-	return change ? 1 : 0;
+	ret = tegra186_asrc_apply_ratio(asrc, id, int_part, frac_part, unlock);
+	if (!ret)
+		tegra186_asrc_cache_ratio(asrc, id, int_part, frac_part);
+
+out_unlock:
+	mutex_unlock(&asrc->ratio_lock);
+
+	return ret ?: change;
 }
 
 static int tegra186_asrc_get_hwcomp_disable(struct snd_kcontrol *kcontrol,
@@ -651,99 +786,63 @@ ASRC_SOURCE_DECL(src_select4, 3);
 ASRC_SOURCE_DECL(src_select5, 4);
 ASRC_SOURCE_DECL(src_select6, 5);
 
-#define SOC_SINGLE_EXT_FRAC(xname, xregbase, xmax, xget, xput)		\
+static int tegra186_asrc_ratio_param_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_mreg_control *mc =
+		(struct soc_mreg_control *)kcontrol->private_value;
+
+	/*
+	 * ALSA exposes one range for all values in an integer array. The
+	 * fractional part needs the full 32-bit range; the integer part is
+	 * validated against the hardware field width in the put callback.
+	 */
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = mc->min;
+	uinfo->value.integer.max = mc->max;
+
+	return 0;
+}
+
+#define SOC_SINGLE_EXT_ARRAY(xname, xregbase, xmax, xget, xput)		\
 {									\
 	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,				\
 	.name	= (xname),						\
-	.info	= snd_soc_info_xr_sx,					\
+	.info	= tegra186_asrc_ratio_param_info,			\
 	.get	= xget,							\
 	.put	= xput,							\
 									\
 	.private_value = (unsigned long)&(struct soc_mreg_control)	\
 	{								\
 		.regbase	= xregbase,				\
-		.regcount	= 1,					\
+		.regcount	= 2,					\
 		.nbits		= 32,					\
 		.invert		= 0,					\
 		.min		= 0,					\
-		.max		= xmax					\
+		.max		= (xmax)				\
 	}								\
 }
 
 static const struct snd_kcontrol_new tegra186_asrc_controls[] = {
-	/* Controls for integer part of ratio */
-	SOC_SINGLE_EXT("Ratio1 Integer Part",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 0),
-		       0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0,
-		       tegra186_asrc_get_ratio_int,
-		       tegra186_asrc_put_ratio_int),
-
-	SOC_SINGLE_EXT("Ratio2 Integer Part",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 1),
-		       0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0,
-		       tegra186_asrc_get_ratio_int,
-		       tegra186_asrc_put_ratio_int),
-
-	SOC_SINGLE_EXT("Ratio3 Integer Part",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 2),
-		       0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0,
-		       tegra186_asrc_get_ratio_int,
-		       tegra186_asrc_put_ratio_int),
-
-	SOC_SINGLE_EXT("Ratio4 Integer Part",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 3),
-		       0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0,
-		       tegra186_asrc_get_ratio_int,
-		       tegra186_asrc_put_ratio_int),
-
-	SOC_SINGLE_EXT("Ratio5 Integer Part",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 4),
-		       0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0,
-		       tegra186_asrc_get_ratio_int,
-		       tegra186_asrc_put_ratio_int),
-
-	SOC_SINGLE_EXT("Ratio6 Integer Part",
-		       ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 5),
-		       0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0,
-		       tegra186_asrc_get_ratio_int,
-		       tegra186_asrc_put_ratio_int),
-
-	/* Controls for fractional part of ratio */
-	SOC_SINGLE_EXT_FRAC("Ratio1 Fractional Part",
-			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 0),
-			    TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-			    tegra186_asrc_get_ratio_frac,
-			    tegra186_asrc_put_ratio_frac),
-
-	SOC_SINGLE_EXT_FRAC("Ratio2 Fractional Part",
-			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 1),
-			    TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-			    tegra186_asrc_get_ratio_frac,
-			    tegra186_asrc_put_ratio_frac),
-
-	SOC_SINGLE_EXT_FRAC("Ratio3 Fractional Part",
-			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 2),
-			    TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-			    tegra186_asrc_get_ratio_frac,
-			    tegra186_asrc_put_ratio_frac),
-
-	SOC_SINGLE_EXT_FRAC("Ratio4 Fractional Part",
-			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 3),
-			    TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-			    tegra186_asrc_get_ratio_frac,
-			    tegra186_asrc_put_ratio_frac),
-
-	SOC_SINGLE_EXT_FRAC("Ratio5 Fractional Part",
-			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 4),
-			    TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-			    tegra186_asrc_get_ratio_frac,
-			    tegra186_asrc_put_ratio_frac),
-
-	SOC_SINGLE_EXT_FRAC("Ratio6 Fractional Part",
-			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 5),
-			    TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
-			    tegra186_asrc_get_ratio_frac,
-			    tegra186_asrc_put_ratio_frac),
+	/* Controls for ASRC */
+	SOC_SINGLE_EXT_ARRAY("Ratio1", 0, TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
+			     tegra186_asrc_get_ratio, tegra186_asrc_put_ratio),
+
+	SOC_SINGLE_EXT_ARRAY("Ratio2", 1, TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
+			     tegra186_asrc_get_ratio, tegra186_asrc_put_ratio),
+
+	SOC_SINGLE_EXT_ARRAY("Ratio3", 2, TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
+			     tegra186_asrc_get_ratio, tegra186_asrc_put_ratio),
+
+	SOC_SINGLE_EXT_ARRAY("Ratio4", 3, TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
+			     tegra186_asrc_get_ratio, tegra186_asrc_put_ratio),
+
+	SOC_SINGLE_EXT_ARRAY("Ratio5", 4, TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
+			     tegra186_asrc_get_ratio, tegra186_asrc_put_ratio),
+
+	SOC_SINGLE_EXT_ARRAY("Ratio6", 5, TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK,
+			     tegra186_asrc_get_ratio, tegra186_asrc_put_ratio),
 
 	/* Source of ratio provider */
 	SOC_ENUM_EXT("Ratio1 Source", src_select1,
@@ -1011,6 +1110,8 @@ static int tegra186_asrc_platform_probe(struct platform_device *pdev)
 			TEGRA186_ASRC_STREAM_DEFAULT_OUTPUT_HW_COMP_THRESH_CFG;
 	}
 
+	mutex_init(&asrc->ratio_lock);
+
 	err = devm_snd_soc_register_component(dev, &tegra186_asrc_cmpnt,
 					      tegra186_asrc_dais,
 					      ARRAY_SIZE(tegra186_asrc_dais));
diff --git a/sound/soc/tegra/tegra186_asrc.h b/sound/soc/tegra/tegra186_asrc.h
index 0c98e26d5e72..9cc8cec6c204 100644
--- a/sound/soc/tegra/tegra186_asrc.h
+++ b/sound/soc/tegra/tegra186_asrc.h
@@ -7,6 +7,8 @@
 #ifndef __TEGRA186_ASRC_H__
 #define __TEGRA186_ASRC_H__
 
+#include <linux/mutex.h>
+
 /* ASRC stream related offset */
 #define TEGRA186_ASRC_CFG				0x0
 #define TEGRA186_ASRC_RATIO_INT_PART			0x4
@@ -111,6 +113,8 @@ struct tegra186_asrc {
 	const struct tegra_asrc_soc_data *soc_data;
 	struct tegra186_asrc_lane lane[TEGRA186_ASRC_STREAM_MAX];
 	struct regmap *regmap;
+	/* Serializes ratio source, cache, and paired register updates. */
+	struct mutex ratio_lock;
 };
 
 #endif
-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC PATCH 3/3] ASoC: tegra: Cache ASRC ratios until streams are active
  2026-09-21  8:57 [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Sheetal
  2026-09-21  8:57 ` [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control Sheetal
  2026-09-21  8:57 ` [RFC PATCH 2/3] ASoC: tegra: Update ASRC ratio controls Sheetal
@ 2026-09-21  8:57 ` Sheetal
  2026-09-21 12:07 ` [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Thierry Reding
  3 siblings, 0 replies; 8+ messages in thread
From: Sheetal @ 2026-09-21  8:57 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Thierry Reding, Jonathan Hunter,
	Sameer Pujar, Kuninori Morimoto, Mohan Kumar, linux-sound,
	linux-tegra, linux-kernel, Sheetal

Cache software ratio control updates when the ASRC device is runtime
suspended or when the target stream is not active. Program the cached
ratio when the stream is configured, and restore it after runtime resume.

Take a runtime PM reference for ratio-source changes when the ASRC device
is runtime-active, even if the target stream is not active, so a
hardware-to-software source transition can safely capture the live ratio
pair and update the source-select register. If the ASRC device is
suspended, avoid waking it from a control update, update the cached
source state, and mark the cached ratio invalid so the
first software apply refreshes the ratio lock state.

This also avoids unsafe volatile-register access during suspended control
updates. For hardware-sourced ratios, return the cached pair when the
device is not runtime active instead of waking it only for a mixer read.

Return an error from runtime resume if restoring a cached ratio fails.
This lets the PM core see that resume did not fully restore the stream
state instead of leaving the hardware running with stale ratio registers.

Signed-off-by: Sheetal <sheetal@nvidia.com>
---
 sound/soc/tegra/tegra186_asrc.c | 106 ++++++++++++++++++++++++++------
 sound/soc/tegra/tegra186_asrc.h |   1 +
 2 files changed, 89 insertions(+), 18 deletions(-)

diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c
index 2b67d23f5fa7..389bc1a294e1 100644
--- a/sound/soc/tegra/tegra186_asrc.c
+++ b/sound/soc/tegra/tegra186_asrc.c
@@ -118,6 +118,7 @@ static void tegra186_asrc_cache_ratio(struct tegra186_asrc *asrc,
 {
 	asrc->lane[id].int_part = int_part;
 	asrc->lane[id].frac_part = frac_part;
+	asrc->lane[id].ratio_valid = true;
 }
 
 static int tegra186_asrc_apply_ratio(struct tegra186_asrc *asrc,
@@ -171,8 +172,15 @@ static int tegra186_asrc_apply_ratio(struct tegra186_asrc *asrc,
 static int tegra186_asrc_apply_cached_ratio(struct tegra186_asrc *asrc,
 					    unsigned int id)
 {
-	return tegra186_asrc_apply_ratio(asrc, id, asrc->lane[id].int_part,
-					asrc->lane[id].frac_part, false);
+	int ret;
+
+	ret = tegra186_asrc_apply_ratio(asrc, id, asrc->lane[id].int_part,
+					asrc->lane[id].frac_part,
+					!asrc->lane[id].ratio_valid);
+	if (!ret)
+		asrc->lane[id].ratio_valid = true;
+
+	return ret;
 }
 
 static bool tegra186_asrc_need_unlock(u64 old_ratio, u64 new_ratio)
@@ -375,19 +383,32 @@ static int tegra186_asrc_put_ratio_source(struct snd_kcontrol *kcontrol,
 	unsigned int new_source = ucontrol->value.enumerated.item[0];
 	unsigned int old_source, int_part = 0, frac_part = 0;
 	bool change = false;
-	int ret;
+	int pm_ret, ret;
 
 	mutex_lock(&asrc->ratio_lock);
 
 	old_source = asrc->lane[id].ratio_source;
 
+	pm_ret = pm_runtime_get_if_active(cmpnt->dev);
+	if (pm_ret < 0) {
+		mutex_unlock(&asrc->ratio_lock);
+		return pm_ret;
+	}
+
 	if (old_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD &&
 	    new_source == TEGRA186_ASRC_RATIO_SOURCE_SW) {
-		ret = tegra186_asrc_read_ratio_pair(asrc, id, &int_part,
-						    &frac_part);
-		if (ret) {
-			mutex_unlock(&asrc->ratio_lock);
-			return ret;
+		if (pm_ret > 0) {
+			ret = tegra186_asrc_read_ratio_pair(asrc, id,
+							    &int_part,
+							    &frac_part);
+			if (ret) {
+				pm_runtime_put(cmpnt->dev);
+				asrc->lane[id].ratio_valid = false;
+				mutex_unlock(&asrc->ratio_lock);
+				return ret;
+			}
+		} else {
+			asrc->lane[id].ratio_valid = false;
 		}
 	}
 
@@ -395,15 +416,20 @@ static int tegra186_asrc_put_ratio_source(struct snd_kcontrol *kcontrol,
 				       TEGRA186_ASRC_STREAM_RATIO_TYPE_MASK,
 				       new_source, &change);
 	if (ret) {
+		if (pm_ret > 0)
+			pm_runtime_put(cmpnt->dev);
 		mutex_unlock(&asrc->ratio_lock);
 		return ret;
 	}
 
 	asrc->lane[id].ratio_source = new_source;
 	if (old_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD &&
-	    new_source == TEGRA186_ASRC_RATIO_SOURCE_SW)
+	    new_source == TEGRA186_ASRC_RATIO_SOURCE_SW && pm_ret > 0)
 		tegra186_asrc_cache_ratio(asrc, id, int_part, frac_part);
 
+	if (pm_ret > 0)
+		pm_runtime_put(cmpnt->dev);
+
 	mutex_unlock(&asrc->ratio_lock);
 
 	return change ? 1 : 0;
@@ -422,18 +448,33 @@ static int tegra186_asrc_get_ratio(struct snd_kcontrol *kcontrol,
 
 	mutex_lock(&asrc->ratio_lock);
 
-	if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD) {
+	int_part = asrc->lane[id].int_part;
+	frac_part = asrc->lane[id].frac_part;
+
+	if (asrc->lane[id].ratio_source != TEGRA186_ASRC_RATIO_SOURCE_ARAD)
+		goto done;
+
+	ret = pm_runtime_get_if_active(cmpnt->dev);
+	if (ret < 0) {
+		mutex_unlock(&asrc->ratio_lock);
+		return ret;
+	}
+
+	if (ret > 0) {
 		ret = tegra186_asrc_read_ratio_pair(asrc, id, &int_part,
 						    &frac_part);
-	} else {
-		int_part = asrc->lane[id].int_part;
-		frac_part = asrc->lane[id].frac_part;
-		ret = 0;
+		if (ret) {
+			pm_runtime_put(cmpnt->dev);
+			mutex_unlock(&asrc->ratio_lock);
+			return ret;
+		}
+		pm_runtime_put(cmpnt->dev);
+		asrc->lane[id].int_part = int_part;
+		asrc->lane[id].frac_part = frac_part;
 	}
 
+done:
 	mutex_unlock(&asrc->ratio_lock);
-	if (ret)
-		return ret;
 
 	ucontrol->value.integer.value[0] = int_part;
 	ucontrol->value.integer.value[1] = frac_part;
@@ -454,7 +495,7 @@ static int tegra186_asrc_put_ratio(struct snd_kcontrol *kcontrol,
 	long int_val = ucontrol->value.integer.value[0];
 	long frac_val = ucontrol->value.integer.value[1];
 	bool change = false, unlock = false;
-	int ret = 0;
+	int ret = 0, pm_ret;
 
 	if (int_val < 0 || int_val > TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK)
 		return -EINVAL;
@@ -488,12 +529,40 @@ static int tegra186_asrc_put_ratio(struct snd_kcontrol *kcontrol,
 	if (!change)
 		goto out_unlock;
 
-	unlock = tegra186_asrc_need_unlock(old_ratio, new_ratio);
+	unlock = !asrc->lane[id].ratio_valid ||
+		 tegra186_asrc_need_unlock(old_ratio, new_ratio);
+
+	pm_ret = pm_runtime_get_if_active(cmpnt->dev);
+	if (pm_ret < 0) {
+		ret = pm_ret;
+		goto out_unlock;
+	}
+
+	if (!pm_ret) {
+		asrc->lane[id].int_part = int_part;
+		asrc->lane[id].frac_part = frac_part;
+		ret = 1;
+		goto out_unlock;
+	}
+
+	ret = regmap_read(asrc->regmap, ASRC_STREAM_REG(TEGRA186_ASRC_STATUS, id),
+			  &pm_ret);
+	if (ret)
+		goto out_pm_put;
+
+	if (!(pm_ret & TEGRA186_ASRC_STREAM_EN)) {
+		asrc->lane[id].int_part = int_part;
+		asrc->lane[id].frac_part = frac_part;
+		ret = 1;
+		goto out_pm_put;
+	}
 
 	ret = tegra186_asrc_apply_ratio(asrc, id, int_part, frac_part, unlock);
 	if (!ret)
 		tegra186_asrc_cache_ratio(asrc, id, int_part, frac_part);
 
+out_pm_put:
+	pm_runtime_put(cmpnt->dev);
 out_unlock:
 	mutex_unlock(&asrc->ratio_lock);
 
@@ -1103,6 +1172,7 @@ static int tegra186_asrc_platform_probe(struct platform_device *pdev)
 		asrc->lane[i].ratio_source = TEGRA186_ASRC_RATIO_SOURCE_SW;
 		asrc->lane[i].int_part = 1;
 		asrc->lane[i].frac_part = 0;
+		asrc->lane[i].ratio_valid = true;
 		asrc->lane[i].hwcomp_disable = 0;
 		asrc->lane[i].input_thresh =
 			TEGRA186_ASRC_STREAM_DEFAULT_INPUT_HW_COMP_THRESH_CFG;
diff --git a/sound/soc/tegra/tegra186_asrc.h b/sound/soc/tegra/tegra186_asrc.h
index 9cc8cec6c204..dabfd2e2c904 100644
--- a/sound/soc/tegra/tegra186_asrc.h
+++ b/sound/soc/tegra/tegra186_asrc.h
@@ -99,6 +99,7 @@
 struct tegra186_asrc_lane {
 	unsigned int int_part;
 	unsigned int frac_part;
+	bool ratio_valid;
 	unsigned int ratio_source;
 	unsigned int hwcomp_disable;
 	unsigned int input_thresh;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control
  2026-09-21  8:57 ` [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control Sheetal
@ 2026-09-21 11:38   ` Thierry Reding
  0 siblings, 0 replies; 8+ messages in thread
From: Thierry Reding @ 2026-09-21 11:38 UTC (permalink / raw)
  To: Sheetal
  Cc: Liam Girdwood, Mark Brown, Jaroslav Kysela, Takashi Iwai,
	Jonathan Hunter, Sameer Pujar, Kuninori Morimoto, Mohan Kumar,
	linux-sound, linux-tegra, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 863 bytes --]

On Mon, Sep 21, 2026 at 08:57:02AM +0000, Sheetal wrote:
> The Stream6 Input Threshold control points at stream index 4, which is
> already used by Stream5. The threshold get and put callbacks derive the
> lane ID from the register offset, so the duplicate index makes Stream6
> operate on lane 4 and leaves lane 5 inaccessible from userspace.
> 
> Use stream index 5 for Stream6.
> 
> Fixes: a2df8c2d5b36 ("ASoC: tegra: Add Tegra186 based ASRC driver")
> Signed-off-by: Sheetal <sheetal@nvidia.com>
> ---
>  sound/soc/tegra/tegra186_asrc.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)

This should be a separate patch since it fixes an existing problem and
is not related to the rework in subsequent patches. It also shouldn't be
blocked on any discussion regarding the ABI change.

Reviewed-by: Thierry Reding <treding@nvidia.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 2/3] ASoC: tegra: Update ASRC ratio controls
  2026-09-21  8:57 ` [RFC PATCH 2/3] ASoC: tegra: Update ASRC ratio controls Sheetal
@ 2026-09-21 11:50   ` Thierry Reding
  0 siblings, 0 replies; 8+ messages in thread
From: Thierry Reding @ 2026-09-21 11:50 UTC (permalink / raw)
  To: Sheetal
  Cc: Liam Girdwood, Mark Brown, Jaroslav Kysela, Takashi Iwai,
	Jonathan Hunter, Sameer Pujar, Kuninori Morimoto, Mohan Kumar,
	linux-sound, linux-tegra, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 7440 bytes --]

On Mon, Sep 21, 2026 at 08:57:03AM +0000, Sheetal wrote:
> Replace the separate integer and fractional ASRC ratio controls with a
> single two-value control for each stream. This is a userspace-visible
> control ABI change, but:
> 
> 1. Keeping writable compatibility controls would preserve the non-atomic
>    update path that can expose a transient mixed ratio to hardware.
> 2. This is limited to only the Tegra ASRC driver.
> 
> Validate both values before programming hardware. The fractional field
> keeps the existing 32-bit maximum, while the integer field is rejected
> when it exceeds the hardware field width.
> 
> ALSA exposes one min and max range for all values in an integer-array
> control, so the paired ratio controls advertise the fractional field's
> full 32-bit range. The put callback validates the integer part separately
> against the hardware field width.
> 
> Serialize ratio-source changes, stream setup, and ratio updates with a
> driver mutex so cached ratio state and paired hardware programming remain
> consistent.
> 
> Unlock and re-lock the ASRC stream before programming a software ratio
> when the new ratio differs from the cached ratio by more than 20%.
> Large ratio jumps require the hardware lock to be refreshed around the
> paired integer/fractional update.
> 
> Roll back the previous hardware pair and lock state if a paired ratio
> update fails after partially programming the hardware.
> 
> Signed-off-by: Sheetal <sheetal@nvidia.com>
> ---
>  sound/soc/tegra/tegra186_asrc.c | 443 ++++++++++++++++++++------------
>  sound/soc/tegra/tegra186_asrc.h |   4 +
>  2 files changed, 276 insertions(+), 171 deletions(-)
> 
> diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c
> index d8ae5d997615..2b67d23f5fa7 100644
> --- a/sound/soc/tegra/tegra186_asrc.c
> +++ b/sound/soc/tegra/tegra186_asrc.c
> @@ -19,6 +19,9 @@
>  #include "tegra186_asrc.h"
>  #include "tegra_cif.h"
>  
> +#define TEGRA186_ASRC_RATIO_PERCENT_SCALE		100
> +#define TEGRA186_ASRC_RATIO_UNLOCK_THRESHOLD_PERCENT	20
> +
>  #define ASRC_STREAM_SOURCE_SELECT(id)					       \
>  	(TEGRA186_ASRC_CFG + ((id) * TEGRA186_ASRC_STREAM_STRIDE))
>  
> @@ -65,13 +68,125 @@ static const struct reg_default tegra186_asrc_reg_defaults[] = {
>  	{ TEGRA186_ASRC_CYA, 0x0},
>  };
>  
> -static void tegra186_asrc_lock_stream(struct tegra186_asrc *asrc,
> -				      unsigned int id)
> +static int tegra186_asrc_set_stream_lock(struct tegra186_asrc *asrc,
> +					 unsigned int id, bool lock)
>  {
> -	regmap_write(asrc->regmap,
> -		     ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS,
> -				     id),
> -		     1);
> +	return regmap_write(asrc->regmap,
> +			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS,
> +					    id),
> +			    lock);
> +}
> +
> +static int tegra186_asrc_write_ratio_pair(struct tegra186_asrc *asrc,
> +					  unsigned int id,
> +					  unsigned int int_part,
> +					  unsigned int frac_part)
> +{
> +	int ret;
> +
> +	ret = regmap_write(asrc->regmap,
> +			   ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id),
> +			   int_part);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(asrc->regmap,
> +			    ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id),
> +			    frac_part);
> +}
> +
> +static int tegra186_asrc_read_ratio_pair(struct tegra186_asrc *asrc,
> +					 unsigned int id, unsigned int *int_part,
> +					 unsigned int *frac_part)
> +{
> +	int ret;
> +
> +	ret = regmap_read(asrc->regmap,
> +			  ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id),
> +			  int_part);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_read(asrc->regmap,
> +			   ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id),
> +			   frac_part);
> +}
> +
> +static void tegra186_asrc_cache_ratio(struct tegra186_asrc *asrc,
> +				      unsigned int id, unsigned int int_part,
> +				      unsigned int frac_part)
> +{
> +	asrc->lane[id].int_part = int_part;
> +	asrc->lane[id].frac_part = frac_part;
> +}
> +
> +static int tegra186_asrc_apply_ratio(struct tegra186_asrc *asrc,
> +				     unsigned int id, unsigned int int_part,
> +				     unsigned int frac_part, bool unlock)
> +{
> +	unsigned int old_int_part, old_frac_part, old_lock;
> +	int ret, restore_ret;
> +
> +	ret = tegra186_asrc_read_ratio_pair(asrc, id, &old_int_part,
> +					    &old_frac_part);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(asrc->regmap,
> +			  ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS, id),
> +			  &old_lock);
> +	if (ret)
> +		return ret;
> +
> +	if (unlock) {
> +		ret = tegra186_asrc_set_stream_lock(asrc, id, false);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = tegra186_asrc_write_ratio_pair(asrc, id, int_part, frac_part);
> +	if (ret)
> +		goto restore_ratio;
> +
> +	ret = tegra186_asrc_set_stream_lock(asrc, id, true);
> +	if (!ret)
> +		return 0;
> +
> +restore_ratio:
> +	restore_ret = tegra186_asrc_write_ratio_pair(asrc, id,
> +						     old_int_part,
> +						     old_frac_part);
> +	if (!restore_ret) {
> +		if (old_lock)
> +			restore_ret = tegra186_asrc_set_stream_lock(asrc, id,
> +								    true);
> +		else
> +			restore_ret = tegra186_asrc_set_stream_lock(asrc, id,
> +								    false);
> +	}
> +
> +	return ret ?: restore_ret;
> +}
> +
> +static int tegra186_asrc_apply_cached_ratio(struct tegra186_asrc *asrc,
> +					    unsigned int id)
> +{
> +	return tegra186_asrc_apply_ratio(asrc, id, asrc->lane[id].int_part,
> +					asrc->lane[id].frac_part, false);
> +}
> +
> +static bool tegra186_asrc_need_unlock(u64 old_ratio, u64 new_ratio)
> +{
> +	u64 ratio_diff;
> +
> +	if (!old_ratio || !new_ratio)
> +		return false;
> +
> +	ratio_diff = (old_ratio > new_ratio) ?
> +		     (old_ratio - new_ratio) : (new_ratio - old_ratio);
> +
> +	return ((ratio_diff * TEGRA186_ASRC_RATIO_PERCENT_SCALE) >
> +		(old_ratio * TEGRA186_ASRC_RATIO_UNLOCK_THRESHOLD_PERCENT));
>  }

There's a fair bit of complication introduced by the concept of locking
here, so I wonder what the impact of this is. The function name implies
that sometimes unlocking is needed and sometimes it isn't.

What if we unlock unconditionally? That doesn't get rid of all the
locking and unlocking, but at least it would avoid the need to track the
current locking status.

The unlock operation doesn't seem overly expensive, so maybe it isn't
worth trying to avoid it?

[...]
> diff --git a/sound/soc/tegra/tegra186_asrc.h b/sound/soc/tegra/tegra186_asrc.h
> index 0c98e26d5e72..9cc8cec6c204 100644
> --- a/sound/soc/tegra/tegra186_asrc.h
> +++ b/sound/soc/tegra/tegra186_asrc.h
> @@ -7,6 +7,8 @@
>  #ifndef __TEGRA186_ASRC_H__
>  #define __TEGRA186_ASRC_H__
>  
> +#include <linux/mutex.h>
> +
>  /* ASRC stream related offset */
>  #define TEGRA186_ASRC_CFG				0x0
>  #define TEGRA186_ASRC_RATIO_INT_PART			0x4
> @@ -111,6 +113,8 @@ struct tegra186_asrc {
>  	const struct tegra_asrc_soc_data *soc_data;
>  	struct tegra186_asrc_lane lane[TEGRA186_ASRC_STREAM_MAX];
>  	struct regmap *regmap;
> +	/* Serializes ratio source, cache, and paired register updates. */
> +	struct mutex ratio_lock;

All of the operations that are serialized by this lock seem to be non-
sleeping register or cache updates. Could the lock be turned into a
spinlock instead?

Thierry

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching
  2026-09-21  8:57 [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Sheetal
                   ` (2 preceding siblings ...)
  2026-09-21  8:57 ` [RFC PATCH 3/3] ASoC: tegra: Cache ASRC ratios until streams are active Sheetal
@ 2026-09-21 12:07 ` Thierry Reding
  2026-09-21 12:51   ` Mark Brown
  3 siblings, 1 reply; 8+ messages in thread
From: Thierry Reding @ 2026-09-21 12:07 UTC (permalink / raw)
  To: Sheetal
  Cc: Liam Girdwood, Mark Brown, Jaroslav Kysela, Takashi Iwai,
	Jonathan Hunter, Sameer Pujar, Kuninori Morimoto, Mohan Kumar,
	linux-sound, linux-tegra, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1870 bytes --]

On Mon, Sep 21, 2026 at 08:57:01AM +0000, Sheetal wrote:
> This RFC series updates the Tegra186 ASRC driver control handling.
> 
> The main RFC point is the userspace-visible ALSA mixer control ABI
> change in patch 2. The existing per-stream controls expose the software
> ratio as separate integer and fractional mixer controls. This series
> replaces each pair with one two-value mixer control so userspace updates
> the integer and fractional parts together. That avoids programming a
> transient mixed ratio to hardware, but it does require userspace that
> sets ASRC software ratios to switch to the new paired controls.

Given the ABI change, do we have a good understanding of who might be
using these features? My understanding is that these are used for the
setup of hardware pipelines involving sample rate conversions. So these
are likely not your regular users that just want to change the volume
for headphones and such. This is for highly specific use-cases. Do we
know if people use extra userspace software on top of ALSA to set these
up, or are they typically dealing with fixed configuration files for
specifying these values?

So essentially to be able to better understand how to proceed it'd be
good to know what the upgrade path is for this. How easy or difficult
will it be for people to transition to this? Can we transition them
automatically by keeping write-only backwards-compatibility controls
that mirror the old ABI and maybe provide a hint that people should be
upgrading to the new controls?

Since we expect this to also fix the occasional glitch caused by
mismatched ratios, I think we really want this, but we need to make sure
users aren't left in the dark about such changes and have a reasonable
upgrade path.

Mark, Liam, do you have any guidelines on how to handle such ABI
changes?

Thierry

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching
  2026-09-21 12:07 ` [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Thierry Reding
@ 2026-09-21 12:51   ` Mark Brown
  0 siblings, 0 replies; 8+ messages in thread
From: Mark Brown @ 2026-09-21 12:51 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Sheetal, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
	Jonathan Hunter, Sameer Pujar, Kuninori Morimoto, Mohan Kumar,
	linux-sound, linux-tegra, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1122 bytes --]

On Mon, Sep 21, 2026 at 02:07:19PM +0200, Thierry Reding wrote:

> Given the ABI change, do we have a good understanding of who might be
> using these features? My understanding is that these are used for the
> setup of hardware pipelines involving sample rate conversions. So these
> are likely not your regular users that just want to change the volume
> for headphones and such. This is for highly specific use-cases. Do we
> know if people use extra userspace software on top of ALSA to set these
> up, or are they typically dealing with fixed configuration files for
> specifying these values?

...

> Mark, Liam, do you have any guidelines on how to handle such ABI
> changes?

I don't really, beyond your already very good summary.  It depends a lot
on knowledge of the users for the device, their level of expertise and
how likely anyone is to be using the individual feature in the first
place.  Obviously it's an ABI break so we shouldn't really be doing it
if we can avoid it, but if everyone involved is happy and has a good way
to deal with the changes then it's not really a problem.  

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-09-21 12:51 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21  8:57 [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Sheetal
2026-09-21  8:57 ` [RFC PATCH 1/3] ASoC: tegra: Fix ASRC Stream6 input threshold control Sheetal
2026-09-21 11:38   ` Thierry Reding
2026-09-21  8:57 ` [RFC PATCH 2/3] ASoC: tegra: Update ASRC ratio controls Sheetal
2026-09-21 11:50   ` Thierry Reding
2026-09-21  8:57 ` [RFC PATCH 3/3] ASoC: tegra: Cache ASRC ratios until streams are active Sheetal
2026-09-21 12:07 ` [RFC PATCH 0/3] ASoC: tegra: ASRC control fixes and ratio caching Thierry Reding
2026-09-21 12:51   ` Mark Brown

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®