/* ia_ffred.c */

/* FFred (transmit side) low level device and buffer management */

/* Copyright (c) 2000  James M. Westall, Dept of Computer Science,
 *                     Clemson University, Clemson SC 29634 USA
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * To obtain a copy of the GNU General Public License write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * This software is derived from software developed by the Interphase
 * corporation and released by them in September 2000. The software
 * released by Interphase carried the following copyright notice:
 *
 *
 *                          Copyright (C) 1993
 *                Interphase Corporation, Dallas, TX 75234
 *                          All Rights Reserved
 *
 * This code contains confidential information and  trade  secrets  of
 * Interphase Corporation which shall not be reproduced or transferred
 * to other programs or disclosed to others or used for  manufacturing
 * or any other purpose without prior written permission of Interphase
 * Corporation.  Use of copyright notice is precautionary and does not
 * imply publication or intent thereof.
 */

/*
 * FILE: ia_ffred.c
 *
 * DESCRIPTION:
 *   This file contains the Fragmentation FRED code.
 *
 * FUNCTIONS:
 * EXPORTED:
 *   see below.
 *
 * STATIC:
 *   see below.
 *
 * GLOBAL DATA:
 *
 */

#include "ia_defs.h"
#include <protocols.h>

/*
 * Exported routines
 */
int ia_ffred_init (ia_softc_t *);
int ia_tx_packet (ia_softc_t*, char *, int , aal_parms_t *);
static size_t ia_setup_ffred(ia_softc_t *softc, struct aal_parms *al,
                             f_buf_desc *desc, caddr_t addr,
                             size_t len);


/*
 * External functions
 */
extern   void ia_set_rate (ia_softc_t *, u_int, u_int);


/*
 * Local routines
 */

#if 0
static   int ia_tx_packet_fast (mblk_t *, ia_softc_t *, struct aal_parms *, int, ushort_t);
static   size_t ia_setup_ffred (ia_softc_t *, struct aal_parms *,
            f_buf_desc *, caddr_t, size_t);
#endif

static   int ia_find_free_tx_desc (ia_softc_t *, ushort_t *);
   int ia_set_rate_queue (ia_softc_t *, u_int, u_int);
static  u_short find_R(u_int);
static  u_int modfunct(u_int, u_int);

extern   int   ia_aal5;
extern   int   ia_rates;
extern   int   ia_tx_vpi;
extern   u_int ia_debug;

#define USE_QUEUE

/*F
 * FUNCTION: ia_ffred_init
 *
 * DESCRIPTION:
 * Do the following to initialize F-FRED
 *
 * o Setup descriptor table
 * o Initialize Transmit Complete Queue (TCQ)
 * o Initialize Packet Ready Queue (PRQ)
 * o Initialize the VC table
 * o Setup rate queues
 * o Set FFRED on-line
 *
 * ARGUMENTS:
 * softc    Device state structure
 *
 * ENVIRONMENT:
 * Called from ia_attach
 *
 * RETURN:
 * IA_SUCCESS  If FFRED initialized sucessfully
 * IA_FAILURE  On failure
 *
 * NOTES:
 */

