I2S audio: Question for Broadcom devs


5 posts
by Conner Labs » Mon Feb 04, 2013 3:42 pm
So, there are a few of us out here, trying to get high-quality audio out of the R-pi, and by and large, failing. Let's review our options.

The analog output jack: 12 bits on a good day with a following wind.

HDMI: Excellent if you have a HDMI TV with digital audio output, but it's not always possible to include one of those in your project.

USB DAC: Can be a bit of a gamble due to Pi's slightly flakey USB. I got good results with a USB2.0 DAC, but I had to hunt high and low for one, and tweak the system carefully. Even when everything else is set up perfectly, a USB DAC may not maintain lip sync in XBMC.

PCM I2S: The Rev B Pi brought I2S digital audio out to the GPIO connector. This could easily be converted to SPDIF ("coax" or "optical" for you home theatre fans) or high quality analog audio using an addon board. That is, if it worked. There have been brave attempts to write a driver for it, but so far they have foundered on getting the DMA to work.

I want to suggest an alternative. As I understand it, the ARM core isn't too good at handling low latency interrupts, they are better serviced by the GPU. I also understand that the GPU already handles the onboard audio: it takes a message queue of audio data from the ARM, and shovels it out of the HDMI and analog ports.

Can I humbly beg whoever supports the GPU firmware, to consider firing a copy of the audio data out of the I2S port too? Or at least to think about it long enough to explain why it can't/won't be done.

My own personal use case is that I want to run RaspBMC with video on an old surplus DVI monitor, and high-quality audio through my stereo. As far as I understand it, this is not possible with what we have just now.
Posts: 26
Joined: Fri Jan 11, 2013 2:45 pm
by paulie » Mon Feb 04, 2013 9:47 pm
I'd be very happy to purchase an audio I/O that used the PCM on the P5 connector.
I need good quality audio input and output for encoding/decoding APRS packet radio.
Anyone anywhere working on this?
Posts: 182
Joined: Thu Jan 19, 2012 6:51 pm
by joan » Mon Feb 04, 2013 11:30 pm
What problems are people having with DMA?

I've only scanned the posts but most solutions seemed to be interrupt based.
User avatar
Posts: 6391
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Conner Labs » Tue Feb 05, 2013 10:31 am
This thread
viewtopic.php?f=44&t=8496&start=175

gives a summary of the attempts to get I2S working. It is working, but using programmed I/O. That means you have only 16 samples of leeway in responding to interrupts. The Pi can't reliably make this interrupt latency, so it stutters under CPU load.

If I understand right, the options for fixing this are:
-Use DMA to fill the I2S FIFO from a larger buffer (or rather double buffers) and only raise an interrupt to the audio driver when one of these needs filling. This is how audio is normally done, and it allows much higher interrupt latency. Assuming of course that the DMA engine has higher priority than regular interrupts.

-Use the GPU instead of the ARM core to fill the I2S FIFO.

If you wanted audio input, the former would probably be the way to go. I expect the audio pipeline into the GPU is one way only.
Posts: 26
Joined: Fri Jan 11, 2013 2:45 pm
by joan » Tue Feb 05, 2013 10:47 am
Richard Hirst, the servoblaster/panalyzer etc. chap, nick rgh, has posted DMA code at https://github.com/richardghirst/PiBits/blob/master/ServoBlaster/servod.c

Richard has example code for kernel (PWM) and userland (PWM and PCM). The code will be clear to anyone working in this area.

My userland code for a 5 micro cycle (we are primarily intrested in using PWM/PCM for timing DMA transfers).

Code: Select all
static void initPCM(void)
{
   int i;

   DBG(DBG_STARTUP, "\n");

   clkReg[CLK_PCMCTL] = CLK_PASSWD | CLK_KILL;

   usleep(10);

   clkReg[CLK_PCMDIV] = CLK_PASSWD | CLK_DIVI(12);

   usleep(10);

   clkReg[CLK_PCMCTL] = CLK_PASSWD |  CLK_SRC(CLK_SRC_OSC);

   usleep(10);

   clkReg[CLK_PCMCTL] |= (CLK_PASSWD | CLK_ENAB);

   usleep(10);

   /* disable PCM so we can modify the regs */

   pcmReg[PCM_CS] = 0;

   usleep(10);

   pcmReg[PCM_FIFO]   = 0;
   pcmReg[PCM_MODE]   = 0;
   pcmReg[PCM_RXC]    = 0;
   pcmReg[PCM_TXC]    = 0;
   pcmReg[PCM_DREQ]   = 0;
   pcmReg[PCM_INTEN]  = 0;
   pcmReg[PCM_INTSTC] = 0;
   pcmReg[PCM_GRAY]   = 0;

   usleep(10);
 
   pcmReg[PCM_TXC] = PCM_TXC_CH1EN; /* enable channel 1 for 8 bit width */

   pcmReg[PCM_MODE] = PCM_MODE_FLEN(7); /* 8 bit frame */

   pcmReg[PCM_CS] |= PCM_CS_STBY; /* clear standby */

   usleep(10);

   pcmReg[PCM_CS] |= PCM_CS_TXCLR; /* clear TX FIFO */

   pcmReg[PCM_CS] |= PCM_CS_DMAEN; /* enable DREQ */

   pcmReg[PCM_DREQ] = PCM_DREQ_TX_PANIC(16) | PCM_DREQ_TX_REQ_L(30);

   pcmReg[PCM_INTSTC] = 0b1111; /* clear status bits */

   /* enable PCM */

   pcmReg[PCM_CS] |= PCM_CS_EN ;

   /* enable tx */

   pcmReg[PCM_CS] |= PCM_CS_TXON;

   dmaPage[0].periphData = 0x0F;

   usleep(10);

   for(i=0; i<9;i++)
   {
         DBG(DBG_STARTUP, "PCM %d: 0x%08x\n", i, pcmReg[i]);
   }
}
User avatar
Posts: 6391
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK