linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* I2S MPC5200
@ 2005-09-16 13:29 Arnaud Carer
  2005-09-16 16:16 ` Bob Peterson
  0 siblings, 1 reply; 2+ messages in thread
From: Arnaud Carer @ 2005-09-16 13:29 UTC (permalink / raw)
  To: linuxppc-embedded

[-- Attachment #1: Type: text/plain, Size: 640 bytes --]

Hi,

I am looking for an I2s Linux driver to implement an audio output with the
mpc5200

 

I2s.c and i2s_ring.c include in the DENX ELDK kernel seems to be wrong.

 

>i2s.c is not really a driver, but a (very OLD) test  version  of  one
>used  to  demonstrate  certain  BestComm  related problems. Don't try
>using it as a real driver ;-)
 
>The ring driver appears to be RX only, 
 
 
Thanks,
 
Arnaud

 

 

 

 

___________________________________

 

Arnaud Carer

Firmware Development 

 

 <http://www.cleode.fr/>  <http://www.cleode.fr/> CLEODE

 <http://www.cleode.fr> www.cleode.fr

Tel: 02 96 48 68 18

Fax: 02 96 48 19 11

 


[-- Attachment #2: Type: text/html, Size: 6536 bytes --]

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: I2S MPC5200
  2005-09-16 13:29 I2S MPC5200 Arnaud Carer
@ 2005-09-16 16:16 ` Bob Peterson
  0 siblings, 0 replies; 2+ messages in thread
From: Bob Peterson @ 2005-09-16 16:16 UTC (permalink / raw)
  To: Arnaud Carer, linuxppc-embedded

[-- Attachment #1: Type: text/plain, Size: 1122 bytes --]

Hi Arnaud (and all),

My I2S audio driver seems to work fine for our icecube-based mpc5200.
It is attached for all the world to smear.  Hardware assumptions are 
included in
the comments.  It assumes a pcm1717 i2s dac running on psc2.
It implements much of the oss and mixer ioctls, volume controls, etc.

It seems to be pretty stable:
I've fixed many interrupt and timing problems of the original and
played many songs for many days with this version of the driver.

Regards,

Bob Peterson
bob@acdstar.com

At 08:29 AM 9/16/2005, Arnaud Carer wrote:
>Hi,
>I am looking for an I2s Linux driver to implement an audio output 
>with the mpc5200
>
>I2s.c and i2s_ring.c include in the DENX ELDK kernel seems to be wrong.
>
> >i2s.c is not really a driver, but a (very OLD) test  version  of  one
> >used  to  demonstrate  certain  BestComm  related problems. Don't try
> >using it as a real driver ;-)
> >The ring driver appears to be RX only,
>Thanks,
>Arnaud
>___________________________________
>
>Arnaud Carer
>Firmware Development
>
>CLEODE
><http://www.cleode.fr>www.cleode.fr
>Tel: 02 96 48 68 18
>Fax: 02 96 48 19 11


[-- Attachment #2: i2s.c --]
[-- Type: text/plain, Size: 36555 bytes --]

/* Converted from original i2s.c to work with PCM1717 dac by Bob Peterson, */
/* Advanced Communication Design, bob@acdstar.com            4/7/2005      */

/* Assumptions made for our hardware:

   Assumes PSC2 is attached to your i2s audio dac

   The control lines are on GPIO pins, mapped as follows:

   GPIO "Timer 0" = Das Blinken Light
   GPIO "Timer 1" = IDE Power
   GPIO "Timer 2" = RSTB on PCM1717
   GPIO "Timer 3" = MC on PCM1717
   GPIO "Timer 4" = ML on PCM1717
   GPIO "Timer 5" = MD on PCM1717
   GPIO "Timer 6" = Backlight enable
   GPIO "Timer 7" = Beeper

Other assumptions:

   This assumes your primary audio device is /dev/I2S
   Some programs, such as mplayer, assume the audio device is /dev/dsp or dspw
   I just made a symlink, but you can change how the device is registered too.

Changes from the original i2s driver included in eldk:

   1. Receive functions and interrupts are disabled (transmit only)
   2. Added ioctls for oss sound device
   3. Added ioctls for mixer device
   4. Added extra ioctl to sound a beeper for a given length of time
   5. Changed how buffering and interrupts are handled.
   6. Added I2S control functions (such as volume control) separate from DAC.

*/


#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/miscdevice.h>

#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/delay.h>

#include <linux/init.h>
#include <asm/mpc5xxx.h>
#include <bestcomm_api.h>
#include <mgt5200/mgt5200.h>
#include <mgt5200/sdma.h>
#include <asm/mpc5xxx_i2s.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include </ppc/eldk/ppc_6xx/usr/src/linux/drivers/sound/sound_config.h>

#define I2S_MINOR 242
#define BEEP_DURATION 5
#define MAX_DMA_BUFFERS 16

/* #define ENABLE_RX */
#define RX_TASK_START
#define TX_TASK_START

/* #define RDEBUG */
/* #define WDEBUG */
/* #define LITEDEBUG */
#define DEBUG_LOST_INT

#ifdef RDEBUG
#define RDPRINTK(fmt, args...)	printk(fmt, ## args)
#else
#define RDPRINTK(fmt, args...)
#endif

#ifdef WDEBUG
#define WDPRINTK(fmt, args...)	printk(fmt, ## args)
#else
#define WDPRINTK(fmt, args...)
#endif

/* Some bit assignments for SICR */
#define DELAY_TIME_SLOT 0x20000000
#define GEN_CLK_INT 0x00800000
#define MULTIWD_ENABLE 0x00400000
#define CLK_POL_RISING 0x00200000
#define LSB_FIRST 0x10000000
#define I2S_WAIT 3 /* 3 microsecond delay between I2S ops */
#define reg1717_vol_l           0
#define reg1717_vol_r           1
#define   reg1717_vol_ldl         0x0100
#define reg1717_mute            2
#define reg1717_io_fmt          3
#define   reg1717_io_iis          0x0001 /* low=normal, high=iis data fmt   */
#define   reg1717_io_lrp          0x0002 /* priority of sample rate clock   */
#define   reg1717_io_iw           0x0004 /* input word length, low=16 h=18  */
#define   reg1717_io_atc          0x0008 /* attenuation ctrl. 0=individual
                                             1=left+right controlled by reg0 */
#define   reg1717_io_output_mute  0x0000 /* all mute */
#define   reg1717_io_output_m_r   0x0010 /* left is mute , right is right */
#define   reg1717_io_output_m_l   0x0020 /* left is mute , right is left  */
#define   reg1717_io_output_m_a   0x0030 /* left is mute , right is l+r/2 */
#define   reg1717_io_output_r_m   0x0040 /* left is right, right is mute  */
#define   reg1717_io_output_r_r   0x0050 /* left is right, right is right */
#define   reg1717_io_output_r_l   0x0060 /* left is right, right is left  */
#define   reg1717_io_output_r_a   0x0070 /* left is right, right is l+r/2 */
#define   reg1717_io_output_l_m   0x0080 /* left is left , right is mute  */
#define   reg1717_io_output_l_r   0x0090 /* left is left , right is right */
#define   reg1717_io_output_l_l   0x00A0 /* left is left , right is left  */
#define   reg1717_io_output_l_a   0x00B0 /* left is left , right is l+r/2 */
#define   reg1717_io_output_a_m   0x00C0 /* left is l+r/2, right is mute  */
#define   reg1717_io_output_a_r   0x00D0 /* left is l+r/2, right is right */
#define   reg1717_io_output_a_l   0x00E0 /* left is l+r/2, right is left  */
#define   reg1717_io_output_a_a   0x00F0 /* left is l+r/2, right is l+r/2 */
#define     reg1717_io_normal (reg1717_io_output_l_r | \
                               reg1717_io_atc /* | \
						reg1717_io_iis*/ ) /* 99 */

#ifdef ENABLE_RX
static volatile ulong rx_released; /* Counter of released BDs */
static volatile ulong rx_assigned; /* Counter of BDs to be assigned */
static volatile ulong rx_data;     /* Counter of BDs read */
#endif
static volatile ulong tx_released; /* Counter of released BDs */
static volatile ulong tx_assigned; /* Counter of BDs to be assigned */

#ifdef DEBUG_LOST_INT
static volatile int tx_int;
static volatile ulong tx_acount; /* real number of assigned BDs */
#endif

static int txtask;
static int initiator_tx;
#ifdef ENABLE_RX
static int rxtask;
static int initiator_rx;
#endif
static int psc_num;
static unsigned long i2s_is_open;
static int configured=0;
static int once;
static int sample_rate = 44100;
static int mixer_dev_id;
static int blinken = 0;
static int beep_duration = 0;
static struct i2s_ioctl defaults;
static DECLARE_WAIT_QUEUE_HEAD(WaitQ);

static void i2s_start_rx_tx (int task);
static void psc_enable(void);

DECLARE_WAIT_QUEUE_HEAD (i2s_wait_tx);

#ifdef ENABLE_RX
DECLARE_WAIT_QUEUE_HEAD (i2s_wait_rx);
#endif

extern int mgt_sdma_load_tasks_image(void);

struct i2s_buffer {
	char *va;
	dma_addr_t pa;
	size_t len;
};

static void timer_int(void *nothing);
static inline void wait(void) { udelay(I2S_WAIT); }
static int i2s_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
		     unsigned long arg);

static struct tq_struct Task = {
  routine: (void(*)(void *)) timer_int, /* The function to run */
  data: NULL
};

static struct i2s_buffer i2s_tx_bufs[MAX_DMA_BUFFERS];
#ifdef ENABLE_RX
static struct i2s_buffer i2s_rx_bufs[MAX_DMA_BUFFERS];
#endif

/* --------------- */
/* ML = frame sync */
/* --------------- */
static void ml_low(void) { /* GPIO Timer 4 */
  u32 gpt4;
  gpt4=*(volatile u32 *)MPC5xxx_GPT4_ENABLE; /* get initial value */
  gpt4&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt4|=0x00000024; /* Set as a GPIO and set the value to zero */
  *(volatile u32 *)MPC5xxx_GPT4_ENABLE=gpt4;
}
static void ml_high(void){
  u32 gpt4;
  gpt4=*(volatile u32 *)MPC5xxx_GPT4_ENABLE; /* get initial value */
  gpt4&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt4|=0x00000034; /* Set as a GPIO and set the value to one */
  *(volatile u32 *)MPC5xxx_GPT4_ENABLE=gpt4;
}

/* --------------- */
/* MC = clock      */
/* --------------- */
static void mc_low(void) { /* GPIO Timer 3 */
  u32 gpt3;
  gpt3=*(volatile u32 *)MPC5xxx_GPT3_ENABLE; /* get initial value */
  gpt3&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt3|=0x00000024; /* Set as a GPIO and set the value to zero */
  *(volatile u32 *)MPC5xxx_GPT3_ENABLE=gpt3;
}
static void mc_high(void){
  u32 gpt3;
  gpt3=*(volatile u32 *)MPC5xxx_GPT3_ENABLE; /* get initial value */
  gpt3&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt3|=0x00000034; /* Set as a GPIO and set the value to one */
  *(volatile u32 *)MPC5xxx_GPT3_ENABLE=gpt3;
}

/* --------------- */
/* MD = data       */
/* --------------- */
static void md_low(void) { /* GPIO Timer 5 */
  u32 gpt5;
  gpt5=*(volatile u32 *)MPC5xxx_GPT5_ENABLE; /* get initial value */
  gpt5&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt5|=0x00000024; /* Set as a GPIO and set the value to zero */
  *(volatile u32 *)MPC5xxx_GPT5_ENABLE=gpt5;
}
static void md_high(void){
  u32 gpt5;
  gpt5=*(volatile u32 *)MPC5xxx_GPT5_ENABLE; /* get initial value */
  gpt5&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt5|=0x00000034; /* Set as a GPIO and set the value to one */
  *(volatile u32 *)MPC5xxx_GPT5_ENABLE=gpt5;
}

