Linux DTS: I2S Audio Codec

Working on some custom embedded Linux hardware I designed, I found myself having a lot of troubles getting an on-board I2S audio codec to work. The issues I encountered were related to the DTS (device tree source) which is used to describe the hardware. After having spent several hours reading the kernel documentation, device tree bindings and existing DTS I decided to write this small blog post as adding a sound device for custom hardware to a DTS does not seem to be a particularly common or popular activity.

Hardware

My custom board is based on a SAMA5D MPU and an SSM2603 audio codec. The MPU interfaces the codec via I2C (control interface) and I2S (digital audio interface). As I’ll only use the codec for recording audio I setup the codec to be the I2S master.

The SSM2603 audio codec is already supported by mainline Linux via the compatible SSM2602 driver.

DTS

What follows are the relevant sections of getting the on-board codec to show up as a usable sound device in the Linux kernel device tree. While the snippets below are certainly specific to my particular hardware, the overall mechanics remain the same. Adjust the necessary peripherals, addresses and so on to fit your scenario.

The first step is to define the I2C device and attaching the correct driver:

ssm2603: ssm2603@1a {
   #sound-dai-cells = <0>;
   compatible = "adi,ssm2603";
   reg = <0x1a>;
};

Note that this is a sub-section of the I2C peripheral section.

Next, we need to define the I2S interface:

i2s0: i2s@f8050000 {
    #sound-dai-cells = <0>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2s0_default>;
    status = "okay";
};

Now we get to the part which was the least “clear” to me: Defining a sound device which the Linux sound subsystem can actually interact with (eg. ALSA). What I figured out relatively quickly is that the DTS needs to have a sound section. What wasn’t immediately clear to me is what goes into the compatible property. Most reference DTS I was looking at had a particular driver for their hardware. In my case, I just had a regular I2S interface with no existing DTS definition for the codec itself. Eventually I came across the simple-audio-card binding which allowed me to get the sound device up & running.

The corresponding DTS section looks like this:

sound {
    compatible = "simple-audio-card";
    simple-audio-card,name = "SSM2603";

    simple-audio-card,dai-link@0 {
        format = "i2s";
        cpu {
            sound-dai = <&i2s0>;
        };
        codec {
            sound-dai = <&ssm2603>;
            bitclock-master;
            frame-master;
            system-clock-frequency = <12288000>;
        };
    };
};

Here, we’re creating a sound device which is compatible to the simple-audio-card binding. We give the device a reasonable name ("SSM2603") and specify that we’re using a DAI (Digital Audio Interface) link between the CPU and the codec. The cpu section only requires a handle to the previously declared I2S device. The codec section requires a handle to the previously declared ssm2603 device and we configure the codec to be both the bitclock master as well as the frame master. The codec’s system-clock-frequency corresponds to the on-board crystal frequency (12.28 MHz in my case).

The simple-audio-card documentation was actually reasonably helpful and provides examples for different scenarios.

Kernel

Mainline Linux already supported the SAMA5D SoC’s I2S peripheral as well as the audio codec itself. Therefore, I only needed to tweak the corresponding knobs in the kernel to end up with a working system:

CONFIG_SND_ATMEL_SOC_I2S=y
CONFIG_SND_SOC=y
CONFIG_SND_SOC_SSM2602=y
CONFIG_SND_SOC_SSM2602_I2C=y
CONFIG_SND_SIMPLE_CARD=y

Conclusion

If everything was configured correctly, an audio device named SSM2603 should show up and is ready to be used.

I’m still pretty much in the dark as to how to determine the correct values for #sound-dai-cells. As much as I dislike it, ‘Trial & Error’ was the recipe to success in this case.

comments powered by Disqus