/******************************************************************************
 *
 * proatm.c 
 *
 * Date: 02/09/2026
 * Version: 3.70
 *
 * LINUX Device driver supporting UBR, CBR, VBR and ABR for PROATM cards 
 *
 *
 * IMPORTANT:   This code is derived from the Linux nicstar driver 
 *              from Rui Prior. Large portions of this driver still exist here.
 *
 * Author: Christian Bucari: bucari@prosum.net
 * Copyright (c) 2002 - 2018 Prosum
 *
 * 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. 
 * 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.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 ******************************************************************************
 * Note on VBR usage:
 *
 * VBR QoS is characterized by pcr, scr and mbs parameters.
 * So far, the atm_trafprm structure of linux/atm.h does not include neither 
 * scr nor mbs. We could provide a patch to modify atm_traf_prm, however we 
 * found simpler to reuse existing parameters. This makes the job without 
 * impacting other ATM layers and drivers.
 * So when using VBR VC's, enter the pcr value into max_pcr, the scr value into
 * pcr and the the mbs value into min_pcr.
 * This VBR implementation is intended mostly for writing ATM  applications 
 * that manage the qos structure directly. Original Linux-atm tools do not 
 * provide VBR support but Prosum provides a slightly modified linux-atm 
 * package supporting the VBR QoS. 
 *
 */

#undef DEBUG

/* Header files ***************************************************************/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/atmdev.h>
#include <linux/atm.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/bitops.h>
#include <linux/workqueue.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/uio.h>
#include "proatm.h"
#include "proatm_suni.h"
#include "proatm_rtbl.c"
#include "proatm_suni.c"

/* Configurable parameters ****************************************************/

#undef GENERAL_DEBUG
#undef EXTRA_DEBUG

#undef PHY_LOOPBACK  

#undef PROATM_POLLING

/* Do not touch these *********************************************************/

#ifdef GENERAL_DEBUG
#define PRINTK(args...) pr_devel(args)
#else
#define PRINTK(args...)
#endif /* GENERAL_DEBUG */

#ifdef EXTRA_DEBUG
#define XPRINTK(args...) pr_devel(args)
#else 
#define XPRINTK(args...)
#endif /* EXTRA_DEBUG */

/* Macros *********************************************************************/

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))

#define CMD_BUSY(card) (readl((card)->membase + STAT) & PROATM_STAT_CMDBZ)

#define PROATM_DELAY mdelay(1)

#define ALIGN_BUS_ADDR(addr, alignment) \
                ((((u32) (addr)) + (((u32) (alignment)) - 1)) & ~(((u32) (alignment)) - 1))

#define ALIGN_ADDRESS(addr, alignment) \
                ((((unsigned long) (addr)) + (((unsigned long) (alignment)) - 1)) \
                                & ~(((unsigned long) (alignment)) - 1))

#if LINUX_VERSION_CODE < KERNEL_VERSION (2,6,14)
#define TIMESTP(sb)  do_gettimeofday(&(sb)->stamp)
#define SKB_UNLINK(a,b) skb_unlink((a))
#else
#define TIMESTP(sb)  __net_timestamp((sb))
#define SKB_UNLINK(a,b) skb_unlink((a),(b))
#endif

#undef CEIL

#define LED_ON \
                do { \
                        volatile  u32 gp = readl(card->membase + GP) & 0xfffffff8;  \
                        udelay(5);  \
                        writel (gp, card->membase + GP);  \
                        udelay(5); \
                } while(0)

#define LED_OFF \
                do { \
                        volatile  u32 gp = readl(card->membase + GP) & 0xfffffff8;  \
                        udelay(5);  \
                        writel (gp | PROATM_GP_EEDO, card->membase + GP);  \
                        udelay(5); \
                } while(0)

#define waitfor_notbusy(card)  while(PROATM_STAT_CMDBZ & (readl((card)->membase + STAT))){udelay(1);}

#ifdef PROATM_USE_WORKQUEUE
#define PUSH_ATM(vcc,skb) \
                do { \
                        unsigned long ppflag; \
                        ATM_SKB((skb))->vcc = (vcc); \
                        TIMESTP((skb)); \
                        PROATM_SKB((skb))->flags = 1; \
                        spin_lock_irqsave (&pushpop_lock, ppflag); \
                        skb_queue_tail (&pushpop_q, (skb)); \
                        spin_unlock_irqrestore (&pushpop_lock, ppflag); \
                        queue_work(pushpop_wq, &pushpop_w); \
                } while(0)

#define POP_ATM(vcc,skb) \
                do { \
                        unsigned long ppflag; \
                        ATM_SKB((skb))->vcc = (vcc); \
                        PROATM_SKB((skb))->flags = 0; \
                        spin_lock_irqsave (&pushpop_lock, ppflag); \
                        skb_queue_tail (&pushpop_q, (skb)); \
                        spin_unlock_irqrestore (&pushpop_lock, ppflag); \
                        queue_work(pushpop_wq, &pushpop_w); \
                } while(0)

#else
#define PUSH_ATM(vcc,skb) \
                do { \
                        ATM_SKB((skb))->vcc = (vcc); \
                        TIMESTP((skb)); \
                        (vcc)->push((vcc),(skb)); \
                } while(0)

#define POP_ATM(vcc,skb) \
                do { \
                        ATM_SKB((skb))->vcc = (vcc); \
                        (vcc)->pop((vcc), (skb)); \
                } while(0)
#endif

#define UNMAP_SKB(skb) \
                do { \
                        pci_unmap_single(card->pcidev, PROATM_SKB((skb))->paddr, \
                                        skb_end_ptr((skb)) - (skb)->data, PCI_DMA_FROMDEVICE); \
                                        PROATM_SKB((skb))->paddr = 0; \
                } while(0)

#ifndef __devinit
#define __devinit
#define __devinitdata
#endif
#ifndef __devexit
#define __devexit
#define __devexit_p(x) x
#endif

/* Function prototypes ******************************************************/
static u32 read_sram(proatm_dev *card, u32 sram_address);
static void write_sram(proatm_dev *card, u32 sram_address, u32 *value,
                int count);
static int __devinit init_card(int i, struct pci_dev *pcidev);
static void __devinit init_card_error(proatm_dev *card, int error);
#if LINUX_VERSION_CODE >= KERNEL_VERSION (2,6,19)
static irqreturn_t proatm_irq_handler(int irq, void *dev_id);
static int proatm_open(struct atm_vcc *vcc);
#else
static irqreturn_t proatm_irq_handler(int irq, void *dev_id, struct pt_regs *regs);
static int proatm_open(struct atm_vcc *vcc);
#endif
static void proatm_close(struct atm_vcc *vcc);
static scq_info *get_scq(proatm_dev *card, int type, u32 scd);
static void free_scq(proatm_dev *card, scq_info *scq, struct atm_vcc *vcc);
static int push_rxbuf(proatm_dev *card, struct sk_buff *skb);
static struct sk_buff *alloc_rxbuf(proatm_dev *card, u32 index);
static void fill_tst(proatm_dev *card, int n, vc_map *vc, u32 conn);
static int proatm_send(struct atm_vcc *vcc, struct sk_buff *skb);
static void process_tsq(proatm_dev *card);
static void drain_scq(proatm_dev *card, scq_info *scq, vc_map *vc, u32 tsrtag);
static void process_rsq(proatm_dev *card);
static void dequeue_rx(proatm_dev *card, proatm_rsqe *rsqe);
static void recycle_rxbufs(proatm_dev *card, struct sk_buff *skb);
static void recycle_rxbuf(proatm_dev *card, struct sk_buff *skb);
static int proatm_proc_read(struct atm_dev *dev, loff_t *pos, char *page);
static int proatm_ioctl(struct atm_dev *dev, unsigned int cmd, void *arg);
static void eeprom_rd(proatm_dev *card, u32 ee_add, u8 *memptr, u32 len);
static int parse_mac(char *mac, unsigned char *esi);
static short h2i(char c);
static void proatm_phy_put(struct atm_dev *dev, unsigned char value,
                unsigned long addr);
static unsigned char proatm_phy_get(struct atm_dev *dev, unsigned long addr);
static int push_on_scq(proatm_dev *card, scq_info *scq);
static int phys_init(proatm_dev *card, u32 *);
static inline int find_conn(proatm_dev *card, short vpi, int vci);
static int check_vc(proatm_dev *card, short vpi, int vci);
static void retreive_lost_buffers(proatm_dev *card);

#ifdef PROATM_RCQ_SUPPORT
static int process_rcqe(proatm_dev *card, proatm_rcqe *rawcell);
static void process_rcq(proatm_dev *card);
#endif

#ifdef PROATM_OAM_SUPPORT
static int oam_type (u32 pti, u32 vci);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
static void push_oamcell (struct work_struct *);
#else
static void push_oamcell (void *dev_id);
#endif
static void push_on_oam_tq (proatm_dev *card, proatm_rcqe *rawcell);
static void activate_deactivate_cc(proatm_dev *card, proatm_rcqe *rawcell);
static int process_oam_cell (proatm_dev *card, proatm_rcqe *rawcell);

static u8 proatm_all1[16] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
                0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