/* --------------- */
/* RSTB = Reset    */
/* --------------- */
static void snd_reset(void) { /* GPIO Timer 2 */
  u32 gpt2;
  gpt2=*(volatile u32 *)MPC5xxx_GPT2_ENABLE; /* get initial value f0000620 */
  gpt2&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt2|=0x00000024; /* Set as a GPIO and set the value to zero */
  *(volatile u32 *)MPC5xxx_GPT2_ENABLE=gpt2;
}
static void snd_normal(void) {
  u32 gpt2;
  gpt2=*(volatile u32 *)MPC5xxx_GPT2_ENABLE; /* get initial value */
  gpt2&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt2|=0x00000034; /* Set as a GPIO and set the value to one */
  *(volatile u32 *)MPC5xxx_GPT2_ENABLE=gpt2;
}

/* --------------- */
/* Blinken Licht   */
/* --------------- */
static void blinken_off(void) { /* GPIO Timer 0 */
  u32 gpt0;
  gpt0=*(volatile u32 *)MPC5xxx_GPT0_ENABLE; /* get initial value */
  gpt0&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt0|=0x00000024; /* Set as a GPIO and set the value to zero */
  *(volatile u32 *)MPC5xxx_GPT0_ENABLE=gpt0;
}
static void blinken_on(void) {
  u32 gpt0;
  gpt0=*(volatile u32 *)MPC5xxx_GPT0_ENABLE; /* get initial value */
  gpt0&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt0|=0x00000034; /* Set as a GPIO and set the value to one */
  *(volatile u32 *)MPC5xxx_GPT0_ENABLE=gpt0;
}

/* --------------- */
/* Beeper          */
/* --------------- */
static void beeper_off(void) { /* GPIO Timer 0 */
  u32 gpt7;
  gpt7=*(volatile u32 *)MPC5xxx_GPT7_ENABLE; /* get initial value */
  gpt7&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt7|=0x00000024; /* Set as a GPIO and set the value to zero */
  *(volatile u32 *)MPC5xxx_GPT7_ENABLE=gpt7;
}
static void beeper_on(void) {
  u32 gpt7;
  gpt7=*(volatile u32 *)MPC5xxx_GPT7_ENABLE; /* get initial value */
  gpt7&=0xffffff00; /* clear the lower bits, preserve the upper bits */
  gpt7|=0x00000034; /* Set as a GPIO and set the value to one */
  *(volatile u32 *)MPC5xxx_GPT7_ENABLE=gpt7;
}

/* ------------------------------------------------------------------------- */
/* i2s_sendbit                                                               */
/* ------------------------------------------------------------------------- */
static void i2s_sendbit(int bit, int bit_num)
{
  mc_low();
  wait();
  if (bit)
    md_high();
  else
    md_low();
  wait();
  wait();
  mc_high();
  wait();
  if (bit_num==0)
    ml_low();
  wait();
  if (bit_num==0) {
    wait();
    ml_high();
  }
}
/* ------------------------------------------------------------------------- */
/* i2s_sendword                                                              */
/* ------------------------------------------------------------------------- */
static void i2s_sendword(unsigned short word)
{
  static int i,v;

  ml_high();
  for (i=15; i>=0; i--) {
    v=(word>>i) & 0x00000001;
    i2s_sendbit(v,i);
  }
  wait();
  wait();
  wait();
  wait();
}

/* ------------------------------------------------------------------------- */
/* tipcm1717setregister */
/* ------------------------------------------------------------------------- */
static void tipcm1717setregister(unsigned short reg,unsigned short val)
{
  static short regval;

  regval=(val | (reg<<9));
  i2s_sendword(regval);
}

/* ------------------------------------------------------------------------- */
/* mixer routines */
/* ------------------------------------------------------------------------- */
static int ssp_open_mixdev(struct inode *inode, struct file *file)
{
  return 0;
}

static int ssp_release_mixdev(struct inode *inode, struct file *file)
{
  return 0;
}

static struct file_operations ssp_mixer_fops = {
  owner:        THIS_MODULE,
  llseek:       no_llseek,
  ioctl:        i2s_ioctl,
  open:         ssp_open_mixdev,
  release:      ssp_release_mixdev,
};

/* ------------------------------------------------------------------------- */
/* ssp_audio_reset */
/* ------------------------------------------------------------------------- */
static void ssp_audio_reset(void)
{
  int flags;

  local_irq_save(flags);
  udelay(100);
  snd_reset();
  udelay(100);
  snd_normal();
  udelay(500); /* give the dac time to settle */
  local_irq_restore(flags);
}

/* ------------------------------------------------------------------------- */
/* ssp_audio_init */
/* ------------------------------------------------------------------------- */
static void ssp_audio_init(void)
{
  int flags;

  /*
    Note from Bob Peterson (ACD): According to EJ:

    The [TI PCM1717] audio DAC for [our] PowerPC [board] is on PSC2.
    It will run in i2s mode.  The pin functions are documented on Page 2-34
    (PDF page 80) of the MPC5200 User Manual.

    I expect to run it in "Codec2 with MCLK" mode, with i2s data formatting.
    See chapter 15 for PSC setup details. [pp 501-555]

    We need to program the MCLK signal to have a clock of close to
    11.2896 MHz (we may have to be a small bit off to make the numbers work,
    the target is a 44.1 KHz sample rate times 256).

    This means we'll need to set the Serial Interface Control Register to:
    -first bit of first time slow of a new frame starts one bit clock cycle
    after the rising edge of frame sync.
    -MSB first
    -Codec mode, 32-bit data
    -use bit clock and frame sync generated internally from MCLK. */

  local_irq_save(flags);
  /* set the initial volume */
  tipcm1717setregister(reg1717_vol_l,  0x0008 | reg1717_vol_ldl);
  tipcm1717setregister(reg1717_vol_r,  0x0008 | reg1717_vol_ldl);
  tipcm1717setregister(reg1717_mute,   0x0000);
  tipcm1717setregister(reg1717_io_fmt, reg1717_io_normal);
  local_irq_restore(flags);
}

