1 // SPDX-License-Identifier: GPL-2.0-only 2 // 3 // ALSA SoC glue to use IIO devices as audio components 4 // 5 // Copyright 2023 CS GROUP France 6 // 7 // Author: Herve Codina <herve.codina@bootlin.com> 8 9 #include <linux/iio/consumer.h> 10 #include <linux/minmax.h> 11 #include <linux/mod_devicetable.h> 12 #include <linux/platform_device.h> 13 #include <linux/slab.h> 14 #include <linux/string_helpers.h> 15 16 #include <sound/soc.h> 17 #include <sound/tlv.h> 18 19 struct audio_iio_aux_chan { 20 struct iio_channel *iio_chan; 21 const char *name; 22 int max; 23 int min; 24 bool is_invert_range; 25 }; 26 27 struct audio_iio_aux { 28 struct device *dev; 29 unsigned int num_chans; 30 struct audio_iio_aux_chan chans[] __counted_by(num_chans); 31 }; 32 33 static int audio_iio_aux_info_volsw(struct snd_kcontrol *kcontrol, 34 struct snd_ctl_elem_info *uinfo) 35 { 36 struct audio_iio_aux_chan *chan = (struct audio_iio_aux_chan *)kcontrol->private_value; 37 38 uinfo->count = 1; 39 uinfo->value.integer.min = 0; 40 uinfo->value.integer.max = chan->max - chan->min; 41 uinfo->type = (uinfo->value.integer.max == 1) ? 42 SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; 43 return 0; 44 } 45 46 static int audio_iio_aux_get_volsw(struct snd_kcontrol *kcontrol, 47 struct snd_ctl_elem_value *ucontrol) 48 { 49 struct audio_iio_aux_chan *chan = (struct audio_iio_aux_chan *)kcontrol->private_value; 50 int max = chan->max; 51 int min = chan->min; 52 bool invert_range = chan->is_invert_range; 53 int ret; 54 int val; 55 56 ret = iio_read_channel_raw(chan->iio_chan, &val); 57 if (ret < 0) 58 return ret; 59 60 ucontrol->value.integer.value[0] = val - min; 61 if (invert_range) 62 ucontrol->value.integer.value[0] = max - ucontrol->value.integer.value[0]; 63 64 return 0; 65 } 66 67 static int audio_iio_aux_put_volsw(struct snd_kcontrol *kcontrol, 68 struct snd_ctl_elem_value *ucontrol) 69 { 70 struct audio_iio_aux_chan *chan = (struct audio_iio_aux_chan *)kcontrol->private_value; 71 int max = chan->max; 72 int min = chan->min; 73 bool invert_range = chan->is_invert_range; 74 int val; 75 int ret; 76 int tmp; 77 78 val = ucontrol->value.integer.value[0]; 79 if (val < 0) 80 return -EINVAL; 81 if (val > max - min) 82 return -EINVAL; 83 84 val = val + min; 85 if (invert_range) 86 val = max - val; 87 88 ret = iio_read_channel_raw(chan->iio_chan, &tmp); 89 if (ret < 0) 90 return ret; 91 92 if (tmp == val) 93 return 0; 94 95 ret = iio_write_channel_raw(chan->iio_chan, val); 96 if (ret) 97 return ret; 98 99 return 1; /* The value changed */ 100 } 101 102 static int audio_iio_aux_add_controls(struct snd_soc_component *component, 103 struct audio_iio_aux_chan *chan) 104 { 105 struct snd_kcontrol_new control = { 106 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 107 .name = chan->name, 108 .info = audio_iio_aux_info_volsw, 109 .get = audio_iio_aux_get_volsw, 110 .put = audio_iio_aux_put_volsw, 111 .private_value = (unsigned long)chan, 112 }; 113 114 return snd_soc_add_component_controls(component, &control, 1); 115 } 116 117 /* 118 * These data could be on stack but they are pretty big. 119 * As ASoC internally copy them and protect them against concurrent accesses 120 * (snd_soc_bind_card() protects using client_mutex), keep them in the global 121 * data area. 122 */ 123 static struct snd_soc_dapm_widget widgets[3]; 124 static struct snd_soc_dapm_route routes[2]; 125 126 /* Be sure sizes are correct (need 3 widgets and 2 routes) */ 127 static_assert(ARRAY_SIZE(widgets) >= 3, "3 widgets are needed"); 128 static_assert(ARRAY_SIZE(routes) >= 2, "2 routes are needed"); 129 130 static int audio_iio_aux_add_dapms(struct snd_soc_component *component, 131 struct audio_iio_aux_chan *chan) 132 { 133 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); 134 char *output_name; 135 char *input_name; 136 char *pga_name; 137 int ret; 138 139 input_name = kasprintf(GFP_KERNEL, "%s IN", chan->name); 140 if (!input_name) 141 return -ENOMEM; 142 143 output_name = kasprintf(GFP_KERNEL, "%s OUT", chan->name); 144 if (!output_name) { 145 ret = -ENOMEM; 146 goto out_free_input_name; 147 } 148 149 pga_name = kasprintf(GFP_KERNEL, "%s PGA", chan->name); 150 if (!pga_name) { 151 ret = -ENOMEM; 152 goto out_free_output_name; 153 } 154 155 widgets[0] = SND_SOC_DAPM_INPUT(input_name); 156 widgets[1] = SND_SOC_DAPM_OUTPUT(output_name); 157 widgets[2] = SND_SOC_DAPM_PGA(pga_name, SND_SOC_NOPM, 0, 0, NULL, 0); 158 ret = snd_soc_dapm_new_controls(dapm, widgets, 3); 159 if (ret) 160 goto out_free_pga_name; 161 162 routes[0].sink = pga_name; 163 routes[0].control = NULL; 164 routes[0].source = input_name; 165 routes[1].sink = output_name; 166 routes[1].control = NULL; 167 routes[1].source = pga_name; 168 ret = snd_soc_dapm_add_routes(dapm, routes, 2); 169 170 /* Allocated names are no more needed (duplicated in ASoC internals) */ 171 172 out_free_pga_name: 173 kfree(pga_name); 174 out_free_output_name: 175 kfree(output_name); 176 out_free_input_name: 177 kfree(input_name); 178 return ret; 179 } 180 181 static int audio_iio_aux_component_probe(struct snd_soc_component *component) 182 { 183 struct audio_iio_aux *iio_aux = snd_soc_component_get_drvdata(component); 184 struct audio_iio_aux_chan *chan; 185 int ret; 186 int i; 187 188 for (i = 0; i < iio_aux->num_chans; i++) { 189 chan = iio_aux->chans + i; 190 191 ret = iio_read_max_channel_raw(chan->iio_chan, &chan->max); 192 if (ret) 193 return dev_err_probe(component->dev, ret, 194 "chan[%d] %s: Cannot get max raw value\n", 195 i, chan->name); 196 197 ret = iio_read_min_channel_raw(chan->iio_chan, &chan->min); 198 if (ret) 199 return dev_err_probe(component->dev, ret, 200 "chan[%d] %s: Cannot get min raw value\n", 201 i, chan->name); 202 203 if (chan->min > chan->max) { 204 /* 205 * This should never happen but to avoid any check 206 * later, just swap values here to ensure that the 207 * minimum value is lower than the maximum value. 208 */ 209 dev_dbg(component->dev, "chan[%d] %s: Swap min and max\n", 210 i, chan->name); 211 swap(chan->min, chan->max); 212 } 213 214 /* Set initial value */ 215 ret = iio_write_channel_raw(chan->iio_chan, 216 chan->is_invert_range ? chan->max : chan->min); 217 if (ret) 218 return dev_err_probe(component->dev, ret, 219 "chan[%d] %s: Cannot set initial value\n", 220 i, chan->name); 221 222 ret = audio_iio_aux_add_controls(component, chan); 223 if (ret) 224 return ret; 225 226 ret = audio_iio_aux_add_dapms(component, chan); 227 if (ret) 228 return ret; 229 230 dev_dbg(component->dev, "chan[%d]: Added %s (min=%d, max=%d, invert=%s)\n", 231 i, chan->name, chan->min, chan->max, 232 str_on_off(chan->is_invert_range)); 233 } 234 235 return 0; 236 } 237 238 static const struct snd_soc_component_driver audio_iio_aux_component_driver = { 239 .probe = audio_iio_aux_component_probe, 240 }; 241 242 static int audio_iio_aux_probe(struct platform_device *pdev) 243 { 244 struct audio_iio_aux_chan *iio_aux_chan; 245 struct device *dev = &pdev->dev; 246 struct audio_iio_aux *iio_aux; 247 const char **names; 248 u32 *invert_ranges; 249 int count; 250 int ret; 251 int i; 252 253 count = device_property_string_array_count(dev, "io-channel-names"); 254 if (count < 0) 255 return dev_err_probe(dev, count, "failed to count io-channel-names\n"); 256 257 iio_aux = devm_kzalloc(dev, struct_size(iio_aux, chans, count), GFP_KERNEL); 258 if (!iio_aux) 259 return -ENOMEM; 260 261 iio_aux->dev = dev; 262 263 iio_aux->num_chans = count; 264 265 names = kcalloc(iio_aux->num_chans, sizeof(*names), GFP_KERNEL); 266 if (!names) 267 return -ENOMEM; 268 269 invert_ranges = kcalloc(iio_aux->num_chans, sizeof(*invert_ranges), GFP_KERNEL); 270 if (!invert_ranges) { 271 ret = -ENOMEM; 272 goto out_free_names; 273 } 274 275 ret = device_property_read_string_array(dev, "io-channel-names", 276 names, iio_aux->num_chans); 277 if (ret < 0) { 278 dev_err_probe(dev, ret, "failed to read io-channel-names\n"); 279 goto out_free_invert_ranges; 280 } 281 282 /* 283 * snd-control-invert-range is optional and can contain fewer items 284 * than the number of channels. Unset values default to 0. 285 */ 286 count = device_property_count_u32(dev, "snd-control-invert-range"); 287 if (count > 0) { 288 count = min_t(unsigned int, count, iio_aux->num_chans); 289 ret = device_property_read_u32_array(dev, "snd-control-invert-range", 290 invert_ranges, count); 291 if (ret < 0) { 292 dev_err_probe(dev, ret, "failed to read snd-control-invert-range\n"); 293 goto out_free_invert_ranges; 294 } 295 } 296 297 for (i = 0; i < iio_aux->num_chans; i++) { 298 iio_aux_chan = iio_aux->chans + i; 299 iio_aux_chan->name = names[i]; 300 iio_aux_chan->is_invert_range = invert_ranges[i]; 301 302 iio_aux_chan->iio_chan = devm_iio_channel_get(dev, iio_aux_chan->name); 303 if (IS_ERR(iio_aux_chan->iio_chan)) { 304 ret = PTR_ERR(iio_aux_chan->iio_chan); 305 dev_err_probe(dev, ret, "get IIO channel '%s' failed\n", 306 iio_aux_chan->name); 307 goto out_free_invert_ranges; 308 } 309 } 310 311 platform_set_drvdata(pdev, iio_aux); 312 313 ret = devm_snd_soc_register_component(dev, &audio_iio_aux_component_driver, 314 NULL, 0); 315 out_free_invert_ranges: 316 kfree(invert_ranges); 317 out_free_names: 318 kfree(names); 319 return ret; 320 } 321 322 static const struct of_device_id audio_iio_aux_ids[] = { 323 { .compatible = "audio-iio-aux" }, 324 { } 325 }; 326 MODULE_DEVICE_TABLE(of, audio_iio_aux_ids); 327 328 static struct platform_driver audio_iio_aux_driver = { 329 .driver = { 330 .name = "audio-iio-aux", 331 .of_match_table = audio_iio_aux_ids, 332 }, 333 .probe = audio_iio_aux_probe, 334 }; 335 module_platform_driver(audio_iio_aux_driver); 336 337 MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); 338 MODULE_DESCRIPTION("IIO ALSA SoC aux driver"); 339 MODULE_LICENSE("GPL"); 340