Unverified Commit 74fc96e8 authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: SOF: ipc4: Add support for control change

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

This series adds support for handling control (switch/enum) change notifications
sent by the firmware.
The use case is similar to what is already used by IPC3 version: the firmware
can update the value of an enum or switch and sends notification to the kernel,
which in turn will notify the user space of a change.
parents 29b0b68f 0ff23d46
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -532,6 +532,35 @@ struct sof_ipc4_notify_resource_data {
#define SOF_IPC4_DEBUG_SLOT_TELEMETRY		0x4c455400
#define SOF_IPC4_DEBUG_SLOT_BROKEN		0x44414544

/**
 * struct sof_ipc4_notify_module_data - payload for module notification
 * @instance_id: instance ID of the originator module of the notification
 * @module_id: module ID of the originator of the notification
 * @event_id: module specific event id
 * @event_data_size: Size of the @event_data (if any) in bytes
 * @event_data: Optional notification data, module and notification dependent
 */
struct sof_ipc4_notify_module_data {
	uint16_t instance_id;
	uint16_t module_id;
	uint32_t event_id;
	uint32_t event_data_size;
	uint8_t event_data[];
} __packed __aligned(4);

/*
 * ALSA kcontrol change notification
 *
 * The event_id of struct sof_ipc4_notify_module_data is divided into two u16:
 *  upper u16: magic number for ALSA kcontrol types: 0xA15A
 *  lower u16: param_id of the control, which is the type of the control
 * The event_data contains the struct sof_ipc4_control_msg_payload of the control
 * which sent the notification.
 */
#define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK		GENMASK(31, 16)
#define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL		0xA15A0000
#define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_PARAMID_MASK	GENMASK(15, 0)

/** @}*/

#endif
+179 −0
Original line number Diff line number Diff line
@@ -240,6 +240,50 @@ sof_ipc4_set_generic_control_data(struct snd_sof_dev *sdev,
	return ret;
}

static void sof_ipc4_refresh_generic_control(struct snd_sof_control *scontrol)
{
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	struct snd_soc_component *scomp = scontrol->scomp;
	struct sof_ipc4_control_msg_payload *data;
	struct sof_ipc4_msg *msg = &cdata->msg;
	size_t data_size;
	unsigned int i;
	int ret;

	if (!scontrol->comp_data_dirty)
		return;

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

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

	data->id = cdata->index;
	data->num_elems = scontrol->num_channels;
	msg->data_ptr = data;
	msg->data_size = data_size;

	scontrol->comp_data_dirty = false;
	ret = sof_ipc4_set_get_kcontrol_data(scontrol, false, true);
	msg->data_ptr = NULL;
	msg->data_size = 0;
	if (!ret) {
		for (i = 0; i < scontrol->num_channels; i++) {
			cdata->chanv[i].channel = data->chanv[i].channel;
			cdata->chanv[i].value = data->chanv[i].value;
		}
	} else {
		dev_err(scomp->dev, "Failed to read control data for %s\n",
			scontrol->name);
		scontrol->comp_data_dirty = true;
	}

	kfree(data);
}

static bool sof_ipc4_switch_put(struct snd_sof_control *scontrol,
				struct snd_ctl_elem_value *ucontrol)
{
@@ -290,6 +334,8 @@ static int sof_ipc4_switch_get(struct snd_sof_control *scontrol,
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	unsigned int i;

	sof_ipc4_refresh_generic_control(scontrol);

	/* read back each channel */
	for (i = 0; i < scontrol->num_channels; i++)
		ucontrol->value.integer.value[i] = cdata->chanv[i].value;
@@ -347,6 +393,8 @@ static int sof_ipc4_enum_get(struct snd_sof_control *scontrol,
	struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
	unsigned int i;

	sof_ipc4_refresh_generic_control(scontrol);

	/* read back each channel */
	for (i = 0; i < scontrol->num_channels; i++)
		ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
@@ -601,6 +649,136 @@ sof_ipc4_volsw_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
	return sof_ipc4_set_volume_data(sdev, swidget, scontrol, false);
}

#define PARAM_ID_FROM_EXTENSION(_ext)	(((_ext) & SOF_IPC4_MOD_EXT_MSG_PARAM_ID_MASK)	\
					 >> SOF_IPC4_MOD_EXT_MSG_PARAM_ID_SHIFT)

static void sof_ipc4_control_update(struct snd_sof_dev *sdev, void *ipc_message)
{
	struct sof_ipc4_msg *ipc4_msg = ipc_message;
	struct sof_ipc4_notify_module_data *ndata = ipc4_msg->data_ptr;
	struct sof_ipc4_control_msg_payload *msg_data;
	struct sof_ipc4_control_data *cdata;
	struct snd_soc_dapm_widget *widget;
	struct snd_sof_control *scontrol;
	struct snd_sof_widget *swidget;
	struct snd_kcontrol *kc = NULL;
	bool scontrol_found = false;
	u32 event_param_id;
	int i, type;

	if (ndata->event_data_size < sizeof(*msg_data)) {
		dev_err(sdev->dev,
			"%s: Invalid event data size for module %u.%u: %u\n",
			__func__, ndata->module_id, ndata->instance_id,
			ndata->event_data_size);
		return;
	}

	event_param_id = ndata->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_PARAMID_MASK;
	switch (event_param_id) {
	case SOF_IPC4_SWITCH_CONTROL_PARAM_ID:
		type = SND_SOC_TPLG_TYPE_MIXER;
		break;
	case SOF_IPC4_ENUM_CONTROL_PARAM_ID:
		type = SND_SOC_TPLG_TYPE_ENUM;
		break;
	default:
		dev_err(sdev->dev,
			"%s: Invalid control type for module %u.%u: %u\n",
			__func__, ndata->module_id, ndata->instance_id,
			event_param_id);
		return;
	}

	/* Find the swidget based on ndata->module_id and ndata->instance_id */
	swidget = sof_ipc4_find_swidget_by_ids(sdev, ndata->module_id,
					       ndata->instance_id);
	if (!swidget) {
		dev_err(sdev->dev, "%s: Failed to find widget for module %u.%u\n",
			__func__, ndata->module_id, ndata->instance_id);
		return;
	}

	/* Find the scontrol which is the source of the notification */
	msg_data = (struct sof_ipc4_control_msg_payload *)ndata->event_data;
	list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
		if (scontrol->comp_id == swidget->comp_id) {
			u32 local_param_id;

			cdata = scontrol->ipc_control_data;
			/*
			 * The scontrol's param_id is stored in the IPC message
			 * template's extension
			 */
			local_param_id = PARAM_ID_FROM_EXTENSION(cdata->msg.extension);
			if (local_param_id == event_param_id &&
			    msg_data->id == cdata->index) {
				scontrol_found = true;
				break;
			}
		}
	}

	if (!scontrol_found) {
		dev_err(sdev->dev,
			"%s: Failed to find control on widget %s: %u:%u\n",
			__func__, swidget->widget->name, ndata->event_id & 0xffff,
			msg_data->id);
		return;
	}

	if (msg_data->num_elems) {
		/*
		 * The message includes the updated value/data, update the
		 * control's local cache using the received notification
		 */
		for (i = 0; i < msg_data->num_elems; i++) {
			u32 channel = msg_data->chanv[i].channel;

			if (channel >= scontrol->num_channels) {
				dev_warn(sdev->dev,
					 "Invalid channel index for %s: %u\n",
					 scontrol->name, i);

				/*
				 * Mark the scontrol as dirty to force a refresh
				 * on next read
				 */
				scontrol->comp_data_dirty = true;
				break;
			}

			cdata->chanv[channel].value = msg_data->chanv[i].value;
		}
	} else {
		/*
		 * Mark the scontrol as dirty because the value/data is changed
		 * in firmware, forcing a refresh on next read access
		 */
		scontrol->comp_data_dirty = true;
	}

	/*
	 * Look up the ALSA kcontrol of the scontrol to be able to send a
	 * notification to user space
	 */
	widget = swidget->widget;
	for (i = 0; i < widget->num_kcontrols; i++) {
		/* skip non matching types or non matching indexes within type */
		if (widget->dobj.widget.kcontrol_type[i] == type &&
		    widget->kcontrol_news[i].index == cdata->index) {
			kc = widget->kcontrols[i];
			break;
		}
	}

	if (!kc)
		return;

	snd_ctl_notify_one(swidget->scomp->card->snd_card,
			   SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
}

/* set up all controls for the widget */
static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
@@ -674,6 +852,7 @@ const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = {
	.bytes_ext_put = sof_ipc4_bytes_ext_put,
	.bytes_ext_get = sof_ipc4_bytes_ext_get,
	.bytes_ext_volatile_get = sof_ipc4_bytes_ext_volatile_get,
	.update = sof_ipc4_control_update,
	.widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup,
	.set_up_volume_table = sof_ipc4_set_up_volume_table,
};
+3 −0
Original line number Diff line number Diff line
@@ -115,6 +115,9 @@ int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev);
struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
							const guid_t *uuid);

