summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2025-03-20 15:35:21 +0000
committerMark Brown <broonie@kernel.org>2025-03-20 15:35:21 +0000
commit47c4f9b1722fd883c9745d7877cb212e41dd2715 (patch)
treeaa36a849c2d6eaf9a38255cb2a9233be5cd072fd /sound
parent83eddf0116b09186f909bc643f2093f266f204ea (diff)
parent502a668fad12b6ca10bcbb615d62e61d3b669c99 (diff)
Tidy up ASoC control get and put handlers
Merge series from Charles Keepax <ckeepax@opensource.cirrus.com>: There is a lot of duplicated and occasionally slightly incorrect code around the ASoC control get and put handlers. This series add some kunit tests and then refactors the code to get all the tests passing and reduce some of the duplication. The focus here is on the volsw handlers, future work could still be done on some of the others but these were the ones that most required attention. Hopefully the only slightly controversal change is the very last patch which changes platform_max to be applied after the control type is determined, more discussion in the commit message for that one.
Diffstat (limited to 'sound')
-rw-r--r--sound/pci/hda/tas2781_hda_i2c.c2
-rw-r--r--sound/pci/hda/tas2781_hda_spi.c2
-rw-r--r--sound/soc/Kconfig7
-rw-r--r--sound/soc/Makefile4
-rw-r--r--sound/soc/codecs/wm5110.c4
-rw-r--r--sound/soc/soc-ops-test.c541
-rw-r--r--sound/soc/soc-ops.c712
-rw-r--r--sound/soc/soc-topology.c4
8 files changed, 798 insertions, 478 deletions
diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c
index be9a90f643eb..50c5e5f26589 100644
--- a/sound/pci/hda/tas2781_hda_i2c.c
+++ b/sound/pci/hda/tas2781_hda_i2c.c
@@ -45,7 +45,7 @@
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw_range, \
+ .info = snd_soc_info_volsw, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .rreg = xreg, .shift = xshift, \
diff --git a/sound/pci/hda/tas2781_hda_spi.c b/sound/pci/hda/tas2781_hda_spi.c
index 00676cbb2c8e..399f2e4c3b62 100644
--- a/sound/pci/hda/tas2781_hda_spi.c
+++ b/sound/pci/hda/tas2781_hda_spi.c
@@ -52,7 +52,7 @@
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.tlv.p = (tlv_array), \
- .info = snd_soc_info_volsw_range, \
+ .info = snd_soc_info_volsw, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = (unsigned long)&(struct soc_mixer_control) { \
.reg = xreg, .rreg = xreg, \
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 5efba76abb31..8b7d51266f81 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -81,6 +81,13 @@ config SND_SOC_UTILS_KUNIT_TEST
help
If you want to perform tests on ALSA SoC utils library say Y here.
+config SND_SOC_OPS_KUNIT_TEST
+ tristate "KUnit tests for SoC ops"
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ If you want to perform tests on ALSA SoC ops library say Y here.
+
config SND_SOC_ACPI
tristate
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 08baaa11d813..358e227c5ab6 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -21,6 +21,10 @@ ifneq ($(CONFIG_SND_SOC_UTILS_KUNIT_TEST),)
obj-$(CONFIG_SND_SOC_UTILS_KUNIT_TEST) += soc-utils-test.o
endif
+ifneq ($(CONFIG_SND_SOC_OPS_KUNIT_TEST),)
+obj-$(CONFIG_SND_SOC_OPS_KUNIT_TEST) += soc-ops-test.o
+endif
+
ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),)
snd-soc-core-y += soc-generic-dmaengine-pcm.o
endif
diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c
index 64eee0d2347d..212eca675f27 100644
--- a/sound/soc/codecs/wm5110.c
+++ b/sound/soc/codecs/wm5110.c
@@ -477,7 +477,7 @@ static int wm5110_in_pga_get(struct snd_kcontrol *kcontrol,
*/
snd_soc_dapm_mutex_lock(dapm);
- ret = snd_soc_get_volsw_range(kcontrol, ucontrol);
+ ret = snd_soc_get_volsw(kcontrol, ucontrol);
snd_soc_dapm_mutex_unlock(dapm);
@@ -497,7 +497,7 @@ static int wm5110_in_pga_put(struct snd_kcontrol *kcontrol,
*/
snd_soc_dapm_mutex_lock(dapm);
- ret = snd_soc_put_volsw_range(kcontrol, ucontrol);
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
snd_soc_dapm_mutex_unlock(dapm);
diff --git a/sound/soc/soc-ops-test.c b/sound/soc/soc-ops-test.c
new file mode 100644
index 000000000000..dc1e482bba6a
--- /dev/null
+++ b/sound/soc/soc-ops-test.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2025 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <kunit/device.h>
+#include <kunit/test.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+#include <sound/asound.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-component.h>
+
+enum soc_ops_test_control_layout {
+ SOC_OPS_TEST_SINGLE,
+ SOC_OPS_TEST_DOUBLE,
+ SOC_OPS_TEST_DOUBLE_R,
+};
+
+#define TEST_MC(clayout, xmin, xmax, xpmax, xsign, xinvert) \
+ .mc = { \
+ .min = xmin, .max = xmax, .platform_max = xpmax, \
+ .reg = 0, .shift = 0, .sign_bit = xsign, .invert = xinvert, \
+ .rreg = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_DOUBLE_R ? 1 : 0, \
+ .rshift = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_DOUBLE ? 16 : 0, \
+ }
+
+#define TEST_UINFO(clayout, ctype, cmin, cmax) \
+ .uinfo = { \
+ .type = SNDRV_CTL_ELEM_TYPE_##ctype, \
+ .count = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_SINGLE ? 1 : 2, \
+ .value.integer.min = cmin, \
+ .value.integer.max = cmax, \
+ }
+
+#define ITEST(cname, clayout, ctype, cfunc, cmin, cmax, \
+ xmin, xmax, xpmax, xsign, xinvert) \
+ { \
+ .name = cname, \
+ .func_name = #cfunc, \
+ .layout = SOC_OPS_TEST_##clayout, \
+ .info = snd_soc_info_##cfunc, \
+ TEST_MC(clayout, xmin, xmax, xpmax, xsign, xinvert), \
+ TEST_UINFO(clayout, ctype, cmin, cmax), \
+ }
+
+#define ATEST(clayout, cfunc, cctl, cret, cinit, \
+ xmask, xreg, xmin, xmax, xpmax, xsign, xinvert) \
+ { \
+ .func_name = #cfunc, \
+ .layout = SOC_OPS_TEST_##clayout, \
+ .put = snd_soc_put_##cfunc, \
+ .get = snd_soc_get_##cfunc, \
+ TEST_MC(clayout, xmin, xmax, xpmax, xsign, xinvert), \
+ .lctl = cctl, .rctl = cctl, \
+ .lmask = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_DOUBLE ? \
+ (xmask) | (xmask) << 16 : (xmask), \
+ .rmask = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_DOUBLE_R ? (xmask) : 0, \
+ .init = cinit ? 0xFFFFFFFF : 0x00000000, \
+ .lreg = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_DOUBLE ? \
+ (xreg) | (xreg) << 16 : (xreg), \
+ .rreg = SOC_OPS_TEST_##clayout == SOC_OPS_TEST_DOUBLE_R ? (xreg) : 0, \
+ .ret = cret, \
+ }
+
+struct soc_ops_test_priv {
+ struct kunit *test;
+
+ struct snd_soc_component component;
+};
+
+struct info_test_param {
+ const char * const name;
+ const char * const func_name;
+ enum soc_ops_test_control_layout layout;
+ struct soc_mixer_control mc;
+ int (*info)(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *info);
+
+ struct snd_ctl_elem_info uinfo;
+};
+
+struct access_test_param {
+ const char * const func_name;
+ enum soc_ops_test_control_layout layout;
+ struct soc_mixer_control mc;
+ int (*put)(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *value);
+ int (*get)(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *value);
+
+ unsigned int init;
+ unsigned int lmask;
+ unsigned int rmask;
+ unsigned int lreg;
+ unsigned int rreg;
+ long lctl;
+ long rctl;
+ int ret;
+};
+
+static const struct info_test_param all_info_test_params[] = {
+ // Handling of volume control name for types
+ ITEST("Test Control", SINGLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Volume Control", SINGLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Control", DOUBLE_R, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Volume", DOUBLE_R, INTEGER, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Volume Control", DOUBLE_R, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Control", DOUBLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Volume", DOUBLE, INTEGER, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Volume Control", DOUBLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 0),
+ ITEST("Test Control", SINGLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 1),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw, 0, 1, 0, 1, 0, 0, 1),
+ ITEST("Test Volume Control", SINGLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 1),
+ ITEST("Test Control", DOUBLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 1),
+ ITEST("Test Volume", DOUBLE, INTEGER, volsw, 0, 1, 0, 1, 0, 0, 1),
+ ITEST("Test Volume Control", DOUBLE, BOOLEAN, volsw, 0, 1, 0, 1, 0, 0, 1),
+ ITEST("Test Control", SINGLE, INTEGER, volsw, 0, 2, 0, 2, 0, 0, 0),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw, 0, 2, 0, 2, 0, 0, 0),
+ ITEST("Test Volume Control", SINGLE, INTEGER, volsw, 0, 2, 0, 2, 0, 0, 0),
+ ITEST("Test Control", SINGLE, INTEGER, volsw, 0, 1, 0, 2, 1, 0, 0),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw, 0, 1, 0, 2, 1, 0, 0),
+ ITEST("Test Volume Control", SINGLE, INTEGER, volsw, 0, 1, 0, 2, 1, 0, 0),
+ // Negative minimums
+ ITEST("Test Control", SINGLE, INTEGER, volsw, 0, 20, -10, 10, 0, 4, 0),
+ ITEST("Test Control", SINGLE, INTEGER, volsw, 0, 15, -10, 10, 15, 4, 0),
+ ITEST("Test Control", SINGLE, INTEGER, volsw, 0, 20, -10, 10, 0, 4, 1),
+ ITEST("Test Control", SINGLE, INTEGER, volsw, 0, 15, -10, 10, 15, 4, 1),
+ // SX control volume control naming
+ ITEST("Test Control", SINGLE, BOOLEAN, volsw_sx, 0, 1, 0xF, 1, 0, 0, 0),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw_sx, 0, 1, 0xF, 1, 0, 0, 0),
+ ITEST("Test Volume Control", SINGLE, BOOLEAN, volsw_sx, 0, 1, 0xF, 1, 0, 0, 0),
+ ITEST("Test Control", SINGLE, INTEGER, volsw_sx, 0, 4, 0xE, 4, 0, 0, 0),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw_sx, 0, 4, 0xE, 4, 0, 0, 0),
+ ITEST("Test Volume Control", SINGLE, INTEGER, volsw_sx, 0, 4, 0xE, 4, 0, 0, 0),
+ ITEST("Test Control", SINGLE, INTEGER, volsw_sx, 0, 3, 0xE, 4, 3, 0, 0),
+ ITEST("Test Volume", SINGLE, INTEGER, volsw_sx, 0, 3, 0xE, 4, 3, 0, 0),
+ ITEST("Test Volume Control", SINGLE, INTEGER, volsw_sx, 0, 3, 0xE, 4, 3, 0, 0),
+};
+
+static const struct access_test_param all_access_test_params[] = {
+ // Single positive value controls
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 0, 0, 0),
+ ATEST(SINGLE, volsw, 0, 0, false, 0x1F, 0x00, 0, 20, 0, 0, 0),
+ ATEST(SINGLE, volsw, 20, 1, false, 0x1F, 0x14, 0, 20, 0, 0, 0),
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 15, 0, 0),
+ ATEST(SINGLE, volsw, 25, -22, false, 0x1F, 0x00, 0, 20, 15, 0, 0),
+ ATEST(SINGLE, volsw, 15, 1, false, 0x1F, 0x0F, 0, 20, 15, 0, 0),
+ // Inverted single positive value controls
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 0, 0, 1),
+ ATEST(SINGLE, volsw, 0, 1, false, 0x1F, 0x14, 0, 20, 0, 0, 1),
+ ATEST(SINGLE, volsw, 20, 0, false, 0x1F, 0x00, 0, 20, 0, 0, 1),
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 15, 0, 1),
+ ATEST(SINGLE, volsw, 25, -22, false, 0x1F, 0x00, 0, 20, 15, 0, 1),
+ ATEST(SINGLE, volsw, 15, 1, false, 0x1F, 0x05, 0, 20, 15, 0, 1),
+ ATEST(SINGLE, volsw, 10, 1, true, 0x1F, 0x0A, 0, 20, 0, 0, 0),
+ ATEST(SINGLE, volsw, 0, 1, true, 0x1F, 0x00, 0, 20, 0, 0, 0),
+ ATEST(SINGLE, volsw, 20, 1, true, 0x1F, 0x14, 0, 20, 0, 0, 0),
+ ATEST(SINGLE, volsw, 10, 1, true, 0x1F, 0x0A, 0, 20, 15, 0, 0),
+ ATEST(SINGLE, volsw, 25, -22, true, 0x1F, 0x00, 0, 20, 15, 0, 0),
+ ATEST(SINGLE, volsw, 15, 1, true, 0x1F, 0x0F, 0, 20, 15, 0, 0),
+ // Single negative value controls
+ ATEST(SINGLE, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 0, 4, 0),
+ ATEST(SINGLE, volsw, 0, 1, false, 0x1F, 0x16, -10, 10, 0, 4, 0),
+ ATEST(SINGLE, volsw, 20, 1, false, 0x1F, 0x0A, -10, 10, 0, 4, 0),
+ ATEST(SINGLE, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(SINGLE, volsw, 25, -22, false, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(SINGLE, volsw, 15, 1, false, 0x1F, 0x05, -10, 10, 15, 4, 0),
+ // Single non-zero minimum positive value controls
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 0, 0, 0),
+ ATEST(SINGLE, volsw, 0, 1, false, 0x1F, 0x0A, 10, 30, 0, 0, 0),
+ ATEST(SINGLE, volsw, 20, 1, false, 0x1F, 0x1E, 10, 30, 0, 0, 0),
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 15, 0, 0),
+ ATEST(SINGLE, volsw, 25, -22, false, 0x1F, 0x00, 10, 30, 15, 0, 0),
+ ATEST(SINGLE, volsw, 15, 1, false, 0x1F, 0x19, 10, 30, 15, 0, 0),
+ // Inverted single non-zero minimum positive value controls
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 0, 0, 1),
+ ATEST(SINGLE, volsw, 0, 1, false, 0x1F, 0x1E, 10, 30, 0, 0, 1),
+ ATEST(SINGLE, volsw, 20, 1, false, 0x1F, 0x0A, 10, 30, 0, 0, 1),
+ ATEST(SINGLE, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 15, 0, 1),
+ ATEST(SINGLE, volsw, 25, -22, false, 0x1F, 0x00, 10, 30, 15, 0, 1),
+ ATEST(SINGLE, volsw, 15, 1, false, 0x1F, 0x0F, 10, 30, 15, 0, 1),
+ // Double register positive value controls
+ ATEST(DOUBLE_R, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw, 0, 0, false, 0x1F, 0x00, 0, 20, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw, 20, 1, false, 0x1F, 0x14, 0, 20, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 15, 0, 0),
+ ATEST(DOUBLE_R, volsw, 25, -22, false, 0x1F, 0x00, 0, 20, 15, 0, 0),
+ ATEST(DOUBLE_R, volsw, 15, 1, false, 0x1F, 0x0F, 0, 20, 15, 0, 0),
+ // Double register negative value controls
+ ATEST(DOUBLE_R, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE_R, volsw, 0, 1, false, 0x1F, 0x16, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE_R, volsw, 20, 1, false, 0x1F, 0x0A, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE_R, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE_R, volsw, 25, -22, false, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE_R, volsw, 15, 1, false, 0x1F, 0x05, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE_R, volsw, 10, 1, true, 0x1F, 0x00, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE_R, volsw, 0, 1, true, 0x1F, 0x16, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE_R, volsw, 20, 1, true, 0x1F, 0x0A, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE_R, volsw, 10, 1, true, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE_R, volsw, 25, -22, true, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE_R, volsw, 15, 1, true, 0x1F, 0x05, -10, 10, 15, 4, 0),
+ // Inverted double register negative value controls
+ ATEST(DOUBLE_R, volsw, 10, 1, true, 0x1F, 0x00, -10, 10, 0, 4, 1),
+ ATEST(DOUBLE_R, volsw, 0, 1, true, 0x1F, 0x0A, -10, 10, 0, 4, 1),
+ ATEST(DOUBLE_R, volsw, 20, 1, true, 0x1F, 0x16, -10, 10, 0, 4, 1),
+ ATEST(DOUBLE_R, volsw, 10, 1, true, 0x1F, 0x00, -10, 10, 15, 4, 1),
+ ATEST(DOUBLE_R, volsw, 25, -22, true, 0x1F, 0x00, -10, 10, 15, 4, 1),
+ ATEST(DOUBLE_R, volsw, 15, 1, true, 0x1F, 0x1B, -10, 10, 15, 4, 1),
+ // Double register non-zero minimum positive value controls
+ ATEST(DOUBLE_R, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw, 0, 1, false, 0x1F, 0x0A, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw, 20, 1, false, 0x1F, 0x1E, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE_R, volsw, 25, -22, false, 0x1F, 0x00, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE_R, volsw, 15, 1, false, 0x1F, 0x19, 10, 30, 15, 0, 0),
+ // Double shift positive value controls
+ ATEST(DOUBLE, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 0, 0, false, 0x1F, 0x00, 0, 20, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 20, 1, false, 0x1F, 0x14, 0, 20, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 10, 1, false, 0x1F, 0x0A, 0, 20, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 25, -22, false, 0x1F, 0x00, 0, 20, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 15, 1, false, 0x1F, 0x0F, 0, 20, 15, 0, 0),
+ // Double shift negative value controls
+ ATEST(DOUBLE, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE, volsw, 0, 1, false, 0x1F, 0x16, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE, volsw, 20, 1, false, 0x1F, 0x0A, -10, 10, 0, 4, 0),
+ ATEST(DOUBLE, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE, volsw, 25, -22, false, 0x1F, 0x00, -10, 10, 15, 4, 0),
+ ATEST(DOUBLE, volsw, 15, 1, false, 0x1F, 0x05, -10, 10, 15, 4, 0),
+ // Inverted double shift negative value controls
+ ATEST(DOUBLE, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 0, 4, 1),
+ ATEST(DOUBLE, volsw, 0, 1, false, 0x1F, 0x0A, -10, 10, 0, 4, 1),
+ ATEST(DOUBLE, volsw, 20, 1, false, 0x1F, 0x16, -10, 10, 0, 4, 1),
+ ATEST(DOUBLE, volsw, 10, 0, false, 0x1F, 0x00, -10, 10, 15, 4, 1),
+ ATEST(DOUBLE, volsw, 25, -22, false, 0x1F, 0x00, -10, 10, 15, 4, 1),
+ ATEST(DOUBLE, volsw, 15, 1, false, 0x1F, 0x1B, -10, 10, 15, 4, 1),
+ // Double shift non-zero minimum positive value controls
+ ATEST(DOUBLE, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 0, 1, false, 0x1F, 0x0A, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 20, 1, false, 0x1F, 0x1E, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 10, 1, false, 0x1F, 0x14, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 25, -22, false, 0x1F, 0x00, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 15, 1, false, 0x1F, 0x19, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 10, 1, true, 0x1F, 0x14, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 0, 1, true, 0x1F, 0x0A, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 20, 1, true, 0x1F, 0x1E, 10, 30, 0, 0, 0),
+ ATEST(DOUBLE, volsw, 10, 1, true, 0x1F, 0x14, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 25, -22, true, 0x1F, 0x00, 10, 30, 15, 0, 0),
+ ATEST(DOUBLE, volsw, 15, 1, true, 0x1F, 0x19, 10, 30, 15, 0, 0),
+ // Single SX all values
+ ATEST(SINGLE, volsw_sx, 0, 1, false, 0xF, 0x0F, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 1, 0, false, 0xF, 0x00, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 2, 1, false, 0xF, 0x01, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 3, 1, false, 0xF, 0x02, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 4, 1, false, 0xF, 0x03, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 5, -22, false, 0xF, 0x00, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 0, 0, true, 0xF, 0x0F, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 1, 1, true, 0xF, 0x00, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 2, 1, true, 0xF, 0x01, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 3, 1, true, 0xF, 0x02, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 4, 1, true, 0xF, 0x03, 0x0F, 4, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 5, -22, true, 0xF, 0x00, 0x0F, 4, 0, 0, 0),
+ // Inverted single SX all values
+ ATEST(SINGLE, volsw_sx, 0, 1, false, 0x1F, 0x03, 0x0F, 4, 0, 0, 1),
+ ATEST(SINGLE, volsw_sx, 1, 1, false, 0x1F, 0x02, 0x0F, 4, 0, 0, 1),
+ ATEST(SINGLE, volsw_sx, 2, 1, false, 0x1F, 0x01, 0x0F, 4, 0, 0, 1),
+ ATEST(SINGLE, volsw_sx, 3, 0, false, 0x1F, 0x00, 0x0F, 4, 0, 0, 1),
+ ATEST(SINGLE, volsw_sx, 4, 1, false, 0x1F, 0x0F, 0x0F, 4, 0, 0, 1),
+ ATEST(SINGLE, volsw_sx, 5, -22, false, 0x1F, 0x00, 0x0F, 4, 0, 0, 1),
+ // Single SX select values
+ ATEST(SINGLE, volsw_sx, 0, 1, false, 0xFF, 0x88, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 1, 1, false, 0xFF, 0x89, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 119, 1, false, 0xFF, 0xFF, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 120, 0, false, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 121, 1, false, 0xFF, 0x01, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 143, 1, false, 0xFF, 0x17, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 144, 1, false, 0xFF, 0x18, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 145, -22, false, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 0, 1, true, 0xFF, 0x88, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 1, 1, true, 0xFF, 0x89, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 119, 0, true, 0xFF, 0xFF, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 120, 1, true, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 121, 1, true, 0xFF, 0x01, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 143, 1, true, 0xFF, 0x17, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 144, 1, true, 0xFF, 0x18, 0x88, 144, 0, 0, 0),
+ ATEST(SINGLE, volsw_sx, 145, -22, true, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ // Double shift SX select values
+ ATEST(DOUBLE, volsw_sx, 0, 1, true, 0xFF, 0x88, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 1, 1, true, 0xFF, 0x89, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 119, 0, true, 0xFF, 0xFF, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 120, 1, true, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 121, 1, true, 0xFF, 0x01, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 143, 1, true, 0xFF, 0x17, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 144, 1, true, 0xFF, 0x18, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE, volsw_sx, 145, -22, true, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ // Double register SX select values
+ ATEST(DOUBLE_R, volsw_sx, 0, 1, true, 0xFF, 0x88, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 1, 1, true, 0xFF, 0x89, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 119, 0, true, 0xFF, 0xFF, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 120, 1, true, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 121, 1, true, 0xFF, 0x01, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 143, 1, true, 0xFF, 0x17, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 144, 1, true, 0xFF, 0x18, 0x88, 144, 0, 0, 0),
+ ATEST(DOUBLE_R, volsw_sx, 145, -22, true, 0xFF, 0x00, 0x88, 144, 0, 0, 0),
+};
+
+static const char *control_type_str(const snd_ctl_elem_type_t type)
+{
+ switch (type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ return "bool";
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ return "int";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *control_layout_str(const enum soc_ops_test_control_layout layout)
+{
+ switch (layout) {
+ case SOC_OPS_TEST_SINGLE:
+ return "single";
+ case SOC_OPS_TEST_DOUBLE:
+ return "double";
+ case SOC_OPS_TEST_DOUBLE_R:
+ return "double_r";
+ default:
+ return "unknown";
+ }
+};
+
+static int mock_regmap_read(void *context, const void *reg_buf,
+ const size_t reg_size, void *val_buf,
+ size_t val_size)
+{
+ struct soc_ops_test_priv *priv = context;
+
+ KUNIT_FAIL(priv->test, "Unexpected bus read");
+
+ return -EIO;
+}
+
+static int mock_regmap_gather_write(void *context,
+ const void *reg_buf, size_t reg_size,
+ const void *val_buf, size_t val_size)
+{
+ struct soc_ops_test_priv *priv = context;
+
+ KUNIT_FAIL(priv->test, "Unexpected bus gather_write");
+
+ return -EIO;
+}
+
+static int mock_regmap_write(void *context, const void *val_buf,
+ size_t val_size)
+{
+ struct soc_ops_test_priv *priv = context;
+
+ KUNIT_FAIL(priv->test, "Unexpected bus write");
+
+ return -EIO;
+}
+
+static const struct regmap_bus mock_regmap_bus = {
+ .read = mock_regmap_read,
+ .write = mock_regmap_write,
+ .gather_write = mock_regmap_gather_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
+ .val_format_endian_default = REGMAP_ENDIAN_NATIVE,
+};
+
+static const struct regmap_config mock_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_format_endian = REGMAP_ENDIAN_NATIVE,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = 0x1,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int soc_ops_test_init(struct kunit *test)
+{
+ struct soc_ops_test_priv *priv;
+ struct regmap *regmap;
+ struct device *dev;
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->test = test;
+
+ dev = kunit_device_register(test, "soc_ops_test_drv");
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ regmap = devm_regmap_init(dev, &mock_regmap_bus, priv, &mock_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /* No actual hardware, we just use the cache */
+ regcache_cache_only(regmap, true);
+
+ priv->component.dev = dev;
+ priv->component.regmap = regmap;
+ mutex_init(&priv->component.io_mutex);
+
+ test->priv = priv;
+
+ return 0;
+}
+
+static void soc_ops_test_exit(struct kunit *test)
+{
+ struct soc_ops_test_priv *priv = test->priv;
+
+ kunit_device_unregister(test, priv->component.dev);
+}
+
+static void info_test_desc(const struct info_test_param *param, char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE,
+ "%s %s %s: ctl range: %ld->%ld, reg range: %d->%d(%d), sign: %d, inv: %d",
+ control_layout_str(param->layout), param->func_name,
+ control_type_str(param->uinfo.type),
+ param->uinfo.value.integer.min, param->uinfo.value.integer.max,
+ param->mc.min, param->mc.max, param->mc.platform_max,
+ param->mc.sign_bit, param->mc.invert);
+}
+
+static void soc_ops_test_info(struct kunit *test)
+{
+ struct soc_ops_test_priv *priv = test->priv;
+ const struct info_test_param *param = test->param_value;
+ const struct snd_ctl_elem_info *target = &param->uinfo;
+ struct snd_ctl_elem_info result;
+ struct snd_kcontrol kctl = {
+ .private_data = &priv->component,
+ .private_value = (unsigned long)&param->mc,
+ };
+ int ret;
+
+ strscpy(kctl.id.name, param->name, sizeof(kctl.id.name));
+
+ ret = param->info(&kctl, &result);
+ KUNIT_ASSERT_FALSE(test, ret);
+
+ KUNIT_EXPECT_EQ(test, result.count, target->count);
+ KUNIT_EXPECT_EQ(test, result.type, target->type);
+ KUNIT_EXPECT_EQ(test, result.value.integer.min, target->value.integer.min);
+ KUNIT_EXPECT_EQ(test, result.value.integer.max, target->value.integer.max);
+}
+
+static void access_test_desc(const struct access_test_param *param, char *desc)
+{
+ if (param->ret < 0) {
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE,
+ "%s %s: %ld,%ld -> range: %d->%d(%d), sign: %d, inv: %d -> err: %d",
+ control_layout_str(param->layout), param->func_name,
+ param->lctl, param->rctl,
+ param->mc.min, param->mc.max, param->mc.platform_max,
+ param->mc.sign_bit, param->mc.invert,
+ param->ret);
+ } else {
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE,
+ "%s %s: %ld,%ld -> range: %d->%d(%d), sign: %d, inv: %d -> %#x,%#x",
+ control_layout_str(param->layout), param->func_name,
+ param->lctl, param->rctl,
+ param->mc.min, param->mc.max, param->mc.platform_max,
+ param->mc.sign_bit, param->mc.invert,
+ param->lreg, param->rreg);
+ }
+}
+
+static void soc_ops_test_access(struct kunit *test)
+{
+ struct soc_ops_test_priv *priv = test->priv;
+ const struct access_test_param *param = test->param_value;
+ struct snd_kcontrol kctl = {
+ .private_data = &priv->component,
+ .private_value = (unsigned long)&param->mc,
+ };
+ struct snd_ctl_elem_value result;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_write(priv->component.regmap, 0x0, param->init);
+ KUNIT_ASSERT_FALSE(test, ret);
+ ret = regmap_write(priv->component.regmap, 0x1, param->init);
+ KUNIT_ASSERT_FALSE(test, ret);
+
+ result.value.integer.value[0] = param->lctl;
+ result.value.integer.value[1] = param->rctl;
+
+ ret = param->put(&kctl, &result);
+ KUNIT_ASSERT_EQ(test, ret, param->ret);
+ if (ret < 0)
+ return;
+
+ ret = regmap_read(priv->component.regmap, 0x0, &val);
+ KUNIT_ASSERT_FALSE(test, ret);
+ KUNIT_EXPECT_EQ(test, val, (param->init & ~param->lmask) | param->lreg);
+
+ ret = regmap_read(priv->component.regmap, 0x1, &val);
+ KUNIT_ASSERT_FALSE(test, ret);
+ KUNIT_EXPECT_EQ(test, val, (param->init & ~param->rmask) | param->rreg);
+
+ result.value.integer.value[0] = 0;
+ result.value.integer.value[1] = 0;
+
+ ret = param->get(&kctl, &result);
+ KUNIT_ASSERT_GE(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, result.value.integer.value[0], param->lctl);
+ if (param->layout != SOC_OPS_TEST_SINGLE)
+ KUNIT_EXPECT_EQ(test, result.value.integer.value[1], param->rctl);
+ else
+ KUNIT_EXPECT_EQ(test, result.value.integer.value[1], 0);
+}
+
+KUNIT_ARRAY_PARAM(all_info_tests, all_info_test_params, info_test_desc);
+KUNIT_ARRAY_PARAM(all_access_tests, all_access_test_params, access_test_desc);
+
+static struct kunit_case soc_ops_test_cases[] = {
+ KUNIT_CASE_PARAM(soc_ops_test_info, all_info_tests_gen_params),
+ KUNIT_CASE_PARAM(soc_ops_test_access, all_access_tests_gen_params),
+ {}
+};
+
+static struct kunit_suite soc_ops_test_suite = {
+ .name = "soc-ops",
+ .init = soc_ops_test_init,
+ .exit = soc_ops_test_exit,
+ .test_cases = soc_ops_test_cases,
+};
+
+kunit_test_suites(&soc_ops_test_suite);
+
+MODULE_DESCRIPTION("ASoC soc-ops kunit test");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index cd5f927bcd4e..8d4dd11c9aef 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -37,7 +37,7 @@
* Returns 0 for success.
*/
int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+ struct snd_ctl_elem_info *uinfo)
{
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
@@ -56,7 +56,7 @@ EXPORT_SYMBOL_GPL(snd_soc_info_enum_double);
* Returns 0 for success.
*/
int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+ struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
@@ -87,7 +87,7 @@ EXPORT_SYMBOL_GPL(snd_soc_get_enum_double);
* Returns 0 for success.
*/
int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+ struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
@@ -110,522 +110,291 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
}
EXPORT_SYMBOL_GPL(snd_soc_put_enum_double);
-/**
- * snd_soc_read_signed - Read a codec register and interpret as signed value
- * @component: component
- * @reg: Register to read
- * @mask: Mask to use after shifting the register value
- * @shift: Right shift of register value
- * @sign_bit: Bit that describes if a number is negative or not.
- * @signed_val: Pointer to where the read value should be stored
- *
- * This functions reads a codec register. The register value is shifted right
- * by 'shift' bits and masked with the given 'mask'. Afterwards it translates
- * the given registervalue into a signed integer if sign_bit is non-zero.
- */
-static void snd_soc_read_signed(struct snd_soc_component *component,
- unsigned int reg, unsigned int mask, unsigned int shift,
- unsigned int sign_bit, int *signed_val)
+static int soc_mixer_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val,
+ unsigned int mask, unsigned int shift, int max)
{
- int ret;
- unsigned int val;
+ int val = (reg_val >> shift) & mask;
- val = snd_soc_component_read(component, reg);
- val = (val >> shift) & mask;
+ if (mc->sign_bit)
+ val = sign_extend32(val, mc->sign_bit);
- if (!sign_bit) {
- *signed_val = val;
- return;
- }
+ val -= mc->min;
- /* non-negative number */
- if (!(val & BIT(sign_bit))) {
- *signed_val = val;
- return;
- }
+ if (mc->invert)
+ val = max - val;
- ret = val;
+ return val & mask;
+}
- /*
- * The register most probably does not contain a full-sized int.
- * Instead we have an arbitrary number of bits in a signed
- * representation which has to be translated into a full-sized int.
- * This is done by filling up all bits above the sign-bit.
- */
- ret |= ~((int)(BIT(sign_bit) - 1));
+static unsigned int soc_mixer_ctl_to_reg(struct soc_mixer_control *mc, int val,
+ unsigned int mask, unsigned int shift,
+ int max)
+{
+ unsigned int reg_val;
- *signed_val = ret;
+ if (mc->invert)
+ val = max - val;
+
+ reg_val = val + mc->min;
+
+ return (reg_val & mask) << shift;
}
-/**
- * snd_soc_info_volsw - single mixer info callback
- * @kcontrol: mixer control
- * @uinfo: control element information
- *
- * Callback to provide information about a single mixer control, or a double
- * mixer control that spans 2 registers.
- *
- * Returns 0 for success.
- */
-int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+static int soc_mixer_valid_ctl(struct soc_mixer_control *mc, long val, int max)
{
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- const char *vol_string = NULL;
- int max;
+ if (val < 0)
+ return -EINVAL;
- max = uinfo->value.integer.max = mc->max - mc->min;
- if (mc->platform_max && mc->platform_max < max)
- max = mc->platform_max;
+ if (mc->platform_max && val > mc->platform_max)
+ return -EINVAL;
+
+ if (val > max)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int soc_mixer_mask(struct soc_mixer_control *mc)
+{
+ if (mc->sign_bit)
+ return GENMASK(mc->sign_bit, 0);
+ else
+ return GENMASK(fls(mc->max) - 1, 0);
+}
+
+static int soc_mixer_sx_mask(struct soc_mixer_control *mc)
+{
+ // min + max will take us 1-bit over the size of the mask
+ return GENMASK(fls(mc->min + mc->max) - 2, 0);
+}
+
+static int soc_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo,
+ struct soc_mixer_control *mc, int max)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
if (max == 1) {
- /* Even two value controls ending in Volume should always be integer */
- vol_string = strstr(kcontrol->id.name, " Volume");
- if (vol_string && !strcmp(vol_string, " Volume"))
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- else
+ /* Even two value controls ending in Volume should be integer */
+ const char *vol_string = strstr(kcontrol->id.name, " Volume");
+
+ if (!vol_string || strcmp(vol_string, " Volume"))
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- } else {
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
}
+ if (mc->platform_max && mc->platform_max < max)
+ max = mc->platform_max;
+
uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = max;
return 0;
}
-EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
-/**
- * snd_soc_info_volsw_sx - Mixer info callback for SX TLV controls
- * @kcontrol: mixer control
- * @uinfo: control element information
- *
- * Callback to provide information about a single mixer control, or a double
- * mixer control that spans 2 registers of the SX TLV type. SX TLV controls
- * have a range that represents both positive and negative values either side
- * of zero but without a sign bit. min is the minimum register value, max is
- * the number of steps.
- *
- * Returns 0 for success.
- */
-int snd_soc_info_volsw_sx(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+static int soc_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ struct soc_mixer_control *mc, int mask, int max)
{
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- int max;
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ unsigned int val1, val_mask;
+ unsigned int val2 = 0;
+ bool double_r = false;
+ int ret;
- if (mc->platform_max)
- max = mc->platform_max;
- else
- max = mc->max;
+ ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[0], max);
+ if (ret)
+ return ret;
- if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- else
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ val1 = soc_mixer_ctl_to_reg(mc, ucontrol->value.integer.value[0],
+ mask, mc->shift, max);
+ val_mask = mask << mc->shift;
- uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = max;
+ if (snd_soc_volsw_is_stereo(mc)) {
+ ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[1], max);
+ if (ret)
+ return ret;
- return 0;
+ if (mc->reg == mc->rreg) {
+ val1 |= soc_mixer_ctl_to_reg(mc,
+ ucontrol->value.integer.value[1],
+ mask, mc->rshift, max);
+ val_mask |= mask << mc->rshift;
+ } else {
+ val2 = soc_mixer_ctl_to_reg(mc,
+ ucontrol->value.integer.value[1],
+ mask, mc->shift, max);
+ double_r = true;
+ }
+ }
+
+ ret = snd_soc_component_update_bits(component, mc->reg, val_mask, val1);
+ if (ret < 0)
+ return ret;
+
+ if (double_r) {
+ int err = snd_soc_component_update_bits(component, mc->rreg,
+ val_mask, val2);
+ /* Don't drop change flag */
+ if (err)
+ return err;
+ }
+
+ return ret;
}
-EXPORT_SYMBOL_GPL(snd_soc_info_volsw_sx);
-/**
- * snd_soc_get_volsw - single mixer get callback
- * @kcontrol: mixer control
- * @ucontrol: control element information
- *
- * Callback to get the value of a single mixer control, or a double mixer
- * control that spans 2 registers.
- *
- * Returns 0 for success.
- */
-int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+static int soc_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ struct soc_mixer_control *mc, int mask, int max)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg = mc->reg;
- unsigned int reg2 = mc->rreg;
- unsigned int shift = mc->shift;
- unsigned int rshift = mc->rshift;
- int max = mc->max;
- int min = mc->min;
- int sign_bit = mc->sign_bit;
- unsigned int mask = (1ULL << fls(max)) - 1;
- unsigned int invert = mc->invert;
+ unsigned int reg_val;
int val;
- if (sign_bit)
- mask = BIT(sign_bit + 1) - 1;
-
- snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val);
+ reg_val = snd_soc_component_read(component, mc->reg);
+ val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max);
- ucontrol->value.integer.value[0] = val - min;
- if (invert)
- ucontrol->value.integer.value[0] =
- max - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[0] = val;
if (snd_soc_volsw_is_stereo(mc)) {
- if (reg == reg2)
- snd_soc_read_signed(component, reg, mask, rshift, sign_bit, &val);
- else
- snd_soc_read_signed(component, reg2, mask, shift, sign_bit, &val);
-
- ucontrol->value.integer.value[1] = val - min;
- if (invert)
- ucontrol->value.integer.value[1] =
- max - ucontrol->value.integer.value[1];
+ if (mc->reg == mc->rreg) {
+ val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->rshift, max);
+ } else {
+ reg_val = snd_soc_component_read(component, mc->rreg);
+ val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max);
+ }
+
+ ucontrol->value.integer.value[1] = val;
}
return 0;
}
-EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
/**
- * snd_soc_put_volsw - single mixer put callback
+ * snd_soc_info_volsw - single mixer info callback with range.
* @kcontrol: mixer control
- * @ucontrol: control element information
+ * @uinfo: control element information
*
- * Callback to set the value of a single mixer control, or a double mixer
- * control that spans 2 registers.
+ * Callback to provide information, with a range, about a single mixer control,
+ * or a double mixer control that spans 2 registers.
*
* Returns 0 for success.
*/
-int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg = mc->reg;
- unsigned int reg2 = mc->rreg;
- unsigned int shift = mc->shift;
- unsigned int rshift = mc->rshift;
- int max = mc->max;
- int min = mc->min;
- unsigned int sign_bit = mc->sign_bit;
- unsigned int mask = (1 << fls(max)) - 1;
- unsigned int invert = mc->invert;
- int err, ret;
- bool type_2r = false;
- unsigned int val2 = 0;
- unsigned int val, val_mask;
- if (sign_bit)
- mask = BIT(sign_bit + 1) - 1;
-
- if (ucontrol->value.integer.value[0] < 0)
- return -EINVAL;
- val = ucontrol->value.integer.value[0];
- if (mc->platform_max && val > mc->platform_max)
- return -EINVAL;
- if (val > max - min)
- return -EINVAL;
- val = (val + min) & mask;
- if (invert)
- val = max - val;
- val_mask = mask << shift;
- val = val << shift;
- if (snd_soc_volsw_is_stereo(mc)) {
- if (ucontrol->value.integer.value[1] < 0)
- return -EINVAL;
- val2 = ucontrol->value.integer.value[1];
- if (mc->platform_max && val2 > mc->platform_max)
- return -EINVAL;
- if (val2 > max - min)
- return -EINVAL;
- val2 = (val2 + min) & mask;
- if (invert)
- val2 = max - val2;
- if (reg == reg2) {
- val_mask |= mask << rshift;
- val |= val2 << rshift;
- } else {
- val2 = val2 << shift;
- type_2r = true;
- }
- }
- err = snd_soc_component_update_bits(component, reg, val_mask, val);
- if (err < 0)
- return err;
- ret = err;
-
- if (type_2r) {
- err = snd_soc_component_update_bits(component, reg2, val_mask,
- val2);
- /* Don't discard any error code or drop change flag */
- if (ret == 0 || err < 0) {
- ret = err;
- }
- }
-
- return ret;
+ return soc_info_volsw(kcontrol, uinfo, mc, mc->max - mc->min);
}
-EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
/**
- * snd_soc_get_volsw_sx - single mixer get callback
+ * snd_soc_info_volsw_sx - Mixer info callback for SX TLV controls
* @kcontrol: mixer control
- * @ucontrol: control element information
+ * @uinfo: control element information
*
- * Callback to get the value of a single mixer control, or a double mixer
- * control that spans 2 registers.
+ * Callback to provide information about a single mixer control, or a double
+ * mixer control that spans 2 registers of the SX TLV type. SX TLV controls
+ * have a range that represents both positive and negative values either side
+ * of zero but without a sign bit. min is the minimum register value, max is
+ * the number of steps.
*
* Returns 0 for success.
*/
-int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int snd_soc_info_volsw_sx(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg = mc->reg;
- unsigned int reg2 = mc->rreg;
- unsigned int shift = mc->shift;
- unsigned int rshift = mc->rshift;
- int max = mc->max;
- int min = mc->min;
- unsigned int mask = (1U << (fls(min + max) - 1)) - 1;
- unsigned int val;
-
- val = snd_soc_component_read(component, reg);
- ucontrol->value.integer.value[0] = ((val >> shift) - min) & mask;
-
- if (snd_soc_volsw_is_stereo(mc)) {
- val = snd_soc_component_read(component, reg2);
- val = ((val >> rshift) - min) & mask;
- ucontrol->value.integer.value[1] = val;
- }
+ (struct soc_mixer_control *)kcontrol->private_value;
- return 0;
+ return soc_info_volsw(kcontrol, uinfo, mc, mc->max);
}
-EXPORT_SYMBOL_GPL(snd_soc_get_volsw_sx);
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_sx);
/**
- * snd_soc_put_volsw_sx - double mixer set callback
+ * snd_soc_get_volsw - single mixer get callback with range
* @kcontrol: mixer control
* @ucontrol: control element information
*
- * Callback to set the value of a double mixer control that spans 2 registers.
+ * Callback to get the value, within a range, of a single mixer control, or a
+ * double mixer control that spans 2 registers.
*
* Returns 0 for success.
*/
-int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
-
- unsigned int reg = mc->reg;
- unsigned int reg2 = mc->rreg;
- unsigned int shift = mc->shift;
- unsigned int rshift = mc->rshift;
- int max = mc->max;
- int min = mc->min;
- unsigned int mask = (1U << (fls(min + max) - 1)) - 1;
- int err = 0;
- int ret;
- unsigned int val, val_mask;
-
- if (ucontrol->value.integer.value[0] < 0)
- return -EINVAL;
- val = ucontrol->value.integer.value[0];
- if (mc->platform_max && val > mc->platform_max)
- return -EINVAL;
- if (val > max)
- return -EINVAL;
- val_mask = mask << shift;
- val = (val + min) & mask;
- val = val << shift;
-
- err = snd_soc_component_update_bits(component, reg, val_mask, val);
- if (err < 0)
- return err;
- ret = err;
-
- if (snd_soc_volsw_is_stereo(mc)) {
- unsigned int val2 = ucontrol->value.integer.value[1];
-
- if (mc->platform_max && val2 > mc->platform_max)
- return -EINVAL;
- if (val2 > max)
- return -EINVAL;
-
- val_mask = mask << rshift;
- val2 = (val2 + min) & mask;
- val2 = val2 << rshift;
-
- err = snd_soc_component_update_bits(component, reg2, val_mask,
- val2);
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int mask = soc_mixer_mask(mc);
- /* Don't discard any error code or drop change flag */
- if (ret == 0 || err < 0) {
- ret = err;
- }
- }
- return ret;
+ return soc_get_volsw(kcontrol, ucontrol, mc, mask, mc->max - mc->min);
}
-EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx);
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
/**
- * snd_soc_info_volsw_range - single mixer info callback with range.
+ * snd_soc_put_volsw - single mixer put callback with range
* @kcontrol: mixer control
- * @uinfo: control element information
+ * @ucontrol: control element information
*
- * Callback to provide information, within a range, about a single
- * mixer control.
+ * Callback to set the value , within a range, of a single mixer control, or
+ * a double mixer control that spans 2 registers.
*
- * returns 0 for success.
+ * Returns 0 for success.
*/
-int snd_soc_info_volsw_range(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- int max;
-
- max = mc->max - mc->min;
- if (mc->platform_max && mc->platform_max < max)
- max = mc->platform_max;
+ unsigned int mask = soc_mixer_mask(mc);
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = max;
-
- return 0;
+ return soc_put_volsw(kcontrol, ucontrol, mc, mask, mc->max - mc->min);
}
-EXPORT_SYMBOL_GPL(snd_soc_info_volsw_range);
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
/**
- * snd_soc_put_volsw_range - single mixer put value callback with range.
+ * snd_soc_get_volsw_sx - single mixer get callback
* @kcontrol: mixer control
* @ucontrol: control element information
*
- * Callback to set the value, within a range, for a single mixer control.
+ * Callback to get the value of a single mixer control, or a double mixer
+ * control that spans 2 registers.
*
* Returns 0 for success.
*/
-int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- unsigned int reg = mc->reg;
- unsigned int rreg = mc->rreg;
- unsigned int shift = mc->shift;
- int min = mc->min;
- int max = mc->max;
- unsigned int mask = (1 << fls(max)) - 1;
- unsigned int invert = mc->invert;
- unsigned int val, val_mask;
- int err, ret, tmp;
-
- tmp = ucontrol->value.integer.value[0];
- if (tmp < 0)
- return -EINVAL;
- if (mc->platform_max && tmp > mc->platform_max)
- return -EINVAL;
- if (tmp > mc->max - mc->min)
- return -EINVAL;
+ unsigned int mask = soc_mixer_sx_mask(mc);
- if (invert)
- val = (max - ucontrol->value.integer.value[0]) & mask;
- else
- val = ((ucontrol->value.integer.value[0] + min) & mask);
- val_mask = mask << shift;
- val = val << shift;
-
- err = snd_soc_component_update_bits(component, reg, val_mask, val);
- if (err < 0)
- return err;
- ret = err;
-
- if (snd_soc_volsw_is_stereo(mc)) {
- tmp = ucontrol->value.integer.value[1];
- if (tmp < 0)
- return -EINVAL;
- if (mc->platform_max && tmp > mc->platform_max)
- return -EINVAL;
- if (tmp > mc->max - mc->min)
- return -EINVAL;
-
- if (invert)
- val = (max - ucontrol->value.integer.value[1]) & mask;
- else
- val = ((ucontrol->value.integer.value[1] + min) & mask);
- val_mask = mask << shift;
- val = val << shift;
-
- err = snd_soc_component_update_bits(component, rreg, val_mask,
- val);
- /* Don't discard any error code or drop change flag */
- if (ret == 0 || err < 0) {
- ret = err;
- }
- }
-
- return ret;
+ return soc_get_volsw(kcontrol, ucontrol, mc, mask, mc->max);
}
-EXPORT_SYMBOL_GPL(snd_soc_put_volsw_range);
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_sx);
/**
- * snd_soc_get_volsw_range - single mixer get callback with range
+ * snd_soc_put_volsw_sx - double mixer set callback
* @kcontrol: mixer control
* @ucontrol: control element information
*
- * Callback to get the value, within a range, of a single mixer control.
+ * Callback to set the value of a double mixer control that spans 2 registers.
*
* Returns 0 for success.
*/
-int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg = mc->reg;
- unsigned int rreg = mc->rreg;
- unsigned int shift = mc->shift;
- int min = mc->min;
- int max = mc->max;
- unsigned int mask = (1 << fls(max)) - 1;
- unsigned int invert = mc->invert;
- unsigned int val;
-
- val = snd_soc_component_read(component, reg);
- ucontrol->value.integer.value[0] = (val >> shift) & mask;
- if (invert)
- ucontrol->value.integer.value[0] =
- max - ucontrol->value.integer.value[0];
- else
- ucontrol->value.integer.value[0] =
- ucontrol->value.integer.value[0] - min;
+ unsigned int mask = soc_mixer_sx_mask(mc);
- if (snd_soc_volsw_is_stereo(mc)) {
- val = snd_soc_component_read(component, rreg);
- ucontrol->value.integer.value[1] = (val >> shift) & mask;
- if (invert)
- ucontrol->value.integer.value[1] =
- max - ucontrol->value.integer.value[1];
- else
- ucontrol->value.integer.value[1] =
- ucontrol->value.integer.value[1] - min;
- }
-
- return 0;
+ return soc_put_volsw(kcontrol, ucontrol, mc, mask, mc->max);
}
-EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range);
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx);
static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl)
{
@@ -663,8 +432,7 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl)
*
* Return 0 for success, else error.
*/
-int snd_soc_limit_volume(struct snd_soc_card *card,
- const char *name, int max)
+int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max)
{
struct snd_kcontrol *kctl;
int ret = -EINVAL;
@@ -675,12 +443,15 @@ int snd_soc_limit_volume(struct snd_soc_card *card,
kctl = snd_soc_card_get_kcontrol(card, name);
if (kctl) {
- struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kctl->private_value;
+
if (max <= mc->max - mc->min) {
mc->platform_max = max;
ret = snd_soc_clip_to_platform_max(kctl);
}
}
+
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
@@ -740,8 +511,8 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol,
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_bytes *params = (void *)kcontrol->private_value;
- int ret, len;
unsigned int val, mask;
+ int ret, len;
if (!component->regmap || !params->num_regs)
return -EINVAL;
@@ -772,15 +543,13 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol,
break;
case 2:
mask = ~params->mask;
- ret = regmap_parse_val(component->regmap,
- &mask, &mask);
+ ret = regmap_parse_val(component->regmap, &mask, &mask);
if (ret != 0)
return ret;
((u16 *)data)[0] &= mask;
- ret = regmap_parse_val(component->regmap,
- &val, &val);
+ ret = regmap_parse_val(component->regmap, &val, &val);
if (ret != 0)
return ret;
@@ -788,15 +557,13 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol,
break;
case 4:
mask = ~params->mask;
- ret = regmap_parse_val(component->regmap,
- &mask, &mask);
+ ret = regmap_parse_val(component->regmap, &mask, &mask);
if (ret != 0)
return ret;
((u32 *)data)[0] &= mask;
- ret = regmap_parse_val(component->regmap,
- &val, &val);
+ ret = regmap_parse_val(component->regmap, &val, &val);
if (ret != 0)
return ret;
@@ -812,7 +579,7 @@ int snd_soc_bytes_put(struct snd_kcontrol *kcontrol,
EXPORT_SYMBOL_GPL(snd_soc_bytes_put);
int snd_soc_bytes_info_ext(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *ucontrol)
+ struct snd_ctl_elem_info *ucontrol)
{
struct soc_bytes_ext *params = (void *)kcontrol->private_value;
@@ -824,7 +591,7 @@ int snd_soc_bytes_info_ext(struct snd_kcontrol *kcontrol,
EXPORT_SYMBOL_GPL(snd_soc_bytes_info_ext);
int snd_soc_bytes_tlv_callback(struct snd_kcontrol *kcontrol, int op_flag,
- unsigned int size, unsigned int __user *tlv)
+ unsigned int size, unsigned int __user *tlv)
{
struct soc_bytes_ext *params = (void *)kcontrol->private_value;
unsigned int count = size < params->max ? size : params->max;
@@ -840,6 +607,7 @@ int snd_soc_bytes_tlv_callback(struct snd_kcontrol *kcontrol, int op_flag,
ret = params->put(kcontrol, tlv, count);
break;
}
+
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_bytes_tlv_callback);
@@ -849,17 +617,19 @@ EXPORT_SYMBOL_GPL(snd_soc_bytes_tlv_callback);
* @kcontrol: mreg control
* @uinfo: control element information
*
- * Callback to provide information of a control that can
- * span multiple codec registers which together
- * forms a single signed value in a MSB/LSB manner.
+ * Callback to provide information of a control that can span multiple
+ * codec registers which together forms a single signed value. Note
+ * that unlike the non-xr variant of sx controls these may or may not
+ * include the sign bit, depending on nbits, and there is no shift.
*
* Returns 0 for success.
*/
int snd_soc_info_xr_sx(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+ struct snd_ctl_elem_info *uinfo)
{
struct soc_mreg_control *mc =
(struct soc_mreg_control *)kcontrol->private_value;
+
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = mc->min;
@@ -874,16 +644,17 @@ EXPORT_SYMBOL_GPL(snd_soc_info_xr_sx);
* @kcontrol: mreg control
* @ucontrol: control element information
*
- * Callback to get the value of a control that can span
- * multiple codec registers which together forms a single
- * signed value in a MSB/LSB manner. The control supports
- * specifying total no of bits used to allow for bitfields
- * across the multiple codec registers.
+ * Callback to get the value of a control that can span multiple codec
+ * registers which together forms a single signed value. The control
+ * supports specifying total no of bits used to allow for bitfields
+ * across the multiple codec registers. Note that unlike the non-xr
+ * variant of sx controls these may or may not include the sign bit,
+ * depending on nbits, and there is no shift.
*
* Returns 0 for success.
*/
int snd_soc_get_xr_sx(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+ struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mreg_control *mc =
@@ -891,23 +662,21 @@ int snd_soc_get_xr_sx(struct snd_kcontrol *kcontrol,
unsigned int regbase = mc->regbase;
unsigned int regcount = mc->regcount;
unsigned int regwshift = component->val_bytes * BITS_PER_BYTE;
- unsigned int regwmask = (1UL<<regwshift)-1;
- unsigned int invert = mc->invert;
- unsigned long mask = (1UL<<mc->nbits)-1;
- long min = mc->min;
- long max = mc->max;
+ unsigned int regwmask = GENMASK(regwshift - 1, 0);
+ unsigned long mask = GENMASK(mc->nbits - 1, 0);
long val = 0;
unsigned int i;
for (i = 0; i < regcount; i++) {
- unsigned int regval = snd_soc_component_read(component, regbase+i);
- val |= (regval & regwmask) << (regwshift*(regcount-i-1));
+ unsigned int regval = snd_soc_component_read(component, regbase + i);
+
+ val |= (regval & regwmask) << (regwshift * (regcount - i - 1));
}
val &= mask;
- if (min < 0 && val > max)
+ if (mc->min < 0 && val > mc->max)
val |= ~mask;
- if (invert)
- val = max - val;
+ if (mc->invert)
+ val = mc->max - val;
ucontrol->value.integer.value[0] = val;
return 0;
@@ -919,16 +688,17 @@ EXPORT_SYMBOL_GPL(snd_soc_get_xr_sx);
* @kcontrol: mreg control
* @ucontrol: control element information
*
- * Callback to set the value of a control that can span
- * multiple codec registers which together forms a single
- * signed value in a MSB/LSB manner. The control supports
- * specifying total no of bits used to allow for bitfields
- * across the multiple codec registers.
+ * Callback to set the value of a control that can span multiple codec
+ * registers which together forms a single signed value. The control
+ * supports specifying total no of bits used to allow for bitfields
+ * across the multiple codec registers. Note that unlike the non-xr
+ * variant of sx controls these may or may not include the sign bit,
+ * depending on nbits, and there is no shift.
*
* Returns 0 for success.
*/
int snd_soc_put_xr_sx(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+ struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mreg_control *mc =
@@ -936,24 +706,25 @@ int snd_soc_put_xr_sx(struct snd_kcontrol *kcontrol,
unsigned int regbase = mc->regbase;
unsigned int regcount = mc->regcount;
unsigned int regwshift = component->val_bytes * BITS_PER_BYTE;
- unsigned int regwmask = (1UL<<regwshift)-1;
- unsigned int invert = mc->invert;
- unsigned long mask = (1UL<<mc->nbits)-1;
- long max = mc->max;
+ unsigned int regwmask = GENMASK(regwshift - 1, 0);
+ unsigned long mask = GENMASK(mc->nbits - 1, 0);
long val = ucontrol->value.integer.value[0];
int ret = 0;
unsigned int i;
if (val < mc->min || val > mc->max)
return -EINVAL;
- if (invert)
- val = max - val;
+ if (mc->invert)
+ val = mc->max - val;
val &= mask;
for (i = 0; i < regcount; i++) {
- unsigned int regval = (val >> (regwshift*(regcount-i-1))) & regwmask;
- unsigned int regmask = (mask >> (regwshift*(regcount-i-1))) & regwmask;
- int err = snd_soc_component_update_bits(component, regbase+i,
+ unsigned int regval = (val >> (regwshift * (regcount - i - 1))) &
+ regwmask;
+ unsigned int regmask = (mask >> (regwshift * (regcount - i - 1))) &
+ regwmask;
+ int err = snd_soc_component_update_bits(component, regbase + i,
regmask, regval);
+
if (err < 0)
return err;
if (err > 0)
@@ -974,22 +745,21 @@ EXPORT_SYMBOL_GPL(snd_soc_put_xr_sx);
* Returns 0 for success.
*/
int snd_soc_get_strobe(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+ struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg = mc->reg;
- unsigned int shift = mc->shift;
- unsigned int mask = 1 << shift;
unsigned int invert = mc->invert != 0;
+ unsigned int mask = BIT(mc->shift);
unsigned int val;
- val = snd_soc_component_read(component, reg);
+ val = snd_soc_component_read(component, mc->reg);
val &= mask;
- if (shift != 0 && val != 0)
- val = val >> shift;
+ if (mc->shift != 0 && val != 0)
+ val = val >> mc->shift;
+
ucontrol->value.enumerated.item[0] = val ^ invert;
return 0;
@@ -1007,24 +777,22 @@ EXPORT_SYMBOL_GPL(snd_soc_get_strobe);
* Returns 1 for success.
*/
int snd_soc_put_strobe(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+ struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg = mc->reg;
- unsigned int shift = mc->shift;
- unsigned int mask = 1 << shift;
- unsigned int invert = mc->invert != 0;
unsigned int strobe = ucontrol->value.enumerated.item[0] != 0;
+ unsigned int invert = mc->invert != 0;
+ unsigned int mask = BIT(mc->shift);
unsigned int val1 = (strobe ^ invert) ? mask : 0;
unsigned int val2 = (strobe ^ invert) ? 0 : mask;
- int err;
+ int ret;
- err = snd_soc_component_update_bits(component, reg, mask, val1);
- if (err < 0)
- return err;
+ ret = snd_soc_component_update_bits(component, mc->reg, mask, val1);
+ if (ret < 0)
+ return ret;
- return snd_soc_component_update_bits(component, reg, mask, val2);
+ return snd_soc_component_update_bits(component, mc->reg, mask, val2);
}
EXPORT_SYMBOL_GPL(snd_soc_put_strobe);
diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c
index 2b86cc3311f7..7b0b8531bb32 100644
--- a/sound/soc/soc-topology.c
+++ b/sound/soc/soc-topology.c
@@ -131,8 +131,8 @@ static const struct snd_soc_tplg_kcontrol_ops io_ops[] = {
snd_soc_put_enum_double, NULL},
{SND_SOC_TPLG_CTL_BYTES, snd_soc_bytes_get,
snd_soc_bytes_put, snd_soc_bytes_info},
- {SND_SOC_TPLG_CTL_RANGE, snd_soc_get_volsw_range,
- snd_soc_put_volsw_range, snd_soc_info_volsw_range},
+ {SND_SOC_TPLG_CTL_RANGE, snd_soc_get_volsw,
+ snd_soc_put_volsw, snd_soc_info_volsw},
{SND_SOC_TPLG_CTL_VOLSW_XR_SX, snd_soc_get_xr_sx,
snd_soc_put_xr_sx, snd_soc_info_xr_sx},
{SND_SOC_TPLG_CTL_STROBE, snd_soc_get_strobe,