]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ASoC: Intel: catpt: New volume and mute control operations
authorCezary Rojewski <cezary.rojewski@intel.com>
Mon, 9 Mar 2026 09:16:02 +0000 (10:16 +0100)
committerMark Brown <broonie@kernel.org>
Wed, 11 Mar 2026 13:34:10 +0000 (13:34 +0000)
The catpt-driver's volume and mute control operations always return '0'
regardless if a change occurred or not. To conform to ALSA's interface,
value '1' shall be returned when a change occurred.

The second major point is power consumption. Existing control operations
always wake the DSP even if no streams are running. In such case waking
the DSP just for the sake of updating the volume (or mute) settings on
the firmware side is a waste of power. The provided implementation
caches the values and updates the settings only when streams are being
opened for streaming or are already running.

As changing existing code is non-trivial, provide new operations
instead. The put() operation, which interests us the most, takes the
following shape:

// two values provided to put():
// pin_id - which stream given control relates to
// value_to_apply - the value from user

if (control->existing_val == value_to_apply)
return 0;

runtime_stream = get_running_stream(pin_id);
if (runtime_stream != NULL) {
ret = send_ipc();
if (ret)
return ret;
}

control->existing_val = value_to_apply;
return 1;

Adheres to ALSA's expectation and avoids sending IPCs if there is no
change to be made.

Two helpers which are part of the patch, catpt_stream_hw_id() and
catpt_stream_volume_regs(), help differentiate between individual
streams and the general MIXER stream. Translates to one pair of
get()/put() instead of two pairs as done currently.

PIN_ID_INVALID is returned if given stream is not currently running -
the constant is part of the firmware's API but remained unused by the
driver.

Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
Link: https://patch.msgid.link/20260309091605.896307-3-cezary.rojewski@intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/intel/catpt/messages.h
sound/soc/intel/catpt/pcm.c

