Commit 74fc96e8 authored by Mark Brown's avatar Mark Brown

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
......@@ -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
......@@ -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,
};
......@@ -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,
......
......@@ -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)
{
......
......@@ -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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment