[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Using DMA with ECP ports



Hi all,

We are using the etrax 100lx dev board to transfer data from a high
speed modem we our building via ecp through the etrax to a remote
collection point via TCP/IP

We are attempting to get the DMA going with a simple test driver but
have experienced the following problems.

1. While trying to use a single descriptor to transfer to a buffer in
the kernel we get the situation where the data transfers but the
transfer does not stop at the end of the one and only descriptor (with a
d_eol bit set) but restarts some several ms later and sometimes a third
time.  The R_DMA3_FIRST register is still pointing to the first
descriptor even though the documentation says that after a d_eol flagged
descriptor it gets reset to 0.

2. We have now noticed that with a small transfer size (say 13 bytes) we
get two sets of two transfers. The first transfer is 64 bytes long
followed by a 13 byte transfer. A large delay then occurs (ms) and the
dual transfer is repeated.

Using our logic analyser we see the following on the ECP port

64bytes-->125us-->13bytes-->4114us-->64bytes-->125us-->13bytes

looking at the output from the attached code i get this snapshot:
-------------------------------------------------------
General config d40084
Parport 0 config c7010074
Parport 1 config df0000ff
Reg mode: 6 (ecp_fwd(5), ecp_rev(6))
Reg perr == nACKREV: 0 (ecp_rev(0))
Reg nack == nPCLK: 0 (inactive (1), active (0))
Reg busy == PACK: 1 (inactive (0), active (1))
Reg fault == nPREQ: 1 (inactive (1), active (0))
Reg sel: 1 (inactive (0), active (1), xflag(1))
Reg tr_rdy: 0 (busy (0), ready (1))
CH#_FIRST = 1074879272
DMA status = 00
 buff phys = 1074879304 fdesc phys = 1074879272 
point ot first = 1074879272
CH#_FIRST = 1074879272
DMA status = 00
DMA sw_len = 13
time is 0
HZ is 100
Page size = 8192
1sec delay CH#_FIRST = 1074879272
----------------------------------------------------------

In our application data is continually ready for reverse transfer from
the modem thus nPCLK is always active. Is this a problem with the ecp
state machine in the etrax?

Any ideas? (code attached)

Mike.
-- 
Michael Sloman
Engineer, Institute for Telecommunications Research
University of South Australia, Mawson Lakes 5095 , AUSTRALIA
Ph: +618-8302-5168   Fx: +618-8302-3873
Email: mikes@xxxxxxx.au
/* es3kdriver programme */

/* Michael Sloman August 2002 */

/* This programme is designed to connect to the two par ports on the etrax */
/* and make available to user apps DMA transfer for port 0 for data and config and */
/* small reads/writes for port 1 plus dma into etrax for the fifo dumps. */




#include <linux/config.h>

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/proc_fs.h>

#include <linux/delay.h>

#include <asm/svinto.h>     /* DMA and register descriptions */
#include <asm/io.h>         /* LED_* I/O functions */
#include <asm/irq.h>
#include <asm/dma.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/timex.h>


#include "es3kdriver.h"

#define ES3K_DEBUG

#define ES3K_REV_MAJ 0
#define ES3K_REV_MIN 1

#define ES3K_MAJOR_NR 240


static const char* es3k_name = "es3k";
static const char* tststr = "This is a test String!";
static const char* ModName = "ESS3000 Device Driver (c)2002 M.Sloman ITR";

static struct proc_dir_entry *es3k_proc_file;

static etrax_dma_descr indma __attribute__ ((aligned(4)));
static etrax_dma_descr outdma __attribute__ ((aligned(4)));
static char dmatest[8192];

static volatile unsigned int int_count;  // interrput counter


/***************************************/
/* Port LowLevel Routines              */
/***************************************/


int es3k_pp0_setup(int dir, int dma)
{
  unsigned long int dma_en = IO_STATE(R_PAR0_CONFIG, dma, disable) ;
  unsigned long int ecp_dir= IO_STATE(R_PAR0_CONFIG, mode, ecp_rev);

  if(dma != 0)
    dma_en = IO_STATE(R_PAR0_CONFIG, dma, enable);
  else
    dma_en = IO_STATE(R_PAR0_CONFIG, dma, disable);
  
  if(dir != 0)
    ecp_dir = IO_STATE(R_PAR0_CONFIG, mode, ecp_fwd);
  else
    ecp_dir = IO_STATE(R_PAR0_CONFIG, mode, ecp_rev);


  *R_PAR0_DELAY = 0;   /* no delays at all */


  *R_PAR0_CONFIG = 
    /* External buffer */
    IO_STATE(R_PAR0_CONFIG, ioe,     noninv)    |
    /* Not connected, don't care */
    IO_STATE(R_PAR0_CONFIG, iseli,   noninv)    |
    /* iautofd is not inverted, inv */
    IO_STATE(R_PAR0_CONFIG, iautofd, inv)    |
    /* Not used in reverse direction, don't care */
    IO_STATE(R_PAR0_CONFIG, istrb,   inv)    |
    /* Not connected, don't care */
    IO_STATE(R_PAR0_CONFIG, iinit,   inv)    |
    /* perror is GND and reverse wants 0, noninv */
    IO_STATE(R_PAR0_CONFIG, iperr,   noninv)    |
    /* ack is not inverted, noninv */
    IO_STATE(R_PAR0_CONFIG, iack,    noninv)    |
    /* busy is not inverted, noninv */
    IO_STATE(R_PAR0_CONFIG, ibusy,   noninv)    |
    /* fault is not inverted, noninv */
    IO_STATE(R_PAR0_CONFIG, ifault,  noninv)    |
    /* select is Vcc and we want 1, noninv */
    IO_STATE(R_PAR0_CONFIG, isel,    noninv)    |
    /* We will run dma, enable */
    /* IO_STATE(R_PAR0_CONFIG, dma, enable)*/
    dma_en                                      |
    /* No run length encoding, disable */
    IO_STATE(R_PAR0_CONFIG, rle_in, disable)    |
    /* No run length encoding, disable */
    IO_STATE(R_PAR0_CONFIG, rle_out, disable)   |
    /* Enable parallel port */
    IO_STATE(R_PAR0_CONFIG, enable, on)         |
    /* Force mode regardless of pin status */
    IO_STATE(R_PAR0_CONFIG, force, on)          |
    /* select ECP Reverse mode*/
    //IO_STATE(R_PAR0_CONFIG, mode, ecp_rev);       
    ecp_dir;

  /* Setup DMA transfer stuff */

  /* nothin here yet -- perhaps should put some safe numbers inti deas */


  return 0;
}

int es3k_pp1_setup(int dir, int dma)
{
  unsigned long int dma_en = IO_STATE(R_PAR1_CONFIG, dma, disable) ;
  unsigned long int ecp_dir= IO_STATE(R_PAR1_CONFIG, mode, ecp_rev);

  if(dma != 0)
    dma_en = IO_STATE(R_PAR1_CONFIG, dma, enable);
  else
    dma_en = IO_STATE(R_PAR1_CONFIG, dma, disable);
  
  if(dir != 0)
    ecp_dir = IO_STATE(R_PAR1_CONFIG, mode, ecp_fwd);
  else
    ecp_dir = IO_STATE(R_PAR1_CONFIG, mode, ecp_rev);


  *R_PAR1_DELAY = 0;   /* no delays at all */


  *R_PAR1_CONFIG = 
    /* External buffer */
    IO_STATE(R_PAR1_CONFIG, ioe,     noninv)    |
    /* Not connected, don't care */
    IO_STATE(R_PAR1_CONFIG, iseli,   noninv)    |
    /* iautofd is not inverted, inv */
    IO_STATE(R_PAR1_CONFIG, iautofd, noninv)    |
    /* Not used in reverse direction, don't care */
    IO_STATE(R_PAR1_CONFIG, istrb,   noninv)    |
    /* Not connected, don't care */
    IO_STATE(R_PAR1_CONFIG, iinit,   noninv)    |
    /* perror is GND and reverse wants 0, noninv */
    IO_STATE(R_PAR1_CONFIG, iperr,   noninv)    |
    /* ack is not inverted, noninv */
    IO_STATE(R_PAR1_CONFIG, iack,    noninv)    |
    /* busy is not inverted, noninv */
    IO_STATE(R_PAR1_CONFIG, ibusy,   noninv)    |
    /* fault is not inverted, noninv */
    IO_STATE(R_PAR1_CONFIG, ifault,  noninv)    |
    /* select is Vcc and we want 1, noninv */
    IO_STATE(R_PAR1_CONFIG, isel,    noninv)    |
    /* We will run dma, enable */
    /* IO_STATE(R_PAR1_CONFIG, dma, enable)*/
    dma_en                                      |
    /* No run length encoding, disable */
    IO_STATE(R_PAR1_CONFIG, rle_in, disable)    |
    /* No run length encoding, disable */
    IO_STATE(R_PAR1_CONFIG, rle_out, disable)   |
    /* Enable parallel port */
    IO_STATE(R_PAR1_CONFIG, enable, on)         |
    /* Force mode regardless of pin status */
    IO_STATE(R_PAR1_CONFIG, force, on)          |
    /* select ECP Reverse mode*/
    //IO_STATE(R_PAR1_CONFIG, mode, ecp_rev);       
    ecp_dir;

  /* Setup DMA transfer stuff */

  /* nothin here yet -- perhaps should put some safe numbers inti deas */


  return 0;
}



/***************************************/
/* Device file routines                */
/***************************************/

static int es3k_ioctl(struct inode *inode, struct file *file,
		     unsigned int cmd,unsigned long arg) 
{ 
  switch(cmd) {
  case ES3K_STATUS:       /* return status string  */
    {
      if (copy_to_user((char*)arg, tststr, sizeof(tststr)))
	return -EFAULT; 
      return 0;
    }
    
  case ES3K_WR_MODE:      /* set up drv mode */
    {
      return 0;
    }      
    
  default:
    return -ENOIOCTLCMD;
  }
}


int es3k_open(struct inode * es3k_inode, struct file * es3k_file)
{

/* Check and fail if already opened by another process */


#ifdef ES3K_DEBUG
  printk(KERN_INFO "%s: Open\n", es3k_name);
#endif
  return 0;
}

int es3k_release(struct inode * es3k_inode, struct file * es3k_file)
{
#ifdef ES3K_DEBUG
  printk(KERN_INFO "%s: Close\n", es3k_name);
#endif
  return 0;
}

ssize_t es3k_read(struct file * es3kfile, char * es3k_cbuff, size_t es3k_len, loff_t * es3k_off)
{
  int ret;
  char fred;

  /*  ret = copy_to_user(es3k_cbuff, tststr, strlen(tststr)); */
  fred = (char)(*R_PAR0_STATUS_DATA)&0xff;
  ret = copy_to_user(es3k_cbuff, &fred ,1);

  // printk(KERN_INFO "%s: copy to user returned %d\n", es3k_name, ret);

  /* return (ssize_t)strlen(tststr); */
  return 1;
}


ssize_t es3k_write(struct file * es3k_file, const char * es3k_cbuff, size_t es3k_len, loff_t * es3k_off )
{
  es3k_file->f_pos+=es3k_len;
  return (ssize_t)es3k_len;
}

static struct file_operations es3k_fops = {
  owner:          THIS_MODULE,
  open:           es3k_open,
  release:        es3k_release,
  read:           es3k_read,
  write:          es3k_write,
  ioctl:          es3k_ioctl,
}; 

// receive interrupt handler - RL
static void rx_interrupt_handler(int irq, void* dev_id, struct pt_regs* regs)
{
  // clear the interrupt
  *R_DMA_CH3_CLR_INTR = IO_STATE(R_DMA_CH3_CLR_INTR,clr_descr,do)|\
    IO_STATE(R_DMA_CH3_CLR_INTR,clr_eop,do);
    
  // printk(KERN_INFO " curr = %ld next = %ld\n", *R_DMA_CH3_DESCR, *R_DMA_CH3_NEXT);
  // printk(KERN_INFO " dma status = %ld\n", *R_DMA_CH3_STATUS);
  // printk(KERN_INFO " dma cmd    = %ld\n", *R_DMA_CH3_CMD   );
  // printk(KERN_INFO " dma buf    = %ld\n", *R_DMA_CH3_BUF   );

}
// transmit interrupt handler - RL
static void tx_interrupt_handler(int irq, void* dev_id, struct pt_regs* regs)
{
  // clear the interrupt
  *R_DMA_CH4_CLR_INTR = IO_STATE(R_DMA_CH2_CLR_INTR,clr_descr,do)|\
    IO_STATE(R_DMA_CH2_CLR_INTR,clr_eop,do);
    
  // printk(KERN_INFO " curr = %ld next = %ld\n", *R_DMA_CH2_DESCR, *R_DMA_CH4_NEXT);
  // printk(KERN_INFO " dma status = %ld\n", *R_DMA_CH2_STATUS);
  // printk(KERN_INFO " dma cmd    = %ld\n", *R_DMA_CH2_CMD   );
  // printk(KERN_INFO " dma buf    = %ld\n", *R_DMA_CH2_BUF   );

}


/*****************************************/
/* PROC file system routines             */
/*****************************************/

static int es3k_write_proc(struct file *file, const char *buffer,
			   unsigned long count, void *data)
{
  
  /*  copy_from_user(HERE, buffer, count);*/
  return (int)count;  // fake reading data
}

#define PRINTF(fmt, args...) sprintf(buf+len, fmt, ## args)

static int es3k_read_proc(char *buf, 
			  char **start, off_t offset, 
			  int count, int *eof, void *data)
{
  ssize_t len = 0;
  unsigned long temp;
  int i;
  int slen;

  unsigned short tstart;
  unsigned short tstop;

  len += PRINTF("\nESS3000 Driver Rev. %d.%d   \n\n",ES3K_REV_MAJ,ES3K_REV_MIN);  
  len += PRINTF("\n");
  len += PRINTF("General config %04x\n", genconfig_shadow);
  len += PRINTF("Parport 0 config %04x\n", *R_PAR0_STATUS_DATA);
  len += PRINTF("Parport 1 config %04x\n", *R_PAR1_STATUS_DATA);

  temp = (*R_PAR0_STATUS)&0xE000;
  temp = temp >> 13;
  len += PRINTF("Reg mode: %lu (ecp_fwd(5), ecp_rev(6))\n", temp);
  
  temp = (*R_PAR0_STATUS)&0x1000;
  temp = temp >> 12;
  len += PRINTF("Reg perr == nACKREV: %lu (ecp_rev(0))\n", temp);
  
  temp = (*R_PAR0_STATUS)&0x0800;
  temp = temp >> 11;
  len += PRINTF("Reg nack == nPCLK: %lu (inactive (1), active (0))\n", temp);
  
  temp = (*R_PAR0_STATUS)&0x0400;
  temp = temp >> 10;
  len += PRINTF("Reg busy == PACK: %lu (inactive (0), active (1))\n", temp);

  temp = (*R_PAR0_STATUS)&0x0200;
  temp = temp >> 9;
  len += PRINTF("Reg fault == nPREQ: %lu (inactive (1), active (0))\n", temp);
  
  temp = (*R_PAR0_STATUS)&0x0100;
  temp = temp >> 8;
  len += PRINTF("Reg sel: %lu (inactive (0), active (1), xflag(1))\n", temp);
  
  temp = (*R_PAR0_STATUS)&0x02;
  temp = temp >> 1;
  len += PRINTF("Reg tr_rdy: %lu (busy (0), ready (1))\n", temp);

  /* do a test dma transfer here to kernel memory */

  // clear any spurious interrupts
  *R_DMA_CH3_CLR_INTR = IO_STATE(R_DMA_CH3_CLR_INTR,clr_descr,do);
 
  // enable the descriptor interrupt
  *R_IRQ_MASK2_SET = IO_STATE(R_IRQ_MASK2_SET, dma3_descr,set);

  indma.sw_len = 13;
  indma.hw_len = 0;
  indma.ctrl = d_eol;                
  indma.next = virt_to_phys(NULL);
  indma.buf = virt_to_phys(dmatest);
  indma.status = 0;
  *R_DMA_CH3_FIRST = virt_to_phys(&indma);

  es3k_pp0_setup(0,1);  // dir = rev dma = on

  RESET_DMA(3);
  WAIT_DMA(3);

  len += PRINTF("CH#_FIRST = %d\n", *R_DMA_CH3_FIRST);
  len += PRINTF("DMA status = %02x\n", indma.status);
  
  len += PRINTF(" buff phys = %ld fdesc phys = %ld \n",virt_to_phys(dmatest), virt_to_phys(&indma));

  tstart = GET_JIFFIES_USEC();

  *R_DMA_CH3_CMD = IO_STATE(R_DMA_CH3_CMD, cmd, start);

  while(*R_DMA_CH3_CMD == 1);

  tstop = GET_JIFFIES_USEC();

  len += PRINTF("point ot first = %ld\n",virt_to_phys(&indma));
  len += PRINTF("CH#_FIRST = %d\n", *R_DMA_CH3_FIRST);
  len += PRINTF("DMA status = %02x\n", indma.status);
  len += PRINTF("DMA sw_len = %d\n", indma.sw_len);
  len += PRINTF("time is %d\n", tstop-tstart); 
  len += PRINTF("HZ is %d\n", HZ);
  len += PRINTF("Page size = %ld\n", PAGE_SIZE);

  mdelay(10);
  
  len += PRINTF("1sec delay CH#_FIRST = %d\n", *R_DMA_CH3_FIRST);


/*   // Tx test */

/*   *R_DMA_CH4_CLR_INTR = IO_STATE(R_DMA_CH2_CLR_INTR,clr_descr,do); */
 
/*   // enable the descriptor interrupt */
/*   *R_IRQ_MASK2_SET = IO_STATE(R_IRQ_MASK2_SET, dma2_descr,set); */

/*   outdma.sw_len = 4095; */
/*   outdma.hw_len = 0; */
/*   outdma.ctrl = d_eol|d_eop;                 */
/*   outdma.next = virt_to_phys(NULL); */
/*   outdma.buf = virt_to_phys(dmatest); */
/*   outdma.status = 0; */
/*   *R_DMA_CH2_FIRST = virt_to_phys(&outdma); */

/*   es3k_pp0_setup(1,1);  // dir = fwd dma = on */

/*   RESET_DMA(2); */
/*   WAIT_DMA(2); */
/*   len += PRINTF("CH#_FIRST = %ld\n", *R_DMA_CH2_FIRST); */
/*   len += PRINTF("DMA status = %02x\n", outdma.status); */

  
/*   len += PRINTF(" buff phys = %ld fdesc phys = %ld \n",virt_to_phys(dmatest), virt_to_phys(&outdma)); */


/*   tstart = GET_JIFFIES_USEC(); */

/*   *R_DMA_CH2_CMD = IO_STATE(R_DMA_CH2_CMD, cmd, start); */

/*   while(*R_DMA_CH2_CMD == 1); */

/*   tstop = GET_JIFFIES_USEC(); */

/*   len += PRINTF("point ot first = %ld\n",virt_to_phys(&outdma)); */
/*   len += PRINTF("CH#_FIRST = %ld\n", *R_DMA_CH2_FIRST); */
/*   len += PRINTF("DMA status = %02x\n", outdma.status); */
/*   len += PRINTF("DMA sw_len = %d\n", outdma.sw_len); */
/*   len += PRINTF("time is %d\n", tstop-tstart);  */
 


  /* Now for a slow test and print to screen if DMA is off */
  
  /*
  for(i=0;i<100;i++)
    {
      len += PRINTF("%c",(char)(*R_PAR0_STATUS_DATA)&0xff);
    }
  */


  len -= offset;  
  if (len < count)
    {
      *eof = 1;
      if (len <= 0)
	return(0);
    } 
  else 
    len = count;
  
  *start = buf + offset;
  
  return(len);
}


/*********************************************/
/* Module Routines                           */
/*********************************************/


static int __init es3k_init_module(void)
{
  int ret;

  printk(KERN_INFO "%s\n",ModName);
  
#ifdef CONFIG_ETRAX_ES3KDRIVER_PORT0
  printk(KERN_INFO "%s: PORT0 config was set\n", es3k_name);

  ret = es3k_pp0_setup(0,0);

  ret = request_dma(PAR0_TX_DMA_NBR, es3k_name);
  if(ret)
    {
      printk(KERN_INFO "%s: dma allocate for %d failed\n",es3k_name, PAR0_TX_DMA_NBR);
    }  
  ret = request_dma(PAR0_RX_DMA_NBR, es3k_name);
  if(ret)
    {
      printk(KERN_INFO "%s: dma allocate for %d failed\n", es3k_name, PAR0_RX_DMA_NBR);
    }  
  // request the interrupt from the kernel
  if(request_irq(IO_BITNR(R_VECT_MASK_RD,dma3),rx_interrupt_handler,0,"es3k pp0 dma rx", NULL))
    panic("Can't allocate Rx IRQ for PP0");
  if(request_irq(IO_BITNR(R_VECT_MASK_RD,dma2),tx_interrupt_handler,0,"es3k pp0 dma tx", NULL))
    panic("Can't allocate Tx IRQ for PP0");

#else
  printk(KERN_INFO "%s: PORT0 config not set\n",es3k_name);
#endif    

     
#ifdef CONFIG_ETRAX_ES3KDRIVER_PORT1
  printk(KERN_INFO "%s: PORT1 config was set\n",es3k_name);

  ret = es3k_pp1_setup(0,0);

  ret = request_dma(PAR1_TX_DMA_NBR, es3k_name);
  if(ret)
    {
      printk(KERN_INFO "%s: dma allocate for %d failed\n", es3k_name, PAR1_TX_DMA_NBR);
    }
  ret = request_dma(PAR1_RX_DMA_NBR, es3k_name);
  if(ret)
    {
      printk(KERN_INFO "%s: dma allocate for %d failed\n", es3k_name, PAR1_RX_DMA_NBR);
    }

  // request the interrupt from the kernel
  if(request_irq(IO_BITNR(R_VECT_MASK_RD,dma5),rx_interrupt_handler,0,"es3k pp1 dma rx", NULL))
    panic("Can't allocate Rx IRQ for PP1");
  if(request_irq(IO_BITNR(R_VECT_MASK_RD,dma4),tx_interrupt_handler,0,"es3k pp1 dma rx", NULL))
    panic("Can't allocate Tx IRQ for PP1");

#else
  printk(KERN_INFO "%s: PORT1 config not set\n", es3k_name);
#endif


  if(register_chrdev(ES3K_MAJOR_NR, es3k_name, &es3k_fops))
    {
      printk(KERN_INFO "%s: unable to get major %d for es3k\n", 
	     es3k_name, ES3K_MAJOR_NR);
      return -1;
    } 

  /* Make our mark in the proc filesystem */
  
  es3k_proc_file = create_proc_entry("es3k",S_IFREG | S_IRUGO, NULL);

  if (!es3k_proc_file)
    {
      printk(KERN_INFO "%s: registering of proc file failed.\n", es3k_name);
    }
  else
    {
      es3k_proc_file->read_proc = es3k_read_proc;
      es3k_proc_file->write_proc = es3k_write_proc;
      es3k_proc_file->owner = THIS_MODULE; 
    }

  printk(KERN_INFO "%s: Finished module_init\n", es3k_name);
  return 0;  
}


/* Register init routine with macro */

module_init(es3k_init_module);