aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/uapi/sound/intel/avs/tokens.h4
-rw-r--r--sound/soc/intel/avs/Makefile2
-rw-r--r--sound/soc/intel/avs/control.c105
-rw-r--r--sound/soc/intel/avs/control.h23
-rw-r--r--sound/soc/intel/avs/messages.c29
-rw-r--r--sound/soc/intel/avs/messages.h33
-rw-r--r--sound/soc/intel/avs/path.c62
-rw-r--r--sound/soc/intel/avs/topology.c76
-rw-r--r--sound/soc/intel/avs/topology.h3
9 files changed, 336 insertions, 1 deletions
diff --git a/include/uapi/sound/intel/avs/tokens.h b/include/uapi/sound/intel/avs/tokens.h
index 754f02b2f444..4ffe546aa409 100644
--- a/include/uapi/sound/intel/avs/tokens.h
+++ b/include/uapi/sound/intel/avs/tokens.h
@@ -108,6 +108,7 @@ enum avs_tplg_token {
AVS_TKN_MOD_CORE_ID_U8 = 1704,
AVS_TKN_MOD_PROC_DOMAIN_U8 = 1705,
AVS_TKN_MOD_MODCFG_EXT_ID_U32 = 1706,
+ AVS_TKN_MOD_KCONTROL_ID_U32 = 1707,
/* struct avs_tplg_path_template */
AVS_TKN_PATH_TMPL_ID_U32 = 1801,
@@ -121,6 +122,9 @@ enum avs_tplg_token {
AVS_TKN_PIN_FMT_INDEX_U32 = 2201,
AVS_TKN_PIN_FMT_IOBS_U32 = 2202,
AVS_TKN_PIN_FMT_AFMT_ID_U32 = 2203,
+
+ /* struct avs_tplg_kcontrol */
+ AVS_TKN_KCONTROL_ID_U32 = 2301,
};
#endif
diff --git a/sound/soc/intel/avs/Makefile b/sound/soc/intel/avs/Makefile
index 1c6924a1ebca..460ee6599daf 100644
--- a/sound/soc/intel/avs/Makefile
+++ b/sound/soc/intel/avs/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o \
- topology.o path.o pcm.o board_selection.o
+ topology.o path.o pcm.o board_selection.o control.o
snd-soc-avs-objs += cldma.o
snd-soc-avs-objs += skl.o apl.o
diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c
new file mode 100644
index 000000000000..a8b14b784f8a
--- /dev/null
+++ b/sound/soc/intel/avs/control.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
+//
+// Authors: Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
+// Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <sound/soc.h>
+#include "avs.h"
+#include "control.h"
+#include "messages.h"
+#include "path.h"
+
+static struct avs_dev *avs_get_kcontrol_adev(struct snd_kcontrol *kcontrol)
+{
+ struct snd_soc_dapm_widget *w;
+
+ w = snd_soc_dapm_kcontrol_widget(kcontrol);
+
+ return to_avs_dev(w->dapm->component->dev);
+}
+
+static struct avs_path_module *avs_get_kcontrol_module(struct avs_dev *adev, u32 id)
+{
+ struct avs_path *path;
+ struct avs_path_pipeline *ppl;
+ struct avs_path_module *mod;
+
+ list_for_each_entry(path, &adev->path_list, node)
+ list_for_each_entry(ppl, &path->ppl_list, node)
+ list_for_each_entry(mod, &ppl->mod_list, node)
+ if (mod->template->ctl_id && mod->template->ctl_id == id)
+ return mod;
+
+ return NULL;
+}
+
+int avs_control_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+ struct avs_control_data *ctl_data = (struct avs_control_data *)mc->dobj.private;
+ struct avs_dev *adev = avs_get_kcontrol_adev(kcontrol);
+ struct avs_volume_cfg *dspvols = NULL;
+ struct avs_path_module *active_module;
+ size_t num_dspvols;
+ int ret = 0;
+
+ /* prevent access to modules while path is being constructed */
+ mutex_lock(&adev->path_mutex);
+
+ active_module = avs_get_kcontrol_module(adev, ctl_data->id);
+ if (active_module) {
+ ret = avs_ipc_peakvol_get_volume(adev, active_module->module_id,
+ active_module->instance_id, &dspvols,
+ &num_dspvols);
+ if (!ret)
+ ucontrol->value.integer.value[0] = dspvols[0].target_volume;
+
+ ret = AVS_IPC_RET(ret);
+ kfree(dspvols);
+ } else {
+ ucontrol->value.integer.value[0] = ctl_data->volume;
+ }
+
+ mutex_unlock(&adev->path_mutex);
+ return ret;
+}
+
+int avs_control_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+ struct avs_control_data *ctl_data = (struct avs_control_data *)mc->dobj.private;
+ struct avs_dev *adev = avs_get_kcontrol_adev(kcontrol);
+ long *volume = &ctl_data->volume;
+ struct avs_path_module *active_module;
+ struct avs_volume_cfg dspvol = {0};
+ long ctlvol = ucontrol->value.integer.value[0];
+ int ret = 0, changed = 0;
+
+ if (ctlvol < 0 || ctlvol > mc->max)
+ return -EINVAL;
+
+ /* prevent access to modules while path is being constructed */
+ mutex_lock(&adev->path_mutex);
+
+ if (*volume != ctlvol) {
+ *volume = ctlvol;
+ changed = 1;
+ }
+
+ active_module = avs_get_kcontrol_module(adev, ctl_data->id);
+ if (active_module) {
+ dspvol.channel_id = AVS_ALL_CHANNELS_MASK;
+ dspvol.target_volume = *volume;
+
+ ret = avs_ipc_peakvol_set_volume(adev, active_module->module_id,
+ active_module->instance_id, &dspvol);
+ ret = AVS_IPC_RET(ret);
+ }
+
+ mutex_unlock(&adev->path_mutex);
+
+ return ret ? ret : changed;
+}
diff --git a/sound/soc/intel/avs/control.h b/sound/soc/intel/avs/control.h
new file mode 100644
index 000000000000..08631bde13c3
--- /dev/null
+++ b/sound/soc/intel/avs/control.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
+ *
+ * Authors: Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
+ * Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SOUND_SOC_INTEL_AVS_CTRL_H
+#define __SOUND_SOC_INTEL_AVS_CTRL_H
+
+#include <sound/control.h>
+
+struct avs_control_data {
+ u32 id;
+
+ long volume;
+};
+
+int avs_control_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+int avs_control_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+
+#endif
diff --git a/sound/soc/intel/avs/messages.c b/sound/soc/intel/avs/messages.c
index e11ae4246416..f887ab5b0311 100644
--- a/sound/soc/intel/avs/messages.c
+++ b/sound/soc/intel/avs/messages.c
@@ -702,6 +702,35 @@ int avs_ipc_copier_set_sink_format(struct avs_dev *adev, u16 module_id,
(u8 *)&cpr_fmt, sizeof(cpr_fmt));
}
+int avs_ipc_peakvol_set_volume(struct avs_dev *adev, u16 module_id, u8 instance_id,
+ struct avs_volume_cfg *vol)
+{
+ return avs_ipc_set_large_config(adev, module_id, instance_id, AVS_PEAKVOL_VOLUME, (u8 *)vol,
+ sizeof(*vol));
+}
+
+int avs_ipc_peakvol_get_volume(struct avs_dev *adev, u16 module_id, u8 instance_id,
+ struct avs_volume_cfg **vols, size_t *num_vols)
+{
+ size_t payload_size;
+ u8 *payload;
+ int ret;
+
+ ret = avs_ipc_get_large_config(adev, module_id, instance_id, AVS_PEAKVOL_VOLUME, NULL, 0,
+ &payload, &payload_size);
+ if (ret)
+ return ret;
+
+ /* Non-zero payload expected for PEAKVOL_VOLUME. */
+ if (!payload_size)
+ return -EREMOTEIO;
+
+ *vols = (struct avs_volume_cfg *)payload;
+ *num_vols = payload_size / sizeof(**vols);
+
+ return 0;
+}
+
#ifdef CONFIG_DEBUG_FS
int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size)
{
diff --git a/sound/soc/intel/avs/messages.h b/sound/soc/intel/avs/messages.h
index 9dd835527e02..d3b60ae7d743 100644
--- a/sound/soc/intel/avs/messages.h
+++ b/sound/soc/intel/avs/messages.h
@@ -561,6 +561,12 @@ int avs_ipc_set_system_time(struct avs_dev *adev);
#define AVS_COPIER_MOD_UUID \
GUID_INIT(0x9BA00C83, 0xCA12, 0x4A83, 0x94, 0x3C, 0x1F, 0xA2, 0xE8, 0x2F, 0x9D, 0xDA)
+#define AVS_PEAKVOL_MOD_UUID \
+ GUID_INIT(0x8A171323, 0x94A3, 0x4E1D, 0xAF, 0xE9, 0xFE, 0x5D, 0xBA, 0xa4, 0xC3, 0x93)
+
+#define AVS_GAIN_MOD_UUID \
+ GUID_INIT(0x61BCA9A8, 0x18D0, 0x4A18, 0x8E, 0x7B, 0x26, 0x39, 0x21, 0x98, 0x04, 0xB7)
+
#define AVS_KPBUFF_MOD_UUID \
GUID_INIT(0xA8A0CB32, 0x4A77, 0x4DB1, 0x85, 0xC7, 0x53, 0xD7, 0xEE, 0x07, 0xBC, 0xE6)
@@ -729,6 +735,19 @@ struct avs_copier_cfg {
struct avs_copier_gtw_cfg gtw_cfg;
} __packed;
+struct avs_volume_cfg {
+ u32 channel_id;
+ u32 target_volume;
+ u32 curve_type;
+ u32 reserved; /* alignment */
+ u64 curve_duration;
+} __packed;
+
+struct avs_peakvol_cfg {
+ struct avs_modcfg_base base;
+ struct avs_volume_cfg vols[];
+} __packed;
+
struct avs_micsel_cfg {
struct avs_modcfg_base base;
struct avs_audio_format out_fmt;
@@ -802,6 +821,20 @@ int avs_ipc_copier_set_sink_format(struct avs_dev *adev, u16 module_id,
const struct avs_audio_format *src_fmt,
const struct avs_audio_format *sink_fmt);
+enum avs_peakvol_runtime_param {
+ AVS_PEAKVOL_VOLUME = 0,
+};
+
+enum avs_audio_curve_type {
+ AVS_AUDIO_CURVE_NONE = 0,
+ AVS_AUDIO_CURVE_WINDOWS_FADE = 1,
+};
+
+int avs_ipc_peakvol_set_volume(struct avs_dev *adev, u16 module_id, u8 instance_id,
+ struct avs_volume_cfg *vol);
+int avs_ipc_peakvol_get_volume(struct avs_dev *adev, u16 module_id, u8 instance_id,
+ struct avs_volume_cfg **vols, size_t *num_vols);
+
#define AVS_PROBE_INST_ID 0
enum avs_probe_runtime_param {
diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c
index ce157a8d6552..05302ab705ae 100644
--- a/sound/soc/intel/avs/path.c
+++ b/sound/soc/intel/avs/path.c
@@ -10,6 +10,7 @@
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "avs.h"
+#include "control.h"
#include "path.h"
#include "topology.h"
@@ -264,6 +265,65 @@ static int avs_copier_create(struct avs_dev *adev, struct avs_path_module *mod)
return ret;
}
+static struct avs_control_data *avs_get_module_control(struct avs_path_module *mod)
+{
+ struct avs_tplg_module *t = mod->template;
+ struct avs_tplg_path_template *path_tmpl;
+ struct snd_soc_dapm_widget *w;
+ int i;
+
+ path_tmpl = t->owner->owner->owner;
+ w = path_tmpl->w;
+
+ for (i = 0; i < w->num_kcontrols; i++) {
+ struct avs_control_data *ctl_data;
+ struct soc_mixer_control *mc;
+
+ mc = (struct soc_mixer_control *)w->kcontrols[i]->private_value;
+ ctl_data = (struct avs_control_data *)mc->dobj.private;
+ if (ctl_data->id == t->ctl_id)
+ return ctl_data;
+ }
+
+ return NULL;
+}
+
+static int avs_peakvol_create(struct avs_dev *adev, struct avs_path_module *mod)
+{
+ struct avs_tplg_module *t = mod->template;
+ struct avs_control_data *ctl_data;
+ struct avs_peakvol_cfg *cfg;
+ int volume = S32_MAX;
+ size_t size;
+ int ret;
+
+ ctl_data = avs_get_module_control(mod);
+ if (ctl_data)
+ volume = ctl_data->volume;
+
+ /* As 2+ channels controls are unsupported, have a single block for all channels. */
+ size = struct_size(cfg, vols, 1);
+ cfg = kzalloc(size, GFP_KERNEL);
+ if (!cfg)
+ return -ENOMEM;
+
+ cfg->base.cpc = t->cfg_base->cpc;
+ cfg->base.ibs = t->cfg_base->ibs;
+ cfg->base.obs = t->cfg_base->obs;
+ cfg->base.is_pages = t->cfg_base->is_pages;
+ cfg->base.audio_fmt = *t->in_fmt;
+ cfg->vols[0].target_volume = volume;
+ cfg->vols[0].channel_id = AVS_ALL_CHANNELS_MASK;
+ cfg->vols[0].curve_type = AVS_AUDIO_CURVE_NONE;
+ cfg->vols[0].curve_duration = 0;
+
+ ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, t->core_id,
+ t->domain, cfg, size, &mod->instance_id);
+
+ kfree(cfg);
+ return ret;
+}
+
static int avs_updown_mix_create(struct avs_dev *adev, struct avs_path_module *mod)
{
struct avs_tplg_module *t = mod->template;
@@ -465,6 +525,8 @@ static struct avs_module_create avs_module_create[] = {
{ &AVS_MIXOUT_MOD_UUID, avs_modbase_create },
{ &AVS_KPBUFF_MOD_UUID, avs_modbase_create },
{ &AVS_COPIER_MOD_UUID, avs_copier_create },
+ { &AVS_PEAKVOL_MOD_UUID, avs_peakvol_create },
+ { &AVS_GAIN_MOD_UUID, avs_peakvol_create },
{ &AVS_MICSEL_MOD_UUID, avs_micsel_create },
{ &AVS_MUX_MOD_UUID, avs_mux_create },
{ &AVS_UPDWMIX_MOD_UUID, avs_updown_mix_create },
diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c
index e845eaf0a1e7..5fee7a8ec06a 100644
--- a/sound/soc/intel/avs/topology.c
+++ b/sound/soc/intel/avs/topology.c
@@ -13,6 +13,7 @@
#include <sound/soc-topology.h>
#include <uapi/sound/intel/avs/tokens.h>
#include "avs.h"
+#include "control.h"
#include "topology.h"
/* Get pointer to vendor array at the specified offset. */
@@ -1070,6 +1071,12 @@ static const struct avs_tplg_token_parser module_parsers[] = {
.offset = offsetof(struct avs_tplg_module, cfg_ext),
.parse = avs_parse_modcfg_ext_ptr,
},
+ {
+ .token = AVS_TKN_MOD_KCONTROL_ID_U32,
+ .type = SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ .offset = offsetof(struct avs_tplg_module, ctl_id),
+ .parse = avs_parse_byte_token,
+ },
};
static struct avs_tplg_module *
@@ -1435,6 +1442,16 @@ static int avs_widget_load(struct snd_soc_component *comp, int index,
return 0;
}
+static int avs_widget_ready(struct snd_soc_component *comp, int index,
+ struct snd_soc_dapm_widget *w,
+ struct snd_soc_tplg_dapm_widget *dw)
+{
+ struct avs_tplg_path_template *template = w->priv;
+
+ template->w = w;
+ return 0;
+}
+
static int avs_dai_load(struct snd_soc_component *comp, int index,
struct snd_soc_dai_driver *dai_drv, struct snd_soc_tplg_pcm *pcm,
struct snd_soc_dai *dai)
@@ -1586,9 +1603,68 @@ static int avs_manifest(struct snd_soc_component *comp, int index,
return avs_tplg_parse_bindings(comp, tuples, remaining);
}
+#define AVS_CONTROL_OPS_VOLUME 257
+
+static const struct snd_soc_tplg_kcontrol_ops avs_control_ops[] = {
+ {
+ .id = AVS_CONTROL_OPS_VOLUME,
+ .get = avs_control_volume_get,
+ .put = avs_control_volume_put,
+ },
+};
+
+static const struct avs_tplg_token_parser control_parsers[] = {
+ {
+ .token = AVS_TKN_KCONTROL_ID_U32,
+ .type = SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ .offset = offsetof(struct avs_control_data, id),
+ .parse = avs_parse_word_token,
+ },
+};
+
+static int
+avs_control_load(struct snd_soc_component *comp, int index, struct snd_kcontrol_new *ctmpl,
+ struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ struct snd_soc_tplg_vendor_array *tuples;
+ struct snd_soc_tplg_mixer_control *tmc;
+ struct avs_control_data *ctl_data;
+ struct soc_mixer_control *mc;
+ size_t block_size;
+ int ret;
+
+ switch (hdr->type) {
+ case SND_SOC_TPLG_TYPE_MIXER:
+ tmc = container_of(hdr, typeof(*tmc), hdr);
+ tuples = tmc->priv.array;
+ block_size = le32_to_cpu(tmc->priv.size);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ctl_data = devm_kzalloc(comp->card->dev, sizeof(*ctl_data), GFP_KERNEL);
+ if (!ctl_data)
+ return -ENOMEM;
+
+ ret = parse_dictionary_entries(comp, tuples, block_size, ctl_data, 1, sizeof(*ctl_data),
+ AVS_TKN_KCONTROL_ID_U32, control_parsers,
+ ARRAY_SIZE(control_parsers));
+ if (ret)
+ return ret;
+
+ mc = (struct soc_mixer_control *)ctmpl->private_value;
+ mc->dobj.private = ctl_data;
+ return 0;
+}
+
static struct snd_soc_tplg_ops avs_tplg_ops = {
+ .io_ops = avs_control_ops,
+ .io_ops_count = ARRAY_SIZE(avs_control_ops),
+ .control_load = avs_control_load,
.dapm_route_load = avs_route_load,
.widget_load = avs_widget_load,
+ .widget_ready = avs_widget_ready,
.dai_load = avs_dai_load,
.link_load = avs_link_load,
.manifest = avs_manifest,
diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h
index 68e5f6312353..6e1c8e9b2496 100644
--- a/sound/soc/intel/avs/topology.h
+++ b/sound/soc/intel/avs/topology.h
@@ -138,6 +138,8 @@ struct avs_tplg_path_template_id {
struct avs_tplg_path_template {
u32 id;
+ struct snd_soc_dapm_widget *w;
+
struct list_head path_list;
struct avs_tplg *owner;
@@ -180,6 +182,7 @@ struct avs_tplg_module {
u8 core_id;
u8 domain;
struct avs_tplg_modcfg_ext *cfg_ext;
+ u32 ctl_id;
struct avs_tplg_pipeline *owner;
/* Pipeline modules management. */