/* ------------------------------------------------------------------------- */
/* original i2s routines start here */
/* ------------------------------------------------------------------------- */
static int i2s_buffers_init(void)
{
  int i;
  for (i = 0; i < MAX_DMA_BUFFERS; i++) {
    if ((i2s_tx_bufs[i].va = 
	 pci_alloc_consistent(NULL, BUFSIZE, &i2s_tx_bufs[i].pa)) == NULL) {
      printk("Error: unable to allocate tx buffer #%d.\n",i+1);
      return -1;
    }
#ifdef ENABLE_RX
    if ((i2s_rx_bufs[i].va = 
	 pci_alloc_consistent(NULL, BUFSIZE, &i2s_rx_bufs[i].pa)) == NULL) {
      printk("Error: unable to allocate rx buffer #%d.\n",i+1);
      return -1;
    }
#endif
  }
  return 0;
}

/* ------------------------------------------------------------------------- */
/* i2s_buffers_release                                                       */
/* ------------------------------------------------------------------------- */
static int i2s_buffers_release(void)
{
  int i;
  for (i = 0; i < MAX_DMA_BUFFERS; i++) {
    pci_free_consistent(NULL, BUFSIZE, i2s_tx_bufs[i].va, i2s_tx_bufs[i].pa);
    TaskBDRelease(txtask);
#ifdef ENABLE_RX
    pci_free_consistent(NULL, BUFSIZE, i2s_rx_bufs[i].va, i2s_rx_bufs[i].pa);
    TaskBDRelease(rxtask);
#endif
  }
  
  return 0;
}

/* ------------------------------------------------------------------------- */
/* i2s_tx_irq - handle transmit interrupt                                    */
/* ------------------------------------------------------------------------- */
static void i2s_tx_irq(int irq, void *dev_id, struct pt_regs *regs)
{
  int bdnum;

#ifdef DEBUG_LOST_INT
  tx_int = 1;
#endif

  TaskIntClear(txtask);
  if (tx_assigned == tx_released) {
    printk("%s: %ld:%ld - false int??\n",__FUNCTION__,tx_assigned,tx_released);
    return;
  }
  while ((bdnum = TaskBDRelease(txtask))>=0) {
    tx_released++;
    if (tx_released>100*MAX_DMA_BUFFERS) { /* make sure our numbers never wrap to negative */
      tx_released-=98*MAX_DMA_BUFFERS;
      tx_assigned-=98*MAX_DMA_BUFFERS;
    }
    if (bdnum != tx_released % MAX_DMA_BUFFERS)
      printk("%s: %d:%ld\n", __FUNCTION__,bdnum,tx_released % MAX_DMA_BUFFERS);
    WDPRINTK("%s: released bd %d\n", __FUNCTION__, 
	     (bdnum + MAX_DMA_BUFFERS - 1) % MAX_DMA_BUFFERS);
  } /* while */
  wake_up(&i2s_wait_tx);
}

#ifdef ENABLE_RX
/* ------------------------------------------------------------------------- */
/* i2s_rx_irq                                                                */
/* ------------------------------------------------------------------------- */
static void i2s_rx_irq(int irq, void *dev_id, struct pt_regs *regs)
{
  int bdnum;

  if (rx_assigned == rx_released) {
    printk("%s: %ld:%ld - false int??\n", __FUNCTION__, rx_assigned, rx_released);
    return;
  }
  
  TaskIntClear(rxtask);

  bdnum = TaskBDRelease(rxtask);
  rx_released++;
  if (bdnum != rx_released % MAX_DMA_BUFFERS)
    printk("%s: %d:%ld\n", __FUNCTION__, bdnum, rx_released % MAX_DMA_BUFFERS);
  RDPRINTK("%s: released bd %d\n", __FUNCTION__, 
	   (bdnum + MAX_DMA_BUFFERS - 1) % MAX_DMA_BUFFERS);
  if (rx_assigned > rx_released) {
    bdnum=TaskBDAssign(rxtask, (void *)i2s_rx_bufs[bdnum].pa, NULL, BUFSIZE,0);
    RDPRINTK("%s: assigned bd %d\n", __FUNCTION__, bdnum);
  }
  wake_up(&i2s_wait_rx);
}
#endif

/* ------------------------------------------------------------------------- */
/**
 *	i2s_write:
 *	@file: file handle
 *	@buf: buffer to write (unused as data does not matter here 
 *	@count: count of bytes
 *	@ppos: pointer to the position to write. No seeks allowed
 *
 */
