xref: /linux/sound/soc/codecs/audio-iio-aux.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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 
audio_iio_aux_info_volsw(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_info * uinfo)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 
audio_iio_aux_get_volsw(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_value * ucontrol)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 
audio_iio_aux_put_volsw(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_value * ucontrol)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 
audio_iio_aux_add_controls(struct snd_soc_component * component,struct audio_iio_aux_chan * chan)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 
audio_iio_aux_add_dapms(struct snd_soc_component * component,struct audio_iio_aux_chan * chan)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 
audio_iio_aux_component_probe(struct snd_soc_component * component)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 
audio_iio_aux_probe(struct platform_device * pdev)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