Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) // // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // // Copyright(c) 2022 Intel Corporation. All rights reserved. // // #include "sof-priv.h" #include "sof-audio.h" #include "ipc4-priv.h" #include "ipc4-topology.h" static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set) { 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); const struct sof_ipc_ops *iops = sdev->ipc->ops; struct sof_ipc4_msg *msg = &cdata->msg; struct snd_sof_widget *swidget; bool widget_found = false; /* 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 -ENOENT; } /* * Volatile controls should always be part of static pipelines and the widget use_count * would always be > 0 in this case. For the others, just return the cached value if the * widget is not set up. */ if (!swidget->use_count) return 0; msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK; msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id); return iops->set_get_data(sdev, msg, msg->data_size, set); } static int sof_ipc4_set_volume_data(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct snd_sof_control *scontrol) { struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; struct sof_ipc4_gain *gain = swidget->private; struct sof_ipc4_msg *msg = &cdata->msg; struct sof_ipc4_gain_data data; bool all_channels_equal = true; u32 value; int ret, i; /* check if all channel values are equal */ value = cdata->chanv[0].value; for (i = 1; i < scontrol->num_channels; i++) { if (cdata->chanv[i].value != value) { all_channels_equal = false; break; } } /* * notify DSP with a single IPC message if all channel values are equal. Otherwise send * a separate IPC for each channel. */ for (i = 0; i < scontrol->num_channels; i++) { if (all_channels_equal) { data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK; data.init_val = cdata->chanv[0].value; } else { data.channels = cdata->chanv[i].channel; data.init_val = cdata->chanv[i].value; } /* set curve type and duration from topology */ data.curve_duration = gain->data.curve_duration; data.curve_type = gain->data.curve_type; msg->data_ptr = &data; msg->data_size = sizeof(data); ret = sof_ipc4_set_get_kcontrol_data(scontrol, true); msg->data_ptr = NULL; msg->data_size = 0; if (ret < 0) { dev_err(sdev->dev, "Failed to set volume update for %s\n", scontrol->name); return ret; } if (all_channels_equal) break; } return 0; } static bool sof_ipc4_volume_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); unsigned int channels = scontrol->num_channels; struct snd_sof_widget *swidget; bool widget_found = false; bool change = false; unsigned int i; int ret; /* update each channel */ for (i = 0; i < channels; i++) { u32 value = mixer_to_ipc(ucontrol->value.integer.value[i], scontrol->volume_table, scontrol->max + 1); 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_volume_data(sdev, swidget, scontrol); if (ret < 0) return false; return change; } static int sof_ipc4_volume_get(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol) { struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; unsigned int channels = scontrol->num_channels; unsigned int i; for (i = 0; i < channels; i++) ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value, scontrol->volume_table, scontrol->max + 1); return 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) { struct snd_sof_control *scontrol; int ret; list_for_each_entry(scontrol, &sdev->kcontrol_list, list) if (scontrol->comp_id == swidget->comp_id) { ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol); if (ret < 0) { dev_err(sdev->dev, "%s: kcontrol %d set up failed for widget %s\n", __func__, scontrol->comp_id, swidget->widget->name); return ret; } } return 0; } static int sof_ipc4_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size) { int i; /* init the volume table */ scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); if (!scontrol->volume_table) return -ENOMEM; /* populate the volume table */ for (i = 0; i < size ; i++) { u32 val = vol_compute_gain(i, tlv); u64 q31val = ((u64)val) << 15; /* Can be over Q1.31, need to saturate */ scontrol->volume_table[i] = q31val > SOF_IPC4_VOL_ZERO_DB ? SOF_IPC4_VOL_ZERO_DB : q31val; } return 0; } const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = { .volume_put = sof_ipc4_volume_put, .volume_get = sof_ipc4_volume_get, .widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup, .set_up_volume_table = sof_ipc4_set_up_volume_table, }; |