/* ------------------------------------------------------------------------- */
static ssize_t i2s_write(struct file *file, const char *buf, size_t count,
			 loff_t *ppos)
{
  size_t cnt = count;
  size_t tc;
  char *ptr = (char *)buf;
  int bdnum, err;
  int n, flags;
  
  if (!configured) {
    printk("i2s: write when not configured.\n");
    return -EINVAL;
  }
  /*  Can't seek (pwrite) on this device  */
  if (ppos != &file->f_pos) {
    printk("i2s: Can't seek (pwrite) on this device.\n");
    return -ESPIPE;
  }
  if (once) {
    /*printk("i2s: First write: starting rx_tx task.\n");*/
    i2s_start_rx_tx(txtask);
    once = 0;
  }
  /*WDPRINTK("%s: count %d\n", __FUNCTION__, count);*/
  
  while(cnt) {
    /* Wait until all buffers will be released */
    /*err = wait_event_interruptible(i2s_wait_tx, tx_assigned==tx_released);*/
    /* Wait until the number of buffers assigned is less than the    */
    /* number of buffers released plus the size of the buffer queue. */
    /*printk("W: Assigned: %d Released: %d\n",tx_assigned,tx_released);*/
    if (tx_assigned-tx_released>=MAX_DMA_BUFFERS) {
      err = wait_event_interruptible(i2s_wait_tx,
				     tx_assigned-tx_released<MAX_DMA_BUFFERS);
      if (err) {
#ifdef DEBUG_LOST_INT
	printk("%s: interrupted, %ld:%ld:%ld, tx_int %d, err %d\n",
	       __FUNCTION__,tx_assigned, tx_acount, tx_released, tx_int, err);
#endif
	break;
      }
    }
    /* prefill the buffers */
    tc = (cnt > BUFSIZE) ? BUFSIZE : cnt;
    if (copy_from_user(i2s_tx_bufs[tx_assigned % MAX_DMA_BUFFERS].va, 
		       ptr, tc)) {
      err =  -EFAULT;
      break;
    }
      
    i2s_tx_bufs[tx_assigned % MAX_DMA_BUFFERS].len = tc;
    n = tx_assigned % MAX_DMA_BUFFERS;
    tx_assigned++;
    ptr += tc;
    cnt -= tc;
    
    /* Now assign the first buffer to the Bestcomm task */
    
    save_flags(flags);cli(); /* Bestcomm can lose interrupts otherwise ??? */
    
#ifdef DEBUG_LOST_INT
    tx_int = 0;
    tx_acount++;
#endif
    // Note from Bob: bd = Buffer Descriptor

    bdnum = TaskBDAssign(txtask, (void *)i2s_tx_bufs[n].pa, NULL, 
			 i2s_tx_bufs[n].len, 0);
    restore_flags(flags);
    if (bdnum < 0) {
      printk("%s:TaskBDAssign error %d(tx stuck??)\n", __FUNCTION__, bdnum);
      return -EIO;
    }
    WDPRINTK("%s: assigned bd %d (buf %d), tc %d\n", __FUNCTION__,
	     bdnum, n, tc);
  }
  
  /*WDPRINTK("%s: sent %d, err %d\n", __FUNCTION__, count - cnt, err);*/
  return (count - cnt) ? (count - cnt) : err;
} /* i2s_write */

/* ------------------------------------------------------------------------- */
/**
 *	i2s_read:
 *	@file: file handle
 *	@buf: buffer to read
 *	@count: length of buffer
 *	@ptr: offset (no seek allowed)
 *
 */
/* ------------------------------------------------------------------------- */
static ssize_t i2s_read(struct file *file, char *buf,size_t count,loff_t *ppos)
{
#ifdef ENABLE_RX
  size_t cnt = count;
  char *ptr = buf;
  size_t bd_count;
  int bdnum;
  int err;

  if (!configured)
    return -EINVAL;

  /*  Can't seek (pread) on this device  */
  if (ppos != &file->f_pos)
    return -ESPIPE;
  
  if (once) {
    i2s_start_rx_tx(rxtask);
    once = 0;
  }

  while (cnt) {
    /* How much BDs are needed (up to MAX_DMA_BUFFERS) */
    bd_count = (cnt + BUFSIZE - 1) / BUFSIZE;
    if (bd_count > MAX_DMA_BUFFERS)
      bd_count = MAX_DMA_BUFFERS;
    rx_assigned += bd_count;
    RDPRINTK("%s: cnt %d, rx_data %ld, ass %ld, rel %ld\n", __FUNCTION__, cnt,
	     rx_data, rx_assigned, rx_released);
    
    /* Start reading the data */
    bdnum = TaskBDAssign(rxtask, 
			 (void *)i2s_rx_bufs[rx_released % MAX_DMA_BUFFERS].pa,
			 NULL, BUFSIZE, 0);
    if (bdnum < 0) {
      printk("%s:TaskBDAssign error %d\n", __FUNCTION__, bdnum);
      return -EIO;
    }
    RDPRINTK("%s:assigned bd %d (buf %ld)\n", __FUNCTION__,
	     bdnum, rx_assigned % MAX_DMA_BUFFERS);
    
    /* Copy the data to user as it arrives */
    while (bd_count--) {
      int len;
      len = (cnt > BUFSIZE) ? BUFSIZE : cnt;
      
      RDPRINTK("%s:to user:rx_data %ld, ass %ld, rel %ld\n", 
	       __FUNCTION__, rx_data,
	       rx_assigned, rx_released);
      /* Wait at least one BD released */
      err = wait_event_interruptible(i2s_wait_rx, rx_data < rx_released);
      if(err) {
	goto out;
      }
      if (copy_to_user(ptr, i2s_rx_bufs[rx_data % MAX_DMA_BUFFERS].va, len)) {
	err = -EFAULT;
	goto out;
      }
      ptr += len;
      cnt -= len;
      rx_data ++;
    }
  }
 out:
  RDPRINTK("%s: read %d, err %d\n", __FUNCTION__, count - cnt, err);
  return (count - cnt) ? (count - cnt) : err;
#else
  return -EINVAL;
#endif
}

/* ------------------------------------------------------------------------- */
/* i2s_start_rx_tx                                                           */
/* ------------------------------------------------------------------------- */
static void i2s_start_rx_tx(int task)
{
#ifdef ENABLE_RX
  if (task == rxtask) {
#ifdef RX_TASK_START
    TaskStart(rxtask, TASK_AUTOSTART_ENABLE, rxtask, TASK_INTERRUPT_ENABLE);
#endif
  }
#else
  if (task == txtask) {
#endif
#ifdef TX_TASK_START
    TaskStart(txtask, TASK_AUTOSTART_ENABLE, txtask, TASK_INTERRUPT_ENABLE);
#endif
  }
  psc_enable();
}

