Commit b8c442b3 authored by Mark Brown's avatar Mark Brown

Add support for IIO devices in ASoC

Merge series from Herve Codina <herve.codina@bootlin.com>:

Several weeks ago, I sent a series [1] for adding a potentiometer as an
auxiliary device in ASoC. The feedback was that the potentiometer should
be directly handled in IIO (as other potentiometers) and something more
generic should be present in ASoC in order to have a binding to import
some IIO devices into sound cards.

The series related to the IIO potentiometer device is already applied.

This series introduces audio-iio-aux. Its goal is to offer the binding
between IIO and ASoC.
It exposes attached IIO devices as ASoC auxiliary devices and allows to
control them through mixer controls.

On my system, the IIO device is a potentiometer and it is present in an
amplifier design present in the audio path.
parents c7a0f10b 6d8ad35d
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/sound/audio-iio-aux.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Audio IIO auxiliary
maintainers:
- Herve Codina <herve.codina@bootlin.com>
description:
Auxiliary device based on Industrial I/O device channels
allOf:
- $ref: dai-common.yaml#
properties:
compatible:
const: audio-iio-aux
io-channels:
description:
Industrial I/O device channels used
io-channel-names:
description:
Industrial I/O channel names related to io-channels.
These names are used to provides sound controls, widgets and routes names.
snd-control-invert-range:
$ref: /schemas/types.yaml#/definitions/uint32-array
description: |
A list of 0/1 flags defining whether or not the related channel is
inverted
items:
enum: [0, 1]
default: 0
description: |
Invert the sound control value compared to the IIO channel raw value.
- 1: The related sound control value is inverted meaning that the
minimum sound control value correspond to the maximum IIO channel
raw value and the maximum sound control value correspond to the
minimum IIO channel raw value.
- 0: The related sound control value is not inverted meaning that the
minimum (resp maximum) sound control value correspond to the
minimum (resp maximum) IIO channel raw value.
required:
- compatible
- io-channels
- io-channel-names
unevaluatedProperties: false
examples:
- |
iio-aux {
compatible = "audio-iio-aux";
io-channels = <&iio 0>, <&iio 1>, <&iio 2>, <&iio 3>;
io-channel-names = "CH0", "CH1", "CH2", "CH3";
/* Invert CH1 and CH2 */
snd-control-invert-range = <0 1 1 0>;
};
...@@ -148,6 +148,15 @@ definitions: ...@@ -148,6 +148,15 @@ definitions:
required: required:
- sound-dai - sound-dai
additional-devs:
type: object
description:
Additional devices used by the simple audio card.
patternProperties:
'^iio-aux(-.+)?$':
type: object
$ref: audio-iio-aux.yaml#
properties: properties:
compatible: compatible:
contains: contains:
...@@ -187,6 +196,8 @@ properties: ...@@ -187,6 +196,8 @@ properties:
$ref: "#/definitions/mclk-fs" $ref: "#/definitions/mclk-fs"
simple-audio-card,aux-devs: simple-audio-card,aux-devs:
$ref: "#/definitions/aux-devs" $ref: "#/definitions/aux-devs"
simple-audio-card,additional-devs:
$ref: "#/definitions/additional-devs"
simple-audio-card,convert-rate: simple-audio-card,convert-rate:
$ref: "#/definitions/convert-rate" $ref: "#/definitions/convert-rate"
simple-audio-card,convert-channels: simple-audio-card,convert-channels:
...@@ -359,6 +370,48 @@ examples: ...@@ -359,6 +370,48 @@ examples:
}; };
}; };
# --------------------
# route audio to/from a codec through an amplifier
# designed with a potentiometer driven by IIO:
# --------------------
- |
sound {
compatible = "simple-audio-card";
simple-audio-card,aux-devs = <&amp_in>, <&amp_out>;
simple-audio-card,routing =
"CODEC LEFTIN", "AMP_IN LEFT OUT",
"CODEC RIGHTIN", "AMP_IN RIGHT OUT",
"AMP_OUT LEFT IN", "CODEC LEFTOUT",
"AMP_OUT RIGHT IN", "CODEC RIGHTOUT";
simple-audio-card,additional-devs {
amp_out: iio-aux-out {
compatible = "audio-iio-aux";
io-channels = <&pot_out 0>, <&pot_out 1>;
io-channel-names = "LEFT", "RIGHT";
snd-control-invert-range = <1 1>;
sound-name-prefix = "AMP_OUT";
};
amp_in: iio_aux-in {
compatible = "audio-iio-aux";
io-channels = <&pot_in 0>, <&pot_in 1>;
io-channel-names = "LEFT", "RIGHT";
sound-name-prefix = "AMP_IN";
};
};
simple-audio-card,cpu {
sound-dai = <&cpu>;
};
simple-audio-card,codec {
sound-dai = <&codec>;
clocks = <&clocks>;
};
};
# -------------------- # --------------------
# Sampling Rate Conversion # Sampling Rate Conversion
# -------------------- # --------------------
......
...@@ -5,9 +5,10 @@ ...@@ -5,9 +5,10 @@
*/ */
#include <linux/err.h> #include <linux/err.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/iio/iio.h> #include <linux/iio/iio.h>
#include <linux/iio/iio-opaque.h> #include <linux/iio/iio-opaque.h>
...@@ -849,15 +850,14 @@ static int iio_channel_read_max(struct iio_channel *chan, ...@@ -849,15 +850,14 @@ static int iio_channel_read_max(struct iio_channel *chan,
int *val, int *val2, int *type, int *val, int *val2, int *type,
enum iio_chan_info_enum info) enum iio_chan_info_enum info)
{ {
int unused;
const int *vals; const int *vals;
int length; int length;
int ret; int ret;
if (!val2)
val2 = &unused;
ret = iio_channel_read_avail(chan, &vals, type, &length, info); ret = iio_channel_read_avail(chan, &vals, type, &length, info);
if (ret < 0)
return ret;
switch (ret) { switch (ret) {
case IIO_AVAIL_RANGE: case IIO_AVAIL_RANGE:
switch (*type) { switch (*type) {
...@@ -866,6 +866,7 @@ static int iio_channel_read_max(struct iio_channel *chan, ...@@ -866,6 +866,7 @@ static int iio_channel_read_max(struct iio_channel *chan,
break; break;
default: default:
*val = vals[4]; *val = vals[4];
if (val2)
*val2 = vals[5]; *val2 = vals[5];
} }
return 0; return 0;
...@@ -875,20 +876,16 @@ static int iio_channel_read_max(struct iio_channel *chan, ...@@ -875,20 +876,16 @@ static int iio_channel_read_max(struct iio_channel *chan,
return -EINVAL; return -EINVAL;
switch (*type) { switch (*type) {
case IIO_VAL_INT: case IIO_VAL_INT:
*val = vals[--length]; *val = max_array(vals, length);
while (length) {
if (vals[--length] > *val)
*val = vals[length];
}
break; break;
default: default:
/* FIXME: learn about max for other iio values */ /* TODO: learn about max for other iio values */
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
default: default:
return ret; return -EINVAL;
} }
} }
...@@ -912,6 +909,69 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val) ...@@ -912,6 +909,69 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val)
} }
EXPORT_SYMBOL_GPL(iio_read_max_channel_raw); EXPORT_SYMBOL_GPL(iio_read_max_channel_raw);
static int iio_channel_read_min(struct iio_channel *chan,
int *val, int *val2, int *type,
enum iio_chan_info_enum info)
{
const int *vals;
int length;
int ret;
ret = iio_channel_read_avail(chan, &vals, type, &length, info);
if (ret < 0)
return ret;
switch (ret) {
case IIO_AVAIL_RANGE:
switch (*type) {
case IIO_VAL_INT:
*val = vals[0];
break;
default:
*val = vals[0];
if (val2)
*val2 = vals[1];
}
return 0;
case IIO_AVAIL_LIST:
if (length <= 0)
return -EINVAL;
switch (*type) {
case IIO_VAL_INT:
*val = min_array(vals, length);
break;
default:
/* TODO: learn about min for other iio values */
return -EINVAL;
}
return 0;
default:
return -EINVAL;
}
}
int iio_read_min_channel_raw(struct iio_channel *chan, int *val)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
int ret;
int type;
mutex_lock(&iio_dev_opaque->info_exist_lock);
if (!chan->indio_dev->info) {
ret = -ENODEV;
goto err_unlock;
}
ret = iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
err_unlock:
mutex_unlock(&iio_dev_opaque->info_exist_lock);
return ret;
}
EXPORT_SYMBOL_GPL(iio_read_min_channel_raw);
int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type) int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
{ {
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
......
...@@ -201,8 +201,9 @@ struct iio_dev ...@@ -201,8 +201,9 @@ struct iio_dev
* @chan: The channel being queried. * @chan: The channel being queried.
* @val: Value read back. * @val: Value read back.
* *
* Note raw reads from iio channels are in adc counts and hence * Note, if standard units are required, raw reads from iio channels
* scale will need to be applied if standard units required. * need the offset (default 0) and scale (default 1) to be applied
* as (raw + offset) * scale.
*/ */
int iio_read_channel_raw(struct iio_channel *chan, int iio_read_channel_raw(struct iio_channel *chan,
int *val); int *val);
...@@ -212,8 +213,9 @@ int iio_read_channel_raw(struct iio_channel *chan, ...@@ -212,8 +213,9 @@ int iio_read_channel_raw(struct iio_channel *chan,
* @chan: The channel being queried. * @chan: The channel being queried.
* @val: Value read back. * @val: Value read back.
* *
* Note raw reads from iio channels are in adc counts and hence * Note, if standard units are required, raw reads from iio channels
* scale will need to be applied if standard units required. * need the offset (default 0) and scale (default 1) to be applied
* as (raw + offset) * scale.
* *
* In opposit to the normal iio_read_channel_raw this function * In opposit to the normal iio_read_channel_raw this function
* returns the average of multiple reads. * returns the average of multiple reads.
...@@ -281,8 +283,9 @@ int iio_read_channel_attribute(struct iio_channel *chan, int *val, ...@@ -281,8 +283,9 @@ int iio_read_channel_attribute(struct iio_channel *chan, int *val,
* @chan: The channel being queried. * @chan: The channel being queried.
* @val: Value being written. * @val: Value being written.
* *
* Note raw writes to iio channels are in dac counts and hence * Note that for raw writes to iio channels, if the value provided is
* scale will need to be applied if standard units required. * in standard units, the affect of the scale and offset must be removed
* as (value / scale) - offset.
*/ */
int iio_write_channel_raw(struct iio_channel *chan, int val); int iio_write_channel_raw(struct iio_channel *chan, int val);
...@@ -292,11 +295,24 @@ int iio_write_channel_raw(struct iio_channel *chan, int val); ...@@ -292,11 +295,24 @@ int iio_write_channel_raw(struct iio_channel *chan, int val);
* @chan: The channel being queried. * @chan: The channel being queried.
* @val: Value read back. * @val: Value read back.
* *
* Note raw reads from iio channels are in adc counts and hence * Note, if standard units are required, raw reads from iio channels
* scale will need to be applied if standard units are required. * need the offset (default 0) and scale (default 1) to be applied
* as (raw + offset) * scale.
*/ */
int iio_read_max_channel_raw(struct iio_channel *chan, int *val); int iio_read_max_channel_raw(struct iio_channel *chan, int *val);
/**
* iio_read_min_channel_raw() - read minimum available raw value from a given
* channel, i.e. the minimum possible value.
* @chan: The channel being queried.
* @val: Value read back.
*
* Note, if standard units are required, raw reads from iio channels
* need the offset (default 0) and scale (default 1) to be applied
* as (raw + offset) * scale.
*/
int iio_read_min_channel_raw(struct iio_channel *chan, int *val);
/** /**
* iio_read_avail_channel_raw() - read available raw values from a given channel * iio_read_avail_channel_raw() - read available raw values from a given channel
* @chan: The channel being queried. * @chan: The channel being queried.
...@@ -308,8 +324,9 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val); ...@@ -308,8 +324,9 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val);
* For ranges, three vals are always returned; min, step and max. * For ranges, three vals are always returned; min, step and max.
* For lists, all the possible values are enumerated. * For lists, all the possible values are enumerated.
* *
* Note raw available values from iio channels are in adc counts and * Note, if standard units are required, raw available values from iio
* hence scale will need to be applied if standard units are required. * channels need the offset (default 0) and scale (default 1) to be applied
* as (raw + offset) * scale.
*/ */
int iio_read_avail_channel_raw(struct iio_channel *chan, int iio_read_avail_channel_raw(struct iio_channel *chan,
const int **vals, int *length); const int **vals, int *length);
......
...@@ -133,6 +133,70 @@ ...@@ -133,6 +133,70 @@
*/ */
#define max_t(type, x, y) __careful_cmp((type)(x), (type)(y), >) #define max_t(type, x, y) __careful_cmp((type)(x), (type)(y), >)
/*
* Remove a const qualifier from integer types
* _Generic(foo, type-name: association, ..., default: association) performs a
* comparison against the foo type (not the qualified type).
* Do not use the const keyword in the type-name as it will not match the
* unqualified type of foo.
*/
#define __unconst_integer_type_cases(type) \
unsigned type: (unsigned type)0, \
signed type: (signed type)0
#define __unconst_integer_typeof(x) typeof( \
_Generic((x), \
char: (char)0, \
__unconst_integer_type_cases(char), \
__unconst_integer_type_cases(short), \
__unconst_integer_type_cases(int), \
__unconst_integer_type_cases(long), \
__unconst_integer_type_cases(long long), \
default: (x)))
/*
* Do not check the array parameter using __must_be_array().
* In the following legit use-case where the "array" passed is a simple pointer,
* __must_be_array() will return a failure.
* --- 8< ---
* int *buff
* ...
* min = min_array(buff, nb_items);
* --- 8< ---
*
* The first typeof(&(array)[0]) is needed in order to support arrays of both
* 'int *buff' and 'int buff[N]' types.
*
* The array can be an array of const items.
* typeof() keeps the const qualifier. Use __unconst_integer_typeof() in order
* to discard the const qualifier for the __element variable.
*/
#define __minmax_array(op, array, len) ({ \
typeof(&(array)[0]) __array = (array); \
typeof(len) __len = (len); \
__unconst_integer_typeof(__array[0]) __element = __array[--__len]; \
while (__len--) \
__element = op(__element, __array[__len]); \
__element; })
/**
* min_array - return minimum of values present in an array
* @array: array
* @len: array length
*
* Note that @len must not be zero (empty array).
*/
#define min_array(array, len) __minmax_array(min, array, len)
/**
* max_array - return maximum of values present in an array
* @array: array
* @len: array length
*
* Note that @len must not be zero (empty array).
*/
#define max_array(array, len) __minmax_array(max, array, len)
/** /**
* clamp_t - return a value clamped to a given range using a given type * clamp_t - return a value clamped to a given range using a given type
* @type: the type of variable to use * @type: the type of variable to use
......
This diff is collapsed.
...@@ -53,6 +53,7 @@ config SND_SOC_ALL_CODECS ...@@ -53,6 +53,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_AK5558 imply SND_SOC_AK5558
imply SND_SOC_ALC5623 imply SND_SOC_ALC5623
imply SND_SOC_ALC5632 imply SND_SOC_ALC5632
imply SND_SOC_AUDIO_IIO_AUX
imply SND_SOC_AW8738 imply SND_SOC_AW8738
imply SND_SOC_AW88395 imply SND_SOC_AW88395
imply SND_SOC_BT_SCO imply SND_SOC_BT_SCO
...@@ -614,6 +615,17 @@ config SND_SOC_ALC5632 ...@@ -614,6 +615,17 @@ config SND_SOC_ALC5632
tristate tristate
depends on I2C depends on I2C
config SND_SOC_AUDIO_IIO_AUX
tristate "Audio IIO Auxiliary device"
depends on IIO
help
Enable support for Industrial I/O devices as audio auxiliary devices.
This allows to have an IIO device present in the audio path and
controlled using mixer controls.
To compile this driver as a module, choose M here: the module
will be called snd-soc-audio-iio-aux.
config SND_SOC_AW8738 config SND_SOC_AW8738
tristate "Awinic AW8738 Audio Amplifier" tristate "Awinic AW8738 Audio Amplifier"
select GPIOLIB select GPIOLIB
......
...@@ -45,6 +45,7 @@ snd-soc-ak4671-objs := ak4671.o ...@@ -45,6 +45,7 @@ snd-soc-ak4671-objs := ak4671.o
snd-soc-ak5386-objs := ak5386.o snd-soc-ak5386-objs := ak5386.o
snd-soc-ak5558-objs := ak5558.o snd-soc-ak5558-objs := ak5558.o
snd-soc-arizona-objs := arizona.o arizona-jack.o snd-soc-arizona-objs := arizona.o arizona-jack.o
snd-soc-audio-iio-aux-objs := audio-iio-aux.o
snd-soc-aw8738-objs := aw8738.o snd-soc-aw8738-objs := aw8738.o
snd-soc-aw88395-lib-objs := aw88395/aw88395_lib.o snd-soc-aw88395-lib-objs := aw88395/aw88395_lib.o
snd-soc-aw88395-objs := aw88395/aw88395.o \ snd-soc-aw88395-objs := aw88395/aw88395.o \
...@@ -428,6 +429,7 @@ obj-$(CONFIG_SND_SOC_AK5558) += snd-soc-ak5558.o ...@@ -428,6 +429,7 @@ obj-$(CONFIG_SND_SOC_AK5558) += snd-soc-ak5558.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o
obj-$(CONFIG_SND_SOC_AUDIO_IIO_AUX) += snd-soc-audio-iio-aux.o
obj-$(CONFIG_SND_SOC_AW8738) += snd-soc-aw8738.o obj-$(CONFIG_SND_SOC_AW8738) += snd-soc-aw8738.o
obj-$(CONFIG_SND_SOC_AW88395_LIB) += snd-soc-aw88395-lib.o obj-$(CONFIG_SND_SOC_AW88395_LIB) += snd-soc-aw88395-lib.o
obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o
......
// SPDX-License-Identifier: GPL-2.0-only
//
// ALSA SoC glue to use IIO devices as audio components
//
// Copyright 2023 CS GROUP France
//
// Author: Herve Codina <herve.codina@bootlin.com>
#include <linux/iio/consumer.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string_helpers.h>
#include <sound/soc.h>
#include <sound/tlv.h>
struct audio_iio_aux_chan {
struct iio_channel *iio_chan;
const char *name;
int max;
int min;
bool is_invert_range;
};
struct audio_iio_aux {
struct device *dev;
struct audio_iio_aux_chan *chans;
unsigned int num_chans;
};
static int audio_iio_aux_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct audio_iio_aux_chan *chan = (struct audio_iio_aux_chan *)kcontrol->private_value;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = chan->max - chan->min;
uinfo->type = (uinfo->value.integer.max == 1) ?
SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
return 0;
}
static int audio_iio_aux_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct audio_iio_aux_chan *chan = (struct audio_iio_aux_chan *)kcontrol->private_value;
int max = chan->max;
int min = chan->min;
bool invert_range = chan->is_invert_range;
int ret;
int val;
ret = iio_read_channel_raw(chan->iio_chan, &val);
if (ret < 0)
return ret;
ucontrol->value.integer.value[0] = val - min;
if (invert_range)
ucontrol->value.integer.value[0] = max - ucontrol->value.integer.value[0];
return 0;
}
static int audio_iio_aux_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct audio_iio_aux_chan *chan = (struct audio_iio_aux_chan *)kcontrol->private_value;
int max = chan->max;
int min = chan->min;
bool invert_range = chan->is_invert_range;
int val;
int ret;
int tmp;
val = ucontrol->value.integer.value[0];
if (val < 0)
return -EINVAL;
if (val > max - min)
return -EINVAL;
val = val + min;
if (invert_range)
val = max - val;
ret = iio_read_channel_raw(chan->iio_chan, &tmp);
if (ret < 0)
return ret;
if (tmp == val)
return 0;
ret = iio_write_channel_raw(chan->iio_chan, val);
if (ret)
return ret;
return 1; /* The value changed */
}
static int audio_iio_aux_add_controls(struct snd_soc_component *component,
struct audio_iio_aux_chan *chan)
{
struct snd_kcontrol_new control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = chan->name,
.info = audio_iio_aux_info_volsw,
.get = audio_iio_aux_get_volsw,
.put = audio_iio_aux_put_volsw,
.private_value = (unsigned long)chan,
};
return snd_soc_add_component_controls(component, &control, 1);
}
/*
* These data could be on stack but they are pretty big.
* As ASoC internally copy them and protect them against concurrent accesses
* (snd_soc_bind_card() protects using client_mutex), keep them in the global
* data area.
*/
static struct snd_soc_dapm_widget widgets[3];
static struct snd_soc_dapm_route routes[2];
/* Be sure sizes are correct (need 3 widgets and 2 routes) */
static_assert(ARRAY_SIZE(widgets) >= 3, "3 widgets are needed");
static_assert(ARRAY_SIZE(routes) >= 2, "2 routes are needed");
static int audio_iio_aux_add_dapms(struct snd_soc_component *component,
struct audio_iio_aux_chan *chan)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
char *output_name;
char *input_name;
char *pga_name;
int ret;
input_name = kasprintf(GFP_KERNEL, "%s IN", chan->name);
if (!input_name)
return -ENOMEM;
output_name = kasprintf(GFP_KERNEL, "%s OUT", chan->name);
if (!output_name) {
ret = -ENOMEM;
goto out_free_input_name;
}
pga_name = kasprintf(GFP_KERNEL, "%s PGA", chan->name);
if (!pga_name) {
ret = -ENOMEM;
goto out_free_output_name;
}
widgets[0] = SND_SOC_DAPM_INPUT(input_name);
widgets[1] = SND_SOC_DAPM_OUTPUT(output_name);
widgets[2] = SND_SOC_DAPM_PGA(pga_name, SND_SOC_NOPM, 0, 0, NULL, 0);
ret = snd_soc_dapm_new_controls(dapm, widgets, 3);
if (ret)
goto out_free_pga_name;
routes[0].sink = pga_name;
routes[0].control = NULL;
routes[0].source = input_name;
routes[1].sink = output_name;
routes[1].control = NULL;
routes[1].source = pga_name;
ret = snd_soc_dapm_add_routes(dapm, routes, 2);
/* Allocated names are no more needed (duplicated in ASoC internals) */
out_free_pga_name:
kfree(pga_name);
out_free_output_name:
kfree(output_name);
out_free_input_name:
kfree(input_name);
return ret;
}
static int audio_iio_aux_component_probe(struct snd_soc_component *component)
{
struct audio_iio_aux *iio_aux = snd_soc_component_get_drvdata(component);
struct audio_iio_aux_chan *chan;
int ret;
int i;
for (i = 0; i < iio_aux->num_chans; i++) {
chan = iio_aux->chans + i;
ret = iio_read_max_channel_raw(chan->iio_chan, &chan->max);
if (ret)
return dev_err_probe(component->dev, ret,
"chan[%d] %s: Cannot get max raw value\n",
i, chan->name);
ret = iio_read_min_channel_raw(chan->iio_chan, &chan->min);
if (ret)
return dev_err_probe(component->dev, ret,
"chan[%d] %s: Cannot get min raw value\n",
i, chan->name);
if (chan->min > chan->max) {
/*
* This should never happen but to avoid any check
* later, just swap values here to ensure that the
* minimum value is lower than the maximum value.
*/
dev_dbg(component->dev, "chan[%d] %s: Swap min and max\n",
i, chan->name);
swap(chan->min, chan->max);
}
/* Set initial value */
ret = iio_write_channel_raw(chan->iio_chan,
chan->is_invert_range ? chan->max : chan->min);
if (ret)
return dev_err_probe(component->dev, ret,
"chan[%d] %s: Cannot set initial value\n",
i, chan->name);
ret = audio_iio_aux_add_controls(component, chan);
if (ret)
return ret;
ret = audio_iio_aux_add_dapms(component, chan);
if (ret)
return ret;
dev_dbg(component->dev, "chan[%d]: Added %s (min=%d, max=%d, invert=%s)\n",
i, chan->name, chan->min, chan->max,
str_on_off(chan->is_invert_range));
}
return 0;
}
static const struct snd_soc_component_driver audio_iio_aux_component_driver = {
.probe = audio_iio_aux_component_probe,
};
static int audio_iio_aux_probe(struct platform_device *pdev)
{
struct audio_iio_aux_chan *iio_aux_chan;
struct device *dev = &pdev->dev;
struct audio_iio_aux *iio_aux;
const char **names;
u32 *invert_ranges;
int count;
int ret;
int i;
iio_aux = devm_kzalloc(dev, sizeof(*iio_aux), GFP_KERNEL);
if (!iio_aux)
return -ENOMEM;
iio_aux->dev = dev;
count = device_property_string_array_count(dev, "io-channel-names");
if (count < 0)
return dev_err_probe(dev, count, "failed to count io-channel-names\n");
iio_aux->num_chans = count;
iio_aux->chans = devm_kmalloc_array(dev, iio_aux->num_chans,
sizeof(*iio_aux->chans), GFP_KERNEL);
if (!iio_aux->chans)
return -ENOMEM;
names = kcalloc(iio_aux->num_chans, sizeof(*names), GFP_KERNEL);
if (!names)
return -ENOMEM;
invert_ranges = kcalloc(iio_aux->num_chans, sizeof(*invert_ranges), GFP_KERNEL);
if (!invert_ranges) {
ret = -ENOMEM;
goto out_free_names;
}
ret = device_property_read_string_array(dev, "io-channel-names",
names, iio_aux->num_chans);
if (ret < 0) {
dev_err_probe(dev, ret, "failed to read io-channel-names\n");
goto out_free_invert_ranges;
}
/*
* snd-control-invert-range is optional and can contain fewer items
* than the number of channels. Unset values default to 0.
*/
count = device_property_count_u32(dev, "snd-control-invert-range");
if (count > 0) {
count = min_t(unsigned int, count, iio_aux->num_chans);
ret = device_property_read_u32_array(dev, "snd-control-invert-range",
invert_ranges, count);
if (ret < 0) {
dev_err_probe(dev, ret, "failed to read snd-control-invert-range\n");
goto out_free_invert_ranges;
}
}
for (i = 0; i < iio_aux->num_chans; i++) {
iio_aux_chan = iio_aux->chans + i;
iio_aux_chan->name = names[i];
iio_aux_chan->is_invert_range = invert_ranges[i];
iio_aux_chan->iio_chan = devm_iio_channel_get(dev, iio_aux_chan->name);
if (IS_ERR(iio_aux_chan->iio_chan)) {
ret = PTR_ERR(iio_aux_chan->iio_chan);
dev_err_probe(dev, ret, "get IIO channel '%s' failed\n",
iio_aux_chan->name);
goto out_free_invert_ranges;
}
}
platform_set_drvdata(pdev, iio_aux);
ret = devm_snd_soc_register_component(dev, &audio_iio_aux_component_driver,
NULL, 0);
out_free_invert_ranges:
kfree(invert_ranges);
out_free_names:
kfree(names);
return ret;
}
static const struct of_device_id audio_iio_aux_ids[] = {
{ .compatible = "audio-iio-aux" },
{ }
};
MODULE_DEVICE_TABLE(of, audio_iio_aux_ids);
static struct platform_driver audio_iio_aux_driver = {
.driver = {
.name = "audio-iio-aux",
.of_match_table = audio_iio_aux_ids,
},
.probe = audio_iio_aux_probe,
};
module_platform_driver(audio_iio_aux_driver);
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
MODULE_DESCRIPTION("IIO ALSA SoC aux driver");
MODULE_LICENSE("GPL");
...@@ -346,6 +346,7 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv, ...@@ -346,6 +346,7 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv,
struct device *dev = simple_priv_to_dev(priv); struct device *dev = simple_priv_to_dev(priv);
struct device_node *top = dev->of_node; struct device_node *top = dev->of_node;
struct device_node *node; struct device_node *node;
struct device_node *add_devs;
uintptr_t dpcm_selectable = (uintptr_t)of_device_get_match_data(dev); uintptr_t dpcm_selectable = (uintptr_t)of_device_get_match_data(dev);
bool is_top = 0; bool is_top = 0;
int ret = 0; int ret = 0;
...@@ -357,6 +358,8 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv, ...@@ -357,6 +358,8 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv,
is_top = 1; is_top = 1;
} }
add_devs = of_get_child_by_name(top, PREFIX "additional-devs");
/* loop for all dai-link */ /* loop for all dai-link */
do { do {
struct asoc_simple_data adata; struct asoc_simple_data adata;
...@@ -365,6 +368,12 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv, ...@@ -365,6 +368,12 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv,
struct device_node *np; struct device_node *np;
int num = of_get_child_count(node); int num = of_get_child_count(node);
/* Skip additional-devs node */
if (node == add_devs) {
node = of_get_next_child(top, node);
continue;
}
/* get codec */ /* get codec */
codec = of_get_child_by_name(node, is_top ? codec = of_get_child_by_name(node, is_top ?
PREFIX "codec" : "codec"); PREFIX "codec" : "codec");
...@@ -378,12 +387,15 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv, ...@@ -378,12 +387,15 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv,
/* get convert-xxx property */ /* get convert-xxx property */
memset(&adata, 0, sizeof(adata)); memset(&adata, 0, sizeof(adata));
for_each_child_of_node(node, np) for_each_child_of_node(node, np) {
if (np == add_devs)
continue;
simple_parse_convert(dev, np, &adata); simple_parse_convert(dev, np, &adata);
}
/* loop for all CPU/Codec node */ /* loop for all CPU/Codec node */
for_each_child_of_node(node, np) { for_each_child_of_node(node, np) {
if (plat == np) if (plat == np || add_devs == np)
continue; continue;
/* /*
* It is DPCM * It is DPCM
...@@ -426,6 +438,7 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv, ...@@ -426,6 +438,7 @@ static int __simple_for_each_link(struct asoc_simple_priv *priv,
} while (!is_top && node); } while (!is_top && node);
error: error:
of_node_put(add_devs);
of_node_put(node); of_node_put(node);
return ret; return ret;
} }
...@@ -463,6 +476,31 @@ static int simple_for_each_link(struct asoc_simple_priv *priv, ...@@ -463,6 +476,31 @@ static int simple_for_each_link(struct asoc_simple_priv *priv,
return ret; return ret;
} }
static void simple_depopulate_aux(void *data)
{
struct asoc_simple_priv *priv = data;
of_platform_depopulate(simple_priv_to_dev(priv));
}
static int simple_populate_aux(struct asoc_simple_priv *priv)
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *node;
int ret;
node = of_get_child_by_name(dev->of_node, PREFIX "additional-devs");
if (!node)
return 0;
ret = of_platform_populate(node, NULL, NULL, dev);
of_node_put(node);
if (ret)
return ret;
return devm_add_action_or_reset(dev, simple_depopulate_aux, priv);
}
static int simple_parse_of(struct asoc_simple_priv *priv, struct link_info *li) static int simple_parse_of(struct asoc_simple_priv *priv, struct link_info *li)
{ {
struct snd_soc_card *card = simple_priv_to_card(priv); struct snd_soc_card *card = simple_priv_to_card(priv);
...@@ -492,6 +530,10 @@ static int simple_parse_of(struct asoc_simple_priv *priv, struct link_info *li) ...@@ -492,6 +530,10 @@ static int simple_parse_of(struct asoc_simple_priv *priv, struct link_info *li)
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = simple_populate_aux(priv);
if (ret < 0)
return ret;
ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs"); ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs");
return ret; return ret;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment