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: chan->iio_chan, val: &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: chan->iio_chan, val: &tmp); |
89 | if (ret < 0) |
90 | return ret; |
91 | |
92 | if (tmp == val) |
93 | return 0; |
94 | |
95 | ret = iio_write_channel_raw(chan: 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, controls: &control, num_controls: 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, fmt: "%s IN" , chan->name); |
140 | if (!input_name) |
141 | return -ENOMEM; |
142 | |
143 | output_name = kasprintf(GFP_KERNEL, fmt: "%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, fmt: "%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, widget: widgets, num: 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, route: routes, num: 2); |
169 | |
170 | /* Allocated names are no more needed (duplicated in ASoC internals) */ |
171 | |
172 | out_free_pga_name: |
173 | kfree(objp: pga_name); |
174 | out_free_output_name: |
175 | kfree(objp: output_name); |
176 | out_free_input_name: |
177 | kfree(objp: 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(c: 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: chan->iio_chan, val: &chan->max); |
192 | if (ret) |
193 | return dev_err_probe(dev: component->dev, err: ret, |
194 | fmt: "chan[%d] %s: Cannot get max raw value\n" , |
195 | i, chan->name); |
196 | |
197 | ret = iio_read_min_channel_raw(chan: chan->iio_chan, val: &chan->min); |
198 | if (ret) |
199 | return dev_err_probe(dev: component->dev, err: ret, |
200 | fmt: "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: chan->iio_chan, |
216 | val: chan->is_invert_range ? chan->max : chan->min); |
217 | if (ret) |
218 | return dev_err_probe(dev: component->dev, err: ret, |
219 | fmt: "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, propname: "io-channel-names" ); |
254 | if (count < 0) |
255 | return dev_err_probe(dev, err: count, fmt: "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(n: iio_aux->num_chans, size: sizeof(*names), GFP_KERNEL); |
266 | if (!names) |
267 | return -ENOMEM; |
268 | |
269 | invert_ranges = kcalloc(n: iio_aux->num_chans, size: 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, propname: "io-channel-names" , |
276 | val: names, nval: iio_aux->num_chans); |
277 | if (ret < 0) { |
278 | dev_err_probe(dev, err: ret, fmt: "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, propname: "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, propname: "snd-control-invert-range" , |
290 | val: invert_ranges, nval: count); |
291 | if (ret < 0) { |
292 | dev_err_probe(dev, err: ret, fmt: "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, consumer_channel: iio_aux_chan->name); |
303 | if (IS_ERR(ptr: iio_aux_chan->iio_chan)) { |
304 | ret = PTR_ERR(ptr: iio_aux_chan->iio_chan); |
305 | dev_err_probe(dev, err: ret, fmt: "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, data: iio_aux); |
312 | |
313 | ret = devm_snd_soc_register_component(dev, component_driver: &audio_iio_aux_component_driver, |
314 | NULL, num_dai: 0); |
315 | out_free_invert_ranges: |
316 | kfree(objp: invert_ranges); |
317 | out_free_names: |
318 | kfree(objp: 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 | |