/* ------------------------------------------------------------------------- */
/* psc_enable                                                                */
/* ------------------------------------------------------------------------- */
static void psc_enable(void)
{
  struct mpc5xxx_psc *psc = (struct mpc5xxx_psc *)(MPC5xxx_PSC1 +
						   ((psc_num - 1) * 0x200));
  psc->command = MPC5xxx_PSC_RX_ENABLE | MPC5xxx_PSC_TX_ENABLE;
}

/* ------------------------------------------------------------------------- */
/* psc_reset                                                                 */
/* ------------------------------------------------------------------------- */
static void psc_reset(void)
{
  struct mpc5xxx_psc *psc = (struct mpc5xxx_psc *)(MPC5xxx_PSC1 +
						   ((psc_num - 1) * 0x200));
  
#ifdef ENABLE_RX
  psc->command = MPC5xxx_PSC_RST_RX;
#endif
  psc->command = MPC5xxx_PSC_RST_TX;
  psc->command = MPC5xxx_PSC_SEL_MODE_REG_1;
  psc->command = MPC5xxx_PSC_RST_ERR_STAT;
}

/* ------------------------------------------------------------------------- */
/* i2s_channel_setup                                                         */
/* ------------------------------------------------------------------------- */
static int i2s_channel_setup(struct i2s_ioctl *arg)
{
  struct mpc5xxx_psc *psc;
  struct mpc5xxx_gpio *gpio = (struct mpc5xxx_gpio *)MPC5xxx_GPIO;
  struct mpc5xxx_cdm *cdm = (struct mpc5xxx_cdm *)MPC5xxx_CDM;
  TaskSetupParamSet_t i2s_setup_tx;
#ifdef ENABLE_RX
  TaskSetupParamSet_t i2s_setup_rx;
#endif

  if (arg->psc_num < 1 || arg->psc_num > 3) {
    printk("i2s: Invalid psc specified during channel_setup.\n");
    return -EINVAL;
  }
  
  psc_num = arg->psc_num;
  
  switch (psc_num) {
  case 1:
    initiator_tx = INITIATOR_PSC1_TX;
#ifdef ENABLE_RX
    initiator_rx = INITIATOR_PSC1_RX;
#endif
    cdm->mclken_div_psc1 |= (0x8000 | arg->mclk_div);
    break;
  case 2:
    initiator_tx = INITIATOR_PSC2_TX;
#ifdef ENABLE_RX
    initiator_rx = INITIATOR_PSC2_RX;
#endif
    cdm->mclken_div_psc2 = (0x8000 | arg->mclk_div);
    break;
  case 3:
    initiator_tx = INITIATOR_PSC3_TX;
#ifdef ENABLE_RX
    initiator_rx = INITIATOR_PSC3_RX;
#endif
    cdm->mclken_div_psc3 |= (0x8000 | arg->mclk_div);
    break;
  }
  psc = (struct mpc5xxx_psc *)(MPC5xxx_PSC1 + ((psc_num - 1) * 0x200));
  /* disable Tx & Rx */
  psc->command = (MPC5xxx_PSC_TX_DISABLE | MPC5xxx_PSC_RX_DISABLE);
  gpio->port_config &= ~(0x7 << ((psc_num - 1) * 4));
  gpio->port_config &= 0xcfffffff; /* Turn off ALTS */
  
  if (arg->mclk) {
    gpio->port_config|=(0x7 << ((arg->psc_num-1)*4)); /* CODEC with MCLK */
  } else {
    gpio->port_config|=(0x6 << ((arg->psc_num-1)*4)); /* CODEC without MCLK */
  }
  
  cdm->clk_enables |= (1 << (arg->psc_num + 4)); /* PSC clock enable */
  /* reset PSC */
  psc_reset();
  
  psc->mode = 0;
  psc->rfalarm = 0x120; /* alarm threshold level */
  psc->tfalarm = 0x120;
  psc->rfcntl = 4; /* granularity */
  psc->tfcntl = 4;
  psc->mpc5xxx_psc_imr = 0; /* disable interrupts */
  
  /* Configure codec parameters  (MSB first) */
  switch (arg->data_width) {
  case 8:
    psc->sicr = 0x01000000;
    psc->ctur = 0x7; /* 8 bits per frame */
    break;
  case 16:
    psc->sicr = 0x02000000; /* 004xxxxx=multi word*/
    psc->ctur = 0xf; /* 0xf; 16 bits per frame */
    break;
  case 24:
    psc->sicr = 0x07000000;
    psc->ctur = 0x17; /* 24 bits per frame */
    break;
  case 32:
    psc->sicr = 0x0f000000;
    psc->ctur = 0x1f; /* 32 bits per frame: set frame width to 32 sclks */
    break;
  default:
    printk("Invalid data_width specified.\n");
    return -EINVAL;
  }

  psc->ccr = (arg->frsync_div*0x100) + arg->bclk_div;
  /* Bob note: I think ccr must be a write-only register.  It comes back 0. */
  /*           However, the databook seems to indicate r/w.  Not sure why. */
  /* DTS = 1, Data MSB first, use 5200 clk and frame sync, multiword.
   * Frame sync active low, data sampled on CLK high.
   */
  psc->sicr |= /*DELAY_TIME_SLOT |*/ MULTIWD_ENABLE | CLK_POL_RISING;
  
  if (arg->master) /* Master mode - PSC will drive SCLK and LRCK */
    psc->sicr |= GEN_CLK_INT;
  
  /* reset PSC again */
  psc_reset();
  
  /* setup the tasks */
  memset(&i2s_setup_tx, 0, sizeof(i2s_setup_tx));
  i2s_setup_tx.Initiator = (MPC5200Initiator_t)(initiator_tx);
  i2s_setup_tx.NumBD = MAX_DMA_BUFFERS;
  i2s_setup_tx.Size.MaxBuf = BUFSIZE;
  i2s_setup_tx.StartAddrDst = (volatile u32)&(psc->tfdata);
  i2s_setup_tx.IncrSrc = arg->data_width/8; /* added by Bob: was 4 */
  i2s_setup_tx.IncrDst = 0;
  i2s_setup_tx.SzSrc = arg->data_width/8; /* was 4 */
  i2s_setup_tx.SzDst = arg->data_width/8; /* was 4 */
  txtask = TaskSetup(TASK_GEN_TX_BD, &i2s_setup_tx);
  
  if (request_irq(MPC5xxx_SDMA_IRQ_BASE + txtask, i2s_tx_irq,
		  SA_INTERRUPT, "i2s tx dma", NULL)) {
    printk(KERN_ERR "i2s: SDMA tx irq allocation failed\n");
    return -EINVAL;
  }
  
#ifdef ENABLE_RX
  memset(&i2s_setup_rx, 0, sizeof(i2s_setup_rx));
  i2s_setup_rx.Initiator = (MPC5200Initiator_t)(initiator_rx);
  i2s_setup_rx.NumBD = MAX_DMA_BUFFERS;
  i2s_setup_rx.Size.MaxBuf = BUFSIZE;
  i2s_setup_rx.StartAddrSrc = (volatile u32)&(psc->rfdata);
  i2s_setup_rx.IncrDst = 4;
  i2s_setup_rx.IncrSrc = 0;
  i2s_setup_rx.SzDst = 4;
  rxtask = TaskSetup(TASK_GEN_RX_BD, &i2s_setup_rx);
  
  if (request_irq(MPC5xxx_SDMA_IRQ_BASE + rxtask, i2s_rx_irq,
		  SA_INTERRUPT, "i2s rx dma", NULL)) {
    printk(KERN_ERR "i2s: SDMA rx irq allocation failed\n");
    return -EINVAL;
  }
#endif  
  configured = 1;
  return 0;
}/* i2s_channel_setup */