static u8 proatm_all0[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
static u8 proatm_llid[16] = PROATM_LLID;
#endif 

#ifdef PROATM_USE_WORKQUEUE
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
static void pushpop (struct work_struct *);
#else
static void pushpop (void *dev_id);
#endif
#endif

/* Global variables ***********************************************************/
static struct proatm_dev *cards[PROATM_MAX_CARDS];
static unsigned num_cards = 0;

static struct atmdev_ops atm_ops = {
                .open = proatm_open,
                .close = proatm_close,
                .ioctl = proatm_ioctl,
                .send = proatm_send,
                .phy_put = proatm_phy_put,
                .phy_get = proatm_phy_get,
                .proc_read = proatm_proc_read,
                .owner = THIS_MODULE,
};

#ifdef PROATM_USE_WORKQUEUE
static struct work_struct pushpop_w;
static struct sk_buff_head pushpop_q;
static struct workqueue_struct *pushpop_wq;
static spinlock_t pushpop_lock;
#endif

#ifdef PROATM_POLLING
static struct timer_list proatm_timer;
#endif
static char *mac[PROATM_MAX_CARDS] = { NULL
#if PROATM_MAX_CARDS > 1
                , NULL
#endif
#if PROATM_MAX_CARDS > 2
                , NULL
#endif
#if PROATM_MAX_CARDS > 3
                , NULL
#endif
#if PROATM_MAX_CARDS > 4
                , NULL
#endif
};
static int vpibits = PROATM_VPIBITS;
static int vcibase = PROATM_VCIBASE;
static int vpibase = PROATM_VPIBASE;
#ifdef PROATM_SUNI_FULL_SDH
static int fullsdh = 1;
#else
static int fullsdh = 0;
#endif

module_param_array(mac, charp, NULL, 0);
MODULE_PARM_DESC(mac, "mac address in the form XX:XX:XX:XX:XX:XX");
module_param(vpibits, int, 0);
MODULE_PARM_DESC(vpibits, "0, 1, 2 or 8");
module_param(vcibase, int, 0);
MODULE_PARM_DESC(vcibase, "see readme file");
module_param(vpibase, int, 0);
MODULE_PARM_DESC(vpibase, "see readme file");
module_param(fullsdh, int, 0);
MODULE_PARM_DESC(fullsdh, "see readme file");

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
#define nfmark mark
#define PROATM_INIT_WORK(a,b,c) INIT_WORK((a),(b))
#else
#define PROATM_INIT_WORK(a,b,c) INIT_WORK((a),(b),(c))
#endif

/* Functions *******************************************************************/

#ifdef NET_SKBUFF_DATA_USES_OFFSET
static inline unsigned char *skb_end_ptr(const struct sk_buff *skb)
{
        return skb->head + skb->end;
}
#else
static inline unsigned char *skb_end_ptr(const struct sk_buff *skb) {
        return skb->end;
}
#endif

static int __devinit proatm_init_one(struct pci_dev *pcidev,
                const struct pci_device_id *ent) {
        static int index = 0;

        PRINTK("proatm%d: proatm_init_one()\n", index);

        cards[index] = NULL;
        if (init_card(index, pcidev)) {
                cards[index] = NULL; /* don't increment index */
                return -ENODEV;
        }
        index++;
        return 0;
}

static void free_free_skb(proatm_dev *card, struct sk_buff *skb) {
        if (PROATM_SKB(skb)->paddr)
                pci_unmap_single(card->pcidev, PROATM_SKB(skb)->paddr,
                                skb_end_ptr(skb) - skb->data, PCI_DMA_FROMDEVICE);
        dev_kfree_skb_any(skb);
}

static void __devexit proatm_remove_one(struct pci_dev *pcidev) {
        int i, j;
        proatm_dev *card = pci_get_drvdata(pcidev);

        PRINTK("proatm: proatm_remove_one()\n");
        i = card->index;
        if (cards[i] == NULL) {
			return;
		}

		writel(0, card->membase + CFG);
		readl(card->membase + CFG);

		udelay(10000); 

		free_irq(card->pcidev->irq, card);

        if (card->atmdev) {
                atm_dev_deregister(card->atmdev);
                suni_stop(card->atmdev);
        }

        pci_disable_device(pcidev);

        for (i = 0; i < MAX_B1; i++) {
			if (card->fb_table[i].skb_ptr != NULL) {
				free_free_skb(card, card->fb_table[i].skb_ptr);
				card->fb_table[i].skb_ptr = NULL;
			}
        }

        free_scq(card, card->scq0, NULL);
        for (j = 0; j < PROATM_SCD_NUM; j++) {
			if (card->scd2vc[j] != NULL)
				free_scq(card, card->scd2vc[j]->scq, card->scd2vc[j]->tx_vcc);
        }
#ifdef PROATM_OAM_SUPPORT
        PRINTK("proatm%d: freeing OAM skb queue.\n", i);
        while ((sb = skb_dequeue (&card->oam_tq)))
			dev_kfree_skb_any (sb);
#endif

        PRINTK("proatm%d: freeing card.\n", i);
        free_pages((unsigned long) card->vcmap,
                        get_order(card->rct_size * sizeof(vc_map)));
        pci_free_consistent(card->pcidev, PROATM_RSQSIZE + PROATM_RSQ_ALIGNMENT,
                        card->rsq.org, card->rsq.org_pa);
        pci_free_consistent(card->pcidev, PROATM_TSQSIZE + PROATM_TSQ_ALIGNMENT,
                        card->tsq.org, card->tsq.org_pa);
        dma_free_coherent(&card->pcidev->dev, card->raw_hnd_size,
                          card->raw_hnd, card->raw_hnd_pa);
        iounmap((void *) card->membase);
        kfree(card);

}


static struct pci_device_id proatm_pci_tbl[] __devinitdata = { {
                PCI_VENDOR_ID_IDT, PCI_DEVICE_ID_IDT_IDT77252, PCI_ANY_ID, PCI_ANY_ID,
                0, 0, 0 }, { 0, } };

MODULE_DEVICE_TABLE( pci, proatm_pci_tbl);

static struct pci_driver proatm_driver = { .name = "proatm", .id_table =
                proatm_pci_tbl, .probe = proatm_init_one, .remove =
                                __devexit_p(proatm_remove_one), };

#ifdef PROATM_POLLING
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
static void proatm_poll(unsigned long arg)
#else
static void proatm_poll(struct timer_list *timer)
#endif
{
        int i;
        proatm_dev *card;
        unsigned long flags;
        u32 stat_r;

        PRINTK("proatm: proatm_poll ()\n");

        for (i = 0; i < num_cards; i++) {
                card = cards[i];
                if (spin_is_locked(&card->int_lock)) {
                        /* Probably it isn't worth spinning */
                        continue;
                }
                spin_lock_irqsave(&card->int_lock, flags);
                stat_r = readl(card->membase + STAT);
                if (stat_r & PROATM_STAT_TSIF) {
                        writel(PROATM_STAT_TSIF, card->membase + STAT);
                        pr_info("proatm%d: TSI\n", card->index);
                        process_tsq(card);
                }
                if (stat_r & PROATM_STAT_EOPDU) {
                        writel(PROATM_STAT_EOPDU, card->membase + STAT);
                        pr_info("proatm%d: End of PDU\n", card->index);
                        process_rsq(card);
                }
                spin_unlock_irqrestore(&card->int_lock, flags);
        }
        mod_timer(&proatm_timer, jiffies + PROATM_POLL_PERIOD);
}

#endif 

static int __init proatm_init(void)
{
        unsigned error = 0;  /* Initialized to remove compile warning */
        struct pci_dev *pci_bridge = NULL;
        u8 cache_size;

        /*
         * On some architectures, the default cache line size is not supported by
         * the bridge, which reduces the performance. Try to set a supported value.
         */
        pci_bridge = pci_get_device(PCI_VENDOR_ID_PLX, 0x8112, NULL);
        while (pci_bridge) {
                if (pci_read_config_byte(pci_bridge, PCI_CACHE_LINE_SIZE, &cache_size)) {
                        pr_err("proatm: error: cannot read bridge cache line size\n");
                        pci_dev_put(pci_bridge);
                        return -EINVAL;
                }
                if (cache_size > 32) {
                        cache_size = 32;
                        if (pci_write_config_byte(pci_bridge, PCI_CACHE_LINE_SIZE, cache_size))
                                pr_err("proatm: cannot set cache line size to %d\n", cache_size);
                        else
                                pr_info("proatm: set the bridge cache line size to %d\n", cache_size);
                }
                pci_dev_put(pci_bridge);
                pci_bridge = pci_get_device(PCI_VENDOR_ID_PLX, 0x8112, pci_bridge);
        }

        /*
         * Print some compilation options
         */
        XPRINTK("proatm: Extra debug enabled.\n");
        PRINTK("proatm: General debug enabled.\n");

#ifdef PROATM_RCQ_SUPPORT
        pr_info ("proatm: version %s, support of raw cells enabled\n", PROATM_VERSION);
#else
        pr_info ("proatm: version %s, support of raw cells disabled\n", PROATM_VERSION);
#endif

#if defined(PROATM_RCQ_SUPPORT) && defined(PROATM_AAL0_TO_RCQ)
        pr_info ("proatm: Cells received on AAL0 VCs are routed to the raw cell queue\n");
#else
        pr_info ("proatm: Cells received on AAL0 VCs cells are routed to standard queues (need EOM bit=1)\n");
#endif

#if defined(PROATM_RCQ_SUPPORT) && defined (PROATM_OAM_SUPPORT)
        pr_info ("proatm: Support of OAM cells enabled\n");
#else
        pr_info ("proatm: Support of OAM cells disabled\n");
#endif

#ifdef PROATM_USE_WORKQUEUE
        pr_info ("proatm: Use Work Queue\n");
#else
        pr_info ("proatm: Do not use Work Queue\n");
#endif

        PRINTK("proatm: proatm_init()\n");

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
        error = pci_register_driver (&proatm_driver);
#else
        error = pci_module_init (&proatm_driver);
#endif
        if (error)
                return error;

#ifdef PHY_LOOPBACK
        pr_info("proatm: using PHY loopback.\n");
#endif /* PHY_LOOPBACK */

#ifdef PROATM_POLLING 
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
        init_timer(&proatm_timer);
        proatm_timer.data = 0UL;
        proatm_timer.function = proatm_poll;
#else
        timer_setup(&proatm_timer, proatm_poll, 0);
#endif
        proatm_timer.expires = jiffies + PROATM_POLL_PERIOD;
        add_timer(&proatm_timer);
#endif

#ifdef PROATM_USE_WORKQUEUE
        /* initialize pushpop queue */
        skb_queue_head_init(&pushpop_q);
        /* init pushpop work queue */
        pushpop_wq = create_workqueue("proatm_pushpop_queue");
        /* init pushpop work */
        PROATM_INIT_WORK(&pushpop_w, pushpop, NULL);
        /* init spin_lock */
        spin_lock_init(&pushpop_lock);
#endif

        return 0;
}

static void __exit proatm_cleanup(void)
{
        PRINTK("proatm: proatm_cleanup()\n");

#ifdef PROATM_USE_WORKQUEUE
        flush_workqueue(pushpop_wq);
        destroy_workqueue(pushpop_wq);
#endif
#ifdef PROATM_POLLING 
        del_timer(&proatm_timer);
#endif
        pci_unregister_driver(&proatm_driver);

}

/* these functions use 4-byte word address */

static u32 read_sram(proatm_dev *card, u32 sram_address) {
        unsigned long flags;
        u32 data;
        sram_address <<= 2;
        sram_address &= 0x001FFFFC; /* 2 MB max */
        sram_address |= 0x50000000; /* SRAM read command */
        spin_lock_irqsave(&card->res_lock, flags);
        waitfor_notbusy(card);
        writel(sram_address, card->membase + CMD);
        waitfor_notbusy(card);
        data = readl(card->membase + DR0);
        spin_unlock_irqrestore(&card->res_lock, flags);
        return data;
}

static void write_sram(proatm_dev *card, u32 sram_address, u32 *value,
                int count) {
        unsigned long flags;
        int i, c;

        count--; /* count range now is 0..3 instead of 1..4 */
        c = count;
        c <<= 2; /* to use increments of 4 */
        spin_lock_irqsave(&card->res_lock, flags);
        waitfor_notbusy(card);
        for (i = 0; i <= c; i += 4)
                writel(*(value++), card->membase + i);

        /* Note: DR# registers are the first 4 dwords in proatm's memspace,
         so card->membase + DR0 = card->membase */
        sram_address <<= 2;
        sram_address &= 0x001FFFFC; /* 2 MB max */
        sram_address |= (0x40000000 | count);
        writel(sram_address, card->membase + CMD);
        waitfor_notbusy(card);
        spin_unlock_irqrestore(&card->res_lock, flags);
}

static int __devinit sram_check(proatm_dev *card) {
        u32 pattern[] = { 0x00000000, 0xFFFFFFFF, 0x55555555, 0xAAAAAAAA };
        int bits = card->sram_size == 512 ? 19 : card->sram_size == 128 ? 17 : 15;
        int i;
        u32 addr, data;

        /* check SRAM data bus */
        for (i = 0; i < sizeof(pattern) / sizeof(u32); i++) {
                write_sram(card, 0x1000, pattern + i, 1);
                if ((data = read_sram(card, 0x1000)) != pattern[i]) {
                        pr_err("proatm%d: SRAM error: write 0x%08X, read 0x%08X\n",
                                        card->index, pattern[i], data);
                        return -1;
                }
        }

        /* check SRAM address bus */
        for (i = 0, addr = 1; i < bits; i++, addr <<= 1) {
                write_sram(card, addr, pattern + 1, 1);
                write_sram(card, 0, pattern, 1);
                if ((data = read_sram(card, addr)) != pattern[1]) {
                        pr_err("proatm%d: SRAM error: address bit %d stuck to GND\n",
                                        card->index, i);
                        return -1;
                }
                write_sram(card, addr, pattern, 1);

                write_sram(card, ~addr, pattern + 1, 1);
                write_sram(card, 0xffffffff, pattern, 1);
                if ((data = read_sram(card, ~addr)) != pattern[1]) {
                        pr_err("proatm%d: SRAM error: address bit %d stuck to VCC\n",
                                        card->index, i);
                        return -1;
                }
                write_sram(card, ~addr, pattern, 1);
        }
        return 0;

}

static int __devinit phys_init(proatm_dev *card, u32 *proatm_cfg) {
        u32 data;

        PRINTK("proatm%d: phys_init ()\n", card->index);

        /* PHY reset */
        writel(0xa, card->membase + GP);
        PROATM_DELAY;
        writel(0x2, card->membase + GP);
        PROATM_DELAY;
        waitfor_notbusy(card);
        XPRINTK("proatm%d: DR0 read (0x%08X).\n", card->index,
                        readl(card->membase + DR0));
        PROATM_DELAY;

        /* Detect PHY type */
        pr_info("proatm%d: PHY type detection called.\n", card->index);
        waitfor_notbusy(card);
        writel(PROATM_CMD_READ_UTILITY | 0x00000100, card->membase + CMD);
        waitfor_notbusy(card);
        data = readl(card->membase + DR0);
        XPRINTK("proatm%d: DR0 read (0x%08X).\n", card->index, data);
        strcpy(card->name, "unknown_card");

        switch (data) {
        case 0x00000030:
        case 0x00000031:
                pr_info("proatm%d: PHY seems to be 155 Mbps.\n", card->index);
                strcpy(card->name, "proatm155");
                card->max_pcr = ATM_OC3_PCR;
#ifdef PHY_LOOPBACK
                waitfor_notbusy(card);
                writel(0x00000002, card->membase + DR0);
                writel(PROATM_CMD_WRITE_UTILITY | 0x00000105, card->membase + CMD);
#endif /* PHY_LOOPBACK */
                break;
        default:
                pr_info("proatm%d: unknown PHY type (0x%08X).\n", card->index, data);
                return 8;
        }
        writel(0x00000000, card->membase + GP);

        /* Find SRAM size */
        data = 0x11111111;
        write_sram(card, 0x4000, &data, 1);
        data = 0x22222222;
        write_sram(card, 0x14000, &data, 1);
        data = 0x33333333;
        write_sram(card, 0x54000, &data, 1);

        XPRINTK ("SRAM(0x4000) = 0x%x\n", read_sram(card, 0x4000));
        XPRINTK ("SRAM(0x14000) = 0x%x\n", read_sram(card, 0x14000));
        XPRINTK ("SRAM(0x54000) = 0x%x\n", read_sram(card, 0x54000));

        if (read_sram(card, 0x4000) == 0x11111111) {
                if (read_sram(card, 0x14000) == 0x22222222) {
                        card->sram_size = 512; /* 512K words */
                        card->rct_size = 16384;
                        card->vcibits = 14 - vpibits;
                        card->tst_num = 4095;
                        *proatm_cfg |= PROATM_CFG_RCTSIZE_16384_ENTRIES;
                } else {
                        card->sram_size = 128; /* 128K words */
                        card->rct_size = 4096;
                        card->vcibits = 12 - vpibits;
                        card->tst_num = 4095;
                        *proatm_cfg |= PROATM_CFG_RCTSIZE_4096_ENTRIES;
                }
        } else {
                card->sram_size = 32; /* 32K words */
                card->rct_size = 1024;
                card->vcibits = 10 - vpibits;
                card->tst_num = 2047;
                *proatm_cfg |= PROATM_CFG_RCTSIZE_1024_ENTRIES;
        }
        pr_info("proatm%d: SRAM size = %dK x 32bit\n", card->index, card->sram_size);

        if (sram_check(card)) /* cannot hurt */
                return 8;

        card->vpibits = vpibits;
        card->maxvpi = (1 << card->vpibits) - 1;
        PRINTK ("    VPI bits = %d, VPI mask = 0x%x\n", card->vpibits, card->maxvpi);
        card->maxvci = (1 << card->vcibits) - 1;
        PRINTK ("    VCI bits = %d, VCI mask = 0x%x\n", card->vcibits, card->maxvci);

        /* dynamic SRAM map */
        card->rct = PROATM_TCT_ENTRY_SIZE * card->rct_size;
        card->rate = card->rct
                        + PROATM_RCT_ENTRY_SIZE * card->rct_size+ 2*PROATM_RFBQ_SIZE;
        card->tst = card->rate + abr_vbr_rate_tables_len;
        card->dtst = card->tst + card->tst_num + 1;
        card->scd = card->dtst + PROATM_DTST_SIZE;
        card->rxfifo = card->sram_size * 1024 - PROATM_RXFIFO_SIZE;
        card->scd_size = ((card->rxfifo - card->scd) / PROATM_SCD_ENTRY_SIZE) - 1;
        if (card->scd_size >= 16383)
                card->scd_size = 16383;
        card->scd_ubr0 = card->scd + card->scd_size * PROATM_SCD_ENTRY_SIZE;

        XPRINTK ("proatm%d: SRAM Map for %d connections:\n", card->index, card->rct_size);
        XPRINTK ("    TCT =      0x0\n");
        XPRINTK ("    RCT =      0x%x\n", card->rct);
        XPRINTK ("    RATE_TBL = 0x%x\n", card->rate);
        XPRINTK ("    TST =      0x%x\n", card->tst);
        XPRINTK ("    DTST =     0x%x\n", card->dtst);
        XPRINTK ("    SCD =      0x%x\n", card->scd);
        XPRINTK ("    SCD_UBR0 = 0x%x\n", card->scd_ubr0);
        XPRINTK ("    RXFIFO =   0x%x\n", card->rxfifo);
        XPRINTK ("    SCD number = %d\n", card->scd_size);

        return 0;
}

static int __devinit init_card(int i, struct pci_dev *pcidev) {
        int j;
        struct proatm_dev *card = NULL;
        unsigned char pci_latency;
        unsigned error = 0;
        u32 data;
        u32 u32d[4];
        u32 proatm_cfg = 0;
        u32 *abr_vbr_rate_tables;
        u32 bcount;
        u32 cfg[9] = { PROATM_CFG_VPIBITS_0, PROATM_CFG_VPIBITS_1,
                        PROATM_CFG_VPIBITS_2, 0, 0, 0, 0, 0, PROATM_CFG_VPIBITS_8 };
        u32 membase, srambase;

        PRINTK("proatm%d: init_card()\n", i);

        if (pci_enable_device(pcidev)) {
                pr_err("proatm%d: can't enable PCI device\n", i);
                error = 2;
                init_card_error(card, error);
                return error;
        }

        if (vpibits != 0 && vpibits != 1 && vpibits != 2 && vpibits != 8) {
                pr_err("proatm%d: incorrect number of vpi bits = %d. Should be 0, 1, 2, or 8.\n",
                                card->index, vpibits);
                return 1;
        }
        proatm_cfg = cfg[vpibits];

        if ((card = kmalloc(sizeof(proatm_dev), GFP_KERNEL)) == NULL) {
                pr_err("proatm%d: can't allocate memory for device structure.\n", i);
                error = 2;
                init_card_error(card, error);
                return error;
        }
        /* clear the proatm_dev structure */
        memset(card, 0, sizeof(proatm_dev));

        cards[i] = card;
        spin_lock_init(&card->int_lock);
        spin_lock_init(&card->res_lock);

        pci_set_drvdata(pcidev, card);

        card->index = i;
        card->atmdev = NULL;
        card->pcidev = pcidev;
        membase = pci_resource_start(pcidev, 1);
        srambase = pci_resource_start(pcidev, 2);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,9)
        card->membase = (unsigned long)ioremap(membase, PROATM_IOREMAP_SIZE);
#else
        card->membase = ioremap(membase, PROATM_IOREMAP_SIZE);
#endif

        if (card->membase == 0) {
                pr_err("proatm%d: can't ioremap() membase.\n", card->index);
                error = 3;
                init_card_error(card, error);
                return error;
        }PRINTK("proatm%d: membase at 0x%lx\n", i, (unsigned long) card->membase);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,9)
        card->fbq[0] = (unsigned long)ioremap(srambase | 0x200000, 4);
        card->fbq[1] = (unsigned long)ioremap(srambase | 0x240000, 4);
#else
        card->fbq[0] = ioremap(srambase | 0x200000, 4);
        card->fbq[1] = ioremap(srambase | 0x240000, 4);
#endif
        if (card->fbq[0] == 0 || card->fbq[1] == 0) {
                pr_err("proatm%d: can't ioremap() the FBQs.\n", card->index);
                error = 3;
                init_card_error(card, error);
                return error;
        }

        pci_set_master(pcidev);
		/*
		* declare the SAR's DMA addressing capability BEFORE any
		* coherent allocation or streaming mapping is performed.
		*
		* The IDT77252 is a 32-bit-only DMA master: TSQB/RSQB/RAWHND and the
		* free buffer queue entries are all 32-bit registers. Without this
		* call the device keeps the default 32-bit mask but no bus_dma_limit
		* (hence "bus limit 0" in the overflow warning), so dev_alloc_skb()
		* happily returns buffers above 4GB on large-memory machines and
		* every dma_map_single() on them fails.
		*
		* This must come after pci_set_master() and before the TSQ/RSQ/RAWHND
		* allocations and the alloc_rxbuf() loop further down.
		*/
        if (dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(32))) {
              pr_err("proatm%d: no usable 32-bit DMA configuration.\n", i);
              error = 6;
              init_card_error(card, error);
              return error;
        }

        if (pci_read_config_byte(pcidev, PCI_LATENCY_TIMER, &pci_latency) != 0) {
                pr_err("proatm%d: can't read PCI latency timer.\n", i);
                error = 6;
                init_card_error(card, error);
                return error;
        }
#ifdef PROATM_PCI_LATENCY
        if (pci_latency < PROATM_PCI_LATENCY) {
                PRINTK("proatm%d: setting PCI latency timer to %d.\n", i, PROATM_PCI_LATENCY);
                for (j = 1; j < 4; j++) {
                        if (pci_write_config_byte(pcidev, PCI_LATENCY_TIMER, PROATM_PCI_LATENCY) != 0)
                                break;
                }
                if (j == 4) {
                        pr_err("proatm%d: can't set PCI latency timer to %d.\n", i, PROATM_PCI_LATENCY);
                        error = 7;
                        init_card_error(card, error);
                        return error;
                }
        }
#endif /* PROATM_PCI_LATENCY */

        /* workaround for SAR bugs #14 and #21*/
        if ((pci_write_config_word(pcidev, 0x40, 0))
                        || (pci_write_config_byte(pcidev, 0x30, 1))) {
                pr_err("proatm%d: can't write to configuration space.\n", i);
                error = 7;
                init_card_error(card, error);
                return error;
        }

        /* Clear timer overflow */
        data = readl(card->membase + STAT);
        if (data & PROATM_STAT_TMROF)
                writel(PROATM_STAT_TMROF, card->membase + STAT);

        /* Software reset */
        writel(PROATM_CFG_SWRST, card->membase + CFG);
        PROATM_DELAY;
        writel(0x00000000, card->membase + CFG);
        data = readl(card->membase + GP);
        PRINTK("proatm%d: GP read (0x%08X).\n", i, data);

        /* Card hardware initialization */
        error = phys_init(card, &proatm_cfg);
        if (error) {
                init_card_error(card, error);
                return error;
        }

        writel(proatm_cfg, card->membase + CFG); /* init rct and tct sizes */

        /* Allocate IRQ */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
        if (request_irq(pcidev->irq, &proatm_irq_handler,
                        IRQF_SHARED, card->name, card) != 0)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
        if (request_irq(pcidev->irq, &proatm_irq_handler,
                        IRQF_DISABLED | IRQF_SHARED, card->name, card) != 0)
#else
                if (request_irq(pcidev->irq, &proatm_irq_handler,
                                SA_INTERRUPT | SA_SHIRQ, card->name, card) != 0)
#endif
                {
                        pr_err("proatm%d: can't allocate IRQ %d for %s.\n", card->index,
                                        pcidev->irq, card->name);
                        error = 9;
                        init_card_error(card, error);
                        return error;
                }

        /* Set the VPI/VCI MSB mask (must be zero to receive OAM cells) */
        card->vpm = (vcibase >> card->vcibits)
                                        | ((vpibase >> card->vpibits) << (16 - card->vcibits));
        card->vpm &= 0x7fff;
        writel(card->vpm, card->membase + VPM);
        pr_info("proatm%d: vpibits=%d, vpibase=%d, vcibase=%d, VPM=0x%x\n", i,
                        vpibits, vpibase, vcibase, card->vpm);
        /* Initialize TSQ */
        card->tsq.org = (char*) pci_alloc_consistent(card->pcidev,
                        PROATM_TSQSIZE + PROATM_TSQ_ALIGNMENT, &card->tsq.org_pa);

        if (card->tsq.org == NULL) {
                pr_err("proatm%d: can't allocate TSQ.\n", i);
                error = 10;
                init_card_error(card, error);
                return error;
        }
        card->tsq.base_pa = ALIGN_BUS_ADDR(card->tsq.org_pa, PROATM_TSQ_ALIGNMENT);
        card->tsq.base = (proatm_tsi *) (card->tsq.org
                        + (card->tsq.base_pa - card->tsq.org_pa));
        card->tsq.next = card->tsq.base;
        for (j = 0; j < PROATM_TSQ_NUM_ENTRIES; j++)
                proatm_tsi_init(card->tsq.base + j);

        writel(0x00000000, card->membase + TSQH);
        writel(card->tsq.base_pa, card->membase + TSQB);

        /* Initialize RSQ */
        card->rsq.org = (char*) pci_alloc_consistent(card->pcidev,
                        PROATM_RSQSIZE + PROATM_RSQ_ALIGNMENT, &card->rsq.org_pa);
        if (card->rsq.org == NULL) {
                pr_err("proatm%d: can't allocate RSQ.\n", i);
                error = 11;
                init_card_error(card, error);
                return error;
        }
        card->rsq.base_pa = ALIGN_BUS_ADDR(card->rsq.org_pa, PROATM_RSQ_ALIGNMENT);
        card->rsq.base = (proatm_rsqe *) (card->rsq.org
                        + (card->rsq.base_pa - card->rsq.org_pa));
        card->rsq.next = card->rsq.base;
        card->rsq.last = card->rsq.base + (PROATM_RSQ_NUM_ENTRIES - 1);
        for (j = 0; j < PROATM_RSQ_NUM_ENTRIES; j++)
                proatm_rsqe_init(card->rsq.base + j);

        writel(0x00000000, card->membase + RSQH);
        writel(card->rsq.base_pa, card->membase + RSQB);

        /* Initialize TCT */
        u32d[0] = 0x00000000;
        u32d[1] = 0x00000000;
        u32d[2] = 0x00000000;
        u32d[3] = 0x00000000;
        for (j = 0; j < (2 * card->rct_size); j++)
                write_sram(card, j * 4, u32d, 4);

        /* Initialize RCT. AAL type is set on opening the VC. */
#ifdef PROATM_RCQ_SUPPORT
        u32d[0] = PROATM_RCTE_RAWCELLINTEN;
#else 
        u32d[0] = 0x00000000;
#endif 
        u32d[1] = 0x00000000;
        u32d[2] = 0x00000000;
        u32d[3] = 0xFFFFFFFF;
        for (j = 0; j < card->rct_size; j++)
                write_sram(card, card->rct + (j * 4), u32d, 4);

        /* Initialize RAWHND */
    	/*
    	 * FIX #3: the raw cell handle area was allocated as 2*sizeof(u32) = 8
    	 * bytes. Without an IOMMU the whole page is present so an oversized
    	 * write by the SAR goes unnoticed; with an IOMMU in translated mode
    	 * only the 8 requested bytes are mapped, and any write past them hits
    	 * an unmapped IOVA. An RCQ entry is 16 bytes, so 8 is too small by
    	 * construction. Allocate a full cache line.
    	 */
    	card->raw_hnd_size = ALIGN(4 * sizeof(u32), 64);
    	card->raw_hnd = dma_alloc_coherent(&pcidev->dev, card->raw_hnd_size,
				   &card->raw_hnd_pa, GFP_KERNEL);

        if (!card->raw_hnd) {
                pr_err("proatm%d: can't allocate raw_hnd.\n", i);
                error = 12;
                init_card_error(card, error);
                return error;
        }
    	memset(card->raw_hnd, 0, card->raw_hnd_size);
    	pr_info("proatm%d: RAWHND pa=%pad size=%zu\n",
    						i, &card->raw_hnd_pa, card->raw_hnd_size);
    	writel(card->raw_hnd_pa, card->membase + RAWHND);

        /* Initialize the vc map */
        card->vcmap = (vc_map *) __get_free_pages(GFP_KERNEL,
                        get_order(card->rct_size * sizeof(vc_map)));
        if (card->vcmap == NULL) {
                pr_err("proatm%d: can't allocate memory for VCs map.\n", i);
                error = 13;
                init_card_error(card, error);
                return error;
        }
        memset(card->vcmap, 0, card->rct_size * sizeof(vc_map));

        for (j = 0; j < PROATM_SCD_NUM; j++)
                card->scd2vc[j] = NULL;

        /* Initialize ABRVBR TABLES */
        writel(PROATM_ABRVBRSIZE | (card->dtst << 2), card->membase + ABRSTD);
        for (j = 0; j < PROATM_DTST_SIZE; j++) {
                write_sram(card, card->dtst + j, u32d, 1);
        }

        /* Initialize the Rate Table */
        abr_vbr_rate_tables = abr_vbr_rate_tables_155Mb;

        for (j = 0; j < abr_vbr_rate_tables_len; j++) {
                write_sram(card, (card->rate + j), &abr_vbr_rate_tables[j], 1);
        }
        writel(card->rate << 2, card->membase + RTBL);

        /* Initialize the TST (use only one) */
        card->tst_free_entries = card->tst_num;
        data = PROATM_TST_OPCODE_VARIABLE;
        for (j = 0; j < card->tst_num; j++)
                write_sram(card, card->tst + j, &data, 1);

        data = proatm_tste_make(PROATM_TST_OPCODE_END, card->tst<<2);
        write_sram(card, card->tst + card->tst_num, &data, 1);

        writel(card->tst << 2, card->membase + TSTB);

        /* Initialize SCQ0, the UBR SCQ for unspecified PCR */
        card->scq0 = get_scq(card, UBR_SCQ, card->scd_ubr0);
        if (card->scq0 == (scq_info *) NULL) {
                pr_err("proatm%d: can't get SCQ0.\n", i);
                error = 14;
                init_card_error(card, error);
                return error;
        }
        u32d[0] = card->scq0->base_pa;
        u32d[1] = (u32) 0x00000000;
        u32d[2] = (u32) 0xffffffff;
        u32d[3] = (u32) 0x00000000;
        write_sram(card, card->scd_ubr0, u32d, 4);
        for (j = 1; j < 3; j++) {
                u32d[0] = (u32) 0x00000000;
                u32d[1] = (u32) 0x00000000;
                u32d[2] = (u32) 0x00000000;
                u32d[3] = (u32) 0x00000000;
                write_sram(card, card->scd_ubr0 + 4 * j, u32d, 4);
        }
        card->scq0->scd = card->scd_ubr0;
        PRINTK("proatm%d: SCQ0 base at 0x%lx.\n", i, (unsigned long)card->scq0->base);

#ifdef PROATM_OAM_SUPPORT
        /* Initialize oam transmit queue */
        skb_queue_head_init(&card->oam_tq);
        /*init skb queue of OAM cells waiting for transmission */
        PROATM_INIT_WORK(&card->oam_w, push_oamcell, (void *)card);
#endif

        /* Initialize RxFIFO */
        writel((card->rxfifo << 2) | PROATM_RXFD_SIZE, card->membase + RXFD);

        /* Initialize free buffer queues registers #34-#45 */
        writel(PROATM_FBQ0_WP | PROATM_FBQ0_RP, card->membase + FBQP0);
        writel(PROATM_FBQ1_WP | PROATM_FBQ0_RP, card->membase + FBQP1);
        writel(PROATM_FBQ2_WP | PROATM_FBQ0_RP, card->membase + FBQP2);
        writel(PROATM_FBQ3_WP | PROATM_FBQ0_RP, card->membase + FBQP3);
        writel(PROATM_B0THLD | PROATM_B0NITHLD | PROATM_B0CITHLD | PROATM_B0SIZE,
                        card->membase + FBQS0);
        writel(PROATM_B1THLD | PROATM_B1NITHLD | PROATM_B1CITHLD | PROATM_B1SIZE,
                        card->membase + FBQS1);
        writel(PROATM_B2THLD | PROATM_B2NITHLD | PROATM_B2CITHLD,
                        card->membase + FBQS2);
        writel(PROATM_B3THLD | PROATM_B3NITHLD | PROATM_B3CITHLD,
                        card->membase + FBQS3);

        card->fbie = 1; /* To prevent push_rxbuf from enabling the interrupt */

        /* Allocate receive buffers : b1 */
        for (j = 0; j < NUM_B1; j++) {
                struct sk_buff *lb;

                if ((lb = alloc_rxbuf(card, j)) == NULL) {
                        pr_err("proatm%d: can't allocate %dth of %d buffers.\n", i, j,
                                        NUM_B1);
                        break;
                }
#ifdef PROATM_RCQ_SUPPORT
                if (j == 0) {
                        /* the first large SKB is reserved for raw cells */
                        card->rcbuf = lb;
                        card->raw_ch = (proatm_rcqe*) lb->data;
                        card->raw_ch_pa = PROATM_SKB(lb)->paddr;
                }
#endif
        }

        /* Verify that push worked well */
        if ((bcount = proatm_fbqc_get(readl(card->membase + FBQP1)))
                        < NUM_B1) {
                pr_err( "proatm%d: Error: allocate %d large buffers but count = %d only.\n",
                                i, j, bcount);
                error = 16;
                init_card_error(card, error);
                return error;
        }

        card->intcnt = 0;
        card->fbie = 1;

        /* Register device */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)
        card->atmdev = atm_dev_register(card->name, &pcidev->dev, &atm_ops, -1,
                        NULL);
#else
        card->atmdev = atm_dev_register(card->name, &atm_ops, - 1, NULL);
#endif

        if (card->atmdev == NULL) {
                pr_err("proatm%d: can't register device %s\n", i, card->name);
                error = 19;
                init_card_error(card, error);
                return error;
        }

        /* Read EEPROM */
        if (parse_mac(mac[i], card->atmdev->esi)) {
                unsigned char *esi = card->atmdev->esi;

                eeprom_rd(card, PROATM_EPROM_PROSUM_MAC_ADDR_OFFSET, esi, 6);
                if (esi[0] != PROSUM_MAC_0 || esi[1] != PROSUM_MAC_1
                                || esi[2] != PROSUM_MAC_2)
                        eeprom_rd(card, PROATM_EPROM_IDT_MAC_ADDR_OFFSET, esi, 6);

                pr_info("proatm%d: MAC address %02X:%02X:%02X:%02X:%02X:%02X\n", i,
                                esi[0], esi[1], esi[2], esi[3], esi[4], esi[5]);
        }
        card->atmdev->dev_data = card;
        card->atmdev->ci_range.vpi_bits = 8;
        card->atmdev->ci_range.vci_bits = 16;
        card->atmdev->link_rate = card->max_pcr;
        card->atmdev->phy = NULL;

        if (card->max_pcr == ATM_OC3_PCR) {
                PRINTK ("proatm%d: Init Suni\n", i);
                suni_init(card->atmdev);

                if (fullsdh) {
                        /* modify Suni settings to make it generate SDH frames */
                        waitfor_notbusy(card);
                        writel(PROATM_CMD_READ_UTILITY | 0x00000146, card->membase + CMD);
                        waitfor_notbusy(card);
                        data = readl(card->membase + DR0);
                        data |= 0x8; /*  Set S1 bit of TPOP arbitrary pointer MSB */
                        waitfor_notbusy(card);
                        writel(data, card->membase + DR0);
                        writel(PROATM_CMD_WRITE_UTILITY | 0x00000146, card->membase + CMD);
                        pr_info("proatm%d: Set SDH framing\n", i);
                }
        }

        suni_start(card->atmdev);

        writel(PROATM_DEFAULT_CFG | proatm_cfg, card->membase + CFG);

        PRINTK("proatm%d: card->vpibits= %d.\n", i, card->vpibits);
        PRINTK("proatm%d: card->vcibits= %d.\n", i, card->vcibits);

        num_cards++;
        return error;
}

static void __devinit init_card_error(proatm_dev *card, int error) {
        PRINTK("proatm%d: init_card_error (error = %d)\n",
                        card->index, error);

        if (error >= 19) {
                writel(0x00000000, card->membase + CFG);
        }
        if (error >= 16) {
                int i;

                for (i = 0; i < MAX_B1; i++) {
                        if (card->fb_table[i].skb_ptr != NULL) {
                                free_free_skb(card, card->fb_table[i].skb_ptr);
                                card->fb_table[i].skb_ptr = NULL;
                        }
                }
        }

        if (error >= 14) {
                free_pages((unsigned long) card->vcmap,
                                get_order(card->rct_size * sizeof(vc_map)));
        }
        if (error >= 13) {
        	if (card->raw_hnd) {
        		dma_free_coherent(&card->pcidev->dev, card->raw_hnd_size,
        				  card->raw_hnd, card->raw_hnd_pa);
        		card->raw_hnd = NULL;
        	}
        }

        if (error >= 12) {
                pci_free_consistent(card->pcidev, PROATM_RSQSIZE + PROATM_RSQ_ALIGNMENT,
                                card->rsq.org, card->rsq.org_pa);
        }
        if (error >= 11) {
                pci_free_consistent(card->pcidev, PROATM_TSQSIZE + PROATM_TSQ_ALIGNMENT,
                                card->tsq.org, card->tsq.org_pa);
        }
        if (error >= 10) {
                free_irq(card->pcidev->irq, card);
        }
        if (error >= 4) {
                iounmap((void *) card->membase);
        }
        if (error >= 3) {
                pci_disable_device(card->pcidev);
                kfree(card);
        }
}

static scq_info *get_scq(proatm_dev *card, int type, u32 scd) {
        scq_info *scq;

        scq = (scq_info *) kmalloc(sizeof(scq_info), GFP_KERNEL);
        if (scq == (scq_info *) NULL)
                return (scq_info *) NULL;
        /* reserve twice the size for alignment purpose*/
        scq->org = pci_alloc_consistent(card->pcidev, 2 * SCQ_SIZE, &scq->org_pa);
        if (scq->org == NULL) {
                kfree(scq);
                return (scq_info *) NULL;
        }
        /* reserve a table of skbuf pointers */
        scq->skb = (struct sk_buff * *) kmalloc(
                        sizeof(struct sk_buff *) * SCQ_NUM_ENTRIES, GFP_KERNEL);
        if (scq->skb == NULL) {
                pci_free_consistent(card->pcidev, 2 * SCQ_SIZE, scq->org, scq->org_pa);
                kfree(scq);
                return (scq_info *) NULL;
        }
        memset(scq->skb, 0, sizeof(struct sk_buff *) * SCQ_NUM_ENTRIES);
        scq->scq_type = type;
        scq->base_pa = ALIGN_BUS_ADDR(scq->org_pa, SCQ_SIZE);
        scq->base = (proatm_scqe *) (scq->org + (scq->base_pa - scq->org_pa));
        scq->next = scq->base;
        scq->last = scq->base + SCQ_NUM_ENTRIES - 1;
        scq->tail = scq->last;
        scq->scd = scd;
        scq->tbd_count = 0;
        skb_queue_head_init(&scq->waiting);
        scq->full = 0;
        spin_lock_init(&scq->lock);
        return scq;
}

/* For variable rate, SCQ vcc must be NULL */
static void free_scq(proatm_dev *card, scq_info *scq, struct atm_vcc *vcc) {
        int i;
        struct sk_buff *skb;

        if (scq == NULL) {
                pr_err("proatm%d: try to free a null SCQ\n", card->index);
                return;
        }

        while ((skb = skb_dequeue(&scq->waiting)))
                dev_kfree_skb_any(skb);

        if (scq == card->scq0) {
                for (i = 0; i < SCQ_NUM_ENTRIES; i++) {
                        if (scq->skb[i] != NULL) {
                                vcc = ATM_SKB(scq->skb[i])->vcc;
                                if (vcc->pop != NULL)
                                        /* return the skb to ATM protocol layers after emission */
                                        POP_ATM(vcc, scq->skb[i]);
                                else
                                        /* or free the skb if the protocol does not care of it */
                                        dev_kfree_skb_any(scq->skb[i]);

                        }
                }
        } else {
                /* vcc must be != NULL */
                if (vcc == NULL) {
                        pr_err("proatm%d: free_scq() called with vcc == NULL.",
                                        card->index);
                        for (i = 0; i < SCQ_NUM_ENTRIES; i++)
                                dev_kfree_skb_any(scq->skb[i]);
                } else {
                        for (i = 0; i < SCQ_NUM_ENTRIES; i++) {
                                if (scq->skb[i] != NULL) {
                                        if (vcc->pop != NULL)
                                                /* return the skb to ATM protocol layers after emission */
                                                POP_ATM(vcc, scq->skb[i]);
                                        else
                                                /* or free the skb if the protocol does not care of it */
                                                dev_kfree_skb_any(scq->skb[i]);

                                }
                        }
                }
        }

        kfree(scq->skb);
        pci_free_consistent(card->pcidev, 2 * SCQ_SIZE, scq->org, scq->org_pa);
        kfree(scq);
}

/*
 * Add one buffer to the pool of free RX buffers.
 * The sk_buff passed must contain the buffer cast to u32.
 * These buffers are filled with incoming data by the SAR. 
 */
static int push_rxbuf(proatm_dev *card, struct sk_buff *skb) {
        unsigned long flags;
        u32 index = PROATM_SKB(skb)->index;
        dma_addr_t paddr = PROATM_SKB(skb)->paddr;
        size_t maplen = skb_end_ptr(skb) - skb->data;

        PRINTK("proatm%d: push_rxbuf ()\n", card->index);

        spin_lock_irqsave(&card->res_lock, flags);

        if (index >= MAX_B1) {
                pr_err("proatm%d: Free buffer index error\n", card->index);
                goto free_skb;
        }


        if (paddr == 0 ) {
            /* this is a new skb */
        	paddr = dma_map_single(&card->pcidev->dev, skb->data,
        	                                maplen, DMA_FROM_DEVICE);
        	if (dma_mapping_error(&card->pcidev->dev, paddr)) {
        		pr_err("proatm%d: DMA mapping failed for rx buffer %u.\n", card->index, index);
        	    goto free_skb;
        	}
         	if (upper_32_bits(paddr)) {
         		pr_err("proatm%d: rx buffer DMA address %pad exceeds 32 bits.\n", card->index, &paddr);
        	    dma_unmap_single(&card->pcidev->dev, paddr, maplen, DMA_FROM_DEVICE);
                goto free_skb;
         	}
            /* write the physical address into the private part of skb */
            PROATM_SKB(skb)->paddr = paddr;
        }
        /* sanity check */
        if (card->fb_table[index].skb_ptr != skb) {
			pr_err("proatm%d: free buffer table corruption.\n", card->index);
			goto free_skb;
        }

        skb->next = skb->prev = NULL;
        PROATM_SKB(skb)->buflen = 0;

        /* Queue the buffer in the SAR queue */
        PRINTK("proatm%d: Pushing buffer, paddr=%pad, handle=0x%08x\n",
                        card->index, &paddr, 0x80000000 | index);

        writel(0x80000000 | index, card->fbq[1]);       /* The handle cannot be null */
        writel(lower_32_bits(paddr), card->fbq[1]);

        /* If needed, reenable the free buffer queue interrupt */
        if (!card->fbie) {
                card->fbie = 1;
                writel((readl(card->membase + CFG) | PROATM_CFG_FBIE),
                                card->membase + CFG);
                PRINTK("proatm%d: reenable the free buffer queue interrupt\n", card->index);
        }
        spin_unlock_irqrestore(&card->res_lock, flags);
        return 0; /* normal return */

free_skb:
        pr_info("proatm%d: Freeing RX buffer.\n", card->index);
        spin_unlock_irqrestore(&card->res_lock, flags);
        return -1;
}

static struct sk_buff *alloc_rxbuf(proatm_dev *card, u32 index) {
        struct sk_buff *skb = NULL;

        skb = __dev_alloc_skb(PROATM_B1SKBSIZE, GFP_ATOMIC);

        if (skb) {
                PROATM_SKB(skb)->paddr = 0;
                PROATM_SKB(skb)->index = index;
                card->fb_table[index].skb_ptr = skb;
                if ((push_rxbuf(card, skb) == 0)) {
                        if (index >= card->fb_count)
                                card->fb_count++;
                }
                else {
                        /* push failed */
                        free_free_skb(card, skb);
                        card->fb_table[index].skb_ptr = NULL;
                        skb = NULL;
                }
        } else {
                /* could not allocate the skb */
                card->fb_table[index].skb_ptr = NULL;
                PRINTK ("proatm%d: could not allocate skb\n", card->index);
        }
        return skb;
}

/* Called by the ISR when too high traffic. Try to allocate more buffers */
static void queueAttentions(proatm_dev *card) {
        int i;
        struct sk_buff *skb;
        u32 sar_stat = readl(card->membase + STAT);

        PRINTK ("proatm%d: Queue attention.\n", card->index);

        if (sar_stat & PROATM_STAT_FBQ1A) {
                PRINTK("proatm%d: FBQ 1 attention.\n", card->index);
                /* fill the queue with min buffers */
                for (i = 0; i < MIN_B1; i++) {
                        skb = alloc_rxbuf(card, card->fb_count);
                        if (skb == NULL) {
                                /* if no more buffers available. Disable Q1A interrupt and give up */
                                /* this interrupt will be enabled again when buffers are recycled */
                                writel(readl(card->membase + CFG) & ~PROATM_CFG_FBIE,
                                                card->membase + CFG);
                                card->fbie = 0;
                                PRINTK("proatm%d: disable free buffer queue attention interrupt.\n", card->index);
                                break;
                        }
                }
        }
}

#define INT_FLAGS ( PROATM_STAT_TSIF  | \
                PROATM_STAT_TXICP | \
                PROATM_STAT_TSQF  | \
                PROATM_STAT_TMROF | \
                PROATM_STAT_PHYI  | \
                PROATM_STAT_RSQF  | \
                PROATM_STAT_EOPDU | \
                PROATM_STAT_RAWCF | \
                PROATM_STAT_FBQ0A | \
                PROATM_STAT_FBQ1A | \
                PROATM_STAT_RSQAF)

#define  CLEAR_FLAGS ( PROATM_STAT_TSIF | PROATM_STAT_TXICP | PROATM_STAT_TMROF \
                | PROATM_STAT_PHYI | PROATM_STAT_EOPDU | PROATM_STAT_RAWCF)

static irqreturn_t proatm_irq_handler(int irq, void *dev_id)
{
        u32 stat_r;
        unsigned long flags;
        proatm_dev *card = (proatm_dev *) dev_id;
        struct atm_dev *dev = card->atmdev;

        spin_lock_irqsave(&card->int_lock, flags);
        stat_r = readl(card->membase + STAT);

		/*
		* 0xffffffff is not a valid STAT value. It is what the PCIe
		* root complex synthesises when an MMIO read gets an Unsupported
		* Request or Completion Timeout, i.e. the endpoint is already gone:
		* surprise removal, link down, or fenced off after an IOMMU fault.
		*/
		if (unlikely(stat_r == 0xffffffff)) {
			if (!card->dead) {
				 card->dead = 1;
				 pr_err("proatm%d: device not responding (STAT=0x%08x); "
								 "PCIe link down or IOMMU fault - giving up.\n",
								 card->index, stat_r);
			}
			spin_unlock_irqrestore(&card->int_lock, flags);
			return IRQ_NONE;
		}

		if (card->dead || !(stat_r & INT_FLAGS)) {
			spin_unlock_irqrestore(&card->int_lock, flags);
			return IRQ_NONE; /* not for us */
        }

        PRINTK("proatm%d: proatm_irq_handler ()\n", card->index);
        XPRINTK("    cfg= 0x%08x, stat= 0x%08x\n", readl(card->membase + CFG), stat_r);

        card->intcnt++;

        writel(stat_r & CLEAR_FLAGS, card->membase + STAT); /* clear IRQ */

        /*
         * Complete CS-PDU received
         * Receive Status Queue is full
         * Receive Status Queue is 7/8 full
         * Queue Attentions
         */

        if (stat_r & (PROATM_STAT_EOPDU | PROATM_STAT_RSQF | PROATM_STAT_RSQAF
                        | PROATM_STAT_FBQ0A | PROATM_STAT_FBQ1A)) {

        	process_rsq(card);

        }
        /* Raw cell received */
        if (stat_r & PROATM_STAT_RAWCF) {
#ifdef PROATM_RCQ_SUPPORT
                pr_info("proatm%d: Raw cell received\n", card->index);
                process_rcq (card);
#else
                pr_err ("proatm%d: Unexpected raw cell received interrupt\n", card->index);
#endif
        }
        if (stat_r & (PROATM_STAT_FBQ0A | PROATM_STAT_FBQ1A))
                queueAttentions(card);

        /* Transmit Status Indicator has been written to T. S. Queue */
        if (stat_r & (PROATM_STAT_TSIF | PROATM_STAT_TSQF)) {
                process_tsq(card);
        }

        /* Incomplete CS-PDU has been transmitted */
        if (stat_r & PROATM_STAT_TXICP) {
                pr_err("proatm%d: Incomplete CS-PDU transmitted.\n", card->index);
        }

        /* Timer overflow */
        if (stat_r & PROATM_STAT_TMROF) {
                PRINTK("proatm%d: Timer overflow.\n", card->index);
                retreive_lost_buffers(card);
        }

        /* PHY device interrupt signal active */
        if (stat_r & PROATM_STAT_PHYI) {
                PRINTK("proatm%d: PHY interrupt.\n", card->index);
                suni_int(dev);
        }

        spin_unlock_irqrestore(&card->int_lock, flags);

        XPRINTK("proatm%d: end of interrupt service\n", card->index);

        return IRQ_HANDLED;
}

#ifdef PROATM_OAM_SUPPORT 
static int oam_type (u32 pti, u32 vci)
{
        if ( (pti == ATM_PTI_SEGF5) &&
                        (vci != 0x00) && (vci != 0x03) &&
                        (vci != 0x04) && (vci != 0x06) && (vci != 0x07) ) {
                PRINTK ("proatm: OAM F5 segment");
                return OAM_F5_SEG;
        }
        else if ( (pti == ATM_PTI_E2EF5) &&
                        (vci != 0x00) && (vci != 0x03) &&
                        (vci != 0x04) && (vci != 0x06) && (vci != 0x07) ) {
                PRINTK ("proatm: OAM F5 end-to-end");
                return OAM_F5_E2E;
        }
        else if (vci == 0x03) {
                PRINTK ("proatm: OAM F4 segment");
                return OAM_F4_SEG;
        }
        else if (vci == 0x04) {
                PRINTK ("proatm: OAM F4 end-to-end");
                return OAM_F4_E2E;
        }
        return 0;
}
#endif

#ifdef PROATM_PRINT_RAW_CELL_MESSAGE
#define rcmes()  pr_info("proatm%d: raw cell received on VC %d.%d, header = 0x%08x\n",  \
                card->index, vpi, vci, rawcell->word_1)
#else
#define rcmes()
#endif

/* Process one raw cell */

#ifdef PROATM_RCQ_SUPPORT
static int process_rcqe(proatm_dev *card, proatm_rcqe *rawcell) {
#if defined(PROATM_OAM_SUPPORT) || defined(PROATM_AAL0_TO_RCQ)
        u32 vci, vpi, pti;
        vpi = (le32_to_cpu(rawcell->word_1) & 0x0FF00000) >> 20;
        vci = (le32_to_cpu(rawcell->word_1) & 0x000FFFF0) >> 4;
        pti = (le32_to_cpu(rawcell->word_1) & 0x0E) >> 1;
#endif

        PRINTK("proatm%d: process_rcqe ()\n", card->index);

#ifdef PROATM_OAM_SUPPORT
        if (oam_type(pti, vci)) {
                u32 conn = find_conn (card, vpi, vci);
                vc_map *vc = &(card->vcmap[conn]);

                PRINTK(" cell received on %d.%d\n", vpi, vci);

                if (!vc->rx) {
                        rcmes(); /* we don't support oam if connection is not open */
                        return 0;
                }

                rawcell->word_2 = conn;
                if (process_oam_cell (card, rawcell))
                        rcmes();
                return 0;
        }
#endif

#ifdef PROATM_AAL0_TO_RCQ
        {
                struct atm_vcc *vcc;
                vc_map *vc;
                struct sk_buff *sb;
                u32 conn;

                conn = find_conn (card, vpi, vci);
                vc = &(card->vcmap[conn]);
                if (!vc->rx) {
                        rcmes(); /* connection is not open */
                        return 0;
                }

                vcc = vc->rx_vcc;

                if (vcc->qos.aal != ATM_AAL0) {
                        rcmes(); /* give up if not AAL0 */
                        return 0;
                }
                sb = dev_alloc_skb(PROATM_RCQE_SIZE);
                if (!sb) {
                        pr_err("proatm%d: Can't allocate buffers for aal0 raw cell.\n", card->index);
                        atomic_add(1, &vcc->stats->rx_drop);
                        return -1;
                }
                if (!atm_charge(vcc, sb->truesize)) {
                        PRINTK("proatm%d: atm_charge() dropped aal0 raw cell.\n", card->index);
                        dev_kfree_skb_any(sb);
                        return -1;
                }
                *((u32 *)sb->data) = le32_to_cpu(rawcell->word_1);
                skb_put(sb, PROATM_AAL0_HEADER);
                memcpy(skb_put(sb, ATM_CELL_PAYLOAD), rawcell->payload, ATM_CELL_PAYLOAD);

                /* transmit the received SKB to ATM protocol layers */
                PUSH_ATM(vcc, sb);

                atomic_inc(&vcc->stats->rx);
                return 0;
        }
#endif
        rcmes();
        return 0;
}

/* 
 * Process Raw Cell Queue 
 */
static void process_rcq(proatm_dev *card)
{
        u32 tail_pa;
        u32 index;
        struct sk_buff *skb = card->rcbuf;

        PRINTK("proatm%d: process_rcq ()\n", card->index);

        if (card->raw_hnd[0] == 0 || card->rcbuf == NULL)
                return;

        pci_dma_sync_single_for_cpu(card->pcidev, PROATM_SKB(skb)->paddr,
                                                        skb_end_ptr(skb) - skb->data, PCI_DMA_FROMDEVICE);

        tail_pa = le32_to_cpu(card->raw_hnd[0]) + PROATM_RCQE_SIZE;

        PRINTK ("proatm%d: index = %d\n", card->index, PROATM_SKB(skb)->index);
        PRINTK ("SAR Queue: %d\n", proatm_fbqc_get(readl(card->membase + FBQP1)));

        while (card->raw_ch_pa != tail_pa) {
                if (rcqe_islast(card->raw_ch)) {
                        if ((u8*) card->raw_ch
                                        < (skb->data + PROATM_B1BUFSIZE - PROATM_RCQE_SIZE)) {
                                /* Sorry, it is not the last raw cell of this skb */
                                pr_err("proatm%d: last raw cell indication error.\n", card->index);
                                return;
                        }
                        index = rcqe_nextbufhandle(card->raw_ch); /* get next buffer type and handle */
                        index &= (MAX_B1 - 1);
                        recycle_rxbuf(card, skb); /* recycle old skb */

                        /* Monitoring lost buffers */
                        card->fb_table[index].sar_count++;
                        if (card->fb_dequeue_count < MAX_B1 * 3)
                                card->fb_dequeue_count++;

                        PRINTK("proatm%d: next index = %d\n", card->index, index);

                        skb = card->rcbuf = card->fb_table[index].skb_ptr; /* get next SKB address */

                        if (skb == NULL) {
                                pr_err( "proatm%d: raw cell queue error. Stop receiving raw cells.\n",
                                                card->index);
                                return;
                        }

                        card->raw_ch = (proatm_rcqe*)(skb->data);
                        card->raw_ch_pa = PROATM_SKB(skb)->paddr;

                        pci_dma_sync_single_for_cpu(card->pcidev, PROATM_SKB(skb)->paddr,
                                        skb_end_ptr(skb) - skb->data, PCI_DMA_FROMDEVICE);
                } else {
                        if ((u8*) card->raw_ch
                                        >= (skb->data + PROATM_B1BUFSIZE - PROATM_RCQE_SIZE)) {
                                /* Sorry, it is the last raw cell of this skb */
                                pr_err("proatm%d: raw cell queue chaining error.\n", card->index);
                                return;
                        }
                        PRINTK("cell = %d\n", (int)(card->raw_ch - (proatm_rcqe*)(skb->data)));
                        process_rcqe(card, card->raw_ch);
                        card->raw_ch++;
                        card->raw_ch_pa += PROATM_RCQE_SIZE;
                }
        }
}
#endif /* PROATM_RCQ_SUPPORT */

/* Compute (scr<<16)/pcr */
static u16 scr_on_pcr(u32 scr, u32 pcr) {
        u32 q = 0;

        scr <<= 8;
        q = scr / pcr;
        scr = (scr % pcr) << 8;
        return (q << 8) | (scr / pcr);
}

/* translate rate to ATM Forum rate 
 * 2^e (1+m/512) = rate   => m = rate * 2^(9-e) - 512
 */
static u16 u32_to_afr(u32 rate) {
        u16 m, e;
        u32 mask;

        if (rate == 0)
                return 0;
        for (mask = 0x80000000, e = 31; mask; mask >>= 1, e--)
                if (rate & mask)
                        break;
        m = e >= 9 ? rate >> (e - 9) : rate << (9 - e);
        m -= 512;

        return (1 << 14 | e << 9 | m);
}
/* 
 * First transform rate to ATM fixed point format (afr)
 * Then find the index corresponding to afr into log_to_conv table
 */
static u8 rate2log(proatm_dev *card, u32 rate) {
        u16 afr_rate = u32_to_afr(rate), afr_value;
        u32 data;
        u8 lower = 0, upper = 255, mid;
        u32 * rate_table;

        rate_table = abr_vbr_rate_tables_155Mb;

        /*
         * Could use the rate2log table but this content
         * research gives better precision
         */
        while (1) {
                mid = (u8)((lower + upper) >> 1);
                data = rate_table[mid];
                afr_value = (u16)(data >> 17); /* extract cps from table */

                if ((afr_rate == afr_value) || (upper <= lower))
                        break;
                else if (afr_rate > afr_value)
                        lower = mid + 1;
                else
                        upper = mid - 1;
        }
        if (afr_value > afr_rate)
                mid--;

        return (mid);
}