struct snd_sof_widget *sof_ipc4_find_swidget_by_ids(struct snd_sof_dev *sdev,
						    u32 module_id, int instance_id);

struct sof_ipc4_base_module_cfg;
void sof_ipc4_update_cpc_from_manifest(struct snd_sof_dev *sdev,
				       struct sof_ipc4_fw_module *fw_module,
+20 −0
Original line number Diff line number Diff line
@@ -167,6 +167,26 @@ static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = {
	[SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)},
};

struct snd_sof_widget *sof_ipc4_find_swidget_by_ids(struct snd_sof_dev *sdev,
						    u32 module_id, int instance_id)
{
	struct snd_sof_widget *swidget;

	list_for_each_entry(swidget, &sdev->widget_list, list) {
		struct sof_ipc4_fw_module *fw_module = swidget->module_info;

		/* Only active module instances have valid instance_id */
		if (!swidget->use_count)
			continue;

		if (fw_module && fw_module->man4_module_entry.id == module_id &&
		    swidget->instance_id == instance_id)
			return swidget;
	}

	return NULL;
}

static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_pin_format *pin_fmt,
				      int num_formats)
{
+57 −0
Original line number Diff line number Diff line
@@ -78,6 +78,9 @@ static const struct sof_ipc4_fw_status {
	{165, "Reserved (ADSP_IPC_PIPELINE_ALREADY_EXISTS removed)"},
};

typedef void (*ipc4_notification_handler)(struct snd_sof_dev *sdev,
					  struct sof_ipc4_msg *msg);

static int sof_ipc4_check_reply_status(struct snd_sof_dev *sdev, u32 status)
{
	int i, ret;
@@ -610,9 +613,55 @@ static int ipc4_fw_ready(struct snd_sof_dev *sdev, struct sof_ipc4_msg *ipc4_msg
	return sof_ipc4_init_msg_memory(sdev);
}

static void sof_ipc4_module_notification_handler(struct snd_sof_dev *sdev,
						 struct sof_ipc4_msg *ipc4_msg)
{
	struct sof_ipc4_notify_module_data *data = ipc4_msg->data_ptr;

	/*
	 * If the notification includes additional, module specific data, then
	 * we need to re-allocate the buffer and re-read the whole payload,
	 * including the event_data
	 */
	if (data->event_data_size) {
		void *new;
		int ret;

		ipc4_msg->data_size += data->event_data_size;

		new = krealloc(ipc4_msg->data_ptr, ipc4_msg->data_size, GFP_KERNEL);
		if (!new) {
			ipc4_msg->data_size -= data->event_data_size;
			return;
		}

		/* re-read the whole payload */
		ipc4_msg->data_ptr = new;
		ret = snd_sof_ipc_msg_data(sdev, NULL, ipc4_msg->data_ptr,
					   ipc4_msg->data_size);
		if (ret < 0) {
			dev_err(sdev->dev,
				"Failed to read the full module notification: %d\n",
				ret);
			return;
		}
		data = ipc4_msg->data_ptr;
	}

	/* Handle ALSA kcontrol notification */
	if ((data->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK) ==
	    SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL) {
		const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;

		if (tplg_ops->control->update)
			tplg_ops->control->update(sdev, ipc4_msg);
	}
}

static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev)
{
	struct sof_ipc4_msg *ipc4_msg = sdev->ipc->msg.rx_data;
	ipc4_notification_handler handler_func = NULL;
	size_t data_size = 0;
	int err;

@@ -648,6 +697,10 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev)
	case SOF_IPC4_NOTIFY_EXCEPTION_CAUGHT:
		snd_sof_dsp_panic(sdev, 0, true);
		break;
	case SOF_IPC4_NOTIFY_MODULE_NOTIFICATION:
		data_size = sizeof(struct sof_ipc4_notify_module_data);
		handler_func = sof_ipc4_module_notification_handler;
		break;
	default:
		dev_dbg(sdev->dev, "Unhandled DSP message: %#x|%#x\n",
			ipc4_msg->primary, ipc4_msg->extension);
@@ -663,6 +716,10 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev)
		snd_sof_ipc_msg_data(sdev, NULL, ipc4_msg->data_ptr, ipc4_msg->data_size);
	}

	/* Handle notifications with payload */
	if (handler_func)
		handler_func(sdev, ipc4_msg);

	sof_ipc4_log_header(sdev->dev, "ipc rx done ", ipc4_msg, true);

	if (data_size) {