int
ia_ffred_init (ia_softc_t *softc)
{
   ffred_t     *ff = softc->brd_regs.ffred;
   ffred_t     *ffL = &softc->brd_regs_ll.ffred;
   u_int       mem_brd;
   u_int       buffer_brd;
   ushort_t    *qptr;
   f_buf_desc  *desc;
   f_vc_table  *vc;
   ulong_t     val;
   int         i;

   printk("IA 5515: FFred initialization entered... \n");

   ia_put32 (&ff->mask_reg, 0xffff);     /* Disable all ints */
   ia_put32 (&ff->mode_reg_0, 0);
   ia_put32 (&ff->cmd_reg, F_SWRESET);

#ifdef DBG_INIT
   printk("IA 5515: FFred reset complete... \n");
#endif

/*
 * Initialize buffer descriptors
 *
 * Note: Descriptor 0 is not used. NUM_TX_DESC does
 * not contain descriptor 0.
 *
 * Both F_DESC_START and TX_PACKET_RAM are 0
 * and so are ph.ffred_mem and ph.buf_mem (mw)
 */
   mem_brd = softc->brd_regs_ph.ffred_mem + F_DESC_START;
   buffer_brd = softc->brd_regs_ph.buf_mem + TX_PACKET_RAM;

#ifdef DBG_INIT
   printk("IA 5515: mem_brd is %x and buffer_brd is %x \n",
                    mem_brd, buffer_brd);
#endif

/*
 *  Here we put the address (0) into fred's desc_base reg
 */
   desc = (r_buf_desc *) (softc->brd_regs.ffred_mem + F_DESC_START);
   ia_put32 (&ff->desc_base, (mem_brd >> 16) & 0xffff);

/*
 * Now we construct desciptors in the area we just defined (we hope)
 */

#ifdef DBG_INIT
   printk("IA 5515: %d Tx descriptors of size %d at %x \n",
                     softc->num_tx_desc, sizeof (f_buf_desc), desc);
#endif

   memset((caddr_t)desc, 0,  sizeof (f_buf_desc));
   desc++;
   for (i=0; i < softc->num_tx_desc; i++)
   {
      memset((caddr_t)desc, 0, sizeof (f_buf_desc));
      ia_putw (&desc->desc_stat, F_AAL5);
      ia_putw (&desc->mid,    0);
      ia_putw (&desc->buf_low,   buffer_brd & 0xffff);
      ia_putw (&desc->buf_high,  buffer_brd >> 16);

      softc->tx_buf[i].baddr = (caddr_t)buffer_brd;
      softc->tx_buf[i].desc = desc;

      desc++;
      buffer_brd += TX_BUF_SIZE;
   }

/*
 * Initialize Transmit Complete Queue and place the free
 * descriptors in it.
 */
   mem_brd = softc->brd_regs_ph.ffred_mem + F_TCQ_START;
   ia_put32 (&ff->queue_base, (mem_brd >> 16) & 0xffff);

   val = mem_brd & 0xffff;
   ia_put32 (&ff->tcq_st_adr, val);
   ia_put32 (&ff->tcq_ed_adr, val + (softc->num_tx_desc * sizeof(ushort_t)));
   ia_put32 (&ff->tcq_rd_ptr, val);
   ia_put32 (&ff->tcq_wr_ptr, val + (softc->num_tx_desc * sizeof(ushort_t)));

#ifdef DBG_INIT
   printk("IA 5515: FFred  tcq rd ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->tcq_rd_ptr));
   printk("IA 5515: FFred  tcq wt ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->tcq_wr_ptr));
   printk("IA 5515: FFred  tcq st ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->tcq_st_adr));
   printk("IA 5515: FFred  tcq ed ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->tcq_ed_adr));
#endif

   qptr = (ushort_t *) (softc->brd_regs.ffred_mem + F_TCQ_START);
#ifdef DBG_INIT
   printk("IA 5515: Fillinq TCQ elements at %x \n", mem_brd);
#endif
   for (i=0; i < softc->num_tx_desc; i++)
   {
      ia_putw (qptr, (ushort_t)i + 1);
      qptr++;
   }

/*
 * Initialize Packet Ready Queue
 */
   mem_brd = softc->brd_regs_ph.ffred_mem + F_PRQ_START;

#ifdef DBG_INIT
   printk("IA 5515: Fillinq PRQ elements at %x \n", mem_brd);
#endif
   val = mem_brd & 0xffff;
   ia_put32 (&ff->prq_st_adr, val);
   ia_put32 (&ff->prq_ed_adr, val + (softc->num_tx_desc * sizeof(ushort_t)));
   ia_put32 (&ff->prq_rd_ptr, val);
   ia_put32 (&ff->prq_wr_ptr, val);

#ifdef DBG_INIT
   printk("IA 5515: FFred  prq rd ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->prq_rd_ptr));
   printk("IA 5515: FFred  prq wt ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->prq_wr_ptr));
   printk("IA 5515: FFred  prq st ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->prq_st_adr));
   printk("IA 5515: FFred  prq ed ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->prq_ed_adr));
#endif

/*
 * Initialize the VC table.
 * Set Time Interval Count to 0 and Cell Quota to 1.
 * This sets Average rate equal to peak rate. Set all
 * VC to use High Priority bank (A) queue "0".
 * -- This is a simple strategy and may need to be
 *    reconsidered later.
 */
   mem_brd = softc->brd_regs_ph.ffred_mem + F_VC_START;
   vc = (f_vc_table *) (softc->brd_regs.ffred_mem + F_VC_START);

   val = 0;
   i = 64*1024;
   while (i != F_NUM_VC)
   {
      i /= 2;
      val++;
   }
   val |= (ushort_t) ((mem_brd >> 8) & 0xfff8);
   ia_put32 (&ff->vc_lkup_base, val);
#ifdef DBG_INIT
   printk("IA 5515: Setting VC Lookup Base to %x \n", val);
#endif

   for (i=0; i<F_NUM_VC; i++)
   {
      memset ((caddr_t)vc, 0, sizeof (f_vc_table));
      ia_putw (&vc->f_hdr01,  (i >> 12) & 0xf);
      ia_putw (&vc->f_hdr23,  (i & 0xfff ) << 4);
      if (i < 16)
         ia_putw (&vc->f_vcmode, VCM_APP_CR32 | VCM_CC_MODE_4 | VCM_RQ_B0);
      else
         ia_putw (&vc->f_vcmode, VCM_APP_CR32 | VCM_CC_MODE_4 | VCM_RQ_B1);
      ia_putw (&vc->f_cq,  0x1);

      /* Add in predefined VPI */
      ia_orw (&vc->f_hdr01,   ((ia_tx_vpi & 0xff) << 4));
      vc++;
   }

/*
 * Setup rate queues. This is the default rate queue.
 * Set rate queue B0 to 155 Mbps.
 * Disable all other rate queues
 */

   ia_put32 (&ff->rq_reg_b0,  RQ_XOFF_EN |
               RQ_RQ_ENABLE |
               RQ_PRESCALER_00 |
               237);       /* Adjust xmit (237) speed here! */

   ia_put32 (&ff->rq_reg_b1,  RQ_XOFF_EN |
               RQ_RQ_ENABLE |
               RQ_PRESCALER_00 |
               237);        /* Adjust xmit (237) speed here! */

/* ia_put32 (&ff->rq_reg_b1, 0);  */
   ia_put32 (&ff->rq_reg_b2, 0);
   ia_put32 (&ff->rq_reg_b3, 0);
   ia_put32 (&ff->rq_reg_a0, 0);
   ia_put32 (&ff->rq_reg_a1, 0);
   ia_put32 (&ff->rq_reg_a2, 0);
   ia_put32 (&ff->rq_reg_a3, 0);

/*
 * Copy real ffred registers into the local copy
 */

   for (i=0; i<(sizeof (ffred_t))/4; i++)
      ((uint_t *)ffL)[i] = ia_get32 (&(((uint_t *)ff)[i])) & 0xffff;

   softc->last_tcq_wr_ptr = ffL->tcq_wr_ptr;

/*
 * Set mode register 0 and 1.
 * Set mask register.
 */
   ia_put32 (&ff->mode_reg_0, F_CM_WAIT_EN |
                              F_PM_REQADR |
                              F_CI_WIDTH16 |
                              F_CM_ERR_MODE);

   ia_put32 (&ff->mode_reg_1, F_PM_LENDIAN |
                              F_LINK_ONLY_BEG |
                              F_COSET_EN |
                              F_CONG_DIS);

   ia_or32 (&ff->mode_reg_0, F_ON_LINE);
   i = ia_get32 (&ff->intr_status_reg);
   ia_put32(&ff->mask_reg, 0);

   printk("IA 5515: Ffred initialization  completed\n");
   return   IA_SUCCESS;
}

/**/
void ia_settxp(
ia_softc_t *softc,
int        rateparm)
{
   ffred_t   *ff = softc->brd_regs.ffred;
   ia_put32 (&ff->rq_reg_b1,  RQ_XOFF_EN |
               RQ_RQ_ENABLE |
               RQ_PRESCALER_00 |
               rateparm);              /* Adjust xmit (237) speed here! */
}

void ia_dump_txb(
ia_softc_t *softc)
{
   int i;
   unsigned int lockflags;

   spin_lock_irqsave(&softc->xmitlock, lockflags);
   printk("IA 5515: Tx backlog is %d \n", softc->tx_backlog);

   for (i = 1; i < 32; i++)
   {
      if (softc->vcctab[i].tx_pending > 0)
      {
         printk("IA 5515: %d pending on vci %d \n",
                          softc->vcctab[i].tx_pending, i);
      }
   }

   spin_unlock_irqrestore(&softc->xmitlock, lockflags);
}

void ia_drive_tx(
ia_softc_t      *softc,
ushort_t        desc_num,
ushort_t        prqwp,
struct atm_vcc  *vcc,
struct sk_buff  *skb)
{
   aal_parms_t   aal;
   int           rc;
   caddr_t       addr;
   ia_tx_buf_t   *tx_buf;
   DLE           *dle;
   ulong_t       baddr;
   int           i;
   int           tc = 0;
   int           len;
   int           total_len;
   ia_dle_list_t *list = &softc->list[TX_LIST];

/* HW descriptor numbers are, alas, 1 origin */

   tx_buf = &softc->tx_buf[desc_num-1];

   len = skb->len;
   softc->driver_stat.tx_packets++;
   softc->driver_stat.tx_bytes += len;

   aal.ap_vpci = vcc->vci;
   aal.ap_mid  = 0;
   aal.ap_orderq = 0;

   softc->vcctab[vcc->vci].tx_active += 1;

/* Setup buffer descriptor and CS trailer */

   total_len = ia_setup_ffred (softc, &aal, tx_buf->desc, tx_buf->addr, len);
#ifdef DBG_XMIT
   printk("IA 5515: ia_setup_fred returned %d \n", len);
#endif

/*
 * Build DLEs to transfer the data to the board
 */
   dle = list->list_write;

#ifdef DBG_XMIT
   printk("IA 5515: FFred  prq wt ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->prq_wr_ptr));
#endif

/* Remember skbuff and vcc address for later */

/* printk("Set dsc %d skb to %x \n", desc_num, skb); */
   tx_buf->skb = skb;
   tx_buf->vcc = vcc;

/* Here we construct the DLE's to transmit the data and the CS trailer */
/* to the board. The first one here copies the data from the skb..     */

   baddr = (ulong_t)tx_buf->baddr;
   dle->dle_local_addr = baddr;
   dle->dle_sys_addr   = virt_to_bus(skb->data);
   dle->dle_count = len;
   dle->dle_mode = DLE_PRQWD;
   dle->dle_prq_wr_ptr  = prqwp;

   if (++dle == list->list_end)
      dle = list->list_start;

   list->list_write = dle;

/* This one puts the CS trailer where it needs to be */

   dle->dle_local_addr = baddr + total_len - 8;
   dle->dle_sys_addr   = (ulong_t)tx_buf->daddr[0];
   dle->dle_count = 8;
   dle->dle_mode = DLE_INT_ENABLE;
   dle->dle_prq_wr_ptr  = prqwp;

   if (++dle == list->list_end)
      dle = list->list_start;

   list->list_write = dle;

   if (softc->logtx)
   {
       do_gettimeofday(&softc->tv);
       printk("IA 5515: %d.%06d snd (%d, %d) vci %d \n",
              softc->tv.tv_sec, softc->tv.tv_usec, total_len,
              len, vcc->vci);
   }

#ifdef DBG_XMIT

   if (vcc->vci == 5)
   {
       do_gettimeofday(&softc->tv);
       printk("IA 5515: %d.%06d snd (%d, %d) vci %d \n",
              softc->tv.tv_sec, softc->tv.tv_usec, total_len,
              len, vcc->vci);
   }

   printk("IA 5515: dle_prq_wr_ptr  is %08x \n",  dle->dle_prq_wr_ptr);
   printk("IA 5515: dle_local_addr  is %08x \n",  dle->dle_local_addr);
   printk("IA 5515: dle_system_addr is %08x \n",  dle->dle_sys_addr);
   printk("IA 5515: message len     is %08x \n",  dle->dle_count);
   printk("IA 5515: message is:" );
   for (i = 0; i < 96; i++)
   {
      if ((i %16) == 0)
         printk("\n");
      printk("%02x ", (unsigned char)tx_buf->addr[i]);
   }
   printk("\n");
   printk("IA 5515: FFred  prq wt ptr is %x \n",
                    ia_getw(&softc->brd_regs.ffred->prq_wr_ptr));
#endif

/*
 * Incrementing the transaction counter starts the DMA xfer
 * The value 2 is the number of DLE's to be processed
 */

   ia_put32 (softc->brd_regs.tx_tc, 2);
   return;
}


/**/

/* Transmission redriver */

int ia_redrive_tx(
ia_softc_t *softc)
{
   int             i;
   int             ndx;
   int             vci = 0;
   ushort_t        desc_num;
   struct sk_buff *skb = 0;
   struct atm_vcc *vcc = 0;
   ushort_t        prqwp;

/* Queuing discpline is strict priority for vcis < 32 */
/* and round robin for vcis >= 32                     */

   for (i = 1; i < 32; i++)
   {
      if (softc->vcctab[i].tx_pending > 0)
      {
          skb = skb_dequeue(&softc->vcctab[i].tx_backlog);
          vci = i;
          break;
      }
   }

/* Nothing hot to go... sched users vccs RR */

   if (skb == 0)
   {
      ndx = softc->last_tx_vcc;
      ndx += 1;
      if (ndx == NUM_VCCS)
         ndx = 32;

      for (i = 32; i < NUM_VCCS; i++)
      {
         if (softc->vcctab[ndx].tx_pending > 0)
         {
             skb = skb_dequeue(&softc->vcctab[ndx].tx_backlog);
             softc->last_tx_vcc = ndx;
             vci = ndx;
             break;
         }
         ndx += 1;
         if (ndx == NUM_VCCS)
            ndx = 32;
      }
   }

/* Since a backlog is counted... this shouldn't happen */

   if (skb == 0)
   {
      printk("IA 5515: NULL skb on vci %d in rdv w/ %d \n", vci,
                                                softc->tx_backlog);
      printk("Ndx = %d and lastvcc is %d \n",
                                     ndx,  softc->last_tx_vcc);
      softc->tx_backlog = 0;
      if (vci > 0)
      {
          softc->vcctab[vci].tx_pending = 0;
      }
      return(IA_FAIL);
   }

   vcc = ATM_SKB(skb)->vcc;
   if (vcc == 0)
   {
      printk("IA 5515: NULL vcc in redrive!! \n");
      return(IA_FAIL);
   }

   desc_num = ia_find_free_tx_desc(softc, &prqwp);
   if ((desc_num < 1) || (desc_num > softc->num_tx_desc))
   {
      printk("IA 5515: Redrive: transmit descriptor %X out of range\n", desc_num);
      return IA_FAIL;
   }

   softc->tx_backlog--;
   softc->vcctab[vci].tx_pending--;

#ifdef DBG_XMIT
   printk("IA_5515: Redriving vci %d with %d and %d \n ", vcc->vci,
              softc->tx_backlog, softc->vcctab[vci].tx_pending);
#endif

   ia_drive_tx(softc, desc_num, prqwp, vcc, skb);
}

/**/
/* Transmission complete interrupt handler.. This unblocks */
/* the transmit queue as needed... also free skbuffs       */

void ia_tx_tcq_intr(
ia_softc_t *softc)
{
   ffred_t   *ff = softc->brd_regs.ffred;
   ffred_t   *ffL = &softc->brd_regs_ll.ffred;
   ia_tx_buf_t *tx_buf;
   ulong_t   lockflags;
   ulong_t   ptr;
   ulong_t   ptr1;
   ulong_t   ptr2;
   ulong_t   fdc;
   int       desc_num;
   struct    atm_vcc *vcc = NULL;
   struct    sk_buff *skb;

   spin_lock_irqsave(&softc->xmitlock, lockflags);

/* The write pointer is where the Fred will post the descriptor */
/* number of the text buffer that it frees.. Our software       */
/* last_tcq_wr_ptr chases the actual tcq_wr_ptr around the      */
/* the pointer list.. Descriptor numbers lying between the      */
/* two represent buffers that are complete but which have skbs  */
/* attached that need to be returned.                           */

   ptr = ia_get32(&ff->tcq_wr_ptr) & 0xffff;
   while (softc->last_tcq_wr_ptr != ptr)
   {
      desc_num = ia_getw((ushort_t *)(softc->brd_regs.ffred_mem +
                                         softc->last_tcq_wr_ptr));
#ifdef DBG_XMIT
      printk("IA5515: last tcq is %x ... desc num = %x \n",
                                 softc->last_tcq_wr_ptr, desc_num);
#endif

      tx_buf = &softc->tx_buf[desc_num-1];
      if (tx_buf->skb != NULL)
      {
         skb = tx_buf->skb;
         vcc = tx_buf->vcc;
         softc->vcctab[vcc->vci].tx_active -= 1;
         softc->driver_stat.tx_skb_returned += skb->len;
#ifdef DBG_XMIT
         printk("skb is %x and vcc is %x \n", skb, vcc);
         printk("frd %d use %d dsc %d \n", skb->len, vcc->tx_inuse, desc_num);
#endif
         if (vcc != NULL)
         {
            if (vcc->pop != NULL)
               vcc->pop(vcc, skb);
            else
               dev_kfree_skb(skb);
         }
         else
         {
            printk("IA_5515: Null vcc address in free desc ???? \n");
            dev_kfree_skb(skb);
         }
      }
      else
      {
         printk("IA_5515: No skb adr in free desc %d\n", desc_num);
      }

      tx_buf->skb = NULL;
      tx_buf->vcc = NULL;

      softc->last_tcq_wr_ptr += 2;
      if (softc->last_tcq_wr_ptr > ffL->tcq_ed_adr)
         softc->last_tcq_wr_ptr = ffL->tcq_st_adr;

      if (softc->tx_backlog > 0)
         ia_redrive_tx(softc);
   }
   spin_unlock_irqrestore(&softc->xmitlock, lockflags);
}

/**/
int  ia_send5skb(
ia_softc_t     *softc,
struct atm_vcc *vcc,                  /* Pointer to VCC struct.   */
struct sk_buff *skb)                  /* Pointer to socket buffer */
{
   unsigned long lockflags;
   aal_parms_t   aal;
   int           rc;
   caddr_t       addr;
   ia_tx_buf_t   *tx_buf;
   ia_dle_list_t *list = &softc->list[TX_LIST];
   DLE           *dle;
   ushort_t      prqwp;
   int           desc_num;
   ulong_t       baddr;
   int           i;
   int           tc = 0;
   int           len;
   int           total_len;


#ifdef DBG_XMIT
   printk("IA 5515: send5skb  req for vci %d \n", vcc->vci);
   printk("IA 5515: buffer at %x len is %d \n" , skb->data, skb->len);
#endif

   spin_lock_irqsave(&softc->xmitlock, lockflags);
   softc->driver_stat.tx_skb_consumed += skb->len;

   aal.ap_vpci = vcc->vci;
   aal.ap_mid  = 0;
   aal.ap_orderq = 0;

/*
 * Find a free descriptor in the transmit complete queue
 * If none available, try to wait a while before returning failure
 */

   desc_num = -1;

   if ((vcc->vci < 16) || (softc->vcctab[vcc->vci].tx_active < 4))
       desc_num = ia_find_free_tx_desc(softc, &prqwp);

   if (desc_num == -1)
   {
      ATM_SKB(skb)->vcc = vcc;
      skb_queue_tail(&softc->vcctab[vcc->vci].tx_backlog, skb);
      softc->vcctab[vcc->vci].tx_pending += 1;
      softc->tx_backlog += 1;
      spin_unlock_irqrestore(&softc->xmitlock, lockflags);
      return IA_SUCCESS;
   }


#ifdef DBG_XMIT
   printk("IA 5515: findfree desc returned desc %d prq %x \n",
              desc_num, prqwp);
#endif

   if ((desc_num < 1) || (desc_num > softc->num_tx_desc))
   {
      printk("IA 5515: transmit descriptor %X out of range\n", desc_num);
      spin_unlock_irqrestore(&softc->xmitlock, lockflags);
      return IA_FAIL;
   }

   ia_drive_tx(softc, desc_num, prqwp,  vcc, skb);
   spin_unlock_irqrestore(&softc->xmitlock, lockflags);
   return IA_SUCCESS;

}

/*F
 * FUNCTION: ia_find_free_tx_desc
 *
 * DESCRIPTION:
 * This routine is called to find a free descriptor in the TCQ.
 *
 * ARGUMENTS:
 * softc    Device state structure
 * prqwp    Board address of PRQ write pointer (Output)
 *
 * ENVIRONMENT:
 * Called from ia_tx_packet
 *
 * RETURN:
 * -1 If transmit complete queue empty
 * else  Free descrptor number
 *
 * NOTES:
 *
 */
static int
ia_find_free_tx_desc (ia_softc_t *softc, ushort_t *prqwp)
{
   ffred_t     *ff = softc->brd_regs.ffred;
   ffred_t     *ffL = &softc->brd_regs_ll.ffred;
   ushort_t    desc_num;
   u_int       comp_code;
   ulong_t     ptr;
   ulong_t     ptr1;
   ulong_t     ptr2;
   ulong_t     fdc;
   ia_tx_buf_t *tx_buf;

/*
 * Any free descriptors available in TCQ?
 * The stuff commented out here is the old logic that was OK
 * for the original code that used double copy with dedicated
 * driver buffers..
 */

#if 0
   ptr = ia_get32 (&ff->tcq_wr_ptr);
   if ((ptr & 0xffff) == ffL->tcq_rd_ptr)
      return -1;
#endif

/* In the present version sk_bufs get bound to descriptors */
/* and they are unbound in the tx_complete interrupt       */
/* handling logic.. This leads to a potentially nasty race */
/* condition in which buffers appear on the hardware tcq   */
/* but haven't yet been consumed by the interrupt handler  */
/* If we were to try to consume one of those here, it would*/
/* cause a huge mess... so we dont!                        */

/* See if the read pointer has caught up with buffers that */
/* haven't been scavenged yet.                             */

   if (softc->last_tcq_wr_ptr == ffL->tcq_rd_ptr)
      return -1;

/* Get the next available descriptor number */

   desc_num = ia_getw ((ushort_t*)(softc->brd_regs.ffred_mem + ffL->tcq_rd_ptr));

   comp_code = desc_num >> 13;
   desc_num &= 0x1fff;

 /*
  * Completion code errors?
  */
   if (comp_code)
   {
      if (comp_code == F_CC_FLUSH)
      {
         softc->ffred_stat.cc_flush++;
         printk("IA_5515: ia_find_free_tx_desc: flush error: 0x%d", comp_code);
      }
      else if (comp_code == F_CC_MEMERR)
      {
         printk("IA_5515: ia_find_free_tx_desc: memory error: 0x%d", comp_code);
         softc->ffred_stat.ce_delink++;
      }
      else if (ia_debug > 0)
         printk("IA_5515: ia_find_free_tx_desc: unknown completion code: 0x%d", comp_code);
   }

/*
 * Update the TCQ read pointer (local and real)
 */

#ifdef DBG_XMIT
   printk("IA 5515: local copy of tcq_rd_ptr is %x \n", ffL->tcq_rd_ptr);
   printk("IA 5515: local copy of tcq_wr_ptr is %x \n", ffL->tcq_wr_ptr);
#endif

   ffL->tcq_rd_ptr += 2;
   if (ffL->tcq_rd_ptr > ffL->tcq_ed_adr)
      ffL->tcq_rd_ptr = ffL->tcq_st_adr;

   ia_put32 (&ff->tcq_rd_ptr, ffL->tcq_rd_ptr);

/* Here we check to see if there is yet another free descriptor */
/* in the tcq... If so we can unplug the queue.                 */

#ifdef USE_NETIF_CALLS
   ptr = ia_get32 (&ff->tcq_wr_ptr);
   ptr1 = (ptr & 0xffff)  - ffL->tcq_st_adr;
   ptr2 = ffL->tcq_rd_ptr - ffL->tcq_st_adr;

   if (ptr1 < ptr2)
      ptr1 += ffL->tcq_ed_adr - ffL->tcq_st_adr;

   fdc = (ptr1 - ptr2) >> 1;

   if (fdc < softc->tx_low_water)
   {
      if (netdev != 0)
      {
         softc->driver_stat.tx_waits++;
         netif_stop_queue(netdev);
      }
   }
#endif

/*
 * Put the descriptor index in the PRQ and increment the
 * local PRQ write pointer. Real PRQ write pointer will
 * be updated by the DLE
 */

   ia_putw ((ushort_t *)(softc->brd_regs.ffred_mem + ffL->prq_wr_ptr), desc_num);
   ffL->prq_wr_ptr += 2;
   if (ffL->prq_wr_ptr > ffL->prq_ed_adr)
      ffL->prq_wr_ptr = ffL->prq_st_adr;

   *prqwp = ffL->prq_wr_ptr;
   return desc_num;
}


/*F
 * FUNCTION: ia_setup_ffred
 *
 * DESCRIPTION:
 * This routine builds the CS header and sets up the buffer descriptor.
 *
 * ARGUMENTS:
 * softc    Device state structure
 * al    AAL parameters
 * desc     Pointer to the buffer descriptor
 * addr     Address of the buffer to build CS headers
 * len      Length of the packet
 *
 * ENVIRONMENT:
 * Called from ia_tx_packet
 *
 * RETURN:
 * Total length of the packet including the CS header.
 *
 * NOTES:
 *
 */
static size_t
ia_setup_ffred (
ia_softc_t *softc,
struct aal_parms *al,
f_buf_desc *desc,
caddr_t addr,
size_t len)
{
   CS_HEADER   *hdr;
   CS_TRAILER  *trl;
   CS5_TRAILER *trl5;
   int      total_len;
   u_int    pad_loc;

/*
 * Set the rate Queue (Disabled in the Solaris driver)
 */

#if 0
   if (ia_rates)
      ia_set_rate_queue (softc, al);
#endif 0

/*
 * Build CPCS header/trailer for AAL 5 packets
*/
   total_len = len + sizeof(CS5_TRAILER);
   total_len = ((total_len+47)/48)*48;
/* trl5 = (CS5_TRAILER *)(addr + total_len - sizeof(CS5_TRAILER)); */
   trl5 = (CS5_TRAILER *)(addr);
   trl5->control = 0;
   trl5->length = swap(len);

/*
 * Set up descriptor
 */
   ia_putw (&desc->desc_stat, F_AAL5 | F_EOM_EN | F_XD_INTT_EN |  F_D_APP_CRC32);
   ia_putw (&desc->vci,       al->ap_vpci);
   ia_putw (&desc->mid,       al->ap_mid);
   ia_putw (&desc->length,    (total_len*52)/48 - 4);
   ia_putw (&desc->order_q,   al->ap_orderq);

   return total_len;
}


/*F
 * FUNCTION: ia_set_rate_queue
 *
 * DESCRIPTION:
 * Process rate queue information.
 * Used with PVC upper layer.
 *
 * ARGUMENTS:
 * softc    Device state structure
 *      vci             vc index
 *      pcr             peak cell rate
 *
 * ENVIRONMENT:
 * Called from ia_tx_packet
 *
 * RETURN:
 *      IA_SUCCESS      on success.
 *      IA_FAILURE      on failure.
 *
 * NOTES:
 *
 */
int
ia_set_rate_queue(ia_softc_t *softc, u_int vci, u_int pcr)
{
   ffred_t     *ff = softc->brd_regs.ffred;
   f_vc_table  *vc = (f_vc_table *) (softc->brd_regs.ffred_mem +
                       F_VC_START);
   u_int       lblt, lbolt, rate_Mbs;
   int         i;

   vc += vci;

   if ( (pcr == 0) || (pcr >= softc->line_crs) ) {
    /* Default Rate Queue (Low Prority) */
      ia_andw (&vc->f_vcmode, ~VCM_RQ_MASK);
      ia_orw (&vc->f_vcmode, VCM_RQ_B0);
      return IA_SUCCESS;
   }

   for (i=0; i<NUM_RATE_Q; i++) {
      if (softc->ia_rq_cntl[i].rq_info == pcr)
         break;
   }

   /*
    * If a rate queue already exists for this rate, use it
    */
   if (i < NUM_RATE_Q) {
      /* Found a match */
      ia_andw (&vc->f_vcmode, ~VCM_RQ_MASK);
      ia_orw (&vc->f_vcmode, i << VCM_RQ_SHIFT);
      softc->ia_rq_cntl[i].lbolt = lbolt;
      return IA_SUCCESS;
   }

   /*
    * Find an unused queue.
    */
   for (i=0; i<NUM_RATE_Q; i++) {
      if (softc->ia_rq_cntl[i].rq_info == 0) {
         if ((pcr + softc->sum_mcr) > softc->line_crs)
            return IA_FAIL;
         else {
            softc->sum_mcr += pcr;
            break;
         }
      }
   }

   /*
    * If not found, find the least recently used entry
    */
   if (i >= NUM_RATE_Q) {
      i = 0;
      lblt = softc->ia_rq_cntl[0].lbolt;
      if (lblt > softc->ia_rq_cntl[1].lbolt) {
         i = 1;
         lblt = softc->ia_rq_cntl[1].lbolt;
      }
      if (lblt > softc->ia_rq_cntl[2].lbolt) {
         i = 2;
         lblt = softc->ia_rq_cntl[2].lbolt;
      }
      if (lblt > softc->ia_rq_cntl[3].lbolt)
         i = 3;

       if ((pcr + softc->sum_mcr - softc->ia_rq_cntl[i].rq_info)
                      > softc->line_crs)
          return IA_FAIL;
       else
          softc->sum_mcr += (pcr - softc->ia_rq_cntl[i].rq_info);
   }

   softc->ia_rq_cntl[i].lbolt = lbolt;
   softc->ia_rq_cntl[i].rq_info = pcr;

   rate_Mbs = (pcr * 53 * 8 * 27)/(26 * 1000 * 1000);

   /*
    * if rate_Mbs == 0, then find_R(rate_Mbs) will divide by zero,
    * so set rate_Mbs = 1
    */
   if (rate_Mbs == 0)
      rate_Mbs = 1;

   /*
    * Set the VC table to point to the proper rate queue
    * And set the rate queue
    */
   if (i == 0) {
      ia_put32 (&ff->rq_reg_a0, RQ_XOFF_EN | RQ_RQ_ENABLE | find_R(rate_Mbs));
      ia_andw (&vc->f_vcmode, ~VCM_RQ_MASK);
      ia_orw (&vc->f_vcmode, VCM_RQ_A0);
   }
   else if (i == 1) {
      ia_put32 (&ff->rq_reg_a1, RQ_XOFF_EN | RQ_RQ_ENABLE | find_R(rate_Mbs));
      ia_andw (&vc->f_vcmode, ~VCM_RQ_MASK);
      ia_orw (&vc->f_vcmode, VCM_RQ_A1);
   }
   else if (i == 2) {
      ia_put32 (&ff->rq_reg_a2, RQ_XOFF_EN | RQ_RQ_ENABLE | find_R(rate_Mbs));
      ia_andw (&vc->f_vcmode, ~VCM_RQ_MASK);
      ia_orw (&vc->f_vcmode, VCM_RQ_A2);
   }
   else {
      ia_put32 (&ff->rq_reg_a3, RQ_XOFF_EN | RQ_RQ_ENABLE | find_R(rate_Mbs));
      ia_andw (&vc->f_vcmode, ~VCM_RQ_MASK);
      ia_orw (&vc->f_vcmode, VCM_RQ_A3);
   }
   return IA_SUCCESS;
}


/*F
 * FUNCTION: ia_set_rate
 *
 * DESCRIPTION:
 * Set the rate queue as specified.
 * Used with LAN Emulation upper layer.
 *
 * ARGUMENTS:
 * softc    Device state structure
 * queue    Which queue
 *      val    prescaler + rq_preload
 *
 * ENVIRONMENT:
 * Called from ia_ioctl
 *
 * RETURN:
 * nothing
 *
 * NOTES:
 *
 */
void
ia_set_rate (ia_softc_t *softc, u_int queue, u_int val)
{
   ffred_t     *ff = softc->brd_regs.ffred;

   val = val & 0x3ff;

   if (queue == 0)
      ia_put32 (&ff->rq_reg_a0, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 1)
      ia_put32 (&ff->rq_reg_a1, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 2)
      ia_put32 (&ff->rq_reg_a2, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 3)
      ia_put32 (&ff->rq_reg_a3, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 4)
      ia_put32 (&ff->rq_reg_b0, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 5)
      ia_put32 (&ff->rq_reg_b1, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 6)
      ia_put32 (&ff->rq_reg_b2, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
   if (queue == 7)
      ia_put32 (&ff->rq_reg_b3, RQ_XOFF_EN | RQ_RQ_ENABLE | val);
}

/*F
 * FUNCTION: ia_ffred_init_vpi
 *
 * DESCRIPTION:
 * Initialize the VPI bits of the headers in the VC table
 *
 * ARGUMENTS:
 * softc    Device state structure
 *
 * ENVIRONMENT:
 * Called from ia_ioctl
 *
 * RETURN:
 * IA_SUCCESS  If FFRED VPI initialized sucessfully
 * IA_FAILURE  On failure
 *
 * NOTES:
 */
int
ia_ffred_init_vpi (ia_softc_t *softc)
{
   f_vc_table  *vc;
   int      i;

   vc = (f_vc_table *) (softc->brd_regs.ffred_mem + F_VC_START);

   for (i=0; i<F_NUM_VC; i++) {
      /* Add in predefined VPI */
      ia_orw (&vc->f_hdr01,   ((ia_tx_vpi & 0xff) << 4));

      vc++;
   }

   return   IA_SUCCESS;
}

#define CLK_PERIOD      40              /* In n-sec for 5515 */
static u_short
find_R(u_int rate)
{
        u_int  Rf, Rf0, Rf1, Rf2, Rf3;
        u_short   prescaler, preload;

   Rf0 = (255 - (424000)/(rate*CLK_PERIOD*4));
   Rf1 = (255 - (424000)/(rate*CLK_PERIOD*16));
   Rf2 = (255 - (424000)/(rate*CLK_PERIOD*64));
   Rf3 = (255 - (424000)/(rate*CLK_PERIOD*256));

        Rf = Rf0;
        prescaler = 0;

        if (modfunct(Rf, 100) > modfunct(Rf1, 100)) {
                Rf = Rf1;
                prescaler = 1;
        }

        if (modfunct(Rf, 100) > modfunct(Rf2, 100)) {
                Rf = Rf2;
                prescaler = 2;
        }

        if (modfunct(Rf, 100) > modfunct(Rf3, 100)) {
                Rf = Rf3;
                prescaler = 3;
        }

        preload = Rf;

        return ( (u_short) (prescaler << 8) | preload);
}

static u_int
modfunct(u_int a, u_int b)
{
        if (a > b)
                return (a - b);
        else
                return (b - a);
}