static int init_tx_cbr(proatm_dev *card, vc_map *vc, struct atm_vcc *vcc,
                int conn) {
        scq_info *scq;
        int j;
        u32 u32d[8];
        int tcr, tcra; /* target cell rate, and absolute value */
        int n = 0; /* Number of entries in the TST */
        u32 tmpl, modl;
        int scdi = 0; /* SCD Index */

        PRINTK("proatm%d: init_tx_cbr ()\n", card->index);
        XPRINTK ("    pcr= 0x%x, max_pcr= 0x%x, min_pcr= 0x%x\n",
                        vcc->qos.txtp.pcr, vcc->qos.txtp.max_pcr, vcc->qos.txtp.min_pcr);

        /* Check requested cell rate and availability of SCD */
        if (vcc->qos.txtp.max_pcr == 0 && vcc->qos.txtp.pcr == 0
                        && vcc->qos.txtp.min_pcr == 0) {
                pr_err("proatm%d: trying to open a CBR vc with cell rate = 0 \n",
                                card->index);
                return -EINVAL;
        }
        /* Cell rate computation */
        tcr = atm_pcr_goal(&(vcc->qos.txtp));
        tcra = tcr >= 0 ? tcr : -tcr;
        PRINTK("proatm%d: target cell rate = %d.\n", card->index, tcr);
        tmpl = (u32) tcra * (u32) card->tst_num;
        modl = tmpl % card->max_pcr;
        n = (int) (tmpl / card->max_pcr);
        if (tcr > 0) {
                if (modl > 0)
                        n++;
        } else if (tcr == 0) {
                if ((n = (card->tst_free_entries - PROATM_TST_RESERVED)) <= 0) {
                        pr_err("proatm%d: no CBR bandwidth free.\n", card->index);
                        return -EINVAL;
                }
        }
        if (n == 0) {
                pr_err("proatm%d: selected bandwidth < granularity.\n", card->index);
                return -EINVAL;
        }
        if (n > (card->tst_free_entries - PROATM_TST_RESERVED)) {
                pr_err("proatm%d: not enough free CBR bandwidth.\n", card->index);
                return -EINVAL;
        } else
                card->tst_free_entries -= n;

        PRINTK("proatm%d: writing %d tst entries.\n", card->index, n);

        /* get SCD */
        for (scdi = 0; scdi < PROATM_SCD_NUM; scdi++) {
                if (card->scd2vc[scdi] == NULL) {
                        card->scd2vc[scdi] = vc;
                        break;
                }
        }
        if (scdi == PROATM_SCD_NUM) {
                pr_err("proatm%d: no SCD available for CBR channel.\n", card->index);
                card->tst_free_entries += n;
                return -EBUSY;
        }
        vc->adr_scd = card->scd + scdi * PROATM_SCD_ENTRY_SIZE;

        /* get SCQ */
        scq = get_scq(card, CBR_SCQ, vc->adr_scd);
        if (scq == (scq_info *) NULL) {
                pr_err("proatm%d: can't get Fixed Rate SCQ.\n", card->index);
                card->scd2vc[scdi] = NULL;
                card->tst_free_entries += n;
                return -ENOMEM;
        }
        vc->scq = scq;

        /* set up SCD */
        u32d[0] = scq->base_pa;
        u32d[1] = (u32) 0x00000000;
        u32d[2] = (u32) 0xffffffff;
        u32d[3] = (u32) 0x00000000;
        write_sram(card, vc->adr_scd, u32d, 4);

        /* initialize the cache */
        for (j = 1; j < 3; j++) {
                u32d[0] = (u32) 0x00000000;
                u32d[1] = (u32) 0x00000000;
                u32d[2] = (u32) 0x00000000;
                u32d[3] = (u32) 0x00000000;
                write_sram(card, vc->adr_scd + 4 * j, u32d, 4);
        }
        PRINTK("proatm%d: proatm_open (), CBR scd written at:0x%x\n", card->index, vc->adr_scd);

        /* set up TCT */
        u32d[0] = PROATM_TCTE_TYPE_CBR | vc->adr_scd;
        u32d[1] = 0x00000000;
        u32d[2] = 0x00000000;
        u32d[3] = 0x00000000;
        write_sram(card, (conn * 8), u32d, 4);
        u32d[4] = 0x00000000;
        u32d[5] = 0x00000000;
        u32d[6] = 0x00000000;
        u32d[7] = 0x00000000;
        write_sram(card, (conn * 8) + 4, u32d + 4, 4);

        PRINTK("proatm: a (type CBR)TCT entry written: word_1= 0x%x.\n", u32d[0]);

        /* set up TST */
        fill_tst(card, n, vc, conn);

        return 0;
}

static int init_tx_ubr(proatm_dev *card, vc_map *vc, struct atm_vcc *vcc,
                int conn) {
        scq_info *scq;
        int j;
        u32 u32d[8];
        u32 max_pcr = 0;
        int scdi = 0; /* SCD Index */
        int fubr0 = 0;

        PRINTK("proatm%d: init_tx_ubr ()\n", card->index);
        XPRINTK ("    pcr= 0x%x, max_pcr= 0x%x\n", vcc->qos.txtp.pcr, vcc->qos.txtp.max_pcr);

        if (vcc->qos.txtp.pcr == 0) {
                PRINTK("proatm%d: trying to open a UBR0 vc (unspecified PCR)\n", card->index);
                vc->init_er = 0xff;
                vc->lacr = 0xff;
                fubr0 = 1;
        } else {
                max_pcr = vcc->qos.txtp.max_pcr;
                if (max_pcr < vcc->qos.txtp.pcr)
                        max_pcr = vcc->qos.txtp.pcr;

                vc->init_er = rate2log(card, max_pcr);
                vc->lacr = rate2log(card, vcc->qos.txtp.pcr);

                XPRINTK ("    max_pcr= 0x%x, init_er= 0x%x, lacr= 0x%x\n",
                                max_pcr, vc->init_er, vc->lacr);
                if (vc->init_er == 0xff && vc->lacr == 0xff)
                        fubr0 = 1;
        }
        if (fubr0) {
                vc->adr_scd = card->scd_ubr0;
                vc->scq = card->scq0; /* no need to update */
        } else {
                /* UBR with specific pcr value. We use a specific SCD */

                /* get SCD */
                for (scdi = 0; scdi < PROATM_SCD_NUM; scdi++) {
                        if (card->scd2vc[scdi] == NULL) {
                                card->scd2vc[scdi] = vc;
                                break;
                        }
                }
                if (scdi == PROATM_SCD_NUM) {
                        pr_err("proatm%d: no SCD available for UBR channel.\n",
                                        card->index);
                        return -EBUSY;
                }
                vc->adr_scd = card->scd + scdi * PROATM_SCD_ENTRY_SIZE;
                scq = get_scq(card, UBR_SCQ, vc->adr_scd);
                if (scq == (scq_info *) NULL) {
                        pr_err("proatm%d: can't get UBR SCQ.\n", card->index);
                        card->scd2vc[scdi] = NULL;
                        return -ENOMEM;
                }
                vc->scq = scq;

                /* set up SCD */
                u32d[0] = scq->base_pa;
                u32d[1] = (u32) 0x00000000;
                u32d[2] = (u32) 0xffffffff;
                u32d[3] = (u32) 0x00000000;
                write_sram(card, vc->adr_scd, u32d, 4);

                /* initialize the cache */
                for (j = 1; j < 3; j++) {
                        u32d[0] = (u32) 0x00000000;
                        u32d[1] = (u32) 0x00000000;
                        u32d[2] = (u32) 0x00000000;
                        u32d[3] = (u32) 0x00000000;
                        write_sram(card, vc->adr_scd + 4 * j, u32d, 4);
                }
                XPRINTK("proatm: UBR scd written at:0x%x\n", (int)vc->adr_scd);
        }

        /* finaly create the UBR entry in the TCT */
        u32d[0] = PROATM_TCTE_TYPE_UBR | vc->adr_scd;
        u32d[1] = 0x00000000;
        u32d[2] = 0x00000000;
        u32d[3] = PROATM_TCTE_HALT | PROATM_TCTE_IDLE;
        write_sram(card, (conn * 8), u32d, 4);
        u32d[4] = 0x00000000;
        u32d[5] = 0x00000000;
        u32d[6] = 0x00000000;
        u32d[7] = 0x80000000;
        write_sram(card, (conn * 8) + 4, u32d + 4, 4);

        XPRINTK ("proatm: a (type UBR) TCT entry written at 0x%lx:\n",
                        (unsigned long)(card->membase + (conn * 8)));
        return 0;
}

/*
 * VBR is normally not supported by Linux. A VBR QoS is defined by 3 parameters
 * PCR, SCR and MBS.  To set a VBR connection, pass the VBR parameters into the
 * standard traffic parameters of the atm_trafprm stucture as following:
 *
 *      traffic_class = ATM_VBR (3),
 *      max_pcr = the needed PCR value, 
 *      pcr =  the needed SCR value,
 *      min_pcr = the needed MBS value (1 to 255)
 */

static int init_tx_vbr(proatm_dev *card, vc_map *vc, struct atm_vcc *vcc,
                int conn) {
        scq_info *scq;
        int j;
        u32 u32d[8];
        u16 pcr_token;
        int scdi = 0; /* SCD Index */
        int scr, pcr, mbs;

        PRINTK("proatm%d: init_tx_vbr ()\n", card->index);
        XPRINTK ("    max_pcr= 0x%x, pcr= 0x%x, min_pcr= 0x%x\n",
                        vcc->qos.txtp.max_pcr, vcc->qos.txtp.pcr, vcc->qos.txtp.min_pcr);

        mbs = vcc->qos.txtp.min_pcr; /* use min_pcr as MBS */
        scr = vcc->qos.txtp.pcr; /* use pcr as SCR */
        pcr = vcc->qos.txtp.max_pcr; /* use max_pcr as PCR */

        if (mbs <= 0 || mbs > 255) {
                pr_err("proatm%d: trying to open a VBR vc with invalid MBS: %d\n",
                                card->index, mbs);
                return -EINVAL;
        }
        if (scr <= 0 || scr > pcr) {
                pr_err( "proatm%d: trying to open a VBR vc with SCR null or higher than PCR: %d\n",
                                card->index, scr);
                return -EINVAL;
        }
        if (pcr <= 0 || pcr > card->max_pcr) {
                pr_err("proatm%d: trying to open a VBR vc with invalid PCR, %d\n",
                                card->index, pcr);
                return -EINVAL;
        }

        /* get SCD */
        for (scdi = 0; scdi < PROATM_SCD_NUM; scdi++) {
                if (card->scd2vc[scdi] == NULL) {
                        card->scd2vc[scdi] = vc;
                        break;
                }
        }
        if (scdi == PROATM_SCD_NUM) {
                pr_err("proatm%d: no SCD available for VBR channel.\n", card->index);
                return -EBUSY;
        }
        vc->adr_scd = card->scd + scdi * PROATM_SCD_ENTRY_SIZE;
        scq = get_scq(card, VBR_SCQ, vc->adr_scd);
        if (scq == (scq_info *) NULL) {
                pr_err("proatm%d: can't get VBR SCQ.\n", card->index);
                card->scd2vc[scdi] = NULL;
                return -ENOMEM;
        }
        vc->scq = scq;

        /* set up SCQ */
        u32d[0] = scq->base_pa;
        u32d[1] = u32d[0] & 0x00001FF0;
        if ((u32d[0] & 0x00001FFF) != u32d[1])
                pr_err("proatm%d: error in scq->base address \n", card->index);

        u32d[2] = (u32) 0xffffffff;
        u32d[3] = (u32) 0x00000000;
        write_sram(card, vc->adr_scd, u32d, 4);
        /* initialize the cache */
        for (j = 1; j < 3; j++) {
                u32d[0] = (u32) 0x00000000;
                u32d[1] = (u32) 0x00000000;
                u32d[2] = (u32) 0x00000000;
                u32d[3] = (u32) 0x00000000;
                write_sram(card, vc->adr_scd + 4 * j, u32d, 4);
        }
        XPRINTK("proatm: VBR scd written at:0x%x\n", vc->adr_scd);

        /* create the VBR entry in the TCT */
        pcr_token = scr_on_pcr(scr, pcr);
        if (pcr_token == 0)
                pcr_token++;
        vc->init_er = rate2log(card, pcr);
        vc->lacr = (u32) rate2log(card, scr);

        u32d[0] = PROATM_TCTE_TYPE_VBR | vc->adr_scd;
        u32d[1] = 0x00000000;
        u32d[2] = PROATM_TCTE_TSIF;
        u32d[3] = PROATM_TCTE_HALT | PROATM_TCTE_IDLE;
        write_sram(card, (conn * 8), u32d, 4);

        u32d[4] = 0x7F << 24; /* max IdleCount = 0x7f*/
        u32d[5] = 0x01 << 24; /* token bucket integer portion=1*/
        u32d[6] = (mbs << 16) | pcr_token; /* icr is mbs */
        u32d[7] = 0x00000000;
        write_sram(card, (conn * 8) + 4, u32d + 4, 4);

        XPRINTK ("proatm: a (type VBR)TCT entry written at 0x%lx:\n",
                        (unsigned long)(card->membase + (conn * 8)));

        return 0;
}

static int init_tx_abr(proatm_dev *card, vc_map *vc, struct atm_vcc *vcc,
                int conn) {
        scq_info *scq;
        int j;
        u32 u32d[8];
        u32 air_table, rdf_table, cdf_table;
        u32 norm_age_count, uili, acrc, lmcr, crm;
        int scdi = 0; /* SCD Index */

        PRINTK("proatm%d: init_tx_vbr ()\n", card->index);
        XPRINTK ("    pcr= 0x%x, max_pcr= 0x%x, min_pcr= 0x%x\n",
                        vcc->qos.txtp.pcr, vcc->qos.txtp.max_pcr, vcc->qos.txtp.min_pcr);
        XPRINTK ("    rif= 0x%x, rdf= Ox%x, cdf= 0x%x\n",
                                        vcc->qos.txtp.rif, vcc->qos.txtp.rdf, vcc->qos.txtp.cdf);

                        if ((vcc->qos.txtp.max_pcr < vcc->qos.txtp.pcr)
                                        || (vcc->qos.txtp.pcr < vcc->qos.txtp.min_pcr)) {
                                pr_err( "proatm%d: ABR vc should have max_pcr >= pcr >= min_pcr: %d, %d, %d\n",
                                                card->index, vcc->qos.txtp.max_pcr, vcc->qos.txtp.pcr,
                                                vcc->qos.txtp.min_pcr);
                                return -EINVAL;
                        }

                        /* get SCD */
                        for (scdi = 0; scdi < PROATM_SCD_NUM; scdi++) {
                                if (card->scd2vc[scdi] == NULL) {
                                        card->scd2vc[scdi] = vc;
                                        break;
                                }
                        }
                        if (scdi == PROATM_SCD_NUM) {
                                pr_err("proatm%d: no SCD available for ABR channel.\n", card->index);
                                return -EBUSY;
                        }
                        vc->adr_scd = card->scd + scdi * PROATM_SCD_ENTRY_SIZE;

                        /* get SCQ */
                        scq = get_scq(card, ABR_SCQ, vc->adr_scd);
                        if (scq == (scq_info *) NULL) {
                                pr_err("proatm%d: can't get abr SCQ.\n", card->index);
                                card->scd2vc[scdi] = NULL;
                                return -ENOMEM;
                        }
                        vc->scq = scq;

                        /* set up SCD */
                        u32d[0] = scq->base_pa;
                        u32d[1] = (u32) u32d[0] & 0x00001FF0;
                        if ((u32d[0] & 0x00001FFF) != u32d[1])
                                pr_err("proatm: proatm_open, error in scq->base address \n");

                        u32d[2] = (u32) 0xffffffff;
                        u32d[3] = (u32) 0x00000000;
                        write_sram(card, vc->adr_scd, u32d, 4);
                        /* initialize the cache */
                        for (j = 1; j < 3; j++) {
                                u32d[0] = (u32) 0x00000000;
                                u32d[1] = (u32) 0x00000000;
                                u32d[2] = (u32) 0x00000000;
                                u32d[3] = (u32) 0x00000000;
                                write_sram(card, vc->adr_scd + 4 * j, u32d, 4);
                        }
                        XPRINTK("proatm: ABR SCD written at:0x%x\n", vc->adr_scd);

                        /* create ABR entry in the TCT */
                        vc->init_er = rate2log(card, vcc->qos.txtp.max_pcr);
                        lmcr = (u32) rate2log(card, vcc->qos.txtp.min_pcr);
                        vc->lacr = (u32) rate2log(card, vcc->qos.txtp.pcr);
                        air_table = 4 + 15 - (vcc->qos.txtp.rif);
                        rdf_table = 20 + 15 - (vcc->qos.txtp.rdf);

                        if (vcc->qos.txtp.cdf_pres)
                                cdf_table = 20 + 15 - (vcc->qos.txtp.cdf);
                        else
                                cdf_table = rdf_table;

                        norm_age_count = 4;
                        uili = 0; /* use it or lose it */
                        acrc = (u8)(vcc->qos.txtp.adtf_pres);
                        crm = 4;

                        u32d[0] = PROATM_TCTE_TYPE_ABR | acrc << 28 | uili << 26 | vc->adr_scd;
                        u32d[1] = crm << 29;
                        u32d[2] = PROATM_TCTE_TSIF;
                        u32d[3] = (PROATM_TCTE_HALT | PROATM_TCTE_IDLE) | (norm_age_count << 21);
                        write_sram(card, (conn * 8), u32d, 4);

                        u32d[4] = (lmcr << 24);
                        u32d[5] = ((cdf_table & 0x3F) << 20) | ((rdf_table & 0x3F) << 14)
                                        | ((air_table & 0x3F) << 8);
                        u32d[6] = 0x00000000;
                        u32d[7] = 0x00000000;
                        write_sram(card, (conn * 8) + 4, u32d + 4, 4);

                        return 0;
}

