diff mbox series

[v2] ASoC: tegra: Add master volume/mute control support

Message ID 1638278605-28225-1-git-send-email-spujar@nvidia.com
State Accepted
Commit 0d242698fa693ab8cb98c11ba7cf7fc8f7242c0b
Headers show
Series [v2] ASoC: tegra: Add master volume/mute control support | expand

Commit Message

Sameer Pujar Nov. 30, 2021, 1:23 p.m. UTC
The MVC module has a per channel control bit, based on which it decides
to apply channel specific volume/mute settings. When per channel control
bit is enabled (which is the default HW configuration), all MVC channel
volume/mute can be independently controlled. If the control is disabled,
channel-0 volume/mute setting is applied by HW to all remaining channels.
Thus add support to leverage this HW feature by exposing master controls
for volume/mute.

With this, now there are per channel and master volume/mute controls.
Users need to just use controls which are suitable for their applications.
The per channel control enable/disable is mananged in driver and hidden
from users, so that they need to just worry about respective volume/mute
controls.

Signed-off-by: Sameer Pujar <spujar@nvidia.com>
---
 changes in v2:
   * Kcontrol put() related comments, received during v1, are addressed
     in another series [0].
   * Thus v2 is rebased on top of recent changes.
   * In doing so, this patch also addresses optimization comment received
     on [0] which suggested usage of regmap_update_bits_check() in the MVC
     driver.

   [0] https://lkml.org/lkml/2021/11/18/930

 sound/soc/tegra/tegra210_mvc.c | 209 ++++++++++++++++++++++++++++++++---------
 sound/soc/tegra/tegra210_mvc.h |   5 +
 2 files changed, 169 insertions(+), 45 deletions(-)

Comments

Mark Brown Dec. 1, 2021, 6:32 p.m. UTC | #1
On Tue, 30 Nov 2021 18:53:25 +0530, Sameer Pujar wrote:
> The MVC module has a per channel control bit, based on which it decides
> to apply channel specific volume/mute settings. When per channel control
> bit is enabled (which is the default HW configuration), all MVC channel
> volume/mute can be independently controlled. If the control is disabled,
> channel-0 volume/mute setting is applied by HW to all remaining channels.
> Thus add support to leverage this HW feature by exposing master controls
> for volume/mute.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next

Thanks!

[1/1] ASoC: tegra: Add master volume/mute control support
      commit: 0d242698fa693ab8cb98c11ba7cf7fc8f7242c0b

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark
diff mbox series

Patch

diff --git a/sound/soc/tegra/tegra210_mvc.c b/sound/soc/tegra/tegra210_mvc.c
index 93cbeb6..2d79138 100644
--- a/sound/soc/tegra/tegra210_mvc.c
+++ b/sound/soc/tegra/tegra210_mvc.c
@@ -108,67 +108,152 @@  static void tegra210_mvc_conv_vol(struct tegra210_mvc *mvc, u8 chan, s32 val)
 	}
 }
 
-static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol,
-				 struct snd_ctl_elem_value *ucontrol)
+static u32 tegra210_mvc_get_ctrl_reg(struct snd_kcontrol *kcontrol)
 {
 	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
 	struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
-	u8 mute_mask;
 	u32 val;
 
 	pm_runtime_get_sync(cmpnt->dev);
 	regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &val);
 	pm_runtime_put(cmpnt->dev);
 
-	mute_mask = (val >>  TEGRA210_MVC_MUTE_SHIFT) &
-		TEGRA210_MUTE_MASK_EN;
+	return val;
+}
+
+static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	u32 val = tegra210_mvc_get_ctrl_reg(kcontrol);
+	u8 mute_mask = TEGRA210_GET_MUTE_VAL(val);
 
-	ucontrol->value.integer.value[0] = mute_mask;
+	/*
+	 * If per channel control is enabled, then return
+	 * exact mute/unmute setting of all channels.
+	 *
+	 * Else report setting based on CH0 bit to reflect
+	 * the correct HW state.
+	 */
+	if (val & TEGRA210_MVC_PER_CHAN_CTRL_EN) {
+		ucontrol->value.integer.value[0] = mute_mask;
+	} else {
+		if (mute_mask & TEGRA210_MVC_CH0_MUTE_EN)
+			ucontrol->value.integer.value[0] =
+				TEGRA210_MUTE_MASK_EN;
+		else
+			ucontrol->value.integer.value[0] = 0;
+	}
 
 	return 0;
 }
 
-static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol,
-				 struct snd_ctl_elem_value *ucontrol)
+static int tegra210_mvc_get_master_mute(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	u32 val = tegra210_mvc_get_ctrl_reg(kcontrol);
+	u8 mute_mask = TEGRA210_GET_MUTE_VAL(val);
+
+	/*
+	 * If per channel control is disabled, then return
+	 * master mute/unmute setting based on CH0 bit.
+	 *
+	 * Else report settings based on state of all
+	 * channels.
+	 */
+	if (!(val & TEGRA210_MVC_PER_CHAN_CTRL_EN)) {
+		ucontrol->value.integer.value[0] =
+			mute_mask & TEGRA210_MVC_CH0_MUTE_EN;
+	} else {
+		if (mute_mask == TEGRA210_MUTE_MASK_EN)
+			ucontrol->value.integer.value[0] =
+				TEGRA210_MVC_CH0_MUTE_EN;
+		else
+			ucontrol->value.integer.value[0] = 0;
+	}
+
+	return 0;
+}
+
+static int tegra210_mvc_volume_switch_timeout(struct snd_soc_component *cmpnt)
 {
-	struct soc_mixer_control *mc =
-		(struct soc_mixer_control *)kcontrol->private_value;
-	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
 	struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
-	unsigned int value;
-	u8 new_mask, old_mask;
+	u32 value;
 	int err;
 
-	pm_runtime_get_sync(cmpnt->dev);
-
-	/* Check if VOLUME_SWITCH is triggered */
 	err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH,
 			value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK),
 			10, 10000);
 	if (err < 0)
-		goto end;
+		dev_err(cmpnt->dev,
+			"Volume switch trigger is still active, err = %d\n",
+			err);
 
-	regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &value);
+	return err;
+}
 
-	old_mask = (value >> TEGRA210_MVC_MUTE_SHIFT) & TEGRA210_MUTE_MASK_EN;
-	new_mask = ucontrol->value.integer.value[0];
+static int tegra210_mvc_update_mute(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol,
+				    bool per_chan_ctrl)
+{
+	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+	struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
+	u32 mute_val = ucontrol->value.integer.value[0];
+	u32 per_ch_ctrl_val;
+	bool change = false;
+	int err;
 
-	if (new_mask == old_mask) {
-		err = 0;
+	pm_runtime_get_sync(cmpnt->dev);
+
+	err = tegra210_mvc_volume_switch_timeout(cmpnt);
+	if (err < 0)
 		goto end;
+
+	if (per_chan_ctrl) {
+		per_ch_ctrl_val = TEGRA210_MVC_PER_CHAN_CTRL_EN;
+	} else {
+		per_ch_ctrl_val = 0;
+
+		if (mute_val)
+			mute_val = TEGRA210_MUTE_MASK_EN;
 	}
 
-	err = regmap_update_bits(mvc->regmap, mc->reg,
+	regmap_update_bits_check(mvc->regmap, TEGRA210_MVC_CTRL,
 				 TEGRA210_MVC_MUTE_MASK,
-				 new_mask << TEGRA210_MVC_MUTE_SHIFT);
-	if (err < 0)
-		goto end;
+				 mute_val << TEGRA210_MVC_MUTE_SHIFT,
+				 &change);
 
-	err = 1;
+	if (change) {
+		regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
+				   TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK,
+				   per_ch_ctrl_val);
+
+		regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
+				   TEGRA210_MVC_VOLUME_SWITCH_MASK,
+				   TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
+	}
 
 end:
 	pm_runtime_put(cmpnt->dev);
-	return err;
+
+	if (err < 0)
+		return err;
+
+	if (change)
+		return 1;
+
+	return 0;
+}
+
+static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	return tegra210_mvc_update_mute(kcontrol, ucontrol, true);
+}
+
+static int tegra210_mvc_put_master_mute(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	return tegra210_mvc_update_mute(kcontrol, ucontrol, false);
 }
 
 static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol,
@@ -178,7 +263,7 @@  static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol,
 		(struct soc_mixer_control *)kcontrol->private_value;
 	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
 	struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
-	u8 chan = (mc->reg - TEGRA210_MVC_TARGET_VOL) / REG_SIZE;
+	u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL);
 	s32 val = mvc->volume[chan];
 
 	if (mvc->curve_type == CURVE_POLY) {
@@ -193,44 +278,55 @@  static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol,
 	return 0;
 }
 
