Unverified Commit 16bb2209 authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: SOF: ipc4-control: Support for Switch and Enum

Merge series from Peter Ujfalusi <peter.ujfalusi@linux.intel.com>:

Currently IPC4 has no notion of a switch or enum type of control which
is a generic concept in ALSA.

The generic support for these control types will be as follows:

- large config is used to send the channel-value par array
- param_id of a SWITCH type is 200
- param_id of an ENUM type is 201

Each module need to support a switch or/and enum must handle these
universal param_ids.  The message payload is described by struct
sof_ipc4_control_msg_payload.
parents cc676c0d 07a866a4
Loading
Loading
Loading
Loading
+173 −2
Original line number Diff line number Diff line
@@ -201,6 +201,159 @@ static int sof_ipc4_volume_get(struct snd_sof_control *scontrol,
	return 0;
}

static int
sof_ipc4_set_generic_control_data(struct snd_sof_dev *sdev,
				  struct snd_sof_widget *swidget,
				  struct snd_sof_control *scontrol, bool lock)
{
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	struct sof_ipc4_control_msg_payload *data;
	struct sof_ipc4_msg *msg = &cdata->msg;
	size_t data_size;
	unsigned int i;
	int ret;

	data_size = struct_size(data, chanv, scontrol->num_channels);
	data = kzalloc(data_size, GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	data->id = cdata->index;
	data->num_elems = scontrol->num_channels;
	for (i = 0; i < scontrol->num_channels; i++) {
		data->chanv[i].channel = cdata->chanv[i].channel;
		data->chanv[i].value = cdata->chanv[i].value;
	}

	msg->data_ptr = data;
	msg->data_size = data_size;

	ret = sof_ipc4_set_get_kcontrol_data(scontrol, true, lock);
	msg->data_ptr = NULL;
	msg->data_size = 0;
	if (ret < 0)
		dev_err(sdev->dev, "Failed to set control update for %s\n",
			scontrol->name);

	kfree(data);

	return ret;
}

static bool sof_ipc4_switch_put(struct snd_sof_control *scontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	struct snd_soc_component *scomp = scontrol->scomp;
	struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
	struct snd_sof_widget *swidget;
	bool widget_found = false;
	bool change = false;
	unsigned int i;
	u32 value;
	int ret;

	/* update each channel */
	for (i = 0; i < scontrol->num_channels; i++) {
		value = ucontrol->value.integer.value[i];
		change = change || (value != cdata->chanv[i].value);
		cdata->chanv[i].channel = i;
		cdata->chanv[i].value = value;
	}

	if (!pm_runtime_active(scomp->dev))
		return change;

	/* find widget associated with the control */
	list_for_each_entry(swidget, &sdev->widget_list, list) {
		if (swidget->comp_id == scontrol->comp_id) {
			widget_found = true;
			break;
		}
	}

	if (!widget_found) {
		dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
		return false;
	}

	ret = sof_ipc4_set_generic_control_data(sdev, swidget, scontrol, true);
	if (ret < 0)
		return false;

	return change;
}

static int sof_ipc4_switch_get(struct snd_sof_control *scontrol,
			       struct snd_ctl_elem_value *ucontrol)
{
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	unsigned int i;

	/* read back each channel */
	for (i = 0; i < scontrol->num_channels; i++)
		ucontrol->value.integer.value[i] = cdata->chanv[i].value;

	return 0;
}

static bool sof_ipc4_enum_put(struct snd_sof_control *scontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	struct snd_soc_component *scomp = scontrol->scomp;
	struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
	struct snd_sof_widget *swidget;
	bool widget_found = false;
	bool change = false;
	unsigned int i;
	u32 value;
	int ret;

	/* update each channel */
	for (i = 0; i < scontrol->num_channels; i++) {
		value = ucontrol->value.enumerated.item[i];
		change = change || (value != cdata->chanv[i].value);
		cdata->chanv[i].channel = i;
		cdata->chanv[i].value = value;
	}

	if (!pm_runtime_active(scomp->dev))
		return change;

	/* find widget associated with the control */
	list_for_each_entry(swidget, &sdev->widget_list, list) {
		if (swidget->comp_id == scontrol->comp_id) {
			widget_found = true;
			break;
		}
	}

	if (!widget_found) {
		dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
		return false;
	}

	ret = sof_ipc4_set_generic_control_data(sdev, swidget, scontrol, true);
	if (ret < 0)
		return false;

	return change;
}

static int sof_ipc4_enum_get(struct snd_sof_control *scontrol,
			     struct snd_ctl_elem_value *ucontrol)
{
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	unsigned int i;

	/* read back each channel */
	for (i = 0; i < scontrol->num_channels; i++)
		ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;

	return 0;
}

static int sof_ipc4_set_get_bytes_data(struct snd_sof_dev *sdev,
				       struct snd_sof_control *scontrol,
				       bool set, bool lock)
@@ -438,6 +591,16 @@ static int sof_ipc4_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
	return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, true);
}