/*  Check VPI and VCI  */

static int check_vc(proatm_dev *card, short vpi, int vci) {
        u16 vp = (u16) vpi;
        u32 vc = (u32) vci;

        /* vci check */
        if ((vc >> card->vcibits) != (card->vpm & (0xffff >> card->vcibits))) {
                return -EINVAL;
        }
        /* vpi check */
        if ((vp >> card->vpibits) != (card->vpm >> (16 - card->vcibits))) {
                return -EINVAL;
        }
        return 0;
}

static inline int find_conn(proatm_dev *card, short vpi, int vci) {
        return ((vpi & card->maxvpi) << card->vcibits) | (vci & card->maxvci);
}

static int proatm_open(struct atm_vcc *vcc) {
        proatm_dev *card;
        vc_map *vc;
        int error;
        int inuse; /* tx or rx vc already in use by another vcc */
        u32 conn;
        unsigned char traffic_class;

        /* the connection number used to index the TCT */
        card = (proatm_dev *) vcc->dev->dev_data;

        PRINTK("\nproatm%d: proatm_open ()\n", card->index);

        if (vcc->qos.aal != ATM_AAL5 && vcc->qos.aal != ATM_AAL0) {
                pr_err("proatm%d: unsupported AAL.\n", card->index);
                return -EINVAL;
        }

        PRINTK("proatm%d: opening vpi.vci %d.%d \n", card->index, (int) vcc->vpi, vcc->vci);
        conn = find_conn(card, vcc->vpi, vcc->vci);

        vc = &(card->vcmap[conn]);

        inuse = 0;
        if (vcc->qos.txtp.traffic_class != ATM_NONE && vc->tx)
                inuse += 1;

        if (vcc->qos.rxtp.traffic_class != ATM_NONE && vc->rx)
                inuse += 2;

        if (inuse) {
                pr_err("proatm%d: %s vci already in use.\n", card->index,
                                inuse == 1 ? "tx" : inuse == 2 ? "rx" : "tx and rx");
                return -EINVAL;
        }

        memset(vc, 0, sizeof(vc_map));

        vcc->dev_data = vc;
        set_bit(ATM_VF_ADDR, &vcc->flags);

        if (vcc->qos.txtp.traffic_class != ATM_NONE) {

                /* NOTE: You are not allowed to modify an open connection's QOS. To change
                 * that, remove the ATM_VF_PARTIAL flag checking. There may be other changes
                 * needed to do that.
                 */

                vc->tx = 1;
                vc->tx_vcc = vcc;
                vc->tbd_count = 0;

                if (!test_bit(ATM_VF_PARTIAL, &vcc->flags)) {

                        set_bit(ATM_VF_PARTIAL, &vcc->flags);

                        traffic_class = vcc->qos.txtp.traffic_class;

                        switch (traffic_class) {

                        case ATM_CBR:
                                PRINTK("proatm%d: trying to open CBR vc.\n", card->index);
                                error = init_tx_cbr(card, vc, vcc, conn);
                                break;

                        case (ATM_UBR):
                                PRINTK("proatm%d: trying to open UBR vc.\n", card->index);
                                error = init_tx_ubr(card, vc, vcc, conn);
                        break;

                        case (ATM_VBR):
                                PRINTK("proatm%d: trying to open VBR vc.\n", card->index);
                                error = init_tx_vbr(card, vc, vcc, conn);
                        break;

                        case (ATM_ABR):
                                PRINTK("proatm%d: trying to open ABR vc.\n", card->index);
                                error = init_tx_abr(card, vc, vcc, conn);
                        break;

                        default:
                                pr_err("proatm%d: Unknown traffic class, %d\n", card->index,
                                                traffic_class);
                                return -EPROTONOSUPPORT;

                        } /* switch (traffic_class) */

                        if (error) {
                                clear_bit(ATM_VF_PARTIAL, &vcc->flags);
                                clear_bit(ATM_VF_ADDR, &vcc->flags);
                                return error;
                        }

                        /* start scheduling for non CBR VC*/
                        if (vcc->qos.txtp.traffic_class != ATM_CBR) {
                                vc->active = 1;
                                PRINTK ("proatm: start scheduling connection %d\n", conn);
                                writel(PROATM_UPD_INIT_ER | ((u32) vc->init_er) << 16 | conn,
                                                card->membase + TCMDQ);
                                writel(PROATM_START_UPD_LACR | ((u32) vc->lacr) << 16 | conn,
                                                card->membase + TCMDQ);

                                mdelay(1);
                        }
                }
        }

        if (vcc->qos.rxtp.traffic_class != ATM_NONE) {
                u32 status;

                vc->rx = 1;
                vc->rx_vcc = vcc;
                vc->rx_sdu_tail = NULL;
                vc->rx_sdu_head = NULL;
                vc->bufcnt = 0;
                vc->totlen = 0;

                /* Open the receive connection */
                if (vcc->qos.aal == ATM_AAL5)
                        status = PROATM_RCTE_FBP1 | PROATM_RCTE_AAL5
                        | PROATM_RCTE_CONNECTOPEN;
                else
                        /* vcc->qos.aal == ATM_AAL0 */
#if defined (PROATM_RCQ_SUPPORT) && (defined PROATM_AAL0_TO_RCQ)
                        /*USE RAW CELL QUEUE*/
                        status = PROATM_RCTE_RAWCELLINTEN | PROATM_RCTE_RCQ | PROATM_RCTE_CONNECTOPEN;
#else
                /* use normal queue */
                status = PROATM_RCTE_FBP1 | PROATM_RCTE_AAL0
                                | PROATM_RCTE_CONNECTOPEN;
#endif

#ifdef PROATM_RCQ_SUPPORT
                status |= PROATM_RCTE_RAWCELLINTEN;
#endif
                write_sram(card, card->rct + conn * PROATM_RCT_ENTRY_SIZE, &status, 1);

        }

        set_bit(ATM_VF_READY, &vcc->flags);
        return 0;
}

static void retreive_lost_buffers(proatm_dev *card) {
        int i, n = 0;
        fb_info *fbip;

        PRINTK ("proatm%d: retreive_lost_buffers ()\n", card->index);

        if (card->fb_dequeue_count >= FB_MAXDQC) {
                PRINTK ("proatm%d: look for lost buffers ()\n", card->index);
                for (i = 0, fbip = card->fb_table; i < card->fb_count; i++, fbip++) {
                        if (fbip->skb_ptr != NULL) {
                                if ((fbip->skb_ptr != card->rcbuf) && (fbip->sar_count == 0)) {
                                        /* this must be a buffer lost by the SAR. Try to push it */
                                        push_rxbuf(card, fbip->skb_ptr);
                                        n++;
                                }
                        } else {
                                /* fill the holes in the table*/
                                alloc_rxbuf (card, i);
                                n++;
                        }
                        fbip->sar_count = 0;
                }
                card->fb_dequeue_count = 0;
        }
        if (n)
                pr_info ("proatm%d: %d buffer retreived\n", card->index, n);
}

/* close connection */
static void proatm_close(struct atm_vcc *vcc) {
        vc_map *vc = vcc->dev_data;
        proatm_dev *card = vcc->dev->dev_data;
        u32 data;
        int i;
        int conn = find_conn(card, vcc->vpi, vcc->vci);
        unsigned long flags;
        proatm_scqe *scqep;
        scq_info *scq;
        u32 u32d[4];

        PRINTK ("\nproatm%d: proatm_close (vpi.vci %d.%d)\n", card->index,
                        (int) vcc->vpi, vcc->vci);

        if (!vc) {
                pr_err(
                                "proatm%d: trying to close an already closed VC: vpi.vci %d.%d)\n",
                                card->index, (int) vcc->vpi, vcc->vci);
                return;
        }
        spin_lock_irqsave(&card->res_lock, flags);
        if (vc->closing) {
                pr_err("proatm%d: trying to close a closing VC: vpi.vci %d.%d)\n",
                                card->index, (int) vcc->vpi, vcc->vci);
                spin_unlock_irqrestore(&card->res_lock, flags);
                return;
        }
        vc->closing = 1;
        spin_unlock_irqrestore(&card->res_lock, flags);
        clear_bit(ATM_VF_READY, &vcc->flags);

        if (vcc->qos.rxtp.traffic_class != ATM_NONE) {
                u32 addr; /* word address */

                /* stop receive part */
                addr = card->rct + conn * PROATM_RCT_ENTRY_SIZE;
                spin_lock_irqsave(&card->res_lock, flags);
                waitfor_notbusy(card);
                //writel(PROATM_CMD_CLOSE_CONNECTION | (conn << 4), card->membase + CMD);
                vc->rx = 0;
                spin_unlock_irqrestore(&card->res_lock, flags);

                /* Erase RCT entry */
#ifdef PROATM_RCQ_SUPPORT
                u32d[0] = PROATM_RCTE_RAWCELLINTEN;
#else
                u32d[0] = 0x00000000;
#endif
                u32d[1] = 0x00000000;
                u32d[2] = 0x00000000;
                u32d[3] = 0xFFFFFFFF;
                write_sram(card, addr, u32d, 4);

                if (vc->rx_sdu_tail != NULL) {
                        pr_err("proatm%d: closing a VC with pending rx buffers.\n",
                                        card->index);
                        spin_lock_irqsave(&card->int_lock, flags);
                        recycle_rxbufs(card, vc->rx_sdu_head);
                        spin_unlock_irqrestore(&card->int_lock, flags);
                        vc->rx_sdu_tail = NULL;
                }
        }
        if (vcc->qos.txtp.traffic_class != ATM_NONE) {
                vc->tx = 0;

                /* stop the connection transmit part if not UBR0 */
                scq = vc->scq;
                if (scq != card->scq0) {
                        if (scq != NULL) {
                                /* wait until scq is empty */
                                for (;;) {
                                        spin_lock_irqsave(&scq->lock, flags);
                                        scqep = scq->next;
                                        if (scqep == scq->base)
                                                scqep = scq->last;
                                        else
                                                scqep--;

                                        if (scqep == scq->tail) {
                                                spin_unlock_irqrestore(&scq->lock, flags);
                                                break; /* scq is empty */
                                        }
                                        spin_unlock_irqrestore(&scq->lock, flags);
                                        schedule();
                                }

                                if (vcc->qos.txtp.traffic_class == ATM_CBR) {
                                        /* Free all TST entries */
                                        data = PROATM_TST_OPCODE_VARIABLE;
                                        for (i = 0; i < card->tst_num; i++) {
                                                if (card->tste2vc[i] == vc) {
                                                        write_sram(card, card->tst + i, &data, 1);
                                                        card->tste2vc[i] = NULL;
                                                        card->tst_free_entries++;
                                                }
                                        }
                                } else {
                                        u32 d;
                                        d = read_sram(card, conn * 8 + 3);
                                        if (!(d & PROATM_TCTE_IDLE)) {
                                                writel(PROATM_HALT | conn, card->membase + TCMDQ);
                                        }
                                }
                                card->scd2vc[(vc->adr_scd - card->scd) / PROATM_SCD_ENTRY_SIZE] = NULL;
                                free_scq(card, vc->scq, vcc);
                                vc->scq = NULL;
                        }
                }
                else {
                        /* Special process for UBR0 connection (scq = scq0)
                         * Wait for end of transmission of all sk_buff
                         * still attached to the closing connection
                         */
                        u32 d;
                        int stat = 1, n;
                        struct sk_buff *skb;

                        for (n = 0; stat && (n < 100); n++) {
                                msleep(5);
                                spin_lock_irqsave(&scq->lock, flags);
                                stat = 0;

                                /* Look for packets in the scq0 waiting queue */
                                for (skb = scq->waiting.next;
                                                !stat && (skb != (struct sk_buff *) &scq->waiting);
                                                skb = skb->next) {
                                        if (ATM_SKB(skb)->vcc == vcc) {
                                                stat = 1;
                                                PRINTK ("proatm%d: tx buffer for VC %d.%d in SCQ0 waiting queue\n",
                                                                                        card->index, (int) vcc->vpi, vcc->vci);
                                        }
                                }

                                /* Look for packets in scq0 */
                                for (i=0; !stat && (i < SCQ_NUM_ENTRIES); i++) {
                                        if (scq->skb[i] && ATM_SKB(scq->skb[i])->vcc == vcc) {
                                                stat = 0x100 | i;
                                                PRINTK ("proatm%d: tx buffer for VC %d.%d in SCQ0\n",
                                                                                        card->index, (int) vcc->vpi, vcc->vci);
                                        }
                                }
                                spin_unlock_irqrestore(&scq->lock, flags);

                        }

                        if (stat) {
                                pr_err( "proatm%d: Error: closing VC %d.%d with pending tx buffers\n",
                                                card->index, (int) vcc->vpi, vcc->vci);
                        }

                        d = read_sram(card, conn * 8 + 3);
                        if (!(d & PROATM_TCTE_IDLE)) {
                                writel(PROATM_HALT | conn, card->membase + TCMDQ);
                        }
                }
        }
        vcc->dev_data = NULL;
        clear_bit(ATM_VF_PARTIAL, &vcc->flags);
        clear_bit(ATM_VF_ADDR, &vcc->flags);

        spin_lock_irqsave(&card->int_lock, flags);
        retreive_lost_buffers(card);
        spin_unlock_irqrestore(&card->int_lock, flags);

        #ifdef EXTRA_DEBUG
        pr_info("STAT = 0x%08X  CFG = 0x%08X  \n", readl(card->membase + STAT),
                        readl(card->membase + CFG));
                        pr_info("TSQ: base = 0x%lx  next = 0x%lx  TSQT = 0x%x \n",
                                        (unsigned long) card->tsq.base, (unsigned long) card->tsq.next, readl(card->membase + TSQT));
        pr_info("RSQ: base = 0x%lx  next = 0x%lx  last = 0x%lx  RSQT = 0x%x \n",
                        (unsigned long) card->rsq.base, (unsigned long) card->rsq.next,
                        (unsigned long) card->rsq.last, readl(card->membase + RSQT));
        pr_info("free buffer queue interrupt %s \n",
                        card->fbie ? "enabled" : "disabled");
        pr_info("SAR: %d  Counter: %d\n",
                        proatm_fbqc_get(readl(card->membase + FBQP1)), card->fb_count);
        #endif
        PRINTK("proatm%d: exiting proatm_close ()\n", card->index);

}

static void fill_tst(proatm_dev *card, int num_entries, vc_map *vc, u32 conn) {
        u32 new_tst;
        unsigned long cl;
        int e, r;
        u32 data;

        /* It would be very complicated to keep the two TSTs synchronized while
         assuring that writes are only made to the inactive TST. So, for now I
         will use only one TST. If problems occur, I will change this again */

        new_tst = card->tst;
        /* Fill procedure */
        for (e = 0; e < card->tst_num; e++) {
                if (card->tste2vc[e] == NULL)
                        break;
        }
        if (e == card->tst_num) {
                pr_err("proatm%d: No free TST entries found. \n", card->index);
                return;
        }
        r = num_entries;
        cl = card->tst_num;
        data = proatm_tste_make(PROATM_TST_OPCODE_FIXED, conn);
        while (r > 0) {
                if (cl >= card->tst_num && card->tste2vc[e] == NULL) {
                        card->tste2vc[e] = vc;
                        write_sram(card, new_tst + e, &data, 1);
                        XPRINTK("    tst entry: 0x%x, written at 0x%x\n", read_sram(card, new_tst + e),
                                        new_tst + e);
                        cl -= card->tst_num;
                        r--;
                }
                if (++e == card->tst_num) {
                        e = 0;
                }
                cl += num_entries;
        }

        /* End of fill procedure */

        /*   not usefull since we use only the first TST
         *
         *   data = proatm_tste_make(PROATM_TST_OPCODE_END, new_tst<<2);
         *   write_sram(card, new_tst + card->tst_num, &data, 1);
         *   write_sram(card, card->tst_addr + card->tst_num, &data, 1);
         *   card->tst_addr = new_tst;
         */
}