/* ------------------------------------------------------------------------- */
/**
 *	i2s_ioctl:
 *	@inode: inode of the device
 *	@file: file handle to the device
 *	@arg: argument pointer
 */
/* ------------------------------------------------------------------------- */
static int i2s_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
		     unsigned long arg)
{
  long val;
  int ret;
  struct i2s_ioctl i2s_arg;
  static int *intpnd,*intmsk;

  switch (cmd) {
  case I2S_SET_CHANNEL:
    if (copy_from_user (&i2s_arg, (struct i2s_ioctl *)arg, sizeof(i2s_arg)))
      return -EFAULT;
    ret = i2s_channel_setup(&i2s_arg);
    break;

  case I2S_DEBUG:
    intpnd=(int *)(0xf0001214);
    intmsk=(int *)(0xf0001218);
    printk("txtask=%d INTPND=%08X INTMSK=%08X\n",
	   txtask,(unsigned int)*intpnd,(unsigned int)*intmsk);
    printk("Assigned=%ld Released=%ld\n",tx_assigned,tx_released);
    break;

  case I2S_BEEP:
    if (arg)
      beep_duration=(int)arg;
    else
      beep_duration=BEEP_DURATION; /* in 100ths of a second */
    beeper_on();
    break;

  case I2S_RESET:
    ssp_audio_reset();
    break;

  case SNDCTL_DSP_STEREO:
    ret = get_user(val, (int *) arg);
    if (ret)
      return ret;
    /* Simple standard DACs are stereo only */
    ret = (val == 0) ? -EINVAL : 1;
    return put_user(ret, (int *) arg);

  case SNDCTL_DSP_CHANNELS:
  case SOUND_PCM_READ_CHANNELS:
    /* Simple standard DACs are stereo only */
    return put_user(2, (long *) arg);

  case SNDCTL_DSP_SPEED:
  case SOUND_PCM_READ_RATE:
    /* We assume the clock doesn't change */
    return put_user(sample_rate, (long *) arg);
    
  case SNDCTL_DSP_SETFMT:
    if (arg!=AFMT_S16_BE) { /* if not signed 16-bit big-endian */
      printk("SNDCTL_DSP_SETFMT: request for 0x%08X rejected.\n",arg);
      return -1;
    }
    /* Simple standard DACs are 16-bit only */
    return put_user(16, (long *) arg);

  case SNDCTL_DSP_GETFMTS:
    /* Simple standard DACs are 16-bit only */
    return put_user(16, (long *) arg);

  case SNDCTL_DSP_SYNC:
    wait_event_interruptible(i2s_wait_tx,tx_assigned==tx_released);
    return 0;

  case SOUND_MIXER_WRITE_VOLUME:
    tipcm1717setregister(reg1717_vol_l, (unsigned short)arg | reg1717_vol_ldl);
    tipcm1717setregister(reg1717_vol_r, (unsigned short)arg | reg1717_vol_ldl);
    return 0;

  case SNDCTL_DSP_GETOSPACE:
    {
      audio_buf_info inf = { 0, };
      int i;
    
      if (!(file->f_mode & FMODE_WRITE))
	return -EINVAL;
      inf.fragments=MAX_DMA_BUFFERS-(tx_assigned-tx_released);
      inf.bytes = BUFSIZE*(MAX_DMA_BUFFERS-(tx_assigned-tx_released));
      inf.fragstotal = MAX_DMA_BUFFERS;
      inf.fragsize = BUFSIZE;
      return copy_to_user((void *)arg, &inf, sizeof(inf));
    }

  case SNDCTL_DSP_GETODELAY:
    if (!(file->f_mode & FMODE_WRITE))
      return -EINVAL;
    put_user(BUFSIZE*(tx_assigned-tx_released), (int *)arg);
    return 0;

  default:
    ret = -EINVAL;
    break;
  }
  return ret;
}

/* ------------------------------------------------------------------------- */
/**
 *	i2s_open:
 *	@inode: inode of device
 *	@file: file handle to device
 */
/* ------------------------------------------------------------------------- */
static int i2s_open(struct inode *inode, struct file *file)
{
  if(test_and_set_bit(0, &i2s_is_open))
    return -EBUSY;
  
  i2s_channel_setup(&defaults);
  ssp_audio_init();
  i2s_is_open = 1;
  tx_assigned = tx_released = 0;
#ifdef ENABLE_RX
  rx_assigned = rx_released = 0;
  rx_data = 0;
#endif
#ifdef DEBUG_LOST_INT
  tx_acount = 0;
#endif
  once = 1;
  return 0;
}

