]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ASoC: SDCA: support Q7.8 volume format
authorShuming Fan <shumingf@realtek.com>
Thu, 6 Nov 2025 09:33:35 +0000 (17:33 +0800)
committerMark Brown <broonie@kernel.org>
Thu, 6 Nov 2025 13:05:44 +0000 (13:05 +0000)
The SDCA specification uses Q7.8 volume format.
This patch adds a field to indicate whether it is SDCA volume control
and supports the volume settings.

Signed-off-by: Shuming Fan <shumingf@realtek.com>
Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://patch.msgid.link/20251106093335.1363237-1-shumingf@realtek.com
Signed-off-by: Mark Brown <broonie@kernel.org>
include/sound/soc.h
sound/soc/sdca/sdca_asoc.c
sound/soc/soc-ops.c

index 1aebf14fcf80a3e17bf219e23527b99977f13431..53b4129ee97a845d58af672b6fe8713d0e328527 100644 (file)
@@ -1225,6 +1225,7 @@ struct soc_mixer_control {
        unsigned int sign_bit;
        unsigned int invert:1;
        unsigned int autodisable:1;
+       unsigned int sdca_q78:1;
 #ifdef CONFIG_SND_SOC_TOPOLOGY
        struct snd_soc_dobj dobj;
 #endif
index c493ec530cc5c80a6f5e509e9f6821992dbc07df..892b7c028faea84c89ccc1ae99e84b0b2a91ac5f 100644 (file)
@@ -795,7 +795,6 @@ static int control_limit_kctl(struct device *dev,
        struct sdca_control_range *range;
        int min, max, step;
        unsigned int *tlv;
-       int shift;
 
        if (control->type != SDCA_CTL_DATATYPE_Q7P8DB)
                return 0;
@@ -814,37 +813,22 @@ static int control_limit_kctl(struct device *dev,
        min = sign_extend32(min, control->nbits - 1);
        max = sign_extend32(max, control->nbits - 1);
 
-       /*
-        * FIXME: Only support power of 2 step sizes as this can be supported
-        * by a simple shift.
-        */
-       if (hweight32(step) != 1) {
-               dev_err(dev, "%s: %s: currently unsupported step size\n",
-                       entity->label, control->label);
-               return -EINVAL;
-       }
-
-       /*
-        * The SDCA volumes are in steps of 1/256th of a dB, a step down of
-        * 64 (shift of 6) gives 1/4dB. 1/4dB is the smallest unit that is also
-        * representable in the ALSA TLVs which are in 1/100ths of a dB.
-        */
-       shift = max(ffs(step) - 1, 6);
-
        tlv = devm_kcalloc(dev, 4, sizeof(*tlv), GFP_KERNEL);
        if (!tlv)
                return -ENOMEM;
 
-       tlv[0] = SNDRV_CTL_TLVT_DB_SCALE;
+       tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX;
        tlv[1] = 2 * sizeof(*tlv);
        tlv[2] = (min * 100) >> 8;
-       tlv[3] = ((1 << shift) * 100) >> 8;
+       tlv[3] = (max * 100) >> 8;
+
+       step = (step * 100) >> 8;
 
-       mc->min = min >> shift;
-       mc->max = max >> shift;
-       mc->shift = shift;
-       mc->rshift = shift;
-       mc->sign_bit = 15 - shift;
+       mc->min = ((int)tlv[2] / step);
+       mc->max = ((int)tlv[3] / step);
+       mc->shift = step;
+       mc->sign_bit = 15;
+       mc->sdca_q78 = 1;
 
        kctl->tlv.p = tlv;
        kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
index d2b6fb8e0b6c695a0ac3f5b38be940dc8a589ab3..ce86978c158d697e3764a7658a6b757b95166de8 100644 (file)
@@ -110,6 +110,36 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
 }
 EXPORT_SYMBOL_GPL(snd_soc_put_enum_double);
 
+static int sdca_soc_q78_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val,
+                               unsigned int mask, unsigned int shift, int max)
+{
+       int val = reg_val;
+
+       if (WARN_ON(!mc->shift))
+               return -EINVAL;
+
+       val = sign_extend32(val, mc->sign_bit);
+       val = (((val * 100) >> 8) / (int)mc->shift);
+       val -= mc->min;
+
+       return val & mask;
+}
+
+static unsigned int sdca_soc_q78_ctl_to_reg(struct soc_mixer_control *mc, int val,
+                                        unsigned int mask, unsigned int shift, int max)
+{
+       unsigned int ret_val;
+       int reg_val;
+
+       if (WARN_ON(!mc->shift))
+               return -EINVAL;
+
+       reg_val = val + mc->min;
+       ret_val = (int)((reg_val * mc->shift) << 8) / 100;
+
+       return ret_val & mask;
+}
+
 static int soc_mixer_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val,
                                unsigned int mask, unsigned int shift, int max)
 {
@@ -197,19 +227,27 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol,
                         struct soc_mixer_control *mc, int mask, int max)
 {
+       unsigned int (*ctl_to_reg)(struct soc_mixer_control *, int, unsigned int, unsigned int, int);
        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->sdca_q78) {
+               ctl_to_reg = sdca_soc_q78_ctl_to_reg;
+               val_mask = mask;
+       } else {
+               ctl_to_reg = soc_mixer_ctl_to_reg;
+               val_mask = mask << mc->shift;
+       }
+
        ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[0], max);
        if (ret)
                return ret;
 
-       val1 = soc_mixer_ctl_to_reg(mc, ucontrol->value.integer.value[0],
+       val1 = ctl_to_reg(mc, ucontrol->value.integer.value[0],
                                    mask, mc->shift, max);
-       val_mask = mask << mc->shift;
 
        if (snd_soc_volsw_is_stereo(mc)) {
                ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[1], max);
@@ -217,14 +255,10 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol,
                        return ret;
 
                if (mc->reg == mc->rreg) {
-                       val1 |= soc_mixer_ctl_to_reg(mc,
-                                                    ucontrol->value.integer.value[1],
-                                                    mask, mc->rshift, max);
+                       val1 |= 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);
+                       val2 = ctl_to_reg(mc, ucontrol->value.integer.value[1], mask, mc->shift, max);
                        double_r = true;
                }
        }
@@ -248,21 +282,27 @@ static int soc_get_volsw(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol,
                         struct soc_mixer_control *mc, int mask, int max)
 {
+       int (*reg_to_ctl)(struct soc_mixer_control *, unsigned int, unsigned int, unsigned int, int);
        struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
        unsigned int reg_val;
        int val;
 
+       if (mc->sdca_q78)
+               reg_to_ctl = sdca_soc_q78_reg_to_ctl;
+       else
+               reg_to_ctl = soc_mixer_reg_to_ctl;
+
        reg_val = snd_soc_component_read(component, mc->reg);
-       val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max);
+       val = reg_to_ctl(mc, reg_val, mask, mc->shift, max);
 
        ucontrol->value.integer.value[0] = val;
 
        if (snd_soc_volsw_is_stereo(mc)) {
                if (mc->reg == mc->rreg) {
-                       val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->rshift, max);
+                       val = 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);
+                       val = reg_to_ctl(mc, reg_val, mask, mc->shift, max);
                }
 
                ucontrol->value.integer.value[1] = val;