ASoC: SOF: ipc4-control: Add support for ALSA enum control
authorPeter Ujfalusi <peter.ujfalusi@linux.intel.com>
Tue, 19 Sep 2023 10:31:15 +0000 (13:31 +0300)
committerMark Brown <broonie@kernel.org>
Tue, 19 Sep 2023 12:48:56 +0000 (13:48 +0100)
Enum controls use generic param_id and a generic struct where the data
is passed to the firmware.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20230919103115.30783-4-peter.ujfalusi@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/sof/ipc4-control.c
sound/soc/sof/ipc4-topology.c

index cabdd89..938efac 100644 (file)
@@ -297,6 +297,63 @@ static int sof_ipc4_switch_get(struct snd_sof_control *scontrol,
        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)
@@ -562,6 +619,11 @@ static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_s
                                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;
                        }
@@ -605,6 +667,8 @@ const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = {
        .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,
index 093dfa5..afd23d3 100644 (file)
@@ -2128,6 +2128,36 @@ static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof
        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;
@@ -2202,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;
        }