Logo

Linux iMX23 audio loop (or using the iMX23 as an amplifier)

Audio BM_AUDIOOUT_TEST_ADCTODAC_LOOP Drivers Embedded Freescale Imx23 Linux Linux Drivers Linux Embedded Loopback Teaching Material

4 minutes

As usual when working with embedded systems every component saved seems like gold to the HW guys (no matter how much software hassles or future limitations this may cause!) and in this context lately I’ve been working on the idea to use the Freescale iMX23 processor, that was anyway present in the project for other needs, as an audio amplifier to pilot a small speaker with no special volume requirements.
Basically the output of a GSM module (audio out) was connected to the MIC input of the iMX23 and then the output to the speaker, just with little strictly needed passive electronics in between. As by the datasheet the processor should be able to pull out till 1.9W at 4Ω and there were some interesting registers to play with, even if not documented so very clearly nor seemed to be used anyhow in the audio driver provided.
The software base used was the LTIB based BSP provided by Freescale, including their kernel patches for a 2.6.31 kernel.

Software wise the first approach was quite crude but was actually working for the first test to understand where we were going to: piping arecord output to aplay input would actually do the trick. There was a quite noticeable delay but it did the trick. Playing with both arecord and aplay parameters (mainly the sampling rate, given also the input quality was anyway pretty low) made the situation better, but still far from optimal. At least this gave quite immediately an idea that the hardware was properly connected and, after playing with various settings of amixer and on the GSM module generating the signal, of the limits in the audio output.

The second approach was still on user-land: write a single C program that will do the operation using directly the ALSA asound library, simply reading on the capturing handle and writing to the playing handle. Better response now but quite some resources used and when the CPU was busy doing other stuff some glitches can occour.

The last and most interesting part now was trying to take it all out of the operating system handling, as some studies were suggesting by some little documented ADCTODAC_LOOP in the “don’t touch it unless you know what you are doing” HW_AUDIOOUT_TEST register. After quite some trying and debugging (mostly not to leave the audio part in a state that makes it furtherly unusable with standard arecord/aplay tools) finally an adeguate procedure was found for enabling this mode (and disabling it) in an independant way, that is without any other tool to be running (of course you should set the mixer values beforehand, but that is not a tool running and emptying the audio pipes):

 

writel(BM_AUDIOOUT_TEST_ADCTODAC_LOOP, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_SET);
writel(BM_AUDIOOUT_HPVOL_MUTE, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR);
writel(BM_AUDIOOUT_PWRDN_HEADPHONE, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
writel(BM_AUDIOOUT_PWRDN_ADC, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
writel(BM_AUDIOOUT_PWRDN_RIGHT_ADC, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
writel(BM_AUDIOOUT_PWRDN_DAC, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, 
          REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR);
writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,   REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);

And to restore to normal mode:

writel(BM_AUDIOOUT_TEST_ADCTODAC_LOOP, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_CLR);
writel(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
writel(BM_AUDIOOUT_PWRDN_ADC, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, 
          REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);

In my case this code was actually inserted into a stand-alone small driver that would export the activation of this feature through a /dev entry (by writing 0 or 1 to the device, and returning the current state if you read it) so it could be very easily manipulated by shell with a simple echo/cat (I’m not publishing the entire driver since it contains too much code that is strictly related to the specific HW). Of course an even more elegant solution could be to integrate this feature as part of the audio driver itself.

The solution seemed to work fine and this way the system performed very well and the audio loop was immediate and not posing performance risks. Interesting enough with the proposed set of register settings the capturing device (or arecord if you wish) can still be working with no problems so some audio analysis can be done on the amplified stream (for example an analysis to prevent the Larsen effect was done in the prototype since also a MIC was later used for other purposes and of course the feedback is always waiting around the corner).
Of course be aware that you are playing with test registers and test features so your mileage may vary!