index a634943eb669305be1174165b2c28fcb982429c5..fcc9f1b46bc2972c98eeaebda931f5af5e9dcb8d 100644 (file)
@@ -69,6 +69,7 @@ struct catpt_fw_version {
 int catpt_ipc_get_fw_version(struct catpt_dev *cdev,
                             struct catpt_fw_version *version);
 
+/* PIN_IDs represent both, individual streams and the general mixer. */
 enum catpt_pin_id {
        CATPT_PIN_ID_SYSTEM = 0,
        CATPT_PIN_ID_REFERENCE = 1,
@@ -79,6 +80,8 @@ enum catpt_pin_id {
        CATPT_PIN_ID_MIXER = 7,
        CATPT_PIN_ID_BLUETOOTH_CAPTURE = 8,
        CATPT_PIN_ID_BLUETOOTH_RENDER = 9,
+       /* 10 is reserved */
+       CATPT_PIN_ID_INVALID = 11,
 };
 
 enum catpt_path_id {
index fbe4821755bda3aa277caca2598261e9841e5b8f..08f1e36a136f6dcb38ea64785e0ce00d2ff404b1 100644 (file)
@@ -114,6 +114,46 @@ catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
        return result;
 }
 
+/* Caller responsible for holding ->stream_mutex. */
+static u8 catpt_stream_hw_id(struct catpt_dev *cdev, enum catpt_pin_id pin_id)
+{
+       struct catpt_stream_runtime *stream;
+
+       switch (pin_id) {
+       default:
+               stream = catpt_stream_find(cdev, pin_id);
+               if (stream)
+                       return stream->info.stream_hw_id;
+               break;
+       case CATPT_PIN_ID_MIXER:
+               if (!list_empty(&cdev->stream_list))
+                       return cdev->mixer.mixer_hw_id;
+               break;
+       }
+
+       return CATPT_PIN_ID_INVALID;
+}
+
+/* Caller responsible for holding ->stream_mutex. */
+static u32 *catpt_stream_volume_regs(struct catpt_dev *cdev, enum catpt_pin_id pin_id)
+{
+       struct catpt_stream_runtime *stream;
+
+       switch (pin_id) {
+       case CATPT_PIN_ID_MIXER:
+               if (!list_empty(&cdev->stream_list))
+                       return &cdev->mixer.volume_regaddr[0];
+               break;
+       default:
+               stream = catpt_stream_find(cdev, pin_id);
+               if (stream)
+                       return &stream->info.volume_regaddr[0];
+               break;
+       }
+
+       return NULL;
+}
+
 static void catpt_stream_read_position(struct catpt_dev *cdev,
                                       struct catpt_stream_runtime *stream, u32 *pos)
 {
@@ -314,6 +354,11 @@ static void catpt_dai_shutdown(struct snd_pcm_substream *substream,
 
 static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol);
 
+struct catpt_control_data {
+       enum catpt_pin_id pin_id;
+       long volumes[CATPT_CHANNELS_MAX];
+};
+
 static int catpt_dai_apply_usettings(struct snd_soc_dai *dai,
                                     struct catpt_stream_runtime *stream)
 {
@@ -855,6 +900,97 @@ static int catpt_volume_info(struct snd_kcontrol *kcontrol,
        return 0;
 }
 
+__maybe_unused
+static int catpt_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kctl);
+       struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+       struct catpt_control_data *data;
+       u32 dspvol, *regs;
+       long *uvolumes;
+       int i;
+
+       data = (struct catpt_control_data *)kctl->private_value;
+       uvolumes = &uctl->value.integer.value[0];
+
+       guard(mutex)(&cdev->stream_mutex);
+
+       regs = catpt_stream_volume_regs(cdev, data->pin_id);
+       if (regs) {
+               for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+                       dspvol = readl(cdev->lpe_ba + regs[i]);
+                       data->volumes[i] = dspvol_to_ctlvol(dspvol);
+               }
+       }
+
+       memcpy(uvolumes, data->volumes, sizeof(data->volumes));
+       return 0;
+}
+
+__maybe_unused
+static int catpt_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kctl);
+       struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+       struct catpt_control_data *data;
+       u8 stream_hw_id;
+       long *uvolumes;
+       int ret;
+
+       data = (struct catpt_control_data *)kctl->private_value;
+       uvolumes = &uctl->value.integer.value[0];
+
+       if (!memcmp(data->volumes, uvolumes, sizeof(data->volumes)))
+               return 0;
+
+       guard(mutex)(&cdev->stream_mutex);
+
+       stream_hw_id = catpt_stream_hw_id(cdev, data->pin_id);
+       if (stream_hw_id != CATPT_PIN_ID_INVALID) {
+               ret = catpt_set_dspvol(cdev, stream_hw_id, uvolumes);
+               if (ret)
+                       return ret;
+       }
+
+       memcpy(data->volumes, uvolumes, sizeof(data->volumes));
+       return 1;
+}
+
+__maybe_unused
+static int catpt_loopback_mute_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
+{
+       uctl->value.integer.value[0] = *(bool *)kctl->private_value;
+       return 0;
+}
+
+__maybe_unused
+static int catpt_loopback_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kctl);
+       struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+       bool *kmute, cmute;
+       u8 stream_hw_id;
+       int ret;
+
+       kmute = (bool *)kctl->private_value;
+       cmute = (bool)uctl->value.integer.value[0];
+
+       if (*kmute == cmute)
+               return 0;
+
+       guard(mutex)(&cdev->stream_mutex);
+
+       stream_hw_id = catpt_stream_hw_id(cdev, CATPT_PIN_ID_REFERENCE);
+       if (stream_hw_id != CATPT_PIN_ID_INVALID) {
+               ret = catpt_ipc_mute_loopback(cdev, stream_hw_id, cmute);
+               if (ret)
+                       return CATPT_IPC_RET(ret);
+       }
+
+       *kmute = cmute;
+       return 1;
+}
+
 static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
 {
@@ -1072,6 +1208,19 @@ static int catpt_waves_param_put(struct snd_kcontrol *kcontrol,
 
 static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1);
 
+#define CATPT_VOLUME_CTL2(kname, pname) {              \
+       .iface  = SNDRV_CTL_ELEM_IFACE_MIXER,           \
+       .name   = kname,                                \
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |     \
+                 SNDRV_CTL_ELEM_ACCESS_TLV_READ,       \
+       .info   = catpt_volume_info,                    \
+       .get    = catpt_volume_get,                     \
+       .put    = catpt_volume_put,                     \
+       .tlv.p  = catpt_volume_tlv,                     \
+       .private_value = (unsigned long)                \
+               &(struct catpt_control_data) { CATPT_PIN_ID_##pname } \
+}
+
 #define CATPT_VOLUME_CTL(kname, sname) \
 {      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
        .name = (kname), \