/* ------------------------------------------------------------------------- */
/**
 *	i2s_release
 *	@inode: inode to board
 *	@file: file handle to board
 */
/* ------------------------------------------------------------------------- */
static int i2s_release(struct inode *inode, struct file *file)
{
  wait_event_interruptible(i2s_wait_tx, tx_assigned == tx_released);
  psc_reset();
  
#ifdef ENABLE_RX
  if (rxtask) {
    TaskStop(rxtask);
    free_irq(MPC5xxx_SDMA_IRQ_BASE + rxtask, NULL);
    rxtask = 0;
  }
#endif
  if (txtask) {
    TaskStop(txtask);
    free_irq(MPC5xxx_SDMA_IRQ_BASE + txtask, NULL);
    TaskBDReset(txtask);
    txtask = 0;
  }

  configured = 0;
  clear_bit(0, &i2s_is_open);
  return 0;
}

/* ------------------------------------------------------------------------- */
/* i2s_poll                                                                  */
/* ------------------------------------------------------------------------- */
static unsigned int i2s_poll(struct file *file, poll_table *wait)
{
  int ret = 0;
  if (configured) {
    ret = (POLLIN | POLLRDNORM);
  }
  return ret;
}

static struct file_operations i2s_fops = {
	owner:		THIS_MODULE,
	llseek:		no_llseek,
	read:		i2s_read,
	write:		i2s_write,
	ioctl:		i2s_ioctl,
	open:		i2s_open,
	release:	i2s_release,
	poll:           i2s_poll,
};

static struct miscdevice i2s_miscdev=
{
	I2S_MINOR,
	"I2S",
	&i2s_fops
};

/* ------------------------------------------------------------------------- */
/*
 *	cleanup_module:
 */
/* ------------------------------------------------------------------------- */
static void __exit i2s_exit(void)
{
  misc_deregister(&i2s_miscdev);
  i2s_buffers_release();
  TaskStop(txtask);
  free_irq(MPC5xxx_SDMA_IRQ_BASE + txtask, NULL);
#ifdef ENABLE_RX
  TaskStop(rxtask);
  free_irq(MPC5xxx_SDMA_IRQ_BASE + rxtask, NULL);
#endif
}

/* ------------------------------------------------------------------------- */
/*
 * 	i2s_init:
 */
/* ------------------------------------------------------------------------- */
static int __init i2s_init(void)
{
  static int ret;
  /*struct mpc5xxx_intr *int_ctrl;*/
  /*sdma_regs *sdma = (sdma_regs *)MPC5xxx_SDMA;*/
  struct mpc5xxx_cdm *cdm = (struct mpc5xxx_cdm *)MPC5xxx_CDM;
  
  printk("i2s.c: audio device driver for PowerPC.\n");
  cdm->pci_clk_sel&=0xfffffffc; /* Speed up the pci clock by factor of 2*/
  udelay(50); /* wait just a little */
  ret = misc_register(&i2s_miscdev);
  if (ret) {
    printk(KERN_ERR "i2s: can't misc_register on minor=%d\n", I2S_MINOR);
    goto out;
  }
  i2s_buffers_init();
  defaults.psc_num=2;
  defaults.mclk_div=0x2e;
  defaults.mclk=1;
  defaults.data_width=16;
  defaults.frsync_div=0x1f;
  defaults.bclk_div=7;
  defaults.master=1;

  printk("We have audio for mpc5200!\n");
  mixer_dev_id = register_sound_mixer(&ssp_mixer_fops, -1);
  /*printk( KERN_INFO "Mixer registered.  id=%d\n",mixer_dev_id);*/
  /*printk(KERN_INFO "Initializing pcm1717 chip.\n");*/
  ssp_audio_reset();
  ssp_audio_init();
  /* int_ctrl=(struct mpc5xxx_intr *)MPC5xxx_INTR; * MBAR_INT_CTRL */
  /* int_ctrl->pimsk &= (~(PIMSK_BESTCOMM)); */
  /* Bob: Assume that u-boot has initialized Bestcomm for the purposes */
  /*      of dmaing to and from the ethernet FEC.                      */
  TasksInitAPI((uint8 *)MPC5xxx_MBAR); /* Still needed */
  /* sdma->taskBar = MPC5xxx_SRAM; */
  /* TasksLoadImage(sdma); */

  ret = 0;
  printk(KERN_INFO "MPC5200 I2S driver (%d BDs x %d bytes).\n", 
	 MAX_DMA_BUFFERS, BUFSIZE);
  /* --------------------------------------------------------------------- */
  /* Install our timer interrupt routine                                   */
  /* --------------------------------------------------------------------- */
  queue_task(&Task, &tq_timer);
 out:
  return ret;
}

/* ------------------------------------------------------------------------ */
/* timer_int - Timer Interrupt                                              */
/* ------------------------------------------------------------------------ */
static void timer_int(void *nothing)
{
  if (beep_duration>0) {
    beep_duration--; /* in 100ths of a second */
    if (!beep_duration)
      beeper_off();
  }

  if (waitqueue_active(&WaitQ)) /* if Cleanup wants us to die */
    wake_up(&WaitQ); /* Now cleanup_module can return */
  else
    queue_task(&Task, &tq_timer); /* put ourselves back in the task queue. */
}

module_init(i2s_init);
module_exit(i2s_exit);

MODULE_DESCRIPTION("Simple driver for the MPC5200 PSC in CODEC I2S mode.\n");
MODULE_LICENSE("GPL");
EXPORT_NO_SYMBOLS;

[-- Attachment #3: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2005-09-16 16:18 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-09-16 13:29 I2S MPC5200 Arnaud Carer
2005-09-16 16:16 ` Bob Peterson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).