Merge patch series "ASoC: Intel: catpt: Overhaul volume and mute control operations"

Cezary Rojewski <cezary.rojewski@intel.com> says:

ASoC: Intel: catpt: Overhaul volume and mute control operations

Short summary first, longer description later:

- fix the return code for kctl->put() - currently the driver returns
  '0' even if changes were done
- lower power consumption for kctl operations by waking the DSP only
  when some streaming is done.  No streaming? Cache the values and
  inform the AudioDSP firmware later.
- drop the existing code duplication between individual and master
  volume controls

The very first patch addresses synchronization problem that exists when
an individual control and its paired stream are manipulated by a user
simultaneously.  As the refactor integrates the fix in its new code,
most of it gets shuffled but in case of a "refactor problem" I've
decided to have it separated and leading the series.  This way the
problem is fixed even if refactor, for whatever reason, would be
reverted.

--
More of in-depth explanation for the refactor, taken from commit 2:

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.
This commit is contained in:
Mark Brown 2026-03-11 13:34:14 +00:00
commit 94ef278e74
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
6 changed files with 181 additions and 239 deletions

View File

@ -96,7 +96,7 @@ struct catpt_dev {
struct catpt_module_type modules[CATPT_MODULE_COUNT];
struct catpt_ssp_device_format devfmt[CATPT_SSP_COUNT];
struct list_head stream_list;
spinlock_t list_lock;
struct mutex stream_mutex;
struct mutex clk_mutex;
struct catpt_dx_context dx_ctx;

View File

@ -226,7 +226,7 @@ static void catpt_dev_init(struct catpt_dev *cdev, struct device *dev,
cdev->spec = spec;
init_completion(&cdev->fw_ready);
INIT_LIST_HEAD(&cdev->stream_list);
spin_lock_init(&cdev->list_lock);
mutex_init(&cdev->stream_mutex);
mutex_init(&cdev->clk_mutex);
/*

View File

@ -5,6 +5,7 @@
// Author: Cezary Rojewski <cezary.rojewski@intel.com>
//
#include <linux/cleanup.h>
#include <linux/irqreturn.h>
#include "core.h"
#include "messages.h"
@ -151,6 +152,8 @@ catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg)
struct catpt_notify_position pos;
struct catpt_notify_glitch glitch;
guard(mutex)(&cdev->stream_mutex);
stream = catpt_stream_find(cdev, msg.stream_hw_id);
if (!stream) {
dev_warn(cdev->dev, "notify %d for non-existent stream %d\n",

View File

@ -90,6 +90,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan)
{
struct catpt_stream_runtime *stream;
/* Lockless as no streams can be added or removed during D3 -> D0 transition. */
list_for_each_entry(stream, &cdev->stream_list, node) {
u32 off, size;
int ret;
@ -180,6 +181,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan)
{
struct catpt_stream_runtime *stream;
/* Lockless as no streams can be added or removed during D3 -> D0 transition. */
list_for_each_entry(stream, &cdev->stream_list, node) {
u32 off, size;
int ret;

View 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 {

View File

@ -5,6 +5,7 @@
// Author: Cezary Rojewski <cezary.rojewski@intel.com>
//
#include <linux/cleanup.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
@ -97,12 +98,12 @@ catpt_get_stream_template(struct snd_pcm_substream *substream)
return catpt_topology[type];
}
/* Caller responsible for holding ->stream_mutex. */
struct catpt_stream_runtime *
catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
{
struct catpt_stream_runtime *pos, *result = NULL;
spin_lock(&cdev->list_lock);
list_for_each_entry(pos, &cdev->stream_list, node) {
if (pos->info.stream_hw_id == stream_hw_id) {
result = pos;
@ -110,42 +111,55 @@ catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
}
}
spin_unlock(&cdev->list_lock);
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)
{
memcpy_fromio(pos, cdev->lpe_ba + stream->info.read_pos_regaddr, sizeof(*pos));
}
static u32 catpt_stream_volume(struct catpt_dev *cdev,
struct catpt_stream_runtime *stream, u32 channel)
{
u32 volume, offset;
if (channel >= CATPT_CHANNELS_MAX)
channel = 0;
offset = stream->info.volume_regaddr[channel];
memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
return volume;
}
static u32 catpt_mixer_volume(struct catpt_dev *cdev,
struct catpt_mixer_stream_info *info, u32 channel)
{
u32 volume, offset;
if (channel >= CATPT_CHANNELS_MAX)
channel = 0;
offset = info->volume_regaddr[channel];
memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
return volume;
}
static void catpt_arrange_page_table(struct snd_pcm_substream *substream,
struct snd_dma_buffer *pgtbl)
{
@ -286,10 +300,6 @@ static int catpt_dai_startup(struct snd_pcm_substream *substream,
INIT_LIST_HEAD(&stream->node);
snd_soc_dai_set_dma_data(dai, substream, stream);
spin_lock(&cdev->list_lock);
list_add_tail(&stream->node, &cdev->stream_list);
spin_unlock(&cdev->list_lock);
return 0;
err_request:
@ -307,10 +317,6 @@ static void catpt_dai_shutdown(struct snd_pcm_substream *substream,
stream = snd_soc_dai_get_dma_data(dai, substream);
spin_lock(&cdev->list_lock);
list_del(&stream->node);
spin_unlock(&cdev->list_lock);
release_resource(stream->persistent);
kfree(stream->persistent);
catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
@ -322,46 +328,62 @@ static void catpt_dai_shutdown(struct snd_pcm_substream *substream,
static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol);
static int catpt_dai_apply_usettings(struct snd_soc_dai *dai,
struct catpt_stream_runtime *stream)
{
struct snd_soc_component *component = dai->component;
struct snd_kcontrol *pos;
struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
const char *name;
int ret;
u32 id = stream->info.stream_hw_id;
struct catpt_control_data {
enum catpt_pin_id pin_id;
long volumes[CATPT_CHANNELS_MAX];
};
/* only selected streams have individual controls */
switch (id) {
static int catpt_apply_volume(struct catpt_dev *cdev, struct snd_soc_card *card, const char *name)
{
struct snd_kcontrol *kctl = snd_ctl_find_id_mixer(card->snd_card, name);
struct catpt_control_data *data;
if (!kctl)
return -ENOENT;
data = (struct catpt_control_data *)kctl->private_value;
return catpt_set_dspvol(cdev, data->pin_id, data->volumes);
}
static int catpt_apply_mute(struct catpt_dev *cdev, struct snd_soc_card *card)
{
struct snd_kcontrol *kctl = snd_ctl_find_id_mixer(card->snd_card, "Loopback Mute");
bool mute;
int ret;
if (!kctl)
return -ENOENT;
mute = *(bool *)kctl->private_value;
ret = catpt_ipc_mute_loopback(cdev, CATPT_PIN_ID_REFERENCE, mute);
return CATPT_IPC_RET(ret);
}
static int catpt_apply_controls(struct catpt_dev *cdev, struct snd_soc_card *card,
struct catpt_stream_runtime *stream)
{
int ret;
/* Update the master volume when the first stream is opened. */
if (list_empty(&cdev->stream_list)) {
ret = catpt_apply_volume(cdev, card, "Master Playback Volume");
if (ret)
return ret;
}
/* Only selected streams have individual controls. */
switch (stream->info.stream_hw_id) {
case CATPT_PIN_ID_OFFLOAD1:
name = "Media0 Playback Volume";
break;
return catpt_apply_volume(cdev, card, "Media0 Playback Volume");
case CATPT_PIN_ID_OFFLOAD2:
name = "Media1 Playback Volume";
break;
return catpt_apply_volume(cdev, card, "Media1 Playback Volume");
case CATPT_PIN_ID_CAPTURE1:
name = "Mic Capture Volume";
break;
return catpt_apply_volume(cdev, card, "Mic Capture Volume");
case CATPT_PIN_ID_REFERENCE:
name = "Loopback Mute";
break;
return catpt_apply_mute(cdev, card);
default:
return 0;
}
list_for_each_entry(pos, &component->card->snd_card->controls, list) {
if (pos->private_data == component &&
!strncmp(name, pos->id.name, sizeof(pos->id.name)))
break;
}
if (list_entry_is_head(pos, &component->card->snd_card->controls, list))
return -ENOENT;
if (stream->template->type != CATPT_STRM_TYPE_LOOPBACK)
return catpt_set_dspvol(cdev, id, (long *)pos->private_value);
ret = catpt_ipc_mute_loopback(cdev, id, *(bool *)pos->private_value);
return CATPT_IPC_RET(ret);
}
static int catpt_dai_hw_params(struct snd_pcm_substream *substream,
@ -410,12 +432,15 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream,
if (ret)
return CATPT_IPC_RET(ret);
ret = catpt_dai_apply_usettings(dai, stream);
guard(mutex)(&cdev->stream_mutex);
ret = catpt_apply_controls(cdev, dai->component->card, stream);
if (ret) {
catpt_ipc_free_stream(cdev, stream->info.stream_hw_id);
return ret;
}
list_add_tail(&stream->node, &cdev->stream_list);
stream->allocated = true;
return 0;
}
@ -430,6 +455,10 @@ static int catpt_dai_hw_free(struct snd_pcm_substream *substream,
if (!stream->allocated)
return 0;
mutex_lock(&cdev->stream_mutex);
list_del(&stream->node);
mutex_unlock(&cdev->stream_mutex);
catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
catpt_ipc_free_stream(cdev, stream->info.stream_hw_id);
@ -856,187 +885,91 @@ static int catpt_volume_info(struct snd_kcontrol *kcontrol,
return 0;
}
static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
static int catpt_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct snd_soc_component *component = snd_kcontrol_chip(kctl);
struct catpt_dev *cdev = dev_get_drvdata(component->dev);
u32 dspvol;
int ret;
struct catpt_control_data *data;
u32 dspvol, *regs;
long *uvolumes;
int i;
ret = pm_runtime_resume_and_get(cdev->dev);
if (ret)
return ret;
data = (struct catpt_control_data *)kctl->private_value;
uvolumes = &uctl->value.integer.value[0];
for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i);
ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
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);
}
}
pm_runtime_put_autosuspend(cdev->dev);
memcpy(uvolumes, data->volumes, sizeof(data->volumes));
return 0;
}
static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
static int catpt_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
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;
ret = pm_runtime_resume_and_get(cdev->dev);
if (ret)
return ret;
data = (struct catpt_control_data *)kctl->private_value;
uvolumes = &uctl->value.integer.value[0];
ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id,
ucontrol->value.integer.value);
pm_runtime_put_autosuspend(cdev->dev);
return ret;
}
static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
enum catpt_pin_id pin_id)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct catpt_stream_runtime *stream;
struct catpt_dev *cdev = dev_get_drvdata(component->dev);
long *ctlvol = (long *)kcontrol->private_value;
u32 dspvol;
int ret;
int i;
stream = catpt_stream_find(cdev, pin_id);
if (!stream) {
for (i = 0; i < CATPT_CHANNELS_MAX; i++)
ucontrol->value.integer.value[i] = ctlvol[i];
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;
}
ret = pm_runtime_resume_and_get(cdev->dev);
if (ret)
return ret;
for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
dspvol = catpt_stream_volume(cdev, stream, i);
ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
}
pm_runtime_put_autosuspend(cdev->dev);
memcpy(data->volumes, uvolumes, sizeof(data->volumes));
return 1;
}
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;
}
static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
enum catpt_pin_id pin_id)
static int catpt_loopback_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct catpt_stream_runtime *stream;
struct snd_soc_component *component = snd_kcontrol_chip(kctl);
struct catpt_dev *cdev = dev_get_drvdata(component->dev);
long *ctlvol = (long *)kcontrol->private_value;
int ret, i;
stream = catpt_stream_find(cdev, pin_id);
if (!stream) {
for (i = 0; i < CATPT_CHANNELS_MAX; i++)
ctlvol[i] = ucontrol->value.integer.value[i];
return 0;
}
ret = pm_runtime_resume_and_get(cdev->dev);
if (ret)
return ret;
ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id,
ucontrol->value.integer.value);
pm_runtime_put_autosuspend(cdev->dev);
if (ret)
return ret;
for (i = 0; i < CATPT_CHANNELS_MAX; i++)
ctlvol[i] = ucontrol->value.integer.value[i];
return 0;
}
static int catpt_offload1_volume_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
}
static int catpt_offload1_volume_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
}
static int catpt_offload2_volume_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
}
static int catpt_offload2_volume_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
}
static int catpt_capture_volume_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
}
static int catpt_capture_volume_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
}
static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value;
return 0;
}
static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct catpt_stream_runtime *stream;
struct catpt_dev *cdev = dev_get_drvdata(component->dev);
bool mute;
bool *kmute, cmute;
u8 stream_hw_id;
int ret;
mute = (bool)ucontrol->value.integer.value[0];
stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE);
if (!stream) {
*(bool *)kcontrol->private_value = mute;
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);
}
ret = pm_runtime_resume_and_get(cdev->dev);
if (ret)
return ret;
ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute);
pm_runtime_put_autosuspend(cdev->dev);
if (ret)
return CATPT_IPC_RET(ret);
*(bool *)kcontrol->private_value = mute;
return 0;
*kmute = cmute;
return 1;
}
static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol,
@ -1067,27 +1000,28 @@ 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_CTL(kname, sname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = (kname), \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = catpt_volume_info, \
.get = catpt_##sname##_volume_get, \
.put = catpt_##sname##_volume_put, \
.tlv.p = catpt_volume_tlv, \
.private_value = (unsigned long) \
&(long[CATPT_CHANNELS_MAX]) {0} }
#define CATPT_VOLUME_CTL(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 } \
}
static const struct snd_kcontrol_new component_kcontrols[] = {
/* Master volume (mixer stream) */
CATPT_VOLUME_CTL("Master Playback Volume", mixer),
CATPT_VOLUME_CTL("Master Playback Volume", MIXER),
/* Individual volume controls for offload and capture */
CATPT_VOLUME_CTL("Media0 Playback Volume", offload1),
CATPT_VOLUME_CTL("Media1 Playback Volume", offload2),
CATPT_VOLUME_CTL("Mic Capture Volume", capture),
CATPT_VOLUME_CTL("Media0 Playback Volume", OFFLOAD1),
CATPT_VOLUME_CTL("Media1 Playback Volume", OFFLOAD2),
CATPT_VOLUME_CTL("Mic Capture Volume", CAPTURE1),
SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0},
catpt_loopback_switch_get, catpt_loopback_switch_put),
catpt_loopback_mute_get, catpt_loopback_mute_put),
/* Enable or disable WAVES module */
SOC_SINGLE_BOOL_EXT("Waves Switch", 0,
catpt_waves_switch_get, catpt_waves_switch_put),