Unverified Commit 35108d6d authored by Mark Brown's avatar Mark Brown
Browse files

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.
parents 966ef755 29d65236
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
# 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

+105 −0
Original line number Diff line number Diff line
// 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;
}
+23 −0
Original line number Diff line number Diff line
/* 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
+29 −0
Original line number Diff line number Diff line
@@ -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)
{
Loading