brintos

brintos / linux-shallow public Read only

0
0
Text · 8.5 KiB · 588e480 Raw
315 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2//3// ALSA SoC glue to use IIO devices as audio components4//5// Copyright 2023 CS GROUP France6//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 20struct 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 28struct 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 34static 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 47static 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 68static 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 103static 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 accesses121 * (snd_soc_bind_card() protects using client_mutex), keep them in the global122 * data area.123 */124static struct snd_soc_dapm_widget widgets[3];125static struct snd_soc_dapm_route routes[2];126 127/* Be sure sizes are correct (need 3 widgets and 2 routes) */128static_assert(ARRAY_SIZE(widgets) >= 3, "3 widgets are needed");129static_assert(ARRAY_SIZE(routes) >= 2, "2 routes are needed");130 131static 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 167static 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 check192			 * later, just swap values here to ensure that the193			 * 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 224static const struct snd_soc_component_driver audio_iio_aux_component_driver = {225	.probe = audio_iio_aux_component_probe,226};227 228static 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 items268	 * 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 297static const struct of_device_id audio_iio_aux_ids[] = {298	{ .compatible = "audio-iio-aux" },299	{ }300};301MODULE_DEVICE_TABLE(of, audio_iio_aux_ids);302 303static 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};310module_platform_driver(audio_iio_aux_driver);311 312MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");313MODULE_DESCRIPTION("IIO ALSA SoC aux driver");314MODULE_LICENSE("GPL");315