static int
sof_ipc4_volsw_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
		     struct snd_sof_control *scontrol)
{
	if (scontrol->max == 1)
		return sof_ipc4_set_generic_control_data(sdev, swidget, scontrol, false);

	return sof_ipc4_set_volume_data(sdev, swidget, scontrol, false);
}

/* set up all controls for the widget */
static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
@@ -450,13 +613,17 @@ static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_s
			case SND_SOC_TPLG_CTL_VOLSW:
			case SND_SOC_TPLG_CTL_VOLSW_SX:
			case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
				ret = sof_ipc4_set_volume_data(sdev, swidget,
							       scontrol, false);
				ret = sof_ipc4_volsw_setup(sdev, swidget, scontrol);
				break;
			case SND_SOC_TPLG_CTL_BYTES:
				ret = sof_ipc4_set_get_bytes_data(sdev, scontrol,
								  true, false);
				break;
			case SND_SOC_TPLG_CTL_ENUM:
			case SND_SOC_TPLG_CTL_ENUM_VALUE:
				ret = sof_ipc4_set_generic_control_data(sdev, swidget,
									scontrol, false);
				break;
			default:
				break;
			}
@@ -498,6 +665,10 @@ sof_ipc4_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_I
const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = {
	.volume_put = sof_ipc4_volume_put,
	.volume_get = sof_ipc4_volume_get,
	.switch_put = sof_ipc4_switch_put,
	.switch_get = sof_ipc4_switch_get,
	.enum_put = sof_ipc4_enum_put,
	.enum_get = sof_ipc4_enum_get,
	.bytes_put = sof_ipc4_bytes_put,
	.bytes_get = sof_ipc4_bytes_get,
	.bytes_ext_put = sof_ipc4_bytes_ext_put,
+46 −3
Original line number Diff line number Diff line
@@ -2107,17 +2107,57 @@ static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof
	msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
	msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);

	/* volume controls with range 0-1 (off/on) are switch controls */
	if (scontrol->max == 1)
		msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_SWITCH_CONTROL_PARAM_ID);
	else
		msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_GAIN_PARAM_ID);

	/* set default volume values to 0dB in control */
	for (i = 0; i < scontrol->num_channels; i++) {
		control_data->chanv[i].channel = i;
		/*
		 * Default, initial values:
		 * - 0dB for volume controls
		 * - off (0) for switch controls - value already zero after
		 *				   memory allocation
		 */
		if (scontrol->max > 1)
			control_data->chanv[i].value = SOF_IPC4_VOL_ZERO_DB;
	}

	return 0;
}

static int sof_ipc4_control_load_enum(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
	struct sof_ipc4_control_data *control_data;
	struct sof_ipc4_msg *msg;
	int i;

	scontrol->size = struct_size(control_data, chanv, scontrol->num_channels);

	/* scontrol->ipc_control_data will be freed in sof_control_unload */
	scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
	if (!scontrol->ipc_control_data)
		return -ENOMEM;

	control_data = scontrol->ipc_control_data;
	control_data->index = scontrol->index;

	msg = &control_data->msg;
	msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
	msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
	msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);

	msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_ENUM_CONTROL_PARAM_ID);

	/* Default, initial value for enums: first enum entry is selected (0) */
	for (i = 0; i < scontrol->num_channels; i++)
		control_data->chanv[i].channel = i;

	return 0;
}

static int sof_ipc4_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
	struct sof_ipc4_control_data *control_data;
@@ -2192,6 +2232,9 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr
		return sof_ipc4_control_load_volume(sdev, scontrol);
	case SND_SOC_TPLG_CTL_BYTES:
		return sof_ipc4_control_load_bytes(sdev, scontrol);
	case SND_SOC_TPLG_CTL_ENUM:
	case SND_SOC_TPLG_CTL_ENUM_VALUE:
		return sof_ipc4_control_load_enum(sdev, scontrol);
	default:
		break;
	}
+18 −1
Original line number Diff line number Diff line
@@ -319,7 +319,7 @@ struct sof_ipc4_copier {
/**
 * struct sof_ipc4_ctrl_value_chan: generic channel mapped value data
 * @channel: Channel ID
 * @value: gain value
 * @value: Value associated with @channel
 */
struct sof_ipc4_ctrl_value_chan {
	u32 channel;
@@ -343,6 +343,23 @@ struct sof_ipc4_control_data {
	};
};

#define SOF_IPC4_SWITCH_CONTROL_PARAM_ID	200
#define SOF_IPC4_ENUM_CONTROL_PARAM_ID		201

/**
 * struct sof_ipc4_control_msg_payload - IPC payload for kcontrol parameters
 * @id: unique id of the control
 * @num_elems: Number of elements in the chanv array
 * @reserved: reserved for future use, must be set to 0
 * @chanv: channel ID and value array
 */
struct sof_ipc4_control_msg_payload {
	uint16_t id;
	uint16_t num_elems;
	uint32_t reserved[4];
	DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv);
} __packed;

/**
 * struct sof_ipc4_gain_data - IPC gain blob
 * @channels: Channels