static int proatm_send(struct atm_vcc *vcc, struct sk_buff *skb) {
        proatm_dev *card;
        vc_map *vc;
        scq_info *scq;
        unsigned long flags;

        /* TBD flags, not CPU flags */
        card = vcc->dev->dev_data;

        PRINTK("proatm%d: proatm_send ()\n", card->index);

        if ((vc = (vc_map *) vcc->dev_data) == NULL) {
                pr_err("proatm%d: vcc->dev_data == NULL on proatm_send().\n",
                                card->index);
                dev_kfree_skb_any(skb);
                return -EINVAL;
        }
        if (!vc->tx) {
                pr_err("proatm%d: Trying to transmit on a non-tx VC.\n", card->index);
                goto error;
        }
        if (vc->closing) {
                pr_err("proatm%d: Trying to send on a closing VC: vpi.vci %d.%d)\n",
                                card->index, (int) vcc->vpi, (int) vcc->vci);
                goto error;
        }
        if (vcc->qos.aal != ATM_AAL5 && vcc->qos.aal != ATM_AAL0) {
                pr_err("proatm%d: Only AAL0 and AAL5 are supported.\n", card->index);
                goto error;
        }
        if (skb_shinfo(skb)->nr_frags != 0) {
                pr_err("proatm%d: No scatter-gather yet.\n", card->index);
                goto error;
        }
        scq = ((vc_map *) vcc->dev_data)->scq;
        ATM_SKB(skb)->vcc = vcc;
        spin_lock_irqsave(&scq->lock, flags);
        skb_queue_tail(&scq->waiting, skb);
        spin_unlock_irqrestore(&scq->lock, flags);
        if (push_on_scq(card, scq)) {
                /* Slow down */
                PRINTK("proatm%d: Back pressure on VCC %d.%d\n", card->index, vcc->vpi, vcc->vci);
                msleep(1);
        }
        return 0;

        error: atomic_inc(&vcc->stats->tx_err);
        dev_kfree_skb_any(skb);
        return -EINVAL;
}

/* try to push a maximum number of skbs on SCQ */

static int push_on_scq(proatm_dev *card, scq_info *scq) {
        proatm_scqe scqe;
        struct sk_buff *skb;
        struct atm_vcc *vcc;
        vc_map *vc;
        u32 tbdflags, buflen, data, tsrtag, index;
        unsigned long flags;
        int z;

        PRINTK("proatm%d: push_on_scq()\n", card->index);

        spin_lock_irqsave(&scq->lock, flags);

        while ((skb = skb_peek(&scq->waiting))) {
                vcc = ATM_SKB(skb)->vcc;
                vc = (vc_map *) vcc->dev_data;
                index = 0x3f & (int) (scq->next - scq->base);
                tsrtag = index & 0x1f;

                z = scq->next - scq->tail;
                if (z < 0)
                        z = SCQ_NUM_ENTRIES + z;

                if (z >= 32) {
                        /* The SCQ is not really full but we cannot store
                         * more than 32 TBDs in the SCQ since the TSR flag
                         * has only 5 bits
                         */
                        /* Check SKB queue length */
                        if (skb_queue_len(&scq->waiting) > MAX_TBD_COUNT) {
                                spin_unlock_irqrestore(&scq->lock, flags);
                                return 1;
                        }
                        break; /* return 0 */
                }
                /* Place the SKB on the SCQ */
                skb_dequeue(&scq->waiting);

                /* Map skb for DMA. Get and remember skb data DMA address */
                PROATM_SKB(skb)->paddr = pci_map_single(
                                card->pcidev, skb->data, skb->len, PCI_DMA_TODEVICE);

                if (skb->nfmark == 0xAB) {
                        /* We do not care AAL type here */
                        proatm_rcqe * rawcell = (proatm_rcqe*) skb->data;
                        PRINTK("proatm: push OAM cell\n");
                        buflen = ATM_CELL_PAYLOAD;
                        tbdflags = PROATM_TBD_OAM | PROATM_TBD_EOPDU;
                        scqe.word_2 = PROATM_SKB(skb)->paddr + 16; /* raw cell payload */
                        scqe.word_3 = cpu_to_le32(0x00000000);
                        scqe.word_4 = cpu_to_le32(rawcell->word_1); /* header */
                } else if (vcc->qos.aal == ATM_AAL5) {
                        buflen = (u32) skb->len;
                        tbdflags = PROATM_TBD_AAL5;
                        scqe.word_2 = PROATM_SKB(skb)->paddr;
                        scqe.word_3 = cpu_to_le32((u32) skb->len);
                        scqe.word_4 =
                                        proatm_tbd_mkword_4(0, (u32) vcc->vpi, (u32) vcc->vci, (u32)vcc->aal_options,
                                                        ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? 1: 0);
                        tbdflags |= PROATM_TBD_EOPDU; /* support only one-buffer CS-PDUs */
                } else {
                        /* (vcc->qos.aal == ATM_AAL0) */
                        u32 header = cpu_to_le32(*((u32 *) skb->data));

#ifdef PROATM_OAM_SUPPORT
                        u32 pti = (header & 0x0E) >> 1;
                        u32 vci = (header & PROATM_TBD_VCI_MASK) >> PROATM_TBD_VCI_SHIFT;
#ifdef PROATM_BUILD_AAL0_HEADER
                        u32 gfc = (header & 0xF0000000) >> 28;
#else
                        u32 vpi = (header & PROATM_TBD_VPI_MASK) >> PROATM_TBD_VPI_SHIFT;
#endif
#else
#ifdef PROATM_BUILD_AAL0_HEADER
                        u32 gfc = (header & 0xF0000000) >> 28;
                        u32 pti = (header & 0x0E) >> 1;
#else
                        u32 vci = (header & PROATM_TBD_VCI_MASK) >> PROATM_TBD_VCI_SHIFT;
                        u32 vpi = (header & PROATM_TBD_VPI_MASK) >> PROATM_TBD_VPI_SHIFT;
#endif
#endif

#ifdef PROATM_OAM_SUPPORT
                        if (oam_type(pti, vci)) {
                                PRINTK(" cell pushed\n");
                                tbdflags = PROATM_TBD_OAM;
                        }
                        else
#endif
                                tbdflags = PROATM_TBD_AAL0;
                        tbdflags |= PROATM_TBD_EOPDU; /* Payload type 1 - end of pdu */
                        /* support only one-buffer CS-PDUs */
                        buflen = ATM_CELL_PAYLOAD; /* i.e., 48 bytes */

                        scqe.word_2 = PROATM_SKB(skb)->paddr + PROATM_AAL0_HEADER;
                        scqe.word_3 = cpu_to_le32(0x00000000);

#ifdef PROATM_BUILD_AAL0_HEADER
                        scqe.word_4 =
                                        proatm_tbd_mkword_4(gfc, (u32) vcc->vpi, (u32) vcc->vci, pti,
                                                        ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? 1 : 0);
#else
                        /* Sanity check */
                        if (vpi != (u32)vcc->vpi || vci != (u32)vcc->vci ) {
                                pr_err ("proatm%d: Wrong VPI.VCI, expected %d.%d, forced %d.%d\n",
                                                card->index, (u32)vcc->vpi, (u32)vcc->vci, vpi, vci);
                        }
                        scqe.word_4 = header;
#endif

                }
                tbdflags |= PROATM_TBD_GTSI | PROATM_TBD_TSIF;
                scqe.word_1 = proatm_tbd_mkword_1(tbdflags, (u32)0, buflen & 0xFFFF);

                XPRINTK("proatm%d: push_on_scq(): current scq-base= 0x%lx \n",
                                card->index, (unsigned long) scq->base);

                scqe.word_1 |= tsrtag << 20;
                memcpy(scq->next, &scqe, sizeof(scqe)); /* copy the TBD into the SCQ entry */
                scq->skb[index] = skb;

                XPRINTK("Sending skb at 0x%lx (pos %d).\n", (unsigned long) skb, index);
                XPRINTK("TBD: 0x%x, 0x%x, 0x%x, 0x%x\n", scqe.word_1,scqe.word_2, scqe.word_3, scqe.word_4);

                if (++scq->next > scq->last)
                        scq->next = scq->base;

                /* update Tail pointer in the SCD */
                data = scq->base_pa + sizeof(proatm_scqe) * (scq->next - scq->base); /* scq is consistent memory */
                write_sram(card, scq->scd, &data, 1);

                /* restart variable bit rate connection if idle */
                /* note that the SAR never stops UBR0 */
                if (scq->scq_type != CBR_SCQ && scq != card->scq0) {
                        if (!vc->active) {
                                writel(PROATM_START | (vc - card->vcmap),
                                                card->membase + TCMDQ);
                                vc->active = 1;
                        }
                }
                atomic_inc(&vcc->stats->tx);
        }

        spin_unlock_irqrestore(&scq->lock, flags);
        return 0;
}

static void process_tsq(proatm_dev *card) {
        u32 type, vci, vpi, tsrtag, i=0;
        scq_info *scq;
        vc_map *vc;
        int conn;
        proatm_tsi *sar_tsq_tail = card->tsq.base;

        PRINTK("proatm%d: process_tsq ()\n", card->index);

        sar_tsq_tail += (0x1ff8 & readl(card->membase + TSQT)) >> 3;

        while (card->tsq.next != sar_tsq_tail) {
        		i = 0;
                while ((i++ < 20) && proatm_tsi_isempty(card->tsq.next))
                        udelay(5);

                if (proatm_tsi_isempty(card->tsq.next))
                        return;

                type = proatm_tsi_gettype(card->tsq.next);

                if (type == PROATM_TSI_TYPE_TBD) {
                        /* the TSI is due to a TBD */
                        vci = proatm_tsi_getvci (card->tsq.next);
                        vpi = proatm_tsi_getvpi (card->tsq.next);
                        conn = find_conn(card, vpi, vci);
                        vc = &(card->vcmap[conn]);
                        scq = vc->scq;
                        tsrtag = proatm_tsi_gettag(card->tsq.next);
                        drain_scq(card, scq, vc, tsrtag);
                        push_on_scq(card, scq);
                } else if (type == PROATM_TSI_TYPE_IDLE) {
                        XPRINTK ("proatm%d: tsi type is PROATM_TSI_TYPE_IDLE\n", card->index);
                        conn = (proatm_tsi_getword_1 (card->tsq.next)) & 0x3fff;
                        if (conn < card->rct_size) {
                                vc = &card->vcmap[conn];
                                /* remember that connection is no longer active*/
                                vc->active = 0;
                        }
                } else {
                        XPRINTK ("proatm%d: unknown tsi type: 0x%x\n", card->index, type);
                }
                /* mark TSI empty */
                proatm_tsi_init(card->tsq.next);

                if (++card->tsq.next == card->tsq.base + PROATM_TSQ_NUM_ENTRIES)
                        card->tsq.next = card->tsq.base;
        }


        writel((u32)((unsigned long) card->tsq.next     - (unsigned long) card->tsq.base),
                                        card->membase + TSQH);

}

/* Free all transmit SKBs from scq->tail+1 to scqe */
static void drain_scq(proatm_dev *card, scq_info *scq, vc_map *vc, u32 tsrtag) {
        struct atm_vcc *vcc;
        struct sk_buff *skb;
        int index;
        volatile proatm_scqe* scqe;
        unsigned long flags;

        PRINTK("proatm%d: drain_scq ()\n", card->index);

        spin_lock_irqsave(&scq->lock, flags);

        scqe = scq->tail+1;
        if (scqe > scq->last)
                scqe = scq->base;

        while (scqe->word_1 != 0) {
                index = scqe - scq->base;
                skb = scq->skb[index];
                XPRINTK("proatm%d: freeing skb at 0x%lx (index %d).\n",
                                card->index, (unsigned long) skb, index);
                if (skb != NULL) {
                        vcc = ATM_SKB(skb)->vcc;

                        pci_unmap_single(card->pcidev, PROATM_SKB(skb)->paddr, skb->len,
                                        PCI_DMA_TODEVICE);

                        if (vcc == NULL) {
                                pr_err("proatm%d: null vcc into sk_buff.\n", card->index);
                                dev_kfree_skb_any(skb);
                        } else {
                                if (vcc->pop != NULL)
                                        /* return the skb to ATM protocol layers after emission */
                                        POP_ATM(vcc, skb);
                                else
                                        /* or free the skb if the protocol does not care of it */
                                        dev_kfree_skb_any(skb);
                        }
                        scq->skb[index] = NULL;
                }
                if (((scqe->word_1 >> 20) & 0x1f) == tsrtag){
                        scqe->word_1 = 0;
                        break;
                }
                scqe->word_1 = 0;
                if (++scqe > scq->last)
                        scqe = scq->base;

        }
        scq->tail = scqe;
        spin_unlock_irqrestore(&scq->lock, flags);
}

static void process_rsq(proatm_dev *card) {
        proatm_rsqe *previous;

        PRINTK("proatm%d: process_rsq ()\n", card->index);

        if (!proatm_rsqe_valid(card->rsq.next))
                return;

        do {
                dequeue_rx(card, card->rsq.next);
                proatm_rsqe_init(card->rsq.next);
                previous = card->rsq.next;
                if (card->rsq.next == card->rsq.last)
                        card->rsq.next = card->rsq.base;
                else
                        card->rsq.next++;
        } while (proatm_rsqe_valid(card->rsq.next));

        writel((u32)((unsigned long) previous - (unsigned long) card->rsq.base),
                        card->membase + RSQH);
}

static void dequeue_rx(proatm_dev *card, proatm_rsqe *rsqe) {
        u32 vpi, vci;
        vc_map *vc;
        struct atm_vcc *vcc;
        struct sk_buff *skb;
        int len;
        u32 conn, index;

        PRINTK ("proatm%d: dequeue_rx ()\n", card->index);

        index = le32_to_cpu(rsqe->buffer_handle); /* get the handle */
        index &= (MAX_B1 - 1);

        PRINTK ("index= %d\n", index);

        skb = card->fb_table[index].skb_ptr;

        if (skb == NULL) {
                pr_err("proatm%d: error: dequeue null skb\n", card->index);
                return;
        }

        /* monitoring of lost buffer */
        card->fb_table[index].sar_count++;
        if (card->fb_dequeue_count < MAX_B1 * 3)
                card->fb_dequeue_count++;

        pci_dma_sync_single_for_cpu(card->pcidev, PROATM_SKB(skb)->paddr,
                        skb_end_ptr(skb) - skb->data, PCI_DMA_FROMDEVICE);
        vpi = proatm_rsqe_vpi(rsqe);
        vci = proatm_rsqe_vci(rsqe);
        if (vpi == 0 && vci == 0) {
                pr_err("proatm%d: SDU received on vc 0.0.\n", card->index);
                PRINTK ("    rsqe:\nw1= 0x%x, w2= 0x%x, w3= 0x%x, w4= 0x%x\n",
                                rsqe->word_1, rsqe->buffer_handle,
                                rsqe->final_aal5_crc32, rsqe->word_4);

                recycle_rxbuf(card, skb);
                return;
        }
        if (check_vc(card, vpi, vci)) {
                pr_err("proatm%d: SDU received on out-of-range vc %d.%d.\n",
                                card->index, vpi, vci);
                recycle_rxbuf(card, skb);
                return;
        }
        conn = find_conn(card, vpi, vci);
        vc = &(card->vcmap[conn]);
        if (!vc->rx) {
                pr_err("proatm%d: SDU received on non-rx vc %d.%d.\n", card->index, vpi,
                                vci);
                recycle_rxbuf(card, skb);
                return;
        }
        vcc = vc->rx_vcc;

        /*
         * AAL0 (handled by SAR)
         * Linux ATM does not allow AAL0 SDUs longer than one cell. If the received
         * SDU is longer than one cell, we must do the reverse of the SAR, ie, segment
         * the data into cells and rebuild the headers
         */
        if (vcc->qos.aal == ATM_AAL0) {
                struct sk_buff *sb;
                unsigned char *cell;
                int i;

                cell = skb->data;
                for (i = proatm_rsqe_cellcount(rsqe); i; i--) {
                        if ((sb = dev_alloc_skb(PROATM_RCQE_SIZE)) == NULL) {
                                pr_err("proatm%d: Can't allocate buffers for aal0.\n",
                                                card->index);
                                atomic_add(i, &vcc->stats->rx_drop);
                                break;
                        }
                        if (!atm_charge(vcc, sb->truesize)) {
                                pr_err("proatm%d: atm_charge() dropped aal0 packets.\n",
                                                card->index);
                                atomic_add(i - 1, &vcc->stats->rx_drop);/* already increased by 1 */
                                dev_kfree_skb_any(sb);
                                break;
                        }
                        /* Rebuild the header */
                        *((u32 *) sb->data) = le32_to_cpu(rsqe->word_1) << 4
                                        | (proatm_rsqe_clp(rsqe) ? 0x00000001 : 0x00000000);
                        if (i == 1 && proatm_rsqe_eopdu(rsqe))
                                *((u32 *) sb->data) |= 0x00000002;

                        skb_put(sb, sizeof(u32));
#ifdef NET_SKBUFF_DATA_USES_OFFSET
                        memcpy(sb->head + sb->tail, cell, ATM_CELL_PAYLOAD); /* tail is an offset */
#else
                        memcpy(sb->tail, cell, ATM_CELL_PAYLOAD); /* tail is a pointer */
#endif
                        skb_put(sb, ATM_CELL_PAYLOAD);

                        /* transmit the received SKB to ATM protocol layers */
                        PUSH_ATM(vcc, sb);

                        atomic_inc(&vcc->stats->rx);
                        cell += ATM_CELL_PAYLOAD;
                }
                recycle_rxbuf(card, skb);
                return;
        }

        /*
         * AAL5
         */

        if (vc->rx_sdu_tail == NULL) {
                /* first sk_buff */
                vc->rx_sdu_head = skb;
                ATM_SKB(skb)->vcc = vcc;
                vc->bufcnt = 1;
                vc->totlen = 0;
        } else {
                vc->rx_sdu_tail->next = skb;
                vc->bufcnt++;
                if (vc->bufcnt >= PROATM_MAX_SKBUFFS) {
                        /* Too many sk_buff's into this SDU */
                        pr_err("proatm%d: received too big AAL5 SDU.\n", card->index);
                        atomic_inc(&vcc->stats->rx_err);
                        recycle_rxbufs(card, vc->rx_sdu_head);
                        goto pduend;
                }
        }
        vc->rx_sdu_tail = skb;
        skb->next = NULL;

        len = proatm_rsqe_cellcount(rsqe) * 48;
        PROATM_SKB(skb)->buflen = len;
        vc->totlen += len;

        /* 
         * Push the SDU to upper layers if we got end of CS-PDU
         */

        if (proatm_rsqe_eopdu(rsqe)) {
                struct sk_buff *skb_first = vc->rx_sdu_head;

                /* This works correctly regardless of the endianness of the host */
                unsigned char *L1L2 = (unsigned char *) ((unsigned long) skb->data + len - 6);
                int pdu_len = L1L2[0];

                pdu_len = pdu_len << 8 | L1L2[1];
                if (pdu_len == 0)
                        pdu_len = 0x10000;


                /* The actual PDU size is pdu_len+8.
                 * totlen is the cumulated payload of all ATM cells used to carry the PDU.
                 * totlen and pdu_len must satisfy this relation:  totlen > pdu_len+8 > totlen - 47
                 */

                if (proatm_rsqe_crcerr(rsqe)
                                || (pdu_len + 8 > vc->totlen || pdu_len + (47 + 8) < vc->totlen)) {
                        pr_err("proatm%d: vcc %d.%d AAL5 PDU error", card->index, vpi, vci);

                        if (pdu_len + 8 > vc->totlen || pdu_len + (47 + 8) < vc->totlen)
                                pr_err(" - PDU size mismatch: %d %d \n", len + 8, vc->totlen);
                        else
                                pr_err(" - wrong CRC.\n");

                        atomic_inc(&vcc->stats->rx_err);
                        recycle_rxbufs(card, vc->rx_sdu_head);
                        goto pduend;
                }

                /* By this point we (hopefully) have a complete AAL5 SDU without errors. */

                XPRINTK ("proatm%d: AAL5 SDU with %d buffers\n", card->index, vc->bufcnt);

                if (vc->bufcnt == 1) {
                        /* The SDU contains one buffer and skb points to it */
                        if (!atm_charge(vcc, skb->truesize)) {
                                XPRINTK ("proatm%d: trash SDU\n", card->index);
                                recycle_rxbuf(card, skb);
                        } 
                        else {
                                UNMAP_SKB(skb);
                                skb_put(skb, pdu_len);
                                alloc_rxbuf(card, index);

                                /* transmit the received SDU to ATM protocol layers */
                                PUSH_ATM(vcc, skb);
                                atomic_inc(&vcc->stats->rx);
                        }
                } 
                else {
                        /* Big SDU. Must put everything into a huge buffer */
                        struct sk_buff *hb, *buf, *buf_next;
                        int remaining, tocopy;

                        /* Allocate a huge buffer */
                        hb = alloc_skb(PROATM_HBUFSIZE, GFP_ATOMIC);
                        if (hb == NULL) {
                                pr_err("proatm%d: cannot allocate huge buffers.\n",
                                                card->index);
                                atomic_inc(&vcc->stats->rx_drop);
                                recycle_rxbufs(card, skb_first);
                                goto pduend;
                        }

                        if (!atm_charge(vcc, hb->truesize)) {
                                /* free all received buffers if upper layers cannot handle this SDU */
                                XPRINTK ("proatm%d: trash huge SDU\n", card->index);
                                recycle_rxbufs(card, skb_first);
                                /* recycle the huge buffer */
                                dev_kfree_skb_any(hb);
                        } else {
                                /* Copy all skbuffs into the huge buffer */
                                remaining = pdu_len;
                                buf = skb_first;
                                while (buf) {
                                        buf_next = buf->next;
                                        tocopy = MIN(remaining, PROATM_SKB(buf)->buflen);
#ifdef NET_SKBUFF_DATA_USES_OFFSET
                                        memcpy(hb->head + hb->tail, buf->data, tocopy);
#else
                                        memcpy(hb->tail, buf->data, tocopy);
#endif
                                        skb_put(hb, tocopy);
                                        remaining -= tocopy;
                                        recycle_rxbuf(card, buf); /* Recycle the  buffer */
                                        buf = buf_next;
                                }

#ifdef EXTRA_DEBUG
                                if (remaining != 0 || hb->len != pdu_len) {
                                        pr_err("proatm%d: Huge buffer len mismatch.\n", card->index);
                                        goto pduend;
                                }
#endif

                                /* transmit the received huge buffer to ATM protocol layers*/
                                PUSH_ATM(vcc, hb);
                                atomic_inc(&vcc->stats->rx);
                        }
                }

                /* The received SDU has been processed */
                pduend: vc->rx_sdu_tail = NULL;
                vc->rx_sdu_head = NULL;
                vc->totlen = 0;
                vc->bufcnt = 0;
        }

}

