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