/*------------------------------------------------------------------------------------------*\
 *   
 *   Copyright (C) 2006 AVM GmbH <fritzbox_info@avm.de>
 *
 *   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 version 2 of the License.
 *
 *   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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
\*------------------------------------------------------------------------------------------*/
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <net/pkt_sched.h>
#include <linux/delay.h>
#include <asm/mach_avm.h>
#include <asm/mach-ohio/led_hal.h>
#include <asm/io.h>

#if !defined(CONFIG_NETCHIP_ADM69961)
#define CONFIG_NETCHIP_ADM69961
#endif
#include <linux/avm_event.h>

#include "cpmac_if.h"
#include "cpmac_const.h"
#include "cpmac_debug.h"
#include "cpphy_const.h"
#include "cpphy_types.h"
#include "cpphy_cppi.h"
#include "cpphy_if.h"
#include "cpmac_reg.h"


/* swap mac addr with [5 - ()] */
#define DA(a, bit) ((unsigned int)(((a)[5-((bit)>>3)] >> ((bit) & 7)) & 1))

/*----------------------------------------------------------------------------------*\
    according to cpmac manual
\*----------------------------------------------------------------------------------*/
static unsigned int cpphy_cppi_hash_fun (unsigned char *a)
{
  unsigned int hash;

  hash  =  DA(a,0) ^ DA(a, 6) ^ DA(a,12) ^ DA(a,18) ^ DA(a,24) ^ DA(a,30) ^ DA(a,36) ^ DA(a,42);
  hash |= (DA(a,1) ^ DA(a, 7) ^ DA(a,13) ^ DA(a,19) ^ DA(a,25) ^ DA(a,31) ^ DA(a,37) ^ DA(a,43)) << 1;
  hash |= (DA(a,2) ^ DA(a, 8) ^ DA(a,14) ^ DA(a,20) ^ DA(a,26) ^ DA(a,32) ^ DA(a,38) ^ DA(a,44)) << 2;
  hash |= (DA(a,3) ^ DA(a, 9) ^ DA(a,15) ^ DA(a,21) ^ DA(a,27) ^ DA(a,33) ^ DA(a,39) ^ DA(a,45)) << 3;
  hash |= (DA(a,4) ^ DA(a,10) ^ DA(a,16) ^ DA(a,22) ^ DA(a,28) ^ DA(a,34) ^ DA(a,40) ^ DA(a,46)) << 4;
  hash |= (DA(a,5) ^ DA(a,11) ^ DA(a,17) ^ DA(a,23) ^ DA(a,29) ^ DA(a,35) ^ DA(a,41) ^ DA(a,47)) << 5;
  return hash;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
static void cpphy_cppi_add_hash (cpphy_cppi_t *cppi, unsigned char *MacAddress)
{
  unsigned int HashValue;
  unsigned int HashBit;

  HashValue = cpphy_cppi_hash_fun (MacAddress);
  if (HashValue < 32) {
    HashBit = (1 << HashValue);
    cppi->hash1 |= HashBit;
  } else {
    HashBit = (1 << (HashValue-32));
    cppi->hash2 |= HashBit;
  }
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
void cpphy_cppi_set_multi_promiscous (cpphy_cppi_t *cppi, unsigned int multi, unsigned int promiscous)
{
  struct net_device *p_dev = cppi->cpmac_priv->owner;

  if (cppi->hw_state != CPPHY_HW_ST_OPENED) {
    DEB_WARN("cpphy_cppi_set_multi_promiscous, ill state %u\n",
        cppi->hw_state);
  } else {
    unsigned int RxMbpEnable, i;

    cppi->hash1 = 0;
    cppi->hash2 = 0;
    RxMbpEnable = CPMAC_RX_MBP_ENABLE (p_dev->base_addr);
    RxMbpEnable &= ~(RX_CAF_EN | RX_MULT_EN);
    if (multi) {
      RxMbpEnable |= RX_MULT_EN;
      if (multi == 1) {
        cppi->hash1 = 0xffffffff;
        cppi->hash2 = 0xffffffff;
      } else {  /* list of multicast addresses */
        struct dev_mc_list *p_dmi = p_dev->mc_list;

        for (i = 0; i < p_dev->mc_count; i++, p_dmi = p_dmi->next) {
          DEB_INFO("cpphy_cppi_set_multi_promiscous, add %02x:%02x:%02x:%02x:%02x:%02x\n",
              p_dmi->dmi_addr[0],
              p_dmi->dmi_addr[1],
              p_dmi->dmi_addr[2],
              p_dmi->dmi_addr[3],
              p_dmi->dmi_addr[4],
              p_dmi->dmi_addr[5]);
          cpphy_cppi_add_hash (cppi, p_dmi->dmi_addr); 
        }
      }
    }
    if (promiscous) {
      RxMbpEnable |= RX_CAF_EN;
    }
    CPMAC_RX_MBP_ENABLE (p_dev->base_addr) = RxMbpEnable;
    CPMAC_MACHASH1 (p_dev->base_addr) = cppi->hash1;
    CPMAC_MACHASH2 (p_dev->base_addr) = cppi->hash2;
    DEB_TRC("cpphy_cppi_set_multi_promiscous, MBP_ENABLE 0x%08X\n", 
        CPMAC_RX_MBP_ENABLE (p_dev->base_addr));
  }
}

/*----------------------------------------------------------------------------------*\
  add counter to stats (may be preinitialized by cpphy_mdio_update_hw_status())
\*----------------------------------------------------------------------------------*/
void cpphy_cppi_update_hw_status (cpphy_cppi_t *cppi, struct net_device_stats *stats)
{
  struct net_device *p_dev = cppi->cpmac_priv->owner;
  unsigned int rx_crc_errors, rx_frame_errors, rx_length_errors, rx_over_errors,
      rx_fifo_errors, rx_missed_errors, tx_carrier_errors, tx_fifo_errors, collisions;

  stats->multicast += CPMAC_RXMULTICASTFRAMES (p_dev->base_addr);

  rx_crc_errors = CPMAC_RXCRCERRORS (p_dev->base_addr);
  stats->rx_crc_errors += rx_crc_errors;
  /* recv'd frame alignment error */
  rx_frame_errors = CPMAC_RXALIGNCODEERRORS (p_dev->base_addr);
  stats->rx_frame_errors += rx_frame_errors;
  rx_length_errors = CPMAC_RXJABBERFRAMES (p_dev->base_addr);
  rx_length_errors += CPMAC_RXUNDERSIZEDFRAMES (p_dev->base_addr);
  rx_length_errors += CPMAC_RXFRAGMENTS (p_dev->base_addr);
  stats->rx_length_errors += rx_length_errors;
  /* receiver ring buff overflow */
  rx_over_errors = CPMAC_RXDMAOVERRUNS (p_dev->base_addr);
  stats->rx_over_errors += rx_over_errors;
  /* recv'r fifo overrun */
  rx_fifo_errors = CPMAC_RXSOFOVERRUNS (p_dev->base_addr) +
    CPMAC_RXMOFOVERRUNS (p_dev->base_addr) -
    rx_over_errors;
  stats->rx_fifo_errors += rx_fifo_errors;
  /* receiver missed packet: to check */
  rx_missed_errors = CPMAC_RXFILTEREDFRAMES (p_dev->base_addr);
  stats->rx_missed_errors += rx_missed_errors;

  DEB_TRC("cpphy_cppi_update_hw_status, %u, %u, %u, %u, %u, %u, %u, %u, %u\n",
    CPMAC_RXCRCERRORS (p_dev->base_addr),
    CPMAC_RXALIGNCODEERRORS (p_dev->base_addr),
    CPMAC_RXJABBERFRAMES (p_dev->base_addr),
    CPMAC_RXUNDERSIZEDFRAMES (p_dev->base_addr),
    CPMAC_RXFRAGMENTS (p_dev->base_addr),
    CPMAC_RXDMAOVERRUNS (p_dev->base_addr),
    CPMAC_RXSOFOVERRUNS (p_dev->base_addr),
    CPMAC_RXMOFOVERRUNS (p_dev->base_addr),
    CPMAC_RXFILTEREDFRAMES (p_dev->base_addr));
   
  stats->rx_errors += 
    rx_length_errors +
    rx_crc_errors +
    rx_frame_errors +
    rx_missed_errors;

  stats->rx_dropped +=
    rx_fifo_errors +
    rx_over_errors;

  /* detailed tx_errors */
  tx_carrier_errors = CPMAC_TXCARRIERSENSEERRORS (p_dev->base_addr);
  stats->tx_carrier_errors += tx_carrier_errors;
  tx_fifo_errors = CPMAC_TXUNDERRUN (p_dev->base_addr);
  stats->tx_fifo_errors += tx_fifo_errors;

  /*--- unsigned long tx_aborted_errors; ---*/
  /*--- unsigned long tx_heartbeat_errors; ---*/
  /*--- unsigned long tx_window_errors; ---*/

  collisions = 
    CPMAC_TXEXCESSIVECOLLISIONS (p_dev->base_addr) +
    CPMAC_TXLATECOLLISIONS (p_dev->base_addr);
  stats->collisions += collisions;
  stats->tx_errors +=
    tx_carrier_errors +
    tx_fifo_errors +
    collisions;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
static void *cpphy_cppi_malloc_buffer (unsigned int size, 
    unsigned int tot_buf_size, unsigned int tot_reserve_bytes, 
    struct net_device *p_dev, struct sk_buff **skb)
{
  if ((*skb = dev_alloc_skb(tot_buf_size))) {
    (*skb)->dev = p_dev;
    skb_reserve (*skb, tot_reserve_bytes);
    return skb_put (*skb, size);
  }
  return NULL;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
void cpphy_cppi_free_rcb (cpphy_cppi_t *cppi)
{
  cpphy_rcb_t *rcb;

  /* Free Rx data buffers attached to descriptors, if necessary */
  rcb = cppi->RxPrevEnqueue;
  do {
    if (rcb->skb) {
      dev_kfree_skb_any(rcb->skb);
      rcb->skb = 0;
    }
    rcb = rcb->Next;
  } while (rcb && (rcb != cppi->RxPrevEnqueue));

  /* free up all desciptors at once */
  kfree (cppi->RcbStart);
  cppi->RcbStart = 0;
}


/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
void cpphy_cppi_free_tcb (cpphy_cppi_t *cppi)
{
  /* free all descriptors at once */
  kfree (cppi->TcbStart);
  cppi->TcbStart = 0;
}


/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
static cpmac_err_t cpphy_cppi_rx_teardown_int(cpphy_cppi_t *cppi)
{
  unsigned int base = ((struct net_device *)cppi->cpmac_priv->owner)->base_addr;

  /* check to see if the interrupt is a teardown interrupt */
  if (((CPMAC_RX_INT_ACK( base , 0 )) & TEARDOWN_ACK) == TEARDOWN_ACK) {
    /* finish channel teardown */

    /* Free channel resources on a FULL teardown */
    if (cppi->RxTeardownPending & CPPHY_FULL_TEARDOWN) {
      cpphy_cppi_free_rcb (cppi);
    }

    /* write completion pointer */
    (CPMAC_RX_INT_ACK( base , 0 )) = TEARDOWN_ACK;

    /* no longer pending teardown */
    cppi->RxTeardownPending &= ~CPPHY_RX_TEARDOWN;
    cppi->RxOpen = 0;
    CPMAC_RX_INTMASK_CLEAR (base) = (1<<0);
    cppi->RxTeardownPending = 0;

    return CPMAC_ERR_NOERR;
  }
  return CPMAC_ERR_NO_TEARDOWN;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
static cpmac_err_t cpphy_cppi_tx_teardown_int (cpphy_cppi_t *cppi)
{
  unsigned int base = cppi->cpmac_priv->owner->base_addr;
  cpphy_tcb_t *Curr;

  if ((CPMAC_TX_INT_ACK (base, cppi->TxChannel) & TEARDOWN_ACK) == TEARDOWN_ACK) {
    Curr = cppi->TxCurrDequeue;
    while (Curr->skb) {
      cpphy_if_tx_complete(cppi, (struct sk_buff *) Curr->skb, CPMAC_ERR_TEARDOWN);
      Curr->skb = NULL;
      Curr = (cpphy_tcb_t *) Curr->Next;
    }
    cppi->TxCurrDequeue = Curr;

    /* teardown for close */
    if (cppi->TxTeardownPending & CPPHY_FULL_TEARDOWN) {
      cpphy_cppi_free_tcb (cppi);
    }

    /* write completion pointer */
    (CPMAC_TX_INT_ACK(base, cppi->TxChannel)) = TEARDOWN_ACK;

    /* no longer pending teardown */
    cppi->TxTeardownPending &= ~CPPHY_TX_TEARDOWN;
    if (cppi->TxTeardownPending & CPPHY_FULL_TEARDOWN) {
      cppi->TxOpen = 0;
    }
    if (cppi->TxTeardownPending & CPPHY_FULL_TEARDOWN) {
      CPMAC_TX_INTMASK_CLEAR(base) = (1 << cppi->TxChannel);
    }
    cppi->TxDmaActive = 0;
#ifdef CPMAC_TX_TIMEOUT
    if (cppi->TxTeardownPending & CPPHY_CALLBACK_TEARDOWN) {
      cppi->TxTeardownPending = 0;
      cpmac_if_teardown_complete (cppi->cpmac_priv);
    } else {
#endif
      cppi->TxTeardownPending = 0;
#ifdef CPMAC_TX_TIMEOUT
    }
#endif
    return CPMAC_ERR_NOERR;
  }
  return CPMAC_ERR_NO_TEARDOWN;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
static cpmac_err_t cpphy_cppi_rx_return(cpphy_cppi_t *cppi, struct sk_buff *skb)
{
  char *pBuf;
  cpmac_err_t ret = CPMAC_ERR_NOERR;
  cpphy_rcb_t *rcb, *rcb_last;
  int base = ((struct net_device *)cppi->cpmac_priv->owner)->base_addr;

  if (cppi->hw_state != CPPHY_HW_ST_OPENED) {
    return CPMAC_ERR_CHAN_NOT_OPEN;
  }

  rcb_last = cppi->RxPrevEnqueue;
  rcb = rcb_last->Next;

  if (skb) {
    rcb->skb = skb;
    rcb->BufPtr = CPPHY_VIRT_TO_PHYS (skb->data);
  } else {
    pBuf = (char *) cpphy_cppi_malloc_buffer (CPPHY_MAX_RX_BUFFER_SIZE,
      CPPHY_TOTAL_RX_BUFFER_SIZE, CPPHY_TOTAL_RX_RESERVED,
      cppi->cpmac_priv->owner, &rcb->skb);
    if (pBuf) {
      /* malloc succeeded, requeue the RCB to the hardware */
      rcb->BufPtr = CPPHY_VIRT_TO_PHYS (pBuf);
    } else {
      /* malloc failed, add this RCB to Needs Buffer List */
      cppi->CurrNeedCount++;
      ret = CPMAC_ERR_NEED_BUFFER;
    }
  }
  if (ret == CPMAC_ERR_NOERR) {
    unsigned int dma_running;
    rcb->HNext = 0;
    rcb->Off_BLen = CPPHY_MAX_RX_BUFFER_SIZE;
    rcb->mode = CB_OWNERSHIP_BIT;
    dma_cache_wback_inv((unsigned long)(void *)rcb, 16);

    /*----------------------------------------------------------------------------------*\
     * If the ownership bit of the last rcb is set, DMA is still running.               *
    \*----------------------------------------------------------------------------------*/
    dma_cache_inv((unsigned long)(void *)rcb_last, 16);
    dma_running = rcb_last->mode & CB_OWNERSHIP_BIT ? 1 : 0;

    /*----------------------------------------------------------------------------------*\
     * Add the rcb to the existing list after the last rcb                              *
     * Attention: This may destroy the ownership bit status, if the DMA controller      *
     *            changed it in nearly the same moment!
    \*----------------------------------------------------------------------------------*/
    rcb_last->HNext = CPPHY_VIRT_TO_PHYS(rcb);
    dma_cache_wback_inv((unsigned long)(void *)rcb_last, 16);
    cppi->RxPrevEnqueue = rcb;
    /* Add one free buffer to FreeBuffer count of CPMAC */
    CPMAC_RX_FREEBUFFER(base, 0) = 1; 

    if(dma_running) { /* We think DMA is still running */
        /* Check the Rx DMA list head to see if we are really still running */
        if((CPMAC_RX_HDP (base, 0)) != 0) {
            /* The rcb is chained and DMA is still running. Everything okay. */
            return ret;
        }

        /* DMA stopped. Did it complete the current rcb as well? */
        if(!(rcb->mode & CB_OWNERSHIP_BIT)) {
            /* Current rcb is used as well. Correct the possibly wrong mode of the *\
             * last packet to allow the GC to work correctly. Transmitting is      *
            \* finished after that.                                                */
            rcb_last->mode  = CB_SOF_BIT | CB_EOF_BIT;
            /*--- cpphy_if_gc_tx_queue(cppi); ---*/
            return ret;
        }
    }
    /* At this point DMA is not running. Set it up again with rcb as the start */
    (CPMAC_RX_HDP(base, 0)) = CPPHY_VIRT_TO_PHYS (rcb);
  }

  return ret;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
static void cpphy_cppi_needs_check (cpphy_cppi_t *cppi)
{
  cpmac_err_t ret;

  while (cppi->CurrNeedCount) {
    ret = cpphy_cppi_rx_return (cppi, NULL);
    if (ret == CPMAC_ERR_NOERR) {
      cppi->CurrNeedCount--;
    } else {
      if (ret == CPMAC_ERR_NEED_BUFFER) {
        /* revert the increment in cpphy_cppi_rx_return () */
        cppi->CurrNeedCount--;
      }
      break;
    }
  }
}

/*----------------------------------------------------------------------------------*\
  main handler of receive interrupts
\*----------------------------------------------------------------------------------*/
cpmac_err_t cpphy_cppi_rx_int (cpphy_cppi_t *cppi)
{
  cpphy_rcb_t *rcb, *rcb_last, *rcb_ack;
  unsigned int mode;
  int base = ((struct net_device *)cppi->cpmac_priv->owner)->base_addr;

  if (cppi->CurrNeedCount) {
    cpphy_cppi_needs_check (cppi);
  }
  /* Handle teardown interrupt */
  if (cppi->RxTeardownPending) {
    if (cpphy_cppi_rx_teardown_int (cppi) == CPMAC_ERR_NOERR) {
      return CPMAC_ERR_NOERR;
    }
  }

  rcb = cppi->RxCurrDequeue;
  rcb_ack = (cpphy_rcb_t *)CPPHY_PHYS_TO_VIRT_CACHE(CPMAC_RX_INT_ACK (base , 0));
  mode = *((volatile unsigned int *) CPPHY_VIRT_TO_VIRT_NO_CACHE(&rcb_ack->mode));
  if (!(mode & CB_OWNERSHIP_BIT)) {
    struct sk_buff *skb;

    /* Write the completion pointer */
    (CPMAC_RX_INT_ACK (base, 0)) = CPPHY_VIRT_TO_PHYS(rcb_ack);
    do {
      skb = (struct sk_buff *)rcb->skb;
      rcb->skb = NULL;

      mode = *((volatile unsigned int *) CPPHY_VIRT_TO_VIRT_NO_CACHE(&rcb->mode));
      if ((cppi->CurrNeedCount <= cppi->MaxNeedCount) && 
          (mode & (CB_SOF_BIT | CB_EOF_BIT)) == (CB_SOF_BIT | CB_EOF_BIT)) {
        cpmac_if_data_from_phy (cppi->cpmac_priv, skb, mode & CB_SIZE_MASK);
        skb = NULL;
      } else {
        /*--- DEB_WARN("cpphy_cppi_rx_int, ignore pkt with ill flags: %#x\n", mode); ---*/
        DEB_INFO("cpphy_cppi_rx_int, ignore pkt with ill flags: %#x\n", mode);
        cppi->cpmac_priv->local_stats_rx_errors++;
      }
      cpphy_cppi_rx_return (cppi, skb);
      rcb_last = rcb;
      rcb = rcb->Next;
    } while (rcb_last != rcb_ack);
    cppi->RxCurrDequeue = rcb;
  } else {
    /* should never happen: rx int while no buffer received */
    DEB_ERR("cpphy_cppi_rx_int, ill rcb_ack: %p (%p,%p)\n",
        rcb_ack, cppi->RxFirst, cppi->RxLast);
  }
  return CPMAC_ERR_NOERR;
}

/*----------------------------------------------------------------------------------*\
  main handler of transmit complete interrupts
\*----------------------------------------------------------------------------------*/
int cpphy_cppi_tx_int (cpphy_cppi_t *cppi)
{
  cpphy_tcb_t *tcb, *tcb_ack, *tcb_last;
  unsigned int mode;
  unsigned int base = cppi->cpmac_priv->owner->base_addr;

  /* Handle teardown interrupt */
  if (cppi->TxTeardownPending) {
    if (cpphy_cppi_tx_teardown_int (cppi) == CPMAC_ERR_NOERR) {
      return CPMAC_ERR_NOERR;
    }
  }
  tcb = cppi->TxCurrDequeue;
  tcb_ack = (cpphy_tcb_t *)CPPHY_PHYS_TO_VIRT_CACHE(CPMAC_TX_INT_ACK (base, cppi->TxChannel));
  mode = *((volatile unsigned int *) CPPHY_VIRT_TO_VIRT_NO_CACHE(&tcb_ack->mode));
  if (!(mode & CB_OWNERSHIP_BIT)) {
    /* Write the completion pointer */
    (CPMAC_TX_INT_ACK (base, cppi->TxChannel)) = CPPHY_VIRT_TO_PHYS(tcb_ack);

    /* if there is an error trace concerning locking: start here */
    if (*((volatile unsigned int *)&tcb_ack->HNext)) {
      /* check possible race condition with dma */
      if (mode & CB_EOQ_BIT) {
        /* restart dma if halted */
        (CPMAC_TX_HDP (base, cppi->TxChannel)) = tcb_ack->HNext;
      }
    } else {
      cppi->TxDmaActive = 0;    /* signal cpphy_if_data_to_phy() to restart dma */
      if (*((volatile unsigned int *)&tcb_ack->HNext)) {
        /* this hopefully never happens, because of higher prio of tasklet */
        DEB_ERR("cpphy_cppi_tx_int, need locking with cpphy_if_data_to_phy()\n");
      }
    }
    /* if there is an error trace concerning locking: end here */

    do {
      cpphy_if_tx_complete(cppi, (struct sk_buff *)tcb->skb, CPMAC_ERR_NOERR);
      tcb->skb = NULL; /* tcb may be reused immediately */
      tcb_last = tcb;
      tcb = (cpphy_tcb_t *) tcb->Next;
    } while (tcb_last != tcb_ack);
    cppi->TxCurrDequeue = tcb;
    {
        struct net_device *p_dev = cppi->cpmac_priv->owner;
        struct Qdisc *q = p_dev->qdisc;

        if(netif_queue_stopped(p_dev)) {
            netif_wake_queue(p_dev);
        } else if(q->q.qlen) {
            netif_schedule(p_dev);
        }
    }
  } else {
    /* should never happen: tx int while no buffer completed */
    DEB_ERR("cpphy_cppi_tx_int, ill tcb_ack: %p (%p,%p)\n",
        tcb_ack, cppi->TxFirst, cppi->TxLast);
  }
  return CPMAC_ERR_NOERR;
}

/*----------------------------------------------------------------------------------*\
  Transmit buffer descriptor allocation
\*----------------------------------------------------------------------------------*/
static cpmac_err_t cpphy_cppi_init_tcb (cpphy_cppi_t *cppi, int Num)
{
  int i;
  cpphy_tcb_t *pTcb = NULL;
  char *AllTcb;
  int  tcbSize, size_malloc;
  cpmac_err_t ret = CPMAC_ERR_NOERR;

  tcbSize = (sizeof (cpphy_tcb_t) + 0xf) & ~0xf;
  size_malloc = (tcbSize * Num) + 0xf;

  /* if the memory has already been allocated, simply reuse it! */
  if (!(AllTcb = cppi->TcbStart)) {
    /* malloc all TCBs at once */
    if (!(AllTcb = (char *)kmalloc (size_malloc, GFP_KERNEL))) {
      ret = CPMAC_ERR_NOMEM;
    } else {
      memset (AllTcb, 0, size_malloc);
      /* keep this address for freeing later */
      cppi->TcbStart = AllTcb;
    }
  }
  if (AllTcb) {
    cpphy_tcb_t *Next;

    /* align to cache line */
    AllTcb = (char *)(((unsigned int)AllTcb + 0xf) & ~0xf);

    /* design: descriptors as logical ring buffer (with HW next ptr NULL terminated) */
    /* TxPrevEnqueue: previously enqueued tx skb (dma active) */
    /* TxCurrDequeue: next tx skb to be send completed */
    cppi->TxCurrDequeue = (cpphy_tcb_t *)AllTcb;
    cppi->TxFirst = cppi->TxCurrDequeue;
    cppi->TxPrevEnqueue = (cpphy_tcb_t *)(AllTcb + ((Num-1)*tcbSize));
    cppi->TxLast = cppi->TxPrevEnqueue;
    cppi->TxDmaActive = 0;
    atomic_set(&cppi->dma_send_running, 0);
    atomic_set(&cppi->dequeue_running, 0);
    
    cppi->TxFirstFree = kmalloc(sizeof(cpphy_tcb_t), GFP_ATOMIC);
    if(cppi->TxFirstFree == NULL) {
      DEB_ERR("No memory for tcb TxFirstFree!\n");
      return CPMAC_ERR_NOMEM;
    }
    memset((char *) cppi->TxFirstFree, 0, sizeof(cpphy_tcb_t));
    if(((unsigned int) cppi->TxFirst) & 0xf) /* FIXME */
      DEB_TEST("Attention! Allocated tcb at unaligned address!\n");
    cppi->TxLastFree = cppi->TxFirstFree;
    
    pTcb = cppi->TxPrevEnqueue;
    pTcb->mode = CB_EOQ_BIT;   /* design: start DMA initially */
    Next = cppi->TxCurrDequeue;   /* build a ring buffer */
    for (i=0; i<Num; i++) {
      pTcb->Next = Next;
      Next = pTcb;
      pTcb = (cpphy_tcb_t *)(((unsigned char *)pTcb) - tcbSize);
    }
  }
  return ret;
}

/*----------------------------------------------------------------------------------*\
  Allocation of receive buffer descriptors and data buffers
\*----------------------------------------------------------------------------------*/
static cpmac_err_t cpphy_cppi_init_rcb(cpphy_cppi_t *cppi, int Num, int Size)
{
  int i;
  cpphy_rcb_t *pRcb, *Next;
  char *pBuf;
  char *AllRcb;
  int rcbSize;
  int size_malloc;
  cpmac_err_t ret = CPMAC_ERR_NOERR;
  unsigned int base = cppi->cpmac_priv->owner->base_addr;

  rcbSize = (sizeof (cpphy_rcb_t) + 0xf) &~ 0xf;
  size_malloc = (rcbSize * Num) + 0xf;

  if (!(AllRcb = (char *)kmalloc (size_malloc, GFP_KERNEL))) {
    ret = CPMAC_ERR_NOMEM;
  } else {
    memset (AllRcb, 0, size_malloc);
    /* keep this address for freeing later */
    cppi->RcbStart = AllRcb;

    /* align to cache line */
    AllRcb = (char *)(((unsigned int)AllRcb + 0xf) & ~0xf);

    cppi->MaxNeedCount = Num - 1;   /* need one complete buff to still get rx int */
    cppi->RxCurrDequeue = (cpphy_rcb_t *)AllRcb;
    cppi->RxFirst = cppi->RxCurrDequeue;
    cppi->RxPrevEnqueue = (cpphy_rcb_t *)(AllRcb + ((Num-1)*rcbSize));
    cppi->RxLast = cppi->RxPrevEnqueue;

    /* design: descriptors as logical ring buffer (with HW next ptr ring NULL terminated) */
    Next = cppi->RxCurrDequeue;
    pRcb = cppi->RxPrevEnqueue;
    for (i=0; i<Num; i++) {
      pRcb->Next = Next;
      pRcb->HNext = i ? CPPHY_VIRT_TO_PHYS(Next) : 0;
      pRcb->Off_BLen = CPPHY_MAX_RX_BUFFER_SIZE;
      pRcb->mode = CB_OWNERSHIP_BIT;
      pBuf = (char *) cpphy_cppi_malloc_buffer (Size, 
          CPPHY_TOTAL_RX_BUFFER_SIZE, CPPHY_TOTAL_RX_RESERVED,
          cppi->cpmac_priv->owner, &pRcb->skb);
      if (!pBuf) {
        cpphy_cppi_free_rcb (cppi);
        return CPMAC_ERR_NO_BUFFER;
      }
      pRcb->BufPtr = CPPHY_VIRT_TO_PHYS(pBuf);
      dma_cache_wback_inv((unsigned long)(void *)pRcb, 16);
      Next = pRcb;
      pRcb = (cpphy_rcb_t *)(((unsigned char *)pRcb) - rcbSize);
    }

    /* Set flow control parameters of CPMAC */
    /* To set freebuffer count to zero we have to create an overflow of the incremental register */
    CPMAC_RX_FREEBUFFER(base, 0) = 0x10000 - CPMAC_RX_FREEBUFFER(base, 0);
    CPMAC_RX_FREEBUFFER(base, 0) = Num - 1;
    CPMAC_RX_FLOWTHRESH(base, 0) = 5;

    /* rx buffer chain to dma */
    (CPMAC_RX_HDP (cppi->cpmac_priv->owner->base_addr, 0)) = 
      CPPHY_VIRT_TO_PHYS(cppi->RxFirst);
  }
  return ret;
}

/*----------------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------------*/
cpmac_err_t cpphy_cppi_start_dma(cpphy_cppi_t *cppi)
{
  struct net_device *p_dev = (struct net_device *)cppi->cpmac_priv->owner;
  cpmac_err_t ret = CPMAC_ERR_NOERR;

  DEB_TRC("cpphy_channel_start_dma, init\n");
  if (cppi->hw_state < CPPHY_HW_ST_OPENED) {
    /* hardware has never been opened, leave immediately */
    DEB_WARN("cpphy_cppi_start_dma, hw not initialized (state %u)\n",
        cppi->hw_state);
    ret = CPMAC_ERR_HW_NOT_INITIALIZED;
  } else {
    if (cppi->TxOpen) {
      DEB_WARN("cpphy_cppi_start_dma, tx, already open\n");
    } else {
      /* Initialize buffer memory for the tx handling */
      if ((ret = cpphy_cppi_init_tcb (cppi, CPPHY_MAX_TX_BUFFERS)) == CPMAC_ERR_NOERR) {
        CPMAC_TX_INTMASK_SET (p_dev->base_addr) = (1 << cppi->TxChannel);
        cppi->TxOpen = 1;
      }
    }
    if (ret == CPMAC_ERR_NOERR) {
      if (cppi->RxOpen) {
        DEB_WARN("cpphy_cppi_start_dma, rx, already open\n");
      } else {
        /* Initialize buffer memory for the rx handling */
        if ((ret = cpphy_cppi_init_rcb (cppi, CPPHY_MAX_RX_BUFFERS, CPPHY_MAX_RX_BUFFER_SIZE)) ==
            CPMAC_ERR_NOERR) {
          CPMAC_RX_INTMASK_SET(p_dev->base_addr)  = (1 << 0);
          cppi->RxOpen = 1;
        } else if (cppi->TxOpen) {
          cppi->TxOpen = 0;
          cpphy_cppi_free_tcb (cppi);
        }
      }
    }
  }
  return ret;
}

/*----------------------------------------------------------------------------------*\
  Start of Teardown. 
\*----------------------------------------------------------------------------------*/
cpmac_err_t cpphy_cppi_teardown (cpphy_cppi_t *cppi, unsigned int Mode)
{
  int timeout=0;
  struct net_device *p_dev = cppi->cpmac_priv->owner;

  if (Mode & CPPHY_TX_TEARDOWN) {
    if (cppi->TxTeardownPending) {
      DEB_WARN("cpphy_cppi_teardown, tx already pending\n");
    } else {
      if (cppi->hw_state < CPPHY_HW_ST_OPENED) {
        /* hardware has never been opened, leave immediately */
        DEB_INFO("cpphy_cppi_teardown, tx, hw in state %u\n",
            cppi->hw_state);
      } else if (!cppi->TxOpen) {
        DEB_WARN("cpphy_cppi_teardown, tx, already torn down\n");
      } else {
        /* set teardown flag for int handler */
        cppi->TxTeardownPending = Mode;

        /* request TX channel teardown */
        (CPMAC_TX_TEARDOWN (p_dev->base_addr)) = cppi->TxChannel;

        /* if mode is blocking: wait until teardown has completed */
        if (Mode & CPPHY_BLOCKING_TEARDOWN) {
          timeout = 0;
          while (cppi->TxTeardownPending & CPPHY_TX_TEARDOWN) {
            msleep_interruptible (2);
            timeout++;
            if (timeout > 100) {
              DEB_WARN("cpphy_cppi_teardown, break tx wait\n");
              break;
            }
          }
        }
      }
    }
  }

  if (Mode & CPPHY_RX_TEARDOWN) {
    if (cppi->RxTeardownPending) {
      DEB_WARN("cpphy_cppi_teardown, rx already pending\n");
    } else {
      if (cppi->hw_state < CPPHY_HW_ST_OPENED) {
        DEB_INFO("cpphy_cppi_teardown, rx, hw in state %u\n",
            cppi->hw_state);
      } else if (!cppi->RxOpen) {
        /* hardware has never been opened, leave immediately */
        DEB_WARN("cpphy_cppi_teardown, rx, already torn down\n");
      } else {
        /* set teardown flag for int handler */
        cppi->RxTeardownPending = Mode;

        /* request RX channel teardown */
        CPMAC_RX_TEARDOWN(p_dev->base_addr) = 0;

        /* if mode is blocking: wait until teardown has completed */
        if (Mode & CPPHY_BLOCKING_TEARDOWN) {
          timeout = 0;
          while (cppi->RxTeardownPending & CPPHY_RX_TEARDOWN) {
            msleep_interruptible (2);
            timeout++;
            if (timeout > 100) {
              DEB_WARN("cpphy_cppi_teardown, break rx wait\n");
              break;
            }
          }
        }
      }
    }
  }
  return CPMAC_ERR_NOERR;
}

