I2S sound: Anyone got it running? (answer is yes!)


587 posts   Page 4 of 24   1, 2, 3, 4, 5, 6, 7 ... 24
by victor martinez » Wed Oct 24, 2012 7:11 am
victor martinez wrote:I have been following the post, I have already changed the clock to 1.4 KHZ and I'm trying to get acces to

PCM_CLK GPIO28
PCM_FS GPIO29
PCM_DIN GPIO30
PCM_DOUT GPIO31

I have already soldered a wire to the Resistors, but I have lost R3 (I'm not the best with the solder)
There is someone can give me the value of R3 (the one with the red circle) or somewhere where I can find it?

Thanks for your time


I have already found it, it's a 10k resistor. I have found it in the schematic. If anyone more needs it you can find here:

http://www.raspberrypi.org/wp-content/u ... s-R1.0.pdf

Hope to be useful
Posts: 3
Joined: Wed Oct 17, 2012 8:34 am
by ceteras » Wed Oct 24, 2012 7:49 am
@Victor Martinez: you can leave the R3 resistor out, it's not needed for the PCM module.
It's value can be found in the old schematics: 10K.
I don't understand what you mean by "changed the clock to 1.4 KHZ". I know of no such clock that should be set at that value for I2S output.

Edit: I'm glad you've figured it out by yourself.
Posts: 192
Joined: Fri Jan 27, 2012 1:42 pm
Location: Romania
by DogEars » Wed Oct 24, 2012 8:20 pm
1.4112Mhz maybe, not Khz?
Posts: 43
Joined: Sun Jun 17, 2012 1:16 pm
by jeroenst » Thu Oct 25, 2012 5:44 pm
For creating a BMC code from normal data, I created this little program:

Code: Select all
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

unsigned int bittobmc (unsigned int previous_bmc, bool bit)
{
 if (bit == 1)
 {
   if ((previous_bmc == 0) || (previous_bmc == 2))
   {
     return 2;
   }
   else
   {
     return 1;
   }
 }
 else
 {
   if ((previous_bmc == 0) || (previous_bmc == 2))
   {
     return 3;
   }
   else
   {
     return 0;
   }
 }
}
const char *byte_to_binary(unsigned long long x, int lenght)
{
    static char b[64];
    b[0] = '\0';

    unsigned long long z = 1;
    for (z <<= (lenght-1); z > 0; z >>= 1)
    {
        strcat(b, ((x & z) == z) ? "1" : "0");
    }

    return b;
}

unsigned long long datatobmccode (unsigned long long previousbmccode, unsigned long data)
{
    unsigned long long bmccode = 0;
    unsigned int bmcbitcode = previousbmccode & 3;
    unsigned int counter = 0;
      while (counter++ < 32)
      {
        bmccode <<= 2;
        bmcbitcode = bittobmc (bmcbitcode, data & 1);
        bmccode += bmcbitcode;
        printf ("Bit = %d, Data = %d, Bmcbitcod(bin)=%d%d, Bmccode(bin)=%s Bmccode(dec)=%llu\n", counter, data & 0x1, bmcbitcode&1, (bmcbitcode&2)>>1,  byte_to_binary(bmccode, 64), bmccode);
        data >>= 1;
      }

  return bmccode;
}

unsigned long getuserinput (void)
{
  char buffer[20];
  fgets(buffer, sizeof(buffer), stdin);
  return strtoul (buffer,NULL,0);
}
void main (void)
{
  unsigned long long bmccode = 0;
  unsigned long userdata = getuserinput();
  bmccode = datatobmccode (bmccode, userdata);
  printf("DATA(BIN)    = %s\n", byte_to_binary(userdata, 32));
  printf("BMCCODE(BIN) = %s\n", byte_to_binary(bmccode, 64));
  printf("BMCCODE(DEC) = %llu\n", bmccode);
  printf("BMCCODE(HEX) = %llX\n", bmccode);
}


Now only we have to do is input a stream with IEC61937 data and output it to the I2S interface that should be fed into an spdif interface with a resistor network to convert the 3,3 (or 5??) volt of I2S to 1,5 volt SPDIF.
Posts: 2
Joined: Tue Oct 23, 2012 12:48 pm
by philpoole » Mon Oct 29, 2012 5:29 pm
Hi All,

I've been tinkering with the code posted on here, and I can now get audio out of a TDA1541A DAC using my rev 2 Raspberry Pi's I2S lines. It's not great, obviously polling is CPU intensive, and there are occasionallly dropouts, but it sound much better than the analogue out.

I had to fiddle about with the frame and frame sync lengths, and the channel positions. I ended up with a frame of 64, and a frame sync of 32 (so two channels of 32 clocks, of which 16 bits are sensible data - the 1541 seems to work that way - although it does work with shorter frames. Unfortunately, in order to do that, I had to double the clock frequency, which doesn't seem to work well. Needing about 6.805 I used the example posted here and the link, but it seems a bit quick to me.
I calculated 0x5A006CD7, but upping it to around 0x5A007xxx seemed a bit better.

Anyway, thought I'd post my changes...

Code: Select all
//
// output a wav file on PCM_DOUT with about 44.1kHz
// based on source from: http://elinux.org/RPi_Low-level_peripherals

#define BCM2708_PERI_BASE        0x20000000
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
#define I2S_BASE                (BCM2708_PERI_BASE + 0x203000) /* GPIO controller */
#define CLOCK_BASE               (BCM2708_PERI_BASE + 0x101000) /* Clocks */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <inttypes.h>

#include <unistd.h>

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define CS_A     0
#define FIFO_A   1
#define MODE_A   2
#define RXC_A    3
#define TXC_A    4
#define DREQ_A   5
#define INTEN_A  6
#define INTSTC_A 7
#define GRAY     8

int  mem_fd;
char *gpio_mem, *gpio_map;
char *i2s_mem, *i2s_map;
char *clk_mem, *clk_map;


// I/O access
volatile unsigned *gpio;
volatile unsigned *i2s;
volatile unsigned *clk;
const char *i2s_register_name[] = {"CS_A", "FIFO_A", "MODE_A", "RXC_A", "TXC_A", "DREQ_A", "INTEN_A", "INTSTC_A", "GRAY"};

// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0

void setup_io();

int main(int argc, char **argv)
{
    int g,rep;

    setup_io();

    printf("Setting GPIO regs to alt0\n");
    for (g=18; g<=21; g++)
    {
        INP_GPIO(g);
        SET_GPIO_ALT(g,0);
    }
    printf("Setup P5 GPIOs (28-31) to alt2\n");

    for (g=28; g<=31; g++)
    {
        INP_GPIO(g);
        SET_GPIO_ALT(g,2);
    }

    int i;
    printf("Memory dump\n");
    for(i=0; i<10;i++) {
        printf("GPIO memory address=0x%08x: 0x%08x\n", gpio+i, *(gpio+i));
    }

    // for(i=0x26; i<=0x27;i++) {
    //   printf("Clock memory address=0x%08x: 0x%08x\n", clk+i, *(clk+i));
    // }

    printf("Disabling I2S clock\n");
    *(clk+0x26) = 0x5A000000;
    *(clk+0x27) = 0x5A000000;
     
    usleep(10);

    printf("Configure I2S clock\n");
    *(clk+0x26) = 0x5A000001;
    //*(clk+0x27) = 0x5A006CD7; //a calculated guess, hopefully about 6.8, but seems out somewhat (I want 64*44100 = 2822400 Hz)
    *(clk+0x27) = 0x5A0070D7;
    usleep(10);
    printf("Enabling I2S clock\n");
    *(clk+0x26) = 0x5A000011;

    // disable I2S so we can modify the regs
    printf("Disable I2S\n");
    *(i2s+CS_A) &= ~(1<<24);
    usleep(100);
    *(i2s+CS_A) = 0;
    usleep(100);

    printf("Clearing FIFOs\n");
    *(i2s+CS_A) |= 1<<3 | 1<<4 | 11<5; // clear TX FIFO
    usleep(10);

    // set register settings
    // --> enable Channel1 with 32bit width
    // For 16 bit I2S, Channel width should be 16, possible positions should be 1 and 17?
    //(e.g. look for 1<<29 and 17<<4
    printf("Setting TX channel settings\n");
    *(i2s+TXC_A) = 0<<31 | 1<<30 | 2 << 20  | 8<<16   |   0<<15 | 1<<14 | 34<<4 | 8 ;

    //Set frame length and frame sync length (32 and 16), and set FTXP=1 so I can inject 2 channels into a single 32 bit word
    *(i2s+MODE_A) = 1 << 24 | 63<<10 | 32;

    // --> disable STBY
    printf("disabling standby\n");
    *(i2s+CS_A) |= 1<<25;
    usleep(50);

    // enable I2S
    *(i2s+CS_A) |= 0x01;

    // enable transmission
    *(i2s+CS_A) |= 0x04;
   
    // --> ENABLE SYNC bit
    printf("setting sync bit high\n");
    *(i2s+CS_A) |= 1<<24;

    if (*(i2s+CS_A) & 1<<24) {
        printf("SYNC bit high, strange.\n");
    } else {
        printf("SYNC bit low, as expected.\n");
    }

    usleep(1);
     
    if (*(i2s+CS_A) & 1<<24) {
        printf("SYNC bit high, as expected.\n");
    } else {
        printf("SYNC bit low, strange.\n");
    }

    printf("Memory dump\n");
    for(i=0; i<9;i++) {
        printf("I2S memory address=0x%08x: 0x%08x    %s\n", i2s+i, *(i2s+i), i2s_register_name[i]);
    }

    // fill FIFO in while loop
    int count=0;
   
    printf("going into loop\n");
    //open a wav file (I've used halcyon, but any Orbital track should be fine :)
    int halcyon_fd = open("./halcyon.wav", O_RDONLY);
    unsigned int data;
    char header[80];
    int bytes_read;
    //Read in enough data from file to ensure we've skipped the header   
    read(halcyon_fd, header, 80);
    header[79]=0;
    printf("header: %s\n", header);
    for(count - 0; count < 80; count++)
        printf(" %d %c %2x ", count, header[count], header[count]);
    printf("\n");
    count = 0;
    while (1) {
        bytes_read = read(halcyon_fd,(void*) &data, 4);
        if(bytes_read==0)
           break;
        while (!((*i2s) & (1<<19))) {usleep(1);}//wait for space in FIFO
        count++;         
        *(i2s+FIFO_A) = data; //FTXP = 1
        if ((++count % 10000)==0)
            printf("Filling FIFO, count=%i data %x\n", count, data);
       
    }

    return 0;

} // main


//
// Set up a memory regions to access GPIO
//
void setup_io()
{
    printf("setup io\n");

    /* open /dev/mem */
    if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
        printf("can't open /dev/mem \n");
        exit (-1);s
    }

    /* mmap GPIO */

    // Allocate MAP block
    if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
        printf("allocation error \n");
        exit (-1);
    }

    // Allocate MAP block
    if ((i2s_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
        printf("allocation error \n");
        exit (-1);
    }

    // Allocate MAP block
    if ((clk_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
        printf("allocation error \n");
        exit (-1);
    }

    // Make sure pointer is on 4K boundary
    if ((unsigned long)gpio_mem % PAGE_SIZE)
        gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);
    // Make sure pointer is on 4K boundary
    if ((unsigned long)i2s_mem % PAGE_SIZE)
        i2s_mem += PAGE_SIZE - ((unsigned long)i2s_mem % PAGE_SIZE);
    // Make sure pointer is on 4K boundary
    if ((unsigned long)clk_mem % PAGE_SIZE)
        clk_mem += PAGE_SIZE - ((unsigned long)clk_mem % PAGE_SIZE);

    // Now map it
    gpio_map = (unsigned char *)mmap(
                               (caddr_t)gpio_mem,
                                BLOCK_SIZE,
                                PROT_READ|PROT_WRITE,
                                MAP_SHARED|MAP_FIXED,
                                mem_fd,
                                GPIO_BASE);

    // Now map it
    i2s_map = (unsigned char *)mmap(
                                 (caddr_t)i2s_mem,
                                  BLOCK_SIZE,
                                  PROT_READ|PROT_WRITE,
                                  MAP_SHARED|MAP_FIXED,
                                  mem_fd,
                                  I2S_BASE);

    clk_map = (unsigned char *)mmap(
                                 (caddr_t)clk_mem,
                                  BLOCK_SIZE,
                                  PROT_READ|PROT_WRITE,
                                  MAP_SHARED|MAP_FIXED,
                                  mem_fd,
                                  CLOCK_BASE);

    if ((long)gpio_map < 0) {
        printf("mmap error %d\n", (int)gpio_map);
        exit (-1);
    }
    if ((long)i2s_map < 0) {
        printf("mmap error %d\n", (int)i2s_map);
        exit (-1);
    }
    if ((long)clk_map < 0) {
        printf("mmap error %d\n", (int)clk_map);
        exit (-1);
    }

    // Always use volatile pointer!
    gpio = (volatile unsigned *)gpio_map;
    i2s = (volatile unsigned *)i2s_map;
    clk = (volatile unsigned *)clk_map;


} // setup_io

Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Wed Oct 31, 2012 7:17 pm
You migh want to check the BCM2835-ARM_Periphrals data sheet. Instead of polling the (FIFO) interrupts could be used:
"8.4.2 Operating in Interrupt mode
a) Set the EN bit to enable the PCM block. Set all operational values to define the frame
and channel settings. Assert RXCLR and/or TXCLR wait for 2 PCM clocks to ensure
the FIFOs are reset. The SYNC bit can be used to determine when 2 clocks have
passed. Set RXTHR/TXTHR to determine the FIFO thresholds.
b) Set INTR and/or INTT to enable interrupts.
c) If transmitting, ensure that sufficient sample words have been written to PCMFIFO
before transmission is started. Set TXON and/or RXON to begin operation.
d) When an interrupt occurs, check RXR. If this is set then one or more sample words
are available in PCMFIFO. If TXW is set then one or more sample words can be sent
to PCMFIFO."