static void recycle_rxbuf(proatm_dev *card, struct sk_buff *skb) {
        PRINTK("proatm%d: recycle_rxbuf()\n", card->index);

        pci_dma_sync_single_for_device(card->pcidev, PROATM_SKB(skb)->paddr,
                        skb_end_ptr(skb) - skb->data, PCI_DMA_FROMDEVICE);
        push_rxbuf(card, skb);

}

static void recycle_rxbufs(proatm_dev *card, struct sk_buff *skb) {
        struct sk_buff *skb_next;
        while (skb) {
                skb_next = skb->next;
                recycle_rxbuf(card, skb);
                skb = skb_next;
        }
}

static int proatm_proc_read(struct atm_dev *dev, loff_t *pos, char *page) {
        proatm_dev *card = (proatm_dev *) dev->dev_data;
        int left = (int) *pos;

        if (!left--) {
                return sprintf(page, "Pool size      : %5d\n", card->fb_count);
        }
        if (!left--) {
                int i, n = 0;
                struct sk_buff *skb;

                /* look for all free buffers registered in the device table */
                for (i = 0; i < MAX_B1; i++) {
                        if ((skb = card->fb_table[i].skb_ptr) != NULL)
                                n++;
                }
                return sprintf(page, "Buffers in Pool: %5d\n", n);
        }
        if (!left--) {
                u32 fbqp1 = readl(card->membase + FBQP1);
                return sprintf(page, "SAR Queue      : %5d\n", proatm_fbqc_get(fbqp1));
        }
        if (!left--) {
                int retval = sprintf(page, "Interrupt Counter: %u \n", card->intcnt);
                card->intcnt = 0;
                return retval;
        }
        return 0;
}

static int proatm_ioctl(struct atm_dev *dev, unsigned int cmd, void *arg) {
        proatm_dev *card;
        card = dev->dev_data;
        pr_info("proatm%d: proatm_ioctl (cmd=0x%x)\n", card->index, cmd);
        return suni_ioctl(dev, cmd, arg);
}

static int parse_mac(char *mac, unsigned char *esi) {
        int i, j;
        short byte1, byte0;

        if (mac == NULL || esi == NULL)
                return -1;

        j = 0;
        for (i = 0; i < 6; i++) {
                if ((byte1 = h2i(mac[j++])) < 0)
                        return -1;

                if ((byte0 = h2i(mac[j++])) < 0)
                        return -1;

                esi[i] = (unsigned char) (byte1 * 16 + byte0);
                if (i < 5) {
                        if (mac[j++] != ':')
                                return -1;

                }
        }
        return 0;
}

static short h2i(char c) {
        if (c >= '0' && c <= '9')
                return (short) (c - '0');

        if (c >= 'A' && c <= 'F')
                return (short) (c - 'A' + 10);

        if (c >= 'a' && c <= 'f')
                return (short) (c - 'a' + 10);

        return -1;
}

static void proatm_phy_put(struct atm_dev *dev, unsigned char value,
                unsigned long addr) {
        proatm_dev *card;
        unsigned long flags;

        card = dev->dev_data;
        spin_lock_irqsave(&card->res_lock, flags);
        waitfor_notbusy(card);
        writel((unsigned long) value, card->membase + DR0);
        writel(PROATM_CMD_WRITE_UTILITY | 0x00000100 | (addr & 0x000000FF),
                        card->membase + CMD);
        spin_unlock_irqrestore(&card->res_lock, flags);
}

static unsigned char proatm_phy_get(struct atm_dev *dev, unsigned long addr) {
        proatm_dev *card;
        unsigned long flags;
        unsigned long data;

        card = dev->dev_data;
        spin_lock_irqsave(&card->res_lock, flags);
        waitfor_notbusy(card);
        writel(PROATM_CMD_READ_UTILITY | 0x00000100 | (addr & 0x000000FF),
                        card->membase + CMD);
        waitfor_notbusy(card);
        data = readl(card->membase + DR0) & 0x000000FF;
        spin_unlock_irqrestore(&card->res_lock, flags);
        return (unsigned char) data;
}

static u8 eeprom_byte_rd(proatm_dev *card, u32 address) {
        int i, value = 0, command = 3;
        volatile u32 gp = readl(card->membase + GP) & 0xfffffff8;

        udelay(5); /* make sure idle */
        writel(gp | PROATM_GP_EECLK | PROATM_GP_EECS, card->membase + GP); /* CS and Clock high */
        udelay(5);
        /* toggle in  READ CMD (00000011) */
        for (i = 7; i >= 0; i--) {
                writel(gp | ((command >> i) & 1), card->membase + GP); /* Clock low */
                udelay(5);
                writel(gp | PROATM_GP_EECLK | ((command >> i) & 1), card->membase + GP); /* Clock high */
                udelay(5);
        }
        /* toggle in address */
        for (i = 7; i >= 0; i--) {
                writel(gp | ((address >> i) & 1), card->membase + GP); /* Clock low */
                udelay(5);
                writel(gp | PROATM_GP_EECLK | ((address >> i) & 1), card->membase + GP); /* Clock high */
                udelay(5);
        }
        /* read EEPROM data */
        for (i = 7; i >= 0; i--) {
                writel(gp, card->membase + GP); /* Clock low */
                udelay(5);
                value |= (readl(card->membase + GP) & PROATM_GP_EEDI) >> (16 - i);
                writel(gp | PROATM_GP_EECLK, card->membase + GP); /* Clock high */
                udelay(5);
        }
        writel(gp, card->membase + GP); /* CS and Clock low */
        return ((u8) value);
}

static void eeprom_rd(proatm_dev *card, u32 ee_add, u8 *memptr, u32 len) {
        while (len-- > 0)
                *memptr++ = eeprom_byte_rd(card, ee_add++);
}

/* send received SKBs or return transmitted SKBs to ATM network layers */
#ifdef PROATM_USE_WORKQUEUE
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
static void pushpop (struct work_struct *work)
#else
static void pushpop (void *dev_id)
#endif
{
        struct sk_buff *skb;
        struct atm_vcc *vcc;
        unsigned long flags;

        for (;;) {
                spin_lock_irqsave (&pushpop_lock, flags);
                skb = skb_dequeue(&pushpop_q);
                spin_unlock_irqrestore(&pushpop_lock, flags);
                if (skb == NULL)
                        return;

                vcc = ATM_SKB(skb)->vcc;
                if (PROATM_SKB(skb)->flags)
                        /* received SKB */
                        vcc->push(vcc, skb);
                else
                        /* transmitted SKB */
                        vcc->pop(vcc, skb);
        }
}
#endif

#ifdef PROATM_OAM_SUPPORT
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
static void push_oamcell (struct work_struct *work)
{
        struct proatm_dev *card = container_of(work, struct proatm_dev, oam_w);
#else
        static void push_oamcell (void *dev_id)
        {
                proatm_dev *card = (proatm_dev *)dev_id;
#endif
                unsigned long flags;
                proatm_rcqe * rawcell;
                vc_map *vc;
                scq_info *scq;
                struct sk_buff *skb;

                spin_lock_irqsave(&card->int_lock, sflags);
                while ((skb = skb_dequeue(&card->oam_tq))) {
                        rawcell = (proatm_rcqe*)skb->data;
                        vc = &(card->vcmap[rawcell->word_2]);
                        scq = vc->scq;
                        if (vc->scq == NULL) {
                                dev_kfree_skb_any(skb);
                                continue;
                        }
                        skb_queue_tail(&scq->waiting, skb);
                        push_on_scq (card, scq);
                }
                spin_unlock_irqrestore(&card->int_lock, sflags);
        }

        static void push_on_oam_tq (proatm_dev *card, proatm_rcqe *rawcell)
        {
                struct sk_buff *skb;
                vc_map *vc = &(card->vcmap[rawcell->word_2]);

                if (!vc->tx) {
                        pr_err ("proatm%d: Cannot send OAM cell on non-tx VC.\n", card->index);
                        return;
                }

                skb = dev_alloc_skb(PROATM_RCQE_SIZE);
                if (!skb) {
                        pr_err("proatm%d: Cannot allocate memory for OAM cell.\n", card->index);
                        return;
                }

                skb->truesize = PROATM_RCQE_SIZE;

                ATM_SKB(skb)->vcc = vc->tx_vcc;
                memcpy(skb->data, (u8*)rawcell, PROATM_RCQE_SIZE);
                skb_put(skb, PROATM_RCQE_SIZE);
                skb->nfmark = 0xAB;
                skb_queue_tail(&card->oam_tq, skb);
                schedule_work(&card->oam_w);
        }

        static void activate_deactivate_cc(proatm_dev *card, proatm_rcqe *rawcell)
        {
                u8* pl = rawcell->payload;
                vc_map *vc = &(card->vcmap[rawcell->word_2]);
                u8 oam_dir;

                switch (pl[1] & OAM_MASK_MSGID) {
                case OAM_ACTIVATE_REQUEST:
                        pr_info ("proatm%d: vcc %d.%d OAM CC activate request\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        oam_dir = pl[1] & OAM_MASK_DIR;
                        if (oam_dir) {
                                vc->oam_cc = 1;
                                pl[1] = oam_dir | OAM_ACTIVATE_CONFIRMED;
                                /* Send the confirmation back */
                                push_on_oam_tq (card, rawcell);
                        }
                        else {
                                pr_info ("proatm%d: vcc %d.%d OAM CC activation with no direction.\n",
                                                card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        }
                        break;

                case OAM_DEACTIVATE_REQUEST:
                        pr_info ("proatm%d: vcc %d.%d OAM CC deactivate request.\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        oam_dir = pl[1] & OAM_MASK_DIR;
                        oam_dir = pl[1] & OAM_MASK_DIR;
                        if (oam_dir) {
                                vc->oam_cc = 0;
                                pl[1] = oam_dir | OAM_DEACTIVATE_CONFIRMED;
                                push_on_oam_tq (card, rawcell);
                        }
                        else {
                                pr_info ("proatm%d: vcc %d.%d OAM CC activation with no direction.\n",
                                                card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        }
                        break;
                default:
                        PRINTK ("proatm%d: vcc %d.%d Discard OAM act-deact CC = 0x%02x\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci, pl[1]);
                        break;
                }
        }

        static int process_oam_cell (proatm_dev *card, proatm_rcqe *rawcell)
        {
                u8* pl = rawcell->payload;
                vc_map *vc = &(card->vcmap[rawcell->word_2]);

                PRINTK ("proatm%d: vcc %d.%d Processing OAM cell\n",
                                card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);

                switch (rawcell->payload[0]) {

                case OAM_ACT_DEACT_CC:
                        activate_deactivate_cc(card, rawcell);
                        break;

                case OAM_LOOPBACK:
                        if (pl[1] & OAM_LOOPBACK_REPLY_MASK) {
                                PRINTK ("proatm%d: vcc %d.%d OAM loopback cell received.\n",
                                                card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);

                                /* Reset loopback indication */
                                pl[1] = 0;
                                if (memcmp (pl+6, proatm_all1, 16)) {
                                        if (memcmp (pl+6, proatm_llid, 16)) {
                                                if (memcmp (pl+6, proatm_all0, 16)) {
                                                        pr_err ("proatm%d: vcc %d.%d Discard OAM loopback cell with wrong direction.\n",
                                                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                                                        break;
                                                }
                                                else {
                                                        /* set LLID to all 1s (endpoint)*/
                                                        memcpy (pl+6, proatm_all1, 16);
                                                }
                                        }
                                }
                                /* Put reply on transmission */
                                push_on_oam_tq (card, rawcell);
                        }
                        else {
                                pr_err ("proatm%d: vcc %d.%d Discard OAM loopback cell with wrong direction.\n",
                                                card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        }
                        break;

                case OAM_AIS:
                        PRINTK ("proatm%d: vcc %d.%d OAM AIS received.\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        /* Don't signal any default */
                        break;

                case OAM_RDI:
                        PRINTK ("proatm%d: vcc %d.%d OAM RDI received.\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        /* Must return CC response */
                        pl[0] = OAM_CC;
                        push_on_oam_tq (card, rawcell);
                        break;

                case OAM_CC:
                        PRINTK ("proatm%d: vcc %d.%d OAM CC received.\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        /* Must return a CC response if CC is enabled on this VC*/
                        if (vc->oam_cc) {
                                pl[0] = OAM_CC;
                                push_on_oam_tq (card, rawcell);
                        }
                        break;

                default:
                        PRINTK ("proatm%d: vcc %d.%d Unknown OAM cell type.\n",
                                        card->index, vc->rx_vcc->vpi, vc->rx_vcc->vci);
                        return -1;
                        break;
                }
                return 0;

        }
#endif

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Bucari <bucari@prosum.net>");
MODULE_DESCRIPTION("PROATM Driver for PROATM cards");
MODULE_VERSION(PROATM_VERSION);

module_init( proatm_init);
module_exit( proatm_cleanup);