-static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol,
-				struct snd_ctl_elem_value *ucontrol)
+static int tegra210_mvc_get_master_vol(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	return tegra210_mvc_get_vol(kcontrol, ucontrol);
+}
+
+static int tegra210_mvc_update_vol(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol,
+				   bool per_ch_enable)
 {
 	struct soc_mixer_control *mc =
 		(struct soc_mixer_control *)kcontrol->private_value;
 	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
 	struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
-	unsigned int reg = mc->reg;
-	unsigned int value;
-	u8 chan;
-	int err, old_volume;
+	u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL);
+	int old_volume = mvc->volume[chan];
+	int err, i;
 
 	pm_runtime_get_sync(cmpnt->dev);
 
-	/* Check if VOLUME_SWITCH is triggered */
-	err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH,
-			value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK),
-			10, 10000);
+	err = tegra210_mvc_volume_switch_timeout(cmpnt);
 	if (err < 0)
 		goto end;
 
-	chan = (reg - TEGRA210_MVC_TARGET_VOL) / REG_SIZE;
-	old_volume = mvc->volume[chan];
-
-	tegra210_mvc_conv_vol(mvc, chan,
-			      ucontrol->value.integer.value[0]);
+	tegra210_mvc_conv_vol(mvc, chan, ucontrol->value.integer.value[0]);
 
 	if (mvc->volume[chan] == old_volume) {
 		err = 0;
 		goto end;
 	}
 
+	if (per_ch_enable) {
+		regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
+				   TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK,
+				   TEGRA210_MVC_PER_CHAN_CTRL_EN);
+	} else {
+		regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
+				   TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, 0);
+
+		for (i = 1; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++)
+			mvc->volume[i] = mvc->volume[chan];
+	}
+
 	/* Configure init volume same as target volume */
 	regmap_write(mvc->regmap,
 		TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, chan),
 		mvc->volume[chan]);
 
-	regmap_write(mvc->regmap, reg, mvc->volume[chan]);
+	regmap_write(mvc->regmap, mc->reg, mvc->volume[chan]);
 
 	regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
 			   TEGRA210_MVC_VOLUME_SWITCH_MASK,
@@ -240,9 +336,22 @@  static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol,
 
 end:
 	pm_runtime_put(cmpnt->dev);
+
 	return err;
 }
 
+static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	return tegra210_mvc_update_vol(kcontrol, ucontrol, true);
+}
+
+static int tegra210_mvc_put_master_vol(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	return tegra210_mvc_update_vol(kcontrol, ucontrol, false);
+}
+
 static void tegra210_mvc_reset_vol_settings(struct tegra210_mvc *mvc,
 					    struct device *dev)
 {
@@ -438,6 +547,16 @@  static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = {
 		       TEGRA210_MVC_CTRL, 0, TEGRA210_MUTE_MASK_EN, 0,
 		       tegra210_mvc_get_mute, tegra210_mvc_put_mute),
 
+	/* Master volume */
+	SOC_SINGLE_EXT("Volume", TEGRA210_MVC_TARGET_VOL, 0, 16000, 0,
+		       tegra210_mvc_get_master_vol,
+		       tegra210_mvc_put_master_vol),
+
+	/* Master mute */
+	SOC_SINGLE_EXT("Mute", TEGRA210_MVC_CTRL, 0, 1, 0,
+		       tegra210_mvc_get_master_mute,
+		       tegra210_mvc_put_master_mute),
+
 	SOC_ENUM_EXT("Curve Type", tegra210_mvc_curve_type_ctrl,
 		     tegra210_mvc_get_curve_type, tegra210_mvc_put_curve_type),
 };
diff --git a/sound/soc/tegra/tegra210_mvc.h b/sound/soc/tegra/tegra210_mvc.h
index def29c4..d775335 100644
--- a/sound/soc/tegra/tegra210_mvc.h
+++ b/sound/soc/tegra/tegra210_mvc.h
@@ -59,6 +59,7 @@ 
 #define TEGRA210_MUTE_MASK_EN			0xff
 #define TEGRA210_MVC_MUTE_MASK			(TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT)
 #define TEGRA210_MVC_MUTE_EN			(TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT)
+#define TEGRA210_MVC_CH0_MUTE_EN		1
 
 #define TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT	30
 #define TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK	(1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT)
@@ -92,6 +93,10 @@ 
 #define TEGRA210_MVC_MAX_CHAN_COUNT 8
 #define TEGRA210_MVC_REG_OFFSET(reg, i) (reg + (REG_SIZE * i))
 
+#define TEGRA210_MVC_GET_CHAN(reg, base) (((reg) - (base)) / REG_SIZE)
+
+#define TEGRA210_GET_MUTE_VAL(val) (((val) >> TEGRA210_MVC_MUTE_SHIFT) & TEGRA210_MUTE_MASK_EN)
+
 #define NUM_GAIN_POLY_COEFFS 9
 
 enum {