1 // SPDX-License-Identifier: GPL-2.0-only
3 // ALSA SoC glue to use IIO devices as audio components
5 // Copyright 2023 CS GROUP France
7 // Author: Herve Codina <herve.codina@bootlin.com>
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>
17 #include <sound/soc.h>
18 #include <sound/tlv.h>
20 struct audio_iio_aux_chan
{
21 struct iio_channel
*iio_chan
;
28 struct audio_iio_aux
{
30 unsigned int num_chans
;
31 struct audio_iio_aux_chan chans
[] __counted_by(num_chans
);
34 static int audio_iio_aux_info_volsw(struct snd_kcontrol
*kcontrol
,
35 struct snd_ctl_elem_info
*uinfo
)
37 struct audio_iio_aux_chan
*chan
= (struct audio_iio_aux_chan
*)kcontrol
->private_value
;
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
;
47 static int audio_iio_aux_get_volsw(struct snd_kcontrol
*kcontrol
,
48 struct snd_ctl_elem_value
*ucontrol
)
50 struct audio_iio_aux_chan
*chan
= (struct audio_iio_aux_chan
*)kcontrol
->private_value
;
53 bool invert_range
= chan
->is_invert_range
;
57 ret
= iio_read_channel_raw(chan
->iio_chan
, &val
);
61 ucontrol
->value
.integer
.value
[0] = val
- min
;
63 ucontrol
->value
.integer
.value
[0] = max
- ucontrol
->value
.integer
.value
[0];
68 static int audio_iio_aux_put_volsw(struct snd_kcontrol
*kcontrol
,
69 struct snd_ctl_elem_value
*ucontrol
)
71 struct audio_iio_aux_chan
*chan
= (struct audio_iio_aux_chan
*)kcontrol
->private_value
;
74 bool invert_range
= chan
->is_invert_range
;
79 val
= ucontrol
->value
.integer
.value
[0];
89 ret
= iio_read_channel_raw(chan
->iio_chan
, &tmp
);
96 ret
= iio_write_channel_raw(chan
->iio_chan
, val
);
100 return 1; /* The value changed */
103 static int audio_iio_aux_add_controls(struct snd_soc_component
*component
,
104 struct audio_iio_aux_chan
*chan
)
106 struct snd_kcontrol_new control
= {
107 .iface
= SNDRV_CTL_ELEM_IFACE_MIXER
,
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
,
115 return snd_soc_add_component_controls(component
, &control
, 1);
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
124 static struct snd_soc_dapm_widget widgets
[3];
125 static struct snd_soc_dapm_route routes
[2];
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");
131 static int audio_iio_aux_add_dapms(struct snd_soc_component
*component
,
132 struct audio_iio_aux_chan
*chan
)
134 struct snd_soc_dapm_context
*dapm
= snd_soc_component_get_dapm(component
);
137 /* Allocated names are not needed afterwards (duplicated in ASoC internals) */
138 char *input_name
__free(kfree
) = kasprintf(GFP_KERNEL
, "%s IN", chan
->name
);
142 char *output_name
__free(kfree
) = kasprintf(GFP_KERNEL
, "%s OUT", chan
->name
);
146 char *pga_name
__free(kfree
) = kasprintf(GFP_KERNEL
, "%s PGA", chan
->name
);
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);
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
;
164 return snd_soc_dapm_add_routes(dapm
, routes
, 2);
167 static int audio_iio_aux_component_probe(struct snd_soc_component
*component
)
169 struct audio_iio_aux
*iio_aux
= snd_soc_component_get_drvdata(component
);
170 struct audio_iio_aux_chan
*chan
;
174 for (i
= 0; i
< iio_aux
->num_chans
; i
++) {
175 chan
= iio_aux
->chans
+ i
;
177 ret
= iio_read_max_channel_raw(chan
->iio_chan
, &chan
->max
);
179 return dev_err_probe(component
->dev
, ret
,
180 "chan[%d] %s: Cannot get max raw value\n",
183 ret
= iio_read_min_channel_raw(chan
->iio_chan
, &chan
->min
);
185 return dev_err_probe(component
->dev
, ret
,
186 "chan[%d] %s: Cannot get min raw value\n",
189 if (chan
->min
> chan
->max
) {
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.
195 dev_dbg(component
->dev
, "chan[%d] %s: Swap min and max\n",
197 swap(chan
->min
, chan
->max
);
200 /* Set initial value */
201 ret
= iio_write_channel_raw(chan
->iio_chan
,
202 chan
->is_invert_range
? chan
->max
: chan
->min
);
204 return dev_err_probe(component
->dev
, ret
,
205 "chan[%d] %s: Cannot set initial value\n",
208 ret
= audio_iio_aux_add_controls(component
, chan
);
212 ret
= audio_iio_aux_add_dapms(component
, chan
);
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
));
224 static const struct snd_soc_component_driver audio_iio_aux_component_driver
= {
225 .probe
= audio_iio_aux_component_probe
,
228 static int audio_iio_aux_probe(struct platform_device
*pdev
)
230 struct audio_iio_aux_chan
*iio_aux_chan
;
231 struct device
*dev
= &pdev
->dev
;
232 struct audio_iio_aux
*iio_aux
;
237 count
= device_property_string_array_count(dev
, "io-channel-names");
239 return dev_err_probe(dev
, count
, "failed to count io-channel-names\n");
241 iio_aux
= devm_kzalloc(dev
, struct_size(iio_aux
, chans
, count
), GFP_KERNEL
);
247 iio_aux
->num_chans
= count
;
249 const char **names
__free(kfree
) = kcalloc(iio_aux
->num_chans
,
255 u32
*invert_ranges
__free(kfree
) = kcalloc(iio_aux
->num_chans
,
256 sizeof(*invert_ranges
),
261 ret
= device_property_read_string_array(dev
, "io-channel-names",
262 names
, iio_aux
->num_chans
);
264 return dev_err_probe(dev
, ret
, "failed to read io-channel-names\n");
267 * snd-control-invert-range is optional and can contain fewer items
268 * than the number of channels. Unset values default to 0.
270 count
= device_property_count_u32(dev
, "snd-control-invert-range");
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
);
276 return dev_err_probe(dev
, ret
, "failed to read snd-control-invert-range\n");
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
];
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",
291 platform_set_drvdata(pdev
, iio_aux
);
293 return devm_snd_soc_register_component(dev
, &audio_iio_aux_component_driver
,
297 static const struct of_device_id audio_iio_aux_ids
[] = {
298 { .compatible
= "audio-iio-aux" },
301 MODULE_DEVICE_TABLE(of
, audio_iio_aux_ids
);
303 static struct platform_driver audio_iio_aux_driver
= {
305 .name
= "audio-iio-aux",
306 .of_match_table
= audio_iio_aux_ids
,
308 .probe
= audio_iio_aux_probe
,
310 module_platform_driver(audio_iio_aux_driver
);
312 MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
313 MODULE_DESCRIPTION("IIO ALSA SoC aux driver");
314 MODULE_LICENSE("GPL");