Add support for DSP volume controls

Merge series from Amadeusz Sławiński <amadeuszx.slawinski@linux.intel.com>:

Some devices like DMIC don't expose native controls or need volume limit
due to possible HW damage. Add support for volume controls allowing to
change volume level in DSP. Maximum volume level is imposed by the
topology file which defines given path.
This commit is contained in:
Mark Brown 2022-12-27 11:54:56 +00:00
commit 35108d6df2
No known key found for this signature in database
GPG key ID: 24D68B725D5487D0
9 changed files with 336 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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)
{

View file

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

View file

@ -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 },

View file

@ -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,

View file

@ -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. */