Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/atm/Kconfig b/drivers/atm/Kconfig
new file mode 100644
index 0000000..489de81
--- /dev/null
+++ b/drivers/atm/Kconfig
@@ -0,0 +1,448 @@
+#
+# ATM device configuration
+#
+
+menu "ATM drivers"
+	depends on NETDEVICES && ATM
+
+config ATM_TCP
+	tristate "ATM over TCP"
+	depends on INET && ATM
+	help
+	  ATM over TCP driver. Useful mainly for development and for
+	  experiments. If unsure, say N.
+
+config ATM_LANAI
+	tristate "Efficient Networks Speedstream 3010"
+	depends on PCI && ATM
+	help
+	  Supports ATM cards based on the Efficient Networks "Lanai"
+	  chipset such as the Speedstream 3010 and the ENI-25p.  The
+	  Speedstream 3060 is currently not supported since we don't
+	  have the code to drive the on-board Alcatel DSL chipset (yet).
+
+config ATM_ENI
+	tristate "Efficient Networks ENI155P"
+	depends on PCI && ATM
+	---help---
+	  Driver for the Efficient Networks ENI155p series and SMC ATM
+	  Power155 155 Mbps ATM adapters. Both, the versions with 512KB and
+	  2MB on-board RAM (Efficient calls them "C" and "S", respectively),
+	  and the FPGA and the ASIC Tonga versions of the board are supported.
+	  The driver works with MMF (-MF or ...F) and UTP-5 (-U5 or ...D)
+	  adapters.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called eni.
+
+config ATM_ENI_DEBUG
+	bool "Enable extended debugging"
+	depends on ATM_ENI
+	help
+	  Extended debugging records various events and displays that list
+	  when an inconsistency is detected. This mechanism is faster than
+	  generally using printks, but still has some impact on performance.
+	  Note that extended debugging may create certain race conditions
+	  itself. Enable this ONLY if you suspect problems with the driver.
+
+config ATM_ENI_TUNE_BURST
+	bool "Fine-tune burst settings"
+	depends on ATM_ENI
+	---help---
+	  In order to obtain good throughput, the ENI NIC can transfer
+	  multiple words of data per PCI bus access cycle. Such a multi-word
+	  transfer is called a burst.
+
+	  The default settings for the burst sizes are suitable for most PCI
+	  chipsets. However, in some cases, large bursts may overrun buffers
+	  in the PCI chipset and cause data corruption. In such cases, large
+	  bursts must be disabled and only (slower) small bursts can be used.
+	  The burst sizes can be set independently in the send (TX) and
+	  receive (RX) direction.
+
+	  Note that enabling many different burst sizes in the same direction
+	  may increase the cost of setting up a transfer such that the
+	  resulting throughput is lower than when using only the largest
+	  available burst size.
+
+	  Also, sometimes larger bursts lead to lower throughput, e.g. on an
+	  Intel 440FX board, a drop from 135 Mbps to 103 Mbps was observed
+	  when going from 8W to 16W bursts.
+
+config ATM_ENI_BURST_TX_16W
+	bool "Enable 16W TX bursts (discouraged)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst sixteen words at once in the send direction. This may work
+	  with recent PCI chipsets, but is known to fail with older chipsets.
+
+config ATM_ENI_BURST_TX_8W
+	bool "Enable 8W TX bursts (recommended)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst eight words at once in the send direction. This is the default
+	  setting.
+
+config ATM_ENI_BURST_TX_4W
+	bool "Enable 4W TX bursts (optional)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst four words at once in the send direction. You may want to try
+	  this if you have disabled 8W bursts. Enabling 4W if 8W is also set
+	  may or may not improve throughput.
+
+config ATM_ENI_BURST_TX_2W
+	bool "Enable 2W TX bursts (optional)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst two words at once in the send direction. You may want to try
+	  this if you have disabled 4W and 8W bursts. Enabling 2W if 4W or 8W
+	  are also set may or may not improve throughput.
+
+config ATM_ENI_BURST_RX_16W
+	bool "Enable 16W RX bursts (discouraged)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst sixteen words at once in the receive direction. This may work
+	  with recent PCI chipsets, but is known to fail with older chipsets.
+
+config ATM_ENI_BURST_RX_8W
+	bool "Enable 8W RX bursts (discouraged)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst eight words at once in the receive direction. This may work
+	  with recent PCI chipsets, but is known to fail with older chipsets,
+	  such as the Intel Neptune series.
+
+config ATM_ENI_BURST_RX_4W
+	bool "Enable 4W RX bursts (recommended)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst four words at once in the receive direction. This is the
+	  default setting. Enabling 4W if 8W is also set may or may not
+	  improve throughput.
+
+config ATM_ENI_BURST_RX_2W
+	bool "Enable 2W RX bursts (optional)"
+	depends on ATM_ENI_TUNE_BURST
+	help
+	  Burst two words at once in the receive direction. You may want to
+	  try this if you have disabled 4W and 8W bursts. Enabling 2W if 4W or
+	  8W are also set may or may not improve throughput.
+
+config ATM_FIRESTREAM
+	tristate "Fujitsu FireStream (FS50/FS155) "
+	depends on PCI && ATM
+	help
+	  Driver for the Fujitsu FireStream 155 (MB86697) and
+	  FireStream 50 (MB86695) ATM PCI chips.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called firestream.
+
+config ATM_ZATM
+	tristate "ZeitNet ZN1221/ZN1225"
+	depends on PCI && ATM
+	help
+	  Driver for the ZeitNet ZN1221 (MMF) and ZN1225 (UTP-5) 155 Mbps ATM
+	  adapters.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called zatm.
+
+config ATM_ZATM_DEBUG
+	bool "Enable extended debugging"
+	depends on ATM_ZATM
+	help
+	  Extended debugging records various events and displays that list
+	  when an inconsistency is detected. This mechanism is faster than
+	  generally using printks, but still has some impact on performance.
+	  Note that extended debugging may create certain race conditions
+	  itself. Enable this ONLY if you suspect problems with the driver.
+
+#   bool 'Rolfs TI TNETA1570' CONFIG_ATM_TNETA1570 y
+#   if [ "$CONFIG_ATM_TNETA1570" = "y" ]; then
+#      bool '  Enable extended debugging' CONFIG_ATM_TNETA1570_DEBUG n
+#   fi
+config ATM_NICSTAR
+	tristate "IDT 77201 (NICStAR) (ForeRunnerLE)"
+	depends on PCI && ATM && !64BIT
+	help
+	  The NICStAR chipset family is used in a large number of ATM NICs for
+	  25 and for 155 Mbps, including IDT cards and the Fore ForeRunnerLE
+	  series. Say Y if you have one of those.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called nicstar.
+
+config ATM_NICSTAR_USE_SUNI
+	bool "Use suni PHY driver (155Mbps)"
+	depends on ATM_NICSTAR
+	help
+	  Support for the S-UNI and compatible PHYsical layer chips. These are
+	  found in most 155Mbps NICStAR based ATM cards, namely in the
+	  ForeRunner LE155 cards. This driver provides detection of cable~
+	  removal and reinsertion and provides some statistics. This driver
+	  doesn't have removal capability when compiled as a module, so if you
+	  need that capability don't include S-UNI support (it's not needed to
+	  make the card work).
+
+config ATM_NICSTAR_USE_IDT77105
+	bool "Use IDT77015 PHY driver (25Mbps)"
+	depends on ATM_NICSTAR
+	help
+	  Support for the PHYsical layer chip in ForeRunner LE25 cards. In
+	  addition to cable removal/reinsertion detection, this driver allows
+	  you to control the loopback mode of the chip via a dedicated IOCTL.
+	  This driver is required for proper handling of temporary carrier
+	  loss, so if you have a 25Mbps NICStAR based ATM card you must say Y.
+
+config ATM_IDT77252
+	tristate "IDT 77252 (NICStAR II)"
+	depends on PCI && ATM
+	help
+	  Driver for the IDT 77252 ATM PCI chips.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called idt77252.
+
+config ATM_IDT77252_DEBUG
+	bool "Enable debugging messages"
+	depends on ATM_IDT77252
+	help
+	  Somewhat useful debugging messages are available. The choice of
+	  messages is controlled by a bitmap.  This may be specified as a
+	  module argument.  See the file <file:drivers/atm/idt77252.h> for
+	  the meanings of the bits in the mask.
+
+	  When active, these messages can have a significant impact on the
+	  speed of the driver, and the size of your syslog files! When
+	  inactive, they will have only a modest impact on performance.
+
+config ATM_IDT77252_RCV_ALL
+	bool "Receive ALL cells in raw queue"
+	depends on ATM_IDT77252
+	help
+	  Enable receiving of all cells on the ATM link, that do not match
+	  an open connection in the raw cell queue of the driver.  Useful
+	  for debugging or special applications only, so the safe answer is N.
+
+config ATM_IDT77252_USE_SUNI
+	bool
+	depends on ATM_IDT77252
+	default y
+
+config ATM_AMBASSADOR
+	tristate "Madge Ambassador (Collage PCI 155 Server)"
+	depends on PCI && ATM
+	help
+	  This is a driver for ATMizer based ATM card produced by Madge
+	  Networks Ltd. Say Y (or M to compile as a module named ambassador)
+	  here if you have one of these cards.
+
+config ATM_AMBASSADOR_DEBUG
+	bool "Enable debugging messages"
+	depends on ATM_AMBASSADOR
+	---help---
+	  Somewhat useful debugging messages are available. The choice of
+	  messages is controlled by a bitmap.  This may be specified as a
+	  module argument (kernel command line argument as well?), changed
+	  dynamically using an ioctl (not yet) or changed by sending the
+	  string "Dxxxx" to VCI 1023 (where x is a hex digit).  See the file
+	  <file:drivers/atm/ambassador.h> for the meanings of the bits in the
+	  mask.
+
+	  When active, these messages can have a significant impact on the
+	  speed of the driver, and the size of your syslog files! When
+	  inactive, they will have only a modest impact on performance.
+
+config ATM_HORIZON
+	tristate "Madge Horizon [Ultra] (Collage PCI 25 and Collage PCI 155 Client)"
+	depends on PCI && ATM
+	help
+	  This is a driver for the Horizon chipset ATM adapter cards once
+	  produced by Madge Networks Ltd. Say Y (or M to compile as a module
+	  named horizon) here if you have one of these cards.
+
+config ATM_HORIZON_DEBUG
+	bool "Enable debugging messages"
+	depends on ATM_HORIZON
+	---help---
+	  Somewhat useful debugging messages are available. The choice of
+	  messages is controlled by a bitmap.  This may be specified as a
+	  module argument (kernel command line argument as well?), changed
+	  dynamically using an ioctl (not yet) or changed by sending the
+	  string "Dxxxx" to VCI 1023 (where x is a hex digit).  See the file
+	  <file:drivers/atm/horizon.h> for the meanings of the bits in the
+	  mask.
+
+	  When active, these messages can have a significant impact on the
+	  speed of the driver, and the size of your syslog files! When
+	  inactive, they will have only a modest impact on performance.
+
+config ATM_IA
+	tristate "Interphase ATM PCI x575/x525/x531"
+	depends on PCI && ATM && !64BIT
+	---help---
+	  This is a driver for the Interphase (i)ChipSAR adapter cards
+	  which include a variety of variants in term of the size of the
+	  control memory (128K-1KVC, 512K-4KVC), the size of the packet
+	  memory (128K, 512K, 1M), and the PHY type (Single/Multi mode OC3,
+	  UTP155, UTP25, DS3 and E3). Go to:
+	  	<http://www.iphase.com/products/ClassSheet.cfm?ClassID=ATM>
+	  for more info about the cards. Say Y (or M to compile as a module
+	  named iphase) here if you have one of these cards.
+
+	  See the file <file:Documentation/networking/iphase.txt> for further
+	  details.
+
+config ATM_IA_DEBUG
+	bool "Enable debugging messages"
+	depends on ATM_IA
+	---help---
+	  Somewhat useful debugging messages are available. The choice of
+	  messages is controlled by a bitmap. This may be specified as a
+	  module argument (kernel command line argument as well?), changed
+	  dynamically using an ioctl (Get the debug utility, iadbg, from
+	  <ftp://ftp.iphase.com/pub/atm/pci/>).
+
+	  See the file <file:drivers/atm/iphase.h> for the meanings of the
+	  bits in the mask.
+
+	  When active, these messages can have a significant impact on the
+	  speed of the driver, and the size of your syslog files! When
+	  inactive, they will have only a modest impact on performance.
+
+config ATM_FORE200E_MAYBE
+	tristate "FORE Systems 200E-series"
+	depends on (PCI || SBUS) && ATM
+	---help---
+	  This is a driver for the FORE Systems 200E-series ATM adapter
+	  cards. It simultaneously supports PCA-200E and SBA-200E models
+	  on PCI and SBUS hosts. Say Y (or M to compile as a module
+	  named fore_200e) here if you have one of these ATM adapters.
+
+	  Note that the driver will actually be compiled only if you
+	  additionally enable the support for PCA-200E and/or SBA-200E
+	  cards.
+
+	  See the file <file:Documentation/networking/fore200e.txt> for
+	  further details.
+
+config ATM_FORE200E_PCA
+	bool "PCA-200E support"
+	depends on ATM_FORE200E_MAYBE && PCI
+	help
+	  Say Y here if you want your PCA-200E cards to be probed.
+
+config ATM_FORE200E_PCA_DEFAULT_FW
+	bool "Use default PCA-200E firmware (normally enabled)"
+	depends on ATM_FORE200E_PCA
+	help
+	  Use the default PCA-200E firmware data shipped with the driver.
+
+	  Normal users do not have to deal with the firmware stuff, so
+	  they should say Y here.
+
+config ATM_FORE200E_PCA_FW
+	string "Pathname of user-supplied binary firmware"
+	depends on ATM_FORE200E_PCA && !ATM_FORE200E_PCA_DEFAULT_FW
+	default ""
+	help
+	  This defines the pathname of an alternative PCA-200E binary
+	  firmware image supplied by the user. This pathname may be
+	  absolute or relative to the drivers/atm directory.
+
+	  The driver comes with an adequate firmware image, so normal users do
+	  not have to supply an alternative one. They just say Y to "Use
+	  default PCA-200E firmware" instead.
+
+config ATM_FORE200E_SBA
+	bool "SBA-200E support"
+	depends on ATM_FORE200E_MAYBE && SBUS
+	help
+	  Say Y here if you want your SBA-200E cards to be probed.
+
+config ATM_FORE200E_SBA_DEFAULT_FW
+	bool "Use default SBA-200E firmware (normally enabled)"
+	depends on ATM_FORE200E_SBA
+	help
+	  Use the default SBA-200E firmware data shipped with the driver.
+
+	  Normal users do not have to deal with the firmware stuff, so
+	  they should say Y here.
+
+config ATM_FORE200E_SBA_FW
+	string "Pathname of user-supplied binary firmware"
+	depends on ATM_FORE200E_SBA && !ATM_FORE200E_SBA_DEFAULT_FW
+	default ""
+	help
+	  This defines the pathname of an alternative SBA-200E binary
+	  firmware image supplied by the user. This pathname may be
+	  absolute or relative to the drivers/atm directory.
+
+	  The driver comes with an adequate firmware image, so normal users do
+	  not have to supply an alternative one. They just say Y to "Use
+	  default SBA-200E firmware", above.
+
+config ATM_FORE200E_USE_TASKLET
+	bool "Defer interrupt work to a tasklet"
+	depends on (PCI || SBUS) && (ATM_FORE200E_PCA || ATM_FORE200E_SBA)
+	default n
+	help
+	  This defers work to be done by the interrupt handler to a
+	  tasklet instead of hanlding everything at interrupt time.  This
+	  may improve the responsive of the host.
+
+config ATM_FORE200E_TX_RETRY
+	int "Maximum number of tx retries"
+	depends on (PCI || SBUS) && (ATM_FORE200E_PCA || ATM_FORE200E_SBA)
+	default "16"
+	---help---
+	  Specifies the number of times the driver attempts to transmit
+	  a message before giving up, if the transmit queue of the ATM card
+	  is transiently saturated.
+
+	  Saturation of the transmit queue may occur only under extreme
+	  conditions, e.g. when a fast host continuously submits very small
+	  frames (<64 bytes) or raw AAL0 cells (48 bytes) to the ATM adapter.
+
+	  Note that under common conditions, it is unlikely that you encounter
+	  a saturation of the transmit queue, so the retry mechanism never
+	  comes into play.
+
+config ATM_FORE200E_DEBUG
+	int "Debugging level (0-3)"
+	depends on (PCI || SBUS) && (ATM_FORE200E_PCA || ATM_FORE200E_SBA)
+	default "0"
+	help
+	  Specifies the level of debugging messages issued by the driver.
+	  The verbosity of the driver increases with the value of this
+	  parameter.
+
+	  When active, these messages can have a significant impact on
+	  the performances of the driver, and the size of your syslog files!
+	  Keep the debugging level to 0 during normal operations.
+
+config ATM_FORE200E
+	tristate
+	depends on (PCI || SBUS) && (ATM_FORE200E_PCA || ATM_FORE200E_SBA)
+	default m if ATM_FORE200E_MAYBE!=y
+	default y if ATM_FORE200E_MAYBE=y
+
+config ATM_HE
+	tristate "ForeRunner HE Series"
+	depends on PCI && ATM
+	help
+	  This is a driver for the Marconi ForeRunner HE-series ATM adapter
+	  cards. It simultaneously supports the 155 and 622 versions.
+
+config ATM_HE_USE_SUNI
+	bool "Use S/UNI PHY driver"
+	depends on ATM_HE
+	help
+	  Support for the S/UNI-Ultra and S/UNI-622 found in the ForeRunner
+	  HE cards.  This driver provides carrier detection some statistics.
+
+endmenu
+
diff --git a/drivers/atm/Makefile b/drivers/atm/Makefile
new file mode 100644
index 0000000..d1dcd8e
--- /dev/null
+++ b/drivers/atm/Makefile
@@ -0,0 +1,71 @@
+#
+# Makefile for the Linux network (ATM) device drivers.
+#
+
+fore_200e-objs	:= fore200e.o
+hostprogs-y	:= fore200e_mkfirm
+
+# Files generated that shall be removed upon make clean
+clean-files := atmsar11.bin atmsar11.bin1 atmsar11.bin2 pca200e.bin \
+	pca200e.bin1 pca200e.bin2 pca200e_ecd.bin pca200e_ecd.bin1 \
+	pca200e_ecd.bin2 sba200e_ecd.bin sba200e_ecd.bin1 sba200e_ecd.bin2
+# Firmware generated that shall be removed upon make clean
+clean-files += fore200e_pca_fw.c fore200e_sba_fw.c
+
+obj-$(CONFIG_ATM_ZATM)		+= zatm.o uPD98402.o
+obj-$(CONFIG_ATM_NICSTAR)	+= nicstar.o
+obj-$(CONFIG_ATM_AMBASSADOR)	+= ambassador.o
+obj-$(CONFIG_ATM_HORIZON)	+= horizon.o
+obj-$(CONFIG_ATM_IA)		+= iphase.o suni.o
+obj-$(CONFIG_ATM_FORE200E)	+= fore_200e.o
+obj-$(CONFIG_ATM_ENI)		+= eni.o suni.o
+obj-$(CONFIG_ATM_IDT77252)	+= idt77252.o
+
+ifeq ($(CONFIG_ATM_NICSTAR_USE_SUNI),y)
+  obj-$(CONFIG_ATM_NICSTAR)	+= suni.o
+endif
+ifeq ($(CONFIG_ATM_NICSTAR_USE_IDT77105),y)
+  obj-$(CONFIG_ATM_NICSTAR)	+= idt77105.o
+endif
+ifeq ($(CONFIG_ATM_IDT77252_USE_SUNI),y)
+  obj-$(CONFIG_ATM_IDT77252)	+= suni.o
+endif
+
+obj-$(CONFIG_ATM_TCP)		+= atmtcp.o
+obj-$(CONFIG_ATM_FIRESTREAM)	+= firestream.o
+obj-$(CONFIG_ATM_LANAI)		+= lanai.o
+
+ifeq ($(CONFIG_ATM_FORE200E_PCA),y)
+  fore_200e-objs		+= fore200e_pca_fw.o
+  # guess the target endianess to choose the right PCA-200E firmware image
+  ifeq ($(CONFIG_ATM_FORE200E_PCA_DEFAULT_FW),y)
+    CONFIG_ATM_FORE200E_PCA_FW = $(shell if test -n "`$(CC) -E -dM $(src)/../../include/asm/byteorder.h | grep ' __LITTLE_ENDIAN '`"; then echo $(obj)/pca200e.bin; else echo $(obj)/pca200e_ecd.bin2; fi)
+  endif
+endif
+
+ifeq ($(CONFIG_ATM_FORE200E_SBA),y)
+  fore_200e-objs		+= fore200e_sba_fw.o
+  ifeq ($(CONFIG_ATM_FORE200E_SBA_DEFAULT_FW),y)
+    CONFIG_ATM_FORE200E_SBA_FW	:= $(obj)/sba200e_ecd.bin2
+  endif
+endif
+obj-$(CONFIG_ATM_HE)		+= he.o
+ifeq ($(CONFIG_ATM_HE_USE_SUNI),y)
+  obj-$(CONFIG_ATM_HE)		+= suni.o
+endif
+
+# FORE Systems 200E-series firmware magic
+$(obj)/fore200e_pca_fw.c: $(patsubst "%", %, $(CONFIG_ATM_FORE200E_PCA_FW)) \
+			  $(obj)/fore200e_mkfirm
+	$(obj)/fore200e_mkfirm -k -b _fore200e_pca_fw \
+	  -i $(CONFIG_ATM_FORE200E_PCA_FW) -o $@
+
+$(obj)/fore200e_sba_fw.c: $(patsubst "%", %, $(CONFIG_ATM_FORE200E_SBA_FW)) \
+			  $(obj)/fore200e_mkfirm
+	$(obj)/fore200e_mkfirm -k -b _fore200e_sba_fw \
+	  -i $(CONFIG_ATM_FORE200E_SBA_FW) -o $@
+
+# deal with the various suffixes of the binary firmware images
+$(obj)/%.bin $(obj)/%.bin1 $(obj)/%.bin2: $(src)/%.data
+	objcopy -Iihex $< -Obinary $@.gz
+	gzip -n -df $@.gz
diff --git a/drivers/atm/ambassador.c b/drivers/atm/ambassador.c
new file mode 100644
index 0000000..c46d952
--- /dev/null
+++ b/drivers/atm/ambassador.c
@@ -0,0 +1,2463 @@
+/*
+  Madge Ambassador ATM Adapter driver.
+  Copyright (C) 1995-1999  Madge Networks Ltd.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+  system and in the file COPYING in the Linux kernel source.
+*/
+
+/* * dedicated to the memory of Graham Gordon 1971-1998 * */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/atmdev.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include <asm/atomic.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+#include "ambassador.h"
+
+#define maintainer_string "Giuliano Procida at Madge Networks <gprocida@madge.com>"
+#define description_string "Madge ATM Ambassador driver"
+#define version_string "1.2.4"
+
+static inline void __init show_version (void) {
+  printk ("%s version %s\n", description_string, version_string);
+}
+
+/*
+  
+  Theory of Operation
+  
+  I Hardware, detection, initialisation and shutdown.
+  
+  1. Supported Hardware
+  
+  This driver is for the PCI ATMizer-based Ambassador card (except
+  very early versions). It is not suitable for the similar EISA "TR7"
+  card. Commercially, both cards are known as Collage Server ATM
+  adapters.
+  
+  The loader supports image transfer to the card, image start and few
+  other miscellaneous commands.
+  
+  Only AAL5 is supported with vpi = 0 and vci in the range 0 to 1023.
+  
+  The cards are big-endian.
+  
+  2. Detection
+  
+  Standard PCI stuff, the early cards are detected and rejected.
+  
+  3. Initialisation
+  
+  The cards are reset and the self-test results are checked. The
+  microcode image is then transferred and started. This waits for a
+  pointer to a descriptor containing details of the host-based queues
+  and buffers and various parameters etc. Once they are processed
+  normal operations may begin. The BIA is read using a microcode
+  command.
+  
+  4. Shutdown
+  
+  This may be accomplished either by a card reset or via the microcode
+  shutdown command. Further investigation required.
+  
+  5. Persistent state
+  
+  The card reset does not affect PCI configuration (good) or the
+  contents of several other "shared run-time registers" (bad) which
+  include doorbell and interrupt control as well as EEPROM and PCI
+  control. The driver must be careful when modifying these registers
+  not to touch bits it does not use and to undo any changes at exit.
+  
+  II Driver software
+  
+  0. Generalities
+  
+  The adapter is quite intelligent (fast) and has a simple interface
+  (few features). VPI is always zero, 1024 VCIs are supported. There
+  is limited cell rate support. UBR channels can be capped and ABR
+  (explicit rate, but not EFCI) is supported. There is no CBR or VBR
+  support.
+  
+  1. Driver <-> Adapter Communication
+  
+  Apart from the basic loader commands, the driver communicates
+  through three entities: the command queue (CQ), the transmit queue
+  pair (TXQ) and the receive queue pairs (RXQ). These three entities
+  are set up by the host and passed to the microcode just after it has
+  been started.
+  
+  All queues are host-based circular queues. They are contiguous and
+  (due to hardware limitations) have some restrictions as to their
+  locations in (bus) memory. They are of the "full means the same as
+  empty so don't do that" variety since the adapter uses pointers
+  internally.
+  
+  The queue pairs work as follows: one queue is for supply to the
+  adapter, items in it are pending and are owned by the adapter; the
+  other is the queue for return from the adapter, items in it have
+  been dealt with by the adapter. The host adds items to the supply
+  (TX descriptors and free RX buffer descriptors) and removes items
+  from the return (TX and RX completions). The adapter deals with out
+  of order completions.
+  
+  Interrupts (card to host) and the doorbell (host to card) are used
+  for signalling.
+  
+  1. CQ
+  
+  This is to communicate "open VC", "close VC", "get stats" etc. to
+  the adapter. At most one command is retired every millisecond by the
+  card. There is no out of order completion or notification. The
+  driver needs to check the return code of the command, waiting as
+  appropriate.
+  
+  2. TXQ
+  
+  TX supply items are of variable length (scatter gather support) and
+  so the queue items are (more or less) pointers to the real thing.
+  Each TX supply item contains a unique, host-supplied handle (the skb
+  bus address seems most sensible as this works for Alphas as well,
+  there is no need to do any endian conversions on the handles).
+  
+  TX return items consist of just the handles above.
+  
+  3. RXQ (up to 4 of these with different lengths and buffer sizes)
+  
+  RX supply items consist of a unique, host-supplied handle (the skb
+  bus address again) and a pointer to the buffer data area.
+  
+  RX return items consist of the handle above, the VC, length and a
+  status word. This just screams "oh so easy" doesn't it?
+
+  Note on RX pool sizes:
+   
+  Each pool should have enough buffers to handle a back-to-back stream
+  of minimum sized frames on a single VC. For example:
+  
+    frame spacing = 3us (about right)
+    
+    delay = IRQ lat + RX handling + RX buffer replenish = 20 (us)  (a guess)
+    
+    min number of buffers for one VC = 1 + delay/spacing (buffers)
+
+    delay/spacing = latency = (20+2)/3 = 7 (buffers)  (rounding up)
+    
+  The 20us delay assumes that there is no need to sleep; if we need to
+  sleep to get buffers we are going to drop frames anyway.
+  
+  In fact, each pool should have enough buffers to support the
+  simultaneous reassembly of a separate frame on each VC and cope with
+  the case in which frames complete in round robin cell fashion on
+  each VC.
+  
+  Only one frame can complete at each cell arrival, so if "n" VCs are
+  open, the worst case is to have them all complete frames together
+  followed by all starting new frames together.
+  
+    desired number of buffers = n + delay/spacing
+    
+  These are the extreme requirements, however, they are "n+k" for some
+  "k" so we have only the constant to choose. This is the argument
+  rx_lats which current defaults to 7.
+  
+  Actually, "n ? n+k : 0" is better and this is what is implemented,
+  subject to the limit given by the pool size.
+  
+  4. Driver locking
+  
+  Simple spinlocks are used around the TX and RX queue mechanisms.
+  Anyone with a faster, working method is welcome to implement it.
+  
+  The adapter command queue is protected with a spinlock. We always
+  wait for commands to complete.
+  
+  A more complex form of locking is used around parts of the VC open
+  and close functions. There are three reasons for a lock: 1. we need
+  to do atomic rate reservation and release (not used yet), 2. Opening
+  sometimes involves two adapter commands which must not be separated
+  by another command on the same VC, 3. the changes to RX pool size
+  must be atomic. The lock needs to work over context switches, so we
+  use a semaphore.
+  
+  III Hardware Features and Microcode Bugs
+  
+  1. Byte Ordering
+  
+  *%^"$&%^$*&^"$(%^$#&^%$(&#%$*(&^#%!"!"!*!
+  
+  2. Memory access
+  
+  All structures that are not accessed using DMA must be 4-byte
+  aligned (not a problem) and must not cross 4MB boundaries.
+  
+  There is a DMA memory hole at E0000000-E00000FF (groan).
+  
+  TX fragments (DMA read) must not cross 4MB boundaries (would be 16MB
+  but for a hardware bug).
+  
+  RX buffers (DMA write) must not cross 16MB boundaries and must
+  include spare trailing bytes up to the next 4-byte boundary; they
+  will be written with rubbish.
+  
+  The PLX likes to prefetch; if reading up to 4 u32 past the end of
+  each TX fragment is not a problem, then TX can be made to go a
+  little faster by passing a flag at init that disables a prefetch
+  workaround. We do not pass this flag. (new microcode only)
+  
+  Now we:
+  . Note that alloc_skb rounds up size to a 16byte boundary.  
+  . Ensure all areas do not traverse 4MB boundaries.
+  . Ensure all areas do not start at a E00000xx bus address.
+  (I cannot be certain, but this may always hold with Linux)
+  . Make all failures cause a loud message.
+  . Discard non-conforming SKBs (causes TX failure or RX fill delay).
+  . Discard non-conforming TX fragment descriptors (the TX fails).
+  In the future we could:
+  . Allow RX areas that traverse 4MB (but not 16MB) boundaries.
+  . Segment TX areas into some/more fragments, when necessary.
+  . Relax checks for non-DMA items (ignore hole).
+  . Give scatter-gather (iovec) requirements using ???. (?)
+  
+  3. VC close is broken (only for new microcode)
+  
+  The VC close adapter microcode command fails to do anything if any
+  frames have been received on the VC but none have been transmitted.
+  Frames continue to be reassembled and passed (with IRQ) to the
+  driver.
+  
+  IV To Do List
+  
+  . Fix bugs!
+  
+  . Timer code may be broken.
+  
+  . Deal with buggy VC close (somehow) in microcode 12.
+  
+  . Handle interrupted and/or non-blocking writes - is this a job for
+    the protocol layer?
+  
+  . Add code to break up TX fragments when they span 4MB boundaries.
+  
+  . Add SUNI phy layer (need to know where SUNI lives on card).
+  
+  . Implement a tx_alloc fn to (a) satisfy TX alignment etc. and (b)
+    leave extra headroom space for Ambassador TX descriptors.
+  
+  . Understand these elements of struct atm_vcc: recvq (proto?),
+    sleep, callback, listenq, backlog_quota, reply and user_back.
+  
+  . Adjust TX/RX skb allocation to favour IP with LANE/CLIP (configurable).
+  
+  . Impose a TX-pending limit (2?) on each VC, help avoid TX q overflow.
+  
+  . Decide whether RX buffer recycling is or can be made completely safe;
+    turn it back on. It looks like Werner is going to axe this.
+  
+  . Implement QoS changes on open VCs (involves extracting parts of VC open
+    and close into separate functions and using them to make changes).
+  
+  . Hack on command queue so that someone can issue multiple commands and wait
+    on the last one (OR only "no-op" or "wait" commands are waited for).
+  
+  . Eliminate need for while-schedule around do_command.
+  
+*/
+
+/********** microcode **********/
+
+#ifdef AMB_NEW_MICROCODE
+#define UCODE(x) UCODE2(atmsar12.x)
+#else
+#define UCODE(x) UCODE2(atmsar11.x)
+#endif
+#define UCODE2(x) #x
+
+static u32 __devinitdata ucode_start =
+#include UCODE(start)
+;
+
+static region __devinitdata ucode_regions[] = {
+#include UCODE(regions)
+  { 0, 0 }
+};
+
+static u32 __devinitdata ucode_data[] = {
+#include UCODE(data)
+  0xdeadbeef
+};
+
+static void do_housekeeping (unsigned long arg);
+/********** globals **********/
+
+static unsigned short debug = 0;
+static unsigned int cmds = 8;
+static unsigned int txs = 32;
+static unsigned int rxs[NUM_RX_POOLS] = { 64, 64, 64, 64 };
+static unsigned int rxs_bs[NUM_RX_POOLS] = { 4080, 12240, 36720, 65535 };
+static unsigned int rx_lats = 7;
+static unsigned char pci_lat = 0;
+
+static const unsigned long onegigmask = -1 << 30;
+
+/********** access to adapter **********/
+
+static inline void wr_plain (const amb_dev * dev, size_t addr, u32 data) {
+  PRINTD (DBG_FLOW|DBG_REGS, "wr: %08zx <- %08x", addr, data);
+#ifdef AMB_MMIO
+  dev->membase[addr / sizeof(u32)] = data;
+#else
+  outl (data, dev->iobase + addr);
+#endif
+}
+
+static inline u32 rd_plain (const amb_dev * dev, size_t addr) {
+#ifdef AMB_MMIO
+  u32 data = dev->membase[addr / sizeof(u32)];
+#else
+  u32 data = inl (dev->iobase + addr);
+#endif
+  PRINTD (DBG_FLOW|DBG_REGS, "rd: %08zx -> %08x", addr, data);
+  return data;
+}
+
+static inline void wr_mem (const amb_dev * dev, size_t addr, u32 data) {
+  __be32 be = cpu_to_be32 (data);
+  PRINTD (DBG_FLOW|DBG_REGS, "wr: %08zx <- %08x b[%08x]", addr, data, be);
+#ifdef AMB_MMIO
+  dev->membase[addr / sizeof(u32)] = be;
+#else
+  outl (be, dev->iobase + addr);
+#endif
+}
+
+static inline u32 rd_mem (const amb_dev * dev, size_t addr) {
+#ifdef AMB_MMIO
+  __be32 be = dev->membase[addr / sizeof(u32)];
+#else
+  __be32 be = inl (dev->iobase + addr);
+#endif
+  u32 data = be32_to_cpu (be);
+  PRINTD (DBG_FLOW|DBG_REGS, "rd: %08zx -> %08x b[%08x]", addr, data, be);
+  return data;
+}
+
+/********** dump routines **********/
+
+static inline void dump_registers (const amb_dev * dev) {
+#ifdef DEBUG_AMBASSADOR
+  if (debug & DBG_REGS) {
+    size_t i;
+    PRINTD (DBG_REGS, "reading PLX control: ");
+    for (i = 0x00; i < 0x30; i += sizeof(u32))
+      rd_mem (dev, i);
+    PRINTD (DBG_REGS, "reading mailboxes: ");
+    for (i = 0x40; i < 0x60; i += sizeof(u32))
+      rd_mem (dev, i);
+    PRINTD (DBG_REGS, "reading doorb irqev irqen reset:");
+    for (i = 0x60; i < 0x70; i += sizeof(u32))
+      rd_mem (dev, i);
+  }
+#else
+  (void) dev;
+#endif
+  return;
+}
+
+static inline void dump_loader_block (volatile loader_block * lb) {
+#ifdef DEBUG_AMBASSADOR
+  unsigned int i;
+  PRINTDB (DBG_LOAD, "lb @ %p; res: %d, cmd: %d, pay:",
+	   lb, be32_to_cpu (lb->result), be32_to_cpu (lb->command));
+  for (i = 0; i < MAX_COMMAND_DATA; ++i)
+    PRINTDM (DBG_LOAD, " %08x", be32_to_cpu (lb->payload.data[i]));
+  PRINTDE (DBG_LOAD, ", vld: %08x", be32_to_cpu (lb->valid));
+#else
+  (void) lb;
+#endif
+  return;
+}
+
+static inline void dump_command (command * cmd) {
+#ifdef DEBUG_AMBASSADOR
+  unsigned int i;
+  PRINTDB (DBG_CMD, "cmd @ %p, req: %08x, pars:",
+	   cmd, /*be32_to_cpu*/ (cmd->request));
+  for (i = 0; i < 3; ++i)
+    PRINTDM (DBG_CMD, " %08x", /*be32_to_cpu*/ (cmd->args.par[i]));
+  PRINTDE (DBG_CMD, "");
+#else
+  (void) cmd;
+#endif
+  return;
+}
+
+static inline void dump_skb (char * prefix, unsigned int vc, struct sk_buff * skb) {
+#ifdef DEBUG_AMBASSADOR
+  unsigned int i;
+  unsigned char * data = skb->data;
+  PRINTDB (DBG_DATA, "%s(%u) ", prefix, vc);
+  for (i=0; i<skb->len && i < 256;i++)
+    PRINTDM (DBG_DATA, "%02x ", data[i]);
+  PRINTDE (DBG_DATA,"");
+#else
+  (void) prefix;
+  (void) vc;
+  (void) skb;
+#endif
+  return;
+}
+
+/********** check memory areas for use by Ambassador **********/
+
+/* see limitations under Hardware Features */
+
+static inline int check_area (void * start, size_t length) {
+  // assumes length > 0
+  const u32 fourmegmask = -1 << 22;
+  const u32 twofivesixmask = -1 << 8;
+  const u32 starthole = 0xE0000000;
+  u32 startaddress = virt_to_bus (start);
+  u32 lastaddress = startaddress+length-1;
+  if ((startaddress ^ lastaddress) & fourmegmask ||
+      (startaddress & twofivesixmask) == starthole) {
+    PRINTK (KERN_ERR, "check_area failure: [%x,%x] - mail maintainer!",
+	    startaddress, lastaddress);
+    return -1;
+  } else {
+    return 0;
+  }
+}
+
+/********** free an skb (as per ATM device driver documentation) **********/
+
+static inline void amb_kfree_skb (struct sk_buff * skb) {
+  if (ATM_SKB(skb)->vcc->pop) {
+    ATM_SKB(skb)->vcc->pop (ATM_SKB(skb)->vcc, skb);
+  } else {
+    dev_kfree_skb_any (skb);
+  }
+}
+
+/********** TX completion **********/
+
+static inline void tx_complete (amb_dev * dev, tx_out * tx) {
+  tx_simple * tx_descr = bus_to_virt (tx->handle);
+  struct sk_buff * skb = tx_descr->skb;
+  
+  PRINTD (DBG_FLOW|DBG_TX, "tx_complete %p %p", dev, tx);
+  
+  // VC layer stats
+  atomic_inc(&ATM_SKB(skb)->vcc->stats->tx);
+  
+  // free the descriptor
+  kfree (tx_descr);
+  
+  // free the skb
+  amb_kfree_skb (skb);
+  
+  dev->stats.tx_ok++;
+  return;
+}
+
+/********** RX completion **********/
+
+static void rx_complete (amb_dev * dev, rx_out * rx) {
+  struct sk_buff * skb = bus_to_virt (rx->handle);
+  u16 vc = be16_to_cpu (rx->vc);
+  // unused: u16 lec_id = be16_to_cpu (rx->lec_id);
+  u16 status = be16_to_cpu (rx->status);
+  u16 rx_len = be16_to_cpu (rx->length);
+  
+  PRINTD (DBG_FLOW|DBG_RX, "rx_complete %p %p (len=%hu)", dev, rx, rx_len);
+  
+  // XXX move this in and add to VC stats ???
+  if (!status) {
+    struct atm_vcc * atm_vcc = dev->rxer[vc];
+    dev->stats.rx.ok++;
+    
+    if (atm_vcc) {
+      
+      if (rx_len <= atm_vcc->qos.rxtp.max_sdu) {
+	
+	if (atm_charge (atm_vcc, skb->truesize)) {
+	  
+	  // prepare socket buffer
+	  ATM_SKB(skb)->vcc = atm_vcc;
+	  skb_put (skb, rx_len);
+	  
+	  dump_skb ("<<<", vc, skb);
+	  
+	  // VC layer stats
+	  atomic_inc(&atm_vcc->stats->rx);
+	  do_gettimeofday(&skb->stamp);
+	  // end of our responsability
+	  atm_vcc->push (atm_vcc, skb);
+	  return;
+	  
+	} else {
+	  // someone fix this (message), please!
+	  PRINTD (DBG_INFO|DBG_RX, "dropped thanks to atm_charge (vc %hu, truesize %u)", vc, skb->truesize);
+	  // drop stats incremented in atm_charge
+	}
+	
+      } else {
+      	PRINTK (KERN_INFO, "dropped over-size frame");
+	// should we count this?
+	atomic_inc(&atm_vcc->stats->rx_drop);
+      }
+      
+    } else {
+      PRINTD (DBG_WARN|DBG_RX, "got frame but RX closed for channel %hu", vc);
+      // this is an adapter bug, only in new version of microcode
+    }
+    
+  } else {
+    dev->stats.rx.error++;
+    if (status & CRC_ERR)
+      dev->stats.rx.badcrc++;
+    if (status & LEN_ERR)
+      dev->stats.rx.toolong++;
+    if (status & ABORT_ERR)
+      dev->stats.rx.aborted++;
+    if (status & UNUSED_ERR)
+      dev->stats.rx.unused++;
+  }
+  
+  dev_kfree_skb_any (skb);
+  return;
+}
+
+/*
+  
+  Note on queue handling.
+  
+  Here "give" and "take" refer to queue entries and a queue (pair)
+  rather than frames to or from the host or adapter. Empty frame
+  buffers are given to the RX queue pair and returned unused or
+  containing RX frames. TX frames (well, pointers to TX fragment
+  lists) are given to the TX queue pair, completions are returned.
+  
+*/
+
+/********** command queue **********/
+
+// I really don't like this, but it's the best I can do at the moment
+
+// also, the callers are responsible for byte order as the microcode
+// sometimes does 16-bit accesses (yuk yuk yuk)
+
+static int command_do (amb_dev * dev, command * cmd) {
+  amb_cq * cq = &dev->cq;
+  volatile amb_cq_ptrs * ptrs = &cq->ptrs;
+  command * my_slot;
+  
+  PRINTD (DBG_FLOW|DBG_CMD, "command_do %p", dev);
+  
+  if (test_bit (dead, &dev->flags))
+    return 0;
+  
+  spin_lock (&cq->lock);
+  
+  // if not full...
+  if (cq->pending < cq->maximum) {
+    // remember my slot for later
+    my_slot = ptrs->in;
+    PRINTD (DBG_CMD, "command in slot %p", my_slot);
+    
+    dump_command (cmd);
+    
+    // copy command in
+    *ptrs->in = *cmd;
+    cq->pending++;
+    ptrs->in = NEXTQ (ptrs->in, ptrs->start, ptrs->limit);
+    
+    // mail the command
+    wr_mem (dev, offsetof(amb_mem, mb.adapter.cmd_address), virt_to_bus (ptrs->in));
+    
+    if (cq->pending > cq->high)
+      cq->high = cq->pending;
+    spin_unlock (&cq->lock);
+    
+    // these comments were in a while-loop before, msleep removes the loop
+    // go to sleep
+    // PRINTD (DBG_CMD, "wait: sleeping %lu for command", timeout);
+    msleep(cq->pending);
+    
+    // wait for my slot to be reached (all waiters are here or above, until...)
+    while (ptrs->out != my_slot) {
+      PRINTD (DBG_CMD, "wait: command slot (now at %p)", ptrs->out);
+      set_current_state(TASK_UNINTERRUPTIBLE);
+      schedule();
+    }
+    
+    // wait on my slot (... one gets to its slot, and... )
+    while (ptrs->out->request != cpu_to_be32 (SRB_COMPLETE)) {
+      PRINTD (DBG_CMD, "wait: command slot completion");
+      set_current_state(TASK_UNINTERRUPTIBLE);
+      schedule();
+    }
+    
+    PRINTD (DBG_CMD, "command complete");
+    // update queue (... moves the queue along to the next slot)
+    spin_lock (&cq->lock);
+    cq->pending--;
+    // copy command out
+    *cmd = *ptrs->out;
+    ptrs->out = NEXTQ (ptrs->out, ptrs->start, ptrs->limit);
+    spin_unlock (&cq->lock);
+    
+    return 0;
+  } else {
+    cq->filled++;
+    spin_unlock (&cq->lock);
+    return -EAGAIN;
+  }
+  
+}
+
+/********** TX queue pair **********/
+
+static inline int tx_give (amb_dev * dev, tx_in * tx) {
+  amb_txq * txq = &dev->txq;
+  unsigned long flags;
+  
+  PRINTD (DBG_FLOW|DBG_TX, "tx_give %p", dev);
+
+  if (test_bit (dead, &dev->flags))
+    return 0;
+  
+  spin_lock_irqsave (&txq->lock, flags);
+  
+  if (txq->pending < txq->maximum) {
+    PRINTD (DBG_TX, "TX in slot %p", txq->in.ptr);
+
+    *txq->in.ptr = *tx;
+    txq->pending++;
+    txq->in.ptr = NEXTQ (txq->in.ptr, txq->in.start, txq->in.limit);
+    // hand over the TX and ring the bell
+    wr_mem (dev, offsetof(amb_mem, mb.adapter.tx_address), virt_to_bus (txq->in.ptr));
+    wr_mem (dev, offsetof(amb_mem, doorbell), TX_FRAME);
+    
+    if (txq->pending > txq->high)
+      txq->high = txq->pending;
+    spin_unlock_irqrestore (&txq->lock, flags);
+    return 0;
+  } else {
+    txq->filled++;
+    spin_unlock_irqrestore (&txq->lock, flags);
+    return -EAGAIN;
+  }
+}
+
+static inline int tx_take (amb_dev * dev) {
+  amb_txq * txq = &dev->txq;
+  unsigned long flags;
+  
+  PRINTD (DBG_FLOW|DBG_TX, "tx_take %p", dev);
+  
+  spin_lock_irqsave (&txq->lock, flags);
+  
+  if (txq->pending && txq->out.ptr->handle) {
+    // deal with TX completion
+    tx_complete (dev, txq->out.ptr);
+    // mark unused again
+    txq->out.ptr->handle = 0;
+    // remove item
+    txq->pending--;
+    txq->out.ptr = NEXTQ (txq->out.ptr, txq->out.start, txq->out.limit);
+    
+    spin_unlock_irqrestore (&txq->lock, flags);
+    return 0;
+  } else {
+    
+    spin_unlock_irqrestore (&txq->lock, flags);
+    return -1;
+  }
+}
+
+/********** RX queue pairs **********/
+
+static inline int rx_give (amb_dev * dev, rx_in * rx, unsigned char pool) {
+  amb_rxq * rxq = &dev->rxq[pool];
+  unsigned long flags;
+  
+  PRINTD (DBG_FLOW|DBG_RX, "rx_give %p[%hu]", dev, pool);
+  
+  spin_lock_irqsave (&rxq->lock, flags);
+  
+  if (rxq->pending < rxq->maximum) {
+    PRINTD (DBG_RX, "RX in slot %p", rxq->in.ptr);
+
+    *rxq->in.ptr = *rx;
+    rxq->pending++;
+    rxq->in.ptr = NEXTQ (rxq->in.ptr, rxq->in.start, rxq->in.limit);
+    // hand over the RX buffer
+    wr_mem (dev, offsetof(amb_mem, mb.adapter.rx_address[pool]), virt_to_bus (rxq->in.ptr));
+    
+    spin_unlock_irqrestore (&rxq->lock, flags);
+    return 0;
+  } else {
+    spin_unlock_irqrestore (&rxq->lock, flags);
+    return -1;
+  }
+}
+
+static inline int rx_take (amb_dev * dev, unsigned char pool) {
+  amb_rxq * rxq = &dev->rxq[pool];
+  unsigned long flags;
+  
+  PRINTD (DBG_FLOW|DBG_RX, "rx_take %p[%hu]", dev, pool);
+  
+  spin_lock_irqsave (&rxq->lock, flags);
+  
+  if (rxq->pending && (rxq->out.ptr->status || rxq->out.ptr->length)) {
+    // deal with RX completion
+    rx_complete (dev, rxq->out.ptr);
+    // mark unused again
+    rxq->out.ptr->status = 0;
+    rxq->out.ptr->length = 0;
+    // remove item
+    rxq->pending--;
+    rxq->out.ptr = NEXTQ (rxq->out.ptr, rxq->out.start, rxq->out.limit);
+    
+    if (rxq->pending < rxq->low)
+      rxq->low = rxq->pending;
+    spin_unlock_irqrestore (&rxq->lock, flags);
+    return 0;
+  } else {
+    if (!rxq->pending && rxq->buffers_wanted)
+      rxq->emptied++;
+    spin_unlock_irqrestore (&rxq->lock, flags);
+    return -1;
+  }
+}
+
+/********** RX Pool handling **********/
+
+/* pre: buffers_wanted = 0, post: pending = 0 */
+static inline void drain_rx_pool (amb_dev * dev, unsigned char pool) {
+  amb_rxq * rxq = &dev->rxq[pool];
+  
+  PRINTD (DBG_FLOW|DBG_POOL, "drain_rx_pool %p %hu", dev, pool);
+  
+  if (test_bit (dead, &dev->flags))
+    return;
+  
+  /* we are not quite like the fill pool routines as we cannot just
+     remove one buffer, we have to remove all of them, but we might as
+     well pretend... */
+  if (rxq->pending > rxq->buffers_wanted) {
+    command cmd;
+    cmd.request = cpu_to_be32 (SRB_FLUSH_BUFFER_Q);
+    cmd.args.flush.flags = cpu_to_be32 (pool << SRB_POOL_SHIFT);
+    while (command_do (dev, &cmd))
+      schedule();
+    /* the pool may also be emptied via the interrupt handler */
+    while (rxq->pending > rxq->buffers_wanted)
+      if (rx_take (dev, pool))
+	schedule();
+  }
+  
+  return;
+}
+
+static void drain_rx_pools (amb_dev * dev) {
+  unsigned char pool;
+  
+  PRINTD (DBG_FLOW|DBG_POOL, "drain_rx_pools %p", dev);
+  
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+    drain_rx_pool (dev, pool);
+}
+
+static inline void fill_rx_pool (amb_dev * dev, unsigned char pool, int priority) {
+  rx_in rx;
+  amb_rxq * rxq;
+  
+  PRINTD (DBG_FLOW|DBG_POOL, "fill_rx_pool %p %hu %x", dev, pool, priority);
+  
+  if (test_bit (dead, &dev->flags))
+    return;
+  
+  rxq = &dev->rxq[pool];
+  while (rxq->pending < rxq->maximum && rxq->pending < rxq->buffers_wanted) {
+    
+    struct sk_buff * skb = alloc_skb (rxq->buffer_size, priority);
+    if (!skb) {
+      PRINTD (DBG_SKB|DBG_POOL, "failed to allocate skb for RX pool %hu", pool);
+      return;
+    }
+    if (check_area (skb->data, skb->truesize)) {
+      dev_kfree_skb_any (skb);
+      return;
+    }
+    // cast needed as there is no %? for pointer differences
+    PRINTD (DBG_SKB, "allocated skb at %p, head %p, area %li",
+	    skb, skb->head, (long) (skb->end - skb->head));
+    rx.handle = virt_to_bus (skb);
+    rx.host_address = cpu_to_be32 (virt_to_bus (skb->data));
+    if (rx_give (dev, &rx, pool))
+      dev_kfree_skb_any (skb);
+    
+  }
+  
+  return;
+}
+
+// top up all RX pools (can also be called as a bottom half)
+static void fill_rx_pools (amb_dev * dev) {
+  unsigned char pool;
+  
+  PRINTD (DBG_FLOW|DBG_POOL, "fill_rx_pools %p", dev);
+  
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+    fill_rx_pool (dev, pool, GFP_ATOMIC);
+  
+  return;
+}
+
+/********** enable host interrupts **********/
+
+static inline void interrupts_on (amb_dev * dev) {
+  wr_plain (dev, offsetof(amb_mem, interrupt_control),
+	    rd_plain (dev, offsetof(amb_mem, interrupt_control))
+	    | AMB_INTERRUPT_BITS);
+}
+
+/********** disable host interrupts **********/
+
+static inline void interrupts_off (amb_dev * dev) {
+  wr_plain (dev, offsetof(amb_mem, interrupt_control),
+	    rd_plain (dev, offsetof(amb_mem, interrupt_control))
+	    &~ AMB_INTERRUPT_BITS);
+}
+
+/********** interrupt handling **********/
+
+static irqreturn_t interrupt_handler(int irq, void *dev_id,
+					struct pt_regs *pt_regs) {
+  amb_dev * dev = (amb_dev *) dev_id;
+  (void) pt_regs;
+  
+  PRINTD (DBG_IRQ|DBG_FLOW, "interrupt_handler: %p", dev_id);
+  
+  if (!dev_id) {
+    PRINTD (DBG_IRQ|DBG_ERR, "irq with NULL dev_id: %d", irq);
+    return IRQ_NONE;
+  }
+  
+  {
+    u32 interrupt = rd_plain (dev, offsetof(amb_mem, interrupt));
+  
+    // for us or someone else sharing the same interrupt
+    if (!interrupt) {
+      PRINTD (DBG_IRQ, "irq not for me: %d", irq);
+      return IRQ_NONE;
+    }
+    
+    // definitely for us
+    PRINTD (DBG_IRQ, "FYI: interrupt was %08x", interrupt);
+    wr_plain (dev, offsetof(amb_mem, interrupt), -1);
+  }
+  
+  {
+    unsigned int irq_work = 0;
+    unsigned char pool;
+    for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+      while (!rx_take (dev, pool))
+	++irq_work;
+    while (!tx_take (dev))
+      ++irq_work;
+  
+    if (irq_work) {
+#ifdef FILL_RX_POOLS_IN_BH
+      schedule_work (&dev->bh);
+#else
+      fill_rx_pools (dev);
+#endif
+
+      PRINTD (DBG_IRQ, "work done: %u", irq_work);
+    } else {
+      PRINTD (DBG_IRQ|DBG_WARN, "no work done");
+    }
+  }
+  
+  PRINTD (DBG_IRQ|DBG_FLOW, "interrupt_handler done: %p", dev_id);
+  return IRQ_HANDLED;
+}
+
+/********** make rate (not quite as much fun as Horizon) **********/
+
+static unsigned int make_rate (unsigned int rate, rounding r,
+			       u16 * bits, unsigned int * actual) {
+  unsigned char exp = -1; // hush gcc
+  unsigned int man = -1;  // hush gcc
+  
+  PRINTD (DBG_FLOW|DBG_QOS, "make_rate %u", rate);
+  
+  // rates in cells per second, ITU format (nasty 16-bit floating-point)
+  // given 5-bit e and 9-bit m:
+  // rate = EITHER (1+m/2^9)*2^e    OR 0
+  // bits = EITHER 1<<14 | e<<9 | m OR 0
+  // (bit 15 is "reserved", bit 14 "non-zero")
+  // smallest rate is 0 (special representation)
+  // largest rate is (1+511/512)*2^31 = 4290772992 (< 2^32-1)
+  // smallest non-zero rate is (1+0/512)*2^0 = 1 (> 0)
+  // simple algorithm:
+  // find position of top bit, this gives e
+  // remove top bit and shift (rounding if feeling clever) by 9-e
+  
+  // ucode bug: please don't set bit 14! so 0 rate not representable
+  
+  if (rate > 0xffc00000U) {
+    // larger than largest representable rate
+    
+    if (r == round_up) {
+	return -EINVAL;
+    } else {
+      exp = 31;
+      man = 511;
+    }
+    
+  } else if (rate) {
+    // representable rate
+    
+    exp = 31;
+    man = rate;
+    
+    // invariant: rate = man*2^(exp-31)
+    while (!(man & (1<<31))) {
+      exp = exp - 1;
+      man = man<<1;
+    }
+    
+    // man has top bit set
+    // rate = (2^31+(man-2^31))*2^(exp-31)
+    // rate = (1+(man-2^31)/2^31)*2^exp
+    man = man<<1;
+    man &= 0xffffffffU; // a nop on 32-bit systems
+    // rate = (1+man/2^32)*2^exp
+    
+    // exp is in the range 0 to 31, man is in the range 0 to 2^32-1
+    // time to lose significance... we want m in the range 0 to 2^9-1
+    // rounding presents a minor problem... we first decide which way
+    // we are rounding (based on given rounding direction and possibly
+    // the bits of the mantissa that are to be discarded).
+    
+    switch (r) {
+      case round_down: {
+	// just truncate
+	man = man>>(32-9);
+	break;
+      }
+      case round_up: {
+	// check all bits that we are discarding
+	if (man & (-1>>9)) {
+	  man = (man>>(32-9)) + 1;
+	  if (man == (1<<9)) {
+	    // no need to check for round up outside of range
+	    man = 0;
+	    exp += 1;
+	  }
+	} else {
+	  man = (man>>(32-9));
+	}
+	break;
+      }
+      case round_nearest: {
+	// check msb that we are discarding
+	if (man & (1<<(32-9-1))) {
+	  man = (man>>(32-9)) + 1;
+	  if (man == (1<<9)) {
+	    // no need to check for round up outside of range
+	    man = 0;
+	    exp += 1;
+	  }
+	} else {
+	  man = (man>>(32-9));
+	}
+	break;
+      }
+    }
+    
+  } else {
+    // zero rate - not representable
+    
+    if (r == round_down) {
+      return -EINVAL;
+    } else {
+      exp = 0;
+      man = 0;
+    }
+    
+  }
+  
+  PRINTD (DBG_QOS, "rate: man=%u, exp=%hu", man, exp);
+  
+  if (bits)
+    *bits = /* (1<<14) | */ (exp<<9) | man;
+  
+  if (actual)
+    *actual = (exp >= 9)
+      ? (1 << exp) + (man << (exp-9))
+      : (1 << exp) + ((man + (1<<(9-exp-1))) >> (9-exp));
+  
+  return 0;
+}
+
+/********** Linux ATM Operations **********/
+
+// some are not yet implemented while others do not make sense for
+// this device
+
+/********** Open a VC **********/
+
+static int amb_open (struct atm_vcc * atm_vcc)
+{
+  int error;
+  
+  struct atm_qos * qos;
+  struct atm_trafprm * txtp;
+  struct atm_trafprm * rxtp;
+  u16 tx_rate_bits;
+  u16 tx_vc_bits = -1; // hush gcc
+  u16 tx_frame_bits = -1; // hush gcc
+  
+  amb_dev * dev = AMB_DEV(atm_vcc->dev);
+  amb_vcc * vcc;
+  unsigned char pool = -1; // hush gcc
+  short vpi = atm_vcc->vpi;
+  int vci = atm_vcc->vci;
+  
+  PRINTD (DBG_FLOW|DBG_VCC, "amb_open %x %x", vpi, vci);
+  
+#ifdef ATM_VPI_UNSPEC
+  // UNSPEC is deprecated, remove this code eventually
+  if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) {
+    PRINTK (KERN_WARNING, "rejecting open with unspecified VPI/VCI (deprecated)");
+    return -EINVAL;
+  }
+#endif
+  
+  if (!(0 <= vpi && vpi < (1<<NUM_VPI_BITS) &&
+	0 <= vci && vci < (1<<NUM_VCI_BITS))) {
+    PRINTD (DBG_WARN|DBG_VCC, "VPI/VCI out of range: %hd/%d", vpi, vci);
+    return -EINVAL;
+  }
+  
+  qos = &atm_vcc->qos;
+  
+  if (qos->aal != ATM_AAL5) {
+    PRINTD (DBG_QOS, "AAL not supported");
+    return -EINVAL;
+  }
+  
+  // traffic parameters
+  
+  PRINTD (DBG_QOS, "TX:");
+  txtp = &qos->txtp;
+  if (txtp->traffic_class != ATM_NONE) {
+    switch (txtp->traffic_class) {
+      case ATM_UBR: {
+	// we take "the PCR" as a rate-cap
+	int pcr = atm_pcr_goal (txtp);
+	if (!pcr) {
+	  // no rate cap
+	  tx_rate_bits = 0;
+	  tx_vc_bits = TX_UBR;
+	  tx_frame_bits = TX_FRAME_NOTCAP;
+	} else {
+	  rounding r;
+	  if (pcr < 0) {
+	    r = round_down;
+	    pcr = -pcr;
+	  } else {
+	    r = round_up;
+	  }
+	  error = make_rate (pcr, r, &tx_rate_bits, NULL);
+	  tx_vc_bits = TX_UBR_CAPPED;
+	  tx_frame_bits = TX_FRAME_CAPPED;
+	}
+	break;
+      }
+#if 0
+      case ATM_ABR: {
+	pcr = atm_pcr_goal (txtp);
+	PRINTD (DBG_QOS, "pcr goal = %d", pcr);
+	break;
+      }
+#endif
+      default: {
+	// PRINTD (DBG_QOS, "request for non-UBR/ABR denied");
+	PRINTD (DBG_QOS, "request for non-UBR denied");
+	return -EINVAL;
+      }
+    }
+    PRINTD (DBG_QOS, "tx_rate_bits=%hx, tx_vc_bits=%hx",
+	    tx_rate_bits, tx_vc_bits);
+  }
+  
+  PRINTD (DBG_QOS, "RX:");
+  rxtp = &qos->rxtp;
+  if (rxtp->traffic_class == ATM_NONE) {
+    // do nothing
+  } else {
+    // choose an RX pool (arranged in increasing size)
+    for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+      if ((unsigned int) rxtp->max_sdu <= dev->rxq[pool].buffer_size) {
+	PRINTD (DBG_VCC|DBG_QOS|DBG_POOL, "chose pool %hu (max_sdu %u <= %u)",
+		pool, rxtp->max_sdu, dev->rxq[pool].buffer_size);
+	break;
+      }
+    if (pool == NUM_RX_POOLS) {
+      PRINTD (DBG_WARN|DBG_VCC|DBG_QOS|DBG_POOL,
+	      "no pool suitable for VC (RX max_sdu %d is too large)",
+	      rxtp->max_sdu);
+      return -EINVAL;
+    }
+    
+    switch (rxtp->traffic_class) {
+      case ATM_UBR: {
+	break;
+      }
+#if 0
+      case ATM_ABR: {
+	pcr = atm_pcr_goal (rxtp);
+	PRINTD (DBG_QOS, "pcr goal = %d", pcr);
+	break;
+      }
+#endif
+      default: {
+	// PRINTD (DBG_QOS, "request for non-UBR/ABR denied");
+	PRINTD (DBG_QOS, "request for non-UBR denied");
+	return -EINVAL;
+      }
+    }
+  }
+  
+  // get space for our vcc stuff
+  vcc = kmalloc (sizeof(amb_vcc), GFP_KERNEL);
+  if (!vcc) {
+    PRINTK (KERN_ERR, "out of memory!");
+    return -ENOMEM;
+  }
+  atm_vcc->dev_data = (void *) vcc;
+  
+  // no failures beyond this point
+  
+  // we are not really "immediately before allocating the connection
+  // identifier in hardware", but it will just have to do!
+  set_bit(ATM_VF_ADDR,&atm_vcc->flags);
+  
+  if (txtp->traffic_class != ATM_NONE) {
+    command cmd;
+    
+    vcc->tx_frame_bits = tx_frame_bits;
+    
+    down (&dev->vcc_sf);
+    if (dev->rxer[vci]) {
+      // RXer on the channel already, just modify rate...
+      cmd.request = cpu_to_be32 (SRB_MODIFY_VC_RATE);
+      cmd.args.modify_rate.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.modify_rate.rate = cpu_to_be32 (tx_rate_bits << SRB_RATE_SHIFT);
+      while (command_do (dev, &cmd))
+	schedule();
+      // ... and TX flags, preserving the RX pool
+      cmd.request = cpu_to_be32 (SRB_MODIFY_VC_FLAGS);
+      cmd.args.modify_flags.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.modify_flags.flags = cpu_to_be32
+	( (AMB_VCC(dev->rxer[vci])->rx_info.pool << SRB_POOL_SHIFT)
+	  | (tx_vc_bits << SRB_FLAGS_SHIFT) );
+      while (command_do (dev, &cmd))
+	schedule();
+    } else {
+      // no RXer on the channel, just open (with pool zero)
+      cmd.request = cpu_to_be32 (SRB_OPEN_VC);
+      cmd.args.open.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.open.flags = cpu_to_be32 (tx_vc_bits << SRB_FLAGS_SHIFT);
+      cmd.args.open.rate = cpu_to_be32 (tx_rate_bits << SRB_RATE_SHIFT);
+      while (command_do (dev, &cmd))
+	schedule();
+    }
+    dev->txer[vci].tx_present = 1;
+    up (&dev->vcc_sf);
+  }
+  
+  if (rxtp->traffic_class != ATM_NONE) {
+    command cmd;
+    
+    vcc->rx_info.pool = pool;
+    
+    down (&dev->vcc_sf); 
+    /* grow RX buffer pool */
+    if (!dev->rxq[pool].buffers_wanted)
+      dev->rxq[pool].buffers_wanted = rx_lats;
+    dev->rxq[pool].buffers_wanted += 1;
+    fill_rx_pool (dev, pool, GFP_KERNEL);
+    
+    if (dev->txer[vci].tx_present) {
+      // TXer on the channel already
+      // switch (from pool zero) to this pool, preserving the TX bits
+      cmd.request = cpu_to_be32 (SRB_MODIFY_VC_FLAGS);
+      cmd.args.modify_flags.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.modify_flags.flags = cpu_to_be32
+	( (pool << SRB_POOL_SHIFT)
+	  | (dev->txer[vci].tx_vc_bits << SRB_FLAGS_SHIFT) );
+    } else {
+      // no TXer on the channel, open the VC (with no rate info)
+      cmd.request = cpu_to_be32 (SRB_OPEN_VC);
+      cmd.args.open.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.open.flags = cpu_to_be32 (pool << SRB_POOL_SHIFT);
+      cmd.args.open.rate = cpu_to_be32 (0);
+    }
+    while (command_do (dev, &cmd))
+      schedule();
+    // this link allows RX frames through
+    dev->rxer[vci] = atm_vcc;
+    up (&dev->vcc_sf);
+  }
+  
+  // indicate readiness
+  set_bit(ATM_VF_READY,&atm_vcc->flags);
+  
+  return 0;
+}
+
+/********** Close a VC **********/
+
+static void amb_close (struct atm_vcc * atm_vcc) {
+  amb_dev * dev = AMB_DEV (atm_vcc->dev);
+  amb_vcc * vcc = AMB_VCC (atm_vcc);
+  u16 vci = atm_vcc->vci;
+  
+  PRINTD (DBG_VCC|DBG_FLOW, "amb_close");
+  
+  // indicate unreadiness
+  clear_bit(ATM_VF_READY,&atm_vcc->flags);
+  
+  // disable TXing
+  if (atm_vcc->qos.txtp.traffic_class != ATM_NONE) {
+    command cmd;
+    
+    down (&dev->vcc_sf);
+    if (dev->rxer[vci]) {
+      // RXer still on the channel, just modify rate... XXX not really needed
+      cmd.request = cpu_to_be32 (SRB_MODIFY_VC_RATE);
+      cmd.args.modify_rate.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.modify_rate.rate = cpu_to_be32 (0);
+      // ... and clear TX rate flags (XXX to stop RM cell output?), preserving RX pool
+    } else {
+      // no RXer on the channel, close channel
+      cmd.request = cpu_to_be32 (SRB_CLOSE_VC);
+      cmd.args.close.vc = cpu_to_be32 (vci); // vpi 0
+    }
+    dev->txer[vci].tx_present = 0;
+    while (command_do (dev, &cmd))
+      schedule();
+    up (&dev->vcc_sf);
+  }
+  
+  // disable RXing
+  if (atm_vcc->qos.rxtp.traffic_class != ATM_NONE) {
+    command cmd;
+    
+    // this is (the?) one reason why we need the amb_vcc struct
+    unsigned char pool = vcc->rx_info.pool;
+    
+    down (&dev->vcc_sf);
+    if (dev->txer[vci].tx_present) {
+      // TXer still on the channel, just go to pool zero XXX not really needed
+      cmd.request = cpu_to_be32 (SRB_MODIFY_VC_FLAGS);
+      cmd.args.modify_flags.vc = cpu_to_be32 (vci);  // vpi 0
+      cmd.args.modify_flags.flags = cpu_to_be32
+	(dev->txer[vci].tx_vc_bits << SRB_FLAGS_SHIFT);
+    } else {
+      // no TXer on the channel, close the VC
+      cmd.request = cpu_to_be32 (SRB_CLOSE_VC);
+      cmd.args.close.vc = cpu_to_be32 (vci); // vpi 0
+    }
+    // forget the rxer - no more skbs will be pushed
+    if (atm_vcc != dev->rxer[vci])
+      PRINTK (KERN_ERR, "%s vcc=%p rxer[vci]=%p",
+	      "arghhh! we're going to die!",
+	      vcc, dev->rxer[vci]);
+    dev->rxer[vci] = NULL;
+    while (command_do (dev, &cmd))
+      schedule();
+    
+    /* shrink RX buffer pool */
+    dev->rxq[pool].buffers_wanted -= 1;
+    if (dev->rxq[pool].buffers_wanted == rx_lats) {
+      dev->rxq[pool].buffers_wanted = 0;
+      drain_rx_pool (dev, pool);
+    }
+    up (&dev->vcc_sf);
+  }
+  
+  // free our structure
+  kfree (vcc);
+  
+  // say the VPI/VCI is free again
+  clear_bit(ATM_VF_ADDR,&atm_vcc->flags);
+
+  return;
+}
+
+/********** Set socket options for a VC **********/
+
+// int amb_getsockopt (struct atm_vcc * atm_vcc, int level, int optname, void * optval, int optlen);
+
+/********** Set socket options for a VC **********/
+
+// int amb_setsockopt (struct atm_vcc * atm_vcc, int level, int optname, void * optval, int optlen);
+
+/********** Send **********/
+
+static int amb_send (struct atm_vcc * atm_vcc, struct sk_buff * skb) {
+  amb_dev * dev = AMB_DEV(atm_vcc->dev);
+  amb_vcc * vcc = AMB_VCC(atm_vcc);
+  u16 vc = atm_vcc->vci;
+  unsigned int tx_len = skb->len;
+  unsigned char * tx_data = skb->data;
+  tx_simple * tx_descr;
+  tx_in tx;
+  
+  if (test_bit (dead, &dev->flags))
+    return -EIO;
+  
+  PRINTD (DBG_FLOW|DBG_TX, "amb_send vc %x data %p len %u",
+	  vc, tx_data, tx_len);
+  
+  dump_skb (">>>", vc, skb);
+  
+  if (!dev->txer[vc].tx_present) {
+    PRINTK (KERN_ERR, "attempt to send on RX-only VC %x", vc);
+    return -EBADFD;
+  }
+  
+  // this is a driver private field so we have to set it ourselves,
+  // despite the fact that we are _required_ to use it to check for a
+  // pop function
+  ATM_SKB(skb)->vcc = atm_vcc;
+  
+  if (skb->len > (size_t) atm_vcc->qos.txtp.max_sdu) {
+    PRINTK (KERN_ERR, "sk_buff length greater than agreed max_sdu, dropping...");
+    return -EIO;
+  }
+  
+  if (check_area (skb->data, skb->len)) {
+    atomic_inc(&atm_vcc->stats->tx_err);
+    return -ENOMEM; // ?
+  }
+  
+  // allocate memory for fragments
+  tx_descr = kmalloc (sizeof(tx_simple), GFP_KERNEL);
+  if (!tx_descr) {
+    PRINTK (KERN_ERR, "could not allocate TX descriptor");
+    return -ENOMEM;
+  }
+  if (check_area (tx_descr, sizeof(tx_simple))) {
+    kfree (tx_descr);
+    return -ENOMEM;
+  }
+  PRINTD (DBG_TX, "fragment list allocated at %p", tx_descr);
+  
+  tx_descr->skb = skb;
+  
+  tx_descr->tx_frag.bytes = cpu_to_be32 (tx_len);
+  tx_descr->tx_frag.address = cpu_to_be32 (virt_to_bus (tx_data));
+  
+  tx_descr->tx_frag_end.handle = virt_to_bus (tx_descr);
+  tx_descr->tx_frag_end.vc = 0;
+  tx_descr->tx_frag_end.next_descriptor_length = 0;
+  tx_descr->tx_frag_end.next_descriptor = 0;
+#ifdef AMB_NEW_MICROCODE
+  tx_descr->tx_frag_end.cpcs_uu = 0;
+  tx_descr->tx_frag_end.cpi = 0;
+  tx_descr->tx_frag_end.pad = 0;
+#endif
+  
+  tx.vc = cpu_to_be16 (vcc->tx_frame_bits | vc);
+  tx.tx_descr_length = cpu_to_be16 (sizeof(tx_frag)+sizeof(tx_frag_end));
+  tx.tx_descr_addr = cpu_to_be32 (virt_to_bus (&tx_descr->tx_frag));
+  
+  while (tx_give (dev, &tx))
+    schedule();
+  return 0;
+}
+
+/********** Change QoS on a VC **********/
+
+// int amb_change_qos (struct atm_vcc * atm_vcc, struct atm_qos * qos, int flags);
+
+/********** Free RX Socket Buffer **********/
+
+#if 0
+static void amb_free_rx_skb (struct atm_vcc * atm_vcc, struct sk_buff * skb) {
+  amb_dev * dev = AMB_DEV (atm_vcc->dev);
+  amb_vcc * vcc = AMB_VCC (atm_vcc);
+  unsigned char pool = vcc->rx_info.pool;
+  rx_in rx;
+  
+  // This may be unsafe for various reasons that I cannot really guess
+  // at. However, I note that the ATM layer calls kfree_skb rather
+  // than dev_kfree_skb at this point so we are least covered as far
+  // as buffer locking goes. There may be bugs if pcap clones RX skbs.
+
+  PRINTD (DBG_FLOW|DBG_SKB, "amb_rx_free skb %p (atm_vcc %p, vcc %p)",
+	  skb, atm_vcc, vcc);
+  
+  rx.handle = virt_to_bus (skb);
+  rx.host_address = cpu_to_be32 (virt_to_bus (skb->data));
+  
+  skb->data = skb->head;
+  skb->tail = skb->head;
+  skb->len = 0;
+  
+  if (!rx_give (dev, &rx, pool)) {
+    // success
+    PRINTD (DBG_SKB|DBG_POOL, "recycled skb for pool %hu", pool);
+    return;
+  }
+  
+  // just do what the ATM layer would have done
+  dev_kfree_skb_any (skb);
+  
+  return;
+}
+#endif
+
+/********** Proc File Output **********/
+
+static int amb_proc_read (struct atm_dev * atm_dev, loff_t * pos, char * page) {
+  amb_dev * dev = AMB_DEV (atm_dev);
+  int left = *pos;
+  unsigned char pool;
+  
+  PRINTD (DBG_FLOW, "amb_proc_read");
+  
+  /* more diagnostics here? */
+  
+  if (!left--) {
+    amb_stats * s = &dev->stats;
+    return sprintf (page,
+		    "frames: TX OK %lu, RX OK %lu, RX bad %lu "
+		    "(CRC %lu, long %lu, aborted %lu, unused %lu).\n",
+		    s->tx_ok, s->rx.ok, s->rx.error,
+		    s->rx.badcrc, s->rx.toolong,
+		    s->rx.aborted, s->rx.unused);
+  }
+  
+  if (!left--) {
+    amb_cq * c = &dev->cq;
+    return sprintf (page, "cmd queue [cur/hi/max]: %u/%u/%u. ",
+		    c->pending, c->high, c->maximum);
+  }
+  
+  if (!left--) {
+    amb_txq * t = &dev->txq;
+    return sprintf (page, "TX queue [cur/max high full]: %u/%u %u %u.\n",
+		    t->pending, t->maximum, t->high, t->filled);
+  }
+  
+  if (!left--) {
+    unsigned int count = sprintf (page, "RX queues [cur/max/req low empty]:");
+    for (pool = 0; pool < NUM_RX_POOLS; ++pool) {
+      amb_rxq * r = &dev->rxq[pool];
+      count += sprintf (page+count, " %u/%u/%u %u %u",
+			r->pending, r->maximum, r->buffers_wanted, r->low, r->emptied);
+    }
+    count += sprintf (page+count, ".\n");
+    return count;
+  }
+  
+  if (!left--) {
+    unsigned int count = sprintf (page, "RX buffer sizes:");
+    for (pool = 0; pool < NUM_RX_POOLS; ++pool) {
+      amb_rxq * r = &dev->rxq[pool];
+      count += sprintf (page+count, " %u", r->buffer_size);
+    }
+    count += sprintf (page+count, ".\n");
+    return count;
+  }
+  
+#if 0
+  if (!left--) {
+    // suni block etc?
+  }
+#endif
+  
+  return 0;
+}
+
+/********** Operation Structure **********/
+
+static const struct atmdev_ops amb_ops = {
+  .open         = amb_open,
+  .close	= amb_close,
+  .send         = amb_send,
+  .proc_read	= amb_proc_read,
+  .owner	= THIS_MODULE,
+};
+
+/********** housekeeping **********/
+static void do_housekeeping (unsigned long arg) {
+  amb_dev * dev = (amb_dev *) arg;
+  
+  // could collect device-specific (not driver/atm-linux) stats here
+      
+  // last resort refill once every ten seconds
+  fill_rx_pools (dev);
+  mod_timer(&dev->housekeeping, jiffies + 10*HZ);
+  
+  return;
+}
+
+/********** creation of communication queues **********/
+
+static int __devinit create_queues (amb_dev * dev, unsigned int cmds,
+				 unsigned int txs, unsigned int * rxs,
+				 unsigned int * rx_buffer_sizes) {
+  unsigned char pool;
+  size_t total = 0;
+  void * memory;
+  void * limit;
+  
+  PRINTD (DBG_FLOW, "create_queues %p", dev);
+  
+  total += cmds * sizeof(command);
+  
+  total += txs * (sizeof(tx_in) + sizeof(tx_out));
+  
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+    total += rxs[pool] * (sizeof(rx_in) + sizeof(rx_out));
+  
+  memory = kmalloc (total, GFP_KERNEL);
+  if (!memory) {
+    PRINTK (KERN_ERR, "could not allocate queues");
+    return -ENOMEM;
+  }
+  if (check_area (memory, total)) {
+    PRINTK (KERN_ERR, "queues allocated in nasty area");
+    kfree (memory);
+    return -ENOMEM;
+  }
+  
+  limit = memory + total;
+  PRINTD (DBG_INIT, "queues from %p to %p", memory, limit);
+  
+  PRINTD (DBG_CMD, "command queue at %p", memory);
+  
+  {
+    command * cmd = memory;
+    amb_cq * cq = &dev->cq;
+    
+    cq->pending = 0;
+    cq->high = 0;
+    cq->maximum = cmds - 1;
+    
+    cq->ptrs.start = cmd;
+    cq->ptrs.in = cmd;
+    cq->ptrs.out = cmd;
+    cq->ptrs.limit = cmd + cmds;
+    
+    memory = cq->ptrs.limit;
+  }
+  
+  PRINTD (DBG_TX, "TX queue pair at %p", memory);
+  
+  {
+    tx_in * in = memory;
+    tx_out * out;
+    amb_txq * txq = &dev->txq;
+    
+    txq->pending = 0;
+    txq->high = 0;
+    txq->filled = 0;
+    txq->maximum = txs - 1;
+    
+    txq->in.start = in;
+    txq->in.ptr = in;
+    txq->in.limit = in + txs;
+    
+    memory = txq->in.limit;
+    out = memory;
+    
+    txq->out.start = out;
+    txq->out.ptr = out;
+    txq->out.limit = out + txs;
+    
+    memory = txq->out.limit;
+  }
+  
+  PRINTD (DBG_RX, "RX queue pairs at %p", memory);
+  
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool) {
+    rx_in * in = memory;
+    rx_out * out;
+    amb_rxq * rxq = &dev->rxq[pool];
+    
+    rxq->buffer_size = rx_buffer_sizes[pool];
+    rxq->buffers_wanted = 0;
+    
+    rxq->pending = 0;
+    rxq->low = rxs[pool] - 1;
+    rxq->emptied = 0;
+    rxq->maximum = rxs[pool] - 1;
+    
+    rxq->in.start = in;
+    rxq->in.ptr = in;
+    rxq->in.limit = in + rxs[pool];
+    
+    memory = rxq->in.limit;
+    out = memory;
+    
+    rxq->out.start = out;
+    rxq->out.ptr = out;
+    rxq->out.limit = out + rxs[pool];
+    
+    memory = rxq->out.limit;
+  }
+  
+  if (memory == limit) {
+    return 0;
+  } else {
+    PRINTK (KERN_ERR, "bad queue alloc %p != %p (tell maintainer)", memory, limit);
+    kfree (limit - total);
+    return -ENOMEM;
+  }
+  
+}
+
+/********** destruction of communication queues **********/
+
+static void destroy_queues (amb_dev * dev) {
+  // all queues assumed empty
+  void * memory = dev->cq.ptrs.start;
+  // includes txq.in, txq.out, rxq[].in and rxq[].out
+  
+  PRINTD (DBG_FLOW, "destroy_queues %p", dev);
+  
+  PRINTD (DBG_INIT, "freeing queues at %p", memory);
+  kfree (memory);
+  
+  return;
+}
+
+/********** basic loader commands and error handling **********/
+// centisecond timeouts - guessing away here
+static unsigned int command_timeouts [] = {
+	[host_memory_test]     = 15,
+	[read_adapter_memory]  = 2,
+	[write_adapter_memory] = 2,
+	[adapter_start]        = 50,
+	[get_version_number]   = 10,
+	[interrupt_host]       = 1,
+	[flash_erase_sector]   = 1,
+	[adap_download_block]  = 1,
+	[adap_erase_flash]     = 1,
+	[adap_run_in_iram]     = 1,
+	[adap_end_download]    = 1
+};
+
+
+static unsigned int command_successes [] = {
+	[host_memory_test]     = COMMAND_PASSED_TEST,
+	[read_adapter_memory]  = COMMAND_READ_DATA_OK,
+	[write_adapter_memory] = COMMAND_WRITE_DATA_OK,
+	[adapter_start]        = COMMAND_COMPLETE,
+	[get_version_number]   = COMMAND_COMPLETE,
+	[interrupt_host]       = COMMAND_COMPLETE,
+	[flash_erase_sector]   = COMMAND_COMPLETE,
+	[adap_download_block]  = COMMAND_COMPLETE,
+	[adap_erase_flash]     = COMMAND_COMPLETE,
+	[adap_run_in_iram]     = COMMAND_COMPLETE,
+	[adap_end_download]    = COMMAND_COMPLETE
+};
+  
+static  int decode_loader_result (loader_command cmd, u32 result)
+{
+	int res;
+	const char *msg;
+
+	if (result == command_successes[cmd])
+		return 0;
+
+	switch (result) {
+		case BAD_COMMAND:
+			res = -EINVAL;
+			msg = "bad command";
+			break;
+		case COMMAND_IN_PROGRESS:
+			res = -ETIMEDOUT;
+			msg = "command in progress";
+			break;
+		case COMMAND_PASSED_TEST:
+			res = 0;
+			msg = "command passed test";
+			break;
+		case COMMAND_FAILED_TEST:
+			res = -EIO;
+			msg = "command failed test";
+			break;
+		case COMMAND_READ_DATA_OK:
+			res = 0;
+			msg = "command read data ok";
+			break;
+		case COMMAND_READ_BAD_ADDRESS:
+			res = -EINVAL;
+			msg = "command read bad address";
+			break;
+		case COMMAND_WRITE_DATA_OK:
+			res = 0;
+			msg = "command write data ok";
+			break;
+		case COMMAND_WRITE_BAD_ADDRESS:
+			res = -EINVAL;
+			msg = "command write bad address";
+			break;
+		case COMMAND_WRITE_FLASH_FAILURE:
+			res = -EIO;
+			msg = "command write flash failure";
+			break;
+		case COMMAND_COMPLETE:
+			res = 0;
+			msg = "command complete";
+			break;
+		case COMMAND_FLASH_ERASE_FAILURE:
+			res = -EIO;
+			msg = "command flash erase failure";
+			break;
+		case COMMAND_WRITE_BAD_DATA:
+			res = -EINVAL;
+			msg = "command write bad data";
+			break;
+		default:
+			res = -EINVAL;
+			msg = "unknown error";
+			PRINTD (DBG_LOAD|DBG_ERR,
+				"decode_loader_result got %d=%x !",
+				result, result);
+			break;
+	}
+
+	PRINTK (KERN_ERR, "%s", msg);
+	return res;
+}
+
+static int __devinit do_loader_command (volatile loader_block * lb,
+				     const amb_dev * dev, loader_command cmd) {
+  
+  unsigned long timeout;
+  
+  PRINTD (DBG_FLOW|DBG_LOAD, "do_loader_command");
+  
+  /* do a command
+     
+     Set the return value to zero, set the command type and set the
+     valid entry to the right magic value. The payload is already
+     correctly byte-ordered so we leave it alone. Hit the doorbell
+     with the bus address of this structure.
+     
+  */
+  
+  lb->result = 0;
+  lb->command = cpu_to_be32 (cmd);
+  lb->valid = cpu_to_be32 (DMA_VALID);
+  // dump_registers (dev);
+  // dump_loader_block (lb);
+  wr_mem (dev, offsetof(amb_mem, doorbell), virt_to_bus (lb) & ~onegigmask);
+  
+  timeout = command_timeouts[cmd] * 10;
+  
+  while (!lb->result || lb->result == cpu_to_be32 (COMMAND_IN_PROGRESS))
+    if (timeout) {
+      timeout = msleep_interruptible(timeout);
+    } else {
+      PRINTD (DBG_LOAD|DBG_ERR, "command %d timed out", cmd);
+      dump_registers (dev);
+      dump_loader_block (lb);
+      return -ETIMEDOUT;
+    }
+  
+  if (cmd == adapter_start) {
+    // wait for start command to acknowledge...
+    timeout = 100;
+    while (rd_plain (dev, offsetof(amb_mem, doorbell)))
+      if (timeout) {
+	timeout = msleep_interruptible(timeout);
+      } else {
+	PRINTD (DBG_LOAD|DBG_ERR, "start command did not clear doorbell, res=%08x",
+		be32_to_cpu (lb->result));
+	dump_registers (dev);
+	return -ETIMEDOUT;
+      }
+    return 0;
+  } else {
+    return decode_loader_result (cmd, be32_to_cpu (lb->result));
+  }
+  
+}
+
+/* loader: determine loader version */
+
+static int __devinit get_loader_version (loader_block * lb,
+				      const amb_dev * dev, u32 * version) {
+  int res;
+  
+  PRINTD (DBG_FLOW|DBG_LOAD, "get_loader_version");
+  
+  res = do_loader_command (lb, dev, get_version_number);
+  if (res)
+    return res;
+  if (version)
+    *version = be32_to_cpu (lb->payload.version);
+  return 0;
+}
+
+/* loader: write memory data blocks */
+
+static int __devinit loader_write (loader_block * lb,
+				const amb_dev * dev, const u32 * data,
+				u32 address, unsigned int count) {
+  unsigned int i;
+  transfer_block * tb = &lb->payload.transfer;
+  
+  PRINTD (DBG_FLOW|DBG_LOAD, "loader_write");
+  
+  if (count > MAX_TRANSFER_DATA)
+    return -EINVAL;
+  tb->address = cpu_to_be32 (address);
+  tb->count = cpu_to_be32 (count);
+  for (i = 0; i < count; ++i)
+    tb->data[i] = cpu_to_be32 (data[i]);
+  return do_loader_command (lb, dev, write_adapter_memory);
+}
+
+/* loader: verify memory data blocks */
+
+static int __devinit loader_verify (loader_block * lb,
+				 const amb_dev * dev, const u32 * data,
+				 u32 address, unsigned int count) {
+  unsigned int i;
+  transfer_block * tb = &lb->payload.transfer;
+  int res;
+  
+  PRINTD (DBG_FLOW|DBG_LOAD, "loader_verify");
+  
+  if (count > MAX_TRANSFER_DATA)
+    return -EINVAL;
+  tb->address = cpu_to_be32 (address);
+  tb->count = cpu_to_be32 (count);
+  res = do_loader_command (lb, dev, read_adapter_memory);
+  if (!res)
+    for (i = 0; i < count; ++i)
+      if (tb->data[i] != cpu_to_be32 (data[i])) {
+	res = -EINVAL;
+	break;
+      }
+  return res;
+}
+
+/* loader: start microcode */
+
+static int __devinit loader_start (loader_block * lb,
+				const amb_dev * dev, u32 address) {
+  PRINTD (DBG_FLOW|DBG_LOAD, "loader_start");
+  
+  lb->payload.start = cpu_to_be32 (address);
+  return do_loader_command (lb, dev, adapter_start);
+}
+
+/********** reset card **********/
+
+static inline void sf (const char * msg)
+{
+	PRINTK (KERN_ERR, "self-test failed: %s", msg);
+}
+
+static int amb_reset (amb_dev * dev, int diags) {
+  u32 word;
+  
+  PRINTD (DBG_FLOW|DBG_LOAD, "amb_reset");
+  
+  word = rd_plain (dev, offsetof(amb_mem, reset_control));
+  // put card into reset state
+  wr_plain (dev, offsetof(amb_mem, reset_control), word | AMB_RESET_BITS);
+  // wait a short while
+  udelay (10);
+#if 1
+  // put card into known good state
+  wr_plain (dev, offsetof(amb_mem, interrupt_control), AMB_DOORBELL_BITS);
+  // clear all interrupts just in case
+  wr_plain (dev, offsetof(amb_mem, interrupt), -1);
+#endif
+  // clear self-test done flag
+  wr_plain (dev, offsetof(amb_mem, mb.loader.ready), 0);
+  // take card out of reset state
+  wr_plain (dev, offsetof(amb_mem, reset_control), word &~ AMB_RESET_BITS);
+  
+  if (diags) { 
+    unsigned long timeout;
+    // 4.2 second wait
+    msleep(4200);
+    // half second time-out
+    timeout = 500;
+    while (!rd_plain (dev, offsetof(amb_mem, mb.loader.ready)))
+      if (timeout) {
+	timeout = msleep_interruptible(timeout);
+      } else {
+	PRINTD (DBG_LOAD|DBG_ERR, "reset timed out");
+	return -ETIMEDOUT;
+      }
+    
+    // get results of self-test
+    // XXX double check byte-order
+    word = rd_mem (dev, offsetof(amb_mem, mb.loader.result));
+    if (word & SELF_TEST_FAILURE) {
+      if (word & GPINT_TST_FAILURE)
+	sf ("interrupt");
+      if (word & SUNI_DATA_PATTERN_FAILURE)
+	sf ("SUNI data pattern");
+      if (word & SUNI_DATA_BITS_FAILURE)
+	sf ("SUNI data bits");
+      if (word & SUNI_UTOPIA_FAILURE)
+	sf ("SUNI UTOPIA interface");
+      if (word & SUNI_FIFO_FAILURE)
+	sf ("SUNI cell buffer FIFO");
+      if (word & SRAM_FAILURE)
+	sf ("bad SRAM");
+      // better return value?
+      return -EIO;
+    }
+    
+  }
+  return 0;
+}
+
+/********** transfer and start the microcode **********/
+
+static int __devinit ucode_init (loader_block * lb, amb_dev * dev) {
+  unsigned int i = 0;
+  unsigned int total = 0;
+  const u32 * pointer = ucode_data;
+  u32 address;
+  unsigned int count;
+  int res;
+  
+  PRINTD (DBG_FLOW|DBG_LOAD, "ucode_init");
+  
+  while (address = ucode_regions[i].start,
+	 count = ucode_regions[i].count) {
+    PRINTD (DBG_LOAD, "starting region (%x, %u)", address, count);
+    while (count) {
+      unsigned int words;
+      if (count <= MAX_TRANSFER_DATA)
+	words = count;
+      else
+	words = MAX_TRANSFER_DATA;
+      total += words;
+      res = loader_write (lb, dev, pointer, address, words);
+      if (res)
+	return res;
+      res = loader_verify (lb, dev, pointer, address, words);
+      if (res)
+	return res;
+      count -= words;
+      address += sizeof(u32) * words;
+      pointer += words;
+    }
+    i += 1;
+  }
+  if (*pointer == 0xdeadbeef) {
+    return loader_start (lb, dev, ucode_start);
+  } else {
+    // cast needed as there is no %? for pointer differnces
+    PRINTD (DBG_LOAD|DBG_ERR,
+	    "offset=%li, *pointer=%x, address=%x, total=%u",
+	    (long) (pointer - ucode_data), *pointer, address, total);
+    PRINTK (KERN_ERR, "incorrect microcode data");
+    return -ENOMEM;
+  }
+}
+
+/********** give adapter parameters **********/
+  
+static inline __be32 bus_addr(void * addr) {
+    return cpu_to_be32 (virt_to_bus (addr));
+}
+
+static int __devinit amb_talk (amb_dev * dev) {
+  adap_talk_block a;
+  unsigned char pool;
+  unsigned long timeout;
+  
+  PRINTD (DBG_FLOW, "amb_talk %p", dev);
+  
+  a.command_start = bus_addr (dev->cq.ptrs.start);
+  a.command_end   = bus_addr (dev->cq.ptrs.limit);
+  a.tx_start      = bus_addr (dev->txq.in.start);
+  a.tx_end        = bus_addr (dev->txq.in.limit);
+  a.txcom_start   = bus_addr (dev->txq.out.start);
+  a.txcom_end     = bus_addr (dev->txq.out.limit);
+  
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool) {
+    // the other "a" items are set up by the adapter
+    a.rec_struct[pool].buffer_start = bus_addr (dev->rxq[pool].in.start);
+    a.rec_struct[pool].buffer_end   = bus_addr (dev->rxq[pool].in.limit);
+    a.rec_struct[pool].rx_start     = bus_addr (dev->rxq[pool].out.start);
+    a.rec_struct[pool].rx_end       = bus_addr (dev->rxq[pool].out.limit);
+    a.rec_struct[pool].buffer_size = cpu_to_be32 (dev->rxq[pool].buffer_size);
+  }
+  
+#ifdef AMB_NEW_MICROCODE
+  // disable fast PLX prefetching
+  a.init_flags = 0;
+#endif
+  
+  // pass the structure
+  wr_mem (dev, offsetof(amb_mem, doorbell), virt_to_bus (&a));
+  
+  // 2.2 second wait (must not touch doorbell during 2 second DMA test)
+  msleep(2200);
+  // give the adapter another half second?
+  timeout = 500;
+  while (rd_plain (dev, offsetof(amb_mem, doorbell)))
+    if (timeout) {
+      timeout = msleep_interruptible(timeout);
+    } else {
+      PRINTD (DBG_INIT|DBG_ERR, "adapter init timed out");
+      return -ETIMEDOUT;
+    }
+  
+  return 0;
+}
+
+// get microcode version
+static void __devinit amb_ucode_version (amb_dev * dev) {
+  u32 major;
+  u32 minor;
+  command cmd;
+  cmd.request = cpu_to_be32 (SRB_GET_VERSION);
+  while (command_do (dev, &cmd)) {
+    set_current_state(TASK_UNINTERRUPTIBLE);
+    schedule();
+  }
+  major = be32_to_cpu (cmd.args.version.major);
+  minor = be32_to_cpu (cmd.args.version.minor);
+  PRINTK (KERN_INFO, "microcode version is %u.%u", major, minor);
+}
+  
+// swap bits within byte to get Ethernet ordering
+static u8 bit_swap (u8 byte)
+{
+    const u8 swap[] = {
+      0x0, 0x8, 0x4, 0xc,
+      0x2, 0xa, 0x6, 0xe,
+      0x1, 0x9, 0x5, 0xd,
+      0x3, 0xb, 0x7, 0xf
+    };
+    return ((swap[byte & 0xf]<<4) | swap[byte>>4]);
+}
+
+// get end station address
+static void __devinit amb_esi (amb_dev * dev, u8 * esi) {
+  u32 lower4;
+  u16 upper2;
+  command cmd;
+  
+  cmd.request = cpu_to_be32 (SRB_GET_BIA);
+  while (command_do (dev, &cmd)) {
+    set_current_state(TASK_UNINTERRUPTIBLE);
+    schedule();
+  }
+  lower4 = be32_to_cpu (cmd.args.bia.lower4);
+  upper2 = be32_to_cpu (cmd.args.bia.upper2);
+  PRINTD (DBG_LOAD, "BIA: lower4: %08x, upper2 %04x", lower4, upper2);
+  
+  if (esi) {
+    unsigned int i;
+    
+    PRINTDB (DBG_INIT, "ESI:");
+    for (i = 0; i < ESI_LEN; ++i) {
+      if (i < 4)
+	  esi[i] = bit_swap (lower4>>(8*i));
+      else
+	  esi[i] = bit_swap (upper2>>(8*(i-4)));
+      PRINTDM (DBG_INIT, " %02x", esi[i]);
+    }
+    
+    PRINTDE (DBG_INIT, "");
+  }
+  
+  return;
+}
+  
+static void fixup_plx_window (amb_dev *dev, loader_block *lb)
+{
+	// fix up the PLX-mapped window base address to match the block
+	unsigned long blb;
+	u32 mapreg;
+	blb = virt_to_bus(lb);
+	// the kernel stack had better not ever cross a 1Gb boundary!
+	mapreg = rd_plain (dev, offsetof(amb_mem, stuff[10]));
+	mapreg &= ~onegigmask;
+	mapreg |= blb & onegigmask;
+	wr_plain (dev, offsetof(amb_mem, stuff[10]), mapreg);
+	return;
+}
+
+static int __devinit amb_init (amb_dev * dev)
+{
+  loader_block lb;
+  
+  u32 version;
+  
+  if (amb_reset (dev, 1)) {
+    PRINTK (KERN_ERR, "card reset failed!");
+  } else {
+    fixup_plx_window (dev, &lb);
+    
+    if (get_loader_version (&lb, dev, &version)) {
+      PRINTK (KERN_INFO, "failed to get loader version");
+    } else {
+      PRINTK (KERN_INFO, "loader version is %08x", version);
+      
+      if (ucode_init (&lb, dev)) {
+	PRINTK (KERN_ERR, "microcode failure");
+      } else if (create_queues (dev, cmds, txs, rxs, rxs_bs)) {
+	PRINTK (KERN_ERR, "failed to get memory for queues");
+      } else {
+	
+	if (amb_talk (dev)) {
+	  PRINTK (KERN_ERR, "adapter did not accept queues");
+	} else {
+	  
+	  amb_ucode_version (dev);
+	  return 0;
+	  
+	} /* amb_talk */
+	
+	destroy_queues (dev);
+      } /* create_queues, ucode_init */
+      
+      amb_reset (dev, 0);
+    } /* get_loader_version */
+    
+  } /* amb_reset */
+  
+  return -EINVAL;
+}
+
+static void setup_dev(amb_dev *dev, struct pci_dev *pci_dev) 
+{
+      unsigned char pool;
+      memset (dev, 0, sizeof(amb_dev));
+      
+      // set up known dev items straight away
+      dev->pci_dev = pci_dev; 
+      pci_set_drvdata(pci_dev, dev);
+      
+      dev->iobase = pci_resource_start (pci_dev, 1);
+      dev->irq = pci_dev->irq; 
+      dev->membase = bus_to_virt(pci_resource_start(pci_dev, 0));
+      
+      // flags (currently only dead)
+      dev->flags = 0;
+      
+      // Allocate cell rates (fibre)
+      // ATM_OC3_PCR = 1555200000/8/270*260/53 - 29/53
+      // to be really pedantic, this should be ATM_OC3c_PCR
+      dev->tx_avail = ATM_OC3_PCR;
+      dev->rx_avail = ATM_OC3_PCR;
+      
+#ifdef FILL_RX_POOLS_IN_BH
+      // initialise bottom half
+      INIT_WORK(&dev->bh, (void (*)(void *)) fill_rx_pools, dev);
+#endif
+      
+      // semaphore for txer/rxer modifications - we cannot use a
+      // spinlock as the critical region needs to switch processes
+      init_MUTEX (&dev->vcc_sf);
+      // queue manipulation spinlocks; we want atomic reads and
+      // writes to the queue descriptors (handles IRQ and SMP)
+      // consider replacing "int pending" -> "atomic_t available"
+      // => problem related to who gets to move queue pointers
+      spin_lock_init (&dev->cq.lock);
+      spin_lock_init (&dev->txq.lock);
+      for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+	spin_lock_init (&dev->rxq[pool].lock);
+}
+
+static void setup_pci_dev(struct pci_dev *pci_dev)
+{
+	unsigned char lat;
+      
+	// enable bus master accesses
+	pci_set_master(pci_dev);
+
+	// frobnicate latency (upwards, usually)
+	pci_read_config_byte (pci_dev, PCI_LATENCY_TIMER, &lat);
+
+	if (!pci_lat)
+		pci_lat = (lat < MIN_PCI_LATENCY) ? MIN_PCI_LATENCY : lat;
+
+	if (lat != pci_lat) {
+		PRINTK (KERN_INFO, "Changing PCI latency timer from %hu to %hu",
+			lat, pci_lat);
+		pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, pci_lat);
+	}
+}
+
+static int __devinit amb_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_ent)
+{
+	amb_dev * dev;
+	int err;
+	unsigned int irq;
+      
+	err = pci_enable_device(pci_dev);
+	if (err < 0) {
+		PRINTK (KERN_ERR, "skipped broken (PLX rev 2) card");
+		goto out;
+	}
+
+	// read resources from PCI configuration space
+	irq = pci_dev->irq;
+
+	if (pci_dev->device == PCI_DEVICE_ID_MADGE_AMBASSADOR_BAD) {
+		PRINTK (KERN_ERR, "skipped broken (PLX rev 2) card");
+		err = -EINVAL;
+		goto out_disable;
+	}
+
+	PRINTD (DBG_INFO, "found Madge ATM adapter (amb) at"
+		" IO %lx, IRQ %u, MEM %p", pci_resource_start(pci_dev, 1),
+		irq, bus_to_virt(pci_resource_start(pci_dev, 0)));
+
+	// check IO region
+	err = pci_request_region(pci_dev, 1, DEV_LABEL);
+	if (err < 0) {
+		PRINTK (KERN_ERR, "IO range already in use!");
+		goto out_disable;
+	}
+
+	dev = kmalloc (sizeof(amb_dev), GFP_KERNEL);
+	if (!dev) {
+		PRINTK (KERN_ERR, "out of memory!");
+		err = -ENOMEM;
+		goto out_release;
+	}
+
+	setup_dev(dev, pci_dev);
+
+	err = amb_init(dev);
+	if (err < 0) {
+		PRINTK (KERN_ERR, "adapter initialisation failure");
+		goto out_free;
+	}
+
+	setup_pci_dev(pci_dev);
+
+	// grab (but share) IRQ and install handler
+	err = request_irq(irq, interrupt_handler, SA_SHIRQ, DEV_LABEL, dev);
+	if (err < 0) {
+		PRINTK (KERN_ERR, "request IRQ failed!");
+		goto out_reset;
+	}
+
+	dev->atm_dev = atm_dev_register (DEV_LABEL, &amb_ops, -1, NULL);
+	if (!dev->atm_dev) {
+		PRINTD (DBG_ERR, "failed to register Madge ATM adapter");
+		err = -EINVAL;
+		goto out_free_irq;
+	}
+
+	PRINTD (DBG_INFO, "registered Madge ATM adapter (no. %d) (%p) at %p",
+		dev->atm_dev->number, dev, dev->atm_dev);
+		dev->atm_dev->dev_data = (void *) dev;
+
+	// register our address
+	amb_esi (dev, dev->atm_dev->esi);
+
+	// 0 bits for vpi, 10 bits for vci
+	dev->atm_dev->ci_range.vpi_bits = NUM_VPI_BITS;
+	dev->atm_dev->ci_range.vci_bits = NUM_VCI_BITS;
+
+	init_timer(&dev->housekeeping);
+	dev->housekeeping.function = do_housekeeping;
+	dev->housekeeping.data = (unsigned long) dev;
+	mod_timer(&dev->housekeeping, jiffies);
+
+	// enable host interrupts
+	interrupts_on (dev);
+
+out:
+	return err;
+
+out_free_irq:
+	free_irq(irq, dev);
+out_reset:
+	amb_reset(dev, 0);
+out_free:
+	kfree(dev);
+out_release:
+	pci_release_region(pci_dev, 1);
+out_disable:
+	pci_disable_device(pci_dev);
+	goto out;
+}
+
+
+static void __devexit amb_remove_one(struct pci_dev *pci_dev)
+{
+	struct amb_dev *dev;
+
+	dev = pci_get_drvdata(pci_dev);
+
+	PRINTD(DBG_INFO|DBG_INIT, "closing %p (atm_dev = %p)", dev, dev->atm_dev);
+	del_timer_sync(&dev->housekeeping);
+	// the drain should not be necessary
+	drain_rx_pools(dev);
+	interrupts_off(dev);
+	amb_reset(dev, 0);
+	free_irq(dev->irq, dev);
+	pci_disable_device(pci_dev);
+	destroy_queues(dev);
+	atm_dev_deregister(dev->atm_dev);
+	kfree(dev);
+	pci_release_region(pci_dev, 1);
+}
+
+static void __init amb_check_args (void) {
+  unsigned char pool;
+  unsigned int max_rx_size;
+  
+#ifdef DEBUG_AMBASSADOR
+  PRINTK (KERN_NOTICE, "debug bitmap is %hx", debug &= DBG_MASK);
+#else
+  if (debug)
+    PRINTK (KERN_NOTICE, "no debugging support");
+#endif
+  
+  if (cmds < MIN_QUEUE_SIZE)
+    PRINTK (KERN_NOTICE, "cmds has been raised to %u",
+	    cmds = MIN_QUEUE_SIZE);
+  
+  if (txs < MIN_QUEUE_SIZE)
+    PRINTK (KERN_NOTICE, "txs has been raised to %u",
+	    txs = MIN_QUEUE_SIZE);
+  
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+    if (rxs[pool] < MIN_QUEUE_SIZE)
+      PRINTK (KERN_NOTICE, "rxs[%hu] has been raised to %u",
+	      pool, rxs[pool] = MIN_QUEUE_SIZE);
+  
+  // buffers sizes should be greater than zero and strictly increasing
+  max_rx_size = 0;
+  for (pool = 0; pool < NUM_RX_POOLS; ++pool)
+    if (rxs_bs[pool] <= max_rx_size)
+      PRINTK (KERN_NOTICE, "useless pool (rxs_bs[%hu] = %u)",
+	      pool, rxs_bs[pool]);
+    else
+      max_rx_size = rxs_bs[pool];
+  
+  if (rx_lats < MIN_RX_BUFFERS)
+    PRINTK (KERN_NOTICE, "rx_lats has been raised to %u",
+	    rx_lats = MIN_RX_BUFFERS);
+  
+  return;
+}
+
+/********** module stuff **********/
+
+MODULE_AUTHOR(maintainer_string);
+MODULE_DESCRIPTION(description_string);
+MODULE_LICENSE("GPL");
+module_param(debug,   ushort, 0644);
+module_param(cmds,    uint, 0);
+module_param(txs,     uint, 0);
+module_param_array(rxs,     uint, NULL, 0);
+module_param_array(rxs_bs,  uint, NULL, 0);
+module_param(rx_lats, uint, 0);
+module_param(pci_lat, byte, 0);
+MODULE_PARM_DESC(debug,   "debug bitmap, see .h file");
+MODULE_PARM_DESC(cmds,    "number of command queue entries");
+MODULE_PARM_DESC(txs,     "number of TX queue entries");
+MODULE_PARM_DESC(rxs,     "number of RX queue entries [" __MODULE_STRING(NUM_RX_POOLS) "]");
+MODULE_PARM_DESC(rxs_bs,  "size of RX buffers [" __MODULE_STRING(NUM_RX_POOLS) "]");
+MODULE_PARM_DESC(rx_lats, "number of extra buffers to cope with RX latencies");
+MODULE_PARM_DESC(pci_lat, "PCI latency in bus cycles");
+
+/********** module entry **********/
+
+static struct pci_device_id amb_pci_tbl[] = {
+	{ PCI_VENDOR_ID_MADGE, PCI_DEVICE_ID_MADGE_AMBASSADOR, PCI_ANY_ID, PCI_ANY_ID,
+	  0, 0, 0 },
+	{ PCI_VENDOR_ID_MADGE, PCI_DEVICE_ID_MADGE_AMBASSADOR_BAD, PCI_ANY_ID, PCI_ANY_ID,
+	  0, 0, 0 },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, amb_pci_tbl);
+
+static struct pci_driver amb_driver = {
+	.name =		"amb",
+	.probe =	amb_probe,
+	.remove =	__devexit_p(amb_remove_one),
+	.id_table =	amb_pci_tbl,
+};
+
+static int __init amb_module_init (void)
+{
+  PRINTD (DBG_FLOW|DBG_INIT, "init_module");
+  
+  // sanity check - cast needed as printk does not support %Zu
+  if (sizeof(amb_mem) != 4*16 + 4*12) {
+    PRINTK (KERN_ERR, "Fix amb_mem (is %lu words).",
+	    (unsigned long) sizeof(amb_mem));
+    return -ENOMEM;
+  }
+  
+  show_version();
+  
+  amb_check_args();
+  
+  // get the juice
+  return pci_register_driver(&amb_driver);
+}
+
+/********** module exit **********/
+
+static void __exit amb_module_exit (void)
+{
+  PRINTD (DBG_FLOW|DBG_INIT, "cleanup_module");
+  
+  return pci_unregister_driver(&amb_driver);
+}
+
+module_init(amb_module_init);
+module_exit(amb_module_exit);
diff --git a/drivers/atm/ambassador.h b/drivers/atm/ambassador.h
new file mode 100644
index 0000000..84a9306
--- /dev/null
+++ b/drivers/atm/ambassador.h
@@ -0,0 +1,679 @@
+/*
+  Madge Ambassador ATM Adapter driver.
+  Copyright (C) 1995-1999  Madge Networks Ltd.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+  system and in the file COPYING in the Linux kernel source.
+*/
+
+#ifndef AMBASSADOR_H
+#define AMBASSADOR_H
+
+#include <linux/config.h>
+
+#ifdef CONFIG_ATM_AMBASSADOR_DEBUG
+#define DEBUG_AMBASSADOR
+#endif
+
+#define DEV_LABEL                          "amb"
+
+#ifndef PCI_VENDOR_ID_MADGE
+#define PCI_VENDOR_ID_MADGE                0x10B6
+#endif
+#ifndef PCI_VENDOR_ID_MADGE_AMBASSADOR
+#define PCI_DEVICE_ID_MADGE_AMBASSADOR     0x1001
+#endif
+#ifndef PCI_VENDOR_ID_MADGE_AMBASSADOR_BAD
+#define PCI_DEVICE_ID_MADGE_AMBASSADOR_BAD 0x1002
+#endif
+
+// diagnostic output
+
+#define PRINTK(severity,format,args...) \
+  printk(severity DEV_LABEL ": " format "\n" , ## args)
+
+#ifdef DEBUG_AMBASSADOR
+
+#define DBG_ERR  0x0001
+#define DBG_WARN 0x0002
+#define DBG_INFO 0x0004
+#define DBG_INIT 0x0008
+#define DBG_LOAD 0x0010
+#define DBG_VCC  0x0020
+#define DBG_QOS  0x0040
+#define DBG_CMD  0x0080
+#define DBG_TX   0x0100
+#define DBG_RX   0x0200
+#define DBG_SKB  0x0400
+#define DBG_POOL 0x0800
+#define DBG_IRQ  0x1000
+#define DBG_FLOW 0x2000
+#define DBG_REGS 0x4000
+#define DBG_DATA 0x8000
+#define DBG_MASK 0xffff
+
+/* the ## prevents the annoying double expansion of the macro arguments */
+/* KERN_INFO is used since KERN_DEBUG often does not make it to the console */
+#define PRINTDB(bits,format,args...) \
+  ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format , ## args) : 1 )
+#define PRINTDM(bits,format,args...) \
+  ( (debug & (bits)) ? printk (format , ## args) : 1 )
+#define PRINTDE(bits,format,args...) \
+  ( (debug & (bits)) ? printk (format "\n" , ## args) : 1 )
+#define PRINTD(bits,format,args...) \
+  ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format "\n" , ## args) : 1 )
+
+#else
+
+#define PRINTD(bits,format,args...)
+#define PRINTDB(bits,format,args...)
+#define PRINTDM(bits,format,args...)
+#define PRINTDE(bits,format,args...)
+
+#endif
+
+#define PRINTDD(bits,format,args...)
+#define PRINTDDB(sec,fmt,args...)
+#define PRINTDDM(sec,fmt,args...)
+#define PRINTDDE(sec,fmt,args...)
+
+// tunable values (?)
+
+/* MUST be powers of two -- why ? */
+#define COM_Q_ENTRIES        8
+#define TX_Q_ENTRIES        32
+#define RX_Q_ENTRIES        64
+
+// fixed values
+
+// guessing
+#define AMB_EXTENT         0x80
+
+// Minimum allowed size for an Ambassador queue
+#define MIN_QUEUE_SIZE     2
+
+// Ambassador microcode allows 1 to 4 pools, we use 4 (simpler)
+#define NUM_RX_POOLS	   4
+
+// minimum RX buffers required to cope with replenishing delay
+#define MIN_RX_BUFFERS	   1
+
+// minimum PCI latency we will tolerate (32 IS TOO SMALL)
+#define MIN_PCI_LATENCY   64 // 255
+
+// VCs supported by card (VPI always 0)
+#define NUM_VPI_BITS       0
+#define NUM_VCI_BITS      10
+#define NUM_VCS         1024
+
+/* The status field bits defined so far. */
+#define RX_ERR		0x8000 // always present if there is an error (hmm)
+#define CRC_ERR		0x4000 // AAL5 CRC error
+#define LEN_ERR		0x2000 // overlength frame
+#define ABORT_ERR	0x1000 // zero length field in received frame
+#define UNUSED_ERR	0x0800 // buffer returned unused
+
+// Adaptor commands
+
+#define SRB_OPEN_VC		0
+/* par_0: dwordswap(VC_number) */
+/* par_1: dwordswap(flags<<16) or wordswap(flags)*/ 
+/* flags:		*/
+
+/* LANE:	0x0004		*/
+/* NOT_UBR:	0x0008		*/
+/* ABR:		0x0010		*/
+
+/* RxPool0:	0x0000		*/
+/* RxPool1:	0x0020		*/
+/* RxPool2:	0x0040		*/
+/* RxPool3:	0x0060		*/
+
+/* par_2: dwordswap(fp_rate<<16) or wordswap(fp_rate) */
+
+#define	SRB_CLOSE_VC		1
+/* par_0: dwordswap(VC_number) */
+
+#define	SRB_GET_BIA		2
+/* returns 		*/
+/* par_0: dwordswap(half BIA) */
+/* par_1: dwordswap(half BIA) */
+
+#define	SRB_GET_SUNI_STATS	3
+/* par_0: dwordswap(physical_host_address) */
+
+#define	SRB_SET_BITS_8		4
+#define	SRB_SET_BITS_16		5
+#define	SRB_SET_BITS_32		6
+#define	SRB_CLEAR_BITS_8	7
+#define	SRB_CLEAR_BITS_16	8
+#define	SRB_CLEAR_BITS_32	9
+/* par_0: dwordswap(ATMizer address)	*/
+/* par_1: dwordswap(mask) */
+
+#define	SRB_SET_8		10
+#define	SRB_SET_16		11
+#define	SRB_SET_32		12
+/* par_0: dwordswap(ATMizer address)	*/
+/* par_1: dwordswap(data) */
+
+#define	SRB_GET_32		13
+/* par_0: dwordswap(ATMizer address)	*/
+/* returns			*/
+/* par_1: dwordswap(ATMizer data) */
+
+#define SRB_GET_VERSION		14
+/* returns 		*/
+/* par_0: dwordswap(Major Version) */
+/* par_1: dwordswap(Minor Version) */
+
+#define SRB_FLUSH_BUFFER_Q	15
+/* Only flags to define which buffer pool; all others must be zero */
+/* par_0: dwordswap(flags<<16) or wordswap(flags)*/ 
+
+#define	SRB_GET_DMA_SPEEDS	16
+/* returns 		*/
+/* par_0: dwordswap(Read speed (bytes/sec)) */
+/* par_1: dwordswap(Write speed (bytes/sec)) */
+
+#define SRB_MODIFY_VC_RATE	17
+/* par_0: dwordswap(VC_number) */
+/* par_1: dwordswap(fp_rate<<16) or wordswap(fp_rate) */
+
+#define SRB_MODIFY_VC_FLAGS	18
+/* par_0: dwordswap(VC_number) */
+/* par_1: dwordswap(flags<<16) or wordswap(flags)*/ 
+
+/* flags:		*/
+
+/* LANE:	0x0004		*/
+/* NOT_UBR:	0x0008		*/
+/* ABR:		0x0010		*/
+
+/* RxPool0:	0x0000		*/
+/* RxPool1:	0x0020		*/
+/* RxPool2:	0x0040		*/
+/* RxPool3:	0x0060		*/
+
+#define SRB_RATE_SHIFT          16
+#define SRB_POOL_SHIFT          (SRB_FLAGS_SHIFT+5)
+#define SRB_FLAGS_SHIFT         16
+
+#define	SRB_STOP_TASKING	19
+#define	SRB_START_TASKING	20
+#define SRB_SHUT_DOWN		21
+#define MAX_SRB			21
+
+#define SRB_COMPLETE		0xffffffff
+
+#define TX_FRAME          	0x80000000
+
+// number of types of SRB MUST be a power of two -- why?
+#define NUM_OF_SRB	32
+
+// number of bits of period info for rate
+#define MAX_RATE_BITS	6
+
+#define TX_UBR          0x0000
+#define TX_UBR_CAPPED   0x0008
+#define TX_ABR          0x0018
+#define TX_FRAME_NOTCAP 0x0000
+#define TX_FRAME_CAPPED 0x8000
+
+#define FP_155_RATE	0x24b1
+#define FP_25_RATE	0x1f9d
+
+/* #define VERSION_NUMBER 0x01000000 // initial release */
+/* #define VERSION_NUMBER 0x01010000 // fixed startup probs PLX MB0 not cleared */
+/* #define VERSION_NUMBER 0x01020000 // changed SUNI reset timings; allowed r/w onchip */
+
+/* #define VERSION_NUMBER 0x01030000 // clear local doorbell int reg on reset */
+/* #define VERSION_NUMBER 0x01040000 // PLX bug work around version PLUS */
+/* remove race conditions on basic interface */
+/* indicate to the host that diagnostics */
+/* have finished; if failed, how and what  */
+/* failed */
+/* fix host memory test to fix PLX bug */
+/* allow flash upgrade and BIA upgrade directly */
+/*  */
+#define VERSION_NUMBER 0x01050025 /* Jason's first hacked version. */
+/* Change in download algorithm */
+
+#define DMA_VALID 0xb728e149 /* completely random */
+
+#define FLASH_BASE 0xa0c00000
+#define FLASH_SIZE 0x00020000			/* 128K */
+#define BIA_BASE (FLASH_BASE+0x0001c000)	/* Flash Sector 7 */
+#define BIA_ADDRESS ((void *)0xa0c1c000)
+#define PLX_BASE 0xe0000000
+
+typedef enum {
+  host_memory_test = 1,
+  read_adapter_memory,
+  write_adapter_memory,
+  adapter_start,
+  get_version_number,
+  interrupt_host,
+  flash_erase_sector,
+  adap_download_block = 0x20,
+  adap_erase_flash,
+  adap_run_in_iram,
+  adap_end_download
+} loader_command;
+
+#define BAD_COMMAND                     (-1)
+#define COMMAND_IN_PROGRESS             1
+#define COMMAND_PASSED_TEST             2
+#define COMMAND_FAILED_TEST             3
+#define COMMAND_READ_DATA_OK            4
+#define COMMAND_READ_BAD_ADDRESS        5
+#define COMMAND_WRITE_DATA_OK           6
+#define COMMAND_WRITE_BAD_ADDRESS       7
+#define COMMAND_WRITE_FLASH_FAILURE     8
+#define COMMAND_COMPLETE                9
+#define COMMAND_FLASH_ERASE_FAILURE	10
+#define COMMAND_WRITE_BAD_DATA		11
+
+/* bit fields for mailbox[0] return values */
+
+#define GPINT_TST_FAILURE               0x00000001      
+#define SUNI_DATA_PATTERN_FAILURE       0x00000002
+#define SUNI_DATA_BITS_FAILURE          0x00000004
+#define SUNI_UTOPIA_FAILURE             0x00000008
+#define SUNI_FIFO_FAILURE               0x00000010
+#define SRAM_FAILURE                    0x00000020
+#define SELF_TEST_FAILURE               0x0000003f
+
+/* mailbox[1] = 0 in progress, -1 on completion */
+/* mailbox[2] = current test 00 00 test(8 bit) phase(8 bit) */
+/* mailbox[3] = last failure, 00 00 test(8 bit) phase(8 bit) */
+/* mailbox[4],mailbox[5],mailbox[6] random failure values */
+
+/* PLX/etc. memory map including command structure */
+
+/* These registers may also be memory mapped in PCI memory */
+
+#define UNUSED_LOADER_MAILBOXES 6
+
+typedef struct {
+  u32 stuff[16];
+  union {
+    struct {
+      u32 result;
+      u32 ready;
+      u32 stuff[UNUSED_LOADER_MAILBOXES];
+    } loader;
+    struct {
+      u32 cmd_address;
+      u32 tx_address;
+      u32 rx_address[NUM_RX_POOLS];
+      u32 gen_counter;
+      u32 spare;
+    } adapter;
+  } mb;
+  u32 doorbell;
+  u32 interrupt;
+  u32 interrupt_control;
+  u32 reset_control;
+} amb_mem;
+
+/* RESET bit, IRQ (card to host) and doorbell (host to card) enable bits */
+#define AMB_RESET_BITS	   0x40000000
+#define AMB_INTERRUPT_BITS 0x00000300
+#define AMB_DOORBELL_BITS  0x00030000
+
+/* loader commands */
+
+#define MAX_COMMAND_DATA 13
+#define MAX_TRANSFER_DATA 11
+
+typedef struct {
+  __be32 address;
+  __be32 count;
+  __be32 data[MAX_TRANSFER_DATA];
+} transfer_block;
+
+typedef struct {
+  __be32 result;
+  __be32 command;
+  union {
+    transfer_block transfer;
+    __be32 version;
+    __be32 start;
+    __be32 data[MAX_COMMAND_DATA];
+  } payload;
+  __be32 valid;
+} loader_block;
+
+/* command queue */
+
+/* Again all data are BIG ENDIAN */
+
+typedef	struct {
+  union {
+    struct {
+      __be32 vc;
+      __be32 flags;
+      __be32 rate;
+    } open;
+    struct {
+      __be32 vc;
+      __be32 rate;
+    } modify_rate;
+    struct {
+      __be32 vc;
+      __be32 flags;
+    } modify_flags;
+    struct {
+      __be32 vc;
+    } close;
+    struct {
+      __be32 lower4;
+      __be32 upper2;
+    } bia;
+    struct {
+      __be32 address;
+    } suni;
+    struct {
+      __be32 major;
+      __be32 minor;
+    } version;
+    struct {
+      __be32 read;
+      __be32 write;
+    } speed;
+    struct {
+      __be32 flags;
+    } flush;
+    struct {
+      __be32 address;
+      __be32 data;
+    } memory;
+    __be32 par[3];
+  } args;
+  __be32 request;
+} command;
+
+/* transmit queues and associated structures */
+
+/* The hosts transmit structure. All BIG ENDIAN; host address
+   restricted to first 1GByte, but address passed to the card must
+   have the top MS bit or'ed in. -- check this */
+
+/* TX is described by 1+ tx_frags followed by a tx_frag_end */
+
+typedef struct {
+  __be32 bytes;
+  __be32 address;
+} tx_frag;
+
+/* apart from handle the fields here are for the adapter to play with
+   and should be set to zero */
+
+typedef struct {
+  u32	handle;
+  u16	vc;
+  u16	next_descriptor_length;
+  u32	next_descriptor;
+#ifdef AMB_NEW_MICROCODE
+  u8    cpcs_uu;
+  u8    cpi;
+  u16   pad;
+#endif
+} tx_frag_end;
+
+typedef struct {
+  tx_frag tx_frag;
+  tx_frag_end tx_frag_end;
+  struct sk_buff * skb;
+} tx_simple;
+
+#if 0
+typedef union {
+  tx_frag	fragment;
+  tx_frag_end	end_of_list;
+} tx_descr;
+#endif
+
+/* this "points" to the sequence of fragments and trailer */
+
+typedef	struct {
+  __be16	vc;
+  __be16	tx_descr_length;
+  __be32	tx_descr_addr;
+} tx_in;
+
+/* handle is the handle from tx_in */
+
+typedef	struct {
+  u32 handle;
+} tx_out;
+
+/* receive frame structure */
+
+/* All BIG ENDIAN; handle is as passed from host; length is zero for
+   aborted frames, and frames with errors. Header is actually VC
+   number, lec-id is NOT yet supported. */
+
+typedef struct {
+  u32  handle;
+  __be16  vc;
+  __be16  lec_id; // unused
+  __be16  status;
+  __be16  length;
+} rx_out;
+
+/* buffer supply structure */
+
+typedef	struct {
+  u32 handle;
+  __be32 host_address;
+} rx_in;
+
+/* This first structure is the area in host memory where the adapter
+   writes its pointer values. These pointer values are BIG ENDIAN and
+   reside in the same 4MB 'page' as this structure. The host gives the
+   adapter the address of this block by sending a doorbell interrupt
+   to the adapter after downloading the code and setting it going. The
+   addresses have the top 10 bits set to 1010000010b -- really?
+   
+   The host must initialise these before handing the block to the
+   adapter. */
+
+typedef struct {
+  __be32 command_start;		/* SRB commands completions */
+  __be32 command_end;		/* SRB commands completions */
+  __be32 tx_start;
+  __be32 tx_end;
+  __be32 txcom_start;		/* tx completions */
+  __be32 txcom_end;		/* tx completions */
+  struct {
+    __be32 buffer_start;
+    __be32 buffer_end;
+    u32 buffer_q_get;
+    u32 buffer_q_end;
+    u32 buffer_aptr;
+    __be32 rx_start;		/* rx completions */
+    __be32 rx_end;
+    u32 rx_ptr;
+    __be32 buffer_size;		/* size of host buffer */
+  } rec_struct[NUM_RX_POOLS];
+#ifdef AMB_NEW_MICROCODE
+  u16 init_flags;
+  u16 talk_block_spare;
+#endif
+} adap_talk_block;
+
+/* This structure must be kept in line with the vcr image in sarmain.h
+   
+   This is the structure in the host filled in by the adapter by
+   GET_SUNI_STATS */
+
+typedef struct {
+  u8	racp_chcs;
+  u8	racp_uhcs;
+  u16	spare;
+  u32	racp_rcell;
+  u32	tacp_tcell;
+  u32	flags;
+  u32	dropped_cells;
+  u32	dropped_frames;
+} suni_stats;
+
+typedef enum {
+  dead
+} amb_flags;
+
+#define NEXTQ(current,start,limit) \
+  ( (current)+1 < (limit) ? (current)+1 : (start) ) 
+
+typedef struct {
+  command * start;
+  command * in;
+  command * out;
+  command * limit;
+} amb_cq_ptrs;
+
+typedef struct {
+  spinlock_t lock;
+  unsigned int pending;
+  unsigned int high;
+  unsigned int filled;
+  unsigned int maximum; // size - 1 (q implementation)
+  amb_cq_ptrs ptrs;
+} amb_cq;
+
+typedef struct {
+  spinlock_t lock;
+  unsigned int pending;
+  unsigned int high;
+  unsigned int filled;
+  unsigned int maximum; // size - 1 (q implementation)
+  struct {
+    tx_in * start;
+    tx_in * ptr;
+    tx_in * limit;
+  } in;
+  struct {
+    tx_out * start;
+    tx_out * ptr;
+    tx_out * limit;
+  } out;
+} amb_txq;
+
+typedef struct {
+  spinlock_t lock;
+  unsigned int pending;
+  unsigned int low;
+  unsigned int emptied;
+  unsigned int maximum; // size - 1 (q implementation)
+  struct {
+    rx_in * start;
+    rx_in * ptr;
+    rx_in * limit;
+  } in;
+  struct {
+    rx_out * start;
+    rx_out * ptr;
+    rx_out * limit;
+  } out;
+  unsigned int buffers_wanted;
+  unsigned int buffer_size;
+} amb_rxq;
+
+typedef struct {
+  unsigned long tx_ok;
+  struct {
+    unsigned long ok;
+    unsigned long error;
+    unsigned long badcrc;
+    unsigned long toolong;
+    unsigned long aborted;
+    unsigned long unused;
+  } rx;
+} amb_stats;
+
+// a single struct pointed to by atm_vcc->dev_data
+
+typedef struct {
+  u8               tx_vc_bits:7;
+  u8               tx_present:1;
+} amb_tx_info;
+
+typedef struct {
+  unsigned char    pool;
+} amb_rx_info;
+
+typedef struct {
+  amb_rx_info      rx_info;
+  u16              tx_frame_bits;
+  unsigned int     tx_rate;
+  unsigned int     rx_rate;
+} amb_vcc;
+
+struct amb_dev {
+  u8               irq;
+  long		   flags;
+  u32              iobase;
+  u32 *            membase;
+
+#ifdef FILL_RX_POOLS_IN_BH
+  struct work_struct bh;
+#endif
+  
+  amb_cq           cq;
+  amb_txq          txq;
+  amb_rxq          rxq[NUM_RX_POOLS];
+  
+  struct semaphore vcc_sf;
+  amb_tx_info      txer[NUM_VCS];
+  struct atm_vcc * rxer[NUM_VCS];
+  unsigned int     tx_avail;
+  unsigned int     rx_avail;
+  
+  amb_stats        stats;
+  
+  struct atm_dev * atm_dev;
+  struct pci_dev * pci_dev;
+  struct timer_list housekeeping;
+};
+
+typedef struct amb_dev amb_dev;
+
+#define AMB_DEV(atm_dev) ((amb_dev *) (atm_dev)->dev_data)
+#define AMB_VCC(atm_vcc) ((amb_vcc *) (atm_vcc)->dev_data)
+
+/* the microcode */
+
+typedef struct {
+  u32 start;
+  unsigned int count;
+} region;
+
+static region ucode_regions[];
+static u32 ucode_data[];
+static u32 ucode_start;
+
+/* rate rounding */
+
+typedef enum {
+  round_up,
+  round_down,
+  round_nearest
+} rounding;
+
+#endif
diff --git a/drivers/atm/atmdev_init.c b/drivers/atm/atmdev_init.c
new file mode 100644
index 0000000..0e09e5c
--- /dev/null
+++ b/drivers/atm/atmdev_init.c
@@ -0,0 +1,54 @@
+/* drivers/atm/atmdev_init.c - ATM device driver initialization */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#include <linux/config.h>
+#include <linux/init.h>
+
+
+#ifdef CONFIG_ATM_ZATM
+extern int zatm_detect(void);
+#endif
+#ifdef CONFIG_ATM_AMBASSADOR
+extern int amb_detect(void);
+#endif
+#ifdef CONFIG_ATM_HORIZON
+extern int hrz_detect(void);
+#endif
+#ifdef CONFIG_ATM_FORE200E
+extern int fore200e_detect(void);
+#endif
+#ifdef CONFIG_ATM_LANAI
+extern int lanai_detect(void);
+#endif
+
+
+/*
+ * For historical reasons, atmdev_init returns the number of devices found.
+ * Note that some detections may not go via atmdev_init (e.g. eni.c), so this
+ * number is meaningless.
+ */
+
+int __init atmdev_init(void)
+{
+	int devs;
+
+	devs = 0;
+#ifdef CONFIG_ATM_ZATM
+	devs += zatm_detect();
+#endif
+#ifdef CONFIG_ATM_AMBASSADOR
+	devs += amb_detect();
+#endif
+#ifdef CONFIG_ATM_HORIZON
+	devs += hrz_detect();
+#endif
+#ifdef CONFIG_ATM_FORE200E
+	devs += fore200e_detect();
+#endif
+#ifdef CONFIG_ATM_LANAI
+	devs += lanai_detect();
+#endif
+	return devs;
+}
diff --git a/drivers/atm/atmsar11.data b/drivers/atm/atmsar11.data
new file mode 100644
index 0000000..5dc8a76
--- /dev/null
+++ b/drivers/atm/atmsar11.data
@@ -0,0 +1,2063 @@
+/*
+  Madge Ambassador ATM Adapter microcode.
+  Copyright (C) 1995-1999  Madge Networks Ltd.
+  
+  This microcode data is placed under the terms of the GNU General
+  Public License. The GPL is contained in /usr/doc/copyright/GPL on a
+  Debian system and in the file COPYING in the Linux kernel source.
+  
+  We would prefer you not to distribute modified versions without
+  consultation and not to ask for assembly/other microcode source.
+*/
+
+  0x401a6800,
+  0x00000000,
+  0x335b007c,
+  0x13600005,
+  0x335b1000,
+  0x3c1aa0c0,
+  0x375a0180,
+  0x03400008,
+  0x00000000,
+  0x1760fffb,
+  0x335b4000,
+  0x401a7000,
+  0x13600003,
+  0x241b0fc0,
+  0xaf9b4500,
+  0x25080008,
+  0x03400008,
+  0x42000010,
+  0x8f810c90,
+  0x32220002,
+  0x10400003,
+  0x3c03a0d1,
+  0x2463f810,
+  0x0060f809,
+  0x24210001,
+  0x1000001a,
+  0xaf810c90,
+  0x82020011,
+  0xaf900c48,
+  0x0441000a,
+  0x34420080,
+  0x967d0002,
+  0x96020012,
+  0x00000000,
+  0x105d0011,
+  0x00000000,
+  0x04110161,
+  0xa6620002,
+  0x1000000d,
+  0xae62000c,
+  0x34848000,
+  0xa2020011,
+  0x4d01ffff,
+  0x00000000,
+  0x8f834c00,
+  0x00000000,
+  0xaf830fec,
+  0x00e0f809,
+  0x03e03821,
+  0x00041400,
+  0x0440fff7,
+  0x00000000,
+  0xaf80460c,
+  0x8e100008,
+  0x4d01ffff,
+  0x00000000,
+  0x8f834c00,
+  0x4900001d,
+  0xaf830fec,
+  0x8f820cbc,
+  0x8f9d0c4c,
+  0x24420001,
+  0x97be0000,
+  0xaf820cbc,
+  0x13c00009,
+  0xaca200d8,
+  0xa7a00000,
+  0x3c0100d1,
+  0x003e0825,
+  0x9422002c,
+  0x0411013f,
+  0xa4220002,
+  0xac22000c,
+  0xac200010,
+  0x8f9e0c54,
+  0x27bd0002,
+  0x17be0002,
+  0x8ca200c0,
+  0x8f9d0c50,
+  0x8f970fc8,
+  0xaf9d0c4c,
+  0x12e20005,
+  0x87804002,
+  0x3c02a0d1,
+  0x2442f94c,
+  0x0040f809,
+  0x00000000,
+  0x00e0f809,
+  0x03e03821,
+  0x4500ffdc,
+  0x8e11000c,
+  0x3c1300d1,
+  0x00111102,
+  0x2c430400,
+  0x1060ffb9,
+  0x00021180,
+  0x02629821,
+  0x8e76003c,
+  0x32220008,
+  0x1440ffb7,
+  0x8e770034,
+  0x8e750030,
+  0x3c03cfb0,
+  0x16c00003,
+  0x02d5102b,
+  0x041100be,
+  0x00000000,
+  0x1040ffa6,
+  0x00701826,
+  0x4d01ffff,
+  0x00000000,
+  0x8f824c00,
+  0xaf974c00,
+  0xaf820fec,
+  0xac760010,
+  0x02609021,
+  0x32220002,
+  0x10400007,
+  0x8f944a00,
+  0x9602003a,
+  0x34840004,
+  0x14400003,
+  0xaf820fbc,
+  0x3c029000,
+  0xaf820fbc,
+  0x8e100008,
+  0x32943f00,
+  0x8e11000c,
+  0x2694ff00,
+  0x12800073,
+  0x3c1300d1,
+  0x49010071,
+  0x32370008,
+  0x16e0006f,
+  0x00111102,
+  0x2c430400,
+  0x1060006c,
+  0x0002b980,
+  0x00041740,
+  0x0440003a,
+  0x02779821,
+  0x12720023,
+  0x26d60030,
+  0xae56003c,
+  0x8e76003c,
+  0x8e770034,
+  0x8e750030,
+  0x3c03cfb0,
+  0x16c00003,
+  0x02d5102b,
+  0x04110091,
+  0x00000000,
+  0x10400060,
+  0x2e821000,
+  0x14400009,
+  0x00701826,
+  0x4d01ffff,
+  0x00000000,
+  0x8f824c00,
+  0xaf974c00,
+  0xac760010,
+  0xae420034,
+  0x1000ffd0,
+  0xaf80460c,
+  0x00e0f809,
+  0x03e03821,
+  0x3c03cfb0,
+  0x00701826,
+  0xae460034,
+  0x4d01ffff,
+  0x00000000,
+  0x8f824c00,
+  0xaf974c00,
+  0xaf820fec,
+  0xac760010,
+  0x1000ffc3,
+  0xaf80460c,
+  0x02d5102b,
+  0x10400042,
+  0x3c17cfb0,
+  0x2e821000,
+  0x14400006,
+  0x02f0b826,
+  0x4d01ffff,
+  0x00000000,
+  0xaef60010,
+  0x1000ffb8,
+  0xaf80460c,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0x8f824c00,
+  0xaf864c00,
+  0xaef60010,
+  0xaf820fec,
+  0x1000ffae,
+  0xaf80460c,
+  0x3084fffb,
+  0x8e570038,
+  0x3242ffc0,
+  0x00021182,
+  0xa7820fb8,
+  0xaf970fb4,
+  0x865d002a,
+  0x865e0008,
+  0xa79d0fba,
+  0x279d0f18,
+  0x33de0060,
+  0x03bee821,
+  0x001ef0c2,
+  0x03bee821,
+  0x8f970c58,
+  0x4d01ffff,
+  0x00000000,
+  0x8f834c00,
+  0x8fa2001c,
+  0x12e30003,
+  0x3c030c40,
+  0x3c1ec000,
+  0xaf9e0fbc,
+  0xac620fb4,
+  0x8fa30018,
+  0x2442000c,
+  0x14430002,
+  0xaf80460c,
+  0x8fa20014,
+  0xae40003c,
+  0xafa2001c,
+  0x8e76003c,
+  0x8e770034,
+  0x8e750030,
+  0x3c03cfb0,
+  0x16c00003,
+  0x02d5102b,
+  0x0411003c,
+  0x00000000,
+  0x00701826,
+  0x4d01ffff,
+  0x00000000,
+  0xaca500e4,
+  0x10400032,
+  0xaf974c00,
+  0x1000ff7f,
+  0xac760010,
+  0x00041740,
+  0x04400007,
+  0x26d60030,
+  0xae56003c,
+  0x00e0f809,
+  0x03e03821,
+  0xaf80460c,
+  0x1000ff39,
+  0xae460034,
+  0x8e570038,
+  0x3242ffc0,
+  0x00021182,
+  0xa7820fb8,
+  0xaf970fb4,
+  0x8f970c58,
+  0x00e0f809,
+  0x03e03821,
+  0x12e60003,
+  0x3c030c40,
+  0x3c02c000,
+  0xaf820fbc,
+  0x865d002a,
+  0x865e0008,
+  0xa79d0fba,
+  0x279d0f18,
+  0x33de0060,
+  0x03bee821,
+  0x001ef0c2,
+  0x03bee821,
+  0x8fa2001c,
+  0x4d01ffff,
+  0x00000000,
+  0x8f974c00,
+  0xac620fb4,
+  0x3084fffb,
+  0x8fa30018,
+  0x2442000c,
+  0x14430002,
+  0xaf80460c,
+  0x8fa20014,
+  0xae40003c,
+  0xafa2001c,
+  0x4d01ffff,
+  0x00000000,
+  0xaca500e4,
+  0x1000ff13,
+  0xaf974c00,
+  0x00e0f809,
+  0x03e03821,
+  0x1000ff0f,
+  0x00000000,
+  0x1040005b,
+  0x867e0008,
+  0x279d0f18,
+  0x33de0060,
+  0x03bee821,
+  0x001e10c2,
+  0x03a2e821,
+  0x8fb70008,
+  0x8fa2000c,
+  0x8ef60004,
+  0x12e20028,
+  0x86620008,
+  0x82030010,
+  0x00021740,
+  0x04410019,
+  0x24630001,
+  0x10600017,
+  0x3c02d1b0,
+  0x00501026,
+  0x4d01ffff,
+  0x00000000,
+  0x8f9e4c00,
+  0xac560010,
+  0x26d6fffe,
+  0x86020010,
+  0x3c03cfb0,
+  0x34632000,
+  0xa662002a,
+  0x8ee20000,
+  0x26f70008,
+  0xae620038,
+  0x8fa20020,
+  0xafb70008,
+  0x2417ffff,
+  0x02c2a821,
+  0x4d01ffff,
+  0x00000000,
+  0xaf9e4c00,
+  0x03e00008,
+  0xae750030,
+  0x8ee20000,
+  0x26f70008,
+  0xae620038,
+  0x8fa20020,
+  0xafb70008,
+  0x2417ffff,
+  0xa677002a,
+  0x02c2a821,
+  0x3c03cfb0,
+  0x03e00008,
+  0xae750030,
+  0x001e18c2,
+  0x00651821,
+  0x8c6300c8,
+  0x8fa20010,
+  0x00000000,
+  0x0062b023,
+  0x1ec00003,
+  0x8fa10004,
+  0x12c0001b,
+  0x0022b023,
+  0x2ec30041,
+  0x14600002,
+  0x3c150040,
+  0x24160040,
+  0x00161e80,
+  0x00031882,
+  0x00751825,
+  0x4d01ffff,
+  0x00000000,
+  0x8f954c00,
+  0x001eb840,
+  0x00771821,
+  0xac624d00,
+  0x00561021,
+  0x14410002,
+  0x27830d00,
+  0x8fa20000,
+  0x02e3b821,
+  0xafa20010,
+  0x02d71821,
+  0xafa3000c,
+  0x4d01ffff,
+  0x00000000,
+  0x8ef60004,
+  0x1000ffb5,
+  0xaf954c00,
+  0x3c16dead,
+  0xae76003c,
+  0xae600038,
+  0x26d5ffff,
+  0x00001021,
+  0x03e00008,
+  0xae750030,
+  0x2c430ab2,
+  0x10600005,
+  0x2c4324b2,
+  0x10000004,
+  0x24020ab2,
+  0x10000002,
+  0x240224b1,
+  0x1060fffd,
+  0x304301ff,
+  0x00031840,
+  0x3c1da0d1,
+  0x27bdd6cc,
+  0x007d1821,
+  0x94630000,
+  0x0002ea42,
+  0x00031c00,
+  0x27bdfffb,
+  0x03e00008,
+  0x03a31006,
+  0x24030fc0,
+  0xaf834500,
+  0x10000002,
+  0x01206021,
+  0x3c0ccfb0,
+  0x11e00056,
+  0x01896026,
+  0x85fe0000,
+  0x00000000,
+  0x13c00047,
+  0x3c02cfb0,
+  0x07c0002d,
+  0x001e1f80,
+  0x04610034,
+  0x001e1fc0,
+  0x04600009,
+  0x3c02d3b0,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0x8f864c00,
+  0x8f990fec,
+  0x1000000b,
+  0xaf994c00,
+  0x01e27826,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0x8f864c00,
+  0xaf994c00,
+  0xadef2010,
+  0x3c02d3b0,
+  0x01e27826,
+  0x8f820fc0,
+  0x8f830fc4,
+  0xaf824d00,
+  0x8de20004,
+  0xa5e00000,
+  0xac620000,
+  0x8c620000,
+  0x24020380,
+  0xaf824d00,
+  0x8f824d00,
+  0x8f820f14,
+  0x24630004,
+  0x14620002,
+  0x2419ffff,
+  0x8f830f10,
+  0xaca500e4,
+  0xaf830fc4,
+  0x4d01ffff,
+  0x00000000,
+  0x8f824c80,
+  0x1000001f,
+  0xade2003c,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0xa5e00000,
+  0x8f864c00,
+  0x15800022,
+  0xaf8f4540,
+  0x10000017,
+  0x01e27826,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0x8f864c00,
+  0xaf994c00,
+  0xadef2010,
+  0x3c02cfb0,
+  0x01e27826,
+  0xa5e00000,
+  0x4d01ffff,
+  0x00000000,
+  0x10000007,
+  0x8f994c00,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0x8f864c00,
+  0x8f990fec,
+  0x1580000a,
+  0xaf8f4500,
+  0x00007821,
+  0x10000014,
+  0xaf190014,
+  0x00e0f809,
+  0x03e03821,
+  0x4d01ffff,
+  0x00000000,
+  0x1180fff8,
+  0x8f864c00,
+  0x85220000,
+  0x01207821,
+  0x0440000a,
+  0x8d290008,
+  0x130b0004,
+  0x000c1602,
+  0xaf190014,
+  0x8d790014,
+  0x0160c021,
+  0xaf994c00,
+  0xad8e4010,
+  0x3042003f,
+  0x01c27021,
+  0x00041780,
+  0x0440018b,
+  0x8f824a00,
+  0x30818000,
+  0x30420004,
+  0x1440ff8d,
+  0x8d4b0000,
+  0x1020000c,
+  0x30847fff,
+  0x8f820c48,
+  0x0120f021,
+  0x24430034,
+  0x8c5d000c,
+  0x24420004,
+  0xafdd000c,
+  0x1462fffc,
+  0x27de0004,
+  0xa5210000,
+  0x1000ff82,
+  0x25080008,
+  0x11600058,
+  0x00000000,
+  0x857d0008,
+  0x8d63000c,
+  0x9562000a,
+  0x8d410004,
+  0x07a10026,
+  0x00621821,
+  0xa563000a,
+  0x00031c02,
+  0x041101a0,
+  0x000318c0,
+  0x001d16c0,
+  0x0441001f,
+  0x27a20080,
+  0x00021cc0,
+  0x0461000e,
+  0x0040e821,
+  0x27bd0080,
+  0x95620000,
+  0x95630002,
+  0x3442000c,
+  0xad22000c,
+  0x24020100,
+  0xa5220010,
+  0x9562002c,
+  0xa5230014,
+  0xa5220012,
+  0xa5200016,
+  0x34028000,
+  0xa5220000,
+  0xa57d0008,
+  0x07a0000c,
+  0x8f820c4c,
+  0x8f830c50,
+  0x2441ffe8,
+  0x0023f02b,
+  0x13c00002,
+  0x00201021,
+  0x24420400,
+  0x945e0000,
+  0x2441fffe,
+  0x17c0fff9,
+  0xad620010,
+  0xa44b0000,
+  0x142b001c,
+  0xad400000,
+  0xad400004,
+  0x254a0008,
+  0x3142007f,
+  0x1440000e,
+  0x00041780,
+  0x04410003,
+  0x8f820fe0,
+  0x10000006,
+  0x34840001,
+  0x34840002,
+  0x24420008,
+  0x34421000,
+  0x38421000,
+  0xaf820fe0,
+  0x354a0100,
+  0x394a0100,
+  0x39420080,
+  0xaf820fe4,
+  0x001d14c0,
+  0x04410003,
+  0x33a2efff,
+  0x1000ff3c,
+  0xa5620008,
+  0x07a0009f,
+  0x33a2fffe,
+  0x10000021,
+  0xa5620008,
+  0x8d620024,
+  0x001d1cc0,
+  0x04610004,
+  0xad420000,
+  0x33a3efff,
+  0x1000ff31,
+  0xa5630008,
+  0x07a00005,
+  0x33a3fffe,
+  0xa5630008,
+  0x8d4b0000,
+  0x1000ffaa,
+  0x00000000,
+  0x1000008e,
+  0x25080008,
+  0x254a0008,
+  0x3142007f,
+  0x1440000e,
+  0x00041780,
+  0x04410003,
+  0x8f820fe0,
+  0x10000006,
+  0x34840001,
+  0x34840002,
+  0x24420008,
+  0x34421000,
+  0x38421000,
+  0xaf820fe0,
+  0x354a0100,
+  0x394a0100,
+  0x39420080,
+  0xaf820fe4,
+  0x11000003,
+  0x8d4b0000,
+  0x1000ff93,
+  0x2508fff8,
+  0x8f820fd8,
+  0x8f830fdc,
+  0x8f810fd4,
+  0x1062001d,
+  0x24620008,
+  0x4d01ffff,
+  0x00000000,
+  0x8f8c4c00,
+  0x847f0000,
+  0x3c1e00d1,
+  0x33fd03ff,
+  0x001d5980,
+  0x017e5821,
+  0x857e0008,
+  0x001de900,
+  0x001e0f00,
+  0x03e1f825,
+  0x07e00003,
+  0xaf820fdc,
+  0x879e0ca0,
+  0x278b0c98,
+  0x07c10042,
+  0x3c020840,
+  0x3c01f7b0,
+  0x8d620020,
+  0x00230826,
+  0xac220000,
+  0x8c620004,
+  0x94630002,
+  0x2442fff8,
+  0x00431021,
+  0x1000004e,
+  0xad620020,
+  0x8f820fd0,
+  0x87830ca0,
+  0x14220007,
+  0x278b0c98,
+  0x41000051,
+  0x3c018000,
+  0xaca100e0,
+  0x8ca100c4,
+  0x00000000,
+  0x1022004c,
+  0x0022e823,
+  0x8f9f0f0c,
+  0x07a10002,
+  0xaf810fd4,
+  0x03e2e823,
+  0x2fa30041,
+  0x14600002,
+  0x3c1e0040,
+  0x241d0040,
+  0x001d1e80,
+  0x00031882,
+  0x007e1825,
+  0x4d01ffff,
+  0x00000000,
+  0x8f8c4c00,
+  0xac624cc0,
+  0x005d1021,
+  0x145f0002,
+  0x27830cc0,
+  0x8f820f08,
+  0x03a3f021,
+  0xaf820fd0,
+  0xaf9e0fd8,
+  0x4d01ffff,
+  0x00000000,
+  0x1000ffc3,
+  0x24620008,
+  0x8d63000c,
+  0x8d7d0010,
+  0xa563000a,
+  0x13a00002,
+  0x00031c02,
+  0xa7a00000,
+  0x000318c0,
+  0x041100ef,
+  0x00681821,
+  0x4d01ffff,
+  0x00000000,
+  0x8f820c44,
+  0x8f830c40,
+  0xad620010,
+  0xa5630004,
+  0xa5630006,
+  0x10000021,
+  0xaf8c4c00,
+  0xa57d0000,
+  0x8c7d0004,
+  0x94630002,
+  0xac5d4c40,
+  0x27a20008,
+  0xad620018,
+  0x03a3e821,
+  0x27bdfff4,
+  0xad7d001c,
+  0x27bd0004,
+  0xad7d0020,
+  0x37c18001,
+  0x001e17c0,
+  0x0441ffe0,
+  0xa5610008,
+  0x4d01ffff,
+  0x00000000,
+  0x8f820c44,
+  0x8f830c40,
+  0xad620010,
+  0xa5630004,
+  0xa5630006,
+  0x8f820fd8,
+  0x8f830fdc,
+  0x4d01ffff,
+  0x00000000,
+  0x1462ff95,
+  0x24620008,
+  0xaf8c4c00,
+  0x87830ca0,
+  0x278b0c98,
+  0x0461fe97,
+  0x00041700,
+  0x04400005,
+  0x95620000,
+  0x11780006,
+  0x00000000,
+  0xaf0e0010,
+  0xa70d0004,
+  0x3084fff7,
+  0x956d0004,
+  0x8d6e0010,
+  0x25adffd0,
+  0x05a1fe8f,
+  0xad22000c,
+  0x3c0cffb0,
+  0x01896026,
+  0x000d1822,
+  0x25ad0030,
+  0x8d7e0018,
+  0x8d61001c,
+  0x4d01ffff,
+  0x00000000,
+  0x103e0036,
+  0x8f9d4c00,
+  0x3c010840,
+  0xac3e4c40,
+  0x27de0008,
+  0x11a00017,
+  0xad7e0018,
+  0x000df600,
+  0x019e6025,
+  0x4d01ffff,
+  0x00000000,
+  0xad8e4010,
+  0x8f8d0c40,
+  0x957e0006,
+  0x8f8e0c44,
+  0x03cdf021,
+  0xa57e0006,
+  0x000cf782,
+  0x000c0e02,
+  0x03c1f021,
+  0x001e0f80,
+  0x000c6200,
+  0x000c6202,
+  0x01816025,
+  0x33de003c,
+  0x019e6021,
+  0x34010001,
+  0x10000008,
+  0xa5210000,
+  0x957e0006,
+  0x4d01ffff,
+  0x00000000,
+  0x8f8d0c40,
+  0x8f8e0c44,
+  0x03cdf021,
+  0xa57e0006,
+  0x4d01ffff,
+  0x00000000,
+  0x01a3f02b,
+  0x17c00008,
+  0x0003f600,
+  0x01a36823,
+  0x019e6025,
+  0x01896026,
+  0x4d01fff7,
+  0x00000000,
+  0x1000fe58,
+  0xaf9d4c00,
+  0x8d7e0018,
+  0x8d61001c,
+  0x00000000,
+  0x143effce,
+  0x006d1823,
+  0x4d01ffff,
+  0x00000000,
+  0x2c610008,
+  0x10200017,
+  0x95610008,
+  0x00000000,
+  0x0001ff80,
+  0x07e0000b,
+  0x34210002,
+  0x006d1821,
+  0x00031e00,
+  0x01836025,
+  0x01896026,
+  0x240d002c,
+  0xa5610008,
+  0x4d01ffff,
+  0x00000000,
+  0x1000fe40,
+  0xaf9d4c00,
+  0x3c1f0c40,
+  0xaffe4fa8,
+  0x3021fffd,
+  0xa5610008,
+  0x3c0cd3cf,
+  0x358ce000,
+  0x10000008,
+  0x34030002,
+  0x3c1f0c40,
+  0xaffe4fa8,
+  0x11a0fff9,
+  0x000df600,
+  0x34030003,
+  0x019e6025,
+  0x01896026,
+  0x34840008,
+  0x34420002,
+  0xad22000c,
+  0x95620006,
+  0xa5230000,
+  0xad220038,
+  0x4d01ffff,
+  0x00000000,
+  0x857e0008,
+  0x8f820fa8,
+  0x97830fac,
+  0xad220004,
+  0x33c17fff,
+  0xad600010,
+  0xa5610008,
+  0x1060fe20,
+  0xaf9d4c00,
+  0xa57e0008,
+  0x00031900,
+  0x30633ff0,
+  0xa5630000,
+  0x8f820fb0,
+  0x3c030840,
+  0xac624c40,
+  0x24430008,
+  0xad630018,
+  0x97830fae,
+  0x2442fff4,
+  0x00621821,
+  0xad63001c,
+  0x4d01ffff,
+  0x00000000,
+  0x8f8d0c40,
+  0x8f830c44,
+  0xa56d0004,
+  0xa56d0006,
+  0xad630010,
+  0x1000fe0a,
+  0xaf9d4c00,
+  0x8f820fe0,
+  0x00040fc0,
+  0x8c430000,
+  0x0421001b,
+  0x8f9f0fe4,
+  0x8c5d0004,
+  0xac400004,
+  0x1060000e,
+  0xac400000,
+  0x00000000,
+  0x94620028,
+  0x00000000,
+  0x005f1020,
+  0x8c410004,
+  0x00000000,
+  0x10200003,
+  0xac430004,
+  0x10000002,
+  0xac230024,
+  0xac430000,
+  0x17a3fff4,
+  0x8c630024,
+  0x8f820fe0,
+  0x3bff0080,
+  0x24420008,
+  0x34421000,
+  0x38421000,
+  0xaf820fe0,
+  0xaf9f0fe4,
+  0x1000fe57,
+  0x3084fffe,
+  0x10600010,
+  0x00000000,
+  0x947d0028,
+  0x00000000,
+  0x03bfe820,
+  0x8fa10004,
+  0xafa30004,
+  0x10200003,
+  0x8c5e0004,
+  0x10000002,
+  0xac230024,
+  0xafa30000,
+  0x8c610024,
+  0x17c3fe48,
+  0xac410000,
+  0xac400004,
+  0xac400000,
+  0x1000fe44,
+  0x3084fffd,
+  0x2c620100,
+  0x1440000e,
+  0x006a1021,
+  0x3143007f,
+  0x01431823,
+  0x00431823,
+  0x3062007f,
+  0xa5620028,
+  0x00621823,
+  0x00031902,
+  0x8f820fe0,
+  0x2463fff8,
+  0x00621821,
+  0x34631000,
+  0x10000003,
+  0x38631000,
+  0x34430100,
+  0x38630100,
+  0x8c620004,
+  0x00000000,
+  0x10400003,
+  0xac6b0004,
+  0x03e00008,
+  0xac4b0024,
+  0x03e00008,
+  0xac6b0000,
+  0x00000002,
+  0xa0d0e000,
+  0x00000000,
+  0x00001000,
+  0x00000006,
+  0x00000008,
+  0x00000000,
+  0x00000008,
+  0x00000002,
+  0xa0d0d648,
+  0x00000000,
+  0x00000888,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x24313200,
+  0x24313200,
+  0x24313200,
+  0x00000000,
+  0x244d4352,
+  0x2420436f,
+  0x70797269,
+  0x67687420,
+  0x28632920,
+  0x4d616467,
+  0x65204e65,
+  0x74776f72,
+  0x6b73204c,
+  0x74642031,
+  0x3939352e,
+  0x20416c6c,
+  0x20726967,
+  0x68747320,
+  0x72657365,
+  0x72766564,
+  0x2e004d61,
+  0x64676520,
+  0x416d6261,
+  0x73736164,
+  0x6f722076,
+  0x312e3031,
+  0x00000000,
+  0x00000001,
+  0x00000001,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0xfff04000,
+  0x00000000,
+  0x0c343e2d,
+  0x00000000,
+  0x3c1ca0d1,
+  0x279c5638,
+  0x3c1da0d1,
+  0x27bddfd0,
+  0x3c08a0d1,
+  0x2508dfd0,
+  0xaf878008,
+  0x0c343c13,
+  0x00000000,
+  0x24040003,
+  0x0097000d,
+  0x3c08bfc0,
+  0x35080230,
+  0x8d080000,
+  0x00000000,
+  0x01000008,
+  0x00000000,
+  0x27bdffd0,
+  0xafbf001c,
+  0xafb10018,
+  0xafb00014,
+  0x3c11fff0,
+  0x00008021,
+  0x3c180056,
+  0x37183b79,
+  0x26190200,
+  0x17200002,
+  0x0319001a,
+  0x0007000d,
+  0x2401ffff,
+  0x17210005,
+  0x00000000,
+  0x3c018000,
+  0x17010002,
+  0x00000000,
+  0x0006000d,
+  0x00001012,
+  0x00101840,
+  0x3c05a0d1,
+  0x24a5d6cc,
+  0x00a32021,
+  0xa4820000,
+  0x26100001,
+  0x2a010200,
+  0x1420ffea,
+  0x00000000,
+  0x3c06a0d1,
+  0x24c6f9e4,
+  0x3c07a0d1,
+  0x24e7d648,
+  0xace60000,
+  0x3c08a0d1,
+  0x2508fb14,
+  0xace80004,
+  0x3c09a0d1,
+  0x2529fc94,
+  0xace90008,
+  0x3c0aa0d1,
+  0x254afcd4,
+  0xacea000c,
+  0x3c0ba0d1,
+  0x256bfba8,
+  0xaceb0010,
+  0x3c0ca0d1,
+  0x258cfbc4,
+  0xacec0014,
+  0x3c0da0d1,
+  0x25adfbe0,
+  0xaced0018,
+  0x3c0ea0d1,
+  0x25cefbfc,
+  0xacee001c,
+  0x3c0fa0d1,
+  0x25effc18,
+  0xacef0020,
+  0x3c18a0d1,
+  0x2718fc34,
+  0xacf80024,
+  0x3c19a0d1,
+  0x2739fc50,
+  0xacf90028,
+  0x3c02a0d1,
+  0x2442fc60,
+  0xace2002c,
+  0x3c03a0d1,
+  0x2463fc70,
+  0xace30030,
+  0x3c04a0d1,
+  0x2484fc80,
+  0xace40034,
+  0x3c05a0d1,
+  0x24a5fcb4,
+  0xace50038,
+  0x3c06a0d1,
+  0x24c6fe08,
+  0xace6003c,
+  0x3c08a0d1,
+  0x2508fe90,
+  0xace80040,
+  0x3c09a0d1,
+  0x2529fa38,
+  0xace90044,
+  0x3c0aa0d1,
+  0x254afa74,
+  0xacea0048,
+  0x24100013,
+  0x3c0ba0d1,
+  0x256bf9d8,
+  0x00106080,
+  0x3c0ea0d1,
+  0x25ced648,
+  0x01cc6821,
+  0xadab0000,
+  0x26100001,
+  0x2a010020,
+  0x1420fff6,
+  0x00000000,
+  0x8f988000,
+  0x00000000,
+  0xaf000100,
+  0x8f828000,
+  0x241903ff,
+  0xa4590202,
+  0x00008021,
+  0x8f868000,
+  0x24030fff,
+  0x00102040,
+  0x24c70380,
+  0x00e42821,
+  0xa4a30000,
+  0x26100001,
+  0x2a010008,
+  0x1420fff7,
+  0x00000000,
+  0x8f898000,
+  0x34089c40,
+  0xad2803a0,
+  0x8f8b8000,
+  0x3c0a00ff,
+  0x354affff,
+  0xad6a03a4,
+  0x00008021,
+  0x8f8f8000,
+  0x240c0fff,
+  0x00106840,
+  0x25f80300,
+  0x030d7021,
+  0xa5cc0000,
+  0x26100001,
+  0x2a010008,
+  0x1420fff7,
+  0x00000000,
+  0x8f828000,
+  0x34199c40,
+  0xac590320,
+  0x8f848000,
+  0x3c0300ff,
+  0x3463ffff,
+  0xac830324,
+  0x8f868000,
+  0x240502ff,
+  0xa4c50202,
+  0x3c08a0c0,
+  0x35080180,
+  0x3c09a0d1,
+  0x2529d5b8,
+  0x250a0028,
+  0x8d0b0000,
+  0x8d0c0004,
+  0xad2b0000,
+  0xad2c0004,
+  0x25080008,
+  0x150afffa,
+  0x25290008,
+  0x40026000,
+  0x00000000,
+  0xafa20028,
+  0x24030022,
+  0x3c04a0e0,
+  0x34840014,
+  0xac830000,
+  0x8fa50028,
+  0x00000000,
+  0x34a61001,
+  0x00c01021,
+  0xafa60028,
+  0x3c07ffbf,
+  0x34e7ffff,
+  0x00c73824,
+  0x00e01021,
+  0xafa70028,
+  0x40876000,
+  0x00000000,
+  0x3c080002,
+  0x3508d890,
+  0x3c09fffe,
+  0x35290130,
+  0xad280000,
+  0x8faa0028,
+  0x3c0bf000,
+  0x014b5825,
+  0x01601021,
+  0xafab0028,
+  0x01606021,
+  0x408c6000,
+  0x00000000,
+  0x00008021,
+  0x00107080,
+  0x022e7821,
+  0xade00000,
+  0x26100001,
+  0x2a010400,
+  0x1420fffa,
+  0x00000000,
+  0x24180001,
+  0x3c19a0e8,
+  0xaf380000,
+  0x24020011,
+  0x3c03a0f0,
+  0x34630017,
+  0xa0620000,
+  0x3c04f0eb,
+  0x34840070,
+  0x3c05fff0,
+  0x34a54a00,
+  0xaca40000,
+  0x3c06fceb,
+  0x34c60070,
+  0xaca60000,
+  0x3c07fff0,
+  0x34e74700,
+  0xace00000,
+  0x00008021,
+  0x3c08fff0,
+  0x35080fc0,
+  0x3c09fff0,
+  0x35294500,
+  0xad280000,
+  0x26100001,
+  0x2a010004,
+  0x1420fff8,
+  0x00000000,
+  0x00008021,
+  0x3c0adead,
+  0x00105980,
+  0x3c0100d1,
+  0x002b0821,
+  0xac2a003c,
+  0x3c0100d1,
+  0x002b0821,
+  0xac200030,
+  0x3c0100d1,
+  0x002b0821,
+  0xac200038,
+  0x240dffff,
+  0x3c0100d1,
+  0x002b0821,
+  0xac2d0014,
+  0x00107100,
+  0x3c0100d1,
+  0x002b0821,
+  0xa42e0000,
+  0x3c0100d1,
+  0x002b0821,
+  0xa4200004,
+  0x24180020,
+  0x3c0100d1,
+  0x002b0821,
+  0xa4380008,
+  0x3c0100d1,
+  0x002b0821,
+  0xac200010,
+  0x26100001,
+  0x2a010400,
+  0x1420ffe0,
+  0x00000000,
+  0x00008021,
+  0x001018c0,
+  0x3c05a0d1,
+  0x24a5e000,
+  0x00a32021,
+  0xac800000,
+  0x3c07a0d1,
+  0x24e7e000,
+  0x24e80004,
+  0x01033021,
+  0xacc00000,
+  0x26100001,
+  0x2a010009,
+  0x1420fff3,
+  0x00000000,
+  0x24090380,
+  0x3c0afff0,
+  0x354a4d00,
+  0xad490000,
+  0x3c0ca080,
+  0x358c009c,
+  0xad800000,
+  0x3c0da080,
+  0x35ad00a0,
+  0xada00000,
+  0x3c0e1100,
+  0x3c0fa080,
+  0x35ef00a8,
+  0xadee0000,
+  0x41010003,
+  0x00000000,
+  0x4100ffff,
+  0x00000000,
+  0x3c18a080,
+  0x371800e0,
+  0x8f190000,
+  0x3c01a0d1,
+  0xac39d6c8,
+  0x0c343d43,
+  0x03202021,
+  0x8fb00014,
+  0x8fbf001c,
+  0x8fb10018,
+  0x03e00008,
+  0x27bd0030,
+  0x0080b821,
+  0x3c1cfff0,
+  0xa3800c84,
+  0xa3800c88,
+  0x8f904400,
+  0x00002021,
+  0xaf800cbc,
+  0x240200a8,
+  0x27830f00,
+  0x2c5d0040,
+  0x17a0000c,
+  0x3c1dffb0,
+  0x03a3e826,
+  0xafb74000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x4d01ffff,
+  0x00000000,
+  0x2442ffc0,
+  0x24630040,
+  0x1000fff3,
+  0x26f70040,
+  0x1040000d,
+  0x00000000,
+  0x0002ee00,
+  0x3c010040,
+  0x03a1e825,
+  0x3c01fff0,
+  0x03a1e826,
+  0x03a3e826,
+  0xafb74000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x4d01ffff,
+  0x00000000,
+  0x3c05a080,
+  0x8f820f08,
+  0x00000000,
+  0xaf820fd4,
+  0xaf820fd0,
+  0xaca200c4,
+  0x8f820f10,
+  0x00000000,
+  0x00021d82,
+  0xaf830fc0,
+  0x00031d80,
+  0x00431023,
+  0x3c01a080,
+  0x00411025,
+  0xaf820fc4,
+  0xaf820f10,
+  0x8f820f14,
+  0x00000000,
+  0x00431023,
+  0x3c01a080,
+  0x00411025,
+  0xaf820f14,
+  0x24030003,
+  0x279d0f18,
+  0x24be00c8,
+  0x27810d00,
+  0x8fa20000,
+  0x00000000,
+  0xafa20010,
+  0xafc20000,
+  0xafa10008,
+  0xafa1000c,
+  0x8fa20014,
+  0x00000000,
+  0xafa2001c,
+  0x27bd0024,
+  0x27de0004,
+  0x24210040,
+  0x1460fff3,
+  0x2463ffff,
+  0x8f820f00,
+  0x00000000,
+  0xaf820fc8,
+  0xaca200c0,
+  0x27820800,
+  0x2403000f,
+  0xac400000,
+  0x24420004,
+  0x1460fffd,
+  0x2463ffff,
+  0x8f830fc0,
+  0x00000000,
+  0xaf834d00,
+  0x8f834d00,
+  0x8f830f14,
+  0x8f820f10,
+  0x2463fffc,
+  0xac400000,
+  0x1443fffe,
+  0x24420004,
+  0x24020380,
+  0xaf824d00,
+  0x279d0f18,
+  0x27a10090,
+  0x8fa20014,
+  0x8fa30018,
+  0x00000000,
+  0x00621823,
+  0x2c7f0040,
+  0x17e00009,
+  0x3c1f0040,
+  0x37ff0800,
+  0x03a0f021,
+  0x4d01ffff,
+  0x00000000,
+  0xafe20000,
+  0x24420040,
+  0x1000fff6,
+  0x2463ffc0,
+  0x10600006,
+  0x37ff0800,
+  0x00031e00,
+  0x03e3f825,
+  0x4d01ffff,
+  0x00000000,
+  0xafe20000,
+  0x27bd0024,
+  0x17a1ffe8,
+  0x00000000,
+  0x00003821,
+  0x8fc20014,
+  0x8fc30018,
+  0x00000000,
+  0x00621823,
+  0x2c7f0040,
+  0x13e00004,
+  0x3c1f0040,
+  0x00030e00,
+  0x10000002,
+  0x03e1f825,
+  0x24030040,
+  0x37ff0800,
+  0x241e03e7,
+  0x00000821,
+  0x4d01ffff,
+  0x00000000,
+  0xafe20000,
+  0x00230821,
+  0x4900fffb,
+  0x00000000,
+  0x87804002,
+  0x17c0fff8,
+  0x27deffff,
+  0x14e00004,
+  0x34e74000,
+  0x03e7f825,
+  0x1000fff0,
+  0xaf810c60,
+  0xaf810c5c,
+  0x3c01a0d1,
+  0x8c22d6c8,
+  0x00000000,
+  0x3c01a080,
+  0xac2200e0,
+  0x3c01a080,
+  0x8c2000e0,
+  0xaf800fb4,
+  0xa7800fb8,
+  0xa7800fba,
+  0xa7800fbc,
+  0xa7800fbe,
+  0x27820cc0,
+  0xaf820fdc,
+  0xaf820fd8,
+  0x3c02a0d1,
+  0x2442dacc,
+  0xaf820c4c,
+  0xaf820c50,
+  0x24420400,
+  0xaf820c54,
+  0x2402001e,
+  0x3c03fff0,
+  0x247d0040,
+  0xac7d0008,
+  0x03a01821,
+  0x1440fffc,
+  0x2442ffff,
+  0x3c1dfff0,
+  0xac7d0008,
+  0x3c02c704,
+  0x3442dd7b,
+  0xaf820c58,
+  0x3c070000,
+  0x24e70158,
+  0x08343fa9,
+  0x00000000,
+  0x8e620038,
+  0x00000000,
+  0x14400005,
+  0x8f830c94,
+  0x12a00022,
+  0x24630001,
+  0x10000020,
+  0xaf830c94,
+  0xaf820fb4,
+  0x3262ffc0,
+  0x00021182,
+  0x8663002a,
+  0xa7820fb8,
+  0x3c02a000,
+  0xaf820fbc,
+  0xa7830fba,
+  0x867e0008,
+  0x279d0f18,
+  0x33de0060,
+  0x03bee821,
+  0x001ef0c2,
+  0x03bee821,
+  0x8fa2001c,
+  0x3c030c40,
+  0x4d01ffff,
+  0x00000000,
+  0x8f974c00,
+  0xac620fb4,
+  0x8fa30018,
+  0x2442000c,
+  0x14430003,
+  0x00000000,
+  0x8fa20014,
+  0x00000000,
+  0xafa2001c,
+  0x4d01ffff,
+  0x00000000,
+  0xaca500e4,
+  0xaf974c00,
+  0x03e00008,
+  0xae60003c,
+  0x3c0da0d1,
+  0x25add500,
+  0x11a00021,
+  0x00000000,
+  0x8da90000,
+  0x00000000,
+  0x1120001d,
+  0x00000000,
+  0x8daa0004,
+  0x8dab0008,
+  0x8dac000c,
+  0x00094740,
+  0x05010004,
+  0x00000000,
+  0x3c08a0d1,
+  0x2508d638,
+  0x01485021,
+  0x00094780,
+  0x05010007,
+  0x00000000,
+  0x1180000d,
+  0x00000000,
+  0xad400000,
+  0x254a0004,
+  0x1000fffb,
+  0x258cfffc,
+  0x11800007,
+  0x00000000,
+  0x8d6e0000,
+  0x256b0004,
+  0xad4e0000,
+  0x254a0004,
+  0x1000fff9,
+  0x258cfffc,
+  0x1000ffe1,
+  0x25ad0010,
+  0x03e00008,
+  0x00000000,
+  0x3c021040,
+  0xac574ff0,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x4d01ffff,
+  0x00000000,
+  0x8f820ffc,
+  0x00000000,
+  0x3042001f,
+  0x00021080,
+  0x3c17a0d1,
+  0x02e2b821,
+  0x26f7d648,
+  0x8ef70000,
+  0x00000000,
+  0x02e00008,
+  0x00000000,
+  0x2402ffff,
+  0xaf820ffc,
+  0x8f970fc8,
+  0x3c021040,
+  0xac570ff0,
+  0x8f820f04,
+  0x26f70010,
+  0x16e20004,
+  0xaf970fc8,
+  0x8f970f00,
+  0x00000000,
+  0xaf970fc8,
+  0x4d01ffff,
+  0x00000000,
+  0x03e00008,
+  0x00000000,
+  0x3c1fa0d1,
+  0x27fff02c,
+  0x1000ffed,
+  0x8f970ff0,
+  0x3c0200d1,
+  0x32f703ff,
+  0x0017b980,
+  0x02e2b825,
+  0xaee0003c,
+  0x2402ffff,
+  0xaee20030,
+  0xaee20014,
+  0x97830ff4,
+  0x97820ff8,
+  0x3c1d0000,
+  0x27bd0698,
+  0xa6e30008,
+  0xa6e20002,
+  0xaf9f0fe8,
+  0x03a0f809,
+  0xa6e2002c,
+  0x8f9f0fe8,
+  0x1000ffd9,
+  0xaee2000c,
+  0x8f970ff0,
+  0x3c0200d1,
+  0x32f703ff,
+  0x0017b980,
+  0x02e2b825,
+  0x97820ff4,
+  0x3c030000,
+  0x24630698,
+  0xa6e20002,
+  0xaf9f0fe8,
+  0x0060f809,
+  0xa6e2002c,
+  0x8f9f0fe8,
+  0x1000ffca,
+  0xaee2000c,
+  0x8f970ff0,
+  0x3c0200d1,
+  0x32f703ff,
+  0x0017b980,
+  0x02e2b825,
+  0x97820ff4,
+  0x00000000,
+  0x96e30008,
+  0xa6e20008,
+  0x00431026,
+  0x30420060,
+  0x1040ffbd,
+  0x8ee2003c,
+  0xaee0003c,
+  0x1040ffba,
+  0x3c028800,
+  0xaf820fbc,
+  0x8ee20038,
+  0xaee00038,
+  0x30630060,
+  0x279d0f18,
+  0x03a3e821,
+  0x000318c2,
+  0x03a3e821,
+  0x8fa3001c,
+  0x1040ffaf,
+  0xaf820fb4,
+  0x3c020c40,
+  0xac430fb4,
+  0x8fa20018,
+  0x2463000c,
+  0x14430003,
+  0x00000000,
+  0x8fa30014,
+  0x00000000,
+  0xafa3001c,
+  0x4d01ffff,
+  0x00000000,
+  0x1000ffa2,
+  0x00000000,
+  0x8f970ff0,
+  0x3c0200d1,
+  0xa7970fb8,
+  0x0017b980,
+  0x32f7ffc0,
+  0x02e2b821,
+  0xaee00030,
+  0x3c02dead,
+  0x8ee3003c,
+  0xaee2003c,
+  0x8ee20038,
+  0x1060ff95,
+  0xaee00038,
+  0x3c038800,
+  0xaf830fbc,
+  0x86e30008,
+  0x27970f18,
+  0x30630060,
+  0x02e3b821,
+  0x000318c2,
+  0x02e3b821,
+  0x8ee3001c,
+  0x1040ff8a,
+  0xaf820fb4,
+  0x3c020c40,
+  0xac430fb4,
+  0x8ee20018,
+  0x2463000c,
+  0x14430003,
+  0x00000000,
+  0x8ee30014,
+  0x00000000,
+  0xaee3001c,
+  0x4d01ffff,
+  0x00000000,
+  0x1000ff7d,
+  0x00000000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x90410000,
+  0x00000000,
+  0x00370825,
+  0x1000ff76,
+  0xa0410000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x94410000,
+  0x00000000,
+  0x00370825,
+  0x1000ff6f,
+  0xa4410000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x8c410000,
+  0x00000000,
+  0x00370825,
+  0x1000ff68,
+  0xac410000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x90410000,
+  0x02e0b827,
+  0x00370824,
+  0x1000ff61,
+  0xa0410000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x94410000,
+  0x02e0b827,
+  0x00370824,
+  0x1000ff5a,
+  0xa4410000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x8c410000,
+  0x02e0b827,
+  0x00370824,
+  0x1000ff53,
+  0xac410000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x1000ff4f,
+  0xa0570000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x1000ff4b,
+  0xa4570000,
+  0x8f820ff0,
+  0x8f970ff4,
+  0x1000ff47,
+  0xac570000,
+  0x8f820ff0,
+  0x00000000,
+  0x8c420000,
+  0x1000ff42,
+  0xaf820ff4,
+  0x3c01a0c2,
+  0x8c22c000,
+  0x00000000,
+  0xaf820ff0,
+  0x3c01a0c2,
+  0x8c22c004,
+  0x1000ff3a,
+  0xaf820ff4,
+  0x3c01a0d1,
+  0x8c22d5ac,
+  0x00000000,
+  0xaf820ff0,
+  0x3c01a0d1,
+  0x8c22d5b0,
+  0x1000ff32,
+  0xaf820ff4,
+  0x3c02a0f0,
+  0xac400000,
+  0x90570153,
+  0x00000000,
+  0xa3970c80,
+  0x90570157,
+  0x00000000,
+  0xa3970c81,
+  0x9057015b,
+  0x00000000,
+  0xa3970c87,
+  0x9057015f,
+  0x00000000,
+  0xa3970c86,
+  0x90570163,
+  0x00000000,
+  0x32f70007,
+  0xa3970c85,
+  0x90570193,
+  0x00000000,
+  0xa3970c8b,
+  0x90570197,
+  0x00000000,
+  0xa3970c8a,
+  0x9057019b,
+  0x00000000,
+  0x32f70007,
+  0xa3970c89,
+  0x9057000b,
+  0x00000000,
+  0x32f700e0,
+  0x00170942,
+  0x90570047,
+  0x00000000,
+  0x32f70078,
+  0x00370825,
+  0x90570067,
+  0x00000000,
+  0x32f7000f,
+  0x0017b9c0,
+  0x00370825,
+  0x905700c7,
+  0x00000000,
+  0x32f7002f,
+  0x0017bac0,
+  0x00370825,
+  0x90570147,
+  0x00000000,
+  0x32f7001e,
+  0x0017bc00,
+  0x00370825,
+  0x90570183,
+  0x00000000,
+  0x32f70060,
+  0x0017bc00,
+  0x00370825,
+  0xaf810c8c,
+  0x3c021840,
+  0x8f970fc8,
+  0x00000000,
+  0x8f970ff0,
+  0x00000000,
+  0xac570c80,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x00000000,
+  0x4d01ffff,
+  0x00000000,
+  0x3c02a0d1,
+  0x2442f998,
+  0xaf800c90,
+  0xaf800c94,
+  0x00400008,
+  0x00000000,
+  0x87970ff0,
+  0x3c1300d1,
+  0xa6770008,
+  0x3c030000,
+  0x24630520,
+  0xaf9f0fe8,
+  0x0060f809,
+  0x24020001,
+  0x8f9f0fe8,
+  0x1040feda,
+  0x97970ff0,
+  0x27830f18,
+  0x00771821,
+  0x0017b8c2,
+  0x02e3b821,
+  0x3c028800,
+  0xaf820fbc,
+  0x8e620038,
+  0xa7800fb8,
+  0xaf820fb4,
+  0x8ee3001c,
+  0x3c020c40,
+  0xac430fb4,
+  0x8ee20018,
+  0x2463000c,
+  0x14430004,
+  0xaee3001c,
+  0x8ee30014,
+  0x00000000,
+  0xaee3001c,
+  0x4d01ffff,
+  0x00000000,
+  0x1000ffdf,
+  0x00000000,
+  0x8f820c5c,
+  0x8f830c60,
+  0xaf820ff0,
+  0x1000febe,
+  0xaf830ff4,
+  0x23890800,
+  0x01201821,
+  0x2402000f,
+  0x206c0040,
+  0xac6c0008,
+  0x01801821,
+  0x1440fffc,
+  0x2042ffff,
+  0xac690008,
+  0x278b0c98,
+  0xa5600000,
+  0x2403ffff,
+  0xad630014,
+  0x34020001,
+  0x34420020,
+  0xa5620008,
+  0x278a0e00,
+  0x01401021,
+  0x00001821,
+  0xac400000,
+  0x24630004,
+  0x2c6c0100,
+  0x1580fffc,
+  0x24420004,
+  0x3c02a0d1,
+  0x2442e000,
+  0xaf820fe0,
+  0x3c1800d1,
+  0x01206021,
+  0x00006821,
+  0x00007821,
+  0x00005821,
+  0x00004021,
+  0x40026000,
+  0x00000000,
+  0x34424001,
+  0x40826000,
+  0x3c020000,
+  0x244206f8,
+  0x00400008,
+  0x00000000,
diff --git a/drivers/atm/atmsar11.regions b/drivers/atm/atmsar11.regions
new file mode 100644
index 0000000..42252b7
--- /dev/null
+++ b/drivers/atm/atmsar11.regions
@@ -0,0 +1,6 @@
+/*
+  See copyright and licensing conditions in ambassador.* files.
+*/
+  { 0x00000080,  993, },
+  { 0xa0d0d500,   80, },
+  { 0xa0d0f000,  978, },
diff --git a/drivers/atm/atmsar11.start b/drivers/atm/atmsar11.start
new file mode 100644
index 0000000..dba55e7
--- /dev/null
+++ b/drivers/atm/atmsar11.start
@@ -0,0 +1,4 @@
+/*
+  See copyright and licensing conditions in ambassador.* files.
+*/
+  0xa0d0f000
diff --git a/drivers/atm/atmtcp.c b/drivers/atm/atmtcp.c
new file mode 100644
index 0000000..f2f01cb
--- /dev/null
+++ b/drivers/atm/atmtcp.c
@@ -0,0 +1,505 @@
+/* drivers/atm/atmtcp.c - ATM over TCP "device" driver */
+
+/* Written 1997-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/atmdev.h>
+#include <linux/atm_tcp.h>
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+
+extern int atm_init_aal5(struct atm_vcc *vcc); /* "raw" AAL5 transport */
+
+
+#define PRIV(dev) ((struct atmtcp_dev_data *) ((dev)->dev_data))
+
+
+struct atmtcp_dev_data {
+	struct atm_vcc *vcc;	/* control VCC; NULL if detached */
+	int persist;		/* non-zero if persistent */
+};
+
+
+#define DEV_LABEL    "atmtcp"
+
+#define MAX_VPI_BITS  8	/* simplifies life */
+#define MAX_VCI_BITS 16
+
+
+/*
+ * Hairy code ahead: the control VCC may be closed while we're still
+ * waiting for an answer, so we need to re-validate out_vcc every once
+ * in a while.
+ */
+
+
+static int atmtcp_send_control(struct atm_vcc *vcc,int type,
+    const struct atmtcp_control *msg,int flag)
+{
+	DECLARE_WAITQUEUE(wait,current);
+	struct atm_vcc *out_vcc;
+	struct sk_buff *skb;
+	struct atmtcp_control *new_msg;
+	int old_test;
+	int error = 0;
+
+	out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL;
+	if (!out_vcc) return -EUNATCH;
+	skb = alloc_skb(sizeof(*msg),GFP_KERNEL);
+	if (!skb) return -ENOMEM;
+	mb();
+	out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL;
+	if (!out_vcc) {
+		dev_kfree_skb(skb);
+		return -EUNATCH;
+	}
+	atm_force_charge(out_vcc,skb->truesize);
+	new_msg = (struct atmtcp_control *) skb_put(skb,sizeof(*new_msg));
+	*new_msg = *msg;
+	new_msg->hdr.length = ATMTCP_HDR_MAGIC;
+	new_msg->type = type;
+	memset(&new_msg->vcc,0,sizeof(atm_kptr_t));
+	*(struct atm_vcc **) &new_msg->vcc = vcc;
+	old_test = test_bit(flag,&vcc->flags);
+	out_vcc->push(out_vcc,skb);
+	add_wait_queue(sk_atm(vcc)->sk_sleep, &wait);
+	while (test_bit(flag,&vcc->flags) == old_test) {
+		mb();
+		out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL;
+		if (!out_vcc) {
+			error = -EUNATCH;
+			break;
+		}
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(sk_atm(vcc)->sk_sleep, &wait);
+	return error;
+}
+
+
+static int atmtcp_recv_control(const struct atmtcp_control *msg)
+{
+	struct atm_vcc *vcc = *(struct atm_vcc **) &msg->vcc;
+
+	vcc->vpi = msg->addr.sap_addr.vpi;
+	vcc->vci = msg->addr.sap_addr.vci;
+	vcc->qos = msg->qos;
+	sk_atm(vcc)->sk_err = -msg->result;
+	switch (msg->type) {
+	    case ATMTCP_CTRL_OPEN:
+		change_bit(ATM_VF_READY,&vcc->flags);
+		break;
+	    case ATMTCP_CTRL_CLOSE:
+		change_bit(ATM_VF_ADDR,&vcc->flags);
+		break;
+	    default:
+		printk(KERN_ERR "atmtcp_recv_control: unknown type %d\n",
+		    msg->type);
+		return -EINVAL;
+	}
+	wake_up(sk_atm(vcc)->sk_sleep);
+	return 0;
+}
+
+
+static void atmtcp_v_dev_close(struct atm_dev *dev)
+{
+	/* Nothing.... Isn't this simple :-)  -- REW */
+}
+
+
+static int atmtcp_v_open(struct atm_vcc *vcc)
+{
+	struct atmtcp_control msg;
+	int error;
+	short vpi = vcc->vpi;
+	int vci = vcc->vci;
+
+	memset(&msg,0,sizeof(msg));
+	msg.addr.sap_family = AF_ATMPVC;
+	msg.hdr.vpi = htons(vpi);
+	msg.addr.sap_addr.vpi = vpi;
+	msg.hdr.vci = htons(vci);
+	msg.addr.sap_addr.vci = vci;
+	if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) return 0;
+	msg.type = ATMTCP_CTRL_OPEN;
+	msg.qos = vcc->qos;
+	set_bit(ATM_VF_ADDR,&vcc->flags);
+	clear_bit(ATM_VF_READY,&vcc->flags); /* just in case ... */
+	error = atmtcp_send_control(vcc,ATMTCP_CTRL_OPEN,&msg,ATM_VF_READY);
+	if (error) return error;
+	return -sk_atm(vcc)->sk_err;
+}
+
+
+static void atmtcp_v_close(struct atm_vcc *vcc)
+{
+	struct atmtcp_control msg;
+
+	memset(&msg,0,sizeof(msg));
+	msg.addr.sap_family = AF_ATMPVC;
+	msg.addr.sap_addr.vpi = vcc->vpi;
+	msg.addr.sap_addr.vci = vcc->vci;
+	clear_bit(ATM_VF_READY,&vcc->flags);
+	(void) atmtcp_send_control(vcc,ATMTCP_CTRL_CLOSE,&msg,ATM_VF_ADDR);
+}
+
+
+static int atmtcp_v_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	struct atm_cirange ci;
+	struct atm_vcc *vcc;
+	struct hlist_node *node;
+	struct sock *s;
+	int i;
+
+	if (cmd != ATM_SETCIRANGE) return -ENOIOCTLCMD;
+	if (copy_from_user(&ci, arg,sizeof(ci))) return -EFAULT;
+	if (ci.vpi_bits == ATM_CI_MAX) ci.vpi_bits = MAX_VPI_BITS;
+	if (ci.vci_bits == ATM_CI_MAX) ci.vci_bits = MAX_VCI_BITS;
+	if (ci.vpi_bits > MAX_VPI_BITS || ci.vpi_bits < 0 ||
+	    ci.vci_bits > MAX_VCI_BITS || ci.vci_bits < 0) return -EINVAL;
+	read_lock(&vcc_sklist_lock);
+	for(i = 0; i < VCC_HTABLE_SIZE; ++i) {
+		struct hlist_head *head = &vcc_hash[i];
+
+		sk_for_each(s, node, head) {
+			vcc = atm_sk(s);
+			if (vcc->dev != dev)
+				continue;
+			if ((vcc->vpi >> ci.vpi_bits) ||
+			    (vcc->vci >> ci.vci_bits)) {
+				read_unlock(&vcc_sklist_lock);
+				return -EBUSY;
+			}
+		}
+	}
+	read_unlock(&vcc_sklist_lock);
+	dev->ci_range = ci;
+	return 0;
+}
+
+
+static int atmtcp_v_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+	struct atmtcp_dev_data *dev_data;
+	struct atm_vcc *out_vcc=NULL; /* Initializer quietens GCC warning */
+	struct sk_buff *new_skb;
+	struct atmtcp_hdr *hdr;
+	int size;
+
+	if (vcc->qos.txtp.traffic_class == ATM_NONE) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+	dev_data = PRIV(vcc->dev);
+	if (dev_data) out_vcc = dev_data->vcc;
+	if (!dev_data || !out_vcc) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		if (dev_data) return 0;
+		atomic_inc(&vcc->stats->tx_err);
+		return -ENOLINK;
+	}
+	size = skb->len+sizeof(struct atmtcp_hdr);
+	new_skb = atm_alloc_charge(out_vcc,size,GFP_ATOMIC);
+	if (!new_skb) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		atomic_inc(&vcc->stats->tx_err);
+		return -ENOBUFS;
+	}
+	hdr = (void *) skb_put(new_skb,sizeof(struct atmtcp_hdr));
+	hdr->vpi = htons(vcc->vpi);
+	hdr->vci = htons(vcc->vci);
+	hdr->length = htonl(skb->len);
+	memcpy(skb_put(new_skb,skb->len),skb->data,skb->len);
+	if (vcc->pop) vcc->pop(vcc,skb);
+	else dev_kfree_skb(skb);
+	out_vcc->push(out_vcc,new_skb);
+	atomic_inc(&vcc->stats->tx);
+	atomic_inc(&out_vcc->stats->rx);
+	return 0;
+}
+
+
+static int atmtcp_v_proc(struct atm_dev *dev,loff_t *pos,char *page)
+{
+	struct atmtcp_dev_data *dev_data = PRIV(dev);
+
+	if (*pos) return 0;
+	if (!dev_data->persist) return sprintf(page,"ephemeral\n");
+	return sprintf(page,"persistent, %sconnected\n",
+	    dev_data->vcc ? "" : "dis");
+}
+
+
+static void atmtcp_c_close(struct atm_vcc *vcc)
+{
+	struct atm_dev *atmtcp_dev;
+	struct atmtcp_dev_data *dev_data;
+	struct sock *s;
+	struct hlist_node *node;
+	struct atm_vcc *walk;
+	int i;
+
+	atmtcp_dev = (struct atm_dev *) vcc->dev_data;
+	dev_data = PRIV(atmtcp_dev);
+	dev_data->vcc = NULL;
+	if (dev_data->persist) return;
+	atmtcp_dev->dev_data = NULL;
+	kfree(dev_data);
+	shutdown_atm_dev(atmtcp_dev);
+	vcc->dev_data = NULL;
+	read_lock(&vcc_sklist_lock);
+	for(i = 0; i < VCC_HTABLE_SIZE; ++i) {
+		struct hlist_head *head = &vcc_hash[i];
+
+		sk_for_each(s, node, head) {
+			walk = atm_sk(s);
+			if (walk->dev != atmtcp_dev)
+				continue;
+			wake_up(s->sk_sleep);
+		}
+	}
+	read_unlock(&vcc_sklist_lock);
+	module_put(THIS_MODULE);
+}
+
+
+static struct atm_vcc *find_vcc(struct atm_dev *dev, short vpi, int vci)
+{
+        struct hlist_head *head;
+        struct atm_vcc *vcc;
+        struct hlist_node *node;
+        struct sock *s;
+
+        head = &vcc_hash[vci & (VCC_HTABLE_SIZE -1)];
+
+        sk_for_each(s, node, head) {
+                vcc = atm_sk(s);
+                if (vcc->dev == dev &&
+                    vcc->vci == vci && vcc->vpi == vpi &&
+                    vcc->qos.rxtp.traffic_class != ATM_NONE) {
+                                return vcc;
+                }
+        }
+        return NULL;
+}
+
+
+static int atmtcp_c_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+	struct atm_dev *dev;
+	struct atmtcp_hdr *hdr;
+	struct atm_vcc *out_vcc;
+	struct sk_buff *new_skb;
+	int result = 0;
+
+	if (!skb->len) return 0;
+	dev = vcc->dev_data;
+	hdr = (struct atmtcp_hdr *) skb->data;
+	if (hdr->length == ATMTCP_HDR_MAGIC) {
+		result = atmtcp_recv_control(
+		    (struct atmtcp_control *) skb->data);
+		goto done;
+	}
+	read_lock(&vcc_sklist_lock);
+	out_vcc = find_vcc(dev, ntohs(hdr->vpi), ntohs(hdr->vci));
+	read_unlock(&vcc_sklist_lock);
+	if (!out_vcc) {
+		atomic_inc(&vcc->stats->tx_err);
+		goto done;
+	}
+	skb_pull(skb,sizeof(struct atmtcp_hdr));
+	new_skb = atm_alloc_charge(out_vcc,skb->len,GFP_KERNEL);
+	if (!new_skb) {
+		result = -ENOBUFS;
+		goto done;
+	}
+	do_gettimeofday(&new_skb->stamp);
+	memcpy(skb_put(new_skb,skb->len),skb->data,skb->len);
+	out_vcc->push(out_vcc,new_skb);
+	atomic_inc(&vcc->stats->tx);
+	atomic_inc(&out_vcc->stats->rx);
+done:
+	if (vcc->pop) vcc->pop(vcc,skb);
+	else dev_kfree_skb(skb);
+	return result;
+}
+
+
+/*
+ * Device operations for the virtual ATM devices created by ATMTCP.
+ */
+
+
+static struct atmdev_ops atmtcp_v_dev_ops = {
+	.dev_close	= atmtcp_v_dev_close,
+	.open		= atmtcp_v_open,
+	.close		= atmtcp_v_close,
+	.ioctl		= atmtcp_v_ioctl,
+	.send		= atmtcp_v_send,
+	.proc_read	= atmtcp_v_proc,
+	.owner		= THIS_MODULE
+};
+
+
+/*
+ * Device operations for the ATMTCP control device.
+ */
+
+
+static struct atmdev_ops atmtcp_c_dev_ops = {
+	.close		= atmtcp_c_close,
+	.send		= atmtcp_c_send
+};
+
+
+static struct atm_dev atmtcp_control_dev = {
+	.ops		= &atmtcp_c_dev_ops,
+	.type		= "atmtcp",
+	.number		= 999,
+	.lock		= SPIN_LOCK_UNLOCKED
+};
+
+
+static int atmtcp_create(int itf,int persist,struct atm_dev **result)
+{
+	struct atmtcp_dev_data *dev_data;
+	struct atm_dev *dev;
+
+	dev_data = kmalloc(sizeof(*dev_data),GFP_KERNEL);
+	if (!dev_data)
+		return -ENOMEM;
+
+	dev = atm_dev_register(DEV_LABEL,&atmtcp_v_dev_ops,itf,NULL);
+	if (!dev) {
+		kfree(dev_data);
+		return itf == -1 ? -ENOMEM : -EBUSY;
+	}
+	dev->ci_range.vpi_bits = MAX_VPI_BITS;
+	dev->ci_range.vci_bits = MAX_VCI_BITS;
+	dev->dev_data = dev_data;
+	PRIV(dev)->vcc = NULL;
+	PRIV(dev)->persist = persist;
+	if (result) *result = dev;
+	return 0;
+}
+
+
+static int atmtcp_attach(struct atm_vcc *vcc,int itf)
+{
+	struct atm_dev *dev;
+
+	dev = NULL;
+	if (itf != -1) dev = atm_dev_lookup(itf);
+	if (dev) {
+		if (dev->ops != &atmtcp_v_dev_ops) {
+			atm_dev_put(dev);
+			return -EMEDIUMTYPE;
+		}
+		if (PRIV(dev)->vcc) return -EBUSY;
+	}
+	else {
+		int error;
+
+		error = atmtcp_create(itf,0,&dev);
+		if (error) return error;
+	}
+	PRIV(dev)->vcc = vcc;
+	vcc->dev = &atmtcp_control_dev;
+	vcc_insert_socket(sk_atm(vcc));
+	set_bit(ATM_VF_META,&vcc->flags);
+	set_bit(ATM_VF_READY,&vcc->flags);
+	vcc->dev_data = dev;
+	(void) atm_init_aal5(vcc); /* @@@ losing AAL in transit ... */
+	vcc->stats = &atmtcp_control_dev.stats.aal5;
+	return dev->number;
+}
+
+
+static int atmtcp_create_persistent(int itf)
+{
+	return atmtcp_create(itf,1,NULL);
+}
+
+
+static int atmtcp_remove_persistent(int itf)
+{
+	struct atm_dev *dev;
+	struct atmtcp_dev_data *dev_data;
+
+	dev = atm_dev_lookup(itf);
+	if (!dev) return -ENODEV;
+	if (dev->ops != &atmtcp_v_dev_ops) {
+		atm_dev_put(dev);
+		return -EMEDIUMTYPE;
+	}
+	dev_data = PRIV(dev);
+	if (!dev_data->persist) return 0;
+	dev_data->persist = 0;
+	if (PRIV(dev)->vcc) return 0;
+	kfree(dev_data);
+	atm_dev_put(dev);
+	shutdown_atm_dev(dev);
+	return 0;
+}
+
+static int atmtcp_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+	int err = 0;
+	struct atm_vcc *vcc = ATM_SD(sock);
+
+	if (cmd != SIOCSIFATMTCP && cmd != ATMTCP_CREATE && cmd != ATMTCP_REMOVE)
+		return -ENOIOCTLCMD;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+		case SIOCSIFATMTCP:
+			err = atmtcp_attach(vcc, (int) arg);
+			if (err >= 0) {
+				sock->state = SS_CONNECTED;
+				__module_get(THIS_MODULE);
+			}
+			break;
+		case ATMTCP_CREATE:
+			err = atmtcp_create_persistent((int) arg);
+			break;
+		case ATMTCP_REMOVE:
+			err = atmtcp_remove_persistent((int) arg);
+			break;
+	}
+	return err;
+}
+
+static struct atm_ioctl atmtcp_ioctl_ops = {
+	.owner 	= THIS_MODULE,
+	.ioctl	= atmtcp_ioctl,
+};
+
+static __init int atmtcp_init(void)
+{
+	register_atm_ioctl(&atmtcp_ioctl_ops);
+	return 0;
+}
+
+
+static void __exit atmtcp_exit(void)
+{
+	deregister_atm_ioctl(&atmtcp_ioctl_ops);
+}
+
+MODULE_LICENSE("GPL");
+module_init(atmtcp_init);
+module_exit(atmtcp_exit);
diff --git a/drivers/atm/eni.c b/drivers/atm/eni.c
new file mode 100644
index 0000000..78e34ee
--- /dev/null
+++ b/drivers/atm/eni.c
@@ -0,0 +1,2299 @@
+/* drivers/atm/eni.c - Efficient Networks ENI155P device driver */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/skbuff.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/uio.h>
+#include <linux/init.h>
+#include <linux/atm_eni.h>
+#include <linux/bitops.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <asm/string.h>
+#include <asm/byteorder.h>
+
+#include "tonga.h"
+#include "midway.h"
+#include "suni.h"
+#include "eni.h"
+
+#if !defined(__i386__) && !defined(__x86_64__)
+#ifndef ioremap_nocache
+#define ioremap_nocache(X,Y) ioremap(X,Y)
+#endif 
+#endif
+
+/*
+ * TODO:
+ *
+ * Show stoppers
+ *  none
+ *
+ * Minor
+ *  - OAM support
+ *  - fix bugs listed below
+ */
+
+/*
+ * KNOWN BUGS:
+ *
+ * - may run into JK-JK bug and deadlock
+ * - should allocate UBR channel first
+ * - buffer space allocation algorithm is stupid
+ *   (RX: should be maxSDU+maxdelay*rate
+ *    TX: should be maxSDU+min(maxSDU,maxdelay*rate) )
+ * - doesn't support OAM cells
+ * - eni_put_free may hang if not putting memory fragments that _complete_
+ *   2^n block (never happens in real life, though)
+ * - keeps IRQ even if initialization fails
+ */
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+#ifndef CONFIG_ATM_ENI_TUNE_BURST
+#define CONFIG_ATM_ENI_BURST_TX_8W
+#define CONFIG_ATM_ENI_BURST_RX_4W
+#endif
+
+
+#ifndef CONFIG_ATM_ENI_DEBUG
+
+
+#define NULLCHECK(x)
+
+#define EVENT(s,a,b)
+
+
+static void event_dump(void)
+{
+}
+
+
+#else
+
+
+/* 
+ * NULL pointer checking
+ */
+
+#define NULLCHECK(x) \
+	if ((unsigned long) (x) < 0x30) \
+		printk(KERN_CRIT #x "==0x%lx\n",(unsigned long) (x))
+
+/*
+ * Very extensive activity logging. Greatly improves bug detection speed but
+ * costs a few Mbps if enabled.
+ */
+
+#define EV 64
+
+static const char *ev[EV];
+static unsigned long ev_a[EV],ev_b[EV];
+static int ec = 0;
+
+
+static void EVENT(const char *s,unsigned long a,unsigned long b)
+{
+	ev[ec] = s; 
+	ev_a[ec] = a;
+	ev_b[ec] = b;
+	ec = (ec+1) % EV;
+}
+
+
+static void event_dump(void)
+{
+	int n,i;
+
+	for (n = 0; n < EV; n++) {
+		i = (ec+n) % EV;
+		printk(KERN_NOTICE);
+		printk(ev[i] ? ev[i] : "(null)",ev_a[i],ev_b[i]);
+	}
+}
+
+
+#endif /* CONFIG_ATM_ENI_DEBUG */
+
+
+/*
+ * NExx   must not be equal at end
+ * EExx   may be equal at end
+ * xxPJOK verify validity of pointer jumps
+ * xxPMOK operating on a circular buffer of "c" words
+ */
+
+#define NEPJOK(a0,a1,b) \
+    ((a0) < (a1) ? (b) <= (a0) || (b) > (a1) : (b) <= (a0) && (b) > (a1))
+#define EEPJOK(a0,a1,b) \
+    ((a0) < (a1) ? (b) < (a0) || (b) >= (a1) : (b) < (a0) && (b) >= (a1))
+#define NEPMOK(a0,d,b,c) NEPJOK(a0,(a0+d) & (c-1),b)
+#define EEPMOK(a0,d,b,c) EEPJOK(a0,(a0+d) & (c-1),b)
+
+
+static int tx_complete = 0,dma_complete = 0,queued = 0,requeued = 0,
+  backlogged = 0,rx_enqueued = 0,rx_dequeued = 0,pushed = 0,submitted = 0,
+  putting = 0;
+
+static struct atm_dev *eni_boards = NULL;
+
+static u32 *cpu_zeroes = NULL; /* aligned "magic" zeroes */
+static dma_addr_t zeroes;
+
+/* Read/write registers on card */
+#define eni_in(r)	readl(eni_dev->reg+(r)*4)
+#define eni_out(v,r)	writel((v),eni_dev->reg+(r)*4)
+
+
+/*-------------------------------- utilities --------------------------------*/
+
+
+static void dump_mem(struct eni_dev *eni_dev)
+{
+	int i;
+
+	for (i = 0; i < eni_dev->free_len; i++)
+		printk(KERN_DEBUG "  %d: %p %d\n",i,
+		    eni_dev->free_list[i].start,
+		    1 << eni_dev->free_list[i].order);
+}
+
+
+static void dump(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+
+	int i;
+
+	eni_dev = ENI_DEV(dev);
+	printk(KERN_NOTICE "Free memory\n");
+	dump_mem(eni_dev);
+	printk(KERN_NOTICE "TX buffers\n");
+	for (i = 0; i < NR_CHAN; i++)
+		if (eni_dev->tx[i].send)
+			printk(KERN_NOTICE "  TX %d @ %p: %ld\n",i,
+			    eni_dev->tx[i].send,eni_dev->tx[i].words*4);
+	printk(KERN_NOTICE "RX buffers\n");
+	for (i = 0; i < 1024; i++)
+		if (eni_dev->rx_map[i] && ENI_VCC(eni_dev->rx_map[i])->rx)
+			printk(KERN_NOTICE "  RX %d @ %p: %ld\n",i,
+			    ENI_VCC(eni_dev->rx_map[i])->recv,
+			    ENI_VCC(eni_dev->rx_map[i])->words*4);
+	printk(KERN_NOTICE "----\n");
+}
+
+
+static void eni_put_free(struct eni_dev *eni_dev, void __iomem *start,
+    unsigned long size)
+{
+	struct eni_free *list;
+	int len,order;
+
+	DPRINTK("init 0x%lx+%ld(0x%lx)\n",start,size,size);
+	start += eni_dev->base_diff;
+	list = eni_dev->free_list;
+	len = eni_dev->free_len;
+	while (size) {
+		if (len >= eni_dev->free_list_size) {
+			printk(KERN_CRIT "eni_put_free overflow (%p,%ld)\n",
+			    start,size);
+			break;
+		}
+		for (order = 0; !(((unsigned long)start | size) & (1 << order)); order++);
+		if (MID_MIN_BUF_SIZE > (1 << order)) {
+			printk(KERN_CRIT "eni_put_free: order %d too small\n",
+			    order);
+			break;
+		}
+		list[len].start = (void __iomem *) start;
+		list[len].order = order;
+		len++;
+		start += 1 << order;
+		size -= 1 << order;
+	}
+	eni_dev->free_len = len;
+	/*dump_mem(eni_dev);*/
+}
+
+
+static void __iomem *eni_alloc_mem(struct eni_dev *eni_dev, unsigned long *size)
+{
+	struct eni_free *list;
+	void __iomem *start;
+	int len,i,order,best_order,index;
+
+	list = eni_dev->free_list;
+	len = eni_dev->free_len;
+	if (*size < MID_MIN_BUF_SIZE) *size = MID_MIN_BUF_SIZE;
+	if (*size > MID_MAX_BUF_SIZE) return NULL;
+	for (order = 0; (1 << order) < *size; order++);
+	DPRINTK("trying: %ld->%d\n",*size,order);
+	best_order = 65; /* we don't have more than 2^64 of anything ... */
+	index = 0; /* silence GCC */
+	for (i = 0; i < len; i++)
+		if (list[i].order == order) {
+			best_order = order;
+			index = i;
+			break;
+		}
+		else if (best_order > list[i].order && list[i].order > order) {
+				best_order = list[i].order;
+				index = i;
+			}
+	if (best_order == 65) return NULL;
+	start = list[index].start-eni_dev->base_diff;
+	list[index] = list[--len];
+	eni_dev->free_len = len;
+	*size = 1 << order;
+	eni_put_free(eni_dev,start+*size,(1 << best_order)-*size);
+	DPRINTK("%ld bytes (order %d) at 0x%lx\n",*size,order,start);
+	memset_io(start,0,*size);       /* never leak data */
+	/*dump_mem(eni_dev);*/
+	return start;
+}
+
+
+static void eni_free_mem(struct eni_dev *eni_dev, void __iomem *start,
+    unsigned long size)
+{
+	struct eni_free *list;
+	int len,i,order;
+
+	start += eni_dev->base_diff;
+	list = eni_dev->free_list;
+	len = eni_dev->free_len;
+	for (order = -1; size; order++) size >>= 1;
+	DPRINTK("eni_free_mem: %p+0x%lx (order %d)\n",start,size,order);
+	for (i = 0; i < len; i++)
+		if (((unsigned long) list[i].start) == ((unsigned long)start^(1 << order)) &&
+		    list[i].order == order) {
+			DPRINTK("match[%d]: 0x%lx/0x%lx(0x%x), %d/%d\n",i,
+			    list[i].start,start,1 << order,list[i].order,order);
+			list[i] = list[--len];
+			start = (void __iomem *) ((unsigned long) start & ~(unsigned long) (1 << order));
+			order++;
+			i = -1;
+			continue;
+		}
+	if (len >= eni_dev->free_list_size) {
+		printk(KERN_ALERT "eni_free_mem overflow (%p,%d)\n",start,
+		    order);
+		return;
+	}
+	list[len].start = start;
+	list[len].order = order;
+	eni_dev->free_len = len+1;
+	/*dump_mem(eni_dev);*/
+}
+
+
+/*----------------------------------- RX ------------------------------------*/
+
+
+#define ENI_VCC_NOS ((struct atm_vcc *) 1)
+
+
+static void rx_ident_err(struct atm_vcc *vcc)
+{
+	struct atm_dev *dev;
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+
+	dev = vcc->dev;
+	eni_dev = ENI_DEV(dev);
+	/* immediately halt adapter */
+	eni_out(eni_in(MID_MC_S) &
+	    ~(MID_DMA_ENABLE | MID_TX_ENABLE | MID_RX_ENABLE),MID_MC_S);
+	/* dump useful information */
+	eni_vcc = ENI_VCC(vcc);
+	printk(KERN_ALERT DEV_LABEL "(itf %d): driver error - RX ident "
+	    "mismatch\n",dev->number);
+	printk(KERN_ALERT "  VCI %d, rxing %d, words %ld\n",vcc->vci,
+	    eni_vcc->rxing,eni_vcc->words);
+	printk(KERN_ALERT "  host descr 0x%lx, rx pos 0x%lx, descr value "
+	    "0x%x\n",eni_vcc->descr,eni_vcc->rx_pos,
+	    (unsigned) readl(eni_vcc->recv+eni_vcc->descr*4));
+	printk(KERN_ALERT "  last %p, servicing %d\n",eni_vcc->last,
+	    eni_vcc->servicing);
+	EVENT("---dump ends here---\n",0,0);
+	printk(KERN_NOTICE "---recent events---\n");
+	event_dump();
+	ENI_DEV(dev)->fast = NULL; /* really stop it */
+	ENI_DEV(dev)->slow = NULL;
+	skb_queue_head_init(&ENI_DEV(dev)->rx_queue);
+}
+
+
+static int do_rx_dma(struct atm_vcc *vcc,struct sk_buff *skb,
+    unsigned long skip,unsigned long size,unsigned long eff)
+{
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+	u32 dma_rd,dma_wr;
+	u32 dma[RX_DMA_BUF*2];
+	dma_addr_t paddr;
+	unsigned long here;
+	int i,j;
+
+	eni_dev = ENI_DEV(vcc->dev);
+	eni_vcc = ENI_VCC(vcc);
+	paddr = 0; /* GCC, shut up */
+	if (skb) {
+		paddr = pci_map_single(eni_dev->pci_dev,skb->data,skb->len,
+		    PCI_DMA_FROMDEVICE);
+		ENI_PRV_PADDR(skb) = paddr;
+		if (paddr & 3)
+			printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %d has "
+			    "mis-aligned RX data (0x%lx)\n",vcc->dev->number,
+			    vcc->vci,(unsigned long) paddr);
+		ENI_PRV_SIZE(skb) = size+skip;
+		    /* PDU plus descriptor */
+		ATM_SKB(skb)->vcc = vcc;
+	}
+	j = 0;
+	if ((eff && skip) || 1) { /* @@@ actually, skip is always == 1 ... */
+		here = (eni_vcc->descr+skip) & (eni_vcc->words-1);
+		dma[j++] = (here << MID_DMA_COUNT_SHIFT) | (vcc->vci
+		    << MID_DMA_VCI_SHIFT) | MID_DT_JK;
+		j++;
+	}
+	here = (eni_vcc->descr+size+skip) & (eni_vcc->words-1);
+	if (!eff) size += skip;
+	else {
+		unsigned long words;
+
+		if (!size) {
+			DPRINTK("strange things happen ...\n");
+			EVENT("strange things happen ... (skip=%ld,eff=%ld)\n",
+			    size,eff);
+		}
+		words = eff;
+		if (paddr & 15) {
+			unsigned long init;
+
+			init = 4-((paddr & 15) >> 2);
+			if (init > words) init = words;
+			dma[j++] = MID_DT_WORD | (init << MID_DMA_COUNT_SHIFT) |
+			    (vcc->vci << MID_DMA_VCI_SHIFT);
+			dma[j++] = paddr;
+			paddr += init << 2;
+			words -= init;
+		}
+#ifdef CONFIG_ATM_ENI_BURST_RX_16W /* may work with some PCI chipsets ... */
+		if (words & ~15) {
+			dma[j++] = MID_DT_16W | ((words >> 4) <<
+			    MID_DMA_COUNT_SHIFT) | (vcc->vci <<
+			    MID_DMA_VCI_SHIFT);
+			dma[j++] = paddr;
+			paddr += (words & ~15) << 2;
+			words &= 15;
+		}
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_8W  /* works only with *some* PCI chipsets ... */
+		if (words & ~7) {
+			dma[j++] = MID_DT_8W | ((words >> 3) <<
+			    MID_DMA_COUNT_SHIFT) | (vcc->vci <<
+			    MID_DMA_VCI_SHIFT);
+			dma[j++] = paddr;
+			paddr += (words & ~7) << 2;
+			words &= 7;
+		}
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_4W /* recommended */
+		if (words & ~3) {
+			dma[j++] = MID_DT_4W | ((words >> 2) <<
+			    MID_DMA_COUNT_SHIFT) | (vcc->vci <<
+			    MID_DMA_VCI_SHIFT);
+			dma[j++] = paddr;
+			paddr += (words & ~3) << 2;
+			words &= 3;
+		}
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_2W /* probably useless if RX_4W, RX_8W, ... */
+		if (words & ~1) {
+			dma[j++] = MID_DT_2W | ((words >> 1) <<
+			    MID_DMA_COUNT_SHIFT) | (vcc->vci <<
+			    MID_DMA_VCI_SHIFT);
+			dma[j++] = paddr;
+			paddr += (words & ~1) << 2;
+			words &= 1;
+		}
+#endif
+		if (words) {
+			dma[j++] = MID_DT_WORD | (words << MID_DMA_COUNT_SHIFT)
+			    | (vcc->vci << MID_DMA_VCI_SHIFT);
+			dma[j++] = paddr;
+		}
+	}
+	if (size != eff) {
+		dma[j++] = (here << MID_DMA_COUNT_SHIFT) |
+		    (vcc->vci << MID_DMA_VCI_SHIFT) | MID_DT_JK;
+		j++;
+	}
+	if (!j || j > 2*RX_DMA_BUF) {
+		printk(KERN_CRIT DEV_LABEL "!j or j too big!!!\n");
+		goto trouble;
+	}
+	dma[j-2] |= MID_DMA_END;
+	j = j >> 1;
+	dma_wr = eni_in(MID_DMA_WR_RX);
+	dma_rd = eni_in(MID_DMA_RD_RX);
+	/*
+	 * Can I move the dma_wr pointer by 2j+1 positions without overwriting
+	 * data that hasn't been read (position of dma_rd) yet ?
+	 */
+	if (!NEPMOK(dma_wr,j+j+1,dma_rd,NR_DMA_RX)) { /* @@@ +1 is ugly */
+		printk(KERN_WARNING DEV_LABEL "(itf %d): RX DMA full\n",
+		    vcc->dev->number);
+		goto trouble;
+	}
+        for (i = 0; i < j; i++) {
+		writel(dma[i*2],eni_dev->rx_dma+dma_wr*8);
+		writel(dma[i*2+1],eni_dev->rx_dma+dma_wr*8+4);
+		dma_wr = (dma_wr+1) & (NR_DMA_RX-1);
+        }
+	if (skb) {
+		ENI_PRV_POS(skb) = eni_vcc->descr+size+1;
+		skb_queue_tail(&eni_dev->rx_queue,skb);
+		eni_vcc->last = skb;
+rx_enqueued++;
+	}
+	eni_vcc->descr = here;
+	eni_out(dma_wr,MID_DMA_WR_RX);
+	return 0;
+
+trouble:
+	if (paddr)
+		pci_unmap_single(eni_dev->pci_dev,paddr,skb->len,
+		    PCI_DMA_FROMDEVICE);
+	if (skb) dev_kfree_skb_irq(skb);
+	return -1;
+}
+
+
+static void discard(struct atm_vcc *vcc,unsigned long size)
+{
+	struct eni_vcc *eni_vcc;
+
+	eni_vcc = ENI_VCC(vcc);
+	EVENT("discard (size=%ld)\n",size,0);
+	while (do_rx_dma(vcc,NULL,1,size,0)) EVENT("BUSY LOOP",0,0);
+	    /* could do a full fallback, but that might be more expensive */
+	if (eni_vcc->rxing) ENI_PRV_POS(eni_vcc->last) += size+1;
+	else eni_vcc->rx_pos = (eni_vcc->rx_pos+size+1) & (eni_vcc->words-1);
+}
+
+
+/*
+ * TODO: should check whether direct copies (without DMA setup, dequeuing on
+ * interrupt, etc.) aren't much faster for AAL0
+ */
+
+static int rx_aal0(struct atm_vcc *vcc)
+{
+	struct eni_vcc *eni_vcc;
+	unsigned long descr;
+	unsigned long length;
+	struct sk_buff *skb;
+
+	DPRINTK(">rx_aal0\n");
+	eni_vcc = ENI_VCC(vcc);
+	descr = readl(eni_vcc->recv+eni_vcc->descr*4);
+	if ((descr & MID_RED_IDEN) != (MID_RED_RX_ID << MID_RED_SHIFT)) {
+		rx_ident_err(vcc);
+		return 1;
+	}
+	if (descr & MID_RED_T) {
+		DPRINTK(DEV_LABEL "(itf %d): trashing empty cell\n",
+		    vcc->dev->number);
+		length = 0;
+		atomic_inc(&vcc->stats->rx_err);
+	}
+	else {
+		length = ATM_CELL_SIZE-1; /* no HEC */
+	}
+	skb = length ? atm_alloc_charge(vcc,length,GFP_ATOMIC) : NULL;
+	if (!skb) {
+		discard(vcc,length >> 2);
+		return 0;
+	}
+	skb_put(skb,length);
+	skb->stamp = eni_vcc->timestamp;
+	DPRINTK("got len %ld\n",length);
+	if (do_rx_dma(vcc,skb,1,length >> 2,length >> 2)) return 1;
+	eni_vcc->rxing++;
+	return 0;
+}
+
+
+static int rx_aal5(struct atm_vcc *vcc)
+{
+	struct eni_vcc *eni_vcc;
+	unsigned long descr;
+	unsigned long size,eff,length;
+	struct sk_buff *skb;
+
+	EVENT("rx_aal5\n",0,0);
+	DPRINTK(">rx_aal5\n");
+	eni_vcc = ENI_VCC(vcc);
+	descr = readl(eni_vcc->recv+eni_vcc->descr*4);
+	if ((descr & MID_RED_IDEN) != (MID_RED_RX_ID << MID_RED_SHIFT)) {
+		rx_ident_err(vcc);
+		return 1;
+	}
+	if (descr & (MID_RED_T | MID_RED_CRC_ERR)) {
+		if (descr & MID_RED_T) {
+			EVENT("empty cell (descr=0x%lx)\n",descr,0);
+			DPRINTK(DEV_LABEL "(itf %d): trashing empty cell\n",
+			    vcc->dev->number);
+			size = 0;
+		}
+		else {
+			static unsigned long silence = 0;
+
+			if (time_after(jiffies, silence) || silence == 0) {
+				printk(KERN_WARNING DEV_LABEL "(itf %d): "
+				    "discarding PDU(s) with CRC error\n",
+				    vcc->dev->number);
+				silence = (jiffies+2*HZ)|1;
+			}
+			size = (descr & MID_RED_COUNT)*(ATM_CELL_PAYLOAD >> 2);
+			EVENT("CRC error (descr=0x%lx,size=%ld)\n",descr,
+			    size);
+		}
+		eff = length = 0;
+		atomic_inc(&vcc->stats->rx_err);
+	}
+	else {
+		size = (descr & MID_RED_COUNT)*(ATM_CELL_PAYLOAD >> 2);
+		DPRINTK("size=%ld\n",size);
+		length = readl(eni_vcc->recv+(((eni_vcc->descr+size-1) &
+		    (eni_vcc->words-1)))*4) & 0xffff;
+				/* -trailer(2)+header(1) */
+		if (length && length <= (size << 2)-8 && length <=
+		  ATM_MAX_AAL5_PDU) eff = (length+3) >> 2;
+		else {				 /* ^ trailer length (8) */
+			EVENT("bad PDU (descr=0x08%lx,length=%ld)\n",descr,
+			    length);
+			printk(KERN_ERR DEV_LABEL "(itf %d): bad AAL5 PDU "
+			    "(VCI=%d,length=%ld,size=%ld (descr 0x%lx))\n",
+			    vcc->dev->number,vcc->vci,length,size << 2,descr);
+			length = eff = 0;
+			atomic_inc(&vcc->stats->rx_err);
+		}
+	}
+	skb = eff ? atm_alloc_charge(vcc,eff << 2,GFP_ATOMIC) : NULL;
+	if (!skb) {
+		discard(vcc,size);
+		return 0;
+	}
+	skb_put(skb,length);
+	DPRINTK("got len %ld\n",length);
+	if (do_rx_dma(vcc,skb,1,size,eff)) return 1;
+	eni_vcc->rxing++;
+	return 0;
+}
+
+
+static inline int rx_vcc(struct atm_vcc *vcc)
+{
+	void __iomem *vci_dsc;
+	unsigned long tmp;
+	struct eni_vcc *eni_vcc;
+
+	eni_vcc = ENI_VCC(vcc);
+	vci_dsc = ENI_DEV(vcc->dev)->vci+vcc->vci*16;
+	EVENT("rx_vcc(1)\n",0,0);
+	while (eni_vcc->descr != (tmp = (readl(vci_dsc+4) & MID_VCI_DESCR) >>
+	    MID_VCI_DESCR_SHIFT)) {
+		EVENT("rx_vcc(2: host dsc=0x%lx, nic dsc=0x%lx)\n",
+		    eni_vcc->descr,tmp);
+		DPRINTK("CB_DESCR %ld REG_DESCR %d\n",ENI_VCC(vcc)->descr,
+		    (((unsigned) readl(vci_dsc+4) & MID_VCI_DESCR) >>
+		    MID_VCI_DESCR_SHIFT));
+		if (ENI_VCC(vcc)->rx(vcc)) return 1;
+	}
+	/* clear IN_SERVICE flag */
+	writel(readl(vci_dsc) & ~MID_VCI_IN_SERVICE,vci_dsc);
+	/*
+	 * If new data has arrived between evaluating the while condition and
+	 * clearing IN_SERVICE, we wouldn't be notified until additional data
+	 * follows. So we have to loop again to be sure.
+	 */
+	EVENT("rx_vcc(3)\n",0,0);
+	while (ENI_VCC(vcc)->descr != (tmp = (readl(vci_dsc+4) & MID_VCI_DESCR)
+	    >> MID_VCI_DESCR_SHIFT)) {
+		EVENT("rx_vcc(4: host dsc=0x%lx, nic dsc=0x%lx)\n",
+		    eni_vcc->descr,tmp);
+		DPRINTK("CB_DESCR %ld REG_DESCR %d\n",ENI_VCC(vcc)->descr,
+		    (((unsigned) readl(vci_dsc+4) & MID_VCI_DESCR) >>
+		    MID_VCI_DESCR_SHIFT));
+		if (ENI_VCC(vcc)->rx(vcc)) return 1;
+	}
+	return 0;
+}
+
+
+static void poll_rx(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	struct atm_vcc *curr;
+
+	eni_dev = ENI_DEV(dev);
+	while ((curr = eni_dev->fast)) {
+		EVENT("poll_rx.fast\n",0,0);
+		if (rx_vcc(curr)) return;
+		eni_dev->fast = ENI_VCC(curr)->next;
+		ENI_VCC(curr)->next = ENI_VCC_NOS;
+		barrier();
+		ENI_VCC(curr)->servicing--;
+	}
+	while ((curr = eni_dev->slow)) {
+		EVENT("poll_rx.slow\n",0,0);
+		if (rx_vcc(curr)) return;
+		eni_dev->slow = ENI_VCC(curr)->next;
+		ENI_VCC(curr)->next = ENI_VCC_NOS;
+		barrier();
+		ENI_VCC(curr)->servicing--;
+	}
+}
+
+
+static void get_service(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	struct atm_vcc *vcc;
+	unsigned long vci;
+
+	DPRINTK(">get_service\n");
+	eni_dev = ENI_DEV(dev);
+	while (eni_in(MID_SERV_WRITE) != eni_dev->serv_read) {
+		vci = readl(eni_dev->service+eni_dev->serv_read*4);
+		eni_dev->serv_read = (eni_dev->serv_read+1) & (NR_SERVICE-1);
+		vcc = eni_dev->rx_map[vci & 1023];
+		if (!vcc) {
+			printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %ld not "
+			    "found\n",dev->number,vci);
+			continue; /* nasty but we try to go on anyway */
+			/* @@@ nope, doesn't work */
+		}
+		EVENT("getting from service\n",0,0);
+		if (ENI_VCC(vcc)->next != ENI_VCC_NOS) {
+			EVENT("double service\n",0,0);
+			DPRINTK("Grr, servicing VCC %ld twice\n",vci);
+			continue;
+		}
+		do_gettimeofday(&ENI_VCC(vcc)->timestamp);
+		ENI_VCC(vcc)->next = NULL;
+		if (vcc->qos.rxtp.traffic_class == ATM_CBR) {
+			if (eni_dev->fast)
+				ENI_VCC(eni_dev->last_fast)->next = vcc;
+			else eni_dev->fast = vcc;
+			eni_dev->last_fast = vcc;
+		}
+		else {
+			if (eni_dev->slow)
+				ENI_VCC(eni_dev->last_slow)->next = vcc;
+			else eni_dev->slow = vcc;
+			eni_dev->last_slow = vcc;
+		}
+putting++;
+		ENI_VCC(vcc)->servicing++;
+	}
+}
+
+
+static void dequeue_rx(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+	struct atm_vcc *vcc;
+	struct sk_buff *skb;
+	void __iomem *vci_dsc;
+	int first;
+
+	eni_dev = ENI_DEV(dev);
+	first = 1;
+	while (1) {
+		skb = skb_dequeue(&eni_dev->rx_queue);
+		if (!skb) {
+			if (first) {
+				DPRINTK(DEV_LABEL "(itf %d): RX but not "
+				    "rxing\n",dev->number);
+				EVENT("nothing to dequeue\n",0,0);
+			}
+			break;
+		}
+		EVENT("dequeued (size=%ld,pos=0x%lx)\n",ENI_PRV_SIZE(skb),
+		    ENI_PRV_POS(skb));
+rx_dequeued++;
+		vcc = ATM_SKB(skb)->vcc;
+		eni_vcc = ENI_VCC(vcc);
+		first = 0;
+		vci_dsc = eni_dev->vci+vcc->vci*16;
+		if (!EEPMOK(eni_vcc->rx_pos,ENI_PRV_SIZE(skb),
+		    (readl(vci_dsc+4) & MID_VCI_READ) >> MID_VCI_READ_SHIFT,
+		    eni_vcc->words)) {
+			EVENT("requeuing\n",0,0);
+			skb_queue_head(&eni_dev->rx_queue,skb);
+			break;
+		}
+		eni_vcc->rxing--;
+		eni_vcc->rx_pos = ENI_PRV_POS(skb) & (eni_vcc->words-1);
+		pci_unmap_single(eni_dev->pci_dev,ENI_PRV_PADDR(skb),skb->len,
+		    PCI_DMA_TODEVICE);
+		if (!skb->len) dev_kfree_skb_irq(skb);
+		else {
+			EVENT("pushing (len=%ld)\n",skb->len,0);
+			if (vcc->qos.aal == ATM_AAL0)
+				*(unsigned long *) skb->data =
+				    ntohl(*(unsigned long *) skb->data);
+			memset(skb->cb,0,sizeof(struct eni_skb_prv));
+			vcc->push(vcc,skb);
+			pushed++;
+		}
+		atomic_inc(&vcc->stats->rx);
+	}
+	wake_up(&eni_dev->rx_wait);
+}
+
+
+static int open_rx_first(struct atm_vcc *vcc)
+{
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+	unsigned long size;
+
+	DPRINTK("open_rx_first\n");
+	eni_dev = ENI_DEV(vcc->dev);
+	eni_vcc = ENI_VCC(vcc);
+	eni_vcc->rx = NULL;
+	if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0;
+	size = vcc->qos.rxtp.max_sdu*eni_dev->rx_mult/100;
+	if (size > MID_MAX_BUF_SIZE && vcc->qos.rxtp.max_sdu <=
+	    MID_MAX_BUF_SIZE)
+		size = MID_MAX_BUF_SIZE;
+	eni_vcc->recv = eni_alloc_mem(eni_dev,&size);
+	DPRINTK("rx at 0x%lx\n",eni_vcc->recv);
+	eni_vcc->words = size >> 2;
+	if (!eni_vcc->recv) return -ENOBUFS;
+	eni_vcc->rx = vcc->qos.aal == ATM_AAL5 ? rx_aal5 : rx_aal0;
+	eni_vcc->descr = 0;
+	eni_vcc->rx_pos = 0;
+	eni_vcc->rxing = 0;
+	eni_vcc->servicing = 0;
+	eni_vcc->next = ENI_VCC_NOS;
+	return 0;
+}
+
+
+static int open_rx_second(struct atm_vcc *vcc)
+{
+	void __iomem *here;
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+	unsigned long size;
+	int order;
+
+	DPRINTK("open_rx_second\n");
+	eni_dev = ENI_DEV(vcc->dev);
+	eni_vcc = ENI_VCC(vcc);
+	if (!eni_vcc->rx) return 0;
+	/* set up VCI descriptor */
+	here = eni_dev->vci+vcc->vci*16;
+	DPRINTK("loc 0x%x\n",(unsigned) (eni_vcc->recv-eni_dev->ram)/4);
+	size = eni_vcc->words >> 8;
+	for (order = -1; size; order++) size >>= 1;
+	writel(0,here+4); /* descr, read = 0 */
+	writel(0,here+8); /* write, state, count = 0 */
+	if (eni_dev->rx_map[vcc->vci])
+		printk(KERN_CRIT DEV_LABEL "(itf %d): BUG - VCI %d already "
+		    "in use\n",vcc->dev->number,vcc->vci);
+	eni_dev->rx_map[vcc->vci] = vcc; /* now it counts */
+	writel(((vcc->qos.aal != ATM_AAL5 ? MID_MODE_RAW : MID_MODE_AAL5) <<
+	    MID_VCI_MODE_SHIFT) | MID_VCI_PTI_MODE |
+	    (((eni_vcc->recv-eni_dev->ram) >> (MID_LOC_SKIP+2)) <<
+	    MID_VCI_LOCATION_SHIFT) | (order << MID_VCI_SIZE_SHIFT),here);
+	return 0;
+}
+
+
+static void close_rx(struct atm_vcc *vcc)
+{
+	DECLARE_WAITQUEUE(wait,current);
+	void __iomem *here;
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+
+	eni_vcc = ENI_VCC(vcc);
+	if (!eni_vcc->rx) return;
+	eni_dev = ENI_DEV(vcc->dev);
+	if (vcc->vpi != ATM_VPI_UNSPEC && vcc->vci != ATM_VCI_UNSPEC) {
+		here = eni_dev->vci+vcc->vci*16;
+		/* block receiver */
+		writel((readl(here) & ~MID_VCI_MODE) | (MID_MODE_TRASH <<
+		    MID_VCI_MODE_SHIFT),here);
+		/* wait for receiver to become idle */
+		udelay(27);
+		/* discard pending cell */
+		writel(readl(here) & ~MID_VCI_IN_SERVICE,here);
+		/* don't accept any new ones */
+		eni_dev->rx_map[vcc->vci] = NULL;
+		/* wait for RX queue to drain */
+		DPRINTK("eni_close: waiting for RX ...\n");
+		EVENT("RX closing\n",0,0);
+		add_wait_queue(&eni_dev->rx_wait,&wait);
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		barrier();
+		for (;;) {
+			/* transition service->rx: rxing++, servicing-- */
+			if (!eni_vcc->servicing) {
+				barrier();
+				if (!eni_vcc->rxing) break;
+			}
+			EVENT("drain PDUs (rx %ld, serv %ld)\n",eni_vcc->rxing,
+			    eni_vcc->servicing);
+			printk(KERN_INFO "%d+%d RX left\n",eni_vcc->servicing,
+			    eni_vcc->rxing);
+			schedule();
+			set_current_state(TASK_UNINTERRUPTIBLE);
+		}
+		for (;;) {
+			int at_end;
+			u32 tmp;
+
+			tasklet_disable(&eni_dev->task);
+			tmp = readl(eni_dev->vci+vcc->vci*16+4) & MID_VCI_READ;
+			at_end = eni_vcc->rx_pos == tmp >> MID_VCI_READ_SHIFT;
+			tasklet_enable(&eni_dev->task);
+			if (at_end) break;
+			EVENT("drain discard (host 0x%lx, nic 0x%lx)\n",
+			    eni_vcc->rx_pos,tmp);
+			printk(KERN_INFO "draining RX: host 0x%lx, nic 0x%x\n",
+			    eni_vcc->rx_pos,tmp);
+			schedule();
+			set_current_state(TASK_UNINTERRUPTIBLE);
+		}
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&eni_dev->rx_wait,&wait);
+	}
+	eni_free_mem(eni_dev,eni_vcc->recv,eni_vcc->words << 2);
+	eni_vcc->rx = NULL;
+}
+
+
+static int start_rx(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+
+	eni_dev = ENI_DEV(dev);
+	eni_dev->rx_map = (struct atm_vcc **) get_zeroed_page(GFP_KERNEL);
+	if (!eni_dev->rx_map) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): couldn't get free page\n",
+		    dev->number);
+		free_page((unsigned long) eni_dev->free_list);
+		return -ENOMEM;
+	}
+	memset(eni_dev->rx_map,0,PAGE_SIZE);
+	eni_dev->rx_mult = DEFAULT_RX_MULT;
+	eni_dev->fast = eni_dev->last_fast = NULL;
+	eni_dev->slow = eni_dev->last_slow = NULL;
+	init_waitqueue_head(&eni_dev->rx_wait);
+	skb_queue_head_init(&eni_dev->rx_queue);
+	eni_dev->serv_read = eni_in(MID_SERV_WRITE);
+	eni_out(0,MID_DMA_WR_RX);
+	return 0;
+}
+
+
+/*----------------------------------- TX ------------------------------------*/
+
+
+enum enq_res { enq_ok,enq_next,enq_jam };
+
+
+static inline void put_dma(int chan,u32 *dma,int *j,dma_addr_t paddr,
+    u32 size)
+{
+	u32 init,words;
+
+	DPRINTK("put_dma: 0x%lx+0x%x\n",(unsigned long) paddr,size);
+	EVENT("put_dma: 0x%lx+0x%lx\n",(unsigned long) paddr,size);
+#if 0 /* don't complain anymore */
+	if (paddr & 3)
+		printk(KERN_ERR "put_dma: unaligned addr (0x%lx)\n",paddr);
+	if (size & 3)
+		printk(KERN_ERR "put_dma: unaligned size (0x%lx)\n",size);
+#endif
+	if (paddr & 3) {
+		init = 4-(paddr & 3);
+		if (init > size || size < 7) init = size;
+		DPRINTK("put_dma: %lx DMA: %d/%d bytes\n",
+		    (unsigned long) paddr,init,size);
+		dma[(*j)++] = MID_DT_BYTE | (init << MID_DMA_COUNT_SHIFT) |
+		    (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += init;
+		size -= init;
+	}
+	words = size >> 2;
+	size &= 3;
+	if (words && (paddr & 31)) {
+		init = 8-((paddr & 31) >> 2);
+		if (init > words) init = words;
+		DPRINTK("put_dma: %lx DMA: %d/%d words\n",
+		    (unsigned long) paddr,init,words);
+		dma[(*j)++] = MID_DT_WORD | (init << MID_DMA_COUNT_SHIFT) |
+		    (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += init << 2;
+		words -= init;
+	}
+#ifdef CONFIG_ATM_ENI_BURST_TX_16W /* may work with some PCI chipsets ... */
+	if (words & ~15) {
+		DPRINTK("put_dma: %lx DMA: %d*16/%d words\n",
+		    (unsigned long) paddr,words >> 4,words);
+		dma[(*j)++] = MID_DT_16W | ((words >> 4) << MID_DMA_COUNT_SHIFT)
+		    | (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += (words & ~15) << 2;
+		words &= 15;
+	}
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_8W /* recommended */
+	if (words & ~7) {
+		DPRINTK("put_dma: %lx DMA: %d*8/%d words\n",
+		    (unsigned long) paddr,words >> 3,words);
+		dma[(*j)++] = MID_DT_8W | ((words >> 3) << MID_DMA_COUNT_SHIFT)
+		    | (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += (words & ~7) << 2;
+		words &= 7;
+	}
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_4W /* probably useless if TX_8W or TX_16W */
+	if (words & ~3) {
+		DPRINTK("put_dma: %lx DMA: %d*4/%d words\n",
+		    (unsigned long) paddr,words >> 2,words);
+		dma[(*j)++] = MID_DT_4W | ((words >> 2) << MID_DMA_COUNT_SHIFT)
+		    | (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += (words & ~3) << 2;
+		words &= 3;
+	}
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_2W /* probably useless if TX_4W, TX_8W, ... */
+	if (words & ~1) {
+		DPRINTK("put_dma: %lx DMA: %d*2/%d words\n",
+		    (unsigned long) paddr,words >> 1,words);
+		dma[(*j)++] = MID_DT_2W | ((words >> 1) << MID_DMA_COUNT_SHIFT)
+		    | (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += (words & ~1) << 2;
+		words &= 1;
+	}
+#endif
+	if (words) {
+		DPRINTK("put_dma: %lx DMA: %d words\n",(unsigned long) paddr,
+		    words);
+		dma[(*j)++] = MID_DT_WORD | (words << MID_DMA_COUNT_SHIFT) |
+		    (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+		paddr += words << 2;
+	}
+	if (size) {
+		DPRINTK("put_dma: %lx DMA: %d bytes\n",(unsigned long) paddr,
+		    size);
+		dma[(*j)++] = MID_DT_BYTE | (size << MID_DMA_COUNT_SHIFT) |
+		    (chan << MID_DMA_CHAN_SHIFT);
+		dma[(*j)++] = paddr;
+	}
+}
+
+
+static enum enq_res do_tx(struct sk_buff *skb)
+{
+	struct atm_vcc *vcc;
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+	struct eni_tx *tx;
+	dma_addr_t paddr;
+	u32 dma_rd,dma_wr;
+	u32 size; /* in words */
+	int aal5,dma_size,i,j;
+
+	DPRINTK(">do_tx\n");
+	NULLCHECK(skb);
+	EVENT("do_tx: skb=0x%lx, %ld bytes\n",(unsigned long) skb,skb->len);
+	vcc = ATM_SKB(skb)->vcc;
+	NULLCHECK(vcc);
+	eni_dev = ENI_DEV(vcc->dev);
+	NULLCHECK(eni_dev);
+	eni_vcc = ENI_VCC(vcc);
+	tx = eni_vcc->tx;
+	NULLCHECK(tx);
+#if 0 /* Enable this for testing with the "align" program */
+	{
+		unsigned int hack = *((char *) skb->data)-'0';
+
+		if (hack < 8) {
+			skb->data += hack;
+			skb->len -= hack;
+		}
+	}
+#endif
+#if 0 /* should work now */
+	if ((unsigned long) skb->data & 3)
+		printk(KERN_ERR DEV_LABEL "(itf %d): VCI %d has mis-aligned "
+		    "TX data\n",vcc->dev->number,vcc->vci);
+#endif
+	/*
+	 * Potential future IP speedup: make hard_header big enough to put
+	 * segmentation descriptor directly into PDU. Saves: 4 slave writes,
+	 * 1 DMA xfer & 2 DMA'ed bytes (protocol layering is for wimps :-)
+	 */
+
+	aal5 = vcc->qos.aal == ATM_AAL5;
+	/* check space in buffer */
+	if (!aal5)
+		size = (ATM_CELL_PAYLOAD >> 2)+TX_DESCR_SIZE;
+			/* cell without HEC plus segmentation header (includes
+			   four-byte cell header) */
+	else {
+		size = skb->len+4*AAL5_TRAILER+ATM_CELL_PAYLOAD-1;
+			/* add AAL5 trailer */
+		size = ((size-(size % ATM_CELL_PAYLOAD)) >> 2)+TX_DESCR_SIZE;
+						/* add segmentation header */
+	}
+	/*
+	 * Can I move tx_pos by size bytes without getting closer than TX_GAP
+	 * to the read pointer ? TX_GAP means to leave some space for what
+	 * the manual calls "too close".
+	 */
+	if (!NEPMOK(tx->tx_pos,size+TX_GAP,
+	    eni_in(MID_TX_RDPTR(tx->index)),tx->words)) {
+		DPRINTK(DEV_LABEL "(itf %d): TX full (size %d)\n",
+		    vcc->dev->number,size);
+		return enq_next;
+	}
+	/* check DMA */
+	dma_wr = eni_in(MID_DMA_WR_TX);
+	dma_rd = eni_in(MID_DMA_RD_TX);
+	dma_size = 3; /* JK for descriptor and final fill, plus final size
+			 mis-alignment fix */
+DPRINTK("iovcnt = %d\n",skb_shinfo(skb)->nr_frags);
+	if (!skb_shinfo(skb)->nr_frags) dma_size += 5;
+	else dma_size += 5*(skb_shinfo(skb)->nr_frags+1);
+	if (dma_size > TX_DMA_BUF) {
+		printk(KERN_CRIT DEV_LABEL "(itf %d): needs %d DMA entries "
+		    "(got only %d)\n",vcc->dev->number,dma_size,TX_DMA_BUF);
+	}
+	DPRINTK("dma_wr is %d, tx_pos is %ld\n",dma_wr,tx->tx_pos);
+	if (dma_wr != dma_rd && ((dma_rd+NR_DMA_TX-dma_wr) & (NR_DMA_TX-1)) <
+	     dma_size) {
+		printk(KERN_WARNING DEV_LABEL "(itf %d): TX DMA full\n",
+		    vcc->dev->number);
+		return enq_jam;
+	}
+	paddr = pci_map_single(eni_dev->pci_dev,skb->data,skb->len,
+	    PCI_DMA_TODEVICE);
+	ENI_PRV_PADDR(skb) = paddr;
+	/* prepare DMA queue entries */
+	j = 0;
+	eni_dev->dma[j++] = (((tx->tx_pos+TX_DESCR_SIZE) & (tx->words-1)) <<
+	     MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) |
+	     MID_DT_JK;
+	j++;
+	if (!skb_shinfo(skb)->nr_frags)
+		if (aal5) put_dma(tx->index,eni_dev->dma,&j,paddr,skb->len);
+		else put_dma(tx->index,eni_dev->dma,&j,paddr+4,skb->len-4);
+	else {
+DPRINTK("doing direct send\n"); /* @@@ well, this doesn't work anyway */
+		for (i = -1; i < skb_shinfo(skb)->nr_frags; i++)
+			if (i == -1)
+				put_dma(tx->index,eni_dev->dma,&j,(unsigned long)
+				    skb->data,
+				    skb->len - skb->data_len);
+			else
+				put_dma(tx->index,eni_dev->dma,&j,(unsigned long)
+				    skb_shinfo(skb)->frags[i].page + skb_shinfo(skb)->frags[i].page_offset,
+				    skb_shinfo(skb)->frags[i].size);
+	}
+	if (skb->len & 3)
+		put_dma(tx->index,eni_dev->dma,&j,zeroes,4-(skb->len & 3));
+	/* JK for AAL5 trailer - AAL0 doesn't need it, but who cares ... */
+	eni_dev->dma[j++] = (((tx->tx_pos+size) & (tx->words-1)) <<
+	     MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) |
+	     MID_DMA_END | MID_DT_JK;
+	j++;
+	DPRINTK("DMA at end: %d\n",j);
+	/* store frame */
+	writel((MID_SEG_TX_ID << MID_SEG_ID_SHIFT) |
+	    (aal5 ? MID_SEG_AAL5 : 0) | (tx->prescaler << MID_SEG_PR_SHIFT) |
+	    (tx->resolution << MID_SEG_RATE_SHIFT) |
+	    (size/(ATM_CELL_PAYLOAD/4)),tx->send+tx->tx_pos*4);
+/*printk("dsc = 0x%08lx\n",(unsigned long) readl(tx->send+tx->tx_pos*4));*/
+	writel((vcc->vci << MID_SEG_VCI_SHIFT) |
+            (aal5 ? 0 : (skb->data[3] & 0xf)) |
+	    (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? MID_SEG_CLP : 0),
+	    tx->send+((tx->tx_pos+1) & (tx->words-1))*4);
+	DPRINTK("size: %d, len:%d\n",size,skb->len);
+	if (aal5)
+		writel(skb->len,tx->send+
+                    ((tx->tx_pos+size-AAL5_TRAILER) & (tx->words-1))*4);
+	j = j >> 1;
+	for (i = 0; i < j; i++) {
+		writel(eni_dev->dma[i*2],eni_dev->tx_dma+dma_wr*8);
+		writel(eni_dev->dma[i*2+1],eni_dev->tx_dma+dma_wr*8+4);
+		dma_wr = (dma_wr+1) & (NR_DMA_TX-1);
+	}
+	ENI_PRV_POS(skb) = tx->tx_pos;
+	ENI_PRV_SIZE(skb) = size;
+	ENI_VCC(vcc)->txing += size;
+	tx->tx_pos = (tx->tx_pos+size) & (tx->words-1);
+	DPRINTK("dma_wr set to %d, tx_pos is now %ld\n",dma_wr,tx->tx_pos);
+	eni_out(dma_wr,MID_DMA_WR_TX);
+	skb_queue_tail(&eni_dev->tx_queue,skb);
+queued++;
+	return enq_ok;
+}
+
+
+static void poll_tx(struct atm_dev *dev)
+{
+	struct eni_tx *tx;
+	struct sk_buff *skb;
+	enum enq_res res;
+	int i;
+
+	DPRINTK(">poll_tx\n");
+	for (i = NR_CHAN-1; i >= 0; i--) {
+		tx = &ENI_DEV(dev)->tx[i];
+		if (tx->send)
+			while ((skb = skb_dequeue(&tx->backlog))) {
+				res = do_tx(skb);
+				if (res == enq_ok) continue;
+				DPRINTK("re-queuing TX PDU\n");
+				skb_queue_head(&tx->backlog,skb);
+requeued++;
+				if (res == enq_jam) return;
+				break;
+			}
+	}
+}
+
+
+static void dequeue_tx(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	struct atm_vcc *vcc;
+	struct sk_buff *skb;
+	struct eni_tx *tx;
+
+	NULLCHECK(dev);
+	eni_dev = ENI_DEV(dev);
+	NULLCHECK(eni_dev);
+	while ((skb = skb_dequeue(&eni_dev->tx_queue))) {
+		vcc = ATM_SKB(skb)->vcc;
+		NULLCHECK(vcc);
+		tx = ENI_VCC(vcc)->tx;
+		NULLCHECK(ENI_VCC(vcc)->tx);
+		DPRINTK("dequeue_tx: next 0x%lx curr 0x%x\n",ENI_PRV_POS(skb),
+		    (unsigned) eni_in(MID_TX_DESCRSTART(tx->index)));
+		if (ENI_VCC(vcc)->txing < tx->words && ENI_PRV_POS(skb) ==
+		    eni_in(MID_TX_DESCRSTART(tx->index))) {
+			skb_queue_head(&eni_dev->tx_queue,skb);
+			break;
+		}
+		ENI_VCC(vcc)->txing -= ENI_PRV_SIZE(skb);
+		pci_unmap_single(eni_dev->pci_dev,ENI_PRV_PADDR(skb),skb->len,
+		    PCI_DMA_TODEVICE);
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb_irq(skb);
+		atomic_inc(&vcc->stats->tx);
+		wake_up(&eni_dev->tx_wait);
+dma_complete++;
+	}
+}
+
+
+static struct eni_tx *alloc_tx(struct eni_dev *eni_dev,int ubr)
+{
+	int i;
+
+	for (i = !ubr; i < NR_CHAN; i++)
+		if (!eni_dev->tx[i].send) return eni_dev->tx+i;
+	return NULL;
+}
+
+
+static int comp_tx(struct eni_dev *eni_dev,int *pcr,int reserved,int *pre,
+    int *res,int unlimited)
+{
+	static const int pre_div[] = { 4,16,128,2048 };
+	    /* 2^(((x+2)^2-(x+2))/2+1) */
+
+	if (unlimited) *pre = *res = 0;
+	else {
+		if (*pcr > 0) {
+			int div;
+
+			for (*pre = 0; *pre < 3; (*pre)++)
+				if (TS_CLOCK/pre_div[*pre]/64 <= *pcr) break;
+			div = pre_div[*pre]**pcr;
+			DPRINTK("min div %d\n",div);
+			*res = TS_CLOCK/div-1;
+		}
+		else {
+			int div;
+
+			if (!*pcr) *pcr = eni_dev->tx_bw+reserved;
+			for (*pre = 3; *pre >= 0; (*pre)--)
+				if (TS_CLOCK/pre_div[*pre]/64 > -*pcr) break;
+			if (*pre < 3) (*pre)++; /* else fail later */
+			div = pre_div[*pre]*-*pcr;
+			DPRINTK("max div %d\n",div);
+			*res = (TS_CLOCK+div-1)/div-1;
+		}
+		if (*res < 0) *res = 0;
+		if (*res > MID_SEG_MAX_RATE) *res = MID_SEG_MAX_RATE;
+	}
+	*pcr = TS_CLOCK/pre_div[*pre]/(*res+1);
+	DPRINTK("out pcr: %d (%d:%d)\n",*pcr,*pre,*res);
+	return 0;
+}
+
+
+static int reserve_or_set_tx(struct atm_vcc *vcc,struct atm_trafprm *txtp,
+    int set_rsv,int set_shp)
+{
+	struct eni_dev *eni_dev = ENI_DEV(vcc->dev);
+	struct eni_vcc *eni_vcc = ENI_VCC(vcc);
+	struct eni_tx *tx;
+	unsigned long size;
+	void __iomem *mem;
+	int rate,ubr,unlimited,new_tx;
+	int pre,res,order;
+	int error;
+
+	rate = atm_pcr_goal(txtp);
+	ubr = txtp->traffic_class == ATM_UBR;
+	unlimited = ubr && (!rate || rate <= -ATM_OC3_PCR ||
+	    rate >= ATM_OC3_PCR);
+	if (!unlimited) {
+		size = txtp->max_sdu*eni_dev->tx_mult/100;
+		if (size > MID_MAX_BUF_SIZE && txtp->max_sdu <=
+		    MID_MAX_BUF_SIZE)
+			size = MID_MAX_BUF_SIZE;
+	}
+	else {
+		if (eni_dev->ubr) {
+			eni_vcc->tx = eni_dev->ubr;
+			txtp->pcr = ATM_OC3_PCR;
+			return 0;
+		}
+		size = UBR_BUFFER;
+	}
+	new_tx = !eni_vcc->tx;
+	mem = NULL; /* for gcc */
+	if (!new_tx) tx = eni_vcc->tx;
+	else {
+		mem = eni_alloc_mem(eni_dev,&size);
+		if (!mem) return -ENOBUFS;
+		tx = alloc_tx(eni_dev,unlimited);
+		if (!tx) {
+			eni_free_mem(eni_dev,mem,size);
+			return -EBUSY;
+		}
+		DPRINTK("got chan %d\n",tx->index);
+		tx->reserved = tx->shaping = 0;
+		tx->send = mem;
+		tx->words = size >> 2;
+		skb_queue_head_init(&tx->backlog);
+		for (order = 0; size > (1 << (order+10)); order++);
+		eni_out((order << MID_SIZE_SHIFT) |
+		    ((tx->send-eni_dev->ram) >> (MID_LOC_SKIP+2)),
+		    MID_TX_PLACE(tx->index));
+		tx->tx_pos = eni_in(MID_TX_DESCRSTART(tx->index)) &
+		    MID_DESCR_START;
+	}
+	error = comp_tx(eni_dev,&rate,tx->reserved,&pre,&res,unlimited);
+	if (!error  && txtp->min_pcr > rate) error = -EINVAL;
+	if (!error && txtp->max_pcr && txtp->max_pcr != ATM_MAX_PCR &&
+	    txtp->max_pcr < rate) error = -EINVAL;
+	if (!error && !ubr && rate > eni_dev->tx_bw+tx->reserved)
+		error = -EINVAL;
+	if (!error && set_rsv && !set_shp && rate < tx->shaping)
+		error = -EINVAL;
+	if (!error && !set_rsv && rate > tx->reserved && !ubr)
+		error = -EINVAL;
+	if (error) {
+		if (new_tx) {
+			tx->send = NULL;
+			eni_free_mem(eni_dev,mem,size);
+		}
+		return error;
+	}
+	txtp->pcr = rate;
+	if (set_rsv && !ubr) {
+		eni_dev->tx_bw += tx->reserved;
+		tx->reserved = rate;
+		eni_dev->tx_bw -= rate;
+	}
+	if (set_shp || (unlimited && new_tx)) {
+		if (unlimited && new_tx) eni_dev->ubr = tx;
+		tx->prescaler = pre;
+		tx->resolution = res;
+		tx->shaping = rate;
+	}
+	if (set_shp) eni_vcc->tx = tx;
+	DPRINTK("rsv %d shp %d\n",tx->reserved,tx->shaping);
+	return 0;
+}
+
+
+static int open_tx_first(struct atm_vcc *vcc)
+{
+	ENI_VCC(vcc)->tx = NULL;
+	if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0;
+	ENI_VCC(vcc)->txing = 0;
+	return reserve_or_set_tx(vcc,&vcc->qos.txtp,1,1);
+}
+
+
+static int open_tx_second(struct atm_vcc *vcc)
+{
+	return 0; /* nothing to do */
+}
+
+
+static void close_tx(struct atm_vcc *vcc)
+{
+	DECLARE_WAITQUEUE(wait,current);
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+
+	eni_vcc = ENI_VCC(vcc);
+	if (!eni_vcc->tx) return;
+	eni_dev = ENI_DEV(vcc->dev);
+	/* wait for TX queue to drain */
+	DPRINTK("eni_close: waiting for TX ...\n");
+	add_wait_queue(&eni_dev->tx_wait,&wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	for (;;) {
+		int txing;
+
+		tasklet_disable(&eni_dev->task);
+		txing = skb_peek(&eni_vcc->tx->backlog) || eni_vcc->txing;
+		tasklet_enable(&eni_dev->task);
+		if (!txing) break;
+		DPRINTK("%d TX left\n",eni_vcc->txing);
+		schedule();
+		set_current_state(TASK_UNINTERRUPTIBLE);
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&eni_dev->tx_wait,&wait);
+	if (eni_vcc->tx != eni_dev->ubr) {
+		/*
+		 * Looping a few times in here is probably far cheaper than
+		 * keeping track of TX completions all the time, so let's poll
+		 * a bit ...
+		 */
+		while (eni_in(MID_TX_RDPTR(eni_vcc->tx->index)) !=
+		    eni_in(MID_TX_DESCRSTART(eni_vcc->tx->index)))
+			schedule();
+		eni_free_mem(eni_dev,eni_vcc->tx->send,eni_vcc->tx->words << 2);
+		eni_vcc->tx->send = NULL;
+		eni_dev->tx_bw += eni_vcc->tx->reserved;
+	}
+	eni_vcc->tx = NULL;
+}
+
+
+static int start_tx(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	int i;
+
+	eni_dev = ENI_DEV(dev);
+	eni_dev->lost = 0;
+	eni_dev->tx_bw = ATM_OC3_PCR;
+	eni_dev->tx_mult = DEFAULT_TX_MULT;
+	init_waitqueue_head(&eni_dev->tx_wait);
+	eni_dev->ubr = NULL;
+	skb_queue_head_init(&eni_dev->tx_queue);
+	eni_out(0,MID_DMA_WR_TX);
+	for (i = 0; i < NR_CHAN; i++) {
+		eni_dev->tx[i].send = NULL;
+		eni_dev->tx[i].index = i;
+	}
+	return 0;
+}
+
+
+/*--------------------------------- common ----------------------------------*/
+
+
+#if 0 /* may become useful again when tuning things */
+
+static void foo(void)
+{
+printk(KERN_INFO
+  "tx_complete=%d,dma_complete=%d,queued=%d,requeued=%d,sub=%d,\n"
+  "backlogged=%d,rx_enqueued=%d,rx_dequeued=%d,putting=%d,pushed=%d\n",
+  tx_complete,dma_complete,queued,requeued,submitted,backlogged,
+  rx_enqueued,rx_dequeued,putting,pushed);
+if (eni_boards) printk(KERN_INFO "loss: %ld\n",ENI_DEV(eni_boards)->lost);
+}
+
+#endif
+
+
+static void bug_int(struct atm_dev *dev,unsigned long reason)
+{
+	struct eni_dev *eni_dev;
+
+	DPRINTK(">bug_int\n");
+	eni_dev = ENI_DEV(dev);
+	if (reason & MID_DMA_ERR_ACK)
+		printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - DMA "
+		    "error\n",dev->number);
+	if (reason & MID_TX_IDENT_MISM)
+		printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - ident "
+		    "mismatch\n",dev->number);
+	if (reason & MID_TX_DMA_OVFL)
+		printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - DMA "
+		    "overflow\n",dev->number);
+	EVENT("---dump ends here---\n",0,0);
+	printk(KERN_NOTICE "---recent events---\n");
+	event_dump();
+}
+
+
+static irqreturn_t eni_int(int irq,void *dev_id,struct pt_regs *regs)
+{
+	struct atm_dev *dev;
+	struct eni_dev *eni_dev;
+	u32 reason;
+
+	DPRINTK(">eni_int\n");
+	dev = dev_id;
+	eni_dev = ENI_DEV(dev);
+	reason = eni_in(MID_ISA);
+	DPRINTK(DEV_LABEL ": int 0x%lx\n",(unsigned long) reason);
+	/*
+	 * Must handle these two right now, because reading ISA doesn't clear
+	 * them, so they re-occur and we never make it to the tasklet. Since
+	 * they're rare, we don't mind the occasional invocation of eni_tasklet
+	 * with eni_dev->events == 0.
+	 */
+	if (reason & MID_STAT_OVFL) {
+		EVENT("stat overflow\n",0,0);
+		eni_dev->lost += eni_in(MID_STAT) & MID_OVFL_TRASH;
+	}
+	if (reason & MID_SUNI_INT) {
+		EVENT("SUNI int\n",0,0);
+		dev->phy->interrupt(dev);
+#if 0
+		foo();
+#endif
+	}
+	spin_lock(&eni_dev->lock);
+	eni_dev->events |= reason;
+	spin_unlock(&eni_dev->lock);
+	tasklet_schedule(&eni_dev->task);
+	return IRQ_HANDLED;
+}
+
+
+static void eni_tasklet(unsigned long data)
+{
+	struct atm_dev *dev = (struct atm_dev *) data;
+	struct eni_dev *eni_dev = ENI_DEV(dev);
+	unsigned long flags;
+	u32 events;
+
+	DPRINTK("eni_tasklet (dev %p)\n",dev);
+	spin_lock_irqsave(&eni_dev->lock,flags);
+	events = xchg(&eni_dev->events,0);
+	spin_unlock_irqrestore(&eni_dev->lock,flags);
+	if (events & MID_RX_DMA_COMPLETE) {
+		EVENT("INT: RX DMA complete, starting dequeue_rx\n",0,0);
+		dequeue_rx(dev);
+		EVENT("dequeue_rx done, starting poll_rx\n",0,0);
+		poll_rx(dev);
+		EVENT("poll_rx done\n",0,0);
+		/* poll_tx ? */
+	}
+	if (events & MID_SERVICE) {
+		EVENT("INT: service, starting get_service\n",0,0);
+		get_service(dev);
+		EVENT("get_service done, starting poll_rx\n",0,0);
+		poll_rx(dev);
+		EVENT("poll_rx done\n",0,0);
+	}
+ 	if (events & MID_TX_DMA_COMPLETE) {
+		EVENT("INT: TX DMA COMPLETE\n",0,0);
+		dequeue_tx(dev);
+	}
+	if (events & MID_TX_COMPLETE) {
+		EVENT("INT: TX COMPLETE\n",0,0);
+tx_complete++;
+		wake_up(&eni_dev->tx_wait);
+		/* poll_rx ? */
+	}
+	if (events & (MID_DMA_ERR_ACK | MID_TX_IDENT_MISM | MID_TX_DMA_OVFL)) {
+		EVENT("bug interrupt\n",0,0);
+		bug_int(dev,events);
+	}
+	poll_tx(dev);
+}
+
+
+/*--------------------------------- entries ---------------------------------*/
+
+
+static const char *media_name[] __devinitdata = {
+    "MMF", "SMF", "MMF", "03?", /*  0- 3 */
+    "UTP", "05?", "06?", "07?", /*  4- 7 */
+    "TAXI","09?", "10?", "11?", /*  8-11 */
+    "12?", "13?", "14?", "15?", /* 12-15 */
+    "MMF", "SMF", "18?", "19?", /* 16-19 */
+    "UTP", "21?", "22?", "23?", /* 20-23 */
+    "24?", "25?", "26?", "27?", /* 24-27 */
+    "28?", "29?", "30?", "31?"  /* 28-31 */
+};
+
+
+#define SET_SEPROM \
+  ({ if (!error && !pci_error) { \
+    pci_error = pci_write_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,tonga); \
+    udelay(10); /* 10 usecs */ \
+  } })
+#define GET_SEPROM \
+  ({ if (!error && !pci_error) { \
+    pci_error = pci_read_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,&tonga); \
+    udelay(10); /* 10 usecs */ \
+  } })
+
+
+static int __devinit get_esi_asic(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	unsigned char tonga;
+	int error,failed,pci_error;
+	int address,i,j;
+
+	eni_dev = ENI_DEV(dev);
+	error = pci_error = 0;
+	tonga = SEPROM_MAGIC | SEPROM_DATA | SEPROM_CLK;
+	SET_SEPROM;
+	for (i = 0; i < ESI_LEN && !error && !pci_error; i++) {
+		/* start operation */
+		tonga |= SEPROM_DATA;
+		SET_SEPROM;
+		tonga |= SEPROM_CLK;
+		SET_SEPROM;
+		tonga &= ~SEPROM_DATA;
+		SET_SEPROM;
+		tonga &= ~SEPROM_CLK;
+		SET_SEPROM;
+		/* send address */
+		address = ((i+SEPROM_ESI_BASE) << 1)+1;
+		for (j = 7; j >= 0; j--) {
+			tonga = (address >> j) & 1 ? tonga | SEPROM_DATA :
+			    tonga & ~SEPROM_DATA;
+			SET_SEPROM;
+			tonga |= SEPROM_CLK;
+			SET_SEPROM;
+			tonga &= ~SEPROM_CLK;
+			SET_SEPROM;
+		}
+		/* get ack */
+		tonga |= SEPROM_DATA;
+		SET_SEPROM;
+		tonga |= SEPROM_CLK;
+		SET_SEPROM;
+		GET_SEPROM;
+		failed = tonga & SEPROM_DATA;
+		tonga &= ~SEPROM_CLK;
+		SET_SEPROM;
+		tonga |= SEPROM_DATA;
+		SET_SEPROM;
+		if (failed) error = -EIO;
+		else {
+			dev->esi[i] = 0;
+			for (j = 7; j >= 0; j--) {
+				dev->esi[i] <<= 1;
+				tonga |= SEPROM_DATA;
+				SET_SEPROM;
+				tonga |= SEPROM_CLK;
+				SET_SEPROM;
+				GET_SEPROM;
+				if (tonga & SEPROM_DATA) dev->esi[i] |= 1;
+				tonga &= ~SEPROM_CLK;
+				SET_SEPROM;
+				tonga |= SEPROM_DATA;
+				SET_SEPROM;
+			}
+			/* get ack */
+			tonga |= SEPROM_DATA;
+			SET_SEPROM;
+			tonga |= SEPROM_CLK;
+			SET_SEPROM;
+			GET_SEPROM;
+			if (!(tonga & SEPROM_DATA)) error = -EIO;
+			tonga &= ~SEPROM_CLK;
+			SET_SEPROM;
+			tonga |= SEPROM_DATA;
+			SET_SEPROM;
+		}
+		/* stop operation */
+		tonga &= ~SEPROM_DATA;
+		SET_SEPROM;
+		tonga |= SEPROM_CLK;
+		SET_SEPROM;
+		tonga |= SEPROM_DATA;
+		SET_SEPROM;
+	}
+	if (pci_error) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): error reading ESI "
+		    "(0x%02x)\n",dev->number,pci_error);
+		error = -EIO;
+	}
+	return error;
+}
+
+
+#undef SET_SEPROM
+#undef GET_SEPROM
+
+
+static int __devinit get_esi_fpga(struct atm_dev *dev, void __iomem *base)
+{
+	void __iomem *mac_base;
+	int i;
+
+	mac_base = base+EPROM_SIZE-sizeof(struct midway_eprom);
+	for (i = 0; i < ESI_LEN; i++) dev->esi[i] = readb(mac_base+(i^3));
+	return 0;
+}
+
+
+static int __devinit eni_do_init(struct atm_dev *dev)
+{
+	struct midway_eprom __iomem *eprom;
+	struct eni_dev *eni_dev;
+	struct pci_dev *pci_dev;
+	unsigned long real_base;
+	void __iomem *base;
+	unsigned char revision;
+	int error,i,last;
+
+	DPRINTK(">eni_init\n");
+	dev->ci_range.vpi_bits = 0;
+	dev->ci_range.vci_bits = NR_VCI_LD;
+	dev->link_rate = ATM_OC3_PCR;
+	eni_dev = ENI_DEV(dev);
+	pci_dev = eni_dev->pci_dev;
+	real_base = pci_resource_start(pci_dev, 0);
+	eni_dev->irq = pci_dev->irq;
+	error = pci_read_config_byte(pci_dev,PCI_REVISION_ID,&revision);
+	if (error) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): init error 0x%02x\n",
+		    dev->number,error);
+		return -EINVAL;
+	}
+	if ((error = pci_write_config_word(pci_dev,PCI_COMMAND,
+	    PCI_COMMAND_MEMORY |
+	    (eni_dev->asic ? PCI_COMMAND_PARITY | PCI_COMMAND_SERR : 0)))) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory "
+		    "(0x%02x)\n",dev->number,error);
+		return -EIO;
+	}
+	printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d,base=0x%lx,irq=%d,",
+	    dev->number,revision,real_base,eni_dev->irq);
+	if (!(base = ioremap_nocache(real_base,MAP_MAX_SIZE))) {
+		printk("\n");
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't set up page "
+		    "mapping\n",dev->number);
+		return error;
+	}
+	eni_dev->base_diff = real_base - (unsigned long) base;
+	/* id may not be present in ASIC Tonga boards - check this @@@ */
+	if (!eni_dev->asic) {
+		eprom = (base+EPROM_SIZE-sizeof(struct midway_eprom));
+		if (readl(&eprom->magic) != ENI155_MAGIC) {
+			printk("\n");
+			printk(KERN_ERR KERN_ERR DEV_LABEL "(itf %d): bad "
+			    "magic - expected 0x%x, got 0x%x\n",dev->number,
+			    ENI155_MAGIC,(unsigned) readl(&eprom->magic));
+			return -EINVAL;
+		}
+	}
+	eni_dev->phy = base+PHY_BASE;
+	eni_dev->reg = base+REG_BASE;
+	eni_dev->ram = base+RAM_BASE;
+	last = MAP_MAX_SIZE-RAM_BASE;
+	for (i = last-RAM_INCREMENT; i >= 0; i -= RAM_INCREMENT) {
+		writel(0x55555555,eni_dev->ram+i);
+		if (readl(eni_dev->ram+i) != 0x55555555) last = i;
+		else {
+			writel(0xAAAAAAAA,eni_dev->ram+i);
+			if (readl(eni_dev->ram+i) != 0xAAAAAAAA) last = i;
+			else writel(i,eni_dev->ram+i);
+		}
+	}
+	for (i = 0; i < last; i += RAM_INCREMENT)
+		if (readl(eni_dev->ram+i) != i) break;
+	eni_dev->mem = i;
+	memset_io(eni_dev->ram,0,eni_dev->mem);
+	/* TODO: should shrink allocation now */
+	printk("mem=%dkB (",eni_dev->mem >> 10);
+	/* TODO: check for non-SUNI, check for TAXI ? */
+	if (!(eni_in(MID_RES_ID_MCON) & 0x200) != !eni_dev->asic) {
+		printk(")\n");
+		printk(KERN_ERR DEV_LABEL "(itf %d): ERROR - wrong id 0x%x\n",
+		    dev->number,(unsigned) eni_in(MID_RES_ID_MCON));
+		return -EINVAL;
+	}
+	error = eni_dev->asic ? get_esi_asic(dev) : get_esi_fpga(dev,base);
+	if (error) return error;
+	for (i = 0; i < ESI_LEN; i++)
+		printk("%s%02X",i ? "-" : "",dev->esi[i]);
+	printk(")\n");
+	printk(KERN_NOTICE DEV_LABEL "(itf %d): %s,%s\n",dev->number,
+	    eni_in(MID_RES_ID_MCON) & 0x200 ? "ASIC" : "FPGA",
+	    media_name[eni_in(MID_RES_ID_MCON) & DAUGTHER_ID]);
+	return suni_init(dev);
+}
+
+
+static int __devinit eni_start(struct atm_dev *dev)
+{
+	struct eni_dev *eni_dev;
+	
+	void __iomem *buf;
+	unsigned long buffer_mem;
+	int error;
+
+	DPRINTK(">eni_start\n");
+	eni_dev = ENI_DEV(dev);
+	if (request_irq(eni_dev->irq,&eni_int,SA_SHIRQ,DEV_LABEL,dev)) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n",
+		    dev->number,eni_dev->irq);
+		return -EAGAIN;
+	}
+	/* @@@ should release IRQ on error */
+	pci_set_master(eni_dev->pci_dev);
+	if ((error = pci_write_config_word(eni_dev->pci_dev,PCI_COMMAND,
+	    PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
+	    (eni_dev->asic ? PCI_COMMAND_PARITY | PCI_COMMAND_SERR : 0)))) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory+"
+		    "master (0x%02x)\n",dev->number,error);
+		return error;
+	}
+	if ((error = pci_write_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,
+	    END_SWAP_DMA))) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't set endian swap "
+		    "(0x%02x)\n",dev->number,error);
+		return error;
+	}
+	/* determine addresses of internal tables */
+	eni_dev->vci = eni_dev->ram;
+	eni_dev->rx_dma = eni_dev->ram+NR_VCI*16;
+	eni_dev->tx_dma = eni_dev->rx_dma+NR_DMA_RX*8;
+	eni_dev->service = eni_dev->tx_dma+NR_DMA_TX*8;
+	buf = eni_dev->service+NR_SERVICE*4;
+	DPRINTK("vci 0x%lx,rx 0x%lx, tx 0x%lx,srv 0x%lx,buf 0x%lx\n",
+	     eni_dev->vci,eni_dev->rx_dma,eni_dev->tx_dma,
+	     eni_dev->service,buf);
+	spin_lock_init(&eni_dev->lock);
+	tasklet_init(&eni_dev->task,eni_tasklet,(unsigned long) dev);
+	eni_dev->events = 0;
+	/* initialize memory management */
+	buffer_mem = eni_dev->mem - (buf - eni_dev->ram);
+	eni_dev->free_list_size = buffer_mem/MID_MIN_BUF_SIZE/2;
+	eni_dev->free_list = (struct eni_free *) kmalloc(
+	    sizeof(struct eni_free)*(eni_dev->free_list_size+1),GFP_KERNEL);
+	if (!eni_dev->free_list) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): couldn't get free page\n",
+		    dev->number);
+		return -ENOMEM;
+	}
+	eni_dev->free_len = 0;
+	eni_put_free(eni_dev,buf,buffer_mem);
+	memset_io(eni_dev->vci,0,16*NR_VCI); /* clear VCI table */
+	/*
+	 * byte_addr  free (k)
+	 * 0x00000000     512  VCI table
+	 * 0x00004000	  496  RX DMA
+	 * 0x00005000	  492  TX DMA
+	 * 0x00006000	  488  service list
+	 * 0x00007000	  484  buffers
+	 * 0x00080000	    0  end (512kB)
+	 */
+	eni_out(0xffffffff,MID_IE);
+	error = start_tx(dev);
+	if (error) return error;
+	error = start_rx(dev);
+	if (error) return error;
+	error = dev->phy->start(dev);
+	if (error) return error;
+	eni_out(eni_in(MID_MC_S) | (1 << MID_INT_SEL_SHIFT) |
+	    MID_TX_LOCK_MODE | MID_DMA_ENABLE | MID_TX_ENABLE | MID_RX_ENABLE,
+	    MID_MC_S);
+	    /* Tonga uses SBus INTReq1 */
+	(void) eni_in(MID_ISA); /* clear Midway interrupts */
+	return 0;
+}
+
+
+static void eni_close(struct atm_vcc *vcc)
+{
+	DPRINTK(">eni_close\n");
+	if (!ENI_VCC(vcc)) return;
+	clear_bit(ATM_VF_READY,&vcc->flags);
+	close_rx(vcc);
+	close_tx(vcc);
+	DPRINTK("eni_close: done waiting\n");
+	/* deallocate memory */
+	kfree(ENI_VCC(vcc));
+	vcc->dev_data = NULL;
+	clear_bit(ATM_VF_ADDR,&vcc->flags);
+	/*foo();*/
+}
+
+
+static int eni_open(struct atm_vcc *vcc)
+{
+	struct eni_dev *eni_dev;
+	struct eni_vcc *eni_vcc;
+	int error;
+	short vpi = vcc->vpi;
+	int vci = vcc->vci;
+
+	DPRINTK(">eni_open\n");
+	EVENT("eni_open\n",0,0);
+	if (!test_bit(ATM_VF_PARTIAL,&vcc->flags))
+		vcc->dev_data = NULL;
+	eni_dev = ENI_DEV(vcc->dev);
+	if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC)
+		set_bit(ATM_VF_ADDR,&vcc->flags);
+	if (vcc->qos.aal != ATM_AAL0 && vcc->qos.aal != ATM_AAL5)
+		return -EINVAL;
+	DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n",vcc->dev->number,vcc->vpi,
+	    vcc->vci);
+	if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) {
+		eni_vcc = kmalloc(sizeof(struct eni_vcc),GFP_KERNEL);
+		if (!eni_vcc) return -ENOMEM;
+		vcc->dev_data = eni_vcc;
+		eni_vcc->tx = NULL; /* for eni_close after open_rx */
+		if ((error = open_rx_first(vcc))) {
+			eni_close(vcc);
+			return error;
+		}
+		if ((error = open_tx_first(vcc))) {
+			eni_close(vcc);
+			return error;
+		}
+	}
+	if (vci == ATM_VPI_UNSPEC || vpi == ATM_VCI_UNSPEC) return 0;
+	if ((error = open_rx_second(vcc))) {
+		eni_close(vcc);
+		return error;
+	}
+	if ((error = open_tx_second(vcc))) {
+		eni_close(vcc);
+		return error;
+	}
+	set_bit(ATM_VF_READY,&vcc->flags);
+	/* should power down SUNI while !ref_count @@@ */
+	return 0;
+}
+
+
+static int eni_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flgs)
+{
+	struct eni_dev *eni_dev = ENI_DEV(vcc->dev);
+	struct eni_tx *tx = ENI_VCC(vcc)->tx;
+	struct sk_buff *skb;
+	int error,rate,rsv,shp;
+
+	if (qos->txtp.traffic_class == ATM_NONE) return 0;
+	if (tx == eni_dev->ubr) return -EBADFD;
+	rate = atm_pcr_goal(&qos->txtp);
+	if (rate < 0) rate = -rate;
+	rsv = shp = 0;
+	if ((flgs & ATM_MF_DEC_RSV) && rate && rate < tx->reserved) rsv = 1;
+	if ((flgs & ATM_MF_INC_RSV) && (!rate || rate > tx->reserved)) rsv = 1;
+	if ((flgs & ATM_MF_DEC_SHP) && rate && rate < tx->shaping) shp = 1;
+	if ((flgs & ATM_MF_INC_SHP) && (!rate || rate > tx->shaping)) shp = 1;
+	if (!rsv && !shp) return 0;
+	error = reserve_or_set_tx(vcc,&qos->txtp,rsv,shp);
+	if (error) return error;
+	if (shp && !(flgs & ATM_MF_IMMED)) return 0;
+	/*
+	 * Walk through the send buffer and patch the rate information in all
+	 * segmentation buffer descriptors of this VCC.
+	 */
+	tasklet_disable(&eni_dev->task);
+	skb_queue_walk(&eni_dev->tx_queue, skb) {
+		void __iomem *dsc;
+
+		if (ATM_SKB(skb)->vcc != vcc) continue;
+		dsc = tx->send+ENI_PRV_POS(skb)*4;
+		writel((readl(dsc) & ~(MID_SEG_RATE | MID_SEG_PR)) |
+		    (tx->prescaler << MID_SEG_PR_SHIFT) |
+		    (tx->resolution << MID_SEG_RATE_SHIFT), dsc);
+	}
+	tasklet_enable(&eni_dev->task);
+	return 0;
+}
+
+
+static int eni_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	struct eni_dev *eni_dev = ENI_DEV(dev);
+
+	if (cmd == ENI_MEMDUMP) {
+		if (!capable(CAP_NET_ADMIN)) return -EPERM;
+		printk(KERN_WARNING "Please use /proc/atm/" DEV_LABEL ":%d "
+		    "instead of obsolete ioctl ENI_MEMDUMP\n",dev->number);
+		dump(dev);
+		return 0;
+	}
+	if (cmd == ENI_SETMULT) {
+		struct eni_multipliers mult;
+
+		if (!capable(CAP_NET_ADMIN)) return -EPERM;
+		if (copy_from_user(&mult, arg,
+		    sizeof(struct eni_multipliers)))
+			return -EFAULT;
+		if ((mult.tx && mult.tx <= 100) || (mult.rx &&mult.rx <= 100) ||
+		    mult.tx > 65536 || mult.rx > 65536)
+			return -EINVAL;
+		if (mult.tx) eni_dev->tx_mult = mult.tx;
+		if (mult.rx) eni_dev->rx_mult = mult.rx;
+		return 0;
+	}
+	if (cmd == ATM_SETCIRANGE) {
+		struct atm_cirange ci;
+
+		if (copy_from_user(&ci, arg,sizeof(struct atm_cirange)))
+			return -EFAULT;
+		if ((ci.vpi_bits == 0 || ci.vpi_bits == ATM_CI_MAX) &&
+		    (ci.vci_bits == NR_VCI_LD || ci.vpi_bits == ATM_CI_MAX))
+		    return 0;
+		return -EINVAL;
+	}
+	if (!dev->phy->ioctl) return -ENOIOCTLCMD;
+	return dev->phy->ioctl(dev,cmd,arg);
+}
+
+
+static int eni_getsockopt(struct atm_vcc *vcc,int level,int optname,
+    void __user *optval,int optlen)
+{
+	return -EINVAL;
+}
+
+
+static int eni_setsockopt(struct atm_vcc *vcc,int level,int optname,
+    void __user *optval,int optlen)
+{
+	return -EINVAL;
+}
+
+
+static int eni_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+	enum enq_res res;
+
+	DPRINTK(">eni_send\n");
+	if (!ENI_VCC(vcc)->tx) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+	if (!skb) {
+		printk(KERN_CRIT "!skb in eni_send ?\n");
+		if (vcc->pop) vcc->pop(vcc,skb);
+		return -EINVAL;
+	}
+	if (vcc->qos.aal == ATM_AAL0) {
+		if (skb->len != ATM_CELL_SIZE-1) {
+			if (vcc->pop) vcc->pop(vcc,skb);
+			else dev_kfree_skb(skb);
+			return -EINVAL;
+		}
+		*(u32 *) skb->data = htonl(*(u32 *) skb->data);
+	}
+submitted++;
+	ATM_SKB(skb)->vcc = vcc;
+	tasklet_disable(&ENI_DEV(vcc->dev)->task);
+	res = do_tx(skb);
+	tasklet_enable(&ENI_DEV(vcc->dev)->task);
+	if (res == enq_ok) return 0;
+	skb_queue_tail(&ENI_VCC(vcc)->tx->backlog,skb);
+backlogged++;
+	tasklet_schedule(&ENI_DEV(vcc->dev)->task);
+	return 0;
+}
+
+static void eni_phy_put(struct atm_dev *dev,unsigned char value,
+    unsigned long addr)
+{
+	writel(value,ENI_DEV(dev)->phy+addr*4);
+}
+
+
+
+static unsigned char eni_phy_get(struct atm_dev *dev,unsigned long addr)
+{
+	return readl(ENI_DEV(dev)->phy+addr*4);
+}
+
+
+static int eni_proc_read(struct atm_dev *dev,loff_t *pos,char *page)
+{
+	struct hlist_node *node;
+	struct sock *s;
+	static const char *signal[] = { "LOST","unknown","okay" };
+	struct eni_dev *eni_dev = ENI_DEV(dev);
+	struct atm_vcc *vcc;
+	int left,i;
+
+	left = *pos;
+	if (!left)
+		return sprintf(page,DEV_LABEL "(itf %d) signal %s, %dkB, "
+		    "%d cps remaining\n",dev->number,signal[(int) dev->signal],
+		    eni_dev->mem >> 10,eni_dev->tx_bw);
+	if (!--left)
+		return sprintf(page,"%4sBursts: TX"
+#if !defined(CONFIG_ATM_ENI_BURST_TX_16W) && \
+    !defined(CONFIG_ATM_ENI_BURST_TX_8W) && \
+    !defined(CONFIG_ATM_ENI_BURST_TX_4W) && \
+    !defined(CONFIG_ATM_ENI_BURST_TX_2W)
+		    " none"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_16W
+		    " 16W"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_8W
+		    " 8W"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_4W
+		    " 4W"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_TX_2W
+		    " 2W"
+#endif
+		    ", RX"
+#if !defined(CONFIG_ATM_ENI_BURST_RX_16W) && \
+    !defined(CONFIG_ATM_ENI_BURST_RX_8W) && \
+    !defined(CONFIG_ATM_ENI_BURST_RX_4W) && \
+    !defined(CONFIG_ATM_ENI_BURST_RX_2W)
+		    " none"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_16W
+		    " 16W"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_8W
+		    " 8W"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_4W
+		    " 4W"
+#endif
+#ifdef CONFIG_ATM_ENI_BURST_RX_2W
+		    " 2W"
+#endif
+#ifndef CONFIG_ATM_ENI_TUNE_BURST
+		    " (default)"
+#endif
+		    "\n","");
+	if (!--left) 
+		return sprintf(page,"%4sBuffer multipliers: tx %d%%, rx %d%%\n",
+		    "",eni_dev->tx_mult,eni_dev->rx_mult);
+	for (i = 0; i < NR_CHAN; i++) {
+		struct eni_tx *tx = eni_dev->tx+i;
+
+		if (!tx->send) continue;
+		if (!--left) {
+			return sprintf(page,"tx[%d]:    0x%ld-0x%ld "
+			    "(%6ld bytes), rsv %d cps, shp %d cps%s\n",i,
+			    (unsigned long) (tx->send - eni_dev->ram),
+			    tx->send-eni_dev->ram+tx->words*4-1,tx->words*4,
+			    tx->reserved,tx->shaping,
+			    tx == eni_dev->ubr ? " (UBR)" : "");
+		}
+		if (--left) continue;
+		return sprintf(page,"%10sbacklog %u packets\n","",
+		    skb_queue_len(&tx->backlog));
+	}
+	read_lock(&vcc_sklist_lock);
+	for(i = 0; i < VCC_HTABLE_SIZE; ++i) {
+		struct hlist_head *head = &vcc_hash[i];
+
+		sk_for_each(s, node, head) {
+			struct eni_vcc *eni_vcc;
+			int length;
+
+			vcc = atm_sk(s);
+			if (vcc->dev != dev)
+				continue;
+			eni_vcc = ENI_VCC(vcc);
+			if (--left) continue;
+			length = sprintf(page,"vcc %4d: ",vcc->vci);
+			if (eni_vcc->rx) {
+				length += sprintf(page+length,"0x%ld-0x%ld "
+				    "(%6ld bytes)",
+				    (unsigned long) (eni_vcc->recv - eni_dev->ram),
+				    eni_vcc->recv-eni_dev->ram+eni_vcc->words*4-1,
+				    eni_vcc->words*4);
+				if (eni_vcc->tx) length += sprintf(page+length,", ");
+			}
+			if (eni_vcc->tx)
+				length += sprintf(page+length,"tx[%d], txing %d bytes",
+				    eni_vcc->tx->index,eni_vcc->txing);
+			page[length] = '\n';
+			read_unlock(&vcc_sklist_lock);
+			return length+1;
+		}
+	}
+	read_unlock(&vcc_sklist_lock);
+	for (i = 0; i < eni_dev->free_len; i++) {
+		struct eni_free *fe = eni_dev->free_list+i;
+		unsigned long offset;
+
+		if (--left) continue;
+		offset = (unsigned long) eni_dev->ram+eni_dev->base_diff;
+		return sprintf(page,"free      %p-%p (%6d bytes)\n",
+		    fe->start-offset,fe->start-offset+(1 << fe->order)-1,
+		    1 << fe->order);
+	}
+	return 0;
+}
+
+
+static const struct atmdev_ops ops = {
+	.open		= eni_open,
+	.close		= eni_close,
+	.ioctl		= eni_ioctl,
+	.getsockopt	= eni_getsockopt,
+	.setsockopt	= eni_setsockopt,
+	.send		= eni_send,
+	.phy_put	= eni_phy_put,
+	.phy_get	= eni_phy_get,
+	.change_qos	= eni_change_qos,
+	.proc_read	= eni_proc_read
+};
+
+
+static int __devinit eni_init_one(struct pci_dev *pci_dev,
+    const struct pci_device_id *ent)
+{
+	struct atm_dev *dev;
+	struct eni_dev *eni_dev;
+	int error = -ENOMEM;
+
+	DPRINTK("eni_init_one\n");
+
+	if (pci_enable_device(pci_dev)) {
+		error = -EIO;
+		goto out0;
+	}
+
+	eni_dev = (struct eni_dev *) kmalloc(sizeof(struct eni_dev),GFP_KERNEL);
+	if (!eni_dev) goto out0;
+	if (!cpu_zeroes) {
+		cpu_zeroes = pci_alloc_consistent(pci_dev,ENI_ZEROES_SIZE,
+		    &zeroes);
+		if (!cpu_zeroes) goto out1;
+	}
+	dev = atm_dev_register(DEV_LABEL,&ops,-1,NULL);
+	if (!dev) goto out2;
+	pci_set_drvdata(pci_dev, dev);
+	eni_dev->pci_dev = pci_dev;
+	dev->dev_data = eni_dev;
+	eni_dev->asic = ent->driver_data;
+	error = eni_do_init(dev);
+	if (error) goto out3;
+	error = eni_start(dev);
+	if (error) goto out3;
+	eni_dev->more = eni_boards;
+	eni_boards = dev;
+	return 0;
+out3:
+	atm_dev_deregister(dev);
+out2:
+	pci_free_consistent(eni_dev->pci_dev,ENI_ZEROES_SIZE,cpu_zeroes,zeroes);
+	cpu_zeroes = NULL;
+out1:
+	kfree(eni_dev);
+out0:
+	return error;
+}
+
+
+static struct pci_device_id eni_pci_tbl[] = {
+	{ PCI_VENDOR_ID_EF, PCI_DEVICE_ID_EF_ATM_FPGA, PCI_ANY_ID, PCI_ANY_ID,
+	  0, 0, 0 /* FPGA */ },
+	{ PCI_VENDOR_ID_EF, PCI_DEVICE_ID_EF_ATM_ASIC, PCI_ANY_ID, PCI_ANY_ID,
+	  0, 0, 1 /* ASIC */ },
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci,eni_pci_tbl);
+
+
+static void __devexit eni_remove_one(struct pci_dev *pci_dev)
+{
+	/* grrr */
+}
+
+
+static struct pci_driver eni_driver = {
+	.name		= DEV_LABEL,
+	.id_table	= eni_pci_tbl,
+	.probe		= eni_init_one,
+	.remove		= __devexit_p(eni_remove_one),
+};
+
+
+static int __init eni_init(void)
+{
+	struct sk_buff *skb; /* dummy for sizeof */
+
+	if (sizeof(skb->cb) < sizeof(struct eni_skb_prv)) {
+		printk(KERN_ERR "eni_detect: skb->cb is too small (%Zd < %Zd)\n",
+		    sizeof(skb->cb),sizeof(struct eni_skb_prv));
+		return -EIO;
+	}
+	return pci_register_driver(&eni_driver);
+}
+
+
+module_init(eni_init);
+/* @@@ since exit routine not defined, this module can not be unloaded */
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/atm/eni.h b/drivers/atm/eni.h
new file mode 100644
index 0000000..385090c
--- /dev/null
+++ b/drivers/atm/eni.h
@@ -0,0 +1,130 @@
+/* drivers/atm/eni.h - Efficient Networks ENI155P device driver declarations */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+ 
+#ifndef DRIVER_ATM_ENI_H
+#define DRIVER_ATM_ENI_H
+
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/skbuff.h>
+#include <linux/time.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+
+#include "midway.h"
+
+
+#define KERNEL_OFFSET	0xC0000000	/* kernel 0x0 is at phys 0xC0000000 */
+#define DEV_LABEL	"eni"
+
+#define UBR_BUFFER	(128*1024)	/* UBR buffer size */
+
+#define RX_DMA_BUF	  8		/* burst and skip a few things */
+#define TX_DMA_BUF	100		/* should be enough for 64 kB */
+
+#define DEFAULT_RX_MULT	300		/* max_sdu*3 */
+#define DEFAULT_TX_MULT	300		/* max_sdu*3 */
+
+#define ENI_ZEROES_SIZE	  4		/* need that many DMA-able zero bytes */
+
+
+struct eni_free {
+	void __iomem *start;		/* counting in bytes */
+	int order;
+};
+
+struct eni_tx {
+	void __iomem *send;		/* base, 0 if unused */
+	int prescaler;			/* shaping prescaler */
+	int resolution;			/* shaping divider */
+	unsigned long tx_pos;		/* current TX write position */
+	unsigned long words;		/* size of TX queue */
+	int index;			/* TX channel number */
+	int reserved;			/* reserved peak cell rate */
+	int shaping;			/* shaped peak cell rate */
+	struct sk_buff_head backlog;	/* queue of waiting TX buffers */
+};
+
+struct eni_vcc {
+	int (*rx)(struct atm_vcc *vcc);	/* RX function, NULL if none */
+	void __iomem *recv;		/* receive buffer */
+	unsigned long words;		/* its size in words */
+	unsigned long descr;		/* next descriptor (RX) */
+	unsigned long rx_pos;		/* current RX descriptor pos */
+	struct eni_tx *tx;		/* TXer, NULL if none */
+	int rxing;			/* number of pending PDUs */
+	int servicing;			/* number of waiting VCs (0 or 1) */
+	int txing;			/* number of pending TX bytes */
+	struct timeval timestamp;	/* for RX timing */
+	struct atm_vcc *next;		/* next pending RX */
+	struct sk_buff *last;		/* last PDU being DMAed (used to carry
+					   discard information) */
+};
+
+struct eni_dev {
+	/*-------------------------------- spinlock */
+	spinlock_t lock;		/* sync with interrupt */
+	struct tasklet_struct task;	/* tasklet for interrupt work */
+	u32 events;			/* pending events */
+	/*-------------------------------- base pointers into Midway address
+					   space */
+	void __iomem *phy;		/* PHY interface chip registers */
+	void __iomem *reg;		/* register base */
+	void __iomem *ram;		/* RAM base */
+	void __iomem *vci;		/* VCI table */
+	void __iomem *rx_dma;		/* RX DMA queue */
+	void __iomem *tx_dma;		/* TX DMA queue */
+	void __iomem *service;		/* service list */
+	/*-------------------------------- TX part */
+	struct eni_tx tx[NR_CHAN];	/* TX channels */
+	struct eni_tx *ubr;		/* UBR channel */
+	struct sk_buff_head tx_queue;	/* PDUs currently being TX DMAed*/
+	wait_queue_head_t tx_wait;	/* for close */
+	int tx_bw;			/* remaining bandwidth */
+	u32 dma[TX_DMA_BUF*2];		/* DMA request scratch area */
+	int tx_mult;			/* buffer size multiplier (percent) */
+	/*-------------------------------- RX part */
+	u32 serv_read;			/* host service read index */
+	struct atm_vcc *fast,*last_fast;/* queues of VCCs with pending PDUs */
+	struct atm_vcc *slow,*last_slow;
+	struct atm_vcc **rx_map;	/* for fast lookups */
+	struct sk_buff_head rx_queue;	/* PDUs currently being RX-DMAed */
+	wait_queue_head_t rx_wait;	/* for close */
+	int rx_mult;			/* buffer size multiplier (percent) */
+	/*-------------------------------- statistics */
+	unsigned long lost;		/* number of lost cells (RX) */
+	/*-------------------------------- memory management */
+	unsigned long base_diff;	/* virtual-real base address */
+	int free_len;			/* free list length */
+	struct eni_free *free_list;	/* free list */
+	int free_list_size;		/* maximum size of free list */
+	/*-------------------------------- ENI links */
+	struct atm_dev *more;		/* other ENI devices */
+	/*-------------------------------- general information */
+	int mem;			/* RAM on board (in bytes) */
+	int asic;			/* PCI interface type, 0 for FPGA */
+	unsigned int irq;		/* IRQ */
+	struct pci_dev *pci_dev;	/* PCI stuff */
+};
+
+
+#define ENI_DEV(d) ((struct eni_dev *) (d)->dev_data)
+#define ENI_VCC(d) ((struct eni_vcc *) (d)->dev_data)
+
+
+struct eni_skb_prv {
+	struct atm_skb_data _;		/* reserved */
+	unsigned long pos;		/* position of next descriptor */
+	int size;			/* PDU size in reassembly buffer */
+	dma_addr_t paddr;		/* DMA handle */
+};
+
+#define ENI_PRV_SIZE(skb) (((struct eni_skb_prv *) (skb)->cb)->size)
+#define ENI_PRV_POS(skb) (((struct eni_skb_prv *) (skb)->cb)->pos)
+#define ENI_PRV_PADDR(skb) (((struct eni_skb_prv *) (skb)->cb)->paddr)
+
+#endif
diff --git a/drivers/atm/firestream.c b/drivers/atm/firestream.c
new file mode 100644
index 0000000..101f0cc
--- /dev/null
+++ b/drivers/atm/firestream.c
@@ -0,0 +1,2053 @@
+
+/* drivers/atm/firestream.c - FireStream 155 (MB86697) and
+ *                            FireStream  50 (MB86695) device driver 
+ */
+ 
+/* Written & (C) 2000 by R.E.Wolff@BitWizard.nl 
+ * Copied snippets from zatm.c by Werner Almesberger, EPFL LRC/ICA 
+ * and ambassador.c Copyright (C) 1995-1999  Madge Networks Ltd 
+ */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+  system and in the file COPYING in the Linux kernel source.
+*/
+
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/ioport.h> /* for request_region */
+#include <linux/uio.h>
+#include <linux/init.h>
+#include <linux/capability.h>
+#include <linux/bitops.h>
+#include <asm/byteorder.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <linux/wait.h>
+
+#include "firestream.h"
+
+static int loopback = 0;
+static int num=0x5a;
+
+/* According to measurements (but they look suspicious to me!) done in
+ * '97, 37% of the packets are one cell in size. So it pays to have
+ * buffers allocated at that size. A large jump in percentage of
+ * packets occurs at packets around 536 bytes in length. So it also
+ * pays to have those pre-allocated. Unfortunately, we can't fully
+ * take advantage of this as the majority of the packets is likely to
+ * be TCP/IP (As where obviously the measurement comes from) There the
+ * link would be opened with say a 1500 byte MTU, and we can't handle
+ * smaller buffers more efficiently than the larger ones. -- REW
+ */
+
+/* Due to the way Linux memory management works, specifying "576" as
+ * an allocation size here isn't going to help. They are allocated
+ * from 1024-byte regions anyway. With the size of the sk_buffs (quite
+ * large), it doesn't pay to allocate the smallest size (64) -- REW */
+
+/* This is all guesswork. Hard numbers to back this up or disprove this, 
+ * are appreciated. -- REW */
+
+/* The last entry should be about 64k. However, the "buffer size" is
+ * passed to the chip in a 16 bit field. I don't know how "65536"
+ * would be interpreted. -- REW */
+
+#define NP FS_NR_FREE_POOLS
+static int rx_buf_sizes[NP]  = {128,  256,  512, 1024, 2048, 4096, 16384, 65520};
+/* log2:                 7     8     9    10    11    12    14     16 */
+
+#if 0
+static int rx_pool_sizes[NP] = {1024, 1024, 512, 256,  128,  64,   32,    32};
+#else
+/* debug */
+static int rx_pool_sizes[NP] = {128,  128,  128, 64,   64,   64,   32,    32};
+#endif
+/* log2:                 10    10    9    8     7     6     5      5  */
+/* sumlog2:              17    18    18   18    18    18    19     21 */
+/* mem allocated:        128k  256k  256k 256k  256k  256k  512k   2M */
+/* tot mem: almost 4M */
+
+/* NP is shorter, so that it fits on a single line. */
+#undef NP
+
+
+/* Small hardware gotcha:
+
+   The FS50 CAM (VP/VC match registers) always take the lowest channel
+   number that matches. This is not a problem.
+
+   However, they also ignore whether the channel is enabled or
+   not. This means that if you allocate channel 0 to 1.2 and then
+   channel 1 to 0.0, then disabeling channel 0 and writing 0 to the
+   match channel for channel 0 will "steal" the traffic from channel
+   1, even if you correctly disable channel 0.
+
+   Workaround: 
+
+   - When disabling channels, write an invalid VP/VC value to the
+   match register. (We use 0xffffffff, which in the worst case 
+   matches VP/VC = <maxVP>/<maxVC>, but I expect it not to match
+   anything as some "when not in use, program to 0" bits are now
+   programmed to 1...)
+
+   - Don't initialize the match registers to 0, as 0.0 is a valid
+   channel.
+*/
+
+
+/* Optimization hints and tips.
+
+   The FireStream chips are very capable of reducing the amount of
+   "interrupt-traffic" for the CPU. This driver requests an interrupt on EVERY
+   action. You could try to minimize this a bit. 
+
+   Besides that, the userspace->kernel copy and the PCI bus are the
+   performance limiting issues for this driver.
+
+   You could queue up a bunch of outgoing packets without telling the
+   FireStream. I'm not sure that's going to win you much though. The
+   Linux layer won't tell us in advance when it's not going to give us
+   any more packets in a while. So this is tricky to implement right without
+   introducing extra delays. 
+  
+   -- REW
+ */
+
+
+
+
+/* The strings that define what the RX queue entry is all about. */
+/* Fujitsu: Please tell me which ones can have a pointer to a 
+   freepool descriptor! */
+static char *res_strings[] = {
+	"RX OK: streaming not EOP", 
+	"RX OK: streaming EOP", 
+	"RX OK: Single buffer packet", 
+	"RX OK: packet mode", 
+	"RX OK: F4 OAM (end to end)", 
+	"RX OK: F4 OAM (Segment)", 
+	"RX OK: F5 OAM (end to end)", 
+	"RX OK: F5 OAM (Segment)", 
+	"RX OK: RM cell", 
+	"RX OK: TRANSP cell", 
+	"RX OK: TRANSPC cell", 
+	"Unmatched cell", 
+	"reserved 12", 
+	"reserved 13", 
+	"reserved 14", 
+	"Unrecognized cell", 
+	"reserved 16", 
+	"reassemby abort: AAL5 abort", 
+	"packet purged", 
+	"packet ageing timeout", 
+	"channel ageing timeout", 
+	"calculated lenght error", 
+	"programmed lenght limit error", 
+	"aal5 crc32 error", 
+	"oam transp or transpc crc10 error", 
+	"reserved 25", 
+	"reserved 26", 
+	"reserved 27", 
+	"reserved 28", 
+	"reserved 29", 
+	"reserved 30", 
+	"reassembly abort: no buffers", 
+	"receive buffer overflow", 
+	"change in GFC", 
+	"receive buffer full", 
+	"low priority discard - no receive descriptor", 
+	"low priority discard - missing end of packet", 
+	"reserved 41", 
+	"reserved 42", 
+	"reserved 43", 
+	"reserved 44", 
+	"reserved 45", 
+	"reserved 46", 
+	"reserved 47", 
+	"reserved 48", 
+	"reserved 49", 
+	"reserved 50", 
+	"reserved 51", 
+	"reserved 52", 
+	"reserved 53", 
+	"reserved 54", 
+	"reserved 55", 
+	"reserved 56", 
+	"reserved 57", 
+	"reserved 58", 
+	"reserved 59", 
+	"reserved 60", 
+	"reserved 61", 
+	"reserved 62", 
+	"reserved 63", 
+};  
+
+static char *irq_bitname[] = {
+	"LPCO",
+	"DPCO",
+	"RBRQ0_W",
+	"RBRQ1_W",
+	"RBRQ2_W",
+	"RBRQ3_W",
+	"RBRQ0_NF",
+	"RBRQ1_NF",
+	"RBRQ2_NF",
+	"RBRQ3_NF",
+	"BFP_SC",
+	"INIT",
+	"INIT_ERR",
+	"USCEO",
+	"UPEC0",
+	"VPFCO",
+	"CRCCO",
+	"HECO",
+	"TBRQ_W",
+	"TBRQ_NF",
+	"CTPQ_E",
+	"GFC_C0",
+	"PCI_FTL",
+	"CSQ_W",
+	"CSQ_NF",
+	"EXT_INT",
+	"RXDMA_S"
+};
+
+
+#define PHY_EOF -1
+#define PHY_CLEARALL -2
+
+struct reginit_item {
+	int reg, val;
+};
+
+
+static struct reginit_item PHY_NTC_INIT[] __devinitdata = {
+	{ PHY_CLEARALL, 0x40 }, 
+	{ 0x12,  0x0001 },
+	{ 0x13,  0x7605 },
+	{ 0x1A,  0x0001 },
+	{ 0x1B,  0x0005 },
+	{ 0x38,  0x0003 },
+	{ 0x39,  0x0006 },   /* changed here to make loopback */
+	{ 0x01,  0x5262 },
+	{ 0x15,  0x0213 },
+	{ 0x00,  0x0003 },
+	{ PHY_EOF, 0},    /* -1 signals end of list */
+};
+
+
+/* Safetyfeature: If the card interrupts more than this number of times
+   in a jiffy (1/100th of a second) then we just disable the interrupt and
+   print a message. This prevents the system from hanging. 
+
+   150000 packets per second is close to the limit a PC is going to have
+   anyway. We therefore have to disable this for production. -- REW */
+#undef IRQ_RATE_LIMIT // 100
+
+/* Interrupts work now. Unlike serial cards, ATM cards don't work all
+   that great without interrupts. -- REW */
+#undef FS_POLL_FREQ // 100
+
+/* 
+   This driver can spew a whole lot of debugging output at you. If you
+   need maximum performance, you should disable the DEBUG define. To
+   aid in debugging in the field, I'm leaving the compile-time debug
+   features enabled, and disable them "runtime". That allows me to
+   instruct people with problems to enable debugging without requiring
+   them to recompile... -- REW
+*/
+#define DEBUG
+
+#ifdef DEBUG
+#define fs_dprintk(f, str...) if (fs_debug & f) printk (str)
+#else
+#define fs_dprintk(f, str...) /* nothing */
+#endif
+
+
+static int fs_keystream = 0;
+
+#ifdef DEBUG
+/* I didn't forget to set this to zero before shipping. Hit me with a stick 
+   if you get this with the debug default not set to zero again. -- REW */
+static int fs_debug = 0;
+#else
+#define fs_debug 0
+#endif
+
+#ifdef MODULE
+#ifdef DEBUG 
+module_param(fs_debug, int, 0644);
+#endif
+module_param(loopback, int, 0);
+module_param(num, int, 0);
+module_param(fs_keystream, int, 0);
+/* XXX Add rx_buf_sizes, and rx_pool_sizes As per request Amar. -- REW */
+#endif
+
+
+#define FS_DEBUG_FLOW    0x00000001
+#define FS_DEBUG_OPEN    0x00000002
+#define FS_DEBUG_QUEUE   0x00000004
+#define FS_DEBUG_IRQ     0x00000008
+#define FS_DEBUG_INIT    0x00000010
+#define FS_DEBUG_SEND    0x00000020
+#define FS_DEBUG_PHY     0x00000040
+#define FS_DEBUG_CLEANUP 0x00000080
+#define FS_DEBUG_QOS     0x00000100
+#define FS_DEBUG_TXQ     0x00000200
+#define FS_DEBUG_ALLOC   0x00000400
+#define FS_DEBUG_TXMEM   0x00000800
+#define FS_DEBUG_QSIZE   0x00001000
+
+
+#define func_enter() fs_dprintk (FS_DEBUG_FLOW, "fs: enter %s\n", __FUNCTION__)
+#define func_exit()  fs_dprintk (FS_DEBUG_FLOW, "fs: exit  %s\n", __FUNCTION__)
+
+
+static struct fs_dev *fs_boards = NULL;
+
+#ifdef DEBUG
+
+static void my_hd (void *addr, int len)
+{
+	int j, ch;
+	unsigned char *ptr = addr;
+
+	while (len > 0) {
+		printk ("%p ", ptr);
+		for (j=0;j < ((len < 16)?len:16);j++) {
+			printk ("%02x %s", ptr[j], (j==7)?" ":"");
+		}
+		for (  ;j < 16;j++) {
+			printk ("   %s", (j==7)?" ":"");
+		}
+		for (j=0;j < ((len < 16)?len:16);j++) {
+			ch = ptr[j];
+			printk ("%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+		}
+		printk ("\n");
+		ptr += 16;
+		len -= 16;
+	}
+}
+#else /* DEBUG */
+static void my_hd (void *addr, int len){}
+#endif /* DEBUG */
+
+/********** free an skb (as per ATM device driver documentation) **********/
+
+/* Hmm. If this is ATM specific, why isn't there an ATM routine for this?
+ * I copied it over from the ambassador driver. -- REW */
+
+static inline void fs_kfree_skb (struct sk_buff * skb) 
+{
+	if (ATM_SKB(skb)->vcc->pop)
+		ATM_SKB(skb)->vcc->pop (ATM_SKB(skb)->vcc, skb);
+	else
+		dev_kfree_skb_any (skb);
+}
+
+
+
+
+/* It seems the ATM forum recommends this horribly complicated 16bit
+ * floating point format. Turns out the Ambassador uses the exact same
+ * encoding. I just copied it over. If Mitch agrees, I'll move it over
+ * to the atm_misc file or something like that. (and remove it from 
+ * here and the ambassador driver) -- REW
+ */
+
+/* The good thing about this format is that it is monotonic. So, 
+   a conversion routine need not be very complicated. To be able to
+   round "nearest" we need to take along a few extra bits. Lets
+   put these after 16 bits, so that we can just return the top 16
+   bits of the 32bit number as the result:
+
+   int mr (unsigned int rate, int r) 
+     {
+     int e = 16+9;
+     static int round[4]={0, 0, 0xffff, 0x8000};
+     if (!rate) return 0;
+     while (rate & 0xfc000000) {
+       rate >>= 1;
+       e++;
+     }
+     while (! (rate & 0xfe000000)) {
+       rate <<= 1;
+       e--;
+     }
+
+// Now the mantissa is in positions bit 16-25. Excepf for the "hidden 1" that's in bit 26.
+     rate &= ~0x02000000;
+// Next add in the exponent
+     rate |= e << (16+9);
+// And perform the rounding:
+     return (rate + round[r]) >> 16;
+   }
+
+   14 lines-of-code. Compare that with the 120 that the Ambassador
+   guys needed. (would be 8 lines shorter if I'd try to really reduce
+   the number of lines:
+
+   int mr (unsigned int rate, int r) 
+   {
+     int e = 16+9;
+     static int round[4]={0, 0, 0xffff, 0x8000};
+     if (!rate) return 0;
+     for (;  rate & 0xfc000000 ;rate >>= 1, e++);
+     for (;!(rate & 0xfe000000);rate <<= 1, e--);
+     return ((rate & ~0x02000000) | (e << (16+9)) + round[r]) >> 16;
+   }
+
+   Exercise for the reader: Remove one more line-of-code, without
+   cheating. (Just joining two lines is cheating). (I know it's
+   possible, don't think you've beat me if you found it... If you
+   manage to lose two lines or more, keep me updated! ;-)
+
+   -- REW */
+
+
+#define ROUND_UP      1
+#define ROUND_DOWN    2
+#define ROUND_NEAREST 3
+/********** make rate (not quite as much fun as Horizon) **********/
+
+static unsigned int make_rate (unsigned int rate, int r,
+			       u16 * bits, unsigned int * actual) 
+{
+	unsigned char exp = -1; /* hush gcc */
+	unsigned int man = -1;  /* hush gcc */
+  
+	fs_dprintk (FS_DEBUG_QOS, "make_rate %u", rate);
+  
+	/* rates in cells per second, ITU format (nasty 16-bit floating-point)
+	   given 5-bit e and 9-bit m:
+	   rate = EITHER (1+m/2^9)*2^e    OR 0
+	   bits = EITHER 1<<14 | e<<9 | m OR 0
+	   (bit 15 is "reserved", bit 14 "non-zero")
+	   smallest rate is 0 (special representation)
+	   largest rate is (1+511/512)*2^31 = 4290772992 (< 2^32-1)
+	   smallest non-zero rate is (1+0/512)*2^0 = 1 (> 0)
+	   simple algorithm:
+	   find position of top bit, this gives e
+	   remove top bit and shift (rounding if feeling clever) by 9-e
+	*/
+	/* Ambassador ucode bug: please don't set bit 14! so 0 rate not
+	   representable. // This should move into the ambassador driver
+	   when properly merged. -- REW */
+  
+	if (rate > 0xffc00000U) {
+		/* larger than largest representable rate */
+    
+		if (r == ROUND_UP) {
+			return -EINVAL;
+		} else {
+			exp = 31;
+			man = 511;
+		}
+    
+	} else if (rate) {
+		/* representable rate */
+    
+		exp = 31;
+		man = rate;
+    
+		/* invariant: rate = man*2^(exp-31) */
+		while (!(man & (1<<31))) {
+			exp = exp - 1;
+			man = man<<1;
+		}
+    
+		/* man has top bit set
+		   rate = (2^31+(man-2^31))*2^(exp-31)
+		   rate = (1+(man-2^31)/2^31)*2^exp 
+		*/
+		man = man<<1;
+		man &= 0xffffffffU; /* a nop on 32-bit systems */
+		/* rate = (1+man/2^32)*2^exp
+    
+		   exp is in the range 0 to 31, man is in the range 0 to 2^32-1
+		   time to lose significance... we want m in the range 0 to 2^9-1
+		   rounding presents a minor problem... we first decide which way
+		   we are rounding (based on given rounding direction and possibly
+		   the bits of the mantissa that are to be discarded).
+		*/
+
+		switch (r) {
+		case ROUND_DOWN: {
+			/* just truncate */
+			man = man>>(32-9);
+			break;
+		}
+		case ROUND_UP: {
+			/* check all bits that we are discarding */
+			if (man & (-1>>9)) {
+				man = (man>>(32-9)) + 1;
+				if (man == (1<<9)) {
+					/* no need to check for round up outside of range */
+					man = 0;
+					exp += 1;
+				}
+			} else {
+				man = (man>>(32-9));
+			}
+			break;
+		}
+		case ROUND_NEAREST: {
+			/* check msb that we are discarding */
+			if (man & (1<<(32-9-1))) {
+				man = (man>>(32-9)) + 1;
+				if (man == (1<<9)) {
+					/* no need to check for round up outside of range */
+					man = 0;
+					exp += 1;
+				}
+			} else {
+				man = (man>>(32-9));
+			}
+			break;
+		}
+		}
+    
+	} else {
+		/* zero rate - not representable */
+    
+		if (r == ROUND_DOWN) {
+			return -EINVAL;
+		} else {
+			exp = 0;
+			man = 0;
+		}
+	}
+  
+	fs_dprintk (FS_DEBUG_QOS, "rate: man=%u, exp=%hu", man, exp);
+  
+	if (bits)
+		*bits = /* (1<<14) | */ (exp<<9) | man;
+  
+	if (actual)
+		*actual = (exp >= 9)
+			? (1 << exp) + (man << (exp-9))
+			: (1 << exp) + ((man + (1<<(9-exp-1))) >> (9-exp));
+  
+	return 0;
+}
+
+
+
+
+/* FireStream access routines */
+/* For DEEP-DOWN debugging these can be rigged to intercept accesses to
+   certain registers or to just log all accesses. */
+
+static inline void write_fs (struct fs_dev *dev, int offset, u32 val)
+{
+	writel (val, dev->base + offset);
+}
+
+
+static inline u32  read_fs (struct fs_dev *dev, int offset)
+{
+	return readl (dev->base + offset);
+}
+
+
+
+static inline struct FS_QENTRY *get_qentry (struct fs_dev *dev, struct queue *q)
+{
+	return bus_to_virt (read_fs (dev, Q_WP(q->offset)) & Q_ADDR_MASK);
+}
+
+
+static void submit_qentry (struct fs_dev *dev, struct queue *q, struct FS_QENTRY *qe)
+{
+	u32 wp;
+	struct FS_QENTRY *cqe;
+
+	/* XXX Sanity check: the write pointer can be checked to be 
+	   still the same as the value passed as qe... -- REW */
+	/*  udelay (5); */
+	while ((wp = read_fs (dev, Q_WP (q->offset))) & Q_FULL) {
+		fs_dprintk (FS_DEBUG_TXQ, "Found queue at %x full. Waiting.\n", 
+			    q->offset);
+		schedule ();
+	}
+
+	wp &= ~0xf;
+	cqe = bus_to_virt (wp);
+	if (qe != cqe) {
+		fs_dprintk (FS_DEBUG_TXQ, "q mismatch! %p %p\n", qe, cqe);
+	}
+
+	write_fs (dev, Q_WP(q->offset), Q_INCWRAP);
+
+	{
+		static int c;
+		if (!(c++ % 100))
+			{
+				int rp, wp;
+				rp =  read_fs (dev, Q_RP(q->offset));
+				wp =  read_fs (dev, Q_WP(q->offset));
+				fs_dprintk (FS_DEBUG_TXQ, "q at %d: %x-%x: %x entries.\n", 
+					    q->offset, rp, wp, wp-rp);
+			}
+	}
+}
+
+#ifdef DEBUG_EXTRA
+static struct FS_QENTRY pq[60];
+static int qp;
+
+static struct FS_BPENTRY dq[60];
+static int qd;
+static void *da[60];
+#endif 
+
+static void submit_queue (struct fs_dev *dev, struct queue *q, 
+			  u32 cmd, u32 p1, u32 p2, u32 p3)
+{
+	struct FS_QENTRY *qe;
+
+	qe = get_qentry (dev, q);
+	qe->cmd = cmd;
+	qe->p0 = p1;
+	qe->p1 = p2;
+	qe->p2 = p3;
+	submit_qentry (dev,  q, qe);
+
+#ifdef DEBUG_EXTRA
+	pq[qp].cmd = cmd;
+	pq[qp].p0 = p1;
+	pq[qp].p1 = p2;
+	pq[qp].p2 = p3;
+	qp++;
+	if (qp >= 60) qp = 0;
+#endif
+}
+
+/* Test the "other" way one day... -- REW */
+#if 1
+#define submit_command submit_queue
+#else
+
+static void submit_command (struct fs_dev *dev, struct queue *q, 
+			    u32 cmd, u32 p1, u32 p2, u32 p3)
+{
+	write_fs (dev, CMDR0, cmd);
+	write_fs (dev, CMDR1, p1);
+	write_fs (dev, CMDR2, p2);
+	write_fs (dev, CMDR3, p3);
+}
+#endif
+
+
+
+static void process_return_queue (struct fs_dev *dev, struct queue *q)
+{
+	long rq;
+	struct FS_QENTRY *qe;
+	void *tc;
+  
+	while (!((rq = read_fs (dev, Q_RP(q->offset))) & Q_EMPTY)) {
+		fs_dprintk (FS_DEBUG_QUEUE, "reaping return queue entry at %lx\n", rq); 
+		qe = bus_to_virt (rq);
+    
+		fs_dprintk (FS_DEBUG_QUEUE, "queue entry: %08x %08x %08x %08x. (%d)\n", 
+			    qe->cmd, qe->p0, qe->p1, qe->p2, STATUS_CODE (qe));
+
+		switch (STATUS_CODE (qe)) {
+		case 5:
+			tc = bus_to_virt (qe->p0);
+			fs_dprintk (FS_DEBUG_ALLOC, "Free tc: %p\n", tc);
+			kfree (tc);
+			break;
+		}
+    
+		write_fs (dev, Q_RP(q->offset), Q_INCWRAP);
+	}
+}
+
+
+static void process_txdone_queue (struct fs_dev *dev, struct queue *q)
+{
+	long rq;
+	long tmp;
+	struct FS_QENTRY *qe;
+	struct sk_buff *skb;
+	struct FS_BPENTRY *td;
+
+	while (!((rq = read_fs (dev, Q_RP(q->offset))) & Q_EMPTY)) {
+		fs_dprintk (FS_DEBUG_QUEUE, "reaping txdone entry at %lx\n", rq); 
+		qe = bus_to_virt (rq);
+    
+		fs_dprintk (FS_DEBUG_QUEUE, "queue entry: %08x %08x %08x %08x: %d\n", 
+			    qe->cmd, qe->p0, qe->p1, qe->p2, STATUS_CODE (qe));
+
+		if (STATUS_CODE (qe) != 2)
+			fs_dprintk (FS_DEBUG_TXMEM, "queue entry: %08x %08x %08x %08x: %d\n", 
+				    qe->cmd, qe->p0, qe->p1, qe->p2, STATUS_CODE (qe));
+
+
+		switch (STATUS_CODE (qe)) {
+		case 0x01: /* This is for AAL0 where we put the chip in streaming mode */
+			/* Fall through */
+		case 0x02:
+			/* Process a real txdone entry. */
+			tmp = qe->p0;
+			if (tmp & 0x0f)
+				printk (KERN_WARNING "td not aligned: %ld\n", tmp);
+			tmp &= ~0x0f;
+			td = bus_to_virt (tmp);
+
+			fs_dprintk (FS_DEBUG_QUEUE, "Pool entry: %08x %08x %08x %08x %p.\n", 
+				    td->flags, td->next, td->bsa, td->aal_bufsize, td->skb );
+      
+			skb = td->skb;
+			if (skb == FS_VCC (ATM_SKB(skb)->vcc)->last_skb) {
+				wake_up_interruptible (& FS_VCC (ATM_SKB(skb)->vcc)->close_wait);
+				FS_VCC (ATM_SKB(skb)->vcc)->last_skb = NULL;
+			}
+			td->dev->ntxpckts--;
+
+			{
+				static int c=0;
+	
+				if (!(c++ % 100)) {
+					fs_dprintk (FS_DEBUG_QSIZE, "[%d]", td->dev->ntxpckts);
+				}
+			}
+
+			atomic_inc(&ATM_SKB(skb)->vcc->stats->tx);
+
+			fs_dprintk (FS_DEBUG_TXMEM, "i");
+			fs_dprintk (FS_DEBUG_ALLOC, "Free t-skb: %p\n", skb);
+			fs_kfree_skb (skb);
+
+			fs_dprintk (FS_DEBUG_ALLOC, "Free trans-d: %p\n", td); 
+			memset (td, 0x12, sizeof (struct FS_BPENTRY));
+			kfree (td);
+			break;
+		default:
+			/* Here we get the tx purge inhibit command ... */
+			/* Action, I believe, is "don't do anything". -- REW */
+			;
+		}
+    
+		write_fs (dev, Q_RP(q->offset), Q_INCWRAP);
+	}
+}
+
+
+static void process_incoming (struct fs_dev *dev, struct queue *q)
+{
+	long rq;
+	struct FS_QENTRY *qe;
+	struct FS_BPENTRY *pe;    
+	struct sk_buff *skb;
+	unsigned int channo;
+	struct atm_vcc *atm_vcc;
+
+	while (!((rq = read_fs (dev, Q_RP(q->offset))) & Q_EMPTY)) {
+		fs_dprintk (FS_DEBUG_QUEUE, "reaping incoming queue entry at %lx\n", rq); 
+		qe = bus_to_virt (rq);
+    
+		fs_dprintk (FS_DEBUG_QUEUE, "queue entry: %08x %08x %08x %08x.  ", 
+			    qe->cmd, qe->p0, qe->p1, qe->p2);
+
+		fs_dprintk (FS_DEBUG_QUEUE, "-> %x: %s\n", 
+			    STATUS_CODE (qe), 
+			    res_strings[STATUS_CODE(qe)]);
+
+		pe = bus_to_virt (qe->p0);
+		fs_dprintk (FS_DEBUG_QUEUE, "Pool entry: %08x %08x %08x %08x %p %p.\n", 
+			    pe->flags, pe->next, pe->bsa, pe->aal_bufsize, 
+			    pe->skb, pe->fp);
+      
+		channo = qe->cmd & 0xffff;
+
+		if (channo < dev->nchannels)
+			atm_vcc = dev->atm_vccs[channo];
+		else
+			atm_vcc = NULL;
+
+		/* Single buffer packet */
+		switch (STATUS_CODE (qe)) {
+		case 0x1:
+			/* Fall through for streaming mode */
+		case 0x2:/* Packet received OK.... */
+			if (atm_vcc) {
+				skb = pe->skb;
+				pe->fp->n--;
+#if 0
+				fs_dprintk (FS_DEBUG_QUEUE, "Got skb: %p\n", skb);
+				if (FS_DEBUG_QUEUE & fs_debug) my_hd (bus_to_virt (pe->bsa), 0x20);
+#endif
+				skb_put (skb, qe->p1 & 0xffff); 
+				ATM_SKB(skb)->vcc = atm_vcc;
+				atomic_inc(&atm_vcc->stats->rx);
+				do_gettimeofday(&skb->stamp);
+				fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p (pushed)\n", skb);
+				atm_vcc->push (atm_vcc, skb);
+				fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", pe);
+				kfree (pe);
+			} else {
+				printk (KERN_ERR "Got a receive on a non-open channel %d.\n", channo);
+			}
+			break;
+		case 0x17:/* AAL 5 CRC32 error. IFF the length field is nonzero, a buffer
+			     has been consumed and needs to be processed. -- REW */
+			if (qe->p1 & 0xffff) {
+				pe = bus_to_virt (qe->p0);
+				pe->fp->n--;
+				fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", pe->skb);
+				dev_kfree_skb_any (pe->skb);
+				fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", pe);
+				kfree (pe);
+			}
+			if (atm_vcc)
+				atomic_inc(&atm_vcc->stats->rx_drop);
+			break;
+		case 0x1f: /*  Reassembly abort: no buffers. */
+			/* Silently increment error counter. */
+			if (atm_vcc)
+				atomic_inc(&atm_vcc->stats->rx_drop);
+			break;
+		default: /* Hmm. Haven't written the code to handle the others yet... -- REW */
+			printk (KERN_WARNING "Don't know what to do with RX status %x: %s.\n", 
+				STATUS_CODE(qe), res_strings[STATUS_CODE (qe)]);
+		}
+		write_fs (dev, Q_RP(q->offset), Q_INCWRAP);
+	}
+}
+
+
+
+#define DO_DIRECTION(tp) ((tp)->traffic_class != ATM_NONE)
+
+static int fs_open(struct atm_vcc *atm_vcc)
+{
+	struct fs_dev *dev;
+	struct fs_vcc *vcc;
+	struct fs_transmit_config *tc;
+	struct atm_trafprm * txtp;
+	struct atm_trafprm * rxtp;
+	/*  struct fs_receive_config *rc;*/
+	/*  struct FS_QENTRY *qe; */
+	int error;
+	int bfp;
+	int to;
+	unsigned short tmc0;
+	short vpi = atm_vcc->vpi;
+	int vci = atm_vcc->vci;
+
+	func_enter ();
+
+	dev = FS_DEV(atm_vcc->dev);
+	fs_dprintk (FS_DEBUG_OPEN, "fs: open on dev: %p, vcc at %p\n", 
+		    dev, atm_vcc);
+
+	if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC)
+		set_bit(ATM_VF_ADDR, &atm_vcc->flags);
+
+	if ((atm_vcc->qos.aal != ATM_AAL5) &&
+	    (atm_vcc->qos.aal != ATM_AAL2))
+	  return -EINVAL; /* XXX AAL0 */
+
+	fs_dprintk (FS_DEBUG_OPEN, "fs: (itf %d): open %d.%d\n", 
+		    atm_vcc->dev->number, atm_vcc->vpi, atm_vcc->vci);	
+
+	/* XXX handle qos parameters (rate limiting) ? */
+
+	vcc = kmalloc(sizeof(struct fs_vcc), GFP_KERNEL);
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc VCC: %p(%Zd)\n", vcc, sizeof(struct fs_vcc));
+	if (!vcc) {
+		clear_bit(ATM_VF_ADDR, &atm_vcc->flags);
+		return -ENOMEM;
+	}
+  
+	atm_vcc->dev_data = vcc;
+	vcc->last_skb = NULL;
+
+	init_waitqueue_head (&vcc->close_wait);
+
+	txtp = &atm_vcc->qos.txtp;
+	rxtp = &atm_vcc->qos.rxtp;
+
+	if (!test_bit(ATM_VF_PARTIAL, &atm_vcc->flags)) {
+		if (IS_FS50(dev)) {
+			/* Increment the channel numer: take a free one next time.  */
+			for (to=33;to;to--, dev->channo++) {
+				/* We only have 32 channels */
+				if (dev->channo >= 32)
+					dev->channo = 0;
+				/* If we need to do RX, AND the RX is inuse, try the next */
+				if (DO_DIRECTION(rxtp) && dev->atm_vccs[dev->channo])
+					continue;
+				/* If we need to do TX, AND the TX is inuse, try the next */
+				if (DO_DIRECTION(txtp) && test_bit (dev->channo, dev->tx_inuse))
+					continue;
+				/* Ok, both are free! (or not needed) */
+				break;
+			}
+			if (!to) {
+				printk ("No more free channels for FS50..\n");
+				return -EBUSY;
+			}
+			vcc->channo = dev->channo;
+			dev->channo &= dev->channel_mask;
+      
+		} else {
+			vcc->channo = (vpi << FS155_VCI_BITS) | (vci);
+			if (((DO_DIRECTION(rxtp) && dev->atm_vccs[vcc->channo])) ||
+			    ( DO_DIRECTION(txtp) && test_bit (vcc->channo, dev->tx_inuse))) {
+				printk ("Channel is in use for FS155.\n");
+				return -EBUSY;
+			}
+		}
+		fs_dprintk (FS_DEBUG_OPEN, "OK. Allocated channel %x(%d).\n", 
+			    vcc->channo, vcc->channo);
+	}
+
+	if (DO_DIRECTION (txtp)) {
+		tc = kmalloc (sizeof (struct fs_transmit_config), GFP_KERNEL);
+		fs_dprintk (FS_DEBUG_ALLOC, "Alloc tc: %p(%Zd)\n",
+			    tc, sizeof (struct fs_transmit_config));
+		if (!tc) {
+			fs_dprintk (FS_DEBUG_OPEN, "fs: can't alloc transmit_config.\n");
+			return -ENOMEM;
+		}
+
+		/* Allocate the "open" entry from the high priority txq. This makes
+		   it most likely that the chip will notice it. It also prevents us
+		   from having to wait for completion. On the other hand, we may
+		   need to wait for completion anyway, to see if it completed
+		   succesfully. */
+
+		switch (atm_vcc->qos.aal) {
+		case ATM_AAL2:
+		case ATM_AAL0:
+		  tc->flags = 0
+		    | TC_FLAGS_TRANSPARENT_PAYLOAD
+		    | TC_FLAGS_PACKET
+		    | (1 << 28)
+		    | TC_FLAGS_TYPE_UBR /* XXX Change to VBR -- PVDL */
+		    | TC_FLAGS_CAL0;
+		  break;
+		case ATM_AAL5:
+		  tc->flags = 0
+			| TC_FLAGS_AAL5
+			| TC_FLAGS_PACKET  /* ??? */
+			| TC_FLAGS_TYPE_CBR
+			| TC_FLAGS_CAL0;
+		  break;
+		default:
+			printk ("Unknown aal: %d\n", atm_vcc->qos.aal);
+			tc->flags = 0;
+		}
+		/* Docs are vague about this atm_hdr field. By the way, the FS
+		 * chip makes odd errors if lower bits are set.... -- REW */
+		tc->atm_hdr =  (vpi << 20) | (vci << 4); 
+		{
+			int pcr = atm_pcr_goal (txtp);
+
+			fs_dprintk (FS_DEBUG_OPEN, "pcr = %d.\n", pcr);
+
+			/* XXX Hmm. officially we're only allowed to do this if rounding 
+			   is round_down -- REW */
+			if (IS_FS50(dev)) {
+				if (pcr > 51840000/53/8)  pcr = 51840000/53/8;
+			} else {
+				if (pcr > 155520000/53/8) pcr = 155520000/53/8;
+			}
+			if (!pcr) {
+				/* no rate cap */
+				tmc0 = IS_FS50(dev)?0x61BE:0x64c9; /* Just copied over the bits from Fujitsu -- REW */
+			} else {
+				int r;
+				if (pcr < 0) {
+					r = ROUND_DOWN;
+					pcr = -pcr;
+				} else {
+					r = ROUND_UP;
+				}
+				error = make_rate (pcr, r, &tmc0, NULL);
+			}
+			fs_dprintk (FS_DEBUG_OPEN, "pcr = %d.\n", pcr);
+		}
+      
+		tc->TMC[0] = tmc0 | 0x4000;
+		tc->TMC[1] = 0; /* Unused */
+		tc->TMC[2] = 0; /* Unused */
+		tc->TMC[3] = 0; /* Unused */
+    
+		tc->spec = 0;    /* UTOPIA address, UDF, HEC: Unused -> 0 */
+		tc->rtag[0] = 0; /* What should I do with routing tags??? 
+				    -- Not used -- AS -- Thanks -- REW*/
+		tc->rtag[1] = 0;
+		tc->rtag[2] = 0;
+
+		if (fs_debug & FS_DEBUG_OPEN) {
+			fs_dprintk (FS_DEBUG_OPEN, "TX config record:\n");
+			my_hd (tc, sizeof (*tc));
+		}
+
+		/* We now use the "submit_command" function to submit commands to
+		   the firestream. There is a define up near the definition of
+		   that routine that switches this routine between immediate write
+		   to the immediate comamnd registers and queuing the commands in
+		   the HPTXQ for execution. This last technique might be more
+		   efficient if we know we're going to submit a whole lot of
+		   commands in one go, but this driver is not setup to be able to
+		   use such a construct. So it probably doen't matter much right
+		   now. -- REW */
+    
+		/* The command is IMMediate and INQueue. The parameters are out-of-line.. */
+		submit_command (dev, &dev->hp_txq, 
+				QE_CMD_CONFIG_TX | QE_CMD_IMM_INQ | vcc->channo,
+				virt_to_bus (tc), 0, 0);
+
+		submit_command (dev, &dev->hp_txq, 
+				QE_CMD_TX_EN | QE_CMD_IMM_INQ | vcc->channo,
+				0, 0, 0);
+		set_bit (vcc->channo, dev->tx_inuse);
+	}
+
+	if (DO_DIRECTION (rxtp)) {
+		dev->atm_vccs[vcc->channo] = atm_vcc;
+
+		for (bfp = 0;bfp < FS_NR_FREE_POOLS; bfp++)
+			if (atm_vcc->qos.rxtp.max_sdu <= dev->rx_fp[bfp].bufsize) break;
+		if (bfp >= FS_NR_FREE_POOLS) {
+			fs_dprintk (FS_DEBUG_OPEN, "No free pool fits sdu: %d.\n", 
+				    atm_vcc->qos.rxtp.max_sdu);
+			/* XXX Cleanup? -- Would just calling fs_close work??? -- REW */
+
+			/* XXX clear tx inuse. Close TX part? */
+			dev->atm_vccs[vcc->channo] = NULL;
+			kfree (vcc);
+			return -EINVAL;
+		}
+
+		switch (atm_vcc->qos.aal) {
+		case ATM_AAL0:
+		case ATM_AAL2:
+			submit_command (dev, &dev->hp_txq,
+					QE_CMD_CONFIG_RX | QE_CMD_IMM_INQ | vcc->channo,
+					RC_FLAGS_TRANSP |
+					RC_FLAGS_BFPS_BFP * bfp |
+					RC_FLAGS_RXBM_PSB, 0, 0);
+			break;
+		case ATM_AAL5:
+			submit_command (dev, &dev->hp_txq,
+					QE_CMD_CONFIG_RX | QE_CMD_IMM_INQ | vcc->channo,
+					RC_FLAGS_AAL5 |
+					RC_FLAGS_BFPS_BFP * bfp |
+					RC_FLAGS_RXBM_PSB, 0, 0);
+			break;
+		};
+		if (IS_FS50 (dev)) {
+			submit_command (dev, &dev->hp_txq, 
+					QE_CMD_REG_WR | QE_CMD_IMM_INQ,
+					0x80 + vcc->channo,
+					(vpi << 16) | vci, 0 ); /* XXX -- Use defines. */
+		}
+		submit_command (dev, &dev->hp_txq, 
+				QE_CMD_RX_EN | QE_CMD_IMM_INQ | vcc->channo,
+				0, 0, 0);
+	}
+    
+	/* Indicate we're done! */
+	set_bit(ATM_VF_READY, &atm_vcc->flags);
+
+	func_exit ();
+	return 0;
+}
+
+
+static void fs_close(struct atm_vcc *atm_vcc)
+{
+	struct fs_dev *dev = FS_DEV (atm_vcc->dev);
+	struct fs_vcc *vcc = FS_VCC (atm_vcc);
+	struct atm_trafprm * txtp;
+	struct atm_trafprm * rxtp;
+
+	func_enter ();
+
+	clear_bit(ATM_VF_READY, &atm_vcc->flags);
+
+	fs_dprintk (FS_DEBUG_QSIZE, "--==**[%d]**==--", dev->ntxpckts);
+	if (vcc->last_skb) {
+		fs_dprintk (FS_DEBUG_QUEUE, "Waiting for skb %p to be sent.\n", 
+			    vcc->last_skb);
+		/* We're going to wait for the last packet to get sent on this VC. It would
+		   be impolite not to send them don't you think? 
+		   XXX
+		   We don't know which packets didn't get sent. So if we get interrupted in 
+		   this sleep_on, we'll lose any reference to these packets. Memory leak!
+		   On the other hand, it's awfully convenient that we can abort a "close" that
+		   is taking too long. Maybe just use non-interruptible sleep on? -- REW */
+		interruptible_sleep_on (& vcc->close_wait);
+	}
+
+	txtp = &atm_vcc->qos.txtp;
+	rxtp = &atm_vcc->qos.rxtp;
+  
+
+	/* See App note XXX (Unpublished as of now) for the reason for the 
+	   removal of the "CMD_IMM_INQ" part of the TX_PURGE_INH... -- REW */
+
+	if (DO_DIRECTION (txtp)) {
+		submit_command (dev,  &dev->hp_txq,
+				QE_CMD_TX_PURGE_INH | /*QE_CMD_IMM_INQ|*/ vcc->channo, 0,0,0);
+		clear_bit (vcc->channo, dev->tx_inuse);
+	}
+
+	if (DO_DIRECTION (rxtp)) {
+		submit_command (dev,  &dev->hp_txq,
+				QE_CMD_RX_PURGE_INH | QE_CMD_IMM_INQ | vcc->channo, 0,0,0);
+		dev->atm_vccs [vcc->channo] = NULL;
+  
+		/* This means that this is configured as a receive channel */
+		if (IS_FS50 (dev)) {
+			/* Disable the receive filter. Is 0/0 indeed an invalid receive
+			   channel? -- REW.  Yes it is. -- Hang. Ok. I'll use -1
+			   (0xfff...) -- REW */
+			submit_command (dev, &dev->hp_txq, 
+					QE_CMD_REG_WR | QE_CMD_IMM_INQ,
+					0x80 + vcc->channo, -1, 0 ); 
+		}
+	}
+
+	fs_dprintk (FS_DEBUG_ALLOC, "Free vcc: %p\n", vcc);
+	kfree (vcc);
+
+	func_exit ();
+}
+
+
+static int fs_send (struct atm_vcc *atm_vcc, struct sk_buff *skb)
+{
+	struct fs_dev *dev = FS_DEV (atm_vcc->dev);
+	struct fs_vcc *vcc = FS_VCC (atm_vcc);
+	struct FS_BPENTRY *td;
+
+	func_enter ();
+
+	fs_dprintk (FS_DEBUG_TXMEM, "I");
+	fs_dprintk (FS_DEBUG_SEND, "Send: atm_vcc %p skb %p vcc %p dev %p\n", 
+		    atm_vcc, skb, vcc, dev);
+
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc t-skb: %p (atm_send)\n", skb);
+
+	ATM_SKB(skb)->vcc = atm_vcc;
+
+	vcc->last_skb = skb;
+
+	td = kmalloc (sizeof (struct FS_BPENTRY), GFP_ATOMIC);
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc transd: %p(%Zd)\n", td, sizeof (struct FS_BPENTRY));
+	if (!td) {
+		/* Oops out of mem */
+		return -ENOMEM;
+	}
+
+	fs_dprintk (FS_DEBUG_SEND, "first word in buffer: %x\n", 
+		    *(int *) skb->data);
+
+	td->flags =  TD_EPI | TD_DATA | skb->len;
+	td->next = 0;
+	td->bsa  = virt_to_bus (skb->data);
+	td->skb = skb;
+	td->dev = dev;
+	dev->ntxpckts++;
+
+#ifdef DEBUG_EXTRA
+	da[qd] = td;
+	dq[qd].flags = td->flags;
+	dq[qd].next  = td->next;
+	dq[qd].bsa   = td->bsa;
+	dq[qd].skb   = td->skb;
+	dq[qd].dev   = td->dev;
+	qd++;
+	if (qd >= 60) qd = 0;
+#endif
+
+	submit_queue (dev, &dev->hp_txq, 
+		      QE_TRANSMIT_DE | vcc->channo,
+		      virt_to_bus (td), 0, 
+		      virt_to_bus (td));
+
+	fs_dprintk (FS_DEBUG_QUEUE, "in send: txq %d txrq %d\n", 
+		    read_fs (dev, Q_EA (dev->hp_txq.offset)) -
+		    read_fs (dev, Q_SA (dev->hp_txq.offset)),
+		    read_fs (dev, Q_EA (dev->tx_relq.offset)) -
+		    read_fs (dev, Q_SA (dev->tx_relq.offset)));
+
+	func_exit ();
+	return 0;
+}
+
+
+/* Some function placeholders for functions we don't yet support. */
+
+#if 0
+static int fs_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	func_enter ();
+	func_exit ();
+	return -ENOIOCTLCMD;
+}
+
+
+static int fs_getsockopt(struct atm_vcc *vcc,int level,int optname,
+			 void __user *optval,int optlen)
+{
+	func_enter ();
+	func_exit ();
+	return 0;
+}
+
+
+static int fs_setsockopt(struct atm_vcc *vcc,int level,int optname,
+			 void __user *optval,int optlen)
+{
+	func_enter ();
+	func_exit ();
+	return 0;
+}
+
+
+static void fs_phy_put(struct atm_dev *dev,unsigned char value,
+		       unsigned long addr)
+{
+	func_enter ();
+	func_exit ();
+}
+
+
+static unsigned char fs_phy_get(struct atm_dev *dev,unsigned long addr)
+{
+	func_enter ();
+	func_exit ();
+	return 0;
+}
+
+
+static int fs_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flags)
+{
+	func_enter ();
+	func_exit ();
+	return 0;
+};
+
+#endif
+
+
+static const struct atmdev_ops ops = {
+	.open =         fs_open,
+	.close =        fs_close,
+	.send =         fs_send,
+	.owner =        THIS_MODULE,
+	/* ioctl:          fs_ioctl, */
+	/* getsockopt:     fs_getsockopt, */
+	/* setsockopt:     fs_setsockopt, */
+	/* change_qos:     fs_change_qos, */
+
+	/* For now implement these internally here... */  
+	/* phy_put:        fs_phy_put, */
+	/* phy_get:        fs_phy_get, */
+};
+
+
+static void __devinit undocumented_pci_fix (struct pci_dev *pdev)
+{
+	int tint;
+
+	/* The Windows driver says: */
+	/* Switch off FireStream Retry Limit Threshold 
+	 */
+
+	/* The register at 0x28 is documented as "reserved", no further
+	   comments. */
+
+	pci_read_config_dword (pdev, 0x28, &tint);
+	if (tint != 0x80) {
+		tint = 0x80;
+		pci_write_config_dword (pdev, 0x28, tint);
+	}
+}
+
+
+
+/**************************************************************************
+ *                              PHY routines                              *
+ **************************************************************************/
+
+static void __devinit write_phy (struct fs_dev *dev, int regnum, int val)
+{
+	submit_command (dev,  &dev->hp_txq, QE_CMD_PRP_WR | QE_CMD_IMM_INQ,
+			regnum, val, 0);
+}
+
+static int __devinit init_phy (struct fs_dev *dev, struct reginit_item *reginit)
+{
+	int i;
+
+	func_enter ();
+	while (reginit->reg != PHY_EOF) {
+		if (reginit->reg == PHY_CLEARALL) {
+			/* "PHY_CLEARALL means clear all registers. Numregisters is in "val". */
+			for (i=0;i<reginit->val;i++) {
+				write_phy (dev, i, 0);
+			}
+		} else {
+			write_phy (dev, reginit->reg, reginit->val);
+		}
+		reginit++;
+	}
+	func_exit ();
+	return 0;
+}
+
+static void reset_chip (struct fs_dev *dev)
+{
+	int i;
+
+	write_fs (dev, SARMODE0, SARMODE0_SRTS0);
+
+	/* Undocumented delay */
+	udelay (128);
+
+	/* The "internal registers are documented to all reset to zero, but 
+	   comments & code in the Windows driver indicates that the pools are
+	   NOT reset. */
+	for (i=0;i < FS_NR_FREE_POOLS;i++) {
+		write_fs (dev, FP_CNF (RXB_FP(i)), 0);
+		write_fs (dev, FP_SA  (RXB_FP(i)), 0);
+		write_fs (dev, FP_EA  (RXB_FP(i)), 0);
+		write_fs (dev, FP_CNT (RXB_FP(i)), 0);
+		write_fs (dev, FP_CTU (RXB_FP(i)), 0);
+	}
+
+	/* The same goes for the match channel registers, although those are
+	   NOT documented that way in the Windows driver. -- REW */
+	/* The Windows driver DOES write 0 to these registers somewhere in
+	   the init sequence. However, a small hardware-feature, will
+	   prevent reception of data on VPI/VCI = 0/0 (Unless the channel
+	   allocated happens to have no disabled channels that have a lower
+	   number. -- REW */
+
+	/* Clear the match channel registers. */
+	if (IS_FS50 (dev)) {
+		for (i=0;i<FS50_NR_CHANNELS;i++) {
+			write_fs (dev, 0x200 + i * 4, -1);
+		}
+	}
+}
+
+static void __devinit *aligned_kmalloc (int size, int flags, int alignment)
+{
+	void  *t;
+
+	if (alignment <= 0x10) {
+		t = kmalloc (size, flags);
+		if ((unsigned long)t & (alignment-1)) {
+			printk ("Kmalloc doesn't align things correctly! %p\n", t);
+			kfree (t);
+			return aligned_kmalloc (size, flags, alignment * 4);
+		}
+		return t;
+	}
+	printk (KERN_ERR "Request for > 0x10 alignment not yet implemented (hard!)\n");
+	return NULL;
+}
+
+static int __devinit init_q (struct fs_dev *dev, 
+			  struct queue *txq, int queue, int nentries, int is_rq)
+{
+	int sz = nentries * sizeof (struct FS_QENTRY);
+	struct FS_QENTRY *p;
+
+	func_enter ();
+
+	fs_dprintk (FS_DEBUG_INIT, "Inititing queue at %x: %d entries:\n", 
+		    queue, nentries);
+
+	p = aligned_kmalloc (sz, GFP_KERNEL, 0x10);
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc queue: %p(%d)\n", p, sz);
+
+	if (!p) return 0;
+
+	write_fs (dev, Q_SA(queue), virt_to_bus(p));
+	write_fs (dev, Q_EA(queue), virt_to_bus(p+nentries-1));
+	write_fs (dev, Q_WP(queue), virt_to_bus(p));
+	write_fs (dev, Q_RP(queue), virt_to_bus(p));
+	if (is_rq) {
+		/* Configuration for the receive queue: 0: interrupt immediately,
+		   no pre-warning to empty queues: We do our best to keep the
+		   queue filled anyway. */
+		write_fs (dev, Q_CNF(queue), 0 ); 
+	}
+
+	txq->sa = p;
+	txq->ea = p;
+	txq->offset = queue; 
+
+	func_exit ();
+	return 1;
+}
+
+
+static int __devinit init_fp (struct fs_dev *dev, 
+			   struct freepool *fp, int queue, int bufsize, int nr_buffers)
+{
+	func_enter ();
+
+	fs_dprintk (FS_DEBUG_INIT, "Inititing free pool at %x:\n", queue);
+
+	write_fs (dev, FP_CNF(queue), (bufsize * RBFP_RBS) | RBFP_RBSVAL | RBFP_CME);
+	write_fs (dev, FP_SA(queue),  0);
+	write_fs (dev, FP_EA(queue),  0);
+	write_fs (dev, FP_CTU(queue), 0);
+	write_fs (dev, FP_CNT(queue), 0);
+
+	fp->offset = queue; 
+	fp->bufsize = bufsize;
+	fp->nr_buffers = nr_buffers;
+
+	func_exit ();
+	return 1;
+}
+
+
+static inline int nr_buffers_in_freepool (struct fs_dev *dev, struct freepool *fp)
+{
+#if 0
+	/* This seems to be unreliable.... */
+	return read_fs (dev, FP_CNT (fp->offset));
+#else
+	return fp->n;
+#endif
+}
+
+
+/* Check if this gets going again if a pool ever runs out.  -- Yes, it
+   does. I've seen "receive abort: no buffers" and things started
+   working again after that...  -- REW */
+
+static void top_off_fp (struct fs_dev *dev, struct freepool *fp, int gfp_flags)
+{
+	struct FS_BPENTRY *qe, *ne;
+	struct sk_buff *skb;
+	int n = 0;
+
+	fs_dprintk (FS_DEBUG_QUEUE, "Topping off queue at %x (%d-%d/%d)\n", 
+		    fp->offset, read_fs (dev, FP_CNT (fp->offset)), fp->n, 
+		    fp->nr_buffers);
+	while (nr_buffers_in_freepool(dev, fp) < fp->nr_buffers) {
+
+		skb = alloc_skb (fp->bufsize, gfp_flags);
+		fs_dprintk (FS_DEBUG_ALLOC, "Alloc rec-skb: %p(%d)\n", skb, fp->bufsize);
+		if (!skb) break;
+		ne = kmalloc (sizeof (struct FS_BPENTRY), gfp_flags);
+		fs_dprintk (FS_DEBUG_ALLOC, "Alloc rec-d: %p(%Zd)\n", ne, sizeof (struct FS_BPENTRY));
+		if (!ne) {
+			fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", skb);
+			dev_kfree_skb_any (skb);
+			break;
+		}
+
+		fs_dprintk (FS_DEBUG_QUEUE, "Adding skb %p desc %p -> %p(%p) ", 
+			    skb, ne, skb->data, skb->head);
+		n++;
+		ne->flags = FP_FLAGS_EPI | fp->bufsize;
+		ne->next  = virt_to_bus (NULL);
+		ne->bsa   = virt_to_bus (skb->data);
+		ne->aal_bufsize = fp->bufsize;
+		ne->skb = skb;
+		ne->fp = fp;
+
+		qe = (struct FS_BPENTRY *) (read_fs (dev, FP_EA(fp->offset)));
+		fs_dprintk (FS_DEBUG_QUEUE, "link at %p\n", qe);
+		if (qe) {
+			qe = bus_to_virt ((long) qe);
+			qe->next = virt_to_bus(ne);
+			qe->flags &= ~FP_FLAGS_EPI;
+		} else
+			write_fs (dev, FP_SA(fp->offset), virt_to_bus(ne));
+
+		write_fs (dev, FP_EA(fp->offset), virt_to_bus (ne));
+		fp->n++;   /* XXX Atomic_inc? */
+		write_fs (dev, FP_CTU(fp->offset), 1);
+	}
+
+	fs_dprintk (FS_DEBUG_QUEUE, "Added %d entries. \n", n);
+}
+
+static void __devexit free_queue (struct fs_dev *dev, struct queue *txq)
+{
+	func_enter ();
+
+	write_fs (dev, Q_SA(txq->offset), 0);
+	write_fs (dev, Q_EA(txq->offset), 0);
+	write_fs (dev, Q_RP(txq->offset), 0);
+	write_fs (dev, Q_WP(txq->offset), 0);
+	/* Configuration ? */
+
+	fs_dprintk (FS_DEBUG_ALLOC, "Free queue: %p\n", txq->sa);
+	kfree (txq->sa);
+
+	func_exit ();
+}
+
+static void __devexit free_freepool (struct fs_dev *dev, struct freepool *fp)
+{
+	func_enter ();
+
+	write_fs (dev, FP_CNF(fp->offset), 0);
+	write_fs (dev, FP_SA (fp->offset), 0);
+	write_fs (dev, FP_EA (fp->offset), 0);
+	write_fs (dev, FP_CNT(fp->offset), 0);
+	write_fs (dev, FP_CTU(fp->offset), 0);
+
+	func_exit ();
+}
+
+
+
+static irqreturn_t fs_irq (int irq, void *dev_id,  struct pt_regs * pt_regs) 
+{
+	int i;
+	u32 status;
+	struct fs_dev *dev = dev_id;
+
+	status = read_fs (dev, ISR);
+	if (!status)
+		return IRQ_NONE;
+
+	func_enter ();
+
+#ifdef IRQ_RATE_LIMIT
+	/* Aaargh! I'm ashamed. This costs more lines-of-code than the actual 
+	   interrupt routine!. (Well, used to when I wrote that comment) -- REW */
+	{
+		static int lastjif;
+		static int nintr=0;
+    
+		if (lastjif == jiffies) {
+			if (++nintr > IRQ_RATE_LIMIT) {
+				free_irq (dev->irq, dev_id);
+				printk (KERN_ERR "fs: Too many interrupts. Turning off interrupt %d.\n", 
+					dev->irq);
+			}
+		} else {
+			lastjif = jiffies;
+			nintr = 0;
+		}
+	}
+#endif
+	fs_dprintk (FS_DEBUG_QUEUE, "in intr: txq %d txrq %d\n", 
+		    read_fs (dev, Q_EA (dev->hp_txq.offset)) -
+		    read_fs (dev, Q_SA (dev->hp_txq.offset)),
+		    read_fs (dev, Q_EA (dev->tx_relq.offset)) -
+		    read_fs (dev, Q_SA (dev->tx_relq.offset)));
+
+	/* print the bits in the ISR register. */
+	if (fs_debug & FS_DEBUG_IRQ) {
+		/* The FS_DEBUG things are unneccesary here. But this way it is
+		   clear for grep that these are debug prints. */
+		fs_dprintk (FS_DEBUG_IRQ,  "IRQ status:");
+		for (i=0;i<27;i++) 
+			if (status & (1 << i)) 
+				fs_dprintk (FS_DEBUG_IRQ, " %s", irq_bitname[i]);
+		fs_dprintk (FS_DEBUG_IRQ, "\n");
+	}
+  
+	if (status & ISR_RBRQ0_W) {
+		fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (0)!!!!\n");
+		process_incoming (dev, &dev->rx_rq[0]);
+		/* items mentioned on RBRQ0 are from FP 0 or 1. */
+		top_off_fp (dev, &dev->rx_fp[0], GFP_ATOMIC);
+		top_off_fp (dev, &dev->rx_fp[1], GFP_ATOMIC);
+	}
+
+	if (status & ISR_RBRQ1_W) {
+		fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (1)!!!!\n");
+		process_incoming (dev, &dev->rx_rq[1]);
+		top_off_fp (dev, &dev->rx_fp[2], GFP_ATOMIC);
+		top_off_fp (dev, &dev->rx_fp[3], GFP_ATOMIC);
+	}
+
+	if (status & ISR_RBRQ2_W) {
+		fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (2)!!!!\n");
+		process_incoming (dev, &dev->rx_rq[2]);
+		top_off_fp (dev, &dev->rx_fp[4], GFP_ATOMIC);
+		top_off_fp (dev, &dev->rx_fp[5], GFP_ATOMIC);
+	}
+
+	if (status & ISR_RBRQ3_W) {
+		fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (3)!!!!\n");
+		process_incoming (dev, &dev->rx_rq[3]);
+		top_off_fp (dev, &dev->rx_fp[6], GFP_ATOMIC);
+		top_off_fp (dev, &dev->rx_fp[7], GFP_ATOMIC);
+	}
+
+	if (status & ISR_CSQ_W) {
+		fs_dprintk (FS_DEBUG_IRQ, "Command executed ok!\n");
+		process_return_queue (dev, &dev->st_q);
+	}
+
+	if (status & ISR_TBRQ_W) {
+		fs_dprintk (FS_DEBUG_IRQ, "Data tramsitted!\n");
+		process_txdone_queue (dev, &dev->tx_relq);
+	}
+
+	func_exit ();
+	return IRQ_HANDLED;
+}
+
+
+#ifdef FS_POLL_FREQ
+static void fs_poll (unsigned long data)
+{
+	struct fs_dev *dev = (struct fs_dev *) data;
+  
+	fs_irq (0, dev, NULL);
+	dev->timer.expires = jiffies + FS_POLL_FREQ;
+	add_timer (&dev->timer);
+}
+#endif
+
+static int __devinit fs_init (struct fs_dev *dev)
+{
+	struct pci_dev  *pci_dev;
+	int isr, to;
+	int i;
+
+	func_enter ();
+	pci_dev = dev->pci_dev;
+
+	printk (KERN_INFO "found a FireStream %d card, base %08lx, irq%d.\n", 
+		IS_FS50(dev)?50:155,
+		pci_resource_start(pci_dev, 0), dev->pci_dev->irq);
+
+	if (fs_debug & FS_DEBUG_INIT)
+		my_hd ((unsigned char *) dev, sizeof (*dev));
+
+	undocumented_pci_fix (pci_dev);
+
+	dev->hw_base = pci_resource_start(pci_dev, 0);
+
+	dev->base = ioremap(dev->hw_base, 0x1000);
+
+	reset_chip (dev);
+  
+	write_fs (dev, SARMODE0, 0 
+		  | (0 * SARMODE0_SHADEN) /* We don't use shadow registers. */
+		  | (1 * SARMODE0_INTMODE_READCLEAR)
+		  | (1 * SARMODE0_CWRE)
+		  | IS_FS50(dev)?SARMODE0_PRPWT_FS50_5: 
+		                 SARMODE0_PRPWT_FS155_3
+		  | (1 * SARMODE0_CALSUP_1)
+		  | IS_FS50 (dev)?(0
+				   | SARMODE0_RXVCS_32
+				   | SARMODE0_ABRVCS_32 
+				   | SARMODE0_TXVCS_32):
+		                  (0
+				   | SARMODE0_RXVCS_1k
+				   | SARMODE0_ABRVCS_1k 
+				   | SARMODE0_TXVCS_1k));
+
+	/* 10ms * 100 is 1 second. That should be enough, as AN3:9 says it takes
+	   1ms. */
+	to = 100;
+	while (--to) {
+		isr = read_fs (dev, ISR);
+
+		/* This bit is documented as "RESERVED" */
+		if (isr & ISR_INIT_ERR) {
+			printk (KERN_ERR "Error initializing the FS... \n");
+			return 1;
+		}
+		if (isr & ISR_INIT) {
+			fs_dprintk (FS_DEBUG_INIT, "Ha! Initialized OK!\n");
+			break;
+		}
+
+		/* Try again after 10ms. */
+		msleep(10);
+	}
+
+	if (!to) {
+		printk (KERN_ERR "timeout initializing the FS... \n");
+		return 1;
+	}
+
+	/* XXX fix for fs155 */
+	dev->channel_mask = 0x1f; 
+	dev->channo = 0;
+
+	/* AN3: 10 */
+	write_fs (dev, SARMODE1, 0 
+		  | (fs_keystream * SARMODE1_DEFHEC) /* XXX PHY */
+		  | ((loopback == 1) * SARMODE1_TSTLP) /* XXX Loopback mode enable... */
+		  | (1 * SARMODE1_DCRM)
+		  | (1 * SARMODE1_DCOAM)
+		  | (0 * SARMODE1_OAMCRC)
+		  | (0 * SARMODE1_DUMPE)
+		  | (0 * SARMODE1_GPLEN) 
+		  | (0 * SARMODE1_GNAM)
+		  | (0 * SARMODE1_GVAS)
+		  | (0 * SARMODE1_GPAS)
+		  | (1 * SARMODE1_GPRI)
+		  | (0 * SARMODE1_PMS)
+		  | (0 * SARMODE1_GFCR)
+		  | (1 * SARMODE1_HECM2)
+		  | (1 * SARMODE1_HECM1)
+		  | (1 * SARMODE1_HECM0)
+		  | (1 << 12) /* That's what hang's driver does. Program to 0 */
+		  | (0 * 0xff) /* XXX FS155 */);
+
+
+	/* Cal prescale etc */
+
+	/* AN3: 11 */
+	write_fs (dev, TMCONF, 0x0000000f);
+	write_fs (dev, CALPRESCALE, 0x01010101 * num);
+	write_fs (dev, 0x80, 0x000F00E4);
+
+	/* AN3: 12 */
+	write_fs (dev, CELLOSCONF, 0
+		  | (   0 * CELLOSCONF_CEN)
+		  | (       CELLOSCONF_SC1)
+		  | (0x80 * CELLOSCONF_COBS)
+		  | (num  * CELLOSCONF_COPK)  /* Changed from 0xff to 0x5a */
+		  | (num  * CELLOSCONF_COST));/* after a hint from Hang. 
+					       * performance jumped 50->70... */
+
+	/* Magic value by Hang */
+	write_fs (dev, CELLOSCONF_COST, 0x0B809191);
+
+	if (IS_FS50 (dev)) {
+		write_fs (dev, RAS0, RAS0_DCD_XHLT);
+		dev->atm_dev->ci_range.vpi_bits = 12;
+		dev->atm_dev->ci_range.vci_bits = 16;
+		dev->nchannels = FS50_NR_CHANNELS;
+	} else {
+		write_fs (dev, RAS0, RAS0_DCD_XHLT 
+			  | (((1 << FS155_VPI_BITS) - 1) * RAS0_VPSEL)
+			  | (((1 << FS155_VCI_BITS) - 1) * RAS0_VCSEL));
+		/* We can chose the split arbitarily. We might be able to 
+		   support more. Whatever. This should do for now. */
+		dev->atm_dev->ci_range.vpi_bits = FS155_VPI_BITS;
+		dev->atm_dev->ci_range.vci_bits = FS155_VCI_BITS;
+    
+		/* Address bits we can't use should be compared to 0. */
+		write_fs (dev, RAC, 0);
+
+		/* Manual (AN9, page 6) says ASF1=0 means compare Utopia address
+		 * too.  I can't find ASF1 anywhere. Anyway, we AND with just the
+		 * other bits, then compare with 0, which is exactly what we
+		 * want. */
+		write_fs (dev, RAM, (1 << (28 - FS155_VPI_BITS - FS155_VCI_BITS)) - 1);
+		dev->nchannels = FS155_NR_CHANNELS;
+	}
+	dev->atm_vccs = kmalloc (dev->nchannels * sizeof (struct atm_vcc *), 
+				 GFP_KERNEL);
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc atmvccs: %p(%Zd)\n",
+		    dev->atm_vccs, dev->nchannels * sizeof (struct atm_vcc *));
+
+	if (!dev->atm_vccs) {
+		printk (KERN_WARNING "Couldn't allocate memory for VCC buffers. Woops!\n");
+		/* XXX Clean up..... */
+		return 1;
+	}
+	memset (dev->atm_vccs, 0, dev->nchannels * sizeof (struct atm_vcc *));
+
+	dev->tx_inuse = kmalloc (dev->nchannels / 8 /* bits/byte */ , GFP_KERNEL);
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc tx_inuse: %p(%d)\n", 
+		    dev->atm_vccs, dev->nchannels / 8);
+
+	if (!dev->tx_inuse) {
+		printk (KERN_WARNING "Couldn't allocate memory for tx_inuse bits!\n");
+		/* XXX Clean up..... */
+		return 1;
+	}
+	memset (dev->tx_inuse, 0, dev->nchannels / 8);
+
+	/* -- RAS1 : FS155 and 50 differ. Default (0) should be OK for both */
+	/* -- RAS2 : FS50 only: Default is OK. */
+
+	/* DMAMODE, default should be OK. -- REW */
+	write_fs (dev, DMAMR, DMAMR_TX_MODE_FULL);
+
+	init_q (dev, &dev->hp_txq, TX_PQ(TXQ_HP), TXQ_NENTRIES, 0);
+	init_q (dev, &dev->lp_txq, TX_PQ(TXQ_LP), TXQ_NENTRIES, 0);
+	init_q (dev, &dev->tx_relq, TXB_RQ, TXQ_NENTRIES, 1);
+	init_q (dev, &dev->st_q, ST_Q, TXQ_NENTRIES, 1);
+
+	for (i=0;i < FS_NR_FREE_POOLS;i++) {
+		init_fp (dev, &dev->rx_fp[i], RXB_FP(i), 
+			 rx_buf_sizes[i], rx_pool_sizes[i]);
+		top_off_fp (dev, &dev->rx_fp[i], GFP_KERNEL);
+	}
+
+
+	for (i=0;i < FS_NR_RX_QUEUES;i++)
+		init_q (dev, &dev->rx_rq[i], RXB_RQ(i), RXRQ_NENTRIES, 1);
+
+	dev->irq = pci_dev->irq;
+	if (request_irq (dev->irq, fs_irq, SA_SHIRQ, "firestream", dev)) {
+		printk (KERN_WARNING "couldn't get irq %d for firestream.\n", pci_dev->irq);
+		/* XXX undo all previous stuff... */
+		return 1;
+	}
+	fs_dprintk (FS_DEBUG_INIT, "Grabbed irq %d for dev at %p.\n", dev->irq, dev);
+  
+	/* We want to be notified of most things. Just the statistics count
+	   overflows are not interesting */
+	write_fs (dev, IMR, 0
+		  | ISR_RBRQ0_W 
+		  | ISR_RBRQ1_W 
+		  | ISR_RBRQ2_W 
+		  | ISR_RBRQ3_W 
+		  | ISR_TBRQ_W
+		  | ISR_CSQ_W);
+
+	write_fs (dev, SARMODE0, 0 
+		  | (0 * SARMODE0_SHADEN) /* We don't use shadow registers. */
+		  | (1 * SARMODE0_GINT)
+		  | (1 * SARMODE0_INTMODE_READCLEAR)
+		  | (0 * SARMODE0_CWRE)
+		  | (IS_FS50(dev)?SARMODE0_PRPWT_FS50_5: 
+		                  SARMODE0_PRPWT_FS155_3)
+		  | (1 * SARMODE0_CALSUP_1)
+		  | (IS_FS50 (dev)?(0
+				    | SARMODE0_RXVCS_32
+				    | SARMODE0_ABRVCS_32 
+				    | SARMODE0_TXVCS_32):
+		                   (0
+				    | SARMODE0_RXVCS_1k
+				    | SARMODE0_ABRVCS_1k 
+				    | SARMODE0_TXVCS_1k))
+		  | (1 * SARMODE0_RUN));
+
+	init_phy (dev, PHY_NTC_INIT);
+
+	if (loopback == 2) {
+		write_phy (dev, 0x39, 0x000e);
+	}
+
+#ifdef FS_POLL_FREQ
+	init_timer (&dev->timer);
+	dev->timer.data = (unsigned long) dev;
+	dev->timer.function = fs_poll;
+	dev->timer.expires = jiffies + FS_POLL_FREQ;
+	add_timer (&dev->timer);
+#endif
+
+	dev->atm_dev->dev_data = dev;
+  
+	func_exit ();
+	return 0;
+}
+
+static int __devinit firestream_init_one (struct pci_dev *pci_dev,
+				       const struct pci_device_id *ent) 
+{
+	struct atm_dev *atm_dev;
+	struct fs_dev *fs_dev;
+	
+	if (pci_enable_device(pci_dev)) 
+		goto err_out;
+
+	fs_dev = kmalloc (sizeof (struct fs_dev), GFP_KERNEL);
+	fs_dprintk (FS_DEBUG_ALLOC, "Alloc fs-dev: %p(%Zd)\n",
+		    fs_dev, sizeof (struct fs_dev));
+	if (!fs_dev)
+		goto err_out;
+
+	memset (fs_dev, 0, sizeof (struct fs_dev));
+  
+	atm_dev = atm_dev_register("fs", &ops, -1, NULL);
+	if (!atm_dev)
+		goto err_out_free_fs_dev;
+  
+	fs_dev->pci_dev = pci_dev;
+	fs_dev->atm_dev = atm_dev;
+	fs_dev->flags = ent->driver_data;
+
+	if (fs_init(fs_dev))
+		goto err_out_free_atm_dev;
+
+	fs_dev->next = fs_boards;
+	fs_boards = fs_dev;
+	return 0;
+
+ err_out_free_atm_dev:
+	atm_dev_deregister(atm_dev);
+ err_out_free_fs_dev:
+ 	kfree(fs_dev);
+ err_out:
+	return -ENODEV;
+}
+
+static void __devexit firestream_remove_one (struct pci_dev *pdev)
+{
+	int i;
+	struct fs_dev *dev, *nxtdev;
+	struct fs_vcc *vcc;
+	struct FS_BPENTRY *fp, *nxt;
+  
+	func_enter ();
+
+#if 0
+	printk ("hptxq:\n");
+	for (i=0;i<60;i++) {
+		printk ("%d: %08x %08x %08x %08x \n", 
+			i, pq[qp].cmd, pq[qp].p0, pq[qp].p1, pq[qp].p2);
+		qp++;
+		if (qp >= 60) qp = 0;
+	}
+
+	printk ("descriptors:\n");
+	for (i=0;i<60;i++) {
+		printk ("%d: %p: %08x %08x %p %p\n", 
+			i, da[qd], dq[qd].flags, dq[qd].bsa, dq[qd].skb, dq[qd].dev);
+		qd++;
+		if (qd >= 60) qd = 0;
+	}
+#endif
+
+	for (dev = fs_boards;dev != NULL;dev=nxtdev) {
+		fs_dprintk (FS_DEBUG_CLEANUP, "Releasing resources for dev at %p.\n", dev);
+
+		/* XXX Hit all the tx channels too! */
+
+		for (i=0;i < dev->nchannels;i++) {
+			if (dev->atm_vccs[i]) {
+				vcc = FS_VCC (dev->atm_vccs[i]);
+				submit_command (dev,  &dev->hp_txq,
+						QE_CMD_TX_PURGE_INH | QE_CMD_IMM_INQ | vcc->channo, 0,0,0);
+				submit_command (dev,  &dev->hp_txq,
+						QE_CMD_RX_PURGE_INH | QE_CMD_IMM_INQ | vcc->channo, 0,0,0);
+
+			}
+		}
+
+		/* XXX Wait a while for the chip to release all buffers. */
+
+		for (i=0;i < FS_NR_FREE_POOLS;i++) {
+			for (fp=bus_to_virt (read_fs (dev, FP_SA(dev->rx_fp[i].offset)));
+			     !(fp->flags & FP_FLAGS_EPI);fp = nxt) {
+				fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", fp->skb);
+				dev_kfree_skb_any (fp->skb);
+				nxt = bus_to_virt (fp->next);
+				fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", fp);
+				kfree (fp);
+			}
+			fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", fp->skb);
+			dev_kfree_skb_any (fp->skb);
+			fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", fp);
+			kfree (fp);
+		}
+
+		/* Hang the chip in "reset", prevent it clobbering memory that is
+		   no longer ours. */
+		reset_chip (dev);
+
+		fs_dprintk (FS_DEBUG_CLEANUP, "Freeing irq%d.\n", dev->irq);
+		free_irq (dev->irq, dev);
+		del_timer (&dev->timer);
+
+		atm_dev_deregister(dev->atm_dev);
+		free_queue (dev, &dev->hp_txq);
+		free_queue (dev, &dev->lp_txq);
+		free_queue (dev, &dev->tx_relq);
+		free_queue (dev, &dev->st_q);
+
+		fs_dprintk (FS_DEBUG_ALLOC, "Free atmvccs: %p\n", dev->atm_vccs);
+		kfree (dev->atm_vccs);
+
+		for (i=0;i< FS_NR_FREE_POOLS;i++)
+			free_freepool (dev, &dev->rx_fp[i]);
+    
+		for (i=0;i < FS_NR_RX_QUEUES;i++)
+			free_queue (dev, &dev->rx_rq[i]);
+
+		fs_dprintk (FS_DEBUG_ALLOC, "Free fs-dev: %p\n", dev);
+		nxtdev = dev->next;
+		kfree (dev);
+	}
+
+	func_exit ();
+}
+
+static struct pci_device_id firestream_pci_tbl[] = {
+	{ PCI_VENDOR_ID_FUJITSU_ME, PCI_DEVICE_ID_FUJITSU_FS50, 
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, FS_IS50},
+	{ PCI_VENDOR_ID_FUJITSU_ME, PCI_DEVICE_ID_FUJITSU_FS155, 
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, FS_IS155},
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, firestream_pci_tbl);
+
+static struct pci_driver firestream_driver = {
+	.name		= "firestream",
+	.id_table	= firestream_pci_tbl,
+	.probe		= firestream_init_one,
+	.remove		= __devexit_p(firestream_remove_one),
+};
+
+static int __init firestream_init_module (void)
+{
+	int error;
+
+	func_enter ();
+	error = pci_register_driver(&firestream_driver);
+	func_exit ();
+	return error;
+}
+
+static void __exit firestream_cleanup_module(void)
+{
+	pci_unregister_driver(&firestream_driver);
+}
+
+module_init(firestream_init_module);
+module_exit(firestream_cleanup_module);
+
+MODULE_LICENSE("GPL");
+
+
+
diff --git a/drivers/atm/firestream.h b/drivers/atm/firestream.h
new file mode 100644
index 0000000..49e783e
--- /dev/null
+++ b/drivers/atm/firestream.h
@@ -0,0 +1,518 @@
+/* drivers/atm/firestream.h - FireStream 155 (MB86697) and
+ *                            FireStream  50 (MB86695) device driver 
+ */
+ 
+/* Written & (C) 2000 by R.E.Wolff@BitWizard.nl 
+ * Copied snippets from zatm.c by Werner Almesberger, EPFL LRC/ICA 
+ * and ambassador.c Copyright (C) 1995-1999  Madge Networks Ltd 
+ */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+  system and in the file COPYING in the Linux kernel source.
+*/
+
+
+/***********************************************************************
+ *                  first the defines for the chip.                    *
+ ***********************************************************************/
+
+
+/********************* General chip parameters. ************************/
+
+#define FS_NR_FREE_POOLS   8
+#define FS_NR_RX_QUEUES    4
+
+
+/********************* queues and queue access macros ******************/
+
+
+/* A queue entry. */
+struct FS_QENTRY {
+	u32 cmd;
+	u32 p0, p1, p2;
+};
+
+
+/* A freepool entry. */
+struct FS_BPENTRY {
+	u32 flags;
+	u32 next;
+	u32 bsa;
+	u32 aal_bufsize;
+
+	/* The hardware doesn't look at this, but we need the SKB somewhere... */
+	struct sk_buff *skb;
+	struct freepool *fp;
+	struct fs_dev *dev;
+};
+
+
+#define STATUS_CODE(qe)  ((qe->cmd >> 22) & 0x3f)
+
+
+/* OFFSETS against the base of a QUEUE... */
+#define QSA     0x00
+#define QEA     0x04
+#define QRP     0x08
+#define QWP     0x0c
+#define QCNF    0x10   /* Only for Release queues! */
+/* Not for the transmit pending queue. */
+
+
+/* OFFSETS against the base of a FREE POOL... */
+#define FPCNF   0x00
+#define FPSA    0x04
+#define FPEA    0x08
+#define FPCNT   0x0c
+#define FPCTU   0x10
+
+#define Q_SA(b)     (b + QSA )
+#define Q_EA(b)     (b + QEA )
+#define Q_RP(b)     (b + QRP )
+#define Q_WP(b)     (b + QWP )
+#define Q_CNF(b)    (b + QCNF)
+
+#define FP_CNF(b)   (b + FPCNF)
+#define FP_SA(b)    (b + FPSA)
+#define FP_EA(b)    (b + FPEA)
+#define FP_CNT(b)   (b + FPCNT)
+#define FP_CTU(b)   (b + FPCTU)
+
+/* bits in a queue register. */
+#define Q_FULL      0x1
+#define Q_EMPTY     0x2
+#define Q_INCWRAP   0x4
+#define Q_ADDR_MASK 0xfffffff0
+
+/* bits in a FreePool config register */
+#define RBFP_RBS    (0x1 << 16)
+#define RBFP_RBSVAL (0x1 << 15)
+#define RBFP_CME    (0x1 << 12)
+#define RBFP_DLP    (0x1 << 11)
+#define RBFP_BFPWT  (0x1 <<  0)
+
+
+
+
+/* FireStream commands. */
+#define QE_CMD_NULL             (0x00 << 22)
+#define QE_CMD_REG_RD           (0x01 << 22)
+#define QE_CMD_REG_RDM          (0x02 << 22)
+#define QE_CMD_REG_WR           (0x03 << 22)
+#define QE_CMD_REG_WRM          (0x04 << 22)
+#define QE_CMD_CONFIG_TX        (0x05 << 22)
+#define QE_CMD_CONFIG_RX        (0x06 << 22)
+#define QE_CMD_PRP_RD           (0x07 << 22)
+#define QE_CMD_PRP_RDM          (0x2a << 22)
+#define QE_CMD_PRP_WR           (0x09 << 22)
+#define QE_CMD_PRP_WRM          (0x2b << 22)
+#define QE_CMD_RX_EN            (0x0a << 22)
+#define QE_CMD_RX_PURGE         (0x0b << 22)
+#define QE_CMD_RX_PURGE_INH     (0x0c << 22)
+#define QE_CMD_TX_EN            (0x0d << 22)
+#define QE_CMD_TX_PURGE         (0x0e << 22)
+#define QE_CMD_TX_PURGE_INH     (0x0f << 22)
+#define QE_CMD_RST_CG           (0x10 << 22)
+#define QE_CMD_SET_CG           (0x11 << 22)
+#define QE_CMD_RST_CLP          (0x12 << 22)
+#define QE_CMD_SET_CLP          (0x13 << 22)
+#define QE_CMD_OVERRIDE         (0x14 << 22)
+#define QE_CMD_ADD_BFP          (0x15 << 22)
+#define QE_CMD_DUMP_TX          (0x16 << 22)
+#define QE_CMD_DUMP_RX          (0x17 << 22)
+#define QE_CMD_LRAM_RD          (0x18 << 22)
+#define QE_CMD_LRAM_RDM         (0x28 << 22)
+#define QE_CMD_LRAM_WR          (0x19 << 22)
+#define QE_CMD_LRAM_WRM         (0x29 << 22)
+#define QE_CMD_LRAM_BSET        (0x1a << 22)
+#define QE_CMD_LRAM_BCLR        (0x1b << 22)
+#define QE_CMD_CONFIG_SEGM      (0x1c << 22)
+#define QE_CMD_READ_SEGM        (0x1d << 22)
+#define QE_CMD_CONFIG_ROUT      (0x1e << 22)
+#define QE_CMD_READ_ROUT        (0x1f << 22)
+#define QE_CMD_CONFIG_TM        (0x20 << 22)
+#define QE_CMD_READ_TM          (0x21 << 22)
+#define QE_CMD_CONFIG_TXBM      (0x22 << 22)
+#define QE_CMD_READ_TXBM        (0x23 << 22)
+#define QE_CMD_CONFIG_RXBM      (0x24 << 22)
+#define QE_CMD_READ_RXBM        (0x25 << 22)
+#define QE_CMD_CONFIG_REAS      (0x26 << 22)
+#define QE_CMD_READ_REAS        (0x27 << 22)
+
+#define QE_TRANSMIT_DE          (0x0 << 30)
+#define QE_CMD_LINKED           (0x1 << 30)
+#define QE_CMD_IMM              (0x2 << 30)
+#define QE_CMD_IMM_INQ          (0x3 << 30)
+
+#define TD_EPI                  (0x1 << 27)
+#define TD_COMMAND              (0x1 << 28)
+
+#define TD_DATA                 (0x0 << 29)
+#define TD_RM_CELL              (0x1 << 29)
+#define TD_OAM_CELL             (0x2 << 29)
+#define TD_OAM_CELL_SEGMENT     (0x3 << 29)
+
+#define TD_BPI                  (0x1 << 20)
+
+#define FP_FLAGS_EPI            (0x1 << 27)
+
+
+#define TX_PQ(i)  (0x00  + (i) * 0x10)
+#define TXB_RQ    (0x20)
+#define ST_Q      (0x48)
+#define RXB_FP(i) (0x90  + (i) * 0x14)
+#define RXB_RQ(i) (0x134 + (i) * 0x14)
+
+
+#define TXQ_HP 0
+#define TXQ_LP 1
+
+/* Phew. You don't want to know how many revisions these simple queue
+ * address macros went through before I got them nice and compact as
+ * they are now. -- REW
+ */
+
+
+/* And now for something completely different: 
+ * The rest of the registers... */
+
+
+#define CMDR0 0x34
+#define CMDR1 0x38
+#define CMDR2 0x3c
+#define CMDR3 0x40
+
+
+#define SARMODE0     0x5c
+
+#define SARMODE0_TXVCS_0    (0x0 << 0)
+#define SARMODE0_TXVCS_1k   (0x1 << 0)
+#define SARMODE0_TXVCS_2k   (0x2 << 0)
+#define SARMODE0_TXVCS_4k   (0x3 << 0)
+#define SARMODE0_TXVCS_8k   (0x4 << 0)
+#define SARMODE0_TXVCS_16k  (0x5 << 0)
+#define SARMODE0_TXVCS_32k  (0x6 << 0)
+#define SARMODE0_TXVCS_64k  (0x7 << 0)
+#define SARMODE0_TXVCS_32   (0x8 << 0)
+
+#define SARMODE0_ABRVCS_0   (0x0 << 4)
+#define SARMODE0_ABRVCS_512 (0x1 << 4)
+#define SARMODE0_ABRVCS_1k  (0x2 << 4)
+#define SARMODE0_ABRVCS_2k  (0x3 << 4)
+#define SARMODE0_ABRVCS_4k  (0x4 << 4)
+#define SARMODE0_ABRVCS_8k  (0x5 << 4)
+#define SARMODE0_ABRVCS_16k (0x6 << 4)
+#define SARMODE0_ABRVCS_32k (0x7 << 4)
+#define SARMODE0_ABRVCS_32  (0x9 << 4) /* The others are "8", this one really has to 
+					  be 9. Tell me you don't believe me. -- REW */
+
+#define SARMODE0_RXVCS_0    (0x0 << 8)
+#define SARMODE0_RXVCS_1k   (0x1 << 8)
+#define SARMODE0_RXVCS_2k   (0x2 << 8)
+#define SARMODE0_RXVCS_4k   (0x3 << 8)
+#define SARMODE0_RXVCS_8k   (0x4 << 8)
+#define SARMODE0_RXVCS_16k  (0x5 << 8)
+#define SARMODE0_RXVCS_32k  (0x6 << 8)
+#define SARMODE0_RXVCS_64k  (0x7 << 8)
+#define SARMODE0_RXVCS_32   (0x8 << 8) 
+
+#define SARMODE0_CALSUP_1  (0x0 << 12)
+#define SARMODE0_CALSUP_2  (0x1 << 12)
+#define SARMODE0_CALSUP_3  (0x2 << 12)
+#define SARMODE0_CALSUP_4  (0x3 << 12)
+
+#define SARMODE0_PRPWT_FS50_0  (0x0 << 14)
+#define SARMODE0_PRPWT_FS50_2  (0x1 << 14)
+#define SARMODE0_PRPWT_FS50_5  (0x2 << 14)
+#define SARMODE0_PRPWT_FS50_11 (0x3 << 14)
+
+#define SARMODE0_PRPWT_FS155_0 (0x0 << 14)
+#define SARMODE0_PRPWT_FS155_1 (0x1 << 14)
+#define SARMODE0_PRPWT_FS155_2 (0x2 << 14)
+#define SARMODE0_PRPWT_FS155_3 (0x3 << 14)
+
+#define SARMODE0_SRTS0     (0x1 << 23)
+#define SARMODE0_SRTS1     (0x1 << 24)
+
+#define SARMODE0_RUN       (0x1 << 25)
+
+#define SARMODE0_UNLOCK    (0x1 << 26)
+#define SARMODE0_CWRE      (0x1 << 27)
+
+
+#define SARMODE0_INTMODE_READCLEAR          (0x0 << 28)
+#define SARMODE0_INTMODE_READNOCLEAR        (0x1 << 28)
+#define SARMODE0_INTMODE_READNOCLEARINHIBIT (0x2 << 28)
+#define SARMODE0_INTMODE_READCLEARINHIBIT   (0x3 << 28)  /* Tell me you don't believe me. */
+
+#define SARMODE0_GINT      (0x1 << 30)
+#define SARMODE0_SHADEN    (0x1 << 31)
+
+
+#define SARMODE1     0x60
+
+
+#define SARMODE1_TRTL_SHIFT 0   /* Program to 0 */
+#define SARMODE1_RRTL_SHIFT 4   /* Program to 0 */
+
+#define SARMODE1_TAGM       (0x1 <<  8)  /* Program to 0 */
+
+#define SARMODE1_HECM0      (0x1 <<  9)
+#define SARMODE1_HECM1      (0x1 << 10)
+#define SARMODE1_HECM2      (0x1 << 11)
+
+#define SARMODE1_GFCE       (0x1 << 14)
+#define SARMODE1_GFCR       (0x1 << 15)
+#define SARMODE1_PMS        (0x1 << 18)
+#define SARMODE1_GPRI       (0x1 << 19)
+#define SARMODE1_GPAS       (0x1 << 20)
+#define SARMODE1_GVAS       (0x1 << 21)
+#define SARMODE1_GNAM       (0x1 << 22)
+#define SARMODE1_GPLEN      (0x1 << 23)
+#define SARMODE1_DUMPE      (0x1 << 24)
+#define SARMODE1_OAMCRC     (0x1 << 25)
+#define SARMODE1_DCOAM      (0x1 << 26)
+#define SARMODE1_DCRM       (0x1 << 27)
+#define SARMODE1_TSTLP      (0x1 << 28)
+#define SARMODE1_DEFHEC     (0x1 << 29)
+
+
+#define ISR      0x64
+#define IUSR     0x68
+#define IMR      0x6c
+
+#define ISR_LPCO          (0x1 <<  0)
+#define ISR_DPCO          (0x1 <<  1)
+#define ISR_RBRQ0_W       (0x1 <<  2)
+#define ISR_RBRQ1_W       (0x1 <<  3)
+#define ISR_RBRQ2_W       (0x1 <<  4)
+#define ISR_RBRQ3_W       (0x1 <<  5)
+#define ISR_RBRQ0_NF      (0x1 <<  6)
+#define ISR_RBRQ1_NF      (0x1 <<  7)
+#define ISR_RBRQ2_NF      (0x1 <<  8)
+#define ISR_RBRQ3_NF      (0x1 <<  9)
+#define ISR_BFP_SC        (0x1 << 10)
+#define ISR_INIT          (0x1 << 11)
+#define ISR_INIT_ERR      (0x1 << 12) /* Documented as "reserved" */
+#define ISR_USCEO         (0x1 << 13)
+#define ISR_UPEC0         (0x1 << 14)
+#define ISR_VPFCO         (0x1 << 15)
+#define ISR_CRCCO         (0x1 << 16)
+#define ISR_HECO          (0x1 << 17)
+#define ISR_TBRQ_W        (0x1 << 18)
+#define ISR_TBRQ_NF       (0x1 << 19)
+#define ISR_CTPQ_E        (0x1 << 20)
+#define ISR_GFC_C0        (0x1 << 21)
+#define ISR_PCI_FTL       (0x1 << 22)
+#define ISR_CSQ_W         (0x1 << 23)
+#define ISR_CSQ_NF        (0x1 << 24)
+#define ISR_EXT_INT       (0x1 << 25)
+#define ISR_RXDMA_S       (0x1 << 26)
+
+
+#define TMCONF 0x78
+/* Bits? */
+
+
+#define CALPRESCALE 0x7c
+/* Bits? */
+
+#define CELLOSCONF 0x84
+#define CELLOSCONF_COTS   (0x1 << 28)
+#define CELLOSCONF_CEN    (0x1 << 27)
+#define CELLOSCONF_SC8    (0x3 << 24)
+#define CELLOSCONF_SC4    (0x2 << 24)
+#define CELLOSCONF_SC2    (0x1 << 24)
+#define CELLOSCONF_SC1    (0x0 << 24)
+
+#define CELLOSCONF_COBS   (0x1 << 16)
+#define CELLOSCONF_COPK   (0x1 <<  8)
+#define CELLOSCONF_COST   (0x1 <<  0)
+/* Bits? */
+
+#define RAS0 0x1bc
+#define RAS0_DCD_XHLT (0x1 << 31)
+
+#define RAS0_VPSEL    (0x1 << 16)
+#define RAS0_VCSEL    (0x1 <<  0)
+
+#define RAS1 0x1c0
+#define RAS1_UTREG    (0x1 << 5)
+
+
+#define DMAMR 0x1cc
+#define DMAMR_TX_MODE_FULL (0x0 << 0)
+#define DMAMR_TX_MODE_PART (0x1 << 0)
+#define DMAMR_TX_MODE_NONE (0x2 << 0) /* And 3 */
+
+
+
+#define RAS2 0x280
+
+#define RAS2_NNI  (0x1 << 0)
+#define RAS2_USEL (0x1 << 1)
+#define RAS2_UBS  (0x1 << 2)
+
+
+
+struct fs_transmit_config {
+	u32 flags;
+	u32 atm_hdr;
+	u32 TMC[4];
+	u32 spec;
+	u32 rtag[3];
+};
+
+#define TC_FLAGS_AAL5      (0x0 << 29)
+#define TC_FLAGS_TRANSPARENT_PAYLOAD (0x1 << 29)
+#define TC_FLAGS_TRANSPARENT_CELL    (0x2 << 29)
+#define TC_FLAGS_STREAMING (0x1 << 28)
+#define TC_FLAGS_PACKET    (0x0) 
+#define TC_FLAGS_TYPE_ABR  (0x0 << 22)
+#define TC_FLAGS_TYPE_CBR  (0x1 << 22)
+#define TC_FLAGS_TYPE_VBR  (0x2 << 22)
+#define TC_FLAGS_TYPE_UBR  (0x3 << 22)
+#define TC_FLAGS_CAL0      (0x0 << 20)
+#define TC_FLAGS_CAL1      (0x1 << 20)
+#define TC_FLAGS_CAL2      (0x2 << 20)
+#define TC_FLAGS_CAL3      (0x3 << 20)
+
+
+#define RC_FLAGS_NAM        (0x1 << 13)
+#define RC_FLAGS_RXBM_PSB   (0x0 << 14)
+#define RC_FLAGS_RXBM_CIF   (0x1 << 14)
+#define RC_FLAGS_RXBM_PMB   (0x2 << 14)
+#define RC_FLAGS_RXBM_STR   (0x4 << 14)
+#define RC_FLAGS_RXBM_SAF   (0x6 << 14)
+#define RC_FLAGS_RXBM_POS   (0x6 << 14)
+#define RC_FLAGS_BFPS       (0x1 << 17)
+
+#define RC_FLAGS_BFPS_BFP   (0x1 << 17)
+
+#define RC_FLAGS_BFPS_BFP0  (0x0 << 17)
+#define RC_FLAGS_BFPS_BFP1  (0x1 << 17)
+#define RC_FLAGS_BFPS_BFP2  (0x2 << 17)
+#define RC_FLAGS_BFPS_BFP3  (0x3 << 17)
+#define RC_FLAGS_BFPS_BFP4  (0x4 << 17)
+#define RC_FLAGS_BFPS_BFP5  (0x5 << 17)
+#define RC_FLAGS_BFPS_BFP6  (0x6 << 17)
+#define RC_FLAGS_BFPS_BFP7  (0x7 << 17)
+#define RC_FLAGS_BFPS_BFP01 (0x8 << 17)
+#define RC_FLAGS_BFPS_BFP23 (0x9 << 17)
+#define RC_FLAGS_BFPS_BFP45 (0xa << 17)
+#define RC_FLAGS_BFPS_BFP67 (0xb << 17)
+#define RC_FLAGS_BFPS_BFP07 (0xc << 17)
+#define RC_FLAGS_BFPS_BFP27 (0xd << 17)
+#define RC_FLAGS_BFPS_BFP47 (0xe << 17)
+
+#define RC_FLAGS_BFPS       (0x1 << 17)
+#define RC_FLAGS_BFPP       (0x1 << 21)
+#define RC_FLAGS_TEVC       (0x1 << 22)
+#define RC_FLAGS_TEP        (0x1 << 23)
+#define RC_FLAGS_AAL5       (0x0 << 24)
+#define RC_FLAGS_TRANSP     (0x1 << 24)
+#define RC_FLAGS_TRANSC     (0x2 << 24)
+#define RC_FLAGS_ML         (0x1 << 27)
+#define RC_FLAGS_TRBRM      (0x1 << 28)
+#define RC_FLAGS_PRI        (0x1 << 29)
+#define RC_FLAGS_HOAM       (0x1 << 30)
+#define RC_FLAGS_CRC10      (0x1 << 31)
+
+
+#define RAC 0x1c8
+#define RAM 0x1c4
+
+
+
+/************************************************************************
+ *         Then the datastructures that the DRIVER uses.                *
+ ************************************************************************/
+
+#define TXQ_NENTRIES  32
+#define RXRQ_NENTRIES 1024
+
+
+struct fs_vcc {
+	int channo;
+	wait_queue_head_t close_wait;
+	struct sk_buff *last_skb;
+};
+
+
+struct queue {
+	struct FS_QENTRY *sa, *ea;  
+	int offset;
+};
+
+struct freepool {
+	int offset;
+	int bufsize;
+	int nr_buffers;
+	int n;
+};
+
+
+struct fs_dev {
+	struct fs_dev *next;		/* other FS devices */
+	int flags;
+
+	unsigned char irq;		/* IRQ */
+	struct pci_dev *pci_dev;	/* PCI stuff */
+	struct atm_dev *atm_dev;
+	struct timer_list timer;
+
+	unsigned long hw_base;		/* mem base address */
+	void __iomem *base;             /* Mapping of base address */
+	int channo;
+	unsigned long channel_mask;
+
+	struct queue    hp_txq, lp_txq, tx_relq, st_q;
+	struct freepool rx_fp[FS_NR_FREE_POOLS];
+	struct queue    rx_rq[FS_NR_RX_QUEUES];
+
+	int nchannels;
+	struct atm_vcc **atm_vccs;
+	void *tx_inuse;
+	int ntxpckts;
+};
+
+
+
+
+/* Number of channesl that the FS50 supports. */
+#define FS50_CHANNEL_BITS  5
+#define FS50_NR_CHANNELS      (1 << FS50_CHANNEL_BITS)
+
+         
+#define FS_DEV(atm_dev) ((struct fs_dev *) (atm_dev)->dev_data)
+#define FS_VCC(atm_vcc) ((struct fs_vcc *) (atm_vcc)->dev_data)
+
+
+#define FS_IS50  0x1
+#define FS_IS155 0x2
+
+#define IS_FS50(dev)  (dev->flags & FS_IS50)
+#define IS_FS155(dev) (dev->flags & FS_IS155)
+ 
+/* Within limits this is user-configurable. */
+/* Note: Currently the sum (10 -> 1k channels) is hardcoded in the driver. */
+#define FS155_VPI_BITS 4
+#define FS155_VCI_BITS 6
+
+#define FS155_CHANNEL_BITS  (FS155_VPI_BITS + FS155_VCI_BITS)
+#define FS155_NR_CHANNELS   (1 << FS155_CHANNEL_BITS)
diff --git a/drivers/atm/fore200e.c b/drivers/atm/fore200e.c
new file mode 100644
index 0000000..196b336
--- /dev/null
+++ b/drivers/atm/fore200e.c
@@ -0,0 +1,3249 @@
+/*
+  $Id: fore200e.c,v 1.5 2000/04/14 10:10:34 davem Exp $
+
+  A FORE Systems 200E-series driver for ATM on Linux.
+  Christophe Lizzi (lizzi@cnam.fr), October 1999-March 2003.
+
+  Based on the PCA-200E driver from Uwe Dannowski (Uwe.Dannowski@inf.tu-dresden.de).
+
+  This driver simultaneously supports PCA-200E and SBA-200E adapters
+  on i386, alpha (untested), powerpc, sparc and sparc64 architectures.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/capability.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/atm_suni.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/string.h>
+#include <asm/page.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#ifdef CONFIG_ATM_FORE200E_SBA
+#include <asm/idprom.h>
+#include <asm/sbus.h>
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/pgtable.h>
+#endif
+
+#if defined(CONFIG_ATM_FORE200E_USE_TASKLET) /* defer interrupt work to a tasklet */
+#define FORE200E_USE_TASKLET
+#endif
+
+#if 0 /* enable the debugging code of the buffer supply queues */
+#define FORE200E_BSQ_DEBUG
+#endif
+
+#if 1 /* ensure correct handling of 52-byte AAL0 SDUs expected by atmdump-like apps */
+#define FORE200E_52BYTE_AAL0_SDU
+#endif
+
+#include "fore200e.h"
+#include "suni.h"
+
+#define FORE200E_VERSION "0.3e"
+
+#define FORE200E         "fore200e: "
+
+#if 0 /* override .config */
+#define CONFIG_ATM_FORE200E_DEBUG 1
+#endif
+#if defined(CONFIG_ATM_FORE200E_DEBUG) && (CONFIG_ATM_FORE200E_DEBUG > 0)
+#define DPRINTK(level, format, args...)  do { if (CONFIG_ATM_FORE200E_DEBUG >= (level)) \
+                                                  printk(FORE200E format, ##args); } while (0)
+#else
+#define DPRINTK(level, format, args...)  do {} while (0)
+#endif
+
+
+#define FORE200E_ALIGN(addr, alignment) \
+        ((((unsigned long)(addr) + (alignment - 1)) & ~(alignment - 1)) - (unsigned long)(addr))
+
+#define FORE200E_DMA_INDEX(dma_addr, type, index)  ((dma_addr) + (index) * sizeof(type))
+
+#define FORE200E_INDEX(virt_addr, type, index)     (&((type *)(virt_addr))[ index ])
+
+#define FORE200E_NEXT_ENTRY(index, modulo)         (index = ++(index) % (modulo))
+
+#if 1
+#define ASSERT(expr)     if (!(expr)) { \
+			     printk(FORE200E "assertion failed! %s[%d]: %s\n", \
+				    __FUNCTION__, __LINE__, #expr); \
+			     panic(FORE200E "%s", __FUNCTION__); \
+			 }
+#else
+#define ASSERT(expr)     do {} while (0)
+#endif
+
+
+static const struct atmdev_ops   fore200e_ops;
+static const struct fore200e_bus fore200e_bus[];
+
+static LIST_HEAD(fore200e_boards);
+
+
+MODULE_AUTHOR("Christophe Lizzi - credits to Uwe Dannowski and Heikki Vatiainen");
+MODULE_DESCRIPTION("FORE Systems 200E-series ATM driver - version " FORE200E_VERSION);
+MODULE_SUPPORTED_DEVICE("PCA-200E, SBA-200E");
+
+
+static const int fore200e_rx_buf_nbr[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ] = {
+    { BUFFER_S1_NBR, BUFFER_L1_NBR },
+    { BUFFER_S2_NBR, BUFFER_L2_NBR }
+};
+
+static const int fore200e_rx_buf_size[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ] = {
+    { BUFFER_S1_SIZE, BUFFER_L1_SIZE },
+    { BUFFER_S2_SIZE, BUFFER_L2_SIZE }
+};
+
+
+#if defined(CONFIG_ATM_FORE200E_DEBUG) && (CONFIG_ATM_FORE200E_DEBUG > 0)
+static const char* fore200e_traffic_class[] = { "NONE", "UBR", "CBR", "VBR", "ABR", "ANY" };
+#endif
+
+
+#if 0 /* currently unused */
+static int 
+fore200e_fore2atm_aal(enum fore200e_aal aal)
+{
+    switch(aal) {
+    case FORE200E_AAL0:  return ATM_AAL0;
+    case FORE200E_AAL34: return ATM_AAL34;
+    case FORE200E_AAL5:  return ATM_AAL5;
+    }
+
+    return -EINVAL;
+}
+#endif
+
+
+static enum fore200e_aal
+fore200e_atm2fore_aal(int aal)
+{
+    switch(aal) {
+    case ATM_AAL0:  return FORE200E_AAL0;
+    case ATM_AAL34: return FORE200E_AAL34;
+    case ATM_AAL1:
+    case ATM_AAL2:
+    case ATM_AAL5:  return FORE200E_AAL5;
+    }
+
+    return -EINVAL;
+}
+
+
+static char*
+fore200e_irq_itoa(int irq)
+{
+#if defined(__sparc_v9__)
+    return __irq_itoa(irq);
+#else
+    static char str[8];
+    sprintf(str, "%d", irq);
+    return str;
+#endif
+}
+
+
+static void*
+fore200e_kmalloc(int size, int flags)
+{
+    void* chunk = kmalloc(size, flags);
+
+    if (chunk)
+	memset(chunk, 0x00, size);
+    else
+	printk(FORE200E "kmalloc() failed, requested size = %d, flags = 0x%x\n", size, flags);
+    
+    return chunk;
+}
+
+
+static void
+fore200e_kfree(void* chunk)
+{
+    kfree(chunk);
+}
+
+
+/* allocate and align a chunk of memory intended to hold the data behing exchanged
+   between the driver and the adapter (using streaming DVMA) */
+
+static int
+fore200e_chunk_alloc(struct fore200e* fore200e, struct chunk* chunk, int size, int alignment, int direction)
+{
+    unsigned long offset = 0;
+
+    if (alignment <= sizeof(int))
+	alignment = 0;
+
+    chunk->alloc_size = size + alignment;
+    chunk->align_size = size;
+    chunk->direction  = direction;
+
+    chunk->alloc_addr = fore200e_kmalloc(chunk->alloc_size, GFP_KERNEL | GFP_DMA);
+    if (chunk->alloc_addr == NULL)
+	return -ENOMEM;
+
+    if (alignment > 0)
+	offset = FORE200E_ALIGN(chunk->alloc_addr, alignment); 
+    
+    chunk->align_addr = chunk->alloc_addr + offset;
+
+    chunk->dma_addr = fore200e->bus->dma_map(fore200e, chunk->align_addr, chunk->align_size, direction);
+    
+    return 0;
+}
+
+
+/* free a chunk of memory */
+
+static void
+fore200e_chunk_free(struct fore200e* fore200e, struct chunk* chunk)
+{
+    fore200e->bus->dma_unmap(fore200e, chunk->dma_addr, chunk->dma_size, chunk->direction);
+
+    fore200e_kfree(chunk->alloc_addr);
+}
+
+
+static void
+fore200e_spin(int msecs)
+{
+    unsigned long timeout = jiffies + msecs_to_jiffies(msecs);
+    while (time_before(jiffies, timeout));
+}
+
+
+static int
+fore200e_poll(struct fore200e* fore200e, volatile u32* addr, u32 val, int msecs)
+{
+    unsigned long timeout = jiffies + msecs_to_jiffies(msecs);
+    int           ok;
+
+    mb();
+    do {
+	if ((ok = (*addr == val)) || (*addr & STATUS_ERROR))
+	    break;
+
+    } while (time_before(jiffies, timeout));
+
+#if 1
+    if (!ok) {
+	printk(FORE200E "cmd polling failed, got status 0x%08x, expected 0x%08x\n",
+	       *addr, val);
+    }
+#endif
+
+    return ok;
+}
+
+
+static int
+fore200e_io_poll(struct fore200e* fore200e, volatile u32 __iomem *addr, u32 val, int msecs)
+{
+    unsigned long timeout = jiffies + msecs_to_jiffies(msecs);
+    int           ok;
+
+    do {
+	if ((ok = (fore200e->bus->read(addr) == val)))
+	    break;
+
+    } while (time_before(jiffies, timeout));
+
+#if 1
+    if (!ok) {
+	printk(FORE200E "I/O polling failed, got status 0x%08x, expected 0x%08x\n",
+	       fore200e->bus->read(addr), val);
+    }
+#endif
+
+    return ok;
+}
+
+
+static void
+fore200e_free_rx_buf(struct fore200e* fore200e)
+{
+    int scheme, magn, nbr;
+    struct buffer* buffer;
+
+    for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) {
+	for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) {
+
+	    if ((buffer = fore200e->host_bsq[ scheme ][ magn ].buffer) != NULL) {
+
+		for (nbr = 0; nbr < fore200e_rx_buf_nbr[ scheme ][ magn ]; nbr++) {
+
+		    struct chunk* data = &buffer[ nbr ].data;
+
+		    if (data->alloc_addr != NULL)
+			fore200e_chunk_free(fore200e, data);
+		}
+	    }
+	}
+    }
+}
+
+
+static void
+fore200e_uninit_bs_queue(struct fore200e* fore200e)
+{
+    int scheme, magn;
+    
+    for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) {
+	for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) {
+
+	    struct chunk* status    = &fore200e->host_bsq[ scheme ][ magn ].status;
+	    struct chunk* rbd_block = &fore200e->host_bsq[ scheme ][ magn ].rbd_block;
+	    
+	    if (status->alloc_addr)
+		fore200e->bus->dma_chunk_free(fore200e, status);
+	    
+	    if (rbd_block->alloc_addr)
+		fore200e->bus->dma_chunk_free(fore200e, rbd_block);
+	}
+    }
+}
+
+
+static int
+fore200e_reset(struct fore200e* fore200e, int diag)
+{
+    int ok;
+
+    fore200e->cp_monitor = fore200e->virt_base + FORE200E_CP_MONITOR_OFFSET;
+    
+    fore200e->bus->write(BSTAT_COLD_START, &fore200e->cp_monitor->bstat);
+
+    fore200e->bus->reset(fore200e);
+
+    if (diag) {
+	ok = fore200e_io_poll(fore200e, &fore200e->cp_monitor->bstat, BSTAT_SELFTEST_OK, 1000);
+	if (ok == 0) {
+	    
+	    printk(FORE200E "device %s self-test failed\n", fore200e->name);
+	    return -ENODEV;
+	}
+
+	printk(FORE200E "device %s self-test passed\n", fore200e->name);
+	
+	fore200e->state = FORE200E_STATE_RESET;
+    }
+
+    return 0;
+}
+
+
+static void
+fore200e_shutdown(struct fore200e* fore200e)
+{
+    printk(FORE200E "removing device %s at 0x%lx, IRQ %s\n",
+	   fore200e->name, fore200e->phys_base, 
+	   fore200e_irq_itoa(fore200e->irq));
+    
+    if (fore200e->state > FORE200E_STATE_RESET) {
+	/* first, reset the board to prevent further interrupts or data transfers */
+	fore200e_reset(fore200e, 0);
+    }
+    
+    /* then, release all allocated resources */
+    switch(fore200e->state) {
+
+    case FORE200E_STATE_COMPLETE:
+	if (fore200e->stats)
+	    kfree(fore200e->stats);
+
+    case FORE200E_STATE_IRQ:
+	free_irq(fore200e->irq, fore200e->atm_dev);
+
+    case FORE200E_STATE_ALLOC_BUF:
+	fore200e_free_rx_buf(fore200e);
+
+    case FORE200E_STATE_INIT_BSQ:
+	fore200e_uninit_bs_queue(fore200e);
+
+    case FORE200E_STATE_INIT_RXQ:
+	fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_rxq.status);
+	fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_rxq.rpd);
+
+    case FORE200E_STATE_INIT_TXQ:
+	fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_txq.status);
+	fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_txq.tpd);
+
+    case FORE200E_STATE_INIT_CMDQ:
+	fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_cmdq.status);
+
+    case FORE200E_STATE_INITIALIZE:
+	/* nothing to do for that state */
+
+    case FORE200E_STATE_START_FW:
+	/* nothing to do for that state */
+
+    case FORE200E_STATE_LOAD_FW:
+	/* nothing to do for that state */
+
+    case FORE200E_STATE_RESET:
+	/* nothing to do for that state */
+
+    case FORE200E_STATE_MAP:
+	fore200e->bus->unmap(fore200e);
+
+    case FORE200E_STATE_CONFIGURE:
+	/* nothing to do for that state */
+
+    case FORE200E_STATE_REGISTER:
+	/* XXX shouldn't we *start* by deregistering the device? */
+	atm_dev_deregister(fore200e->atm_dev);
+
+    case FORE200E_STATE_BLANK:
+	/* nothing to do for that state */
+	break;
+    }
+}
+
+
+#ifdef CONFIG_ATM_FORE200E_PCA
+
+static u32 fore200e_pca_read(volatile u32 __iomem *addr)
+{
+    /* on big-endian hosts, the board is configured to convert
+       the endianess of slave RAM accesses  */
+    return le32_to_cpu(readl(addr));
+}
+
+
+static void fore200e_pca_write(u32 val, volatile u32 __iomem *addr)
+{
+    /* on big-endian hosts, the board is configured to convert
+       the endianess of slave RAM accesses  */
+    writel(cpu_to_le32(val), addr);
+}
+
+
+static u32
+fore200e_pca_dma_map(struct fore200e* fore200e, void* virt_addr, int size, int direction)
+{
+    u32 dma_addr = pci_map_single((struct pci_dev*)fore200e->bus_dev, virt_addr, size, direction);
+
+    DPRINTK(3, "PCI DVMA mapping: virt_addr = 0x%p, size = %d, direction = %d,  --> dma_addr = 0x%08x\n",
+	    virt_addr, size, direction, dma_addr);
+    
+    return dma_addr;
+}
+
+
+static void
+fore200e_pca_dma_unmap(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+    DPRINTK(3, "PCI DVMA unmapping: dma_addr = 0x%08x, size = %d, direction = %d\n",
+	    dma_addr, size, direction);
+
+    pci_unmap_single((struct pci_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+static void
+fore200e_pca_dma_sync_for_cpu(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+    DPRINTK(3, "PCI DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+    pci_dma_sync_single_for_cpu((struct pci_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+static void
+fore200e_pca_dma_sync_for_device(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+    DPRINTK(3, "PCI DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+    pci_dma_sync_single_for_device((struct pci_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+/* allocate a DMA consistent chunk of memory intended to act as a communication mechanism
+   (to hold descriptors, status, queues, etc.) shared by the driver and the adapter */
+
+static int
+fore200e_pca_dma_chunk_alloc(struct fore200e* fore200e, struct chunk* chunk,
+			     int size, int nbr, int alignment)
+{
+    /* returned chunks are page-aligned */
+    chunk->alloc_size = size * nbr;
+    chunk->alloc_addr = pci_alloc_consistent((struct pci_dev*)fore200e->bus_dev,
+					     chunk->alloc_size,
+					     &chunk->dma_addr);
+    
+    if ((chunk->alloc_addr == NULL) || (chunk->dma_addr == 0))
+	return -ENOMEM;
+
+    chunk->align_addr = chunk->alloc_addr;
+    
+    return 0;
+}
+
+
+/* free a DMA consistent chunk of memory */
+
+static void
+fore200e_pca_dma_chunk_free(struct fore200e* fore200e, struct chunk* chunk)
+{
+    pci_free_consistent((struct pci_dev*)fore200e->bus_dev,
+			chunk->alloc_size,
+			chunk->alloc_addr,
+			chunk->dma_addr);
+}
+
+
+static int
+fore200e_pca_irq_check(struct fore200e* fore200e)
+{
+    /* this is a 1 bit register */
+    int irq_posted = readl(fore200e->regs.pca.psr);
+
+#if defined(CONFIG_ATM_FORE200E_DEBUG) && (CONFIG_ATM_FORE200E_DEBUG == 2)
+    if (irq_posted && (readl(fore200e->regs.pca.hcr) & PCA200E_HCR_OUTFULL)) {
+	DPRINTK(2,"FIFO OUT full, device %d\n", fore200e->atm_dev->number);
+    }
+#endif
+
+    return irq_posted;
+}
+
+
+static void
+fore200e_pca_irq_ack(struct fore200e* fore200e)
+{
+    writel(PCA200E_HCR_CLRINTR, fore200e->regs.pca.hcr);
+}
+
+
+static void
+fore200e_pca_reset(struct fore200e* fore200e)
+{
+    writel(PCA200E_HCR_RESET, fore200e->regs.pca.hcr);
+    fore200e_spin(10);
+    writel(0, fore200e->regs.pca.hcr);
+}
+
+
+static int __init
+fore200e_pca_map(struct fore200e* fore200e)
+{
+    DPRINTK(2, "device %s being mapped in memory\n", fore200e->name);
+
+    fore200e->virt_base = ioremap(fore200e->phys_base, PCA200E_IOSPACE_LENGTH);
+    
+    if (fore200e->virt_base == NULL) {
+	printk(FORE200E "can't map device %s\n", fore200e->name);
+	return -EFAULT;
+    }
+
+    DPRINTK(1, "device %s mapped to 0x%p\n", fore200e->name, fore200e->virt_base);
+
+    /* gain access to the PCA specific registers  */
+    fore200e->regs.pca.hcr = fore200e->virt_base + PCA200E_HCR_OFFSET;
+    fore200e->regs.pca.imr = fore200e->virt_base + PCA200E_IMR_OFFSET;
+    fore200e->regs.pca.psr = fore200e->virt_base + PCA200E_PSR_OFFSET;
+
+    fore200e->state = FORE200E_STATE_MAP;
+    return 0;
+}
+
+
+static void
+fore200e_pca_unmap(struct fore200e* fore200e)
+{
+    DPRINTK(2, "device %s being unmapped from memory\n", fore200e->name);
+
+    if (fore200e->virt_base != NULL)
+	iounmap(fore200e->virt_base);
+}
+
+
+static int __init
+fore200e_pca_configure(struct fore200e* fore200e)
+{
+    struct pci_dev* pci_dev = (struct pci_dev*)fore200e->bus_dev;
+    u8              master_ctrl, latency;
+
+    DPRINTK(2, "device %s being configured\n", fore200e->name);
+
+    if ((pci_dev->irq == 0) || (pci_dev->irq == 0xFF)) {
+	printk(FORE200E "incorrect IRQ setting - misconfigured PCI-PCI bridge?\n");
+	return -EIO;
+    }
+
+    pci_read_config_byte(pci_dev, PCA200E_PCI_MASTER_CTRL, &master_ctrl);
+
+    master_ctrl = master_ctrl
+#if defined(__BIG_ENDIAN)
+	/* request the PCA board to convert the endianess of slave RAM accesses */
+	| PCA200E_CTRL_CONVERT_ENDIAN
+#endif
+#if 0
+        | PCA200E_CTRL_DIS_CACHE_RD
+        | PCA200E_CTRL_DIS_WRT_INVAL
+        | PCA200E_CTRL_ENA_CONT_REQ_MODE
+        | PCA200E_CTRL_2_CACHE_WRT_INVAL
+#endif
+	| PCA200E_CTRL_LARGE_PCI_BURSTS;
+    
+    pci_write_config_byte(pci_dev, PCA200E_PCI_MASTER_CTRL, master_ctrl);
+
+    /* raise latency from 32 (default) to 192, as this seems to prevent NIC
+       lockups (under heavy rx loads) due to continuous 'FIFO OUT full' condition.
+       this may impact the performances of other PCI devices on the same bus, though */
+    latency = 192;
+    pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
+
+    fore200e->state = FORE200E_STATE_CONFIGURE;
+    return 0;
+}
+
+
+static int __init
+fore200e_pca_prom_read(struct fore200e* fore200e, struct prom_data* prom)
+{
+    struct host_cmdq*       cmdq  = &fore200e->host_cmdq;
+    struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ];
+    struct prom_opcode      opcode;
+    int                     ok;
+    u32                     prom_dma;
+
+    FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD);
+
+    opcode.opcode = OPCODE_GET_PROM;
+    opcode.pad    = 0;
+
+    prom_dma = fore200e->bus->dma_map(fore200e, prom, sizeof(struct prom_data), DMA_FROM_DEVICE);
+
+    fore200e->bus->write(prom_dma, &entry->cp_entry->cmd.prom_block.prom_haddr);
+    
+    *entry->status = STATUS_PENDING;
+
+    fore200e->bus->write(*(u32*)&opcode, (u32 __iomem *)&entry->cp_entry->cmd.prom_block.opcode);
+
+    ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400);
+
+    *entry->status = STATUS_FREE;
+
+    fore200e->bus->dma_unmap(fore200e, prom_dma, sizeof(struct prom_data), DMA_FROM_DEVICE);
+
+    if (ok == 0) {
+	printk(FORE200E "unable to get PROM data from device %s\n", fore200e->name);
+	return -EIO;
+    }
+
+#if defined(__BIG_ENDIAN)
+    
+#define swap_here(addr) (*((u32*)(addr)) = swab32( *((u32*)(addr)) ))
+
+    /* MAC address is stored as little-endian */
+    swap_here(&prom->mac_addr[0]);
+    swap_here(&prom->mac_addr[4]);
+#endif
+    
+    return 0;
+}
+
+
+static int
+fore200e_pca_proc_read(struct fore200e* fore200e, char *page)
+{
+    struct pci_dev* pci_dev = (struct pci_dev*)fore200e->bus_dev;
+
+    return sprintf(page, "   PCI bus/slot/function:\t%d/%d/%d\n",
+		   pci_dev->bus->number, PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn));
+}
+
+#endif /* CONFIG_ATM_FORE200E_PCA */
+
+
+#ifdef CONFIG_ATM_FORE200E_SBA
+
+static u32
+fore200e_sba_read(volatile u32 __iomem *addr)
+{
+    return sbus_readl(addr);
+}
+
+
+static void
+fore200e_sba_write(u32 val, volatile u32 __iomem *addr)
+{
+    sbus_writel(val, addr);
+}
+
+
+static u32
+fore200e_sba_dma_map(struct fore200e* fore200e, void* virt_addr, int size, int direction)
+{
+    u32 dma_addr = sbus_map_single((struct sbus_dev*)fore200e->bus_dev, virt_addr, size, direction);
+
+    DPRINTK(3, "SBUS DVMA mapping: virt_addr = 0x%p, size = %d, direction = %d --> dma_addr = 0x%08x\n",
+	    virt_addr, size, direction, dma_addr);
+    
+    return dma_addr;
+}
+
+
+static void
+fore200e_sba_dma_unmap(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+    DPRINTK(3, "SBUS DVMA unmapping: dma_addr = 0x%08x, size = %d, direction = %d,\n",
+	    dma_addr, size, direction);
+
+    sbus_unmap_single((struct sbus_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+static void
+fore200e_sba_dma_sync_for_cpu(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+    DPRINTK(3, "SBUS DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+    
+    sbus_dma_sync_single_for_cpu((struct sbus_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+static void
+fore200e_sba_dma_sync_for_device(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+    DPRINTK(3, "SBUS DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+    sbus_dma_sync_single_for_device((struct sbus_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+/* allocate a DVMA consistent chunk of memory intended to act as a communication mechanism
+   (to hold descriptors, status, queues, etc.) shared by the driver and the adapter */
+
+static int
+fore200e_sba_dma_chunk_alloc(struct fore200e* fore200e, struct chunk* chunk,
+			     int size, int nbr, int alignment)
+{
+    chunk->alloc_size = chunk->align_size = size * nbr;
+
+    /* returned chunks are page-aligned */
+    chunk->alloc_addr = sbus_alloc_consistent((struct sbus_dev*)fore200e->bus_dev,
+					      chunk->alloc_size,
+					      &chunk->dma_addr);
+
+    if ((chunk->alloc_addr == NULL) || (chunk->dma_addr == 0))
+	return -ENOMEM;
+
+    chunk->align_addr = chunk->alloc_addr;
+    
+    return 0;
+}
+
+
+/* free a DVMA consistent chunk of memory */
+
+static void
+fore200e_sba_dma_chunk_free(struct fore200e* fore200e, struct chunk* chunk)
+{
+    sbus_free_consistent((struct sbus_dev*)fore200e->bus_dev,
+			 chunk->alloc_size,
+			 chunk->alloc_addr,
+			 chunk->dma_addr);
+}
+
+
+static void
+fore200e_sba_irq_enable(struct fore200e* fore200e)
+{
+    u32 hcr = fore200e->bus->read(fore200e->regs.sba.hcr) & SBA200E_HCR_STICKY;
+    fore200e->bus->write(hcr | SBA200E_HCR_INTR_ENA, fore200e->regs.sba.hcr);
+}
+
+
+static int
+fore200e_sba_irq_check(struct fore200e* fore200e)
+{
+    return fore200e->bus->read(fore200e->regs.sba.hcr) & SBA200E_HCR_INTR_REQ;
+}
+
+
+static void
+fore200e_sba_irq_ack(struct fore200e* fore200e)
+{
+    u32 hcr = fore200e->bus->read(fore200e->regs.sba.hcr) & SBA200E_HCR_STICKY;
+    fore200e->bus->write(hcr | SBA200E_HCR_INTR_CLR, fore200e->regs.sba.hcr);
+}
+
+
+static void
+fore200e_sba_reset(struct fore200e* fore200e)
+{
+    fore200e->bus->write(SBA200E_HCR_RESET, fore200e->regs.sba.hcr);
+    fore200e_spin(10);
+    fore200e->bus->write(0, fore200e->regs.sba.hcr);
+}
+
+
+static int __init
+fore200e_sba_map(struct fore200e* fore200e)
+{
+    struct sbus_dev* sbus_dev = (struct sbus_dev*)fore200e->bus_dev;
+    unsigned int bursts;
+
+    /* gain access to the SBA specific registers  */
+    fore200e->regs.sba.hcr = sbus_ioremap(&sbus_dev->resource[0], 0, SBA200E_HCR_LENGTH, "SBA HCR");
+    fore200e->regs.sba.bsr = sbus_ioremap(&sbus_dev->resource[1], 0, SBA200E_BSR_LENGTH, "SBA BSR");
+    fore200e->regs.sba.isr = sbus_ioremap(&sbus_dev->resource[2], 0, SBA200E_ISR_LENGTH, "SBA ISR");
+    fore200e->virt_base    = sbus_ioremap(&sbus_dev->resource[3], 0, SBA200E_RAM_LENGTH, "SBA RAM");
+
+    if (fore200e->virt_base == NULL) {
+	printk(FORE200E "unable to map RAM of device %s\n", fore200e->name);
+	return -EFAULT;
+    }
+
+    DPRINTK(1, "device %s mapped to 0x%p\n", fore200e->name, fore200e->virt_base);
+    
+    fore200e->bus->write(0x02, fore200e->regs.sba.isr); /* XXX hardwired interrupt level */
+
+    /* get the supported DVMA burst sizes */
+    bursts = prom_getintdefault(sbus_dev->bus->prom_node, "burst-sizes", 0x00);
+
+    if (sbus_can_dma_64bit(sbus_dev))
+	sbus_set_sbus64(sbus_dev, bursts);
+
+    fore200e->state = FORE200E_STATE_MAP;
+    return 0;
+}
+
+
+static void
+fore200e_sba_unmap(struct fore200e* fore200e)
+{
+    sbus_iounmap(fore200e->regs.sba.hcr, SBA200E_HCR_LENGTH);
+    sbus_iounmap(fore200e->regs.sba.bsr, SBA200E_BSR_LENGTH);
+    sbus_iounmap(fore200e->regs.sba.isr, SBA200E_ISR_LENGTH);
+    sbus_iounmap(fore200e->virt_base,    SBA200E_RAM_LENGTH);
+}
+
+
+static int __init
+fore200e_sba_configure(struct fore200e* fore200e)
+{
+    fore200e->state = FORE200E_STATE_CONFIGURE;
+    return 0;
+}
+
+
+static struct fore200e* __init
+fore200e_sba_detect(const struct fore200e_bus* bus, int index)
+{
+    struct fore200e*          fore200e;
+    struct sbus_bus* sbus_bus;
+    struct sbus_dev* sbus_dev = NULL;
+    
+    unsigned int     count = 0;
+    
+    for_each_sbus (sbus_bus) {
+	for_each_sbusdev (sbus_dev, sbus_bus) {
+	    if (strcmp(sbus_dev->prom_name, SBA200E_PROM_NAME) == 0) {
+		if (count >= index)
+		    goto found;
+		count++;
+	    }
+	}
+    }
+    return NULL;
+    
+  found:
+    if (sbus_dev->num_registers != 4) {
+	printk(FORE200E "this %s device has %d instead of 4 registers\n",
+	       bus->model_name, sbus_dev->num_registers);
+	return NULL;
+    }
+
+    fore200e = fore200e_kmalloc(sizeof(struct fore200e), GFP_KERNEL);
+    if (fore200e == NULL)
+	return NULL;
+
+    fore200e->bus     = bus;
+    fore200e->bus_dev = sbus_dev;
+    fore200e->irq     = sbus_dev->irqs[ 0 ];
+
+    fore200e->phys_base = (unsigned long)sbus_dev;
+
+    sprintf(fore200e->name, "%s-%d", bus->model_name, index - 1);
+    
+    return fore200e;
+}
+
+
+static int __init
+fore200e_sba_prom_read(struct fore200e* fore200e, struct prom_data* prom)
+{
+    struct sbus_dev* sbus_dev = (struct sbus_dev*) fore200e->bus_dev;
+    int                       len;
+
+    len = prom_getproperty(sbus_dev->prom_node, "macaddrlo2", &prom->mac_addr[ 4 ], 4);
+    if (len < 0)
+	return -EBUSY;
+
+    len = prom_getproperty(sbus_dev->prom_node, "macaddrhi4", &prom->mac_addr[ 2 ], 4);
+    if (len < 0)
+	return -EBUSY;
+    
+    prom_getproperty(sbus_dev->prom_node, "serialnumber",
+		     (char*)&prom->serial_number, sizeof(prom->serial_number));
+    
+    prom_getproperty(sbus_dev->prom_node, "promversion",
+		     (char*)&prom->hw_revision, sizeof(prom->hw_revision));
+    
+    return 0;
+}
+
+
+static int
+fore200e_sba_proc_read(struct fore200e* fore200e, char *page)
+{
+    struct sbus_dev* sbus_dev = (struct sbus_dev*)fore200e->bus_dev;
+
+    return sprintf(page, "   SBUS slot/device:\t\t%d/'%s'\n", sbus_dev->slot, sbus_dev->prom_name);
+}
+#endif /* CONFIG_ATM_FORE200E_SBA */
+
+
+static void
+fore200e_tx_irq(struct fore200e* fore200e)
+{
+    struct host_txq*        txq = &fore200e->host_txq;
+    struct host_txq_entry*  entry;
+    struct atm_vcc*         vcc;
+    struct fore200e_vc_map* vc_map;
+
+    if (fore200e->host_txq.txing == 0)
+	return;
+
+    for (;;) {
+	
+	entry = &txq->host_entry[ txq->tail ];
+
+        if ((*entry->status & STATUS_COMPLETE) == 0) {
+	    break;
+	}
+
+	DPRINTK(3, "TX COMPLETED: entry = %p [tail = %d], vc_map = %p, skb = %p\n", 
+		entry, txq->tail, entry->vc_map, entry->skb);
+
+	/* free copy of misaligned data */
+	if (entry->data)
+	    kfree(entry->data);
+	
+	/* remove DMA mapping */
+	fore200e->bus->dma_unmap(fore200e, entry->tpd->tsd[ 0 ].buffer, entry->tpd->tsd[ 0 ].length,
+				 DMA_TO_DEVICE);
+
+	vc_map = entry->vc_map;
+
+	/* vcc closed since the time the entry was submitted for tx? */
+	if ((vc_map->vcc == NULL) ||
+	    (test_bit(ATM_VF_READY, &vc_map->vcc->flags) == 0)) {
+
+	    DPRINTK(1, "no ready vcc found for PDU sent on device %d\n",
+		    fore200e->atm_dev->number);
+
+	    dev_kfree_skb_any(entry->skb);
+	}
+	else {
+	    ASSERT(vc_map->vcc);
+
+	    /* vcc closed then immediately re-opened? */
+	    if (vc_map->incarn != entry->incarn) {
+
+		/* when a vcc is closed, some PDUs may be still pending in the tx queue.
+		   if the same vcc is immediately re-opened, those pending PDUs must
+		   not be popped after the completion of their emission, as they refer
+		   to the prior incarnation of that vcc. otherwise, sk_atm(vcc)->sk_wmem_alloc
+		   would be decremented by the size of the (unrelated) skb, possibly
+		   leading to a negative sk->sk_wmem_alloc count, ultimately freezing the vcc.
+		   we thus bind the tx entry to the current incarnation of the vcc
+		   when the entry is submitted for tx. When the tx later completes,
+		   if the incarnation number of the tx entry does not match the one
+		   of the vcc, then this implies that the vcc has been closed then re-opened.
+		   we thus just drop the skb here. */
+
+		DPRINTK(1, "vcc closed-then-re-opened; dropping PDU sent on device %d\n",
+			fore200e->atm_dev->number);
+
+		dev_kfree_skb_any(entry->skb);
+	    }
+	    else {
+		vcc = vc_map->vcc;
+		ASSERT(vcc);
+
+		/* notify tx completion */
+		if (vcc->pop) {
+		    vcc->pop(vcc, entry->skb);
+		}
+		else {
+		    dev_kfree_skb_any(entry->skb);
+		}
+#if 1
+		/* race fixed by the above incarnation mechanism, but... */
+		if (atomic_read(&sk_atm(vcc)->sk_wmem_alloc) < 0) {
+		    atomic_set(&sk_atm(vcc)->sk_wmem_alloc, 0);
+		}
+#endif
+		/* check error condition */
+		if (*entry->status & STATUS_ERROR)
+		    atomic_inc(&vcc->stats->tx_err);
+		else
+		    atomic_inc(&vcc->stats->tx);
+	    }
+	}
+
+	*entry->status = STATUS_FREE;
+
+	fore200e->host_txq.txing--;
+
+	FORE200E_NEXT_ENTRY(txq->tail, QUEUE_SIZE_TX);
+    }
+}
+
+
+#ifdef FORE200E_BSQ_DEBUG
+int bsq_audit(int where, struct host_bsq* bsq, int scheme, int magn)
+{
+    struct buffer* buffer;
+    int count = 0;
+
+    buffer = bsq->freebuf;
+    while (buffer) {
+
+	if (buffer->supplied) {
+	    printk(FORE200E "bsq_audit(%d): queue %d.%d, buffer %ld supplied but in free list!\n",
+		   where, scheme, magn, buffer->index);
+	}
+
+	if (buffer->magn != magn) {
+	    printk(FORE200E "bsq_audit(%d): queue %d.%d, buffer %ld, unexpected magn = %d\n",
+		   where, scheme, magn, buffer->index, buffer->magn);
+	}
+
+	if (buffer->scheme != scheme) {
+	    printk(FORE200E "bsq_audit(%d): queue %d.%d, buffer %ld, unexpected scheme = %d\n",
+		   where, scheme, magn, buffer->index, buffer->scheme);
+	}
+
+	if ((buffer->index < 0) || (buffer->index >= fore200e_rx_buf_nbr[ scheme ][ magn ])) {
+	    printk(FORE200E "bsq_audit(%d): queue %d.%d, out of range buffer index = %ld !\n",
+		   where, scheme, magn, buffer->index);
+	}
+
+	count++;
+	buffer = buffer->next;
+    }
+
+    if (count != bsq->freebuf_count) {
+	printk(FORE200E "bsq_audit(%d): queue %d.%d, %d bufs in free list, but freebuf_count = %d\n",
+	       where, scheme, magn, count, bsq->freebuf_count);
+    }
+    return 0;
+}
+#endif
+
+
+static void
+fore200e_supply(struct fore200e* fore200e)
+{
+    int  scheme, magn, i;
+
+    struct host_bsq*       bsq;
+    struct host_bsq_entry* entry;
+    struct buffer*         buffer;
+
+    for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) {
+	for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) {
+
+	    bsq = &fore200e->host_bsq[ scheme ][ magn ];
+
+#ifdef FORE200E_BSQ_DEBUG
+	    bsq_audit(1, bsq, scheme, magn);
+#endif
+	    while (bsq->freebuf_count >= RBD_BLK_SIZE) {
+
+		DPRINTK(2, "supplying %d rx buffers to queue %d / %d, freebuf_count = %d\n",
+			RBD_BLK_SIZE, scheme, magn, bsq->freebuf_count);
+
+		entry = &bsq->host_entry[ bsq->head ];
+
+		for (i = 0; i < RBD_BLK_SIZE; i++) {
+
+		    /* take the first buffer in the free buffer list */
+		    buffer = bsq->freebuf;
+		    if (!buffer) {
+			printk(FORE200E "no more free bufs in queue %d.%d, but freebuf_count = %d\n",
+			       scheme, magn, bsq->freebuf_count);
+			return;
+		    }
+		    bsq->freebuf = buffer->next;
+		    
+#ifdef FORE200E_BSQ_DEBUG
+		    if (buffer->supplied)
+			printk(FORE200E "queue %d.%d, buffer %lu already supplied\n",
+			       scheme, magn, buffer->index);
+		    buffer->supplied = 1;
+#endif
+		    entry->rbd_block->rbd[ i ].buffer_haddr = buffer->data.dma_addr;
+		    entry->rbd_block->rbd[ i ].handle       = FORE200E_BUF2HDL(buffer);
+		}
+
+		FORE200E_NEXT_ENTRY(bsq->head, QUEUE_SIZE_BS);
+
+ 		/* decrease accordingly the number of free rx buffers */
+		bsq->freebuf_count -= RBD_BLK_SIZE;
+
+		*entry->status = STATUS_PENDING;
+		fore200e->bus->write(entry->rbd_block_dma, &entry->cp_entry->rbd_block_haddr);
+	    }
+	}
+    }
+}
+
+
+static int
+fore200e_push_rpd(struct fore200e* fore200e, struct atm_vcc* vcc, struct rpd* rpd)
+{
+    struct sk_buff*      skb;
+    struct buffer*       buffer;
+    struct fore200e_vcc* fore200e_vcc;
+    int                  i, pdu_len = 0;
+#ifdef FORE200E_52BYTE_AAL0_SDU
+    u32                  cell_header = 0;
+#endif
+
+    ASSERT(vcc);
+    
+    fore200e_vcc = FORE200E_VCC(vcc);
+    ASSERT(fore200e_vcc);
+
+#ifdef FORE200E_52BYTE_AAL0_SDU
+    if ((vcc->qos.aal == ATM_AAL0) && (vcc->qos.rxtp.max_sdu == ATM_AAL0_SDU)) {
+
+	cell_header = (rpd->atm_header.gfc << ATM_HDR_GFC_SHIFT) |
+	              (rpd->atm_header.vpi << ATM_HDR_VPI_SHIFT) |
+                      (rpd->atm_header.vci << ATM_HDR_VCI_SHIFT) |
+                      (rpd->atm_header.plt << ATM_HDR_PTI_SHIFT) | 
+                       rpd->atm_header.clp;
+	pdu_len = 4;
+    }
+#endif
+    
+    /* compute total PDU length */
+    for (i = 0; i < rpd->nseg; i++)
+	pdu_len += rpd->rsd[ i ].length;
+    
+    skb = alloc_skb(pdu_len, GFP_ATOMIC);
+    if (skb == NULL) {
+	DPRINTK(2, "unable to alloc new skb, rx PDU length = %d\n", pdu_len);
+
+	atomic_inc(&vcc->stats->rx_drop);
+	return -ENOMEM;
+    } 
+
+    do_gettimeofday(&skb->stamp);
+    
+#ifdef FORE200E_52BYTE_AAL0_SDU
+    if (cell_header) {
+	*((u32*)skb_put(skb, 4)) = cell_header;
+    }
+#endif
+
+    /* reassemble segments */
+    for (i = 0; i < rpd->nseg; i++) {
+	
+	/* rebuild rx buffer address from rsd handle */
+	buffer = FORE200E_HDL2BUF(rpd->rsd[ i ].handle);
+	
+	/* Make device DMA transfer visible to CPU.  */
+	fore200e->bus->dma_sync_for_cpu(fore200e, buffer->data.dma_addr, rpd->rsd[ i ].length, DMA_FROM_DEVICE);
+	
+	memcpy(skb_put(skb, rpd->rsd[ i ].length), buffer->data.align_addr, rpd->rsd[ i ].length);
+
+	/* Now let the device get at it again.  */
+	fore200e->bus->dma_sync_for_device(fore200e, buffer->data.dma_addr, rpd->rsd[ i ].length, DMA_FROM_DEVICE);
+    }
+
+    DPRINTK(3, "rx skb: len = %d, truesize = %d\n", skb->len, skb->truesize);
+    
+    if (pdu_len < fore200e_vcc->rx_min_pdu)
+	fore200e_vcc->rx_min_pdu = pdu_len;
+    if (pdu_len > fore200e_vcc->rx_max_pdu)
+	fore200e_vcc->rx_max_pdu = pdu_len;
+    fore200e_vcc->rx_pdu++;
+
+    /* push PDU */
+    if (atm_charge(vcc, skb->truesize) == 0) {
+
+	DPRINTK(2, "receive buffers saturated for %d.%d.%d - PDU dropped\n",
+		vcc->itf, vcc->vpi, vcc->vci);
+
+	dev_kfree_skb_any(skb);
+
+	atomic_inc(&vcc->stats->rx_drop);
+	return -ENOMEM;
+    }
+
+    ASSERT(atomic_read(&sk_atm(vcc)->sk_wmem_alloc) >= 0);
+
+    vcc->push(vcc, skb);
+    atomic_inc(&vcc->stats->rx);
+
+    ASSERT(atomic_read(&sk_atm(vcc)->sk_wmem_alloc) >= 0);
+
+    return 0;
+}
+
+
+static void
+fore200e_collect_rpd(struct fore200e* fore200e, struct rpd* rpd)
+{
+    struct host_bsq* bsq;
+    struct buffer*   buffer;
+    int              i;
+    
+    for (i = 0; i < rpd->nseg; i++) {
+
+	/* rebuild rx buffer address from rsd handle */
+	buffer = FORE200E_HDL2BUF(rpd->rsd[ i ].handle);
+
+	bsq = &fore200e->host_bsq[ buffer->scheme ][ buffer->magn ];
+
+#ifdef FORE200E_BSQ_DEBUG
+	bsq_audit(2, bsq, buffer->scheme, buffer->magn);
+
+	if (buffer->supplied == 0)
+	    printk(FORE200E "queue %d.%d, buffer %ld was not supplied\n",
+		   buffer->scheme, buffer->magn, buffer->index);
+	buffer->supplied = 0;
+#endif
+
+	/* re-insert the buffer into the free buffer list */
+	buffer->next = bsq->freebuf;
+	bsq->freebuf = buffer;
+
+	/* then increment the number of free rx buffers */
+	bsq->freebuf_count++;
+    }
+}
+
+
+static void
+fore200e_rx_irq(struct fore200e* fore200e)
+{
+    struct host_rxq*        rxq = &fore200e->host_rxq;
+    struct host_rxq_entry*  entry;
+    struct atm_vcc*         vcc;
+    struct fore200e_vc_map* vc_map;
+
+    for (;;) {
+	
+	entry = &rxq->host_entry[ rxq->head ];
+
+	/* no more received PDUs */
+	if ((*entry->status & STATUS_COMPLETE) == 0)
+	    break;
+
+	vc_map = FORE200E_VC_MAP(fore200e, entry->rpd->atm_header.vpi, entry->rpd->atm_header.vci);
+
+	if ((vc_map->vcc == NULL) ||
+	    (test_bit(ATM_VF_READY, &vc_map->vcc->flags) == 0)) {
+
+	    DPRINTK(1, "no ready VC found for PDU received on %d.%d.%d\n",
+		    fore200e->atm_dev->number,
+		    entry->rpd->atm_header.vpi, entry->rpd->atm_header.vci);
+	}
+	else {
+	    vcc = vc_map->vcc;
+	    ASSERT(vcc);
+
+	    if ((*entry->status & STATUS_ERROR) == 0) {
+
+		fore200e_push_rpd(fore200e, vcc, entry->rpd);
+	    }
+	    else {
+		DPRINTK(2, "damaged PDU on %d.%d.%d\n",
+			fore200e->atm_dev->number,
+			entry->rpd->atm_header.vpi, entry->rpd->atm_header.vci);
+		atomic_inc(&vcc->stats->rx_err);
+	    }
+	}
+
+	FORE200E_NEXT_ENTRY(rxq->head, QUEUE_SIZE_RX);
+
+	fore200e_collect_rpd(fore200e, entry->rpd);
+
+	/* rewrite the rpd address to ack the received PDU */
+	fore200e->bus->write(entry->rpd_dma, &entry->cp_entry->rpd_haddr);
+	*entry->status = STATUS_FREE;
+
+	fore200e_supply(fore200e);
+    }
+}
+
+
+#ifndef FORE200E_USE_TASKLET
+static void
+fore200e_irq(struct fore200e* fore200e)
+{
+    unsigned long flags;
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+    fore200e_rx_irq(fore200e);
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+    fore200e_tx_irq(fore200e);
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+}
+#endif
+
+
+static irqreturn_t
+fore200e_interrupt(int irq, void* dev, struct pt_regs* regs)
+{
+    struct fore200e* fore200e = FORE200E_DEV((struct atm_dev*)dev);
+
+    if (fore200e->bus->irq_check(fore200e) == 0) {
+	
+	DPRINTK(3, "interrupt NOT triggered by device %d\n", fore200e->atm_dev->number);
+	return IRQ_NONE;
+    }
+    DPRINTK(3, "interrupt triggered by device %d\n", fore200e->atm_dev->number);
+
+#ifdef FORE200E_USE_TASKLET
+    tasklet_schedule(&fore200e->tx_tasklet);
+    tasklet_schedule(&fore200e->rx_tasklet);
+#else
+    fore200e_irq(fore200e);
+#endif
+    
+    fore200e->bus->irq_ack(fore200e);
+    return IRQ_HANDLED;
+}
+
+
+#ifdef FORE200E_USE_TASKLET
+static void
+fore200e_tx_tasklet(unsigned long data)
+{
+    struct fore200e* fore200e = (struct fore200e*) data;
+    unsigned long flags;
+
+    DPRINTK(3, "tx tasklet scheduled for device %d\n", fore200e->atm_dev->number);
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+    fore200e_tx_irq(fore200e);
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+}
+
+
+static void
+fore200e_rx_tasklet(unsigned long data)
+{
+    struct fore200e* fore200e = (struct fore200e*) data;
+    unsigned long    flags;
+
+    DPRINTK(3, "rx tasklet scheduled for device %d\n", fore200e->atm_dev->number);
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+    fore200e_rx_irq((struct fore200e*) data);
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+}
+#endif
+
+
+static int
+fore200e_select_scheme(struct atm_vcc* vcc)
+{
+    /* fairly balance the VCs over (identical) buffer schemes */
+    int scheme = vcc->vci % 2 ? BUFFER_SCHEME_ONE : BUFFER_SCHEME_TWO;
+
+    DPRINTK(1, "VC %d.%d.%d uses buffer scheme %d\n",
+	    vcc->itf, vcc->vpi, vcc->vci, scheme);
+
+    return scheme;
+}
+
+
+static int 
+fore200e_activate_vcin(struct fore200e* fore200e, int activate, struct atm_vcc* vcc, int mtu)
+{
+    struct host_cmdq*        cmdq  = &fore200e->host_cmdq;
+    struct host_cmdq_entry*  entry = &cmdq->host_entry[ cmdq->head ];
+    struct activate_opcode   activ_opcode;
+    struct deactivate_opcode deactiv_opcode;
+    struct vpvc              vpvc;
+    int                      ok;
+    enum fore200e_aal        aal = fore200e_atm2fore_aal(vcc->qos.aal);
+
+    FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD);
+    
+    if (activate) {
+	FORE200E_VCC(vcc)->scheme = fore200e_select_scheme(vcc);
+	
+	activ_opcode.opcode = OPCODE_ACTIVATE_VCIN;
+	activ_opcode.aal    = aal;
+	activ_opcode.scheme = FORE200E_VCC(vcc)->scheme;
+	activ_opcode.pad    = 0;
+    }
+    else {
+	deactiv_opcode.opcode = OPCODE_DEACTIVATE_VCIN;
+	deactiv_opcode.pad    = 0;
+    }
+
+    vpvc.vci = vcc->vci;
+    vpvc.vpi = vcc->vpi;
+
+    *entry->status = STATUS_PENDING;
+
+    if (activate) {
+
+#ifdef FORE200E_52BYTE_AAL0_SDU
+	mtu = 48;
+#endif
+	/* the MTU is not used by the cp, except in the case of AAL0 */
+	fore200e->bus->write(mtu,                        &entry->cp_entry->cmd.activate_block.mtu);
+	fore200e->bus->write(*(u32*)&vpvc,         (u32 __iomem *)&entry->cp_entry->cmd.activate_block.vpvc);
+	fore200e->bus->write(*(u32*)&activ_opcode, (u32 __iomem *)&entry->cp_entry->cmd.activate_block.opcode);
+    }
+    else {
+	fore200e->bus->write(*(u32*)&vpvc,         (u32 __iomem *)&entry->cp_entry->cmd.deactivate_block.vpvc);
+	fore200e->bus->write(*(u32*)&deactiv_opcode, (u32 __iomem *)&entry->cp_entry->cmd.deactivate_block.opcode);
+    }
+
+    ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400);
+
+    *entry->status = STATUS_FREE;
+
+    if (ok == 0) {
+	printk(FORE200E "unable to %s VC %d.%d.%d\n",
+	       activate ? "open" : "close", vcc->itf, vcc->vpi, vcc->vci);
+	return -EIO;
+    }
+
+    DPRINTK(1, "VC %d.%d.%d %sed\n", vcc->itf, vcc->vpi, vcc->vci, 
+	    activate ? "open" : "clos");
+
+    return 0;
+}
+
+
+#define FORE200E_MAX_BACK2BACK_CELLS 255    /* XXX depends on CDVT */
+
+static void
+fore200e_rate_ctrl(struct atm_qos* qos, struct tpd_rate* rate)
+{
+    if (qos->txtp.max_pcr < ATM_OC3_PCR) {
+    
+	/* compute the data cells to idle cells ratio from the tx PCR */
+	rate->data_cells = qos->txtp.max_pcr * FORE200E_MAX_BACK2BACK_CELLS / ATM_OC3_PCR;
+	rate->idle_cells = FORE200E_MAX_BACK2BACK_CELLS - rate->data_cells;
+    }
+    else {
+	/* disable rate control */
+	rate->data_cells = rate->idle_cells = 0;
+    }
+}
+
+
+static int
+fore200e_open(struct atm_vcc *vcc)
+{
+    struct fore200e*        fore200e = FORE200E_DEV(vcc->dev);
+    struct fore200e_vcc*    fore200e_vcc;
+    struct fore200e_vc_map* vc_map;
+    unsigned long	    flags;
+    int			    vci = vcc->vci;
+    short		    vpi = vcc->vpi;
+
+    ASSERT((vpi >= 0) && (vpi < 1<<FORE200E_VPI_BITS));
+    ASSERT((vci >= 0) && (vci < 1<<FORE200E_VCI_BITS));
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+
+    vc_map = FORE200E_VC_MAP(fore200e, vpi, vci);
+    if (vc_map->vcc) {
+
+	spin_unlock_irqrestore(&fore200e->q_lock, flags);
+
+	printk(FORE200E "VC %d.%d.%d already in use\n",
+	       fore200e->atm_dev->number, vpi, vci);
+
+	return -EINVAL;
+    }
+
+    vc_map->vcc = vcc;
+
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+
+    fore200e_vcc = fore200e_kmalloc(sizeof(struct fore200e_vcc), GFP_ATOMIC);
+    if (fore200e_vcc == NULL) {
+	vc_map->vcc = NULL;
+	return -ENOMEM;
+    }
+
+    DPRINTK(2, "opening %d.%d.%d:%d QoS = (tx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d; "
+	    "rx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d)\n",
+	    vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal),
+	    fore200e_traffic_class[ vcc->qos.txtp.traffic_class ],
+	    vcc->qos.txtp.min_pcr, vcc->qos.txtp.max_pcr, vcc->qos.txtp.max_cdv, vcc->qos.txtp.max_sdu,
+	    fore200e_traffic_class[ vcc->qos.rxtp.traffic_class ],
+	    vcc->qos.rxtp.min_pcr, vcc->qos.rxtp.max_pcr, vcc->qos.rxtp.max_cdv, vcc->qos.rxtp.max_sdu);
+    
+    /* pseudo-CBR bandwidth requested? */
+    if ((vcc->qos.txtp.traffic_class == ATM_CBR) && (vcc->qos.txtp.max_pcr > 0)) {
+	
+	down(&fore200e->rate_sf);
+	if (fore200e->available_cell_rate < vcc->qos.txtp.max_pcr) {
+	    up(&fore200e->rate_sf);
+
+	    fore200e_kfree(fore200e_vcc);
+	    vc_map->vcc = NULL;
+	    return -EAGAIN;
+	}
+
+	/* reserve bandwidth */
+	fore200e->available_cell_rate -= vcc->qos.txtp.max_pcr;
+	up(&fore200e->rate_sf);
+    }
+    
+    vcc->itf = vcc->dev->number;
+
+    set_bit(ATM_VF_PARTIAL,&vcc->flags);
+    set_bit(ATM_VF_ADDR, &vcc->flags);
+
+    vcc->dev_data = fore200e_vcc;
+    
+    if (fore200e_activate_vcin(fore200e, 1, vcc, vcc->qos.rxtp.max_sdu) < 0) {
+
+	vc_map->vcc = NULL;
+
+	clear_bit(ATM_VF_ADDR, &vcc->flags);
+	clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+
+	vcc->dev_data = NULL;
+
+	fore200e->available_cell_rate += vcc->qos.txtp.max_pcr;
+
+	fore200e_kfree(fore200e_vcc);
+	return -EINVAL;
+    }
+    
+    /* compute rate control parameters */
+    if ((vcc->qos.txtp.traffic_class == ATM_CBR) && (vcc->qos.txtp.max_pcr > 0)) {
+	
+	fore200e_rate_ctrl(&vcc->qos, &fore200e_vcc->rate);
+	set_bit(ATM_VF_HASQOS, &vcc->flags);
+
+	DPRINTK(3, "tx on %d.%d.%d:%d, tx PCR = %d, rx PCR = %d, data_cells = %u, idle_cells = %u\n",
+		vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal),
+		vcc->qos.txtp.max_pcr, vcc->qos.rxtp.max_pcr, 
+		fore200e_vcc->rate.data_cells, fore200e_vcc->rate.idle_cells);
+    }
+    
+    fore200e_vcc->tx_min_pdu = fore200e_vcc->rx_min_pdu = MAX_PDU_SIZE + 1;
+    fore200e_vcc->tx_max_pdu = fore200e_vcc->rx_max_pdu = 0;
+    fore200e_vcc->tx_pdu     = fore200e_vcc->rx_pdu     = 0;
+
+    /* new incarnation of the vcc */
+    vc_map->incarn = ++fore200e->incarn_count;
+
+    /* VC unusable before this flag is set */
+    set_bit(ATM_VF_READY, &vcc->flags);
+
+    return 0;
+}
+
+
+static void
+fore200e_close(struct atm_vcc* vcc)
+{
+    struct fore200e*        fore200e = FORE200E_DEV(vcc->dev);
+    struct fore200e_vcc*    fore200e_vcc;
+    struct fore200e_vc_map* vc_map;
+    unsigned long           flags;
+
+    ASSERT(vcc);
+    ASSERT((vcc->vpi >= 0) && (vcc->vpi < 1<<FORE200E_VPI_BITS));
+    ASSERT((vcc->vci >= 0) && (vcc->vci < 1<<FORE200E_VCI_BITS));
+
+    DPRINTK(2, "closing %d.%d.%d:%d\n", vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal));
+
+    clear_bit(ATM_VF_READY, &vcc->flags);
+
+    fore200e_activate_vcin(fore200e, 0, vcc, 0);
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+
+    vc_map = FORE200E_VC_MAP(fore200e, vcc->vpi, vcc->vci);
+
+    /* the vc is no longer considered as "in use" by fore200e_open() */
+    vc_map->vcc = NULL;
+
+    vcc->itf = vcc->vci = vcc->vpi = 0;
+
+    fore200e_vcc = FORE200E_VCC(vcc);
+    vcc->dev_data = NULL;
+
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+
+    /* release reserved bandwidth, if any */
+    if ((vcc->qos.txtp.traffic_class == ATM_CBR) && (vcc->qos.txtp.max_pcr > 0)) {
+
+	down(&fore200e->rate_sf);
+	fore200e->available_cell_rate += vcc->qos.txtp.max_pcr;
+	up(&fore200e->rate_sf);
+
+	clear_bit(ATM_VF_HASQOS, &vcc->flags);
+    }
+
+    clear_bit(ATM_VF_ADDR, &vcc->flags);
+    clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+
+    ASSERT(fore200e_vcc);
+    fore200e_kfree(fore200e_vcc);
+}
+
+
+static int
+fore200e_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+    struct fore200e*        fore200e     = FORE200E_DEV(vcc->dev);
+    struct fore200e_vcc*    fore200e_vcc = FORE200E_VCC(vcc);
+    struct fore200e_vc_map* vc_map;
+    struct host_txq*        txq          = &fore200e->host_txq;
+    struct host_txq_entry*  entry;
+    struct tpd*             tpd;
+    struct tpd_haddr        tpd_haddr;
+    int                     retry        = CONFIG_ATM_FORE200E_TX_RETRY;
+    int                     tx_copy      = 0;
+    int                     tx_len       = skb->len;
+    u32*                    cell_header  = NULL;
+    unsigned char*          skb_data;
+    int                     skb_len;
+    unsigned char*          data;
+    unsigned long           flags;
+
+    ASSERT(vcc);
+    ASSERT(atomic_read(&sk_atm(vcc)->sk_wmem_alloc) >= 0);
+    ASSERT(fore200e);
+    ASSERT(fore200e_vcc);
+
+    if (!test_bit(ATM_VF_READY, &vcc->flags)) {
+	DPRINTK(1, "VC %d.%d.%d not ready for tx\n", vcc->itf, vcc->vpi, vcc->vpi);
+	dev_kfree_skb_any(skb);
+	return -EINVAL;
+    }
+
+#ifdef FORE200E_52BYTE_AAL0_SDU
+    if ((vcc->qos.aal == ATM_AAL0) && (vcc->qos.txtp.max_sdu == ATM_AAL0_SDU)) {
+	cell_header = (u32*) skb->data;
+	skb_data    = skb->data + 4;    /* skip 4-byte cell header */
+	skb_len     = tx_len = skb->len  - 4;
+
+	DPRINTK(3, "user-supplied cell header = 0x%08x\n", *cell_header);
+    }
+    else 
+#endif
+    {
+	skb_data = skb->data;
+	skb_len  = skb->len;
+    }
+    
+    if (((unsigned long)skb_data) & 0x3) {
+
+	DPRINTK(2, "misaligned tx PDU on device %s\n", fore200e->name);
+	tx_copy = 1;
+	tx_len  = skb_len;
+    }
+
+    if ((vcc->qos.aal == ATM_AAL0) && (skb_len % ATM_CELL_PAYLOAD)) {
+
+        /* this simply NUKES the PCA board */
+	DPRINTK(2, "incomplete tx AAL0 PDU on device %s\n", fore200e->name);
+	tx_copy = 1;
+	tx_len  = ((skb_len / ATM_CELL_PAYLOAD) + 1) * ATM_CELL_PAYLOAD;
+    }
+    
+    if (tx_copy) {
+	data = kmalloc(tx_len, GFP_ATOMIC | GFP_DMA);
+	if (data == NULL) {
+	    if (vcc->pop) {
+		vcc->pop(vcc, skb);
+	    }
+	    else {
+		dev_kfree_skb_any(skb);
+	    }
+	    return -ENOMEM;
+	}
+
+	memcpy(data, skb_data, skb_len);
+	if (skb_len < tx_len)
+	    memset(data + skb_len, 0x00, tx_len - skb_len);
+    }
+    else {
+	data = skb_data;
+    }
+
+    vc_map = FORE200E_VC_MAP(fore200e, vcc->vpi, vcc->vci);
+    ASSERT(vc_map->vcc == vcc);
+
+  retry_here:
+
+    spin_lock_irqsave(&fore200e->q_lock, flags);
+
+    entry = &txq->host_entry[ txq->head ];
+
+    if ((*entry->status != STATUS_FREE) || (txq->txing >= QUEUE_SIZE_TX - 2)) {
+
+	/* try to free completed tx queue entries */
+	fore200e_tx_irq(fore200e);
+
+	if (*entry->status != STATUS_FREE) {
+
+	    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+
+	    /* retry once again? */
+	    if (--retry > 0) {
+		udelay(50);
+		goto retry_here;
+	    }
+
+	    atomic_inc(&vcc->stats->tx_err);
+
+	    fore200e->tx_sat++;
+	    DPRINTK(2, "tx queue of device %s is saturated, PDU dropped - heartbeat is %08x\n",
+		    fore200e->name, fore200e->cp_queues->heartbeat);
+	    if (vcc->pop) {
+		vcc->pop(vcc, skb);
+	    }
+	    else {
+		dev_kfree_skb_any(skb);
+	    }
+
+	    if (tx_copy)
+		kfree(data);
+
+	    return -ENOBUFS;
+	}
+    }
+
+    entry->incarn = vc_map->incarn;
+    entry->vc_map = vc_map;
+    entry->skb    = skb;
+    entry->data   = tx_copy ? data : NULL;
+
+    tpd = entry->tpd;
+    tpd->tsd[ 0 ].buffer = fore200e->bus->dma_map(fore200e, data, tx_len, DMA_TO_DEVICE);
+    tpd->tsd[ 0 ].length = tx_len;
+
+    FORE200E_NEXT_ENTRY(txq->head, QUEUE_SIZE_TX);
+    txq->txing++;
+
+    /* The dma_map call above implies a dma_sync so the device can use it,
+     * thus no explicit dma_sync call is necessary here.
+     */
+    
+    DPRINTK(3, "tx on %d.%d.%d:%d, len = %u (%u)\n", 
+	    vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal),
+	    tpd->tsd[0].length, skb_len);
+
+    if (skb_len < fore200e_vcc->tx_min_pdu)
+	fore200e_vcc->tx_min_pdu = skb_len;
+    if (skb_len > fore200e_vcc->tx_max_pdu)
+	fore200e_vcc->tx_max_pdu = skb_len;
+    fore200e_vcc->tx_pdu++;
+
+    /* set tx rate control information */
+    tpd->rate.data_cells = fore200e_vcc->rate.data_cells;
+    tpd->rate.idle_cells = fore200e_vcc->rate.idle_cells;
+
+    if (cell_header) {
+	tpd->atm_header.clp = (*cell_header & ATM_HDR_CLP);
+	tpd->atm_header.plt = (*cell_header & ATM_HDR_PTI_MASK) >> ATM_HDR_PTI_SHIFT;
+	tpd->atm_header.vci = (*cell_header & ATM_HDR_VCI_MASK) >> ATM_HDR_VCI_SHIFT;
+	tpd->atm_header.vpi = (*cell_header & ATM_HDR_VPI_MASK) >> ATM_HDR_VPI_SHIFT;
+	tpd->atm_header.gfc = (*cell_header & ATM_HDR_GFC_MASK) >> ATM_HDR_GFC_SHIFT;
+    }
+    else {
+	/* set the ATM header, common to all cells conveying the PDU */
+	tpd->atm_header.clp = 0;
+	tpd->atm_header.plt = 0;
+	tpd->atm_header.vci = vcc->vci;
+	tpd->atm_header.vpi = vcc->vpi;
+	tpd->atm_header.gfc = 0;
+    }
+
+    tpd->spec.length = tx_len;
+    tpd->spec.nseg   = 1;
+    tpd->spec.aal    = fore200e_atm2fore_aal(vcc->qos.aal);
+    tpd->spec.intr   = 1;
+
+    tpd_haddr.size  = sizeof(struct tpd) / (1<<TPD_HADDR_SHIFT);  /* size is expressed in 32 byte blocks */
+    tpd_haddr.pad   = 0;
+    tpd_haddr.haddr = entry->tpd_dma >> TPD_HADDR_SHIFT;          /* shift the address, as we are in a bitfield */
+
+    *entry->status = STATUS_PENDING;
+    fore200e->bus->write(*(u32*)&tpd_haddr, (u32 __iomem *)&entry->cp_entry->tpd_haddr);
+
+    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+
+    return 0;
+}
+
+
+static int
+fore200e_getstats(struct fore200e* fore200e)
+{
+    struct host_cmdq*       cmdq  = &fore200e->host_cmdq;
+    struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ];
+    struct stats_opcode     opcode;
+    int                     ok;
+    u32                     stats_dma_addr;
+
+    if (fore200e->stats == NULL) {
+	fore200e->stats = fore200e_kmalloc(sizeof(struct stats), GFP_KERNEL | GFP_DMA);
+	if (fore200e->stats == NULL)
+	    return -ENOMEM;
+    }
+    
+    stats_dma_addr = fore200e->bus->dma_map(fore200e, fore200e->stats,
+					    sizeof(struct stats), DMA_FROM_DEVICE);
+    
+    FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD);
+
+    opcode.opcode = OPCODE_GET_STATS;
+    opcode.pad    = 0;
+
+    fore200e->bus->write(stats_dma_addr, &entry->cp_entry->cmd.stats_block.stats_haddr);
+    
+    *entry->status = STATUS_PENDING;
+
+    fore200e->bus->write(*(u32*)&opcode, (u32 __iomem *)&entry->cp_entry->cmd.stats_block.opcode);
+
+    ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400);
+
+    *entry->status = STATUS_FREE;
+
+    fore200e->bus->dma_unmap(fore200e, stats_dma_addr, sizeof(struct stats), DMA_FROM_DEVICE);
+    
+    if (ok == 0) {
+	printk(FORE200E "unable to get statistics from device %s\n", fore200e->name);
+	return -EIO;
+    }
+
+    return 0;
+}
+
+
+static int
+fore200e_getsockopt(struct atm_vcc* vcc, int level, int optname, void __user *optval, int optlen)
+{
+    /* struct fore200e* fore200e = FORE200E_DEV(vcc->dev); */
+
+    DPRINTK(2, "getsockopt %d.%d.%d, level = %d, optname = 0x%x, optval = 0x%p, optlen = %d\n",
+	    vcc->itf, vcc->vpi, vcc->vci, level, optname, optval, optlen);
+
+    return -EINVAL;
+}
+
+
+static int
+fore200e_setsockopt(struct atm_vcc* vcc, int level, int optname, void __user *optval, int optlen)
+{
+    /* struct fore200e* fore200e = FORE200E_DEV(vcc->dev); */
+    
+    DPRINTK(2, "setsockopt %d.%d.%d, level = %d, optname = 0x%x, optval = 0x%p, optlen = %d\n",
+	    vcc->itf, vcc->vpi, vcc->vci, level, optname, optval, optlen);
+    
+    return -EINVAL;
+}
+
+
+#if 0 /* currently unused */
+static int
+fore200e_get_oc3(struct fore200e* fore200e, struct oc3_regs* regs)
+{
+    struct host_cmdq*       cmdq  = &fore200e->host_cmdq;
+    struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ];
+    struct oc3_opcode       opcode;
+    int                     ok;
+    u32                     oc3_regs_dma_addr;
+
+    oc3_regs_dma_addr = fore200e->bus->dma_map(fore200e, regs, sizeof(struct oc3_regs), DMA_FROM_DEVICE);
+
+    FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD);
+
+    opcode.opcode = OPCODE_GET_OC3;
+    opcode.reg    = 0;
+    opcode.value  = 0;
+    opcode.mask   = 0;
+
+    fore200e->bus->write(oc3_regs_dma_addr, &entry->cp_entry->cmd.oc3_block.regs_haddr);
+    
+    *entry->status = STATUS_PENDING;
+
+    fore200e->bus->write(*(u32*)&opcode, (u32*)&entry->cp_entry->cmd.oc3_block.opcode);
+
+    ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400);
+
+    *entry->status = STATUS_FREE;
+
+    fore200e->bus->dma_unmap(fore200e, oc3_regs_dma_addr, sizeof(struct oc3_regs), DMA_FROM_DEVICE);
+    
+    if (ok == 0) {
+	printk(FORE200E "unable to get OC-3 regs of device %s\n", fore200e->name);
+	return -EIO;
+    }
+
+    return 0;
+}
+#endif
+
+
+static int
+fore200e_set_oc3(struct fore200e* fore200e, u32 reg, u32 value, u32 mask)
+{
+    struct host_cmdq*       cmdq  = &fore200e->host_cmdq;
+    struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ];
+    struct oc3_opcode       opcode;
+    int                     ok;
+
+    DPRINTK(2, "set OC-3 reg = 0x%02x, value = 0x%02x, mask = 0x%02x\n", reg, value, mask);
+
+    FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD);
+
+    opcode.opcode = OPCODE_SET_OC3;
+    opcode.reg    = reg;
+    opcode.value  = value;
+    opcode.mask   = mask;
+
+    fore200e->bus->write(0, &entry->cp_entry->cmd.oc3_block.regs_haddr);
+    
+    *entry->status = STATUS_PENDING;
+
+    fore200e->bus->write(*(u32*)&opcode, (u32 __iomem *)&entry->cp_entry->cmd.oc3_block.opcode);
+
+    ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400);
+
+    *entry->status = STATUS_FREE;
+
+    if (ok == 0) {
+	printk(FORE200E "unable to set OC-3 reg 0x%02x of device %s\n", reg, fore200e->name);
+	return -EIO;
+    }
+
+    return 0;
+}
+
+
+static int
+fore200e_setloop(struct fore200e* fore200e, int loop_mode)
+{
+    u32 mct_value, mct_mask;
+    int error;
+
+    if (!capable(CAP_NET_ADMIN))
+	return -EPERM;
+    
+    switch (loop_mode) {
+
+    case ATM_LM_NONE:
+	mct_value = 0; 
+	mct_mask  = SUNI_MCT_DLE | SUNI_MCT_LLE;
+	break;
+	
+    case ATM_LM_LOC_PHY:
+	mct_value = mct_mask = SUNI_MCT_DLE;
+	break;
+
+    case ATM_LM_RMT_PHY:
+	mct_value = mct_mask = SUNI_MCT_LLE;
+	break;
+
+    default:
+	return -EINVAL;
+    }
+
+    error = fore200e_set_oc3(fore200e, SUNI_MCT, mct_value, mct_mask);
+    if (error == 0)
+	fore200e->loop_mode = loop_mode;
+
+    return error;
+}
+
+
+static inline unsigned int
+fore200e_swap(unsigned int in)
+{
+#if defined(__LITTLE_ENDIAN)
+    return swab32(in);
+#else
+    return in;
+#endif
+}
+
+
+static int
+fore200e_fetch_stats(struct fore200e* fore200e, struct sonet_stats __user *arg)
+{
+    struct sonet_stats tmp;
+
+    if (fore200e_getstats(fore200e) < 0)
+	return -EIO;
+
+    tmp.section_bip = fore200e_swap(fore200e->stats->oc3.section_bip8_errors);
+    tmp.line_bip    = fore200e_swap(fore200e->stats->oc3.line_bip24_errors);
+    tmp.path_bip    = fore200e_swap(fore200e->stats->oc3.path_bip8_errors);
+    tmp.line_febe   = fore200e_swap(fore200e->stats->oc3.line_febe_errors);
+    tmp.path_febe   = fore200e_swap(fore200e->stats->oc3.path_febe_errors);
+    tmp.corr_hcs    = fore200e_swap(fore200e->stats->oc3.corr_hcs_errors);
+    tmp.uncorr_hcs  = fore200e_swap(fore200e->stats->oc3.ucorr_hcs_errors);
+    tmp.tx_cells    = fore200e_swap(fore200e->stats->aal0.cells_transmitted)  +
+	              fore200e_swap(fore200e->stats->aal34.cells_transmitted) +
+	              fore200e_swap(fore200e->stats->aal5.cells_transmitted);
+    tmp.rx_cells    = fore200e_swap(fore200e->stats->aal0.cells_received)     +
+	              fore200e_swap(fore200e->stats->aal34.cells_received)    +
+	              fore200e_swap(fore200e->stats->aal5.cells_received);
+
+    if (arg)
+	return copy_to_user(arg, &tmp, sizeof(struct sonet_stats)) ? -EFAULT : 0;	
+    
+    return 0;
+}
+
+
+static int
+fore200e_ioctl(struct atm_dev* dev, unsigned int cmd, void __user * arg)
+{
+    struct fore200e* fore200e = FORE200E_DEV(dev);
+    
+    DPRINTK(2, "ioctl cmd = 0x%x (%u), arg = 0x%p (%lu)\n", cmd, cmd, arg, (unsigned long)arg);
+
+    switch (cmd) {
+
+    case SONET_GETSTAT:
+	return fore200e_fetch_stats(fore200e, (struct sonet_stats __user *)arg);
+
+    case SONET_GETDIAG:
+	return put_user(0, (int __user *)arg) ? -EFAULT : 0;
+
+    case ATM_SETLOOP:
+	return fore200e_setloop(fore200e, (int)(unsigned long)arg);
+
+    case ATM_GETLOOP:
+	return put_user(fore200e->loop_mode, (int __user *)arg) ? -EFAULT : 0;
+
+    case ATM_QUERYLOOP:
+	return put_user(ATM_LM_LOC_PHY | ATM_LM_RMT_PHY, (int __user *)arg) ? -EFAULT : 0;
+    }
+
+    return -ENOSYS; /* not implemented */
+}
+
+
+static int
+fore200e_change_qos(struct atm_vcc* vcc,struct atm_qos* qos, int flags)
+{
+    struct fore200e_vcc* fore200e_vcc = FORE200E_VCC(vcc);
+    struct fore200e*     fore200e     = FORE200E_DEV(vcc->dev);
+
+    if (!test_bit(ATM_VF_READY, &vcc->flags)) {
+	DPRINTK(1, "VC %d.%d.%d not ready for QoS change\n", vcc->itf, vcc->vpi, vcc->vpi);
+	return -EINVAL;
+    }
+
+    DPRINTK(2, "change_qos %d.%d.%d, "
+	    "(tx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d; "
+	    "rx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d), flags = 0x%x\n"
+	    "available_cell_rate = %u",
+	    vcc->itf, vcc->vpi, vcc->vci,
+	    fore200e_traffic_class[ qos->txtp.traffic_class ],
+	    qos->txtp.min_pcr, qos->txtp.max_pcr, qos->txtp.max_cdv, qos->txtp.max_sdu,
+	    fore200e_traffic_class[ qos->rxtp.traffic_class ],
+	    qos->rxtp.min_pcr, qos->rxtp.max_pcr, qos->rxtp.max_cdv, qos->rxtp.max_sdu,
+	    flags, fore200e->available_cell_rate);
+
+    if ((qos->txtp.traffic_class == ATM_CBR) && (qos->txtp.max_pcr > 0)) {
+
+	down(&fore200e->rate_sf);
+	if (fore200e->available_cell_rate + vcc->qos.txtp.max_pcr < qos->txtp.max_pcr) {
+	    up(&fore200e->rate_sf);
+	    return -EAGAIN;
+	}
+
+	fore200e->available_cell_rate += vcc->qos.txtp.max_pcr;
+	fore200e->available_cell_rate -= qos->txtp.max_pcr;
+
+	up(&fore200e->rate_sf);
+	
+	memcpy(&vcc->qos, qos, sizeof(struct atm_qos));
+	
+	/* update rate control parameters */
+	fore200e_rate_ctrl(qos, &fore200e_vcc->rate);
+
+	set_bit(ATM_VF_HASQOS, &vcc->flags);
+
+	return 0;
+    }
+    
+    return -EINVAL;
+}
+    
+
+static int __init
+fore200e_irq_request(struct fore200e* fore200e)
+{
+    if (request_irq(fore200e->irq, fore200e_interrupt, SA_SHIRQ, fore200e->name, fore200e->atm_dev) < 0) {
+
+	printk(FORE200E "unable to reserve IRQ %s for device %s\n",
+	       fore200e_irq_itoa(fore200e->irq), fore200e->name);
+	return -EBUSY;
+    }
+
+    printk(FORE200E "IRQ %s reserved for device %s\n",
+	   fore200e_irq_itoa(fore200e->irq), fore200e->name);
+
+#ifdef FORE200E_USE_TASKLET
+    tasklet_init(&fore200e->tx_tasklet, fore200e_tx_tasklet, (unsigned long)fore200e);
+    tasklet_init(&fore200e->rx_tasklet, fore200e_rx_tasklet, (unsigned long)fore200e);
+#endif
+
+    fore200e->state = FORE200E_STATE_IRQ;
+    return 0;
+}
+
+
+static int __init
+fore200e_get_esi(struct fore200e* fore200e)
+{
+    struct prom_data* prom = fore200e_kmalloc(sizeof(struct prom_data), GFP_KERNEL | GFP_DMA);
+    int ok, i;
+
+    if (!prom)
+	return -ENOMEM;
+
+    ok = fore200e->bus->prom_read(fore200e, prom);
+    if (ok < 0) {
+	fore200e_kfree(prom);
+	return -EBUSY;
+    }
+	
+    printk(FORE200E "device %s, rev. %c, S/N: %d, ESI: %02x:%02x:%02x:%02x:%02x:%02x\n", 
+	   fore200e->name, 
+	   (prom->hw_revision & 0xFF) + '@',    /* probably meaningless with SBA boards */
+	   prom->serial_number & 0xFFFF,
+	   prom->mac_addr[ 2 ], prom->mac_addr[ 3 ], prom->mac_addr[ 4 ],
+	   prom->mac_addr[ 5 ], prom->mac_addr[ 6 ], prom->mac_addr[ 7 ]);
+	
+    for (i = 0; i < ESI_LEN; i++) {
+	fore200e->esi[ i ] = fore200e->atm_dev->esi[ i ] = prom->mac_addr[ i + 2 ];
+    }
+    
+    fore200e_kfree(prom);
+
+    return 0;
+}
+
+
+static int __init
+fore200e_alloc_rx_buf(struct fore200e* fore200e)
+{
+    int scheme, magn, nbr, size, i;
+
+    struct host_bsq* bsq;
+    struct buffer*   buffer;
+
+    for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) {
+	for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) {
+
+	    bsq = &fore200e->host_bsq[ scheme ][ magn ];
+
+	    nbr  = fore200e_rx_buf_nbr[ scheme ][ magn ];
+	    size = fore200e_rx_buf_size[ scheme ][ magn ];
+
+	    DPRINTK(2, "rx buffers %d / %d are being allocated\n", scheme, magn);
+
+	    /* allocate the array of receive buffers */
+	    buffer = bsq->buffer = fore200e_kmalloc(nbr * sizeof(struct buffer), GFP_KERNEL);
+
+	    if (buffer == NULL)
+		return -ENOMEM;
+
+	    bsq->freebuf = NULL;
+
+	    for (i = 0; i < nbr; i++) {
+
+		buffer[ i ].scheme = scheme;
+		buffer[ i ].magn   = magn;
+#ifdef FORE200E_BSQ_DEBUG
+		buffer[ i ].index  = i;
+		buffer[ i ].supplied = 0;
+#endif
+
+		/* allocate the receive buffer body */
+		if (fore200e_chunk_alloc(fore200e,
+					 &buffer[ i ].data, size, fore200e->bus->buffer_alignment,
+					 DMA_FROM_DEVICE) < 0) {
+		    
+		    while (i > 0)
+			fore200e_chunk_free(fore200e, &buffer[ --i ].data);
+		    fore200e_kfree(buffer);
+		    
+		    return -ENOMEM;
+		}
+
+		/* insert the buffer into the free buffer list */
+		buffer[ i ].next = bsq->freebuf;
+		bsq->freebuf = &buffer[ i ];
+	    }
+	    /* all the buffers are free, initially */
+	    bsq->freebuf_count = nbr;
+
+#ifdef FORE200E_BSQ_DEBUG
+	    bsq_audit(3, bsq, scheme, magn);
+#endif
+	}
+    }
+
+    fore200e->state = FORE200E_STATE_ALLOC_BUF;
+    return 0;
+}
+
+
+static int __init
+fore200e_init_bs_queue(struct fore200e* fore200e)
+{
+    int scheme, magn, i;
+
+    struct host_bsq*     bsq;
+    struct cp_bsq_entry __iomem * cp_entry;
+
+    for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) {
+	for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) {
+
+	    DPRINTK(2, "buffer supply queue %d / %d is being initialized\n", scheme, magn);
+
+	    bsq = &fore200e->host_bsq[ scheme ][ magn ];
+
+	    /* allocate and align the array of status words */
+	    if (fore200e->bus->dma_chunk_alloc(fore200e,
+					       &bsq->status,
+					       sizeof(enum status), 
+					       QUEUE_SIZE_BS,
+					       fore200e->bus->status_alignment) < 0) {
+		return -ENOMEM;
+	    }
+
+	    /* allocate and align the array of receive buffer descriptors */
+	    if (fore200e->bus->dma_chunk_alloc(fore200e,
+					       &bsq->rbd_block,
+					       sizeof(struct rbd_block),
+					       QUEUE_SIZE_BS,
+					       fore200e->bus->descr_alignment) < 0) {
+		
+		fore200e->bus->dma_chunk_free(fore200e, &bsq->status);
+		return -ENOMEM;
+	    }
+	    
+	    /* get the base address of the cp resident buffer supply queue entries */
+	    cp_entry = fore200e->virt_base + 
+		       fore200e->bus->read(&fore200e->cp_queues->cp_bsq[ scheme ][ magn ]);
+	    
+	    /* fill the host resident and cp resident buffer supply queue entries */
+	    for (i = 0; i < QUEUE_SIZE_BS; i++) {
+		
+		bsq->host_entry[ i ].status = 
+		                     FORE200E_INDEX(bsq->status.align_addr, enum status, i);
+	        bsq->host_entry[ i ].rbd_block =
+		                     FORE200E_INDEX(bsq->rbd_block.align_addr, struct rbd_block, i);
+		bsq->host_entry[ i ].rbd_block_dma =
+		                     FORE200E_DMA_INDEX(bsq->rbd_block.dma_addr, struct rbd_block, i);
+		bsq->host_entry[ i ].cp_entry = &cp_entry[ i ];
+		
+		*bsq->host_entry[ i ].status = STATUS_FREE;
+		
+		fore200e->bus->write(FORE200E_DMA_INDEX(bsq->status.dma_addr, enum status, i), 
+				     &cp_entry[ i ].status_haddr);
+	    }
+	}
+    }
+
+    fore200e->state = FORE200E_STATE_INIT_BSQ;
+    return 0;
+}
+
+
+static int __init
+fore200e_init_rx_queue(struct fore200e* fore200e)
+{
+    struct host_rxq*     rxq =  &fore200e->host_rxq;
+    struct cp_rxq_entry __iomem * cp_entry;
+    int i;
+
+    DPRINTK(2, "receive queue is being initialized\n");
+
+    /* allocate and align the array of status words */
+    if (fore200e->bus->dma_chunk_alloc(fore200e,
+				       &rxq->status,
+				       sizeof(enum status), 
+				       QUEUE_SIZE_RX,
+				       fore200e->bus->status_alignment) < 0) {
+	return -ENOMEM;
+    }
+
+    /* allocate and align the array of receive PDU descriptors */
+    if (fore200e->bus->dma_chunk_alloc(fore200e,
+				       &rxq->rpd,
+				       sizeof(struct rpd), 
+				       QUEUE_SIZE_RX,
+				       fore200e->bus->descr_alignment) < 0) {
+	
+	fore200e->bus->dma_chunk_free(fore200e, &rxq->status);
+	return -ENOMEM;
+    }
+
+    /* get the base address of the cp resident rx queue entries */
+    cp_entry = fore200e->virt_base + fore200e->bus->read(&fore200e->cp_queues->cp_rxq);
+
+    /* fill the host resident and cp resident rx entries */
+    for (i=0; i < QUEUE_SIZE_RX; i++) {
+	
+	rxq->host_entry[ i ].status = 
+	                     FORE200E_INDEX(rxq->status.align_addr, enum status, i);
+	rxq->host_entry[ i ].rpd = 
+	                     FORE200E_INDEX(rxq->rpd.align_addr, struct rpd, i);
+	rxq->host_entry[ i ].rpd_dma = 
+	                     FORE200E_DMA_INDEX(rxq->rpd.dma_addr, struct rpd, i);
+	rxq->host_entry[ i ].cp_entry = &cp_entry[ i ];
+
+	*rxq->host_entry[ i ].status = STATUS_FREE;
+
+	fore200e->bus->write(FORE200E_DMA_INDEX(rxq->status.dma_addr, enum status, i), 
+			     &cp_entry[ i ].status_haddr);
+
+	fore200e->bus->write(FORE200E_DMA_INDEX(rxq->rpd.dma_addr, struct rpd, i),
+			     &cp_entry[ i ].rpd_haddr);
+    }
+
+    /* set the head entry of the queue */
+    rxq->head = 0;
+
+    fore200e->state = FORE200E_STATE_INIT_RXQ;
+    return 0;
+}
+
+
+static int __init
+fore200e_init_tx_queue(struct fore200e* fore200e)
+{
+    struct host_txq*     txq =  &fore200e->host_txq;
+    struct cp_txq_entry __iomem * cp_entry;
+    int i;
+
+    DPRINTK(2, "transmit queue is being initialized\n");
+
+    /* allocate and align the array of status words */
+    if (fore200e->bus->dma_chunk_alloc(fore200e,
+				       &txq->status,
+				       sizeof(enum status), 
+				       QUEUE_SIZE_TX,
+				       fore200e->bus->status_alignment) < 0) {
+	return -ENOMEM;
+    }
+
+    /* allocate and align the array of transmit PDU descriptors */
+    if (fore200e->bus->dma_chunk_alloc(fore200e,
+				       &txq->tpd,
+				       sizeof(struct tpd), 
+				       QUEUE_SIZE_TX,
+				       fore200e->bus->descr_alignment) < 0) {
+	
+	fore200e->bus->dma_chunk_free(fore200e, &txq->status);
+	return -ENOMEM;
+    }
+
+    /* get the base address of the cp resident tx queue entries */
+    cp_entry = fore200e->virt_base + fore200e->bus->read(&fore200e->cp_queues->cp_txq);
+
+    /* fill the host resident and cp resident tx entries */
+    for (i=0; i < QUEUE_SIZE_TX; i++) {
+	
+	txq->host_entry[ i ].status = 
+	                     FORE200E_INDEX(txq->status.align_addr, enum status, i);
+	txq->host_entry[ i ].tpd = 
+	                     FORE200E_INDEX(txq->tpd.align_addr, struct tpd, i);
+	txq->host_entry[ i ].tpd_dma  = 
+                             FORE200E_DMA_INDEX(txq->tpd.dma_addr, struct tpd, i);
+	txq->host_entry[ i ].cp_entry = &cp_entry[ i ];
+
+	*txq->host_entry[ i ].status = STATUS_FREE;
+	
+	fore200e->bus->write(FORE200E_DMA_INDEX(txq->status.dma_addr, enum status, i), 
+			     &cp_entry[ i ].status_haddr);
+	
+        /* although there is a one-to-one mapping of tx queue entries and tpds,
+	   we do not write here the DMA (physical) base address of each tpd into
+	   the related cp resident entry, because the cp relies on this write
+	   operation to detect that a new pdu has been submitted for tx */
+    }
+
+    /* set the head and tail entries of the queue */
+    txq->head = 0;
+    txq->tail = 0;
+
+    fore200e->state = FORE200E_STATE_INIT_TXQ;
+    return 0;
+}
+
+
+static int __init
+fore200e_init_cmd_queue(struct fore200e* fore200e)
+{
+    struct host_cmdq*     cmdq =  &fore200e->host_cmdq;
+    struct cp_cmdq_entry __iomem * cp_entry;
+    int i;
+
+    DPRINTK(2, "command queue is being initialized\n");
+
+    /* allocate and align the array of status words */
+    if (fore200e->bus->dma_chunk_alloc(fore200e,
+				       &cmdq->status,
+				       sizeof(enum status), 
+				       QUEUE_SIZE_CMD,
+				       fore200e->bus->status_alignment) < 0) {
+	return -ENOMEM;
+    }
+    
+    /* get the base address of the cp resident cmd queue entries */
+    cp_entry = fore200e->virt_base + fore200e->bus->read(&fore200e->cp_queues->cp_cmdq);
+
+    /* fill the host resident and cp resident cmd entries */
+    for (i=0; i < QUEUE_SIZE_CMD; i++) {
+	
+	cmdq->host_entry[ i ].status   = 
+                              FORE200E_INDEX(cmdq->status.align_addr, enum status, i);
+	cmdq->host_entry[ i ].cp_entry = &cp_entry[ i ];
+
+	*cmdq->host_entry[ i ].status = STATUS_FREE;
+
+	fore200e->bus->write(FORE200E_DMA_INDEX(cmdq->status.dma_addr, enum status, i), 
+                             &cp_entry[ i ].status_haddr);
+    }
+
+    /* set the head entry of the queue */
+    cmdq->head = 0;
+
+    fore200e->state = FORE200E_STATE_INIT_CMDQ;
+    return 0;
+}
+
+
+static void __init
+fore200e_param_bs_queue(struct fore200e* fore200e,
+			enum buffer_scheme scheme, enum buffer_magn magn,
+			int queue_length, int pool_size, int supply_blksize)
+{
+    struct bs_spec __iomem * bs_spec = &fore200e->cp_queues->init.bs_spec[ scheme ][ magn ];
+
+    fore200e->bus->write(queue_length,                           &bs_spec->queue_length);
+    fore200e->bus->write(fore200e_rx_buf_size[ scheme ][ magn ], &bs_spec->buffer_size);
+    fore200e->bus->write(pool_size,                              &bs_spec->pool_size);
+    fore200e->bus->write(supply_blksize,                         &bs_spec->supply_blksize);
+}
+
+
+static int __init
+fore200e_initialize(struct fore200e* fore200e)
+{
+    struct cp_queues __iomem * cpq;
+    int               ok, scheme, magn;
+
+    DPRINTK(2, "device %s being initialized\n", fore200e->name);
+
+    init_MUTEX(&fore200e->rate_sf);
+    spin_lock_init(&fore200e->q_lock);
+
+    cpq = fore200e->cp_queues = fore200e->virt_base + FORE200E_CP_QUEUES_OFFSET;
+
+    /* enable cp to host interrupts */
+    fore200e->bus->write(1, &cpq->imask);
+
+    if (fore200e->bus->irq_enable)
+	fore200e->bus->irq_enable(fore200e);
+    
+    fore200e->bus->write(NBR_CONNECT, &cpq->init.num_connect);
+
+    fore200e->bus->write(QUEUE_SIZE_CMD, &cpq->init.cmd_queue_len);
+    fore200e->bus->write(QUEUE_SIZE_RX,  &cpq->init.rx_queue_len);
+    fore200e->bus->write(QUEUE_SIZE_TX,  &cpq->init.tx_queue_len);
+
+    fore200e->bus->write(RSD_EXTENSION,  &cpq->init.rsd_extension);
+    fore200e->bus->write(TSD_EXTENSION,  &cpq->init.tsd_extension);
+
+    for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++)
+	for (magn = 0; magn < BUFFER_MAGN_NBR; magn++)
+	    fore200e_param_bs_queue(fore200e, scheme, magn,
+				    QUEUE_SIZE_BS, 
+				    fore200e_rx_buf_nbr[ scheme ][ magn ],
+				    RBD_BLK_SIZE);
+
+    /* issue the initialize command */
+    fore200e->bus->write(STATUS_PENDING,    &cpq->init.status);
+    fore200e->bus->write(OPCODE_INITIALIZE, &cpq->init.opcode);
+
+    ok = fore200e_io_poll(fore200e, &cpq->init.status, STATUS_COMPLETE, 3000);
+    if (ok == 0) {
+	printk(FORE200E "device %s initialization failed\n", fore200e->name);
+	return -ENODEV;
+    }
+
+    printk(FORE200E "device %s initialized\n", fore200e->name);
+
+    fore200e->state = FORE200E_STATE_INITIALIZE;
+    return 0;
+}
+
+
+static void __init
+fore200e_monitor_putc(struct fore200e* fore200e, char c)
+{
+    struct cp_monitor __iomem * monitor = fore200e->cp_monitor;
+
+#if 0
+    printk("%c", c);
+#endif
+    fore200e->bus->write(((u32) c) | FORE200E_CP_MONITOR_UART_AVAIL, &monitor->soft_uart.send);
+}
+
+
+static int __init
+fore200e_monitor_getc(struct fore200e* fore200e)
+{
+    struct cp_monitor __iomem * monitor = fore200e->cp_monitor;
+    unsigned long      timeout = jiffies + msecs_to_jiffies(50);
+    int                c;
+
+    while (time_before(jiffies, timeout)) {
+
+	c = (int) fore200e->bus->read(&monitor->soft_uart.recv);
+
+	if (c & FORE200E_CP_MONITOR_UART_AVAIL) {
+
+	    fore200e->bus->write(FORE200E_CP_MONITOR_UART_FREE, &monitor->soft_uart.recv);
+#if 0
+	    printk("%c", c & 0xFF);
+#endif
+	    return c & 0xFF;
+	}
+    }
+
+    return -1;
+}
+
+
+static void __init
+fore200e_monitor_puts(struct fore200e* fore200e, char* str)
+{
+    while (*str) {
+
+	/* the i960 monitor doesn't accept any new character if it has something to say */
+	while (fore200e_monitor_getc(fore200e) >= 0);
+	
+	fore200e_monitor_putc(fore200e, *str++);
+    }
+
+    while (fore200e_monitor_getc(fore200e) >= 0);
+}
+
+
+static int __init
+fore200e_start_fw(struct fore200e* fore200e)
+{
+    int               ok;
+    char              cmd[ 48 ];
+    struct fw_header* fw_header = (struct fw_header*) fore200e->bus->fw_data;
+
+    DPRINTK(2, "device %s firmware being started\n", fore200e->name);
+
+#if defined(__sparc_v9__)
+    /* reported to be required by SBA cards on some sparc64 hosts */
+    fore200e_spin(100);
+#endif
+
+    sprintf(cmd, "\rgo %x\r", le32_to_cpu(fw_header->start_offset));
+
+    fore200e_monitor_puts(fore200e, cmd);
+
+    ok = fore200e_io_poll(fore200e, &fore200e->cp_monitor->bstat, BSTAT_CP_RUNNING, 1000);
+    if (ok == 0) {
+	printk(FORE200E "device %s firmware didn't start\n", fore200e->name);
+	return -ENODEV;
+    }
+
+    printk(FORE200E "device %s firmware started\n", fore200e->name);
+
+    fore200e->state = FORE200E_STATE_START_FW;
+    return 0;
+}
+
+
+static int __init
+fore200e_load_fw(struct fore200e* fore200e)
+{
+    u32* fw_data = (u32*) fore200e->bus->fw_data;
+    u32  fw_size = (u32) *fore200e->bus->fw_size / sizeof(u32);
+
+    struct fw_header* fw_header = (struct fw_header*) fw_data;
+
+    u32 __iomem *load_addr = fore200e->virt_base + le32_to_cpu(fw_header->load_offset);
+
+    DPRINTK(2, "device %s firmware being loaded at 0x%p (%d words)\n", 
+	    fore200e->name, load_addr, fw_size);
+
+    if (le32_to_cpu(fw_header->magic) != FW_HEADER_MAGIC) {
+	printk(FORE200E "corrupted %s firmware image\n", fore200e->bus->model_name);
+	return -ENODEV;
+    }
+
+    for (; fw_size--; fw_data++, load_addr++)
+	fore200e->bus->write(le32_to_cpu(*fw_data), load_addr);
+
+    fore200e->state = FORE200E_STATE_LOAD_FW;
+    return 0;
+}
+
+
+static int __init
+fore200e_register(struct fore200e* fore200e)
+{
+    struct atm_dev* atm_dev;
+
+    DPRINTK(2, "device %s being registered\n", fore200e->name);
+
+    atm_dev = atm_dev_register(fore200e->bus->proc_name, &fore200e_ops, -1,
+      NULL); 
+    if (atm_dev == NULL) {
+	printk(FORE200E "unable to register device %s\n", fore200e->name);
+	return -ENODEV;
+    }
+
+    atm_dev->dev_data = fore200e;
+    fore200e->atm_dev = atm_dev;
+
+    atm_dev->ci_range.vpi_bits = FORE200E_VPI_BITS;
+    atm_dev->ci_range.vci_bits = FORE200E_VCI_BITS;
+
+    fore200e->available_cell_rate = ATM_OC3_PCR;
+
+    fore200e->state = FORE200E_STATE_REGISTER;
+    return 0;
+}
+
+
+static int __init
+fore200e_init(struct fore200e* fore200e)
+{
+    if (fore200e_register(fore200e) < 0)
+	return -ENODEV;
+    
+    if (fore200e->bus->configure(fore200e) < 0)
+	return -ENODEV;
+
+    if (fore200e->bus->map(fore200e) < 0)
+	return -ENODEV;
+
+    if (fore200e_reset(fore200e, 1) < 0)
+	return -ENODEV;
+
+    if (fore200e_load_fw(fore200e) < 0)
+	return -ENODEV;
+
+    if (fore200e_start_fw(fore200e) < 0)
+	return -ENODEV;
+
+    if (fore200e_initialize(fore200e) < 0)
+	return -ENODEV;
+
+    if (fore200e_init_cmd_queue(fore200e) < 0)
+	return -ENOMEM;
+
+    if (fore200e_init_tx_queue(fore200e) < 0)
+	return -ENOMEM;
+
+    if (fore200e_init_rx_queue(fore200e) < 0)
+	return -ENOMEM;
+
+    if (fore200e_init_bs_queue(fore200e) < 0)
+	return -ENOMEM;
+
+    if (fore200e_alloc_rx_buf(fore200e) < 0)
+	return -ENOMEM;
+
+    if (fore200e_get_esi(fore200e) < 0)
+	return -EIO;
+
+    if (fore200e_irq_request(fore200e) < 0)
+	return -EBUSY;
+
+    fore200e_supply(fore200e);
+    
+    /* all done, board initialization is now complete */
+    fore200e->state = FORE200E_STATE_COMPLETE;
+    return 0;
+}
+
+
+static int __devinit
+fore200e_pca_detect(struct pci_dev *pci_dev, const struct pci_device_id *pci_ent)
+{
+    const struct fore200e_bus* bus = (struct fore200e_bus*) pci_ent->driver_data;
+    struct fore200e* fore200e;
+    int err = 0;
+    static int index = 0;
+
+    if (pci_enable_device(pci_dev)) {
+	err = -EINVAL;
+	goto out;
+    }
+    
+    fore200e = fore200e_kmalloc(sizeof(struct fore200e), GFP_KERNEL);
+    if (fore200e == NULL) {
+	err = -ENOMEM;
+	goto out_disable;
+    }
+
+    fore200e->bus       = bus;
+    fore200e->bus_dev   = pci_dev;    
+    fore200e->irq       = pci_dev->irq;
+    fore200e->phys_base = pci_resource_start(pci_dev, 0);
+
+    sprintf(fore200e->name, "%s-%d", bus->model_name, index - 1);
+
+    pci_set_master(pci_dev);
+
+    printk(FORE200E "device %s found at 0x%lx, IRQ %s\n",
+	   fore200e->bus->model_name, 
+	   fore200e->phys_base, fore200e_irq_itoa(fore200e->irq));
+
+    sprintf(fore200e->name, "%s-%d", bus->model_name, index);
+
+    err = fore200e_init(fore200e);
+    if (err < 0) {
+	fore200e_shutdown(fore200e);
+	goto out_free;
+    }
+
+    ++index;
+    pci_set_drvdata(pci_dev, fore200e);
+
+out:
+    return err;
+
+out_free:
+    kfree(fore200e);
+out_disable:
+    pci_disable_device(pci_dev);
+    goto out;
+}
+
+
+static void __devexit fore200e_pca_remove_one(struct pci_dev *pci_dev)
+{
+    struct fore200e *fore200e;
+
+    fore200e = pci_get_drvdata(pci_dev);
+
+    list_del(&fore200e->entry);
+
+    fore200e_shutdown(fore200e);
+    kfree(fore200e);
+    pci_disable_device(pci_dev);
+}
+
+
+#ifdef CONFIG_ATM_FORE200E_PCA
+static struct pci_device_id fore200e_pca_tbl[] = {
+    { PCI_VENDOR_ID_FORE, PCI_DEVICE_ID_FORE_PCA200E, PCI_ANY_ID, PCI_ANY_ID,
+      0, 0, (unsigned long) &fore200e_bus[0] },
+    { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, fore200e_pca_tbl);
+
+static struct pci_driver fore200e_pca_driver = {
+    .name =     "fore_200e",
+    .probe =    fore200e_pca_detect,
+    .remove =   __devexit_p(fore200e_pca_remove_one),
+    .id_table = fore200e_pca_tbl,
+};
+#endif
+
+
+static int __init
+fore200e_module_init(void)
+{
+    const struct fore200e_bus* bus;
+    struct       fore200e*     fore200e;
+    int                        index;
+
+    printk(FORE200E "FORE Systems 200E-series ATM driver - version " FORE200E_VERSION "\n");
+
+    /* for each configured bus interface */
+    for (bus = fore200e_bus; bus->model_name; bus++) {
+
+	/* detect all boards present on that bus */
+	for (index = 0; bus->detect && (fore200e = bus->detect(bus, index)); index++) {
+	    
+	    printk(FORE200E "device %s found at 0x%lx, IRQ %s\n",
+		   fore200e->bus->model_name, 
+		   fore200e->phys_base, fore200e_irq_itoa(fore200e->irq));
+
+	    sprintf(fore200e->name, "%s-%d", bus->model_name, index);
+
+	    if (fore200e_init(fore200e) < 0) {
+
+		fore200e_shutdown(fore200e);
+		break;
+	    }
+
+	    list_add(&fore200e->entry, &fore200e_boards);
+	}
+    }
+
+#ifdef CONFIG_ATM_FORE200E_PCA
+    if (!pci_module_init(&fore200e_pca_driver))
+	return 0;
+#endif
+
+    if (!list_empty(&fore200e_boards))
+	return 0;
+
+    return -ENODEV;
+}
+
+
+static void __exit
+fore200e_module_cleanup(void)
+{
+    struct fore200e *fore200e, *next;
+
+#ifdef CONFIG_ATM_FORE200E_PCA
+    pci_unregister_driver(&fore200e_pca_driver);
+#endif
+
+    list_for_each_entry_safe(fore200e, next, &fore200e_boards, entry) {
+	fore200e_shutdown(fore200e);
+	kfree(fore200e);
+    }
+    DPRINTK(1, "module being removed\n");
+}
+
+
+static int
+fore200e_proc_read(struct atm_dev *dev, loff_t* pos, char* page)
+{
+    struct fore200e*     fore200e  = FORE200E_DEV(dev);
+    struct fore200e_vcc* fore200e_vcc;
+    struct atm_vcc*      vcc;
+    int                  i, len, left = *pos;
+    unsigned long        flags;
+
+    if (!left--) {
+
+	if (fore200e_getstats(fore200e) < 0)
+	    return -EIO;
+
+	len = sprintf(page,"\n"
+		       " device:\n"
+		       "   internal name:\t\t%s\n", fore200e->name);
+
+	/* print bus-specific information */
+	if (fore200e->bus->proc_read)
+	    len += fore200e->bus->proc_read(fore200e, page + len);
+	
+	len += sprintf(page + len,
+		"   interrupt line:\t\t%s\n"
+		"   physical base address:\t0x%p\n"
+		"   virtual base address:\t0x%p\n"
+		"   factory address (ESI):\t%02x:%02x:%02x:%02x:%02x:%02x\n"
+		"   board serial number:\t\t%d\n\n",
+		fore200e_irq_itoa(fore200e->irq),
+		(void*)fore200e->phys_base,
+		fore200e->virt_base,
+		fore200e->esi[0], fore200e->esi[1], fore200e->esi[2],
+		fore200e->esi[3], fore200e->esi[4], fore200e->esi[5],
+		fore200e->esi[4] * 256 + fore200e->esi[5]);
+
+	return len;
+    }
+
+    if (!left--)
+	return sprintf(page,
+		       "   free small bufs, scheme 1:\t%d\n"
+		       "   free large bufs, scheme 1:\t%d\n"
+		       "   free small bufs, scheme 2:\t%d\n"
+		       "   free large bufs, scheme 2:\t%d\n",
+		       fore200e->host_bsq[ BUFFER_SCHEME_ONE ][ BUFFER_MAGN_SMALL ].freebuf_count,
+		       fore200e->host_bsq[ BUFFER_SCHEME_ONE ][ BUFFER_MAGN_LARGE ].freebuf_count,
+		       fore200e->host_bsq[ BUFFER_SCHEME_TWO ][ BUFFER_MAGN_SMALL ].freebuf_count,
+		       fore200e->host_bsq[ BUFFER_SCHEME_TWO ][ BUFFER_MAGN_LARGE ].freebuf_count);
+
+    if (!left--) {
+	u32 hb = fore200e->bus->read(&fore200e->cp_queues->heartbeat);
+
+	len = sprintf(page,"\n\n"
+		      " cell processor:\n"
+		      "   heartbeat state:\t\t");
+	
+	if (hb >> 16 != 0xDEAD)
+	    len += sprintf(page + len, "0x%08x\n", hb);
+	else
+	    len += sprintf(page + len, "*** FATAL ERROR %04x ***\n", hb & 0xFFFF);
+
+	return len;
+    }
+
+    if (!left--) {
+	static const char* media_name[] = {
+	    "unshielded twisted pair",
+	    "multimode optical fiber ST",
+	    "multimode optical fiber SC",
+	    "single-mode optical fiber ST",
+	    "single-mode optical fiber SC",
+	    "unknown"
+	};
+
+	static const char* oc3_mode[] = {
+	    "normal operation",
+	    "diagnostic loopback",
+	    "line loopback",
+	    "unknown"
+	};
+
+	u32 fw_release     = fore200e->bus->read(&fore200e->cp_queues->fw_release);
+	u32 mon960_release = fore200e->bus->read(&fore200e->cp_queues->mon960_release);
+	u32 oc3_revision   = fore200e->bus->read(&fore200e->cp_queues->oc3_revision);
+	u32 media_index    = FORE200E_MEDIA_INDEX(fore200e->bus->read(&fore200e->cp_queues->media_type));
+	u32 oc3_index;
+
+	if ((media_index < 0) || (media_index > 4))
+	    media_index = 5;
+	
+	switch (fore200e->loop_mode) {
+	    case ATM_LM_NONE:    oc3_index = 0;
+		                 break;
+	    case ATM_LM_LOC_PHY: oc3_index = 1;
+		                 break;
+	    case ATM_LM_RMT_PHY: oc3_index = 2;
+		                 break;
+	    default:             oc3_index = 3;
+	}
+
+	return sprintf(page,
+		       "   firmware release:\t\t%d.%d.%d\n"
+		       "   monitor release:\t\t%d.%d\n"
+		       "   media type:\t\t\t%s\n"
+		       "   OC-3 revision:\t\t0x%x\n"
+                       "   OC-3 mode:\t\t\t%s",
+		       fw_release >> 16, fw_release << 16 >> 24,  fw_release << 24 >> 24,
+		       mon960_release >> 16, mon960_release << 16 >> 16,
+		       media_name[ media_index ],
+		       oc3_revision,
+		       oc3_mode[ oc3_index ]);
+    }
+
+    if (!left--) {
+	struct cp_monitor __iomem * cp_monitor = fore200e->cp_monitor;
+
+	return sprintf(page,
+		       "\n\n"
+		       " monitor:\n"
+		       "   version number:\t\t%d\n"
+		       "   boot status word:\t\t0x%08x\n",
+		       fore200e->bus->read(&cp_monitor->mon_version),
+		       fore200e->bus->read(&cp_monitor->bstat));
+    }
+
+    if (!left--)
+	return sprintf(page,
+		       "\n"
+		       " device statistics:\n"
+		       "  4b5b:\n"
+		       "     crc_header_errors:\t\t%10u\n"
+		       "     framing_errors:\t\t%10u\n",
+		       fore200e_swap(fore200e->stats->phy.crc_header_errors),
+		       fore200e_swap(fore200e->stats->phy.framing_errors));
+    
+    if (!left--)
+	return sprintf(page, "\n"
+		       "  OC-3:\n"
+		       "     section_bip8_errors:\t%10u\n"
+		       "     path_bip8_errors:\t\t%10u\n"
+		       "     line_bip24_errors:\t\t%10u\n"
+		       "     line_febe_errors:\t\t%10u\n"
+		       "     path_febe_errors:\t\t%10u\n"
+		       "     corr_hcs_errors:\t\t%10u\n"
+		       "     ucorr_hcs_errors:\t\t%10u\n",
+		       fore200e_swap(fore200e->stats->oc3.section_bip8_errors),
+		       fore200e_swap(fore200e->stats->oc3.path_bip8_errors),
+		       fore200e_swap(fore200e->stats->oc3.line_bip24_errors),
+		       fore200e_swap(fore200e->stats->oc3.line_febe_errors),
+		       fore200e_swap(fore200e->stats->oc3.path_febe_errors),
+		       fore200e_swap(fore200e->stats->oc3.corr_hcs_errors),
+		       fore200e_swap(fore200e->stats->oc3.ucorr_hcs_errors));
+
+    if (!left--)
+	return sprintf(page,"\n"
+		       "   ATM:\t\t\t\t     cells\n"
+		       "     TX:\t\t\t%10u\n"
+		       "     RX:\t\t\t%10u\n"
+		       "     vpi out of range:\t\t%10u\n"
+		       "     vpi no conn:\t\t%10u\n"
+		       "     vci out of range:\t\t%10u\n"
+		       "     vci no conn:\t\t%10u\n",
+		       fore200e_swap(fore200e->stats->atm.cells_transmitted),
+		       fore200e_swap(fore200e->stats->atm.cells_received),
+		       fore200e_swap(fore200e->stats->atm.vpi_bad_range),
+		       fore200e_swap(fore200e->stats->atm.vpi_no_conn),
+		       fore200e_swap(fore200e->stats->atm.vci_bad_range),
+		       fore200e_swap(fore200e->stats->atm.vci_no_conn));
+    
+    if (!left--)
+	return sprintf(page,"\n"
+		       "   AAL0:\t\t\t     cells\n"
+		       "     TX:\t\t\t%10u\n"
+		       "     RX:\t\t\t%10u\n"
+		       "     dropped:\t\t\t%10u\n",
+		       fore200e_swap(fore200e->stats->aal0.cells_transmitted),
+		       fore200e_swap(fore200e->stats->aal0.cells_received),
+		       fore200e_swap(fore200e->stats->aal0.cells_dropped));
+    
+    if (!left--)
+	return sprintf(page,"\n"
+		       "   AAL3/4:\n"
+		       "     SAR sublayer:\t\t     cells\n"
+		       "       TX:\t\t\t%10u\n"
+		       "       RX:\t\t\t%10u\n"
+		       "       dropped:\t\t\t%10u\n"
+		       "       CRC errors:\t\t%10u\n"
+		       "       protocol errors:\t\t%10u\n\n"
+		       "     CS  sublayer:\t\t      PDUs\n"
+		       "       TX:\t\t\t%10u\n"
+		       "       RX:\t\t\t%10u\n"
+		       "       dropped:\t\t\t%10u\n"
+		       "       protocol errors:\t\t%10u\n",
+		       fore200e_swap(fore200e->stats->aal34.cells_transmitted),
+		       fore200e_swap(fore200e->stats->aal34.cells_received),
+		       fore200e_swap(fore200e->stats->aal34.cells_dropped),
+		       fore200e_swap(fore200e->stats->aal34.cells_crc_errors),
+		       fore200e_swap(fore200e->stats->aal34.cells_protocol_errors),
+		       fore200e_swap(fore200e->stats->aal34.cspdus_transmitted),
+		       fore200e_swap(fore200e->stats->aal34.cspdus_received),
+		       fore200e_swap(fore200e->stats->aal34.cspdus_dropped),
+		       fore200e_swap(fore200e->stats->aal34.cspdus_protocol_errors));
+    
+    if (!left--)
+	return sprintf(page,"\n"
+		       "   AAL5:\n"
+		       "     SAR sublayer:\t\t     cells\n"
+		       "       TX:\t\t\t%10u\n"
+		       "       RX:\t\t\t%10u\n"
+		       "       dropped:\t\t\t%10u\n"
+		       "       congestions:\t\t%10u\n\n"
+		       "     CS  sublayer:\t\t      PDUs\n"
+		       "       TX:\t\t\t%10u\n"
+		       "       RX:\t\t\t%10u\n"
+		       "       dropped:\t\t\t%10u\n"
+		       "       CRC errors:\t\t%10u\n"
+		       "       protocol errors:\t\t%10u\n",
+		       fore200e_swap(fore200e->stats->aal5.cells_transmitted),
+		       fore200e_swap(fore200e->stats->aal5.cells_received),
+		       fore200e_swap(fore200e->stats->aal5.cells_dropped),
+		       fore200e_swap(fore200e->stats->aal5.congestion_experienced),
+		       fore200e_swap(fore200e->stats->aal5.cspdus_transmitted),
+		       fore200e_swap(fore200e->stats->aal5.cspdus_received),
+		       fore200e_swap(fore200e->stats->aal5.cspdus_dropped),
+		       fore200e_swap(fore200e->stats->aal5.cspdus_crc_errors),
+		       fore200e_swap(fore200e->stats->aal5.cspdus_protocol_errors));
+    
+    if (!left--)
+	return sprintf(page,"\n"
+		       "   AUX:\t\t       allocation failures\n"
+		       "     small b1:\t\t\t%10u\n"
+		       "     large b1:\t\t\t%10u\n"
+		       "     small b2:\t\t\t%10u\n"
+		       "     large b2:\t\t\t%10u\n"
+		       "     RX PDUs:\t\t\t%10u\n"
+		       "     TX PDUs:\t\t\t%10lu\n",
+		       fore200e_swap(fore200e->stats->aux.small_b1_failed),
+		       fore200e_swap(fore200e->stats->aux.large_b1_failed),
+		       fore200e_swap(fore200e->stats->aux.small_b2_failed),
+		       fore200e_swap(fore200e->stats->aux.large_b2_failed),
+		       fore200e_swap(fore200e->stats->aux.rpd_alloc_failed),
+		       fore200e->tx_sat);
+    
+    if (!left--)
+	return sprintf(page,"\n"
+		       " receive carrier:\t\t\t%s\n",
+		       fore200e->stats->aux.receive_carrier ? "ON" : "OFF!");
+    
+    if (!left--) {
+        return sprintf(page,"\n"
+		       " VCCs:\n  address   VPI VCI   AAL "
+		       "TX PDUs   TX min/max size  RX PDUs   RX min/max size\n");
+    }
+
+    for (i = 0; i < NBR_CONNECT; i++) {
+
+	vcc = fore200e->vc_map[i].vcc;
+
+	if (vcc == NULL)
+	    continue;
+
+	spin_lock_irqsave(&fore200e->q_lock, flags);
+
+	if (vcc && test_bit(ATM_VF_READY, &vcc->flags) && !left--) {
+
+	    fore200e_vcc = FORE200E_VCC(vcc);
+	    ASSERT(fore200e_vcc);
+
+	    len = sprintf(page,
+			  "  %08x  %03d %05d %1d   %09lu %05d/%05d      %09lu %05d/%05d\n",
+			  (u32)(unsigned long)vcc,
+			  vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal),
+			  fore200e_vcc->tx_pdu,
+			  fore200e_vcc->tx_min_pdu > 0xFFFF ? 0 : fore200e_vcc->tx_min_pdu,
+			  fore200e_vcc->tx_max_pdu,
+			  fore200e_vcc->rx_pdu,
+			  fore200e_vcc->rx_min_pdu > 0xFFFF ? 0 : fore200e_vcc->rx_min_pdu,
+			  fore200e_vcc->rx_max_pdu);
+
+	    spin_unlock_irqrestore(&fore200e->q_lock, flags);
+	    return len;
+	}
+
+	spin_unlock_irqrestore(&fore200e->q_lock, flags);
+    }
+    
+    return 0;
+}
+
+module_init(fore200e_module_init);
+module_exit(fore200e_module_cleanup);
+
+
+static const struct atmdev_ops fore200e_ops =
+{
+	.open       = fore200e_open,
+	.close      = fore200e_close,
+	.ioctl      = fore200e_ioctl,
+	.getsockopt = fore200e_getsockopt,
+	.setsockopt = fore200e_setsockopt,
+	.send       = fore200e_send,
+	.change_qos = fore200e_change_qos,
+	.proc_read  = fore200e_proc_read,
+	.owner      = THIS_MODULE
+};
+
+
+#ifdef CONFIG_ATM_FORE200E_PCA
+extern const unsigned char _fore200e_pca_fw_data[];
+extern const unsigned int  _fore200e_pca_fw_size;
+#endif
+#ifdef CONFIG_ATM_FORE200E_SBA
+extern const unsigned char _fore200e_sba_fw_data[];
+extern const unsigned int  _fore200e_sba_fw_size;
+#endif
+
+static const struct fore200e_bus fore200e_bus[] = {
+#ifdef CONFIG_ATM_FORE200E_PCA
+    { "PCA-200E", "pca200e", 32, 4, 32, 
+      _fore200e_pca_fw_data, &_fore200e_pca_fw_size,
+      fore200e_pca_read,
+      fore200e_pca_write,
+      fore200e_pca_dma_map,
+      fore200e_pca_dma_unmap,
+      fore200e_pca_dma_sync_for_cpu,
+      fore200e_pca_dma_sync_for_device,
+      fore200e_pca_dma_chunk_alloc,
+      fore200e_pca_dma_chunk_free,
+      NULL,
+      fore200e_pca_configure,
+      fore200e_pca_map,
+      fore200e_pca_reset,
+      fore200e_pca_prom_read,
+      fore200e_pca_unmap,
+      NULL,
+      fore200e_pca_irq_check,
+      fore200e_pca_irq_ack,
+      fore200e_pca_proc_read,
+    },
+#endif
+#ifdef CONFIG_ATM_FORE200E_SBA
+    { "SBA-200E", "sba200e", 32, 64, 32,
+      _fore200e_sba_fw_data, &_fore200e_sba_fw_size,
+      fore200e_sba_read,
+      fore200e_sba_write,
+      fore200e_sba_dma_map,
+      fore200e_sba_dma_unmap,
+      fore200e_sba_dma_sync_for_cpu,
+      fore200e_sba_dma_sync_for_device,
+      fore200e_sba_dma_chunk_alloc,
+      fore200e_sba_dma_chunk_free,
+      fore200e_sba_detect, 
+      fore200e_sba_configure,
+      fore200e_sba_map,
+      fore200e_sba_reset,
+      fore200e_sba_prom_read,
+      fore200e_sba_unmap,
+      fore200e_sba_irq_enable,
+      fore200e_sba_irq_check,
+      fore200e_sba_irq_ack,
+      fore200e_sba_proc_read,
+    },
+#endif
+    {}
+};
+
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
diff --git a/drivers/atm/fore200e.h b/drivers/atm/fore200e.h
new file mode 100644
index 0000000..2558eb8
--- /dev/null
+++ b/drivers/atm/fore200e.h
@@ -0,0 +1,985 @@
+/* $Id: fore200e.h,v 1.4 2000/04/14 10:10:34 davem Exp $ */
+#ifndef _FORE200E_H
+#define _FORE200E_H
+
+#ifdef __KERNEL__
+#include <linux/config.h>
+
+/* rx buffer sizes */
+
+#define SMALL_BUFFER_SIZE    384     /* size of small buffers (multiple of 48 (PCA) and 64 (SBA) bytes) */
+#define LARGE_BUFFER_SIZE    4032    /* size of large buffers (multiple of 48 (PCA) and 64 (SBA) bytes) */
+
+
+#define RBD_BLK_SIZE	     32      /* nbr of supplied rx buffers per rbd */
+
+
+#define MAX_PDU_SIZE	     65535   /* maximum PDU size supported by AALs */
+
+
+#define BUFFER_S1_SIZE       SMALL_BUFFER_SIZE    /* size of small buffers, scheme 1 */
+#define BUFFER_L1_SIZE       LARGE_BUFFER_SIZE    /* size of large buffers, scheme 1 */
+
+#define BUFFER_S2_SIZE       SMALL_BUFFER_SIZE    /* size of small buffers, scheme 2 */
+#define BUFFER_L2_SIZE       LARGE_BUFFER_SIZE    /* size of large buffers, scheme 2 */
+
+#define BUFFER_S1_NBR        (RBD_BLK_SIZE * 6)
+#define BUFFER_L1_NBR        (RBD_BLK_SIZE * 4)
+
+#define BUFFER_S2_NBR        (RBD_BLK_SIZE * 6)
+#define BUFFER_L2_NBR        (RBD_BLK_SIZE * 4)
+
+
+#define QUEUE_SIZE_CMD       16	     /* command queue capacity       */
+#define QUEUE_SIZE_RX	     64	     /* receive queue capacity       */
+#define QUEUE_SIZE_TX	     256     /* transmit queue capacity      */
+#define QUEUE_SIZE_BS        32	     /* buffer supply queue capacity */
+
+#define FORE200E_VPI_BITS     0
+#define FORE200E_VCI_BITS    10
+#define NBR_CONNECT          (1 << (FORE200E_VPI_BITS + FORE200E_VCI_BITS)) /* number of connections */
+
+
+#define TSD_FIXED            2
+#define TSD_EXTENSION        0
+#define TSD_NBR              (TSD_FIXED + TSD_EXTENSION)
+
+
+/* the cp starts putting a received PDU into one *small* buffer,
+   then it uses a number of *large* buffers for the trailing data. 
+   we compute here the total number of receive segment descriptors 
+   required to hold the largest possible PDU */
+
+#define RSD_REQUIRED  (((MAX_PDU_SIZE - SMALL_BUFFER_SIZE + LARGE_BUFFER_SIZE) / LARGE_BUFFER_SIZE) + 1)
+
+#define RSD_FIXED     3
+
+/* RSD_REQUIRED receive segment descriptors are enough to describe a max-sized PDU,
+   but we have to keep the size of the receive PDU descriptor multiple of 32 bytes,
+   so we add one extra RSD to RSD_EXTENSION 
+   (WARNING: THIS MAY CHANGE IF BUFFER SIZES ARE MODIFIED) */
+
+#define RSD_EXTENSION  ((RSD_REQUIRED - RSD_FIXED) + 1)
+#define RSD_NBR         (RSD_FIXED + RSD_EXTENSION)
+
+
+#define FORE200E_DEV(d)          ((struct fore200e*)((d)->dev_data))
+#define FORE200E_VCC(d)          ((struct fore200e_vcc*)((d)->dev_data))
+
+/* bitfields endian games */
+
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+#define BITFIELD2(b1, b2)                    b1; b2;
+#define BITFIELD3(b1, b2, b3)                b1; b2; b3;
+#define BITFIELD4(b1, b2, b3, b4)            b1; b2; b3; b4;
+#define BITFIELD5(b1, b2, b3, b4, b5)        b1; b2; b3; b4; b5;
+#define BITFIELD6(b1, b2, b3, b4, b5, b6)    b1; b2; b3; b4; b5; b6;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+#define BITFIELD2(b1, b2)                                    b2; b1;
+#define BITFIELD3(b1, b2, b3)                            b3; b2; b1;
+#define BITFIELD4(b1, b2, b3, b4)                    b4; b3; b2; b1;
+#define BITFIELD5(b1, b2, b3, b4, b5)            b5; b4; b3; b2; b1;
+#define BITFIELD6(b1, b2, b3, b4, b5, b6)    b6; b5; b4; b3; b2; b1;
+#else
+#error unknown bitfield endianess
+#endif
+
+ 
+/* ATM cell header (minus HEC byte) */
+
+typedef struct atm_header {
+    BITFIELD5( 
+        u32 clp :  1,    /* cell loss priority         */
+        u32 plt :  3,    /* payload type               */
+        u32 vci : 16,    /* virtual channel identifier */
+        u32 vpi :  8,    /* virtual path identifier    */
+        u32 gfc :  4     /* generic flow control       */
+   )
+} atm_header_t;
+
+
+/* ATM adaptation layer id */
+
+typedef enum fore200e_aal {
+    FORE200E_AAL0  = 0,
+    FORE200E_AAL34 = 4,
+    FORE200E_AAL5  = 5,
+} fore200e_aal_t;
+
+
+/* transmit PDU descriptor specification */
+
+typedef struct tpd_spec {
+    BITFIELD4(
+        u32               length : 16,    /* total PDU length            */
+        u32               nseg   :  8,    /* number of transmit segments */
+        enum fore200e_aal aal    :  4,    /* adaptation layer            */
+        u32               intr   :  4     /* interrupt requested         */
+    )
+} tpd_spec_t;
+
+
+/* transmit PDU rate control */
+
+typedef struct tpd_rate
+{
+    BITFIELD2( 
+        u32 idle_cells : 16,    /* number of idle cells to insert   */
+        u32 data_cells : 16     /* number of data cells to transmit */
+    )
+} tpd_rate_t;
+
+
+/* transmit segment descriptor */
+
+typedef struct tsd {
+    u32 buffer;    /* transmit buffer DMA address */
+    u32 length;    /* number of bytes in buffer   */
+} tsd_t;
+
+
+/* transmit PDU descriptor */
+
+typedef struct tpd {
+    struct atm_header atm_header;        /* ATM header minus HEC byte    */
+    struct tpd_spec   spec;              /* tpd specification            */
+    struct tpd_rate   rate;              /* tpd rate control             */
+    u32               pad;               /* reserved                     */
+    struct tsd        tsd[ TSD_NBR ];    /* transmit segment descriptors */
+} tpd_t;
+
+
+/* receive segment descriptor */
+
+typedef struct rsd {
+    u32 handle;    /* host supplied receive buffer handle */
+    u32 length;    /* number of bytes in buffer           */
+} rsd_t;
+
+
+/* receive PDU descriptor */
+
+typedef struct rpd {
+    struct atm_header atm_header;        /* ATM header minus HEC byte   */
+    u32               nseg;              /* number of receive segments  */
+    struct rsd        rsd[ RSD_NBR ];    /* receive segment descriptors */
+} rpd_t;
+
+
+/* buffer scheme */
+
+typedef enum buffer_scheme {
+    BUFFER_SCHEME_ONE,
+    BUFFER_SCHEME_TWO,
+    BUFFER_SCHEME_NBR    /* always last */
+} buffer_scheme_t;
+
+
+/* buffer magnitude */
+
+typedef enum buffer_magn {
+    BUFFER_MAGN_SMALL,
+    BUFFER_MAGN_LARGE,
+    BUFFER_MAGN_NBR    /* always last */
+} buffer_magn_t;
+
+
+/* receive buffer descriptor */
+
+typedef struct rbd {
+    u32 handle;          /* host supplied handle            */
+    u32 buffer_haddr;    /* host DMA address of host buffer */
+} rbd_t;
+
+
+/* receive buffer descriptor block */
+
+typedef struct rbd_block {
+    struct rbd rbd[ RBD_BLK_SIZE ];    /* receive buffer descriptor */
+} rbd_block_t;
+
+
+/* tpd DMA address */
+
+typedef struct tpd_haddr {
+    BITFIELD3( 
+        u32 size  :  4,    /* tpd size expressed in 32 byte blocks     */
+        u32 pad   :  1,    /* reserved                                 */
+        u32 haddr : 27     /* tpd DMA addr aligned on 32 byte boundary */
+    )
+} tpd_haddr_t;
+
+#define TPD_HADDR_SHIFT 5  /* addr aligned on 32 byte boundary */
+
+/* cp resident transmit queue entry */
+
+typedef struct cp_txq_entry {
+    struct tpd_haddr tpd_haddr;       /* host DMA address of tpd                */
+    u32              status_haddr;    /* host DMA address of completion status  */
+} cp_txq_entry_t;
+
+
+/* cp resident receive queue entry */
+
+typedef struct cp_rxq_entry {
+    u32 rpd_haddr;       /* host DMA address of rpd                */
+    u32 status_haddr;    /* host DMA address of completion status  */
+} cp_rxq_entry_t;
+
+
+/* cp resident buffer supply queue entry */
+
+typedef struct cp_bsq_entry {
+    u32 rbd_block_haddr;    /* host DMA address of rbd block          */
+    u32 status_haddr;       /* host DMA address of completion status  */
+} cp_bsq_entry_t;
+
+
+/* completion status */
+
+typedef volatile enum status {
+    STATUS_PENDING  = (1<<0),    /* initial status (written by host)  */
+    STATUS_COMPLETE = (1<<1),    /* completion status (written by cp) */
+    STATUS_FREE     = (1<<2),    /* initial status (written by host)  */
+    STATUS_ERROR    = (1<<3)     /* completion status (written by cp) */
+} status_t;
+
+
+/* cp operation code */
+
+typedef enum opcode {
+    OPCODE_INITIALIZE = 1,          /* initialize board                       */
+    OPCODE_ACTIVATE_VCIN,           /* activate incoming VCI                  */
+    OPCODE_ACTIVATE_VCOUT,          /* activate outgoing VCI                  */
+    OPCODE_DEACTIVATE_VCIN,         /* deactivate incoming VCI                */
+    OPCODE_DEACTIVATE_VCOUT,        /* deactivate incoing VCI                 */
+    OPCODE_GET_STATS,               /* get board statistics                   */
+    OPCODE_SET_OC3,                 /* set OC-3 registers                     */
+    OPCODE_GET_OC3,                 /* get OC-3 registers                     */
+    OPCODE_RESET_STATS,             /* reset board statistics                 */
+    OPCODE_GET_PROM,                /* get expansion PROM data (PCI specific) */
+    OPCODE_SET_VPI_BITS,            /* set x bits of those decoded by the
+				       firmware to be low order bits from
+				       the VPI field of the ATM cell header   */
+    OPCODE_REQUEST_INTR = (1<<7)    /* request interrupt                      */
+} opcode_t;
+
+
+/* virtual path / virtual channel identifers */
+
+typedef struct vpvc {
+    BITFIELD3(
+        u32 vci : 16,    /* virtual channel identifier */
+        u32 vpi :  8,    /* virtual path identifier    */
+        u32 pad :  8     /* reserved                   */
+    )
+} vpvc_t;
+
+
+/* activate VC command opcode */
+
+typedef struct activate_opcode {
+    BITFIELD4( 
+        enum opcode        opcode : 8,    /* cp opcode        */
+        enum fore200e_aal  aal    : 8,    /* adaptation layer */
+        enum buffer_scheme scheme : 8,    /* buffer scheme    */
+        u32  pad                  : 8     /* reserved         */
+   )
+} activate_opcode_t;
+
+
+/* activate VC command block */
+
+typedef struct activate_block {
+    struct activate_opcode  opcode;    /* activate VC command opcode */
+    struct vpvc             vpvc;      /* VPI/VCI                    */
+    u32                     mtu;       /* for AAL0 only              */
+
+} activate_block_t;
+
+
+/* deactivate VC command opcode */
+
+typedef struct deactivate_opcode {
+    BITFIELD2(
+        enum opcode opcode :  8,    /* cp opcode */
+        u32         pad    : 24     /* reserved  */
+    )
+} deactivate_opcode_t;
+
+
+/* deactivate VC command block */
+
+typedef struct deactivate_block {
+    struct deactivate_opcode opcode;    /* deactivate VC command opcode */
+    struct vpvc              vpvc;      /* VPI/VCI                      */
+} deactivate_block_t;
+
+
+/* OC-3 registers */
+
+typedef struct oc3_regs {
+    u32 reg[ 128 ];    /* see the PMC Sierra PC5346 S/UNI-155-Lite
+			  Saturn User Network Interface documentation
+			  for a description of the OC-3 chip registers */
+} oc3_regs_t;
+
+
+/* set/get OC-3 regs command opcode */
+
+typedef struct oc3_opcode {
+    BITFIELD4(
+        enum opcode opcode : 8,    /* cp opcode                           */
+	u32         reg    : 8,    /* register index                      */
+	u32         value  : 8,    /* register value                      */
+	u32         mask   : 8     /* register mask that specifies which
+				      bits of the register value field
+				      are significant                     */
+    )
+} oc3_opcode_t;
+
+
+/* set/get OC-3 regs command block */
+
+typedef struct oc3_block {
+    struct oc3_opcode opcode;        /* set/get OC-3 regs command opcode     */
+    u32               regs_haddr;    /* host DMA address of OC-3 regs buffer */
+} oc3_block_t;
+
+
+/* physical encoding statistics */
+
+typedef struct stats_phy {
+    u32 crc_header_errors;    /* cells received with bad header CRC */
+    u32 framing_errors;       /* cells received with bad framing    */
+    u32 pad[ 2 ];             /* i960 padding                       */
+} stats_phy_t;
+
+
+/* OC-3 statistics */
+
+typedef struct stats_oc3 {
+    u32 section_bip8_errors;    /* section 8 bit interleaved parity    */
+    u32 path_bip8_errors;       /* path 8 bit interleaved parity       */
+    u32 line_bip24_errors;      /* line 24 bit interleaved parity      */
+    u32 line_febe_errors;       /* line far end block errors           */
+    u32 path_febe_errors;       /* path far end block errors           */
+    u32 corr_hcs_errors;        /* correctable header check sequence   */
+    u32 ucorr_hcs_errors;       /* uncorrectable header check sequence */
+    u32 pad[ 1 ];               /* i960 padding                        */
+} stats_oc3_t;
+
+
+/* ATM statistics */
+
+typedef struct stats_atm {
+    u32	cells_transmitted;    /* cells transmitted                 */
+    u32	cells_received;       /* cells received                    */
+    u32	vpi_bad_range;        /* cell drops: VPI out of range      */
+    u32	vpi_no_conn;          /* cell drops: no connection for VPI */
+    u32	vci_bad_range;        /* cell drops: VCI out of range      */
+    u32	vci_no_conn;          /* cell drops: no connection for VCI */
+    u32	pad[ 2 ];             /* i960 padding                      */
+} stats_atm_t;
+
+/* AAL0 statistics */
+
+typedef struct stats_aal0 {
+    u32	cells_transmitted;    /* cells transmitted */
+    u32	cells_received;       /* cells received    */
+    u32	cells_dropped;        /* cells dropped     */
+    u32	pad[ 1 ];             /* i960 padding      */
+} stats_aal0_t;
+
+
+/* AAL3/4 statistics */
+
+typedef struct stats_aal34 {
+    u32	cells_transmitted;         /* cells transmitted from segmented PDUs */
+    u32	cells_received;            /* cells reassembled into PDUs           */
+    u32	cells_crc_errors;          /* payload CRC error count               */
+    u32	cells_protocol_errors;     /* SAR or CS layer protocol errors       */
+    u32	cells_dropped;             /* cells dropped: partial reassembly     */
+    u32	cspdus_transmitted;        /* CS PDUs transmitted                   */
+    u32	cspdus_received;           /* CS PDUs received                      */
+    u32	cspdus_protocol_errors;    /* CS layer protocol errors              */
+    u32	cspdus_dropped;            /* reassembled PDUs drop'd (in cells)    */
+    u32	pad[ 3 ];                  /* i960 padding                          */
+} stats_aal34_t;
+
+
+/* AAL5 statistics */
+
+typedef struct stats_aal5 {
+    u32	cells_transmitted;         /* cells transmitted from segmented SDUs */
+    u32	cells_received;		   /* cells reassembled into SDUs           */
+    u32	cells_dropped;		   /* reassembled PDUs dropped (in cells)   */
+    u32	congestion_experienced;    /* CRC error and length wrong            */
+    u32	cspdus_transmitted;        /* CS PDUs transmitted                   */
+    u32	cspdus_received;           /* CS PDUs received                      */
+    u32	cspdus_crc_errors;         /* CS PDUs CRC errors                    */
+    u32	cspdus_protocol_errors;    /* CS layer protocol errors              */
+    u32	cspdus_dropped;            /* reassembled PDUs dropped              */
+    u32	pad[ 3 ];                  /* i960 padding                          */
+} stats_aal5_t;
+
+
+/* auxiliary statistics */
+
+typedef struct stats_aux {
+    u32	small_b1_failed;     /* receive BD allocation failures  */
+    u32	large_b1_failed;     /* receive BD allocation failures  */
+    u32	small_b2_failed;     /* receive BD allocation failures  */
+    u32	large_b2_failed;     /* receive BD allocation failures  */
+    u32	rpd_alloc_failed;    /* receive PDU allocation failures */
+    u32	receive_carrier;     /* no carrier = 0, carrier = 1     */
+    u32	pad[ 2 ];            /* i960 padding                    */
+} stats_aux_t;
+
+
+/* whole statistics buffer */
+
+typedef struct stats {
+    struct stats_phy   phy;      /* physical encoding statistics */
+    struct stats_oc3   oc3;      /* OC-3 statistics              */
+    struct stats_atm   atm;      /* ATM statistics               */
+    struct stats_aal0  aal0;     /* AAL0 statistics              */
+    struct stats_aal34 aal34;    /* AAL3/4 statistics            */
+    struct stats_aal5  aal5;     /* AAL5 statistics              */
+    struct stats_aux   aux;      /* auxiliary statistics         */
+} stats_t;
+
+
+/* get statistics command opcode */
+
+typedef struct stats_opcode {
+    BITFIELD2(
+        enum opcode opcode :  8,    /* cp opcode */
+        u32         pad    : 24     /* reserved  */
+    )
+} stats_opcode_t;
+
+
+/* get statistics command block */
+
+typedef struct stats_block {
+    struct stats_opcode opcode;         /* get statistics command opcode    */
+    u32                 stats_haddr;    /* host DMA address of stats buffer */
+} stats_block_t;
+
+
+/* expansion PROM data (PCI specific) */
+
+typedef struct prom_data {
+    u32 hw_revision;      /* hardware revision   */
+    u32 serial_number;    /* board serial number */
+    u8  mac_addr[ 8 ];    /* board MAC address   */
+} prom_data_t;
+
+
+/* get expansion PROM data command opcode */
+
+typedef struct prom_opcode {
+    BITFIELD2(
+        enum opcode opcode :  8,    /* cp opcode */
+        u32         pad    : 24     /* reserved  */
+    )
+} prom_opcode_t;
+
+
+/* get expansion PROM data command block */
+
+typedef struct prom_block {
+    struct prom_opcode opcode;        /* get PROM data command opcode    */
+    u32                prom_haddr;    /* host DMA address of PROM buffer */
+} prom_block_t;
+
+
+/* cp command */
+
+typedef union cmd {
+    enum   opcode           opcode;           /* operation code          */
+    struct activate_block   activate_block;   /* activate VC             */
+    struct deactivate_block deactivate_block; /* deactivate VC           */
+    struct stats_block      stats_block;      /* get statistics          */
+    struct prom_block       prom_block;       /* get expansion PROM data */
+    struct oc3_block        oc3_block;        /* get/set OC-3 registers  */
+    u32                     pad[ 4 ];         /* i960 padding            */
+} cmd_t;
+
+
+/* cp resident command queue */
+
+typedef struct cp_cmdq_entry {
+    union cmd cmd;             /* command                               */
+    u32       status_haddr;    /* host DMA address of completion status */
+    u32       pad[ 3 ];        /* i960 padding                          */
+} cp_cmdq_entry_t;
+
+
+/* host resident transmit queue entry */
+
+typedef struct host_txq_entry {
+    struct cp_txq_entry __iomem *cp_entry;    /* addr of cp resident tx queue entry       */
+    enum   status*          status;      /* addr of host resident status             */
+    struct tpd*             tpd;         /* addr of transmit PDU descriptor          */
+    u32                     tpd_dma;     /* DMA address of tpd                       */
+    struct sk_buff*         skb;         /* related skb                              */
+    void*                   data;        /* copy of misaligned data                  */
+    unsigned long           incarn;      /* vc_map incarnation when submitted for tx */
+    struct fore200e_vc_map* vc_map;
+
+} host_txq_entry_t;
+
+
+/* host resident receive queue entry */
+
+typedef struct host_rxq_entry {
+    struct cp_rxq_entry __iomem *cp_entry;    /* addr of cp resident rx queue entry */
+    enum   status*       status;      /* addr of host resident status       */
+    struct rpd*          rpd;         /* addr of receive PDU descriptor     */
+    u32                  rpd_dma;     /* DMA address of rpd                 */
+} host_rxq_entry_t;
+
+
+/* host resident buffer supply queue entry */
+
+typedef struct host_bsq_entry {
+    struct cp_bsq_entry __iomem *cp_entry;         /* addr of cp resident buffer supply queue entry */
+    enum   status*       status;           /* addr of host resident status                  */
+    struct rbd_block*    rbd_block;        /* addr of receive buffer descriptor block       */
+    u32                  rbd_block_dma;    /* DMA address od rdb                            */
+} host_bsq_entry_t;
+
+
+/* host resident command queue entry */
+
+typedef struct host_cmdq_entry {
+    struct cp_cmdq_entry __iomem *cp_entry;    /* addr of cp resident cmd queue entry */
+    enum status *status;	       /* addr of host resident status        */
+} host_cmdq_entry_t;
+
+
+/* chunk of memory */
+
+typedef struct chunk {
+    void* alloc_addr;    /* base address of allocated chunk */
+    void* align_addr;    /* base address of aligned chunk   */
+    dma_addr_t dma_addr; /* DMA address of aligned chunk    */
+    int   direction;     /* direction of DMA mapping        */
+    u32   alloc_size;    /* length of allocated chunk       */
+    u32   align_size;    /* length of aligned chunk         */
+} chunk_t;
+
+#define dma_size align_size             /* DMA useable size */
+
+
+/* host resident receive buffer */
+
+typedef struct buffer {
+    struct buffer*       next;        /* next receive buffer     */
+    enum   buffer_scheme scheme;      /* buffer scheme           */
+    enum   buffer_magn   magn;        /* buffer magnitude        */
+    struct chunk         data;        /* data buffer             */
+#ifdef FORE200E_BSQ_DEBUG
+    unsigned long        index;       /* buffer # in queue       */
+    int                  supplied;    /* 'buffer supplied' flag  */
+#endif
+} buffer_t;
+
+
+#if (BITS_PER_LONG == 32)
+#define FORE200E_BUF2HDL(buffer)    ((u32)(buffer))
+#define FORE200E_HDL2BUF(handle)    ((struct buffer*)(handle))
+#else   /* deal with 64 bit pointers */
+#define FORE200E_BUF2HDL(buffer)    ((u32)((u64)(buffer)))
+#define FORE200E_HDL2BUF(handle)    ((struct buffer*)(((u64)(handle)) | PAGE_OFFSET))
+#endif
+
+
+/* host resident command queue */
+
+typedef struct host_cmdq {
+    struct host_cmdq_entry host_entry[ QUEUE_SIZE_CMD ];    /* host resident cmd queue entries        */
+    int                    head;                            /* head of cmd queue                      */
+    struct chunk           status;                          /* array of completion status      */
+} host_cmdq_t;
+
+
+/* host resident transmit queue */
+
+typedef struct host_txq {
+    struct host_txq_entry host_entry[ QUEUE_SIZE_TX ];    /* host resident tx queue entries         */
+    int                   head;                           /* head of tx queue                       */
+    int                   tail;                           /* tail of tx queue                       */
+    struct chunk          tpd;                            /* array of tpds                          */
+    struct chunk          status;                         /* arry of completion status              */
+    int                   txing;                          /* number of pending PDUs in tx queue     */
+} host_txq_t;
+
+
+/* host resident receive queue */
+
+typedef struct host_rxq {
+    struct host_rxq_entry  host_entry[ QUEUE_SIZE_RX ];    /* host resident rx queue entries         */
+    int                    head;                           /* head of rx queue                       */
+    struct chunk           rpd;                            /* array of rpds                          */
+    struct chunk           status;                         /* array of completion status             */
+} host_rxq_t;
+
+
+/* host resident buffer supply queues */
+
+typedef struct host_bsq {
+    struct host_bsq_entry host_entry[ QUEUE_SIZE_BS ];    /* host resident buffer supply queue entries */
+    int                   head;                           /* head of buffer supply queue               */
+    struct chunk          rbd_block;                      /* array of rbds                             */
+    struct chunk          status;                         /* array of completion status                */
+    struct buffer*        buffer;                         /* array of rx buffers                       */
+    struct buffer*        freebuf;                        /* list of free rx buffers                   */
+    volatile int          freebuf_count;                  /* count of free rx buffers                  */
+} host_bsq_t;
+
+
+/* header of the firmware image */
+
+typedef struct fw_header {
+    u32 magic;           /* magic number                               */
+    u32 version;         /* firmware version id                        */
+    u32 load_offset;     /* fw load offset in board memory             */
+    u32 start_offset;    /* fw execution start address in board memory */
+} fw_header_t;
+
+#define FW_HEADER_MAGIC  0x65726f66    /* 'fore' */
+
+
+/* receive buffer supply queues scheme specification */
+
+typedef struct bs_spec {
+    u32	queue_length;      /* queue capacity                     */
+    u32	buffer_size;	   /* host buffer size			 */
+    u32	pool_size;	   /* number of rbds			 */
+    u32	supply_blksize;    /* num of rbds in I/O block (multiple
+			      of 4 between 4 and 124 inclusive)	 */
+} bs_spec_t;
+
+
+/* initialization command block (one-time command, not in cmd queue) */
+
+typedef struct init_block {
+    enum opcode  opcode;               /* initialize command             */
+    enum status	 status;	       /* related status word            */
+    u32          receive_threshold;    /* not used                       */
+    u32          num_connect;          /* ATM connections                */
+    u32          cmd_queue_len;        /* length of command queue        */
+    u32          tx_queue_len;         /* length of transmit queue       */
+    u32          rx_queue_len;         /* length of receive queue        */
+    u32          rsd_extension;        /* number of extra 32 byte blocks */
+    u32          tsd_extension;        /* number of extra 32 byte blocks */
+    u32          conless_vpvc;         /* not used                       */
+    u32          pad[ 2 ];             /* force quad alignment           */
+    struct bs_spec bs_spec[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ];      /* buffer supply queues spec */
+} init_block_t;
+
+
+typedef enum media_type {
+    MEDIA_TYPE_CAT5_UTP  = 0x06,    /* unshielded twisted pair */
+    MEDIA_TYPE_MM_OC3_ST = 0x16,    /* multimode fiber ST      */
+    MEDIA_TYPE_MM_OC3_SC = 0x26,    /* multimode fiber SC      */
+    MEDIA_TYPE_SM_OC3_ST = 0x36,    /* single-mode fiber ST    */
+    MEDIA_TYPE_SM_OC3_SC = 0x46     /* single-mode fiber SC    */
+} media_type_t;
+
+#define FORE200E_MEDIA_INDEX(media_type)   ((media_type)>>4)
+
+
+/* cp resident queues */
+
+typedef struct cp_queues {
+    u32	              cp_cmdq;         /* command queue                      */
+    u32	              cp_txq;          /* transmit queue                     */
+    u32	              cp_rxq;          /* receive queue                      */
+    u32               cp_bsq[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ];        /* buffer supply queues */
+    u32	              imask;             /* 1 enables cp to host interrupts  */
+    u32	              istat;             /* 1 for interrupt posted           */
+    u32	              heap_base;         /* offset form beginning of ram     */
+    u32	              heap_size;         /* space available for queues       */
+    u32	              hlogger;           /* non zero for host logging        */
+    u32               heartbeat;         /* cp heartbeat                     */
+    u32	              fw_release;        /* firmware version                 */
+    u32	              mon960_release;    /* i960 monitor version             */
+    u32	              tq_plen;           /* transmit throughput measurements */
+    /* make sure the init block remains on a quad word boundary              */
+    struct init_block init;              /* one time cmd, not in cmd queue   */
+    enum   media_type media_type;        /* media type id                    */
+    u32               oc3_revision;      /* OC-3 revision number             */
+} cp_queues_t;
+
+
+/* boot status */
+
+typedef enum boot_status {
+    BSTAT_COLD_START    = (u32) 0xc01dc01d,    /* cold start              */
+    BSTAT_SELFTEST_OK   = (u32) 0x02201958,    /* self-test ok            */
+    BSTAT_SELFTEST_FAIL = (u32) 0xadbadbad,    /* self-test failed        */
+    BSTAT_CP_RUNNING    = (u32) 0xce11feed,    /* cp is running           */
+    BSTAT_MON_TOO_BIG   = (u32) 0x10aded00     /* i960 monitor is too big */
+} boot_status_t;
+
+
+/* software UART */
+
+typedef struct soft_uart {
+    u32 send;    /* write register */
+    u32 recv;    /* read register  */
+} soft_uart_t;
+
+#define FORE200E_CP_MONITOR_UART_FREE     0x00000000
+#define FORE200E_CP_MONITOR_UART_AVAIL    0x01000000
+
+
+/* i960 monitor */
+
+typedef struct cp_monitor {
+    struct soft_uart    soft_uart;      /* software UART           */
+    enum boot_status	bstat;          /* boot status             */
+    u32			app_base;       /* application base offset */
+    u32			mon_version;    /* i960 monitor version    */
+} cp_monitor_t;
+
+
+/* device state */
+
+typedef enum fore200e_state {
+    FORE200E_STATE_BLANK,         /* initial state                     */
+    FORE200E_STATE_REGISTER,      /* device registered                 */
+    FORE200E_STATE_CONFIGURE,     /* bus interface configured          */
+    FORE200E_STATE_MAP,           /* board space mapped in host memory */
+    FORE200E_STATE_RESET,         /* board resetted                    */
+    FORE200E_STATE_LOAD_FW,       /* firmware loaded                   */
+    FORE200E_STATE_START_FW,      /* firmware started                  */
+    FORE200E_STATE_INITIALIZE,    /* initialize command successful     */
+    FORE200E_STATE_INIT_CMDQ,     /* command queue initialized         */
+    FORE200E_STATE_INIT_TXQ,      /* transmit queue initialized        */
+    FORE200E_STATE_INIT_RXQ,      /* receive queue initialized         */
+    FORE200E_STATE_INIT_BSQ,      /* buffer supply queue initialized   */
+    FORE200E_STATE_ALLOC_BUF,     /* receive buffers allocated         */
+    FORE200E_STATE_IRQ,           /* host interrupt requested          */
+    FORE200E_STATE_COMPLETE       /* initialization completed          */
+} fore200e_state;
+
+
+/* PCA-200E registers */
+
+typedef struct fore200e_pca_regs {
+    volatile u32 __iomem * hcr;    /* address of host control register        */
+    volatile u32 __iomem * imr;    /* address of host interrupt mask register */
+    volatile u32 __iomem * psr;    /* address of PCI specific register        */
+} fore200e_pca_regs_t;
+
+
+/* SBA-200E registers */
+
+typedef struct fore200e_sba_regs {
+    volatile u32 __iomem *hcr;    /* address of host control register              */
+    volatile u32 __iomem *bsr;    /* address of burst transfer size register       */
+    volatile u32 __iomem *isr;    /* address of interrupt level selection register */
+} fore200e_sba_regs_t;
+
+
+/* model-specific registers */
+
+typedef union fore200e_regs {
+    struct fore200e_pca_regs pca;    /* PCA-200E registers */
+    struct fore200e_sba_regs sba;    /* SBA-200E registers */
+} fore200e_regs;
+
+
+struct fore200e;
+
+/* bus-dependent data */
+
+typedef struct fore200e_bus {
+    char*                model_name;          /* board model name                       */
+    char*                proc_name;           /* board name under /proc/atm             */
+    int                  descr_alignment;     /* tpd/rpd/rbd DMA alignment requirement  */
+    int                  buffer_alignment;    /* rx buffers DMA alignment requirement   */
+    int                  status_alignment;    /* status words DMA alignment requirement */
+    const unsigned char* fw_data;             /* address of firmware data start         */
+    const unsigned int*  fw_size;             /* address of firmware data size          */
+    u32                  (*read)(volatile u32 __iomem *);
+    void                 (*write)(u32, volatile u32 __iomem *);
+    u32                  (*dma_map)(struct fore200e*, void*, int, int);
+    void                 (*dma_unmap)(struct fore200e*, u32, int, int);
+    void                 (*dma_sync_for_cpu)(struct fore200e*, u32, int, int);
+    void                 (*dma_sync_for_device)(struct fore200e*, u32, int, int);
+    int                  (*dma_chunk_alloc)(struct fore200e*, struct chunk*, int, int, int);
+    void                 (*dma_chunk_free)(struct fore200e*, struct chunk*);
+    struct fore200e*     (*detect)(const struct fore200e_bus*, int);
+    int                  (*configure)(struct fore200e*); 
+    int                  (*map)(struct fore200e*); 
+    void                 (*reset)(struct fore200e*);
+    int                  (*prom_read)(struct fore200e*, struct prom_data*);
+    void                 (*unmap)(struct fore200e*);
+    void                 (*irq_enable)(struct fore200e*);
+    int                  (*irq_check)(struct fore200e*);
+    void                 (*irq_ack)(struct fore200e*);
+    int                  (*proc_read)(struct fore200e*, char*);
+} fore200e_bus_t;
+
+/* vc mapping */
+
+typedef struct fore200e_vc_map {
+    struct atm_vcc* vcc;       /* vcc entry              */
+    unsigned long   incarn;    /* vcc incarnation number */
+} fore200e_vc_map_t;
+
+#define FORE200E_VC_MAP(fore200e, vpi, vci)  \
+        (& (fore200e)->vc_map[ ((vpi) << FORE200E_VCI_BITS) | (vci) ])
+
+
+/* per-device data */
+
+typedef struct fore200e {
+    struct       list_head     entry;                  /* next device                        */
+    const struct fore200e_bus* bus;                    /* bus-dependent code and data        */
+    union        fore200e_regs regs;                   /* bus-dependent registers            */
+    struct       atm_dev*      atm_dev;                /* ATM device                         */
+
+    enum fore200e_state        state;                  /* device state                       */
+
+    char                       name[16];               /* device name                        */
+    void*                      bus_dev;                /* bus-specific kernel data           */
+    int                        irq;                    /* irq number                         */
+    unsigned long              phys_base;              /* physical base address              */
+    void __iomem *             virt_base;              /* virtual base address               */
+    
+    unsigned char              esi[ ESI_LEN ];         /* end system identifier              */
+
+    struct cp_monitor __iomem *         cp_monitor;    /* i960 monitor address               */
+    struct cp_queues __iomem *          cp_queues;              /* cp resident queues                 */
+    struct host_cmdq           host_cmdq;              /* host resident cmd queue            */
+    struct host_txq            host_txq;               /* host resident tx queue             */
+    struct host_rxq            host_rxq;               /* host resident rx queue             */
+                                                       /* host resident buffer supply queues */
+    struct host_bsq            host_bsq[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ];       
+
+    u32                        available_cell_rate;    /* remaining pseudo-CBR bw on link    */
+
+    int                        loop_mode;              /* S/UNI loopback mode                */
+
+    struct stats*              stats;                  /* last snapshot of the stats         */
+    
+    struct semaphore           rate_sf;                /* protects rate reservation ops      */
+    spinlock_t                 q_lock;                 /* protects queue ops                 */
+#ifdef FORE200E_USE_TASKLET
+    struct tasklet_struct      tx_tasklet;             /* performs tx interrupt work         */
+    struct tasklet_struct      rx_tasklet;             /* performs rx interrupt work         */
+#endif
+    unsigned long              tx_sat;                 /* tx queue saturation count          */
+
+    unsigned long              incarn_count;
+    struct fore200e_vc_map     vc_map[ NBR_CONNECT ];  /* vc mapping                         */
+} fore200e_t;
+
+
+/* per-vcc data */
+
+typedef struct fore200e_vcc {
+    enum buffer_scheme     scheme;             /* rx buffer scheme                   */
+    struct tpd_rate        rate;               /* tx rate control data               */
+    int                    rx_min_pdu;         /* size of smallest PDU received      */
+    int                    rx_max_pdu;         /* size of largest PDU received       */
+    int                    tx_min_pdu;         /* size of smallest PDU transmitted   */
+    int                    tx_max_pdu;         /* size of largest PDU transmitted    */
+    unsigned long          tx_pdu;             /* nbr of tx pdus                     */
+    unsigned long          rx_pdu;             /* nbr of rx pdus                     */
+} fore200e_vcc_t;
+
+
+
+/* 200E-series common memory layout */
+
+#define FORE200E_CP_MONITOR_OFFSET	0x00000400    /* i960 monitor interface */
+#define FORE200E_CP_QUEUES_OFFSET	0x00004d40    /* cp resident queues     */
+
+
+/* PCA-200E memory layout */
+
+#define PCA200E_IOSPACE_LENGTH	        0x00200000
+
+#define PCA200E_HCR_OFFSET		0x00100000    /* board control register */
+#define PCA200E_IMR_OFFSET		0x00100004    /* host IRQ mask register */
+#define PCA200E_PSR_OFFSET		0x00100008    /* PCI specific register  */
+
+
+/* PCA-200E host control register */
+
+#define PCA200E_HCR_RESET     (1<<0)    /* read / write */
+#define PCA200E_HCR_HOLD_LOCK (1<<1)    /* read / write */
+#define PCA200E_HCR_I960FAIL  (1<<2)    /* read         */
+#define PCA200E_HCR_INTRB     (1<<2)    /* write        */
+#define PCA200E_HCR_HOLD_ACK  (1<<3)    /* read         */
+#define PCA200E_HCR_INTRA     (1<<3)    /* write        */
+#define PCA200E_HCR_OUTFULL   (1<<4)    /* read         */
+#define PCA200E_HCR_CLRINTR   (1<<4)    /* write        */
+#define PCA200E_HCR_ESPHOLD   (1<<5)    /* read         */
+#define PCA200E_HCR_INFULL    (1<<6)    /* read         */
+#define PCA200E_HCR_TESTMODE  (1<<7)    /* read         */
+
+
+/* PCA-200E PCI bus interface regs (offsets in PCI config space) */
+
+#define PCA200E_PCI_LATENCY      0x40    /* maximum slave latenty            */
+#define PCA200E_PCI_MASTER_CTRL  0x41    /* master control                   */
+#define PCA200E_PCI_THRESHOLD    0x42    /* burst / continous req threshold  */
+
+/* PBI master control register */
+
+#define PCA200E_CTRL_DIS_CACHE_RD      (1<<0)    /* disable cache-line reads                         */
+#define PCA200E_CTRL_DIS_WRT_INVAL     (1<<1)    /* disable writes and invalidates                   */
+#define PCA200E_CTRL_2_CACHE_WRT_INVAL (1<<2)    /* require 2 cache-lines for writes and invalidates */
+#define PCA200E_CTRL_IGN_LAT_TIMER     (1<<3)    /* ignore the latency timer                         */
+#define PCA200E_CTRL_ENA_CONT_REQ_MODE (1<<4)    /* enable continuous request mode                   */
+#define PCA200E_CTRL_LARGE_PCI_BURSTS  (1<<5)    /* force large PCI bus bursts                       */
+#define PCA200E_CTRL_CONVERT_ENDIAN    (1<<6)    /* convert endianess of slave RAM accesses          */
+
+
+
+#define SBA200E_PROM_NAME  "FORE,sba-200e"    /* device name in openprom tree */
+
+
+/* size of SBA-200E registers */
+
+#define SBA200E_HCR_LENGTH        4
+#define SBA200E_BSR_LENGTH        4
+#define SBA200E_ISR_LENGTH        4
+#define SBA200E_RAM_LENGTH  0x40000
+
+
+/* SBA-200E SBUS burst transfer size register */
+
+#define SBA200E_BSR_BURST4   0x04
+#define SBA200E_BSR_BURST8   0x08
+#define SBA200E_BSR_BURST16  0x10
+
+
+/* SBA-200E host control register */
+
+#define SBA200E_HCR_RESET        (1<<0)    /* read / write (sticky) */
+#define SBA200E_HCR_HOLD_LOCK    (1<<1)    /* read / write (sticky) */
+#define SBA200E_HCR_I960FAIL     (1<<2)    /* read                  */
+#define SBA200E_HCR_I960SETINTR  (1<<2)    /* write                 */
+#define SBA200E_HCR_OUTFULL      (1<<3)    /* read                  */
+#define SBA200E_HCR_INTR_CLR     (1<<3)    /* write                 */
+#define SBA200E_HCR_INTR_ENA     (1<<4)    /* read / write (sticky) */
+#define SBA200E_HCR_ESPHOLD      (1<<5)    /* read                  */
+#define SBA200E_HCR_INFULL       (1<<6)    /* read                  */
+#define SBA200E_HCR_TESTMODE     (1<<7)    /* read                  */
+#define SBA200E_HCR_INTR_REQ     (1<<8)    /* read                  */
+
+#define SBA200E_HCR_STICKY       (SBA200E_HCR_RESET | SBA200E_HCR_HOLD_LOCK | SBA200E_HCR_INTR_ENA)
+
+
+#endif /* __KERNEL__ */
+#endif /* _FORE200E_H */
diff --git a/drivers/atm/fore200e_firmware_copyright b/drivers/atm/fore200e_firmware_copyright
new file mode 100644
index 0000000..d58e649
--- /dev/null
+++ b/drivers/atm/fore200e_firmware_copyright
@@ -0,0 +1,31 @@
+
+These microcode data are placed under the terms of the GNU General Public License. 
+
+We would prefer you not to distribute modified versions of it and not to ask
+for assembly or other microcode source.
+
+Copyright (c) 1995-2000 FORE Systems, Inc., as an unpublished work.  This 
+notice does not imply unrestricted or public access to these materials which
+are a trade secret of FORE Systems, Inc. or its subsidiaries or affiliates 
+(together referred to as "FORE"), and which may not be reproduced, used, sold 
+or transferred to any third party without FORE's prior written consent.  All
+rights reserved.
+
+U.S. Government Restricted Rights.  If you are licensing the Software on 
+behalf of the U.S. Government ("Government"), the following provisions apply
+to you.  If the software is supplied to the Department of Defense ("DoD"), it 
+is classified as "Commercial Computer Software" under paragraph 252.227-7014
+of the DoD Supplement to the Federal Acquisition Regulations ("DFARS") (or any 
+successor regulations) and the Government is acquiring only the license
+rights granted herein (the license rights customarily provided to non-Government 
+users).  If the Software is supplied to any unit or agency of the Government
+other than the DoD, it is classified as "Restricted Computer Software" and
+the Government's rights in the Software are defined in paragraph 52.227-19 of
+the Federal Acquisition Regulations ("FAR") (or any successor regulations) or,
+in the cases of NASA, in paragraph 18.52.227-86 of the NASA Supplement to the FAR 
+(or any successor regulations).
+
+FORE Systems is a registered trademark, and ForeRunner, ForeRunnerLE, and 
+ForeThought are trademarks of FORE Systems, Inc.  All other brands or product 
+names are trademarks or registered trademarks of their respective holders.
+
diff --git a/drivers/atm/fore200e_mkfirm.c b/drivers/atm/fore200e_mkfirm.c
new file mode 100644
index 0000000..2ebe1a1
--- /dev/null
+++ b/drivers/atm/fore200e_mkfirm.c
@@ -0,0 +1,156 @@
+/*
+  $Id: fore200e_mkfirm.c,v 1.1 2000/02/21 16:04:32 davem Exp $
+
+  mkfirm.c: generates a C readable file from a binary firmware image
+
+  Christophe Lizzi (lizzi@{csti.fr, cnam.fr}), June 1999.
+  
+  This software may be used and distributed according to the terms
+  of the GNU General Public License, incorporated herein by reference.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+
+char* default_basename = "pca200e"; /* was initially written for the PCA-200E firmware */
+char* default_infname  = "<stdin>";
+char* default_outfname = "<stdout>";
+
+char* progname;
+int   verbose  = 0;
+int   inkernel = 0;
+
+
+void usage(void)
+{
+    fprintf(stderr,
+	    "%s: [-v] [-k] [-b basename ] [-i firmware.bin] [-o firmware.c]\n",
+	    progname);
+    exit(-1);
+}
+
+
+int main(int argc, char** argv)
+{
+    time_t   now;
+    char*    infname  = NULL;
+    char*    outfname = NULL;
+    char*    basename = NULL;
+    FILE*    infile;
+    FILE*    outfile;
+    unsigned firmsize;
+    int      c;
+
+    progname = *(argv++);
+    
+    while (argc > 1) {
+        if ((*argv)[0] == '-') {
+            switch ((*argv)[1]) {
+	    case 'i':
+		if (argc-- < 3)
+		    usage();
+		infname = *(++argv);
+		break;
+	    case 'o':
+		if (argc-- < 3)
+		    usage();
+		outfname = *(++argv);
+		break;
+	    case 'b':
+		if (argc-- < 3)
+		    usage();
+		basename = *(++argv);
+		break;
+	    case 'v':
+		verbose = 1;
+		break;
+	    case 'k':
+		inkernel = 1;
+		break;
+	    default:
+		usage();
+            }
+	}
+	else {
+	    usage();
+	}
+	argc--;
+        argv++;
+    }
+    
+    if (infname != NULL) {
+	infile = fopen(infname, "r");
+	if (infile == NULL) {
+	    fprintf(stderr, "%s: can't open %s for reading\n",
+		    progname, infname);
+	    exit(-2);
+	}
+    }
+    else {
+	infile = stdin;
+	infname = default_infname;
+    }
+
+    if (outfname) {
+	outfile = fopen(outfname, "w");
+	if (outfile == NULL) {
+	    fprintf(stderr, "%s: can't open %s for writing\n",
+		    progname, outfname);
+	    exit(-3);
+	}
+    }
+    else {
+	outfile = stdout;
+	outfname = default_outfname;
+    }
+
+    if (basename == NULL)
+	basename = default_basename;
+
+    if (verbose) {
+	fprintf(stderr, "%s: input file = %s\n", progname, infname );
+	fprintf(stderr, "%s: output file = %s\n", progname, outfname );
+	fprintf(stderr, "%s: firmware basename = %s\n", progname, basename );
+    }
+
+    time(&now);
+    fprintf(outfile, "/*\n  generated by %s from %s on %s"
+	    "  DO NOT EDIT!\n*/\n\n",
+	    progname, infname, ctime(&now));
+
+    if (inkernel)
+	fprintf(outfile, "#include <linux/init.h>\n\n" );
+    
+    /* XXX force 32 bit alignment? */
+    fprintf(outfile, "const unsigned char%s %s_data[] = {\n", 
+	    inkernel ? " __initdata" : "", basename );
+    
+    c = getc(infile);
+    fprintf(outfile,"\t0x%02x", c);
+    firmsize = 1;
+    
+    while ((c = getc(infile)) >= 0) {
+	
+	if (firmsize++ % 8)
+	    fprintf(outfile,", 0x%02x", c);
+	else
+	    fprintf(outfile,",\n\t0x%02x", c);
+    }
+
+    fprintf(outfile, "\n};\n\n");
+
+    fprintf(outfile, "const unsigned int%s %s_size = %u;\n",
+	    inkernel ? " __initdata" : "", basename, firmsize );
+
+    if (infile != stdin)
+	fclose(infile);
+    if (outfile != stdout)
+	fclose(outfile);
+
+    if(verbose)
+	fprintf(stderr, "%s: firmware size = %u\n", progname, firmsize);
+
+    exit(0);
+}
diff --git a/drivers/atm/he.c b/drivers/atm/he.c
new file mode 100644
index 0000000..c2c31a5
--- /dev/null
+++ b/drivers/atm/he.c
@@ -0,0 +1,3091 @@
+/* $Id: he.c,v 1.18 2003/05/06 22:57:15 chas Exp $ */
+
+/*
+
+  he.c
+
+  ForeRunnerHE ATM Adapter driver for ATM on Linux
+  Copyright (C) 1999-2001  Naval Research Laboratory
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+/*
+
+  he.c
+
+  ForeRunnerHE ATM Adapter driver for ATM on Linux
+  Copyright (C) 1999-2001  Naval Research Laboratory
+
+  Permission to use, copy, modify and distribute this software and its
+  documentation is hereby granted, provided that both the copyright
+  notice and this permission notice appear in all copies of the software,
+  derivative works or modified versions, and any portions thereof, and
+  that both notices appear in supporting documentation.
+
+  NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
+  DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
+  RESULTING FROM THE USE OF THIS SOFTWARE.
+
+  This driver was written using the "Programmer's Reference Manual for
+  ForeRunnerHE(tm)", MANU0361-01 - Rev. A, 08/21/98.
+
+  AUTHORS:
+	chas williams <chas@cmf.nrl.navy.mil>
+	eric kinzie <ekinzie@cmf.nrl.navy.mil>
+
+  NOTES:
+	4096 supported 'connections'
+	group 0 is used for all traffic
+	interrupt queue 0 is used for all interrupts
+	aal0 support (based on work from ulrich.u.muller@nokia.com)
+
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+
+#include <linux/atmdev.h>
+#include <linux/atm.h>
+#include <linux/sonet.h>
+
+#define USE_TASKLET
+#undef USE_SCATTERGATHER
+#undef USE_CHECKSUM_HW			/* still confused about this */
+#define USE_RBPS
+#undef USE_RBPS_POOL			/* if memory is tight try this */
+#undef USE_RBPL_POOL			/* if memory is tight try this */
+#define USE_TPD_POOL
+/* #undef CONFIG_ATM_HE_USE_SUNI */
+/* #undef HE_DEBUG */
+
+#include "he.h"
+#include "suni.h"
+#include <linux/atm_he.h>
+
+#define hprintk(fmt,args...)	printk(KERN_ERR DEV_LABEL "%d: " fmt, he_dev->number , ##args)
+
+#ifdef HE_DEBUG
+#define HPRINTK(fmt,args...)	printk(KERN_DEBUG DEV_LABEL "%d: " fmt, he_dev->number , ##args)
+#else /* !HE_DEBUG */
+#define HPRINTK(fmt,args...)	do { } while (0)
+#endif /* HE_DEBUG */
+
+/* version definition */
+
+static char *version = "$Id: he.c,v 1.18 2003/05/06 22:57:15 chas Exp $";
+
+/* declarations */
+
+static int he_open(struct atm_vcc *vcc);
+static void he_close(struct atm_vcc *vcc);
+static int he_send(struct atm_vcc *vcc, struct sk_buff *skb);
+static int he_ioctl(struct atm_dev *dev, unsigned int cmd, void __user *arg);
+static irqreturn_t he_irq_handler(int irq, void *dev_id, struct pt_regs *regs);
+static void he_tasklet(unsigned long data);
+static int he_proc_read(struct atm_dev *dev,loff_t *pos,char *page);
+static int he_start(struct atm_dev *dev);
+static void he_stop(struct he_dev *dev);
+static void he_phy_put(struct atm_dev *, unsigned char, unsigned long);
+static unsigned char he_phy_get(struct atm_dev *, unsigned long);
+
+static u8 read_prom_byte(struct he_dev *he_dev, int addr);
+
+/* globals */
+
+static struct he_dev *he_devs;
+static int disable64;
+static short nvpibits = -1;
+static short nvcibits = -1;
+static short rx_skb_reserve = 16;
+static int irq_coalesce = 1;
+static int sdh = 0;
+
+/* Read from EEPROM = 0000 0011b */
+static unsigned int readtab[] = {
+	CS_HIGH | CLK_HIGH,
+	CS_LOW | CLK_LOW,
+	CLK_HIGH,               /* 0 */
+	CLK_LOW,
+	CLK_HIGH,               /* 0 */
+	CLK_LOW,
+	CLK_HIGH,               /* 0 */
+	CLK_LOW,
+	CLK_HIGH,               /* 0 */
+	CLK_LOW,
+	CLK_HIGH,               /* 0 */
+	CLK_LOW,
+	CLK_HIGH,               /* 0 */
+	CLK_LOW | SI_HIGH,
+	CLK_HIGH | SI_HIGH,     /* 1 */
+	CLK_LOW | SI_HIGH,
+	CLK_HIGH | SI_HIGH      /* 1 */
+};     
+ 
+/* Clock to read from/write to the EEPROM */
+static unsigned int clocktab[] = {
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW,
+	CLK_HIGH,
+	CLK_LOW
+};     
+
+static struct atmdev_ops he_ops =
+{
+	.open =		he_open,
+	.close =	he_close,	
+	.ioctl =	he_ioctl,	
+	.send =		he_send,
+	.phy_put =	he_phy_put,
+	.phy_get =	he_phy_get,
+	.proc_read =	he_proc_read,
+	.owner =	THIS_MODULE
+};
+
+#define he_writel(dev, val, reg)	do { writel(val, (dev)->membase + (reg)); wmb(); } while (0)
+#define he_readl(dev, reg)		readl((dev)->membase + (reg))
+
+/* section 2.12 connection memory access */
+
+static __inline__ void
+he_writel_internal(struct he_dev *he_dev, unsigned val, unsigned addr,
+								unsigned flags)
+{
+	he_writel(he_dev, val, CON_DAT);
+	(void) he_readl(he_dev, CON_DAT);		/* flush posted writes */
+	he_writel(he_dev, flags | CON_CTL_WRITE | CON_CTL_ADDR(addr), CON_CTL);
+	while (he_readl(he_dev, CON_CTL) & CON_CTL_BUSY);
+}
+
+#define he_writel_rcm(dev, val, reg) 				\
+			he_writel_internal(dev, val, reg, CON_CTL_RCM)
+
+#define he_writel_tcm(dev, val, reg) 				\
+			he_writel_internal(dev, val, reg, CON_CTL_TCM)
+
+#define he_writel_mbox(dev, val, reg) 				\
+			he_writel_internal(dev, val, reg, CON_CTL_MBOX)
+
+static unsigned
+he_readl_internal(struct he_dev *he_dev, unsigned addr, unsigned flags)
+{
+	he_writel(he_dev, flags | CON_CTL_READ | CON_CTL_ADDR(addr), CON_CTL);
+	while (he_readl(he_dev, CON_CTL) & CON_CTL_BUSY);
+	return he_readl(he_dev, CON_DAT);
+}
+
+#define he_readl_rcm(dev, reg) \
+			he_readl_internal(dev, reg, CON_CTL_RCM)
+
+#define he_readl_tcm(dev, reg) \
+			he_readl_internal(dev, reg, CON_CTL_TCM)
+
+#define he_readl_mbox(dev, reg) \
+			he_readl_internal(dev, reg, CON_CTL_MBOX)
+
+
+/* figure 2.2 connection id */
+
+#define he_mkcid(dev, vpi, vci)		(((vpi << (dev)->vcibits) | vci) & 0x1fff)
+
+/* 2.5.1 per connection transmit state registers */
+
+#define he_writel_tsr0(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 0)
+#define he_readl_tsr0(dev, cid) \
+		he_readl_tcm(dev, CONFIG_TSRA | (cid << 3) | 0)
+
+#define he_writel_tsr1(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 1)
+
+#define he_writel_tsr2(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 2)
+
+#define he_writel_tsr3(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 3)
+
+#define he_writel_tsr4(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 4)
+
+	/* from page 2-20
+	 *
+	 * NOTE While the transmit connection is active, bits 23 through 0
+	 *      of this register must not be written by the host.  Byte
+	 *      enables should be used during normal operation when writing
+	 *      the most significant byte.
+	 */
+
+#define he_writel_tsr4_upper(dev, val, cid) \
+		he_writel_internal(dev, val, CONFIG_TSRA | (cid << 3) | 4, \
+							CON_CTL_TCM \
+							| CON_BYTE_DISABLE_2 \
+							| CON_BYTE_DISABLE_1 \
+							| CON_BYTE_DISABLE_0)
+
+#define he_readl_tsr4(dev, cid) \
+		he_readl_tcm(dev, CONFIG_TSRA | (cid << 3) | 4)
+
+#define he_writel_tsr5(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 5)
+
+#define he_writel_tsr6(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 6)
+
+#define he_writel_tsr7(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 7)
+
+
+#define he_writel_tsr8(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 0)
+
+#define he_writel_tsr9(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 1)
+
+#define he_writel_tsr10(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 2)
+
+#define he_writel_tsr11(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 3)
+
+
+#define he_writel_tsr12(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRC | (cid << 1) | 0)
+
+#define he_writel_tsr13(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRC | (cid << 1) | 1)
+
+
+#define he_writel_tsr14(dev, val, cid) \
+		he_writel_tcm(dev, val, CONFIG_TSRD | cid)
+
+#define he_writel_tsr14_upper(dev, val, cid) \
+		he_writel_internal(dev, val, CONFIG_TSRD | cid, \
+							CON_CTL_TCM \
+							| CON_BYTE_DISABLE_2 \
+							| CON_BYTE_DISABLE_1 \
+							| CON_BYTE_DISABLE_0)
+
+/* 2.7.1 per connection receive state registers */
+
+#define he_writel_rsr0(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 0)
+#define he_readl_rsr0(dev, cid) \
+		he_readl_rcm(dev, 0x00000 | (cid << 3) | 0)
+
+#define he_writel_rsr1(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 1)
+
+#define he_writel_rsr2(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 2)
+
+#define he_writel_rsr3(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 3)
+
+#define he_writel_rsr4(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 4)
+
+#define he_writel_rsr5(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 5)
+
+#define he_writel_rsr6(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 6)
+
+#define he_writel_rsr7(dev, val, cid) \
+		he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 7)
+
+static __inline__ struct atm_vcc*
+__find_vcc(struct he_dev *he_dev, unsigned cid)
+{
+	struct hlist_head *head;
+	struct atm_vcc *vcc;
+	struct hlist_node *node;
+	struct sock *s;
+	short vpi;
+	int vci;
+
+	vpi = cid >> he_dev->vcibits;
+	vci = cid & ((1 << he_dev->vcibits) - 1);
+	head = &vcc_hash[vci & (VCC_HTABLE_SIZE -1)];
+
+	sk_for_each(s, node, head) {
+		vcc = atm_sk(s);
+		if (vcc->dev == he_dev->atm_dev &&
+		    vcc->vci == vci && vcc->vpi == vpi &&
+		    vcc->qos.rxtp.traffic_class != ATM_NONE) {
+				return vcc;
+		}
+	}
+	return NULL;
+}
+
+static int __devinit
+he_init_one(struct pci_dev *pci_dev, const struct pci_device_id *pci_ent)
+{
+	struct atm_dev *atm_dev = NULL;
+	struct he_dev *he_dev = NULL;
+	int err = 0;
+
+	printk(KERN_INFO "he: %s\n", version);
+
+	if (pci_enable_device(pci_dev))
+		return -EIO;
+	if (pci_set_dma_mask(pci_dev, HE_DMA_MASK) != 0) {
+		printk(KERN_WARNING "he: no suitable dma available\n");
+		err = -EIO;
+		goto init_one_failure;
+	}
+
+	atm_dev = atm_dev_register(DEV_LABEL, &he_ops, -1, NULL);
+	if (!atm_dev) {
+		err = -ENODEV;
+		goto init_one_failure;
+	}
+	pci_set_drvdata(pci_dev, atm_dev);
+
+	he_dev = (struct he_dev *) kmalloc(sizeof(struct he_dev),
+							GFP_KERNEL);
+	if (!he_dev) {
+		err = -ENOMEM;
+		goto init_one_failure;
+	}
+	memset(he_dev, 0, sizeof(struct he_dev));
+
+	he_dev->pci_dev = pci_dev;
+	he_dev->atm_dev = atm_dev;
+	he_dev->atm_dev->dev_data = he_dev;
+	atm_dev->dev_data = he_dev;
+	he_dev->number = atm_dev->number;
+	if (he_start(atm_dev)) {
+		he_stop(he_dev);
+		err = -ENODEV;
+		goto init_one_failure;
+	}
+	he_dev->next = NULL;
+	if (he_devs)
+		he_dev->next = he_devs;
+	he_devs = he_dev;
+	return 0;
+
+init_one_failure:
+	if (atm_dev)
+		atm_dev_deregister(atm_dev);
+	if (he_dev)
+		kfree(he_dev);
+	pci_disable_device(pci_dev);
+	return err;
+}
+
+static void __devexit
+he_remove_one (struct pci_dev *pci_dev)
+{
+	struct atm_dev *atm_dev;
+	struct he_dev *he_dev;
+
+	atm_dev = pci_get_drvdata(pci_dev);
+	he_dev = HE_DEV(atm_dev);
+
+	/* need to remove from he_devs */
+
+	he_stop(he_dev);
+	atm_dev_deregister(atm_dev);
+	kfree(he_dev);
+
+	pci_set_drvdata(pci_dev, NULL);
+	pci_disable_device(pci_dev);
+}
+
+
+static unsigned
+rate_to_atmf(unsigned rate)		/* cps to atm forum format */
+{
+#define NONZERO (1 << 14)
+
+	unsigned exp = 0;
+
+	if (rate == 0)
+		return 0;
+
+	rate <<= 9;
+	while (rate > 0x3ff) {
+		++exp;
+		rate >>= 1;
+	}
+
+	return (NONZERO | (exp << 9) | (rate & 0x1ff));
+}
+
+static void __init
+he_init_rx_lbfp0(struct he_dev *he_dev)
+{
+	unsigned i, lbm_offset, lbufd_index, lbuf_addr, lbuf_count;
+	unsigned lbufs_per_row = he_dev->cells_per_row / he_dev->cells_per_lbuf;
+	unsigned lbuf_bufsize = he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD;
+	unsigned row_offset = he_dev->r0_startrow * he_dev->bytes_per_row;
+	
+	lbufd_index = 0;
+	lbm_offset = he_readl(he_dev, RCMLBM_BA);
+
+	he_writel(he_dev, lbufd_index, RLBF0_H);
+
+	for (i = 0, lbuf_count = 0; i < he_dev->r0_numbuffs; ++i) {
+		lbufd_index += 2;
+		lbuf_addr = (row_offset + (lbuf_count * lbuf_bufsize)) / 32;
+
+		he_writel_rcm(he_dev, lbuf_addr, lbm_offset);
+		he_writel_rcm(he_dev, lbufd_index, lbm_offset + 1);
+
+		if (++lbuf_count == lbufs_per_row) {
+			lbuf_count = 0;
+			row_offset += he_dev->bytes_per_row;
+		}
+		lbm_offset += 4;
+	}
+		
+	he_writel(he_dev, lbufd_index - 2, RLBF0_T);
+	he_writel(he_dev, he_dev->r0_numbuffs, RLBF0_C);
+}
+
+static void __init
+he_init_rx_lbfp1(struct he_dev *he_dev)
+{
+	unsigned i, lbm_offset, lbufd_index, lbuf_addr, lbuf_count;
+	unsigned lbufs_per_row = he_dev->cells_per_row / he_dev->cells_per_lbuf;
+	unsigned lbuf_bufsize = he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD;
+	unsigned row_offset = he_dev->r1_startrow * he_dev->bytes_per_row;
+	
+	lbufd_index = 1;
+	lbm_offset = he_readl(he_dev, RCMLBM_BA) + (2 * lbufd_index);
+
+	he_writel(he_dev, lbufd_index, RLBF1_H);
+
+	for (i = 0, lbuf_count = 0; i < he_dev->r1_numbuffs; ++i) {
+		lbufd_index += 2;
+		lbuf_addr = (row_offset + (lbuf_count * lbuf_bufsize)) / 32;
+
+		he_writel_rcm(he_dev, lbuf_addr, lbm_offset);
+		he_writel_rcm(he_dev, lbufd_index, lbm_offset + 1);
+
+		if (++lbuf_count == lbufs_per_row) {
+			lbuf_count = 0;
+			row_offset += he_dev->bytes_per_row;
+		}
+		lbm_offset += 4;
+	}
+		
+	he_writel(he_dev, lbufd_index - 2, RLBF1_T);
+	he_writel(he_dev, he_dev->r1_numbuffs, RLBF1_C);
+}
+
+static void __init
+he_init_tx_lbfp(struct he_dev *he_dev)
+{
+	unsigned i, lbm_offset, lbufd_index, lbuf_addr, lbuf_count;
+	unsigned lbufs_per_row = he_dev->cells_per_row / he_dev->cells_per_lbuf;
+	unsigned lbuf_bufsize = he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD;
+	unsigned row_offset = he_dev->tx_startrow * he_dev->bytes_per_row;
+	
+	lbufd_index = he_dev->r0_numbuffs + he_dev->r1_numbuffs;
+	lbm_offset = he_readl(he_dev, RCMLBM_BA) + (2 * lbufd_index);
+
+	he_writel(he_dev, lbufd_index, TLBF_H);
+
+	for (i = 0, lbuf_count = 0; i < he_dev->tx_numbuffs; ++i) {
+		lbufd_index += 1;
+		lbuf_addr = (row_offset + (lbuf_count * lbuf_bufsize)) / 32;
+
+		he_writel_rcm(he_dev, lbuf_addr, lbm_offset);
+		he_writel_rcm(he_dev, lbufd_index, lbm_offset + 1);
+
+		if (++lbuf_count == lbufs_per_row) {
+			lbuf_count = 0;
+			row_offset += he_dev->bytes_per_row;
+		}
+		lbm_offset += 2;
+	}
+		
+	he_writel(he_dev, lbufd_index - 1, TLBF_T);
+}
+
+static int __init
+he_init_tpdrq(struct he_dev *he_dev)
+{
+	he_dev->tpdrq_base = pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_TPDRQ_SIZE * sizeof(struct he_tpdrq), &he_dev->tpdrq_phys);
+	if (he_dev->tpdrq_base == NULL) {
+		hprintk("failed to alloc tpdrq\n");
+		return -ENOMEM;
+	}
+	memset(he_dev->tpdrq_base, 0,
+				CONFIG_TPDRQ_SIZE * sizeof(struct he_tpdrq));
+
+	he_dev->tpdrq_tail = he_dev->tpdrq_base;
+	he_dev->tpdrq_head = he_dev->tpdrq_base;
+
+	he_writel(he_dev, he_dev->tpdrq_phys, TPDRQ_B_H);
+	he_writel(he_dev, 0, TPDRQ_T);	
+	he_writel(he_dev, CONFIG_TPDRQ_SIZE - 1, TPDRQ_S);
+
+	return 0;
+}
+
+static void __init
+he_init_cs_block(struct he_dev *he_dev)
+{
+	unsigned clock, rate, delta;
+	int reg;
+
+	/* 5.1.7 cs block initialization */
+
+	for (reg = 0; reg < 0x20; ++reg)
+		he_writel_mbox(he_dev, 0x0, CS_STTIM0 + reg);
+
+	/* rate grid timer reload values */
+
+	clock = he_is622(he_dev) ? 66667000 : 50000000;
+	rate = he_dev->atm_dev->link_rate;
+	delta = rate / 16 / 2;
+
+	for (reg = 0; reg < 0x10; ++reg) {
+		/* 2.4 internal transmit function
+		 *
+	 	 * we initialize the first row in the rate grid.
+		 * values are period (in clock cycles) of timer
+		 */
+		unsigned period = clock / rate;
+
+		he_writel_mbox(he_dev, period, CS_TGRLD0 + reg);
+		rate -= delta;
+	}
+
+	if (he_is622(he_dev)) {
+		/* table 5.2 (4 cells per lbuf) */
+		he_writel_mbox(he_dev, 0x000800fa, CS_ERTHR0);
+		he_writel_mbox(he_dev, 0x000c33cb, CS_ERTHR1);
+		he_writel_mbox(he_dev, 0x0010101b, CS_ERTHR2);
+		he_writel_mbox(he_dev, 0x00181dac, CS_ERTHR3);
+		he_writel_mbox(he_dev, 0x00280600, CS_ERTHR4);
+
+		/* table 5.3, 5.4, 5.5, 5.6, 5.7 */
+		he_writel_mbox(he_dev, 0x023de8b3, CS_ERCTL0);
+		he_writel_mbox(he_dev, 0x1801, CS_ERCTL1);
+		he_writel_mbox(he_dev, 0x68b3, CS_ERCTL2);
+		he_writel_mbox(he_dev, 0x1280, CS_ERSTAT0);
+		he_writel_mbox(he_dev, 0x68b3, CS_ERSTAT1);
+		he_writel_mbox(he_dev, 0x14585, CS_RTFWR);
+
+		he_writel_mbox(he_dev, 0x4680, CS_RTATR);
+
+		/* table 5.8 */
+		he_writel_mbox(he_dev, 0x00159ece, CS_TFBSET);
+		he_writel_mbox(he_dev, 0x68b3, CS_WCRMAX);
+		he_writel_mbox(he_dev, 0x5eb3, CS_WCRMIN);
+		he_writel_mbox(he_dev, 0xe8b3, CS_WCRINC);
+		he_writel_mbox(he_dev, 0xdeb3, CS_WCRDEC);
+		he_writel_mbox(he_dev, 0x68b3, CS_WCRCEIL);
+
+		/* table 5.9 */
+		he_writel_mbox(he_dev, 0x5, CS_OTPPER);
+		he_writel_mbox(he_dev, 0x14, CS_OTWPER);
+	} else {
+		/* table 5.1 (4 cells per lbuf) */
+		he_writel_mbox(he_dev, 0x000400ea, CS_ERTHR0);
+		he_writel_mbox(he_dev, 0x00063388, CS_ERTHR1);
+		he_writel_mbox(he_dev, 0x00081018, CS_ERTHR2);
+		he_writel_mbox(he_dev, 0x000c1dac, CS_ERTHR3);
+		he_writel_mbox(he_dev, 0x0014051a, CS_ERTHR4);
+
+		/* table 5.3, 5.4, 5.5, 5.6, 5.7 */
+		he_writel_mbox(he_dev, 0x0235e4b1, CS_ERCTL0);
+		he_writel_mbox(he_dev, 0x4701, CS_ERCTL1);
+		he_writel_mbox(he_dev, 0x64b1, CS_ERCTL2);
+		he_writel_mbox(he_dev, 0x1280, CS_ERSTAT0);
+		he_writel_mbox(he_dev, 0x64b1, CS_ERSTAT1);
+		he_writel_mbox(he_dev, 0xf424, CS_RTFWR);
+
+		he_writel_mbox(he_dev, 0x4680, CS_RTATR);
+
+		/* table 5.8 */
+		he_writel_mbox(he_dev, 0x000563b7, CS_TFBSET);
+		he_writel_mbox(he_dev, 0x64b1, CS_WCRMAX);
+		he_writel_mbox(he_dev, 0x5ab1, CS_WCRMIN);
+		he_writel_mbox(he_dev, 0xe4b1, CS_WCRINC);
+		he_writel_mbox(he_dev, 0xdab1, CS_WCRDEC);
+		he_writel_mbox(he_dev, 0x64b1, CS_WCRCEIL);
+
+		/* table 5.9 */
+		he_writel_mbox(he_dev, 0x6, CS_OTPPER);
+		he_writel_mbox(he_dev, 0x1e, CS_OTWPER);
+	}
+
+	he_writel_mbox(he_dev, 0x8, CS_OTTLIM);
+
+	for (reg = 0; reg < 0x8; ++reg)
+		he_writel_mbox(he_dev, 0x0, CS_HGRRT0 + reg);
+
+}
+
+static int __init
+he_init_cs_block_rcm(struct he_dev *he_dev)
+{
+	unsigned (*rategrid)[16][16];
+	unsigned rate, delta;
+	int i, j, reg;
+
+	unsigned rate_atmf, exp, man;
+	unsigned long long rate_cps;
+	int mult, buf, buf_limit = 4;
+
+	rategrid = kmalloc( sizeof(unsigned) * 16 * 16, GFP_KERNEL);
+	if (!rategrid)
+		return -ENOMEM;
+
+	/* initialize rate grid group table */
+
+	for (reg = 0x0; reg < 0xff; ++reg)
+		he_writel_rcm(he_dev, 0x0, CONFIG_RCMABR + reg);
+
+	/* initialize rate controller groups */
+
+	for (reg = 0x100; reg < 0x1ff; ++reg)
+		he_writel_rcm(he_dev, 0x0, CONFIG_RCMABR + reg);
+	
+	/* initialize tNrm lookup table */
+
+	/* the manual makes reference to a routine in a sample driver
+	   for proper configuration; fortunately, we only need this
+	   in order to support abr connection */
+	
+	/* initialize rate to group table */
+
+	rate = he_dev->atm_dev->link_rate;
+	delta = rate / 32;
+
+	/*
+	 * 2.4 transmit internal functions
+	 * 
+	 * we construct a copy of the rate grid used by the scheduler
+	 * in order to construct the rate to group table below
+	 */
+
+	for (j = 0; j < 16; j++) {
+		(*rategrid)[0][j] = rate;
+		rate -= delta;
+	}
+
+	for (i = 1; i < 16; i++)
+		for (j = 0; j < 16; j++)
+			if (i > 14)
+				(*rategrid)[i][j] = (*rategrid)[i - 1][j] / 4;
+			else
+				(*rategrid)[i][j] = (*rategrid)[i - 1][j] / 2;
+
+	/*
+	 * 2.4 transmit internal function
+	 *
+	 * this table maps the upper 5 bits of exponent and mantissa
+	 * of the atm forum representation of the rate into an index
+	 * on rate grid  
+	 */
+
+	rate_atmf = 0;
+	while (rate_atmf < 0x400) {
+		man = (rate_atmf & 0x1f) << 4;
+		exp = rate_atmf >> 5;
+
+		/* 
+			instead of '/ 512', use '>> 9' to prevent a call
+			to divdu3 on x86 platforms
+		*/
+		rate_cps = (unsigned long long) (1 << exp) * (man + 512) >> 9;
+
+		if (rate_cps < 10)
+			rate_cps = 10;	/* 2.2.1 minimum payload rate is 10 cps */
+
+		for (i = 255; i > 0; i--)
+			if ((*rategrid)[i/16][i%16] >= rate_cps)
+				break;	 /* pick nearest rate instead? */
+
+		/*
+		 * each table entry is 16 bits: (rate grid index (8 bits)
+		 * and a buffer limit (8 bits)
+		 * there are two table entries in each 32-bit register
+		 */
+
+#ifdef notdef
+		buf = rate_cps * he_dev->tx_numbuffs /
+				(he_dev->atm_dev->link_rate * 2);
+#else
+		/* this is pretty, but avoids _divdu3 and is mostly correct */
+		mult = he_dev->atm_dev->link_rate / ATM_OC3_PCR;
+		if (rate_cps > (272 * mult))
+			buf = 4;
+		else if (rate_cps > (204 * mult))
+			buf = 3;
+		else if (rate_cps > (136 * mult))
+			buf = 2;
+		else if (rate_cps > (68 * mult))
+			buf = 1;
+		else
+			buf = 0;
+#endif
+		if (buf > buf_limit)
+			buf = buf_limit;
+		reg = (reg << 16) | ((i << 8) | buf);
+
+#define RTGTBL_OFFSET 0x400
+	  
+		if (rate_atmf & 0x1)
+			he_writel_rcm(he_dev, reg,
+				CONFIG_RCMABR + RTGTBL_OFFSET + (rate_atmf >> 1));
+
+		++rate_atmf;
+	}
+
+	kfree(rategrid);
+	return 0;
+}
+
+static int __init
+he_init_group(struct he_dev *he_dev, int group)
+{
+	int i;
+
+#ifdef USE_RBPS
+	/* small buffer pool */
+#ifdef USE_RBPS_POOL
+	he_dev->rbps_pool = pci_pool_create("rbps", he_dev->pci_dev,
+			CONFIG_RBPS_BUFSIZE, 8, 0);
+	if (he_dev->rbps_pool == NULL) {
+		hprintk("unable to create rbps pages\n");
+		return -ENOMEM;
+	}
+#else /* !USE_RBPS_POOL */
+	he_dev->rbps_pages = pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_RBPS_SIZE * CONFIG_RBPS_BUFSIZE, &he_dev->rbps_pages_phys);
+	if (he_dev->rbps_pages == NULL) {
+		hprintk("unable to create rbps page pool\n");
+		return -ENOMEM;
+	}
+#endif /* USE_RBPS_POOL */
+
+	he_dev->rbps_base = pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_RBPS_SIZE * sizeof(struct he_rbp), &he_dev->rbps_phys);
+	if (he_dev->rbps_base == NULL) {
+		hprintk("failed to alloc rbps\n");
+		return -ENOMEM;
+	}
+	memset(he_dev->rbps_base, 0, CONFIG_RBPS_SIZE * sizeof(struct he_rbp));
+	he_dev->rbps_virt = kmalloc(CONFIG_RBPS_SIZE * sizeof(struct he_virt), GFP_KERNEL);
+
+	for (i = 0; i < CONFIG_RBPS_SIZE; ++i) {
+		dma_addr_t dma_handle;
+		void *cpuaddr;
+
+#ifdef USE_RBPS_POOL 
+		cpuaddr = pci_pool_alloc(he_dev->rbps_pool, SLAB_KERNEL|SLAB_DMA, &dma_handle);
+		if (cpuaddr == NULL)
+			return -ENOMEM;
+#else
+		cpuaddr = he_dev->rbps_pages + (i * CONFIG_RBPS_BUFSIZE);
+		dma_handle = he_dev->rbps_pages_phys + (i * CONFIG_RBPS_BUFSIZE);
+#endif
+
+		he_dev->rbps_virt[i].virt = cpuaddr;
+		he_dev->rbps_base[i].status = RBP_LOANED | RBP_SMALLBUF | (i << RBP_INDEX_OFF);
+		he_dev->rbps_base[i].phys = dma_handle;
+
+	}
+	he_dev->rbps_tail = &he_dev->rbps_base[CONFIG_RBPS_SIZE - 1];
+
+	he_writel(he_dev, he_dev->rbps_phys, G0_RBPS_S + (group * 32));
+	he_writel(he_dev, RBPS_MASK(he_dev->rbps_tail),
+						G0_RBPS_T + (group * 32));
+	he_writel(he_dev, CONFIG_RBPS_BUFSIZE/4,
+						G0_RBPS_BS + (group * 32));
+	he_writel(he_dev,
+			RBP_THRESH(CONFIG_RBPS_THRESH) |
+			RBP_QSIZE(CONFIG_RBPS_SIZE - 1) |
+			RBP_INT_ENB,
+						G0_RBPS_QI + (group * 32));
+#else /* !USE_RBPS */
+	he_writel(he_dev, 0x0, G0_RBPS_S + (group * 32));
+	he_writel(he_dev, 0x0, G0_RBPS_T + (group * 32));
+	he_writel(he_dev, 0x0, G0_RBPS_QI + (group * 32));
+	he_writel(he_dev, RBP_THRESH(0x1) | RBP_QSIZE(0x0),
+						G0_RBPS_BS + (group * 32));
+#endif /* USE_RBPS */
+
+	/* large buffer pool */
+#ifdef USE_RBPL_POOL
+	he_dev->rbpl_pool = pci_pool_create("rbpl", he_dev->pci_dev,
+			CONFIG_RBPL_BUFSIZE, 8, 0);
+	if (he_dev->rbpl_pool == NULL) {
+		hprintk("unable to create rbpl pool\n");
+		return -ENOMEM;
+	}
+#else /* !USE_RBPL_POOL */
+	he_dev->rbpl_pages = (void *) pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_RBPL_SIZE * CONFIG_RBPL_BUFSIZE, &he_dev->rbpl_pages_phys);
+	if (he_dev->rbpl_pages == NULL) {
+		hprintk("unable to create rbpl pages\n");
+		return -ENOMEM;
+	}
+#endif /* USE_RBPL_POOL */
+
+	he_dev->rbpl_base = pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_RBPL_SIZE * sizeof(struct he_rbp), &he_dev->rbpl_phys);
+	if (he_dev->rbpl_base == NULL) {
+		hprintk("failed to alloc rbpl\n");
+		return -ENOMEM;
+	}
+	memset(he_dev->rbpl_base, 0, CONFIG_RBPL_SIZE * sizeof(struct he_rbp));
+	he_dev->rbpl_virt = kmalloc(CONFIG_RBPL_SIZE * sizeof(struct he_virt), GFP_KERNEL);
+
+	for (i = 0; i < CONFIG_RBPL_SIZE; ++i) {
+		dma_addr_t dma_handle;
+		void *cpuaddr;
+
+#ifdef USE_RBPL_POOL
+		cpuaddr = pci_pool_alloc(he_dev->rbpl_pool, SLAB_KERNEL|SLAB_DMA, &dma_handle);
+		if (cpuaddr == NULL)
+			return -ENOMEM;
+#else
+		cpuaddr = he_dev->rbpl_pages + (i * CONFIG_RBPL_BUFSIZE);
+		dma_handle = he_dev->rbpl_pages_phys + (i * CONFIG_RBPL_BUFSIZE);
+#endif
+
+		he_dev->rbpl_virt[i].virt = cpuaddr;
+		he_dev->rbpl_base[i].status = RBP_LOANED | (i << RBP_INDEX_OFF);
+		he_dev->rbpl_base[i].phys = dma_handle;
+	}
+	he_dev->rbpl_tail = &he_dev->rbpl_base[CONFIG_RBPL_SIZE - 1];
+
+	he_writel(he_dev, he_dev->rbpl_phys, G0_RBPL_S + (group * 32));
+	he_writel(he_dev, RBPL_MASK(he_dev->rbpl_tail),
+						G0_RBPL_T + (group * 32));
+	he_writel(he_dev, CONFIG_RBPL_BUFSIZE/4,
+						G0_RBPL_BS + (group * 32));
+	he_writel(he_dev,
+			RBP_THRESH(CONFIG_RBPL_THRESH) |
+			RBP_QSIZE(CONFIG_RBPL_SIZE - 1) |
+			RBP_INT_ENB,
+						G0_RBPL_QI + (group * 32));
+
+	/* rx buffer ready queue */
+
+	he_dev->rbrq_base = pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_RBRQ_SIZE * sizeof(struct he_rbrq), &he_dev->rbrq_phys);
+	if (he_dev->rbrq_base == NULL) {
+		hprintk("failed to allocate rbrq\n");
+		return -ENOMEM;
+	}
+	memset(he_dev->rbrq_base, 0, CONFIG_RBRQ_SIZE * sizeof(struct he_rbrq));
+
+	he_dev->rbrq_head = he_dev->rbrq_base;
+	he_writel(he_dev, he_dev->rbrq_phys, G0_RBRQ_ST + (group * 16));
+	he_writel(he_dev, 0, G0_RBRQ_H + (group * 16));
+	he_writel(he_dev,
+		RBRQ_THRESH(CONFIG_RBRQ_THRESH) | RBRQ_SIZE(CONFIG_RBRQ_SIZE - 1),
+						G0_RBRQ_Q + (group * 16));
+	if (irq_coalesce) {
+		hprintk("coalescing interrupts\n");
+		he_writel(he_dev, RBRQ_TIME(768) | RBRQ_COUNT(7),
+						G0_RBRQ_I + (group * 16));
+	} else
+		he_writel(he_dev, RBRQ_TIME(0) | RBRQ_COUNT(1),
+						G0_RBRQ_I + (group * 16));
+
+	/* tx buffer ready queue */
+
+	he_dev->tbrq_base = pci_alloc_consistent(he_dev->pci_dev,
+		CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq), &he_dev->tbrq_phys);
+	if (he_dev->tbrq_base == NULL) {
+		hprintk("failed to allocate tbrq\n");
+		return -ENOMEM;
+	}
+	memset(he_dev->tbrq_base, 0, CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq));
+
+	he_dev->tbrq_head = he_dev->tbrq_base;
+
+	he_writel(he_dev, he_dev->tbrq_phys, G0_TBRQ_B_T + (group * 16));
+	he_writel(he_dev, 0, G0_TBRQ_H + (group * 16));
+	he_writel(he_dev, CONFIG_TBRQ_SIZE - 1, G0_TBRQ_S + (group * 16));
+	he_writel(he_dev, CONFIG_TBRQ_THRESH, G0_TBRQ_THRESH + (group * 16));
+
+	return 0;
+}
+
+static int __init
+he_init_irq(struct he_dev *he_dev)
+{
+	int i;
+
+	/* 2.9.3.5  tail offset for each interrupt queue is located after the
+		    end of the interrupt queue */
+
+	he_dev->irq_base = pci_alloc_consistent(he_dev->pci_dev,
+			(CONFIG_IRQ_SIZE+1) * sizeof(struct he_irq), &he_dev->irq_phys);
+	if (he_dev->irq_base == NULL) {
+		hprintk("failed to allocate irq\n");
+		return -ENOMEM;
+	}
+	he_dev->irq_tailoffset = (unsigned *)
+					&he_dev->irq_base[CONFIG_IRQ_SIZE];
+	*he_dev->irq_tailoffset = 0;
+	he_dev->irq_head = he_dev->irq_base;
+	he_dev->irq_tail = he_dev->irq_base;
+
+	for (i = 0; i < CONFIG_IRQ_SIZE; ++i)
+		he_dev->irq_base[i].isw = ITYPE_INVALID;
+
+	he_writel(he_dev, he_dev->irq_phys, IRQ0_BASE);
+	he_writel(he_dev,
+		IRQ_SIZE(CONFIG_IRQ_SIZE) | IRQ_THRESH(CONFIG_IRQ_THRESH),
+								IRQ0_HEAD);
+	he_writel(he_dev, IRQ_INT_A | IRQ_TYPE_LINE, IRQ0_CNTL);
+	he_writel(he_dev, 0x0, IRQ0_DATA);
+
+	he_writel(he_dev, 0x0, IRQ1_BASE);
+	he_writel(he_dev, 0x0, IRQ1_HEAD);
+	he_writel(he_dev, 0x0, IRQ1_CNTL);
+	he_writel(he_dev, 0x0, IRQ1_DATA);
+
+	he_writel(he_dev, 0x0, IRQ2_BASE);
+	he_writel(he_dev, 0x0, IRQ2_HEAD);
+	he_writel(he_dev, 0x0, IRQ2_CNTL);
+	he_writel(he_dev, 0x0, IRQ2_DATA);
+
+	he_writel(he_dev, 0x0, IRQ3_BASE);
+	he_writel(he_dev, 0x0, IRQ3_HEAD);
+	he_writel(he_dev, 0x0, IRQ3_CNTL);
+	he_writel(he_dev, 0x0, IRQ3_DATA);
+
+	/* 2.9.3.2 interrupt queue mapping registers */
+
+	he_writel(he_dev, 0x0, GRP_10_MAP);
+	he_writel(he_dev, 0x0, GRP_32_MAP);
+	he_writel(he_dev, 0x0, GRP_54_MAP);
+	he_writel(he_dev, 0x0, GRP_76_MAP);
+
+	if (request_irq(he_dev->pci_dev->irq, he_irq_handler, SA_INTERRUPT|SA_SHIRQ, DEV_LABEL, he_dev)) {
+		hprintk("irq %d already in use\n", he_dev->pci_dev->irq);
+		return -EINVAL;
+	}   
+
+	he_dev->irq = he_dev->pci_dev->irq;
+
+	return 0;
+}
+
+static int __init
+he_start(struct atm_dev *dev)
+{
+	struct he_dev *he_dev;
+	struct pci_dev *pci_dev;
+	unsigned long membase;
+
+	u16 command;
+	u32 gen_cntl_0, host_cntl, lb_swap;
+	u8 cache_size, timer;
+	
+	unsigned err;
+	unsigned int status, reg;
+	int i, group;
+
+	he_dev = HE_DEV(dev);
+	pci_dev = he_dev->pci_dev;
+
+	membase = pci_resource_start(pci_dev, 0);
+	HPRINTK("membase = 0x%lx  irq = %d.\n", membase, pci_dev->irq);
+
+	/*
+	 * pci bus controller initialization 
+	 */
+
+	/* 4.3 pci bus controller-specific initialization */
+	if (pci_read_config_dword(pci_dev, GEN_CNTL_0, &gen_cntl_0) != 0) {
+		hprintk("can't read GEN_CNTL_0\n");
+		return -EINVAL;
+	}
+	gen_cntl_0 |= (MRL_ENB | MRM_ENB | IGNORE_TIMEOUT);
+	if (pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0) != 0) {
+		hprintk("can't write GEN_CNTL_0.\n");
+		return -EINVAL;
+	}
+
+	if (pci_read_config_word(pci_dev, PCI_COMMAND, &command) != 0) {
+		hprintk("can't read PCI_COMMAND.\n");
+		return -EINVAL;
+	}
+
+	command |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE);
+	if (pci_write_config_word(pci_dev, PCI_COMMAND, command) != 0) {
+		hprintk("can't enable memory.\n");
+		return -EINVAL;
+	}
+
+	if (pci_read_config_byte(pci_dev, PCI_CACHE_LINE_SIZE, &cache_size)) {
+		hprintk("can't read cache line size?\n");
+		return -EINVAL;
+	}
+
+	if (cache_size < 16) {
+		cache_size = 16;
+		if (pci_write_config_byte(pci_dev, PCI_CACHE_LINE_SIZE, cache_size))
+			hprintk("can't set cache line size to %d\n", cache_size);
+	}
+
+	if (pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &timer)) {
+		hprintk("can't read latency timer?\n");
+		return -EINVAL;
+	}
+
+	/* from table 3.9
+	 *
+	 * LAT_TIMER = 1 + AVG_LAT + BURST_SIZE/BUS_SIZE
+	 * 
+	 * AVG_LAT: The average first data read/write latency [maximum 16 clock cycles]
+	 * BURST_SIZE: 1536 bytes (read) for 622, 768 bytes (read) for 155 [192 clock cycles]
+	 *
+	 */ 
+#define LAT_TIMER 209
+	if (timer < LAT_TIMER) {
+		HPRINTK("latency timer was %d, setting to %d\n", timer, LAT_TIMER);
+		timer = LAT_TIMER;
+		if (pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, timer))
+			hprintk("can't set latency timer to %d\n", timer);
+	}
+
+	if (!(he_dev->membase = ioremap(membase, HE_REGMAP_SIZE))) {
+		hprintk("can't set up page mapping\n");
+		return -EINVAL;
+	}
+
+	/* 4.4 card reset */
+	he_writel(he_dev, 0x0, RESET_CNTL);
+	he_writel(he_dev, 0xff, RESET_CNTL);
+
+	udelay(16*1000);	/* 16 ms */
+	status = he_readl(he_dev, RESET_CNTL);
+	if ((status & BOARD_RST_STATUS) == 0) {
+		hprintk("reset failed\n");
+		return -EINVAL;
+	}
+
+	/* 4.5 set bus width */
+	host_cntl = he_readl(he_dev, HOST_CNTL);
+	if (host_cntl & PCI_BUS_SIZE64)
+		gen_cntl_0 |= ENBL_64;
+	else
+		gen_cntl_0 &= ~ENBL_64;
+
+	if (disable64 == 1) {
+		hprintk("disabling 64-bit pci bus transfers\n");
+		gen_cntl_0 &= ~ENBL_64;
+	}
+
+	if (gen_cntl_0 & ENBL_64)
+		hprintk("64-bit transfers enabled\n");
+
+	pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0);
+
+	/* 4.7 read prom contents */
+	for (i = 0; i < PROD_ID_LEN; ++i)
+		he_dev->prod_id[i] = read_prom_byte(he_dev, PROD_ID + i);
+
+	he_dev->media = read_prom_byte(he_dev, MEDIA);
+
+	for (i = 0; i < 6; ++i)
+		dev->esi[i] = read_prom_byte(he_dev, MAC_ADDR + i);
+
+	hprintk("%s%s, %x:%x:%x:%x:%x:%x\n",
+				he_dev->prod_id,
+					he_dev->media & 0x40 ? "SM" : "MM",
+						dev->esi[0],
+						dev->esi[1],
+						dev->esi[2],
+						dev->esi[3],
+						dev->esi[4],
+						dev->esi[5]);
+	he_dev->atm_dev->link_rate = he_is622(he_dev) ?
+						ATM_OC12_PCR : ATM_OC3_PCR;
+
+	/* 4.6 set host endianess */
+	lb_swap = he_readl(he_dev, LB_SWAP);
+	if (he_is622(he_dev))
+		lb_swap &= ~XFER_SIZE;		/* 4 cells */
+	else
+		lb_swap |= XFER_SIZE;		/* 8 cells */
+#ifdef __BIG_ENDIAN
+	lb_swap |= DESC_WR_SWAP | INTR_SWAP | BIG_ENDIAN_HOST;
+#else
+	lb_swap &= ~(DESC_WR_SWAP | INTR_SWAP | BIG_ENDIAN_HOST |
+			DATA_WR_SWAP | DATA_RD_SWAP | DESC_RD_SWAP);
+#endif /* __BIG_ENDIAN */
+	he_writel(he_dev, lb_swap, LB_SWAP);
+
+	/* 4.8 sdram controller initialization */
+	he_writel(he_dev, he_is622(he_dev) ? LB_64_ENB : 0x0, SDRAM_CTL);
+
+	/* 4.9 initialize rnum value */
+	lb_swap |= SWAP_RNUM_MAX(0xf);
+	he_writel(he_dev, lb_swap, LB_SWAP);
+
+	/* 4.10 initialize the interrupt queues */
+	if ((err = he_init_irq(he_dev)) != 0)
+		return err;
+
+#ifdef USE_TASKLET
+	tasklet_init(&he_dev->tasklet, he_tasklet, (unsigned long) he_dev);
+#endif
+	spin_lock_init(&he_dev->global_lock);
+
+	/* 4.11 enable pci bus controller state machines */
+	host_cntl |= (OUTFF_ENB | CMDFF_ENB |
+				QUICK_RD_RETRY | QUICK_WR_RETRY | PERR_INT_ENB);
+	he_writel(he_dev, host_cntl, HOST_CNTL);
+
+	gen_cntl_0 |= INT_PROC_ENBL|INIT_ENB;
+	pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0);
+
+	/*
+	 * atm network controller initialization
+	 */
+
+	/* 5.1.1 generic configuration state */
+
+	/*
+	 *		local (cell) buffer memory map
+	 *                    
+	 *             HE155                          HE622
+	 *                                                      
+	 *        0 ____________1023 bytes  0 _______________________2047 bytes
+	 *         |            |            |                   |   |
+	 *         |  utility   |            |        rx0        |   |
+	 *        5|____________|         255|___________________| u |
+	 *        6|            |         256|                   | t |
+	 *         |            |            |                   | i |
+	 *         |    rx0     |     row    |        tx         | l |
+	 *         |            |            |                   | i |
+	 *         |            |         767|___________________| t |
+	 *      517|____________|         768|                   | y |
+	 * row  518|            |            |        rx1        |   |
+	 *         |            |        1023|___________________|___|
+	 *         |            |
+	 *         |    tx      |
+	 *         |            |
+	 *         |            |
+	 *     1535|____________|
+	 *     1536|            |
+	 *         |    rx1     |
+	 *     2047|____________|
+	 *
+	 */
+
+	/* total 4096 connections */
+	he_dev->vcibits = CONFIG_DEFAULT_VCIBITS;
+	he_dev->vpibits = CONFIG_DEFAULT_VPIBITS;
+
+	if (nvpibits != -1 && nvcibits != -1 && nvpibits+nvcibits != HE_MAXCIDBITS) {
+		hprintk("nvpibits + nvcibits != %d\n", HE_MAXCIDBITS);
+		return -ENODEV;
+	}
+
+	if (nvpibits != -1) {
+		he_dev->vpibits = nvpibits;
+		he_dev->vcibits = HE_MAXCIDBITS - nvpibits;
+	}
+
+	if (nvcibits != -1) {
+		he_dev->vcibits = nvcibits;
+		he_dev->vpibits = HE_MAXCIDBITS - nvcibits;
+	}
+
+
+	if (he_is622(he_dev)) {
+		he_dev->cells_per_row = 40;
+		he_dev->bytes_per_row = 2048;
+		he_dev->r0_numrows = 256;
+		he_dev->tx_numrows = 512;
+		he_dev->r1_numrows = 256;
+		he_dev->r0_startrow = 0;
+		he_dev->tx_startrow = 256;
+		he_dev->r1_startrow = 768;
+	} else {
+		he_dev->cells_per_row = 20;
+		he_dev->bytes_per_row = 1024;
+		he_dev->r0_numrows = 512;
+		he_dev->tx_numrows = 1018;
+		he_dev->r1_numrows = 512;
+		he_dev->r0_startrow = 6;
+		he_dev->tx_startrow = 518;
+		he_dev->r1_startrow = 1536;
+	}
+
+	he_dev->cells_per_lbuf = 4;
+	he_dev->buffer_limit = 4;
+	he_dev->r0_numbuffs = he_dev->r0_numrows *
+				he_dev->cells_per_row / he_dev->cells_per_lbuf;
+	if (he_dev->r0_numbuffs > 2560)
+		he_dev->r0_numbuffs = 2560;
+
+	he_dev->r1_numbuffs = he_dev->r1_numrows *
+				he_dev->cells_per_row / he_dev->cells_per_lbuf;
+	if (he_dev->r1_numbuffs > 2560)
+		he_dev->r1_numbuffs = 2560;
+
+	he_dev->tx_numbuffs = he_dev->tx_numrows *
+				he_dev->cells_per_row / he_dev->cells_per_lbuf;
+	if (he_dev->tx_numbuffs > 5120)
+		he_dev->tx_numbuffs = 5120;
+
+	/* 5.1.2 configure hardware dependent registers */
+
+	he_writel(he_dev, 
+		SLICE_X(0x2) | ARB_RNUM_MAX(0xf) | TH_PRTY(0x3) |
+		RH_PRTY(0x3) | TL_PRTY(0x2) | RL_PRTY(0x1) |
+		(he_is622(he_dev) ? BUS_MULTI(0x28) : BUS_MULTI(0x46)) |
+		(he_is622(he_dev) ? NET_PREF(0x50) : NET_PREF(0x8c)),
+								LBARB);
+
+	he_writel(he_dev, BANK_ON |
+		(he_is622(he_dev) ? (REF_RATE(0x384) | WIDE_DATA) : REF_RATE(0x150)),
+								SDRAMCON);
+
+	he_writel(he_dev,
+		(he_is622(he_dev) ? RM_BANK_WAIT(1) : RM_BANK_WAIT(0)) |
+						RM_RW_WAIT(1), RCMCONFIG);
+	he_writel(he_dev,
+		(he_is622(he_dev) ? TM_BANK_WAIT(2) : TM_BANK_WAIT(1)) |
+						TM_RW_WAIT(1), TCMCONFIG);
+
+	he_writel(he_dev, he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD, LB_CONFIG);
+
+	he_writel(he_dev, 
+		(he_is622(he_dev) ? UT_RD_DELAY(8) : UT_RD_DELAY(0)) |
+		(he_is622(he_dev) ? RC_UT_MODE(0) : RC_UT_MODE(1)) |
+		RX_VALVP(he_dev->vpibits) |
+		RX_VALVC(he_dev->vcibits),			 RC_CONFIG);
+
+	he_writel(he_dev, DRF_THRESH(0x20) |
+		(he_is622(he_dev) ? TX_UT_MODE(0) : TX_UT_MODE(1)) |
+		TX_VCI_MASK(he_dev->vcibits) |
+		LBFREE_CNT(he_dev->tx_numbuffs), 		TX_CONFIG);
+
+	he_writel(he_dev, 0x0, TXAAL5_PROTO);
+
+	he_writel(he_dev, PHY_INT_ENB |
+		(he_is622(he_dev) ? PTMR_PRE(67 - 1) : PTMR_PRE(50 - 1)),
+								RH_CONFIG);
+
+	/* 5.1.3 initialize connection memory */
+
+	for (i = 0; i < TCM_MEM_SIZE; ++i)
+		he_writel_tcm(he_dev, 0, i);
+
+	for (i = 0; i < RCM_MEM_SIZE; ++i)
+		he_writel_rcm(he_dev, 0, i);
+
+	/*
+	 *	transmit connection memory map
+	 *
+	 *                  tx memory
+	 *          0x0 ___________________
+	 *             |                   |
+	 *             |                   |
+	 *             |       TSRa        |
+	 *             |                   |
+	 *             |                   |
+	 *       0x8000|___________________|
+	 *             |                   |
+	 *             |       TSRb        |
+	 *       0xc000|___________________|
+	 *             |                   |
+	 *             |       TSRc        |
+	 *       0xe000|___________________|
+	 *             |       TSRd        |
+	 *       0xf000|___________________|
+	 *             |       tmABR       |
+	 *      0x10000|___________________|
+	 *             |                   |
+	 *             |       tmTPD       |
+	 *             |___________________|
+	 *             |                   |
+	 *                      ....
+	 *      0x1ffff|___________________|
+	 *
+	 *
+	 */
+
+	he_writel(he_dev, CONFIG_TSRB, TSRB_BA);
+	he_writel(he_dev, CONFIG_TSRC, TSRC_BA);
+	he_writel(he_dev, CONFIG_TSRD, TSRD_BA);
+	he_writel(he_dev, CONFIG_TMABR, TMABR_BA);
+	he_writel(he_dev, CONFIG_TPDBA, TPD_BA);
+
+
+	/*
+	 *	receive connection memory map
+	 *
+	 *          0x0 ___________________
+	 *             |                   |
+	 *             |                   |
+	 *             |       RSRa        |
+	 *             |                   |
+	 *             |                   |
+	 *       0x8000|___________________|
+	 *             |                   |
+	 *             |             rx0/1 |
+	 *             |       LBM         |   link lists of local
+	 *             |             tx    |   buffer memory 
+	 *             |                   |
+	 *       0xd000|___________________|
+	 *             |                   |
+	 *             |      rmABR        |
+	 *       0xe000|___________________|
+	 *             |                   |
+	 *             |       RSRb        |
+	 *             |___________________|
+	 *             |                   |
+	 *                      ....
+	 *       0xffff|___________________|
+	 */
+
+	he_writel(he_dev, 0x08000, RCMLBM_BA);
+	he_writel(he_dev, 0x0e000, RCMRSRB_BA);
+	he_writel(he_dev, 0x0d800, RCMABR_BA);
+
+	/* 5.1.4 initialize local buffer free pools linked lists */
+
+	he_init_rx_lbfp0(he_dev);
+	he_init_rx_lbfp1(he_dev);
+
+	he_writel(he_dev, 0x0, RLBC_H);
+	he_writel(he_dev, 0x0, RLBC_T);
+	he_writel(he_dev, 0x0, RLBC_H2);
+
+	he_writel(he_dev, 512, RXTHRSH);	/* 10% of r0+r1 buffers */
+	he_writel(he_dev, 256, LITHRSH); 	/* 5% of r0+r1 buffers */
+
+	he_init_tx_lbfp(he_dev);
+
+	he_writel(he_dev, he_is622(he_dev) ? 0x104780 : 0x800, UBUFF_BA);
+
+	/* 5.1.5 initialize intermediate receive queues */
+
+	if (he_is622(he_dev)) {
+		he_writel(he_dev, 0x000f, G0_INMQ_S);
+		he_writel(he_dev, 0x200f, G0_INMQ_L);
+
+		he_writel(he_dev, 0x001f, G1_INMQ_S);
+		he_writel(he_dev, 0x201f, G1_INMQ_L);
+
+		he_writel(he_dev, 0x002f, G2_INMQ_S);
+		he_writel(he_dev, 0x202f, G2_INMQ_L);
+
+		he_writel(he_dev, 0x003f, G3_INMQ_S);
+		he_writel(he_dev, 0x203f, G3_INMQ_L);
+
+		he_writel(he_dev, 0x004f, G4_INMQ_S);
+		he_writel(he_dev, 0x204f, G4_INMQ_L);
+
+		he_writel(he_dev, 0x005f, G5_INMQ_S);
+		he_writel(he_dev, 0x205f, G5_INMQ_L);
+
+		he_writel(he_dev, 0x006f, G6_INMQ_S);
+		he_writel(he_dev, 0x206f, G6_INMQ_L);
+
+		he_writel(he_dev, 0x007f, G7_INMQ_S);
+		he_writel(he_dev, 0x207f, G7_INMQ_L);
+	} else {
+		he_writel(he_dev, 0x0000, G0_INMQ_S);
+		he_writel(he_dev, 0x0008, G0_INMQ_L);
+
+		he_writel(he_dev, 0x0001, G1_INMQ_S);
+		he_writel(he_dev, 0x0009, G1_INMQ_L);
+
+		he_writel(he_dev, 0x0002, G2_INMQ_S);
+		he_writel(he_dev, 0x000a, G2_INMQ_L);
+
+		he_writel(he_dev, 0x0003, G3_INMQ_S);
+		he_writel(he_dev, 0x000b, G3_INMQ_L);
+
+		he_writel(he_dev, 0x0004, G4_INMQ_S);
+		he_writel(he_dev, 0x000c, G4_INMQ_L);
+
+		he_writel(he_dev, 0x0005, G5_INMQ_S);
+		he_writel(he_dev, 0x000d, G5_INMQ_L);
+
+		he_writel(he_dev, 0x0006, G6_INMQ_S);
+		he_writel(he_dev, 0x000e, G6_INMQ_L);
+
+		he_writel(he_dev, 0x0007, G7_INMQ_S);
+		he_writel(he_dev, 0x000f, G7_INMQ_L);
+	}
+
+	/* 5.1.6 application tunable parameters */
+
+	he_writel(he_dev, 0x0, MCC);
+	he_writel(he_dev, 0x0, OEC);
+	he_writel(he_dev, 0x0, DCC);
+	he_writel(he_dev, 0x0, CEC);
+	
+	/* 5.1.7 cs block initialization */
+
+	he_init_cs_block(he_dev);
+
+	/* 5.1.8 cs block connection memory initialization */
+	
+	if (he_init_cs_block_rcm(he_dev) < 0)
+		return -ENOMEM;
+
+	/* 5.1.10 initialize host structures */
+
+	he_init_tpdrq(he_dev);
+
+#ifdef USE_TPD_POOL
+	he_dev->tpd_pool = pci_pool_create("tpd", he_dev->pci_dev,
+		sizeof(struct he_tpd), TPD_ALIGNMENT, 0);
+	if (he_dev->tpd_pool == NULL) {
+		hprintk("unable to create tpd pci_pool\n");
+		return -ENOMEM;         
+	}
+
+	INIT_LIST_HEAD(&he_dev->outstanding_tpds);
+#else
+	he_dev->tpd_base = (void *) pci_alloc_consistent(he_dev->pci_dev,
+			CONFIG_NUMTPDS * sizeof(struct he_tpd), &he_dev->tpd_base_phys);
+	if (!he_dev->tpd_base)
+		return -ENOMEM;
+
+	for (i = 0; i < CONFIG_NUMTPDS; ++i) {
+		he_dev->tpd_base[i].status = (i << TPD_ADDR_SHIFT);
+		he_dev->tpd_base[i].inuse = 0;
+	}
+		
+	he_dev->tpd_head = he_dev->tpd_base;
+	he_dev->tpd_end = &he_dev->tpd_base[CONFIG_NUMTPDS - 1];
+#endif
+
+	if (he_init_group(he_dev, 0) != 0)
+		return -ENOMEM;
+
+	for (group = 1; group < HE_NUM_GROUPS; ++group) {
+		he_writel(he_dev, 0x0, G0_RBPS_S + (group * 32));
+		he_writel(he_dev, 0x0, G0_RBPS_T + (group * 32));
+		he_writel(he_dev, 0x0, G0_RBPS_QI + (group * 32));
+		he_writel(he_dev, RBP_THRESH(0x1) | RBP_QSIZE(0x0),
+						G0_RBPS_BS + (group * 32));
+
+		he_writel(he_dev, 0x0, G0_RBPL_S + (group * 32));
+		he_writel(he_dev, 0x0, G0_RBPL_T + (group * 32));
+		he_writel(he_dev, RBP_THRESH(0x1) | RBP_QSIZE(0x0),
+						G0_RBPL_QI + (group * 32));
+		he_writel(he_dev, 0x0, G0_RBPL_BS + (group * 32));
+
+		he_writel(he_dev, 0x0, G0_RBRQ_ST + (group * 16));
+		he_writel(he_dev, 0x0, G0_RBRQ_H + (group * 16));
+		he_writel(he_dev, RBRQ_THRESH(0x1) | RBRQ_SIZE(0x0),
+						G0_RBRQ_Q + (group * 16));
+		he_writel(he_dev, 0x0, G0_RBRQ_I + (group * 16));
+
+		he_writel(he_dev, 0x0, G0_TBRQ_B_T + (group * 16));
+		he_writel(he_dev, 0x0, G0_TBRQ_H + (group * 16));
+		he_writel(he_dev, TBRQ_THRESH(0x1),
+						G0_TBRQ_THRESH + (group * 16));
+		he_writel(he_dev, 0x0, G0_TBRQ_S + (group * 16));
+	}
+
+	/* host status page */
+
+	he_dev->hsp = pci_alloc_consistent(he_dev->pci_dev,
+				sizeof(struct he_hsp), &he_dev->hsp_phys);
+	if (he_dev->hsp == NULL) {
+		hprintk("failed to allocate host status page\n");
+		return -ENOMEM;
+	}
+	memset(he_dev->hsp, 0, sizeof(struct he_hsp));
+	he_writel(he_dev, he_dev->hsp_phys, HSP_BA);
+
+	/* initialize framer */
+
+#ifdef CONFIG_ATM_HE_USE_SUNI
+	suni_init(he_dev->atm_dev);
+	if (he_dev->atm_dev->phy && he_dev->atm_dev->phy->start)
+		he_dev->atm_dev->phy->start(he_dev->atm_dev);
+#endif /* CONFIG_ATM_HE_USE_SUNI */
+
+	if (sdh) {
+		/* this really should be in suni.c but for now... */
+		int val;
+
+		val = he_phy_get(he_dev->atm_dev, SUNI_TPOP_APM);
+		val = (val & ~SUNI_TPOP_APM_S) | (SUNI_TPOP_S_SDH << SUNI_TPOP_APM_S_SHIFT);
+		he_phy_put(he_dev->atm_dev, val, SUNI_TPOP_APM);
+	}
+
+	/* 5.1.12 enable transmit and receive */
+
+	reg = he_readl_mbox(he_dev, CS_ERCTL0);
+	reg |= TX_ENABLE|ER_ENABLE;
+	he_writel_mbox(he_dev, reg, CS_ERCTL0);
+
+	reg = he_readl(he_dev, RC_CONFIG);
+	reg |= RX_ENABLE;
+	he_writel(he_dev, reg, RC_CONFIG);
+
+	for (i = 0; i < HE_NUM_CS_STPER; ++i) {
+		he_dev->cs_stper[i].inuse = 0;
+		he_dev->cs_stper[i].pcr = -1;
+	}
+	he_dev->total_bw = 0;
+
+
+	/* atm linux initialization */
+
+	he_dev->atm_dev->ci_range.vpi_bits = he_dev->vpibits;
+	he_dev->atm_dev->ci_range.vci_bits = he_dev->vcibits;
+
+	he_dev->irq_peak = 0;
+	he_dev->rbrq_peak = 0;
+	he_dev->rbpl_peak = 0;
+	he_dev->tbrq_peak = 0;
+
+	HPRINTK("hell bent for leather!\n");
+
+	return 0;
+}
+
+static void
+he_stop(struct he_dev *he_dev)
+{
+	u16 command;
+	u32 gen_cntl_0, reg;
+	struct pci_dev *pci_dev;
+
+	pci_dev = he_dev->pci_dev;
+
+	/* disable interrupts */
+
+	if (he_dev->membase) {
+		pci_read_config_dword(pci_dev, GEN_CNTL_0, &gen_cntl_0);
+		gen_cntl_0 &= ~(INT_PROC_ENBL | INIT_ENB);
+		pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0);
+
+#ifdef USE_TASKLET
+		tasklet_disable(&he_dev->tasklet);
+#endif
+
+		/* disable recv and transmit */
+
+		reg = he_readl_mbox(he_dev, CS_ERCTL0);
+		reg &= ~(TX_ENABLE|ER_ENABLE);
+		he_writel_mbox(he_dev, reg, CS_ERCTL0);
+
+		reg = he_readl(he_dev, RC_CONFIG);
+		reg &= ~(RX_ENABLE);
+		he_writel(he_dev, reg, RC_CONFIG);
+	}
+
+#ifdef CONFIG_ATM_HE_USE_SUNI
+	if (he_dev->atm_dev->phy && he_dev->atm_dev->phy->stop)
+		he_dev->atm_dev->phy->stop(he_dev->atm_dev);
+#endif /* CONFIG_ATM_HE_USE_SUNI */
+
+	if (he_dev->irq)
+		free_irq(he_dev->irq, he_dev);
+
+	if (he_dev->irq_base)
+		pci_free_consistent(he_dev->pci_dev, (CONFIG_IRQ_SIZE+1)
+			* sizeof(struct he_irq), he_dev->irq_base, he_dev->irq_phys);
+
+	if (he_dev->hsp)
+		pci_free_consistent(he_dev->pci_dev, sizeof(struct he_hsp),
+						he_dev->hsp, he_dev->hsp_phys);
+
+	if (he_dev->rbpl_base) {
+#ifdef USE_RBPL_POOL
+		for (i = 0; i < CONFIG_RBPL_SIZE; ++i) {
+			void *cpuaddr = he_dev->rbpl_virt[i].virt;
+			dma_addr_t dma_handle = he_dev->rbpl_base[i].phys;
+
+			pci_pool_free(he_dev->rbpl_pool, cpuaddr, dma_handle);
+		}
+#else
+		pci_free_consistent(he_dev->pci_dev, CONFIG_RBPL_SIZE
+			* CONFIG_RBPL_BUFSIZE, he_dev->rbpl_pages, he_dev->rbpl_pages_phys);
+#endif
+		pci_free_consistent(he_dev->pci_dev, CONFIG_RBPL_SIZE
+			* sizeof(struct he_rbp), he_dev->rbpl_base, he_dev->rbpl_phys);
+	}
+
+#ifdef USE_RBPL_POOL
+	if (he_dev->rbpl_pool)
+		pci_pool_destroy(he_dev->rbpl_pool);
+#endif
+
+#ifdef USE_RBPS
+	if (he_dev->rbps_base) {
+#ifdef USE_RBPS_POOL
+		for (i = 0; i < CONFIG_RBPS_SIZE; ++i) {
+			void *cpuaddr = he_dev->rbps_virt[i].virt;
+			dma_addr_t dma_handle = he_dev->rbps_base[i].phys;
+
+			pci_pool_free(he_dev->rbps_pool, cpuaddr, dma_handle);
+		}
+#else
+		pci_free_consistent(he_dev->pci_dev, CONFIG_RBPS_SIZE
+			* CONFIG_RBPS_BUFSIZE, he_dev->rbps_pages, he_dev->rbps_pages_phys);
+#endif
+		pci_free_consistent(he_dev->pci_dev, CONFIG_RBPS_SIZE
+			* sizeof(struct he_rbp), he_dev->rbps_base, he_dev->rbps_phys);
+	}
+
+#ifdef USE_RBPS_POOL
+	if (he_dev->rbps_pool)
+		pci_pool_destroy(he_dev->rbps_pool);
+#endif
+
+#endif /* USE_RBPS */
+
+	if (he_dev->rbrq_base)
+		pci_free_consistent(he_dev->pci_dev, CONFIG_RBRQ_SIZE * sizeof(struct he_rbrq),
+							he_dev->rbrq_base, he_dev->rbrq_phys);
+
+	if (he_dev->tbrq_base)
+		pci_free_consistent(he_dev->pci_dev, CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq),
+							he_dev->tbrq_base, he_dev->tbrq_phys);
+
+	if (he_dev->tpdrq_base)
+		pci_free_consistent(he_dev->pci_dev, CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq),
+							he_dev->tpdrq_base, he_dev->tpdrq_phys);
+
+#ifdef USE_TPD_POOL
+	if (he_dev->tpd_pool)
+		pci_pool_destroy(he_dev->tpd_pool);
+#else
+	if (he_dev->tpd_base)
+		pci_free_consistent(he_dev->pci_dev, CONFIG_NUMTPDS * sizeof(struct he_tpd),
+							he_dev->tpd_base, he_dev->tpd_base_phys);
+#endif
+
+	if (he_dev->pci_dev) {
+		pci_read_config_word(he_dev->pci_dev, PCI_COMMAND, &command);
+		command &= ~(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+		pci_write_config_word(he_dev->pci_dev, PCI_COMMAND, command);
+	}
+	
+	if (he_dev->membase)
+		iounmap(he_dev->membase);
+}
+
+static struct he_tpd *
+__alloc_tpd(struct he_dev *he_dev)
+{
+#ifdef USE_TPD_POOL
+	struct he_tpd *tpd;
+	dma_addr_t dma_handle; 
+
+	tpd = pci_pool_alloc(he_dev->tpd_pool, SLAB_ATOMIC|SLAB_DMA, &dma_handle);              
+	if (tpd == NULL)
+		return NULL;
+			
+	tpd->status = TPD_ADDR(dma_handle);
+	tpd->reserved = 0; 
+	tpd->iovec[0].addr = 0; tpd->iovec[0].len = 0;
+	tpd->iovec[1].addr = 0; tpd->iovec[1].len = 0;
+	tpd->iovec[2].addr = 0; tpd->iovec[2].len = 0;
+
+	return tpd;
+#else
+	int i;
+
+	for (i = 0; i < CONFIG_NUMTPDS; ++i) {
+		++he_dev->tpd_head;
+		if (he_dev->tpd_head > he_dev->tpd_end) {
+			he_dev->tpd_head = he_dev->tpd_base;
+		}
+
+		if (!he_dev->tpd_head->inuse) {
+			he_dev->tpd_head->inuse = 1;
+			he_dev->tpd_head->status &= TPD_MASK;
+			he_dev->tpd_head->iovec[0].addr = 0; he_dev->tpd_head->iovec[0].len = 0;
+			he_dev->tpd_head->iovec[1].addr = 0; he_dev->tpd_head->iovec[1].len = 0;
+			he_dev->tpd_head->iovec[2].addr = 0; he_dev->tpd_head->iovec[2].len = 0;
+			return he_dev->tpd_head;
+		}
+	}
+	hprintk("out of tpds -- increase CONFIG_NUMTPDS (%d)\n", CONFIG_NUMTPDS);
+	return NULL;
+#endif
+}
+
+#define AAL5_LEN(buf,len) 						\
+			((((unsigned char *)(buf))[(len)-6] << 8) |	\
+				(((unsigned char *)(buf))[(len)-5]))
+
+/* 2.10.1.2 receive
+ *
+ * aal5 packets can optionally return the tcp checksum in the lower
+ * 16 bits of the crc (RSR0_TCP_CKSUM)
+ */
+
+#define TCP_CKSUM(buf,len) 						\
+			((((unsigned char *)(buf))[(len)-2] << 8) |	\
+				(((unsigned char *)(buf))[(len-1)]))
+
+static int
+he_service_rbrq(struct he_dev *he_dev, int group)
+{
+	struct he_rbrq *rbrq_tail = (struct he_rbrq *)
+				((unsigned long)he_dev->rbrq_base |
+					he_dev->hsp->group[group].rbrq_tail);
+	struct he_rbp *rbp = NULL;
+	unsigned cid, lastcid = -1;
+	unsigned buf_len = 0;
+	struct sk_buff *skb;
+	struct atm_vcc *vcc = NULL;
+	struct he_vcc *he_vcc;
+	struct he_iovec *iov;
+	int pdus_assembled = 0;
+	int updated = 0;
+
+	read_lock(&vcc_sklist_lock);
+	while (he_dev->rbrq_head != rbrq_tail) {
+		++updated;
+
+		HPRINTK("%p rbrq%d 0x%x len=%d cid=0x%x %s%s%s%s%s%s\n",
+			he_dev->rbrq_head, group,
+			RBRQ_ADDR(he_dev->rbrq_head),
+			RBRQ_BUFLEN(he_dev->rbrq_head),
+			RBRQ_CID(he_dev->rbrq_head),
+			RBRQ_CRC_ERR(he_dev->rbrq_head) ? " CRC_ERR" : "",
+			RBRQ_LEN_ERR(he_dev->rbrq_head) ? " LEN_ERR" : "",
+			RBRQ_END_PDU(he_dev->rbrq_head) ? " END_PDU" : "",
+			RBRQ_AAL5_PROT(he_dev->rbrq_head) ? " AAL5_PROT" : "",
+			RBRQ_CON_CLOSED(he_dev->rbrq_head) ? " CON_CLOSED" : "",
+			RBRQ_HBUF_ERR(he_dev->rbrq_head) ? " HBUF_ERR" : "");
+
+#ifdef USE_RBPS
+		if (RBRQ_ADDR(he_dev->rbrq_head) & RBP_SMALLBUF)
+			rbp = &he_dev->rbps_base[RBP_INDEX(RBRQ_ADDR(he_dev->rbrq_head))];
+		else
+#endif
+			rbp = &he_dev->rbpl_base[RBP_INDEX(RBRQ_ADDR(he_dev->rbrq_head))];
+		
+		buf_len = RBRQ_BUFLEN(he_dev->rbrq_head) * 4;
+		cid = RBRQ_CID(he_dev->rbrq_head);
+
+		if (cid != lastcid)
+			vcc = __find_vcc(he_dev, cid);
+		lastcid = cid;
+
+		if (vcc == NULL) {
+			hprintk("vcc == NULL  (cid 0x%x)\n", cid);
+			if (!RBRQ_HBUF_ERR(he_dev->rbrq_head))
+					rbp->status &= ~RBP_LOANED;
+					
+			goto next_rbrq_entry;
+		}
+
+		he_vcc = HE_VCC(vcc);
+		if (he_vcc == NULL) {
+			hprintk("he_vcc == NULL  (cid 0x%x)\n", cid);
+			if (!RBRQ_HBUF_ERR(he_dev->rbrq_head))
+					rbp->status &= ~RBP_LOANED;
+			goto next_rbrq_entry;
+		}
+
+		if (RBRQ_HBUF_ERR(he_dev->rbrq_head)) {
+			hprintk("HBUF_ERR!  (cid 0x%x)\n", cid);
+				atomic_inc(&vcc->stats->rx_drop);
+			goto return_host_buffers;
+		}
+
+		he_vcc->iov_tail->iov_base = RBRQ_ADDR(he_dev->rbrq_head);
+		he_vcc->iov_tail->iov_len = buf_len;
+		he_vcc->pdu_len += buf_len;
+		++he_vcc->iov_tail;
+
+		if (RBRQ_CON_CLOSED(he_dev->rbrq_head)) {
+			lastcid = -1;
+			HPRINTK("wake_up rx_waitq  (cid 0x%x)\n", cid);
+			wake_up(&he_vcc->rx_waitq);
+			goto return_host_buffers;
+		}
+
+#ifdef notdef
+		if ((he_vcc->iov_tail - he_vcc->iov_head) > HE_MAXIOV) {
+			hprintk("iovec full!  cid 0x%x\n", cid);
+			goto return_host_buffers;
+		}
+#endif
+		if (!RBRQ_END_PDU(he_dev->rbrq_head))
+			goto next_rbrq_entry;
+
+		if (RBRQ_LEN_ERR(he_dev->rbrq_head)
+				|| RBRQ_CRC_ERR(he_dev->rbrq_head)) {
+			HPRINTK("%s%s (%d.%d)\n",
+				RBRQ_CRC_ERR(he_dev->rbrq_head)
+							? "CRC_ERR " : "",
+				RBRQ_LEN_ERR(he_dev->rbrq_head)
+							? "LEN_ERR" : "",
+							vcc->vpi, vcc->vci);
+			atomic_inc(&vcc->stats->rx_err);
+			goto return_host_buffers;
+		}
+
+		skb = atm_alloc_charge(vcc, he_vcc->pdu_len + rx_skb_reserve,
+							GFP_ATOMIC);
+		if (!skb) {
+			HPRINTK("charge failed (%d.%d)\n", vcc->vpi, vcc->vci);
+			goto return_host_buffers;
+		}
+
+		if (rx_skb_reserve > 0)
+			skb_reserve(skb, rx_skb_reserve);
+
+		do_gettimeofday(&skb->stamp);
+
+		for (iov = he_vcc->iov_head;
+				iov < he_vcc->iov_tail; ++iov) {
+#ifdef USE_RBPS
+			if (iov->iov_base & RBP_SMALLBUF)
+				memcpy(skb_put(skb, iov->iov_len),
+					he_dev->rbps_virt[RBP_INDEX(iov->iov_base)].virt, iov->iov_len);
+			else
+#endif
+				memcpy(skb_put(skb, iov->iov_len),
+					he_dev->rbpl_virt[RBP_INDEX(iov->iov_base)].virt, iov->iov_len);
+		}
+
+		switch (vcc->qos.aal) {
+			case ATM_AAL0:
+				/* 2.10.1.5 raw cell receive */
+				skb->len = ATM_AAL0_SDU;
+				skb->tail = skb->data + skb->len;
+				break;
+			case ATM_AAL5:
+				/* 2.10.1.2 aal5 receive */
+
+				skb->len = AAL5_LEN(skb->data, he_vcc->pdu_len);
+				skb->tail = skb->data + skb->len;
+#ifdef USE_CHECKSUM_HW
+				if (vcc->vpi == 0 && vcc->vci >= ATM_NOT_RSV_VCI) {
+					skb->ip_summed = CHECKSUM_HW;
+					skb->csum = TCP_CKSUM(skb->data,
+							he_vcc->pdu_len);
+				}
+#endif
+				break;
+		}
+
+#ifdef should_never_happen
+		if (skb->len > vcc->qos.rxtp.max_sdu)
+			hprintk("pdu_len (%d) > vcc->qos.rxtp.max_sdu (%d)!  cid 0x%x\n", skb->len, vcc->qos.rxtp.max_sdu, cid);
+#endif
+
+#ifdef notdef
+		ATM_SKB(skb)->vcc = vcc;
+#endif
+		vcc->push(vcc, skb);
+
+		atomic_inc(&vcc->stats->rx);
+
+return_host_buffers:
+		++pdus_assembled;
+
+		for (iov = he_vcc->iov_head;
+				iov < he_vcc->iov_tail; ++iov) {
+#ifdef USE_RBPS
+			if (iov->iov_base & RBP_SMALLBUF)
+				rbp = &he_dev->rbps_base[RBP_INDEX(iov->iov_base)];
+			else
+#endif
+				rbp = &he_dev->rbpl_base[RBP_INDEX(iov->iov_base)];
+
+			rbp->status &= ~RBP_LOANED;
+		}
+
+		he_vcc->iov_tail = he_vcc->iov_head;
+		he_vcc->pdu_len = 0;
+
+next_rbrq_entry:
+		he_dev->rbrq_head = (struct he_rbrq *)
+				((unsigned long) he_dev->rbrq_base |
+					RBRQ_MASK(++he_dev->rbrq_head));
+
+	}
+	read_unlock(&vcc_sklist_lock);
+
+	if (updated) {
+		if (updated > he_dev->rbrq_peak)
+			he_dev->rbrq_peak = updated;
+
+		he_writel(he_dev, RBRQ_MASK(he_dev->rbrq_head),
+						G0_RBRQ_H + (group * 16));
+	}
+
+	return pdus_assembled;
+}
+
+static void
+he_service_tbrq(struct he_dev *he_dev, int group)
+{
+	struct he_tbrq *tbrq_tail = (struct he_tbrq *)
+				((unsigned long)he_dev->tbrq_base |
+					he_dev->hsp->group[group].tbrq_tail);
+	struct he_tpd *tpd;
+	int slot, updated = 0;
+#ifdef USE_TPD_POOL
+	struct he_tpd *__tpd;
+#endif
+
+	/* 2.1.6 transmit buffer return queue */
+
+	while (he_dev->tbrq_head != tbrq_tail) {
+		++updated;
+
+		HPRINTK("tbrq%d 0x%x%s%s\n",
+			group,
+			TBRQ_TPD(he_dev->tbrq_head), 
+			TBRQ_EOS(he_dev->tbrq_head) ? " EOS" : "",
+			TBRQ_MULTIPLE(he_dev->tbrq_head) ? " MULTIPLE" : "");
+#ifdef USE_TPD_POOL
+		tpd = NULL;
+		list_for_each_entry(__tpd, &he_dev->outstanding_tpds, entry) {
+			if (TPD_ADDR(__tpd->status) == TBRQ_TPD(he_dev->tbrq_head)) {
+				tpd = __tpd;
+				list_del(&__tpd->entry);
+				break;
+			}
+		}
+
+		if (tpd == NULL) {
+			hprintk("unable to locate tpd for dma buffer %x\n",
+						TBRQ_TPD(he_dev->tbrq_head));
+			goto next_tbrq_entry;
+		}
+#else
+		tpd = &he_dev->tpd_base[ TPD_INDEX(TBRQ_TPD(he_dev->tbrq_head)) ];
+#endif
+
+		if (TBRQ_EOS(he_dev->tbrq_head)) {
+			HPRINTK("wake_up(tx_waitq) cid 0x%x\n",
+				he_mkcid(he_dev, tpd->vcc->vpi, tpd->vcc->vci));
+			if (tpd->vcc)
+				wake_up(&HE_VCC(tpd->vcc)->tx_waitq);
+
+			goto next_tbrq_entry;
+		}
+
+		for (slot = 0; slot < TPD_MAXIOV; ++slot) {
+			if (tpd->iovec[slot].addr)
+				pci_unmap_single(he_dev->pci_dev,
+					tpd->iovec[slot].addr,
+					tpd->iovec[slot].len & TPD_LEN_MASK,
+							PCI_DMA_TODEVICE);
+			if (tpd->iovec[slot].len & TPD_LST)
+				break;
+				
+		}
+
+		if (tpd->skb) {	/* && !TBRQ_MULTIPLE(he_dev->tbrq_head) */
+			if (tpd->vcc && tpd->vcc->pop)
+				tpd->vcc->pop(tpd->vcc, tpd->skb);
+			else
+				dev_kfree_skb_any(tpd->skb);
+		}
+
+next_tbrq_entry:
+#ifdef USE_TPD_POOL
+		if (tpd)
+			pci_pool_free(he_dev->tpd_pool, tpd, TPD_ADDR(tpd->status));
+#else
+		tpd->inuse = 0;
+#endif
+		he_dev->tbrq_head = (struct he_tbrq *)
+				((unsigned long) he_dev->tbrq_base |
+					TBRQ_MASK(++he_dev->tbrq_head));
+	}
+
+	if (updated) {
+		if (updated > he_dev->tbrq_peak)
+			he_dev->tbrq_peak = updated;
+
+		he_writel(he_dev, TBRQ_MASK(he_dev->tbrq_head),
+						G0_TBRQ_H + (group * 16));
+	}
+}
+
+
+static void
+he_service_rbpl(struct he_dev *he_dev, int group)
+{
+	struct he_rbp *newtail;
+	struct he_rbp *rbpl_head;
+	int moved = 0;
+
+	rbpl_head = (struct he_rbp *) ((unsigned long)he_dev->rbpl_base |
+					RBPL_MASK(he_readl(he_dev, G0_RBPL_S)));
+
+	for (;;) {
+		newtail = (struct he_rbp *) ((unsigned long)he_dev->rbpl_base |
+						RBPL_MASK(he_dev->rbpl_tail+1));
+
+		/* table 3.42 -- rbpl_tail should never be set to rbpl_head */
+		if ((newtail == rbpl_head) || (newtail->status & RBP_LOANED))
+			break;
+
+		newtail->status |= RBP_LOANED;
+		he_dev->rbpl_tail = newtail;
+		++moved;
+	} 
+
+	if (moved)
+		he_writel(he_dev, RBPL_MASK(he_dev->rbpl_tail), G0_RBPL_T);
+}
+
+#ifdef USE_RBPS
+static void
+he_service_rbps(struct he_dev *he_dev, int group)
+{
+	struct he_rbp *newtail;
+	struct he_rbp *rbps_head;
+	int moved = 0;
+
+	rbps_head = (struct he_rbp *) ((unsigned long)he_dev->rbps_base |
+					RBPS_MASK(he_readl(he_dev, G0_RBPS_S)));
+
+	for (;;) {
+		newtail = (struct he_rbp *) ((unsigned long)he_dev->rbps_base |
+						RBPS_MASK(he_dev->rbps_tail+1));
+
+		/* table 3.42 -- rbps_tail should never be set to rbps_head */
+		if ((newtail == rbps_head) || (newtail->status & RBP_LOANED))
+			break;
+
+		newtail->status |= RBP_LOANED;
+		he_dev->rbps_tail = newtail;
+		++moved;
+	} 
+
+	if (moved)
+		he_writel(he_dev, RBPS_MASK(he_dev->rbps_tail), G0_RBPS_T);
+}
+#endif /* USE_RBPS */
+
+static void
+he_tasklet(unsigned long data)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = (struct he_dev *) data;
+	int group, type;
+	int updated = 0;
+
+	HPRINTK("tasklet (0x%lx)\n", data);
+#ifdef USE_TASKLET
+	spin_lock_irqsave(&he_dev->global_lock, flags);
+#endif
+
+	while (he_dev->irq_head != he_dev->irq_tail) {
+		++updated;
+
+		type = ITYPE_TYPE(he_dev->irq_head->isw);
+		group = ITYPE_GROUP(he_dev->irq_head->isw);
+
+		switch (type) {
+			case ITYPE_RBRQ_THRESH:
+				HPRINTK("rbrq%d threshold\n", group);
+				/* fall through */
+			case ITYPE_RBRQ_TIMER:
+				if (he_service_rbrq(he_dev, group)) {
+					he_service_rbpl(he_dev, group);
+#ifdef USE_RBPS
+					he_service_rbps(he_dev, group);
+#endif /* USE_RBPS */
+				}
+				break;
+			case ITYPE_TBRQ_THRESH:
+				HPRINTK("tbrq%d threshold\n", group);
+				/* fall through */
+			case ITYPE_TPD_COMPLETE:
+				he_service_tbrq(he_dev, group);
+				break;
+			case ITYPE_RBPL_THRESH:
+				he_service_rbpl(he_dev, group);
+				break;
+			case ITYPE_RBPS_THRESH:
+#ifdef USE_RBPS
+				he_service_rbps(he_dev, group);
+#endif /* USE_RBPS */
+				break;
+			case ITYPE_PHY:
+				HPRINTK("phy interrupt\n");
+#ifdef CONFIG_ATM_HE_USE_SUNI
+				spin_unlock_irqrestore(&he_dev->global_lock, flags);
+				if (he_dev->atm_dev->phy && he_dev->atm_dev->phy->interrupt)
+					he_dev->atm_dev->phy->interrupt(he_dev->atm_dev);
+				spin_lock_irqsave(&he_dev->global_lock, flags);
+#endif
+				break;
+			case ITYPE_OTHER:
+				switch (type|group) {
+					case ITYPE_PARITY:
+						hprintk("parity error\n");
+						break;
+					case ITYPE_ABORT:
+						hprintk("abort 0x%x\n", he_readl(he_dev, ABORT_ADDR));
+						break;
+				}
+				break;
+			case ITYPE_TYPE(ITYPE_INVALID):
+				/* see 8.1.1 -- check all queues */
+
+				HPRINTK("isw not updated 0x%x\n", he_dev->irq_head->isw);
+
+				he_service_rbrq(he_dev, 0);
+				he_service_rbpl(he_dev, 0);
+#ifdef USE_RBPS
+				he_service_rbps(he_dev, 0);
+#endif /* USE_RBPS */
+				he_service_tbrq(he_dev, 0);
+				break;
+			default:
+				hprintk("bad isw 0x%x?\n", he_dev->irq_head->isw);
+		}
+
+		he_dev->irq_head->isw = ITYPE_INVALID;
+
+		he_dev->irq_head = (struct he_irq *) NEXT_ENTRY(he_dev->irq_base, he_dev->irq_head, IRQ_MASK);
+	}
+
+	if (updated) {
+		if (updated > he_dev->irq_peak)
+			he_dev->irq_peak = updated;
+
+		he_writel(he_dev,
+			IRQ_SIZE(CONFIG_IRQ_SIZE) |
+			IRQ_THRESH(CONFIG_IRQ_THRESH) |
+			IRQ_TAIL(he_dev->irq_tail), IRQ0_HEAD);
+		(void) he_readl(he_dev, INT_FIFO); /* 8.1.2 controller errata; flush posted writes */
+	}
+#ifdef USE_TASKLET
+	spin_unlock_irqrestore(&he_dev->global_lock, flags);
+#endif
+}
+
+static irqreturn_t
+he_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = (struct he_dev * )dev_id;
+	int handled = 0;
+
+	if (he_dev == NULL)
+		return IRQ_NONE;
+
+	spin_lock_irqsave(&he_dev->global_lock, flags);
+
+	he_dev->irq_tail = (struct he_irq *) (((unsigned long)he_dev->irq_base) |
+						(*he_dev->irq_tailoffset << 2));
+
+	if (he_dev->irq_tail == he_dev->irq_head) {
+		HPRINTK("tailoffset not updated?\n");
+		he_dev->irq_tail = (struct he_irq *) ((unsigned long)he_dev->irq_base |
+			((he_readl(he_dev, IRQ0_BASE) & IRQ_MASK) << 2));
+		(void) he_readl(he_dev, INT_FIFO);	/* 8.1.2 controller errata */
+	}
+
+#ifdef DEBUG
+	if (he_dev->irq_head == he_dev->irq_tail /* && !IRQ_PENDING */)
+		hprintk("spurious (or shared) interrupt?\n");
+#endif
+
+	if (he_dev->irq_head != he_dev->irq_tail) {
+		handled = 1;
+#ifdef USE_TASKLET
+		tasklet_schedule(&he_dev->tasklet);
+#else
+		he_tasklet((unsigned long) he_dev);
+#endif
+		he_writel(he_dev, INT_CLEAR_A, INT_FIFO);	/* clear interrupt */
+		(void) he_readl(he_dev, INT_FIFO);		/* flush posted writes */
+	}
+	spin_unlock_irqrestore(&he_dev->global_lock, flags);
+	return IRQ_RETVAL(handled);
+
+}
+
+static __inline__ void
+__enqueue_tpd(struct he_dev *he_dev, struct he_tpd *tpd, unsigned cid)
+{
+	struct he_tpdrq *new_tail;
+
+	HPRINTK("tpdrq %p cid 0x%x -> tpdrq_tail %p\n",
+					tpd, cid, he_dev->tpdrq_tail);
+
+	/* new_tail = he_dev->tpdrq_tail; */
+	new_tail = (struct he_tpdrq *) ((unsigned long) he_dev->tpdrq_base |
+					TPDRQ_MASK(he_dev->tpdrq_tail+1));
+
+	/*
+	 * check to see if we are about to set the tail == head
+	 * if true, update the head pointer from the adapter
+	 * to see if this is really the case (reading the queue
+	 * head for every enqueue would be unnecessarily slow)
+	 */
+
+	if (new_tail == he_dev->tpdrq_head) {
+		he_dev->tpdrq_head = (struct he_tpdrq *)
+			(((unsigned long)he_dev->tpdrq_base) |
+				TPDRQ_MASK(he_readl(he_dev, TPDRQ_B_H)));
+
+		if (new_tail == he_dev->tpdrq_head) {
+			hprintk("tpdrq full (cid 0x%x)\n", cid);
+			/*
+			 * FIXME
+			 * push tpd onto a transmit backlog queue
+			 * after service_tbrq, service the backlog
+			 * for now, we just drop the pdu
+			 */
+			if (tpd->skb) {
+				if (tpd->vcc->pop)
+					tpd->vcc->pop(tpd->vcc, tpd->skb);
+				else
+					dev_kfree_skb_any(tpd->skb);
+				atomic_inc(&tpd->vcc->stats->tx_err);
+			}
+#ifdef USE_TPD_POOL
+			pci_pool_free(he_dev->tpd_pool, tpd, TPD_ADDR(tpd->status));
+#else
+			tpd->inuse = 0;
+#endif
+			return;
+		}
+	}
+
+	/* 2.1.5 transmit packet descriptor ready queue */
+#ifdef USE_TPD_POOL
+	list_add_tail(&tpd->entry, &he_dev->outstanding_tpds);
+	he_dev->tpdrq_tail->tpd = TPD_ADDR(tpd->status);
+#else
+	he_dev->tpdrq_tail->tpd = he_dev->tpd_base_phys +
+				(TPD_INDEX(tpd->status) * sizeof(struct he_tpd));
+#endif
+	he_dev->tpdrq_tail->cid = cid;
+	wmb();
+
+	he_dev->tpdrq_tail = new_tail;
+
+	he_writel(he_dev, TPDRQ_MASK(he_dev->tpdrq_tail), TPDRQ_T);
+	(void) he_readl(he_dev, TPDRQ_T);		/* flush posted writes */
+}
+
+static int
+he_open(struct atm_vcc *vcc)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = HE_DEV(vcc->dev);
+	struct he_vcc *he_vcc;
+	int err = 0;
+	unsigned cid, rsr0, rsr1, rsr4, tsr0, tsr0_aal, tsr4, period, reg, clock;
+	short vpi = vcc->vpi;
+	int vci = vcc->vci;
+
+	if (vci == ATM_VCI_UNSPEC || vpi == ATM_VPI_UNSPEC)
+		return 0;
+
+	HPRINTK("open vcc %p %d.%d\n", vcc, vpi, vci);
+
+	set_bit(ATM_VF_ADDR, &vcc->flags);
+
+	cid = he_mkcid(he_dev, vpi, vci);
+
+	he_vcc = (struct he_vcc *) kmalloc(sizeof(struct he_vcc), GFP_ATOMIC);
+	if (he_vcc == NULL) {
+		hprintk("unable to allocate he_vcc during open\n");
+		return -ENOMEM;
+	}
+
+	he_vcc->iov_tail = he_vcc->iov_head;
+	he_vcc->pdu_len = 0;
+	he_vcc->rc_index = -1;
+
+	init_waitqueue_head(&he_vcc->rx_waitq);
+	init_waitqueue_head(&he_vcc->tx_waitq);
+
+	vcc->dev_data = he_vcc;
+
+	if (vcc->qos.txtp.traffic_class != ATM_NONE) {
+		int pcr_goal;
+
+		pcr_goal = atm_pcr_goal(&vcc->qos.txtp);
+		if (pcr_goal == 0)
+			pcr_goal = he_dev->atm_dev->link_rate;
+		if (pcr_goal < 0)	/* means round down, technically */
+			pcr_goal = -pcr_goal;
+
+		HPRINTK("open tx cid 0x%x pcr_goal %d\n", cid, pcr_goal);
+
+		switch (vcc->qos.aal) {
+			case ATM_AAL5:
+				tsr0_aal = TSR0_AAL5;
+				tsr4 = TSR4_AAL5;
+				break;
+			case ATM_AAL0:
+				tsr0_aal = TSR0_AAL0_SDU;
+				tsr4 = TSR4_AAL0_SDU;
+				break;
+			default:
+				err = -EINVAL;
+				goto open_failed;
+		}
+
+		spin_lock_irqsave(&he_dev->global_lock, flags);
+		tsr0 = he_readl_tsr0(he_dev, cid);
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+		if (TSR0_CONN_STATE(tsr0) != 0) {
+			hprintk("cid 0x%x not idle (tsr0 = 0x%x)\n", cid, tsr0);
+			err = -EBUSY;
+			goto open_failed;
+		}
+
+		switch (vcc->qos.txtp.traffic_class) {
+			case ATM_UBR:
+				/* 2.3.3.1 open connection ubr */
+
+				tsr0 = TSR0_UBR | TSR0_GROUP(0) | tsr0_aal |
+					TSR0_USE_WMIN | TSR0_UPDATE_GER;
+				break;
+
+			case ATM_CBR:
+				/* 2.3.3.2 open connection cbr */
+
+				/* 8.2.3 cbr scheduler wrap problem -- limit to 90% total link rate */
+				if ((he_dev->total_bw + pcr_goal)
+					> (he_dev->atm_dev->link_rate * 9 / 10))
+				{
+					err = -EBUSY;
+					goto open_failed;
+				}
+
+				spin_lock_irqsave(&he_dev->global_lock, flags);			/* also protects he_dev->cs_stper[] */
+
+				/* find an unused cs_stper register */
+				for (reg = 0; reg < HE_NUM_CS_STPER; ++reg)
+					if (he_dev->cs_stper[reg].inuse == 0 || 
+					    he_dev->cs_stper[reg].pcr == pcr_goal)
+							break;
+
+				if (reg == HE_NUM_CS_STPER) {
+					err = -EBUSY;
+					spin_unlock_irqrestore(&he_dev->global_lock, flags);
+					goto open_failed;
+				}
+
+				he_dev->total_bw += pcr_goal;
+
+				he_vcc->rc_index = reg;
+				++he_dev->cs_stper[reg].inuse;
+				he_dev->cs_stper[reg].pcr = pcr_goal;
+
+				clock = he_is622(he_dev) ? 66667000 : 50000000;
+				period = clock / pcr_goal;
+				
+				HPRINTK("rc_index = %d period = %d\n",
+								reg, period);
+
+				he_writel_mbox(he_dev, rate_to_atmf(period/2),
+							CS_STPER0 + reg);
+				spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+				tsr0 = TSR0_CBR | TSR0_GROUP(0) | tsr0_aal |
+							TSR0_RC_INDEX(reg);
+
+				break;
+			default:
+				err = -EINVAL;
+				goto open_failed;
+		}
+
+		spin_lock_irqsave(&he_dev->global_lock, flags);
+
+		he_writel_tsr0(he_dev, tsr0, cid);
+		he_writel_tsr4(he_dev, tsr4 | 1, cid);
+		he_writel_tsr1(he_dev, TSR1_MCR(rate_to_atmf(0)) |
+					TSR1_PCR(rate_to_atmf(pcr_goal)), cid);
+		he_writel_tsr2(he_dev, TSR2_ACR(rate_to_atmf(pcr_goal)), cid);
+		he_writel_tsr9(he_dev, TSR9_OPEN_CONN, cid);
+
+		he_writel_tsr3(he_dev, 0x0, cid);
+		he_writel_tsr5(he_dev, 0x0, cid);
+		he_writel_tsr6(he_dev, 0x0, cid);
+		he_writel_tsr7(he_dev, 0x0, cid);
+		he_writel_tsr8(he_dev, 0x0, cid);
+		he_writel_tsr10(he_dev, 0x0, cid);
+		he_writel_tsr11(he_dev, 0x0, cid);
+		he_writel_tsr12(he_dev, 0x0, cid);
+		he_writel_tsr13(he_dev, 0x0, cid);
+		he_writel_tsr14(he_dev, 0x0, cid);
+		(void) he_readl_tsr0(he_dev, cid);		/* flush posted writes */
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+	}
+
+	if (vcc->qos.rxtp.traffic_class != ATM_NONE) {
+		unsigned aal;
+
+		HPRINTK("open rx cid 0x%x (rx_waitq %p)\n", cid,
+		 				&HE_VCC(vcc)->rx_waitq);
+
+		switch (vcc->qos.aal) {
+			case ATM_AAL5:
+				aal = RSR0_AAL5;
+				break;
+			case ATM_AAL0:
+				aal = RSR0_RAWCELL;
+				break;
+			default:
+				err = -EINVAL;
+				goto open_failed;
+		}
+
+		spin_lock_irqsave(&he_dev->global_lock, flags);
+
+		rsr0 = he_readl_rsr0(he_dev, cid);
+		if (rsr0 & RSR0_OPEN_CONN) {
+			spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+			hprintk("cid 0x%x not idle (rsr0 = 0x%x)\n", cid, rsr0);
+			err = -EBUSY;
+			goto open_failed;
+		}
+
+#ifdef USE_RBPS
+		rsr1 = RSR1_GROUP(0);
+		rsr4 = RSR4_GROUP(0);
+#else /* !USE_RBPS */
+		rsr1 = RSR1_GROUP(0)|RSR1_RBPL_ONLY;
+		rsr4 = RSR4_GROUP(0)|RSR4_RBPL_ONLY;
+#endif /* USE_RBPS */
+		rsr0 = vcc->qos.rxtp.traffic_class == ATM_UBR ? 
+				(RSR0_EPD_ENABLE|RSR0_PPD_ENABLE) : 0;
+
+#ifdef USE_CHECKSUM_HW
+		if (vpi == 0 && vci >= ATM_NOT_RSV_VCI)
+			rsr0 |= RSR0_TCP_CKSUM;
+#endif
+
+		he_writel_rsr4(he_dev, rsr4, cid);
+		he_writel_rsr1(he_dev, rsr1, cid);
+		/* 5.1.11 last parameter initialized should be
+			  the open/closed indication in rsr0 */
+		he_writel_rsr0(he_dev,
+			rsr0 | RSR0_START_PDU | RSR0_OPEN_CONN | aal, cid);
+		(void) he_readl_rsr0(he_dev, cid);		/* flush posted writes */
+
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+	}
+
+open_failed:
+
+	if (err) {
+		if (he_vcc)
+			kfree(he_vcc);
+		clear_bit(ATM_VF_ADDR, &vcc->flags);
+	}
+	else
+		set_bit(ATM_VF_READY, &vcc->flags);
+
+	return err;
+}
+
+static void
+he_close(struct atm_vcc *vcc)
+{
+	unsigned long flags;
+	DECLARE_WAITQUEUE(wait, current);
+	struct he_dev *he_dev = HE_DEV(vcc->dev);
+	struct he_tpd *tpd;
+	unsigned cid;
+	struct he_vcc *he_vcc = HE_VCC(vcc);
+#define MAX_RETRY 30
+	int retry = 0, sleep = 1, tx_inuse;
+
+	HPRINTK("close vcc %p %d.%d\n", vcc, vcc->vpi, vcc->vci);
+
+	clear_bit(ATM_VF_READY, &vcc->flags);
+	cid = he_mkcid(he_dev, vcc->vpi, vcc->vci);
+
+	if (vcc->qos.rxtp.traffic_class != ATM_NONE) {
+		int timeout;
+
+		HPRINTK("close rx cid 0x%x\n", cid);
+
+		/* 2.7.2.2 close receive operation */
+
+		/* wait for previous close (if any) to finish */
+
+		spin_lock_irqsave(&he_dev->global_lock, flags);
+		while (he_readl(he_dev, RCC_STAT) & RCC_BUSY) {
+			HPRINTK("close cid 0x%x RCC_BUSY\n", cid);
+			udelay(250);
+		}
+
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		add_wait_queue(&he_vcc->rx_waitq, &wait);
+
+		he_writel_rsr0(he_dev, RSR0_CLOSE_CONN, cid);
+		(void) he_readl_rsr0(he_dev, cid);		/* flush posted writes */
+		he_writel_mbox(he_dev, cid, RXCON_CLOSE);
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+		timeout = schedule_timeout(30*HZ);
+
+		remove_wait_queue(&he_vcc->rx_waitq, &wait);
+		set_current_state(TASK_RUNNING);
+
+		if (timeout == 0)
+			hprintk("close rx timeout cid 0x%x\n", cid);
+
+		HPRINTK("close rx cid 0x%x complete\n", cid);
+
+	}
+
+	if (vcc->qos.txtp.traffic_class != ATM_NONE) {
+		volatile unsigned tsr4, tsr0;
+		int timeout;
+
+		HPRINTK("close tx cid 0x%x\n", cid);
+		
+		/* 2.1.2
+		 *
+		 * ... the host must first stop queueing packets to the TPDRQ
+		 * on the connection to be closed, then wait for all outstanding
+		 * packets to be transmitted and their buffers returned to the
+		 * TBRQ. When the last packet on the connection arrives in the
+		 * TBRQ, the host issues the close command to the adapter.
+		 */
+
+		while (((tx_inuse = atomic_read(&sk_atm(vcc)->sk_wmem_alloc)) > 0) &&
+		       (retry < MAX_RETRY)) {
+			msleep(sleep);
+			if (sleep < 250)
+				sleep = sleep * 2;
+
+			++retry;
+		}
+
+		if (tx_inuse)
+			hprintk("close tx cid 0x%x tx_inuse = %d\n", cid, tx_inuse);
+
+		/* 2.3.1.1 generic close operations with flush */
+
+		spin_lock_irqsave(&he_dev->global_lock, flags);
+		he_writel_tsr4_upper(he_dev, TSR4_FLUSH_CONN, cid);
+					/* also clears TSR4_SESSION_ENDED */
+
+		switch (vcc->qos.txtp.traffic_class) {
+			case ATM_UBR:
+				he_writel_tsr1(he_dev, 
+					TSR1_MCR(rate_to_atmf(200000))
+					| TSR1_PCR(0), cid);
+				break;
+			case ATM_CBR:
+				he_writel_tsr14_upper(he_dev, TSR14_DELETE, cid);
+				break;
+		}
+		(void) he_readl_tsr4(he_dev, cid);		/* flush posted writes */
+
+		tpd = __alloc_tpd(he_dev);
+		if (tpd == NULL) {
+			hprintk("close tx he_alloc_tpd failed cid 0x%x\n", cid);
+			goto close_tx_incomplete;
+		}
+		tpd->status |= TPD_EOS | TPD_INT;
+		tpd->skb = NULL;
+		tpd->vcc = vcc;
+		wmb();
+
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		add_wait_queue(&he_vcc->tx_waitq, &wait);
+		__enqueue_tpd(he_dev, tpd, cid);
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+		timeout = schedule_timeout(30*HZ);
+
+		remove_wait_queue(&he_vcc->tx_waitq, &wait);
+		set_current_state(TASK_RUNNING);
+
+		spin_lock_irqsave(&he_dev->global_lock, flags);
+
+		if (timeout == 0) {
+			hprintk("close tx timeout cid 0x%x\n", cid);
+			goto close_tx_incomplete;
+		}
+
+		while (!((tsr4 = he_readl_tsr4(he_dev, cid)) & TSR4_SESSION_ENDED)) {
+			HPRINTK("close tx cid 0x%x !TSR4_SESSION_ENDED (tsr4 = 0x%x)\n", cid, tsr4);
+			udelay(250);
+		}
+
+		while (TSR0_CONN_STATE(tsr0 = he_readl_tsr0(he_dev, cid)) != 0) {
+			HPRINTK("close tx cid 0x%x TSR0_CONN_STATE != 0 (tsr0 = 0x%x)\n", cid, tsr0);
+			udelay(250);
+		}
+
+close_tx_incomplete:
+
+		if (vcc->qos.txtp.traffic_class == ATM_CBR) {
+			int reg = he_vcc->rc_index;
+
+			HPRINTK("cs_stper reg = %d\n", reg);
+
+			if (he_dev->cs_stper[reg].inuse == 0)
+				hprintk("cs_stper[%d].inuse = 0!\n", reg);
+			else
+				--he_dev->cs_stper[reg].inuse;
+
+			he_dev->total_bw -= he_dev->cs_stper[reg].pcr;
+		}
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+		HPRINTK("close tx cid 0x%x complete\n", cid);
+	}
+
+	kfree(he_vcc);
+
+	clear_bit(ATM_VF_ADDR, &vcc->flags);
+}
+
+static int
+he_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = HE_DEV(vcc->dev);
+	unsigned cid = he_mkcid(he_dev, vcc->vpi, vcc->vci);
+	struct he_tpd *tpd;
+#ifdef USE_SCATTERGATHER
+	int i, slot = 0;
+#endif
+
+#define HE_TPD_BUFSIZE 0xffff
+
+	HPRINTK("send %d.%d\n", vcc->vpi, vcc->vci);
+
+	if ((skb->len > HE_TPD_BUFSIZE) ||
+	    ((vcc->qos.aal == ATM_AAL0) && (skb->len != ATM_AAL0_SDU))) {
+		hprintk("buffer too large (or small) -- %d bytes\n", skb->len );
+		if (vcc->pop)
+			vcc->pop(vcc, skb);
+		else
+			dev_kfree_skb_any(skb);
+		atomic_inc(&vcc->stats->tx_err);
+		return -EINVAL;
+	}
+
+#ifndef USE_SCATTERGATHER
+	if (skb_shinfo(skb)->nr_frags) {
+		hprintk("no scatter/gather support\n");
+		if (vcc->pop)
+			vcc->pop(vcc, skb);
+		else
+			dev_kfree_skb_any(skb);
+		atomic_inc(&vcc->stats->tx_err);
+		return -EINVAL;
+	}
+#endif
+	spin_lock_irqsave(&he_dev->global_lock, flags);
+
+	tpd = __alloc_tpd(he_dev);
+	if (tpd == NULL) {
+		if (vcc->pop)
+			vcc->pop(vcc, skb);
+		else
+			dev_kfree_skb_any(skb);
+		atomic_inc(&vcc->stats->tx_err);
+		spin_unlock_irqrestore(&he_dev->global_lock, flags);
+		return -ENOMEM;
+	}
+
+	if (vcc->qos.aal == ATM_AAL5)
+		tpd->status |= TPD_CELLTYPE(TPD_USERCELL);
+	else {
+		char *pti_clp = (void *) (skb->data + 3);
+		int clp, pti;
+
+		pti = (*pti_clp & ATM_HDR_PTI_MASK) >> ATM_HDR_PTI_SHIFT; 
+		clp = (*pti_clp & ATM_HDR_CLP);
+		tpd->status |= TPD_CELLTYPE(pti);
+		if (clp)
+			tpd->status |= TPD_CLP;
+
+		skb_pull(skb, ATM_AAL0_SDU - ATM_CELL_PAYLOAD);
+	}
+
+#ifdef USE_SCATTERGATHER
+	tpd->iovec[slot].addr = pci_map_single(he_dev->pci_dev, skb->data,
+				skb->len - skb->data_len, PCI_DMA_TODEVICE);
+	tpd->iovec[slot].len = skb->len - skb->data_len;
+	++slot;
+
+	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+		if (slot == TPD_MAXIOV) {	/* queue tpd; start new tpd */
+			tpd->vcc = vcc;
+			tpd->skb = NULL;	/* not the last fragment
+						   so dont ->push() yet */
+			wmb();
+
+			__enqueue_tpd(he_dev, tpd, cid);
+			tpd = __alloc_tpd(he_dev);
+			if (tpd == NULL) {
+				if (vcc->pop)
+					vcc->pop(vcc, skb);
+				else
+					dev_kfree_skb_any(skb);
+				atomic_inc(&vcc->stats->tx_err);
+				spin_unlock_irqrestore(&he_dev->global_lock, flags);
+				return -ENOMEM;
+			}
+			tpd->status |= TPD_USERCELL;
+			slot = 0;
+		}
+
+		tpd->iovec[slot].addr = pci_map_single(he_dev->pci_dev,
+			(void *) page_address(frag->page) + frag->page_offset,
+				frag->size, PCI_DMA_TODEVICE);
+		tpd->iovec[slot].len = frag->size;
+		++slot;
+
+	}
+
+	tpd->iovec[slot - 1].len |= TPD_LST;
+#else
+	tpd->address0 = pci_map_single(he_dev->pci_dev, skb->data, skb->len, PCI_DMA_TODEVICE);
+	tpd->length0 = skb->len | TPD_LST;
+#endif
+	tpd->status |= TPD_INT;
+
+	tpd->vcc = vcc;
+	tpd->skb = skb;
+	wmb();
+	ATM_SKB(skb)->vcc = vcc;
+
+	__enqueue_tpd(he_dev, tpd, cid);
+	spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+	atomic_inc(&vcc->stats->tx);
+
+	return 0;
+}
+
+static int
+he_ioctl(struct atm_dev *atm_dev, unsigned int cmd, void __user *arg)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = HE_DEV(atm_dev);
+	struct he_ioctl_reg reg;
+	int err = 0;
+
+	switch (cmd) {
+		case HE_GET_REG:
+			if (!capable(CAP_NET_ADMIN))
+				return -EPERM;
+
+			if (copy_from_user(&reg, arg,
+					   sizeof(struct he_ioctl_reg)))
+				return -EFAULT;
+			
+			spin_lock_irqsave(&he_dev->global_lock, flags);
+			switch (reg.type) {
+				case HE_REGTYPE_PCI:
+					reg.val = he_readl(he_dev, reg.addr);
+					break;
+				case HE_REGTYPE_RCM:
+					reg.val =
+						he_readl_rcm(he_dev, reg.addr);
+					break;
+				case HE_REGTYPE_TCM:
+					reg.val =
+						he_readl_tcm(he_dev, reg.addr);
+					break;
+				case HE_REGTYPE_MBOX:
+					reg.val =
+						he_readl_mbox(he_dev, reg.addr);
+					break;
+				default:
+					err = -EINVAL;
+					break;
+			}
+			spin_unlock_irqrestore(&he_dev->global_lock, flags);
+			if (err == 0)
+				if (copy_to_user(arg, &reg,
+							sizeof(struct he_ioctl_reg)))
+					return -EFAULT;
+			break;
+		default:
+#ifdef CONFIG_ATM_HE_USE_SUNI
+			if (atm_dev->phy && atm_dev->phy->ioctl)
+				err = atm_dev->phy->ioctl(atm_dev, cmd, arg);
+#else /* CONFIG_ATM_HE_USE_SUNI */
+			err = -EINVAL;
+#endif /* CONFIG_ATM_HE_USE_SUNI */
+			break;
+	}
+
+	return err;
+}
+
+static void
+he_phy_put(struct atm_dev *atm_dev, unsigned char val, unsigned long addr)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = HE_DEV(atm_dev);
+
+	HPRINTK("phy_put(val 0x%x, addr 0x%lx)\n", val, addr);
+
+	spin_lock_irqsave(&he_dev->global_lock, flags);
+	he_writel(he_dev, val, FRAMER + (addr*4));
+	(void) he_readl(he_dev, FRAMER + (addr*4));		/* flush posted writes */
+	spin_unlock_irqrestore(&he_dev->global_lock, flags);
+}
+ 
+	
+static unsigned char
+he_phy_get(struct atm_dev *atm_dev, unsigned long addr)
+{ 
+	unsigned long flags;
+	struct he_dev *he_dev = HE_DEV(atm_dev);
+	unsigned reg;
+
+	spin_lock_irqsave(&he_dev->global_lock, flags);
+	reg = he_readl(he_dev, FRAMER + (addr*4));
+	spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+	HPRINTK("phy_get(addr 0x%lx) =0x%x\n", addr, reg);
+	return reg;
+}
+
+static int
+he_proc_read(struct atm_dev *dev, loff_t *pos, char *page)
+{
+	unsigned long flags;
+	struct he_dev *he_dev = HE_DEV(dev);
+	int left, i;
+#ifdef notdef
+	struct he_rbrq *rbrq_tail;
+	struct he_tpdrq *tpdrq_head;
+	int rbpl_head, rbpl_tail;
+#endif
+	static long mcc = 0, oec = 0, dcc = 0, cec = 0;
+
+
+	left = *pos;
+	if (!left--)
+		return sprintf(page, "%s\n", version);
+
+	if (!left--)
+		return sprintf(page, "%s%s\n\n",
+			he_dev->prod_id, he_dev->media & 0x40 ? "SM" : "MM");
+
+	if (!left--)
+		return sprintf(page, "Mismatched Cells  VPI/VCI Not Open  Dropped Cells  RCM Dropped Cells\n");
+
+	spin_lock_irqsave(&he_dev->global_lock, flags);
+	mcc += he_readl(he_dev, MCC);
+	oec += he_readl(he_dev, OEC);
+	dcc += he_readl(he_dev, DCC);
+	cec += he_readl(he_dev, CEC);
+	spin_unlock_irqrestore(&he_dev->global_lock, flags);
+
+	if (!left--)
+		return sprintf(page, "%16ld  %16ld  %13ld  %17ld\n\n", 
+							mcc, oec, dcc, cec);
+
+	if (!left--)
+		return sprintf(page, "irq_size = %d  inuse = ?  peak = %d\n",
+				CONFIG_IRQ_SIZE, he_dev->irq_peak);
+
+	if (!left--)
+		return sprintf(page, "tpdrq_size = %d  inuse = ?\n",
+						CONFIG_TPDRQ_SIZE);
+
+	if (!left--)
+		return sprintf(page, "rbrq_size = %d  inuse = ?  peak = %d\n",
+				CONFIG_RBRQ_SIZE, he_dev->rbrq_peak);
+
+	if (!left--)
+		return sprintf(page, "tbrq_size = %d  peak = %d\n",
+					CONFIG_TBRQ_SIZE, he_dev->tbrq_peak);
+
+
+#ifdef notdef
+	rbpl_head = RBPL_MASK(he_readl(he_dev, G0_RBPL_S));
+	rbpl_tail = RBPL_MASK(he_readl(he_dev, G0_RBPL_T));
+
+	inuse = rbpl_head - rbpl_tail;
+	if (inuse < 0)
+		inuse += CONFIG_RBPL_SIZE * sizeof(struct he_rbp);
+	inuse /= sizeof(struct he_rbp);
+
+	if (!left--)
+		return sprintf(page, "rbpl_size = %d  inuse = %d\n\n",
+						CONFIG_RBPL_SIZE, inuse);
+#endif
+
+	if (!left--)
+		return sprintf(page, "rate controller periods (cbr)\n                 pcr  #vc\n");
+
+	for (i = 0; i < HE_NUM_CS_STPER; ++i)
+		if (!left--)
+			return sprintf(page, "cs_stper%-2d  %8ld  %3d\n", i,
+						he_dev->cs_stper[i].pcr,
+						he_dev->cs_stper[i].inuse);
+
+	if (!left--)
+		return sprintf(page, "total bw (cbr): %d  (limit %d)\n",
+			he_dev->total_bw, he_dev->atm_dev->link_rate * 10 / 9);
+
+	return 0;
+}
+
+/* eeprom routines  -- see 4.7 */
+
+u8
+read_prom_byte(struct he_dev *he_dev, int addr)
+{
+	u32 val = 0, tmp_read = 0;
+	int i, j = 0;
+	u8 byte_read = 0;
+
+	val = readl(he_dev->membase + HOST_CNTL);
+	val &= 0xFFFFE0FF;
+       
+	/* Turn on write enable */
+	val |= 0x800;
+	he_writel(he_dev, val, HOST_CNTL);
+       
+	/* Send READ instruction */
+	for (i = 0; i < sizeof(readtab)/sizeof(readtab[0]); i++) {
+		he_writel(he_dev, val | readtab[i], HOST_CNTL);
+		udelay(EEPROM_DELAY);
+	}
+       
+	/* Next, we need to send the byte address to read from */
+	for (i = 7; i >= 0; i--) {
+		he_writel(he_dev, val | clocktab[j++] | (((addr >> i) & 1) << 9), HOST_CNTL);
+		udelay(EEPROM_DELAY);
+		he_writel(he_dev, val | clocktab[j++] | (((addr >> i) & 1) << 9), HOST_CNTL);
+		udelay(EEPROM_DELAY);
+	}
+       
+	j = 0;
+
+	val &= 0xFFFFF7FF;      /* Turn off write enable */
+	he_writel(he_dev, val, HOST_CNTL);
+       
+	/* Now, we can read data from the EEPROM by clocking it in */
+	for (i = 7; i >= 0; i--) {
+		he_writel(he_dev, val | clocktab[j++], HOST_CNTL);
+		udelay(EEPROM_DELAY);
+		tmp_read = he_readl(he_dev, HOST_CNTL);
+		byte_read |= (unsigned char)
+			   ((tmp_read & ID_DOUT) >> ID_DOFFSET << i);
+		he_writel(he_dev, val | clocktab[j++], HOST_CNTL);
+		udelay(EEPROM_DELAY);
+	}
+       
+	he_writel(he_dev, val | ID_CS, HOST_CNTL);
+	udelay(EEPROM_DELAY);
+
+	return byte_read;
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("chas williams <chas@cmf.nrl.navy.mil>");
+MODULE_DESCRIPTION("ForeRunnerHE ATM Adapter driver");
+module_param(disable64, bool, 0);
+MODULE_PARM_DESC(disable64, "disable 64-bit pci bus transfers");
+module_param(nvpibits, short, 0);
+MODULE_PARM_DESC(nvpibits, "numbers of bits for vpi (default 0)");
+module_param(nvcibits, short, 0);
+MODULE_PARM_DESC(nvcibits, "numbers of bits for vci (default 12)");
+module_param(rx_skb_reserve, short, 0);
+MODULE_PARM_DESC(rx_skb_reserve, "padding for receive skb (default 16)");
+module_param(irq_coalesce, bool, 0);
+MODULE_PARM_DESC(irq_coalesce, "use interrupt coalescing (default 1)");
+module_param(sdh, bool, 0);
+MODULE_PARM_DESC(sdh, "use SDH framing (default 0)");
+
+static struct pci_device_id he_pci_tbl[] = {
+	{ PCI_VENDOR_ID_FORE, PCI_DEVICE_ID_FORE_HE, PCI_ANY_ID, PCI_ANY_ID,
+	  0, 0, 0 },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, he_pci_tbl);
+
+static struct pci_driver he_driver = {
+	.name =		"he",
+	.probe =	he_init_one,
+	.remove =	__devexit_p(he_remove_one),
+	.id_table =	he_pci_tbl,
+};
+
+static int __init he_init(void)
+{
+	return pci_register_driver(&he_driver);
+}
+
+static void __exit he_cleanup(void)
+{
+	pci_unregister_driver(&he_driver);
+}
+
+module_init(he_init);
+module_exit(he_cleanup);
diff --git a/drivers/atm/he.h b/drivers/atm/he.h
new file mode 100644
index 0000000..1a90385
--- /dev/null
+++ b/drivers/atm/he.h
@@ -0,0 +1,895 @@
+/* $Id: he.h,v 1.4 2003/05/06 22:48:00 chas Exp $ */
+
+/*
+
+  he.h
+
+  ForeRunnerHE ATM Adapter driver for ATM on Linux
+  Copyright (C) 1999-2001  Naval Research Laboratory
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+/*
+
+  he.h
+
+  ForeRunnerHE ATM Adapter driver for ATM on Linux
+  Copyright (C) 1999-2000  Naval Research Laboratory
+
+  Permission to use, copy, modify and distribute this software and its
+  documentation is hereby granted, provided that both the copyright
+  notice and this permission notice appear in all copies of the software,
+  derivative works or modified versions, and any portions thereof, and
+  that both notices appear in supporting documentation.
+
+  NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
+  DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
+  RESULTING FROM THE USE OF THIS SOFTWARE.
+
+ */
+
+#ifndef _HE_H_
+#define _HE_H_
+
+#define DEV_LABEL       "he"
+
+#define CONFIG_DEFAULT_VCIBITS	12
+#define CONFIG_DEFAULT_VPIBITS	0
+
+#define CONFIG_IRQ_SIZE		128
+#define CONFIG_IRQ_THRESH	(CONFIG_IRQ_SIZE/2)
+
+#define CONFIG_NUMTPDS		256
+
+#define CONFIG_TPDRQ_SIZE	512
+#define TPDRQ_MASK(x)		(((unsigned long)(x))&((CONFIG_TPDRQ_SIZE<<3)-1))
+
+#define CONFIG_RBRQ_SIZE	512
+#define CONFIG_RBRQ_THRESH	400
+#define RBRQ_MASK(x)		(((unsigned long)(x))&((CONFIG_RBRQ_SIZE<<3)-1))
+
+#define CONFIG_TBRQ_SIZE	512
+#define CONFIG_TBRQ_THRESH	400
+#define TBRQ_MASK(x)		(((unsigned long)(x))&((CONFIG_TBRQ_SIZE<<2)-1))
+
+#define CONFIG_RBPL_SIZE	512
+#define CONFIG_RBPL_THRESH	64
+#define CONFIG_RBPL_BUFSIZE	4096
+#define RBPL_MASK(x)		(((unsigned long)(x))&((CONFIG_RBPL_SIZE<<3)-1))
+
+#define CONFIG_RBPS_SIZE	1024
+#define CONFIG_RBPS_THRESH	64
+#define CONFIG_RBPS_BUFSIZE	128
+#define RBPS_MASK(x)		(((unsigned long)(x))&((CONFIG_RBPS_SIZE<<3)-1))
+
+/* 5.1.3 initialize connection memory */
+
+#define CONFIG_RSRA		0x00000
+#define CONFIG_RCMLBM		0x08000
+#define CONFIG_RCMABR		0x0d800
+#define CONFIG_RSRB		0x0e000
+
+#define CONFIG_TSRA		0x00000
+#define CONFIG_TSRB		0x08000
+#define CONFIG_TSRC		0x0c000
+#define CONFIG_TSRD		0x0e000
+#define CONFIG_TMABR		0x0f000
+#define CONFIG_TPDBA		0x10000
+
+#define HE_MAXCIDBITS		12
+
+/* 2.9.3.3 interrupt encodings */
+
+struct he_irq {
+	volatile u32 isw;
+};
+
+#define IRQ_ALIGNMENT		0x1000
+
+#define NEXT_ENTRY(base, tail, mask) \
+				(((unsigned long)base)|(((unsigned long)(tail+1))&mask))
+
+#define ITYPE_INVALID		0xffffffff
+#define ITYPE_TBRQ_THRESH	(0<<3)
+#define ITYPE_TPD_COMPLETE	(1<<3)
+#define ITYPE_RBPS_THRESH	(2<<3)
+#define ITYPE_RBPL_THRESH	(3<<3)
+#define ITYPE_RBRQ_THRESH	(4<<3)
+#define ITYPE_RBRQ_TIMER	(5<<3)
+#define ITYPE_PHY		(6<<3)
+#define ITYPE_OTHER		0x80
+#define ITYPE_PARITY		0x81
+#define ITYPE_ABORT		0x82
+
+#define ITYPE_GROUP(x)		(x & 0x7)
+#define ITYPE_TYPE(x)		(x & 0xf8)
+
+#define HE_NUM_GROUPS 8
+
+/* 2.1.4 transmit packet descriptor */
+
+struct he_tpd {
+
+	/* read by the adapter */
+
+	volatile u32 status;
+	volatile u32 reserved;
+
+#define TPD_MAXIOV	3
+	struct {
+		u32 addr, len;
+	} iovec[TPD_MAXIOV];
+
+#define address0 iovec[0].addr
+#define length0 iovec[0].len
+
+	/* linux-atm extensions */
+
+	struct sk_buff *skb;
+	struct atm_vcc *vcc;
+
+#ifdef USE_TPD_POOL
+	struct list_head entry;
+#else
+	u32 inuse;
+	char padding[32 - sizeof(u32) - (2*sizeof(void*))];
+#endif
+};
+
+#define TPD_ALIGNMENT	64
+#define TPD_LEN_MASK	0xffff
+
+#define TPD_ADDR_SHIFT  6
+#define TPD_MASK	0xffffffc0
+#define TPD_ADDR(x)	((x) & TPD_MASK)
+#define TPD_INDEX(x)	(TPD_ADDR(x) >> TPD_ADDR_SHIFT)
+
+
+/* table 2.3 transmit buffer return elements */
+
+struct he_tbrq {
+	volatile u32 tbre;
+};
+
+#define TBRQ_ALIGNMENT	CONFIG_TBRQ_SIZE
+
+#define TBRQ_TPD(tbrq)		((tbrq)->tbre & 0xffffffc0)
+#define TBRQ_EOS(tbrq)		((tbrq)->tbre & (1<<3))
+#define TBRQ_MULTIPLE(tbrq)	((tbrq)->tbre & (1))
+
+/* table 2.21 receive buffer return queue element field organization */
+
+struct he_rbrq {
+	volatile u32 addr;
+	volatile u32 cidlen;
+};
+
+#define RBRQ_ALIGNMENT	CONFIG_RBRQ_SIZE
+
+#define RBRQ_ADDR(rbrq)		((rbrq)->addr & 0xffffffc0)
+#define RBRQ_CRC_ERR(rbrq)	((rbrq)->addr & (1<<5))
+#define RBRQ_LEN_ERR(rbrq)	((rbrq)->addr & (1<<4))
+#define RBRQ_END_PDU(rbrq)	((rbrq)->addr & (1<<3))
+#define RBRQ_AAL5_PROT(rbrq)	((rbrq)->addr & (1<<2))
+#define RBRQ_CON_CLOSED(rbrq)	((rbrq)->addr & (1<<1))
+#define RBRQ_HBUF_ERR(rbrq)	((rbrq)->addr & 1)
+#define RBRQ_CID(rbrq)		(((rbrq)->cidlen >> 16) & 0x1fff)
+#define RBRQ_BUFLEN(rbrq)	((rbrq)->cidlen & 0xffff)
+
+/* figure 2.3 transmit packet descriptor ready queue */
+
+struct he_tpdrq {
+	volatile u32 tpd;
+	volatile u32 cid;
+};
+
+#define TPDRQ_ALIGNMENT CONFIG_TPDRQ_SIZE
+
+/* table 2.30 host status page detail */
+
+#define HSP_ALIGNMENT	0x400		/* must align on 1k boundary */
+
+struct he_hsp {
+	struct he_hsp_entry {
+		volatile u32 tbrq_tail; 
+		volatile u32 reserved1[15];
+		volatile u32 rbrq_tail; 
+		volatile u32 reserved2[15];
+	} group[HE_NUM_GROUPS];
+};
+
+/* figure 2.9 receive buffer pools */
+
+struct he_rbp {
+	volatile u32 phys;
+	volatile u32 status;
+};
+
+/* NOTE: it is suggested that virt be the virtual address of the host
+   buffer.  on a 64-bit machine, this would not work.  Instead, we
+   store the real virtual address in another list, and store an index
+   (and buffer status) in the virt member.
+*/
+
+#define RBP_INDEX_OFF	6
+#define RBP_INDEX(x)	(((long)(x) >> RBP_INDEX_OFF) & 0xffff)
+#define RBP_LOANED	0x80000000
+#define RBP_SMALLBUF	0x40000000
+
+struct he_virt {
+	void *virt;
+};
+
+#define RBPL_ALIGNMENT CONFIG_RBPL_SIZE
+#define RBPS_ALIGNMENT CONFIG_RBPS_SIZE
+
+#ifdef notyet
+struct he_group {
+	u32 rpbs_size, rpbs_qsize;
+	struct he_rbp rbps_ba;
+
+	u32 rpbl_size, rpbl_qsize;
+	struct he_rpb_entry *rbpl_ba;
+};
+#endif
+
+#define HE_LOOKUP_VCC(dev, cid) ((dev)->he_vcc_table[(cid)].vcc)
+
+struct he_vcc_table 
+{
+	struct atm_vcc *vcc;
+};
+
+struct he_cs_stper
+{
+	long pcr;
+	int inuse;
+};
+
+#define HE_NUM_CS_STPER		16
+
+struct he_dev {
+	unsigned int number;
+	unsigned int irq;
+	void __iomem *membase;
+
+	char prod_id[30];
+	char mac_addr[6];
+	int media;			/*  
+					 *  0x26 = HE155 MM 
+					 *  0x27 = HE622 MM 
+					 *  0x46 = HE155 SM 
+					 *  0x47 = HE622 SM 
+					 */
+
+
+	unsigned int vcibits, vpibits;
+	unsigned int cells_per_row;
+	unsigned int bytes_per_row;
+	unsigned int cells_per_lbuf;
+	unsigned int r0_numrows, r0_startrow, r0_numbuffs;
+	unsigned int r1_numrows, r1_startrow, r1_numbuffs;
+	unsigned int tx_numrows, tx_startrow, tx_numbuffs;
+	unsigned int buffer_limit;
+
+	struct he_vcc_table *he_vcc_table;
+
+#ifdef notyet
+	struct he_group group[HE_NUM_GROUPS];
+#endif
+	struct he_cs_stper cs_stper[HE_NUM_CS_STPER];
+	unsigned total_bw;
+
+	dma_addr_t irq_phys;
+	struct he_irq *irq_base, *irq_head, *irq_tail;
+	volatile unsigned *irq_tailoffset;
+	int irq_peak;
+
+#ifdef USE_TASKLET
+	struct tasklet_struct tasklet;
+#endif
+#ifdef USE_TPD_POOL
+	struct pci_pool *tpd_pool;
+	struct list_head outstanding_tpds;
+#else
+	struct he_tpd *tpd_head, *tpd_base, *tpd_end;
+	dma_addr_t tpd_base_phys;
+#endif
+
+	dma_addr_t tpdrq_phys;
+	struct he_tpdrq *tpdrq_base, *tpdrq_tail, *tpdrq_head;
+
+	spinlock_t global_lock;		/* 8.1.5 pci transaction ordering
+					  error problem */
+	dma_addr_t rbrq_phys;
+	struct he_rbrq *rbrq_base, *rbrq_head;
+	int rbrq_peak;
+
+#ifdef USE_RBPL_POOL
+	struct pci_pool *rbpl_pool;
+#else
+	void *rbpl_pages;
+	dma_addr_t rbpl_pages_phys;
+#endif
+	dma_addr_t rbpl_phys;
+	struct he_rbp *rbpl_base, *rbpl_tail;
+	struct he_virt *rbpl_virt;
+	int rbpl_peak;
+
+#ifdef USE_RBPS
+#ifdef USE_RBPS_POOL
+	struct pci_pool *rbps_pool;
+#else
+	void *rbps_pages;
+	dma_addr_t rbps_pages_phys;
+#endif
+#endif
+	dma_addr_t rbps_phys;
+	struct he_rbp *rbps_base, *rbps_tail;
+	struct he_virt *rbps_virt;
+	int rbps_peak;
+
+	dma_addr_t tbrq_phys;
+	struct he_tbrq *tbrq_base, *tbrq_head;
+	int tbrq_peak;
+
+	dma_addr_t hsp_phys;
+	struct he_hsp *hsp;
+
+	struct pci_dev *pci_dev;
+	struct atm_dev *atm_dev;
+	struct he_dev *next;
+};
+
+struct he_iovec
+{
+	u32 iov_base;
+	u32 iov_len;
+};
+
+#define HE_MAXIOV 20
+
+struct he_vcc
+{
+	struct he_iovec iov_head[HE_MAXIOV];
+	struct he_iovec *iov_tail;
+	int pdu_len;
+
+	int rc_index;
+
+	wait_queue_head_t rx_waitq;
+	wait_queue_head_t tx_waitq;
+};
+
+#define HE_VCC(vcc)	((struct he_vcc *)(vcc->dev_data))
+
+#define PCI_VENDOR_ID_FORE	0x1127
+#define PCI_DEVICE_ID_FORE_HE	0x400
+
+#define HE_DMA_MASK		0xffffffff
+
+#define GEN_CNTL_0				0x40
+#define  INT_PROC_ENBL		(1<<25)
+#define  SLAVE_ENDIAN_MODE	(1<<16)
+#define  MRL_ENB		(1<<5)
+#define  MRM_ENB		(1<<4)
+#define  INIT_ENB		(1<<2)
+#define  IGNORE_TIMEOUT		(1<<1)
+#define  ENBL_64		(1<<0)
+
+#define MIN_PCI_LATENCY		32	/* errata 8.1.3 */
+
+#define HE_DEV(dev) ((struct he_dev *) (dev)->dev_data)
+
+#define he_is622(dev)	((dev)->media & 0x1)
+
+#define HE_REGMAP_SIZE	0x100000
+
+#define RESET_CNTL	0x80000
+#define  BOARD_RST_STATUS	(1<<6)
+
+#define HOST_CNTL	0x80004
+#define  PCI_BUS_SIZE64			(1<<27)
+#define  DESC_RD_STATIC_64		(1<<26)
+#define  DATA_RD_STATIC_64		(1<<25)
+#define  DATA_WR_STATIC_64		(1<<24)
+#define  ID_CS				(1<<12)
+#define  ID_WREN			(1<<11)
+#define  ID_DOUT			(1<<10)
+#define   ID_DOFFSET			10
+#define  ID_DIN				(1<<9)
+#define  ID_CLOCK			(1<<8)
+#define  QUICK_RD_RETRY			(1<<7)
+#define  QUICK_WR_RETRY			(1<<6)
+#define  OUTFF_ENB			(1<<5)
+#define  CMDFF_ENB			(1<<4)
+#define  PERR_INT_ENB			(1<<2)
+#define  IGNORE_INTR			(1<<0)
+
+#define LB_SWAP		0x80008
+#define  SWAP_RNUM_MAX(x)	(x<<27)
+#define  DATA_WR_SWAP		(1<<20)
+#define  DESC_RD_SWAP		(1<<19)
+#define  DATA_RD_SWAP		(1<<18)
+#define  INTR_SWAP		(1<<17)
+#define  DESC_WR_SWAP		(1<<16)
+#define  SDRAM_INIT		(1<<15)
+#define  BIG_ENDIAN_HOST	(1<<14)
+#define  XFER_SIZE		(1<<7)
+
+#define LB_MEM_ADDR	0x8000c
+#define LB_MEM_DATA	0x80010
+
+#define LB_MEM_ACCESS	0x80014
+#define  LB_MEM_HNDSHK		(1<<30)
+#define  LM_MEM_WRITE		(0x7)
+#define  LM_MEM_READ		(0x3)
+
+#define SDRAM_CTL	0x80018
+#define  LB_64_ENB		(1<<3)
+#define  LB_TWR			(1<<2)
+#define  LB_TRP			(1<<1)
+#define  LB_TRAS		(1<<0)
+
+#define INT_FIFO	0x8001c
+#define  INT_MASK_D		(1<<15)
+#define  INT_MASK_C		(1<<14)
+#define  INT_MASK_B		(1<<13)
+#define  INT_MASK_A		(1<<12)
+#define  INT_CLEAR_D		(1<<11)
+#define  INT_CLEAR_C		(1<<10)
+#define  INT_CLEAR_B		(1<<9)
+#define  INT_CLEAR_A		(1<<8)
+
+#define ABORT_ADDR	0x80020
+
+#define IRQ0_BASE	0x80080
+#define  IRQ_BASE(x)		(x<<12)
+#define  IRQ_MASK		((CONFIG_IRQ_SIZE<<2)-1)	/* was 0x3ff */
+#define  IRQ_TAIL(x)		(((unsigned long)(x)) & IRQ_MASK)
+#define IRQ0_HEAD	0x80084
+#define  IRQ_SIZE(x)		(x<<22)
+#define  IRQ_THRESH(x)		(x<<12)
+#define  IRQ_HEAD(x)		(x<<2)
+/* #define  IRQ_PENDING		(1) 		conflict with linux/irq.h */
+#define IRQ0_CNTL	0x80088
+#define  IRQ_ADDRSEL(x)		(x<<2)
+#define  IRQ_INT_A		(0<<2)
+#define  IRQ_INT_B		(1<<2)
+#define  IRQ_INT_C		(2<<2)
+#define  IRQ_INT_D		(3<<2)
+#define  IRQ_TYPE_ADDR		0x1
+#define  IRQ_TYPE_LINE		0x0
+#define IRQ0_DATA	0x8008c
+
+#define IRQ1_BASE	0x80090
+#define IRQ1_HEAD	0x80094
+#define IRQ1_CNTL	0x80098
+#define IRQ1_DATA	0x8009c
+
+#define IRQ2_BASE	0x800a0
+#define IRQ2_HEAD	0x800a4
+#define IRQ2_CNTL	0x800a8
+#define IRQ2_DATA	0x800ac
+
+#define IRQ3_BASE	0x800b0
+#define IRQ3_HEAD	0x800b4
+#define IRQ3_CNTL	0x800b8
+#define IRQ3_DATA	0x800bc
+
+#define GRP_10_MAP	0x800c0
+#define GRP_32_MAP	0x800c4
+#define GRP_54_MAP	0x800c8
+#define GRP_76_MAP	0x800cc
+
+#define	G0_RBPS_S	0x80400
+#define G0_RBPS_T	0x80404
+#define  RBP_TAIL(x)		((x)<<3)
+#define  RBP_MASK(x)		((x)|0x1fff)
+#define G0_RBPS_QI	0x80408
+#define  RBP_QSIZE(x)		((x)<<14)
+#define  RBP_INT_ENB		(1<<13)
+#define  RBP_THRESH(x)		(x)
+#define G0_RBPS_BS	0x8040c
+#define G0_RBPL_S	0x80410
+#define G0_RBPL_T	0x80414
+#define G0_RBPL_QI	0x80418 
+#define G0_RBPL_BS	0x8041c
+
+#define	G1_RBPS_S	0x80420
+#define G1_RBPS_T	0x80424
+#define G1_RBPS_QI	0x80428
+#define G1_RBPS_BS	0x8042c
+#define G1_RBPL_S	0x80430
+#define G1_RBPL_T	0x80434
+#define G1_RBPL_QI	0x80438
+#define G1_RBPL_BS	0x8043c
+
+#define	G2_RBPS_S	0x80440
+#define G2_RBPS_T	0x80444
+#define G2_RBPS_QI	0x80448
+#define G2_RBPS_BS	0x8044c
+#define G2_RBPL_S	0x80450
+#define G2_RBPL_T	0x80454
+#define G2_RBPL_QI	0x80458
+#define G2_RBPL_BS	0x8045c
+
+#define	G3_RBPS_S	0x80460
+#define G3_RBPS_T	0x80464
+#define G3_RBPS_QI	0x80468
+#define G3_RBPS_BS	0x8046c
+#define G3_RBPL_S	0x80470
+#define G3_RBPL_T	0x80474
+#define G3_RBPL_QI	0x80478
+#define G3_RBPL_BS	0x8047c
+
+#define	G4_RBPS_S	0x80480
+#define G4_RBPS_T	0x80484
+#define G4_RBPS_QI	0x80488
+#define G4_RBPS_BS	0x8048c
+#define G4_RBPL_S	0x80490
+#define G4_RBPL_T	0x80494
+#define G4_RBPL_QI	0x80498
+#define G4_RBPL_BS	0x8049c
+
+#define	G5_RBPS_S	0x804a0
+#define G5_RBPS_T	0x804a4
+#define G5_RBPS_QI	0x804a8
+#define G5_RBPS_BS	0x804ac
+#define G5_RBPL_S	0x804b0
+#define G5_RBPL_T	0x804b4
+#define G5_RBPL_QI	0x804b8
+#define G5_RBPL_BS	0x804bc
+
+#define	G6_RBPS_S	0x804c0
+#define G6_RBPS_T	0x804c4
+#define G6_RBPS_QI	0x804c8
+#define G6_RBPS_BS	0x804cc
+#define G6_RBPL_S	0x804d0
+#define G6_RBPL_T	0x804d4
+#define G6_RBPL_QI	0x804d8
+#define G6_RBPL_BS	0x804dc
+
+#define	G7_RBPS_S	0x804e0
+#define G7_RBPS_T	0x804e4
+#define G7_RBPS_QI	0x804e8
+#define G7_RBPS_BS	0x804ec
+
+#define G7_RBPL_S	0x804f0
+#define G7_RBPL_T	0x804f4
+#define G7_RBPL_QI	0x804f8
+#define G7_RBPL_BS	0x804fc
+
+#define G0_RBRQ_ST	0x80500
+#define G0_RBRQ_H	0x80504
+#define G0_RBRQ_Q	0x80508
+#define  RBRQ_THRESH(x)		((x)<<13)
+#define  RBRQ_SIZE(x)		(x)
+#define G0_RBRQ_I	0x8050c
+#define  RBRQ_TIME(x)		((x)<<8)
+#define  RBRQ_COUNT(x)		(x)
+
+/* fill in 1 ... 7 later */
+
+#define G0_TBRQ_B_T	0x80600
+#define G0_TBRQ_H	0x80604
+#define G0_TBRQ_S	0x80608
+#define G0_TBRQ_THRESH	0x8060c
+#define  TBRQ_THRESH(x)		(x)
+
+/* fill in 1 ... 7 later */
+
+#define RH_CONFIG	0x805c0
+#define  PHY_INT_ENB	(1<<10)
+#define  OAM_GID(x)	(x<<7)
+#define  PTMR_PRE(x)	(x)
+
+#define G0_INMQ_S	0x80580
+#define G0_INMQ_L	0x80584
+#define G1_INMQ_S	0x80588
+#define G1_INMQ_L	0x8058c
+#define G2_INMQ_S	0x80590
+#define G2_INMQ_L	0x80594
+#define G3_INMQ_S	0x80598
+#define G3_INMQ_L	0x8059c
+#define G4_INMQ_S	0x805a0
+#define G4_INMQ_L	0x805a4
+#define G5_INMQ_S	0x805a8
+#define G5_INMQ_L	0x805ac
+#define G6_INMQ_S	0x805b0
+#define G6_INMQ_L	0x805b4
+#define G7_INMQ_S	0x805b8
+#define G7_INMQ_L	0x805bc
+
+#define TPDRQ_B_H	0x80680
+#define TPDRQ_T		0x80684
+#define TPDRQ_S		0x80688
+
+#define UBUFF_BA	0x8068c
+
+#define RLBF0_H		0x806c0
+#define RLBF0_T		0x806c4
+#define RLBF1_H		0x806c8
+#define RLBF1_T		0x806cc
+#define RLBC_H		0x806d0
+#define RLBC_T		0x806d4
+#define RLBC_H2		0x806d8
+#define TLBF_H		0x806e0
+#define TLBF_T		0x806e4
+#define RLBF0_C		0x806e8
+#define RLBF1_C		0x806ec
+#define RXTHRSH		0x806f0
+#define LITHRSH		0x806f4
+
+#define LBARB		0x80700
+#define  SLICE_X(x)		 (x<<28)
+#define  ARB_RNUM_MAX(x)	 (x<<23)
+#define  TH_PRTY(x)		 (x<<21)
+#define  RH_PRTY(x)		 (x<<19)
+#define  TL_PRTY(x)		 (x<<17)
+#define  RL_PRTY(x)		 (x<<15)
+#define  BUS_MULTI(x)		 (x<<8)
+#define  NET_PREF(x)		 (x)
+
+#define SDRAMCON	0x80704
+#define	 BANK_ON		(1<<14)
+#define	 WIDE_DATA		(1<<13)
+#define	 TWR_WAIT		(1<<12)
+#define	 TRP_WAIT		(1<<11)
+#define	 TRAS_WAIT		(1<<10)
+#define	 REF_RATE(x)		(x)
+
+#define LBSTAT		0x80708
+
+#define RCC_STAT	0x8070c
+#define  RCC_BUSY		(1)
+
+#define TCMCONFIG	0x80740
+#define  TM_DESL2		(1<<10)
+#define	 TM_BANK_WAIT(x)	(x<<6)
+#define	 TM_ADD_BANK4(x)	(x<<4)
+#define  TM_PAR_CHECK(x)	(x<<3)
+#define  TM_RW_WAIT(x)		(x<<2)
+#define  TM_SRAM_TYPE(x)	(x)
+
+#define TSRB_BA		0x80744	
+#define TSRC_BA		0x80748	
+#define TMABR_BA	0x8074c	
+#define TPD_BA		0x80750	
+#define TSRD_BA		0x80758	
+
+#define TX_CONFIG	0x80760
+#define  DRF_THRESH(x)		(x<<22)
+#define  TX_UT_MODE(x)		(x<<21)
+#define  TX_VCI_MASK(x)		(x<<17)
+#define  LBFREE_CNT(x)		(x)
+
+#define TXAAL5_PROTO	0x80764
+#define  CPCS_UU(x)		(x<<8)
+#define  CPI(x)			(x)
+
+#define RCMCONFIG	0x80780
+#define  RM_DESL2(x)		(x<<10)
+#define  RM_BANK_WAIT(x)	(x<<6)
+#define  RM_ADD_BANK(x)		(x<<4)
+#define  RM_PAR_CHECK(x)	(x<<3)
+#define  RM_RW_WAIT(x)		(x<<2)
+#define  RM_SRAM_TYPE(x)	(x)
+
+#define RCMRSRB_BA	0x80784
+#define RCMLBM_BA	0x80788
+#define RCMABR_BA	0x8078c
+
+#define RC_CONFIG	0x807c0
+#define  UT_RD_DELAY(x)		(x<<11)
+#define  WRAP_MODE(x)		(x<<10)
+#define  RC_UT_MODE(x)		(x<<9)
+#define  RX_ENABLE		(1<<8)
+#define  RX_VALVP(x)		(x<<4)
+#define  RX_VALVC(x)		(x)
+
+#define MCC		0x807c4
+#define OEC		0x807c8
+#define DCC		0x807cc
+#define CEC		0x807d0
+
+#define HSP_BA		0x807f0
+
+#define LB_CONFIG	0x807f4
+#define  LB_SIZE(x)		(x)
+
+#define CON_DAT		0x807f8
+#define CON_CTL		0x807fc
+#define  CON_CTL_MBOX		(2<<30)
+#define  CON_CTL_TCM		(1<<30)
+#define  CON_CTL_RCM		(0<<30)
+#define  CON_CTL_WRITE		(1<<29)
+#define  CON_CTL_READ		(0<<29)
+#define  CON_CTL_BUSY		(1<<28)
+#define  CON_BYTE_DISABLE_3	(1<<22)		/* 24..31 */
+#define  CON_BYTE_DISABLE_2	(1<<21)		/* 16..23 */
+#define  CON_BYTE_DISABLE_1	(1<<20)		/* 8..15 */
+#define  CON_BYTE_DISABLE_0	(1<<19)		/* 0..7 */
+#define  CON_CTL_ADDR(x)	(x)
+
+#define FRAMER		0x80800		/* to 0x80bfc */
+
+/* 3.3 network controller (internal) mailbox registers */
+
+#define CS_STPER0	0x0
+	/* ... */
+#define CS_STPER31	0x01f
+
+#define CS_STTIM0	0x020
+	/* ... */
+#define CS_STTIM31	0x03f
+
+#define CS_TGRLD0	0x040
+	/* ... */
+#define CS_TGRLD15	0x04f
+
+#define CS_ERTHR0	0x050
+#define CS_ERTHR1	0x051
+#define CS_ERTHR2	0x052
+#define CS_ERTHR3	0x053
+#define CS_ERTHR4	0x054
+#define CS_ERCTL0	0x055
+#define  TX_ENABLE		(1<<28)
+#define  ER_ENABLE		(1<<27)
+#define CS_ERCTL1	0x056
+#define CS_ERCTL2	0x057
+#define CS_ERSTAT0	0x058
+#define CS_ERSTAT1	0x059
+
+#define CS_RTCCT	0x060
+#define CS_RTFWC	0x061
+#define CS_RTFWR	0x062
+#define CS_RTFTC	0x063
+#define CS_RTATR	0x064
+
+#define CS_TFBSET	0x070
+#define CS_TFBADD	0x071
+#define CS_TFBSUB	0x072
+#define CS_WCRMAX	0x073
+#define CS_WCRMIN	0x074
+#define CS_WCRINC	0x075
+#define CS_WCRDEC	0x076
+#define CS_WCRCEIL	0x077
+#define CS_BWDCNT	0x078
+
+#define CS_OTPPER	0x080
+#define CS_OTWPER	0x081
+#define CS_OTTLIM	0x082
+#define CS_OTTCNT	0x083
+
+#define CS_HGRRT0	0x090
+	/* ... */
+#define CS_HGRRT7	0x097
+
+#define CS_ORPTRS	0x0a0
+
+#define RXCON_CLOSE	0x100
+
+
+#define RCM_MEM_SIZE	0x10000		/* 1M of 32-bit registers */
+#define TCM_MEM_SIZE	0x20000		/* 2M of 32-bit registers */
+
+/* 2.5 transmit connection memory registers */
+
+#define TSR0_CONN_STATE(x)	((x>>28) & 0x7)
+#define TSR0_USE_WMIN		(1<<23)
+#define TSR0_GROUP(x)		((x & 0x7)<<18)
+#define TSR0_ABR		(2<<16)
+#define TSR0_UBR		(1<<16)
+#define TSR0_CBR		(0<<16)
+#define TSR0_PROT		(1<<15)
+#define TSR0_AAL0_SDU		(2<<12)
+#define TSR0_AAL0		(1<<12)
+#define TSR0_AAL5		(0<<12)
+#define TSR0_HALT_ER		(1<<11)
+#define TSR0_MARK_CI		(1<<10)
+#define TSR0_MARK_ER		(1<<9)
+#define TSR0_UPDATE_GER		(1<<8)
+#define TSR0_RC_INDEX(x)	(x & 0x1F)
+
+#define TSR1_PCR(x)		((x & 0x7FFF)<<16)
+#define TSR1_MCR(x)		(x & 0x7FFF)
+
+#define TSR2_ACR(x)		((x & 0x7FFF)<<16)
+
+#define TSR3_NRM_CNT(x)		((x & 0xFF)<<24)
+#define TSR3_CRM_CNT(x)		(x & 0xFFFF)
+
+#define TSR4_FLUSH_CONN		(1<<31)
+#define TSR4_SESSION_ENDED	(1<<30)
+#define TSR4_CRC10		(1<<28)
+#define TSR4_NULL_CRC10		(1<<27)
+#define TSR4_PROT		(1<<26)
+#define TSR4_AAL0_SDU		(2<<23)
+#define TSR4_AAL0		(1<<23)
+#define TSR4_AAL5		(0<<23)
+
+#define TSR9_OPEN_CONN		(1<<20)
+
+#define TSR11_ICR(x)		((x & 0x7FFF)<<16)
+#define TSR11_TRM(x)		((x & 0x7)<<13)
+#define TSR11_NRM(x)		((x & 0x7)<<10)
+#define TSR11_ADTF(x)		(x & 0x3FF)
+
+#define TSR13_RDF(x)		((x & 0xF)<<23)
+#define TSR13_RIF(x)		((x & 0xF)<<19)
+#define TSR13_CDF(x)		((x & 0x7)<<16)
+#define TSR13_CRM(x)		(x & 0xFFFF)
+
+#define TSR14_DELETE		(1<<31)
+#define TSR14_ABR_CLOSE		(1<<16)
+
+/* 2.7.1 per connection receieve state registers */
+
+#define RSR0_START_PDU	(1<<10)
+#define RSR0_OPEN_CONN	(1<<6)
+#define RSR0_CLOSE_CONN	(0<<6)
+#define RSR0_PPD_ENABLE	(1<<5)
+#define RSR0_EPD_ENABLE	(1<<4)
+#define RSR0_TCP_CKSUM	(1<<3)
+#define RSR0_AAL5		(0)
+#define RSR0_AAL0		(1)
+#define RSR0_AAL0_SDU		(2)
+#define RSR0_RAWCELL		(3)
+#define RSR0_RAWCELL_CRC10	(4)
+
+#define RSR1_AQI_ENABLE	(1<<20)
+#define RSR1_RBPL_ONLY	(1<<19)
+#define RSR1_GROUP(x)	((x)<<16)
+
+#define RSR4_AQI_ENABLE (1<<30)
+#define RSR4_GROUP(x)	((x)<<27)
+#define RSR4_RBPL_ONLY	(1<<26)
+
+/* 2.1.4 transmit packet descriptor */
+
+#define	TPD_USERCELL		0x0
+#define	TPD_SEGMENT_OAMF5	0x4
+#define	TPD_END2END_OAMF5	0x5
+#define	TPD_RMCELL		0x6
+#define TPD_CELLTYPE(x)		(x<<3)
+#define TPD_EOS			(1<<2)
+#define TPD_CLP			(1<<1)
+#define TPD_INT			(1<<0)
+#define TPD_LST		(1<<31)
+
+/* table 4.3 serial eeprom information */
+
+#define PROD_ID		0x08	/* char[] */
+#define  PROD_ID_LEN	30
+#define HW_REV		0x26	/* char[] */
+#define M_SN		0x3a	/* integer */
+#define MEDIA		0x3e	/* integer */
+#define  HE155MM	0x26
+#define  HE155SM	0x27
+#define  HE622MM	0x46
+#define  HE622SM	0x47
+#define MAC_ADDR	0x42	/* char[] */
+
+#define CS_LOW		0x0
+#define CS_HIGH		ID_CS /* HOST_CNTL_ID_PROM_SEL */
+#define CLK_LOW		0x0
+#define CLK_HIGH	ID_CLOCK /* HOST_CNTL_ID_PROM_CLOCK */
+#define SI_HIGH		ID_DIN /* HOST_CNTL_ID_PROM_DATA_IN */
+#define EEPROM_DELAY	400 /* microseconds */
+
+#endif /* _HE_H_ */
diff --git a/drivers/atm/horizon.c b/drivers/atm/horizon.c
new file mode 100644
index 0000000..924a2c8
--- /dev/null
+++ b/drivers/atm/horizon.c
@@ -0,0 +1,2953 @@
+/*
+  Madge Horizon ATM Adapter driver.
+  Copyright (C) 1995-1999  Madge Networks Ltd.
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+  
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  
+  The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+  system and in the file COPYING in the Linux kernel source.
+*/
+
+/*
+  IMPORTANT NOTE: Madge Networks no longer makes the adapters
+  supported by this driver and makes no commitment to maintain it.
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/skbuff.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/uio.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/wait.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <asm/string.h>
+#include <asm/byteorder.h>
+
+#include "horizon.h"
+
+#define maintainer_string "Giuliano Procida at Madge Networks <gprocida@madge.com>"
+#define description_string "Madge ATM Horizon [Ultra] driver"
+#define version_string "1.2.1"
+
+static inline void __init show_version (void) {
+  printk ("%s version %s\n", description_string, version_string);
+}
+
+/*
+  
+  CREDITS
+  
+  Driver and documentation by:
+  
+  Chris Aston        Madge Networks
+  Giuliano Procida   Madge Networks
+  Simon Benham       Madge Networks
+  Simon Johnson      Madge Networks
+  Various Others     Madge Networks
+  
+  Some inspiration taken from other drivers by:
+  
+  Alexandru Cucos    UTBv
+  Kari Mettinen      University of Helsinki
+  Werner Almesberger EPFL LRC
+  
+  Theory of Operation
+  
+  I Hardware, detection, initialisation and shutdown.
+  
+  1. Supported Hardware
+  
+  This driver should handle all variants of the PCI Madge ATM adapters
+  with the Horizon chipset. These are all PCI cards supporting PIO, BM
+  DMA and a form of MMIO (registers only, not internal RAM).
+  
+  The driver is only known to work with SONET and UTP Horizon Ultra
+  cards at 155Mb/s. However, code is in place to deal with both the
+  original Horizon and 25Mb/s operation.
+  
+  There are two revisions of the Horizon ASIC: the original and the
+  Ultra. Details of hardware bugs are in section III.
+  
+  The ASIC version can be distinguished by chip markings but is NOT
+  indicated by the PCI revision (all adapters seem to have PCI rev 1).
+  
+  I believe that:
+  
+  Horizon       => Collage  25 PCI Adapter (UTP and STP)
+  Horizon Ultra => Collage 155 PCI Client (UTP or SONET)
+  Ambassador x  => Collage 155 PCI Server (completely different)
+  
+  Horizon (25Mb/s) is fitted with UTP and STP connectors. It seems to
+  have a Madge B154 plus glue logic serializer. I have also found a
+  really ancient version of this with slightly different glue. It
+  comes with the revision 0 (140-025-01) ASIC.
+  
+  Horizon Ultra (155Mb/s) is fitted with either a Pulse Medialink
+  output (UTP) or an HP HFBR 5205 output (SONET). It has either
+  Madge's SAMBA framer or a SUNI-lite device (early versions). It
+  comes with the revision 1 (140-027-01) ASIC.
+  
+  2. Detection
+  
+  All Horizon-based cards present with the same PCI Vendor and Device
+  IDs. The standard Linux 2.2 PCI API is used to locate any cards and
+  to enable bus-mastering (with appropriate latency).
+  
+  ATM_LAYER_STATUS in the control register distinguishes between the
+  two possible physical layers (25 and 155). It is not clear whether
+  the 155 cards can also operate at 25Mbps. We rely on the fact that a
+  card operates at 155 if and only if it has the newer Horizon Ultra
+  ASIC.
+  
+  For 155 cards the two possible framers are probed for and then set
+  up for loop-timing.
+  
+  3. Initialisation
+  
+  The card is reset and then put into a known state. The physical
+  layer is configured for normal operation at the appropriate speed;
+  in the case of the 155 cards, the framer is initialised with
+  line-based timing; the internal RAM is zeroed and the allocation of
+  buffers for RX and TX is made; the Burnt In Address is read and
+  copied to the ATM ESI; various policy settings for RX (VPI bits,
+  unknown VCs, oam cells) are made. Ideally all policy items should be
+  configurable at module load (if not actually on-demand), however,
+  only the vpi vs vci bit allocation can be specified at insmod.
+  
+  4. Shutdown
+  
+  This is in response to module_cleaup. No VCs are in use and the card
+  should be idle; it is reset.
+  
+  II Driver software (as it should be)
+  
+  0. Traffic Parameters
+  
+  The traffic classes (not an enumeration) are currently: ATM_NONE (no
+  traffic), ATM_UBR, ATM_CBR, ATM_VBR and ATM_ABR, ATM_ANYCLASS
+  (compatible with everything). Together with (perhaps only some of)
+  the following items they make up the traffic specification.
+  
+  struct atm_trafprm {
+    unsigned char traffic_class; traffic class (ATM_UBR, ...)
+    int           max_pcr;       maximum PCR in cells per second
+    int           pcr;           desired PCR in cells per second
+    int           min_pcr;       minimum PCR in cells per second
+    int           max_cdv;       maximum CDV in microseconds
+    int           max_sdu;       maximum SDU in bytes
+  };
+  
+  Note that these denote bandwidth available not bandwidth used; the
+  possibilities according to ATMF are:
+  
+  Real Time (cdv and max CDT given)
+  
+  CBR(pcr)             pcr bandwidth always available
+  rtVBR(pcr,scr,mbs)   scr bandwidth always available, upto pcr at mbs too
+  
+  Non Real Time
+  
+  nrtVBR(pcr,scr,mbs)  scr bandwidth always available, upto pcr at mbs too
+  UBR()
+  ABR(mcr,pcr)         mcr bandwidth always available, upto pcr (depending) too
+  
+  mbs is max burst size (bucket)
+  pcr and scr have associated cdvt values
+  mcr is like scr but has no cdtv
+  cdtv may differ at each hop
+  
+  Some of the above items are qos items (as opposed to traffic
+  parameters). We have nothing to do with qos. All except ABR can have
+  their traffic parameters converted to GCRA parameters. The GCRA may
+  be implemented as a (real-number) leaky bucket. The GCRA can be used
+  in complicated ways by switches and in simpler ways by end-stations.
+  It can be used both to filter incoming cells and shape out-going
+  cells.
+  
+  ATM Linux actually supports:
+  
+  ATM_NONE() (no traffic in this direction)
+  ATM_UBR(max_frame_size)
+  ATM_CBR(max/min_pcr, max_cdv, max_frame_size)
+  
+  0 or ATM_MAX_PCR are used to indicate maximum available PCR
+  
+  A traffic specification consists of the AAL type and separate
+  traffic specifications for either direction. In ATM Linux it is:
+  
+  struct atm_qos {
+  struct atm_trafprm txtp;
+  struct atm_trafprm rxtp;
+  unsigned char aal;
+  };
+  
+  AAL types are:
+  
+  ATM_NO_AAL    AAL not specified
+  ATM_AAL0      "raw" ATM cells
+  ATM_AAL1      AAL1 (CBR)
+  ATM_AAL2      AAL2 (VBR)
+  ATM_AAL34     AAL3/4 (data)
+  ATM_AAL5      AAL5 (data)
+  ATM_SAAL      signaling AAL
+  
+  The Horizon has support for AAL frame types: 0, 3/4 and 5. However,
+  it does not implement AAL 3/4 SAR and it has a different notion of
+  "raw cell" to ATM Linux's (48 bytes vs. 52 bytes) so neither are
+  supported by this driver.
+  
+  The Horizon has limited support for ABR (including UBR), VBR and
+  CBR. Each TX channel has a bucket (containing up to 31 cell units)
+  and two timers (PCR and SCR) associated with it that can be used to
+  govern cell emissions and host notification (in the case of ABR this
+  is presumably so that RM cells may be emitted at appropriate times).
+  The timers may either be disabled or may be set to any of 240 values
+  (determined by the clock crystal, a fixed (?) per-device divider, a
+  configurable divider and a configurable timer preload value).
+  
+  At the moment only UBR and CBR are supported by the driver. VBR will
+  be supported as soon as ATM for Linux supports it. ABR support is
+  very unlikely as RM cell handling is completely up to the driver.
+  
+  1. TX (TX channel setup and TX transfer)
+  
+  The TX half of the driver owns the TX Horizon registers. The TX
+  component in the IRQ handler is the BM completion handler. This can
+  only be entered when tx_busy is true (enforced by hardware). The
+  other TX component can only be entered when tx_busy is false
+  (enforced by driver). So TX is single-threaded.
+  
+  Apart from a minor optimisation to not re-select the last channel,
+  the TX send component works as follows:
+  
+  Atomic test and set tx_busy until we succeed; we should implement
+  some sort of timeout so that tx_busy will never be stuck at true.
+  
+  If no TX channel is set up for this VC we wait for an idle one (if
+  necessary) and set it up.
+  
+  At this point we have a TX channel ready for use. We wait for enough
+  buffers to become available then start a TX transmit (set the TX
+  descriptor, schedule transfer, exit).
+  
+  The IRQ component handles TX completion (stats, free buffer, tx_busy
+  unset, exit). We also re-schedule further transfers for the same
+  frame if needed.
+  
+  TX setup in more detail:
+  
+  TX open is a nop, the relevant information is held in the hrz_vcc
+  (vcc->dev_data) structure and is "cached" on the card.
+  
+  TX close gets the TX lock and clears the channel from the "cache".
+  
+  2. RX (Data Available and RX transfer)
+  
+  The RX half of the driver owns the RX registers. There are two RX
+  components in the IRQ handler: the data available handler deals with
+  fresh data that has arrived on the card, the BM completion handler
+  is very similar to the TX completion handler. The data available
+  handler grabs the rx_lock and it is only released once the data has
+  been discarded or completely transferred to the host. The BM
+  completion handler only runs when the lock is held; the data
+  available handler is locked out over the same period.
+  
+  Data available on the card triggers an interrupt. If the data is not
+  suitable for our existing RX channels or we cannot allocate a buffer
+  it is flushed. Otherwise an RX receive is scheduled. Multiple RX
+  transfers may be scheduled for the same frame.
+  
+  RX setup in more detail:
+  
+  RX open...
+  RX close...
+  
+  III Hardware Bugs
+  
+  0. Byte vs Word addressing of adapter RAM.
+  
+  A design feature; see the .h file (especially the memory map).
+  
+  1. Bus Master Data Transfers (original Horizon only, fixed in Ultra)
+  
+  The host must not start a transmit direction transfer at a
+  non-four-byte boundary in host memory. Instead the host should
+  perform a byte, or a two byte, or one byte followed by two byte
+  transfer in order to start the rest of the transfer on a four byte
+  boundary. RX is OK.
+  
+  Simultaneous transmit and receive direction bus master transfers are
+  not allowed.
+  
+  The simplest solution to these two is to always do PIO (never DMA)
+  in the TX direction on the original Horizon. More complicated
+  solutions are likely to hurt my brain.
+  
+  2. Loss of buffer on close VC
+  
+  When a VC is being closed, the buffer associated with it is not
+  returned to the pool. The host must store the reference to this
+  buffer and when opening a new VC then give it to that new VC.
+  
+  The host intervention currently consists of stacking such a buffer
+  pointer at VC close and checking the stack at VC open.
+  
+  3. Failure to close a VC
+  
+  If a VC is currently receiving a frame then closing the VC may fail
+  and the frame continues to be received.
+  
+  The solution is to make sure any received frames are flushed when
+  ready. This is currently done just before the solution to 2.
+  
+  4. PCI bus (original Horizon only, fixed in Ultra)
+  
+  Reading from the data port prior to initialisation will hang the PCI
+  bus. Just don't do that then! We don't.
+  
+  IV To Do List
+  
+  . Timer code may be broken.
+  
+  . Allow users to specify buffer allocation split for TX and RX.
+  
+  . Deal once and for all with buggy VC close.
+  
+  . Handle interrupted and/or non-blocking operations.
+  
+  . Change some macros to functions and move from .h to .c.
+  
+  . Try to limit the number of TX frames each VC may have queued, in
+    order to reduce the chances of TX buffer exhaustion.
+  
+  . Implement VBR (bucket and timers not understood) and ABR (need to
+    do RM cells manually); also no Linux support for either.
+  
+  . Implement QoS changes on open VCs (involves extracting parts of VC open
+    and close into separate functions and using them to make changes).
+  
+*/
+
+/********** globals **********/
+
+static void do_housekeeping (unsigned long arg);
+
+static unsigned short debug = 0;
+static unsigned short vpi_bits = 0;
+static int max_tx_size = 9000;
+static int max_rx_size = 9000;
+static unsigned char pci_lat = 0;
+
+/********** access functions **********/
+
+/* Read / Write Horizon registers */
+static inline void wr_regl (const hrz_dev * dev, unsigned char reg, u32 data) {
+  outl (cpu_to_le32 (data), dev->iobase + reg);
+}
+
+static inline u32 rd_regl (const hrz_dev * dev, unsigned char reg) {
+  return le32_to_cpu (inl (dev->iobase + reg));
+}
+
+static inline void wr_regw (const hrz_dev * dev, unsigned char reg, u16 data) {
+  outw (cpu_to_le16 (data), dev->iobase + reg);
+}
+
+static inline u16 rd_regw (const hrz_dev * dev, unsigned char reg) {
+  return le16_to_cpu (inw (dev->iobase + reg));
+}
+
+static inline void wrs_regb (const hrz_dev * dev, unsigned char reg, void * addr, u32 len) {
+  outsb (dev->iobase + reg, addr, len);
+}
+
+static inline void rds_regb (const hrz_dev * dev, unsigned char reg, void * addr, u32 len) {
+  insb (dev->iobase + reg, addr, len);
+}
+
+/* Read / Write to a given address in Horizon buffer memory.
+   Interrupts must be disabled between the address register and data
+   port accesses as these must form an atomic operation. */
+static inline void wr_mem (const hrz_dev * dev, HDW * addr, u32 data) {
+  // wr_regl (dev, MEM_WR_ADDR_REG_OFF, (u32) addr);
+  wr_regl (dev, MEM_WR_ADDR_REG_OFF, (addr - (HDW *) 0) * sizeof(HDW));
+  wr_regl (dev, MEMORY_PORT_OFF, data);
+}
+
+static inline u32 rd_mem (const hrz_dev * dev, HDW * addr) {
+  // wr_regl (dev, MEM_RD_ADDR_REG_OFF, (u32) addr);
+  wr_regl (dev, MEM_RD_ADDR_REG_OFF, (addr - (HDW *) 0) * sizeof(HDW));
+  return rd_regl (dev, MEMORY_PORT_OFF);
+}
+
+static inline void wr_framer (const hrz_dev * dev, u32 addr, u32 data) {
+  wr_regl (dev, MEM_WR_ADDR_REG_OFF, (u32) addr | 0x80000000);
+  wr_regl (dev, MEMORY_PORT_OFF, data);
+}
+
+static inline u32 rd_framer (const hrz_dev * dev, u32 addr) {
+  wr_regl (dev, MEM_RD_ADDR_REG_OFF, (u32) addr | 0x80000000);
+  return rd_regl (dev, MEMORY_PORT_OFF);
+}
+
+/********** specialised access functions **********/
+
+/* RX */
+
+static inline void FLUSH_RX_CHANNEL (hrz_dev * dev, u16 channel) {
+  wr_regw (dev, RX_CHANNEL_PORT_OFF, FLUSH_CHANNEL | channel);
+  return;
+}
+
+static inline void WAIT_FLUSH_RX_COMPLETE (hrz_dev * dev) {
+  while (rd_regw (dev, RX_CHANNEL_PORT_OFF) & FLUSH_CHANNEL)
+    ;
+  return;
+}
+
+static inline void SELECT_RX_CHANNEL (hrz_dev * dev, u16 channel) {
+  wr_regw (dev, RX_CHANNEL_PORT_OFF, channel);
+  return;
+}
+
+static inline void WAIT_UPDATE_COMPLETE (hrz_dev * dev) {
+  while (rd_regw (dev, RX_CHANNEL_PORT_OFF) & RX_CHANNEL_UPDATE_IN_PROGRESS)
+    ;
+  return;
+}
+
+/* TX */
+
+static inline void SELECT_TX_CHANNEL (hrz_dev * dev, u16 tx_channel) {
+  wr_regl (dev, TX_CHANNEL_PORT_OFF, tx_channel);
+  return;
+}
+
+/* Update or query one configuration parameter of a particular channel. */
+
+static inline void update_tx_channel_config (hrz_dev * dev, short chan, u8 mode, u16 value) {
+  wr_regw (dev, TX_CHANNEL_CONFIG_COMMAND_OFF,
+	   chan * TX_CHANNEL_CONFIG_MULT | mode);
+    wr_regw (dev, TX_CHANNEL_CONFIG_DATA_OFF, value);
+    return;
+}
+
+static inline u16 query_tx_channel_config (hrz_dev * dev, short chan, u8 mode) {
+  wr_regw (dev, TX_CHANNEL_CONFIG_COMMAND_OFF,
+	   chan * TX_CHANNEL_CONFIG_MULT | mode);
+    return rd_regw (dev, TX_CHANNEL_CONFIG_DATA_OFF);
+}
+
+/********** dump functions **********/
+
+static inline void dump_skb (char * prefix, unsigned int vc, struct sk_buff * skb) {
+#ifdef DEBUG_HORIZON
+  unsigned int i;
+  unsigned char * data = skb->data;
+  PRINTDB (DBG_DATA, "%s(%u) ", prefix, vc);
+  for (i=0; i<skb->len && i < 256;i++)
+    PRINTDM (DBG_DATA, "%02x ", data[i]);
+  PRINTDE (DBG_DATA,"");
+#else
+  (void) prefix;
+  (void) vc;
+  (void) skb;
+#endif
+  return;
+}
+
+static inline void dump_regs (hrz_dev * dev) {
+#ifdef DEBUG_HORIZON
+  PRINTD (DBG_REGS, "CONTROL 0: %#x", rd_regl (dev, CONTROL_0_REG));
+  PRINTD (DBG_REGS, "RX CONFIG: %#x", rd_regw (dev, RX_CONFIG_OFF));
+  PRINTD (DBG_REGS, "TX CONFIG: %#x", rd_regw (dev, TX_CONFIG_OFF));
+  PRINTD (DBG_REGS, "TX STATUS: %#x", rd_regw (dev, TX_STATUS_OFF));
+  PRINTD (DBG_REGS, "IRQ ENBLE: %#x", rd_regl (dev, INT_ENABLE_REG_OFF));
+  PRINTD (DBG_REGS, "IRQ SORCE: %#x", rd_regl (dev, INT_SOURCE_REG_OFF));
+#else
+  (void) dev;
+#endif
+  return;
+}
+
+static inline void dump_framer (hrz_dev * dev) {
+#ifdef DEBUG_HORIZON
+  unsigned int i;
+  PRINTDB (DBG_REGS, "framer registers:");
+  for (i = 0; i < 0x10; ++i)
+    PRINTDM (DBG_REGS, " %02x", rd_framer (dev, i));
+  PRINTDE (DBG_REGS,"");
+#else
+  (void) dev;
+#endif
+  return;
+}
+
+/********** VPI/VCI <-> (RX) channel conversions **********/
+
+/* RX channels are 10 bit integers, these fns are quite paranoid */
+
+static inline int channel_to_vpivci (const u16 channel, short * vpi, int * vci) {
+  unsigned short vci_bits = 10 - vpi_bits;
+  if ((channel & RX_CHANNEL_MASK) == channel) {
+    *vci = channel & ((~0)<<vci_bits);
+    *vpi = channel >> vci_bits;
+    return channel ? 0 : -EINVAL;
+  }
+  return -EINVAL;
+}
+
+static inline int vpivci_to_channel (u16 * channel, const short vpi, const int vci) {
+  unsigned short vci_bits = 10 - vpi_bits;
+  if (0 <= vpi && vpi < 1<<vpi_bits && 0 <= vci && vci < 1<<vci_bits) {
+    *channel = vpi<<vci_bits | vci;
+    return *channel ? 0 : -EINVAL;
+  }
+  return -EINVAL;
+}
+
+/********** decode RX queue entries **********/
+
+static inline u16 rx_q_entry_to_length (u32 x) {
+  return x & RX_Q_ENTRY_LENGTH_MASK;
+}
+
+static inline u16 rx_q_entry_to_rx_channel (u32 x) {
+  return (x>>RX_Q_ENTRY_CHANNEL_SHIFT) & RX_CHANNEL_MASK;
+}
+
+/* Cell Transmit Rate Values
+ *
+ * the cell transmit rate (cells per sec) can be set to a variety of
+ * different values by specifying two parameters: a timer preload from
+ * 1 to 16 (stored as 0 to 15) and a clock divider (2 to the power of
+ * an exponent from 0 to 14; the special value 15 disables the timer).
+ *
+ * cellrate = baserate / (preload * 2^divider)
+ *
+ * The maximum cell rate that can be specified is therefore just the
+ * base rate. Halving the preload is equivalent to adding 1 to the
+ * divider and so values 1 to 8 of the preload are redundant except
+ * in the case of a maximal divider (14).
+ *
+ * Given a desired cell rate, an algorithm to determine the preload
+ * and divider is:
+ * 
+ * a) x = baserate / cellrate, want p * 2^d = x (as far as possible)
+ * b) if x > 16 * 2^14 then set p = 16, d = 14 (min rate), done
+ *    if x <= 16 then set p = x, d = 0 (high rates), done
+ * c) now have 16 < x <= 2^18, or 1 < x/16 <= 2^14 and we want to
+ *    know n such that 2^(n-1) < x/16 <= 2^n, so slide a bit until
+ *    we find the range (n will be between 1 and 14), set d = n
+ * d) Also have 8 < x/2^n <= 16, so set p nearest x/2^n
+ *
+ * The algorithm used below is a minor variant of the above.
+ *
+ * The base rate is derived from the oscillator frequency (Hz) using a
+ * fixed divider:
+ *
+ * baserate = freq / 32 in the case of some Unknown Card
+ * baserate = freq / 8  in the case of the Horizon        25
+ * baserate = freq / 8  in the case of the Horizon Ultra 155
+ *
+ * The Horizon cards have oscillators and base rates as follows:
+ *
+ * Card               Oscillator  Base Rate
+ * Unknown Card       33 MHz      1.03125 MHz (33 MHz = PCI freq)
+ * Horizon        25  32 MHz      4       MHz
+ * Horizon Ultra 155  40 MHz      5       MHz
+ *
+ * The following defines give the base rates in Hz. These were
+ * previously a factor of 100 larger, no doubt someone was using
+ * cps*100.
+ */
+
+#define BR_UKN 1031250l
+#define BR_HRZ 4000000l
+#define BR_ULT 5000000l
+
+// d is an exponent
+#define CR_MIND 0
+#define CR_MAXD 14
+
+// p ranges from 1 to a power of 2
+#define CR_MAXPEXP 4
+ 
+static int make_rate (const hrz_dev * dev, u32 c, rounding r,
+		      u16 * bits, unsigned int * actual)
+{
+	// note: rounding the rate down means rounding 'p' up
+	const unsigned long br = test_bit(ultra, &dev->flags) ? BR_ULT : BR_HRZ;
+  
+	u32 div = CR_MIND;
+	u32 pre;
+  
+	// br_exp and br_man are used to avoid overflowing (c*maxp*2^d) in
+	// the tests below. We could think harder about exact possibilities
+	// of failure...
+  
+	unsigned long br_man = br;
+	unsigned int br_exp = 0;
+  
+	PRINTD (DBG_QOS|DBG_FLOW, "make_rate b=%lu, c=%u, %s", br, c,
+		r == round_up ? "up" : r == round_down ? "down" : "nearest");
+  
+	// avoid div by zero
+	if (!c) {
+		PRINTD (DBG_QOS|DBG_ERR, "zero rate is not allowed!");
+		return -EINVAL;
+	}
+  
+	while (br_exp < CR_MAXPEXP + CR_MIND && (br_man % 2 == 0)) {
+		br_man = br_man >> 1;
+		++br_exp;
+	}
+	// (br >>br_exp) <<br_exp == br and
+	// br_exp <= CR_MAXPEXP+CR_MIND
+  
+	if (br_man <= (c << (CR_MAXPEXP+CR_MIND-br_exp))) {
+		// Equivalent to: B <= (c << (MAXPEXP+MIND))
+		// take care of rounding
+		switch (r) {
+			case round_down:
+				pre = (br+(c<<div)-1)/(c<<div);
+				// but p must be non-zero
+				if (!pre)
+					pre = 1;
+				break;
+			case round_nearest:
+				pre = (br+(c<<div)/2)/(c<<div);
+				// but p must be non-zero
+				if (!pre)
+					pre = 1;
+				break;
+			default:	/* round_up */
+				pre = br/(c<<div);
+				// but p must be non-zero
+				if (!pre)
+					return -EINVAL;
+		}
+		PRINTD (DBG_QOS, "A: p=%u, d=%u", pre, div);
+		goto got_it;
+	}
+  
+	// at this point we have
+	// d == MIND and (c << (MAXPEXP+MIND)) < B
+	while (div < CR_MAXD) {
+		div++;
+		if (br_man <= (c << (CR_MAXPEXP+div-br_exp))) {
+			// Equivalent to: B <= (c << (MAXPEXP+d))
+			// c << (MAXPEXP+d-1) < B <= c << (MAXPEXP+d)
+			// 1 << (MAXPEXP-1) < B/2^d/c <= 1 << MAXPEXP
+			// MAXP/2 < B/c2^d <= MAXP
+			// take care of rounding
+			switch (r) {
+				case round_down:
+					pre = (br+(c<<div)-1)/(c<<div);
+					break;
+				case round_nearest:
+					pre = (br+(c<<div)/2)/(c<<div);
+					break;
+				default: /* round_up */
+					pre = br/(c<<div);
+			}
+			PRINTD (DBG_QOS, "B: p=%u, d=%u", pre, div);
+			goto got_it;
+		}
+	}
+	// at this point we have
+	// d == MAXD and (c << (MAXPEXP+MAXD)) < B
+	// but we cannot go any higher
+	// take care of rounding
+	if (r == round_down)
+		return -EINVAL;
+	pre = 1 << CR_MAXPEXP;
+	PRINTD (DBG_QOS, "C: p=%u, d=%u", pre, div);
+got_it:
+	// paranoia
+	if (div > CR_MAXD || (!pre) || pre > 1<<CR_MAXPEXP) {
+		PRINTD (DBG_QOS, "set_cr internal failure: d=%u p=%u",
+			div, pre);
+		return -EINVAL;
+	} else {
+		if (bits)
+			*bits = (div<<CLOCK_SELECT_SHIFT) | (pre-1);
+		if (actual) {
+			*actual = (br + (pre<<div) - 1) / (pre<<div);
+			PRINTD (DBG_QOS, "actual rate: %u", *actual);
+		}
+		return 0;
+	}
+}
+
+static int make_rate_with_tolerance (const hrz_dev * dev, u32 c, rounding r, unsigned int tol,
+				     u16 * bit_pattern, unsigned int * actual) {
+  unsigned int my_actual;
+  
+  PRINTD (DBG_QOS|DBG_FLOW, "make_rate_with_tolerance c=%u, %s, tol=%u",
+	  c, (r == round_up) ? "up" : (r == round_down) ? "down" : "nearest", tol);
+  
+  if (!actual)
+    // actual rate is not returned
+    actual = &my_actual;
+  
+  if (make_rate (dev, c, round_nearest, bit_pattern, actual))
+    // should never happen as round_nearest always succeeds
+    return -1;
+  
+  if (c - tol <= *actual && *actual <= c + tol)
+    // within tolerance
+    return 0;
+  else
+    // intolerant, try rounding instead
+    return make_rate (dev, c, r, bit_pattern, actual);
+}
+
+/********** Listen on a VC **********/
+
+static int hrz_open_rx (hrz_dev * dev, u16 channel) {
+  // is there any guarantee that we don't get two simulataneous
+  // identical calls of this function from different processes? yes
+  // rate_lock
+  unsigned long flags;
+  u32 channel_type; // u16?
+  
+  u16 buf_ptr = RX_CHANNEL_IDLE;
+  
+  rx_ch_desc * rx_desc = &memmap->rx_descs[channel];
+  
+  PRINTD (DBG_FLOW, "hrz_open_rx %x", channel);
+  
+  spin_lock_irqsave (&dev->mem_lock, flags);
+  channel_type = rd_mem (dev, &rx_desc->wr_buf_type) & BUFFER_PTR_MASK;
+  spin_unlock_irqrestore (&dev->mem_lock, flags);
+  
+  // very serious error, should never occur
+  if (channel_type != RX_CHANNEL_DISABLED) {
+    PRINTD (DBG_ERR|DBG_VCC, "RX channel for VC already open");
+    return -EBUSY; // clean up?
+  }
+  
+  // Give back spare buffer
+  if (dev->noof_spare_buffers) {
+    buf_ptr = dev->spare_buffers[--dev->noof_spare_buffers];
+    PRINTD (DBG_VCC, "using a spare buffer: %u", buf_ptr);
+    // should never occur
+    if (buf_ptr == RX_CHANNEL_DISABLED || buf_ptr == RX_CHANNEL_IDLE) {
+      // but easy to recover from
+      PRINTD (DBG_ERR|DBG_VCC, "bad spare buffer pointer, using IDLE");
+      buf_ptr = RX_CHANNEL_IDLE;
+    }
+  } else {
+    PRINTD (DBG_VCC, "using IDLE buffer pointer");
+  }
+  
+  // Channel is currently disabled so change its status to idle
+  
+  // do we really need to save the flags again?
+  spin_lock_irqsave (&dev->mem_lock, flags);
+  
+  wr_mem (dev, &rx_desc->wr_buf_type,
+	  buf_ptr | CHANNEL_TYPE_AAL5 | FIRST_CELL_OF_AAL5_FRAME);
+  if (buf_ptr != RX_CHANNEL_IDLE)
+    wr_mem (dev, &rx_desc->rd_buf_type, buf_ptr);
+  
+  spin_unlock_irqrestore (&dev->mem_lock, flags);
+  
+  // rxer->rate = make_rate (qos->peak_cells);
+  
+  PRINTD (DBG_FLOW, "hrz_open_rx ok");
+  
+  return 0;
+}
+
+#if 0
+/********** change vc rate for a given vc **********/
+
+static void hrz_change_vc_qos (ATM_RXER * rxer, MAAL_QOS * qos) {
+  rxer->rate = make_rate (qos->peak_cells);
+}
+#endif
+
+/********** free an skb (as per ATM device driver documentation) **********/
+
+static inline void hrz_kfree_skb (struct sk_buff * skb) {
+  if (ATM_SKB(skb)->vcc->pop) {
+    ATM_SKB(skb)->vcc->pop (ATM_SKB(skb)->vcc, skb);
+  } else {
+    dev_kfree_skb_any (skb);
+  }
+}
+
+/********** cancel listen on a VC **********/
+
+static void hrz_close_rx (hrz_dev * dev, u16 vc) {
+  unsigned long flags;
+  
+  u32 value;
+  
+  u32 r1, r2;
+  
+  rx_ch_desc * rx_desc = &memmap->rx_descs[vc];
+  
+  int was_idle = 0;
+  
+  spin_lock_irqsave (&dev->mem_lock, flags);
+  value = rd_mem (dev, &rx_desc->wr_buf_type) & BUFFER_PTR_MASK;
+  spin_unlock_irqrestore (&dev->mem_lock, flags);
+  
+  if (value == RX_CHANNEL_DISABLED) {
+    // I suppose this could happen once we deal with _NONE traffic properly
+    PRINTD (DBG_VCC, "closing VC: RX channel %u already disabled", vc);
+    return;
+  }
+  if (value == RX_CHANNEL_IDLE)
+    was_idle = 1;
+  
+  spin_lock_irqsave (&dev->mem_lock, flags);
+  
+  for (;;) {
+    wr_mem (dev, &rx_desc->wr_buf_type, RX_CHANNEL_DISABLED);
+    
+    if ((rd_mem (dev, &rx_desc->wr_buf_type) & BUFFER_PTR_MASK) == RX_CHANNEL_DISABLED)
+      break;
+    
+    was_idle = 0;
+  }
+  
+  if (was_idle) {
+    spin_unlock_irqrestore (&dev->mem_lock, flags);
+    return;
+  }
+  
+  WAIT_FLUSH_RX_COMPLETE(dev);
+  
+  // XXX Is this all really necessary? We can rely on the rx_data_av
+  // handler to discard frames that remain queued for delivery. If the
+  // worry is that immediately reopening the channel (perhaps by a
+  // different process) may cause some data to be mis-delivered then
+  // there may still be a simpler solution (such as busy-waiting on
+  // rx_busy once the channel is disabled or before a new one is
+  // opened - does this leave any holes?). Arguably setting up and
+  // tearing down the TX and RX halves of each virtual circuit could
+  // most safely be done within ?x_busy protected regions.
+  
+  // OK, current changes are that Simon's marker is disabled and we DO
+  // look for NULL rxer elsewhere. The code here seems flush frames
+  // and then remember the last dead cell belonging to the channel
+  // just disabled - the cell gets relinked at the next vc_open.
+  // However, when all VCs are closed or only a few opened there are a
+  // handful of buffers that are unusable.
+  
+  // Does anyone feel like documenting spare_buffers properly?
+  // Does anyone feel like fixing this in a nicer way?
+  
+  // Flush any data which is left in the channel
+  for (;;) {
+    // Change the rx channel port to something different to the RX
+    // channel we are trying to close to force Horizon to flush the rx
+    // channel read and write pointers.
+    
+    u16 other = vc^(RX_CHANS/2);
+    
+    SELECT_RX_CHANNEL (dev, other);
+    WAIT_UPDATE_COMPLETE (dev);
+    
+    r1 = rd_mem (dev, &rx_desc->rd_buf_type);
+    
+    // Select this RX channel. Flush doesn't seem to work unless we
+    // select an RX channel before hand
+    
+    SELECT_RX_CHANNEL (dev, vc);
+    WAIT_UPDATE_COMPLETE (dev);
+    
+    // Attempt to flush a frame on this RX channel
+    
+    FLUSH_RX_CHANNEL (dev, vc);
+    WAIT_FLUSH_RX_COMPLETE (dev);
+    
+    // Force Horizon to flush rx channel read and write pointers as before
+    
+    SELECT_RX_CHANNEL (dev, other);
+    WAIT_UPDATE_COMPLETE (dev);
+    
+    r2 = rd_mem (dev, &rx_desc->rd_buf_type);
+    
+    PRINTD (DBG_VCC|DBG_RX, "r1 = %u, r2 = %u", r1, r2);
+    
+    if (r1 == r2) {
+      dev->spare_buffers[dev->noof_spare_buffers++] = (u16)r1;
+      break;
+    }
+  }
+  
+#if 0
+  {
+    rx_q_entry * wr_ptr = &memmap->rx_q_entries[rd_regw (dev, RX_QUEUE_WR_PTR_OFF)];
+    rx_q_entry * rd_ptr = dev->rx_q_entry;
+    
+    PRINTD (DBG_VCC|DBG_RX, "rd_ptr = %u, wr_ptr = %u", rd_ptr, wr_ptr);
+    
+    while (rd_ptr != wr_ptr) {
+      u32 x = rd_mem (dev, (HDW *) rd_ptr);
+      
+      if (vc == rx_q_entry_to_rx_channel (x)) {
+	x |= SIMONS_DODGEY_MARKER;
+	
+	PRINTD (DBG_RX|DBG_VCC|DBG_WARN, "marking a frame as dodgey");
+	
+	wr_mem (dev, (HDW *) rd_ptr, x);
+      }
+      
+      if (rd_ptr == dev->rx_q_wrap)
+	rd_ptr = dev->rx_q_reset;
+      else
+	rd_ptr++;
+    }
+  }
+#endif
+  
+  spin_unlock_irqrestore (&dev->mem_lock, flags);
+  
+  return;
+}
+
+/********** schedule RX transfers **********/
+
+// Note on tail recursion: a GCC developer said that it is not likely
+// to be fixed soon, so do not define TAILRECUSRIONWORKS unless you
+// are sure it does as you may otherwise overflow the kernel stack.
+
+// giving this fn a return value would help GCC, alledgedly
+
+static void rx_schedule (hrz_dev * dev, int irq) {
+  unsigned int rx_bytes;
+  
+  int pio_instead = 0;
+#ifndef TAILRECURSIONWORKS
+  pio_instead = 1;
+  while (pio_instead) {
+#endif
+    // bytes waiting for RX transfer
+    rx_bytes = dev->rx_bytes;
+    
+#if 0
+    spin_count = 0;
+    while (rd_regl (dev, MASTER_RX_COUNT_REG_OFF)) {
+      PRINTD (DBG_RX|DBG_WARN, "RX error: other PCI Bus Master RX still in progress!");
+      if (++spin_count > 10) {
+	PRINTD (DBG_RX|DBG_ERR, "spun out waiting PCI Bus Master RX completion");
+	wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0);
+	clear_bit (rx_busy, &dev->flags);
+	hrz_kfree_skb (dev->rx_skb);
+	return;
+      }
+    }
+#endif
+    
+    // this code follows the TX code but (at the moment) there is only
+    // one region - the skb itself. I don't know if this will change,
+    // but it doesn't hurt to have the code here, disabled.
+    
+    if (rx_bytes) {
+      // start next transfer within same region
+      if (rx_bytes <= MAX_PIO_COUNT) {
+	PRINTD (DBG_RX|DBG_BUS, "(pio)");
+	pio_instead = 1;
+      }
+      if (rx_bytes <= MAX_TRANSFER_COUNT) {
+	PRINTD (DBG_RX|DBG_BUS, "(simple or last multi)");
+	dev->rx_bytes = 0;
+      } else {
+	PRINTD (DBG_RX|DBG_BUS, "(continuing multi)");
+	dev->rx_bytes = rx_bytes - MAX_TRANSFER_COUNT;
+	rx_bytes = MAX_TRANSFER_COUNT;
+      }
+    } else {
+      // rx_bytes == 0 -- we're between regions
+      // regions remaining to transfer
+#if 0
+      unsigned int rx_regions = dev->rx_regions;
+#else
+      unsigned int rx_regions = 0;
+#endif
+      
+      if (rx_regions) {
+#if 0
+	// start a new region
+	dev->rx_addr = dev->rx_iovec->iov_base;
+	rx_bytes = dev->rx_iovec->iov_len;
+	++dev->rx_iovec;
+	dev->rx_regions = rx_regions - 1;
+	
+	if (rx_bytes <= MAX_PIO_COUNT) {
+	  PRINTD (DBG_RX|DBG_BUS, "(pio)");
+	  pio_instead = 1;
+	}
+	if (rx_bytes <= MAX_TRANSFER_COUNT) {
+	  PRINTD (DBG_RX|DBG_BUS, "(full region)");
+	  dev->rx_bytes = 0;
+	} else {
+	  PRINTD (DBG_RX|DBG_BUS, "(start multi region)");
+	  dev->rx_bytes = rx_bytes - MAX_TRANSFER_COUNT;
+	  rx_bytes = MAX_TRANSFER_COUNT;
+	}
+#endif
+      } else {
+	// rx_regions == 0
+	// that's all folks - end of frame
+	struct sk_buff * skb = dev->rx_skb;
+	// dev->rx_iovec = 0;
+	
+	FLUSH_RX_CHANNEL (dev, dev->rx_channel);
+	
+	dump_skb ("<<<", dev->rx_channel, skb);
+	
+	PRINTD (DBG_RX|DBG_SKB, "push %p %u", skb->data, skb->len);
+	
+	{
+	  struct atm_vcc * vcc = ATM_SKB(skb)->vcc;
+	  // VC layer stats
+	  atomic_inc(&vcc->stats->rx);
+	  do_gettimeofday(&skb->stamp);
+	  // end of our responsability
+	  vcc->push (vcc, skb);
+	}
+      }
+    }
+    
+    // note: writing RX_COUNT clears any interrupt condition
+    if (rx_bytes) {
+      if (pio_instead) {
+	if (irq)
+	  wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0);
+	rds_regb (dev, DATA_PORT_OFF, dev->rx_addr, rx_bytes);
+      } else {
+	wr_regl (dev, MASTER_RX_ADDR_REG_OFF, virt_to_bus (dev->rx_addr));
+	wr_regl (dev, MASTER_RX_COUNT_REG_OFF, rx_bytes);
+      }
+      dev->rx_addr += rx_bytes;
+    } else {
+      if (irq)
+	wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0);
+      // allow another RX thread to start
+      YELLOW_LED_ON(dev);
+      clear_bit (rx_busy, &dev->flags);
+      PRINTD (DBG_RX, "cleared rx_busy for dev %p", dev);
+    }
+    
+#ifdef TAILRECURSIONWORKS
+    // and we all bless optimised tail calls
+    if (pio_instead)
+      return rx_schedule (dev, 0);
+    return;
+#else
+    // grrrrrrr!
+    irq = 0;
+  }
+  return;
+#endif
+}
+
+/********** handle RX bus master complete events **********/
+
+static inline void rx_bus_master_complete_handler (hrz_dev * dev) {
+  if (test_bit (rx_busy, &dev->flags)) {
+    rx_schedule (dev, 1);
+  } else {
+    PRINTD (DBG_RX|DBG_ERR, "unexpected RX bus master completion");
+    // clear interrupt condition on adapter
+    wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0);
+  }
+  return;
+}
+
+/********** (queue to) become the next TX thread **********/
+
+static inline int tx_hold (hrz_dev * dev) {
+  PRINTD (DBG_TX, "sleeping at tx lock %p %lu", dev, dev->flags);
+  wait_event_interruptible(dev->tx_queue, (!test_and_set_bit(tx_busy, &dev->flags)));
+  PRINTD (DBG_TX, "woken at tx lock %p %lu", dev, dev->flags);
+  if (signal_pending (current))
+    return -1;
+  PRINTD (DBG_TX, "set tx_busy for dev %p", dev);
+  return 0;
+}
+
+/********** allow another TX thread to start **********/
+
+static inline void tx_release (hrz_dev * dev) {
+  clear_bit (tx_busy, &dev->flags);
+  PRINTD (DBG_TX, "cleared tx_busy for dev %p", dev);
+  wake_up_interruptible (&dev->tx_queue);
+}
+
+/********** schedule TX transfers **********/
+
+static void tx_schedule (hrz_dev * const dev, int irq) {
+  unsigned int tx_bytes;
+  
+  int append_desc = 0;
+  
+  int pio_instead = 0;
+#ifndef TAILRECURSIONWORKS
+  pio_instead = 1;
+  while (pio_instead) {
+#endif
+    // bytes in current region waiting for TX transfer
+    tx_bytes = dev->tx_bytes;
+    
+#if 0
+    spin_count = 0;
+    while (rd_regl (dev, MASTER_TX_COUNT_REG_OFF)) {
+      PRINTD (DBG_TX|DBG_WARN, "TX error: other PCI Bus Master TX still in progress!");
+      if (++spin_count > 10) {
+	PRINTD (DBG_TX|DBG_ERR, "spun out waiting PCI Bus Master TX completion");
+	wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0);
+	tx_release (dev);
+	hrz_kfree_skb (dev->tx_skb);
+	return;
+      }
+    }
+#endif
+    
+    if (tx_bytes) {
+      // start next transfer within same region
+      if (!test_bit (ultra, &dev->flags) || tx_bytes <= MAX_PIO_COUNT) {
+	PRINTD (DBG_TX|DBG_BUS, "(pio)");
+	pio_instead = 1;
+      }
+      if (tx_bytes <= MAX_TRANSFER_COUNT) {
+	PRINTD (DBG_TX|DBG_BUS, "(simple or last multi)");
+	if (!dev->tx_iovec) {
+	  // end of last region
+	  append_desc = 1;
+	}
+	dev->tx_bytes = 0;
+      } else {
+	PRINTD (DBG_TX|DBG_BUS, "(continuing multi)");
+	dev->tx_bytes = tx_bytes - MAX_TRANSFER_COUNT;
+	tx_bytes = MAX_TRANSFER_COUNT;
+      }
+    } else {
+      // tx_bytes == 0 -- we're between regions
+      // regions remaining to transfer
+      unsigned int tx_regions = dev->tx_regions;
+      
+      if (tx_regions) {
+	// start a new region
+	dev->tx_addr = dev->tx_iovec->iov_base;
+	tx_bytes = dev->tx_iovec->iov_len;
+	++dev->tx_iovec;
+	dev->tx_regions = tx_regions - 1;
+	
+	if (!test_bit (ultra, &dev->flags) || tx_bytes <= MAX_PIO_COUNT) {
+	  PRINTD (DBG_TX|DBG_BUS, "(pio)");
+	  pio_instead = 1;
+	}
+	if (tx_bytes <= MAX_TRANSFER_COUNT) {
+	  PRINTD (DBG_TX|DBG_BUS, "(full region)");
+	  dev->tx_bytes = 0;
+	} else {
+	  PRINTD (DBG_TX|DBG_BUS, "(start multi region)");
+	  dev->tx_bytes = tx_bytes - MAX_TRANSFER_COUNT;
+	  tx_bytes = MAX_TRANSFER_COUNT;
+	}
+      } else {
+	// tx_regions == 0
+	// that's all folks - end of frame
+	struct sk_buff * skb = dev->tx_skb;
+	dev->tx_iovec = NULL;
+	
+	// VC layer stats
+	atomic_inc(&ATM_SKB(skb)->vcc->stats->tx);
+	
+	// free the skb
+	hrz_kfree_skb (skb);
+      }
+    }
+    
+    // note: writing TX_COUNT clears any interrupt condition
+    if (tx_bytes) {
+      if (pio_instead) {
+	if (irq)
+	  wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0);
+	wrs_regb (dev, DATA_PORT_OFF, dev->tx_addr, tx_bytes);
+	if (append_desc)
+	  wr_regl (dev, TX_DESCRIPTOR_PORT_OFF, cpu_to_be32 (dev->tx_skb->len));
+      } else {
+	wr_regl (dev, MASTER_TX_ADDR_REG_OFF, virt_to_bus (dev->tx_addr));
+	if (append_desc)
+	  wr_regl (dev, TX_DESCRIPTOR_REG_OFF, cpu_to_be32 (dev->tx_skb->len));
+	wr_regl (dev, MASTER_TX_COUNT_REG_OFF,
+		 append_desc
+		 ? tx_bytes | MASTER_TX_AUTO_APPEND_DESC
+		 : tx_bytes);
+      }
+      dev->tx_addr += tx_bytes;
+    } else {
+      if (irq)
+	wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0);
+      YELLOW_LED_ON(dev);
+      tx_release (dev);
+    }
+    
+#ifdef TAILRECURSIONWORKS
+    // and we all bless optimised tail calls
+    if (pio_instead)
+      return tx_schedule (dev, 0);
+    return;
+#else
+    // grrrrrrr!
+    irq = 0;
+  }
+  return;
+#endif
+}
+
+/********** handle TX bus master complete events **********/
+
+static inline void tx_bus_master_complete_handler (hrz_dev * dev) {
+  if (test_bit (tx_busy, &dev->flags)) {
+    tx_schedule (dev, 1);
+  } else {
+    PRINTD (DBG_TX|DBG_ERR, "unexpected TX bus master completion");
+    // clear interrupt condition on adapter
+    wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0);
+  }
+  return;
+}
+
+/********** move RX Q pointer to next item in circular buffer **********/
+
+// called only from IRQ sub-handler
+static inline u32 rx_queue_entry_next (hrz_dev * dev) {
+  u32 rx_queue_entry;
+  spin_lock (&dev->mem_lock);
+  rx_queue_entry = rd_mem (dev, &dev->rx_q_entry->entry);
+  if (dev->rx_q_entry == dev->rx_q_wrap)
+    dev->rx_q_entry = dev->rx_q_reset;
+  else
+    dev->rx_q_entry++;
+  wr_regw (dev, RX_QUEUE_RD_PTR_OFF, dev->rx_q_entry - dev->rx_q_reset);
+  spin_unlock (&dev->mem_lock);
+  return rx_queue_entry;
+}
+
+/********** handle RX disabled by device **********/
+
+static inline void rx_disabled_handler (hrz_dev * dev) {
+  wr_regw (dev, RX_CONFIG_OFF, rd_regw (dev, RX_CONFIG_OFF) | RX_ENABLE);
+  // count me please
+  PRINTK (KERN_WARNING, "RX was disabled!");
+}
+
+/********** handle RX data received by device **********/
+
+// called from IRQ handler
+static inline void rx_data_av_handler (hrz_dev * dev) {
+  u32 rx_queue_entry;
+  u32 rx_queue_entry_flags;
+  u16 rx_len;
+  u16 rx_channel;
+  
+  PRINTD (DBG_FLOW, "hrz_data_av_handler");
+  
+  // try to grab rx lock (not possible during RX bus mastering)
+  if (test_and_set_bit (rx_busy, &dev->flags)) {
+    PRINTD (DBG_RX, "locked out of rx lock");
+    return;
+  }
+  PRINTD (DBG_RX, "set rx_busy for dev %p", dev);
+  // lock is cleared if we fail now, o/w after bus master completion
+  
+  YELLOW_LED_OFF(dev);
+  
+  rx_queue_entry = rx_queue_entry_next (dev);
+  
+  rx_len = rx_q_entry_to_length (rx_queue_entry);
+  rx_channel = rx_q_entry_to_rx_channel (rx_queue_entry);
+  
+  WAIT_FLUSH_RX_COMPLETE (dev);
+  
+  SELECT_RX_CHANNEL (dev, rx_channel);
+  
+  PRINTD (DBG_RX, "rx_queue_entry is: %#x", rx_queue_entry);
+  rx_queue_entry_flags = rx_queue_entry & (RX_CRC_32_OK|RX_COMPLETE_FRAME|SIMONS_DODGEY_MARKER);
+  
+  if (!rx_len) {
+    // (at least) bus-mastering breaks if we try to handle a
+    // zero-length frame, besides AAL5 does not support them
+    PRINTK (KERN_ERR, "zero-length frame!");
+    rx_queue_entry_flags &= ~RX_COMPLETE_FRAME;
+  }
+  
+  if (rx_queue_entry_flags & SIMONS_DODGEY_MARKER) {
+    PRINTD (DBG_RX|DBG_ERR, "Simon's marker detected!");
+  }
+  if (rx_queue_entry_flags == (RX_CRC_32_OK | RX_COMPLETE_FRAME)) {
+    struct atm_vcc * atm_vcc;
+    
+    PRINTD (DBG_RX, "got a frame on rx_channel %x len %u", rx_channel, rx_len);
+    
+    atm_vcc = dev->rxer[rx_channel];
+    // if no vcc is assigned to this channel, we should drop the frame
+    // (is this what SIMONS etc. was trying to achieve?)
+    
+    if (atm_vcc) {
+      
+      if (atm_vcc->qos.rxtp.traffic_class != ATM_NONE) {
+	
+	if (rx_len <= atm_vcc->qos.rxtp.max_sdu) {
+	    
+	  struct sk_buff * skb = atm_alloc_charge (atm_vcc, rx_len, GFP_ATOMIC);
+	  if (skb) {
+	    // remember this so we can push it later
+	    dev->rx_skb = skb;
+	    // remember this so we can flush it later
+	    dev->rx_channel = rx_channel;
+	    
+	    // prepare socket buffer
+	    skb_put (skb, rx_len);
+	    ATM_SKB(skb)->vcc = atm_vcc;
+	    
+	    // simple transfer
+	    // dev->rx_regions = 0;
+	    // dev->rx_iovec = 0;
+	    dev->rx_bytes = rx_len;
+	    dev->rx_addr = skb->data;
+	    PRINTD (DBG_RX, "RX start simple transfer (addr %p, len %d)",
+		    skb->data, rx_len);
+	    
+	    // do the business
+	    rx_schedule (dev, 0);
+	    return;
+	    
+	  } else {
+	    PRINTD (DBG_SKB|DBG_WARN, "failed to get skb");
+	  }
+	  
+	} else {
+	  PRINTK (KERN_INFO, "frame received on TX-only VC %x", rx_channel);
+	  // do we count this?
+	}
+	
+      } else {
+	PRINTK (KERN_WARNING, "dropped over-size frame");
+	// do we count this?
+      }
+      
+    } else {
+      PRINTD (DBG_WARN|DBG_VCC|DBG_RX, "no VCC for this frame (VC closed)");
+      // do we count this?
+    }
+    
+  } else {
+    // Wait update complete ? SPONG
+  }
+  
+  // RX was aborted
+  YELLOW_LED_ON(dev);
+  
+  FLUSH_RX_CHANNEL (dev,rx_channel);
+  clear_bit (rx_busy, &dev->flags);
+  
+  return;
+}
+
+/********** interrupt handler **********/
+
+static irqreturn_t interrupt_handler(int irq, void *dev_id,
+					struct pt_regs *pt_regs) {
+  hrz_dev * dev = (hrz_dev *) dev_id;
+  u32 int_source;
+  unsigned int irq_ok;
+  (void) pt_regs;
+  
+  PRINTD (DBG_FLOW, "interrupt_handler: %p", dev_id);
+  
+  if (!dev_id) {
+    PRINTD (DBG_IRQ|DBG_ERR, "irq with NULL dev_id: %d", irq);
+    return IRQ_NONE;
+  }
+  if (irq != dev->irq) {
+    PRINTD (DBG_IRQ|DBG_ERR, "irq mismatch: %d", irq);
+    return IRQ_NONE;
+  }
+  
+  // definitely for us
+  irq_ok = 0;
+  while ((int_source = rd_regl (dev, INT_SOURCE_REG_OFF)
+	  & INTERESTING_INTERRUPTS)) {
+    // In the interests of fairness, the (inline) handlers below are
+    // called in sequence and without immediate return to the head of
+    // the while loop. This is only of issue for slow hosts (or when
+    // debugging messages are on). Really slow hosts may find a fast
+    // sender keeps them permanently in the IRQ handler. :(
+    
+    // (only an issue for slow hosts) RX completion goes before
+    // rx_data_av as the former implies rx_busy and so the latter
+    // would just abort. If it reschedules another transfer
+    // (continuing the same frame) then it will not clear rx_busy.
+    
+    // (only an issue for slow hosts) TX completion goes before RX
+    // data available as it is a much shorter routine - there is the
+    // chance that any further transfers it schedules will be complete
+    // by the time of the return to the head of the while loop
+    
+    if (int_source & RX_BUS_MASTER_COMPLETE) {
+      ++irq_ok;
+      PRINTD (DBG_IRQ|DBG_BUS|DBG_RX, "rx_bus_master_complete asserted");
+      rx_bus_master_complete_handler (dev);
+    }
+    if (int_source & TX_BUS_MASTER_COMPLETE) {
+      ++irq_ok;
+      PRINTD (DBG_IRQ|DBG_BUS|DBG_TX, "tx_bus_master_complete asserted");
+      tx_bus_master_complete_handler (dev);
+    }
+    if (int_source & RX_DATA_AV) {
+      ++irq_ok;
+      PRINTD (DBG_IRQ|DBG_RX, "rx_data_av asserted");
+      rx_data_av_handler (dev);
+    }
+  }
+  if (irq_ok) {
+    PRINTD (DBG_IRQ, "work done: %u", irq_ok);
+  } else {
+    PRINTD (DBG_IRQ|DBG_WARN, "spurious interrupt source: %#x", int_source);
+  }
+  
+  PRINTD (DBG_IRQ|DBG_FLOW, "interrupt_handler done: %p", dev_id);
+  if (irq_ok)
+	return IRQ_HANDLED;
+  return IRQ_NONE;
+}
+
+/********** housekeeping **********/
+
+static void do_housekeeping (unsigned long arg) {
+  // just stats at the moment
+  hrz_dev * dev = (hrz_dev *) arg;
+
+  // collect device-specific (not driver/atm-linux) stats here
+  dev->tx_cell_count += rd_regw (dev, TX_CELL_COUNT_OFF);
+  dev->rx_cell_count += rd_regw (dev, RX_CELL_COUNT_OFF);
+  dev->hec_error_count += rd_regw (dev, HEC_ERROR_COUNT_OFF);
+  dev->unassigned_cell_count += rd_regw (dev, UNASSIGNED_CELL_COUNT_OFF);
+
+  mod_timer (&dev->housekeeping, jiffies + HZ/10);
+
+  return;
+}
+
+/********** find an idle channel for TX and set it up **********/
+
+// called with tx_busy set
+static inline short setup_idle_tx_channel (hrz_dev * dev, hrz_vcc * vcc) {
+  unsigned short idle_channels;
+  short tx_channel = -1;
+  unsigned int spin_count;
+  PRINTD (DBG_FLOW|DBG_TX, "setup_idle_tx_channel %p", dev);
+  
+  // better would be to fail immediately, the caller can then decide whether
+  // to wait or drop (depending on whether this is UBR etc.)
+  spin_count = 0;
+  while (!(idle_channels = rd_regw (dev, TX_STATUS_OFF) & IDLE_CHANNELS_MASK)) {
+    PRINTD (DBG_TX|DBG_WARN, "waiting for idle TX channel");
+    // delay a bit here
+    if (++spin_count > 100) {
+      PRINTD (DBG_TX|DBG_ERR, "spun out waiting for idle TX channel");
+      return -EBUSY;
+    }
+  }
+  
+  // got an idle channel
+  {
+    // tx_idle ensures we look for idle channels in RR order
+    int chan = dev->tx_idle;
+    
+    int keep_going = 1;
+    while (keep_going) {
+      if (idle_channels & (1<<chan)) {
+	tx_channel = chan;
+	keep_going = 0;
+      }
+      ++chan;
+      if (chan == TX_CHANS)
+	chan = 0;
+    }
+    
+    dev->tx_idle = chan;
+  }
+  
+  // set up the channel we found
+  {
+    // Initialise the cell header in the transmit channel descriptor
+    // a.k.a. prepare the channel and remember that we have done so.
+    
+    tx_ch_desc * tx_desc = &memmap->tx_descs[tx_channel];
+    u16 rd_ptr;
+    u16 wr_ptr;
+    u16 channel = vcc->channel;
+    
+    unsigned long flags;
+    spin_lock_irqsave (&dev->mem_lock, flags);
+    
+    // Update the transmit channel record.
+    dev->tx_channel_record[tx_channel] = channel;
+    
+    // xBR channel
+    update_tx_channel_config (dev, tx_channel, RATE_TYPE_ACCESS,
+			      vcc->tx_xbr_bits);
+    
+    // Update the PCR counter preload value etc.
+    update_tx_channel_config (dev, tx_channel, PCR_TIMER_ACCESS,
+			      vcc->tx_pcr_bits);
+
+#if 0
+    if (vcc->tx_xbr_bits == VBR_RATE_TYPE) {
+      // SCR timer
+      update_tx_channel_config (dev, tx_channel, SCR_TIMER_ACCESS,
+				vcc->tx_scr_bits);
+      
+      // Bucket size...
+      update_tx_channel_config (dev, tx_channel, BUCKET_CAPACITY_ACCESS,
+				vcc->tx_bucket_bits);
+      
+      // ... and fullness
+      update_tx_channel_config (dev, tx_channel, BUCKET_FULLNESS_ACCESS,
+				vcc->tx_bucket_bits);
+    }
+#endif
+
+    // Initialise the read and write buffer pointers
+    rd_ptr = rd_mem (dev, &tx_desc->rd_buf_type) & BUFFER_PTR_MASK;
+    wr_ptr = rd_mem (dev, &tx_desc->wr_buf_type) & BUFFER_PTR_MASK;
+    
+    // idle TX channels should have identical pointers
+    if (rd_ptr != wr_ptr) {
+      PRINTD (DBG_TX|DBG_ERR, "TX buffer pointers are broken!");
+      // spin_unlock... return -E...
+      // I wonder if gcc would get rid of one of the pointer aliases
+    }
+    PRINTD (DBG_TX, "TX buffer pointers are: rd %x, wr %x.",
+	    rd_ptr, wr_ptr);
+    
+    switch (vcc->aal) {
+      case aal0:
+	PRINTD (DBG_QOS|DBG_TX, "tx_channel: aal0");
+	rd_ptr |= CHANNEL_TYPE_RAW_CELLS;
+	wr_ptr |= CHANNEL_TYPE_RAW_CELLS;
+	break;
+      case aal34:
+	PRINTD (DBG_QOS|DBG_TX, "tx_channel: aal34");
+	rd_ptr |= CHANNEL_TYPE_AAL3_4;
+	wr_ptr |= CHANNEL_TYPE_AAL3_4;
+	break;
+      case aal5:
+	rd_ptr |= CHANNEL_TYPE_AAL5;
+	wr_ptr |= CHANNEL_TYPE_AAL5;
+	// Initialise the CRC
+	wr_mem (dev, &tx_desc->partial_crc, INITIAL_CRC);
+	break;
+    }
+    
+    wr_mem (dev, &tx_desc->rd_buf_type, rd_ptr);
+    wr_mem (dev, &tx_desc->wr_buf_type, wr_ptr);
+    
+    // Write the Cell Header
+    // Payload Type, CLP and GFC would go here if non-zero
+    wr_mem (dev, &tx_desc->cell_header, channel);
+    
+    spin_unlock_irqrestore (&dev->mem_lock, flags);
+  }
+  
+  return tx_channel;
+}
+
+/********** send a frame **********/
+
+static int hrz_send (struct atm_vcc * atm_vcc, struct sk_buff * skb) {
+  unsigned int spin_count;
+  int free_buffers;
+  hrz_dev * dev = HRZ_DEV(atm_vcc->dev);
+  hrz_vcc * vcc = HRZ_VCC(atm_vcc);
+  u16 channel = vcc->channel;
+  
+  u32 buffers_required;
+  
+  /* signed for error return */
+  short tx_channel;
+  
+  PRINTD (DBG_FLOW|DBG_TX, "hrz_send vc %x data %p len %u",
+	  channel, skb->data, skb->len);
+  
+  dump_skb (">>>", channel, skb);
+  
+  if (atm_vcc->qos.txtp.traffic_class == ATM_NONE) {
+    PRINTK (KERN_ERR, "attempt to send on RX-only VC %x", channel);
+    hrz_kfree_skb (skb);
+    return -EIO;
+  }
+  
+  // don't understand this
+  ATM_SKB(skb)->vcc = atm_vcc;
+  
+  if (skb->len > atm_vcc->qos.txtp.max_sdu) {
+    PRINTK (KERN_ERR, "sk_buff length greater than agreed max_sdu, dropping...");
+    hrz_kfree_skb (skb);
+    return -EIO;
+  }
+  
+  if (!channel) {
+    PRINTD (DBG_ERR|DBG_TX, "attempt to transmit on zero (rx_)channel");
+    hrz_kfree_skb (skb);
+    return -EIO;
+  }
+  
+#if 0
+  {
+    // where would be a better place for this? housekeeping?
+    u16 status;
+    pci_read_config_word (dev->pci_dev, PCI_STATUS, &status);
+    if (status & PCI_STATUS_REC_MASTER_ABORT) {
+      PRINTD (DBG_BUS|DBG_ERR, "Clearing PCI Master Abort (and cleaning up)");
+      status &= ~PCI_STATUS_REC_MASTER_ABORT;
+      pci_write_config_word (dev->pci_dev, PCI_STATUS, status);
+      if (test_bit (tx_busy, &dev->flags)) {
+	hrz_kfree_skb (dev->tx_skb);
+	tx_release (dev);
+      }
+    }
+  }
+#endif
+  
+#ifdef DEBUG_HORIZON
+  /* wey-hey! */
+  if (channel == 1023) {
+    unsigned int i;
+    unsigned short d = 0;
+    char * s = skb->data;
+    if (*s++ == 'D') {
+      for (i = 0; i < 4; ++i) {
+	d = (d<<4) | ((*s <= '9') ? (*s - '0') : (*s - 'a' + 10));
+	++s;
+      }
+      PRINTK (KERN_INFO, "debug bitmap is now %hx", debug = d);
+    }
+  }
+#endif
+  
+  // wait until TX is free and grab lock
+  if (tx_hold (dev)) {
+    hrz_kfree_skb (skb);
+    return -ERESTARTSYS;
+  }
+ 
+  // Wait for enough space to be available in transmit buffer memory.
+  
+  // should be number of cells needed + 2 (according to hardware docs)
+  // = ((framelen+8)+47) / 48 + 2
+  // = (framelen+7) / 48 + 3, hmm... faster to put addition inside XXX
+  buffers_required = (skb->len+(ATM_AAL5_TRAILER-1)) / ATM_CELL_PAYLOAD + 3;
+  
+  // replace with timer and sleep, add dev->tx_buffers_queue (max 1 entry)
+  spin_count = 0;
+  while ((free_buffers = rd_regw (dev, TX_FREE_BUFFER_COUNT_OFF)) < buffers_required) {
+    PRINTD (DBG_TX, "waiting for free TX buffers, got %d of %d",
+	    free_buffers, buffers_required);
+    // what is the appropriate delay? implement a timeout? (depending on line speed?)
+    // mdelay (1);
+    // what happens if we kill (current_pid, SIGKILL) ?
+    schedule();
+    if (++spin_count > 1000) {
+      PRINTD (DBG_TX|DBG_ERR, "spun out waiting for tx buffers, got %d of %d",
+	      free_buffers, buffers_required);
+      tx_release (dev);
+      hrz_kfree_skb (skb);
+      return -ERESTARTSYS;
+    }
+  }
+  
+  // Select a channel to transmit the frame on.
+  if (channel == dev->last_vc) {
+    PRINTD (DBG_TX, "last vc hack: hit");
+    tx_channel = dev->tx_last;
+  } else {
+    PRINTD (DBG_TX, "last vc hack: miss");
+    // Are we currently transmitting this VC on one of the channels?
+    for (tx_channel = 0; tx_channel < TX_CHANS; ++tx_channel)
+      if (dev->tx_channel_record[tx_channel] == channel) {
+	PRINTD (DBG_TX, "vc already on channel: hit");
+	break;
+      }
+    if (tx_channel == TX_CHANS) { 
+      PRINTD (DBG_TX, "vc already on channel: miss");
+      // Find and set up an idle channel.
+      tx_channel = setup_idle_tx_channel (dev, vcc);
+      if (tx_channel < 0) {
+	PRINTD (DBG_TX|DBG_ERR, "failed to get channel");
+	tx_release (dev);
+	return tx_channel;
+      }
+    }
+    
+    PRINTD (DBG_TX, "got channel");
+    SELECT_TX_CHANNEL(dev, tx_channel);
+    
+    dev->last_vc = channel;
+    dev->tx_last = tx_channel;
+  }
+  
+  PRINTD (DBG_TX, "using channel %u", tx_channel);
+  
+  YELLOW_LED_OFF(dev);
+  
+  // TX start transfer
+  
+  {
+    unsigned int tx_len = skb->len;
+    unsigned int tx_iovcnt = skb_shinfo(skb)->nr_frags;
+    // remember this so we can free it later
+    dev->tx_skb = skb;
+    
+    if (tx_iovcnt) {
+      // scatter gather transfer
+      dev->tx_regions = tx_iovcnt;
+      dev->tx_iovec = NULL;		/* @@@ needs rewritten */
+      dev->tx_bytes = 0;
+      PRINTD (DBG_TX|DBG_BUS, "TX start scatter-gather transfer (iovec %p, len %d)",
+	      skb->data, tx_len);
+      tx_release (dev);
+      hrz_kfree_skb (skb);
+      return -EIO;
+    } else {
+      // simple transfer
+      dev->tx_regions = 0;
+      dev->tx_iovec = NULL;
+      dev->tx_bytes = tx_len;
+      dev->tx_addr = skb->data;
+      PRINTD (DBG_TX|DBG_BUS, "TX start simple transfer (addr %p, len %d)",
+	      skb->data, tx_len);
+    }
+    
+    // and do the business
+    tx_schedule (dev, 0);
+    
+  }
+  
+  return 0;
+}
+
+/********** reset a card **********/
+
+static void hrz_reset (const hrz_dev * dev) {
+  u32 control_0_reg = rd_regl (dev, CONTROL_0_REG);
+  
+  // why not set RESET_HORIZON to one and wait for the card to
+  // reassert that bit as zero? Like so:
+  control_0_reg = control_0_reg & RESET_HORIZON;
+  wr_regl (dev, CONTROL_0_REG, control_0_reg);
+  while (control_0_reg & RESET_HORIZON)
+    control_0_reg = rd_regl (dev, CONTROL_0_REG);
+  
+  // old reset code retained:
+  wr_regl (dev, CONTROL_0_REG, control_0_reg |
+	   RESET_ATM | RESET_RX | RESET_TX | RESET_HOST);
+  // just guessing here
+  udelay (1000);
+  
+  wr_regl (dev, CONTROL_0_REG, control_0_reg);
+}
+
+/********** read the burnt in address **********/
+
+static inline void WRITE_IT_WAIT (const hrz_dev *dev, u32 ctrl)
+{
+	wr_regl (dev, CONTROL_0_REG, ctrl);
+	udelay (5);
+}
+  
+static inline void CLOCK_IT (const hrz_dev *dev, u32 ctrl)
+{
+	// DI must be valid around rising SK edge
+	WRITE_IT_WAIT(dev, ctrl & ~SEEPROM_SK);
+	WRITE_IT_WAIT(dev, ctrl | SEEPROM_SK);
+}
+
+static u16 __init read_bia (const hrz_dev * dev, u16 addr)
+{
+  u32 ctrl = rd_regl (dev, CONTROL_0_REG);
+  
+  const unsigned int addr_bits = 6;
+  const unsigned int data_bits = 16;
+  
+  unsigned int i;
+  
+  u16 res;
+  
+  ctrl &= ~(SEEPROM_CS | SEEPROM_SK | SEEPROM_DI);
+  WRITE_IT_WAIT(dev, ctrl);
+  
+  // wake Serial EEPROM and send 110 (READ) command
+  ctrl |=  (SEEPROM_CS | SEEPROM_DI);
+  CLOCK_IT(dev, ctrl);
+  
+  ctrl |= SEEPROM_DI;
+  CLOCK_IT(dev, ctrl);
+  
+  ctrl &= ~SEEPROM_DI;
+  CLOCK_IT(dev, ctrl);
+  
+  for (i=0; i<addr_bits; i++) {
+    if (addr & (1 << (addr_bits-1)))
+      ctrl |= SEEPROM_DI;
+    else
+      ctrl &= ~SEEPROM_DI;
+    
+    CLOCK_IT(dev, ctrl);
+    
+    addr = addr << 1;
+  }
+  
+  // we could check that we have DO = 0 here
+  ctrl &= ~SEEPROM_DI;
+  
+  res = 0;
+  for (i=0;i<data_bits;i++) {
+    res = res >> 1;
+    
+    CLOCK_IT(dev, ctrl);
+    
+    if (rd_regl (dev, CONTROL_0_REG) & SEEPROM_DO)
+      res |= (1 << (data_bits-1));
+  }
+  
+  ctrl &= ~(SEEPROM_SK | SEEPROM_CS);
+  WRITE_IT_WAIT(dev, ctrl);
+  
+  return res;
+}
+
+/********** initialise a card **********/
+
+static int __init hrz_init (hrz_dev * dev) {
+  int onefivefive;
+  
+  u16 chan;
+  
+  int buff_count;
+  
+  HDW * mem;
+  
+  cell_buf * tx_desc;
+  cell_buf * rx_desc;
+  
+  u32 ctrl;
+  
+  ctrl = rd_regl (dev, CONTROL_0_REG);
+  PRINTD (DBG_INFO, "ctrl0reg is %#x", ctrl);
+  onefivefive = ctrl & ATM_LAYER_STATUS;
+  
+  if (onefivefive)
+    printk (DEV_LABEL ": Horizon Ultra (at 155.52 MBps)");
+  else
+    printk (DEV_LABEL ": Horizon (at 25 MBps)");
+  
+  printk (":");
+  // Reset the card to get everything in a known state
+  
+  printk (" reset");
+  hrz_reset (dev);
+  
+  // Clear all the buffer memory
+  
+  printk (" clearing memory");
+  
+  for (mem = (HDW *) memmap; mem < (HDW *) (memmap + 1); ++mem)
+    wr_mem (dev, mem, 0);
+  
+  printk (" tx channels");
+  
+  // All transmit eight channels are set up as AAL5 ABR channels with
+  // a 16us cell spacing. Why?
+  
+  // Channel 0 gets the free buffer at 100h, channel 1 gets the free
+  // buffer at 110h etc.
+  
+  for (chan = 0; chan < TX_CHANS; ++chan) {
+    tx_ch_desc * tx_desc = &memmap->tx_descs[chan];
+    cell_buf * buf = &memmap->inittxbufs[chan];
+    
+    // initialise the read and write buffer pointers
+    wr_mem (dev, &tx_desc->rd_buf_type, BUF_PTR(buf));
+    wr_mem (dev, &tx_desc->wr_buf_type, BUF_PTR(buf));
+    
+    // set the status of the initial buffers to empty
+    wr_mem (dev, &buf->next, BUFF_STATUS_EMPTY);
+  }
+  
+  // Use space bufn3 at the moment for tx buffers
+  
+  printk (" tx buffers");
+  
+  tx_desc = memmap->bufn3;
+  
+  wr_mem (dev, &memmap->txfreebufstart.next, BUF_PTR(tx_desc) | BUFF_STATUS_EMPTY);
+  
+  for (buff_count = 0; buff_count < BUFN3_SIZE-1; buff_count++) {
+    wr_mem (dev, &tx_desc->next, BUF_PTR(tx_desc+1) | BUFF_STATUS_EMPTY);
+    tx_desc++;
+  }
+  
+  wr_mem (dev, &tx_desc->next, BUF_PTR(&memmap->txfreebufend) | BUFF_STATUS_EMPTY);
+  
+  // Initialise the transmit free buffer count
+  wr_regw (dev, TX_FREE_BUFFER_COUNT_OFF, BUFN3_SIZE);
+  
+  printk (" rx channels");
+  
+  // Initialise all of the receive channels to be AAL5 disabled with
+  // an interrupt threshold of 0
+  
+  for (chan = 0; chan < RX_CHANS; ++chan) {
+    rx_ch_desc * rx_desc = &memmap->rx_descs[chan];
+    
+    wr_mem (dev, &rx_desc->wr_buf_type, CHANNEL_TYPE_AAL5 | RX_CHANNEL_DISABLED);
+  }
+  
+  printk (" rx buffers");
+  
+  // Use space bufn4 at the moment for rx buffers
+  
+  rx_desc = memmap->bufn4;
+  
+  wr_mem (dev, &memmap->rxfreebufstart.next, BUF_PTR(rx_desc) | BUFF_STATUS_EMPTY);
+  
+  for (buff_count = 0; buff_count < BUFN4_SIZE-1; buff_count++) {
+    wr_mem (dev, &rx_desc->next, BUF_PTR(rx_desc+1) | BUFF_STATUS_EMPTY);
+    
+    rx_desc++;
+  }
+  
+  wr_mem (dev, &rx_desc->next, BUF_PTR(&memmap->rxfreebufend) | BUFF_STATUS_EMPTY);
+  
+  // Initialise the receive free buffer count
+  wr_regw (dev, RX_FREE_BUFFER_COUNT_OFF, BUFN4_SIZE);
+  
+  // Initialize Horizons registers
+  
+  // TX config
+  wr_regw (dev, TX_CONFIG_OFF,
+	   ABR_ROUND_ROBIN | TX_NORMAL_OPERATION | DRVR_DRVRBAR_ENABLE);
+  
+  // RX config. Use 10-x VC bits, x VP bits, non user cells in channel 0.
+  wr_regw (dev, RX_CONFIG_OFF,
+	   DISCARD_UNUSED_VPI_VCI_BITS_SET | NON_USER_CELLS_IN_ONE_CHANNEL | vpi_bits);
+  
+  // RX line config
+  wr_regw (dev, RX_LINE_CONFIG_OFF,
+	   LOCK_DETECT_ENABLE | FREQUENCY_DETECT_ENABLE | GXTALOUT_SELECT_DIV4);
+  
+  // Set the max AAL5 cell count to be just enough to contain the
+  // largest AAL5 frame that the user wants to receive
+  wr_regw (dev, MAX_AAL5_CELL_COUNT_OFF,
+	   (max_rx_size + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD);
+  
+  // Enable receive
+  wr_regw (dev, RX_CONFIG_OFF, rd_regw (dev, RX_CONFIG_OFF) | RX_ENABLE);
+  
+  printk (" control");
+  
+  // Drive the OE of the LEDs then turn the green LED on
+  ctrl |= GREEN_LED_OE | YELLOW_LED_OE | GREEN_LED | YELLOW_LED;
+  wr_regl (dev, CONTROL_0_REG, ctrl);
+  
+  // Test for a 155-capable card
+  
+  if (onefivefive) {
+    // Select 155 mode... make this a choice (or: how do we detect
+    // external line speed and switch?)
+    ctrl |= ATM_LAYER_SELECT;
+    wr_regl (dev, CONTROL_0_REG, ctrl);
+    
+    // test SUNI-lite vs SAMBA
+    
+    // Register 0x00 in the SUNI will have some of bits 3-7 set, and
+    // they will always be zero for the SAMBA.  Ha!  Bloody hardware
+    // engineers.  It'll never work.
+    
+    if (rd_framer (dev, 0) & 0x00f0) {
+      // SUNI
+      printk (" SUNI");
+      
+      // Reset, just in case
+      wr_framer (dev, 0x00, 0x0080);
+      wr_framer (dev, 0x00, 0x0000);
+      
+      // Configure transmit FIFO
+      wr_framer (dev, 0x63, rd_framer (dev, 0x63) | 0x0002);
+      
+      // Set line timed mode
+      wr_framer (dev, 0x05, rd_framer (dev, 0x05) | 0x0001);
+    } else {
+      // SAMBA
+      printk (" SAMBA");
+      
+      // Reset, just in case
+      wr_framer (dev, 0, rd_framer (dev, 0) | 0x0001);
+      wr_framer (dev, 0, rd_framer (dev, 0) &~ 0x0001);
+      
+      // Turn off diagnostic loopback and enable line-timed mode
+      wr_framer (dev, 0, 0x0002);
+      
+      // Turn on transmit outputs
+      wr_framer (dev, 2, 0x0B80);
+    }
+  } else {
+    // Select 25 mode
+    ctrl &= ~ATM_LAYER_SELECT;
+    
+    // Madge B154 setup
+    // none required?
+  }
+  
+  printk (" LEDs");
+  
+  GREEN_LED_ON(dev);
+  YELLOW_LED_ON(dev);
+  
+  printk (" ESI=");
+  
+  {
+    u16 b = 0;
+    int i;
+    u8 * esi = dev->atm_dev->esi;
+    
+    // in the card I have, EEPROM
+    // addresses 0, 1, 2 contain 0
+    // addresess 5, 6 etc. contain ffff
+    // NB: Madge prefix is 00 00 f6 (which is 00 00 6f in Ethernet bit order)
+    // the read_bia routine gets the BIA in Ethernet bit order
+    
+    for (i=0; i < ESI_LEN; ++i) {
+      if (i % 2 == 0)
+	b = read_bia (dev, i/2 + 2);
+      else
+	b = b >> 8;
+      esi[i] = b & 0xFF;
+      printk ("%02x", esi[i]);
+    }
+  }
+  
+  // Enable RX_Q and ?X_COMPLETE interrupts only
+  wr_regl (dev, INT_ENABLE_REG_OFF, INTERESTING_INTERRUPTS);
+  printk (" IRQ on");
+  
+  printk (".\n");
+  
+  return onefivefive;
+}
+
+/********** check max_sdu **********/
+
+static int check_max_sdu (hrz_aal aal, struct atm_trafprm * tp, unsigned int max_frame_size) {
+  PRINTD (DBG_FLOW|DBG_QOS, "check_max_sdu");
+  
+  switch (aal) {
+    case aal0:
+      if (!(tp->max_sdu)) {
+	PRINTD (DBG_QOS, "defaulting max_sdu");
+	tp->max_sdu = ATM_AAL0_SDU;
+      } else if (tp->max_sdu != ATM_AAL0_SDU) {
+	PRINTD (DBG_QOS|DBG_ERR, "rejecting max_sdu");
+	return -EINVAL;
+      }
+      break;
+    case aal34:
+      if (tp->max_sdu == 0 || tp->max_sdu > ATM_MAX_AAL34_PDU) {
+	PRINTD (DBG_QOS, "%sing max_sdu", tp->max_sdu ? "capp" : "default");
+	tp->max_sdu = ATM_MAX_AAL34_PDU;
+      }
+      break;
+    case aal5:
+      if (tp->max_sdu == 0 || tp->max_sdu > max_frame_size) {
+	PRINTD (DBG_QOS, "%sing max_sdu", tp->max_sdu ? "capp" : "default");
+	tp->max_sdu = max_frame_size;
+      }
+      break;
+  }
+  return 0;
+}
+
+/********** check pcr **********/
+
+// something like this should be part of ATM Linux
+static int atm_pcr_check (struct atm_trafprm * tp, unsigned int pcr) {
+  // we are assuming non-UBR, and non-special values of pcr
+  if (tp->min_pcr == ATM_MAX_PCR)
+    PRINTD (DBG_QOS, "luser gave min_pcr = ATM_MAX_PCR");
+  else if (tp->min_pcr < 0)
+    PRINTD (DBG_QOS, "luser gave negative min_pcr");
+  else if (tp->min_pcr && tp->min_pcr > pcr)
+    PRINTD (DBG_QOS, "pcr less than min_pcr");
+  else
+    // !! max_pcr = UNSPEC (0) is equivalent to max_pcr = MAX (-1)
+    // easier to #define ATM_MAX_PCR 0 and have all rates unsigned?
+    // [this would get rid of next two conditionals]
+    if ((0) && tp->max_pcr == ATM_MAX_PCR)
+      PRINTD (DBG_QOS, "luser gave max_pcr = ATM_MAX_PCR");
+    else if ((tp->max_pcr != ATM_MAX_PCR) && tp->max_pcr < 0)
+      PRINTD (DBG_QOS, "luser gave negative max_pcr");
+    else if (tp->max_pcr && tp->max_pcr != ATM_MAX_PCR && tp->max_pcr < pcr)
+      PRINTD (DBG_QOS, "pcr greater than max_pcr");
+    else {
+      // each limit unspecified or not violated
+      PRINTD (DBG_QOS, "xBR(pcr) OK");
+      return 0;
+    }
+  PRINTD (DBG_QOS, "pcr=%u, tp: min_pcr=%d, pcr=%d, max_pcr=%d",
+	  pcr, tp->min_pcr, tp->pcr, tp->max_pcr);
+  return -EINVAL;
+}
+
+/********** open VC **********/
+
+static int hrz_open (struct atm_vcc *atm_vcc)
+{
+  int error;
+  u16 channel;
+  
+  struct atm_qos * qos;
+  struct atm_trafprm * txtp;
+  struct atm_trafprm * rxtp;
+  
+  hrz_dev * dev = HRZ_DEV(atm_vcc->dev);
+  hrz_vcc vcc;
+  hrz_vcc * vccp; // allocated late
+  short vpi = atm_vcc->vpi;
+  int vci = atm_vcc->vci;
+  PRINTD (DBG_FLOW|DBG_VCC, "hrz_open %x %x", vpi, vci);
+  
+#ifdef ATM_VPI_UNSPEC
+  // UNSPEC is deprecated, remove this code eventually
+  if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) {
+    PRINTK (KERN_WARNING, "rejecting open with unspecified VPI/VCI (deprecated)");
+    return -EINVAL;
+  }
+#endif
+  
+  error = vpivci_to_channel (&channel, vpi, vci);
+  if (error) {
+    PRINTD (DBG_WARN|DBG_VCC, "VPI/VCI out of range: %hd/%d", vpi, vci);
+    return error;
+  }
+  
+  vcc.channel = channel;
+  // max speed for the moment
+  vcc.tx_rate = 0x0;
+  
+  qos = &atm_vcc->qos;
+  
+  // check AAL and remember it
+  switch (qos->aal) {
+    case ATM_AAL0:
+      // we would if it were 48 bytes and not 52!
+      PRINTD (DBG_QOS|DBG_VCC, "AAL0");
+      vcc.aal = aal0;
+      break;
+    case ATM_AAL34:
+      // we would if I knew how do the SAR!
+      PRINTD (DBG_QOS|DBG_VCC, "AAL3/4");
+      vcc.aal = aal34;
+      break;
+    case ATM_AAL5:
+      PRINTD (DBG_QOS|DBG_VCC, "AAL5");
+      vcc.aal = aal5;
+      break;
+    default:
+      PRINTD (DBG_QOS|DBG_VCC, "Bad AAL!");
+      return -EINVAL;
+      break;
+  }
+  
+  // TX traffic parameters
+  
+  // there are two, interrelated problems here: 1. the reservation of
+  // PCR is not a binary choice, we are given bounds and/or a
+  // desirable value; 2. the device is only capable of certain values,
+  // most of which are not integers. It is almost certainly acceptable
+  // to be off by a maximum of 1 to 10 cps.
+  
+  // Pragmatic choice: always store an integral PCR as that which has
+  // been allocated, even if we allocate a little (or a lot) less,
+  // after rounding. The actual allocation depends on what we can
+  // manage with our rate selection algorithm. The rate selection
+  // algorithm is given an integral PCR and a tolerance and told
+  // whether it should round the value up or down if the tolerance is
+  // exceeded; it returns: a) the actual rate selected (rounded up to
+  // the nearest integer), b) a bit pattern to feed to the timer
+  // register, and c) a failure value if no applicable rate exists.
+  
+  // Part of the job is done by atm_pcr_goal which gives us a PCR
+  // specification which says: EITHER grab the maximum available PCR
+  // (and perhaps a lower bound which we musn't pass), OR grab this
+  // amount, rounding down if you have to (and perhaps a lower bound
+  // which we musn't pass) OR grab this amount, rounding up if you
+  // have to (and perhaps an upper bound which we musn't pass). If any
+  // bounds ARE passed we fail. Note that rounding is only rounding to
+  // match device limitations, we do not round down to satisfy
+  // bandwidth availability even if this would not violate any given
+  // lower bound.
+  
+  // Note: telephony = 64kb/s = 48 byte cell payload @ 500/3 cells/s
+  // (say) so this is not even a binary fixpoint cell rate (but this
+  // device can do it). To avoid this sort of hassle we use a
+  // tolerance parameter (currently fixed at 10 cps).
+  
+  PRINTD (DBG_QOS, "TX:");
+  
+  txtp = &qos->txtp;
+  
+  // set up defaults for no traffic
+  vcc.tx_rate = 0;
+  // who knows what would actually happen if you try and send on this?
+  vcc.tx_xbr_bits = IDLE_RATE_TYPE;
+  vcc.tx_pcr_bits = CLOCK_DISABLE;
+#if 0
+  vcc.tx_scr_bits = CLOCK_DISABLE;
+  vcc.tx_bucket_bits = 0;
+#endif
+  
+  if (txtp->traffic_class != ATM_NONE) {
+    error = check_max_sdu (vcc.aal, txtp, max_tx_size);
+    if (error) {
+      PRINTD (DBG_QOS, "TX max_sdu check failed");
+      return error;
+    }
+    
+    switch (txtp->traffic_class) {
+      case ATM_UBR: {
+	// we take "the PCR" as a rate-cap
+	// not reserved
+	vcc.tx_rate = 0;
+	make_rate (dev, 1<<30, round_nearest, &vcc.tx_pcr_bits, NULL);
+	vcc.tx_xbr_bits = ABR_RATE_TYPE;
+	break;
+      }
+#if 0
+      case ATM_ABR: {
+	// reserve min, allow up to max
+	vcc.tx_rate = 0; // ?
+	make_rate (dev, 1<<30, round_nearest, &vcc.tx_pcr_bits, 0);
+	vcc.tx_xbr_bits = ABR_RATE_TYPE;
+	break;
+      }
+#endif
+      case ATM_CBR: {
+	int pcr = atm_pcr_goal (txtp);
+	rounding r;
+	if (!pcr) {
+	  // down vs. up, remaining bandwidth vs. unlimited bandwidth!!
+	  // should really have: once someone gets unlimited bandwidth
+	  // that no more non-UBR channels can be opened until the
+	  // unlimited one closes?? For the moment, round_down means
+	  // greedy people actually get something and not nothing
+	  r = round_down;
+	  // slight race (no locking) here so we may get -EAGAIN
+	  // later; the greedy bastards would deserve it :)
+	  PRINTD (DBG_QOS, "snatching all remaining TX bandwidth");
+	  pcr = dev->tx_avail;
+	} else if (pcr < 0) {
+	  r = round_down;
+	  pcr = -pcr;
+	} else {
+	  r = round_up;
+	}
+	error = make_rate_with_tolerance (dev, pcr, r, 10,
+					  &vcc.tx_pcr_bits, &vcc.tx_rate);
+	if (error) {
+	  PRINTD (DBG_QOS, "could not make rate from TX PCR");
+	  return error;
+	}
+	// not really clear what further checking is needed
+	error = atm_pcr_check (txtp, vcc.tx_rate);
+	if (error) {
+	  PRINTD (DBG_QOS, "TX PCR failed consistency check");
+	  return error;
+	}
+	vcc.tx_xbr_bits = CBR_RATE_TYPE;
+	break;
+      }
+#if 0
+      case ATM_VBR: {
+	int pcr = atm_pcr_goal (txtp);
+	// int scr = atm_scr_goal (txtp);
+	int scr = pcr/2; // just for fun
+	unsigned int mbs = 60; // just for fun
+	rounding pr;
+	rounding sr;
+	unsigned int bucket;
+	if (!pcr) {
+	  pr = round_nearest;
+	  pcr = 1<<30;
+	} else if (pcr < 0) {
+	  pr = round_down;
+	  pcr = -pcr;
+	} else {
+	  pr = round_up;
+	}
+	error = make_rate_with_tolerance (dev, pcr, pr, 10,
+					  &vcc.tx_pcr_bits, 0);
+	if (!scr) {
+	  // see comments for PCR with CBR above
+	  sr = round_down;
+	  // slight race (no locking) here so we may get -EAGAIN
+	  // later; the greedy bastards would deserve it :)
+	  PRINTD (DBG_QOS, "snatching all remaining TX bandwidth");
+	  scr = dev->tx_avail;
+	} else if (scr < 0) {
+	  sr = round_down;
+	  scr = -scr;
+	} else {
+	  sr = round_up;
+	}
+	error = make_rate_with_tolerance (dev, scr, sr, 10,
+					  &vcc.tx_scr_bits, &vcc.tx_rate);
+	if (error) {
+	  PRINTD (DBG_QOS, "could not make rate from TX SCR");
+	  return error;
+	}
+	// not really clear what further checking is needed
+	// error = atm_scr_check (txtp, vcc.tx_rate);
+	if (error) {
+	  PRINTD (DBG_QOS, "TX SCR failed consistency check");
+	  return error;
+	}
+	// bucket calculations (from a piece of paper...) cell bucket
+	// capacity must be largest integer smaller than m(p-s)/p + 1
+	// where m = max burst size, p = pcr, s = scr
+	bucket = mbs*(pcr-scr)/pcr;
+	if (bucket*pcr != mbs*(pcr-scr))
+	  bucket += 1;
+	if (bucket > BUCKET_MAX_SIZE) {
+	  PRINTD (DBG_QOS, "shrinking bucket from %u to %u",
+		  bucket, BUCKET_MAX_SIZE);
+	  bucket = BUCKET_MAX_SIZE;
+	}
+	vcc.tx_xbr_bits = VBR_RATE_TYPE;
+	vcc.tx_bucket_bits = bucket;
+	break;
+      }
+#endif
+      default: {
+	PRINTD (DBG_QOS, "unsupported TX traffic class");
+	return -EINVAL;
+	break;
+      }
+    }
+  }
+  
+  // RX traffic parameters
+  
+  PRINTD (DBG_QOS, "RX:");
+  
+  rxtp = &qos->rxtp;
+  
+  // set up defaults for no traffic
+  vcc.rx_rate = 0;
+  
+  if (rxtp->traffic_class != ATM_NONE) {
+    error = check_max_sdu (vcc.aal, rxtp, max_rx_size);
+    if (error) {
+      PRINTD (DBG_QOS, "RX max_sdu check failed");
+      return error;
+    }
+    switch (rxtp->traffic_class) {
+      case ATM_UBR: {
+	// not reserved
+	break;
+      }
+#if 0
+      case ATM_ABR: {
+	// reserve min
+	vcc.rx_rate = 0; // ?
+	break;
+      }
+#endif
+      case ATM_CBR: {
+	int pcr = atm_pcr_goal (rxtp);
+	if (!pcr) {
+	  // slight race (no locking) here so we may get -EAGAIN
+	  // later; the greedy bastards would deserve it :)
+	  PRINTD (DBG_QOS, "snatching all remaining RX bandwidth");
+	  pcr = dev->rx_avail;
+	} else if (pcr < 0) {
+	  pcr = -pcr;
+	}
+	vcc.rx_rate = pcr;
+	// not really clear what further checking is needed
+	error = atm_pcr_check (rxtp, vcc.rx_rate);
+	if (error) {
+	  PRINTD (DBG_QOS, "RX PCR failed consistency check");
+	  return error;
+	}
+	break;
+      }
+#if 0
+      case ATM_VBR: {
+	// int scr = atm_scr_goal (rxtp);
+	int scr = 1<<16; // just for fun
+	if (!scr) {
+	  // slight race (no locking) here so we may get -EAGAIN
+	  // later; the greedy bastards would deserve it :)
+	  PRINTD (DBG_QOS, "snatching all remaining RX bandwidth");
+	  scr = dev->rx_avail;
+	} else if (scr < 0) {
+	  scr = -scr;
+	}
+	vcc.rx_rate = scr;
+	// not really clear what further checking is needed
+	// error = atm_scr_check (rxtp, vcc.rx_rate);
+	if (error) {
+	  PRINTD (DBG_QOS, "RX SCR failed consistency check");
+	  return error;
+	}
+	break;
+      }
+#endif
+      default: {
+	PRINTD (DBG_QOS, "unsupported RX traffic class");
+	return -EINVAL;
+	break;
+      }
+    }
+  }
+  
+  
+  // late abort useful for diagnostics
+  if (vcc.aal != aal5) {
+    PRINTD (DBG_QOS, "AAL not supported");
+    return -EINVAL;
+  }
+  
+  // get space for our vcc stuff and copy parameters into it
+  vccp = kmalloc (sizeof(hrz_vcc), GFP_KERNEL);
+  if (!vccp) {
+    PRINTK (KERN_ERR, "out of memory!");
+    return -ENOMEM;
+  }
+  *vccp = vcc;
+  
+  // clear error and grab cell rate resource lock
+  error = 0;
+  spin_lock (&dev->rate_lock);
+  
+  if (vcc.tx_rate > dev->tx_avail) {
+    PRINTD (DBG_QOS, "not enough TX PCR left");
+    error = -EAGAIN;
+  }
+  
+  if (vcc.rx_rate > dev->rx_avail) {
+    PRINTD (DBG_QOS, "not enough RX PCR left");
+    error = -EAGAIN;
+  }
+  
+  if (!error) {
+    // really consume cell rates
+    dev->tx_avail -= vcc.tx_rate;
+    dev->rx_avail -= vcc.rx_rate;
+    PRINTD (DBG_QOS|DBG_VCC, "reserving %u TX PCR and %u RX PCR",
+	    vcc.tx_rate, vcc.rx_rate);
+  }
+  
+  // release lock and exit on error
+  spin_unlock (&dev->rate_lock);
+  if (error) {
+    PRINTD (DBG_QOS|DBG_VCC, "insufficient cell rate resources");
+    kfree (vccp);
+    return error;
+  }
+  
+  // this is "immediately before allocating the connection identifier
+  // in hardware" - so long as the next call does not fail :)
+  set_bit(ATM_VF_ADDR,&atm_vcc->flags);
+  
+  // any errors here are very serious and should never occur
+  
+  if (rxtp->traffic_class != ATM_NONE) {
+    if (dev->rxer[channel]) {
+      PRINTD (DBG_ERR|DBG_VCC, "VC already open for RX");
+      error = -EBUSY;
+    }
+    if (!error)
+      error = hrz_open_rx (dev, channel);
+    if (error) {
+      kfree (vccp);
+      return error;
+    }
+    // this link allows RX frames through
+    dev->rxer[channel] = atm_vcc;
+  }
+  
+  // success, set elements of atm_vcc
+  atm_vcc->dev_data = (void *) vccp;
+  
+  // indicate readiness
+  set_bit(ATM_VF_READY,&atm_vcc->flags);
+  
+  return 0;
+}
+
+/********** close VC **********/
+
+static void hrz_close (struct atm_vcc * atm_vcc) {
+  hrz_dev * dev = HRZ_DEV(atm_vcc->dev);
+  hrz_vcc * vcc = HRZ_VCC(atm_vcc);
+  u16 channel = vcc->channel;
+  PRINTD (DBG_VCC|DBG_FLOW, "hrz_close");
+  
+  // indicate unreadiness
+  clear_bit(ATM_VF_READY,&atm_vcc->flags);
+
+  if (atm_vcc->qos.txtp.traffic_class != ATM_NONE) {
+    unsigned int i;
+    
+    // let any TX on this channel that has started complete
+    // no restart, just keep trying
+    while (tx_hold (dev))
+      ;
+    // remove record of any tx_channel having been setup for this channel
+    for (i = 0; i < TX_CHANS; ++i)
+      if (dev->tx_channel_record[i] == channel) {
+	dev->tx_channel_record[i] = -1;
+	break;
+      }
+    if (dev->last_vc == channel)
+      dev->tx_last = -1;
+    tx_release (dev);
+  }
+
+  if (atm_vcc->qos.rxtp.traffic_class != ATM_NONE) {
+    // disable RXing - it tries quite hard
+    hrz_close_rx (dev, channel);
+    // forget the vcc - no more skbs will be pushed
+    if (atm_vcc != dev->rxer[channel])
+      PRINTK (KERN_ERR, "%s atm_vcc=%p rxer[channel]=%p",
+	      "arghhh! we're going to die!",
+	      atm_vcc, dev->rxer[channel]);
+    dev->rxer[channel] = NULL;
+  }
+  
+  // atomically release our rate reservation
+  spin_lock (&dev->rate_lock);
+  PRINTD (DBG_QOS|DBG_VCC, "releasing %u TX PCR and %u RX PCR",
+	  vcc->tx_rate, vcc->rx_rate);
+  dev->tx_avail += vcc->tx_rate;
+  dev->rx_avail += vcc->rx_rate;
+  spin_unlock (&dev->rate_lock);
+  
+  // free our structure
+  kfree (vcc);
+  // say the VPI/VCI is free again
+  clear_bit(ATM_VF_ADDR,&atm_vcc->flags);
+}
+
+#if 0
+static int hrz_getsockopt (struct atm_vcc * atm_vcc, int level, int optname,
+			   void *optval, int optlen) {
+  hrz_dev * dev = HRZ_DEV(atm_vcc->dev);
+  PRINTD (DBG_FLOW|DBG_VCC, "hrz_getsockopt");
+  switch (level) {
+    case SOL_SOCKET:
+      switch (optname) {
+//	case SO_BCTXOPT:
+//	  break;
+//	case SO_BCRXOPT:
+//	  break;
+	default:
+	  return -ENOPROTOOPT;
+	  break;
+      };
+      break;
+  }
+  return -EINVAL;
+}
+
+static int hrz_setsockopt (struct atm_vcc * atm_vcc, int level, int optname,
+			   void *optval, int optlen) {
+  hrz_dev * dev = HRZ_DEV(atm_vcc->dev);
+  PRINTD (DBG_FLOW|DBG_VCC, "hrz_setsockopt");
+  switch (level) {
+    case SOL_SOCKET:
+      switch (optname) {
+//	case SO_BCTXOPT:
+//	  break;
+//	case SO_BCRXOPT:
+//	  break;
+	default:
+	  return -ENOPROTOOPT;
+	  break;
+      };
+      break;
+  }
+  return -EINVAL;
+}
+#endif
+
+#if 0
+static int hrz_ioctl (struct atm_dev * atm_dev, unsigned int cmd, void *arg) {
+  hrz_dev * dev = HRZ_DEV(atm_dev);
+  PRINTD (DBG_FLOW, "hrz_ioctl");
+  return -1;
+}
+
+unsigned char hrz_phy_get (struct atm_dev * atm_dev, unsigned long addr) {
+  hrz_dev * dev = HRZ_DEV(atm_dev);
+  PRINTD (DBG_FLOW, "hrz_phy_get");
+  return 0;
+}
+
+static void hrz_phy_put (struct atm_dev * atm_dev, unsigned char value,
+			 unsigned long addr) {
+  hrz_dev * dev = HRZ_DEV(atm_dev);
+  PRINTD (DBG_FLOW, "hrz_phy_put");
+}
+
+static int hrz_change_qos (struct atm_vcc * atm_vcc, struct atm_qos *qos, int flgs) {
+  hrz_dev * dev = HRZ_DEV(vcc->dev);
+  PRINTD (DBG_FLOW, "hrz_change_qos");
+  return -1;
+}
+#endif
+
+/********** proc file contents **********/
+
+static int hrz_proc_read (struct atm_dev * atm_dev, loff_t * pos, char * page) {
+  hrz_dev * dev = HRZ_DEV(atm_dev);
+  int left = *pos;
+  PRINTD (DBG_FLOW, "hrz_proc_read");
+  
+  /* more diagnostics here? */
+  
+#if 0
+  if (!left--) {
+    unsigned int count = sprintf (page, "vbr buckets:");
+    unsigned int i;
+    for (i = 0; i < TX_CHANS; ++i)
+      count += sprintf (page, " %u/%u",
+			query_tx_channel_config (dev, i, BUCKET_FULLNESS_ACCESS),
+			query_tx_channel_config (dev, i, BUCKET_CAPACITY_ACCESS));
+    count += sprintf (page+count, ".\n");
+    return count;
+  }
+#endif
+  
+  if (!left--)
+    return sprintf (page,
+		    "cells: TX %lu, RX %lu, HEC errors %lu, unassigned %lu.\n",
+		    dev->tx_cell_count, dev->rx_cell_count,
+		    dev->hec_error_count, dev->unassigned_cell_count);
+  
+  if (!left--)
+    return sprintf (page,
+		    "free cell buffers: TX %hu, RX %hu+%hu.\n",
+		    rd_regw (dev, TX_FREE_BUFFER_COUNT_OFF),
+		    rd_regw (dev, RX_FREE_BUFFER_COUNT_OFF),
+		    dev->noof_spare_buffers);
+  
+  if (!left--)
+    return sprintf (page,
+		    "cps remaining: TX %u, RX %u\n",
+		    dev->tx_avail, dev->rx_avail);
+  
+  return 0;
+}
+
+static const struct atmdev_ops hrz_ops = {
+  .open	= hrz_open,
+  .close	= hrz_close,
+  .send	= hrz_send,
+  .proc_read	= hrz_proc_read,
+  .owner	= THIS_MODULE,
+};
+
+static int __devinit hrz_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_ent)
+{
+	hrz_dev * dev;
+	int err = 0;
+
+	// adapter slot free, read resources from PCI configuration space
+	u32 iobase = pci_resource_start (pci_dev, 0);
+	u32 * membase = bus_to_virt (pci_resource_start (pci_dev, 1));
+	unsigned int irq;
+	unsigned char lat;
+
+	PRINTD (DBG_FLOW, "hrz_probe");
+
+	if (pci_enable_device(pci_dev))
+		return -EINVAL;
+
+	/* XXX DEV_LABEL is a guess */
+	if (!request_region(iobase, HRZ_IO_EXTENT, DEV_LABEL)) {
+		return -EINVAL;
+		goto out_disable;
+	}
+
+	dev = kmalloc(sizeof(hrz_dev), GFP_KERNEL);
+	if (!dev) {
+		// perhaps we should be nice: deregister all adapters and abort?
+		PRINTD(DBG_ERR, "out of memory");
+		err = -ENOMEM;
+		goto out_release;
+	}
+
+	memset(dev, 0, sizeof(hrz_dev));
+
+	pci_set_drvdata(pci_dev, dev);
+
+	// grab IRQ and install handler - move this someplace more sensible
+	irq = pci_dev->irq;
+	if (request_irq(irq,
+			interrupt_handler,
+			SA_SHIRQ, /* irqflags guess */
+			DEV_LABEL, /* name guess */
+			dev)) {
+		PRINTD(DBG_WARN, "request IRQ failed!");
+		err = -EINVAL;
+		goto out_free;
+	}
+
+	PRINTD(DBG_INFO, "found Madge ATM adapter (hrz) at: IO %x, IRQ %u, MEM %p",
+	       iobase, irq, membase);
+
+	dev->atm_dev = atm_dev_register(DEV_LABEL, &hrz_ops, -1, NULL);
+	if (!(dev->atm_dev)) {
+		PRINTD(DBG_ERR, "failed to register Madge ATM adapter");
+		err = -EINVAL;
+		goto out_free_irq;
+	}
+
+	PRINTD(DBG_INFO, "registered Madge ATM adapter (no. %d) (%p) at %p",
+	       dev->atm_dev->number, dev, dev->atm_dev);
+	dev->atm_dev->dev_data = (void *) dev;
+	dev->pci_dev = pci_dev; 
+
+	// enable bus master accesses
+	pci_set_master(pci_dev);
+
+	// frobnicate latency (upwards, usually)
+	pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &lat);
+	if (pci_lat) {
+		PRINTD(DBG_INFO, "%s PCI latency timer from %hu to %hu",
+		       "changing", lat, pci_lat);
+		pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, pci_lat);
+	} else if (lat < MIN_PCI_LATENCY) {
+		PRINTK(KERN_INFO, "%s PCI latency timer from %hu to %hu",
+		       "increasing", lat, MIN_PCI_LATENCY);
+		pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, MIN_PCI_LATENCY);
+	}
+
+	dev->iobase = iobase;
+	dev->irq = irq; 
+	dev->membase = membase; 
+
+	dev->rx_q_entry = dev->rx_q_reset = &memmap->rx_q_entries[0];
+	dev->rx_q_wrap  = &memmap->rx_q_entries[RX_CHANS-1];
+
+	// these next three are performance hacks
+	dev->last_vc = -1;
+	dev->tx_last = -1;
+	dev->tx_idle = 0;
+
+	dev->tx_regions = 0;
+	dev->tx_bytes = 0;
+	dev->tx_skb = NULL;
+	dev->tx_iovec = NULL;
+
+	dev->tx_cell_count = 0;
+	dev->rx_cell_count = 0;
+	dev->hec_error_count = 0;
+	dev->unassigned_cell_count = 0;
+
+	dev->noof_spare_buffers = 0;
+
+	{
+		unsigned int i;
+		for (i = 0; i < TX_CHANS; ++i)
+			dev->tx_channel_record[i] = -1;
+	}
+
+	dev->flags = 0;
+
+	// Allocate cell rates and remember ASIC version
+	// Fibre: ATM_OC3_PCR = 1555200000/8/270*260/53 - 29/53
+	// Copper: (WRONG) we want 6 into the above, close to 25Mb/s
+	// Copper: (plagarise!) 25600000/8/270*260/53 - n/53
+
+	if (hrz_init(dev)) {
+		// to be really pedantic, this should be ATM_OC3c_PCR
+		dev->tx_avail = ATM_OC3_PCR;
+		dev->rx_avail = ATM_OC3_PCR;
+		set_bit(ultra, &dev->flags); // NOT "|= ultra" !
+	} else {
+		dev->tx_avail = ((25600000/8)*26)/(27*53);
+		dev->rx_avail = ((25600000/8)*26)/(27*53);
+		PRINTD(DBG_WARN, "Buggy ASIC: no TX bus-mastering.");
+	}
+
+	// rate changes spinlock
+	spin_lock_init(&dev->rate_lock);
+
+	// on-board memory access spinlock; we want atomic reads and
+	// writes to adapter memory (handles IRQ and SMP)
+	spin_lock_init(&dev->mem_lock);
+
+	init_waitqueue_head(&dev->tx_queue);
+
+	// vpi in 0..4, vci in 6..10
+	dev->atm_dev->ci_range.vpi_bits = vpi_bits;
+	dev->atm_dev->ci_range.vci_bits = 10-vpi_bits;
+
+	init_timer(&dev->housekeeping);
+	dev->housekeeping.function = do_housekeeping;
+	dev->housekeeping.data = (unsigned long) dev;
+	mod_timer(&dev->housekeeping, jiffies);
+
+out:
+	return err;
+
+out_free_irq:
+	free_irq(dev->irq, dev);
+out_free:
+	kfree(dev);
+out_release:
+	release_region(iobase, HRZ_IO_EXTENT);
+out_disable:
+	pci_disable_device(pci_dev);
+	goto out;
+}
+
+static void __devexit hrz_remove_one(struct pci_dev *pci_dev)
+{
+	hrz_dev *dev;
+
+	dev = pci_get_drvdata(pci_dev);
+
+	PRINTD(DBG_INFO, "closing %p (atm_dev = %p)", dev, dev->atm_dev);
+	del_timer_sync(&dev->housekeeping);
+	hrz_reset(dev);
+	atm_dev_deregister(dev->atm_dev);
+	free_irq(dev->irq, dev);
+	release_region(dev->iobase, HRZ_IO_EXTENT);
+	kfree(dev);
+
+	pci_disable_device(pci_dev);
+}
+
+static void __init hrz_check_args (void) {
+#ifdef DEBUG_HORIZON
+  PRINTK (KERN_NOTICE, "debug bitmap is %hx", debug &= DBG_MASK);
+#else
+  if (debug)
+    PRINTK (KERN_NOTICE, "no debug support in this image");
+#endif
+  
+  if (vpi_bits > HRZ_MAX_VPI)
+    PRINTK (KERN_ERR, "vpi_bits has been limited to %hu",
+	    vpi_bits = HRZ_MAX_VPI);
+  
+  if (max_tx_size < 0 || max_tx_size > TX_AAL5_LIMIT)
+    PRINTK (KERN_NOTICE, "max_tx_size has been limited to %hu",
+	    max_tx_size = TX_AAL5_LIMIT);
+  
+  if (max_rx_size < 0 || max_rx_size > RX_AAL5_LIMIT)
+    PRINTK (KERN_NOTICE, "max_rx_size has been limited to %hu",
+	    max_rx_size = RX_AAL5_LIMIT);
+  
+  return;
+}
+
+MODULE_AUTHOR(maintainer_string);
+MODULE_DESCRIPTION(description_string);
+MODULE_LICENSE("GPL");
+module_param(debug, ushort, 0644);
+module_param(vpi_bits, ushort, 0);
+module_param(max_tx_size, int, 0);
+module_param(max_rx_size, int, 0);
+module_param(pci_lat, byte, 0);
+MODULE_PARM_DESC(debug, "debug bitmap, see .h file");
+MODULE_PARM_DESC(vpi_bits, "number of bits (0..4) to allocate to VPIs");
+MODULE_PARM_DESC(max_tx_size, "maximum size of TX AAL5 frames");
+MODULE_PARM_DESC(max_rx_size, "maximum size of RX AAL5 frames");
+MODULE_PARM_DESC(pci_lat, "PCI latency in bus cycles");
+
+static struct pci_device_id hrz_pci_tbl[] = {
+	{ PCI_VENDOR_ID_MADGE, PCI_DEVICE_ID_MADGE_HORIZON, PCI_ANY_ID, PCI_ANY_ID,
+	  0, 0, 0 },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, hrz_pci_tbl);
+
+static struct pci_driver hrz_driver = {
+	.name =		"horizon",
+	.probe =	hrz_probe,
+	.remove =	__devexit_p(hrz_remove_one),
+	.id_table =	hrz_pci_tbl,
+};
+
+/********** module entry **********/
+
+static int __init hrz_module_init (void) {
+  // sanity check - cast is needed since printk does not support %Zu
+  if (sizeof(struct MEMMAP) != 128*1024/4) {
+    PRINTK (KERN_ERR, "Fix struct MEMMAP (is %lu fakewords).",
+	    (unsigned long) sizeof(struct MEMMAP));
+    return -ENOMEM;
+  }
+  
+  show_version();
+  
+  // check arguments
+  hrz_check_args();
+  
+  // get the juice
+  return pci_register_driver(&hrz_driver);
+}
+
+/********** module exit **********/
+
+static void __exit hrz_module_exit (void) {
+  PRINTD (DBG_FLOW, "cleanup_module");
+  
+  return pci_unregister_driver(&hrz_driver);
+}
+
+module_init(hrz_module_init);
+module_exit(hrz_module_exit);
diff --git a/drivers/atm/horizon.h b/drivers/atm/horizon.h
new file mode 100644
index 0000000..e2cc702
--- /dev/null
+++ b/drivers/atm/horizon.h
@@ -0,0 +1,508 @@
+/*
+  Madge Horizon ATM Adapter driver.
+  Copyright (C) 1995-1999  Madge Networks Ltd.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+  system and in the file COPYING in the Linux kernel source.
+*/
+
+/*
+  IMPORTANT NOTE: Madge Networks no longer makes the adapters
+  supported by this driver and makes no commitment to maintain it.
+*/
+
+/* too many macros - change to inline functions */
+
+#ifndef DRIVER_ATM_HORIZON_H
+#define DRIVER_ATM_HORIZON_H
+
+#include <linux/config.h>
+
+#ifdef CONFIG_ATM_HORIZON_DEBUG
+#define DEBUG_HORIZON
+#endif
+
+#define DEV_LABEL                         "hrz"
+
+#ifndef PCI_VENDOR_ID_MADGE
+#define PCI_VENDOR_ID_MADGE               0x10B6
+#endif
+#ifndef PCI_DEVICE_ID_MADGE_HORIZON
+#define PCI_DEVICE_ID_MADGE_HORIZON       0x1000
+#endif
+
+// diagnostic output
+
+#define PRINTK(severity,format,args...) \
+  printk(severity DEV_LABEL ": " format "\n" , ## args)
+
+#ifdef DEBUG_HORIZON
+
+#define DBG_ERR  0x0001
+#define DBG_WARN 0x0002
+#define DBG_INFO 0x0004
+#define DBG_VCC  0x0008
+#define DBG_QOS  0x0010
+#define DBG_TX   0x0020
+#define DBG_RX   0x0040
+#define DBG_SKB  0x0080
+#define DBG_IRQ  0x0100
+#define DBG_FLOW 0x0200
+#define DBG_BUS  0x0400
+#define DBG_REGS 0x0800
+#define DBG_DATA 0x1000
+#define DBG_MASK 0x1fff
+
+/* the ## prevents the annoying double expansion of the macro arguments */
+/* KERN_INFO is used since KERN_DEBUG often does not make it to the console */
+#define PRINTDB(bits,format,args...) \
+  ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format , ## args) : 1 )
+#define PRINTDM(bits,format,args...) \
+  ( (debug & (bits)) ? printk (format , ## args) : 1 )
+#define PRINTDE(bits,format,args...) \
+  ( (debug & (bits)) ? printk (format "\n" , ## args) : 1 )
+#define PRINTD(bits,format,args...) \
+  ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format "\n" , ## args) : 1 )
+
+#else
+
+#define PRINTD(bits,format,args...)
+#define PRINTDB(bits,format,args...)
+#define PRINTDM(bits,format,args...)
+#define PRINTDE(bits,format,args...)
+
+#endif
+
+#define PRINTDD(sec,fmt,args...)
+#define PRINTDDB(sec,fmt,args...)
+#define PRINTDDM(sec,fmt,args...)
+#define PRINTDDE(sec,fmt,args...)
+
+// fixed constants
+
+#define SPARE_BUFFER_POOL_SIZE            MAX_VCS
+#define HRZ_MAX_VPI                       4
+#define MIN_PCI_LATENCY                   48 // 24 IS TOO SMALL
+
+/*  Horizon specific bits */
+/*  Register offsets */
+
+#define HRZ_IO_EXTENT                     0x80
+
+#define DATA_PORT_OFF                     0x00
+#define TX_CHANNEL_PORT_OFF               0x04
+#define TX_DESCRIPTOR_PORT_OFF            0x08
+#define MEMORY_PORT_OFF                   0x0C
+#define MEM_WR_ADDR_REG_OFF               0x14
+#define MEM_RD_ADDR_REG_OFF               0x18
+#define CONTROL_0_REG                     0x1C
+#define INT_SOURCE_REG_OFF                0x20
+#define INT_ENABLE_REG_OFF                0x24
+#define MASTER_RX_ADDR_REG_OFF            0x28
+#define MASTER_RX_COUNT_REG_OFF           0x2C
+#define MASTER_TX_ADDR_REG_OFF            0x30
+#define MASTER_TX_COUNT_REG_OFF           0x34
+#define TX_DESCRIPTOR_REG_OFF             0x38
+#define TX_CHANNEL_CONFIG_COMMAND_OFF     0x40
+#define TX_CHANNEL_CONFIG_DATA_OFF        0x44
+#define TX_FREE_BUFFER_COUNT_OFF          0x48
+#define RX_FREE_BUFFER_COUNT_OFF          0x4C
+#define TX_CONFIG_OFF                     0x50
+#define TX_STATUS_OFF                     0x54
+#define RX_CONFIG_OFF                     0x58
+#define RX_LINE_CONFIG_OFF                0x5C
+#define RX_QUEUE_RD_PTR_OFF               0x60
+#define RX_QUEUE_WR_PTR_OFF               0x64
+#define MAX_AAL5_CELL_COUNT_OFF           0x68
+#define RX_CHANNEL_PORT_OFF               0x6C
+#define TX_CELL_COUNT_OFF                 0x70
+#define RX_CELL_COUNT_OFF                 0x74
+#define HEC_ERROR_COUNT_OFF               0x78
+#define UNASSIGNED_CELL_COUNT_OFF         0x7C
+
+/*  Register bit definitions */
+
+/* Control 0 register */
+
+#define SEEPROM_DO                        0x00000001
+#define SEEPROM_DI                        0x00000002
+#define SEEPROM_SK                        0x00000004
+#define SEEPROM_CS                        0x00000008
+#define DEBUG_BIT_0                       0x00000010
+#define DEBUG_BIT_1                       0x00000020
+#define DEBUG_BIT_2                       0x00000040
+//      RESERVED                          0x00000080
+#define DEBUG_BIT_0_OE                    0x00000100
+#define DEBUG_BIT_1_OE                    0x00000200
+#define DEBUG_BIT_2_OE                    0x00000400
+//      RESERVED                          0x00000800
+#define DEBUG_BIT_0_STATE                 0x00001000
+#define DEBUG_BIT_1_STATE                 0x00002000
+#define DEBUG_BIT_2_STATE                 0x00004000
+//      RESERVED                          0x00008000
+#define GENERAL_BIT_0                     0x00010000
+#define GENERAL_BIT_1                     0x00020000
+#define GENERAL_BIT_2                     0x00040000
+#define GENERAL_BIT_3                     0x00080000
+#define RESET_HORIZON                     0x00100000
+#define RESET_ATM                         0x00200000
+#define RESET_RX                          0x00400000
+#define RESET_TX                          0x00800000
+#define RESET_HOST                        0x01000000
+//      RESERVED                          0x02000000
+#define TARGET_RETRY_DISABLE              0x04000000
+#define ATM_LAYER_SELECT                  0x08000000
+#define ATM_LAYER_STATUS                  0x10000000
+//      RESERVED                          0xE0000000
+
+/* Interrupt source and enable registers */
+
+#define RX_DATA_AV                        0x00000001
+#define RX_DISABLED                       0x00000002
+#define TIMING_MARKER                     0x00000004
+#define FORCED                            0x00000008
+#define RX_BUS_MASTER_COMPLETE            0x00000010
+#define TX_BUS_MASTER_COMPLETE            0x00000020
+#define ABR_TX_CELL_COUNT_INT             0x00000040
+#define DEBUG_INT                         0x00000080
+//      RESERVED                          0xFFFFFF00
+
+/* PIO and Bus Mastering */
+
+#define MAX_PIO_COUNT                     0x000000ff // 255 - make tunable?
+// 8188 is a hard limit for bus mastering
+#define MAX_TRANSFER_COUNT                0x00001ffc // 8188
+#define MASTER_TX_AUTO_APPEND_DESC        0x80000000
+
+/* TX channel config command port */
+
+#define PCR_TIMER_ACCESS                      0x0000
+#define SCR_TIMER_ACCESS                      0x0001
+#define BUCKET_CAPACITY_ACCESS                0x0002
+#define BUCKET_FULLNESS_ACCESS                0x0003
+#define RATE_TYPE_ACCESS                      0x0004
+//      UNUSED                                0x00F8
+#define TX_CHANNEL_CONFIG_MULT                0x0100
+//      UNUSED                                0xF800
+#define BUCKET_MAX_SIZE                       0x003f
+
+/* TX channel config data port */
+
+#define CLOCK_SELECT_SHIFT                    4
+#define CLOCK_DISABLE                         0x00ff
+
+#define IDLE_RATE_TYPE                       0x0
+#define ABR_RATE_TYPE                        0x1
+#define VBR_RATE_TYPE                        0x2
+#define CBR_RATE_TYPE                        0x3
+
+/* TX config register */
+
+#define DRVR_DRVRBAR_ENABLE                   0x0001
+#define TXCLK_MUX_SELECT_RCLK                 0x0002
+#define TRANSMIT_TIMING_MARKER                0x0004
+#define LOOPBACK_TIMING_MARKER                0x0008
+#define TX_TEST_MODE_16MHz                    0x0000
+#define TX_TEST_MODE_8MHz                     0x0010
+#define TX_TEST_MODE_5_33MHz                  0x0020
+#define TX_TEST_MODE_4MHz                     0x0030
+#define TX_TEST_MODE_3_2MHz                   0x0040
+#define TX_TEST_MODE_2_66MHz                  0x0050
+#define TX_TEST_MODE_2_29MHz                  0x0060
+#define TX_NORMAL_OPERATION                   0x0070
+#define ABR_ROUND_ROBIN                       0x0080
+
+/* TX status register */
+
+#define IDLE_CHANNELS_MASK                    0x00FF
+#define ABR_CELL_COUNT_REACHED_MULT           0x0100 
+#define ABR_CELL_COUNT_REACHED_MASK           0xFF
+
+/* RX config register */
+
+#define NON_USER_CELLS_IN_ONE_CHANNEL         0x0008
+#define RX_ENABLE                             0x0010
+#define IGNORE_UNUSED_VPI_VCI_BITS_SET        0x0000
+#define NON_USER_UNUSED_VPI_VCI_BITS_SET      0x0020
+#define DISCARD_UNUSED_VPI_VCI_BITS_SET       0x0040
+
+/* RX line config register */
+
+#define SIGNAL_LOSS                           0x0001
+#define FREQUENCY_DETECT_ERROR                0x0002
+#define LOCK_DETECT_ERROR                     0x0004
+#define SELECT_INTERNAL_LOOPBACK              0x0008
+#define LOCK_DETECT_ENABLE                    0x0010
+#define FREQUENCY_DETECT_ENABLE               0x0020
+#define USER_FRAQ                             0x0040
+#define GXTALOUT_SELECT_DIV4                  0x0080
+#define GXTALOUT_SELECT_NO_GATING             0x0100
+#define TIMING_MARKER_RECEIVED                0x0200
+
+/* RX channel port */
+
+#define RX_CHANNEL_MASK                       0x03FF
+// UNUSED                                     0x3C00
+#define FLUSH_CHANNEL                         0x4000
+#define RX_CHANNEL_UPDATE_IN_PROGRESS         0x8000
+
+/* Receive queue entry */
+
+#define RX_Q_ENTRY_LENGTH_MASK            0x0000FFFF
+#define RX_Q_ENTRY_CHANNEL_SHIFT          16
+#define SIMONS_DODGEY_MARKER              0x08000000
+#define RX_CONGESTION_EXPERIENCED         0x10000000
+#define RX_CRC_10_OK                      0x20000000
+#define RX_CRC_32_OK                      0x40000000
+#define RX_COMPLETE_FRAME                 0x80000000
+
+/*  Offsets and constants for use with the buffer memory         */
+
+/* Buffer pointers and channel types */
+
+#define BUFFER_PTR_MASK                   0x0000FFFF
+#define RX_INT_THRESHOLD_MULT             0x00010000
+#define RX_INT_THRESHOLD_MASK             0x07FF
+#define INT_EVERY_N_CELLS                 0x08000000
+#define CONGESTION_EXPERIENCED            0x10000000
+#define FIRST_CELL_OF_AAL5_FRAME          0x20000000
+#define CHANNEL_TYPE_AAL5                 0x00000000
+#define CHANNEL_TYPE_RAW_CELLS            0x40000000
+#define CHANNEL_TYPE_AAL3_4               0x80000000
+
+/* Buffer status stuff */
+
+#define BUFF_STATUS_MASK                  0x00030000
+#define BUFF_STATUS_EMPTY                 0x00000000
+#define BUFF_STATUS_CELL_AV               0x00010000
+#define BUFF_STATUS_LAST_CELL_AV          0x00020000
+
+/* Transmit channel stuff */
+
+/* Receive channel stuff */
+
+#define RX_CHANNEL_DISABLED               0x00000000
+#define RX_CHANNEL_IDLE                   0x00000001
+
+/*  General things */
+
+#define INITIAL_CRC                       0xFFFFFFFF
+
+// A Horizon u32, a byte! Really nasty. Horizon pointers are (32 bit)
+// word addresses and so standard C pointer operations break (as they
+// assume byte addresses); so we pretend that Horizon words (and word
+// pointers) are bytes (and byte pointers) for the purposes of having
+// a memory map that works.
+
+typedef u8 HDW;
+
+typedef struct cell_buf {
+  HDW payload[12];
+  HDW next;
+  HDW cell_count;               // AAL5 rx bufs
+  HDW res;
+  union {
+    HDW partial_crc;            // AAL5 rx bufs
+    HDW cell_header;            // RAW     bufs
+  } u;
+} cell_buf;
+
+typedef struct tx_ch_desc {
+  HDW rd_buf_type;
+  HDW wr_buf_type;
+  HDW partial_crc;
+  HDW cell_header;
+} tx_ch_desc;
+
+typedef struct rx_ch_desc {
+  HDW wr_buf_type;
+  HDW rd_buf_type;
+} rx_ch_desc;
+
+typedef struct rx_q_entry {
+  HDW entry;
+} rx_q_entry;
+
+#define TX_CHANS 8
+#define RX_CHANS 1024
+#define RX_QS 1024
+#define MAX_VCS RX_CHANS
+
+/* Horizon buffer memory map */
+
+// TX Channel Descriptors         2
+// TX Initial Buffers             8 // TX_CHANS
+#define BUFN1_SIZE              118 // (126 - TX_CHANS)
+//      RX/TX Start/End Buffers   4
+#define BUFN2_SIZE              124
+//      RX Queue Entries         64
+#define BUFN3_SIZE              192
+//      RX Channel Descriptors  128
+#define BUFN4_SIZE             1408
+//      TOTAL cell_buff chunks 2048
+
+//    cell_buf             bufs[2048];
+//    HDW                  dws[32768];
+
+typedef struct MEMMAP {
+  tx_ch_desc  tx_descs[TX_CHANS];     //  8 *    4 =    32 , 0x0020
+  cell_buf    inittxbufs[TX_CHANS];   // these are really
+  cell_buf    bufn1[BUFN1_SIZE];      // part of this pool
+  cell_buf    txfreebufstart;
+  cell_buf    txfreebufend;
+  cell_buf    rxfreebufstart;
+  cell_buf    rxfreebufend;           // 8+118+1+1+1+1+124 = 254
+  cell_buf    bufn2[BUFN2_SIZE];      // 16 *  254 =  4064 , 0x1000
+  rx_q_entry  rx_q_entries[RX_QS];    //  1 * 1024 =  1024 , 0x1400
+  cell_buf    bufn3[BUFN3_SIZE];      // 16 *  192 =  3072 , 0x2000
+  rx_ch_desc  rx_descs[MAX_VCS];      //  2 * 1024 =  2048 , 0x2800
+  cell_buf    bufn4[BUFN4_SIZE];      // 16 * 1408 = 22528 , 0x8000
+} MEMMAP;
+
+#define memmap ((MEMMAP *)0)
+
+/* end horizon specific bits */
+
+typedef enum {
+  aal0,
+  aal34,
+  aal5
+} hrz_aal;
+
+typedef enum {
+  tx_busy,
+  rx_busy,
+  ultra
+} hrz_flags;
+
+// a single struct pointed to by atm_vcc->dev_data
+
+typedef struct {
+  unsigned int        tx_rate;
+  unsigned int        rx_rate;
+  u16                 channel;
+  u16                 tx_xbr_bits;
+  u16                 tx_pcr_bits;
+#if 0
+  u16                 tx_scr_bits;
+  u16                 tx_bucket_bits;
+#endif
+  hrz_aal             aal;
+} hrz_vcc;
+
+struct hrz_dev {
+  
+  u32                 iobase;
+  u32 *               membase;
+
+  struct sk_buff *    rx_skb;     // skb being RXed
+  unsigned int        rx_bytes;   // bytes remaining to RX within region
+  void *              rx_addr;    // addr to send bytes to (for PIO)
+  unsigned int        rx_channel; // channel that the skb is going out on
+
+  struct sk_buff *    tx_skb;     // skb being TXed
+  unsigned int        tx_bytes;   // bytes remaining to TX within region
+  void *              tx_addr;    // addr to send bytes from (for PIO)
+  struct iovec *      tx_iovec;   // remaining regions
+  unsigned int        tx_regions; // number of remaining regions
+
+  spinlock_t          mem_lock;
+  wait_queue_head_t   tx_queue;
+
+  u8                  irq;
+  long		      flags;
+  u8                  tx_last;
+  u8                  tx_idle;
+
+  rx_q_entry *        rx_q_reset;
+  rx_q_entry *        rx_q_entry;
+  rx_q_entry *        rx_q_wrap;
+
+  struct atm_dev *    atm_dev;
+
+  u32                 last_vc;
+  
+  int                 noof_spare_buffers;
+  u16                 spare_buffers[SPARE_BUFFER_POOL_SIZE];
+
+  u16                 tx_channel_record[TX_CHANS];
+
+  // this is what we follow when we get incoming data
+  u32              txer[MAX_VCS/32];
+  struct atm_vcc * rxer[MAX_VCS];
+
+  // cell rate allocation
+  spinlock_t       rate_lock;
+  unsigned int     rx_avail;
+  unsigned int     tx_avail;
+  
+  // dev stats
+  unsigned long    tx_cell_count;
+  unsigned long    rx_cell_count;
+  unsigned long    hec_error_count;
+  unsigned long    unassigned_cell_count;
+
+  struct pci_dev * pci_dev;
+  struct timer_list housekeeping;
+};
+
+typedef struct hrz_dev hrz_dev;
+
+/* macros for use later */
+
+#define BUF_PTR(cbptr) ((cbptr) - (cell_buf *) 0)
+
+#define INTERESTING_INTERRUPTS \
+  (RX_DATA_AV | RX_DISABLED | TX_BUS_MASTER_COMPLETE | RX_BUS_MASTER_COMPLETE)
+
+// 190 cells by default (192 TX buffers - 2 elbow room, see docs)
+#define TX_AAL5_LIMIT (190*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER) // 9112
+
+// Have enough RX buffers (unless we allow other buffer splits)
+#define RX_AAL5_LIMIT ATM_MAX_AAL5_PDU
+
+/* multi-statement macro protector */
+#define DW(x) do{ x } while(0)
+
+#define HRZ_DEV(atm_dev) ((hrz_dev *) (atm_dev)->dev_data)
+#define HRZ_VCC(atm_vcc) ((hrz_vcc *) (atm_vcc)->dev_data)
+
+/* Turn the LEDs on and off                                                 */
+// The LEDs bits are upside down in that setting the bit in the debug
+// register will turn the appropriate LED off.
+
+#define YELLOW_LED    DEBUG_BIT_0
+#define GREEN_LED     DEBUG_BIT_1
+#define YELLOW_LED_OE DEBUG_BIT_0_OE
+#define GREEN_LED_OE  DEBUG_BIT_1_OE
+
+#define GREEN_LED_OFF(dev)                      \
+  wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) | GREEN_LED)
+#define GREEN_LED_ON(dev)                       \
+  wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) &~ GREEN_LED)
+#define YELLOW_LED_OFF(dev)                     \
+  wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) | YELLOW_LED)
+#define YELLOW_LED_ON(dev)                      \
+  wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) &~ YELLOW_LED)
+
+typedef enum {
+  round_up,
+  round_down,
+  round_nearest
+} rounding;
+
+#endif /* DRIVER_ATM_HORIZON_H */
diff --git a/drivers/atm/idt77105.c b/drivers/atm/idt77105.c
new file mode 100644
index 0000000..b8c260e
--- /dev/null
+++ b/drivers/atm/idt77105.c
@@ -0,0 +1,380 @@
+/* drivers/atm/idt77105.c - IDT77105 (PHY) driver */
+ 
+/* Written 1999 by Greg Banks, NEC Australia <gnb@linuxfan.com>. Based on suni.c */
+
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/capability.h>
+#include <linux/atm_idt77105.h>
+#include <linux/spinlock.h>
+#include <asm/system.h>
+#include <asm/param.h>
+#include <asm/uaccess.h>
+
+#include "idt77105.h"
+
+#undef GENERAL_DEBUG
+
+#ifdef GENERAL_DEBUG
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct idt77105_priv {
+	struct idt77105_stats stats;    /* link diagnostics */
+	struct atm_dev *dev;		/* device back-pointer */
+	struct idt77105_priv *next;
+        int loop_mode;
+        unsigned char old_mcr;          /* storage of MCR reg while signal lost */
+};
+
+static DEFINE_SPINLOCK(idt77105_priv_lock);
+
+#define PRIV(dev) ((struct idt77105_priv *) dev->phy_data)
+
+#define PUT(val,reg) dev->ops->phy_put(dev,val,IDT77105_##reg)
+#define GET(reg) dev->ops->phy_get(dev,IDT77105_##reg)
+
+static void idt77105_stats_timer_func(unsigned long);
+static void idt77105_restart_timer_func(unsigned long);
+
+
+static struct timer_list stats_timer =
+    TIMER_INITIALIZER(idt77105_stats_timer_func, 0, 0);
+static struct timer_list restart_timer =
+    TIMER_INITIALIZER(idt77105_restart_timer_func, 0, 0);
+static int start_timer = 1;
+static struct idt77105_priv *idt77105_all = NULL;
+
+/*
+ * Retrieve the value of one of the IDT77105's counters.
+ * `counter' is one of the IDT77105_CTRSEL_* constants.
+ */
+static u16 get_counter(struct atm_dev *dev, int counter)
+{
+        u16 val;
+        
+        /* write the counter bit into PHY register 6 */
+        PUT(counter, CTRSEL);
+        /* read the low 8 bits from register 4 */
+        val = GET(CTRLO);
+        /* read the high 8 bits from register 5 */
+        val |= GET(CTRHI)<<8;
+        
+        return val;
+}
+
+/*
+ * Timer function called every second to gather statistics
+ * from the 77105. This is done because the h/w registers
+ * will overflow if not read at least once per second. The
+ * kernel's stats are much higher precision. Also, having
+ * a separate copy of the stats allows implementation of
+ * an ioctl which gathers the stats *without* zero'ing them.
+ */
+static void idt77105_stats_timer_func(unsigned long dummy)
+{
+	struct idt77105_priv *walk;
+	struct atm_dev *dev;
+	struct idt77105_stats *stats;
+
+        DPRINTK("IDT77105 gathering statistics\n");
+	for (walk = idt77105_all; walk; walk = walk->next) {
+		dev = walk->dev;
+                
+		stats = &walk->stats;
+                stats->symbol_errors += get_counter(dev, IDT77105_CTRSEL_SEC);
+                stats->tx_cells += get_counter(dev, IDT77105_CTRSEL_TCC);
+                stats->rx_cells += get_counter(dev, IDT77105_CTRSEL_RCC);
+                stats->rx_hec_errors += get_counter(dev, IDT77105_CTRSEL_RHEC);
+	}
+        if (!start_timer) mod_timer(&stats_timer,jiffies+IDT77105_STATS_TIMER_PERIOD);
+}
+
+
+/*
+ * A separate timer func which handles restarting PHY chips which
+ * have had the cable re-inserted after being pulled out. This is
+ * done by polling the Good Signal Bit in the Interrupt Status
+ * register every 5 seconds. The other technique (checking Good
+ * Signal Bit in the interrupt handler) cannot be used because PHY
+ * interrupts need to be disabled when the cable is pulled out
+ * to avoid lots of spurious cell error interrupts.
+ */
+static void idt77105_restart_timer_func(unsigned long dummy)
+{
+	struct idt77105_priv *walk;
+	struct atm_dev *dev;
+        unsigned char istat;
+
+        DPRINTK("IDT77105 checking for cable re-insertion\n");
+	for (walk = idt77105_all; walk; walk = walk->next) {
+		dev = walk->dev;
+                
+                if (dev->signal != ATM_PHY_SIG_LOST)
+                    continue;
+                    
+                istat = GET(ISTAT); /* side effect: clears all interrupt status bits */
+                if (istat & IDT77105_ISTAT_GOODSIG) {
+                    /* Found signal again */
+                    dev->signal = ATM_PHY_SIG_FOUND;
+	            printk(KERN_NOTICE "%s(itf %d): signal detected again\n",
+                        dev->type,dev->number);
+                    /* flush the receive FIFO */
+                    PUT( GET(DIAG) | IDT77105_DIAG_RFLUSH, DIAG);
+                    /* re-enable interrupts */
+	            PUT( walk->old_mcr ,MCR);
+                }
+	}
+        if (!start_timer) mod_timer(&restart_timer,jiffies+IDT77105_RESTART_TIMER_PERIOD);
+}
+
+
+static int fetch_stats(struct atm_dev *dev,struct idt77105_stats __user *arg,int zero)
+{
+	unsigned long flags;
+	struct idt77105_stats stats;
+
+	spin_lock_irqsave(&idt77105_priv_lock, flags);
+	memcpy(&stats, &PRIV(dev)->stats, sizeof(struct idt77105_stats));
+	if (zero)
+		memset(&PRIV(dev)->stats, 0, sizeof(struct idt77105_stats));
+	spin_unlock_irqrestore(&idt77105_priv_lock, flags);
+	if (arg == NULL)
+		return 0;
+	return copy_to_user(arg, &PRIV(dev)->stats,
+		    sizeof(struct idt77105_stats)) ? -EFAULT : 0;
+}
+
+
+static int set_loopback(struct atm_dev *dev,int mode)
+{
+	int diag;
+
+	diag = GET(DIAG) & ~IDT77105_DIAG_LCMASK;
+	switch (mode) {
+		case ATM_LM_NONE:
+			break;
+		case ATM_LM_LOC_ATM:
+			diag |= IDT77105_DIAG_LC_PHY_LOOPBACK;
+			break;
+		case ATM_LM_RMT_ATM:
+			diag |= IDT77105_DIAG_LC_LINE_LOOPBACK;
+			break;
+		default:
+			return -EINVAL;
+	}
+	PUT(diag,DIAG);
+	printk(KERN_NOTICE "%s(%d) Loopback mode is: %s\n", dev->type,
+	    dev->number,
+	    (mode == ATM_LM_NONE ? "NONE" : 
+	      (mode == ATM_LM_LOC_ATM ? "DIAG (local)" :
+		(mode == IDT77105_DIAG_LC_LINE_LOOPBACK ? "LOOP (remote)" :
+		  "unknown")))
+		    );
+	PRIV(dev)->loop_mode = mode;
+	return 0;
+}
+
+
+static int idt77105_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+        printk(KERN_NOTICE "%s(%d) idt77105_ioctl() called\n",dev->type,dev->number);
+	switch (cmd) {
+		case IDT77105_GETSTATZ:
+			if (!capable(CAP_NET_ADMIN)) return -EPERM;
+			/* fall through */
+		case IDT77105_GETSTAT:
+			return fetch_stats(dev, arg, cmd == IDT77105_GETSTATZ);
+		case ATM_SETLOOP:
+			return set_loopback(dev,(int)(unsigned long) arg);
+		case ATM_GETLOOP:
+			return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ?
+			    -EFAULT : 0;
+		case ATM_QUERYLOOP:
+			return put_user(ATM_LM_LOC_ATM | ATM_LM_RMT_ATM,
+			    (int __user *) arg) ? -EFAULT : 0;
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+
+
+static void idt77105_int(struct atm_dev *dev)
+{
+        unsigned char istat;
+        
+        istat = GET(ISTAT); /* side effect: clears all interrupt status bits */
+     
+        DPRINTK("IDT77105 generated an interrupt, istat=%02x\n", (unsigned)istat);
+                
+        if (istat & IDT77105_ISTAT_RSCC) {
+            /* Rx Signal Condition Change - line went up or down */
+            if (istat & IDT77105_ISTAT_GOODSIG) {   /* signal detected again */
+                /* This should not happen (restart timer does it) but JIC */
+                dev->signal = ATM_PHY_SIG_FOUND;
+            } else {    /* signal lost */
+                /*
+                 * Disable interrupts and stop all transmission and
+                 * reception - the restart timer will restore these.
+                 */
+                PRIV(dev)->old_mcr = GET(MCR);
+	        PUT(
+                    (PRIV(dev)->old_mcr|
+                    IDT77105_MCR_DREC|
+                    IDT77105_MCR_DRIC|
+                    IDT77105_MCR_HALTTX
+                    ) & ~IDT77105_MCR_EIP, MCR);
+                dev->signal = ATM_PHY_SIG_LOST;
+	        printk(KERN_NOTICE "%s(itf %d): signal lost\n",
+                    dev->type,dev->number);
+            }
+        }
+        
+        if (istat & IDT77105_ISTAT_RFO) {
+            /* Rx FIFO Overrun -- perform a FIFO flush */
+            PUT( GET(DIAG) | IDT77105_DIAG_RFLUSH, DIAG);
+	    printk(KERN_NOTICE "%s(itf %d): receive FIFO overrun\n",
+                dev->type,dev->number);
+        }
+#ifdef GENERAL_DEBUG
+        if (istat & (IDT77105_ISTAT_HECERR | IDT77105_ISTAT_SCR |
+                     IDT77105_ISTAT_RSE)) {
+            /* normally don't care - just report in stats */
+	    printk(KERN_NOTICE "%s(itf %d): received cell with error\n",
+                dev->type,dev->number);
+        }
+#endif
+}
+
+
+static int idt77105_start(struct atm_dev *dev)
+{
+	unsigned long flags;
+
+	if (!(dev->dev_data = kmalloc(sizeof(struct idt77105_priv),GFP_KERNEL)))
+		return -ENOMEM;
+	PRIV(dev)->dev = dev;
+	spin_lock_irqsave(&idt77105_priv_lock, flags);
+	PRIV(dev)->next = idt77105_all;
+	idt77105_all = PRIV(dev);
+	spin_unlock_irqrestore(&idt77105_priv_lock, flags);
+	memset(&PRIV(dev)->stats,0,sizeof(struct idt77105_stats));
+        
+        /* initialise dev->signal from Good Signal Bit */
+        dev->signal = GET(ISTAT) & IDT77105_ISTAT_GOODSIG ? ATM_PHY_SIG_FOUND :
+	  ATM_PHY_SIG_LOST;
+	if (dev->signal == ATM_PHY_SIG_LOST)
+		printk(KERN_WARNING "%s(itf %d): no signal\n",dev->type,
+		    dev->number);
+
+        /* initialise loop mode from hardware */
+        switch ( GET(DIAG) & IDT77105_DIAG_LCMASK ) {
+        case IDT77105_DIAG_LC_NORMAL:
+            PRIV(dev)->loop_mode = ATM_LM_NONE;
+            break;
+        case IDT77105_DIAG_LC_PHY_LOOPBACK:
+            PRIV(dev)->loop_mode = ATM_LM_LOC_ATM;
+            break;
+        case IDT77105_DIAG_LC_LINE_LOOPBACK:
+            PRIV(dev)->loop_mode = ATM_LM_RMT_ATM;
+            break;
+        }
+        
+        /* enable interrupts, e.g. on loss of signal */
+        PRIV(dev)->old_mcr = GET(MCR);
+        if (dev->signal == ATM_PHY_SIG_FOUND) {
+            PRIV(dev)->old_mcr |= IDT77105_MCR_EIP;
+	    PUT(PRIV(dev)->old_mcr, MCR);
+        }
+
+                    
+	idt77105_stats_timer_func(0); /* clear 77105 counters */
+	(void) fetch_stats(dev,NULL,1); /* clear kernel counters */
+        
+	spin_lock_irqsave(&idt77105_priv_lock, flags);
+	if (start_timer) {
+		start_timer = 0;
+                
+		init_timer(&stats_timer);
+		stats_timer.expires = jiffies+IDT77105_STATS_TIMER_PERIOD;
+		stats_timer.function = idt77105_stats_timer_func;
+		add_timer(&stats_timer);
+                
+		init_timer(&restart_timer);
+		restart_timer.expires = jiffies+IDT77105_RESTART_TIMER_PERIOD;
+		restart_timer.function = idt77105_restart_timer_func;
+		add_timer(&restart_timer);
+	}
+	spin_unlock_irqrestore(&idt77105_priv_lock, flags);
+	return 0;
+}
+
+
+static int idt77105_stop(struct atm_dev *dev)
+{
+	struct idt77105_priv *walk, *prev;
+
+        DPRINTK("%s(itf %d): stopping IDT77105\n",dev->type,dev->number);
+        
+        /* disable interrupts */
+	PUT( GET(MCR) & ~IDT77105_MCR_EIP, MCR );
+        
+        /* detach private struct from atm_dev & free */
+	for (prev = NULL, walk = idt77105_all ;
+             walk != NULL;
+             prev = walk, walk = walk->next) {
+            if (walk->dev == dev) {
+                if (prev != NULL)
+                    prev->next = walk->next;
+                else
+                    idt77105_all = walk->next;
+	        dev->phy = NULL;
+                dev->dev_data = NULL;
+                kfree(walk);
+                break;
+            }
+        }
+
+	return 0;
+}
+
+
+static const struct atmphy_ops idt77105_ops = {
+	.start = 	idt77105_start,
+	.ioctl =	idt77105_ioctl,
+	.interrupt =	idt77105_int,
+	.stop =		idt77105_stop,
+};
+
+
+int idt77105_init(struct atm_dev *dev)
+{
+	dev->phy = &idt77105_ops;
+	return 0;
+}
+
+EXPORT_SYMBOL(idt77105_init);
+
+static void __exit idt77105_exit(void)
+{
+        /* turn off timers */
+        del_timer(&stats_timer);
+        del_timer(&restart_timer);
+}
+
+module_exit(idt77105_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/atm/idt77105.h b/drivers/atm/idt77105.h
new file mode 100644
index 0000000..8ba8218
--- /dev/null
+++ b/drivers/atm/idt77105.h
@@ -0,0 +1,91 @@
+/* drivers/atm/idt77105.h - IDT77105 (PHY) declarations */
+ 
+/* Written 1999 by Greg Banks, NEC Australia <gnb@linuxfan.com>. Based on suni.h */
+ 
+
+#ifndef DRIVER_ATM_IDT77105_H
+#define DRIVER_ATM_IDT77105_H
+
+#include <linux/atmdev.h>
+#include <linux/atmioc.h>
+
+
+/* IDT77105 registers */
+
+#define IDT77105_MCR		0x0	/* Master Control Register */
+#define IDT77105_ISTAT	        0x1	/* Interrupt Status */
+#define IDT77105_DIAG   	0x2	/* Diagnostic Control */
+#define IDT77105_LEDHEC		0x3	/* LED Driver & HEC Status/Control */
+#define IDT77105_CTRLO		0x4	/* Low Byte Counter Register */
+#define IDT77105_CTRHI		0x5	/* High Byte Counter Register */
+#define IDT77105_CTRSEL		0x6	/* Counter Register Read Select */
+
+/* IDT77105 register values */
+
+/* MCR */
+#define IDT77105_MCR_UPLO	0x80	/* R/W, User Prog'le Output Latch */
+#define IDT77105_MCR_DREC	0x40	/* R/W, Discard Receive Error Cells */
+#define IDT77105_MCR_ECEIO	0x20	/* R/W, Enable Cell Error Interrupts
+                                         * Only */
+#define IDT77105_MCR_TDPC	0x10	/* R/W, Transmit Data Parity Check */
+#define IDT77105_MCR_DRIC	0x08	/* R/W, Discard Received Idle Cells */
+#define IDT77105_MCR_HALTTX	0x04	/* R/W, Halt Tx */
+#define IDT77105_MCR_UMODE	0x02	/* R/W, Utopia (cell/byte) Mode */
+#define IDT77105_MCR_EIP	0x01	/* R/W, Enable Interrupt Pin */
+
+/* ISTAT */
+#define IDT77105_ISTAT_GOODSIG	0x40	/* R, Good Signal Bit */
+#define IDT77105_ISTAT_HECERR	0x20	/* sticky, HEC Error*/
+#define IDT77105_ISTAT_SCR	0x10	/* sticky, Short Cell Received */
+#define IDT77105_ISTAT_TPE	0x08	/* sticky, Transmit Parity Error */
+#define IDT77105_ISTAT_RSCC	0x04	/* sticky, Rx Signal Condition Change */
+#define IDT77105_ISTAT_RSE	0x02	/* sticky, Rx Symbol Error */
+#define IDT77105_ISTAT_RFO	0x01	/* sticky, Rx FIFO Overrun */
+
+/* DIAG */
+#define IDT77105_DIAG_FTD	0x80	/* R/W, Force TxClav deassert */
+#define IDT77105_DIAG_ROS	0x40	/* R/W, RxClav operation select */
+#define IDT77105_DIAG_MPCS	0x20	/* R/W, Multi-PHY config'n select */
+#define IDT77105_DIAG_RFLUSH	0x10	/* R/W, clear receive FIFO */
+#define IDT77105_DIAG_ITPE	0x08	/* R/W, Insert Tx payload error */
+#define IDT77105_DIAG_ITHE	0x04	/* R/W, Insert Tx HEC error */
+#define IDT77105_DIAG_UMODE	0x02	/* R/W, Utopia (cell/byte) Mode */
+#define IDT77105_DIAG_LCMASK	0x03	/* R/W, Loopback Control */
+
+#define IDT77105_DIAG_LC_NORMAL         0x00	/* Receive from network */
+#define IDT77105_DIAG_LC_PHY_LOOPBACK	0x02
+#define IDT77105_DIAG_LC_LINE_LOOPBACK	0x03
+
+/* LEDHEC */
+#define IDT77105_LEDHEC_DRHC	0x40	/* R/W, Disable Rx HEC check */
+#define IDT77105_LEDHEC_DTHC	0x20	/* R/W, Disable Tx HEC calculation */
+#define IDT77105_LEDHEC_RPWMASK	0x18	/* R/W, RxRef pulse width select */
+#define IDT77105_LEDHEC_TFS	0x04	/* R, Tx FIFO Status (1=empty) */
+#define IDT77105_LEDHEC_TLS	0x02	/* R, Tx LED Status (1=lit) */
+#define IDT77105_LEDHEC_RLS	0x01	/* R, Rx LED Status (1=lit) */
+
+#define IDT77105_LEDHEC_RPW_1	0x00	/* RxRef active for 1 RxClk cycle */
+#define IDT77105_LEDHEC_RPW_2	0x08	/* RxRef active for 2 RxClk cycle */
+#define IDT77105_LEDHEC_RPW_4	0x10	/* RxRef active for 4 RxClk cycle */
+#define IDT77105_LEDHEC_RPW_8	0x18	/* RxRef active for 8 RxClk cycle */
+
+/* CTRSEL */
+#define IDT77105_CTRSEL_SEC	0x08	/* W, Symbol Error Counter */
+#define IDT77105_CTRSEL_TCC	0x04	/* W, Tx Cell Counter */
+#define IDT77105_CTRSEL_RCC	0x02	/* W, Rx Cell Counter */
+#define IDT77105_CTRSEL_RHEC	0x01	/* W, Rx HEC Error Counter */
+
+#ifdef __KERNEL__
+int idt77105_init(struct atm_dev *dev) __init;
+#endif
+
+/*
+ * Tunable parameters
+ */
+ 
+/* Time between samples of the hardware cell counters. Should be <= 1 sec */
+#define IDT77105_STATS_TIMER_PERIOD     (HZ) 
+/* Time between checks to see if the signal has been found again */
+#define IDT77105_RESTART_TIMER_PERIOD   (5 * HZ)
+
+#endif
diff --git a/drivers/atm/idt77252.c b/drivers/atm/idt77252.c
new file mode 100644
index 0000000..baaf1a3
--- /dev/null
+++ b/drivers/atm/idt77252.c
@@ -0,0 +1,3882 @@
+/******************************************************************* 
+ * ident "$Id: idt77252.c,v 1.2 2001/11/11 08:13:54 ecd Exp $"
+ *
+ * $Author: ecd $
+ * $Date: 2001/11/11 08:13:54 $
+ *
+ * Copyright (c) 2000 ATecoM GmbH 
+ *
+ * The author may be reached at ecd@atecom.com.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR   IMPLIED
+ * WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ * NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT,  INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ * USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * 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.
+ *
+ *******************************************************************/
+static char const rcsid[] =
+"$Id: idt77252.c,v 1.2 2001/11/11 08:13:54 ecd Exp $";
+
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/pci.h>
+#include <linux/skbuff.h>
+#include <linux/kernel.h>
+#include <linux/vmalloc.h>
+#include <linux/netdevice.h>
+#include <linux/atmdev.h>
+#include <linux/atm.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/wait.h>
+#include <asm/semaphore.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <asm/byteorder.h>
+
+#ifdef CONFIG_ATM_IDT77252_USE_SUNI
+#include "suni.h"
+#endif /* CONFIG_ATM_IDT77252_USE_SUNI */
+
+
+#include "idt77252.h"
+#include "idt77252_tables.h"
+
+static unsigned int vpibits = 1;
+
+
+#define CONFIG_ATM_IDT77252_SEND_IDLE 1
+
+
+/*
+ * Debug HACKs.
+ */
+#define DEBUG_MODULE 1
+#undef HAVE_EEPROM	/* does not work, yet. */
+
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+static unsigned long debug = DBG_GENERAL;
+#endif
+
+
+#define SAR_RX_DELAY	(SAR_CFG_RXINT_NODELAY)
+
+
+/*
+ * SCQ Handling.
+ */
+static struct scq_info *alloc_scq(struct idt77252_dev *, int);
+static void free_scq(struct idt77252_dev *, struct scq_info *);
+static int queue_skb(struct idt77252_dev *, struct vc_map *,
+		     struct sk_buff *, int oam);
+static void drain_scq(struct idt77252_dev *, struct vc_map *);
+static unsigned long get_free_scd(struct idt77252_dev *, struct vc_map *);
+static void fill_scd(struct idt77252_dev *, struct scq_info *, int);
+
+/*
+ * FBQ Handling.
+ */
+static int push_rx_skb(struct idt77252_dev *,
+		       struct sk_buff *, int queue);
+static void recycle_rx_skb(struct idt77252_dev *, struct sk_buff *);
+static void flush_rx_pool(struct idt77252_dev *, struct rx_pool *);
+static void recycle_rx_pool_skb(struct idt77252_dev *,
+				struct rx_pool *);
+static void add_rx_skb(struct idt77252_dev *, int queue,
+		       unsigned int size, unsigned int count);
+
+/*
+ * RSQ Handling.
+ */
+static int init_rsq(struct idt77252_dev *);
+static void deinit_rsq(struct idt77252_dev *);
+static void idt77252_rx(struct idt77252_dev *);
+
+/*
+ * TSQ handling.
+ */
+static int init_tsq(struct idt77252_dev *);
+static void deinit_tsq(struct idt77252_dev *);
+static void idt77252_tx(struct idt77252_dev *);
+
+
+/*
+ * ATM Interface.
+ */
+static void idt77252_dev_close(struct atm_dev *dev);
+static int idt77252_open(struct atm_vcc *vcc);
+static void idt77252_close(struct atm_vcc *vcc);
+static int idt77252_send(struct atm_vcc *vcc, struct sk_buff *skb);
+static int idt77252_send_oam(struct atm_vcc *vcc, void *cell,
+			     int flags);
+static void idt77252_phy_put(struct atm_dev *dev, unsigned char value,
+			     unsigned long addr);
+static unsigned char idt77252_phy_get(struct atm_dev *dev, unsigned long addr);
+static int idt77252_change_qos(struct atm_vcc *vcc, struct atm_qos *qos,
+			       int flags);
+static int idt77252_proc_read(struct atm_dev *dev, loff_t * pos,
+			      char *page);
+static void idt77252_softint(void *dev_id);
+
+
+static struct atmdev_ops idt77252_ops =
+{
+	.dev_close	= idt77252_dev_close,
+	.open		= idt77252_open,
+	.close		= idt77252_close,
+	.send		= idt77252_send,
+	.send_oam	= idt77252_send_oam,
+	.phy_put	= idt77252_phy_put,
+	.phy_get	= idt77252_phy_get,
+	.change_qos	= idt77252_change_qos,
+	.proc_read	= idt77252_proc_read,
+	.owner		= THIS_MODULE
+};
+
+static struct idt77252_dev *idt77252_chain = NULL;
+static unsigned int idt77252_sram_write_errors = 0;
+
+/*****************************************************************************/
+/*                                                                           */
+/* I/O and Utility Bus                                                       */
+/*                                                                           */
+/*****************************************************************************/
+
+static void
+waitfor_idle(struct idt77252_dev *card)
+{
+	u32 stat;
+
+	stat = readl(SAR_REG_STAT);
+	while (stat & SAR_STAT_CMDBZ)
+		stat = readl(SAR_REG_STAT);
+}
+
+static u32
+read_sram(struct idt77252_dev *card, unsigned long addr)
+{
+	unsigned long flags;
+	u32 value;
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	writel(SAR_CMD_READ_SRAM | (addr << 2), SAR_REG_CMD);
+	waitfor_idle(card);
+	value = readl(SAR_REG_DR0);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+	return value;
+}
+
+static void
+write_sram(struct idt77252_dev *card, unsigned long addr, u32 value)
+{
+	unsigned long flags;
+
+	if ((idt77252_sram_write_errors == 0) &&
+	    (((addr > card->tst[0] + card->tst_size - 2) &&
+	      (addr < card->tst[0] + card->tst_size)) ||
+	     ((addr > card->tst[1] + card->tst_size - 2) &&
+	      (addr < card->tst[1] + card->tst_size)))) {
+		printk("%s: ERROR: TST JMP section at %08lx written: %08x\n",
+		       card->name, addr, value);
+	}
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	writel(value, SAR_REG_DR0);
+	writel(SAR_CMD_WRITE_SRAM | (addr << 2), SAR_REG_CMD);
+	waitfor_idle(card);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+}
+
+static u8
+read_utility(void *dev, unsigned long ubus_addr)
+{
+	struct idt77252_dev *card = dev;
+	unsigned long flags;
+	u8 value;
+
+	if (!card) {
+		printk("Error: No such device.\n");
+		return -1;
+	}
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	writel(SAR_CMD_READ_UTILITY + ubus_addr, SAR_REG_CMD);
+	waitfor_idle(card);
+	value = readl(SAR_REG_DR0);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+	return value;
+}
+
+static void
+write_utility(void *dev, unsigned long ubus_addr, u8 value)
+{
+	struct idt77252_dev *card = dev;
+	unsigned long flags;
+
+	if (!card) {
+		printk("Error: No such device.\n");
+		return;
+	}
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	writel((u32) value, SAR_REG_DR0);
+	writel(SAR_CMD_WRITE_UTILITY + ubus_addr, SAR_REG_CMD);
+	waitfor_idle(card);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+}
+
+#ifdef HAVE_EEPROM
+static u32 rdsrtab[] =
+{
+	SAR_GP_EECS | SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO,	/* 1 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO	/* 1 */
+};
+
+static u32 wrentab[] =
+{
+	SAR_GP_EECS | SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO,	/* 1 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO,	/* 1 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK			/* 0 */
+};
+
+static u32 rdtab[] =
+{
+	SAR_GP_EECS | SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO,	/* 1 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO	/* 1 */
+};
+
+static u32 wrtab[] =
+{
+	SAR_GP_EECS | SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	0,
+	SAR_GP_EESCLK,			/* 0 */
+	SAR_GP_EEDO,
+	SAR_GP_EESCLK | SAR_GP_EEDO,	/* 1 */
+	0,
+	SAR_GP_EESCLK			/* 0 */
+};
+
+static u32 clktab[] =
+{
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0,
+	SAR_GP_EESCLK,
+	0
+};
+
+static u32
+idt77252_read_gp(struct idt77252_dev *card)
+{
+	u32 gp;
+
+	gp = readl(SAR_REG_GP);
+#if 0
+	printk("RD: %s\n", gp & SAR_GP_EEDI ? "1" : "0");
+#endif
+	return gp;
+}
+
+static void
+idt77252_write_gp(struct idt77252_dev *card, u32 value)
+{
+	unsigned long flags;
+
+#if 0
+	printk("WR: %s %s %s\n", value & SAR_GP_EECS ? "   " : "/CS",
+	       value & SAR_GP_EESCLK ? "HIGH" : "LOW ",
+	       value & SAR_GP_EEDO   ? "1" : "0");
+#endif
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	waitfor_idle(card);
+	writel(value, SAR_REG_GP);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+}
+
+static u8
+idt77252_eeprom_read_status(struct idt77252_dev *card)
+{
+	u8 byte;
+	u32 gp;
+	int i, j;
+
+	gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO);
+
+	for (i = 0; i < sizeof(rdsrtab)/sizeof(rdsrtab[0]); i++) {
+		idt77252_write_gp(card, gp | rdsrtab[i]);
+		udelay(5);
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	byte = 0;
+	for (i = 0, j = 0; i < 8; i++) {
+		byte <<= 1;
+
+		idt77252_write_gp(card, gp | clktab[j++]);
+		udelay(5);
+
+		byte |= idt77252_read_gp(card) & SAR_GP_EEDI ? 1 : 0;
+
+		idt77252_write_gp(card, gp | clktab[j++]);
+		udelay(5);
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	return byte;
+}
+
+static u8
+idt77252_eeprom_read_byte(struct idt77252_dev *card, u8 offset)
+{
+	u8 byte;
+	u32 gp;
+	int i, j;
+
+	gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO);
+
+	for (i = 0; i < sizeof(rdtab)/sizeof(rdtab[0]); i++) {
+		idt77252_write_gp(card, gp | rdtab[i]);
+		udelay(5);
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	for (i = 0, j = 0; i < 8; i++) {
+		idt77252_write_gp(card, gp | clktab[j++] |
+					(offset & 1 ? SAR_GP_EEDO : 0));
+		udelay(5);
+
+		idt77252_write_gp(card, gp | clktab[j++] |
+					(offset & 1 ? SAR_GP_EEDO : 0));
+		udelay(5);
+
+		offset >>= 1;
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	byte = 0;
+	for (i = 0, j = 0; i < 8; i++) {
+		byte <<= 1;
+
+		idt77252_write_gp(card, gp | clktab[j++]);
+		udelay(5);
+
+		byte |= idt77252_read_gp(card) & SAR_GP_EEDI ? 1 : 0;
+
+		idt77252_write_gp(card, gp | clktab[j++]);
+		udelay(5);
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	return byte;
+}
+
+static void
+idt77252_eeprom_write_byte(struct idt77252_dev *card, u8 offset, u8 data)
+{
+	u32 gp;
+	int i, j;
+
+	gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO);
+
+	for (i = 0; i < sizeof(wrentab)/sizeof(wrentab[0]); i++) {
+		idt77252_write_gp(card, gp | wrentab[i]);
+		udelay(5);
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	for (i = 0; i < sizeof(wrtab)/sizeof(wrtab[0]); i++) {
+		idt77252_write_gp(card, gp | wrtab[i]);
+		udelay(5);
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	for (i = 0, j = 0; i < 8; i++) {
+		idt77252_write_gp(card, gp | clktab[j++] |
+					(offset & 1 ? SAR_GP_EEDO : 0));
+		udelay(5);
+
+		idt77252_write_gp(card, gp | clktab[j++] |
+					(offset & 1 ? SAR_GP_EEDO : 0));
+		udelay(5);
+
+		offset >>= 1;
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+
+	for (i = 0, j = 0; i < 8; i++) {
+		idt77252_write_gp(card, gp | clktab[j++] |
+					(data & 1 ? SAR_GP_EEDO : 0));
+		udelay(5);
+
+		idt77252_write_gp(card, gp | clktab[j++] |
+					(data & 1 ? SAR_GP_EEDO : 0));
+		udelay(5);
+
+		data >>= 1;
+	}
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+}
+
+static void
+idt77252_eeprom_init(struct idt77252_dev *card)
+{
+	u32 gp;
+
+	gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO);
+
+	idt77252_write_gp(card, gp | SAR_GP_EECS | SAR_GP_EESCLK);
+	udelay(5);
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+	idt77252_write_gp(card, gp | SAR_GP_EECS | SAR_GP_EESCLK);
+	udelay(5);
+	idt77252_write_gp(card, gp | SAR_GP_EECS);
+	udelay(5);
+}
+#endif /* HAVE_EEPROM */
+
+
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+static void
+dump_tct(struct idt77252_dev *card, int index)
+{
+	unsigned long tct;
+	int i;
+
+	tct = (unsigned long) (card->tct_base + index * SAR_SRAM_TCT_SIZE);
+
+	printk("%s: TCT %x:", card->name, index);
+	for (i = 0; i < 8; i++) {
+		printk(" %08x", read_sram(card, tct + i));
+	}
+	printk("\n");
+}
+
+static void
+idt77252_tx_dump(struct idt77252_dev *card)
+{
+	struct atm_vcc *vcc;
+	struct vc_map *vc;
+	int i;
+
+	printk("%s\n", __FUNCTION__);
+	for (i = 0; i < card->tct_size; i++) {
+		vc = card->vcs[i];
+		if (!vc)
+			continue;
+
+		vcc = NULL;
+		if (vc->rx_vcc)
+			vcc = vc->rx_vcc;
+		else if (vc->tx_vcc)
+			vcc = vc->tx_vcc;
+
+		if (!vcc)
+			continue;
+
+		printk("%s: Connection %d:\n", card->name, vc->index);
+		dump_tct(card, vc->index);
+	}
+}
+#endif
+
+
+/*****************************************************************************/
+/*                                                                           */
+/* SCQ Handling                                                              */
+/*                                                                           */
+/*****************************************************************************/
+
+static int
+sb_pool_add(struct idt77252_dev *card, struct sk_buff *skb, int queue)
+{
+	struct sb_pool *pool = &card->sbpool[queue];
+	int index;
+
+	index = pool->index;
+	while (pool->skb[index]) {
+		index = (index + 1) & FBQ_MASK;
+		if (index == pool->index)
+			return -ENOBUFS;
+	}
+
+	pool->skb[index] = skb;
+	IDT77252_PRV_POOL(skb) = POOL_HANDLE(queue, index);
+
+	pool->index = (index + 1) & FBQ_MASK;
+	return 0;
+}
+
+static void
+sb_pool_remove(struct idt77252_dev *card, struct sk_buff *skb)
+{
+	unsigned int queue, index;
+	u32 handle;
+
+	handle = IDT77252_PRV_POOL(skb);
+
+	queue = POOL_QUEUE(handle);
+	if (queue > 3)
+		return;
+
+	index = POOL_INDEX(handle);
+	if (index > FBQ_SIZE - 1)
+		return;
+
+	card->sbpool[queue].skb[index] = NULL;
+}
+
+static struct sk_buff *
+sb_pool_skb(struct idt77252_dev *card, u32 handle)
+{
+	unsigned int queue, index;
+
+	queue = POOL_QUEUE(handle);
+	if (queue > 3)
+		return NULL;
+
+	index = POOL_INDEX(handle);
+	if (index > FBQ_SIZE - 1)
+		return NULL;
+
+	return card->sbpool[queue].skb[index];
+}
+
+static struct scq_info *
+alloc_scq(struct idt77252_dev *card, int class)
+{
+	struct scq_info *scq;
+
+	scq = (struct scq_info *) kmalloc(sizeof(struct scq_info), GFP_KERNEL);
+	if (!scq)
+		return NULL;
+	memset(scq, 0, sizeof(struct scq_info));
+
+	scq->base = pci_alloc_consistent(card->pcidev, SCQ_SIZE,
+					 &scq->paddr);
+	if (scq->base == NULL) {
+		kfree(scq);
+		return NULL;
+	}
+	memset(scq->base, 0, SCQ_SIZE);
+
+	scq->next = scq->base;
+	scq->last = scq->base + (SCQ_ENTRIES - 1);
+	atomic_set(&scq->used, 0);
+
+	spin_lock_init(&scq->lock);
+	spin_lock_init(&scq->skblock);
+
+	skb_queue_head_init(&scq->transmit);
+	skb_queue_head_init(&scq->pending);
+
+	TXPRINTK("idt77252: SCQ: base 0x%p, next 0x%p, last 0x%p, paddr %08llx\n",
+		 scq->base, scq->next, scq->last, (unsigned long long)scq->paddr);
+
+	return scq;
+}
+
+static void
+free_scq(struct idt77252_dev *card, struct scq_info *scq)
+{
+	struct sk_buff *skb;
+	struct atm_vcc *vcc;
+
+	pci_free_consistent(card->pcidev, SCQ_SIZE,
+			    scq->base, scq->paddr);
+
+	while ((skb = skb_dequeue(&scq->transmit))) {
+		pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+				 skb->len, PCI_DMA_TODEVICE);
+
+		vcc = ATM_SKB(skb)->vcc;
+		if (vcc->pop)
+			vcc->pop(vcc, skb);
+		else
+			dev_kfree_skb(skb);
+	}
+
+	while ((skb = skb_dequeue(&scq->pending))) {
+		pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+				 skb->len, PCI_DMA_TODEVICE);
+
+		vcc = ATM_SKB(skb)->vcc;
+		if (vcc->pop)
+			vcc->pop(vcc, skb);
+		else
+			dev_kfree_skb(skb);
+	}
+
+	kfree(scq);
+}
+
+
+static int
+push_on_scq(struct idt77252_dev *card, struct vc_map *vc, struct sk_buff *skb)
+{
+	struct scq_info *scq = vc->scq;
+	unsigned long flags;
+	struct scqe *tbd;
+	int entries;
+
+	TXPRINTK("%s: SCQ: next 0x%p\n", card->name, scq->next);
+
+	atomic_inc(&scq->used);
+	entries = atomic_read(&scq->used);
+	if (entries > (SCQ_ENTRIES - 1)) {
+		atomic_dec(&scq->used);
+		goto out;
+	}
+
+	skb_queue_tail(&scq->transmit, skb);
+
+	spin_lock_irqsave(&vc->lock, flags);
+	if (vc->estimator) {
+		struct atm_vcc *vcc = vc->tx_vcc;
+		struct sock *sk = sk_atm(vcc);
+
+		vc->estimator->cells += (skb->len + 47) / 48;
+		if (atomic_read(&sk->sk_wmem_alloc) >
+		    (sk->sk_sndbuf >> 1)) {
+			u32 cps = vc->estimator->maxcps;
+
+			vc->estimator->cps = cps;
+			vc->estimator->avcps = cps << 5;
+			if (vc->lacr < vc->init_er) {
+				vc->lacr = vc->init_er;
+				writel(TCMDQ_LACR | (vc->lacr << 16) |
+				       vc->index, SAR_REG_TCMDQ);
+			}
+		}
+	}
+	spin_unlock_irqrestore(&vc->lock, flags);
+
+	tbd = &IDT77252_PRV_TBD(skb);
+
+	spin_lock_irqsave(&scq->lock, flags);
+	scq->next->word_1 = cpu_to_le32(tbd->word_1 |
+					SAR_TBD_TSIF | SAR_TBD_GTSI);
+	scq->next->word_2 = cpu_to_le32(tbd->word_2);
+	scq->next->word_3 = cpu_to_le32(tbd->word_3);
+	scq->next->word_4 = cpu_to_le32(tbd->word_4);
+
+	if (scq->next == scq->last)
+		scq->next = scq->base;
+	else
+		scq->next++;
+
+	write_sram(card, scq->scd,
+		   scq->paddr +
+		   (u32)((unsigned long)scq->next - (unsigned long)scq->base));
+	spin_unlock_irqrestore(&scq->lock, flags);
+
+	scq->trans_start = jiffies;
+
+	if (test_and_clear_bit(VCF_IDLE, &vc->flags)) {
+		writel(TCMDQ_START_LACR | (vc->lacr << 16) | vc->index,
+		       SAR_REG_TCMDQ);
+	}
+
+	TXPRINTK("%d entries in SCQ used (push).\n", atomic_read(&scq->used));
+
+	XPRINTK("%s: SCQ (after push %2d) head = 0x%x, next = 0x%p.\n",
+		card->name, atomic_read(&scq->used),
+		read_sram(card, scq->scd + 1), scq->next);
+
+	return 0;
+
+out:
+	if (jiffies - scq->trans_start > HZ) {
+		printk("%s: Error pushing TBD for %d.%d\n",
+		       card->name, vc->tx_vcc->vpi, vc->tx_vcc->vci);
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+		idt77252_tx_dump(card);
+#endif
+		scq->trans_start = jiffies;
+	}
+
+	return -ENOBUFS;
+}
+
+
+static void
+drain_scq(struct idt77252_dev *card, struct vc_map *vc)
+{
+	struct scq_info *scq = vc->scq;
+	struct sk_buff *skb;
+	struct atm_vcc *vcc;
+
+	TXPRINTK("%s: SCQ (before drain %2d) next = 0x%p.\n",
+		 card->name, atomic_read(&scq->used), scq->next);
+
+	skb = skb_dequeue(&scq->transmit);
+	if (skb) {
+		TXPRINTK("%s: freeing skb at %p.\n", card->name, skb);
+
+		pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+				 skb->len, PCI_DMA_TODEVICE);
+
+		vcc = ATM_SKB(skb)->vcc;
+
+		if (vcc->pop)
+			vcc->pop(vcc, skb);
+		else
+			dev_kfree_skb(skb);
+
+		atomic_inc(&vcc->stats->tx);
+	}
+
+	atomic_dec(&scq->used);
+
+	spin_lock(&scq->skblock);
+	while ((skb = skb_dequeue(&scq->pending))) {
+		if (push_on_scq(card, vc, skb)) {
+			skb_queue_head(&vc->scq->pending, skb);
+			break;
+		}
+	}
+	spin_unlock(&scq->skblock);
+}
+
+static int
+queue_skb(struct idt77252_dev *card, struct vc_map *vc,
+	  struct sk_buff *skb, int oam)
+{
+	struct atm_vcc *vcc;
+	struct scqe *tbd;
+	unsigned long flags;
+	int error;
+	int aal;
+
+	if (skb->len == 0) {
+		printk("%s: invalid skb->len (%d)\n", card->name, skb->len);
+		return -EINVAL;
+	}
+
+	TXPRINTK("%s: Sending %d bytes of data.\n",
+		 card->name, skb->len);
+
+	tbd = &IDT77252_PRV_TBD(skb);
+	vcc = ATM_SKB(skb)->vcc;
+
+	IDT77252_PRV_PADDR(skb) = pci_map_single(card->pcidev, skb->data,
+						 skb->len, PCI_DMA_TODEVICE);
+
+	error = -EINVAL;
+
+	if (oam) {
+		if (skb->len != 52)
+			goto errout;
+
+		tbd->word_1 = SAR_TBD_OAM | ATM_CELL_PAYLOAD | SAR_TBD_EPDU;
+		tbd->word_2 = IDT77252_PRV_PADDR(skb) + 4;
+		tbd->word_3 = 0x00000000;
+		tbd->word_4 = (skb->data[0] << 24) | (skb->data[1] << 16) |
+			      (skb->data[2] <<  8) | (skb->data[3] <<  0);
+
+		if (test_bit(VCF_RSV, &vc->flags))
+			vc = card->vcs[0];
+
+		goto done;
+	}
+
+	if (test_bit(VCF_RSV, &vc->flags)) {
+		printk("%s: Trying to transmit on reserved VC\n", card->name);
+		goto errout;
+	}
+
+	aal = vcc->qos.aal;
+
+	switch (aal) {
+	case ATM_AAL0:
+	case ATM_AAL34:
+		if (skb->len > 52)
+			goto errout;
+
+		if (aal == ATM_AAL0)
+			tbd->word_1 = SAR_TBD_EPDU | SAR_TBD_AAL0 |
+				      ATM_CELL_PAYLOAD;
+		else
+			tbd->word_1 = SAR_TBD_EPDU | SAR_TBD_AAL34 |
+				      ATM_CELL_PAYLOAD;
+
+		tbd->word_2 = IDT77252_PRV_PADDR(skb) + 4;
+		tbd->word_3 = 0x00000000;
+		tbd->word_4 = (skb->data[0] << 24) | (skb->data[1] << 16) |
+			      (skb->data[2] <<  8) | (skb->data[3] <<  0);
+		break;
+
+	case ATM_AAL5:
+		tbd->word_1 = SAR_TBD_EPDU | SAR_TBD_AAL5 | skb->len;
+		tbd->word_2 = IDT77252_PRV_PADDR(skb);
+		tbd->word_3 = skb->len;
+		tbd->word_4 = (vcc->vpi << SAR_TBD_VPI_SHIFT) |
+			      (vcc->vci << SAR_TBD_VCI_SHIFT);
+		break;
+
+	case ATM_AAL1:
+	case ATM_AAL2:
+	default:
+		printk("%s: Traffic type not supported.\n", card->name);
+		error = -EPROTONOSUPPORT;
+		goto errout;
+	}
+
+done:
+	spin_lock_irqsave(&vc->scq->skblock, flags);
+	skb_queue_tail(&vc->scq->pending, skb);
+
+	while ((skb = skb_dequeue(&vc->scq->pending))) {
+		if (push_on_scq(card, vc, skb)) {
+			skb_queue_head(&vc->scq->pending, skb);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&vc->scq->skblock, flags);
+
+	return 0;
+
+errout:
+	pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+			 skb->len, PCI_DMA_TODEVICE);
+	return error;
+}
+
+static unsigned long
+get_free_scd(struct idt77252_dev *card, struct vc_map *vc)
+{
+	int i;
+
+	for (i = 0; i < card->scd_size; i++) {
+		if (!card->scd2vc[i]) {
+			card->scd2vc[i] = vc;
+			vc->scd_index = i;
+			return card->scd_base + i * SAR_SRAM_SCD_SIZE;
+		}
+	}
+	return 0;
+}
+
+static void
+fill_scd(struct idt77252_dev *card, struct scq_info *scq, int class)
+{
+	write_sram(card, scq->scd, scq->paddr);
+	write_sram(card, scq->scd + 1, 0x00000000);
+	write_sram(card, scq->scd + 2, 0xffffffff);
+	write_sram(card, scq->scd + 3, 0x00000000);
+}
+
+static void
+clear_scd(struct idt77252_dev *card, struct scq_info *scq, int class)
+{
+	return;
+}
+
+/*****************************************************************************/
+/*                                                                           */
+/* RSQ Handling                                                              */
+/*                                                                           */
+/*****************************************************************************/
+
+static int
+init_rsq(struct idt77252_dev *card)
+{
+	struct rsq_entry *rsqe;
+
+	card->rsq.base = pci_alloc_consistent(card->pcidev, RSQSIZE,
+					      &card->rsq.paddr);
+	if (card->rsq.base == NULL) {
+		printk("%s: can't allocate RSQ.\n", card->name);
+		return -1;
+	}
+	memset(card->rsq.base, 0, RSQSIZE);
+
+	card->rsq.last = card->rsq.base + RSQ_NUM_ENTRIES - 1;
+	card->rsq.next = card->rsq.last;
+	for (rsqe = card->rsq.base; rsqe <= card->rsq.last; rsqe++)
+		rsqe->word_4 = 0;
+
+	writel((unsigned long) card->rsq.last - (unsigned long) card->rsq.base,
+	       SAR_REG_RSQH);
+	writel(card->rsq.paddr, SAR_REG_RSQB);
+
+	IPRINTK("%s: RSQ base at 0x%lx (0x%x).\n", card->name,
+		(unsigned long) card->rsq.base,
+		readl(SAR_REG_RSQB));
+	IPRINTK("%s: RSQ head = 0x%x, base = 0x%x, tail = 0x%x.\n",
+		card->name,
+		readl(SAR_REG_RSQH),
+		readl(SAR_REG_RSQB),
+		readl(SAR_REG_RSQT));
+
+	return 0;
+}
+
+static void
+deinit_rsq(struct idt77252_dev *card)
+{
+	pci_free_consistent(card->pcidev, RSQSIZE,
+			    card->rsq.base, card->rsq.paddr);
+}
+
+static void
+dequeue_rx(struct idt77252_dev *card, struct rsq_entry *rsqe)
+{
+	struct atm_vcc *vcc;
+	struct sk_buff *skb;
+	struct rx_pool *rpp;
+	struct vc_map *vc;
+	u32 header, vpi, vci;
+	u32 stat;
+	int i;
+
+	stat = le32_to_cpu(rsqe->word_4);
+
+	if (stat & SAR_RSQE_IDLE) {
+		RXPRINTK("%s: message about inactive connection.\n",
+			 card->name);
+		return;
+	}
+
+	skb = sb_pool_skb(card, le32_to_cpu(rsqe->word_2));
+	if (skb == NULL) {
+		printk("%s: NULL skb in %s, rsqe: %08x %08x %08x %08x\n",
+		       card->name, __FUNCTION__,
+		       le32_to_cpu(rsqe->word_1), le32_to_cpu(rsqe->word_2),
+		       le32_to_cpu(rsqe->word_3), le32_to_cpu(rsqe->word_4));
+		return;
+	}
+
+	header = le32_to_cpu(rsqe->word_1);
+	vpi = (header >> 16) & 0x00ff;
+	vci = (header >>  0) & 0xffff;
+
+	RXPRINTK("%s: SDU for %d.%d received in buffer 0x%p (data 0x%p).\n",
+		 card->name, vpi, vci, skb, skb->data);
+
+	if ((vpi >= (1 << card->vpibits)) || (vci != (vci & card->vcimask))) {
+		printk("%s: SDU received for out-of-range vc %u.%u\n",
+		       card->name, vpi, vci);
+		recycle_rx_skb(card, skb);
+		return;
+	}
+
+	vc = card->vcs[VPCI2VC(card, vpi, vci)];
+	if (!vc || !test_bit(VCF_RX, &vc->flags)) {
+		printk("%s: SDU received on non RX vc %u.%u\n",
+		       card->name, vpi, vci);
+		recycle_rx_skb(card, skb);
+		return;
+	}
+
+	vcc = vc->rx_vcc;
+
+	pci_dma_sync_single_for_cpu(card->pcidev, IDT77252_PRV_PADDR(skb),
+				    skb->end - skb->data, PCI_DMA_FROMDEVICE);
+
+	if ((vcc->qos.aal == ATM_AAL0) ||
+	    (vcc->qos.aal == ATM_AAL34)) {
+		struct sk_buff *sb;
+		unsigned char *cell;
+		u32 aal0;
+
+		cell = skb->data;
+		for (i = (stat & SAR_RSQE_CELLCNT); i; i--) {
+			if ((sb = dev_alloc_skb(64)) == NULL) {
+				printk("%s: Can't allocate buffers for aal0.\n",
+				       card->name);
+				atomic_add(i, &vcc->stats->rx_drop);
+				break;
+			}
+			if (!atm_charge(vcc, sb->truesize)) {
+				RXPRINTK("%s: atm_charge() dropped aal0 packets.\n",
+					 card->name);
+				atomic_add(i - 1, &vcc->stats->rx_drop);
+				dev_kfree_skb(sb);
+				break;
+			}
+			aal0 = (vpi << ATM_HDR_VPI_SHIFT) |
+			       (vci << ATM_HDR_VCI_SHIFT);
+			aal0 |= (stat & SAR_RSQE_EPDU) ? 0x00000002 : 0;
+			aal0 |= (stat & SAR_RSQE_CLP)  ? 0x00000001 : 0;
+
+			*((u32 *) sb->data) = aal0;
+			skb_put(sb, sizeof(u32));
+			memcpy(skb_put(sb, ATM_CELL_PAYLOAD),
+			       cell, ATM_CELL_PAYLOAD);
+
+			ATM_SKB(sb)->vcc = vcc;
+			do_gettimeofday(&sb->stamp);
+			vcc->push(vcc, sb);
+			atomic_inc(&vcc->stats->rx);
+
+			cell += ATM_CELL_PAYLOAD;
+		}
+
+		recycle_rx_skb(card, skb);
+		return;
+	}
+	if (vcc->qos.aal != ATM_AAL5) {
+		printk("%s: Unexpected AAL type in dequeue_rx(): %d.\n",
+		       card->name, vcc->qos.aal);
+		recycle_rx_skb(card, skb);
+		return;
+	}
+	skb->len = (stat & SAR_RSQE_CELLCNT) * ATM_CELL_PAYLOAD;
+
+	rpp = &vc->rcv.rx_pool;
+
+	rpp->len += skb->len;
+	if (!rpp->count++)
+		rpp->first = skb;
+	*rpp->last = skb;
+	rpp->last = &skb->next;
+
+	if (stat & SAR_RSQE_EPDU) {
+		unsigned char *l1l2;
+		unsigned int len;
+
+		l1l2 = (unsigned char *) ((unsigned long) skb->data + skb->len - 6);
+
+		len = (l1l2[0] << 8) | l1l2[1];
+		len = len ? len : 0x10000;
+
+		RXPRINTK("%s: PDU has %d bytes.\n", card->name, len);
+
+		if ((len + 8 > rpp->len) || (len + (47 + 8) < rpp->len)) {
+			RXPRINTK("%s: AAL5 PDU size mismatch: %d != %d. "
+			         "(CDC: %08x)\n",
+			         card->name, len, rpp->len, readl(SAR_REG_CDC));
+			recycle_rx_pool_skb(card, rpp);
+			atomic_inc(&vcc->stats->rx_err);
+			return;
+		}
+		if (stat & SAR_RSQE_CRC) {
+			RXPRINTK("%s: AAL5 CRC error.\n", card->name);
+			recycle_rx_pool_skb(card, rpp);
+			atomic_inc(&vcc->stats->rx_err);
+			return;
+		}
+		if (rpp->count > 1) {
+			struct sk_buff *sb;
+
+			skb = dev_alloc_skb(rpp->len);
+			if (!skb) {
+				RXPRINTK("%s: Can't alloc RX skb.\n",
+					 card->name);
+				recycle_rx_pool_skb(card, rpp);
+				atomic_inc(&vcc->stats->rx_err);
+				return;
+			}
+			if (!atm_charge(vcc, skb->truesize)) {
+				recycle_rx_pool_skb(card, rpp);
+				dev_kfree_skb(skb);
+				return;
+			}
+			sb = rpp->first;
+			for (i = 0; i < rpp->count; i++) {
+				memcpy(skb_put(skb, sb->len),
+				       sb->data, sb->len);
+				sb = sb->next;
+			}
+
+			recycle_rx_pool_skb(card, rpp);
+
+			skb_trim(skb, len);
+			ATM_SKB(skb)->vcc = vcc;
+			do_gettimeofday(&skb->stamp);
+
+			vcc->push(vcc, skb);
+			atomic_inc(&vcc->stats->rx);
+
+			return;
+		}
+
+		skb->next = NULL;
+		flush_rx_pool(card, rpp);
+
+		if (!atm_charge(vcc, skb->truesize)) {
+			recycle_rx_skb(card, skb);
+			return;
+		}
+
+		pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+				 skb->end - skb->data, PCI_DMA_FROMDEVICE);
+		sb_pool_remove(card, skb);
+
+		skb_trim(skb, len);
+		ATM_SKB(skb)->vcc = vcc;
+		do_gettimeofday(&skb->stamp);
+
+		vcc->push(vcc, skb);
+		atomic_inc(&vcc->stats->rx);
+
+		if (skb->truesize > SAR_FB_SIZE_3)
+			add_rx_skb(card, 3, SAR_FB_SIZE_3, 1);
+		else if (skb->truesize > SAR_FB_SIZE_2)
+			add_rx_skb(card, 2, SAR_FB_SIZE_2, 1);
+		else if (skb->truesize > SAR_FB_SIZE_1)
+			add_rx_skb(card, 1, SAR_FB_SIZE_1, 1);
+		else
+			add_rx_skb(card, 0, SAR_FB_SIZE_0, 1);
+		return;
+	}
+}
+
+static void
+idt77252_rx(struct idt77252_dev *card)
+{
+	struct rsq_entry *rsqe;
+
+	if (card->rsq.next == card->rsq.last)
+		rsqe = card->rsq.base;
+	else
+		rsqe = card->rsq.next + 1;
+
+	if (!(le32_to_cpu(rsqe->word_4) & SAR_RSQE_VALID)) {
+		RXPRINTK("%s: no entry in RSQ.\n", card->name);
+		return;
+	}
+
+	do {
+		dequeue_rx(card, rsqe);
+		rsqe->word_4 = 0;
+		card->rsq.next = rsqe;
+		if (card->rsq.next == card->rsq.last)
+			rsqe = card->rsq.base;
+		else
+			rsqe = card->rsq.next + 1;
+	} while (le32_to_cpu(rsqe->word_4) & SAR_RSQE_VALID);
+
+	writel((unsigned long) card->rsq.next - (unsigned long) card->rsq.base,
+	       SAR_REG_RSQH);
+}
+
+static void
+idt77252_rx_raw(struct idt77252_dev *card)
+{
+	struct sk_buff	*queue;
+	u32		head, tail;
+	struct atm_vcc	*vcc;
+	struct vc_map	*vc;
+	struct sk_buff	*sb;
+
+	if (card->raw_cell_head == NULL) {
+		u32 handle = le32_to_cpu(*(card->raw_cell_hnd + 1));
+		card->raw_cell_head = sb_pool_skb(card, handle);
+	}
+
+	queue = card->raw_cell_head;
+	if (!queue)
+		return;
+
+	head = IDT77252_PRV_PADDR(queue) + (queue->data - queue->head - 16);
+	tail = readl(SAR_REG_RAWCT);
+
+	pci_dma_sync_single_for_cpu(card->pcidev, IDT77252_PRV_PADDR(queue),
+				    queue->end - queue->head - 16,
+				    PCI_DMA_FROMDEVICE);
+
+	while (head != tail) {
+		unsigned int vpi, vci, pti;
+		u32 header;
+
+		header = le32_to_cpu(*(u32 *) &queue->data[0]);
+
+		vpi = (header & ATM_HDR_VPI_MASK) >> ATM_HDR_VPI_SHIFT;
+		vci = (header & ATM_HDR_VCI_MASK) >> ATM_HDR_VCI_SHIFT;
+		pti = (header & ATM_HDR_PTI_MASK) >> ATM_HDR_PTI_SHIFT;
+
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+		if (debug & DBG_RAW_CELL) {
+			int i;
+
+			printk("%s: raw cell %x.%02x.%04x.%x.%x\n",
+			       card->name, (header >> 28) & 0x000f,
+			       (header >> 20) & 0x00ff,
+			       (header >>  4) & 0xffff,
+			       (header >>  1) & 0x0007,
+			       (header >>  0) & 0x0001);
+			for (i = 16; i < 64; i++)
+				printk(" %02x", queue->data[i]);
+			printk("\n");
+		}
+#endif
+
+		if (vpi >= (1<<card->vpibits) || vci >= (1<<card->vcibits)) {
+			RPRINTK("%s: SDU received for out-of-range vc %u.%u\n",
+				card->name, vpi, vci);
+			goto drop;
+		}
+
+		vc = card->vcs[VPCI2VC(card, vpi, vci)];
+		if (!vc || !test_bit(VCF_RX, &vc->flags)) {
+			RPRINTK("%s: SDU received on non RX vc %u.%u\n",
+				card->name, vpi, vci);
+			goto drop;
+		}
+
+		vcc = vc->rx_vcc;
+
+		if (vcc->qos.aal != ATM_AAL0) {
+			RPRINTK("%s: raw cell for non AAL0 vc %u.%u\n",
+				card->name, vpi, vci);
+			atomic_inc(&vcc->stats->rx_drop);
+			goto drop;
+		}
+	
+		if ((sb = dev_alloc_skb(64)) == NULL) {
+			printk("%s: Can't allocate buffers for AAL0.\n",
+			       card->name);
+			atomic_inc(&vcc->stats->rx_err);
+			goto drop;
+		}
+
+		if (!atm_charge(vcc, sb->truesize)) {
+			RXPRINTK("%s: atm_charge() dropped AAL0 packets.\n",
+				 card->name);
+			dev_kfree_skb(sb);
+			goto drop;
+		}
+
+		*((u32 *) sb->data) = header;
+		skb_put(sb, sizeof(u32));
+		memcpy(skb_put(sb, ATM_CELL_PAYLOAD), &(queue->data[16]),
+		       ATM_CELL_PAYLOAD);
+
+		ATM_SKB(sb)->vcc = vcc;
+		do_gettimeofday(&sb->stamp);
+		vcc->push(vcc, sb);
+		atomic_inc(&vcc->stats->rx);
+
+drop:
+		skb_pull(queue, 64);
+
+		head = IDT77252_PRV_PADDR(queue)
+					+ (queue->data - queue->head - 16);
+
+		if (queue->len < 128) {
+			struct sk_buff *next;
+			u32 handle;
+
+			head = le32_to_cpu(*(u32 *) &queue->data[0]);
+			handle = le32_to_cpu(*(u32 *) &queue->data[4]);
+
+			next = sb_pool_skb(card, handle);
+			recycle_rx_skb(card, queue);
+
+			if (next) {
+				card->raw_cell_head = next;
+				queue = card->raw_cell_head;
+				pci_dma_sync_single_for_cpu(card->pcidev,
+							    IDT77252_PRV_PADDR(queue),
+							    queue->end - queue->data,
+							    PCI_DMA_FROMDEVICE);
+			} else {
+				card->raw_cell_head = NULL;
+				printk("%s: raw cell queue overrun\n",
+				       card->name);
+				break;
+			}
+		}
+	}
+}
+
+
+/*****************************************************************************/
+/*                                                                           */
+/* TSQ Handling                                                              */
+/*                                                                           */
+/*****************************************************************************/
+
+static int
+init_tsq(struct idt77252_dev *card)
+{
+	struct tsq_entry *tsqe;
+
+	card->tsq.base = pci_alloc_consistent(card->pcidev, RSQSIZE,
+					      &card->tsq.paddr);
+	if (card->tsq.base == NULL) {
+		printk("%s: can't allocate TSQ.\n", card->name);
+		return -1;
+	}
+	memset(card->tsq.base, 0, TSQSIZE);
+
+	card->tsq.last = card->tsq.base + TSQ_NUM_ENTRIES - 1;
+	card->tsq.next = card->tsq.last;
+	for (tsqe = card->tsq.base; tsqe <= card->tsq.last; tsqe++)
+		tsqe->word_2 = cpu_to_le32(SAR_TSQE_INVALID);
+
+	writel(card->tsq.paddr, SAR_REG_TSQB);
+	writel((unsigned long) card->tsq.next - (unsigned long) card->tsq.base,
+	       SAR_REG_TSQH);
+
+	return 0;
+}
+
+static void
+deinit_tsq(struct idt77252_dev *card)
+{
+	pci_free_consistent(card->pcidev, TSQSIZE,
+			    card->tsq.base, card->tsq.paddr);
+}
+
+static void
+idt77252_tx(struct idt77252_dev *card)
+{
+	struct tsq_entry *tsqe;
+	unsigned int vpi, vci;
+	struct vc_map *vc;
+	u32 conn, stat;
+
+	if (card->tsq.next == card->tsq.last)
+		tsqe = card->tsq.base;
+	else
+		tsqe = card->tsq.next + 1;
+
+	TXPRINTK("idt77252_tx: tsq  %p: base %p, next %p, last %p\n", tsqe,
+		 card->tsq.base, card->tsq.next, card->tsq.last);
+	TXPRINTK("idt77252_tx: tsqb %08x, tsqt %08x, tsqh %08x, \n",
+		 readl(SAR_REG_TSQB),
+		 readl(SAR_REG_TSQT),
+		 readl(SAR_REG_TSQH));
+
+	stat = le32_to_cpu(tsqe->word_2);
+
+	if (stat & SAR_TSQE_INVALID)
+		return;
+
+	do {
+		TXPRINTK("tsqe: 0x%p [0x%08x 0x%08x]\n", tsqe,
+			 le32_to_cpu(tsqe->word_1),
+			 le32_to_cpu(tsqe->word_2));
+
+		switch (stat & SAR_TSQE_TYPE) {
+		case SAR_TSQE_TYPE_TIMER:
+			TXPRINTK("%s: Timer RollOver detected.\n", card->name);
+			break;
+
+		case SAR_TSQE_TYPE_IDLE:
+
+			conn = le32_to_cpu(tsqe->word_1);
+
+			if (SAR_TSQE_TAG(stat) == 0x10) {
+#ifdef	NOTDEF
+				printk("%s: Connection %d halted.\n",
+				       card->name,
+				       le32_to_cpu(tsqe->word_1) & 0x1fff);
+#endif
+				break;
+			}
+
+			vc = card->vcs[conn & 0x1fff];
+			if (!vc) {
+				printk("%s: could not find VC from conn %d\n",
+				       card->name, conn & 0x1fff);
+				break;
+			}
+
+			printk("%s: Connection %d IDLE.\n",
+			       card->name, vc->index);
+
+			set_bit(VCF_IDLE, &vc->flags);
+			break;
+
+		case SAR_TSQE_TYPE_TSR:
+
+			conn = le32_to_cpu(tsqe->word_1);
+
+			vc = card->vcs[conn & 0x1fff];
+			if (!vc) {
+				printk("%s: no VC at index %d\n",
+				       card->name,
+				       le32_to_cpu(tsqe->word_1) & 0x1fff);
+				break;
+			}
+
+			drain_scq(card, vc);
+			break;
+
+		case SAR_TSQE_TYPE_TBD_COMP:
+
+			conn = le32_to_cpu(tsqe->word_1);
+
+			vpi = (conn >> SAR_TBD_VPI_SHIFT) & 0x00ff;
+			vci = (conn >> SAR_TBD_VCI_SHIFT) & 0xffff;
+
+			if (vpi >= (1 << card->vpibits) ||
+			    vci >= (1 << card->vcibits)) {
+				printk("%s: TBD complete: "
+				       "out of range VPI.VCI %u.%u\n",
+				       card->name, vpi, vci);
+				break;
+			}
+
+			vc = card->vcs[VPCI2VC(card, vpi, vci)];
+			if (!vc) {
+				printk("%s: TBD complete: "
+				       "no VC at VPI.VCI %u.%u\n",
+				       card->name, vpi, vci);
+				break;
+			}
+
+			drain_scq(card, vc);
+			break;
+		}
+
+		tsqe->word_2 = cpu_to_le32(SAR_TSQE_INVALID);
+
+		card->tsq.next = tsqe;
+		if (card->tsq.next == card->tsq.last)
+			tsqe = card->tsq.base;
+		else
+			tsqe = card->tsq.next + 1;
+
+		TXPRINTK("tsqe: %p: base %p, next %p, last %p\n", tsqe,
+			 card->tsq.base, card->tsq.next, card->tsq.last);
+
+		stat = le32_to_cpu(tsqe->word_2);
+
+	} while (!(stat & SAR_TSQE_INVALID));
+
+	writel((unsigned long)card->tsq.next - (unsigned long)card->tsq.base,
+	       SAR_REG_TSQH);
+
+	XPRINTK("idt77252_tx-after writel%d: TSQ head = 0x%x, tail = 0x%x, next = 0x%p.\n",
+		card->index, readl(SAR_REG_TSQH),
+		readl(SAR_REG_TSQT), card->tsq.next);
+}
+
+
+static void
+tst_timer(unsigned long data)
+{
+	struct idt77252_dev *card = (struct idt77252_dev *)data;
+	unsigned long base, idle, jump;
+	unsigned long flags;
+	u32 pc;
+	int e;
+
+	spin_lock_irqsave(&card->tst_lock, flags);
+
+	base = card->tst[card->tst_index];
+	idle = card->tst[card->tst_index ^ 1];
+
+	if (test_bit(TST_SWITCH_WAIT, &card->tst_state)) {
+		jump = base + card->tst_size - 2;
+
+		pc = readl(SAR_REG_NOW) >> 2;
+		if ((pc ^ idle) & ~(card->tst_size - 1)) {
+			mod_timer(&card->tst_timer, jiffies + 1);
+			goto out;
+		}
+
+		clear_bit(TST_SWITCH_WAIT, &card->tst_state);
+
+		card->tst_index ^= 1;
+		write_sram(card, jump, TSTE_OPC_JMP | (base << 2));
+
+		base = card->tst[card->tst_index];
+		idle = card->tst[card->tst_index ^ 1];
+
+		for (e = 0; e < card->tst_size - 2; e++) {
+			if (card->soft_tst[e].tste & TSTE_PUSH_IDLE) {
+				write_sram(card, idle + e,
+					   card->soft_tst[e].tste & TSTE_MASK);
+				card->soft_tst[e].tste &= ~(TSTE_PUSH_IDLE);
+			}
+		}
+	}
+
+	if (test_and_clear_bit(TST_SWITCH_PENDING, &card->tst_state)) {
+
+		for (e = 0; e < card->tst_size - 2; e++) {
+			if (card->soft_tst[e].tste & TSTE_PUSH_ACTIVE) {
+				write_sram(card, idle + e,
+					   card->soft_tst[e].tste & TSTE_MASK);
+				card->soft_tst[e].tste &= ~(TSTE_PUSH_ACTIVE);
+				card->soft_tst[e].tste |= TSTE_PUSH_IDLE;
+			}
+		}
+
+		jump = base + card->tst_size - 2;
+
+		write_sram(card, jump, TSTE_OPC_NULL);
+		set_bit(TST_SWITCH_WAIT, &card->tst_state);
+
+		mod_timer(&card->tst_timer, jiffies + 1);
+	}
+
+out:
+	spin_unlock_irqrestore(&card->tst_lock, flags);
+}
+
+static int
+__fill_tst(struct idt77252_dev *card, struct vc_map *vc,
+	   int n, unsigned int opc)
+{
+	unsigned long cl, avail;
+	unsigned long idle;
+	int e, r;
+	u32 data;
+
+	avail = card->tst_size - 2;
+	for (e = 0; e < avail; e++) {
+		if (card->soft_tst[e].vc == NULL)
+			break;
+	}
+	if (e >= avail) {
+		printk("%s: No free TST entries found\n", card->name);
+		return -1;
+	}
+
+	NPRINTK("%s: conn %d: first TST entry at %d.\n",
+		card->name, vc ? vc->index : -1, e);
+
+	r = n;
+	cl = avail;
+	data = opc & TSTE_OPC_MASK;
+	if (vc && (opc != TSTE_OPC_NULL))
+		data = opc | vc->index;
+
+	idle = card->tst[card->tst_index ^ 1];
+
+	/*
+	 * Fill Soft TST.
+	 */
+	while (r > 0) {
+		if ((cl >= avail) && (card->soft_tst[e].vc == NULL)) {
+			if (vc)
+				card->soft_tst[e].vc = vc;
+			else
+				card->soft_tst[e].vc = (void *)-1;
+
+			card->soft_tst[e].tste = data;
+			if (timer_pending(&card->tst_timer))
+				card->soft_tst[e].tste |= TSTE_PUSH_ACTIVE;
+			else {
+				write_sram(card, idle + e, data);
+				card->soft_tst[e].tste |= TSTE_PUSH_IDLE;
+			}
+
+			cl -= card->tst_size;
+			r--;
+		}
+
+		if (++e == avail)
+			e = 0;
+		cl += n;
+	}
+
+	return 0;
+}
+
+static int
+fill_tst(struct idt77252_dev *card, struct vc_map *vc, int n, unsigned int opc)
+{
+	unsigned long flags;
+	int res;
+
+	spin_lock_irqsave(&card->tst_lock, flags);
+
+	res = __fill_tst(card, vc, n, opc);
+
+	set_bit(TST_SWITCH_PENDING, &card->tst_state);
+	if (!timer_pending(&card->tst_timer))
+		mod_timer(&card->tst_timer, jiffies + 1);
+
+	spin_unlock_irqrestore(&card->tst_lock, flags);
+	return res;
+}
+
+static int
+__clear_tst(struct idt77252_dev *card, struct vc_map *vc)
+{
+	unsigned long idle;
+	int e;
+
+	idle = card->tst[card->tst_index ^ 1];
+
+	for (e = 0; e < card->tst_size - 2; e++) {
+		if (card->soft_tst[e].vc == vc) {
+			card->soft_tst[e].vc = NULL;
+
+			card->soft_tst[e].tste = TSTE_OPC_VAR;
+			if (timer_pending(&card->tst_timer))
+				card->soft_tst[e].tste |= TSTE_PUSH_ACTIVE;
+			else {
+				write_sram(card, idle + e, TSTE_OPC_VAR);
+				card->soft_tst[e].tste |= TSTE_PUSH_IDLE;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int
+clear_tst(struct idt77252_dev *card, struct vc_map *vc)
+{
+	unsigned long flags;
+	int res;
+
+	spin_lock_irqsave(&card->tst_lock, flags);
+
+	res = __clear_tst(card, vc);
+
+	set_bit(TST_SWITCH_PENDING, &card->tst_state);
+	if (!timer_pending(&card->tst_timer))
+		mod_timer(&card->tst_timer, jiffies + 1);
+
+	spin_unlock_irqrestore(&card->tst_lock, flags);
+	return res;
+}
+
+static int
+change_tst(struct idt77252_dev *card, struct vc_map *vc,
+	   int n, unsigned int opc)
+{
+	unsigned long flags;
+	int res;
+
+	spin_lock_irqsave(&card->tst_lock, flags);
+
+	__clear_tst(card, vc);
+	res = __fill_tst(card, vc, n, opc);
+
+	set_bit(TST_SWITCH_PENDING, &card->tst_state);
+	if (!timer_pending(&card->tst_timer))
+		mod_timer(&card->tst_timer, jiffies + 1);
+
+	spin_unlock_irqrestore(&card->tst_lock, flags);
+	return res;
+}
+
+
+static int
+set_tct(struct idt77252_dev *card, struct vc_map *vc)
+{
+	unsigned long tct;
+
+	tct = (unsigned long) (card->tct_base + vc->index * SAR_SRAM_TCT_SIZE);
+
+	switch (vc->class) {
+	case SCHED_CBR:
+		OPRINTK("%s: writing TCT at 0x%lx, SCD 0x%lx.\n",
+		        card->name, tct, vc->scq->scd);
+
+		write_sram(card, tct + 0, TCT_CBR | vc->scq->scd);
+		write_sram(card, tct + 1, 0);
+		write_sram(card, tct + 2, 0);
+		write_sram(card, tct + 3, 0);
+		write_sram(card, tct + 4, 0);
+		write_sram(card, tct + 5, 0);
+		write_sram(card, tct + 6, 0);
+		write_sram(card, tct + 7, 0);
+		break;
+
+	case SCHED_UBR:
+		OPRINTK("%s: writing TCT at 0x%lx, SCD 0x%lx.\n",
+		        card->name, tct, vc->scq->scd);
+
+		write_sram(card, tct + 0, TCT_UBR | vc->scq->scd);
+		write_sram(card, tct + 1, 0);
+		write_sram(card, tct + 2, TCT_TSIF);
+		write_sram(card, tct + 3, TCT_HALT | TCT_IDLE);
+		write_sram(card, tct + 4, 0);
+		write_sram(card, tct + 5, vc->init_er);
+		write_sram(card, tct + 6, 0);
+		write_sram(card, tct + 7, TCT_FLAG_UBR);
+		break;
+
+	case SCHED_VBR:
+	case SCHED_ABR:
+	default:
+		return -ENOSYS;
+	}
+
+	return 0;
+}
+
+/*****************************************************************************/
+/*                                                                           */
+/* FBQ Handling                                                              */
+/*                                                                           */
+/*****************************************************************************/
+
+static __inline__ int
+idt77252_fbq_level(struct idt77252_dev *card, int queue)
+{
+	return (readl(SAR_REG_STAT) >> (16 + (queue << 2))) & 0x0f;
+}
+
+static __inline__ int
+idt77252_fbq_full(struct idt77252_dev *card, int queue)
+{
+	return (readl(SAR_REG_STAT) >> (16 + (queue << 2))) == 0x0f;
+}
+
+static int
+push_rx_skb(struct idt77252_dev *card, struct sk_buff *skb, int queue)
+{
+	unsigned long flags;
+	u32 handle;
+	u32 addr;
+
+	skb->data = skb->tail = skb->head;
+	skb->len = 0;
+
+	skb_reserve(skb, 16);
+
+	switch (queue) {
+	case 0:
+		skb_put(skb, SAR_FB_SIZE_0);
+		break;
+	case 1:
+		skb_put(skb, SAR_FB_SIZE_1);
+		break;
+	case 2:
+		skb_put(skb, SAR_FB_SIZE_2);
+		break;
+	case 3:
+		skb_put(skb, SAR_FB_SIZE_3);
+		break;
+	default:
+		dev_kfree_skb(skb);
+		return -1;
+	}
+
+	if (idt77252_fbq_full(card, queue))
+		return -1;
+
+	memset(&skb->data[(skb->len & ~(0x3f)) - 64], 0, 2 * sizeof(u32));
+
+	handle = IDT77252_PRV_POOL(skb);
+	addr = IDT77252_PRV_PADDR(skb);
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	writel(handle, card->fbq[queue]);
+	writel(addr, card->fbq[queue]);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+
+	return 0;
+}
+
+static void
+add_rx_skb(struct idt77252_dev *card, int queue,
+	   unsigned int size, unsigned int count)
+{
+	struct sk_buff *skb;
+	dma_addr_t paddr;
+	u32 handle;
+
+	while (count--) {
+		skb = dev_alloc_skb(size);
+		if (!skb)
+			return;
+
+		if (sb_pool_add(card, skb, queue)) {
+			printk("%s: SB POOL full\n", __FUNCTION__);
+			goto outfree;
+		}
+
+		paddr = pci_map_single(card->pcidev, skb->data,
+				       skb->end - skb->data,
+				       PCI_DMA_FROMDEVICE);
+		IDT77252_PRV_PADDR(skb) = paddr;
+
+		if (push_rx_skb(card, skb, queue)) {
+			printk("%s: FB QUEUE full\n", __FUNCTION__);
+			goto outunmap;
+		}
+	}
+
+	return;
+
+outunmap:
+	pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+			 skb->end - skb->data, PCI_DMA_FROMDEVICE);
+
+	handle = IDT77252_PRV_POOL(skb);
+	card->sbpool[POOL_QUEUE(handle)].skb[POOL_INDEX(handle)] = NULL;
+
+outfree:
+	dev_kfree_skb(skb);
+}
+
+
+static void
+recycle_rx_skb(struct idt77252_dev *card, struct sk_buff *skb)
+{
+	u32 handle = IDT77252_PRV_POOL(skb);
+	int err;
+
+	pci_dma_sync_single_for_device(card->pcidev, IDT77252_PRV_PADDR(skb),
+				       skb->end - skb->data, PCI_DMA_FROMDEVICE);
+
+	err = push_rx_skb(card, skb, POOL_QUEUE(handle));
+	if (err) {
+		pci_unmap_single(card->pcidev, IDT77252_PRV_PADDR(skb),
+				 skb->end - skb->data, PCI_DMA_FROMDEVICE);
+		sb_pool_remove(card, skb);
+		dev_kfree_skb(skb);
+	}
+}
+
+static void
+flush_rx_pool(struct idt77252_dev *card, struct rx_pool *rpp)
+{
+	rpp->len = 0;
+	rpp->count = 0;
+	rpp->first = NULL;
+	rpp->last = &rpp->first;
+}
+
+static void
+recycle_rx_pool_skb(struct idt77252_dev *card, struct rx_pool *rpp)
+{
+	struct sk_buff *skb, *next;
+	int i;
+
+	skb = rpp->first;
+	for (i = 0; i < rpp->count; i++) {
+		next = skb->next;
+		skb->next = NULL;
+		recycle_rx_skb(card, skb);
+		skb = next;
+	}
+	flush_rx_pool(card, rpp);
+}
+
+/*****************************************************************************/
+/*                                                                           */
+/* ATM Interface                                                             */
+/*                                                                           */
+/*****************************************************************************/
+
+static void
+idt77252_phy_put(struct atm_dev *dev, unsigned char value, unsigned long addr)
+{
+	write_utility(dev->dev_data, 0x100 + (addr & 0x1ff), value);
+}
+
+static unsigned char
+idt77252_phy_get(struct atm_dev *dev, unsigned long addr)
+{
+	return read_utility(dev->dev_data, 0x100 + (addr & 0x1ff));
+}
+
+static inline int
+idt77252_send_skb(struct atm_vcc *vcc, struct sk_buff *skb, int oam)
+{
+	struct atm_dev *dev = vcc->dev;
+	struct idt77252_dev *card = dev->dev_data;
+	struct vc_map *vc = vcc->dev_data;
+	int err;
+
+	if (vc == NULL) {
+		printk("%s: NULL connection in send().\n", card->name);
+		atomic_inc(&vcc->stats->tx_err);
+		dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+	if (!test_bit(VCF_TX, &vc->flags)) {
+		printk("%s: Trying to transmit on a non-tx VC.\n", card->name);
+		atomic_inc(&vcc->stats->tx_err);
+		dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	switch (vcc->qos.aal) {
+	case ATM_AAL0:
+	case ATM_AAL1:
+	case ATM_AAL5:
+		break;
+	default:
+		printk("%s: Unsupported AAL: %d\n", card->name, vcc->qos.aal);
+		atomic_inc(&vcc->stats->tx_err);
+		dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	if (skb_shinfo(skb)->nr_frags != 0) {
+		printk("%s: No scatter-gather yet.\n", card->name);
+		atomic_inc(&vcc->stats->tx_err);
+		dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+	ATM_SKB(skb)->vcc = vcc;
+
+	err = queue_skb(card, vc, skb, oam);
+	if (err) {
+		atomic_inc(&vcc->stats->tx_err);
+		dev_kfree_skb(skb);
+		return err;
+	}
+
+	return 0;
+}
+
+int
+idt77252_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+	return idt77252_send_skb(vcc, skb, 0);
+}
+
+static int
+idt77252_send_oam(struct atm_vcc *vcc, void *cell, int flags)
+{
+	struct atm_dev *dev = vcc->dev;
+	struct idt77252_dev *card = dev->dev_data;
+	struct sk_buff *skb;
+
+	skb = dev_alloc_skb(64);
+	if (!skb) {
+		printk("%s: Out of memory in send_oam().\n", card->name);
+		atomic_inc(&vcc->stats->tx_err);
+		return -ENOMEM;
+	}
+	atomic_add(skb->truesize, &sk_atm(vcc)->sk_wmem_alloc);
+
+	memcpy(skb_put(skb, 52), cell, 52);
+
+	return idt77252_send_skb(vcc, skb, 1);
+}
+
+static __inline__ unsigned int
+idt77252_fls(unsigned int x)
+{
+	int r = 1;
+
+	if (x == 0)
+		return 0;
+	if (x & 0xffff0000) {
+		x >>= 16;
+		r += 16;
+	}
+	if (x & 0xff00) {
+		x >>= 8;
+		r += 8;
+	}
+	if (x & 0xf0) {
+		x >>= 4;
+		r += 4;
+	}
+	if (x & 0xc) {
+		x >>= 2;
+		r += 2;
+	}
+	if (x & 0x2)
+		r += 1;
+	return r;
+}
+
+static u16
+idt77252_int_to_atmfp(unsigned int rate)
+{
+	u16 m, e;
+
+	if (rate == 0)
+		return 0;
+	e = idt77252_fls(rate) - 1;
+	if (e < 9)
+		m = (rate - (1 << e)) << (9 - e);
+	else if (e == 9)
+		m = (rate - (1 << e));
+	else /* e > 9 */
+		m = (rate - (1 << e)) >> (e - 9);
+	return 0x4000 | (e << 9) | m;
+}
+
+static u8
+idt77252_rate_logindex(struct idt77252_dev *card, int pcr)
+{
+	u16 afp;
+
+	afp = idt77252_int_to_atmfp(pcr < 0 ? -pcr : pcr);
+	if (pcr < 0)
+		return rate_to_log[(afp >> 5) & 0x1ff];
+	return rate_to_log[((afp >> 5) + 1) & 0x1ff];
+}
+
+static void
+idt77252_est_timer(unsigned long data)
+{
+	struct vc_map *vc = (struct vc_map *)data;
+	struct idt77252_dev *card = vc->card;
+	struct rate_estimator *est;
+	unsigned long flags;
+	u32 rate, cps;
+	u64 ncells;
+	u8 lacr;
+
+	spin_lock_irqsave(&vc->lock, flags);
+	est = vc->estimator;
+	if (!est)
+		goto out;
+
+	ncells = est->cells;
+
+	rate = ((u32)(ncells - est->last_cells)) << (7 - est->interval);
+	est->last_cells = ncells;
+	est->avcps += ((long)rate - (long)est->avcps) >> est->ewma_log;
+	est->cps = (est->avcps + 0x1f) >> 5;
+
+	cps = est->cps;
+	if (cps < (est->maxcps >> 4))
+		cps = est->maxcps >> 4;
+
+	lacr = idt77252_rate_logindex(card, cps);
+	if (lacr > vc->max_er)
+		lacr = vc->max_er;
+
+	if (lacr != vc->lacr) {
+		vc->lacr = lacr;
+		writel(TCMDQ_LACR|(vc->lacr << 16)|vc->index, SAR_REG_TCMDQ);
+	}
+
+	est->timer.expires = jiffies + ((HZ / 4) << est->interval);
+	add_timer(&est->timer);
+
+out:
+	spin_unlock_irqrestore(&vc->lock, flags);
+}
+
+static struct rate_estimator *
+idt77252_init_est(struct vc_map *vc, int pcr)
+{
+	struct rate_estimator *est;
+
+	est = kmalloc(sizeof(struct rate_estimator), GFP_KERNEL);
+	if (!est)
+		return NULL;
+	memset(est, 0, sizeof(*est));
+
+	est->maxcps = pcr < 0 ? -pcr : pcr;
+	est->cps = est->maxcps;
+	est->avcps = est->cps << 5;
+
+	est->interval = 2;		/* XXX: make this configurable */
+	est->ewma_log = 2;		/* XXX: make this configurable */
+	init_timer(&est->timer);
+	est->timer.data = (unsigned long)vc;
+	est->timer.function = idt77252_est_timer;
+
+	est->timer.expires = jiffies + ((HZ / 4) << est->interval);
+	add_timer(&est->timer);
+
+	return est;
+}
+
+static int
+idt77252_init_cbr(struct idt77252_dev *card, struct vc_map *vc,
+		  struct atm_vcc *vcc, struct atm_qos *qos)
+{
+	int tst_free, tst_used, tst_entries;
+	unsigned long tmpl, modl;
+	int tcr, tcra;
+
+	if ((qos->txtp.max_pcr == 0) &&
+	    (qos->txtp.pcr == 0) && (qos->txtp.min_pcr == 0)) {
+		printk("%s: trying to open a CBR VC with cell rate = 0\n",
+		       card->name);
+		return -EINVAL;
+	}
+
+	tst_used = 0;
+	tst_free = card->tst_free;
+	if (test_bit(VCF_TX, &vc->flags))
+		tst_used = vc->ntste;
+	tst_free += tst_used;
+
+	tcr = atm_pcr_goal(&qos->txtp);
+	tcra = tcr >= 0 ? tcr : -tcr;
+
+	TXPRINTK("%s: CBR target cell rate = %d\n", card->name, tcra);
+
+	tmpl = (unsigned long) tcra * ((unsigned long) card->tst_size - 2);
+	modl = tmpl % (unsigned long)card->utopia_pcr;
+
+	tst_entries = (int) (tmpl / card->utopia_pcr);
+	if (tcr > 0) {
+		if (modl > 0)
+			tst_entries++;
+	} else if (tcr == 0) {
+		tst_entries = tst_free - SAR_TST_RESERVED;
+		if (tst_entries <= 0) {
+			printk("%s: no CBR bandwidth free.\n", card->name);
+			return -ENOSR;
+		}
+	}
+
+	if (tst_entries == 0) {
+		printk("%s: selected CBR bandwidth < granularity.\n",
+		       card->name);
+		return -EINVAL;
+	}
+
+	if (tst_entries > (tst_free - SAR_TST_RESERVED)) {
+		printk("%s: not enough CBR bandwidth free.\n", card->name);
+		return -ENOSR;
+	}
+
+	vc->ntste = tst_entries;
+
+	card->tst_free = tst_free - tst_entries;
+	if (test_bit(VCF_TX, &vc->flags)) {
+		if (tst_used == tst_entries)
+			return 0;
+
+		OPRINTK("%s: modify %d -> %d entries in TST.\n",
+			card->name, tst_used, tst_entries);
+		change_tst(card, vc, tst_entries, TSTE_OPC_CBR);
+		return 0;
+	}
+
+	OPRINTK("%s: setting %d entries in TST.\n", card->name, tst_entries);
+	fill_tst(card, vc, tst_entries, TSTE_OPC_CBR);
+	return 0;
+}
+
+static int
+idt77252_init_ubr(struct idt77252_dev *card, struct vc_map *vc,
+		  struct atm_vcc *vcc, struct atm_qos *qos)
+{
+	unsigned long flags;
+	int tcr;
+
+	spin_lock_irqsave(&vc->lock, flags);
+	if (vc->estimator) {
+		del_timer(&vc->estimator->timer);
+		kfree(vc->estimator);
+		vc->estimator = NULL;
+	}
+	spin_unlock_irqrestore(&vc->lock, flags);
+
+	tcr = atm_pcr_goal(&qos->txtp);
+	if (tcr == 0)
+		tcr = card->link_pcr;
+
+	vc->estimator = idt77252_init_est(vc, tcr);
+
+	vc->class = SCHED_UBR;
+	vc->init_er = idt77252_rate_logindex(card, tcr);
+	vc->lacr = vc->init_er;
+	if (tcr < 0)
+		vc->max_er = vc->init_er;
+	else
+		vc->max_er = 0xff;
+
+	return 0;
+}
+
+static int
+idt77252_init_tx(struct idt77252_dev *card, struct vc_map *vc,
+		 struct atm_vcc *vcc, struct atm_qos *qos)
+{
+	int error;
+
+	if (test_bit(VCF_TX, &vc->flags))
+		return -EBUSY;
+
+	switch (qos->txtp.traffic_class) {
+		case ATM_CBR:
+			vc->class = SCHED_CBR;
+			break;
+
+		case ATM_UBR:
+			vc->class = SCHED_UBR;
+			break;
+
+		case ATM_VBR:
+		case ATM_ABR:
+		default:
+			return -EPROTONOSUPPORT;
+	}
+
+	vc->scq = alloc_scq(card, vc->class);
+	if (!vc->scq) {
+		printk("%s: can't get SCQ.\n", card->name);
+		return -ENOMEM;
+	}
+
+	vc->scq->scd = get_free_scd(card, vc);
+	if (vc->scq->scd == 0) {
+		printk("%s: no SCD available.\n", card->name);
+		free_scq(card, vc->scq);
+		return -ENOMEM;
+	}
+
+	fill_scd(card, vc->scq, vc->class);
+
+	if (set_tct(card, vc)) {
+		printk("%s: class %d not supported.\n",
+		       card->name, qos->txtp.traffic_class);
+
+		card->scd2vc[vc->scd_index] = NULL;
+		free_scq(card, vc->scq);
+		return -EPROTONOSUPPORT;
+	}
+
+	switch (vc->class) {
+		case SCHED_CBR:
+			error = idt77252_init_cbr(card, vc, vcc, qos);
+			if (error) {
+				card->scd2vc[vc->scd_index] = NULL;
+				free_scq(card, vc->scq);
+				return error;
+			}
+
+			clear_bit(VCF_IDLE, &vc->flags);
+			writel(TCMDQ_START | vc->index, SAR_REG_TCMDQ);
+			break;
+
+		case SCHED_UBR:
+			error = idt77252_init_ubr(card, vc, vcc, qos);
+			if (error) {
+				card->scd2vc[vc->scd_index] = NULL;
+				free_scq(card, vc->scq);
+				return error;
+			}
+
+			set_bit(VCF_IDLE, &vc->flags);
+			break;
+	}
+
+	vc->tx_vcc = vcc;
+	set_bit(VCF_TX, &vc->flags);
+	return 0;
+}
+
+static int
+idt77252_init_rx(struct idt77252_dev *card, struct vc_map *vc,
+		 struct atm_vcc *vcc, struct atm_qos *qos)
+{
+	unsigned long flags;
+	unsigned long addr;
+	u32 rcte = 0;
+
+	if (test_bit(VCF_RX, &vc->flags))
+		return -EBUSY;
+
+	vc->rx_vcc = vcc;
+	set_bit(VCF_RX, &vc->flags);
+
+	if ((vcc->vci == 3) || (vcc->vci == 4))
+		return 0;
+
+	flush_rx_pool(card, &vc->rcv.rx_pool);
+
+	rcte |= SAR_RCTE_CONNECTOPEN;
+	rcte |= SAR_RCTE_RAWCELLINTEN;
+
+	switch (qos->aal) {
+		case ATM_AAL0:
+			rcte |= SAR_RCTE_RCQ;
+			break;
+		case ATM_AAL1:
+			rcte |= SAR_RCTE_OAM; /* Let SAR drop Video */
+			break;
+		case ATM_AAL34:
+			rcte |= SAR_RCTE_AAL34;
+			break;
+		case ATM_AAL5:
+			rcte |= SAR_RCTE_AAL5;
+			break;
+		default:
+			rcte |= SAR_RCTE_RCQ;
+			break;
+	}
+
+	if (qos->aal != ATM_AAL5)
+		rcte |= SAR_RCTE_FBP_1;
+	else if (qos->rxtp.max_sdu > SAR_FB_SIZE_2)
+		rcte |= SAR_RCTE_FBP_3;
+	else if (qos->rxtp.max_sdu > SAR_FB_SIZE_1)
+		rcte |= SAR_RCTE_FBP_2;
+	else if (qos->rxtp.max_sdu > SAR_FB_SIZE_0)
+		rcte |= SAR_RCTE_FBP_1;
+	else
+		rcte |= SAR_RCTE_FBP_01;
+
+	addr = card->rct_base + (vc->index << 2);
+
+	OPRINTK("%s: writing RCT at 0x%lx\n", card->name, addr);
+	write_sram(card, addr, rcte);
+
+	spin_lock_irqsave(&card->cmd_lock, flags);
+	writel(SAR_CMD_OPEN_CONNECTION | (addr << 2), SAR_REG_CMD);
+	waitfor_idle(card);
+	spin_unlock_irqrestore(&card->cmd_lock, flags);
+
+	return 0;
+}
+
+static int
+idt77252_open(struct atm_vcc *vcc)
+{
+	struct atm_dev *dev = vcc->dev;
+	struct idt77252_dev *card = dev->dev_data;
+	struct vc_map *vc;
+	unsigned int index;
+	unsigned int inuse;
+	int error;
+	int vci = vcc->vci;
+	short vpi = vcc->vpi;
+
+	if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC)
+		return 0;
+
+	if (vpi >= (1 << card->vpibits)) {
+		printk("%s: unsupported VPI: %d\n", card->name, vpi);
+		return -EINVAL;
+	}
+
+	if (vci >= (1 << card->vcibits)) {
+		printk("%s: unsupported VCI: %d\n", card->name, vci);
+		return -EINVAL;
+	}
+
+	set_bit(ATM_VF_ADDR, &vcc->flags);
+
+	down(&card->mutex);
+
+	OPRINTK("%s: opening vpi.vci: %d.%d\n", card->name, vpi, vci);
+
+	switch (vcc->qos.aal) {
+	case ATM_AAL0:
+	case ATM_AAL1:
+	case ATM_AAL5:
+		break;
+	default:
+		printk("%s: Unsupported AAL: %d\n", card->name, vcc->qos.aal);
+		up(&card->mutex);
+		return -EPROTONOSUPPORT;
+	}
+
+	index = VPCI2VC(card, vpi, vci);
+	if (!card->vcs[index]) {
+		card->vcs[index] = kmalloc(sizeof(struct vc_map), GFP_KERNEL);
+		if (!card->vcs[index]) {
+			printk("%s: can't alloc vc in open()\n", card->name);
+			up(&card->mutex);
+			return -ENOMEM;
+		}
+		memset(card->vcs[index], 0, sizeof(struct vc_map));
+
+		card->vcs[index]->card = card;
+		card->vcs[index]->index = index;
+
+		spin_lock_init(&card->vcs[index]->lock);
+	}
+	vc = card->vcs[index];
+
+	vcc->dev_data = vc;
+
+	IPRINTK("%s: idt77252_open: vc = %d (%d.%d) %s/%s (max RX SDU: %u)\n",
+	        card->name, vc->index, vcc->vpi, vcc->vci,
+	        vcc->qos.rxtp.traffic_class != ATM_NONE ? "rx" : "--",
+	        vcc->qos.txtp.traffic_class != ATM_NONE ? "tx" : "--",
+	        vcc->qos.rxtp.max_sdu);
+
+	inuse = 0;
+	if (vcc->qos.txtp.traffic_class != ATM_NONE &&
+	    test_bit(VCF_TX, &vc->flags))
+		inuse = 1;
+	if (vcc->qos.rxtp.traffic_class != ATM_NONE &&
+	    test_bit(VCF_RX, &vc->flags))
+		inuse += 2;
+
+	if (inuse) {
+		printk("%s: %s vci already in use.\n", card->name,
+		       inuse == 1 ? "tx" : inuse == 2 ? "rx" : "tx and rx");
+		up(&card->mutex);
+		return -EADDRINUSE;
+	}
+
+	if (vcc->qos.txtp.traffic_class != ATM_NONE) {
+		error = idt77252_init_tx(card, vc, vcc, &vcc->qos);
+		if (error) {
+			up(&card->mutex);
+			return error;
+		}
+	}
+
+	if (vcc->qos.rxtp.traffic_class != ATM_NONE) {
+		error = idt77252_init_rx(card, vc, vcc, &vcc->qos);
+		if (error) {
+			up(&card->mutex);
+			return error;
+		}
+	}
+
+	set_bit(ATM_VF_READY, &vcc->flags);
+
+	up(&card->mutex);
+	return 0;
+}
+
+static void
+idt77252_close(struct atm_vcc *vcc)
+{
+	struct atm_dev *dev = vcc->dev;
+	struct idt77252_dev *card = dev->dev_data;
+	struct vc_map *vc = vcc->dev_data;
+	unsigned long flags;
+	unsigned long addr;
+	unsigned long timeout;
+
+	down(&card->mutex);
+
+	IPRINTK("%s: idt77252_close: vc = %d (%d.%d)\n",
+		card->name, vc->index, vcc->vpi, vcc->vci);
+
+	clear_bit(ATM_VF_READY, &vcc->flags);
+
+	if (vcc->qos.rxtp.traffic_class != ATM_NONE) {
+
+		spin_lock_irqsave(&vc->lock, flags);
+		clear_bit(VCF_RX, &vc->flags);
+		vc->rx_vcc = NULL;
+		spin_unlock_irqrestore(&vc->lock, flags);
+
+		if ((vcc->vci == 3) || (vcc->vci == 4))
+			goto done;
+
+		addr = card->rct_base + vc->index * SAR_SRAM_RCT_SIZE;
+
+		spin_lock_irqsave(&card->cmd_lock, flags);
+		writel(SAR_CMD_CLOSE_CONNECTION | (addr << 2), SAR_REG_CMD);
+		waitfor_idle(card);
+		spin_unlock_irqrestore(&card->cmd_lock, flags);
+
+		if (vc->rcv.rx_pool.count) {
+			DPRINTK("%s: closing a VC with pending rx buffers.\n",
+				card->name);
+
+			recycle_rx_pool_skb(card, &vc->rcv.rx_pool);
+		}
+	}
+
+done:
+	if (vcc->qos.txtp.traffic_class != ATM_NONE) {
+
+		spin_lock_irqsave(&vc->lock, flags);
+		clear_bit(VCF_TX, &vc->flags);
+		clear_bit(VCF_IDLE, &vc->flags);
+		clear_bit(VCF_RSV, &vc->flags);
+		vc->tx_vcc = NULL;
+
+		if (vc->estimator) {
+			del_timer(&vc->estimator->timer);
+			kfree(vc->estimator);
+			vc->estimator = NULL;
+		}
+		spin_unlock_irqrestore(&vc->lock, flags);
+
+		timeout = 5 * 1000;
+		while (atomic_read(&vc->scq->used) > 0) {
+			timeout = msleep_interruptible(timeout);
+			if (!timeout)
+				break;
+		}
+		if (!timeout)
+			printk("%s: SCQ drain timeout: %u used\n",
+			       card->name, atomic_read(&vc->scq->used));
+
+		writel(TCMDQ_HALT | vc->index, SAR_REG_TCMDQ);
+		clear_scd(card, vc->scq, vc->class);
+
+		if (vc->class == SCHED_CBR) {
+			clear_tst(card, vc);
+			card->tst_free += vc->ntste;
+			vc->ntste = 0;
+		}
+
+		card->scd2vc[vc->scd_index] = NULL;
+		free_scq(card, vc->scq);
+	}
+
+	up(&card->mutex);
+}
+
+static int
+idt77252_change_qos(struct atm_vcc *vcc, struct atm_qos *qos, int flags)
+{
+	struct atm_dev *dev = vcc->dev;
+	struct idt77252_dev *card = dev->dev_data;
+	struct vc_map *vc = vcc->dev_data;
+	int error = 0;
+
+	down(&card->mutex);
+
+	if (qos->txtp.traffic_class != ATM_NONE) {
+	    	if (!test_bit(VCF_TX, &vc->flags)) {
+			error = idt77252_init_tx(card, vc, vcc, qos);
+			if (error)
+				goto out;
+		} else {
+			switch (qos->txtp.traffic_class) {
+			case ATM_CBR:
+				error = idt77252_init_cbr(card, vc, vcc, qos);
+				if (error)
+					goto out;
+				break;
+
+			case ATM_UBR:
+				error = idt77252_init_ubr(card, vc, vcc, qos);
+				if (error)
+					goto out;
+
+				if (!test_bit(VCF_IDLE, &vc->flags)) {
+					writel(TCMDQ_LACR | (vc->lacr << 16) |
+					       vc->index, SAR_REG_TCMDQ);
+				}
+				break;
+
+			case ATM_VBR:
+			case ATM_ABR:
+				error = -EOPNOTSUPP;
+				goto out;
+			}
+		}
+	}
+
+	if ((qos->rxtp.traffic_class != ATM_NONE) &&
+	    !test_bit(VCF_RX, &vc->flags)) {
+		error = idt77252_init_rx(card, vc, vcc, qos);
+		if (error)
+			goto out;
+	}
+
+	memcpy(&vcc->qos, qos, sizeof(struct atm_qos));
+
+	set_bit(ATM_VF_HASQOS, &vcc->flags);
+
+out:
+	up(&card->mutex);
+	return error;
+}
+
+static int
+idt77252_proc_read(struct atm_dev *dev, loff_t * pos, char *page)
+{
+	struct idt77252_dev *card = dev->dev_data;
+	int i, left;
+
+	left = (int) *pos;
+	if (!left--)
+		return sprintf(page, "IDT77252 Interrupts:\n");
+	if (!left--)
+		return sprintf(page, "TSIF:  %lu\n", card->irqstat[15]);
+	if (!left--)
+		return sprintf(page, "TXICP: %lu\n", card->irqstat[14]);
+	if (!left--)
+		return sprintf(page, "TSQF:  %lu\n", card->irqstat[12]);
+	if (!left--)
+		return sprintf(page, "TMROF: %lu\n", card->irqstat[11]);
+	if (!left--)
+		return sprintf(page, "PHYI:  %lu\n", card->irqstat[10]);
+	if (!left--)
+		return sprintf(page, "FBQ3A: %lu\n", card->irqstat[8]);
+	if (!left--)
+		return sprintf(page, "FBQ2A: %lu\n", card->irqstat[7]);
+	if (!left--)
+		return sprintf(page, "RSQF:  %lu\n", card->irqstat[6]);
+	if (!left--)
+		return sprintf(page, "EPDU:  %lu\n", card->irqstat[5]);
+	if (!left--)
+		return sprintf(page, "RAWCF: %lu\n", card->irqstat[4]);
+	if (!left--)
+		return sprintf(page, "FBQ1A: %lu\n", card->irqstat[3]);
+	if (!left--)
+		return sprintf(page, "FBQ0A: %lu\n", card->irqstat[2]);
+	if (!left--)
+		return sprintf(page, "RSQAF: %lu\n", card->irqstat[1]);
+	if (!left--)
+		return sprintf(page, "IDT77252 Transmit Connection Table:\n");
+
+	for (i = 0; i < card->tct_size; i++) {
+		unsigned long tct;
+		struct atm_vcc *vcc;
+		struct vc_map *vc;
+		char *p;
+
+		vc = card->vcs[i];
+		if (!vc)
+			continue;
+
+		vcc = NULL;
+		if (vc->tx_vcc)
+			vcc = vc->tx_vcc;
+		if (!vcc)
+			continue;
+		if (left--)
+			continue;
+
+		p = page;
+		p += sprintf(p, "  %4u: %u.%u: ", i, vcc->vpi, vcc->vci);
+		tct = (unsigned long) (card->tct_base + i * SAR_SRAM_TCT_SIZE);
+
+		for (i = 0; i < 8; i++)
+			p += sprintf(p, " %08x", read_sram(card, tct + i));
+		p += sprintf(p, "\n");
+		return p - page;
+	}
+	return 0;
+}
+
+/*****************************************************************************/
+/*                                                                           */
+/* Interrupt handler                                                         */
+/*                                                                           */
+/*****************************************************************************/
+
+static void
+idt77252_collect_stat(struct idt77252_dev *card)
+{
+	u32 cdc, vpec, icc;
+
+	cdc = readl(SAR_REG_CDC);
+	vpec = readl(SAR_REG_VPEC);
+	icc = readl(SAR_REG_ICC);
+
+#ifdef	NOTDEF
+	printk("%s:", card->name);
+
+	if (cdc & 0x7f0000) {
+		char *s = "";
+
+		printk(" [");
+		if (cdc & (1 << 22)) {
+			printk("%sRM ID", s);
+			s = " | ";
+		}
+		if (cdc & (1 << 21)) {
+			printk("%sCON TAB", s);
+			s = " | ";
+		}
+		if (cdc & (1 << 20)) {
+			printk("%sNO FB", s);
+			s = " | ";
+		}
+		if (cdc & (1 << 19)) {
+			printk("%sOAM CRC", s);
+			s = " | ";
+		}
+		if (cdc & (1 << 18)) {
+			printk("%sRM CRC", s);
+			s = " | ";
+		}
+		if (cdc & (1 << 17)) {
+			printk("%sRM FIFO", s);
+			s = " | ";
+		}
+		if (cdc & (1 << 16)) {
+			printk("%sRX FIFO", s);
+			s = " | ";
+		}
+		printk("]");
+	}
+
+	printk(" CDC %04x, VPEC %04x, ICC: %04x\n",
+	       cdc & 0xffff, vpec & 0xffff, icc & 0xffff);
+#endif
+}
+
+static irqreturn_t
+idt77252_interrupt(int irq, void *dev_id, struct pt_regs *ptregs)
+{
+	struct idt77252_dev *card = dev_id;
+	u32 stat;
+
+	stat = readl(SAR_REG_STAT) & 0xffff;
+	if (!stat)	/* no interrupt for us */
+		return IRQ_NONE;
+
+	if (test_and_set_bit(IDT77252_BIT_INTERRUPT, &card->flags)) {
+		printk("%s: Re-entering irq_handler()\n", card->name);
+		goto out;
+	}
+
+	writel(stat, SAR_REG_STAT);	/* reset interrupt */
+
+	if (stat & SAR_STAT_TSIF) {	/* entry written to TSQ  */
+		INTPRINTK("%s: TSIF\n", card->name);
+		card->irqstat[15]++;
+		idt77252_tx(card);
+	}
+	if (stat & SAR_STAT_TXICP) {	/* Incomplete CS-PDU has  */
+		INTPRINTK("%s: TXICP\n", card->name);
+		card->irqstat[14]++;
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+		idt77252_tx_dump(card);
+#endif
+	}
+	if (stat & SAR_STAT_TSQF) {	/* TSQ 7/8 full           */
+		INTPRINTK("%s: TSQF\n", card->name);
+		card->irqstat[12]++;
+		idt77252_tx(card);
+	}
+	if (stat & SAR_STAT_TMROF) {	/* Timer overflow         */
+		INTPRINTK("%s: TMROF\n", card->name);
+		card->irqstat[11]++;
+		idt77252_collect_stat(card);
+	}
+
+	if (stat & SAR_STAT_EPDU) {	/* Got complete CS-PDU    */
+		INTPRINTK("%s: EPDU\n", card->name);
+		card->irqstat[5]++;
+		idt77252_rx(card);
+	}
+	if (stat & SAR_STAT_RSQAF) {	/* RSQ is 7/8 full        */
+		INTPRINTK("%s: RSQAF\n", card->name);
+		card->irqstat[1]++;
+		idt77252_rx(card);
+	}
+	if (stat & SAR_STAT_RSQF) {	/* RSQ is full            */
+		INTPRINTK("%s: RSQF\n", card->name);
+		card->irqstat[6]++;
+		idt77252_rx(card);
+	}
+	if (stat & SAR_STAT_RAWCF) {	/* Raw cell received      */
+		INTPRINTK("%s: RAWCF\n", card->name);
+		card->irqstat[4]++;
+		idt77252_rx_raw(card);
+	}
+
+	if (stat & SAR_STAT_PHYI) {	/* PHY device interrupt   */
+		INTPRINTK("%s: PHYI", card->name);
+		card->irqstat[10]++;
+		if (card->atmdev->phy && card->atmdev->phy->interrupt)
+			card->atmdev->phy->interrupt(card->atmdev);
+	}
+
+	if (stat & (SAR_STAT_FBQ0A | SAR_STAT_FBQ1A |
+		    SAR_STAT_FBQ2A | SAR_STAT_FBQ3A)) {
+
+		writel(readl(SAR_REG_CFG) & ~(SAR_CFG_FBIE), SAR_REG_CFG);
+
+		INTPRINTK("%s: FBQA: %04x\n", card->name, stat);
+
+		if (stat & SAR_STAT_FBQ0A)
+			card->irqstat[2]++;
+		if (stat & SAR_STAT_FBQ1A)
+			card->irqstat[3]++;
+		if (stat & SAR_STAT_FBQ2A)
+			card->irqstat[7]++;
+		if (stat & SAR_STAT_FBQ3A)
+			card->irqstat[8]++;
+
+		schedule_work(&card->tqueue);
+	}
+
+out:
+	clear_bit(IDT77252_BIT_INTERRUPT, &card->flags);
+	return IRQ_HANDLED;
+}
+
+static void
+idt77252_softint(void *dev_id)
+{
+	struct idt77252_dev *card = dev_id;
+	u32 stat;
+	int done;
+
+	for (done = 1; ; done = 1) {
+		stat = readl(SAR_REG_STAT) >> 16;
+
+		if ((stat & 0x0f) < SAR_FBQ0_HIGH) {
+			add_rx_skb(card, 0, SAR_FB_SIZE_0, 32);
+			done = 0;
+		}
+
+		stat >>= 4;
+		if ((stat & 0x0f) < SAR_FBQ1_HIGH) {
+			add_rx_skb(card, 1, SAR_FB_SIZE_1, 32);
+			done = 0;
+		}
+
+		stat >>= 4;
+		if ((stat & 0x0f) < SAR_FBQ2_HIGH) {
+			add_rx_skb(card, 2, SAR_FB_SIZE_2, 32);
+			done = 0;
+		}
+
+		stat >>= 4;
+		if ((stat & 0x0f) < SAR_FBQ3_HIGH) {
+			add_rx_skb(card, 3, SAR_FB_SIZE_3, 32);
+			done = 0;
+		}
+
+		if (done)
+			break;
+	}
+
+	writel(readl(SAR_REG_CFG) | SAR_CFG_FBIE, SAR_REG_CFG);
+}
+
+
+static int
+open_card_oam(struct idt77252_dev *card)
+{
+	unsigned long flags;
+	unsigned long addr;
+	struct vc_map *vc;
+	int vpi, vci;
+	int index;
+	u32 rcte;
+
+	for (vpi = 0; vpi < (1 << card->vpibits); vpi++) {
+		for (vci = 3; vci < 5; vci++) {
+			index = VPCI2VC(card, vpi, vci);
+
+			vc = kmalloc(sizeof(struct vc_map), GFP_KERNEL);
+			if (!vc) {
+				printk("%s: can't alloc vc\n", card->name);
+				return -ENOMEM;
+			}
+			memset(vc, 0, sizeof(struct vc_map));
+
+			vc->index = index;
+			card->vcs[index] = vc;
+
+			flush_rx_pool(card, &vc->rcv.rx_pool);
+
+			rcte = SAR_RCTE_CONNECTOPEN |
+			       SAR_RCTE_RAWCELLINTEN |
+			       SAR_RCTE_RCQ |
+			       SAR_RCTE_FBP_1;
+
+			addr = card->rct_base + (vc->index << 2);
+			write_sram(card, addr, rcte);
+
+			spin_lock_irqsave(&card->cmd_lock, flags);
+			writel(SAR_CMD_OPEN_CONNECTION | (addr << 2),
+			       SAR_REG_CMD);
+			waitfor_idle(card);
+			spin_unlock_irqrestore(&card->cmd_lock, flags);
+		}
+	}
+
+	return 0;
+}
+
+static void
+close_card_oam(struct idt77252_dev *card)
+{
+	unsigned long flags;
+	unsigned long addr;
+	struct vc_map *vc;
+	int vpi, vci;
+	int index;
+
+	for (vpi = 0; vpi < (1 << card->vpibits); vpi++) {
+		for (vci = 3; vci < 5; vci++) {
+			index = VPCI2VC(card, vpi, vci);
+			vc = card->vcs[index];
+
+			addr = card->rct_base + vc->index * SAR_SRAM_RCT_SIZE;
+
+			spin_lock_irqsave(&card->cmd_lock, flags);
+			writel(SAR_CMD_CLOSE_CONNECTION | (addr << 2),
+			       SAR_REG_CMD);
+			waitfor_idle(card);
+			spin_unlock_irqrestore(&card->cmd_lock, flags);
+
+			if (vc->rcv.rx_pool.count) {
+				DPRINTK("%s: closing a VC "
+					"with pending rx buffers.\n",
+					card->name);
+
+				recycle_rx_pool_skb(card, &vc->rcv.rx_pool);
+			}
+		}
+	}
+}
+
+static int
+open_card_ubr0(struct idt77252_dev *card)
+{
+	struct vc_map *vc;
+
+	vc = kmalloc(sizeof(struct vc_map), GFP_KERNEL);
+	if (!vc) {
+		printk("%s: can't alloc vc\n", card->name);
+		return -ENOMEM;
+	}
+	memset(vc, 0, sizeof(struct vc_map));
+	card->vcs[0] = vc;
+	vc->class = SCHED_UBR0;
+
+	vc->scq = alloc_scq(card, vc->class);
+	if (!vc->scq) {
+		printk("%s: can't get SCQ.\n", card->name);
+		return -ENOMEM;
+	}
+
+	card->scd2vc[0] = vc;
+	vc->scd_index = 0;
+	vc->scq->scd = card->scd_base;
+
+	fill_scd(card, vc->scq, vc->class);
+
+	write_sram(card, card->tct_base + 0, TCT_UBR | card->scd_base);
+	write_sram(card, card->tct_base + 1, 0);
+	write_sram(card, card->tct_base + 2, 0);
+	write_sram(card, card->tct_base + 3, 0);
+	write_sram(card, card->tct_base + 4, 0);
+	write_sram(card, card->tct_base + 5, 0);
+	write_sram(card, card->tct_base + 6, 0);
+	write_sram(card, card->tct_base + 7, TCT_FLAG_UBR);
+
+	clear_bit(VCF_IDLE, &vc->flags);
+	writel(TCMDQ_START | 0, SAR_REG_TCMDQ);
+	return 0;
+}
+
+static int
+idt77252_dev_open(struct idt77252_dev *card)
+{
+	u32 conf;
+
+	if (!test_bit(IDT77252_BIT_INIT, &card->flags)) {
+		printk("%s: SAR not yet initialized.\n", card->name);
+		return -1;
+	}
+
+	conf = SAR_CFG_RXPTH|	/* enable receive path                  */
+	    SAR_RX_DELAY |	/* interrupt on complete PDU		*/
+	    SAR_CFG_RAWIE |	/* interrupt enable on raw cells        */
+	    SAR_CFG_RQFIE |	/* interrupt on RSQ almost full         */
+	    SAR_CFG_TMOIE |	/* interrupt on timer overflow          */
+	    SAR_CFG_FBIE |	/* interrupt on low free buffers        */
+	    SAR_CFG_TXEN |	/* transmit operation enable            */
+	    SAR_CFG_TXINT |	/* interrupt on transmit status         */
+	    SAR_CFG_TXUIE |	/* interrupt on transmit underrun       */
+	    SAR_CFG_TXSFI |	/* interrupt on TSQ almost full         */
+	    SAR_CFG_PHYIE	/* enable PHY interrupts		*/
+	    ;
+
+#ifdef CONFIG_ATM_IDT77252_RCV_ALL
+	/* Test RAW cell receive. */
+	conf |= SAR_CFG_VPECA;
+#endif
+
+	writel(readl(SAR_REG_CFG) | conf, SAR_REG_CFG);
+
+	if (open_card_oam(card)) {
+		printk("%s: Error initializing OAM.\n", card->name);
+		return -1;
+	}
+
+	if (open_card_ubr0(card)) {
+		printk("%s: Error initializing UBR0.\n", card->name);
+		return -1;
+	}
+
+	IPRINTK("%s: opened IDT77252 ABR SAR.\n", card->name);
+	return 0;
+}
+
+void
+idt77252_dev_close(struct atm_dev *dev)
+{
+	struct idt77252_dev *card = dev->dev_data;
+	u32 conf;
+
+	close_card_oam(card);
+
+	conf = SAR_CFG_RXPTH |	/* enable receive path           */
+	    SAR_RX_DELAY |	/* interrupt on complete PDU     */
+	    SAR_CFG_RAWIE |	/* interrupt enable on raw cells */
+	    SAR_CFG_RQFIE |	/* interrupt on RSQ almost full  */
+	    SAR_CFG_TMOIE |	/* interrupt on timer overflow   */
+	    SAR_CFG_FBIE |	/* interrupt on low free buffers */
+	    SAR_CFG_TXEN |	/* transmit operation enable     */
+	    SAR_CFG_TXINT |	/* interrupt on transmit status  */
+	    SAR_CFG_TXUIE |	/* interrupt on xmit underrun    */
+	    SAR_CFG_TXSFI	/* interrupt on TSQ almost full  */
+	    ;
+
+	writel(readl(SAR_REG_CFG) & ~(conf), SAR_REG_CFG);
+
+	DIPRINTK("%s: closed IDT77252 ABR SAR.\n", card->name);
+}
+
+
+/*****************************************************************************/
+/*                                                                           */
+/* Initialisation and Deinitialization of IDT77252                           */
+/*                                                                           */
+/*****************************************************************************/
+
+
+static void
+deinit_card(struct idt77252_dev *card)
+{
+	struct sk_buff *skb;
+	int i, j;
+
+	if (!test_bit(IDT77252_BIT_INIT, &card->flags)) {
+		printk("%s: SAR not yet initialized.\n", card->name);
+		return;
+	}
+	DIPRINTK("idt77252: deinitialize card %u\n", card->index);
+
+	writel(0, SAR_REG_CFG);
+
+	if (card->atmdev)
+		atm_dev_deregister(card->atmdev);
+
+	for (i = 0; i < 4; i++) {
+		for (j = 0; j < FBQ_SIZE; j++) {
+			skb = card->sbpool[i].skb[j];
+			if (skb) {
+				pci_unmap_single(card->pcidev,
+						 IDT77252_PRV_PADDR(skb),
+						 skb->end - skb->data,
+						 PCI_DMA_FROMDEVICE);
+				card->sbpool[i].skb[j] = NULL;
+				dev_kfree_skb(skb);
+			}
+		}
+	}
+
+	vfree(card->soft_tst);
+
+	vfree(card->scd2vc);
+
+	vfree(card->vcs);
+
+	if (card->raw_cell_hnd) {
+		pci_free_consistent(card->pcidev, 2 * sizeof(u32),
+				    card->raw_cell_hnd, card->raw_cell_paddr);
+	}
+
+	if (card->rsq.base) {
+		DIPRINTK("%s: Release RSQ ...\n", card->name);
+		deinit_rsq(card);
+	}
+
+	if (card->tsq.base) {
+		DIPRINTK("%s: Release TSQ ...\n", card->name);
+		deinit_tsq(card);
+	}
+
+	DIPRINTK("idt77252: Release IRQ.\n");
+	free_irq(card->pcidev->irq, card);
+
+	for (i = 0; i < 4; i++) {
+		if (card->fbq[i])
+			iounmap(card->fbq[i]);
+	}
+
+	if (card->membase)
+		iounmap(card->membase);
+
+	clear_bit(IDT77252_BIT_INIT, &card->flags);
+	DIPRINTK("%s: Card deinitialized.\n", card->name);
+}
+
+
+static int __devinit
+init_sram(struct idt77252_dev *card)
+{
+	int i;
+
+	for (i = 0; i < card->sramsize; i += 4)
+		write_sram(card, (i >> 2), 0);
+
+	/* set SRAM layout for THIS card */
+	if (card->sramsize == (512 * 1024)) {
+		card->tct_base = SAR_SRAM_TCT_128_BASE;
+		card->tct_size = (SAR_SRAM_TCT_128_TOP - card->tct_base + 1)
+		    / SAR_SRAM_TCT_SIZE;
+		card->rct_base = SAR_SRAM_RCT_128_BASE;
+		card->rct_size = (SAR_SRAM_RCT_128_TOP - card->rct_base + 1)
+		    / SAR_SRAM_RCT_SIZE;
+		card->rt_base = SAR_SRAM_RT_128_BASE;
+		card->scd_base = SAR_SRAM_SCD_128_BASE;
+		card->scd_size = (SAR_SRAM_SCD_128_TOP - card->scd_base + 1)
+		    / SAR_SRAM_SCD_SIZE;
+		card->tst[0] = SAR_SRAM_TST1_128_BASE;
+		card->tst[1] = SAR_SRAM_TST2_128_BASE;
+		card->tst_size = SAR_SRAM_TST1_128_TOP - card->tst[0] + 1;
+		card->abrst_base = SAR_SRAM_ABRSTD_128_BASE;
+		card->abrst_size = SAR_ABRSTD_SIZE_8K;
+		card->fifo_base = SAR_SRAM_FIFO_128_BASE;
+		card->fifo_size = SAR_RXFD_SIZE_32K;
+	} else {
+		card->tct_base = SAR_SRAM_TCT_32_BASE;
+		card->tct_size = (SAR_SRAM_TCT_32_TOP - card->tct_base + 1)
+		    / SAR_SRAM_TCT_SIZE;
+		card->rct_base = SAR_SRAM_RCT_32_BASE;
+		card->rct_size = (SAR_SRAM_RCT_32_TOP - card->rct_base + 1)
+		    / SAR_SRAM_RCT_SIZE;
+		card->rt_base = SAR_SRAM_RT_32_BASE;
+		card->scd_base = SAR_SRAM_SCD_32_BASE;
+		card->scd_size = (SAR_SRAM_SCD_32_TOP - card->scd_base + 1)
+		    / SAR_SRAM_SCD_SIZE;
+		card->tst[0] = SAR_SRAM_TST1_32_BASE;
+		card->tst[1] = SAR_SRAM_TST2_32_BASE;
+		card->tst_size = (SAR_SRAM_TST1_32_TOP - card->tst[0] + 1);
+		card->abrst_base = SAR_SRAM_ABRSTD_32_BASE;
+		card->abrst_size = SAR_ABRSTD_SIZE_1K;
+		card->fifo_base = SAR_SRAM_FIFO_32_BASE;
+		card->fifo_size = SAR_RXFD_SIZE_4K;
+	}
+
+	/* Initialize TCT */
+	for (i = 0; i < card->tct_size; i++) {
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 0, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 1, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 2, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 3, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 4, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 5, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 6, 0);
+		write_sram(card, i * SAR_SRAM_TCT_SIZE + 7, 0);
+	}
+
+	/* Initialize RCT */
+	for (i = 0; i < card->rct_size; i++) {
+		write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE,
+				    (u32) SAR_RCTE_RAWCELLINTEN);
+		write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE + 1,
+				    (u32) 0);
+		write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE + 2,
+				    (u32) 0);
+		write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE + 3,
+				    (u32) 0xffffffff);
+	}
+
+	writel((SAR_FBQ0_LOW << 28) | 0x00000000 | 0x00000000 |
+	       (SAR_FB_SIZE_0 / 48), SAR_REG_FBQS0);
+	writel((SAR_FBQ1_LOW << 28) | 0x00000000 | 0x00000000 |
+	       (SAR_FB_SIZE_1 / 48), SAR_REG_FBQS1);
+	writel((SAR_FBQ2_LOW << 28) | 0x00000000 | 0x00000000 |
+	       (SAR_FB_SIZE_2 / 48), SAR_REG_FBQS2);
+	writel((SAR_FBQ3_LOW << 28) | 0x00000000 | 0x00000000 |
+	       (SAR_FB_SIZE_3 / 48), SAR_REG_FBQS3);
+
+	/* Initialize rate table  */
+	for (i = 0; i < 256; i++) {
+		write_sram(card, card->rt_base + i, log_to_rate[i]);
+	}
+
+	for (i = 0; i < 128; i++) {
+		unsigned int tmp;
+
+		tmp  = rate_to_log[(i << 2) + 0] << 0;
+		tmp |= rate_to_log[(i << 2) + 1] << 8;
+		tmp |= rate_to_log[(i << 2) + 2] << 16;
+		tmp |= rate_to_log[(i << 2) + 3] << 24;
+		write_sram(card, card->rt_base + 256 + i, tmp);
+	}
+
+#if 0 /* Fill RDF and AIR tables. */
+	for (i = 0; i < 128; i++) {
+		unsigned int tmp;
+
+		tmp = RDF[0][(i << 1) + 0] << 16;
+		tmp |= RDF[0][(i << 1) + 1] << 0;
+		write_sram(card, card->rt_base + 512 + i, tmp);
+	}
+
+	for (i = 0; i < 128; i++) {
+		unsigned int tmp;
+
+		tmp = AIR[0][(i << 1) + 0] << 16;
+		tmp |= AIR[0][(i << 1) + 1] << 0;
+		write_sram(card, card->rt_base + 640 + i, tmp);
+	}
+#endif
+
+	IPRINTK("%s: initialize rate table ...\n", card->name);
+	writel(card->rt_base << 2, SAR_REG_RTBL);
+
+	/* Initialize TSTs */
+	IPRINTK("%s: initialize TST ...\n", card->name);
+	card->tst_free = card->tst_size - 2;	/* last two are jumps */
+
+	for (i = card->tst[0]; i < card->tst[0] + card->tst_size - 2; i++)
+		write_sram(card, i, TSTE_OPC_VAR);
+	write_sram(card, i++, TSTE_OPC_JMP | (card->tst[0] << 2));
+	idt77252_sram_write_errors = 1;
+	write_sram(card, i++, TSTE_OPC_JMP | (card->tst[1] << 2));
+	idt77252_sram_write_errors = 0;
+	for (i = card->tst[1]; i < card->tst[1] + card->tst_size - 2; i++)
+		write_sram(card, i, TSTE_OPC_VAR);
+	write_sram(card, i++, TSTE_OPC_JMP | (card->tst[1] << 2));
+	idt77252_sram_write_errors = 1;
+	write_sram(card, i++, TSTE_OPC_JMP | (card->tst[0] << 2));
+	idt77252_sram_write_errors = 0;
+
+	card->tst_index = 0;
+	writel(card->tst[0] << 2, SAR_REG_TSTB);
+
+	/* Initialize ABRSTD and Receive FIFO */
+	IPRINTK("%s: initialize ABRSTD ...\n", card->name);
+	writel(card->abrst_size | (card->abrst_base << 2),
+	       SAR_REG_ABRSTD);
+
+	IPRINTK("%s: initialize receive fifo ...\n", card->name);
+	writel(card->fifo_size | (card->fifo_base << 2),
+	       SAR_REG_RXFD);
+
+	IPRINTK("%s: SRAM initialization complete.\n", card->name);
+	return 0;
+}
+
+static int __devinit
+init_card(struct atm_dev *dev)
+{
+	struct idt77252_dev *card = dev->dev_data;
+	struct pci_dev *pcidev = card->pcidev;
+	unsigned long tmpl, modl;
+	unsigned int linkrate, rsvdcr;
+	unsigned int tst_entries;
+	struct net_device *tmp;
+	char tname[10];
+
+	u32 size;
+	u_char pci_byte;
+	u32 conf;
+	int i, k;
+
+	if (test_bit(IDT77252_BIT_INIT, &card->flags)) {
+		printk("Error: SAR already initialized.\n");
+		return -1;
+	}
+
+/*****************************************************************/
+/*   P C I   C O N F I G U R A T I O N                           */
+/*****************************************************************/
+
+	/* Set PCI Retry-Timeout and TRDY timeout */
+	IPRINTK("%s: Checking PCI retries.\n", card->name);
+	if (pci_read_config_byte(pcidev, 0x40, &pci_byte) != 0) {
+		printk("%s: can't read PCI retry timeout.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	if (pci_byte != 0) {
+		IPRINTK("%s: PCI retry timeout: %d, set to 0.\n",
+			card->name, pci_byte);
+		if (pci_write_config_byte(pcidev, 0x40, 0) != 0) {
+			printk("%s: can't set PCI retry timeout.\n",
+			       card->name);
+			deinit_card(card);
+			return -1;
+		}
+	}
+	IPRINTK("%s: Checking PCI TRDY.\n", card->name);
+	if (pci_read_config_byte(pcidev, 0x41, &pci_byte) != 0) {
+		printk("%s: can't read PCI TRDY timeout.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	if (pci_byte != 0) {
+		IPRINTK("%s: PCI TRDY timeout: %d, set to 0.\n",
+		        card->name, pci_byte);
+		if (pci_write_config_byte(pcidev, 0x41, 0) != 0) {
+			printk("%s: can't set PCI TRDY timeout.\n", card->name);
+			deinit_card(card);
+			return -1;
+		}
+	}
+	/* Reset Timer register */
+	if (readl(SAR_REG_STAT) & SAR_STAT_TMROF) {
+		printk("%s: resetting timer overflow.\n", card->name);
+		writel(SAR_STAT_TMROF, SAR_REG_STAT);
+	}
+	IPRINTK("%s: Request IRQ ... ", card->name);
+	if (request_irq(pcidev->irq, idt77252_interrupt, SA_INTERRUPT|SA_SHIRQ,
+			card->name, card) != 0) {
+		printk("%s: can't allocate IRQ.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	IPRINTK("got %d.\n", pcidev->irq);
+
+/*****************************************************************/
+/*   C H E C K   A N D   I N I T   S R A M                       */
+/*****************************************************************/
+
+	IPRINTK("%s: Initializing SRAM\n", card->name);
+
+	/* preset size of connecton table, so that init_sram() knows about it */
+	conf =	SAR_CFG_TX_FIFO_SIZE_9 |	/* Use maximum fifo size */
+		SAR_CFG_RXSTQ_SIZE_8k |		/* Receive Status Queue is 8k */
+		SAR_CFG_IDLE_CLP |		/* Set CLP on idle cells */
+#ifndef CONFIG_ATM_IDT77252_SEND_IDLE
+		SAR_CFG_NO_IDLE |		/* Do not send idle cells */
+#endif
+		0;
+
+	if (card->sramsize == (512 * 1024))
+		conf |= SAR_CFG_CNTBL_1k;
+	else
+		conf |= SAR_CFG_CNTBL_512;
+
+	switch (vpibits) {
+	case 0:
+		conf |= SAR_CFG_VPVCS_0;
+		break;
+	default:
+	case 1:
+		conf |= SAR_CFG_VPVCS_1;
+		break;
+	case 2:
+		conf |= SAR_CFG_VPVCS_2;
+		break;
+	case 8:
+		conf |= SAR_CFG_VPVCS_8;
+		break;
+	}
+
+	writel(readl(SAR_REG_CFG) | conf, SAR_REG_CFG);
+
+	if (init_sram(card) < 0)
+		return -1;
+
+/********************************************************************/
+/*  A L L O C   R A M   A N D   S E T   V A R I O U S   T H I N G S */
+/********************************************************************/
+	/* Initialize TSQ */
+	if (0 != init_tsq(card)) {
+		deinit_card(card);
+		return -1;
+	}
+	/* Initialize RSQ */
+	if (0 != init_rsq(card)) {
+		deinit_card(card);
+		return -1;
+	}
+
+	card->vpibits = vpibits;
+	if (card->sramsize == (512 * 1024)) {
+		card->vcibits = 10 - card->vpibits;
+	} else {
+		card->vcibits = 9 - card->vpibits;
+	}
+
+	card->vcimask = 0;
+	for (k = 0, i = 1; k < card->vcibits; k++) {
+		card->vcimask |= i;
+		i <<= 1;
+	}
+
+	IPRINTK("%s: Setting VPI/VCI mask to zero.\n", card->name);
+	writel(0, SAR_REG_VPM);
+
+	/* Little Endian Order   */
+	writel(0, SAR_REG_GP);
+
+	/* Initialize RAW Cell Handle Register  */
+	card->raw_cell_hnd = pci_alloc_consistent(card->pcidev, 2 * sizeof(u32),
+						  &card->raw_cell_paddr);
+	if (!card->raw_cell_hnd) {
+		printk("%s: memory allocation failure.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	memset(card->raw_cell_hnd, 0, 2 * sizeof(u32));
+	writel(card->raw_cell_paddr, SAR_REG_RAWHND);
+	IPRINTK("%s: raw cell handle is at 0x%p.\n", card->name,
+		card->raw_cell_hnd);
+
+	size = sizeof(struct vc_map *) * card->tct_size;
+	IPRINTK("%s: allocate %d byte for VC map.\n", card->name, size);
+	if (NULL == (card->vcs = vmalloc(size))) {
+		printk("%s: memory allocation failure.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	memset(card->vcs, 0, size);
+
+	size = sizeof(struct vc_map *) * card->scd_size;
+	IPRINTK("%s: allocate %d byte for SCD to VC mapping.\n",
+	        card->name, size);
+	if (NULL == (card->scd2vc = vmalloc(size))) {
+		printk("%s: memory allocation failure.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	memset(card->scd2vc, 0, size);
+
+	size = sizeof(struct tst_info) * (card->tst_size - 2);
+	IPRINTK("%s: allocate %d byte for TST to VC mapping.\n",
+		card->name, size);
+	if (NULL == (card->soft_tst = vmalloc(size))) {
+		printk("%s: memory allocation failure.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	for (i = 0; i < card->tst_size - 2; i++) {
+		card->soft_tst[i].tste = TSTE_OPC_VAR;
+		card->soft_tst[i].vc = NULL;
+	}
+
+	if (dev->phy == NULL) {
+		printk("%s: No LT device defined.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	if (dev->phy->ioctl == NULL) {
+		printk("%s: LT had no IOCTL funtion defined.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+
+#ifdef	CONFIG_ATM_IDT77252_USE_SUNI
+	/*
+	 * this is a jhs hack to get around special functionality in the
+	 * phy driver for the atecom hardware; the functionality doesn't
+	 * exist in the linux atm suni driver
+	 *
+	 * it isn't the right way to do things, but as the guy from NIST
+	 * said, talking about their measurement of the fine structure
+	 * constant, "it's good enough for government work."
+	 */
+	linkrate = 149760000;
+#endif
+
+	card->link_pcr = (linkrate / 8 / 53);
+	printk("%s: Linkrate on ATM line : %u bit/s, %u cell/s.\n",
+	       card->name, linkrate, card->link_pcr);
+
+#ifdef CONFIG_ATM_IDT77252_SEND_IDLE
+	card->utopia_pcr = card->link_pcr;
+#else
+	card->utopia_pcr = (160000000 / 8 / 54);
+#endif
+
+	rsvdcr = 0;
+	if (card->utopia_pcr > card->link_pcr)
+		rsvdcr = card->utopia_pcr - card->link_pcr;
+
+	tmpl = (unsigned long) rsvdcr * ((unsigned long) card->tst_size - 2);
+	modl = tmpl % (unsigned long)card->utopia_pcr;
+	tst_entries = (int) (tmpl / (unsigned long)card->utopia_pcr);
+	if (modl)
+		tst_entries++;
+	card->tst_free -= tst_entries;
+	fill_tst(card, NULL, tst_entries, TSTE_OPC_NULL);
+
+#ifdef HAVE_EEPROM
+	idt77252_eeprom_init(card);
+	printk("%s: EEPROM: %02x:", card->name,
+		idt77252_eeprom_read_status(card));
+
+	for (i = 0; i < 0x80; i++) {
+		printk(" %02x", 
+		idt77252_eeprom_read_byte(card, i)
+		);
+	}
+	printk("\n");
+#endif /* HAVE_EEPROM */
+
+	/*
+	 * XXX: <hack>
+	 */
+	sprintf(tname, "eth%d", card->index);
+	tmp = dev_get_by_name(tname);	/* jhs: was "tmp = dev_get(tname);" */
+	if (tmp) {
+		memcpy(card->atmdev->esi, tmp->dev_addr, 6);
+
+		printk("%s: ESI %02x:%02x:%02x:%02x:%02x:%02x\n",
+		       card->name, card->atmdev->esi[0], card->atmdev->esi[1],
+		       card->atmdev->esi[2], card->atmdev->esi[3],
+		       card->atmdev->esi[4], card->atmdev->esi[5]);
+	}
+	/*
+	 * XXX: </hack>
+	 */
+
+	/* Set Maximum Deficit Count for now. */
+	writel(0xffff, SAR_REG_MDFCT);
+
+	set_bit(IDT77252_BIT_INIT, &card->flags);
+
+	XPRINTK("%s: IDT77252 ABR SAR initialization complete.\n", card->name);
+	return 0;
+}
+
+
+/*****************************************************************************/
+/*                                                                           */
+/* Probing of IDT77252 ABR SAR                                               */
+/*                                                                           */
+/*****************************************************************************/
+
+
+static int __devinit
+idt77252_preset(struct idt77252_dev *card)
+{
+	u16 pci_command;
+
+/*****************************************************************/
+/*   P C I   C O N F I G U R A T I O N                           */
+/*****************************************************************/
+
+	XPRINTK("%s: Enable PCI master and memory access for SAR.\n",
+		card->name);
+	if (pci_read_config_word(card->pcidev, PCI_COMMAND, &pci_command)) {
+		printk("%s: can't read PCI_COMMAND.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+	if (!(pci_command & PCI_COMMAND_IO)) {
+		printk("%s: PCI_COMMAND: %04x (???)\n",
+		       card->name, pci_command);
+		deinit_card(card);
+		return (-1);
+	}
+	pci_command |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+	if (pci_write_config_word(card->pcidev, PCI_COMMAND, pci_command)) {
+		printk("%s: can't write PCI_COMMAND.\n", card->name);
+		deinit_card(card);
+		return -1;
+	}
+/*****************************************************************/
+/*   G E N E R I C   R E S E T                                   */
+/*****************************************************************/
+
+	/* Software reset */
+	writel(SAR_CFG_SWRST, SAR_REG_CFG);
+	mdelay(1);
+	writel(0, SAR_REG_CFG);
+
+	IPRINTK("%s: Software resetted.\n", card->name);
+	return 0;
+}
+
+
+static unsigned long __devinit
+probe_sram(struct idt77252_dev *card)
+{
+	u32 data, addr;
+
+	writel(0, SAR_REG_DR0);
+	writel(SAR_CMD_WRITE_SRAM | (0 << 2), SAR_REG_CMD);
+
+	for (addr = 0x4000; addr < 0x80000; addr += 0x4000) {
+		writel(0xdeadbeef, SAR_REG_DR0);
+		writel(SAR_CMD_WRITE_SRAM | (addr << 2), SAR_REG_CMD);
+
+		writel(SAR_CMD_READ_SRAM | (0 << 2), SAR_REG_CMD);
+		data = readl(SAR_REG_DR0);
+
+		if (data != 0)
+			break;
+	}
+
+	return addr * sizeof(u32);
+}
+
+static int __devinit
+idt77252_init_one(struct pci_dev *pcidev, const struct pci_device_id *id)
+{
+	static struct idt77252_dev **last = &idt77252_chain;
+	static int index = 0;
+
+	unsigned long membase, srambase;
+	struct idt77252_dev *card;
+	struct atm_dev *dev;
+	ushort revision = 0;
+	int i, err;
+
+
+	if ((err = pci_enable_device(pcidev))) {
+		printk("idt77252: can't enable PCI device at %s\n", pci_name(pcidev));
+		return err;
+	}
+
+	if (pci_read_config_word(pcidev, PCI_REVISION_ID, &revision)) {
+		printk("idt77252-%d: can't read PCI_REVISION_ID\n", index);
+		err = -ENODEV;
+		goto err_out_disable_pdev;
+	}
+
+	card = kmalloc(sizeof(struct idt77252_dev), GFP_KERNEL);
+	if (!card) {
+		printk("idt77252-%d: can't allocate private data\n", index);
+		err = -ENOMEM;
+		goto err_out_disable_pdev;
+	}
+	memset(card, 0, sizeof(struct idt77252_dev));
+
+	card->revision = revision;
+	card->index = index;
+	card->pcidev = pcidev;
+	sprintf(card->name, "idt77252-%d", card->index);
+
+	INIT_WORK(&card->tqueue, idt77252_softint, (void *)card);
+
+	membase = pci_resource_start(pcidev, 1);
+	srambase = pci_resource_start(pcidev, 2);
+
+	init_MUTEX(&card->mutex);
+	spin_lock_init(&card->cmd_lock);
+	spin_lock_init(&card->tst_lock);
+
+	init_timer(&card->tst_timer);
+	card->tst_timer.data = (unsigned long)card;
+	card->tst_timer.function = tst_timer;
+
+	/* Do the I/O remapping... */
+	card->membase = ioremap(membase, 1024);
+	if (!card->membase) {
+		printk("%s: can't ioremap() membase\n", card->name);
+		err = -EIO;
+		goto err_out_free_card;
+	}
+
+	if (idt77252_preset(card)) {
+		printk("%s: preset failed\n", card->name);
+		err = -EIO;
+		goto err_out_iounmap;
+	}
+
+	dev = atm_dev_register("idt77252", &idt77252_ops, -1, NULL);
+	if (!dev) {
+		printk("%s: can't register atm device\n", card->name);
+		err = -EIO;
+		goto err_out_iounmap;
+	}
+	dev->dev_data = card;
+	card->atmdev = dev;
+
+#ifdef	CONFIG_ATM_IDT77252_USE_SUNI
+	suni_init(dev);
+	if (!dev->phy) {
+		printk("%s: can't init SUNI\n", card->name);
+		err = -EIO;
+		goto err_out_deinit_card;
+	}
+#endif	/* CONFIG_ATM_IDT77252_USE_SUNI */
+
+	card->sramsize = probe_sram(card);
+
+	for (i = 0; i < 4; i++) {
+		card->fbq[i] = ioremap(srambase | 0x200000 | (i << 18), 4);
+		if (!card->fbq[i]) {
+			printk("%s: can't ioremap() FBQ%d\n", card->name, i);
+			err = -EIO;
+			goto err_out_deinit_card;
+		}
+	}
+
+	printk("%s: ABR SAR (Rev %c): MEM %08lx SRAM %08lx [%u KB]\n",
+	       card->name, ((revision > 1) && (revision < 25)) ?
+	       'A' + revision - 1 : '?', membase, srambase,
+	       card->sramsize / 1024);
+
+	if (init_card(dev)) {
+		printk("%s: init_card failed\n", card->name);
+		err = -EIO;
+		goto err_out_deinit_card;
+	}
+
+	dev->ci_range.vpi_bits = card->vpibits;
+	dev->ci_range.vci_bits = card->vcibits;
+	dev->link_rate = card->link_pcr;
+
+	if (dev->phy->start)
+		dev->phy->start(dev);
+
+	if (idt77252_dev_open(card)) {
+		printk("%s: dev_open failed\n", card->name);
+		err = -EIO;
+		goto err_out_stop;
+	}
+
+	*last = card;
+	last = &card->next;
+	index++;
+
+	return 0;
+
+err_out_stop:
+	if (dev->phy->stop)
+		dev->phy->stop(dev);
+
+err_out_deinit_card:
+	deinit_card(card);
+
+err_out_iounmap:
+	iounmap(card->membase);
+
+err_out_free_card:
+	kfree(card);
+
+err_out_disable_pdev:
+	pci_disable_device(pcidev);
+	return err;
+}
+
+static struct pci_device_id idt77252_pci_tbl[] =
+{
+	{ PCI_VENDOR_ID_IDT, PCI_DEVICE_ID_IDT_IDT77252,
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, idt77252_pci_tbl);
+
+static struct pci_driver idt77252_driver = {
+	.name		= "idt77252",
+	.id_table	= idt77252_pci_tbl,
+	.probe		= idt77252_init_one,
+};
+
+static int __init idt77252_init(void)
+{
+	struct sk_buff *skb;
+
+	printk("%s: at %p\n", __FUNCTION__, idt77252_init);
+
+	if (sizeof(skb->cb) < sizeof(struct atm_skb_data) +
+			      sizeof(struct idt77252_skb_prv)) {
+		printk(KERN_ERR "%s: skb->cb is too small (%lu < %lu)\n",
+		       __FUNCTION__, (unsigned long) sizeof(skb->cb),
+		       (unsigned long) sizeof(struct atm_skb_data) +
+				       sizeof(struct idt77252_skb_prv));
+		return -EIO;
+	}
+
+	return pci_register_driver(&idt77252_driver);
+}
+
+static void __exit idt77252_exit(void)
+{
+	struct idt77252_dev *card;
+	struct atm_dev *dev;
+
+	pci_unregister_driver(&idt77252_driver);
+
+	while (idt77252_chain) {
+		card = idt77252_chain;
+		dev = card->atmdev;
+		idt77252_chain = card->next;
+
+		if (dev->phy->stop)
+			dev->phy->stop(dev);
+		deinit_card(card);
+		pci_disable_device(card->pcidev);
+		kfree(card);
+	}
+
+	DIPRINTK("idt77252: finished cleanup-module().\n");
+}
+
+module_init(idt77252_init);
+module_exit(idt77252_exit);
+
+MODULE_LICENSE("GPL");
+
+module_param(vpibits, uint, 0);
+MODULE_PARM_DESC(vpibits, "number of VPI bits supported (0, 1, or 2)");
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+module_param(debug, ulong, 0644);
+MODULE_PARM_DESC(debug,   "debug bitmap, see drivers/atm/idt77252.h");
+#endif
+
+MODULE_AUTHOR("Eddie C. Dost <ecd@atecom.com>");
+MODULE_DESCRIPTION("IDT77252 ABR SAR Driver");
diff --git a/drivers/atm/idt77252.h b/drivers/atm/idt77252.h
new file mode 100644
index 0000000..544b397
--- /dev/null
+++ b/drivers/atm/idt77252.h
@@ -0,0 +1,819 @@
+/******************************************************************* 
+ * ident "$Id: idt77252.h,v 1.2 2001/11/11 08:13:54 ecd Exp $"
+ *
+ * $Author: ecd $
+ * $Date: 2001/11/11 08:13:54 $
+ *
+ * Copyright (c) 2000 ATecoM GmbH 
+ *
+ * The author may be reached at ecd@atecom.com.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR   IMPLIED
+ * WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ * NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT,  INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ * USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * 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.
+ *
+ *******************************************************************/
+
+#ifndef _IDT77252_H
+#define _IDT77252_H 1
+
+
+#include <linux/ptrace.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+
+
+/*****************************************************************************/
+/*                                                                           */
+/* Makros                                                                    */
+/*                                                                           */
+/*****************************************************************************/
+#define VPCI2VC(card, vpi, vci) \
+        (((vpi) << card->vcibits) | ((vci) & card->vcimask))
+
+/*****************************************************************************/
+/*                                                                           */
+/*   DEBUGGING definitions                                                   */
+/*                                                                           */
+/*****************************************************************************/
+
+#define DBG_RAW_CELL	0x00000400
+#define DBG_TINY	0x00000200
+#define DBG_GENERAL     0x00000100
+#define DBG_XGENERAL    0x00000080
+#define DBG_INIT        0x00000040
+#define DBG_DEINIT      0x00000020
+#define DBG_INTERRUPT   0x00000010
+#define DBG_OPEN_CONN   0x00000008
+#define DBG_CLOSE_CONN  0x00000004
+#define DBG_RX_DATA     0x00000002
+#define DBG_TX_DATA     0x00000001
+
+#ifdef CONFIG_ATM_IDT77252_DEBUG
+
+#define CPRINTK(args...)   do { if (debug & DBG_CLOSE_CONN) printk(args); } while(0)
+#define OPRINTK(args...)   do { if (debug & DBG_OPEN_CONN)  printk(args); } while(0)
+#define IPRINTK(args...)   do { if (debug & DBG_INIT)       printk(args); } while(0)
+#define INTPRINTK(args...) do { if (debug & DBG_INTERRUPT)  printk(args); } while(0)
+#define DIPRINTK(args...)  do { if (debug & DBG_DEINIT)     printk(args); } while(0)
+#define TXPRINTK(args...)  do { if (debug & DBG_TX_DATA)    printk(args); } while(0)
+#define RXPRINTK(args...)  do { if (debug & DBG_RX_DATA)    printk(args); } while(0)
+#define XPRINTK(args...)   do { if (debug & DBG_XGENERAL)   printk(args); } while(0)
+#define DPRINTK(args...)   do { if (debug & DBG_GENERAL)    printk(args); } while(0)
+#define NPRINTK(args...)   do { if (debug & DBG_TINY)	    printk(args); } while(0)
+#define RPRINTK(args...)   do { if (debug & DBG_RAW_CELL)   printk(args); } while(0)
+
+#else
+
+#define CPRINTK(args...)	do { } while(0)
+#define OPRINTK(args...)	do { } while(0)
+#define IPRINTK(args...)	do { } while(0)
+#define INTPRINTK(args...)	do { } while(0)
+#define DIPRINTK(args...)	do { } while(0)
+#define TXPRINTK(args...)	do { } while(0)
+#define RXPRINTK(args...)	do { } while(0)
+#define XPRINTK(args...)	do { } while(0)
+#define DPRINTK(args...)	do { } while(0)
+#define NPRINTK(args...)	do { } while(0)
+#define RPRINTK(args...)	do { } while(0)
+
+#endif
+
+#define SCHED_UBR0		0
+#define SCHED_UBR		1
+#define SCHED_VBR		2
+#define SCHED_ABR		3
+#define SCHED_CBR		4
+
+#define SCQFULL_TIMEOUT		HZ
+
+/*****************************************************************************/
+/*                                                                           */
+/*   Free Buffer Queue Layout                                                */
+/*                                                                           */
+/*****************************************************************************/
+#define SAR_FB_SIZE_0		(2048 - 256)
+#define SAR_FB_SIZE_1		(4096 - 256)
+#define SAR_FB_SIZE_2		(8192 - 256)
+#define SAR_FB_SIZE_3		(16384 - 256)
+
+#define SAR_FBQ0_LOW		4
+#define SAR_FBQ0_HIGH		8
+#define SAR_FBQ1_LOW		2
+#define SAR_FBQ1_HIGH		4
+#define SAR_FBQ2_LOW		1
+#define SAR_FBQ2_HIGH		2
+#define SAR_FBQ3_LOW		1
+#define SAR_FBQ3_HIGH		2
+
+#if 0
+#define SAR_TST_RESERVED	44	/* Num TST reserved for UBR/ABR/VBR */
+#else
+#define SAR_TST_RESERVED	0	/* Num TST reserved for UBR/ABR/VBR */
+#endif
+
+#define TCT_CBR			0x00000000
+#define TCT_UBR			0x00000000
+#define TCT_VBR			0x40000000
+#define TCT_ABR			0x80000000
+#define TCT_TYPE		0xc0000000
+
+#define TCT_RR			0x20000000
+#define TCT_LMCR		0x08000000
+#define TCT_SCD_MASK		0x0007ffff
+
+#define TCT_TSIF		0x00004000
+#define TCT_HALT		0x80000000
+#define TCT_IDLE		0x40000000
+#define TCT_FLAG_UBR		0x80000000
+
+/*****************************************************************************/
+/*                                                                           */
+/*   Structure describing an IDT77252                                        */
+/*                                                                           */
+/*****************************************************************************/
+
+struct scqe
+{
+	u32		word_1;
+	u32		word_2;
+	u32		word_3;
+	u32		word_4;
+};
+
+#define SCQ_ENTRIES	64
+#define SCQ_SIZE	(SCQ_ENTRIES * sizeof(struct scqe))
+#define SCQ_MASK	(SCQ_SIZE - 1)
+
+struct scq_info
+{
+	struct scqe		*base;
+	struct scqe		*next;
+	struct scqe		*last;
+	dma_addr_t		paddr;
+	spinlock_t		lock;
+	atomic_t		used;
+	unsigned long		trans_start;
+        unsigned long		scd;
+	spinlock_t		skblock;
+	struct sk_buff_head	transmit;
+	struct sk_buff_head	pending;
+};
+
+struct rx_pool {
+	struct sk_buff		*first;
+	struct sk_buff		**last;
+	unsigned int		len;
+	unsigned int		count;
+};
+
+struct aal1 {
+	unsigned int		total;
+	unsigned int		count;
+	struct sk_buff		*data;
+	unsigned char		sequence;
+};
+
+struct rate_estimator {
+	struct timer_list	timer;
+	unsigned int		interval;
+	unsigned int		ewma_log;
+	u64			cells;
+	u64			last_cells;
+	long			avcps;
+	u32			cps;
+	u32			maxcps;
+};
+
+struct vc_map {
+	unsigned int		index;
+	unsigned long		flags;
+#define VCF_TX		0
+#define VCF_RX		1
+#define VCF_IDLE	2
+#define VCF_RSV		3
+	unsigned int		class;
+	u8			init_er;
+	u8			lacr;
+	u8			max_er;
+	unsigned int		ntste;
+	spinlock_t		lock;
+	struct atm_vcc		*tx_vcc;
+	struct atm_vcc		*rx_vcc;
+	struct idt77252_dev	*card;
+	struct scq_info		*scq;		/* To keep track of the SCQ */
+	struct rate_estimator	*estimator;
+	int			scd_index;
+	union {
+		struct rx_pool	rx_pool;
+		struct aal1	aal1;
+	} rcv;
+};
+
+/*****************************************************************************/
+/*                                                                           */
+/*   RCTE - Receive Connection Table Entry                                   */
+/*                                                                           */
+/*****************************************************************************/
+
+struct rct_entry
+{
+	u32		word_1;
+	u32		buffer_handle;
+	u32		dma_address;
+	u32		aal5_crc32;
+};
+
+/*****************************************************************************/
+/*                                                                           */
+/*   RSQ - Receive Status Queue                                              */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_RSQE_VALID      0x80000000
+#define SAR_RSQE_IDLE       0x40000000
+#define SAR_RSQE_BUF_MASK   0x00030000
+#define SAR_RSQE_BUF_ASGN   0x00008000
+#define SAR_RSQE_NZGFC      0x00004000
+#define SAR_RSQE_EPDU       0x00002000
+#define SAR_RSQE_BUF_CONT   0x00001000
+#define SAR_RSQE_EFCIE      0x00000800
+#define SAR_RSQE_CLP        0x00000400
+#define SAR_RSQE_CRC        0x00000200
+#define SAR_RSQE_CELLCNT    0x000001FF
+
+
+#define RSQSIZE            8192
+#define RSQ_NUM_ENTRIES    (RSQSIZE / 16)
+#define RSQ_ALIGNMENT      8192
+
+struct rsq_entry {
+	u32			word_1;
+	u32			word_2;
+	u32			word_3;
+	u32			word_4;
+};
+
+struct rsq_info {
+	struct rsq_entry	*base;
+	struct rsq_entry	*next;
+	struct rsq_entry	*last;
+	dma_addr_t		paddr;
+};
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   TSQ - Transmit Status Queue                                             */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_TSQE_INVALID         0x80000000
+#define SAR_TSQE_TIMESTAMP       0x00FFFFFF
+#define SAR_TSQE_TYPE		 0x60000000
+#define SAR_TSQE_TYPE_TIMER      0x00000000
+#define SAR_TSQE_TYPE_TSR        0x20000000
+#define SAR_TSQE_TYPE_IDLE       0x40000000
+#define SAR_TSQE_TYPE_TBD_COMP   0x60000000
+
+#define SAR_TSQE_TAG(stat)	(((stat) >> 24) & 0x1f)
+
+#define TSQSIZE            8192
+#define TSQ_NUM_ENTRIES    1024
+#define TSQ_ALIGNMENT      8192
+
+struct tsq_entry
+{
+	u32			word_1;
+	u32			word_2;
+};
+
+struct tsq_info
+{
+	struct tsq_entry	*base;
+	struct tsq_entry	*next;
+	struct tsq_entry	*last;
+	dma_addr_t		paddr;
+};
+
+struct tst_info
+{
+	struct vc_map		*vc;
+	u32			tste;
+};
+
+#define TSTE_MASK		0x601fffff
+
+#define TSTE_OPC_MASK		0x60000000
+#define TSTE_OPC_NULL		0x00000000
+#define TSTE_OPC_CBR		0x20000000
+#define TSTE_OPC_VAR		0x40000000
+#define TSTE_OPC_JMP		0x60000000
+
+#define TSTE_PUSH_IDLE		0x01000000
+#define TSTE_PUSH_ACTIVE	0x02000000
+
+#define TST_SWITCH_DONE		0
+#define TST_SWITCH_PENDING	1
+#define TST_SWITCH_WAIT		2
+
+#define FBQ_SHIFT		9
+#define FBQ_SIZE		(1 << FBQ_SHIFT)
+#define FBQ_MASK		(FBQ_SIZE - 1)
+
+struct sb_pool
+{
+	unsigned int		index;
+	struct sk_buff		*skb[FBQ_SIZE];
+};
+
+#define POOL_HANDLE(queue, index)	(((queue + 1) << 16) | (index))
+#define POOL_QUEUE(handle)		(((handle) >> 16) - 1)
+#define POOL_INDEX(handle)		((handle) & 0xffff)
+
+struct idt77252_dev
+{
+        struct tsq_info		tsq;		/* Transmit Status Queue */
+        struct rsq_info		rsq;		/* Receive Status Queue */
+
+	struct pci_dev		*pcidev;	/* PCI handle (desriptor) */
+	struct atm_dev		*atmdev;	/* ATM device desriptor */
+
+	void __iomem		*membase;	/* SAR's memory base address */
+	unsigned long		srambase;	/* SAR's sram  base address */
+	void __iomem		*fbq[4];	/* FBQ fill addresses */
+
+	struct semaphore	mutex;
+	spinlock_t		cmd_lock;	/* for r/w utility/sram */
+
+	unsigned long		softstat;
+	unsigned long		flags;		/* see blow */
+
+	struct work_struct	tqueue;
+
+	unsigned long		tct_base;	/* TCT base address in SRAM */
+        unsigned long		rct_base;	/* RCT base address in SRAM */
+        unsigned long		rt_base;	/* Rate Table base in SRAM */
+        unsigned long		scd_base;	/* SCD base address in SRAM */
+        unsigned long		tst[2];		/* TST base address in SRAM */
+	unsigned long		abrst_base;	/* ABRST base address in SRAM */
+        unsigned long		fifo_base;	/* RX FIFO base in SRAM */
+
+	unsigned long		irqstat[16];
+
+	unsigned int		sramsize;	/* SAR's sram size */
+
+        unsigned int		tct_size;	/* total TCT entries */
+        unsigned int		rct_size;	/* total RCT entries */
+        unsigned int		scd_size;	/* length of SCD */
+        unsigned int		tst_size;	/* total TST entries */
+        unsigned int		tst_free;	/* free TSTEs in TST */
+        unsigned int		abrst_size;	/* size of ABRST in words */
+        unsigned int		fifo_size;	/* size of RX FIFO in words */
+
+        unsigned int		vpibits;	/* Bits used for VPI index */
+        unsigned int		vcibits;	/* Bits used for VCI index */
+        unsigned int		vcimask;	/* Mask for VCI index */
+
+	unsigned int		utopia_pcr;	/* Utopia Itf's Cell Rate */
+	unsigned int		link_pcr;	/* PHY's Peek Cell Rate */
+
+	struct vc_map		**vcs;		/* Open Connections */
+	struct vc_map		**scd2vc;	/* SCD to Connection map */
+
+	struct tst_info		*soft_tst;	/* TST to Connection map */
+	unsigned int		tst_index;	/* Current TST in use */
+	struct timer_list	tst_timer;
+	spinlock_t		tst_lock;
+	unsigned long		tst_state;
+
+	struct sb_pool		sbpool[4];	/* Pool of RX skbuffs */
+	struct sk_buff		*raw_cell_head; /* Pointer to raw cell queue */
+	u32			*raw_cell_hnd;	/* Pointer to RCQ handle */
+	dma_addr_t		raw_cell_paddr;
+
+	int			index;		/* SAR's ID */
+	int			revision;	/* chip revision */
+
+	char			name[16];	/* Device name */
+
+	struct idt77252_dev	*next;
+};
+
+
+/* definition for flag field above */
+#define IDT77252_BIT_INIT		1
+#define IDT77252_BIT_INTERRUPT		2
+
+
+#define ATM_CELL_PAYLOAD         48
+
+#define FREEBUF_ALIGNMENT        16
+
+/*****************************************************************************/
+/*                                                                           */
+/* Makros                                                                    */
+/*                                                                           */
+/*****************************************************************************/
+#define ALIGN_ADDRESS(addr, alignment) \
+        ((((u32)(addr)) + (((u32)(alignment))-1)) & ~(((u32)(alignment)) - 1))
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   ABR SAR Network operation Register                                      */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_REG_DR0	(card->membase + 0x00)
+#define SAR_REG_DR1	(card->membase + 0x04)
+#define SAR_REG_DR2	(card->membase + 0x08)
+#define SAR_REG_DR3	(card->membase + 0x0C)
+#define SAR_REG_CMD	(card->membase + 0x10)
+#define SAR_REG_CFG	(card->membase + 0x14)
+#define SAR_REG_STAT	(card->membase + 0x18)
+#define SAR_REG_RSQB	(card->membase + 0x1C)
+#define SAR_REG_RSQT	(card->membase + 0x20)
+#define SAR_REG_RSQH	(card->membase + 0x24)
+#define SAR_REG_CDC	(card->membase + 0x28)
+#define SAR_REG_VPEC	(card->membase + 0x2C)
+#define SAR_REG_ICC	(card->membase + 0x30)
+#define SAR_REG_RAWCT	(card->membase + 0x34)
+#define SAR_REG_TMR	(card->membase + 0x38)
+#define SAR_REG_TSTB	(card->membase + 0x3C)
+#define SAR_REG_TSQB	(card->membase + 0x40)
+#define SAR_REG_TSQT	(card->membase + 0x44)
+#define SAR_REG_TSQH	(card->membase + 0x48)
+#define SAR_REG_GP	(card->membase + 0x4C)
+#define SAR_REG_VPM	(card->membase + 0x50)
+#define SAR_REG_RXFD	(card->membase + 0x54)
+#define SAR_REG_RXFT	(card->membase + 0x58)
+#define SAR_REG_RXFH	(card->membase + 0x5C)
+#define SAR_REG_RAWHND	(card->membase + 0x60)
+#define SAR_REG_RXSTAT	(card->membase + 0x64)
+#define SAR_REG_ABRSTD	(card->membase + 0x68)
+#define SAR_REG_ABRRQ	(card->membase + 0x6C)
+#define SAR_REG_VBRRQ	(card->membase + 0x70)
+#define SAR_REG_RTBL	(card->membase + 0x74)
+#define SAR_REG_MDFCT	(card->membase + 0x78)
+#define SAR_REG_TXSTAT	(card->membase + 0x7C)
+#define SAR_REG_TCMDQ	(card->membase + 0x80)
+#define SAR_REG_IRCP	(card->membase + 0x84)
+#define SAR_REG_FBQP0	(card->membase + 0x88)
+#define SAR_REG_FBQP1	(card->membase + 0x8C)
+#define SAR_REG_FBQP2	(card->membase + 0x90)
+#define SAR_REG_FBQP3	(card->membase + 0x94)
+#define SAR_REG_FBQS0	(card->membase + 0x98)
+#define SAR_REG_FBQS1	(card->membase + 0x9C)
+#define SAR_REG_FBQS2	(card->membase + 0xA0)
+#define SAR_REG_FBQS3	(card->membase + 0xA4)
+#define SAR_REG_FBQWP0	(card->membase + 0xA8)
+#define SAR_REG_FBQWP1	(card->membase + 0xAC)
+#define SAR_REG_FBQWP2	(card->membase + 0xB0)
+#define SAR_REG_FBQWP3	(card->membase + 0xB4)
+#define SAR_REG_NOW	(card->membase + 0xB8)
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   Commands                                                                */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_CMD_NO_OPERATION         0x00000000
+#define SAR_CMD_OPENCLOSE_CONNECTION 0x20000000
+#define SAR_CMD_WRITE_SRAM           0x40000000
+#define SAR_CMD_READ_SRAM            0x50000000
+#define SAR_CMD_READ_UTILITY         0x80000000
+#define SAR_CMD_WRITE_UTILITY        0x90000000
+
+#define SAR_CMD_OPEN_CONNECTION     (SAR_CMD_OPENCLOSE_CONNECTION | 0x00080000)
+#define SAR_CMD_CLOSE_CONNECTION     SAR_CMD_OPENCLOSE_CONNECTION
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   Configuration Register bits                                             */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_CFG_SWRST          0x80000000  /* Software reset                 */
+#define SAR_CFG_LOOP           0x40000000  /* Internal Loopback              */
+#define SAR_CFG_RXPTH          0x20000000  /* Receive Path Enable            */
+#define SAR_CFG_IDLE_CLP       0x10000000  /* SAR set CLP Bits of Null Cells */
+#define SAR_CFG_TX_FIFO_SIZE_1 0x04000000  /* TX FIFO Size = 1 cell          */
+#define SAR_CFG_TX_FIFO_SIZE_2 0x08000000  /* TX FIFO Size = 2 cells         */
+#define SAR_CFG_TX_FIFO_SIZE_4 0x0C000000  /* TX FIFO Size = 4 cells         */
+#define SAR_CFG_TX_FIFO_SIZE_9 0x00000000  /* TX FIFO Size = 9 cells (full)  */
+#define SAR_CFG_NO_IDLE        0x02000000  /* SAR sends no Null Cells        */
+#define SAR_CFG_RSVD1          0x01000000  /* Reserved                       */
+#define SAR_CFG_RXSTQ_SIZE_2k  0x00000000  /* RX Stat Queue Size = 2048 byte */
+#define SAR_CFG_RXSTQ_SIZE_4k  0x00400000  /* RX Stat Queue Size = 4096 byte */
+#define SAR_CFG_RXSTQ_SIZE_8k  0x00800000  /* RX Stat Queue Size = 8192 byte */
+#define SAR_CFG_RXSTQ_SIZE_R   0x00C00000  /* RX Stat Queue Size = reserved  */
+#define SAR_CFG_ICAPT          0x00200000  /* accept Invalid Cells           */
+#define SAR_CFG_IGGFC          0x00100000  /* Ignore GFC                     */
+#define SAR_CFG_VPVCS_0        0x00000000  /* VPI/VCI Select bit range       */
+#define SAR_CFG_VPVCS_1        0x00040000  /* VPI/VCI Select bit range       */
+#define SAR_CFG_VPVCS_2        0x00080000  /* VPI/VCI Select bit range       */
+#define SAR_CFG_VPVCS_8        0x000C0000  /* VPI/VCI Select bit range       */
+#define SAR_CFG_CNTBL_1k       0x00000000  /* Connection Table Size          */
+#define SAR_CFG_CNTBL_4k       0x00010000  /* Connection Table Size          */
+#define SAR_CFG_CNTBL_16k      0x00020000  /* Connection Table Size          */
+#define SAR_CFG_CNTBL_512      0x00030000  /* Connection Table Size          */
+#define SAR_CFG_VPECA          0x00008000  /* VPI/VCI Error Cell Accept      */
+#define SAR_CFG_RXINT_NOINT    0x00000000  /* No Interrupt on PDU received   */
+#define SAR_CFG_RXINT_NODELAY  0x00001000  /* Interrupt without delay to host*/
+#define SAR_CFG_RXINT_256US    0x00002000  /* Interrupt with delay 256 usec  */
+#define SAR_CFG_RXINT_505US    0x00003000  /* Interrupt with delay 505 usec  */
+#define SAR_CFG_RXINT_742US    0x00004000  /* Interrupt with delay 742 usec  */
+#define SAR_CFG_RAWIE          0x00000800  /* Raw Cell Queue Interrupt Enable*/
+#define SAR_CFG_RQFIE          0x00000400  /* RSQ Almost Full Int Enable     */
+#define SAR_CFG_RSVD2          0x00000200  /* Reserved                       */
+#define SAR_CFG_CACHE          0x00000100  /* DMA on Cache Line Boundary     */
+#define SAR_CFG_TMOIE          0x00000080  /* Timer Roll Over Int Enable     */
+#define SAR_CFG_FBIE           0x00000040  /* Free Buffer Queue Int Enable   */
+#define SAR_CFG_TXEN           0x00000020  /* Transmit Operation Enable      */
+#define SAR_CFG_TXINT          0x00000010  /* Transmit status Int Enable     */
+#define SAR_CFG_TXUIE          0x00000008  /* Transmit underrun Int Enable   */
+#define SAR_CFG_UMODE          0x00000004  /* Utopia Mode Select             */
+#define SAR_CFG_TXSFI          0x00000002  /* Transmit status Full Int Enable*/
+#define SAR_CFG_PHYIE          0x00000001  /* PHY Interrupt Enable           */
+
+#define SAR_CFG_TX_FIFO_SIZE_MASK 0x0C000000  /* TX FIFO Size Mask           */
+#define SAR_CFG_RXSTQSIZE_MASK 0x00C00000
+#define SAR_CFG_CNTBL_MASK     0x00030000
+#define SAR_CFG_RXINT_MASK     0x00007000
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   Status Register bits                                                    */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_STAT_FRAC_3     0xF0000000 /* Fraction of Free Buffer Queue 3 */
+#define SAR_STAT_FRAC_2     0x0F000000 /* Fraction of Free Buffer Queue 2 */
+#define SAR_STAT_FRAC_1     0x00F00000 /* Fraction of Free Buffer Queue 1 */
+#define SAR_STAT_FRAC_0     0x000F0000 /* Fraction of Free Buffer Queue 0 */
+#define SAR_STAT_TSIF       0x00008000 /* Transmit Status Indicator       */
+#define SAR_STAT_TXICP      0x00004000 /* Transmit Status Indicator       */
+#define SAR_STAT_RSVD1      0x00002000 /* Reserved                        */
+#define SAR_STAT_TSQF       0x00001000 /* Transmit Status Queue full      */
+#define SAR_STAT_TMROF      0x00000800 /* Timer overflow                  */
+#define SAR_STAT_PHYI       0x00000400 /* PHY device Interrupt flag       */
+#define SAR_STAT_CMDBZ      0x00000200 /* ABR SAR Comand Busy Flag        */
+#define SAR_STAT_FBQ3A      0x00000100 /* Free Buffer Queue 3 Attention   */
+#define SAR_STAT_FBQ2A      0x00000080 /* Free Buffer Queue 2 Attention   */
+#define SAR_STAT_RSQF       0x00000040 /* Receive Status Queue full       */
+#define SAR_STAT_EPDU       0x00000020 /* End Of PDU Flag                 */
+#define SAR_STAT_RAWCF      0x00000010 /* Raw Cell Flag                   */ 
+#define SAR_STAT_FBQ1A      0x00000008 /* Free Buffer Queue 1 Attention   */
+#define SAR_STAT_FBQ0A      0x00000004 /* Free Buffer Queue 0 Attention   */
+#define SAR_STAT_RSQAF      0x00000002 /* Receive Status Queue almost full*/  
+#define SAR_STAT_RSVD2      0x00000001 /* Reserved                        */
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   General Purpose Register bits                                           */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_GP_TXNCC_MASK   0xff000000  /* Transmit Negative Credit Count   */
+#define SAR_GP_EEDI         0x00010000  /* EEPROM Data In                   */
+#define SAR_GP_BIGE         0x00008000  /* Big Endian Operation             */
+#define SAR_GP_RM_NORMAL    0x00000000  /* Normal handling of RM cells      */
+#define SAR_GP_RM_TO_RCQ    0x00002000  /* put RM cells into Raw Cell Queue */
+#define SAR_GP_RM_RSVD      0x00004000  /* Reserved                         */
+#define SAR_GP_RM_INHIBIT   0x00006000  /* Inhibit update of Connection tab */
+#define SAR_GP_PHY_RESET    0x00000008  /* PHY Reset                        */
+#define SAR_GP_EESCLK	    0x00000004	/* EEPROM SCLK			    */
+#define SAR_GP_EECS	    0x00000002	/* EEPROM Chip Select		    */
+#define SAR_GP_EEDO	    0x00000001	/* EEPROM Data Out		    */
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   SAR local SRAM layout for 128k work SRAM                                */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_SRAM_SCD_SIZE        12
+#define SAR_SRAM_TCT_SIZE         8
+#define SAR_SRAM_RCT_SIZE         4
+
+#define SAR_SRAM_TCT_128_BASE    0x00000
+#define SAR_SRAM_TCT_128_TOP     0x01fff
+#define SAR_SRAM_RCT_128_BASE    0x02000
+#define SAR_SRAM_RCT_128_TOP     0x02fff
+#define SAR_SRAM_FB0_128_BASE    0x03000
+#define SAR_SRAM_FB0_128_TOP     0x033ff
+#define SAR_SRAM_FB1_128_BASE    0x03400
+#define SAR_SRAM_FB1_128_TOP     0x037ff
+#define SAR_SRAM_FB2_128_BASE    0x03800
+#define SAR_SRAM_FB2_128_TOP     0x03bff
+#define SAR_SRAM_FB3_128_BASE    0x03c00
+#define SAR_SRAM_FB3_128_TOP     0x03fff
+#define SAR_SRAM_SCD_128_BASE    0x04000
+#define SAR_SRAM_SCD_128_TOP     0x07fff
+#define SAR_SRAM_TST1_128_BASE   0x08000
+#define SAR_SRAM_TST1_128_TOP    0x0bfff
+#define SAR_SRAM_TST2_128_BASE   0x0c000
+#define SAR_SRAM_TST2_128_TOP    0x0ffff
+#define SAR_SRAM_ABRSTD_128_BASE 0x10000
+#define SAR_SRAM_ABRSTD_128_TOP  0x13fff
+#define SAR_SRAM_RT_128_BASE     0x14000
+#define SAR_SRAM_RT_128_TOP      0x15fff
+
+#define SAR_SRAM_FIFO_128_BASE   0x18000
+#define SAR_SRAM_FIFO_128_TOP    0x1ffff
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   SAR local SRAM layout for 32k work SRAM                                 */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_SRAM_TCT_32_BASE     0x00000
+#define SAR_SRAM_TCT_32_TOP      0x00fff
+#define SAR_SRAM_RCT_32_BASE     0x01000
+#define SAR_SRAM_RCT_32_TOP      0x017ff
+#define SAR_SRAM_FB0_32_BASE     0x01800
+#define SAR_SRAM_FB0_32_TOP      0x01bff
+#define SAR_SRAM_FB1_32_BASE     0x01c00
+#define SAR_SRAM_FB1_32_TOP      0x01fff
+#define SAR_SRAM_FB2_32_BASE     0x02000
+#define SAR_SRAM_FB2_32_TOP      0x023ff
+#define SAR_SRAM_FB3_32_BASE     0x02400
+#define SAR_SRAM_FB3_32_TOP      0x027ff
+#define SAR_SRAM_SCD_32_BASE     0x02800
+#define SAR_SRAM_SCD_32_TOP      0x03fff
+#define SAR_SRAM_TST1_32_BASE    0x04000
+#define SAR_SRAM_TST1_32_TOP     0x04fff
+#define SAR_SRAM_TST2_32_BASE    0x05000
+#define SAR_SRAM_TST2_32_TOP     0x05fff
+#define SAR_SRAM_ABRSTD_32_BASE  0x06000
+#define SAR_SRAM_ABRSTD_32_TOP   0x067ff
+#define SAR_SRAM_RT_32_BASE      0x06800
+#define SAR_SRAM_RT_32_TOP       0x06fff
+#define SAR_SRAM_FIFO_32_BASE    0x07000
+#define SAR_SRAM_FIFO_32_TOP     0x07fff
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   TSR - Transmit Status Request                                           */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_TSR_TYPE_TSR  0x80000000
+#define SAR_TSR_TYPE_TBD  0x00000000
+#define SAR_TSR_TSIF      0x20000000
+#define SAR_TSR_TAG_MASK  0x01F00000
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   TBD - Transmit Buffer Descriptor                                        */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_TBD_EPDU      0x40000000
+#define SAR_TBD_TSIF      0x20000000
+#define SAR_TBD_OAM       0x10000000
+#define SAR_TBD_AAL0      0x00000000
+#define SAR_TBD_AAL34     0x04000000
+#define SAR_TBD_AAL5      0x08000000
+#define SAR_TBD_GTSI      0x02000000
+#define SAR_TBD_TAG_MASK  0x01F00000
+
+#define SAR_TBD_VPI_MASK  0x0FF00000
+#define SAR_TBD_VCI_MASK  0x000FFFF0
+#define SAR_TBD_VC_MASK   (SAR_TBD_VPI_MASK | SAR_TBD_VCI_MASK)
+
+#define SAR_TBD_VPI_SHIFT 20
+#define SAR_TBD_VCI_SHIFT 4
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   RXFD - Receive FIFO Descriptor                                          */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_RXFD_SIZE_MASK     0x0F000000
+#define SAR_RXFD_SIZE_512      0x00000000  /* 512 words                      */
+#define SAR_RXFD_SIZE_1K       0x01000000  /* 1k words                       */
+#define SAR_RXFD_SIZE_2K       0x02000000  /* 2k words                       */
+#define SAR_RXFD_SIZE_4K       0x03000000  /* 4k words                       */
+#define SAR_RXFD_SIZE_8K       0x04000000  /* 8k words                       */
+#define SAR_RXFD_SIZE_16K      0x05000000  /* 16k words                      */
+#define SAR_RXFD_SIZE_32K      0x06000000  /* 32k words                      */
+#define SAR_RXFD_SIZE_64K      0x07000000  /* 64k words                      */
+#define SAR_RXFD_SIZE_128K     0x08000000  /* 128k words                     */
+#define SAR_RXFD_SIZE_256K     0x09000000  /* 256k words                     */
+#define SAR_RXFD_ADDR_MASK     0x001ffc00
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   ABRSTD - ABR + VBR Schedule Tables                                      */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_ABRSTD_SIZE_MASK   0x07000000
+#define SAR_ABRSTD_SIZE_512    0x00000000  /* 512 words                      */
+#define SAR_ABRSTD_SIZE_1K     0x01000000  /* 1k words                       */
+#define SAR_ABRSTD_SIZE_2K     0x02000000  /* 2k words                       */
+#define SAR_ABRSTD_SIZE_4K     0x03000000  /* 4k words                       */
+#define SAR_ABRSTD_SIZE_8K     0x04000000  /* 8k words                       */
+#define SAR_ABRSTD_SIZE_16K    0x05000000  /* 16k words                      */
+#define SAR_ABRSTD_ADDR_MASK   0x001ffc00
+
+
+/*****************************************************************************/
+/*                                                                           */
+/*   RCTE - Receive Connection Table Entry                                   */
+/*                                                                           */
+/*****************************************************************************/
+
+#define SAR_RCTE_IL_MASK       0xE0000000  /* inactivity limit               */
+#define SAR_RCTE_IC_MASK       0x1C000000  /* inactivity count               */
+#define SAR_RCTE_RSVD          0x02000000  /* reserved                       */
+#define SAR_RCTE_LCD           0x01000000  /* last cell data                 */
+#define SAR_RCTE_CI_VC         0x00800000  /* EFCI in previous cell of VC    */
+#define SAR_RCTE_FBP_01        0x00000000  /* 1. cell->FBQ0, others->FBQ1    */
+#define SAR_RCTE_FBP_1         0x00200000  /* use FBQ 1 for all cells        */
+#define SAR_RCTE_FBP_2         0x00400000  /* use FBQ 2 for all cells        */
+#define SAR_RCTE_FBP_3         0x00600000  /* use FBQ 3 for all cells        */
+#define SAR_RCTE_NZ_GFC        0x00100000  /* non zero GFC in all cell of VC */
+#define SAR_RCTE_CONNECTOPEN   0x00080000  /* VC is open                     */
+#define SAR_RCTE_AAL_MASK      0x00070000  /* mask for AAL type field s.b.   */
+#define SAR_RCTE_RAWCELLINTEN  0x00008000  /* raw cell interrupt enable      */
+#define SAR_RCTE_RXCONCELLADDR 0x00004000  /* RX constant cell address       */
+#define SAR_RCTE_BUFFSTAT_MASK 0x00003000  /* buffer status                  */
+#define SAR_RCTE_EFCI          0x00000800  /* EFCI Congestion flag           */
+#define SAR_RCTE_CLP           0x00000400  /* Cell Loss Priority flag        */
+#define SAR_RCTE_CRC           0x00000200  /* Recieved CRC Error             */
+#define SAR_RCTE_CELLCNT_MASK  0x000001FF  /* cell Count                     */
+
+#define SAR_RCTE_AAL0          0x00000000  /* AAL types for ALL field        */
+#define SAR_RCTE_AAL34         0x00010000
+#define SAR_RCTE_AAL5          0x00020000
+#define SAR_RCTE_RCQ           0x00030000
+#define SAR_RCTE_OAM           0x00040000
+
+#define TCMDQ_START		0x01000000
+#define TCMDQ_LACR		0x02000000
+#define TCMDQ_START_LACR	0x03000000
+#define TCMDQ_INIT_ER		0x04000000
+#define TCMDQ_HALT		0x05000000
+
+
+struct idt77252_skb_prv {
+	struct scqe	tbd;	/* Transmit Buffer Descriptor */
+	dma_addr_t	paddr;	/* DMA handle */
+	u32		pool;	/* sb_pool handle */
+};
+
+#define IDT77252_PRV_TBD(skb)	\
+	(((struct idt77252_skb_prv *)(ATM_SKB(skb)+1))->tbd)
+#define IDT77252_PRV_PADDR(skb)	\
+	(((struct idt77252_skb_prv *)(ATM_SKB(skb)+1))->paddr)
+#define IDT77252_PRV_POOL(skb)	\
+	(((struct idt77252_skb_prv *)(ATM_SKB(skb)+1))->pool)
+
+/*****************************************************************************/
+/*                                                                           */
+/*   PCI related items                                                       */
+/*                                                                           */
+/*****************************************************************************/
+
+#ifndef PCI_VENDOR_ID_IDT
+#define PCI_VENDOR_ID_IDT 0x111D
+#endif /* PCI_VENDOR_ID_IDT */
+
+#ifndef PCI_DEVICE_ID_IDT_IDT77252
+#define PCI_DEVICE_ID_IDT_IDT77252 0x0003
+#endif /* PCI_DEVICE_ID_IDT_IDT772052 */
+
+
+#endif /* !(_IDT77252_H) */
diff --git a/drivers/atm/idt77252_tables.h b/drivers/atm/idt77252_tables.h
new file mode 100644
index 0000000..b6c8ee5
--- /dev/null
+++ b/drivers/atm/idt77252_tables.h
@@ -0,0 +1,780 @@
+/* Do not edit, automatically generated by `./genrtbl'.
+ *
+ * Cell Line Rate: 353207.55 (155520000 bps)
+ */
+
+static unsigned int log_to_rate[] =
+{
+/* 000 */ 0x8d022e27, /* cps =     10.02, nrm =  3, interval = 35264.00 */
+/* 001 */ 0x8d362e11, /* cps =     10.42, nrm =  3, interval = 33856.00 */
+/* 002 */ 0x8d6e2bf8, /* cps =     10.86, nrm =  3, interval = 32512.00 */
+/* 003 */ 0x8da82bcf, /* cps =     11.31, nrm =  3, interval = 31200.00 */
+/* 004 */ 0x8de42ba8, /* cps =     11.78, nrm =  3, interval = 29952.00 */
+/* 005 */ 0x8e242b82, /* cps =     12.28, nrm =  3, interval = 28736.00 */
+/* 006 */ 0x8e662b5e, /* cps =     12.80, nrm =  3, interval = 27584.00 */
+/* 007 */ 0x8eaa2b3c, /* cps =     13.33, nrm =  3, interval = 26496.00 */
+/* 008 */ 0x8ef22b1a, /* cps =     13.89, nrm =  3, interval = 25408.00 */
+/* 009 */ 0x8f3e2afa, /* cps =     14.48, nrm =  3, interval = 24384.00 */
+/* 010 */ 0x8f8a2adc, /* cps =     15.08, nrm =  3, interval = 23424.00 */
+/* 011 */ 0x8fdc2abe, /* cps =     15.72, nrm =  3, interval = 22464.00 */
+/* 012 */ 0x90182aa2, /* cps =     16.38, nrm =  3, interval = 21568.00 */
+/* 013 */ 0x90422a87, /* cps =     17.03, nrm =  3, interval = 20704.00 */
+/* 014 */ 0x90702a6d, /* cps =     17.75, nrm =  3, interval = 19872.00 */
+/* 015 */ 0x90a02a54, /* cps =     18.50, nrm =  3, interval = 19072.00 */
+/* 016 */ 0x90d22a3c, /* cps =     19.28, nrm =  3, interval = 18304.00 */
+/* 017 */ 0x91062a25, /* cps =     20.09, nrm =  3, interval = 17568.00 */
+/* 018 */ 0x913c2a0f, /* cps =     20.94, nrm =  3, interval = 16864.00 */
+/* 019 */ 0x917427f3, /* cps =     21.81, nrm =  3, interval = 16176.00 */
+/* 020 */ 0x91b027ca, /* cps =     22.75, nrm =  3, interval = 15520.00 */
+/* 021 */ 0x91ec27a3, /* cps =     23.69, nrm =  3, interval = 14896.00 */
+/* 022 */ 0x922c277e, /* cps =     24.69, nrm =  3, interval = 14304.00 */
+/* 023 */ 0x926e275a, /* cps =     25.72, nrm =  3, interval = 13728.00 */
+/* 024 */ 0x92b42737, /* cps =     26.81, nrm =  3, interval = 13168.00 */
+/* 025 */ 0x92fc2716, /* cps =     27.94, nrm =  3, interval = 12640.00 */
+/* 026 */ 0x934626f6, /* cps =     29.09, nrm =  3, interval = 12128.00 */
+/* 027 */ 0x939426d8, /* cps =     30.31, nrm =  3, interval = 11648.00 */
+/* 028 */ 0x93e426bb, /* cps =     31.56, nrm =  3, interval = 11184.00 */
+/* 029 */ 0x941e269e, /* cps =     32.94, nrm =  3, interval = 10720.00 */
+/* 030 */ 0x944a2683, /* cps =     34.31, nrm =  3, interval = 10288.00 */
+/* 031 */ 0x9476266a, /* cps =     35.69, nrm =  3, interval =  9888.00 */
+/* 032 */ 0x94a62651, /* cps =     37.19, nrm =  3, interval =  9488.00 */
+/* 033 */ 0x94d82639, /* cps =     38.75, nrm =  3, interval =  9104.00 */
+/* 034 */ 0x950c6622, /* cps =     40.38, nrm =  4, interval =  8736.00 */
+/* 035 */ 0x9544660c, /* cps =     42.12, nrm =  4, interval =  8384.00 */
+/* 036 */ 0x957c63ee, /* cps =     43.88, nrm =  4, interval =  8048.00 */
+/* 037 */ 0x95b663c6, /* cps =     45.69, nrm =  4, interval =  7728.00 */
+/* 038 */ 0x95f4639f, /* cps =     47.62, nrm =  4, interval =  7416.00 */
+/* 039 */ 0x96346379, /* cps =     49.62, nrm =  4, interval =  7112.00 */
+/* 040 */ 0x96766356, /* cps =     51.69, nrm =  4, interval =  6832.00 */
+/* 041 */ 0x96bc6333, /* cps =     53.88, nrm =  4, interval =  6552.00 */
+/* 042 */ 0x97046312, /* cps =     56.12, nrm =  4, interval =  6288.00 */
+/* 043 */ 0x974e62f3, /* cps =     58.44, nrm =  4, interval =  6040.00 */
+/* 044 */ 0x979e62d4, /* cps =     60.94, nrm =  4, interval =  5792.00 */
+/* 045 */ 0x97f062b7, /* cps =     63.50, nrm =  4, interval =  5560.00 */
+/* 046 */ 0x9822629b, /* cps =     66.12, nrm =  4, interval =  5336.00 */
+/* 047 */ 0x984e6280, /* cps =     68.88, nrm =  4, interval =  5120.00 */
+/* 048 */ 0x987e6266, /* cps =     71.88, nrm =  4, interval =  4912.00 */
+/* 049 */ 0x98ac624e, /* cps =     74.75, nrm =  4, interval =  4720.00 */
+/* 050 */ 0x98e06236, /* cps =     78.00, nrm =  4, interval =  4528.00 */
+/* 051 */ 0x9914a21f, /* cps =     81.25, nrm =  8, interval =  4344.00 */
+/* 052 */ 0x994aa209, /* cps =     84.62, nrm =  8, interval =  4168.00 */
+/* 053 */ 0x99829fe9, /* cps =     88.12, nrm =  8, interval =  4004.00 */
+/* 054 */ 0x99be9fc1, /* cps =     91.88, nrm =  8, interval =  3844.00 */
+/* 055 */ 0x99fc9f9a, /* cps =     95.75, nrm =  8, interval =  3688.00 */
+/* 056 */ 0x9a3c9f75, /* cps =     99.75, nrm =  8, interval =  3540.00 */
+/* 057 */ 0x9a809f51, /* cps =    104.00, nrm =  8, interval =  3396.00 */
+/* 058 */ 0x9ac49f2f, /* cps =    108.25, nrm =  8, interval =  3260.00 */
+/* 059 */ 0x9b0e9f0e, /* cps =    112.88, nrm =  8, interval =  3128.00 */
+/* 060 */ 0x9b589eef, /* cps =    117.50, nrm =  8, interval =  3004.00 */
+/* 061 */ 0x9ba69ed1, /* cps =    122.38, nrm =  8, interval =  2884.00 */
+/* 062 */ 0x9bf89eb4, /* cps =    127.50, nrm =  8, interval =  2768.00 */
+/* 063 */ 0x9c269e98, /* cps =    132.75, nrm =  8, interval =  2656.00 */
+/* 064 */ 0x9c549e7d, /* cps =    138.50, nrm =  8, interval =  2548.00 */
+/* 065 */ 0x9c849e63, /* cps =    144.50, nrm =  8, interval =  2444.00 */
+/* 066 */ 0x9cb29e4b, /* cps =    150.25, nrm =  8, interval =  2348.00 */
+/* 067 */ 0x9ce69e33, /* cps =    156.75, nrm =  8, interval =  2252.00 */
+/* 068 */ 0x9d1cde1c, /* cps =    163.50, nrm = 16, interval =  2160.00 */
+/* 069 */ 0x9d50de07, /* cps =    170.00, nrm = 16, interval =  2076.00 */
+/* 070 */ 0x9d8adbe4, /* cps =    177.25, nrm = 16, interval =  1992.00 */
+/* 071 */ 0x9dc4dbbc, /* cps =    184.50, nrm = 16, interval =  1912.00 */
+/* 072 */ 0x9e02db96, /* cps =    192.25, nrm = 16, interval =  1836.00 */
+/* 073 */ 0x9e42db71, /* cps =    200.25, nrm = 16, interval =  1762.00 */
+/* 074 */ 0x9e86db4d, /* cps =    208.75, nrm = 16, interval =  1690.00 */
+/* 075 */ 0x9ecedb2b, /* cps =    217.75, nrm = 16, interval =  1622.00 */
+/* 076 */ 0x9f16db0a, /* cps =    226.75, nrm = 16, interval =  1556.00 */
+/* 077 */ 0x9f62daeb, /* cps =    236.25, nrm = 16, interval =  1494.00 */
+/* 078 */ 0x9fb2dacd, /* cps =    246.25, nrm = 16, interval =  1434.00 */
+/* 079 */ 0xa002dab0, /* cps =    256.50, nrm = 16, interval =  1376.00 */
+/* 080 */ 0xa02eda94, /* cps =    267.50, nrm = 16, interval =  1320.00 */
+/* 081 */ 0xa05ada7a, /* cps =    278.50, nrm = 16, interval =  1268.00 */
+/* 082 */ 0xa088da60, /* cps =    290.00, nrm = 16, interval =  1216.00 */
+/* 083 */ 0xa0b8da48, /* cps =    302.00, nrm = 16, interval =  1168.00 */
+/* 084 */ 0xa0ecda30, /* cps =    315.00, nrm = 16, interval =  1120.00 */
+/* 085 */ 0xa1211a1a, /* cps =    328.00, nrm = 32, interval =  1076.00 */
+/* 086 */ 0xa1591a04, /* cps =    342.00, nrm = 32, interval =  1032.00 */
+/* 087 */ 0xa19117df, /* cps =    356.00, nrm = 32, interval =   991.00 */
+/* 088 */ 0xa1cd17b7, /* cps =    371.00, nrm = 32, interval =   951.00 */
+/* 089 */ 0xa20b1791, /* cps =    386.50, nrm = 32, interval =   913.00 */
+/* 090 */ 0xa24d176c, /* cps =    403.00, nrm = 32, interval =   876.00 */
+/* 091 */ 0xa28f1749, /* cps =    419.50, nrm = 32, interval =   841.00 */
+/* 092 */ 0xa2d71727, /* cps =    437.50, nrm = 32, interval =   807.00 */
+/* 093 */ 0xa31f1707, /* cps =    455.50, nrm = 32, interval =   775.00 */
+/* 094 */ 0xa36d16e7, /* cps =    475.00, nrm = 32, interval =   743.00 */
+/* 095 */ 0xa3bd16c9, /* cps =    495.00, nrm = 32, interval =   713.00 */
+/* 096 */ 0xa40716ad, /* cps =    515.00, nrm = 32, interval =   685.00 */
+/* 097 */ 0xa4331691, /* cps =    537.00, nrm = 32, interval =   657.00 */
+/* 098 */ 0xa45f1677, /* cps =    559.00, nrm = 32, interval =   631.00 */
+/* 099 */ 0xa48f165d, /* cps =    583.00, nrm = 32, interval =   605.00 */
+/* 100 */ 0xa4bf1645, /* cps =    607.00, nrm = 32, interval =   581.00 */
+/* 101 */ 0xa4f1162e, /* cps =    632.00, nrm = 32, interval =   558.00 */
+/* 102 */ 0xa5291617, /* cps =    660.00, nrm = 32, interval =   535.00 */
+/* 103 */ 0xa55f1602, /* cps =    687.00, nrm = 32, interval =   514.00 */
+/* 104 */ 0xa59913da, /* cps =    716.00, nrm = 32, interval =   493.00 */
+/* 105 */ 0xa5d513b2, /* cps =    746.00, nrm = 32, interval =   473.00 */
+/* 106 */ 0xa613138c, /* cps =    777.00, nrm = 32, interval =   454.00 */
+/* 107 */ 0xa6551368, /* cps =    810.00, nrm = 32, interval =   436.00 */
+/* 108 */ 0xa6971345, /* cps =    843.00, nrm = 32, interval =   418.50 */
+/* 109 */ 0xa6df1323, /* cps =    879.00, nrm = 32, interval =   401.50 */
+/* 110 */ 0xa7291303, /* cps =    916.00, nrm = 32, interval =   385.50 */
+/* 111 */ 0xa77512e4, /* cps =    954.00, nrm = 32, interval =   370.00 */
+/* 112 */ 0xa7c512c6, /* cps =    994.00, nrm = 32, interval =   355.00 */
+/* 113 */ 0xa80d12a9, /* cps =   1036.00, nrm = 32, interval =   340.50 */
+/* 114 */ 0xa839128e, /* cps =   1080.00, nrm = 32, interval =   327.00 */
+/* 115 */ 0xa8651274, /* cps =   1124.00, nrm = 32, interval =   314.00 */
+/* 116 */ 0xa895125a, /* cps =   1172.00, nrm = 32, interval =   301.00 */
+/* 117 */ 0xa8c71242, /* cps =   1222.00, nrm = 32, interval =   289.00 */
+/* 118 */ 0xa8f9122b, /* cps =   1272.00, nrm = 32, interval =   277.50 */
+/* 119 */ 0xa92f1214, /* cps =   1326.00, nrm = 32, interval =   266.00 */
+/* 120 */ 0xa9670ffe, /* cps =   1382.00, nrm = 32, interval =   255.50 */
+/* 121 */ 0xa9a10fd5, /* cps =   1440.00, nrm = 32, interval =   245.25 */
+/* 122 */ 0xa9db0fae, /* cps =   1498.00, nrm = 32, interval =   235.50 */
+/* 123 */ 0xaa1b0f88, /* cps =   1562.00, nrm = 32, interval =   226.00 */
+/* 124 */ 0xaa5d0f63, /* cps =   1628.00, nrm = 32, interval =   216.75 */
+/* 125 */ 0xaaa10f41, /* cps =   1696.00, nrm = 32, interval =   208.25 */
+/* 126 */ 0xaae90f1f, /* cps =   1768.00, nrm = 32, interval =   199.75 */
+/* 127 */ 0xab330eff, /* cps =   1842.00, nrm = 32, interval =   191.75 */
+/* 128 */ 0xab7f0ee0, /* cps =   1918.00, nrm = 32, interval =   184.00 */
+/* 129 */ 0xabd10ec2, /* cps =   2000.00, nrm = 32, interval =   176.50 */
+/* 130 */ 0xac110ea6, /* cps =   2080.00, nrm = 32, interval =   169.50 */
+/* 131 */ 0xac3d0e8b, /* cps =   2168.00, nrm = 32, interval =   162.75 */
+/* 132 */ 0xac6d0e70, /* cps =   2264.00, nrm = 32, interval =   156.00 */
+/* 133 */ 0xac9b0e57, /* cps =   2356.00, nrm = 32, interval =   149.75 */
+/* 134 */ 0xaccd0e3f, /* cps =   2456.00, nrm = 32, interval =   143.75 */
+/* 135 */ 0xacff0e28, /* cps =   2556.00, nrm = 32, interval =   138.00 */
+/* 136 */ 0xad350e12, /* cps =   2664.00, nrm = 32, interval =   132.50 */
+/* 137 */ 0xad6d0bf9, /* cps =   2776.00, nrm = 32, interval =   127.12 */
+/* 138 */ 0xada70bd0, /* cps =   2892.00, nrm = 32, interval =   122.00 */
+/* 139 */ 0xade30ba9, /* cps =   3012.00, nrm = 32, interval =   117.12 */
+/* 140 */ 0xae230b83, /* cps =   3140.00, nrm = 32, interval =   112.38 */
+/* 141 */ 0xae650b5f, /* cps =   3272.00, nrm = 32, interval =   107.88 */
+/* 142 */ 0xaeab0b3c, /* cps =   3412.00, nrm = 32, interval =   103.50 */
+/* 143 */ 0xaef10b1b, /* cps =   3552.00, nrm = 32, interval =    99.38 */
+/* 144 */ 0xaf3b0afb, /* cps =   3700.00, nrm = 32, interval =    95.38 */
+/* 145 */ 0xaf8b0adc, /* cps =   3860.00, nrm = 32, interval =    91.50 */
+/* 146 */ 0xafd90abf, /* cps =   4016.00, nrm = 32, interval =    87.88 */
+/* 147 */ 0xb0170aa3, /* cps =   4184.00, nrm = 32, interval =    84.38 */
+/* 148 */ 0xb0430a87, /* cps =   4360.00, nrm = 32, interval =    80.88 */
+/* 149 */ 0xb0710a6d, /* cps =   4544.00, nrm = 32, interval =    77.62 */
+/* 150 */ 0xb0a10a54, /* cps =   4736.00, nrm = 32, interval =    74.50 */
+/* 151 */ 0xb0d30a3c, /* cps =   4936.00, nrm = 32, interval =    71.50 */
+/* 152 */ 0xb1070a25, /* cps =   5144.00, nrm = 32, interval =    68.62 */
+/* 153 */ 0xb13d0a0f, /* cps =   5360.00, nrm = 32, interval =    65.88 */
+/* 154 */ 0xb17507f4, /* cps =   5584.00, nrm = 32, interval =    63.25 */
+/* 155 */ 0xb1af07cb, /* cps =   5816.00, nrm = 32, interval =    60.69 */
+/* 156 */ 0xb1eb07a4, /* cps =   6056.00, nrm = 32, interval =    58.25 */
+/* 157 */ 0xb22b077f, /* cps =   6312.00, nrm = 32, interval =    55.94 */
+/* 158 */ 0xb26d075b, /* cps =   6576.00, nrm = 32, interval =    53.69 */
+/* 159 */ 0xb2b30738, /* cps =   6856.00, nrm = 32, interval =    51.50 */
+/* 160 */ 0xb2fb0717, /* cps =   7144.00, nrm = 32, interval =    49.44 */
+/* 161 */ 0xb34506f7, /* cps =   7440.00, nrm = 32, interval =    47.44 */
+/* 162 */ 0xb39306d9, /* cps =   7752.00, nrm = 32, interval =    45.56 */
+/* 163 */ 0xb3e506bb, /* cps =   8080.00, nrm = 32, interval =    43.69 */
+/* 164 */ 0xb41d069f, /* cps =   8416.00, nrm = 32, interval =    41.94 */
+/* 165 */ 0xb4490684, /* cps =   8768.00, nrm = 32, interval =    40.25 */
+/* 166 */ 0xb477066a, /* cps =   9136.00, nrm = 32, interval =    38.62 */
+/* 167 */ 0xb4a70651, /* cps =   9520.00, nrm = 32, interval =    37.06 */
+/* 168 */ 0xb4d90639, /* cps =   9920.00, nrm = 32, interval =    35.56 */
+/* 169 */ 0xb50d0622, /* cps =  10336.00, nrm = 32, interval =    34.12 */
+/* 170 */ 0xb545060c, /* cps =  10784.00, nrm = 32, interval =    32.75 */
+/* 171 */ 0xb57b03ef, /* cps =  11216.00, nrm = 32, interval =    31.47 */
+/* 172 */ 0xb5b503c7, /* cps =  11680.00, nrm = 32, interval =    30.22 */
+/* 173 */ 0xb5f303a0, /* cps =  12176.00, nrm = 32, interval =    29.00 */
+/* 174 */ 0xb633037a, /* cps =  12688.00, nrm = 32, interval =    27.81 */
+/* 175 */ 0xb6750357, /* cps =  13216.00, nrm = 32, interval =    26.72 */
+/* 176 */ 0xb6bb0334, /* cps =  13776.00, nrm = 32, interval =    25.62 */
+/* 177 */ 0xb7030313, /* cps =  14352.00, nrm = 32, interval =    24.59 */
+/* 178 */ 0xb74f02f3, /* cps =  14960.00, nrm = 32, interval =    23.59 */
+/* 179 */ 0xb79d02d5, /* cps =  15584.00, nrm = 32, interval =    22.66 */
+/* 180 */ 0xb7ed02b8, /* cps =  16224.00, nrm = 32, interval =    21.75 */
+/* 181 */ 0xb821029c, /* cps =  16896.00, nrm = 32, interval =    20.88 */
+/* 182 */ 0xb84f0281, /* cps =  17632.00, nrm = 32, interval =    20.03 */
+/* 183 */ 0xb87d0267, /* cps =  18368.00, nrm = 32, interval =    19.22 */
+/* 184 */ 0xb8ad024e, /* cps =  19136.00, nrm = 32, interval =    18.44 */
+/* 185 */ 0xb8dd0237, /* cps =  19904.00, nrm = 32, interval =    17.72 */
+/* 186 */ 0xb9130220, /* cps =  20768.00, nrm = 32, interval =    17.00 */
+/* 187 */ 0xb949020a, /* cps =  21632.00, nrm = 32, interval =    16.31 */
+/* 188 */ 0xb98301f5, /* cps =  22560.00, nrm = 32, interval =    15.66 */
+/* 189 */ 0xb9bd01e1, /* cps =  23488.00, nrm = 32, interval =    15.03 */
+/* 190 */ 0xb9fd01cd, /* cps =  24512.00, nrm = 32, interval =    14.41 */
+/* 191 */ 0xba3b01bb, /* cps =  25504.00, nrm = 32, interval =    13.84 */
+/* 192 */ 0xba7f01a9, /* cps =  26592.00, nrm = 32, interval =    13.28 */
+/* 193 */ 0xbac30198, /* cps =  27680.00, nrm = 32, interval =    12.75 */
+/* 194 */ 0xbb0f0187, /* cps =  28896.00, nrm = 32, interval =    12.22 */
+/* 195 */ 0xbb570178, /* cps =  30048.00, nrm = 32, interval =    11.75 */
+/* 196 */ 0xbbab0168, /* cps =  31392.00, nrm = 32, interval =    11.25 */
+/* 197 */ 0xbbf9015a, /* cps =  32640.00, nrm = 32, interval =    10.81 */
+/* 198 */ 0xbc27014c, /* cps =  33984.00, nrm = 32, interval =    10.38 */
+/* 199 */ 0xbc53013f, /* cps =  35392.00, nrm = 32, interval =     9.97 */
+/* 200 */ 0xbc830132, /* cps =  36928.00, nrm = 32, interval =     9.56 */
+/* 201 */ 0xbcb50125, /* cps =  38528.00, nrm = 32, interval =     9.16 */
+/* 202 */ 0xbce5011a, /* cps =  40064.00, nrm = 32, interval =     8.81 */
+/* 203 */ 0xbd1d010e, /* cps =  41856.00, nrm = 32, interval =     8.44 */
+/* 204 */ 0xbd530103, /* cps =  43584.00, nrm = 32, interval =     8.09 */
+/* 205 */ 0xbd8b00f9, /* cps =  45376.00, nrm = 32, interval =     7.78 */
+/* 206 */ 0xbdc500ef, /* cps =  47232.00, nrm = 32, interval =     7.47 */
+/* 207 */ 0xbe0700e5, /* cps =  49344.00, nrm = 32, interval =     7.16 */
+/* 208 */ 0xbe4500dc, /* cps =  51328.00, nrm = 32, interval =     6.88 */
+/* 209 */ 0xbe8900d3, /* cps =  53504.00, nrm = 32, interval =     6.59 */
+/* 210 */ 0xbecb00cb, /* cps =  55616.00, nrm = 32, interval =     6.34 */
+/* 211 */ 0xbf1d00c2, /* cps =  58240.00, nrm = 32, interval =     6.06 */
+/* 212 */ 0xbf6100bb, /* cps =  60416.00, nrm = 32, interval =     5.84 */
+/* 213 */ 0xbfb500b3, /* cps =  63104.00, nrm = 32, interval =     5.59 */
+/* 214 */ 0xc00300ac, /* cps =  65664.00, nrm = 32, interval =     5.38 */
+/* 215 */ 0xc02f00a5, /* cps =  68480.00, nrm = 32, interval =     5.16 */
+/* 216 */ 0xc05d009e, /* cps =  71424.00, nrm = 32, interval =     4.94 */
+/* 217 */ 0xc0890098, /* cps =  74240.00, nrm = 32, interval =     4.75 */
+/* 218 */ 0xc0b90092, /* cps =  77312.00, nrm = 32, interval =     4.56 */
+/* 219 */ 0xc0ed008c, /* cps =  80640.00, nrm = 32, interval =     4.38 */
+/* 220 */ 0xc1250086, /* cps =  84224.00, nrm = 32, interval =     4.19 */
+/* 221 */ 0xc1590081, /* cps =  87552.00, nrm = 32, interval =     4.03 */
+/* 222 */ 0xc191007c, /* cps =  91136.00, nrm = 32, interval =     3.88 */
+/* 223 */ 0xc1cd0077, /* cps =  94976.00, nrm = 32, interval =     3.72 */
+/* 224 */ 0xc20d0072, /* cps =  99072.00, nrm = 32, interval =     3.56 */
+/* 225 */ 0xc255006d, /* cps = 103680.00, nrm = 32, interval =     3.41 */
+/* 226 */ 0xc2910069, /* cps = 107520.00, nrm = 32, interval =     3.28 */
+/* 227 */ 0xc2d50065, /* cps = 111872.00, nrm = 32, interval =     3.16 */
+/* 228 */ 0xc32f0060, /* cps = 117632.00, nrm = 32, interval =     3.00 */
+/* 229 */ 0xc36b005d, /* cps = 121472.00, nrm = 32, interval =     2.91 */
+/* 230 */ 0xc3c10059, /* cps = 126976.00, nrm = 32, interval =     2.78 */
+/* 231 */ 0xc40f0055, /* cps = 132864.00, nrm = 32, interval =     2.66 */
+/* 232 */ 0xc4350052, /* cps = 137728.00, nrm = 32, interval =     2.56 */
+/* 233 */ 0xc46d004e, /* cps = 144896.00, nrm = 32, interval =     2.44 */
+/* 234 */ 0xc499004b, /* cps = 150528.00, nrm = 32, interval =     2.34 */
+/* 235 */ 0xc4cb0048, /* cps = 156928.00, nrm = 32, interval =     2.25 */
+/* 236 */ 0xc4ff0045, /* cps = 163584.00, nrm = 32, interval =     2.16 */
+/* 237 */ 0xc5250043, /* cps = 168448.00, nrm = 32, interval =     2.09 */
+/* 238 */ 0xc5630040, /* cps = 176384.00, nrm = 32, interval =     2.00 */
+/* 239 */ 0xc5a7003d, /* cps = 185088.00, nrm = 32, interval =     1.91 */
+/* 240 */ 0xc5d9003b, /* cps = 191488.00, nrm = 32, interval =     1.84 */
+/* 241 */ 0xc6290038, /* cps = 201728.00, nrm = 32, interval =     1.75 */
+/* 242 */ 0xc6630036, /* cps = 209152.00, nrm = 32, interval =     1.69 */
+/* 243 */ 0xc6a30034, /* cps = 217344.00, nrm = 32, interval =     1.62 */
+/* 244 */ 0xc6e70032, /* cps = 226048.00, nrm = 32, interval =     1.56 */
+/* 245 */ 0xc72f0030, /* cps = 235264.00, nrm = 32, interval =     1.50 */
+/* 246 */ 0xc77f002e, /* cps = 245504.00, nrm = 32, interval =     1.44 */
+/* 247 */ 0xc7d7002c, /* cps = 256768.00, nrm = 32, interval =     1.38 */
+/* 248 */ 0xc81b002a, /* cps = 268800.00, nrm = 32, interval =     1.31 */
+/* 249 */ 0xc84f0028, /* cps = 282112.00, nrm = 32, interval =     1.25 */
+/* 250 */ 0xc86d0027, /* cps = 289792.00, nrm = 32, interval =     1.22 */
+/* 251 */ 0xc8a90025, /* cps = 305152.00, nrm = 32, interval =     1.16 */
+/* 252 */ 0xc8cb0024, /* cps = 313856.00, nrm = 32, interval =     1.12 */
+/* 253 */ 0xc9130022, /* cps = 332288.00, nrm = 32, interval =     1.06 */
+/* 254 */ 0xc9390021, /* cps = 342016.00, nrm = 32, interval =     1.03 */
+/* 255 */ 0xc9630020, /* cps = 352768.00, nrm = 32, interval =     1.00 */
+};
+
+static unsigned char rate_to_log[] =
+{
+/*          1.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.06 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.12 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.19 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.31 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.38 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.44 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.56 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.62 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.69 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.81 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.88 =>   0 */ 0x00, /* =>     10.02 */
+/*          1.94 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.12 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.38 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.62 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          2.88 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.12 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.38 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.62 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          3.88 =>   0 */ 0x00, /* =>     10.02 */
+/*          4.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          4.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          4.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          4.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          5.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          5.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          5.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          5.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          6.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          6.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          6.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          6.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          7.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          7.25 =>   0 */ 0x00, /* =>     10.02 */
+/*          7.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          7.75 =>   0 */ 0x00, /* =>     10.02 */
+/*          8.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          8.50 =>   0 */ 0x00, /* =>     10.02 */
+/*          9.00 =>   0 */ 0x00, /* =>     10.02 */
+/*          9.50 =>   0 */ 0x00, /* =>     10.02 */
+/*         10.00 =>   0 */ 0x00, /* =>     10.02 */
+/*         10.50 =>   1 */ 0x01, /* =>     10.42 */
+/*         11.00 =>   2 */ 0x02, /* =>     10.86 */
+/*         11.50 =>   3 */ 0x03, /* =>     11.31 */
+/*         12.00 =>   4 */ 0x04, /* =>     11.78 */
+/*         12.50 =>   5 */ 0x05, /* =>     12.28 */
+/*         13.00 =>   6 */ 0x06, /* =>     12.80 */
+/*         13.50 =>   7 */ 0x07, /* =>     13.33 */
+/*         14.00 =>   8 */ 0x08, /* =>     13.89 */
+/*         14.50 =>   9 */ 0x09, /* =>     14.48 */
+/*         15.00 =>   9 */ 0x09, /* =>     14.48 */
+/*         15.50 =>  10 */ 0x0a, /* =>     15.08 */
+/*         16.00 =>  11 */ 0x0b, /* =>     15.72 */
+/*         17.00 =>  12 */ 0x0c, /* =>     16.38 */
+/*         18.00 =>  14 */ 0x0e, /* =>     17.75 */
+/*         19.00 =>  15 */ 0x0f, /* =>     18.50 */
+/*         20.00 =>  16 */ 0x10, /* =>     19.28 */
+/*         21.00 =>  18 */ 0x12, /* =>     20.94 */
+/*         22.00 =>  19 */ 0x13, /* =>     21.81 */
+/*         23.00 =>  20 */ 0x14, /* =>     22.75 */
+/*         24.00 =>  21 */ 0x15, /* =>     23.69 */
+/*         25.00 =>  22 */ 0x16, /* =>     24.69 */
+/*         26.00 =>  23 */ 0x17, /* =>     25.72 */
+/*         27.00 =>  24 */ 0x18, /* =>     26.81 */
+/*         28.00 =>  25 */ 0x19, /* =>     27.94 */
+/*         29.00 =>  25 */ 0x19, /* =>     27.94 */
+/*         30.00 =>  26 */ 0x1a, /* =>     29.09 */
+/*         31.00 =>  27 */ 0x1b, /* =>     30.31 */
+/*         32.00 =>  28 */ 0x1c, /* =>     31.56 */
+/*         34.00 =>  29 */ 0x1d, /* =>     32.94 */
+/*         36.00 =>  31 */ 0x1f, /* =>     35.69 */
+/*         38.00 =>  32 */ 0x20, /* =>     37.19 */
+/*         40.00 =>  33 */ 0x21, /* =>     38.75 */
+/*         42.00 =>  34 */ 0x22, /* =>     40.38 */
+/*         44.00 =>  36 */ 0x24, /* =>     43.88 */
+/*         46.00 =>  37 */ 0x25, /* =>     45.69 */
+/*         48.00 =>  38 */ 0x26, /* =>     47.62 */
+/*         50.00 =>  39 */ 0x27, /* =>     49.62 */
+/*         52.00 =>  40 */ 0x28, /* =>     51.69 */
+/*         54.00 =>  41 */ 0x29, /* =>     53.88 */
+/*         56.00 =>  41 */ 0x29, /* =>     53.88 */
+/*         58.00 =>  42 */ 0x2a, /* =>     56.12 */
+/*         60.00 =>  43 */ 0x2b, /* =>     58.44 */
+/*         62.00 =>  44 */ 0x2c, /* =>     60.94 */
+/*         64.00 =>  45 */ 0x2d, /* =>     63.50 */
+/*         68.00 =>  46 */ 0x2e, /* =>     66.12 */
+/*         72.00 =>  48 */ 0x30, /* =>     71.88 */
+/*         76.00 =>  49 */ 0x31, /* =>     74.75 */
+/*         80.00 =>  50 */ 0x32, /* =>     78.00 */
+/*         84.00 =>  51 */ 0x33, /* =>     81.25 */
+/*         88.00 =>  52 */ 0x34, /* =>     84.62 */
+/*         92.00 =>  54 */ 0x36, /* =>     91.88 */
+/*         96.00 =>  55 */ 0x37, /* =>     95.75 */
+/*        100.00 =>  56 */ 0x38, /* =>     99.75 */
+/*        104.00 =>  56 */ 0x38, /* =>     99.75 */
+/*        108.00 =>  57 */ 0x39, /* =>    104.00 */
+/*        112.00 =>  58 */ 0x3a, /* =>    108.25 */
+/*        116.00 =>  59 */ 0x3b, /* =>    112.88 */
+/*        120.00 =>  60 */ 0x3c, /* =>    117.50 */
+/*        124.00 =>  61 */ 0x3d, /* =>    122.38 */
+/*        128.00 =>  62 */ 0x3e, /* =>    127.50 */
+/*        136.00 =>  63 */ 0x3f, /* =>    132.75 */
+/*        144.00 =>  64 */ 0x40, /* =>    138.50 */
+/*        152.00 =>  66 */ 0x42, /* =>    150.25 */
+/*        160.00 =>  67 */ 0x43, /* =>    156.75 */
+/*        168.00 =>  68 */ 0x44, /* =>    163.50 */
+/*        176.00 =>  69 */ 0x45, /* =>    170.00 */
+/*        184.00 =>  70 */ 0x46, /* =>    177.25 */
+/*        192.00 =>  71 */ 0x47, /* =>    184.50 */
+/*        200.00 =>  72 */ 0x48, /* =>    192.25 */
+/*        208.00 =>  73 */ 0x49, /* =>    200.25 */
+/*        216.00 =>  74 */ 0x4a, /* =>    208.75 */
+/*        224.00 =>  75 */ 0x4b, /* =>    217.75 */
+/*        232.00 =>  76 */ 0x4c, /* =>    226.75 */
+/*        240.00 =>  77 */ 0x4d, /* =>    236.25 */
+/*        248.00 =>  78 */ 0x4e, /* =>    246.25 */
+/*        256.00 =>  78 */ 0x4e, /* =>    246.25 */
+/*        272.00 =>  80 */ 0x50, /* =>    267.50 */
+/*        288.00 =>  81 */ 0x51, /* =>    278.50 */
+/*        304.00 =>  83 */ 0x53, /* =>    302.00 */
+/*        320.00 =>  84 */ 0x54, /* =>    315.00 */
+/*        336.00 =>  85 */ 0x55, /* =>    328.00 */
+/*        352.00 =>  86 */ 0x56, /* =>    342.00 */
+/*        368.00 =>  87 */ 0x57, /* =>    356.00 */
+/*        384.00 =>  88 */ 0x58, /* =>    371.00 */
+/*        400.00 =>  89 */ 0x59, /* =>    386.50 */
+/*        416.00 =>  90 */ 0x5a, /* =>    403.00 */
+/*        432.00 =>  91 */ 0x5b, /* =>    419.50 */
+/*        448.00 =>  92 */ 0x5c, /* =>    437.50 */
+/*        464.00 =>  93 */ 0x5d, /* =>    455.50 */
+/*        480.00 =>  94 */ 0x5e, /* =>    475.00 */
+/*        496.00 =>  95 */ 0x5f, /* =>    495.00 */
+/*        512.00 =>  95 */ 0x5f, /* =>    495.00 */
+/*        544.00 =>  97 */ 0x61, /* =>    537.00 */
+/*        576.00 =>  98 */ 0x62, /* =>    559.00 */
+/*        608.00 => 100 */ 0x64, /* =>    607.00 */
+/*        640.00 => 101 */ 0x65, /* =>    632.00 */
+/*        672.00 => 102 */ 0x66, /* =>    660.00 */
+/*        704.00 => 103 */ 0x67, /* =>    687.00 */
+/*        736.00 => 104 */ 0x68, /* =>    716.00 */
+/*        768.00 => 105 */ 0x69, /* =>    746.00 */
+/*        800.00 => 106 */ 0x6a, /* =>    777.00 */
+/*        832.00 => 107 */ 0x6b, /* =>    810.00 */
+/*        864.00 => 108 */ 0x6c, /* =>    843.00 */
+/*        896.00 => 109 */ 0x6d, /* =>    879.00 */
+/*        928.00 => 110 */ 0x6e, /* =>    916.00 */
+/*        960.00 => 111 */ 0x6f, /* =>    954.00 */
+/*        992.00 => 111 */ 0x6f, /* =>    954.00 */
+/*       1024.00 => 112 */ 0x70, /* =>    994.00 */
+/*       1088.00 => 114 */ 0x72, /* =>   1080.00 */
+/*       1152.00 => 115 */ 0x73, /* =>   1124.00 */
+/*       1216.00 => 116 */ 0x74, /* =>   1172.00 */
+/*       1280.00 => 118 */ 0x76, /* =>   1272.00 */
+/*       1344.00 => 119 */ 0x77, /* =>   1326.00 */
+/*       1408.00 => 120 */ 0x78, /* =>   1382.00 */
+/*       1472.00 => 121 */ 0x79, /* =>   1440.00 */
+/*       1536.00 => 122 */ 0x7a, /* =>   1498.00 */
+/*       1600.00 => 123 */ 0x7b, /* =>   1562.00 */
+/*       1664.00 => 124 */ 0x7c, /* =>   1628.00 */
+/*       1728.00 => 125 */ 0x7d, /* =>   1696.00 */
+/*       1792.00 => 126 */ 0x7e, /* =>   1768.00 */
+/*       1856.00 => 127 */ 0x7f, /* =>   1842.00 */
+/*       1920.00 => 128 */ 0x80, /* =>   1918.00 */
+/*       1984.00 => 128 */ 0x80, /* =>   1918.00 */
+/*       2048.00 => 129 */ 0x81, /* =>   2000.00 */
+/*       2176.00 => 131 */ 0x83, /* =>   2168.00 */
+/*       2304.00 => 132 */ 0x84, /* =>   2264.00 */
+/*       2432.00 => 133 */ 0x85, /* =>   2356.00 */
+/*       2560.00 => 135 */ 0x87, /* =>   2556.00 */
+/*       2688.00 => 136 */ 0x88, /* =>   2664.00 */
+/*       2816.00 => 137 */ 0x89, /* =>   2776.00 */
+/*       2944.00 => 138 */ 0x8a, /* =>   2892.00 */
+/*       3072.00 => 139 */ 0x8b, /* =>   3012.00 */
+/*       3200.00 => 140 */ 0x8c, /* =>   3140.00 */
+/*       3328.00 => 141 */ 0x8d, /* =>   3272.00 */
+/*       3456.00 => 142 */ 0x8e, /* =>   3412.00 */
+/*       3584.00 => 143 */ 0x8f, /* =>   3552.00 */
+/*       3712.00 => 144 */ 0x90, /* =>   3700.00 */
+/*       3840.00 => 144 */ 0x90, /* =>   3700.00 */
+/*       3968.00 => 145 */ 0x91, /* =>   3860.00 */
+/*       4096.00 => 146 */ 0x92, /* =>   4016.00 */
+/*       4352.00 => 147 */ 0x93, /* =>   4184.00 */
+/*       4608.00 => 149 */ 0x95, /* =>   4544.00 */
+/*       4864.00 => 150 */ 0x96, /* =>   4736.00 */
+/*       5120.00 => 151 */ 0x97, /* =>   4936.00 */
+/*       5376.00 => 153 */ 0x99, /* =>   5360.00 */
+/*       5632.00 => 154 */ 0x9a, /* =>   5584.00 */
+/*       5888.00 => 155 */ 0x9b, /* =>   5816.00 */
+/*       6144.00 => 156 */ 0x9c, /* =>   6056.00 */
+/*       6400.00 => 157 */ 0x9d, /* =>   6312.00 */
+/*       6656.00 => 158 */ 0x9e, /* =>   6576.00 */
+/*       6912.00 => 159 */ 0x9f, /* =>   6856.00 */
+/*       7168.00 => 160 */ 0xa0, /* =>   7144.00 */
+/*       7424.00 => 160 */ 0xa0, /* =>   7144.00 */
+/*       7680.00 => 161 */ 0xa1, /* =>   7440.00 */
+/*       7936.00 => 162 */ 0xa2, /* =>   7752.00 */
+/*       8192.00 => 163 */ 0xa3, /* =>   8080.00 */
+/*       8704.00 => 164 */ 0xa4, /* =>   8416.00 */
+/*       9216.00 => 166 */ 0xa6, /* =>   9136.00 */
+/*       9728.00 => 167 */ 0xa7, /* =>   9520.00 */
+/*      10240.00 => 168 */ 0xa8, /* =>   9920.00 */
+/*      10752.00 => 169 */ 0xa9, /* =>  10336.00 */
+/*      11264.00 => 171 */ 0xab, /* =>  11216.00 */
+/*      11776.00 => 172 */ 0xac, /* =>  11680.00 */
+/*      12288.00 => 173 */ 0xad, /* =>  12176.00 */
+/*      12800.00 => 174 */ 0xae, /* =>  12688.00 */
+/*      13312.00 => 175 */ 0xaf, /* =>  13216.00 */
+/*      13824.00 => 176 */ 0xb0, /* =>  13776.00 */
+/*      14336.00 => 176 */ 0xb0, /* =>  13776.00 */
+/*      14848.00 => 177 */ 0xb1, /* =>  14352.00 */
+/*      15360.00 => 178 */ 0xb2, /* =>  14960.00 */
+/*      15872.00 => 179 */ 0xb3, /* =>  15584.00 */
+/*      16384.00 => 180 */ 0xb4, /* =>  16224.00 */
+/*      17408.00 => 181 */ 0xb5, /* =>  16896.00 */
+/*      18432.00 => 183 */ 0xb7, /* =>  18368.00 */
+/*      19456.00 => 184 */ 0xb8, /* =>  19136.00 */
+/*      20480.00 => 185 */ 0xb9, /* =>  19904.00 */
+/*      21504.00 => 186 */ 0xba, /* =>  20768.00 */
+/*      22528.00 => 187 */ 0xbb, /* =>  21632.00 */
+/*      23552.00 => 189 */ 0xbd, /* =>  23488.00 */
+/*      24576.00 => 190 */ 0xbe, /* =>  24512.00 */
+/*      25600.00 => 191 */ 0xbf, /* =>  25504.00 */
+/*      26624.00 => 192 */ 0xc0, /* =>  26592.00 */
+/*      27648.00 => 192 */ 0xc0, /* =>  26592.00 */
+/*      28672.00 => 193 */ 0xc1, /* =>  27680.00 */
+/*      29696.00 => 194 */ 0xc2, /* =>  28896.00 */
+/*      30720.00 => 195 */ 0xc3, /* =>  30048.00 */
+/*      31744.00 => 196 */ 0xc4, /* =>  31392.00 */
+/*      32768.00 => 197 */ 0xc5, /* =>  32640.00 */
+/*      34816.00 => 198 */ 0xc6, /* =>  33984.00 */
+/*      36864.00 => 199 */ 0xc7, /* =>  35392.00 */
+/*      38912.00 => 201 */ 0xc9, /* =>  38528.00 */
+/*      40960.00 => 202 */ 0xca, /* =>  40064.00 */
+/*      43008.00 => 203 */ 0xcb, /* =>  41856.00 */
+/*      45056.00 => 204 */ 0xcc, /* =>  43584.00 */
+/*      47104.00 => 205 */ 0xcd, /* =>  45376.00 */
+/*      49152.00 => 206 */ 0xce, /* =>  47232.00 */
+/*      51200.00 => 207 */ 0xcf, /* =>  49344.00 */
+/*      53248.00 => 208 */ 0xd0, /* =>  51328.00 */
+/*      55296.00 => 209 */ 0xd1, /* =>  53504.00 */
+/*      57344.00 => 210 */ 0xd2, /* =>  55616.00 */
+/*      59392.00 => 211 */ 0xd3, /* =>  58240.00 */
+/*      61440.00 => 212 */ 0xd4, /* =>  60416.00 */
+/*      63488.00 => 213 */ 0xd5, /* =>  63104.00 */
+/*      65536.00 => 213 */ 0xd5, /* =>  63104.00 */
+/*      69632.00 => 215 */ 0xd7, /* =>  68480.00 */
+/*      73728.00 => 216 */ 0xd8, /* =>  71424.00 */
+/*      77824.00 => 218 */ 0xda, /* =>  77312.00 */
+/*      81920.00 => 219 */ 0xdb, /* =>  80640.00 */
+/*      86016.00 => 220 */ 0xdc, /* =>  84224.00 */
+/*      90112.00 => 221 */ 0xdd, /* =>  87552.00 */
+/*      94208.00 => 222 */ 0xde, /* =>  91136.00 */
+/*      98304.00 => 223 */ 0xdf, /* =>  94976.00 */
+/*     102400.00 => 224 */ 0xe0, /* =>  99072.00 */
+/*     106496.00 => 225 */ 0xe1, /* => 103680.00 */
+/*     110592.00 => 226 */ 0xe2, /* => 107520.00 */
+/*     114688.00 => 227 */ 0xe3, /* => 111872.00 */
+/*     118784.00 => 228 */ 0xe4, /* => 117632.00 */
+/*     122880.00 => 229 */ 0xe5, /* => 121472.00 */
+/*     126976.00 => 229 */ 0xe5, /* => 121472.00 */
+/*     131072.00 => 230 */ 0xe6, /* => 126976.00 */
+/*     139264.00 => 232 */ 0xe8, /* => 137728.00 */
+/*     147456.00 => 233 */ 0xe9, /* => 144896.00 */
+/*     155648.00 => 234 */ 0xea, /* => 150528.00 */
+/*     163840.00 => 236 */ 0xec, /* => 163584.00 */
+/*     172032.00 => 237 */ 0xed, /* => 168448.00 */
+/*     180224.00 => 238 */ 0xee, /* => 176384.00 */
+/*     188416.00 => 239 */ 0xef, /* => 185088.00 */
+/*     196608.00 => 240 */ 0xf0, /* => 191488.00 */
+/*     204800.00 => 241 */ 0xf1, /* => 201728.00 */
+/*     212992.00 => 242 */ 0xf2, /* => 209152.00 */
+/*     221184.00 => 243 */ 0xf3, /* => 217344.00 */
+/*     229376.00 => 244 */ 0xf4, /* => 226048.00 */
+/*     237568.00 => 245 */ 0xf5, /* => 235264.00 */
+/*     245760.00 => 246 */ 0xf6, /* => 245504.00 */
+/*     253952.00 => 246 */ 0xf6, /* => 245504.00 */
+/*     262144.00 => 247 */ 0xf7, /* => 256768.00 */
+/*     278528.00 => 248 */ 0xf8, /* => 268800.00 */
+/*     294912.00 => 250 */ 0xfa, /* => 289792.00 */
+/*     311296.00 => 251 */ 0xfb, /* => 305152.00 */
+/*     327680.00 => 252 */ 0xfc, /* => 313856.00 */
+/*     344064.00 => 254 */ 0xfe, /* => 342016.00 */
+/*     360448.00 => 255 */ 0xff, /* => 352768.00 */
+/*     376832.00 => 255 */ 0xff, /* => 352768.00 */
+/*     393216.00 => 255 */ 0xff, /* => 352768.00 */
+/*     409600.00 => 255 */ 0xff, /* => 352768.00 */
+/*     425984.00 => 255 */ 0xff, /* => 352768.00 */
+/*     442368.00 => 255 */ 0xff, /* => 352768.00 */
+/*     458752.00 => 255 */ 0xff, /* => 352768.00 */
+/*     475136.00 => 255 */ 0xff, /* => 352768.00 */
+/*     491520.00 => 255 */ 0xff, /* => 352768.00 */
+/*     507904.00 => 255 */ 0xff, /* => 352768.00 */
+/*     524288.00 => 255 */ 0xff, /* => 352768.00 */
+/*     557056.00 => 255 */ 0xff, /* => 352768.00 */
+/*     589824.00 => 255 */ 0xff, /* => 352768.00 */
+/*     622592.00 => 255 */ 0xff, /* => 352768.00 */
+/*     655360.00 => 255 */ 0xff, /* => 352768.00 */
+/*     688128.00 => 255 */ 0xff, /* => 352768.00 */
+/*     720896.00 => 255 */ 0xff, /* => 352768.00 */
+/*     753664.00 => 255 */ 0xff, /* => 352768.00 */
+/*     786432.00 => 255 */ 0xff, /* => 352768.00 */
+/*     819200.00 => 255 */ 0xff, /* => 352768.00 */
+/*     851968.00 => 255 */ 0xff, /* => 352768.00 */
+/*     884736.00 => 255 */ 0xff, /* => 352768.00 */
+/*     917504.00 => 255 */ 0xff, /* => 352768.00 */
+/*     950272.00 => 255 */ 0xff, /* => 352768.00 */
+/*     983040.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1015808.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1048576.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1114112.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1179648.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1245184.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1310720.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1376256.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1441792.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1507328.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1572864.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1638400.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1703936.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1769472.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1835008.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1900544.00 => 255 */ 0xff, /* => 352768.00 */
+/*    1966080.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2031616.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2097152.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2228224.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2359296.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2490368.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2621440.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2752512.00 => 255 */ 0xff, /* => 352768.00 */
+/*    2883584.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3014656.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3145728.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3276800.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3407872.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3538944.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3670016.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3801088.00 => 255 */ 0xff, /* => 352768.00 */
+/*    3932160.00 => 255 */ 0xff, /* => 352768.00 */
+/*    4063232.00 => 255 */ 0xff, /* => 352768.00 */
+/*    4194304.00 => 255 */ 0xff, /* => 352768.00 */
+/*    4456448.00 => 255 */ 0xff, /* => 352768.00 */
+/*    4718592.00 => 255 */ 0xff, /* => 352768.00 */
+/*    4980736.00 => 255 */ 0xff, /* => 352768.00 */
+/*    5242880.00 => 255 */ 0xff, /* => 352768.00 */
+/*    5505024.00 => 255 */ 0xff, /* => 352768.00 */
+/*    5767168.00 => 255 */ 0xff, /* => 352768.00 */
+/*    6029312.00 => 255 */ 0xff, /* => 352768.00 */
+/*    6291456.00 => 255 */ 0xff, /* => 352768.00 */
+/*    6553600.00 => 255 */ 0xff, /* => 352768.00 */
+/*    6815744.00 => 255 */ 0xff, /* => 352768.00 */
+/*    7077888.00 => 255 */ 0xff, /* => 352768.00 */
+/*    7340032.00 => 255 */ 0xff, /* => 352768.00 */
+/*    7602176.00 => 255 */ 0xff, /* => 352768.00 */
+/*    7864320.00 => 255 */ 0xff, /* => 352768.00 */
+/*    8126464.00 => 255 */ 0xff, /* => 352768.00 */
+/*    8388608.00 => 255 */ 0xff, /* => 352768.00 */
+/*    8912896.00 => 255 */ 0xff, /* => 352768.00 */
+/*    9437184.00 => 255 */ 0xff, /* => 352768.00 */
+/*    9961472.00 => 255 */ 0xff, /* => 352768.00 */
+/*   10485760.00 => 255 */ 0xff, /* => 352768.00 */
+/*   11010048.00 => 255 */ 0xff, /* => 352768.00 */
+/*   11534336.00 => 255 */ 0xff, /* => 352768.00 */
+/*   12058624.00 => 255 */ 0xff, /* => 352768.00 */
+/*   12582912.00 => 255 */ 0xff, /* => 352768.00 */
+/*   13107200.00 => 255 */ 0xff, /* => 352768.00 */
+/*   13631488.00 => 255 */ 0xff, /* => 352768.00 */
+/*   14155776.00 => 255 */ 0xff, /* => 352768.00 */
+/*   14680064.00 => 255 */ 0xff, /* => 352768.00 */
+/*   15204352.00 => 255 */ 0xff, /* => 352768.00 */
+/*   15728640.00 => 255 */ 0xff, /* => 352768.00 */
+/*   16252928.00 => 255 */ 0xff, /* => 352768.00 */
+/*   16777216.00 => 255 */ 0xff, /* => 352768.00 */
+/*   17825792.00 => 255 */ 0xff, /* => 352768.00 */
+/*   18874368.00 => 255 */ 0xff, /* => 352768.00 */
+/*   19922944.00 => 255 */ 0xff, /* => 352768.00 */
+/*   20971520.00 => 255 */ 0xff, /* => 352768.00 */
+/*   22020096.00 => 255 */ 0xff, /* => 352768.00 */
+/*   23068672.00 => 255 */ 0xff, /* => 352768.00 */
+/*   24117248.00 => 255 */ 0xff, /* => 352768.00 */
+/*   25165824.00 => 255 */ 0xff, /* => 352768.00 */
+/*   26214400.00 => 255 */ 0xff, /* => 352768.00 */
+/*   27262976.00 => 255 */ 0xff, /* => 352768.00 */
+/*   28311552.00 => 255 */ 0xff, /* => 352768.00 */
+/*   29360128.00 => 255 */ 0xff, /* => 352768.00 */
+/*   30408704.00 => 255 */ 0xff, /* => 352768.00 */
+/*   31457280.00 => 255 */ 0xff, /* => 352768.00 */
+/*   32505856.00 => 255 */ 0xff, /* => 352768.00 */
+/*   33554432.00 => 255 */ 0xff, /* => 352768.00 */
+/*   35651584.00 => 255 */ 0xff, /* => 352768.00 */
+/*   37748736.00 => 255 */ 0xff, /* => 352768.00 */
+/*   39845888.00 => 255 */ 0xff, /* => 352768.00 */
+/*   41943040.00 => 255 */ 0xff, /* => 352768.00 */
+/*   44040192.00 => 255 */ 0xff, /* => 352768.00 */
+/*   46137344.00 => 255 */ 0xff, /* => 352768.00 */
+/*   48234496.00 => 255 */ 0xff, /* => 352768.00 */
+/*   50331648.00 => 255 */ 0xff, /* => 352768.00 */
+/*   52428800.00 => 255 */ 0xff, /* => 352768.00 */
+/*   54525952.00 => 255 */ 0xff, /* => 352768.00 */
+/*   56623104.00 => 255 */ 0xff, /* => 352768.00 */
+/*   58720256.00 => 255 */ 0xff, /* => 352768.00 */
+/*   60817408.00 => 255 */ 0xff, /* => 352768.00 */
+/*   62914560.00 => 255 */ 0xff, /* => 352768.00 */
+/*   65011712.00 => 255 */ 0xff, /* => 352768.00 */
+/*   67108864.00 => 255 */ 0xff, /* => 352768.00 */
+/*   71303168.00 => 255 */ 0xff, /* => 352768.00 */
+/*   75497472.00 => 255 */ 0xff, /* => 352768.00 */
+/*   79691776.00 => 255 */ 0xff, /* => 352768.00 */
+/*   83886080.00 => 255 */ 0xff, /* => 352768.00 */
+/*   88080384.00 => 255 */ 0xff, /* => 352768.00 */
+/*   92274688.00 => 255 */ 0xff, /* => 352768.00 */
+/*   96468992.00 => 255 */ 0xff, /* => 352768.00 */
+/*  100663296.00 => 255 */ 0xff, /* => 352768.00 */
+/*  104857600.00 => 255 */ 0xff, /* => 352768.00 */
+/*  109051904.00 => 255 */ 0xff, /* => 352768.00 */
+/*  113246208.00 => 255 */ 0xff, /* => 352768.00 */
+/*  117440512.00 => 255 */ 0xff, /* => 352768.00 */
+/*  121634816.00 => 255 */ 0xff, /* => 352768.00 */
+/*  125829120.00 => 255 */ 0xff, /* => 352768.00 */
+/*  130023424.00 => 255 */ 0xff, /* => 352768.00 */
+/*  134217728.00 => 255 */ 0xff, /* => 352768.00 */
+/*  142606336.00 => 255 */ 0xff, /* => 352768.00 */
+/*  150994944.00 => 255 */ 0xff, /* => 352768.00 */
+/*  159383552.00 => 255 */ 0xff, /* => 352768.00 */
+/*  167772160.00 => 255 */ 0xff, /* => 352768.00 */
+/*  176160768.00 => 255 */ 0xff, /* => 352768.00 */
+/*  184549376.00 => 255 */ 0xff, /* => 352768.00 */
+/*  192937984.00 => 255 */ 0xff, /* => 352768.00 */
+/*  201326592.00 => 255 */ 0xff, /* => 352768.00 */
+/*  209715200.00 => 255 */ 0xff, /* => 352768.00 */
+/*  218103808.00 => 255 */ 0xff, /* => 352768.00 */
+/*  226492416.00 => 255 */ 0xff, /* => 352768.00 */
+/*  234881024.00 => 255 */ 0xff, /* => 352768.00 */
+/*  243269632.00 => 255 */ 0xff, /* => 352768.00 */
+/*  251658240.00 => 255 */ 0xff, /* => 352768.00 */
+/*  260046848.00 => 255 */ 0xff, /* => 352768.00 */
+/*  268435456.00 => 255 */ 0xff, /* => 352768.00 */
+/*  285212672.00 => 255 */ 0xff, /* => 352768.00 */
+/*  301989888.00 => 255 */ 0xff, /* => 352768.00 */
+/*  318767104.00 => 255 */ 0xff, /* => 352768.00 */
+/*  335544320.00 => 255 */ 0xff, /* => 352768.00 */
+/*  352321536.00 => 255 */ 0xff, /* => 352768.00 */
+/*  369098752.00 => 255 */ 0xff, /* => 352768.00 */
+/*  385875968.00 => 255 */ 0xff, /* => 352768.00 */
+/*  402653184.00 => 255 */ 0xff, /* => 352768.00 */
+/*  419430400.00 => 255 */ 0xff, /* => 352768.00 */
+/*  436207616.00 => 255 */ 0xff, /* => 352768.00 */
+/*  452984832.00 => 255 */ 0xff, /* => 352768.00 */
+/*  469762048.00 => 255 */ 0xff, /* => 352768.00 */
+/*  486539264.00 => 255 */ 0xff, /* => 352768.00 */
+/*  503316480.00 => 255 */ 0xff, /* => 352768.00 */
+/*  520093696.00 => 255 */ 0xff, /* => 352768.00 */
+/*  536870912.00 => 255 */ 0xff, /* => 352768.00 */
+/*  570425344.00 => 255 */ 0xff, /* => 352768.00 */
+/*  603979776.00 => 255 */ 0xff, /* => 352768.00 */
+/*  637534208.00 => 255 */ 0xff, /* => 352768.00 */
+/*  671088640.00 => 255 */ 0xff, /* => 352768.00 */
+/*  704643072.00 => 255 */ 0xff, /* => 352768.00 */
+/*  738197504.00 => 255 */ 0xff, /* => 352768.00 */
+/*  771751936.00 => 255 */ 0xff, /* => 352768.00 */
+/*  805306368.00 => 255 */ 0xff, /* => 352768.00 */
+/*  838860800.00 => 255 */ 0xff, /* => 352768.00 */
+/*  872415232.00 => 255 */ 0xff, /* => 352768.00 */
+/*  905969664.00 => 255 */ 0xff, /* => 352768.00 */
+/*  939524096.00 => 255 */ 0xff, /* => 352768.00 */
+/*  973078528.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1006632960.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1040187392.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1073741824.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1140850688.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1207959552.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1275068416.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1342177280.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1409286144.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1476395008.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1543503872.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1610612736.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1677721600.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1744830464.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1811939328.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1879048192.00 => 255 */ 0xff, /* => 352768.00 */
+/* 1946157056.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2013265920.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2080374784.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2147483648.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2281701376.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2415919104.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2550136832.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2684354560.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2818572288.00 => 255 */ 0xff, /* => 352768.00 */
+/* 2952790016.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3087007744.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3221225472.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3355443200.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3489660928.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3623878656.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3758096384.00 => 255 */ 0xff, /* => 352768.00 */
+/* 3892314112.00 => 255 */ 0xff, /* => 352768.00 */
+/* 4026531840.00 => 255 */ 0xff, /* => 352768.00 */
+/* 4160749568.00 => 255 */ 0xff, /* => 352768.00 */
+};
diff --git a/drivers/atm/iphase.c b/drivers/atm/iphase.c
new file mode 100644
index 0000000..a43575a
--- /dev/null
+++ b/drivers/atm/iphase.c
@@ -0,0 +1,3296 @@
+/******************************************************************************
+         iphase.c: Device driver for Interphase ATM PCI adapter cards 
+                    Author: Peter Wang  <pwang@iphase.com>            
+		   Some fixes: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+                   Interphase Corporation  <www.iphase.com>           
+                               Version: 1.0                           
+*******************************************************************************
+      
+      This software may be used and distributed according to the terms
+      of the GNU General Public License (GPL), incorporated herein by reference.
+      Drivers based on this skeleton fall under the GPL and must retain
+      the authorship (implicit copyright) notice.
+
+      This program is distributed in the hope that it will be useful, but
+      WITHOUT ANY WARRANTY; without even the implied warranty of
+      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+      General Public License for more details.
+      
+      Modified from an incomplete driver for Interphase 5575 1KVC 1M card which 
+      was originally written by Monalisa Agrawal at UNH. Now this driver 
+      supports a variety of varients of Interphase ATM PCI (i)Chip adapter 
+      card family (See www.iphase.com/products/ClassSheet.cfm?ClassID=ATM) 
+      in terms of PHY type, the size of control memory and the size of 
+      packet memory. The followings are the change log and history:
+     
+          Bugfix the Mona's UBR driver.
+          Modify the basic memory allocation and dma logic.
+          Port the driver to the latest kernel from 2.0.46.
+          Complete the ABR logic of the driver, and added the ABR work-
+              around for the hardware anormalies.
+          Add the CBR support.
+	  Add the flow control logic to the driver to allow rate-limit VC.
+          Add 4K VC support to the board with 512K control memory.
+          Add the support of all the variants of the Interphase ATM PCI 
+          (i)Chip adapter cards including x575 (155M OC3 and UTP155), x525
+          (25M UTP25) and x531 (DS3 and E3).
+          Add SMP support.
+
+      Support and updates available at: ftp://ftp.iphase.com/pub/atm
+
+*******************************************************************************/
+
+#include <linux/module.h>  
+#include <linux/kernel.h>  
+#include <linux/mm.h>  
+#include <linux/pci.h>  
+#include <linux/errno.h>  
+#include <linux/atm.h>  
+#include <linux/atmdev.h>  
+#include <linux/sonet.h>  
+#include <linux/skbuff.h>  
+#include <linux/time.h>  
+#include <linux/delay.h>  
+#include <linux/uio.h>  
+#include <linux/init.h>  
+#include <linux/wait.h>
+#include <asm/system.h>  
+#include <asm/io.h>  
+#include <asm/atomic.h>  
+#include <asm/uaccess.h>  
+#include <asm/string.h>  
+#include <asm/byteorder.h>  
+#include <linux/vmalloc.h>  
+#include "iphase.h"		  
+#include "suni.h"		  
+#define swap(x) (((x & 0xff) << 8) | ((x & 0xff00) >> 8))  
+struct suni_priv {
+        struct k_sonet_stats sonet_stats; /* link diagnostics */
+        unsigned char loop_mode;        /* loopback mode */
+        struct atm_dev *dev;            /* device back-pointer */
+        struct suni_priv *next;         /* next SUNI */
+}; 
+#define PRIV(dev) ((struct suni_priv *) dev->phy_data)
+
+static unsigned char ia_phy_get(struct atm_dev *dev, unsigned long addr);
+static void desc_dbg(IADEV *iadev);
+
+static IADEV *ia_dev[8];
+static struct atm_dev *_ia_dev[8];
+static int iadev_count;
+static void ia_led_timer(unsigned long arg);
+static struct timer_list ia_timer = TIMER_INITIALIZER(ia_led_timer, 0, 0);
+static int IA_TX_BUF = DFL_TX_BUFFERS, IA_TX_BUF_SZ = DFL_TX_BUF_SZ;
+static int IA_RX_BUF = DFL_RX_BUFFERS, IA_RX_BUF_SZ = DFL_RX_BUF_SZ;
+static uint IADebugFlag = /* IF_IADBG_ERR | IF_IADBG_CBR| IF_IADBG_INIT_ADAPTER
+            |IF_IADBG_ABR | IF_IADBG_EVENT*/ 0; 
+
+module_param(IA_TX_BUF, int, 0);
+module_param(IA_TX_BUF_SZ, int, 0);
+module_param(IA_RX_BUF, int, 0);
+module_param(IA_RX_BUF_SZ, int, 0);
+module_param(IADebugFlag, uint, 0644);
+
+MODULE_LICENSE("GPL");
+
+#if BITS_PER_LONG != 32
+#  error FIXME: this driver only works on 32-bit platforms
+#endif
+
+/**************************** IA_LIB **********************************/
+
+static void ia_init_rtn_q (IARTN_Q *que) 
+{ 
+   que->next = NULL; 
+   que->tail = NULL; 
+}
+
+static void ia_enque_head_rtn_q (IARTN_Q *que, IARTN_Q * data) 
+{
+   data->next = NULL;
+   if (que->next == NULL) 
+      que->next = que->tail = data;
+   else {
+      data->next = que->next;
+      que->next = data;
+   } 
+   return;
+}
+
+static int ia_enque_rtn_q (IARTN_Q *que, struct desc_tbl_t data) {
+   IARTN_Q *entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+   if (!entry) return -1;
+   entry->data = data;
+   entry->next = NULL;
+   if (que->next == NULL) 
+      que->next = que->tail = entry;
+   else {
+      que->tail->next = entry;
+      que->tail = que->tail->next;
+   }      
+   return 1;
+}
+
+static IARTN_Q * ia_deque_rtn_q (IARTN_Q *que) {
+   IARTN_Q *tmpdata;
+   if (que->next == NULL)
+      return NULL;
+   tmpdata = que->next;
+   if ( que->next == que->tail)  
+      que->next = que->tail = NULL;
+   else 
+      que->next = que->next->next;
+   return tmpdata;
+}
+
+static void ia_hack_tcq(IADEV *dev) {
+
+  u_short 		desc1;
+  u_short		tcq_wr;
+  struct ia_vcc         *iavcc_r = NULL; 
+
+  tcq_wr = readl(dev->seg_reg+TCQ_WR_PTR) & 0xffff;
+  while (dev->host_tcq_wr != tcq_wr) {
+     desc1 = *(u_short *)(dev->seg_ram + dev->host_tcq_wr);
+     if (!desc1) ;
+     else if (!dev->desc_tbl[desc1 -1].timestamp) {
+        IF_ABR(printk(" Desc %d is reset at %ld\n", desc1 -1, jiffies);)
+        *(u_short *) (dev->seg_ram + dev->host_tcq_wr) = 0;
+     }                                 
+     else if (dev->desc_tbl[desc1 -1].timestamp) {
+        if (!(iavcc_r = dev->desc_tbl[desc1 -1].iavcc)) { 
+           printk("IA: Fatal err in get_desc\n");
+           continue;
+        }
+        iavcc_r->vc_desc_cnt--;
+        dev->desc_tbl[desc1 -1].timestamp = 0;
+        IF_EVENT(printk("ia_hack: return_q skb = 0x%x desc = %d\n", 
+                                   (u32)dev->desc_tbl[desc1 -1].txskb, desc1);)
+        if (iavcc_r->pcr < dev->rate_limit) {
+           IA_SKB_STATE (dev->desc_tbl[desc1-1].txskb) |= IA_TX_DONE;
+           if (ia_enque_rtn_q(&dev->tx_return_q, dev->desc_tbl[desc1 -1]) < 0)
+              printk("ia_hack_tcq: No memory available\n");
+        } 
+        dev->desc_tbl[desc1 -1].iavcc = NULL;
+        dev->desc_tbl[desc1 -1].txskb = NULL;
+     }
+     dev->host_tcq_wr += 2;
+     if (dev->host_tcq_wr > dev->ffL.tcq_ed) 
+        dev->host_tcq_wr = dev->ffL.tcq_st;
+  }
+} /* ia_hack_tcq */
+
+static u16 get_desc (IADEV *dev, struct ia_vcc *iavcc) {
+  u_short 		desc_num, i;
+  struct sk_buff        *skb;
+  struct ia_vcc         *iavcc_r = NULL; 
+  unsigned long delta;
+  static unsigned long timer = 0;
+  int ltimeout;
+
+  ia_hack_tcq (dev);
+  if(((jiffies - timer)>50)||((dev->ffL.tcq_rd==dev->host_tcq_wr))){      
+     timer = jiffies; 
+     i=0;
+     while (i < dev->num_tx_desc) {
+        if (!dev->desc_tbl[i].timestamp) {
+           i++;
+           continue;
+        }
+        ltimeout = dev->desc_tbl[i].iavcc->ltimeout; 
+        delta = jiffies - dev->desc_tbl[i].timestamp;
+        if (delta >= ltimeout) {
+           IF_ABR(printk("RECOVER run!! desc_tbl %d = %d  delta = %ld, time = %ld\n", i,dev->desc_tbl[i].timestamp, delta, jiffies);)
+           if (dev->ffL.tcq_rd == dev->ffL.tcq_st) 
+              dev->ffL.tcq_rd =  dev->ffL.tcq_ed;
+           else 
+              dev->ffL.tcq_rd -= 2;
+           *(u_short *)(dev->seg_ram + dev->ffL.tcq_rd) = i+1;
+           if (!(skb = dev->desc_tbl[i].txskb) || 
+                          !(iavcc_r = dev->desc_tbl[i].iavcc))
+              printk("Fatal err, desc table vcc or skb is NULL\n");
+           else 
+              iavcc_r->vc_desc_cnt--;
+           dev->desc_tbl[i].timestamp = 0;
+           dev->desc_tbl[i].iavcc = NULL;
+           dev->desc_tbl[i].txskb = NULL;
+        }
+        i++;
+     } /* while */
+  }
+  if (dev->ffL.tcq_rd == dev->host_tcq_wr) 
+     return 0xFFFF;
+    
+  /* Get the next available descriptor number from TCQ */
+  desc_num = *(u_short *)(dev->seg_ram + dev->ffL.tcq_rd);
+
+  while (!desc_num || (dev->desc_tbl[desc_num -1]).timestamp) {
+     dev->ffL.tcq_rd += 2;
+     if (dev->ffL.tcq_rd > dev->ffL.tcq_ed) 
+     dev->ffL.tcq_rd = dev->ffL.tcq_st;
+     if (dev->ffL.tcq_rd == dev->host_tcq_wr) 
+        return 0xFFFF; 
+     desc_num = *(u_short *)(dev->seg_ram + dev->ffL.tcq_rd);
+  }
+
+  /* get system time */
+  dev->desc_tbl[desc_num -1].timestamp = jiffies;
+  return desc_num;
+}
+
+static void clear_lockup (struct atm_vcc *vcc, IADEV *dev) {
+  u_char          	foundLockUp;
+  vcstatus_t		*vcstatus;
+  u_short               *shd_tbl;
+  u_short               tempCellSlot, tempFract;
+  struct main_vc *abr_vc = (struct main_vc *)dev->MAIN_VC_TABLE_ADDR;
+  struct ext_vc *eabr_vc = (struct ext_vc *)dev->EXT_VC_TABLE_ADDR;
+  u_int  i;
+
+  if (vcc->qos.txtp.traffic_class == ATM_ABR) {
+     vcstatus = (vcstatus_t *) &(dev->testTable[vcc->vci]->vc_status);
+     vcstatus->cnt++;
+     foundLockUp = 0;
+     if( vcstatus->cnt == 0x05 ) {
+        abr_vc += vcc->vci;
+	eabr_vc += vcc->vci;
+	if( eabr_vc->last_desc ) {
+	   if( (abr_vc->status & 0x07) == ABR_STATE /* 0x2 */ ) {
+              /* Wait for 10 Micro sec */
+              udelay(10);
+	      if ((eabr_vc->last_desc)&&((abr_vc->status & 0x07)==ABR_STATE))
+		 foundLockUp = 1;
+           }
+	   else {
+	      tempCellSlot = abr_vc->last_cell_slot;
+              tempFract    = abr_vc->fraction;
+              if((tempCellSlot == dev->testTable[vcc->vci]->lastTime)
+                         && (tempFract == dev->testTable[vcc->vci]->fract))
+	         foundLockUp = 1; 		    
+              dev->testTable[vcc->vci]->lastTime = tempCellSlot;   
+              dev->testTable[vcc->vci]->fract = tempFract; 
+	   } 	    
+        } /* last descriptor */	 	   
+        vcstatus->cnt = 0;     	
+     } /* vcstatus->cnt */
+	
+     if (foundLockUp) {
+        IF_ABR(printk("LOCK UP found\n");) 
+	writew(0xFFFD, dev->seg_reg+MODE_REG_0);
+        /* Wait for 10 Micro sec */
+        udelay(10); 
+        abr_vc->status &= 0xFFF8;
+        abr_vc->status |= 0x0001;  /* state is idle */
+	shd_tbl = (u_short *)dev->ABR_SCHED_TABLE_ADDR;                
+	for( i = 0; ((i < dev->num_vc) && (shd_tbl[i])); i++ );
+	if (i < dev->num_vc)
+           shd_tbl[i] = vcc->vci;
+        else
+           IF_ERR(printk("ABR Seg. may not continue on VC %x\n",vcc->vci);)
+        writew(T_ONLINE, dev->seg_reg+MODE_REG_0);
+        writew(~(TRANSMIT_DONE|TCQ_NOT_EMPTY), dev->seg_reg+SEG_MASK_REG);
+        writew(TRANSMIT_DONE, dev->seg_reg+SEG_INTR_STATUS_REG);       
+	vcstatus->cnt = 0;
+     } /* foundLockUp */
+
+  } /* if an ABR VC */
+
+
+}
+ 
+/*
+** Conversion of 24-bit cellrate (cells/sec) to 16-bit floating point format.
+**
+**  +----+----+------------------+-------------------------------+
+**  |  R | NZ |  5-bit exponent  |        9-bit mantissa         |
+**  +----+----+------------------+-------------------------------+
+** 
+**    R = reserverd (written as 0)
+**    NZ = 0 if 0 cells/sec; 1 otherwise
+**
+**    if NZ = 1, rate = 1.mmmmmmmmm x 2^(eeeee) cells/sec
+*/
+static u16
+cellrate_to_float(u32 cr)
+{
+
+#define	NZ 		0x4000
+#define	M_BITS		9		/* Number of bits in mantissa */
+#define	E_BITS		5		/* Number of bits in exponent */
+#define	M_MASK		0x1ff		
+#define	E_MASK		0x1f
+  u16   flot;
+  u32	tmp = cr & 0x00ffffff;
+  int 	i   = 0;
+  if (cr == 0)
+     return 0;
+  while (tmp != 1) {
+     tmp >>= 1;
+     i++;
+  }
+  if (i == M_BITS)
+     flot = NZ | (i << M_BITS) | (cr & M_MASK);
+  else if (i < M_BITS)
+     flot = NZ | (i << M_BITS) | ((cr << (M_BITS - i)) & M_MASK);
+  else
+     flot = NZ | (i << M_BITS) | ((cr >> (i - M_BITS)) & M_MASK);
+  return flot;
+}
+
+#if 0
+/*
+** Conversion of 16-bit floating point format to 24-bit cellrate (cells/sec).
+*/
+static u32
+float_to_cellrate(u16 rate)
+{
+  u32   exp, mantissa, cps;
+  if ((rate & NZ) == 0)
+     return 0;
+  exp = (rate >> M_BITS) & E_MASK;
+  mantissa = rate & M_MASK;
+  if (exp == 0)
+     return 1;
+  cps = (1 << M_BITS) | mantissa;
+  if (exp == M_BITS)
+     cps = cps;
+  else if (exp > M_BITS)
+     cps <<= (exp - M_BITS);
+  else
+     cps >>= (M_BITS - exp);
+  return cps;
+}
+#endif 
+
+static void init_abr_vc (IADEV *dev, srv_cls_param_t *srv_p) {
+  srv_p->class_type = ATM_ABR;
+  srv_p->pcr        = dev->LineRate;
+  srv_p->mcr        = 0;
+  srv_p->icr        = 0x055cb7;
+  srv_p->tbe        = 0xffffff;
+  srv_p->frtt       = 0x3a;
+  srv_p->rif        = 0xf;
+  srv_p->rdf        = 0xb;
+  srv_p->nrm        = 0x4;
+  srv_p->trm        = 0x7;
+  srv_p->cdf        = 0x3;
+  srv_p->adtf       = 50;
+}
+
+static int
+ia_open_abr_vc(IADEV *dev, srv_cls_param_t *srv_p, 
+                                                struct atm_vcc *vcc, u8 flag)
+{
+  f_vc_abr_entry  *f_abr_vc;
+  r_vc_abr_entry  *r_abr_vc;
+  u32		icr;
+  u8		trm, nrm, crm;
+  u16		adtf, air, *ptr16;	
+  f_abr_vc =(f_vc_abr_entry *)dev->MAIN_VC_TABLE_ADDR;
+  f_abr_vc += vcc->vci;       
+  switch (flag) {
+     case 1: /* FFRED initialization */
+#if 0  /* sanity check */
+       if (srv_p->pcr == 0)
+          return INVALID_PCR;
+       if (srv_p->pcr > dev->LineRate)
+          srv_p->pcr = dev->LineRate;
+       if ((srv_p->mcr + dev->sum_mcr) > dev->LineRate)
+	  return MCR_UNAVAILABLE;
+       if (srv_p->mcr > srv_p->pcr)
+	  return INVALID_MCR;
+       if (!(srv_p->icr))
+	  srv_p->icr = srv_p->pcr;
+       if ((srv_p->icr < srv_p->mcr) || (srv_p->icr > srv_p->pcr))
+	  return INVALID_ICR;
+       if ((srv_p->tbe < MIN_TBE) || (srv_p->tbe > MAX_TBE))
+	  return INVALID_TBE;
+       if ((srv_p->frtt < MIN_FRTT) || (srv_p->frtt > MAX_FRTT))
+	  return INVALID_FRTT;
+       if (srv_p->nrm > MAX_NRM)
+	  return INVALID_NRM;
+       if (srv_p->trm > MAX_TRM)
+	  return INVALID_TRM;
+       if (srv_p->adtf > MAX_ADTF)
+          return INVALID_ADTF;
+       else if (srv_p->adtf == 0)
+	  srv_p->adtf = 1;
+       if (srv_p->cdf > MAX_CDF)
+	  return INVALID_CDF;
+       if (srv_p->rif > MAX_RIF)
+	  return INVALID_RIF;
+       if (srv_p->rdf > MAX_RDF)
+	  return INVALID_RDF;
+#endif
+       memset ((caddr_t)f_abr_vc, 0, sizeof(*f_abr_vc));
+       f_abr_vc->f_vc_type = ABR;
+       nrm = 2 << srv_p->nrm;     /* (2 ** (srv_p->nrm +1)) */
+			          /* i.e 2**n = 2 << (n-1) */
+       f_abr_vc->f_nrm = nrm << 8 | nrm;
+       trm = 100000/(2 << (16 - srv_p->trm));
+       if ( trm == 0) trm = 1;
+       f_abr_vc->f_nrmexp =(((srv_p->nrm +1) & 0x0f) << 12)|(MRM << 8) | trm;
+       crm = srv_p->tbe / nrm;
+       if (crm == 0) crm = 1;
+       f_abr_vc->f_crm = crm & 0xff;
+       f_abr_vc->f_pcr = cellrate_to_float(srv_p->pcr);
+       icr = min( srv_p->icr, (srv_p->tbe > srv_p->frtt) ?
+				((srv_p->tbe/srv_p->frtt)*1000000) :
+				(1000000/(srv_p->frtt/srv_p->tbe)));
+       f_abr_vc->f_icr = cellrate_to_float(icr);
+       adtf = (10000 * srv_p->adtf)/8192;
+       if (adtf == 0) adtf = 1; 
+       f_abr_vc->f_cdf = ((7 - srv_p->cdf) << 12 | adtf) & 0xfff;
+       f_abr_vc->f_mcr = cellrate_to_float(srv_p->mcr);
+       f_abr_vc->f_acr = f_abr_vc->f_icr;
+       f_abr_vc->f_status = 0x0042;
+       break;
+    case 0: /* RFRED initialization */	
+       ptr16 = (u_short *)(dev->reass_ram + REASS_TABLE*dev->memSize); 
+       *(ptr16 + vcc->vci) = NO_AAL5_PKT | REASS_ABR;
+       r_abr_vc = (r_vc_abr_entry*)(dev->reass_ram+ABR_VC_TABLE*dev->memSize);
+       r_abr_vc += vcc->vci;
+       r_abr_vc->r_status_rdf = (15 - srv_p->rdf) & 0x000f;
+       air = srv_p->pcr << (15 - srv_p->rif);
+       if (air == 0) air = 1;
+       r_abr_vc->r_air = cellrate_to_float(air);
+       dev->testTable[vcc->vci]->vc_status = VC_ACTIVE | VC_ABR;
+       dev->sum_mcr	   += srv_p->mcr;
+       dev->n_abr++;
+       break;
+    default:
+       break;
+  }
+  return	0;
+}
+static int ia_cbr_setup (IADEV *dev, struct atm_vcc *vcc) {
+   u32 rateLow=0, rateHigh, rate;
+   int entries;
+   struct ia_vcc *ia_vcc;
+
+   int   idealSlot =0, testSlot, toBeAssigned, inc;
+   u32   spacing;
+   u16  *SchedTbl, *TstSchedTbl;
+   u16  cbrVC, vcIndex;
+   u32   fracSlot    = 0;
+   u32   sp_mod      = 0;
+   u32   sp_mod2     = 0;
+
+   /* IpAdjustTrafficParams */
+   if (vcc->qos.txtp.max_pcr <= 0) {
+      IF_ERR(printk("PCR for CBR not defined\n");)
+      return -1;
+   }
+   rate = vcc->qos.txtp.max_pcr;
+   entries = rate / dev->Granularity;
+   IF_CBR(printk("CBR: CBR entries=0x%x for rate=0x%x & Gran=0x%x\n",
+                                entries, rate, dev->Granularity);)
+   if (entries < 1)
+      IF_CBR(printk("CBR: Bandwidth smaller than granularity of CBR table\n");) 
+   rateLow  =  entries * dev->Granularity;
+   rateHigh = (entries + 1) * dev->Granularity;
+   if (3*(rate - rateLow) > (rateHigh - rate))
+      entries++;
+   if (entries > dev->CbrRemEntries) {
+      IF_CBR(printk("CBR: Not enough bandwidth to support this PCR.\n");)
+      IF_CBR(printk("Entries = 0x%x, CbrRemEntries = 0x%x.\n",
+                                       entries, dev->CbrRemEntries);)
+      return -EBUSY;
+   }   
+
+   ia_vcc = INPH_IA_VCC(vcc);
+   ia_vcc->NumCbrEntry = entries; 
+   dev->sum_mcr += entries * dev->Granularity; 
+   /* IaFFrednInsertCbrSched */
+   // Starting at an arbitrary location, place the entries into the table
+   // as smoothly as possible
+   cbrVC   = 0;
+   spacing = dev->CbrTotEntries / entries;
+   sp_mod  = dev->CbrTotEntries % entries; // get modulo
+   toBeAssigned = entries;
+   fracSlot = 0;
+   vcIndex  = vcc->vci;
+   IF_CBR(printk("Vci=0x%x,Spacing=0x%x,Sp_mod=0x%x\n",vcIndex,spacing,sp_mod);)
+   while (toBeAssigned)
+   {
+      // If this is the first time, start the table loading for this connection
+      // as close to entryPoint as possible.
+      if (toBeAssigned == entries)
+      {
+         idealSlot = dev->CbrEntryPt;
+         dev->CbrEntryPt += 2;    // Adding 2 helps to prevent clumping
+         if (dev->CbrEntryPt >= dev->CbrTotEntries) 
+            dev->CbrEntryPt -= dev->CbrTotEntries;// Wrap if necessary
+      } else {
+         idealSlot += (u32)(spacing + fracSlot); // Point to the next location
+         // in the table that would be  smoothest
+         fracSlot = ((sp_mod + sp_mod2) / entries);  // get new integer part
+         sp_mod2  = ((sp_mod + sp_mod2) % entries);  // calc new fractional part
+      }
+      if (idealSlot >= (int)dev->CbrTotEntries) 
+         idealSlot -= dev->CbrTotEntries;  
+      // Continuously check around this ideal value until a null
+      // location is encountered.
+      SchedTbl = (u16*)(dev->seg_ram+CBR_SCHED_TABLE*dev->memSize); 
+      inc = 0;
+      testSlot = idealSlot;
+      TstSchedTbl = (u16*)(SchedTbl+testSlot);  //set index and read in value
+      IF_CBR(printk("CBR Testslot 0x%x AT Location 0x%x, NumToAssign=%d\n",
+                                testSlot, (u32)TstSchedTbl,toBeAssigned);) 
+      memcpy((caddr_t)&cbrVC,(caddr_t)TstSchedTbl,sizeof(cbrVC));
+      while (cbrVC)  // If another VC at this location, we have to keep looking
+      {
+          inc++;
+          testSlot = idealSlot - inc;
+          if (testSlot < 0) { // Wrap if necessary
+             testSlot += dev->CbrTotEntries;
+             IF_CBR(printk("Testslot Wrap. STable Start=0x%x,Testslot=%d\n",
+                                                       (u32)SchedTbl,testSlot);)
+          }
+          TstSchedTbl = (u16 *)(SchedTbl + testSlot);  // set table index
+          memcpy((caddr_t)&cbrVC,(caddr_t)TstSchedTbl,sizeof(cbrVC)); 
+          if (!cbrVC)
+             break;
+          testSlot = idealSlot + inc;
+          if (testSlot >= (int)dev->CbrTotEntries) { // Wrap if necessary
+             testSlot -= dev->CbrTotEntries;
+             IF_CBR(printk("TotCbrEntries=%d",dev->CbrTotEntries);)
+             IF_CBR(printk(" Testslot=0x%x ToBeAssgned=%d\n", 
+                                            testSlot, toBeAssigned);)
+          } 
+          // set table index and read in value
+          TstSchedTbl = (u16*)(SchedTbl + testSlot);
+          IF_CBR(printk("Reading CBR Tbl from 0x%x, CbrVal=0x%x Iteration %d\n",
+                          (u32)TstSchedTbl,cbrVC,inc);) 
+          memcpy((caddr_t)&cbrVC,(caddr_t)TstSchedTbl,sizeof(cbrVC));
+       } /* while */
+       // Move this VCI number into this location of the CBR Sched table.
+       memcpy((caddr_t)TstSchedTbl, (caddr_t)&vcIndex,sizeof(TstSchedTbl));
+       dev->CbrRemEntries--;
+       toBeAssigned--;
+   } /* while */ 
+
+   /* IaFFrednCbrEnable */
+   dev->NumEnabledCBR++;
+   if (dev->NumEnabledCBR == 1) {
+       writew((CBR_EN | UBR_EN | ABR_EN | (0x23 << 2)), dev->seg_reg+STPARMS);
+       IF_CBR(printk("CBR is enabled\n");)
+   }
+   return 0;
+}
+static void ia_cbrVc_close (struct atm_vcc *vcc) {
+   IADEV *iadev;
+   u16 *SchedTbl, NullVci = 0;
+   u32 i, NumFound;
+
+   iadev = INPH_IA_DEV(vcc->dev);
+   iadev->NumEnabledCBR--;
+   SchedTbl = (u16*)(iadev->seg_ram+CBR_SCHED_TABLE*iadev->memSize);
+   if (iadev->NumEnabledCBR == 0) {
+      writew((UBR_EN | ABR_EN | (0x23 << 2)), iadev->seg_reg+STPARMS);
+      IF_CBR (printk("CBR support disabled\n");)
+   }
+   NumFound = 0;
+   for (i=0; i < iadev->CbrTotEntries; i++)
+   {
+      if (*SchedTbl == vcc->vci) {
+         iadev->CbrRemEntries++;
+         *SchedTbl = NullVci;
+         IF_CBR(NumFound++;)
+      }
+      SchedTbl++;   
+   } 
+   IF_CBR(printk("Exit ia_cbrVc_close, NumRemoved=%d\n",NumFound);)
+}
+
+static int ia_avail_descs(IADEV *iadev) {
+   int tmp = 0;
+   ia_hack_tcq(iadev);
+   if (iadev->host_tcq_wr >= iadev->ffL.tcq_rd)
+      tmp = (iadev->host_tcq_wr - iadev->ffL.tcq_rd) / 2;
+   else
+      tmp = (iadev->ffL.tcq_ed - iadev->ffL.tcq_rd + 2 + iadev->host_tcq_wr -
+                   iadev->ffL.tcq_st) / 2;
+   return tmp;
+}    
+
+static int ia_pkt_tx (struct atm_vcc *vcc, struct sk_buff *skb);
+
+static int ia_que_tx (IADEV *iadev) { 
+   struct sk_buff *skb;
+   int num_desc;
+   struct atm_vcc *vcc;
+   struct ia_vcc *iavcc;
+   num_desc = ia_avail_descs(iadev);
+
+   while (num_desc && (skb = skb_dequeue(&iadev->tx_backlog))) {
+      if (!(vcc = ATM_SKB(skb)->vcc)) {
+         dev_kfree_skb_any(skb);
+         printk("ia_que_tx: Null vcc\n");
+         break;
+      }
+      if (!test_bit(ATM_VF_READY,&vcc->flags)) {
+         dev_kfree_skb_any(skb);
+         printk("Free the SKB on closed vci %d \n", vcc->vci);
+         break;
+      }
+      iavcc = INPH_IA_VCC(vcc);
+      if (ia_pkt_tx (vcc, skb)) {
+         skb_queue_head(&iadev->tx_backlog, skb);
+      }
+      num_desc--;
+   }
+   return 0;
+}
+
+static void ia_tx_poll (IADEV *iadev) {
+   struct atm_vcc *vcc = NULL;
+   struct sk_buff *skb = NULL, *skb1 = NULL;
+   struct ia_vcc *iavcc;
+   IARTN_Q *  rtne;
+
+   ia_hack_tcq(iadev);
+   while ( (rtne = ia_deque_rtn_q(&iadev->tx_return_q))) {
+       skb = rtne->data.txskb;
+       if (!skb) {
+           printk("ia_tx_poll: skb is null\n");
+           goto out;
+       }
+       vcc = ATM_SKB(skb)->vcc;
+       if (!vcc) {
+           printk("ia_tx_poll: vcc is null\n");
+           dev_kfree_skb_any(skb);
+	   goto out;
+       }
+
+       iavcc = INPH_IA_VCC(vcc);
+       if (!iavcc) {
+           printk("ia_tx_poll: iavcc is null\n");
+           dev_kfree_skb_any(skb);
+	   goto out;
+       }
+
+       skb1 = skb_dequeue(&iavcc->txing_skb);
+       while (skb1 && (skb1 != skb)) {
+          if (!(IA_SKB_STATE(skb1) & IA_TX_DONE)) {
+             printk("IA_tx_intr: Vci %d lost pkt!!!\n", vcc->vci);
+          }
+          IF_ERR(printk("Release the SKB not match\n");)
+          if ((vcc->pop) && (skb1->len != 0))
+          {
+             vcc->pop(vcc, skb1);
+             IF_EVENT(printk("Tansmit Done - skb 0x%lx return\n",
+                                                          (long)skb1);)
+          }
+          else 
+             dev_kfree_skb_any(skb1);
+          skb1 = skb_dequeue(&iavcc->txing_skb);
+       }                                                        
+       if (!skb1) {
+          IF_EVENT(printk("IA: Vci %d - skb not found requed\n",vcc->vci);)
+          ia_enque_head_rtn_q (&iadev->tx_return_q, rtne);
+          break;
+       }
+       if ((vcc->pop) && (skb->len != 0))
+       {
+          vcc->pop(vcc, skb);
+          IF_EVENT(printk("Tx Done - skb 0x%lx return\n",(long)skb);)
+       }
+       else 
+          dev_kfree_skb_any(skb);
+       kfree(rtne);
+    }
+    ia_que_tx(iadev);
+out:
+    return;
+}
+#if 0
+static void ia_eeprom_put (IADEV *iadev, u32 addr, u_short val)
+{
+        u32	t;
+	int	i;
+	/*
+	 * Issue a command to enable writes to the NOVRAM
+	 */
+	NVRAM_CMD (EXTEND + EWEN);
+	NVRAM_CLR_CE;
+	/*
+	 * issue the write command
+	 */
+	NVRAM_CMD(IAWRITE + addr);
+	/* 
+	 * Send the data, starting with D15, then D14, and so on for 16 bits
+	 */
+	for (i=15; i>=0; i--) {
+		NVRAM_CLKOUT (val & 0x8000);
+		val <<= 1;
+	}
+	NVRAM_CLR_CE;
+	CFG_OR(NVCE);
+	t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); 
+	while (!(t & NVDO))
+		t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); 
+
+	NVRAM_CLR_CE;
+	/*
+	 * disable writes again
+	 */
+	NVRAM_CMD(EXTEND + EWDS)
+	NVRAM_CLR_CE;
+	CFG_AND(~NVDI);
+}
+#endif
+
+static u16 ia_eeprom_get (IADEV *iadev, u32 addr)
+{
+	u_short	val;
+        u32	t;
+	int	i;
+	/*
+	 * Read the first bit that was clocked with the falling edge of the
+	 * the last command data clock
+	 */
+	NVRAM_CMD(IAREAD + addr);
+	/*
+	 * Now read the rest of the bits, the next bit read is D14, then D13,
+	 * and so on.
+	 */
+	val = 0;
+	for (i=15; i>=0; i--) {
+		NVRAM_CLKIN(t);
+		val |= (t << i);
+	}
+	NVRAM_CLR_CE;
+	CFG_AND(~NVDI);
+	return val;
+}
+
+static void ia_hw_type(IADEV *iadev) {
+   u_short memType = ia_eeprom_get(iadev, 25);   
+   iadev->memType = memType;
+   if ((memType & MEM_SIZE_MASK) == MEM_SIZE_1M) {
+      iadev->num_tx_desc = IA_TX_BUF;
+      iadev->tx_buf_sz = IA_TX_BUF_SZ;
+      iadev->num_rx_desc = IA_RX_BUF;
+      iadev->rx_buf_sz = IA_RX_BUF_SZ; 
+   } else if ((memType & MEM_SIZE_MASK) == MEM_SIZE_512K) {
+      if (IA_TX_BUF == DFL_TX_BUFFERS)
+        iadev->num_tx_desc = IA_TX_BUF / 2;
+      else 
+        iadev->num_tx_desc = IA_TX_BUF;
+      iadev->tx_buf_sz = IA_TX_BUF_SZ;
+      if (IA_RX_BUF == DFL_RX_BUFFERS)
+        iadev->num_rx_desc = IA_RX_BUF / 2;
+      else
+        iadev->num_rx_desc = IA_RX_BUF;
+      iadev->rx_buf_sz = IA_RX_BUF_SZ;
+   }
+   else {
+      if (IA_TX_BUF == DFL_TX_BUFFERS) 
+        iadev->num_tx_desc = IA_TX_BUF / 8;
+      else
+        iadev->num_tx_desc = IA_TX_BUF;
+      iadev->tx_buf_sz = IA_TX_BUF_SZ;
+      if (IA_RX_BUF == DFL_RX_BUFFERS)
+        iadev->num_rx_desc = IA_RX_BUF / 8;
+      else
+        iadev->num_rx_desc = IA_RX_BUF;
+      iadev->rx_buf_sz = IA_RX_BUF_SZ; 
+   } 
+   iadev->rx_pkt_ram = TX_PACKET_RAM + (iadev->num_tx_desc * iadev->tx_buf_sz); 
+   IF_INIT(printk("BUF: tx=%d,sz=%d rx=%d sz= %d rx_pkt_ram=%d\n",
+         iadev->num_tx_desc, iadev->tx_buf_sz, iadev->num_rx_desc,
+         iadev->rx_buf_sz, iadev->rx_pkt_ram);)
+
+#if 0
+   if ((memType & FE_MASK) == FE_SINGLE_MODE) {
+      iadev->phy_type = PHY_OC3C_S;
+   else if ((memType & FE_MASK) == FE_UTP_OPTION)
+      iadev->phy_type = PHY_UTP155;
+   else
+     iadev->phy_type = PHY_OC3C_M;
+#endif
+   
+   iadev->phy_type = memType & FE_MASK;
+   IF_INIT(printk("memType = 0x%x iadev->phy_type = 0x%x\n", 
+                                         memType,iadev->phy_type);)
+   if (iadev->phy_type == FE_25MBIT_PHY) 
+      iadev->LineRate = (u32)(((25600000/8)*26)/(27*53));
+   else if (iadev->phy_type == FE_DS3_PHY)
+      iadev->LineRate = (u32)(((44736000/8)*26)/(27*53));
+   else if (iadev->phy_type == FE_E3_PHY) 
+      iadev->LineRate = (u32)(((34368000/8)*26)/(27*53));
+   else
+       iadev->LineRate = (u32)(ATM_OC3_PCR);
+   IF_INIT(printk("iadev->LineRate = %d \n", iadev->LineRate);)
+
+}
+
+static void IaFrontEndIntr(IADEV *iadev) {
+  volatile IA_SUNI *suni;
+  volatile ia_mb25_t *mb25;
+  volatile suni_pm7345_t *suni_pm7345;
+  u32 intr_status;
+  u_int frmr_intr;
+
+  if(iadev->phy_type & FE_25MBIT_PHY) {
+     mb25 = (ia_mb25_t*)iadev->phy;
+     iadev->carrier_detect =  Boolean(mb25->mb25_intr_status & MB25_IS_GSB);
+  } else if (iadev->phy_type & FE_DS3_PHY) {
+     suni_pm7345 = (suni_pm7345_t *)iadev->phy;
+     /* clear FRMR interrupts */
+     frmr_intr   = suni_pm7345->suni_ds3_frm_intr_stat; 
+     iadev->carrier_detect =  
+           Boolean(!(suni_pm7345->suni_ds3_frm_stat & SUNI_DS3_LOSV));
+  } else if (iadev->phy_type & FE_E3_PHY ) {
+     suni_pm7345 = (suni_pm7345_t *)iadev->phy;
+     frmr_intr   = suni_pm7345->suni_e3_frm_maint_intr_ind;
+     iadev->carrier_detect =
+           Boolean(!(suni_pm7345->suni_e3_frm_fram_intr_ind_stat&SUNI_E3_LOS));
+  }
+  else { 
+     suni = (IA_SUNI *)iadev->phy;
+     intr_status = suni->suni_rsop_status & 0xff;
+     iadev->carrier_detect = Boolean(!(suni->suni_rsop_status & SUNI_LOSV));
+  }
+  if (iadev->carrier_detect)
+    printk("IA: SUNI carrier detected\n");
+  else
+    printk("IA: SUNI carrier lost signal\n"); 
+  return;
+}
+
+static void ia_mb25_init (IADEV *iadev)
+{
+   volatile ia_mb25_t  *mb25 = (ia_mb25_t*)iadev->phy;
+#if 0
+   mb25->mb25_master_ctrl = MB25_MC_DRIC | MB25_MC_DREC | MB25_MC_ENABLED;
+#endif
+   mb25->mb25_master_ctrl = MB25_MC_DRIC | MB25_MC_DREC;
+   mb25->mb25_diag_control = 0;
+   /*
+    * Initialize carrier detect state
+    */
+   iadev->carrier_detect =  Boolean(mb25->mb25_intr_status & MB25_IS_GSB);
+   return;
+}                   
+
+static void ia_suni_pm7345_init (IADEV *iadev)
+{
+   volatile suni_pm7345_t *suni_pm7345 = (suni_pm7345_t *)iadev->phy;
+   if (iadev->phy_type & FE_DS3_PHY)
+   {
+      iadev->carrier_detect = 
+          Boolean(!(suni_pm7345->suni_ds3_frm_stat & SUNI_DS3_LOSV)); 
+      suni_pm7345->suni_ds3_frm_intr_enbl = 0x17;
+      suni_pm7345->suni_ds3_frm_cfg = 1;
+      suni_pm7345->suni_ds3_tran_cfg = 1;
+      suni_pm7345->suni_config = 0;
+      suni_pm7345->suni_splr_cfg = 0;
+      suni_pm7345->suni_splt_cfg = 0;
+   }
+   else 
+   {
+      iadev->carrier_detect = 
+          Boolean(!(suni_pm7345->suni_e3_frm_fram_intr_ind_stat & SUNI_E3_LOS));
+      suni_pm7345->suni_e3_frm_fram_options = 0x4;
+      suni_pm7345->suni_e3_frm_maint_options = 0x20;
+      suni_pm7345->suni_e3_frm_fram_intr_enbl = 0x1d;
+      suni_pm7345->suni_e3_frm_maint_intr_enbl = 0x30;
+      suni_pm7345->suni_e3_tran_stat_diag_options = 0x0;
+      suni_pm7345->suni_e3_tran_fram_options = 0x1;
+      suni_pm7345->suni_config = SUNI_PM7345_E3ENBL;
+      suni_pm7345->suni_splr_cfg = 0x41;
+      suni_pm7345->suni_splt_cfg = 0x41;
+   } 
+   /*
+    * Enable RSOP loss of signal interrupt.
+    */
+   suni_pm7345->suni_intr_enbl = 0x28;
+ 
+   /*
+    * Clear error counters
+    */
+   suni_pm7345->suni_id_reset = 0;
+
+   /*
+    * Clear "PMCTST" in master test register.
+    */
+   suni_pm7345->suni_master_test = 0;
+
+   suni_pm7345->suni_rxcp_ctrl = 0x2c;
+   suni_pm7345->suni_rxcp_fctrl = 0x81;
+ 
+   suni_pm7345->suni_rxcp_idle_pat_h1 =
+   	suni_pm7345->suni_rxcp_idle_pat_h2 =
+   	suni_pm7345->suni_rxcp_idle_pat_h3 = 0;
+   suni_pm7345->suni_rxcp_idle_pat_h4 = 1;
+ 
+   suni_pm7345->suni_rxcp_idle_mask_h1 = 0xff;
+   suni_pm7345->suni_rxcp_idle_mask_h2 = 0xff;
+   suni_pm7345->suni_rxcp_idle_mask_h3 = 0xff;
+   suni_pm7345->suni_rxcp_idle_mask_h4 = 0xfe;
+ 
+   suni_pm7345->suni_rxcp_cell_pat_h1 =
+   	suni_pm7345->suni_rxcp_cell_pat_h2 =
+   	suni_pm7345->suni_rxcp_cell_pat_h3 = 0;
+   suni_pm7345->suni_rxcp_cell_pat_h4 = 1;
+ 
+   suni_pm7345->suni_rxcp_cell_mask_h1 =
+   	suni_pm7345->suni_rxcp_cell_mask_h2 =
+   	suni_pm7345->suni_rxcp_cell_mask_h3 =
+   	suni_pm7345->suni_rxcp_cell_mask_h4 = 0xff;
+ 
+   suni_pm7345->suni_txcp_ctrl = 0xa4;
+   suni_pm7345->suni_txcp_intr_en_sts = 0x10;
+   suni_pm7345->suni_txcp_idle_pat_h5 = 0x55;
+ 
+   suni_pm7345->suni_config &= ~(SUNI_PM7345_LLB |
+                                 SUNI_PM7345_CLB |
+                                 SUNI_PM7345_DLB |
+                                  SUNI_PM7345_PLB);
+#ifdef __SNMP__
+   suni_pm7345->suni_rxcp_intr_en_sts |= SUNI_OOCDE;
+#endif /* __SNMP__ */
+   return;
+}
+
+
+/***************************** IA_LIB END *****************************/
+    
+static int tcnter = 0;
+static void xdump( u_char*  cp, int  length, char*  prefix )
+{
+    int col, count;
+    u_char prntBuf[120];
+    u_char*  pBuf = prntBuf;
+    count = 0;
+    while(count < length){
+        pBuf += sprintf( pBuf, "%s", prefix );
+        for(col = 0;count + col < length && col < 16; col++){
+            if (col != 0 && (col % 4) == 0)
+                pBuf += sprintf( pBuf, " " );
+            pBuf += sprintf( pBuf, "%02X ", cp[count + col] );
+        }
+        while(col++ < 16){      /* pad end of buffer with blanks */
+            if ((col % 4) == 0)
+                sprintf( pBuf, " " );
+            pBuf += sprintf( pBuf, "   " );
+        }
+        pBuf += sprintf( pBuf, "  " );
+        for(col = 0;count + col < length && col < 16; col++){
+            if (isprint((int)cp[count + col]))
+                pBuf += sprintf( pBuf, "%c", cp[count + col] );
+            else
+                pBuf += sprintf( pBuf, "." );
+                }
+        sprintf( pBuf, "\n" );
+        // SPrint(prntBuf);
+        printk(prntBuf);
+        count += col;
+        pBuf = prntBuf;
+    }
+
+}  /* close xdump(... */
+
+  
+static struct atm_dev *ia_boards = NULL;  
+  
+#define ACTUAL_RAM_BASE \
+	RAM_BASE*((iadev->mem)/(128 * 1024))  
+#define ACTUAL_SEG_RAM_BASE \
+	IPHASE5575_FRAG_CONTROL_RAM_BASE*((iadev->mem)/(128 * 1024))  
+#define ACTUAL_REASS_RAM_BASE \
+	IPHASE5575_REASS_CONTROL_RAM_BASE*((iadev->mem)/(128 * 1024))  
+  
+  
+/*-- some utilities and memory allocation stuff will come here -------------*/  
+  
+static void desc_dbg(IADEV *iadev) {
+
+  u_short tcq_wr_ptr, tcq_st_ptr, tcq_ed_ptr;
+  u32 i;
+  void __iomem *tmp;
+  // regval = readl((u32)ia_cmds->maddr);
+  tcq_wr_ptr =  readw(iadev->seg_reg+TCQ_WR_PTR);
+  printk("B_tcq_wr = 0x%x desc = %d last desc = %d\n",
+                     tcq_wr_ptr, readw(iadev->seg_ram+tcq_wr_ptr),
+                     readw(iadev->seg_ram+tcq_wr_ptr-2));
+  printk(" host_tcq_wr = 0x%x  host_tcq_rd = 0x%x \n",  iadev->host_tcq_wr, 
+                   iadev->ffL.tcq_rd);
+  tcq_st_ptr =  readw(iadev->seg_reg+TCQ_ST_ADR);
+  tcq_ed_ptr =  readw(iadev->seg_reg+TCQ_ED_ADR);
+  printk("tcq_st_ptr = 0x%x    tcq_ed_ptr = 0x%x \n", tcq_st_ptr, tcq_ed_ptr);
+  i = 0;
+  while (tcq_st_ptr != tcq_ed_ptr) {
+      tmp = iadev->seg_ram+tcq_st_ptr;
+      printk("TCQ slot %d desc = %d  Addr = %p\n", i++, readw(tmp), tmp);
+      tcq_st_ptr += 2;
+  }
+  for(i=0; i <iadev->num_tx_desc; i++)
+      printk("Desc_tbl[%d] = %d \n", i, iadev->desc_tbl[i].timestamp);
+} 
+  
+  
+/*----------------------------- Recieving side stuff --------------------------*/  
+ 
+static void rx_excp_rcvd(struct atm_dev *dev)  
+{  
+#if 0 /* closing the receiving size will cause too many excp int */  
+  IADEV *iadev;  
+  u_short state;  
+  u_short excpq_rd_ptr;  
+  //u_short *ptr;  
+  int vci, error = 1;  
+  iadev = INPH_IA_DEV(dev);  
+  state = readl(iadev->reass_reg + STATE_REG) & 0xffff;  
+  while((state & EXCPQ_EMPTY) != EXCPQ_EMPTY)  
+  { printk("state = %x \n", state); 
+        excpq_rd_ptr = readw(iadev->reass_reg + EXCP_Q_RD_PTR) & 0xffff;  
+ printk("state = %x excpq_rd_ptr = %x \n", state, excpq_rd_ptr); 
+        if (excpq_rd_ptr == *(u16*)(iadev->reass_reg + EXCP_Q_WR_PTR))
+            IF_ERR(printk("excpq_rd_ptr is wrong!!!\n");)
+        // TODO: update exception stat
+	vci = readw(iadev->reass_ram+excpq_rd_ptr);  
+	error = readw(iadev->reass_ram+excpq_rd_ptr+2) & 0x0007;  
+        // pwang_test
+	excpq_rd_ptr += 4;  
+	if (excpq_rd_ptr > (readw(iadev->reass_reg + EXCP_Q_ED_ADR)& 0xffff))  
+ 	    excpq_rd_ptr = readw(iadev->reass_reg + EXCP_Q_ST_ADR)& 0xffff;
+	writew( excpq_rd_ptr, iadev->reass_reg + EXCP_Q_RD_PTR);  
+        state = readl(iadev->reass_reg + STATE_REG) & 0xffff;  
+  }  
+#endif
+}  
+  
+static void free_desc(struct atm_dev *dev, int desc)  
+{  
+	IADEV *iadev;  
+	iadev = INPH_IA_DEV(dev);  
+        writew(desc, iadev->reass_ram+iadev->rfL.fdq_wr); 
+	iadev->rfL.fdq_wr +=2;
+	if (iadev->rfL.fdq_wr > iadev->rfL.fdq_ed)
+		iadev->rfL.fdq_wr =  iadev->rfL.fdq_st;  
+	writew(iadev->rfL.fdq_wr, iadev->reass_reg+FREEQ_WR_PTR);  
+}  
+  
+  
+static int rx_pkt(struct atm_dev *dev)  
+{  
+	IADEV *iadev;  
+	struct atm_vcc *vcc;  
+	unsigned short status;  
+	struct rx_buf_desc __iomem *buf_desc_ptr;  
+	int desc;   
+	struct dle* wr_ptr;  
+	int len;  
+	struct sk_buff *skb;  
+	u_int buf_addr, dma_addr;  
+
+	iadev = INPH_IA_DEV(dev);  
+	if (iadev->rfL.pcq_rd == (readw(iadev->reass_reg+PCQ_WR_PTR)&0xffff)) 
+	{  
+   	    printk(KERN_ERR DEV_LABEL "(itf %d) Receive queue empty\n", dev->number);  
+	    return -EINVAL;  
+	}  
+	/* mask 1st 3 bits to get the actual descno. */  
+	desc = readw(iadev->reass_ram+iadev->rfL.pcq_rd) & 0x1fff;  
+        IF_RX(printk("reass_ram = %p iadev->rfL.pcq_rd = 0x%x desc = %d\n", 
+                                    iadev->reass_ram, iadev->rfL.pcq_rd, desc);
+              printk(" pcq_wr_ptr = 0x%x\n",
+                               readw(iadev->reass_reg+PCQ_WR_PTR)&0xffff);)
+	/* update the read pointer  - maybe we shud do this in the end*/  
+	if ( iadev->rfL.pcq_rd== iadev->rfL.pcq_ed) 
+		iadev->rfL.pcq_rd = iadev->rfL.pcq_st;  
+	else  
+		iadev->rfL.pcq_rd += 2;
+	writew(iadev->rfL.pcq_rd, iadev->reass_reg+PCQ_RD_PTR);  
+  
+	/* get the buffer desc entry.  
+		update stuff. - doesn't seem to be any update necessary  
+	*/  
+	buf_desc_ptr = iadev->RX_DESC_BASE_ADDR;
+	/* make the ptr point to the corresponding buffer desc entry */  
+	buf_desc_ptr += desc;	  
+        if (!desc || (desc > iadev->num_rx_desc) || 
+                      ((buf_desc_ptr->vc_index & 0xffff) > iadev->num_vc)) { 
+            free_desc(dev, desc);
+            IF_ERR(printk("IA: bad descriptor desc = %d \n", desc);)
+            return -1;
+        }
+	vcc = iadev->rx_open[buf_desc_ptr->vc_index & 0xffff];  
+	if (!vcc)  
+	{      
+                free_desc(dev, desc); 
+		printk("IA: null vcc, drop PDU\n");  
+		return -1;  
+	}  
+	  
+  
+	/* might want to check the status bits for errors */  
+	status = (u_short) (buf_desc_ptr->desc_mode);  
+	if (status & (RX_CER | RX_PTE | RX_OFL))  
+	{  
+                atomic_inc(&vcc->stats->rx_err);
+		IF_ERR(printk("IA: bad packet, dropping it");)  
+                if (status & RX_CER) { 
+                    IF_ERR(printk(" cause: packet CRC error\n");)
+                }
+                else if (status & RX_PTE) {
+                    IF_ERR(printk(" cause: packet time out\n");)
+                }
+                else {
+                    IF_ERR(printk(" cause: buffer over flow\n");)
+                }
+		goto out_free_desc;
+	}  
+  
+	/*  
+		build DLE.	  
+	*/  
+  
+	buf_addr = (buf_desc_ptr->buf_start_hi << 16) | buf_desc_ptr->buf_start_lo;  
+	dma_addr = (buf_desc_ptr->dma_start_hi << 16) | buf_desc_ptr->dma_start_lo;  
+	len = dma_addr - buf_addr;  
+        if (len > iadev->rx_buf_sz) {
+           printk("Over %d bytes sdu received, dropped!!!\n", iadev->rx_buf_sz);
+           atomic_inc(&vcc->stats->rx_err);
+	   goto out_free_desc;
+        }
+		  
+        if (!(skb = atm_alloc_charge(vcc, len, GFP_ATOMIC))) {
+           if (vcc->vci < 32)
+              printk("Drop control packets\n");
+	      goto out_free_desc;
+        }
+	skb_put(skb,len);  
+        // pwang_test
+        ATM_SKB(skb)->vcc = vcc;
+        ATM_DESC(skb) = desc;        
+	skb_queue_tail(&iadev->rx_dma_q, skb);  
+
+	/* Build the DLE structure */  
+	wr_ptr = iadev->rx_dle_q.write;  
+	wr_ptr->sys_pkt_addr = pci_map_single(iadev->pci, skb->data,
+		len, PCI_DMA_FROMDEVICE);
+	wr_ptr->local_pkt_addr = buf_addr;  
+	wr_ptr->bytes = len;	/* We don't know this do we ?? */  
+	wr_ptr->mode = DMA_INT_ENABLE;  
+  
+	/* shud take care of wrap around here too. */  
+        if(++wr_ptr == iadev->rx_dle_q.end)
+             wr_ptr = iadev->rx_dle_q.start;
+	iadev->rx_dle_q.write = wr_ptr;  
+	udelay(1);  
+	/* Increment transaction counter */  
+	writel(1, iadev->dma+IPHASE5575_RX_COUNTER);   
+out:	return 0;  
+out_free_desc:
+        free_desc(dev, desc);
+        goto out;
+}  
+  
+static void rx_intr(struct atm_dev *dev)  
+{  
+  IADEV *iadev;  
+  u_short status;  
+  u_short state, i;  
+  
+  iadev = INPH_IA_DEV(dev);  
+  status = readl(iadev->reass_reg+REASS_INTR_STATUS_REG) & 0xffff;  
+  IF_EVENT(printk("rx_intr: status = 0x%x\n", status);)
+  if (status & RX_PKT_RCVD)  
+  {  
+	/* do something */  
+	/* Basically recvd an interrupt for receving a packet.  
+	A descriptor would have been written to the packet complete   
+	queue. Get all the descriptors and set up dma to move the   
+	packets till the packet complete queue is empty..  
+	*/  
+	state = readl(iadev->reass_reg + STATE_REG) & 0xffff;  
+        IF_EVENT(printk("Rx intr status: RX_PKT_RCVD %08x\n", status);) 
+	while(!(state & PCQ_EMPTY))  
+	{  
+             rx_pkt(dev);  
+	     state = readl(iadev->reass_reg + STATE_REG) & 0xffff;  
+	}  
+        iadev->rxing = 1;
+  }  
+  if (status & RX_FREEQ_EMPT)  
+  {   
+     if (iadev->rxing) {
+        iadev->rx_tmp_cnt = iadev->rx_pkt_cnt;
+        iadev->rx_tmp_jif = jiffies; 
+        iadev->rxing = 0;
+     } 
+     else if (((jiffies - iadev->rx_tmp_jif) > 50) && 
+               ((iadev->rx_pkt_cnt - iadev->rx_tmp_cnt) == 0)) {
+        for (i = 1; i <= iadev->num_rx_desc; i++)
+               free_desc(dev, i);
+printk("Test logic RUN!!!!\n");
+        writew( ~(RX_FREEQ_EMPT|RX_EXCP_RCVD),iadev->reass_reg+REASS_MASK_REG);
+        iadev->rxing = 1;
+     }
+     IF_EVENT(printk("Rx intr status: RX_FREEQ_EMPT %08x\n", status);)  
+  }  
+
+  if (status & RX_EXCP_RCVD)  
+  {  
+	/* probably need to handle the exception queue also. */  
+	IF_EVENT(printk("Rx intr status: RX_EXCP_RCVD %08x\n", status);)  
+	rx_excp_rcvd(dev);  
+  }  
+
+
+  if (status & RX_RAW_RCVD)  
+  {  
+	/* need to handle the raw incoming cells. This deepnds on   
+	whether we have programmed to receive the raw cells or not.  
+	Else ignore. */  
+	IF_EVENT(printk("Rx intr status:  RX_RAW_RCVD %08x\n", status);)  
+  }  
+}  
+  
+  
+static void rx_dle_intr(struct atm_dev *dev)  
+{  
+  IADEV *iadev;  
+  struct atm_vcc *vcc;   
+  struct sk_buff *skb;  
+  int desc;  
+  u_short state;   
+  struct dle *dle, *cur_dle;  
+  u_int dle_lp;  
+  int len;
+  iadev = INPH_IA_DEV(dev);  
+ 
+  /* free all the dles done, that is just update our own dle read pointer   
+	- do we really need to do this. Think not. */  
+  /* DMA is done, just get all the recevie buffers from the rx dma queue  
+	and push them up to the higher layer protocol. Also free the desc  
+	associated with the buffer. */  
+  dle = iadev->rx_dle_q.read;  
+  dle_lp = readl(iadev->dma+IPHASE5575_RX_LIST_ADDR) & (sizeof(struct dle)*DLE_ENTRIES - 1);  
+  cur_dle = (struct dle*)(iadev->rx_dle_q.start + (dle_lp >> 4));  
+  while(dle != cur_dle)  
+  {  
+      /* free the DMAed skb */  
+      skb = skb_dequeue(&iadev->rx_dma_q);  
+      if (!skb)  
+         goto INCR_DLE;
+      desc = ATM_DESC(skb);
+      free_desc(dev, desc);  
+               
+      if (!(len = skb->len))
+      {  
+          printk("rx_dle_intr: skb len 0\n");  
+	  dev_kfree_skb_any(skb);  
+      }  
+      else  
+      {  
+          struct cpcs_trailer *trailer;
+          u_short length;
+          struct ia_vcc *ia_vcc;
+
+	  pci_unmap_single(iadev->pci, iadev->rx_dle_q.write->sys_pkt_addr,
+	  	len, PCI_DMA_FROMDEVICE);
+          /* no VCC related housekeeping done as yet. lets see */  
+          vcc = ATM_SKB(skb)->vcc;
+	  if (!vcc) {
+	      printk("IA: null vcc\n");  
+              dev_kfree_skb_any(skb);
+              goto INCR_DLE;
+          }
+          ia_vcc = INPH_IA_VCC(vcc);
+          if (ia_vcc == NULL)
+          {
+             atomic_inc(&vcc->stats->rx_err);
+             dev_kfree_skb_any(skb);
+             atm_return(vcc, atm_guess_pdu2truesize(len));
+             goto INCR_DLE;
+           }
+          // get real pkt length  pwang_test
+          trailer = (struct cpcs_trailer*)((u_char *)skb->data +
+                                 skb->len - sizeof(*trailer));
+          length =  swap(trailer->length);
+          if ((length > iadev->rx_buf_sz) || (length > 
+                              (skb->len - sizeof(struct cpcs_trailer))))
+          {
+             atomic_inc(&vcc->stats->rx_err);
+             IF_ERR(printk("rx_dle_intr: Bad  AAL5 trailer %d (skb len %d)", 
+                                                            length, skb->len);)
+             dev_kfree_skb_any(skb);
+             atm_return(vcc, atm_guess_pdu2truesize(len));
+             goto INCR_DLE;
+          }
+          skb_trim(skb, length);
+          
+	  /* Display the packet */  
+	  IF_RXPKT(printk("\nDmad Recvd data: len = %d \n", skb->len);  
+          xdump(skb->data, skb->len, "RX: ");
+          printk("\n");)
+
+	  IF_RX(printk("rx_dle_intr: skb push");)  
+	  vcc->push(vcc,skb);  
+	  atomic_inc(&vcc->stats->rx);
+          iadev->rx_pkt_cnt++;
+      }  
+INCR_DLE:
+      if (++dle == iadev->rx_dle_q.end)  
+    	  dle = iadev->rx_dle_q.start;  
+  }  
+  iadev->rx_dle_q.read = dle;  
+  
+  /* if the interrupts are masked because there were no free desc available,  
+		unmask them now. */ 
+  if (!iadev->rxing) {
+     state = readl(iadev->reass_reg + STATE_REG) & 0xffff;
+     if (!(state & FREEQ_EMPTY)) {
+        state = readl(iadev->reass_reg + REASS_MASK_REG) & 0xffff;
+        writel(state & ~(RX_FREEQ_EMPT |/* RX_EXCP_RCVD |*/ RX_PKT_RCVD),
+                                      iadev->reass_reg+REASS_MASK_REG);
+        iadev->rxing++; 
+     }
+  }
+}  
+  
+  
+static int open_rx(struct atm_vcc *vcc)  
+{  
+	IADEV *iadev;  
+	u_short __iomem *vc_table;  
+	u_short __iomem *reass_ptr;  
+	IF_EVENT(printk("iadev: open_rx %d.%d\n", vcc->vpi, vcc->vci);)
+
+	if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0;    
+	iadev = INPH_IA_DEV(vcc->dev);  
+        if (vcc->qos.rxtp.traffic_class == ATM_ABR) {  
+           if (iadev->phy_type & FE_25MBIT_PHY) {
+               printk("IA:  ABR not support\n");
+               return -EINVAL; 
+           }
+        }
+	/* Make only this VCI in the vc table valid and let all   
+		others be invalid entries */  
+	vc_table = iadev->reass_ram+RX_VC_TABLE*iadev->memSize;
+	vc_table += vcc->vci;
+	/* mask the last 6 bits and OR it with 3 for 1K VCs */  
+
+        *vc_table = vcc->vci << 6;
+	/* Also keep a list of open rx vcs so that we can attach them with  
+		incoming PDUs later. */  
+	if ((vcc->qos.rxtp.traffic_class == ATM_ABR) || 
+                                (vcc->qos.txtp.traffic_class == ATM_ABR))  
+	{  
+                srv_cls_param_t srv_p;
+                init_abr_vc(iadev, &srv_p);
+                ia_open_abr_vc(iadev, &srv_p, vcc, 0);
+	} 
+       	else {  /* for UBR  later may need to add CBR logic */
+        	reass_ptr = iadev->reass_ram+REASS_TABLE*iadev->memSize;
+           	reass_ptr += vcc->vci;
+           	*reass_ptr = NO_AAL5_PKT;
+       	}
+	
+	if (iadev->rx_open[vcc->vci])  
+		printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %d already open\n",  
+			vcc->dev->number, vcc->vci);  
+	iadev->rx_open[vcc->vci] = vcc;  
+	return 0;  
+}  
+  
+static int rx_init(struct atm_dev *dev)  
+{  
+	IADEV *iadev;  
+	struct rx_buf_desc __iomem *buf_desc_ptr;  
+	unsigned long rx_pkt_start = 0;  
+	void *dle_addr;  
+	struct abr_vc_table  *abr_vc_table; 
+	u16 *vc_table;  
+	u16 *reass_table;  
+        u16 *ptr16;
+	int i,j, vcsize_sel;  
+	u_short freeq_st_adr;  
+	u_short *freeq_start;  
+  
+	iadev = INPH_IA_DEV(dev);  
+  //    spin_lock_init(&iadev->rx_lock); 
+  
+	/* Allocate 4k bytes - more aligned than needed (4k boundary) */
+	dle_addr = pci_alloc_consistent(iadev->pci, DLE_TOTAL_SIZE,
+					&iadev->rx_dle_dma);  
+	if (!dle_addr)  {  
+		printk(KERN_ERR DEV_LABEL "can't allocate DLEs\n");
+		goto err_out;
+	}
+	iadev->rx_dle_q.start = (struct dle*)dle_addr;  
+	iadev->rx_dle_q.read = iadev->rx_dle_q.start;  
+	iadev->rx_dle_q.write = iadev->rx_dle_q.start;  
+	iadev->rx_dle_q.end = (struct dle*)((u32)dle_addr+sizeof(struct dle)*DLE_ENTRIES);  
+	/* the end of the dle q points to the entry after the last  
+	DLE that can be used. */  
+  
+	/* write the upper 20 bits of the start address to rx list address register */  
+	writel(iadev->rx_dle_dma & 0xfffff000,
+	       iadev->dma + IPHASE5575_RX_LIST_ADDR);  
+	IF_INIT(printk("Tx Dle list addr: 0x%08x value: 0x%0x\n", 
+                      (u32)(iadev->dma+IPHASE5575_TX_LIST_ADDR), 
+                      *(u32*)(iadev->dma+IPHASE5575_TX_LIST_ADDR));  
+	printk("Rx Dle list addr: 0x%08x value: 0x%0x\n", 
+                      (u32)(iadev->dma+IPHASE5575_RX_LIST_ADDR), 
+                      *(u32*)(iadev->dma+IPHASE5575_RX_LIST_ADDR));)  
+  
+	writew(0xffff, iadev->reass_reg+REASS_MASK_REG);  
+	writew(0, iadev->reass_reg+MODE_REG);  
+	writew(RESET_REASS, iadev->reass_reg+REASS_COMMAND_REG);  
+  
+	/* Receive side control memory map  
+	   -------------------------------  
+  
+		Buffer descr	0x0000 (736 - 23K)  
+		VP Table	0x5c00 (256 - 512)  
+		Except q	0x5e00 (128 - 512)  
+		Free buffer q	0x6000 (1K - 2K)  
+		Packet comp q	0x6800 (1K - 2K)  
+		Reass Table	0x7000 (1K - 2K)  
+		VC Table	0x7800 (1K - 2K)  
+		ABR VC Table	0x8000 (1K - 32K)  
+	*/  
+	  
+	/* Base address for Buffer Descriptor Table */  
+	writew(RX_DESC_BASE >> 16, iadev->reass_reg+REASS_DESC_BASE);  
+	/* Set the buffer size register */  
+	writew(iadev->rx_buf_sz, iadev->reass_reg+BUF_SIZE);  
+  
+	/* Initialize each entry in the Buffer Descriptor Table */  
+        iadev->RX_DESC_BASE_ADDR = iadev->reass_ram+RX_DESC_BASE*iadev->memSize;
+	buf_desc_ptr = iadev->RX_DESC_BASE_ADDR;
+	memset_io(buf_desc_ptr, 0, sizeof(*buf_desc_ptr));
+	buf_desc_ptr++;  
+	rx_pkt_start = iadev->rx_pkt_ram;  
+	for(i=1; i<=iadev->num_rx_desc; i++)  
+	{  
+		memset_io(buf_desc_ptr, 0, sizeof(*buf_desc_ptr));  
+		buf_desc_ptr->buf_start_hi = rx_pkt_start >> 16;  
+		buf_desc_ptr->buf_start_lo = rx_pkt_start & 0x0000ffff;  
+		buf_desc_ptr++;		  
+		rx_pkt_start += iadev->rx_buf_sz;  
+	}  
+	IF_INIT(printk("Rx Buffer desc ptr: 0x%0x\n", (u32)(buf_desc_ptr));)  
+        i = FREE_BUF_DESC_Q*iadev->memSize; 
+	writew(i >> 16,  iadev->reass_reg+REASS_QUEUE_BASE); 
+        writew(i, iadev->reass_reg+FREEQ_ST_ADR);
+        writew(i+iadev->num_rx_desc*sizeof(u_short), 
+                                         iadev->reass_reg+FREEQ_ED_ADR);
+        writew(i, iadev->reass_reg+FREEQ_RD_PTR);
+        writew(i+iadev->num_rx_desc*sizeof(u_short), 
+                                        iadev->reass_reg+FREEQ_WR_PTR);    
+	/* Fill the FREEQ with all the free descriptors. */  
+	freeq_st_adr = readw(iadev->reass_reg+FREEQ_ST_ADR);  
+	freeq_start = (u_short *)(iadev->reass_ram+freeq_st_adr);  
+	for(i=1; i<=iadev->num_rx_desc; i++)  
+	{  
+		*freeq_start = (u_short)i;  
+		freeq_start++;  
+	}  
+	IF_INIT(printk("freeq_start: 0x%0x\n", (u32)freeq_start);)  
+        /* Packet Complete Queue */
+        i = (PKT_COMP_Q * iadev->memSize) & 0xffff;
+        writew(i, iadev->reass_reg+PCQ_ST_ADR);
+        writew(i+iadev->num_vc*sizeof(u_short), iadev->reass_reg+PCQ_ED_ADR);
+        writew(i, iadev->reass_reg+PCQ_RD_PTR);
+        writew(i, iadev->reass_reg+PCQ_WR_PTR);
+
+        /* Exception Queue */
+        i = (EXCEPTION_Q * iadev->memSize) & 0xffff;
+        writew(i, iadev->reass_reg+EXCP_Q_ST_ADR);
+        writew(i + NUM_RX_EXCP * sizeof(RX_ERROR_Q), 
+                                             iadev->reass_reg+EXCP_Q_ED_ADR);
+        writew(i, iadev->reass_reg+EXCP_Q_RD_PTR);
+        writew(i, iadev->reass_reg+EXCP_Q_WR_PTR); 
+ 
+    	/* Load local copy of FREEQ and PCQ ptrs */
+        iadev->rfL.fdq_st = readw(iadev->reass_reg+FREEQ_ST_ADR) & 0xffff;
+       	iadev->rfL.fdq_ed = readw(iadev->reass_reg+FREEQ_ED_ADR) & 0xffff ;
+	iadev->rfL.fdq_rd = readw(iadev->reass_reg+FREEQ_RD_PTR) & 0xffff;
+	iadev->rfL.fdq_wr = readw(iadev->reass_reg+FREEQ_WR_PTR) & 0xffff;
+        iadev->rfL.pcq_st = readw(iadev->reass_reg+PCQ_ST_ADR) & 0xffff;
+	iadev->rfL.pcq_ed = readw(iadev->reass_reg+PCQ_ED_ADR) & 0xffff;
+	iadev->rfL.pcq_rd = readw(iadev->reass_reg+PCQ_RD_PTR) & 0xffff;
+	iadev->rfL.pcq_wr = readw(iadev->reass_reg+PCQ_WR_PTR) & 0xffff;
+	
+        IF_INIT(printk("INIT:pcq_st:0x%x pcq_ed:0x%x pcq_rd:0x%x pcq_wr:0x%x", 
+              iadev->rfL.pcq_st, iadev->rfL.pcq_ed, iadev->rfL.pcq_rd, 
+              iadev->rfL.pcq_wr);)		  
+	/* just for check - no VP TBL */  
+	/* VP Table */  
+	/* writew(0x0b80, iadev->reass_reg+VP_LKUP_BASE); */  
+	/* initialize VP Table for invalid VPIs  
+		- I guess we can write all 1s or 0x000f in the entire memory  
+		  space or something similar.  
+	*/  
+  
+	/* This seems to work and looks right to me too !!! */  
+        i =  REASS_TABLE * iadev->memSize;
+	writew((i >> 3), iadev->reass_reg+REASS_TABLE_BASE);   
+ 	/* initialize Reassembly table to I don't know what ???? */  
+	reass_table = (u16 *)(iadev->reass_ram+i);  
+        j = REASS_TABLE_SZ * iadev->memSize;
+	for(i=0; i < j; i++)  
+		*reass_table++ = NO_AAL5_PKT;  
+       i = 8*1024;
+       vcsize_sel =  0;
+       while (i != iadev->num_vc) {
+          i /= 2;
+          vcsize_sel++;
+       }
+       i = RX_VC_TABLE * iadev->memSize;
+       writew(((i>>3) & 0xfff8) | vcsize_sel, iadev->reass_reg+VC_LKUP_BASE);
+       vc_table = (u16 *)(iadev->reass_ram+RX_VC_TABLE*iadev->memSize);  
+        j = RX_VC_TABLE_SZ * iadev->memSize;
+	for(i = 0; i < j; i++)  
+	{  
+		/* shift the reassembly pointer by 3 + lower 3 bits of   
+		vc_lkup_base register (=3 for 1K VCs) and the last byte   
+		is those low 3 bits.   
+		Shall program this later.  
+		*/  
+		*vc_table = (i << 6) | 15;	/* for invalid VCI */  
+		vc_table++;  
+	}  
+        /* ABR VC table */
+        i =  ABR_VC_TABLE * iadev->memSize;
+        writew(i >> 3, iadev->reass_reg+ABR_LKUP_BASE);
+                   
+        i = ABR_VC_TABLE * iadev->memSize;
+	abr_vc_table = (struct abr_vc_table *)(iadev->reass_ram+i);  
+        j = REASS_TABLE_SZ * iadev->memSize;
+        memset ((char*)abr_vc_table, 0, j * sizeof(*abr_vc_table));
+    	for(i = 0; i < j; i++) {   		
+		abr_vc_table->rdf = 0x0003;
+             	abr_vc_table->air = 0x5eb1;
+	       	abr_vc_table++;   	
+        }  
+
+	/* Initialize other registers */  
+  
+	/* VP Filter Register set for VC Reassembly only */  
+	writew(0xff00, iadev->reass_reg+VP_FILTER);  
+        writew(0, iadev->reass_reg+XTRA_RM_OFFSET);
+	writew(0x1,  iadev->reass_reg+PROTOCOL_ID);
+
+	/* Packet Timeout Count  related Registers : 
+	   Set packet timeout to occur in about 3 seconds
+	   Set Packet Aging Interval count register to overflow in about 4 us
+ 	*/  
+        writew(0xF6F8, iadev->reass_reg+PKT_TM_CNT );
+        ptr16 = (u16*)j;
+        i = ((u32)ptr16 >> 6) & 0xff;
+	ptr16  += j - 1;
+	i |=(((u32)ptr16 << 2) & 0xff00);
+        writew(i, iadev->reass_reg+TMOUT_RANGE);
+        /* initiate the desc_tble */
+        for(i=0; i<iadev->num_tx_desc;i++)
+            iadev->desc_tbl[i].timestamp = 0;
+
+	/* to clear the interrupt status register - read it */  
+	readw(iadev->reass_reg+REASS_INTR_STATUS_REG);   
+  
+	/* Mask Register - clear it */  
+	writew(~(RX_FREEQ_EMPT|RX_PKT_RCVD), iadev->reass_reg+REASS_MASK_REG);  
+  
+	skb_queue_head_init(&iadev->rx_dma_q);  
+	iadev->rx_free_desc_qhead = NULL;   
+	iadev->rx_open = kmalloc(4*iadev->num_vc,GFP_KERNEL);
+	if (!iadev->rx_open)  
+	{  
+		printk(KERN_ERR DEV_LABEL "itf %d couldn't get free page\n",
+		dev->number);  
+		goto err_free_dle;
+	}  
+	memset(iadev->rx_open, 0, 4*iadev->num_vc);  
+        iadev->rxing = 1;
+        iadev->rx_pkt_cnt = 0;
+	/* Mode Register */  
+	writew(R_ONLINE, iadev->reass_reg+MODE_REG);  
+	return 0;  
+
+err_free_dle:
+	pci_free_consistent(iadev->pci, DLE_TOTAL_SIZE, iadev->rx_dle_q.start,
+			    iadev->rx_dle_dma);  
+err_out:
+	return -ENOMEM;
+}  
+  
+
+/*  
+	The memory map suggested in appendix A and the coding for it.   
+	Keeping it around just in case we change our mind later.  
+  
+		Buffer descr	0x0000 (128 - 4K)  
+		UBR sched	0x1000 (1K - 4K)  
+		UBR Wait q	0x2000 (1K - 4K)  
+		Commn queues	0x3000 Packet Ready, Trasmit comp(0x3100)  
+					(128 - 256) each  
+		extended VC	0x4000 (1K - 8K)  
+		ABR sched	0x6000	and ABR wait queue (1K - 2K) each  
+		CBR sched	0x7000 (as needed)  
+		VC table	0x8000 (1K - 32K)  
+*/  
+  
+static void tx_intr(struct atm_dev *dev)  
+{  
+	IADEV *iadev;  
+	unsigned short status;  
+        unsigned long flags;
+
+	iadev = INPH_IA_DEV(dev);  
+  
+	status = readl(iadev->seg_reg+SEG_INTR_STATUS_REG);  
+        if (status & TRANSMIT_DONE){
+
+           IF_EVENT(printk("Tansmit Done Intr logic run\n");)
+           spin_lock_irqsave(&iadev->tx_lock, flags);
+           ia_tx_poll(iadev);
+           spin_unlock_irqrestore(&iadev->tx_lock, flags);
+           writew(TRANSMIT_DONE, iadev->seg_reg+SEG_INTR_STATUS_REG);
+           if (iadev->close_pending)  
+               wake_up(&iadev->close_wait);
+        }     	  
+	if (status & TCQ_NOT_EMPTY)  
+	{  
+	    IF_EVENT(printk("TCQ_NOT_EMPTY int received\n");)  
+	}  
+}  
+  
+static void tx_dle_intr(struct atm_dev *dev)
+{
+        IADEV *iadev;
+        struct dle *dle, *cur_dle; 
+        struct sk_buff *skb;
+        struct atm_vcc *vcc;
+        struct ia_vcc  *iavcc;
+        u_int dle_lp;
+        unsigned long flags;
+
+        iadev = INPH_IA_DEV(dev);
+        spin_lock_irqsave(&iadev->tx_lock, flags);   
+        dle = iadev->tx_dle_q.read;
+        dle_lp = readl(iadev->dma+IPHASE5575_TX_LIST_ADDR) & 
+                                        (sizeof(struct dle)*DLE_ENTRIES - 1);
+        cur_dle = (struct dle*)(iadev->tx_dle_q.start + (dle_lp >> 4));
+        while (dle != cur_dle)
+        {
+            /* free the DMAed skb */ 
+            skb = skb_dequeue(&iadev->tx_dma_q); 
+            if (!skb) break;
+
+	    /* Revenge of the 2 dle (skb + trailer) used in ia_pkt_tx() */
+	    if (!((dle - iadev->tx_dle_q.start)%(2*sizeof(struct dle)))) {
+		pci_unmap_single(iadev->pci, dle->sys_pkt_addr, skb->len,
+				 PCI_DMA_TODEVICE);
+	    }
+            vcc = ATM_SKB(skb)->vcc;
+            if (!vcc) {
+                  printk("tx_dle_intr: vcc is null\n");
+		  spin_unlock_irqrestore(&iadev->tx_lock, flags);
+                  dev_kfree_skb_any(skb);
+
+                  return;
+            }
+            iavcc = INPH_IA_VCC(vcc);
+            if (!iavcc) {
+                  printk("tx_dle_intr: iavcc is null\n");
+		  spin_unlock_irqrestore(&iadev->tx_lock, flags);
+                  dev_kfree_skb_any(skb);
+                  return;
+            }
+            if (vcc->qos.txtp.pcr >= iadev->rate_limit) {
+               if ((vcc->pop) && (skb->len != 0))
+               {     
+                 vcc->pop(vcc, skb);
+               } 
+               else {
+                 dev_kfree_skb_any(skb);
+               }
+            }
+            else { /* Hold the rate-limited skb for flow control */
+               IA_SKB_STATE(skb) |= IA_DLED;
+               skb_queue_tail(&iavcc->txing_skb, skb);
+            }
+            IF_EVENT(printk("tx_dle_intr: enque skb = 0x%x \n", (u32)skb);)
+            if (++dle == iadev->tx_dle_q.end)
+                 dle = iadev->tx_dle_q.start;
+        }
+        iadev->tx_dle_q.read = dle;
+        spin_unlock_irqrestore(&iadev->tx_lock, flags);
+}
+  
+static int open_tx(struct atm_vcc *vcc)  
+{  
+	struct ia_vcc *ia_vcc;  
+	IADEV *iadev;  
+	struct main_vc *vc;  
+	struct ext_vc *evc;  
+        int ret;
+	IF_EVENT(printk("iadev: open_tx entered vcc->vci = %d\n", vcc->vci);)  
+	if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0;  
+	iadev = INPH_IA_DEV(vcc->dev);  
+        
+        if (iadev->phy_type & FE_25MBIT_PHY) {
+           if (vcc->qos.txtp.traffic_class == ATM_ABR) {
+               printk("IA:  ABR not support\n");
+               return -EINVAL; 
+           }
+	  if (vcc->qos.txtp.traffic_class == ATM_CBR) {
+               printk("IA:  CBR not support\n");
+               return -EINVAL; 
+          }
+        }
+        ia_vcc =  INPH_IA_VCC(vcc);
+        memset((caddr_t)ia_vcc, 0, sizeof(*ia_vcc));
+        if (vcc->qos.txtp.max_sdu > 
+                         (iadev->tx_buf_sz - sizeof(struct cpcs_trailer))){
+           printk("IA:  SDU size over (%d) the configured SDU size %d\n",
+		  vcc->qos.txtp.max_sdu,iadev->tx_buf_sz);
+	   vcc->dev_data = NULL;
+           kfree(ia_vcc);
+           return -EINVAL; 
+        }
+	ia_vcc->vc_desc_cnt = 0;
+        ia_vcc->txing = 1;
+
+        /* find pcr */
+        if (vcc->qos.txtp.max_pcr == ATM_MAX_PCR) 
+           vcc->qos.txtp.pcr = iadev->LineRate;
+        else if ((vcc->qos.txtp.max_pcr == 0)&&( vcc->qos.txtp.pcr <= 0))
+           vcc->qos.txtp.pcr = iadev->LineRate;
+        else if ((vcc->qos.txtp.max_pcr > vcc->qos.txtp.pcr) && (vcc->qos.txtp.max_pcr> 0)) 
+           vcc->qos.txtp.pcr = vcc->qos.txtp.max_pcr;
+        if (vcc->qos.txtp.pcr > iadev->LineRate)
+             vcc->qos.txtp.pcr = iadev->LineRate;
+        ia_vcc->pcr = vcc->qos.txtp.pcr;
+
+        if (ia_vcc->pcr > (iadev->LineRate / 6) ) ia_vcc->ltimeout = HZ / 10;
+        else if (ia_vcc->pcr > (iadev->LineRate / 130)) ia_vcc->ltimeout = HZ;
+        else if (ia_vcc->pcr <= 170) ia_vcc->ltimeout = 16 * HZ;
+        else ia_vcc->ltimeout = 2700 * HZ  / ia_vcc->pcr;
+        if (ia_vcc->pcr < iadev->rate_limit)
+           skb_queue_head_init (&ia_vcc->txing_skb);
+        if (ia_vcc->pcr < iadev->rate_limit) {
+	   struct sock *sk = sk_atm(vcc);
+
+	   if (vcc->qos.txtp.max_sdu != 0) {
+               if (ia_vcc->pcr > 60000)
+                  sk->sk_sndbuf = vcc->qos.txtp.max_sdu * 5;
+               else if (ia_vcc->pcr > 2000)
+                  sk->sk_sndbuf = vcc->qos.txtp.max_sdu * 4;
+               else
+                 sk->sk_sndbuf = vcc->qos.txtp.max_sdu * 3;
+           }
+           else
+             sk->sk_sndbuf = 24576;
+        }
+           
+	vc = (struct main_vc *)iadev->MAIN_VC_TABLE_ADDR;  
+	evc = (struct ext_vc *)iadev->EXT_VC_TABLE_ADDR;  
+	vc += vcc->vci;  
+	evc += vcc->vci;  
+	memset((caddr_t)vc, 0, sizeof(*vc));  
+	memset((caddr_t)evc, 0, sizeof(*evc));  
+	  
+	/* store the most significant 4 bits of vci as the last 4 bits   
+		of first part of atm header.  
+	   store the last 12 bits of vci as first 12 bits of the second  
+		part of the atm header.  
+	*/  
+	evc->atm_hdr1 = (vcc->vci >> 12) & 0x000f;  
+	evc->atm_hdr2 = (vcc->vci & 0x0fff) << 4;  
+ 
+	/* check the following for different traffic classes */  
+	if (vcc->qos.txtp.traffic_class == ATM_UBR)  
+	{  
+		vc->type = UBR;  
+                vc->status = CRC_APPEND;
+		vc->acr = cellrate_to_float(iadev->LineRate);  
+                if (vcc->qos.txtp.pcr > 0) 
+                   vc->acr = cellrate_to_float(vcc->qos.txtp.pcr);  
+                IF_UBR(printk("UBR: txtp.pcr = 0x%x f_rate = 0x%x\n", 
+                                             vcc->qos.txtp.max_pcr,vc->acr);)
+	}  
+	else if (vcc->qos.txtp.traffic_class == ATM_ABR)  
+	{       srv_cls_param_t srv_p;
+		IF_ABR(printk("Tx ABR VCC\n");)  
+                init_abr_vc(iadev, &srv_p);
+                if (vcc->qos.txtp.pcr > 0) 
+                   srv_p.pcr = vcc->qos.txtp.pcr;
+                if (vcc->qos.txtp.min_pcr > 0) {
+                   int tmpsum = iadev->sum_mcr+iadev->sum_cbr+vcc->qos.txtp.min_pcr;
+                   if (tmpsum > iadev->LineRate)
+                       return -EBUSY;
+                   srv_p.mcr = vcc->qos.txtp.min_pcr;
+                   iadev->sum_mcr += vcc->qos.txtp.min_pcr;
+                } 
+                else srv_p.mcr = 0;
+                if (vcc->qos.txtp.icr)
+                   srv_p.icr = vcc->qos.txtp.icr;
+                if (vcc->qos.txtp.tbe)
+                   srv_p.tbe = vcc->qos.txtp.tbe;
+                if (vcc->qos.txtp.frtt)
+                   srv_p.frtt = vcc->qos.txtp.frtt;
+                if (vcc->qos.txtp.rif)
+                   srv_p.rif = vcc->qos.txtp.rif;
+                if (vcc->qos.txtp.rdf)
+                   srv_p.rdf = vcc->qos.txtp.rdf;
+                if (vcc->qos.txtp.nrm_pres)
+                   srv_p.nrm = vcc->qos.txtp.nrm;
+                if (vcc->qos.txtp.trm_pres)
+                   srv_p.trm = vcc->qos.txtp.trm;
+                if (vcc->qos.txtp.adtf_pres)
+                   srv_p.adtf = vcc->qos.txtp.adtf;
+                if (vcc->qos.txtp.cdf_pres)
+                   srv_p.cdf = vcc->qos.txtp.cdf;    
+                if (srv_p.icr > srv_p.pcr)
+                   srv_p.icr = srv_p.pcr;    
+                IF_ABR(printk("ABR:vcc->qos.txtp.max_pcr = %d  mcr = %d\n", 
+                                                      srv_p.pcr, srv_p.mcr);)
+		ia_open_abr_vc(iadev, &srv_p, vcc, 1);
+	} else if (vcc->qos.txtp.traffic_class == ATM_CBR) {
+                if (iadev->phy_type & FE_25MBIT_PHY) {
+                    printk("IA:  CBR not support\n");
+                    return -EINVAL; 
+                }
+                if (vcc->qos.txtp.max_pcr > iadev->LineRate) {
+                   IF_CBR(printk("PCR is not availble\n");)
+                   return -1;
+                }
+                vc->type = CBR;
+                vc->status = CRC_APPEND;
+                if ((ret = ia_cbr_setup (iadev, vcc)) < 0) {     
+                    return ret;
+                }
+       } 
+	else  
+           printk("iadev:  Non UBR, ABR and CBR traffic not supportedn"); 
+        
+        iadev->testTable[vcc->vci]->vc_status |= VC_ACTIVE;
+	IF_EVENT(printk("ia open_tx returning \n");)  
+	return 0;  
+}  
+  
+  
+static int tx_init(struct atm_dev *dev)  
+{  
+	IADEV *iadev;  
+	struct tx_buf_desc *buf_desc_ptr;
+	unsigned int tx_pkt_start;  
+	void *dle_addr;  
+	int i;  
+	u_short tcq_st_adr;  
+	u_short *tcq_start;  
+	u_short prq_st_adr;  
+	u_short *prq_start;  
+	struct main_vc *vc;  
+	struct ext_vc *evc;   
+        u_short tmp16;
+        u32 vcsize_sel;
+ 
+	iadev = INPH_IA_DEV(dev);  
+        spin_lock_init(&iadev->tx_lock);
+ 
+	IF_INIT(printk("Tx MASK REG: 0x%0x\n", 
+                                readw(iadev->seg_reg+SEG_MASK_REG));)  
+
+	/* Allocate 4k (boundary aligned) bytes */
+	dle_addr = pci_alloc_consistent(iadev->pci, DLE_TOTAL_SIZE,
+					&iadev->tx_dle_dma);  
+	if (!dle_addr)  {
+		printk(KERN_ERR DEV_LABEL "can't allocate DLEs\n");
+		goto err_out;
+	}
+	iadev->tx_dle_q.start = (struct dle*)dle_addr;  
+	iadev->tx_dle_q.read = iadev->tx_dle_q.start;  
+	iadev->tx_dle_q.write = iadev->tx_dle_q.start;  
+	iadev->tx_dle_q.end = (struct dle*)((u32)dle_addr+sizeof(struct dle)*DLE_ENTRIES);  
+
+	/* write the upper 20 bits of the start address to tx list address register */  
+	writel(iadev->tx_dle_dma & 0xfffff000,
+	       iadev->dma + IPHASE5575_TX_LIST_ADDR);  
+	writew(0xffff, iadev->seg_reg+SEG_MASK_REG);  
+	writew(0, iadev->seg_reg+MODE_REG_0);  
+	writew(RESET_SEG, iadev->seg_reg+SEG_COMMAND_REG);  
+        iadev->MAIN_VC_TABLE_ADDR = iadev->seg_ram+MAIN_VC_TABLE*iadev->memSize;
+        iadev->EXT_VC_TABLE_ADDR = iadev->seg_ram+EXT_VC_TABLE*iadev->memSize;
+        iadev->ABR_SCHED_TABLE_ADDR=iadev->seg_ram+ABR_SCHED_TABLE*iadev->memSize;
+  
+	/*  
+	   Transmit side control memory map  
+	   --------------------------------    
+	 Buffer descr 	0x0000 (128 - 4K)  
+	 Commn queues	0x1000	Transmit comp, Packet ready(0x1400)   
+					(512 - 1K) each  
+					TCQ - 4K, PRQ - 5K  
+	 CBR Table 	0x1800 (as needed) - 6K  
+	 UBR Table	0x3000 (1K - 4K) - 12K  
+	 UBR Wait queue	0x4000 (1K - 4K) - 16K  
+	 ABR sched	0x5000	and ABR wait queue (1K - 2K) each  
+				ABR Tbl - 20K, ABR Wq - 22K   
+	 extended VC	0x6000 (1K - 8K) - 24K  
+	 VC Table	0x8000 (1K - 32K) - 32K  
+	  
+	Between 0x2000 (8K) and 0x3000 (12K) there is 4K space left for VBR Tbl  
+	and Wait q, which can be allotted later.  
+	*/  
+     
+	/* Buffer Descriptor Table Base address */  
+	writew(TX_DESC_BASE, iadev->seg_reg+SEG_DESC_BASE);  
+  
+	/* initialize each entry in the buffer descriptor table */  
+	buf_desc_ptr =(struct tx_buf_desc *)(iadev->seg_ram+TX_DESC_BASE);  
+	memset((caddr_t)buf_desc_ptr, 0, sizeof(*buf_desc_ptr));  
+	buf_desc_ptr++;  
+	tx_pkt_start = TX_PACKET_RAM;  
+	for(i=1; i<=iadev->num_tx_desc; i++)  
+	{  
+		memset((caddr_t)buf_desc_ptr, 0, sizeof(*buf_desc_ptr));  
+		buf_desc_ptr->desc_mode = AAL5;  
+		buf_desc_ptr->buf_start_hi = tx_pkt_start >> 16;  
+		buf_desc_ptr->buf_start_lo = tx_pkt_start & 0x0000ffff;  
+		buf_desc_ptr++;		  
+		tx_pkt_start += iadev->tx_buf_sz;  
+	}  
+        iadev->tx_buf = kmalloc(iadev->num_tx_desc*sizeof(struct cpcs_trailer_desc), GFP_KERNEL);
+        if (!iadev->tx_buf) {
+            printk(KERN_ERR DEV_LABEL " couldn't get mem\n");
+	    goto err_free_dle;
+        }
+       	for (i= 0; i< iadev->num_tx_desc; i++)
+       	{
+	    struct cpcs_trailer *cpcs;
+ 
+       	    cpcs = kmalloc(sizeof(*cpcs), GFP_KERNEL|GFP_DMA);
+            if(!cpcs) {                
+		printk(KERN_ERR DEV_LABEL " couldn't get freepage\n"); 
+		goto err_free_tx_bufs;
+            }
+	    iadev->tx_buf[i].cpcs = cpcs;
+	    iadev->tx_buf[i].dma_addr = pci_map_single(iadev->pci,
+		cpcs, sizeof(*cpcs), PCI_DMA_TODEVICE);
+        }
+        iadev->desc_tbl = kmalloc(iadev->num_tx_desc *
+                                   sizeof(struct desc_tbl_t), GFP_KERNEL);
+	if (!iadev->desc_tbl) {
+		printk(KERN_ERR DEV_LABEL " couldn't get mem\n");
+		goto err_free_all_tx_bufs;
+	}
+  
+	/* Communication Queues base address */  
+        i = TX_COMP_Q * iadev->memSize;
+	writew(i >> 16, iadev->seg_reg+SEG_QUEUE_BASE);  
+  
+	/* Transmit Complete Queue */  
+	writew(i, iadev->seg_reg+TCQ_ST_ADR);  
+	writew(i, iadev->seg_reg+TCQ_RD_PTR);  
+	writew(i+iadev->num_tx_desc*sizeof(u_short),iadev->seg_reg+TCQ_WR_PTR); 
+	iadev->host_tcq_wr = i + iadev->num_tx_desc*sizeof(u_short);
+        writew(i+2 * iadev->num_tx_desc * sizeof(u_short), 
+                                              iadev->seg_reg+TCQ_ED_ADR); 
+	/* Fill the TCQ with all the free descriptors. */  
+	tcq_st_adr = readw(iadev->seg_reg+TCQ_ST_ADR);  
+	tcq_start = (u_short *)(iadev->seg_ram+tcq_st_adr);  
+	for(i=1; i<=iadev->num_tx_desc; i++)  
+	{  
+		*tcq_start = (u_short)i;  
+		tcq_start++;  
+	}  
+  
+	/* Packet Ready Queue */  
+        i = PKT_RDY_Q * iadev->memSize; 
+	writew(i, iadev->seg_reg+PRQ_ST_ADR);  
+	writew(i+2 * iadev->num_tx_desc * sizeof(u_short), 
+                                              iadev->seg_reg+PRQ_ED_ADR);
+	writew(i, iadev->seg_reg+PRQ_RD_PTR);  
+	writew(i, iadev->seg_reg+PRQ_WR_PTR);  
+	 
+        /* Load local copy of PRQ and TCQ ptrs */
+        iadev->ffL.prq_st = readw(iadev->seg_reg+PRQ_ST_ADR) & 0xffff;
+	iadev->ffL.prq_ed = readw(iadev->seg_reg+PRQ_ED_ADR) & 0xffff;
+ 	iadev->ffL.prq_wr = readw(iadev->seg_reg+PRQ_WR_PTR) & 0xffff;
+
+	iadev->ffL.tcq_st = readw(iadev->seg_reg+TCQ_ST_ADR) & 0xffff;
+	iadev->ffL.tcq_ed = readw(iadev->seg_reg+TCQ_ED_ADR) & 0xffff;
+	iadev->ffL.tcq_rd = readw(iadev->seg_reg+TCQ_RD_PTR) & 0xffff;
+
+	/* Just for safety initializing the queue to have desc 1 always */  
+	/* Fill the PRQ with all the free descriptors. */  
+	prq_st_adr = readw(iadev->seg_reg+PRQ_ST_ADR);  
+	prq_start = (u_short *)(iadev->seg_ram+prq_st_adr);  
+	for(i=1; i<=iadev->num_tx_desc; i++)  
+	{  
+		*prq_start = (u_short)0;	/* desc 1 in all entries */  
+		prq_start++;  
+	}  
+	/* CBR Table */  
+        IF_INIT(printk("Start CBR Init\n");)
+#if 1  /* for 1K VC board, CBR_PTR_BASE is 0 */
+        writew(0,iadev->seg_reg+CBR_PTR_BASE);
+#else /* Charlie's logic is wrong ? */
+        tmp16 = (iadev->seg_ram+CBR_SCHED_TABLE*iadev->memSize)>>17;
+        IF_INIT(printk("cbr_ptr_base = 0x%x ", tmp16);)
+        writew(tmp16,iadev->seg_reg+CBR_PTR_BASE);
+#endif
+
+        IF_INIT(printk("value in register = 0x%x\n",
+                                   readw(iadev->seg_reg+CBR_PTR_BASE));)
+        tmp16 = (CBR_SCHED_TABLE*iadev->memSize) >> 1;
+        writew(tmp16, iadev->seg_reg+CBR_TAB_BEG);
+        IF_INIT(printk("cbr_tab_beg = 0x%x in reg = 0x%x \n", tmp16,
+                                        readw(iadev->seg_reg+CBR_TAB_BEG));)
+        writew(tmp16, iadev->seg_reg+CBR_TAB_END+1); // CBR_PTR;
+        tmp16 = (CBR_SCHED_TABLE*iadev->memSize + iadev->num_vc*6 - 2) >> 1;
+        writew(tmp16, iadev->seg_reg+CBR_TAB_END);
+        IF_INIT(printk("iadev->seg_reg = 0x%x CBR_PTR_BASE = 0x%x\n",
+               (u32)iadev->seg_reg, readw(iadev->seg_reg+CBR_PTR_BASE));)
+        IF_INIT(printk("CBR_TAB_BEG = 0x%x, CBR_TAB_END = 0x%x, CBR_PTR = 0x%x\n",
+          readw(iadev->seg_reg+CBR_TAB_BEG), readw(iadev->seg_reg+CBR_TAB_END),
+          readw(iadev->seg_reg+CBR_TAB_END+1));)
+
+        /* Initialize the CBR Schedualing Table */
+        memset_io(iadev->seg_ram+CBR_SCHED_TABLE*iadev->memSize, 
+                                                          0, iadev->num_vc*6); 
+        iadev->CbrRemEntries = iadev->CbrTotEntries = iadev->num_vc*3;
+        iadev->CbrEntryPt = 0;
+        iadev->Granularity = MAX_ATM_155 / iadev->CbrTotEntries;
+        iadev->NumEnabledCBR = 0;
+
+	/* UBR scheduling Table and wait queue */  
+	/* initialize all bytes of UBR scheduler table and wait queue to 0   
+		- SCHEDSZ is 1K (# of entries).  
+		- UBR Table size is 4K  
+		- UBR wait queue is 4K  
+	   since the table and wait queues are contiguous, all the bytes   
+	   can be initialized by one memeset.  
+	*/  
+        
+        vcsize_sel = 0;
+        i = 8*1024;
+        while (i != iadev->num_vc) {
+          i /= 2;
+          vcsize_sel++;
+        }
+ 
+        i = MAIN_VC_TABLE * iadev->memSize;
+        writew(vcsize_sel | ((i >> 8) & 0xfff8),iadev->seg_reg+VCT_BASE);
+        i =  EXT_VC_TABLE * iadev->memSize;
+        writew((i >> 8) & 0xfffe, iadev->seg_reg+VCTE_BASE);
+        i = UBR_SCHED_TABLE * iadev->memSize;
+        writew((i & 0xffff) >> 11,  iadev->seg_reg+UBR_SBPTR_BASE);
+        i = UBR_WAIT_Q * iadev->memSize; 
+        writew((i >> 7) & 0xffff,  iadev->seg_reg+UBRWQ_BASE);
+ 	memset((caddr_t)(iadev->seg_ram+UBR_SCHED_TABLE*iadev->memSize),
+                                                       0, iadev->num_vc*8);
+	/* ABR scheduling Table(0x5000-0x57ff) and wait queue(0x5800-0x5fff)*/  
+	/* initialize all bytes of ABR scheduler table and wait queue to 0   
+		- SCHEDSZ is 1K (# of entries).  
+		- ABR Table size is 2K  
+		- ABR wait queue is 2K  
+	   since the table and wait queues are contiguous, all the bytes   
+	   can be intialized by one memeset.  
+	*/  
+        i = ABR_SCHED_TABLE * iadev->memSize;
+        writew((i >> 11) & 0xffff, iadev->seg_reg+ABR_SBPTR_BASE);
+        i = ABR_WAIT_Q * iadev->memSize;
+        writew((i >> 7) & 0xffff, iadev->seg_reg+ABRWQ_BASE);
+ 
+        i = ABR_SCHED_TABLE*iadev->memSize;
+	memset((caddr_t)(iadev->seg_ram+i),  0, iadev->num_vc*4);
+	vc = (struct main_vc *)iadev->MAIN_VC_TABLE_ADDR;  
+	evc = (struct ext_vc *)iadev->EXT_VC_TABLE_ADDR;  
+        iadev->testTable = kmalloc(sizeof(long)*iadev->num_vc, GFP_KERNEL); 
+        if (!iadev->testTable) {
+           printk("Get freepage  failed\n");
+	   goto err_free_desc_tbl;
+        }
+	for(i=0; i<iadev->num_vc; i++)  
+	{  
+		memset((caddr_t)vc, 0, sizeof(*vc));  
+		memset((caddr_t)evc, 0, sizeof(*evc));  
+                iadev->testTable[i] = kmalloc(sizeof(struct testTable_t),
+						GFP_KERNEL);
+		if (!iadev->testTable[i])
+			goto err_free_test_tables;
+              	iadev->testTable[i]->lastTime = 0;
+ 		iadev->testTable[i]->fract = 0;
+                iadev->testTable[i]->vc_status = VC_UBR;
+		vc++;  
+		evc++;  
+	}  
+  
+	/* Other Initialization */  
+	  
+	/* Max Rate Register */  
+        if (iadev->phy_type & FE_25MBIT_PHY) {
+	   writew(RATE25, iadev->seg_reg+MAXRATE);  
+	   writew((UBR_EN | (0x23 << 2)), iadev->seg_reg+STPARMS);  
+        }
+        else {
+	   writew(cellrate_to_float(iadev->LineRate),iadev->seg_reg+MAXRATE);
+	   writew((UBR_EN | ABR_EN | (0x23 << 2)), iadev->seg_reg+STPARMS);  
+        }
+	/* Set Idle Header Reigisters to be sure */  
+	writew(0, iadev->seg_reg+IDLEHEADHI);  
+	writew(0, iadev->seg_reg+IDLEHEADLO);  
+  
+	/* Program ABR UBR Priority Register  as  PRI_ABR_UBR_EQUAL */
+        writew(0xaa00, iadev->seg_reg+ABRUBR_ARB); 
+
+        iadev->close_pending = 0;
+        init_waitqueue_head(&iadev->close_wait);
+        init_waitqueue_head(&iadev->timeout_wait);
+	skb_queue_head_init(&iadev->tx_dma_q);  
+	ia_init_rtn_q(&iadev->tx_return_q);  
+
+	/* RM Cell Protocol ID and Message Type */  
+	writew(RM_TYPE_4_0, iadev->seg_reg+RM_TYPE);  
+        skb_queue_head_init (&iadev->tx_backlog);
+  
+	/* Mode Register 1 */  
+	writew(MODE_REG_1_VAL, iadev->seg_reg+MODE_REG_1);  
+  
+	/* Mode Register 0 */  
+	writew(T_ONLINE, iadev->seg_reg+MODE_REG_0);  
+  
+	/* Interrupt Status Register - read to clear */  
+	readw(iadev->seg_reg+SEG_INTR_STATUS_REG);  
+  
+	/* Interrupt Mask Reg- don't mask TCQ_NOT_EMPTY interrupt generation */  
+        writew(~(TRANSMIT_DONE | TCQ_NOT_EMPTY), iadev->seg_reg+SEG_MASK_REG);
+        writew(TRANSMIT_DONE, iadev->seg_reg+SEG_INTR_STATUS_REG);  
+        iadev->tx_pkt_cnt = 0;
+        iadev->rate_limit = iadev->LineRate / 3;
+  
+	return 0;
+
+err_free_test_tables:
+	while (--i >= 0)
+		kfree(iadev->testTable[i]);
+	kfree(iadev->testTable);
+err_free_desc_tbl:
+	kfree(iadev->desc_tbl);
+err_free_all_tx_bufs:
+	i = iadev->num_tx_desc;
+err_free_tx_bufs:
+	while (--i >= 0) {
+		struct cpcs_trailer_desc *desc = iadev->tx_buf + i;
+
+		pci_unmap_single(iadev->pci, desc->dma_addr,
+			sizeof(*desc->cpcs), PCI_DMA_TODEVICE);
+		kfree(desc->cpcs);
+	}
+	kfree(iadev->tx_buf);
+err_free_dle:
+	pci_free_consistent(iadev->pci, DLE_TOTAL_SIZE, iadev->tx_dle_q.start,
+			    iadev->tx_dle_dma);  
+err_out:
+	return -ENOMEM;
+}   
+   
+static irqreturn_t ia_int(int irq, void *dev_id, struct pt_regs *regs)  
+{  
+   struct atm_dev *dev;  
+   IADEV *iadev;  
+   unsigned int status;  
+   int handled = 0;
+
+   dev = dev_id;  
+   iadev = INPH_IA_DEV(dev);  
+   while( (status = readl(iadev->reg+IPHASE5575_BUS_STATUS_REG) & 0x7f))  
+   { 
+	handled = 1;
+        IF_EVENT(printk("ia_int: status = 0x%x\n", status);) 
+	if (status & STAT_REASSINT)  
+	{  
+	   /* do something */  
+	   IF_EVENT(printk("REASSINT Bus status reg: %08x\n", status);) 
+	   rx_intr(dev);  
+	}  
+	if (status & STAT_DLERINT)  
+	{  
+	   /* Clear this bit by writing a 1 to it. */  
+	   *(u_int *)(iadev->reg+IPHASE5575_BUS_STATUS_REG) = STAT_DLERINT;
+	   rx_dle_intr(dev);  
+	}  
+	if (status & STAT_SEGINT)  
+	{  
+	   /* do something */ 
+           IF_EVENT(printk("IA: tx_intr \n");) 
+	   tx_intr(dev);  
+	}  
+	if (status & STAT_DLETINT)  
+	{  
+	   *(u_int *)(iadev->reg+IPHASE5575_BUS_STATUS_REG) = STAT_DLETINT;  
+	   tx_dle_intr(dev);  
+	}  
+	if (status & (STAT_FEINT | STAT_ERRINT | STAT_MARKINT))  
+	{  
+           if (status & STAT_FEINT) 
+               IaFrontEndIntr(iadev);
+	}  
+   }
+   return IRQ_RETVAL(handled);
+}  
+	  
+	  
+	  
+/*----------------------------- entries --------------------------------*/  
+static int get_esi(struct atm_dev *dev)  
+{  
+	IADEV *iadev;  
+	int i;  
+	u32 mac1;  
+	u16 mac2;  
+	  
+	iadev = INPH_IA_DEV(dev);  
+	mac1 = cpu_to_be32(le32_to_cpu(readl(  
+				iadev->reg+IPHASE5575_MAC1)));  
+	mac2 = cpu_to_be16(le16_to_cpu(readl(iadev->reg+IPHASE5575_MAC2)));  
+	IF_INIT(printk("ESI: 0x%08x%04x\n", mac1, mac2);)  
+	for (i=0; i<MAC1_LEN; i++)  
+		dev->esi[i] = mac1 >>(8*(MAC1_LEN-1-i));  
+	  
+	for (i=0; i<MAC2_LEN; i++)  
+		dev->esi[i+MAC1_LEN] = mac2 >>(8*(MAC2_LEN - 1 -i));  
+	return 0;  
+}  
+	  
+static int reset_sar(struct atm_dev *dev)  
+{  
+	IADEV *iadev;  
+	int i, error = 1;  
+	unsigned int pci[64];  
+	  
+	iadev = INPH_IA_DEV(dev);  
+	for(i=0; i<64; i++)  
+	  if ((error = pci_read_config_dword(iadev->pci,  
+				i*4, &pci[i])) != PCIBIOS_SUCCESSFUL)  
+  	      return error;  
+	writel(0, iadev->reg+IPHASE5575_EXT_RESET);  
+	for(i=0; i<64; i++)  
+	  if ((error = pci_write_config_dword(iadev->pci,  
+					i*4, pci[i])) != PCIBIOS_SUCCESSFUL)  
+	    return error;  
+	udelay(5);  
+	return 0;  
+}  
+	  
+	  
+static int __init ia_init(struct atm_dev *dev)
+{  
+	IADEV *iadev;  
+	unsigned long real_base;
+	void __iomem *base;
+	unsigned short command;  
+	unsigned char revision;  
+	int error, i; 
+	  
+	/* The device has been identified and registered. Now we read   
+	   necessary configuration info like memory base address,   
+	   interrupt number etc */  
+	  
+	IF_INIT(printk(">ia_init\n");)  
+	dev->ci_range.vpi_bits = 0;  
+	dev->ci_range.vci_bits = NR_VCI_LD;  
+
+	iadev = INPH_IA_DEV(dev);  
+	real_base = pci_resource_start (iadev->pci, 0);
+	iadev->irq = iadev->pci->irq;
+		  
+	if ((error = pci_read_config_word(iadev->pci, PCI_COMMAND,&command))   
+		    || (error = pci_read_config_byte(iadev->pci,   
+				PCI_REVISION_ID,&revision)))   
+	{  
+		printk(KERN_ERR DEV_LABEL "(itf %d): init error 0x%x\n",  
+				dev->number,error);  
+		return -EINVAL;  
+	}  
+	IF_INIT(printk(DEV_LABEL "(itf %d): rev.%d,realbase=0x%lx,irq=%d\n",  
+			dev->number, revision, real_base, iadev->irq);)  
+	  
+	/* find mapping size of board */  
+	  
+	iadev->pci_map_size = pci_resource_len(iadev->pci, 0);
+
+        if (iadev->pci_map_size == 0x100000){
+          iadev->num_vc = 4096;
+	  dev->ci_range.vci_bits = NR_VCI_4K_LD;  
+          iadev->memSize = 4;
+        }
+        else if (iadev->pci_map_size == 0x40000) {
+          iadev->num_vc = 1024;
+          iadev->memSize = 1;
+        }
+        else {
+           printk("Unknown pci_map_size = 0x%x\n", iadev->pci_map_size);
+           return -EINVAL;
+        }
+	IF_INIT(printk (DEV_LABEL "map size: %i\n", iadev->pci_map_size);)  
+	  
+	/* enable bus mastering */
+	pci_set_master(iadev->pci);
+
+	/*  
+	 * Delay at least 1us before doing any mem accesses (how 'bout 10?)  
+	 */  
+	udelay(10);  
+	  
+	/* mapping the physical address to a virtual address in address space */  
+	base = ioremap(real_base,iadev->pci_map_size);  /* ioremap is not resolved ??? */  
+	  
+	if (!base)  
+	{  
+		printk(DEV_LABEL " (itf %d): can't set up page mapping\n",  
+			    dev->number);  
+		return error;  
+	}  
+	IF_INIT(printk(DEV_LABEL " (itf %d): rev.%d,base=%p,irq=%d\n",  
+			dev->number, revision, base, iadev->irq);)  
+	  
+	/* filling the iphase dev structure */  
+	iadev->mem = iadev->pci_map_size /2;  
+	iadev->real_base = real_base;  
+	iadev->base = base;  
+		  
+	/* Bus Interface Control Registers */  
+	iadev->reg = base + REG_BASE;
+	/* Segmentation Control Registers */  
+	iadev->seg_reg = base + SEG_BASE;
+	/* Reassembly Control Registers */  
+	iadev->reass_reg = base + REASS_BASE;  
+	/* Front end/ DMA control registers */  
+	iadev->phy = base + PHY_BASE;  
+	iadev->dma = base + PHY_BASE;  
+	/* RAM - Segmentation RAm and Reassembly RAM */  
+	iadev->ram = base + ACTUAL_RAM_BASE;  
+	iadev->seg_ram = base + ACTUAL_SEG_RAM_BASE;  
+	iadev->reass_ram = base + ACTUAL_REASS_RAM_BASE;  
+  
+	/* lets print out the above */  
+	IF_INIT(printk("Base addrs: %p %p %p \n %p %p %p %p\n", 
+          iadev->reg,iadev->seg_reg,iadev->reass_reg, 
+          iadev->phy, iadev->ram, iadev->seg_ram, 
+          iadev->reass_ram);) 
+	  
+	/* lets try reading the MAC address */  
+	error = get_esi(dev);  
+	if (error) {
+	  iounmap(iadev->base);
+	  return error;  
+	}
+        printk("IA: ");
+	for (i=0; i < ESI_LEN; i++)  
+                printk("%s%02X",i ? "-" : "",dev->esi[i]);  
+        printk("\n");  
+  
+        /* reset SAR */  
+        if (reset_sar(dev)) {
+	   iounmap(iadev->base);
+           printk("IA: reset SAR fail, please try again\n");
+           return 1;
+        }
+	return 0;  
+}  
+
+static void ia_update_stats(IADEV *iadev) {
+    if (!iadev->carrier_detect)
+        return;
+    iadev->rx_cell_cnt += readw(iadev->reass_reg+CELL_CTR0)&0xffff;
+    iadev->rx_cell_cnt += (readw(iadev->reass_reg+CELL_CTR1) & 0xffff) << 16;
+    iadev->drop_rxpkt +=  readw(iadev->reass_reg + DRP_PKT_CNTR ) & 0xffff;
+    iadev->drop_rxcell += readw(iadev->reass_reg + ERR_CNTR) & 0xffff;
+    iadev->tx_cell_cnt += readw(iadev->seg_reg + CELL_CTR_LO_AUTO)&0xffff;
+    iadev->tx_cell_cnt += (readw(iadev->seg_reg+CELL_CTR_HIGH_AUTO)&0xffff)<<16;
+    return;
+}
+  
+static void ia_led_timer(unsigned long arg) {
+ 	unsigned long flags;
+  	static u_char blinking[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+        u_char i;
+        static u32 ctrl_reg; 
+        for (i = 0; i < iadev_count; i++) {
+           if (ia_dev[i]) {
+	      ctrl_reg = readl(ia_dev[i]->reg+IPHASE5575_BUS_CONTROL_REG);
+	      if (blinking[i] == 0) {
+		 blinking[i]++;
+                 ctrl_reg &= (~CTRL_LED);
+                 writel(ctrl_reg, ia_dev[i]->reg+IPHASE5575_BUS_CONTROL_REG);
+                 ia_update_stats(ia_dev[i]);
+              }
+              else {
+		 blinking[i] = 0;
+		 ctrl_reg |= CTRL_LED;
+                 writel(ctrl_reg, ia_dev[i]->reg+IPHASE5575_BUS_CONTROL_REG);
+                 spin_lock_irqsave(&ia_dev[i]->tx_lock, flags);
+                 if (ia_dev[i]->close_pending)  
+                    wake_up(&ia_dev[i]->close_wait);
+                 ia_tx_poll(ia_dev[i]);
+                 spin_unlock_irqrestore(&ia_dev[i]->tx_lock, flags);
+              }
+           }
+        }
+	mod_timer(&ia_timer, jiffies + HZ / 4);
+ 	return;
+}
+
+static void ia_phy_put(struct atm_dev *dev, unsigned char value,   
+	unsigned long addr)  
+{  
+	writel(value, INPH_IA_DEV(dev)->phy+addr);  
+}  
+  
+static unsigned char ia_phy_get(struct atm_dev *dev, unsigned long addr)  
+{  
+	return readl(INPH_IA_DEV(dev)->phy+addr);  
+}  
+
+static void ia_free_tx(IADEV *iadev)
+{
+	int i;
+
+	kfree(iadev->desc_tbl);
+	for (i = 0; i < iadev->num_vc; i++)
+		kfree(iadev->testTable[i]);
+	kfree(iadev->testTable);
+	for (i = 0; i < iadev->num_tx_desc; i++) {
+		struct cpcs_trailer_desc *desc = iadev->tx_buf + i;
+
+		pci_unmap_single(iadev->pci, desc->dma_addr,
+			sizeof(*desc->cpcs), PCI_DMA_TODEVICE);
+		kfree(desc->cpcs);
+	}
+	kfree(iadev->tx_buf);
+	pci_free_consistent(iadev->pci, DLE_TOTAL_SIZE, iadev->tx_dle_q.start,
+			    iadev->tx_dle_dma);  
+}
+
+static void ia_free_rx(IADEV *iadev)
+{
+	kfree(iadev->rx_open);
+	pci_free_consistent(iadev->pci, DLE_TOTAL_SIZE, iadev->rx_dle_q.start,
+			  iadev->rx_dle_dma);  
+}
+
+static int __init ia_start(struct atm_dev *dev)
+{  
+	IADEV *iadev;  
+	int error;  
+	unsigned char phy;  
+	u32 ctrl_reg;  
+	IF_EVENT(printk(">ia_start\n");)  
+	iadev = INPH_IA_DEV(dev);  
+        if (request_irq(iadev->irq, &ia_int, SA_SHIRQ, DEV_LABEL, dev)) {  
+                printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n",  
+                    dev->number, iadev->irq);  
+		error = -EAGAIN;
+		goto err_out;
+        }  
+        /* @@@ should release IRQ on error */  
+	/* enabling memory + master */  
+        if ((error = pci_write_config_word(iadev->pci,   
+				PCI_COMMAND,   
+				PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER )))   
+	{  
+                printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory+"  
+                    "master (0x%x)\n",dev->number, error);  
+		error = -EIO;  
+		goto err_free_irq;
+        }  
+	udelay(10);  
+  
+	/* Maybe we should reset the front end, initialize Bus Interface Control   
+		Registers and see. */  
+  
+	IF_INIT(printk("Bus ctrl reg: %08x\n", 
+                            readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG));)  
+	ctrl_reg = readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG);  
+	ctrl_reg = (ctrl_reg & (CTRL_LED | CTRL_FE_RST))  
+			| CTRL_B8  
+			| CTRL_B16  
+			| CTRL_B32  
+			| CTRL_B48  
+			| CTRL_B64  
+			| CTRL_B128  
+			| CTRL_ERRMASK  
+			| CTRL_DLETMASK		/* shud be removed l8r */  
+			| CTRL_DLERMASK  
+			| CTRL_SEGMASK  
+			| CTRL_REASSMASK 	  
+			| CTRL_FEMASK  
+			| CTRL_CSPREEMPT;  
+  
+       writel(ctrl_reg, iadev->reg+IPHASE5575_BUS_CONTROL_REG);   
+  
+	IF_INIT(printk("Bus ctrl reg after initializing: %08x\n", 
+                           readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG));  
+	   printk("Bus status reg after init: %08x\n", 
+                            readl(iadev->reg+IPHASE5575_BUS_STATUS_REG));)  
+    
+        ia_hw_type(iadev); 
+	error = tx_init(dev);  
+	if (error)
+		goto err_free_irq;
+	error = rx_init(dev);  
+	if (error)
+		goto err_free_tx;
+  
+	ctrl_reg = readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG);  
+       	writel(ctrl_reg | CTRL_FE_RST, iadev->reg+IPHASE5575_BUS_CONTROL_REG);   
+	IF_INIT(printk("Bus ctrl reg after initializing: %08x\n", 
+                               readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG));)  
+        phy = 0; /* resolve compiler complaint */
+        IF_INIT ( 
+	if ((phy=ia_phy_get(dev,0)) == 0x30)  
+		printk("IA: pm5346,rev.%d\n",phy&0x0f);  
+	else  
+		printk("IA: utopia,rev.%0x\n",phy);) 
+
+	if (iadev->phy_type &  FE_25MBIT_PHY)
+           ia_mb25_init(iadev);
+	else if (iadev->phy_type & (FE_DS3_PHY | FE_E3_PHY))
+           ia_suni_pm7345_init(iadev);
+	else {
+		error = suni_init(dev);
+		if (error)
+			goto err_free_rx;
+		/* 
+		 * Enable interrupt on loss of signal
+		 * SUNI_RSOP_CIE - 0x10
+		 * SUNI_RSOP_CIE_LOSE - 0x04
+		 */
+		ia_phy_put(dev, ia_phy_get(dev, 0x10) | 0x04, 0x10);
+#ifndef MODULE
+		error = dev->phy->start(dev);
+		if (error)
+			goto err_free_rx;
+#endif
+		/* Get iadev->carrier_detect status */
+		IaFrontEndIntr(iadev);
+	}
+	return 0;
+
+err_free_rx:
+	ia_free_rx(iadev);
+err_free_tx:
+	ia_free_tx(iadev);
+err_free_irq:
+	free_irq(iadev->irq, dev);  
+err_out:
+	return error;
+}  
+  
+static void ia_close(struct atm_vcc *vcc)  
+{
+	DEFINE_WAIT(wait);
+        u16 *vc_table;
+        IADEV *iadev;
+        struct ia_vcc *ia_vcc;
+        struct sk_buff *skb = NULL;
+        struct sk_buff_head tmp_tx_backlog, tmp_vcc_backlog;
+        unsigned long closetime, flags;
+
+        iadev = INPH_IA_DEV(vcc->dev);
+        ia_vcc = INPH_IA_VCC(vcc);
+	if (!ia_vcc) return;  
+
+        IF_EVENT(printk("ia_close: ia_vcc->vc_desc_cnt = %d  vci = %d\n", 
+                                              ia_vcc->vc_desc_cnt,vcc->vci);)
+	clear_bit(ATM_VF_READY,&vcc->flags);
+        skb_queue_head_init (&tmp_tx_backlog);
+        skb_queue_head_init (&tmp_vcc_backlog); 
+        if (vcc->qos.txtp.traffic_class != ATM_NONE) {
+           iadev->close_pending++;
+	   prepare_to_wait(&iadev->timeout_wait, &wait, TASK_UNINTERRUPTIBLE);
+	   schedule_timeout(50);
+	   finish_wait(&iadev->timeout_wait, &wait);
+           spin_lock_irqsave(&iadev->tx_lock, flags); 
+           while((skb = skb_dequeue(&iadev->tx_backlog))) {
+              if (ATM_SKB(skb)->vcc == vcc){ 
+                 if (vcc->pop) vcc->pop(vcc, skb);
+                 else dev_kfree_skb_any(skb);
+              }
+              else 
+                 skb_queue_tail(&tmp_tx_backlog, skb);
+           } 
+           while((skb = skb_dequeue(&tmp_tx_backlog))) 
+             skb_queue_tail(&iadev->tx_backlog, skb);
+           IF_EVENT(printk("IA TX Done decs_cnt = %d\n", ia_vcc->vc_desc_cnt);) 
+           closetime = 300000 / ia_vcc->pcr;
+           if (closetime == 0)
+              closetime = 1;
+           spin_unlock_irqrestore(&iadev->tx_lock, flags);
+           wait_event_timeout(iadev->close_wait, (ia_vcc->vc_desc_cnt <= 0), closetime);
+           spin_lock_irqsave(&iadev->tx_lock, flags);
+           iadev->close_pending--;
+           iadev->testTable[vcc->vci]->lastTime = 0;
+           iadev->testTable[vcc->vci]->fract = 0; 
+           iadev->testTable[vcc->vci]->vc_status = VC_UBR; 
+           if (vcc->qos.txtp.traffic_class == ATM_ABR) {
+              if (vcc->qos.txtp.min_pcr > 0)
+                 iadev->sum_mcr -= vcc->qos.txtp.min_pcr;
+           }
+           if (vcc->qos.txtp.traffic_class == ATM_CBR) {
+              ia_vcc = INPH_IA_VCC(vcc); 
+              iadev->sum_mcr -= ia_vcc->NumCbrEntry*iadev->Granularity;
+              ia_cbrVc_close (vcc);
+           }
+           spin_unlock_irqrestore(&iadev->tx_lock, flags);
+        }
+        
+        if (vcc->qos.rxtp.traffic_class != ATM_NONE) {   
+           // reset reass table
+           vc_table = (u16 *)(iadev->reass_ram+REASS_TABLE*iadev->memSize);
+           vc_table += vcc->vci; 
+           *vc_table = NO_AAL5_PKT;
+           // reset vc table
+           vc_table = (u16 *)(iadev->reass_ram+RX_VC_TABLE*iadev->memSize);
+           vc_table += vcc->vci;
+           *vc_table = (vcc->vci << 6) | 15;
+           if (vcc->qos.rxtp.traffic_class == ATM_ABR) {
+              struct abr_vc_table __iomem *abr_vc_table = 
+                                (iadev->reass_ram+ABR_VC_TABLE*iadev->memSize);
+              abr_vc_table +=  vcc->vci;
+              abr_vc_table->rdf = 0x0003;
+              abr_vc_table->air = 0x5eb1;
+           }                                 
+           // Drain the packets
+           rx_dle_intr(vcc->dev); 
+           iadev->rx_open[vcc->vci] = NULL;
+        }
+	kfree(INPH_IA_VCC(vcc));  
+        ia_vcc = NULL;
+        vcc->dev_data = NULL;
+        clear_bit(ATM_VF_ADDR,&vcc->flags);
+        return;        
+}  
+  
+static int ia_open(struct atm_vcc *vcc)
+{  
+	IADEV *iadev;  
+	struct ia_vcc *ia_vcc;  
+	int error;  
+	if (!test_bit(ATM_VF_PARTIAL,&vcc->flags))  
+	{  
+		IF_EVENT(printk("ia: not partially allocated resources\n");)  
+		vcc->dev_data = NULL;
+	}  
+	iadev = INPH_IA_DEV(vcc->dev);  
+	if (vcc->vci != ATM_VPI_UNSPEC && vcc->vpi != ATM_VCI_UNSPEC)  
+	{  
+		IF_EVENT(printk("iphase open: unspec part\n");)  
+		set_bit(ATM_VF_ADDR,&vcc->flags);
+	}  
+	if (vcc->qos.aal != ATM_AAL5)  
+		return -EINVAL;  
+	IF_EVENT(printk(DEV_LABEL "(itf %d): open %d.%d\n", 
+                                 vcc->dev->number, vcc->vpi, vcc->vci);)  
+  
+	/* Device dependent initialization */  
+	ia_vcc = kmalloc(sizeof(*ia_vcc), GFP_KERNEL);  
+	if (!ia_vcc) return -ENOMEM;  
+	vcc->dev_data = ia_vcc;
+  
+	if ((error = open_rx(vcc)))  
+	{  
+		IF_EVENT(printk("iadev: error in open_rx, closing\n");)  
+		ia_close(vcc);  
+		return error;  
+	}  
+  
+	if ((error = open_tx(vcc)))  
+	{  
+		IF_EVENT(printk("iadev: error in open_tx, closing\n");)  
+		ia_close(vcc);  
+		return error;  
+	}  
+  
+	set_bit(ATM_VF_READY,&vcc->flags);
+
+#if 0
+        {
+           static u8 first = 1; 
+           if (first) {
+              ia_timer.expires = jiffies + 3*HZ;
+              add_timer(&ia_timer);
+              first = 0;
+           }           
+        }
+#endif
+	IF_EVENT(printk("ia open returning\n");)  
+	return 0;  
+}  
+  
+static int ia_change_qos(struct atm_vcc *vcc, struct atm_qos *qos, int flags)  
+{  
+	IF_EVENT(printk(">ia_change_qos\n");)  
+	return 0;  
+}  
+  
+static int ia_ioctl(struct atm_dev *dev, unsigned int cmd, void __user *arg)  
+{  
+   IA_CMDBUF ia_cmds;
+   IADEV *iadev;
+   int i, board;
+   u16 __user *tmps;
+   IF_EVENT(printk(">ia_ioctl\n");)  
+   if (cmd != IA_CMD) {
+      if (!dev->phy->ioctl) return -EINVAL;
+      return dev->phy->ioctl(dev,cmd,arg);
+   }
+   if (copy_from_user(&ia_cmds, arg, sizeof ia_cmds)) return -EFAULT; 
+   board = ia_cmds.status;
+   if ((board < 0) || (board > iadev_count))
+         board = 0;    
+   iadev = ia_dev[board];
+   switch (ia_cmds.cmd) {
+   case MEMDUMP:
+   {
+	switch (ia_cmds.sub_cmd) {
+       	  case MEMDUMP_DEV:     
+	     if (!capable(CAP_NET_ADMIN)) return -EPERM;
+	     if (copy_to_user(ia_cmds.buf, iadev, sizeof(IADEV)))
+                return -EFAULT;
+             ia_cmds.status = 0;
+             break;
+          case MEMDUMP_SEGREG:
+	     if (!capable(CAP_NET_ADMIN)) return -EPERM;
+             tmps = (u16 __user *)ia_cmds.buf;
+             for(i=0; i<0x80; i+=2, tmps++)
+                if(put_user((u16)(readl(iadev->seg_reg+i) & 0xffff), tmps)) return -EFAULT;
+             ia_cmds.status = 0;
+             ia_cmds.len = 0x80;
+             break;
+          case MEMDUMP_REASSREG:
+	     if (!capable(CAP_NET_ADMIN)) return -EPERM;
+             tmps = (u16 __user *)ia_cmds.buf;
+             for(i=0; i<0x80; i+=2, tmps++)
+                if(put_user((u16)(readl(iadev->reass_reg+i) & 0xffff), tmps)) return -EFAULT;
+             ia_cmds.status = 0;
+             ia_cmds.len = 0x80;
+             break;
+          case MEMDUMP_FFL:
+          {  
+             ia_regs_t       *regs_local;
+             ffredn_t        *ffL;
+             rfredn_t        *rfL;
+                     
+	     if (!capable(CAP_NET_ADMIN)) return -EPERM;
+	     regs_local = kmalloc(sizeof(*regs_local), GFP_KERNEL);
+	     if (!regs_local) return -ENOMEM;
+	     ffL = &regs_local->ffredn;
+	     rfL = &regs_local->rfredn;
+             /* Copy real rfred registers into the local copy */
+ 	     for (i=0; i<(sizeof (rfredn_t))/4; i++)
+                ((u_int *)rfL)[i] = readl(iadev->reass_reg + i) & 0xffff;
+             	/* Copy real ffred registers into the local copy */
+	     for (i=0; i<(sizeof (ffredn_t))/4; i++)
+                ((u_int *)ffL)[i] = readl(iadev->seg_reg + i) & 0xffff;
+
+             if (copy_to_user(ia_cmds.buf, regs_local,sizeof(ia_regs_t))) {
+                kfree(regs_local);
+                return -EFAULT;
+             }
+             kfree(regs_local);
+             printk("Board %d registers dumped\n", board);
+             ia_cmds.status = 0;                  
+	 }	
+    	     break;        
+         case READ_REG:
+         {  
+	     if (!capable(CAP_NET_ADMIN)) return -EPERM;
+             desc_dbg(iadev); 
+             ia_cmds.status = 0; 
+         }
+             break;
+         case 0x6:
+         {  
+             ia_cmds.status = 0; 
+             printk("skb = 0x%lx\n", (long)skb_peek(&iadev->tx_backlog));
+             printk("rtn_q: 0x%lx\n",(long)ia_deque_rtn_q(&iadev->tx_return_q));
+         }
+             break;
+         case 0x8:
+         {
+             struct k_sonet_stats *stats;
+             stats = &PRIV(_ia_dev[board])->sonet_stats;
+             printk("section_bip: %d\n", atomic_read(&stats->section_bip));
+             printk("line_bip   : %d\n", atomic_read(&stats->line_bip));
+             printk("path_bip   : %d\n", atomic_read(&stats->path_bip));
+             printk("line_febe  : %d\n", atomic_read(&stats->line_febe));
+             printk("path_febe  : %d\n", atomic_read(&stats->path_febe));
+             printk("corr_hcs   : %d\n", atomic_read(&stats->corr_hcs));
+             printk("uncorr_hcs : %d\n", atomic_read(&stats->uncorr_hcs));
+             printk("tx_cells   : %d\n", atomic_read(&stats->tx_cells));
+             printk("rx_cells   : %d\n", atomic_read(&stats->rx_cells));
+         }
+            ia_cmds.status = 0;
+            break;
+         case 0x9:
+	    if (!capable(CAP_NET_ADMIN)) return -EPERM;
+            for (i = 1; i <= iadev->num_rx_desc; i++)
+               free_desc(_ia_dev[board], i);
+            writew( ~(RX_FREEQ_EMPT | RX_EXCP_RCVD), 
+                                            iadev->reass_reg+REASS_MASK_REG);
+            iadev->rxing = 1;
+            
+            ia_cmds.status = 0;
+            break;
+
+         case 0xb:
+	    if (!capable(CAP_NET_ADMIN)) return -EPERM;
+            IaFrontEndIntr(iadev);
+            break;
+         case 0xa:
+	    if (!capable(CAP_NET_ADMIN)) return -EPERM;
+         {  
+             ia_cmds.status = 0; 
+             IADebugFlag = ia_cmds.maddr;
+             printk("New debug option loaded\n");
+         }
+             break;
+         default:
+             ia_cmds.status = 0;
+             break;
+      }	
+   }
+      break;
+   default:
+      break;
+
+   }	
+   return 0;  
+}  
+  
+static int ia_getsockopt(struct atm_vcc *vcc, int level, int optname,   
+	void __user *optval, int optlen)  
+{  
+	IF_EVENT(printk(">ia_getsockopt\n");)  
+	return -EINVAL;  
+}  
+  
+static int ia_setsockopt(struct atm_vcc *vcc, int level, int optname,   
+	void __user *optval, int optlen)  
+{  
+	IF_EVENT(printk(">ia_setsockopt\n");)  
+	return -EINVAL;  
+}  
+  
+static int ia_pkt_tx (struct atm_vcc *vcc, struct sk_buff *skb) {
+        IADEV *iadev;
+        struct dle *wr_ptr;
+        struct tx_buf_desc __iomem *buf_desc_ptr;
+        int desc;
+        int comp_code;
+        int total_len;
+        struct cpcs_trailer *trailer;
+        struct ia_vcc *iavcc;
+
+        iadev = INPH_IA_DEV(vcc->dev);  
+        iavcc = INPH_IA_VCC(vcc);
+        if (!iavcc->txing) {
+           printk("discard packet on closed VC\n");
+           if (vcc->pop)
+		vcc->pop(vcc, skb);
+           else
+		dev_kfree_skb_any(skb);
+	   return 0;
+        }
+
+        if (skb->len > iadev->tx_buf_sz - 8) {
+           printk("Transmit size over tx buffer size\n");
+           if (vcc->pop)
+                 vcc->pop(vcc, skb);
+           else
+                 dev_kfree_skb_any(skb);
+          return 0;
+        }
+        if ((u32)skb->data & 3) {
+           printk("Misaligned SKB\n");
+           if (vcc->pop)
+                 vcc->pop(vcc, skb);
+           else
+                 dev_kfree_skb_any(skb);
+           return 0;
+        }       
+	/* Get a descriptor number from our free descriptor queue  
+	   We get the descr number from the TCQ now, since I am using  
+	   the TCQ as a free buffer queue. Initially TCQ will be   
+	   initialized with all the descriptors and is hence, full.  
+	*/
+	desc = get_desc (iadev, iavcc);
+	if (desc == 0xffff) 
+	    return 1;
+	comp_code = desc >> 13;  
+	desc &= 0x1fff;  
+  
+	if ((desc == 0) || (desc > iadev->num_tx_desc))  
+	{  
+		IF_ERR(printk(DEV_LABEL "invalid desc for send: %d\n", desc);) 
+                atomic_inc(&vcc->stats->tx);
+		if (vcc->pop)   
+		    vcc->pop(vcc, skb);   
+		else  
+		    dev_kfree_skb_any(skb);
+		return 0;   /* return SUCCESS */
+	}  
+  
+	if (comp_code)  
+	{  
+	    IF_ERR(printk(DEV_LABEL "send desc:%d completion code %d error\n", 
+                                                            desc, comp_code);)  
+	}  
+       
+        /* remember the desc and vcc mapping */
+        iavcc->vc_desc_cnt++;
+        iadev->desc_tbl[desc-1].iavcc = iavcc;
+        iadev->desc_tbl[desc-1].txskb = skb;
+        IA_SKB_STATE(skb) = 0;
+
+        iadev->ffL.tcq_rd += 2;
+        if (iadev->ffL.tcq_rd > iadev->ffL.tcq_ed)
+	  	iadev->ffL.tcq_rd  = iadev->ffL.tcq_st;
+	writew(iadev->ffL.tcq_rd, iadev->seg_reg+TCQ_RD_PTR);
+  
+	/* Put the descriptor number in the packet ready queue  
+		and put the updated write pointer in the DLE field   
+	*/   
+	*(u16*)(iadev->seg_ram+iadev->ffL.prq_wr) = desc; 
+
+ 	iadev->ffL.prq_wr += 2;
+        if (iadev->ffL.prq_wr > iadev->ffL.prq_ed)
+                iadev->ffL.prq_wr = iadev->ffL.prq_st;
+	  
+	/* Figure out the exact length of the packet and padding required to 
+           make it  aligned on a 48 byte boundary.  */
+	total_len = skb->len + sizeof(struct cpcs_trailer);  
+	total_len = ((total_len + 47) / 48) * 48;
+	IF_TX(printk("ia packet len:%d padding:%d\n", total_len, total_len - skb->len);)  
+ 
+	/* Put the packet in a tx buffer */   
+	trailer = iadev->tx_buf[desc-1].cpcs;
+        IF_TX(printk("Sent: skb = 0x%x skb->data: 0x%x len: %d, desc: %d\n",
+                  (u32)skb, (u32)skb->data, skb->len, desc);)
+	trailer->control = 0; 
+        /*big endian*/ 
+	trailer->length = ((skb->len & 0xff) << 8) | ((skb->len & 0xff00) >> 8);
+	trailer->crc32 = 0;	/* not needed - dummy bytes */  
+
+	/* Display the packet */  
+	IF_TXPKT(printk("Sent data: len = %d MsgNum = %d\n", 
+                                                        skb->len, tcnter++);  
+        xdump(skb->data, skb->len, "TX: ");
+        printk("\n");)
+
+	/* Build the buffer descriptor */  
+	buf_desc_ptr = iadev->seg_ram+TX_DESC_BASE;
+	buf_desc_ptr += desc;	/* points to the corresponding entry */  
+	buf_desc_ptr->desc_mode = AAL5 | EOM_EN | APP_CRC32 | CMPL_INT;   
+	/* Huh ? p.115 of users guide describes this as a read-only register */
+        writew(TRANSMIT_DONE, iadev->seg_reg+SEG_INTR_STATUS_REG);
+	buf_desc_ptr->vc_index = vcc->vci;
+	buf_desc_ptr->bytes = total_len;  
+
+        if (vcc->qos.txtp.traffic_class == ATM_ABR)  
+	   clear_lockup (vcc, iadev);
+
+	/* Build the DLE structure */  
+	wr_ptr = iadev->tx_dle_q.write;  
+	memset((caddr_t)wr_ptr, 0, sizeof(*wr_ptr));  
+	wr_ptr->sys_pkt_addr = pci_map_single(iadev->pci, skb->data,
+		skb->len, PCI_DMA_TODEVICE);
+	wr_ptr->local_pkt_addr = (buf_desc_ptr->buf_start_hi << 16) | 
+                                                  buf_desc_ptr->buf_start_lo;  
+	/* wr_ptr->bytes = swap(total_len);	didn't seem to affect ?? */  
+	wr_ptr->bytes = skb->len;  
+
+        /* hw bug - DLEs of 0x2d, 0x2e, 0x2f cause DMA lockup */
+        if ((wr_ptr->bytes >> 2) == 0xb)
+           wr_ptr->bytes = 0x30;
+
+	wr_ptr->mode = TX_DLE_PSI; 
+	wr_ptr->prq_wr_ptr_data = 0;
+  
+	/* end is not to be used for the DLE q */  
+	if (++wr_ptr == iadev->tx_dle_q.end)  
+		wr_ptr = iadev->tx_dle_q.start;  
+        
+        /* Build trailer dle */
+        wr_ptr->sys_pkt_addr = iadev->tx_buf[desc-1].dma_addr;
+        wr_ptr->local_pkt_addr = ((buf_desc_ptr->buf_start_hi << 16) | 
+          buf_desc_ptr->buf_start_lo) + total_len - sizeof(struct cpcs_trailer);
+
+        wr_ptr->bytes = sizeof(struct cpcs_trailer);
+        wr_ptr->mode = DMA_INT_ENABLE; 
+        wr_ptr->prq_wr_ptr_data = iadev->ffL.prq_wr;
+        
+        /* end is not to be used for the DLE q */
+        if (++wr_ptr == iadev->tx_dle_q.end)  
+                wr_ptr = iadev->tx_dle_q.start;
+
+	iadev->tx_dle_q.write = wr_ptr;  
+        ATM_DESC(skb) = vcc->vci;
+        skb_queue_tail(&iadev->tx_dma_q, skb);
+
+        atomic_inc(&vcc->stats->tx);
+        iadev->tx_pkt_cnt++;
+	/* Increment transaction counter */  
+	writel(2, iadev->dma+IPHASE5575_TX_COUNTER);  
+        
+#if 0        
+        /* add flow control logic */ 
+        if (atomic_read(&vcc->stats->tx) % 20 == 0) {
+          if (iavcc->vc_desc_cnt > 10) {
+             vcc->tx_quota =  vcc->tx_quota * 3 / 4;
+            printk("Tx1:  vcc->tx_quota = %d \n", (u32)vcc->tx_quota );
+              iavcc->flow_inc = -1;
+              iavcc->saved_tx_quota = vcc->tx_quota;
+           } else if ((iavcc->flow_inc < 0) && (iavcc->vc_desc_cnt < 3)) {
+             // vcc->tx_quota = 3 * iavcc->saved_tx_quota / 4;
+             printk("Tx2:  vcc->tx_quota = %d \n", (u32)vcc->tx_quota ); 
+              iavcc->flow_inc = 0;
+           }
+        }
+#endif
+	IF_TX(printk("ia send done\n");)  
+	return 0;  
+}  
+
+static int ia_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+        IADEV *iadev; 
+        struct ia_vcc *iavcc;
+        unsigned long flags;
+
+        iadev = INPH_IA_DEV(vcc->dev);
+        iavcc = INPH_IA_VCC(vcc); 
+        if ((!skb)||(skb->len>(iadev->tx_buf_sz-sizeof(struct cpcs_trailer))))
+        {
+            if (!skb)
+                printk(KERN_CRIT "null skb in ia_send\n");
+            else dev_kfree_skb_any(skb);
+            return -EINVAL;
+        }                         
+        spin_lock_irqsave(&iadev->tx_lock, flags); 
+        if (!test_bit(ATM_VF_READY,&vcc->flags)){ 
+            dev_kfree_skb_any(skb);
+            spin_unlock_irqrestore(&iadev->tx_lock, flags);
+            return -EINVAL; 
+        }
+        ATM_SKB(skb)->vcc = vcc;
+ 
+        if (skb_peek(&iadev->tx_backlog)) {
+           skb_queue_tail(&iadev->tx_backlog, skb);
+        }
+        else {
+           if (ia_pkt_tx (vcc, skb)) {
+              skb_queue_tail(&iadev->tx_backlog, skb);
+           }
+        }
+        spin_unlock_irqrestore(&iadev->tx_lock, flags);
+        return 0;
+
+}
+
+static int ia_proc_read(struct atm_dev *dev,loff_t *pos,char *page)
+{ 
+  int   left = *pos, n;   
+  char  *tmpPtr;
+  IADEV *iadev = INPH_IA_DEV(dev);
+  if(!left--) {
+     if (iadev->phy_type == FE_25MBIT_PHY) {
+       n = sprintf(page, "  Board Type         :  Iphase5525-1KVC-128K\n");
+       return n;
+     }
+     if (iadev->phy_type == FE_DS3_PHY)
+        n = sprintf(page, "  Board Type         :  Iphase-ATM-DS3");
+     else if (iadev->phy_type == FE_E3_PHY)
+        n = sprintf(page, "  Board Type         :  Iphase-ATM-E3");
+     else if (iadev->phy_type == FE_UTP_OPTION)
+         n = sprintf(page, "  Board Type         :  Iphase-ATM-UTP155"); 
+     else
+        n = sprintf(page, "  Board Type         :  Iphase-ATM-OC3");
+     tmpPtr = page + n;
+     if (iadev->pci_map_size == 0x40000)
+        n += sprintf(tmpPtr, "-1KVC-");
+     else
+        n += sprintf(tmpPtr, "-4KVC-");  
+     tmpPtr = page + n; 
+     if ((iadev->memType & MEM_SIZE_MASK) == MEM_SIZE_1M)
+        n += sprintf(tmpPtr, "1M  \n");
+     else if ((iadev->memType & MEM_SIZE_MASK) == MEM_SIZE_512K)
+        n += sprintf(tmpPtr, "512K\n");
+     else
+       n += sprintf(tmpPtr, "128K\n");
+     return n;
+  }
+  if (!left) {
+     return  sprintf(page, "  Number of Tx Buffer:  %u\n"
+                           "  Size of Tx Buffer  :  %u\n"
+                           "  Number of Rx Buffer:  %u\n"
+                           "  Size of Rx Buffer  :  %u\n"
+                           "  Packets Receiverd  :  %u\n"
+                           "  Packets Transmitted:  %u\n"
+                           "  Cells Received     :  %u\n"
+                           "  Cells Transmitted  :  %u\n"
+                           "  Board Dropped Cells:  %u\n"
+                           "  Board Dropped Pkts :  %u\n",
+                           iadev->num_tx_desc,  iadev->tx_buf_sz,
+                           iadev->num_rx_desc,  iadev->rx_buf_sz,
+                           iadev->rx_pkt_cnt,   iadev->tx_pkt_cnt,
+                           iadev->rx_cell_cnt, iadev->tx_cell_cnt,
+                           iadev->drop_rxcell, iadev->drop_rxpkt);                        
+  }
+  return 0;
+}
+  
+static const struct atmdev_ops ops = {  
+	.open		= ia_open,  
+	.close		= ia_close,  
+	.ioctl		= ia_ioctl,  
+	.getsockopt	= ia_getsockopt,  
+	.setsockopt	= ia_setsockopt,  
+	.send		= ia_send,  
+	.phy_put	= ia_phy_put,  
+	.phy_get	= ia_phy_get,  
+	.change_qos	= ia_change_qos,  
+	.proc_read	= ia_proc_read,
+	.owner		= THIS_MODULE,
+};  
+	  
+static int __devinit ia_init_one(struct pci_dev *pdev,
+				 const struct pci_device_id *ent)
+{  
+	struct atm_dev *dev;  
+	IADEV *iadev;  
+        unsigned long flags;
+	int ret;
+
+	iadev = kmalloc(sizeof(*iadev), GFP_KERNEL); 
+	if (!iadev) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+	memset(iadev, 0, sizeof(*iadev));
+	iadev->pci = pdev;
+
+	IF_INIT(printk("ia detected at bus:%d dev: %d function:%d\n",
+		pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));)
+	if (pci_enable_device(pdev)) {
+		ret = -ENODEV;
+		goto err_out_free_iadev;
+	}
+	dev = atm_dev_register(DEV_LABEL, &ops, -1, NULL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err_out_disable_dev;
+	}
+	dev->dev_data = iadev;
+	IF_INIT(printk(DEV_LABEL "registered at (itf :%d)\n", dev->number);)
+	IF_INIT(printk("dev_id = 0x%x iadev->LineRate = %d \n", (u32)dev,
+		iadev->LineRate);)
+
+	ia_dev[iadev_count] = iadev;
+	_ia_dev[iadev_count] = dev;
+	iadev_count++;
+	spin_lock_init(&iadev->misc_lock);
+	/* First fixes first. I don't want to think about this now. */
+	spin_lock_irqsave(&iadev->misc_lock, flags); 
+	if (ia_init(dev) || ia_start(dev)) {  
+		IF_INIT(printk("IA register failed!\n");)
+		iadev_count--;
+		ia_dev[iadev_count] = NULL;
+		_ia_dev[iadev_count] = NULL;
+		spin_unlock_irqrestore(&iadev->misc_lock, flags); 
+		ret = -EINVAL;
+		goto err_out_deregister_dev;
+	}
+	spin_unlock_irqrestore(&iadev->misc_lock, flags); 
+	IF_EVENT(printk("iadev_count = %d\n", iadev_count);)
+
+	iadev->next_board = ia_boards;  
+	ia_boards = dev;  
+
+	pci_set_drvdata(pdev, dev);
+
+	return 0;
+
+err_out_deregister_dev:
+	atm_dev_deregister(dev);  
+err_out_disable_dev:
+	pci_disable_device(pdev);
+err_out_free_iadev:
+	kfree(iadev);
+err_out:
+	return ret;
+}
+
+static void __devexit ia_remove_one(struct pci_dev *pdev)
+{
+	struct atm_dev *dev = pci_get_drvdata(pdev);
+	IADEV *iadev = INPH_IA_DEV(dev);
+
+	ia_phy_put(dev, ia_phy_get(dev,0x10) & ~(0x4), 0x10); 
+	udelay(1);
+
+	/* De-register device */  
+      	free_irq(iadev->irq, dev);
+	iadev_count--;
+	ia_dev[iadev_count] = NULL;
+	_ia_dev[iadev_count] = NULL;
+	IF_EVENT(printk("deregistering iav at (itf:%d)\n", dev->number);)
+	atm_dev_deregister(dev);
+
+      	iounmap(iadev->base);  
+	pci_disable_device(pdev);
+
+	ia_free_rx(iadev);
+	ia_free_tx(iadev);
+
+      	kfree(iadev);
+}
+
+static struct pci_device_id ia_pci_tbl[] = {
+	{ PCI_VENDOR_ID_IPHASE, 0x0008, PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_IPHASE, 0x0009, PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, ia_pci_tbl);
+
+static struct pci_driver ia_driver = {
+	.name =         DEV_LABEL,
+	.id_table =     ia_pci_tbl,
+	.probe =        ia_init_one,
+	.remove =       __devexit_p(ia_remove_one),
+};
+
+static int __init ia_module_init(void)
+{
+	int ret;
+
+	ret = pci_register_driver(&ia_driver);
+	if (ret >= 0) {
+		ia_timer.expires = jiffies + 3*HZ;
+		add_timer(&ia_timer); 
+	} else
+		printk(KERN_ERR DEV_LABEL ": no adapter found\n");  
+	return ret;
+}
+
+static void __exit ia_module_exit(void)
+{
+	pci_unregister_driver(&ia_driver);
+
+        del_timer(&ia_timer);
+}
+
+module_init(ia_module_init);
+module_exit(ia_module_exit);
diff --git a/drivers/atm/iphase.h b/drivers/atm/iphase.h
new file mode 100644
index 0000000..b8d0bd4
--- /dev/null
+++ b/drivers/atm/iphase.h
@@ -0,0 +1,1464 @@
+/******************************************************************************
+             Device driver for Interphase ATM PCI adapter cards 
+                    Author: Peter Wang  <pwang@iphase.com>            
+                   Interphase Corporation  <www.iphase.com>           
+                               Version: 1.0   
+               iphase.h:  This is the header file for iphase.c. 
+*******************************************************************************
+      
+      This software may be used and distributed according to the terms
+      of the GNU General Public License (GPL), incorporated herein by reference.
+      Drivers based on this skeleton fall under the GPL and must retain
+      the authorship (implicit copyright) notice.
+
+      This program is distributed in the hope that it will be useful, but
+      WITHOUT ANY WARRANTY; without even the implied warranty of
+      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+      General Public License for more details.
+      
+      Modified from an incomplete driver for Interphase 5575 1KVC 1M card which 
+      was originally written by Monalisa Agrawal at UNH. Now this driver 
+      supports a variety of varients of Interphase ATM PCI (i)Chip adapter 
+      card family (See www.iphase.com/products/ClassSheet.cfm?ClassID=ATM) 
+      in terms of PHY type, the size of control memory and the size of 
+      packet memory. The followings are the change log and history:
+     
+          Bugfix the Mona's UBR driver.
+          Modify the basic memory allocation and dma logic.
+          Port the driver to the latest kernel from 2.0.46.
+          Complete the ABR logic of the driver, and added the ABR work-
+              around for the hardware anormalies.
+          Add the CBR support.
+	  Add the flow control logic to the driver to allow rate-limit VC.
+          Add 4K VC support to the board with 512K control memory.
+          Add the support of all the variants of the Interphase ATM PCI 
+          (i)Chip adapter cards including x575 (155M OC3 and UTP155), x525
+          (25M UTP25) and x531 (DS3 and E3).
+          Add SMP support.
+
+      Support and updates available at: ftp://ftp.iphase.com/pub/atm
+
+*******************************************************************************/
+  
+#ifndef IPHASE_H  
+#define IPHASE_H  
+
+#include <linux/config.h>
+
+/************************ IADBG DEFINE *********************************/
+/* IADebugFlag Bit Map */ 
+#define IF_IADBG_INIT_ADAPTER   0x00000001        // init adapter info
+#define IF_IADBG_TX             0x00000002        // debug TX
+#define IF_IADBG_RX             0x00000004        // debug RX
+#define IF_IADBG_QUERY_INFO     0x00000008        // debug Request call
+#define IF_IADBG_SHUTDOWN       0x00000010        // debug shutdown event
+#define IF_IADBG_INTR           0x00000020        // debug interrupt DPC
+#define IF_IADBG_TXPKT          0x00000040  	  // debug TX PKT
+#define IF_IADBG_RXPKT          0x00000080  	  // debug RX PKT
+#define IF_IADBG_ERR            0x00000100        // debug system error
+#define IF_IADBG_EVENT          0x00000200        // debug event
+#define IF_IADBG_DIS_INTR       0x00001000        // debug disable interrupt
+#define IF_IADBG_EN_INTR        0x00002000        // debug enable interrupt
+#define IF_IADBG_LOUD           0x00004000        // debugging info
+#define IF_IADBG_VERY_LOUD      0x00008000        // excessive debugging info
+#define IF_IADBG_CBR            0x00100000  	  //
+#define IF_IADBG_UBR            0x00200000  	  //
+#define IF_IADBG_ABR            0x00400000        //
+#define IF_IADBG_DESC           0x01000000        //
+#define IF_IADBG_SUNI_STAT      0x02000000        // suni statistics
+#define IF_IADBG_RESET          0x04000000        
+
+#define IF_IADBG(f) if (IADebugFlag & (f))
+
+#ifdef  CONFIG_ATM_IA_DEBUG   /* Debug build */
+
+#define IF_LOUD(A) IF_IADBG(IF_IADBG_LOUD) { A }
+#define IF_ERR(A) IF_IADBG(IF_IADBG_ERR) { A }
+#define IF_VERY_LOUD(A) IF_IADBG( IF_IADBG_VERY_LOUD ) { A }
+
+#define IF_INIT_ADAPTER(A) IF_IADBG( IF_IADBG_INIT_ADAPTER ) { A }
+#define IF_INIT(A) IF_IADBG( IF_IADBG_INIT_ADAPTER ) { A }
+#define IF_SUNI_STAT(A) IF_IADBG( IF_IADBG_SUNI_STAT ) { A }
+#define IF_QUERY_INFO(A) IF_IADBG( IF_IADBG_QUERY_INFO ) { A }
+#define IF_COPY_OVER(A) IF_IADBG( IF_IADBG_COPY_OVER ) { A }
+
+#define IF_INTR(A) IF_IADBG( IF_IADBG_INTR ) { A }
+#define IF_DIS_INTR(A) IF_IADBG( IF_IADBG_DIS_INTR ) { A }
+#define IF_EN_INTR(A) IF_IADBG( IF_IADBG_EN_INTR ) { A }
+
+#define IF_TX(A) IF_IADBG( IF_IADBG_TX ) { A }
+#define IF_RX(A) IF_IADBG( IF_IADBG_RX ) { A }
+#define IF_TXPKT(A) IF_IADBG( IF_IADBG_TXPKT ) { A }
+#define IF_RXPKT(A) IF_IADBG( IF_IADBG_RXPKT ) { A }
+
+#define IF_SHUTDOWN(A) IF_IADBG(IF_IADBG_SHUTDOWN) { A }
+#define IF_CBR(A) IF_IADBG( IF_IADBG_CBR ) { A }
+#define IF_UBR(A) IF_IADBG( IF_IADBG_UBR ) { A }
+#define IF_ABR(A) IF_IADBG( IF_IADBG_ABR ) { A }
+#define IF_EVENT(A) IF_IADBG( IF_IADBG_EVENT) { A }
+
+#else /* free build */
+#define IF_LOUD(A)
+#define IF_VERY_LOUD(A)
+#define IF_INIT_ADAPTER(A)
+#define IF_INIT(A)
+#define IF_SUNI_STAT(A)
+#define IF_PVC_CHKPKT(A)
+#define IF_QUERY_INFO(A)
+#define IF_COPY_OVER(A)
+#define IF_HANG(A)
+#define IF_INTR(A)
+#define IF_DIS_INTR(A)
+#define IF_EN_INTR(A)
+#define IF_TX(A)
+#define IF_RX(A)
+#define IF_TXDEBUG(A)
+#define IF_VC(A)
+#define IF_ERR(A) 
+#define IF_CBR(A)
+#define IF_UBR(A)
+#define IF_ABR(A)
+#define IF_SHUTDOWN(A)
+#define DbgPrint(A)
+#define IF_EVENT(A)
+#define IF_TXPKT(A) 
+#define IF_RXPKT(A)
+#endif /* CONFIG_ATM_IA_DEBUG */ 
+
+#define isprint(a) ((a >=' ')&&(a <= '~'))  
+#define ATM_DESC(skb) (skb->protocol)
+#define IA_SKB_STATE(skb) (skb->protocol)
+#define IA_DLED   1
+#define IA_TX_DONE 2
+
+/* iadbg defines */
+#define IA_CMD   0x7749
+typedef struct {
+	int cmd;
+        int sub_cmd;
+        int len;
+        u32 maddr;
+        int status;
+        void __user *buf;
+} IA_CMDBUF, *PIA_CMDBUF;
+
+/* cmds */
+#define MEMDUMP     		0x01
+
+/* sub_cmds */
+#define MEMDUMP_SEGREG          0x2
+#define MEMDUMP_DEV  		0x1
+#define MEMDUMP_REASSREG        0x3
+#define MEMDUMP_FFL             0x4
+#define READ_REG                0x5
+#define WAKE_DBG_WAIT           0x6
+
+/************************ IADBG DEFINE END ***************************/
+
+#define Boolean(x)    	((x) ? 1 : 0)
+#define NR_VCI 1024		/* number of VCIs */  
+#define NR_VCI_LD 10		/* log2(NR_VCI) */  
+#define NR_VCI_4K 4096 		/* number of VCIs */  
+#define NR_VCI_4K_LD 12		/* log2(NR_VCI) */  
+#define MEM_VALID 0xfffffff0	/* mask base address with this */  
+  
+#ifndef PCI_VENDOR_ID_IPHASE  
+#define PCI_VENDOR_ID_IPHASE 0x107e  
+#endif  
+#ifndef PCI_DEVICE_ID_IPHASE_5575  
+#define PCI_DEVICE_ID_IPHASE_5575 0x0008  
+#endif  
+#define DEV_LABEL 	"ia"  
+#define PCR	207692  
+#define ICR	100000  
+#define MCR	0  
+#define TBE	1000  
+#define FRTT	1  
+#define RIF	2		  
+#define RDF	4  
+#define NRMCODE 5	/* 0 - 7 */  
+#define TRMCODE	3	/* 0 - 7 */  
+#define CDFCODE	6  
+#define ATDFCODE 2	/* 0 - 15 */  
+  
+/*---------------------- Packet/Cell Memory ------------------------*/  
+#define TX_PACKET_RAM 	0x00000 /* start of Trasnmit Packet memory - 0 */  
+#define DFL_TX_BUF_SZ	10240	/* 10 K buffers */  
+#define DFL_TX_BUFFERS     50 	/* number of packet buffers for Tx   
+					- descriptor 0 unused */  
+#define REASS_RAM_SIZE 0x10000  /* for 64K 1K VC board */  
+#define RX_PACKET_RAM 	0x80000 /* start of Receive Packet memory - 512K */  
+#define DFL_RX_BUF_SZ	10240	/* 10k buffers */  
+#define DFL_RX_BUFFERS      50	/* number of packet buffers for Rx   
+					- descriptor 0 unused */  
+  
+struct cpcs_trailer 
+{  
+	u_short control;  
+	u_short length;  
+	u_int	crc32;  
+};  
+
+struct cpcs_trailer_desc
+{
+	struct cpcs_trailer *cpcs;
+	dma_addr_t dma_addr;
+};
+
+struct ia_vcc 
+{ 
+	int rxing;	 
+	int txing;		 
+        int NumCbrEntry;
+        u32 pcr;
+        u32 saved_tx_quota;
+        int flow_inc;
+        struct sk_buff_head txing_skb; 
+        int  ltimeout;                  
+        u8  vc_desc_cnt;                
+                
+};  
+  
+struct abr_vc_table 
+{  
+	u_char status;  
+	u_char rdf;  
+	u_short air;  
+	u_int res[3];  
+	u_int req_rm_cell_data1;  
+	u_int req_rm_cell_data2;  
+	u_int add_rm_cell_data1;  
+	u_int add_rm_cell_data2;  
+};  
+    
+/* 32 byte entries */  
+struct main_vc 
+{  
+	u_short 	type;  
+#define ABR	0x8000  
+#define UBR 	0xc000  
+#define CBR	0x0000  
+	/* ABR fields */  
+	u_short 	nrm;	 
+ 	u_short 	trm;	   
+	u_short 	rm_timestamp_hi;  
+	u_short 	rm_timestamp_lo:8,  
+			crm:8;		  
+	u_short 	remainder; 	/* ABR and UBR fields - last 10 bits*/  
+	u_short 	next_vc_sched;  
+	u_short 	present_desc;	/* all classes */  
+	u_short 	last_cell_slot;	/* ABR and UBR */  
+	u_short 	pcr;  
+	u_short 	fraction;  
+	u_short 	icr;  
+	u_short 	atdf;  
+	u_short 	mcr;  
+	u_short 	acr;		 
+	u_short 	unack:8,  
+			status:8;	/* all classes */  
+#define UIOLI 0x80  
+#define CRC_APPEND 0x40			/* for status field - CRC-32 append */  
+#define ABR_STATE 0x02  
+  
+};  
+  
+  
+/* 8 byte entries */  
+struct ext_vc 
+{  
+	u_short 	atm_hdr1;  
+	u_short 	atm_hdr2;  
+	u_short 	last_desc;  
+      	u_short 	out_of_rate_link;   /* reserved for UBR and CBR */  
+};  
+  
+  
+#define DLE_ENTRIES 256  
+#define DMA_INT_ENABLE 0x0002	/* use for both Tx and Rx */  
+#define TX_DLE_PSI 0x0001  
+#define DLE_TOTAL_SIZE (sizeof(struct dle)*DLE_ENTRIES)
+  
+/* Descriptor List Entries (DLE) */  
+struct dle 
+{  
+	u32 	sys_pkt_addr;  
+	u32 	local_pkt_addr;  
+	u32 	bytes;  
+	u16 	prq_wr_ptr_data;  
+	u16 	mode;  
+};  
+  
+struct dle_q 
+{  
+	struct dle 	*start;  
+	struct dle 	*end;  
+	struct dle 	*read;  
+	struct dle 	*write;  
+};  
+  
+struct free_desc_q 
+{  
+	int 	desc;	/* Descriptor number */  
+	struct free_desc_q *next;  
+};  
+  
+struct tx_buf_desc {  
+	unsigned short desc_mode;  
+	unsigned short vc_index;  
+	unsigned short res1;		/* reserved field */  
+	unsigned short bytes;  
+	unsigned short buf_start_hi;  
+	unsigned short buf_start_lo;  
+	unsigned short res2[10];	/* reserved field */  
+};  
+	  
+  
+struct rx_buf_desc { 
+	unsigned short desc_mode;
+	unsigned short vc_index;
+	unsigned short vpi; 
+	unsigned short bytes; 
+	unsigned short buf_start_hi;  
+	unsigned short buf_start_lo;  
+	unsigned short dma_start_hi;  
+	unsigned short dma_start_lo;  
+	unsigned short crc_upper;  
+	unsigned short crc_lower;  
+	unsigned short res:8, timeout:8;  
+	unsigned short res2[5];	/* reserved field */  
+};  
+  
+/*--------SAR stuff ---------------------*/  
+  
+#define EPROM_SIZE 0x40000	/* says 64K in the docs ??? */  
+#define MAC1_LEN	4	   					  
+#define MAC2_LEN	2  
+   
+/*------------ PCI Memory Space Map, 128K SAR memory ----------------*/  
+#define IPHASE5575_PCI_CONFIG_REG_BASE	0x0000  
+#define IPHASE5575_BUS_CONTROL_REG_BASE 0x1000	/* offsets 0x00 - 0x3c */  
+#define IPHASE5575_FRAG_CONTROL_REG_BASE 0x2000  
+#define IPHASE5575_REASS_CONTROL_REG_BASE 0x3000  
+#define IPHASE5575_DMA_CONTROL_REG_BASE	0x4000  
+#define IPHASE5575_FRONT_END_REG_BASE IPHASE5575_DMA_CONTROL_REG_BASE  
+#define IPHASE5575_FRAG_CONTROL_RAM_BASE 0x10000  
+#define IPHASE5575_REASS_CONTROL_RAM_BASE 0x20000  
+  
+/*------------ Bus interface control registers -----------------*/  
+#define IPHASE5575_BUS_CONTROL_REG	0x00  
+#define IPHASE5575_BUS_STATUS_REG	0x01	/* actual offset 0x04 */  
+#define IPHASE5575_MAC1			0x02  
+#define IPHASE5575_REV			0x03  
+#define IPHASE5575_MAC2			0x03	/*actual offset 0x0e-reg 0x0c*/  
+#define IPHASE5575_EXT_RESET		0x04  
+#define IPHASE5575_INT_RESET		0x05	/* addr 1c ?? reg 0x06 */  
+#define IPHASE5575_PCI_ADDR_PAGE	0x07	/* reg 0x08, 0x09 ?? */  
+#define IPHASE5575_EEPROM_ACCESS	0x0a	/* actual offset 0x28 */  
+#define IPHASE5575_CELL_FIFO_QUEUE_SZ	0x0b  
+#define IPHASE5575_CELL_FIFO_MARK_STATE	0x0c  
+#define IPHASE5575_CELL_FIFO_READ_PTR	0x0d  
+#define IPHASE5575_CELL_FIFO_WRITE_PTR	0x0e  
+#define IPHASE5575_CELL_FIFO_CELLS_AVL	0x0f	/* actual offset 0x3c */  
+  
+/* Bus Interface Control Register bits */  
+#define CTRL_FE_RST	0x80000000  
+#define CTRL_LED	0x40000000  
+#define CTRL_25MBPHY	0x10000000  
+#define CTRL_ENCMBMEM	0x08000000  
+#define CTRL_ENOFFSEG	0x01000000  
+#define CTRL_ERRMASK	0x00400000  
+#define CTRL_DLETMASK	0x00100000  
+#define CTRL_DLERMASK	0x00080000  
+#define CTRL_FEMASK	0x00040000  
+#define CTRL_SEGMASK	0x00020000  
+#define CTRL_REASSMASK	0x00010000  
+#define CTRL_CSPREEMPT	0x00002000  
+#define CTRL_B128	0x00000200  
+#define CTRL_B64	0x00000100  
+#define CTRL_B48	0x00000080  
+#define CTRL_B32	0x00000040  
+#define CTRL_B16	0x00000020  
+#define CTRL_B8		0x00000010  
+  
+/* Bus Interface Status Register bits */  
+#define STAT_CMEMSIZ	0xc0000000  
+#define STAT_ADPARCK	0x20000000  
+#define STAT_RESVD	0x1fffff80  
+#define STAT_ERRINT	0x00000040  
+#define STAT_MARKINT	0x00000020  
+#define STAT_DLETINT	0x00000010  
+#define STAT_DLERINT	0x00000008  
+#define STAT_FEINT	0x00000004  
+#define STAT_SEGINT	0x00000002  
+#define STAT_REASSINT	0x00000001  
+  
+  
+/*--------------- Segmentation control registers -----------------*/  
+/* The segmentation registers are 16 bits access and the addresses  
+	are defined as such so the addresses are the actual "offsets" */  
+#define IDLEHEADHI	0x00  
+#define IDLEHEADLO	0x01  
+#define MAXRATE		0x02  
+/* Values for MAXRATE register for 155Mbps and 25.6 Mbps operation */  
+#define RATE155		0x64b1 // 16 bits float format 
+#define MAX_ATM_155     352768 // Cells/second p.118
+#define RATE25		0x5f9d  
+  
+#define STPARMS		0x03  
+#define STPARMS_1K	0x008c  
+#define STPARMS_2K	0x0049  
+#define STPARMS_4K	0x0026  
+#define COMP_EN		0x4000  
+#define CBR_EN		0x2000  
+#define ABR_EN		0x0800  
+#define UBR_EN		0x0400  
+  
+#define ABRUBR_ARB	0x04  
+#define RM_TYPE		0x05  
+/*Value for RM_TYPE register for ATM Forum Traffic Mangement4.0 support*/  
+#define RM_TYPE_4_0	0x0100  
+  
+#define SEG_COMMAND_REG		0x17  
+/* Values for the command register */  
+#define RESET_SEG 0x0055  
+#define RESET_SEG_STATE	0x00aa  
+#define RESET_TX_CELL_CTR 0x00cc  
+  
+#define CBR_PTR_BASE	0x20  
+#define ABR_SBPTR_BASE	0x22  
+#define UBR_SBPTR_BASE  0x23  
+#define ABRWQ_BASE	0x26  
+#define UBRWQ_BASE	0x27  
+#define VCT_BASE	0x28  
+#define VCTE_BASE	0x29  
+#define CBR_TAB_BEG	0x2c  
+#define CBR_TAB_END	0x2d  
+#define PRQ_ST_ADR	0x30  
+#define PRQ_ED_ADR	0x31  
+#define PRQ_RD_PTR	0x32  
+#define PRQ_WR_PTR	0x33  
+#define TCQ_ST_ADR	0x34  
+#define TCQ_ED_ADR 	0x35  
+#define TCQ_RD_PTR	0x36  
+#define TCQ_WR_PTR	0x37  
+#define SEG_QUEUE_BASE	0x40  
+#define SEG_DESC_BASE	0x41  
+#define MODE_REG_0	0x45  
+#define T_ONLINE	0x0002		/* (i)chipSAR is online */  
+  
+#define MODE_REG_1	0x46  
+#define MODE_REG_1_VAL	0x0400		/*for propoer device operation*/  
+  
+#define SEG_INTR_STATUS_REG 0x47  
+#define SEG_MASK_REG	0x48  
+#define TRANSMIT_DONE 0x0200
+#define TCQ_NOT_EMPTY 0x1000	/* this can be used for both the interrupt   
+				status registers as well as the mask register */  
+  
+#define CELL_CTR_HIGH_AUTO 0x49  
+#define CELL_CTR_HIGH_NOAUTO 0xc9  
+#define CELL_CTR_LO_AUTO 0x4a  
+#define CELL_CTR_LO_NOAUTO 0xca  
+  
+/* Diagnostic registers */  
+#define NEXTDESC 	0x59  
+#define NEXTVC		0x5a  
+#define PSLOTCNT	0x5d  
+#define NEWDN		0x6a  
+#define NEWVC		0x6b  
+#define SBPTR		0x6c  
+#define ABRWQ_WRPTR	0x6f  
+#define ABRWQ_RDPTR	0x70  
+#define UBRWQ_WRPTR	0x71  
+#define UBRWQ_RDPTR	0x72  
+#define CBR_VC		0x73  
+#define ABR_SBVC	0x75  
+#define UBR_SBVC	0x76  
+#define ABRNEXTLINK	0x78  
+#define UBRNEXTLINK	0x79  
+  
+  
+/*----------------- Reassembly control registers ---------------------*/  
+/* The reassembly registers are 16 bits access and the addresses  
+	are defined as such so the addresses are the actual "offsets" */  
+#define MODE_REG	0x00  
+#define R_ONLINE	0x0002		/* (i)chip is online */  
+#define IGN_RAW_FL     	0x0004
+  
+#define PROTOCOL_ID	0x01  
+#define REASS_MASK_REG	0x02  
+#define REASS_INTR_STATUS_REG	0x03  
+/* Interrupt Status register bits */  
+#define RX_PKT_CTR_OF	0x8000  
+#define RX_ERR_CTR_OF	0x4000  
+#define RX_CELL_CTR_OF	0x1000  
+#define RX_FREEQ_EMPT	0x0200  
+#define RX_EXCPQ_FL	0x0080  
+#define	RX_RAWQ_FL	0x0010  
+#define RX_EXCP_RCVD	0x0008  
+#define RX_PKT_RCVD	0x0004  
+#define RX_RAW_RCVD	0x0001  
+  
+#define DRP_PKT_CNTR	0x04  
+#define ERR_CNTR	0x05  
+#define RAW_BASE_ADR	0x08  
+#define CELL_CTR0	0x0c  
+#define CELL_CTR1	0x0d  
+#define REASS_COMMAND_REG	0x0f  
+/* Values for command register */  
+#define RESET_REASS	0x0055  
+#define RESET_REASS_STATE 0x00aa  
+#define RESET_DRP_PKT_CNTR 0x00f1  
+#define RESET_ERR_CNTR	0x00f2  
+#define RESET_CELL_CNTR 0x00f8  
+#define RESET_REASS_ALL_REGS 0x00ff  
+  
+#define REASS_DESC_BASE	0x10  
+#define VC_LKUP_BASE	0x11  
+#define REASS_TABLE_BASE 0x12  
+#define REASS_QUEUE_BASE 0x13  
+#define PKT_TM_CNT	0x16  
+#define TMOUT_RANGE	0x17  
+#define INTRVL_CNTR	0x18  
+#define TMOUT_INDX	0x19  
+#define VP_LKUP_BASE	0x1c  
+#define VP_FILTER	0x1d  
+#define ABR_LKUP_BASE	0x1e  
+#define FREEQ_ST_ADR	0x24  
+#define FREEQ_ED_ADR	0x25  
+#define FREEQ_RD_PTR	0x26  
+#define FREEQ_WR_PTR	0x27  
+#define PCQ_ST_ADR	0x28  
+#define PCQ_ED_ADR	0x29  
+#define PCQ_RD_PTR	0x2a  
+#define PCQ_WR_PTR	0x2b  
+#define EXCP_Q_ST_ADR	0x2c  
+#define EXCP_Q_ED_ADR	0x2d  
+#define EXCP_Q_RD_PTR	0x2e  
+#define EXCP_Q_WR_PTR	0x2f  
+#define CC_FIFO_ST_ADR	0x34  
+#define CC_FIFO_ED_ADR	0x35  
+#define CC_FIFO_RD_PTR	0x36  
+#define CC_FIFO_WR_PTR	0x37  
+#define STATE_REG	0x38  
+#define BUF_SIZE	0x42  
+#define XTRA_RM_OFFSET	0x44  
+#define DRP_PKT_CNTR_NC	0x84  
+#define ERR_CNTR_NC	0x85  
+#define CELL_CNTR0_NC	0x8c  
+#define CELL_CNTR1_NC	0x8d  
+  
+/* State Register bits */  
+#define EXCPQ_EMPTY	0x0040  
+#define PCQ_EMPTY	0x0010  
+#define FREEQ_EMPTY	0x0004  
+  
+  
+/*----------------- Front End registers/ DMA control --------------*/  
+/* There is a lot of documentation error regarding these offsets ???   
+	eg:- 2 offsets given 800, a00 for rx counter  
+	similarly many others  
+   Remember again that the offsets are to be 4*register number, so  
+	correct the #defines here   
+*/  
+#define IPHASE5575_TX_COUNTER		0x200	/* offset - 0x800 */  
+#define IPHASE5575_RX_COUNTER		0x280	/* offset - 0xa00 */  
+#define IPHASE5575_TX_LIST_ADDR		0x300	/* offset - 0xc00 */  
+#define IPHASE5575_RX_LIST_ADDR		0x380	/* offset - 0xe00 */  
+  
+/*--------------------------- RAM ---------------------------*/  
+/* These memory maps are actually offsets from the segmentation and reassembly  RAM base addresses */  
+  
+/* Segmentation Control Memory map */  
+#define TX_DESC_BASE	0x0000	/* Buffer Decriptor Table */  
+#define TX_COMP_Q	0x1000	/* Transmit Complete Queue */  
+#define PKT_RDY_Q	0x1400	/* Packet Ready Queue */  
+#define CBR_SCHED_TABLE	0x1800	/* CBR Table */  
+#define UBR_SCHED_TABLE	0x3000	/* UBR Table */  
+#define UBR_WAIT_Q	0x4000	/* UBR Wait Queue */  
+#define ABR_SCHED_TABLE	0x5000	/* ABR Table */  
+#define ABR_WAIT_Q	0x5800	/* ABR Wait Queue */  
+#define EXT_VC_TABLE	0x6000	/* Extended VC Table */  
+#define MAIN_VC_TABLE	0x8000	/* Main VC Table */  
+#define SCHEDSZ		1024	/* ABR and UBR Scheduling Table size */  
+#define TX_DESC_TABLE_SZ 128	/* Number of entries in the Transmit   
+					Buffer Descriptor Table */  
+  
+/* These are used as table offsets in Descriptor Table address generation */  
+#define DESC_MODE	0x0  
+#define VC_INDEX	0x1  
+#define BYTE_CNT	0x3  
+#define PKT_START_HI	0x4  
+#define PKT_START_LO	0x5  
+  
+/* Descriptor Mode Word Bits */  
+#define EOM_EN	0x0800  
+#define AAL5	0x0100  
+#define APP_CRC32 0x0400  
+#define CMPL_INT  0x1000
+  
+#define TABLE_ADDRESS(db, dn, to) \
+	(((unsigned long)(db & 0x04)) << 16) | (dn << 5) | (to << 1)  
+  
+/* Reassembly Control Memory Map */  
+#define RX_DESC_BASE	0x0000	/* Buffer Descriptor Table */  
+#define VP_TABLE	0x5c00	/* VP Table */  
+#define EXCEPTION_Q	0x5e00	/* Exception Queue */  
+#define FREE_BUF_DESC_Q	0x6000	/* Free Buffer Descriptor Queue */  
+#define PKT_COMP_Q	0x6800	/* Packet Complete Queue */  
+#define REASS_TABLE	0x7000	/* Reassembly Table */  
+#define RX_VC_TABLE	0x7800	/* VC Table */  
+#define ABR_VC_TABLE	0x8000	/* ABR VC Table */  
+#define RX_DESC_TABLE_SZ 736	/* Number of entries in the Receive   
+					Buffer Descriptor Table */  
+#define VP_TABLE_SZ	256	 /* Number of entries in VPTable */   
+#define RX_VC_TABLE_SZ 	1024	/* Number of entries in VC Table */   
+#define REASS_TABLE_SZ 	1024	/* Number of entries in Reassembly Table */  
+ /* Buffer Descriptor Table */  
+#define RX_ACT	0x8000  
+#define RX_VPVC	0x4000  
+#define RX_CNG	0x0040  
+#define RX_CER	0x0008  
+#define RX_PTE	0x0004  
+#define RX_OFL	0x0002  
+#define NUM_RX_EXCP   32
+
+/* Reassembly Table */  
+#define NO_AAL5_PKT	0x0000  
+#define AAL5_PKT_REASSEMBLED 0x4000  
+#define AAL5_PKT_TERMINATED 0x8000  
+#define RAW_PKT		0xc000  
+#define REASS_ABR	0x2000  
+  
+/*-------------------- Base Registers --------------------*/  
+#define REG_BASE IPHASE5575_BUS_CONTROL_REG_BASE  
+#define RAM_BASE IPHASE5575_FRAG_CONTROL_RAM_BASE  
+#define PHY_BASE IPHASE5575_FRONT_END_REG_BASE  
+#define SEG_BASE IPHASE5575_FRAG_CONTROL_REG_BASE  
+#define REASS_BASE IPHASE5575_REASS_CONTROL_REG_BASE  
+
+typedef volatile u_int  freg_t;
+typedef u_int   rreg_t;
+
+typedef struct _ffredn_t {
+        freg_t  idlehead_high;  /* Idle cell header (high)              */
+        freg_t  idlehead_low;   /* Idle cell header (low)               */
+        freg_t  maxrate;        /* Maximum rate                         */
+        freg_t  stparms;        /* Traffic Management Parameters        */
+        freg_t  abrubr_abr;     /* ABRUBR Priority Byte 1, TCR Byte 0   */
+        freg_t  rm_type;        /*                                      */
+        u_int   filler5[0x17 - 0x06];
+        freg_t  cmd_reg;        /* Command register                     */
+        u_int   filler18[0x20 - 0x18];
+        freg_t  cbr_base;       /* CBR Pointer Base                     */
+        freg_t  vbr_base;       /* VBR Pointer Base                     */
+        freg_t  abr_base;       /* ABR Pointer Base                     */
+        freg_t  ubr_base;       /* UBR Pointer Base                     */
+        u_int   filler24;
+        freg_t  vbrwq_base;     /* VBR Wait Queue Base                  */
+        freg_t  abrwq_base;     /* ABR Wait Queue Base                  */
+        freg_t  ubrwq_base;     /* UBR Wait Queue Base                  */
+        freg_t  vct_base;       /* Main VC Table Base                   */
+        freg_t  vcte_base;      /* Extended Main VC Table Base          */
+        u_int   filler2a[0x2C - 0x2A];
+        freg_t  cbr_tab_beg;    /* CBR Table Begin                      */
+        freg_t  cbr_tab_end;    /* CBR Table End                        */
+        freg_t  cbr_pointer;    /* CBR Pointer                          */
+        u_int   filler2f[0x30 - 0x2F];
+        freg_t  prq_st_adr;     /* Packet Ready Queue Start Address     */
+        freg_t  prq_ed_adr;     /* Packet Ready Queue End Address       */
+        freg_t  prq_rd_ptr;     /* Packet Ready Queue read pointer      */
+        freg_t  prq_wr_ptr;     /* Packet Ready Queue write pointer     */
+        freg_t  tcq_st_adr;     /* Transmit Complete Queue Start Address*/
+        freg_t  tcq_ed_adr;     /* Transmit Complete Queue End Address  */
+        freg_t  tcq_rd_ptr;     /* Transmit Complete Queue read pointer */
+        freg_t  tcq_wr_ptr;     /* Transmit Complete Queue write pointer*/
+        u_int   filler38[0x40 - 0x38];
+        freg_t  queue_base;     /* Base address for PRQ and TCQ         */
+        freg_t  desc_base;      /* Base address of descriptor table     */
+        u_int   filler42[0x45 - 0x42];
+        freg_t  mode_reg_0;     /* Mode register 0                      */
+        freg_t  mode_reg_1;     /* Mode register 1                      */
+        freg_t  intr_status_reg;/* Interrupt Status register            */
+        freg_t  mask_reg;       /* Mask Register                        */
+        freg_t  cell_ctr_high1; /* Total cell transfer count (high)     */
+        freg_t  cell_ctr_lo1;   /* Total cell transfer count (low)      */
+        freg_t  state_reg;      /* Status register                      */
+        u_int   filler4c[0x58 - 0x4c];
+        freg_t  curr_desc_num;  /* Contains the current descriptor num  */
+        freg_t  next_desc;      /* Next descriptor                      */
+        freg_t  next_vc;        /* Next VC                              */
+        u_int   filler5b[0x5d - 0x5b];
+        freg_t  present_slot_cnt;/* Present slot count                  */
+        u_int   filler5e[0x6a - 0x5e];
+        freg_t  new_desc_num;   /* New descriptor number                */
+        freg_t  new_vc;         /* New VC                               */
+        freg_t  sched_tbl_ptr;  /* Schedule table pointer               */
+        freg_t  vbrwq_wptr;     /* VBR wait queue write pointer         */
+        freg_t  vbrwq_rptr;     /* VBR wait queue read pointer          */
+        freg_t  abrwq_wptr;     /* ABR wait queue write pointer         */
+        freg_t  abrwq_rptr;     /* ABR wait queue read pointer          */
+        freg_t  ubrwq_wptr;     /* UBR wait queue write pointer         */
+        freg_t  ubrwq_rptr;     /* UBR wait queue read pointer          */
+        freg_t  cbr_vc;         /* CBR VC                               */
+        freg_t  vbr_sb_vc;      /* VBR SB VC                            */
+        freg_t  abr_sb_vc;      /* ABR SB VC                            */
+        freg_t  ubr_sb_vc;      /* UBR SB VC                            */
+        freg_t  vbr_next_link;  /* VBR next link                        */
+        freg_t  abr_next_link;  /* ABR next link                        */
+        freg_t  ubr_next_link;  /* UBR next link                        */
+        u_int   filler7a[0x7c-0x7a];
+        freg_t  out_rate_head;  /* Out of rate head                     */
+        u_int   filler7d[0xca-0x7d]; /* pad out to full address space   */
+        freg_t  cell_ctr_high1_nc;/* Total cell transfer count (high)   */
+        freg_t  cell_ctr_lo1_nc;/* Total cell transfer count (low)      */
+        u_int   fillercc[0x100-0xcc]; /* pad out to full address space   */
+} ffredn_t;
+
+typedef struct _rfredn_t {
+        rreg_t  mode_reg_0;     /* Mode register 0                      */
+        rreg_t  protocol_id;    /* Protocol ID                          */
+        rreg_t  mask_reg;       /* Mask Register                        */
+        rreg_t  intr_status_reg;/* Interrupt status register            */
+        rreg_t  drp_pkt_cntr;   /* Dropped packet cntr (clear on read)  */
+        rreg_t  err_cntr;       /* Error Counter (cleared on read)      */
+        u_int   filler6[0x08 - 0x06];
+        rreg_t  raw_base_adr;   /* Base addr for raw cell Q             */
+        u_int   filler2[0x0c - 0x09];
+        rreg_t  cell_ctr0;      /* Cell Counter 0 (cleared when read)   */
+        rreg_t  cell_ctr1;      /* Cell Counter 1 (cleared when read)   */
+        u_int   filler3[0x0f - 0x0e];
+        rreg_t  cmd_reg;        /* Command register                     */
+        rreg_t  desc_base;      /* Base address for description table   */
+        rreg_t  vc_lkup_base;   /* Base address for VC lookup table     */
+        rreg_t  reass_base;     /* Base address for reassembler table   */
+        rreg_t  queue_base;     /* Base address for Communication queue */
+        u_int   filler14[0x16 - 0x14];
+        rreg_t  pkt_tm_cnt;     /* Packet Timeout and count register    */
+        rreg_t  tmout_range;    /* Range of reassembley IDs for timeout */
+        rreg_t  intrvl_cntr;    /* Packet aging interval counter        */
+        rreg_t  tmout_indx;     /* index of pkt being tested for aging  */
+        u_int   filler1a[0x1c - 0x1a];
+        rreg_t  vp_lkup_base;   /* Base address for VP lookup table     */
+        rreg_t  vp_filter;      /* VP filter register                   */
+        rreg_t  abr_lkup_base;  /* Base address of ABR VC Table         */
+        u_int   filler1f[0x24 - 0x1f];
+        rreg_t  fdq_st_adr;     /* Free desc queue start address        */
+        rreg_t  fdq_ed_adr;     /* Free desc queue end address          */
+        rreg_t  fdq_rd_ptr;     /* Free desc queue read pointer         */
+        rreg_t  fdq_wr_ptr;     /* Free desc queue write pointer        */
+        rreg_t  pcq_st_adr;     /* Packet Complete queue start address  */
+        rreg_t  pcq_ed_adr;     /* Packet Complete queue end address    */
+        rreg_t  pcq_rd_ptr;     /* Packet Complete queue read pointer   */
+        rreg_t  pcq_wr_ptr;     /* Packet Complete queue write pointer  */
+        rreg_t  excp_st_adr;    /* Exception queue start address        */
+        rreg_t  excp_ed_adr;    /* Exception queue end address          */
+        rreg_t  excp_rd_ptr;    /* Exception queue read pointer         */
+        rreg_t  excp_wr_ptr;    /* Exception queue write pointer        */
+        u_int   filler30[0x34 - 0x30];
+        rreg_t  raw_st_adr;     /* Raw Cell start address               */
+        rreg_t  raw_ed_adr;     /* Raw Cell end address                 */
+        rreg_t  raw_rd_ptr;     /* Raw Cell read pointer                */
+        rreg_t  raw_wr_ptr;     /* Raw Cell write pointer               */
+        rreg_t  state_reg;      /* State Register                       */
+        u_int   filler39[0x42 - 0x39];
+        rreg_t  buf_size;       /* Buffer size                          */
+        u_int   filler43;
+        rreg_t  xtra_rm_offset; /* Offset of the additional turnaround RM */
+        u_int   filler45[0x84 - 0x45];
+        rreg_t  drp_pkt_cntr_nc;/* Dropped Packet cntr, Not clear on rd */
+        rreg_t  err_cntr_nc;    /* Error Counter, Not clear on read     */
+        u_int   filler86[0x8c - 0x86];
+        rreg_t  cell_ctr0_nc;   /* Cell Counter 0,  Not clear on read   */
+        rreg_t  cell_ctr1_nc;   /* Cell Counter 1, Not clear on read    */
+        u_int   filler8e[0x100-0x8e]; /* pad out to full address space   */
+} rfredn_t;
+
+typedef struct {
+        /* Atlantic */
+        ffredn_t        ffredn;         /* F FRED                       */
+        rfredn_t        rfredn;         /* R FRED                       */
+} ia_regs_t;
+
+typedef struct {
+	u_short		f_vc_type;	/* VC type              */
+	u_short		f_nrm;		/* Nrm			*/
+	u_short		f_nrmexp;	/* Nrm Exp              */
+	u_short		reserved6;	/* 			*/
+	u_short		f_crm;		/* Crm			*/
+	u_short		reserved10;	/* Reserved		*/
+	u_short		reserved12;	/* Reserved		*/
+	u_short		reserved14;	/* Reserved		*/
+	u_short		last_cell_slot;	/* last_cell_slot_count	*/
+	u_short		f_pcr;		/* Peak Cell Rate	*/
+	u_short		fraction;	/* fraction		*/
+	u_short		f_icr;		/* Initial Cell Rate	*/
+	u_short		f_cdf;		/* */
+	u_short		f_mcr;		/* Minimum Cell Rate	*/
+	u_short		f_acr;		/* Allowed Cell Rate	*/
+	u_short		f_status;	/* */
+} f_vc_abr_entry;
+
+typedef struct {
+        u_short         r_status_rdf;   /* status + RDF         */
+        u_short         r_air;          /* AIR                  */
+        u_short         reserved4[14];  /* Reserved             */
+} r_vc_abr_entry;   
+
+#define MRM 3
+
+typedef struct srv_cls_param {
+        u32 class_type;         /* CBR/VBR/ABR/UBR; use the enum above */
+        u32 pcr;                /* Peak Cell Rate (24-bit) */ 
+        /* VBR parameters */
+        u32 scr;                /* sustainable cell rate */
+        u32 max_burst_size;     /* ?? cell rate or data rate */
+ 
+        /* ABR only UNI 4.0 Parameters */
+        u32 mcr;                /* Min Cell Rate (24-bit) */
+        u32 icr;                /* Initial Cell Rate (24-bit) */
+        u32 tbe;                /* Transient Buffer Exposure (24-bit) */
+        u32 frtt;               /* Fixed Round Trip Time (24-bit) */
+ 
+#if 0   /* Additional Parameters of TM 4.0 */
+bits  31          30           29          28       27-25 24-22 21-19  18-9
+-----------------------------------------------------------------------------
+| NRM present | TRM prsnt | CDF prsnt | ADTF prsnt | NRM | TRM | CDF | ADTF |
+-----------------------------------------------------------------------------
+#endif /* 0 */
+ 
+        u8 nrm;                 /* Max # of Cells for each forward RM
+                                        cell (3-bit) */
+        u8 trm;                 /* Time between forward RM cells (3-bit) */
+        u16 adtf;               /* ACR Decrease Time Factor (10-bit) */
+        u8 cdf;                 /* Cutoff Decrease Factor (3-bit) */
+        u8 rif;                 /* Rate Increment Factor (4-bit) */
+        u8 rdf;                 /* Rate Decrease Factor (4-bit) */
+        u8 reserved;            /* 8 bits to keep structure word aligned */
+} srv_cls_param_t;
+
+struct testTable_t {
+	u16 lastTime; 
+	u16 fract; 
+	u8 vc_status;
+}; 
+
+typedef struct {
+	u16 vci;
+	u16 error;
+} RX_ERROR_Q;
+
+typedef struct {
+	u8 active: 1; 
+	u8 abr: 1; 
+	u8 ubr: 1; 
+	u8 cnt: 5;
+#define VC_ACTIVE 	0x01
+#define VC_ABR		0x02
+#define VC_UBR		0x04
+} vcstatus_t;
+  
+struct ia_rfL_t {
+    	u32  fdq_st;     /* Free desc queue start address        */
+        u32  fdq_ed;     /* Free desc queue end address          */
+        u32  fdq_rd;     /* Free desc queue read pointer         */
+        u32  fdq_wr;     /* Free desc queue write pointer        */
+        u32  pcq_st;     /* Packet Complete queue start address  */
+        u32  pcq_ed;     /* Packet Complete queue end address    */
+        u32  pcq_rd;     /* Packet Complete queue read pointer   */
+        u32  pcq_wr;     /* Packet Complete queue write pointer  */ 
+};
+
+struct ia_ffL_t {
+	u32  prq_st;     /* Packet Ready Queue Start Address     */
+        u32  prq_ed;     /* Packet Ready Queue End Address       */
+        u32  prq_wr;     /* Packet Ready Queue write pointer     */
+        u32  tcq_st;     /* Transmit Complete Queue Start Address*/
+        u32  tcq_ed;     /* Transmit Complete Queue End Address  */
+        u32  tcq_rd;     /* Transmit Complete Queue read pointer */
+};
+
+struct desc_tbl_t {
+        u32 timestamp;
+        struct ia_vcc *iavcc;
+        struct sk_buff *txskb;
+}; 
+
+typedef struct ia_rtn_q {
+   struct desc_tbl_t data;
+   struct ia_rtn_q *next, *tail;
+} IARTN_Q;
+
+#define SUNI_LOSV   	0x04
+typedef struct {
+        u32   suni_master_reset;      /* SUNI Master Reset and Identity     */
+        u32   suni_master_config;     /* SUNI Master Configuration          */
+        u32   suni_master_intr_stat;  /* SUNI Master Interrupt Status       */
+        u32   suni_reserved1;         /* Reserved                           */
+        u32   suni_master_clk_monitor;/* SUNI Master Clock Monitor          */
+        u32   suni_master_control;    /* SUNI Master Clock Monitor          */
+        u32   suni_reserved2[10];     /* Reserved                           */
+
+        u32   suni_rsop_control;      /* RSOP Control/Interrupt Enable      */
+        u32   suni_rsop_status;       /* RSOP Status/Interrupt States       */
+        u32   suni_rsop_section_bip8l;/* RSOP Section BIP-8 LSB             */
+        u32   suni_rsop_section_bip8m;/* RSOP Section BIP-8 MSB             */
+
+        u32   suni_tsop_control;      /* TSOP Control                       */
+        u32   suni_tsop_diag;         /* TSOP Disgnostics                   */
+        u32   suni_tsop_reserved[2];  /* TSOP Reserved                      */
+
+        u32   suni_rlop_cs;           /* RLOP Control/Status                */
+        u32   suni_rlop_intr;         /* RLOP Interrupt Enable/Status       */
+        u32   suni_rlop_line_bip24l;  /* RLOP Line BIP-24 LSB               */
+        u32   suni_rlop_line_bip24;   /* RLOP Line BIP-24                   */
+        u32   suni_rlop_line_bip24m;  /* RLOP Line BIP-24 MSB               */
+        u32   suni_rlop_line_febel;   /* RLOP Line FEBE LSB                 */
+        u32   suni_rlop_line_febe;    /* RLOP Line FEBE                     */
+        u32   suni_rlop_line_febem;   /* RLOP Line FEBE MSB                 */
+
+        u32   suni_tlop_control;      /* TLOP Control                       */
+        u32   suni_tlop_disg;         /* TLOP Disgnostics                   */
+        u32   suni_tlop_reserved[14]; /* TLOP Reserved                      */
+
+        u32   suni_rpop_cs;           /* RPOP Status/Control                */
+        u32   suni_rpop_intr;         /* RPOP Interrupt/Status              */
+        u32   suni_rpop_reserved;     /* RPOP Reserved                      */
+        u32   suni_rpop_intr_ena;     /* RPOP Interrupt Enable              */
+        u32   suni_rpop_reserved1[3]; /* RPOP Reserved                      */
+        u32   suni_rpop_path_sig;     /* RPOP Path Signal Label             */
+        u32   suni_rpop_bip8l;        /* RPOP Path BIP-8 LSB                */
+        u32   suni_rpop_bip8m;        /* RPOP Path BIP-8 MSB                */
+        u32   suni_rpop_febel;        /* RPOP Path FEBE LSB                 */
+        u32   suni_rpop_febem;        /* RPOP Path FEBE MSB                 */
+        u32   suni_rpop_reserved2[4]; /* RPOP Reserved                      */
+
+        u32   suni_tpop_cntrl_daig;   /* TPOP Control/Disgnostics           */
+        u32   suni_tpop_pointer_ctrl; /* TPOP Pointer Control               */
+        u32   suni_tpop_sourcer_ctrl; /* TPOP Source Control                */
+        u32   suni_tpop_reserved1[2]; /* TPOP Reserved                      */
+        u32   suni_tpop_arb_prtl;     /* TPOP Arbitrary Pointer LSB         */
+        u32   suni_tpop_arb_prtm;     /* TPOP Arbitrary Pointer MSB         */
+        u32   suni_tpop_reserved2;    /* TPOP Reserved                      */
+        u32   suni_tpop_path_sig;     /* TPOP Path Signal Lable             */
+        u32   suni_tpop_path_status;  /* TPOP Path Status                   */
+        u32   suni_tpop_reserved3[6]; /* TPOP Reserved                      */              
+
+        u32   suni_racp_cs;           /* RACP Control/Status                */
+        u32   suni_racp_intr;         /* RACP Interrupt Enable/Status       */
+        u32   suni_racp_hdr_pattern;  /* RACP Match Header Pattern          */
+        u32   suni_racp_hdr_mask;     /* RACP Match Header Mask             */
+        u32   suni_racp_corr_hcs;     /* RACP Correctable HCS Error Count   */
+        u32   suni_racp_uncorr_hcs;   /* RACP Uncorrectable HCS Error Count */
+        u32   suni_racp_reserved[10]; /* RACP Reserved                      */
+
+        u32   suni_tacp_control;      /* TACP Control                       */
+        u32   suni_tacp_idle_hdr_pat; /* TACP Idle Cell Header Pattern      */
+        u32   suni_tacp_idle_pay_pay; /* TACP Idle Cell Payld Octet Pattern */
+        u32   suni_tacp_reserved[5];  /* TACP Reserved                      */
+
+        u32   suni_reserved3[24];     /* Reserved                           */
+
+        u32   suni_master_test;       /* SUNI Master Test                   */
+        u32   suni_reserved_test;     /* SUNI Reserved for Test             */
+} IA_SUNI;
+
+
+typedef struct _SUNI_STATS_
+{
+   u32 valid;                       // 1 = oc3 PHY card
+   u32 carrier_detect;              // GPIN input
+   // RSOP: receive section overhead processor
+   u16 rsop_oof_state;              // 1 = out of frame
+   u16 rsop_lof_state;              // 1 = loss of frame
+   u16 rsop_los_state;              // 1 = loss of signal
+   u32 rsop_los_count;              // loss of signal count
+   u32 rsop_bse_count;              // section BIP-8 error count
+   // RLOP: receive line overhead processor
+   u16 rlop_ferf_state;             // 1 = far end receive failure
+   u16 rlop_lais_state;             // 1 = line AIS
+   u32 rlop_lbe_count;              // BIP-24 count
+   u32 rlop_febe_count;             // FEBE count;
+   // RPOP: receive path overhead processor
+   u16 rpop_lop_state;              // 1 = LOP
+   u16 rpop_pais_state;             // 1 = path AIS
+   u16 rpop_pyel_state;             // 1 = path yellow alert
+   u32 rpop_bip_count;              // path BIP-8 error count
+   u32 rpop_febe_count;             // path FEBE error count
+   u16 rpop_psig;                   // path signal label value
+   // RACP: receive ATM cell processor
+   u16 racp_hp_state;               // hunt/presync state
+   u32 racp_fu_count;               // FIFO underrun count
+   u32 racp_fo_count;               // FIFO overrun count
+   u32 racp_chcs_count;             // correctable HCS error count
+   u32 racp_uchcs_count;            // uncorrectable HCS error count
+} IA_SUNI_STATS; 
+
+typedef struct iadev_t {  
+	/*-----base pointers into (i)chipSAR+ address space */   
+	u32 __iomem *phy;		/* base pointer into phy(SUNI) */  
+	u32 __iomem *dma;		/* base pointer into DMA control   
+						registers */  
+	u32 __iomem *reg;		/* base pointer to SAR registers  
+					   - Bus Interface Control Regs */  
+	u32 __iomem *seg_reg;		/* base pointer to segmentation engine  
+						internal registers */  
+	u32 __iomem *reass_reg;		/* base pointer to reassemble engine  
+						internal registers */  
+	u32 __iomem *ram;		/* base pointer to SAR RAM */  
+	void __iomem *seg_ram;  
+	void __iomem *reass_ram;  
+	struct dle_q tx_dle_q;  
+	struct free_desc_q *tx_free_desc_qhead;  
+	struct sk_buff_head tx_dma_q, tx_backlog;  
+        spinlock_t            tx_lock;
+        IARTN_Q               tx_return_q;
+        u32                   close_pending;
+        wait_queue_head_t    close_wait;
+        wait_queue_head_t    timeout_wait;
+	struct cpcs_trailer_desc *tx_buf;
+        u16 num_tx_desc, tx_buf_sz, rate_limit;
+        u32 tx_cell_cnt, tx_pkt_cnt;
+        void __iomem *MAIN_VC_TABLE_ADDR, *EXT_VC_TABLE_ADDR, *ABR_SCHED_TABLE_ADDR;
+	struct dle_q rx_dle_q;  
+	struct free_desc_q *rx_free_desc_qhead;  
+	struct sk_buff_head rx_dma_q;  
+        spinlock_t rx_lock, misc_lock;
+	struct atm_vcc **rx_open;	/* list of all open VCs */  
+        u16 num_rx_desc, rx_buf_sz, rxing;
+        u32 rx_pkt_ram, rx_tmp_cnt, rx_tmp_jif;
+        void __iomem *RX_DESC_BASE_ADDR;
+        u32 drop_rxpkt, drop_rxcell, rx_cell_cnt, rx_pkt_cnt;
+	struct atm_dev *next_board;	/* other iphase devices */  
+	struct pci_dev *pci;  
+	int mem;  
+	unsigned int real_base;	/* real and virtual base address */  
+	void __iomem *base;
+	unsigned int pci_map_size;	/*pci map size of board */  
+	unsigned char irq;  
+	unsigned char bus;  
+	unsigned char dev_fn;  
+        u_short  phy_type;
+        u_short  num_vc, memSize, memType;
+        struct ia_ffL_t ffL;
+        struct ia_rfL_t rfL;
+        /* Suni stat */
+        // IA_SUNI_STATS suni_stats;
+        unsigned char carrier_detect;
+        /* CBR related */
+        // transmit DMA & Receive
+        unsigned int tx_dma_cnt;     // number of elements on dma queue
+        unsigned int rx_dma_cnt;     // number of elements on rx dma queue
+        unsigned int NumEnabledCBR;  // number of CBR VCI's enabled.     CBR
+        // receive MARK  for Cell FIFO
+        unsigned int rx_mark_cnt;    // number of elements on mark queue
+        unsigned int CbrTotEntries;  // Total CBR Entries in Scheduling Table.
+        unsigned int CbrRemEntries;  // Remaining CBR Entries in Scheduling Table.
+        unsigned int CbrEntryPt;     // CBR Sched Table Entry Point.
+        unsigned int Granularity;    // CBR Granularity given Table Size.
+        /* ABR related */
+	unsigned int  sum_mcr, sum_cbr, LineRate;
+	unsigned int  n_abr;
+        struct desc_tbl_t *desc_tbl;
+        u_short host_tcq_wr;
+        struct testTable_t **testTable;
+	dma_addr_t tx_dle_dma;
+	dma_addr_t rx_dle_dma;
+} IADEV;
+  
+  
+#define INPH_IA_DEV(d) ((IADEV *) (d)->dev_data)  
+#define INPH_IA_VCC(v) ((struct ia_vcc *) (v)->dev_data)  
+
+/******************* IDT77105 25MB/s PHY DEFINE *****************************/
+typedef struct {
+	u_int	mb25_master_ctrl;	/* Master control		     */
+	u_int	mb25_intr_status;	/* Interrupt status		     */
+	u_int	mb25_diag_control;	/* Diagnostic control		     */
+	u_int	mb25_led_hec;		/* LED driver and HEC status/control */
+	u_int	mb25_low_byte_counter;	/* Low byte counter		     */
+	u_int	mb25_high_byte_counter;	/* High byte counter		     */
+} ia_mb25_t;
+
+/*
+ * Master Control
+ */
+#define	MB25_MC_UPLO	0x80		/* UPLO				     */
+#define	MB25_MC_DREC	0x40		/* Discard receive cell errors	     */
+#define	MB25_MC_ECEIO	0x20		/* Enable Cell Error Interrupts Only */
+#define	MB25_MC_TDPC	0x10		/* Transmit data parity check	     */
+#define	MB25_MC_DRIC	0x08		/* Discard receive idle cells	     */
+#define	MB25_MC_HALTTX	0x04		/* Halt Tx			     */
+#define	MB25_MC_UMS	0x02		/* UTOPIA mode select		     */
+#define	MB25_MC_ENABLED	0x01		/* Enable interrupt		     */
+
+/*
+ * Interrupt Status
+ */
+#define	MB25_IS_GSB	0x40		/* GOOD Symbol Bit		     */	
+#define	MB25_IS_HECECR	0x20		/* HEC error cell received	     */
+#define	MB25_IS_SCR	0x10		/* "Short Cell" Received	     */
+#define	MB25_IS_TPE	0x08		/* Trnamsit Parity Error	     */
+#define	MB25_IS_RSCC	0x04		/* Receive Signal Condition change   */
+#define	MB25_IS_RCSE	0x02		/* Received Cell Symbol Error	     */
+#define	MB25_IS_RFIFOO	0x01		/* Received FIFO Overrun	     */
+
+/*
+ * Diagnostic Control
+ */
+#define	MB25_DC_FTXCD	0x80		/* Force TxClav deassert	     */	
+#define	MB25_DC_RXCOS	0x40		/* RxClav operation select	     */
+#define	MB25_DC_ECEIO	0x20		/* Single/Multi-PHY config select    */
+#define	MB25_DC_RLFLUSH	0x10		/* Clear receive FIFO		     */
+#define	MB25_DC_IXPE	0x08		/* Insert xmit payload error	     */
+#define	MB25_DC_IXHECE	0x04		/* Insert Xmit HEC Error	     */
+#define	MB25_DC_LB_MASK	0x03		/* Loopback control mask	     */
+
+#define	MB25_DC_LL	0x03		/* Line Loopback		     */
+#define	MB25_DC_PL	0x02		/* PHY Loopback			     */
+#define	MB25_DC_NM	0x00		
+
+#define FE_MASK 	0x00F0
+#define FE_MULTI_MODE	0x0000
+#define FE_SINGLE_MODE  0x0010 
+#define FE_UTP_OPTION  	0x0020
+#define FE_25MBIT_PHY	0x0040
+#define FE_DS3_PHY      0x0080          /* DS3 */
+#define FE_E3_PHY       0x0090          /* E3 */
+		     
+/*********************** SUNI_PM7345 PHY DEFINE HERE *********************/
+typedef struct _suni_pm7345_t
+{
+    u_int suni_config;     /* SUNI Configuration */
+    u_int suni_intr_enbl;  /* SUNI Interrupt Enable */
+    u_int suni_intr_stat;  /* SUNI Interrupt Status */
+    u_int suni_control;    /* SUNI Control */
+    u_int suni_id_reset;   /* SUNI Reset and Identity */
+    u_int suni_data_link_ctrl;
+    u_int suni_rboc_conf_intr_enbl;
+    u_int suni_rboc_stat;
+    u_int suni_ds3_frm_cfg;
+    u_int suni_ds3_frm_intr_enbl;
+    u_int suni_ds3_frm_intr_stat;
+    u_int suni_ds3_frm_stat;
+    u_int suni_rfdl_cfg;
+    u_int suni_rfdl_enbl_stat;
+    u_int suni_rfdl_stat;
+    u_int suni_rfdl_data;
+    u_int suni_pmon_chng;
+    u_int suni_pmon_intr_enbl_stat;
+    u_int suni_reserved1[0x13-0x11];
+    u_int suni_pmon_lcv_evt_cnt_lsb;
+    u_int suni_pmon_lcv_evt_cnt_msb;
+    u_int suni_pmon_fbe_evt_cnt_lsb;
+    u_int suni_pmon_fbe_evt_cnt_msb;
+    u_int suni_pmon_sez_det_cnt_lsb;
+    u_int suni_pmon_sez_det_cnt_msb;
+    u_int suni_pmon_pe_evt_cnt_lsb;
+    u_int suni_pmon_pe_evt_cnt_msb;
+    u_int suni_pmon_ppe_evt_cnt_lsb;
+    u_int suni_pmon_ppe_evt_cnt_msb;
+    u_int suni_pmon_febe_evt_cnt_lsb;
+    u_int suni_pmon_febe_evt_cnt_msb;
+    u_int suni_ds3_tran_cfg;
+    u_int suni_ds3_tran_diag;
+    u_int suni_reserved2[0x23-0x21];
+    u_int suni_xfdl_cfg;
+    u_int suni_xfdl_intr_st;
+    u_int suni_xfdl_xmit_data;
+    u_int suni_xboc_code;
+    u_int suni_splr_cfg;
+    u_int suni_splr_intr_en;
+    u_int suni_splr_intr_st;
+    u_int suni_splr_status;
+    u_int suni_splt_cfg;
+    u_int suni_splt_cntl;
+    u_int suni_splt_diag_g1;
+    u_int suni_splt_f1;
+    u_int suni_cppm_loc_meters;
+    u_int suni_cppm_chng_of_cppm_perf_meter;
+    u_int suni_cppm_b1_err_cnt_lsb;
+    u_int suni_cppm_b1_err_cnt_msb;
+    u_int suni_cppm_framing_err_cnt_lsb;
+    u_int suni_cppm_framing_err_cnt_msb;
+    u_int suni_cppm_febe_cnt_lsb;
+    u_int suni_cppm_febe_cnt_msb;
+    u_int suni_cppm_hcs_err_cnt_lsb;
+    u_int suni_cppm_hcs_err_cnt_msb;
+    u_int suni_cppm_idle_un_cell_cnt_lsb;
+    u_int suni_cppm_idle_un_cell_cnt_msb;
+    u_int suni_cppm_rcv_cell_cnt_lsb;
+    u_int suni_cppm_rcv_cell_cnt_msb;
+    u_int suni_cppm_xmit_cell_cnt_lsb;
+    u_int suni_cppm_xmit_cell_cnt_msb;
+    u_int suni_rxcp_ctrl;
+    u_int suni_rxcp_fctrl;
+    u_int suni_rxcp_intr_en_sts;
+    u_int suni_rxcp_idle_pat_h1;
+    u_int suni_rxcp_idle_pat_h2;
+    u_int suni_rxcp_idle_pat_h3;
+    u_int suni_rxcp_idle_pat_h4;
+    u_int suni_rxcp_idle_mask_h1;
+    u_int suni_rxcp_idle_mask_h2;
+    u_int suni_rxcp_idle_mask_h3;
+    u_int suni_rxcp_idle_mask_h4;
+    u_int suni_rxcp_cell_pat_h1;
+    u_int suni_rxcp_cell_pat_h2;
+    u_int suni_rxcp_cell_pat_h3;
+    u_int suni_rxcp_cell_pat_h4;
+    u_int suni_rxcp_cell_mask_h1;
+    u_int suni_rxcp_cell_mask_h2;
+    u_int suni_rxcp_cell_mask_h3;
+    u_int suni_rxcp_cell_mask_h4;
+    u_int suni_rxcp_hcs_cs;
+    u_int suni_rxcp_lcd_cnt_threshold;
+    u_int suni_reserved3[0x57-0x54];
+    u_int suni_txcp_ctrl;
+    u_int suni_txcp_intr_en_sts;
+    u_int suni_txcp_idle_pat_h1;
+    u_int suni_txcp_idle_pat_h2;
+    u_int suni_txcp_idle_pat_h3;
+    u_int suni_txcp_idle_pat_h4;
+    u_int suni_txcp_idle_pat_h5;
+    u_int suni_txcp_idle_payload;
+    u_int suni_e3_frm_fram_options;
+    u_int suni_e3_frm_maint_options;
+    u_int suni_e3_frm_fram_intr_enbl;
+    u_int suni_e3_frm_fram_intr_ind_stat;
+    u_int suni_e3_frm_maint_intr_enbl;
+    u_int suni_e3_frm_maint_intr_ind;
+    u_int suni_e3_frm_maint_stat;
+    u_int suni_reserved4;
+    u_int suni_e3_tran_fram_options;
+    u_int suni_e3_tran_stat_diag_options;
+    u_int suni_e3_tran_bip_8_err_mask;
+    u_int suni_e3_tran_maint_adapt_options;
+    u_int suni_ttb_ctrl;
+    u_int suni_ttb_trail_trace_id_stat;
+    u_int suni_ttb_ind_addr;
+    u_int suni_ttb_ind_data;
+    u_int suni_ttb_exp_payload_type;
+    u_int suni_ttb_payload_type_ctrl_stat;
+    u_int suni_pad5[0x7f-0x71];
+    u_int suni_master_test;
+    u_int suni_pad6[0xff-0x80];
+}suni_pm7345_t;
+
+#define SUNI_PM7345_T suni_pm7345_t
+#define SUNI_PM7345     0x20            /* Suni chip type */
+#define SUNI_PM5346     0x30            /* Suni chip type */
+/*
+ * SUNI_PM7345 Configuration
+ */
+#define SUNI_PM7345_CLB         0x01    /* Cell loopback        */
+#define SUNI_PM7345_PLB         0x02    /* Payload loopback     */
+#define SUNI_PM7345_DLB         0x04    /* Diagnostic loopback  */
+#define SUNI_PM7345_LLB         0x80    /* Line loopback        */
+#define SUNI_PM7345_E3ENBL      0x40    /* E3 enable bit        */
+#define SUNI_PM7345_LOOPT       0x10    /* LOOPT enable bit     */
+#define SUNI_PM7345_FIFOBP      0x20    /* FIFO bypass          */
+#define SUNI_PM7345_FRMRBP      0x08    /* Framer bypass        */
+/*
+ * DS3 FRMR Interrupt Enable
+ */
+#define SUNI_DS3_COFAE  0x80            /* Enable change of frame align */
+#define SUNI_DS3_REDE   0x40            /* Enable DS3 RED state intr    */
+#define SUNI_DS3_CBITE  0x20            /* Enable Appl ID channel intr  */
+#define SUNI_DS3_FERFE  0x10            /* Enable Far End Receive Failure intr*/
+#define SUNI_DS3_IDLE   0x08            /* Enable Idle signal intr      */
+#define SUNI_DS3_AISE   0x04            /* Enable Alarm Indication signal intr*/
+#define SUNI_DS3_OOFE   0x02            /* Enable Out of frame intr     */
+#define SUNI_DS3_LOSE   0x01            /* Enable Loss of signal intr   */
+ 
+/*
+ * DS3 FRMR Status
+ */
+#define SUNI_DS3_ACE    0x80            /* Additional Configuration Reg */
+#define SUNI_DS3_REDV   0x40            /* DS3 RED state                */
+#define SUNI_DS3_CBITV  0x20            /* Application ID channel state */
+#define SUNI_DS3_FERFV  0x10            /* Far End Receive Failure state*/
+#define SUNI_DS3_IDLV   0x08            /* Idle signal state            */
+#define SUNI_DS3_AISV   0x04            /* Alarm Indication signal state*/
+#define SUNI_DS3_OOFV   0x02            /* Out of frame state           */
+#define SUNI_DS3_LOSV   0x01            /* Loss of signal state         */
+
+/*
+ * E3 FRMR Interrupt/Status
+ */
+#define SUNI_E3_CZDI    0x40            /* Consecutive Zeros indicator  */
+#define SUNI_E3_LOSI    0x20            /* Loss of signal intr status   */
+#define SUNI_E3_LCVI    0x10            /* Line code violation intr     */
+#define SUNI_E3_COFAI   0x08            /* Change of frame align intr   */
+#define SUNI_E3_OOFI    0x04            /* Out of frame intr status     */
+#define SUNI_E3_LOS     0x02            /* Loss of signal state         */
+#define SUNI_E3_OOF     0x01            /* Out of frame state           */
+
+/*
+ * E3 FRMR Maintenance Status
+ */
+#define SUNI_E3_AISD    0x80            /* Alarm Indication signal state*/
+#define SUNI_E3_FERF_RAI        0x40    /* FERF/RAI indicator           */
+#define SUNI_E3_FEBE    0x20            /* Far End Block Error indicator*/
+
+/*
+ * RXCP Control/Status
+ */
+#define SUNI_DS3_HCSPASS        0x80    /* Pass cell with HEC errors    */
+#define SUNI_DS3_HCSDQDB        0x40    /* Control octets in HCS calc   */
+#define SUNI_DS3_HCSADD         0x20    /* Add coset poly               */
+#define SUNI_DS3_HCK            0x10    /* Control FIFO data path integ chk*/
+#define SUNI_DS3_BLOCK          0x08    /* Enable cell filtering        */
+#define SUNI_DS3_DSCR           0x04    /* Disable payload descrambling */
+#define SUNI_DS3_OOCDV          0x02    /* Cell delineation state       */
+#define SUNI_DS3_FIFORST        0x01    /* Cell FIFO reset              */
+
+/*
+ * RXCP Interrupt Enable/Status
+ */
+#define SUNI_DS3_OOCDE  0x80            /* Intr enable, change in CDS   */
+#define SUNI_DS3_HCSE   0x40            /* Intr enable, corr HCS errors */
+#define SUNI_DS3_FIFOE  0x20            /* Intr enable, unco HCS errors */
+#define SUNI_DS3_OOCDI  0x10            /* SYNC state                   */
+#define SUNI_DS3_UHCSI  0x08            /* Uncorr. HCS errors detected  */
+#define SUNI_DS3_COCAI  0x04            /* Corr. HCS errors detected    */
+#define SUNI_DS3_FOVRI  0x02            /* FIFO overrun                 */
+#define SUNI_DS3_FUDRI  0x01            /* FIFO underrun                */
+
+///////////////////SUNI_PM7345 PHY DEFINE END /////////////////////////////
+
+/* ia_eeprom define*/
+#define MEM_SIZE_MASK   0x000F          /* mask of 4 bits defining memory size*/
+#define MEM_SIZE_128K   0x0000          /* board has 128k buffer */
+#define MEM_SIZE_512K   0x0001          /* board has 512K of buffer */
+#define MEM_SIZE_1M     0x0002          /* board has 1M of buffer */
+                                        /* 0x3 to 0xF are reserved for future */
+
+#define FE_MASK         0x00F0          /* mask of 4 bits defining FE type */
+#define FE_MULTI_MODE   0x0000          /* 155 MBit multimode fiber */
+#define FE_SINGLE_MODE  0x0010          /* 155 MBit single mode laser */
+#define FE_UTP_OPTION   0x0020          /* 155 MBit UTP front end */
+
+#define	NOVRAM_SIZE	64
+#define	CMD_LEN		10
+
+/***********
+ *
+ *	Switches and defines for header files.
+ *
+ *	The following defines are used to turn on and off
+ *	various options in the header files. Primarily useful
+ *	for debugging.
+ *
+ ***********/
+
+/*
+ * a list of the commands that can be sent to the NOVRAM
+ */
+
+#define	EXTEND	0x100
+#define	IAWRITE	0x140
+#define	IAREAD	0x180
+#define	ERASE	0x1c0
+
+#define	EWDS	0x00
+#define	WRAL	0x10
+#define	ERAL	0x20
+#define	EWEN	0x30
+
+/*
+ * these bits duplicate the hw_flip.h register settings
+ * note: how the data in / out bits are defined in the flipper specification 
+ */
+
+#define	NVCE	0x02
+#define	NVSK	0x01
+#define	NVDO	0x08	
+#define NVDI	0x04
+/***********************
+ *
+ * This define ands the value and the current config register and puts
+ * the result in the config register
+ *
+ ***********************/
+
+#define	CFG_AND(val) { \
+		u32 t; \
+		t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); \
+		t &= (val); \
+		writel(t, iadev->reg+IPHASE5575_EEPROM_ACCESS); \
+	}
+
+/***********************
+ *
+ * This define ors the value and the current config register and puts
+ * the result in the config register
+ *
+ ***********************/
+
+#define	CFG_OR(val) { \
+		u32 t; \
+		t =  readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); \
+		t |= (val); \
+		writel(t, iadev->reg+IPHASE5575_EEPROM_ACCESS); \
+	}
+
+/***********************
+ *
+ * Send a command to the NOVRAM, the command is in cmd.
+ *
+ * clear CE and SK. Then assert CE.
+ * Clock each of the command bits out in the correct order with SK
+ * exit with CE still asserted
+ *
+ ***********************/
+
+#define	NVRAM_CMD(cmd) { \
+		int	i; \
+		u_short c = cmd; \
+		CFG_AND(~(NVCE|NVSK)); \
+		CFG_OR(NVCE); \
+		for (i=0; i<CMD_LEN; i++) { \
+			NVRAM_CLKOUT((c & (1 << (CMD_LEN - 1))) ? 1 : 0); \
+			c <<= 1; \
+		} \
+	}
+
+/***********************
+ *
+ * clear the CE, this must be used after each command is complete
+ *
+ ***********************/
+
+#define	NVRAM_CLR_CE	{CFG_AND(~NVCE)}
+
+/***********************
+ *
+ * clock the data bit in bitval out to the NOVRAM.  The bitval must be
+ * a 1 or 0, or the clockout operation is undefined
+ *
+ ***********************/
+
+#define	NVRAM_CLKOUT(bitval) { \
+		CFG_AND(~NVDI); \
+		CFG_OR((bitval) ? NVDI : 0); \
+		CFG_OR(NVSK); \
+		CFG_AND( ~NVSK); \
+	}
+
+/***********************
+ *
+ * clock the data bit in and return a 1 or 0, depending on the value
+ * that was received from the NOVRAM
+ *
+ ***********************/
+
+#define	NVRAM_CLKIN(value) { \
+		u32 _t; \
+		CFG_OR(NVSK); \
+		CFG_AND(~NVSK); \
+		_t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); \
+		value = (_t & NVDO) ? 1 : 0; \
+	}
+
+
+#endif /* IPHASE_H */
diff --git a/drivers/atm/lanai.c b/drivers/atm/lanai.c
new file mode 100644
index 0000000..ffe3afa
--- /dev/null
+++ b/drivers/atm/lanai.c
@@ -0,0 +1,2770 @@
+/* lanai.c -- Copyright 1999-2003 by Mitchell Blank Jr <mitch@sfgoth.com>
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation; either version
+ *  2 of the License, or (at your option) any later version.
+ *
+ * This driver supports ATM cards based on the Efficient "Lanai"
+ * chipset such as the Speedstream 3010 and the ENI-25p.  The
+ * Speedstream 3060 is currently not supported since we don't
+ * have the code to drive the on-board Alcatel DSL chipset (yet).
+ *
+ * Thanks to Efficient for supporting this project with hardware,
+ * documentation, and by answering my questions.
+ *
+ * Things not working yet:
+ *
+ * o  We don't support the Speedstream 3060 yet - this card has
+ *    an on-board DSL modem chip by Alcatel and the driver will
+ *    need some extra code added to handle it
+ *
+ * o  Note that due to limitations of the Lanai only one VCC can be
+ *    in CBR at once
+ *
+ * o We don't currently parse the EEPROM at all.  The code is all
+ *   there as per the spec, but it doesn't actually work.  I think
+ *   there may be some issues with the docs.  Anyway, do NOT
+ *   enable it yet - bugs in that code may actually damage your
+ *   hardware!  Because of this you should hardware an ESI before
+ *   trying to use this in a LANE or MPOA environment.
+ *
+ * o  AAL0 is stubbed in but the actual rx/tx path isn't written yet:
+ *	vcc_tx_aal0() needs to send or queue a SKB
+ *	vcc_tx_unqueue_aal0() needs to attempt to send queued SKBs
+ *	vcc_rx_aal0() needs to handle AAL0 interrupts
+ *    This isn't too much work - I just wanted to get other things
+ *    done first.
+ *
+ * o  lanai_change_qos() isn't written yet
+ *
+ * o  There aren't any ioctl's yet -- I'd like to eventually support
+ *    setting loopback and LED modes that way.  (see lanai_ioctl)
+ *
+ * o  If the segmentation engine or DMA gets shut down we should restart
+ *    card as per section 17.0i.  (see lanai_reset)
+ *
+ * o setsockopt(SO_CIRANGE) isn't done (although despite what the
+ *   API says it isn't exactly commonly implemented)
+ */
+
+/* Version history:
+ *   v.1.00 -- 26-JUL-2003 -- PCI/DMA updates
+ *   v.0.02 -- 11-JAN-2000 -- Endian fixes
+ *   v.0.01 -- 30-NOV-1999 -- Initial release
+ */
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/atmdev.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+/* -------------------- TUNABLE PARAMATERS: */
+
+/*
+ * Maximum number of VCIs per card.  Setting it lower could theoretically
+ * save some memory, but since we allocate our vcc list with get_free_pages,
+ * it's not really likely for most architectures
+ */
+#define NUM_VCI			(1024)
+
+/*
+ * Enable extra debugging
+ */
+#define DEBUG
+/*
+ * Debug _all_ register operations with card, except the memory test.
+ * Also disables the timed poll to prevent extra chattiness.  This
+ * isn't for normal use
+ */
+#undef DEBUG_RW
+
+/*
+ * The programming guide specifies a full test of the on-board SRAM
+ * at initialization time.  Undefine to remove this
+ */
+#define FULL_MEMORY_TEST
+
+/*
+ * This is the number of (4 byte) service entries that we will
+ * try to allocate at startup.  Note that we will end up with
+ * one PAGE_SIZE's worth regardless of what this is set to
+ */
+#define SERVICE_ENTRIES		(1024)
+/* TODO: make above a module load-time option */
+
+/*
+ * We normally read the onboard EEPROM in order to discover our MAC
+ * address.  Undefine to _not_ do this
+ */
+/* #define READ_EEPROM */ /* ***DONT ENABLE YET*** */
+/* TODO: make above a module load-time option (also) */
+
+/*
+ * Depth of TX fifo (in 128 byte units; range 2-31)
+ * Smaller numbers are better for network latency
+ * Larger numbers are better for PCI latency
+ * I'm really sure where the best tradeoff is, but the BSD driver uses
+ * 7 and it seems to work ok.
+ */
+#define TX_FIFO_DEPTH		(7)
+/* TODO: make above a module load-time option */
+
+/*
+ * How often (in jiffies) we will try to unstick stuck connections -
+ * shouldn't need to happen much
+ */
+#define LANAI_POLL_PERIOD	(10*HZ)
+/* TODO: make above a module load-time option */
+
+/*
+ * When allocating an AAL5 receiving buffer, try to make it at least
+ * large enough to hold this many max_sdu sized PDUs
+ */
+#define AAL5_RX_MULTIPLIER	(3)
+/* TODO: make above a module load-time option */
+
+/*
+ * Same for transmitting buffer
+ */
+#define AAL5_TX_MULTIPLIER	(3)
+/* TODO: make above a module load-time option */
+
+/*
+ * When allocating an AAL0 transmiting buffer, how many cells should fit.
+ * Remember we'll end up with a PAGE_SIZE of them anyway, so this isn't
+ * really critical
+ */
+#define AAL0_TX_MULTIPLIER	(40)
+/* TODO: make above a module load-time option */
+
+/*
+ * How large should we make the AAL0 receiving buffer.  Remember that this
+ * is shared between all AAL0 VC's
+ */
+#define AAL0_RX_BUFFER_SIZE	(PAGE_SIZE)
+/* TODO: make above a module load-time option */
+
+/*
+ * Should we use Lanai's "powerdown" feature when no vcc's are bound?
+ */
+/* #define USE_POWERDOWN */
+/* TODO: make above a module load-time option (also) */
+
+/* -------------------- DEBUGGING AIDS: */
+
+#define DEV_LABEL "lanai"
+
+#ifdef DEBUG
+
+#define DPRINTK(format, args...) \
+	printk(KERN_DEBUG DEV_LABEL ": " format, ##args)
+#define APRINTK(truth, format, args...) \
+	do { \
+		if (unlikely(!(truth))) \
+			printk(KERN_ERR DEV_LABEL ": " format, ##args); \
+	} while (0)
+
+#else /* !DEBUG */
+
+#define DPRINTK(format, args...)
+#define APRINTK(truth, format, args...)
+
+#endif /* DEBUG */
+
+#ifdef DEBUG_RW
+#define RWDEBUG(format, args...) \
+	printk(KERN_DEBUG DEV_LABEL ": " format, ##args)
+#else /* !DEBUG_RW */
+#define RWDEBUG(format, args...)
+#endif
+
+/* -------------------- DATA DEFINITIONS: */
+
+#define LANAI_MAPPING_SIZE	(0x40000)
+#define LANAI_EEPROM_SIZE	(128)
+
+typedef int vci_t;
+typedef void __iomem *bus_addr_t;
+
+/* DMA buffer in host memory for TX, RX, or service list. */
+struct lanai_buffer {
+	u32 *start;	/* From get_free_pages */
+	u32 *end;	/* One past last byte */
+	u32 *ptr;	/* Pointer to current host location */
+	dma_addr_t dmaaddr;
+};
+
+struct lanai_vcc_stats {
+	unsigned rx_nomem;
+	union {
+		struct {
+			unsigned rx_badlen;
+			unsigned service_trash;
+			unsigned service_stream;
+			unsigned service_rxcrc;
+		} aal5;
+		struct {
+		} aal0;
+	} x;
+};
+
+struct lanai_dev;			/* Forward declaration */
+
+/*
+ * This is the card-specific per-vcc data.  Note that unlike some other
+ * drivers there is NOT a 1-to-1 correspondance between these and
+ * atm_vcc's - each one of these represents an actual 2-way vcc, but
+ * an atm_vcc can be 1-way and share with a 1-way vcc in the other
+ * direction.  To make it weirder, there can even be 0-way vccs
+ * bound to us, waiting to do a change_qos
+ */
+struct lanai_vcc {
+	bus_addr_t vbase;		/* Base of VCC's registers */
+	struct lanai_vcc_stats stats;
+	int nref;			/* # of atm_vcc's who reference us */
+	vci_t vci;
+	struct {
+		struct lanai_buffer buf;
+		struct atm_vcc *atmvcc;	/* atm_vcc who is receiver */
+	} rx;
+	struct {
+		struct lanai_buffer buf;
+		struct atm_vcc *atmvcc;	/* atm_vcc who is transmitter */
+		int endptr;		/* last endptr from service entry */
+		struct sk_buff_head backlog;
+		void (*unqueue)(struct lanai_dev *, struct lanai_vcc *, int);
+	} tx;
+};
+
+enum lanai_type {
+	lanai2	= PCI_VENDOR_ID_EF_ATM_LANAI2,
+	lanaihb	= PCI_VENDOR_ID_EF_ATM_LANAIHB
+};
+
+struct lanai_dev_stats {
+	unsigned ovfl_trash;	/* # of cells dropped - buffer overflow */
+	unsigned vci_trash;	/* # of cells dropped - closed vci */
+	unsigned hec_err;	/* # of cells dropped - bad HEC */
+	unsigned atm_ovfl;	/* # of cells dropped - rx fifo overflow */
+	unsigned pcierr_parity_detect;
+	unsigned pcierr_serr_set;
+	unsigned pcierr_master_abort;
+	unsigned pcierr_m_target_abort;
+	unsigned pcierr_s_target_abort;
+	unsigned pcierr_master_parity;
+	unsigned service_notx;
+	unsigned service_norx;
+	unsigned service_rxnotaal5;
+	unsigned dma_reenable;
+	unsigned card_reset;
+};
+
+struct lanai_dev {
+	bus_addr_t base;
+	struct lanai_dev_stats stats;
+	struct lanai_buffer service;
+	struct lanai_vcc **vccs;
+#ifdef USE_POWERDOWN
+	int nbound;			/* number of bound vccs */
+#endif
+	enum lanai_type type;
+	vci_t num_vci;			/* Currently just NUM_VCI */
+	u8 eeprom[LANAI_EEPROM_SIZE];
+	u32 serialno, magicno;
+	struct pci_dev *pci;
+	DECLARE_BITMAP(backlog_vccs, NUM_VCI);   /* VCCs with tx backlog */
+	DECLARE_BITMAP(transmit_ready, NUM_VCI); /* VCCs with transmit space */
+	struct timer_list timer;
+	int naal0;
+	struct lanai_buffer aal0buf;	/* AAL0 RX buffers */
+	u32 conf1, conf2;		/* CONFIG[12] registers */
+	u32 status;			/* STATUS register */
+	spinlock_t endtxlock;
+	spinlock_t servicelock;
+	struct atm_vcc *cbrvcc;
+	int number;
+	int board_rev;
+	u8 pci_revision;
+/* TODO - look at race conditions with maintence of conf1/conf2 */
+/* TODO - transmit locking: should we use _irq not _irqsave? */
+/* TODO - organize above in some rational fashion (see <asm/cache.h>) */
+};
+
+/*
+ * Each device has two bitmaps for each VCC (baclog_vccs and transmit_ready)
+ * This function iterates one of these, calling a given function for each
+ * vci with their bit set
+ */
+static void vci_bitfield_iterate(struct lanai_dev *lanai,
+	/*const*/ unsigned long *lp,
+	void (*func)(struct lanai_dev *,vci_t vci))
+{
+	vci_t vci = find_first_bit(lp, NUM_VCI);
+	while (vci < NUM_VCI) {
+		func(lanai, vci);
+		vci = find_next_bit(lp, NUM_VCI, vci + 1);
+	}
+}
+
+/* -------------------- BUFFER  UTILITIES: */
+
+/*
+ * Lanai needs DMA buffers aligned to 256 bytes of at least 1024 bytes -
+ * usually any page allocation will do.  Just to be safe in case
+ * PAGE_SIZE is insanely tiny, though...
+ */
+#define LANAI_PAGE_SIZE   ((PAGE_SIZE >= 1024) ? PAGE_SIZE : 1024)
+
+/*
+ * Allocate a buffer in host RAM for service list, RX, or TX
+ * Returns buf->start==NULL if no memory
+ * Note that the size will be rounded up 2^n bytes, and
+ * if we can't allocate that we'll settle for something smaller
+ * until minbytes
+ */
+static void lanai_buf_allocate(struct lanai_buffer *buf,
+	size_t bytes, size_t minbytes, struct pci_dev *pci)
+{
+	int size;
+
+	if (bytes > (128 * 1024))	/* max lanai buffer size */
+		bytes = 128 * 1024;
+	for (size = LANAI_PAGE_SIZE; size < bytes; size *= 2)
+		;
+	if (minbytes < LANAI_PAGE_SIZE)
+		minbytes = LANAI_PAGE_SIZE;
+	do {
+		/*
+		 * Technically we could use non-consistent mappings for
+		 * everything, but the way the lanai uses DMA memory would
+		 * make that a terrific pain.  This is much simpler.
+		 */
+		buf->start = pci_alloc_consistent(pci, size, &buf->dmaaddr);
+		if (buf->start != NULL) {	/* Success */
+			/* Lanai requires 256-byte alignment of DMA bufs */
+			APRINTK((buf->dmaaddr & ~0xFFFFFF00) == 0,
+			    "bad dmaaddr: 0x%lx\n",
+			    (unsigned long) buf->dmaaddr);
+			buf->ptr = buf->start;
+			buf->end = (u32 *)
+			    (&((unsigned char *) buf->start)[size]);
+			memset(buf->start, 0, size);
+			break;
+		}
+		size /= 2;
+	} while (size >= minbytes);
+}
+
+/* size of buffer in bytes */
+static inline size_t lanai_buf_size(const struct lanai_buffer *buf)
+{
+	return ((unsigned long) buf->end) - ((unsigned long) buf->start);
+}
+
+static void lanai_buf_deallocate(struct lanai_buffer *buf,
+	struct pci_dev *pci)
+{
+	if (buf->start != NULL) {
+		pci_free_consistent(pci, lanai_buf_size(buf),
+		    buf->start, buf->dmaaddr);
+		buf->start = buf->end = buf->ptr = NULL;
+	}
+}
+
+/* size of buffer as "card order" (0=1k .. 7=128k) */
+static int lanai_buf_size_cardorder(const struct lanai_buffer *buf)
+{
+	int order = get_order(lanai_buf_size(buf)) + (PAGE_SHIFT - 10);
+
+	/* This can only happen if PAGE_SIZE is gigantic, but just in case */
+	if (order > 7)
+		order = 7;
+	return order;
+}
+
+/* -------------------- PORT I/O UTILITIES: */
+
+/* Registers (and their bit-fields) */
+enum lanai_register {
+	Reset_Reg		= 0x00,	/* Reset; read for chip type; bits: */
+#define   RESET_GET_BOARD_REV(x)    (((x)>> 0)&0x03)	/* Board revision */
+#define   RESET_GET_BOARD_ID(x)	    (((x)>> 2)&0x03)	/* Board ID */
+#define     BOARD_ID_LANAI256		(0)	/* 25.6M adapter card */
+	Endian_Reg		= 0x04,	/* Endian setting */
+	IntStatus_Reg		= 0x08,	/* Interrupt status */
+	IntStatusMasked_Reg	= 0x0C,	/* Interrupt status (masked) */
+	IntAck_Reg		= 0x10,	/* Interrupt acknowledge */
+	IntAckMasked_Reg	= 0x14,	/* Interrupt acknowledge (masked) */
+	IntStatusSet_Reg	= 0x18,	/* Get status + enable/disable */
+	IntStatusSetMasked_Reg	= 0x1C,	/* Get status + en/di (masked) */
+	IntControlEna_Reg	= 0x20,	/* Interrupt control enable */
+	IntControlDis_Reg	= 0x24,	/* Interrupt control disable */
+	Status_Reg		= 0x28,	/* Status */
+#define   STATUS_PROMDATA	 (0x00000001)	/* PROM_DATA pin */
+#define   STATUS_WAITING	 (0x00000002)	/* Interrupt being delayed */
+#define	  STATUS_SOOL		 (0x00000004)	/* SOOL alarm */
+#define   STATUS_LOCD		 (0x00000008)	/* LOCD alarm */
+#define	  STATUS_LED		 (0x00000010)	/* LED (HAPPI) output */
+#define   STATUS_GPIN		 (0x00000020)	/* GPIN pin */
+#define   STATUS_BUTTBUSY	 (0x00000040)	/* Butt register is pending */
+	Config1_Reg		= 0x2C,	/* Config word 1; bits: */
+#define   CONFIG1_PROMDATA	 (0x00000001)	/* PROM_DATA pin */
+#define   CONFIG1_PROMCLK	 (0x00000002)	/* PROM_CLK pin */
+#define   CONFIG1_SET_READMODE(x) ((x)*0x004)	/* PCI BM reads; values: */
+#define     READMODE_PLAIN	    (0)		/*   Plain memory read */
+#define     READMODE_LINE	    (2)		/*   Memory read line */
+#define     READMODE_MULTIPLE	    (3)		/*   Memory read multiple */
+#define   CONFIG1_DMA_ENABLE	 (0x00000010)	/* Turn on DMA */
+#define   CONFIG1_POWERDOWN	 (0x00000020)	/* Turn off clocks */
+#define   CONFIG1_SET_LOOPMODE(x) ((x)*0x080)	/* Clock&loop mode; values: */
+#define     LOOPMODE_NORMAL	    (0)		/*   Normal - no loop */
+#define     LOOPMODE_TIME	    (1)
+#define     LOOPMODE_DIAG	    (2)
+#define     LOOPMODE_LINE	    (3)
+#define   CONFIG1_MASK_LOOPMODE  (0x00000180)
+#define   CONFIG1_SET_LEDMODE(x) ((x)*0x0200)	/* Mode of LED; values: */
+#define     LEDMODE_NOT_SOOL	    (0)		/*   !SOOL */
+#define	    LEDMODE_OFF		    (1)		/*   0     */
+#define	    LEDMODE_ON		    (2)		/*   1     */
+#define	    LEDMODE_NOT_LOCD	    (3)		/*   !LOCD */
+#define	    LEDMORE_GPIN	    (4)		/*   GPIN  */
+#define     LEDMODE_NOT_GPIN	    (7)		/*   !GPIN */
+#define   CONFIG1_MASK_LEDMODE	 (0x00000E00)
+#define   CONFIG1_GPOUT1	 (0x00001000)	/* Toggle for reset */
+#define   CONFIG1_GPOUT2	 (0x00002000)	/* Loopback PHY */
+#define   CONFIG1_GPOUT3	 (0x00004000)	/* Loopback lanai */
+	Config2_Reg		= 0x30,	/* Config word 2; bits: */
+#define   CONFIG2_HOWMANY	 (0x00000001)	/* >512 VCIs? */
+#define   CONFIG2_PTI7_MODE	 (0x00000002)	/* Make PTI=7 RM, not OAM */
+#define   CONFIG2_VPI_CHK_DIS	 (0x00000004)	/* Ignore RX VPI value */
+#define   CONFIG2_HEC_DROP	 (0x00000008)	/* Drop cells w/ HEC errors */
+#define   CONFIG2_VCI0_NORMAL	 (0x00000010)	/* Treat VCI=0 normally */
+#define   CONFIG2_CBR_ENABLE	 (0x00000020)	/* Deal with CBR traffic */
+#define   CONFIG2_TRASH_ALL	 (0x00000040)	/* Trashing incoming cells */
+#define   CONFIG2_TX_DISABLE	 (0x00000080)	/* Trashing outgoing cells */
+#define   CONFIG2_SET_TRASH	 (0x00000100)	/* Turn trashing on */
+	Statistics_Reg		= 0x34,	/* Statistics; bits: */
+#define   STATS_GET_FIFO_OVFL(x)    (((x)>> 0)&0xFF)	/* FIFO overflowed */
+#define   STATS_GET_HEC_ERR(x)      (((x)>> 8)&0xFF)	/* HEC was bad */
+#define   STATS_GET_BAD_VCI(x)      (((x)>>16)&0xFF)	/* VCI not open */
+#define   STATS_GET_BUF_OVFL(x)     (((x)>>24)&0xFF)	/* VCC buffer full */
+	ServiceStuff_Reg	= 0x38,	/* Service stuff; bits: */
+#define   SSTUFF_SET_SIZE(x) ((x)*0x20000000)	/* size of service buffer */
+#define   SSTUFF_SET_ADDR(x)	    ((x)>>8)	/* set address of buffer */
+	ServWrite_Reg		= 0x3C,	/* ServWrite Pointer */
+	ServRead_Reg		= 0x40,	/* ServRead Pointer */
+	TxDepth_Reg		= 0x44,	/* FIFO Transmit Depth */
+	Butt_Reg		= 0x48,	/* Butt register */
+	CBR_ICG_Reg		= 0x50,
+	CBR_PTR_Reg		= 0x54,
+	PingCount_Reg		= 0x58,	/* Ping count */
+	DMA_Addr_Reg		= 0x5C	/* DMA address */
+};
+
+static inline bus_addr_t reg_addr(const struct lanai_dev *lanai,
+	enum lanai_register reg)
+{
+	return lanai->base + reg;
+}
+
+static inline u32 reg_read(const struct lanai_dev *lanai,
+	enum lanai_register reg)
+{
+	u32 t;
+	t = readl(reg_addr(lanai, reg));
+	RWDEBUG("R [0x%08X] 0x%02X = 0x%08X\n", (unsigned int) lanai->base,
+	    (int) reg, t);
+	return t;
+}
+
+static inline void reg_write(const struct lanai_dev *lanai, u32 val,
+	enum lanai_register reg)
+{
+	RWDEBUG("W [0x%08X] 0x%02X < 0x%08X\n", (unsigned int) lanai->base,
+	    (int) reg, val);
+	writel(val, reg_addr(lanai, reg));
+}
+
+static inline void conf1_write(const struct lanai_dev *lanai)
+{
+	reg_write(lanai, lanai->conf1, Config1_Reg);
+}
+
+static inline void conf2_write(const struct lanai_dev *lanai)
+{
+	reg_write(lanai, lanai->conf2, Config2_Reg);
+}
+
+/* Same as conf2_write(), but defers I/O if we're powered down */
+static inline void conf2_write_if_powerup(const struct lanai_dev *lanai)
+{
+#ifdef USE_POWERDOWN
+	if (unlikely((lanai->conf1 & CONFIG1_POWERDOWN) != 0))
+		return;
+#endif /* USE_POWERDOWN */
+	conf2_write(lanai);
+}
+
+static inline void reset_board(const struct lanai_dev *lanai)
+{
+	DPRINTK("about to reset board\n");
+	reg_write(lanai, 0, Reset_Reg);
+	/*
+	 * If we don't delay a little while here then we can end up
+	 * leaving the card in a VERY weird state and lock up the
+	 * PCI bus.  This isn't documented anywhere but I've convinced
+	 * myself after a lot of painful experimentation
+	 */
+	udelay(5);
+}
+
+/* -------------------- CARD SRAM UTILITIES: */
+
+/* The SRAM is mapped into normal PCI memory space - the only catch is
+ * that it is only 16-bits wide but must be accessed as 32-bit.  The
+ * 16 high bits will be zero.  We don't hide this, since they get
+ * programmed mostly like discrete registers anyway
+ */
+#define SRAM_START (0x20000)
+#define SRAM_BYTES (0x20000)	/* Again, half don't really exist */
+
+static inline bus_addr_t sram_addr(const struct lanai_dev *lanai, int offset)
+{
+	return lanai->base + SRAM_START + offset;
+}
+
+static inline u32 sram_read(const struct lanai_dev *lanai, int offset)
+{
+	return readl(sram_addr(lanai, offset));
+}
+
+static inline void sram_write(const struct lanai_dev *lanai,
+	u32 val, int offset)
+{
+	writel(val, sram_addr(lanai, offset));
+}
+
+static int __init sram_test_word(
+	const struct lanai_dev *lanai, int offset, u32 pattern)
+{
+	u32 readback;
+	sram_write(lanai, pattern, offset);
+	readback = sram_read(lanai, offset);
+	if (likely(readback == pattern))
+		return 0;
+	printk(KERN_ERR DEV_LABEL
+	    "(itf %d): SRAM word at %d bad: wrote 0x%X, read 0x%X\n",
+	    lanai->number, offset,
+	    (unsigned int) pattern, (unsigned int) readback);
+	return -EIO;
+}
+
+static int __devinit sram_test_pass(const struct lanai_dev *lanai, u32 pattern)
+{
+	int offset, result = 0;
+	for (offset = 0; offset < SRAM_BYTES && result == 0; offset += 4)
+		result = sram_test_word(lanai, offset, pattern);
+	return result;
+}
+
+static int __devinit sram_test_and_clear(const struct lanai_dev *lanai)
+{
+#ifdef FULL_MEMORY_TEST
+	int result;
+	DPRINTK("testing SRAM\n");
+	if ((result = sram_test_pass(lanai, 0x5555)) != 0)
+		return result;
+	if ((result = sram_test_pass(lanai, 0xAAAA)) != 0)
+		return result;
+#endif
+	DPRINTK("clearing SRAM\n");
+	return sram_test_pass(lanai, 0x0000);
+}
+
+/* -------------------- CARD-BASED VCC TABLE UTILITIES: */
+
+/* vcc table */
+enum lanai_vcc_offset {
+	vcc_rxaddr1		= 0x00,	/* Location1, plus bits: */
+#define   RXADDR1_SET_SIZE(x) ((x)*0x0000100)	/* size of RX buffer */
+#define   RXADDR1_SET_RMMODE(x) ((x)*0x00800)	/* RM cell action; values: */
+#define     RMMODE_TRASH	  (0)		/*   discard */
+#define     RMMODE_PRESERVE	  (1)		/*   input as AAL0 */
+#define     RMMODE_PIPE		  (2)		/*   pipe to coscheduler */
+#define     RMMODE_PIPEALL	  (3)		/*   pipe non-RM too */
+#define   RXADDR1_OAM_PRESERVE	 (0x00002000)	/* Input OAM cells as AAL0 */
+#define   RXADDR1_SET_MODE(x) ((x)*0x0004000)	/* Reassembly mode */
+#define     RXMODE_TRASH	  (0)		/*   discard */
+#define     RXMODE_AAL0		  (1)		/*   non-AAL5 mode */
+#define     RXMODE_AAL5		  (2)		/*   AAL5, intr. each PDU */
+#define     RXMODE_AAL5_STREAM	  (3)		/*   AAL5 w/o per-PDU intr */
+	vcc_rxaddr2		= 0x04,	/* Location2 */
+	vcc_rxcrc1		= 0x08,	/* RX CRC claculation space */
+	vcc_rxcrc2		= 0x0C,
+	vcc_rxwriteptr		= 0x10, /* RX writeptr, plus bits: */
+#define   RXWRITEPTR_LASTEFCI	 (0x00002000)	/* Last PDU had EFCI bit */
+#define   RXWRITEPTR_DROPPING	 (0x00004000)	/* Had error, dropping */
+#define   RXWRITEPTR_TRASHING	 (0x00008000)	/* Trashing */
+	vcc_rxbufstart		= 0x14,	/* RX bufstart, plus bits: */
+#define   RXBUFSTART_CLP	 (0x00004000)
+#define   RXBUFSTART_CI		 (0x00008000)
+	vcc_rxreadptr		= 0x18,	/* RX readptr */
+	vcc_txicg		= 0x1C, /* TX ICG */
+	vcc_txaddr1		= 0x20,	/* Location1, plus bits: */
+#define   TXADDR1_SET_SIZE(x) ((x)*0x0000100)	/* size of TX buffer */
+#define   TXADDR1_ABR		 (0x00008000)	/* use ABR (doesn't work) */
+	vcc_txaddr2		= 0x24,	/* Location2 */
+	vcc_txcrc1		= 0x28,	/* TX CRC claculation space */
+	vcc_txcrc2		= 0x2C,
+	vcc_txreadptr		= 0x30, /* TX Readptr, plus bits: */
+#define   TXREADPTR_GET_PTR(x) ((x)&0x01FFF)
+#define   TXREADPTR_MASK_DELTA	(0x0000E000)	/* ? */
+	vcc_txendptr		= 0x34, /* TX Endptr, plus bits: */
+#define   TXENDPTR_CLP		(0x00002000)
+#define   TXENDPTR_MASK_PDUMODE	(0x0000C000)	/* PDU mode; values: */
+#define     PDUMODE_AAL0	 (0*0x04000)
+#define     PDUMODE_AAL5	 (2*0x04000)
+#define     PDUMODE_AAL5STREAM	 (3*0x04000)
+	vcc_txwriteptr		= 0x38,	/* TX Writeptr */
+#define   TXWRITEPTR_GET_PTR(x) ((x)&0x1FFF)
+	vcc_txcbr_next		= 0x3C	/* # of next CBR VCI in ring */
+#define   TXCBR_NEXT_BOZO	(0x00008000)	/* "bozo bit" */
+};
+
+#define CARDVCC_SIZE	(0x40)
+
+static inline bus_addr_t cardvcc_addr(const struct lanai_dev *lanai,
+	vci_t vci)
+{
+	return sram_addr(lanai, vci * CARDVCC_SIZE);
+}
+
+static inline u32 cardvcc_read(const struct lanai_vcc *lvcc,
+	enum lanai_vcc_offset offset)
+{
+	u32 val;
+	APRINTK(lvcc->vbase != NULL, "cardvcc_read: unbound vcc!\n");
+	val= readl(lvcc->vbase + offset);
+	RWDEBUG("VR vci=%04d 0x%02X = 0x%08X\n",
+	    lvcc->vci, (int) offset, val);
+	return val;
+}
+
+static inline void cardvcc_write(const struct lanai_vcc *lvcc,
+	u32 val, enum lanai_vcc_offset offset)
+{
+	APRINTK(lvcc->vbase != NULL, "cardvcc_write: unbound vcc!\n");
+	APRINTK((val & ~0xFFFF) == 0,
+	    "cardvcc_write: bad val 0x%X (vci=%d, addr=0x%02X)\n",
+	    (unsigned int) val, lvcc->vci, (unsigned int) offset);
+	RWDEBUG("VW vci=%04d 0x%02X > 0x%08X\n",
+	    lvcc->vci, (unsigned int) offset, (unsigned int) val);
+	writel(val, lvcc->vbase + offset);
+}
+
+/* -------------------- COMPUTE SIZE OF AN AAL5 PDU: */
+
+/* How many bytes will an AAL5 PDU take to transmit - remember that:
+ *   o  we need to add 8 bytes for length, CPI, UU, and CRC
+ *   o  we need to round up to 48 bytes for cells
+ */
+static inline int aal5_size(int size)
+{
+	int cells = (size + 8 + 47) / 48;
+	return cells * 48;
+}
+
+/* How many bytes can we send if we have "space" space, assuming we have
+ * to send full cells
+ */
+static inline int aal5_spacefor(int space)
+{
+	int cells = space / 48;
+	return cells * 48;
+}
+
+/* -------------------- FREE AN ATM SKB: */
+
+static inline void lanai_free_skb(struct atm_vcc *atmvcc, struct sk_buff *skb)
+{
+	if (atmvcc->pop != NULL)
+		atmvcc->pop(atmvcc, skb);
+	else
+		dev_kfree_skb_any(skb);
+}
+
+/* -------------------- TURN VCCS ON AND OFF: */
+
+static void host_vcc_start_rx(const struct lanai_vcc *lvcc)
+{
+	u32 addr1;
+	if (lvcc->rx.atmvcc->qos.aal == ATM_AAL5) {
+		dma_addr_t dmaaddr = lvcc->rx.buf.dmaaddr;
+		cardvcc_write(lvcc, 0xFFFF, vcc_rxcrc1);
+		cardvcc_write(lvcc, 0xFFFF, vcc_rxcrc2);
+		cardvcc_write(lvcc, 0, vcc_rxwriteptr);
+		cardvcc_write(lvcc, 0, vcc_rxbufstart);
+		cardvcc_write(lvcc, 0, vcc_rxreadptr);
+		cardvcc_write(lvcc, (dmaaddr >> 16) & 0xFFFF, vcc_rxaddr2);
+		addr1 = ((dmaaddr >> 8) & 0xFF) |
+		    RXADDR1_SET_SIZE(lanai_buf_size_cardorder(&lvcc->rx.buf))|
+		    RXADDR1_SET_RMMODE(RMMODE_TRASH) |	/* ??? */
+		 /* RXADDR1_OAM_PRESERVE |	--- no OAM support yet */
+		    RXADDR1_SET_MODE(RXMODE_AAL5);
+	} else
+		addr1 = RXADDR1_SET_RMMODE(RMMODE_PRESERVE) | /* ??? */
+		    RXADDR1_OAM_PRESERVE |		      /* ??? */
+		    RXADDR1_SET_MODE(RXMODE_AAL0);
+	/* This one must be last! */
+	cardvcc_write(lvcc, addr1, vcc_rxaddr1);
+}
+
+static void host_vcc_start_tx(const struct lanai_vcc *lvcc)
+{
+	dma_addr_t dmaaddr = lvcc->tx.buf.dmaaddr;
+	cardvcc_write(lvcc, 0, vcc_txicg);
+	cardvcc_write(lvcc, 0xFFFF, vcc_txcrc1);
+	cardvcc_write(lvcc, 0xFFFF, vcc_txcrc2);
+	cardvcc_write(lvcc, 0, vcc_txreadptr);
+	cardvcc_write(lvcc, 0, vcc_txendptr);
+	cardvcc_write(lvcc, 0, vcc_txwriteptr);
+	cardvcc_write(lvcc,
+		(lvcc->tx.atmvcc->qos.txtp.traffic_class == ATM_CBR) ?
+		TXCBR_NEXT_BOZO | lvcc->vci : 0, vcc_txcbr_next);
+	cardvcc_write(lvcc, (dmaaddr >> 16) & 0xFFFF, vcc_txaddr2);
+	cardvcc_write(lvcc,
+	    ((dmaaddr >> 8) & 0xFF) |
+	    TXADDR1_SET_SIZE(lanai_buf_size_cardorder(&lvcc->tx.buf)),
+	    vcc_txaddr1);
+}
+
+/* Shutdown receiving on card */
+static void lanai_shutdown_rx_vci(const struct lanai_vcc *lvcc)
+{
+	if (lvcc->vbase == NULL)	/* We were never bound to a VCI */
+		return;
+	/* 15.1.1 - set to trashing, wait one cell time (15us) */
+	cardvcc_write(lvcc,
+	    RXADDR1_SET_RMMODE(RMMODE_TRASH) |
+	    RXADDR1_SET_MODE(RXMODE_TRASH), vcc_rxaddr1);
+	udelay(15);
+	/* 15.1.2 - clear rest of entries */
+	cardvcc_write(lvcc, 0, vcc_rxaddr2);
+	cardvcc_write(lvcc, 0, vcc_rxcrc1);
+	cardvcc_write(lvcc, 0, vcc_rxcrc2);
+	cardvcc_write(lvcc, 0, vcc_rxwriteptr);
+	cardvcc_write(lvcc, 0, vcc_rxbufstart);
+	cardvcc_write(lvcc, 0, vcc_rxreadptr);
+}
+
+/* Shutdown transmitting on card.
+ * Unfortunately the lanai needs us to wait until all the data
+ * drains out of the buffer before we can dealloc it, so this
+ * can take awhile -- up to 370ms for a full 128KB buffer
+ * assuming everone else is quiet.  In theory the time is
+ * boundless if there's a CBR VCC holding things up.
+ */
+static void lanai_shutdown_tx_vci(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc)
+{
+	struct sk_buff *skb;
+	unsigned long flags, timeout;
+	int read, write, lastread = -1;
+	APRINTK(!in_interrupt(),
+	    "lanai_shutdown_tx_vci called w/o process context!\n");
+	if (lvcc->vbase == NULL)	/* We were never bound to a VCI */
+		return;
+	/* 15.2.1 - wait for queue to drain */
+	while ((skb = skb_dequeue(&lvcc->tx.backlog)) != NULL)
+		lanai_free_skb(lvcc->tx.atmvcc, skb);
+	read_lock_irqsave(&vcc_sklist_lock, flags);
+	__clear_bit(lvcc->vci, lanai->backlog_vccs);
+	read_unlock_irqrestore(&vcc_sklist_lock, flags);
+	/*
+	 * We need to wait for the VCC to drain but don't wait forever.  We
+	 * give each 1K of buffer size 1/128th of a second to clear out.
+	 * TODO: maybe disable CBR if we're about to timeout?
+	 */
+	timeout = jiffies +
+	    (((lanai_buf_size(&lvcc->tx.buf) / 1024) * HZ) >> 7);
+	write = TXWRITEPTR_GET_PTR(cardvcc_read(lvcc, vcc_txwriteptr));
+	for (;;) {
+		read = TXREADPTR_GET_PTR(cardvcc_read(lvcc, vcc_txreadptr));
+		if (read == write &&	   /* Is TX buffer empty? */
+		    (lvcc->tx.atmvcc->qos.txtp.traffic_class != ATM_CBR ||
+		    (cardvcc_read(lvcc, vcc_txcbr_next) &
+		    TXCBR_NEXT_BOZO) == 0))
+			break;
+		if (read != lastread) {	   /* Has there been any progress? */
+			lastread = read;
+			timeout += HZ / 10;
+		}
+		if (unlikely(time_after(jiffies, timeout))) {
+			printk(KERN_ERR DEV_LABEL "(itf %d): Timed out on "
+			    "backlog closing vci %d\n",
+			    lvcc->tx.atmvcc->dev->number, lvcc->vci);
+			DPRINTK("read, write = %d, %d\n", read, write);
+			break;
+		}
+		msleep(40);
+	}
+	/* 15.2.2 - clear out all tx registers */
+	cardvcc_write(lvcc, 0, vcc_txreadptr);
+	cardvcc_write(lvcc, 0, vcc_txwriteptr);
+	cardvcc_write(lvcc, 0, vcc_txendptr);
+	cardvcc_write(lvcc, 0, vcc_txcrc1);
+	cardvcc_write(lvcc, 0, vcc_txcrc2);
+	cardvcc_write(lvcc, 0, vcc_txaddr2);
+	cardvcc_write(lvcc, 0, vcc_txaddr1);
+}
+
+/* -------------------- MANAGING AAL0 RX BUFFER: */
+
+static inline int aal0_buffer_allocate(struct lanai_dev *lanai)
+{
+	DPRINTK("aal0_buffer_allocate: allocating AAL0 RX buffer\n");
+	lanai_buf_allocate(&lanai->aal0buf, AAL0_RX_BUFFER_SIZE, 80,
+			   lanai->pci);
+	return (lanai->aal0buf.start == NULL) ? -ENOMEM : 0;
+}
+
+static inline void aal0_buffer_free(struct lanai_dev *lanai)
+{
+	DPRINTK("aal0_buffer_allocate: freeing AAL0 RX buffer\n");
+	lanai_buf_deallocate(&lanai->aal0buf, lanai->pci);
+}
+
+/* -------------------- EEPROM UTILITIES: */
+
+/* Offsets of data in the EEPROM */
+#define EEPROM_COPYRIGHT	(0)
+#define EEPROM_COPYRIGHT_LEN	(44)
+#define EEPROM_CHECKSUM		(62)
+#define EEPROM_CHECKSUM_REV	(63)
+#define EEPROM_MAC		(64)
+#define EEPROM_MAC_REV		(70)
+#define EEPROM_SERIAL		(112)
+#define EEPROM_SERIAL_REV	(116)
+#define EEPROM_MAGIC		(120)
+#define EEPROM_MAGIC_REV	(124)
+
+#define EEPROM_MAGIC_VALUE	(0x5AB478D2)
+
+#ifndef READ_EEPROM
+
+/* Stub functions to use if EEPROM reading is disabled */
+static int __devinit eeprom_read(struct lanai_dev *lanai)
+{
+	printk(KERN_INFO DEV_LABEL "(itf %d): *NOT* reading EEPROM\n",
+	    lanai->number);
+	memset(&lanai->eeprom[EEPROM_MAC], 0, 6);
+	return 0;
+}
+
+static int __devinit eeprom_validate(struct lanai_dev *lanai)
+{
+	lanai->serialno = 0;
+	lanai->magicno = EEPROM_MAGIC_VALUE;
+	return 0;
+}
+
+#else /* READ_EEPROM */
+
+static int __devinit eeprom_read(struct lanai_dev *lanai)
+{
+	int i, address;
+	u8 data;
+	u32 tmp;
+#define set_config1(x)   do { lanai->conf1 = x; conf1_write(lanai); \
+			    } while (0)
+#define clock_h()	 set_config1(lanai->conf1 | CONFIG1_PROMCLK)
+#define clock_l()	 set_config1(lanai->conf1 &~ CONFIG1_PROMCLK)
+#define data_h()	 set_config1(lanai->conf1 | CONFIG1_PROMDATA)
+#define data_l()	 set_config1(lanai->conf1 &~ CONFIG1_PROMDATA)
+#define pre_read()	 do { data_h(); clock_h(); udelay(5); } while (0)
+#define read_pin()	 (reg_read(lanai, Status_Reg) & STATUS_PROMDATA)
+#define send_stop()	 do { data_l(); udelay(5); clock_h(); udelay(5); \
+			      data_h(); udelay(5); } while (0)
+	/* start with both clock and data high */
+	data_h(); clock_h(); udelay(5);
+	for (address = 0; address < LANAI_EEPROM_SIZE; address++) {
+		data = (address << 1) | 1;	/* Command=read + address */
+		/* send start bit */
+		data_l(); udelay(5);
+		clock_l(); udelay(5);
+		for (i = 128; i != 0; i >>= 1) {   /* write command out */
+			tmp = (lanai->conf1 & ~CONFIG1_PROMDATA) |
+			    (data & i) ? CONFIG1_PROMDATA : 0;
+			if (lanai->conf1 != tmp) {
+				set_config1(tmp);
+				udelay(5);	/* Let new data settle */
+			}
+			clock_h(); udelay(5); clock_l(); udelay(5);
+		}
+		/* look for ack */
+		data_h(); clock_h(); udelay(5);
+		if (read_pin() != 0)
+			goto error;	/* No ack seen */
+		clock_l(); udelay(5);
+		/* read back result */
+		for (data = 0, i = 7; i >= 0; i--) {
+			data_h(); clock_h(); udelay(5);
+			data = (data << 1) | !!read_pin();
+			clock_l(); udelay(5);
+		}
+		/* look again for ack */
+		data_h(); clock_h(); udelay(5);
+		if (read_pin() == 0)
+			goto error;	/* Spurious ack */
+		clock_l(); udelay(5);
+		send_stop();
+		lanai->eeprom[address] = data;
+		DPRINTK("EEPROM 0x%04X %02X\n",
+		    (unsigned int) address, (unsigned int) data);
+	}
+	return 0;
+    error:
+	clock_l(); udelay(5);		/* finish read */
+	send_stop();
+	printk(KERN_ERR DEV_LABEL "(itf %d): error reading EEPROM byte %d\n",
+	    lanai->number, address);
+	return -EIO;
+#undef set_config1
+#undef clock_h
+#undef clock_l
+#undef data_h
+#undef data_l
+#undef pre_read
+#undef read_pin
+#undef send_stop
+}
+
+/* read a big-endian 4-byte value out of eeprom */
+static inline u32 eeprom_be4(const struct lanai_dev *lanai, int address)
+{
+	return be32_to_cpup((u32 *) (&lanai->eeprom[address]));
+}
+
+/* Checksum/validate EEPROM contents */
+static int __devinit eeprom_validate(struct lanai_dev *lanai)
+{
+	int i, s;
+	u32 v;
+	const u8 *e = lanai->eeprom;
+#ifdef DEBUG
+	/* First, see if we can get an ASCIIZ string out of the copyright */
+	for (i = EEPROM_COPYRIGHT;
+	    i < (EEPROM_COPYRIGHT + EEPROM_COPYRIGHT_LEN); i++)
+		if (e[i] < 0x20 || e[i] > 0x7E)
+			break;
+	if ( i != EEPROM_COPYRIGHT &&
+	    i != EEPROM_COPYRIGHT + EEPROM_COPYRIGHT_LEN && e[i] == '\0')
+		DPRINTK("eeprom: copyright = \"%s\"\n",
+		    (char *) &e[EEPROM_COPYRIGHT]);
+	else
+		DPRINTK("eeprom: copyright not found\n");
+#endif
+	/* Validate checksum */
+	for (i = s = 0; i < EEPROM_CHECKSUM; i++)
+		s += e[i];
+	s &= 0xFF;
+	if (s != e[EEPROM_CHECKSUM]) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM checksum bad "
+		    "(wanted 0x%02X, got 0x%02X)\n", lanai->number,
+		    (unsigned int) s, (unsigned int) e[EEPROM_CHECKSUM]);
+		return -EIO;
+	}
+	s ^= 0xFF;
+	if (s != e[EEPROM_CHECKSUM_REV]) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM inverse checksum "
+		    "bad (wanted 0x%02X, got 0x%02X)\n", lanai->number,
+		    (unsigned int) s, (unsigned int) e[EEPROM_CHECKSUM_REV]);
+		return -EIO;
+	}
+	/* Verify MAC address */
+	for (i = 0; i < 6; i++)
+		if ((e[EEPROM_MAC + i] ^ e[EEPROM_MAC_REV + i]) != 0xFF) {
+			printk(KERN_ERR DEV_LABEL
+			    "(itf %d) : EEPROM MAC addresses don't match "
+			    "(0x%02X, inverse 0x%02X)\n", lanai->number,
+			    (unsigned int) e[EEPROM_MAC + i],
+			    (unsigned int) e[EEPROM_MAC_REV + i]);
+			return -EIO;
+		}
+	DPRINTK("eeprom: MAC address = %02X:%02X:%02X:%02X:%02X:%02X\n",
+		e[EEPROM_MAC + 0], e[EEPROM_MAC + 1], e[EEPROM_MAC + 2],
+		e[EEPROM_MAC + 3], e[EEPROM_MAC + 4], e[EEPROM_MAC + 5]);
+	/* Verify serial number */
+	lanai->serialno = eeprom_be4(lanai, EEPROM_SERIAL);
+	v = eeprom_be4(lanai, EEPROM_SERIAL_REV);
+	if ((lanai->serialno ^ v) != 0xFFFFFFFF) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM serial numbers "
+		    "don't match (0x%08X, inverse 0x%08X)\n", lanai->number,
+		    (unsigned int) lanai->serialno, (unsigned int) v);
+		return -EIO;
+	}
+	DPRINTK("eeprom: Serial number = %d\n", (unsigned int) lanai->serialno);
+	/* Verify magic number */
+	lanai->magicno = eeprom_be4(lanai, EEPROM_MAGIC);
+	v = eeprom_be4(lanai, EEPROM_MAGIC_REV);
+	if ((lanai->magicno ^ v) != 0xFFFFFFFF) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM magic numbers "
+		    "don't match (0x%08X, inverse 0x%08X)\n", lanai->number,
+		    lanai->magicno, v);
+		return -EIO;
+	}
+	DPRINTK("eeprom: Magic number = 0x%08X\n", lanai->magicno);
+	if (lanai->magicno != EEPROM_MAGIC_VALUE)
+		printk(KERN_WARNING DEV_LABEL "(itf %d): warning - EEPROM "
+		    "magic not what expected (got 0x%08X, not 0x%08X)\n",
+		    lanai->number, (unsigned int) lanai->magicno,
+		    (unsigned int) EEPROM_MAGIC_VALUE);
+	return 0;
+}
+
+#endif /* READ_EEPROM */
+
+static inline const u8 *eeprom_mac(const struct lanai_dev *lanai)
+{
+	return &lanai->eeprom[EEPROM_MAC];
+}
+
+/* -------------------- INTERRUPT HANDLING UTILITIES: */
+
+/* Interrupt types */
+#define INT_STATS	(0x00000002)	/* Statistics counter overflow */
+#define INT_SOOL	(0x00000004)	/* SOOL changed state */
+#define INT_LOCD	(0x00000008)	/* LOCD changed state */
+#define INT_LED		(0x00000010)	/* LED (HAPPI) changed state */
+#define INT_GPIN	(0x00000020)	/* GPIN changed state */
+#define INT_PING	(0x00000040)	/* PING_COUNT fulfilled */
+#define INT_WAKE	(0x00000080)	/* Lanai wants bus */
+#define INT_CBR0	(0x00000100)	/* CBR sched hit VCI 0 */
+#define INT_LOCK	(0x00000200)	/* Service list overflow */
+#define INT_MISMATCH	(0x00000400)	/* TX magic list mismatch */
+#define INT_AAL0_STR	(0x00000800)	/* Non-AAL5 buffer half filled */
+#define INT_AAL0	(0x00001000)	/* Non-AAL5 data available */
+#define INT_SERVICE	(0x00002000)	/* Service list entries available */
+#define INT_TABORTSENT	(0x00004000)	/* Target abort sent by lanai */
+#define INT_TABORTBM	(0x00008000)	/* Abort rcv'd as bus master */
+#define INT_TIMEOUTBM	(0x00010000)	/* No response to bus master */
+#define INT_PCIPARITY	(0x00020000)	/* Parity error on PCI */
+
+/* Sets of the above */
+#define INT_ALL		(0x0003FFFE)	/* All interrupts */
+#define INT_STATUS	(0x0000003C)	/* Some status pin changed */
+#define INT_DMASHUT	(0x00038000)	/* DMA engine got shut down */
+#define INT_SEGSHUT	(0x00000700)	/* Segmentation got shut down */
+
+static inline u32 intr_pending(const struct lanai_dev *lanai)
+{
+	return reg_read(lanai, IntStatusMasked_Reg);
+}
+
+static inline void intr_enable(const struct lanai_dev *lanai, u32 i)
+{
+	reg_write(lanai, i, IntControlEna_Reg);
+}
+
+static inline void intr_disable(const struct lanai_dev *lanai, u32 i)
+{
+	reg_write(lanai, i, IntControlDis_Reg);
+}
+
+/* -------------------- CARD/PCI STATUS: */
+
+static void status_message(int itf, const char *name, int status)
+{
+	static const char *onoff[2] = { "off to on", "on to off" };
+	printk(KERN_INFO DEV_LABEL "(itf %d): %s changed from %s\n",
+	    itf, name, onoff[!status]);
+}
+
+static void lanai_check_status(struct lanai_dev *lanai)
+{
+	u32 new = reg_read(lanai, Status_Reg);
+	u32 changes = new ^ lanai->status;
+	lanai->status = new;
+#define e(flag, name) \
+		if (changes & flag) \
+			status_message(lanai->number, name, new & flag)
+	e(STATUS_SOOL, "SOOL");
+	e(STATUS_LOCD, "LOCD");
+	e(STATUS_LED, "LED");
+	e(STATUS_GPIN, "GPIN");
+#undef e
+}
+
+static void pcistatus_got(int itf, const char *name)
+{
+	printk(KERN_INFO DEV_LABEL "(itf %d): PCI got %s error\n", itf, name);
+}
+
+static void pcistatus_check(struct lanai_dev *lanai, int clearonly)
+{
+	u16 s;
+	int result;
+	result = pci_read_config_word(lanai->pci, PCI_STATUS, &s);
+	if (result != PCIBIOS_SUCCESSFUL) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't read PCI_STATUS: "
+		    "%d\n", lanai->number, result);
+		return;
+	}
+	s &= PCI_STATUS_DETECTED_PARITY | PCI_STATUS_SIG_SYSTEM_ERROR |
+	    PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT |
+	    PCI_STATUS_SIG_TARGET_ABORT | PCI_STATUS_PARITY;
+	if (s == 0)
+		return;
+	result = pci_write_config_word(lanai->pci, PCI_STATUS, s);
+	if (result != PCIBIOS_SUCCESSFUL)
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't write PCI_STATUS: "
+		    "%d\n", lanai->number, result);
+	if (clearonly)
+		return;
+#define e(flag, name, stat) \
+		if (s & flag) { \
+			pcistatus_got(lanai->number, name); \
+			++lanai->stats.pcierr_##stat; \
+		}
+	e(PCI_STATUS_DETECTED_PARITY, "parity", parity_detect);
+	e(PCI_STATUS_SIG_SYSTEM_ERROR, "signalled system", serr_set);
+	e(PCI_STATUS_REC_MASTER_ABORT, "master", master_abort);
+	e(PCI_STATUS_REC_TARGET_ABORT, "master target", m_target_abort);
+	e(PCI_STATUS_SIG_TARGET_ABORT, "slave", s_target_abort);
+	e(PCI_STATUS_PARITY, "master parity", master_parity);
+#undef e
+}
+
+/* -------------------- VCC TX BUFFER UTILITIES: */
+
+/* space left in tx buffer in bytes */
+static inline int vcc_tx_space(const struct lanai_vcc *lvcc, int endptr)
+{
+	int r;
+	r = endptr * 16;
+	r -= ((unsigned long) lvcc->tx.buf.ptr) -
+	    ((unsigned long) lvcc->tx.buf.start);
+	r -= 16;	/* Leave "bubble" - if start==end it looks empty */
+	if (r < 0)
+		r += lanai_buf_size(&lvcc->tx.buf);
+	return r;
+}
+
+/* test if VCC is currently backlogged */
+static inline int vcc_is_backlogged(/*const*/ struct lanai_vcc *lvcc)
+{
+	return !skb_queue_empty(&lvcc->tx.backlog);
+}
+
+/* Bit fields in the segmentation buffer descriptor */
+#define DESCRIPTOR_MAGIC	(0xD0000000)
+#define DESCRIPTOR_AAL5		(0x00008000)
+#define DESCRIPTOR_AAL5_STREAM	(0x00004000)
+#define DESCRIPTOR_CLP		(0x00002000)
+
+/* Add 32-bit descriptor with its padding */
+static inline void vcc_tx_add_aal5_descriptor(struct lanai_vcc *lvcc,
+	u32 flags, int len)
+{
+	int pos;
+	APRINTK((((unsigned long) lvcc->tx.buf.ptr) & 15) == 0,
+	    "vcc_tx_add_aal5_descriptor: bad ptr=%p\n", lvcc->tx.buf.ptr);
+	lvcc->tx.buf.ptr += 4;	/* Hope the values REALLY don't matter */
+	pos = ((unsigned char *) lvcc->tx.buf.ptr) -
+	    (unsigned char *) lvcc->tx.buf.start;
+	APRINTK((pos & ~0x0001FFF0) == 0,
+	    "vcc_tx_add_aal5_descriptor: bad pos (%d) before, vci=%d, "
+	    "start,ptr,end=%p,%p,%p\n", pos, lvcc->vci,
+	    lvcc->tx.buf.start, lvcc->tx.buf.ptr, lvcc->tx.buf.end);
+	pos = (pos + len) & (lanai_buf_size(&lvcc->tx.buf) - 1);
+	APRINTK((pos & ~0x0001FFF0) == 0,
+	    "vcc_tx_add_aal5_descriptor: bad pos (%d) after, vci=%d, "
+	    "start,ptr,end=%p,%p,%p\n", pos, lvcc->vci,
+	    lvcc->tx.buf.start, lvcc->tx.buf.ptr, lvcc->tx.buf.end);
+	lvcc->tx.buf.ptr[-1] =
+	    cpu_to_le32(DESCRIPTOR_MAGIC | DESCRIPTOR_AAL5 |
+	    ((lvcc->tx.atmvcc->atm_options & ATM_ATMOPT_CLP) ?
+	    DESCRIPTOR_CLP : 0) | flags | pos >> 4);
+	if (lvcc->tx.buf.ptr >= lvcc->tx.buf.end)
+		lvcc->tx.buf.ptr = lvcc->tx.buf.start;
+}
+
+/* Add 32-bit AAL5 trailer and leave room for its CRC */
+static inline void vcc_tx_add_aal5_trailer(struct lanai_vcc *lvcc,
+	int len, int cpi, int uu)
+{
+	APRINTK((((unsigned long) lvcc->tx.buf.ptr) & 15) == 8,
+	    "vcc_tx_add_aal5_trailer: bad ptr=%p\n", lvcc->tx.buf.ptr);
+	lvcc->tx.buf.ptr += 2;
+	lvcc->tx.buf.ptr[-2] = cpu_to_be32((uu << 24) | (cpi << 16) | len);
+	if (lvcc->tx.buf.ptr >= lvcc->tx.buf.end)
+		lvcc->tx.buf.ptr = lvcc->tx.buf.start;
+}
+
+static inline void vcc_tx_memcpy(struct lanai_vcc *lvcc,
+	const unsigned char *src, int n)
+{
+	unsigned char *e;
+	int m;
+	e = ((unsigned char *) lvcc->tx.buf.ptr) + n;
+	m = e - (unsigned char *) lvcc->tx.buf.end;
+	if (m < 0)
+		m = 0;
+	memcpy(lvcc->tx.buf.ptr, src, n - m);
+	if (m != 0) {
+		memcpy(lvcc->tx.buf.start, src + n - m, m);
+		e = ((unsigned char *) lvcc->tx.buf.start) + m;
+	}
+	lvcc->tx.buf.ptr = (u32 *) e;
+}
+
+static inline void vcc_tx_memzero(struct lanai_vcc *lvcc, int n)
+{
+	unsigned char *e;
+	int m;
+	if (n == 0)
+		return;
+	e = ((unsigned char *) lvcc->tx.buf.ptr) + n;
+	m = e - (unsigned char *) lvcc->tx.buf.end;
+	if (m < 0)
+		m = 0;
+	memset(lvcc->tx.buf.ptr, 0, n - m);
+	if (m != 0) {
+		memset(lvcc->tx.buf.start, 0, m);
+		e = ((unsigned char *) lvcc->tx.buf.start) + m;
+	}
+	lvcc->tx.buf.ptr = (u32 *) e;
+}
+
+/* Update "butt" register to specify new WritePtr */
+static inline void lanai_endtx(struct lanai_dev *lanai,
+	const struct lanai_vcc *lvcc)
+{
+	int i, ptr = ((unsigned char *) lvcc->tx.buf.ptr) -
+	    (unsigned char *) lvcc->tx.buf.start;
+	APRINTK((ptr & ~0x0001FFF0) == 0,
+	    "lanai_endtx: bad ptr (%d), vci=%d, start,ptr,end=%p,%p,%p\n",
+	    ptr, lvcc->vci, lvcc->tx.buf.start, lvcc->tx.buf.ptr,
+	    lvcc->tx.buf.end);
+
+	/*
+	 * Since the "butt register" is a shared resounce on the card we
+	 * serialize all accesses to it through this spinlock.  This is
+	 * mostly just paranoia sicne the register is rarely "busy" anyway
+	 * but is needed for correctness.
+	 */
+	spin_lock(&lanai->endtxlock);
+	/*
+	 * We need to check if the "butt busy" bit is set before
+	 * updating the butt register.  In theory this should
+	 * never happen because the ATM card is plenty fast at
+	 * updating the register.  Still, we should make sure
+	 */
+	for (i = 0; reg_read(lanai, Status_Reg) & STATUS_BUTTBUSY; i++) {
+		if (unlikely(i > 50)) {
+			printk(KERN_ERR DEV_LABEL "(itf %d): butt register "
+			    "always busy!\n", lanai->number);
+			break;
+		}
+		udelay(5);
+	}
+	/*
+	 * Before we tall the card to start work we need to be sure 100% of
+	 * the info in the service buffer has been written before we tell
+	 * the card about it
+	 */
+	wmb();
+	reg_write(lanai, (ptr << 12) | lvcc->vci, Butt_Reg);
+	spin_unlock(&lanai->endtxlock);
+}
+
+/*
+ * Add one AAL5 PDU to lvcc's transmit buffer.  Caller garauntees there's
+ * space available.  "pdusize" is the number of bytes the PDU will take
+ */
+static void lanai_send_one_aal5(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc, struct sk_buff *skb, int pdusize)
+{
+	int pad;
+	APRINTK(pdusize == aal5_size(skb->len),
+	    "lanai_send_one_aal5: wrong size packet (%d != %d)\n",
+	    pdusize, aal5_size(skb->len));
+	vcc_tx_add_aal5_descriptor(lvcc, 0, pdusize);
+	pad = pdusize - skb->len - 8;
+	APRINTK(pad >= 0, "pad is negative (%d)\n", pad);
+	APRINTK(pad < 48, "pad is too big (%d)\n", pad);
+	vcc_tx_memcpy(lvcc, skb->data, skb->len);
+	vcc_tx_memzero(lvcc, pad);
+	vcc_tx_add_aal5_trailer(lvcc, skb->len, 0, 0);
+	lanai_endtx(lanai, lvcc);
+	lanai_free_skb(lvcc->tx.atmvcc, skb);
+	atomic_inc(&lvcc->tx.atmvcc->stats->tx);
+}
+
+/* Try to fill the buffer - don't call unless there is backlog */
+static void vcc_tx_unqueue_aal5(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc, int endptr)
+{
+	int n;
+	struct sk_buff *skb;
+	int space = vcc_tx_space(lvcc, endptr);
+	APRINTK(vcc_is_backlogged(lvcc),
+	    "vcc_tx_unqueue() called with empty backlog (vci=%d)\n",
+	    lvcc->vci);
+	while (space >= 64) {
+		skb = skb_dequeue(&lvcc->tx.backlog);
+		if (skb == NULL)
+			goto no_backlog;
+		n = aal5_size(skb->len);
+		if (n + 16 > space) {
+			/* No room for this packet - put it back on queue */
+			skb_queue_head(&lvcc->tx.backlog, skb);
+			return;
+		}
+		lanai_send_one_aal5(lanai, lvcc, skb, n);
+		space -= n + 16;
+	}
+	if (!vcc_is_backlogged(lvcc)) {
+	    no_backlog:
+		__clear_bit(lvcc->vci, lanai->backlog_vccs);
+	}
+}
+
+/* Given an skb that we want to transmit either send it now or queue */
+static void vcc_tx_aal5(struct lanai_dev *lanai, struct lanai_vcc *lvcc,
+	struct sk_buff *skb)
+{
+	int space, n;
+	if (vcc_is_backlogged(lvcc))		/* Already backlogged */
+		goto queue_it;
+	space = vcc_tx_space(lvcc,
+		    TXREADPTR_GET_PTR(cardvcc_read(lvcc, vcc_txreadptr)));
+	n = aal5_size(skb->len);
+	APRINTK(n + 16 >= 64, "vcc_tx_aal5: n too small (%d)\n", n);
+	if (space < n + 16) {			/* No space for this PDU */
+		__set_bit(lvcc->vci, lanai->backlog_vccs);
+	    queue_it:
+		skb_queue_tail(&lvcc->tx.backlog, skb);
+		return;
+	}
+	lanai_send_one_aal5(lanai, lvcc, skb, n);
+}
+
+static void vcc_tx_unqueue_aal0(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc, int endptr)
+{
+	printk(KERN_INFO DEV_LABEL
+	    ": vcc_tx_unqueue_aal0: not implemented\n");
+}
+
+static void vcc_tx_aal0(struct lanai_dev *lanai, struct lanai_vcc *lvcc,
+	struct sk_buff *skb)
+{
+	printk(KERN_INFO DEV_LABEL ": vcc_tx_aal0: not implemented\n");
+	/* Remember to increment lvcc->tx.atmvcc->stats->tx */
+	lanai_free_skb(lvcc->tx.atmvcc, skb);
+}
+
+/* -------------------- VCC RX BUFFER UTILITIES: */
+
+/* unlike the _tx_ cousins, this doesn't update ptr */
+static inline void vcc_rx_memcpy(unsigned char *dest,
+	const struct lanai_vcc *lvcc, int n)
+{
+	int m = ((const unsigned char *) lvcc->rx.buf.ptr) + n -
+	    ((const unsigned char *) (lvcc->rx.buf.end));
+	if (m < 0)
+		m = 0;
+	memcpy(dest, lvcc->rx.buf.ptr, n - m);
+	memcpy(dest + n - m, lvcc->rx.buf.start, m);
+	/* Make sure that these copies don't get reordered */
+	barrier();
+}
+
+/* Receive AAL5 data on a VCC with a particular endptr */
+static void vcc_rx_aal5(struct lanai_vcc *lvcc, int endptr)
+{
+	int size;
+	struct sk_buff *skb;
+	/*const*/ u32 *x, *end = &lvcc->rx.buf.start[endptr * 4];
+	int n = ((unsigned long) end) - ((unsigned long) lvcc->rx.buf.ptr);
+	if (n < 0)
+		n += lanai_buf_size(&lvcc->rx.buf);
+	APRINTK(n >= 0 && n < lanai_buf_size(&lvcc->rx.buf) && !(n & 15),
+	    "vcc_rx_aal5: n out of range (%d/%Zu)\n",
+	    n, lanai_buf_size(&lvcc->rx.buf));
+	/* Recover the second-to-last word to get true pdu length */
+	if ((x = &end[-2]) < lvcc->rx.buf.start)
+		x = &lvcc->rx.buf.end[-2];
+	/*
+	 * Before we actually read from the buffer, make sure the memory
+	 * changes have arrived
+	 */
+	rmb();
+	size = be32_to_cpup(x) & 0xffff;
+	if (unlikely(n != aal5_size(size))) {
+		/* Make sure size matches padding */
+		printk(KERN_INFO DEV_LABEL "(itf %d): Got bad AAL5 length "
+		    "on vci=%d - size=%d n=%d\n",
+		    lvcc->rx.atmvcc->dev->number, lvcc->vci, size, n);
+		lvcc->stats.x.aal5.rx_badlen++;
+		goto out;
+	}
+	skb = atm_alloc_charge(lvcc->rx.atmvcc, size, GFP_ATOMIC);
+	if (unlikely(skb == NULL)) {
+		lvcc->stats.rx_nomem++;
+		goto out;
+	}
+	skb_put(skb, size);
+	vcc_rx_memcpy(skb->data, lvcc, size);
+	ATM_SKB(skb)->vcc = lvcc->rx.atmvcc;
+	do_gettimeofday(&skb->stamp);
+	lvcc->rx.atmvcc->push(lvcc->rx.atmvcc, skb);
+	atomic_inc(&lvcc->rx.atmvcc->stats->rx);
+    out:
+	lvcc->rx.buf.ptr = end;
+	cardvcc_write(lvcc, endptr, vcc_rxreadptr);
+}
+
+static void vcc_rx_aal0(struct lanai_dev *lanai)
+{
+	printk(KERN_INFO DEV_LABEL ": vcc_rx_aal0: not implemented\n");
+	/* Remember to get read_lock(&vcc_sklist_lock) while looking up VC */
+	/* Remember to increment lvcc->rx.atmvcc->stats->rx */
+}
+
+/* -------------------- MANAGING HOST-BASED VCC TABLE: */
+
+/* Decide whether to use vmalloc or get_zeroed_page for VCC table */
+#if (NUM_VCI * BITS_PER_LONG) <= PAGE_SIZE
+#define VCCTABLE_GETFREEPAGE
+#else
+#include <linux/vmalloc.h>
+#endif
+
+static int __devinit vcc_table_allocate(struct lanai_dev *lanai)
+{
+#ifdef VCCTABLE_GETFREEPAGE
+	APRINTK((lanai->num_vci) * sizeof(struct lanai_vcc *) <= PAGE_SIZE,
+	    "vcc table > PAGE_SIZE!");
+	lanai->vccs = (struct lanai_vcc **) get_zeroed_page(GFP_KERNEL);
+	return (lanai->vccs == NULL) ? -ENOMEM : 0;
+#else
+	int bytes = (lanai->num_vci) * sizeof(struct lanai_vcc *);
+	lanai->vccs = (struct lanai_vcc **) vmalloc(bytes);
+	if (unlikely(lanai->vccs == NULL))
+		return -ENOMEM;
+	memset(lanai->vccs, 0, bytes);
+	return 0;
+#endif
+}
+
+static inline void vcc_table_deallocate(const struct lanai_dev *lanai)
+{
+#ifdef VCCTABLE_GETFREEPAGE
+	free_page((unsigned long) lanai->vccs);
+#else
+	vfree(lanai->vccs);
+#endif
+}
+
+/* Allocate a fresh lanai_vcc, with the appropriate things cleared */
+static inline struct lanai_vcc *new_lanai_vcc(void)
+{
+	struct lanai_vcc *lvcc;
+	lvcc = (struct lanai_vcc *) kmalloc(sizeof(*lvcc), GFP_KERNEL);
+	if (likely(lvcc != NULL)) {
+		lvcc->vbase = NULL;
+		lvcc->rx.atmvcc = lvcc->tx.atmvcc = NULL;
+		lvcc->nref = 0;
+		memset(&lvcc->stats, 0, sizeof lvcc->stats);
+		lvcc->rx.buf.start = lvcc->tx.buf.start = NULL;
+		skb_queue_head_init(&lvcc->tx.backlog);
+#ifdef DEBUG
+		lvcc->tx.unqueue = NULL;
+		lvcc->vci = -1;
+#endif
+	}
+	return lvcc;
+}
+
+static int lanai_get_sized_buffer(struct lanai_dev *lanai,
+	struct lanai_buffer *buf, int max_sdu, int multiplier,
+	const char *name)
+{
+	int size;
+	if (unlikely(max_sdu < 1))
+		max_sdu = 1;
+	max_sdu = aal5_size(max_sdu);
+	size = (max_sdu + 16) * multiplier + 16;
+	lanai_buf_allocate(buf, size, max_sdu + 32, lanai->pci);
+	if (unlikely(buf->start == NULL))
+		return -ENOMEM;
+	if (unlikely(lanai_buf_size(buf) < size))
+		printk(KERN_WARNING DEV_LABEL "(itf %d): wanted %d bytes "
+		    "for %s buffer, got only %Zu\n", lanai->number, size,
+		    name, lanai_buf_size(buf));
+	DPRINTK("Allocated %Zu byte %s buffer\n", lanai_buf_size(buf), name);
+	return 0;
+}
+
+/* Setup a RX buffer for a currently unbound AAL5 vci */
+static inline int lanai_setup_rx_vci_aal5(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc, const struct atm_qos *qos)
+{
+	return lanai_get_sized_buffer(lanai, &lvcc->rx.buf,
+	    qos->rxtp.max_sdu, AAL5_RX_MULTIPLIER, "RX");
+}
+
+/* Setup a TX buffer for a currently unbound AAL5 vci */
+static int lanai_setup_tx_vci(struct lanai_dev *lanai, struct lanai_vcc *lvcc,
+	const struct atm_qos *qos)
+{
+	int max_sdu, multiplier;
+	if (qos->aal == ATM_AAL0) {
+		lvcc->tx.unqueue = vcc_tx_unqueue_aal0;
+		max_sdu = ATM_CELL_SIZE - 1;
+		multiplier = AAL0_TX_MULTIPLIER;
+	} else {
+		lvcc->tx.unqueue = vcc_tx_unqueue_aal5;
+		max_sdu = qos->txtp.max_sdu;
+		multiplier = AAL5_TX_MULTIPLIER;
+	}
+	return lanai_get_sized_buffer(lanai, &lvcc->tx.buf, max_sdu,
+	    multiplier, "TX");
+}
+
+static inline void host_vcc_bind(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc, vci_t vci)
+{
+	if (lvcc->vbase != NULL)
+		return;    /* We already were bound in the other direction */
+	DPRINTK("Binding vci %d\n", vci);
+#ifdef USE_POWERDOWN
+	if (lanai->nbound++ == 0) {
+		DPRINTK("Coming out of powerdown\n");
+		lanai->conf1 &= ~CONFIG1_POWERDOWN;
+		conf1_write(lanai);
+		conf2_write(lanai);
+	}
+#endif
+	lvcc->vbase = cardvcc_addr(lanai, vci);
+	lanai->vccs[lvcc->vci = vci] = lvcc;
+}
+
+static inline void host_vcc_unbind(struct lanai_dev *lanai,
+	struct lanai_vcc *lvcc)
+{
+	if (lvcc->vbase == NULL)
+		return;	/* This vcc was never bound */
+	DPRINTK("Unbinding vci %d\n", lvcc->vci);
+	lvcc->vbase = NULL;
+	lanai->vccs[lvcc->vci] = NULL;
+#ifdef USE_POWERDOWN
+	if (--lanai->nbound == 0) {
+		DPRINTK("Going into powerdown\n");
+		lanai->conf1 |= CONFIG1_POWERDOWN;
+		conf1_write(lanai);
+	}
+#endif
+}
+
+/* -------------------- RESET CARD: */
+
+static void lanai_reset(struct lanai_dev *lanai)
+{
+	printk(KERN_CRIT DEV_LABEL "(itf %d): *NOT* reseting - not "
+	    "implemented\n", lanai->number);
+	/* TODO */
+	/* The following is just a hack until we write the real
+	 * resetter - at least ack whatever interrupt sent us
+	 * here
+	 */
+	reg_write(lanai, INT_ALL, IntAck_Reg);
+	lanai->stats.card_reset++;
+}
+
+/* -------------------- SERVICE LIST UTILITIES: */
+
+/*
+ * Allocate service buffer and tell card about it
+ */
+static int __devinit service_buffer_allocate(struct lanai_dev *lanai)
+{
+	lanai_buf_allocate(&lanai->service, SERVICE_ENTRIES * 4, 8,
+	    lanai->pci);
+	if (unlikely(lanai->service.start == NULL))
+		return -ENOMEM;
+	DPRINTK("allocated service buffer at 0x%08lX, size %Zu(%d)\n",
+	    (unsigned long) lanai->service.start,
+	    lanai_buf_size(&lanai->service),
+	    lanai_buf_size_cardorder(&lanai->service));
+	/* Clear ServWrite register to be safe */
+	reg_write(lanai, 0, ServWrite_Reg);
+	/* ServiceStuff register contains size and address of buffer */
+	reg_write(lanai,
+	    SSTUFF_SET_SIZE(lanai_buf_size_cardorder(&lanai->service)) |
+	    SSTUFF_SET_ADDR(lanai->service.dmaaddr),
+	    ServiceStuff_Reg);
+	return 0;
+}
+
+static inline void service_buffer_deallocate(struct lanai_dev *lanai)
+{
+	lanai_buf_deallocate(&lanai->service, lanai->pci);
+}
+
+/* Bitfields in service list */
+#define SERVICE_TX	(0x80000000)	/* Was from transmission */
+#define SERVICE_TRASH	(0x40000000)	/* RXed PDU was trashed */
+#define SERVICE_CRCERR	(0x20000000)	/* RXed PDU had CRC error */
+#define SERVICE_CI	(0x10000000)	/* RXed PDU had CI set */
+#define SERVICE_CLP	(0x08000000)	/* RXed PDU had CLP set */
+#define SERVICE_STREAM	(0x04000000)	/* RX Stream mode */
+#define SERVICE_GET_VCI(x) (((x)>>16)&0x3FF)
+#define SERVICE_GET_END(x) ((x)&0x1FFF)
+
+/* Handle one thing from the service list - returns true if it marked a
+ * VCC ready for xmit
+ */
+static int handle_service(struct lanai_dev *lanai, u32 s)
+{
+	vci_t vci = SERVICE_GET_VCI(s);
+	struct lanai_vcc *lvcc;
+	read_lock(&vcc_sklist_lock);
+	lvcc = lanai->vccs[vci];
+	if (unlikely(lvcc == NULL)) {
+		read_unlock(&vcc_sklist_lock);
+		DPRINTK("(itf %d) got service entry 0x%X for nonexistent "
+		    "vcc %d\n", lanai->number, (unsigned int) s, vci);
+		if (s & SERVICE_TX)
+			lanai->stats.service_notx++;
+		else
+			lanai->stats.service_norx++;
+		return 0;
+	}
+	if (s & SERVICE_TX) {			/* segmentation interrupt */
+		if (unlikely(lvcc->tx.atmvcc == NULL)) {
+			read_unlock(&vcc_sklist_lock);
+			DPRINTK("(itf %d) got service entry 0x%X for non-TX "
+			    "vcc %d\n", lanai->number, (unsigned int) s, vci);
+			lanai->stats.service_notx++;
+			return 0;
+		}
+		__set_bit(vci, lanai->transmit_ready);
+		lvcc->tx.endptr = SERVICE_GET_END(s);
+		read_unlock(&vcc_sklist_lock);
+		return 1;
+	}
+	if (unlikely(lvcc->rx.atmvcc == NULL)) {
+		read_unlock(&vcc_sklist_lock);
+		DPRINTK("(itf %d) got service entry 0x%X for non-RX "
+		    "vcc %d\n", lanai->number, (unsigned int) s, vci);
+		lanai->stats.service_norx++;
+		return 0;
+	}
+	if (unlikely(lvcc->rx.atmvcc->qos.aal != ATM_AAL5)) {
+		read_unlock(&vcc_sklist_lock);
+		DPRINTK("(itf %d) got RX service entry 0x%X for non-AAL5 "
+		    "vcc %d\n", lanai->number, (unsigned int) s, vci);
+		lanai->stats.service_rxnotaal5++;
+		atomic_inc(&lvcc->rx.atmvcc->stats->rx_err);
+		return 0;
+	}
+	if (likely(!(s & (SERVICE_TRASH | SERVICE_STREAM | SERVICE_CRCERR)))) {
+		vcc_rx_aal5(lvcc, SERVICE_GET_END(s));
+		read_unlock(&vcc_sklist_lock);
+		return 0;
+	}
+	if (s & SERVICE_TRASH) {
+		int bytes;
+		read_unlock(&vcc_sklist_lock);
+		DPRINTK("got trashed rx pdu on vci %d\n", vci);
+		atomic_inc(&lvcc->rx.atmvcc->stats->rx_err);
+		lvcc->stats.x.aal5.service_trash++;
+		bytes = (SERVICE_GET_END(s) * 16) -
+		    (((unsigned long) lvcc->rx.buf.ptr) -
+		    ((unsigned long) lvcc->rx.buf.start)) + 47;
+		if (bytes < 0)
+			bytes += lanai_buf_size(&lvcc->rx.buf);
+		lanai->stats.ovfl_trash += (bytes / 48);
+		return 0;
+	}
+	if (s & SERVICE_STREAM) {
+		read_unlock(&vcc_sklist_lock);
+		atomic_inc(&lvcc->rx.atmvcc->stats->rx_err);
+		lvcc->stats.x.aal5.service_stream++;
+		printk(KERN_ERR DEV_LABEL "(itf %d): Got AAL5 stream "
+		    "PDU on VCI %d!\n", lanai->number, vci);
+		lanai_reset(lanai);
+		return 0;
+	}
+	DPRINTK("got rx crc error on vci %d\n", vci);
+	atomic_inc(&lvcc->rx.atmvcc->stats->rx_err);
+	lvcc->stats.x.aal5.service_rxcrc++;
+	lvcc->rx.buf.ptr = &lvcc->rx.buf.start[SERVICE_GET_END(s) * 4];
+	cardvcc_write(lvcc, SERVICE_GET_END(s), vcc_rxreadptr);
+	read_unlock(&vcc_sklist_lock);
+	return 0;
+}
+
+/* Try transmitting on all VCIs that we marked ready to serve */
+static void iter_transmit(struct lanai_dev *lanai, vci_t vci)
+{
+	struct lanai_vcc *lvcc = lanai->vccs[vci];
+	if (vcc_is_backlogged(lvcc))
+		lvcc->tx.unqueue(lanai, lvcc, lvcc->tx.endptr);
+}
+
+/* Run service queue -- called from interrupt context or with
+ * interrupts otherwise disabled and with the lanai->servicelock
+ * lock held
+ */
+static void run_service(struct lanai_dev *lanai)
+{
+	int ntx = 0;
+	u32 wreg = reg_read(lanai, ServWrite_Reg);
+	const u32 *end = lanai->service.start + wreg;
+	while (lanai->service.ptr != end) {
+		ntx += handle_service(lanai,
+		    le32_to_cpup(lanai->service.ptr++));
+		if (lanai->service.ptr >= lanai->service.end)
+			lanai->service.ptr = lanai->service.start;
+	}
+	reg_write(lanai, wreg, ServRead_Reg);
+	if (ntx != 0) {
+		read_lock(&vcc_sklist_lock);
+		vci_bitfield_iterate(lanai, lanai->transmit_ready,
+		    iter_transmit);
+		bitmap_zero(lanai->transmit_ready, NUM_VCI);
+		read_unlock(&vcc_sklist_lock);
+	}
+}
+
+/* -------------------- GATHER STATISTICS: */
+
+static void get_statistics(struct lanai_dev *lanai)
+{
+	u32 statreg = reg_read(lanai, Statistics_Reg);
+	lanai->stats.atm_ovfl += STATS_GET_FIFO_OVFL(statreg);
+	lanai->stats.hec_err += STATS_GET_HEC_ERR(statreg);
+	lanai->stats.vci_trash += STATS_GET_BAD_VCI(statreg);
+	lanai->stats.ovfl_trash += STATS_GET_BUF_OVFL(statreg);
+}
+
+/* -------------------- POLLING TIMER: */
+
+#ifndef DEBUG_RW
+/* Try to undequeue 1 backlogged vcc */
+static void iter_dequeue(struct lanai_dev *lanai, vci_t vci)
+{
+	struct lanai_vcc *lvcc = lanai->vccs[vci];
+	int endptr;
+	if (lvcc == NULL || lvcc->tx.atmvcc == NULL ||
+	    !vcc_is_backlogged(lvcc)) {
+		__clear_bit(vci, lanai->backlog_vccs);
+		return;
+	}
+	endptr = TXREADPTR_GET_PTR(cardvcc_read(lvcc, vcc_txreadptr));
+	lvcc->tx.unqueue(lanai, lvcc, endptr);
+}
+#endif /* !DEBUG_RW */
+
+static void lanai_timed_poll(unsigned long arg)
+{
+	struct lanai_dev *lanai = (struct lanai_dev *) arg;
+#ifndef DEBUG_RW
+	unsigned long flags;
+#ifdef USE_POWERDOWN
+	if (lanai->conf1 & CONFIG1_POWERDOWN)
+		return;
+#endif /* USE_POWERDOWN */
+	local_irq_save(flags);
+	/* If we can grab the spinlock, check if any services need to be run */
+	if (spin_trylock(&lanai->servicelock)) {
+		run_service(lanai);
+		spin_unlock(&lanai->servicelock);
+	}
+	/* ...and see if any backlogged VCs can make progress */
+	/* unfortunately linux has no read_trylock() currently */
+	read_lock(&vcc_sklist_lock);
+	vci_bitfield_iterate(lanai, lanai->backlog_vccs, iter_dequeue);
+	read_unlock(&vcc_sklist_lock);
+	local_irq_restore(flags);
+
+	get_statistics(lanai);
+#endif /* !DEBUG_RW */
+	mod_timer(&lanai->timer, jiffies + LANAI_POLL_PERIOD);
+}
+
+static inline void lanai_timed_poll_start(struct lanai_dev *lanai)
+{
+	init_timer(&lanai->timer);
+	lanai->timer.expires = jiffies + LANAI_POLL_PERIOD;
+	lanai->timer.data = (unsigned long) lanai;
+	lanai->timer.function = lanai_timed_poll;
+	add_timer(&lanai->timer);
+}
+
+static inline void lanai_timed_poll_stop(struct lanai_dev *lanai)
+{
+	del_timer_sync(&lanai->timer);
+}
+
+/* -------------------- INTERRUPT SERVICE: */
+
+static inline void lanai_int_1(struct lanai_dev *lanai, u32 reason)
+{
+	u32 ack = 0;
+	if (reason & INT_SERVICE) {
+		ack = INT_SERVICE;
+		spin_lock(&lanai->servicelock);
+		run_service(lanai);
+		spin_unlock(&lanai->servicelock);
+	}
+	if (reason & (INT_AAL0_STR | INT_AAL0)) {
+		ack |= reason & (INT_AAL0_STR | INT_AAL0);
+		vcc_rx_aal0(lanai);
+	}
+	/* The rest of the interrupts are pretty rare */
+	if (ack == reason)
+		goto done;
+	if (reason & INT_STATS) {
+		reason &= ~INT_STATS;	/* No need to ack */
+		get_statistics(lanai);
+	}
+	if (reason & INT_STATUS) {
+		ack |= reason & INT_STATUS;
+		lanai_check_status(lanai);
+	}
+	if (unlikely(reason & INT_DMASHUT)) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): driver error - DMA "
+		    "shutdown, reason=0x%08X, address=0x%08X\n",
+		    lanai->number, (unsigned int) (reason & INT_DMASHUT),
+		    (unsigned int) reg_read(lanai, DMA_Addr_Reg));
+		if (reason & INT_TABORTBM) {
+			lanai_reset(lanai);
+			return;
+		}
+		ack |= (reason & INT_DMASHUT);
+		printk(KERN_ERR DEV_LABEL "(itf %d): re-enabling DMA\n",
+		    lanai->number);
+		conf1_write(lanai);
+		lanai->stats.dma_reenable++;
+		pcistatus_check(lanai, 0);
+	}
+	if (unlikely(reason & INT_TABORTSENT)) {
+		ack |= (reason & INT_TABORTSENT);
+		printk(KERN_ERR DEV_LABEL "(itf %d): sent PCI target abort\n",
+		    lanai->number);
+		pcistatus_check(lanai, 0);
+	}
+	if (unlikely(reason & INT_SEGSHUT)) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): driver error - "
+		    "segmentation shutdown, reason=0x%08X\n", lanai->number,
+		    (unsigned int) (reason & INT_SEGSHUT));
+		lanai_reset(lanai);
+		return;
+	}
+	if (unlikely(reason & (INT_PING | INT_WAKE))) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): driver error - "
+		    "unexpected interrupt 0x%08X, resetting\n",
+		    lanai->number,
+		    (unsigned int) (reason & (INT_PING | INT_WAKE)));
+		lanai_reset(lanai);
+		return;
+	}
+#ifdef DEBUG
+	if (unlikely(ack != reason)) {
+		DPRINTK("unacked ints: 0x%08X\n",
+		    (unsigned int) (reason & ~ack));
+		ack = reason;
+	}
+#endif
+   done:
+	if (ack != 0)
+		reg_write(lanai, ack, IntAck_Reg);
+}
+
+static irqreturn_t lanai_int(int irq, void *devid, struct pt_regs *regs)
+{
+	struct lanai_dev *lanai = (struct lanai_dev *) devid;
+	u32 reason;
+
+	(void) irq; (void) regs;	/* unused variables */
+
+#ifdef USE_POWERDOWN
+	/*
+	 * If we're powered down we shouldn't be generating any interrupts -
+	 * so assume that this is a shared interrupt line and it's for someone
+	 * else
+	 */
+	if (unlikely(lanai->conf1 & CONFIG1_POWERDOWN))
+		return IRQ_NONE;
+#endif
+
+	reason = intr_pending(lanai);
+	if (reason == 0)
+		return IRQ_NONE;	/* Must be for someone else */
+
+	do {
+		if (unlikely(reason == 0xFFFFFFFF))
+			break;		/* Maybe we've been unplugged? */
+		lanai_int_1(lanai, reason);
+		reason = intr_pending(lanai);
+	} while (reason != 0);
+
+	return IRQ_HANDLED;
+}
+
+/* TODO - it would be nice if we could use the "delayed interrupt" system
+ *   to some advantage
+ */
+
+/* -------------------- CHECK BOARD ID/REV: */
+
+/*
+ * The board id and revision are stored both in the reset register and
+ * in the PCI configuration space - the documentation says to check
+ * each of them.  If revp!=NULL we store the revision there
+ */
+static int check_board_id_and_rev(const char *name, u32 val, int *revp)
+{
+	DPRINTK("%s says board_id=%d, board_rev=%d\n", name,
+		(int) RESET_GET_BOARD_ID(val),
+		(int) RESET_GET_BOARD_REV(val));
+	if (RESET_GET_BOARD_ID(val) != BOARD_ID_LANAI256) {
+		printk(KERN_ERR DEV_LABEL ": Found %s board-id %d -- not a "
+		    "Lanai 25.6\n", name, (int) RESET_GET_BOARD_ID(val));
+		return -ENODEV;
+	}
+	if (revp != NULL)
+		*revp = RESET_GET_BOARD_REV(val);
+	return 0;
+}
+
+/* -------------------- PCI INITIALIZATION/SHUTDOWN: */
+
+static int __devinit lanai_pci_start(struct lanai_dev *lanai)
+{
+	struct pci_dev *pci = lanai->pci;
+	int result;
+	u16 w;
+
+	if (pci_enable_device(pci) != 0) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't enable "
+		    "PCI device", lanai->number);
+		return -ENXIO;
+	}
+	pci_set_master(pci);
+	if (pci_set_dma_mask(pci, DMA_32BIT_MASK) != 0) {
+		printk(KERN_WARNING DEV_LABEL
+		    "(itf %d): No suitable DMA available.\n", lanai->number);
+		return -EBUSY;
+	}
+	if (pci_set_consistent_dma_mask(pci, 0xFFFFFFFF) != 0) {
+		printk(KERN_WARNING DEV_LABEL
+		    "(itf %d): No suitable DMA available.\n", lanai->number);
+		return -EBUSY;
+	}
+	/* Get the pci revision byte */
+	result = pci_read_config_byte(pci, PCI_REVISION_ID,
+	    &lanai->pci_revision);
+	if (result != PCIBIOS_SUCCESSFUL) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't read "
+		    "PCI_REVISION_ID: %d\n", lanai->number, result);
+		return -EINVAL;
+	}
+	result = pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &w);
+	if (result != PCIBIOS_SUCCESSFUL) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't read "
+		    "PCI_SUBSYSTEM_ID: %d\n", lanai->number, result);
+		return -EINVAL;
+	}
+	result = check_board_id_and_rev("PCI", w, NULL);
+	if (result != 0)
+		return result;
+	/* Set latency timer to zero as per lanai docs */
+	result = pci_write_config_byte(pci, PCI_LATENCY_TIMER, 0);
+	if (result != PCIBIOS_SUCCESSFUL) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't write "
+		    "PCI_LATENCY_TIMER: %d\n", lanai->number, result);
+		return -EINVAL;
+	}
+	pcistatus_check(lanai, 1);
+	pcistatus_check(lanai, 0);
+	return 0;
+}
+
+/* -------------------- VPI/VCI ALLOCATION: */
+
+/*
+ * We _can_ use VCI==0 for normal traffic, but only for UBR (or we'll
+ * get a CBRZERO interrupt), and we can use it only if noone is receiving
+ * AAL0 traffic (since they will use the same queue) - according to the
+ * docs we shouldn't even use it for AAL0 traffic
+ */
+static inline int vci0_is_ok(struct lanai_dev *lanai,
+	const struct atm_qos *qos)
+{
+	if (qos->txtp.traffic_class == ATM_CBR || qos->aal == ATM_AAL0)
+		return 0;
+	if (qos->rxtp.traffic_class != ATM_NONE) {
+		if (lanai->naal0 != 0)
+			return 0;
+		lanai->conf2 |= CONFIG2_VCI0_NORMAL;
+		conf2_write_if_powerup(lanai);
+	}
+	return 1;
+}
+
+/* return true if vci is currently unused, or if requested qos is
+ * compatible
+ */
+static int vci_is_ok(struct lanai_dev *lanai, vci_t vci,
+	const struct atm_vcc *atmvcc)
+{
+	const struct atm_qos *qos = &atmvcc->qos;
+	const struct lanai_vcc *lvcc = lanai->vccs[vci];
+	if (vci == 0 && !vci0_is_ok(lanai, qos))
+		return 0;
+	if (unlikely(lvcc != NULL)) {
+		if (qos->rxtp.traffic_class != ATM_NONE &&
+		    lvcc->rx.atmvcc != NULL && lvcc->rx.atmvcc != atmvcc)
+			return 0;
+		if (qos->txtp.traffic_class != ATM_NONE &&
+		    lvcc->tx.atmvcc != NULL && lvcc->tx.atmvcc != atmvcc)
+			return 0;
+		if (qos->txtp.traffic_class == ATM_CBR &&
+		    lanai->cbrvcc != NULL && lanai->cbrvcc != atmvcc)
+			return 0;
+	}
+	if (qos->aal == ATM_AAL0 && lanai->naal0 == 0 &&
+	    qos->rxtp.traffic_class != ATM_NONE) {
+		const struct lanai_vcc *vci0 = lanai->vccs[0];
+		if (vci0 != NULL && vci0->rx.atmvcc != NULL)
+			return 0;
+		lanai->conf2 &= ~CONFIG2_VCI0_NORMAL;
+		conf2_write_if_powerup(lanai);
+	}
+	return 1;
+}
+
+static int lanai_normalize_ci(struct lanai_dev *lanai,
+	const struct atm_vcc *atmvcc, short *vpip, vci_t *vcip)
+{
+	switch (*vpip) {
+		case ATM_VPI_ANY:
+			*vpip = 0;
+			/* FALLTHROUGH */
+		case 0:
+			break;
+		default:
+			return -EADDRINUSE;
+	}
+	switch (*vcip) {
+		case ATM_VCI_ANY:
+			for (*vcip = ATM_NOT_RSV_VCI; *vcip < lanai->num_vci;
+			    (*vcip)++)
+				if (vci_is_ok(lanai, *vcip, atmvcc))
+					return 0;
+			return -EADDRINUSE;
+		default:
+			if (*vcip >= lanai->num_vci || *vcip < 0 ||
+			    !vci_is_ok(lanai, *vcip, atmvcc))
+				return -EADDRINUSE;
+	}
+	return 0;
+}
+
+/* -------------------- MANAGE CBR: */
+
+/*
+ * CBR ICG is stored as a fixed-point number with 4 fractional bits.
+ * Note that storing a number greater than 2046.0 will result in
+ * incorrect shaping
+ */
+#define CBRICG_FRAC_BITS	(4)
+#define CBRICG_MAX		(2046 << CBRICG_FRAC_BITS)
+
+/*
+ * ICG is related to PCR with the formula PCR = MAXPCR / (ICG + 1)
+ * where MAXPCR is (according to the docs) 25600000/(54*8),
+ * which is equal to (3125<<9)/27.
+ *
+ * Solving for ICG, we get:
+ *    ICG = MAXPCR/PCR - 1
+ *    ICG = (3125<<9)/(27*PCR) - 1
+ *    ICG = ((3125<<9) - (27*PCR)) / (27*PCR)
+ *
+ * The end result is supposed to be a fixed-point number with FRAC_BITS
+ * bits of a fractional part, so we keep everything in the numerator
+ * shifted by that much as we compute
+ *
+ */
+static int pcr_to_cbricg(/*const*/ struct atm_qos *qos)
+{
+	int rounddown = 0;	/* 1 = Round PCR down, i.e. round ICG _up_ */
+	int x, icg, pcr = atm_pcr_goal(&qos->txtp);
+	if (pcr == 0)		/* Use maximum bandwidth */
+		return 0;
+	if (pcr < 0) {
+		rounddown = 1;
+		pcr = -pcr;
+	}
+	x = pcr * 27;
+	icg = (3125 << (9 + CBRICG_FRAC_BITS)) - (x << CBRICG_FRAC_BITS);
+	if (rounddown)
+		icg += x - 1;
+	icg /= x;
+	if (icg > CBRICG_MAX)
+		icg = CBRICG_MAX;
+	DPRINTK("pcr_to_cbricg: pcr=%d rounddown=%c icg=%d\n",
+	    pcr, rounddown ? 'Y' : 'N', icg);
+	return icg;
+}
+
+static inline void lanai_cbr_setup(struct lanai_dev *lanai)
+{
+	reg_write(lanai, pcr_to_cbricg(&lanai->cbrvcc->qos), CBR_ICG_Reg);
+	reg_write(lanai, lanai->cbrvcc->vci, CBR_PTR_Reg);
+	lanai->conf2 |= CONFIG2_CBR_ENABLE;
+	conf2_write(lanai);
+}
+
+static inline void lanai_cbr_shutdown(struct lanai_dev *lanai)
+{
+	lanai->conf2 &= ~CONFIG2_CBR_ENABLE;
+	conf2_write(lanai);
+}
+
+/* -------------------- OPERATIONS: */
+
+/* setup a newly detected device */
+static int __devinit lanai_dev_open(struct atm_dev *atmdev)
+{
+	struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data;
+	unsigned long raw_base;
+	int result;
+
+	DPRINTK("In lanai_dev_open()\n");
+	/* Basic device fields */
+	lanai->number = atmdev->number;
+	lanai->num_vci = NUM_VCI;
+	bitmap_zero(lanai->backlog_vccs, NUM_VCI);
+	bitmap_zero(lanai->transmit_ready, NUM_VCI);
+	lanai->naal0 = 0;
+#ifdef USE_POWERDOWN
+	lanai->nbound = 0;
+#endif
+	lanai->cbrvcc = NULL;
+	memset(&lanai->stats, 0, sizeof lanai->stats);
+	spin_lock_init(&lanai->endtxlock);
+	spin_lock_init(&lanai->servicelock);
+	atmdev->ci_range.vpi_bits = 0;
+	atmdev->ci_range.vci_bits = 0;
+	while (1 << atmdev->ci_range.vci_bits < lanai->num_vci)
+		atmdev->ci_range.vci_bits++;
+	atmdev->link_rate = ATM_25_PCR;
+
+	/* 3.2: PCI initialization */
+	if ((result = lanai_pci_start(lanai)) != 0)
+		goto error;
+	raw_base = lanai->pci->resource[0].start;
+	lanai->base = (bus_addr_t) ioremap(raw_base, LANAI_MAPPING_SIZE);
+	if (lanai->base == NULL) {
+		printk(KERN_ERR DEV_LABEL ": couldn't remap I/O space\n");
+		goto error_pci;
+	}
+	/* 3.3: Reset lanai and PHY */
+	reset_board(lanai);
+	lanai->conf1 = reg_read(lanai, Config1_Reg);
+	lanai->conf1 &= ~(CONFIG1_GPOUT1 | CONFIG1_POWERDOWN |
+	    CONFIG1_MASK_LEDMODE);
+	lanai->conf1 |= CONFIG1_SET_LEDMODE(LEDMODE_NOT_SOOL);
+	reg_write(lanai, lanai->conf1 | CONFIG1_GPOUT1, Config1_Reg);
+	udelay(1000);
+	conf1_write(lanai);
+
+	/*
+	 * 3.4: Turn on endian mode for big-endian hardware
+	 *   We don't actually want to do this - the actual bit fields
+	 *   in the endian register are not documented anywhere.
+	 *   Instead we do the bit-flipping ourselves on big-endian
+	 *   hardware.
+	 *
+	 * 3.5: get the board ID/rev by reading the reset register
+	 */
+	result = check_board_id_and_rev("register",
+	    reg_read(lanai, Reset_Reg), &lanai->board_rev);
+	if (result != 0)
+		goto error_unmap;
+
+	/* 3.6: read EEPROM */
+	if ((result = eeprom_read(lanai)) != 0)
+		goto error_unmap;
+	if ((result = eeprom_validate(lanai)) != 0)
+		goto error_unmap;
+
+	/* 3.7: re-reset PHY, do loopback tests, setup PHY */
+	reg_write(lanai, lanai->conf1 | CONFIG1_GPOUT1, Config1_Reg);
+	udelay(1000);
+	conf1_write(lanai);
+	/* TODO - loopback tests */
+	lanai->conf1 |= (CONFIG1_GPOUT2 | CONFIG1_GPOUT3 | CONFIG1_DMA_ENABLE);
+	conf1_write(lanai);
+
+	/* 3.8/3.9: test and initialize card SRAM */
+	if ((result = sram_test_and_clear(lanai)) != 0)
+		goto error_unmap;
+
+	/* 3.10: initialize lanai registers */
+	lanai->conf1 |= CONFIG1_DMA_ENABLE;
+	conf1_write(lanai);
+	if ((result = service_buffer_allocate(lanai)) != 0)
+		goto error_unmap;
+	if ((result = vcc_table_allocate(lanai)) != 0)
+		goto error_service;
+	lanai->conf2 = (lanai->num_vci >= 512 ? CONFIG2_HOWMANY : 0) |
+	    CONFIG2_HEC_DROP |	/* ??? */ CONFIG2_PTI7_MODE;
+	conf2_write(lanai);
+	reg_write(lanai, TX_FIFO_DEPTH, TxDepth_Reg);
+	reg_write(lanai, 0, CBR_ICG_Reg);	/* CBR defaults to no limit */
+	if ((result = request_irq(lanai->pci->irq, lanai_int, SA_SHIRQ,
+	    DEV_LABEL, lanai)) != 0) {
+		printk(KERN_ERR DEV_LABEL ": can't allocate interrupt\n");
+		goto error_vcctable;
+	}
+	mb();				/* Make sure that all that made it */
+	intr_enable(lanai, INT_ALL & ~(INT_PING | INT_WAKE));
+	/* 3.11: initialize loop mode (i.e. turn looping off) */
+	lanai->conf1 = (lanai->conf1 & ~CONFIG1_MASK_LOOPMODE) |
+	    CONFIG1_SET_LOOPMODE(LOOPMODE_NORMAL) |
+	    CONFIG1_GPOUT2 | CONFIG1_GPOUT3;
+	conf1_write(lanai);
+	lanai->status = reg_read(lanai, Status_Reg);
+	/* We're now done initializing this card */
+#ifdef USE_POWERDOWN
+	lanai->conf1 |= CONFIG1_POWERDOWN;
+	conf1_write(lanai);
+#endif
+	memcpy(atmdev->esi, eeprom_mac(lanai), ESI_LEN);
+	lanai_timed_poll_start(lanai);
+	printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d, base=0x%lx, irq=%u "
+	    "(%02X-%02X-%02X-%02X-%02X-%02X)\n", lanai->number,
+	    (int) lanai->pci_revision, (unsigned long) lanai->base,
+	    lanai->pci->irq,
+	    atmdev->esi[0], atmdev->esi[1], atmdev->esi[2],
+	    atmdev->esi[3], atmdev->esi[4], atmdev->esi[5]);
+	printk(KERN_NOTICE DEV_LABEL "(itf %d): LANAI%s, serialno=%u(0x%X), "
+	    "board_rev=%d\n", lanai->number,
+	    lanai->type==lanai2 ? "2" : "HB", (unsigned int) lanai->serialno,
+	    (unsigned int) lanai->serialno, lanai->board_rev);
+	return 0;
+
+    error_vcctable:
+	vcc_table_deallocate(lanai);
+    error_service:
+	service_buffer_deallocate(lanai);
+    error_unmap:
+	reset_board(lanai);
+#ifdef USE_POWERDOWN
+	lanai->conf1 = reg_read(lanai, Config1_Reg) | CONFIG1_POWERDOWN;
+	conf1_write(lanai);
+#endif
+	iounmap(lanai->base);
+    error_pci:
+	pci_disable_device(lanai->pci);
+    error:
+	return result;
+}
+
+/* called when device is being shutdown, and all vcc's are gone - higher
+ * levels will deallocate the atm device for us
+ */
+static void lanai_dev_close(struct atm_dev *atmdev)
+{
+	struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data;
+	printk(KERN_INFO DEV_LABEL "(itf %d): shutting down interface\n",
+	    lanai->number);
+	lanai_timed_poll_stop(lanai);
+#ifdef USE_POWERDOWN
+	lanai->conf1 = reg_read(lanai, Config1_Reg) & ~CONFIG1_POWERDOWN;
+	conf1_write(lanai);
+#endif
+	intr_disable(lanai, INT_ALL);
+	free_irq(lanai->pci->irq, lanai);
+	reset_board(lanai);
+#ifdef USE_POWERDOWN
+	lanai->conf1 |= CONFIG1_POWERDOWN;
+	conf1_write(lanai);
+#endif
+	pci_disable_device(lanai->pci);
+	vcc_table_deallocate(lanai);
+	service_buffer_deallocate(lanai);
+	iounmap(lanai->base);
+	kfree(lanai);
+}
+
+/* close a vcc */
+static void lanai_close(struct atm_vcc *atmvcc)
+{
+	struct lanai_vcc *lvcc = (struct lanai_vcc *) atmvcc->dev_data;
+	struct lanai_dev *lanai = (struct lanai_dev *) atmvcc->dev->dev_data;
+	if (lvcc == NULL)
+		return;
+	clear_bit(ATM_VF_READY, &atmvcc->flags);
+	clear_bit(ATM_VF_PARTIAL, &atmvcc->flags);
+	if (lvcc->rx.atmvcc == atmvcc) {
+		lanai_shutdown_rx_vci(lvcc);
+		if (atmvcc->qos.aal == ATM_AAL0) {
+			if (--lanai->naal0 <= 0)
+				aal0_buffer_free(lanai);
+		} else
+			lanai_buf_deallocate(&lvcc->rx.buf, lanai->pci);
+		lvcc->rx.atmvcc = NULL;
+	}
+	if (lvcc->tx.atmvcc == atmvcc) {
+		if (atmvcc == lanai->cbrvcc) {
+			if (lvcc->vbase != NULL)
+				lanai_cbr_shutdown(lanai);
+			lanai->cbrvcc = NULL;
+		}
+		lanai_shutdown_tx_vci(lanai, lvcc);
+		lanai_buf_deallocate(&lvcc->tx.buf, lanai->pci);
+		lvcc->tx.atmvcc = NULL;
+	}
+	if (--lvcc->nref == 0) {
+		host_vcc_unbind(lanai, lvcc);
+		kfree(lvcc);
+	}
+	atmvcc->dev_data = NULL;
+	clear_bit(ATM_VF_ADDR, &atmvcc->flags);
+}
+
+/* open a vcc on the card to vpi/vci */
+static int lanai_open(struct atm_vcc *atmvcc)
+{
+	struct lanai_dev *lanai;
+	struct lanai_vcc *lvcc;
+	int result = 0;
+	int vci = atmvcc->vci;
+	short vpi = atmvcc->vpi;
+	/* we don't support partial open - it's not really useful anyway */
+	if ((test_bit(ATM_VF_PARTIAL, &atmvcc->flags)) ||
+	    (vpi == ATM_VPI_UNSPEC) || (vci == ATM_VCI_UNSPEC))
+		return -EINVAL;
+	lanai = (struct lanai_dev *) atmvcc->dev->dev_data;
+	result = lanai_normalize_ci(lanai, atmvcc, &vpi, &vci);
+	if (unlikely(result != 0))
+		goto out;
+	set_bit(ATM_VF_ADDR, &atmvcc->flags);
+	if (atmvcc->qos.aal != ATM_AAL0 && atmvcc->qos.aal != ATM_AAL5)
+		return -EINVAL;
+	DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n", lanai->number,
+	    (int) vpi, vci);
+	lvcc = lanai->vccs[vci];
+	if (lvcc == NULL) {
+		lvcc = new_lanai_vcc();
+		if (unlikely(lvcc == NULL))
+			return -ENOMEM;
+		atmvcc->dev_data = lvcc;
+	}
+	lvcc->nref++;
+	if (atmvcc->qos.rxtp.traffic_class != ATM_NONE) {
+		APRINTK(lvcc->rx.atmvcc == NULL, "rx.atmvcc!=NULL, vci=%d\n",
+		    vci);
+		if (atmvcc->qos.aal == ATM_AAL0) {
+			if (lanai->naal0 == 0)
+				result = aal0_buffer_allocate(lanai);
+		} else
+			result = lanai_setup_rx_vci_aal5(
+			    lanai, lvcc, &atmvcc->qos);
+		if (unlikely(result != 0))
+			goto out_free;
+		lvcc->rx.atmvcc = atmvcc;
+		lvcc->stats.rx_nomem = 0;
+		lvcc->stats.x.aal5.rx_badlen = 0;
+		lvcc->stats.x.aal5.service_trash = 0;
+		lvcc->stats.x.aal5.service_stream = 0;
+		lvcc->stats.x.aal5.service_rxcrc = 0;
+		if (atmvcc->qos.aal == ATM_AAL0)
+			lanai->naal0++;
+	}
+	if (atmvcc->qos.txtp.traffic_class != ATM_NONE) {
+		APRINTK(lvcc->tx.atmvcc == NULL, "tx.atmvcc!=NULL, vci=%d\n",
+		    vci);
+		result = lanai_setup_tx_vci(lanai, lvcc, &atmvcc->qos);
+		if (unlikely(result != 0))
+			goto out_free;
+		lvcc->tx.atmvcc = atmvcc;
+		if (atmvcc->qos.txtp.traffic_class == ATM_CBR) {
+			APRINTK(lanai->cbrvcc == NULL,
+			    "cbrvcc!=NULL, vci=%d\n", vci);
+			lanai->cbrvcc = atmvcc;
+		}
+	}
+	host_vcc_bind(lanai, lvcc, vci);
+	/*
+	 * Make sure everything made it to RAM before we tell the card about
+	 * the VCC
+	 */
+	wmb();
+	if (atmvcc == lvcc->rx.atmvcc)
+		host_vcc_start_rx(lvcc);
+	if (atmvcc == lvcc->tx.atmvcc) {
+		host_vcc_start_tx(lvcc);
+		if (lanai->cbrvcc == atmvcc)
+			lanai_cbr_setup(lanai);
+	}
+	set_bit(ATM_VF_READY, &atmvcc->flags);
+	return 0;
+    out_free:
+	lanai_close(atmvcc);
+    out:
+	return result;
+}
+
+#if 0
+/* ioctl operations for card */
+/* NOTE: these are all DEBUGGING ONLY currently */
+static int lanai_ioctl(struct atm_dev *atmdev, unsigned int cmd, void __user *arg)
+{
+	int result = 0;
+	struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data;
+	switch(cmd) {
+		case 2106275:
+			shutdown_atm_dev(atmdev);
+			return 0;
+		case 2200000: {
+			unsigned long flags;
+			spin_lock_irqsave(&lanai->servicelock, flags);
+			run_service(lanai);
+			spin_unlock_irqrestore(&lanai->servicelock, flags);
+			return 0; }
+		case 2200002:
+			get_statistics(lanai);
+			return 0;
+		case 2200003: {
+			unsigned int i;
+			for (i = 0; i <= 0x5C ; i += 4) {
+				if (i==0x48) /* Write-only butt reg */
+					continue;
+				printk(KERN_CRIT DEV_LABEL "  0x%02X: "
+				    "0x%08X\n", i,
+				    (unsigned int) readl(lanai->base + i));
+				barrier(); mb();
+				pcistatus_check(lanai, 0);
+				barrier(); mb();
+			}
+			return 0; }
+		case 2200004: {
+			u8 b;
+			u16 w;
+			u32 dw;
+			struct pci_dev *pci = lanai->pci;
+			(void) pci_read_config_word(pci, PCI_VENDOR_ID, &w);
+			DPRINTK("vendor = 0x%X\n", (unsigned int) w);
+			(void) pci_read_config_word(pci, PCI_DEVICE_ID, &w);
+			DPRINTK("device = 0x%X\n", (unsigned int) w);
+			(void) pci_read_config_word(pci, PCI_COMMAND, &w);
+			DPRINTK("command = 0x%X\n", (unsigned int) w);
+			(void) pci_read_config_word(pci, PCI_STATUS, &w);
+			DPRINTK("status = 0x%X\n", (unsigned int) w);
+			(void) pci_read_config_dword(pci,
+			    PCI_CLASS_REVISION, &dw);
+			DPRINTK("class/revision = 0x%X\n", (unsigned int) dw);
+			(void) pci_read_config_byte(pci,
+			    PCI_CACHE_LINE_SIZE, &b);
+			DPRINTK("cache line size = 0x%X\n", (unsigned int) b);
+			(void) pci_read_config_byte(pci, PCI_LATENCY_TIMER, &b);
+			DPRINTK("latency = %d (0x%X)\n",
+			    (int) b, (unsigned int) b);
+			(void) pci_read_config_byte(pci, PCI_HEADER_TYPE, &b);
+			DPRINTK("header type = 0x%X\n", (unsigned int) b);
+			(void) pci_read_config_byte(pci, PCI_BIST, &b);
+			DPRINTK("bist = 0x%X\n", (unsigned int) b);
+			/* skipping a few here */
+			(void) pci_read_config_byte(pci,
+			    PCI_INTERRUPT_LINE, &b);
+			DPRINTK("pci_int_line = 0x%X\n", (unsigned int) b);
+			(void) pci_read_config_byte(pci,
+			    PCI_INTERRUPT_PIN, &b);
+			DPRINTK("pci_int_pin = 0x%X\n", (unsigned int) b);
+			(void) pci_read_config_byte(pci, PCI_MIN_GNT, &b);
+			DPRINTK("min_gnt = 0x%X\n", (unsigned int) b);
+			(void) pci_read_config_byte(pci, PCI_MAX_LAT, &b);
+			DPRINTK("max_lat = 0x%X\n", (unsigned int) b); }
+			return 0;
+#ifdef USE_POWERDOWN
+		case 2200005:
+			DPRINTK("Coming out of powerdown\n");
+			lanai->conf1 &= ~CONFIG1_POWERDOWN;
+			conf1_write(lanai);
+			return 0;
+#endif
+		default:
+			result = -ENOIOCTLCMD;
+	}
+	return result;
+}
+#else /* !0 */
+#define lanai_ioctl NULL
+#endif /* 0 */
+
+static int lanai_send(struct atm_vcc *atmvcc, struct sk_buff *skb)
+{
+	struct lanai_vcc *lvcc = (struct lanai_vcc *) atmvcc->dev_data;
+	struct lanai_dev *lanai = (struct lanai_dev *) atmvcc->dev->dev_data;
+	unsigned long flags;
+	if (unlikely(lvcc == NULL || lvcc->vbase == NULL ||
+	      lvcc->tx.atmvcc != atmvcc))
+		goto einval;
+#ifdef DEBUG
+	if (unlikely(skb == NULL)) {
+		DPRINTK("lanai_send: skb==NULL for vci=%d\n", atmvcc->vci);
+		goto einval;
+	}
+	if (unlikely(lanai == NULL)) {
+		DPRINTK("lanai_send: lanai==NULL for vci=%d\n", atmvcc->vci);
+		goto einval;
+	}
+#endif
+	ATM_SKB(skb)->vcc = atmvcc;
+	switch (atmvcc->qos.aal) {
+		case ATM_AAL5:
+			read_lock_irqsave(&vcc_sklist_lock, flags);
+			vcc_tx_aal5(lanai, lvcc, skb);
+			read_unlock_irqrestore(&vcc_sklist_lock, flags);
+			return 0;
+		case ATM_AAL0:
+			if (unlikely(skb->len != ATM_CELL_SIZE-1))
+				goto einval;
+  /* NOTE - this next line is technically invalid - we haven't unshared skb */
+			cpu_to_be32s((u32 *) skb->data);
+			read_lock_irqsave(&vcc_sklist_lock, flags);
+			vcc_tx_aal0(lanai, lvcc, skb);
+			read_unlock_irqrestore(&vcc_sklist_lock, flags);
+			return 0;
+	}
+	DPRINTK("lanai_send: bad aal=%d on vci=%d\n", (int) atmvcc->qos.aal,
+	    atmvcc->vci);
+    einval:
+	lanai_free_skb(atmvcc, skb);
+	return -EINVAL;
+}
+
+static int lanai_change_qos(struct atm_vcc *atmvcc,
+	/*const*/ struct atm_qos *qos, int flags)
+{
+	return -EBUSY;		/* TODO: need to write this */
+}
+
+#ifndef CONFIG_PROC_FS
+#define lanai_proc_read NULL
+#else
+static int lanai_proc_read(struct atm_dev *atmdev, loff_t *pos, char *page)
+{
+	struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data;
+	loff_t left = *pos;
+	struct lanai_vcc *lvcc;
+	if (left-- == 0)
+		return sprintf(page, DEV_LABEL "(itf %d): chip=LANAI%s, "
+		    "serial=%u, magic=0x%08X, num_vci=%d\n",
+		    atmdev->number, lanai->type==lanai2 ? "2" : "HB",
+		    (unsigned int) lanai->serialno,
+		    (unsigned int) lanai->magicno, lanai->num_vci);
+	if (left-- == 0)
+		return sprintf(page, "revision: board=%d, pci_if=%d\n",
+		    lanai->board_rev, (int) lanai->pci_revision);
+	if (left-- == 0)
+		return sprintf(page, "EEPROM ESI: "
+		    "%02X:%02X:%02X:%02X:%02X:%02X\n",
+		    lanai->eeprom[EEPROM_MAC + 0],
+		    lanai->eeprom[EEPROM_MAC + 1],
+		    lanai->eeprom[EEPROM_MAC + 2],
+		    lanai->eeprom[EEPROM_MAC + 3],
+		    lanai->eeprom[EEPROM_MAC + 4],
+		    lanai->eeprom[EEPROM_MAC + 5]);
+	if (left-- == 0)
+		return sprintf(page, "status: SOOL=%d, LOCD=%d, LED=%d, "
+		    "GPIN=%d\n", (lanai->status & STATUS_SOOL) ? 1 : 0,
+		    (lanai->status & STATUS_LOCD) ? 1 : 0,
+		    (lanai->status & STATUS_LED) ? 1 : 0,
+		    (lanai->status & STATUS_GPIN) ? 1 : 0);
+	if (left-- == 0)
+		return sprintf(page, "global buffer sizes: service=%Zu, "
+		    "aal0_rx=%Zu\n", lanai_buf_size(&lanai->service),
+		    lanai->naal0 ? lanai_buf_size(&lanai->aal0buf) : 0);
+	if (left-- == 0) {
+		get_statistics(lanai);
+		return sprintf(page, "cells in error: overflow=%u, "
+		    "closed_vci=%u, bad_HEC=%u, rx_fifo=%u\n",
+		    lanai->stats.ovfl_trash, lanai->stats.vci_trash,
+		    lanai->stats.hec_err, lanai->stats.atm_ovfl);
+	}
+	if (left-- == 0)
+		return sprintf(page, "PCI errors: parity_detect=%u, "
+		    "master_abort=%u, master_target_abort=%u,\n",
+		    lanai->stats.pcierr_parity_detect,
+		    lanai->stats.pcierr_serr_set,
+		    lanai->stats.pcierr_m_target_abort);
+	if (left-- == 0)
+		return sprintf(page, "            slave_target_abort=%u, "
+		    "master_parity=%u\n", lanai->stats.pcierr_s_target_abort,
+		    lanai->stats.pcierr_master_parity);
+	if (left-- == 0)
+		return sprintf(page, "                     no_tx=%u, "
+		    "no_rx=%u, bad_rx_aal=%u\n", lanai->stats.service_norx,
+		    lanai->stats.service_notx,
+		    lanai->stats.service_rxnotaal5);
+	if (left-- == 0)
+		return sprintf(page, "resets: dma=%u, card=%u\n",
+		    lanai->stats.dma_reenable, lanai->stats.card_reset);
+	/* At this point, "left" should be the VCI we're looking for */
+	read_lock(&vcc_sklist_lock);
+	for (; ; left++) {
+		if (left >= NUM_VCI) {
+			left = 0;
+			goto out;
+		}
+		if ((lvcc = lanai->vccs[left]) != NULL)
+			break;
+		(*pos)++;
+	}
+	/* Note that we re-use "left" here since we're done with it */
+	left = sprintf(page, "VCI %4d: nref=%d, rx_nomem=%u",  (vci_t) left,
+	    lvcc->nref, lvcc->stats.rx_nomem);
+	if (lvcc->rx.atmvcc != NULL) {
+		left += sprintf(&page[left], ",\n          rx_AAL=%d",
+		    lvcc->rx.atmvcc->qos.aal == ATM_AAL5 ? 5 : 0);
+		if (lvcc->rx.atmvcc->qos.aal == ATM_AAL5)
+			left += sprintf(&page[left], ", rx_buf_size=%Zu, "
+			    "rx_bad_len=%u,\n          rx_service_trash=%u, "
+			    "rx_service_stream=%u, rx_bad_crc=%u",
+			    lanai_buf_size(&lvcc->rx.buf),
+			    lvcc->stats.x.aal5.rx_badlen,
+			    lvcc->stats.x.aal5.service_trash,
+			    lvcc->stats.x.aal5.service_stream,
+			    lvcc->stats.x.aal5.service_rxcrc);
+	}
+	if (lvcc->tx.atmvcc != NULL)
+		left += sprintf(&page[left], ",\n          tx_AAL=%d, "
+		    "tx_buf_size=%Zu, tx_qos=%cBR, tx_backlogged=%c",
+		    lvcc->tx.atmvcc->qos.aal == ATM_AAL5 ? 5 : 0,
+		    lanai_buf_size(&lvcc->tx.buf),
+		    lvcc->tx.atmvcc == lanai->cbrvcc ? 'C' : 'U',
+		    vcc_is_backlogged(lvcc) ? 'Y' : 'N');
+	page[left++] = '\n';
+	page[left] = '\0';
+    out:
+	read_unlock(&vcc_sklist_lock);
+	return left;
+}
+#endif /* CONFIG_PROC_FS */
+
+/* -------------------- HOOKS: */
+
+static const struct atmdev_ops ops = {
+	.dev_close	= lanai_dev_close,
+	.open		= lanai_open,
+	.close		= lanai_close,
+	.ioctl		= lanai_ioctl,
+	.getsockopt	= NULL,
+	.setsockopt	= NULL,
+	.send		= lanai_send,
+	.phy_put	= NULL,
+	.phy_get	= NULL,
+	.change_qos	= lanai_change_qos,
+	.proc_read	= lanai_proc_read,
+	.owner		= THIS_MODULE
+};
+
+/* initialize one probed card */
+static int __devinit lanai_init_one(struct pci_dev *pci,
+				    const struct pci_device_id *ident)
+{
+	struct lanai_dev *lanai;
+	struct atm_dev *atmdev;
+	int result;
+
+	lanai = (struct lanai_dev *) kmalloc(sizeof(*lanai), GFP_KERNEL);
+	if (lanai == NULL) {
+		printk(KERN_ERR DEV_LABEL
+		       ": couldn't allocate dev_data structure!\n");
+		return -ENOMEM;
+	}
+
+	atmdev = atm_dev_register(DEV_LABEL, &ops, -1, NULL);
+	if (atmdev == NULL) {
+		printk(KERN_ERR DEV_LABEL
+		    ": couldn't register atm device!\n");
+		kfree(lanai);
+		return -EBUSY;
+	}
+
+	atmdev->dev_data = lanai;
+	lanai->pci = pci;
+	lanai->type = (enum lanai_type) ident->device;
+
+	result = lanai_dev_open(atmdev);
+	if (result != 0) {
+		DPRINTK("lanai_start() failed, err=%d\n", -result);
+		atm_dev_deregister(atmdev);
+		kfree(lanai);
+	}
+	return result;
+}
+
+static struct pci_device_id lanai_pci_tbl[] = {
+	{
+		PCI_VENDOR_ID_EF, PCI_VENDOR_ID_EF_ATM_LANAI2,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0
+	},
+	{
+		PCI_VENDOR_ID_EF, PCI_VENDOR_ID_EF_ATM_LANAIHB,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0
+	},
+	{ 0, }	/* terminal entry */
+};
+MODULE_DEVICE_TABLE(pci, lanai_pci_tbl);
+
+static struct pci_driver lanai_driver = {
+	.name     = DEV_LABEL,
+	.id_table = lanai_pci_tbl,
+	.probe    = lanai_init_one,
+};
+
+static int __init lanai_module_init(void)
+{
+	int x;
+
+	x = pci_register_driver(&lanai_driver);
+	if (x != 0)
+		printk(KERN_ERR DEV_LABEL ": no adapter found\n");
+	return x;
+}
+
+static void __exit lanai_module_exit(void)
+{
+	/* We'll only get called when all the interfaces are already
+	 * gone, so there isn't much to do
+	 */
+	DPRINTK("cleanup_module()\n");
+}
+
+module_init(lanai_module_init);
+module_exit(lanai_module_exit);
+
+MODULE_AUTHOR("Mitchell Blank Jr <mitch@sfgoth.com>");
+MODULE_DESCRIPTION("Efficient Networks Speedstream 3010 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/atm/midway.h b/drivers/atm/midway.h
new file mode 100644
index 0000000..432525a
--- /dev/null
+++ b/drivers/atm/midway.h
@@ -0,0 +1,265 @@
+/* drivers/atm/midway.h - Efficient Networks Midway (SAR) description */
+ 
+/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#ifndef DRIVERS_ATM_MIDWAY_H
+#define DRIVERS_ATM_MIDWAY_H
+
+
+#define NR_VCI		1024		/* number of VCIs */
+#define NR_VCI_LD	10		/* log2(NR_VCI) */
+#define NR_DMA_RX	512		/* RX DMA queue entries */
+#define NR_DMA_TX	512		/* TX DMA queue entries */
+#define NR_SERVICE	NR_VCI		/* service list size */
+#define NR_CHAN		8		/* number of TX channels */
+#define TS_CLOCK	25000000	/* traffic shaper clock (cell/sec) */
+
+#define MAP_MAX_SIZE	0x00400000	/* memory window for max config */
+#define EPROM_SIZE	0x00010000
+#define	MEM_VALID	0xffc00000	/* mask base address with this */
+#define PHY_BASE	0x00020000	/* offset of PHY register are */
+#define REG_BASE	0x00040000	/* offset of Midway register area */
+#define RAM_BASE	0x00200000	/* offset of RAM area */
+#define RAM_INCREMENT	0x00020000	/* probe for RAM every 128kB */
+
+#define MID_VCI_BASE	RAM_BASE
+#define MID_DMA_RX_BASE	(MID_VCI_BASE+NR_VCI*16)
+#define MID_DMA_TX_BASE	(MID_DMA_RX_BASE+NR_DMA_RX*8)
+#define MID_SERVICE_BASE (MID_DMA_TX_BASE+NR_DMA_TX*8)
+#define MID_FREE_BASE	(MID_SERVICE_BASE+NR_SERVICE*4)
+
+#define MAC_LEN 6 /* atm.h */
+
+#define MID_MIN_BUF_SIZE (1024)		/*   1 kB is minimum */
+#define MID_MAX_BUF_SIZE (128*1024)	/* 128 kB is maximum */
+
+#define RX_DESCR_SIZE	1		/* RX PDU descr is 1 longword */
+#define TX_DESCR_SIZE	2		/* TX PDU descr is 2 longwords */
+#define AAL5_TRAILER	(ATM_AAL5_TRAILER/4) /* AAL5 trailer is 2 longwords */
+
+#define TX_GAP		8		/* TX buffer gap (words) */
+
+/*
+ * Midway Reset/ID
+ *
+ * All values read-only. Writing to this register resets Midway chip.
+ */
+
+#define MID_RES_ID_MCON	0x00		/* Midway Reset/ID */
+
+#define MID_ID		0xf0000000	/* Midway version */
+#define MID_SHIFT	24
+#define MID_MOTHER_ID	0x00000700	/* mother board id */
+#define MID_MOTHER_SHIFT 8
+#define MID_CON_TI	0x00000080	/* 0: normal ctrl; 1: SABRE */
+#define MID_CON_SUNI	0x00000040	/* 0: UTOPIA; 1: SUNI */
+#define MID_CON_V6	0x00000020	/* 0: non-pipel UTOPIA (required iff
+					   !CON_SUNI; 1: UTOPIA */
+#define DAUGTHER_ID	0x0000001f	/* daugther board id */
+
+/*
+ * Interrupt Status Acknowledge, Interrupt Status & Interrupt Enable
+ */
+
+#define MID_ISA		0x01		/* Interrupt Status Acknowledge */
+#define MID_IS		0x02		/* Interrupt Status */
+#define MID_IE		0x03		/* Interrupt Enable */
+
+#define MID_TX_COMPLETE_7 0x00010000	/* channel N completed a PDU */
+#define MID_TX_COMPLETE_6 0x00008000	/* transmission */
+#define MID_TX_COMPLETE_5 0x00004000
+#define MID_TX_COMPLETE_4 0x00002000
+#define MID_TX_COMPLETE_3 0x00001000
+#define MID_TX_COMPLETE_2 0x00000800
+#define MID_TX_COMPLETE_1 0x00000400
+#define MID_TX_COMPLETE_0 0x00000200
+#define MID_TX_COMPLETE	0x0001fe00	/* any TX */
+#define MID_TX_DMA_OVFL	0x00000100	/* DMA to adapter overflow */
+#define MID_TX_IDENT_MISM 0x00000080	/* TX: ident mismatch => halted */
+#define MID_DMA_LERR_ACK 0x00000040	/* LERR - SBus ? */
+#define MID_DMA_ERR_ACK	0x00000020	/* DMA error */
+#define	MID_RX_DMA_COMPLETE 0x00000010	/* DMA to host done */
+#define MID_TX_DMA_COMPLETE 0x00000008	/* DMA from host done */
+#define MID_SERVICE	0x00000004	/* something in service list */
+#define MID_SUNI_INT	0x00000002	/* interrupt from SUNI */
+#define MID_STAT_OVFL	0x00000001	/* statistics overflow */
+
+/*
+ * Master Control/Status
+ */
+
+#define MID_MC_S	0x04
+
+#define MID_INT_SELECT	0x000001C0	/* Interrupt level (000: off) */
+#define MID_INT_SEL_SHIFT 6
+#define	MID_TX_LOCK_MODE 0x00000020	/* 0: streaming; 1: TX ovfl->lock */
+#define MID_DMA_ENABLE	0x00000010	/* R: 0: disable; 1: enable
+					   W: 0: no change; 1: enable */
+#define MID_TX_ENABLE	0x00000008	/* R: 0: TX disabled; 1: enabled
+					   W: 0: no change; 1: enable */
+#define MID_RX_ENABLE	0x00000004	/* like TX */
+#define MID_WAIT_1MS	0x00000002	/* R: 0: timer not running; 1: running
+					   W: 0: no change; 1: no interrupts
+							       for 1 ms */
+#define MID_WAIT_500US	0x00000001	/* like WAIT_1MS, but 0.5 ms */
+
+/*
+ * Statistics
+ *
+ * Cleared when reading.
+ */
+
+#define MID_STAT		0x05
+
+#define MID_VCI_TRASH	0xFFFF0000	/* trashed cells because of VCI mode */
+#define MID_VCI_TRASH_SHIFT 16
+#define MID_OVFL_TRASH	0x0000FFFF	/* trashed cells because of overflow */
+
+/*
+ * Address registers
+ */
+
+#define MID_SERV_WRITE	0x06	/* free pos in service area (R, 10 bits) */
+#define MID_DMA_ADDR	0x07	/* virtual DMA address (R, 32 bits) */
+#define MID_DMA_WR_RX	0x08	/* (RW, 9 bits) */
+#define MID_DMA_RD_RX	0x09
+#define MID_DMA_WR_TX	0x0A
+#define MID_DMA_RD_TX	0x0B
+
+/*
+ * Transmit Place Registers (0x10+4*channel)
+ */
+
+#define MID_TX_PLACE(c)	(0x10+4*(c))
+
+#define MID_SIZE	0x00003800	/* size, N*256 x 32 bit */
+#define MID_SIZE_SHIFT	11
+#define MID_LOCATION	0x000007FF	/* location in adapter memory (word) */
+
+#define MID_LOC_SKIP	8		/* 8 bits of location are always zero
+					   (applies to all uses of location) */
+
+/*
+ * Transmit ReadPtr Registers (0x11+4*channel)
+ */
+
+#define MID_TX_RDPTR(c)	(0x11+4*(c))
+
+#define MID_READ_PTR	0x00007FFF	/* next word for PHY */
+
+/*
+ * Transmit DescrStart Registers (0x12+4*channel)
+ */
+
+#define MID_TX_DESCRSTART(c) (0x12+4*(c))
+
+#define MID_DESCR_START	0x00007FFF	/* seg buffer being DMAed */
+
+#define ENI155_MAGIC	0xa54b872d
+
+struct midway_eprom {
+	unsigned char mac[MAC_LEN],inv_mac[MAC_LEN];
+	unsigned char pad[36];
+	u32 serial,inv_serial;
+	u32 magic,inv_magic;
+};
+
+
+/*
+ * VCI table entry
+ */
+
+#define MID_VCI_IN_SERVICE	0x00000001	/* set if VCI is currently in
+						   service list */
+#define MID_VCI_SIZE		0x00038000	/* reassembly buffer size,
+						   2*<size> kB */
+#define MID_VCI_SIZE_SHIFT	15
+#define MID_VCI_LOCATION	0x1ffc0000	/* buffer location */
+#define MID_VCI_LOCATION_SHIFT	18
+#define MID_VCI_PTI_MODE	0x20000000	/* 0: trash, 1: preserve */
+#define MID_VCI_MODE		0xc0000000
+#define MID_VCI_MODE_SHIFT	30
+#define MID_VCI_READ		0x00007fff
+#define MID_VCI_READ_SHIFT	0
+#define MID_VCI_DESCR		0x7fff0000
+#define MID_VCI_DESCR_SHIFT	16
+#define MID_VCI_COUNT		0x000007ff
+#define MID_VCI_COUNT_SHIFT	0
+#define MID_VCI_STATE		0x0000c000
+#define MID_VCI_STATE_SHIFT	14
+#define MID_VCI_WRITE		0x7fff0000
+#define MID_VCI_WRITE_SHIFT	16
+
+#define MID_MODE_TRASH	0
+#define MID_MODE_RAW	1
+#define MID_MODE_AAL5	2
+
+/*
+ * Reassembly buffer descriptor
+ */
+
+#define MID_RED_COUNT		0x000007ff
+#define MID_RED_CRC_ERR		0x00000800
+#define MID_RED_T		0x00001000
+#define MID_RED_CE		0x00010000
+#define MID_RED_CLP		0x01000000
+#define MID_RED_IDEN		0xfe000000
+#define MID_RED_SHIFT		25
+
+#define MID_RED_RX_ID		0x1b		/* constant identifier */
+
+/*
+ * Segmentation buffer descriptor
+ */
+
+#define MID_SEG_COUNT		MID_RED_COUNT
+#define MID_SEG_RATE		0x01f80000
+#define MID_SEG_RATE_SHIFT	19
+#define MID_SEG_PR		0x06000000
+#define MID_SEG_PR_SHIFT	25
+#define MID_SEG_AAL5		0x08000000
+#define MID_SEG_ID		0xf0000000
+#define MID_SEG_ID_SHIFT	28
+#define MID_SEG_MAX_RATE	63
+
+#define MID_SEG_CLP		0x00000001
+#define MID_SEG_PTI		0x0000000e
+#define MID_SEG_PTI_SHIFT	1
+#define MID_SEG_VCI		0x00003ff0
+#define MID_SEG_VCI_SHIFT	4
+
+#define MID_SEG_TX_ID		0xb		/* constant identifier */
+
+/*
+ * DMA entry
+ */
+
+#define MID_DMA_COUNT		0xffff0000
+#define MID_DMA_COUNT_SHIFT	16
+#define MID_DMA_END		0x00000020
+#define MID_DMA_TYPE		0x0000000f
+
+#define MID_DT_JK	0x3
+#define MID_DT_WORD	0x0
+#define MID_DT_2W	0x7
+#define MID_DT_4W	0x4
+#define MID_DT_8W	0x5
+#define MID_DT_16W	0x6
+#define MID_DT_2WM	0xf
+#define MID_DT_4WM	0xc
+#define MID_DT_8WM	0xd
+#define MID_DT_16WM	0xe
+
+/* only for RX*/
+#define MID_DMA_VCI		0x0000ffc0
+#define	MID_DMA_VCI_SHIFT	6
+
+/* only for TX */
+#define MID_DMA_CHAN		0x000001c0
+#define MID_DMA_CHAN_SHIFT	6
+
+#define MID_DT_BYTE	0x1
+#define MID_DT_HWORD	0x2
+
+#endif
diff --git a/drivers/atm/nicstar.c b/drivers/atm/nicstar.c
new file mode 100644
index 0000000..85bf5c8
--- /dev/null
+++ b/drivers/atm/nicstar.c
@@ -0,0 +1,3105 @@
+/******************************************************************************
+ *
+ * nicstar.c
+ *
+ * Device driver supporting CBR for IDT 77201/77211 "NICStAR" based cards.
+ *
+ * IMPORTANT: The included file nicstarmac.c was NOT WRITTEN BY ME.
+ *            It was taken from the frle-0.22 device driver.
+ *            As the file doesn't have a copyright notice, in the file
+ *            nicstarmac.copyright I put the copyright notice from the
+ *            frle-0.22 device driver.
+ *            Some code is based on the nicstar driver by M. Welsh.
+ *
+ * Author: Rui Prior (rprior@inescn.pt)
+ * PowerPC support by Jay Talbott (jay_talbott@mcg.mot.com) April 1999
+ *
+ *
+ * (C) INESC 1999
+ *
+ *
+ ******************************************************************************/
+
+
+/**** IMPORTANT INFORMATION ***************************************************
+ *
+ * There are currently three types of spinlocks:
+ *
+ * 1 - Per card interrupt spinlock (to protect structures and such)
+ * 2 - Per SCQ scq spinlock
+ * 3 - Per card resource spinlock (to access registers, etc.)
+ *
+ * These must NEVER be grabbed in reverse order.
+ *
+ ******************************************************************************/
+
+/* Header files ***************************************************************/
+
+#include <linux/module.h>
+#include <linux/config.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 <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include "nicstar.h"
+#ifdef CONFIG_ATM_NICSTAR_USE_SUNI
+#include "suni.h"
+#endif /* CONFIG_ATM_NICSTAR_USE_SUNI */
+#ifdef CONFIG_ATM_NICSTAR_USE_IDT77105
+#include "idt77105.h"
+#endif /* CONFIG_ATM_NICSTAR_USE_IDT77105 */
+
+#if BITS_PER_LONG != 32
+#  error FIXME: this driver requires a 32-bit platform
+#endif
+
+/* Additional code ************************************************************/
+
+#include "nicstarmac.c"
+
+
+/* Configurable parameters ****************************************************/
+
+#undef PHY_LOOPBACK
+#undef TX_DEBUG
+#undef RX_DEBUG
+#undef GENERAL_DEBUG
+#undef EXTRA_DEBUG
+
+#undef NS_USE_DESTRUCTORS /* For now keep this undefined unless you know
+                             you're going to use only raw ATM */
+
+
+/* Do not touch these *********************************************************/
+
+#ifdef TX_DEBUG
+#define TXPRINTK(args...) printk(args)
+#else
+#define TXPRINTK(args...)
+#endif /* TX_DEBUG */
+
+#ifdef RX_DEBUG
+#define RXPRINTK(args...) printk(args)
+#else
+#define RXPRINTK(args...)
+#endif /* RX_DEBUG */
+
+#ifdef GENERAL_DEBUG
+#define PRINTK(args...) printk(args)
+#else
+#define PRINTK(args...)
+#endif /* GENERAL_DEBUG */
+
+#ifdef EXTRA_DEBUG
+#define XPRINTK(args...) printk(args)
+#else
+#define XPRINTK(args...)
+#endif /* EXTRA_DEBUG */
+
+
+/* Macros *********************************************************************/
+
+#define CMD_BUSY(card) (readl((card)->membase + STAT) & NS_STAT_CMDBZ)
+
+#define NS_DELAY mdelay(1)
+
+#define ALIGN_BUS_ADDR(addr, alignment) \
+        ((((u32) (addr)) + (((u32) (alignment)) - 1)) & ~(((u32) (alignment)) - 1))
+#define ALIGN_ADDRESS(addr, alignment) \
+        bus_to_virt(ALIGN_BUS_ADDR(virt_to_bus(addr), alignment))
+
+#undef CEIL
+
+#ifndef ATM_SKB
+#define ATM_SKB(s) (&(s)->atm)
+#endif
+
+   /* Spinlock debugging stuff */
+#ifdef NS_DEBUG_SPINLOCKS /* See nicstar.h */
+#define ns_grab_int_lock(card,flags) \
+ do { \
+    unsigned long nsdsf, nsdsf2; \
+    local_irq_save(flags); \
+    save_flags(nsdsf); cli();\
+    if (nsdsf & (1<<9)) printk ("nicstar.c: ints %sabled -> enabled.\n", \
+                                (flags)&(1<<9)?"en":"dis"); \
+    if (spin_is_locked(&(card)->int_lock) && \
+        (card)->cpu_int == smp_processor_id()) { \
+       printk("nicstar.c: line %d (cpu %d) int_lock already locked at line %d (cpu %d)\n", \
+              __LINE__, smp_processor_id(), (card)->has_int_lock, \
+              (card)->cpu_int); \
+       printk("nicstar.c: ints were %sabled.\n", ((flags)&(1<<9)?"en":"dis")); \
+    } \
+    if (spin_is_locked(&(card)->res_lock) && \
+        (card)->cpu_res == smp_processor_id()) { \
+       printk("nicstar.c: line %d (cpu %d) res_lock locked at line %d (cpu %d)(trying int)\n", \
+              __LINE__, smp_processor_id(), (card)->has_res_lock, \
+              (card)->cpu_res); \
+       printk("nicstar.c: ints were %sabled.\n", ((flags)&(1<<9)?"en":"dis")); \
+    } \
+    spin_lock_irq(&(card)->int_lock); \
+    (card)->has_int_lock = __LINE__; \
+    (card)->cpu_int = smp_processor_id(); \
+    restore_flags(nsdsf); } while (0)
+#define ns_grab_res_lock(card,flags) \
+ do { \
+    unsigned long nsdsf, nsdsf2; \
+    local_irq_save(flags); \
+    save_flags(nsdsf); cli();\
+    if (nsdsf & (1<<9)) printk ("nicstar.c: ints %sabled -> enabled.\n", \
+                                (flags)&(1<<9)?"en":"dis"); \
+    if (spin_is_locked(&(card)->res_lock) && \
+        (card)->cpu_res == smp_processor_id()) { \
+       printk("nicstar.c: line %d (cpu %d) res_lock already locked at line %d (cpu %d)\n", \
+              __LINE__, smp_processor_id(), (card)->has_res_lock, \
+              (card)->cpu_res); \
+       printk("nicstar.c: ints were %sabled.\n", ((flags)&(1<<9)?"en":"dis")); \
+    } \
+    spin_lock_irq(&(card)->res_lock); \
+    (card)->has_res_lock = __LINE__; \
+    (card)->cpu_res = smp_processor_id(); \
+    restore_flags(nsdsf); } while (0)
+#define ns_grab_scq_lock(card,scq,flags) \
+ do { \
+    unsigned long nsdsf, nsdsf2; \
+    local_irq_save(flags); \
+    save_flags(nsdsf); cli();\
+    if (nsdsf & (1<<9)) printk ("nicstar.c: ints %sabled -> enabled.\n", \
+                                (flags)&(1<<9)?"en":"dis"); \
+    if (spin_is_locked(&(scq)->lock) && \
+        (scq)->cpu_lock == smp_processor_id()) { \
+       printk("nicstar.c: line %d (cpu %d) this scq_lock already locked at line %d (cpu %d)\n", \
+              __LINE__, smp_processor_id(), (scq)->has_lock, \
+              (scq)->cpu_lock); \
+       printk("nicstar.c: ints were %sabled.\n", ((flags)&(1<<9)?"en":"dis")); \
+    } \
+    if (spin_is_locked(&(card)->res_lock) && \
+        (card)->cpu_res == smp_processor_id()) { \
+       printk("nicstar.c: line %d (cpu %d) res_lock locked at line %d (cpu %d)(trying scq)\n", \
+              __LINE__, smp_processor_id(), (card)->has_res_lock, \
+              (card)->cpu_res); \
+       printk("nicstar.c: ints were %sabled.\n", ((flags)&(1<<9)?"en":"dis")); \
+    } \
+    spin_lock_irq(&(scq)->lock); \
+    (scq)->has_lock = __LINE__; \
+    (scq)->cpu_lock = smp_processor_id(); \
+    restore_flags(nsdsf); } while (0)
+#else /* !NS_DEBUG_SPINLOCKS */
+#define ns_grab_int_lock(card,flags) \
+        spin_lock_irqsave(&(card)->int_lock,(flags))
+#define ns_grab_res_lock(card,flags) \
+        spin_lock_irqsave(&(card)->res_lock,(flags))
+#define ns_grab_scq_lock(card,scq,flags) \
+        spin_lock_irqsave(&(scq)->lock,flags)
+#endif /* NS_DEBUG_SPINLOCKS */
+
+
+/* Function declarations ******************************************************/
+
+static u32 ns_read_sram(ns_dev *card, u32 sram_address);
+static void ns_write_sram(ns_dev *card, u32 sram_address, u32 *value, int count);
+static int __devinit ns_init_card(int i, struct pci_dev *pcidev);
+static void __devinit ns_init_card_error(ns_dev *card, int error);
+static scq_info *get_scq(int size, u32 scd);
+static void free_scq(scq_info *scq, struct atm_vcc *vcc);
+static void push_rxbufs(ns_dev *card, u32 type, u32 handle1, u32 addr1,
+                       u32 handle2, u32 addr2);
+static irqreturn_t ns_irq_handler(int irq, void *dev_id, struct pt_regs *regs);
+static int ns_open(struct atm_vcc *vcc);
+static void ns_close(struct atm_vcc *vcc);
+static void fill_tst(ns_dev *card, int n, vc_map *vc);
+static int ns_send(struct atm_vcc *vcc, struct sk_buff *skb);
+static int push_scqe(ns_dev *card, vc_map *vc, scq_info *scq, ns_scqe *tbd,
+                     struct sk_buff *skb);
+static void process_tsq(ns_dev *card);
+static void drain_scq(ns_dev *card, scq_info *scq, int pos);
+static void process_rsq(ns_dev *card);
+static void dequeue_rx(ns_dev *card, ns_rsqe *rsqe);
+#ifdef NS_USE_DESTRUCTORS
+static void ns_sb_destructor(struct sk_buff *sb);
+static void ns_lb_destructor(struct sk_buff *lb);
+static void ns_hb_destructor(struct sk_buff *hb);
+#endif /* NS_USE_DESTRUCTORS */
+static void recycle_rx_buf(ns_dev *card, struct sk_buff *skb);
+static void recycle_iovec_rx_bufs(ns_dev *card, struct iovec *iov, int count);
+static void recycle_iov_buf(ns_dev *card, struct sk_buff *iovb);
+static void dequeue_sm_buf(ns_dev *card, struct sk_buff *sb);
+static void dequeue_lg_buf(ns_dev *card, struct sk_buff *lb);
+static int ns_proc_read(struct atm_dev *dev, loff_t *pos, char *page);
+static int ns_ioctl(struct atm_dev *dev, unsigned int cmd, void __user *arg);
+static void which_list(ns_dev *card, struct sk_buff *skb);
+static void ns_poll(unsigned long arg);
+static int ns_parse_mac(char *mac, unsigned char *esi);
+static short ns_h2i(char c);
+static void ns_phy_put(struct atm_dev *dev, unsigned char value,
+                       unsigned long addr);
+static unsigned char ns_phy_get(struct atm_dev *dev, unsigned long addr);
+
+
+
+/* Global variables ***********************************************************/
+
+static struct ns_dev *cards[NS_MAX_CARDS];
+static unsigned num_cards;
+static struct atmdev_ops atm_ops =
+{
+   .open	= ns_open,
+   .close	= ns_close,
+   .ioctl	= ns_ioctl,
+   .send	= ns_send,
+   .phy_put	= ns_phy_put,
+   .phy_get	= ns_phy_get,
+   .proc_read	= ns_proc_read,
+   .owner	= THIS_MODULE,
+};
+static struct timer_list ns_timer;
+static char *mac[NS_MAX_CARDS];
+module_param_array(mac, charp, NULL, 0);
+MODULE_LICENSE("GPL");
+
+
+/* Functions*******************************************************************/
+
+static int __devinit nicstar_init_one(struct pci_dev *pcidev,
+				      const struct pci_device_id *ent)
+{
+   static int index = -1;
+   unsigned int error;
+
+   index++;
+   cards[index] = NULL;
+
+   error = ns_init_card(index, pcidev);
+   if (error) {
+      cards[index--] = NULL;	/* don't increment index */
+      goto err_out;
+   }
+
+   return 0;
+err_out:
+   return -ENODEV;
+}
+
+
+
+static void __devexit nicstar_remove_one(struct pci_dev *pcidev)
+{
+   int i, j;
+   ns_dev *card = pci_get_drvdata(pcidev);
+   struct sk_buff *hb;
+   struct sk_buff *iovb;
+   struct sk_buff *lb;
+   struct sk_buff *sb;
+   
+   i = card->index;
+
+   if (cards[i] == NULL)
+      return;
+
+   if (card->atmdev->phy && card->atmdev->phy->stop)
+      card->atmdev->phy->stop(card->atmdev);
+
+   /* Stop everything */
+   writel(0x00000000, card->membase + CFG);
+
+   /* De-register device */
+   atm_dev_deregister(card->atmdev);
+
+   /* Disable PCI device */
+   pci_disable_device(pcidev);
+   
+   /* Free up resources */
+   j = 0;
+   PRINTK("nicstar%d: freeing %d huge buffers.\n", i, card->hbpool.count);
+   while ((hb = skb_dequeue(&card->hbpool.queue)) != NULL)
+   {
+      dev_kfree_skb_any(hb);
+      j++;
+   }
+   PRINTK("nicstar%d: %d huge buffers freed.\n", i, j);
+   j = 0;
+   PRINTK("nicstar%d: freeing %d iovec buffers.\n", i, card->iovpool.count);
+   while ((iovb = skb_dequeue(&card->iovpool.queue)) != NULL)
+   {
+      dev_kfree_skb_any(iovb);
+      j++;
+   }
+   PRINTK("nicstar%d: %d iovec buffers freed.\n", i, j);
+   while ((lb = skb_dequeue(&card->lbpool.queue)) != NULL)
+      dev_kfree_skb_any(lb);
+   while ((sb = skb_dequeue(&card->sbpool.queue)) != NULL)
+      dev_kfree_skb_any(sb);
+   free_scq(card->scq0, NULL);
+   for (j = 0; j < NS_FRSCD_NUM; j++)
+   {
+      if (card->scd2vc[j] != NULL)
+         free_scq(card->scd2vc[j]->scq, card->scd2vc[j]->tx_vcc);
+   }
+   kfree(card->rsq.org);
+   kfree(card->tsq.org);
+   free_irq(card->pcidev->irq, card);
+   iounmap(card->membase);
+   kfree(card);
+}
+
+
+
+static struct pci_device_id nicstar_pci_tbl[] __devinitdata =
+{
+	{PCI_VENDOR_ID_IDT, PCI_DEVICE_ID_IDT_IDT77201,
+	 PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{0,}			/* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, nicstar_pci_tbl);
+
+
+
+static struct pci_driver nicstar_driver = {
+	.name		= "nicstar",
+	.id_table	= nicstar_pci_tbl,
+	.probe		= nicstar_init_one,
+	.remove		= __devexit_p(nicstar_remove_one),
+};
+
+
+
+static int __init nicstar_init(void)
+{
+   unsigned error = 0;	/* Initialized to remove compile warning */
+
+   XPRINTK("nicstar: nicstar_init() called.\n");
+
+   error = pci_register_driver(&nicstar_driver);
+   
+   TXPRINTK("nicstar: TX debug enabled.\n");
+   RXPRINTK("nicstar: RX debug enabled.\n");
+   PRINTK("nicstar: General debug enabled.\n");
+#ifdef PHY_LOOPBACK
+   printk("nicstar: using PHY loopback.\n");
+#endif /* PHY_LOOPBACK */
+   XPRINTK("nicstar: nicstar_init() returned.\n");
+
+   if (!error) {
+      init_timer(&ns_timer);
+      ns_timer.expires = jiffies + NS_POLL_PERIOD;
+      ns_timer.data = 0UL;
+      ns_timer.function = ns_poll;
+      add_timer(&ns_timer);
+   }
+   
+   return error;
+}
+
+
+
+static void __exit nicstar_cleanup(void)
+{
+   XPRINTK("nicstar: nicstar_cleanup() called.\n");
+
+   del_timer(&ns_timer);
+
+   pci_unregister_driver(&nicstar_driver);
+
+   XPRINTK("nicstar: nicstar_cleanup() returned.\n");
+}
+
+
+
+static u32 ns_read_sram(ns_dev *card, u32 sram_address)
+{
+   unsigned long flags;
+   u32 data;
+   sram_address <<= 2;
+   sram_address &= 0x0007FFFC;	/* address must be dword aligned */
+   sram_address |= 0x50000000;	/* SRAM read command */
+   ns_grab_res_lock(card, flags);
+   while (CMD_BUSY(card));
+   writel(sram_address, card->membase + CMD);
+   while (CMD_BUSY(card));
+   data = readl(card->membase + DR0);
+   spin_unlock_irqrestore(&card->res_lock, flags);
+   return data;
+}
+
+
+   
+static void ns_write_sram(ns_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 */
+   ns_grab_res_lock(card, flags);
+   while (CMD_BUSY(card));
+   for (i = 0; i <= c; i += 4)
+      writel(*(value++), card->membase + i);
+   /* Note: DR# registers are the first 4 dwords in nicstar's memspace,
+            so card->membase + DR0 == card->membase */
+   sram_address <<= 2;
+   sram_address &= 0x0007FFFC;
+   sram_address |= (0x40000000 | count);
+   writel(sram_address, card->membase + CMD);
+   spin_unlock_irqrestore(&card->res_lock, flags);
+}
+
+
+static int __devinit ns_init_card(int i, struct pci_dev *pcidev)
+{
+   int j;
+   struct ns_dev *card = NULL;
+   unsigned char pci_latency;
+   unsigned error;
+   u32 data;
+   u32 u32d[4];
+   u32 ns_cfg_rctsize;
+   int bcount;
+   unsigned long membase;
+
+   error = 0;
+
+   if (pci_enable_device(pcidev))
+   {
+      printk("nicstar%d: can't enable PCI device\n", i);
+      error = 2;
+      ns_init_card_error(card, error);
+      return error;
+   }
+
+   if ((card = kmalloc(sizeof(ns_dev), GFP_KERNEL)) == NULL)
+   {
+      printk("nicstar%d: can't allocate memory for device structure.\n", i);
+      error = 2;
+      ns_init_card_error(card, error);
+      return error;
+   }
+   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);
+   card->membase = ioremap(membase, NS_IOREMAP_SIZE);
+   if (card->membase == 0)
+   {
+      printk("nicstar%d: can't ioremap() membase.\n",i);
+      error = 3;
+      ns_init_card_error(card, error);
+      return error;
+   }
+   PRINTK("nicstar%d: membase at 0x%x.\n", i, card->membase);
+
+   pci_set_master(pcidev);
+
+   if (pci_read_config_byte(pcidev, PCI_LATENCY_TIMER, &pci_latency) != 0)
+   {
+      printk("nicstar%d: can't read PCI latency timer.\n", i);
+      error = 6;
+      ns_init_card_error(card, error);
+      return error;
+   }
+#ifdef NS_PCI_LATENCY
+   if (pci_latency < NS_PCI_LATENCY)
+   {
+      PRINTK("nicstar%d: setting PCI latency timer to %d.\n", i, NS_PCI_LATENCY);
+      for (j = 1; j < 4; j++)
+      {
+         if (pci_write_config_byte(pcidev, PCI_LATENCY_TIMER, NS_PCI_LATENCY) != 0)
+	    break;
+      }
+      if (j == 4)
+      {
+         printk("nicstar%d: can't set PCI latency timer to %d.\n", i, NS_PCI_LATENCY);
+         error = 7;
+         ns_init_card_error(card, error);
+	 return error;
+      }
+   }
+#endif /* NS_PCI_LATENCY */
+      
+   /* Clear timer overflow */
+   data = readl(card->membase + STAT);
+   if (data & NS_STAT_TMROF)
+      writel(NS_STAT_TMROF, card->membase + STAT);
+
+   /* Software reset */
+   writel(NS_CFG_SWRST, card->membase + CFG);
+   NS_DELAY;
+   writel(0x00000000, card->membase + CFG);
+
+   /* PHY reset */
+   writel(0x00000008, card->membase + GP);
+   NS_DELAY;
+   writel(0x00000001, card->membase + GP);
+   NS_DELAY;
+   while (CMD_BUSY(card));
+   writel(NS_CMD_WRITE_UTILITY | 0x00000100, card->membase + CMD);	/* Sync UTOPIA with SAR clock */
+   NS_DELAY;
+      
+   /* Detect PHY type */
+   while (CMD_BUSY(card));
+   writel(NS_CMD_READ_UTILITY | 0x00000200, card->membase + CMD);
+   while (CMD_BUSY(card));
+   data = readl(card->membase + DR0);
+   switch(data) {
+      case 0x00000009:
+         printk("nicstar%d: PHY seems to be 25 Mbps.\n", i);
+         card->max_pcr = ATM_25_PCR;
+         while(CMD_BUSY(card));
+         writel(0x00000008, card->membase + DR0);
+         writel(NS_CMD_WRITE_UTILITY | 0x00000200, card->membase + CMD);
+         /* Clear an eventual pending interrupt */
+         writel(NS_STAT_SFBQF, card->membase + STAT);
+#ifdef PHY_LOOPBACK
+         while(CMD_BUSY(card));
+         writel(0x00000022, card->membase + DR0);
+         writel(NS_CMD_WRITE_UTILITY | 0x00000202, card->membase + CMD);
+#endif /* PHY_LOOPBACK */
+	 break;
+      case 0x00000030:
+      case 0x00000031:
+         printk("nicstar%d: PHY seems to be 155 Mbps.\n", i);
+         card->max_pcr = ATM_OC3_PCR;
+#ifdef PHY_LOOPBACK
+         while(CMD_BUSY(card));
+         writel(0x00000002, card->membase + DR0);
+         writel(NS_CMD_WRITE_UTILITY | 0x00000205, card->membase + CMD);
+#endif /* PHY_LOOPBACK */
+	 break;
+      default:
+         printk("nicstar%d: unknown PHY type (0x%08X).\n", i, data);
+         error = 8;
+         ns_init_card_error(card, error);
+         return error;
+   }
+   writel(0x00000000, card->membase + GP);
+
+   /* Determine SRAM size */
+   data = 0x76543210;
+   ns_write_sram(card, 0x1C003, &data, 1);
+   data = 0x89ABCDEF;
+   ns_write_sram(card, 0x14003, &data, 1);
+   if (ns_read_sram(card, 0x14003) == 0x89ABCDEF &&
+       ns_read_sram(card, 0x1C003) == 0x76543210)
+       card->sram_size = 128;
+   else
+      card->sram_size = 32;
+   PRINTK("nicstar%d: %dK x 32bit SRAM size.\n", i, card->sram_size);
+
+   card->rct_size = NS_MAX_RCTSIZE;
+
+#if (NS_MAX_RCTSIZE == 4096)
+   if (card->sram_size == 128)
+      printk("nicstar%d: limiting maximum VCI. See NS_MAX_RCTSIZE in nicstar.h\n", i);
+#elif (NS_MAX_RCTSIZE == 16384)
+   if (card->sram_size == 32)
+   {
+      printk("nicstar%d: wasting memory. See NS_MAX_RCTSIZE in nicstar.h\n", i);
+      card->rct_size = 4096;
+   }
+#else
+#error NS_MAX_RCTSIZE must be either 4096 or 16384 in nicstar.c
+#endif
+
+   card->vpibits = NS_VPIBITS;
+   if (card->rct_size == 4096)
+      card->vcibits = 12 - NS_VPIBITS;
+   else /* card->rct_size == 16384 */
+      card->vcibits = 14 - NS_VPIBITS;
+
+   /* Initialize the nicstar eeprom/eprom stuff, for the MAC addr */
+   if (mac[i] == NULL)
+      nicstar_init_eprom(card->membase);
+
+   if (request_irq(pcidev->irq, &ns_irq_handler, SA_INTERRUPT | SA_SHIRQ, "nicstar", card) != 0)
+   {
+      printk("nicstar%d: can't allocate IRQ %d.\n", i, pcidev->irq);
+      error = 9;
+      ns_init_card_error(card, error);
+      return error;
+   }
+
+   /* Set the VPI/VCI MSb mask to zero so we can receive OAM cells */
+   writel(0x00000000, card->membase + VPM);
+      
+   /* Initialize TSQ */
+   card->tsq.org = kmalloc(NS_TSQSIZE + NS_TSQ_ALIGNMENT, GFP_KERNEL);
+   if (card->tsq.org == NULL)
+   {
+      printk("nicstar%d: can't allocate TSQ.\n", i);
+      error = 10;
+      ns_init_card_error(card, error);
+      return error;
+   }
+   card->tsq.base = (ns_tsi *) ALIGN_ADDRESS(card->tsq.org, NS_TSQ_ALIGNMENT);
+   card->tsq.next = card->tsq.base;
+   card->tsq.last = card->tsq.base + (NS_TSQ_NUM_ENTRIES - 1);
+   for (j = 0; j < NS_TSQ_NUM_ENTRIES; j++)
+      ns_tsi_init(card->tsq.base + j);
+   writel(0x00000000, card->membase + TSQH);
+   writel((u32) virt_to_bus(card->tsq.base), card->membase + TSQB);
+   PRINTK("nicstar%d: TSQ base at 0x%x  0x%x  0x%x.\n", i, (u32) card->tsq.base,
+          (u32) virt_to_bus(card->tsq.base), readl(card->membase + TSQB));
+      
+   /* Initialize RSQ */
+   card->rsq.org = kmalloc(NS_RSQSIZE + NS_RSQ_ALIGNMENT, GFP_KERNEL);
+   if (card->rsq.org == NULL)
+   {
+      printk("nicstar%d: can't allocate RSQ.\n", i);
+      error = 11;
+      ns_init_card_error(card, error);
+      return error;
+   }
+   card->rsq.base = (ns_rsqe *) ALIGN_ADDRESS(card->rsq.org, NS_RSQ_ALIGNMENT);
+   card->rsq.next = card->rsq.base;
+   card->rsq.last = card->rsq.base + (NS_RSQ_NUM_ENTRIES - 1);
+   for (j = 0; j < NS_RSQ_NUM_ENTRIES; j++)
+      ns_rsqe_init(card->rsq.base + j);
+   writel(0x00000000, card->membase + RSQH);
+   writel((u32) virt_to_bus(card->rsq.base), card->membase + RSQB);
+   PRINTK("nicstar%d: RSQ base at 0x%x.\n", i, (u32) card->rsq.base);
+      
+   /* Initialize SCQ0, the only VBR SCQ used */
+   card->scq1 = (scq_info *) NULL;
+   card->scq2 = (scq_info *) NULL;
+   card->scq0 = get_scq(VBR_SCQSIZE, NS_VRSCD0);
+   if (card->scq0 == (scq_info *) NULL)
+   {
+      printk("nicstar%d: can't get SCQ0.\n", i);
+      error = 12;
+      ns_init_card_error(card, error);
+      return error;
+   }
+   u32d[0] = (u32) virt_to_bus(card->scq0->base);
+   u32d[1] = (u32) 0x00000000;
+   u32d[2] = (u32) 0xffffffff;
+   u32d[3] = (u32) 0x00000000;
+   ns_write_sram(card, NS_VRSCD0, u32d, 4);
+   ns_write_sram(card, NS_VRSCD1, u32d, 4);	/* These last two won't be used */
+   ns_write_sram(card, NS_VRSCD2, u32d, 4);	/* but are initialized, just in case... */
+   card->scq0->scd = NS_VRSCD0;
+   PRINTK("nicstar%d: VBR-SCQ0 base at 0x%x.\n", i, (u32) card->scq0->base);
+
+   /* Initialize TSTs */
+   card->tst_addr = NS_TST0;
+   card->tst_free_entries = NS_TST_NUM_ENTRIES;
+   data = NS_TST_OPCODE_VARIABLE;
+   for (j = 0; j < NS_TST_NUM_ENTRIES; j++)
+      ns_write_sram(card, NS_TST0 + j, &data, 1);
+   data = ns_tste_make(NS_TST_OPCODE_END, NS_TST0);
+   ns_write_sram(card, NS_TST0 + NS_TST_NUM_ENTRIES, &data, 1);
+   for (j = 0; j < NS_TST_NUM_ENTRIES; j++)
+      ns_write_sram(card, NS_TST1 + j, &data, 1);
+   data = ns_tste_make(NS_TST_OPCODE_END, NS_TST1);
+   ns_write_sram(card, NS_TST1 + NS_TST_NUM_ENTRIES, &data, 1);
+   for (j = 0; j < NS_TST_NUM_ENTRIES; j++)
+      card->tste2vc[j] = NULL;
+   writel(NS_TST0 << 2, card->membase + TSTB);
+
+
+   /* Initialize RCT. AAL type is set on opening the VC. */
+#ifdef RCQ_SUPPORT
+   u32d[0] = NS_RCTE_RAWCELLINTEN;
+#else
+   u32d[0] = 0x00000000;
+#endif /* RCQ_SUPPORT */
+   u32d[1] = 0x00000000;
+   u32d[2] = 0x00000000;
+   u32d[3] = 0xFFFFFFFF;
+   for (j = 0; j < card->rct_size; j++)
+      ns_write_sram(card, j * 4, u32d, 4);      
+      
+   memset(card->vcmap, 0, NS_MAX_RCTSIZE * sizeof(vc_map));
+      
+   for (j = 0; j < NS_FRSCD_NUM; j++)
+      card->scd2vc[j] = NULL;
+
+   /* Initialize buffer levels */
+   card->sbnr.min = MIN_SB;
+   card->sbnr.init = NUM_SB;
+   card->sbnr.max = MAX_SB;
+   card->lbnr.min = MIN_LB;
+   card->lbnr.init = NUM_LB;
+   card->lbnr.max = MAX_LB;
+   card->iovnr.min = MIN_IOVB;
+   card->iovnr.init = NUM_IOVB;
+   card->iovnr.max = MAX_IOVB;
+   card->hbnr.min = MIN_HB;
+   card->hbnr.init = NUM_HB;
+   card->hbnr.max = MAX_HB;
+   
+   card->sm_handle = 0x00000000;
+   card->sm_addr = 0x00000000;
+   card->lg_handle = 0x00000000;
+   card->lg_addr = 0x00000000;
+   
+   card->efbie = 1;	/* To prevent push_rxbufs from enabling the interrupt */
+
+   /* Pre-allocate some huge buffers */
+   skb_queue_head_init(&card->hbpool.queue);
+   card->hbpool.count = 0;
+   for (j = 0; j < NUM_HB; j++)
+   {
+      struct sk_buff *hb;
+      hb = __dev_alloc_skb(NS_HBUFSIZE, GFP_KERNEL);
+      if (hb == NULL)
+      {
+         printk("nicstar%d: can't allocate %dth of %d huge buffers.\n",
+                i, j, NUM_HB);
+         error = 13;
+         ns_init_card_error(card, error);
+	 return error;
+      }
+      skb_queue_tail(&card->hbpool.queue, hb);
+      card->hbpool.count++;
+   }
+
+
+   /* Allocate large buffers */
+   skb_queue_head_init(&card->lbpool.queue);
+   card->lbpool.count = 0;			/* Not used */
+   for (j = 0; j < NUM_LB; j++)
+   {
+      struct sk_buff *lb;
+      lb = __dev_alloc_skb(NS_LGSKBSIZE, GFP_KERNEL);
+      if (lb == NULL)
+      {
+         printk("nicstar%d: can't allocate %dth of %d large buffers.\n",
+                i, j, NUM_LB);
+         error = 14;
+         ns_init_card_error(card, error);
+	 return error;
+      }
+      skb_queue_tail(&card->lbpool.queue, lb);
+      skb_reserve(lb, NS_SMBUFSIZE);
+      push_rxbufs(card, BUF_LG, (u32) lb, (u32) virt_to_bus(lb->data), 0, 0);
+      /* Due to the implementation of push_rxbufs() this is 1, not 0 */
+      if (j == 1)
+      {
+         card->rcbuf = lb;
+         card->rawch = (u32) virt_to_bus(lb->data);
+      }
+   }
+   /* Test for strange behaviour which leads to crashes */
+   if ((bcount = ns_stat_lfbqc_get(readl(card->membase + STAT))) < card->lbnr.min)
+   {
+      printk("nicstar%d: Strange... Just allocated %d large buffers and lfbqc = %d.\n",
+             i, j, bcount);
+      error = 14;
+      ns_init_card_error(card, error);
+      return error;
+   }
+      
+
+   /* Allocate small buffers */
+   skb_queue_head_init(&card->sbpool.queue);
+   card->sbpool.count = 0;			/* Not used */
+   for (j = 0; j < NUM_SB; j++)
+   {
+      struct sk_buff *sb;
+      sb = __dev_alloc_skb(NS_SMSKBSIZE, GFP_KERNEL);
+      if (sb == NULL)
+      {
+         printk("nicstar%d: can't allocate %dth of %d small buffers.\n",
+                i, j, NUM_SB);
+         error = 15;
+         ns_init_card_error(card, error);
+	 return error;
+      }
+      skb_queue_tail(&card->sbpool.queue, sb);
+      skb_reserve(sb, NS_AAL0_HEADER);
+      push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data), 0, 0);
+   }
+   /* Test for strange behaviour which leads to crashes */
+   if ((bcount = ns_stat_sfbqc_get(readl(card->membase + STAT))) < card->sbnr.min)
+   {
+      printk("nicstar%d: Strange... Just allocated %d small buffers and sfbqc = %d.\n",
+             i, j, bcount);
+      error = 15;
+      ns_init_card_error(card, error);
+      return error;
+   }
+      
+
+   /* Allocate iovec buffers */
+   skb_queue_head_init(&card->iovpool.queue);
+   card->iovpool.count = 0;
+   for (j = 0; j < NUM_IOVB; j++)
+   {
+      struct sk_buff *iovb;
+      iovb = alloc_skb(NS_IOVBUFSIZE, GFP_KERNEL);
+      if (iovb == NULL)
+      {
+         printk("nicstar%d: can't allocate %dth of %d iovec buffers.\n",
+                i, j, NUM_IOVB);
+         error = 16;
+         ns_init_card_error(card, error);
+	 return error;
+      }
+      skb_queue_tail(&card->iovpool.queue, iovb);
+      card->iovpool.count++;
+   }
+
+   card->intcnt = 0;
+
+   /* Configure NICStAR */
+   if (card->rct_size == 4096)
+      ns_cfg_rctsize = NS_CFG_RCTSIZE_4096_ENTRIES;
+   else /* (card->rct_size == 16384) */
+      ns_cfg_rctsize = NS_CFG_RCTSIZE_16384_ENTRIES;
+
+   card->efbie = 1;
+
+   /* Register device */
+   card->atmdev = atm_dev_register("nicstar", &atm_ops, -1, NULL);
+   if (card->atmdev == NULL)
+   {
+      printk("nicstar%d: can't register device.\n", i);
+      error = 17;
+      ns_init_card_error(card, error);
+      return error;
+   }
+      
+   if (ns_parse_mac(mac[i], card->atmdev->esi)) {
+      nicstar_read_eprom(card->membase, NICSTAR_EPROM_MAC_ADDR_OFFSET,
+                         card->atmdev->esi, 6);
+      if (memcmp(card->atmdev->esi, "\x00\x00\x00\x00\x00\x00", 6) == 0) {
+         nicstar_read_eprom(card->membase, NICSTAR_EPROM_MAC_ADDR_OFFSET_ALT,
+                         card->atmdev->esi, 6);
+      }
+   }
+
+   printk("nicstar%d: MAC address %02X:%02X:%02X:%02X:%02X:%02X\n", i,
+          card->atmdev->esi[0], card->atmdev->esi[1], card->atmdev->esi[2],
+          card->atmdev->esi[3], card->atmdev->esi[4], card->atmdev->esi[5]);
+
+   card->atmdev->dev_data = card;
+   card->atmdev->ci_range.vpi_bits = card->vpibits;
+   card->atmdev->ci_range.vci_bits = card->vcibits;
+   card->atmdev->link_rate = card->max_pcr;
+   card->atmdev->phy = NULL;
+
+#ifdef CONFIG_ATM_NICSTAR_USE_SUNI
+   if (card->max_pcr == ATM_OC3_PCR)
+      suni_init(card->atmdev);
+#endif /* CONFIG_ATM_NICSTAR_USE_SUNI */
+
+#ifdef CONFIG_ATM_NICSTAR_USE_IDT77105
+   if (card->max_pcr == ATM_25_PCR)
+      idt77105_init(card->atmdev);
+#endif /* CONFIG_ATM_NICSTAR_USE_IDT77105 */
+
+   if (card->atmdev->phy && card->atmdev->phy->start)
+      card->atmdev->phy->start(card->atmdev);
+
+   writel(NS_CFG_RXPATH |
+          NS_CFG_SMBUFSIZE |
+          NS_CFG_LGBUFSIZE |
+          NS_CFG_EFBIE |
+          NS_CFG_RSQSIZE |
+          NS_CFG_VPIBITS |
+          ns_cfg_rctsize |
+          NS_CFG_RXINT_NODELAY |
+          NS_CFG_RAWIE |		/* Only enabled if RCQ_SUPPORT */
+          NS_CFG_RSQAFIE |
+          NS_CFG_TXEN |
+          NS_CFG_TXIE |
+          NS_CFG_TSQFIE_OPT |		/* Only enabled if ENABLE_TSQFIE */ 
+          NS_CFG_PHYIE,
+          card->membase + CFG);
+
+   num_cards++;
+
+   return error;
+}
+
+
+
+static void __devinit ns_init_card_error(ns_dev *card, int error)
+{
+   if (error >= 17)
+   {
+      writel(0x00000000, card->membase + CFG);
+   }
+   if (error >= 16)
+   {
+      struct sk_buff *iovb;
+      while ((iovb = skb_dequeue(&card->iovpool.queue)) != NULL)
+         dev_kfree_skb_any(iovb);
+   }
+   if (error >= 15)
+   {
+      struct sk_buff *sb;
+      while ((sb = skb_dequeue(&card->sbpool.queue)) != NULL)
+         dev_kfree_skb_any(sb);
+      free_scq(card->scq0, NULL);
+   }
+   if (error >= 14)
+   {
+      struct sk_buff *lb;
+      while ((lb = skb_dequeue(&card->lbpool.queue)) != NULL)
+         dev_kfree_skb_any(lb);
+   }
+   if (error >= 13)
+   {
+      struct sk_buff *hb;
+      while ((hb = skb_dequeue(&card->hbpool.queue)) != NULL)
+         dev_kfree_skb_any(hb);
+   }
+   if (error >= 12)
+   {
+      kfree(card->rsq.org);
+   }
+   if (error >= 11)
+   {
+      kfree(card->tsq.org);
+   }
+   if (error >= 10)
+   {
+      free_irq(card->pcidev->irq, card);
+   }
+   if (error >= 4)
+   {
+      iounmap(card->membase);
+   }
+   if (error >= 3)
+   {
+      pci_disable_device(card->pcidev);
+      kfree(card);
+   }
+}
+
+
+
+static scq_info *get_scq(int size, u32 scd)
+{
+   scq_info *scq;
+   int i;
+
+   if (size != VBR_SCQSIZE && size != CBR_SCQSIZE)
+      return (scq_info *) NULL;
+
+   scq = (scq_info *) kmalloc(sizeof(scq_info), GFP_KERNEL);
+   if (scq == (scq_info *) NULL)
+      return (scq_info *) NULL;
+   scq->org = kmalloc(2 * size, GFP_KERNEL);
+   if (scq->org == NULL)
+   {
+      kfree(scq);
+      return (scq_info *) NULL;
+   }
+   scq->skb = (struct sk_buff **) kmalloc(sizeof(struct sk_buff *) *
+                                          (size / NS_SCQE_SIZE), GFP_KERNEL);
+   if (scq->skb == (struct sk_buff **) NULL)
+   {
+      kfree(scq->org);
+      kfree(scq);
+      return (scq_info *) NULL;
+   }
+   scq->num_entries = size / NS_SCQE_SIZE;
+   scq->base = (ns_scqe *) ALIGN_ADDRESS(scq->org, size);
+   scq->next = scq->base;
+   scq->last = scq->base + (scq->num_entries - 1);
+   scq->tail = scq->last;
+   scq->scd = scd;
+   scq->num_entries = size / NS_SCQE_SIZE;
+   scq->tbd_count = 0;
+   init_waitqueue_head(&scq->scqfull_waitq);
+   scq->full = 0;
+   spin_lock_init(&scq->lock);
+
+   for (i = 0; i < scq->num_entries; i++)
+      scq->skb[i] = NULL;
+
+   return scq;
+}
+
+
+
+/* For variable rate SCQ vcc must be NULL */
+static void free_scq(scq_info *scq, struct atm_vcc *vcc)
+{
+   int i;
+
+   if (scq->num_entries == VBR_SCQ_NUM_ENTRIES)
+      for (i = 0; i < scq->num_entries; i++)
+      {
+         if (scq->skb[i] != NULL)
+	 {
+            vcc = ATM_SKB(scq->skb[i])->vcc;
+            if (vcc->pop != NULL)
+	       vcc->pop(vcc, scq->skb[i]);
+	    else
+               dev_kfree_skb_any(scq->skb[i]);
+         }
+      }
+   else /* vcc must be != NULL */
+   {
+      if (vcc == NULL)
+      {
+         printk("nicstar: free_scq() called with vcc == NULL for fixed rate scq.");
+         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)
+                  vcc->pop(vcc, scq->skb[i]);
+               else
+                  dev_kfree_skb_any(scq->skb[i]);
+            }
+         }
+   }
+   kfree(scq->skb);
+   kfree(scq->org);
+   kfree(scq);
+}
+
+
+
+/* The handles passed must be pointers to the sk_buff containing the small
+   or large buffer(s) cast to u32. */
+static void push_rxbufs(ns_dev *card, u32 type, u32 handle1, u32 addr1,
+                       u32 handle2, u32 addr2)
+{
+   u32 stat;
+   unsigned long flags;
+   
+
+#ifdef GENERAL_DEBUG
+   if (!addr1)
+      printk("nicstar%d: push_rxbufs called with addr1 = 0.\n", card->index);
+#endif /* GENERAL_DEBUG */
+
+   stat = readl(card->membase + STAT);
+   card->sbfqc = ns_stat_sfbqc_get(stat);
+   card->lbfqc = ns_stat_lfbqc_get(stat);
+   if (type == BUF_SM)
+   {
+      if (!addr2)
+      {
+         if (card->sm_addr)
+	 {
+	    addr2 = card->sm_addr;
+	    handle2 = card->sm_handle;
+	    card->sm_addr = 0x00000000;
+	    card->sm_handle = 0x00000000;
+	 }
+	 else /* (!sm_addr) */
+	 {
+	    card->sm_addr = addr1;
+	    card->sm_handle = handle1;
+	 }
+      }      
+   }
+   else /* type == BUF_LG */
+   {
+      if (!addr2)
+      {
+         if (card->lg_addr)
+	 {
+	    addr2 = card->lg_addr;
+	    handle2 = card->lg_handle;
+	    card->lg_addr = 0x00000000;
+	    card->lg_handle = 0x00000000;
+	 }
+	 else /* (!lg_addr) */
+	 {
+	    card->lg_addr = addr1;
+	    card->lg_handle = handle1;
+	 }
+      }      
+   }
+
+   if (addr2)
+   {
+      if (type == BUF_SM)
+      {
+         if (card->sbfqc >= card->sbnr.max)
+         {
+            skb_unlink((struct sk_buff *) handle1);
+            dev_kfree_skb_any((struct sk_buff *) handle1);
+            skb_unlink((struct sk_buff *) handle2);
+            dev_kfree_skb_any((struct sk_buff *) handle2);
+            return;
+         }
+	 else
+            card->sbfqc += 2;
+      }
+      else /* (type == BUF_LG) */
+      {
+         if (card->lbfqc >= card->lbnr.max)
+         {
+            skb_unlink((struct sk_buff *) handle1);
+            dev_kfree_skb_any((struct sk_buff *) handle1);
+            skb_unlink((struct sk_buff *) handle2);
+            dev_kfree_skb_any((struct sk_buff *) handle2);
+            return;
+         }
+         else
+            card->lbfqc += 2;
+      }
+
+      ns_grab_res_lock(card, flags);
+
+      while (CMD_BUSY(card));
+      writel(addr2, card->membase + DR3);
+      writel(handle2, card->membase + DR2);
+      writel(addr1, card->membase + DR1);
+      writel(handle1, card->membase + DR0);
+      writel(NS_CMD_WRITE_FREEBUFQ | (u32) type, card->membase + CMD);
+ 
+      spin_unlock_irqrestore(&card->res_lock, flags);
+
+      XPRINTK("nicstar%d: Pushing %s buffers at 0x%x and 0x%x.\n", card->index,
+              (type == BUF_SM ? "small" : "large"), addr1, addr2);
+   }
+
+   if (!card->efbie && card->sbfqc >= card->sbnr.min &&
+       card->lbfqc >= card->lbnr.min)
+   {
+      card->efbie = 1;
+      writel((readl(card->membase + CFG) | NS_CFG_EFBIE), card->membase + CFG);
+   }
+
+   return;
+}
+
+
+
+static irqreturn_t ns_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+   u32 stat_r;
+   ns_dev *card;
+   struct atm_dev *dev;
+   unsigned long flags;
+
+   card = (ns_dev *) dev_id;
+   dev = card->atmdev;
+   card->intcnt++;
+
+   PRINTK("nicstar%d: NICStAR generated an interrupt\n", card->index);
+
+   ns_grab_int_lock(card, flags);
+   
+   stat_r = readl(card->membase + STAT);
+
+   /* Transmit Status Indicator has been written to T. S. Queue */
+   if (stat_r & NS_STAT_TSIF)
+   {
+      TXPRINTK("nicstar%d: TSI interrupt\n", card->index);
+      process_tsq(card);
+      writel(NS_STAT_TSIF, card->membase + STAT);
+   }
+   
+   /* Incomplete CS-PDU has been transmitted */
+   if (stat_r & NS_STAT_TXICP)
+   {
+      writel(NS_STAT_TXICP, card->membase + STAT);
+      TXPRINTK("nicstar%d: Incomplete CS-PDU transmitted.\n",
+               card->index);
+   }
+   
+   /* Transmit Status Queue 7/8 full */
+   if (stat_r & NS_STAT_TSQF)
+   {
+      writel(NS_STAT_TSQF, card->membase + STAT);
+      PRINTK("nicstar%d: TSQ full.\n", card->index);
+      process_tsq(card);
+   }
+   
+   /* Timer overflow */
+   if (stat_r & NS_STAT_TMROF)
+   {
+      writel(NS_STAT_TMROF, card->membase + STAT);
+      PRINTK("nicstar%d: Timer overflow.\n", card->index);
+   }
+   
+   /* PHY device interrupt signal active */
+   if (stat_r & NS_STAT_PHYI)
+   {
+      writel(NS_STAT_PHYI, card->membase + STAT);
+      PRINTK("nicstar%d: PHY interrupt.\n", card->index);
+      if (dev->phy && dev->phy->interrupt) {
+         dev->phy->interrupt(dev);
+      }
+   }
+
+   /* Small Buffer Queue is full */
+   if (stat_r & NS_STAT_SFBQF)
+   {
+      writel(NS_STAT_SFBQF, card->membase + STAT);
+      printk("nicstar%d: Small free buffer queue is full.\n", card->index);
+   }
+   
+   /* Large Buffer Queue is full */
+   if (stat_r & NS_STAT_LFBQF)
+   {
+      writel(NS_STAT_LFBQF, card->membase + STAT);
+      printk("nicstar%d: Large free buffer queue is full.\n", card->index);
+   }
+
+   /* Receive Status Queue is full */
+   if (stat_r & NS_STAT_RSQF)
+   {
+      writel(NS_STAT_RSQF, card->membase + STAT);
+      printk("nicstar%d: RSQ full.\n", card->index);
+      process_rsq(card);
+   }
+
+   /* Complete CS-PDU received */
+   if (stat_r & NS_STAT_EOPDU)
+   {
+      RXPRINTK("nicstar%d: End of CS-PDU received.\n", card->index);
+      process_rsq(card);
+      writel(NS_STAT_EOPDU, card->membase + STAT);
+   }
+
+   /* Raw cell received */
+   if (stat_r & NS_STAT_RAWCF)
+   {
+      writel(NS_STAT_RAWCF, card->membase + STAT);
+#ifndef RCQ_SUPPORT
+      printk("nicstar%d: Raw cell received and no support yet...\n",
+             card->index);
+#endif /* RCQ_SUPPORT */
+      /* NOTE: the following procedure may keep a raw cell pending until the
+               next interrupt. As this preliminary support is only meant to
+               avoid buffer leakage, this is not an issue. */
+      while (readl(card->membase + RAWCT) != card->rawch)
+      {
+         ns_rcqe *rawcell;
+
+         rawcell = (ns_rcqe *) bus_to_virt(card->rawch);
+         if (ns_rcqe_islast(rawcell))
+         {
+            struct sk_buff *oldbuf;
+
+            oldbuf = card->rcbuf;
+            card->rcbuf = (struct sk_buff *) ns_rcqe_nextbufhandle(rawcell);
+            card->rawch = (u32) virt_to_bus(card->rcbuf->data);
+            recycle_rx_buf(card, oldbuf);
+         }
+         else
+            card->rawch += NS_RCQE_SIZE;
+      }
+   }
+
+   /* Small buffer queue is empty */
+   if (stat_r & NS_STAT_SFBQE)
+   {
+      int i;
+      struct sk_buff *sb;
+
+      writel(NS_STAT_SFBQE, card->membase + STAT);
+      printk("nicstar%d: Small free buffer queue empty.\n",
+             card->index);
+      for (i = 0; i < card->sbnr.min; i++)
+      {
+         sb = dev_alloc_skb(NS_SMSKBSIZE);
+         if (sb == NULL)
+         {
+            writel(readl(card->membase + CFG) & ~NS_CFG_EFBIE, card->membase + CFG);
+            card->efbie = 0;
+            break;
+         }
+         skb_queue_tail(&card->sbpool.queue, sb);
+         skb_reserve(sb, NS_AAL0_HEADER);
+         push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data), 0, 0);
+      }
+      card->sbfqc = i;
+      process_rsq(card);
+   }
+
+   /* Large buffer queue empty */
+   if (stat_r & NS_STAT_LFBQE)
+   {
+      int i;
+      struct sk_buff *lb;
+
+      writel(NS_STAT_LFBQE, card->membase + STAT);
+      printk("nicstar%d: Large free buffer queue empty.\n",
+             card->index);
+      for (i = 0; i < card->lbnr.min; i++)
+      {
+         lb = dev_alloc_skb(NS_LGSKBSIZE);
+         if (lb == NULL)
+         {
+            writel(readl(card->membase + CFG) & ~NS_CFG_EFBIE, card->membase + CFG);
+            card->efbie = 0;
+            break;
+         }
+         skb_queue_tail(&card->lbpool.queue, lb);
+         skb_reserve(lb, NS_SMBUFSIZE);
+         push_rxbufs(card, BUF_LG, (u32) lb, (u32) virt_to_bus(lb->data), 0, 0);
+      }
+      card->lbfqc = i;
+      process_rsq(card);
+   }
+
+   /* Receive Status Queue is 7/8 full */
+   if (stat_r & NS_STAT_RSQAF)
+   {
+      writel(NS_STAT_RSQAF, card->membase + STAT);
+      RXPRINTK("nicstar%d: RSQ almost full.\n", card->index);
+      process_rsq(card);
+   }
+   
+   spin_unlock_irqrestore(&card->int_lock, flags);
+   PRINTK("nicstar%d: end of interrupt service\n", card->index);
+   return IRQ_HANDLED;
+}
+
+
+
+static int ns_open(struct atm_vcc *vcc)
+{
+   ns_dev *card;
+   vc_map *vc;
+   unsigned long tmpl, modl;
+   int tcr, tcra;	/* target cell rate, and absolute value */
+   int n = 0;		/* Number of entries in the TST. Initialized to remove
+                           the compiler warning. */
+   u32 u32d[4];
+   int frscdi = 0;	/* Index of the SCD. Initialized to remove the compiler
+                           warning. How I wish compilers were clever enough to
+			   tell which variables can truly be used
+			   uninitialized... */
+   int inuse;		/* tx or rx vc already in use by another vcc */
+   short vpi = vcc->vpi;
+   int vci = vcc->vci;
+
+   card = (ns_dev *) vcc->dev->dev_data;
+   PRINTK("nicstar%d: opening vpi.vci %d.%d \n", card->index, (int) vpi, vci);
+   if (vcc->qos.aal != ATM_AAL5 && vcc->qos.aal != ATM_AAL0)
+   {
+      PRINTK("nicstar%d: unsupported AAL.\n", card->index);
+      return -EINVAL;
+   }
+
+   vc = &(card->vcmap[vpi << card->vcibits | vci]);
+   vcc->dev_data = vc;
+
+   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)
+   {
+      printk("nicstar%d: %s vci already in use.\n", card->index,
+             inuse == 1 ? "tx" : inuse == 2 ? "rx" : "tx and rx");
+      return -EINVAL;
+   }
+
+   set_bit(ATM_VF_ADDR,&vcc->flags);
+
+   /* 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. */
+   if (!test_bit(ATM_VF_PARTIAL,&vcc->flags))
+   {
+      scq_info *scq;
+      
+      set_bit(ATM_VF_PARTIAL,&vcc->flags);
+      if (vcc->qos.txtp.traffic_class == ATM_CBR)
+      {
+         /* 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)
+         {
+            PRINTK("nicstar%d: trying to open a CBR vc with cell rate = 0 \n",
+	           card->index);
+	    clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+	    clear_bit(ATM_VF_ADDR,&vcc->flags);
+            return -EINVAL;
+         }
+
+         tcr = atm_pcr_goal(&(vcc->qos.txtp));
+         tcra = tcr >= 0 ? tcr : -tcr;
+      
+         PRINTK("nicstar%d: target cell rate = %d.\n", card->index,
+                vcc->qos.txtp.max_pcr);
+
+         tmpl = (unsigned long)tcra * (unsigned long)NS_TST_NUM_ENTRIES;
+         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 - NS_TST_RESERVED)) <= 0)
+	    {
+               PRINTK("nicstar%d: no CBR bandwidth free.\n", card->index);
+	       clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+	       clear_bit(ATM_VF_ADDR,&vcc->flags);
+               return -EINVAL;
+            }
+         }
+
+         if (n == 0)
+         {
+            printk("nicstar%d: selected bandwidth < granularity.\n", card->index);
+	    clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+	    clear_bit(ATM_VF_ADDR,&vcc->flags);
+            return -EINVAL;
+         }
+
+         if (n > (card->tst_free_entries - NS_TST_RESERVED))
+         {
+            PRINTK("nicstar%d: not enough free CBR bandwidth.\n", card->index);
+	    clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+	    clear_bit(ATM_VF_ADDR,&vcc->flags);
+            return -EINVAL;
+         }
+         else
+            card->tst_free_entries -= n;
+
+         XPRINTK("nicstar%d: writing %d tst entries.\n", card->index, n);
+         for (frscdi = 0; frscdi < NS_FRSCD_NUM; frscdi++)
+         {
+            if (card->scd2vc[frscdi] == NULL)
+            {
+               card->scd2vc[frscdi] = vc;
+               break;
+	    }
+         }
+         if (frscdi == NS_FRSCD_NUM)
+         {
+            PRINTK("nicstar%d: no SCD available for CBR channel.\n", card->index);
+            card->tst_free_entries += n;
+	    clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+	    clear_bit(ATM_VF_ADDR,&vcc->flags);
+	    return -EBUSY;
+         }
+
+         vc->cbr_scd = NS_FRSCD + frscdi * NS_FRSCD_SIZE;
+
+         scq = get_scq(CBR_SCQSIZE, vc->cbr_scd);
+         if (scq == (scq_info *) NULL)
+         {
+            PRINTK("nicstar%d: can't get fixed rate SCQ.\n", card->index);
+            card->scd2vc[frscdi] = NULL;
+            card->tst_free_entries += n;
+	    clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+	    clear_bit(ATM_VF_ADDR,&vcc->flags);
+            return -ENOMEM;
+         }
+	 vc->scq = scq;
+         u32d[0] = (u32) virt_to_bus(scq->base);
+         u32d[1] = (u32) 0x00000000;
+         u32d[2] = (u32) 0xffffffff;
+         u32d[3] = (u32) 0x00000000;
+         ns_write_sram(card, vc->cbr_scd, u32d, 4);
+         
+	 fill_tst(card, n, vc);
+      }
+      else if (vcc->qos.txtp.traffic_class == ATM_UBR)
+      {
+         vc->cbr_scd = 0x00000000;
+	 vc->scq = card->scq0;
+      }
+      
+      if (vcc->qos.txtp.traffic_class != ATM_NONE)
+      {
+         vc->tx = 1;
+	 vc->tx_vcc = vcc;
+	 vc->tbd_count = 0;
+      }
+      if (vcc->qos.rxtp.traffic_class != ATM_NONE)
+      {
+         u32 status;
+      
+         vc->rx = 1;
+         vc->rx_vcc = vcc;
+         vc->rx_iov = NULL;
+
+	 /* Open the connection in hardware */
+	 if (vcc->qos.aal == ATM_AAL5)
+	    status = NS_RCTE_AAL5 | NS_RCTE_CONNECTOPEN;
+	 else /* vcc->qos.aal == ATM_AAL0 */
+	    status = NS_RCTE_AAL0 | NS_RCTE_CONNECTOPEN;
+#ifdef RCQ_SUPPORT
+         status |= NS_RCTE_RAWCELLINTEN;
+#endif /* RCQ_SUPPORT */
+         ns_write_sram(card, NS_RCT + (vpi << card->vcibits | vci) *
+	               NS_RCT_ENTRY_SIZE, &status, 1);
+      }
+      
+   }
+   
+   set_bit(ATM_VF_READY,&vcc->flags);
+   return 0;
+}
+
+
+
+static void ns_close(struct atm_vcc *vcc)
+{
+   vc_map *vc;
+   ns_dev *card;
+   u32 data;
+   int i;
+   
+   vc = vcc->dev_data;
+   card = vcc->dev->dev_data;
+   PRINTK("nicstar%d: closing vpi.vci %d.%d \n", card->index,
+          (int) vcc->vpi, vcc->vci);
+
+   clear_bit(ATM_VF_READY,&vcc->flags);
+   
+   if (vcc->qos.rxtp.traffic_class != ATM_NONE)
+   {
+      u32 addr;
+      unsigned long flags;
+      
+      addr = NS_RCT + (vcc->vpi << card->vcibits | vcc->vci) * NS_RCT_ENTRY_SIZE;
+      ns_grab_res_lock(card, flags);
+      while(CMD_BUSY(card));
+      writel(NS_CMD_CLOSE_CONNECTION | addr << 2, card->membase + CMD);
+      spin_unlock_irqrestore(&card->res_lock, flags);
+      
+      vc->rx = 0;
+      if (vc->rx_iov != NULL)
+      {
+	 struct sk_buff *iovb;
+	 u32 stat;
+   
+         stat = readl(card->membase + STAT);
+         card->sbfqc = ns_stat_sfbqc_get(stat);   
+         card->lbfqc = ns_stat_lfbqc_get(stat);
+
+         PRINTK("nicstar%d: closing a VC with pending rx buffers.\n",
+	        card->index);
+         iovb = vc->rx_iov;
+         recycle_iovec_rx_bufs(card, (struct iovec *) iovb->data,
+	                       NS_SKB(iovb)->iovcnt);
+         NS_SKB(iovb)->iovcnt = 0;
+         NS_SKB(iovb)->vcc = NULL;
+         ns_grab_int_lock(card, flags);
+         recycle_iov_buf(card, iovb);
+         spin_unlock_irqrestore(&card->int_lock, flags);
+         vc->rx_iov = NULL;
+      }
+   }
+
+   if (vcc->qos.txtp.traffic_class != ATM_NONE)
+   {
+      vc->tx = 0;
+   }
+
+   if (vcc->qos.txtp.traffic_class == ATM_CBR)
+   {
+      unsigned long flags;
+      ns_scqe *scqep;
+      scq_info *scq;
+
+      scq = vc->scq;
+
+      for (;;)
+      {
+         ns_grab_scq_lock(card, scq, flags);
+         scqep = scq->next;
+         if (scqep == scq->base)
+            scqep = scq->last;
+         else
+            scqep--;
+         if (scqep == scq->tail)
+         {
+            spin_unlock_irqrestore(&scq->lock, flags);
+            break;
+         }
+         /* If the last entry is not a TSR, place one in the SCQ in order to
+            be able to completely drain it and then close. */
+         if (!ns_scqe_is_tsr(scqep) && scq->tail != scq->next)
+         {
+            ns_scqe tsr;
+            u32 scdi, scqi;
+            u32 data;
+            int index;
+
+            tsr.word_1 = ns_tsr_mkword_1(NS_TSR_INTENABLE);
+            scdi = (vc->cbr_scd - NS_FRSCD) / NS_FRSCD_SIZE;
+            scqi = scq->next - scq->base;
+            tsr.word_2 = ns_tsr_mkword_2(scdi, scqi);
+            tsr.word_3 = 0x00000000;
+            tsr.word_4 = 0x00000000;
+            *scq->next = tsr;
+            index = (int) scqi;
+            scq->skb[index] = NULL;
+            if (scq->next == scq->last)
+               scq->next = scq->base;
+            else
+               scq->next++;
+            data = (u32) virt_to_bus(scq->next);
+            ns_write_sram(card, scq->scd, &data, 1);
+         }
+         spin_unlock_irqrestore(&scq->lock, flags);
+         schedule();
+      }
+
+      /* Free all TST entries */
+      data = NS_TST_OPCODE_VARIABLE;
+      for (i = 0; i < NS_TST_NUM_ENTRIES; i++)
+      {
+         if (card->tste2vc[i] == vc)
+	 {
+            ns_write_sram(card, card->tst_addr + i, &data, 1);
+            card->tste2vc[i] = NULL;
+            card->tst_free_entries++;
+	 }
+      }
+      
+      card->scd2vc[(vc->cbr_scd - NS_FRSCD) / NS_FRSCD_SIZE] = NULL;
+      free_scq(vc->scq, vcc);
+   }
+
+   /* remove all references to vcc before deleting it */
+   if (vcc->qos.txtp.traffic_class != ATM_NONE)
+   {
+     unsigned long flags;
+     scq_info *scq = card->scq0;
+
+     ns_grab_scq_lock(card, scq, flags);
+
+     for(i = 0; i < scq->num_entries; i++) {
+       if(scq->skb[i] && ATM_SKB(scq->skb[i])->vcc == vcc) {
+        ATM_SKB(scq->skb[i])->vcc = NULL;
+	atm_return(vcc, scq->skb[i]->truesize);
+        PRINTK("nicstar: deleted pending vcc mapping\n");
+       }
+     }
+
+     spin_unlock_irqrestore(&scq->lock, flags);
+   }
+
+   vcc->dev_data = NULL;
+   clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+   clear_bit(ATM_VF_ADDR,&vcc->flags);
+
+#ifdef RX_DEBUG
+   {
+      u32 stat, cfg;
+      stat = readl(card->membase + STAT);
+      cfg = readl(card->membase + CFG);
+      printk("STAT = 0x%08X  CFG = 0x%08X  \n", stat, cfg);
+      printk("TSQ: base = 0x%08X  next = 0x%08X  last = 0x%08X  TSQT = 0x%08X \n",
+             (u32) card->tsq.base, (u32) card->tsq.next,(u32) card->tsq.last,
+	     readl(card->membase + TSQT));
+      printk("RSQ: base = 0x%08X  next = 0x%08X  last = 0x%08X  RSQT = 0x%08X \n",
+             (u32) card->rsq.base, (u32) card->rsq.next,(u32) card->rsq.last,
+	     readl(card->membase + RSQT));
+      printk("Empty free buffer queue interrupt %s \n",
+             card->efbie ? "enabled" : "disabled");
+      printk("SBCNT = %d  count = %d   LBCNT = %d count = %d \n",
+             ns_stat_sfbqc_get(stat), card->sbpool.count,
+	     ns_stat_lfbqc_get(stat), card->lbpool.count);
+      printk("hbpool.count = %d  iovpool.count = %d \n",
+             card->hbpool.count, card->iovpool.count);
+   }
+#endif /* RX_DEBUG */
+}
+
+
+
+static void fill_tst(ns_dev *card, int n, vc_map *vc)
+{
+   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_addr;
+
+   /* Fill procedure */
+
+   for (e = 0; e < NS_TST_NUM_ENTRIES; e++)
+   {
+      if (card->tste2vc[e] == NULL)
+         break;
+   }
+   if (e == NS_TST_NUM_ENTRIES) {
+      printk("nicstar%d: No free TST entries found. \n", card->index);
+      return;
+   }
+
+   r = n;
+   cl = NS_TST_NUM_ENTRIES;
+   data = ns_tste_make(NS_TST_OPCODE_FIXED, vc->cbr_scd);
+      
+   while (r > 0)
+   {
+      if (cl >= NS_TST_NUM_ENTRIES && card->tste2vc[e] == NULL)
+      {
+         card->tste2vc[e] = vc;
+         ns_write_sram(card, new_tst + e, &data, 1);
+         cl -= NS_TST_NUM_ENTRIES;
+         r--;
+      }
+
+      if (++e == NS_TST_NUM_ENTRIES) {
+         e = 0;
+      }
+      cl += n;
+   }
+   
+   /* End of fill procedure */
+   
+   data = ns_tste_make(NS_TST_OPCODE_END, new_tst);
+   ns_write_sram(card, new_tst + NS_TST_NUM_ENTRIES, &data, 1);
+   ns_write_sram(card, card->tst_addr + NS_TST_NUM_ENTRIES, &data, 1);
+   card->tst_addr = new_tst;
+}
+
+
+
+static int ns_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+   ns_dev *card;
+   vc_map *vc;
+   scq_info *scq;
+   unsigned long buflen;
+   ns_scqe scqe;
+   u32 flags;		/* TBD flags, not CPU flags */
+   
+   card = vcc->dev->dev_data;
+   TXPRINTK("nicstar%d: ns_send() called.\n", card->index);
+   if ((vc = (vc_map *) vcc->dev_data) == NULL)
+   {
+      printk("nicstar%d: vcc->dev_data == NULL on ns_send().\n", card->index);
+      atomic_inc(&vcc->stats->tx_err);
+      dev_kfree_skb_any(skb);
+      return -EINVAL;
+   }
+   
+   if (!vc->tx)
+   {
+      printk("nicstar%d: Trying to transmit on a non-tx VC.\n", card->index);
+      atomic_inc(&vcc->stats->tx_err);
+      dev_kfree_skb_any(skb);
+      return -EINVAL;
+   }
+   
+   if (vcc->qos.aal != ATM_AAL5 && vcc->qos.aal != ATM_AAL0)
+   {
+      printk("nicstar%d: Only AAL0 and AAL5 are supported.\n", card->index);
+      atomic_inc(&vcc->stats->tx_err);
+      dev_kfree_skb_any(skb);
+      return -EINVAL;
+   }
+   
+   if (skb_shinfo(skb)->nr_frags != 0)
+   {
+      printk("nicstar%d: No scatter-gather yet.\n", card->index);
+      atomic_inc(&vcc->stats->tx_err);
+      dev_kfree_skb_any(skb);
+      return -EINVAL;
+   }
+   
+   ATM_SKB(skb)->vcc = vcc;
+
+   if (vcc->qos.aal == ATM_AAL5)
+   {
+      buflen = (skb->len + 47 + 8) / 48 * 48;	/* Multiple of 48 */
+      flags = NS_TBD_AAL5;
+      scqe.word_2 = cpu_to_le32((u32) virt_to_bus(skb->data));
+      scqe.word_3 = cpu_to_le32((u32) skb->len);
+      scqe.word_4 = ns_tbd_mkword_4(0, (u32) vcc->vpi, (u32) vcc->vci, 0,
+                           ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? 1 : 0);
+      flags |= NS_TBD_EOPDU;
+   }
+   else /* (vcc->qos.aal == ATM_AAL0) */
+   {
+      buflen = ATM_CELL_PAYLOAD;	/* i.e., 48 bytes */
+      flags = NS_TBD_AAL0;
+      scqe.word_2 = cpu_to_le32((u32) virt_to_bus(skb->data) + NS_AAL0_HEADER);
+      scqe.word_3 = cpu_to_le32(0x00000000);
+      if (*skb->data & 0x02)	/* Payload type 1 - end of pdu */
+         flags |= NS_TBD_EOPDU;
+      scqe.word_4 = cpu_to_le32(*((u32 *) skb->data) & ~NS_TBD_VC_MASK);
+      /* Force the VPI/VCI to be the same as in VCC struct */
+      scqe.word_4 |= cpu_to_le32((((u32) vcc->vpi) << NS_TBD_VPI_SHIFT |
+                                 ((u32) vcc->vci) << NS_TBD_VCI_SHIFT) &
+                                 NS_TBD_VC_MASK);
+   }
+
+   if (vcc->qos.txtp.traffic_class == ATM_CBR)
+   {
+      scqe.word_1 = ns_tbd_mkword_1_novbr(flags, (u32) buflen);
+      scq = ((vc_map *) vcc->dev_data)->scq;
+   }
+   else
+   {
+      scqe.word_1 = ns_tbd_mkword_1(flags, (u32) 1, (u32) 1, (u32) buflen);
+      scq = card->scq0;
+   }
+
+   if (push_scqe(card, vc, scq, &scqe, skb) != 0)
+   {
+      atomic_inc(&vcc->stats->tx_err);
+      dev_kfree_skb_any(skb);
+      return -EIO;
+   }
+   atomic_inc(&vcc->stats->tx);
+
+   return 0;
+}
+
+
+
+static int push_scqe(ns_dev *card, vc_map *vc, scq_info *scq, ns_scqe *tbd,
+                     struct sk_buff *skb)
+{
+   unsigned long flags;
+   ns_scqe tsr;
+   u32 scdi, scqi;
+   int scq_is_vbr;
+   u32 data;
+   int index;
+   
+   ns_grab_scq_lock(card, scq, flags);
+   while (scq->tail == scq->next)
+   {
+      if (in_interrupt()) {
+         spin_unlock_irqrestore(&scq->lock, flags);
+         printk("nicstar%d: Error pushing TBD.\n", card->index);
+         return 1;
+      }
+
+      scq->full = 1;
+      spin_unlock_irqrestore(&scq->lock, flags);
+      interruptible_sleep_on_timeout(&scq->scqfull_waitq, SCQFULL_TIMEOUT);
+      ns_grab_scq_lock(card, scq, flags);
+
+      if (scq->full) {
+         spin_unlock_irqrestore(&scq->lock, flags);
+         printk("nicstar%d: Timeout pushing TBD.\n", card->index);
+         return 1;
+      }
+   }
+   *scq->next = *tbd;
+   index = (int) (scq->next - scq->base);
+   scq->skb[index] = skb;
+   XPRINTK("nicstar%d: sending skb at 0x%x (pos %d).\n",
+           card->index, (u32) skb, index);
+   XPRINTK("nicstar%d: TBD written:\n0x%x\n0x%x\n0x%x\n0x%x\n at 0x%x.\n",
+           card->index, le32_to_cpu(tbd->word_1), le32_to_cpu(tbd->word_2),
+           le32_to_cpu(tbd->word_3), le32_to_cpu(tbd->word_4),
+           (u32) scq->next);
+   if (scq->next == scq->last)
+      scq->next = scq->base;
+   else
+      scq->next++;
+
+   vc->tbd_count++;
+   if (scq->num_entries == VBR_SCQ_NUM_ENTRIES)
+   {
+      scq->tbd_count++;
+      scq_is_vbr = 1;
+   }
+   else
+      scq_is_vbr = 0;
+
+   if (vc->tbd_count >= MAX_TBD_PER_VC || scq->tbd_count >= MAX_TBD_PER_SCQ)
+   {
+      int has_run = 0;
+
+      while (scq->tail == scq->next)
+      {
+         if (in_interrupt()) {
+            data = (u32) virt_to_bus(scq->next);
+            ns_write_sram(card, scq->scd, &data, 1);
+            spin_unlock_irqrestore(&scq->lock, flags);
+            printk("nicstar%d: Error pushing TSR.\n", card->index);
+            return 0;
+         }
+
+         scq->full = 1;
+         if (has_run++) break;
+         spin_unlock_irqrestore(&scq->lock, flags);
+         interruptible_sleep_on_timeout(&scq->scqfull_waitq, SCQFULL_TIMEOUT);
+         ns_grab_scq_lock(card, scq, flags);
+      }
+
+      if (!scq->full)
+      {
+         tsr.word_1 = ns_tsr_mkword_1(NS_TSR_INTENABLE);
+         if (scq_is_vbr)
+            scdi = NS_TSR_SCDISVBR;
+         else
+            scdi = (vc->cbr_scd - NS_FRSCD) / NS_FRSCD_SIZE;
+         scqi = scq->next - scq->base;
+         tsr.word_2 = ns_tsr_mkword_2(scdi, scqi);
+         tsr.word_3 = 0x00000000;
+         tsr.word_4 = 0x00000000;
+
+         *scq->next = tsr;
+         index = (int) scqi;
+         scq->skb[index] = NULL;
+         XPRINTK("nicstar%d: TSR written:\n0x%x\n0x%x\n0x%x\n0x%x\n at 0x%x.\n",
+                 card->index, le32_to_cpu(tsr.word_1), le32_to_cpu(tsr.word_2),
+                 le32_to_cpu(tsr.word_3), le32_to_cpu(tsr.word_4),
+		 (u32) scq->next);
+         if (scq->next == scq->last)
+            scq->next = scq->base;
+         else
+            scq->next++;
+         vc->tbd_count = 0;
+         scq->tbd_count = 0;
+      }
+      else
+         PRINTK("nicstar%d: Timeout pushing TSR.\n", card->index);
+   }
+   data = (u32) virt_to_bus(scq->next);
+   ns_write_sram(card, scq->scd, &data, 1);
+   
+   spin_unlock_irqrestore(&scq->lock, flags);
+   
+   return 0;
+}
+
+
+
+static void process_tsq(ns_dev *card)
+{
+   u32 scdi;
+   scq_info *scq;
+   ns_tsi *previous = NULL, *one_ahead, *two_ahead;
+   int serviced_entries;   /* flag indicating at least on entry was serviced */
+   
+   serviced_entries = 0;
+   
+   if (card->tsq.next == card->tsq.last)
+      one_ahead = card->tsq.base;
+   else
+      one_ahead = card->tsq.next + 1;
+
+   if (one_ahead == card->tsq.last)
+      two_ahead = card->tsq.base;
+   else
+      two_ahead = one_ahead + 1;
+   
+   while (!ns_tsi_isempty(card->tsq.next) || !ns_tsi_isempty(one_ahead) ||
+          !ns_tsi_isempty(two_ahead))
+          /* At most two empty, as stated in the 77201 errata */
+   {
+      serviced_entries = 1;
+    
+      /* Skip the one or two possible empty entries */
+      while (ns_tsi_isempty(card->tsq.next)) {
+         if (card->tsq.next == card->tsq.last)
+            card->tsq.next = card->tsq.base;
+         else
+            card->tsq.next++;
+      }
+    
+      if (!ns_tsi_tmrof(card->tsq.next))
+      {
+         scdi = ns_tsi_getscdindex(card->tsq.next);
+	 if (scdi == NS_TSI_SCDISVBR)
+	    scq = card->scq0;
+	 else
+	 {
+	    if (card->scd2vc[scdi] == NULL)
+	    {
+	       printk("nicstar%d: could not find VC from SCD index.\n",
+	              card->index);
+               ns_tsi_init(card->tsq.next);
+               return;
+            }
+            scq = card->scd2vc[scdi]->scq;
+         }
+         drain_scq(card, scq, ns_tsi_getscqpos(card->tsq.next));
+         scq->full = 0;
+         wake_up_interruptible(&(scq->scqfull_waitq));
+      }
+
+      ns_tsi_init(card->tsq.next);
+      previous = card->tsq.next;
+      if (card->tsq.next == card->tsq.last)
+         card->tsq.next = card->tsq.base;
+      else
+         card->tsq.next++;
+
+      if (card->tsq.next == card->tsq.last)
+         one_ahead = card->tsq.base;
+      else
+         one_ahead = card->tsq.next + 1;
+
+      if (one_ahead == card->tsq.last)
+         two_ahead = card->tsq.base;
+      else
+         two_ahead = one_ahead + 1;
+   }
+
+   if (serviced_entries) {
+      writel((((u32) previous) - ((u32) card->tsq.base)),
+             card->membase + TSQH);
+   }
+}
+
+
+
+static void drain_scq(ns_dev *card, scq_info *scq, int pos)
+{
+   struct atm_vcc *vcc;
+   struct sk_buff *skb;
+   int i;
+   unsigned long flags;
+   
+   XPRINTK("nicstar%d: drain_scq() called, scq at 0x%x, pos %d.\n",
+           card->index, (u32) scq, pos);
+   if (pos >= scq->num_entries)
+   {
+      printk("nicstar%d: Bad index on drain_scq().\n", card->index);
+      return;
+   }
+
+   ns_grab_scq_lock(card, scq, flags);
+   i = (int) (scq->tail - scq->base);
+   if (++i == scq->num_entries)
+      i = 0;
+   while (i != pos)
+   {
+      skb = scq->skb[i];
+      XPRINTK("nicstar%d: freeing skb at 0x%x (index %d).\n",
+              card->index, (u32) skb, i);
+      if (skb != NULL)
+      {
+         vcc = ATM_SKB(skb)->vcc;
+	 if (vcc && vcc->pop != NULL) {
+	    vcc->pop(vcc, skb);
+	 } else {
+	    dev_kfree_skb_irq(skb);
+         }
+	 scq->skb[i] = NULL;
+      }
+      if (++i == scq->num_entries)
+         i = 0;
+   }
+   scq->tail = scq->base + pos;
+   spin_unlock_irqrestore(&scq->lock, flags);
+}
+
+
+
+static void process_rsq(ns_dev *card)
+{
+   ns_rsqe *previous;
+
+   if (!ns_rsqe_valid(card->rsq.next))
+      return;
+   while (ns_rsqe_valid(card->rsq.next))
+   {
+      dequeue_rx(card, card->rsq.next);
+      ns_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++;
+   }
+   writel((((u32) previous) - ((u32) card->rsq.base)),
+          card->membase + RSQH);
+}
+
+
+
+static void dequeue_rx(ns_dev *card, ns_rsqe *rsqe)
+{
+   u32 vpi, vci;
+   vc_map *vc;
+   struct sk_buff *iovb;
+   struct iovec *iov;
+   struct atm_vcc *vcc;
+   struct sk_buff *skb;
+   unsigned short aal5_len;
+   int len;
+   u32 stat;
+
+   stat = readl(card->membase + STAT);
+   card->sbfqc = ns_stat_sfbqc_get(stat);   
+   card->lbfqc = ns_stat_lfbqc_get(stat);
+
+   skb = (struct sk_buff *) le32_to_cpu(rsqe->buffer_handle);
+   vpi = ns_rsqe_vpi(rsqe);
+   vci = ns_rsqe_vci(rsqe);
+   if (vpi >= 1UL << card->vpibits || vci >= 1UL << card->vcibits)
+   {
+      printk("nicstar%d: SDU received for out-of-range vc %d.%d.\n",
+             card->index, vpi, vci);
+      recycle_rx_buf(card, skb);
+      return;
+   }
+   
+   vc = &(card->vcmap[vpi << card->vcibits | vci]);
+   if (!vc->rx)
+   {
+      RXPRINTK("nicstar%d: SDU received on non-rx vc %d.%d.\n",
+             card->index, vpi, vci);
+      recycle_rx_buf(card, skb);
+      return;
+   }
+
+   vcc = vc->rx_vcc;
+
+   if (vcc->qos.aal == ATM_AAL0)
+   {
+      struct sk_buff *sb;
+      unsigned char *cell;
+      int i;
+
+      cell = skb->data;
+      for (i = ns_rsqe_cellcount(rsqe); i; i--)
+      {
+         if ((sb = dev_alloc_skb(NS_SMSKBSIZE)) == NULL)
+         {
+            printk("nicstar%d: Can't allocate buffers for aal0.\n",
+                   card->index);
+            atomic_add(i,&vcc->stats->rx_drop);
+            break;
+         }
+         if (!atm_charge(vcc, sb->truesize))
+         {
+            RXPRINTK("nicstar%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 |
+                               (ns_rsqe_clp(rsqe) ? 0x00000001 : 0x00000000);
+         if (i == 1 && ns_rsqe_eopdu(rsqe))
+            *((u32 *) sb->data) |= 0x00000002;
+         skb_put(sb, NS_AAL0_HEADER);
+         memcpy(sb->tail, cell, ATM_CELL_PAYLOAD);
+         skb_put(sb, ATM_CELL_PAYLOAD);
+         ATM_SKB(sb)->vcc = vcc;
+         do_gettimeofday(&sb->stamp);
+         vcc->push(vcc, sb);
+         atomic_inc(&vcc->stats->rx);
+         cell += ATM_CELL_PAYLOAD;
+      }
+
+      recycle_rx_buf(card, skb);
+      return;
+   }
+
+   /* To reach this point, the AAL layer can only be AAL5 */
+
+   if ((iovb = vc->rx_iov) == NULL)
+   {
+      iovb = skb_dequeue(&(card->iovpool.queue));
+      if (iovb == NULL)		/* No buffers in the queue */
+      {
+         iovb = alloc_skb(NS_IOVBUFSIZE, GFP_ATOMIC);
+	 if (iovb == NULL)
+	 {
+	    printk("nicstar%d: Out of iovec buffers.\n", card->index);
+            atomic_inc(&vcc->stats->rx_drop);
+            recycle_rx_buf(card, skb);
+            return;
+	 }
+      }
+      else
+         if (--card->iovpool.count < card->iovnr.min)
+	 {
+	    struct sk_buff *new_iovb;
+	    if ((new_iovb = alloc_skb(NS_IOVBUFSIZE, GFP_ATOMIC)) != NULL)
+	    {
+               skb_queue_tail(&card->iovpool.queue, new_iovb);
+               card->iovpool.count++;
+	    }
+	 }
+      vc->rx_iov = iovb;
+      NS_SKB(iovb)->iovcnt = 0;
+      iovb->len = 0;
+      iovb->tail = iovb->data = iovb->head;
+      NS_SKB(iovb)->vcc = vcc;
+      /* IMPORTANT: a pointer to the sk_buff containing the small or large
+                    buffer is stored as iovec base, NOT a pointer to the 
+	            small or large buffer itself. */
+   }
+   else if (NS_SKB(iovb)->iovcnt >= NS_MAX_IOVECS)
+   {
+      printk("nicstar%d: received too big AAL5 SDU.\n", card->index);
+      atomic_inc(&vcc->stats->rx_err);
+      recycle_iovec_rx_bufs(card, (struct iovec *) iovb->data, NS_MAX_IOVECS);
+      NS_SKB(iovb)->iovcnt = 0;
+      iovb->len = 0;
+      iovb->tail = iovb->data = iovb->head;
+      NS_SKB(iovb)->vcc = vcc;
+   }
+   iov = &((struct iovec *) iovb->data)[NS_SKB(iovb)->iovcnt++];
+   iov->iov_base = (void *) skb;
+   iov->iov_len = ns_rsqe_cellcount(rsqe) * 48;
+   iovb->len += iov->iov_len;
+
+   if (NS_SKB(iovb)->iovcnt == 1)
+   {
+      if (skb->list != &card->sbpool.queue)
+      {
+         printk("nicstar%d: Expected a small buffer, and this is not one.\n",
+	        card->index);
+         which_list(card, skb);
+         atomic_inc(&vcc->stats->rx_err);
+         recycle_rx_buf(card, skb);
+         vc->rx_iov = NULL;
+         recycle_iov_buf(card, iovb);
+         return;
+      }
+   }
+   else /* NS_SKB(iovb)->iovcnt >= 2 */
+   {
+      if (skb->list != &card->lbpool.queue)
+      {
+         printk("nicstar%d: Expected a large buffer, and this is not one.\n",
+	        card->index);
+         which_list(card, skb);
+         atomic_inc(&vcc->stats->rx_err);
+         recycle_iovec_rx_bufs(card, (struct iovec *) iovb->data,
+	                       NS_SKB(iovb)->iovcnt);
+         vc->rx_iov = NULL;
+         recycle_iov_buf(card, iovb);
+	 return;
+      }
+   }
+
+   if (ns_rsqe_eopdu(rsqe))
+   {
+      /* This works correctly regardless of the endianness of the host */
+      unsigned char *L1L2 = (unsigned char *)((u32)skb->data +
+                                              iov->iov_len - 6);
+      aal5_len = L1L2[0] << 8 | L1L2[1];
+      len = (aal5_len == 0x0000) ? 0x10000 : aal5_len;
+      if (ns_rsqe_crcerr(rsqe) ||
+          len + 8 > iovb->len || len + (47 + 8) < iovb->len)
+      {
+         printk("nicstar%d: AAL5 CRC error", card->index);
+         if (len + 8 > iovb->len || len + (47 + 8) < iovb->len)
+            printk(" - PDU size mismatch.\n");
+         else
+            printk(".\n");
+         atomic_inc(&vcc->stats->rx_err);
+         recycle_iovec_rx_bufs(card, (struct iovec *) iovb->data,
+	   NS_SKB(iovb)->iovcnt);
+	 vc->rx_iov = NULL;
+         recycle_iov_buf(card, iovb);
+	 return;
+      }
+
+      /* By this point we (hopefully) have a complete SDU without errors. */
+
+      if (NS_SKB(iovb)->iovcnt == 1)	/* Just a small buffer */
+      {
+         /* skb points to a small buffer */
+         if (!atm_charge(vcc, skb->truesize))
+         {
+            push_rxbufs(card, BUF_SM, (u32) skb, (u32) virt_to_bus(skb->data),
+                        0, 0);
+            atomic_inc(&vcc->stats->rx_drop);
+         }
+         else
+	 {
+            skb_put(skb, len);
+            dequeue_sm_buf(card, skb);
+#ifdef NS_USE_DESTRUCTORS
+            skb->destructor = ns_sb_destructor;
+#endif /* NS_USE_DESTRUCTORS */
+            ATM_SKB(skb)->vcc = vcc;
+            do_gettimeofday(&skb->stamp);
+            vcc->push(vcc, skb);
+            atomic_inc(&vcc->stats->rx);
+         }
+      }
+      else if (NS_SKB(iovb)->iovcnt == 2)	/* One small plus one large buffer */
+      {
+         struct sk_buff *sb;
+
+         sb = (struct sk_buff *) (iov - 1)->iov_base;
+         /* skb points to a large buffer */
+
+         if (len <= NS_SMBUFSIZE)
+	 {
+            if (!atm_charge(vcc, sb->truesize))
+            {
+               push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data),
+                           0, 0);
+               atomic_inc(&vcc->stats->rx_drop);
+            }
+            else
+	    {
+               skb_put(sb, len);
+               dequeue_sm_buf(card, sb);
+#ifdef NS_USE_DESTRUCTORS
+               sb->destructor = ns_sb_destructor;
+#endif /* NS_USE_DESTRUCTORS */
+               ATM_SKB(sb)->vcc = vcc;
+               do_gettimeofday(&sb->stamp);
+               vcc->push(vcc, sb);
+               atomic_inc(&vcc->stats->rx);
+            }
+
+            push_rxbufs(card, BUF_LG, (u32) skb,
+	                   (u32) virt_to_bus(skb->data), 0, 0);
+
+	 }
+	 else			/* len > NS_SMBUFSIZE, the usual case */
+	 {
+            if (!atm_charge(vcc, skb->truesize))
+            {
+               push_rxbufs(card, BUF_LG, (u32) skb,
+                           (u32) virt_to_bus(skb->data), 0, 0);
+               atomic_inc(&vcc->stats->rx_drop);
+            }
+            else
+            {
+               dequeue_lg_buf(card, skb);
+#ifdef NS_USE_DESTRUCTORS
+               skb->destructor = ns_lb_destructor;
+#endif /* NS_USE_DESTRUCTORS */
+               skb_push(skb, NS_SMBUFSIZE);
+               memcpy(skb->data, sb->data, NS_SMBUFSIZE);
+               skb_put(skb, len - NS_SMBUFSIZE);
+               ATM_SKB(skb)->vcc = vcc;
+               do_gettimeofday(&skb->stamp);
+               vcc->push(vcc, skb);
+               atomic_inc(&vcc->stats->rx);
+            }
+
+            push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data),
+                        0, 0);
+
+         }
+	 
+      }
+      else				/* Must push a huge buffer */
+      {
+         struct sk_buff *hb, *sb, *lb;
+	 int remaining, tocopy;
+         int j;
+
+         hb = skb_dequeue(&(card->hbpool.queue));
+         if (hb == NULL)		/* No buffers in the queue */
+         {
+
+            hb = dev_alloc_skb(NS_HBUFSIZE);
+            if (hb == NULL)
+            {
+               printk("nicstar%d: Out of huge buffers.\n", card->index);
+               atomic_inc(&vcc->stats->rx_drop);
+               recycle_iovec_rx_bufs(card, (struct iovec *) iovb->data,
+	                             NS_SKB(iovb)->iovcnt);
+               vc->rx_iov = NULL;
+               recycle_iov_buf(card, iovb);
+               return;
+            }
+            else if (card->hbpool.count < card->hbnr.min)
+	    {
+               struct sk_buff *new_hb;
+               if ((new_hb = dev_alloc_skb(NS_HBUFSIZE)) != NULL)
+               {
+                  skb_queue_tail(&card->hbpool.queue, new_hb);
+                  card->hbpool.count++;
+               }
+            }
+	 }
+	 else
+         if (--card->hbpool.count < card->hbnr.min)
+         {
+            struct sk_buff *new_hb;
+            if ((new_hb = dev_alloc_skb(NS_HBUFSIZE)) != NULL)
+            {
+               skb_queue_tail(&card->hbpool.queue, new_hb);
+               card->hbpool.count++;
+            }
+            if (card->hbpool.count < card->hbnr.min)
+	    {
+               if ((new_hb = dev_alloc_skb(NS_HBUFSIZE)) != NULL)
+               {
+                  skb_queue_tail(&card->hbpool.queue, new_hb);
+                  card->hbpool.count++;
+               }
+            }
+         }
+
+         iov = (struct iovec *) iovb->data;
+
+         if (!atm_charge(vcc, hb->truesize))
+	 {
+            recycle_iovec_rx_bufs(card, iov, NS_SKB(iovb)->iovcnt);
+            if (card->hbpool.count < card->hbnr.max)
+            {
+               skb_queue_tail(&card->hbpool.queue, hb);
+               card->hbpool.count++;
+            }
+	    else
+	       dev_kfree_skb_any(hb);
+	    atomic_inc(&vcc->stats->rx_drop);
+         }
+         else
+	 {
+            /* Copy the small buffer to the huge buffer */
+            sb = (struct sk_buff *) iov->iov_base;
+            memcpy(hb->data, sb->data, iov->iov_len);
+            skb_put(hb, iov->iov_len);
+            remaining = len - iov->iov_len;
+            iov++;
+            /* Free the small buffer */
+            push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data),
+                        0, 0);
+
+            /* Copy all large buffers to the huge buffer and free them */
+            for (j = 1; j < NS_SKB(iovb)->iovcnt; j++)
+            {
+               lb = (struct sk_buff *) iov->iov_base;
+               tocopy = min_t(int, remaining, iov->iov_len);
+               memcpy(hb->tail, lb->data, tocopy);
+               skb_put(hb, tocopy);
+               iov++;
+               remaining -= tocopy;
+               push_rxbufs(card, BUF_LG, (u32) lb,
+                           (u32) virt_to_bus(lb->data), 0, 0);
+            }
+#ifdef EXTRA_DEBUG
+            if (remaining != 0 || hb->len != len)
+               printk("nicstar%d: Huge buffer len mismatch.\n", card->index);
+#endif /* EXTRA_DEBUG */
+            ATM_SKB(hb)->vcc = vcc;
+#ifdef NS_USE_DESTRUCTORS
+            hb->destructor = ns_hb_destructor;
+#endif /* NS_USE_DESTRUCTORS */
+            do_gettimeofday(&hb->stamp);
+            vcc->push(vcc, hb);
+            atomic_inc(&vcc->stats->rx);
+         }
+      }
+
+      vc->rx_iov = NULL;
+      recycle_iov_buf(card, iovb);
+   }
+
+}
+
+
+
+#ifdef NS_USE_DESTRUCTORS
+
+static void ns_sb_destructor(struct sk_buff *sb)
+{
+   ns_dev *card;
+   u32 stat;
+
+   card = (ns_dev *) ATM_SKB(sb)->vcc->dev->dev_data;
+   stat = readl(card->membase + STAT);
+   card->sbfqc = ns_stat_sfbqc_get(stat);   
+   card->lbfqc = ns_stat_lfbqc_get(stat);
+
+   do
+   {
+      sb = __dev_alloc_skb(NS_SMSKBSIZE, GFP_KERNEL);
+      if (sb == NULL)
+         break;
+      skb_queue_tail(&card->sbpool.queue, sb);
+      skb_reserve(sb, NS_AAL0_HEADER);
+      push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data), 0, 0);
+   } while (card->sbfqc < card->sbnr.min);
+}
+
+
+
+static void ns_lb_destructor(struct sk_buff *lb)
+{
+   ns_dev *card;
+   u32 stat;
+
+   card = (ns_dev *) ATM_SKB(lb)->vcc->dev->dev_data;
+   stat = readl(card->membase + STAT);
+   card->sbfqc = ns_stat_sfbqc_get(stat);   
+   card->lbfqc = ns_stat_lfbqc_get(stat);
+
+   do
+   {
+      lb = __dev_alloc_skb(NS_LGSKBSIZE, GFP_KERNEL);
+      if (lb == NULL)
+         break;
+      skb_queue_tail(&card->lbpool.queue, lb);
+      skb_reserve(lb, NS_SMBUFSIZE);
+      push_rxbufs(card, BUF_LG, (u32) lb, (u32) virt_to_bus(lb->data), 0, 0);
+   } while (card->lbfqc < card->lbnr.min);
+}
+
+
+
+static void ns_hb_destructor(struct sk_buff *hb)
+{
+   ns_dev *card;
+
+   card = (ns_dev *) ATM_SKB(hb)->vcc->dev->dev_data;
+
+   while (card->hbpool.count < card->hbnr.init)
+   {
+      hb = __dev_alloc_skb(NS_HBUFSIZE, GFP_KERNEL);
+      if (hb == NULL)
+         break;
+      skb_queue_tail(&card->hbpool.queue, hb);
+      card->hbpool.count++;
+   }
+}
+
+#endif /* NS_USE_DESTRUCTORS */
+
+
+
+static void recycle_rx_buf(ns_dev *card, struct sk_buff *skb)
+{
+   if (skb->list == &card->sbpool.queue)
+      push_rxbufs(card, BUF_SM, (u32) skb, (u32) virt_to_bus(skb->data), 0, 0);
+   else if (skb->list == &card->lbpool.queue)
+      push_rxbufs(card, BUF_LG, (u32) skb, (u32) virt_to_bus(skb->data), 0, 0);
+   else
+   {
+      printk("nicstar%d: What kind of rx buffer is this?\n", card->index);
+      dev_kfree_skb_any(skb);
+   }
+}
+
+
+
+static void recycle_iovec_rx_bufs(ns_dev *card, struct iovec *iov, int count)
+{
+   struct sk_buff *skb;
+
+   for (; count > 0; count--)
+   {
+      skb = (struct sk_buff *) (iov++)->iov_base;
+      if (skb->list == &card->sbpool.queue)
+         push_rxbufs(card, BUF_SM, (u32) skb, (u32) virt_to_bus(skb->data),
+	             0, 0);
+      else if (skb->list == &card->lbpool.queue)
+         push_rxbufs(card, BUF_LG, (u32) skb, (u32) virt_to_bus(skb->data),
+	             0, 0);
+      else
+      {
+         printk("nicstar%d: What kind of rx buffer is this?\n", card->index);
+         dev_kfree_skb_any(skb);
+      }
+   }
+}
+
+
+
+static void recycle_iov_buf(ns_dev *card, struct sk_buff *iovb)
+{
+   if (card->iovpool.count < card->iovnr.max)
+   {
+      skb_queue_tail(&card->iovpool.queue, iovb);
+      card->iovpool.count++;
+   }
+   else
+      dev_kfree_skb_any(iovb);
+}
+
+
+
+static void dequeue_sm_buf(ns_dev *card, struct sk_buff *sb)
+{
+   skb_unlink(sb);
+#ifdef NS_USE_DESTRUCTORS
+   if (card->sbfqc < card->sbnr.min)
+#else
+   if (card->sbfqc < card->sbnr.init)
+   {
+      struct sk_buff *new_sb;
+      if ((new_sb = dev_alloc_skb(NS_SMSKBSIZE)) != NULL)
+      {
+         skb_queue_tail(&card->sbpool.queue, new_sb);
+         skb_reserve(new_sb, NS_AAL0_HEADER);
+         push_rxbufs(card, BUF_SM, (u32) new_sb,
+                     (u32) virt_to_bus(new_sb->data), 0, 0);
+      }
+   }
+   if (card->sbfqc < card->sbnr.init)
+#endif /* NS_USE_DESTRUCTORS */
+   {
+      struct sk_buff *new_sb;
+      if ((new_sb = dev_alloc_skb(NS_SMSKBSIZE)) != NULL)
+      {
+         skb_queue_tail(&card->sbpool.queue, new_sb);
+         skb_reserve(new_sb, NS_AAL0_HEADER);
+         push_rxbufs(card, BUF_SM, (u32) new_sb,
+                     (u32) virt_to_bus(new_sb->data), 0, 0);
+      }
+   }
+}
+
+
+
+static void dequeue_lg_buf(ns_dev *card, struct sk_buff *lb)
+{
+   skb_unlink(lb);
+#ifdef NS_USE_DESTRUCTORS
+   if (card->lbfqc < card->lbnr.min)
+#else
+   if (card->lbfqc < card->lbnr.init)
+   {
+      struct sk_buff *new_lb;
+      if ((new_lb = dev_alloc_skb(NS_LGSKBSIZE)) != NULL)
+      {
+         skb_queue_tail(&card->lbpool.queue, new_lb);
+         skb_reserve(new_lb, NS_SMBUFSIZE);
+         push_rxbufs(card, BUF_LG, (u32) new_lb,
+                     (u32) virt_to_bus(new_lb->data), 0, 0);
+      }
+   }
+   if (card->lbfqc < card->lbnr.init)
+#endif /* NS_USE_DESTRUCTORS */
+   {
+      struct sk_buff *new_lb;
+      if ((new_lb = dev_alloc_skb(NS_LGSKBSIZE)) != NULL)
+      {
+         skb_queue_tail(&card->lbpool.queue, new_lb);
+         skb_reserve(new_lb, NS_SMBUFSIZE);
+         push_rxbufs(card, BUF_LG, (u32) new_lb,
+                     (u32) virt_to_bus(new_lb->data), 0, 0);
+      }
+   }
+}
+
+
+
+static int ns_proc_read(struct atm_dev *dev, loff_t *pos, char *page)
+{
+   u32 stat;
+   ns_dev *card;
+   int left;
+
+   left = (int) *pos;
+   card = (ns_dev *) dev->dev_data;
+   stat = readl(card->membase + STAT);
+   if (!left--)
+      return sprintf(page, "Pool   count    min   init    max \n");
+   if (!left--)
+      return sprintf(page, "Small  %5d  %5d  %5d  %5d \n",
+                     ns_stat_sfbqc_get(stat), card->sbnr.min, card->sbnr.init,
+		     card->sbnr.max);
+   if (!left--)
+      return sprintf(page, "Large  %5d  %5d  %5d  %5d \n",
+                     ns_stat_lfbqc_get(stat), card->lbnr.min, card->lbnr.init,
+		     card->lbnr.max);
+   if (!left--)
+      return sprintf(page, "Huge   %5d  %5d  %5d  %5d \n", card->hbpool.count,
+                     card->hbnr.min, card->hbnr.init, card->hbnr.max);
+   if (!left--)
+      return sprintf(page, "Iovec  %5d  %5d  %5d  %5d \n", card->iovpool.count,
+                     card->iovnr.min, card->iovnr.init, card->iovnr.max);
+   if (!left--)
+   {
+      int retval;
+      retval = sprintf(page, "Interrupt counter: %u \n", card->intcnt);
+      card->intcnt = 0;
+      return retval;
+   }
+#if 0
+   /* Dump 25.6 Mbps PHY registers */
+   /* Now there's a 25.6 Mbps PHY driver this code isn't needed. I left it
+      here just in case it's needed for debugging. */
+   if (card->max_pcr == ATM_25_PCR && !left--)
+   {
+      u32 phy_regs[4];
+      u32 i;
+
+      for (i = 0; i < 4; i++)
+      {
+         while (CMD_BUSY(card));
+         writel(NS_CMD_READ_UTILITY | 0x00000200 | i, card->membase + CMD);
+         while (CMD_BUSY(card));
+         phy_regs[i] = readl(card->membase + DR0) & 0x000000FF;
+      }
+
+      return sprintf(page, "PHY regs: 0x%02X 0x%02X 0x%02X 0x%02X \n",
+                     phy_regs[0], phy_regs[1], phy_regs[2], phy_regs[3]);
+   }
+#endif /* 0 - Dump 25.6 Mbps PHY registers */
+#if 0
+   /* Dump TST */
+   if (left-- < NS_TST_NUM_ENTRIES)
+   {
+      if (card->tste2vc[left + 1] == NULL)
+         return sprintf(page, "%5d - VBR/UBR \n", left + 1);
+      else
+         return sprintf(page, "%5d - %d %d \n", left + 1,
+                        card->tste2vc[left + 1]->tx_vcc->vpi,
+                        card->tste2vc[left + 1]->tx_vcc->vci);
+   }
+#endif /* 0 */
+   return 0;
+}
+
+
+
+static int ns_ioctl(struct atm_dev *dev, unsigned int cmd, void __user *arg)
+{
+   ns_dev *card;
+   pool_levels pl;
+   int btype;
+   unsigned long flags;
+
+   card = dev->dev_data;
+   switch (cmd)
+   {
+      case NS_GETPSTAT:
+         if (get_user(pl.buftype, &((pool_levels __user *) arg)->buftype))
+	    return -EFAULT;
+         switch (pl.buftype)
+	 {
+	    case NS_BUFTYPE_SMALL:
+	       pl.count = ns_stat_sfbqc_get(readl(card->membase + STAT));
+	       pl.level.min = card->sbnr.min;
+	       pl.level.init = card->sbnr.init;
+	       pl.level.max = card->sbnr.max;
+	       break;
+
+	    case NS_BUFTYPE_LARGE:
+	       pl.count = ns_stat_lfbqc_get(readl(card->membase + STAT));
+	       pl.level.min = card->lbnr.min;
+	       pl.level.init = card->lbnr.init;
+	       pl.level.max = card->lbnr.max;
+	       break;
+
+	    case NS_BUFTYPE_HUGE:
+	       pl.count = card->hbpool.count;
+	       pl.level.min = card->hbnr.min;
+	       pl.level.init = card->hbnr.init;
+	       pl.level.max = card->hbnr.max;
+	       break;
+
+	    case NS_BUFTYPE_IOVEC:
+	       pl.count = card->iovpool.count;
+	       pl.level.min = card->iovnr.min;
+	       pl.level.init = card->iovnr.init;
+	       pl.level.max = card->iovnr.max;
+	       break;
+
+            default:
+	       return -ENOIOCTLCMD;
+
+	 }
+         if (!copy_to_user((pool_levels __user *) arg, &pl, sizeof(pl)))
+	    return (sizeof(pl));
+	 else
+	    return -EFAULT;
+
+      case NS_SETBUFLEV:
+         if (!capable(CAP_NET_ADMIN))
+	    return -EPERM;
+         if (copy_from_user(&pl, (pool_levels __user *) arg, sizeof(pl)))
+	    return -EFAULT;
+	 if (pl.level.min >= pl.level.init || pl.level.init >= pl.level.max)
+	    return -EINVAL;
+	 if (pl.level.min == 0)
+	    return -EINVAL;
+         switch (pl.buftype)
+	 {
+	    case NS_BUFTYPE_SMALL:
+               if (pl.level.max > TOP_SB)
+	          return -EINVAL;
+	       card->sbnr.min = pl.level.min;
+	       card->sbnr.init = pl.level.init;
+	       card->sbnr.max = pl.level.max;
+	       break;
+
+	    case NS_BUFTYPE_LARGE:
+               if (pl.level.max > TOP_LB)
+	          return -EINVAL;
+	       card->lbnr.min = pl.level.min;
+	       card->lbnr.init = pl.level.init;
+	       card->lbnr.max = pl.level.max;
+	       break;
+
+	    case NS_BUFTYPE_HUGE:
+               if (pl.level.max > TOP_HB)
+	          return -EINVAL;
+	       card->hbnr.min = pl.level.min;
+	       card->hbnr.init = pl.level.init;
+	       card->hbnr.max = pl.level.max;
+	       break;
+
+	    case NS_BUFTYPE_IOVEC:
+               if (pl.level.max > TOP_IOVB)
+	          return -EINVAL;
+	       card->iovnr.min = pl.level.min;
+	       card->iovnr.init = pl.level.init;
+	       card->iovnr.max = pl.level.max;
+	       break;
+
+            default:
+	       return -EINVAL;
+
+         }	 
+         return 0;
+
+      case NS_ADJBUFLEV:
+         if (!capable(CAP_NET_ADMIN))
+	    return -EPERM;
+         btype = (int) arg;	/* an int is the same size as a pointer */
+         switch (btype)
+	 {
+	    case NS_BUFTYPE_SMALL:
+	       while (card->sbfqc < card->sbnr.init)
+	       {
+                  struct sk_buff *sb;
+
+                  sb = __dev_alloc_skb(NS_SMSKBSIZE, GFP_KERNEL);
+                  if (sb == NULL)
+                     return -ENOMEM;
+                  skb_queue_tail(&card->sbpool.queue, sb);
+                  skb_reserve(sb, NS_AAL0_HEADER);
+                  push_rxbufs(card, BUF_SM, (u32) sb, (u32) virt_to_bus(sb->data), 0, 0);
+	       }
+	       break;
+
+            case NS_BUFTYPE_LARGE:
+	       while (card->lbfqc < card->lbnr.init)
+	       {
+                  struct sk_buff *lb;
+
+                  lb = __dev_alloc_skb(NS_LGSKBSIZE, GFP_KERNEL);
+                  if (lb == NULL)
+                     return -ENOMEM;
+                  skb_queue_tail(&card->lbpool.queue, lb);
+                  skb_reserve(lb, NS_SMBUFSIZE);
+                  push_rxbufs(card, BUF_LG, (u32) lb, (u32) virt_to_bus(lb->data), 0, 0);
+	       }
+	       break;
+
+            case NS_BUFTYPE_HUGE:
+               while (card->hbpool.count > card->hbnr.init)
+	       {
+                  struct sk_buff *hb;
+
+                  ns_grab_int_lock(card, flags);
+		  hb = skb_dequeue(&card->hbpool.queue);
+		  card->hbpool.count--;
+                  spin_unlock_irqrestore(&card->int_lock, flags);
+                  if (hb == NULL)
+		     printk("nicstar%d: huge buffer count inconsistent.\n",
+		            card->index);
+                  else
+		     dev_kfree_skb_any(hb);
+		  
+	       }
+               while (card->hbpool.count < card->hbnr.init)
+               {
+                  struct sk_buff *hb;
+
+                  hb = __dev_alloc_skb(NS_HBUFSIZE, GFP_KERNEL);
+                  if (hb == NULL)
+                     return -ENOMEM;
+                  ns_grab_int_lock(card, flags);
+                  skb_queue_tail(&card->hbpool.queue, hb);
+                  card->hbpool.count++;
+                  spin_unlock_irqrestore(&card->int_lock, flags);
+               }
+	       break;
+
+            case NS_BUFTYPE_IOVEC:
+	       while (card->iovpool.count > card->iovnr.init)
+	       {
+	          struct sk_buff *iovb;
+
+                  ns_grab_int_lock(card, flags);
+		  iovb = skb_dequeue(&card->iovpool.queue);
+		  card->iovpool.count--;
+                  spin_unlock_irqrestore(&card->int_lock, flags);
+                  if (iovb == NULL)
+		     printk("nicstar%d: iovec buffer count inconsistent.\n",
+		            card->index);
+                  else
+		     dev_kfree_skb_any(iovb);
+
+	       }
+               while (card->iovpool.count < card->iovnr.init)
+	       {
+	          struct sk_buff *iovb;
+
+                  iovb = alloc_skb(NS_IOVBUFSIZE, GFP_KERNEL);
+                  if (iovb == NULL)
+                     return -ENOMEM;
+                  ns_grab_int_lock(card, flags);
+                  skb_queue_tail(&card->iovpool.queue, iovb);
+                  card->iovpool.count++;
+                  spin_unlock_irqrestore(&card->int_lock, flags);
+	       }
+	       break;
+
+            default:
+	       return -EINVAL;
+
+	 }
+         return 0;
+
+      default:
+         if (dev->phy && dev->phy->ioctl) {
+            return dev->phy->ioctl(dev, cmd, arg);
+         }
+         else {
+            printk("nicstar%d: %s == NULL \n", card->index,
+                   dev->phy ? "dev->phy->ioctl" : "dev->phy");
+            return -ENOIOCTLCMD;
+         }
+   }
+}
+
+
+
+static void which_list(ns_dev *card, struct sk_buff *skb)
+{
+   printk("It's a %s buffer.\n", skb->list == &card->sbpool.queue ?
+          "small" : skb->list == &card->lbpool.queue ? "large" :
+	  skb->list == &card->hbpool.queue ? "huge" :
+	  skb->list == &card->iovpool.queue ? "iovec" : "unknown");
+}
+
+
+
+static void ns_poll(unsigned long arg)
+{
+   int i;
+   ns_dev *card;
+   unsigned long flags;
+   u32 stat_r, stat_w;
+
+   PRINTK("nicstar: Entering ns_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;
+      }
+      ns_grab_int_lock(card, flags);
+
+      stat_w = 0;
+      stat_r = readl(card->membase + STAT);
+      if (stat_r & NS_STAT_TSIF)
+         stat_w |= NS_STAT_TSIF;
+      if (stat_r & NS_STAT_EOPDU)
+         stat_w |= NS_STAT_EOPDU;
+
+      process_tsq(card);
+      process_rsq(card);
+
+      writel(stat_w, card->membase + STAT);
+      spin_unlock_irqrestore(&card->int_lock, flags);
+   }
+   mod_timer(&ns_timer, jiffies + NS_POLL_PERIOD);
+   PRINTK("nicstar: Leaving ns_poll().\n");
+}
+
+
+
+static int ns_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 = ns_h2i(mac[j++])) < 0)
+         return -1;
+      if ((byte0 = ns_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 ns_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 ns_phy_put(struct atm_dev *dev, unsigned char value,
+                    unsigned long addr)
+{
+   ns_dev *card;
+   unsigned long flags;
+
+   card = dev->dev_data;
+   ns_grab_res_lock(card, flags);
+   while(CMD_BUSY(card));
+   writel((unsigned long) value, card->membase + DR0);
+   writel(NS_CMD_WRITE_UTILITY | 0x00000200 | (addr & 0x000000FF),
+          card->membase + CMD);
+   spin_unlock_irqrestore(&card->res_lock, flags);
+}
+
+
+
+static unsigned char ns_phy_get(struct atm_dev *dev, unsigned long addr)
+{
+   ns_dev *card;
+   unsigned long flags;
+   unsigned long data;
+
+   card = dev->dev_data;
+   ns_grab_res_lock(card, flags);
+   while(CMD_BUSY(card));
+   writel(NS_CMD_READ_UTILITY | 0x00000200 | (addr & 0x000000FF),
+          card->membase + CMD);
+   while(CMD_BUSY(card));
+   data = readl(card->membase + DR0) & 0x000000FF;
+   spin_unlock_irqrestore(&card->res_lock, flags);
+   return (unsigned char) data;
+}
+
+
+
+module_init(nicstar_init);
+module_exit(nicstar_cleanup);
diff --git a/drivers/atm/nicstar.h b/drivers/atm/nicstar.h
new file mode 100644
index 0000000..ea83c46
--- /dev/null
+++ b/drivers/atm/nicstar.h
@@ -0,0 +1,820 @@
+/******************************************************************************
+ *
+ * nicstar.h
+ *
+ * Header file for the nicstar device driver.
+ *
+ * Author: Rui Prior (rprior@inescn.pt)
+ * PowerPC support by Jay Talbott (jay_talbott@mcg.mot.com) April 1999
+ *
+ * (C) INESC 1998
+ *
+ ******************************************************************************/
+
+
+#ifndef _LINUX_NICSTAR_H_
+#define _LINUX_NICSTAR_H_
+
+
+/* Includes *******************************************************************/
+
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/uio.h>
+#include <linux/skbuff.h>
+#include <linux/atmdev.h>
+#include <linux/atm_nicstar.h>
+
+
+/* Options ********************************************************************/
+
+#undef NS_DEBUG_SPINLOCKS
+
+#define NS_MAX_CARDS 4		/* Maximum number of NICStAR based cards
+				   controlled by the device driver. Must
+                                   be <= 5 */
+
+#undef RCQ_SUPPORT		/* Do not define this for now */
+
+#define NS_TST_NUM_ENTRIES 2340	/* + 1 for return */
+#define NS_TST_RESERVED 340	/* N. entries reserved for UBR/ABR/VBR */
+
+#define NS_SMBUFSIZE 48		/* 48, 96, 240 or 2048 */
+#define NS_LGBUFSIZE 16384	/* 2048, 4096, 8192 or 16384 */
+#define NS_RSQSIZE 8192		/* 2048, 4096 or 8192 */
+#define NS_VPIBITS 2		/* 0, 1, 2, or 8 */
+
+#define NS_MAX_RCTSIZE 4096	/* Number of entries. 4096 or 16384.
+                                   Define 4096 only if (all) your card(s)
+				   have 32K x 32bit SRAM, in which case
+				   setting this to 16384 will just waste a
+				   lot of memory.
+				   Setting this to 4096 for a card with
+				   128K x 32bit SRAM will limit the maximum
+				   VCI. */
+
+/*#define NS_PCI_LATENCY 64*/	/* Must be a multiple of 32 */
+
+	/* Number of buffers initially allocated */
+#define NUM_SB 32	/* Must be even */
+#define NUM_LB 24	/* Must be even */
+#define NUM_HB 8	/* Pre-allocated huge buffers */
+#define NUM_IOVB 48	/* Iovec buffers */
+
+	/* Lower level for count of buffers */
+#define MIN_SB 8	/* Must be even */
+#define MIN_LB 8	/* Must be even */
+#define MIN_HB 6
+#define MIN_IOVB 8
+
+	/* Upper level for count of buffers */
+#define MAX_SB 64	/* Must be even, <= 508 */
+#define MAX_LB 48	/* Must be even, <= 508 */
+#define MAX_HB 10
+#define MAX_IOVB 80
+
+	/* These are the absolute maximum allowed for the ioctl() */
+#define TOP_SB 256	/* Must be even, <= 508 */
+#define TOP_LB 128	/* Must be even, <= 508 */
+#define TOP_HB 64
+#define TOP_IOVB 256
+
+
+#define MAX_TBD_PER_VC 1	/* Number of TBDs before a TSR */
+#define MAX_TBD_PER_SCQ 10	/* Only meaningful for variable rate SCQs */
+
+#undef ENABLE_TSQFIE
+
+#define SCQFULL_TIMEOUT (5 * HZ)
+
+#define NS_POLL_PERIOD (HZ)
+
+#define PCR_TOLERANCE (1.0001)
+
+
+
+/* ESI stuff ******************************************************************/
+
+#define NICSTAR_EPROM_MAC_ADDR_OFFSET 0x6C
+#define NICSTAR_EPROM_MAC_ADDR_OFFSET_ALT 0xF6
+
+
+/* #defines *******************************************************************/
+
+#define NS_IOREMAP_SIZE 4096
+
+#define BUF_SM 0x00000000	/* These two are used for push_rxbufs() */
+#define BUF_LG 0x00000001       /* CMD, Write_FreeBufQ, LBUF bit */
+
+#define NS_HBUFSIZE 65568	/* Size of max. AAL5 PDU */
+#define NS_MAX_IOVECS (2 + (65568 - NS_SMBUFSIZE) / \
+                       (NS_LGBUFSIZE - (NS_LGBUFSIZE % 48)))
+#define NS_IOVBUFSIZE (NS_MAX_IOVECS * (sizeof(struct iovec)))
+
+#define NS_SMBUFSIZE_USABLE (NS_SMBUFSIZE - NS_SMBUFSIZE % 48)
+#define NS_LGBUFSIZE_USABLE (NS_LGBUFSIZE - NS_LGBUFSIZE % 48)
+
+#define NS_AAL0_HEADER (ATM_AAL0_SDU - ATM_CELL_PAYLOAD)	/* 4 bytes */
+
+#define NS_SMSKBSIZE (NS_SMBUFSIZE + NS_AAL0_HEADER)
+#define NS_LGSKBSIZE (NS_SMBUFSIZE + NS_LGBUFSIZE)
+
+
+/* NICStAR structures located in host memory **********************************/
+
+
+
+/* RSQ - Receive Status Queue 
+ *
+ * Written by the NICStAR, read by the device driver.
+ */
+
+typedef struct ns_rsqe
+{
+   u32 word_1;
+   u32 buffer_handle;
+   u32 final_aal5_crc32;
+   u32 word_4;
+} ns_rsqe;
+
+#define ns_rsqe_vpi(ns_rsqep) \
+        ((le32_to_cpu((ns_rsqep)->word_1) & 0x00FF0000) >> 16)
+#define ns_rsqe_vci(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_1) & 0x0000FFFF)
+
+#define NS_RSQE_VALID      0x80000000
+#define NS_RSQE_NZGFC      0x00004000
+#define NS_RSQE_EOPDU      0x00002000
+#define NS_RSQE_BUFSIZE    0x00001000
+#define NS_RSQE_CONGESTION 0x00000800
+#define NS_RSQE_CLP        0x00000400
+#define NS_RSQE_CRCERR     0x00000200
+
+#define NS_RSQE_BUFSIZE_SM 0x00000000
+#define NS_RSQE_BUFSIZE_LG 0x00001000
+
+#define ns_rsqe_valid(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_VALID)
+#define ns_rsqe_nzgfc(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_NZGFC)
+#define ns_rsqe_eopdu(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_EOPDU)
+#define ns_rsqe_bufsize(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_BUFSIZE)
+#define ns_rsqe_congestion(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_CONGESTION)
+#define ns_rsqe_clp(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_CLP)
+#define ns_rsqe_crcerr(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_CRCERR)
+
+#define ns_rsqe_cellcount(ns_rsqep) \
+        (le32_to_cpu((ns_rsqep)->word_4) & 0x000001FF)
+#define ns_rsqe_init(ns_rsqep) \
+        ((ns_rsqep)->word_4 = cpu_to_le32(0x00000000)) 
+
+#define NS_RSQ_NUM_ENTRIES (NS_RSQSIZE / 16)
+#define NS_RSQ_ALIGNMENT NS_RSQSIZE
+
+
+
+/* RCQ - Raw Cell Queue
+ *
+ * Written by the NICStAR, read by the device driver.
+ */
+
+typedef struct cell_payload
+{
+   u32 word[12];
+} cell_payload;
+
+typedef struct ns_rcqe
+{
+   u32 word_1;
+   u32 word_2;
+   u32 word_3;
+   u32 word_4;
+   cell_payload payload;
+} ns_rcqe;
+
+#define NS_RCQE_SIZE 64		/* bytes */
+
+#define ns_rcqe_islast(ns_rcqep) \
+        (le32_to_cpu((ns_rcqep)->word_2) != 0x00000000)
+#define ns_rcqe_cellheader(ns_rcqep) \
+        (le32_to_cpu((ns_rcqep)->word_1))
+#define ns_rcqe_nextbufhandle(ns_rcqep) \
+        (le32_to_cpu((ns_rcqep)->word_2))
+
+
+
+/* SCQ - Segmentation Channel Queue 
+ *
+ * Written by the device driver, read by the NICStAR.
+ */
+
+typedef struct ns_scqe
+{
+   u32 word_1;
+   u32 word_2;
+   u32 word_3;
+   u32 word_4;
+} ns_scqe;
+
+   /* NOTE: SCQ entries can be either a TBD (Transmit Buffer Descriptors)
+            or TSR (Transmit Status Requests) */
+
+#define NS_SCQE_TYPE_TBD 0x00000000
+#define NS_SCQE_TYPE_TSR 0x80000000
+
+
+#define NS_TBD_EOPDU 0x40000000
+#define NS_TBD_AAL0  0x00000000
+#define NS_TBD_AAL34 0x04000000
+#define NS_TBD_AAL5  0x08000000
+
+#define NS_TBD_VPI_MASK 0x0FF00000
+#define NS_TBD_VCI_MASK 0x000FFFF0
+#define NS_TBD_VC_MASK (NS_TBD_VPI_MASK | NS_TBD_VCI_MASK)
+
+#define NS_TBD_VPI_SHIFT 20
+#define NS_TBD_VCI_SHIFT 4
+
+#define ns_tbd_mkword_1(flags, m, n, buflen) \
+      (cpu_to_le32((flags) | (m) << 23 | (n) << 16 | (buflen)))
+#define ns_tbd_mkword_1_novbr(flags, buflen) \
+      (cpu_to_le32((flags) | (buflen) | 0x00810000))
+#define ns_tbd_mkword_3(control, pdulen) \
+      (cpu_to_le32((control) << 16 | (pdulen)))
+#define ns_tbd_mkword_4(gfc, vpi, vci, pt, clp) \
+      (cpu_to_le32((gfc) << 28 | (vpi) << 20 | (vci) << 4 | (pt) << 1 | (clp)))
+
+
+#define NS_TSR_INTENABLE 0x20000000
+
+#define NS_TSR_SCDISVBR 0xFFFF		/* Use as scdi for VBR SCD */
+
+#define ns_tsr_mkword_1(flags) \
+        (cpu_to_le32(NS_SCQE_TYPE_TSR | (flags)))
+#define ns_tsr_mkword_2(scdi, scqi) \
+        (cpu_to_le32((scdi) << 16 | 0x00008000 | (scqi)))
+
+#define ns_scqe_is_tsr(ns_scqep) \
+        (le32_to_cpu((ns_scqep)->word_1) & NS_SCQE_TYPE_TSR)
+
+#define VBR_SCQ_NUM_ENTRIES 512
+#define VBR_SCQSIZE 8192
+#define CBR_SCQ_NUM_ENTRIES 64
+#define CBR_SCQSIZE 1024
+
+#define NS_SCQE_SIZE 16
+
+
+
+/* TSQ - Transmit Status Queue
+ *
+ * Written by the NICStAR, read by the device driver.
+ */
+
+typedef struct ns_tsi
+{
+   u32 word_1;
+   u32 word_2;
+} ns_tsi;
+
+   /* NOTE: The first word can be a status word copied from the TSR which
+            originated the TSI, or a timer overflow indicator. In this last
+	    case, the value of the first word is all zeroes. */
+
+#define NS_TSI_EMPTY          0x80000000
+#define NS_TSI_TIMESTAMP_MASK 0x00FFFFFF
+
+#define ns_tsi_isempty(ns_tsip) \
+        (le32_to_cpu((ns_tsip)->word_2) & NS_TSI_EMPTY)
+#define ns_tsi_gettimestamp(ns_tsip) \
+        (le32_to_cpu((ns_tsip)->word_2) & NS_TSI_TIMESTAMP_MASK)
+
+#define ns_tsi_init(ns_tsip) \
+        ((ns_tsip)->word_2 = cpu_to_le32(NS_TSI_EMPTY))
+
+
+#define NS_TSQSIZE 8192
+#define NS_TSQ_NUM_ENTRIES 1024
+#define NS_TSQ_ALIGNMENT 8192
+
+
+#define NS_TSI_SCDISVBR NS_TSR_SCDISVBR
+
+#define ns_tsi_tmrof(ns_tsip) \
+        (le32_to_cpu((ns_tsip)->word_1) == 0x00000000)
+#define ns_tsi_getscdindex(ns_tsip) \
+        ((le32_to_cpu((ns_tsip)->word_1) & 0xFFFF0000) >> 16)
+#define ns_tsi_getscqpos(ns_tsip) \
+        (le32_to_cpu((ns_tsip)->word_1) & 0x00007FFF)
+
+
+
+/* NICStAR structures located in local SRAM ***********************************/
+
+
+
+/* RCT - Receive Connection Table
+ *
+ * Written by both the NICStAR and the device driver.
+ */
+
+typedef struct ns_rcte
+{
+   u32 word_1;
+   u32 buffer_handle;
+   u32 dma_address;
+   u32 aal5_crc32;
+} ns_rcte;
+
+#define NS_RCTE_BSFB            0x00200000  /* Rev. D only */
+#define NS_RCTE_NZGFC           0x00100000
+#define NS_RCTE_CONNECTOPEN     0x00080000
+#define NS_RCTE_AALMASK         0x00070000
+#define NS_RCTE_AAL0            0x00000000
+#define NS_RCTE_AAL34           0x00010000
+#define NS_RCTE_AAL5            0x00020000
+#define NS_RCTE_RCQ             0x00030000
+#define NS_RCTE_RAWCELLINTEN    0x00008000
+#define NS_RCTE_RXCONSTCELLADDR 0x00004000
+#define NS_RCTE_BUFFVALID       0x00002000
+#define NS_RCTE_FBDSIZE         0x00001000
+#define NS_RCTE_EFCI            0x00000800
+#define NS_RCTE_CLP             0x00000400
+#define NS_RCTE_CRCERROR        0x00000200
+#define NS_RCTE_CELLCOUNT_MASK  0x000001FF
+
+#define NS_RCTE_FBDSIZE_SM 0x00000000
+#define NS_RCTE_FBDSIZE_LG 0x00001000
+
+#define NS_RCT_ENTRY_SIZE 4	/* Number of dwords */
+
+   /* NOTE: We could make macros to contruct the first word of the RCTE,
+            but that doesn't seem to make much sense... */
+
+
+
+/* FBD - Free Buffer Descriptor
+ *
+ * Written by the device driver using via the command register.
+ */
+
+typedef struct ns_fbd
+{
+   u32 buffer_handle;
+   u32 dma_address;
+} ns_fbd;
+
+
+
+
+/* TST - Transmit Schedule Table
+ *
+ * Written by the device driver.
+ */
+
+typedef u32 ns_tste;
+
+#define NS_TST_OPCODE_MASK 0x60000000
+
+#define NS_TST_OPCODE_NULL     0x00000000 /* Insert null cell */
+#define NS_TST_OPCODE_FIXED    0x20000000 /* Cell from a fixed rate channel */
+#define NS_TST_OPCODE_VARIABLE 0x40000000
+#define NS_TST_OPCODE_END      0x60000000 /* Jump */
+
+#define ns_tste_make(opcode, sramad) (opcode | sramad)
+
+   /* NOTE:
+
+      - When the opcode is FIXED, sramad specifies the SRAM address of the
+        SCD for that fixed rate channel.
+      - When the opcode is END, sramad specifies the SRAM address of the
+        location of the next TST entry to read.
+    */
+
+
+
+/* SCD - Segmentation Channel Descriptor
+ *
+ * Written by both the device driver and the NICStAR
+ */
+
+typedef struct ns_scd
+{
+   u32 word_1;
+   u32 word_2;
+   u32 partial_aal5_crc;
+   u32 reserved;
+   ns_scqe cache_a;
+   ns_scqe cache_b;
+} ns_scd;
+
+#define NS_SCD_BASE_MASK_VAR 0xFFFFE000		/* Variable rate */
+#define NS_SCD_BASE_MASK_FIX 0xFFFFFC00		/* Fixed rate */
+#define NS_SCD_TAIL_MASK_VAR 0x00001FF0
+#define NS_SCD_TAIL_MASK_FIX 0x000003F0
+#define NS_SCD_HEAD_MASK_VAR 0x00001FF0
+#define NS_SCD_HEAD_MASK_FIX 0x000003F0
+#define NS_SCD_XMITFOREVER   0x02000000
+
+   /* NOTE: There are other fields in word 2 of the SCD, but as they should
+            not be needed in the device driver they are not defined here. */
+
+
+
+
+/* NICStAR local SRAM memory map **********************************************/
+
+
+#define NS_RCT           0x00000
+#define NS_RCT_32_END    0x03FFF
+#define NS_RCT_128_END   0x0FFFF
+#define NS_UNUSED_32     0x04000
+#define NS_UNUSED_128    0x10000
+#define NS_UNUSED_END    0x1BFFF
+#define NS_TST_FRSCD     0x1C000
+#define NS_TST_FRSCD_END 0x1E7DB
+#define NS_VRSCD2        0x1E7DC
+#define NS_VRSCD2_END    0x1E7E7
+#define NS_VRSCD1        0x1E7E8
+#define NS_VRSCD1_END    0x1E7F3
+#define NS_VRSCD0        0x1E7F4
+#define NS_VRSCD0_END    0x1E7FF
+#define NS_RXFIFO        0x1E800
+#define NS_RXFIFO_END    0x1F7FF
+#define NS_SMFBQ         0x1F800
+#define NS_SMFBQ_END     0x1FBFF
+#define NS_LGFBQ         0x1FC00
+#define NS_LGFBQ_END     0x1FFFF
+
+
+
+/* NISCtAR operation registers ************************************************/
+
+
+/* See Section 3.4 of `IDT77211 NICStAR User Manual' from www.idt.com */
+
+enum ns_regs
+{
+   DR0   = 0x00,      /* Data Register 0 R/W*/
+   DR1   = 0x04,      /* Data Register 1 W */
+   DR2   = 0x08,      /* Data Register 2 W */
+   DR3   = 0x0C,      /* Data Register 3 W */
+   CMD   = 0x10,      /* Command W */
+   CFG   = 0x14,      /* Configuration R/W */
+   STAT  = 0x18,      /* Status R/W */
+   RSQB  = 0x1C,      /* Receive Status Queue Base W */
+   RSQT  = 0x20,      /* Receive Status Queue Tail R */
+   RSQH  = 0x24,      /* Receive Status Queue Head W */
+   CDC   = 0x28,      /* Cell Drop Counter R/clear */
+   VPEC  = 0x2C,      /* VPI/VCI Lookup Error Count R/clear */
+   ICC   = 0x30,      /* Invalid Cell Count R/clear */
+   RAWCT = 0x34,      /* Raw Cell Tail R */
+   TMR   = 0x38,      /* Timer R */
+   TSTB  = 0x3C,      /* Transmit Schedule Table Base R/W */
+   TSQB  = 0x40,      /* Transmit Status Queue Base W */
+   TSQT  = 0x44,      /* Transmit Status Queue Tail R */
+   TSQH  = 0x48,      /* Transmit Status Queue Head W */
+   GP    = 0x4C,      /* General Purpose R/W */
+   VPM   = 0x50       /* VPI/VCI Mask W */
+};
+
+
+/* NICStAR commands issued to the CMD register ********************************/
+
+
+/* Top 4 bits are command opcode, lower 28 are parameters. */
+
+#define NS_CMD_NO_OPERATION         0x00000000
+        /* params always 0 */
+
+#define NS_CMD_OPENCLOSE_CONNECTION 0x20000000
+        /* b19{1=open,0=close} b18-2{SRAM addr} */
+
+#define NS_CMD_WRITE_SRAM           0x40000000
+        /* b18-2{SRAM addr} b1-0{burst size} */
+
+#define NS_CMD_READ_SRAM            0x50000000
+        /* b18-2{SRAM addr} */
+
+#define NS_CMD_WRITE_FREEBUFQ       0x60000000
+        /* b0{large buf indicator} */
+
+#define NS_CMD_READ_UTILITY         0x80000000
+        /* b8{1=select UTL_CS1} b9{1=select UTL_CS0} b7-0{bus addr} */
+
+#define NS_CMD_WRITE_UTILITY        0x90000000
+        /* b8{1=select UTL_CS1} b9{1=select UTL_CS0} b7-0{bus addr} */
+
+#define NS_CMD_OPEN_CONNECTION (NS_CMD_OPENCLOSE_CONNECTION | 0x00080000)
+#define NS_CMD_CLOSE_CONNECTION NS_CMD_OPENCLOSE_CONNECTION
+
+
+/* NICStAR configuration bits *************************************************/
+
+#define NS_CFG_SWRST          0x80000000    /* Software Reset */
+#define NS_CFG_RXPATH         0x20000000    /* Receive Path Enable */
+#define NS_CFG_SMBUFSIZE_MASK 0x18000000    /* Small Receive Buffer Size */
+#define NS_CFG_LGBUFSIZE_MASK 0x06000000    /* Large Receive Buffer Size */
+#define NS_CFG_EFBIE          0x01000000    /* Empty Free Buffer Queue
+                                               Interrupt Enable */
+#define NS_CFG_RSQSIZE_MASK   0x00C00000    /* Receive Status Queue Size */
+#define NS_CFG_ICACCEPT       0x00200000    /* Invalid Cell Accept */
+#define NS_CFG_IGNOREGFC      0x00100000    /* Ignore General Flow Control */
+#define NS_CFG_VPIBITS_MASK   0x000C0000    /* VPI/VCI Bits Size Select */
+#define NS_CFG_RCTSIZE_MASK   0x00030000    /* Receive Connection Table Size */
+#define NS_CFG_VCERRACCEPT    0x00008000    /* VPI/VCI Error Cell Accept */
+#define NS_CFG_RXINT_MASK     0x00007000    /* End of Receive PDU Interrupt
+                                               Handling */
+#define NS_CFG_RAWIE          0x00000800    /* Raw Cell Qu' Interrupt Enable */
+#define NS_CFG_RSQAFIE        0x00000400    /* Receive Queue Almost Full
+                                               Interrupt Enable */
+#define NS_CFG_RXRM           0x00000200    /* Receive RM Cells */
+#define NS_CFG_TMRROIE        0x00000080    /* Timer Roll Over Interrupt
+                                               Enable */
+#define NS_CFG_TXEN           0x00000020    /* Transmit Operation Enable */
+#define NS_CFG_TXIE           0x00000010    /* Transmit Status Interrupt
+                                               Enable */
+#define NS_CFG_TXURIE         0x00000008    /* Transmit Under-run Interrupt
+                                               Enable */
+#define NS_CFG_UMODE          0x00000004    /* Utopia Mode (cell/byte) Select */
+#define NS_CFG_TSQFIE         0x00000002    /* Transmit Status Queue Full
+                                               Interrupt Enable */
+#define NS_CFG_PHYIE          0x00000001    /* PHY Interrupt Enable */
+
+#define NS_CFG_SMBUFSIZE_48    0x00000000
+#define NS_CFG_SMBUFSIZE_96    0x08000000
+#define NS_CFG_SMBUFSIZE_240   0x10000000
+#define NS_CFG_SMBUFSIZE_2048  0x18000000
+
+#define NS_CFG_LGBUFSIZE_2048  0x00000000
+#define NS_CFG_LGBUFSIZE_4096  0x02000000
+#define NS_CFG_LGBUFSIZE_8192  0x04000000
+#define NS_CFG_LGBUFSIZE_16384 0x06000000
+
+#define NS_CFG_RSQSIZE_2048 0x00000000
+#define NS_CFG_RSQSIZE_4096 0x00400000
+#define NS_CFG_RSQSIZE_8192 0x00800000
+
+#define NS_CFG_VPIBITS_0 0x00000000
+#define NS_CFG_VPIBITS_1 0x00040000
+#define NS_CFG_VPIBITS_2 0x00080000
+#define NS_CFG_VPIBITS_8 0x000C0000
+
+#define NS_CFG_RCTSIZE_4096_ENTRIES  0x00000000
+#define NS_CFG_RCTSIZE_8192_ENTRIES  0x00010000
+#define NS_CFG_RCTSIZE_16384_ENTRIES 0x00020000
+
+#define NS_CFG_RXINT_NOINT   0x00000000
+#define NS_CFG_RXINT_NODELAY 0x00001000
+#define NS_CFG_RXINT_314US   0x00002000
+#define NS_CFG_RXINT_624US   0x00003000
+#define NS_CFG_RXINT_899US   0x00004000
+
+
+/* NICStAR STATus bits ********************************************************/
+
+#define NS_STAT_SFBQC_MASK 0xFF000000   /* hi 8 bits Small Buffer Queue Count */
+#define NS_STAT_LFBQC_MASK 0x00FF0000   /* hi 8 bits Large Buffer Queue Count */
+#define NS_STAT_TSIF       0x00008000   /* Transmit Status Queue Indicator */
+#define NS_STAT_TXICP      0x00004000   /* Transmit Incomplete PDU */
+#define NS_STAT_TSQF       0x00001000   /* Transmit Status Queue Full */
+#define NS_STAT_TMROF      0x00000800   /* Timer Overflow */
+#define NS_STAT_PHYI       0x00000400   /* PHY Device Interrupt */
+#define NS_STAT_CMDBZ      0x00000200   /* Command Busy */
+#define NS_STAT_SFBQF      0x00000100   /* Small Buffer Queue Full */
+#define NS_STAT_LFBQF      0x00000080   /* Large Buffer Queue Full */
+#define NS_STAT_RSQF       0x00000040   /* Receive Status Queue Full */
+#define NS_STAT_EOPDU      0x00000020   /* End of PDU */
+#define NS_STAT_RAWCF      0x00000010   /* Raw Cell Flag */
+#define NS_STAT_SFBQE      0x00000008   /* Small Buffer Queue Empty */
+#define NS_STAT_LFBQE      0x00000004   /* Large Buffer Queue Empty */
+#define NS_STAT_RSQAF      0x00000002   /* Receive Status Queue Almost Full */
+
+#define ns_stat_sfbqc_get(stat) (((stat) & NS_STAT_SFBQC_MASK) >> 23)
+#define ns_stat_lfbqc_get(stat) (((stat) & NS_STAT_LFBQC_MASK) >> 15)
+
+
+
+/* #defines which depend on other #defines ************************************/
+
+
+#define NS_TST0 NS_TST_FRSCD
+#define NS_TST1 (NS_TST_FRSCD + NS_TST_NUM_ENTRIES + 1)
+
+#define NS_FRSCD (NS_TST1 + NS_TST_NUM_ENTRIES + 1)
+#define NS_FRSCD_SIZE 12	/* 12 dwords */
+#define NS_FRSCD_NUM ((NS_TST_FRSCD_END + 1 - NS_FRSCD) / NS_FRSCD_SIZE)
+
+#if (NS_SMBUFSIZE == 48)
+#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_48
+#elif (NS_SMBUFSIZE == 96)
+#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_96
+#elif (NS_SMBUFSIZE == 240)
+#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_240
+#elif (NS_SMBUFSIZE == 2048)
+#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_2048
+#else
+#error NS_SMBUFSIZE is incorrect in nicstar.h
+#endif /* NS_SMBUFSIZE */
+
+#if (NS_LGBUFSIZE == 2048)
+#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_2048
+#elif (NS_LGBUFSIZE == 4096)
+#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_4096
+#elif (NS_LGBUFSIZE == 8192)
+#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_8192
+#elif (NS_LGBUFSIZE == 16384)
+#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_16384
+#else
+#error NS_LGBUFSIZE is incorrect in nicstar.h
+#endif /* NS_LGBUFSIZE */
+
+#if (NS_RSQSIZE == 2048)
+#define NS_CFG_RSQSIZE NS_CFG_RSQSIZE_2048
+#elif (NS_RSQSIZE == 4096)
+#define NS_CFG_RSQSIZE NS_CFG_RSQSIZE_4096
+#elif (NS_RSQSIZE == 8192)
+#define NS_CFG_RSQSIZE NS_CFG_RSQSIZE_8192
+#else
+#error NS_RSQSIZE is incorrect in nicstar.h
+#endif /* NS_RSQSIZE */
+
+#if (NS_VPIBITS == 0)
+#define NS_CFG_VPIBITS NS_CFG_VPIBITS_0
+#elif (NS_VPIBITS == 1)
+#define NS_CFG_VPIBITS NS_CFG_VPIBITS_1
+#elif (NS_VPIBITS == 2)
+#define NS_CFG_VPIBITS NS_CFG_VPIBITS_2
+#elif (NS_VPIBITS == 8)
+#define NS_CFG_VPIBITS NS_CFG_VPIBITS_8
+#else
+#error NS_VPIBITS is incorrect in nicstar.h
+#endif /* NS_VPIBITS */
+
+#ifdef RCQ_SUPPORT
+#define NS_CFG_RAWIE_OPT NS_CFG_RAWIE
+#else
+#define NS_CFG_RAWIE_OPT 0x00000000
+#endif /* RCQ_SUPPORT */
+
+#ifdef ENABLE_TSQFIE
+#define NS_CFG_TSQFIE_OPT NS_CFG_TSQFIE
+#else
+#define NS_CFG_TSQFIE_OPT 0x00000000
+#endif /* ENABLE_TSQFIE */
+
+
+/* PCI stuff ******************************************************************/
+
+#ifndef PCI_VENDOR_ID_IDT
+#define PCI_VENDOR_ID_IDT 0x111D
+#endif /* PCI_VENDOR_ID_IDT */
+
+#ifndef PCI_DEVICE_ID_IDT_IDT77201
+#define PCI_DEVICE_ID_IDT_IDT77201 0x0001
+#endif /* PCI_DEVICE_ID_IDT_IDT77201 */
+
+
+
+/* Device driver structures ***************************************************/
+
+
+typedef struct tsq_info
+{
+   void *org;
+   ns_tsi *base;
+   ns_tsi *next;
+   ns_tsi *last;
+} tsq_info;
+
+
+typedef struct scq_info
+{
+   void *org;
+   ns_scqe *base;
+   ns_scqe *last;
+   ns_scqe *next;
+   volatile ns_scqe *tail;		/* Not related to the nicstar register */
+   unsigned num_entries;
+   struct sk_buff **skb;		/* Pointer to an array of pointers
+                                           to the sk_buffs used for tx */
+   u32 scd;				/* SRAM address of the corresponding
+                                           SCD */
+   int tbd_count;			/* Only meaningful on variable rate */
+   wait_queue_head_t scqfull_waitq;
+   volatile char full;			/* SCQ full indicator */
+   spinlock_t lock;			/* SCQ spinlock */
+#ifdef NS_DEBUG_SPINLOCKS
+   volatile long has_lock;
+   volatile int cpu_lock;
+#endif /* NS_DEBUG_SPINLOCKS */
+} scq_info;
+
+
+
+typedef struct rsq_info
+{
+   void *org;
+   ns_rsqe *base;
+   ns_rsqe *next;
+   ns_rsqe *last;
+} rsq_info;
+
+
+typedef struct skb_pool
+{
+   volatile int count;			/* number of buffers in the queue */
+   struct sk_buff_head queue;
+} skb_pool;
+
+/* NOTE: for small and large buffer pools, the count is not used, as the
+         actual value used for buffer management is the one read from the
+	 card. */
+
+
+typedef struct vc_map
+{
+   volatile unsigned int tx:1;				/* TX vc? */
+   volatile unsigned int rx:1;				/* RX vc? */
+   struct atm_vcc *tx_vcc, *rx_vcc;
+   struct sk_buff *rx_iov;		/* RX iovector skb */
+   scq_info *scq;			/* To keep track of the SCQ */
+   u32 cbr_scd;				/* SRAM address of the corresponding
+               				   SCD. 0x00000000 for UBR/VBR/ABR */
+   int tbd_count;
+} vc_map;
+
+
+struct ns_skb_data
+{
+	struct atm_vcc *vcc;
+	int iovcnt;
+};
+
+#define NS_SKB(skb) (((struct ns_skb_data *) (skb)->cb))
+
+
+typedef struct ns_dev
+{
+   int index;				/* Card ID to the device driver */
+   int sram_size;			/* In k x 32bit words. 32 or 128 */
+   void __iomem *membase;		/* Card's memory base address */
+   unsigned long max_pcr;
+   int rct_size;			/* Number of entries */
+   int vpibits;
+   int vcibits;
+   struct pci_dev *pcidev;
+   struct atm_dev *atmdev;
+   tsq_info tsq;
+   rsq_info rsq;
+   scq_info *scq0, *scq1, *scq2;	/* VBR SCQs */
+   skb_pool sbpool;			/* Small buffers */
+   skb_pool lbpool;			/* Large buffers */
+   skb_pool hbpool;			/* Pre-allocated huge buffers */
+   skb_pool iovpool;			/* iovector buffers */
+   volatile int efbie;			/* Empty free buf. queue int. enabled */
+   volatile u32 tst_addr;		/* SRAM address of the TST in use */
+   volatile int tst_free_entries;
+   vc_map vcmap[NS_MAX_RCTSIZE];
+   vc_map *tste2vc[NS_TST_NUM_ENTRIES];
+   vc_map *scd2vc[NS_FRSCD_NUM];
+   buf_nr sbnr;
+   buf_nr lbnr;
+   buf_nr hbnr;
+   buf_nr iovnr;
+   int sbfqc;
+   int lbfqc;
+   u32 sm_handle;
+   u32 sm_addr;
+   u32 lg_handle;
+   u32 lg_addr;
+   struct sk_buff *rcbuf;		/* Current raw cell buffer */
+   u32 rawch;				/* Raw cell queue head */
+   unsigned intcnt;			/* Interrupt counter */
+   spinlock_t int_lock;		/* Interrupt lock */
+   spinlock_t res_lock;		/* Card resource lock */
+#ifdef NS_DEBUG_SPINLOCKS
+   volatile long has_int_lock;
+   volatile int cpu_int;
+   volatile long has_res_lock;
+   volatile int cpu_res;
+#endif /* NS_DEBUG_SPINLOCKS */
+} ns_dev;
+
+
+   /* NOTE: Each tste2vc entry relates a given TST entry to the corresponding
+            CBR vc. If the entry is not allocated, it must be NULL.
+	    
+	    There are two TSTs so the driver can modify them on the fly
+	    without stopping the transmission.
+	    
+	    scd2vc allows us to find out unused fixed rate SCDs, because
+	    they must have a NULL pointer here. */
+
+
+#endif /* _LINUX_NICSTAR_H_ */
diff --git a/drivers/atm/nicstarmac.c b/drivers/atm/nicstarmac.c
new file mode 100644
index 0000000..2c5e3ae
--- /dev/null
+++ b/drivers/atm/nicstarmac.c
@@ -0,0 +1,274 @@
+/*
+ * this file included by nicstar.c
+ */
+
+/*
+ * nicstarmac.c
+ * Read this ForeRunner's MAC address from eprom/eeprom
+ */
+
+typedef void __iomem *virt_addr_t;
+
+#define CYCLE_DELAY 5
+
+/* This was the original definition
+#define osp_MicroDelay(microsec) \
+    do { int _i = 4*microsec; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0)
+*/
+#define osp_MicroDelay(microsec) {unsigned long useconds = (microsec); \
+                                  udelay((useconds));}
+
+
+/* The following tables represent the timing diagrams found in
+ * the Data Sheet for the Xicor X25020 EEProm.  The #defines below
+ * represent the bits in the NICStAR's General Purpose register
+ * that must be toggled for the corresponding actions on the EEProm
+ * to occur.
+ */
+
+/* Write Data To EEProm from SI line on rising edge of CLK */
+/* Read Data From EEProm on falling edge of CLK */
+
+#define CS_HIGH		0x0002		/* Chip select high */
+#define CS_LOW		0x0000		/* Chip select low (active low)*/
+#define CLK_HIGH	0x0004		/* Clock high */
+#define CLK_LOW		0x0000		/* Clock low  */
+#define SI_HIGH		0x0001		/* Serial input data high */
+#define SI_LOW		0x0000		/* Serial input data low */
+
+/* Read Status Register = 0000 0101b */
+#if 0
+static u_int32_t rdsrtab[] =
+{
+    CS_HIGH | CLK_HIGH, 
+    CS_LOW | CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW,
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW | SI_HIGH, 
+    CLK_HIGH | SI_HIGH,   /* 1 */
+    CLK_LOW | SI_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW | SI_HIGH, 
+    CLK_HIGH | SI_HIGH   /* 1 */
+};
+#endif  /*  0  */
+
+
+/* Read from EEPROM = 0000 0011b */
+static u_int32_t readtab[] =
+{
+    /*
+    CS_HIGH | CLK_HIGH, 
+    */
+    CS_LOW | CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW,
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW, 
+    CLK_HIGH,             /* 0 */
+    CLK_LOW | SI_HIGH, 
+    CLK_HIGH | SI_HIGH,   /* 1 */
+    CLK_LOW | SI_HIGH, 
+    CLK_HIGH | SI_HIGH    /* 1 */
+};
+
+
+/* Clock to read from/write to the eeprom */
+static u_int32_t clocktab[] =
+{	
+    CLK_LOW,
+    CLK_HIGH,
+    CLK_LOW, 
+    CLK_HIGH,
+    CLK_LOW,
+    CLK_HIGH,
+    CLK_LOW, 
+    CLK_HIGH,
+    CLK_LOW, 
+    CLK_HIGH,
+    CLK_LOW, 
+    CLK_HIGH, 
+    CLK_LOW, 
+    CLK_HIGH,
+    CLK_LOW, 
+    CLK_HIGH,
+    CLK_LOW 
+};
+
+
+#define NICSTAR_REG_WRITE(bs, reg, val) \
+	while ( readl(bs + STAT) & 0x0200 ) ; \
+	writel((val),(base)+(reg))
+#define NICSTAR_REG_READ(bs, reg) \
+	readl((base)+(reg))
+#define NICSTAR_REG_GENERAL_PURPOSE GP
+
+/*
+ * This routine will clock the Read_Status_reg function into the X2520
+ * eeprom, then pull the result from bit 16 of the NicSTaR's General Purpose 
+ * register.  
+ */
+#if 0
+u_int32_t
+nicstar_read_eprom_status( virt_addr_t base )
+{
+   u_int32_t	val;
+   u_int32_t	rbyte;
+   int32_t	i, j;
+
+   /* Send read instruction */
+   val = NICSTAR_REG_READ( base, NICSTAR_REG_GENERAL_PURPOSE ) & 0xFFFFFFF0;
+
+   for (i=0; i<sizeof rdsrtab/sizeof rdsrtab[0]; i++)
+   {
+	NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+		(val | rdsrtab[i]) );
+        osp_MicroDelay( CYCLE_DELAY );
+   }
+
+   /* Done sending instruction - now pull data off of bit 16, MSB first */
+   /* Data clocked out of eeprom on falling edge of clock */
+
+   rbyte = 0;
+   for (i=7, j=0; i>=0; i--)
+   {
+	NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+		(val | clocktab[j++]) );
+        rbyte |= (((NICSTAR_REG_READ( base, NICSTAR_REG_GENERAL_PURPOSE)
+			& 0x00010000) >> 16) << i);
+	NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+		(val | clocktab[j++]) );
+        osp_MicroDelay( CYCLE_DELAY );
+   }
+   NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE, 2 );
+   osp_MicroDelay( CYCLE_DELAY );
+   return rbyte;
+}
+#endif  /*  0  */
+
+
+/*
+ * This routine will clock the Read_data function into the X2520
+ * eeprom, followed by the address to read from, through the NicSTaR's General
+ * Purpose register.  
+ */
+ 
+static u_int8_t 
+read_eprom_byte(virt_addr_t base, u_int8_t offset)
+{
+   u_int32_t val = 0;
+   int i,j=0;
+   u_int8_t tempread = 0;
+
+   val = NICSTAR_REG_READ( base, NICSTAR_REG_GENERAL_PURPOSE ) & 0xFFFFFFF0;
+
+   /* Send READ instruction */
+   for (i=0; i<sizeof readtab/sizeof readtab[0]; i++)
+   {
+	NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+		(val | readtab[i]) );
+        osp_MicroDelay( CYCLE_DELAY );
+   }
+
+   /* Next, we need to send the byte address to read from */
+   for (i=7; i>=0; i--)
+   {
+      NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+      		(val | clocktab[j++] | ((offset >> i) & 1) ) );
+      osp_MicroDelay(CYCLE_DELAY);
+      NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+      		(val | clocktab[j++] | ((offset >> i) & 1) ) );
+      osp_MicroDelay( CYCLE_DELAY );
+   }
+
+   j = 0;
+   
+   /* Now, we can read data from the eeprom by clocking it in */
+   for (i=7; i>=0; i--)
+   {
+      NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+      		(val | clocktab[j++]) );
+      osp_MicroDelay( CYCLE_DELAY );
+      tempread |= (((NICSTAR_REG_READ( base, NICSTAR_REG_GENERAL_PURPOSE )
+		& 0x00010000) >> 16) << i);
+      NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE,
+      		(val | clocktab[j++]) );
+      osp_MicroDelay( CYCLE_DELAY );
+   }
+
+   NICSTAR_REG_WRITE( base, NICSTAR_REG_GENERAL_PURPOSE, 2 );
+   osp_MicroDelay( CYCLE_DELAY );
+   return tempread;
+}
+
+
+static void
+nicstar_init_eprom( virt_addr_t base )
+{
+    u_int32_t val;
+
+    /*
+     * turn chip select off
+     */
+    val = NICSTAR_REG_READ(base, NICSTAR_REG_GENERAL_PURPOSE) & 0xFFFFFFF0;
+
+    NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE,
+    	(val | CS_HIGH | CLK_HIGH));
+    osp_MicroDelay( CYCLE_DELAY );
+
+    NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE,
+    	(val | CS_HIGH | CLK_LOW));
+    osp_MicroDelay( CYCLE_DELAY );
+
+    NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE,
+    	(val | CS_HIGH | CLK_HIGH));
+    osp_MicroDelay( CYCLE_DELAY );
+
+    NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE,
+    	(val | CS_HIGH | CLK_LOW));
+    osp_MicroDelay( CYCLE_DELAY );
+}
+
+
+/*
+ * This routine will be the interface to the ReadPromByte function
+ * above.
+ */ 
+
+static void
+nicstar_read_eprom(
+    virt_addr_t	base,
+    u_int8_t	prom_offset,
+    u_int8_t	*buffer,
+    u_int32_t	nbytes )
+{
+    u_int		i;
+    
+    for (i=0; i<nbytes; i++)
+    {
+	buffer[i] = read_eprom_byte( base, prom_offset );
+	++prom_offset;
+ 	osp_MicroDelay( CYCLE_DELAY );
+    }
+}
+
+
+/*
+void osp_MicroDelay(int x) {
+    
+}
+*/
+
diff --git a/drivers/atm/nicstarmac.copyright b/drivers/atm/nicstarmac.copyright
new file mode 100644
index 0000000..2e15b39
--- /dev/null
+++ b/drivers/atm/nicstarmac.copyright
@@ -0,0 +1,61 @@
+/* nicstar.c  v0.22  Jawaid Bazyar (bazyar@hypermall.com)
+ * nicstar.c, M. Welsh (matt.welsh@cl.cam.ac.uk)
+ *
+ * Hacked October, 1997 by Jawaid Bazyar, Interlink Advertising Services Inc.
+ * 	http://www.hypermall.com/
+ * 10/1/97 - commented out CFG_PHYIE bit - we don't care when the PHY
+ *	interrupts us (except possibly for removal/insertion of the cable?)
+ * 10/4/97 - began heavy inline documentation of the code. Corrected typos
+ *	and spelling mistakes.
+ * 10/5/97 - added code to handle PHY interrupts, disable PHY on
+ *	loss of link, and correctly re-enable PHY when link is
+ *	re-established. (put back CFG_PHYIE)
+ *
+ *   Modified to work with the IDT7721 nicstar -- AAL5 (tested) only.
+ *
+ * R. D. Rechenmacher <ron@fnal.gov>, Aug. 6, 1997 $Revision: 1.1 $ $Date: 1999/08/20 11:00:11 $
+ *
+ * Linux driver for the IDT77201 NICStAR PCI ATM controller.
+ * PHY component is expected to be 155 Mbps S/UNI-Lite or IDT 77155;
+ * see init_nicstar() for PHY initialization to change this. This driver
+ * expects the Linux ATM stack to support scatter-gather lists 
+ * (skb->atm.iovcnt != 0) for Rx skb's passed to vcc->push.
+ *
+ * Implementing minimal-copy of received data:
+ *   IDT always receives data into a small buffer, then large buffers
+ *     as needed. This means that data must always be copied to create
+ *     the linear buffer needed by most non-ATM protocol stacks (e.g. IP)
+ *     Fix is simple: make large buffers large enough to hold entire
+ *     SDU, and leave <small_buffer_data> bytes empty at the start. Then
+ *     copy small buffer contents to head of large buffer.
+ *   Trick is to avoid fragmenting Linux, due to need for a lot of large
+ *     buffers. This is done by 2 things:
+ *       1) skb->destructor / skb->atm.recycle_buffer
+ *            combined, allow nicstar_free_rx_skb to be called to
+ *            recycle large data buffers
+ *       2) skb_clone of received buffers
+ *   See nicstar_free_rx_skb and linearize_buffer for implementation
+ *     details.
+ *
+ *
+ *
+ * Copyright (c) 1996 University of Cambridge Computer Laboratory
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ * 
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   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.
+ *
+ * M. Welsh, 6 July 1996
+ *
+ *
+ */
diff --git a/drivers/atm/pca200e.data b/drivers/atm/pca200e.data
new file mode 100644
index 0000000..e78e83b
--- /dev/null
+++ b/drivers/atm/pca200e.data
@@ -0,0 +1,850 @@
+:150000001F8B0808AB5A10380203706361323030652E62696E4D
+:150015007D00E43A0D7014D7796FA5BDE84EC86211A7333020EE
+:15002A00AD89C00A23EA83AA589C7E7C38D8152EB887477677D3
+:15003F0095C39C3DB2AB388CA324C4A509352BFBB085BBD0C73F
+:150054007210B903C92991CCD1B1C242255BCCD81EA5C34C6826
+:1500690006271AC6D36A3A31B976D4A9A683DB4B07BB38265C56
+:15007E00BFEFBDB7777BA7030B2733994C35737AFBBEF7BDEFE7
+:15009300EF7DDFF7BEF7769FFEEAD79F221221E1ED844C3E4677
+:1500A8007EA3BFF036F827CF8597C3AF0C7E920B16595BCE5AA8
+:1500BD00296B6483D83E9F7DBE8FF50BE74A0B45FB1F274FAA79
+:1500D200D82E2867139DF637FD937EF1D55FB0769FE8678BDAFB
+:1500E7007D9BD8885451515172FE27E4138E9FC9949CBFF026BC
+:1500FC00741DF83ECE59823FF23BF89346493F6B4F17C1B3A7CE
+:15011100B3B79C97D3275B5ABFEC3CF9579457703B3CBFEFD600
+:15012600FC38236CA91B5E347EDBFA67F7ED4397956EA4D3C5F4
+:15013B007CE6A567799EFFF5CFC4FF7BDF938BF83E83EDE59F02
+:15015000FEAC24BF8A3C3F2FF9FDFF933CF51EF2FFEC2FEBFA11
+:150165002341C38CBC5F4EAA265F5EAF04BC51F0059FD1419ED8
+:15017A00063493D465A2384E66A0171C30231F40AB5CB5646FC8
+:15018F005CBFB633DECCC614D2DAF622F15D3189EFEA3EE28B83
+:1501A4007D99F8DABE4D7C2418A438AF3129015D7507F1032EBA
+:1501B900E174827F46C82229AE2BC63A9D50E9253960EC005FCA
+:1501CE00F2EDFE0AF12A9D5EBD6A35F1B5AC441A49BAD94F22C6
+:1501E300DECB544F180D1A51FACD8C4A7C034B93DAFD6455A8F9
+:1501F8009AAC5AB74C9542EF11E23DB0946A0F1B0DA10BF0CC0C
+:15020D00F9A4A8097BCA1D751474A02FEC02593C75C9E870D176
+:15022200B8CF352EC3783C379E1C2893C98017C6A57B3CDD0E4D
+:15023700CE32426A9CB99F03FC2E81BF46AD0D06544FD0190B08
+:15024C00C0580B8E897EFDF490DE08FD652E9CFAE911DD5F24FE
+:15026100CF832469DAB1116BE0F3C437B686F8D275C437AC9220
+:150276000542BFF6CC0320B22AB7237E1F5B97A4E927A397490C
+:15028B0064C43AFF0CD8ACCE8886D37F632A7F4C16005E289CAF
+:1502A0003E491DDAFB083513C6B0A6B8E4929626F531E0877479
+:1502B50082E58C9E2503DDD45DC4777E3BF1051F253E09684E42
+:1502CA00C3BAC26825AC39F5225F6598EE23B366227C52ABFC3A
+:1502DF00BC2754E61BD1FFEBAE6DCDFE8D49AAEA38EE89A35A1B
+:1502F4009DF0DCF4254234681BBB09E98536033F2F3C5F835F24
+:15030900107E147E1AE8AA0406A36989DB63C95ADE9F9272EBA7
+:15031E00C17C6131AC4519193457028723BE118D0433D6F063E5
+:150333005C6E1C77EC2981FD118663B2FA3A455F8D11A2D66BC0
+:15034800AFE9B096E6D4A38454D70D004ECA8235541117C7A5F2
+:15035D002D26F8E4B07D3848BA956402FC7BF8EC956CB6B6D35F
+:1503720091EB21B280C218CAB04122B5957583D126189B7D88FF
+:15038700FB2BDA46560F52056C867C6CE85FF1135F19E0C948D1
+:15039C0023873342916798F3A6E45FA58C9021887DB9A8DF9307
+:1503B1002EECF7421F693AB054DE6F73F4FDF414E83A6B66B2C0
+:1503C6000B11C3BA0E45D0D1074E3318C92C24FE074FF267E847
+:1503DB00E03AE67193D635C40D9FD66A65B471CABA5AC66D9C17
+:1503F00081B68DE4F5200AEA316B3E3EF5F8D4CAF0C902BFBC6E
+:1504050003FD12ED00BE39F8E7C4E765F2A6F8BCC8083DA6B648
+:15041A00335DAAA0AFC4DEA66A6CDC8418EA26910FAD6A0821BE
+:15042F0012B4A9C269D1DDAC9DB05A98BD06B91D807702D6021B
+:15044400F02CA479BF88CD3D82BE3F92D49137C262E0EB5969BB
+:15045900D6AC8DA4F4A3A0EB808FEB8570E6F34897F9F77CE4C2
+:15046E0071E4E07C73F2C0FC256AC3208B2D5C834D43BA3F060F
+:15048300F39566B386103FC611E321E23D02F1168A79426C3DFD
+:15049800E159DA32AAA34C083FBA62DC2474847A94BF031D86A2
+:1504AD00ACE5EAEB969CDC4FF3F3216F03DE5414FD8ED3DA3050
+:1504C2005F5AC953795A804F2146D05612811C0DB6A0BC0E67DE
+:1504D7007C6E471FC3A5CFA04B06639EFA201E11FA182E7D3E53
+:1504EC009556913E89227D129F511FDBA5CF05970CF63CF54199
+:15050100BCE097B83EB64B9F4FA555A4CF60913E839F511F752A
+:1505160026AF4FCB4C5E0684CF471FC48B75737DF079C37C69B3
+:15052B0015E973BC489FE32E7DC231AFD997FEF15925301975DC
+:150540007CBC5E33F5D918F2E53E82FD69D1B745FF82E8237F22
+:15055500EC4FB07ED2A4626FD8C3F7363321FA29D11F14FD6938
+:15056A00D13F2EFA9D40678FFA1ACBD131181B507F88FBA8451E
+:15057F00E179507D8362EC4FC2734A7D8786D5D526CF431356CC
+:1505940010E6D51152BB2CE6690F243DED35694FBB17D6017487
+:1505A900B251C766F514A3D3037337AB67189D043C77A9E728AB
+:1505BE00CE3FCFE5A0C8B347ED17F9CDB09A812EE4A09AFBC861
+:1505D30005F3ECCE1F76B0B8059C6AD51342D87777BEC16093F7
+:1505E8002ED82B3BDF613094C9813DB7F3A50E87FE6A95AF1F58
+:1505FD00D259C69E53B447F047991EAA1FDDE8D0747091968332
+:15061200EBC88AB2D5095CA4FB07AA87ED030961D37494DB348F
+:15062700C27225D77D497EBF32958271CE6F8DA0D12CF612E37F
+:15063C00718ED32568206F3FDF874C7B477EAC4DD8310AE35B40
+:15065100C17E683B139EA3EA6178A6D65B4CA65926E72EF555F4
+:150666007A82D977D06A9A610E58F3D80D4F6BFDF4DDFAC37506
+:15067B00E7D67D672AA93DD881720C301B55C6E4D0860EB97506
+:150690007D5DFF3A0A636BD898CDE4AD4C7A42CBDE915B037587
+:1506A50087D7593056DDC1E5477B55429CDCF8B5DCFAAB15AFBD
+:1506BA00AE3B0263FFD3EE69AF8C5584FEF3FD0FDA90E6BFADE7
+:1506CF0030DB70FEBF9C186B43DC4BEFBFDE4682BD8C27C86F5A
+:1506E400B3BC185CC264063DED086BF730DA2418B655D6F63110
+:1506F900394850B53126EEFCD1AC2EBD1B83F83B6D56056C5662
+:15070E0027F079B3565739DFC3A2AC8D591AB48B37FD4097B6BD
+:150723007D4527CA41F38E00D6C48665887A30CEDA5E6BA09CE8
+:15073800EF7568CF8A7EC03FF80DC05F6B56078280AFB25C86D9
+:15074D00F863ACEDB32658DBC26CBEE04780FFEEB7017F9BB98C
+:15076200301001FCB0C5E54E5A0DD0BEC8D6618FD53893DFDBC0
+:15077700489D0A781A5B9B27616DFAD4435409C08E179C365B01
+:15078C00B86D2C5EB34E5BCDD0CEC0B98106CBBA25A29A87AEC7
+:1507A100676BD0977601BC4A7DCDC2BA15ED575E1DD7B78610CF
+:1507B6008FC715EE954F0A5CB4B78837139F9F079E8AEFA21E32
+:1507CB00DF9814679714AB9163E99F59FEBDE3263A704FFA4DF8
+:1507E0000BFAD400D9FCE1115DF1C541C7772D591DB7BA1C7929
+:1507F500D4BBCC1B9F701EC761BE22E4A1429EB736E6E5C1BDA9
+:15080A00EE92C09D74C933790B79222E79BA401EE8535A429E39
+:15081F00F3ABF2F23C2B785CC43812F24C0A799A5CF2E05E759D
+:15083400BFC0457F73E4C1E79BC91376C9B319E4813E4D9690D5
+:15084900A7D925CFE55F711E6D33B8A771799007CA73BC252F86
+:15085E000FEE3567392EE35506B935DE3E625D87B3AC9363DDC5
+:15087300675D387B325FEEC53DCA370CF1D064D2707F1F9E1BAD
+:15088800BCCC7732962CFCB60AF76B17AFD80C1694A4D6EBDAB7
+:15089D0047E58DFC1CEB75E1E10563311E21B6794C95704FA00C
+:1508B20031EEBF8BC93DD0270326EC0F8A54674771FCCEF0B040
+:1508C7007E67F81CD864D8EA401CC819480FE1811DBC76E5FDFE
+:1508DC00733A83FDD508D6AA24406D9DCF3FA75FCC66FD65D592
+:1508F100FDFAEE7BF332F5F0FDC225936D769033AD01550A3A24
+:15090600BCF12CBF86F184F305E007567C68E59EDB3FCCF1498D
+:15091B00D79F692B73E8803CC25E4CAEDA152370463A4A2DE42F
+:15093000AB34998BC0DE1BD01C0AA7C5715314ED0FC74F4B510E
+:150945005ED2BDC9319893001F18B3A2AE734B17D4E2CFA89EB1
+:15095A00D6B7245E6394E2F350520E95A6DD6079943780F65B70
+:15096F00507B1C857AE36D0B6B12491D8133EA88E6D41A72B92A
+:15098400A835607E52D421448C255D7548EE0F723FD656E84744
+:15099900CA3D28974DE33C4751AF90CFEB9603D61BE545BA8197
+:1509AE00906D2A44D446CA190BE550DE5F85B273DF637264CCC1
+:1509C300C15E487501388B928C8974B4ED9C4E8FD80F395D9B32
+:1509D800D9A7F6FDFD5482B3B6141B358F92514D3A30CEEA2EE8
+:1509ED003EC7B6108744E478BE6ECB98555F46FA54D0E77A23D8
+:150A0200FDE876AE1FE7932AE0C3EC226CC2EC98E676BC7347DE
+:150A1700DC0A446C361675F3A48267306C72595A4C85D9A5D310
+:150A2C006467AB60D0E4761AA00C1E19A6CFDE057584F27DAC4C
+:150A4100810A64F09F5845DD6B073896ACC05936324E1D3FC1D0
+:150A56001C843796C7485C2391FD168998CC2EAC0E807119F419
+:150A6B00A52D86899716E555719D1E5CABF77860FDA686D87D2E
+:150A8000881FD74839ABCBEADB34C06AE6FC196F49F9DC3367A7
+:150A9500FF9653FCBCE83E774E9DC198FD9433E7203F734E0EF2
+:150AAA00E7CE9BECEC19F9BEE5F8961C30A2634DFCFEA0D0B70D
+:150ABF00B82FA14CBDC23E6C6D4249E6574419B2081DA247F1E2
+:150AD400AE02FC0A7D81D9CC00FA74C84ADCC82E72F9336B3524
+:150AE90075186487D8A757CCC5B06FE37D56B5BAAAF912D674D6
+:150AFE0012F13EA3AE0D5D83985C9FF6B7B3DAEE31CEB713DA06
+:150B130045E420F33B90DB12700BE117C47D4058E0468A700568
+:150B2800DC42F87111EF0EFD1E316777D11C01B710DE2BE8F75C
+:150B3D000A5CA30857C02D84B709FA2B05FD06818B78F8BCDCC9
+:150B5200956F1A5D63F88C67293C4379C18FCAAB46C037862CF0
+:150B6700B497ACBCA2E37A07D5613B00F6AA091FED901553AFF3
+:150B7C00EDBFA257A9A7AC65C6076D814DFFADCBB131EB44D2FC
+:150B9100D3ED8D9966269B5D0C355EAB1CBB62393E5B09B92DA1
+:150BA6007D3DEB73C7C0B7A0CE95599D4AE7C4A388AF5C5E4121
+:150BBB001ACAA1213D513EACA16C353B1A2C279ED9DA634E30EB
+:150BD0002027A4DFC63C22E273C22A8E67F405C61362C61D27AE
+:150BE5002FDE11D7C365DC0F1591D33E2D4E5E82FD3B17230768
+:150BFA008634CC078AD84F31565642CAC2B3E0D3AC9E17310500
+:150C0F00F1F318F89BA8DF73B0FBC5B9E2E6B1D4226269A8F448
+:150C2400FD8D2B9E7ABEF0DBCFD57473E2296C3D2DEC7EBCF2E1
+:150C3900AE00DF13950DDEA802CFB7FA713CC25A35E0ECA52AC3
+:150C4E00D412F544A96ED2E3655F78CA23E0B4C678CA19C73BC6
+:150C63007A25DCF084ECD008279EA8719E37E5E1B9FD8ADDB182
+:150C78000DC0764CD423AADC4D73B519BFDF7C84EDF7B3589BA5
+:150C8D002978178F2324729206D4F666ACDF181C6C7FFDBEF62F
+:150CA2003F04FFB4091D3E8BEDE2C8A08EF7A1481361354A427E
+:150CB700BF0075C79CFD52F0EFBA09FFF58CFF80C9F2281DB6EB
+:150CCC00918E943ECEE946809780E173BA047D6A637DC3E9E326
+:150CE100FD30D41426ABD5A0BF066353F5B7AD57AB426111E732
+:150CF6002175793BD0A435CA01DD9101E36E51513FF72CF85916
+:150D0B00533FD0D6AB0F846AD4079A03EAAAD056276FA94F71C2
+:150D2000DA82A6E43B3E87AEF48FB786AD4E2F6F75EEA36584E2
+:150D3500837D8F64208743DE10F7CD8B56A7E5565C0F7627CD82
+:150D4A0071E811C84132E2404C200ECA9A85BA8E1AFB35425244
+:150D5F00980BCDECDF9F97C1AF71CF55D02E2C2EA660BF823D2D
+:150D74006135190E61FC6476BEDEE1BEA7FD9C787F107F84E908
+:150D89005860EF2C9930495D2A9AA76D08DAB6C1624F81FD644F
+:150D9E0072445B638C94A45D2168373E42BCEE7D285F5F65CC2D
+:150DB300E4D7B03E3172F5C9FCF381CDF301E856321F28AE3A51
+:150DC80028771E688C4A5BD641CD07B107B58A72379C210E6DFD
+:150DDD00D477415EF648712D0AAD1C4846132A3F977C1772DDE5
+:150DF200B1E4C7CDE4EA10BDF6B5FC7B8D3D5FFFDDFEA623C476
+:150E070037F149D60767196DF37D72BB73D787F76764B77176CD
+:150E1C0012DFEDED4E9E9D62ED24C612B4E9B319F6CE0FCEC553
+:150E310060A795E28EC5592B49ACD55EA03DFBA77C1F408D2F19
+:150E4600C19925111ED61AB1FD22D431CC768DCC76686BC46913
+:150E5B00025948755C5BFE89B05F4C62F603E3079A805E15C03F
+:150E70007F7E9F7C2F5BCFEDA2BE82166B17AC59900EF6BB59E8
+:150E85003D95F781473ED50706C49DFE70491F5072FB7DC6422E
+:150E9A009DC136B6B08D2D6C630BDBD8689B72C8E56E9F99AF8B
+:150EAF003DF1DD13D451C14A757F10CEF8BE3C6C2DC00E06535C
+:150EC40005B03F02D8D1E09803AB42582DC056042711C6EE3D4A
+:150ED900B87DDFFB18EC09763DFFF15CBBBEF730F18D7D8C764C
+:150EEE006DB877BE7ACD579F7809FF2813FE1105BE17B615CA1F
+:150F0300D922135F23C8E20159979490B511E67899AC4DF7DEFF
+:150F1800CE1ACC57DEDE12F2960B795F0759976C9BEBCF06FAC8
+:150F2D004B095F8E5DCBFACA408FC8B5B97AC4804EF81AEAE194
+:150F4200BFF7767DE976F4E929A18F2CF4F9F956E2EB84DF675D
+:150F5700E1BFF97F4127B5812A6A1365EFE620074AB029B701EC
+:150F6C001CFB32E934357C0E6AA60AD659AEEA96A26EFA5B76F9
+:150F8100970E79676B6C88BD2B8E7D53DCF73CC76A5433FD0D60
+:150F9600A89D643847E33B55DC9401EF62EC9455F5C419EBC295
+:150FAB00479C3601BAD9858639057D89F7BD631F15CA33267057
+:150FC000DF83B68B244DBFCAF9118DF3433EC8CFDE5DC86F3932
+:150FD500E0553D71CADA0AFC3441837EC4F9C5043FE87BDDF609
+:150FEA0054843DCD3FE1EFB8AF3E440AC61789F15D62FCBDA29D
+:150FFF00F11A31BE558C8F158D2F16E34D623CC1C63366D79E29
+:15101400FC793F0B3A5202FB37ECD5DEE52452707687BF81A5FC
+:15102900B646E14C41EA923BF0AC5963EC5F87EFF53591D70ED8
+:15103E002C9DD53AC22F873A5DF7E92F4C3CF113B4D573BB2F35
+:1510530075045DF0CBAFFEF57584B7EEF84987FBFE7DFA8D6F83
+:151068009D40F893FFF0E30EC2BE871834E3FFFC179BFC0163E8
+:15107D0047B297F8269F24BE3972BAEE17827F59B87FCB380E23
+:15109200F9167388548D39197231C24AECC74EAE81B351FBEE40
+:1510A7002DE2DE07700F6C19D52A638F065F811671F66EE7672C
+:1510BC003C1C73CE320C5644AF8EDFF7F1EF332E0FE8F683F8F2
+:1510D1001D01FB1640C47E8ADD2918BE51B6571056CB2419BE69
+:1510E6005F39CDEE52768B7B1784A9EA283B4BED71C18202D67F
+:1510FB00E7823509D8DE99FCB707866B1CED4B26086954472D8C
+:15111000370CBF436C2882554932692E84518A67BFD838550E10
+:151125008DEA2D3826F4C6EF6508BD9BD99D8AF91FDC58F453B2
+:15113A002F9B9FF345D18A7E649C4A07F09C0338ECFD3DE713EE
+:15114F005647E93EA827B19EC2F3EE65F0B7441FE9C6F74ED3D0
+:15116400397FE1B66DACE2760DA74FE6E40CA74FD3FE2DE3DA2C
+:151179006675DC72D37C79E98086FB33D28C15ECEFA3ECEE6226
+:15118E00AB80ED1132EE113206605F6732E27B2576864DE1DED8
+:1511A300CF6A05B6F78BB51C106B298B6F2998CDA06605DE16C5
+:1511B8007EFF9280338317CFA17866127A7845AB14B5176F64D1
+:1511CD000BEA546EDF93EC5E0EF76903F4C3332E3E3B30F2F086
+:1511E2005C58991BC6EAE794D509272B493C6F56381C6C66A124
+:1511F700DD6A33CCCE0143C8C160013B1AD89812E727389FC223
+:15120C009C5A03D60DD688B591717321D2A3A356297C52029F42
+:15122100E4F0DFE4F605183C5B7B9DCFF944FCBD20F4E4B19C55
+:1512360062758BE4E804CF57A514F3F7A03F3FFEF296FCB8034D
+:15124B007BA9044C7E782ECCE386B9623AE7DF22A69C7875C78E
+:15126000727F512C633B25C66E36C72831C7196BC4F68BF9B97C
+:151275009590BB8DBBC902278FA04D5E747C0E9EEBA7E37AAC39
+:15128A00687CC1E594CE69A4CC1648B68998A71B7CAC06F7016D
+:15129F0073733E27A17F605C38637DEE31F6ED1BA7C35A178D76
+:1512B400CE221A8E0DB80F7298510C037A2F38307F1E66948027
+:1512C900555617C250A7FD2E9D1D58BC04ACBCDA0D334CBB4EC1
+:1512DE0026E1D5C23EB08F60CEC0B8F483CF634D85DFE4B17ECD
+:1512F3002015AD75BD4B225584BD3342FFF533FF1D311D3FAFDB
+:151308003C84DF1BD87400BFB50BF35C568A8672DB34600CF7B2
+:15131D00176514F12C2D1717498AF91CF3E12ECC25D0C77907C1
+:1513320097A634461F7DC54F6829B8E2829B6EFC25A5E10AC018
+:151347007B9DEFDEEA788E75DB6BAB74137BF94BEBBAE0B20DCC
+:15135C0067E4D1BE83504BB03C301FBBFD1669A19EB75A03F3CC
+:1513710076E4FACCB40AD7D51679DED9AB793E2EB475613E2E11
+:15138600210BCCE1B2A44CD602ED85480F6ABE927628814F729C
+:15139B00F885F2ED75F91DC6AF543D37BE49F5DCF82EAB9E1BB7
+:1513B000CBA404EC15DFDCF8F654CF8D65B90886F847DC73F32E
+:1513C500EF3C2B79FD8531CEF706B469BD6BEF83D6D825BEDF9F
+:1513DA0020AEBD50291A935D63FEA231AF6B6C49D158956B6C58
+:1513EF00B922F611E52D4A1493CAEA307BCFC4BF63A4F41A6BD3
+:1514040007E9F532BEE765581B34A1A82072F5889E30C635FCEE
+:151419005676B13CA21F2B1FD78E854735AC55BE639CD3BC1730
+:15142E003FD0192E201F360E68CA5653AF81BC5CE97AFF8BDFE1
+:151443008FCAE638833F17AB0ACDB8D613DFFBFFD37DFC7B9AE7
+:1514580058EEDB1B80CFF0335F65F2D7CDCB92DFC4EF4EC4B7BF
+:15146D003313ECBB277E5F3EC1BF8D080E50FEBD0C1538830C25
+:15148200A7D7F57E03DF9F3F2BF84CCEE17347011FFE7DCD0460
+:15149700FB7ECAE1630B3E5D820FC719345551A725A13D119479
+:1514AC00BA2B0E8DE8FEF02AFD353C9FC4EE6E0BC42A425745A7
+:1514C1007C5D8ADD139A85672FD8BF5E8BEBD433DA5719F3B4AB
+:1514D600E33A292ABE8B033BBE097935297577A9A72C388AD66C
+:1514EB00C8CA5A88EB03B42E7CB0ED30665CA5DFC46F5D37FF53
+:151500003B9CEB22BFB41AD45F5ACEFBE836F58015560F5BFEA9
+:15151500F408FDBFF6BE3E3A8AEBCAF355AB5A6A498DA816ADA6
+:15152A0046C2209588708447715A422648964C43182F78306934
+:15153F00639CAD12C26EDB644C1C26A3DD61E7704E58BB255AC4
+:1515540020E10729D548462638B4B064E30938322B123C47248E
+:1515690062E275F02C61B48CC390C4269D19C626332456BC4A65
+:15157E0086CD38F4DEDFABAAEE9210FE9839B367FF58D5D1A9A8
+:15159300EA57EFE3BE7BEFBBEFDEF7EEBB657887B6D5087BF17D
+:1515A800081FA63A83A941B22B5F3491CE945E0EDF6E779BEBA1
+:1515BD00BF3ED0EC2E5FA1FD996EDA75A02C9E5157FCDBF00DF9
+:1515D200AF6E8D4C2B5F4CE523EA336693FA8A5DBE77C6F2D17B
+:1515E700E31818D5AD80254CEF6AD47623AC7673ACFB9A2CD1D0
+:1515FC00A6A93F37BD12FC228E7293F5B5C9B184594CF2CC8307
+:151611007DE9E8A0E98BF59AFED8A869EDDBB8F9F8A4CDC7F152
+:15162600297C9CE1DFB1214D71F16F51CCDB98E151EC1B61AFE5
+:15163B008478348FE466095BA45B7DABB6FA16196876F3735093
+:15165000ED364231F94E6BBFC1E0F0E51DF97BAC8FC45BA1DF9D
+:15166500AF6E60F987CA929AA22E16B459053AC0F5491D31629D
+:15167A00EA5123A26EE04A68756B1FE9A75864EF1B7F41737C57
+:15168F00777BEDF1DB6FF95B14BBFD285AA9BF3945A7743575DF
+:1516A4008C67CB1C31B9ED0FE7E415FB9AE349AD9878DC5D3E9C
+:1516B900AAF61A1BA87D8DE0D0D483F47FD56853AB8CED6A8D70
+:1516CE001157EB8D2EB5C930D45544BB477493FD595B754AEC79
+:1516E3009FB6F553FEA43A6A1C51B9D1F7EC515EC28EE97336A4
+:1516F8003DCB17BC759527367D92772E58CC776DAAE5BB9F6D89
+:15170D00E05D6FADE04F2F38CEB166F2B91FC0892426ECBAFDF3
+:15172200CF9EE2FED387F59EB7F6F262B677A91B2E3205F38BA3
+:15173700D455CD99B46807AF92587EB13B4D74A083F39BA4BF13
+:15174C0071217D43BA16EB3032FB606FDDF89E191DFCFD821912
+:15176100EA235E1B79279D5F953C6C88B1053FE0CB37DAD7F014
+:151776008388129F788B3A85AE7290F2BC1FCCFA9DF8A6FB9DCA
+:15178B0010AF1E14B65E3B7C7A4CE13F4D63DF4B32A32F49FA86
+:1517A0006CD3104F596B5EA6DF3A5F31A744D87D9326DEAB6A39
+:1517B500027BA94167BC63FD5E8B55124FE0EC483B8FBBDA56CD
+:1517CA0066F0C3F1C5A85D3127C44DEC57F6A9528B323AC0AF33
+:1517DF00D96D627F734A9BF4DE37ADCDE9FB071B5CED3357FB1F
+:1517F400EA0CEDCBAEF6E7CFD07ED5C76C1FFE3589863077601A
+:15180900010C3BE65830CCA7B6A6B7AF8CBE28DA526303A46BCC
+:15181E000CB732A5D384EF8F4CB67188DA9D1F1B309E5EF06B13
+:15183300E1D331E9F6F371EDCFAC7D2AEB3F22E52774A9ECA464
+:15184800BE7EACB3D1B78E0B5D46B92FA995EC18E1511F8B60C6
+:15185D007C96EC18E4317A866F01F21B296F0B337E6D62EF18A4
+:15187200699E6969D4D712C77F24188AB5865929DFD939B88DCC
+:15188700190F70F08FFA790234A4B5FACEEDA1F64EF292D096AF
+:15189C00D6B93B8EF208B5118C5B3A33F2083F10E3707FF1B807
+:1518B10021F67738C13277473D27B9DDD6B177ADF0F3098696FE
+:1518C600B576DCFB29FD3CE1E9B598E74ECFA5FF20CE4084AD8B
+:1518DB00730562BF0D739DBD9F2CF6434331A9B94059AFA36E52
+:1518F00094654A3397A5C37AA7381BF0B258170EC2C732BA3C2A
+:15190500B35621C717E9589F484C785A426F35F0D08A7B74362A
+:15191A003E6286562CB6FD5AC4BA96B557611C3597624F3D3A72
+:15192F0018BF4DDDB4043693D88735068633FFCA603C4875F9B3
+:15194400F32BF52BE974E08DE57AD3E34F7A9A1C5A5DA0BEB02D
+:15195900F0761BEEB69BC2EDB954A1CBA79337C21E5E6686ED09
+:15196E00F593E9F04346032FE883D59719FA30FE0D731DFA6039
+:151983003C175F29FA10113028D1B80EF80D35A70577C08F3B83
+:15199800F15EC92CCC25E37BF8E0EFD285428F540EC7C7976FC2
+:1519AD00AA1FA5BEADA2BE39EF77FCCE75D410FE24048BAFE8E2
+:1519C200A8E085B93B4EF00999C598B16838A00CEA993335F4F6
+:1519D7005B8D25E8FD31FE3EDEC37710FB414A3B6C06E386DFF9
+:1519EC006E7FA97D597EF71525048FB3FA041F233316FB9D6202
+:151A0100BF69D883FD6A137BBB57D3E950D6FF89C6CBBFB17C5F
+:151A1600625F767D5894CF961DB6FF8DCCFFB4F5E2B6988F27FD
+:151A2B0053DF3715E2733535305C1CDA4EF56CE1C154A5312B41
+:151A400095D3B22AB5D80884DA88DE63A2CE10CDC92CFAFBFC5E
+:151A55003519FBF21A87AF8EFCFA2384C752BE16FABD021FF809
+:151A6A00A8F0B5EA8DD7697F1EA96DBE47F5349FF75EE1A1C844
+:151A7F002797826E1BD2E9C249C9B193BA8D3CD02D2AEF32DA11
+:151A9400E013B89683D6C85743F9CEDAF9C2A91554EF6A739572
+:151AA900C573C38286F6BD26760FEF16FB8295246F5682A68619
+:151ABE0045C3A9F70E09EB8657787A391C7107881F3FAD4DE607
+:151AD30058F517F101FD41755663B13AABF6A5CA924673E18293
+:151AE800C657F18EE018BC9E2E5CA84A8D024E85F4B072A3D58F
+:151AFD00C7FAF9E51FA7F333E7F1C60F5B7B8DE387CD4365D585
+:151B12001AF58C63BFABD7AE9FCA37A32E8DEA72F23AF9B6524A
+:151B27009E7D6B06B45D34C6D0875B49E64D6F6BFB8FAD3D8A0C
+:151B3C00522AFFAA64C185737B5D180BE37163BE7F500FFE6E98
+:151B5100BF8101BE3AF5A2619DD34A3333F282D647F5CEF3D710
+:151B6600E8A42B97C0D7BC865DE189C837DB70F62E89B1BB66B3
+:151B7B00B16E12AD72D990EE25FEC7DE506364A89129CF59B491
+:151B9000B1F5378C6159F0994A70455A05FCCA73E69B3F4AE70C
+:151BA5001FF558FD5C49B46E9A81A6B751DBD3FB34F8A3B4D82E
+:151BBA0013443D5BEDF2F36252A3779C6440740FF7F8137A2424
+:151BCF00B59F375D4AE73B6573699CE02CDDA88D779CE714B2F2
+:151BE40080F0E0E4E9A777E2D9788AC77F98B6CE3F529E37DF4D
+:151BF9009F8A7BF0E04CB012FE4B4A53ED46FD050BE783D3CA28
+:151C0E008D02FFE07371566D0F77708D3371AB530326C73E7BDE
+:151C2300785ABF5957A6DF472F5AFD06ED515F1BD55763D34CAD
+:151C3800F0E6A0C59BF04B97D6FCBE1EA5F7B73AFC67D3C2C2FA
+:151C4D0063B60EEDA2056B1BF1C4C8BF583C31CF5FAF8706A432
+:151C6200468737B662BC5BE73B6DDE209D3F32A42D64279AC1E8
+:151C770017C18661C10F667CA1E6EB7E519769AE5C68F381E5DB
+:151C8C00F73A95EEA493FA42CA61E2F9765E447C116CAFD595EC
+:151CA100376C7C3BE3C15506F6D44CF8BE1DE39DF203E737B4A5
+:151CB60043E3E928D5E9F8D3E4BA78631BF186FAA6C51B12E1DB
+:151CCB0057B67963EDDF5AF8157B568493CBBFCDD20FF5CD8A7C
+:151CE0006ED1B59F7CA99E649C599D5A6356A5569824E74C9214
+:151CF50083C60CFB4DE21C33F4143FD901C2276E063F6FD2937B
+:151D0A00785E51B7DE06BF4CD2DFA09749F9033AF6B5BAEC73CC
+:151D1F000BF02D7B50F8330D0B5B4AF877E34C34D94F87CA4E3B
+:151D34006ACC1812BEDE4AB2D384BDBB2A794AE877F1E410782A
+:151D4900728EA2769AABD453A641F712E388FEAA6ABD67954398
+:151D5E00FCF24DF43FD8438150B4750FE66FA58A17939CAF263F
+:151D73003BE676F8E23B7633D96915DA55BD27B2BF0DF8F35FD4
+:151D8800C8DACDADEA7DE6DB3F93EE94957BF52A7505AF225DD7
+:151D9D00FE92EA6D0ED2FD5BF04514E36C0F2F3D9FCE7FAD5214
+:151DB2006E249BDC207BD698C78EDD15211B50B6694D76AC01A8
+:151DC70059D9E44A433E25146EA53ACD2ED5DFECF8589692BDD0
+:151DDC009BC72C3D0DBF8F52DDBB95A7DAA08F55CF618580CB48
+:151DF1006D67BA6DCCE80FAD71D029F257F36BC5AC9029EF99AF
+:151E0600412A0399E186C9E2BF7A7E3BCD0B800F7004D5970D68
+:151E1B00F4218F781FEBE76C7C31749E6068C5A774F41D7A90B7
+:151E3000F774834E6085AAA82E21038C1E1E559FE1AFCE26DABE
+:151E45002BC46754DE6907766090F41F4A33DD6DDE934E8B36D0
+:151E5A00855EA51C364748472B8FD9B22C6AC939D4D9361BEBFD
+:151E6F00F6614A3B2CE0F2B2BD7A0FE08AEE3121A71CBA396BC1
+:151E84001ED13740BBF704ED707E5C55498F573A877DEA1DA687
+:151E99007705E940D1BDA26EE8F5D0E9FDEAB366CD75A929AB9C
+:151EAE00CF583470F0EDA6C360C0F2BFBCD97B1E60856E7A0042
+:151EC3009EB1BFA139D2A117E495526526D6701D7AD4E49355C5
+:151ED800774DE7D7A87D9E2F9E7AC72C095D6CCD49922D7DFE54
+:151EED000A8D8D2FF00AED4FF425B16FF37D9D27DAC43E90ED91
+:151F0200971EA6BBB087B08608C28CC1C77DD80C8BB3DCDF30DF
+:151F1700E684CE50BB3F249952DAEA8B3D493AED72CC2B66C792
+:151F2C00BD0F918E7BBFD171EFBD7A774A024E857E9B87392E76
+:151F41001CB5F5DB2ECB2F8FC66CCCF6B31F16F76ADB0638627B
+:151F56008CDC073B2B4265B6087D3A2D7C44525C444EC819E394
+:151F6B00E1582D9F9DCC69093CBB9E074EB7E852F2040F40BFA2
+:151F8000879E7C1F7CD25F163EB87DEC9B1ACABD4FCA53DF677C
+:151F9500BF69EDA7517DFBD97734B6ED1847F4903EF66D0DBA40
+:151FAA0018E4771F7B4D832F2DE089920E87FC80E55689059E76
+:151FBF00227915185BA9DF4DFDF12B8FE8B2FA9C81BE3A7E50F6
+:151FD4002C7A9FD931EB5EE1039C4F3031638DD9C70632FE501A
+:151FE90028CFA2CF99AF125FFACE8DE86CA4D384FD04FA0FDB39
+:151FFE007BCC68ABE8524AEFCD7941E8F56BB1AE3C72CC1C63DA
+:15201300225F1CFA3CFCE2B0B78867D80758AF213917C7BC7364
+:15202800ADE27073B9EA69F49D233DBCFE18A7726DED852B5BE4
+:15203D00A05F930D5A124DBD635468DFD3674BCF0B9DD8E33F85
+:15205200A1236F09FC1BEA87903F7270672FCD457DC236050FD3
+:15206700887764234E923CF49D3BA8CF3BF79CCE2607054C382D
+:15207C00EF437C2564FC1FC6D6365978F986E13BFD72960F9219
+:15209100DE0C1F84C007842787F6BEC82ABDABCD63D111F4336D
+:1520A600BA4D879E4FB31735879E2CFA0D73103ABACD2B53F1A0
+:1520BB0067E1EDABAFFF9186337253717644E0ACA44DF848DBB0
+:1520D000381B06CE026CFC581CBEDAD79EBC7789A80FB6CCA84C
+:1520E5008533165D346CFD77F65B733B1B9E3B7A42E047AC7FA6
+:1520FA0019C7CC02E8C5935FE16F104B127F9414A53CCDC0ED45
+:15210F00E39E2796FE69CA736728E66976F30978C373E91DDDF8
+:1521240027BDA077AB599C04A78C0DCBF6035D2D7EB6C60AF085
+:1521390094E1F5E9769FBDEF6EAAEBEFF49DBBAAC367D9EAA701
+:15214E00C1710644E83FE1301778D9F669AB7FCA401C6BEB2D2E
+:15216300EA034BB0FFE1659D7A70C75745FFD8E6E778EFD81756
+:15217800493E7CD174D93BFDC2D6C19E36E18245474CA78E203C
+:15218D00D951852E5C8C117F2797AF6FBA7EDDC2C76FCA9FA8EE
+:1521A200DD40FA96859FBFD7EBA4273F003FDF27FC3CFFEF82DF
+:1521B7009F97809F7577E98E6D9E6661CE267F6EE1836CE6DE14
+:1521CC0038EC050B1F0BD8223DF8FE62818F9C68A95E1E2F6F3A
+:1521E100BE45CC71FB782CF5AEB9ED97B0974B5BE9D9807CB1CE
+:1521F600EC1FC21BFBE32C0F2A583310FF84BB0117EE5E369D99
+:15220B00F6A6E3AE9770B78D70F77736EE2E12EED07EC8D5FE2D
+:15222000B55F586D4F6DF7056D5FFE505666DC5C56CC71C98A24
+:15223500A64C3EEBEC74667C2CC4BAEEBAE7333244D8E7E3545F
+:15224A002E3EA44D2EF70AD8204B204722A4E3418E604D0A67B3
+:15225F002050EEE9944B9EE4BCA461FF0FE736F05EBCB3E449EC
+:15227400896FDD4BFA016A5BC8946D4362CC627D0A7D2E76F53C
+:1522890059A63EBB65CD74FFA551F8E6285C2FBA64E801D20F34
+:15229E00FA9232F547354EDE27F4CA906B8F8C063EE99034B72D
+:1522B3000357276DFE4079DF42962F8FEFD495F33BB5A9FEFC1E
+:1522C80086B0357697F56B42B7945917703526D65C86E20BB10B
+:1522DD00A743632327752C2EA74EC47D7ED27308BF05F1A1C669
+:1522F200E2A841F301E73D29B94556137CAEF217382B182AC2CA
+:1523070019BA0C4C5161776CC799BBF2D5ADD0B1587958D821CF
+:15231C00D095271EB07C67AFE15EBE8CE6DDDF3F207CF7C2EA17
+:15233100F1D40333FB731B87B07ED12FD62F76931E5C34D629A3
+:15234600D609D8B89C301A8E58B04787E245F06FB4D33DB1A12D
+:15235B00B8D2302CFAD7C3F66A384FE85B47F3883104BD08739A
+:152370008C41735BA67FC591C146A7ACB58F9B79BE019E6B8741
+:15238500B2EB31BBCB86B5A2B80D8F711378285D215B1AB0B83F
+:15239A00DB94ACFD988ECC5A8DBD3F8FBD68567ED2DAF7B4FD65
+:1523AF00110977167EACB5F4A978543A0F4CF12FA27110BF1F74
+:1523C4007D305A31164897E928B77CA2E21D94DE1B4D6ABE22CE
+:1523D9004B2F4B114C5772E258F32AC4FBE87ACB96C1DAC1428D
+:1523EE00DB8F6AB5E09123F1FBE89D03BB809BF26BEBC51EA3A5
+:15240300D8075C6BE7D35DF956A45EE8A0FF38D28DF558CFF983
+:15241800761CF31EFA82FD4FC0D1BB73480B8ED9F050BAD3EE10
+:15242D0016BBBE9E19DAED77B5BBD5CEF78C2BDF4A6A17F99220
+:15244200AE7C6D76BE8119E05B99FA661C67108E0818BF1BC70A
+:152457001ED815824521F89E16BC734CF8CDF47E8F6065D6BA3B
+:15246C00E01517ACDBECBA5F9C01D661170CDBED7C2FCF00EB9F
+:152481004957BE1D76BE573E0056D0D881F5EA87C07AD5056B9E
+:15249600CAAEBBFDFE1B61EDBA3F0BC3653B5FF7FD37A1E7FD24
+:1524AB00597A5EBE093D2FBBDABDE2D0738676FB5DED5E75E845
+:1524C000799376B76CC0F81A050E8C0977BBE8AFBD7E8F74875A
+:1524D500E71FA7FCFBD95086E737782D9E7F5FB2783EB621DB46
+:1524EA0076D46BB5FDF0860F681BFBA576BB4E7B93AE7E6EB0F7
+:1524FF00EBF8E2CC751818B38E6FCEB5E9328FE4CBE5C1ACBCF8
+:1525140013E359ACCD22E69421FC099832624057F1F93BF5DD6E
+:152529006586A6D0FCA0AC3DA597F213FA3E9A12DC32E809EAA9
+:15253E00D74CB2C729179286B4FDA437825F2CFFC7BD644B24A5
+:152553003BE0EFBD52D4959D1BEAA82E0FCD0B19B94A7A896F04
+:15256800DD9090A9429E3AF3451CF27448C853650DC14F725439
+:15257D00723D4F9D8FB0DFDBAF4965869EC61A18F6DAADB37498
+:15259200F6DC346CB56FD8F234DA678A35D5F121C3EACBA0069B
+:1525A7007F17EC0B189897C85E99ABBCA2FB48E7C6DC34E37C24
+:1525BC0032945D0FC73A1CF6944270D7856FBEFD9BE6F339D379
+:1525D100D7C2AD75DCA3864CF348A0419C756301E917FA933497
+:1525E600B72D10BAC911331264D1D7688EF6150D0A9F0E2B1601
+:1525FB0058B5D807DA5DA66AAC7E2F6F13FB0B3D64EF579AE254
+:152610004CDCFFEDFFC3BD1C30B36DEFF238D159C0EE7ABF99F3
+:15262500F4B6E5F4FECBEC1F9722C609FAB0ABEC88735E5915B9
+:15263A007D237D48F44321BBE2E3FE0FF4F29215EFEAD9B4A1AD
+:15264F00E175B1F412A7AD0ACDB2D53F4A5BF1E48D7535C6BE43
+:152664002BEA8A7E40F918952B5D33B5DCEDB18796587433CC8C
+:15267900D5A09B5D2E4C79FDD3F2D6C52AECBC2A17FB43365DAB
+:15268E006591AFC75C3061C77B237CE26C5D5D2CAF51ECBDC060
+:1526A3007F53E44F7092F13109F989A7113746B6E2C319880F2E
+:1526B80027CE3F132F89B399E383A67B3FA9F5886B3F087BA603
+:1526CD00DB388F603F272A6C1B71BE5251E0FF613FAB3807990B
+:1526E200DD0FEA75955F18931B9D3D21D421AFE17A8F589B1E44
+:1526F7001C9E74EDA33AECEF63A3F96FC977E57CD9F39EF47519
+:15270C00E951E9335250BAC6FE8EFD35FB2E7B990DB1AF319314
+:1527210075B30EB683FD2929DA5F628FB147D9436C23D3D8036A
+:152736006C3DFB1CBB97FD015BCDEE66ABD84AB6822D67CDACF0
+:15274B008935B206B68C2D6577B07AB684D5B15A16669F66B73C
+:15276000B34FB11ABA7E8FDDC616D355CD3E49D7AD6C115D55E8
+:15277500747D82AE857455D2A5D25521AE72BA16886BBEB86E5A
+:15278A0011D73CFB2A1357A97DCDCD5C21FB2AC95C41D735C7EC
+:15279F0075154FB902D32EE5866BF60D57D18CD7AC9B5EFE0F7A
+:1527B400BC0A3FF42AF8C857FE875E3E26CE181FB7D6B754E111
+:1527C9008B4EBA3DE9F151A61E475A15A519F673353D8FDBCFB4
+:1527DE0035F4AC48F48C35BEF26ACA1FE1065BCDC7C95E5024A0
+:1527F3008D2BEAC2E34A8CFE93F49F5A785C8C2BB11E522DF667
+:15280800DB55D54ECBB7D3244A8BD96905769A87D292765AA167
+:15281D009D964369A88FDA14B02B2C1AB6EB825F60D8AE037E98
+:152832008061BB2CFCFEC254262AC16651F9F04B5828BD8D33C0
+:1528470075E1016F6899A8272AE2EED41AC549A9CE776631CD9E
+:15285C006B95077C671A843C887FC0BBE10F78073F38BC2BA2B2
+:1528710077946E225D7EBD41176D8F2F361DDC3938473FC66D43
+:15288600189561411343153E7661037E200CFD1465C2ADC02FED
+:15289B0070A878ACFCA96FDBEF99CA9BD485A661D7C35EB1D235
+:1528B000A3942F8A754A65A129FBC8A6BFC93D2A65F38CBB9EA0
+:1528C500154FF6D9B0D345FD63E9D988ED64F947AA7C42ACE7CD
+:1528DA00A8563C48B12EAA8A5891B9F6B3F043F310AF617D2323
+:1528EF00BC8C477FE16109CF32332FA4B6CA91067DA2B241C487
+:15290400A998F8AEF70EE004BFE12B3046E5FC6C19CEE49A35C6
+:15291900840B529EB84FAD33F3D86D04FB6DA63714691D9F2168
+:15292E004D91AC34A6BE67C2BEAACE99AE9FA98648037FD3FD64
+:152943004155AA9389664ABCBD4E86FF8C5209F9ED634A2D354B
+:15295800B9CCA81171196A4DA6002E55C012F594521B8B0DB4AE
+:15296D002F9F29D595F8E27ADC7DF1C575B847D1D7F1B090F94B
+:15298200C82BF818F29DF489B81813CB7844F8D946901E4BCFE8
+:152997005E4D79A2464CAC776BF659FC188F88B5932DD699FCEB
+:1529AC00709B75469E6DB7E3F076D9677810F764360195205DD9
+:1529C1002461182CA141FFC1EFF796CB77B0F10EC3AFCA752C4C
+:1529D600BAD39844BFC655C35A07B2CE2DD7A472EA449D549F2D
+:1529EB00386783E768AFD15516D72431FFF59A9973E3D1FFDE24
+:152A00008FFECD579F3664F575D21913464495EF78E9A197A820
+:152A1500FED7FA918EB41A4A3B302DAD94D2764D4BDBB69CE0E0
+:152A2A0052DEEEFFF243EFD49262C6FDEA25633B3BC3E7AF7812
+:152A3F00476FA9F869EDD7965DB17DA5E362BD6D41FC027C739B
+:152A5400845EB160C5395D4E0E1A9EF223ADAAFAAD7E664C9A2E
+:152A6900BEAE5EA193637EF68DF52D0D618EA47698F10DF3161F
+:152A7E00E91D1D7828603FAD1D7EE8974B0906A2E92582EB9C07
+:152A930021F17DFAD736BDCD6F89D05CACEC330967F1EBCB57B1
+:152AA800D5FABAA9BDE8CE617624176BE5C33E1F7483DD077C9B
+:152ABD00ABDFA1F4FD07ACDF2FD8BFBF7DE00CCDB7C9EBD3C29B
+:152AD20060477FD89FF09C3180B7ED6C8CC7BF935BF7C413B97E
+:152AE70075581F3830FE356DDEF9FD5ACEB937F4BBD985DA799A
+:152AFC0063A774C41685FE62AD2F8E8BBE96287FAE4F3E7CC918
+:152B11009C27BD46EF2F98BEF8B1A5D88342DD130F9F337CC974
+:152B2600DCBA912773EF10671887451C98E3090FD90CCA090325
+:152B3B007DF9EDA1CEFAD069AA5BF9078EFE28803BFABD611FD5
+:152B5000620529BB8F2B02FEEF1F7F9BC66081BAB70B6BB57987
+:152B65006A67BC27CE3589F49389CA1F1AD05192A4777CD58762
+:152B7A00F83ABC6BA2F2D726E5DDF34BB5E68E28E5DF115B5586
+:152B8F0007BB4A8C2DE59821279F2519E9AD8B8D1DAD63C60920
+:152BA4006302BC47EF317ED653BA9C3C46FCD3297C13ABEDD81E
+:152BB9006D62AC197BC53E47B5186706F725F7D2388B73996858
+:152BCE00AB305E8FBB8FF13ADC113FCA97ECA33AC68C36EC2BC0
+:152BE300261386E4231C85C74CFC061F627C9CF92EDE0D923EE6
+:152BF800493A7F3261DE16E7227D8CD2F11BFBC81395DCF0C751
+:152C0D00E4BA00E96B1827139583E63CB22746AF7BEF205C9AE6
+:152C220067DE4FFB1482A789DAB8807115EEE71324839BA85E80
+:152C3700963C68BE91F1E7EE32FC8481300177A193D733E374C9
+:152C4C00BFF0632179D61D39A7FDE51772EBFA59A7D61FEFD3ED
+:152C61007E5925DFD14FF6DADC44AFDEB3936B3D0D17C55EB4FD
+:152C7600927ABAFFB645E59F11BA5B383E8C9824810509C4C806
+:152C8B0032E627459FAC182F3EE8A37111C3A55AC6DEFBDB8611
+:152CA000B41AF18A5F357BC6B806FF415FF22095DB0D3C8B58F6
+:152CB50057A5A9A7CDF9A9AF9BD5849BF9D4D7642EF634778BAF
+:152CCA00F159ADEE16BEF1A20E85236E9FAF54FDAA217CF63673
+:152CDF00714EE50CC806393662D4A7BE437977D358E9A579EE05
+:152CF400A08E3A3D6787442CE5B6BF60AC0CF62FD980885722D7
+:152D0900AFDBA5539F0E78D6EDD3B7E4B1A9E3631CB16EBAF850
+:152D1E0057DE92BFFFB424E82262000553F033EA177E46C029AE
+:152D3300E2FE045942AC5F8A78669FB77CBF906E8EF56AA5A9CF
+:152D48005DFDE1D4F3496505D77DA403E7ACE5BAAC72D3EF4371
+:152D5D008CB783BC3AD2B714EBBE25EDC43FEA102F8DE3F709B1
+:152D72003364FF464C0D4BEE937D9AF2D7210DEBC2BE8CFF7CCB
+:152D8700BF8881E3016EC67B8D2A4107EB8CA2447637CEE55AC6
+:152D9C006713AD3DBD37A92E896000ED413FA9EB981E5AD069C0
+:152DB100EE8F58E7DA104346FA032EF47445D0D1107E6A8AE003
+:152DC600EBE78E5F1636449788B723A748868D4F0A3C07C84604
+:152DDB00F60B1F837E116FC72362565BBE6BCC78CA443C46C088
+:152DF00029A5BEC2AB539FA875CB76C874D04FC8F58C4C27F975
+:152E0500BE25B72598FA8D29E22C1E07FFED32F7FC2E5D520F81
+:152E1A003F3B9B06C07130B5CFF0F05E3DD068C5BCF3906C4040
+:152E2F00DC763FEB5DE2271E99C77A6B41BFAD2EDEDC6AD1C9B0
+:152E4400D88E7BF99556C15B4437FC96C5B982B888E3047A8367
+:152E5900BE22361FC632D1733A2D6551D6A2C387E1F623D3083F
+:152E6E003470E3DEA1C7C7A04181A041BF1DE7284B8B0FA58182
+:152E83006B6E75F03FF62DC4A72C0ABB69511CFB64ADB04BA3BE
+:152E9800EDC3D79EFCE452259647F368BB01DB13B28A7F25B730
+:152EAD00EEE2BF38F2C79217A544ABE205DC94A84FA01DFA68E6
+:152EC200C53AB2DA407BA83B130F23A31F59F42873D1628780A9
+:152ED700A7CB3AB740EF11574B49ED33253521F808BF35D77979
+:152EEC00E0A0AB7CF0F399F8E022AE783F1B12FB163ED0BC7DCA
+:152F010048F3AB5E218B15F007C99EB288257B32E38EFA2C6D48
+:152F1600C1FC4BF735E7040E689C06FB7F6BF53735EDB313D89E
+:152F2B00A3290D75B596D1DCAFB2E4F1301B3B1E61A9E3D72E7F
+:152F4000A4F3E93E4CBF87297D58B3CBDF2CFF9137A7E6AFB6EC
+:152F5500F3631E1FFD8DDBFF14F32C8BBACB8AB8C362CF8D1B4F
+:152F6A00D79F94EF6882BF8B3A1AFFEA99535A2E9917B2DADB5D
+:152F7F0085B84805EAAB5D38FF68F9838F8933508A3A42764A45
+:152F9400C2F0A716D5C97F93CE071ECE5E4AE74FAFD30D9B3808
+:152FA9003B48659BD4678CC99F71D2890C1173C9E2C33F330520
+:152FBE00EF5AB19D267A483FAC233DE8E3F0E6C4CF7A8D84A7CE
+:152FD300CB6047E496899F7133E1E9379BD41EF337D7313EE251
+:152FE800B62DD26E5E86EE13361CBDD48AEB6E1C34A0034B3494
+:152FFD006E003BFD3677FC17D2FBC25113FAAE83B39387B2F8C4
+:153012006EFD7516BFF3110B22544A7A79BB7196E0DECE629CE6
+:15302700A5C2FDD88FF2A7AA8DDCD3B5FAB7E28B6B3B972D9B72
+:15303C00A22BE631F8892E13FA539EF428C98F35869A8AF63372
+:15305100E557A6CF57A9C3D7D4D213172E2D205BC453AE8936F5
+:15306600105F167AB6B7BD562F8AD4D617509B472BEE5C3A3F3C
+:15307B00A5D25C576D94A6C286E46FD43B9FADE5DE31B2FBC612
+:153090009799CE1E37621B0A5D5D6927FDB1AA56C65E6AF49146
+:1530A50061663C7CC0D785F301FF49DC53CB3D7764D7834AED9C
+:1530BA00B882D5B69C0A939C5AA6414F9FA74A61118F54C869C1
+:1530CF00B241A83FB22B2695357F3E3F5C3676502FB3D7338382
+:1530E400340F63BE65E3AF9801E9B25E5759F419567EA695B16D
+:1530F9002B5C61FFB03428BDABEF53BD75D005C32413E74B43AF
+:15310E007A49E864AB2FF20F4BE6D3DD73AE4FF8AD601D6FEEF2
+:153123000AC4C23B69FBC7C4F9159AD78BE327F4C07CD24B94E8
+:153138008EE18FFA3F97746FDCFB97E7D517C7F2C82679C5604B
+:15314D00A9E78FBF41E3687EEA6903B24AC411A0FE07683EBE55
+:153162005AD6BB2442BA0CE69B07BC86D05F7EFC0CAF75749694
+:15317700ABBF4BDF546719FEDFE99BEA2CCB6EA6B344DFE129A5
+:15318C00E7FC27640CE1CA57F985A505620F0F783BB53478FAFC
+:1531A1001754FF29BE9E70B77F99E19CA917780AC54732780A6C
+:1531B600A516D5FAAE604FB963587EFD14E473685E6A562DFD13
+:1531CB00368A5325E25E905A20EE06C9F199FA4FF369BD67F568
+:1531E000BBFAE56B691FF080BED6D87D9F78DFEAFBFC19FA8E7C
+:1531F50032C3D73E7EFF1710DD4B2147A33DFD6DBF9966BF5070
+:15320A001FC55AAA8B9E6BDD79A27DC63DAC9F6F201D3DF78709
+:15321F00BD4B54F5345920295E2C91DEA55C3083365FCA381F10
+:15323400A4FCC4DCCE2EF280745587DCBB65C5BB7A78EC3BF5FD
+:15324900B73458F8BC85BD63F975517BB7C05E095F143805CF3D
+:15325E00C1BF133EA2E5296F9D9367EEE94B19BCCFA5B28ADA53
+:15327300DE1F2F4CB420EF56ECE54447FA117B31E7CC29BC3BCF
+:15328800E0397B4A37D26427D1FB6AF50B4B71073C93CBF3EE4B
+:15329D00D8F6D04F491FFC89E1535F3E50A3FEC85C3669F73195
+:1532B200BA6BD88FF8AAC9F5DC7A86ADB46BF8CA72ABFC49C28D
+:1532C7005BD0AEEBDAF2C796DECE5EA813B68E2B2FCAA20E77B1
+:1532DC00B9B394A7E3A90B5A58FD8F4B908EF99154E5CFE1F98C
+:1532F10019CC91D15D49E89F285B803D65C225BE1F33BFE7B062
+:15330600C0EB3C957402BBFEA229CF736B3DD6737F813ABB967E
+:15331B00EE86A7C8D2CB51BFE7EB7B74A1037C876405E9576696
+:15333000E4794D8924F4529A3B443C16C53A83F361FF34A7745C
+:15334500495DEB75F8193265A7987794129A3FEC7947CC41785F
+:15335A00F731E61F67EEB1E7A119E71FCC3D9883EAC599FBCC51
+:15336F005CC3D5316BAEB1DA5159A9FA2B730E74099A977C6AF3
+:15338400B7B9E5BDB4AF20C9BB0A08EEFA48A2CEB1FF4A691EE8
+:15339900439ED5EF39F4EE349AEA2CBB716254BC37F03E4CF834
+:1533AE00417996BC6C486557881F39D98D5D3C37998817533BB2
+:1533C3004A72A41F632E27758AA34EF01A78AE5AFDD901F0D4A9
+:1533D800C5F71DFDCC8699607DA3E7591AC3EDC6D65F58F3BB94
+:1533ED005897A5B6467F951D5F8190E5EFE1B3CF1924432C9F18
+:1534020074CFFC2B01EB77DCCBF23748AC707B0ECBC77986F8A2
+:15341700D8D46F3B4DFC2D4D4C1711129DBA46FF6DF46FD0FFC6
+:15342C0030FD8FD3FF04FD2B3FA2F73FB2F28ABD83A0EAC46896
+:15344100E467C7F0E1884A1DB1F06F555B97B0FA5F755EA47C51
+:1534560015FDCF100EAE926CBE6AB2FA761EFB4B7B9DAD7E8045
+:15346B0027E9B947F5B62834AEC7DE465F470DC4BC14E73EEA46
+:15348000BB39DAA977F43BCA1FA7FCB7B2BDCDE299DA539A5FFA
+:15349500340CF4433961C0BF36B4E298CE627B39DEA3EC7EF867
+:1534AA0063217634D5592CEA7C45B429621B7D601DC77851E412
+:1534BF005433F2A3CDB9A11BFDA9597082F7B2ECFEADF0DF0892
+:1534D40066CFECBF799A1AA43993D59FE216AEE1DB316CF5B34B
+:1534E9007E888B36C73B0DAC81948C1DD3156AB7247E54878F79
+:1534FE009EAC4E4DC31A888FD202E35D7ACF84A7C5FDCE47EF92
+:1535130042CA7FD3FD64878E601FD4D97725F8FCCC155F697AF0
+:153528000CA57FE37B9C131A0922364775EB55BA7B953A1D3188
+:15353D0072FDB14A3397CA5C0BB142F9BFB61B56BC617917D2C3
+:153552000629CD1F1BA03967917E71B9749757E9D0C7290DF1AC
+:153567004BBB903F5669E07C8D8A73A29426C706CC61A4D3B355
+:15357C00417C7B9164217CFC6BE63271C68307B23609CE1C5FF6
+:15359100F558F1B2B6219DE007C079471077F45DAE9C7F464330
+:1535A6003F4AE11BA48C709FFF98BE8E6CB339AADC92973A2800
+:1535BB00E2B610FCC5A114F2EFED90BA7B853F27CE18DE966C99
+:1535D0005AA2F82D5F0BC466982DEA1C3AE0A33C3E2B4FB034D1
+:1535E500292F918EDC589645F304DE0C9AF7AAE658DF8082FF11
+:1535FA00BDC3E7E373E0979EC8F887C9EAA4D95A427822B92460
+:15360F00137EA2F4CC66F0E72F25BC5BDF3CF96B737BEA6F0F36
+:15362400C897DAF5CBC553EBC7F744DAECFABDA1B156CA6B50E9
+:15363900DE7ED4B58764E75A196B3AED343E2254FF6AEE53F13D
+:15364E001C6D853FB7447A2EE9C2A65FD5CDB504838C33722494
+:15366300A7F17D95F96A3B87DCDC537646C37AECAAD43F13CFF4
+:15367800FEC05C4DF724F1C25ABAAF4E7DA61FB410FEE76AEDB5
+:15368D00011FD5514FF59706587E581DE0169C5FE8079C388F73
+:1536A200BD92F475D8309EF258EB9EB2360DF23E42EDC4A82EF4
+:1536B700CA773C4AF7CDF41FA1FA733167184D7C436AB919A3B7
+:1536CC0076A2A9FDFD38F3B09ADA89AA3D070E521E8DDA3AAA9F
+:1536E100D8676C4987403B84C0923D6549AD551D306304D7662A
+:1536F600AA7F82F07335C00A7D54E632DD714E9EF8CFBC46CFF7
+:15370B00ABA88E8900CE1924F8167A7E35007E575BDFA4FB6AE1
+:153720004A93296D989EFD418B46D371ECA697F7525CDF4C7926
+:153735008B55B939C745CFB03A48B8867F31F68427699C0F9A63
+:15374A008A4A73DA349A875F9B2AAFE12FA284734806776564EA
+:15375F00301C123057DF4DD38107E71CB78D7051CE386594ECF6
+:1537740018B4E4DCB663960C8AEE35E0D33217320A6B58888D3C
+:1537890019957765DADD3669CBEF1E9249581B3C684A65713D24
+:15379E00E37F4678A0692F9FE62E23389BE5E3AC06D9408DFDF8
+:1537B30012CB273E32A89F8D7E1AC705EA82CFACCA63F9F3E96C
+:1537C80019DF929195DDFA28F169BFC20ABFCCAA1A494008FC7C
+:1537DD003E42CF09C5C26B39AB6A8EDA721FB240C04675602E4B
+:1537F20088501E12E685CBE8AE282FEA997874341F5FA8686FF7
+:1538070084CFA788BD46F7ABCBBDCD0AE5DB5A7CE31872C60954
+:15381C00F6CC300E2798F50D3C278F9FF860A7FD9DBA72D5DBD0
+:153831005C4DF5E4975F213A1D9A820B85FA73167CA3DC26CE74
+:15384600D0EC8AE13B033DC613915ED1FFFC4FD3FC46F800FFDC
+:15385B002D25B4416F7FB508E30EFD7AD1C01CD1C83A455EA932
+:153870002CAC77E663AD63C0AC72FAA5741B32E916D5E0BD40AC
+:1538850017740503FE7E0AD581323EF5457AFF2B33BFC82AC7C6
+:15389A0094174DE0783BE5CF75CD5B6AD1D4F59B6B45AC3048CA
+:1538AF0079268AC0F35546428C936A314EE0DF0499FBE6F5749D
+:1538C4003EFC750AE289466FB45A57D54AE3CA72A97994CAD023
+:1538D900382EF4BAEA2FB6CFD665ED55D598781B01B576EAA8ED
+:1538EE005B39FF7B22D6307CB5B09624931C95A9BE2292F7E8B6
+:15390300C7C44303F50FD2337CAD64711EC8D603E0E3F536F634
+:15391800F82CF8E5D810C9A4170D712EDDD255D3C53407E3FBCC
+:15392D0055B2FA8C69CB9300BE0D09F926C33FC2FEFEC424D5C1
+:15394200F3E318F1606CC85E334EA7FF6A6CB0D14FBF713687E5
+:1539570045DBF9F642969F5B4934B4EBFE2DF5DDE90BD67050D7
+:15396C008785AB230257F0FB0DB1458D4E7F0AA80F93158B9A3D
+:1539810071CE7372162BACB8981D3F326414D96517C986FC2802
+:15399600FDC4FE9B809FF409C0E21D233B3B3A4A750D4057248D
+:1539AB003C7467FC45BC946FB480E5DFA30276AB6F778D250892
+:1539C000B735A60337F611FFC7B19F1CC5FA83A82F84B58C55BA
+:1539D50007A03FE3BD903D36BEEAA7ED4F59E36515B7F15B828E
+:1539EA00FD0F3FE5298EC9CD9B73B26347C9AFC1B7BB047ED1E3
+:1539FF00D6BCB141D2A306B1A79816E71AE9AF3CF242E3453F3E
+:153A140070F373DD194F23F6F95EE80781F1A7F4C0F9255AB02E
+:153A2900BD419F97929A049F19DD7C07CDEFBE50360E0960AEE7
+:153A3E00639F6A862E3096EFD0CDEAFB6FD177AA0BF97136C947
+:153A53001FB3E28A20CDDBB448C46C9745DC120BA6BF2298E058
+:153A6800DF081AECC7593A9A0333F44D791BA103CAA9534601AC
+:153A7D007BA57132ED6D9E97F23607A7F58164880F78925343FB
+:153A920037A72BE1183038FCE84F9D30FA52DE9650D3908009B9
+:153AA7007066F4D570B5A08B22F42AC0FD29EC41091CCD8B0F3C
+:153ABC00D43B78D94C32D76BD3CD473A5451A5D468F5CDC2C5E5
+:153AD10075C1E30306C64D89DDCE3F52F98FD3DF85D4DFAA4288
+:153AE600F437ABCF839FB723662FF1815F75F529F6B2D117930E
+:153AFB00B27D52AD3E59F850459F9CF3D2363D44BF0CD798284B
+:153B100076E10E7A01F0E6F4674EE84A2BC9419A7F7F6EE6B539
+:153B2500D3F852F6F0373C969E087C6C60D9710F19B839730EAD
+:153B3A00D7D28BC4B96ABBDC412A071E49A28C4B9E857F379504
+:153B4F00FFFDEA51923983FC4F2343B5CA8E11D12F5FB2D374B5
+:153B6400FA0C79017E2F16B1E59F35DDB20973899C7CD6EC230E
+:153B79005D5321FB3E4032DF9F841CE3A6D5CFE12C9D934346AA
+:153B8E00A8E984C04788FA60AF6F171440BE8AF1C5752BEEFC13
+:153BA30015D1DEC2152FB8C697859FA2D38322FE2F70F7C8C3D1
+:153BB80096BF2BEAFF0EDB4B7C3068049A3A6DFE1FCCF0FF61C9
+:153BCD00E27FC0073B05EB7C820F62E003C3E683D10CDFCFCFB8
+:153BE2009FCAF747057F0E66E9AF8E18A82BCBD3832EFA774D11
+:153BF700A33FE0D96BF537293763ECB8C70C7ECB49C4324A1863
+:153C0C004F936CB360B6FAB98BF8DAE1E110C1574C7CEBC07AA5
+:153C21008D602D2758DB7C37F2ECABBF01BFBAE08D11BC311723
+:153C3600BCAA1B5E4B36BAF502A5A953C0EBC8216B6EE8123030
+:153C4B00CB31CB87CF8824DA8A5C7362EADA541EC4B91256DF0C
+:153C60006BE0FB53B01DB5FF952ECCF01FD92E6F2256C061F98F
+:153C75004E2566D92FC591A33AA972C52595724BE07CBFE6913D
+:153C8A0058600EDE8D8F18CAF9A426930D739CE055C8E6958910
+:153C9F00C7B0AEE5F321561CE721A2A74AB64FFCA9DE960EA415
+:153CB40077FD33E6910EDFD953629FF44AC5DE3BD8F8A8618648
+:153CC900A516D4539ED88B98C1C1B988EF41E9896EEC77ED3513
+:153CDE0065AA0F7B61E237B52B8BB5FABDA62887B68CBD387FDC
+:153CF3003CA73C2937B65B653AE26413E16C936F2DD785BF237A
+:153D08007C526C9B08B0A2FE9DABB996B36E528FD370E9FD2C8B
+:153D1D00D7D06799CA94278E6D02FC98CF010B60036F2E2318BE
+:153D32004ACE9FD4E662CE7FFD59BD98719A2F92DAB7302FD91C
+:153D470075E2FB086DD4579473FABE8BEAEFDA7742F851827F75
+:153D5C0044DEE84887AFFB9FF51460A33EA12F6CF220CE5EA6C6
+:153D71008107D29D39F0B9142C4BEF1D5C5CCCE0C16A2B830789
+:153D8600ACDF8D5B78087D576EEC5A873DC921DE4EE9BD185FB6
+:153D9B008413A4C13644AC330BDE538483631AEACF59F76B7D0D
+:153DB00087C0C3312D40FD030E149C3BC6DA3BF4326A0B310BAB
+:153DC500C3D47E39C941271EA1751E24CED93B8CED29EBD2DED5
+:153DDA0023DD754198FD61952ADFE5C45B1FFEBC88472874DE64
+:153DEF00D5C2AE5A7420286268EE16DF804059E2EB7CFA3D8C92
+:153E04007DC0F17F72C5B3251B113184845E087F7AF8A685976F
+:153E190071863C4ADD01D249C6E2D5DE16C957A3CB91C5FA20AF
+:153E2E00C18AF80B48AFD0FA750573907A6FA34FFDA346457D70
+:153E4300A0598EAA3ADEA54847C77DB3388FF4E722966149836F
+:153E58006A5AF51E860E34B68CDE5568861E8E0F36D6501DD54A
+:153E6D00544795FA40A35347A9783FB58DCBCB1F10F5FA45BD41
+:153E8200BFEA70F25ABF0F1B89E57D778A33DA0D13565B510BB7
+:153E97007EE0EAACF0EFAF36A3948E39F62A6385D2EAC5BA120B
+:153EAC00818E68F793F20D8A7C61BE01E58DC526F28ECD90178A
+:153EC1006D6F07ED92569E8337C9B30163D3CEB3FD2679C294B6
+:153ED600C76FE7D9302D4FAE522FBE37847CE095ADCB7F762708
+:153EEB006425FA21533F719F43F2DCA79AE64CEB596166FBCB27
+:153F0000638C2A8363D86F147A0B3DE34CAD88371B8D0BDE2D37
+:153F1500B1CECE67D68424C9E2B74CBD549FEAAC1F093F34B224
+:153F2A004F7F90C3B07E029FEF94A06FB6BC150BEB8881746A9C
+:153F3F00D3AA9FDE958C3FA707CF3FA7C1462DA1B1126C1A1181
+:153F54006397F836A0B013F598673FF7560E53E3234B133497BD
+:153F6900FBC9362A461F146E5A3E6FC233A400318BFDF1634B40
+:153F7E00A04B237F10F53B700839DE65C11749D48BEFFDD9F533
+:153F9300CE8F0F2EAD99566FC60EA6B941949F617DE61FEDF10C
+:153FA800877EC15626788B2F63DC2855D9F2EE785B347E71160F
+:153FBD00EB9F662877745A391A9F379C27B4CA1FE23F9FA1FCAC
+:153FD200D68F50FEDD19CAD57F28BC87F89519CABDCF3EBCBDF5
+:153FE700D434FD0AFC1274F31FD6D5FFC9F54DBA19DEC7AF7EC8
+:153FFC00F0FB890F7BFFEF5DFFB4F77EF778F8A0F5D6FFFFFE71
+:154011005FF55E71E1BF789A7CFA7F077E9B3FA69D399A78FFE4
+:15402600C6F1F061F5B9F94D2A3BA2F779129B3F2ABF61BCCE52
+:15403B009155FE79711EAABAB553896D5E49C3DA1FF8828EF8BF
+:1540500077F80EB8FFEE6E1D631E3A823FB0459FB5AA5B57D5B8
+:154065002FF15977B7EBC1755CF34B957AB0A8524F90CE66CB4D
+:15407A00874DD7482F5102D53AEAFBD7D675680DD786E173B5CB
+:15408F00AE4A877F33BC776435A7E57192F925FE4ABD605D8D23
+:1540A400FEF5358BB58A758BB5907FB3DE99F37B1AF6A316C828
+:1540B90031C4C70922AF2C7D4217EB40ABDA850C822CAA284A4F
+:1540CE0068F08372DE178BF78B756F202EF4DA8A07E3BAF0CFA1
+:1540E3002438CFCED0FE4ABBFD426AFF10B55FE26A1BED9653F3
+:1540F800FBC827EAA5FADD6D5FB3EB9D70D59B4EFF34ED474C7B
+:15410D00C55AD6B29E4C18D41DCF8971E022CDACBA6EA7297B4C
+:154122002EE1C54F6DE6AF5D4CF8A9D416F86BF4F99F965BBC93
+:15413700AC7D63C51FCB2D87662704FEB12E457378106BA2B712
+:15414C0009DD240BBB881D01FD90C59F147D2398CEDA308DA2A4
+:15416100EFFE0D7AC5BEF5BA54E4C096BEEEC0D648B095FA2B21
+:15417600457A9C14638756F94535BA5FC0D4AD2D284AE83D3BB9
+:15418B00F764F80078FE6D3A6DC3C0443F71F6CEDA779D78128F
+:1541A000ED8EDB7048655DF8269C6F36B76008ECABD203D41E5E
+:1541B500FAD662FBB8C3394CE0252F8B970AE29BC2B54D7AC9E4
+:1541CA00BA7A01CFEE9C6EAD80F052E84FE8B92CB1718FCD975C
+:1541DF000B45DFB3ED5EA1B60C7A17207C11DD8B1D1840FB8A9B
+:1541F400D51B34D8DC15EB3668D3E94F83275020CE13B2828A7C
+:154209003307C57BE95C8D5EF2604C97D997F45994E780C44203
+:15421E005EFB791F3D17AE23F8884F0A8BEA75FEBD06EDD0EB49
+:154233000D5AE1B9B57A09A7B47DAB74FED97B88DFEFD140FB92
+:1542480012C265C55A0B07724C6A51D9165E4F38F34A0FEBFE75
+:15425D00580E62F807F3B15753BEBA7596F4A8EE9757EAFE153F
+:15427200D4D6DD8FEBFE55F7E87B73566A5823975764F3E7852A
+:15428700AC3324227FA2D6CAFFDA1775FFAB0DFADE9DB59A97C5
+:15429C00E6ECC0F7366B159FCE69C177627CF1DA8DF9630D1B06
+:1542B1009FC6B78679AD5E71EE615D3ADBA073CA2BF9B2F8DF16
+:1542C60086311D12674E362176B4AF9DCA9D6ED828ECDA65ACCF
+:1542DB00253710DE981758B6B1A26BBDB69B6401E982C5BBE8C3
+:1542F000FE879031FFF331DDBFAB41870DA1AA8F72B16F98F838
+:15430500A22EADFD922EDEBDD640EF6BF5BD842FD01C7C0DFB89
+:15431A00ABE09B524BC539C21F6FD74BCE7D512F3CDB2DE8DE86
+:15432F0043749F45F42E88F08D6E39045A835EC25FD7A623F818
+:15434400F06E82A505F0138E11377A1D3D2772B66AB3A81DC03C
+:15435900DFB9D37ABEFB7ABA0472C8F98DBC34AF6C3A4869289B
+:15436E000B7E36C98C04FE20E7C8BE9CE3F0D276CA237CAB09C3
+:1543830086ADF41CF8DE7CADE26C29E1B3549F896F7EC9B27C78
+:15439800F377CCE21BE0BD80F826E4DF2A70F1759B77C03725B1
+:1543AD009CD2A6F14E77FCBEC70A425B8826B1D66EF6E863B372
+:1543C20042D156C9FFB8EEFC2E8C3CFA186833AB7DFD46F67A95
+:1543D700CB46E75EF1AE477C1FA220BE7E63E158CB46299FE89F
+:1543EC00FC894AEE25DC5F277A0367B70BFC7B5A0A7907E1FE98
+:1544010071C2FD1E21B7DDF8CFB1F19FB0BE4B11947C9FA0B6CC
+:15441600A39BCE085A8057A29B46A7D102DF6D7BD0A605C9D76D
+:15442B00E2F52E5ADCEAA2C57BC287303B8677D2F8ED75D106C1
+:15444000EB55A03BD277D8F4394FB471E8B1659AFFE787D1E3D1
+:15445500828B1E7F65D30363B8C4A6C50D7420DAB86991177966
+:15446A00E4B1DCC8FAC7D876D6E21F7B64E32C65CBC63CF69F69
+:15447F001F2B88FFC946FFD8A3E277C51F8B315AEC67F43BF207
+:15449400258177794725F7908CBD9E17E5050EDE696C16FA775A
+:1544A900929CBB5F2F5CFBD414BC17CC84F7AE85A0B90BEF3177
+:1544BE008177A7BF6EFC936C2FBEDBC63FD67C30361CFC17BBB3
+:1544D300F0EF116B37167E7B6DFC164136D13BEC33BBF9DE2957
+:1544E800FF4B21FF2D9AA19C887B69D3C1C279FD14589E408C43
+:1544FD00AAA4D48273D5099237B954C72304C3A17B37EB15EBF0
+:154512009A34256789969773A7061EF693BC61AF376C3CF4031B
+:154527007A776EAD569CF3592D3FE70FAC77D2CA8D6CCD3D1BF6
+:15453C0077C73C2D806B2170522ED6B603B9AA47CC8548473C72
+:1545510037C7A641FBD89BCA71E6687B9E403EF80E03AEB7C9AB
+:15456600ECA43C99F1FE1A8D77CC5117D26905EB41D003684EA5
+:15457B000BBE6AE7FF163D57BC2BE35E7CD2EA77C1FF01EF9849
+:06459000C0E6B892000035
+:00000001FF
diff --git a/drivers/atm/pca200e_ecd.data b/drivers/atm/pca200e_ecd.data
new file mode 100644
index 0000000..eca84aa
--- /dev/null
+++ b/drivers/atm/pca200e_ecd.data
@@ -0,0 +1,906 @@
+:150000001F8B0808AC5A10380203706361323030655F65636428
+:150015002E62696E327D00DC3A0D7054459AFD261333136278A4
+:15002A00192663E02479728060A10E9063213F64F0700F3DE05E
+:15003F009E6CDC7B2F3514B35EF0A28B9A5A731E554B91474C5C
+:1500540034E11AB6692618B7609D8404A2121CA8648D7551435D
+:150069009DA578A56C8AF276A9AB829DF3AC92DD52CC5EB177A0
+:15007E00D4CA32F77DDD6F665E263FA25B775B7753D5E9F7BEA8
+:15009300FEFAFBEBEFFBFAEB7E79F4A91F6C270A21A1870849C1
+:1500A8007C974CFA8536C11F37B9A99FFEAD9C49302569258321
+:1500BD00D8EF4EEE6E14EF59E3B3EDFED3E3C735EC67E50822CC
+:1500D200A9FE0FFD29BF7CEA97A26F4EC993D537AF13234A5E2D
+:1500E7005EDE94F3BF245F4AFCF1F129E7CF9E866E0ADE2C3919
+:1500FC002BF0237F849F3240F688FEB5EC75792D39E3BCB43E9B
+:15011100C9A9F54BDE24FFBC9C3C6987DDCD33F3938CB0674E4E
+:1501260078D6F8D7D63FD9DC8CEEABDC4824B2F9DC949E391965
+:15013B00FED7BF11FF975E7267F17D1CFB4BE77E3625BFBC0C26
+:150150003F0FF9BFFF5372CB72671A1F3D3EF99DF51312ECCF0D
+:15016500C070095C0E5FF8FFFE4B3A7E246851FDD31C5230FA46
+:15017A00FC0A35E009832F79ADB5E45140A3A4743C8CE3E39F62
+:15018F00C35BB09DEAFF05BD7A95BB3DADE6B56DADE538465425
+:1501A40052C90E11EF08B4773A8857FB013CB7112F090619CEAC
+:1501B9005B125380AEB695F80197D874FE9A9022A5D554ADE572
+:1501CE002661CA73EE80B5F5F26AE22D7F9A78FC814838484AB5
+:1501E300E8B36DBD4D843D4C4930CE42B06FCC091861CFB9BDAD
+:1501F8002621C3B438D010BE6DD7091AF29090DFEA334930C6AA
+:15020D001187E86D9CB09E2EDF18033C8DD220A9BB6D57390DB4
+:1502220011D2D8B26F23C02CEA0FAC0EB76CBADB3C4F48F1BBF2
+:150237001157A5EBD25FC0FCCB804A3412ECA211D133EA167DD2
+:15024C003B8518510311A53A5FDD62226D9C4BD46AEA567ACCA9
+:15026100362DB78EE8A7683E21017F201E4E927EEAB6169944DB
+:15027600AFE1ADE3AEBAC0C53534B0EE4194CF8AC2FE47C6065E
+:15028B007960DD5253D1FA6834346000BC45C0D909BE0A681025
+:1502A000BDD7BA4BDBBA12ED8A7C09EB8EA79BDA6BF9816681AC
+:1502B500F70EF3723259F4518D59F578B3AB0A66E7A3597F0E69
+:1502CA00BA90E04E5BEEC669E5765D2A33DD6762936427C1D5C0
+:1502DF005CDA40CA8A7AA03EA807AC0147BBA02E52A72974180E
+:1502F4007B956F461DD851EB3EA14348C8A0EA9689F2332DA72B
+:150309000E7B941FFB00D8FFD6801526637B69AB8FCC22A5F03C
+:15031E00ACF65863355BCB4740B7F5A05B6A3CEC239954156CC1
+:15033300E7B09E9AA7F084F085DB760DD171378910B6285EA406
+:15034800F64A5F403DE05D8BB4C2F800BD8EE3418BAF06B8AA3D
+:15035D00EE81F5E96393DE6D3B92E0385D564748698085091946
+:15037200A79EC256E0D34F49792B1D759310AC032BD6FBCDCEAF
+:1503870038D845EFE5456A87F95932097ABB5B050D98BFE30F8A
+:15039C009CDF2BE6B767E667E6C6EDC6D24DB7E7A56AA4888777
+:1503B1003626DE3B6D253EE5C5810BE19CD8095A7CFEB241D8BF
+:1503C600765A663C6DAE8CBC4EF7B70D35420264F51833C16105
+:1503DB00A6438F32018C232C303A64E29A23DCADBDCAE604CE52
+:1503F000C2DAFC0BE48392B027D20C3E546386122FF0964DDB3D
+:15040500C0A7BEC35A366D323B120AE8B357F8531ECA1ED46DF0
+:15041A007F6AE732A6800FFA49302E6321B8C48EB97E560BEFE0
+:15042F00458110CC6910FE9B84D825C10415992A67940623CBF7
+:15044400E9EC584E5DD1912DB4E84C9DA9C486689188ABB8F0F0
+:15045900BD43E494A124DEA49DE43503E75D87B4D6F9E7F81CCD
+:15046E00E748EF05F296419A062866F84EF23AC04791363CBF24
+:150483000BCFC31CE5D213EF71C44759162BA4E81F2077148DF9
+:15049800DE677E1BF429501F117ABAB5A3E037FD527EFD21DE68
+:1504AD0072EB2653890C502FC844D803BC937403BD7E2113CE66
+:1504C20027FA51FE0EC4AAE7DCA04906DB38E62BF04FDB0E52E9
+:1504D700EFC24B09339A731CE3886F2C203A191CE0A344E0591A
+:1504EC00183F514DC49F88258C471F213EC2FAAC68A8CFB85650
+:15050100D6535DAAB92A3CE7C0EFCB0728CC6BDC33EBBE3AF4E9
+:15051600E76BC964B19EF8949519FF64CE568E091F74150C995D
+:15052B00885B1C83D82FEF43FCD0E167A306513B39C4E31CF4C7
+:150540000131A6FE965F4D26FD9E7387CD79E78E9AE46AAF90F1
+:1505550009FC2A0E7E2562E5D1C8C62AB40BFA87E7CCA98C1F9A
+:15056A00E07CDB0F02E0079ED07A136DD5DEE892EB27D74DDAA8
+:15057F009075F0D47A1E222F1BA9F524FAABBC1763C2F6998923
+:15059400F69376FBD1FB4F007E4396CDFA85CD8A1BD166C3B678
+:1505A900CDE268B322323660755A03C6B5E64D2B053DCC1D2390
+:1505BE00D266445F1497ADAD0B68E03E15BF6D6448D8278AEB56
+:1505D300C80678BEF73EB0C30FE947E092E01FC585095735DAFE
+:1505E800F671D7EE55CF245C958188AB5ADA037C046D01BEE121
+:1505FD00BAF4A9E9518E9B1D5AC626FE09B121732DAEABB48BBB
+:150612008C15B459DAD7B3F32CC428FA34D7B6547ACE7D067369
+:15062700345B4F0631B39A266B102748855D9AEE95FAA9DD5677
+:15063C00D4EA35EAB4875792D2583897B499FE5D3F12FA91FA31
+:15065100A3343AFA18E487C7B823BF7489DC027E87B620FA20D5
+:150666004FD1F043DE9AE5B0C528F877AC664BD58D1BD21EFFFA
+:15067B0059BA7B79AD423CD23EFF6EAE509A67B0CF7B609F6360
+:15069000FF23F63989F6D9BCD64CED8550E85072F557D21EB076
+:1506A5004745AD6EE339DB1EF3C922D37F7DA9B0478E5E629653
+:1506BA005AA5D57F827B8FBE9F46125FF04F66E1FE5412866761
+:1506CF0086F945D818ED469ECAF8A08A7BB46860BB6E87ED4EC3
+:1506E400F114BF6CDB45C1764D60BB8F6DDB5D00DB21FF8083E0
+:1506F9007F83CD7B22DFE3C67E6F5F26674C9F2BE638724555DF
+:15070E001A0F7DDA111F0B20A778361F4BE710B11F8EC13CAB3F
+:15072300CFB85A932B64C35C82792494788F611E51E60E9B4A3C
+:1507380007E413987728E1C8273927219F0C603EF1E1B81893A8
+:15074D00F9A4D8B3F9A4F963E02D724A539F88D97980873AFBA5
+:150762001C3A37E59359CE5C33A1781D3BC1DC9B7BCDA235ED12
+:15077700A29E2C523E379B4988CE17BAF7F3909FE8EF821F7925
+:15078C000AB11608D25AE1474B445DF7FC5CCD20E5FB68A3A870
+:1507A100170E70A2DF010DFCFD7FBBF54429CA4C9ABEA016F86E
+:1507B6008190DD315E0F7E5103E34F925FAF825A94A30ECFCD41
+:1507CB00EDC7BD45FA3FEA06F6167AA890B7BE6EEB8ED2E275F7
+:1507E0005F9819585F7C7324B932C5ABCC90B5C0CDF0B262939A
+:1507F500695544DE16B4F419E647605EC90313E7DD13D9B652B6
+:15080A00AE1BE31B70DDEC7941C02DC8C25D1129B371352AEAA4
+:15081F003D7B5DDD02EF009F3F4EEA549887F684FAAA68452469
+:15083400AF42D45290DF570BFC56BA0BF015C4D73BF911F04B90
+:1508490037E243DE03FC62DCA7D1977CA206EDE5CEFA7063BDC6
+:15085E00A3BEDB4CC197290D617DA68BDC791A7B55055EA967AE
+:150873000D7A477DD7EA98BF20E2AE48D57848C3FD00350F601C
+:150888007C421EC69849CFB37FEA060FC6604F20B001CE496318
+:15089D00F45BB141FAE3B6A126DC2B99A8E7346641AFCCBD0C5D
+:1508B200FC9F80B3D24E383761AD1C83FDE1320F41BEF0EFBA70
+:1508C7005F9C89FC501BE39EAAC2D98AE8F5D48775DE582DD4FD
+:1508DC0079C130D653FB649EE04837404E899AD0B2CF592B7220
+:1508F10048F13F47DC707EFA7B13EB3699AF0DFBFCA4DB755C24
+:150906003B75477AA046AD605EF541B3E5D6BBCD36A0A978FFF8
+:15091B00C6DC8B750C9CBB3007DDB2E7553337827B40B7D80387
+:150930008A033190A792DF42FECCF4C379E3167106B1EBAEB1A5
+:150945001EEEC7F307D45D9DA1DE74BD05671CBE429CA18E43BC
+:15095A00BE5B682CD0CC95E2ACA1F6C4DD381FFA828EE5E21D9F
+:15096F00CF4F17DE36CBDB9B95EA476A72D279D11F53A6CF9FA5
+:150984002F7597077ACCF25BD6C49886A7633517D785EC80E7CC
+:15099900C4DF12320AAD0BDA4E683AB425D008B40B301E87C6CB
+:1509AE00A0E9625E4FDC6EB04FF43059BBFD16CF2CA13DDE56FB
+:1509C30043BEDBB50EF83FC2317F3629F20C3409C7410F71268F
+:1509D8008F2F88CBF60A4BF1D9381D5E9ADF82B8362D3FA475C3
+:1509ED00BA4BD293F8E3643ABE8067E39C2533D1CBD03A3C13A1
+:150A02009E8DD33425BF89326D9C964E46A68553D2C9D040BBF7
+:150A17007F55C31A995C3D2CF1C7A25CE40D8CB1E29881790360
+:150A2C00623586F12B629A58F412FA289C3F647C34424C9608E5
+:150A41007F9B2E4E5E8138715DFA8579CB9E939363247D66979D
+:150A5600B181F1823501C620C60CC64E3A56ACA39958518FC96B
+:150A6B00B3FA18D457D69F1A6B2156F09CCE223913E224153FF3
+:150A80001F431E9A8D7990EDA5175CF2ACFE817D7D38027114D6
+:150A950081380A7D8D38DA6CC751C3639938FA009E23DF07232E
+:150AAA004223D0128F43850E8D416B8016825602ED1AE0753D49
+:150ABF0036318EC41D03C412CAEFD9DF938E27E09B965B03B992
+:150AD400F7BCFF8A21C70726C557AA05537E9F8DEBE047317E33
+:150AE900DEEF81F157441D23C75BE2B2C13A3649FEF5022F9BEF
+:150AFE004E8B23CE5AE21F91E9F8B54C8AB35E320D3D874FEF6F
+:150B13009A915E86969EC69B420F382B230E9D92DF4499668A69
+:150B28008D7A32959D60BE4D7FE194E3683F394E74A0F3154D74
+:150B3D00CE4DE17F988EBFD4BA8B38D4FBB846C8AC542C4EA83B
+:150B520027713FDF91D98F156FABA86DB78CB65588BD1DFC5798
+:150B67007D286614EF1AA4BA479E078B77F5D28847D6AE88CF94
+:150B7C00B0C665526784B9B260E38E7DBC445B88674CB6E10A5C
+:150B910021EE75DDA61F635A2DC718F12B780796AE33FA999E1D
+:150BA60043484524B702756A8067E58101519721BC73FE108595
+:150BBB009862B92AE8AF77F3DBB513DC1D6B73DC635914F1AC84
+:150BD00087F12E56D25A75B3B4622F6768017E1CF67CBF1E338F
+:150BE50015C85F985FE2F23B9EE0F375F4B11CFA743964B06EE9
+:150BFA00521FC48BD74A7D2C873E5F492B4B9FC12C7D06BFA10A
+:150C0F003E71873E671D32C46F521FC44B7C47EA1377E8F3954C
+:150C2400B4B2F419CED267F81BEAA35EC9E8B3E44A460684DF02
+:150C39008C3E88A7DAFAE0F3AA9BA595A5CF48963E230E7D4207
+:150C4E00F6FDA61A180DA77CBCCCA066BB897CA58FE0FB4EFBF0
+:150C63003D6EBF37D8EFC81FDF0DF11EA3C29781C7CE1CD1D360
+:150C780041FBBDDF7E1FB6DF2FD8EF23F6FB6AA0B3533BC6C47E
+:150C8D007E0B63EDDA49BC1BE40C9EBBB49FE2F99F8FC273BFE6
+:150CA200F6160B698BA97DE6E61ACC2B857D695E24B73A10CB76
+:150CB700ADF62572AB3DB00E78DE451DCBB597059D7A98BB5EAC
+:150CCC003B25E844E1B9567B83E1FC77A41C0C79D66B7B98A408
+:150CE1006BF18540B710724F0D394B4F6F3BB916715096422D36
+:150CF600B702613FD9F653011B75C0F66E7B4BC050A614EC99A4
+:150D0B006DCFAD4DD15FACC9F5433AF3C4733FABB7F9A34C0FC4
+:150D2000960DAC49D14CE122AD146E4A5694ADD4C645BAF768FE
+:150D3500B9D5381EB56DAA3D216D1ABA22EF6F904789BD3FE19D
+:150D4A00B8BEC3392EF9DD65D358286174F44989B3DEA681BC57
+:150D5F00FD4803C6C69FC88C55D9760CC3F846B01FDA8EC2739B
+:150D7400583BC0F0DC392264BA2CE4DCA1BDC88E08FB76F1DBED
+:150D8900AF0807F47DF7466E65D9853BCDCDA56F2C7F612C6631
+:150D9E007C7B2DCAD12E6C940F67B9556BDD952B4AF72C6730C3
+:150DB3007697188B0B79F363B915F3DE7257064A0F2CE7305641
+:150DC800B856CA8FF6CA8738B9F17B77E5EFE6BFB8FC208CFDBE
+:150DDD0047756E753E9C577F7DF1F32AA4F9F17C5A85F3FFF557
+:150DF200C86015E29EBF78026AAD06C113E48F5BA22F113283A0
+:150E07009E715DF43B056D120CC555D1370A39E07C18C798B8BB
+:150E1C00EDCC6553F93002F1F71A2D10DF7F625CCEBBCC6B45C5
+:150E31002F6D4482116E403F67DD5153D9F47DA8B3F6D15B712C
+:150E4600AF04BB49BE31DE2AFA06DE2EFA2E61CFBC3D80BFEF5E
+:150E5B0069C0BF9B16068280AF895C26F2ADE81BF9B0E8570B92
+:150E70009BCF3A03F81FFE10F037D1D9011DF0435CCA1DE37EDB
+:150E8500E89F15EBB093975CC9EC6DA454033C43ACCD23B0367D
+:150E9A008DDA7EA606C6007681AE96B6E141D15FE0E5D037AD30
+:150EAF00245E1674D52944A3FBAF277DE84B3B005EA01D83BA29
+:150EC400C112F6CB81B3F8ED77209E8C2BDC2B1FB171D1DE7613
+:150ED900BC517CDE0D3C55EF0766EB9A98FD6DB39F22BF48E2BF
+:150EEE0067DCBF6B08EB5F529F789DC33BB340367FA8CF54BDFC
+:150F0300782FF021776B43FC9315B63CDA1DF4C69792C76198CC
+:150F1800AFDAF2305B1EAA65E4C1BDEEBC8D3BEA9067740679E9
+:150F2D0074873CB5200FBC3336853CB50E799EB4797C807164D6
+:150F4200CB336ACB73BD2C230FEE55F7D9B8E86F2979F0793A72
+:150F570079420E79D6833CF0CE6253C8F3CEF28C3C977E277943
+:150F6C002CBB827B9A940779A03C3B1CF2E05E735AE20A5E2E36
+:150F8100C8ADADD57DFC7A32994CE55867F917D10659443B01F6
+:150F960039BA96AA810DE1CE35FD14FF4FA873ECB821758973F1
+:150FAB00DDCE291BAFE0A505F1791CFB6658EB6539856F9A5A59
+:150FC00002CE58E939FD7C839D8F53B806E04521B67D901B9DD8
+:150FD500F3752DCA6A81BF017218DA61689FB1466D21DBA92DFB
+:150FEA00039F2967ED5A15ACD57A56663C627272B07149A28F90
+:150FFF0046ADBEC62EC08F6923AC5FA3ACF3C8095A4C06CC398E
+:151014005B8FD0F9173FA3AD5BEFA46DF397D2E7B62EA7CF1F57
+:151029005943DB2FAEA387E69FA6783FF357BFC8C198A6F8BFCB
+:15103E00542F1C7993169CE9310F5CDC07F5C0BE554EB9CEDE5D
+:1510530043BC85DAFAEA344C6FA1E5F712AFCF09130AB4D0C3DD
+:15106800D3C06BAF2527C18D480BC37C89F6F12F9E621E6BA1D1
+:15107D00CBD64C01875AE093F9C4BB30D6C3704D312FD45EC9C3
+:15109200D405E99F3AC2884A995B13DF2FD855FCC78E2063D72D
+:1510A70044DFC5AEBB64AD33EE92DF494812EAA2C23E336A0D67
+:1510BC0019F87D638EBBCF3C9433641C0A0D189DB0962F586F8B
+:1510D100189E0F3E1777C9F825E280D561A81BA9897E90EF9079
+:1510E6000FCFF76E3A24E0CF440A2A3E49FD7B8D9D377E5E23CE
+:1510FB00F7C7487A2F8D31947F01EC63F9B1DF97CF8BFD1DEE8E
+:1511100023F65E382CEA18B97F0DCB5C073693FB1FB371BA04B5
+:151125004E43669F14F1F0A4CD6774129F5B27F091FBE5B0A8F2
+:15113A0087527CE2369FF5361F89D345651EEE67B171428E04DB
+:15114F0095BABCFD7DA63FB4C83C06BAFA2277560560AFBA7A78
+:1511640043EA7B3E726FC56578F680FDCB0C3807E50E34C29EFD
+:1511790055AD94F687550DBF8B04E9D8DB9027624A5DADF6326D
+:15118E00DED9F9DCEADDB00F75B0D2D0BEAA0324DA48D43DD4DD
+:1511A30055DA15762F851A4DFF0D5FA8FD86BB6C5B57691D3C31
+:1511B800A41DE0FE441F63B97D4DCBB46E8E79C51A053D12BD56
+:1511CD004C059A08276A94C281CBE3E4B9E527DDD5CEF965C6B7
+:1511E2004193DB34702E3E232D1D68B94B97844359F37D303FCA
+:1511F700A4BDC8A186B0E747A79CAFFE23EE4D4BC2284B10C611
+:15120C0096419D11D43AC45D3427AD8D86F62B9EAB609E3B411A
+:1512210039E96C74475AB90FF66017D6BB7A2FF744A2BC2032BB
+:1512360022EE9C27FAF1B0EDC7D6043F4EFBEF689FA13AFCB79B
+:15124B00106A85B48F821FE0FAA18FE6ED1F308BC92253EAB616
+:1512600044EA16EAAE76FAB35FEB608188BBD28DE768C2A807ED
+:15127500D6A3E94EE2C5F7E7E7B61B259B89F7A5B93143D596FA
+:15128A008AB5598FEB0073D9FAE876EC73578BFF21CABF23206C
+:15129F0069AAAB3C61159E676A128F885E0BA4E4D620EECA880B
+:1512B4005AB8CC6C752DDD9E6BC399CFDA9E1AC7FF6352430BCD
+:1512C900BFE74ED10845BFE74B3DAFCBC0459C801EF81D11EF02
+:1512DE00CD0E6AEE3A96DE9BE4BDD916BCABB2EFA5314E08DEDD
+:1512F300AFE9C75940AB2EC7B51570BD8F5DAFA95E29EEFB990A
+:15130800BCE39FA37799F8BD016922CCA746CDB3C9A47F527EA9
+:15131D00B2F9D74EE2DF6EDF9D4B39EE127766BD71A4A3240E38
+:151332004BBA3A7E4790307C4E4C419FC571FF91F4D500F0620B
+:1513470094E2BE68D3F4F9347FD50AADA02264E78D0AEDF66A05
+:15135C00A0C97C6A47FAEE1068B3A2ACF7F4B3CD8F8F1D355654
+:1513710068F757F8B4FBCB03DAA28AD47DEB0A6DBBA46DD35490
+:15138600BC8727D155DE9F19B638717B25D610F8EC4638D8F716
+:15139B00207ECF8950FC3E731ABFE72C4EB82B713D104791385F
+:1513B000EC20E660C4D1FB38E2A0ACA501AC11A1B67B88903162
+:1513C5000236FE4BE255C5DD690CCF9BEA79D24FF15ED2F20E35
+:1513DA0088EF6B44AC7B4CE0FBD5D7E47730FBDC81304D1BA0C3
+:1513EF00E28CE280056D58A303B6CC8635E0D9B754F0E33B1C7E
+:15140400E3E2FF0C865D648936C09D30DC971766C188CB4DCA55
+:1514190027C2583DF46A6488A910CBABB3C712CAF87016AC0146
+:15142E0060235930F7BF29E3A3593002B0771066AFF37F13F3E9
+:1514430084C1511569F6242F3203617CC0644C280C0F0C1AF656
+:15145800229960D0640871E2A297502C37025AEFB141264ADC60
+:15146D00E0A1CC4156D92DEE7CC144126CB07926122DDD9DB8D3
+:1514820099327844070F14B7425DEE8E73BD2D6A2B6559B7D497
+:151497009D52B355DE15578538BAE8B2559C73DFD7DD6FE6CDD5
+:1514AC006450F1F6EA7E4CBD37DDFDBEEFEBAFBFFEBEAFFBFBA7
+:1514C100BA31DE44D82DB80EE738919E4FBE9AF31B37FF6E8C0B
+:1514D600D97D8E4A1BC19FE113CC3500EB7D15DAA8986B25E05C
+:1514EB0087B471F62449D02133CEFDA473208FA0ED37A19F74B0
+:151500003663C7C458757D99E6E3144ABE9AA12F947C9BF50CD4
+:151515008CEBAB1D34234CD447180B4298111E57823AA4431D81
+:15152A006768C3D7CAB2AD92C6AD92463FE63CA91C16DF3FF7EC
+:15153F00E1FF709CDAB90469291BFD5236446E9DE415D833C0CF
+:151554000DBEEBAB6C10E61C105FC6F58DEC875B3ED5BCE799CF
+:15156900AF3269B70EFEBE8AB2C2569792B0D97682E72DADE6FD
+:15157E00717D5146D64D2F2B7296012DDCE704CDE492EFAB5527
+:15159300511E4ABE0134BEC1CA529CCF7E53D2C1CBC0E6F81BA9
+:1515A800C7A4FE495015BE293361DC608CF8337C825E8FF0D8F6
+:1515BD00B855A83D29D09E64DABF2DF80B65F06EEDC9F843208F
+:1515D200770BC03B94FDC477DE271FD06EF709DE2FB9C6F8FCD9
+:1515E70009F8B2F5FBDAB2F576D9D305CA9475D3CB4A9C65796F
+:1515FC00F1542EDF728EDAF3DFA91B7AF274033EA764DDD5BE7C
+:15161100310B7C63D755E3F3FBD96FD1EE4ECD27B36CBD843CC7
+:1516260079CA9639785F78B6D748E4D5CF3A3766081863B4A142
+:15163B001CFCA948C92615F7A301D6853FCFEA38940F9C17769B
+:15165000DD94B30E78E19670F8D301632C0F86CD032183A2CC22
+:15166500CC2F03787BED325FB6CC2C5036D39B5B867DEA73F4B0
+:15167A00D92EEB2D5056EC7596993451A9105621F9436214752A
+:15168F0006CE4B1FC83CC6CA306EC2FFFBC7DACB1C6B51524A3A
+:1516A400F89A13E5D7C7E5374E6D399F7910F72B13AC1DE6BA33
+:1516B9003F2474592118EA35C380BA700CDFB19DA53BD60968C7
+:1516CE001F501F6E475D02FFF1BB0315633A878FB2E223AC503C
+:1516E300B9EA28A7CEF6E585CB5528EF92712E94FB7C7E617C84
+:1516F800B820BF7C85FB3AEB5CC21832C7A3072A123AD703DF1D
+:15170D00866F7F4258D8CFAF1B037A2D747D6758B9E39A907AAE
+:15172200DEB6FD421FE7F23A571F17A005BE11B48C51FE04D841
+:15173700D7233CF06F0AF2A1407B92699F4BDF1E87DCE1FC7519
+:15174C0079A7CF6FE29D3EBF8BBCD3E7322950B6DF337D7E97A8
+:1517610078A7CF6525AF0CDB3FE7FC36BB26E13ABB92CF716123
+:151776001B90A70B1DB60FE3E0DD72AF86386CA19A57A738EAD9
+:15178B007C79756E475D795E5DA9A36E812AED48204577F72309
+:1517A0005D268F1F667D8F380BDA7BE6DCDF88338C69E058640C
+:1517B500FD8D98217C8BDCDC45A266737E547F75BBABAD8E9F57
+:1517CA0067C09C7AF516428A0E361AC9CFD31E5843EA53F054F3
+:1517DF00802798FB87FB643F03997A3EF6C77A726984BDD79F79
+:1517F4008DAFB70EFCFFFECEB7104F0DD0CA423DFAEA3B9555D3
+:1518090076EC09E339A4DB7C7312CFA274C7DE9CE2CF495CDB6C
+:15181E00C033F9668A9F5101FFF25EB157E2F039A98B8C9F43E4
+:151833007E52807D39C4E339D2CEF6839D1D053B0BCFEF8FEA65
+:151848008277D59C77B6CD8D7D96F6106FAFCE3E93FC8BB8384D
+:15185D00FF50FF35013C13CA3FBC53B903F1EDB957CC93DDF0B5
+:15187200447F12734E79CC08FEAFCFF137E36CCD771C7335E3A0
+:15188700FFA62CEC2BF024217992903C49489E249027C5B09E3B
+:15189C00BE16FEE15CF26A4B0233B5BEC04BC58257A2EC562824
+:1518B1003B1018CB295B01654381E3765929969541D98D8149CF
+:1518C6002CE3FBEE829F7A0AF8077C6C4D4DE7E3A53B89A721F4
+:1518DB00857CACBAE34FDD9FFE02F25024E5610FE03DDF9C4B45
+:1518F0006783423C473F853903B4BEF4E9745A5BE11BFA29D2CB
+:151905005AF3AD69B5E391DF86DEED05E82D96F49E015A1B9ABF
+:15191A00A7CBAF8939BE0564F7E38B694F11F4E3838BD3FB616C
+:15192F00029CD317B11FBE6BEA47E63CEEB7EC4F5781FE28B23F
+:151944003F5756114F17FCBE0BFEF73E813E69554CD56A18D73C
+:15195900FDA0F35CCB09998472FCAF9056FA21BE0774AA017723
+:15196E00166886A56A2D2F22FE08E899B511DC0F8C51DC35426B
+:151983009B98A262AD12DB0FB65831C11EF6F3FD2C0AEDCE4436
+:151998008E58A51DC7ACB512762F3CD3D79B74233C5DE2BFBBA6
+:1519AD00218F9E06D95687E746F0357D9AC04774810FF120BED8
+:1519C200E833B9F81640BBD28E23D65AC0F7E10501C327F17D8A
+:1519D7007C41E083FF6E273F55C9CFA34D44D437919CFA39B6C6
+:1519EC00FE93F59756E6D6CF95F5DB65FD0779F5F364FD5A59A5
+:151A01007F9CD7A7A8EFD9ECF9A334F491913E1DF790DC68A32C
+:151A1600F3CE1BF5833FC4F0CC47656C7333EE2F997D75959A13
+:151A2B0012D4A53E1BA838AF3787F6055B1DF1DCBD273B7E8D0B
+:151A4000BC7ABCFBFDE680A3FCDC0BCF5CC1F2959B7FDDEC8CD9
+:151A5500FF9E7DEBA72F63F983FFF8AB665289B1C761DAFBDB56
+:151A6A00275A7C7E7373EC87C433761FF14CA3D391AF9B7A36C9
+:151A7F00D73E0B5F2365A10E7179C53A8C97A9B97DB3FD236C72
+:151A94003713647D3028721BB0ED40DBB83E33725F607F1BD7AA
+:151AA900E5CCB47539D4719D0F3079591E3C5C6F52B02D5D5C15
+:151ABE0086ED35183306CD2A1DF7BDC16FF4F457C4F4BBC89216
+:151AD300A0972CA99BAFB98298233650C1F4756655F0BD0C5F2F
+:151AE800C6F83EC46CCE0FB6177D9412F0F546F3EA3D8E7A059E
+:151AFD00EA7BB37CED2F5248BF1BEA5DB5307EB2CD7C6ECFC40A
+:151B1200B7D741FB8DB2FDF740FFFBB592805DE787BA0622E42F
+:151B270009FF2F3C3B6254C3FFF98E360BCFC60D5F1E3DF7C0A8
+:151B3C0038252271CB0DBAB6FECBB4277625EDD9FA557A96EC0C
+:151B5100DFAA49D0D3203F397AC527E574B491F079134A3E4B1F
+:151B660093256416CA19FAC136EC27619E610C4775D0E5AA8842
+:151B7B0019C723DCFED2808C3FEF01B8D5C0FFDE45C493AA12E9
+:151B9000B27FB20160CB7D72226DF99364980E4E8EEA336F4273
+:151BA5009B1EA0C297D5E43E6D35C576459EB8F1AF8B7E50EF22
+:151BBA006B8CB1062BEB6F45E0BD669090283C1B9E23C484E70C
+:151BCF0065F895C3FB65F885E0C79E13FB5D08A7F53FD29ED399
+:151BE400B82693FFDDE7D39EE3F89F8DB079303E181BA8E47B6F
+:151BF9000929EBF4FD843C87B6818D58367FED75C669919FC22E
+:151C0E0061281AC6285EC77C7A9ABA8F102FEA228C9B711FF33D
+:151C230088E56AA306EE2763EC8AE7CAAB23B8B75AD6E4A0E3B9
+:151C3800AC9BCCC2D82AE2C7734FB857B100EAC73CD25E713CCB
+:151C4D000798CA7535A326FC5FF05F609F501F89FC8B0C2D99AC
+:151C6200B39958F63B570AF0F17D3CC5F18E7B97F63BEE59F2DD
+:151C7700B80FFC1F73C4B96C783E76C4405D904A5FCFE71EC6BA
+:151C8C004486FEF9282F23A9EB89EFDD51F10E7F701CB36D6341
+:151CA10016C65186268FEA650ADF43B1447C2566613CA5C94103
+:151CB600B7D8BF1CA60F4C3D30B5787234472E67A35C227D20D4
+:151CCB009BBB6F271E1EAF0C1F61E8F7EDD7560510861B73A2DD
+:151CE00022A798163941FDFC9CD3179697E700279862EF075FF5
+:151CF50014F916FC5C82DAC3F74C791C397CCC7A5CCEC76BF51C
+:151D0A00279B1CFCFA661A246E3CAB84F80BCC81F25560EBE5AC
+:151D1F00FC60B8F7AC522E5764B288801D328662CA263C7B40E5
+:151D3400EFE5716FBFBD6E9B43460CD46BC85B2AE9C3EFCFDEF8
+:151D4900463CCAD45386FAFE53BA5347E2792466821EE76BBC5A
+:151D5E007E9AFEBBD30AC626152EFBA6581F4D017FCCB87E3379
+:151D7300190DE299C5D496A3CDCE71B94EEA8BEE159C0779F150
+:151D8800175CBFD9704D0E374D4ED2CC99447594611CED7B66CF
+:151D9D00BC20EC191276F90A92B38F9B858F79A4FD745FC5B0E0
+:151DB200CECFC7838E45FA63FCEC42DC5C8CEB03C0519C1C3729
+:151DC70095E409D35D1A37704F7026E09B1B66862B42E9A1A492
+:151DDC00B249D17AE90DEA2F8D33E9B4DF1B5382D9757098A279
+:151DF1005FBBDB85BAB015CAAAF9790D9EAE8B3EC3FD620D515C
+:151E06008ECFCA06983B77BFC8D75A01ED4D727F017AC13E46AC
+:151E1B000EA37D1CE6F6715FC549DD3BD9773BC737A5F4B2C6FC
+:151E300031417B386E7AD1DF93E54591B8A9362678FF0E9103BC
+:151E4500BA05F2EA5E07F38DC5AD9BC51955E68A8C67FA3737FD
+:151E5A00341AB4BF15FA2AF33E8D9EF70E67F371F7817FE13540
+:151E6F00253DEC2AF440B90AFA176971E2748978EADE9C3334E2
+:151E8400A82741C648E549B18F20FD33E09DE08FC82DC8E5A33C
+:151E9900DAF762CE7E3DCCBDC806EC036BC7791A4A1EDE5B29A3
+:151EAE00D681E683503E188EE96EAFD86B4B024DE78B4D3CA3AA
+:151EC3003E0BEB03EB458E0CDAB0C572EDD8CA6564CCAC833ABB
+:151ED8009B764E37B46F58CFFD6C1EC75F2BDB353ADAB5245FE1
+:151EED00DD0B3F13CBA3EB798CC4C43831F605D7AA48C7E05399
+:151F020071DD3729F704A1DCC6DB25E1FD5501BCBB1D78B7CB1C
+:151F1700763F71B4BB0BF0623BD3D12E2ADBF514A0EFAEE41B6C
+:151F2C0026CEB17E4EE3DF9B685FDC408B0AF43DCF65679CEF03
+:151F4100430FFE13D04AC4397EB783D66E097BA000ADCC41C374
+:151F56006ED9EE50015A871DEDF6C8762F7C0DAD38C636ADA5E6
+:151F6B00DF406BA983D6A484DDB1613AAD5D1BB2347C2CDB6D89
+:151F8000DB7095F1DC901D4FE52AE3A938F09EB7C7B300DEDD56
+:151F95000EBC17ECF1BC0ADED68D38BF2690074C75E2C5FECA8E
+:151FAA00FB36B0DC96F935D0FE308967647E638990F92B2E21E2
+:151FBF00F3A18D59DCE11281BB65E3D7E006BC3E89D7C6E77309
+:151FD400F473A384D1561806C3396BEF7597E7EB3CD02FC75F90
+:151FE900CAEA3B3E9FF95D0A4719B767784F8E7A9CA11FE92EF7
+:151FFE00ED33F6815FAA826D53D79E32CAE909E32021739C3A1C
+:15201300E86FA05F85748FFD9DDF15D70F9BE33ACA8B88271E8C
+:15202800D0613DBC17D7BF77715859DBB01C6015815DC8E8D5B4
+:15203D00D0B8EE5E17E73A95EB53DB5E98A84FE35C9FAA6D40B2
+:152052003FE85197E33DA7BF9598AF31ACBB2A9881E7F178AE2F
+:152067008CB0F9D23625047E26F5697848F8946063455F46F90A
+:15207C00F9097E3E08ED52641CECD23B863B7682A26D2A684F28
+:152091005ECEAE1F193FC7D46BF8319C8E7B15F2FFA5747A5E1E
+:1520A600FE5A52EC77F278BD359C02FDEA81B915AEA12CD41386
+:1520BB00455DEC2E163A59917E7C293CA37F486772FF588AE7B0
+:1520D000E79539F3C6944671EF4CABA31D5EF893DF0E6DC2F3A4
+:1520E5001A9E5B3AC910CE83987F510BFF8167E877BB2A13EDD1
+:1520FA00587F4368D4B821F40ECA8CD5CC7345C618C2C372FF1D
+:15210F00E6B895FDFF8EC1CBFE3A8E3E0BF1B38421BE7F076D69
+:15212400BAAFC8DB631CFF324BD3608AFB4D0E9A12B4991C5B1C
+:15213900E9D75C011B37DEBDC3F34230FF1ADAFB6FFCC2CABCBE
+:15214E00AFFA228327B9F058535126EF07D6AB320F1BE92ABB9B
+:15216300310E7EFE10B3F819D31A3A07F87D3BB24315B044DB21
+:152178003186FC07B368A9EAD330D6C7E19B414BD18E5BED0E8D
+:15218D00BF12D6DDE411AD64E5844BAE8B0BE05983BE4B41D8DD
+:1521A200555689FA16C09EA0840D594AEC94856704C4797E691E
+:1521B700BB51BF15CBF98B396DF25C0091759305FD19915B88C8
+:1521CC0072746106D29560ED226798B9257D6E271D682B8B05AC
+:1521E1005C7F30415522CF2AA8C72CA443FD6889C56D51868E26
+:1521F60014ED8A65E51BE11CBD4FC0194C09F8026E82F3A7EC3D
+:15220B00A33EE68235223F8347C6753C138F3EB0F82661619B93
+:152220009C7900F03F8C65FD1FBBDF083F719FE89FC0339683D4
+:15223500C7DE73C0B69C8F49C1C71B36F75AFE7082D7259C38A8
+:15224A0033E7914354D0D24055CE975679875080BA653E202FCF
+:15225F000F8FB28717A1EFFA0ACF4773D2E07B7909738E1DB4DE
+:15227400B114F508C6FE992D27E80BE30D65CF935E9DDFD31477
+:15228900A69C2FFCAC05D42BD05FD660525C841797F61AECC022
+:15229E00ADC607FF96F690A911BEBEF7E118A96F1A8A768AF9BB
+:1522B30023E24E017B0D535E3C7D1DD5D37624735E3EB39E9A77
+:1522C8003A66DD7EADEBA9F06B962D539847365C22D6BDB93243
+:1522DD002564086919967CE163128C7179E2EB5958C7A33D4393
+:1522F200794219E0BC32C51AFEA5AF44AE52A1F504CF1B0EC06E
+:15230700DC6717E83C7FA01DF767BCDA92FAF7D19EC23AFC1118
+:15231C00EDD6E0659893CB93BE55DC36DE2FF37DEEC74B4F0E1C
+:1523310070B9E3F960A2DCC2F2F7E4FA3622DB46F3DACA720B9E
+:15234600CBEDF3B836FC5EF9CD60DE37B2DCC2F22E09BF5FB607
+:15235B008DE5B595E51696DBEBD4C512FE6AD916DBE1FB028718
+:15237000BE6975D4E13B9E6DC2334D6E90A362B0558A36622134
+:15238500BF1475BF81E31DD046F19EA9B9734343CD0AAC57CB16
+:15239A007A2E18A5DA116BBEF94993BFE55343891CB75E8E95C9
+:1523AF006C72C39A0BECF1BC9991192B95C805CB96D999A0DB1C
+:1523C4009257709F2E3B077E0A3652E1E746D8B4F928E757464D
+:1523D900AF200CF5E08831583CAA236D73375759F67CE632C014
+:1523EE00CFE58EF3BB8BF8592FA147ACFCF92CF2400127CC19EB
+:15240300E73C796A76AF91F8ADD84754A54EFBA679F27480DF1E
+:152418008D23E6C8C008CF89518BB3F98F8B31F7007C8D07F85F
+:15242D00DE7E36AEB50EE4CD67CBCDFF36BEE9984FAD45FF072C
+:152442003A3ACF9F086B23ACC4DBC7F9F912C06BFAD4E10B5CB6
+:15245700CCB3BB400BB611FB20A6C5F7472AC7DA4B0EC68D21C8
+:15246C009015B5294E93220662B9C57E29BF37096D33D66BB0B8
+:152481006E4FA17D97F0307EAB4716B1622FDE75D743272F66C3
+:1524960071AB05FC107704F0F23D0AD3C2F11A02BBAF4E8CD0AA
+:1524AB00CB12278E6F0E4EA877E7E1CCED7F75FB4607FE900352
+:1524C000BF5600BFE2C0BFA000FEAA6BC48FFE456F6380DAB4A9
+:1524D500200D139F081A1600AE7CFCEAC4118E4B8B8CC0DA3C30
+:1524EA00D10E6B6F0B7D1F45E377CACC5B101961CF83DF833679
+:1524FF00ED92D3CFC1589F5A65F5B65103EF74BBF46455338FA4
+:152514002756DFB4B4AA6D6B50FB6143E366ED9EBF587FB7B6A6
+:15252900E127BBBA3B1FDDA5ADEFDCDED9B1AB33A8B56CB9679D
+:15253E00C7CECE8D5D3B7EFCA3AEEE2DF5CB6E5B16D0AA1FD909
+:15255300D5B56DE7CE1D8F6DE978BC235457D75877DBD23F5BD6
+:15256800AE3DBC6D7BE72E2DD0585BD750DB7807BC04EB03C11A
+:15257D00FADBB4AA4CFCCCC6575BBBB533BAA3BBF6A11D8F4646
+:152592006B3BB67644773D51FB68C7B6C76A77ED7CA8B6735700
+:1525A700B4F6E1276A3B3AB6F3B2ED3B764497EDBAA96E857601
+:1525BC00ABD6B9755BB7B6B5F3E18E1F6FEFD61EEAEA78EC478C
+:1525D1009D5AF5EEEECEDDDD4B11CBB783BF75E7B6C73B77D624
+:1525E6003E0CDDDAD2D1FDE896AD3B1F5FF6D04D2B1AAE063F10
+:1525FB00039E9F6799E30FB7EFC77D42B58ACEF537B4576BF5C9
+:152610006C199EE5B1F3F423CACA85FA05E350E870B4880CD334
+:15262500F63FA63379FAEDDABDD67FFECEB552517F6054692D59
+:15263A00B40AE6DE39D0253E78BE85BA539ECBEEFA22ED797785
+:15264F009112DCA81D613550379F8C3787B401C6EBC3CAD30165
+:1526640028437DD3E428C3762AD8408069F56BA5AB6C9B500E21
+:15267900BA710611BE04FEBF02B0F7A9CF44D14F1CFE657A16F7
+:15268E00D2E5CC6B77E6B4C72E8B585C1F6F5F4DBBA03D513FA2
+:1526A300B77CF0CD04E68D39F0B328E1676797B9C85CA40FE9F8
+:1526B800F069C718F66106AC53317F8E4C2DC538ACCFDF72AB4E
+:1526CD00817DC77B164BFEA1D1C02B54AA0096D8AF3A44C3DAC6
+:1526E2000BB4FE6DC00DFE940FBEB7F1E0391EBCB301CA2C2721
+:1526F700CE35E934C7C9EF6D04FFEA783A3DA732E2127B6AE153
+:15270C00017A1DD80584997C0BF3660350F60AA7AB04ECE521AB
+:152721007E6FC47EDC972BB3C7CD3E6311FB038EDDE77CEC30F5
+:152736007EA36930EFD4BE845B5B6195B41C85F13EC061E33C5F
+:15274B00C43958AABD6CD57CE56ACAFAAB620C6C7E3BC7C1F730
+:152760004EDAFD75F5574EA66739C703E951811E9F63BCA211D6
+:15277500378D257F65A920875A722431D7BF1BDA76515F7211DD
+:15278A009B9D2CDEB43AB914E435DA8E776AE01D89782F0E0997
+:15279F00DF4DDF55709DA453F465947F79D0C07B36D662FC4621
+:1527B400C533BB61EE8B0E9ACBF5D74275ABD66845ABDE2F396E
+:1527C9004FFDA15BF83E63EFEBE959975C765ED6009B9133FE04
+:1527DE006B29DE5D89EDDAA1DD19D92E906C01B8ADD66A11DF97
+:1527F30048F03B29E5B326B2860EF0BC9145D485F77F014FC4CC
+:152808009D94B9CFBD2E71278EB8E76B047CE95AFD52B180EFB5
+:15281D00A523C603DAECE05C6D76DDEB8BCA82D6E21B83A7B1E3
+:152832000EE3BAE3E9598B355790D3A90E50DC8376C3BC3C0DA3
+:15284700F320137F9B7A45D8E7A957AC9F5754EB7CBF1874E630
+:15285C00A0840FDFF3BDD47E8065B7B5DB6D873607DBF0BEB4B9
+:1528710012DE07DCDFCDC7A57F21E6512B7CCFE358324E77F2F6
+:1528860028C233D982D251C3F7DF87190A4E6B12CF389FE7E720
+:15289B00A9ACD0ABFA10C09D5F5A632C07BEE2D9D81A729EF631
+:1528B00086DE88624C2D896B83B6A5860563751DC84EC99E710C
+:1528C5008AB90441DCFF507F21C646CE65F4A915BE2ED380AECD
+:1528DA00503BA75FFD85357129ED395A24FA79178C75538131D3
+:1528EF00857575597E9F7A2FA5790E09C2D92EBF9F0F73AE6456
+:15290400CA34F0BEBA2258A784928729FD4DDA637F8B730575F3
+:15291900E284E43BC66F911701E083DD6618EAF83B7B864652D2
+:15292E006911EF8436A54773798F325888569CC7E5C91EE6FB92
+:152943004CF0FC83BFCDFD6E02F98F72CE75C37E6AF37A1078EE
+:15295800DD9A1CB1265E933E80B3DFB83724FB4D7F2FFA8D63CC
+:15296D008FF0A200AF468E1997CD51219B788ED6D576B71186B2
+:15298200FA9B6DF9936321F89885D1F07B416B1464E2E3D78499
+:152997004CCC2FAD37FC23AEA02D1B78CC3B2AE2B952364EE849
+:1529AC00E8DF2D262756A15CF81A135C1E2C73B1EE1E386228C5
+:1529C100A073174B3910FB02B9E37E12F8EC575F0199EFA15EF8
+:1529D600900B5F4F9D91BA28F96DCF07C737181B2FC4EF6538A7
+:1529EB00DFA13DE7793E1E984F9837649F7FB9CE211BDD201B4B
+:152A0000D13342365C781796940DCC8343DEF01C07CCEB782D44
+:152A15003B7E086F76B8CBD0FFFD2FEB41C759D5C936AB2AD9BA
+:152A2A0062819EB3400FB202F909FC5E665CEF946A553CC61AE4
+:152A3F002BB00F06FA9BCEF00E1891728C1B8DB5A3DFE6F28C47
+:152A540018B89FA6CB73D6E8833FC0F79712FCAC18DFFF8275A5
+:152A69003B9E0FFB79C54958ABC6F95E981AEBB3300F7275EC6C
+:152A7E0014F7FFCC581C65729EAAF559ABB553168367191B3372
+:152A93004E6BA29E2C8AD38FAFE21FBAC984E723A5B9F8B1A2B3
+:152AA800CF5D3F73FD4F7B4F1B1DC575DD9BD5AC342B0D627676
+:152ABD00D95D495848232C12C9519C5DB1C8922CC242A88FEC13
+:152AD20010654B9D744612F63AB603B19D84B634C7E784C42B00
+:152AE7005889058FF03015F65A266695480E6E209539909014D9
+:152AFC0092A5761C9152B2E1381C9AA4F638716C9AB8B6EC6293
+:152B110087A436DB7BEFCCAC56027F25ED39FDD13367CECCFB72
+:152B2600BEEFDEF7EEBBEFBDFBEEBB95BB86F37317D82FD98F5D
+:152B3B00D9F7D9636C823DC40CB69D6D619BD917D84676275BBC
+:152B5000CF6E6537B33EA6B04FB0B5ECE3EC63ECA3AC9B5DC784
+:152B650056B3556C255BC196C38CB29375B076D6C696B1085B45
+:152B7A00CA5A599885D887D8D5EC83AC059E0FB0AB58333C4D64
+:152B8F00ECFDF0BC8F2D81A7119E2BE1590C4F033C323CF5F4B3
+:152BA400D4C1B3889E5A7AAEA067A1FDD4D0536D3F558527687B
+:152BB9003F81C2E32F7A16143DBE598F77CE235DF2CCBFE4A91D
+:152BCE00BCEC33EF2D1FF16D9F8A777CCADFF5E379C747C0C3D7
+:152BE300535AF6BB963D6099CEB280EC52D7DE1F63F241F46BAC
+:152BF800043FDDFE6F82FF9CFDDF02FF1207FFC863EA9A207EDC
+:152C0D0054D359B7968349B2C4299A242F3E28C5E1CDC06B2E5B
+:152C22003E487602C97E6C13D9BE9365DBCF63FB71E017B7FD26
+:152C3700CA6D3F17F8656CBF0ADBAF04FC303F2893CE264A2C4B
+:152C4C0016B2F342BDE2909D07EA1187ECB4A8371C8234310E91
+:152C6100F768656DE3B751D0BA4A83D9F6A81BE41AF48BD1B952
+:152C7600FFB0EECB70ADC25433F081865161AA83EC1B26DE2674
+:152C8B006CF26DC2C81E0B84554218F81BE8CF9FE850A9EC5CF1
+:152CA000B3E1E0CEC139D62367C3983B4234D1659A8787749C89
+:152CB500F731AC27A509F5237E118792CB8A3F99B5C3813776CE
+:152CCA00C98B0DDDC9E7B8E51F837831E49BD262831740067A12
+:152CDF008B6F8C9B89932BFA975C33FFBAED4FF8CFE6E7A36DBF
+:152CF40009EB3CB7AC4D93FD5BD9D2E720DD4999743D4AED7F28
+:152D09003A37EB82B686F66043EDC03B5D2CE96A37CA609ECA75
+:152D1E00473BD4E9860E5A9F9BFEBE7B19E204DDB8469A857495
+:152D3300226B471D6CA30570018C5613E456A38C5D05B05F65E1
+:152D4800B883D1FEDC65FC24CEF263F2AB06F2DAA6B96B14217A
+:152D5D0059273F6CDFF0BD49E65A79A099941868E571BE2C35E6
+:152D7200A03D4A18CFC35064BBDE42EB5061834908974CB0C425
+:152D87005CD55046B38EE5F353D5AA94688EE0574834B7E23778
+:152D9C008675CD8548A6C6B8D48ED15E6528A425A84FB46B511B
+:152DB100D26D8AA27F3C3FBF1BE2C4C88E24CAD0D659D8B8163F
+:152DC600A573001BAC33B1A18D961E14BBDBBE272065EB48E02C
+:152DDB003AEF7C002A09F397A4AEB3A482FB3DE87E7505BF8CF3
+:152DF000E5B6E8A2CCB7B2D856FD3CD62B27EB96DD5C4B6FAAC7
+:152E0500C52C69A53C213F5A57C2FFD8889EAA49281CD9F31C8E
+:152E1A00310A7A20B11FA4B17EB5F2FD3A2F9F30989E84791408
+:152E2F00BFEC9B377F13F27F328DFEE8D7027EA373FCAAC16F26
+:152E4400688EDFA6150097F47CFA7337BF1006815913E5A7F5FB
+:152E5900BBD99456BBF205B5B7FE99F043EDE7ECB316093A3FF3
+:152E6E00BC287106EF0EA075B3452B4FA97C665C77C1B828CBA6
+:152E8300DF4A33FDBC21A44668AE836B6D4276775B10E7515087
+:152E98000ED3BF615CC1BDA0221ECAD933E1C99B5F6A031880EB
+:152EAD00A64F035CA7744EDBA93EB4EE79ED8AA806ED68A70154
+:152EC200384B5C5CB13A2C6C87F2625B27D9BE52B42D3E2908A7
+:152ED70068EB74DBA8D0FD02F83F306AB9BF6EBBBF3D3A95CFC1
+:152EEC00073217E75CF311FB693AE99AD2116F77B3AC96F85E05
+:152F010069EB97BF5CDA8AFA10A3B9879485A71F504A4E3DA5BB
+:152F16005EC7CE8417668FA964672DA4DB76067354D780F4F77E
+:152F2B00EAF95B9E3616724F42F81943481C68C3392CE63D7D7E
+:152F4000CB295DC894B61EBAA77419CA26C447A4B18349570694
+:152F5500E87C58C7BAFC61EF6024781CF2967EAD617D24843B52
+:152F6A00F6F8A4807B23D2B68312C1FFC383CF431F2C978753B1
+:152F7F0068DBBA4C1E4CEC4A680AD7A3411FFDA98E3657E38FD5
+:152F940031769F80FB095A6ABAE13503E2EE78496E591683F8DE
+:152FA9009BE3AB5B518EA1BE251DD0F9CC578047BA5BE3D9FD8E
+:152FBE00AD4C3FAC4F63DB8370EC3F6BC19FCF1C80F63348B612
+:152FD300299A6CDB31D4D7F461B20BDF44FD0C64DBCC30F4B3E3
+:152FE80084C6036D25A645F02B30AD15BFB85F266476431E596D
+:152FFD00B2C55A9D49EA9C00380A65C9862AB643EC1FFBA6308D
+:153012006CDC88A1CC9D491A572534F2CF803FBA512E9C6ED029
+:153027007431CEB77A4176C47E32DD306E2C0C66FA8F5E742F22
+:15303C00035C1A536FE40509E0E98232CE60BF0AA5B52CF0E088
+:153051002EC89765F6184F15CEF7A47411301002E09E1FD42243
+:153066004C3F9EA67929F0B3EDD153CAE9DB4B5BD36C504927FD
+:15307B00762B171BF965E9E8B052951C51776DD5945D1D6749C3
+:15309000B694CCFBD39D4BEAAEA13DA3506212D762BD8B92B8B7
+:1530A50027A8D766A84ED69AB680F67513B466DDCEA32CFDBCA3
+:1530BA00CE75A38EE113C6AEACA6E07AA190D903E9B6219E69A5
+:1530CF006FAFDABCDFA8351F369A0037B550D75C298805108ECA
+:1530E400FDB349DE46B63C280F49439B4E42B57C9F4E36F7D6B9
+:1530F900691AA4D39137F0F1437AC4FC1EC4DD66A00D4E616AB7
+:15310E008F8A79BA4E4E908E5AEC71C66A70BF3F74846C05F0F8
+:153123003D432AD469D4D5B3534D95B1D9FD23876BFB29ED8BE7
+:15313800FFC6FFF0618EE8427B1E7EF311D203A45B7670CD1FF4
+:15314D00FD5892F4B5B0CED6FE478AF63F8CEC88526D0EA543D0
+:15316200E6231969A5A60AD76B6AC91A4DE565CD1005DCD3DEE3
+:15317700A3354577B7E1B9B1C000B41F98B75627D07DD808DA47
+:15318C006E3CD36EF1FDDD86DF145BD18FEC8D16F643D2B4E610
+:1531A100EF42DCE446F446A28375C69963FB34D4CBB6CE365B69
+:1531B6007720FC02F2E2D6A0CE5E86E8C7A50EA8C14583C60317
+:1531CB0051EB5C2CAE9DBB7A2CDBCB3716D921E03EAA912D62AA
+:1531E0000969AB7FED20D93284FAE33A346F022FCB9D277C7B31
+:1531F5004DBE57A4B9439AF6195CA46B6ADDB101F35C03ED4235
+:15320A0021BC9CF945ADC95C102EE6F1C8DB918EC4DF0BBC1DC8
+:15321F00F8FC86D25EBFF9BA4176DBFE11DBE190B1E5CD7CA012
+:15323400E54DECBB162D10D77E73A7EED246546FA7B5D7EF02FD
+:153249001E81F7CB886C64A9086D65211B09231DB5A236AA591F
+:15325E00F4D2D3A4F375AE9FDA18D00FDD3CB3F45F5336DD9182
+:15327300CE9806DD48D7B934251AD8F478271CBF5B5AFD4FD299
+:15328800A29C6891B6F7796668F28EB4281A6B1D3AE8DF477B45
+:15329D005995A1629AF8E2EF0F93DDEDD8C0E4857BDEDF26C538
+:1532B200CB605C1DD0D1B636F2AECD5F2C6D9DFA2F871F59FCB0
+:1532C700A31A68E65BA4191CF04DA421D6D5DAF3B1CAC0F230DC
+:1532DC00EFC2B9A882BC64D1A5A688269B099E94657705C27175
+:1532F1005F5132771A9C9CA4F6846EA5C8BE40A4287DE4930561
+:153306007D5FD2134EB309D2DB6C42DA0F4C28A2EC26DE1C225F
+:15331B003D404DAF895ABCA8D00FA1CEDC061C8FE17BFD29C2BE
+:1533300001F45B7FEA0F567DCD39D76CA18DFBEA60AABF06645E
+:153345000199650E8658F660949907AB7F9BF7C07712DC93E0AA
+:15335A003FD96DA77FABF8CAB9D9F1ABEDF838AEEF7FBDA84C2E
+:15336F00D4C780F947715AB2CF45771668FAC57BF8655DB87E3E
+:153384002E1F4DDC37754C2985E9062F8FA4D04E49B9FC448ADD
+:15339900EC86D37E5096CE544AF22198B72475D15CD27AE3FDB6
+:1533AE00790FE2417B30EF999B67316CA4770869BBE407F5F373
+:1533C300CF6A2023E96403C56A877F6750DBB56CAD4CEF0279DE
+:1533D800B115E4A2F7D236A79F1DD193AE94CEF6F1BDD3CF6A0E
+:1533ED0046D29536BAE45DC6EB17B17F24ECB9C980F11CCA42CA
+:15340200407FDC5F95A18C629C5C787A363E717FD796672D5DEB
+:15341700687D8F8EB23307FD0BEB086E63F5DF82BC188A914D54
+:15342C0074279FEC8F67F2697C2D2FD4E2D9A16035C8F103FAC1
+:153441004928F36E16D798194AE3BA986836E9A5C7C3EAB7121E
+:15345600CDE1C1F6F659B26519C375E276EAF765DCADC067AE49
+:15346B00D765339666D22B862034A8B8D66CC9958BDBCA61EE8B
+:15348000E2AA53A80CB4878769DC0361B5321A8E944399FBEB54
+:15349500AF6DAB3565181B9BF46A33A47362A73AF895B0E6CE17
+:1534AA00C23C31D76E387788A0EE07B374D041DE6C0CF3B8563E
+:1534BF001DFBD424D36F191552B87FF857F43557B896CDACFF5A
+:1534D40057DB7A174D363F0B013F6B5750AEF7C95C88E62A504F
+:1534E9000FDEB11D937B64B226BB47ADB1EF6FF0C3388DE3317F
+:1534FE00CB7DC7F072CFA9AD0D95D7B0BAA97EC6CE6912FB759A
+:153513009B9F7B59DD29BB5B51560C01AFACE526D440F048BF54
+:1535280010FDF5D25AF8BA4EED26BBF4C827AB56A26EC011FBD2
+:15353D00BEA1843605E3BE2F7158F5D682DC52B0CFFCCE6F157A
+:15355200C8E6F84DAF288BF8E2653067F98ECECC470E8E83CCE6
+:15356700556BDEAF23EFC276817BD85E18AF9FAA19591A05598C
+:15357C0007C7A14FB875926FFEE9412DECC8346FBC997F4B99EA
+:1535910026FDFBFC5BCA34ED6F25D3C45ED04CE77C39F21CC0B6
+:1535A60095D0707B5B39E93423DE8EB5F98FFF07E47F4C5B0B28
+:1535BB00B87BA05D776C76109E828943053C05CD2561E11CEEF2
+:1535D000296D99E44F1C437E1D5C68CE0B835BF79901FA969B53
+:1535E5008BE8AB035FBF5CFD619C8DB8BA5F564F5EC80B880779
+:1535FA00AC6B8B5D7766D7BDF63275C734E90BEFBDFE8B80EE1D
+:15360F00D5C85763BBD2B1D7E7CC6FA08E747744113D5B8AE3A5
+:15362400C476EB37B0B47623C8F0CF3F35B254968F4F0297D654
+:153639007C1C8CB7D219C36FB74B1EF505A49F1B77B3B39A97FE
+:15364E007B91EE4FB862E5CB6A28FBBDC8151D163EAF602F502E
+:15366300DBC3FE7A05CE67426709A7D8E6703D17D784EB4C771E
+:15367800AB13A7EAF8D305BC57415A491E48272A92BD18F72EE4
+:15368D005CB28D1D4AA32E4AC9D4310C1B759D3CA6EA799847E0
+:1536A2004178937C7B1B7E119EF32BCA966DBAF91990177FAEFD
+:1536B7000BF263A32DF2BF1AD5E7ED3AC6862645D437CBACD512
+:1536CC00AC7F9C4B0D4D9E5B61A53F0278F3DB795D58B1BEED6D
+:1536E1006AF6F5569A0B15C5C5B49847713AB4F7BEE5DE334AFE
+:1536F60048FECBA5E88FE32588D21FC7FF0771CC8C0D65503E7B
+:15370B00C5B4E5A8630FB8C4F361B5BBBE4A785D28838C60E796
+:153720005F39EBBF2AECB2FED3E5F2FC307C7557A525B763FE8C
+:15373500AE8777A83437FA21F00A90BB8CE8238A144DAAD5302F
+:15374A0096D079BD7761A31F5F1863525C6AAD8AFB984CDA4A08
+:15375F00E3901480F1C41E87684CC2B0F7301E3963913D2E5D94
+:15377400763CC2B108C7A408D9F4288C291A3B618D29563932C9
+:15378900AB965F3116A06C01E394206F37BA5FCD0BE5192D5589
+:15379E000E7047A2C956677E580DE31AC6697AD5A1F7A05EDB5A
+:1537B3006ACD2B733FA0701DC343801F4CCF32CFE97417484EF5
+:1537C8008379654A2BCD24133E2847CA1C4A639F2B318F699847
+:1537DD0027B6356C734DF2B3A3D8A6CEBEE1C86B36CC00EBA1A5
+:1537F2005D5F813E3CA0D37959B439FA5D6B5CDDF3CA4CFFF2E4
+:1538070006ADF32F82BD5F3659C53C208B7AF62DB0DC719E794D
+:15381C0036BB98670A5E4BCE6EB27993A523E2C6BB03FCCE58AA
+:1538310098D136039EFCC73BD45520CFBA07C6D491DC98522753
+:15384600BB3B17CA1FFC30AE9D511AA9592D9C178D3540BBE016
+:15385B0086F87883EE96701D8E1B7A716F73849D1FD0CC1378F1
+:15387000B9D15C797276F9AB12CD4BFFA4722ED1BF09F53756D0
+:15388500B10A495AACA2BE2DEE75476A58855BFA06E9475E9528
+:15389A0079B30B75A7825F0AA3AC4B3A51645F00CFE5423CA71A
+:1538AF003CF4EF02F78CCD6E9AEB94E3BEF81341B47DD3D4FF48
+:1538C4004610F36D253D9F5C35AB10E363C05797A8EF93B98E87
+:1538D90052482FC61B8C0BE0CF7F6940B7F412F921679DC02DFA
+:1538EE006D517F0161685F6F04E340BD704F4F46DD29F0E3E3FC
+:1539030063C651F487FF17618E7ED64E877B5BCF813F7EC78161
+:15391800C6783EF0A8CB3A53398234F74FA31D4656B60FEDE209
+:15392D00BDAC49A71F5450AFB51AE737D2214D100FA83D40DB6E
+:153942000532DF5B66EE21BB48B5C00B8326C61FDEC26D1FA1AC
+:15395700FBE170AE7355A66BA9245A672FD0F6C97CCA736254CD
+:15396C0080388215C75F9DE19772FB2E4DCB6265A4EFAA03DE24
+:153981002201EB4C23EAB7A0DD41DCCF37C18F49C9C2FE1A2F08
+:153996009F37EE02BAF1801711FA66BCCABEA3720E7D437141CA
+:1539AB008B9B3F3CC83F3DA0BEE177F2AD2DE49BB2F38DDBB65E
+:1539C000677700AF385A86B695E47E964B18B4FE2E4534AEAEF2
+:1539D500BD9F379B0CC1443FA095D94EE5579BD306CA4392991B
+:1539EA00813CE3FDB82EB9A3668342BCAA6E637FAD8967CCCFD0
+:1539FF00F6F3F2678DA341AB7D34429A263337DA02E923905F61
+:153A1400D702E609417A0BB63F4B236C68A77415CAA076FB725B
+:153A2900D545FBA3E6EF8C1D3531254A792AFD317043FC83ABCF
+:153A3E00E11B87BC4B614E70A3B9D7889A0FA4BB4D7D34065FA4
+:153A5300DCD38BC17F16C215282BE7B36DF1B20CE1009015D890
+:153A6800519351FACD8C711BE41B07D8FCD85E01572DF015E0BB
+:153A7D008B362E6508AF85FFD590871FBE8D3077DB00FFE716D1
+:153A920060DB065C815F37F8F1C06F9E02BF50D0A2477C8E4D94
+:153AA7005FF7D30915DA5D854FE6979714D18B4378E47103D74A
+:153ABC00A042F2B826E0FFE5F4E5E83C48448BB958C5D5995BCC
+:153AD10022D86F4A3B642D4B3A81CD7A359E6991068C52DA2366
+:153AE600E6869C3C3250667D7A8CCE0570350975C67E4B14E2A1
+:153AFB00453406F90D215EFC4D05DEA69CC231AD5945BBE28BDE
+:153B1000E5DEA56CD32B8353CF32E6E4C3360D68999FD8FB05AF
+:153B25009BC63413FE51074328C0D3A12FC47105C39EB5F48CEF
+:153B3A005B1CFE047ED99FA0AEDEB2E502C00A3207C1EDD09B26
+:153B4F00492097DBE9826CF972B6A983F277F46D310DE45551D0
+:153B6400C9BA280CF32A0B5E8A2FECDF7E6E461F5EC2BB96FCCD
+:153B7900FB0A75ECFA31D6F1807A030C0DD29726F4F88F11604E
+:153B8E00B44D64ADF7EBE0CEC03B096F16DE1CBC26BCD3787B99
+:153BA300758E0CAD3119DE50CE8ACB360D5A75B574E6C9CD7E81
+:153BB80039DB2DCF7147E7B8E373DC8939EECC1C77768EDB9CD5
+:153BCD00E366BF9A53FEAF8A74FA013FDDDC6CFB0393483BFFD1
+:153BE20064C16E531CEA161838AC56D2D9503B1F9873E3DCFA61
+:153BF70086F8BA48E0D8849ECE219D66F03605EEB3F09E83F799
+:153C0C0002BC02D0A71ADE2678DBE1ED86578177C34FACB8ECF6
+:153C2100D004E12DE3D01FDC8837BDC88D784B14B9116F1B8B77
+:153C3600DC88B778911BF1162B7223DEA2456EC45BA8C88D78AC
+:153C4B00938BCBFFD58C5D442A1FDC818E8C816D1BF54BF97BFD
+:153C6000D65DEB9C6B279C5DCE3E03E0576445F89D1BE74F0C29
+:153C750047DE7314E417908DF427E6310FEA1AC29CBA33CD31E8
+:153C8A008F00FFC0673A451823CBE545D73C05E3622DFC231DFB
+:153C9F0079699B7A971778D67C5671076BEC8CCEB778DA4DF0DC
+:153CB4001F996FF1B23AD6B8DCD11B91D1CE2F8CF93AE48138E6
+:153CC9009BAE84F11BD29FAB44D9E1D1193B0A20EF9DA91F80D0
+:153CDE0071793FCA08FAF40AF7F227208EECBDFCF8E48C73B8E8
+:153CF3009789E311A0D1531C4F8472B7DA67BC41DE597E16F2D1
+:153D0800F290FC3E9B8FD502BC9B251C0F51BED8AEEF88E3B99A
+:153D1D009E47F52DD1FD547FCF87DCBD92DCAC23CF6F03B4794F
+:153D320057EEC6B5306DE33C5CCF1FD399FEA81E80B97B271B90
+:153D4700A4F8839E0985AB6987B175CCC07B8078905BC9B66F83
+:153D5C006C587B6A3E8E637ABFE44D511E47459021218D203F57
+:153D7100AAF330167B2A715D6ECC401B0688E746C9923F10D617
+:153D86009C385BBEDB378F553C01F965E6E138D3A827696C6ACA
+:153D9B00A2B109CF137235213A9F7EF662DE539E18E874C79ABA
+:153DB00054596ED05F5CC12DBF0BD24CCDB76422070F3E5B5772
+:153DC5006B66FD43D6A7A7914F6D25BB6B98BF74FA030A9ED3D9
+:153DDA0040987DD1B1A53CC82A3CE4390D79F6824C85E7407863
+:153DEF00D265A57138407607A62D9BA17C7CC21065EBFED03C4A
+:153E0400E9413FAA0580E7F3F26306C6AF043E4AF7A59780FC2C
+:153E19008C770C16DDDF721EF2F8511CDA5E1C6D16587691BFD7
+:153E2E00CBC63A4570E33883B88C9633CFEBD0665F65039D0EF2
+:153E4300EC164EF6116FC23CE8CC045BD2E9C07D1EE046FD401A
+:153E5800A04305DAF9C0B6C1A3DED985BCC7B1FB5D5CA7605144
+:153E6D009D9CF106EB45FBBCC0EF102E777618E039AA511DA462
+:153E8200319C7340BDB7CFD89D8D0F68231EE6B9016075D27CEB
+:153E9700383AD8897BD4FF7CE0E7FB45B9C50890AEA70A7D760A
+:153EAC00B5E1C0CECBFB691E86FF38C64B368ED6D8703A7A6FBD
+:153EC100362E03B8BFD60261BE38BFDC5F32D337244FA36ADD4C
+:153ED600E39AA6F28CE3E3CB5924827BD6796BEF219FFFF2F1E0
+:153EEB0008F82575BC5BC09BBB57F59EBE56918EAF54EB645735
+:153F000017E9B4E6B6A30E89C7533C6EDA70B6B225342E4F0ACC
+:153F1500CCF334D4F167D0F6D8A6AF525E185F405E1F19A77B66
+:153F2A000BE8FC47570B9D7B00395CFF6662AC13CF44207E1FE7
+:153F3F00887374AF9C8D779FCF747756A1BC6E1ED3CFDFFC9DFD
+:153F5400E53F0337D69937272EDFDE0047567BB3DA8F681ED6B2
+:153F6900779BEEDE60D7049587FDCCA2A74C3875F835DFB5043E
+:153F7E00F729315CAFCB8E459CFAC6B8D9ED12F5122EC6B94E46
+:153F930084DBC1DB4BD971E201621C78825DCECF208F775BA70C
+:153FA8003AD3BD3C543EBB2D76BF9EF7AC063A8AF1A2BAC88FA0
+:153FBD00E9A87F56A80BCC61ACFA3715EA8270F05D1FA4BA1001
+:153FD2009EE2636F8927B7D4A8DAEB1E79ACAF707C17E9FAE127
+:153FE700DC688FCBE6138003E0E99E82CDF1DC8076F2BF1C7DE8
+:153FFC00CB16AA13EACF3A696F837448EF34A429E63377BF6960
+:15401100B55751DEAFCBF171ED73D18930968D6D51DA7CD43A54
+:1540260057969B80F9C2A041F6E1A72D5EEEF415DE3C60489E81
+:15403B0009D50B3CF77E985B8926F215CDAEFF9199FA43BBA8E9
+:15405000EA3A46F5AF02BAD9F42D8C21E5C8EFA83F68AA6507E9
+:15406500DCEA0F17D1164AE4D0ACFEF09BEC21B28F82386BBD10
+:15407A00C53AEF8DE53CCC863BA12D5BFDA46BD06AC700EB5097
+:15408F0002CF30EBFDBBC9B6F9B139F4D62D7AD71F5D6E805B09
+:1540A4008C8F17D1F5908E6966E83A6ED72B45F5429EE3ED1A8C
+:1540B900A63A2DCCF0CBB1ED3BB42CB45FF0E333686B3FA90FC2
+:1540CE0041FFB3ECEC5BF5F802B44FA72DFAD8D1CE4A281F618E
+:1540E300B900B02C84B677A86C76DB3BF99FD0EEE422F8E200AC
+:1540F8005FBC083ED981CFE24B84CFAE4182AF982F4C4F5B7B51
+:15410D00C8F8E5E3D69DB37A34B9B1B268CC49FCDE694B85B9DC
+:15412200AB8F454608B768CB223D9DAF28B43D906D7E81BADD7A
+:154137005FE5AF95E2D6FCDB17DDAF3E0969020D7CAFF7745A0A
+:15414C007171CCBB00C3728774E97446E1610E7E10609570CC13
+:1541610081F683EB92828072A7A6058347FA65909112F78EF437
+:154176006E41FFD4EF90876F114E1EA37DF073F5C3CB58EEA8CC
+:15418B006E84B85ECCA72E398C36C1FC5578BF10F827613ECE96
+:1541A000A4618387FC708F93DC502E4F7B2AC306A5C3B2F461E7
+:1541B500D4175D5097E13B07AC345B1230A7C77511618DA6D2CC
+:1541CA00FDBCA87364CFE91156CC7F6BB7A694F49C5713F9BC33
+:1541DF006FE4239A8275E6214D5DF2C03A841FC74E840561C3C2
+:1541F4003D8476802170FA88521587BA9EF88AEA631AF0F08C51
+:15420900F22D1C17EC3CD1FE3CC8C03E4CE7D47D08F24FED3C5F
+:15421E004CF7FE627BA1B8B1435B84EDBF534D840DEA8475C1C0
+:154233007B41912E8807A61FD3109F6DA83600E10E2ECE16F0E9
+:15424800609555C003AEBFE62C3C04BFCF77A67A70AF79421B7B
+:15425D0000FF11EC2F8013F4C3B50D1C832D788F010E0E289865
+:154272007F49CF6BEA66C2C301C50BF5431C48782E05F74EF013
+:15428700CE0D280B6DDA86A0FCBAE0B97EC75EAD65DF04E6FFDB
+:15429C00AF30B6A326A5BCCACE698B42ECD38D32FF61C79E62DB
+:1542B100EE9364AF96ECCE74D31AC19251EB6C608AD245CD6D7D
+:1542C6001AB46D0F7C711FCDC8BD36D3CEF13C18AE55611B6D33
+:1542DB00C1B934EA1E86DA35F61AE9B58DC2989B4D34B97B3965
+:1542F00021A2F289B0BA0760AD5726F19E385D977777D1FC14F6
+:15430500E248C03B44F9639D82FCD94E49FEC4723E26E3789DC3
+:15431A007D0EE461FCF6E35C2AF628D9BC0D74C886953FCA8CB7
+:15432F0063D91084B540FA2648DF08E9D1AF56FE44A79307B46F
+:15434400A74E11C205087F6EC567293F91E666AF6C71E26CC493
+:154359007200267FC7B495B76EC18D388A60DF00385BE467BB1B
+:15436E00F88E901183705C2FE882F934970AAB5202F7D2ACF8F1
+:15438300981FB481000FF9603CD2E77E0DCF015AF769601F6EDA
+:1543980067F6FC1ADBBC346E97B3AF1F6D3254D3D97DCB0FE76F
+:1543AD004CE8876D2260E9B05F325FE2388B9E057FC8BFD6990B
+:1543C200FF901E1F944F7704650CF3B54BE75CD6D9C17D3A8668
+:1543D70021FF015E6B95057102B9AFA9FED35F53505EF5771D0F
+:1543EC00B2EEE580303FCEFDD9611CDFB58FBF50C26AB387DBB4
+:1544010036C2B827DAF30C4B67D09273D066BC9038B0B40A64E3
+:154416004F3FE61BDA67C151E0932982CF1F4D46C83E04B8311D
+:15442B00CFEAE8785B6D519E85B90BF05C4A6FF3CFDFD8ED19DF
+:15444000EB81733B80CDF71CADE934CEA4293E8F48F76DDFA789
+:15445500FDF632E9F6CF4907ED5DBFDC7A52D4DCABFDFB65D2F4
+:15446A00DFF52ED2BF7C997491778477AF76EE32E9DE60EF5C67
+:15447F00DEF49B73F6D3707DA7B8FD01ADA65F9BBDBE31373CC9
+:15449400F1FADB874FBF53F8FF76FE73C2C5E2F6FF76EB03FFC6
+:1544A9001FFE47854B45F8F7CDE54BFF67E0B7DB47ECBDA5C765
+:1544BE00FEB580974916FB24D9B869EA1F94E2B7AD82AE287AF2
+:1544D3006F573508C7BB56C5EBB6AB74D607656EEF0675DEEA92
+:1544E800EDAA2CDFA9CDBB6E40F5F7688AC835A8FECA063509AF
+:1544FD00728BDDA7D75D80B159F236A998DF1F9BD7DEEB35652F
+:154512001275FF7A70EE12D7502389974B7A3F073C392036A83C
+:15452700E53D2DEAC3D7372BF53DCD4A50BC4D1D2CF98082F66E
+:15453C002017F17167AEE2C7F83C77A54A6B0FAB078877200F1F
+:15455100A9AF4C2AA8DFE584FB28BC59757B1324DFD5DF9450C1
+:15456600697FC9BE2F7C2E0CAB6C182A0086BD0043A0A87C2C1D
+:15457B00B70E60C078942FE45F5CF6053BDFE9A27CF3F967F20B
+:15459000229E150FB3DEB578473AE49D28896B888F3CB3F2BAA4
+:1545A5009AB10555801B11CAF4AC69061C35288BC416B5F6430B
+:1545BA007CAF9B0DF4D57F9EEFDD3B3F4934C0759110C0817BDE
+:1545CF00705791DD9019D81126929358E21EAA1BC074D286E933
+:1545E40028D65DBC51ADDFB956E52A1DD8F2171DD83A01B66A62
+:1545F900B181FC1320203AF4F254B6A822C1B45D595499547755
+:15460E006DDD51680B88E73FE4F3360C8CEA893695ACFDE3E983
+:154623007BB0DC9C0D075793C2BBA784F99A05837767A3EA852E
+:15463800F2B06EBDB62E3F2ABD115ECA66F0520F6DA7624D974C
+:15464D001AE889103CDB4AB62BE580970A31A996B264DF0EBB47
+:154662006D2EA6BACF947B0ECAD221CC0BF802BAFB1C1890F65F
+:15467700F5DD372A280BD5F7DCA8CCA53F74226F39ED1BCCF4C2
+:15468C00A7FAA93D148F3BD5A2066E8AAB3CBB539D07711FE630
+:1546A10058D06DFF1BF05FD10370427BA9A88CA8DAE31DCADEFE
+:1546B600131D4AC5A9356A4003BF9DAB55ED233740DBBF41C1A6
+:1546CB003610009CD6AFB170C1C7B95E996DA0B60D729ADFCD92
+:1546E000DDA28AF112BCF7C4EFC1BD9ABAEEFE79DCADAAC8AF72
+:1546F50052C59550DE7577A8E2EA1BD4E192554A04D70156CE75
+:15470A00C42F0B5AE766287E326CC57FF233AAF844873ABC35B0
+:15471F00ACB861DCF53E7E9B52FFA1925EB48D2C24C27D9E6CDC
+:1547340047DFFD684F4A0BABF5A76E51B9931DAA067139618691
+:15474900169BB08F07E99CCD3AB4A5220C40BAE31D7D34D76B64
+:15475E0067BDA5DE505F99B7BDAF3EB556D906BC4106FF21F8F1
+:154773007E1A79CE4FD6ABE250878A72B52CDF8AB29D20263FAF
+:15478800A3726BEE5429ECC90E080FABC38033A43FB6719C93FD
+:15479D0094FF03D75B7F0A70A80DA881539F512B4E6EA736B0B1
+:1547B2000BDAC03CA07D7954EB2BE64B4877A41DE928DB34C57B
+:1547C70036791DC0D28BF0039EF16EBF1EF84F96DCA5CC837208
+:1547DC0010FEC1ADD6FFAA8BF900F225C78D71616C58B7278FDB
+:1547F1006B3856DB36606A85F843BE0773AE054EBBBA1BE290E4
+:154806003E39C070D71CFD4AEFE3B54AFDC96AC06DB57AB97630
+:15481B00F4129B6947BF64563B421A94433B0A8A77115E1EB6C7
+:15483000DB12B6A380067E73DAD2F6C49FAF2F0F6E00FAC4FB9D
+:15484500B7B35BD7CF0BC6FA39F10ED57157446F5D8F749A376F
+:15485A00B0B68F9DE8ED73BEF52FBBE87E9DF2C4DABE8A6C6F1C
+:15486F001FE7019A5FD9A0B9810E1781F688BFAB8916AEDE0ABE
+:154884006D0BD0E10EA0C30EE2E9C5B428B16991B4EEF5F17365
+:15489900C29550766CDD14D105DB4D6CDDD13974019AF86EB218
+:1548AE00E9027CD7B7B6882EEF2BA2CBABA43339D3B7B742BF10
+:1548C3001E29A213D987863680FE9B6D5A9D063A39B4D9F01E37
+:1548D800E971A6881E3FB2E9817D3A60D3E2123A006D8A695101
+:1548ED0016FDD4FAD2E8DAF5EC6ED62B663FD5374FDAD057C62A
+:15490200FE7A7D79E26FFAC4ECADE4AEFF3CF5579FC8C01DBD70
+:1549170093F0CE6F6ED05CC07B2F96C5B47207EFD04F2BC4AD95
+:15492C00C0FFFE42AD5873EF2CBC975F0EEFA9C548F322BCC7E7
+:1549410009EF4E7D8BF10F3CDF779D8D7F5C13C17EE2E0DF5732
+:15495600847F17AD6D58F81DB1F15B897C0AC2F0DEF0E23EE01F
+:15496B00A47F89C6058B66988EECD3DB74B0701E9905CB97015C
+:15498000CF6286EB7D12BE49E03DA59007DE9FBCF763B7A9F5A9
+:154995003D5D8A54B254292BB956C1362C02EF61273AFAF6FE68
+:1549AA000B849D5AA3F84A3EA2784A3E6A8571ABFAD8F537F4B0
+:1549BF006D8BBB7A11AEC5F69DF2D046BCA5B28BC648F4477B35
+:1549D40018CE1C05CB47BB9125CED86D8F1F180F75A511AEE79C
+:1549E900F3793FC429F4FD27A1EFE3D87506A693B85E82F2017F
+:1549FE008C75FE27ECF8DF82FFFA9779FCFA8E505E2975976B5E
+:154A1300FC36D4D3629C7597298C2C795F74FC768369A4F38504
+:104A2800FEFE99F5D6F2FF06E2E5D1A4A89C0000A7
+:00000001FF
diff --git a/drivers/atm/sba200e_ecd.data b/drivers/atm/sba200e_ecd.data
new file mode 100644
index 0000000..d097e74
--- /dev/null
+++ b/drivers/atm/sba200e_ecd.data
@@ -0,0 +1,928 @@
+:150000001F8B0808AC5A10380203736261323030655F65636426
+:150015002E62696E327D00DC3A0D6C14D7996FD7B3F5AE71CCD4
+:15002A0078592F3F8DD70F0AA949E8DD022E07F61A1644AA40C3
+:15003F00012D88A433EE52268844E8924B571792229D1B0F8EB1
+:15005400013B7DD0C7608813E1640D5E58529C6C909D101DE4AC
+:1500690016357749A4E4BA8A7227541DC9AA17295C4A2F76455E
+:15007E00259438EC7DDF9B19EF7831C4A1524F772B8DDF9BF742
+:15009300BEF7FDBFEFFBDE1B3FFCD3BF7F88B808896E2484FED3
+:1500A8008890844A880EFD1CB4BBA00DB710128396439B8076CC
+:1500BD0018DA4E68B51FC3036D16DA1DB8364E88026DE92FBA6D
+:1500D2001EFE486452BF7C63D90D63AE825E0863FB54E1A984C2
+:1500E700782F999F6AB59F9E3C49B19D522690D8ED9FFB737D9F
+:1500FC00FCD38F45DB66F353D2B6AD1433AEF2F2F209D77F491D
+:15011100BE34E18787275C3FF52678EDF13693B20B7EE47FE17D
+:15012600E71A20BB45FB4AA95D5E29DC72DD983C8589E52B4C68
+:15013B00927E7959B9A987A7DA6E4DCF24842D778E97CC7F63BA
+:15015000F90B6D6DE8BEAEEBF97C299D49C95956A43F7A5BF4D5
+:150165005F7C512AA1FBB7D87EF4AFBF99905E79919E97FCDF83
+:15017A00FFB93C759E5BCDF3F48DEFDA29E89C2A8EA109DC0E0B
+:15018F005FF8FFFE2B387E24ACB3FC6765A432BB6F911CF4C674
+:1501A400C1977CFA72F2308031121A8EE3BC3E026FE14E96FF67
+:1501B900025AF9AA21793BD46B5B3B1A708EC8A429FF1CF1557A
+:1501CE003E4F7C81FDC4977802FA5DC447C2618EEBEA932EC057
+:1501E3004BB79000C012130F873C52EDEA50657DA14AB86BAFA6
+:1501F80014D4B75C5C467C1D4F126F20B8231E269759EF9EFE32
+:15020D009D846F61249CE1FA03844C0B6A716FD52F20EB9C6518
+:1502220035C1447C7AEB6916F59268404FA9249C341086C4F6C2
+:15023700182477ACC79FE300570FFC87E3FBC3A4657AEB6A1692
+:15024C0085F4D4BE7FB34AE4F5AC7D7DB3FA3C213546D2DD045F
+:150261007C32C81F7230EF6A9E22B7A8B81EE7116EDFCCCB8A9D
+:1502760067E549751FF5B490DC6C5641483010844C26EF66BEA1
+:15028B006067FCC3B9C4E721F3D53DC3EE1669F72655BAB04CF6
+:1502A00095B6AC654B008EF03EFD6EBA6531EA08F113F958A63E
+:1502B500F8F4EB015853B966BE7AB950A8FEB04D8DB4FF933BA0
+:1502CA00021254BC2478DA75DB3C456FC2D306E429775C5F2546
+:1502DF0078A202FFB7626115F9D9AB95B5608BFC601B04DD5402
+:1502F4000575C0F90BA1C39DD5640A91FBF4DC8A2D0DE780D715
+:150309001DC0AB3D1FAB26E3C3487898BD07DA0F053964FC6180
+:15031E00B09F6E2C85F4EFDDC054B2B33F93978886ED30B49447
+:15033300768879E085CA723BCEF75CC37918AB1763BB718C8F81
+:15034800E218973A503F887F41CB78FCF545FC0256ACB3E8DA10
+:15035D0034052D8BEE84341DF8D924F1874DFC62BDC065D1B458
+:1503720069396575E2BF52823F5CC47F03AE9BF17F2BFD283F5C
+:15038700BE7DFE1BC41863C362AC4325B1FE8C3D3EF66ED31296
+:15039C00F6BE39FF0257281DAF69ED17F8883C2F83386A5A1923
+:1503B1001BB5E418C30B73E3E48AF573539E9BF37F2BFCD76E07
+:1503C600827FCCEEB1FE1E1B7F838D9FA45929AE521C387FCD8B
+:1503DB00E143B62C02A73CD433A10CE3F647A7B91FAAFA55D204
+:1503F00030CFB4AD06B685FE0DBED990327DD38903EC5BB9A572
+:15040500685F6F5587E09B3474B0AC44A2105B784D2CAD1ECE76
+:15041A00B85B80BE512D77A9570A85A0D33FD6FD99EB3BC4FAF6
+:15042F00CE09D78FAD053C57715DCCE12B18A2352F4BE4DF3E26
+:15044400A3E73F3562F9670D7FEEAC3AFD034D21B14BAC4EB966
+:15045900475D4C7FC5F6DC3B9020F2BF81EF26793FC4F5605035
+:15046E0089631EE0D00FC49222DEE3788D3E00FDB481E324B744
+:15048300A8470EEE8A93DC7B10B366C4F7CDDCA178E9E3ACFDEA
+:15049800FD956A27C4B7F6F7D7837DE688787987152FBF0532CD
+:1504AD00C87598AB92BC1BCF26E134E7D056692EE07F3ED0CF67
+:1504C20033CC96D2CAB56AA12CCB24D72A55EADDC8BAC949C5A3
+:1504D700A50DB0EEB2E30AC28C421A3D4C5E56C05E0CAB886E6F
+:1504EC00F23A8C67712DF4FF45113C02DE68FE6D03E4309096C9
+:15050100DB45AABB203749D1BBD5BB80A7629CBF17F86C6701DD
+:15051600E06D6788F8BC40FB933677C4BBE135950CFEDCC09CF9
+:15052B0087FC728B5FC455F5515EED2E3BA9A05ED65592181934
+:150540001C30B244C0E918E7BB519E705AC4FC2A42FC2496D294
+:1505550047B7F635873457A377C309F0B30106F089DD3F9C0F86
+:15056A00364FF16B85424D9DF26B359AFF9457B94EA8B1FCDB9D
+:15057F009C84327177E57915E18379C83D202BD2385A0672CBE6
+:1505940003461053742C63CEC97F32C0F601EF8697D559078ED5
+:1505A900C3BE4D097EC0EE19B0BBD8136BE99A8829F3A21ECFAA
+:1505BE00CAE3AAB013E634CB4601878D1EADB5725A02721ADA1A
+:1505D30000748276C8A27FA15F800E9016D959D40FEAE5975DB2
+:1505E800DF079D844D9D583CD83A09002E874EAA854E56AC5F7D
+:1505FD002CF0900C0BB60AF9C00FF764AC07F676126B984CB013
+:1506120055E82BFA3CD80FD619159837071F671F423DF547CC48
+:150627009D9ABBB94EF94FD5EDFED9920D9ABB2948DDCDE3ED05
+:15063C007B3FE4F1ACE201DD96CA8CF2A1DC1EB2006AA3679877
+:150651000CB2CABD9BD88E3B896FAFB6B1C9BBE105F0796ED6EE
+:1506660014E1A5ACB002FD803221E3D52B26CFBC5F7F80DEBF28
+:15067B00988492F15AB2470D8C32C12FE9EF63DDD98560AF85B3
+:1506900006ECCF8CF5F4E05E053D0AD9486CD0C0F501D8C35394
+:1506A5001C72BD05754ABA6D6364519B29DBDD753F5B485DC4FE
+:1506BA006BCAFA6BF53A79F216B2E641D6939396B5F5DBC477B6
+:1506CF004CC8FA30C8BAC39435047BBBE1F7A67CB1E3FA532095
+:1506E4005F6DF63BEAD4489390AD2C36430DE9A1E66F635D12CB
+:1506F9003B20628096FFDC38EB2553A0E5B81F85AE5007644910
+:15070E00D12FE4BE8CF5801EFA8A7AC8BD6A209D523DF4801E4A
+:1507230074D043D0D24325E80169074B68831F4E194FF38472E3
+:15073800C0972AEED189F7E6346B6F46C6E6D1C78027EC8760F4
+:15074D00EF3AF72BEE55C253FAB5151EC10BC417D8AF2761BF9D
+:150762007ECADD9543AA6BE659D5D53524F6EC11C79EED297B45
+:15077700C5DEB37E9C3F52DCB335E8FFE8D7CE7D3B0BE05046FB
+:15078C00BF4346AD9C4C71EEE7B1FC943BCDFDC1CBF1EA46B191
+:1507A1005788F48AD452BDAC53F4AB5DEB45BC8E06486C8EF056
+:1507B6005F6EE65EC88572F20A93D6ACC59C974940CE2B4C4D3A
+:1507CB00B05EFDB8C2662694C06E880F77A4541FFA4FCCB0FC60
+:1507E00087C7671D931ABD55AB61BFC6581BD118FA4ADA05352E
+:1507F50041586188A322F20CD3A03568598B1F7CEB04ACF36627
+:15080A008FA9011DF3FFEF0D51CBE01EF84BB6DB3E6764B09B53
+:15081F00E9E04BC89773FE51F0AD1524C1B6E9879693F065A3B1
+:150834001E6581396FB61DCE4A585F7C435A0F9AB4B278CE7380
+:15084900CCAD4A8E3621FE34B9357E5D33D7E74BD637265BC568
+:15085E00FAA513ACA7DA492677758B1C6DCF47110FCCDD958C37
+:1508730034A1FEEDB501FD984A61CE8BF0B1B9803B950925C1C8
+:15088800A6164C8305238BF5A9CC647C22F0A6D48CF11F7DAC82
+:15089D00167D35D76F94D64FEB1E76D43FA2AE642C8AB58F2916
+:1508B200DF196CCD7ACFEA53689DF58F63FD1C0D7D1060A1FEDC
+:1508C700411CD21AA61EC43D0BF2E27E1A572BE3F1F93E2FEEED
+:1508DC0053C31FCCC6F17C5BA75C51FF86BE06E7DDA19D24F6BF
+:1508F10004C39C9E7B40D4893C8BF50E9C875D33CD9A270A7153
+:1509060003EB2B92DB8E7006C2C9C119702E7E5A9C8BA747FA68
+:15091B00C59938181D54B1CEC0F544AE15676212AA8F4F8F3447
+:150930008873309E89CB8066FBFEFBADF3F0BDEAF32EFB3CBC70
+:15094500D43C7BDE10C78B67CF89F2349E3DDFAA29397B62AE9C
+:15095A00B6CFC3B992F33087986B3F373B0F7F4D8C7C4CDB1875
+:15096F00F11C3CA3BA601DC6BA3D10C776BF9B56BC55C7314647
+:150984008A5A40C43898C739A0853569CD9CA4A7C98449A13EBA
+:15099900A222060A9B891858EDADFA15F8C48098DB4EACF36D2F
+:1509AE0091DF1EC423D60B9D9B7024F62763F7BB19657A24259F
+:1509C3006C00670103C726F7FCB3217C16EC1BA8492A585360D5
+:1509D8002C47BF7906F9224BD94BA843FB5EE0366C531F98D827
+:1509ED003613DD0B94C796AA28EB3EBC178056DC0B403BFE5E71
+:150A020060693CEEBAF15EE0EB6CD6FE17B0D944FAE413D8D15A
+:150A17005B750FD830CA2C7939CAEB6D9D6DE290470CBF43DE6A
+:150A2C00042153049FAD7D967D070C8499DC73613C3F729F6190
+:150A4100E39D01786D5BDBF1E9CE478AF1C5E5EB107ADA94DD30
+:150A5600D3689F51E58D49A5A67590C5800FAC0FF04CAA79CD1A
+:150A6B003C8DF05CE80DFC0A748363EE92B161475C9A41E7F61F
+:150A800060CCB93802B97A659F1AD0D07F1A18C81E708580C77E
+:150A950031BED23C564648A3E66944597640DFB5C63C7FE1F838
+:150AAA00E1DA2106B508F7C82F412CEF33EEA4A70D29B9C7210B
+:150ABF0097CE104EFF119EB74C5C4B268B2B79AA880BE087AD61
+:150AD40073A08BE8784634F0DDA6F34DE4D11DF2F43878D02783
+:150AE900290FC2651E30E5D11DF27C2DAE1279AE96C873F536FA
+:150AFE00E5C938E479C7C1436692F2205CFE7E539E8C439EAFE6
+:150B1300C55522CFB51279AEDDA63CC991A23C67478A3CE0F891
+:150B280064E44138D99207FB4B268BAB449ED11279461DF2440C
+:150B3D00ADFB0C19F3B9E5E3750A53EFFB09D2357D04DF975A45
+:150B5200EF19EBBDDE7A47FAF83E43BC2799F065A0B1AB4CB4FF
+:150B6700ECAAF59EB6DEAF59EF17ADF751EBFD1DC0B38B9EE038
+:150B7C00D8D760AE93BECCC5BD03F47BE86B5CD403D04FD337E7
+:150B91007994DEC5CC73C5592303EBE05C51334BF33407939EC7
+:150BA600667FDED3EC053B606D8F3236D05302CF7658BB9ABEDF
+:150BBB002AF074437F337D83E3FAB74C3E38D2DC4E777313AFA4
+:150BD0006E0C025EBC6B5841DE6167B6BEBC5CDC59004C15F5B9
+:150BE50034E2D80B5B5F136359C7D8335BDF1463C8933DF6E484
+:150BFA00D6BDCB6DFC7751D37E886796E8A7F9768B3EF2B4B65E
+:150C0F006E60998DD386455C36ACCD2BF216B26011EF5F514FEF
+:150C2400B3B84BB1743AFC88A9D39CF8EE61CA3EC3CE1F304F8C
+:150C3900C7CD9BF4EEB6700C9A638CFF9D09F3A1850369071099
+:150C4E0007CC651F2DCE452C3DC6617E1DE80F75C7A01FA7072F
+:150C6300399E8FCE099E2E0B3E1FA5CFF15EA1DF1EE3F48870DB
+:150C780040FF03D73D4D7B67526543E88D85CFE692CA0F962315
+:150C8D001F9D424715509B2E592E352D0AED5EC861EE6E319754
+:150CA20011FC56243D8DB3DE949A82A1830B0D98AB5A6EF28FE3
+:150CB700FAAA807D72FD2BA9E98BDAE7161E82B93F367B9A2BEB
+:150CCC00B4F2C6CF2EFD2182387F57CB22B8FEB7BD831184FDD0
+:150CE100E0D269C8FB3B044DE03FA38B7686E019E4CCC444BBDF
+:150CF6004BE026E1682629DA84E0036A8E0CEE89E9172EABAEBD
+:150D0B00F735D87FAFB0CA60268EFA31D75D36368BD6D41109F9
+:150D20006B8602EDB495C755D7FA47A0F6D9CFEEC05C097A3363
+:150D3500E9268D0ED1EE303A45DB23F4590EE705D7FEC701FEB1
+:150D4A007BAC2A1806782A6219C20F8A36619C15ED52A1F32969
+:150D5F001700FEFD7F10B5D5D4600CE0A386C977D2E887F6692B
+:150D740061875D467AA498DB4808EAADB0226CB30D6C93A007C3
+:150D8900B81CCCC1D845B6B4CCBA2B17ED45A301DA9DDF273E14
+:150D9E001E76B7C0318F1D182DF8D1971E85F14A7A02EA055D0D
+:150DB300E8AF0CCE160BBE8370E6BEC25CB9CD82457D5BFB8D79
+:150DC80061FF29A029FBDE533B9625AD6F6D507FC1B896FF8DAF
+:150DDD0011681D62E8C0DBF3AF1BF0CE75E02D104DA9B20FCFF3
+:150DF20039EF1B121D323E69B0F8A1B3D9F52F4D1A4761BD6C70
+:150E0700F1C32D7E8ECE29F283B9EE030B36EBE0277B0B7E623A
+:150E1C000E7E36033FF0CEF904FC6C76F0F39845E33DDC47160B
+:150E31003F598B9F4A073F98AB5659B0E86F363FD8BF193F51AC
+:150E4600073FAB811F78E7C909F8796B71919F8FBE30699C1BBB
+:150E5B00C19C66F28334909FD6D9457E30D79C3161052D37C413
+:150E7000D68EE694310A676A3BC63A3F0F6874906BF434C4E84F
+:150E8500CD4C0EDE173FBC2CCDF0FF560EE74E2AD65D9091B78B
+:150E9A0062CA7F8CE0652EF17B1D79334EFB7959D57995E60779
+:150EAF0058714DDAB868C5631B5601B86ED8DB7E888DCEF53124
+:150EC400DACD37037D05F850E85178AEF0049DCB77D105E03353
+:150ED9000DBC9346C056AB799DB24D35C8A1447D3EC5BAF55427
+:150EEE00A207E093F41C4F53C60FF79E663564409DB6A597D514
+:150F03005EBAC23AB67C97EDA99DCFF66E59C8F6F52E639D97C5
+:150F180056B223B56718DED37CFCDB32DCD30CFFB7E7D9DEF32D
+:150F2D00ACF2C231F5E0A5FD500FEC5FE2E4EB9D30F155D1D593
+:150F4200CD6363B176D6B090F8FCCE3121403B3B7A93F1CDD75E
+:150F57000A378C2B5A3BC77889FA09D44FB08EB7B33B9B26184E
+:150F6C00875AE06A1DF179434911ABC046F2DCE4318EF6C5F74D
+:150F81004F46AC1A413EC789CCB844C51D2BBF8AFF6810E6FCBA
+:150F96009A687BF8A8DBAC7586451B66A4007551554AEDD6878E
+:150FAB00946EBD5F9926A5D4236543CA91E88072186CF9ACFEB4
+:150FC00086E27DEF0FAA8005E0837A9722AF632AFA4185833FB6
+:150FD500BC8390D890187F52AB6CFCC4FE770F2B6EFCFB0A33BF
+:150FEA003F6A63B934C991E73990C72A925F35CC4A3E8179C4C6
+:150FFF00CA8567451D63E6AFB366AC039D99F98F5B303D026617
+:1510140047314F8AFDF09845277B039D3BC6D131F3E559510FD6
+:15102900D97432169D0F2D3A264C0F33E3709A87AF12D21B76BE
+:15103E00B5941F48A981E83CBCDFF3FBB5EF468290ABAE5E372C
+:15105300E5FD40FBEBC6CBD0F782FEEB14380779061290B39AFC
+:151068005DA1745CA678DF1B66C92CC489A4AB65333D652C022E
+:15107D001C92FC3DC8435D3C14DD1F3948BA13788676877AE21E
+:15109200D23D50A3C5468CB974C4705BBA8ED02E234A0F1A8197
+:1510A7007C8A734F6AE702DA67605C895D0039F2FD5C069C38D8
+:1510BC000E276206072EAF93E6A617FA9A9DEBEB9443AA61E19E
+:1510D100C0B5D8475C147049A1FA78B464BD1FD647E97306D4F3
+:1510E60010D6FAEE09D7E7FE1173537D1C7909C3DC02A833C232
+:1510FB00B48BE1DD96413A120AFD2FC3E3C238779A19E470422A
+:15111000D23A0C3FE46037D6BBB17EC3AB751B95DA39718F365C
+:15112500DE8FCF5A7EAC8FF3E331FFCDA614D9E1BF55502B8C04
+:15113A00F928F801DA0F7DB4FCC0805A43E6A9A66CF5A66CD11A
+:15114F00BE66A73F0768170F6A529384E766C29917ECB1733E0C
+:15116400F1E1FBBE999DCAC54DC4F7E2CCA422D3F9C236ABD16A
+:151179000EB096AFEE7E085BCF52F13F2D15B383264E79893719
+:15118E002E43FF568F0947444B8336DF14F65D1D91AB16A81DE5
+:1511A300EEF90F79AC71EED71FB2E7F1FF6AE4E8DC07251B479E
+:1511B800B4FB41BFDD5F591C17FB04E498438E36617E801A931D
+:1511CD004E8BF5604D631CA2520BD51883B37A0061360998CB74
+:1511E200D6779F4EBCA3A0247692076973837DCF82DF034657E0
+:1511F700342F36BFD19B7797884F4E7633C487637EB95B7D077F
+:15120C00713AE82F17B829C33B5949FE0CE87719B80EE95BEBFC
+:1512210032B80EE89D71F62F3AF158BF83B9E39073F0FBEF1552
+:15123600568DF74FFC285B8BFB92DE1B09D2798DCC8A0F3FA1C4
+:15124B000F35EA629F778AEFE5BBA75C413ADC2F3F67DE75C28B
+:1512600018E4AB1A1BBFA4CD6558B309D81F7EDE4272FD7C9ADA
+:1512750003B6C2829520374F16EE10F0BA0AE2DD0B5A5D13D6FA
+:15128A0008E677FE432A9C9E184473435AF339F8E46CD63B4C08
+:15129F00104FA67AF42893454EE83FF3BB42A11AEF06B789B5BD
+:1512B40069B17681B69FD5C84FAB776AE759448333656C9E811D
+:1512C900F489DC6F081EB09F4B19923C5745DC52FEA88DDBE4E5
+:1512DE0011F6DA589FA78C2CD058A44D6FACD2A6477EA04D6DF9
+:1512F30044BE376975CDB67C923C5B2D956F3273820F7BACC455
+:151308007E7C18BF7D9F03DB65E24B08A9C63AC6E53BA7BEE98E
+:15131D00C2FBC734D64D747A10725E6CC8C0DA424A0E31D7BBE7
+:15133200E7D5FF065E7B5CC48BF6C77BF9465AD358456B227E15
+:151347007A47E366D0D32A5ADBB8CEB237E2A8965F1577D4E057
+:15135C000C34289F51CB80C60298AF86BE1BFA088773F6B90BB5
+:15137100E15DD638AE0F26E78571BD8CB2027D19F8284CA5ECCB
+:1513860008E953189C8702AE3EF17DE059F17F5203D67D685A64
+:15139B009CD1719F797D57D4368823337C4755D7CC4EF57052CB
+:1513B0006AA9B9705E9D23EE5933868EDF5E760E88EF34384755
+:1513C5008E0DB1CBE2FB0C65B3C8A9E508432C18F17DA56F880C
+:1513DA00CDD2BE6A126B89398EDF4DF4248EFFBC89C883064DB6
+:1513EF00A6F03B8821AF1950A330EED79AC5B817DEA980AB6D72
+:1514040092F05E22F6BA7118FC026582FD5F8D7715B3A2A71B27
+:15141900C59D456ED038AC4942EF788FF70CC43C5BC6A0252368
+:15142E00CA11B1F27D7D01CF75FF43CC13064755A4D993BC91CC
+:151443001988C3038611280C0F1CD6E045326894648038C168C9
+:151458002556E40688EE7B5CD089266EB450729055768B5B5FCA
+:15146D003081041F5CF34C206BE1DE84CD94932B2203071A3D8B
+:15148200ACCBDE79BB68A5AE721677C7D5B9DEECD69E4779615E
+:151497008D6CDC65AB38E7BEAFBBDFCC9BC9A0E0DED5FD987AC3
+:1514AC006FBEEEF77D5F7FFDF5F77DDDFD7547F5F31B315A88AC
+:1514C1001BB88ED10BB618607BCEF31C0253B2E941318E59B05A
+:1514D600E751CCF18671A03F4CDCB28FC53814642D3F01387A39
+:1514EB00433155770FB3BC0699D9A328ABEF954FB03595493179
+:151500002F4698A20CB3BD8A291B2C2060E47206562A6057702E
+:151515006D46C454576DDF30DD1B292025CAB0698761DCE8CF5F
+:15152A00819102899467C3E8203CE5C8192A83AFA9C032D15E21
+:15153F00DC8F21F4769E6B22BEBFF4E59C7F70B1EFE3D4E291CB
+:151554003FC5FFF019EAE881BE606BC0316AD10929C3F44592AE
+:1515690030FAF4188BBB3F01BB07D1C3168CBB93E9B888CB7601
+:15157E00C1548AC935947C3DCD6728F916EDE819566B6CBC2309
+:151593004EF46F7DA14186B319437E5C0345D9C8C3B42F1C65B4
+:1515A800F07A019F12B1117B029F3EDC2F92193EA6035EFC1FE0
+:1515BD008E1956FFA7447F768BFE64BA60C90D6224A00FF3A197
+:1515D200D7696F826C29B2D96359B4C7259E72CEF3BD2FD3E9DE
+:1515E700C559F685CBFB75B6CF555344C27ADD1915C74C0DDB68
+:1515FC0013E730B2613AACC00E2BC6711C376BC1E339C47B8D20
+:15161100CCE1A1E409E0F7049D3FC9E4EED3053F0C0636CD57B4
+:15162600191773BF8421C3372C8708FA8C3DC3678CD9888F0E7C
+:15163B009BF9EA933CF549BAFE5B5CD600837773773ADE8E53ED
+:151650006931CC3E443BF19DB5C90BBC5B6D82F729479CE97FDA
+:15166500C09B29DF579729B7607BF3C0A40DD3614E3B2C67FFB1
+:15167A0091E9BB1863D6F8B58FED8E9CB18D4FE7C7710DCBAE50
+:15168F00F58D9EE71BABEC255B19C674ED8BC82CCBA6A03CAC55
+:1516A400727C5F72A1536BC8299F85B4EF47DA71A3028C776FB2
+:1516B900C4B945C6BD0EC0157E28639F5037702C5865E5F6325F
+:1516CE0090834BE0614F1B8EA21C1C56FBB9FE71989E0B037C5D
+:1516E3007B2C983703D3F3C0667AB261D8A62E5B9B2D58671E54
+:1516F80058A1C70ED38D4431D8B685423E246AA0FDC0F1E915CD
+:15170D00FA1E4FEB3BC0D017D8D63A4811616B1AA8BF5EA6BF42
+:1517220031C3D2F39907713D3C418FC2B8F785B86DCB8743BE31
+:15173700611C5086397BF230D6331B6CF350B4EF681FB7A15DC2
+:15174C00C1B526F8EEC0C2B8CAF0839EC14490E683CB36B861D9
+:15176100AFBF203F5C0678AB43D8DF626E732D99615C500F659D
+:1517760079E5E5CDDFD6591F27B43E7DB8EDC0C284CA7310AFE4
+:15178B00436EFF8BB8B09D5FD507C68DF0F58D7165F76B42D8B7
+:1517A0007BCB77737B9C2DEB6C7B9C8717F886F302710A3E0187
+:1517B500F76CC4C7F6C3F3C8214F7D92AE9FCDDF6E9BDEE11865
+:1517CA007678A68F71E2993EC60B3CD3C733C903DBEF9E3EC6AB
+:1517DF009D9EE9E359CA8161FD57ECDF66E6BC221EC371CE7D03
+:1517F40003CA7489CDF7C15397785C62129B2F9473CA245B99AC
+:1518090037A7CC652B5B905356642B5B2C0B3F129834E23DC8D7
+:15181E00972EF2CBAD582446838A3390893D6294E5EA425F6464
+:15183300628FA8C6E38C9CD853CEE4C8C8BE924647DD2AE853AA
+:1518480098670742867C3B2105072B357D32E5763A3AD5367878
+:15185D004A20935E85AFC3FE0874EA70F4F7E5646A805EE941AA
+:15187200044EC44576EDFFFFFDD5D610F7D86729370D75A835F8
+:15188700F749EBACBD4DDC2F24EDFA293667688F9E1A67CFD1D9
+:15189C00532CC7AA3D796A929DC989D2C446BE16678B410D07AF
+:1518B10019FE18E539713F71ABF7F3BC65EE67BBC1CF0E829F2F
+:1518C600051FC9E456C2E466F9EA1AE083783AD5F2CF84EC22A0
+:1518DB000E263BB47D51C0E307F847F749AB91D6D98D7C8C8CD2
+:1518F000C013D71B1D0A8B154DFCBF212BF68CD107BF617F17F3
+:15190500B179E0A4896D045924842C12421609218B04CAA2F079
+:15191A005E57A33D7EB95EF9E158F228CB033395AEC0D1422EFD
+:15192F002B0EBB13600702F12CD83D00EB0B9CB66045089B0F62
+:15194400B05B03A30863FB3A5CA6572E810C4196172F4D9765C3
+:15195900C37AE2BE700965E95F7DBDEDBADEF69CCBA30F054261
+:15196E001F3EAA26EEDAEA6C3E2B24E2AE075E9CC0EBDA3CBC7E
+:15198300C6A17E29E3B574F58DF4C1F5F27B3C0FBF8582DF525A
+:15199800A0DD1F9AAEBFFA35F4B77322E52E8076B44F4C6F471A
+:1519AD0012F0344F603BBCAB6F54976EA43DF13CED91447B1A71
+:1519C200818773F7417BBE01FDD6FF8236297E2A2BA594D97E0D
+:1519D700B0798EBB0889001CFF4BA4D6D88DEF01D550403A8B99
+:1519EC0015CD9495EA57D91E33D899FA08AE37478DF001EE1352
+:151A010095837CAE32FE32F86249077FD8CDD64B0DA837161922
+:151A1600328B9A4E9A139F72DC7EC09B9AAD1B53F0DFC1FFBBA4
+:151A2B002E7C9ACDCF0551F70A3C1B20DEF42A9C1E51393DA4D7
+:151A400083F4D02BDBE92D867A454D43663DD0DB2D709CFE9440
+:151A5500D3EB14F4E0BFCB2E4F59C8B3086489E5ED5524AB7C94
+:151A6A008E65FFAA7879434EF95C513E26CACB73CAE789F2E328
+:151A7F00A2DCCBCA278D1A9A39CF92823652D2A5E2DE9D0B7DD7
+:151A940074CEF9956E888768D4B905E28CAD55B8AEA977AD2A29
+:151AA90056A4A02AE29A9E8517D5AAD0BE60AD2D5F60CF48D3BE
+:151ABE000728ABE7DB3FAC0AD8E01FFFF0E5AB085FB3F5832A70
+:151AD3007B7EC18537BFFF1AC29FF8DB9F559162DCDBEE373A7F
+:151AE800FFE5856AAF4FDF4A1E236E974ADCD3F8B4E58F2987E0
+:151AFD00B2FD338F35264DB4210E0F9FC331989CDD362B3EC2C4
+:151B12007A3341D7A7D6F2DC19ACDB5337ACCE8C3C12D85FC732
+:151B2700EC391DB5EC39945967724637E69FEF1B9FA7DCF14B5D
+:151B3C002977661E46B55EDDAFE2BE0AC48DEEEE8551753D59D3
+:151B51001EF490E5AB16298EE0799045CF42AA6ED0FDC17369BF
+:151B6600B9C4D99AC4CD4C1E740FC6284E88F50673CADDB672FB
+:151B7B0009CA3B3372ED2E9048B70BCA1D65D07FA2CE22E6D307
+:151B9000F8B73741FD0651FF0EB0FF3EE1DFB0CC07651584EB9F
+:151BA50013FE5F7261402B81FF8B6C75965C8869DE1C7E1E8494
+:151BBA007E4A4462A60B7C6CF96F53EEE8D594BBF9CBD42CD1C5
+:151BCF00BE7511B0D3A03F5976C52BF45442F9839E86927F6EF3
+:151BE400E84E320BF50CE3600B37AE5FE21EA16CE30B73854FA4
+:151BF90047980F360222BFA108F02E00F97B9712B7EEE7BABFE7
+:151C0E00600DE016FB3054F8F31749BFD13B3AA8BEB90CF7E984
+:151C230002068F654B0C561EA830D07717B863DA0F963E5CEE8D
+:151C3800AD8CD2D65732F1168577A90F2255788EC2EF1CFC9498
+:151C4D005EB0AFF01B17BFD65EBEFE85F82EFE12D7BF94C6F455
+:151C6200BC4CC0A3FFC1E161012F009F5683EF7480CE837E2B7B
+:151C770000FF50CCD618264DE9DB84BC823E830E9896DCADF9D7
+:151C8C000796596D9394037406B3ADD4083C4A48D17FA6DC93CD
+:151CA100249BAEEE22B370AD1BF1FF33CC476702DD31A81376E8
+:151CB600733F65E191051EF932E76DF493947B90E7F5883A101A
+:151CCB0007D1210DC7EC646A361B23B837D6F7F7C7198C4CCED0
+:151CE00026DE9F0EF277F883F2CED48D9AB89FD6377A5C9D2F99
+:151CF500B1F50E93EFB3454DDC575B6BA3C3D71DFB8DC7C61FD8
+:151D0A001B5F363A98A53F37A3FE209FA043814ADCA386B8203C
+:151D1F003C443146DBAFAC0BB01C45CC8D8BBC4B95C819C3C77B
+:151D340072E8BF303D7CED9BF68A7557BE8E3BC2F70C020ADF8D
+:151D49002F40BA387EC227CDE7593FDC40FC373E8438D2F3B0B3
+:151D5E00AFE741D0065A69FA79F475DB3AF0CB69FB17A5E3B899
+:151D7300D62B1BEC9C14192D20E037D8FA3EE0A0631B591E841D
+:151D8800CF9A67CD2103EC0C12CA784CD834FCBEFB5EE296C690
+:151D9D005FD2E40F5F52ED362D6D77D99C6CC448FDD57B923824
+:151DB2003324A32D66F3191AA37790C120E6EA4FFDFB7095BD05
+:151DC7006F6E1263BBA482E45D1FE56B9BBAC03B22F026742EFA
+:151DDC003FC0ABC738DE25C7B3F0CE1078C757E7E0457B08F1E8
+:151DF100D5BE85FD2A3BAF0F3610F126393E7D19EEC9CB83B482
+:151E06003039AC4BC933BAAB28C6D6F966029DB961AA3922869F
+:151E1B007128296D91944EE316F91D6D2C95F279A25230334FC2
+:151E30000D1B1877EE72A0ADAA6DB4CEDD635E358B4B587EE23F
+:151E4500A4A9E2B3B8A291C80FBCCAE64201E554E8D13CED0713
+:151E5A00FF15E847FFD5CFFCD7BE8523AA67B4EB5E466F5CEA4B
+:151E6F00A49571CE7B38A67B301E13F082484C972B13AC7D8726
+:151E8400C801D584B1ECDA30C8F6D1BE85361EFAC411194EB76D
+:151E99006F6E6830687DCBED46FA7D1A3FFDFD997CEC7DE0FFB5
+:151EAE003DBAE0875E831F80CB601F91173B4D07DF4FDF9375AB
+:151EC3002604ED1501BE8B47F83C5FC44F203B2E1F9E4F922D53
+:151ED80047B9EBD5ACFD0B186FC88303FA1AD73F43C9237B8A4E
+:151EED00F95C4DFF25C07BC351D5E5116B64C0D3C5421DCF3873
+:151F0200CFC2F2B64D3C470A7DCC3231BFAB653A12D7FF14CA3C
+:151F17002CDE19DF507FD7261607B33C8E7A51EF7BB67AD5C94A
+:151F2C00D7F7C04F47786213DBD3D0314F00DB82F349E4A3F77A
+:151F4100A598EA1DE5FC20DCA2DB2AF09DCC4377C446779BA8EC
+:151F5600F7B6ADDE7AA08BF5466DF5DA44BD9FE4E16F7DF2845B
+:151F6B008EE3EA1CE3F16F74F403B5789E06F83BCC746798AD4C
+:151F800015F7FE1DF04A0619AFB5365EDB05EEF7F3F03A6EE3A1
+:151F95006197A8F78F7978BD60ABB75BD4FBD7AFE035BA39C326
+:151FAA006BFDD7F05A6FE33529700F6C9ECE6B7C7386875F8943
+:151FBF007A439BAFD19F9B33FD59738DFEACB1D1BD68F5671EA7
+:151FD400BA2336BA13567F5E836E77038EAFB328031AB6D3C5F7
+:151FE900F68AFB3F106EE9FC7EA87F84C4D23ADFE0E43A7FD59C
+:151FFE00C1755E6FC8D00E3B39ED8E86AFA00D741B045D8B5E7B
+:1520130083AD9D0D02474F7E1C14C7ACB516ADE6DA3C3C4FF630
+:152028005AC6DEB1F18CE771E5E394F92FBCB7473E4D31CE73E4
+:15203D00157569FB206E94C197C9F5EF6A0B8C33DA41DCA3B7F4
+:15205200D9A01F40BBF2D91EEB3B9F23A61ED18755D417BEF704
+:15206700774085F9EA1E9C9FAE67B832BEE12EC055007E216DFF
+:15207C005743C3AA6B438CD954664F2D7FA1A33D8D317B2AD7C5
+:1520910001FF60471DB6F7ACF61663BE4EBFEA5848B514E65951
+:1520A600E0BAEF659EFBC57D5382D3A7C29E86FB786C07BE8FF4
+:1520BB00B765909DE791C3BA46D12F4586C12FBDADB9A2670C99
+:1520D000F44D79FDC98F32F33B9ECFD0A9F970BB1AD712C4FFBC
+:1520E500A9546A5EEE5C8FAF47B2FD70B3640AECAB1BC656B88C
+:1520FA00D4A0A18E36B4C5AE426E93252BCEC6F32DBF4BA57368
+:15210F003F9529969F39DF9E372855F27B70545B3DBC8028B73B
+:152124001EFA84C30A9EA11AA188E7093C4355E6C0DBD558FC4D
+:15213900EB284E3462F92DA141ED96D0DBA8336615D6C19800DF
+:15214E00F3A901EEDB1A3333FFDFD618ECCF6218A3101F4D680E
+:15216300FCFBB7D1A77B0B3C1DDA7BBFCDF0E49F6271928DA775
+:152178008451454EAEF1298E80451BEF02BA83D18C52ACEFBB81
+:15218D00F50B33FDBEEE8B349DE492936B0BD2795F309F147980
+:1521A200F8C8D7FC5B3127A28F9AEC2C5729CB19BA17C5217371
+:1521B7005CBC6E9CA2FCC12D9AB2BC17FAFA347CD36B4ACA69E7
+:1521CC00B3D1164F5E7C84906714E79AB30E316FCD43E7218C26
+:1521E1005DF2E2F69B4EF94DC07DD620B4CF94A2EF9A986F8196
+:1521F600EB9F69DF8DF38A42317E11EFB7C57D14A20CFFE7B9AD
+:15220B00EF83E596A21E4DCC40BE127482E78C5397E0CF65E79A
+:1522200003FA1BF945BCBE6082E51BB1BC0FF9A4897CC83F5F73
+:152235008EBC73DD13EBF1150319FD463C138F703CFE298E9FB9
+:15224A00E34D30F9CCFF791775C01C0EEFAFE823C32A9EA3C3D2
+:15225F0098977F9330B14ED63800FCC707ECF7C9F07623FEC926
+:152274004778FB389D78161D6B4D00EB323926B91C6FD9DA6987
+:15228900E23D116C4E63A7993E5F1B32382F1586CCE4526BF06A
+:15229E009C8480E112F9A00C1E1EA44F2DC5D8F518CB47B4F334
+:1522B300E07D6D39B5F71DD431257908F7E6A9A5272CC606DB7A
+:1522C8007A9874AAECDEA8B0C1E4827C61B904EDA515BA819379
+:1522DD00E4C2A24E8D1EB8533B7F21E526E3036CFEEDC53E92E8
+:1522F2004F6992F22EF545F8F9796BEEB2A070FAFCA9A36E8876
+:15230700CDE7B2E650E327CD7B6F740E15FE4BD3D229CC23ECDB
+:15231C0077F2F969B64E711D425E4A845C589F04A33C7F4D1EC1
+:1523310030713E8DFE0CF5097580C94AE773E9A35FA6E6D8F47E
+:15234600384B9F59DE7800C63E9D30E6F9028DB87EE2519697DC
+:15235B007F88FE14E6E9CF287706AFC098BC2BE965E7B07B1FA4
+:15237000E5F4E38FE2251B0798DE610E97809B083F27E6B551F3
+:152385005137915357C04D841F15E3DDC2FF9EF8E67CCE37023B
+:15239A006E22BC55E03F27EA2673EA0AB889F0B502FF3281BF77
+:1523AF0053D4C57AF8BED8666FBA6D65F88E67DBF04C9B0BF426
+:1523C400A8107C95A40C98282F49DEAFA19F092883788676EE70
+:1523D900DC505F9504F3D3F91D135A9132642ED22FADF5557FB6
+:1523EE00A64991D3E66B51E71617CFFB9C373332638D14993002
+:152403002D9D9D09B62D7915D7D13263E0FBE02325766E884EE9
+:152418001B8F627CA5ED0AE2900F0E68BD85832AF23677ABDF7C
+:15242D00B4C633D301B409101B3892EFB2FD46FEFD88993B9E8E
+:1524420079FE24D08431631F272FDDDCA925FE89AFF3C9C2A6AC
+:152457007DDD38D98BB9A2D618E91960392BB2F0A7385696619D
+:15246C006E00C41A8FB1B5F7CCDED306C51910E7A9FEF0FD47F0
+:15248100DB78AA2DF83FB0D139F1445819A04E4F17936729E02E
+:15249600ABFF2CE377E9E7397E1778C13A44EEC03326265B170D
+:1524AB00298E373A0FC6B43ED015796D8CDFCF00B8703D05C6F8
+:1524C000E33C764700F8662CC77BC33087D8F2BFB8BFAA46965F
+:1524D500D2420FDEBDD7618CFF3A435BCE1387B8224097AF4988
+:1524EA0098D85F7DE0F7E5B303C6154113FB378B2694BB7268E4
+:1524FF0066B7BFA4B1C1463F6CA3AFE4A12FD9E82FCE43DF7F80
+:1525140083F431BEE8AC0C18162FC8C3D825CEC362A0954B5FF5
+:152529003E3BC46829910103EF4082B9B789B18FA4F03B8F16DC
+:15253E004706E861887BD0A74DD9E31C5C7393FD66679DA1E108
+:152553005D60532FFAABD87E5FC96D2BFC75CD41E54F2A2AB7BB
+:152568002A0FFEF1A60794CDDFDBD9DEF2EC4E6553CBB696A616
+:15257D009D2D41A5FAF107B7EF686968DDFEDDEFB4B63F5EBE5C
+:15259200F2EE9501A5E4999DAD4FEFD8B1FDB9C79B9E6F0AADAF
+:1525A7005A55B9EAEE157F7497F2D4D3DB5A762A81CAB255156B
+:1525BC006595ABE125581E0896DFADF8D3FB5B16BDB2B2E696EB
+:1525D100B6EDED654F6E7FB6ADACA9B9A96DE70B65CF363DFDA7
+:1525E6005CD9CE1D4F96B5EC6C2B7BEA85B2A6A66D0CB66DFB24
+:1525FB00F6B6953B6F5B758F72A7D2D2FC74BBD2DCF254D3775B
+:15261000B7B52B4FB6363DF79D16A564577BCBAEF61548E5FA76
+:15262500F037EF78FAF9961D654F41B31E6F6A7FF6F1E61DCF95
+:15263A00AF7CF2B67B2AAE853F8D9E9D679AE30B37EE67F71A4D
+:15264F00F88DB9BE8AC612A59CAEC4B35CD6398D88B406EFD1B8
+:1526640038143AD25640FA8DC6DFA7D2E7341A958DE627BF703B
+:15267900AC91E48735BF526DF861EC7D0CB6C40BCF37D176E26F
+:15268E003E09DD6FB47E9172FF74A9146C50866829942D22C3C6
+:1526A3005521A587B2F2B0B4370030B4376B6D30AC27830F04B5
+:1526B8009C66B752B4CEF2090BC036CE203C96C0FF5701F73E78
+:1526CD00F9E5368C13CFFD756A16F2653FD7603FD310BDC2F71F
+:1526E200CABA58FD12A31FEA13F9B2E9856FCE027D3B4FB48D99
+:1526F700B0B3D32B1D642EF2877C78959314DB3003E6A998E3FD
+:15270C0046C657E03EA9D7577DA7866DEF02393A7F52A9E1B5D5
+:152721006A7EC0C5D7AB0E1961E58746CD08D08678CA0BDF5BC8
+:1527360074F01C97B77A850630D34EF3A1548AD1EC62FD72CC9E
+:15274B003C9D4ACDC1FBC4F8BA738F7113F805C439F916AEE733
+:152760000700768CF1E5047F7908F90AEFC775B9F956BF5967CC
+:152775006CA2BFC3BEBBCCFA0EF7571405C69DDC957029F7980F
+:15278A00CEEAE3D0DF07186E1C8738068B94D7CCD22F1D6BED4A
+:15279F00EBBF28734BDEF67EA87927E5FAAAF2D27752B3ECFD49
+:1527B40081FCC8C08FD7D65F6D1197114DFEEC556C93121D4848
+:1527C900CCC13BE7E8622314ED4890E2B6464F926CF14697828B
+:1527DE003F3EC7EEC3F3F96AA1ED0F18787F29CE1DDF9470BE3A
+:1527F300A41A18D348EF3FA3E11D51F5B8CF12C6B3DB611693D4
+:15280800F6EA41D50C55AEDBA414ACFBD079D1F0856E67EB8DA0
+:15281D00462235EB23B12F128AF6D09BB2F4A01EF3BFE763BD01
+:1528320066A8773ABDAF1231F07EEE40B49A12F915EA666D83D9
+:15284700793795F63ADCEB35DC4767F617BEDFE3C0F58C8BFC2C
+:15285C00CEAAE601889DCB204E78925E29242ECFD880F69872A0
+:152871007370AE72F3AA3796CE0FBEBAECD6E018C091DED4894A
+:15288600D4AC658A23C8F8917B0C5C7376C1383C0F7A9FDE0F44
+:15289B001B3FC6FDF1F831F32F1696A86C7D186CE4D142116F97
+:1528B000290EB6761A075C565DAB5E3BD4395837A0E29DA7C812
+:1528C5002FAEE7E6D2C2F18BDFB7C2F7EF39385FB8A679E10D71
+:1528DA001C3F7BE9E2A241CDFBDF47282A4A6D14CFB45F64E72D
+:1528EF00E7CCD0EB6A1FE05D5454AADD8577C28E47D55272D174
+:15290400E80C9D68C33D2E05FBA96E8566429FA0AE38770F1B8D
+:15291900B8B71FC4F58EF08F791F88B18B31B4C4E6610AF015FA
+:15292E006A64FC877F6C8E4DA5DCA70B783BD7439FAECDD37719
+:15294300308F9E9FDBA6DEA914CBE9403CEDE2FB4530C69CE3B3
+:152958003A8C83FD4601CC4B42C923863196725BDFCE80B1811F
+:15296D0036F0AC903BEEA7A22CC22007AB4E3F94B1F7F1970D63
+:15298200CCA762FB8F50A7F48D6CD95F0539E6E315C7ED82680B
+:1529970007F57FCE653E359CFD1D933FEA33B305FB0D4BD6BDC7
+:1529AC0020EBDAE88079F5B8F0F9F676A34E8A761FFD0D6F378E
+:1529C100F63DE2DB05F84A599F458C5D2C6E133AFA2BAEA378CF
+:1529D6007EDA51F780867B86DFB2F450F40997670657E8379C5D
+:1529EB00E736D00DE083E9C6A2A2B59AEF178EA0A523783CBFC9
+:152A00008DEFB30A1D39A3625CB78C9C5987FAE1AD4C30BD3021
+:152A1500F565AAEBFD214D025BBB4CE8035F0FC8EEFF1190B788
+:152A2A004F3EC6E29DD9A01F5EB0BF577E2DE46E8D0BDB37B8AA
+:152A3F00679D4FEE2B717C437D94FD343A30AE7A218EAC17FDA3
+:152A54003EC3A623EDA023AD1F701D71809C25A123E597B99C53
+:152A690059EE01F6FFF14C3F223E4FB84D6BFDB7B672B0676627
+:152A7E0049B4CEF447FFA7BDEF8F8EE2B8F3AC1EF54833D22015
+:152A93005AC3CC20B0905A443892A33833625024598481B05E2D
+:152AA8009910769673B2DD92B0C7B1BD109B64B93B6E1FEF85E7
+:152ABD00C42318890137B8E913589615337224AF7C0B398507C9
+:152AD200397C4F24E2C239B021DC2C211C9764F1388FD8BAACE3
+:152AE700CF283E62E3C466EEFBA9EE1E8D846CECDDBDF7EE8F21
+:152AFC009B7EF3BAEBF7B7BEDF6F557DABEA5BDF5A69AC4D3DC0
+:152B110064AC4ED519D4075AF65353DC2E34E6371EB9869F2B10
+:152B26000ACEB2EE45FDB55654BA5B0DDE817DA2914EC869824F
+:152B3B007B50C5FAD98475AE1E32F7037C3D69949F0DE4EB5DA3
+:152B5000B09131CED8A10527686E3ACCD7BEA454B701BDC4D514
+:152B6500A9935CDE8BA786C193F324B9DB582D9F34747AFBF5F8
+:152B7A0011F5B46C86B3EA61EDEA07C8832E36E6FE07F17305BB
+:152B8F005F77BC253C273C2C7C56F00937D8AFD97F633F64DFE9
+:152BA40063C3EC5966B0DD6C07DBCEFE9A6D618FB38DEC61F62A
+:152BB90020EB600AFB125BCFFE8C7D917D81B5B17BD96AB68A61
+:152BCE00AD642BD8729A41B6B066D6C41AD93216664B59030BD8
+:152BE300B120FB0CBB9B7D9AD5D3F3297617ABA3A7967D929E0F
+:152BF8003BD9127A6AE8F9043D8BE9A9A647A6A78A3F95F42CC7
+:152C0D00E24F057FEEE0CF42EB59C09F72EB999F7B02D6E3CFE1
+:152C22003DBEBC675EDEE39DF694CD78A45B9EB9B73CA5B33E15
+:152C3700733EF0F17CE85372DBA7F8233FEEDB3E2E6E3B338E52
+:152C4C00F3B1C15A4DE6674B4856A96CEA8C32F928FC6AC84FD6
+:152C6100B7BE6BE93B6D7DD7D3B724D037FA96CA5A8A1FD17442
+:152C7600D6A6A569522C098A26C98B8F4A31FAA7E89F597C1419
+:152C8B00E598B6496BB90D3659B6FCDC969F407E31CBAFD8F202
+:152CA00073905FCAF22BB1FC0AC80FF95199FC2CAAC4A2412BC1
+:152CB5002FE8F906AD3CA0D71BB4D2428F374869A202F66465D7
+:152CCA002D880E49BE4BA3D975BF93E469F845B99D8790EE4D6B
+:152CDF00090DAE3375D4FEABFB5D679A558E9F0F091BFD90302C
+:152CF4006E7F87C24A298CFC0DF88B679B555E76BACEB07167CF
+:152D0900E31CF5485B30EA2F719AE8329F770775CCF318EAC994
+:152D1E00D3043B815FE0507298F1E3A7AC70EA135BE5C5866EE7
+:152D3300E593FA2FA67F94E245D15F4A8B0DD105BBA3B3BFA3AF
+:152D4800C2549C74DEB7E498FAD62D7F8EFFF1EC5CE82698E770
+:152D5D00F7656DD2B4C76AEA6D705D4699EB74145ADFFC9CB4E6
+:152D720083788DD11C34D8447DA783251C4D4611ECA1469AD5B9
+:152D8700C9EA66BE1E37F943E732E0046EAC898E533A0F6B8218
+:152D9C004EB4514FB8A08E5673C90D4611BB8B60BFCB700622DC
+:152DB1009DE959FC24C1F463F25B06FADADA996B124159E77EE0
+:152DC600E0EF029CCB151A44A29914EF6A10313F96AAF537A019
+:152DDB003B2F85A8C826BD9EAF3BC17E6C33D7C9803BEA28A727
+:152DF00032EA74942F9E2957A5785D186F57BCAE01EF28EA9AFF
+:152E05000E72191A71391FC3DE6230A8C5799B68D2225CD728D1
+:152E1A0002FF58766E1BC58972FB879095CD33CF312DC2F5F20E
+:152E2F0037F133A82CB8C53C03CDB659F714242D5D08ACEBCEA1
+:152E440025A012345F49E83A4B28D8DF81FBAD15E23296DEA113
+:152E59007B64B1814577C2AE9C8BA565DDB4FF6AEA31D5670A9B
+:152E6E001A789E941F5F47C277B4574F2E882B42AA17DF464EDC
+:152E8300CF23FA5FFB50BF0AF9A02ECA670DA62768DE242EFB76
+:152E9800EE83DFA5FC5FEE833FFCEAC9AF7F865F39F9F5CCF080
+:152EAD00DBBA82E0925EEBFBFA83AF874860D63CF2157D1B3BFC
+:152EC200A355AC7C5D6DAF7A25F46CD3045FB75814BF44F57999
+:152ED7004A5BB4F2BCEABA4FE336CACB2DBBF3934F88CBC4D496
+:152EEC0090EEA0F15196BFDFC7F4EB862B69DAE3E476D4C70FBC
+:152F01003406307FA2F298FEB7C61DC2EB2AF051CC5E098D3CFA
+:152F1600F86623C142B4BD42F09DD7056D9FFAEC86D7B43B22A6
+:152F2B005486B4CF20DCC56FAE581D72ED46F93B47D94821CDB2
+:152F4000E3BE33EA725DA3F05DFDAEB6D7C9FFE97ED3FD3796FB
+:152F5500FB3FF59FC966FD7DF679C0E8CFFB128E333AF0B68DCF
+:152F6A008D6BF11F14367CEB5B850DD07FE84F3FAB2CBCF0B4B0
+:152F7F005270FEA2BA865D0A2D1C3FA9422748F8C900F4F5CCDC
+:152F94003A120D70EED52FFD07F5FA43578C85C2CB14E792E1D4
+:152FA9008A1F69C4BC15F94F3E745E77A50A1B469F285CC6F5AF
+:152FBE00645FE26BDE47138E14D1FAB88E7AFCE1507738708AB3
+:152FD300F2977EA3A12E12608EFE68D485FD1069D75189C3FEC9
+:152FE800E3A3AF513B2C96F726B3244F16C9DDF1FD714D11D6BF
+:152FFD0069D44E7FAEC36E288D00EC2917F610B4E464F5EF0D02
+:153012008ABBE74DB97E5994E26F8FAD6EE0EBAF05E6594D31D5
+:15302700F56DEA279D0DB1F1C30D4C3FAE4F82FFB0BE47E1EB7B
+:15303C00C95F4C1D211EEAE6F6486A2D7B41BCBDE97BB9DDF0EB
+:153051005ADED648AE4DEDA5B616D744A2ABC4B430DE2EA635C4
+:15306600E08D3D3257EA00EC1D733BA2E5A9842EB85EC63C93F4
+:15307B00DB0B052FA28DB8CE216CC88842EE4E258CBBE21AF7B7
+:153090007FEF27541EB921134ED2D4D013131BCA486E445B997A
+:1530A500AC1E321606529D63379DCB0897C6E9F7B22E89E06916
+:1530BA00A5322EA16D05FB349DFAE156CA97A5068C8BBC7E4946
+:1530CF009DDAA91424C0DEEED6C24C3F053BDAA3E82F7747CE85
+:1530E4002BBF7EB4B0A18F752B7DF1034AE91271595F64AF3217
+:1530F9003FD1ABEEDFA929FB9B2F73D952CA1CEC5BB3A4F2B3DC
+:15310E007C8F28181FC5DA6BD9A204F600F58A14AF8FB986EDC6
+:1531230032EDF5E3BB4D24193AF39A2EB411EFEAA78DFDE39A1A
+:1531380082F541576A80D2ED028EF95E5E79E6A0519179CEA8B5
+:15314D0025BC54503D338524165038DA67ADBC8BDB6EE1794811
+:153162009A7191E6A1E5F2533AB7B1B841D3289D8EBE418C1DA2
+:15317700D3C3991F50DC5DD4467A699C1B509167762EF4786000
+:15318C004B80BAFC7327D583F15EC5BBB257756843AA6B7CB87A
+:1531A10011E7E87DA78E98F6BC4F33B6003A00D029A39FB8AE2A
+:1531B60047A57AF73BD6ED53134579776CA4B1D69FD4BEF10F46
+:1531CB00E28F0F0A9C667C0FC4977981EB03F25B806033057EB2
+:1531E0002CC1F5B78013733F24C9F7438CF15EA53CD3D317CC90
+:1531F500BC9092566ABC0F2958ABA9A2AC191E17F6B807B4DAA8
+:15320A00C881469CF3F277116FD1BCB63C0EF7712360B985B33F
+:15321F0003AA392E1C307C194F03FCB0B6EFCAED8FF4F13D009A
+:15323400077097EED56B389DCCF3C9021BD1A0476D9E4B1EE5BE
+:153249007DE9AF282F612DFAB214A7AF903CA20616751B4F47B0
+:15325E00CC73AC584B17BEA071BBBA12A7B3693B5AE23CFF9D4E
+:15327300A35761D392EA8DF56831437D5BFA3AA7435946C4B92C
+:15328800E57980A79CDB5F4A76DA773430FD4903F6C100A7902A
+:15329D00F986569B9917CAEFFBD1E783BEBCDFCFF5F9D4FF6FB5
+:1532B2002A6CF765DEC638C06DAB48991EE3B1F7B37EDFFBE0EC
+:1532C700779306C0B12FB34F7768BD6A598BB9E7EFA07E03F7AF
+:1532DC00DE7858EF520FF1D042D61B02FDCE7D798A77CF59B649
+:1532F1007F2F73DDAF894ECE7B4437B845BEDF11E7FB58A037C4
+:15330600E8CB6D5EA19D133D67D292E3DEA2C3ED70FB9169045F
+:15331B001AE4E3DEA6C7C7A04131A7419FB5CF33458BDBD220BD
+:153330006FECB5F11F1B87BDB4D2603E2DBCB14F86605F8345EF
+:15334500BB466F3CF1C946295644E36C972E901FFAB1CDDF28C2
+:15335A006C38F147FB3CA0D99F9413ADBC8B3443A03A8176A8A8
+:15336F00A3B9E7639681F290B77DBE7D4A7E32E9B1208F16DB67
+:15338400393C49D3EE0E85635F51CAEC3304EBBE1BB8953CFDD8
+:15339900E3AD79E9B77E39A7E7CBF583FBD830D7DBDC049A7748
+:1533AE000D2B1ED9C9FBE92D5C0F50D31744CCBE29D7EEA8CE2A
+:1533C300C2268CCBF4BEEF3CC701B553DFB63F98F5CDCCB8F661
+:1533D8000BF6DCCB03C9CE052413C82C7534C8C68F4658E668BC
+:1533ED00F93F66DDF41E25F728F98F3659E93F28BE32313DBE77
+:15340200CB8A8F717EE0EDBC32B1DF48F391FCB4A80F9F7B51F9
+:153417007D6E92FCD28AF573792CFED499934A2189F6A2DC9BBD
+:15342C00849D9A62F97412679ECDFDA0717EE651928FD13C2606
+:15344100A17B324B1A4E1FC8BA81878167B3EE9979E6C3C6F7CB
+:1534560031296DABFC8C7EFD558D64259C19485A7CF8EF0DCEEC
+:15346B00BB416EB37E723FC98F0D241F7D1CDE9C7CB5574F3836
+:15348000481E1D11DB275FD58C84A3CF6895F71B6FDF44FB88C7
+:153495005B73952EE32A6422A23FF657652A231F2737AE4CC7E0
+:1534AA0027F6772DF9D6D47FD60774C8D202B52FD491DC46F8DA
+:1534BF00DF91FC188C72BBDF763E727A2A1FDFEFB3AE0A9CED31
+:1534D4000994C32EB47E91CADC06BBF91968DC46344FA6562FE1
+:1534E9003C15527F10AF0B753735F13EAB88D1DC287D9F562434
+:1534FE003C4C7DCB9A19B2A6639928DFA7CB99681FEC1CBB5C2A
+:15351300D514DE66C9998B1B8B694EE3A8547859B08B0879DDE3
+:15352800D915524B23A17031953D54754F634546A631B3562FB7
+:15353D00CF0475C1D3A2767F3BA439C769FE986E326C7BD4D0FD
+:1535520001E132BFD445F2674D4874134CD1AF8C32FDA17E5706
+:1535670012FB88FF9ABF332B1CCB6ED5BF29B7F4306AAD7E2D55
+:15357C0048FD5A9302B9DF2B0B413E9709364DD94049BF30BA8B
+:15359100607C405D60DD59E0A3711C63314BBF64940957D55FDC
+:1535A60057977E96914CCED88426B1DF34FA846B6A69B5B301F8
+:1535BB0072648464F60A6158D593DD4A39E618D46FA29F14DD49
+:1535D000AF73FB344CDA31FA00FBEF212ECBC1760CB91792FC9F
+:1535E5008DB7FD5F2817B5E7DC83669A8FF23F9CCD06642ABF7B
+:1535FA00D1CA9FDBF69F257FFB1F4F4DE57DA7FCA73C0DEC3F98
+:15360F0049B566F92E8259B6E250BF1E58287F32945F1FF84BF5
+:15362400DC864D11CDA95ED259E685A33E6AE7C998735945E6DD
+:15363900A08EFE14BC8A7DF5329219DA16F42E8D903C8631F194
+:15364E004B4E9DCB608DCF68215BEEDAF47EF603E52ECFBBD91D
+:153663000F94BB9A6691BB1CE78EA890BD206FD9F2574EEE8AAB
+:153678000C37E28CBB6F25CEBABFAE35DD9CDE1FF23E9368DB97
+:15368D0056FD686331D7C99ED03CEC64A3EFD4FF22584E6A8B1D
+:1536A200334B7278704D38DB6D3CC006D104E1E9DECC1C93BEB6
+:1536B700382B6386EB62F2B87A37A1A1006E2B0C7E080B4085CD
+:1536CC006486DF091AAF3E088F243B841D6DD7D41BEF645DC0D6
+:1536E1002770566FE1B0E63D138715B3E010694EBFF32F8B4708
+:1536F6003FE457C2E3A2C089CECBC063747F5FECF733AE95B49A
+:15370B007065F3FFEDDCF2CCF4D103FA1AD6A7DD4FF39DEFFF58
+:15372000AC77A92C9F1AA5514CF30A24A74A970C9FD54E45E8FD
+:153735005348BF34B6B1CB5A99F0867AC7CA6BEA216A87341F91
+:15374A006FC01AB85466A898FBB2F401CD357E90DE07B53B5395
+:15375F00628397E6805EF6BAEA3D754515F8BD15A3DCBE14B79D
+:15377400AFF4438C1B716D60FC07E13B28DE1D142F30DE13B21D
+:153789006D3F4994066D54A638B63F6C3FC11FEDAB32736F4889
+:15379E009A28E434E736A7CE5EE16D0AF76905327786A6EC5777
+:1537B3005DE1FD43F0D5D2507EF9F2AB81696EE9D5CA696EE8E9
+:1537C8005BCDA7B2E673B8BAF3E03A990757771E5C272DB8FEA1
+:1537DD0084E072E6C175320FAE25A19976B582164FE7E0CAF8FC
+:1537F200A7C3955934CD0D9EA9A0B1B531833E90DF11C9F8DC00
+:153807004700EF8C111DC61AE711CFCC3F457C263F63283769B9
+:15381C009E9E7E5E0F73FB865D7DF192443BDCDBF85D55C7FA7E
+:15383100A00F5570866091BBFA1DE7BECD797284AFDD3CAF0766
+:15384600E5471BCDB897B5EB2B8A96791E7885E629BFD45DF29A
+:15385B00F7FAEBE5FF61C47F37939F7A463DD07F4CADD7CC6F34
+:15387000CCF37B46275698F98C52BE3E2BCF1B2B3636DECDFE86
+:15388500A681CFD3F3E2222DF2C84F778EE2EC78F2921294FFC4
+:15389A006229FC21BFD134EECFF0FD0C64B8684F0AF323A42D33
+:1538AF00C6990FE25D9C27ACD8FF3CE763F4AFA2957FE9B4EFA6
+:1538C400F92187F9DD572CCF0DD15B7770BD769DE7EF786E8FEB
+:1538D900CA714CF3761FC9FF46E405458A24D472C23F3FDF2953
+:1538EE009936FF6FF72799272924D7ABD85767D24E2E1749FE99
+:15390300B862CB455C4642D8C7908F6CD9C8929366958F201BEC
+:1539180041460A731B30391947D3CF99328E598ECCCAE5DF195D
+:15392D00F320EB92DCE492771B23D7B2AEE294962C26B8C3914D
+:153942004483BD76514E7216E224AF8166DD3AAB15DBB75F33B8
+:15395700E5AEF11FF3701DE141C20FD2B3D4555D583041E3BBD3
+:15396C00466D21A915A612712F9523A58EF5A19F04DF214FCC1D
+:15398100DFC17FB5F2ABFDE6BC7E265FF7F2FE653EC9C5DE53D5
+:153996007B492619D3C08315046BEB1FF3E55FAB8E54B7792958
+:1539AB00677BE6CDACBF96E4DD6DBF356556BE1741F09D79D3A5
+:1539C0004C531630CF6FB9ACFDDFD1F9CC4D7329F7C83CD38DB4
+:1539D500F3A3DB1DCC7D86FEE6FCB0D6B28F62EA383953423B4C
+:1539EA00F33569F6D962CF4F894F4E35ABAB683EE6EC1A547BD6
+:1539FF00D3834AA5EC6C59287FFA73580BE669A43A35771E3916
+:153A14005A4D7C24F488B16ADD29615D59E879E3505D985DEFCD
+:153A2900D2B6FC149750CD9C0F4D2F7F55BC6EE93FAB9C5BF459
+:153A3E00C7829D35F35989242D56A12F0E5D0DD843724A7FCB73
+:153A5300F57BEF4ABDDF0ADDBFC03743E8C3B84E1FB761111D23
+:153A680036D6523CBB3CF8B7927BEACE013E472F96024D9DA766
+:153A7D0003B0AF54DBF95E00F936987A72E5ACC4131BA4B1734E
+:153A9200897AA72C34632FD513AB366E90BFF8CD2EDDD4AB1599
+:153AA7007B2E5AF32BA7B443FD1585C13E682FE250BDB0372D1B
+:153ABC0043F78FFCC4D8A031067FFA7EA390B92F5BE98AC87D98
+:153AD10095FCF11E221AC35ED698C33CB3DB0B9AFB26614796E4
+:153AE600158DC02EE7354DBAF08C02BDEC729C75938E692ECFE7
+:153AFB0011751DD1769E2CB6176506349CF3A8A07E349041FC40
+:153B1000BD3B30C60996EDC0BB52AD4B258F797608B675E6F2B3
+:153B25003C87FB711F93CB8CE32B4F894B85915BD3B26811D7DC
+:153B3A00D7D6096F61BF797616FA59B09B0A3D948C1F77C324AA
+:153B4F0072F73188F2756333D14D24BC78A82DC7E89BCDB21F0F
+:153B64001B8CB9B458E6C747C52B5DEA7B3E3BDF8A5CBE492BCA
+:153B7900DF98653B7B0FF52D63458C9F1966E9B8C1F793A4B0E2
+:153B8E00265436758A995AC395811FD12AD3C4CB2FCF4C92FB54
+:153BA3004CA79449519EB14EACB3EF59B049E17D5BE596CE8A23
+:153BB8000C6C185CEE14E5AF196301933F6A284D6D26DD5F4F2A
+:153BCD00E9C3945FEB3CE60E527A13B63FE9036CB0B3BC0A7361
+:153BE20028669F5B8E744632EF187B16449508CF53E98C929B8F
+:153BF700E21F5D4DEF18E55D4873DAFB33878C48E6E9BEB68CD8
+:153C0C00DE1FA537F6A8A3F43D4EE10A9595F65AB6C4598AE365
+:153C210000F64B61F3B13393321EA17C63049B0FFC4AB8AAA7B5
+:153C3600B78BDEB0D12B5378057DAFA63C7CF4AEA1F9DA26FA1D
+:153C4B009E9807DE265C915F1BF989D4D75C24BF60C0A4476CD9
+:153C6000864D72E795B84A7C57E295C5E50579F412008F3C64E5
+:153C7500604D35280F515F3764CC464FF33C53588B3A58C9DDD8
+:153C8A00A987C2683785CDB216E53AAD757A39BF43A8CB30756C
+:153C9F001D841E3B8F149559D537C8CFB5080BE2EA947DA00895
+:153CB400C50B6B8CF2EB015E7CB5B9BE6DE2EF3106D6A9B8178D
+:153CC90061B1DCBE946DFD5DB7F21BC6EC7CD8D62E2DF8736B0E
+:153CDE00FF6BEBA0B685BE7B6467BB2B074FB3BE10E310C27EAD
+:153CF30063EAC9D7DBFD13F9457F0E5DD365CB5D046B8C997058
+:153D0800DBF4E6772A5AE9026CF972B6B599E76FEB8B230DE54F
+:153D1D005552CA5A7918F22A0ADC8A2FB46F9F30759E439A7424
+:153D3200501D4772753C7701753CA2AE8149D46F0EEB9317007C
+:153D4700306C5F99FB57F2CF281AFD23F48FD23F46FF2DF48FD5
+:153D5C00D35FA77F8AFEA3F41FFF9919976DED36EB6A9EF9E018
+:153D71006E7D867B74863B3DC33D39C32DBD36DD1D9CE18ECEF0
+:153D8600706F99E1D667B8475FCB3B9342F86913A6DBB78880A5
+:153D9B0076BED19C5DB049AA97BFEBB85ACACF365BF9548E74A6
+:153DB000626D684D6C43D87F7258AFBD083A4DE14D21F726FA49
+:153DC5006FA37F92FE7DF41FA1FF09FA9FA1FF65FA4FD0FFC613
+:153DDA0045332E3B36CCF196B2E94F6EE04DCF73036FF13C37CD
+:153DEF00F0B625CF0DBCC5F2DCC05B34CF0DBC45F2DCC05B3084
+:153E0400CF0DBCC9F9E5BF36657F93974F6E7F73CA006F433FFD
+:153E19005A7C62C33DB67D048EB3D9EC7F107E3D2C0FBF33E3C5
+:153E2E00FC33C3D1F78C91FC42B2947E7A0E734357B658165B92
+:153E4300FA04E676D137F5332D1E1A238BE5459FBD48E36205B5
+:153E58007D838EA2B44BDD5C467DD65C56F218AB6989CC35FBFF
+:153E6D00B407E83B3CD7ECCB2A59CD725BFF49869D72DCFB4582
+:153E820079006793A5347E53FA8952C80E2F4ED9E92019F152A8
+:153E970055178DCB872123E8932B9CCB4F531CB96CF6F1C91EC9
+:153EAC00E7B0378FF1689299363AEC781E2A77A7659780E49DE9
+:153EC100E597292F3797F7A7F7631504EF7609E321E48BDDFA81
+:153ED6009E18CEA5BDA8EF881CE6F5777FC6D92EC9753AFA7C2A
+:153EEB009C112A5B79006BB9DA9639D89F1AD499FEA2EE178621
+:153F0000D516D6CDE377BB87156141138DAD83460DD54D243929
+:153F150097DB268FEED52ECEC538A6774A65499EC7988764486F
+:153F2A004AE3925FD4451A8BDDA558571EA4BEFC450378AE91FA
+:153F3F004CF903B0A63DD3E5BB9139ACE434E5979A8371A6469B
+:153F54004FF0B1A9968F4D380F2B2C08727B0A976F66DDC5F1B1
+:153F6900AE1667B45695E56AFD8D15C2F2CD94E6CC5C5326B23D
+:153F7E00F1E0B5740FA7F477653D4EBC29A57772BB7EC85FBA96
+:153F9300F02905E78C00B33732B854245945A43C2729CF769297
+:153FA800A9708E49E4BAD87C1C862D490379A0BD8BB161C323AE
+:153FBD009BF7BA66B91EFF8B9A9FFA7C51FE9E81F8B8FB976419
+:153FD200B00D1305243FE3CEC7BCFBA71294C7DFC588F762B02A
+:153FE700B561DA75FFCF6CB0C5436E8C33C065A498B9DF269E84
+:153FFC007D8B75B5D8B09B3819E17D13F2E0677ED892161BEE59
+:15401100EB0437F45D890E25B02303DE100907176F64DDF6BD19
+:1540260005F9750AE4D5C91E6F502FAEB740FD1DE0728ED39C6C
+:15403B00233AA6F13A4883FD34F7A07AEF9EB2731CEBD27ADD53
+:15405000CCBD8660B5D37C2ED2DD029D8B9F1CF9E5618F5C6F8D
+:15406500F8B9AEB24A6D76B561C32ECA87F9BC0DDF18E3250BE4
+:15407A00476B2D386D3D4E0B977EEC15D7539837262EF7154C5C
+:15408F00B50DC95DA39AF7EBF6F1F28C5343CB59380C1D8CAC5D
+:1540A400B96796CD7EEB5498FC123AEE46294B3FA9965DB84765
+:1540B900914EAD542B65472BD7C94EEF864E94DB9D3F6E5A70DC
+:1540CE0036B0257C5C1E7531F715AAE32F88F7D8D6E7795E88FB
+:1540E300EF425F1F1EE2F7AEF0F34BADF5FCDC0EC9E1FA77E3C0
+:1540F800832D38D303FC3E8D7B0B49A6B5F0EEF5669C2DF321EE
+:15410D00AF674EEAD71F7C69F92FC88D3A8B99E1D9F98D706485
+:15412200F29BC93F9ECC71FD40C6D91E681DE6E5A19D99F4946F
+:15413700394EEDFE5A6C5D82B51884EB95E38361BBBE51613A5F
+:15414C005F42CFE6664C6801DC36DEDE1C1FE27D8027467D8299
+:1541610055CE2F288F8F5AA7CA8C7379B0783A2FB6BD9D75AFA9
+:15417600263A7A62797591BFA7439F3257179AC398F5AFCDD556
+:15418B00057088AD9FE675E1788A0D7E209E9C528D6AAD9364C6
+:1541A000515FD7A9FD5C671573A30187D54F100EA84F77E7EEE2
+:1541B5004C487769E7FE68EB0FD7F33A411FDC4EFB08A503BD44
+:1541CA00FB284D7E3FB3ED7D935F3DF2615D8E0D695F8F0C8732
+:1541DF0050367851DA3E669E8B4C0FD37CA11BFB9D567D877409
+:1541F400BBAD88992386E41E56CBA8CF3D48732B4F06FD8A6685
+:15420900D5FFC454FD892FE6B79EE4F59F4F74B3E89B1B438A6B
+:15421E00D1DFF1F6A0C1DE7FAE3DDC840D9FF0B169EDE1B7E3CD
+:15423300C7B85D1FE0ACE121D35E01CA798EED6D215E36DB49B7
+:154248006BB7C9C7046B4F1C67F0F5CE03B8B3217A7206BD7508
+:15425D0093DE5563CB0D727B624379743DA623CD145D87AC7ADB
+:154272002579BDD0E794B5EEE5755A98129783F76D5AE6F89743
+:15428700FCC414EE0A49E83DD4FECC7B42CC7AFC35F1A7CD8B26
+:15429C005E36D6524AE503961B04CB42E2BD6345D379EFDCFF00
+:1542B10026BE93F3E08B117CB13CF8641B3EB35FE2F86CEDE6C9
+:1542C600F0E5F70BF1EBA6CE03DE62CCBC07588F24B694E68D22
+:1542DB0039F1776D5ECACD5DBD2CDCCB710B5B2C7D93D9921C44
+:1542F000EF916CF32B9C55785EBC478A99F36F6FE4B0FA32A58C
+:15430500F1578BED6517FA1487C0CAE6212C7D4C972EA414DCF3
+:15431A00F17094609530E610FF6C06AFBA20776A5A2070A253C4
+:15432F00261929FE646FFB0EF827DF411FBEC375EE24D7DB9887
+:15434400A8DABB8CA5C7742328B4239FCAC45ED89CF3CDC7FD16
+:1543590068E49FC07AB5B4D710293FECCD7337952BF23DC1BDA2
+:15436E00064F87B2F4BDD07F9E87BB95BBCC343BE234A7C7BA03
+:15438300886BAD063D0E3FD7A1B3E6F412BF4F604CDFD9A6299D
+:1543980005EBAEABF16CD6DBFB794D419D454A539938B201F0C4
+:1543AD0063EC042C80CD4F75692218FC174E28F36354D7B3DF2C
+:1543C20056BD4CA33E3CA57C1FE38295A77876402519D88B7446
+:1543D70076DD7B28FFE4BEE3FC1E66F00B8F1B3DB6C3B5FB1DAF
+:1543EC003503D8A84EA80BBB3EA0812EC003D34F6AC06723D44E
+:154401005C28DCC6C5E51C1ECCB27278C07A6DDAC443E08762E3
+:154416004B721D742486B52EF2EF457B219CC00F6B1B18834D1B
+:15442B00784F120E8E28C8BF60DDEFD5ED1C0F479432AA1F70F9
+:1544400020E15C15D5DB853B83A82CD84C0E52F99581894EDBE9
+:154455001EB2699F27AEE9C4837B162495B7D884B628C8FEB2C2
+:15446A0046163F97B3D7F917DC1E32B79BD4C6D70896F49B67EE
+:15447F005B933C5D24B30B7756B8E98D7D6023F6EE149FE33C0E
+:1544940023D6AAC0A3F5984B439736D8A4E937B89E663F8DB948
+:1544A900E3F15A67BBE00AAB623CA40E10AC55CA28EEB9D475D6
+:1544BE00F9402B9F9F521C89FA0E8FFCC51697FCB51649FED26B
+:1544D30072312A63BC1EBF4AF230DE9D984B455FE43695FDCD24
+:1544E800B261E60F9971703C4861F594BE96D2D7507AF855C8F3
+:1544FD005F6AB1F3207E6AF150B88BC2AFAEF81ACFCFC3E766D2
+:15451200BFDB61C7D9827208265FF3A499B76EC20D1C85D136AC
+:1545270008CE7AF9D556B1396864281CEB05AD349F1692215583
+:15453C008A630FD88C8FFC8807FC22E58378FC7C020EA4C7CC33
+:15455100FB80D0869B9835BF06CF4B435639239DB02952CE6D45
+:154566004F987E9833C18FEB1B9A67316E992F098249CF9C3FD4
+:15457B00E55F61CF7FB85E2A95CFEF384B195BDEBD75CE659ECD
+:154590007D1DD11186FE07772CF8ADFEC69FFE8EEABBF01D0521
+:1545A500F2AAAFF59879AF1085F930F767C731BE6BAFFCB680E3
+:1545BA00558C1F6FDC42E39EC79A67983AB0A69C833B095CF13E
+:1545CF00234BE793ECE943BEC111138E5C3F99E4F0F9228930CA
+:1545E400B76F426EE4591E196AACC8CB333777A13E97A7B7FA20
+:1545F900CFDF5AFC8C7A606E47B079AFF2359D9AA934F9E76932
+:15460E00F9FDE74F69FF384BBAC333D211BFEBB3AD27453287BE
+:15462300B4FF394BFACD1F21FDB559D2856F0BEF216D6296747F
+:15463800EFB1DB9737F9FE8CFD27ACEFE4F31FD6FCDF9DBEBE22
+:15464D0031335CFAC38787C76F17FE7F3BFF19E19E7CFEFFB008
+:15466200F581FF1FFE4F0A97F2F0EF9DD92FFD3F03BFC51FD198
+:154677008F971EED6B9E287359ECCBDC46536D67B7147B641541
+:15468C0035454FD9A3AA46E1B82BDA73EF6E15ED14E3A4A76CC6
+:1546A100933A67F56E55961FD7E6DCDBA5FAD6698A47A8567DC5
+:1546B600A5D56A82E416AB4D6FB84163B35456AB22BF7F6A5E9C
+:1546CB0087EED314EC110BEB30778969D0A813E582F6AF539F69
+:1546E000ECF754ABC5EBEAD5E7EEAB53AAD6D52901CF236A774F
+:1546F500C1A714DCC5B8488CD973151FE28BC22754BEF6B0BABF
+:15470A008BF71DE843AA4A130AF413ED702F0FAF539D65712E7A
+:15471F00DF553D1057F9FE1264E459605865C15042301C22180D
+:15473400FC79E5A3DC4A8201F178BE947F7ED937AC7C27F3F2CE
+:15474900CD665FC97A60EB20C4DAD7331640DEF18298067C644E
+:15475E009999D7DD8CCD9B4FB8F15099EEB57584A36A6591A745
+:154773005EADF88CD8EE645D1D557F25B61F9A9BE034C0BA4825
+:1547880090E0C01EDC5DDC76DA14EC5EAE07833DB8F813BC6EA9
+:15479D0004D3390BA631D4DD73BF5AB56FBD2A94DAB0656FDA01
+:1547B200B0B5106CE59E6AEE1F2701D1A697BBB45EF5709876A1
+:1547C7002B8B4A13EAFE9D7B72BC003CFF219BB56060BC9EB026
+:1547DC000966EE374F3E8172D3161CB81FEC0C7DCFD54C18CA91
+:1547F100F6D5A865541EEAD66E9D4D81D226C74BD1145EAA8851
+:15480600774AD6B6AAFE75610ECFAE82DD4A31E1A5C493500B35
+:15481B0059A2638FC59B8B79DDA7CA9DA0B2740A2B237C11DDC4
+:15483000BD360CA07D55DBFD0A64A1AA75F72B33E94F8DA8AC8E
+:1548450098EF1B4CB5A7AA33033C9E70BE5EF53F105345F6B844
+:15485A003A87E23E27B080D3FA36E8BB641DC149FC52521A56D0
+:15486F00B51F352B87CE362B25E7D7AA7E8DFCF6AD56B5CFAF8A
+:1548840021DE5FA38007FC84D3AAB5262EC498D02EB34D9CB7E4
+:15489900494EF3398587544FAC0077EBF8DCD8ABA96CEB9C2374
+:1548AE003CAC7AC455AA67259577EF63AA67F51A756FC12A05F2
+:1548C300FA1EE2CAA9F84501F31C188F9F0899F15FFEAAEA3924
+:1548D800DDACEEDD19529C34EE96FDE811A5EA3305EDB0BDEDB4
+:1548ED008A873ADCE3CD1D07610F4D0BA955E71F528573CDAA2E
+:15490200467105D7142DB6A28D07F8B9B10DB005E4EAA274A731
+:154917009A3BF85CAF89B51796053B8ACA9A3AAA92EB955DD473
+:15492C0037C8E4DF43EFBF449FF3F71B554F4FB30AB95A961F63
+:15494100866CE7F224BEAA0A6B1F5779D8CBCD141E52F712CEDB
+:15495600407FF038E624C5FF5168AF3A4F38D4BA54FFF9AFAA3B
+:15496B0025E776731ED84F3C3087685F1CD13AF2FB25D01DB469
+:15498000E33AF5164DC193F7122CED809FF08CBB49D7D177A2D7
+:1549950060B33287CA01FCDD3BCDEF5537B37EF44BB61B7169FF
+:1549AA006CD83090C51A8EC9DB064DAD803FF47B34E79A67F3A6
+:1549BF00D5368AC3CF47100C9B67E80797FDA842A93A574EB8AA
+:1549D4002D5767E3A337D9141FFD9A997C041A14131F053C9B2E
+:1549E900395E9EB378097CE4D7C86F062FED8EFFF9C6E2C026AC
+:1549FE00A24FAC73377B78E39C40B453F03CA6DAEE92C8C31BD2
+:154A130041A7395DEB3BD8D9F60EFB5D75CDC1EF702A8EAFEF25
+:154A280028196FEF10DC44F34F546B4EA2C34DA23DF07737A785
+:154A3D0085A3BD44DB4174788CE8B087F7E9F9B428B0689130FA
+:154A5200EF8EF209AE4F50D9D10D67385DC037D10D6333E84242
+:154A670034F13E60D185FA5DEFFA3CBADC994797B7B8CEEF5418
+:154A7C00DBDE49EDBA378F4EDCFE38F100FCB75BB4BA803BFF2F
+:154A91002CDA6CFA98F4B894478FBFB3E88136EDB768710B1D40
+:154AA6008836F9B4288A7C65636164FD46B68DB57BC6BFD23197
+:154ABB0047DAD451C4FECDC6E2F8BFEDF08C3FCCDD557FC5DBED
+:154AD000ABD7C3C81D799CE35DDC5EAD39A8EFBD5914D58A6DA5
+:154AE500BC533B2DF1ECA4FEEF5FA9256B9F9C86F7E2D9F09E3E
+:154AFA005C0C9AE7E13DC6F16ED7371FFFD4E77BEFB5F08F35C1
+:154B0F0011B4131BFFDE3CFC3BF8DA8689DF5E0BBFA5E8A7280A
+:154B2400AC92E65AF96DC04EFF261F174C9A211DE8A658743081
+:154B3900711E9E06CBB708CF9E94D0FE32BD13D4F714521EB8D2
+:154B4E00FFFDD0171F51ABD6B52A52C152A5A8E01E053CECA121
+:154B6300BE879D6DEE38F4530A3BBF56F1167C5E71177CC10C75
+:154B7800135675B0FBD674EC8A39DA01D762E084CA261E292BCC
+:154B8D00941D7C8C843FECB9D87314940FBBA705F6D86D8D1FA2
+:154BA20088075D7FC0F55A36EBA338B9B6FF32B57D8C5D9768CE
+:154BB7003A89F512C80734D6F94E5BF1BF4FDF55D744BCBD27B6
+:154BCC00785E4975BF63E8117EC65230EF62A69125EB8D0C3DF1
+:154BE1006A308DEB88C1DF37B5DE5AFC7F00E871AED038A00037
+:014BF60000BE
+:00000001FF
diff --git a/drivers/atm/suni.c b/drivers/atm/suni.c
new file mode 100644
index 0000000..06817de
--- /dev/null
+++ b/drivers/atm/suni.c
@@ -0,0 +1,311 @@
+/* drivers/atm/suni.c - PMC PM5346 SUNI (PHY) driver */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/capability.h>
+#include <linux/atm_suni.h>
+#include <asm/system.h>
+#include <asm/param.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#include "suni.h"
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct suni_priv {
+	struct k_sonet_stats sonet_stats; /* link diagnostics */
+	int loop_mode;			/* loopback mode */
+	struct atm_dev *dev;		/* device back-pointer */
+	struct suni_priv *next;		/* next SUNI */
+};
+
+
+#define PRIV(dev) ((struct suni_priv *) dev->phy_data)
+
+#define PUT(val,reg) dev->ops->phy_put(dev,val,SUNI_##reg)
+#define GET(reg) dev->ops->phy_get(dev,SUNI_##reg)
+#define REG_CHANGE(mask,shift,value,reg) \
+  PUT((GET(reg) & ~(mask)) | ((value) << (shift)),reg)
+
+
+static struct timer_list poll_timer;
+static struct suni_priv *sunis = NULL;
+static DEFINE_SPINLOCK(sunis_lock);
+
+
+#define ADD_LIMITED(s,v) \
+    atomic_add((v),&stats->s); \
+    if (atomic_read(&stats->s) < 0) atomic_set(&stats->s,INT_MAX);
+
+
+static void suni_hz(unsigned long from_timer)
+{
+	struct suni_priv *walk;
+	struct atm_dev *dev;
+	struct k_sonet_stats *stats;
+
+	for (walk = sunis; walk; walk = walk->next) {
+		dev = walk->dev;
+		stats = &walk->sonet_stats;
+		PUT(0,MRI); /* latch counters */
+		udelay(1);
+		ADD_LIMITED(section_bip,(GET(RSOP_SBL) & 0xff) |
+		    ((GET(RSOP_SBM) & 0xff) << 8));
+		ADD_LIMITED(line_bip,(GET(RLOP_LBL) & 0xff) |
+		    ((GET(RLOP_LB) & 0xff) << 8) |
+		    ((GET(RLOP_LBM) & 0xf) << 16));
+		ADD_LIMITED(path_bip,(GET(RPOP_PBL) & 0xff) |
+		    ((GET(RPOP_PBM) & 0xff) << 8));
+		ADD_LIMITED(line_febe,(GET(RLOP_LFL) & 0xff) |
+		    ((GET(RLOP_LF) & 0xff) << 8) |
+		    ((GET(RLOP_LFM) & 0xf) << 16));
+		ADD_LIMITED(path_febe,(GET(RPOP_PFL) & 0xff) |
+		    ((GET(RPOP_PFM) & 0xff) << 8));
+		ADD_LIMITED(corr_hcs,GET(RACP_CHEC) & 0xff);
+		ADD_LIMITED(uncorr_hcs,GET(RACP_UHEC) & 0xff);
+		ADD_LIMITED(rx_cells,(GET(RACP_RCCL) & 0xff) |
+		    ((GET(RACP_RCC) & 0xff) << 8) |
+		    ((GET(RACP_RCCM) & 7) << 16));
+		ADD_LIMITED(tx_cells,(GET(TACP_TCCL) & 0xff) |
+		    ((GET(TACP_TCC) & 0xff) << 8) |
+		    ((GET(TACP_TCCM) & 7) << 16));
+	}
+	if (from_timer) mod_timer(&poll_timer,jiffies+HZ);
+}
+
+
+#undef ADD_LIMITED
+
+
+static int fetch_stats(struct atm_dev *dev,struct sonet_stats __user *arg,int zero)
+{
+	struct sonet_stats tmp;
+	int error = 0;
+
+	sonet_copy_stats(&PRIV(dev)->sonet_stats,&tmp);
+	if (arg) error = copy_to_user(arg,&tmp,sizeof(tmp));
+	if (zero && !error) sonet_subtract_stats(&PRIV(dev)->sonet_stats,&tmp);
+	return error ? -EFAULT : 0;
+}
+
+
+#define HANDLE_FLAG(flag,reg,bit) \
+  if (todo & flag) { \
+    if (set) PUT(GET(reg) | bit,reg); \
+    else PUT(GET(reg) & ~bit,reg); \
+    todo &= ~flag; \
+  }
+
+
+static int change_diag(struct atm_dev *dev,void __user *arg,int set)
+{
+	int todo;
+
+	if (get_user(todo,(int __user *)arg)) return -EFAULT;
+	HANDLE_FLAG(SONET_INS_SBIP,TSOP_DIAG,SUNI_TSOP_DIAG_DBIP8);
+	HANDLE_FLAG(SONET_INS_LBIP,TLOP_DIAG,SUNI_TLOP_DIAG_DBIP);
+	HANDLE_FLAG(SONET_INS_PBIP,TPOP_CD,SUNI_TPOP_DIAG_DB3);
+	HANDLE_FLAG(SONET_INS_FRAME,RSOP_CIE,SUNI_RSOP_CIE_FOOF);
+	HANDLE_FLAG(SONET_INS_LAIS,TSOP_CTRL,SUNI_TSOP_CTRL_LAIS);
+	HANDLE_FLAG(SONET_INS_PAIS,TPOP_CD,SUNI_TPOP_DIAG_PAIS);
+	HANDLE_FLAG(SONET_INS_LOS,TSOP_DIAG,SUNI_TSOP_DIAG_DLOS);
+	HANDLE_FLAG(SONET_INS_HCS,TACP_CS,SUNI_TACP_CS_DHCS);
+	return put_user(todo,(int __user *)arg) ? -EFAULT : 0;
+}
+
+
+#undef HANDLE_FLAG
+
+
+static int get_diag(struct atm_dev *dev,void __user *arg)
+{
+	int set;
+
+	set = 0;
+	if (GET(TSOP_DIAG) & SUNI_TSOP_DIAG_DBIP8) set |= SONET_INS_SBIP;
+	if (GET(TLOP_DIAG) & SUNI_TLOP_DIAG_DBIP) set |= SONET_INS_LBIP;
+	if (GET(TPOP_CD) & SUNI_TPOP_DIAG_DB3) set |= SONET_INS_PBIP;
+	/* SONET_INS_FRAME is one-shot only */
+	if (GET(TSOP_CTRL) & SUNI_TSOP_CTRL_LAIS) set |= SONET_INS_LAIS;
+	if (GET(TPOP_CD) & SUNI_TPOP_DIAG_PAIS) set |= SONET_INS_PAIS;
+	if (GET(TSOP_DIAG) & SUNI_TSOP_DIAG_DLOS) set |= SONET_INS_LOS;
+	if (GET(TACP_CS) & SUNI_TACP_CS_DHCS) set |= SONET_INS_HCS;
+	return put_user(set,(int __user *)arg) ? -EFAULT : 0;
+}
+
+
+static int set_loopback(struct atm_dev *dev,int mode)
+{
+	unsigned char control;
+
+	control = GET(MCT) & ~(SUNI_MCT_DLE | SUNI_MCT_LLE);
+	switch (mode) {
+		case ATM_LM_NONE:
+			break;
+		case ATM_LM_LOC_PHY:
+			control |= SUNI_MCT_DLE;
+			break;
+		case ATM_LM_RMT_PHY:
+			control |= SUNI_MCT_LLE;
+			break;
+		default:
+			return -EINVAL;
+	}
+	PUT(control,MCT);
+	PRIV(dev)->loop_mode = mode;
+	return 0;
+}
+
+
+static int suni_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	switch (cmd) {
+		case SONET_GETSTATZ:
+		case SONET_GETSTAT:
+			return fetch_stats(dev, arg, cmd == SONET_GETSTATZ);
+		case SONET_SETDIAG:
+			return change_diag(dev,arg,1);
+		case SONET_CLRDIAG:
+			return change_diag(dev,arg,0);
+		case SONET_GETDIAG:
+			return get_diag(dev,arg);
+		case SONET_SETFRAMING:
+			if (arg != SONET_FRAME_SONET) return -EINVAL;
+			return 0;
+		case SONET_GETFRAMING:
+			return put_user(SONET_FRAME_SONET,(int __user *)arg) ?
+			    -EFAULT : 0;
+		case SONET_GETFRSENSE:
+			return -EINVAL;
+		case ATM_SETLOOP:
+			return set_loopback(dev,(int)(unsigned long)arg);
+		case ATM_GETLOOP:
+			return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ?
+			    -EFAULT : 0;
+		case ATM_QUERYLOOP:
+			return put_user(ATM_LM_LOC_PHY | ATM_LM_RMT_PHY,
+			    (int __user *) arg) ? -EFAULT : 0;
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+
+static void poll_los(struct atm_dev *dev)
+{
+	dev->signal = GET(RSOP_SIS) & SUNI_RSOP_SIS_LOSV ? ATM_PHY_SIG_LOST :
+	  ATM_PHY_SIG_FOUND;
+}
+
+
+static void suni_int(struct atm_dev *dev)
+{
+	poll_los(dev);
+	printk(KERN_NOTICE "%s(itf %d): signal %s\n",dev->type,dev->number,
+	    dev->signal == ATM_PHY_SIG_LOST ?  "lost" : "detected again");
+}
+
+
+static int suni_start(struct atm_dev *dev)
+{
+	unsigned long flags;
+	int first;
+
+	if (!(dev->phy_data = kmalloc(sizeof(struct suni_priv),GFP_KERNEL)))
+		return -ENOMEM;
+
+	PRIV(dev)->dev = dev;
+	spin_lock_irqsave(&sunis_lock,flags);
+	first = !sunis;
+	PRIV(dev)->next = sunis;
+	sunis = PRIV(dev);
+	spin_unlock_irqrestore(&sunis_lock,flags);
+	memset(&PRIV(dev)->sonet_stats,0,sizeof(struct k_sonet_stats));
+	PUT(GET(RSOP_CIE) | SUNI_RSOP_CIE_LOSE,RSOP_CIE);
+		/* interrupt on loss of signal */
+	poll_los(dev); /* ... and clear SUNI interrupts */
+	if (dev->signal == ATM_PHY_SIG_LOST)
+		printk(KERN_WARNING "%s(itf %d): no signal\n",dev->type,
+		    dev->number);
+	PRIV(dev)->loop_mode = ATM_LM_NONE;
+	suni_hz(0); /* clear SUNI counters */
+	(void) fetch_stats(dev,NULL,1); /* clear kernel counters */
+	if (first) {
+		init_timer(&poll_timer);
+		poll_timer.expires = jiffies+HZ;
+		poll_timer.function = suni_hz;
+		poll_timer.data = 1;
+#if 0
+printk(KERN_DEBUG "[u] p=0x%lx,n=0x%lx\n",(unsigned long) poll_timer.list.prev,
+    (unsigned long) poll_timer.list.next);
+#endif
+		add_timer(&poll_timer);
+	}
+	return 0;
+}
+
+
+static int suni_stop(struct atm_dev *dev)
+{
+	struct suni_priv **walk;
+	unsigned long flags;
+
+	/* let SAR driver worry about stopping interrupts */
+	spin_lock_irqsave(&sunis_lock,flags);
+	for (walk = &sunis; *walk != PRIV(dev);
+	    walk = &PRIV((*walk)->dev)->next);
+	*walk = PRIV((*walk)->dev)->next;
+	if (!sunis) del_timer_sync(&poll_timer);
+	spin_unlock_irqrestore(&sunis_lock,flags);
+	kfree(PRIV(dev));
+
+	return 0;
+}
+
+
+static const struct atmphy_ops suni_ops = {
+	.start		= suni_start,
+	.ioctl		= suni_ioctl,
+	.interrupt	= suni_int,
+	.stop		= suni_stop,
+};
+
+
+int suni_init(struct atm_dev *dev)
+{
+	unsigned char mri;
+
+	mri = GET(MRI); /* reset SUNI */
+	PUT(mri | SUNI_MRI_RESET,MRI);
+	PUT(mri,MRI);
+	PUT((GET(MT) & SUNI_MT_DS27_53),MT); /* disable all tests */
+	REG_CHANGE(SUNI_TPOP_APM_S,SUNI_TPOP_APM_S_SHIFT,SUNI_TPOP_S_SONET,
+	    TPOP_APM); /* use SONET */
+	REG_CHANGE(SUNI_TACP_IUCHP_CLP,0,SUNI_TACP_IUCHP_CLP,
+	    TACP_IUCHP); /* idle cells */
+	PUT(SUNI_IDLE_PATTERN,TACP_IUCPOP);
+	dev->phy = &suni_ops;
+	return 0;
+}
+
+EXPORT_SYMBOL(suni_init);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/atm/suni.h b/drivers/atm/suni.h
new file mode 100644
index 0000000..d14c835
--- /dev/null
+++ b/drivers/atm/suni.h
@@ -0,0 +1,211 @@
+/* drivers/atm/suni.h - PMC PM5346 SUNI (PHY) declarations */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#ifndef DRIVER_ATM_SUNI_H
+#define DRIVER_ATM_SUNI_H
+
+#include <linux/atmdev.h>
+#include <linux/atmioc.h>
+
+
+/* SUNI registers */
+
+#define SUNI_MRI		0x00	/* Master Reset and Identity / Load
+					   Meter */
+#define SUNI_MC			0x01	/* Master Configuration */
+#define SUNI_MIS		0x02	/* Master Interrupt Status */
+			  /* no 0x03 */
+#define SUNI_MCM		0x04	/* Master Clock Monitor */
+#define SUNI_MCT		0x05	/* Master Control */
+#define SUNI_CSCS		0x06	/* Clock Synthesis Control and Status */
+#define SUNI_CRCS		0x07	/* Clock Recovery Control and Status */
+			     /* 0x08-0x0F reserved */
+#define SUNI_RSOP_CIE		0x10	/* RSOP Control/Interrupt Enable */
+#define SUNI_RSOP_SIS		0x11	/* RSOP Status/Interrupt Status */
+#define SUNI_RSOP_SBL		0x12	/* RSOP Section BIP-8 LSB */
+#define SUNI_RSOP_SBM		0x13	/* RSOP Section BIP-8 MSB */
+#define SUNI_TSOP_CTRL		0x14	/* TSOP Control */
+#define SUNI_TSOP_DIAG		0x15	/* TSOP Diagnostic */
+			     /* 0x16-0x17 reserved */
+#define SUNI_RLOP_CS		0x18	/* RLOP Control/Status */
+#define SUNI_RLOP_IES		0x19	/* RLOP Interrupt Enable/Status */
+#define SUNI_RLOP_LBL		0x1A	/* RLOP Line BIP-8/24 LSB */
+#define SUNI_RLOP_LB		0x1B	/* RLOP Line BIP-8/24 */
+#define SUNI_RLOP_LBM		0x1C	/* RLOP Line BIP-8/24 MSB */
+#define SUNI_RLOP_LFL		0x1D	/* RLOP Line FEBE LSB */
+#define SUNI_RLOP_LF 		0x1E	/* RLOP Line FEBE */
+#define SUNI_RLOP_LFM		0x1F	/* RLOP Line FEBE MSB */
+#define SUNI_TLOP_CTRL		0x20	/* TLOP Control */
+#define SUNI_TLOP_DIAG		0x21	/* TLOP Diagnostic */
+			     /* 0x22-0x2F reserved */
+#define SUNI_RPOP_SC		0x30	/* RPOP Status/Control */
+#define SUNI_RPOP_IS		0x31	/* RPOP Interrupt Status */
+			     /* 0x32 reserved */
+#define SUNI_RPOP_IE		0x33	/* RPOP Interrupt Enable */
+			     /* 0x34-0x36 reserved */
+#define SUNI_RPOP_PSL		0x37	/* RPOP Path Signal Label */
+#define SUNI_RPOP_PBL		0x38	/* RPOP Path BIP-8 LSB */
+#define SUNI_RPOP_PBM		0x39	/* RPOP Path BIP-8 MSB */
+#define SUNI_RPOP_PFL		0x3A	/* RPOP Path FEBE LSB */
+#define SUNI_RPOP_PFM		0x3B	/* RPOP Path FEBE MSB */
+			     /* 0x3C reserved */
+#define SUNI_RPOP_PBC		0x3D	/* RPOP Path BIP-8 Configuration */
+			     /* 0x3E-0x3F reserved */
+#define SUNI_TPOP_CD		0x40	/* TPOP Control/Diagnostic */
+#define SUNI_TPOP_PC		0x41	/* TPOP Pointer Control */
+			     /* 0x42-0x44 reserved */
+#define SUNI_TPOP_APL		0x45	/* TPOP Arbitrary Pointer LSB */
+#define SUNI_TPOP_APM		0x46	/* TPOP Arbitrary Pointer MSB */
+			     /* 0x47 reserved */
+#define SUNI_TPOP_PSL		0x48	/* TPOP Path Signal Label */
+#define SUNI_TPOP_PS		0x49	/* TPOP Path Status */
+			     /* 0x4A-0x4F reserved */
+#define SUNI_RACP_CS		0x50	/* RACP Control/Status */
+#define SUNI_RACP_IES		0x51	/* RACP Interrupt Enable/Status */
+#define SUNI_RACP_MHP		0x52	/* RACP Match Header Pattern */
+#define SUNI_RACP_MHM		0x53	/* RACP Match Header Mask */
+#define SUNI_RACP_CHEC		0x54	/* RACP Correctable HCS Error Count */
+#define SUNI_RACP_UHEC		0x55	/* RACP Uncorrectable HCS Err Count */
+#define SUNI_RACP_RCCL		0x56	/* RACP Receive Cell Counter LSB */
+#define SUNI_RACP_RCC		0x57	/* RACP Receive Cell Counter */
+#define SUNI_RACP_RCCM		0x58	/* RACP Receive Cell Counter MSB */
+#define SUNI_RACP_CFG		0x59	/* RACP Configuration */
+			     /* 0x5A-0x5F reserved */
+#define SUNI_TACP_CS		0x60	/* TACP Control/Status */
+#define SUNI_TACP_IUCHP		0x61	/* TACP Idle/Unassigned Cell Hdr Pat */
+#define SUNI_TACP_IUCPOP	0x62	/* TACP Idle/Unassigned Cell Payload
+					   Octet Pattern */
+#define SUNI_TACP_FIFO		0x63	/* TACP FIFO Configuration */
+#define SUNI_TACP_TCCL		0x64	/* TACP Transmit Cell Counter LSB */
+#define SUNI_TACP_TCC		0x65	/* TACP Transmit Cell Counter */
+#define SUNI_TACP_TCCM		0x66	/* TACP Transmit Cell Counter MSB */
+#define SUNI_TACP_CFG		0x67	/* TACP Configuration */
+			     /* 0x68-0x7F reserved */
+#define	SUNI_MT			0x80	/* Master Test */
+			     /* 0x81-0xFF reserved */
+
+/* SUNI register values */
+
+
+/* MRI is reg 0 */
+#define SUNI_MRI_ID		0x0f	/* R, SUNI revision number */
+#define SUNI_MRI_ID_SHIFT 	0
+#define SUNI_MRI_TYPE		0x70	/* R, SUNI type (lite is 011) */
+#define SUNI_MRI_TYPE_SHIFT 	4
+#define SUNI_MRI_RESET		0x80	/* RW, reset & power down chip
+					   0: normal operation
+					   1: reset & low power */
+/* MCT is reg 5 */
+#define SUNI_MCT_LOOPT		0x01	/* RW, timing source, 0: from
+					   TRCLK+/- */
+#define SUNI_MCT_DLE		0x02	/* RW, diagnostic loopback */
+#define SUNI_MCT_LLE		0x04	/* RW, line loopback */
+#define SUNI_MCT_FIXPTR		0x20	/* RW, disable transmit payload pointer
+					   adjustments
+					   0: payload ptr controlled by TPOP
+					      ptr control reg
+					   1: payload pointer fixed at 522 */
+#define SUNI_MCT_LCDV		0x40	/* R, loss of cell delineation */
+#define SUNI_MCT_LCDE		0x80	/* RW, loss of cell delineation
+					   interrupt (1: on) */
+/* RSOP_CIE is reg 0x10 */
+#define SUNI_RSOP_CIE_OOFE	0x01	/* RW, enable interrupt on frame alarm
+					   state change */
+#define SUNI_RSOP_CIE_LOFE	0x02	/* RW, enable interrupt on loss of
+					   frame state change */
+#define SUNI_RSOP_CIE_LOSE	0x04	/* RW, enable interrupt on loss of
+					   signal state change */
+#define SUNI_RSOP_CIE_BIPEE	0x08	/* RW, enable interrupt on section
+					   BIP-8 error (B1) */
+#define SUNI_RSOP_CIE_FOOF	0x20	/* W, force RSOP out of frame at next
+					   boundary */
+#define SUNI_RSOP_CIE_DDS	0x40	/* RW, disable scrambling */
+
+/* RSOP_SIS is reg 0x11 */
+#define SUNI_RSOP_SIS_OOFV	0x01	/* R, out of frame */
+#define SUNI_RSOP_SIS_LOFV	0x02	/* R, loss of frame */
+#define SUNI_RSOP_SIS_LOSV	0x04	/* R, loss of signal */
+#define SUNI_RSOP_SIS_OOFI	0x08	/* R, out of frame interrupt */
+#define SUNI_RSOP_SIS_LOFI	0x10	/* R, loss of frame interrupt */
+#define SUNI_RSOP_SIS_LOSI	0x20	/* R, loss of signal interrupt */
+#define SUNI_RSOP_SIS_BIPEI	0x40	/* R, section BIP-8 interrupt */
+
+/* TSOP_CTRL is reg 0x14 */
+#define SUNI_TSOP_CTRL_LAIS	0x01	/* insert alarm indication signal */
+#define SUNI_TSOP_CTRL_DS	0x40	/* disable scrambling */
+
+/* TSOP_DIAG is reg 0x15 */
+#define SUNI_TSOP_DIAG_DFP	0x01	/* insert single bit error cont. */
+#define SUNI_TSOP_DIAG_DBIP8	0x02	/* insert section BIP err (cont) */
+#define SUNI_TSOP_DIAG_DLOS	0x04	/* set line to zero (loss of signal) */
+
+/* TLOP_DIAG is reg 0x21 */
+#define SUNI_TLOP_DIAG_DBIP	0x01	/* insert line BIP err (continuously) */
+
+/* TPOP_DIAG is reg 0x40 */
+#define SUNI_TPOP_DIAG_PAIS	0x01	/* insert STS path alarm ind (cont) */
+#define SUNI_TPOP_DIAG_DB3	0x02	/* insert path BIP err (continuously) */
+
+/* TPOP_APM is reg 0x46 */
+#define SUNI_TPOP_APM_APTR	0x03	/* RW, arbitrary pointer, upper 2
+					   bits */
+#define SUNI_TPOP_APM_APTR_SHIFT 0
+#define SUNI_TPOP_APM_S		0x0c	/* RW, "unused" bits of payload
+					   pointer */
+#define SUNI_TPOP_APM_S_SHIFT	2
+#define SUNI_TPOP_APM_NDF	0xf0	 /* RW, NDF bits */
+#define SUNI_TPOP_APM_NDF_SHIFT	4
+
+#define SUNI_TPOP_S_SONET	0	/* set S bits to 00 */
+#define SUNI_TPOP_S_SDH		2	/* set S bits to 10 */
+
+/* RACP_IES is reg 0x51 */
+#define SUNI_RACP_IES_FOVRI	0x02	/* R, FIFO overrun */
+#define SUNI_RACP_IES_UHCSI	0x04	/* R, uncorrectable HCS error */
+#define SUNI_RACP_IES_CHCSI	0x08	/* R, correctable HCS error */
+#define SUNI_RACP_IES_OOCDI	0x10	/* R, change of cell delineation
+					   state */
+#define SUNI_RACP_IES_FIFOE	0x20	/* RW, enable FIFO overrun interrupt */
+#define SUNI_RACP_IES_HCSE	0x40	/* RW, enable HCS error interrupt */
+#define SUNI_RACP_IES_OOCDE	0x80	/* RW, enable cell delineation state
+					   change interrupt */
+
+/* TACP_CS is reg 0x60 */
+#define SUNI_TACP_CS_FIFORST	0x01	/* RW, reset transmit FIFO (sticky) */
+#define SUNI_TACP_CS_DSCR	0x02	/* RW, disable payload scrambling */
+#define SUNI_TACP_CS_HCAADD	0x04	/* RW, add coset polynomial to HCS */
+#define SUNI_TACP_CS_DHCS	0x10	/* RW, insert HCS errors */
+#define SUNI_TACP_CS_FOVRI	0x20	/* R, FIFO overrun */
+#define SUNI_TACP_CS_TSOCI	0x40	/* R, TSOC input high */
+#define SUNI_TACP_CS_FIFOE	0x80	/* RW, enable FIFO overrun interrupt */
+
+/* TACP_IUCHP is reg 0x61 */
+#define SUNI_TACP_IUCHP_CLP	0x01	/* RW, 8th bit of 4th octet of i/u
+					   pattern */
+#define SUNI_TACP_IUCHP_PTI	0x0e	/* RW, 5th-7th bits of 4th octet of i/u
+					   pattern */
+#define SUNI_TACP_IUCHP_PTI_SHIFT 1
+#define SUNI_TACP_IUCHP_GFC	0xf0	/* RW, 1st-4th bits of 1st octet of i/u
+					   pattern */
+#define SUNI_TACP_IUCHP_GFC_SHIFT 4
+
+/* MT is reg 0x80 */
+#define SUNI_MT_HIZIO		0x01	/* RW, all but data bus & MP interface
+					   tri-state */
+#define SUNI_MT_HIZDATA		0x02	/* W, also tri-state data bus */
+#define SUNI_MT_IOTST		0x04	/* RW, enable test mode */
+#define SUNI_MT_DBCTRL		0x08	/* W, control data bus by CSB pin */
+#define SUNI_MT_PMCTST		0x10	/* W, PMC test mode */
+#define SUNI_MT_DS27_53		0x80	/* RW, select between 8- or 16- bit */
+
+
+#define SUNI_IDLE_PATTERN       0x6a    /* idle pattern */
+
+
+#ifdef __KERNEL__
+int suni_init(struct atm_dev *dev);
+#endif
+
+#endif
diff --git a/drivers/atm/tonga.h b/drivers/atm/tonga.h
new file mode 100644
index 0000000..672da96
--- /dev/null
+++ b/drivers/atm/tonga.h
@@ -0,0 +1,20 @@
+/* drivers/atm/tonga.h - Efficient Networks Tonga (PCI bridge) declarations */
+ 
+/* Written 1995 by Werner Almesberger, EPFL LRC */
+ 
+
+#ifndef DRIVER_ATM_TONGA_H
+#define DRIVER_ATM_TONGA_H
+
+#define PCI_TONGA_CTRL	0x60	/* control register */
+
+#define END_SWAP_DMA	0x80	/* endian swap on DMA */
+#define END_SWAP_BYTE	0x40	/* endian swap on slave byte accesses */
+#define END_SWAP_WORD	0x20	/* endian swap on slave word accesses */
+#define SEPROM_MAGIC	0x0c	/* obscure required pattern (ASIC only) */
+#define SEPROM_DATA	0x02	/* serial EEPROM data (ASIC only) */
+#define SEPROM_CLK	0x01	/* serial EEPROM clock (ASIC only) */
+
+#define SEPROM_ESI_BASE	64	/* start of ESI in serial EEPROM */
+
+#endif
diff --git a/drivers/atm/uPD98401.h b/drivers/atm/uPD98401.h
new file mode 100644
index 0000000..0ab3650
--- /dev/null
+++ b/drivers/atm/uPD98401.h
@@ -0,0 +1,292 @@
+/* drivers/atm/uPD98401.h - NEC uPD98401 (SAR) declarations */
+ 
+/* Written 1995 by Werner Almesberger, EPFL LRC */
+ 
+
+#ifndef DRIVERS_ATM_uPD98401_H
+#define DRIVERS_ATM_uPD98401_H
+
+
+#define MAX_CRAM_SIZE	(1 << 18)	/* 2^18 words */
+#define RAM_INCREMENT	1024		/* check in 4 kB increments */
+
+#define uPD98401_PORTS	0x24		/* probably more ? */
+
+
+/*
+ * Commands
+ */
+
+#define uPD98401_OPEN_CHAN	0x20000000 /* open channel */
+#define uPD98401_CHAN_ADDR	0x0003fff8 /*	channel address */
+#define uPD98401_CHAN_ADDR_SHIFT 3
+#define uPD98401_CLOSE_CHAN	0x24000000 /* close channel */
+#define uPD98401_CHAN_RT	0x02000000 /*	RX/TX (0 TX, 1 RX) */
+#define uPD98401_DEACT_CHAN	0x28000000 /* deactivate channel */
+#define uPD98401_TX_READY	0x30000000 /* TX ready */
+#define uPD98401_ADD_BAT	0x34000000 /* add batches */
+#define uPD98401_POOL		0x000f0000 /* pool number */
+#define uPD98401_POOL_SHIFT	16
+#define uPD98401_POOL_NUMBAT	0x0000ffff /* number of batches */
+#define uPD98401_NOP		0x3f000000 /* NOP */
+#define uPD98401_IND_ACC	0x00000000 /* Indirect Access */
+#define uPD98401_IA_RW		0x10000000 /*	Read/Write (0 W, 1 R) */
+#define uPD98401_IA_B3		0x08000000 /*	Byte select, 1 enable */
+#define uPD98401_IA_B2		0x04000000
+#define uPD98401_IA_B1		0x02000000
+#define uPD98401_IA_B0		0x01000000
+#define uPD98401_IA_BALL	0x0f000000 /*   whole longword */
+#define uPD98401_IA_TGT		0x000c0000 /*	Target */
+#define uPD98401_IA_TGT_SHIFT	18
+#define uPD98401_IA_TGT_CM	0	   /*	- Control Memory */
+#define uPD98401_IA_TGT_SAR	1	   /*	- uPD98401 registers */
+#define uPD98401_IA_TGT_PHY	3	   /*   - PHY device */
+#define uPD98401_IA_ADDR	0x0003ffff
+
+/*
+ * Command Register Status
+ */
+
+#define uPD98401_BUSY		0x80000000 /* SAR is busy */
+#define uPD98401_LOCKED		0x40000000 /* SAR is locked by other CPU */
+
+/*
+ * Indications
+ */
+
+/* Normal (AAL5) Receive Indication */
+#define uPD98401_AAL5_UINFO	0xffff0000 /* user-supplied information */
+#define uPD98401_AAL5_UINFO_SHIFT 16
+#define uPD98401_AAL5_SIZE	0x0000ffff /* PDU size (in _CELLS_ !!) */
+#define uPD98401_AAL5_CHAN	0x7fff0000 /* Channel number */
+#define uPD98401_AAL5_CHAN_SHIFT	16
+#define uPD98401_AAL5_ERR	0x00008000 /* Error indication */
+#define uPD98401_AAL5_CI	0x00004000 /* Congestion Indication */
+#define uPD98401_AAL5_CLP	0x00002000 /* CLP (>= 1 cell had CLP=1) */
+#define uPD98401_AAL5_ES	0x00000f00 /* Error Status */
+#define uPD98401_AAL5_ES_SHIFT	8
+#define uPD98401_AAL5_ES_NONE	0	   /*	No error */
+#define uPD98401_AAL5_ES_FREE	1	   /*	Receiver free buf underflow */
+#define uPD98401_AAL5_ES_FIFO	2	   /*	Receiver FIFO overrun */
+#define uPD98401_AAL5_ES_TOOBIG	3	   /*	Maximum length violation */
+#define uPD98401_AAL5_ES_CRC	4	   /*	CRC error */
+#define uPD98401_AAL5_ES_ABORT	5	   /*	User abort */
+#define uPD98401_AAL5_ES_LENGTH	6	   /*   Length violation */
+#define uPD98401_AAL5_ES_T1	7	   /*	T1 error (timeout) */
+#define uPD98401_AAL5_ES_DEACT	8	   /*	Deactivated with DEACT_CHAN */
+#define uPD98401_AAL5_POOL	0x0000001f /* Free buffer pool number */
+
+/* Raw Cell Indication */
+#define uPD98401_RAW_UINFO	uPD98401_AAL5_UINFO
+#define uPD98401_RAW_UINFO_SHIFT uPD98401_AAL5_UINFO_SHIFT
+#define uPD98401_RAW_HEC	0x000000ff /* HEC */
+#define uPD98401_RAW_CHAN	uPD98401_AAL5_CHAN
+#define uPD98401_RAW_CHAN_SHIFT uPD98401_AAL5_CHAN_SHIFT
+
+/* Transmit Indication */
+#define uPD98401_TXI_CONN	0x7fff0000 /* Connection Number */
+#define uPD98401_TXI_CONN_SHIFT	16
+#define uPD98401_TXI_ACTIVE	0x00008000 /* Channel remains active */
+#define uPD98401_TXI_PQP	0x00007fff /* Packet Queue Pointer */
+
+/*
+ * Directly Addressable Registers
+ */
+
+#define uPD98401_GMR	0x00	/* General Mode Register */
+#define uPD98401_GSR	0x01	/* General Status Register */
+#define uPD98401_IMR	0x02	/* Interrupt Mask Register */
+#define uPD98401_RQU	0x03	/* Receive Queue Underrun */
+#define uPD98401_RQA	0x04	/* Receive Queue Alert */
+#define uPD98401_ADDR	0x05	/* Last Burst Address */
+#define uPD98401_VER	0x06	/* Version Number */
+#define uPD98401_SWR	0x07	/* Software Reset */
+#define uPD98401_CMR	0x08	/* Command Register */
+#define uPD98401_CMR_L	0x09	/* Command Register and Lock/Unlock */
+#define uPD98401_CER	0x0a	/* Command Extension Register */
+#define uPD98401_CER_L	0x0b	/* Command Ext Reg and Lock/Unlock */
+
+#define uPD98401_MSH(n) (0x10+(n))	/* Mailbox n Start Address High */
+#define uPD98401_MSL(n) (0x14+(n))	/* Mailbox n Start Address High */
+#define uPD98401_MBA(n) (0x18+(n))	/* Mailbox n Bottom Address */
+#define uPD98401_MTA(n) (0x1c+(n))	/* Mailbox n Tail Address */
+#define uPD98401_MWA(n) (0x20+(n))	/* Mailbox n Write Address */
+
+/* GMR is at 0x00 */
+#define uPD98401_GMR_ONE	0x80000000 /* Must be set to one */
+#define uPD98401_GMR_SLM	0x40000000 /* Address mode (0 word, 1 byte) */
+#define uPD98401_GMR_CPE	0x00008000 /* Control Memory Parity Enable */
+#define uPD98401_GMR_LP		0x00004000 /* Loopback */
+#define uPD98401_GMR_WA		0x00002000 /* Early Bus Write Abort/RDY */
+#define uPD98401_GMR_RA		0x00001000 /* Early Read Abort/RDY */
+#define uPD98401_GMR_SZ		0x00000f00 /* Burst Size Enable */
+#define uPD98401_BURST16	0x00000800 /*	16-word burst */
+#define uPD98401_BURST8		0x00000400 /*	 8-word burst */
+#define uPD98401_BURST4		0x00000200 /*	 4-word burst */
+#define uPD98401_BURST2		0x00000100 /*	 2-word burst */
+#define uPD98401_GMR_AD		0x00000080 /* Address (burst resolution) Disable */
+#define uPD98401_GMR_BO		0x00000040 /* Byte Order (0 little, 1 big) */
+#define uPD98401_GMR_PM		0x00000020 /* Bus Parity Mode (0 byte, 1 word)*/
+#define uPD98401_GMR_PC		0x00000010 /* Bus Parity Control (0even,1odd) */
+#define uPD98401_GMR_BPE	0x00000008 /* Bus Parity Enable */
+#define uPD98401_GMR_DR		0x00000004 /* Receive Drop Mode (0drop,1don't)*/
+#define uPD98401_GMR_SE		0x00000002 /* Shapers Enable */
+#define uPD98401_GMR_RE		0x00000001 /* Receiver Enable */
+
+/* GSR is at 0x01, IMR is at 0x02 */
+#define uPD98401_INT_PI		0x80000000 /* PHY interrupt */
+#define uPD98401_INT_RQA	0x40000000 /* Receive Queue Alert */
+#define uPD98401_INT_RQU	0x20000000 /* Receive Queue Underrun */
+#define uPD98401_INT_RD		0x10000000 /* Receiver Deactivated */
+#define uPD98401_INT_SPE	0x08000000 /* System Parity Error */
+#define uPD98401_INT_CPE	0x04000000 /* Control Memory Parity Error */
+#define uPD98401_INT_SBE	0x02000000 /* System Bus Error */
+#define uPD98401_INT_IND	0x01000000 /* Initialization Done */
+#define uPD98401_INT_RCR	0x0000ff00 /* Raw Cell Received */
+#define uPD98401_INT_RCR_SHIFT	8
+#define uPD98401_INT_MF		0x000000f0 /* Mailbox Full */
+#define uPD98401_INT_MF_SHIFT	4
+#define uPD98401_INT_MM		0x0000000f /* Mailbox Modified */
+
+/* VER is at 0x06 */
+#define uPD98401_MAJOR		0x0000ff00 /* Major revision */
+#define uPD98401_MAJOR_SHIFT	8
+#define uPD98401_MINOR		0x000000ff /* Minor revision */
+
+/*
+ * Indirectly Addressable Registers
+ */
+
+#define uPD98401_IM(n)	(0x40000+(n))	/* Scheduler n I and M */
+#define uPD98401_X(n)	(0x40010+(n))	/* Scheduler n X */
+#define uPD98401_Y(n)	(0x40020+(n))	/* Scheduler n Y */
+#define uPD98401_PC(n)	(0x40030+(n))	/* Scheduler n P, C, p and c */
+#define uPD98401_PS(n)	(0x40040+(n))	/* Scheduler n priority and status */
+
+/* IM contents */
+#define uPD98401_IM_I		0xff000000 /* I */
+#define uPD98401_IM_I_SHIFT	24
+#define uPD98401_IM_M		0x00ffffff /* M */
+
+/* PC contents */
+#define uPD98401_PC_P		0xff000000 /* P */
+#define uPD98401_PC_P_SHIFT	24
+#define uPD98401_PC_C		0x00ff0000 /* C */
+#define uPD98401_PC_C_SHIFT	16
+#define uPD98401_PC_p		0x0000ff00 /* p */
+#define uPD98401_PC_p_SHIFT	8
+#define uPD98401_PC_c		0x000000ff /* c */
+
+/* PS contents */
+#define uPD98401_PS_PRIO	0xf0	/* Priority level (0 high, 15 low) */
+#define uPD98401_PS_PRIO_SHIFT	4
+#define uPD98401_PS_S		0x08	/* Scan - must be 0 (internal) */
+#define uPD98401_PS_R		0x04	/* Round Robin (internal) */
+#define uPD98401_PS_A		0x02	/* Active (internal) */
+#define uPD98401_PS_E		0x01	/* Enabled */
+
+#define uPD98401_TOS	0x40100	/* Top of Stack Control Memory Address */
+#define uPD98401_SMA	0x40200	/* Shapers Control Memory Start Address */
+#define uPD98401_PMA	0x40201	/* Receive Pool Control Memory Start Address */
+#define uPD98401_T1R	0x40300	/* T1 Register */
+#define uPD98401_VRR	0x40301	/* VPI/VCI Reduction Register/Recv. Shutdown */
+#define uPD98401_TSR	0x40302	/* Time-Stamp Register */
+
+/* VRR is at 0x40301 */
+#define uPD98401_VRR_SDM	0x80000000 /* Shutdown Mode */
+#define uPD98401_VRR_SHIFT	0x000f0000 /* VPI/VCI Shift */
+#define uPD98401_VRR_SHIFT_SHIFT 16
+#define uPD98401_VRR_MASK	0x0000ffff /* VPI/VCI mask */
+
+/*
+ * TX packet descriptor
+ */
+
+#define uPD98401_TXPD_SIZE	16	   /* descriptor size (in bytes) */
+
+#define uPD98401_TXPD_V		0x80000000 /* Valid bit */
+#define uPD98401_TXPD_DP	0x40000000 /* Descriptor (1) or Pointer (0) */
+#define uPD98401_TXPD_SM	0x20000000 /* Single (1) or Multiple (0) */
+#define uPD98401_TXPD_CLPM	0x18000000 /* CLP mode */
+#define uPD98401_CLPM_0		0	   /*	00 CLP = 0 */
+#define uPD98401_CLPM_1		3	   /*	11 CLP = 1 */
+#define uPD98401_CLPM_LAST	1	   /*	01 CLP unless last cell */
+#define uPD98401_TXPD_CLPM_SHIFT 27
+#define uPD98401_TXPD_PTI	0x07000000 /* PTI pattern */
+#define uPD98401_TXPD_PTI_SHIFT	24
+#define uPD98401_TXPD_GFC	0x00f00000 /* GFC pattern */
+#define uPD98401_TXPD_GFC_SHIFT	20
+#define uPD98401_TXPD_C10	0x00040000 /* insert CRC-10 */
+#define uPD98401_TXPD_AAL5	0x00020000 /* AAL5 processing */
+#define uPD98401_TXPD_MB	0x00010000 /* TX mailbox number */
+#define uPD98401_TXPD_UU	0x0000ff00 /* CPCS-UU */
+#define uPD98401_TXPD_UU_SHIFT	8
+#define uPD98401_TXPD_CPI	0x000000ff /* CPI */
+
+/*
+ * TX buffer descriptor
+ */
+
+#define uPD98401_TXBD_SIZE	8	   /* descriptor size (in bytes) */
+
+#define uPD98401_TXBD_LAST	0x80000000 /* last buffer in packet */
+
+/*
+ * TX VC table
+ */
+
+/* 1st word has the same structure as in a TX packet descriptor */
+#define uPD98401_TXVC_L		0x80000000 /* last buffer */
+#define uPD98401_TXVC_SHP	0x0f000000 /* shaper number */
+#define uPD98401_TXVC_SHP_SHIFT	24
+#define uPD98401_TXVC_VPI	0x00ff0000 /* VPI */
+#define uPD98401_TXVC_VPI_SHIFT	16
+#define uPD98401_TXVC_VCI	0x0000ffff /* VCI */
+#define uPD98401_TXVC_QRP	6	   /* Queue Read Pointer is in word 6 */
+
+/*
+ * RX free buffer pools descriptor
+ */
+
+#define uPD98401_RXFP_ALERT	0x70000000 /* low water mark */
+#define uPD98401_RXFP_ALERT_SHIFT 28
+#define uPD98401_RXFP_BFSZ	0x0f000000 /* buffer size, 64*2^n */
+#define uPD98401_RXFP_BFSZ_SHIFT 24
+#define uPD98401_RXFP_BTSZ	0x00ff0000 /* batch size, n+1 */
+#define uPD98401_RXFP_BTSZ_SHIFT 16
+#define uPD98401_RXFP_REMAIN	0x0000ffff /* remaining batches in pool */
+
+/*
+ * RX VC table
+ */
+
+#define uPD98401_RXVC_BTSZ	0xff000000 /* remaining free buffers in batch */
+#define uPD98401_RXVC_BTSZ_SHIFT 24
+#define uPD98401_RXVC_MB	0x00200000 /* RX mailbox number */
+#define uPD98401_RXVC_POOL	0x001f0000 /* free buffer pool number */
+#define uPD98401_RXVC_POOL_SHIFT 16
+#define uPD98401_RXVC_UINFO	0x0000ffff /* user-supplied information */
+#define uPD98401_RXVC_T1	0xffff0000 /* T1 timestamp */
+#define uPD98401_RXVC_T1_SHIFT	16
+#define uPD98401_RXVC_PR	0x00008000 /* Packet Reception, 1 if busy */
+#define uPD98401_RXVC_DR	0x00004000 /* FIFO Drop */
+#define uPD98401_RXVC_OD	0x00001000 /* Drop OAM cells */
+#define uPD98401_RXVC_AR	0x00000800 /* AAL5 or raw cell; 1 if AAL5 */
+#define uPD98401_RXVC_MAXSEG	0x000007ff /* max number of segments per PDU */
+#define uPD98401_RXVC_REM	0xfffe0000 /* remaining words in curr buffer */
+#define uPD98401_RXVC_REM_SHIFT	17
+#define uPD98401_RXVC_CLP	0x00010000 /* CLP received */
+#define uPD98401_RXVC_BFA	0x00008000 /* Buffer Assigned */
+#define uPD98401_RXVC_BTA	0x00004000 /* Batch Assigned */
+#define uPD98401_RXVC_CI	0x00002000 /* Congestion Indication */
+#define uPD98401_RXVC_DD	0x00001000 /* Dropping incoming cells */
+#define uPD98401_RXVC_DP	0x00000800 /* like PR ? */
+#define uPD98401_RXVC_CURSEG	0x000007ff /* Current Segment count */
+
+/*
+ * RX lookup table
+ */
+
+#define uPD98401_RXLT_ENBL	0x8000	   /* Enable */
+
+#endif
diff --git a/drivers/atm/uPD98402.c b/drivers/atm/uPD98402.c
new file mode 100644
index 0000000..9504cce
--- /dev/null
+++ b/drivers/atm/uPD98402.c
@@ -0,0 +1,265 @@
+/* drivers/atm/uPD98402.c - NEC uPD98402 (PHY) declarations */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#include <linux/module.h>
+#include <linux/sched.h> /* for jiffies */
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#include "uPD98402.h"
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct uPD98402_priv {
+	struct k_sonet_stats sonet_stats;/* link diagnostics */
+	unsigned char framing;		/* SONET/SDH framing */
+	int loop_mode;			/* loopback mode */
+	spinlock_t lock;
+};
+
+
+#define PRIV(dev) ((struct uPD98402_priv *) dev->phy_data)
+
+#define PUT(val,reg) dev->ops->phy_put(dev,val,uPD98402_##reg)
+#define GET(reg) dev->ops->phy_get(dev,uPD98402_##reg)
+
+
+static int fetch_stats(struct atm_dev *dev,struct sonet_stats __user *arg,int zero)
+{
+	struct sonet_stats tmp;
+ 	int error = 0;
+
+	atomic_add(GET(HECCT),&PRIV(dev)->sonet_stats.uncorr_hcs);
+	sonet_copy_stats(&PRIV(dev)->sonet_stats,&tmp);
+	if (arg) error = copy_to_user(arg,&tmp,sizeof(tmp));
+	if (zero && !error) {
+		/* unused fields are reported as -1, but we must not "adjust"
+		   them */
+		tmp.corr_hcs = tmp.tx_cells = tmp.rx_cells = 0;
+		sonet_subtract_stats(&PRIV(dev)->sonet_stats,&tmp);
+	}
+	return error ? -EFAULT : 0;
+}
+
+
+static int set_framing(struct atm_dev *dev,unsigned char framing)
+{
+	static const unsigned char sonet[] = { 1,2,3,0 };
+	static const unsigned char sdh[] = { 1,0,0,2 };
+	const char *set;
+	unsigned long flags;
+ 
+	switch (framing) {
+		case SONET_FRAME_SONET:
+			set = sonet;
+			break;
+		case SONET_FRAME_SDH:
+			set = sdh;
+			break;
+		default:
+			return -EINVAL;
+	}
+	spin_lock_irqsave(&PRIV(dev)->lock, flags);
+	PUT(set[0],C11T);
+	PUT(set[1],C12T);
+	PUT(set[2],C13T);
+	PUT((GET(MDR) & ~uPD98402_MDR_SS_MASK) | (set[3] <<
+	    uPD98402_MDR_SS_SHIFT),MDR);
+	spin_unlock_irqrestore(&PRIV(dev)->lock, flags);
+	return 0;
+}
+
+
+static int get_sense(struct atm_dev *dev,u8 __user *arg)
+{
+	unsigned long flags;
+	unsigned char s[3];
+
+	spin_lock_irqsave(&PRIV(dev)->lock, flags);
+	s[0] = GET(C11R);
+	s[1] = GET(C12R);
+	s[2] = GET(C13R);
+	spin_unlock_irqrestore(&PRIV(dev)->lock, flags);
+	return (put_user(s[0], arg) || put_user(s[1], arg+1) ||
+	    put_user(s[2], arg+2) || put_user(0xff, arg+3) ||
+	    put_user(0xff, arg+4) || put_user(0xff, arg+5)) ? -EFAULT : 0;
+}
+
+
+static int set_loopback(struct atm_dev *dev,int mode)
+{
+	unsigned char mode_reg;
+
+	mode_reg = GET(MDR) & ~(uPD98402_MDR_TPLP | uPD98402_MDR_ALP |
+	    uPD98402_MDR_RPLP);
+	switch (__ATM_LM_XTLOC(mode)) {
+		case __ATM_LM_NONE:
+			break;
+		case __ATM_LM_PHY:
+			mode_reg |= uPD98402_MDR_TPLP;
+			break;
+		case __ATM_LM_ATM:
+			mode_reg |= uPD98402_MDR_ALP;
+			break;
+		default:
+			return -EINVAL;
+	}
+	switch (__ATM_LM_XTRMT(mode)) {
+		case __ATM_LM_NONE:
+			break;
+		case __ATM_LM_PHY:
+			mode_reg |= uPD98402_MDR_RPLP;
+			break;
+		default:
+			return -EINVAL;
+	}
+	PUT(mode_reg,MDR);
+	PRIV(dev)->loop_mode = mode;
+	return 0;
+}
+
+
+static int uPD98402_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	switch (cmd) {
+
+		case SONET_GETSTATZ:
+                case SONET_GETSTAT:
+			return fetch_stats(dev,arg, cmd == SONET_GETSTATZ);
+		case SONET_SETFRAMING:
+			return set_framing(dev, (int)(unsigned long)arg);
+		case SONET_GETFRAMING:
+			return put_user(PRIV(dev)->framing,(int __user *)arg) ?
+			    -EFAULT : 0;
+		case SONET_GETFRSENSE:
+			return get_sense(dev,arg);
+		case ATM_SETLOOP:
+			return set_loopback(dev, (int)(unsigned long)arg);
+		case ATM_GETLOOP:
+			return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ?
+			    -EFAULT : 0;
+		case ATM_QUERYLOOP:
+			return put_user(ATM_LM_LOC_PHY | ATM_LM_LOC_ATM |
+			    ATM_LM_RMT_PHY,(int __user *)arg) ? -EFAULT : 0;
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+
+#define ADD_LIMITED(s,v) \
+    { atomic_add(GET(v),&PRIV(dev)->sonet_stats.s); \
+    if (atomic_read(&PRIV(dev)->sonet_stats.s) < 0) \
+	atomic_set(&PRIV(dev)->sonet_stats.s,INT_MAX); }
+
+
+static void stat_event(struct atm_dev *dev)
+{
+	unsigned char events;
+
+	events = GET(PCR);
+	if (events & uPD98402_PFM_PFEB) ADD_LIMITED(path_febe,PFECB);
+	if (events & uPD98402_PFM_LFEB) ADD_LIMITED(line_febe,LECCT);
+	if (events & uPD98402_PFM_B3E) ADD_LIMITED(path_bip,B3ECT);
+	if (events & uPD98402_PFM_B2E) ADD_LIMITED(line_bip,B2ECT);
+	if (events & uPD98402_PFM_B1E) ADD_LIMITED(section_bip,B1ECT);
+}
+
+
+#undef ADD_LIMITED
+
+
+static void uPD98402_int(struct atm_dev *dev)
+{
+	static unsigned long silence = 0;
+	unsigned char reason;
+
+	while ((reason = GET(PICR))) {
+		if (reason & uPD98402_INT_LOS)
+			printk(KERN_NOTICE "%s(itf %d): signal lost\n",
+			    dev->type,dev->number);
+		if (reason & uPD98402_INT_PFM) stat_event(dev);
+		if (reason & uPD98402_INT_PCO) {
+			(void) GET(PCOCR); /* clear interrupt cause */
+			atomic_add(GET(HECCT),
+			    &PRIV(dev)->sonet_stats.uncorr_hcs);
+		}
+		if ((reason & uPD98402_INT_RFO) && 
+		    (time_after(jiffies, silence) || silence == 0)) {
+			printk(KERN_WARNING "%s(itf %d): uPD98402 receive "
+			    "FIFO overflow\n",dev->type,dev->number);
+			silence = (jiffies+HZ/2)|1;
+		}
+	}
+}
+
+
+static int uPD98402_start(struct atm_dev *dev)
+{
+	DPRINTK("phy_start\n");
+	if (!(dev->dev_data = kmalloc(sizeof(struct uPD98402_priv),GFP_KERNEL)))
+		return -ENOMEM;
+	spin_lock_init(&PRIV(dev)->lock);
+	memset(&PRIV(dev)->sonet_stats,0,sizeof(struct k_sonet_stats));
+	(void) GET(PCR); /* clear performance events */
+	PUT(uPD98402_PFM_FJ,PCMR); /* ignore frequency adj */
+	(void) GET(PCOCR); /* clear overflows */
+	PUT(~uPD98402_PCO_HECC,PCOMR);
+	(void) GET(PICR); /* clear interrupts */
+	PUT(~(uPD98402_INT_PFM | uPD98402_INT_ALM | uPD98402_INT_RFO |
+	  uPD98402_INT_LOS),PIMR); /* enable them */
+	(void) fetch_stats(dev,NULL,1); /* clear kernel counters */
+	atomic_set(&PRIV(dev)->sonet_stats.corr_hcs,-1);
+	atomic_set(&PRIV(dev)->sonet_stats.tx_cells,-1);
+	atomic_set(&PRIV(dev)->sonet_stats.rx_cells,-1);
+	return 0;
+}
+
+
+static int uPD98402_stop(struct atm_dev *dev)
+{
+	/* let SAR driver worry about stopping interrupts */
+	kfree(PRIV(dev));
+	return 0;
+}
+
+
+static const struct atmphy_ops uPD98402_ops = {
+	.start		= uPD98402_start,
+	.ioctl		= uPD98402_ioctl,
+	.interrupt	= uPD98402_int,
+	.stop		= uPD98402_stop,
+};
+
+
+int uPD98402_init(struct atm_dev *dev)
+{
+DPRINTK("phy_init\n");
+	dev->phy = &uPD98402_ops;
+	return 0;
+}
+
+
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(uPD98402_init);
+
+static __init int uPD98402_module_init(void)
+{
+	return 0;
+}
+module_init(uPD98402_module_init);
+/* module_exit not defined so not unloadable */
diff --git a/drivers/atm/uPD98402.h b/drivers/atm/uPD98402.h
new file mode 100644
index 0000000..c947214
--- /dev/null
+++ b/drivers/atm/uPD98402.h
@@ -0,0 +1,106 @@
+/* drivers/atm/uPD98402.h - NEC uPD98402 (PHY) declarations */
+ 
+/* Written 1995 by Werner Almesberger, EPFL LRC */
+
+
+#ifndef DRIVERS_ATM_uPD98402_H
+#define DRIVERS_ATM_uPD98402_H
+
+/*
+ * Registers
+ */
+
+#define uPD98402_CMR		0x00	/* Command Register */
+#define uPD98402_MDR		0x01	/* Mode Register */
+#define uPD98402_PICR		0x02	/* PHY Interrupt Cause Register */
+#define uPD98402_PIMR		0x03	/* PHY Interrupt Mask Register */
+#define uPD98402_ACR		0x04	/* Alarm Cause Register */
+#define uPD98402_ACMR		0x05	/* Alarm Cause Mask Register */
+#define uPD98402_PCR		0x06	/* Performance Cause Register */
+#define uPD98402_PCMR		0x07	/* Performance Cause Mask Register */
+#define uPD98402_IACM		0x08	/* Internal Alarm Cause Mask Register */
+#define uPD98402_B1ECT		0x09	/* B1 Error Count Register */
+#define uPD98402_B2ECT		0x0a	/* B2 Error Count Register */
+#define uPD98402_B3ECT		0x0b	/* B3 Error Count Regster */
+#define uPD98402_PFECB		0x0c	/* Path FEBE Count Register */
+#define uPD98402_LECCT		0x0d	/* Line FEBE Count Register */
+#define uPD98402_HECCT		0x0e	/* HEC Error Count Register */
+#define uPD98402_FJCT		0x0f	/* Frequence Justification Count Reg */
+#define uPD98402_PCOCR		0x10	/* Perf. Counter Overflow Cause Reg */
+#define uPD98402_PCOMR		0x11	/* Perf. Counter Overflow Mask Reg */
+#define uPD98402_C11T		0x20	/* C11T Data Register */
+#define uPD98402_C12T		0x21	/* C12T Data Register */
+#define uPD98402_C13T		0x22	/* C13T Data Register */
+#define uPD98402_F1T		0x23	/* F1T Data Register */
+#define uPD98402_K2T		0x25	/* K2T Data Register */
+#define uPD98402_C2T		0x26	/* C2T Data Register */
+#define uPD98402_F2T		0x27	/* F2T Data Register */
+#define uPD98402_C11R		0x30	/* C11T Data Register */
+#define uPD98402_C12R		0x31	/* C12T Data Register */
+#define uPD98402_C13R		0x32	/* C13T Data Register */
+#define uPD98402_F1R		0x33	/* F1T Data Register */
+#define uPD98402_K2R		0x35	/* K2T Data Register */
+#define uPD98402_C2R		0x36	/* C2T Data Register */
+#define uPD98402_F2R		0x37	/* F2T Data Register */
+
+/* CMR is at 0x00 */
+#define uPD98402_CMR_PFRF	0x01	/* Send path FERF */
+#define uPD98402_CMR_LFRF	0x02	/* Send line FERF */
+#define uPD98402_CMR_PAIS	0x04	/* Send path AIS */
+#define uPD98402_CMR_LAIS	0x08	/* Send line AIS */
+
+/* MDR is at 0x01 */
+#define uPD98402_MDR_ALP	0x01	/* ATM layer loopback */
+#define uPD98402_MDR_TPLP	0x02	/* PMD loopback, to host */
+#define uPD98402_MDR_RPLP	0x04	/* PMD loopback, to network */
+#define uPD98402_MDR_SS0	0x08	/* SS0 */
+#define uPD98402_MDR_SS1	0x10	/* SS1 */
+#define uPD98402_MDR_SS_MASK	0x18	/* mask */
+#define uPD98402_MDR_SS_SHIFT	3	/* shift */
+#define uPD98402_MDR_HEC	0x20	/* disable HEC inbound processing */
+#define uPD98402_MDR_FSR	0x40	/* disable frame scrambler */
+#define uPD98402_MDR_CSR	0x80	/* disable cell scrambler */
+
+/* PICR is at 0x02, PIMR is at 0x03 */
+#define uPD98402_INT_PFM	0x01	/* performance counter has changed */
+#define uPD98402_INT_ALM	0x02	/* line fault */
+#define uPD98402_INT_RFO	0x04	/* receive FIFO overflow */
+#define uPD98402_INT_PCO	0x08	/* performance counter overflow */
+#define uPD98402_INT_OTD	0x20	/* OTD has occurred */
+#define uPD98402_INT_LOS	0x40	/* Loss Of Signal */
+#define uPD98402_INT_LOF	0x80	/* Loss Of Frame */
+
+/* ACR is as 0x04, ACMR is at 0x05 */
+#define uPD98402_ALM_PFRF	0x01	/* path FERF */
+#define uPD98402_ALM_LFRF	0x02	/* line FERF */
+#define uPD98402_ALM_PAIS	0x04	/* path AIS */
+#define uPD98402_ALM_LAIS	0x08	/* line AIS */
+#define uPD98402_ALM_LOD	0x10	/* loss of delineation */
+#define uPD98402_ALM_LOP	0x20	/* loss of pointer */
+#define uPD98402_ALM_OOF	0x40	/* out of frame */
+
+/* PCR is at 0x06, PCMR is at 0x07 */
+#define uPD98402_PFM_PFEB	0x01	/* path FEBE */
+#define uPD98402_PFM_LFEB	0x02	/* line FEBE */
+#define uPD98402_PFM_B3E	0x04	/* B3 error */
+#define uPD98402_PFM_B2E	0x08	/* B2 error */
+#define uPD98402_PFM_B1E	0x10	/* B1 error */
+#define uPD98402_PFM_FJ		0x20	/* frequency justification */
+
+/* IACM is at 0x08 */
+#define uPD98402_IACM_PFRF	0x01	/* don't generate path FERF */
+#define uPD98402_IACM_LFRF	0x02	/* don't generate line FERF */
+
+/* PCOCR is at 0x010, PCOMR is at 0x11 */
+#define uPD98402_PCO_B1EC	0x01	/* B1ECT overflow */
+#define uPD98402_PCO_B2EC	0x02	/* B2ECT overflow */
+#define uPD98402_PCO_B3EC	0x04	/* B3ECT overflow */
+#define uPD98402_PCO_PFBC	0x08	/* PFEBC overflow */
+#define uPD98402_PCO_LFBC	0x10	/* LFEVC overflow */
+#define uPD98402_PCO_HECC	0x20	/* HECCT overflow */
+#define uPD98402_PCO_FJC	0x40	/* FJCT overflow */
+
+
+int uPD98402_init(struct atm_dev *dev);
+
+#endif
diff --git a/drivers/atm/zatm.c b/drivers/atm/zatm.c
new file mode 100644
index 0000000..47a8005
--- /dev/null
+++ b/drivers/atm/zatm.c
@@ -0,0 +1,1646 @@
+/* drivers/atm/zatm.c - ZeitNet ZN122x device driver */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/ioport.h> /* for request_region */
+#include <linux/uio.h>
+#include <linux/init.h>
+#include <linux/atm_zatm.h>
+#include <linux/capability.h>
+#include <linux/bitops.h>
+#include <linux/wait.h>
+#include <asm/byteorder.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+
+#include "uPD98401.h"
+#include "uPD98402.h"
+#include "zeprom.h"
+#include "zatm.h"
+
+
+/*
+ * TODO:
+ *
+ * Minor features
+ *  - support 64 kB SDUs (will have to use multibuffer batches then :-( )
+ *  - proper use of CDV, credit = max(1,CDVT*PCR)
+ *  - AAL0
+ *  - better receive timestamps
+ *  - OAM
+ */
+
+#define ZATM_COPPER	1
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+#ifndef CONFIG_ATM_ZATM_DEBUG
+
+
+#define NULLCHECK(x)
+
+#define EVENT(s,a,b)
+
+
+static void event_dump(void)
+{
+}
+
+
+#else
+
+
+/* 
+ * NULL pointer checking
+ */
+
+#define NULLCHECK(x) \
+  if ((unsigned long) (x) < 0x30) printk(KERN_CRIT #x "==0x%x\n", (int) (x))
+
+/*
+ * Very extensive activity logging. Greatly improves bug detection speed but
+ * costs a few Mbps if enabled.
+ */
+
+#define EV 64
+
+static const char *ev[EV];
+static unsigned long ev_a[EV],ev_b[EV];
+static int ec = 0;
+
+
+static void EVENT(const char *s,unsigned long a,unsigned long b)
+{
+	ev[ec] = s; 
+	ev_a[ec] = a;
+	ev_b[ec] = b;
+	ec = (ec+1) % EV;
+}
+
+
+static void event_dump(void)
+{
+	int n,i;
+
+	printk(KERN_NOTICE "----- event dump follows -----\n");
+	for (n = 0; n < EV; n++) {
+		i = (ec+n) % EV;
+		printk(KERN_NOTICE);
+		printk(ev[i] ? ev[i] : "(null)",ev_a[i],ev_b[i]);
+	}
+	printk(KERN_NOTICE "----- event dump ends here -----\n");
+}
+
+
+#endif /* CONFIG_ATM_ZATM_DEBUG */
+
+
+#define RING_BUSY	1	/* indication from do_tx that PDU has to be
+				   backlogged */
+
+static struct atm_dev *zatm_boards = NULL;
+static unsigned long dummy[2] = {0,0};
+
+
+#define zin_n(r) inl(zatm_dev->base+r*4)
+#define zin(r) inl(zatm_dev->base+uPD98401_##r*4)
+#define zout(v,r) outl(v,zatm_dev->base+uPD98401_##r*4)
+#define zwait while (zin(CMR) & uPD98401_BUSY)
+
+/* RX0, RX1, TX0, TX1 */
+static const int mbx_entries[NR_MBX] = { 1024,1024,1024,1024 };
+static const int mbx_esize[NR_MBX] = { 16,16,4,4 }; /* entry size in bytes */
+
+#define MBX_SIZE(i) (mbx_entries[i]*mbx_esize[i])
+
+
+/*-------------------------------- utilities --------------------------------*/
+
+
+static void zpokel(struct zatm_dev *zatm_dev,u32 value,u32 addr)
+{
+	zwait;
+	zout(value,CER);
+	zout(uPD98401_IND_ACC | uPD98401_IA_BALL |
+	    (uPD98401_IA_TGT_CM << uPD98401_IA_TGT_SHIFT) | addr,CMR);
+}
+
+
+static u32 zpeekl(struct zatm_dev *zatm_dev,u32 addr)
+{
+	zwait;
+	zout(uPD98401_IND_ACC | uPD98401_IA_BALL | uPD98401_IA_RW |
+	  (uPD98401_IA_TGT_CM << uPD98401_IA_TGT_SHIFT) | addr,CMR);
+	zwait;
+	return zin(CER);
+}
+
+
+/*------------------------------- free lists --------------------------------*/
+
+
+/*
+ * Free buffer head structure:
+ *   [0] pointer to buffer (for SAR)
+ *   [1] buffer descr link pointer (for SAR)
+ *   [2] back pointer to skb (for poll_rx)
+ *   [3] data
+ *   ...
+ */
+
+struct rx_buffer_head {
+	u32		buffer;	/* pointer to buffer (for SAR) */
+	u32		link;	/* buffer descriptor link pointer (for SAR) */
+	struct sk_buff	*skb;	/* back pointer to skb (for poll_rx) */
+};
+
+
+static void refill_pool(struct atm_dev *dev,int pool)
+{
+	struct zatm_dev *zatm_dev;
+	struct sk_buff *skb;
+	struct rx_buffer_head *first;
+	unsigned long flags;
+	int align,offset,free,count,size;
+
+	EVENT("refill_pool\n",0,0);
+	zatm_dev = ZATM_DEV(dev);
+	size = (64 << (pool <= ZATM_AAL5_POOL_BASE ? 0 :
+	    pool-ZATM_AAL5_POOL_BASE))+sizeof(struct rx_buffer_head);
+	if (size < PAGE_SIZE) {
+		align = 32; /* for 32 byte alignment */
+		offset = sizeof(struct rx_buffer_head);
+	}
+	else {
+		align = 4096;
+		offset = zatm_dev->pool_info[pool].offset+
+		    sizeof(struct rx_buffer_head);
+	}
+	size += align;
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	free = zpeekl(zatm_dev,zatm_dev->pool_base+2*pool) &
+	    uPD98401_RXFP_REMAIN;
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	if (free >= zatm_dev->pool_info[pool].low_water) return;
+	EVENT("starting ... POOL: 0x%x, 0x%x\n",
+	    zpeekl(zatm_dev,zatm_dev->pool_base+2*pool),
+	    zpeekl(zatm_dev,zatm_dev->pool_base+2*pool+1));
+	EVENT("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]);
+	count = 0;
+	first = NULL;
+	while (free < zatm_dev->pool_info[pool].high_water) {
+		struct rx_buffer_head *head;
+
+		skb = alloc_skb(size,GFP_ATOMIC);
+		if (!skb) {
+			printk(KERN_WARNING DEV_LABEL "(Itf %d): got no new "
+			    "skb (%d) with %d free\n",dev->number,size,free);
+			break;
+		}
+		skb_reserve(skb,(unsigned char *) ((((unsigned long) skb->data+
+		    align+offset-1) & ~(unsigned long) (align-1))-offset)-
+		    skb->data);
+		head = (struct rx_buffer_head *) skb->data;
+		skb_reserve(skb,sizeof(struct rx_buffer_head));
+		if (!first) first = head;
+		count++;
+		head->buffer = virt_to_bus(skb->data);
+		head->link = 0;
+		head->skb = skb;
+		EVENT("enq skb 0x%08lx/0x%08lx\n",(unsigned long) skb,
+		    (unsigned long) head);
+		spin_lock_irqsave(&zatm_dev->lock, flags);
+		if (zatm_dev->last_free[pool])
+			((struct rx_buffer_head *) (zatm_dev->last_free[pool]->
+			    data))[-1].link = virt_to_bus(head);
+		zatm_dev->last_free[pool] = skb;
+		skb_queue_tail(&zatm_dev->pool[pool],skb);
+		spin_unlock_irqrestore(&zatm_dev->lock, flags);
+		free++;
+	}
+	if (first) {
+		spin_lock_irqsave(&zatm_dev->lock, flags);
+		zwait;
+		zout(virt_to_bus(first),CER);
+		zout(uPD98401_ADD_BAT | (pool << uPD98401_POOL_SHIFT) | count,
+		    CMR);
+		spin_unlock_irqrestore(&zatm_dev->lock, flags);
+		EVENT ("POOL: 0x%x, 0x%x\n",
+		    zpeekl(zatm_dev,zatm_dev->pool_base+2*pool),
+		    zpeekl(zatm_dev,zatm_dev->pool_base+2*pool+1));
+		EVENT("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]);
+	}
+}
+
+
+static void drain_free(struct atm_dev *dev,int pool)
+{
+	skb_queue_purge(&ZATM_DEV(dev)->pool[pool]);
+}
+
+
+static int pool_index(int max_pdu)
+{
+	int i;
+
+	if (max_pdu % ATM_CELL_PAYLOAD)
+		printk(KERN_ERR DEV_LABEL ": driver error in pool_index: "
+		    "max_pdu is %d\n",max_pdu);
+	if (max_pdu > 65536) return -1;
+	for (i = 0; (64 << i) < max_pdu; i++);
+	return i+ZATM_AAL5_POOL_BASE;
+}
+
+
+/* use_pool isn't reentrant */
+
+
+static void use_pool(struct atm_dev *dev,int pool)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long flags;
+	int size;
+
+	zatm_dev = ZATM_DEV(dev);
+	if (!(zatm_dev->pool_info[pool].ref_count++)) {
+		skb_queue_head_init(&zatm_dev->pool[pool]);
+		size = pool-ZATM_AAL5_POOL_BASE;
+		if (size < 0) size = 0; /* 64B... */
+		else if (size > 10) size = 10; /* ... 64kB */
+		spin_lock_irqsave(&zatm_dev->lock, flags);
+		zpokel(zatm_dev,((zatm_dev->pool_info[pool].low_water/4) <<
+		    uPD98401_RXFP_ALERT_SHIFT) |
+		    (1 << uPD98401_RXFP_BTSZ_SHIFT) |
+		    (size << uPD98401_RXFP_BFSZ_SHIFT),
+		    zatm_dev->pool_base+pool*2);
+		zpokel(zatm_dev,(unsigned long) dummy,zatm_dev->pool_base+
+		    pool*2+1);
+		spin_unlock_irqrestore(&zatm_dev->lock, flags);
+		zatm_dev->last_free[pool] = NULL;
+		refill_pool(dev,pool);
+	}
+	DPRINTK("pool %d: %d\n",pool,zatm_dev->pool_info[pool].ref_count);
+}
+
+
+static void unuse_pool(struct atm_dev *dev,int pool)
+{
+	if (!(--ZATM_DEV(dev)->pool_info[pool].ref_count))
+		drain_free(dev,pool);
+}
+
+/*----------------------------------- RX ------------------------------------*/
+
+
+#if 0
+static void exception(struct atm_vcc *vcc)
+{
+   static int count = 0;
+   struct zatm_dev *zatm_dev = ZATM_DEV(vcc->dev);
+   struct zatm_vcc *zatm_vcc = ZATM_VCC(vcc);
+   unsigned long *qrp;
+   int i;
+
+   if (count++ > 2) return;
+   for (i = 0; i < 8; i++)
+	printk("TX%d: 0x%08lx\n",i,
+	  zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+i));
+   for (i = 0; i < 5; i++)
+	printk("SH%d: 0x%08lx\n",i,
+	  zpeekl(zatm_dev,uPD98401_IM(zatm_vcc->shaper)+16*i));
+   qrp = (unsigned long *) zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+
+     uPD98401_TXVC_QRP);
+   printk("qrp=0x%08lx\n",(unsigned long) qrp);
+   for (i = 0; i < 4; i++) printk("QRP[%d]: 0x%08lx",i,qrp[i]);
+}
+#endif
+
+
+static const char *err_txt[] = {
+	"No error",
+	"RX buf underflow",
+	"RX FIFO overrun",
+	"Maximum len violation",
+	"CRC error",
+	"User abort",
+	"Length violation",
+	"T1 error",
+	"Deactivated",
+	"???",
+	"???",
+	"???",
+	"???",
+	"???",
+	"???",
+	"???"
+};
+
+
+static void poll_rx(struct atm_dev *dev,int mbx)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long pos;
+	u32 x;
+	int error;
+
+	EVENT("poll_rx\n",0,0);
+	zatm_dev = ZATM_DEV(dev);
+	pos = (zatm_dev->mbx_start[mbx] & ~0xffffUL) | zin(MTA(mbx));
+	while (x = zin(MWA(mbx)), (pos & 0xffff) != x) {
+		u32 *here;
+		struct sk_buff *skb;
+		struct atm_vcc *vcc;
+		int cells,size,chan;
+
+		EVENT("MBX: host 0x%lx, nic 0x%x\n",pos,x);
+		here = (u32 *) pos;
+		if (((pos += 16) & 0xffff) == zatm_dev->mbx_end[mbx])
+			pos = zatm_dev->mbx_start[mbx];
+		cells = here[0] & uPD98401_AAL5_SIZE;
+#if 0
+printk("RX IND: 0x%x, 0x%x, 0x%x, 0x%x\n",here[0],here[1],here[2],here[3]);
+{
+unsigned long *x;
+		printk("POOL: 0x%08x, 0x%08x\n",zpeekl(zatm_dev,
+		      zatm_dev->pool_base),
+		      zpeekl(zatm_dev,zatm_dev->pool_base+1));
+		x = (unsigned long *) here[2];
+		printk("[0..3] = 0x%08lx, 0x%08lx, 0x%08lx, 0x%08lx\n",
+		    x[0],x[1],x[2],x[3]);
+}
+#endif
+		error = 0;
+		if (here[3] & uPD98401_AAL5_ERR) {
+			error = (here[3] & uPD98401_AAL5_ES) >>
+			    uPD98401_AAL5_ES_SHIFT;
+			if (error == uPD98401_AAL5_ES_DEACT ||
+			    error == uPD98401_AAL5_ES_FREE) continue;
+		}
+EVENT("error code 0x%x/0x%x\n",(here[3] & uPD98401_AAL5_ES) >>
+  uPD98401_AAL5_ES_SHIFT,error);
+		skb = ((struct rx_buffer_head *) bus_to_virt(here[2]))->skb;
+		do_gettimeofday(&skb->stamp);
+#if 0
+printk("[-3..0] 0x%08lx 0x%08lx 0x%08lx 0x%08lx\n",((unsigned *) skb->data)[-3],
+  ((unsigned *) skb->data)[-2],((unsigned *) skb->data)[-1],
+  ((unsigned *) skb->data)[0]);
+#endif
+		EVENT("skb 0x%lx, here 0x%lx\n",(unsigned long) skb,
+		    (unsigned long) here);
+#if 0
+printk("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]);
+#endif
+		size = error ? 0 : ntohs(((__be16 *) skb->data)[cells*
+		    ATM_CELL_PAYLOAD/sizeof(u16)-3]);
+		EVENT("got skb 0x%lx, size %d\n",(unsigned long) skb,size);
+		chan = (here[3] & uPD98401_AAL5_CHAN) >>
+		    uPD98401_AAL5_CHAN_SHIFT;
+		if (chan < zatm_dev->chans && zatm_dev->rx_map[chan]) {
+			vcc = zatm_dev->rx_map[chan];
+			if (skb == zatm_dev->last_free[ZATM_VCC(vcc)->pool])
+				zatm_dev->last_free[ZATM_VCC(vcc)->pool] = NULL;
+			skb_unlink(skb);
+		}
+		else {
+			printk(KERN_ERR DEV_LABEL "(itf %d): RX indication "
+			    "for non-existing channel\n",dev->number);
+			size = 0;
+			vcc = NULL;
+			event_dump();
+		}
+		if (error) {
+			static unsigned long silence = 0;
+			static int last_error = 0;
+
+			if (error != last_error ||
+			    time_after(jiffies, silence)  || silence == 0){
+				printk(KERN_WARNING DEV_LABEL "(itf %d): "
+				    "chan %d error %s\n",dev->number,chan,
+				    err_txt[error]);
+				last_error = error;
+				silence = (jiffies+2*HZ)|1;
+			}
+			size = 0;
+		}
+		if (size && (size > cells*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER ||
+		    size <= (cells-1)*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER)) {
+			printk(KERN_ERR DEV_LABEL "(itf %d): size %d with %d "
+			    "cells\n",dev->number,size,cells);
+			size = 0;
+			event_dump();
+		}
+		if (size > ATM_MAX_AAL5_PDU) {
+			printk(KERN_ERR DEV_LABEL "(itf %d): size too big "
+			    "(%d)\n",dev->number,size);
+			size = 0;
+			event_dump();
+		}
+		if (!size) {
+			dev_kfree_skb_irq(skb);
+			if (vcc) atomic_inc(&vcc->stats->rx_err);
+			continue;
+		}
+		if (!atm_charge(vcc,skb->truesize)) {
+			dev_kfree_skb_irq(skb);
+			continue;
+		}
+		skb->len = size;
+		ATM_SKB(skb)->vcc = vcc;
+		vcc->push(vcc,skb);
+		atomic_inc(&vcc->stats->rx);
+	}
+	zout(pos & 0xffff,MTA(mbx));
+#if 0 /* probably a stupid idea */
+	refill_pool(dev,zatm_vcc->pool);
+		/* maybe this saves us a few interrupts */
+#endif
+}
+
+
+static int open_rx_first(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	unsigned long flags;
+	unsigned short chan;
+	int cells;
+
+	DPRINTK("open_rx_first (0x%x)\n",inb_p(0xc053));
+	zatm_dev = ZATM_DEV(vcc->dev);
+	zatm_vcc = ZATM_VCC(vcc);
+	zatm_vcc->rx_chan = 0;
+	if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0;
+	if (vcc->qos.aal == ATM_AAL5) {
+		if (vcc->qos.rxtp.max_sdu > 65464)
+			vcc->qos.rxtp.max_sdu = 65464;
+			/* fix this - we may want to receive 64kB SDUs
+			   later */
+		cells = (vcc->qos.rxtp.max_sdu+ATM_AAL5_TRAILER+
+		    ATM_CELL_PAYLOAD-1)/ATM_CELL_PAYLOAD;
+		zatm_vcc->pool = pool_index(cells*ATM_CELL_PAYLOAD);
+	}
+	else {
+		cells = 1;
+		zatm_vcc->pool = ZATM_AAL0_POOL;
+	}
+	if (zatm_vcc->pool < 0) return -EMSGSIZE;
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	zwait;
+	zout(uPD98401_OPEN_CHAN,CMR);
+	zwait;
+	DPRINTK("0x%x 0x%x\n",zin(CMR),zin(CER));
+	chan = (zin(CMR) & uPD98401_CHAN_ADDR) >> uPD98401_CHAN_ADDR_SHIFT;
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	DPRINTK("chan is %d\n",chan);
+	if (!chan) return -EAGAIN;
+	use_pool(vcc->dev,zatm_vcc->pool);
+	DPRINTK("pool %d\n",zatm_vcc->pool);
+	/* set up VC descriptor */
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	zpokel(zatm_dev,zatm_vcc->pool << uPD98401_RXVC_POOL_SHIFT,
+	    chan*VC_SIZE/4);
+	zpokel(zatm_dev,uPD98401_RXVC_OD | (vcc->qos.aal == ATM_AAL5 ?
+	    uPD98401_RXVC_AR : 0) | cells,chan*VC_SIZE/4+1);
+	zpokel(zatm_dev,0,chan*VC_SIZE/4+2);
+	zatm_vcc->rx_chan = chan;
+	zatm_dev->rx_map[chan] = vcc;
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	return 0;
+}
+
+
+static int open_rx_second(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	unsigned long flags;
+	int pos,shift;
+
+	DPRINTK("open_rx_second (0x%x)\n",inb_p(0xc053));
+	zatm_dev = ZATM_DEV(vcc->dev);
+	zatm_vcc = ZATM_VCC(vcc);
+	if (!zatm_vcc->rx_chan) return 0;
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	/* should also handle VPI @@@ */
+	pos = vcc->vci >> 1;
+	shift = (1-(vcc->vci & 1)) << 4;
+	zpokel(zatm_dev,(zpeekl(zatm_dev,pos) & ~(0xffff << shift)) |
+	    ((zatm_vcc->rx_chan | uPD98401_RXLT_ENBL) << shift),pos);
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	return 0;
+}
+
+
+static void close_rx(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	unsigned long flags;
+	int pos,shift;
+
+	zatm_vcc = ZATM_VCC(vcc);
+	zatm_dev = ZATM_DEV(vcc->dev);
+	if (!zatm_vcc->rx_chan) return;
+	DPRINTK("close_rx\n");
+	/* disable receiver */
+	if (vcc->vpi != ATM_VPI_UNSPEC && vcc->vci != ATM_VCI_UNSPEC) {
+		spin_lock_irqsave(&zatm_dev->lock, flags);
+		pos = vcc->vci >> 1;
+		shift = (1-(vcc->vci & 1)) << 4;
+		zpokel(zatm_dev,zpeekl(zatm_dev,pos) & ~(0xffff << shift),pos);
+		zwait;
+		zout(uPD98401_NOP,CMR);
+		zwait;
+		zout(uPD98401_NOP,CMR);
+		spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	}
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	zwait;
+	zout(uPD98401_DEACT_CHAN | uPD98401_CHAN_RT | (zatm_vcc->rx_chan <<
+	    uPD98401_CHAN_ADDR_SHIFT),CMR);
+	zwait;
+	udelay(10); /* why oh why ... ? */
+	zout(uPD98401_CLOSE_CHAN | uPD98401_CHAN_RT | (zatm_vcc->rx_chan <<
+	    uPD98401_CHAN_ADDR_SHIFT),CMR);
+	zwait;
+	if (!(zin(CMR) & uPD98401_CHAN_ADDR))
+		printk(KERN_CRIT DEV_LABEL "(itf %d): can't close RX channel "
+		    "%d\n",vcc->dev->number,zatm_vcc->rx_chan);
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	zatm_dev->rx_map[zatm_vcc->rx_chan] = NULL;
+	zatm_vcc->rx_chan = 0;
+	unuse_pool(vcc->dev,zatm_vcc->pool);
+}
+
+
+static int start_rx(struct atm_dev *dev)
+{
+	struct zatm_dev *zatm_dev;
+	int size,i;
+
+DPRINTK("start_rx\n");
+	zatm_dev = ZATM_DEV(dev);
+	size = sizeof(struct atm_vcc *)*zatm_dev->chans;
+	zatm_dev->rx_map = (struct atm_vcc **) kmalloc(size,GFP_KERNEL);
+	if (!zatm_dev->rx_map) return -ENOMEM;
+	memset(zatm_dev->rx_map,0,size);
+	/* set VPI/VCI split (use all VCIs and give what's left to VPIs) */
+	zpokel(zatm_dev,(1 << dev->ci_range.vci_bits)-1,uPD98401_VRR);
+	/* prepare free buffer pools */
+	for (i = 0; i <= ZATM_LAST_POOL; i++) {
+		zatm_dev->pool_info[i].ref_count = 0;
+		zatm_dev->pool_info[i].rqa_count = 0;
+		zatm_dev->pool_info[i].rqu_count = 0;
+		zatm_dev->pool_info[i].low_water = LOW_MARK;
+		zatm_dev->pool_info[i].high_water = HIGH_MARK;
+		zatm_dev->pool_info[i].offset = 0;
+		zatm_dev->pool_info[i].next_off = 0;
+		zatm_dev->pool_info[i].next_cnt = 0;
+		zatm_dev->pool_info[i].next_thres = OFF_CNG_THRES;
+	}
+	return 0;
+}
+
+
+/*----------------------------------- TX ------------------------------------*/
+
+
+static int do_tx(struct sk_buff *skb)
+{
+	struct atm_vcc *vcc;
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	u32 *dsc;
+	unsigned long flags;
+
+	EVENT("do_tx\n",0,0);
+	DPRINTK("sending skb %p\n",skb);
+	vcc = ATM_SKB(skb)->vcc;
+	zatm_dev = ZATM_DEV(vcc->dev);
+	zatm_vcc = ZATM_VCC(vcc);
+	EVENT("iovcnt=%d\n",skb_shinfo(skb)->nr_frags,0);
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	if (!skb_shinfo(skb)->nr_frags) {
+		if (zatm_vcc->txing == RING_ENTRIES-1) {
+			spin_unlock_irqrestore(&zatm_dev->lock, flags);
+			return RING_BUSY;
+		}
+		zatm_vcc->txing++;
+		dsc = zatm_vcc->ring+zatm_vcc->ring_curr;
+		zatm_vcc->ring_curr = (zatm_vcc->ring_curr+RING_WORDS) &
+		    (RING_ENTRIES*RING_WORDS-1);
+		dsc[1] = 0;
+		dsc[2] = skb->len;
+		dsc[3] = virt_to_bus(skb->data);
+		mb();
+		dsc[0] = uPD98401_TXPD_V | uPD98401_TXPD_DP | uPD98401_TXPD_SM
+		    | (vcc->qos.aal == ATM_AAL5 ? uPD98401_TXPD_AAL5 : 0 |
+		    (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ?
+		    uPD98401_CLPM_1 : uPD98401_CLPM_0));
+		EVENT("dsc (0x%lx)\n",(unsigned long) dsc,0);
+	}
+	else {
+printk("NONONONOO!!!!\n");
+		dsc = NULL;
+#if 0
+		u32 *put;
+		int i;
+
+		dsc = (u32 *) kmalloc(uPD98401_TXPD_SIZE*2+
+		    uPD98401_TXBD_SIZE*ATM_SKB(skb)->iovcnt,GFP_ATOMIC);
+		if (!dsc) {
+			if (vcc->pop) vcc->pop(vcc,skb);
+			else dev_kfree_skb_irq(skb);
+			return -EAGAIN;
+		}
+		/* @@@ should check alignment */
+		put = dsc+8;
+		dsc[0] = uPD98401_TXPD_V | uPD98401_TXPD_DP |
+		    (vcc->aal == ATM_AAL5 ? uPD98401_TXPD_AAL5 : 0 |
+		    (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ?
+		    uPD98401_CLPM_1 : uPD98401_CLPM_0));
+		dsc[1] = 0;
+		dsc[2] = ATM_SKB(skb)->iovcnt*uPD98401_TXBD_SIZE;
+		dsc[3] = virt_to_bus(put);
+		for (i = 0; i < ATM_SKB(skb)->iovcnt; i++) {
+			*put++ = ((struct iovec *) skb->data)[i].iov_len;
+			*put++ = virt_to_bus(((struct iovec *)
+			    skb->data)[i].iov_base);
+		}
+		put[-2] |= uPD98401_TXBD_LAST;
+#endif
+	}
+	ZATM_PRV_DSC(skb) = dsc;
+	skb_queue_tail(&zatm_vcc->tx_queue,skb);
+	DPRINTK("QRP=0x%08lx\n",zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+
+	  uPD98401_TXVC_QRP));
+	zwait;
+	zout(uPD98401_TX_READY | (zatm_vcc->tx_chan <<
+	    uPD98401_CHAN_ADDR_SHIFT),CMR);
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	EVENT("done\n",0,0);
+	return 0;
+}
+
+
+static inline void dequeue_tx(struct atm_vcc *vcc)
+{
+	struct zatm_vcc *zatm_vcc;
+	struct sk_buff *skb;
+
+	EVENT("dequeue_tx\n",0,0);
+	zatm_vcc = ZATM_VCC(vcc);
+	skb = skb_dequeue(&zatm_vcc->tx_queue);
+	if (!skb) {
+		printk(KERN_CRIT DEV_LABEL "(itf %d): dequeue_tx but not "
+		    "txing\n",vcc->dev->number);
+		return;
+	}
+#if 0 /* @@@ would fail on CLP */
+if (*ZATM_PRV_DSC(skb) != (uPD98401_TXPD_V | uPD98401_TXPD_DP |
+  uPD98401_TXPD_SM | uPD98401_TXPD_AAL5)) printk("@#*$!!!!  (%08x)\n",
+  *ZATM_PRV_DSC(skb));
+#endif
+	*ZATM_PRV_DSC(skb) = 0; /* mark as invalid */
+	zatm_vcc->txing--;
+	if (vcc->pop) vcc->pop(vcc,skb);
+	else dev_kfree_skb_irq(skb);
+	while ((skb = skb_dequeue(&zatm_vcc->backlog)))
+		if (do_tx(skb) == RING_BUSY) {
+			skb_queue_head(&zatm_vcc->backlog,skb);
+			break;
+		}
+	atomic_inc(&vcc->stats->tx);
+	wake_up(&zatm_vcc->tx_wait);
+}
+
+
+static void poll_tx(struct atm_dev *dev,int mbx)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long pos;
+	u32 x;
+
+	EVENT("poll_tx\n",0,0);
+	zatm_dev = ZATM_DEV(dev);
+	pos = (zatm_dev->mbx_start[mbx] & ~0xffffUL) | zin(MTA(mbx));
+	while (x = zin(MWA(mbx)), (pos & 0xffff) != x) {
+		int chan;
+
+#if 1
+		u32 data,*addr;
+
+		EVENT("MBX: host 0x%lx, nic 0x%x\n",pos,x);
+		addr = (u32 *) pos;
+		data = *addr;
+		chan = (data & uPD98401_TXI_CONN) >> uPD98401_TXI_CONN_SHIFT;
+		EVENT("addr = 0x%lx, data = 0x%08x,",(unsigned long) addr,
+		    data);
+		EVENT("chan = %d\n",chan,0);
+#else
+NO !
+		chan = (zatm_dev->mbx_start[mbx][pos >> 2] & uPD98401_TXI_CONN)
+		>> uPD98401_TXI_CONN_SHIFT;
+#endif
+		if (chan < zatm_dev->chans && zatm_dev->tx_map[chan])
+			dequeue_tx(zatm_dev->tx_map[chan]);
+		else {
+			printk(KERN_CRIT DEV_LABEL "(itf %d): TX indication "
+			    "for non-existing channel %d\n",dev->number,chan);
+			event_dump();
+		}
+		if (((pos += 4) & 0xffff) == zatm_dev->mbx_end[mbx])
+			pos = zatm_dev->mbx_start[mbx];
+	}
+	zout(pos & 0xffff,MTA(mbx));
+}
+
+
+/*
+ * BUG BUG BUG: Doesn't handle "new-style" rate specification yet.
+ */
+
+static int alloc_shaper(struct atm_dev *dev,int *pcr,int min,int max,int ubr)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long flags;
+	unsigned long i,m,c;
+	int shaper;
+
+	DPRINTK("alloc_shaper (min = %d, max = %d)\n",min,max);
+	zatm_dev = ZATM_DEV(dev);
+	if (!zatm_dev->free_shapers) return -EAGAIN;
+	for (shaper = 0; !((zatm_dev->free_shapers >> shaper) & 1); shaper++);
+	zatm_dev->free_shapers &= ~1 << shaper;
+	if (ubr) {
+		c = 5;
+		i = m = 1;
+		zatm_dev->ubr_ref_cnt++;
+		zatm_dev->ubr = shaper;
+	}
+	else {
+		if (min) {
+			if (min <= 255) {
+				i = min;
+				m = ATM_OC3_PCR;
+			}
+			else {
+				i = 255;
+				m = ATM_OC3_PCR*255/min;
+			}
+		}
+		else {
+			if (max > zatm_dev->tx_bw) max = zatm_dev->tx_bw;
+			if (max <= 255) {
+				i = max;
+				m = ATM_OC3_PCR;
+			}
+			else {
+				i = 255;
+				m = (ATM_OC3_PCR*255+max-1)/max;
+			}
+		}
+		if (i > m) {
+			printk(KERN_CRIT DEV_LABEL "shaper algorithm botched "
+			    "[%d,%d] -> i=%ld,m=%ld\n",min,max,i,m);
+			m = i;
+		}
+		*pcr = i*ATM_OC3_PCR/m;
+		c = 20; /* @@@ should use max_cdv ! */
+		if ((min && *pcr < min) || (max && *pcr > max)) return -EINVAL;
+		if (zatm_dev->tx_bw < *pcr) return -EAGAIN;
+		zatm_dev->tx_bw -= *pcr;
+	}
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	DPRINTK("i = %d, m = %d, PCR = %d\n",i,m,*pcr);
+	zpokel(zatm_dev,(i << uPD98401_IM_I_SHIFT) | m,uPD98401_IM(shaper));
+	zpokel(zatm_dev,c << uPD98401_PC_C_SHIFT,uPD98401_PC(shaper));
+	zpokel(zatm_dev,0,uPD98401_X(shaper));
+	zpokel(zatm_dev,0,uPD98401_Y(shaper));
+	zpokel(zatm_dev,uPD98401_PS_E,uPD98401_PS(shaper));
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	return shaper;
+}
+
+
+static void dealloc_shaper(struct atm_dev *dev,int shaper)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long flags;
+
+	zatm_dev = ZATM_DEV(dev);
+	if (shaper == zatm_dev->ubr) {
+		if (--zatm_dev->ubr_ref_cnt) return;
+		zatm_dev->ubr = -1;
+	}
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	zpokel(zatm_dev,zpeekl(zatm_dev,uPD98401_PS(shaper)) & ~uPD98401_PS_E,
+	    uPD98401_PS(shaper));
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	zatm_dev->free_shapers |= 1 << shaper;
+}
+
+
+static void close_tx(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	unsigned long flags;
+	int chan;
+
+	zatm_vcc = ZATM_VCC(vcc);
+	zatm_dev = ZATM_DEV(vcc->dev);
+	chan = zatm_vcc->tx_chan;
+	if (!chan) return;
+	DPRINTK("close_tx\n");
+	if (skb_peek(&zatm_vcc->backlog)) {
+		printk("waiting for backlog to drain ...\n");
+		event_dump();
+		wait_event(zatm_vcc->tx_wait, !skb_peek(&zatm_vcc->backlog));
+	}
+	if (skb_peek(&zatm_vcc->tx_queue)) {
+		printk("waiting for TX queue to drain ...\n");
+		event_dump();
+		wait_event(zatm_vcc->tx_wait, !skb_peek(&zatm_vcc->tx_queue));
+	}
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+#if 0
+	zwait;
+	zout(uPD98401_DEACT_CHAN | (chan << uPD98401_CHAN_ADDR_SHIFT),CMR);
+#endif
+	zwait;
+	zout(uPD98401_CLOSE_CHAN | (chan << uPD98401_CHAN_ADDR_SHIFT),CMR);
+	zwait;
+	if (!(zin(CMR) & uPD98401_CHAN_ADDR))
+		printk(KERN_CRIT DEV_LABEL "(itf %d): can't close TX channel "
+		    "%d\n",vcc->dev->number,chan);
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	zatm_vcc->tx_chan = 0;
+	zatm_dev->tx_map[chan] = NULL;
+	if (zatm_vcc->shaper != zatm_dev->ubr) {
+		zatm_dev->tx_bw += vcc->qos.txtp.min_pcr;
+		dealloc_shaper(vcc->dev,zatm_vcc->shaper);
+	}
+	if (zatm_vcc->ring) kfree(zatm_vcc->ring);
+}
+
+
+static int open_tx_first(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	unsigned long flags;
+	u32 *loop;
+	unsigned short chan;
+	int pcr,unlimited;
+
+	DPRINTK("open_tx_first\n");
+	zatm_dev = ZATM_DEV(vcc->dev);
+	zatm_vcc = ZATM_VCC(vcc);
+	zatm_vcc->tx_chan = 0;
+	if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0;
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	zwait;
+	zout(uPD98401_OPEN_CHAN,CMR);
+	zwait;
+	DPRINTK("0x%x 0x%x\n",zin(CMR),zin(CER));
+	chan = (zin(CMR) & uPD98401_CHAN_ADDR) >> uPD98401_CHAN_ADDR_SHIFT;
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	DPRINTK("chan is %d\n",chan);
+	if (!chan) return -EAGAIN;
+	unlimited = vcc->qos.txtp.traffic_class == ATM_UBR &&
+	    (!vcc->qos.txtp.max_pcr || vcc->qos.txtp.max_pcr == ATM_MAX_PCR ||
+	    vcc->qos.txtp.max_pcr >= ATM_OC3_PCR);
+	if (unlimited && zatm_dev->ubr != -1) zatm_vcc->shaper = zatm_dev->ubr;
+	else {
+		if (unlimited) vcc->qos.txtp.max_sdu = ATM_MAX_AAL5_PDU;
+		if ((zatm_vcc->shaper = alloc_shaper(vcc->dev,&pcr,
+		    vcc->qos.txtp.min_pcr,vcc->qos.txtp.max_pcr,unlimited))
+		    < 0) {
+			close_tx(vcc);
+			return zatm_vcc->shaper;
+		}
+		if (pcr > ATM_OC3_PCR) pcr = ATM_OC3_PCR;
+		vcc->qos.txtp.min_pcr = vcc->qos.txtp.max_pcr = pcr;
+	}
+	zatm_vcc->tx_chan = chan;
+	skb_queue_head_init(&zatm_vcc->tx_queue);
+	init_waitqueue_head(&zatm_vcc->tx_wait);
+	/* initialize ring */
+	zatm_vcc->ring = kmalloc(RING_SIZE,GFP_KERNEL);
+	if (!zatm_vcc->ring) return -ENOMEM;
+	memset(zatm_vcc->ring,0,RING_SIZE);
+	loop = zatm_vcc->ring+RING_ENTRIES*RING_WORDS;
+	loop[0] = uPD98401_TXPD_V;
+	loop[1] = loop[2] = 0;
+	loop[3] = virt_to_bus(zatm_vcc->ring);
+	zatm_vcc->ring_curr = 0;
+	zatm_vcc->txing = 0;
+	skb_queue_head_init(&zatm_vcc->backlog);
+	zpokel(zatm_dev,virt_to_bus(zatm_vcc->ring),
+	    chan*VC_SIZE/4+uPD98401_TXVC_QRP);
+	return 0;
+}
+
+
+static int open_tx_second(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	unsigned long flags;
+
+	DPRINTK("open_tx_second\n");
+	zatm_dev = ZATM_DEV(vcc->dev);
+	zatm_vcc = ZATM_VCC(vcc);
+	if (!zatm_vcc->tx_chan) return 0;
+	/* set up VC descriptor */
+	spin_lock_irqsave(&zatm_dev->lock, flags);
+	zpokel(zatm_dev,0,zatm_vcc->tx_chan*VC_SIZE/4);
+	zpokel(zatm_dev,uPD98401_TXVC_L | (zatm_vcc->shaper <<
+	    uPD98401_TXVC_SHP_SHIFT) | (vcc->vpi << uPD98401_TXVC_VPI_SHIFT) |
+	    vcc->vci,zatm_vcc->tx_chan*VC_SIZE/4+1);
+	zpokel(zatm_dev,0,zatm_vcc->tx_chan*VC_SIZE/4+2);
+	spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	zatm_dev->tx_map[zatm_vcc->tx_chan] = vcc;
+	return 0;
+}
+
+
+static int start_tx(struct atm_dev *dev)
+{
+	struct zatm_dev *zatm_dev;
+	int i;
+
+	DPRINTK("start_tx\n");
+	zatm_dev = ZATM_DEV(dev);
+	zatm_dev->tx_map = (struct atm_vcc **) kmalloc(sizeof(struct atm_vcc *)*
+	    zatm_dev->chans,GFP_KERNEL);
+	if (!zatm_dev->tx_map) return -ENOMEM;
+	zatm_dev->tx_bw = ATM_OC3_PCR;
+	zatm_dev->free_shapers = (1 << NR_SHAPERS)-1;
+	zatm_dev->ubr = -1;
+	zatm_dev->ubr_ref_cnt = 0;
+	/* initialize shapers */
+	for (i = 0; i < NR_SHAPERS; i++) zpokel(zatm_dev,0,uPD98401_PS(i));
+	return 0;
+}
+
+
+/*------------------------------- interrupts --------------------------------*/
+
+
+static irqreturn_t zatm_int(int irq,void *dev_id,struct pt_regs *regs)
+{
+	struct atm_dev *dev;
+	struct zatm_dev *zatm_dev;
+	u32 reason;
+	int handled = 0;
+
+	dev = dev_id;
+	zatm_dev = ZATM_DEV(dev);
+	while ((reason = zin(GSR))) {
+		handled = 1;
+		EVENT("reason 0x%x\n",reason,0);
+		if (reason & uPD98401_INT_PI) {
+			EVENT("PHY int\n",0,0);
+			dev->phy->interrupt(dev);
+		}
+		if (reason & uPD98401_INT_RQA) {
+			unsigned long pools;
+			int i;
+
+			pools = zin(RQA);
+			EVENT("RQA (0x%08x)\n",pools,0);
+			for (i = 0; pools; i++) {
+				if (pools & 1) {
+					refill_pool(dev,i);
+					zatm_dev->pool_info[i].rqa_count++;
+				}
+				pools >>= 1;
+			}
+		}
+		if (reason & uPD98401_INT_RQU) {
+			unsigned long pools;
+			int i;
+			pools = zin(RQU);
+			printk(KERN_WARNING DEV_LABEL "(itf %d): RQU 0x%08lx\n",
+			    dev->number,pools);
+			event_dump();
+			for (i = 0; pools; i++) {
+				if (pools & 1) {
+					refill_pool(dev,i);
+					zatm_dev->pool_info[i].rqu_count++;
+				}
+				pools >>= 1;
+			}
+		}
+		/* don't handle RD */
+		if (reason & uPD98401_INT_SPE)
+			printk(KERN_ALERT DEV_LABEL "(itf %d): system parity "
+			    "error at 0x%08x\n",dev->number,zin(ADDR));
+		if (reason & uPD98401_INT_CPE)
+			printk(KERN_ALERT DEV_LABEL "(itf %d): control memory "
+			    "parity error at 0x%08x\n",dev->number,zin(ADDR));
+		if (reason & uPD98401_INT_SBE) {
+			printk(KERN_ALERT DEV_LABEL "(itf %d): system bus "
+			    "error at 0x%08x\n",dev->number,zin(ADDR));
+			event_dump();
+		}
+		/* don't handle IND */
+		if (reason & uPD98401_INT_MF) {
+			printk(KERN_CRIT DEV_LABEL "(itf %d): mailbox full "
+			    "(0x%x)\n",dev->number,(reason & uPD98401_INT_MF)
+			    >> uPD98401_INT_MF_SHIFT);
+			event_dump();
+			    /* @@@ should try to recover */
+		}
+		if (reason & uPD98401_INT_MM) {
+			if (reason & 1) poll_rx(dev,0);
+			if (reason & 2) poll_rx(dev,1);
+			if (reason & 4) poll_tx(dev,2);
+			if (reason & 8) poll_tx(dev,3);
+		}
+		/* @@@ handle RCRn */
+	}
+	return IRQ_RETVAL(handled);
+}
+
+
+/*----------------------------- (E)EPROM access -----------------------------*/
+
+
+static void __devinit eprom_set(struct zatm_dev *zatm_dev,unsigned long value,
+    unsigned short cmd)
+{
+	int error;
+
+	if ((error = pci_write_config_dword(zatm_dev->pci_dev,cmd,value)))
+		printk(KERN_ERR DEV_LABEL ": PCI write failed (0x%02x)\n",
+		    error);
+}
+
+
+static unsigned long __devinit eprom_get(struct zatm_dev *zatm_dev,
+    unsigned short cmd)
+{
+	unsigned int value;
+	int error;
+
+	if ((error = pci_read_config_dword(zatm_dev->pci_dev,cmd,&value)))
+		printk(KERN_ERR DEV_LABEL ": PCI read failed (0x%02x)\n",
+		    error);
+	return value;
+}
+
+
+static void __devinit eprom_put_bits(struct zatm_dev *zatm_dev,
+    unsigned long data,int bits,unsigned short cmd)
+{
+	unsigned long value;
+	int i;
+
+	for (i = bits-1; i >= 0; i--) {
+		value = ZEPROM_CS | (((data >> i) & 1) ? ZEPROM_DI : 0);
+		eprom_set(zatm_dev,value,cmd);
+		eprom_set(zatm_dev,value | ZEPROM_SK,cmd);
+		eprom_set(zatm_dev,value,cmd);
+	}
+}
+
+
+static void __devinit eprom_get_byte(struct zatm_dev *zatm_dev,
+    unsigned char *byte,unsigned short cmd)
+{
+	int i;
+
+	*byte = 0;
+	for (i = 8; i; i--) {
+		eprom_set(zatm_dev,ZEPROM_CS,cmd);
+		eprom_set(zatm_dev,ZEPROM_CS | ZEPROM_SK,cmd);
+		*byte <<= 1;
+		if (eprom_get(zatm_dev,cmd) & ZEPROM_DO) *byte |= 1;
+		eprom_set(zatm_dev,ZEPROM_CS,cmd);
+	}
+}
+
+
+static unsigned char __devinit eprom_try_esi(struct atm_dev *dev,
+    unsigned short cmd,int offset,int swap)
+{
+	unsigned char buf[ZEPROM_SIZE];
+	struct zatm_dev *zatm_dev;
+	int i;
+
+	zatm_dev = ZATM_DEV(dev);
+	for (i = 0; i < ZEPROM_SIZE; i += 2) {
+		eprom_set(zatm_dev,ZEPROM_CS,cmd); /* select EPROM */
+		eprom_put_bits(zatm_dev,ZEPROM_CMD_READ,ZEPROM_CMD_LEN,cmd);
+		eprom_put_bits(zatm_dev,i >> 1,ZEPROM_ADDR_LEN,cmd);
+		eprom_get_byte(zatm_dev,buf+i+swap,cmd);
+		eprom_get_byte(zatm_dev,buf+i+1-swap,cmd);
+		eprom_set(zatm_dev,0,cmd); /* deselect EPROM */
+	}
+	memcpy(dev->esi,buf+offset,ESI_LEN);
+	return memcmp(dev->esi,"\0\0\0\0\0",ESI_LEN); /* assumes ESI_LEN == 6 */
+}
+
+
+static void __devinit eprom_get_esi(struct atm_dev *dev)
+{
+	if (eprom_try_esi(dev,ZEPROM_V1_REG,ZEPROM_V1_ESI_OFF,1)) return;
+	(void) eprom_try_esi(dev,ZEPROM_V2_REG,ZEPROM_V2_ESI_OFF,0);
+}
+
+
+/*--------------------------------- entries ---------------------------------*/
+
+
+static int __init zatm_init(struct atm_dev *dev)
+{
+	struct zatm_dev *zatm_dev;
+	struct pci_dev *pci_dev;
+	unsigned short command;
+	unsigned char revision;
+	int error,i,last;
+	unsigned long t0,t1,t2;
+
+	DPRINTK(">zatm_init\n");
+	zatm_dev = ZATM_DEV(dev);
+	spin_lock_init(&zatm_dev->lock);
+	pci_dev = zatm_dev->pci_dev;
+	zatm_dev->base = pci_resource_start(pci_dev, 0);
+	zatm_dev->irq = pci_dev->irq;
+	if ((error = pci_read_config_word(pci_dev,PCI_COMMAND,&command)) ||
+	    (error = pci_read_config_byte(pci_dev,PCI_REVISION_ID,&revision))) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): init error 0x%02x\n",
+		    dev->number,error);
+		return -EINVAL;
+	}
+	if ((error = pci_write_config_word(pci_dev,PCI_COMMAND,
+	    command | PCI_COMMAND_IO | PCI_COMMAND_MASTER))) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): can't enable IO (0x%02x)"
+		    "\n",dev->number,error);
+		return -EIO;
+	}
+	eprom_get_esi(dev);
+	printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d,base=0x%x,irq=%d,",
+	    dev->number,revision,zatm_dev->base,zatm_dev->irq);
+	/* reset uPD98401 */
+	zout(0,SWR);
+	while (!(zin(GSR) & uPD98401_INT_IND));
+	zout(uPD98401_GMR_ONE /*uPD98401_BURST4*/,GMR);
+	last = MAX_CRAM_SIZE;
+	for (i = last-RAM_INCREMENT; i >= 0; i -= RAM_INCREMENT) {
+		zpokel(zatm_dev,0x55555555,i);
+		if (zpeekl(zatm_dev,i) != 0x55555555) last = i;
+		else {
+			zpokel(zatm_dev,0xAAAAAAAA,i);
+			if (zpeekl(zatm_dev,i) != 0xAAAAAAAA) last = i;
+			else zpokel(zatm_dev,i,i);
+		}
+	}
+	for (i = 0; i < last; i += RAM_INCREMENT)
+		if (zpeekl(zatm_dev,i) != i) break;
+	zatm_dev->mem = i << 2;
+	while (i) zpokel(zatm_dev,0,--i);
+	/* reset again to rebuild memory pointers */
+	zout(0,SWR);
+	while (!(zin(GSR) & uPD98401_INT_IND));
+	zout(uPD98401_GMR_ONE | uPD98401_BURST8 | uPD98401_BURST4 |
+	    uPD98401_BURST2 | uPD98401_GMR_PM | uPD98401_GMR_DR,GMR);
+	/* TODO: should shrink allocation now */
+	printk("mem=%dkB,%s (",zatm_dev->mem >> 10,zatm_dev->copper ? "UTP" :
+	    "MMF");
+	for (i = 0; i < ESI_LEN; i++)
+		printk("%02X%s",dev->esi[i],i == ESI_LEN-1 ? ")\n" : "-");
+	do {
+		unsigned long flags;
+
+		spin_lock_irqsave(&zatm_dev->lock, flags);
+		t0 = zpeekl(zatm_dev,uPD98401_TSR);
+		udelay(10);
+		t1 = zpeekl(zatm_dev,uPD98401_TSR);
+		udelay(1010);
+		t2 = zpeekl(zatm_dev,uPD98401_TSR);
+		spin_unlock_irqrestore(&zatm_dev->lock, flags);
+	}
+	while (t0 > t1 || t1 > t2); /* loop if wrapping ... */
+	zatm_dev->khz = t2-2*t1+t0;
+	printk(KERN_NOTICE DEV_LABEL "(itf %d): uPD98401 %d.%d at %d.%03d "
+	    "MHz\n",dev->number,
+	    (zin(VER) & uPD98401_MAJOR) >> uPD98401_MAJOR_SHIFT,
+            zin(VER) & uPD98401_MINOR,zatm_dev->khz/1000,zatm_dev->khz % 1000);
+	return uPD98402_init(dev);
+}
+
+
+static int __init zatm_start(struct atm_dev *dev)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long curr;
+	int pools,vccs,rx;
+	int error,i,ld;
+
+	DPRINTK("zatm_start\n");
+	zatm_dev = ZATM_DEV(dev);
+	zatm_dev->rx_map = zatm_dev->tx_map = NULL;
+	for (i = 0; i < NR_MBX; i++)
+		zatm_dev->mbx_start[i] = 0;
+	if (request_irq(zatm_dev->irq,&zatm_int,SA_SHIRQ,DEV_LABEL,dev)) {
+		printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n",
+		    dev->number,zatm_dev->irq);
+		return -EAGAIN;
+	}
+	request_region(zatm_dev->base,uPD98401_PORTS,DEV_LABEL);
+	/* define memory regions */
+	pools = NR_POOLS;
+	if (NR_SHAPERS*SHAPER_SIZE > pools*POOL_SIZE)
+		pools = NR_SHAPERS*SHAPER_SIZE/POOL_SIZE;
+	vccs = (zatm_dev->mem-NR_SHAPERS*SHAPER_SIZE-pools*POOL_SIZE)/
+	    (2*VC_SIZE+RX_SIZE);
+	ld = -1;
+	for (rx = 1; rx < vccs; rx <<= 1) ld++;
+	dev->ci_range.vpi_bits = 0; /* @@@ no VPI for now */
+	dev->ci_range.vci_bits = ld;
+	dev->link_rate = ATM_OC3_PCR;
+	zatm_dev->chans = vccs; /* ??? */
+	curr = rx*RX_SIZE/4;
+	DPRINTK("RX pool 0x%08lx\n",curr);
+	zpokel(zatm_dev,curr,uPD98401_PMA); /* receive pool */
+	zatm_dev->pool_base = curr;
+	curr += pools*POOL_SIZE/4;
+	DPRINTK("Shapers 0x%08lx\n",curr);
+	zpokel(zatm_dev,curr,uPD98401_SMA); /* shapers */
+	curr += NR_SHAPERS*SHAPER_SIZE/4;
+	DPRINTK("Free    0x%08lx\n",curr);
+	zpokel(zatm_dev,curr,uPD98401_TOS); /* free pool */
+	printk(KERN_INFO DEV_LABEL "(itf %d): %d shapers, %d pools, %d RX, "
+	    "%ld VCs\n",dev->number,NR_SHAPERS,pools,rx,
+	    (zatm_dev->mem-curr*4)/VC_SIZE);
+	/* create mailboxes */
+	for (i = 0; i < NR_MBX; i++)
+		if (mbx_entries[i]) {
+			unsigned long here;
+
+			here = (unsigned long) kmalloc(2*MBX_SIZE(i),
+			    GFP_KERNEL);
+			if (!here) {
+				error = -ENOMEM;
+				goto out;
+			}
+			if ((here^(here+MBX_SIZE(i))) & ~0xffffUL)/* paranoia */
+				here = (here & ~0xffffUL)+0x10000;
+			zatm_dev->mbx_start[i] = here;
+			if ((here^virt_to_bus((void *) here)) & 0xffff) {
+				printk(KERN_ERR DEV_LABEL "(itf %d): system "
+				    "bus incompatible with driver\n",
+				    dev->number);
+				error = -ENODEV;
+				goto out;
+			}
+			DPRINTK("mbx@0x%08lx-0x%08lx\n",here,here+MBX_SIZE(i));
+			zatm_dev->mbx_end[i] = (here+MBX_SIZE(i)) & 0xffff;
+			zout(virt_to_bus((void *) here) >> 16,MSH(i));
+			zout(virt_to_bus((void *) here),MSL(i));
+			zout((here+MBX_SIZE(i)) & 0xffff,MBA(i));
+			zout(here & 0xffff,MTA(i));
+			zout(here & 0xffff,MWA(i));
+		}
+	error = start_tx(dev);
+	if (error) goto out;
+	error = start_rx(dev);
+	if (error) goto out;
+	error = dev->phy->start(dev);
+	if (error) goto out;
+	zout(0xffffffff,IMR); /* enable interrupts */
+	/* enable TX & RX */
+	zout(zin(GMR) | uPD98401_GMR_SE | uPD98401_GMR_RE,GMR);
+	return 0;
+    out:
+	for (i = 0; i < NR_MBX; i++)
+		if (zatm_dev->mbx_start[i] != 0)
+			kfree((void *) zatm_dev->mbx_start[i]);
+	if (zatm_dev->rx_map != NULL)
+		kfree(zatm_dev->rx_map);
+	if (zatm_dev->tx_map != NULL)
+		kfree(zatm_dev->tx_map);
+	free_irq(zatm_dev->irq, dev);
+	return error;
+}
+
+
+static void zatm_close(struct atm_vcc *vcc)
+{
+        DPRINTK(">zatm_close\n");
+        if (!ZATM_VCC(vcc)) return;
+	clear_bit(ATM_VF_READY,&vcc->flags);
+        close_rx(vcc);
+	EVENT("close_tx\n",0,0);
+        close_tx(vcc);
+        DPRINTK("zatm_close: done waiting\n");
+        /* deallocate memory */
+        kfree(ZATM_VCC(vcc));
+	vcc->dev_data = NULL;
+	clear_bit(ATM_VF_ADDR,&vcc->flags);
+}
+
+
+static int zatm_open(struct atm_vcc *vcc)
+{
+	struct zatm_dev *zatm_dev;
+	struct zatm_vcc *zatm_vcc;
+	short vpi = vcc->vpi;
+	int vci = vcc->vci;
+	int error;
+
+	DPRINTK(">zatm_open\n");
+	zatm_dev = ZATM_DEV(vcc->dev);
+	if (!test_bit(ATM_VF_PARTIAL,&vcc->flags))
+		vcc->dev_data = NULL;
+	if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC)
+		set_bit(ATM_VF_ADDR,&vcc->flags);
+	if (vcc->qos.aal != ATM_AAL5) return -EINVAL; /* @@@ AAL0 */
+	DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n",vcc->dev->number,vcc->vpi,
+	    vcc->vci);
+	if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) {
+		zatm_vcc = kmalloc(sizeof(struct zatm_vcc),GFP_KERNEL);
+		if (!zatm_vcc) {
+			clear_bit(ATM_VF_ADDR,&vcc->flags);
+			return -ENOMEM;
+		}
+		vcc->dev_data = zatm_vcc;
+		ZATM_VCC(vcc)->tx_chan = 0; /* for zatm_close after open_rx */
+		if ((error = open_rx_first(vcc))) {
+	                zatm_close(vcc);
+	                return error;
+	        }
+		if ((error = open_tx_first(vcc))) {
+			zatm_close(vcc);
+			return error;
+	        }
+	}
+	if (vci == ATM_VPI_UNSPEC || vpi == ATM_VCI_UNSPEC) return 0;
+	if ((error = open_rx_second(vcc))) {
+		zatm_close(vcc);
+		return error;
+        }
+	if ((error = open_tx_second(vcc))) {
+		zatm_close(vcc);
+		return error;
+        }
+	set_bit(ATM_VF_READY,&vcc->flags);
+        return 0;
+}
+
+
+static int zatm_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flags)
+{
+	printk("Not yet implemented\n");
+	return -ENOSYS;
+	/* @@@ */
+}
+
+
+static int zatm_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	struct zatm_dev *zatm_dev;
+	unsigned long flags;
+
+	zatm_dev = ZATM_DEV(dev);
+	switch (cmd) {
+		case ZATM_GETPOOLZ:
+			if (!capable(CAP_NET_ADMIN)) return -EPERM;
+			/* fall through */
+		case ZATM_GETPOOL:
+			{
+				struct zatm_pool_info info;
+				int pool;
+
+				if (get_user(pool,
+				    &((struct zatm_pool_req __user *) arg)->pool_num))
+					return -EFAULT;
+				if (pool < 0 || pool > ZATM_LAST_POOL)
+					return -EINVAL;
+				spin_lock_irqsave(&zatm_dev->lock, flags);
+				info = zatm_dev->pool_info[pool];
+				if (cmd == ZATM_GETPOOLZ) {
+					zatm_dev->pool_info[pool].rqa_count = 0;
+					zatm_dev->pool_info[pool].rqu_count = 0;
+				}
+				spin_unlock_irqrestore(&zatm_dev->lock, flags);
+				return copy_to_user(
+				    &((struct zatm_pool_req __user *) arg)->info,
+				    &info,sizeof(info)) ? -EFAULT : 0;
+			}
+		case ZATM_SETPOOL:
+			{
+				struct zatm_pool_info info;
+				int pool;
+
+				if (!capable(CAP_NET_ADMIN)) return -EPERM;
+				if (get_user(pool,
+				    &((struct zatm_pool_req __user *) arg)->pool_num))
+					return -EFAULT;
+				if (pool < 0 || pool > ZATM_LAST_POOL)
+					return -EINVAL;
+				if (copy_from_user(&info,
+				    &((struct zatm_pool_req __user *) arg)->info,
+				    sizeof(info))) return -EFAULT;
+				if (!info.low_water)
+					info.low_water = zatm_dev->
+					    pool_info[pool].low_water;
+				if (!info.high_water)
+					info.high_water = zatm_dev->
+					    pool_info[pool].high_water;
+				if (!info.next_thres)
+					info.next_thres = zatm_dev->
+					    pool_info[pool].next_thres;
+				if (info.low_water >= info.high_water ||
+				    info.low_water < 0)
+					return -EINVAL;
+				spin_lock_irqsave(&zatm_dev->lock, flags);
+				zatm_dev->pool_info[pool].low_water =
+				    info.low_water;
+				zatm_dev->pool_info[pool].high_water =
+				    info.high_water;
+				zatm_dev->pool_info[pool].next_thres =
+				    info.next_thres;
+				spin_unlock_irqrestore(&zatm_dev->lock, flags);
+				return 0;
+			}
+		default:
+        		if (!dev->phy->ioctl) return -ENOIOCTLCMD;
+		        return dev->phy->ioctl(dev,cmd,arg);
+	}
+}
+
+
+static int zatm_getsockopt(struct atm_vcc *vcc,int level,int optname,
+    void __user *optval,int optlen)
+{
+	return -EINVAL;
+}
+
+
+static int zatm_setsockopt(struct atm_vcc *vcc,int level,int optname,
+    void __user *optval,int optlen)
+{
+	return -EINVAL;
+}
+
+static int zatm_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+	int error;
+
+	EVENT(">zatm_send 0x%lx\n",(unsigned long) skb,0);
+	if (!ZATM_VCC(vcc)->tx_chan || !test_bit(ATM_VF_READY,&vcc->flags)) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+	if (!skb) {
+		printk(KERN_CRIT "!skb in zatm_send ?\n");
+		if (vcc->pop) vcc->pop(vcc,skb);
+		return -EINVAL;
+	}
+	ATM_SKB(skb)->vcc = vcc;
+	error = do_tx(skb);
+	if (error != RING_BUSY) return error;
+	skb_queue_tail(&ZATM_VCC(vcc)->backlog,skb);
+	return 0;
+}
+
+
+static void zatm_phy_put(struct atm_dev *dev,unsigned char value,
+    unsigned long addr)
+{
+	struct zatm_dev *zatm_dev;
+
+	zatm_dev = ZATM_DEV(dev);
+	zwait;
+	zout(value,CER);
+	zout(uPD98401_IND_ACC | uPD98401_IA_B0 |
+	    (uPD98401_IA_TGT_PHY << uPD98401_IA_TGT_SHIFT) | addr,CMR);
+}
+
+
+static unsigned char zatm_phy_get(struct atm_dev *dev,unsigned long addr)
+{
+	struct zatm_dev *zatm_dev;
+
+	zatm_dev = ZATM_DEV(dev);
+	zwait;
+	zout(uPD98401_IND_ACC | uPD98401_IA_B0 | uPD98401_IA_RW |
+	  (uPD98401_IA_TGT_PHY << uPD98401_IA_TGT_SHIFT) | addr,CMR);
+	zwait;
+	return zin(CER) & 0xff;
+}
+
+
+static const struct atmdev_ops ops = {
+	.open		= zatm_open,
+	.close		= zatm_close,
+	.ioctl		= zatm_ioctl,
+	.getsockopt	= zatm_getsockopt,
+	.setsockopt	= zatm_setsockopt,
+	.send		= zatm_send,
+	.phy_put	= zatm_phy_put,
+	.phy_get	= zatm_phy_get,
+	.change_qos	= zatm_change_qos,
+};
+
+static int __devinit zatm_init_one(struct pci_dev *pci_dev,
+				   const struct pci_device_id *ent)
+{
+	struct atm_dev *dev;
+	struct zatm_dev *zatm_dev;
+	int ret = -ENOMEM;
+
+	zatm_dev = (struct zatm_dev *) kmalloc(sizeof(*zatm_dev), GFP_KERNEL);
+	if (!zatm_dev) {
+		printk(KERN_EMERG "%s: memory shortage\n", DEV_LABEL);
+		goto out;
+	}
+
+	dev = atm_dev_register(DEV_LABEL, &ops, -1, NULL);
+	if (!dev)
+		goto out_free;
+
+	ret = pci_enable_device(pci_dev);
+	if (ret < 0)
+		goto out_deregister;
+
+	ret = pci_request_regions(pci_dev, DEV_LABEL);
+	if (ret < 0)
+		goto out_disable;
+
+	zatm_dev->pci_dev = pci_dev;
+	dev->dev_data = zatm_dev;
+	zatm_dev->copper = (int)ent->driver_data;
+	if ((ret = zatm_init(dev)) || (ret = zatm_start(dev)))
+		goto out_release;
+
+	pci_set_drvdata(pci_dev, dev);
+	zatm_dev->more = zatm_boards;
+	zatm_boards = dev;
+	ret = 0;
+out:
+	return ret;
+
+out_release:
+	pci_release_regions(pci_dev);
+out_disable:
+	pci_disable_device(pci_dev);
+out_deregister:
+	atm_dev_deregister(dev);
+out_free:
+	kfree(zatm_dev);
+	goto out;
+}
+
+
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id zatm_pci_tbl[] __devinitdata = {
+	{ PCI_VENDOR_ID_ZEITNET, PCI_DEVICE_ID_ZEITNET_1221,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, ZATM_COPPER },
+	{ PCI_VENDOR_ID_ZEITNET, PCI_DEVICE_ID_ZEITNET_1225,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, zatm_pci_tbl);
+
+static struct pci_driver zatm_driver = {
+	.name =		DEV_LABEL,
+	.id_table =	zatm_pci_tbl,
+	.probe =	zatm_init_one,
+};
+
+static int __init zatm_init_module(void)
+{
+	return pci_register_driver(&zatm_driver);
+}
+
+module_init(zatm_init_module);
+/* module_exit not defined so not unloadable */
diff --git a/drivers/atm/zatm.h b/drivers/atm/zatm.h
new file mode 100644
index 0000000..34a0480
--- /dev/null
+++ b/drivers/atm/zatm.h
@@ -0,0 +1,103 @@
+/* drivers/atm/zatm.h - ZeitNet ZN122x device driver declarations */
+
+/* Written 1995-1998 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef DRIVER_ATM_ZATM_H
+#define DRIVER_ATM_ZATM_H
+
+#include <linux/config.h>
+#include <linux/skbuff.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/pci.h>
+
+
+#define DEV_LABEL	"zatm"
+
+#define MAX_AAL5_PDU	10240	/* allocate for AAL5 PDUs of this size */
+#define MAX_RX_SIZE_LD	14	/* ceil(log2((MAX_AAL5_PDU+47)/48)) */
+
+#define LOW_MARK	12	/* start adding new buffers if less than 12 */
+#define HIGH_MARK	30	/* stop adding buffers after reaching 30 */
+#define OFF_CNG_THRES	5	/* threshold for offset changes */
+
+#define RX_SIZE		2	/* RX lookup entry size (in bytes) */
+#define NR_POOLS	32	/* number of free buffer pointers */
+#define POOL_SIZE	8	/* buffer entry size (in bytes) */
+#define NR_SHAPERS	16	/* number of shapers */
+#define SHAPER_SIZE	4	/* shaper entry size (in bytes) */
+#define VC_SIZE		32	/* VC dsc (TX or RX) size (in bytes) */
+
+#define RING_ENTRIES	32	/* ring entries (without back pointer) */
+#define RING_WORDS	4	/* ring element size */
+#define RING_SIZE	(sizeof(unsigned long)*(RING_ENTRIES+1)*RING_WORDS)
+
+#define NR_MBX		4	/* four mailboxes */
+#define MBX_RX_0	0	/* mailbox indices */
+#define MBX_RX_1	1
+#define MBX_TX_0	2
+#define MBX_TX_1	3
+
+struct zatm_vcc {
+	/*-------------------------------- RX part */
+	int rx_chan;			/* RX channel, 0 if none */
+	int pool;			/* free buffer pool */
+	/*-------------------------------- TX part */
+	int tx_chan;			/* TX channel, 0 if none */
+	int shaper;			/* shaper, <0 if none */
+	struct sk_buff_head tx_queue;	/* list of buffers in transit */
+	wait_queue_head_t tx_wait;	/* for close */
+	u32 *ring;			/* transmit ring */
+	int ring_curr;			/* current write position */
+	int txing;			/* number of transmits in progress */
+	struct sk_buff_head backlog;	/* list of buffers waiting for ring */
+};
+
+struct zatm_dev {
+	/*-------------------------------- TX part */
+	int tx_bw;			/* remaining bandwidth */
+	u32 free_shapers;		/* bit set */
+	int ubr;			/* UBR shaper; -1 if none */
+	int ubr_ref_cnt;		/* number of VCs using UBR shaper */
+	/*-------------------------------- RX part */
+	int pool_ref[NR_POOLS];		/* free buffer pool usage counters */
+	volatile struct sk_buff *last_free[NR_POOLS];
+					/* last entry in respective pool */
+	struct sk_buff_head pool[NR_POOLS];/* free buffer pools */
+	struct zatm_pool_info pool_info[NR_POOLS]; /* pool information */
+	/*-------------------------------- maps */
+	struct atm_vcc **tx_map;	/* TX VCCs */
+	struct atm_vcc **rx_map;	/* RX VCCs */
+	int chans;			/* map size, must be 2^n */
+	/*-------------------------------- mailboxes */
+	unsigned long mbx_start[NR_MBX];/* start addresses */
+	u16 mbx_end[NR_MBX];		/* end offset (in bytes) */
+	/*-------------------------------- other pointers */
+	u32 pool_base;			/* Free buffer pool dsc (word addr) */
+	/*-------------------------------- ZATM links */
+	struct atm_dev *more;		/* other ZATM devices */
+	/*-------------------------------- general information */
+	int mem;			/* RAM on board (in bytes) */
+	int khz;			/* timer clock */
+	int copper;			/* PHY type */
+	unsigned char irq;		/* IRQ */
+	unsigned int base;		/* IO base address */
+	struct pci_dev *pci_dev;	/* PCI stuff */
+	spinlock_t lock;
+};
+
+
+#define ZATM_DEV(d) ((struct zatm_dev *) (d)->dev_data)
+#define ZATM_VCC(d) ((struct zatm_vcc *) (d)->dev_data)
+
+
+struct zatm_skb_prv {
+	struct atm_skb_data _;		/* reserved */
+	u32 *dsc;			/* pointer to skb's descriptor */
+};
+
+#define ZATM_PRV_DSC(skb) (((struct zatm_skb_prv *) (skb)->cb)->dsc)
+
+#endif
diff --git a/drivers/atm/zeprom.h b/drivers/atm/zeprom.h
new file mode 100644
index 0000000..019bb82
--- /dev/null
+++ b/drivers/atm/zeprom.h
@@ -0,0 +1,34 @@
+/* drivers/atm/zeprom.h - ZeitNet ZN122x EEPROM (NM93C46) declarations */
+
+/* Written 1995,1996 by Werner Almesberger, EPFL LRC */
+
+
+#ifndef DRIVER_ATM_ZEPROM_H
+#define DRIVER_ATM_ZEPROM_H
+
+/* Different versions use different control registers */
+
+#define ZEPROM_V1_REG	PCI_VENDOR_ID	/* PCI register */
+#define ZEPROM_V2_REG	0x40
+
+/* Bits in contol register */
+
+#define ZEPROM_SK	0x80000000	/* strobe (probably on raising edge) */
+#define ZEPROM_CS	0x40000000	/* Chip Select */
+#define ZEPROM_DI	0x20000000	/* Data Input */
+#define ZEPROM_DO	0x10000000	/* Data Output */
+
+#define ZEPROM_SIZE	32		/* 32 bytes */
+#define ZEPROM_V1_ESI_OFF 24		/* ESI offset in EEPROM (V1) */
+#define ZEPROM_V2_ESI_OFF 4		/* ESI offset in EEPROM (V2) */
+
+#define ZEPROM_CMD_LEN	3		/* commands are three bits */
+#define ZEPROM_ADDR_LEN	6		/* addresses are six bits */
+
+/* Commands (3 bits) */
+
+#define ZEPROM_CMD_READ	6
+
+/* No other commands are needed. */
+
+#endif