audio-iio-aux: when IIO meets audio

Some time ago, one of our customers came to us with a hardware design featuring the Renesas X9250 digital potentiometer in an audio path, as shown in the following diagram. Naturally, our goal was to find the proper way to support this hardware design in the upstream Linux kernel.

Audio path with potentiometer

Since the X9250 is part of an audio path, the first thing that came to mind was the ALSA subsystem of the Linux kernel. However, the Renesas X9250 is a generic digital potentiometer, and audio is only one of its many possible use cases.

The Linux kernel also has a separate subsystem called IIO (Industrial I/O), which provides support for a wide range of devices, including accelerometers, various types of sensors, ADCs, DACs, and potentiometers. Potentiometers are already supported by IIO, as can be seen in the drivers/iio/potentiometer directory.

IIO is not specific to audio, so implementing the Renesas X9250 driver as an IIO driver made more sense than implementing an ALSA-specific driver. This way, the same driver can be reused for non-audio use cases as well.

However, we still needed a way for the potentiometer to be handled as a regular component of an audio path through the ALSA subsystem.

Enter audio-iio-aux

To provide a bridge between the ALSA and IIO worlds, we developed and upstreamed the audio-iio-aux driver.

This driver exposes auxiliary audio devices. In ALSA terminology, these are audio devices that do not provide a Digital Audio Interface (DAI): they do not consume or produce digital audio themselves. Instead, they are typically present in the analog part of an audio path, for example as analog amplifiers or filters.

On one side, audio-iio-aux consumes an IIO device, such as a potentiometer. On the other side, it registers an auxiliary audio device with the ALSA subsystem.

The auxiliary audio device exposes ALSA mixer controls. When these controls are adjusted, audio-iio-aux translates the changes into requests to the underlying IIO device and its driver.

Using audio-iio-aux

Once enabled using CONFIG_SND_SOC_AUDIO_IIO_AUX, the driver can be configured through its Device Tree binding.

As an example is worth a thousand words, let’s look at a complete Device Tree example:

spi: spi@a80 {
	...
	pot_in: x9250@1 {
		compatible = "renesas,x9250u";
		spi-max-frequency = <1000000>;
		reg = <1>;
		vcc-supply = <&reg_5v>;
		avp-supply = <&reg_p5va>;
		avn-supply = <&reg_n5va>;
		spi-cs-high;
		#io-channel-cells = <1>;
	};

	pot_out: x9250@5 {
		compatible = "renesas,x9250u";
		spi-max-frequency = <1000000>;
		reg = <5>;
		vcc-supply = <&reg_5v>;
		avp-supply = <&reg_p5va>;
		avn-supply = <&reg_n5va>;
		spi-cs-high;
		#io-channel-cells = <1>;
	};
};

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>;
	};
};

So, in detail:

  • The Renesas X9250 potentiometers are first described as SPI devices in the Device Tree nodes named x9250@1 and x9250@5. We have two potentiometers on the same SPI bus, each with its own chip select. The first has the Device Tree label pot_in, while the second has the label pot_out. The driver for renesas,x9250u will register an IIO device for each of these nodes.
  • The sound node then describes the whole audio card, using the popular simple-audio-card Device Tree binding. The example uses the usual simple-audio-card,cpu and simple-audio-card,codec nodes to describe the CPU-side and codec-side DAIs.
  • As part of our work on audio-iio-aux, we introduced the new simple-audio-card,additional-devs child node of simple-audio-card. Here, we instantiate two devices, iio-aux-out and iio-aux-in. Using their io-channels properties, these devices reference pot_out and pot_in, respectively, which are the IIO devices described above. audio-iio-aux then instantiates two auxiliary audio devices. These are referenced by the simple-audio-card,aux-devs property and subsequently used in the simple-audio-card,routing audio routing table. The AMP_IN LEFT OUT, AMP_IN RIGHT OUT, AMP_OUT LEFT IN, and AMP_OUT RIGHT IN audio components therefore become available from the audio routing point of view thanks to audio-iio-aux.

With all this Device Tree wiring in place, the potentiometers become part of the audio path and can be controlled using amixer and/or alsamixer.

Conclusion

The audio-iio-aux driver is available upstream. It was contributed in 2023 as part of [PATCH v6 12/13] ASoC: codecs: Add support for the generic IIO auxiliary devices and landed in Linux v6.6.

Bridges between kernel subsystems such as this one allow end users to control their hardware using the subsystem that is most appropriate for each device, without requiring duplicate drivers or subsystem-specific implementations.

In this case, the X9250 can remain a generic IIO device, while audio-iio-aux provides the glue needed to integrate it into an ALSA/ASoC audio card. No additional driver code is required: the integration is entirely described through Device Tree.

This small driver is therefore a good example of how connecting existing kernel subsystems can make hardware support both more reusable and easier to integrate.

Leave a Reply