Now, problem is how the receive the interrups in user mode. One option is to make a kernel module which installs the IRQ (#55 me thinks) handler and then signals your user mode app/driver whenever the FIFO is becoming empty (you can set the threshold). See http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html
Implement a system call to let the kernel module know your PID and the in IRQ handler signal the user mode program (if signal is allowed kernel, don't know). However, it might be best to use alsa API and implement a proper alsa-driver after all.

It would be obviously best to write a BCM2835-i2s ALSA driver with support to capture and playback.
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by mhelin » Wed Oct 31, 2012 8:16 pm
There is also UIO user space device model (https://www.osadl.org/fileadmin/dam/rtlws/12/Koch.pdf).

For writing alsa driver this might be useful: http://ben-collins.blogspot.fi/2010/04/ ... river.html

Here another example how to receive interrupts (from parallel port device in this case) in user space:
http://www.xenoscope.com/weblog/2008/09 ... -in-linux/
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by philpoole » Wed Oct 31, 2012 10:13 pm
You're right, it's sub-optimal in it's current form. It needs to be in a driver.
I've started knocking up a module build that matches my kernel. I'm not completely sure how to implement an ALSA driver (I've had a brief look at a few websites), but intend to create a driver that can be written to as a file, which should provide a basis to play with DMA and interrupts in kernel space.

I have made a slight improvement in performance. I now read far larger chunks from file, and usleep for longer between writes to the FIFO register. top suggests that the process is now only using about 30% - as opposed to upwards of 95%. However, when some background activity occurs, it can still glitch slightly. Hence the need for a better solution (e.g. DMA and interrupts).

e.g.
Code: Select all
    unsigned int data, f;
#define BUFFER_SIZE 6400
    unsigned int buffer[BUFFER_SIZE];
    char header[80];
    int bytes_read;
    //Read in enough data from file to ensure we've skipped the header   
    read(halcyon_fd, header, 80);
    header[79]=0;
    printf("header: %s\n", header);
    for(count - 0; count < 80; count++)
        printf(" %d %c %2x ", count, header[count], header[count]);
    printf("\n");
    count = 0;
    while (1) {
        bytes_read = read(halcyon_fd,(void*) &buffer[0], 4*BUFFER_SIZE);
        if(bytes_read==0)
           break;
       
        count++;         
        for( f = 0; f<BUFFER_SIZE; f++){while (!((*i2s) & (1<<19))) {usleep(4);}//wait for space in FIFO
            *(i2s+FIFO_A) = buffer[f];} //FTXP = 1
    }
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Thu Nov 01, 2012 9:26 am
There's some discussion on usleep accuracy here:

http://comments.gmane.org/gmane.linux.p ... rnel/12875

"Try to increase HZ (in asm/param.h or asm/arch/param.h) from 100 to 500 or
1000. Scheduling will be faster and usleep will get better precision, at the
expense of more interrupts and overhead." (requires recompiling of kernel though).

You might want to play with the threshold levels (TXTHR) as well. I think if you set it to 01 then when TXW is set you should have (the datasheet descriptions for TXTHR values are useless) more than 32 words space in TX FIFO, so write the 32 words at a go without polling the status between writing the individual words. Then sleep for approximate time of 16 (or 32 if you pack two 16-bit L+R samples to single 32-bit word) / Fs (44100) between the next poll of TXW and writing of 32 words to FIFO. Well, not sure about the timer resolution as 16/44100 = 360 us but usleep has some ~117 usec overhead (?)
(viewtopic.php?t=10479&p=118168, viewtopic.php?f=68&t=8067&p=96770).
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by garym1957 » Fri Nov 02, 2012 3:13 pm
What is I2S?
I know what i2c is...
Posts: 11
Joined: Fri Aug 24, 2012 6:04 am
by ceteras » Fri Nov 02, 2012 3:31 pm
garym1957 wrote:What is I2S?
I know what i2c is...

I2S, also known as Inter-IC Sound, Integrated Interchip Sound, or IIS, is an electrical serial bus interface standard used for connecting digital audio devices together. It is most commonly used to carry PCM information between the CD transport and the DAC in a CD player. The I2S bus separates clock and data signals, resulting in a very low jitter connection.

Source: http://en.wikipedia.org/wiki/I%C2%B2S
Posts: 192
Joined: Fri Jan 27, 2012 1:42 pm
Location: Romania
by philpoole » Sun Nov 04, 2012 9:55 pm
Been tinkering a bit.
Inspired by dummy.c in the Linux source code (basically a skeleton for an ALSA driver), I've knocked up a crude ALSA driver that bit bangs the PCM interface to output I2S. It is worse than the user space code submitted previously - because there is more running in the background (e.g. MPD and GMPC), but it is working.
At the moment, it's just wasting time polling the FIFO register to see if it can write more data.
top tells me that I'm using about 90% CPU, but if I comment out the code that does the actual register polling and writing to FIFO, the CPU usage plummets, unsurprisingly to about 5%. So, obviously bit banging is not the way to go.
Struggling to get interrupts working at the moment, but I feel it should be a step towards a solution.
There's no point posting anything at the moment, as it's not very good in it's current form, and requires module building, but I am thinking about putting it on github.
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Mon Nov 05, 2012 2:28 pm
Excellent and a step to right direction. However, been wondering if the FIFO interrupts are the right solution to the problem after all due to Linux kernel interrupt architecture (though ARM supports also FIQ's which are higher priority interrupts, see http://stackoverflow.com/questions/9739 ... upt-system ). Interrupt handler is not supposed to do things like writing streams of data, instead they just do basic controlling and then schedule the heavier task by using queue_work() or queue_delayed_work() function (see http://www.linuxtopia.org/online_books/ ... x1236.html or
http://www.linuxtopia.org/online_books/ ... x1191.html for an example, or the other kernel module / driver implementations).

So instead we would rather set up two or more (configurable amount) of memory blocks for DMA transfers the addresses of which are configured to Control Blocks which contain also the pointer to next CB. So in addition to that by enabling the DMA interrupts the driver would know which block is done and which is DMA'ed currently so the data can be written to the one block output next. For virtual instruments (synths etc) or realtime audio processing those blocks should be short (32-64 samples), for media player longer maybe.

Anyway, I think ALSA contains all needed interfaces for controlling things like these. If ALSA is not used / implemented in the driver then just a mere kernel module + user space application with support to JACK would do fine.

Btw.
Another good starting point source in addition to dummy.c is aloop-kernel.c

http://alsa-driver.sourcearchive.com/do ... ource.html

See also the resources here:
http://www.alsa-project.org/main/index.php/Minivosc
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by philpoole » Mon Nov 05, 2012 5:12 pm
Yes, I had been playing with scheduling work and tasklets when I was playing with this. I copy_from_user() to a page, and I was hoping to schedule work to write that data to a FIFO. Currently, I've abandoned the queued work for bit banging, and it seems to work just as a starting point (within reason, the issue is polling registers scuppering everything - as expected, wouldn't need to do that in an ISR, it would be triggered by those bits).

For this ISR, I'm not sure there's a lot of merit in queuing work here. It's not an easy decision, because I need to write either 32 or 64 words in a tight loop. Looking at queue_work() in the kernel, I'm guessing it performs more instructions than that (and uses the stack a bit for function calls, etc).
Sort of an irrelevant discussion for now, I need interrupts to trigger first, before I start writing any kind of useful ISR.
Anyway, I agree it's not the ideal result, which would be using DMA and DREQ , but it is very likely a step in the right direction. I wrote a sound blaster driver years ago at uni, and DMA'd to a FIFO from a double buffer. The ISR handled the DMA, and prompted CPU writes to the other, dormant buffer. So I'd probably want to do something like that.
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by philpoole » Tue Nov 06, 2012 9:49 pm
I've been pulling my hair out a bit. Interrupts just don't seem to trigger for PCM.
I've simplified things down a bit. I've created a module that registers an ISR (request_irq() ) for IRQ 55, I then set up the PCM registers (similar to the userspace code - which works in a driver with bitbanging (although not ideal).
Then, the driver tries to fill the FIFO with zeroes, and then delays - so hopefully should cause both a FIFO overflow and underflow.
However, I'm not seeing interrupts firing at all. cat /proc/interrupts happily shows that the IRQ is registered, but not triggered. I have a counter incremented by the ISR, which remains zero.
I've tried different trigger levels (HIGH, LOW, RISING, FALLING, 0) to no avail. I've tried differing values for TXTHR. I've uninstalled all snd- modules prior to running this. I've rearranged the ordering of poking the PCM registers, etc. All to no avail.

Is 55 the correct IRQ?

Here's the code. Where am I going wrong?
Code: Select all
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/hrtimer.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/interrupt.h>

//IO stuff
#include <linux/ioport.h>
#include <asm/io.h>
#define BCM2708_PERI_BASE        0x20000000
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
#define I2S_BASE                (BCM2708_PERI_BASE + 0x203000) /* GPIO controller */
#define CLOCK_BASE               (BCM2708_PERI_BASE + 0x101000) /* Clocks */
#define INT_BASE                (BCM2708_PERI_BASE + 0x00B200)


#define CS_A     0
#define FIFO_A   1
#define MODE_A   2
#define RXC_A    3
#define TXC_A    4
#define DREQ_A   5
#define INTEN_A  6
#define INTSTC_A 7
#define GRAY     8

const char *i2s_register_name[] = {"CS_A", "FIFO_A", "MODE_A", "RXC_A", "TXC_A", "DREQ_A", "INTEN_A", "INTSTC_A", "GRAY"};

unsigned int *i2s_registers;
unsigned int *gpio;
unsigned int *clock_registers;
unsigned int *int_registers;

static int isr_count =0;

 irqreturn_t i2s_interrupt_handler(int irq, void *dev_id)
{
   isr_count++;
   *(i2s_registers+INTSTC_A)=0x0f; //set the bit to clear the interrupt
    return IRQ_HANDLED;
}

unsigned int data_for_interrupt = 0xdeadbeef;



static int hello_init(void)
{

        int i=0, err=0;
   int pin;
        unsigned int temp;
        printk(KERN_EMERG "Hello, world\n");

   err = request_irq(55, i2s_interrupt_handler, IRQF_TRIGGER_RISING, "I2S",(void*)&data_for_interrupt);
        if(err)
           printk(KERN_EMERG "COMPUTER SAYS NO INTS\n");
        else
           printk(KERN_EMERG "INTERRUPTS AHOY!\n");
        enable_irq(55);
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

   gpio = ioremap(GPIO_BASE, SZ_16K);

   /* SPI is on GPIO 7..11 */
   for (pin = 28; pin <= 31; pin++) {
      INP_GPIO(pin);      /* set mode to GPIO input first */
      SET_GPIO_ALT(pin, 2);   /* set mode to ALT 0 */
   }

#undef INP_GPIO
#undef SET_GPIO_ALT

    i2s_registers = ioremap(I2S_BASE, 128);
    clock_registers = ioremap(CLOCK_BASE, 32);
    int_registers = ioremap(INT_BASE, 32);
    temp = *(int_registers+5);
    printk(KERN_EMERG "%x  INT REGISTERS>>>  %x\n", (unsigned int) (int_registers+5), temp);
  printk(KERN_EMERG "%x  PENDING REGISTERS>>>  %x\n", (unsigned int) (int_registers+2), *(int_registers+2));
   *(int_registers+5) |= 1<<23;
    temp = *(int_registers+5);
    printk(KERN_EMERG "%x tweaked INT REGISTERS>>>  %x\n", (unsigned int) (int_registers+5), temp);
 
       
    printk(KERN_EMERG "Disabling I2S clock\n");
    *(clock_registers+0x26) = 0x5A000000;
    *(clock_registers+0x27) = 0x5A000000;
     
    udelay(10);

    printk(KERN_EMERG "Configure I2S clock\n");
    *(clock_registers+0x26) = 0x5A000001;

    *(clock_registers+0x27) = 0x5A006CD7; //a calculated guess, hopefully about 6.8, but seems out somewhat (I want 64*44100 = 2822400 Hz)
    udelay(10);
    printk(KERN_EMERG "Enabling I2S clock\n");
    *(clock_registers+0x26) = 0x5A000011;

    // disable I2S so we can modify the regs
    printk(KERN_EMERG "Disable I2S\n");
   // *(i2s_registers+CS_A) &= ~(1<<24);
    udelay(100);
    //*(i2s_registers+CS_A) = 0;
    udelay(100);

    // enable I2S
    *(i2s_registers+CS_A) |= 0x01;
   
    printk(KERN_EMERG "Clearing FIFOs\n");
    *(i2s_registers+CS_A) |= 1<<3 | 1<<4 | 3<<5; // clear TX FIFO
    udelay(10);
 
    // set register settings
    // --> enable Channel1 with 32bit width
    // For 16 bit I2S, Channel width should be 16, possible positions should be 1 and 17?
    //(e.g. look for 1<<29 and 17<<4
    printk(KERN_EMERG "Setting TX channel settings\n");
    *(i2s_registers+TXC_A) = 0<<31 | 1<<30 | 1 << 20  | 8<<16   |   0<<15 | 1<<14 | 33<<4 | 8 ;

    //Set frame length and frame sync length (32 and 16), and set FTXP=1 so I can inject 2 channels into a single 32 bit word
    *(i2s_registers+MODE_A) = 1 << 24 | 63<<10 | 32;

    // --> disable STBY
    printk(KERN_EMERG "disabling standby\n");
    *(i2s_registers+CS_A) |= 1<<25;
    udelay(50);

//*(i2s_registers+CS_A) |= 2 <<5;
//enable interrupts
    *(i2s_registers+INTEN_A) = 0x0;
    for(i = 0; i < 63; i++)
      (*(i2s_registers+FIFO_A)) = 0;
   
    *(i2s_registers+INTSTC_A) = 0x000f;// clear status bits
    for(i=0; i<9;i++) {
       printk(KERN_EMERG "I2S memory address=0x%08x: 0x%08x    %s\n", (unsigned int)i2s_registers+i, *(i2s_registers+i), i2s_register_name[i]);
    }
    // enable transmission
    *(i2s_registers+CS_A) |= 0x04;
   
    // --> ENABLE SYNC bit
    printk(KERN_EMERG "setting sync bit high\n");
    *(i2s_registers+CS_A) |= 1<<24;

    if (*(i2s_registers+CS_A) & 1<<24) {
        printk(KERN_EMERG "SYNC bit high, strange.\n");
    } else {
        printk(KERN_EMERG "SYNC bit low, as expected.\n");
    }

    udelay(1);
     
    if (*(i2s_registers+CS_A) & 1<<24) {
        printk(KERN_EMERG "SYNC bit high, as expected.\n");
    } else {
        printk(KERN_EMERG "SYNC bit low, strange.\n");
    }
    printk(KERN_EMERG "Memory dump\n");

    for(i=0; i<9;i++) {
        printk(KERN_EMERG "I2S memory address=0x%08x: 0x%08x    %s\n", (unsigned int)i2s_registers+i, *(i2s_registers+i), i2s_register_name[i]);
    }

    msleep(300);
   //Let's fill up the FIFO, let it overflow, and then sleep, so it must surely underflow. Surely that'll trigger something??!
  printk(KERN_EMERG "%x  PENDING REGISTERS>>>  %x\n", (unsigned int) (int_registers+2), *(int_registers+2));
    printk(KERN_EMERG "WRITING...");
    for(i = 0; i < 640; i++)
      (*(i2s_registers+FIFO_A)) = 0;
    printk(KERN_EMERG "WRITTEN\n");
    msleep(300);

  printk(KERN_EMERG "%x  PENDING REGISTERS>>>  %x\n", (unsigned int) (int_registers+2), *(int_registers+2));
    printk(KERN_EMERG "WRITING...");
    for(i = 0; i < 640; i++)
      (*(i2s_registers+FIFO_A)) = 0;
    printk(KERN_EMERG "WRITTEN\n");
    msleep(300);

  printk(KERN_EMERG "%x  PENDING REGISTERS>>>  %x\n", (unsigned int) (int_registers+2), *(int_registers+2));
    printk(KERN_EMERG "WRITING...");
    for(i = 0; i < 640; i++)
      (*(i2s_registers+FIFO_A)) = 0;
    printk(KERN_EMERG "WRITTEN\n");

  printk(KERN_EMERG "%x  PENDING REGISTERS>>>  %x\n", (unsigned int) (int_registers+2), *(int_registers+2));
       return 0;
}

static void hello_exit(void)
{
    printk(KERN_EMERG "Disable I2S\n");
printk(KERN_EMERG "%x  PENDING REGISTERS1>>>  %x\n", (unsigned int) (int_registers+1), *(int_registers+1));
 
  printk(KERN_EMERG "%x  PENDING REGISTERS2>>>  %x\n", (unsigned int) (int_registers+2), *(int_registers+2));
    *(i2s_registers+CS_A) &= ~(1<<24);
    udelay(100);
    *(i2s_registers+CS_A) = 0;
    udelay(100);
    *(i2s_registers+INTSTC_A) = 0xf;
    *(i2s_registers+INTEN_A) = 0;

    iounmap(i2s_registers);
    iounmap(gpio);
    iounmap(clock_registers);
    iounmap(int_registers);
    disable_irq(55);
    free_irq(55,(void*)&data_for_interrupt);
     

    printk(KERN_EMERG "Goodbye, cruel world ISR COUNT %d\n", isr_count);
}
module_init(hello_init);
module_exit(hello_exit);
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Wed Nov 07, 2012 10:03 am
I think because 55 is a GPU IRQ you have to enable it also in BCM2835 Interrupt Enable Reqister 2 (adress 0x214) because it belongs to IRQ source range 63:32. See page 117 (enable) and 118 (disable).

I think the interrupts are level sensitive, not edge: "For each interrupt source (ARM or GPU) there is an interrupt enable bit (read/write) and an interrupt pending bit (Read Only). All interrupts generated by the arm control block are level sensitive interrupts. Thus all interrupts remain asserted until disabled or the interrupt source is cleared." in the beginning of Interrupts chapter. Guess the FIFO irq will be cleared when the FIFO is filled again above threshold, or writing 1 to asserter INTSTC_A Register.

The data sheet also tells to wait after FIFO clear: "Assert RXCLR and/or TXCLR wait for 2 PCM clocks to ensure the FIFOs are reset.", though don't know if this must be implemented literally like that in hello_init(). But you are right, the FIFO should be filled before enabling I2S output: "If transmitting, ensure that sufficient sample words have been written to PCMFIFO before transmission is started. Set TXON and/or RXON to begin operation."
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by philpoole » Wed Nov 07, 2012 12:02 pm
Hi,
Thanks for the reply.
I'll double check the addresses, but I am setting the bit in the interrupt enable register in the code, and I have tried all TRIGGER types, typically HIGH (I started playing with that in desperation).

Maybe I haven't enabled the PCM block in the correct order (I have played with that too, but maybe haven't nailed it yet).
I think I might go even further back to basics and implement a GPIO driver interrupt! Surely that should work?
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by philpoole » Wed Nov 07, 2012 1:01 pm
Ah, reading a bit further, looks like I need to set bits in the basic register table as well, either 9 or 17 by the looks of things, and call request_irq(9 or 17 - depending on which one works). Forgot about the indirection.
Something to try later...
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Wed Nov 07, 2012 8:12 pm
There has been problems with GPIO interrupts as well, but I think it has been already corrected with a patch. See this thread:
viewtopic.php?f=44&t=7509

Do you have possibly some other driver installed blocking the interrupt, like the snd-bcm2835? Guess the VC may do some nasty things in background. I'm going to put together simple TDA1543 DAC, though some codec would actually be better. Wonder if the Mikroelectronica WM8731 Audio Codec PROTO (http://www.mikroe.com/add-on-boards/aud ... dec-proto/) could be get working with the RPi I2S interface. It's got onboard oscillator so it should work as master (RPi as I2S slave).
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by mhelin » Wed Nov 07, 2012 9:16 pm
philpoole,

Have to checked the memory addresses, in kernel mode they are different than in user mode, right? They start from 0xF200 0000, not from 0x2000 0000 like in user space. In VC bus address space the I/O peripherals start at 0x7E00 0000 though I think we don't need to know that except later when playing with DMA which needs the memory mapped to VC bus address space. Anyway, is the ioremap really doing that? I would use hardcoded addresses instead. And even if it works aren't the bus addresses to be remapped in 0x7E00000 space before mapping to virtual (kernel) space? I'm new to this architecture so it takes little bit time to get used to. Anyway, in /arch/arm/mach-bcm2708/include/mach/platform.h there is macro IO_ADDRESS:

Code: Select all
/* macros to get at IO space when running virtually */
#define IO_ADDRESS(x)   (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
#define __io_address(a)     __io(IO_ADDRESS(a))
/*
 *  SDRAM
 */
#define BCM2708_SDRAM_BASE           0x00000000

#define BCM2708_PERI_BASE        0x20000000
#define IC0_BASE                 (BCM2708_PERI_BASE + 0x2000)
#define ST_BASE                  (BCM2708_PERI_BASE + 0x3000)   /* System Timer */
#define MPHI_BASE                (BCM2708_PERI_BASE + 0x6000)   /* Message -based Parallel Host Interface */
#define DMA_BASE                 (BCM2708_PERI_BASE + 0x7000)   /* DMA controller */
#define ARM_BASE                 (BCM2708_PERI_BASE + 0xB000)    /* BCM2708 ARM control block */
#define PM_BASE                  (BCM2708_PERI_BASE + 0x100000) /* Power Management, Reset controller and Watchdog registers */
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO */

...
#define ARM_IRQ2_BASE                  32
#define INTERRUPT_VC_I2SPCM            (ARM_IRQ2_BASE + 23)



and other constants needed. Ok, maybe no problems there. In those definitions I2S IO address is missing.
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by philpoole » Wed Nov 07, 2012 9:46 pm
Not sure about 0xF2000000, I see it documented, but most things refer to BCM2708_PERI_BASE, and I can get the PCM block to output with that.

I'm happy the PCM block is behaving itself, it obviously works badly when polled, and when I dump the PCM registers I can see the INTSTC_A register goes from clear to set (implying an interrupt should be triggered), but I'm not seeing anything happen in the interrupt pending registers.
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Wed Nov 07, 2012 10:09 pm
How about this in your code:
Code: Select all
#define INT_BASE                (BCM2708_PERI_BASE + 0x00B200)
int_registers = ioremap(INT_BASE, 32);
*(int_registers + 5) |= 1 << 23;

0x2000B205 (in PDF base 0x205) is not defined at all for interrupt registers, I think you should use "Interrupt enable register 2." @ offset 0x214 which transfers in your code to
Code: Select all
*(int_registers + 0x14) |= 1 << 23;

RIght?
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by philpoole » Wed Nov 07, 2012 10:54 pm
Yeah, but its a pointer to unsigned int, so when you increment it, it points to the next address.
(You can see the same principle being used for the PCM registers, and they work).
I can rewrite it completely with hardwired addresses, but I think it might be scraping the barrel a bit (but hey! there's obviously something wrong)
Posts: 69
Joined: Mon Oct 29, 2012 5:09 pm
by mhelin » Thu Nov 08, 2012 7:03 am
philpoole wrote:Yeah, but its a pointer to unsigned int, so when you increment it, it points to the next address.

Yes obviously, 5 (word offset) x (32/8)=20 =0x14 (byte offset). Makes it hard to compare to the data sheet register addresses and offsets though.
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm
by mhelin » Thu Nov 08, 2012 8:23 am
Noticed some irq handlers are static, some not, does it matter? Also the BCM ethernet driver's DMA IRQ handler seems to have just the flag IRQF_DISABLED set in request_irq call, the irq is really enabled in DMA registers later. If and when the irq handler is supposed to clear the interrupt should'n you try filling the FIFO (with zeros to test) in ISR? I'm just thinking that in your case maybe the interrupt is not really handled when it's fired the first time.

Is this the bug:

Code: Select all
    //enable interrupts
        *(i2s_registers+INTEN_A) = 0x0;


That just disables all interrupts, shouldn't you set it at least (if you don't want to handle errors) to 0x00000001? (TXW=1 "TX Write Interrupt Enable Setting this bit enables interrupts from PCM block when TX FIFO level is less than the specified TXTHR level. "
Posts: 99
Joined: Wed Oct 17, 2012 7:18 pm